Compare commits

...

451 Commits

Author SHA1 Message Date
YunaiV d1cb99c74a feat(im):增加 im 的功能说明 2026-06-01 08:16:37 +08:00
YunaiV b46b1647eb feat(im):合并 im 最新版本到 master 分支 2026-05-31 22:56:55 +08:00
YunaiV 2f7ec5b78f Merge branch 'im' of https://gitee.com/yudaocode/yudao-ui-admin-vue3
# Conflicts:
#	package.json
#	pnpm-lock.yaml
#	src/api/system/user/index.ts
2026-05-31 22:40:16 +08:00
YunaiV 37c70daaaf Merge remote-tracking branch 'origin/master' 2026-05-31 21:51:09 +08:00
YunaiV 944dd34d9e (〃'▽'〃)_v2026_05_发布:新增 WMS 仓储管理系统,完成 Vben5 IoT/MES/WMS 双端适配 2026-05-31 21:51:04 +08:00
YunaiV ff3c7884cc fix(bpm): 完善流程实例打印字段展示
- Vben5 web-antd/web-ele/web-antdv-next 同步支持更多表单字段打印
- Vue3 + Element Plus 流程打印补齐字典、用户、部门、省市区、文件、图片、开关、富文本等字段展示
- 普通字段和流程记录改为安全文本渲染,保留富文本 HTML 展示
- 打印时间改为每次打开弹窗时刷新
- web-ele 补充 ElButton 显式导入,避免运行时组件解析风险
2026-05-31 18:22:15 +08:00
YunaiV 595b8c5bb4 fix(form-create): 修复字典选择器 modelValue 回显兼容
- Vben5 web-ele 使用默认 modelValue 绑定,修正相关注释避免误用 model-value
- Vben5 web-antd 保留 value 绑定并明确组件库差异
- Vue3 DictSelect 显式接收 modelValue 并派发 update:modelValue,避免选择值更新后回显异常
2026-05-31 17:16:17 +08:00
YunaiV e6c0976c52 fix(crm): 修复待办线索漏传归属场景 2026-05-31 09:37:36 +08:00
YunaiV ed31f0d3c1 fix(bpm): 修复审批详情表单与时间线重叠 2026-05-31 09:30:24 +08:00
YunaiV 2e68890c7d fix(iot): 完善场景联动配置必填校验
- Vue3 抽取场景联动校验工具,统一触发器、附加条件和执行器校验
- Vben5 antd/ele 同步场景联动提交前兜底校验
- 补充 CRON 表达式、JSON 参数和动态字段必填校验
- 保留 deviceId=0 表示全部设备的业务语义
2026-05-31 00:36:40 +08:00
芋道源码 8405a07dd0
!880 fix: 修复场景联动配置中所有必填字段缺少了实际的验证
Merge pull request !880 from 熊猫大侠/master-iotscene
2026-05-30 15:13:58 +00:00
YunaiV 1b7fb5ed5d feat(member): 会员增加 email 字段 2026-05-30 22:55:27 +08:00
YunaiV aff1439629 feat: 优化 IoT 告警模板选择
- 后端 mail/sms/notify 模板 simple-list 仅返回启用模板精简字段
- 前端补充 mail/sms/notify 模板 simple-list API 封装
- vue3 与 vben antd/ele 在各自 system 模块封装模板选择组件
- IoT 告警配置按接收类型动态选择短信、邮件、站内信模板
- 补充前端 IotAlertReceiveTypeEnum,替换表单内裸常量
2026-05-30 22:06:01 +08:00
芋道源码 9a4f9b6995
!879 fix: 优化IoT告警配置支持动态选择邮件、短信、站内信的模板,不再依赖templateCode。
Merge pull request !879 from 熊猫大侠/master-iot-message
2026-05-30 12:57:22 +00:00
YunaiV d08413a68e fix(mes): 修改多个组件以使用整数类型的字典选项 2026-05-30 20:38:43 +08:00
YunaiV 89a49cf19c refactor(frontend): 统一当前登录用户 ID 获取入口
- 新增 getCurrentUserId 到 utils/auth
- 替换 IM、CRM、BPM、MES、Mall 等模块中直接读取 userStore.getUser.id 的写法
- 移除 IM 内部 currentUser 工具依赖,统一从全局 auth 工具获取当前用户编号
- 保留 userStore 对昵称、头像、部门等非 ID 字段的读取
2026-05-28 23:38:46 +08:00
YunaiV 763e11eb78 refactor(im): 统一本地存储入口和 store 命名
- 删除 utils/storage.ts,getCurrentUserId 移到 utils/user.ts
- StorageKeys 移到 utils/db.ts,按 localStorage / settings 分组
- db 客户端新增 clearStore;整桶 store 改为 clearStore + 循环 put 单事务
- 业务 store action / getter 统一改为 verbXxxList / verbXxx 风格
- draft API 加 Conversation 前缀;FriendStore loadFriends 改名 loadFriendData
- 卸载 localforage 依赖
2026-05-28 19:49:54 +08:00
YunaiV 664904bd06 refactor(im): 拆分会话消息存储并合并草稿
- 新增 IM IndexedDB DB client,按当前用户初始化本地库
- 将会话与消息拆成 conversations / messages 逐条存储
- 将草稿合并进 Conversation.draft,删除 draftStore
- 优化 pull 批量写入,消息、会话摘要和游标同事务落库
- 统一 store action 命名,清理旧 localStorage key 和 TODO
- 保留 maxId settings 游标,避免本地消息回收后游标回退
2026-05-28 08:39:49 +08:00
YunaiV 811b93d9f1 refactor(im): 拆分会话和消息本地存储
- 新增 IM IndexedDB DB 封装、schema、key helper 和 session guard
- 新增 messageStore,支持消息逐条持久化、分页加载、ack 合并、撤回和回执更新
- 调整 conversationStore 只持久化会话摘要,不再内嵌 messages 数组
- 切换发送、拉取、WebSocket、媒体上传和消息组件到 messageStore
- 增加离开 IM 时的 store 清理和本地存储序列化保护
2026-05-27 23:46:18 +08:00
YunaiV e80e5203a4 feat(mes): 迁移生产报工 antd/ele 并补齐任务选择器 2026-05-26 22:27:27 +08:00
熊猫大侠 c8b132433a
Merge branch 'master' of gitee.com:yudaocode/yudao-ui-admin-vue3 into master-iotscene
Signed-off-by: 熊猫大侠 <1565636758@qq.com>
2026-05-26 06:02:26 +00:00
panda 57596361ef fix: 修复场景联动配置中所有必填字段缺少了实际的验证 2026-05-26 11:22:24 +08:00
YunaiV 685ada8475 fix(mes): 修正安灯迁移的配置角色显示和记录只读字段 2026-05-26 00:08:46 +08:00
YunaiV e1b8370267 fix: 修复 IM 申请与 RTC 边界问题
- 复用好友申请、群申请和群邀请唯一键冲突后的旧记录,并补充测试
- 收敛 RTC 旁观者加入、忙线校验、追加邀请超员和群通话通知逻辑
- 为 RTC 参与者补充房间用户唯一约束与 MySQL 迁移
- 统一群本体管理请求的 id 字段,并同步前端调用
- 修复前端来电活跃态守卫和 LiveKit 重连前断开旧房间
- 清理群成员通知基类命名和相关注释
2026-05-25 20:54:11 +08:00
YunaiV a4dfb717aa fix(im):批量修复群管理、RTC 和消息链路问题
- 修复群管理行锁、管理员角色更新、群主转让、置顶消息并发问题
- 修复好友申请 maxId 游标、重复申请排序、通知类型校验和消息内容结构校验
- 修复消息统计口径、RTC token 鉴权、离会通知、前端拉取取消和媒体重试
- 优化表情批量删除、WebSocket 推送注释、群 READ 字段和相关单测
- 更新 bug_todo、bug_done 和 bug_rejected,剩余 9 个待修
2026-05-25 09:04:25 +08:00
YunaiV 06a008b030 fix(iot): 修复规则数据回显与 JSON 参数校验
- 修复 HTTP 数据目的编辑 URL 回显时机
- 复用 isEmptyVal 校验 JSON 必填参数,允许 0 和 false
2026-05-25 00:44:02 +08:00
YunaiV f3807e30d5 fix(im): 批量修复 P1/P2 问题
- 修复管理端消息内容搜索和私聊双向查询
- 加强 RTC 通话并发状态保护,去除重复接口错误提示
- 支持成员永久禁言
- 脱敏群消息 WebSocket 定向收件人字段
- 更新 IM bug 台账,剩余 P1/P2 共 35 个
2026-05-25 00:28:59 +08:00
YunaiV dc1a582fbd fix(iot): 修复源端 IoT 对齐基线问题
- 收紧物模型标识符前端校验规则
- 修复场景联动设备状态触发器校验
- 修正设备列表状态字段展示
2026-05-25 00:11:19 +08:00
YunaiV 8b06efe5ee fix: 加强 IM 上传 URL 与 RTC 来电载荷校验 2026-05-24 23:41:46 +08:00
YunaiV 309a4bf4d0 fix(im): 强化好友关系、消息历史和前端交互
- 校验群资料字段长度,并在同意好友申请时复验双方用户
- 仅向双向有效好友推送资料更新通知
- WebSocket 推送收件人去重,并忽略空用户编号
- 群聊和私聊历史保留撤回消息记录
- 校验群通话排除发起人后仍需存在被邀请人
- 统一 IM 前端接口参数传递方式
- 抽取全局 URL 安全打开工具,并复用到消息预览
- 防止好友申请同意和拒绝按钮重复操作
- 补充好友、消息、RTC、WebSocket 相关测试
2026-05-24 21:24:15 +08:00
YunaiV 2ede2b371f fix(im): 批量修复 P0 安全边界和通话流程问题
- 拒绝匿名 WebSocket 握手,收紧 RTC 接听和入会忙线校验
- 支持封禁群解散,管理端解散改为独立权限码
- 增加个人表情数量配置、唯一约束和并发重复兜底
- 修复 RTC 异常断开上报、视频远端音频和好友选择大列表渲染
- 让个人表情添加失败透出后端业务错误
- 流转 P0 bug 文档,并按产品取舍记录 apiSecret 默认值不强制拦截
2026-05-24 20:21:00 +08:00
YunaiV 00f273ca77 fix(im):加固好友、群成员与群消息边界流程
- 清理好友重加时的 deleteTime 和历史备注残留
- 清理群成员重入时的 quitTime、muteEndTime 和邀请来源残留
- 允许封禁群成员主动退群,仍拦截已解散群
- 校验群已读游标的消息归属和可见性
- 收窄群消息置顶通知为专用展示对象并同步前端展示
- 回填群成员单查接口的昵称和头像
- 补充相关回归测试并流转 IM bug 状态文档
2026-05-24 18:22:07 +08:00
YunaiV dd009a1de0 优化代码排版 2026-05-24 00:18:49 +08:00
芋道源码 fa42f8e574
Merge pull request #225 from DevDengChao/codex/lint-mall-bpm-misc
chore: fix mall bpm and misc lint
2026-05-23 22:07:05 +08:00
芋道源码 a1c43ed427
Merge pull request #224 from DevDengChao/codex/lint-mp-mes
fix: lint mp and mes views
2026-05-23 22:06:15 +08:00
芋道源码 31a7f6248a
Merge pull request #223 from DevDengChao/codex/lint-iot-ai
chore: fix ai and iot lint
2026-05-23 22:06:03 +08:00
芋道源码 02c0d0cb3b
Merge pull request #222 from DevDengChao/codex/lint-components
fix: lint src components
2026-05-23 21:35:25 +08:00
YunaiV a2fbf5b712 fix: clean up BPMN viewer resize observer 2026-05-23 21:06:20 +08:00
YunaiV cb78c2935d Merge remote-tracking branch 'origin/master' 2026-05-23 18:24:57 +08:00
YunaiV 798318ef7d Merge branch 'master' of https://github.com/yudaocode/yudao-ui-admin-vue3 2026-05-23 18:21:33 +08:00
芋道源码 8971c37059
Merge pull request #221 from HmEJ/feature/bpm
流程图居中显示
2026-05-23 18:21:10 +08:00
芋道源码 7dd7309e9c
!878 fix: 场景联动中设备状态变更配置的参数值(在线-online,离线-offline)与后台实际使用的(在线-1,离线-2)不一致,导致场景不生效。
Merge pull request !878 from 熊猫大侠/master-iot
2026-05-23 09:33:27 +00:00
YunaiV 1612e3e1b6 fix(iot): 场景联动动作类型切换清理逻辑失效
updateActionType 先调 onActionTypeChange(此时 action.type 仍是旧值)
再赋新值,修复 type guard 永远 false 导致切换执行器类型不清空旧
identifier;onActionTypeChange 内恒真的 type !== action.type
简化为 if (action.identifier)
2026-05-22 20:24:58 +08:00
YunaiV 72d8c499a4 feat(im): 修一批管理端统计与成员选择器细节
- 消息趋势 / 用户趋势图表加 loading 态(接口错误由全局拦截器统一提示)
- 群成员选择器 grid 模式补右上角 × 移除按钮
- 统计接口 6 个 API 补全返回值泛型
2026-05-22 20:15:15 +08:00
panda fa653ac887 fix: 优化IoT告警配置支持动态选择邮件、短信、站内信的模板,不再依赖templateCode。 2026-05-22 14:52:50 +08:00
panda 1888757854 fix: 场景联动中设备状态变更配置的参数值(在线-online,离线-offline)与后台实际使用的(在线-1,离线-2)不一致,导致场景不生效。 2026-05-22 10:50:28 +08:00
YunaiV 38ecc4f40c feat(im): 修一批前端性能 / 跨账号防御与侧边栏占位
- friendStore.getFriend 改 friendMap 索引,高频反查从 O(N) 降到 O(1)
- faceStore 加 storeEpoch,切账号后旧表情拉取 / 增删响应不再回写新账号
- friendStore 写路径统一补 epoch 守卫(loadFriendInfo / 单查申请 / 删好友 /
  免打扰 / 置顶 / 拉黑 / 备注),切账号瞬间的旧响应不污染新账号好友状态
- 私聊侧边栏 friend 缺失时给加载占位,替代原本的空白抽屉
2026-05-22 08:38:56 +08:00
YunaiV 9893aedbb2 feat(im): 修一批 WS 健壮性与跨账号防御
- WS 重连改指数退避(1→2→4→8→16→30s + jitter),频率封顶不再固定 3s 形成惊群
- onerror 不再调 reconnect,主动 close 让 onclose 成为唯一重连入口,避免双触计数 +2
- 私聊 / 群消息入口加防御层,senderId / receiverId / 定向 receiverUserIds 不含当前用户的帧直接丢弃
- useMessagePuller 引入 epoch + userId 双重快照,离开 IM / 切账号时旧 pull 写入前自检跳出
- cancelPull 同步清 WS messageBuffer,防止下次进 IM 把旧 session 缓冲帧回放进新 store
2026-05-21 20:02:44 +08:00
YunaiV 5a983bb1eb feat(im): 修一批上传安全与群聊交互问题
- 限制消息媒体上传大小,并让视频独立上传路径复用同一校验
- 禁止发送可执行 / 脚本类文件扩展名
- 切账号时废弃好友 store 未返回请求
- 多选转发过滤撤回 / 系统类消息
- 邀请群成员时前端拦截人数上限
- 允许群管理员 @ 所有人
2026-05-21 17:31:46 +08:00
YunaiV b5bc537f86 feat(alert): simplify alert config loading and display 2026-05-21 17:27:27 +08:00
YunaiV fead282395 feat(im): 修一批正确性 / UX 细节:群名 trim 空、敏感词 / 封禁理由空白校验、默认群名计入创建者、ack 后重算会话摘要、文本重试复用 clientMessageId 防重复 2026-05-21 15:57:46 +08:00
YunaiV 73aa578c9b feat(im): 修管理端 3 处:群消息 atUserNicknames 类型允许 null、移除前端无效的「消息内容」查询入口、表情包宽高加表单校验 2026-05-21 15:10:34 +08:00
YunaiV 7a236b4378 feat(im): 修 L-13/L-16:PagedScroller 加 itemKey 防索引乱位、私聊 Message.targetId 改对端 userId、抽 getPrivateMessagePeerId 收敛 4 处 peer 计算 2026-05-21 14:50:42 +08:00
YunaiV 1015423431 feat(im): 修一批 L 危:window.open 加 noopener、@ 浮层箭头方向跟随定位、置顶展开切群重置、加黑名单取消静默、菜单负坐标兜底、群选择回显跨页补查 2026-05-21 14:25:21 +08:00
YunaiV 3949e0c89f fix(im): 修复 IM 前端批量 UX 状态问题 2026-05-21 13:25:55 +08:00
YunaiV 29b257b8cd feat(im): 修一批状态串扰:群申请列表防同群 WS 推送乱序覆盖、群免打扰同步会话、跨端群已读清 @、联系人 selection 跟随 store、好友申请按钮并发锁 2026-05-21 11:11:36 +08:00
YunaiV 1e08e9fbca feat(im): 修 IM 切账号串号:loadConversations 开头清状态、加 loadedUserId 守门跨账号同 key 误激活、新增 clear() 对齐 friend/group store 2026-05-21 10:06:52 +08:00
YunaiV 69653163b0 feat(im): 修 IM 历史消息切会话串号:loadEarlier 用 getConversationKey 守卫,watch(conversation) 重置分页态 2026-05-21 09:05:44 +08:00
YunaiV 8468d9bf4d feat(im): 修 WebSocket 重复连接:connect 入口检测旧 socket 状态复用 / 重建,disconnect 解绑全部 handler 2026-05-21 08:41:44 +08:00
YunaiV f7cda1fc4e feat(im): 修一组细节:会话 silent 跟随新消息同步、合并末尾刷摘要 + 群 @ 标记、录音 1s 下限、邀请 reload 透 friendIds、pull 游标取最大 id 2026-05-21 01:13:29 +08:00
YunaiV fee633b0c8 feat(iot): 优化代码,尽量使用 ProductStatusEnum 枚举 2026-05-21 00:17:57 +08:00
YunaiV b63492199a feat(im): 对齐微信:免打扰会话改小红点、消息 Tab 二次点击滚动到下一未读、移除工具栏 hover tooltip 2026-05-20 23:57:18 +08:00
YunaiV c6b6e723e0 feat(im): 将 style 尽量多的改成 unocss,ai 友好 2026-05-20 19:38:14 +08:00
YunaiV 0a07d4a2e4 feat(im): 将 style 尽量多的改成 unocss,ai 友好 2026-05-20 13:56:46 +08:00
YunaiV fc812aef26 feat(im): 增加频道消息的已读状态 2026-05-20 01:00:46 +08:00
YunaiV f26c65c03f fix(iot): 固件操作的权限校验 2026-05-20 00:41:32 +08:00
YunaiV 30b963149a feat(im): 修复频道消息的引用展示不对; 2026-05-19 23:57:46 +08:00
YunaiV 9a36cfe933 feat(im): 继续优化频道的各种代码(v4)优化卡片样式 2026-05-19 23:52:11 +08:00
YunaiV 94e5fc00ac feat(im): 继续优化频道的各种代码(v3) 2026-05-19 22:06:38 +08:00
YunaiV 2442a01e48 feat(im): 继续优化频道的各种代码(v2) 2026-05-19 17:48:35 +08:00
YunaiV bfd407d75b feat(im): 继续优化频道的各种代码, 2026-05-19 17:18:48 +08:00
YunaiV b6d123ac72 feat(im): 增加频道的检查 2026-05-19 14:18:08 +08:00
YunaiV 5ebbbf7499 feat(im): 新增频道消息的前端实现 2026-05-19 13:26:32 +08:00
YunaiV 8ad7180c2b fix(CheckOrderForm): remove preselectDisabled option from SKU selection 2026-05-18 21:21:15 +08:00
DevDengChao f5bcaf22f9 chore: fix mall bpm misc lint
Co-authored-by: Codex <codex@openai.com>
2026-05-18 14:19:49 +08:00
DevDengChao 40d762070f fix: lint src components
Co-authored-by: Codex <codex@openai.com>
2026-05-18 14:19:40 +08:00
DevDengChao 7fa9311753 fix lint issues in mp and mes views
Co-authored-by: Codex <codex@openai.com>
2026-05-18 14:18:27 +08:00
DevDengChao 60c74b991e chore: fix ai iot lint
Co-authored-by: Codex <codex@openai.com>
2026-05-18 14:16:58 +08:00
YunaiV c4519a8696 feat(alert): enhance description input to textarea for better usability
feat(movement): add function to retrieve selected inventory keys
2026-05-18 13:30:04 +08:00
YunaiV b52ad0c34b refactor: 前端 IM API 移除 src/api/im/home 中间目录,face / friend / group / message / rtc 直接放在 src/api/im 下 2026-05-18 13:20:43 +08:00
YunaiV 5c2ee259a6 feat(im): 管理后台新增通话记录只读查询(列表 / 详情 / 参与者);im_rtc_participant 增加 call_id 关联 im_rtc_call.id
 feat(im): 管理后台新增通话记录页面(列表 + 详情抽屉 + 参与者表),消息预览补 RTC_CALL_START / END 文案
2026-05-18 12:37:51 +08:00
YunaiV 8329a6a885 feat(im): 振铃超时 Job 单人粒度标 NO_ANSWER + 独立 NO_ANSWER 信令推送
 feat(im): 处理 RTC_CALL(NO_ANSWER) 信令;私聊气泡显示「未接听」
2026-05-18 09:45:32 +08:00
YunaiV 073c54bc1d feat(wms):优化出库的交互,已选择的库存,disabled 掉,体验更好 2026-05-18 08:58:33 +08:00
YunaiV f58d1d88c8 feat(im): 振铃超时 Job 单人粒度标 NO_ANSWER + 独立 NO_ANSWER 信令推送
 feat(im): 处理 RTC_CALL(NO_ANSWER) 信令;私聊气泡显示「未接听」
2026-05-18 08:03:52 +08:00
YunaiV f6963cde37 Merge remote-tracking branch 'origin/master' 2026-05-18 01:03:15 +08:00
YunaiV 0c54bf28b3 fix(iot):物模型编辑回显时,service / event 子字段补数组兜底,避免参数列表绑定 undefined 2026-05-18 01:03:04 +08:00
YunaiV d0cd93de5a chore: fix eslint warnings from vite dev 2026-05-18 00:59:59 +08:00
YunaiV a2d043bc72 fix: normalize scss variable injection on Windows
- normalize injected variables.scss path for Windows Sass
- skip variable-defining scss files to avoid duplicate global variables
2026-05-18 00:47:16 +08:00
YunaiV 7622a44bbb fix:尝试修复 windows 的兼容性:additionalData: `@use "${pathResolve('src/styles/variables.scss')}" as *;`, 2026-05-18 00:31:55 +08:00
YunaiV 12a41da241 feat(im): 修复摄像头关闭后画面卡在最后一帧(pickStream 短路 mute 状态) 2026-05-18 00:26:14 +08:00
YunaiV ae7f3a8bc5 feat(im): 群通话本端拒绝 / 挂断后立即从胶囊条移除自己,无需等后端推回 2026-05-18 00:12:28 +08:00
YunaiV b9b085f1ee feat(im): 群通话本端拒绝 / 挂断后立即从胶囊条移除自己,无需等后端推回 2026-05-18 00:00:53 +08:00
YunaiV dc318c8e75 feat(im): 通话事件接入会话列表预览(私聊补 START 入消息流);文案统一「语音通话」 2026-05-17 22:26:07 +08:00
YunaiV 6cd49f0ce4 feat(im): 修复主叫取消通话时摄像头偶发未关闭的竞态 2026-05-17 21:02:54 +08:00
YunaiV 85207bec98 feat(im): 群通话发起/接听时主动同步胶囊条,避免依赖 webhook 延迟 2026-05-17 20:27:43 +08:00
YunaiV 46c436e0df feat(wms):将首页的枚举值去掉,统一合并到 constants 里,更聚焦点 2026-05-17 18:17:30 +08:00
YunaiV 5d222bdf48 feat(im): 通话窗扬声器开关 + 按钮关闭态统一深色样式 + 群通话支持刷新后重新加入
- useLiveKitRoom 增加 speakerEnabled 状态 + setSpeakerEnabled;audio 元素 :muted 联动,实现扬声器实际开关
- mic / speaker / camera / 屏幕共享 4 个按钮关闭态统一 bg-white/15 深色(之前一直 bg-white 像「开」)
- speaker / camera / 屏幕共享 关闭态 icon 借用 tabler:volume-off / video-off / device-laptop-off 显斜线(ant-design 缺 muted 变体)
- RtcGroupCallBanner 修复刷新后无法重新加入:按钮文案改为「已在通话中 / 重新加入 / 加入」三态;按钮文字色锁定深色防暗色主题不可见
- RtcCallIncoming 对齐微信样式:右上角小条 + 横排(头像 / 名 / 按钮);群聊带「通话成员」头像行
- RtcCallRunning UnoCSS 重写 + 接收 isGroup prop(去 conversationType 派生)
- RtcCallParticipantTile UnoCSS 重写 + speakerEnabled 透传静音
- 注释 / UI 文案半角省略号 → 全角……;watcher 参数 hidden → suppressTick
2026-05-17 17:37:37 +08:00
YunaiV ceb1aa9bce Merge remote-tracking branch 'origin/master' 2026-05-17 17:30:48 +08:00
YunaiV 26a3b87114 feat(iot):移除 DeviceTableSelect.vue、ProductTableSelect.vue 无用组件 2026-05-17 17:30:38 +08:00
YunaiV 03d0ce800d feat(im): 优化群邀请的 running 的交互 2026-05-17 16:46:10 +08:00
YunaiV a3f89d686c Merge pull request #215 from yudaocode/upgrade
# Conflicts:
#	build/vite/optimize.ts
#	package-lock.json
#	package.json
#	pnpm-lock.yaml
2026-05-17 13:56:29 +08:00
YunaiV 68922ebf02 feat(im): 优化群邀请的 incoming、inviting 的交互 2026-05-17 10:36:01 +08:00
YunaiV e629ac3825 feat(im): 增加群 call title(进度)情况 2026-05-16 21:39:44 +08:00
YunaiV 8b4351e4f3 feat(im): 增加群邀请电话的 dialog 2026-05-16 21:39:23 +08:00
YunaiV 28473434da fix: reduce low-risk vue-tsc errors in erp forms
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:48:35 +08:00
YunaiV 1f47e3c9d6 feat:增加 ts:check 优化,避免 ai 校验 oom 报错 2026-05-16 17:47:11 +08:00
YunaiV 4f4c64cfff feat(wms):调整 README.md 2026-05-16 15:09:42 +08:00
YunaiV 9a49c2df20 feat(wms):调整 README.md 2026-05-16 14:56:10 +08:00
YunaiV 9b8b1322b7 Merge branch 'wms' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 2026-05-16 14:41:33 +08:00
YunaiV ecb15b6481 feat:更新 README.md 2026-05-16 14:40:33 +08:00
YunaiV a18c81511c fix(wms): 完善单据状态保护与金额精度处理
- 后端补充商品、往来企业唯一性校验
- 单据更新改为按草稿状态条件更新,避免覆盖已完成单据
- 补充 WMS 金额、规格精度迁移 SQL 与测试表结构
- 前端统一明细金额兜底计算,优化完成/作废取消处理
2026-05-15 19:59:45 +08:00
YunaiV b3bb1114ba fix(wms): 完善单据状态保护与金额精度处理
- 后端补充商品、往来企业唯一性校验
- 单据更新改为按草稿状态条件更新,避免覆盖已完成单据
- 补充 WMS 金额、规格精度迁移 SQL 与测试表结构
- 前端统一明细金额兜底计算,优化完成/作废取消处理
2026-05-15 19:48:33 +08:00
YunaiV 50cfbfe58b feat(wms): 统一数量金额精度并清理 schema 脱钩
后端:
- 新增 sql/mysql/wms/20260515_wms_amount_precision.sql
  11 张表金额字段统一升到 decimal(16,2),覆盖 SKU 单价、单据主表/明细
  总金额/行金额、盘库实际金额、库存流水单价/行金额
- 新增 sql/mysql/wms/20260515_wms_sku_dimension_precision.sql
  SKU 长宽高对齐 lite 改为 decimal(10,1)、毛/净重改为 decimal(10,3)
- 测试 SQL create_tables.sql 全量同步生产 MySQL:数量 (20,2)、
  金额 (16,2)、长度 (10,1)、重量 (10,3),修复"测试 schema 与生产
  脱钩"导致单测假阳性的隐患
- WmsWarehouseServiceImpl.validateWarehouseCodeUnique 去掉
  StrUtil.isBlank 提前 return,因 code 已由 VO 层 @NotBlank 强制非空
- WmsWarehouseServiceImplTest 同步调整

前端:
- ReceiptOrderForm / ReceiptOrderDetail 合计行去掉"单价合计"派生展示,
  单价不能跨行相加;保留数量合计与行金额合计

文档:
- review-opus.md 收口至仅剩 F10 (SQL 导出,用户认领)
- 新增 fix-plan.md 与 精度调整-codex讨论.md,沉淀本轮决策依据
2026-05-15 18:52:37 +08:00
YunaiV c81116678a feat(wms): 拆 simple-list 列表 VO、补首页校验与业务单号搜索框
后端:
- 新增 WmsItemListReqVO / WmsMerchantListReqVO,simple-list 接口不再
  复用分页 PageReqVO,Swagger 上不再误暴露 pageNo/pageSize 字段
- WmsItemController / WmsMerchantController 的 getXxxSimpleList 改用
  独立 ListReqVO;Mapper.selectList、Service.getXxxList 同步调整签名
- WmsHomeStatisticsServiceImpl 三个查询入口加 validateWarehouseIfPresent,
  非空 warehouseId 走 warehouseService.validateWarehouseExists 校验,
  避免前端误传任意 id 直接落到首页 SQL
- 新增 sql/mysql/wms/20260515_wms_total_price.sql:幂等给 4 张明细 / 流水表
  补 total_price 列并按 ROUND(quantity*price, 2) 回填历史数据

前端:
- receipt/index.vue + shipment/index.vue 搜索栏补 bizOrderNo 输入控件,
  对齐已声明的 queryParams 与后端 PageReqVO 支持
- WmsHomeOrderSummaryCards.getStatusPercent 改 function 声明,并去掉
  最小 4% 占比下限,保留真实比例
2026-05-15 18:23:59 +08:00
YunaiV fa570c2637 feat(wms): 持久化出入库移库明细行金额并补全库存流水金额展示 2026-05-15 14:11:14 +08:00
YunaiV 2ffbcbd71f feat(wms):打印时,更新、新增的信息整合 2026-05-15 13:25:56 +08:00
YunaiV 524ed28973 feat(wms):打印时的 barcode 标签 2026-05-15 13:12:23 +08:00
YunaiV 508d06b493 feat(wms):更新修复进展 2026-05-15 13:09:30 +08:00
YunaiV f82ae7e0c8 feat(wms):优化整体代码结构 2026-05-15 12:59:11 +08:00
YunaiV 24343f66fc feat(wms):调整 check 的实现 2026-05-15 11:00:14 +08:00
YunaiV 52972506a8 feat(wms):更新修复进展 2026-05-15 10:45:28 +08:00
YunaiV d128df618e feat(wms):增加 code 字段生成(从后端到前端),用户更可控 2026-05-15 10:22:37 +08:00
YunaiV 5f944548a3 fix(wms): 调整文档的最新内容 2026-05-15 09:46:40 +08:00
YunaiV 7bae330828 fix(wms): 移库选择商品时,必须两个仓库都选择完 2026-05-15 09:06:59 +08:00
YunaiV 7ff8cb78e1 feat(wms):统一 4 个订单界面的样式和代码风格 2026-05-15 08:44:08 +08:00
YunaiV 8252f7b068 feat(wms):优化 onlyPositiveQuantity 只查询库存非空的处理。 2026-05-15 08:24:54 +08:00
YunaiV 19db64c08a feat(wms):优化代码的排版 2026-05-14 23:04:50 +08:00
YunaiV e0352af8b0 feat(wms):优化代码的排版 2026-05-14 22:53:53 +08:00
YunaiV ae54f938cf feat(wms):优化首页的代码实现 2026-05-14 22:35:53 +08:00
YunaiV b455ce4949 feat(im): 优化 rtcStore 的命名 2026-05-14 22:15:35 +08:00
YunaiV 58537a34c7 feat(wms):增加首页的 review 2026-05-14 18:53:56 +08:00
YunaiV 4a811fb0bb feat(im): 评审下 rtcStore 的实现 2026-05-14 17:16:46 +08:00
YunaiV 804ad667e0 feat(wms):优化盘库单的实现 2026-05-14 17:03:49 +08:00
YunaiV eaedb1e0ca feat(wms):移库管理,调整合计金额、数量的字段与交互。(前端负责展示,后端负责计算) 2026-05-14 09:46:19 +08:00
YunaiV e579a4de13 feat(im): 优化 rtc 整体弹窗界面 2026-05-14 09:44:39 +08:00
YunaiV df013ac69c feat(wms):出库管理,调整合计金额、数量的字段与交互。(前端负责展示,后端负责计算) 2026-05-14 09:07:26 +08:00
YunaiV 6f96d004a9 feat(wms):入库管理,调整合计金额、数量的字段与交互。(前端负责展示,后端负责计算) 2026-05-14 08:39:04 +08:00
YunaiV 44808eb3f4 feat(wms):调整 order_time 放到【仓库】后面 2026-05-14 00:02:03 +08:00
YunaiV a7911bcbcf feat(wms):增加 order_time 单据字段 2026-05-13 23:31:03 +08:00
YunaiV a170ae37ab feat(im): 优化消息的 format 相关的逻辑,从 user 抽到 message 工具类里,更加统一 2026-05-13 23:27:02 +08:00
YunaiV 841d2cb763 feat(im): 统一 im 的 api 风格,保持一致性 2026-05-13 22:58:09 +08:00
YunaiV 8d06f87e0f feat(wms):减法,去掉批次号等字段 2026-05-13 22:06:37 +08:00
YunaiV fdbb98fe65 feat(wms):减法,去掉 area 表 2026-05-13 20:29:25 +08:00
YunaiV c5948d405e feat(wms):减法,去掉 detail 表,和 mes 更对齐 2026-05-13 18:42:51 +08:00
YunaiV 70aff05ef5 feat(wms):新增移库、盘库管理 2026-05-13 09:47:45 +08:00
YunaiV b3b35e147b feat(wms):新增出库管理 2026-05-13 08:57:41 +08:00
YunaiV 765c8ea94f feat(wms):修复只能删除作废的入库单的问题 2026-05-13 00:42:29 +08:00
YunaiV ac49ba5c6d feat(wms):增加供应商 select 组件 2026-05-12 23:34:45 +08:00
YunaiV 3ef4e8424f feat(wms):增加入库列表的评审 2026-05-12 23:30:06 +08:00
YunaiV c865dfe488 feat(wms):进一步优化入库单的后端实现(对齐 mes) 2026-05-12 23:02:46 +08:00
YunaiV 18e5c97bf3 feat(im): 将后端的 roomName 和 callId 融合,简化字段和逻辑(一致性更好、概念更简洁) 2026-05-12 20:29:08 +08:00
HmEJ 680c0e0e8b feat: 使流程图居中显示 2026-05-12 16:50:13 +08:00
YunaiV 38cb980ce4 feat(im): 完善 rtc 的后端代码逻辑,各种代码风格的优化 2026-05-12 13:17:04 +08:00
YunaiV 0be2674277 feat(wms):增加 inv 库存的新增、修改方法,并提供相关单测 2026-05-12 11:14:21 +08:00
YunaiV f0cd639137 feat(wms):优化 inventory history 2026-05-11 15:11:39 +08:00
YunaiV 314293ced3 feat(wms):优化 inventory 的实现 2026-05-11 14:10:16 +08:00
YunaiV 32bbd912a2 feat(wms):增加 inventory history 2026-05-11 13:07:35 +08:00
YunaiV 32442830b0 feat(wms):增加 inventory 2026-05-11 09:45:24 +08:00
YunaiV 711d5abc0a feat(wms):完善往来企业 2026-05-10 23:56:28 +08:00
YunaiV 4da16e95f5 feat(wms):完善商品信息、SKU 信息 2026-05-10 22:46:23 +08:00
YunaiV d890781149 feat(wms):增加商品信息、SKU 信息 2026-05-10 21:33:42 +08:00
YunaiV 30e4fef7bb feat(wms):增加商品分类、商品品牌。 2026-05-10 16:38:25 +08:00
YunaiV 8105cc786a (〃'▽'〃)_v2026_04_发布:新增代码生成器 Excel 导入,增强 IoT 场景联动与数据流转 2026-05-10 10:59:52 +08:00
YunaiV eb8002ce0b chore: make build scripts cross-platform
- use cross-env for NODE_OPTIONS in build commands
- align local/dev/test/stage/prod build scripts with Vite modes
- add cross-env to pnpm lockfile
2026-05-10 10:01:08 +08:00
YunaiV 1aad2f1648 feat(wms):迁移到 md 更整体 2026-05-10 09:02:51 +08:00
YunaiV c3737d3b7a feat(wms):增加 warehouse 功能 2026-05-10 01:22:38 +08:00
YunaiV 750f25410c feat(im): 基于 livekit 构建 im 通话(语音聊天、视频聊天、共享桌面)v0.1:推进中 2026-05-09 15:23:07 +08:00
YunaiV e12596ad7c ♻️ refactor(im): 移动 IM 相关 API 引用至新路径 2026-05-09 01:25:03 +08:00
YunaiV 2935d7d112 feat(im): 拆出私聊 / 群聊已读两个全局开关,关闭后禁用接口与所有 UI 入口(含群回执)
ImProperties.message 新增 privateReadEnabled / groupReadEnabled,前端 config.ts 同步镜像。关闭后:
- 后端:read 系列接口(read / getMaxReadMessageId / getGroupReadUserIds)抛业务异常;sendGroupMessage 强制 NO_RECEIPT 忽略 receipt=true;pull 群消息跳过 Redis 已读游标读取与 readCount 补齐
- 前端:气泡已读标签 / 群回执 popover / 「发送回执消息」下拉入口 / admin 列表「状态」「回执」列与详情对应字段按开关隐藏;自动上报 / 冷启动同步对方已读位置 / WS READ & RECEIPT handler 全部按开关短路兜底,避免打到禁用接口
- 单测:补 @Spy ImProperties 修复原本就在的 NPE,加 disabled 分支断言
2026-05-09 01:07:18 +08:00
YunaiV 46b06b0444 【新增】IM:群头像支持成员头像九宫格兜底
群头像为空时,取前 9 个成员头像在 Canvas 上拼九宫格 dataURL;空头像 / 加载失败的格子画跟 UserAvatar 同款色卡(首字 + charCode 哈希调色板)。
- 新增 GroupAvatar 组件包一层 UserAvatar;按容器 size × DPR 自适应画布像素,避免 retina 屏糊
- utils/group.ts 加 buildGroupAvatar 与 LRU 缓存 facade(上限 200);utils/image.ts 抽公共 loadImage;utils/user.ts 抽 getAvatarText / getAvatarBgColor 供 UserAvatar 与拼图共用
- GroupItem / GroupInfo / ConversationItem / ConversationPickerPanel 按会话类型分支换用 GroupAvatar
2026-05-08 18:28:02 +08:00
YunaiV 70e7a1c900 【新增】IM:群头像支持成员头像九宫格兜底
群头像为空时,取前 9 个成员头像在 Canvas 上拼九宫格 dataURL;空头像 / 加载失败的格子画跟 UserAvatar 同款色卡(首字 + charCode 哈希调色板)。
- 新增 GroupAvatar 组件包一层 UserAvatar;按容器 size × DPR 自适应画布像素,避免 retina 屏糊
- utils/group.ts 加 buildGroupAvatar 与 LRU 缓存 facade(上限 200);utils/image.ts 抽公共 loadImage;utils/user.ts 抽 getAvatarText / getAvatarBgColor 供 UserAvatar 与拼图共用
- GroupItem / GroupInfo / ConversationItem / ConversationPickerPanel 按会话类型分支换用 GroupAvatar
2026-05-08 18:27:53 +08:00
YunaiV c5b082ca80 ♻️ refactor(im): 业务策略数值从 ImCommonConstants 上移到 ImProperties(按 group / message 子模块分组),常量类仅保留 AT_USER_ID_ALL 协议契约值
♻️ refactor(im): 抽出 utils/config.ts 集中数值常量,按业务域统一前缀(GROUP_ / MESSAGE_ / FRIEND_ / CONVERSATION_ / FORWARD_),constants.ts 只留协议枚举与契约值
2026-05-08 17:42:13 +08:00
YunaiV dfd5b39a17 feat(im): MessageForwardDialog 接入「创建聊天」分支 + 收尾打磨
- MessageForwardDialog 加 view: 'conversation' | 'contact' 切换:
  - 模板按 view 切 ConversationPickerPanel / FriendPickerPanel;dialog header 用 slot 渲染「← 返回」
  - handleSwitchToContact 切 view + 清留言(避免不可见输入框留言被静默发出)
  - handleCreateGroupAndSend 复用 forwardToTarget(newConversation),merge / single 都按 mode 自动跑
  - 成功 / 失败统一末尾退多选 + 关弹窗,避免源会话遗留多选态
- 顺手清掉 GroupMemberAddDialog / MessageForwardDialog 末尾多余空行

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 16:16:01 +08:00
YunaiV 312df4c73d refactor(im): 抽象 IM 选择类弹窗为 PickerPanel 体系,对齐微信 PC
- 拆「业务壳 + 纯 PickerPanel」两层;13 个 dialog 统一 ref + open(opts) 接口
- 新增 FriendPickerPanel / ConversationPickerPanel / GroupMemberPickerPanel
- 抽 useFriendBuckets / useSelectedItems composable + buildDefaultGroupName / picker-dialog.scss mixin
- conversationStore 加 recentForwardConversationKeys 系列 action(持久化到 IDB)
- 三态语义固化:hide > locked > disabled
- 圆形勾选用微信绿;主按钮跟随项目主题色;最近转发横向头像 + 移除模式
- 删 GroupMemberSelector(由 GroupMemberPickerPanel 替代)/ FriendLite.deleted 死字段
- 配套:project_duibiao/im/dialog-picker-{contract,wechat-compare}.md
2026-05-08 14:06:48 +08:00
YunaiV 40ac2daca8 feat(im):文本气泡 @ 高亮支持点击 + URL 自动识别成可点击链接
- @ 段:群消息按 atUserIds 反查群成员,候选 name 兼容历史字面量(真实昵称 / 好友备注 / 群自定义昵称),displayName 统一收敛到 nickname,让历史消息也能渲染成 @真实昵称;@ 段点击弹 UserInfoCard
- @所有人:注入 IM_AT_ALL_USER_ID 虚拟候选,对齐微信 PC 仅高亮配色不挂点击
- 同名歧义:同字面量对应多个 userId 时标记 ambiguous,parser 整段消费成普通文本,避免错绑用户
- URL:识别 http(s) / www. 起头链接,<a target="_blank"> 新标签打开;默认补 https://
- TipSegment 加 link 变体作为统一文本片段类型,TEXT 气泡与灰条 tip 共用 TipSegments 组件渲染
- MessageInput @ token 文本统一用真实昵称,不再掺好友备注 / 群自定义昵称
2026-05-08 01:23:09 +08:00
YunaiV 094ab44094 🎨 refactor(im): joinMentionSegments 用 flatMap 替代命令式 push
少一个可变 out + 命令式 forEach,意图更直观

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 23:59:00 +08:00
YunaiV 9eb221e8d2 feat(im): 灰条 tip 文案的 mention 段支持点击弹 UserInfoCard
群广播 / 撤回 / 好友事件 tip 文案从纯字符串拆成 TipSegment[],mention
段携带 userId,渲染层挂点击 → uiStore.openUserInfoCardAtEvent。

- utils/message.ts:加 TipSegment 协议 + 零依赖 helper
- utils/user.ts、utils/conversation.ts:加 segments builder,string 版
  改写为 segmentsToText 包装,避免 case 表分叉
- TipSegments.vue:按 activeConversation 推断 addSource,群里走
  GROUP+群名、私聊走 SEARCH;nickname 不传备注避免 UserInfo 首屏闪
- MessageItem.vue / MessageHistory.vue:tip 块切 <TipSegments>

顺手补:utils/constants.ts 新增 SystemUserSexEnum,替换 IM 模块 sex
硬编码 1 / 2

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 23:46:50 +08:00
YunaiV 5b85a4c469 feat(im): 浏览器标签 title 拼上未读数前缀 2026-05-07 22:17:03 +08:00
YunaiV 7fa1dbc55c 🐛 fix(im): 当前打开的会话在列表里不再展示 [草稿] 前缀 2026-05-07 21:55:45 +08:00
YunaiV cc93b8a742 feat(im): 初始化消息转发 v0.4:第四次评审(语音播放器资源释放打磨)
agent 三轮复审后的质量打磨,无功能变更。

- useVoicePlayer.stop:audio.removeAttribute('src') + audio.load() 替代 audio.src = '';不会触发空 src 加载的 error 事件,也能让浏览器立即释放底层 decoder buffer
- useVoicePlayer.play:同 key 再点的 stop() 改 stop(key),意图自解释(我想停的就是我自己)
- useVoicePlayer 移除未消费的 currentKey 暴露;调用方都走 isPlaying(key) 派生
- home/index.vue onUnmounted 追加 voicePlayer.stop():模块级单例 audio 不会随视图卸载自动停,补主壳兜底
2026-05-07 21:46:33 +08:00
YunaiV 0b07091e79 🐛 fix(im): 修复切会话 watch 仅监听 targetId 导致私聊与群聊 id 同号时不触发
home/index.vue 已读 watch、MessagePanel 滚动 / 侧栏 / 群资料预拉 watch 都只看 targetId,
私聊 1 切群 1 时不会触发,会把侧栏 / 新消息浮窗 / 滚动状态 / 已读上报漏掉,且不跑 ensureGroupData。
对齐多选 + 语音那两条 watch,统一改成监听 [type, targetId]。
2026-05-07 21:26:40 +08:00
YunaiV 0323566878 feat(im): 初始化消息转发 v0.3:第三次评审(语音播放全局互斥)
新点的语音停掉旧的,对齐微信 PC 语义;一次只播一条,主面板 / 历史抽屉 / 合并详情共享同一播放态,避免多窗口同时出声。

- 新增 useVoicePlayer composable:模块级单例 currentTask + play / stop;ended / error / play().catch 全部 once: true 收尾,避免 listener 泄漏;play(key, url) / stop(key?) / isPlaying(key) 以 Symbol 当播放身份
- MessageBubble setup 里 Symbol('im-message-bubble-voice') 生成实例级 voiceKey;voicePlaying 改成派生 computed,移除本地 currentAudio
- MessageBubble 卸载兜底:调 voicePlayer.stop(voiceKey) 仅停自己;防止删除消息 / 历史抽屉切筛选导致气泡消失但 audio 仍在响;不会误伤别处仍可见的同 url 气泡
- MessagePanel 切会话 watch 追加 voicePlayer.stop()
- MessageHistory 关闭抽屉 watch 追加 voicePlayer.stop()
- MessageMergeDetailDialog handleClose 追加 voicePlayer.stop()
2026-05-07 21:26:12 +08:00
YunaiV 82d065c270 feat(im): 初始化消息转发 v0.2:第二次优化部分代码(一些工具类等) 2026-05-07 20:34:09 +08:00
YunaiV cf01143632 feat(im): 初始化消息转发 v0.1:第一次评审 2026-05-07 19:47:27 +08:00
YunaiV 9bcdc92806 feat(im): 初始化群名片 v0.2:第二次评审(需求各种进群的小问题) 2026-05-07 19:07:37 +08:00
YunaiV f5936a6e7f feat(im): 初始化群名片 v0.2:第二次评审(需求各种进群的小问题) 2026-05-07 18:01:43 +08:00
YunaiV d175fe86be feat(im): 初始化群名片 v0.2:第二次评审(需求各种进群的小问题) 2026-05-07 17:28:16 +08:00
YunaiV ce66a507ef feat(im): 初始化群名片 v0.2:第二次评审(需求各种进群的小问题) 2026-05-07 17:25:03 +08:00
YunaiV 65d5aacac9 feat(im): 初始化群名片 v0.1:第一次评审 2026-05-07 13:07:56 +08:00
YunaiV 808ad575fc feat(im): 初始化群申请 v0.5:第六把 review(性能 / 健壮性 / 简洁度收口)
后端
- createInviteRequestList N+1 → 3 SQL:批量 select IN + update IN + insertBatch;20 人邀请从 40 RTT 降到 3 RTT
- service 不再出现 mybatis:复用记录的 update(null, wrapper) 下沉到 Im{Group,Friend}RequestMapper.update*Reset helper
- inviteGroupMember 入参去重切 hutool:CollUtil.subtractToList(CollUtil.distinct(...), activeMemberUserIds)
- 删除 dead 字段 inviterUserId(GroupRequestApprovedNotification / GroupRequestRejectedNotification):前端不再消费

前端
- 1505 / 1506 通知改静默:同意走群事件 1509 / 1510 渲染系统提示,拒绝不再打扰
- 修竞态:addByRequestId 校验 handleResult === UNHANDLED,避免 1503 在途时被 1505 / 1506 抢先后又把已处理记录塞回未处理列表
- 修复 dialog 复用记录刷新:watch key 含 inviterUserId / applyContent,同 id 不同内容也触发 refetch;actingId 期间跳过避免本端动作多余 RTT
- 修复 willGoApproval 误报:group.ownerUserId 兜底群主;members 未到位时保守按非审批处理
- unhandledCountMap memoized getter:O(N) 扫一次缓存到 Map,ConversationItem 直读 Map 消除 O(N×M) 重复 filter
2026-05-07 08:13:27 +08:00
YunaiV cb26df3ca1 feat(im): 初始化群申请 v0.4:第五把 review(多轮 finding 修复 + 通知静默化)
- 邀请路径写 addSource=INVITE;群主 / 管理员邀请绕过审批;inviteGroupMember 入参去重
- getGroupRequest 越权校验加成员有效状态判断;新增 list-by-group 接口
- 申请列表按 update_time 倒序,update(null, wrapper) 路径手动刷 updateTime
- addByRequestId 不再 skip 同 id,复用记录刷新并置顶
- GroupRequestListDialog 单群模式订阅 store 增量同步;GroupMemberAddDialog 审批分支文案区分
- ConversationItem 增加 [X 条进群申请] 红字前缀;MessagePanel 顶部胶囊横幅
- 1505 / 1506 通知改静默:同意走群事件渲染系统提示,拒绝不再打扰;清掉 dead inviterUserId 字段
2026-05-07 00:51:48 +08:00
YunaiV b2ba42049b feat(im): 初始化群申请 v0.3:第四把 review(优化界面,进一步对齐微信界面)【之前提交错了】 2026-05-06 23:57:54 +08:00
YunaiV f746aebe08 feat(im): 初始化群申请 v0.3:第四把 review(优化界面,进一步对齐微信界面) 2026-05-06 23:57:03 +08:00
YunaiV 0eca952c6a feat(im): 初始化表情包 v0.3:第四把 review(增加表情管理的界面) 2026-05-06 23:00:08 +08:00
YunaiV a98e32554c feat(im): 初始化表情包 v0.2:第三把 review 2026-05-06 21:08:46 +08:00
YunaiV 2f513f7b8f feat(im): 初始化群申请 v0.2:第三把 review 2026-05-06 20:51:45 +08:00
YunaiV 8eebfd4744 feat(im): 初始化表情包 v0.1:第二把 review 2026-05-06 20:50:55 +08:00
YunaiV 1ed5dc7e6a feat(im): 初始化表情包 v0.0:第一把 review 2026-05-06 19:42:19 +08:00
YunaiV 8fc5273a88 feat(im): 初始化群申请 v0.1:第二把 review 2026-05-06 18:52:30 +08:00
DevDengChao 81711a98c9 docs: 删除依赖升级备注 2026-05-06 16:55:01 +08:00
DevDengChao 766b3906fa docs: note sequential build verification
Co-authored-by: OpenAI <support@openai.com>
2026-05-06 16:30:35 +08:00
DevDengChao cd63cf2b34 chore: remove e2e tests and playwright
Co-authored-by: OpenAI <support@openai.com>
2026-05-06 16:24:25 +08:00
DevDengChao 84ae85f545 chore: upgrade runtime dependencies
Co-authored-by: OpenAI <support@openai.com>
2026-05-06 16:13:36 +08:00
DevDengChao 78b6679e63 chore: upgrade dev tooling dependencies
Co-authored-by: OpenAI <support@openai.com>
2026-05-06 16:07:04 +08:00
DevDengChao ee5ed1f97b merge: upstream master into upgrade
Co-authored-by: OpenAI <support@openai.com>
2026-05-06 16:00:18 +08:00
YunaiV 3be0daf115 feat(im): 初始化群申请 v0.0:第一把 review 2026-05-06 14:53:47 +08:00
YunaiV 4868d69ed8 feat(im): 优化名片消息类型 v0.4:增加转发成功失败的提示 2026-05-06 12:18:31 +08:00
YunaiV 1ac0650984 ♻️ refactor(im):注释对齐 + patchMessage 复用 applyServerMessageUpdate
- recomputeConversationLast / videoCoverUrl / showSendingLoading 三处 JSDoc 跟实现对齐:
  原描述还停留在旧设计(lastSendTime 不重算 / 占位 coverUrl 用 blob / 「外层 loading 多余」),
  这轮一并改成当前事实,避免后续维护被误导
- patchMessage 删手写 revoke + Object.assign,改调 applyServerMessageUpdate,
  与 ackMessage / insertMessage(existingIndex) 共用一份服务端字段合并语义;
  「值未变早返回」保留在 patchMessage 顶部
- 抽 BLOB_URL_PREFIX 常量替代散落在 utils/message.ts 与 useMediaUploader.ts 的 3 处 'blob:' 字面量
2026-05-06 10:38:43 +08:00
YunaiV 30d695d702 🐛 fix(im):codex 评审修复发送中状态边角
- recomputeConversationLast 空 messages 时一并清 lastSendTime,
  避免刷新后被 drop 的媒体占位让空会话仍按旧时间排在列表前面
- 视频占位 coverUrl 不再赋 blob URL:<video poster> 期待图片资源,
  传 video blob 在部分浏览器会退化成黑底,cover 等 probe 出真实 URL 后由 commit 阶段一起 patch
- useMediaUploader 暴露 requireMediaHandler typed accessor,
  消除 video 链路 mediaTypeHandlers[VIDEO]! 非空断言
- MessageItem 把外层 loading 的 (!isUploading || isVoice) 抽成 showSendingLoading computed
2026-05-06 10:18:49 +08:00
YunaiV 459eaa5428 ♻️ refactor(im): 优化消息存储逻辑,减少不必要的处理
改进消息存储时的处理逻辑,通过提前检查是否存在 _localFile 来优化性能,避免不必要的全量映射操作。
2026-05-06 08:50:13 +08:00
YunaiV 59aab8ecdc feat(im): 优化名片消息类型 v0.3:增加表情选择 2026-05-06 08:47:18 +08:00
YunaiV b17f7a57e5 feat(im): 优化发送中的能力 v0.2:简化各种 kind、复用各种逻辑 2026-05-06 08:46:33 +08:00
YunaiV 957a63f8f4 feat(im): 优化名片消息类型 v0.2:优化转发弹窗的界面样式 2026-05-06 08:33:03 +08:00
YunaiV c15d75ba91 feat(im): 优化发送中的能力 v0.1:各种清理时的边界 2026-05-06 08:22:41 +08:00
YunaiV 3836467481 feat(im): 优化名片消息类型 v0.1:补充缺失的名片展示 2026-05-06 08:21:52 +08:00
YunaiV f3de29f95f feat(im): 增加名片消息类型 2026-05-06 08:00:36 +08:00
YunaiV 8f2eddea4a feat(im): 增加发送中的能力,针对图片、文件、视频等 2026-05-06 08:00:23 +08:00
YunaiV f8cc9d14d9 feat(im): 优化下禁言弹窗的样式 2026-05-05 22:51:26 +08:00
YunaiV 5bd99c53c2 🐛 fix(im):codex 评审修复 FRIEND_ADD / FRIEND_DELETE 接收方 peer 与 clear 气泡
- FRIEND_ADD 接收方 peer 改按帧 sender / receiver 反推:becomeFriends 单条入库后双方收到同一份 payload,payload.friendUserId 固定是 toUserId,本端真正的对端要看自己是 sender 还是 receiver;新增 websocketStore.computeFriendPeerId 算好后传给 friendStore.applyFriendAdd/DeleteNotification
- FRIEND_DELETE clear=true 跳过气泡插入:clear 语义是清会话本身,气泡分支按 isFriendDeleteWithClear 校验,避免在已清会话里写虚拟消息

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 22:32:04 +08:00
YunaiV c653c2fa2b feat(im): 增强消息处理逻辑,支持好友通知与撤回消息 2026-05-05 22:15:48 +08:00
YunaiV 4e181564b8 feat(im): 清理一些 TODO 的修复 2026-05-05 22:04:45 +08:00
YunaiV a9f54fdee1 feat(im): 重构普通消息类型,和 openim 的消息编号对齐 2026-05-05 21:56:05 +08:00
YunaiV 055d4bab27 feat(im): 联动好友 / 群通知重构,抽 useMuteOverlay 统一禁言拦截与媒体上传公共骨架 2026-05-05 21:33:27 +08:00
YunaiV e48316231c feat(im): 增加好友申请的管理界面 2026-05-05 19:36:29 +08:00
YunaiV 390b66aee8 feat(im): 群禁言/封禁 UI 交互 + 群主解散群聊
一、群禁言交互
- MessageItem 右键菜单新增「禁言/解除禁言/移除」(权限校验)
- 新增 GroupMuteMemberDialog 禁言时长选择弹窗
- ConversationGroupSide 新增「全群禁言」开关
- MessageInput 新增禁言/封禁覆盖层,拦截所有发送入口
- canManageSender 目标角色未知时不展示管理菜单
- 全群禁言群主按 ownerUserId 直接豁免,不依赖成员列表

二、群封禁(GROUP_BANNED 1533)
- constants 激活 GROUP_BANNED 常量,扩展 isGroupNotification 范围
- groupStore 处理 1533 事件,实时更新 banned 字段
- MessageInput 覆盖层最高优先级判断 banned(红底)
- Group 类型、convertGroup 映射 banned 字段

三、禁言事件(1512-1515)
- constants 激活 GROUP_MEMBER_MUTED / CANCEL_MUTED / MUTED / CANCEL_MUTED
- groupStore 处理 1512-1515 事件,更新 muteEndTime / mutedAll
- user.ts 新增 4 条禁言 + 2 条封禁通知文案
- GroupNotificationPayload 扩展 mutedUserId / muteEndTime / banned

四、群主解散群聊
- ConversationGroupSide 底部按钮:群主显示「解散群聊」,非群主显示「退出群聊」
- 新增 handleDissolve 实现(二次确认 → dissolveGroup API → 清本地数据)
2026-05-05 18:41:28 +08:00
芋道源码 9d8d0647be
!877 feat(system): 优化用户选择 UserSelectV2 布局,多选支持、默认选中当前用户支持、禁选支持、默认部门支持,可替代…
Merge pull request !877 from 半栈幼儿员/hotfix/user
2026-05-05 10:10:47 +00:00
YunaiV 7b4c7028a0 feat(im):群禁言功能(阶段三管理后台 UI)
1. group API 新增 mutedAll 字段 + 3 个禁言接口(muteAll、muteMember、cancelMuteMember)
2. group member API 新增 muteEndTime 字段
3. 管理后台群列表新增「全群禁言」列(已禁言/未禁言标签)
4. 群详情抽屉新增「全群禁言」状态行 + 成员表新增「禁言状态」列(禁言中 + 到期时间)
2026-05-05 16:58:51 +08:00
YunaiV 4d006f8e73 feat(im):将"免打扰"字段从 muted 全量重命名为 silent(DO/VO/Service/Mapper/测试/SQL + 前端 types/store/组件/管理后台),为后续 mute 禁言功能腾出词族 2026-05-05 13:51:53 +08:00
preschooler 536e54062e feat(system): 优化用户选择 UserSelectV2 布局,多选支持、默认选中当前用户支持、禁选支持、默认部门支持,可替代项目所有位置,可移除原 UserSelectForm、UserSelect,避免一次性查询所有用户 2026-05-05 12:35:31 +08:00
YunaiV dd75c702db feat(im):删好友「同时清空聊天记录」弹窗 + 顶部「对方还不是你的朋友」胶囊(点击弹 UserInfoCard)+ fetchFriends 不动 DISABLE 尊重用户选择 2026-05-05 00:57:58 +08:00
YunaiV 2a55748296 feat(im):增加好友删除时,增加是否删除本地聊天的选项 2026-05-05 00:33:06 +08:00
YunaiV 1400bd80dd 🐛 fix(im):loadFriendRequest 按 id 大小决定插入位置,老 id 不入避免破坏倒序 + loadMore 重复 push 2026-05-04 23:06:12 +08:00
YunaiV 14e3f85cb0 ♻️ refactor(im):用户申请列表,增加流式查询,避免一次性加载过多,或者历史无法被加载到。 2026-05-04 22:46:20 +08:00
YunaiV 9fc25b7109 ♻️ refactor(im):friendStore 修 setMuted/removeFriend 一致性 + 新申请直推列表 + 清 dispatcher 防御兜底 2026-05-04 21:13:36 +08:00
YunaiV 45bac49ec0 ♻️ refactor(im):friendStore 回调参数 f 改全名 friend / existing 2026-05-04 19:52:44 +08:00
YunaiV b242b017c0 feat(im):fetchFriends 加 pending 去重 + FRIEND_APPLICATION 重命名 RECEIVED + inflight 命名调整为 pending 2026-05-04 19:14:51 +08:00
YunaiV cfd152addf feat(im):实现 1209 FRIEND_INFO_UPDATED 推送(system 发 AdminUserProfileUpdateMessage,IM Consumer 监听后批量推好友多端) 2026-05-04 18:24:08 +08:00
YunaiV 7618d58a66 feat(im):好友模块 code review 多项修复(补 block/unblock 全链路 + UserInfo 菜单入口、silent 后主动入库、防 currentUserId 切账号失活、雪崩去重与命名/枚举清理) 2026-05-04 17:31:21 +08:00
YunaiV 5c2a185ff9 【IM 优化】通讯录顶部搜索栏改为 h-14,与消息 Tab 顶部对齐,避免切换抖动 2026-05-04 16:47:33 +08:00
YunaiV 42566d1e86 feat(im): 优化好友申请逻辑,增加自我添加校验与异常处理
更新好友申请功能,使用 computed 包裹当前用户 ID,避免在 keep-alive 实例中持有旧 ID。增加自我添加好友的校验逻辑,防止用户添加自己为好友。同时,增强自动通过好友申请的异常处理,确保在事务提交后能正确处理失败情况。
2026-05-04 16:41:55 +08:00
YunaiV 7141e431e2 feat(im): 前端增加「黑名单」的操作 2026-05-04 16:24:12 +08:00
YunaiV 1b51926b19 refactor(im): 移除 TIP_TIME 消息类型,时间分隔条改为渲染时按 prevMessage.sendTime 计算
顺带修复 Bug-Y(删除最后一条消息后孤立时间分隔条)
2026-05-04 16:05:23 +08:00
YunaiV 63c4dd1096 fix(im):修复对话置顶被关闭时,默认还选择了第一个置顶对话,导致又被打开。 2026-05-04 12:00:03 +08:00
YunaiV b6ca1187b1 feat(im): 增加好友申请的逻辑(v1.3:修复各种边界情况,包括静默添加好友) 2026-05-04 11:08:03 +08:00
YunaiV 89ee5d51ea feat(im): 增加好友申请的逻辑(v1.2:增加相关枚举、字典,减少硬编码) 2026-05-04 10:44:09 +08:00
YunaiV 5b9acb4813 fix(im):从群聊,点击好友聊天时,关闭之前的群聊面板 2026-05-04 10:31:02 +08:00
YunaiV 0e979a9f67 fix(im):私聊头像,也可以点击,方便好友管理。 2026-05-04 10:30:30 +08:00
YunaiV ce2cdc112f fix(im):仅普通消息进行声音通知 2026-05-04 10:29:41 +08:00
YunaiV 1469d8bb3d feat(im): 增加好友申请的逻辑(v1.1:增加各种 code review 注释) 2026-05-04 09:47:25 +08:00
YunaiV f86cd30af4 feat(im): 增加好友申请的逻辑(v1) 2026-05-04 09:18:35 +08:00
芋道源码 5e937d797d
!851 feat:增加说明文案
Merge pull request !851 from steven/feat-自定义海报代码补充开发
2026-05-03 10:59:47 +00:00
芋道源码 3a1f520dc6
!876 回退 'Pull Request !870 : fix: 菜单名称过长时没有正确显示省略号'
Merge pull request !876 from 芋道源码/revert-merge-870-master
2026-05-03 10:55:53 +00:00
芋道源码 5e6b6bdd8e
回退 'Pull Request !870 : fix: 菜单名称过长时没有正确显示省略号' 2026-05-03 10:55:35 +00:00
YunaiV 53b96f87a0 Merge remote-tracking branch 'origin/feat/mes' into feat/mes 2026-05-03 18:48:25 +08:00
YunaiV b7a13a0000 ♻️ refactor(service): 优化请求拦截器中的 token 设置逻辑,简化白名单判断 2026-05-03 18:48:12 +08:00
YunaiV 6d5705b655 fix(bpm):修正流程实例审批弹窗网关分支重算的并发与提交问题
- 提交时不再用节点表单值覆盖 data.variables;与预览阶段使用同一份合并变量
- onChange 加 useDebounceFn(300ms) + 请求序号去重,handleAudit 提交前 await 最新一轮重算
- 切换任务时重置请求序号与 pending 重算
- 改用 form-create 官方 formData() 取节点表单当前值
- 双 nextTick 改为 until 等 fApi 就绪,1s 兜底超时
2026-05-03 18:48:12 +08:00
YunaiV 8571a27a15 fix: 【framework】关闭 TagsView 标签后 keep-alive 缓存未收缩,导致 DOM/JS heap 不回收
delView/delAllViews 误用 delCachedView,关闭非当前标签时会去删 currentRoute
对应的缓存,把要关的 name 留在 cachedViews 里,keep-alive include 不收缩,
旧组件实例无法 unmount。

回退到基于剩余 visitedViews 重建 cachedViews 的实现(对应 5718c7881 之前的写法);
delCachedView 自身保留 issue #180 的修复,仍供 refreshPage 使用。
2026-05-03 18:48:12 +08:00
YunaiV cdcd200c7d 【修复】form-create 单图上传规则 disabled 字段标题与默认值错配 2026-05-03 18:48:12 +08:00
YunaiV 6232330c81 【修复】IoT 场景联动:事件触发器比较值改普通文本输入,允许留空(事件发生即匹配) 2026-05-03 18:48:12 +08:00
YunaiV 418f0c4f52 🐛 fix(system):修复租户 get-by-website 接口不支持端口的问题
🐛 fix(mes):修复常见缺陷的「检测项类型」错用独立字典的问题

「常见缺陷」与「检测项设置」的「检测项类型」语义一致,应共用同一份字典;DefectForm 与列表页统一改为 MES_INDICATOR_TYPE,并清理未使用的 MES_DEFECT_TYPE 常量。
2026-05-03 18:48:12 +08:00
YunaiV 5bce60fd29 ♻️ refactor(service): 优化请求拦截器中的 token 设置逻辑,简化白名单判断 2026-05-03 18:48:04 +08:00
YunaiV beddbe7785 Merge remote-tracking branch 'origin/feat/mes' into feat/mes 2026-05-03 18:39:16 +08:00
芋道源码 2e65691737
!870 fix: 菜单名称过长时没有正确显示省略号
Merge pull request !870 from 李家辉/fix-text-overflow
2026-05-03 10:38:57 +00:00
YunaiV 95cecc8870 fix(bpm):修正流程实例审批弹窗网关分支重算的并发与提交问题
- 提交时不再用节点表单值覆盖 data.variables;与预览阶段使用同一份合并变量
- onChange 加 useDebounceFn(300ms) + 请求序号去重,handleAudit 提交前 await 最新一轮重算
- 切换任务时重置请求序号与 pending 重算
- 改用 form-create 官方 formData() 取节点表单当前值
- 双 nextTick 改为 until 等 fApi 就绪,1s 兜底超时
2026-05-03 18:38:18 +08:00
YunaiV 7a4300116e fix: 【framework】关闭 TagsView 标签后 keep-alive 缓存未收缩,导致 DOM/JS heap 不回收
delView/delAllViews 误用 delCachedView,关闭非当前标签时会去删 currentRoute
对应的缓存,把要关的 name 留在 cachedViews 里,keep-alive include 不收缩,
旧组件实例无法 unmount。

回退到基于剩余 visitedViews 重建 cachedViews 的实现(对应 5718c7881 之前的写法);
delCachedView 自身保留 issue #180 的修复,仍供 refreshPage 使用。
2026-05-03 18:38:18 +08:00
YunaiV 51542e336b 【修复】form-create 单图上传规则 disabled 字段标题与默认值错配 2026-05-03 18:38:18 +08:00
YunaiV 7f33206057 【修复】IoT 场景联动:事件触发器比较值改普通文本输入,允许留空(事件发生即匹配) 2026-05-03 18:38:18 +08:00
YunaiV 8e1430c1a4 🐛 fix(system):修复租户 get-by-website 接口不支持端口的问题
🐛 fix(mes):修复常见缺陷的「检测项类型」错用独立字典的问题

「常见缺陷」与「检测项设置」的「检测项类型」语义一致,应共用同一份字典;DefectForm 与列表页统一改为 MES_INDICATOR_TYPE,并清理未使用的 MES_DEFECT_TYPE 常量。
2026-05-03 18:38:18 +08:00
芋道源码 aafe5f12bc
!866 fix: 修复请求拦截器bug
Merge pull request !866 from funcong/fc-fix-bug
2026-05-03 10:37:47 +00:00
YunaiV 9df6828255 fix(bpm):修正流程实例审批弹窗网关分支重算的并发与提交问题
- 提交时不再用节点表单值覆盖 data.variables;与预览阶段使用同一份合并变量
- onChange 加 useDebounceFn(300ms) + 请求序号去重,handleAudit 提交前 await 最新一轮重算
- 切换任务时重置请求序号与 pending 重算
- 改用 form-create 官方 formData() 取节点表单当前值
- 双 nextTick 改为 until 等 fApi 就绪,1s 兜底超时
2026-05-03 16:34:55 +08:00
YunaiV 06e2ca3100 Merge remote-tracking branch 'origin/feat/mes' into feat/mes 2026-05-03 13:33:23 +08:00
YunaiV fa9facfa0b fix: 【framework】关闭 TagsView 标签后 keep-alive 缓存未收缩,导致 DOM/JS heap 不回收
delView/delAllViews 误用 delCachedView,关闭非当前标签时会去删 currentRoute
对应的缓存,把要关的 name 留在 cachedViews 里,keep-alive include 不收缩,
旧组件实例无法 unmount。

回退到基于剩余 visitedViews 重建 cachedViews 的实现(对应 5718c7881 之前的写法);
delCachedView 自身保留 issue #180 的修复,仍供 refreshPage 使用。
2026-05-03 13:32:43 +08:00
YunaiV 0cc2bff0f4 【修复】form-create 单图上传规则 disabled 字段标题与默认值错配 2026-05-03 13:32:43 +08:00
YunaiV 192a118823 【修复】IoT 场景联动:事件触发器比较值改普通文本输入,允许留空(事件发生即匹配) 2026-05-03 13:32:43 +08:00
YunaiV d2e82b710b 🐛 fix(system):修复租户 get-by-website 接口不支持端口的问题
🐛 fix(mes):修复常见缺陷的「检测项类型」错用独立字典的问题

「常见缺陷」与「检测项设置」的「检测项类型」语义一致,应共用同一份字典;DefectForm 与列表页统一改为 MES_INDICATOR_TYPE,并清理未使用的 MES_DEFECT_TYPE 常量。
2026-05-03 13:32:43 +08:00
YunaiV bf79e07d5c ♻️ refactor(im): 清理代码中的 TODO 注释并优化逻辑 2026-05-03 13:27:31 +08:00
YunaiV 7c129c18c4 feat(im): 增加群消息的置顶 2026-05-03 12:53:24 +08:00
YunaiV 01e0e8e37b feat(im): 增加群消息的置顶 2026-05-03 12:15:39 +08:00
芋道源码 583b409fad
!864 条件节点添加包含和不包含操作符
Merge pull request !864 from Lesan/bugfix/bpm-202602
2026-05-03 03:01:48 +00:00
YunaiV ffb69063b9 feat(im): 重构群通知相关,对齐 openim 的消息编号(继续优化代码) 2026-05-03 09:22:53 +08:00
YunaiV 5d0755eea9 Merge remote-tracking branch 'origin/feat/mes' into feat/mes 2026-05-03 09:20:21 +08:00
芋道源码 a698cb1635
!867 feat(iot): 前端新增 Database 数据目的配置表单
Merge pull request !867 from puhui999/master
2026-05-03 01:19:11 +00:00
YunaiV 43372c05ad feat(im): 重构群通知相关,对齐 openim 的消息编号 2026-05-03 02:00:43 +08:00
YunaiV a704620f84 fix: 【framework】关闭 TagsView 标签后 keep-alive 缓存未收缩,导致 DOM/JS heap 不回收
delView/delAllViews 误用 delCachedView,关闭非当前标签时会去删 currentRoute
对应的缓存,把要关的 name 留在 cachedViews 里,keep-alive include 不收缩,
旧组件实例无法 unmount。

回退到基于剩余 visitedViews 重建 cachedViews 的实现(对应 5718c7881 之前的写法);
delCachedView 自身保留 issue #180 的修复,仍供 refreshPage 使用。
2026-05-03 00:28:27 +08:00
芋道源码 7fd0a24ca5
!875 fix(bpm):修复流程网关分支问题
Merge pull request !875 from 郭某人/master
2026-05-02 16:01:00 +00:00
YunaiV e98d575b3a 【修复】form-create 单图上传规则 disabled 字段标题与默认值错配 2026-05-02 22:56:56 +08:00
YunaiV d5a9e2e313 【修复】IoT 场景联动:事件触发器比较值改普通文本输入,允许留空(事件发生即匹配) 2026-05-02 14:32:42 +08:00
YunaiV fa27c27831 feat(im): 增加群角色(管理员) 2026-05-02 14:31:42 +08:00
芋道源码 f9c0cace70
!871 fix: 修复响应拦截器 code=0 的逻辑错误
Merge pull request !871 from zhulh/fix/code-0-bug
2026-05-02 03:21:51 +00:00
YunaiV 3146f64edc feat(im): 未读计数改用 isNormalMessage,对齐后端 normal 语义 2026-05-02 09:06:01 +08:00
YunaiV 2c3842582f 🐛 fix(system):修复租户 get-by-website 接口不支持端口的问题
🐛 fix(mes):修复常见缺陷的「检测项类型」错用独立字典的问题

「常见缺陷」与「检测项设置」的「检测项类型」语义一致,应共用同一份字典;DefectForm 与列表页统一改为 MES_INDICATOR_TYPE,并清理未使用的 MES_DEFECT_TYPE 常量。
2026-05-02 00:35:16 +08:00
YunaiV fbd8615398 feat(im): 消息右键菜单优化 + 修复图片场景滚不到底
- MessageItem:「回复」→「引用」并加图标;撤回 / 删除互斥(自己消息 2 分钟内显示撤回,超出 / 对方消息显示删除),均加分割线 + 红色样式对齐微信;MENU_KEYS 抽 const 防 typo;引用块从气泡上方移到下方,selfSend 时竖线镜像到右侧
- MessagePanel:scrollToBottom 改 async + waitMediaSettled 等图片 / 视频元数据加载;用 expectedScrollTop drift 替代 distanceFromBottom,修复「图片加载完底部上移、误判用户已滚走」导致到不了底
- ReplyPreview:删等价的 filePayload / voicePayload alias,直接复用 parsedPayload
- uiStore:ContextMenuItem 加 icon? 字段,支持菜单项前置图标
2026-05-01 23:06:14 +08:00
YunaiV 52fdf0bcab feat(im): 消息右键菜单优化 + 修复图片场景滚不到底
- MessageItem:「回复」→「引用」并加图标;撤回 / 删除互斥(自己消息 2 分钟内显示撤回,超出 / 对方消息显示删除),均加分割线 + 红色样式对齐微信;MENU_KEYS 抽 const 防 typo;引用块从气泡上方移到下方,selfSend 时竖线镜像到右侧
- MessagePanel:scrollToBottom 改 async + waitMediaSettled 等图片 / 视频元数据加载;用 expectedScrollTop drift 替代 distanceFromBottom,修复「图片加载完底部上移、误判用户已滚走」导致到不了底
- ReplyPreview:删等价的 filePayload / voicePayload alias,直接复用 parsedPayload
- uiStore:ContextMenuItem 加 icon? 字段,支持菜单项前置图标
2026-05-01 23:04:56 +08:00
YunaiV 43666dc56c feat(im): 优化【消息引用】的功能,增加文件、图片的展示调整 2026-05-01 19:05:57 +08:00
YunaiV ef901b5381 feat(im): 优化【消息引用】的功能,来自第二波 code review,解决安全性问题 2026-05-01 18:20:04 +08:00
YunaiV cfeee7bbb7 feat(im): 优化【消息引用】的功能,来自第一波 code review 2026-05-01 18:09:02 +08:00
YunaiV 1dfab43b8a feat(im): 增加【消息引用】的功能 2026-05-01 18:03:05 +08:00
YunaiV 744229a02e feat(im): 优化语音输入的交互。 2026-05-01 09:59:27 +08:00
YunaiV 63c711f9e2 feat(im): 增加视频消息 2026-05-01 09:47:01 +08:00
YunaiV 82022b86de feat(im): 实现 im 的首页统计 2026-05-01 09:25:39 +08:00
YunaiV f5656c8a2f feat(im): 同步输入框状态以支持粘贴功能 2026-05-01 08:50:51 +08:00
YunaiV 31dc1b1198 feat(im): 用户的输入,改成 userselectv2,增强体验。 2026-05-01 08:49:14 +08:00
YunaiV 3cc7ac7f8b Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into im
# Conflicts:
#	pnpm-lock.yaml
#	src/router/modules/remaining.ts
#	src/utils/dict.ts
2026-05-01 08:22:54 +08:00
YunaiV 7ed6fa5579 feat(im): 增加群管理的完善 2026-05-01 08:19:13 +08:00
YunaiV 8564788b11 feat(im): 对齐微信的图标展示 2026-05-01 08:17:24 +08:00
YunaiV 92b1466597 feat(im): 增加群管理的 code review 2026-05-01 07:52:31 +08:00
YunaiV 238862b572 feat(im): 增加发送草稿,切换对话的时候,不丢失。对齐微信 2026-05-01 07:52:18 +08:00
YunaiV be654bce50 feat(im): 增加私聊消息的管理 2026-05-01 07:46:19 +08:00
YunaiV d64a695673 feat(im): 增加群聊消息的管理 2026-05-01 07:08:05 +08:00
YunaiV dfbae06afa feat(im): 优化输入框的样式 2026-05-01 06:59:14 +08:00
YunaiV 384a0c134a feat(im): 完成敏感词的管理 2026-04-30 22:25:25 +08:00
YunaiV 9f1fc9ef78 reviewed 2026-04-30 21:38:17 +08:00
YunaiV fd1ba30bdb feat(im): 优化好友列表的管理 2026-04-30 21:09:03 +08:00
YunaiV 01fff53aaf feat(im): 增加 im 的管理界面 2026-04-30 19:04:31 +08:00
YunaiV 4b4c4fab11 feat(im): 优化群聊的功能界面 2026-04-30 16:59:56 +08:00
YunaiV 368b385267 feat(im): 增加群邀请的功能 2026-04-30 15:47:32 +08:00
YunaiV 0ab8b292f2 feat(im): 增加 pinyin 功能 2026-04-30 15:22:35 +08:00
YunaiV d19bdd42d5 feat(im): 优化添加好友界面 2026-04-30 14:53:41 +08:00
YunaiV 0c7d1f0df6 feat(im): 新增通讯录界面 2026-04-30 14:07:03 +08:00
YunaiV a762dfff84 feat(im): 优化整体包结构,将 friend、group 通用组件抽过去。 2026-04-30 10:11:20 +08:00
YunaiV 4b64153044 feat(im): 完善 friend、group 相关的本地存储(疯狂优化) 2026-04-29 22:03:54 +08:00
YunaiV e90f9e5237 feat(im): 增加 friend、group 相关的本地存储 2026-04-29 15:50:49 +08:00
YunaiV de39bc7fc1 feat(im): 优化代码,移除 message 里的 name 存储,避免更新困难。(为 friend、group 独立存储做准备) 2026-04-28 23:32:40 +08:00
YunaiV f0fc144e8a feat(im): 调整代码结构,优化 side 样式 2026-04-28 20:14:24 +08:00
YunaiV 431a0bfb93 feat(im): 调整代码结构,优化 side 样式 2026-04-28 20:13:01 +08:00
YunaiV ba34e4adc0 feat(im): 优化整体 message 包结构 2026-04-28 09:30:12 +08:00
YunaiV 29a03ef03d feat(im): 优化整体 message 包结构 2026-04-28 09:29:40 +08:00
YunaiV 122b1ba748 feat(im): 优化 message 的导入 2026-04-28 08:48:38 +08:00
YunaiV 56b0630847 feat(im): 优化 icon 的导入 2026-04-28 08:15:29 +08:00
YunaiV 6ead932813 feat(im): 优化 icon 的导入 2026-04-28 08:15:10 +08:00
YunaiV 9fc10b304c feat(im): 增加 ChatPanel.vue 组件 2026-04-28 01:15:04 +08:00
YunaiV 4c8898b6f5 🐛 fix(im): 上传 URL 取错字段,粘贴图片 / 文件 / 语音消息加载失败
axios 配置里 request.upload 直接返回完整 axios response(不是 res.data,
跟 get/post/put 不一致),原代码 (await updateFile(form)) as unknown as string
把整个 {data, status, headers, ...} 对象当成 URL 塞进消息 JSON,接收端
<el-image src> 拿到的是序列化串自然加载失败。

uploadAndSendImage / uploadAndSendFile / onVoiceSend 三处统一改成 .data 取值:
  ((await updateFile(form)) as { data?: string })?.data
跟 mall PictureSelectUpload / bpm SignDialog 等其它业务代码取 URL 的方式一致。
2026-04-28 01:14:24 +08:00
YunaiV 9c5b11e551 feat(im): 支持历史消息的加载 2026-04-28 01:08:45 +08:00
YunaiV e9be6ef8b3 feat(im): 增加群消息的回执开关,通过向下箭头 2026-04-27 23:56:50 +08:00
YunaiV 29695b649a feat(im): 增加群消息的回执开关,通过向下箭头 2026-04-27 23:54:41 +08:00
YunaiV 8847cdb79f feat(im): 新增 MessageReadStatus.vue 2026-04-27 22:36:47 +08:00
YunaiV bfa267120a ♻️ refactor(im): MessageItem 头像顶右 + MentionPicker/MessageInput 命名清理
【MessageItem.vue】
- 头像合一:双 v-if 头像(左/右)收成单一 <UserAvatar>,DOM 顺序固定为
  [头像, 气泡],selfSend 靠外层 flex-row-reverse 翻视觉 → 头像顶右、气泡在
  头像左侧。早先双 v-if + row-reverse 让自己消息时气泡顶右、头像反而被
  挤在 bubble 左边,跟微信观感不对齐
- 5 处脚本 TODO 注释补齐:groupMembersForReadStatus / handleContextMenu /
  handleRecall / handleDelete,解释 WHY 而不是 WHAT
- formatTipTime 局部变量按"不缩写"展开:d → messageDate / n → value /
  hm → hourMinute / (a,b) → (left,right) / weeks → weekNames
- senderAvatar / groupMembersForReadStatus 回调参数 m → member、g → group

【MessageInput.vue】
- groupMembers producer 局部变量 g → group、(m) => → (member) =>

【MentionPicker.vue】
- memberItems 过滤回调 (m) => → (member) =>
2026-04-27 21:48:34 +08:00
YunaiV 8fd21da555 🐛 fix(im): TIP_TEXT 系统提示不再显示空白
群解散 / 退群 / 踢人 等系统提示后端发的是裸字符串,之前按 TextMessage JSON
解析 → 主聊天窗显示空行、会话列表摘要变空。

- message.ts:新增 resolveTipText helper,兼容裸字符串 + {"content":"..."}
- MessageItem / conversationStore.resolveLastContent 把 TIP_TEXT 从 TEXT
  分支拆出来,统一走 resolveTipText(TEXT 仍按 JSON 解析,没有裸字符串可能)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 19:59:56 +08:00
YunaiV 9e8d04249c 🐛 fix(im): TIP_TEXT 系统提示不再显示空白
群解散 / 退群 / 踢人 等系统提示后端发的是裸字符串,之前按 TextMessage JSON
解析 → 主聊天窗显示空行、会话列表摘要变空。

- message.ts:新增 resolveTipText helper,兼容裸字符串 + {"content":"..."}
- MessageItem / conversationStore.resolveLastContent 把 TIP_TEXT 从 TEXT
  分支拆出来,统一走 resolveTipText(TEXT 仍按 JSON 解析,没有裸字符串可能)
2026-04-27 19:56:54 +08:00
YunaiV cb5d30e327 feat(im): 新增 MessageItem.vue 2026-04-27 19:11:31 +08:00
YunaiV ccc9aca21c feat(im): MessageInput 工具栏挪到底部 + 4 图标统一 Iconify + 聊天历史挪到右上角
对齐微信 PC:输入区在上、操作图标在下;会话级操作(如聊天历史)统一放 header 右上角

【MessageInput.vue】
- 模板顺序对调:editor 在上 / 工具栏在下(justify-between:左 4 图标 gap-1 / 右"发 送"按钮)
- editor min-height 80 → 100、padding 8/12 → 10/14,输入区视觉权重接近微信
- 4 个图标统一走 Iconify ant-design outlined 同源,避免 ep / antd 混用视觉割裂:
  - 表情:Sunny → ant-design:smile-outlined(Element Plus 没有 smile,必须走 Iconify)
  - 图片:Picture → ant-design:picture-outlined
  - 文件夹:Paperclip → ant-design:folder-outlined(附件 → 文件夹更贴近微信观感)
  - 语音:Microphone → ant-design:audio-outlined
  - 整条 @element-plus/icons-vue import 删除,全部改 <span class="message-input__tool inline-flex …">
    + <Icon icon="…" :size="18" /> 的统一外壳;scoped CSS 的 :deep(svg) 继续命中,padding / hover
    样式不动;DOM 实测 4 图标全部 30×30、top:761、间距 34px 完全对齐
- EmojiPicker class:bottom-9 left-3 → bottom-full left-3 mb-2,picker 从工具栏顶部向上弹出
  (旧值在新布局下会浮在工具栏内部,盖住图标)
- 删 defineEmits<{ openHistory }>():聊天历史挪到 ChatPanel header 后已没有调用方

【ChatPanel.vue】
- header 右上角新增"聊天历史"图标(Tickets),点击直接 historyVisible = true 弹"历史消息"抽屉
  (对齐微信 PC:右上角集中放会话级操作;并列在原"聊天信息 / 群聊信息"图标左侧)
- <MessageInput :key="…" @open-history="…"> 上的 listener 摘掉,emit 链路完整解耦
2026-04-27 15:46:13 +08:00
YunaiV fc82ed3d7e ♻️ refactor(input): 优化粘贴文件处理逻辑,简化代码结构 2026-04-27 14:30:38 +08:00
YunaiV cba5c15604 feat(im): MessageInput / MentionPicker / ChatPanel 三连修——粘贴文件、切群清空、命名规范
【ChatPanel.vue】
- 加 messageInputKey computed(type-targetId)+ MessageInput :key 绑它,
  切会话强制 unmount + remount editor / mention range / 草稿全归零,
  避免 A 群打了一半的字 / @ token 漏到 B 群被发出去
  (早先用 inline template literal 做 :key,Vue SFC 编译没把表达式接到
  vnode.key 上,hmr / 完整 reload 都看到 key=null;改 computed 后正常)
【MessageInput.vue】
- onPaste 加 clipboardData.items 扫一轮:image/* → uploadAndSendImage,
  其它 file → uploadAndSendFile,纯文本兜底走 nativeExec('insertText');
  截图 / 拖入图片 / 拖入文件不再被默默吞掉
- 抽 uploadAndSendImage / uploadAndSendFile 两个共用函数,
  onImagePicked / onFilePicked 改成薄包装走它们,避免上传逻辑双份
- 删 nativeExec 里的 // eslint-disable-next-line @typescript-eslint/no-deprecated:
  项目当前 @typescript-eslint v7 没有这条规则,加了会让 lint 报"规则不存在",
  反而把 lint 拖红;改用单纯 JSDoc 解释为什么留着 execCommand
- 重命名 mentionPos → mentionPosition(prop / ref 一致),按"变量不缩写"
- 7 个方法补 JSDoc:onSelectionChange / insertText / onPaste / onInput /
  onKeydown / onImagePicked / onFilePicked / onVoiceSend;复杂的
  collectFromEditor 和 handleSend 加分步 1./2./3. 内联注释
- data-empty 改用属性"存在 / 缺失"模拟(template 里 data-empty="",JS 里
  raw 为空就 set ''、否则 delete),CSS 选择器同步改 [data-empty],
  比 [data-empty='true'] 直观
【MentionPicker.vue】
- prop pos → position(不缩写);ref / 内部解构 / 默认值都跟着改
- <el-icon><UserFilled /></el-icon> → <Icon icon="ep:user-filled">:
  用全局 Icon 组件走 Iconify,少一个 EP 图标 import
- scrollToTop / scrollToActive 局部变量 wrap → scrollWrap、
  itemH → itemHeight、activeTop → activeOffsetTop;
  v-for 与 handleSelect 的 (m) → (member)
2026-04-27 13:57:18 +08:00
YunaiV 678c2d6834 feat(im): MessageInput 切 contenteditable + MentionPicker 对齐微信,修一堆 @ 浮层 bug
【MessageInput.vue】
- textarea → contenteditable div:拿真·光标 rect 给浮层定位(textarea 拿不到),@ 成员
  以 <span data-id contenteditable=false> token 节点存在,删 token 即删 atUserIds
- collectFromEditor:DOM walk 还原 plain text + atUserIds(text / br / span[data-id] /
  div / 其他元素 五种节点分支),过滤零宽空格
- handleSend:从 DOM 收集而非 ref<string>,atUserIds 走 Set 去重;分步注释
- placeholder 用 [data-empty]::before + JS 维护属性"存在 / 缺失"模拟,避开浏览器删空
  后留 <br> 让 :empty 不命中
- @ 浮层位置:bottom 锚定(picker 下沿贴 @ 上方 8px),无论候选多寡下沿固定,不再
  随 picker 高度变化漂移;上方放不下才翻成 top 锚定到 @ 下方
- @ 浮层规则:regex 改成 `(?:^|\s)@([^\s@]*)$`,避免 email-like "test@example.com"
  误触发;锚定在 @ 字符位置而非 caret,否则用户每多敲一字浮层右移
- click outside 关浮层:document mousedown 监听,target 不在 editor / picker 内即关
- Enter 兜底:mention 浮层无候选时 fall through 到正常发送,避免按 Enter 没反应
- token 首位 ZWSP:token 是 editor 第一个节点时 contenteditable=false 边缘会让光标
  无法挪到 token 前,补一个零宽空格当锚点;DOM walk 滤掉
- Shift+Enter 强制 br(execCommand insertLineBreak),DOM walk 不必处理多换行容器
- onPaste 用 execCommand('insertText') 剥光所有 HTML,不留外部样式 / 脚本
- onEditorScroll 同步浮层位置,多行 + 滚动条场景下 picker 跟随 caret
- selection 保存:document selectionchange 监听 + 仅 editor 内时记录,emoji 面板偷
  焦点后能回到原位

【MentionPicker.vue】
- 视觉对齐微信 PC:顶部"所有人"虚拟项(蓝方块 + UserFilled 图标)+ "群成员"分组
  header + 底部三角指针;rounded-md + soft shadow
- "全体成员" → "所有人";userId=-1 / 文案常量化到 utils/constants.ts
  (IM_AT_ALL_USER_ID / IM_AT_ALL_NICKNAME),三个文件共用,不再散落
- !fixed + !h-75 / max-height:用 UnoCSS important 变体压过 Element Plus 的
  .el-scrollbar { position:relative; height:100% } 默认 CSS——之前 picker 落到父
  容器坐标系导致 y=1326 飞出视口外,肉眼看不到的根因
- pos prop 从 {x, y} → {x, top?, bottom?},配合 MessageInput 的 bottom 锚定
- allItem / memberItems 拆成两个 computed,showMembers 做扁平合并供键盘导航;
  群成员上限 100 去掉,浮层本就支持滚动
- 5 个内部函数 / watch 全部补 JSDoc(showMembers / visible 两个 watch、scrollToTop /
  scrollToActive / handleSelect)
2026-04-27 13:21:27 +08:00
YunaiV 3ea04663f2 feat(im): IM 5 个 store 补 HMR + 抽 atAll 常量 + 全面补齐 JSDoc
- 全部 5 个 store(conversation / friend / group / ui / websocket)加
  acceptHMRUpdate;Pinia 单例的 actions 是 wrapper 闭包,Vite 推新模块时
  不会自动替换闭包内的旧函数体,导致改 store 后看着热重载、跑的还是旧逻辑
- 抽 IM_AT_ALL_USER_ID(-1)+ IM_AT_ALL_NICKNAME('所有人')到
  utils/constants.ts;conversationStore 删本地 AT_ALL_FLAG 改用共享常量;
  MentionPicker 渲染虚拟项 / ChatGroupMember 类型注释也都引这两个常量
- groupStore.loadGroups 改成合并而非全量替换:用 groupMap 按 id 找已有项,
  保留 loadGroupMembers 写过的 members / memberCount / muted(这三个字段
  不在 ImGroupRespVO 里,全量替换会被冲掉)
- groupStore.loadGroupMembers 重写为分步注释(1. 缓存 / 2. 拉取 /
  3. 回填 muted / 4.1 占位 / 4.2 直写);await 之后必须重新 getGroup
  防 race(loadGroupMembers 与 loadGroups 并发时用入口快照会把真实 name
  覆盖成 String(groupId))
- types/GroupMember 补 muted 字段,convertGroupMember 透传,
  解决 vue-tsc TS2339 / TS2353
- 5 个 store 缺 JSDoc 的方法全部补齐:removePrivateConversation /
  removeGroupConversation / getFriend / getActiveFriends / isFriend /
  loadGroupInfo / upsertGroup / stopHeartbeat
- 全局"墓碑"措辞统一为"软删保留记录",types / friendStore / groupStore 三处
- groupStore 删冗余注释(与代码自描述重复的)若干处;变量 g/old 改 group/existing
2026-04-27 13:10:15 +08:00
YunaiV a0ed0d800c feat(im): 群聊免打扰接入后端,完善免打扰失败回滚 + ContextMenu 微调
- groupStore.setMuted 改 async,调 /im/group-member/update 推后端
- GroupMember.muted 在类型层补齐;convertGroupMember 保留 muted;
  loadGroupMembers 拉完成员后用当前用户那条 member.muted 回填 group.muted
  与 conversation.muted,避免冷启动后服务端已免打扰的群在会话列表里仍显示为
  未免打扰
- ConversationItem.handleMuted 失败回滚:catch 后 ElMessage.error 并反向
  setMuted 把 conversationStore(已 saveConversations 落盘)拽回正确状态
- ContextMenu 分割线改用 h-[1px] + bg(UnoCSS 不带 border-style preflight,
  border-t 在空内容 div 上不显形),文案 text-center → text-left 贴近微信
- groupStore.setMuted 改 async 后,ConversationItem 里两路 setMuted 调用
  都用 void 显式 fire-and-forget,风格统一
2026-04-27 09:29:49 +08:00
YunaiV 45a530e8c7 feat(im): 新增 MentionPicker.vue、MessageInput.vue、VoiceRecorder.vue 三个组件,vibe~ 2026-04-27 09:20:10 +08:00
YunaiV 6add0b0600 feat(im): 群聊免打扰接入后端,完善免打扰失败回滚 + ContextMenu 微调
- groupStore.setMuted 改 async,调 /im/group-member/update 推后端
- GroupMember.muted 在类型层补齐;convertGroupMember 保留 muted;
  loadGroupMembers 拉完成员后用当前用户那条 member.muted 回填 group.muted
  与 conversation.muted,避免冷启动后服务端已免打扰的群在会话列表里仍显示为
  未免打扰
- ConversationItem.handleMuted 失败回滚:catch 后 ElMessage.error 并反向
  setMuted 把 conversationStore(已 saveConversations 落盘)拽回正确状态
- ContextMenu 分割线改用 h-[1px] + bg(UnoCSS 不带 border-style preflight,
  border-t 在空内容 div 上不显形),文案 text-center → text-left 贴近微信
- groupStore.setMuted 改 async 后,ConversationItem 里两路 setMuted 调用
  都用 void 显式 fire-and-forget,风格统一
2026-04-27 09:03:18 +08:00
YunaiV 7f84c428a5 feat(im): 优化 ConversationItem.vue,对齐微信的时间展示。 2026-04-27 08:51:35 +08:00
YunaiV e85f8edcaa feat(im): 优化 ConversationItem.vue,对齐微信交互 2026-04-27 08:42:39 +08:00
YunaiV 115e0482db feat(im): 优化 ConversationItem.vue 增加相关评审 2026-04-27 08:22:14 +08:00
YunaiV a1a9053aaa feat(im): 优化 MessagePage.vue 页面,对齐微信交互 2026-04-27 00:51:31 +08:00
YunaiV 1a0c11f685 feat(im): 优化 MessagePage.vue 页面,对齐微信交互 2026-04-27 00:51:15 +08:00
YunaiV e1b52be8ea feat(im): 新增 MessagePage.vue 页面 + 部分 review 2026-04-27 00:01:43 +08:00
YunaiV 8790d6b128 feat(im): 优化 ToolBar.vue 组件
- tabs / goTab / goProfile 从 path 切到路由 name:path 后期容易随前缀调整变动,name 更稳定
- isActive 由 path 前缀比对简化为 route.name 全等比对(IM 三 Tab 无嵌套子路由,足够)
- 群聊图标 svg-icon:peoples → ant-design:team:三人组合跟 ep:user(单人)视觉区分更明显
- 给 goTab / goProfile 补 JSDoc;清理 5 个 TODO @AI
2026-04-26 23:45:27 +08:00
YunaiV 3a77001b42 🐛 fix(im): 修复主壳初始化期间消息漏拉 / 缓冲回放失效
三处时序竞态修复:
- loading=true 提前到 connect 前,避免 WS 早于 pullOnce 推进 maxId 漏拉断线积压
- loading=false 提到 flushBuffer 前,让回放走正常 insertMessage 而非被 push 回 buffer
- 加 bootstrapped 守卫,避免 isConnected watcher 在 friend/group 加载完前抢跑

附带:主壳文件名 Index.vue → index.vue 对齐其他模块小写惯例;清理 5 个 TODO @AI。
2026-04-26 23:32:55 +08:00
YunaiV 8a7991261f 🐛 fix(im): 主动断开 WS 后不再自动重连,同步复位 isConnected
disconnect() 之前调用 socket.close() 是异步触发 onclose,回调里会
无条件走 reconnect,导致离开 IM 主壳后 3 秒又会在后台重新打开 WebSocket。
修复:
- close 前先解绑 onclose / onerror handler,主动关闭路径不再走自动重连
- onclose 已被解绑后没人帮我们设 isConnected=false,disconnect 内手动复位,
  避免 socket=null 但 isConnected=true 的状态不一致
2026-04-26 23:08:23 +08:00
YunaiV 1e02a40ec4 feat(im): 添加 IM 聊天模块的全屏容器组件,并添加相关 review 注释 2026-04-26 22:02:02 +08:00
YunaiV 9570f25cdc feat(im): 优化 ToolBar.vue 组件 2026-04-26 21:51:09 +08:00
YunaiV d37af6d959 feat(im): 优化 PagedScroller.vue 组件 2026-04-26 21:46:13 +08:00
YunaiV 802a10cf85 feat(im): 优化 ResizableAside.vue 组件 2026-04-26 21:35:27 +08:00
YunaiV a973406b2a feat(im): 优化 ContextMenu.vue 组件 2026-04-26 20:19:19 +08:00
YunaiV 43771b0f47 feat(im): 优化 EmojiPicker.vue 组件 2026-04-26 19:49:44 +08:00
YunaiV 20c6631e7a feat(im): 新增 UserInfoCard.vue 2026-04-26 19:08:02 +08:00
YunaiV 9a4e79e4ef 🐛 fix(im): conversation.messages 入 IDB 前用 toRaw 解 Proxy,否则 structuredClone 抛 DataCloneError 静默丢消息 2026-04-26 17:57:50 +08:00
YunaiV f1d44c8267 feat(im): 优化 UserAvatar.vue 通用用户头像组件 2026-04-26 17:52:32 +08:00
YunaiV 969d8237ce feat(im): 增加 UserAvatar.vue 通用用户头像组件 2026-04-26 17:32:47 +08:00
YunaiV f929ebc184 feat(im): 增加 conversationStore.ts 未来的优化 todo; 2026-04-26 16:13:58 +08:00
YunaiV 2c1ff59286 feat(im): 初始化 useMessageSender.ts 2026-04-26 15:56:24 +08:00
YunaiV e573462cb7 feat(im): 增加 useMessagePuller 用于首次消息的拉取 2026-04-26 10:38:14 +08:00
YunaiV 8c1f17f5a6 🐛 fix(im): 私聊已读消费端卡 maxReadId + 上报 messageId 与后端对齐
handlePrivateReceipt 收到对方 RECEIPT 时丢弃了后端编码在 DTO id 字段
的 maxReadId,applyReadReceipt 把会话里所有 selfSend 未撤回消息一刀切
标 READ;回执在路上时刚发的消息会被误标已读。
- applyReadReceipt 的 markPrivateRead 改为 privateReadMaxId,按
  id <= maxReadId 卡边界,超过 maxReadId 的自发消息保留原状态;
- handlePrivateReceipt 透传 websocketMessage.id 作为 privateReadMaxId;
- apiReadPrivateMessages 增加 messageId 形参,与后端新接口对齐;
- websocketStore 私聊自动已读用刚到的消息 id;useMessageSender.readActive
  把私聊 / 群聊的 maxMessageId 计算合并到调用前。
2026-04-26 09:46:09 +08:00
YunaiV a35698fc07 🐛 fix(im): 群聊离线拉取看不到撤回提示,pull 路径接入 recallMessage
pullByType 之前对 RECALL 信号一律 skip、只靠原消息 status=RECALL 走 OR 兜底渲染。
当 pull 的 minId 卡在原消息处、回拉只返回信号时,本地缓存里的老消息没人翻成
RECALL,会一直停在原态——配合后端群聊 mapper 过滤掉 status=RECALL 的原消息,群聊
离线撤回完全不可见。

改成 pull / WS 走同一套 dispatch:
- pullByType 信号转 conversationStore.recallMessage(),跟 WS 路径一致
- recallMessage 把 parseRecallMessageId 收敛进内部,第 3 个参数从
  messageId: number 改成 recallSignalContent: string,4 个调用点都缩成一行
- MessageItem.isRecall 只判 type=RECALL,去掉 status=RECALL OR 分支
  (conversationStore 里跳未读 / 跳已读那两处对 status 的判断是业务逻辑保留)
2026-04-26 00:28:43 +08:00
YunaiV 66514fc597 ♻️ refactor(im): conversationStore 存储改为 IndexedDB 按会话分桶 + 命名统一
- 持久化迁到 localforage(IndexedDB),meta 索引与单会话 messages 分 key 存,消除写放大
- saveConversations 支持 不传 / 单个 / 数组 三种粒度;签名改为 sync void(fire-and-forget)
- 修复 sortConversations 仅刷 meta 不刷 messages 导致离线消息重启丢失的 bug
- 方法重命名:saveToStorage→saveConversations、updateMessageState→ackMessage、applyRecall→recallMessage、refreshConversations→sortConversations、removeLocalMessage→removeMessage、_removeMessagesStorage→removeConversationMessages
- 删除 dead field Conversation.lastReadCount;TIP_TIME clientMessageId 改用 uuid
2026-04-25 22:52:00 +08:00
YunaiV 2785e2bea6 feat(im): 重构优化 store 方案 2026-04-25 16:45:31 +08:00
YunaiV e30e30ea51 🐛 fix(im): 撤回信号错用 TIP_TEXT,应为 RECALL 2026-04-25 11:42:34 +08:00
YunaiV 505b3b5953 feat(im): 重构部分老代码。 2026-04-24 22:55:58 +08:00
YunaiV a28694074e 🔧 chore(deps): 添加 localforage 依赖:https://localforage.docschina.org/ 2026-04-24 22:12:54 +08:00
YunaiV d6f96a56a2 feat(im): 优化 ConversationItem.vue 逻辑 2026-04-24 21:54:20 +08:00
YunaiV 68d3ad10d4 feat(im): 优化 im 前端的工具类 2026-04-24 21:36:09 +08:00
YunaiV 5f16cd74e0 feat(im): 优化 im 前端的枚举类 2026-04-24 00:50:09 +08:00
YunaiV 6664afb851 ♻️ refactor(im): 重命名 IM 模块中的多个类和文件以简化结构 2026-04-23 20:38:27 +08:00
YunaiV 6d6515c06d ♻️ refactor(im): 用户查询接口迁移到 system/user
- /api/im/user:移除 getSelfInfo/getUserListByName,改用 /system/user/get-simple、/system/user/list-by-nickname
- AddFriendDialog:切换为 getSimpleUserListByNickname,去掉已废弃的 userName 展示
2026-04-23 01:36:45 +08:00
guoanhao 11495a64f5 fix(bpm):修复流程网关分支问题 2026-04-22 17:37:30 +08:00
YunaiV 76be404c69 🎨 style(Message): 修复铃铛对齐问题,调整样式为 flex-center 2026-04-19 00:30:34 +08:00
YunaiV 9fb796194e feat(im): 前端的 IM 增加 Layout 整体布局 2026-04-19 00:30:07 +08:00
YunaiV 518851ce74 merge: 合并 master 分支到 im
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 09:38:25 +08:00
DevDengChao 14edd68d77 test: harden e2e auth setup
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 16:23:50 +08:00
DevDengChao 968a1ccb40 chore: upgrade typescript to 6.0.2
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 14:54:46 +08:00
DevDengChao c163ed152c chore: upgrade unplugin-vue-components
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 14:46:30 +08:00
DevDengChao 2499d59f28 chore: upgrade console runtime dependencies
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 14:38:29 +08:00
DevDengChao 176cddc21f chore: upgrade console dev dependencies
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 14:33:43 +08:00
zhulianghu 74128f53a5 fix: 修复响应拦截器 code=0 的逻辑错误 2026-04-01 09:53:32 +08:00
lijiahui 491e09c136 fix: 菜单名称过长时没有正确显示省略号 2026-03-30 15:31:51 +08:00
DevDengChao 5959539a03 docs: add Vite 8 upgrade and validation notes
- document Vite 8 config migration notes
- record effective upgrade validation commands and existing baseline issues

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-03-23 11:10:23 +08:00
DevDengChao 26c7544829 chore: upgrade vite to 8.0.1 and @vitejs/plugin-legacy to 8.0.0
- vite
- @vitejs/plugin-legacy
- adapt vite.config.ts for Vite 8 code splitting and Lightning CSS recovery

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-03-23 11:08:26 +08:00
DevDengChao c792f5fa0f chore: upgrade stylelint-order to 8.1.1
- stylelint-order

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-03-23 11:00:59 +08:00
DevDengChao f497bf8e23 chore: upgrade wangEditor and BPMN packages
- @wangeditor-next/editor
- @wangeditor-next/plugin-mention
- bpmn-js
- diagram-js
- bpmn-js-token-simulation

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-03-23 10:58:02 +08:00
DevDengChao eade6bd9a4 chore: upgrade eslint, stylelint, typescript-eslint and unocss
- eslint
- stylelint
- typescript-eslint
- unocss
- @unocss/eslint-config
- @unocss/eslint-plugin
- @unocss/transformer-variant-group

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-03-23 10:53:25 +08:00
DevDengChao 91c97d7302 chore: upgrade @vitejs/plugin-vue*, rollup, sass, vue-tsc and Vite helpers
- @vitejs/plugin-vue
- @vitejs/plugin-vue-jsx
- rollup
- sass
- vite-plugin-eslint2
- vite-plugin-svg-icons-ng
- vue-tsc

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-03-23 10:48:47 +08:00
DevDengChao 27b3c36976 chore: upgrade vue, vue-router, element-plus, vue-i18n and dayjs
- vue
- vue-router
- element-plus
- vue-i18n
- dayjs

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-03-23 10:43:21 +08:00
DevDengChao bfcce06577 chore: upgrade @commitlint/*, @types/*, lint-staged, terser and @iconify/json
- @commitlint/cli
- @commitlint/config-conventional
- @iconify/json
- @types/node
- @types/qs
- lint-staged
- terser

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-03-23 10:39:12 +08:00
puhui999 dfee5b999d style(iot): 优化 Database 数据目的的建表提示UX
- 表名输入框右侧附加「查看/收起表结构提示」按钮
- 引入 el-collapse-transition 结合酷炫终端卡片实现平滑的折叠动画
- 修正 Vue template 中由于缺少闭合 div 导致的语法编译错误
2026-03-13 12:46:57 +08:00
puhui999 9f19835a80 feat(iot): Database 表单增加建表 SQL 提示和一键复制
- 顶部 el-alert 友好提示用户需要先创建表
- 内嵌 SQL 文本框(monospace字体) + 复制按钮
- tableName 默认值设为 iot_device_message_sink
2026-03-13 12:30:04 +08:00
puhui999 ad376b24b4 feat(iot): 前端新增 Database 数据目的配置表单
1. DatabaseConfigForm.vue: 新增 Database 配置表单(JDBC地址/用户名/密码/目标表名)
2. config/index.ts: 导出 DatabaseConfigForm 组件
3. DataSinkForm.vue: 引入 DatabaseConfigForm 条件渲染和校验规则
4. api/sink/index.ts: 添加 DatabaseConfig 接口和联合类型
2026-03-13 12:27:09 +08:00
funcong 8cffb4a8ca fix: 修复请求拦截器bug 2026-03-10 18:48:03 +08:00
DevDengChao a997f25f98 Merge remote-tracking branch 'upstream/master' into upgrade 2026-03-09 09:57:30 +08:00
DevDengChao 52e538aa43 fix(router): auto-reload on chunk load failure after rebuild
Add two layers of error handling for stale chunk imports:
- `vite:preloadError` listener in main.ts for Vite preload failures
- `router.onError` in router/index.ts for dynamic import failures during navigation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:31:02 +08:00
DevDengChao c4908548a1 feat(cropper): upgrade cropperjs from v1 to v2
cropperjs v2 is a complete rewrite using Web Components architecture.

- Cropper.vue: rewrite to use v2 API
  - `new Cropper(img, { container })` with Web Components template
  - `selection.$toCanvas()` (async) replaces `cropper.getCroppedCanvas()`
  - Selection `change` event replaces `crop`/`cropmove` callbacks
  - CropperImage `load` event replaces `ready` callback
- CopperModal.vue: update toolbar handlers
  - `cropperImage.$rotate()` replaces `cropper.rotate()`
  - `cropperImage.$zoom()` replaces `cropper.zoom()`
  - `cropperImage.$scale()` replaces `cropper.scaleX/Y()`
  - `cropperImage.$resetTransform()` + `selection.$reset()` replaces `cropper.reset()`
- types.ts: replace `Cropper.Data` with inline type (v2 has no Data type)
- Remove v1 CSS import (v2 uses shadow DOM styles)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:37:34 +08:00
DevDengChao 2520de56b4 refactor(icon): replace deprecated @iconify/iconify with @iconify/vue
- Remove @iconify/iconify (deprecated), @purge-icons/generated, vite-plugin-purge-icons
- Add @iconify/vue which uses @iconify/utils iconToSVG internally
- Rewrite Icon.vue to use @iconify/vue Icon component instead of manual DOM manipulation
- Pre-load ep/fa/fa-solid icon sets via addCollection for offline support
- Other icon sets (ion, mdi, heroicons, etc.) load from Iconify API on demand
- Remove PurgeIcons() from Vite plugin config
- Verified: all 22 icons on login page render correctly as SVGs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 12:26:12 +08:00
DevDengChao 860d2c0b29 chore(deps): minor updates for bpmn-js and purge-icons
- bpmn-js 18.12.0 → 18.13.0
- bpmn-js-properties-panel 5.52.1 → 5.53.0
- @purge-icons/generated 0.9.0 → 0.10.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:22:25 +08:00
DevDengChao 53a1024c11 chore(deps): patch updates for UnoCSS ecosystem (66.6.x)
- unocss 66.6.2 → 66.6.5
- @unocss/eslint-config 66.6.3 → 66.6.5
- @unocss/eslint-plugin 66.6.3 → 66.6.5
- @unocss/transformer-variant-group 66.6.3 → 66.6.5

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:19:42 +08:00
DevDengChao 037b465a64 chore(deps): patch updates for toolchain (commitlint, lint-staged, postcss, iconify/json)
- @commitlint/cli 20.4.2 → 20.4.3
- @commitlint/config-conventional 20.4.2 → 20.4.3
- lint-staged 16.3.1 → 16.3.2
- postcss 8.5.6 → 8.5.8
- @iconify/json 2.2.444 → 2.2.446

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:17:06 +08:00
DevDengChao ac6097aa9a fix: resolve ESLint 10 + eslint-plugin-vue 10 rule compat for dev server
Disable rules that are new/stricter in eslint-plugin-vue 10 and
typescript-eslint 8+ which would block dev server rendering via
vite-plugin-eslint2:
- vue/no-ref-as-operand, vue/no-mutating-props,
  vue/no-side-effects-in-computed-properties
- @typescript-eslint/no-unused-expressions, no-unsafe-function-type,
  no-wrapper-object-types, no-this-alias, no-empty-object-type
- Ignore auto-generated src/types/auto-components.d.ts
- Fix hasPermi.ts short-circuit expression

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 01:00:29 +08:00
DevDengChao cb5f0fb3f0 fix(deps): restore @iconify/iconify required by @purge-icons/generated
Re-added @iconify/iconify as runtime dependency — it's imported by
@purge-icons/generated which is used in src/plugins/svgIcon/index.ts.
Also reverted @purge-icons/generated 0.10 back to 0.9 (both versions
have the same @iconify/iconify import).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 20:56:06 +08:00
DevDengChao 49f0fb06f4 chore(deps): BPMN ecosystem upgrade and cleanup (Phase D)
- diagram-js 12 → 15, min-dash 4 → 5
- Remove unused fast-xml-parser (replaced by steady-xml)
- @purge-icons/generated 0.9 → 0.10

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 20:50:46 +08:00
DevDengChao c0845eae75 chore(deps): medium-risk major upgrades (Phase C)
- stylelint 16 → 17, config-recommended 14 → 18, config-standard 36 → 40, order 6 → 7
- vue-types 5 → 6
- video.js 7 → 8
- cropperjs v2 evaluated but reverted to v1 due to incompatible API rewrite

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 20:46:12 +08:00
DevDengChao e31423bc6d chore(deps): low-risk major upgrades (Phase B)
- Remove deprecated @iconify/iconify (replaced by @purge-icons/generated)
- @commitlint/cli + config-conventional 19 → 20
- lint-staged 15 → 16
- rimraf 5 → 6
- markmap-common/lib/toolbar/view 0.16-0.17 → 0.18
- vue3-signature 0.2 → 0.4
- vue-dompurify-html 4 → 5

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 20:35:24 +08:00
DevDengChao 5284b00706 chore(deps): update all semver-compatible packages to latest
Bump ~23 packages within their declared semver ranges, including:
- vue 3.5.26 → 3.5.29
- dayjs, lodash-es, qs, highlight.js, jsencrypt
- prettier, postcss, rollup, terser, autoprefixer
- and others

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 20:33:08 +08:00
DevDengChao 550c30eae4 chore: post-upgrade cleanup
- Update Node.js engine requirement to >= 20.19.0 (Vite 7 requirement)
- Remove duplicate entries in optimizeDeps include list
- Remove build-time-only packages (sass, unocss) from optimizeDeps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 20:12:44 +08:00
DevDengChao fd11e07e92 chore(deps): upgrade Vite 5.1.4 → 7.3.1 and ecosystem plugins
Core:
- vite 5.1.4 → 7.3.1
- @vitejs/plugin-vue 5 → 6.0.4
- @vitejs/plugin-vue-jsx 3 → 5.1.4
- @vitejs/plugin-legacy 5 → 7.2.1
- @types/node 20.17.9 → 25.3.3

Plugins:
- unplugin-auto-import 0.16.7 → 21.0.0
- unplugin-vue-components 0.25.2 → 31.0.0
- unplugin-element-plus 0.8.0 → 0.11.2
- vite-plugin-svg-icons-ng 1.3.1 → 1.5.2
- vite-plugin-top-level-await 1.4.4 → 1.6.0

Config:
- Switch to Sass Modern Compiler API (api: 'modern-compiler')
- Remove silenceDeprecations workaround

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:49:57 +08:00
DevDengChao f4b8fea579 chore(deps): upgrade ECharts 5.5.0 → 6.0.0
Note: echarts-wordcloud has unmet peer dep (expects echarts ^5)
but works at runtime. Monitor for official v6 support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:46:13 +08:00
DevDengChao 6ffcbbc1d2 chore(deps): migrate ESLint 8 → 10 with flat config
- Upgrade eslint 8.57.1 → 10.0.2
- Migrate .eslintrc.js → eslint.config.mjs (flat config)
- Replace vite-plugin-eslint → vite-plugin-eslint2
- Replace @typescript-eslint/eslint-plugin + parser → typescript-eslint
- Upgrade eslint-plugin-vue 9 → 10, vue-eslint-parser 9 → 10
- Remove eslint-define-config, eslint-config-prettier, eslint-plugin-prettier
- Delete .eslintignore (now handled in flat config ignores)
- Remove deprecated rules: vue/script-setup-uses-vars, vue/no-setup-props-destructure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:44:40 +08:00
DevDengChao 53f779afa2 chore(deps): upgrade UnoCSS 0.58.5 → 66.6.2 and related packages
- unocss 0.58.9 → 66.6.2
- @unocss/eslint-config 0.57.7 → 66.6.3
- @unocss/eslint-plugin 66.1.0-beta.5 → 66.6.3
- @unocss/transformer-variant-group 0.58.9 → 66.6.3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:38:32 +08:00
DevDengChao 643eb92aff chore(deps): upgrade Vue I18n 9.10.2 → 11.2.8, fix related compat issues
- Upgrade vue-i18n to 11.2.8, @intlify/unplugin-vue-i18n to 11.0.7
- Remove vue-i18n CJS alias from vite.config.ts (no longer needed)
- Remove vue-i18n from optimizeDeps include list
- Upgrade bpmn-js-token-simulation 0.36.2 → 0.39.2 (ids@3 compat)
- Fix duplicate route name 'Redirect' (Vue Router 5 enforces uniqueness)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:36:31 +08:00
DevDengChao 7991028e0a chore(deps): upgrade @vueuse/core 10.9.0 → 14.2.1
Crosses 4 major versions. All composables used by the project remain
compatible. All 24 E2E tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:16:43 +08:00
DevDengChao c0414a563f chore(deps): upgrade Vue Router 4.4.5 → 5.0.3
Major version with no breaking changes for this codebase. Dynamic route
generation via router.addRoute() and navigation guards work correctly.
All 24 E2E tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:14:18 +08:00
DevDengChao 7326634cd3 chore(deps): upgrade Pinia 2.1.7 → 3.0.4, persistedstate 3.2.1 → 4.7.1
Pinia 3 is a "boring major" removing only deprecated APIs. The
persistedstate plugin v4 default export remains compatible.
All 24 E2E tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:12:27 +08:00
DevDengChao 81f00adae3 chore(deps): upgrade TypeScript 5.3.3 → 5.9.3
Multiple minor versions with improved type inference. All 24 E2E tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:10:28 +08:00
DevDengChao 136ef314b5 chore(deps): upgrade bpmn-js-properties-panel 5.23.0 → 5.52.1
bpmn-js was already at v18. Properties panel minor update.
All 24 E2E tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:06:04 +08:00
DevDengChao c13d5d7dfd chore(deps): upgrade vue-tsc 1.8.27 → 3.2.5
Major version bump with stricter type checking. Pre-existing type errors
in source code are surfaced but do not affect runtime. All 24 E2E tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:03:39 +08:00
DevDengChao b6f40353ad chore(deps): upgrade Sass 1.69.5 → 1.97.3
Patch update. All 24 E2E tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:00:25 +08:00
DevDengChao dec43ffcee chore(deps): upgrade Element Plus 2.11.1 → 2.13.3
Minor update. All 24 E2E tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 18:58:47 +08:00
DevDengChao 3dad65a53f chore(deps): upgrade Axios 1.9.0 → 1.13.6
Minor/patch update. All 24 E2E tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 18:56:55 +08:00
DevDengChao 9864cf5a92 chore(deps): upgrade Vue 3.5.12 → 3.5.26
Patch update with no breaking changes. All 24 E2E tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 18:54:36 +08:00
DevDengChao 1b9fcc51a1 test: add Playwright E2E test suite as regression safety net
Set up 24 E2E tests covering auth, navigation, user CRUD, permissions,
UI features, and smoke tests using Playwright with API mocking via
page.route(). This provides a safety net before proceeding with
dependency upgrades.

- Add playwright.config.ts with setup project + storageState auth
- Add .env.e2e disabling captcha/tenant/encryption for test mode
- Add e2e/ directory with fixtures, helpers, page objects, and tests
- Add test:e2e scripts to package.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 18:49:50 +08:00
LesanOuO 80128c5406 feat: 条件节点添加包含和不包含操作符 2026-02-14 16:53:21 +08:00
8614095 3314dfe365 feat:增加说明文案 2026-01-15 11:13:20 +08:00
dylanmay d599ca91c6 接口变更 2025-02-10 14:34:29 +08:00
dylanmay 5c1bb25237 feat: 联系人 2024-12-12 22:19:14 +08:00
dylanmay e5b90372a6 数据持久化 2024-11-12 09:12:10 +08:00
dylanmay 5feb3e6815 socket接入 2024-11-12 09:11:08 +08:00
dylanmay 755bf1bb08 去掉conversatNo的使用 2024-10-28 16:31:04 +08:00
YunaiV 0d347643ca 【代码评审】IM:会话、消息相关的接口 2024-10-28 09:41:29 +08:00
dylanmay f3968db2e0 会话和消息处理 2024-10-26 19:45:08 +08:00
YunaiV 90461a8cdf Merge branch 'im' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into im 2024-10-20 13:52:05 +08:00
YunaiV d8d3366687 Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into im 2024-10-20 13:51:15 +08:00
dylanmay 90619542c8 单聊对接 2024-10-19 16:06:29 +08:00
YunaiV d2a212fb80 Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into im
# Conflicts:
#	src/router/modules/remaining.ts
2024-10-14 12:34:09 +08:00
dylanmay aaba03d001 update friend 2023-09-22 16:30:43 +08:00
dylanmay 5b8b51a894 feat: friends 2023-09-20 17:51:50 +08:00
dylanmay e3f8a3a94b feat: chat 2023-09-08 17:36:37 +08:00
488 changed files with 54062 additions and 24754 deletions

View File

@ -1,8 +0,0 @@
/build/
/config/
/dist/
/*.js
/test/unit/coverage/
/node_modules/*
/dist*
/src/main.ts

View File

@ -1,75 +0,0 @@
// @ts-check
const { defineConfig } = require('eslint-define-config')
module.exports = defineConfig({
root: true,
env: {
browser: true,
node: true,
es6: true
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
sourceType: 'module',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true
}
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended',
'@unocss'
],
rules: {
'vue/no-setup-props-destructure': 'off',
'vue/script-setup-uses-vars': 'error',
'vue/no-reserved-component-names': 'off',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'vue/custom-event-name-casing': 'off',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'no-unused-vars': 'off',
'space-before-function-paren': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'off',
'vue/require-toggle-inside-transition': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always'
},
svg: 'always',
math: 'always'
}
],
'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'off',
'prettier/prettier': 'off', // 芋艿:默认关闭 prettier 的 ESLint 校验,因为我们使用的是 IDE 的 Prettier 插件
'@unocss/order': 'off', // 芋艿:禁用 unocss 【css】顺序的提示因为暂时不需要这么严格警告也有点繁琐
'@unocss/order-attributify': 'off' // 芋艿:禁用 unocss 【属性】顺序的提示,因为暂时不需要这么严格,警告也有点繁琐
}
})

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ pnpm-debug
auto-*.d.ts
.idea
.history
output/

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

98
.vscode/settings.json vendored
View File

@ -1,5 +1,7 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.tsdk": "./node_modules/typescript/lib",
"volar.tsPlugin": true,
"volar.tsPluginStatus": false,
"npm.packageManager": "pnpm",
"editor.tabSize": 2,
"prettier.printWidth": 100, //
@ -83,54 +85,75 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
"source.fixAll.eslint": "explicit"
},
"editor.formatOnSave": true,
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
}
},
"i18n-ally.localesPaths": ["src/locales"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": false,
"i18n-ally.namespace": true,
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"cSpell.words": [
"brotli",
"browserslist",
"codemirror",
"commitlint",
"cropperjs",
"echart",
"echarts",
"esnext",
"esno",
"iconify",
"INTLIFY",
"lintstagedrc",
"logicflow",
"nprogress",
"pinia",
"pnpm",
"qrcode",
"sider",
"sortablejs",
"stylelint",
"svgs",
"unocss",
"unplugin",
"unref",
"videojs",
"VITE",
"vitejs",
"vueuse",
"wangeditor",
"xingyu",
"yudao",
"zxcvbn"
"unocss",
"browserslist",
"esnext",
"unplugin",
"qrcode",
"sider",
"pinia",
"sider",
"nprogress",
"INTLIFY",
"stylelint",
"esno",
"vitejs",
"sortablejs",
"codemirror",
"iconify",
"commitlint",
"videojs",
"echarts",
"wangeditor",
"cropperjs",
"logicflow",
"vueuse",
"zxcvbn",
"lintstagedrc",
"brotli",
"sider",
"pnpm"
],
"vetur.format.scriptInitialIndent": true,
"vetur.format.styleInitialIndent": true,
"vetur.validation.script": false,
"MicroPython.executeButton": [
{
"text": "▶",
"tooltip": "运行",
"alignment": "left",
"command": "extension.executeFile",
"priority": 3.5
}
],
"MicroPython.syncButton": [
{
"text": "$(sync)",
"tooltip": "同步",
"alignment": "left",
"command": "extension.execute",
"priority": 4
}
],
//
"explorer.fileNesting.enabled": true,
@ -139,8 +162,7 @@
"*.ts": "$(capture).test.ts, $(capture).test.tsx",
"*.tsx": "$(capture).test.ts, $(capture).test.tsx",
"*.env": "$(capture).env.*",
"package.json": "pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.eslintrc-auto-import.json,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore"
"package.json": "pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore"
},
"terminal.integrated.scrollback": 10000,
"nuxt.isNuxtApp": false
"terminal.integrated.scrollback": 10000
}

View File

@ -87,7 +87,7 @@
* 通用模块(必选):系统功能、基础设施
* 通用模块(可选):工作流程、支付系统、数据报表、会员中心
* 业务系统(按需):ERP 系统、CRM 系统、MES 系统、商城系统、微信公众号、AI 大模型、IoT 物联网
* 业务系统(按需):Mall 电子商城、OA 办公自动化、ERP 企业资源计划系统、WMS 仓库管理系统、CRM 客户关系管理、CMS 内容管理系统、MES 执行制造系统、AI 大模型平台、IoT 物联网系统、IM 即时通讯系统、Mobile 手机移动端、Report 数据大屏
### 系统功能
@ -238,6 +238,14 @@
![功能图](/.image/common/erp-feature.png)
### WMS 系统
演示地址:<https://doc.iocoder.cn/wms-preview/>
![功能图](/.image/common/wms-feature.png)
![预览图](/.image/common/wms-preview.png)
### CRM 系统
演示地址:<https://doc.iocoder.cn/crm-preview/>
@ -276,6 +284,19 @@
![预览图](/.image/common/iot-preview.png)
### IM 即时通讯
演示地址Vue3 + Element Plus<http://dashboard-vue3.yudao.iocoder.cn>
使用文档:<https://doc.iocoder.cn/im-preview/>
![功能图](/.image/common/im-feature.png)
| 聊天界面 | 聊天管理 |
| --- | --- |
| ![聊天界面](/.image/common/im-preview-home.png) | ![聊天管理](/.image/common/im-preview-manager.png) |
## 🐷 演示图
### 系统功能

View File

@ -2,8 +2,7 @@ import { resolve } from 'path'
import Vue from '@vitejs/plugin-vue'
import VueJsx from '@vitejs/plugin-vue-jsx'
import progress from 'vite-plugin-progress'
import EslintPlugin from 'vite-plugin-eslint'
import PurgeIcons from 'vite-plugin-purge-icons'
import EslintPlugin from 'vite-plugin-eslint2'
import { ViteEjsPlugin } from 'vite-plugin-ejs'
// @ts-ignore
import ElementPlus from 'unplugin-element-plus/vite'
@ -29,7 +28,6 @@ export function createVitePlugins() {
VueJsx(),
UnoCSS(),
progress(),
PurgeIcons(),
ElementPlus({}),
AutoImport({
include: [
@ -64,7 +62,7 @@ export function createVitePlugins() {
dts: 'src/types/auto-components.d.ts',
// 自定义组件的解析器
resolvers: [ElementPlusResolver()],
globs: ["src/components/**/**.{vue, md}", '!src/components/DiyEditor/components/mobile/**']
globs: ['src/components/**/**.{vue, md}', '!src/components/DiyEditor/components/mobile/**']
}),
EslintPlugin({
cache: false,
@ -77,7 +75,7 @@ export function createVitePlugins() {
}),
createSvgIconsPlugin({
iconDirs: [pathResolve('src/assets/svgs')],
symbolId: 'icon-[dir]-[name]',
symbolId: 'icon-[dir]-[name]'
}),
viteCompression({
verbose: true, // 是否在控制台输出压缩结果

View File

@ -2,22 +2,18 @@ const include = [
'qs',
'url',
'vue',
'sass',
'mitt',
'axios',
'pinia',
'dayjs',
'qrcode',
'unocss',
'vue-router',
'vue-types',
'vue-i18n',
'crypto-js',
'cropperjs',
'lodash-es',
'nprogress',
'web-storage-cache',
'@iconify/iconify',
'@vueuse/core',
'@zxcvbn-ts/core',
'echarts/core',
@ -91,18 +87,9 @@ const include = [
'element-plus/es/components/dropdown-menu/style/css',
'element-plus/es/components/dropdown-item/style/css',
'element-plus/es/components/skeleton/style/css',
'element-plus/es/components/skeleton/style/css',
'element-plus/es/components/backtop/style/css',
'element-plus/es/components/menu/style/css',
'element-plus/es/components/sub-menu/style/css',
'element-plus/es/components/menu-item/style/css',
'element-plus/es/components/dropdown/style/css',
'element-plus/es/components/skeleton-item/style/css',
'element-plus/es/components/tree/style/css',
'element-plus/es/components/dropdown-menu/style/css',
'element-plus/es/components/dropdown-item/style/css',
'element-plus/es/components/badge/style/css',
'element-plus/es/components/breadcrumb/style/css',
'element-plus/es/components/breadcrumb-item/style/css',
'element-plus/es/components/image/style/css',
'element-plus/es/components/collapse-transition/style/css',
'element-plus/es/components/timeline/style/css',
@ -119,6 +106,6 @@ const include = [
'element-plus/es/components/progress/style/css'
]
const exclude = ['@iconify/json']
const exclude: string[] = []
export { include, exclude }

115
eslint.config.mjs Normal file
View File

@ -0,0 +1,115 @@
import pluginVue from 'eslint-plugin-vue'
import tseslint from 'typescript-eslint'
import unocss from '@unocss/eslint-config/flat'
import autoImportGlobals from './.eslintrc-auto-import.json' with { type: 'json' }
export default tseslint.config(
// Global ignores (replaces .eslintignore)
{
ignores: [
'build/',
'config/',
'dist/',
'dist*/',
'*.js',
'*.mjs',
'!eslint.config.mjs',
'test/unit/coverage/',
'node_modules/',
'src/main.ts',
'src/types/auto-components.d.ts'
]
},
// Base TypeScript config
...tseslint.configs.recommended,
// Vue recommended config
...pluginVue.configs['flat/recommended'],
// UnoCSS config
unocss,
// Vue files use vue-eslint-parser with TypeScript parser
{
files: ['**/*.vue'],
languageOptions: {
parserOptions: {
parser: tseslint.parser
}
}
},
// Main rules config
{
languageOptions: {
ecmaVersion: 2020,
sourceType: 'module',
globals: {
...autoImportGlobals.globals
},
parserOptions: {
ecmaFeatures: {
jsx: true
}
}
},
rules: {
// Vue rules
'vue/no-reserved-component-names': 'off',
'vue/custom-event-name-casing': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'off',
'vue/require-toggle-inside-transition': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always'
},
svg: 'always',
math: 'always'
}
],
'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'off',
// TypeScript rules
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'@typescript-eslint/no-unsafe-function-type': 'off',
'@typescript-eslint/no-wrapper-object-types': 'off',
'@typescript-eslint/no-this-alias': 'off',
'@typescript-eslint/no-empty-object-type': 'off',
'vue/no-ref-as-operand': 'off',
'vue/no-mutating-props': 'off',
'vue/no-side-effects-in-computed-properties': 'off',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'no-unused-vars': 'off',
'space-before-function-paren': 'off',
// UnoCSS rules - 芋艿:禁用 unocss 顺序提示
'@unocss/order': 'off',
'@unocss/order-attributify': 'off',
'unocss/order': 'off',
'unocss/order-attributify': 'off'
}
}
)

17542
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "yudao-ui-admin-vue3",
"version": "2026.03-snapshot",
"version": "2026.05-snapshot",
"description": "基于vue3、vite4、element-plus、typesScript",
"author": "xingyu",
"private": false,
@ -8,143 +8,138 @@
"i": "pnpm install",
"dev": "vite --mode env.local",
"dev-server": "vite --mode dev",
"ts:check": "vue-tsc --noEmit",
"build:local": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build",
"build:dev": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode dev",
"build:test": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode test",
"build:stage": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode stage",
"build:prod": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode prod",
"ts:check": "node --max_old_space_size=8192 ./node_modules/vue-tsc/bin/vue-tsc.js --noEmit",
"build:local": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode env.local",
"build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode dev",
"build:test": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode test",
"build:stage": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode stage",
"build:prod": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode prod",
"serve:dev": "vite preview --mode dev",
"serve:prod": "vite preview --mode prod",
"preview": "pnpm build:local && vite preview",
"clean": "npx rimraf node_modules",
"clean:cache": "npx rimraf node_modules/.cache",
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
"lint:eslint": "eslint --fix ./src",
"lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
"lint:style": "stylelint --fix \"./src/**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c "
},
"dependencies": {
"@element-plus/icons-vue": "2.3.2",
"@form-create/designer": "^3.2.6",
"@form-create/element-ui": "^3.2.11",
"@iconify/iconify": "^3.1.1",
"@form-create/designer": "^3.4.0",
"@form-create/element-ui": "^3.2.38",
"@iconify/vue": "^5.0.1",
"@microsoft/fetch-event-source": "^2.0.1",
"@videojs-player/vue": "^1.0.0",
"@vueuse/core": "^10.9.0",
"@wangeditor-next/editor": "^5.6.46",
"@vueuse/core": "^14.3.0",
"@wangeditor-next/editor": "^5.7.0",
"@wangeditor-next/editor-for-vue": "^5.1.14",
"@wangeditor-next/plugin-mention": "^1.0.16",
"@wangeditor-next/plugin-mention": "^2.0.0",
"@zxcvbn-ts/core": "^3.0.4",
"animate.css": "^4.1.1",
"axios": "1.9.0",
"axios": "1.16.0",
"benz-amr-recorder": "^1.1.5",
"bpmn-js-token-simulation": "^0.36.0",
"bpmn-js-token-simulation": "^0.39.3",
"camunda-bpmn-moddle": "^7.0.1",
"cropperjs": "^1.6.1",
"cropperjs": "^2.1.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.10",
"dayjs": "^1.11.20",
"dayjs-plugin-lunar": "^1.4.1",
"dhtmlx-gantt": "^9.1.1",
"diagram-js": "^12.8.0",
"driver.js": "^1.3.1",
"echarts": "^5.5.0",
"diagram-js": "^15.14.0",
"driver.js": "^1.4.0",
"echarts": "^6.0.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "2.11.1",
"element-plus": "2.13.7",
"fast-xml-parser": "^4.3.2",
"highlight.js": "^11.9.0",
"highlight.js": "^11.11.1",
"jsbarcode": "^3.12.3",
"jsencrypt": "^3.3.2",
"jsoneditor": "^10.1.3",
"lodash-es": "^4.17.21",
"markdown-it": "^14.1.0",
"markmap-common": "^0.16.0",
"markmap-lib": "^0.16.1",
"markmap-toolbar": "^0.17.0",
"markmap-view": "^0.16.0",
"min-dash": "^4.1.1",
"jsencrypt": "^3.5.4",
"jsoneditor": "^10.4.3",
"livekit-client": "^2.18.9",
"lodash-es": "^4.18.1",
"markdown-it": "^14.1.1",
"markmap-common": "^0.18.9",
"markmap-lib": "^0.18.12",
"markmap-toolbar": "^0.18.12",
"markmap-view": "^0.18.12",
"min-dash": "^5.0.0",
"mitt": "^3.0.1",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"qrcode": "^1.5.3",
"qs": "^6.12.0",
"snabbdom": "^3.6.2",
"sortablejs": "^1.15.3",
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"qrcode": "^1.5.4",
"qs": "^6.15.1",
"snabbdom": "^3.6.3",
"sortablejs": "^1.15.7",
"steady-xml": "^0.1.0",
"tyme4ts": "^1.4.6",
"url": "^0.11.3",
"video.js": "^7.21.5",
"vue": "3.5.12",
"vue-dompurify-html": "^4.1.4",
"vue-i18n": "9.10.2",
"vue-router": "4.4.5",
"vue-types": "^5.1.1",
"url": "^0.11.4",
"video.js": "^8.23.8",
"vue": "3.5.34",
"vue-dompurify-html": "^5.3.0",
"vue-i18n": "11.4.0",
"vue-router": "5.0.6",
"vue-types": "^6.0.0",
"vue3-print-nb": "^0.1.4",
"vue3-signature": "^0.2.4",
"vue3-signature": "^0.4.4",
"vuedraggable": "^4.1.0",
"web-storage-cache": "^1.1.1",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@commitlint/cli": "^19.0.1",
"@commitlint/config-conventional": "^19.0.0",
"@iconify/json": "^2.2.187",
"@intlify/unplugin-vue-i18n": "^2.0.0",
"@purge-icons/generated": "^0.9.0",
"@types/jsoneditor": "^9.9.5",
"@commitlint/cli": "^20.5.3",
"@commitlint/config-conventional": "^20.5.3",
"@iconify/json": "^2.2.470",
"@intlify/unplugin-vue-i18n": "^11.1.2",
"@types/jsoneditor": "^9.9.6",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.11.21",
"@types/node": "^25.6.0",
"@types/nprogress": "^0.2.3",
"@types/qrcode": "^1.5.5",
"@types/qs": "^6.9.12",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"@unocss/eslint-config": "^0.57.4",
"@unocss/eslint-plugin": "66.1.0-beta.5",
"@unocss/transformer-variant-group": "^0.58.5",
"@vitejs/plugin-legacy": "^5.3.1",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"autoprefixer": "^10.4.17",
"bpmn-js": "^17.9.2",
"bpmn-js-properties-panel": "5.23.0",
"consola": "^3.2.3",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.22.0",
"lint-staged": "^15.2.2",
"postcss": "^8.4.35",
"postcss-html": "^1.6.0",
"@types/qrcode": "^1.5.6",
"@types/qs": "^6.15.0",
"@unocss/eslint-config": "^66.6.8",
"@unocss/eslint-plugin": "66.6.8",
"@unocss/transformer-variant-group": "^66.6.8",
"@vitejs/plugin-legacy": "^8.0.1",
"@vitejs/plugin-vue": "^6.0.6",
"@vitejs/plugin-vue-jsx": "^5.1.5",
"autoprefixer": "^10.5.0",
"bpmn-js": "^18.16.1",
"bpmn-js-properties-panel": "5.54.0",
"consola": "^3.4.2",
"eslint": "^10.3.0",
"eslint-plugin-vue": "^10.9.1",
"lint-staged": "^16.4.0",
"postcss": "^8.5.14",
"postcss-html": "^1.8.1",
"postcss-scss": "^4.0.9",
"prettier": "^3.2.5",
"prettier-eslint": "^16.3.0",
"rimraf": "^5.0.5",
"rollup": "^4.12.0",
"sass": "^1.69.5",
"stylelint": "^16.2.1",
"prettier": "^3.8.3",
"prettier-eslint": "^16.4.2",
"rimraf": "^6.1.3",
"rollup": "^4.60.3",
"sass": "^1.99.0",
"stylelint": "^17.11.0",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^14.0.0",
"stylelint-config-standard": "^36.0.0",
"stylelint-order": "^6.0.4",
"terser": "^5.28.1",
"typescript": "5.3.3",
"unocss": "^0.58.5",
"unplugin-auto-import": "^0.16.7",
"unplugin-element-plus": "^0.8.0",
"unplugin-vue-components": "^0.25.2",
"vite": "5.1.4",
"stylelint-config-recommended": "^18.0.0",
"stylelint-config-standard": "^40.0.0",
"stylelint-order": "^8.1.1",
"terser": "^5.46.2",
"typescript": "6.0.3",
"typescript-eslint": "^8.59.2",
"unocss": "^66.6.8",
"unplugin-auto-import": "^21.0.0",
"unplugin-element-plus": "^0.11.2",
"unplugin-vue-components": "^32.0.0",
"vite": "8.0.10",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-ejs": "^1.7.0",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-eslint2": "^5.1.0",
"vite-plugin-progress": "^0.0.7",
"vite-plugin-purge-icons": "^0.10.0",
"vite-plugin-svg-icons-ng": "^1.3.1",
"vite-plugin-top-level-await": "^1.4.4",
"vue-eslint-parser": "^9.3.2",
"vue-tsc": "^1.8.27"
"vite-plugin-svg-icons-ng": "^1.9.0",
"vite-plugin-top-level-await": "^1.6.0",
"vue-eslint-parser": "^10.4.0",
"vue-tsc": "^3.2.8"
},
"license": "MIT",
"repository": {
@ -157,7 +152,7 @@
"homepage": "https://gitee.com/yudaocode/yudao-ui-admin-vue3",
"web-types": "./web-types.json",
"engines": {
"node": ">= 16.0.0",
"node": ">= 20.19.0",
"pnpm": ">=8.6.0"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
import request from '@/config/axios'
// 用户端能看到的频道素材详情
export interface ImChannelMaterialRespVO {
id: number
channelId: number
type: number
title: string
coverUrl?: string
summary?: string
content?: string
url?: string
}
// 获取频道素材详情;用于客户端点击图文卡片渲染详情页
export const getChannelMaterial = (id: number) => {
return request.get<ImChannelMaterialRespVO>({ url: '/im/channel/material/get', params: { id } })
}

View File

@ -0,0 +1,23 @@
import request from '@/config/axios'
// 用户端表情包项(精简版)
export interface ImFacePackUserItemVO {
id: number
url: string
name?: string
width: number
height: number
}
// 用户端表情包 + 嵌套 items
export interface ImFacePackUserVO {
id: number
name: string
icon?: string
items: ImFacePackUserItemVO[]
}
// 拉取所有启用的系统表情包(含表情列表)
export const getFacePackList = () => {
return request.get<ImFacePackUserVO[]>({ url: '/im/face-pack/list' })
}

View File

@ -0,0 +1,33 @@
import request from '@/config/axios'
// 个人表情
export interface ImFaceUserItemVO {
id: number
url: string
name?: string
width: number
height: number
}
// 添加个人表情请求
export interface ImFaceUserItemSaveReqVO {
url: string
name?: string
width: number
height: number
}
// 获取我的个人表情列表
export const getFaceUserItemList = () => {
return request.get<ImFaceUserItemVO[]>({ url: '/im/face-user-item/list' })
}
// 添加个人表情
export const createFaceUserItem = (data: ImFaceUserItemSaveReqVO) => {
return request.post<number>({ url: '/im/face-user-item/create', data })
}
// 删除个人表情
export const deleteFaceUserItem = (id: number) => {
return request.delete({ url: '/im/face-user-item/delete', params: { id } })
}

View File

@ -0,0 +1,62 @@
import request from '@/config/axios'
// IM 好友 Response VO
export interface ImFriendRespVO {
id: number // 关系记录编号
friendUserId: number // 好友的用户编号
silent?: boolean // 是否免打扰
displayName?: string // 好友展示备注(仅自己可见)
displayNamePinyin?: string // 备注的拼音(小写无空格,前端按首字母分桶 / 拼音搜索)
addSource?: number // 添加来源;参见 ImFriendAddSourceEnum
pinned?: boolean // 是否置顶联系人
blocked?: boolean // 是否拉黑
status?: number // 好友状态0=正常1=已删除)
addTime?: string // 添加好友时间
deleteTime?: string // 删除好友时间
// 聚合字段(自 AdminUser
nickname?: string // 好友昵称
nicknamePinyin?: string // 昵称的拼音(小写无空格,前端按首字母分桶 / 拼音搜索)
avatar?: string // 好友头像
}
// IM 好友更新 Request VO
export interface ImFriendUpdateReqVO {
friendUserId: number // 好友的用户编号
silent?: boolean // 是否免打扰
displayName?: string // 好友展示备注
pinned?: boolean // 是否置顶联系人
}
// 获得当前登录用户的好友列表
export const getMyFriendList = () => {
return request.get<ImFriendRespVO[]>({ url: '/im/friend/list' })
}
// 获得好友详情
export const getFriend = (friendUserId: number | string) => {
return request.get<ImFriendRespVO>({ url: '/im/friend/get', params: { friendUserId } })
}
// 删除好友(单向软删除)
export const deleteFriend = (friendUserId: number | string, clear: boolean) => {
return request.delete<boolean>({
url: '/im/friend/delete',
params: { friendUserId, clear }
})
}
// 更新好友信息(备注 / 免打扰 / 联系人置顶)
export const updateFriend = (data: ImFriendUpdateReqVO) => {
return request.put<boolean>({ url: '/im/friend/update', data })
}
// 拉黑好友(必须先是好友;单边屏蔽对方私聊消息)
export const blockFriend = (friendUserId: number | string) => {
return request.put<boolean>({ url: '/im/friend/block', params: { friendUserId } })
}
// 移出黑名单
export const unblockFriend = (friendUserId: number | string) => {
return request.put<boolean>({ url: '/im/friend/unblock', params: { friendUserId } })
}

View File

@ -0,0 +1,65 @@
import request from '@/config/axios'
// IM 好友申请 Response VO
export interface ImFriendRequestRespVO {
id: number // 申请编号
fromUserId: number // 发起方用户编号
toUserId: number // 接收方用户编号
handleResult: number // 处理结果0=未处理1=同意2=拒绝
applyContent?: string // 申请理由
handleContent?: string // 处理理由(接收方拒绝时可选填)
addSource?: number // 添加来源;参见 ImFriendAddSourceEnum
handleTime?: string // 处理时间
createTime: string // 申请创建时间
// 聚合字段(自 AdminUser
fromNickname?: string // 发起方昵称
fromAvatar?: string // 发起方头像
toNickname?: string // 接收方昵称
toAvatar?: string // 接收方头像
}
// IM 好友申请发起 Request VO
export interface ImFriendRequestApplyReqVO {
toUserId: number // 接收方用户编号
applyContent?: string // 申请理由
displayName?: string // 对接收方的备注(仅自己可见)
addSource?: number // 添加来源
}
// 发起好友申请
export const applyFriendRequest = (data: ImFriendRequestApplyReqVO) => {
return request.post<number | null>({ url: '/im/friend-request/apply', data })
}
// 同意好友申请
export const agreeFriendRequest = (id: number | string) => {
return request.put<boolean>({ url: '/im/friend-request/agree', params: { id } })
}
// 拒绝好友申请
export const refuseFriendRequest = (id: number | string, handleContent?: string) => {
return request.put<boolean>({
url: '/im/friend-request/refuse',
params: { id, handleContent }
})
}
// 查询「我相关」的好友申请列表(游标分页:传 maxId 加载更多)
export const getMyFriendRequestList = (limit: number, maxId?: number) => {
const params: Record<string, number> = { limit }
if (maxId != null) {
params.maxId = maxId
}
return request.get<ImFriendRequestRespVO[]>({
url: '/im/friend-request/list',
params
})
}
// 按 id 单查「我相关」的申请记录带越权过滤WebSocket 通知到达后用)
export const getMyFriendRequest = (id: number) => {
return request.get<ImFriendRequestRespVO | null>({
url: '/im/friend-request/get',
params: { id }
})
}

137
src/api/im/group/index.ts Normal file
View File

@ -0,0 +1,137 @@
import request from '@/config/axios'
import type { ImGroupMessageRespVO } from '@/api/im/message/group'
// 群 Response VO
export interface ImGroupRespVO {
id: number // 编号
name: string // 群名称
ownerUserId: number // 群主用户编号
avatar?: string // 群头像
notice?: string // 群公告
banned?: boolean // 是否封禁
mutedAll?: boolean // 是否全群禁言
joinApproval?: boolean // 进群是否需群主 / 管理员审批
bannedTime?: string // 封禁时间
status: number // 群状态0=正常1=已解散)
dissolvedTime?: string // 解散时间
createTime?: string // 创建时间
pinnedMessages?: ImGroupMessageRespVO[] // 群置顶消息列表(后端关联回填,仅当登录用户是群成员时非空)
}
// 群消息置顶 / 取消置顶 Request VO
export interface ImGroupMessagePinReqVO {
id: number // 群编号
messageId: number // 消息编号
}
// 群创建 Request VO
export interface ImGroupCreateReqVO {
name: string // 群名称
memberUserIds?: number[] // 初始成员用户编号列表(建群同时邀请的好友,不含创建者自己)
joinApproval?: boolean // 进群是否需审批;不传默认 false 自由进群
}
// 群更新 Request VO
export interface ImGroupUpdateReqVO {
id: number // 群编号
name?: string // 群名称
avatar?: string // 群头像
notice?: string // 群公告
joinApproval?: boolean // 进群是否需审批
}
// 添加 / 撤销群管理员 Request VO
export interface ImGroupAdminReqVO {
id: number // 群编号
userIds: number[] // 目标用户编号列表
}
// 群主转让 Request VO
export interface ImGroupTransferOwnerReqVO {
id: number // 群编号
newOwnerUserId: number // 新群主用户编号
}
// 全群禁言 / 取消 Request VO
export interface ImGroupMuteAllReqVO {
id: number // 群编号
mutedAll: boolean // 是否全群禁言
}
// 成员禁言 Request VO
export interface ImGroupMuteMemberReqVO {
id: number // 群编号
userId: number // 被禁言的用户编号
mutedSeconds: number // 禁言时长0 表示永久禁言
}
// 取消成员禁言 Request VO
export interface ImGroupCancelMuteMemberReqVO {
id: number // 群编号
userId: number // 被取消禁言的用户编号
}
// 获得当前登录用户的群列表
export const getMyGroupList = () => {
return request.get<ImGroupRespVO[]>({ url: '/im/group/list' })
}
// 获得群详情
export const getGroup = (id: number | string) => {
return request.get<ImGroupRespVO>({ url: '/im/group/get', params: { id } })
}
// 创建群
export const createGroup = (data: ImGroupCreateReqVO) => {
return request.post<ImGroupRespVO>({ url: '/im/group/create', data })
}
// 更新群
export const updateGroup = (data: ImGroupUpdateReqVO) => {
return request.put<ImGroupRespVO>({ url: '/im/group/update', data })
}
// 解散群
export const dissolveGroup = (id: number | string) => {
return request.delete<boolean>({ url: '/im/group/dissolve', params: { id } })
}
// 添加群管理员(仅群主可调)
export const addGroupAdmin = (data: ImGroupAdminReqVO) => {
return request.put<boolean>({ url: '/im/group/add-admin', data })
}
// 撤销群管理员(仅群主可调)
export const removeGroupAdmin = (data: ImGroupAdminReqVO) => {
return request.put<boolean>({ url: '/im/group/remove-admin', data })
}
// 转让群主(仅老群主可调;旧群主转让后降为普通成员)
export const transferGroupOwner = (data: ImGroupTransferOwnerReqVO) => {
return request.put<boolean>({ url: '/im/group/transfer-owner', data })
}
// 置顶群消息(仅群主 / 管理员可调)
export const pinGroupMessage = (data: ImGroupMessagePinReqVO) => {
return request.put<boolean>({ url: '/im/group/pin-message', data })
}
// 取消置顶群消息(仅群主 / 管理员可调)
export const unpinGroupMessage = (data: ImGroupMessagePinReqVO) => {
return request.put<boolean>({ url: '/im/group/unpin-message', data })
}
// 全群禁言 / 取消(仅群主 / 管理员可调)
export const muteAll = (data: ImGroupMuteAllReqVO) => {
return request.put<boolean>({ url: '/im/group/mute-all', data })
}
// 禁言成员
export const muteMember = (data: ImGroupMuteMemberReqVO) => {
return request.put<boolean>({ url: '/im/group/mute-member', data })
}
// 取消成员禁言
export const cancelMuteMember = (data: ImGroupCancelMuteMemberReqVO) => {
return request.put<boolean>({ url: '/im/group/cancel-mute-member', data })
}

View File

@ -0,0 +1,76 @@
import request from '@/config/axios'
// 群成员 Response VO
export interface ImGroupMemberRespVO {
id: number // 编号
groupId: number // 群编号
userId: number // 用户编号
displayUserName?: string // 组内显示名(群主设置的备注)
groupRemark?: string // 群备注(当前用户对群的备注)
silent?: boolean // 是否免打扰
status?: number // 成员状态0=在群1=退群)
role?: number // 成员角色,参见 ImGroupMemberRole 枚举
joinTime?: string // 入群时间
quitTime?: string // 退群时间
muteEndTime?: string // 禁言到期时间
createTime?: string // 创建时间
// 聚合字段(自 AdminUser
nickname?: string // 用户昵称
avatar?: string // 用户头像
}
// 群成员邀请 Request VO
export interface ImGroupMemberInviteReqVO {
groupId: number // 群编号
memberUserIds: number[] // 被邀请的用户编号列表
}
// 群成员移除 Request VO
export interface ImGroupMemberRemoveReqVO {
groupId: number // 群编号
memberUserIds: number[] // 被移除的用户编号列表
}
// 群成员更新 Request VO
export interface ImGroupMemberUpdateReqVO {
groupId: number // 群编号
displayUserName?: string // 群内昵称
groupRemark?: string // 群备注
silent?: boolean // 是否免打扰
}
// 邀请用户加入群
export const inviteGroupMember = (data: ImGroupMemberInviteReqVO) => {
return request.post<boolean>({ url: '/im/group/invite', data })
}
// 退出群
export const quitGroup = (groupId: number | string) => {
return request.delete<boolean>({ url: '/im/group/quit', params: { groupId } })
}
// 移除群成员
export const removeGroupMember = (data: ImGroupMemberRemoveReqVO) => {
return request.delete<boolean>({ url: '/im/group/kicking', data })
}
// 获得群成员详情
export const getGroupMember = (groupId: number, userId: number) => {
return request.get<ImGroupMemberRespVO>({
url: '/im/group-member/get',
params: { groupId, userId }
})
}
// 获得指定群的成员列表(聚合 AdminUser 昵称 / 头像)
export const getGroupMemberList = (groupId: number | string) => {
return request.get<ImGroupMemberRespVO[]>({
url: '/im/group-member/list',
params: { groupId }
})
}
// 更新群成员
export const updateGroupMember = (data: ImGroupMemberUpdateReqVO) => {
return request.put<boolean>({ url: '/im/group-member/update', data })
}

View File

@ -0,0 +1,71 @@
import request from '@/config/axios'
// IM 加群申请 Response VO
export interface ImGroupRequestRespVO {
id: number // 申请编号
groupId: number // 群编号
userId: number // 申请人 / 被邀请人用户编号
inviterUserId?: number // 邀请人NULL 表示用户主动申请
handleResult: number // 处理结果0=未处理1=同意2=拒绝
applyContent?: string // 申请理由
handleContent?: string // 处理理由(拒绝时可选填)
handleUserId?: number // 处理人用户编号
addSource?: number // 加入来源;参见 ImGroupAddSourceEnum
handleTime?: string // 处理时间
createTime: string // 申请创建时间
// 聚合字段
userNickname?: string // 申请人 / 被邀请人昵称
userAvatar?: string // 申请人 / 被邀请人头像
inviterNickname?: string // 邀请人昵称
inviterAvatar?: string // 邀请人头像
groupName?: string // 群名称
groupAvatar?: string // 群头像
}
// IM 加群申请发起 Request VO
export interface ImGroupRequestApplyReqVO {
groupId: number // 群编号
applyContent?: string // 申请理由
addSource?: number // 加入来源
}
// 申请加群
export const applyJoinGroup = (data: ImGroupRequestApplyReqVO) => {
return request.post<number | null>({ url: '/im/group-request/apply', data })
}
// 同意加群申请(群主或管理员)
export const agreeGroupRequest = (id: number | string) => {
return request.put<boolean>({ url: '/im/group-request/agree', params: { id } })
}
// 拒绝加群申请(群主或管理员)
export const refuseGroupRequest = (id: number | string, handleContent?: string) => {
return request.put<boolean>({
url: '/im/group-request/refuse',
params: { id, handleContent }
})
}
// 查询「我管理的所有群」下的未处理加群申请列表(不分页);前端 store 据此派生横幅红点 + Drawer 列表
export const getUnhandledRequestList = () => {
return request.get<ImGroupRequestRespVO[]>({
url: '/im/group-request/unhandled-list'
})
}
// 查询指定群下的全部加群申请(含已处理);仅群主 / 管理员可查
export const getGroupRequestListByGroupId = (groupId: number) => {
return request.get<ImGroupRequestRespVO[]>({
url: '/im/group-request/list-by-group',
params: { groupId }
})
}
// 按 id 单查申请记录带越权过滤WebSocket 通知到达后用)
export const getMyGroupRequest = (id: number) => {
return request.get<ImGroupRequestRespVO | null>({
url: '/im/group-request/get',
params: { id }
})
}

View File

@ -0,0 +1,41 @@
import request from '@/config/axios'
export interface ImManagerChannelVO {
id: number
code: string
name: string
avatar?: string
sort: number
status: number
createTime?: Date
}
// 获得频道分页
export const getManagerChannelPage = (params: PageParam) => {
return request.get({ url: '/im/manager/channel/page', params })
}
// 获得频道详情
export const getManagerChannel = (id: number) => {
return request.get({ url: '/im/manager/channel/get', params: { id } })
}
// 新增频道
export const createManagerChannel = (data: ImManagerChannelVO) => {
return request.post({ url: '/im/manager/channel/create', data })
}
// 修改频道
export const updateManagerChannel = (data: ImManagerChannelVO) => {
return request.put({ url: '/im/manager/channel/update', data })
}
// 删除频道
export const deleteManagerChannel = (id: number) => {
return request.delete({ url: '/im/manager/channel/delete', params: { id } })
}
// 获得启用的频道精简列表(表单选择用)
export const getSimpleChannelList = () => {
return request.get<ImManagerChannelVO[]>({ url: '/im/manager/channel/simple-list' })
}

View File

@ -0,0 +1,47 @@
import request from '@/config/axios'
export interface ImManagerChannelMaterialVO {
id: number
channelId: number
channelName?: string
type: number
title: string
coverUrl?: string
summary?: string
content?: string
url?: string
createTime?: Date
}
// 获得素材分页
export const getManagerChannelMaterialPage = (params: PageParam) => {
return request.get({ url: '/im/manager/channel-material/page', params })
}
// 获得指定频道下的素材精简列表
export const getSimpleManagerChannelMaterialList = (channelId: number) => {
return request.get({
url: '/im/manager/channel-material/simple-list',
params: { channelId }
})
}
// 获得素材详情
export const getManagerChannelMaterial = (id: number) => {
return request.get({ url: '/im/manager/channel-material/get', params: { id } })
}
// 新增素材
export const createManagerChannelMaterial = (data: ImManagerChannelMaterialVO) => {
return request.post({ url: '/im/manager/channel-material/create', data })
}
// 修改素材
export const updateManagerChannelMaterial = (data: ImManagerChannelMaterialVO) => {
return request.put({ url: '/im/manager/channel-material/update', data })
}
// 删除素材
export const deleteManagerChannelMaterial = (id: number) => {
return request.delete({ url: '/im/manager/channel-material/delete', params: { id } })
}

View File

@ -0,0 +1,34 @@
import request from '@/config/axios'
export interface ImManagerChannelMessageVO {
id: number
channelId: number
channelName?: string
materialId: number
materialTitle?: string
materialCoverUrl?: string
type: number
content?: string
receiverUserIds?: number[]
sendTime?: Date
}
export interface ImManagerChannelMessageSendReqVO {
materialId: number
receiverUserIds?: number[]
}
// 立即推送频道消息
export const sendManagerChannelMessage = (data: ImManagerChannelMessageSendReqVO) => {
return request.post({ url: '/im/manager/channel-message/send', data })
}
// 删除频道消息
export const deleteManagerChannelMessage = (id: number) => {
return request.delete({ url: '/im/manager/channel-message/delete', params: { id } })
}
// 获得频道消息分页
export const getManagerChannelMessagePage = (params: PageParam) => {
return request.get({ url: '/im/manager/channel-message/page', params })
}

View File

@ -0,0 +1,46 @@
import request from '@/config/axios'
export interface ImManagerFacePackItemVO {
id: number
packId: number
url: string
name?: string
width: number
height: number
sort: number
status: number
createTime?: Date
}
// 获得表情分页
export const getManagerFacePackItemPage = (params: PageParam) => {
return request.get({ url: '/im/manager/face-pack-item/page', params })
}
// 获得表情详情
export const getManagerFacePackItem = (id: number) => {
return request.get({ url: '/im/manager/face-pack-item/get', params: { id } })
}
// 新增表情
export const createManagerFacePackItem = (data: ImManagerFacePackItemVO) => {
return request.post({ url: '/im/manager/face-pack-item/create', data })
}
// 修改表情
export const updateManagerFacePackItem = (data: ImManagerFacePackItemVO) => {
return request.put({ url: '/im/manager/face-pack-item/update', data })
}
// 删除表情
export const deleteManagerFacePackItem = (id: number) => {
return request.delete({ url: '/im/manager/face-pack-item/delete', params: { id } })
}
// 批量删除表情
export const deleteManagerFacePackItemList = (ids: number[]) => {
return request.delete({
url: '/im/manager/face-pack-item/delete-list',
params: { ids: ids.join(',') }
})
}

View File

@ -0,0 +1,43 @@
import request from '@/config/axios'
export interface ImManagerFacePackVO {
id: number
name: string
icon?: string
sort: number
status: number
createTime?: Date
}
// 获得表情包分页
export const getManagerFacePackPage = (params: PageParam) => {
return request.get({ url: '/im/manager/face-pack/page', params })
}
// 获得表情包详情
export const getManagerFacePack = (id: number) => {
return request.get({ url: '/im/manager/face-pack/get', params: { id } })
}
// 新增表情包
export const createManagerFacePack = (data: ImManagerFacePackVO) => {
return request.post({ url: '/im/manager/face-pack/create', data })
}
// 修改表情包
export const updateManagerFacePack = (data: ImManagerFacePackVO) => {
return request.put({ url: '/im/manager/face-pack/update', data })
}
// 删除表情包
export const deleteManagerFacePack = (id: number) => {
return request.delete({ url: '/im/manager/face-pack/delete', params: { id } })
}
// 批量删除表情包
export const deleteManagerFacePackList = (ids: number[]) => {
return request.delete({
url: '/im/manager/face-pack/delete-list',
params: { ids: ids.join(',') }
})
}

View File

@ -0,0 +1,22 @@
import request from '@/config/axios'
export interface ImManagerFaceUserItemVO {
id: number
userId: number
userNickname?: string
url: string
name?: string
width?: number
height?: number
createTime?: Date
}
// 获得用户表情分页
export const getManagerFaceUserItemPage = (params: PageParam) => {
return request.get({ url: '/im/manager/face-user-item/page', params })
}
// 删除用户表情
export const deleteManagerFaceUserItem = (id: number) => {
return request.delete({ url: '/im/manager/face-user-item/delete', params: { id } })
}

View File

@ -0,0 +1,23 @@
import request from '@/config/axios'
export interface ImManagerFriendVO {
id: number
userId: number
userNickname?: string
friendUserId: number
friendNickname?: string
displayName?: string
addSource?: number
silent: boolean
pinned: boolean
blocked: boolean
status: number
addTime?: Date
deleteTime?: Date
createTime: Date
}
// 获得好友关系分页
export const getManagerFriendPage = (params: PageParam) => {
return request.get({ url: '/im/manager/friend/page', params })
}

View File

@ -0,0 +1,21 @@
import request from '@/config/axios'
export interface ImManagerFriendRequestVO {
id: number
fromUserId: number
fromNickname?: string
toUserId: number
toNickname?: string
applyContent?: string
displayName?: string
addSource?: number
handleResult: number
handleContent?: string
handleTime?: Date
createTime: Date
}
// 获得好友申请分页
export const getManagerFriendRequestPage = (params: PageParam) => {
return request.get({ url: '/im/manager/friend-request/page', params })
}

View File

@ -0,0 +1,62 @@
import request from '@/config/axios'
export interface ImManagerGroupVO {
id: number
name: string
avatar?: string
notice?: string
ownerUserId: number
ownerNickname?: string
memberCount?: number
status: number
banned: boolean
mutedAll?: boolean // 是否全群禁言
bannedReason?: string
bannedTime?: Date
dissolvedTime?: Date
createTime: Date
}
export interface ImManagerGroupMemberVO {
userId: number
nickname?: string
avatar?: string
displayUserName?: string
groupRemark?: string
silent?: boolean
status: number
role?: number // 成员角色,参见 ImGroupMemberRole 枚举
joinTime?: Date
quitTime?: Date
muteEndTime?: Date // 禁言到期时间
}
// 获得群分页
export const getManagerGroupPage = (params: PageParam) => {
return request.get({ url: '/im/manager/group/page', params })
}
// 获得群详情
export const getManagerGroup = (id: number) => {
return request.get({ url: '/im/manager/group/get', params: { id } })
}
// 封禁群
export const banManagerGroup = (data: { id: number; reason: string }) => {
return request.put({ url: '/im/manager/group/ban', data })
}
// 解封群
export const unbanManagerGroup = (id: number) => {
return request.put({ url: '/im/manager/group/unban', params: { id } })
}
// 解散群
export const dissolveManagerGroup = (id: number) => {
return request.delete({ url: '/im/manager/group/dissolve', params: { id } })
}
// 获得群成员列表(含已退群成员,由前端按需过滤)
export const getManagerGroupMemberList = (groupId: number) => {
return request.get({ url: '/im/manager/group/member/list', params: { groupId } })
}

View File

@ -0,0 +1,24 @@
import request from '@/config/axios'
export interface ImManagerGroupRequestVO {
id: number
groupId: number
groupName?: string
userId: number
userNickname?: string
inviterUserId?: number
inviterNickname?: string
applyContent?: string
addSource?: number
handleResult: number
handleUserId?: number
handleNickname?: string
handleContent?: string
handleTime?: Date
createTime: Date
}
// 获得加群申请分页
export const getManagerGroupRequestPage = (params: PageParam) => {
return request.get({ url: '/im/manager/group-request/page', params })
}

View File

@ -0,0 +1,29 @@
import request from '@/config/axios'
export interface ImManagerGroupMessageVO {
id: number
clientMessageId?: string
groupId: number
groupName?: string
senderId: number
senderNickname?: string
type: number
content: string
status: number
atUserIds?: number[]
// 与 atUserIds 同长度;后端对找不到 / 已删除的成员返回 nullUI 用 `?.[idx] || userId` 回退到 userId 渲染
atUserNicknames?: (string | null)[]
receiptStatus?: number
sendTime: Date
createTime: Date
}
// 获得群聊消息分页
export const getManagerGroupMessagePage = (params: PageParam) => {
return request.get({ url: '/im/manager/message/group/page', params })
}
// 获得群聊消息详情
export const getManagerGroupMessage = (id: number) => {
return request.get({ url: '/im/manager/message/group/get', params: { id } })
}

View File

@ -0,0 +1,25 @@
import request from '@/config/axios'
export interface ImManagerPrivateMessageVO {
id: number
clientMessageId?: string
senderId: number
senderNickname?: string
receiverId: number
receiverNickname?: string
type: number
content: string
status: number
sendTime: Date
createTime: Date
}
// 获得私聊消息分页
export const getManagerPrivateMessagePage = (params: PageParam) => {
return request.get({ url: '/im/manager/message/private/page', params })
}
// 获得私聊消息详情
export const getManagerPrivateMessage = (id: number) => {
return request.get({ url: '/im/manager/message/private/get', params: { id } })
}

View File

@ -0,0 +1,40 @@
import request from '@/config/axios'
export interface ImManagerRtcCallVO {
id: number
room: string
conversationType: number
mediaType: number
inviterUserId: number
inviterNickname?: string
groupId?: number
groupName?: string
status: number
endReason?: number
startTime: Date
acceptTime?: Date
endTime?: Date
createTime: Date
}
export interface ImManagerRtcParticipantVO {
id: number
callId: number
userId: number
userNickname?: string
role: number
status: number
inviteTime: Date
acceptTime?: Date
leaveTime?: Date
}
// 获得通话记录分页
export const getManagerRtcCallPage = (params: PageParam) => {
return request.get({ url: '/im/manager/rtc/page', params })
}
// 获得通话参与者列表
export const getManagerRtcCallParticipantList = (id: number) => {
return request.get({ url: '/im/manager/rtc/participant-list', params: { id } })
}

View File

@ -0,0 +1,43 @@
import request from '@/config/axios'
export interface ImManagerSensitiveWordVO {
id: number
word: string
status: number
creator?: string
creatorName?: string
createTime?: Date
}
// 获得敏感词分页
export const getManagerSensitiveWordPage = (params: PageParam) => {
return request.get({ url: '/im/manager/sensitive-word/page', params })
}
// 获得敏感词详情
export const getManagerSensitiveWord = (id: number) => {
return request.get({ url: '/im/manager/sensitive-word/get', params: { id } })
}
// 新增敏感词
export const createManagerSensitiveWord = (data: ImManagerSensitiveWordVO) => {
return request.post({ url: '/im/manager/sensitive-word/create', data })
}
// 修改敏感词
export const updateManagerSensitiveWord = (data: ImManagerSensitiveWordVO) => {
return request.put({ url: '/im/manager/sensitive-word/update', data })
}
// 删除敏感词
export const deleteManagerSensitiveWord = (id: number) => {
return request.delete({ url: '/im/manager/sensitive-word/delete', params: { id } })
}
// 批量删除敏感词
export const deleteManagerSensitiveWordList = (ids: number[]) => {
return request.delete({
url: '/im/manager/sensitive-word/delete-list',
params: { ids: ids.join(',') }
})
}

View File

@ -0,0 +1,70 @@
import request from '@/config/axios'
export interface ImStatisticsOverviewVO {
totalUser: number
newUserToday: number
totalGroup: number
newGroupToday: number
activeUserDaily: number
activeUserWeekly: number
activeUserMonthly: number
privateMessageToday: number
groupMessageToday: number
privateMessageYesterday: number
groupMessageYesterday: number
}
export interface ImStatisticsTrendVO {
dates: string[]
series: Record<string, number[]>
}
export interface ImStatisticsMessageTypeVO {
type: number // 参见 ImMessageTypeEnum 枚举类,由前端按 DICT_TYPE.IM_MESSAGE_TYPE 翻译
value: number
}
export interface ImStatisticsGroupSizeVO {
range: string
count: number
}
export interface ImStatisticsTopSenderVO {
userId: number
nickname: string
messageCount: number
}
// 获得 KPI 概览
export const getStatisticsOverview = (): Promise<ImStatisticsOverviewVO> => {
return request.get<ImStatisticsOverviewVO>({ url: '/im/manager/statistics/overview' })
}
// 获得消息趋势(私聊 + 群聊双线)
export const getMessageTrend = (days: number): Promise<ImStatisticsTrendVO> => {
return request.get<ImStatisticsTrendVO>({ url: '/im/manager/statistics/message-trend', params: { days } })
}
// 获得用户趋势(新增注册 + 日活双线)
export const getUserTrend = (days: number): Promise<ImStatisticsTrendVO> => {
return request.get<ImStatisticsTrendVO>({ url: '/im/manager/statistics/user-trend', params: { days } })
}
// 获得消息类型分布(最近 30 天)
export const getMessageTypeDistribution = (): Promise<ImStatisticsMessageTypeVO[]> => {
return request.get<ImStatisticsMessageTypeVO[]>({
url: '/im/manager/statistics/message-type-distribution'
})
}
// 获得群规模分布
export const getGroupSizeDistribution = (): Promise<ImStatisticsGroupSizeVO[]> => {
return request.get<ImStatisticsGroupSizeVO[]>({
url: '/im/manager/statistics/group-size-distribution'
})
}
// 获得消息 TOP 发送者(最近 30 天)
export const getTopSenders = (): Promise<ImStatisticsTopSenderVO[]> => {
return request.get<ImStatisticsTopSenderVO[]>({ url: '/im/manager/statistics/top-senders' })
}

View File

@ -0,0 +1,30 @@
import request from '@/config/axios'
export interface ImChannelMessageRespVO {
id: number
clientMessageId?: string
channelId: number
materialId: number
type: number
content: string
/** 当前用户已读态pull 时按 Redis 游标计算填充,多端同步使用 */
status?: number
sendTime: string
}
// 拉取当前用户应收的频道消息(离线增量);按 minId 游标分页
export const pullChannelMessages = (params: { minId: number; size?: number }, signal?: AbortSignal) => {
return request.get<ImChannelMessageRespVO[]>({
url: '/im/channel/message/pull',
params,
signal
})
}
// 上报频道消息已读位置;切到频道会话或拉到新消息后调
export const readChannelMessages = (channelId: number, messageId: number) => {
return request.put({
url: '/im/channel/message/read',
params: { channelId, messageId }
})
}

View File

@ -0,0 +1,76 @@
import request from '@/config/axios'
// 群聊消息 Response VO
export interface ImGroupMessageRespVO {
id: number // 消息编号
clientMessageId: string // 客户端消息编号
senderId: number // 发送人编号
groupId: number // 群编号
type: number // 消息类型
content: string // 消息内容JSON 格式)
status: number // 消息状态
sendTime: string // 发送时间
atUserIds?: number[] // @ 目标用户编号列表
receiverUserIds?: number[] // 定向接收用户编号列表
receiptStatus?: number // 回执状态
readCount?: number // 已读人数(回执消息、且发送人为当前用户时有值)
}
// 群聊消息发送 Request VO
export interface ImGroupMessageSendReqVO {
clientMessageId: string // 客户端消息编号
groupId: number // 群编号
type: number // 消息类型
content: string // 消息内容JSON 格式)
atUserIds?: number[] // @ 目标用户编号列表
receipt?: boolean // 是否需要回执
}
// 群聊历史消息列表 Request VO
export interface ImGroupMessageListReqVO {
groupId: number | string // 群编号
maxId?: number | string // 起始消息编号(不含),为空则从最新消息开始
limit: number // 拉取数量1 ~ 200
}
// 发送群聊消息
export const sendGroupMessage = (data: ImGroupMessageSendReqVO) => {
return request.post<ImGroupMessageRespVO>({ url: '/im/message/group/send', data })
}
// 拉取群聊消息(增量)
export const pullGroupMessages = (
params: { minId: number | string; size: number },
signal?: AbortSignal
) => {
return request.get<ImGroupMessageRespVO[]>({ url: '/im/message/group/pull', params, signal })
}
// 查询群聊历史消息
export const getGroupMessageList = (params: ImGroupMessageListReqVO) => {
return request.get<ImGroupMessageRespVO[]>({ url: '/im/message/group/list', params })
}
// 标记群聊消息已读
export const readGroupMessages = (groupId: number | string, messageId: number | string) => {
return request.put<boolean>({
url: '/im/message/group/read',
params: { groupId, messageId }
})
}
// 撤回群聊消息
export const recallGroupMessage = (id: number | string) => {
return request.delete<ImGroupMessageRespVO>({
url: '/im/message/group/recall',
params: { id }
})
}
// 获取群消息已读用户列表
export const getGroupReadUsers = (params: {
groupId: number | string
messageId: number | string
}) => {
return request.get<number[]>({ url: '/im/message/group/get-read-user-ids', params })
}

View File

@ -0,0 +1,71 @@
import request from '@/config/axios'
// 私聊消息 Response VO
export interface ImPrivateMessageRespVO {
id: number // 消息编号
clientMessageId: string // 客户端消息编号
senderId: number // 发送人编号
receiverId: number // 接收人编号
type: number // 消息类型
content: string // 消息内容JSON 格式)
status: number // 消息状态
sendTime: string // 发送时间
}
// 私聊消息发送 Request VO
export interface ImPrivateMessageSendReqVO {
clientMessageId: string // 客户端消息编号
receiverId: number // 接收人编号
type: number // 消息类型
content: string // 消息内容JSON 格式)
}
// 私聊历史消息列表 Request VO
export interface ImPrivateMessageListReqVO {
receiverId: number | string // 接收人编号(对方)
maxId?: number | string // 起始消息编号(不含),为空则从最新消息开始
limit: number // 拉取数量1 ~ 200
}
// 发送私聊消息
export const sendPrivateMessage = (data: ImPrivateMessageSendReqVO) => {
return request.post<ImPrivateMessageRespVO>({ url: '/im/message/private/send', data })
}
// 拉取私聊消息(增量)
export const pullPrivateMessages = (
params: { minId: number | string; size: number },
signal?: AbortSignal
) => {
return request.get<ImPrivateMessageRespVO[]>({ url: '/im/message/private/pull', params, signal })
}
// 查询私聊历史消息
export const getPrivateMessageList = (params: ImPrivateMessageListReqVO) => {
return request.get<ImPrivateMessageRespVO[]>({ url: '/im/message/private/list', params })
}
// 标记私聊消息已读
export const readPrivateMessages = (receiverId: number | string, messageId: number | string) => {
return request.put<boolean>({
url: '/im/message/private/read',
params: { receiverId, messageId }
})
}
// 查询对方已读到我发的最大消息 id多端 / 离线后用于补齐已读状态)
export const getPrivateMaxReadMessageId = (peerId: number | string, signal?: AbortSignal) => {
return request.get<number | null>({
url: '/im/message/private/max-read-message-id',
params: { peerId },
signal
})
}
// 撤回私聊消息
export const recallPrivateMessage = (id: number | string) => {
return request.delete<ImPrivateMessageRespVO>({
url: '/im/message/private/recall',
params: { id }
})
}

88
src/api/im/rtc/index.ts Normal file
View File

@ -0,0 +1,88 @@
import request from '@/config/axios'
// 创建新通话请求 VO
export interface ImRtcCallCreateReqVO {
conversationType: number
mediaType: number
groupId?: number
inviteeIds: number[] // 被邀请的用户编号集合;私聊必传 1 个对端,群聊必传至少 1 人
}
// 通话中追加邀请请求 VO仅群通话可用
export interface ImRtcCallInviteReqVO {
room: string
inviteeIds: number[]
}
// 通话会话 VOcreate / join / accept / refreshToken 共用
export interface ImRtcCallRespVO {
room: string // 业务通话编号(同时作为 LiveKit 房间名)
livekitUrl: string
token?: string // ENDED 状态时为 null无需 connect LiveKit
conversationType: number
mediaType: number
status: number
endReason?: number // 结束原因;仅 status=ENDED 时有值
inviterId: number
groupId?: number
inviteeIds?: number[]
joinedUserIds?: number[]
}
// 群活跃通话查询响应;不含 token
export interface ImRtcGroupCallRespVO {
room: string
groupId: number
mediaType: number
inviterId: number
joinedUserIds?: number[]
inviteeIds?: number[]
}
// 创建新通话;私聊或群聊根据 conversationType 区分
export const createCall = (data: ImRtcCallCreateReqVO) => {
return request.post<ImRtcCallRespVO>({ url: '/im/rtc/create', data })
}
// 通话中追加邀请;仅群通话可用
export const inviteCall = (data: ImRtcCallInviteReqVO) => {
return request.post<boolean>({ url: '/im/rtc/invite', data })
}
// 加入已有群通话;用于胶囊条「加入」按钮
export const joinCall = (room: string) => {
return request.post<ImRtcCallRespVO>({ url: '/im/rtc/join', params: { room } })
}
// 接听通话
export const acceptCall = (room: string) => {
return request.post<ImRtcCallRespVO>({ url: '/im/rtc/accept', params: { room } })
}
// 拒绝通话
export const rejectCall = (room: string) => {
return request.post<boolean>({ url: '/im/rtc/reject', params: { room } })
}
// 取消邀请;主叫接通前调用
export const cancelCall = (room: string) => {
return request.post<boolean>({ url: '/im/rtc/cancel', params: { room } })
}
// 离开通话;接通后调用
export const leaveCall = (room: string) => {
return request.post<boolean>({ url: '/im/rtc/leave', params: { room } })
}
// 振铃超时检查RUNNING 端 timer 兜底,触发后端立即扫描该 room 的超时 INVITING接口静默
export const noAnswerCallCheck = (room: string) => {
return request.post<boolean>({ url: '/im/rtc/no-answer-call-check', params: { room } })
}
// 查询当前进行中的通话;目前仅群聊场景(胶囊条),返回 null 表示无活跃通话
export const getActiveCall = (groupId: number) => {
return request.get<ImRtcGroupCallRespVO | null>({
url: '/im/rtc/get-active-call',
params: { groupId }
})
}

View File

@ -10,6 +10,9 @@ export interface AlertConfig {
sceneRuleIds: string // 关联的场景联动规则编号数组
receiveUserIds: string // 接收的用户编号数组
receiveTypes: string // 接收的类型数组
smsTemplateCode?: string // 短信模板编号
mailTemplateCode?: string // 邮件模板编号
notifyTemplateCode?: string // 站内信模板编号
}
// IoT 告警配置 API

View File

@ -13,6 +13,7 @@ export interface DataSinkVO {
| TcpConfig
| WebSocketConfig
| MqttConfig
| DatabaseConfig
| RocketMQConfig
| KafkaMQConfig
| RabbitMQConfig
@ -73,6 +74,14 @@ export interface MqttConfig extends Config {
topic: string
}
/** Database 配置 */
export interface DatabaseConfig extends Config {
jdbcUrl: string
username: string
password: string
tableName: string
}
/** RocketMQ 配置 */
export interface RocketMQConfig extends Config {
nameServer: string

View File

@ -215,8 +215,8 @@ export const ThingModelFormRules = {
identifier: [
{ required: true, message: '标识符不能为空', trigger: 'blur' },
{
pattern: /^[a-zA-Z0-9_]{1,50}$/,
message: '支持大小写字母、数字和下划线,不超过 50 个字符',
pattern: /^[a-zA-Z][a-zA-Z0-9_]{0,31}$/,
message: '支持大小写字母、数字和下划线,必须以字母开头,不超过 32 个字符',
trigger: 'blur'
},
{

View File

@ -9,6 +9,7 @@ export interface UserVO {
loginIp: string
mark: string
mobile: string
email: string | undefined
name: string | undefined
nickname: string | undefined
registerIp: string

View File

@ -6,6 +6,7 @@ export interface ProAndonConfigVO {
reason: string // 呼叫原因
level: number // 级别
handlerRoleId: number // 处置人角色编号
handlerRoleName: string // 处置人角色名称
handlerUserId: number // 处置人编号
handlerUserNickname: string // 处置人昵称
remark: string // 备注

View File

@ -5,7 +5,7 @@ export interface WmMiscIssueVO {
id: number
code: string
name: string
type: string
type: number
sourceDocType: string
sourceDocId: number
sourceDocCode: string

View File

@ -10,6 +10,7 @@ export interface DeptVO {
phone: string
email: string
createTime: Date
children?: DeptVO[]
}
// 查询部门(精简)列表

View File

@ -19,6 +19,16 @@ export interface MailSendReqVO {
templateParams: Map<String, Object>
}
export interface MailTemplateSimpleVO {
id: number
name: string
code: string
}
// 查询邮件模版精简列表
export const getSimpleMailTemplateList = async () => {
return await request.get({ url: '/system/mail-template/simple-list' })
}
// 查询邮件模版列表
export const getMailTemplatePage = async (params: PageParam) => {
return await request.get({ url: '/system/mail-template/page', params })

View File

@ -18,6 +18,17 @@ export interface NotifySendReqVO {
templateParams: Map<String, Object>
}
export interface NotifyTemplateSimpleVO {
id: number
name: string
code: string
}
// 查询站内信模板精简列表
export const getSimpleNotifyTemplateList = async () => {
return await request.get({ url: '/system/notify-template/simple-list' })
}
// 查询站内信模板列表
export const getNotifyTemplatePage = async (params: PageParam) => {
return await request.get({ url: '/system/notify-template/page', params })
@ -45,7 +56,10 @@ export const deleteNotifyTemplate = async (id: number) => {
// 批量删除站内信模板
export const deleteNotifyTemplateList = async (ids: number[]) => {
return await request.delete({ url: '/system/notify-template/delete-list', params: { ids: ids.join(',') } })
return await request.delete({
url: '/system/notify-template/delete-list',
params: { ids: ids.join(',') }
})
}
// 发送站内信

View File

@ -21,6 +21,16 @@ export interface SendSmsReqVO {
templateParams: Map<String, Object>
}
export interface SmsTemplateSimpleVO {
id: number
name: string
code: string
}
// 查询短信模板精简列表
export const getSimpleSmsTemplateList = () => {
return request.get({ url: '/system/sms-template/simple-list' })
}
// 查询短信模板列表
export const getSmsTemplatePage = (params: PageParam) => {
return request.get({ url: '/system/sms-template/page', params })

View File

@ -5,6 +5,7 @@ export interface UserVO {
username: string
nickname: string
deptId: number
deptName?: string
postIds: string[]
email: string
mobile: string
@ -17,11 +18,34 @@ export interface UserVO {
createTime: Date
}
// 获取用户精简信息列表
export const getSimpleUserList = (): Promise<UserVO[]> => {
return request.get({ url: '/system/user/simple-list' })
}
// 按用户编号查询用户精简信息(点头像弹名片)
export const getSimpleUser = (id: number | string) => {
return request.get<UserVO>({ url: '/system/user/get-simple', params: { id } })
}
// 按昵称模糊搜索用户(加好友)
export const getSimpleUserListByNickname = (nickname: string) => {
return request.get<UserVO[]>({
url: '/system/user/list-by-nickname',
params: { nickname }
})
}
// 查询用户管理列表
export const getUserPage = (params: PageParam) => {
return request.get({ url: '/system/user/page', params })
}
// 查询用户管理列表
export const getUserList = (ids: number[]) => {
return request.get({ url: '/system/user/list', params: { ids: ids.join(',') } })
}
// 查询用户详情
export const getUser = (id: number) => {
return request.get({ url: '/system/user/get?id=' + id })
@ -74,8 +98,3 @@ export const updateUserStatus = (id: number, status: number) => {
}
return request.put({ url: '/system/user/update-status', data: data })
}
// 获取用户精简信息列表
export const getSimpleUserList = (): Promise<UserVO[]> => {
return request.get({ url: '/system/user/simple-list' })
}

77
src/api/wms/home/index.ts Normal file
View File

@ -0,0 +1,77 @@
import request from '@/config/axios'
// WMS 首页统计查询参数
export interface WmsHomeStatisticsReqVO {
warehouseId?: number
goodsLimit?: number
warehouseLimit?: number
}
// WMS 首页单据状态统计 VO
export interface WmsHomeOrderStatusVO {
status: number
count: number
}
// WMS 首页单据汇总统计 VO
export interface WmsHomeOrderSummaryVO {
type: number
total: number
statuses: WmsHomeOrderStatusVO[]
}
// WMS 首页单据趋势 VO
export interface WmsHomeOrderTrendVO {
time: string | number
receiptCount: number
shipmentCount: number
movementCount: number
checkCount: number
}
// WMS 首页商品库存排行 VO
export interface WmsHomeInventoryItemRankVO {
id: number
name: string
quantity: number
}
// WMS 首页仓库库存排行 VO
export interface WmsHomeInventoryWarehouseRankVO {
id: number
name: string
quantity: number
}
// WMS 首页库存汇总统计 VO
export interface WmsHomeInventorySummaryVO {
totalQuantity: number
goodsShareList: WmsHomeInventoryItemRankVO[]
warehouseDistributionList: WmsHomeInventoryWarehouseRankVO[]
}
// WMS 首页统计 API
export const WmsHomeStatisticsApi = {
// 获得首页单据汇总统计
getOrderSummary: async (params?: WmsHomeStatisticsReqVO): Promise<WmsHomeOrderSummaryVO[]> => {
return await request.get({ url: `/wms/home-statistics/order-summary`, params })
},
// 获得首页单据趋势
getOrderTrend: async (
days?: number,
params?: WmsHomeStatisticsReqVO
): Promise<WmsHomeOrderTrendVO[]> => {
return await request.get({
url: `/wms/home-statistics/order-trend`,
params: { ...params, days }
})
},
// 获得首页库存汇总统计
getInventorySummary: async (
params?: WmsHomeStatisticsReqVO
): Promise<WmsHomeInventorySummaryVO> => {
return await request.get({ url: `/wms/home-statistics/inventory-summary`, params })
}
}

View File

@ -0,0 +1,33 @@
import request from '@/config/axios'
// WMS 库存记录 VO
export interface InventoryHistoryVO {
id?: number
itemId?: number
itemCode?: string
itemName?: string
unit?: string
skuId?: number
skuCode?: string
skuName?: string
warehouseId?: number
warehouseName?: string
quantity?: number
beforeQuantity?: number
afterQuantity?: number
price?: number
totalPrice?: number
remark?: string
orderId?: number
orderNo?: string
orderType?: number
createTime?: Date
}
// WMS 库存记录 API
export const InventoryHistoryApi = {
// 查询库存记录分页
getInventoryHistoryPage: async (params: any) => {
return await request.get({ url: '/wms/inventory-history/page', params })
}
}

View File

@ -0,0 +1,36 @@
import request from '@/config/axios'
// WMS 库存统计 VO
export interface InventoryVO {
id?: number
itemId?: number
itemCode?: string
itemName?: string
unit?: string
skuId?: number
skuCode?: string
skuName?: string
warehouseId?: number
warehouseName?: string
quantity?: number
remark?: string
createTime?: Date
}
// WMS 库存统计列表 Request VO
export interface InventoryListReqVO {
warehouseId: number
}
// WMS 库存统计 API
export const InventoryApi = {
// 查询库存统计分页
getInventoryPage: async (params: any) => {
return await request.get({ url: '/wms/inventory/page', params })
},
// 查询库存统计列表
getInventoryList: async (params: InventoryListReqVO) => {
return await request.get({ url: '/wms/inventory/list', params })
}
}

View File

@ -0,0 +1,47 @@
import request from '@/config/axios'
// WMS 商品品牌 VO
export interface ItemBrandVO {
id?: number
code?: string
name?: string
createTime?: Date
}
// WMS 商品品牌 API
export const ItemBrandApi = {
// 查询商品品牌分页
getItemBrandPage: async (params: any) => {
return await request.get({ url: '/wms/item-brand/page', params })
},
// 查询商品品牌精简列表
getItemBrandSimpleList: async () => {
return await request.get({ url: '/wms/item-brand/simple-list' })
},
// 查询商品品牌详情
getItemBrand: async (id: number) => {
return await request.get({ url: '/wms/item-brand/get?id=' + id })
},
// 新增商品品牌
createItemBrand: async (data: ItemBrandVO) => {
return await request.post({ url: '/wms/item-brand/create', data })
},
// 修改商品品牌
updateItemBrand: async (data: ItemBrandVO) => {
return await request.put({ url: '/wms/item-brand/update', data })
},
// 删除商品品牌
deleteItemBrand: async (id: number) => {
return await request.delete({ url: '/wms/item-brand/delete?id=' + id })
},
// 导出商品品牌
exportItemBrand: async (params) => {
return await request.download({ url: '/wms/item-brand/export-excel', params })
}
}

View File

@ -0,0 +1,46 @@
import request from '@/config/axios'
// WMS 商品分类 VO
export interface ItemCategoryVO {
id?: number
parentId?: number
code?: string
name?: string
sort?: number
status?: number
createTime?: Date
children?: ItemCategoryVO[]
}
// WMS 商品分类 API
export const ItemCategoryApi = {
// 查询商品分类列表
getItemCategoryList: async (params?: any) => {
return await request.get({ url: '/wms/item-category/list', params })
},
// 查询商品分类精简列表
getItemCategorySimpleList: async () => {
return await request.get({ url: '/wms/item-category/simple-list' })
},
// 查询商品分类详情
getItemCategory: async (id: number) => {
return await request.get({ url: '/wms/item-category/get?id=' + id })
},
// 新增商品分类
createItemCategory: async (data: ItemCategoryVO) => {
return await request.post({ url: '/wms/item-category/create', data })
},
// 修改商品分类
updateItemCategory: async (data: ItemCategoryVO) => {
return await request.put({ url: '/wms/item-category/update', data })
},
// 删除商品分类
deleteItemCategory: async (id: number) => {
return await request.delete({ url: '/wms/item-category/delete?id=' + id })
}
}

View File

@ -0,0 +1,55 @@
import request from '@/config/axios'
import { ItemSkuVO } from './sku'
// WMS 商品 VO
export interface ItemVO {
id?: number
code?: string
name?: string
categoryId?: number
categoryName?: string
unit?: string
brandId?: number
brandName?: string
remark?: string
skus?: ItemSkuVO[]
createTime?: Date
}
// WMS 商品 API
export const ItemApi = {
// 查询商品分页
getItemPage: async (params: any) => {
return await request.get({ url: '/wms/item/page', params })
},
// 查询商品精简列表
getItemSimpleList: async (params?: any) => {
return await request.get({ url: '/wms/item/simple-list', params })
},
// 查询商品详情
getItem: async (id: number) => {
return await request.get({ url: '/wms/item/get?id=' + id })
},
// 新增商品
createItem: async (data: ItemVO) => {
return await request.post({ url: '/wms/item/create', data })
},
// 修改商品
updateItem: async (data: ItemVO) => {
return await request.put({ url: '/wms/item/update', data })
},
// 删除商品
deleteItem: async (id: number) => {
return await request.delete({ url: '/wms/item/delete?id=' + id })
},
// 导出商品
exportItem: async (params: any) => {
return await request.download({ url: '/wms/item/export-excel', params })
}
}

View File

@ -0,0 +1,33 @@
import request from '@/config/axios'
// WMS 商品 SKU VO
export interface ItemSkuVO {
id?: number
name?: string
itemId?: number
itemCode?: string
itemName?: string
categoryId?: number
categoryName?: string
unit?: string
brandId?: number
brandName?: string
barCode?: string
code?: string
length?: number
width?: number
height?: number
grossWeight?: number
netWeight?: number
costPrice?: number
sellingPrice?: number
createTime?: Date
}
// WMS 商品 SKU API
export const ItemSkuApi = {
// 按 SKU 维度分页(支持商品 / 品牌 / 分类多表联查筛选)
getItemSkuPage: async (params: any) => {
return await request.get({ url: '/wms/item-sku/page', params })
}
}

View File

@ -0,0 +1,61 @@
import request from '@/config/axios'
// WMS 往来企业 VO
export interface MerchantVO {
id?: number
code?: string
name?: string
type?: number
level?: string
bankName?: string
bankAccount?: string
address?: string
mobile?: string
telephone?: string
contact?: string
email?: string
remark?: string
createTime?: Date
}
export interface MerchantSimpleListReqVO {
types?: number[]
}
// WMS 往来企业 API
export const MerchantApi = {
// 查询往来企业分页
getMerchantPage: async (params: any) => {
return await request.get({ url: '/wms/merchant/page', params })
},
// 查询往来企业精简列表
getMerchantSimpleList: async (params?: MerchantSimpleListReqVO) => {
return await request.get({ url: '/wms/merchant/simple-list', params })
},
// 查询往来企业详情
getMerchant: async (id: number) => {
return await request.get({ url: '/wms/merchant/get?id=' + id })
},
// 新增往来企业
createMerchant: async (data: MerchantVO) => {
return await request.post({ url: '/wms/merchant/create', data })
},
// 修改往来企业
updateMerchant: async (data: MerchantVO) => {
return await request.put({ url: '/wms/merchant/update', data })
},
// 删除往来企业
deleteMerchant: async (id: number) => {
return await request.delete({ url: '/wms/merchant/delete?id=' + id })
},
// 导出往来企业
exportMerchant: async (params: any) => {
return await request.download({ url: '/wms/merchant/export-excel', params })
}
}

View File

@ -0,0 +1,49 @@
import request from '@/config/axios'
// WMS 仓库 VO
export interface WarehouseVO {
id?: number
code?: string
name?: string
remark?: string
sort?: number
createTime?: Date
}
// WMS 仓库 API
export const WarehouseApi = {
// 查询仓库分页
getWarehousePage: async (params: any) => {
return await request.get({ url: '/wms/warehouse/page', params })
},
// 查询仓库精简列表
getWarehouseSimpleList: async () => {
return await request.get({ url: '/wms/warehouse/simple-list' })
},
// 查询仓库详情
getWarehouse: async (id: number) => {
return await request.get({ url: '/wms/warehouse/get?id=' + id })
},
// 新增仓库
createWarehouse: async (data: WarehouseVO) => {
return await request.post({ url: '/wms/warehouse/create', data })
},
// 修改仓库
updateWarehouse: async (data: WarehouseVO) => {
return await request.put({ url: '/wms/warehouse/update', data })
},
// 删除仓库
deleteWarehouse: async (id: number) => {
return await request.delete({ url: '/wms/warehouse/delete?id=' + id })
},
// 导出仓库
exportWarehouse: async (params) => {
return await request.download({ url: '/wms/warehouse/export-excel', params })
}
}

View File

@ -0,0 +1,21 @@
// WMS 盘库单明细 VO
export interface CheckOrderDetailVO {
id?: number
orderId?: number
itemId?: number
itemCode?: string
itemName?: string
unit?: string
skuId?: number
skuCode?: string
skuName?: string
inventoryId?: number
warehouseId?: number
warehouseName?: string
receiptTime?: Date
quantity?: number
checkQuantity?: number
availableQuantity?: number
price?: number
createTime?: Date
}

View File

@ -0,0 +1,73 @@
import request from '@/config/axios'
import { CheckOrderDetailVO } from './detail'
// WMS 盘库单 VO
export interface CheckOrderVO {
id?: number
no?: string
orderTime?: string
status?: number
remark?: string
warehouseId?: number
warehouseName?: string
totalQuantity?: number
totalPrice?: number
actualPrice?: number
details?: CheckOrderDetailVO[]
createTime?: Date
creator?: string
creatorName?: string
updateTime?: Date
updater?: string
updaterName?: string
}
// WMS 盘库单 API
export const CheckOrderApi = {
// 查询盘库单分页
getCheckOrderPage: async (params: any) => {
return await request.get({ url: '/wms/check-order/page', params })
},
// 查询盘库单详情
getCheckOrder: async (id: number) => {
return await request.get({ url: '/wms/check-order/get?id=' + id })
},
// 查询盘库单明细
getCheckOrderDetailListByOrderId: async (orderId: number) => {
return await request.get({
url: '/wms/check-order-detail/list-by-order-id?orderId=' + orderId
})
},
// 新增盘库单
createCheckOrder: async (data: CheckOrderVO) => {
return await request.post({ url: '/wms/check-order/create', data })
},
// 修改盘库单
updateCheckOrder: async (data: CheckOrderVO) => {
return await request.put({ url: '/wms/check-order/update', data })
},
// 完成盘库
completeCheckOrder: async (id: number) => {
return await request.put({ url: '/wms/check-order/complete?id=' + id })
},
// 作废盘库单
cancelCheckOrder: async (id: number) => {
return await request.put({ url: '/wms/check-order/cancel?id=' + id })
},
// 删除盘库单
deleteCheckOrder: async (id: number) => {
return await request.delete({ url: '/wms/check-order/delete?id=' + id })
},
// 导出盘库单
exportCheckOrder: async (params: any) => {
return await request.download({ url: '/wms/check-order/export-excel', params })
}
}

View File

@ -0,0 +1,21 @@
// WMS 移库单明细 VO
export interface MovementOrderDetailVO {
id?: number
orderId?: number
itemId?: number
itemCode?: string
itemName?: string
unit?: string
skuId?: number
skuCode?: string
skuName?: string
sourceWarehouseId?: number
sourceWarehouseName?: string
targetWarehouseId?: number
targetWarehouseName?: string
quantity?: number
availableQuantity?: number
price?: number
totalPrice?: number
createTime?: Date
}

View File

@ -0,0 +1,74 @@
import request from '@/config/axios'
import { MovementOrderDetailVO } from './detail'
// WMS 移库单 VO
export interface MovementOrderVO {
id?: number
no?: string
orderTime?: string
status?: number
remark?: string
sourceWarehouseId?: number
sourceWarehouseName?: string
targetWarehouseId?: number
targetWarehouseName?: string
totalQuantity?: number
totalPrice?: number
details?: MovementOrderDetailVO[]
createTime?: Date
creator?: string
creatorName?: string
updateTime?: Date
updater?: string
updaterName?: string
}
// WMS 移库单 API
export const MovementOrderApi = {
// 查询移库单分页
getMovementOrderPage: async (params: any) => {
return await request.get({ url: '/wms/movement-order/page', params })
},
// 查询移库单详情
getMovementOrder: async (id: number) => {
return await request.get({ url: '/wms/movement-order/get?id=' + id })
},
// 查询移库单明细
getMovementOrderDetailListByOrderId: async (orderId: number) => {
return await request.get({
url: '/wms/movement-order-detail/list-by-order-id?orderId=' + orderId
})
},
// 新增移库单
createMovementOrder: async (data: MovementOrderVO) => {
return await request.post({ url: '/wms/movement-order/create', data })
},
// 修改移库单
updateMovementOrder: async (data: MovementOrderVO) => {
return await request.put({ url: '/wms/movement-order/update', data })
},
// 完成移库
completeMovementOrder: async (id: number) => {
return await request.put({ url: '/wms/movement-order/complete?id=' + id })
},
// 作废移库单
cancelMovementOrder: async (id: number) => {
return await request.put({ url: '/wms/movement-order/cancel?id=' + id })
},
// 删除移库单
deleteMovementOrder: async (id: number) => {
return await request.delete({ url: '/wms/movement-order/delete?id=' + id })
},
// 导出移库单
exportMovementOrder: async (params: any) => {
return await request.download({ url: '/wms/movement-order/export-excel', params })
}
}

View File

@ -0,0 +1,18 @@
// WMS 入库单明细 VO
export interface ReceiptOrderDetailVO {
id?: number
orderId?: number
itemId?: number
itemCode?: string
itemName?: string
unit?: string
skuId?: number
skuCode?: string
skuName?: string
warehouseId?: number
warehouseName?: string
quantity?: number
price?: number
totalPrice?: number
createTime?: Date
}

View File

@ -0,0 +1,76 @@
import request from '@/config/axios'
import { ReceiptOrderDetailVO } from './detail'
// WMS 入库单 VO
export interface ReceiptOrderVO {
id?: number
no?: string
type?: number
orderTime?: string
status?: number
bizOrderNo?: string
merchantId?: number
merchantName?: string
remark?: string
warehouseId?: number
warehouseName?: string
totalQuantity?: number
totalPrice?: number
details?: ReceiptOrderDetailVO[]
createTime?: Date
creator?: string
creatorName?: string
updateTime?: Date
updater?: string
updaterName?: string
}
// WMS 入库单 API
export const ReceiptOrderApi = {
// 查询入库单分页
getReceiptOrderPage: async (params: any) => {
return await request.get({ url: '/wms/receipt-order/page', params })
},
// 查询入库单详情
getReceiptOrder: async (id: number) => {
return await request.get({ url: '/wms/receipt-order/get?id=' + id })
},
// 查询入库单明细
getReceiptOrderDetailListByOrderId: async (orderId: number) => {
return await request.get({
url: '/wms/receipt-order-detail/list-by-order-id?orderId=' + orderId
})
},
// 新增入库单
createReceiptOrder: async (data: ReceiptOrderVO) => {
return await request.post({ url: '/wms/receipt-order/create', data })
},
// 修改入库单
updateReceiptOrder: async (data: ReceiptOrderVO) => {
return await request.put({ url: '/wms/receipt-order/update', data })
},
// 完成入库
completeReceiptOrder: async (id: number) => {
return await request.put({ url: '/wms/receipt-order/complete?id=' + id })
},
// 作废入库单
cancelReceiptOrder: async (id: number) => {
return await request.put({ url: '/wms/receipt-order/cancel?id=' + id })
},
// 删除入库单
deleteReceiptOrder: async (id: number) => {
return await request.delete({ url: '/wms/receipt-order/delete?id=' + id })
},
// 导出入库单
exportReceiptOrder: async (params: any) => {
return await request.download({ url: '/wms/receipt-order/export-excel', params })
}
}

View File

@ -0,0 +1,19 @@
// WMS 出库单明细 VO
export interface ShipmentOrderDetailVO {
id?: number
orderId?: number
itemId?: number
itemCode?: string
itemName?: string
unit?: string
skuId?: number
skuCode?: string
skuName?: string
warehouseId?: number
warehouseName?: string
quantity?: number
availableQuantity?: number
price?: number
totalPrice?: number
createTime?: Date
}

View File

@ -0,0 +1,76 @@
import request from '@/config/axios'
import { ShipmentOrderDetailVO } from './detail'
// WMS 出库单 VO
export interface ShipmentOrderVO {
id?: number
no?: string
type?: number
orderTime?: string
status?: number
bizOrderNo?: string
merchantId?: number
merchantName?: string
remark?: string
warehouseId?: number
warehouseName?: string
totalQuantity?: number
totalPrice?: number
details?: ShipmentOrderDetailVO[]
createTime?: Date
creator?: string
creatorName?: string
updateTime?: Date
updater?: string
updaterName?: string
}
// WMS 出库单 API
export const ShipmentOrderApi = {
// 查询出库单分页
getShipmentOrderPage: async (params: any) => {
return await request.get({ url: '/wms/shipment-order/page', params })
},
// 查询出库单详情
getShipmentOrder: async (id: number) => {
return await request.get({ url: '/wms/shipment-order/get?id=' + id })
},
// 查询出库单明细
getShipmentOrderDetailListByOrderId: async (orderId: number) => {
return await request.get({
url: '/wms/shipment-order-detail/list-by-order-id?orderId=' + orderId
})
},
// 新增出库单
createShipmentOrder: async (data: ShipmentOrderVO) => {
return await request.post({ url: '/wms/shipment-order/create', data })
},
// 修改出库单
updateShipmentOrder: async (data: ShipmentOrderVO) => {
return await request.put({ url: '/wms/shipment-order/update', data })
},
// 完成出库
completeShipmentOrder: async (id: number) => {
return await request.put({ url: '/wms/shipment-order/complete?id=' + id })
},
// 作废出库单
cancelShipmentOrder: async (id: number) => {
return await request.put({ url: '/wms/shipment-order/cancel?id=' + id })
},
// 删除出库单
deleteShipmentOrder: async (id: number) => {
return await request.delete({ url: '/wms/shipment-order/delete?id=' + id })
},
// 导出出库单
exportShipmentOrder: async (params: any) => {
return await request.download({ url: '/wms/shipment-order/export-excel', params })
}
}

Binary file not shown.

View File

@ -162,13 +162,24 @@ function handleReady(cropperInstance: Cropper) {
}
function handlerToolbar(event: string, arg?: number) {
if (event === 'scaleX') {
scaleX = arg = scaleX === -1 ? 1 : -1
if (!cropper.value) return
const cropperImage = cropper.value.getCropperImage()
const cropperSelection = cropper.value.getCropperSelection()
if (event === 'reset') {
cropperImage?.$resetTransform()
cropperSelection?.$reset()
} else if (event === 'rotate') {
cropperImage?.$rotate(`${arg}deg`)
} else if (event === 'scaleX') {
scaleX = scaleX === -1 ? 1 : -1
cropperImage?.$scale(scaleX, 1)
} else if (event === 'scaleY') {
scaleY = scaleY === -1 ? 1 : -1
cropperImage?.$scale(1, scaleY)
} else if (event === 'zoom') {
cropperImage?.$zoom(arg!)
}
if (event === 'scaleY') {
scaleY = arg = scaleY === -1 ? 1 : -1
}
cropper?.value?.[event]?.(arg)
}
async function handleOk() {
@ -208,7 +219,8 @@ $prefix-cls: #{$namespace}-cropper-am;
&-cropper {
height: 300px;
background: #eee;
background-image: linear-gradient(
background-image:
linear-gradient(
45deg,
rgb(0 0 0 / 25%) 25%,
transparent 0,

View File

@ -1,50 +1,17 @@
<template>
<div :class="getClass" :style="getWrapperStyle">
<img
v-show="isReady"
ref="imgElRef"
:alt="alt"
:crossorigin="crossorigin"
:src="src"
:style="getImageStyle"
/>
<div ref="containerRef" :class="getClass" :style="getWrapperStyle">
<img v-show="false" ref="imgElRef" :alt="alt" :crossorigin="crossorigin" :src="src" />
</div>
</template>
<script lang="ts" setup>
import { CSSProperties, PropType } from 'vue'
import Cropper from 'cropperjs'
import 'cropperjs/dist/cropper.css'
import { useDesign } from '@/hooks/web/useDesign'
import { propTypes } from '@/utils/propTypes'
import { useDebounceFn } from '@vueuse/core'
defineOptions({ name: 'Cropper' })
type Options = Cropper.Options
const defaultOptions: Options = {
aspectRatio: 1,
zoomable: true,
zoomOnTouch: true,
zoomOnWheel: true,
cropBoxMovable: true,
cropBoxResizable: true,
toggleDragModeOnDblclick: true,
autoCrop: true,
background: true,
highlight: true,
center: true,
responsive: true,
restore: true,
checkCrossOrigin: true,
checkOrientation: true,
scalable: true,
modal: true,
guides: true,
movable: true,
rotatable: true
}
const props = defineProps({
src: propTypes.string.def(''),
alt: propTypes.string.def(''),
@ -56,35 +23,21 @@ const props = defineProps({
default: undefined
},
imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
options: { type: Object as PropType<Options>, default: () => ({}) }
options: { type: Object as PropType<Record<string, any>>, default: () => ({}) }
})
const emit = defineEmits(['cropend', 'ready', 'cropendError'])
const attrs = useAttrs()
const imgElRef = ref<ElRef<HTMLImageElement>>()
const cropper = ref<Nullable<Cropper>>()
const isReady = ref(false)
const imgElRef = ref<HTMLImageElement>()
const containerRef = ref<HTMLElement>()
const cropper = ref<Cropper>()
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('cropper-image')
const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80)
const getImageStyle = computed((): CSSProperties => {
return {
height: props.height,
maxWidth: '100%',
...props.imageStyle
}
})
const getClass = computed(() => {
return [
prefixCls,
attrs.class,
{
[`${prefixCls}--circled`]: props.circled
}
]
return [prefixCls, attrs.class]
})
const getWrapperStyle = computed((): CSSProperties => {
return { height: `${props.height}`.replace(/px/, '') + 'px' }
@ -98,27 +51,39 @@ onUnmounted(() => {
async function init() {
const imgEl = unref(imgElRef)
if (!imgEl) {
return
}
const containerEl = unref(containerRef)
if (!imgEl || !containerEl) return
cropper.value = new Cropper(imgEl, {
...defaultOptions,
ready: () => {
isReady.value = true
realTimeCroppered()
emit('ready', cropper.value)
},
crop() {
debounceRealTimeCroppered()
},
zoom() {
debounceRealTimeCroppered()
},
cropmove() {
debounceRealTimeCroppered()
},
container: containerEl,
...props.options
})
// Wait for custom elements to be ready, then configure
await nextTick()
const cropperSelection = cropper.value.getCropperSelection()
const cropperImage = cropper.value.getCropperImage()
if (cropperSelection) {
cropperSelection.initialCoverage = 0.5
cropperSelection.aspectRatio = 1
cropperSelection.movable = true
cropperSelection.resizable = true
cropperSelection.addEventListener('change', () => {
debounceRealTimeCroppered()
})
}
if (cropperImage) {
cropperImage.addEventListener('transform', () => {
debounceRealTimeCroppered()
})
// Emit ready once image loads
cropperImage.addEventListener('load', () => {
emit('ready', cropper.value)
realTimeCroppered()
})
}
}
// Real-time display preview
@ -127,17 +92,27 @@ function realTimeCroppered() {
}
// event: return base64 and width and height information after cropping
function croppered() {
if (!cropper.value) {
return
async function croppered() {
if (!cropper.value) return
const selection = cropper.value.getCropperSelection()
if (!selection) return
const imgInfo = {
x: selection.x,
y: selection.y,
width: selection.width,
height: selection.height
}
try {
let canvas = await selection.$toCanvas()
if (props.circled) {
canvas = getRoundedCanvas(canvas)
}
let imgInfo = cropper.value.getData()
const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas()
canvas.toBlob((blob) => {
if (!blob) {
return
}
let fileReader: FileReader = new FileReader()
if (!blob) return
const fileReader = new FileReader()
fileReader.readAsDataURL(blob)
fileReader.onloadend = (e) => {
emit('cropend', {
@ -149,11 +124,13 @@ function croppered() {
emit('cropendError')
}
}, 'image/png')
} catch {
// Selection may not be ready yet
}
}
// Get a circular picture canvas
function getRoundedCanvas() {
const sourceCanvas = cropper.value!.getCroppedCanvas()
function getRoundedCanvas(sourceCanvas: HTMLCanvasElement) {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')!
const width = sourceCanvas.width
@ -169,15 +146,3 @@ function getRoundedCanvas() {
return canvas
}
</script>
<style lang="scss">
$prefix-cls: #{$namespace}-cropper-image;
.#{$prefix-cls} {
&--circled {
.cropper-view-box,
.cropper-face {
border-radius: 50%;
}
}
}
</style>

View File

@ -2,7 +2,7 @@ import type Cropper from 'cropperjs'
export interface CropendResult {
imgBase64: string
imgInfo: Cropper.Data
imgInfo: { x: number; y: number; width: number; height: number }
}
export type { Cropper }

View File

@ -116,7 +116,8 @@ const toggleClick = () => {
:row="{
label: item.label
}"
>{{ item.label }}
>
{{ item.label }}
</slot>
</template>
@ -130,9 +131,7 @@ const toggleClick = () => {
<DictTag :type="item.dictType" :value="data[item.field] + ''" />
</slot>
<slot v-else :name="item.field" :row="data">
{{
item.mappedField ? data[item.mappedField] : data[item.field]
}}
{{ item.mappedField ? data[item.mappedField] : data[item.field] }}
</slot>
</template>
</ElDescriptionsItem>

View File

@ -165,8 +165,8 @@ $toolbar-position: -55px;
width: 80px;
height: 25px;
font-size: 12px;
color: #6a6a6a;
line-height: 25px;
color: #6a6a6a;
text-align: center;
background: #fff;
box-shadow:

View File

@ -94,9 +94,9 @@ const handleCloneComponent = (component: DiyComponent<any>) => {
<style scoped lang="scss">
.editor-left {
z-index: 1;
flex-shrink: 0;
user-select: none;
box-shadow: 8px 0 8px -8px rgb(0 0 0 / 12%);
user-select: none;
flex-shrink: 0;
:deep(.el-collapse) {
border-top: none;

View File

@ -22,8 +22,9 @@
<div
v-if="property.indicator === 'number'"
class="absolute bottom-10px right-10px rounded-xl bg-black p-x-8px p-y-2px text-10px text-white opacity-40"
>{{ currentIndex }} / {{ property.items.length }}</div
>
{{ currentIndex }} / {{ property.items.length }}
</div>
</div>
</template>
<script setup lang="ts">

View File

@ -55,12 +55,12 @@ const handleToggleFab = () => {
/* 模态背景 */
.modal-bg {
position: absolute;
left: calc(50% - 375px / 2);
top: 0;
left: calc(50% - 375px / 2);
z-index: 11;
width: 375px;
height: 100%;
background-color: rgba(#000000, 0.4);
background-color: rgb(0 0 0 / 40%);
}
.fab-icon {

View File

@ -192,39 +192,39 @@ const handleAppLinkChange = (appLink: AppLink) => {
<style scoped lang="scss">
.hot-zone {
position: absolute;
background: var(--el-color-primary-light-7);
opacity: 0.8;
border: 1px solid var(--el-color-primary);
color: var(--el-color-primary);
font-size: 16px;
z-index: 10;
display: flex;
font-size: 16px;
color: var(--el-color-primary);
cursor: move;
background: var(--el-color-primary-light-7);
border: 1px solid var(--el-color-primary);
opacity: 0.8;
align-items: center;
justify-content: center;
cursor: move;
z-index: 10;
/* 控制点 */
.ctrl-dot {
position: absolute;
z-index: 11;
width: 8px;
height: 8px;
border-radius: 50%;
border: inherit;
background-color: #fff;
z-index: 11;
border: inherit;
border-radius: 50%;
}
.delete {
display: none;
position: absolute;
top: 0;
right: 0;
display: none;
padding: 2px 2px 6px 6px;
background-color: var(--el-color-primary);
border-radius: 0 0 0 80%;
cursor: pointer;
color: #fff;
text-align: right;
cursor: pointer;
background-color: var(--el-color-primary);
border-radius: 0 0 0 80%;
}
&:hover {

View File

@ -28,15 +28,15 @@ const props = defineProps<{ property: HotZoneProperty }>()
<style scoped lang="scss">
.hot-zone {
position: absolute;
background: var(--el-color-primary-light-7);
opacity: 0.8;
border: 1px solid var(--el-color-primary);
color: var(--el-color-primary);
font-size: 14px;
z-index: 10;
display: flex;
font-size: 14px;
color: var(--el-color-primary);
cursor: move;
background: var(--el-color-primary-light-7);
border: 1px solid var(--el-color-primary);
opacity: 0.8;
align-items: center;
justify-content: center;
cursor: move;
z-index: 10;
}
</style>

View File

@ -42,22 +42,22 @@ const handleOpenEditDialog = () => {
<style scoped lang="scss">
.hot-zone {
position: absolute;
display: flex;
font-size: 12px;
color: #fff;
cursor: move;
background: #409effbf;
border: 1px solid var(--el-color-primary);
color: #fff;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
cursor: move;
/* 控制点 */
.ctrl-dot {
position: absolute;
width: 4px;
height: 4px;
border-radius: 50%;
background-color: #fff;
border-radius: 50%;
}
}
</style>

View File

@ -103,13 +103,16 @@ watch(
.el-carousel__indicator {
padding-top: 0;
padding-bottom: 0;
.el-carousel__button {
--el-carousel-indicator-height: 6px;
--el-carousel-indicator-width: 6px;
--el-carousel-indicator-out-color: #ff6000;
border-radius: 6px;
}
}
.el-carousel__indicator.is-active {
.el-carousel__button {
--el-carousel-indicator-width: 12px;

View File

@ -93,8 +93,8 @@ defineOptions({ name: 'NavigationBarCellProperty' })
const props = withDefaults(
defineProps<{
modelValue: NavigationBarCellProperty[]
isMp: boolean
modelValue?: NavigationBarCellProperty[]
isMp?: boolean
}>(),
{
modelValue: () => [],

View File

@ -67,10 +67,10 @@ const getSearchProp = computed(() => (cell: NavigationBarCellProperty) => {
.navigation-bar {
display: flex;
height: 50px;
padding: 0 6px;
background: #fff;
justify-content: space-between;
align-items: center;
padding: 0 6px;
/* 左边 */
.left {

View File

@ -77,7 +77,8 @@
v-if="property.fields.marketPrice.show && spu.marketPrice"
class="ml-4px text-10px line-through"
:style="{ color: property.fields.marketPrice.color }"
>{{ fenToYuan(spu.marketPrice) }}
>
{{ fenToYuan(spu.marketPrice) }}
</span>
</div>
<div class="text-12px">

View File

@ -74,8 +74,9 @@
v-if="property.fields.marketPrice.show && spu.marketPrice"
class="ml-4px text-10px line-through"
:style="{ color: property.fields.marketPrice.color }"
>{{ fenToYuan(spu.marketPrice) }}</span
>
{{ fenToYuan(spu.marketPrice) }}
</span>
</div>
<div class="text-12px">
<!-- 销量 -->

View File

@ -74,8 +74,9 @@
v-if="property.fields.marketPrice.show && spu.marketPrice"
class="ml-4px text-10px line-through"
:style="{ color: property.fields.marketPrice.color }"
>{{ fenToYuan(spu.marketPrice) }}</span
>
{{ fenToYuan(spu.marketPrice) }}
</span>
</div>
<div class="text-12px">
<!-- 销量 -->

View File

@ -583,12 +583,12 @@ $toolbar-height: 42px;
gap: 8px;
:deep(.el-tag) {
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1);
border: none;
box-shadow: 0 2px 8px 0 rgb(0 0 0 / 10%);
.el-tag__content {
width: 100%;
display: flex;
width: 100%;
align-items: center;
justify-content: flex-start;

View File

@ -27,7 +27,6 @@ const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('form')
export default defineComponent({
// eslint-disable-next-line vue/no-reserved-component-names
name: 'Form',
props: {
// Form

View File

@ -1,6 +1,12 @@
<!-- 数据字典 Select 选择器 -->
<template>
<el-select v-if="selectType === 'select'" class="w-1/1" v-bind="attrs">
<el-select
v-if="selectType === 'select'"
v-model="selectedValue"
class="w-1/1"
v-bind="attrs"
@change="handleChange"
>
<el-option
v-for="(dict, index) in getDictOptions"
:key="index"
@ -8,12 +14,24 @@
:value="dict.value"
/>
</el-select>
<el-radio-group v-if="selectType === 'radio'" class="w-1/1" v-bind="attrs">
<el-radio-group
v-if="selectType === 'radio'"
v-model="selectedValue"
class="w-1/1"
v-bind="attrs"
@change="handleChange"
>
<el-radio v-for="(dict, index) in getDictOptions" :key="index" :value="dict.value">
{{ dict.label }}
</el-radio>
</el-radio-group>
<el-checkbox-group v-if="selectType === 'checkbox'" class="w-1/1" v-bind="attrs">
<el-checkbox-group
v-if="selectType === 'checkbox'"
v-model="selectedValue"
class="w-1/1"
v-bind="attrs"
@change="handleChange"
>
<el-checkbox
v-for="(dict, index) in getDictOptions"
:key="index"
@ -33,6 +51,7 @@ const attrs = useAttrs()
//
interface Props {
dictType: string //
modelValue?: any // form-create modelValue
valueType?: 'str' | 'int' | 'bool' //
selectType?: 'select' | 'radio' | 'checkbox' // select checkbox radio
formCreateInject?: any
@ -43,6 +62,20 @@ const props = withDefaults(defineProps<Props>(), {
selectType: 'select'
})
const emit = defineEmits<{
(e: 'update:modelValue', value: any): void
}>()
const selectedValue = ref<any>()
watch(
() => props.modelValue,
(newValue) => {
selectedValue.value = newValue
},
{ immediate: true }
)
//
const getDictOptions = computed(() => {
switch (props.valueType) {
@ -56,4 +89,8 @@ const getDictOptions = computed(() => {
return []
}
})
const handleChange = (value: any) => {
emit('update:modelValue', value)
}
</script>

View File

@ -55,7 +55,6 @@ const displayUrl = computed(() => props.url || props.modelValue || '') // 显示
const showPreview = computed(() => {
return displayUrl.value && isUrl(displayUrl.value)
}) //
</script>
<style scoped>
@ -64,9 +63,9 @@ const showPreview = computed(() => {
}
.iframe-preview {
overflow: hidden;
border: 1px solid #dcdfe6;
border-radius: 4px;
overflow: hidden;
}
.iframe-content {
@ -76,11 +75,11 @@ const showPreview = computed(() => {
.iframe-placeholder {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
background-color: #fafafa;
border: 1px dashed #dcdfe6;
border-radius: 4px;
background-color: #fafafa;
align-items: center;
justify-content: center;
}
</style>

View File

@ -2,7 +2,7 @@ import request from '@/config/axios'
import { isEmpty } from '@/utils/is'
import { ApiSelectProps } from '@/components/FormCreate/src/type'
import { jsonParse } from '@/utils'
import { useUserStoreWithOut } from '@/store/modules/user'
import { getCurrentUserId } from '@/utils/auth'
export const useApiSelect = (option: ApiSelectProps) => {
return defineComponent({
@ -99,9 +99,7 @@ export const useApiSelect = (option: ApiSelectProps) => {
}
// 获取当前用户 ID
const userStore = useUserStoreWithOut()
const user = userStore.getUser
const currentUserId = user?.id
const currentUserId = getCurrentUserId()
if (currentUserId) {
// 根据多选/单选模式设置默认值
const defaultValue = props.multiple ? [currentUserId] : currentUserId

View File

@ -74,8 +74,8 @@ export const useUploadImgRule = () => {
{
type: 'switch',
field: 'disabled',
title: '是否显示删除按钮',
value: true
title: '是否禁用',
value: false
},
{
type: 'switch',

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import Iconify from '@purge-icons/generated'
import { Icon as IconifyIcon } from '@iconify/vue'
import { useDesign } from '@/hooks/web/useDesign'
defineOptions({ name: 'Icon' })
@ -20,57 +20,16 @@ const props = defineProps({
svgClass: propTypes.string.def('')
})
const elRef = ref<ElRef>(null)
const isLocal = computed(() => props.icon?.startsWith('svg-icon:'))
const symbolId = computed(() => {
return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon
})
const getIconifyStyle = computed(() => {
const { color, size } = props
return {
fontSize: `${size}px`,
height: '1em',
color
}
})
const getSvgClass = computed(() => {
const { svgClass } = props
return `iconify ${svgClass}`
})
const updateIcon = async (icon: string) => {
if (unref(isLocal)) return
const el = unref(elRef)
if (!el) return
await nextTick()
if (!icon) return
const svg = Iconify.renderSVG(icon, {})
if (svg) {
el.textContent = ''
el.appendChild(svg)
} else {
const span = document.createElement('span')
span.className = 'iconify'
span.dataset.icon = icon
el.textContent = ''
el.appendChild(span)
}
}
watch(
() => props.icon,
(icon: string) => {
updateIcon(icon)
}
)
</script>
<template>
@ -79,8 +38,11 @@ watch(
<use :xlink:href="symbolId" />
</svg>
<span v-else ref="elRef" :class="$attrs.class" :style="getIconifyStyle">
<span :class="getSvgClass" :data-icon="symbolId"></span>
</span>
<IconifyIcon
v-else
:icon="symbolId"
:class="getSvgClass"
:style="{ fontSize: `${size}px`, color }"
/>
</ElIcon>
</template>

View File

@ -14,7 +14,6 @@ defineProps({
title: propTypes.string.def(''),
schema: {
type: Array as PropType<Array<string | TipSchema>>,
required: true,
default: () => []
},
showIndex: propTypes.bool.def(true),

View File

@ -26,6 +26,7 @@ const { modelValue, color } = useVModels(props, emit)
<style scoped lang="scss">
:deep(.el-input-group__append) {
padding: 0;
.el-color-picker__trigger {
padding: 0;
border-left: none;

View File

@ -225,15 +225,16 @@ const eachCube = (callback: (x: number, y: number, cube: Cube) => void) => {
<style lang="scss" scoped>
.cube-table {
position: relative;
border-spacing: 0;
border-collapse: collapse;
border-spacing: 0;
.cube {
border: 1px solid var(--el-border-color);
text-align: center;
color: var(--el-text-color-secondary);
text-align: center;
cursor: pointer;
border: 1px solid var(--el-border-color);
box-sizing: border-box;
&.active {
background: var(--el-color-primary-light-9);
}
@ -242,28 +243,28 @@ const eachCube = (callback: (x: number, y: number, cube: Cube) => void) => {
.hot-area {
position: absolute;
display: flex;
color: var(--el-color-primary);
cursor: pointer;
background: var(--el-color-primary-light-8);
border: 1px solid var(--el-color-primary);
border-collapse: collapse;
border-spacing: 0;
box-sizing: border-box;
align-items: center;
justify-content: center;
border: 1px solid var(--el-color-primary);
background: var(--el-color-primary-light-8);
color: var(--el-color-primary);
box-sizing: border-box;
border-spacing: 0;
border-collapse: collapse;
cursor: pointer;
.btn-delete {
z-index: 1;
position: absolute;
top: -8px;
right: -8px;
height: 16px;
width: 16px;
z-index: 1;
display: flex;
width: 16px;
height: 16px;
background-color: #fff;
border-radius: 50%;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: #fff;
}
}
}

Some files were not shown because too many files have changed in this diff Show More