Compare commits

...

328 Commits

Author SHA1 Message Date
YunaiV b3f916639a fix(mall): 调整 Banner API 到 promotion 目录 2026-06-23 04:39:42 -07:00
YunaiV 2979bd99c1 feat(mall): 商品 tab 计数跟随筛选条件
getTabsCount 传入 queryParams,使 tab 数量与列表筛选口径一致;首页统计卡片仍用无参版(全局总数)。
2026-06-22 09:16:43 -07:00
YunaiV 8b2b93c92c docs(ai): 修正 Midjourney base64Array 字段注释
将错误的 size 注释改为“参考图 base64 列表”,与字段语义一致。
2026-06-21 21:24:25 -07:00
YunaiV 1072541927 fix(ai): 会话 createTime 按契约必填
- ChatConversationVO.createTime 改为必填字段
- 排序和分组时间计算去掉 Number(... || 0) 兜底
- 避免用 0 掩盖异常响应数据

ts:check 保持 0
2026-06-21 08:51:21 -07:00
YunaiV 69444994ad fix(ts): 收尾 IOT 物模型类型,ts:check 清零
- 去掉 ThingModelProperty/Event/Service 重复空接口,dataSpecs 建为通用联合类型
- dataSpecsList 支持结构体/枚举/布尔项,补齐枚举项 identifier/accessMode
- 物模型表单编辑态用类型断言;DataDefinition/ThingModelProperty 对动态 dataSpecs 安全访问
- 场景规则动态字段改用 Object.assign,规避 keyof 写入被推成 never

Co-Authored-By: Codex <codex@openai.com>

ts:check 26 → 0
2026-06-21 07:06:20 -07:00
YunaiV 983ac71559 fix(ts): 补全 ERP 订单 VO 并收敛 items 类型
- PurchaseOrderVO/SaleOrderVO 补齐详情/表单实际字段,新增 PurchaseOrderItemVO/SaleOrderItemVO
- 修正采购 VO 错字段:customerId→supplierId、outCount→inCount
- 采购/销售订单及入库/出库/退货表单 items 从 never[] 收敛到对应 item VO
- 订单选择弹窗选中行明确类型,提交用 !(对应按钮未选禁用约束)
- 财务付款/收款补 item 金额类型;价格计算对空值做 ?? 0

Co-Authored-By: Codex <codex@openai.com>

ts:check 85 → 26,ERP 错误清零,无新增类型错误
2026-06-21 06:32:17 -07:00
YunaiV 361f4d8d15 fix(ts): 收敛 CRM/BPM/IOT 局部类型
- CRM 商机状态 VO 合并:statuses 改为 BusinessStatusVO[],defaultStatus 改为 endStatus
- BPM form-create 对齐 v-model:api,用 @form-create/element-ui 的 Api 类型替代 ApiAttrs 并移除 @ts-ignore
- IOT OTA 任务 status 改可选、deviceScope 保持必填,详情页去掉假默认值
- 修复 SpuSelect expandRowKeys 类型,补 CRM 统计表格类型,移除 UserSignList 冗余 nickname 筛选
- 集合对比清理 vue-tsc 增量缓存后确认 0 新增错误

ts:check 133 → 85,无新增类型错误
2026-06-21 05:52:14 -07:00
YunaiV 08abd54ca0 fix(ts): 收敛局部类型并删除 XButton
- 删除 XButton/XTextButton,调用点改回 el-button
- ContextMenu 收窄 trigger,移除 Element Plus 不支持的 focus
- RewardActivityVO 三个商品范围数组改必填,表单创建/加载/重置均初始化为 []
- SpuShowcase 恢复直接 v-model,移除 computed 中转
- MES、Demo03、Codegen 等 VO 对齐表单场景,减少局部 Omit/unknown 断言
- MP 补 SimpleTagVO,修复用户标签 never[] 问题
- Redis 图表 option 在声明处定型,模板去除 EChartsOption cast
- 修复 member 统计页 fenToYuan 漏导入

ts:check 252 → 133,无新增类型错误
2026-06-21 04:44:53 -07:00
YunaiV f4b2cc4569 chore(ts): 为类型检查增加增量缓存
- ts:check 增加 --incremental 和 tsBuildInfoFile

- 缓存写入 node_modules/.cache/vue-tsc,避免污染仓库

- 热缓存耗时约 46.5s → 15.3s
2026-06-21 01:17:23 -07:00
YunaiV ba6ac7a420 fix(ts): 批量收敛类型问题并支持 Dialog loading
- Dialog 增加 loading prop,支付详情弹窗改用统一加载态
- 对齐支付/会员/商城等 API VO 类型,修复 dall3 尺寸入参转换
- 复用 VO/ReqVO 简化表单和查询参数类型
- 必填字段使用非空断言,减少过度防御
- 忽略 .playwright-cli 自动化测试产物

ts:check 478 → 252,无新增类型错误
2026-06-21 01:10:09 -07:00
YunaiV bc25430fa5 fix(ts): 低风险类型修复并修复预览交互问题
- formatDate 入参放宽为 dayjs.ConfigType,删除冗余 formatDateByConfig
- 多处 ref([]) 补精确数组类型(BPM/AI workflow/SMS log/DiyEditor 等)
- 路由参数/模板 index 显式 Number(),Upload 响应补局部类型
- LeaveCreateData 局部扩展 startUserSelectAssignees,不污染共享 VO
- 修复 mall 订单详情 formatDate.deliveryTime typo(发货时间行此前不显示)
- 修复 FloatingActionButton 缺失 handleActive,预览态点击仅收起面板

ts:check 542 → 478,无新增类型错误
2026-06-20 21:46:59 -07:00
YunaiV 3f779091be fix(ts): 修复 BPMN 设计器类型错误
- 设计器/预览补 DOM 空值保护与局部 service 类型(canvas/elementRegistry)
- UserTaskCustomConfig/ElementForm/ElementMultiInstance 等补 ref/数组/element 类型
- ProcessListener/Expression Dialog 补声明实际已 emit 的 select 事件
- UserTask 删除 return 后不可达旧代码
- ElementMultiInstance:loopCharacteristics 按需写入,不影响 ApproveMethod 扩展保存
- 运行时:keyboard 配置 { bindTo: document } → { bind: true },适配 diagram-js 15
2026-06-20 19:43:55 -07:00
YunaiV cb9400e93d fix(ts): 批量精确补全 CRM/ERP/MES 表单字段类型
- 各业务域表单状态按需标注 number/string | undefined,不使用 Partial,不改共享 VO
- FollowUpRecordForm 补全模板已绑定但缺失的字段
- RoleAssignMenuForm 修正 reactive 误用 .value,恢复菜单加载和重置逻辑
- BPMN 设计器组件保留不动

ts:check 748 → 590,无新增类型错误
2026-06-20 11:40:07 -07:00
YunaiV 9c95bebc26 fix(ts): 精确补全 Mall 表单字段类型,修复 TS2322
- 表单状态字段按需标注 number/string | undefined(不使用 Partial,不改共享 VO)
- UploadImgs 用 computed 兜底 [],避免传入 undefined
- spu 路由 categoryId 从 query 读取转 Number
- 必填字符串字段用 || '' 收口;skuData 改 ref<Sku>() 并由模板 v-if 兜底

ts:check 768 → 748,无新增类型错误
2026-06-20 10:46:50 -07:00
芋道源码 e566d9f60a
!885 Merge remote-tracking branch 'origin/master' into master-bpm-bug-fix
Merge pull request !885 from Jason/master-bpm-bug-fix
2026-06-20 17:44:33 +00:00
YunaiV e58fe91a29 fix(ts): 修复 delete/联合类型/ECharts 等低风险类型错误
- delete 操作符改为解构 rest(afterSale/UserAftersale/OrderUpdatePrice 提交参数)
- v-for 索引、createTime 等联合类型用 Number() 收敛
- ECharts axisLabel.textStyle → axisLabel.color(顺带让标签颜色真正生效)
- AlipayChannelForm 证书上传抽取 readCertFile 并补 UploadRequestHandler 类型
- ExpressForm reset 对齐 DeliveryExpressVO(移除 picUrl、补 code/logo/sort)
ts:check 795 → 768,无新增类型错误
2026-06-20 10:25:11 -07:00
YunaiV 0970806dca fix(ts): 补全音乐播放器进度交互并修复低风险类型错误
- 音乐列表改用 typed provide/inject + MusicSong,audioBar 接真实 audio 状态(进度/时长/seek/换源重载)
- 修复 SocialLogin 验证码因布尔误判而从不显示
- getBoolDictOptions 返回类型收窄为 boolean,:key 统一 String()
- CRM/售后 tab.paneName 判空 + String() 后写入查询参数
- demo03 子表入参字段名对齐接口(demo03courses/demo03grade)等单点修复

ts:check 829 → 795,无新增类型错误
2026-06-20 09:56:29 -07:00
YunaiV a57df0b2de fix(ts): 修复树表分页遗留与搜索失效,清理低风险类型错误
- 移除 erp 产品分类 / demo02 树表残留的 Pagination(/list + handleTree 不应分页)
- list API 增加可选 params 并转发,修复搜索条件被静默丢弃
- 修复 el-option value、coupon formatter、导入后缀、事件参数等低风险类型问题

ts:check 855 → 829,无新增类型错误
2026-06-20 09:08:05 -07:00
jason 2c4b6a65c8 Merge remote-tracking branch 'origin/master' into master-bpm-bug-fix 2026-06-20 23:56:36 +08:00
jason 950850529d fix: [bpm] simple 设计器导入问题修复 2026-06-20 23:38:05 +08:00
YunaiV 63dfc5e2c3 refactor(ts): 清理未使用代码并修复低风险类型错误
- 移除未使用的 import/变量/参数及死代码(TS6133/6192/6196)
- Element Plus size="mini" → "small"
- 补充 DICT_TYPE.PAY_TRANSFER_TYPE
- RTC 媒体元素改用 callback ref,卸载时清理 srcObject

ts:check 错误 1003 → 855,无新增类型错误
2026-06-20 08:29:55 -07:00
jason 4aba1f9ddb Merge remote-tracking branch 'origin/master' into master-bpm-bug-fix 2026-06-20 22:22:50 +08:00
jason 026f7e9b10 fix: [bpm] 并行分支后的审批人自选问题修复,https://t.zsxq.com/daxv1 2026-06-20 22:22:16 +08:00
YunaiV 18ed1cdfed style: 使用 Prettier 格式化源码 2026-06-20 07:01:34 -07:00
YunaiV 3208a76868 chore(lint): 调整 lint 配置并修复规则问题 2026-06-20 07:01:20 -07:00
jason e55613edc6 fix :[bpm] 超时时间为天时格式问题修复 2026-06-20 21:57:40 +08:00
YunaiV 2ea2d9b18f perf(build): 继续优化构建和 lint 执行效率
- 关闭构建 gzip size 统计并调整 chunk 警告阈值
- 移除 Vite dev/build 链路中的 ESLint 和 progress 插件
- 删除未使用的 legacy、terser、ejs、eslint、progress 构建依赖
- 为 ESLint、Stylelint、Prettier 增加缓存脚本
- 新增 lint-staged 配置,仅处理变更文件

perf(lint): 将 ESLint 热缓存耗时从约 16.3s 降到约 1.3s
2026-06-20 04:57:48 -07:00
YunaiV 3d37515cfc perf(build): 将生产构建从约 41.6s 优化到约 21.6s
- 使用 oxc 替换 terser 作为生产压缩器
- 生产构建跳过 ESLint、progress 和 auto d.ts 生成
- gzip/brotli 产物改为通过 VITE_COMPRESS 按需生成
- 移除无用的 EJS 和 top-level-await 构建插件
- 删除 vite-plugin-top-level-await 依赖
2026-06-20 04:31:53 -07:00
芋道源码 fb94d37f5f
!883 feat(im): 配置项增加IM即时通讯图标显示与隐藏
Merge pull request !883 from 梧桐听雨/master
2026-06-20 10:50:36 +00:00
YunaiV fe69fd7d94 fix: 移除未使用的 vue i18n 插件依赖
在移除 Vite 插件使用后,同步删除 @intlify/unplugin-vue-i18n。
保持 package.json 与 pnpm-lock.yaml 一致,避免继续保留可能触发
vue-i18n optimizer 问题的 esm-bundler alias 依赖路径。
2026-06-20 03:31:38 -07:00
芋道源码 774a9f24fc
Merge pull request #231 from DevDengChao/fix/vue-i18n-dep-optimizer
fix: remove @intlify/unplugin-vue-i18n to resolve init_runtime_dom_esm_bundler ReferenceError
2026-06-20 03:06:05 -07:00
YunaiV b0dcc7c840 feat(im): 优化免打扰会话未读展示
- 免打扰会话保留头像小红点提示
- 会话预览行展示免打扰未读条数
- 总未读统计继续排除免打扰会话
- 同步 Vue3 + Vben 三端会话项展示
2026-06-19 18:29:31 -07:00
YunaiV a5299ef2e8 fix(im):修复已读补上报字段与群通话竞态
- 将会话已上报读位置字段改为 reportedReadMessageId,避免旧 readMessageId 本地缓存污染跳过判断
- read API 跳过逻辑继续基于已上报读位置,失败后可重新进入会话补上报
- RTC_CALL_END 按 room 移除群通话胶囊,避免旧房间结束事件误删新房间通话
- participantsLoaded 仅在同 room 下继承,避免新房间误跳过参与者补齐
2026-06-19 11:16:42 -07:00
YunaiV b6e13c59c7 feat(im):优化已读上报补偿与群通话探测缓存
- 会话新增 readMessageId,记录已上报到服务端的最大已读消息编号
- readActive 与 WebSocket 自动已读改为基于服务端已上报读位置判断是否跳过接口
- read 接口成功后同步 readMessageId,失败时保留本端已读体验并允许后续重新进入补上报
- 拉取服务端 read 进度时同步更新会话 readMessageId,同时保持本地读位置单调合并
- 群信息新增 activeCallLoaded / activeCallExpired,首登与重连时失效群通话探测缓存
- 群通话胶囊在本地无通话且探测过期时懒加载 getActiveCall,避免离线错过通话后无法发现
- 群通话写入或移除时标记探测已加载,并避免通话探测状态写入 IndexedDB
- 为 IndexedDB DO 类型补充存储结构注释
2026-06-19 11:05:06 -07:00
YunaiV 2172415cad feat(im):优化已读上报、群详情缓存与 RTC 通话状态
- 已读上报增加本地读位置覆盖判断,避免切换会话和当前会话自动已读时重复调用 read 接口
- 标记会话已读时同步推进本地 read 游标并写入 IndexedDB,接口失败仅记录日志
- 缓存私聊对方 maxReadMessageId,并在状态补拉、回执更新和退出 IM 时维护缓存
- 增加群详情 infoLoaded 内存标记,减少切群时重复拉取群详情,手动刷新和关键通知仍强制刷新
- 同步 GROUP_INFO_UPDATE 的 joinApproval,避免群审批配置在前端缓存中陈旧
- 优化群通话胶囊条状态,记录 participantsLoaded,按需补齐参与者并在通话无人时移除胶囊
- RTC_CALL_START 生成群通话最小胶囊条,后续由参与者事件和 getActiveCall 补齐
- 退出 IM 时清理 RTC 状态和群通话缓存
- Vben antd/antd-next 调整媒体元素为函数 ref,修复 MediaStream 与元素挂载时序问题
- 修复 Vben 消息历史弹窗回调类型标注
2026-06-19 10:05:37 -07:00
YunaiV dcafe6efdc fix(im): 将频道消息的 pull 改成 pullChannelMessageList 2026-06-18 19:58:41 -07:00
YunaiV 61c9e1acf2 fix(im): 修复群备注首屏展示和聊天列表名称覆盖
- 后端群 VO 返回当前用户维度的 groupRemark 和 silent
- 群列表构建时通过成员关系回填个人群设置,并继续仅对有效成员回填置顶消息
- Vue3 群列表同步时以接口返回的个人群设置为准,只保留成员缓存
- 会话名写入入口统一使用 getGroupDisplayName,避免群备注被原群名覆盖
- 空群头像且成员未加载时异步预拉群成员,用于合成群头像
- 启用 IM Maven 模块和 yudao-server 对 IM 模块的依赖
2026-06-18 08:59:04 -07:00
YunaiV ab2fa4e6b8 fix(im):群昵称修改改为静默同步
- 后端将 GROUP_MEMBER_NICKNAME_UPDATE 改为非持久化事件,避免写入群聊历史消息
- 保留群昵称变更的 WebSocket 在线同步,继续更新成员 displayUserName
- Vue3 聊天侧栏从当前群成员的 displayUserName 回填「我在本群的昵称」
- Vue3 WebSocket 收到 GROUP_MEMBER_NICKNAME_UPDATE 时只同步 groupStore,不再插入消息列表
- 补充后端单测,覆盖群昵称变更事件不入库但仍推送
2026-06-18 06:57:05 -07:00
YunaiV fc7cd7bc07 fix(home): 统一首页项目卡片底部对齐
- 使用 UnoCSS 调整项目卡片等高布局
- 将项目描述和日期贴到底部,避免短内容卡片提前显示
- 限制长文本换行并保持日期不换行
2026-06-17 17:46:56 -07:00
YunaiV e5bba07dec fix(im): 补回录音格式探测和 IM 初始化门控
- 录音前按浏览器能力选择 MIME 类型,并按实际格式生成语音文件后缀
- 频道消息发送表单改用 UserSelectV2 多选接收人
- IM 外层壳完成本地库和缓存恢复后再挂载子路由
2026-06-17 19:51:59 +08:00
芋道源码 68c5f3fc4b
!884 Merge remote-tracking branch 'origin/master'
Merge pull request !884 from 芋道源码/master
2026-06-17 08:32:32 +00:00
YunaiV 07c8f143ea fix(im): 修复重新登录会话未读闪烁
- 新增会话读位置本地存储,独立维护 conversationReads
- 启动时先恢复本地读位置,并在会话列表渲染前修正未读状态
- 消息入库时基于读位置过滤已读历史消息,避免重新累计未读
- READ 同步与主动已读统一走 conversationStore,保证读位置单调推进
- 兼容旧会话 readMessageId 数据迁移
2026-06-17 13:30:16 +08:00
YunaiV 8ba76813ae fix(im): 收敛离线拉取的实时副作用
- 离线 pull 只还原历史好友、群聊事件气泡,不再重放实时通知副作用
- 好友详情请求增加 in-flight 去重,有效好友已存在时跳过重复拉取
- 修复软删好友重新添加时被本地缓存误跳过的问题
- 群创建通知只拉群详情,群成员改为进入会话后懒加载
- 避免群基础信息缺失或退群时兜底拉取整群成员
2026-06-17 09:20:29 +08:00
YunaiV ddafacf64d fix(im): 修复历史退群群未读和成员加载问题
- 已读上报使用会话末条消息编号兜底
- 历史退群群不再请求群成员列表
- 群聊 read 放开当前成员校验,保留可见性校验
- Vben 群详情补齐 joinStatus
- 补充退群群已读边界测试
2026-06-17 01:38:52 +08:00
YunaiV cb625d11bb fix(im): 修复点击未读会话后重新登录未读回潮
点击会话时,消息列表可能尚未加载完成,导致已读上报只能从空列表算出 messageId = 0,并跳过服务端已读接口。此时本地未读已清零,但服务端读位置未推进,重新登录后会按服务端状态恢复未读。

已读上报时改为取已加载消息最大编号和会话末条服务端消息编号的较大值,确保消息列表尚未加载时也能使用 lastMessageId 推进服务端读位置。
2026-06-17 00:48:44 +08:00
YunaiV 4879c4705f refactor: 扁平化 IM WebSocket 通知推送 API
- 将 WebSocket 推送入口统一为 userId/userIds + conversationType + contentType + payload
- 移除业务侧 ImNotificationWebSocketDTO 构造和无会话专用发送入口
- 收敛私聊、群聊、频道、好友、加群申请、RTC 通知调用路径
- 精简 ImNotificationWebSocketDTO,仅保留统一外壳字段
- 保留群消息 payload 的 receiptStatus、readCount、receiverUserIds
- 更新相关单元测试,覆盖群消息通知 payload 字段
2026-06-16 11:38:56 +08:00
liguangni 37e964dc91 feat(im): 配置项增加IM即时通讯图标显示与隐藏 2026-06-16 09:12:34 +08:00
DevDengChao 9552a3cd2e fix: remove @intlify/unplugin-vue-i18n to resolve ReferenceError
The plugin forces vue-i18n to use esm-bundler.js entry via resolve alias,
which contains esbuild/rolldown internal module init function references
(init_runtime_dom_esm_bundler, etc.). When the bundler splits code into
chunks, these cross-chunk references become orphaned, causing ReferenceError.

Removing the plugin lets vue-i18n use its standard .mjs entry, which
does not have these problematic references. Locale messages are then
compiled at runtime instead of build time, but functionality is unchanged.

Fixes #230
See https://github.com/rolldown/rolldown/issues/9515
2026-06-15 23:04:13 +08:00
YunaiV 2685bc357f feat(im): 增强消息拉取与状态补偿可靠性
- 新增会话读位置持久化接口与前端同步逻辑
- 增加好友、好友申请、加群申请的增量拉取补偿
- 统一前端 pull 编排,增加回扫窗口、落库等待和账号切换守卫
- 调整群成员为按群懒加载缓存,并移除全局成员增量链路
- 修复消息落库、读位置补偿、READ 事件乱序下的未读状态一致性
- 完善群申请红点快照刷新和管理员角色变化补偿
- 更新消息存储设计与修复记录文档
2026-06-15 08:26:32 +08:00
YunaiV cf85fd4c86 feat(im): 统一消息读位置和回执状态模型
- 新增 im_conversation_read 会话读位置表,并补充消息存储推拉相关索引
- 群消息固化 receiver_user_ids 快照,按可见成员快照拉取和统计回执
- 统一消息 status 为 NORMAL/RECALL,新增私聊 receipt_status 并复用统一回执状态
- 前端改用 receiptStatus 展示私聊已读、群回执和频道已读态
- 补齐私聊、群聊、频道 WebSocket 已读同步和离线补偿逻辑
- 更新 IM 消息状态、回执状态字典和管理后台展示
- 调整相关单测和测试建表脚本
2026-06-14 09:34:16 +08:00
YunaiV 9b416c722c Merge remote-tracking branch 'origin/master' 2026-06-14 02:48:19 +08:00
YunaiV 44136d310b feat(bpm): 支持审批任务附件上传与展示
- 审批通过、审批拒绝弹窗新增附件/图片上传
- 审批提交时携带 attachments 字段
- 审批完成或关闭弹窗后清理附件表单状态
- 审批流时间线支持展示审批附件
- 图片附件支持预览,非图片附件支持链接打开
- 统一附件上传目录、文件类型白名单和 5MB 大小限制
- ApprovalTaskInfo 增加 attachments 字段
2026-06-14 02:48:10 +08:00
YunaiV 8c796950f9 feat: 完善 IM 群历史消息拉取与历史群前端门控
- 后端群列表返回历史群成员状态 joinStatus,用于区分当前群和历史退群群
- 群消息拉取支持基于 receiver_user_ids 快照过滤可见消息
- 补充群消息 pull、群成员候选、私聊 pull 相关索引与 SQL 脚本
- 前端接入 joinStatus,并封装历史退群群判断
- 历史退群群禁发、隐藏群操作入口,并从通讯录、转发、推荐名片候选中排除
- 保留历史群会话展示能力,用于查看退群前历史消息
2026-06-14 02:01:09 +08:00
YunaiV 8b2dcc2b99 Merge remote-tracking branch 'origin/master' 2026-06-14 01:24:00 +08:00
YunaiV e61581eef2 fix(vite): 使用相对路径注入 SCSS 变量
对应知识星球问题:https://wx.zsxq.com/group/88858522214142/topic/82255281581554842

将 variables.scss 的注入路径从项目绝对路径改为相对当前 SCSS 文件的路径,避免 Windows 中文目录下 Sass 解析 @use 失败。
2026-06-14 01:23:54 +08:00
YunaiV 61c71b9a0e feat(infra): 增加定时任务同步入口
- 为 vue3 同步任务按钮补充 infra:job:create 权限
- 为 vben antd、antdv-next、ele 增加 syncJob API
- 为 vben 定时任务列表增加同步任务按钮和确认/loading 流程
2026-06-14 01:21:36 +08:00
YunaiV 067c99aec8 Merge branch 'dev' of https://github.com/yudaocode/yudao-ui-admin-vue3
# Conflicts:
#	src/views/bpm/model/index_new.vue
2026-06-14 01:11:15 +08:00
芋道源码 827665dd50
Merge pull request #229 from HmEJ/patch-1
feat(infra): 定时任务添加同步按钮
2026-06-14 01:06:25 +08:00
YunaiV 17428e9867 fix(mes): 修复 SN 码查看条码缺少 bizType
- 新增 SN 码条码业务类型与配置
- 支持按 SN 明细查询对应条码
- 抽离 SN 码明细弹窗组件
- 同步 vben antd/ele 的条码入口

Refs: https://t.zsxq.com/1YCqD
2026-06-13 18:51:45 +08:00
YunaiV 05d6196780 fix(vue3): 修复混合布局顶级菜单点击失效
rootOnly 顶部菜单改为优先查找可见根路由,避免命中 remaining.ts 中同 path 的隐藏静态父路由。

修复混合垂直、混合双列布局下支付管理、会员中心、CRM、MES 等顶级菜单点击无反应的问题。
2026-06-12 21:23:58 +08:00
YunaiV 74aaa6605e feat(layout): 对齐 Vben 5 布局与菜单路由行为
- 补齐六种 Vben 布局模式及设置面板入口
- 支持顶部根菜单、侧边 split 菜单、混合布局与双列菜单联动
- 支持菜单路由 query/hash/params、动态路径与登录重定向保参
- 外链路由唯一化,并支持 iframe 外链页面
- 调整设置入口、面包屑与折叠按钮展示逻辑
- 修复水平菜单更多弹层,仅展示溢出根菜单并避免原生弹层重复
- 新增布局路由与交互自测脚本
2026-06-12 15:11:13 +08:00
GGbond 7ea5cf5589
feat(infra): 定时任务添加同步按钮 2026-06-11 14:56:30 +08:00
YunaiV 394a3d075a feat(bpm): 支持流程打印渲染业务表单
- 在 vue3、vben antd、vben ele 打印弹窗中动态加载业务表单组件
- 未启用自定义打印模板且流程表单字段为空时,使用 formCustomViewPath 渲染业务详情
- 业务表单独立于打印表格渲染,避免表格单元格内嵌页面导致布局和分页问题
- 保留 readonly、print-mode 作为业务详情组件打印态适配约定
2026-06-11 01:21:56 +08:00
YunaiV caa6fa9be3 fix: 修复头像裁剪容器高度异常
- 穿透设置 cropperjs 动态插入的 cropper-canvas 高度
- 解决头像上传裁剪区域只显示 100px 的问题
2026-06-07 18:29:02 +08:00
YunaiV 849f33c5a1 Merge remote-tracking branch 'origin/master' 2026-06-07 18:10:32 +08:00
YunaiV a08bdab7eb fix: 修复 ContentWrap 搜索栏异常滚动条
- 覆盖 Element Plus 新版 el-card body 默认 overflow:auto
- 避免搜索栏 ContentWrap 出现多余内部滚动条
2026-06-07 18:10:22 +08:00
芋道源码 5f5702ab17
!881 fix: 修复 IM 用户项 API 引用路径大小写错误
Merge pull request !881 from 久我山八重很不高兴w/master
2026-06-07 06:22:59 +00:00
芋道源码 70f683fda2
!882 fix: 修复场景联动列表页最近触发未能正确显示最后触发时间
Merge pull request !882 from 熊猫大侠/master-iotscene
2026-06-07 06:21:46 +00:00
YunaiV a4f8760415 refactor(mes): 移除冗余农历插件依赖
- 移除节假日页面未使用的 dayjs-plugin-lunar 扩展
- 统一使用 tyme4ts 计算农历、节日和节气信息
- 清理 package.json 和 pnpm-lock.yaml 中的冗余依赖
2026-06-07 01:46:01 +08:00
YunaiV 9bcffb8b38 fix(router): 修复父子同名路由导致子菜单丢失
- 收窄同名子路由折叠条件
- 仅在父级只有一个同名默认页时折叠
- 修复商城装修下装修模板菜单不显示的问题
2026-06-07 01:30:47 +08:00
YunaiV 8f24a79444 Merge remote-tracking branch 'origin/master' 2026-06-06 23:40:27 +08:00
YunaiV 0e46f09779 fix(mall): 修复首页账户充值金额单位显示错误
- 首页运营数据的账户充值金额从分转换为元后展示
- 同步修复 Vue3 与 Vben 的 antd、ele、antdv-next 三端
2026-06-06 23:40:11 +08:00
panda 69cd0b7875 fix: 修复场景联动列表页最近触发未能正确显示最后触发时间 2026-06-02 11:26:19 +08:00
久我山八重很不高兴w ba5c378a00
update src/views/im/manager/face/userItem/index.vue.
Signed-off-by: 久我山八重很不高兴w <479067860@qq.com>
2026-06-01 14:00:10 +00:00
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 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 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 f3807e30d5 fix(im): 批量修复 P1/P2 问题
- 修复管理端消息内容搜索和私聊双向查询
- 加强 RTC 通话并发状态保护,去除重复接口错误提示
- 支持成员永久禁言
- 脱敏群消息 WebSocket 定向收件人字段
- 更新 IM bug 台账,剩余 P1/P2 共 35 个
2026-05-25 00:28:59 +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 72d8c499a4 feat(im): 修一批管理端统计与成员选择器细节
- 消息趋势 / 用户趋势图表加 loading 态(接口错误由全局拦截器统一提示)
- 群成员选择器 grid 模式补右上角 × 移除按钮
- 统计接口 6 个 API 补全返回值泛型
2026-05-22 20:15:15 +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 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 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 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 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 f58d1d88c8 feat(im): 振铃超时 Job 单人粒度标 NO_ANSWER + 独立 NO_ANSWER 信令推送
 feat(im): 处理 RTC_CALL(NO_ANSWER) 信令;私聊气泡显示「未接听」
2026-05-18 08:03:52 +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 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 03d0ce800d feat(im): 优化群邀请的 running 的交互 2026-05-17 16:46:10 +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 b455ce4949 feat(im): 优化 rtcStore 的命名 2026-05-14 22:15:35 +08:00
YunaiV 4a811fb0bb feat(im): 评审下 rtcStore 的实现 2026-05-14 17:16:46 +08:00
YunaiV e579a4de13 feat(im): 优化 rtc 整体弹窗界面 2026-05-14 09:44:39 +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 18e5c97bf3 feat(im): 将后端的 roomName 和 callId 融合,简化字段和逻辑(一致性更好、概念更简洁) 2026-05-12 20:29:08 +08:00
YunaiV 38cb980ce4 feat(im): 完善 rtc 的后端代码逻辑,各种代码风格的优化 2026-05-12 13:17:04 +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
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
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
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
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
YunaiV ffb69063b9 feat(im): 重构群通知相关,对齐 openim 的消息编号(继续优化代码) 2026-05-03 09:22:53 +08:00
YunaiV 43372c05ad feat(im): 重构群通知相关,对齐 openim 的消息编号 2026-05-03 02:00:43 +08:00
YunaiV fa27c27831 feat(im): 增加群角色(管理员) 2026-05-02 14:31:42 +08:00
YunaiV 3146f64edc feat(im): 未读计数改用 isNormalMessage,对齐后端 normal 语义 2026-05-02 09:06:01 +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
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
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
芋道源码 71d294ee29
Merge pull request #92 from GoldenZqqq/bpm-2st-stage
流程模型列表页面样式与文字细节调整优化
2024-10-16 19:38:20 +08:00
GoldenZqqq 860d3131ae feat: 流程模型列表页面样式与文字细节调整优化 2024-10-14 15:17:05 +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
765 changed files with 39868 additions and 5526 deletions

View File

@ -21,6 +21,9 @@ VITE_DROP_CONSOLE=false
# 是否sourcemap
VITE_SOURCEMAP=true
# 是否开启压缩,可以设置为 none, gzip, brotli, gzip,brotli
VITE_COMPRESS=none
# 打包路径
VITE_BASE_PATH=/

View File

@ -21,6 +21,9 @@ VITE_DROP_CONSOLE=false
# 是否sourcemap
VITE_SOURCEMAP=false
# 是否开启压缩,可以设置为 none, gzip, brotli, gzip,brotli
VITE_COMPRESS=none
# 打包路径
VITE_BASE_PATH=/

View File

@ -21,6 +21,9 @@ VITE_DROP_CONSOLE=true
# 是否sourcemap
VITE_SOURCEMAP=false
# 是否开启压缩,可以设置为 none, gzip, brotli, gzip,brotli
VITE_COMPRESS=none
# 打包路径
VITE_BASE_PATH=/

2
.gitignore vendored
View File

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

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

View File

@ -7,5 +7,6 @@
/src/types/env.d.ts
/src/types/auto-components.d.ts
/src/types/auto-imports.d.ts
/src/components/Tinyflow/ui/**
/docs/**/*
CHANGELOG

View File

@ -3,4 +3,5 @@
public/*
/dist*
/src/types/env.d.ts
/src/components/Tinyflow/ui/**
/docs/**/*

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

@ -284,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

@ -1,33 +1,31 @@
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-eslint2'
import { ViteEjsPlugin } from 'vite-plugin-ejs'
// @ts-ignore
import ElementPlus from 'unplugin-element-plus/vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import viteCompression from 'vite-plugin-compression'
import topLevelAwait from 'vite-plugin-top-level-await'
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons-ng'
import UnoCSS from 'unocss/vite'
export function createVitePlugins() {
export function createVitePlugins(isBuild = false, env: Record<string, string> = {}) {
const root = process.cwd()
const compressTypes = (env.VITE_COMPRESS || '')
.split(',')
.map((item) => item.trim())
.filter((item) => item === 'gzip' || item === 'brotli')
// 路径查找
function pathResolve(dir: string) {
return resolve(root, '.', dir)
}
return [
const plugins = [
Vue(),
VueJsx(),
UnoCSS(),
progress(),
ElementPlus({}),
AutoImport({
include: [
@ -49,7 +47,7 @@ export function createVitePlugins() {
'@/utils/dict': ['DICT_TYPE']
}
],
dts: 'src/types/auto-imports.d.ts',
dts: !isBuild && 'src/types/auto-imports.d.ts',
resolvers: [ElementPlusResolver()],
eslintrc: {
enabled: false, // Default `false`
@ -59,25 +57,16 @@ export function createVitePlugins() {
}),
Components({
// 生成自定义 `auto-components.d.ts` 全局声明
dts: 'src/types/auto-components.d.ts',
dts: !isBuild && 'src/types/auto-components.d.ts',
// 自定义组件的解析器
resolvers: [ElementPlusResolver()],
globs: ['src/components/**/**.{vue, md}', '!src/components/DiyEditor/components/mobile/**']
}),
EslintPlugin({
cache: false,
include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件
}),
VueI18nPlugin({
runtimeOnly: true,
compositionOnly: true,
include: [resolve(__dirname, 'src/locales/**')]
}),
createSvgIconsPlugin({
iconDirs: [pathResolve('src/assets/svgs')],
symbolId: 'icon-[dir]-[name]'
}),
viteCompression({
isBuild && compressTypes.includes('gzip') && viteCompression({
verbose: true, // 是否在控制台输出压缩结果
disable: false, // 是否禁用
threshold: 10240, // 体积大于 threshold 才会被压缩,单位 b
@ -85,13 +74,15 @@ export function createVitePlugins() {
ext: '.gz', // 生成的压缩包后缀
deleteOriginFile: false //压缩后是否删除源文件
}),
ViteEjsPlugin(),
topLevelAwait({
// https://juejin.cn/post/7152191742513512485
// The export name of top-level await promise for each chunk module
promiseExportName: '__tla',
// The function to generate import names of top-level await promise in each chunk module
promiseImportName: (i) => `__tla_${i}`
isBuild && compressTypes.includes('brotli') && viteCompression({
verbose: true,
disable: false,
threshold: 10240,
algorithm: 'brotliCompress',
ext: '.br',
deleteOriginFile: false
})
]
return plugins.filter(Boolean)
}

View File

@ -17,7 +17,8 @@ export default tseslint.config(
'test/unit/coverage/',
'node_modules/',
'src/main.ts',
'src/types/auto-components.d.ts'
'src/types/auto-components.d.ts',
'src/components/Tinyflow/ui/**'
]
},
@ -61,6 +62,7 @@ export default tseslint.config(
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/html-indent': 'off', // Vue 模板缩进交给 Prettier避免格式化规则互相拉扯。
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',

12
lint-staged.config.mjs Normal file
View File

@ -0,0 +1,12 @@
export default {
'src/**/*.{js,ts,tsx,vue}': [
'eslint --fix --cache --cache-location node_modules/.cache/eslint/',
'prettier --write --loglevel warn --cache --cache-location node_modules/.cache/prettier/.prettier-cache'
],
'src/**/*.{css,less,scss,postcss,vue}': [
'stylelint --fix --cache --cache-location node_modules/.cache/stylelint/'
],
'src/**/*.{json,html,md}': [
'prettier --write --loglevel warn --cache --cache-location node_modules/.cache/prettier/.prettier-cache'
]
}

View File

@ -8,7 +8,7 @@
"i": "pnpm install",
"dev": "vite --mode env.local",
"dev-server": "vite --mode dev",
"ts:check": "node --max_old_space_size=8192 ./node_modules/vue-tsc/bin/vue-tsc.js --noEmit",
"ts:check": "node --max_old_space_size=8192 ./node_modules/vue-tsc/bin/vue-tsc.js --noEmit --incremental --tsBuildInfoFile node_modules/.cache/vue-tsc/tsconfig.tsbuildinfo",
"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",
@ -19,10 +19,14 @@
"preview": "pnpm build:local && vite preview",
"clean": "npx rimraf node_modules",
"clean:cache": "npx rimraf node_modules/.cache",
"lint:eslint": "eslint --fix ./src",
"lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
"lint": "pnpm lint:eslint:check && pnpm lint:style:check && pnpm lint:format:check",
"lint:eslint": "eslint --fix ./src --cache --cache-location node_modules/.cache/eslint/",
"lint:eslint:check": "eslint ./src --cache --cache-location node_modules/.cache/eslint/",
"lint:format": "prettier --write --log-level warn --cache --cache-location node_modules/.cache/prettier/.prettier-cache \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
"lint:format:check": "prettier --check --cache --cache-location node_modules/.cache/prettier/.prettier-cache \"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 "
"lint:style:check": "stylelint \"./src/**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged"
},
"dependencies": {
"@element-plus/icons-vue": "2.3.2",
@ -44,7 +48,6 @@
"cropperjs": "^2.1.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.20",
"dayjs-plugin-lunar": "^1.4.1",
"dhtmlx-gantt": "^9.1.1",
"diagram-js": "^15.14.0",
"driver.js": "^1.4.0",
@ -56,6 +59,7 @@
"jsbarcode": "^3.12.3",
"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",
@ -90,7 +94,6 @@
"@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": "^25.6.0",
@ -100,7 +103,6 @@
"@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",
@ -123,7 +125,6 @@
"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",
@ -132,11 +133,7 @@
"unplugin-vue-components": "^32.0.0",
"vite": "8.0.10",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-ejs": "^1.7.0",
"vite-plugin-eslint2": "^5.1.0",
"vite-plugin-progress": "^0.0.7",
"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"
},

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@ export interface ChatConversationVO {
temperature: number // 温度参数
maxTokens: number // 单条回复的最大 Token 数量
maxContexts: number // 上下文的最大 Message 数量
createTime?: Date // 创建时间
createTime: Date // 创建时间
// 额外字段
systemMessage?: string // 角色设定
modelName?: string // 模型名字
@ -39,7 +39,7 @@ export const ChatConversationApi = {
},
// 删除【我的】聊天对话
deleteChatConversationMy: async (id: string) => {
deleteChatConversationMy: async (id: number) => {
return await request.delete({ url: `/ai/chat/conversation/delete-my?id=${id}` })
},

View File

@ -23,15 +23,15 @@ export interface ImageDrawReqVO {
prompt: string // 提示词
modelId: number // 模型
style: string // 图像生成的风格
width: string // 图片宽度
height: string // 图片高度
width: number // 图片宽度
height: number // 图片高度
options: object // 绘制参数Map<String, String>
}
export interface ImageMidjourneyImagineReqVO {
prompt: string // 提示词
modelId: number // 模型
base64Array: string[] // size不能为空
base64Array: string[] // 参考图 base64 列表
width: string // 图片宽度
height: string // 图片高度
version: string // 版本

View File

@ -36,6 +36,7 @@ export type ApprovalTaskInfo = {
assigneeUser: User
status: number
reason: string
attachments?: string[]
signPicUrl: string
}

View File

@ -1,6 +1,5 @@
import request from '@/config/axios'
export const updateBpmSimpleModel = async (data) => {
return await request.post({
url: '/bpm/model/simple/update',

View File

@ -1,14 +1,18 @@
import request from '@/config/axios'
export interface BusinessStatusTypeVO {
id: number
id?: number
name: string
deptIds: number[]
statuses?: {
id: number
name: string
percent: number
}
statuses: BusinessStatusVO[]
}
export interface BusinessStatusVO {
id?: number
name: string
percent?: number
endStatus?: number
key?: string
}
export const DEFAULT_STATUSES = [
@ -30,7 +34,7 @@ export const DEFAULT_STATUSES = [
name: '无效',
percent: 0
}
]
] satisfies BusinessStatusVO[]
// 查询商机状态组列表
export const getBusinessStatusPage = async (params: any) => {

View File

@ -11,6 +11,11 @@ export interface FinancePaymentVO {
remark: string // 备注
}
// ERP 付款单项 VO
export interface FinancePaymentItemVO {
paymentPrice: number // 本次付款金额,单位:元
}
// ERP 付款单 API
export const FinancePaymentApi = {
// 查询付款单分页

View File

@ -11,6 +11,11 @@ export interface FinanceReceiptVO {
remark: string // 备注
}
// ERP 收款单项 VO
export interface FinanceReceiptItemVO {
receiptPrice: number // 本次收款金额,单位:元
}
// ERP 收款单 API
export const FinanceReceiptApi = {
// 查询收款单分页

View File

@ -13,8 +13,8 @@ export interface ProductCategoryVO {
// ERP 产品分类 API
export const ProductCategoryApi = {
// 查询产品分类列表
getProductCategoryList: async () => {
return await request.get({ url: `/erp/product-category/list` })
getProductCategoryList: async (params?: any) => {
return await request.get({ url: `/erp/product-category/list`, params })
},
// 查询产品分类精简列表

View File

@ -2,16 +2,45 @@ import request from '@/config/axios'
// ERP 采购订单 VO
export interface PurchaseOrderVO {
id: number // 订单工单编号
no: string // 采购订单号
customerId: number // 客户编号
orderTime: Date // 订单时间
totalCount: number // 合计数量
id?: number // 订单工单编号
no?: string // 采购订单号
supplierId?: number // 供应商编号
accountId?: number // 结算账户编号
orderTime?: Date // 订单时间
totalCount?: number // 合计数量
totalProductPrice?: number // 合计产品金额,单位:元
discountPercent?: number // 优惠率
discountPrice?: number // 优惠金额,单位:元
totalPrice: number // 合计金额,单位:元
status: number // 状态
remark: string // 备注
outCount: number // 采购出库数量
returnCount: number // 采购退货数量
depositPrice?: number // 订金金额,单位:元
status?: number // 状态
remark?: string // 备注
fileUrl: string // 附件地址
inCount?: number // 采购入库数量
returnCount?: number // 采购退货数量
items: PurchaseOrderItemVO[] // 订单项
}
// ERP 采购订单项 VO
export interface PurchaseOrderItemVO {
id?: number
orderItemId?: number
productId?: number
productName?: string
productUnitName?: string
productBarCode?: string
productPrice?: number
stockCount?: number
count: number
totalCount?: number
inCount?: number
returnCount?: number
totalProductPrice?: number
taxPercent?: number
taxPrice?: number
totalPrice: number
remark?: string
warehouseId?: number
}
// ERP 采购订单 API

View File

@ -2,16 +2,46 @@ import request from '@/config/axios'
// ERP 销售订单 VO
export interface SaleOrderVO {
id: number // 订单工单编号
no: string // 销售订单号
customerId: number // 客户编号
orderTime: Date // 订单时间
totalCount: number // 合计数量
id?: number // 订单工单编号
no?: string // 销售订单号
customerId?: number // 客户编号
accountId?: number // 结算账户编号
saleUserId?: number // 销售人员编号
orderTime?: Date // 订单时间
totalCount?: number // 合计数量
totalProductPrice?: number // 合计产品金额,单位:元
discountPercent?: number // 优惠率
discountPrice?: number // 优惠金额,单位:元
totalPrice: number // 合计金额,单位:元
status: number // 状态
remark: string // 备注
outCount: number // 销售出库数量
returnCount: number // 销售退货数量
depositPrice?: number // 订金金额,单位:元
status?: number // 状态
remark?: string // 备注
fileUrl: string // 附件地址
outCount?: number // 销售出库数量
returnCount?: number // 销售退货数量
items: SaleOrderItemVO[] // 订单项
}
// ERP 销售订单项 VO
export interface SaleOrderItemVO {
id?: number
orderItemId?: number
productId?: number
productName?: string
productUnitName?: string
productBarCode?: string
productPrice?: number
stockCount?: number
count: number
totalCount?: number
outCount?: number
returnCount?: number
totalProductPrice?: number
taxPercent?: number
taxPrice?: number
totalPrice: number
remark?: string
warehouseId?: number
}
// ERP 销售订单 API

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,19 @@
import request from '@/config/axios'
// IM 会话读位置 Response VO
export interface ImConversationReadRespVO {
id: number // 读位置编号(增量拉取游标用)
conversationType: number // 会话类型,参见 ImConversationType
targetId: number // 会话目标编号
messageId: number // 最大已读消息编号
updateTime?: number // 最近更新时间(毫秒时间戳,增量拉取游标用)
}
// 增量拉取当前用户的会话读位置(重连 / 离线补偿)
export const pullMyConversationReadList = (params: {
lastUpdateTime?: number
lastId?: number
limit: number
}) => {
return request.get<ImConversationReadRespVO[]>({ url: '/im/conversation-read/pull', params })
}

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,71 @@
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 // 删除好友时间
updateTime?: number // 最近更新时间(毫秒时间戳,增量拉取游标用)
// 聚合字段(自 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 pullMyFriendList = (params: {
lastUpdateTime?: number
lastId?: number
limit: number
}) => {
return request.get<ImFriendRespVO[]>({ url: '/im/friend/pull', params })
}
// 获得好友详情
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,75 @@
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 // 申请创建时间
updateTime?: number // 最近更新时间(毫秒时间戳,增量拉取游标用)
// 聚合字段(自 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
})
}
// 增量拉取「我相关」的好友申请变更(重连 / 离线补偿)
export const pullMyFriendRequestList = (params: {
lastUpdateTime?: number
lastId?: number
limit: number
}) => {
return request.get<ImFriendRequestRespVO[]>({ url: '/im/friend-request/pull', params })
}
// 按 id 单查「我相关」的申请记录带越权过滤WebSocket 通知到达后用)
export const getMyFriendRequest = (id: number) => {
return request.get<ImFriendRequestRespVO | null>({
url: '/im/friend-request/get',
params: { id }
})
}

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

@ -0,0 +1,140 @@
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[] // 群置顶消息列表(后端关联回填,仅当登录用户是群成员时非空)
joinStatus?: number // 当前登录用户在该群的成员状态(参见 CommonStatusEnum0 在群 / 1 已退群);历史退群群仍返回,供展示离线消息的群名 / 头像
groupRemark?: string // 当前登录用户对该群的备注
silent?: boolean // 当前登录用户是否免打扰
}
// 群消息置顶 / 取消置顶 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,81 @@
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 // 申请创建时间
updateTime?: number // 最近更新时间(毫秒时间戳,增量拉取游标用)
// 聚合字段
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 }
})
}
// 增量拉取我管理的所有群下加群申请变更(重连 / 离线补偿)
export const pullMyGroupRequestList = (params: {
lastUpdateTime?: number
lastId?: number
limit: number
}) => {
return request.get<ImGroupRequestRespVO[]>({ url: '/im/group-request/pull', params })
}

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,26 @@
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
receiptStatus: 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,76 @@
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 // 参见 ImContentTypeEnum 枚举类,由前端按 DICT_TYPE.IM_CONTENT_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,32 @@
import request from '@/config/axios'
export interface ImChannelMessageRespVO {
id: number
clientMessageId?: string
channelId: number
materialId: number
type: number
content: string
receiptStatus?: number
sendTime: string
}
// 拉取当前用户应收的频道消息(离线增量);按 minId 游标分页
export const pullChannelMessageList = (
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 pullGroupMessageList = (
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,73 @@
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 // 消息状态(正常 / 已撤回)
receiptStatus?: number // 回执状态(不需要 / 待完成 / 已完成),对齐 ImMessageReceiptStatus
sendTime: string // 发送时间
}
// 私聊消息发送 Request VO
export interface ImPrivateMessageSendReqVO {
clientMessageId: string // 客户端消息编号
receiverId: number // 接收人编号
type: number // 内容类型
content: string // 消息内容JSON 格式)
receipt?: boolean // 是否需要回执;不传后端默认 true普通私聊用户消息
}
// 私聊历史消息列表 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 pullPrivateMessageList = (
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

@ -20,6 +20,45 @@ export type CodegenTableVO = {
parentMenuId: number
}
export type CodegenTableSaveReqVO = CodegenTableVO & {
frontType?: number | null
genPath?: string
genType?: string
masterTableId?: number
subJoinColumnId?: number
subJoinMany?: boolean
treeParentColumnId?: number
treeNameColumnId?: number
}
export const createEmptyCodegenTableSaveReqVO = (): CodegenTableSaveReqVO => ({
id: 0,
tableId: 0,
isParentMenuIdValid: false,
dataSourceConfigId: 0,
scene: 0,
tableName: '',
tableComment: '',
remark: '',
moduleName: '',
businessName: '',
className: '',
classComment: '',
author: '',
createTime: new Date(),
updateTime: new Date(),
templateType: 0,
parentMenuId: 0,
frontType: null,
genPath: '',
genType: '',
masterTableId: undefined,
subJoinColumnId: undefined,
subJoinMany: undefined,
treeParentColumnId: undefined,
treeNameColumnId: undefined
})
export type CodegenColumnVO = {
id: number
tableId: number
@ -52,7 +91,7 @@ export type CodegenPreviewVO = {
}
export type CodegenUpdateReqVO = {
table: CodegenTableVO | any
table: CodegenTableSaveReqVO
columns: CodegenColumnVO[]
}
@ -68,7 +107,7 @@ export const getCodegenTablePage = (params: PageParam) => {
// 查询详情代码生成表定义
export const getCodegenTable = (id: number) => {
return request.get({ url: '/infra/codegen/detail?tableId=' + id })
return request.get<CodegenUpdateReqVO>({ url: '/infra/codegen/detail?tableId=' + id })
}
// 修改代码生成表定义

View File

@ -26,7 +26,10 @@ export const deleteDataSourceConfig = (id: number) => {
// 批量删除数据源配置
export const deleteDataSourceConfigList = (ids: number[]) => {
return request.delete({ url: '/infra/data-source-config/delete-list', params: { ids: ids.join(',') } })
return request.delete({
url: '/infra/data-source-config/delete-list',
params: { ids: ids.join(',') }
})
}
// 查询数据源配置详情

View File

@ -7,8 +7,8 @@ export interface Demo02CategoryVO {
}
// 查询示例分类列表
export const getDemo02CategoryList = async () => {
return await request.get({ url: `/infra/demo02-category/list` })
export const getDemo02CategoryList = async (params?: any) => {
return await request.get({ url: `/infra/demo02-category/list`, params })
}
// 查询示例分类详情

View File

@ -1,29 +1,28 @@
import request from '@/config/axios'
import type { Dayjs } from 'dayjs';
/** 学生课程信息 */
export interface Demo03Course {
id: number; // 编号
studentId?: number; // 学生编号
name?: string; // 名字
score?: number; // 分数
id?: number // 编号
studentId?: number // 学生编号
name?: string // 名字
score?: number // 分数
}
/** 学生班级信息 */
export interface Demo03Grade {
id: number; // 编号
studentId?: number; // 学生编号
name?: string; // 名字
teacher?: string; // 班主任
id?: number // 编号
studentId?: number // 学生编号
name?: string // 名字
teacher?: string // 班主任
}
/** 学生信息 */
export interface Demo03Student {
id: number; // 编号
name?: string; // 名字
sex?: number; // 性别
birthday?: string | Dayjs; // 出生日期
description?: string; // 简介
id?: number // 编号
name?: string // 名字
sex?: number // 性别
birthday?: string | number // 出生日期
description?: string // 简介
}
// 学生 API
@ -55,7 +54,9 @@ export const Demo03StudentApi = {
/** 批量删除学生 */
deleteDemo03StudentList: async (ids: number[]) => {
return await request.delete({ url: `/infra/demo03-student-erp/delete-list?ids=${ids.join(',')}` })
return await request.delete({
url: `/infra/demo03-student-erp/delete-list?ids=${ids.join(',')}`
})
},
// 导出学生 Excel
@ -63,7 +64,7 @@ export const Demo03StudentApi = {
return await request.download({ url: `/infra/demo03-student-erp/export-excel`, params })
},
// ==================== 子表(学生课程) ====================
// ==================== 子表(学生课程) ====================
// 获得学生课程分页
getDemo03CoursePage: async (params) => {
@ -86,7 +87,9 @@ export const Demo03StudentApi = {
/** 批量删除学生课程 */
deleteDemo03CourseList: async (ids: number[]) => {
return await request.delete({ url: `/infra/demo03-student-erp/demo03-course/delete-list?ids=${ids.join(',')}` })
return await request.delete({
url: `/infra/demo03-student-erp/demo03-course/delete-list?ids=${ids.join(',')}`
})
},
// 获得学生课程
@ -94,7 +97,7 @@ export const Demo03StudentApi = {
return await request.get({ url: `/infra/demo03-student-erp/demo03-course/get?id=` + id })
},
// ==================== 子表(学生班级) ====================
// ==================== 子表(学生班级) ====================
// 获得学生班级分页
getDemo03GradePage: async (params) => {
@ -117,11 +120,13 @@ export const Demo03StudentApi = {
/** 批量删除学生班级 */
deleteDemo03GradeList: async (ids: number[]) => {
return await request.delete({ url: `/infra/demo03-student-erp/demo03-grade/delete-list?ids=${ids.join(',')}` })
return await request.delete({
url: `/infra/demo03-student-erp/demo03-grade/delete-list?ids=${ids.join(',')}`
})
},
// 获得学生班级
getDemo03Grade: async (id: number) => {
return await request.get({ url: `/infra/demo03-student-erp/demo03-grade/get?id=` + id })
},
}
}

View File

@ -1,29 +1,28 @@
import request from '@/config/axios'
import type { Dayjs } from 'dayjs';
/** 学生课程信息 */
export interface Demo03Course {
id: number; // 编号
studentId?: number; // 学生编号
name?: string; // 名字
score?: number; // 分数
id?: number // 编号
studentId?: number // 学生编号
name?: string // 名字
score?: number // 分数
}
/** 学生班级信息 */
export interface Demo03Grade {
id: number; // 编号
studentId?: number; // 学生编号
name?: string; // 名字
teacher?: string; // 班主任
id?: number // 编号
studentId?: number // 学生编号
name?: string // 名字
teacher?: string // 班主任
}
/** 学生信息 */
export interface Demo03Student {
id: number; // 编号
name?: string; // 名字
sex?: number; // 性别
birthday?: string | Dayjs; // 出生日期
description?: string; // 简介
id?: number // 编号
name?: string // 名字
sex?: number // 性别
birthday?: string | number // 出生日期
description?: string // 简介
demo03courses?: Demo03Course[]
demo03grade?: Demo03Grade
}
@ -57,7 +56,9 @@ export const Demo03StudentApi = {
/** 批量删除学生 */
deleteDemo03StudentList: async (ids: number[]) => {
return await request.delete({ url: `/infra/demo03-student-inner/delete-list?ids=${ids.join(',')}` })
return await request.delete({
url: `/infra/demo03-student-inner/delete-list?ids=${ids.join(',')}`
})
},
// 导出学生 Excel
@ -65,17 +66,21 @@ export const Demo03StudentApi = {
return await request.download({ url: `/infra/demo03-student-inner/export-excel`, params })
},
// ==================== 子表(学生课程) ====================
// ==================== 子表(学生课程) ====================
// 获得学生课程列表
getDemo03CourseListByStudentId: async (studentId) => {
return await request.get({ url: `/infra/demo03-student-inner/demo03-course/list-by-student-id?studentId=` + studentId })
return await request.get({
url: `/infra/demo03-student-inner/demo03-course/list-by-student-id?studentId=` + studentId
})
},
// ==================== 子表(学生班级) ====================
// ==================== 子表(学生班级) ====================
// 获得学生班级
getDemo03GradeByStudentId: async (studentId) => {
return await request.get({ url: `/infra/demo03-student-inner/demo03-grade/get-by-student-id?studentId=` + studentId })
},
return await request.get({
url: `/infra/demo03-student-inner/demo03-grade/get-by-student-id?studentId=` + studentId
})
}
}

View File

@ -1,29 +1,28 @@
import request from '@/config/axios'
import type { Dayjs } from 'dayjs';
/** 学生课程信息 */
export interface Demo03Course {
id: number; // 编号
studentId?: number; // 学生编号
name?: string; // 名字
score?: number; // 分数
id?: number // 编号
studentId?: number // 学生编号
name?: string // 名字
score?: number // 分数
}
/** 学生班级信息 */
export interface Demo03Grade {
id: number; // 编号
studentId?: number; // 学生编号
name?: string; // 名字
teacher?: string; // 班主任
id?: number // 编号
studentId?: number // 学生编号
name?: string // 名字
teacher?: string // 班主任
}
/** 学生信息 */
export interface Demo03Student {
id: number; // 编号
name?: string; // 名字
sex?: number; // 性别
birthday?: string | Dayjs; // 出生日期
description?: string; // 简介
id?: number // 编号
name?: string // 名字
sex?: number // 性别
birthday?: string | number // 出生日期
description?: string // 简介
demo03courses?: Demo03Course[]
demo03grade?: Demo03Grade
}
@ -57,7 +56,9 @@ export const Demo03StudentApi = {
/** 批量删除学生 */
deleteDemo03StudentList: async (ids: number[]) => {
return await request.delete({ url: `/infra/demo03-student-normal/delete-list?ids=${ids.join(',')}` })
return await request.delete({
url: `/infra/demo03-student-normal/delete-list?ids=${ids.join(',')}`
})
},
// 导出学生 Excel
@ -65,17 +66,21 @@ export const Demo03StudentApi = {
return await request.download({ url: `/infra/demo03-student-normal/export-excel`, params })
},
// ==================== 子表(学生课程) ====================
// ==================== 子表(学生课程) ====================
// 获得学生课程列表
getDemo03CourseListByStudentId: async (studentId) => {
return await request.get({ url: `/infra/demo03-student-normal/demo03-course/list-by-student-id?studentId=` + studentId })
return await request.get({
url: `/infra/demo03-student-normal/demo03-course/list-by-student-id?studentId=` + studentId
})
},
// ==================== 子表(学生班级) ====================
// ==================== 子表(学生班级) ====================
// 获得学生班级
getDemo03GradeByStudentId: async (studentId) => {
return await request.get({ url: `/infra/demo03-student-normal/demo03-grade/get-by-student-id?studentId=` + studentId })
},
return await request.get({
url: `/infra/demo03-student-normal/demo03-grade/get-by-student-id?studentId=` + studentId
})
}
}

View File

@ -66,3 +66,8 @@ export const runJob = (id: number) => {
export const getJobNextTimes = (id: number) => {
return request.get({ url: '/infra/job/get_next_times?id=' + id })
}
// 同步定时任务到 Quartz
export const syncJob = () => {
return request.post({ url: '/infra/job/sync' })
}

View File

@ -6,8 +6,8 @@ export interface OtaTask {
name: string // 任务名称
description?: string // 任务描述
firmwareId?: number // 固件编号
status: number // 任务状态
deviceScope?: number // 升级范围
status?: number // 任务状态
deviceScope: number // 升级范围
deviceIds?: number[] // 指定设备ID列表当升级范围为指定设备时使用
deviceTotalCount?: number // 设备总共数量
deviceSuccessCount?: number // 设备成功数量

View File

@ -5,6 +5,7 @@ export interface IotSceneRule {
id?: number // 场景编号
name: string // 场景名称
description?: string // 场景描述
lastTriggerTime?: string // 最近触发时间
status: number // 场景状态0-开启1-关闭
triggers: Trigger[] // 触发器数组
actions: Action[] // 执行器数组

View File

@ -18,47 +18,35 @@ export interface ThingModelData {
service?: ThingModelService // 服务
}
/**
* ThingModelProperty
*/
export interface ThingModelProperty {
[key: string]: any
}
/**
* ThingModelEvent
*/
export interface ThingModelEvent {
[key: string]: any
}
/**
* ThingModelService
*/
export interface ThingModelService {
[key: string]: any
}
/** dataSpecs 数值型数据结构 */
export interface DataSpecsNumberData {
dataType: 'int' | 'float' | 'double' // 数据类型,取值为 INT、FLOAT 或 DOUBLE
max: string // 最大值,必须与 dataType 设置一致,且为 STRING 类型
min: string // 最小值,必须与 dataType 设置一致,且为 STRING 类型
step: string // 步长,必须与 dataType 设置一致,且为 STRING 类型
dataType: string // 数据类型,取值为 INT、FLOAT 或 DOUBLE
max?: string // 最大值,必须与 dataType 设置一致,且为 STRING 类型
min?: string // 最小值,必须与 dataType 设置一致,且为 STRING 类型
step?: string // 步长,必须与 dataType 设置一致,且为 STRING 类型
precise?: string // 精度,当 dataType 为 FLOAT 或 DOUBLE 时可选
defaultValue?: string // 默认值,可选
unit: string // 单位的符号
unitName: string // 单位的名称
unit?: string // 单位的符号
unitName?: string // 单位的名称
}
/** dataSpecs 枚举型数据结构 */
export interface DataSpecsEnumOrBoolData {
dataType: 'enum' | 'bool'
dataType: string
defaultValue?: string // 默认值,可选
name: string // 枚举项的名称
value: number | undefined // 枚举值
}
/** dataSpecs 通用数据结构 */
export type ThingModelDataSpecs =
| DataSpecsNumberData
| DataSpecsEnumOrBoolData
| ThingModelDateOrTextDataSpecs
| ThingModelArrayDataSpecs
| ThingModelStructDataSpecs
| Record<string, any>
/** 物模型TSL响应数据结构 */
export interface IotThingModelTSLResp {
productId: number
@ -76,8 +64,9 @@ export interface ThingModelProperty {
required?: boolean
dataType: string
description?: string
dataSpecs?: ThingModelProperty
dataSpecsList?: ThingModelProperty[]
dataSpecs?: ThingModelDataSpecs
dataSpecsList?: ThingModelPropertyDataSpecs[]
value?: number
}
/** 物模型事件 */
@ -110,8 +99,8 @@ export interface ThingModelParam {
direction: string
paraOrder?: number
dataType: string
dataSpecs?: ThingModelProperty
dataSpecsList?: ThingModelProperty[]
dataSpecs?: ThingModelDataSpecs
dataSpecsList?: ThingModelPropertyDataSpecs[]
}
/** 数值型数据规范 */
@ -142,24 +131,26 @@ export interface ThingModelDateOrTextDataSpecs {
/** 数组型数据规范 */
export interface ThingModelArrayDataSpecs {
dataType: 'array'
size: number
childDataType: string
dataSpecsList?: ThingModelProperty[]
dataType: string
size?: number
childDataType?: string
dataSpecsList?: ThingModelPropertyDataSpecs[]
}
/** 结构体型数据规范 */
export interface ThingModelStructDataSpecs {
dataType: 'struct'
identifier: string
name: string
accessMode: string
dataType: string
identifier?: string
name?: string
accessMode?: string
required?: boolean
childDataType: string
dataSpecs?: ThingModelProperty
dataSpecsList?: ThingModelProperty[]
childDataType?: string
dataSpecs?: ThingModelDataSpecs
dataSpecsList?: ThingModelPropertyDataSpecs[]
}
export type ThingModelPropertyDataSpecs = ThingModelProperty | DataSpecsEnumOrBoolData
// IoT 产品物模型 API
export const ThingModelApi = {
// 查询产品物模型分页

View File

@ -2,11 +2,17 @@ import request from '@/config/axios'
export interface Favorite {
id?: number
userId?: string // 用户编号
userId?: number // 用户编号
spuId?: number | null // 商品 SPU 编号
name?: string
picUrl?: string
price?: number
salesCount?: number
createTime?: Date
status?: number
}
// 获得 ProductFavorite 列表
export const getFavoritePage = (params: PageParam) => {
return request.get({ url: '/product/favorite/page', params })
return request.get<PageResult<Favorite[]>>({ url: '/product/favorite/page', params })
}

View File

@ -65,9 +65,9 @@ export const getSpuPage = (params: PageParam) => {
return request.get({ url: '/product/spu/page', params })
}
// 获得 Spu 列表 tabsCount
export const getTabsCount = () => {
return request.get({ url: '/product/spu/get-count' })
// 获得 Spu 列表 tabsCount(支持按 name/categoryId/createTime 筛选)
export const getTabsCount = (params?: any) => {
return request.get({ url: '/product/spu/get-count', params })
}
// 创建商品 Spu

View File

@ -5,6 +5,8 @@ export interface CombinationActivityVO {
id?: number
name?: string
spuId?: number
spuName?: string
picUrl?: string
totalLimitCount?: number
singleLimitCount?: number
startTime?: Date

View File

@ -10,10 +10,10 @@ export interface RewardActivityVO {
conditionType?: number
productScope?: number
rules: RewardRule[]
productScopeValues: number[] // 商品范围:值为品类编号列表、商品编号列表
// 如下仅用于表单,不提交
productScopeValues?: number[] // 商品范围:值为品类编号列表、商品编号列表
productCategoryIds?: number[]
productSpuIds?: number[]
productCategoryIds: number[]
productSpuIds: number[]
}
// 优惠规则

View File

@ -5,6 +5,7 @@ export interface SeckillActivityVO {
id?: number
spuId?: number
name?: string
picUrl?: string
status?: number
remark?: string
startTime?: Date

View File

@ -115,5 +115,7 @@ export const getOrderCountTrendComparison = (
/** 时间参数需要格式化, 确保接口能识别 */
const formatDateParam = (params: TradeTrendReqVO) => {
return { times: [formatDate(params.times[0]), formatDate(params.times[1])] } as TradeTrendReqVO
return {
times: [formatDate(params.times[0]), formatDate(params.times[1])]
} as TradeTrendReqVO
}

View File

@ -1,4 +1,5 @@
import request from '@/config/axios'
import type { OrderItemRespVO, OrderVO } from '@/api/mall/trade/order'
export interface TradeAfterSaleVO {
id?: number | null // 售后编号,主键自增
@ -32,6 +33,21 @@ export interface TradeAfterSaleVO {
receiveReason?: string // 收货备注
}
export interface TradeAfterSaleDetailVO extends TradeAfterSaleVO {
order: OrderVO
orderItem?: OrderItemRespVO
user?: {
id?: number
nickname?: string
}
logs: Array<{
id?: number
createTime?: Date
content?: string
userType?: number
}>
}
export interface ProductPropertiesVO {
propertyId?: number | null // 属性的编号
propertyName?: string // 属性的名称
@ -41,12 +57,15 @@ export interface ProductPropertiesVO {
// 获得交易售后分页
export const getAfterSalePage = async (params) => {
return await request.get({ url: `/trade/after-sale/page`, params })
return await request.get<PageResult<TradeAfterSaleVO[]>>({
url: `/trade/after-sale/page`,
params
})
}
// 获得交易售后详情
export const getAfterSale = async (id: any) => {
return await request.get({ url: `/trade/after-sale/get-detail?id=${id}` })
return await request.get<TradeAfterSaleDetailVO>({ url: `/trade/after-sale/get-detail?id=${id}` })
}
// 同意售后

View File

@ -20,12 +20,15 @@ export const createBrokerageUser = (data: any) => {
// 查询分销用户列表
export const getBrokerageUserPage = async (params: any) => {
return await request.get({ url: `/trade/brokerage-user/page`, params })
return await request.get<PageResult<BrokerageUserVO[]>>({
url: `/trade/brokerage-user/page`,
params
})
}
// 查询分销用户详情
export const getBrokerageUser = async (id: number) => {
return await request.get({ url: `/trade/brokerage-user/get?id=` + id })
return await request.get<BrokerageUserVO>({ url: `/trade/brokerage-user/get?id=` + id })
}
// 修改推广员

View File

@ -1,20 +1,28 @@
import request from '@/config/axios'
export interface ConfigVO {
id?: number | null
afterSaleRefundReasons: string[]
afterSaleReturnReasons: string[]
deliveryExpressFreeEnabled: boolean
deliveryExpressFreePrice: number
deliveryPickUpEnabled: boolean
brokerageEnabled: boolean
brokerageEnabledCondition: number
brokerageBindMode: number
brokeragePosterUrls: string
brokerageEnabledCondition?: number
brokerageBindMode?: number
brokeragePosterUrls: string[]
brokerageFirstPercent: number
brokerageSecondPercent: number
brokerageWithdrawMinPrice: number
brokerageWithdrawFeePercent: number
brokerageFrozenDays: number
brokerageWithdrawTypes: string
brokerageWithdrawTypes: number[]
tencentLbsKey?: string
}
// 查询交易中心配置详情
export const getTradeConfig = async () => {
return await request.get({ url: `/trade/config/get` })
return await request.get<ConfigVO>({ url: `/trade/config/get` })
}
// 保存交易中心配置

View File

@ -1,7 +1,7 @@
import request from '@/config/axios'
export interface DeliveryExpressVO {
id: number
id?: number
code: string
name: string
logo: string
@ -11,17 +11,20 @@ export interface DeliveryExpressVO {
// 查询快递公司列表
export const getDeliveryExpressPage = async (params: PageParam) => {
return await request.get({ url: '/trade/delivery/express/page', params })
return await request.get<PageResult<DeliveryExpressVO[]>>({
url: '/trade/delivery/express/page',
params
})
}
// 查询快递公司详情
export const getDeliveryExpress = async (id: number) => {
return await request.get({ url: '/trade/delivery/express/get?id=' + id })
return await request.get<DeliveryExpressVO>({ url: '/trade/delivery/express/get?id=' + id })
}
// 获得快递公司精简信息列表
export const getSimpleDeliveryExpressList = () => {
return request.get({ url: '/trade/delivery/express/list-all-simple' })
return request.get<DeliveryExpressVO[]>({ url: '/trade/delivery/express/list-all-simple' })
}
// 新增快递公司

View File

@ -1,12 +1,12 @@
import request from '@/config/axios'
export interface DeliveryExpressTemplateVO {
id: number
id?: number
name: string
chargeMode: number
sort: number
templateCharge: ExpressTemplateChargeVO[]
templateFree: ExpressTemplateFreeVO[]
charges: ExpressTemplateChargeVO[]
frees: ExpressTemplateFreeVO[]
}
export declare type ExpressTemplateChargeVO = {
@ -25,17 +25,24 @@ export declare type ExpressTemplateFreeVO = {
// 查询快递运费模板列表
export const getDeliveryExpressTemplatePage = async (params: PageParam) => {
return await request.get({ url: '/trade/delivery/express-template/page', params })
return await request.get<PageResult<DeliveryExpressTemplateVO[]>>({
url: '/trade/delivery/express-template/page',
params
})
}
// 查询快递运费模板详情
export const getDeliveryExpressTemplate = async (id: number) => {
return await request.get({ url: '/trade/delivery/express-template/get?id=' + id })
return await request.get<DeliveryExpressTemplateVO>({
url: '/trade/delivery/express-template/get?id=' + id
})
}
// 查询快递运费模板详情
export const getSimpleTemplateList = async () => {
return await request.get({ url: '/trade/delivery/express-template/list-all-simple' })
return await request.get<DeliveryExpressTemplateVO[]>({
url: '/trade/delivery/express-template/list-all-simple'
})
}
// 新增快递运费模板

View File

@ -1,34 +1,48 @@
import request from '@/config/axios'
import type { UserVO } from '@/api/system/user'
export interface DeliveryPickUpStoreVO {
id: number
id?: number
name: string
introduction: string
phone: string
areaId: number
areaId?: number
detailAddress: string
logo: string
openingTime: string
closingTime: string
latitude: number
longitude: number
openingTime?: string
closingTime?: string
latitude?: number
longitude?: number
status: number
verifyUserIds: number[] // 绑定用户编号组数
verifyUserIds?: number[] // 绑定用户编号组数
verifyUsers?: UserVO[] // 绑定用户列表
}
export interface DeliveryPickUpStoreBindReqVO {
id: number
verifyUserIds: number[]
}
// 查询自提门店列表
export const getDeliveryPickUpStorePage = async (params: any) => {
return await request.get({ url: '/trade/delivery/pick-up-store/page', params })
return await request.get<PageResult<DeliveryPickUpStoreVO[]>>({
url: '/trade/delivery/pick-up-store/page',
params
})
}
// 查询自提门店详情
export const getDeliveryPickUpStore = async (id: number) => {
return await request.get({ url: '/trade/delivery/pick-up-store/get?id=' + id })
return await request.get<DeliveryPickUpStoreVO>({
url: '/trade/delivery/pick-up-store/get?id=' + id
})
}
// 查询自提门店精简列表
export const getSimpleDeliveryPickUpStoreList = async (): Promise<DeliveryPickUpStoreVO[]> => {
return await request.get({ url: '/trade/delivery/pick-up-store/simple-list' })
export const getSimpleDeliveryPickUpStoreList = async () => {
return await request.get<DeliveryPickUpStoreVO[]>({
url: '/trade/delivery/pick-up-store/simple-list'
})
}
// 新增自提门店
@ -47,6 +61,6 @@ export const deleteDeliveryPickUpStore = async (id: number) => {
}
// 绑定自提店员
export const bindStoreStaffId = async (data: any) => {
export const bindStoreStaffId = async (data: DeliveryPickUpStoreBindReqVO) => {
return await request.post({ url: '/trade/delivery/pick-up-store/bind', data })
}

View File

@ -122,7 +122,7 @@ export interface TradeOrderSummaryRespVO {
// 查询交易订单列表
export const getOrderPage = async (params: any) => {
return await request.get({ url: `/trade/order/page`, params })
return await request.get<PageResult<OrderVO[]>>({ url: `/trade/order/page`, params })
}
// 查询交易订单统计
@ -132,12 +132,14 @@ export const getOrderSummary = async (params: any) => {
// 查询交易订单详情
export const getOrder = async (id: number | null) => {
return await request.get({ url: `/trade/order/get-detail?id=` + id })
return await request.get<OrderVO>({ url: `/trade/order/get-detail?id=` + id })
}
// 查询交易订单物流详情
export const getExpressTrackList = async (id: number | null) => {
return await request.get({ url: `/trade/order/get-express-track-list?id=` + id })
return await request.get<Array<{ time?: Date | string; content?: string }>>({
url: `/trade/order/get-express-track-list?id=` + id
})
}
export interface DeliveryVO {

View File

@ -1,8 +1,8 @@
import request from '@/config/axios'
export interface ConfigVO {
id: number
pointTradeDeductEnable: number
id?: number
pointTradeDeductEnable: boolean
pointTradeDeductUnitPrice: number
pointTradeDeductMaxPrice: number
pointTradeGivePoint: number
@ -10,7 +10,7 @@ export interface ConfigVO {
// 查询积分设置详情
export const getConfig = async () => {
return await request.get({ url: `/member/config/get` })
return await request.get<ConfigVO>({ url: `/member/config/get` })
}
// 新增修改积分设置

View File

@ -13,10 +13,13 @@ export interface ExperienceRecordVO {
// 查询会员经验记录列表
export const getExperienceRecordPage = async (params) => {
return await request.get({ url: `/member/experience-record/page`, params })
return await request.get<PageResult<ExperienceRecordVO[]>>({
url: `/member/experience-record/page`,
params
})
}
// 查询会员经验记录详情
export const getExperienceRecord = async (id: number) => {
return await request.get({ url: `/member/experience-record/get?id=` + id })
return await request.get<ExperienceRecordVO>({ url: `/member/experience-record/get?id=` + id })
}

View File

@ -1,36 +1,51 @@
import request from '@/config/axios'
export interface UserVO {
id?: number
avatar?: string
birthday?: number
createTime?: number
loginDate?: number
loginIp?: string
mark?: string
mobile?: string
email?: string
password?: string
name?: string
nickname?: string
registerIp?: string
sex?: number
status?: number
areaId?: number
areaName?: string
tagIds?: number[]
groupId?: number
levelId?: number
levelName?: string | null
point?: number | null
totalPoint?: number | null
experience?: number | null
}
export interface UserLevelUpdateReqVO {
id: number
avatar: string | undefined
birthday: number | undefined
createTime: number | undefined
loginDate: number | undefined
loginIp: string
mark: string
mobile: string
email: string | undefined
name: string | undefined
nickname: string | undefined
registerIp: string
sex: number
status: number
areaId: number | undefined
areaName: string | undefined
levelName: string | null
point: number | undefined | null
totalPoint: number | undefined | null
experience: number | null | undefined
levelId: number
reason: string
}
export interface UserPointUpdateReqVO {
id: number
point: number
}
// 查询会员用户列表
export const getUserPage = async (params) => {
return await request.get({ url: `/member/user/page`, params })
return await request.get<PageResult<UserVO[]>>({ url: `/member/user/page`, params })
}
// 查询会员用户详情
export const getUser = async (id: number) => {
return await request.get({ url: `/member/user/get?id=` + id })
return await request.get<UserVO>({ url: `/member/user/get?id=` + id })
}
// 修改会员用户
@ -39,11 +54,11 @@ export const updateUser = async (data: UserVO) => {
}
// 修改会员用户等级
export const updateUserLevel = async (data: any) => {
export const updateUserLevel = async (data: UserLevelUpdateReqVO) => {
return await request.put({ url: `/member/user/update-level`, data })
}
// 修改会员用户积分
export const updateUserPoint = async (data: any) => {
export const updateUserPoint = async (data: UserPointUpdateReqVO) => {
return await request.put({ url: `/member/user/update-point`, data })
}

View File

@ -2,14 +2,14 @@ import request from '@/config/axios'
// MES 物料产品分类 VO
export interface MdItemTypeVO {
id: number // 分类编号
parentId: number // 父分类编号
code: string // 分类编码
name: string // 分类名称
id?: number // 分类编号
parentId?: number // 父分类编号
code?: string // 分类编码
name?: string // 分类名称
itemOrProduct: string // 物料/产品标识
sort: number // 显示排序
status: number // 状态
remark: string // 备注
remark?: string // 备注
}
// MES 物料产品分类 API

View File

@ -2,14 +2,14 @@ import request from '@/config/axios'
// MES 计量单位 VO
export interface MdUnitMeasureVO {
id: number // 单位编号
code: string // 单位编码
name: string // 单位名称
id?: number // 单位编号
code?: string // 单位编码
name?: string // 单位名称
primaryFlag: boolean // 是否主单位
primaryId: number // 主单位编号
changeRate: number // 与主单位换算比例
primaryId?: number // 主单位编号
changeRate?: number // 与主单位换算比例
status: number // 状态
remark: string // 备注
remark?: string // 备注
}
// MES 计量单位 API

View File

@ -13,6 +13,20 @@ export interface WmSnGroupVO {
createTime?: Date
}
export interface WmSnVO {
id?: number
uuid?: string
code?: string
itemId?: number
itemCode?: string
itemName?: string
specification?: string
unitName?: string
batchCode?: string
workOrderId?: number
createTime?: Date
}
export interface WmSnGenerateVO {
itemId?: number
batchCode?: string
@ -30,6 +44,11 @@ export const getSnGroupPage = async (params: any) => {
return await request.get({ url: `/mes/wm/sn/group-page`, params })
}
// 获得批次 SN 码明细列表
export const getSnListByUuid = async (uuid: string) => {
return await request.get({ url: `/mes/wm/sn/list-by-uuid`, params: { uuid } })
}
// 批量删除 SN 码(按批次 UUID
export const deleteSnBatch = async (uuid: string) => {
return await request.delete({ url: `/mes/wm/sn/delete-batch`, params: { uuid } })

View File

@ -7,6 +7,11 @@ export interface TagVO {
createTime: Date
}
export interface SimpleTagVO {
tagId: number
name: string
}
// 创建公众号标签
export const createTag = (data: TagVO) => {
return request.post({
@ -47,7 +52,7 @@ export const getTagPage = (query: PageParam) => {
// 获取公众号标签精简信息列表
export const getSimpleTagList = () => {
return request.get({
return request.get<SimpleTagVO[]>({
url: '/mp/tag/list-all-simple'
})
}

View File

@ -62,7 +62,7 @@ export const deleteApp = (id: number) => {
// 获得支付应用列表
export const getAppList = () => {
return request.get({
return request.get<AppVO[]>({
url: '/pay/app/list'
})
}

View File

@ -1,15 +1,43 @@
import request from '@/config/axios'
export interface NotifyLogVO {
id?: number
status?: number
notifyTimes?: number
lastExecuteTime?: Date
createTime?: Date
response?: string
}
export interface NotifyTaskVO {
id?: number
appId?: number
appName?: string
merchantOrderId?: string
merchantRefundId?: string
merchantTransferId?: string
dataId?: number
type?: number
status?: number
notifyTimes?: number
maxNotifyTimes?: number
lastExecuteTime?: Date
nextNotifyTime?: Date
createTime?: Date
updateTime?: Date
logs?: NotifyLogVO[]
}
// 获得支付通知明细
export const getNotifyTaskDetail = (id) => {
return request.get({
export const getNotifyTaskDetail = (id: number) => {
return request.get<NotifyTaskVO>({
url: '/pay/notify/get-detail?id=' + id
})
}
// 获得支付通知分页
export const getNotifyTaskPage = (query) => {
return request.get({
export const getNotifyTaskPage = (query: any) => {
return request.get<PageResult<NotifyTaskVO[]>>({
url: '/pay/notify/page',
params: query
})

View File

@ -12,6 +12,7 @@ export interface OrderVO {
notifyUrl: string
notifyStatus: number
amount: number
price: number
channelFeeRate: number
channelFeeAmount: number
status: number
@ -28,64 +29,43 @@ export interface OrderVO {
createTime: Date
}
export interface OrderPageReqVO extends PageParam {
merchantId?: number
appId?: number
channelId?: number
channelCode?: string
export interface OrderDetailVO {
merchantOrderId?: string
no?: string
appId?: number
appName?: string
status?: number
price?: number
refundPrice?: number
channelFeePrice?: number
channelFeeRate?: number
successTime?: Date
expireTime?: Date
createTime?: Date
updateTime?: Date
subject?: string
body?: string
notifyUrl?: string
notifyStatus?: number
amount?: number
channelFeeRate?: number
channelFeeAmount?: number
status?: number
expireTime?: Date[]
successTime?: Date[]
notifyTime?: Date[]
successExtensionId?: number
refundStatus?: number
refundTimes?: number
channelUserId?: string
channelOrderNo?: string
createTime?: Date[]
}
export interface OrderExportReqVO {
merchantId?: number
appId?: number
channelId?: number
channelCode?: string
merchantOrderId?: string
subject?: string
body?: string
notifyUrl?: string
notifyStatus?: number
amount?: number
channelFeeRate?: number
channelFeeAmount?: number
status?: number
expireTime?: Date[]
successTime?: Date[]
notifyTime?: Date[]
successExtensionId?: number
refundStatus?: number
refundTimes?: number
channelUserId?: string
userIp?: string
channelOrderNo?: string
createTime?: Date[]
channelUserId?: string
notifyUrl?: string
extension?: {
channelNotifyData?: string
}
}
// 查询列表支付订单
export const getOrderPage = async (params: OrderPageReqVO) => {
return await request.get({ url: '/pay/order/page', params })
export const getOrderPage = async (params: any) => {
return await request.get<PageResult<OrderDetailVO[]>>({
url: '/pay/order/page',
params
})
}
// 查询详情支付订单
export const getOrder = async (id: number, sync?: boolean) => {
return await request.get({
return await request.get<OrderVO>({
url: '/pay/order/get',
params: {
id,
@ -96,7 +76,7 @@ export const getOrder = async (id: number, sync?: boolean) => {
// 获得支付订单的明细
export const getOrderDetail = async (id: number) => {
return await request.get({ url: '/pay/order/get-detail?id=' + id })
return await request.get<OrderDetailVO>({ url: '/pay/order/get-detail?id=' + id })
}
// 提交支付订单
@ -105,6 +85,6 @@ export const submitOrder = async (data: any) => {
}
// 导出支付订单
export const exportOrder = async (params: OrderExportReqVO) => {
export const exportOrder = async (params: any) => {
return await request.download({ url: '/pay/order/export-excel', params })
}

View File

@ -29,70 +29,36 @@ export interface RefundVO {
createTime: Date
}
export interface RefundPageReqVO extends PageParam {
merchantId?: number
appId?: number
channelId?: number
channelCode?: string
orderId?: string
tradeNo?: string
export interface RefundDetailVO {
merchantRefundId?: string
channelRefundNo?: string
merchantOrderId?: string
merchantRefundNo?: string
notifyUrl?: string
notifyStatus?: number
channelOrderNo?: string
appId?: number
appName?: string
payPrice?: number
refundPrice?: number
status?: number
type?: number
payAmount?: number
refundAmount?: number
successTime?: Date
createTime?: Date
updateTime?: Date
channelCode?: string
reason?: string
userIp?: string
channelOrderNo?: string
channelRefundNo?: string
channelErrorCode?: string
channelErrorMsg?: string
channelExtras?: string
expireTime?: Date[]
successTime?: Date[]
notifyTime?: Date[]
createTime?: Date[]
}
export interface PayRefundExportReqVO {
merchantId?: number
appId?: number
channelId?: number
channelCode?: string
orderId?: string
tradeNo?: string
merchantOrderId?: string
merchantRefundNo?: string
notifyUrl?: string
notifyStatus?: number
status?: number
type?: number
payAmount?: number
refundAmount?: number
reason?: string
userIp?: string
channelOrderNo?: string
channelRefundNo?: string
channelErrorCode?: string
channelErrorMsg?: string
channelExtras?: string
expireTime?: Date[]
successTime?: Date[]
notifyTime?: Date[]
createTime?: Date[]
channelNotifyData?: string
}
// 查询列表退款订单
export const getRefundPage = (params: RefundPageReqVO) => {
return request.get({ url: '/pay/refund/page', params })
export const getRefundPage = (params: any) => {
return request.get<PageResult<RefundDetailVO[]>>({ url: '/pay/refund/page', params })
}
// 查询详情退款订单
export const getRefund = (id: number) => {
return request.get({ url: '/pay/refund/get?id=' + id })
return request.get<RefundDetailVO>({ url: '/pay/refund/get?id=' + id })
}
// 新增退款订单
@ -111,6 +77,6 @@ export const deleteRefund = (id: number) => {
}
// 导出退款订单
export const exportRefund = (params: PayRefundExportReqVO) => {
export const exportRefund = (params: any) => {
return request.download({ url: '/pay/refund/export-excel', params })
}

View File

@ -1,16 +1,42 @@
import request from '@/config/axios'
export interface TransferVO {
id?: number
no?: string
appId?: number
appName?: string
channelId?: number
channelCode?: string
merchantTransferId?: string
type?: string
status?: number
successTime?: Date
price?: number
subject?: string
userName?: string
userAccount?: string
accountNo?: string
channelTransferNo?: string
userIp?: string
notifyUrl?: string
channelNotifyData?: string
createTime?: Date
}
// 查询转账单列表
export const getTransferPage = async (params: PageParam) => {
return await request.get({ url: `/pay/transfer/page`, params })
export const getTransferPage = async (params: any) => {
return await request.get<PageResult<TransferVO[]>>({
url: `/pay/transfer/page`,
params
})
}
// 查询转账单详情
export const getTransfer = async (id: number) => {
return await request.get({ url: '/pay/transfer/get?id=' + id })
return await request.get<TransferVO>({ url: '/pay/transfer/get?id=' + id })
}
// 导出转账单
export const exportTransfer = async (params: PageParam) => {
export const exportTransfer = async (params: any) => {
return await request.download({ url: '/pay/transfer/export-excel', params })
}

View File

@ -16,6 +16,12 @@ export interface WalletVO {
freezePrice: number
}
/** 修改会员钱包余额 Request VO */
export interface WalletBalanceUpdateReqVO {
userId: number
balance: number
}
/** 查询用户钱包详情 */
export const getWallet = async (params: PayWalletUserReqVO) => {
return await request.get<WalletVO>({ url: `/pay/wallet/get`, params })
@ -27,6 +33,6 @@ export const getWalletPage = async (params: any) => {
}
/** 修改会员钱包余额 */
export const updateWalletBalance = async (data: any) => {
export const updateWalletBalance = async (data: WalletBalanceUpdateReqVO) => {
return await request.put({ url: `/pay/wallet/update-balance`, data })
}

View File

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

View File

@ -1,8 +1,8 @@
import request from '@/config/axios'
export interface DictDataVO {
id: number
sort: number
id?: number
sort?: number
label: string
value: string
dictType: string
@ -10,7 +10,7 @@ export interface DictDataVO {
colorType: string
cssClass: string
remark: string
createTime: Date
createTime?: Date
}
// 查询字典数据(精简)列表

View File

@ -1,12 +1,12 @@
import request from '@/config/axios'
export interface DictTypeVO {
id: number
id?: number
name: string
type: string
status: number
remark: string
createTime: Date
createTime?: Date
}
// 查询字典(精简)列表

View File

@ -1,7 +1,7 @@
import request from '@/config/axios'
export interface MailAccountVO {
id: number
id?: number
mail: string
username: string
password: string
@ -38,7 +38,10 @@ export const deleteMailAccount = async (id: number) => {
// 批量删除邮箱账号
export const deleteMailAccountList = async (ids: number[]) => {
return await request.delete({ url: '/system/mail-account/delete-list', params: { ids: ids.join(',') } })
return await request.delete({
url: '/system/mail-account/delete-list',
params: { ids: ids.join(',') }
})
}
// 获得邮箱账号精简列表

View File

@ -48,5 +48,8 @@ export const deleteOAuth2Client = (id: number) => {
// 批量删除 OAuth2 客户端
export const deleteOAuth2ClientList = (ids: number[]) => {
return request.delete({ url: '/system/oauth2-client/delete-list', params: { ids: ids.join(',') } })
return request.delete({
url: '/system/oauth2-client/delete-list',
params: { ids: ids.join(',') }
})
}

View File

@ -39,7 +39,10 @@ export const deleteTenantPackage = (id: number) => {
// 批量删除租户套餐
export const deleteTenantPackageList = (ids: number[]) => {
return request.delete({ url: '/system/tenant-package/delete-list', params: { ids: ids.join(',') } })
return request.delete({
url: '/system/tenant-package/delete-list',
params: { ids: ids.join(',') }
})
}
// 获取租户套餐精简信息列表

View File

@ -5,6 +5,7 @@ export interface UserVO {
username: string
nickname: string
deptId: number
deptName?: string
postIds: string[]
email: string
mobile: string
@ -15,7 +16,24 @@ export interface UserVO {
remark: string
loginDate: Date
createTime: Date
disabled?: boolean
}
// 获取用户精简信息列表
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 }
})
}
// 查询用户管理列表
@ -80,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' })
}

Binary file not shown.

View File

@ -7,6 +7,7 @@ import { useWindowSize } from '@vueuse/core'
import { useAppStore } from '@/store/modules/app'
import { setCssVar } from '@/utils'
import { useDesign } from '@/hooks/web/useDesign'
import { normalizeLayout } from '@/utils/layout'
const { variables } = useDesign()
@ -33,7 +34,9 @@ watch(
!appStore.getMobile ? appStore.setMobile(true) : undefined
setCssVar('--left-menu-min-width', '0')
appStore.setCollapse(true)
appStore.getLayout !== 'classic' ? appStore.setLayout('classic') : undefined
normalizeLayout(appStore.getLayout) !== 'sidebar-nav'
? appStore.setLayout('sidebar-nav')
: undefined
} else {
appStore.getMobile ? appStore.setMobile(false) : undefined
setCssVar('--left-menu-min-width', '64px')

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