Compare commits

...

200 Commits

Author SHA1 Message Date
580439fec8 解决条件判断不对看不到语音功能的问题
Some checks failed
Check / lint (push) Has been cancelled
Check / typecheck (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
2025-06-03 16:18:22 +08:00
454b9ddbbd Merge branch 'wyfMain-dev-useBase'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-06-03 15:31:32 +08:00
079db90021 新增语音输入功能权限校验及未授权处理;正式环境开放2个账号用于测试 2025-06-03 15:30:33 +08:00
3719c162c3 无感处理语音输入功能,等待后续使用 2025-04-29 09:03:05 +08:00
eb2473df6f 进行无感处理,并去掉不需要的文件 2025-04-28 19:27:53 +08:00
f056ebd176 Merge branch 'wyfMain-dev-sqlite' into wyfMain-dev-useBase 2025-04-28 19:25:09 +08:00
033cf16ae7 新增调用原生安卓视频多选插件并获取对应地址的方法 2025-04-28 19:13:16 +08:00
f19660c03c 测试其他场景的sql 2025-04-28 13:19:21 +08:00
e0e3cfdd12 修复线上头像刷新不同步问题
Some checks failed
Check / lint (push) Has been cancelled
Check / typecheck (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
2025-04-28 13:13:55 +08:00
0a15ae5463 修改sql实现插入一条数据 2025-04-25 13:18:47 +08:00
d5df575aac 调整相关sql语句 2025-04-25 11:20:41 +08:00
bc99d083c9 Merge branch 'LiWenHao'
Some checks failed
Check / lint (push) Has been cancelled
Check / typecheck (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
2025-04-25 10:38:58 +08:00
68c867eee9 刷新通讯录bug 2025-04-25 10:38:47 +08:00
d137f39471 尝试调用数据库sql建库建表插入数据 2025-04-24 11:18:39 +08:00
dd31ad21be Merge branch 'LiWenHao'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-24 09:49:20 +08:00
c139828e71 通讯录 2025-04-24 09:48:52 +08:00
11bb78cc5e 尝试创建chat数据库,创建聊天记录数据表 2025-04-23 14:24:10 +08:00
b97219565e 新增检查聊天页面是否可通信方法,用于辅助检测是否出现白屏及恢复
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-23 13:39:25 +08:00
65dc8002db 增加语音输入不可以同时引用的限制,并处理样式 2025-04-23 09:55:04 +08:00
dd42b26d1f 新增语音输入和语音转文字接口对接,并调整部分样式 2025-04-22 19:06:02 +08:00
eb1523516c Merge branch 'main' into wyfMain-dev-useBase 2025-04-22 17:10:41 +08:00
02999dc782 解决解散等场景下会出现已读/未读/总人数为-1的情况
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-22 15:20:24 +08:00
dd0ccb9818 新增收起应用时同时停止已读未读定时器,并在恢复后打开;解决群聊踢人后自己也看不到右上角的管理按钮问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-22 14:49:08 +08:00
71d97f069f 处理媒体文件因为预加载导致的观察者监听错误问题;解决未读消息列表中会有自己的问题;解决多个媒体文件已读未读状态错误的问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-21 16:21:54 +08:00
7be32e930c Merge branch 'LiWenHao'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-21 11:15:12 +08:00
885707f328 群聊按钮新增防抖 全选按钮可以点击 2025-04-21 11:13:19 +08:00
f76198b674 解决资源文件无法监听已读的问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-21 11:06:48 +08:00
66828a254c 处理已读未读样式优化;处理@功能新问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-21 09:52:59 +08:00
3898c637b4 完成第一版已读未读功能 2025-04-18 17:17:13 +08:00
29dcfac775 增加对两个视角已读未读消息的处理,并调整触发时机;增加map维护已读数量 2025-04-17 17:05:42 +08:00
e4aa6181a8 调整语音输入框样式 2025-04-16 18:59:36 +08:00
676ad46ab5 新增已读未读功能对两种视角的处理,等待接口和socket联调 2025-04-16 17:19:31 +08:00
3b70cafff6 新增语音输入组件,调整H5使用的方法,保证能唤起麦克风 2025-04-16 13:36:32 +08:00
9e2daf8b2e Merge branch 'LiWenHao'
Some checks failed
Check / lint (push) Has been cancelled
Check / typecheck (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
2025-04-11 15:54:50 +08:00
651c920b7a 建群按钮置灰逻辑修改 踢出群聊我的样式修改 投诉增加导航栏 2025-04-11 15:54:40 +08:00
4090ee0547 Merge branch 'wyfMain-dev'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-11 15:53:15 +08:00
251cf7d66f 解决AI编辑器自动补全代码导致的数据源使用错误问题 2025-04-11 15:52:39 +08:00
b7eea81200 Merge branch 'wyfMain-dev'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-11 14:54:00 +08:00
df1cc68b59 修改@功能问题;新增IOS版本号判断使用哪个输入框;处理聊天记录加载问题 2025-04-11 14:53:01 +08:00
a5d9009910 新增华为审核投诉功能 修改群聊低于三人按钮置灰逻辑 修改移出群自己在列表最上方并且不在列表出现问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-11 09:51:06 +08:00
bbb5dd99fc 新增聊天普通输入框,用于处理手机系统版本过低时不支持quill-edior导致的问题 2025-04-10 19:57:41 +08:00
2953d50e4b 解决加载聊天数据10条后不满一屏,无法继续加载更多数据的问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-10 16:15:44 +08:00
4381520f3c 新增清除缓存时同时清理聊天记录缓存;新增打包编译文件用时间戳维护 2025-04-10 11:30:56 +08:00
4a5d005600 处理打包后vue文件也添加时间戳 2025-04-09 19:57:00 +08:00
ec17da2e2c 新增OA墨册刷新缓存时,同时刷新聊天;新增打包文件名时间戳,保证每次打包后上传会自动刷新;修改聊天webview的loading和error界面,更明显并于OA的作区分
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-09 19:06:07 +08:00
041692afe8 处理聊天框无法复制复杂富文本
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-09 15:08:54 +08:00
9536ce98a6 处理iPhone13 Pro聊天输入框能弹出键盘但没聚焦导致无法打字问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-09 10:02:58 +08:00
30412b109e 1、处理撤回按钮点击区域大小;2、发送按钮新增loading;3、解决只有一条聊天记录搜索不到、单聊按日期搜索时搜索结果不正确问题;4、解决部分艾特功能在部分手机上样式错误等问题 2025-04-08 16:58:28 +08:00
87ca206b2b 解决聊天记录不足一屏时无法加载更多聊天记录数据的问题;新增接收OA修改头像同步刷新聊天头像
Some checks failed
Check / lint (push) Has been cancelled
Check / typecheck (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
2025-04-07 14:10:16 +08:00
d494d514b5 解决选择人员搜索时,会只添加当前选择的人问题
Some checks failed
Check / lint (push) Has been cancelled
Check / typecheck (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
2025-04-03 19:04:19 +08:00
f6038e95a0 解决聊天全部放开后出现的问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-03 15:45:12 +08:00
3183ff5049 增加体制内聊天的地址
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-02 15:09:30 +08:00
e0305ab496 处理部分安卓机未读消息数量问题;处理表情发送选择不能滑动问题;处理安卓不能发送原图问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-04-02 13:57:27 +08:00
21ebbf92d5 Merge branch 'wyfMain-dev'
Some checks failed
Check / lint (push) Has been cancelled
Check / typecheck (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
2025-03-31 14:31:07 +08:00
f8c3689588 处理被踢出群时如果会话已被删除还会有红点的问题;处理上传图片视频没有正确调用publish接口的问题 2025-03-31 14:29:58 +08:00
wwt
31dcd0ebca Merge branch 'wwt'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-31 09:27:04 +08:00
wwt
c374154f0f Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/chat-app
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
* 'main' of https://gitea-inner.fontree.cn/scout666/chat-app:
  处理文件上传中友好提示;处理文件上传失败场景图标展示;解决图片上传loading问题;替换所有进度条颜色更醒目;
  处理聊天未读消息数量统计、应用角标、视频预览、切换账号清除缓存问题
  解决删除会话后发消息,数量统计不正确问题;解决图片显示不正常问题
  处理未读消息数量显示、删除回话自动已读、显示样式等问题
2025-03-31 09:26:45 +08:00
wwt
dd3e217bb1 1 2025-03-31 09:26:33 +08:00
wwt
96c4fbaad9 过滤测试数据的erp用户接口 2025-03-31 09:25:59 +08:00
e7ec387735 处理文件上传中友好提示;处理文件上传失败场景图标展示;解决图片上传loading问题;替换所有进度条颜色更醒目;
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-30 16:22:29 +08:00
6140c625e4 处理聊天未读消息数量统计、应用角标、视频预览、切换账号清除缓存问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-29 17:15:19 +08:00
99ffb6ec05 解决删除会话后发消息,数量统计不正确问题;解决图片显示不正常问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-28 18:33:43 +08:00
f17250f236 处理未读消息数量显示、删除回话自动已读、显示样式等问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-28 16:13:45 +08:00
e904dae8a0 修改聊天项目初始化时,获取token、个人信息和socket连接调用顺序问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-28 09:58:35 +08:00
66e95da62e 修改打包配置,准备上线
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-27 17:52:25 +08:00
fea7504a91 修改上线前遇到的问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-27 17:25:12 +08:00
2676e70c0c Merge branch 'wyfMain-dev'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-26 19:52:46 +08:00
f9f7e6e45d 处理上传进度条;处理多选记录的取消;处理发送图片对方没有更新;处理解散群、踢人在对方会话列表中没有该会话情况 2025-03-26 19:51:07 +08:00
wwt
fdf71eb4da Merge branch 'wwt'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-26 16:18:33 +08:00
1f4c5ba6df 处理@显示、链接跳转、免打扰数量统计等问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-26 14:30:20 +08:00
wwt
687cc0ec38 fix bug#4053 2025-03-26 09:59:26 +08:00
3642d3e2fa 处理样式错误写法
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-25 19:46:04 +08:00
8ed2bf7113 处理各种场景热更新;新增1106消息类型处理解散;群聊按图片视频类型查看增加视频播放;修改选择图片组件为plus组件
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-25 19:40:19 +08:00
17d9ed737d 解决IOS或其他设备推送时可能找不到webview的问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-25 13:12:06 +08:00
04dcbdf331 处理未读消息数量场景补全情况
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-25 10:33:30 +08:00
fa2098c565 处理oa底部tabbar未读消息数量显示 2025-03-24 20:03:52 +08:00
383abed2e8 Merge branch 'wyfMain-dev'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-24 16:39:04 +08:00
3a15ec6c00 处理搜索结果问题、搜索分页问题、会话问题,@xxx问题等 2025-03-24 16:37:29 +08:00
wwt
2fa381bea1 fix bug#4088
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-24 15:55:54 +08:00
wwt
988912de2f 处理全局消息提示,移除部分页面接口的提示
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-24 15:24:16 +08:00
wwt
47d5cab23a 处理message提示
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-24 14:55:21 +08:00
wwt
ed2d6dfbd5 Merge branch 'main' into wwt 2025-03-24 14:18:43 +08:00
wwt
e780d44255 1
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-24 14:18:33 +08:00
wwt
f6d66fa71c 处理消息提示 2025-03-24 14:18:11 +08:00
wwt
5822c80f6a 1 2025-03-24 11:36:28 +08:00
wwt
fe857f1e81 Merge branch 'wwt'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-24 11:34:59 +08:00
wwt
4a670201c6 fix bug#3909 2025-03-24 11:34:13 +08:00
a06f0b2489 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/chat-app
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-24 09:51:59 +08:00
84cca43b52 处理搜索跳转指定聊天记录后向下查,数据会错乱的问题 2025-03-24 09:50:57 +08:00
wwt
b771a3c910 Merge branch 'wwt'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-24 09:47:43 +08:00
wwt
0b542e2e63 fix bug#4066 2025-03-24 09:43:46 +08:00
c2a9676b09 Merge branch 'wyfMain-dev'
Some checks failed
Check / lint (push) Has been cancelled
Check / typecheck (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
2025-03-22 14:23:50 +08:00
7595af1d33 处理1116消息类型;新增查看部门页面 2025-03-22 14:22:09 +08:00
d781a94a89 A-Z排序的人员列表新增#处理首字母不在其中的;新增查看部门页面;处理聊天记录本地缓存时卡死的问题 2025-03-21 17:01:34 +08:00
wwt
d0063bf11f fix bug#3900
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-21 16:26:19 +08:00
wwt
11ff75f0d3 fix bug#4063
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-21 16:10:03 +08:00
wwt
8d82024a1e fix bug#3948 2025-03-21 15:32:39 +08:00
wwt
982adc3f4b Merge branch 'wwt'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-21 13:35:37 +08:00
wwt
efc9ffea12 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/chat-app
* 'main' of https://gitea-inner.fontree.cn/scout666/chat-app:
  完成输入框组件中@样式和相对应的功能
  修改部门创建权限规则
2025-03-21 13:35:28 +08:00
wwt
d66aa108d8 1 2025-03-21 13:35:09 +08:00
wwt
165f960d14 fix bug#3909 2025-03-21 13:15:39 +08:00
d13ae03355 Merge branch 'wyfMain-dev'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-20 19:13:11 +08:00
ce9f977efd 完成输入框组件中@样式和相对应的功能 2025-03-20 19:12:46 +08:00
wwt
32e8428f46 Merge branch 'wwt'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-20 16:53:27 +08:00
wwt
cb123c6331 fix bug#3888 2025-03-20 16:53:07 +08:00
2f64cda0d4 修改部门创建权限规则 2025-03-20 15:43:28 +08:00
9e8e3aeba3 Merge branch 'wyfMain-dev'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-20 14:52:23 +08:00
wwt
1f64ffd8aa 调整发起群聊,部门创建的权限
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-20 14:39:57 +08:00
8034a303e7 解决推送消息点击后,跳转页面读不出数据的问题 2025-03-20 14:35:40 +08:00
张 元山
8eb0f0b4f2 处理@
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-20 11:02:29 +08:00
92a7d74c6c 处理按群成员搜索聊天记录页面的接口调用与跳转;处理聊天信息推送的信息接收与跳转 2025-03-19 19:58:23 +08:00
d7c813977d Merge branch 'wyfMain-dev'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-19 09:13:35 +08:00
2642a885f0 处理本地缓存没有时,聊天记录的跳转,并处理直接调起相机、文件查看等问题 2025-03-18 20:00:41 +08:00
wwt
ba52f9a576 Merge branch 'wwt'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-18 09:45:56 +08:00
wwt
33106bfa80 fix bug#3962 2025-03-18 09:45:42 +08:00
026e4fa3e6 处理搜索不同条件、不同类型跳转对应聊天记录 2025-03-17 19:54:43 +08:00
wwt
92cda98ca9 fix bug#3948 发起群聊返回后,清楚之前的数据 2025-03-17 18:45:20 +08:00
wwt
a9febca5e5 改回之前的接口 2025-03-17 18:35:19 +08:00
wwt
b8fd50394c fix bug#3907 群成员把管理员提前显示 2025-03-17 17:39:35 +08:00
a6810d4f12 Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/chat-app
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-17 16:34:01 +08:00
wwt
b4b614b545 通讯录的接口修改 2025-03-17 16:32:42 +08:00
b0abca9bf2 修改撤回显示规则 2025-03-17 16:32:29 +08:00
wwt
505bb9b9f8 修改踢出为移出 2025-03-17 15:57:45 +08:00
wwt
ad2b7d42e3 fix bug#3888
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-17 15:43:10 +08:00
wwt
16300b68bf fix bug#3900 2025-03-17 15:21:40 +08:00
wwt
4b24e7a269 fix bug#3918 2025-03-17 14:52:30 +08:00
wwt
0e640c7ad6 提交解决冲突
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-17 11:23:07 +08:00
wwt
010528e52e 1 2025-03-17 11:21:52 +08:00
wwt
baa05faa24 fix bug#3977 群公告只能管理员编辑;#3887 从消息聊天窗口退出后,首页列表的红点消失 2025-03-17 11:21:23 +08:00
380b0120bc 解决从搜索聊天记录跳转聊天时,下拉刷新上拉加载数据错误问题;解决图片、视频、文件上传后会变成2条问题;
Some checks failed
Check / lint (push) Has been cancelled
Check / typecheck (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
2025-03-15 16:44:40 +08:00
894bb9bf28 Merge branch 'wyfMain-dev'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-15 09:42:33 +08:00
11ef845e9c 修改测试环境token获取方式 2025-03-15 09:41:11 +08:00
cba592b87d 处理聊天在webview打开时底部tabbar 2025-03-14 17:05:05 +08:00
3fe60616a5 解决测试出现的细节问题 2025-03-14 13:15:54 +08:00
wwt
28625c7943 Merge branch 'wwt'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-14 11:20:12 +08:00
wwt
566bba5308 fix bug#3958 补充群人数,群类型 2025-03-14 11:15:39 +08:00
wwt
feee532366 fix bug#3960 2025-03-14 10:49:01 +08:00
wwt
67f923b516 fix bug#3957 根据talk_type处理群聊人数显示 2025-03-14 10:33:14 +08:00
wwt
4abd2d8047 fix bug#3949 限制群名称字符长度 2025-03-14 10:25:04 +08:00
98e3a0710a Merge branch 'wyfMain-dev'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-14 09:50:10 +08:00
747653f54a 完成电话拨打功能 2025-03-14 09:49:30 +08:00
wwt
e6075849fe 消息页 参数冲突
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-14 09:48:05 +08:00
wwt
c7343a012a 1
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-14 09:46:39 +08:00
wwt
2d28872a12 fix bug#3892 重新编辑已撤回的消息 2025-03-14 09:46:03 +08:00
wwt
659c2c3e12 fix bug#3941 1114系统消息补充 2025-03-14 09:45:02 +08:00
2304ddfc45 修改token
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-14 08:57:50 +08:00
3414fb63b8 Merge branch 'wyfMain-dev'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-13 19:55:27 +08:00
55e13e6024 开发搜索跳转聊天记录并查询上下文功能 2025-03-13 19:54:29 +08:00
wwt
be047f30c1 fix bug#3892 2025-03-13 13:08:58 +08:00
wwt
f6ce053787 Merge branch 'wwt'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-13 10:09:18 +08:00
wwt
382c3fed39 fix bug#3905 2025-03-13 10:08:49 +08:00
e4c2b7cdcb Merge branch 'wyfMain-dev'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-13 09:33:55 +08:00
adb57bda88 完成部分艾特功能 2025-03-12 19:59:54 +08:00
wwt
d303c8e94e fix bug#3931
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-12 16:31:25 +08:00
4b3062eefa 新增艾特功能的选择界面 2025-03-12 16:26:39 +08:00
36342b4dd9 封装按字母选人页面为自定义组件 2025-03-12 16:25:42 +08:00
wwt
0cb90fcc3b fix bug #3909
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-12 15:55:27 +08:00
wwt
7d2cde3b65 fix bug#3910
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-12 15:46:16 +08:00
wwt
4be1c7c9a4 处理冲突
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-12 14:57:55 +08:00
wwt
c408b97b1e token 2025-03-12 14:47:14 +08:00
ae2a447fa4 新增聊天页面选择提醒的人弹窗 2025-03-11 19:56:43 +08:00
f2abedd88e 解决会话列表持久化后,聊天页面卡死的问题 2025-03-11 13:58:25 +08:00
9b904b4fda Merge branch 'wyfMain-dev'
Some checks failed
Check / lint (push) Has been cancelled
Check / typecheck (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
2025-03-11 11:36:18 +08:00
8b61369006 调整打包配置 2025-03-11 11:35:42 +08:00
scout
93428be5df 添加命令
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-10 19:07:30 +08:00
8c9b9d0f2c Merge branch 'zhangyuanshan-20250310' into wyfMain-dev 2025-03-10 15:31:23 +08:00
ada0bc0ee9 新增引入消息显示 2025-03-10 15:09:26 +08:00
张 元山
ff5a23b18e 构建 2025-03-10 14:28:52 +08:00
78756431b7 处理部分环境配置 2025-03-10 10:59:45 +08:00
226a24b06b 处理提测前的样式问题
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-10 09:57:21 +08:00
c4b4ec323a 处理聊天数据请求特殊情况
Some checks failed
Check / lint (push) Has been cancelled
Check / typecheck (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
2025-03-07 16:14:25 +08:00
e782b72b74 Merge branch 'wyfMain-dev'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-07 15:06:49 +08:00
ba0a5384e0 解决聊天记录页面分页、数据加载、渲染等问题 2025-03-07 15:02:53 +08:00
ea73fa8b9b 处理聊天页面中出现的问题;优化页面中部门明细的样式问题 2025-03-06 18:50:11 +08:00
c9eb55ed5f 接入naive-ui组件 2025-03-06 09:04:59 +08:00
wwt
f1fa0208c1 更新搜索框背景色;通讯录导航栏背景图;聊天设置排版;聊天列表群名过长排版
Some checks failed
Check / lint (push) Has been cancelled
Check / typecheck (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
2025-03-05 13:14:54 +08:00
wwt
b151f90fa6 提交修改
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-04 18:56:34 +08:00
wwt
37c5b604c9 更新z-paging组件为最新版本;修改首页的样式;添加下拉刷新;群聊设置的样式调整 2025-03-04 18:55:17 +08:00
11aacea856 自定义checkbox新增disabled状态;选择人员新增已在群内人员禁止选择
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-04 16:38:44 +08:00
aa9adb6211 Merge branch 'main' into wyfMain-dev
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-04 14:11:21 +08:00
69fd096c4d 完成部门选择和人员选择页面新规则 2025-03-04 14:10:25 +08:00
82ba6ddbd7 修改选择部门和人员的规则 2025-03-04 11:43:08 +08:00
wwt
ccc8c41b2d Merge branch 'wwt'
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-04 11:41:54 +08:00
wwt
cef9d08356 token 2025-03-04 11:41:09 +08:00
wwt
69c5934ef4 首页样式改动:顶部;搜索框;滑块;消息间距 2025-03-04 11:40:37 +08:00
wwt
160788a107 修改搜索框默认提示的字体颜色 2025-03-04 09:14:53 +08:00
d7e8df5386 修改人员选择规则
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-03 17:05:22 +08:00
b58f25945e 修改部门选择规则
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
2025-03-03 15:22:53 +08:00
bdfd84bd35 修改部分选择人员的规则与交互 2025-02-18 19:51:23 +08:00
a0578c57ad 解决群管理员列表【我】的判断规则不适配新版的问题;新增管理员选择页面搜索功能 2025-02-14 19:52:01 +08:00
e9bd2bf6cd 修改部门群管理员岗位新增修改规则,增加部门岗位对应关系 2025-02-13 08:49:20 +08:00
2c063f3d4f 重构部分聊天详情页面;重构部分管理员选择页面 2025-02-11 11:45:19 +08:00
5c55411aa3 重构部分聊天选择页面 2025-02-08 17:09:57 +08:00
df07c953bf 重构选择群类型页面部分样式;将选择部门和选择群成员合并到一起;去除选择管理员页面,复用设置管理员页面;调整按部门选择人员样式和逻辑;调整创建群聊部分规则 2025-02-07 17:00:57 +08:00
dafe65bb72 修改选择部门和新增群成员页面交互与样式;新增通讯录页面复用部分适配 2025-02-06 17:01:28 +08:00
78ca543946 新增通讯录页面;新增发起单聊功能;新增聊天会话左滑删除并同时删除localStorage中的记录 2025-02-05 16:22:32 +08:00
2e3b0b994b 新增自定义头像通用组件;进行发起群聊、选择部门页面部分重构 2025-01-24 17:01:50 +08:00
8704691821 更新zpaging组件版本至最新;新增自定义导航栏组件 2025-01-24 11:18:26 +08:00
4f57419c5d 实现普通群项目群的拉人、踢人功能;优化人员选择功能界面 2025-01-23 16:45:49 +08:00
489fb71be5 实现普通群创建和项目群创建;实现群头像修改功能;实现项目群和普通群的管理员设置、移除、查看 2025-01-22 16:37:28 +08:00
163 changed files with 20863 additions and 7805 deletions

75
auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,75 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const effectScope: typeof import('vue')['effectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useDialog: typeof import('naive-ui')['useDialog']
const useId: typeof import('vue')['useId']
const useLoadingBar: typeof import('naive-ui')['useLoadingBar']
const useMessage: typeof import('naive-ui')['useMessage']
const useModel: typeof import('vue')['useModel']
const useNotification: typeof import('naive-ui')['useNotification']
const useSlots: typeof import('vue')['useSlots']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}

72
components.d.ts vendored Normal file
View File

@ -0,0 +1,72 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AsyncError: typeof import('./src/components/async-error/index.vue')['default']
AsyncLoading: typeof import('./src/components/async-loading/index.vue')['default']
AudioMessage: typeof import('./src/components/talk/message/AudioMessage.vue')['default']
Avatar: typeof import('./src/components/base/Avatar.vue')['default']
AvatarCropper: typeof import('./src/components/base/AvatarCropper.vue')['default']
AvatarModule: typeof import('./src/components/avatar-module/index.vue')['default']
CheckBox: typeof import('./src/components/checkBox/index.vue')['default']
CodeMessage: typeof import('./src/components/talk/message/CodeMessage.vue')['default']
CustomBtn: typeof import('./src/components/custom-btn/custom-btn.vue')['default']
CustomInput: typeof import('./src/components/custom-input/custom-input.vue')['default']
CustomNavbar: typeof import('./src/components/custom-navbar/index.vue')['default']
CustomRefresher: typeof import('./src/components/custom-refresher/custom-refresher.vue')['default']
DeepBubble: typeof import('./src/components/deep-bubble/deep-bubble.vue')['default']
FileMessage: typeof import('./src/components/talk/message/FileMessage.vue')['default']
ForwardMessage: typeof import('./src/components/talk/message/ForwardMessage.vue')['default']
ForwardRecord: typeof import('./src/components/talk/ForwardRecord.vue')['default']
GroupNoticeMessage: typeof import('./src/components/talk/message/GroupNoticeMessage.vue')['default']
HistoryRecord: typeof import('./src/components/talk/HistoryRecord.vue')['default']
ImageMessage: typeof import('./src/components/talk/message/ImageMessage.vue')['default']
LinkMessage: typeof import('./src/components/talk/message/LinkMessage.vue')['default']
Loading: typeof import('./src/components/base/Loading.vue')['default']
LoginMessage: typeof import('./src/components/talk/message/LoginMessage.vue')['default']
Message: typeof import('./src/components/x-message/message/index.vue')['default']
MixedMessage: typeof import('./src/components/talk/message/MixedMessage.vue')['default']
NButton: typeof import('naive-ui')['NButton']
NIcon: typeof import('naive-ui')['NIcon']
PageAnimation: typeof import('./src/components/page-animation/index.vue')['default']
RevokeMessage: typeof import('./src/components/talk/message/RevokeMessage.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SysGroupAdminMessage: typeof import('./src/components/talk/message/system/SysGroupAdminMessage.vue')['default']
SysGroupCancelMutedMessage: typeof import('./src/components/talk/message/system/SysGroupCancelMutedMessage.vue')['default']
SysGroupCreateMessage: typeof import('./src/components/talk/message/system/SysGroupCreateMessage.vue')['default']
SysGroupDismissed: typeof import('./src/components/talk/message/system/SysGroupDismissed.vue')['default']
SysGroupInfoChangeMessage: typeof import('./src/components/talk/message/system/SysGroupInfoChangeMessage.vue')['default']
SysGroupJoinMessage: typeof import('./src/components/talk/message/system/SysGroupJoinMessage.vue')['default']
SysGroupMemberCancelMutedMessage: typeof import('./src/components/talk/message/system/SysGroupMemberCancelMutedMessage.vue')['default']
SysGroupMemberKickedMessage: typeof import('./src/components/talk/message/system/SysGroupMemberKickedMessage.vue')['default']
SysGroupMemberMutedMessage: typeof import('./src/components/talk/message/system/SysGroupMemberMutedMessage.vue')['default']
SysGroupMemberQuitMessage: typeof import('./src/components/talk/message/system/SysGroupMemberQuitMessage.vue')['default']
SysGroupMemberRemovedMessage: typeof import('./src/components/talk/message/system/SysGroupMemberRemovedMessage.vue')['default']
SysGroupMutedMessage: typeof import('./src/components/talk/message/system/SysGroupMutedMessage.vue')['default']
SysGroupTransferMessage: typeof import('./src/components/talk/message/system/SysGroupTransferMessage.vue')['default']
SysTextMessage: typeof import('./src/components/talk/message/system/SysTextMessage.vue')['default']
TabbarItem: typeof import('./src/components/x-tabbar/components/tabbar-item/index.vue')['default']
TextMessage: typeof import('./src/components/talk/message/TextMessage.vue')['default']
UnknownMessage: typeof import('./src/components/talk/message/UnknownMessage.vue')['default']
UploadsModal: typeof import('./src/components/base/UploadsModal.vue')['default']
VideoMessage: typeof import('./src/components/talk/message/VideoMessage.vue')['default']
VoteMessage: typeof import('./src/components/talk/message/VoteMessage.vue')['default']
XCalendar: typeof import('./src/components/x-calendar/index.vue')['default']
XCaptcha: typeof import('./src/components/x-captcha/index.vue')['default']
XConfirm: typeof import('./src/components/x-confirm/index.vue')['default']
XDateSelect: typeof import('./src/components/x-date-select/index.vue')['default']
XLoaderror: typeof import('./src/components/x-loaderror/index.vue')['default']
XLoading: typeof import('./src/components/x-loading/index.vue')['default']
XMessage: typeof import('./src/components/x-message/index.vue')['default']
XPaging: typeof import('./src/components/x-paging/index.vue')['default']
XTabbar: typeof import('./src/components/x-tabbar/index.vue')['default']
Xtime: typeof import('./src/components/base/Xtime.vue')['default']
}
}

6
env/.env.dev vendored
View File

@ -5,4 +5,8 @@ VITE_SHOW_CONSOLE = true
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP = true
# baseUrl
VITE_BASEURL = 'http://warehouse.szjixun.cn/oa_backend'
VITE_BASEURL = 'http://172.16.100.93:8503'
#VITE_SOCKET_API
VITE_SOCKET_API = 'ws://172.16.100.93:8504'
# EPRAPI baseUrl
VITE_EPR_BASEURL = 'http://114.218.158.24:9020'

19
env/.env.prod vendored
View File

@ -1,8 +1,21 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'prod'
# 是否显示console
VITE_SHOW_CONSOLE = true
VITE_SHOW_CONSOLE = false
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP = true
VITE_SHOW_SOURCEMAP = false
# baseUrl
VITE_BASEURL = 'https://oa-a.szjixun.cn/api'
VITE_BASEURL = 'https://chat-out.szjixun.cn' #体制外
#VITE_SOCKET_API
VITE_SOCKET_API = 'wss://chat-out.szjixun.cn' #体制外
# EPRAPI baseUrl
VITE_EPR_BASEURL = 'https://erpapi-out.szjixun.cn' #体制外
# # baseUrl
# VITE_BASEURL = 'https://chat.szjixun.cn' #体制内
# #VITE_SOCKET_API
# VITE_SOCKET_API = 'wss://chat.szjixun.cn' #体制内
# # EPRAPI baseUrl
# VITE_EPR_BASEURL = 'https://erpapi.fontree.cn' #体制内

12
env/.env.test vendored
View File

@ -4,10 +4,16 @@ NODE_ENV = 'test'
VITE_SHOW_CONSOLE = true
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP = true
# # baseUrl
VITE_BASEURL = 'http://172.16.100.93:8503'
# #VITE_SOCKET_API
VITE_SOCKET_API = 'ws://172.16.100.93:8504'
# baseUrl
# VITE_BASEURL = 'https://warehouse.szjixun.cn/oa_backend'
VITE_BASEURL = 'http://172.16.100.93:9503'
# VITE_BASEURL = 'http://192.168.88.21:9503'
#VITE_SOCKET_API
VITE_SOCKET_API = 'ws://172.16.100.93:9504'
# VITE_SOCKET_API = 'ws://192.168.88.21:9504'
# EPRAPI baseUrl
VITE_EPR_BASEURL = 'http://114.218.158.24:9020'

View File

@ -8,7 +8,9 @@
"test:h5": "uni --mode test --port 2468",
"prod:h5": "uni --mode prod",
"build:h5:test": "uni build --mode test",
"build:h5:prod": "uni build --mode prod"
"build:h5:prod": "uni build --mode prod",
"preview:h5": "uni preview --mode test",
"preview:h5:prod": "uni preview --mode prod"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-alpha-4000020240111001",
@ -25,6 +27,7 @@
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-4000020240111001",
"@dcloudio/uni-mp-xhs": "3.0.0-alpha-4000020240111001",
"@dcloudio/uni-quickapp-webview": "3.0.0-alpha-4000020240111001",
"@icon-park/vue-next": "^1.4.2",
"@uni-helper/axios-adapter": "^1.5.2",
"@uni-helper/localforage-adapter": "^1.0.2",
"@uni-helper/uni-use": "^0.19.12",
@ -36,7 +39,9 @@
"lodash": "^4.17.21",
"nzh": "^1.0.13",
"pinia-plugin-persistedstate": "^4.1.3",
"quill-mention": "^6.0.2",
"quill": "^1.3.7",
"quill-mention": "^4.1.0",
"recorder-core": "^1.3.25011100",
"vconsole": "^3.15.1",
"vue": "^3.3.8",
"vue-i18n": "11.0.0-rc.1"
@ -58,13 +63,16 @@
"@vue/runtime-core": "^3.3.8",
"@vue/tsconfig": "^0.5.1",
"lint-staged": "^15.2.0",
"naive-ui": "^2.41.0",
"pinia": "2.0.36",
"sass": "^1.77.8",
"sass": "1.62.1",
"simple-git-hooks": "^2.9.0",
"typescript": "^5.3.3",
"unocss": "^0.58.9",
"unocss-applet": "^0.8.2",
"vite": "^5.0.11",
"unplugin-auto-import": "^19.1.1",
"unplugin-vue-components": "^28.4.1",
"vite": "4.5.1",
"vue-tsc": "^1.8.27"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,43 +1,65 @@
<script setup>
import {useStatus} from "@/store/status";
import { useUserStore } from '@/store'
import {
useProvideUserModal,
} from '@/hooks'
import { useStatus } from '@/store/status'
import { useUserStore, useDialogueListStore } from '@/store'
import { useProvideUserModal } from '@/hooks'
import {useAuth} from "@/store/auth";
const {token} = useAuth()
import ws from '@/connect'
const {statusBarHeight}= useStatus()
import {uniStorage} from "@/utils/uniStorage.js"
const { statusBarHeight } = useStatus()
const { uid, isShow } = useProvideUserModal()
const userStore = useUserStore()
const root = document.documentElement
root.style.setProperty('--statusBarHeight',`${statusBarHeight.value}px`)
const init = () => {
root.style.setProperty('--statusBarHeight', `${statusBarHeight.value}px`)
const handleWebview = () => {
let statusBarHeight_ = window?.plus?.navigator?.getStatusbarHeight()
const webview = plus.webview.currentWebview()
// webview.setStyle({
// top: statusBarHeight_,
// bottom: 0,
// })
console.log("webview", webview)
token.value = webview.token
if(webview?.doClearDialogueList){
useDialogueListStore().dialogueList.value = []
uniStorage.removeItem('dialogueList')
}
userStore.loadSetting()
ws.connect()
}
const init = () => {
if (typeof plus !== 'undefined') {
handleWebview()
} else {
document.addEventListener('plusready', () => {
handleWebview()
})
}
}
init()
</script>
<style lang="scss">
@import "@/static/css/color.scss";
@import "@/static/css/font.scss";
<style lang="scss">
@import '@/static/css/color.scss';
@import '@/static/css/font.scss';
/* #ifdef APP-NVUE */
@import '@/uni_modules/tmui/scss/nvue.css';
/* #endif */
/* #ifndef APP-NVUE */
@import '@/uni_modules/tmui/scss/noNvue.css';
/* #endif */
*{
* {
box-sizing: border-box;
}
/*解决阅览图片关闭按钮会显示在状态栏区域的问题*/
#u-a-p>div>div{
margin-top:var(--statusBarHeight)
#u-a-p > div > div {
margin-top: var(--statusBarHeight);
}
/*不显示滚动条的类*/
.no-scroll {
-ms-overflow-style: none; /* IE 和 Edge */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 和 Edge */
scrollbar-width: none; /* Firefox */
}
.no-scroll::-webkit-scrollbar {
display: none; /* Webkit 浏览器 */
display: none; /* Webkit 浏览器 */
}
</style>

View File

@ -1,5 +1,6 @@
import request from '@/service/index.js'
import qs from 'qs'
import { useTalkStore, useDialogueStore } from '@/store'
// 获取聊天列表服务接口
export const ServeGetTalkList = (data) => {
@ -38,7 +39,31 @@ export const ServeTopTalkList = (data) => {
}
// 清除聊天消息未读数服务接口
export const ServeClearTalkUnreadNum = (data) => {
export const ServeClearTalkUnreadNum = (data, unReadNum) => {
console.log('=======chatApp==UnreadNum', unReadNum)
if (
!useTalkStore().items[
useTalkStore().findTalkIndex(useDialogueStore().index_name)
]?.is_disturb
) {
if (typeof plus !== 'undefined') {
let OAWebView = plus.webview.all()
OAWebView.forEach((webview) => {
if (webview.id === 'webviewId1') {
webview.evalJS(`updateUnreadMsgNumReduce('${unReadNum}')`)
}
})
} else {
document.addEventListener('plusready', () => {
let OAWebView = plus.webview.all()
OAWebView.forEach((webview) => {
if (webview.id === 'webviewId1') {
webview.evalJS(`updateUnreadMsgNumReduce('${unReadNum}')`)
}
})
})
}
}
return request({
url: '/api/v1/talk/unread/clear',
method: 'POST',
@ -135,6 +160,15 @@ export const ServeRemoveRecords = (data) => {
})
}
//清空聊天记录
export const ServeEmptyMessage = (data) => {
return request({
url: '/api/v1/talk/message/empty',
method: 'POST',
data,
})
}
// 收藏表情包服务接口
export const ServeCollectEmoticon = (data) => {
return request({
@ -160,13 +194,49 @@ export const ServeConfirmVoteHandle = (data) => {
})
}
export const uploadImg = (data,onProgressFn) => {
export const uploadImg = (data, onProgressFn) => {
return request({
url: '/upload/img',
method: 'POST',
data:data,
baseURL:import.meta.env.VITE_EPR_BASEURL,
isFormData:true,
onUploadProgress:(progressEvent)=>onProgressFn(progressEvent,data.get('file'))
data: data,
baseURL: import.meta.env.VITE_EPR_BASEURL,
isFormData: true,
onUploadProgress: (progressEvent) =>
onProgressFn(progressEvent, data.get('file')),
})
}
// 根据msg_id获取消息
export const detailGetRecordsContext = (data) => {
return request({
url: '/api/v1/talk/record/detail',
method: 'GET',
data,
})
}
// 获取自己消息已读回执列表
export const ServeReadConditionList = (data) => {
return request({
url: '/api/v1/talk/my-records/read/condition',
method: 'POST',
data,
})
}
// 获取消息已读未读详情
export const ServeMessageReadDetail = (data) => {
return request({
url: '/api/v1/talk/my-records/read/condition',
method: 'POST',
data,
})
}
// 语音转文字
export const ServeConvertText = (data) => {
return request({
url: '/api/v1/talk/message/voice-to-text',
method: 'POST',
data,
})
}

View File

@ -26,6 +26,22 @@ export const departmentV2TreeAll = (data) => {
data,
})
}
// 通讯录过滤测试部门
export const departmentV2TreeAll2 = (data) => {
return request({
url: '/api/v1/contact/department/v2/tree/all',
method: 'POST',
data,
})
}
// 查询是否有权限
export const userHasPermission = (data) => {
return request({
url: '/api/v1/contact/check/erp/rule',
method: 'POST',
data,
})
}
//获取指定部门下的所有岗位
export const v2TreePositionByDepartment = (data) => {
@ -45,6 +61,14 @@ export const userV2List = (data) => {
data,
})
}
// 过滤测试数据的erp用户接口
export const userV2List2 = (data) => {
return request({
url: '/api/v1/group/erp/users',
method: 'POST',
data,
})
}
export const groupCreateDept = (data) => {
return request({

View File

@ -45,3 +45,12 @@ export const ServeTalkDate = (data) => {
data,
})
}
//获取会话Id
export const ServeGetSessionId = (data) => {
return request({
url: '/api/v1/talk/session/getId',
method: 'POST',
data,
})
}

View File

@ -10,10 +10,9 @@ export const ServeGetUserSetting = (data) => {
export const userInfoApi = (data) => {
return request({
url: '/user/info',
url: '/api/v1/users/erp/info',
method: 'POST',
data,
baseURL:import.meta.env.VITE_EPR_BASEURL,
})
}

View File

@ -0,0 +1,89 @@
<template>
<div class="chat-app-error-page">
<div class="error-container">
<div class="error-icon">
<i class="iconfont icon-wifi"></i>
</div>
<div class="error-message">
<span>您的网络好像波动了一下~</span>
</div>
<div class="reload-button" @click="handleReload">
<span>重新加载</span>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref, computed, nextTick } from 'vue'
const handleReload = () => {
location.reload(true);
}
onMounted(() => {
if (typeof plus !== 'undefined') {
} else {
document.addEventListener('plusready', () => {
})
}
})
</script>
<style scoped lang="scss">
.chat-app-error-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-position: bottom center;
.error-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 32px;
background: #ffffff;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
max-width: 80%;
width: 320px;
.error-icon {
font-size: 48px;
color: #ff6b6b;
margin-bottom: 16px;
}
.error-message {
font-size: 16px;
color: #333333;
margin-bottom: 24px;
text-align: center;
}
.reload-button {
padding: 12px 32px;
background: $theme-primary;
color: #ffffff;
border-radius: 24px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
opacity: 0.9;
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
}
}
}
}
</style>

View File

@ -0,0 +1,81 @@
<script setup>
import { computed } from 'vue';
</script>
<template>
<div class="loader">
<p class="heading">加载中</p>
<div class="loading">
<div class="load"></div>
<div class="load"></div>
<div class="load"></div>
<div class="load"></div>
</div>
</div>
</template>
<style scoped lang="scss">
.loader {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-position: bottom center;
}
.heading {
color: black;
letter-spacing: 0.2em;
margin-bottom: 1em;
}
.loading {
display: flex;
width: 5em;
align-items: center;
justify-content: center;
}
.load {
width: 23px;
height: 3px;
background-color: limegreen;
animation: 1s move_5011 infinite;
border-radius: 5px;
margin: 0.1em;
}
.load:nth-child(1) {
animation-delay: 0.2s;
}
.load:nth-child(2) {
animation-delay: 0.4s;
}
.load:nth-child(3) {
animation-delay: 0.6s;
}
@keyframes move_5011 {
0% {
width: 0.2em;
}
25% {
width: 0.7em;
}
50% {
width: 1.5em;
}
100% {
width: 0.2em;
}
}
</style>

View File

@ -1,29 +1,95 @@
<script setup>
import TmImage from "@/uni_modules/tmui/components/tm-image/tm-image.vue";
import {useAuth} from "@/store/auth";
import { useClockIn } from "@/store/clockIn/index.js";
const {userInfo}=useAuth()
const {workingTimeInfoData,actionTypeData} = useClockIn()
</script>
<template>
<div class="flex-shrink-0 pl-[16rpx] pr-[40rpx] flex items-center rounded-[8rpx] w-[686rpx] h-[154rpx] bg-white">
<div class="rounded-full overflow-hidden w-[96rpx] h-[96rpx]">
<tm-image preview :width="96" :height="96" :src="userInfo.Avatar"></tm-image>
</div>
<div class="ml-[20rpx]">
<div class="flex items-center">
<div class="text-[32rpx] text-black">{{ userInfo.NickName }}</div>
<div class="mx-[14rpx] h-[30rpx] w-[1rpx] bg-[#F7F7F7]"></div>
<div class="w-[40rpx] h-[40rpx]">
<img v-if="actionTypeData.isWorkDay ===1" class="w-[40rpx] h-[40rpx]" src="@/static/image/clockIn/zu3275@3x.png" alt="">
<img v-else class="w-[40rpx] h-[40rpx]" src="@/static/image/clockIn/rest3275@2x.png" alt="">
</div>
</div>
<div class="mt-[5rpx] flex">
<div class="text-[24rpx] text-[#999999]">{{ workingTimeInfoData.WorkTimeTemplateName }}</div>
<div class="text-[#46299D] text-[24rpx]">考勤规则</div>
</div>
</div>
<slot name="right"></slot>
<div class="avatar-module" :style="[customStyle, { background: avatar ? '#fff' : '' }]">
<img :src="avatar" v-if="avatar" />
<span v-else :style="customTextStyle">{{ text_avatar }}</span>
</div>
</template>
<script setup>
//
import groupNormal from '@/static/image/chatList/groupNormal.png'
import groupDepartment from '@/static/image/chatList/groupDepartment.png'
import groupProject from '@/static/image/chatList/groupProject.png'
import groupCompany from '@/static/image/chatList/groupCompany.png'
import { computed, defineProps } from 'vue'
const props = defineProps({
mode: {
//1=2=
type: Number,
default: 0,
},
avatar: {
//
type: String,
default: '',
},
userName: {
//
type: String,
default: '',
},
groupType: {
//1=2=3=4=/
type: Number,
default: 0,
},
customStyle: {
//
type: Object,
default() {
return {}
},
},
customTextStyle: {
//
type: Object,
default() {
return {}
},
},
})
//
const avatar = computed(() => {
let avatar_img = props?.avatar
if (!avatar_img) {
if (props?.mode === 1) {
} else if (props?.mode === 2) {
if (props?.groupType === 1) {
avatar_img = groupNormal
} else if (props?.groupType === 2) {
avatar_img = groupDepartment
} else if (props?.groupType === 3) {
avatar_img = groupProject
} else if (props?.groupType === 4) {
avatar_img = groupCompany
}
}
}
return avatar_img
})
//
const text_avatar = computed(() => {
return props?.userName.length >= 2
? props?.userName.slice(-2)
: props?.userName
})
</script>
<style lang="scss" scoped>
.avatar-module {
border-radius: 50%;
overflow: hidden;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background: linear-gradient(to right, #674bbc, #46299d);
flex-shrink: 0;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
</style>

View File

@ -90,8 +90,6 @@ const onSubmit = () => {
ServeUploadAvatar(form).then((res) => {
if (res.code == 200) {
emit('success', res.data.avatar)
} else {
message.warning(res.message)
}
})
})

View File

@ -1,56 +1,63 @@
<template>
<div @click="onCheck" >
<div @click.stop="onCheck">
<tm-image :width="size" :height="size" :src="imageSrc"></tm-image>
</div>
</template>
<script setup>
import { defineProps,computed,defineEmits } from "vue";
import circle from "@/static/image/checkBox/circle@2x.png";
import zu6039 from "@/static/image/checkBox/zu6039@2x.png";
import zu6040 from "@/static/image/checkBox/zu6040@2x.png";
import { defineProps, computed, defineEmits } from 'vue'
import circle from '@/static/image/checkBox/circle@2x.png'
import zu6039 from '@/static/image/checkBox/zu6039@2x.png'
import zu6040 from '@/static/image/checkBox/zu6040@2x.png'
import checkBoxDisabled from '@/static/image/checkBox/disabled.png'
const props = defineProps({
modelValue: {
type: String,
default: "noChecked", // noChecked, checked, halfChecked
default: 'noChecked', // noChecked, checked, halfChecked, disabled
},
size: {
type: Number,
default: 28,
},
});
disabled: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['update:modelValue','change']);
const emit = defineEmits(['update:modelValue', 'change'])
const imageSrc = computed(() => {
switch (props.modelValue) {
case "noChecked":
return circle;
case "checked":
return zu6039;
case "halfChecked":
return zu6040;
if (props.disabled) {
return checkBoxDisabled
}
});
switch (props.modelValue) {
case 'noChecked':
return circle
case 'checked':
return zu6039
case 'halfChecked':
return zu6040
}
})
const onCheck = () => {
if (props.disabled) return
switch (props.modelValue) {
case "noChecked":
emit('update:modelValue', 'checked');
emit('change', 'checked');
break;
case "checked":
emit('update:modelValue', 'noChecked');
emit('change', 'noChecked');
break;
case "halfChecked":
emit('update:modelValue', 'checked');
emit('change', 'checked');
break;
case 'noChecked':
emit('update:modelValue', 'checked')
emit('change', 'checked')
break
case 'checked':
emit('update:modelValue', 'noChecked')
emit('change', 'noChecked')
break
case 'halfChecked':
emit('update:modelValue', 'checked')
emit('change', 'checked')
break
}
};
}
</script>
<style scoped lang='less'>
</style>
<style scoped lang="less"></style>

View File

@ -6,10 +6,21 @@
props.subBtnText ? 'apposition-btn-style' : '',
]"
>
<wd-button custom-class="custom-sub-btn-class" v-if="props.subBtnText">
<wd-button
custom-class="custom-sub-btn-class"
v-if="props.subBtnText"
@click="clickSubBtn"
>
{{ props.subBtnText }}
</wd-button>
<wd-button custom-class="custom-btn-class" @click="clickBtn">
<wd-button
custom-class="custom-btn-class"
@click="clickBtn"
:disabled="props?.disabled"
:class="[props?.disabled ? 'custom-btn-class-disabled' : '']"
:plain="props?.plain"
:loading="props?.isLoading"
>
{{ props.btnText }}
</wd-button>
</div>
@ -18,14 +29,22 @@
import { reactive } from 'vue'
import { defineProps, defineEmits } from 'vue'
const state = reactive({})
const emits = defineEmits(['clickBtn'])
const emits = defineEmits(['clickSubBtn', 'clickBtn'])
const props = defineProps({
isBottom: false, //
btnText: '', //
subBtnText: '', //
disabled: false, //
plain: false, //
isLoading: false, //
})
//
//
const clickSubBtn = () => {
emits('clickSubBtn')
}
//
const clickBtn = () => {
emits('clickBtn')
}
@ -38,28 +57,33 @@ const clickBtn = () => {
justify-content: center;
.custom-sub-btn-class {
background-color: #eee9f8;
padding: 18rpx 185rpx;
padding: 18rpx 0;
border-radius: 8rpx;
box-shadow: 0 6px 12px 2px rgba(188, 188, 188, 0.08);
font-size: 28rpx;
font-weight: 500;
line-height: 40rpx;
color: $theme-primary;
width: 426rpx;
height: 76rpx;
}
.custom-btn-class {
background-color: $theme-primary;
padding: 18rpx 185rpx;
padding: 18rpx 0;
border-radius: 8rpx;
box-shadow: 0 6px 12px 2px rgba(188, 188, 188, 0.08);
font-size: 28rpx;
font-weight: 500;
line-height: 40rpx;
width: 426rpx;
height: 76rpx;
}
}
.custom-btn-bottom {
width: 100%;
background-color: #fff;
padding: 14rpx 0 72rpx;
box-shadow: 0 1px 0 2px rgba(231, 231, 231, 1);
}
.apposition-btn-style {
padding: 14rpx 30rpx 72rpx;
@ -71,4 +95,8 @@ const clickBtn = () => {
padding: 18rpx 124rpx;
}
}
.custom-btn-class-disabled {
background-color: #e6e6e6 !important;
color: #bebebe !important;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div class="custom-search-input">
<div class="custom-input">
<tm-input
class="search-input"
placeholder="请输入…"
@ -12,14 +12,29 @@
@input="inputSearchText"
:showClear="true"
@clear="clearInput"
placeholderStyle="color:#BABABA"
:disabled="props?.disabled"
></tm-input>
<div v-if="props?.disabled" class="custom-input-disabled"></div>
</div>
</template>
<script setup>
import { defineProps, defineEmits, reactive, watch } from 'vue'
const props = defineProps({
searchText: String,
first_talk_record_infos: Object,
searchText: {
type: String,
default: ''
},
first_talk_record_infos: {
type: Object,
default(){
return {}
}
},
disabled: {
type: Boolean,
default: false
},
})
const state = reactive({
searchText: '', //
@ -44,15 +59,27 @@ const inputSearchText = (e) => {
}
</script>
<style scoped lang="scss">
.custom-search-input {
.custom-input {
width: 100%;
position: relative;
.search-input {
width: 100%;
}
::v-deep .noNvueBorder > .noNvueBorder > .noNvueBorder {
background: #f9f9fd !important;
border-radius: 8rpx !important;
}
.search-input::v-deep .tmicon-times-circle-fill::before {
content: '\e82a';
color: #d2d2d5;
}
.custom-input-disabled {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
}
</style>

View File

@ -0,0 +1,55 @@
<template>
<tm-navbar
:hideBack="props.hideBack"
:hideHome="props.hideHome"
:title="props.title"
:shadow="props.shadowNum"
:fontSize="34"
:showStatusBar="false"
>
<template #left>
<slot name="left"></slot>
</template>
<template #subTitle>
<slot name="subTitle"></slot>
</template>
<template #right>
<slot name="right"></slot>
</template>
</tm-navbar>
</template>
<script setup>
import tmNavbar from '@/uni_modules/tmui/components/tm-navbar/tm-navbar.vue'
import { defineProps } from 'vue'
const props = defineProps({
title: {
//
type: String,
default: '',
},
hideBack: {
//
type: Boolean,
default: false,
},
shadowNum: {
//
type: Number,
default: 1,
},
hideHome: {
type: Boolean,
default: false,
},
})
</script>
<style scoped lang="scss">
::v-deep .text-view {
text {
font-weight: 500;
line-height: 48rpx;
}
}
</style>

View File

@ -0,0 +1,73 @@
<!-- z-paging自定义的下拉刷新view -->
<template>
<view class="refresher-container">
<!-- 这里的图片请换成自己项目的图片 -->
<image
class="refresh-image rotating"
mode="aspectFit"
style="width:48rpx;height:48rpx;"
src="@/static/image/clockIn/loading@2x.png"
></image>
<text class="refresh-text">{{ refreshText }}</text>
</view>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
status: {
type: String,
default: ''
}
});
//
const refreshText = computed(() => {
const statusTextMap = {
'default': '哎呀,用点力继续下拉!',
'release-to-refresh': '拉疼我啦,松手刷新~~',
'loading': '正在努力刷新中...',
'complete': '刷新成功啦~'
};
return statusTextMap[props.status];
});
</script>
<style scoped>
.refresher-container {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
height: 150rpx;
background: rgba(249, 249, 253, 1);
flex-direction: column;
align-items: center;
justify-content: center;
}
.refresh-image {
width: 48rpx;
height: 48rpx;
margin-bottom: 10rpx;
margin-top: 10rpx;
}
.rotating {
animation: rotate 2s linear infinite;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.refresh-text {
font-size: 28rpx;
color: rgba(0, 0, 0, 0.4);
}
</style>

View File

@ -25,7 +25,15 @@
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="copy07"></tm-image>
<div>复制</div>
<div class="mt-1">复制</div>
</div>
<div
v-if="props.isShowConvertText"
@click="() => itemClick('convertText')"
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="copy07"></tm-image>
<div class="mt-1">转文字</div>
</div>
<div
@click="() => itemClick('multipleChoose')"
@ -36,7 +44,7 @@
:height="40"
:src="multipleChoices"
></tm-image>
<div>多选</div>
<div class="mt-1">多选</div>
</div>
<div
v-if="props.isShowCite"
@ -44,7 +52,7 @@
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="cite"></tm-image>
<div>引用</div>
<div class="mt-1">引用</div>
</div>
<div
v-if="props.isShowWithdraw"
@ -52,14 +60,14 @@
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="withdraw"></tm-image>
<div>撤回</div>
<div class="mt-1">撤回</div>
</div>
<div
@click="() => itemClick('actionDelete')"
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="delete07"></tm-image>
<div>删除</div>
<div class="mt-1">删除</div>
</div>
</div>
<div :style="data.iconStyle" class="icon"></div>
@ -78,8 +86,8 @@
//
// uniapp & vue
import { onLoad, onReady } from "@dcloudio/uni-app";
import { defineEmits, defineProps } from "vue";
import { onLoad, onReady } from '@dcloudio/uni-app'
import { defineEmits, defineProps } from 'vue'
import {
reactive,
ref,
@ -88,17 +96,17 @@ import {
nextTick,
getCurrentInstance,
onMounted,
onBeforeUnmount
} from "vue";
import copy07 from "@/static/image/chatList/copy07@2x.png";
import multipleChoices from "@/static/image/chatList/multipleChoices@2x.png";
import cite from "@/static/image/chatList/cite@2x.png";
import withdraw from "@/static/image/chatList/withdraw@2x.png";
import delete07 from "@/static/image/chatList/delete@2x.png";
onBeforeUnmount,
} from 'vue'
import copy07 from '@/static/image/chatList/copy07@2x.png'
import multipleChoices from '@/static/image/chatList/multipleChoices@2x.png'
import cite from '@/static/image/chatList/cite@2x.png'
import withdraw from '@/static/image/chatList/withdraw@2x.png'
import delete07 from '@/static/image/chatList/delete@2x.png'
// pinia
const systemInfo = uni.getSystemInfoSync();
const bubbleRef = ref(null);
const systemInfo = uni.getSystemInfoSync()
const bubbleRef = ref(null)
const props = defineProps({
isShowCopy: {
@ -113,109 +121,114 @@ const props = defineProps({
type: Boolean,
default: true,
},
});
isShowConvertText: {
//
type: Boolean,
default: false,
},
})
const emits = defineEmits(["clickMenu"]);
const emits = defineEmits(['clickMenu'])
/**
* @name 生成UUID
*/
const uuid = () => {
const reg = /[xy]/g;
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
const reg = /[xy]/g
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
.replace(reg, function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
v = c == 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
.replace(/-/g, "");
};
.replace(/-/g, '')
}
const popoverBoxId = `ID${uuid()}`;
const popoverContentId = `ID${uuid()}`;
const instance = getCurrentInstance();
const popoverBoxId = `ID${uuid()}`
const popoverContentId = `ID${uuid()}`
const instance = getCurrentInstance()
const data = reactive({
popoverShow: false,
defaultStyle: {},
showStyle: {
left: 0,
right: "",
transform: "",
right: '',
transform: '',
},
iconStyle: {
left: "",
right: "",
transform: "",
left: '',
right: '',
transform: '',
},
});
})
/**
* @name 获取DOM
*/
const getDom = (dom) => {
return new Promise((resolve, reject) => {
const query = uni.createSelectorQuery().in(instance);
let select = query.select(dom);
const query = uni.createSelectorQuery().in(instance)
let select = query.select(dom)
const boundingClientRect = select.boundingClientRect((data) => {
resolve(data);
});
boundingClientRect.exec();
});
};
resolve(data)
})
boundingClientRect.exec()
})
}
const itemClick = (item) => {
emits("clickMenu", item);
};
emits('clickMenu', item)
}
// 5
let pressDownTime = 0;
let time = null;
let pressDownTime = 0
let time = null
const onTouchstart = () => {
time && clearTimeout(time);
time = setTimeout(open, 500);
};
time && clearTimeout(time)
time = setTimeout(open, 500)
}
const onTouchend = () => {
time && clearTimeout(time);
};
time && clearTimeout(time)
}
const open = async () => {
let popoverContent = await getDom(`#${popoverContentId}`);
let popoverBox = await getDom(`#${popoverBoxId}`);
let popoverContent = await getDom(`#${popoverContentId}`)
let popoverBox = await getDom(`#${popoverBoxId}`)
//
let originX = popoverBox.width / 2;
let originX = popoverBox.width / 2
//
let isTop = popoverBox.top - 50 > popoverContent.height;
let isTop = popoverBox.top - 50 > popoverContent.height
//
data.defaultStyle = {
top: isTop ? "60rpx" : "auto",
bottom: !isTop ? "-20rpx" : "auto",
transform: `translateY(${isTop ? "-100%" : "100%"}) scale(.8)`,
};
top: isTop ? '60rpx' : 'auto',
bottom: !isTop ? '-20rpx' : 'auto',
transform: `translateY(${isTop ? '-100%' : '100%'}) scale(.8)`,
}
//
if (popoverBox.left > systemInfo.windowWidth - popoverBox.right) {
data.defaultStyle.right = 0;
data.defaultStyle.right = 0
//
data.defaultStyle["transform-origin"] = `${
data.defaultStyle['transform-origin'] = `${
popoverContent.width - originX
}px ${isTop ? "100%" : "0%"}`;
}px ${isTop ? '100%' : '0%'}`
} else {
data.defaultStyle.left = 0;
data.defaultStyle.left = 0
//
data.defaultStyle["transform-origin"] = `${originX}px ${
isTop ? "100%" : "0%"
}`;
data.defaultStyle['transform-origin'] = `${originX}px ${
isTop ? '100%' : '0%'
}`
}
data.showStyle = { ...data.defaultStyle };
data.showStyle = { ...data.defaultStyle }
// icon
let iconDefsultStyle = {
transform: `translate(0%, ${isTop ? "20%" : "-20%"})`,
"border-top-color": isTop ? "#333333" : "",
"border-bottom-color": !isTop ? "#333333" : "",
top: !isTop ? "-20rpx" : "auto",
bottom: isTop ? "-20rpx" : "auto",
};
transform: `translate(0%, ${isTop ? '20%' : '-20%'})`,
'border-top-color': isTop ? '#333333' : '',
'border-bottom-color': !isTop ? '#333333' : '',
top: !isTop ? '-20rpx' : 'auto',
bottom: isTop ? '-20rpx' : 'auto',
}
setTimeout(() => {
if (popoverBox.left > systemInfo.windowWidth - popoverBox.right) {
@ -224,55 +237,55 @@ const open = async () => {
...data.defaultStyle,
//
opacity: 1,
transform: `translateY(${isTop ? "-100%" : "100%"}) scale(1)`,
"pointer-events": "auto",
};
transform: `translateY(${isTop ? '-100%' : '100%'}) scale(1)`,
'pointer-events': 'auto',
}
data.iconStyle = {
right: `${originX}px`,
left: "auto",
left: 'auto',
...iconDefsultStyle,
};
}
} else {
data.showStyle = {
//
...data.defaultStyle,
//
opacity: 1,
transform: `translateY(${isTop ? "-100%" : "100%"}) scale(1)`,
"pointer-events": "auto",
};
transform: `translateY(${isTop ? '-100%' : '100%'}) scale(1)`,
'pointer-events': 'auto',
}
data.iconStyle = {
left: `${originX}px`,
right: "auto",
right: 'auto',
...iconDefsultStyle,
};
}
}
if (!data.popoverShow) data.popoverShow = true;
}, 200);
};
if (!data.popoverShow) data.popoverShow = true
}, 200)
}
const close = (time) => {
setTimeout(() => {
data.popoverShow = false;
data.showStyle = data.defaultStyle;
}, time || 0);
};
data.popoverShow = false
data.showStyle = data.defaultStyle
}, time || 0)
}
const handleClickOutside = (event) => {
if (data.popoverShow = false) {
if ((data.popoverShow = false)) {
return false
}
if (bubbleRef.value && !bubbleRef.value.contains(event.target)) {
close();
close()
}
};
}
onMounted(() => {
document.addEventListener('click', handleClickOutside);
});
document.addEventListener('click', handleClickOutside)
})
onBeforeUnmount(() => {
document.removeEventListener('click', handleClickOutside);
});
document.removeEventListener('click', handleClickOutside)
})
</script>
<style lang="scss" scoped>

View File

@ -1,91 +1,95 @@
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { PlayOne, PauseOne } from '@icon-park/vue-next'
import { ITalkRecordExtraAudio, ITalkRecord } from '@/types/chat'
import { ref, reactive, onMounted } from "vue";
import { PlayOne, PauseOne } from "@icon-park/vue-next";
import { ITalkRecordExtraAudio, ITalkRecord } from "@/types/chat";
defineProps<{
extra: ITalkRecordExtraAudio
data: ITalkRecord
maxWidth?: Boolean
}>()
const props = defineProps<{
extra: ITalkRecordExtraAudio;
data: ITalkRecord;
maxWidth?: Boolean;
}>();
const audioRef = ref()
const audioRef = ref();
const audioContext = ref<any>(null);
const durationDesc = ref('-')
const durationDesc = ref("-");
const state = reactive({
isAudioPlay: false,
progress: 0,
duration: 0,
currentTime: 0,
loading: true
})
loading: true,
});
onMounted(() => {
// 使uni-app
audioContext.value = uni.createInnerAudioContext();
audioContext.value.src = props.extra.url;
audioContext.value.onCanplay(() => {
state.duration = audioContext.value.duration;
durationDesc.value = formatTime(parseInt(audioContext.value.duration));
state.loading = false;
});
audioContext.value.onTimeUpdate(() => {
if (audioContext.value.duration == 0) {
state.progress = 0;
} else {
state.currentTime = audioContext.value.currentTime;
state.progress =
(audioContext.value.currentTime / audioContext.value.duration) * 100;
}
});
audioContext.value.onEnded(() => {
state.isAudioPlay = false;
state.progress = 0;
});
audioContext.value.onError((e) => {
console.log("音频播放异常===>", e);
});
});
const onPlay = () => {
if (state.isAudioPlay) {
audioRef.value.pause()
audioContext.value.pause();
} else {
audioRef.value.play()
audioContext.value.play();
}
state.isAudioPlay = !state.isAudioPlay
}
state.isAudioPlay = !state.isAudioPlay;
};
const onPlayEnd = () => {
state.isAudioPlay = false
state.progress = 0
}
const onCanplay = () => {
state.duration = audioRef.value.duration
durationDesc.value = formatTime(parseInt(audioRef.value.duration))
state.loading = false
}
const onError = (e: any) => {
console.log('音频播放异常===>', e)
}
const onTimeUpdate = () => {
let audio = audioRef.value
if (audio.duration == 0) {
state.progress = 0
} else {
state.currentTime = audio.currentTime
state.progress = (audio.currentTime / audio.duration) * 100
}
}
state.isAudioPlay = false;
state.progress = 0;
};
const formatTime = (value: number = 0) => {
if (value == 0) {
return '-'
return "-";
}
const minutes = Math.floor(value / 60)
let seconds = value
const minutes = Math.floor(value / 60);
let seconds = value;
if (minutes > 0) {
seconds = Math.floor(value - minutes * 60)
seconds = Math.floor(value - minutes * 60);
}
return `${minutes}'${seconds}"`
}
return `${minutes}'${seconds}"`;
};
</script>
<template>
<div class="im-message-audio">
<audio
ref="audioRef"
preload="auto"
type="audio/mp3,audio/wav"
:src="extra.url"
@timeupdate="onTimeUpdate"
@ended="onPlayEnd"
@canplay="onCanplay"
@error="onError"
/>
<div class="play">
<div class="btn pointer" @click.stop="onPlay">
<n-icon :size="18" :component="state.isAudioPlay ? PauseOne : PlayOne" />
<n-icon
:size="18"
:component="state.isAudioPlay ? PauseOne : PlayOne"
/>
</div>
</div>
<div class="desc">
@ -241,7 +245,7 @@ const formatTime = (value: number = 0) => {
}
}
html[theme-mode='dark'] {
html[theme-mode="dark"] {
.im-message-audio {
--audio-bg-color: #2c2c32;
--audio-btn-bg-color: rgb(78, 75, 75);

View File

@ -1,33 +1,32 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { NCode } from 'naive-ui'
import { Copy, Stretching } from '@icon-park/vue-next'
import { clipboard } from '@/utils/common'
import { useUtil } from '@/hooks'
import { ITalkRecordExtraCode, ITalkRecord } from '@/types/chat'
import { ref } from "vue";
import { NCode, useMessage } from "naive-ui";
import { Copy, Stretching } from "@icon-park/vue-next";
import { clipboard } from "@/utils/common";
import { ITalkRecordExtraCode, ITalkRecord } from "@/types/chat";
const props = defineProps<{
extra: ITalkRecordExtraCode
data: ITalkRecord
maxWidth?: Boolean
}>()
extra: ITalkRecordExtraCode;
data: ITalkRecord;
maxWidth?: Boolean;
}>();
const { useMessage } = useUtil()
const lineMumber = props.extra.code.trim().split('\n').length
const full = ref(false)
const message = useMessage();
const lineMumber = props.extra.code.trim().split("\n").length;
const full = ref(false);
const onClipboard = () => {
clipboard(props.extra.code, () => {
useMessage.success('复制成功')
})
}
message.success("复制成功");
});
};
</script>
<template>
<section
class="im-message-code el-container is-vertical"
:class="{
maxwidth: maxWidth,
full: full
full: full,
}"
>
<header class="el-header tools">
@ -37,9 +36,16 @@ const onClipboard = () => {
<n-icon class="icon" :component="Copy" @click="onClipboard" />
</p>
</header>
<main class="el-main me-scrollbar me-scrollbar-thumb" :lineMumber="lineMumber">
<main
class="el-main me-scrollbar me-scrollbar-thumb"
:lineMumber="lineMumber"
>
<n-code :language="extra.lang" :code="extra.code" show-line-numbers />
<div class="el-footer mask pointer" v-show="lineMumber > 20" @click="full = !full">
<div
class="el-footer mask pointer"
v-show="lineMumber > 20"
@click="full = !full"
>
查看更多
</div>
</main>
@ -110,17 +116,25 @@ const onClipboard = () => {
position: sticky;
bottom: 0;
left: 0;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 100%
);
color: var(--im-text-color);
}
}
html[theme-mode='dark'] {
html[theme-mode="dark"] {
.im-message-code {
background: var(--im-message-bg-color);
.mask {
background: linear-gradient(to bottom, transparent 0%, var(--im-bg-color) 100%);
background: linear-gradient(
to bottom,
transparent 0%,
var(--im-bg-color) 100%
);
}
}
}

View File

@ -25,69 +25,130 @@ const getFileTypeIMG = computed(() => {
let objT = {
finishedImg: '',
blankImg: '',
progressColor: ''
};
progressColor: '',
}
switch (suffix) {
case 'pdf':
objT.finishedImg = filePaperPDF
objT.blankImg = filePaperPDFBlank
objT.progressColor = '#DE4E4E'
break;
break
case 'doc':
case 'docx':
objT.finishedImg = filePaperWord
objT.blankImg = filePaperWordBlank
objT.progressColor = '#2750B2'
break;
break
case 'xls':
case 'xlsx':
objT.finishedImg = filePaperExcel
objT.blankImg = filePaperExcelBlank
objT.progressColor = '#3C7F4B'
break;
break
case 'ppt':
case 'pptx':
objT.finishedImg = filePaperPPT
objT.blankImg = filePaperPPTBlank
objT.progressColor = '#B74B2B'
break;
break
default:
objT.finishedImg = filePaperOther
objT.blankImg = filePaperOtherBlank
objT.progressColor = '#747474'
objT.progressColor = '#46299d'
}
return objT
})
const previewPDF = () => {
if (typeof plus !== 'undefined') {
downloadAndOpenFile()
} else {
document.addEventListener('plusready', () => {
downloadAndOpenFile()
})
}
}
const downloadAndOpenFile = () => {
uni.showLoading({ title: '加载中...', mask: true })
const downloadUrl = props?.extra?.path
if (!downloadUrl) {
uni.hideLoading()
uni.showToast({ title: '文件路径无效', icon: 'none' })
return
}
const options = {
filename: '_doc/downloads/', //
}
const dtask = plus.downloader.createDownload(downloadUrl, options, function (
d,
status,
) {
if (status === 200) {
uni.hideLoading()
const filePath = d.filename
if (filePath) {
plus.runtime.openFile(filePath, {}, function () {})
} else {
uni.showToast({ title: '文件路径无效', icon: 'none' })
}
} else {
uni.hideLoading()
}
})
dtask.start()
}
</script>
<template>
<section
class="file-message"
@click="previewPDF"
:class="{ left: data.float === 'left', right: data.float === 'right' }"
>
<div class="flex justify-between">
<div class="w-[228rpx] text-[32rpx] text-[#1A1A1A] h-[88rpx] leading-[44rpx] textEllipsis">
<div
class="w-[228rpx] text-[32rpx] text-[#1A1A1A] h-[88rpx] leading-[44rpx] textEllipsis file_name"
>
{{ extra.name }}
</div>
<div v-if="data.uploadStatus === 2 || !data.uploadStatus" class="w-[95rpx]">
<tm-image :width="95" :height="95" :src="getFileTypeIMG.finishedImg"></tm-image>
<div
v-if="data.uploadStatus === 2 || !data.uploadStatus"
class="w-[95rpx]"
>
<tm-image
:width="95"
:height="95"
:src="getFileTypeIMG.finishedImg"
></tm-image>
</div>
<div v-if="data.uploadStatus === 1 || data.uploadStatus === 3" class="w-[95rpx]">
<tm-image :width="95" :height="95" :src="getFileTypeIMG.blankImg"></tm-image>
<wd-circle
customClass="circleProgress"
:modelValue="data.uploadCurrent"
layerColor="#E3E3E3"
:color="getFileTypeIMG.progressColor"
:strokeWidth="3"
:size="20"
></wd-circle>
<div
v-if="data.uploadStatus === 1 || data.uploadStatus === 3"
class="w-[95rpx]"
>
<tm-image
:width="95"
:height="95"
:src="getFileTypeIMG.blankImg"
></tm-image>
<wd-circle v-if="data.uploadStatus === 1"
customClass="circleProgress"
:modelValue="data.uploadCurrent"
layerColor="#E3E3E3"
:color="getFileTypeIMG.progressColor"
:strokeWidth="3"
:size="20"
></wd-circle>
<div class="upload-failed" v-if="data.uploadStatus === 3">
<tm-icon :font-size="20" name="tmicon-times" color="#fff"></tm-icon>
</div>
</div>
</div>
<div class="divider mt-[28rpx]"></div>
<div class="text-[24rpx] text-[#747474] mt-[10rpx]">{{ fileFormatSize(extra.size) }}</div>
<div class="text-[24rpx] text-[#747474] mt-[10rpx]">
{{ fileFormatSize(extra.size) }}
</div>
<!-- <div class="main">
<div class="ext">{{ getFileNameSuffix(extra.name) }}</div>
<div class="file-box">
@ -124,6 +185,11 @@ const getFileTypeIMG = computed(() => {
border-radius: 16rpx 0 16rpx 16rpx;
}
.file_name {
word-break: break-all; /* 在任意字符间断行 */
word-wrap: break-word; /* 允许长单词换行到下一行 */
}
.main {
height: 45px;
display: flex;
@ -212,7 +278,7 @@ const getFileTypeIMG = computed(() => {
}
.divider {
background-color: #E7E7E7;
background-color: #e7e7e7;
height: 1rpx;
}
@ -225,4 +291,20 @@ const getFileTypeIMG = computed(() => {
width: 40rpx !important;
height: 40rpx !important;
}
.upload-failed {
position: absolute;
top: 120rpx;
right: 52rpx;
transform: translate(-50%, -50%);
z-index: 1;
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
color: #ff4d4f;
background: #ff4d4f;
border-radius: 50%;
}
</style>

View File

@ -12,9 +12,15 @@ const props = defineProps<{
const isShowRecord = ref(false)
const title = computed(() => {
return [...new Set(props.extra.records.map((v) => v.nickname))].join('、')
const uniqueNames = [...new Set(props.extra.records.map(v => v.nickname))];
if (uniqueNames.length <= 2) {
return uniqueNames.join('和');
} else {
return uniqueNames.slice(0, 2).join('和') + '等';
}
// return [...new Set(props.extra.records.map((v) => v.nickname))].join('')
})
console.log(props.extra.records)
const onClick = () => {
// isShowRecord.value = true
uni.navigateTo({
@ -24,7 +30,7 @@ const onClick = () => {
</script>
<template>
<section class="im-message-forward pointer" :class="{ left: data.float === 'left' }" @click="onClick">
<div class="title">{{ title }} 的会话记录</div>
<div class="title">{{ extra.forward_name || title}}的会话记录</div>
<div class="list" v-for="(record, index) in extra.records" :key="index">
<p>
<span>{{ record.nickname }}: </span>
@ -59,13 +65,14 @@ const onClick = () => {
}
.title {
height: 44rpx;
max-height: 88rpx;
line-height: 44rpx;
font-size: 32rpx;
color: #1A1A1A;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 400;
margin-bottom: 8rpx;
}

View File

@ -20,7 +20,8 @@ let show = ref(false)
}"
>
<div class="title">
<n-tag :bordered="false" size="small" type="primary">群公告</n-tag>
<!-- <n-tag :bordered="false" size="small" type="primary">群公告</n-tag> -->
<text>群公告</text>
<!-- {{ extra.title }} -->
</div>
<div class="title" :class="{ ellipsis: !show }">

View File

@ -12,40 +12,40 @@ const img = computed(() => {
// console.log(props.extra);
let info = {
width: 0,
height: 0
height: 0,
}
if (props.extra.url.includes('blob:http://')) {
info = {
width: props.extra.width,
height: props.extra.height
height: props.extra.height,
}
}else {
} else {
info = getImageInfo(props.extra.url)
}
if (info.width == 0 || info.height == 0) {
return {
width: 450,
height: 298
height: 298,
}
}
if(info.width<300){
if (info.width < 300) {
return {
width: 300,
height: info.height / (info.width / 300)
height: info.height / (info.width / 300),
}
}
if (info.width < 350) {
return {
width: info.width,
height: info.height
height: info.height,
}
}
return {
width: 350,
height: info.height / (info.width / 350)
height: info.height / (info.width / 350),
}
})
</script>
@ -54,13 +54,30 @@ const img = computed(() => {
class="im-message-image"
:class="{
left: data.float === 'left',
right: data.float === 'right'
right: data.float === 'right',
}"
>
<div class="image-container">
<tm-image preview :width="img.width" :height="img.height" :src="extra.url" />
<wd-circle custom-class="circleProgress" v-if="props.data.uploadCurrent && props.data.uploadCurrent<100" v-model="props.data.uploadCurrent" color="#ffffff" layer-color="#E3E3E3"></wd-circle>
</div>
<div class="image-container">
<div class="relative">
<tm-image
preview
:width="img.width"
:height="img.height"
:src="extra.url"
model="aspectFill"
/>
<wd-circle
custom-class="circleProgress"
v-if="data.uploadStatus === 1"
v-model="props.data.uploadCurrent"
color="#46299d"
layer-color="#E3E3E3"
></wd-circle>
<div class="upload-failed" v-if="data.uploadStatus === 3">
<tm-icon :font-size="20" name="tmicon-times" color="#fff"></tm-icon>
</div>
</div>
</div>
</section>
</template>
<style lang="less" scoped>
@ -79,12 +96,12 @@ const img = computed(() => {
}
&.right {
background-color: #46299D;
background-color: #46299d;
border-radius: 16rpx 0 16rpx 16rpx;
}
}
.image-container {
position: relative;
position: relative;
.circleProgress {
position: absolute;
@ -94,4 +111,18 @@ const img = computed(() => {
z-index: 1;
}
}
.upload-failed {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
background: #ff4d4f;
border-radius: 50%;
}
</style>

View File

@ -0,0 +1,95 @@
<!-- 完全复制的textMessage组件没有用处仅兜底真有14类型时的场景后续会单独做制作分享卡片功能到时再根据分享卡片样式重做本页面 -->
<!-- 完全复制的textMessage组件没有用处仅兜底真有14类型时的场景后续会单独做制作分享卡片功能到时再根据分享卡片样式重做本页面 -->
<!-- 完全复制的textMessage组件没有用处仅兜底真有14类型时的场景后续会单独做制作分享卡片功能到时再根据分享卡片样式重做本页面 -->
<!-- 完全复制的textMessage组件没有用处仅兜底真有14类型时的场景后续会单独做制作分享卡片功能到时再根据分享卡片样式重做本页面 -->
<!-- 完全复制的textMessage组件没有用处仅兜底真有14类型时的场景后续会单独做制作分享卡片功能到时再根据分享卡片样式重做本页面 -->
<!-- 完全复制的textMessage组件没有用处仅兜底真有14类型时的场景后续会单独做制作分享卡片功能到时再根据分享卡片样式重做本页面 -->
<!-- 完全复制的textMessage组件没有用处仅兜底真有14类型时的场景后续会单独做制作分享卡片功能到时再根据分享卡片样式重做本页面 -->
<!-- 完全复制的textMessage组件没有用处仅兜底真有14类型时的场景后续会单独做制作分享卡片功能到时再根据分享卡片样式重做本页面 -->
<!-- 完全复制的textMessage组件没有用处仅兜底真有14类型时的场景后续会单独做制作分享卡片功能到时再根据分享卡片样式重做本页面 -->
<!-- 完全复制的textMessage组件没有用处仅兜底真有14类型时的场景后续会单独做制作分享卡片功能到时再根据分享卡片样式重做本页面 -->
<script lang="ts" setup>
import { textReplaceEmoji } from '@/utils/emojis'
import { textReplaceLink, textReplaceMention } from '@/utils/strings'
import { ITalkRecordExtraText, ITalkRecord } from '@/types/chat'
const props = defineProps<{
extra: ITalkRecordExtraText
data: ITalkRecord
maxWidth?: boolean
source?: 'panel' | 'forward' | 'history'
}>()
const float = props.data.float
let textContent = props.extra?.content || ''
textContent = textReplaceLink(textContent)
if (props.data.talk_type == 2) {
textContent = textReplaceMention(textContent, '#1890ff')
}
textContent = textReplaceEmoji(textContent)
</script>
<template>
<div
class="im-message-text"
:class="{
left: float == 'left',
right: float == 'right',
maxwidth: maxWidth,
'radius-reset': source != 'panel',
}"
>
<pre v-html="textContent" />
</div>
</template>
<style lang="less" scoped>
.im-message-text {
min-width: 40rpx;
min-height: 40rpx;
padding: 22rpx 30rpx;
color: #1a1a1a;
background: #ffffff;
border-radius: 0 16rpx 16rpx 16rpx;
&.right {
background-color: #46299d;
color: #ffffff;
border-radius: 16rpx 0 16rpx 16rpx;
}
&.maxwidth {
max-width: 486rpx;
}
&.radius-reset {
border-radius: 0;
}
pre {
white-space: pre-wrap;
overflow: hidden;
word-break: break-word;
word-wrap: break-word;
font-size: 32rpx;
font-family: 'PingFang SC', 'Microsoft YaHei', 'Alibaba PuHuiTi 2.0 45';
line-height: 44rpx;
:deep(.emoji) {
vertical-align: text-bottom;
margin: 0 10rpx;
width: 44rpx;
height: 44rpx;
}
:deep(a) {
color: #2196f3;
text-decoration: revert;
}
}
}
</style>

View File

@ -4,34 +4,97 @@ import { formatTime } from '@/utils/datetime'
defineProps({
login_uid: {
type: Number,
default: 0
default: 0,
},
user_id: {
type: Number,
default: 0
default: 0,
},
talk_type: {
type: Number,
default: 0
default: 0,
},
nickname: {
type: String,
default: ''
default: '',
},
datetime: {
type: String,
default: ''
default: '',
},
msg_id: {
type: String,
default: '',
},
revokeInfo: {
type: Object,
default() {
return {}
},
},
extra:{
type: String,
default: '',
}
})
</script>
<template>
<div class="im-message-revoke">
<div class="content">
<span v-if="login_uid == user_id"> 你撤回了一条消息 | {{ formatTime(datetime) }} </span>
<span v-else-if="talk_type == 1"> 对方撤回了一条消息 | {{ formatTime(datetime) }} </span>
<span v-else>
"{{ nickname }}" 撤回了一条消息 |
<div class="content" v-if="JSON.stringify(revokeInfo) !== '{}'">
<span v-if="talk_type === 1 && login_uid === revokeInfo.withdraw_id">
你撤回了一条消息 | {{ formatTime(datetime) }}
<slot></slot>
<!-- 添加插槽用于放置重新编辑按钮 -->
</span>
<span v-if="talk_type === 1 && login_uid !== revokeInfo.withdraw_id">
{{revokeInfo.withdraw_name}}撤回了一条消息 | {{ formatTime(datetime) }}
</span>
<span v-if="talk_type === 2 && login_uid === revokeInfo.withdraw_id && login_uid === revokeInfo.retracted_id">
你撤回了一条消息 |
{{ formatTime(datetime) }}
<slot></slot>
</span>
<span v-if="talk_type === 2 && login_uid === revokeInfo.withdraw_id && login_uid !== revokeInfo.retracted_id">
你撤回了{{revokeInfo.retracted_name}}一条消息 |
{{ formatTime(datetime) }}
</span>
<span v-if="talk_type === 2 && login_uid !== revokeInfo.withdraw_id && revokeInfo.withdraw_id === revokeInfo.retracted_id">
{{revokeInfo.withdraw_name}}撤回了一条消息 |
{{ formatTime(datetime) }}
</span>
<span v-if="talk_type === 2 && login_uid !== revokeInfo.withdraw_id && login_uid === revokeInfo.retracted_id && revokeInfo.withdraw_id !== revokeInfo.retracted_id">
{{revokeInfo.withdraw_name}}撤回了你一条消息 |
{{ formatTime(datetime) }}
</span>
<span v-if="talk_type === 2 && login_uid !== revokeInfo.withdraw_id && login_uid !== revokeInfo.retracted_id && revokeInfo.withdraw_id !== revokeInfo.retracted_id">
{{revokeInfo.withdraw_name}}撤回了{{revokeInfo.retracted_name}}一条消息 |
{{ formatTime(datetime) }}
</span>
<!-- <span v-if="login_uid == user_idA"> 你撤回B了一条消息 | {{ formatTime(datetime) }} </span>
<span v-else-if="login_uid == user_idB"> A撤回你了一条消息 | {{ formatTime(datetime) }} </span>
<span v-else> A撤回B了一条消息 | {{ formatTime(datetime) }} </span> -->
</div>
<div class="content" v-if="JSON.stringify(revokeInfo) === '{}'">
<span v-if="talk_type === 1 && login_uid === user_id">
你撤回了一条消息 | {{ formatTime(datetime) }}
<slot></slot>
<!-- 添加插槽用于放置重新编辑按钮 -->
</span>
<span v-if="talk_type === 1 && login_uid !== user_id">
{{nickname}}撤回了一条消息 | {{ formatTime(datetime) }}
</span>
<span v-if="talk_type === 2 && !extra && login_uid === user_id">
你撤回了一条消息 | {{ formatTime(datetime) }}
<slot></slot>
<!-- 添加插槽用于放置重新编辑按钮 -->
</span>
<span v-if="talk_type === 2 && !extra && login_uid !== user_id">
{{nickname}}撤回了一条消息 | {{ formatTime(datetime) }}
</span>
<span v-if="talk_type === 2 && extra">
{{extra}} | {{ formatTime(datetime) }}
</span>
</div>
</div>

View File

@ -2,6 +2,7 @@
import { textReplaceEmoji } from '@/utils/emojis'
import { textReplaceLink, textReplaceMention } from '@/utils/strings'
import { ITalkRecordExtraText, ITalkRecord } from '@/types/chat'
import { computed } from 'vue'
const props = defineProps<{
extra: ITalkRecordExtraText
@ -12,15 +13,14 @@ const props = defineProps<{
const float = props.data.float
let textContent = props.extra?.content || ''
textContent = textReplaceLink(textContent)
if (props.data.talk_type == 2) {
textContent = textReplaceMention(textContent, '#1890ff')
}
textContent = textReplaceEmoji(textContent)
const textContent = computed(() => {
let text = props.extra?.content || ''
// text = textReplaceLink(text)
if (props.data.talk_type == 2) {
text = textReplaceMention(text, '#1890ff')
}
return textReplaceEmoji(text)
})
</script>
<template>

View File

@ -1,8 +1,8 @@
<script lang="ts" setup>
import { ref, nextTick, getCurrentInstance, computed, onMounted } from 'vue'
import { getImageInfo } from '@/utils/functions'
import playCircle from "@/static/image/chatList/playCircle@2x.png";
import { useStatus } from "@/store/status";
import playCircle from '@/static/image/chatList/playCircle@2x.png'
import { useStatus } from '@/store/status'
const { statusBarHeight } = useStatus()
const instance = getCurrentInstance()
@ -20,12 +20,12 @@ const open = ref(false)
const img = computed(() => {
let info = {
width: 0,
height: 0
height: 0,
}
if (props.extra.url.includes('blob:http://')) {
info = {
width: props.extra.width,
height: props.extra.height
height: props.extra.height,
}
} else {
info = getImageInfo(props.extra.url)
@ -34,26 +34,26 @@ const img = computed(() => {
if (info.width == 0 || info.height == 0) {
return {
width: 450,
height: 298
height: 298,
}
}
if (info.width < 300) {
return {
width: 300,
height: info.height / (info.width / 300)
height: info.height / (info.width / 300),
}
}
if (info.width < 350) {
return {
width: info.width,
height: info.height
height: info.height,
}
}
return {
width: 350,
height: info.height / (info.width / 350)
height: info.height / (info.width / 350),
}
})
@ -67,57 +67,98 @@ const fullscreenchange = (e) => {
/* 视频播放 获取第一帧 */
const canplay = (e) => {
console.log('Video can play:', e);
console.log('Video can play:', e)
if (e.target) {
setTimeout(() => {
e.target.pause();
}, 200);
e.target.pause()
}, 200)
}
};
}
async function onPlay() {
videoContext.value = uni.createVideoContext(props.extra.url, instance);
videoContext.value.requestFullScreen({ direction: 2 });
videoContext.value.play()
open.value = true
await nextTick()
videoContext.value = uni.createVideoContext(props.extra.url, instance)
setTimeout(() => {
//
videoContext.value.requestFullScreen({ direction: 2 })
//
setTimeout(() => {
videoContext.value.play()
}, 100)
}, 200)
}
onMounted(() => {
videoRef.value = uni.createVideoContext(props.data.msg_id);
videoRef.value = uni.createVideoContext(props.data.msg_id)
videoRef.value.play()
setTimeout(() => {
videoRef.value.pause()
}, 200);
}, 200)
})
</script>
<template>
<section class="im-message-video" :class="{ left: data.float === 'left' }" @click="onPlay">
<div class="coverVideo" :style="{
width: img.width + 'rpx',
height: img.height + 'rpx'
}" v-if="props.extra.url.includes('blob:http://')">
<video :id="data.msg_id" :autoplay="false" disablepictureinpicture muted :src="props.extra.url" width="100%"
height="100%" playsinline preload="auto" controls="false" x5-playsinline
webkit-playsinline style="object-fit: cover; pointer-events: none;">
</video>
<section
class="im-message-video"
:class="{ left: data.float === 'left' }"
@click="onPlay"
>
<div
class="coverVideo"
:style="{
width: img.width + 'rpx',
height: img.height + 'rpx',
}"
v-if="props.extra.url.includes('blob:http://')"
>
<video
:id="data.msg_id"
:autoplay="false"
disablepictureinpicture
muted
:src="props.extra.url"
width="100%"
height="100%"
playsinline
preload="auto"
controls="false"
x5-playsinline
webkit-playsinline
style="object-fit: cover; pointer-events: none;"
></video>
</div>
<wd-img v-else :width="`${img.width}rpx`" :height="`${img.height}rpx`" :src="data.extra.cover" />
<div v-if="data.uploadStatus === 2 || !data.uploadStatus" class="btn-video" :style="{
width: img.width + 'rpx',
height: img.height + 'rpx'
}">
<wd-img
v-else
:width="`${img.width}rpx`"
:height="`${img.height}rpx`"
:src="data.extra.cover"
/>
<div
v-if="data.uploadStatus === 2 || !data.uploadStatus"
class="btn-video"
:style="{
width: img.width + 'rpx',
height: img.height + 'rpx',
}"
>
<tm-image :src="playCircle" :width="80" :height="80" />
</div>
<div v-else class="btn-video" :style="{
width: img.width + 'rpx',
height: img.height + 'rpx'
}" >
<div
v-else
class="btn-video"
:style="{
width: img.width + 'rpx',
height: img.height + 'rpx',
}"
>
<wd-circle
v-if="data.uploadStatus === 1"
v-model="props.data.uploadCurrent"
customClass="circleProgress"
layerColor="#E3E3E3"
color="#FFFFFF"
color="#46299d"
layer-color="#E3E3E3"
:strokeWidth="6"
:size="40"
></wd-circle>
@ -131,18 +172,32 @@ onMounted(() => {
:width="70"
:percent="props.data.uploadCurrent">
</tm-progress> -->
</div>
<div v-show="open">
<video :src="props.extra.url" controls @fullscreenchange="fullscreenchange" :id="props.extra.url">
</video>
<div class="upload-failed" v-if="data.uploadStatus === 3">
<tm-icon :font-size="20" name="tmicon-times" color="#fff"></tm-icon>
</div>
</div>
</section>
<teleport to="body">
<div v-show="open" class="video-container">
<video
:src="props.extra.url"
controls
@fullscreenchange="fullscreenchange"
:id="props.extra.url"
playsinline
webkit-playsinline
x5-playsinline
class="fullscreen-video"
></video>
</div>
</teleport>
</template>
<style lang="less" scoped>
.im-message-video {
overflow: hidden;
padding: 20rpx 18rpx;
background: #46299D;
background: #46299d;
min-width: 30rpx;
min-height: 30rpx;
display: inline-flex;
@ -200,10 +255,43 @@ onMounted(() => {
:deep(.uni-video-bar) {
display: none;
}
}
.circleProgress {
width: 80rpx !important;
height: 80rpx !important;
}
.video-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: #000;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.fullscreen-video {
width: 100%;
height: 100%;
object-fit: contain;
}
.upload-failed {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
background: #ff4d4f;
border-radius: 50%;
}
</style>

View File

@ -0,0 +1,25 @@
<script setup>
import './sys-message.less'
import { useInject } from '@/hooks'
defineProps({
extra: Object,
data: Object
})
const { showUserInfoModal } = useInject()
</script>
<template>
<div class="im-message-sys-text">
<div class="sys-text">
<template v-for="(user, index) in extra.members" :key="index">
<a @click="showUserInfoModal(user.user_id)">{{ user.nickname }}</a>
<em v-show="index < extra.members.length - 1"></em>
</template>
<span>已成为管理员</span>
</div>
</div>
</template>

View File

@ -0,0 +1,19 @@
<script setup>
import './sys-message.less'
import { useInject } from '@/hooks'
const { showUserInfoModal } = useInject()
defineProps({
extra: Object,
data: Object
})
</script>
<template>
<div class="im-message-sys-text">
<div class="sys-text">
<span>{{ extra.content }}</span>
</div>
</div>
</template>

View File

@ -0,0 +1,25 @@
<script setup>
import './sys-message.less'
import { useInject } from '@/hooks'
const { showUserInfoModal } = useInject()
defineProps({
extra: Object,
data: Object
})
</script>
<template>
<div class="im-message-sys-text">
<div class="sys-text">
<a @click="showUserInfoModal(data.user_id)">
<!-- {{ data.nickname }} -->
管理员
</a>
<!-- <span>修改群名为</span>
<span>"{{ extra.group_name }}"</span> -->
<span>修改了群信息</span>
</div>
</div>
</template>

View File

@ -24,7 +24,7 @@ const { showUserInfoModal } = useInject()
<em v-show="index < extra.members.length - 1"></em>
</template>
<span>出群聊</span>
<span>出群聊</span>
</div>
</div>
</template>

View File

@ -0,0 +1,23 @@
<script setup>
import './sys-message.less'
import { useInject } from '@/hooks'
const { showUserInfoModal } = useInject()
defineProps({
extra: Object,
data: Object,
})
</script>
<template>
<div class="im-message-sys-text">
<div class="sys-text">
<template v-for="(user, index) in extra?.members" :key="index">
<a @click="showUserInfoModal(user.user_id)">{{ user.nickname }}</a>
<em v-show="index < extra.members.length - 1"></em>
</template>
<span>已离开此群</span>
</div>
</div>
</template>

View File

@ -1,79 +1,114 @@
<script setup>
import {ref,nextTick} from 'vue'
import WdPopup from "@/uni_modules/wot-design-uni/components/wd-popup/wd-popup.vue";
import TmButton from "@/uni_modules/tmui/components/tm-button/tm-button.vue";
import {language} from "@/uni_modules/tmui/tool/lib/language"
const confirmState=ref(false)
const cancel=ref(true)
let onConfirm=null
let onCancel=null
const confirm=ref(true)
const contentText=ref('')
const imageRef=ref('')
const confirmLabel=ref('确定')
const cancelLabel=ref('取消')
const confirmC=ref('#46299D')
const cancelC=ref('#1A1A1A')
const sendCancel=()=>{
confirmState.value=false
if (typeof onCancel==='function'){
import { ref, nextTick } from 'vue'
import WdPopup from '@/uni_modules/wot-design-uni/components/wd-popup/wd-popup.vue'
import TmButton from '@/uni_modules/tmui/components/tm-button/tm-button.vue'
import { language } from '@/uni_modules/tmui/tool/lib/language'
const confirmState = ref(false)
const cancel = ref(true)
let onConfirm = null
let onCancel = null
const confirm = ref(true)
const contentText = ref('')
const subContentText = ref('')
const subContentC = ref('#000')
const imageRef = ref('')
const confirmLabel = ref('确定')
const cancelLabel = ref('取消')
const confirmC = ref('#46299D')
const cancelC = ref('#1A1A1A')
const sendCancel = () => {
confirmState.value = false
if (typeof onCancel === 'function') {
onCancel()
}
}
const sendConfirm=()=>{
confirmState.value=false
if (typeof onConfirm==='function'){
const sendConfirm = () => {
confirmState.value = false
if (typeof onConfirm === 'function') {
onConfirm()
}
}
const showConfirm=({content,image,onConfirm:confirm,onCancel:cancel,confirmText,cancelText,confirmColor,cancelColor})=>{
confirmState.value=true
contentText.value=content
imageRef.value = image?image:''
onConfirm=confirm
onCancel=cancel
const showConfirm = ({
content,
image,
onConfirm: confirm,
onCancel: cancel,
confirmText,
cancelText,
confirmColor,
cancelColor,
subContent,
subContentColor,
}) => {
confirmState.value = true
contentText.value = content
imageRef.value = image ? image : ''
onConfirm = confirm
onCancel = cancel
confirmLabel.value = confirmText || confirmLabel.value
cancelLabel.value = cancelText || cancelLabel.value
confirmC.value = confirmColor || confirmC.value
cancelC.value = cancelColor || cancelC.value
subContentText.value = subContent || subContentText.value
subContentC.value = subContentColor || subContentC.value
}
defineExpose({
showConfirm
showConfirm,
})
</script>
<template>
<wd-popup custom-style="border-radius: 16rpx;" modal-style="background-color: #000000;opacity: 0.6;" v-model="confirmState">
<wd-popup
custom-style="border-radius: 16rpx;"
modal-style="background-color: #000000;opacity: 0.6;"
v-model="confirmState"
>
<div class="flex flex-col w-[640rpx]">
<div v-if="imageRef===''" class="flex justify-center items-center h-[288rpx] text-[32rpx] font-bold text-[#1A1A1A]">
{{contentText}}
<div
v-if="imageRef === ''"
class="flex flex-col justify-center items-center h-[288rpx] text-[#1A1A1A] popup-content"
>
<span class="text-[32rpx] font-bold">{{ contentText }}</span>
<span
class="text-[28rpx] font-regular"
v-if="subContentText"
:style="{ color: subContentC }"
>
{{ subContentText }}
</span>
</div>
<div v-else class="flex flex-col items-center h-[456rpx] text-[32rpx] font-bold text-[#1A1A1A]" >
<div class="wrap1 mt-[32rpx] mb-[44rpx]" >
<img :src="imageRef" alt="">
<div
v-else
class="flex flex-col items-center h-[456rpx] text-[32rpx] font-bold text-[#1A1A1A]"
>
<div class="wrap1 mt-[32rpx] mb-[44rpx]">
<img :src="imageRef" alt="" />
</div>
<div class="mb-[56rpx]" > {{contentText}} </div>
<div class="mb-[56rpx]">{{ contentText }}</div>
</div>
<div class="flex flex-grow border-t-solid border-[#E7E7E7] border-1rpx text-[32rpx]">
<div
class="flex flex-grow border-t-solid border-[#E7E7E7] border-1rpx text-[32rpx]"
>
<div class="flex justify-center items-center text-[#1A1A1A]">
<tm-button
@click="sendCancel"
:width="319"
@touchstart="cancel=false"
@touchend="cancel=true"
@touchstart="cancel = false"
@touchend="cancel = true"
:fontSize="32"
:height="112"
:margin="[0]"
:font-color="cancelC"
:transprent="cancel"
text
:label="cancelLabel"></tm-button>
:label="cancelLabel"
></tm-button>
</div>
<div class="h-[112rpx] w-[1rpx] bg-[#E7E7E7]"></div>
<div class="flex justify-center items-center text-[#CF3050]">
<tm-button
@click="sendConfirm"
@touchstart="confirm=false"
@touchend="confirm=true"
@touchstart="confirm = false"
@touchend="confirm = true"
:width="319"
:fontSize="32"
:transprent="confirm"
@ -81,17 +116,23 @@ defineExpose({
:margin="[0]"
:font-color="confirmC"
text
:label="confirmLabel"></tm-button>
:label="confirmLabel"
></tm-button>
</div>
</div>
</div>
</wd-popup>
</template>
<style scoped lang="scss">
.popup-content {
span {
line-height: 44rpx;
}
}
.wrap1 {
img {
width: 381.59rpx;
height: 280.14rpx;
}
}
img {
width: 381.59rpx;
height: 280.14rpx;
}
}
</style>

View File

@ -1,11 +0,0 @@
<script setup>
import tmNavbar from '@/uni_modules/tmui/components/tm-navbar/tm-navbar.vue';
import {useStatus} from "@/store/status"
const {currentNavbar} = useStatus()
</script>
<template>
<tm-navbar :hideBack="false" hideHome :title="currentNavbar.title"/>
</template>
<style scoped lang="scss">
</style>

View File

@ -1,20 +1,30 @@
<script setup>
import {ref} from 'vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
const pagingRef=ref(null)
import { ref } from "vue";
import ZPaging from "@/uni_modules/z-paging/components/z-paging/z-paging.vue";
const pagingRef = ref(null);
defineExpose({
pagingRef
})
pagingRef,
});
</script>
<template>
<z-paging ref="pagingRef" :show-scrollbar="false" :refresher-end-bounce-enabled="false" :refresher-complete-duration="500" :refresher-complete-delay="500" :refresher-fps="60" show-refresher-update-time use-virtual-list :fixed="false" v-bind="{ ...$attrs, ...$props}">
<template v-for="(slot, name) in $slots" :key="name" #[name]>
<slot :name="name"></slot>
</template>
</z-paging>
<z-paging
ref="pagingRef"
:show-scrollbar="false"
:refresher-end-bounce-enabled="false"
:refresher-complete-duration="500"
:refresher-complete-delay="500"
:refresher-fps="60"
show-refresher-update-time
use-virtual-list
:fixed="false"
v-bind="{ ...$attrs, ...$props }"
>
<template v-for="(slot, name) in $slots" :key="name" #[name]>
<slot :name="name"></slot>
</template>
</z-paging>
</template>
<style scoped lang="scss">
</style>

View File

@ -30,6 +30,7 @@ class Connect {
},
// Websocket 连接成功回调方法
onOpen: () => {
console.log("socket已连接")
// 更新 WebSocket 连接状态
useUserStore().updateSocketStatus(true)
// online.value = true;
@ -37,6 +38,7 @@ class Connect {
},
// Websocket 断开连接回调方法
onClose: () => {
console.log("socket已断开")
// 更新 WebSocket 连接状态
useUserStore().updateSocketStatus(false)
// online.value = false

View File

@ -11,12 +11,13 @@ export const ChatMsgTypeLogin = 10 // 登录消息
export const ChatMsgTypeVote = 11 // 投票消息
export const ChatMsgTypeMixed = 12 // 混合消息
export const ChatMsgTypeGroupNotice = 13 // 群公告消息
export const ChatMsgTypeLink = 14 // 链接消息
export const ChatMsgSysText = 1000 // 系统文本消息
export const ChatMsgSysGroupCreate = 1101 // 创建群聊消息
export const ChatMsgSysGroupMemberJoin = 1102 // 加入群聊消息
export const ChatMsgSysGroupMemberQuit = 1103 // 群成员退出群消息
export const ChatMsgSysGroupMemberKicked = 1104 // 踢出群成员消息
export const ChatMsgSysGroupMemberKicked = 1104 // 移出群成员消息(普通群、项目群被踢)
export const ChatMsgSysGroupMessageRevoke = 1105 // 管理员撤回成员消息
export const ChatMsgSysGroupDismissed = 1106 // 群解散
export const ChatMsgSysGroupMuted = 1107 // 群禁言
@ -25,6 +26,9 @@ export const ChatMsgSysGroupMemberMuted = 1109 // 群成员禁言
export const ChatMsgSysGroupMemberCancelMuted = 1110 // 群成员解除禁言
export const ChatMsgSysGroupNotice = 1111 // 编辑群公告
export const ChatMsgSysGroupTransfer = 1113 // 变更群主
export const ChatMsgSysGroupAdmin = 1114 // 设置管理员
export const ChatMsgSysGroupMemberRemoved = 1115 // 移出群成员消息(部门群、公司群自动移出)
export const ChatMsgSysGroupInfoChange = 1116 // 管理员更新了群信息
export const ChatMsgTypeMapping = {
[ChatMsgTypeText]: '[文本消息]',
@ -40,18 +44,23 @@ export const ChatMsgTypeMapping = {
[ChatMsgTypeCode]: '[代码消息]',
[ChatMsgTypeMixed]: '[图文消息]',
[ChatMsgTypeGroupNotice]: '[群公告]',
[ChatMsgTypeLink]: '[链接]',
[ChatMsgSysText]: '[系统消息]',
[ChatMsgSysGroupCreate]: '[创建群消息]',
[ChatMsgSysGroupMemberJoin]: '[加入群消息]',
[ChatMsgSysGroupMemberQuit]: '[退出群消息]',
[ChatMsgSysGroupMemberKicked]: '[出群消息]',
[ChatMsgSysGroupMemberKicked]: '[出群消息]',
[ChatMsgSysGroupMessageRevoke]: '[撤回消息]',
[ChatMsgSysGroupDismissed]: '[群解散消息]',
[ChatMsgSysGroupMuted]: '[群禁言消息]',
[ChatMsgSysGroupCancelMuted]: '[群解除禁言消息]',
[ChatMsgSysGroupMemberMuted]: '[群成员禁言消息]',
[ChatMsgSysGroupMemberCancelMuted]: '[群成员解除禁言消息]',
[ChatMsgSysGroupNotice]: '[群公告]'
[ChatMsgSysGroupNotice]: '[群公告]',
[ChatMsgSysGroupTransfer]: '[转让群主]',
[ChatMsgSysGroupAdmin]: '[设置管理员]',
[ChatMsgSysGroupMemberRemoved]: '[移出群成员消息]',
[ChatMsgSysGroupInfoChange]: '[群信息更新]'
}
// 消息类型 - 消息组件 映射关系
@ -69,18 +78,22 @@ export const MessageComponents = {
[ChatMsgTypeCode]: 'code-message',
[ChatMsgTypeMixed]: 'mixed-message',
[ChatMsgTypeGroupNotice]: 'group-notice-message',
[ChatMsgTypeLink]: 'link-message',
[ChatMsgSysText]: 'sys-text-message',
[ChatMsgSysGroupCreate]: 'sys-group-create-message',
[ChatMsgSysGroupMemberJoin]: 'sys-group-join-message',
[ChatMsgSysGroupMemberQuit]: 'sys-group-member-quit-message',
[ChatMsgSysGroupMemberKicked]: 'sys-group-member-kicked-message',
// [ChatMsgSysGroupMessageRevoke]: '[撤回消息]',
// [ChatMsgSysGroupDismissed]: '[群解散消息]',
[ChatMsgSysGroupDismissed]: 'sys-group-dismissed',
[ChatMsgSysGroupMuted]: 'sys-group-muted-message',
[ChatMsgSysGroupCancelMuted]: 'sys-group-cancel-muted-message',
[ChatMsgSysGroupMemberMuted]: 'sys-group-member-muted-message',
[ChatMsgSysGroupMemberCancelMuted]: 'sys-group-member-cancel-muted-message',
[ChatMsgSysGroupTransfer]: 'sys-group-transfer-message'
[ChatMsgSysGroupTransfer]: 'sys-group-transfer-message',
[ChatMsgSysGroupAdmin]:'sys-group-admin-message',
[ChatMsgSysGroupMemberRemoved]:'sys-group-member-removed-message',
[ChatMsgSysGroupInfoChange]:'sys-group-info-change-message'
}
// 可转发的消息类型
@ -92,5 +105,6 @@ export const ForwardableMessageType = [
ChatMsgTypeVideo,
ChatMsgTypeFile,
ChatMsgTypeLocation,
ChatMsgTypeCard
ChatMsgTypeCard,
ChatMsgTypeLink
]

View File

@ -68,20 +68,38 @@ class Revoke extends Base {
}
handle() {
const {updateDialogueRecord} = useDialogueListStore()
const { updateDialogueRecord } = useDialogueListStore()
useTalkStore().updateItem({
index_name: this.getIndexName(),
msg_text: this.resource.text,
updated_at: parseTime(new Date())
revokeInfo: {
retracted_id: this.resource.retracted_id,
retracted_name: this.resource.retracted_name,
withdraw_id: this.resource.withdraw_id,
withdraw_name: this.resource.withdraw_name,
},
updated_at: parseTime(new Date()),
})
useDialogueStore().updateDialogueRecord({
msg_id: this.msg_id,
is_revoke: 1
revokeInfo: {
retracted_id: this.resource.retracted_id,
retracted_name: this.resource.retracted_name,
withdraw_id: this.resource.withdraw_id,
withdraw_name: this.resource.withdraw_name,
},
is_revoke: 1,
})
updateDialogueRecord({
msg_id: this.msg_id,
is_revoke: 1
revokeInfo: {
retracted_id: this.resource.retracted_id,
retracted_name: this.resource.retracted_name,
withdraw_id: this.resource.withdraw_id,
withdraw_name: this.resource.withdraw_name,
},
is_revoke: 1,
})
}
}

View File

@ -5,8 +5,16 @@ import { parseTime } from '@/utils/datetime'
import * as message from '@/constant/message'
import { formatTalkItem, palyMusic, formatTalkRecord } from '@/utils/talk'
// import { isElectronMode } from '@/utils/common'
import { ServeClearTalkUnreadNum, ServeCreateTalkList } from '@/api/chat/index.js'
import { useTalkStore, useDialogueStore,useDialogueListStore } from '@/store'
import {
ServeClearTalkUnreadNum,
ServeCreateTalkList,
} from '@/api/chat/index.js'
import {
useTalkStore,
useDialogueStore,
useDialogueListStore,
useGroupStore,
} from '@/store'
/**
* 好友状态事件
@ -32,6 +40,11 @@ class Talk extends Base {
*/
talk_type = 0
/**
* 文件上传唯一随机值
*/
fileNum = ''
/**
* 初始化构造方法
*
@ -43,6 +56,10 @@ class Talk extends Base {
this.sender_id = resource.sender_id
this.receiver_id = resource.receiver_id
this.talk_type = resource.talk_type
// this.fileNum = resource.file_num
if (resource.file_num) {
resource.data.file_num = resource.file_num
}
this.resource = resource.data
this.handle()
@ -89,11 +106,10 @@ class Talk extends Base {
play() {
// 客户端有消息提示
// if (isElectronMode()) return
// useSettingsStore().isPromptTone && palyMusic()
}
handle() {
async handle() {
// 不是自己发送的消息则需要播放提示音
if (!this.isCurrSender()) {
this.play()
@ -101,7 +117,21 @@ class Talk extends Base {
// 判断会话列表是否存在,不存在则创建
if (useTalkStore().findTalkIndex(this.getIndexName()) == -1) {
return this.addTalkItem()
if (this.resource.msg_type == 1102) {
//被邀请进入群聊时,需要热更新会话列表
await useTalkStore().loadTalkList()
} else if (this.resource.msg_type == 1106) {
//群解散时,需要热更新会话列表
await useTalkStore().loadTalkList()
} else if (
this.resource.msg_type == 1104 ||
this.resource.msg_type == 1115
) {
//群成员被移出时,需要热更新会话列表
await useTalkStore().loadTalkList()
} else {
return this.addTalkItem()
}
}
// 判断当前是否正在和好友对话
@ -109,6 +139,37 @@ class Talk extends Base {
this.insertTalkRecord()
} else {
this.updateTalkItem()
if (
!useTalkStore().items[useTalkStore().findTalkIndex(this.getIndexName())]
?.is_disturb &&
!(
useTalkStore().findTalkIndex(this.getIndexName()) == -1 &&
(this.resource.msg_type == 1104 || this.resource.msg_type == 1115)
)
) {
this.updateUnreadMsgNumAdd()
}
}
}
//更新未读数量+1
updateUnreadMsgNumAdd() {
if (typeof plus !== 'undefined') {
let OAWebView = plus.webview.all()
OAWebView.forEach((webview) => {
if (webview.id === 'webviewId1') {
webview.evalJS(`updateUnreadMsgNumAdd()`)
}
})
} else {
document.addEventListener('plusready', () => {
let OAWebView = plus.webview.all()
OAWebView.forEach((webview) => {
if (webview.id === 'webviewId1') {
webview.evalJS(`updateUnreadMsgNumAdd()`)
}
})
})
}
}
@ -123,7 +184,6 @@ class Talk extends Base {
// lang: 'zh-CN',
// body: '您有新的消息请注意查收'
// })
// notification.onclick = () => {
// notification.close()
// }
@ -151,12 +211,16 @@ class Talk extends Base {
ServeCreateTalkList({
talk_type,
receiver_id
}).then(({ code, data }) => {
receiver_id,
}).then(async ({ code, data }) => {
if (code == 200) {
let item = formatTalkItem(data)
item.unread_num = 1
if (!item?.is_disturb) {
item.unread_num = 1
this.updateUnreadMsgNumAdd()
}
useTalkStore().addItem(item)
await useTalkStore().loadTalkList()
}
})
}
@ -166,24 +230,76 @@ class Talk extends Base {
*/
insertTalkRecord() {
let record = this.resource
let newRecord = formatTalkRecord(this.getAccountId(), this.resource);
const {addDialogueRecord,addChatRecord} = useDialogueListStore()
let newRecord = formatTalkRecord(this.getAccountId(), this.resource)
const { addDialogueRecord, addChatRecord } = useDialogueListStore()
// 群成员变化的消息,需要更新群成员列表
if ([1102, 1103, 1104].includes(record.msg_type)) {
if ([1102, 1103, 1104, 1115].includes(record.msg_type)) {
useDialogueStore().updateGroupMembers()
}
addDialogueRecord([newRecord],'add')
addChatRecord(this.getIndexName(),newRecord)
//群解散时,需要更新群成员权限
if ([1106].includes(record.msg_type)) {
useDialogueStore().updateDismiss()
}
//群成员被移出时,需要更新群成员权限
if ([1104, 1115].includes(record.msg_type)) {
console.error(this.resource.extra.members, 'this.resource.extra.members')
if (this.resource?.extra?.members?.length > 0) {
const isMeQuit = this.resource.extra.members.find(
(item) => item.user_id === this.getAccountId(),
)
if (isMeQuit) {
useDialogueStore().updateQuit()
}
}
}
//群禁言变化时,需要更新群禁言状态——即更新群成员列表
if ([1107, 1108, 1109, 1110].includes(record.msg_type)) {
useDialogueStore().updateGroupMembers()
}
//群公告变化时,需要更新群公告(新增和修改有热更新,删除没有)
if ([13].includes(record.msg_type)) {
useGroupStore().ServeGetGroupNotices()
}
//群管理员变化时,需要更新群管理员列表——即更新群成员列表,同时更新群信息
if ([1114].includes(record.msg_type)) {
useDialogueStore().updateGroupMembers()
useGroupStore().ServeGroupDetail()
}
if ([1116].includes(record.msg_type)) {
// 更新会话信息
useDialogueStore().setDialogue({
name: record.extra.group_name,
talk_type: record.talk_type,
receiver_id: record.receiver_id,
})
// 更新群聊信息
useGroupStore().updateGroupInfo({
group_name: record.extra.group_name,
avatar: record.extra.group_avatar,
})
// 更新会话列表中的会话信息
const dialogue = useDialogueListStore().getDialogueList(
`${record.talk_type}_${record.receiver_id}`,
)
if (dialogue) {
dialogue.talk.username = record.extra.group_name
}
}
addDialogueRecord([newRecord], 'add')
addChatRecord(this.getIndexName(), newRecord)
useDialogueStore().addDialogueRecord(newRecord)
if (!this.isCurrSender()) {
// 推送已读消息
setTimeout(() => {
ws.emit('im.message.read', {
receiver_id: this.sender_id,
msg_ids: [this.resource.msg_id]
})
}, 1000)
// setTimeout(() => {
// ws.emit('im.message.read', {
// receiver_id: this.sender_id,
// msg_ids: [this.resource.msg_id],
// })
// }, 1000)
}
// 获取聊天面板元素节点
@ -191,7 +307,8 @@ class Talk extends Base {
if (!el) return
// 判断的滚动条是否在底部
const isBottom = Math.ceil(el.scrollTop) + el.clientHeight >= el.scrollHeight
const isBottom =
Math.ceil(el.scrollTop) + el.clientHeight >= el.scrollHeight
if (isBottom || record.user_id == this.getAccountId()) {
nextTick(() => {
@ -204,14 +321,15 @@ class Talk extends Base {
useTalkStore().updateItem({
index_name: this.getIndexName(),
msg_text: this.getTalkText(),
updated_at: parseTime(new Date())
updated_at: parseTime(new Date()),
})
if (this.talk_type == 1 && this.getAccountId() !== this.sender_id) {
ServeClearTalkUnreadNum({
talk_type: 1,
receiver_id: this.sender_id
})
//不在此处维护未读消息数量
// ServeClearTalkUnreadNum({
// talk_type: 1,
// receiver_id: this.sender_id,
// })
}
}
@ -222,8 +340,31 @@ class Talk extends Base {
useTalkStore().updateMessage({
index_name: this.getIndexName(),
msg_text: this.getTalkText(),
updated_at: parseTime(new Date())
updated_at: parseTime(new Date()),
})
//收到新消息时,同时判断是否有人@我
if (
this.resource.msg_type === 1 &&
this.resource?.extra?.mentions?.length > 0
) {
const findMention = this.resource?.extra?.mentions?.find(
(mention) => mention === this.getAccountId(),
)
//有人@我或者@所有人,则更新会话列表
if (findMention || this.resource?.extra?.mentions?.includes(0)) {
useTalkStore().loadTalkList()
}
}
if (this.resource.msg_type == 1116) {
// 更新会话列表中的会话信息
const dialogue = useDialogueListStore().getDialogueList(
`${this.resource.talk_type}_${this.resource.receiver_id}`,
)
if (dialogue) {
dialogue.talk.username = this.resource.extra.group_name
}
useTalkStore().loadTalkList()
}
}
}

View File

@ -11,8 +11,13 @@ import { reactive, nextTick, computed, h, inject } from 'vue'
// EditTwo,
// IdCard
// } from '@icon-park/vue-next'
import { ServeTopTalkList, ServeDeleteTalkList, ServeSetNotDisturb } from '@/api/chat'
import { useDialogueStore, useTalkStore } from '@/store'
import {
ServeTopTalkList,
ServeDeleteTalkList,
ServeSetNotDisturb,
ServeClearTalkUnreadNum,
} from '@/api/chat'
import { useDialogueStore, useTalkStore, useDialogueListStore } from '@/store'
import { ServeSecedeGroup } from '@/api/group'
// import { ServeDeleteContact, ServeEditContactRemark } from '@/api/contact'
// import { NInput } from 'naive-ui'
@ -23,11 +28,12 @@ export function useSessionMenu() {
show: false,
x: 0,
y: 0,
item: {}
item: {},
})
const dialogueStore = useDialogueStore()
const talkStore = useTalkStore()
const dialogueListStore = useDialogueListStore()
const user = inject('$user')
@ -105,6 +111,7 @@ export function useSessionMenu() {
const onDeleteTalk = (index_name = '') => {
talkStore.delItem(index_name)
dialogueListStore.delDialogueStorage(index_name)
index_name === indexName.value && dialogueStore.$reset()
}
@ -116,10 +123,22 @@ export function useSessionMenu() {
// 移除会话
const onRemoveTalk = (item) => {
ServeDeleteTalkList({
list_id: item.id
list_id: item.id,
}).then(({ code }) => {
if (code == 200) {
onDeleteTalk(item.index_name)
console.error(item, 'item')
if (item.unread_num > 0) {
//同时已读
ServeClearTalkUnreadNum(
{
talk_type: item.talk_type,
receiver_id: item.receiver_id,
},
item.unread_num,
).then(() => {
})
}
}
})
}
@ -129,13 +148,13 @@ export function useSessionMenu() {
ServeSetNotDisturb({
talk_type: item.talk_type,
receiver_id: item.receiver_id,
is_disturb: item.is_disturb == 0 ? 1 : 0
is_disturb: item.is_disturb == 0 ? 1 : 0,
}).then(({ code, message }) => {
if (code == 200) {
message.success('设置成功!')
talkStore.updateItem({
index_name: item.index_name,
is_disturb: item.is_disturb == 0 ? 1 : 0
is_disturb: item.is_disturb == 0 ? 1 : 0,
})
} else {
message.error(message)
@ -151,12 +170,12 @@ export function useSessionMenu() {
ServeTopTalkList({
list_id: item.id,
type: item.is_top == 0 ? 1 : 2
type: item.is_top == 0 ? 1 : 2,
}).then(({ code, message }) => {
if (code == 200) {
talkStore.updateItem({
index_name: item.index_name,
is_top: item.is_top == 0 ? 1 : 0
is_top: item.is_top == 0 ? 1 : 0,
})
} else {
message.error(message)
@ -199,7 +218,7 @@ export function useSessionMenu() {
negativeText: '取消',
onPositiveClick: () => {
ServeSecedeGroup({
group_id: item.receiver_id
group_id: item.receiver_id,
}).then(({ code, message }) => {
if (code == 200) {
message.success('已退出群聊')
@ -208,7 +227,7 @@ export function useSessionMenu() {
message.error(message)
}
})
}
},
})
}
@ -257,12 +276,18 @@ export function useSessionMenu() {
disturb: onSetDisturb,
signout_group: onSignOutGroup,
delete_contact: onDeleteContact,
remark: onChangeRemark
remark: onChangeRemark,
}
dropdown.show = false
evnets[key] && evnets[key](dropdown.item)
}
return { dropdown, onCloseContextMenu, onContextMenuTalkHandle, onToTopTalk }
return {
dropdown,
onCloseContextMenu,
onContextMenuTalkHandle,
onToTopTalk,
onRemoveTalk,
}
}

View File

@ -7,25 +7,29 @@ import lodash from 'lodash'
export const useTalkRecord = (uid) => {
const dialogueStore = useDialogueStore()
const { getDialogueList,addDialogueRecord,zpagingComplete } = useDialogueListStore()
const {
getDialogueList,
addDialogueRecord,
zpagingComplete,
} = useDialogueListStore()
const records = computed(() => {
const dialogueList = getDialogueList(dialogueStore.index_name)
if(dialogueList){
if (dialogueList) {
return lodash.cloneDeep(dialogueList.records).reverse()
}
return lodash.cloneDeep(dialogueStore.records)?.reverse()
} )
})
const location = reactive({
msgid: '',
num: 0
num: 0,
})
const loadConfig = reactive({
receiver_id: 0,
talk_type: 0,
status: 0,
cursor: 0
cursor: 0,
})
const onJumpMessage = (msgid) => {
@ -49,7 +53,7 @@ export const useTalkRecord = (uid) => {
return el?.scrollTo({
top: 0,
behavior: 'smooth'
behavior: 'smooth',
})
}
@ -57,7 +61,7 @@ export const useTalkRecord = (uid) => {
location.num = 0
element?.scrollIntoView({
behavior: 'smooth'
behavior: 'smooth',
})
addClass(element, 'border')
@ -70,7 +74,7 @@ export const useTalkRecord = (uid) => {
// 加载数据列表
const load = async (params) => {
const request = {
limit:30,
limit: 30,
...params,
talk_type: params.talk_type,
receiver_id: params.receiver_id,
@ -81,9 +85,9 @@ export const useTalkRecord = (uid) => {
let scrollHeight = 0
const el = document.getElementById('imChatPanel')
// if (el) {
// scrollHeight = el.scrollHeight
// }
if (el) {
scrollHeight = el.scrollHeight
}
const { data, code } = await ServeTalkRecords(request)
if (code != 200) {
return (loadConfig.status = 1)
@ -104,10 +108,14 @@ export const useTalkRecord = (uid) => {
const reverseItems = lodash.cloneDeep(items).reverse()
dialogueStore.unshiftDialogueRecord(reverseItems)
addDialogueRecord(params.direction=='down'?reverseItems:items,params.direction=='down'?'add':'unshift')
zpagingComplete(dialogueStore.index_name)
addDialogueRecord(
params.direction == 'down' ? reverseItems : items,
params.direction == 'down' ? 'add' : 'unshift',
)
zpagingComplete(dialogueStore.index_name, reverseItems)
const dialogueList = getDialogueList(dialogueStore.index_name)
loadConfig.status = dialogueList.records?.[0]?.sequence > 1 ? 1 : 2
loadConfig.status =
dialogueList.records?.[0]?.sequence > 1 && data?.items?.length > 0 ? 1 : 2
// loadConfig.cursor = data.cursor
nextTick(() => {
@ -133,17 +141,19 @@ export const useTalkRecord = (uid) => {
const onRefreshLoad = () => {
let dialogueList = getDialogueList(dialogueStore.index_name)
if (dialogueList.records[0].sequence === 1 ) {
if (dialogueList.records[0].sequence === 1) {
return false
}
if (loadConfig.status == 1) {
let filterList = dialogueList.records.filter(item=>item.sequence !== -1)
let filterList = dialogueList.records.filter(
(item) => item.sequence !== -1,
)
loadConfig.cursor = filterList[0].sequence
load({
receiver_id: loadConfig.receiver_id,
talk_type: loadConfig.talk_type,
direction:'up',
no_limit:0,
direction: 'up',
no_limit: 0,
})
}
}
@ -152,12 +162,14 @@ export const useTalkRecord = (uid) => {
let dialogueList = getDialogueList(params.index_name)
if (!dialogueList) {
loadConfig.cursor = 0
}else{
let filterList = dialogueList.records.filter(item=>item.sequence !== -1)
if(params.direction=='up'){
} else {
let filterList = dialogueList.records.filter(
(item) => item.sequence !== -1,
)
if (params.direction == 'up') {
loadConfig.cursor = filterList?.[0]?.sequence
}else{
loadConfig.cursor = filterList?.[filterList.length-1]?.sequence
} else {
loadConfig.cursor = filterList?.[filterList.length - 1]?.sequence
}
}
loadConfig.receiver_id = params.receiver_id

View File

@ -1,52 +1,142 @@
import customNavbar from '@/components/custom-navbar/index'
import { createSSRApp } from 'vue'
import App from './App.vue'
import dayjs from "dayjs";
import dayjs from 'dayjs'
import 'virtual:uno.css'
import VConsole from "vconsole";
import VConsole from 'vconsole'
import '@/utils/uni.webview.js'
import tmui from "@/uni_modules/tmui"
import {config} from "@/config/tmui/index.js";
import 'dayjs/locale/zh-cn';
import xLoaderror from '@/components/x-loaderror/index.vue'
import { vLoading } from "@/components/x-loading/index.js"
import tmui from '@/uni_modules/tmui'
import { config } from '@/config/tmui/index.js'
import 'dayjs/locale/zh-cn'
import xLoaderror from '@/components/x-loaderror/index.vue'
import asyncLoading from '@/components/async-loading/index.vue'
import asyncError from '@/components/async-error/index.vue'
import { vLoading } from '@/components/x-loading/index.js'
import messagePopup from '@/components/x-message/useMessagePopup'
import pageAnimation from '@/components/page-animation/index.vue'
import * as plugins from './plugins'
const {showMessage}=messagePopup()
import { useDialogueStore, useTalkStore, useUserStore, useDialogueListStore } from '@/store'
import {uniStorage} from "@/utils/uniStorage.js"
const { showMessage } = messagePopup()
dayjs.locale('zh-cn')
if (import.meta.env.VITE_SHOW_CONSOLE){
if (import.meta.env.VITE_SHOW_CONSOLE === 'true') {
new VConsole()
}
export function createApp() {
const app = createSSRApp(App)
plugins.setPinia(app)
plugins.setComponents(app)
app.use(tmui,{...config})
app.directive("loading", vLoading)
app.use(tmui, { ...config })
app.directive('loading', vLoading)
app.mixin(pageAnimation)
app.component('x-loaderror',xLoaderror)
app.component('customNavbar', customNavbar)
app.component('x-loaderror', xLoaderror)
app.component('AsyncLoading', asyncLoading)
app.component('AsyncError', asyncError)
app.directive('no-space', {
mounted(el) {
el.addEventListener('input', (e) => {
const originalValue = e.target.value;
const newValue = originalValue.replace(/\s/g, '');
const originalValue = e.target.value
const newValue = originalValue.replace(/\s/g, '')
if (originalValue !== newValue) {
e.target.value = newValue;
e.target.dispatchEvent(new Event('input'));
e.target.value = newValue
e.target.dispatchEvent(new Event('input'))
}
});
}
})
},
})
//获取当前聊天页面所在页面并通过当前的receiver_id判断是否要创建本地通知栏消息
window.getCurrentChatRoute = (msg) => {
let pushMsg = JSON.parse(decodeURIComponent(msg))
//当前所在聊天会话的receiver_id
const receiver_id = pushMsg?.payload?.receiver_id
// 获取当前页面路径
const pages = getCurrentPages()
const page = pages[pages.length - 1]
console.log(page.route)
const dialogueStore = useDialogueStore()
console.log(dialogueStore?.talk?.receiver_id)
if (
page?.route === 'pages/dialog/index' &&
receiver_id === dialogueStore?.talk?.receiver_id
) {
return
}
console.log('===准备创建本地通知栏消息')
let OAWebView = plus.webview.all()
OAWebView.forEach((webview, index) => {
if (webview.id === 'webviewId1') {
webview.evalJS(`doCreatePushMessage('${msg}')`)
}
})
}
//处理聊天推送弹窗点开
window.openUniPushMsg = (msg) => {
console.log('=====点击通知栏消息')
let pushMsg = JSON.parse(decodeURIComponent(msg))
console.log('=====pushMsg', pushMsg)
//由于弹窗前处理了不该弹窗的场景,因此这里弹窗可以一并处理
//也就是都跳转到聊天页面
const talkStore = useTalkStore()
talkStore.toTalk(pushMsg?.payload?.talk_type, pushMsg?.payload?.receiver_id)
}
//处理当用户信息发生变化时,更新用户信息
window.updateUserInfo = () => {
useUserStore().loadSetting()
}
// 通讯录跳转
window.handleContacts = () => {
uni.navigateTo({
url: '/pages/chooseByDeps/index?chooseMode=3&type=true'
});
};
//处理OA、墨册强制刷新时聊天同步强制刷新
window.doLocationRefresh = () => {
uniStorage.removeItem('dialogueList')
uniStorage.removeItem('dialogue')
useUserStore().loadSetting()
useDialogueListStore().dialogueList.value = []
// location.reload(true)
}
//检查聊天页面是否可用
window.checkChatWebviewAvailable = () => {
let OAWebView = plus.webview.all()
OAWebView.forEach((webview, index) => {
if (webview.id === 'webviewId1') {
webview.evalJS(`doneCheckChatWebviewAvailable()`)
}
})
}
//获取从base传来的多选视频列表
window.getBaseMulVideo = (videoList) => {
const videos = JSON.parse(decodeURIComponent(videoList))
console.error('=====videos', videos)
if(videos.length > 0){
const videoUri = videos[0]
console.error('=====videoUri', videoUri)
}
}
window.message = ['success', 'error', 'warning'].reduce((acc, type) => {
acc[type] = (message) => {
if (typeof message === 'string') {
showMessage({ type, message });
showMessage({ type, message })
} else if (typeof message === 'object') {
showMessage({ type, ...message });
showMessage({ type, ...message })
}
};
return acc;
}, {});
}
return acc
}, {})
return {
app,
}

View File

@ -5,8 +5,7 @@
"^tm-(.*)": "@/tmui/components/tm-$1/tm-$1.vue"
}
},
"pages": [
{
"pages": [{
"path": "pages/index/index",
"type": "page",
"style": {
@ -38,22 +37,6 @@
"enablePullDownRefresh": false
}
},
{
"path": "pages/chooseDeps/index",
"type": "page",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/chooseMembers/index",
"type": "page",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh":false
}
},
{
"path": "pages/chooseGroupAdmin/index",
"type": "page",
@ -186,6 +169,30 @@
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/chooseByDeps/index",
"type": "page",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/chatSettings/groupManage/manageGroupDeps",
"type": "page",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/complaintReport/index",
"type": "page",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
}
],
"globalStyle": {

View File

@ -9,7 +9,7 @@
? ''
: '0',
}"
v-for="(memberItem, memberIndex) in props?.memberList"
v-for="(memberItem, memberIndex) in sortedMemberList"
@click="toUserDetailPage(memberItem)"
>
<div
@ -19,12 +19,12 @@
"
>
<div class="group-member-avatar">
<img v-if="memberItem.avatar" :src="memberItem.avatar" />
<span v-if="!memberItem.avatar" class="text-[24rpx] font-bold">
<img v-if="memberItem?.avatar" :src="memberItem?.avatar" />
<span v-if="!memberItem?.avatar" class="text-[24rpx] font-bold">
{{
memberItem.nickname.length >= 2
? memberItem.nickname.slice(-2)
: memberItem.nickname
(memberItem?.nickname || memberItem?.nickName)?.length >= 2
? (memberItem?.nickname || memberItem?.nickName).slice(-2)
: memberItem?.nickname || memberItem?.nickName
}}
</span>
</div>
@ -38,25 +38,40 @@
</div>
<div class="group-member-name">
<span class="text-[24rpx] font-regular">
{{ memberItem.nickname }}
{{ memberItem.nickname || memberItem.nickName }}
</span>
</div>
</div>
</div>
<div class="group-member-list-each">
<div
class="group-member-list-each"
@click="groupAddMembers"
v-if="
(props?.groupType == 1 || props?.groupType == 3) &&
!props?.hideAddRemoveBtns
"
>
<div class="group-member-each">
<div class="group-member-avatar" :style="{ background: 'unset' }">
<img src="/src/static/image/chatSettings/add-member.png" />
<img src="@/static/image/chatSettings/add-member.png" />
</div>
<div class="group-member-name">
<span class="text-[24rpx] font-regular">添加</span>
</div>
</div>
</div>
<div class="group-member-list-each" v-if="props?.is_manager">
<div
class="group-member-list-each"
@click="groupRemoveMembers"
v-if="
props?.is_manager &&
(props?.groupType == 1 || props?.groupType == 3) &&
!props?.hideAddRemoveBtns
"
>
<div class="group-member-each">
<div class="group-member-avatar" :style="{ background: 'unset' }">
<img src="/src/static/image/chatSettings/remove-member.png" />
<img src="@/static/image/chatSettings/remove-member.png" />
</div>
<div class="group-member-name">
<span class="text-[24rpx] font-regular">移除</span>
@ -66,19 +81,69 @@
</div>
</template>
<script setup>
import { defineProps } from 'vue'
import { defineProps, computed } from 'vue'
const props = defineProps({
memberList: Array, //
memberListsLimit: Number, //
is_manager: Boolean, //
groupType: Number, //
hideAddRemoveBtns: Boolean, //
})
//
const sortedMemberList = computed(() => {
if (!props.memberList || props.memberList.length === 0) return [];
//
const indexedList = props.memberList.map((item, index) => ({ item, index }));
// leader
return indexedList.sort((a, b) => {
const leaderA = a.item.leader || 0;
const leaderB = b.item.leader || 0;
// leaderleader
if ((leaderA === 1 || leaderA === 2) && (leaderB !== 1 && leaderB !== 2)) {
return -1; // a
}
if ((leaderB === 1 || leaderB === 2) && (leaderA !== 1 && leaderA !== 2)) {
return 1; // b
}
// leader
if ((leaderA === 1 || leaderA === 2) && (leaderB === 1 || leaderB === 2)) {
return leaderB - leaderA; // leader
}
//
return a.index - b.index;
}).map(item => item.item); //
})
//
const toUserDetailPage = (userItem) => {
console.log(userItem.erp_user_id)
console.log(userItem)
uni.navigateTo({
url:
'/pages/dialog/dialogDetail/userDetail?erpUserId=' + userItem.erp_user_id,
'/pages/dialog/dialogDetail/userDetail?erpUserId=' +
(userItem.erp_user_id || userItem.ID),
})
}
//
const groupAddMembers = () => {
uni.navigateTo({
url:
'/pages/chooseByDeps/index?chooseMode=2&preSelectedMembers=' +
encodeURIComponent(JSON.stringify(props?.memberList)),
})
}
//
const groupRemoveMembers = () => {
uni.navigateTo({
url:
'/pages/chatSettings/groupManage/selectMembers?manageType=removeMembers',
})
}
</script>

View File

@ -7,6 +7,10 @@
? 'select-member-item-card'
: 'select-member-item-list'
"
:style="{
padding:
props?.manageType === 'removeMembers' ? '18rpx 62rpx 18rpx 34rpx' : '',
}"
>
<div class="member-info">
<slot name="left"></slot>
@ -39,12 +43,21 @@
{{ $t('silence.tag.hasDone') }}
</span>
</div>
<div
class="is-mine-tag"
v-if="
props?.memberItem?.is_mine && props?.manageType === 'removeMembers'
"
>
<img src="@/static/image/chatSettings/is-mine.png" />
</div>
<div
class="is-admin-tag"
v-if="
(props?.memberItem?.leader === 1 ||
props?.memberItem?.leader === 2) &&
props?.manageType === 'admin'
(props?.manageType === 'admin' ||
props?.manageType === 'removeMembers')
"
>
<span class="text-[28rpx] font-regular">
@ -135,16 +148,31 @@ const clickItem = () => {
}
}
.select-member-tags {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
.done-silence-tag {
span {
color: #b4b4b4;
line-height: 40rpx;
}
}
.is-mine-tag {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
img {
width: 52rpx;
height: 52rpx;
}
}
.is-admin-tag {
padding: 6rpx 12rpx;
border: 2rpx solid #b4b4b4;
border-radius: 8rpx;
margin: 0 0 0 20rpx;
span {
color: #b4b4b4;
line-height: 40rpx;

View File

@ -0,0 +1,803 @@
<template>
<div class="select-member-by-alphabet">
<ZPaging ref="zPaging" :show-scrollbar="false" :use-virtual-list="true" :virtual-list-col="5"
:refresher-enabled="false" :loading-more-enabled="false" @scroll="onScroll" :fixed="false"
:height="props?.selectAreaHeight">
<div class="select-members">
<div class="search-member" v-if="
props?.manageType !== 'removeMembers' &&
!(
props?.manageType === 'admin' &&
(groupParams.groupInfo.group_type === 2 ||
groupParams.groupInfo.group_type === 4)
)
">
<customInput :searchText="state.searchText" @inputSearchText="inputSearchText"></customInput>
</div>
<div v-show="props?.manageType == 'removeMembers'" class="my-self">
<div class="my-self-left">
<image style="width: 72rpx;border-radius: 50%;height: 72rpx;" :src="mySelfMember.avatar" mode=""></image>
<div style="padding: 0 20rpx;">{{mySelfMember.nickname}}</div>
<img style="width: 45rpx;" src="@/static/image/chatSettings/is-mine.png" />
</div>
<div class="my-self-right">
{{ $t('group.identify.admin') }}
</div>
</div>
<div class="member-list" :style="{
padding: props?.manageType === 'searchRecord' ? '20rpx 0 0' : '',
}">
<div class="member-list-alphabet-anchor-point" :style="{
top: props?.manageType === 'mention' ? '90rpx' : '',
}">
<div class="member-list-alphabet-anchor-point-each" v-for="(alphabetItem, alphabetIndex) in state?.alphabet"
:key="alphabetIndex" :style="{
margin: state?.alphabet?.length > 17 ? '0' : '',
}" @click.stop="scrollToView(alphabetItem)">
<span class="text-[32rpx] font-regular" :style="{
color:
state.currentAlphabet === alphabetItem ? '#7A58DE' : '',
}">
{{ alphabetItem }}
</span>
</div>
</div>
<div class="member-list-alphabet" v-for="(alphabetItem, alphabetIndex) in state.resultMemberList"
:key="alphabetIndex">
<div class="member-list-alphabet-key" :style="{
padding:
props?.manageType === 'searchRecord' ||
props?.manageType === 'removeMembers' ||
props?.manageType === 'mention'
? '10rpx 30rpx'
: '',
}" v-if="
alphabetItem?.memberList?.length > 0 &&
alphabetItem?.key !== '0'
" :id="alphabetItem.key === '#' ? 'special-hash' : alphabetItem.key" :ref="
(el) => {
if (el) alphabetElementRefs[alphabetIndex] = el
}
">
<span class="text-[32rpx] font-regular">
{{ alphabetItem.key }}
</span>
</div>
<div v-if="alphabetItem?.memberList?.length > 0">
<div class="member-list-each" v-for="(item, index) in alphabetItem?.memberList" :key="index">
<tm-checkbox-group v-model="item.checkArr">
<selectMemberItem :groupType="groupParams.groupInfo.group_type" :memberItem="item"
@clickItem="handleClickItem(item)" :manageType="props?.manageType" :itemStyle="
props?.manageType === 'searchRecord' ||
props?.manageType === 'removeMembers' ||
props?.manageType === 'mention'
? 'list'
: 'card'
">
<template #left v-if="props?.manageType !== 'searchRecord'">
<div v-if="
props?.manageType === 'removeMembers' && item?.is_mine
">
<tm-checkbox color="#fff" :transprent="true" :border="0" :disabled="true"></tm-checkbox>
</div>
<tm-checkbox v-if="
!(
props?.manageType === 'removeMembers' &&
item?.is_mine
) && props?.isMulSelect
" :round="10" :color="
item?.checkArr?.length > 0 ? '#46299d' : '#B4B4B4'
" :outlined="
item?.checkArr?.length > 0 ||
(props?.manageType === 'silence' &&
item.is_mute === 1) ||
(props?.manageType === 'admin' &&
(item.leader === 1 || item.leader === 2))
? false
: true
" :value="item.id" :disabled="
(props?.manageType === 'silence' &&
item.is_mute === 1) ||
(props?.manageType === 'admin' &&
(item.leader === 1 || item.leader === 2))
" @change="checkBoxChange"></tm-checkbox>
</template>
</selectMemberItem>
</tm-checkbox-group>
</div>
</div>
</div>
</div>
</div>
</ZPaging>
</div>
</template>
<script setup>
import customInput from '@/components/custom-input/custom-input.vue'
import selectMemberItem from '../components/select-member-item.vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
import groupAllMember from '@/static/image/chatList/groupAllMember.png'
import {
computed,
onMounted,
reactive,
ref,
watch,
nextTick,
defineProps,
defineEmits,
} from 'vue'
import {
ServeGroupNoSpeak,
ServeEditGroupAdmin,
ServeGroupAssignAdmin,
ServeRemoveMembersGroup,
} from '@/api/group/index.js'
import {
useDialogueStore,
useGroupStore,
useGroupTypeStore
} from '@/store'
const emits = defineEmits([
'updateSelectedMembersNum',
'getSelectResult',
'getMentionSelectLists',
])
const zPaging = ref()
useZPaging(zPaging)
const groupStore = useGroupStore()
const groupParams = reactive({
groupInfo: computed(() => groupStore.groupInfo),
})
const groupTypeStore = useGroupTypeStore()
const groupTypeParams = reactive({
departmentAllPositions: computed(() => groupTypeStore.departmentAllPositions),
})
const dialogueStore = useDialogueStore()
const dialogueParams = reactive({
memberList: computed(() => {
const lowerCaseSearchText = state?.searchText.toLowerCase()
return dialogueStore.members.filter((item) =>
state?.searchText ?
item.nickname.toLowerCase().includes(lowerCaseSearchText) :
true,
)
}),
receiverId: computed(() => dialogueStore.talk.receiver_id),
})
const props = defineProps({
manageType: {
//
type: String,
default: '',
},
isCreateDepGroup: {
//
type: Number,
default: 0,
},
selectAreaHeight: {
//
type: String,
default: '',
},
isMulSelect: {
//
type: Boolean,
default: true,
},
})
const state = reactive({
searchText: '', //
alphabet: [], //A-Z
currentAlphabet: 'A', //A-Z
resultMemberList: [], //A-Z
isAssign: false, //view
scrollDirection: '', //
})
watch(
() => dialogueParams?.memberList,
(newMemberList) => {
assembleAlphabetMemberList(newMemberList)
}, {
deep: true
},
)
watch(
() => groupParams?.groupInfo,
(newGroupInfo) => {
assembleAlphabetMemberList(dialogueParams?.memberList)
}, {
deep: true
},
)
watch(
() => props?.isMulSelect,
(newIsMulSelect) => {
if (props?.manageType === 'mention') {
if (!newIsMulSelect) {
state.resultMemberList.unshift({
key: '0',
memberList: [{
avatar: groupAllMember,
erp_user_id: 0,
gender: 0,
is_mute: 0,
key: '0',
leader: 0,
nickname: '所有人',
remark: '',
user_id: 0,
}, ],
})
} else {
if (state.resultMemberList[0].key === '0') {
state.resultMemberList.splice(0, 1)
}
}
}
}, {
deep: true,
immediate: true
},
)
//A-Z tag
const alphabetElementRefs = ref([])
//
let observer
onMounted(() => {
if (props?.manageType) {
assembleAlphabetMemberList(dialogueParams?.memberList)
}
if (props?.isCreateDepGroup) {
assembleAlphabetMemberList()
}
dialogueParams.memberList.forEach((ele) => {
ele.checkArr = []
})
const options = {
root: null, // 使
threshold: 1.0, // 100%
}
observer = new IntersectionObserver(handleIntersection, options)
nextTick(() => {
watch(
alphabetElementRefs,
(newAlphabetElementRefs) => {
if (Array.isArray(newAlphabetElementRefs)) {
newAlphabetElementRefs.forEach((el, index) => {
observeElement(el, index)
})
}
}, {
immediate: true,
deep: true
},
)
if (alphabetElementRefs.value.length > 0) {
alphabetElementRefs.value.forEach((el, index) =>
observeElement(el, index),
)
}
})
})
//
const observeElement = (el, index) => {
if (el && observer) {
observer.observe(el)
}
}
//
const handleIntersection = (entries) => {
if (state.isAssign) {
state.isAssign = false
return
}
entries.forEach((entry) => {
if (!entry.isIntersecting && state.scrollDirection === 'down') {
state.currentAlphabet = entry.target.id
} else if (entry.isIntersecting && state.scrollDirection === 'up') {
if (state?.alphabet?.length > 1) {
state?.alphabet.forEach((item, index) => {
if (item === entry.target.id && index > 0) {
state.currentAlphabet = state?.alphabet[index - 1]
}
})
} else {
state.currentAlphabet = entry.target.id
}
}
})
}
//
const inputSearchText = (e) => {
// console.log(e)
state.searchText = e
}
//item
const handleClickItem = (item) => {
if (
(props?.manageType === 'silence' && item.is_mute === 1) ||
(props?.manageType === 'admin' &&
(item.leader === 1 || item.leader === 2)) ||
(props?.manageType === 'removeMembers' && item.is_mine) ||
(props?.manageType === 'mention' && !props?.isMulSelect)
) {
if (props?.manageType === 'mention' && !props?.isMulSelect) {
emits('getMentionSelectLists', [item])
}
return
}
if (props?.manageType === 'searchRecord') {
uni.navigateTo({
url: '/pages/search/searchByCondition/index?condition=member&groupMemberId=' +
item.id,
})
return
}
let itemList = dialogueParams.memberList
if (
props?.manageType === 'admin' &&
(groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4 ||
props?.isCreateDepGroup === 1)
) {
itemList = state.resultMemberList[0].memberList
}
itemList.forEach((ele) => {
if (ele.id == item.id) {
ele.checkArr = ele.checkArr?.length > 0 ? [] : [item.id]
if (ele.checkArr?.length > 0) {
emits('updateSelectedMembersNum', 1)
} else {
emits('updateSelectedMembersNum', -1)
}
}
})
}
const mySelfMember = ref({})
//A-Z
const assembleAlphabetMemberList = async (newMemberList) => {
if (props?.manageType === 'removeMembers' && Array.isArray(newMemberList)) {
// is_mine true
for (let i = 0; i < newMemberList.length; i++) {
const item = newMemberList[i];
if (item?.is_mine === true) {
mySelfMember.value = item;
newMemberList.splice(i, 1); //
break; //
}
}
}
if (
props?.manageType === 'searchRecord' ||
props?.manageType === 'removeMembers' ||
props?.manageType === 'mention'
) {
const resultMemberList = ref([])
const alphabet = Array.from({
length: 26
}, (_, i) =>
String.fromCharCode(i + 65),
)
let tempAlphabet = []
//
let groupedData = {
'#': [],
}
alphabet.forEach((letter) => {
groupedData[letter] = []
})
//
if (newMemberList) {
newMemberList.forEach((item) => {
const key = item.key?.toUpperCase()
if (alphabet.includes(key)) {
groupedData[key].push(item)
} else {
groupedData['#'].push(item)
}
})
}
//
alphabet.forEach((letter) => {
if (groupedData[letter].length > 0) {
tempAlphabet.push(letter)
}
resultMemberList.value.push({
key: letter,
memberList: groupedData[letter],
})
})
// #
if (groupedData['#'].length > 0) {
tempAlphabet.push('#')
resultMemberList.value.push({
key: '#',
memberList: groupedData['#'],
})
}
state.alphabet = tempAlphabet
if (props?.manageType === 'mention' && !props?.isMulSelect) {
resultMemberList.value.unshift({
key: '0',
memberList: [{
avatar: groupAllMember,
erp_user_id: 0,
gender: 0,
is_mute: 0,
key: '0',
leader: 0,
nickname: '所有人',
remark: '',
user_id: 0,
}, ],
})
}
state.resultMemberList = resultMemberList
} else {
if (
(groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4) &&
props?.manageType === 'admin'
) {
let departmentIdsArr = []
if (groupParams?.groupInfo?.deptInfos?.length > 0) {
groupParams.groupInfo.deptInfos.forEach((item) => {
departmentIdsArr.push(item.dept_id)
})
}
getPosiByDep(departmentIdsArr)
} else if (props?.isCreateDepGroup === 1) {
let departmentIdsArr = []
if (groupTypeStore?.depCheckedKeys?.value?.length > 0) {
groupTypeStore.depCheckedKeys.value.forEach((item) => {
departmentIdsArr.push(item.ID)
})
}
getPosiByDep(departmentIdsArr)
} else {
state.resultMemberList = [{
key: '',
memberList: newMemberList,
}, ]
}
}
}
//
const getPosiByDep = async (departmentIdsArr) => {
await groupTypeStore.getPositionByDepartment({
IDs: departmentIdsArr,
})
let departmentAllPositions = []
if (groupTypeParams?.departmentAllPositions?.value?.length > 0) {
groupTypeParams?.departmentAllPositions?.value?.forEach((item) => {
item?.AllPositions?.forEach((positionItem) => {
departmentAllPositions.push({
nickname: item.name + '-' + positionItem.name,
id: item.ID + '-' + positionItem.ID,
checkArr: [],
positionInfo: {
dept_id: item.ID,
dept_name: item.name,
position_id: positionItem.ID,
position_name: positionItem.name,
},
})
})
})
}
if (groupParams?.groupInfo?.groupAdminList?.length > 0) {
groupParams?.groupInfo?.groupAdminList.forEach((item) => {
departmentAllPositions.forEach((idsItem) => {
if (item.dept_id + '-' + item.position_id == idsItem.id) {
idsItem.leader = 1
}
})
})
}
if (
props?.isCreateDepGroup === 1 &&
groupTypeStore?.groupAdmins?.value?.length > 0
) {
departmentAllPositions.forEach((allPos) => {
groupTypeStore.groupAdmins.value.forEach((admin) => {
if (allPos.id === admin.id) {
allPos.checkArr = [allPos.id]
}
})
})
}
if (state?.searchText) {
const lowerCaseSearchText = state?.searchText.toLowerCase()
departmentAllPositions = departmentAllPositions.filter((item) =>
state?.searchText ?
item.nickname.toLowerCase().includes(lowerCaseSearchText) :
true,
)
}
state.resultMemberList = [{
key: '',
memberList: departmentAllPositions,
}, ]
}
//view
const scrollToView = (alphabet) => {
state.currentAlphabet = alphabet
state.isAssign = true
//
const offsetHeight = document.getElementById('topArea')?.clientHeight ?
document.getElementById('topArea').clientHeight - 1 :
props?.manageType === 'mention' ?
140 :
80
// 使scrollIntoViewById
const targetId = alphabet === '#' ? 'special-hash' : alphabet
zPaging.value?.scrollIntoViewById(targetId, offsetHeight)
}
//
const onScroll = (e) => {
if (e.detail.deltaY < 0) {
state.scrollDirection = 'down'
} else if (e.detail.deltaY > 0) {
state.scrollDirection = 'up'
} else {
state.scrollDirection = ''
}
}
//
const checkBoxChange = (e) => {
if (e) {
emits('updateSelectedMembersNum', 1)
} else {
emits('updateSelectedMembersNum', -1)
}
}
//
const confirmSelectMembers = () => {
let selectedUserIds = ''
let itemList = dialogueStore.members
let positionInfos = []
let selectUserInfos = []
if (
props?.manageType === 'admin' &&
(groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4 ||
props?.isCreateDepGroup === 1)
) {
itemList = state.resultMemberList[0].memberList
}
itemList.forEach((ele) => {
if (ele.checkArr?.length > 0) {
if (!selectedUserIds) {
selectedUserIds = String(ele.checkArr[0])
} else {
selectedUserIds += ',' + ele.checkArr[0]
}
selectUserInfos.push(ele)
}
if (
ele.checkArr?.length > 0 ||
(ele.leader && (ele.leader == 1 || ele.leader == 2))
) {
if (props?.isCreateDepGroup === 1) {
let posInfo = Object.assign({}, ele.positionInfo, {
name: ele.nickname,
id: ele.id,
})
positionInfos.push(posInfo)
} else {
positionInfos.push(ele.positionInfo)
}
}
})
console.log(selectedUserIds)
if (selectedUserIds) {
if (props?.manageType === 'silence') {
let params = {
mode: 1, //12
group_id: dialogueParams.receiverId, //id
user_ids: selectedUserIds, //ids
}
console.log(params)
const resp = ServeGroupNoSpeak(params)
resp.then(({
code,
data
}) => {
console.log(data)
if (code == 200) {
useDialogueStore().updateGroupMembers()
} else {}
})
resp.catch(() => {})
} else if (props?.manageType === 'admin') {
if (props?.isCreateDepGroup === 1) {
// console.log(positionInfos)
groupTypeStore.groupAdmins.value = positionInfos
uni.navigateBack({
delta: 1,
})
} else {
if (
groupParams.groupInfo.group_type == 1 ||
groupParams.groupInfo.group_type == 3
) {
let params = {
mode: 1, //12
group_id: dialogueParams.receiverId, //id
user_ids: selectedUserIds,
}
console.log(params)
const resp = ServeGroupAssignAdmin(params)
resp.then(({
code,
data
}) => {
console.log(data)
if (code == 200) {
useDialogueStore().updateGroupMembers()
} else {}
})
resp.catch(() => {})
} else if (
groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4
) {
let params = {
source: 'app',
id: dialogueParams.receiverId,
deptInfos: groupParams.groupInfo.deptInfos,
positionInfos: positionInfos,
}
console.log(params)
const resp = ServeEditGroupAdmin(params)
resp.then(({
code,
data
}) => {
console.log(data)
if (code == 200) {
groupStore.ServeGroupDetail()
} else {}
})
resp.catch(() => {})
}
}
} else if (props?.manageType === 'removeMembers') {
let params = {
group_id: dialogueParams.receiverId, //id
members_ids: selectedUserIds, //id
}
console.log(params)
const resp = ServeRemoveMembersGroup(params)
resp.then(({
code,
data
}) => {
console.log(data)
if (code == 200) {
// console.error(-selectedUserIds.split(',').length)
emits('updateSelectedMembersNum', -selectedUserIds.split(',').length)
useDialogueStore().updateGroupMembers()
groupStore.ServeGroupDetail()
} else {}
})
resp.catch(() => {})
} else if (props?.manageType === 'mention') {
emits('getSelectResult', selectUserInfos)
}
}
}
//
defineExpose({
confirmSelectMembers,
})
</script>
<style lang="scss" scoped>
.my-self {
padding: 10rpx 60rpx;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
.my-self-left {
display: flex;
align-items: center;
margin-left: 8%;
}
.my-self-right {
color: #b4b4b4;
padding: 0.1875rem 0.375rem;
border: #b4b4b4 1px solid;
border-radius: 8rpx;
font-size: 0.875rem;
}
}
.select-member-by-alphabet {
.select-members {
padding: 20rpx 32rpx;
.search-member {
padding: 22rpx 16rpx;
background-color: #fff;
}
.member-list {
.member-list-alphabet-anchor-point {
position: fixed;
right: 32rpx;
top: 0;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.member-list-alphabet-anchor-point-each {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0 0 14rpx;
span {
width: 52rpx;
text-align: center;
line-height: 44rpx;
color: $theme-text;
}
}
}
.member-list-alphabet {
.member-list-alphabet-key {
background-color: #f3f3f3;
span {
line-height: 44rpx;
color: $theme-text;
}
}
}
}
}
}
</style>

View File

@ -5,7 +5,10 @@
<span class="text-[32rpx] font-regular">{{ props?.item?.label }}</span>
</div>
<div class="item-main-value" @click="toManagePage(props?.item)">
<span class="text-[32rpx] font-regular" v-if="props?.item?.value">
<span
class="text-[32rpx] font-regular"
v-if="props?.item?.value && props?.item?.value.length < 29"
>
{{ props?.item?.value }}
</span>
<img
@ -13,9 +16,10 @@
props?.item?.hasPointer &&
(props?.isManager ||
(!props?.isManager &&
props?.item?.label !== $t('chat.settings.groupName')))
(props?.item?.label !== $t('chat.settings.groupName') ||
props?.item?.label !== $t('chat.settings.groupType'))))
"
src="/src/static/image/chatSettings/pointer.png"
src="@/static/image/chatSettings/pointer.png"
/>
<tm-switch
:width="88"
@ -30,7 +34,13 @@
></tm-switch>
</div>
</div>
<div class="item-sub" v-if="props?.item?.subValue">
<div
class="item-sub"
v-if="props?.item?.value && props?.item?.value.length > 28"
>
<span class="text-[32rpx] font-regular">{{ props?.item?.value }}</span>
</div>
<div class="item-sub cab1" v-if="props?.item?.subValue">
<span class="text-[32rpx] font-regular">{{ props?.item?.subValue }}</span>
</div>
</div>
@ -61,7 +71,14 @@ const props = defineProps({
})
const emits = defineEmits(['toManagePage', 'changeSwitch'])
const toManagePage = (item) => {
emits('toManagePage', item.label)
if (
props?.item?.hasPointer &&
(props?.isManager ||
(!props?.isManager &&
props?.item?.label !== t('chat.settings.groupName')))
) {
emits('toManagePage', item.label)
}
}
const modelValue = computed(() => {
let switchStatus = false
@ -111,6 +128,7 @@ const changeSwitch = (e, item) => {
flex-direction: row;
align-items: center;
justify-content: flex-end;
padding: 0 0 0 20rpx;
span {
line-height: 44rpx;
color: #747474;
@ -125,14 +143,17 @@ const changeSwitch = (e, item) => {
.item-sub {
margin: 28rpx 0 0;
white-space: nowrap;
overflow: hidden;
max-width: 100%;
text-overflow: ellipsis;
span {
line-height: 44rpx;
color: #747474;
}
}
.cab1 {
white-space: nowrap;
overflow: hidden;
max-width: 100%;
text-overflow: ellipsis;
}
}
</style>

View File

@ -3,51 +3,126 @@
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ $t('chat.settings.editAvatar') }}
</span>
</div>
</tm-navbar>
<customNavbar :title="$t('chat.settings.editAvatar')"></customNavbar>
</template>
<div class="edit-group-info">
<div class="group-avatar">
<img :src="state.groupAvatar" />
<avatarModule
:mode="2"
:avatar="groupParams?.groupInfo?.avatar"
:groupType="groupParams?.groupInfo?.group_type"
:customStyle="{
width: '460rpx',
height: '460rpx',
borderRadius: '0',
}"
></avatarModule>
</div>
</div>
<customBtn :btnText="$t('button.text.edit')"></customBtn>
<customBtn
:btnText="$t('button.text.edit')"
@click="editAvatar"
:isLoading="state.isLoading"
></customBtn>
</ZPaging>
</div>
</div>
</template>
<script setup>
import avatarModule from '@/components/avatar-module/index.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue'
import { ServeEditGroup } from '@/api/group/index.js'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { onLoad } from '@dcloudio/uni-app'
import { reactive } from 'vue'
import { reactive, computed, watch } from 'vue'
import { useGroupStore, useDialogueStore, useDialogueListStore } from '@/store'
import { uploadImg } from '@/api/chat'
import { uniqueId } from '@/utils'
const groupStore = useGroupStore()
const groupParams = reactive({
groupInfo: computed(() => groupStore.groupInfo),
})
const dialogueStore = useDialogueStore()
const dialogueParams = reactive({
receiver_id: computed(() => dialogueStore.talk.receiver_id),
})
const state = reactive({
pageTitle: '', //
groupAvatar: '', //
groupName: '', //
isLoading: false, //
})
const onProgressFn = (progress, id) => {
console.log((progress.loaded / progress.total) * 100, 'progress')
useDialogueListStore().updateUploadProgress(
id,
(progress.loaded / progress.total) * 100,
)
}
onLoad((options) => {
console.log(options)
if (options.groupAvatar) {
state.groupAvatar = options.groupAvatar
}
})
//
const clearGroupNameInput = () => {
state.groupName = ''
}
//
const confirmEdit = () => {
console.log(state.groupName)
//
const editAvatar = () => {
uni.chooseImage({
sourceType: ['album'],
count: 1,
success: async (res) => {
console.log(res, 'res')
state.isLoading = true
res.tempFiles.forEach(async (file) => {
console.log(file)
let image = new Image()
image.src = URL.createObjectURL(file)
image.onload = () => {
const form = new FormData()
form.append('file', file)
form.append('source', 'fonchain-chat')
form.append('urlParam', `width=${image.width}&height=${image.height}`)
let randomId = uniqueId()
uploadImg(form, (e) => onProgressFn(e, randomId)).then(
({ status, data, msg }) => {
console.log(status, data, msg)
if (status == 0) {
let avatar = data.ori_url
let params = {
group_id: dialogueParams.receiver_id,
group_name: groupParams.groupInfo.group_name,
avatar: avatar,
}
console.log(params)
const resp = ServeEditGroup(params)
resp.then(({ code }) => {
if (code == 200) {
state.isLoading = false
groupStore.updateGroupInfo({
avatar: data.ori_url,
})
// uni.navigateBack({
// delta: 1,
// })
} else {
state.isLoading = false
}
})
resp.catch(() => {
state.isLoading = false})
} else {
state.isLoading = false
}
},
).catch(() => {
state.isLoading = false
})
}
})
},
})
}
</script>
<style scoped lang="scss">
@ -56,7 +131,7 @@ const confirmEdit = () => {
background-image: url('@/static/image/mine/background.png');
background-size: cover;
background-repeat: no-repeat;
background-position:center bottom;
background-position: center bottom;
}
.edit-group-info {
@ -70,10 +145,6 @@ const confirmEdit = () => {
flex-direction: row;
align-items: center;
justify-content: center;
img {
width: 460rpx;
height: 460rpx;
}
}
}
</style>

View File

@ -3,17 +3,21 @@
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ $t('chat.settings.editGroupName') }}
</span>
</div>
</tm-navbar>
<customNavbar
:title="$t('chat.settings.editGroupName')"
></customNavbar>
</template>
<div class="edit-group-info">
<div class="group-avatar">
<img :src="state.groupAvatar" />
<avatarModule
:mode="2"
:avatar="groupParams?.groupInfo?.avatar"
:groupType="groupParams?.groupInfo?.group_type"
:customStyle="{
width: '192rpx',
height: '192rpx',
}"
></avatarModule>
</div>
<div class="group-name">
<span class="text-[28rpx] font-medium">
@ -25,10 +29,13 @@
:placeholder="$t('edit.groupName.placeholder')"
placeholder-style="color:#B4B4B4;font-size:28rpx;font-weight:500;line-height:40rpx;"
v-model="state.groupName"
@input="handleGroupNameInput"
maxlength="20"
/>
<img
v-if="state.groupName"
class="groupName-input-clearBtn"
src="/src/static/image/chatSettings/clear-btn.png"
src="@/static/image/chatSettings/clear-btn.png"
@click="clearGroupNameInput"
/>
</div>
@ -46,6 +53,7 @@
</div>
</template>
<script setup>
import avatarModule from '@/components/avatar-module/index.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue'
import { ServeEditGroup } from '@/api/group/index.js'
import { useGroupStore, useDialogueStore } from '@/store'
@ -65,15 +73,11 @@ const dialogueParams = reactive({
const state = reactive({
pageTitle: '', //
groupAvatar: '', //
groupName: '', //
})
onLoad((options) => {
console.log(options)
if (options.groupAvatar) {
state.groupAvatar = options.groupAvatar
}
state.groupName = groupParams.groupInfo.group_name
})
@ -81,7 +85,12 @@ onLoad((options) => {
const clearGroupNameInput = () => {
state.groupName = ''
}
const handleGroupNameInput = (e) => {
if (state.groupName.length > 20) {
// 20
state.groupName = state.groupName.slice(0, 20)
}
}
//
const confirmEdit = () => {
console.log(state.groupName)
@ -91,6 +100,7 @@ const confirmEdit = () => {
let params = {
group_id: dialogueParams.receiver_id,
group_name: state.groupName,
avatar: groupParams.groupInfo.avatar,
}
console.log(params)
const resp = ServeEditGroup(params)
@ -125,15 +135,10 @@ const confirmEdit = () => {
.edit-group-info {
.group-avatar {
padding: 250rpx 0 88rpx;
border-radius: 50%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
img {
width: 192rpx;
height: 192rpx;
}
}
.group-name {
padding: 0 32rpx;
@ -149,7 +154,7 @@ const confirmEdit = () => {
height: 110rpx;
box-shadow: 0 6px 12px 2px rgba(188, 188, 188, 0.08);
padding: 0 74rpx 0 32rpx;
color: #B747474;
color: #747474;
font-size: 28rpx;
font-weight: 500;
line-height: 40rpx;

View File

@ -3,13 +3,7 @@
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ $t('chat.settings.groupAdmin') }}
</span>
</div>
</tm-navbar>
<customNavbar :title="$t('chat.settings.groupAdmin')"></customNavbar>
</template>
<div class="manage-group-admin">
<span class="manage-group-admin-title text-[28rpx] font-regular">
@ -23,14 +17,36 @@
>
<div
class="group-admin-list-each"
:style="{
padding:
groupParams?.groupInfo?.group_type == 1 ||
groupParams?.groupInfo?.group_type == 3
? '18rpx 26rpx 18rpx 14rpx'
: '',
}"
v-for="(item, index) in state?.groupAdminList"
:key="index"
>
<span>{{ item.deptPos }}</span>
<selectMemberItem
v-if="
groupParams?.groupInfo?.group_type == 1 ||
groupParams?.groupInfo?.group_type == 3
"
:groupType="groupParams.groupInfo.group_type"
:memberItem="item"
></selectMemberItem>
<span
v-if="
groupParams?.groupInfo?.group_type == 2 ||
groupParams?.groupInfo?.group_type == 4
"
>
{{ item.deptPos }}
</span>
<div class="group-admin-list-each-btns">
<img
v-if="item.is_mine"
src="/src/static/image/chatSettings/is-mine.png"
src="@/static/image/chatSettings/is-mine.png"
/>
<div
class="group-admin-list-each-btns-each"
@ -51,7 +67,7 @@
margin: state?.groupAdminList.length == 0 ? '20rpx 0 0' : '',
}"
>
<img src="/src/static/image/chatSettings/add-btn.png" />
<img src="@/static/image/chatSettings/add-btn.png" />
<span class="text-[28rpx] font-medium">
{{ $t('chat.manage.addAdmin') }}
</span>
@ -62,11 +78,15 @@
</div>
</template>
<script setup>
import selectMemberItem from '../components/select-member-item.vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { onLoad } from '@dcloudio/uni-app'
import { computed, onMounted, reactive, watch } from 'vue'
import { ServeEditGroupAdmin } from '@/api/group/index.js'
import {
ServeEditGroupAdmin,
ServeGroupAssignAdmin,
} from '@/api/group/index.js'
import { useGroupStore, useDialogueStore } from '@/store'
import { useAuth } from '@/store/auth'
@ -90,7 +110,7 @@ const state = reactive({
})
watch(
() => groupParams?.groupInfo,
[() => groupParams?.groupInfo, , () => dialogueParams?.adminList],
(newGroupInfo) => {
getGroupAdminList()
},
@ -107,38 +127,59 @@ onMounted(async () => {
//
const getGroupAdminList = () => {
let myPositionsList = []
if (groupParams?.groupInfo?.groupAdminList?.length > 0) {
groupParams?.groupInfo?.groupAdminList.forEach((groupAdminItem) => {
if (userInfo?.value?.PositionUsers?.length > 0) {
userInfo?.value?.PositionUsers.forEach((item) => {
// console.log(item.DepartmentId + '-' + item.PositionID)
if (item.PositionID === groupAdminItem.position_id) {
myPositionsList.push(groupAdminItem)
}
})
}
})
}
if (
groupParams?.groupInfo?.groupAdminList?.length > 0 &&
myPositionsList?.length > 0
groupParams?.groupInfo?.group_type == 1 ||
groupParams?.groupInfo?.group_type == 3
) {
groupParams.groupInfo.groupAdminList.forEach((ele) => {
myPositionsList.forEach((item) => {
if (ele.deptPos === item.deptPos) {
if (dialogueParams?.adminList?.length > 0) {
dialogueParams?.adminList.forEach((item) => {
if (item?.erp_user_id == userInfo?.value?.ID) {
item.is_mine = true
if (myPositionsList?.length == 1) {
item.cannotRemove = true
}
item.cannotRemove = true
}
})
})
}
state.groupAdminList = dialogueParams?.adminList
} else if (
groupParams?.groupInfo?.group_type == 2 ||
groupParams?.groupInfo?.group_type == 4
) {
let myPositionsList = []
if (groupParams?.groupInfo?.groupAdminList?.length > 0) {
groupParams?.groupInfo?.groupAdminList.forEach((groupAdminItem) => {
if (userInfo?.value?.PositionUsers?.length > 0) {
userInfo?.value?.PositionUsers.forEach((item) => {
// console.log(item.DepartmentId + '-' + item.PositionID)
if (
item.PositionID === groupAdminItem.position_id &&
item.DepartmentId === groupAdminItem.dept_id
) {
myPositionsList.push(groupAdminItem)
}
})
}
})
}
if (
groupParams?.groupInfo?.groupAdminList?.length > 0 &&
myPositionsList?.length > 0
) {
groupParams.groupInfo.groupAdminList.forEach((ele) => {
myPositionsList.forEach((item) => {
if (ele.deptPos === item.deptPos) {
item.is_mine = true
if (myPositionsList?.length == 1) {
item.cannotRemove = true
}
}
})
})
}
state.groupAdminList = groupParams?.groupInfo?.groupAdminList
}
state.groupAdminList = groupParams?.groupInfo?.groupAdminList
}
//
//
const toSelectMembersPage = () => {
uni.navigateTo({
url: '/pages/chatSettings/groupManage/selectMembers?manageType=admin',
@ -147,35 +188,54 @@ const toSelectMembersPage = () => {
//
const removeGroupAdmin = (adminItem) => {
let positionInfos = []
if (state?.groupAdminList?.length > 0) {
state?.groupAdminList.forEach((item) => {
if (
item.dept_id != adminItem.dept_id ||
item.position_id != adminItem.position_id
) {
positionInfos.push({
position_id: item.position_id,
position_name: item.deptPos,
})
}
})
if (
groupParams?.groupInfo?.group_type == 1 ||
groupParams?.groupInfo?.group_type == 3
) {
let params = {
source: 'app',
id: dialogueParams.receiverId,
deptInfos: groupParams.groupInfo.deptInfos,
positionInfos: positionInfos,
mode: 2, //12
group_id: dialogueParams.receiverId, //id
user_ids: String(adminItem.id),
}
console.log(params)
const resp = ServeEditGroupAdmin(params)
const resp = ServeGroupAssignAdmin(params)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
groupStore.ServeGroupDetail()
useDialogueStore().updateGroupMembers()
} else {
}
})
resp.catch(() => {})
} else if (
groupParams?.groupInfo?.group_type == 2 ||
groupParams?.groupInfo?.group_type == 4
) {
let positionInfos = []
if (state?.groupAdminList?.length > 0) {
positionInfos = state?.groupAdminList.filter((item) => {
return (
item.dept_id != adminItem.dept_id ||
item.position_id != adminItem.position_id
)
})
let params = {
source: 'app',
id: dialogueParams.receiverId,
deptInfos: groupParams.groupInfo.deptInfos,
positionInfos: positionInfos,
}
console.log(params)
const resp = ServeEditGroupAdmin(params)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
groupStore.ServeGroupDetail()
} else {
}
})
resp.catch(() => {})
}
}
}
</script>
@ -196,18 +256,23 @@ const removeGroupAdmin = (adminItem) => {
.group-admin-list {
.group-admin-list-each {
padding: 36rpx 0;
margin: 0 32rpx;
padding: 36rpx 26rpx 36rpx 14rpx;
margin: 0 18rpx;
border-bottom: 1px solid $theme-border-color;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
::v-deep .select-member-item {
padding: 0;
border-bottom: 0;
}
.group-admin-list-each-btns {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
flex-shrink: 0;
img {
width: 52rpx;
height: 52rpx;

View File

@ -0,0 +1,76 @@
<template>
<div class="outer-layer manage-group-deps-page">
<div class="root">
<ZPaging
ref="zPaging"
:show-scrollbar="false"
:use-virtual-list="true"
:virtual-list-col="5"
:auto="false"
:refresher-enabled="false"
:loading-more-enabled="false"
>
<template #top>
<customNavbar :title="$t('pageTitle.view.deps')"></customNavbar>
</template>
<div class="group-deps-list">
<div
class="group-deps-list-each"
v-for="item in groupParams.groupInfo.deptInfos"
:key="item.dept_id"
>
<span>{{ item.dept_name }}</span>
</div>
</div>
</ZPaging>
</div>
</div>
</template>
<script setup>
import settingFormItem from '../components/settingFormItem.vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { onLoad } from '@dcloudio/uni-app'
import { computed, onMounted, reactive } from 'vue'
import { useGroupStore } from '@/store'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const groupStore = useGroupStore()
const groupParams = reactive({
groupInfo: computed(() => groupStore.groupInfo),
})
const state = reactive({})
onLoad((options) => {
console.log(options)
})
onMounted(() => {
console.log(groupParams.groupInfo.deptInfos)
})
</script>
<style scoped lang="scss">
.outer-layer {
flex: 1;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-repeat: no-repeat;
}
.group-deps-list {
margin: 20rpx 32rpx;
background-color: #fff;
.group-deps-list-each {
padding: 34rpx 32rpx;
border-bottom: 1px solid $theme-border-color;
span {
font-size: 28rpx;
font-weight: 500;
color: #333;
line-height: 40rpx;
}
}
}
</style>

View File

@ -11,16 +11,12 @@
:loading-more-enabled="false"
>
<template #top>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ $t('chat.settings.groupMember') }}
</span>
</div>
</tm-navbar>
<customNavbar :title="$t('chat.settings.groupMember')"></customNavbar>
</template>
<div class="group-members-list">
<groupMemberList
:groupType="groupParams?.groupInfo?.group_type"
:is_manager="groupParams?.groupInfo?.is_manager"
:memberList="talkParams?.memberList"
></groupMemberList>
</div>
@ -33,13 +29,18 @@ import groupMemberList from '../components/groupMembersList.vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
import { ref, computed, reactive } from 'vue'
import { useDialogueStore } from '@/store'
import { useDialogueStore, useGroupStore } from '@/store'
const dialogueStore = useDialogueStore()
const talkParams = reactive({
memberList: computed(() => dialogueStore.members),
})
const groupStore = useGroupStore()
const groupParams = reactive({
groupInfo: computed(() => groupStore.groupInfo),
})
const zPaging = ref()
useZPaging(zPaging)
</script>

View File

@ -3,13 +3,7 @@
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ $t('chat.settings.groupGag') }}
</span>
</div>
</tm-navbar>
<customNavbar :title="$t('chat.settings.groupGag')"></customNavbar>
</template>
<div class="manage-group-silence">
<span class="manage-group-silence-title text-[28rpx] font-regular">
@ -48,7 +42,7 @@
class="add-silence-member-btn chat-settings-card"
@click="toSelectMembersPage"
>
<img src="/src/static/image/chatSettings/add-btn.png" />
<img src="@/static/image/chatSettings/add-btn.png" />
<span class="text-[28rpx] font-medium">
{{ $t('chat.manage.addSilenceMember') }}
</span>

View File

@ -3,11 +3,9 @@
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<tm-navbar
<customNavbar
:hideBack="state.editMode === 3 ? false : true"
hideHome
title=""
:leftWidth="220"
:title="$t('chat.settings.groupNotice')"
>
<template #left v-if="state.editMode !== 3">
<div class="nav-bar-cancel-btn">
@ -16,12 +14,7 @@
</span>
</div>
</template>
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ $t('chat.settings.groupNotice') }}
</span>
</div>
<template #right>
<template #right v-if="state.isManager === 'true'">
<div
v-if="state.editMode !== 3"
class="nav-bar-done-btn"
@ -42,11 +35,11 @@
</span>
</div>
</template>
</tm-navbar>
</customNavbar>
</template>
<div class="notice-text-area">
<div class="notice-view-area">
<div class="notice-view-info" v-if="state.editMode !== 1">
<div class="notice-view-info" v-if="state.editMode !== 1 && state?.groupNoticeObj?.content">
<div class="notice-creater-avatar">
<img :src="state?.groupNoticeObj?.avatar" />
</div>
@ -60,9 +53,12 @@
</div>
</div>
<div class="notice-view-content" v-if="state.editMode === 3">
<span class="text-[32rpx] font-regular">
<span class="text-[32rpx] font-regular" v-if="state?.groupNoticeObj?.content">
{{ state?.groupNoticeObj?.content }}
</span>
<span class="text-[32rpx] font-regular color-[#898989]" v-else>
{{ $t('groupNotice.notice.empty') }}
</span>
</div>
<wd-textarea
size="large"
@ -84,6 +80,7 @@ import useConfirm from '@/components/x-confirm/useConfirm.js'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { reactive, computed, onMounted } from 'vue'
import { useGroupStore, useDialogueStore } from '@/store'
import { onLoad } from '@dcloudio/uni-app'
import {
ServeEditGroupNotice,
ServeDeleteGroupNotice,
@ -105,7 +102,8 @@ const state = reactive({
groupNoticeObj: null, //
groupNotice: '', //
canDoComplete: false, //
editMode: 1, // 123
editMode: 3, // 123
isManager: 'false', //
})
onMounted(() => {
@ -114,6 +112,14 @@ onMounted(() => {
state.editMode = 3
state.groupNoticeObj = groupParams.groupNotice[0]
inputGroupNotice(groupParams?.groupNotice[0]?.content)
} else if( state.isManager === 'true'){
state.editMode = 1
}
})
onLoad((options) => {
console.log(options)
if (options?.is_manager) {
state.isManager = options?.is_manager
}
})

View File

@ -11,488 +11,137 @@
@scroll="onScroll"
>
<template #top>
<tm-navbar
:hideBack="false"
hideHome
title=""
:leftWidth="220"
id="topArea"
>
<div class="navBar-title flex flex-col items-center justify-center">
<span
class="text-[34rpx] font-medium"
v-if="state.manageType === 'silence'"
>
{{ $t('chat.manage.addSilenceMember') }}
</span>
<span
class="text-[34rpx] font-medium"
v-if="state.manageType === 'admin'"
>
{{ $t('chat.manage.addAdmin') }}
</span>
<span
class="text-[34rpx] font-medium"
v-if="state.manageType === 'searchRecord'"
>
{{ $t('search.condition.member') }}
</span>
</div>
</tm-navbar>
<div id="topArea">
<customNavbar :title="pageTitle"></customNavbar>
</div>
</template>
<div class="select-members">
<div class="search-member">
<customInput
:searchText="state.searchText"
@inputSearchText="inputSearchText"
></customInput>
</div>
<div
class="member-list"
:style="{
padding: state.manageType === 'searchRecord' ? '20rpx 0 0' : '',
}"
>
<div class="member-list-alphabet-anchor-point">
<div
class="member-list-alphabet-anchor-point-each"
v-for="(alphabetItem, alphabetIndex) in state?.alphabet"
:key="alphabetIndex"
:style="{
margin: state?.alphabet?.length > 17 ? '0' : '',
}"
@click.stop="scrollToView(alphabetItem)"
>
<span
class="text-[32rpx] font-regular"
:style="{
color:
state.currentAlphabet === alphabetItem ? '#7A58DE' : '',
}"
>
{{ alphabetItem }}
</span>
</div>
</div>
<div
class="member-list-alphabet"
v-for="(alphabetItem, alphabetIndex) in state.resultMemberList"
:key="alphabetIndex"
>
<div
class="member-list-alphabet-key"
:style="{
padding:
state.manageType === 'searchRecord' ? '10rpx 30rpx' : '',
}"
v-if="alphabetItem?.memberList?.length > 0"
:id="alphabetItem.key"
:ref="
(el) => {
if (el) alphabetElementRefs[alphabetIndex] = el
}
"
>
<span class="text-[32rpx] font-regular">
{{ alphabetItem.key }}
</span>
</div>
<div v-if="alphabetItem?.memberList?.length > 0">
<div
class="member-list-each"
v-for="(item, index) in alphabetItem?.memberList"
:key="index"
>
<tm-checkbox-group v-model="item.checkArr">
<selectMemberItem
:groupType="groupParams.groupInfo.group_type"
:memberItem="item"
@clickItem="handleClickItem(item)"
:manageType="state.manageType"
:itemStyle="
state.manageType === 'searchRecord' ? 'list' : 'card'
"
>
<template
#left
v-if="state.manageType !== 'searchRecord'"
>
<tm-checkbox
:round="10"
:color="
item?.checkArr?.length > 0 ? '#46299d' : '#B4B4B4'
"
:outlined="
item?.checkArr?.length > 0 ||
(state.manageType === 'silence' &&
item.is_mute === 1) ||
(state.manageType === 'admin' &&
(item.leader === 1 || item.leader === 2))
? false
: true
"
:value="item.id"
:disabled="
(state.manageType === 'silence' &&
item.is_mute === 1) ||
(state.manageType === 'admin' &&
(item.leader === 1 || item.leader === 2))
"
></tm-checkbox>
</template>
</selectMemberItem>
</tm-checkbox-group>
</div>
</div>
</div>
</div>
</div>
<selectMemberByAlphabet
:manageType="state.manageType"
:isCreateDepGroup="state.isCreateDepGroup"
ref="selectMemberByAlphabetRef"
:selectAreaHeight="state.selectAreaHeight"
@updateSelectedMembersNum="updateSelectedMembersNum"
></selectMemberByAlphabet>
<template #bottom v-if="state.manageType !== 'searchRecord'">
<customBtn
:isBottom="true"
:btnText="$t('ok')"
@clickBtn="confirmSilenceMember"
></customBtn>
<div id="footArea">
<customBtn
v-if="state.manageType !== 'removeMembers'"
:isBottom="true"
:btnText="$t('ok')"
@clickBtn="confirmSelectMembers"
></customBtn>
<div
class="confirm-btn-area"
v-if="state.manageType === 'removeMembers'"
>
<div class="confirm-btn-area-statistic-text">
<span class="text-[28rpx] font-medium">
{{
$t('select.member.num') +
'' +
state.selectedMembersNum +
$t('statistic.unit.person')
}}
</span>
</div>
<customBtn
:btnText="$t('ok')"
@clickBtn="confirmSelectMembers"
:disabled="state.selectedMembersNum == 0 ? true : false"
></customBtn>
</div>
</div>
</template>
</ZPaging>
</div>
</div>
</template>
<script setup>
import customInput from '@/components/custom-input/custom-input.vue'
import selectMemberItem from '../components/select-member-item.vue'
import selectMemberByAlphabet from '../components/selectMemberByAlphabet.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
import { computed, onMounted, reactive, ref, watch, nextTick } from 'vue'
import { computed, reactive, ref, onMounted, nextTick } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { ServeGroupNoSpeak, ServeEditGroupAdmin } from '@/api/group/index.js'
import { useDialogueStore, useGroupStore, useGroupTypeStore } from '@/store'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const selectMemberByAlphabetRef = ref(null)
const zPaging = ref()
useZPaging(zPaging)
const groupStore = useGroupStore()
const groupParams = reactive({
groupInfo: computed(() => groupStore.groupInfo),
})
const groupTypeStore = useGroupTypeStore()
const groupTypeParams = reactive({
departmentAllPositions: computed(() => groupTypeStore.departmentAllPositions),
})
const dialogueStore = useDialogueStore()
const dialogueParams = reactive({
memberList: computed(() => {
const lowerCaseSearchText = state?.searchText.toLowerCase()
return dialogueStore.members.filter((item) =>
state?.searchText
? item.nickname.toLowerCase().includes(lowerCaseSearchText)
: true,
)
}),
receiverId: computed(() => dialogueStore.talk.receiver_id),
})
const state = reactive({
searchText: '', //
manageType: '', //
alphabet: [], //A-Z
resultMemberList: [], //A-Z
currentAlphabet: 'A', //A-Z
scrollDirection: '', //
isAssign: false, //view
selectedMembersNum: 0, //
isCreateDepGroup: 0, //
selectAreaHeight: 0, //
})
watch(
() => dialogueParams?.memberList,
(newMemberList) => {
assembleAlphabetMemberList(newMemberList)
},
{ deep: true },
)
watch(
() => groupParams?.groupInfo,
(newGroupInfo) => {
assembleAlphabetMemberList(dialogueParams?.memberList)
},
{ deep: true },
)
//A-Z tag
const alphabetElementRefs = ref([])
//
let observer
onLoad((options) => {
console.log(options)
// console.log(options)
if (options.manageType) {
state.manageType = options.manageType
assembleAlphabetMemberList(dialogueParams?.memberList)
}
if (options.isCreateDepGroup) {
state.isCreateDepGroup = Number(options.isCreateDepGroup)
}
})
onMounted(() => {
dialogueParams.memberList.forEach((ele) => {
ele.checkArr = []
})
const options = {
root: null, // 使
threshold: 1.0, // 100%
}
observer = new IntersectionObserver(handleIntersection, options)
nextTick(() => {
watch(
alphabetElementRefs,
(newAlphabetElementRefs) => {
if (Array.isArray(newAlphabetElementRefs)) {
newAlphabetElementRefs.forEach((el, index) => {
observeElement(el, index)
})
let selectAreaHeight = uni.getSystemInfoSync().windowHeight
// console.log(':', uni.getSystemInfoSync().windowHeight)
const topAreaQuery = uni.createSelectorQuery()
topAreaQuery
.select('#topArea')
.boundingClientRect((res) => {
if (res) {
// console.log(':', res.height)
selectAreaHeight = selectAreaHeight - res.height
}
},
{ immediate: true, deep: true },
)
if (alphabetElementRefs.value.length > 0) {
alphabetElementRefs.value.forEach((el, index) =>
observeElement(el, index),
)
}
})
.exec()
const footAreaQuery = uni.createSelectorQuery()
footAreaQuery
.select('#footArea')
.boundingClientRect((res) => {
if (res) {
// console.log(':', res.height)
selectAreaHeight = selectAreaHeight - res.height
}
})
.exec()
// console.log(selectAreaHeight)
state.selectAreaHeight = selectAreaHeight + 'px'
})
})
//
const handleIntersection = (entries) => {
if (state.isAssign) {
state.isAssign = false
return
//
const pageTitle = computed(() => {
let page_title = ''
if (state.manageType === 'silence') {
page_title = t('chat.manage.addSilenceMember')
} else if (state.manageType === 'admin') {
page_title = t('chat.manage.addAdmin')
} else if (state.manageType === 'searchRecord') {
page_title = t('search.condition.member')
} else if (state.manageType === 'removeMembers') {
page_title = t('select.member.remove')
}
entries.forEach((entry) => {
if (!entry.isIntersecting && state.scrollDirection === 'down') {
state.currentAlphabet = entry.target.id
} else if (entry.isIntersecting && state.scrollDirection === 'up') {
if (state?.alphabet?.length > 1) {
state?.alphabet.forEach((item, index) => {
if (item === entry.target.id && index > 0) {
state.currentAlphabet = state?.alphabet[index - 1]
}
})
} else {
state.currentAlphabet = entry.target.id
}
}
})
}
return page_title
})
//
const observeElement = (el, index) => {
if (el && observer) {
observer.observe(el)
//
const confirmSelectMembers = () => {
if (selectMemberByAlphabetRef.value) {
selectMemberByAlphabetRef.value.confirmSelectMembers()
}
}
//
const inputSearchText = (e) => {
// console.log(e)
state.searchText = e
}
//item
const handleClickItem = (item) => {
if (
(state.manageType === 'silence' && item.is_mute === 1) ||
(state.manageType === 'admin' && (item.leader === 1 || item.leader === 2))
) {
return
}
let itemList = dialogueParams.memberList
if (
state.manageType === 'admin' &&
(groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4)
) {
itemList = state.resultMemberList[0].memberList
}
itemList.forEach((ele) => {
if (ele.id == item.id) {
ele.checkArr = ele.checkArr?.length > 0 ? [] : [item.id]
}
})
}
//
const confirmSilenceMember = () => {
let selectedUserIds = ''
let itemList = dialogueParams.memberList
let positionInfos = []
if (
state.manageType === 'admin' &&
(groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4)
) {
itemList = state.resultMemberList[0].memberList
}
itemList.forEach((ele) => {
if (ele.checkArr?.length > 0) {
if (!selectedUserIds) {
selectedUserIds = String(ele.checkArr[0])
} else {
selectedUserIds += ',' + ele.checkArr[0]
}
}
if (
ele.checkArr?.length > 0 ||
(ele.leader && (ele.leader == 1 || ele.leader == 2))
) {
positionInfos.push(ele.positionInfo)
}
})
console.log(selectedUserIds)
if (selectedUserIds) {
if (state.manageType === 'silence') {
let params = {
mode: 1, //12
group_id: dialogueParams.receiverId, //id
user_ids: selectedUserIds, //ids
}
console.log(params)
const resp = ServeGroupNoSpeak(params)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
useDialogueStore().updateGroupMembers()
} else {
}
})
resp.catch(() => {})
} else if (state.manageType === 'admin') {
if (
groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4
) {
let params = {
source: 'app',
id: dialogueParams.receiverId,
deptInfos: groupParams.groupInfo.deptInfos,
positionInfos: positionInfos,
}
console.log(params)
const resp = ServeEditGroupAdmin(params)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
groupStore.ServeGroupDetail()
} else {
}
})
resp.catch(() => {})
}
}
}
}
//A-Z
const assembleAlphabetMemberList = async (newMemberList) => {
if (state.manageType === 'searchRecord') {
const resultMemberList = ref([])
const alphabet = Array.from({ length: 26 }, (_, i) =>
String.fromCharCode(i + 65),
)
let tempAlphabet = []
alphabet.forEach((letter) => {
const matchedItems = newMemberList.filter((item) => item.key === letter)
if (matchedItems.length > 0) {
tempAlphabet.push(letter)
}
resultMemberList.value.push({
key: letter,
memberList: matchedItems.length ? matchedItems : [],
})
})
state.alphabet = tempAlphabet
state.resultMemberList = resultMemberList
} else {
if (
(groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4) &&
state.manageType === 'admin'
) {
let departmentIdsArr = []
if (groupParams?.groupInfo?.deptInfos?.length > 0) {
groupParams.groupInfo.deptInfos.forEach((item) => {
departmentIdsArr.push(item.dept_id)
})
}
await groupTypeStore.getPositionByDepartment({
IDs: departmentIdsArr,
})
let departmentAllPositions = []
if (groupTypeParams?.departmentAllPositions?.value?.length > 0) {
groupTypeParams?.departmentAllPositions?.value?.forEach((item) => {
item?.AllPositions?.forEach((positionItem) => {
departmentAllPositions.push({
nickname: item.name + '-' + positionItem.name,
id: item.ID + '-' + positionItem.ID,
checkArr: [],
positionInfo: {
position_id: positionItem.ID,
position_name: positionItem.name,
},
})
})
})
}
if (groupParams?.groupInfo?.groupAdminList?.length > 0) {
groupParams?.groupInfo?.groupAdminList.forEach((item) => {
departmentAllPositions.forEach((idsItem) => {
if (item.dept_id + '-' + item.position_id == idsItem.id) {
idsItem.leader = 1
}
})
})
}
// console.log(departmentAllPositions)
state.resultMemberList = [
{
key: '',
memberList: departmentAllPositions,
},
]
} else {
state.resultMemberList = [
{
key: '',
memberList: newMemberList,
},
]
}
}
}
//view
const scrollToView = (alphabet) => {
state.currentAlphabet = alphabet
state.isAssign = true
console.log()
zPaging.value?.scrollIntoViewById(
alphabet,
document.getElementById('topArea').clientHeight
? document.getElementById('topArea').clientHeight - 1
: 80,
)
}
//
const onScroll = (e) => {
if (e.detail.deltaY < 0) {
state.scrollDirection = 'down'
} else if (e.detail.deltaY > 0) {
state.scrollDirection = 'up'
} else {
state.scrollDirection = ''
}
//
const updateSelectedMembersNum = (numChange) => {
state.selectedMembersNum = state.selectedMembersNum + numChange
}
</script>
<style scoped lang="scss">
@ -503,45 +152,27 @@ const onScroll = (e) => {
background-repeat: no-repeat;
}
.select-members {
padding: 20rpx 32rpx;
.search-member {
padding: 22rpx 16rpx;
background-color: #fff;
.confirm-btn-area {
background-color: #fff;
padding: 14rpx 32rpx 72rpx;
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
.confirm-btn-area-statistic-text {
margin: 18rpx 0 0;
span {
line-height: 40rpx;
color: $theme-text;
}
}
.member-list {
.member-list-alphabet-anchor-point {
position: fixed;
right: 32rpx;
top: 0;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.member-list-alphabet-anchor-point-each {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0 0 14rpx;
span {
width: 52rpx;
text-align: center;
line-height: 44rpx;
color: $theme-text;
}
}
}
.member-list-alphabet {
.member-list-alphabet-key {
background-color: #f3f3f3;
span {
line-height: 44rpx;
color: $theme-text;
}
}
}
::v-deep .custom-btn-class {
padding: 18rpx 104rpx !important;
}
::v-deep .is-disabled {
background-color: #e6e6e6 !important;
color: #bebebe !important;
}
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +1,76 @@
<template>
<div>
<div @click="cellClick" :class="['chatItem', props.data.is_top === 1 ? 'isTop' : '']">
<div
@click="cellClick"
:class="['chatItem', props.data.is_top === 1 ? 'isTop' : '']"
>
<div>
<tm-checkbox v-if="props.isMultiSelect" :round="10" :modelValue="props.data.isCheck"
@update:modelValue="props.data.isCheck = !props.data.isCheck" :size="42" :color="props.data.isCheck ? '#46299D' : '#BABABA'"></tm-checkbox>
<tm-checkbox
v-if="props.isMultiSelect"
:round="10"
:modelValue="props.data.isCheck"
@update:modelValue="props.data.isCheck = !props.data.isCheck"
:size="32"
:color="props.data.isCheck ? '#46299D' : '#BABABA'"
class="pr-[4rpx]"
></tm-checkbox>
</div>
<div class="avatarImg">
<tm-image :width="96" :height="96" :round="12" :src="props.data.avatar"></tm-image>
<avatarModule
:mode="props?.data?.group_type === 0 ? 1 : 2"
:avatar="props?.data?.avatar"
:groupType="props?.data?.group_type"
:userName="props?.data?.name"
:customStyle="{ width: '96rpx', height: '96rpx' }"
:customTextStyle="{
fontSize: '32rpx',
fontWeight: 'bold',
color: '#fff',
lineHeight: '44rpx',
}"
></avatarModule>
</div>
<div class="chatInfo">
<div class="chatInfo_1">
<div class="flex items-center">
<div class="text-[#000000] text-[32rpx] font-bold opacity-90">{{ props.data.name }}</div>
<div
class="text-[#171717] text-[32rpx] font-medium leading-[44rpx]"
>
<span>{{ props.data.name }}</span>
<span v-if="props.data.talk_type === 2">
{{ props.data.group_member_num }}
</span>
<span v-if="props.data.group_type === 2" class="depTag tag">
部门
</span>
<span v-if="props.data.group_type === 3" class="projectTag tag">
项目
</span>
<span v-if="props.data.group_type === 4" class="companyTag tag">
公司
</span>
</div>
</div>
</div>
</div>
</div>
<div v-if="props.index !== talkStore.talkItems.length - 1" class="divider"></div>
<div
v-if="props.index !== talkStore.talkItems.length - 1"
class="divider"
></div>
</div>
</template>
<script setup>
import { ref, reactive, defineProps, defineEmits } from "vue"
import dayjs from "dayjs";
import avatarModule from '@/components/avatar-module/index.vue'
import { ref, reactive, defineProps, defineEmits } from 'vue'
import dayjs from 'dayjs'
import { beautifyTime } from '@/utils/datetime'
import { ServeClearTalkUnreadNum } from '@/api/chat'
import { useTalkStore, useDialogueStore } from '@/store'
import { useSessionMenu } from '@/hooks'
const talkStore = useTalkStore()
const {
onToTopTalk
} = useSessionMenu()
const { onToTopTalk } = useSessionMenu()
const dialogueStore = useDialogueStore()
const emit = defineEmits(['itemClick'])
const props = defineProps({
@ -48,25 +88,22 @@ const props = defineProps({
type: Boolean,
default: false,
},
});
})
const cellClick = () => {
emit('itemClick', props.data)
}
</script>
<style lang="scss" scoped>
.chatItem {
width: 100%;
height: 154rpx;
padding: 30rpx 16rpx;
padding: 24rpx 18rpx;
display: flex;
align-items: center;
&.isTop {
background-color: #F3F3F3;
background-color: #f3f3f3;
}
}
@ -77,7 +114,7 @@ const cellClick = () => {
.chatInfo {
flex: 1;
margin-left: 20rpx;
margin-left: 16rpx;
}
.chatInfo_1 {
@ -97,19 +134,33 @@ const cellClick = () => {
font-size: 28rpx;
color: #000000;
opacity: 40%;
}
.companyTag {
width: 76rpx;
height: 38rpx;
border: 1px solid #7A58DE;
font-size: 24rpx;
.tag {
display: inline-flex;
align-items: center;
text-align: center;
// margin-left: 10rpx;
margin-top: 4rpx;
vertical-align: top;
height: 38rpx;
line-height: 38rpx;
padding: 0 10rpx;
font-size: 24rpx;
border-radius: 6rpx;
color: #7A58DE;
font-weight: bold;
margin-left: 12rpx;
}
.companyTag {
border: 1px solid #7a58de;
color: #7a58de;
}
.depTag {
border: 1px solid #377ec6;
color: #377ec6;
}
.projectTag {
border: 1px solid #c1681c;
color: #c1681c;
}
.textEllipsis {

View File

@ -1,74 +1,134 @@
<template>
<div class="outer-layer">
<div>
<tm-navbar :hideBack="true" hideHome :leftWidth="320">
<template v-slot:left>
<div @click="handleClose" class="ml-[48rpx] text-[34rpx] leading-[40rpx]">
关闭
<div class="choose-chat-page">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<customNavbar
:title="$t('pageTitle.select.shareChat')"
:hideBack="true"
>
<template #left>
<div
@click="handleClose"
class="ml-[42rpx] text-[34rpx] leading-[48rpx] font-regular"
>
{{ $t('button.text.close') }}
</div>
</template>
<template #right>
<div
v-if="!isMultiSelect"
@click="isMultiSelect = true"
class="mr-[36rpx] text-[34rpx] leading-[48rpx] font-regular"
>
{{ $t('button.multiple.choice') }}
</div>
<div v-else class="mr-[36rpx] btnBox">
<customBtn
:btnText="
selectItems.length
? $t('button.text.done') + `(${selectItems.length})`
: $t('button.text.done')
"
:disabled="!selectItems.length"
@clickBtn="handleFinish"
></customBtn>
</div>
</template>
</customNavbar>
</template>
<div class="choose-chat">
<div class="searchRoot">
<customInput></customInput>
</div>
<div class="contentRoot">
<chatItem
v-for="(item, index) in items"
:key="item.index_name"
:index="index"
:data="item"
:isMultiSelect="isMultiSelect"
@itemClick="itemClick"
/>
</div>
</div>
<tm-modal
v-model:show="showWin"
:height="forwardModalHeight"
:round="4"
okColor="#FFFFFF"
@ok="handleOk"
@cancel="handleCancel"
:okText="'发送'"
>
<template v-slot:title>
<div
class="w-full pl-[30rpx] leading-[48rpx] mt-[30rpx] font-bold text-[32rpx]"
>
发送给:
</div>
</template>
<template v-slot:default>
<div class="text-[34rpx] font-bold ml-[-80rpx] leading-[40rpx]">
选择一个聊天
<div
class="pl-[22rpx] pr-[32rpx] pt-[10rpx] flex items-center justify-start flex-wrap"
>
<div
v-if="selectItemsModal.length > 1"
v-for="item in selectItemsModal"
class="w-1/4 mt-[20rpx] flex flex-col items-center justify-center"
>
<div class="w-[96rpx] h-[96rpx] rounded-[60rpx] flex-center">
<tm-image
:width="96"
:height="96"
:round="12"
:src="item.avatar"
></tm-image>
</div>
<div
class="mt-[8rpx] text-[#666666] text-[24rpx] w-[94rpx] truncate"
>
<span>{{ item.name }}</span>
</div>
</div>
<div v-else class="w-full flex items-center justify-start">
<avatarModule
:mode="2"
:avatar="selectItemsModal[0].avatar"
:groupType="selectItemsModal[0].group_type"
:customStyle="{ width: '96rpx', height: '96rpx' }"
></avatarModule>
<div class="ml-[16rpx] text-[#161616] text-[32rpx] font-bold">
{{ selectItemsModal[0].name }}
<span v-if="selectItemsModal[0].talk_type === 2">{{ selectItemsModal[0].group_member_num }}</span>
<span v-if="selectItemsModal[0].group_type === 2" class="depTag tag">
部门
</span>
<span v-if="selectItemsModal[0].group_type === 3" class="projectTag tag">
项目
</span>
<span v-if="selectItemsModal[0].group_type === 4" class="companyTag tag">
公司
</span>
</div>
</div>
</div>
</template>
<template v-slot:right>
<div v-if="!isMultiSelect" @click="isMultiSelect = true" class="mr-[36rpx] text-[34rpx] leading-[40rpx]">
多选
</div>
<div v-else class="mr-[36rpx] btnBox">
<tm-button @click="handleFinish" color="#5B3FB1" :fontSize="34" :disabled="!selectItems.length"
:margin="[10]" :shadow="0" size="small" :width="selectItems.length ? 162 : 116"
:label="selectItems.length ? `完成(${selectItems.length})` : '完成'">
</tm-button>
</div>
</template>
</tm-navbar>
</div>
<div class="root">
<div class="searchRoot">
<tm-input placeholder="请输入…" color="#F9F9FD" :round="1" prefix="tmicon-search" prefixColor="#46299D"></tm-input>
</div>
<div class="contentRoot">
<chatItem v-for="(item, index) in items" :key="item.index_name" :index="index" :data="item"
:isMultiSelect="isMultiSelect" @itemClick="itemClick" />
</div>
</div>
<tm-modal v-model:show="showWin" :height="forwardModalHeight" :round="4" okColor="#FFFFFF" @ok="handleOk"
@cancel="handleCancel">
<template v-slot:title>
<div class="w-full pl-[30rpx] leading-[48rpx] mt-[30rpx] font-bold text-[32rpx]">
发送给:
</div>
</template>
<template v-slot:default>
<div class="pl-[22rpx] pr-[32rpx] pt-[10rpx] flex items-center justify-start flex-wrap">
<div v-if="selectItemsModal.length > 1" v-for="item in selectItemsModal"
class="w-1/4 mt-[20rpx] flex flex-col items-center justify-center">
<div class="w-[96rpx] h-[96rpx] rounded-[60rpx] flex-center">
<tm-image :width="96" :height="96" :round="12" :src="item.avatar"></tm-image>
</div>
<div class="mt-[8rpx] text-[#666666] text-[24rpx] w-[94rpx] truncate">{{ item.name }}</div>
</div>
<div v-else class="w-full flex items-center justify-start">
<div class="w-[96rpx] h-[96rpx] rounded-[60rpx] flex-center">
<tm-image :width="96" :height="96" :round="12" :src="selectItemsModal[0].avatar"></tm-image>
</div>
<div class="ml-[16rpx] text-[#161616] text-[32rpx] font-bold ">{{ selectItemsModal[0].name }}</div>
</div>
</div>
</template>
</tm-modal>
</tm-modal>
</ZPaging>
</div>
</template>
<script setup>
import { ref, watch, computed, onMounted, onUnmounted } from 'vue';
import { onShow, onLoad } from "@dcloudio/uni-app";
import { useChatList } from "@/store/chatList/index.js";
import { useAuth } from "@/store/auth";
import customInput from '@/components/custom-input/custom-input.vue'
import avatarModule from '@/components/avatar-module/index.vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue'
import { ref, watch, computed, onMounted, onUnmounted } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import { useTalkStore, useUserStore, useDialogueStore } from '@/store'
import chatItem from './components/chatItem.vue';
import addCircle from "@/static/image/chatList/addCircle.png";
import chatItem from './components/chatItem.vue'
import addCircle from '@/static/image/chatList/addCircle.png'
import lodash from 'lodash'
const talkStore = useTalkStore()
@ -87,13 +147,13 @@ const items = ref([])
// })
const selectItems = computed(() => {
return items.value.filter(item => item.isCheck)
return items.value.filter((item) => item.isCheck)
})
const selectItemsModal = ref([])
const itemClick = (item) => {
console.log(item);
console.log(item)
if (isMultiSelect.value) {
item.isCheck = !item.isCheck
return false
@ -104,7 +164,7 @@ const itemClick = (item) => {
}
const handleFinish = () => {
console.log(selectItems.value);
console.log(selectItems.value)
selectItemsModal.value = selectItems.value
if (selectItemsModal.value.length > 4) {
forwardModalHeight.value = 560
@ -123,7 +183,6 @@ const handleClose = () => {
const handleOk = () => {
let msg_ids = dialogueStore.forwardMessages.map((item) => item.msg_id)
let user_ids = []
let group_ids = []
@ -139,24 +198,28 @@ const handleOk = () => {
mode: dialogueStore.forwardType,
message_ids: msg_ids,
uids: user_ids,
gids: group_ids
gids: group_ids,
})
uni.navigateBack()
}
const handleCancel = () => {
console.log('cancel');
console.log('cancel')
}
watch(() => talkStore, (newValue, oldValue) => {
console.log(talkStore);
}, { deep: true, immediate: true })
watch(
() => talkStore,
(newValue, oldValue) => {
console.log(talkStore)
},
{ deep: true, immediate: true },
)
onMounted(() => {
talkStore.loadTalkList()
items.value = lodash.cloneDeep(talkStore.talkItems)
console.log(talkStore.talkItems)
items.value = lodash.cloneDeep(talkStore.talkItems).filter(item=>item.is_dismiss === 0 && item.is_quit === 0)
})
onUnmounted(() => {
dialogueStore.setForwardType('')
@ -164,24 +227,20 @@ onUnmounted(() => {
})
</script>
<style scoped lang="scss">
uni-page-body,
page {
::v-deep .zp-paging-container-content {
height: 100%;
display: flex;
}
.outer-layer {
overflow-y: auto;
.choose-chat {
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
background-size: cover;
padding: 0 32rpx 20rpx 32rpx;
padding: 20rpx 32rpx;
display: flex;
flex-direction: column;
}
.root {
flex: 1;
padding: 20rpx 0;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-position: bottom center;
background-attachment: fixed;
}
.searchRoot {
@ -193,35 +252,65 @@ page {
margin-top: 20rpx;
background-color: #fff;
}
.tag{
display: inline-flex;
align-items: center;
text-align: center;
// margin-left: 10rpx;
margin-top: 4rpx;
vertical-align: top;
height: 38rpx;
line-height: 38rpx;
padding: 0 10rpx;
font-size: 24rpx;
border-radius: 6rpx;
font-weight: bold;
}
.companyTag {
border: 1px solid #7a58de;
color: #7a58de;
}
.depTag {
border: 1px solid #377ec6;
color: #377ec6;
}
.projectTag {
border: 1px solid #c1681c;
color: #c1681c;
}
.btnBox {
:deep(.button) {
&[disabled="true"] {
background-color: #F3F3F3 !important;
color: #BABABA !important;
}
::v-deep .custom-btn-class {
padding: 6rpx 24rpx !important;
width: unset !important;
min-width: unset !important;
height: unset !important;
}
::v-deep .wd-button__text {
font-size: 34rpx !important;
font-weight: 400 !important;
line-height: 48rpx !important;
}
}
:deep(.noNvueBorder.round-bl-4) {
border-top: 1rpx solid #E7E7E7 !important;
background-color: #FFFFFF !important;
border-top: 1rpx solid #e7e7e7 !important;
background-color: #ffffff !important;
uni-text {
color: #1A1A1A !important;
color: #1a1a1a !important;
font-size: 32rpx !important;
line-height: 48rpx !important;
}
}
:deep(.noNvueBorder.round-br-4) {
border-top: 1rpx solid #E7E7E7 !important;
border-left: 1rpx solid #E7E7E7 !important;
background-color: #FFFFFF !important;
border-top: 1rpx solid #e7e7e7 !important;
border-left: 1rpx solid #e7e7e7 !important;
background-color: #ffffff !important;
uni-text {
font-weight: bold;
color: #46299D !important;
color: #46299d !important;
font-size: 32rpx !important;
line-height: 48rpx !important;
}

View File

@ -1,193 +1,222 @@
<template>
<div class="outer-layer">
<div>
<tm-navbar :hideBack="false" hideHome title="选择部门"> </tm-navbar>
</div>
<div class="root">
<div
class="w-full h-[1134rpx] mb-[20rpx] pl-[32rpx] pr-[32rpx] pb-[20rpx] overflow-y-auto"
>
<div class="pl-[16rpx] pr-[16rpx] pt-[22rpx] pb-[24rpx] bg-[#FFFFFF]">
<tm-input
placeholder="请输入…"
color="#F9F9FD"
:round="1"
prefix="tmicon-search"
prefixColor="#46299D"
searchBgColor="#F9F9FD"
focusColor="#F9F9FD"
v-model="searchVal"
></tm-input>
<div
v-if="crumbs.length"
class="w-full overflow-x-auto mt-[22rpx] leading-[48rpx] text-[#2F2F2F] flex items-center no-scrollbar"
ref="crumbsContainer"
>
<div class="choose-deps-page">
<zPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<customNavbar :title="$t('pageTitle.select.department')"></customNavbar>
</template>
<div class="choose-deps">
<div class="w-full pl-[32rpx] pr-[32rpx] overflow-y-auto">
<div class="pl-[16rpx] pr-[16rpx] pt-[22rpx] pb-[24rpx] bg-[#FFFFFF]">
<tm-input
placeholder="请输入…"
color="#F9F9FD"
:round="1"
prefix="tmicon-search"
prefixColor="#46299D"
searchBgColor="#F9F9FD"
focusColor="#F9F9FD"
v-model="searchVal"
></tm-input>
<div
v-for="(item, index) in crumbs"
class="flex items-center text-[28rpx] leading-[48rpx] whitespace-nowrap"
:class="[
index === crumbsIndex ? 'text-[#747474]' : 'text-[#46299D]',
index === 0 ? '' : 'ml-[12rpx]',
]"
@click="handleCrumbsClick(index)"
v-if="crumbs.length"
class="w-full overflow-x-auto mt-[22rpx] leading-[48rpx] text-[#2F2F2F] flex items-center no-scrollbar"
ref="crumbsContainer"
>
<div>{{ item.name }}</div>
<div
v-if="index !== crumbs.length - 1"
class="ml-[20rpx] flex items-center mb-[2rpx]"
v-for="(item, index) in crumbs"
class="flex items-center text-[28rpx] leading-[48rpx] whitespace-nowrap"
:class="[
index === crumbsIndex ? 'text-[#747474]' : 'text-[#46299D]',
index === 0 ? '' : 'ml-[12rpx]',
]"
@click="handleCrumbsClick(index)"
>
<tm-icon
name="tmicon-angle-right"
:font-size="20"
:color="index !== crumbs.length - 1 ? '#7A58DE' : '#C1B4EA'"
></tm-icon>
</div>
</div>
</div>
</div>
<div
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
@click="()=>allCheck(allCheckStatus)"
>
<div>
<checkBox
:disabled="currentCrumbs?.sons?.length === 0"
:modelValue="allCheckStatus"
></checkBox>
</div>
<div class="font-bold text-[28rpx] leading-[54rpx] ml-[20rpx]">
全选
</div>
</div>
<div
v-if="currentCrumbs?.sons?.length"
v-for="item in currentCrumbs?.sons"
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
>
<div class="w-full flex items-center justify-between">
<div
class="flex items-center"
>
<div>
<checkBox
:disabled="!item?.sons?.length"
v-model="item.checkStatus"
@change="(val) => checkItemChange(item, val)"
></checkBox>
</div>
<div class="font-bold text-[28rpx] leading-[54rpx] ml-[20rpx]">
{{ item.name }}
</div>
</div>
<div v-if="item.sons?.length" class="flex items-center mr-[32rpx]">
<div class="vDivider mr-[32rpx]"></div>
<div @click="() => toNextLevel(item)" class="flex items-center">
<div class="mr-[12rpx]">
<tm-image
:width="26"
:height="26"
:src="item.checkStatus !== 'checked' ? downDep : downDepDis"
></tm-image>
</div>
<div>{{ item.name }}</div>
<div
class="text-[28rpx] leading-[54rpx] font-bold"
:class="
item.checkStatus !== 'checked'
? 'text-[#46299D]'
: 'text-[#C1B4EA]'
"
v-if="index !== crumbs.length - 1"
class="ml-[20rpx] flex items-center mb-[2rpx]"
>
下级
<tm-icon
name="tmicon-angle-right"
:font-size="20"
:color="index !== crumbs.length - 1 ? '#7A58DE' : '#C1B4EA'"
></tm-icon>
</div>
</div>
</div>
</div>
<div
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
@click="() => allCheck(allCheckStatus)"
>
<div>
<checkBox
:disabled="currentCrumbs?.sons?.length === 0"
:modelValue="allCheckStatus"
></checkBox>
</div>
<div class="font-bold text-[28rpx] leading-[54rpx] ml-[20rpx]">
全选
</div>
</div>
<div
v-if="currentCrumbs?.sons?.length"
v-for="item in currentCrumbs?.sons"
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
>
<div class="w-full flex items-center justify-between">
<div class="flex items-center">
<div>
<checkBox
:disabled="!item?.sons?.length"
v-model="item.checkStatus"
@change="(val) => checkItemChange(item, val)"
></checkBox>
</div>
<div class="font-bold text-[28rpx] leading-[54rpx] ml-[20rpx]">
{{ item.name }}
</div>
</div>
<div
v-if="item.sons?.length"
class="flex items-center mr-[32rpx]"
>
<div class="vDivider mr-[32rpx]"></div>
<div @click="() => toNextLevel(item)" class="flex items-center">
<div class="mr-[12rpx]">
<tm-image
:width="26"
:height="26"
:src="
item.checkStatus !== 'checked' ? downDep : downDepDis
"
></tm-image>
</div>
<div
class="text-[28rpx] leading-[54rpx] font-bold"
:class="
item.checkStatus !== 'checked'
? 'text-[#46299D]'
: 'text-[#C1B4EA]'
"
>
下级
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="h-[162rpx] pl-[32rpx] pr-[32rpx] bg-[#FFFFFF]">
<div class="mt-[14rpx] flex justify-between">
<div class="flex flex-col">
<div @click="openDrawer" class="flex items-center text-[28rpx] leading-[60rpx] text-[#000000] font-bold">
<div>已选择部门数</div>
<div>{{ allCheckedList.length }}</div>
<div class="ml-[28rpx]">
<tm-icon :fontSize="24" color="#46299D" name="tmicon-angle-up"></tm-icon>
<template #bottom>
<div class="h-[162rpx] pl-[32rpx] pr-[32rpx] bg-[#FFFFFF]">
<div class="mt-[14rpx] flex justify-between">
<div class="flex flex-col">
<div
@click="openDrawer"
class="flex items-center text-[28rpx] leading-[60rpx] text-[#000000] font-bold"
>
<div>已选择部门数</div>
<div>{{ allCheckedList.length }}</div>
<div class="ml-[28rpx]">
<tm-icon
:fontSize="24"
color="#46299D"
name="tmicon-angle-up"
></tm-icon>
</div>
</div>
<div
class="text-[24rpx] leading-[24rpx] text-[#7A58DE] w-[280rpx] truncate"
>
{{ allCheckedList.map((v) => v.name).toString() }}
</div>
</div>
<div class="text-[24rpx] leading-[24rpx] text-[#7A58DE] w-[280rpx] truncate">
{{ allCheckedList.map(v=>v.name).toString() }}
<div class="btnBox">
<tm-button
@click="handleConfirm"
color="#46299D"
:disabled="!allCheckedList.length"
disabledColor="#E6E6E6"
:margin="[0]"
:shadow="0"
:width="264"
:height="76"
size="large"
label="确定"
></tm-button>
</div>
</div>
<div class="btnBox">
<tm-button
@click="handleConfirm"
color="#46299D"
:disabled="!allCheckedList.length"
disabledColor="#E6E6E6"
:margin="[0]"
:shadow="0"
:width="264"
:height="76"
size="large"
label="确定"
>
</tm-button>
</div>
</div>
</div>
</div>
<tm-drawer
placement="bottom"
v-model:show="showWin"
:height="800"
:hideHeader="true"
:round="5"
>
<div class="flex flex-col w-full h-full pt-[36rpx] pl-[32rpx] pr-[32rpx] leading-[60rpx]" >
<div class="text-[32rpx] font-bold flex items-center justify-between" >
<div class="flex items-center ml-[10rpx]">
<div>已选择部门数</div>
<div>{{ allCheckedList.length }}</div>
</div>
<tm-drawer
placement="bottom"
v-model:show="showWin"
:height="800"
:hideHeader="true"
:round="5"
>
<div
class="text-[#7A58DE] mr-[10rpx]"
@click="()=>showWin = false"
class="flex flex-col w-full h-full pt-[36rpx] pl-[32rpx] pr-[32rpx] leading-[60rpx]"
>
确定
</div>
</div>
<div class="flex-1 pb-[20rpx] overflow-y-auto pt-[30rpx]" >
<div
v-for="(item,index) in allCheckedList"
class="flex flex-col"
>
<div v-if="index ===0" class="divider" ></div>
<div
class="flex items-center justify-between mt-[36rpx] font-bold text-[#000000] leading-[54rpx] mb-[34rpx]"
class="text-[32rpx] font-bold flex items-center justify-between"
>
<div class="text-[28rpx] ml-[10rpx]">{{ item.name }}</div>
<div class="diyBtn">
<tm-button @click="()=>deleteNode(item)" :disabled="userDepIds.includes(item.ID)" :margin="[10]" :shadow="0" text size="small" :width="106" :height="50" outlined label="移除"></tm-button>
<div class="flex items-center ml-[10rpx]">
<div>已选择部门数</div>
<div>{{ allCheckedList.length }}</div>
</div>
<div
class="text-[#7A58DE] mr-[10rpx]"
@click="() => (showWin = false)"
>
确定
</div>
</div>
<div class="flex-1 pb-[20rpx] overflow-y-auto pt-[30rpx]">
<div
v-for="(item, index) in allCheckedList"
class="flex flex-col"
>
<div v-if="index === 0" class="divider"></div>
<div
class="flex items-center justify-between mt-[36rpx] font-bold text-[#000000] leading-[54rpx] mb-[34rpx]"
>
<div class="text-[28rpx] ml-[10rpx]">{{ item.name }}</div>
<div class="diyBtn">
<tm-button
@click="() => deleteNode(item)"
:disabled="userDepIds.includes(item.ID)"
:margin="[10]"
:shadow="0"
text
size="small"
:width="106"
:height="50"
outlined
label="移除"
></tm-button>
</div>
</div>
<div class="divider"></div>
</div>
</div>
<div class="divider" ></div>
</div>
</div>
</div>
</tm-drawer>
</tm-drawer>
</template>
</zPaging>
</div>
</template>
<script setup>
import { ref, watch, computed, onMounted,nextTick } from "vue";
import { onShow, onLoad } from "@dcloudio/uni-app";
import { useChatList } from "@/store/chatList/index.js";
import { useAuth } from "@/store/auth";
import { useTalkStore, useUserStore } from "@/store";
import { useGroupTypeStore } from "@/store/groupType";
import downDep from "@/static/image/chatList/downDep.png";
import downDepDis from "@/static/image/chatList/downDepDis.png";
import checkBox from "@/components/checkBox/index.vue";
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { ref, watch, computed, onMounted, nextTick } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import { useTalkStore, useUserStore } from '@/store'
import { useGroupTypeStore } from '@/store/groupType'
import downDep from '@/static/image/chatList/downDep.png'
import downDepDis from '@/static/image/chatList/downDepDis.png'
import checkBox from '@/components/checkBox/index.vue'
import lodash from 'lodash'
const {
@ -197,201 +226,204 @@ const {
crumbs,
crumbsIndex,
depCheckedKeys,
} = useGroupTypeStore();
} = useGroupTypeStore()
const userStore = useUserStore()
const searchVal = ref("");
const crumbsContainer = ref(null);
const showWin = ref(false);
const searchVal = ref('')
const crumbsContainer = ref(null)
const showWin = ref(false)
const getAllCheckedNodes = (node, checkedNodes = []) => {
if (node.checkStatus === 'checked') {
checkedNodes.push(node);
checkedNodes.push(node)
}
if (node.sons && Array.isArray(node.sons)) {
node.sons.forEach(son => getAllCheckedNodes(son, checkedNodes));
node.sons.forEach((son) => getAllCheckedNodes(son, checkedNodes))
}
return checkedNodes;
};
return checkedNodes
}
const userDepIds = computed(() => {
return userStore.deps.map(v=>v.dept_id);
});
return userStore.deps.map((v) => v.dept_id)
})
const allCheckedList = computed(() => {
const checkedNodes = [];
depTreeMyList.value.forEach(node => getAllCheckedNodes(node, checkedNodes));
console.log("checkedNodes", checkedNodes);
const checkedNodes = []
depTreeMyList.value.forEach((node) => getAllCheckedNodes(node, checkedNodes))
console.log('checkedNodes', checkedNodes)
return checkedNodes;
return checkedNodes
})
const currentCrumbs = computed(() => {
if (crumbs.value[crumbsIndex.value] ) {
if (searchVal.value && searchVal.value !== "") {
if (crumbs.value[crumbsIndex.value]) {
if (searchVal.value && searchVal.value !== '') {
let filterSons = crumbs.value[crumbsIndex.value].sons.filter((item) =>
item.name.includes(searchVal.value)
);
return {
...crumbs.value[crumbsIndex.value],
sons: filterSons,
};
item.name.includes(searchVal.value),
)
return {
...crumbs.value[crumbsIndex.value],
sons: filterSons,
}
}
console.log("allCheckedList", crumbs.value[crumbsIndex.value]);
return crumbs.value[crumbsIndex.value];
console.log('allCheckedList', crumbs.value[crumbsIndex.value])
return crumbs.value[crumbsIndex.value]
}
return {}
});
})
const allCheckStatus = computed(() => {
if (!currentCrumbs.value.sons) {
return "noChecked";
return 'noChecked'
}
const allChecked = currentCrumbs.value.sons.every((son) => son.checkStatus === "checked");
const someChecked =currentCrumbs.value.sons.some(
(son) =>
son.checkStatus === "checked" || son.checkStatus === "halfChecked"
);
if (allChecked) {
return "checked";
const allChecked = currentCrumbs.value.sons.every(
(son) => son.checkStatus === 'checked',
)
const someChecked = currentCrumbs.value.sons.some(
(son) => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
)
if (allChecked) {
return 'checked'
} else if (someChecked) {
return "halfChecked";
return 'halfChecked'
} else {
return "noChecked";
return 'noChecked'
}
})
const findNodeById = (node, targetId) => {
if (node.ID === targetId) {
return node;
return node
}
if (node.sons && Array.isArray(node.sons)) {
for (const son of node.sons) {
const found = findNodeById(son, targetId);
const found = findNodeById(son, targetId)
if (found) {
return found;
return found
}
}
}
return null;
};
return null
}
const findParentNode = (node, targetId) => {
if (!node.sons || !Array.isArray(node.sons)) return null;
if (!node.sons || !Array.isArray(node.sons)) return null
for (const son of node.sons) {
if (son.ID === targetId) {
return node;
return node
}
const parent = findParentNode(son, targetId);
const parent = findParentNode(son, targetId)
if (parent) {
return parent;
return parent
}
}
return null;
};
return null
}
//
const updateParentStatus = (node) => {
const parent = findParentNode(depTreeMyList.value[0], node.ID);
if (!parent) return;
if (parent.checkStatus !== "checked"){
const parent = findParentNode(depTreeMyList.value[0], node.ID)
if (!parent) return
if (parent.checkStatus !== 'checked') {
const allChecked =
parent.sons &&
Array.isArray(parent.sons) &&
parent.sons.every((son) => son.checkStatus === "checked");
parent.sons.every((son) => son.checkStatus === 'checked')
const someChecked =
parent.sons &&
Array.isArray(parent.sons) &&
parent.sons.some(
(son) =>
son.checkStatus === "checked" || son.checkStatus === "halfChecked"
);
son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
)
if (allChecked) {
parent.checkStatus = "halfChecked";
parent.checkStatus = 'halfChecked'
} else if (someChecked) {
parent.checkStatus = "halfChecked";
parent.checkStatus = 'halfChecked'
} else {
parent.checkStatus = "noChecked";
parent.checkStatus = 'noChecked'
}
}
updateParentStatus(parent);
};
updateParentStatus(parent)
}
//
const updateNodeStatus = (node) => {
if (!node.sons || !Array.isArray(node.sons)) return;
const allChecked = node.sons.every(son => son.checkStatus === 'checked');
const someChecked = node.sons.some(son => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked');
if (!node.sons || !Array.isArray(node.sons)) return
const allChecked = node.sons.every((son) => son.checkStatus === 'checked')
const someChecked = node.sons.some(
(son) => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
)
if (allChecked) {
node.checkStatus = 'halfChecked';
node.checkStatus = 'halfChecked'
} else if (someChecked) {
node.checkStatus = 'halfChecked';
node.checkStatus = 'halfChecked'
} else {
node.checkStatus = 'noChecked';
node.checkStatus = 'noChecked'
}
};
}
//
const updateCheckStatus = (node, status) => {
node.checkStatus = status;
node.checkStatus = status
if (node.sons && Array.isArray(node.sons) && node.sons.length > 0) {
node.sons.forEach((son) => updateCheckStatus(son, status));
node.sons.forEach((son) => updateCheckStatus(son, status))
}
};
}
const checkItemChange = (item, val) => {
//
updateCheckStatus(item, val);
updateCheckStatus(item, val)
//
updateParentStatus(item);
};
updateParentStatus(item)
}
const deleteNode = (item) => {
const treeNode = findNodeById(depTreeMyList.value[0], item.ID);
if (!treeNode) return;
treeNode.checkStatus = "noChecked";
updateNodeStatus(treeNode);
updateParentStatus(treeNode);
};
const treeNode = findNodeById(depTreeMyList.value[0], item.ID)
if (!treeNode) return
treeNode.checkStatus = 'noChecked'
updateNodeStatus(treeNode)
updateParentStatus(treeNode)
}
const toNextLevel = async (item) => {
if (item.checkStatus !== "checked") {
crumbs.value.push(item);
crumbsIndex.value++;
await nextTick();
if (item.checkStatus !== 'checked') {
crumbs.value.push(item)
crumbsIndex.value++
await nextTick()
if (crumbsContainer.value) {
crumbsContainer.value.scrollLeft = crumbsContainer.value.scrollWidth;
crumbsContainer.value.scrollLeft = crumbsContainer.value.scrollWidth
}
}
};
}
const handleCrumbsClick = (index) => {
crumbsIndex.value = index;
crumbs.value = crumbs.value.slice(0, index + 1);
};
crumbsIndex.value = index
crumbs.value = crumbs.value.slice(0, index + 1)
}
const allCheck = (status) => {
let statusT = 'noChecked';
if (status === "checked") {
statusT = "noChecked";
let statusT = 'noChecked'
if (status === 'checked') {
statusT = 'noChecked'
} else {
statusT = "checked";
statusT = 'checked'
}
currentCrumbs.value.sons.forEach((item) => {
const itemT = findNodeById(depTreeMyList.value[0], item.ID)
if (!itemT) return;
checkItemChange(itemT, statusT);
});
};
if (!itemT) return
checkItemChange(itemT, statusT)
})
}
const openDrawer = () => {
showWin.value = true;
if (allCheckedList.length>0) {
showWin.value = true
if (allCheckedList.length > 0) {
}
};
}
// watch(() => depTreeMyList.value, (newValue, oldValue) => {
// console.log("depTreeMyList", newValue);
@ -404,54 +436,48 @@ const openDrawer = () => {
const handleConfirm = () => {
depCheckedKeys.value = lodash.cloneDeep(allCheckedList.value)
console.log("depCheckedKeys", depCheckedKeys.value);
console.log('depCheckedKeys', depCheckedKeys.value)
uni.navigateBack();
uni.navigateBack()
}
const initCheckedKeys = () => {
depCheckedKeys.value.forEach((item) => {
const node = findNodeById(depTreeMyList.value[0], item.ID);
const node = findNodeById(depTreeMyList.value[0], item.ID)
if (node) {
node.checkStatus = "checked";
updateParentStatus(node);
node.checkStatus = 'checked'
updateParentStatus(node)
}
});
};
})
}
const init = async () => {
crumbsIndex.value = 0;
await getDepsTreeMy();
crumbs.value = depTreeMyList.value.length ? [depTreeMyList.value[0]] : [];
initCheckedKeys();
};
crumbsIndex.value = 0
await getDepsTreeMy()
crumbs.value = depTreeMyList.value.length ? [depTreeMyList.value[0]] : []
initCheckedKeys()
}
onMounted(() => {
init();
});
init()
})
</script>
<style scoped lang="scss">
uni-page-body,
page {
::v-deep .zp-paging-container-content {
height: 100%;
}
.outer-layer {
overflow-y: auto;
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
background-size: cover;
padding-bottom: 0;
display: flex;
flex-direction: column;
}
.root {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-top: 18rpx;
overflow: hidden;
.choose-deps-page {
.choose-deps {
flex: 1;
display: flex;
flex-direction: column;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
padding: 20rpx 0;
}
}
.divider {
height: 1rpx;
background-color: #707070;
@ -478,42 +504,42 @@ page {
background-repeat: no-repeat;
border-radius: 12rpx;
&.firstPanel {
background-image: url("@/static/image/chatList/zu6033@2x.png");
background-image: url('@/static/image/chatList/zu6033@2x.png');
}
&.secondPanel {
background-image: url("@/static/image/chatList/zu6031@2x.png");
background-image: url('@/static/image/chatList/zu6031@2x.png');
margin-top: 28rpx;
margin-bottom: 28rpx;
}
&.thirdPanel {
background-image: url("@/static/image/chatList/zu6032@2x.png");
background-image: url('@/static/image/chatList/zu6032@2x.png');
}
&.activePanel {
box-shadow: 0 0 0 3rpx #46299d;
}
}
.btnBox {
:deep(uni-button[disabled="true"]) {
:deep(uni-button[disabled='true']) {
color: #bebebe !important;
}
}
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.no-scrollbar::-webkit-scrollbar {
display: none; /* Chrome, Safari, and Opera */
display: none; /* Chrome, Safari, and Opera */
}
.diyBtn {
:deep(uni-button) {
color: #191919 !important;
border: 1rpx solid #D6D6D8 !important;
background-color: #FFFFFF !important;
&[disabled="true"] {
color: #BEBEBE !important;
border: 1rpx solid #E6E6E6 !important;
background-color: #E6E6E6 !important;
border: 1rpx solid #d6d6d8 !important;
background-color: #ffffff !important;
&[disabled='true'] {
color: #bebebe !important;
border: 1rpx solid #e6e6e6 !important;
background-color: #e6e6e6 !important;
}
}
}

View File

@ -1,159 +1,220 @@
<template>
<div class="outer-layer">
<div>
<tm-navbar :hideBack="false" hideHome title="选择群类型">
</tm-navbar>
</div>
<div class="root">
<div class="w-full h-[1134rpx] mb-[20rpx] pl-[32rpx] pr-[32rpx] overflow-y-auto" >
<div class=" pl-[32rpx] pr-[32rpx] pt-[44rpx] pb-[42rpx] bg-[#FFFFFF]" >
<div class="text-[40rpx] leading-[54rpx] text-[#2F2F2F] font-bold" >
群类型保存后将不可修改
</div>
<div class="text-[28rpx] leading-[54rpx] text-[#46299D] mt-[16rpx]" >
请创建过程中正确选择
</div>
<div class="mt-[54rpx] w-full h-[872rpx]" >
<div @click="groupActiveIndex = 0" class="groupCard firstPanel" :class="groupActiveIndex === 0?'activePanel':''">
<div class="w-full h-full pt-[64rpx] pl-[36rpx]">
<div class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold" >
普通群
</div>
<div class="text-[24rpx] leading-[36rpx] text-[#939393] w-[216rpx]" >
员工线上沟通专用群
离职后自动退群
<div class="choose-group-type-page">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<customNavbar :title="$t('pageTitle.select.groupType')"></customNavbar>
</template>
<div class="choose-group-type">
<div class="w-full h-[1134rpx] mb-[20rpx] pl-[32rpx] pr-[32rpx]">
<div class="pl-[32rpx] pr-[32rpx] pt-[44rpx] pb-[42rpx] bg-[#FFFFFF]">
<div class="text-[40rpx] leading-[54rpx] text-[#2F2F2F] font-bold">
群类型保存后将不可修改
</div>
<div class="text-[28rpx] leading-[54rpx] text-[#46299D] mt-[16rpx]">
请创建过程中正确选择
</div>
<div class="mt-[54rpx] w-full h-[872rpx]">
<div
@click="groupActiveIndex = 0;depCheckedKeys = []"
class="groupCard firstPanel"
:class="groupActiveIndex === 0 ? 'activePanel' : ''"
>
<div class="w-full h-full pt-[64rpx] pl-[36rpx]">
<div
class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold"
>
普通群
</div>
<div
class="text-[24rpx] leading-[36rpx] text-[#939393] w-[216rpx]"
>
员工线上沟通专用群 离职后自动退群
</div>
</div>
</div>
</div>
<div @click="groupActiveIndex = 1" class="groupCard secondPanel" :class="groupActiveIndex === 1?'activePanel':''">
<div class="w-full h-full pr-[36rpx] flex flex-col items-end justify-center">
<div class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold" >
部门群
</div>
<div class="text-[24rpx] leading-[36rpx] text-[#939393] w-[288rpx] text-end" >
指定部门员工入职自动进群
离职后自动退群
</div>
<div v-if="!depCheckedKeys.length" @click="chooseDep" class="text-[24rpx] leading-[36rpx] text-[#C1B4EA] flex items-center mt-[16rpx]" >
<div :class="groupActiveIndex === 1?'text-[#7A58DE]':'text-[#C1B4EA]'" >选择部门</div>
<div class="ml-[20rpx]">
<tm-icon name="tmicon-angle-right" :font-size="18" :color="groupActiveIndex === 1?'#7A58DE':'#C1B4EA'"></tm-icon>
<div
v-if="isHasPermission"
@click="groupActiveIndex = 1"
class="groupCard secondPanel"
:class="groupActiveIndex === 1 ? 'activePanel' : ''"
>
<div
class="w-full h-full pr-[36rpx] flex flex-col items-end justify-center"
>
<div
class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold"
>
部门群
</div>
</div>
<div v-else class="text-[24rpx] leading-[36rpx] flex flex-col justify-center items-end mt-[16rpx] mb-[22rpx]" >
<div class="max-w-[336rpx] truncate" >
{{ depCheckedKeys.map(v=>v.name).toString() }}
<div
class="text-[24rpx] leading-[36rpx] text-[#939393] w-[288rpx] text-end"
>
指定部门员工入职自动进群 离职后自动退群
</div>
<div @click="chooseDep" class="flex items-center" >
<div class="text-[#7A58DE]" >查看全部</div>
<div
v-if="!depCheckedKeys.length"
@click="chooseDep"
class="text-[24rpx] leading-[36rpx] text-[#C1B4EA] flex items-center mt-[16rpx]"
>
<div
:class="
groupActiveIndex === 1
? 'text-[#7A58DE]'
: 'text-[#C1B4EA]'
"
>
选择部门
</div>
<div class="ml-[20rpx]">
<tm-icon name="tmicon-angle-right" :font-size="18" :color="groupActiveIndex === 1?'#7A58DE':'#C1B4EA'"></tm-icon>
<tm-icon
name="tmicon-angle-right"
:font-size="18"
:color="groupActiveIndex === 1 ? '#7A58DE' : '#C1B4EA'"
></tm-icon>
</div>
</div>
<div
v-else
class="text-[24rpx] leading-[36rpx] flex flex-col justify-center items-end mt-[16rpx] mb-[22rpx]"
>
<div class="max-w-[336rpx] truncate">
{{ depCheckedKeys.map((v) => v.name).toString() }}
</div>
<div @click="chooseDep" class="flex items-center">
<div class="text-[#7A58DE]">查看全部</div>
<div class="ml-[20rpx]">
<tm-icon
name="tmicon-angle-right"
:font-size="18"
:color="
groupActiveIndex === 1 ? '#7A58DE' : '#C1B4EA'
"
></tm-icon>
</div>
</div>
</div>
</div>
</div>
</div>
<div @click="groupActiveIndex = 2" class="groupCard thirdPanel" :class="groupActiveIndex === 2?'activePanel':''">
<div class="w-full h-full pt-[64rpx] pl-[36rpx]">
<div class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold" >
项目群
</div>
<div class="text-[24rpx] leading-[36rpx] text-[#939393] w-[216rpx]" >
项目成员沟通专用群
离职后自动退群
<div
v-if="isCreateProjecy"
@click="groupActiveIndex = 2;depCheckedKeys = [];"
class="groupCard thirdPanel"
:class="groupActiveIndex === 2 ? 'activePanel' : ''"
>
<div class="w-full h-full pt-[64rpx] pl-[36rpx]">
<div
class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold"
>
项目群
</div>
<div
class="text-[24rpx] leading-[36rpx] text-[#939393] w-[216rpx]"
>
项目成员沟通专用群 离职后自动退群
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="h-[162rpx] flex justify-center bg-[#FFFFFF]">
<div class="mt-[14rpx] btnBox" >
<tm-button
@click="handleConfirm"
color="#46299D"
:disabled="confirmBtnStatus"
disabledColor="#E6E6E6"
:margin="[0]"
:shadow="0"
:width="426"
:height="76"
size="large"
label="确定"
>
</tm-button>
</div>
</div>
</div>
<template #bottom>
<customBtn
:isBottom="true"
:btnText="$t('ok')"
@click="handleConfirm"
:disabled="confirmBtnStatus"
></customBtn>
</template>
</ZPaging>
</div>
</template>
<script setup>
import { ref, watch, computed } from "vue";
import { onShow, onLoad } from "@dcloudio/uni-app";
import { useChatList } from "@/store/chatList/index.js";
import { useAuth } from "@/store/auth";
import { useTalkStore, useUserStore } from "@/store";
import { useGroupTypeStore } from "@/store/groupType";
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue'
import { ref, watch, computed } from 'vue'
import { onShow, onLoad, onUnload } from '@dcloudio/uni-app'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import { useTalkStore, useUserStore } from '@/store'
import { useGroupTypeStore } from '@/store/groupType'
import { userHasPermission } from '@/api/deps/index.js'
const { groupActiveIndex, depCheckedKeys } = useGroupTypeStore()
const { userInfo } = useAuth()
onUnload(()=> {
const { groupActiveIndex,depCheckedKeys } = useGroupTypeStore();
})
const isHasPermission = ref(false)
const isCreateProjecy = ref(false)
onShow( async() =>{
const isHasRes = await userHasPermission({
erpUserId: userInfo?.value?.ID,
ruleUrl: [
"auth_chat_app_create_all_dept",
"auth_chat_app_create_limit_dept"
]
})
if (isHasRes.code === 200) {
if (isHasRes.data.auth_chat_app_create_all_dept || isHasRes.data.auth_chat_app_create_limit_dept) {
isHasPermission.value = true
} else{
isHasPermission.value = false
}
isCreateProjecy.value = isHasRes.data.btn_rule_create_project_group
}
})
const confirmBtnStatus = computed(() => {
let disabledT = false;
let disabledT = false
switch (groupActiveIndex.value) {
case 0:
break;
break
case 1:
if (!depCheckedKeys.value.length) {
disabledT = true;
disabledT = true
}
break;
break
case 2:
break;
break
default:
break;
break
}
return disabledT
})
const chooseDep = () => {
uni.navigateTo({
url: '/pages/chooseDeps/index'
});
};
url: '/pages/chooseByDeps/index?chooseMode=1',
})
}
const handleConfirm = () => {
uni.navigateBack()
// uni.navigateTo({
// url: '/pages/creatGroupChat/index'
// });
};
}
</script>
<style scoped lang="scss">
uni-page-body,
page {
::v-deep .zp-paging-container-content {
height: 100%;
display: flex;
}
.outer-layer {
overflow-y: auto;
.choose-group-type {
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
display: flex;
flex-direction: column;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
padding-bottom: 0;
display: flex;
flex-direction: column;
background-position: bottom center;
padding: 20rpx 0;
background-attachment: fixed;
}
.root {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-top: 18rpx;
overflow: hidden;
}
.divider{
.divider {
height: 1rpx;
background-color: #7C7C7C;
background-color: #7c7c7c;
opacity: 0.6;
}
.avatar-placeholder {
@ -171,24 +232,23 @@ page {
background-repeat: no-repeat;
border-radius: 12rpx;
&.firstPanel {
background-image: url("@/static/image/chatList/zu6033@2x.png");
background-image: url('@/static/image/chatList/zu6033@2x.png');
margin-bottom: 28rpx;
}
&.secondPanel {
background-image: url("@/static/image/chatList/zu6031@2x.png");
margin-top: 28rpx;
background-image: url('@/static/image/chatList/zu6031@2x.png');
margin-bottom: 28rpx;
}
&.thirdPanel {
background-image: url("@/static/image/chatList/zu6032@2x.png");
background-image: url('@/static/image/chatList/zu6032@2x.png');
}
&.activePanel {
box-shadow: 0 0 0 3rpx #46299D;
box-shadow: 0 0 0 3rpx #46299d;
}
}
.btnBox {
:deep(uni-button[disabled="true"]){
color: #BEBEBE !important;
:deep(uni-button[disabled='true']) {
color: #bebebe !important;
}
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div class="outer-layer">
<div>
<tm-navbar :hideBack="false" hideHome title="选择部门"> </tm-navbar>
<tm-navbar :hideBack="false" hideHome title="选择部门"></tm-navbar>
</div>
<div class="root">
<div
@ -22,7 +22,7 @@
v-if="crumbs.length"
class="w-full overflow-x-auto mt-[22rpx] leading-[48rpx] text-[#2F2F2F] flex items-center no-scrollbar"
ref="crumbsContainer"
>
>
<div
v-for="(item, index) in crumbs"
class="flex items-center text-[28rpx] leading-[48rpx] whitespace-nowrap"
@ -48,7 +48,7 @@
</div>
<div
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
@click="()=>allCheck(allCheckStatus)"
@click="() => allCheck(allCheckStatus)"
>
<div>
<checkBox
@ -63,12 +63,20 @@
<div
v-if="currentCrumbs?.sons?.length"
v-for="item in currentCrumbs?.sons"
@click="
checkItemChange(
item,
item.checkStatus
? item.checkStatus === 'checked'
? 'noChecked'
: 'checked'
: 'checked',
)
"
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
>
<div class="w-full flex items-center justify-between">
<div
class="flex items-center"
>
<div class="flex items-center">
<div>
<checkBox
:disabled="!item?.sons?.length"
@ -82,7 +90,10 @@
</div>
<div v-if="item.sons?.length" class="flex items-center mr-[32rpx]">
<div class="vDivider mr-[32rpx]"></div>
<div @click="() => toNextLevel(item)" class="flex items-center">
<div
@click.stop="() => toNextLevel(item)"
class="flex items-center"
>
<div class="mr-[12rpx]">
<tm-image
:width="26"
@ -108,15 +119,26 @@
<div
v-if="currentMembers.length"
v-for="item in currentMembers"
@click="
checkMember(
item,
membersCheckedKeys.filter((v) => v.ID === item.ID).length > 0
? 'noChecked'
: 'checked',
)
"
class="pl-[32rpx] pr-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
>
<div class="w-full flex items-center justify-between">
<div
class="flex items-center"
>
<div class="flex items-center">
<div>
<checkBox
:modelValue="membersCheckedKeys.filter(v=>v.ID === item.ID).length>0?'checked':'noChecked'"
:modelValue="
membersCheckedKeys.filter((v) => v.ID === item.ID).length >
0
? 'checked'
: 'noChecked'
"
@change="(val) => checkMember(item, val)"
></checkBox>
</div>
@ -124,24 +146,30 @@
<div class="userAvatar flex items-center justify-center">
{{ item.nickName.slice(-2) }}
</div>
<div class="ml-[20rpx] flex flex-col items-center">
<div class="text-[28rpx] font-bold" >{{ item.nickName }}</div>
<div class="text-[20rpx] text-[#747474]" >{{ item.jobNum }}</div>
<div class="ml-[20rpx] flex flex-col justify-center">
<div class="text-[28rpx] font-bold">{{ item.nickName }}</div>
<div class="text-[20rpx] text-[#747474]">
{{ item.jobNum }}
</div>
</div>
<tm-popover position="tc">
<div class="ml-[20rpx] flex h-[68rpx] flex-wrap line-clamp-2 max-w-[342rpx]">
<div
class="ml-[6rpx] flex h-[68rpx] flex-wrap line-clamp-2 max-w-[342rpx]"
>
<div
v-for="post in item.positions"
class="postTag truncate mb-[4rpx] mr-[14rpx] max-w-[164rpx]"
class="postTag truncate mb-[4rpx] ml-[14rpx] max-w-[164rpx]"
>
{{ post.name }}
</div>
</div>
<template v-slot:label>
<div class="max-h-[250rpx] overflow-y-auto pt-[10rpx] pl-[18rpx] pr-[18rpx] pb-[12rpx]">
<div
class="max-h-[250rpx] overflow-y-auto pt-[10rpx] pl-[18rpx] pr-[18rpx] pb-[12rpx]"
>
<div
v-for="post in item.positions"
class="postTag truncate mb-[10rpx] "
class="postTag truncate mb-[10rpx]"
>
{{ post.name }}
</div>
@ -152,25 +180,33 @@
</div>
</div>
</div>
</div>
<div class="h-[162rpx] pl-[32rpx] pr-[32rpx] bg-[#FFFFFF]">
<div class="mt-[14rpx] flex justify-between">
<div class="flex flex-col">
<div @click="openDrawer" class="flex items-center text-[28rpx] leading-[60rpx] text-[#000000] font-bold">
<div
@click="openDrawer"
class="flex items-center text-[28rpx] leading-[60rpx] text-[#000000] font-bold"
>
<div>已选择</div>
<div>{{ totalMembers }}</div>
<div class="ml-[28rpx]">
<tm-icon :fontSize="24" color="#46299D" name="tmicon-angle-up"></tm-icon>
<tm-icon
:fontSize="24"
color="#46299D"
name="tmicon-angle-up"
></tm-icon>
</div>
</div>
<div class="text-[24rpx] leading-[24rpx] text-[#7A58DE] w-[280rpx] truncate ">
<span v-for="item in allCheckedList" >
<div
class="text-[24rpx] leading-[24rpx] text-[#7A58DE] w-[280rpx] truncate"
>
<span v-for="item in allCheckedList">
{{ item.name }}({{ item.staffNum }})
</span>
<span v-for="(item,index) in membersCheckedKeys" >
<span v-for="(item, index) in membersCheckedKeys">
{{ item.nickName }}
{{ index!== membersCheckedKeys.length-1?',':"" }}
{{ index !== membersCheckedKeys.length - 1 ? ',' : '' }}
</span>
</div>
</div>
@ -186,8 +222,7 @@
:height="76"
size="large"
label="确定"
>
</tm-button>
></tm-button>
</div>
</div>
</div>
@ -199,61 +234,86 @@
:hideHeader="true"
:round="5"
>
<div class="flex flex-col w-full h-full pt-[36rpx] pl-[32rpx] pr-[32rpx] leading-[60rpx]" >
<div class="text-[32rpx] font-bold flex items-center justify-between" >
<div
class="flex flex-col w-full h-full pt-[36rpx] pl-[32rpx] pr-[32rpx] leading-[60rpx]"
>
<div class="text-[32rpx] font-bold flex items-center justify-between">
<div class="flex items-center ml-[10rpx]">
<div>已选择部门数</div>
<div>{{ allCheckedList.length }}</div>
</div>
<div
class="text-[#7A58DE] mr-[10rpx]"
@click="()=>showWin = false"
@click="() => (showWin = false)"
>
确定
</div>
</div>
<div class="flex-1 pb-[20rpx] overflow-y-auto pt-[30rpx]" >
<div
v-for="(item,index) in allCheckedList"
class="flex flex-col"
>
<div v-if="index ===0" class="divider" ></div>
<div class="flex-1 pb-[20rpx] overflow-y-auto pt-[30rpx]">
<div v-for="(item, index) in allCheckedList" class="flex flex-col">
<div v-if="index === 0" class="divider"></div>
<div
class="flex items-center justify-between mt-[36rpx] font-bold text-[#000000] leading-[54rpx] mb-[34rpx]"
>
<div class="text-[28rpx] ml-[10rpx]">{{ item.name }}{{ item.staffNum }}</div>
<div class="text-[28rpx] ml-[10rpx]">
{{ item.name }}{{ item.staffNum }}
</div>
<div class="diyBtn">
<tm-button @click="()=>deleteNode(item)" :disabled="userDepIds.includes(item.ID)" :margin="[10]" :shadow="0" text size="small" :width="106" :height="50" outlined label="移除"></tm-button>
<tm-button
@click="() => deleteNode(item)"
:disabled="userDepIds.includes(item.ID)"
:margin="[10]"
:shadow="0"
text
size="small"
:width="106"
:height="50"
outlined
label="移除"
></tm-button>
</div>
</div>
<div class="divider" ></div>
<div class="divider"></div>
</div>
<div
v-for="(item,index) in membersCheckedKeys"
v-for="(item, index) in membersCheckedKeys"
class="flex flex-col"
>
<div v-if="index ===0 && allCheckedList.length==0" class="divider" ></div>
<div
v-if="index === 0 && allCheckedList.length == 0"
class="divider"
></div>
<div
class="flex items-center justify-between mt-[36rpx] font-bold text-[#000000] leading-[54rpx] mb-[34rpx]"
>
<div class="text-[28rpx] ml-[10rpx] flex">
<div>
{{ item.nickName }}({{ item.jobNum }})
</div>
<div class="ml-[20rpx] flex h-[68rpx] flex-wrap line-clamp-2 max-w-[342rpx]">
<div
v-for="post in item.positions"
class="postTag truncate mb-[4rpx] mr-[14rpx] max-w-[164rpx]"
>
{{ post.name }}
</div>
<div>{{ item.nickName }}({{ item.jobNum }})</div>
<div
class="ml-[20rpx] flex h-[68rpx] flex-wrap line-clamp-2 max-w-[342rpx]"
>
<div
v-for="post in item.positions"
class="postTag truncate mb-[4rpx] mr-[14rpx] max-w-[164rpx]"
>
{{ post.name }}
</div>
</div>
</div>
<div class="diyBtn">
<tm-button @click="()=>deleteMember(item)" :margin="[10]" :shadow="0" text size="small" :width="106" :height="50" outlined label="移除"></tm-button>
<tm-button
@click="() => deleteMember(item)"
:margin="[10]"
:shadow="0"
text
size="small"
:width="106"
:height="50"
outlined
label="移除"
></tm-button>
</div>
</div>
<div class="divider" ></div>
<div class="divider"></div>
</div>
</div>
</div>
@ -261,15 +321,15 @@
</div>
</template>
<script setup>
import { ref, watch, computed, onMounted,nextTick } from "vue";
import { onShow, onLoad } from "@dcloudio/uni-app";
import { useChatList } from "@/store/chatList/index.js";
import { useAuth } from "@/store/auth";
import { useTalkStore, useUserStore } from "@/store";
import { useGroupTypeStore } from "@/store/groupType";
import downDep from "@/static/image/chatList/downDep.png";
import downDepDis from "@/static/image/chatList/downDepDis.png";
import checkBox from "@/components/checkBox/index.vue";
import { ref, watch, computed, onMounted, nextTick } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import { useTalkStore, useUserStore } from '@/store'
import { useGroupTypeStore } from '@/store/groupType'
import downDep from '@/static/image/chatList/downDep.png'
import downDepDis from '@/static/image/chatList/downDepDis.png'
import checkBox from '@/components/checkBox/index.vue'
import lodash from 'lodash'
const {
@ -282,76 +342,78 @@ const {
getDepMembers,
membersCheckedKeys,
allChooseMembers,
} = useGroupTypeStore();
} = useGroupTypeStore()
const userStore = useUserStore()
const searchVal = ref("");
const crumbsContainer = ref(null);
const showWin = ref(false);
const searchVal = ref('')
const crumbsContainer = ref(null)
const showWin = ref(false)
const currentMembers = ref([])
const getAllCheckedNodes = (node, checkedNodes = []) => {
if (node.checkStatus === 'checked') {
checkedNodes.push(node);
checkedNodes.push(node)
}
if (node.sons && Array.isArray(node.sons)) {
node.sons.forEach(son => getAllCheckedNodes(son, checkedNodes));
node.sons.forEach((son) => getAllCheckedNodes(son, checkedNodes))
}
return checkedNodes;
};
return checkedNodes
}
const userDepIds = computed(() => {
return userStore.deps.map(v=>v.dept_id);
});
return userStore.deps.map((v) => v.dept_id)
})
const calculateTotalStaffNum = (node) => {
let total = node.staffNum || 0;
let total = node.staffNum || 0
if (node.sons && Array.isArray(node.sons)) {
node.sons.forEach(son => {
total += calculateTotalStaffNum(son);
});
node.sons.forEach((son) => {
total += calculateTotalStaffNum(son)
})
}
return total;
};
return total
}
const allCheckedList = computed(() => {
const checkedNodes = [];
depTreeMyList.value.forEach(node => getAllCheckedNodes(node, checkedNodes));
console.log("checkedNodes", checkedNodes);
const checkedNodes = []
depTreeMyList.value.forEach((node) => getAllCheckedNodes(node, checkedNodes))
console.log('checkedNodes', checkedNodes)
return checkedNodes;
return checkedNodes
})
const currentCrumbs = computed(() => {
if (crumbs.value[crumbsIndex.value] ) {
if (searchVal.value && searchVal.value !== "") {
if (crumbs.value[crumbsIndex.value]) {
if (searchVal.value && searchVal.value !== '') {
let filterSons = crumbs.value[crumbsIndex.value].sons.filter((item) =>
item.name.includes(searchVal.value)
);
return {
...crumbs.value[crumbsIndex.value],
sons: filterSons,
};
item.name.includes(searchVal.value),
)
return {
...crumbs.value[crumbsIndex.value],
sons: filterSons,
}
}
console.log("allCheckedList", crumbs.value[crumbsIndex.value]);
return crumbs.value[crumbsIndex.value];
console.log('allCheckedList', crumbs.value[crumbsIndex.value])
return crumbs.value[crumbsIndex.value]
}
return {}
});
})
const getCurrentMembers = async (depItem) => {
const res = await getDepMembers({
departmentId:depItem.ID,
status:'notactive'
departmentId: depItem.ID,
status: 'notactive',
})
if (res.status === 0) {
currentMembers.value = res.data.data.length?res.data.data.map(v=>{
return {
...v,
isMember:true,
}
}):[]
}else{
if (res.code === 200) {
currentMembers.value = res.data.data.length
? res.data.data.map((v) => {
return {
...v,
isMember: true,
}
})
: []
} else {
currentMembers.value = []
}
}
@ -359,168 +421,178 @@ const getCurrentMembers = async (depItem) => {
const checkMember = (item, val) => {
if (val === 'checked') {
membersCheckedKeys.value.push(item)
}else {
membersCheckedKeys.value = membersCheckedKeys.value.filter(v=>v.ID !==item.ID)
} else {
membersCheckedKeys.value = membersCheckedKeys.value.filter(
(v) => v.ID !== item.ID,
)
}
}
watch(() => currentCrumbs.value, (newValue, oldValue) => {
getCurrentMembers(newValue)
})
watch(
() => currentCrumbs.value,
(newValue, oldValue) => {
getCurrentMembers(newValue)
},
)
const allCheckStatus = computed(() => {
if (!currentCrumbs.value.sons) {
return "noChecked";
return 'noChecked'
}
const allChecked = currentCrumbs.value.sons.every((son) => son.checkStatus === "checked");
const someChecked =currentCrumbs.value.sons.some(
(son) =>
son.checkStatus === "checked" || son.checkStatus === "halfChecked"
);
if (allChecked) {
return "checked";
const allChecked = currentCrumbs.value.sons.every(
(son) => son.checkStatus === 'checked',
)
const someChecked = currentCrumbs.value.sons.some(
(son) => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
)
if (allChecked) {
return 'checked'
} else if (someChecked) {
return "halfChecked";
return 'halfChecked'
} else {
return "noChecked";
return 'noChecked'
}
})
const findNodeById = (node, targetId) => {
if (node.ID === targetId) {
return node;
return node
}
if (node.sons && Array.isArray(node.sons)) {
for (const son of node.sons) {
const found = findNodeById(son, targetId);
const found = findNodeById(son, targetId)
if (found) {
return found;
return found
}
}
}
return null;
};
return null
}
const findParentNode = (node, targetId) => {
if (!node.sons || !Array.isArray(node.sons)) return null;
if (!node.sons || !Array.isArray(node.sons)) return null
for (const son of node.sons) {
if (son.ID === targetId) {
return node;
return node
}
const parent = findParentNode(son, targetId);
const parent = findParentNode(son, targetId)
if (parent) {
return parent;
return parent
}
}
return null;
};
return null
}
//
const updateParentStatus = (node) => {
const parent = findParentNode(depTreeMyList.value[0], node.ID);
if (!parent) return;
if (parent.checkStatus !== "checked"){
const parent = findParentNode(depTreeMyList.value[0], node.ID)
if (!parent) return
if (parent.checkStatus !== 'checked') {
const allChecked =
parent.sons &&
Array.isArray(parent.sons) &&
parent.sons.every((son) => son.checkStatus === "checked");
parent.sons.every((son) => son.checkStatus === 'checked')
const someChecked =
parent.sons &&
Array.isArray(parent.sons) &&
parent.sons.some(
(son) =>
son.checkStatus === "checked" || son.checkStatus === "halfChecked"
);
son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
)
if (allChecked) {
parent.checkStatus = "halfChecked";
parent.checkStatus = 'halfChecked'
} else if (someChecked) {
parent.checkStatus = "halfChecked";
parent.checkStatus = 'halfChecked'
} else {
parent.checkStatus = "noChecked";
parent.checkStatus = 'noChecked'
}
}
updateParentStatus(parent);
};
updateParentStatus(parent)
}
//
const updateNodeStatus = (node) => {
if (!node.sons || !Array.isArray(node.sons)) return;
const allChecked = node.sons.every(son => son.checkStatus === 'checked');
const someChecked = node.sons.some(son => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked');
if (!node.sons || !Array.isArray(node.sons)) return
const allChecked = node.sons.every((son) => son.checkStatus === 'checked')
const someChecked = node.sons.some(
(son) => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
)
if (allChecked) {
node.checkStatus = 'halfChecked';
node.checkStatus = 'halfChecked'
} else if (someChecked) {
node.checkStatus = 'halfChecked';
node.checkStatus = 'halfChecked'
} else {
node.checkStatus = 'noChecked';
node.checkStatus = 'noChecked'
}
};
}
//
const updateCheckStatus = (node, status) => {
node.checkStatus = status;
node.checkStatus = status
if (node.sons && Array.isArray(node.sons) && node.sons.length > 0) {
node.sons.forEach((son) => updateCheckStatus(son, status));
node.sons.forEach((son) => updateCheckStatus(son, status))
}
};
}
const checkItemChange = (item, val) => {
//
updateCheckStatus(item, val);
updateCheckStatus(item, val)
//
updateParentStatus(item);
};
updateParentStatus(item)
}
const deleteNode = (item) => {
const treeNode = findNodeById(depTreeMyList.value[0], item.ID);
if (!treeNode) return;
treeNode.checkStatus = "noChecked";
updateNodeStatus(treeNode);
updateParentStatus(treeNode);
};
const treeNode = findNodeById(depTreeMyList.value[0], item.ID)
if (!treeNode) return
treeNode.checkStatus = 'noChecked'
updateNodeStatus(treeNode)
updateParentStatus(treeNode)
}
const deleteMember = (item) => {
membersCheckedKeys.value = membersCheckedKeys.value.filter(v=>v.ID !== item.ID)
membersCheckedKeys.value = membersCheckedKeys.value.filter(
(v) => v.ID !== item.ID,
)
}
const toNextLevel = async (item) => {
if (item.checkStatus !== "checked") {
crumbs.value.push(item);
crumbsIndex.value++;
await nextTick();
if (item.checkStatus !== 'checked') {
crumbs.value.push(item)
crumbsIndex.value++
await nextTick()
if (crumbsContainer.value) {
crumbsContainer.value.scrollLeft = crumbsContainer.value.scrollWidth;
crumbsContainer.value.scrollLeft = crumbsContainer.value.scrollWidth
}
}
};
}
const handleCrumbsClick = (index) => {
crumbsIndex.value = index;
crumbs.value = crumbs.value.slice(0, index + 1);
};
crumbsIndex.value = index
crumbs.value = crumbs.value.slice(0, index + 1)
}
const allCheck = (status) => {
let statusT = 'noChecked';
if (status === "checked") {
statusT = "noChecked";
let statusT = 'noChecked'
if (status === 'checked') {
statusT = 'noChecked'
} else {
statusT = "checked";
statusT = 'checked'
}
currentCrumbs.value.sons.forEach((item) => {
const itemT = findNodeById(depTreeMyList.value[0], item.ID)
if (!itemT) return;
checkItemChange(itemT, statusT);
});
};
if (!itemT) return
checkItemChange(itemT, statusT)
})
}
const openDrawer = () => {
showWin.value = true;
if (allCheckedList.length>0) {
showWin.value = true
if (allCheckedList.length > 0) {
}
};
}
// watch(() => depTreeMyList.value, (newValue, oldValue) => {
// console.log("depTreeMyList", newValue);
@ -531,59 +603,62 @@ const openDrawer = () => {
// console.log("searchVal", newValue);
// });
const getDepTotalMembers = (item) => {
const rootNode = depTreeMyList.value[0];
const targetNode = findNodeById(rootNode, item.ID);
const rootNode = depTreeMyList.value[0]
const targetNode = findNodeById(rootNode, item.ID)
if (targetNode) {
return calculateTotalStaffNum(targetNode);
return calculateTotalStaffNum(targetNode)
}
return 0;
return 0
}
const totalMembers = computed(() => {
const depMembers = allCheckedList.value.reduce((sum, item) => sum + item.staffNum, 0);
const memberCount = membersCheckedKeys.value.length;
return depMembers + memberCount;
const depMembers = allCheckedList.value.reduce(
(sum, item) => sum + item.staffNum,
0,
)
const memberCount = membersCheckedKeys.value.length
return depMembers + memberCount
})
const handleConfirm = async () => {
uni.showLoading()
const listT = membersCheckedKeys.value.map(v=>v)
const listT = membersCheckedKeys.value.map((v) => v)
const res = await getDepMembers({
departmentIds:allCheckedList.value.map(v=>v.ID),
status:'notactive',
departmentIds: allCheckedList.value.map((v) => v.ID),
status: 'notactive',
})
if (res.status == 0 && res.data?.data?.length) {
res.data?.data.forEach(v=>{
if (res.code == 200 && res.data?.data?.length) {
res.data?.data.forEach((v) => {
listT.push(v)
})
}
allChooseMembers.value = listT
depCheckedKeys.value = lodash.cloneDeep(allCheckedList.value)
uni.hideLoading()
uni.navigateBack();
uni.navigateBack()
}
const initCheckedKeys = () => {
depCheckedKeys.value.forEach((item) => {
const node = findNodeById(depTreeMyList.value[0], item.ID);
const node = findNodeById(depTreeMyList.value[0], item.ID)
if (node) {
node.checkStatus = "checked";
updateParentStatus(node);
node.checkStatus = 'checked'
updateParentStatus(node)
}
});
};
})
}
const init = async () => {
crumbsIndex.value = 0;
await getDepsTreeMy();
crumbs.value = depTreeMyList.value.length ? [depTreeMyList.value[0]] : [];
initCheckedKeys();
};
crumbsIndex.value = 0
await getDepsTreeMy()
crumbs.value = depTreeMyList.value.length ? [depTreeMyList.value[0]] : []
initCheckedKeys()
}
onMounted(() => {
init();
});
init()
})
</script>
<style scoped lang="scss">
uni-page-body,
@ -593,7 +668,7 @@ page {
.outer-layer {
overflow-y: auto;
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
padding-bottom: 0;
display: flex;
@ -633,47 +708,47 @@ page {
background-repeat: no-repeat;
border-radius: 12rpx;
&.firstPanel {
background-image: url("@/static/image/chatList/zu6033@2x.png");
background-image: url('@/static/image/chatList/zu6033@2x.png');
}
&.secondPanel {
background-image: url("@/static/image/chatList/zu6031@2x.png");
background-image: url('@/static/image/chatList/zu6031@2x.png');
margin-top: 28rpx;
margin-bottom: 28rpx;
}
&.thirdPanel {
background-image: url("@/static/image/chatList/zu6032@2x.png");
background-image: url('@/static/image/chatList/zu6032@2x.png');
}
&.activePanel {
box-shadow: 0 0 0 3rpx #46299d;
}
}
.btnBox {
:deep(uni-button[disabled="true"]) {
:deep(uni-button[disabled='true']) {
color: #bebebe !important;
}
}
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.no-scrollbar::-webkit-scrollbar {
display: none; /* Chrome, Safari, and Opera */
display: none; /* Chrome, Safari, and Opera */
}
.diyBtn {
:deep(uni-button) {
color: #191919 !important;
border: 1rpx solid #D6D6D8 !important;
background-color: #FFFFFF !important;
&[disabled="true"] {
color: #BEBEBE !important;
border: 1rpx solid #E6E6E6 !important;
background-color: #E6E6E6 !important;
border: 1rpx solid #d6d6d8 !important;
background-color: #ffffff !important;
&[disabled='true'] {
color: #bebebe !important;
border: 1rpx solid #e6e6e6 !important;
background-color: #e6e6e6 !important;
}
}
}
.userAvatar{
background: linear-gradient(#674BBC, #46299D);
.userAvatar {
background: linear-gradient(#674bbc, #46299d);
width: 72rpx;
height: 72rpx;
border-radius: 36rpx;
@ -681,12 +756,12 @@ page {
font-size: 24rpx;
font-weight: bold;
}
.postTag{
background-color: #EEE9F8;
.postTag {
background-color: #eee9f8;
height: 32rpx;
line-height: 32rpx;
font-size: 20rpx;
padding: 0 12rpx 0 12rpx;
color: #46299D;
color: #46299d;
}
</style>

View File

@ -0,0 +1,366 @@
<template>
<div class="outer-layer user-detail-page">
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<customNavbar :title="$t('complaint.title')"></customNavbar>
</template>
<!-- 投诉主体内容 -->
<view class="complaint-container">
<!-- 投诉类型选择 -->
<view class="form-item">
<text class="form-label">{{ $t('complaint.selectType') }}</text>
<picker mode="selector" :range="complaintTypes" range-key="label" @change="handleTypeChange">
<view class="picker">
{{ selectedType.label || $t('complaint.selectPlaceholder') }}
<uni-icons type="arrowright" size="16" color="#999"></uni-icons>
</view>
</picker>
</view>
<!-- 图片证据上传 -->
<view class="form-item">
<text class="form-label">{{ $t('complaint.imageEvidence') }}</text>
<view class="upload-area">
<view v-for="(img, index) in imageList" :key="index" class="image-wrapper">
<image :src="img" mode="aspectFill" class="uploaded-image" @click="previewImage(index)" />
<uni-icons type="close" size="18" color="#fff" class="delete-icon"
@click="removeImage(index)"></uni-icons>
</view>
<view v-if="imageList.length < 9" class="upload-btn" @click="chooseImage">
<uni-icons type="plusempty" size="28" color="#999"></uni-icons>
<text class="upload-text">{{ $t('complaint.addImage') }}</text>
</view>
</view>
</view>
<!-- 投诉内容 -->
<view class="form-item">
<text class="form-label">{{ $t('complaint.complaintContent') }}</text>
<textarea v-model="complaintContent" :placeholder="$t('complaint.contentPlaceholder')"
class="content-textarea"></textarea>
</view>
<!-- 投诉须知 -->
<view class="notice-box">
<view class="notice-header" @click="toggleNotice">
<text class="notice-title">{{ $t('complaint.noticeTitle') }}</text>
<text class="toggle-btn">
{{ isNoticeExpanded ? $t('complaint.collapse') : $t('complaint.expand') }}
</text>
</view>
<!-- 折叠状态只显示前两项 -->
<view class="notice-content" v-if="!isNoticeExpanded">
<view class="notice-item">
<text>{{ $t('complaint.noticeone') }}</text>
</view>
<view class="notice-item">
<text>{{ $t('complaint.noticetwo') }}</text>
</view>
</view>
<!-- 展开状态显示全部 -->
<view class="notice-content" v-else>
<view class="notice-item">
<text>{{ $t('complaint.noticeone') }}</text>
</view>
<view class="notice-item">
<text>{{ $t('complaint.noticetwo') }}</text>
</view>
<view class="notice-item">
<text>{{ $t('complaint.noticethree') }}</text>
</view>
<view class="notice-item">
<text>{{ $t('complaint.noticefour') }}</text>
</view>
<view class="notice-item">
<text>{{ $t('complaint.noticefive') }}</text>
</view>
<view class="notice-item">
<text>{{ $t('complaint.noticenoticeContent') }}</text>
</view>
</view>
</view>
<button class="submit-btn" :disabled="!selectedType.value" @click="handleSubmit">
{{ $t('complaint.submit') }}
</button>
</view>
</ZPaging>
<!-- 固定在底部的提交按钮 -->
</div>
</div>
</template>
<script setup>
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import {
ref
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app';
import {
useI18n
} from 'vue-i18n';
const {
t
} = useI18n();
//
const complaintTypes = computed(() => [{
label: t('complaint.typeOptions.porn'),
value: 'porn'
},
{
label: t('complaint.typeOptions.illegal'),
value: 'illegal'
},
{
label: t('complaint.typeOptions.gambling'),
value: 'gambling'
},
{
label: t('complaint.typeOptions.violence'),
value: 'violence'
},
{
label: t('complaint.typeOptions.selfHarm'),
value: 'selfHarm'
},
{
label: t('complaint.typeOptions.other'),
value: 'other'
}
]);
//
const selectedType = ref({});
const imageList = ref([]);
const complaintContent = ref('');
const isNoticeExpanded = ref(false);
//
const toggleNotice = () => {
isNoticeExpanded.value = !isNoticeExpanded.value;
};
//
const handleTypeChange = (e) => {
selectedType.value = complaintTypes.value[e.detail.value];
};
//
const chooseImage = () => {
uni.chooseImage({
count: 9 - imageList.value.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
imageList.value = [...imageList.value, ...res.tempFilePaths];
if (imageList.value.length > 9) {
uni.showToast({
title: `最多只能选择9张图片`,
icon: 'none'
});
}
}
});
};
//
const removeImage = (index) => {
imageList.value.splice(index, 1);
};
//
const previewImage = (index) => {
uni.previewImage({
current: index,
urls: imageList.value
});
};
//
const handleSubmit = () => {
const formData = {
type: selectedType.value,
images: imageList.value,
content: complaintContent.value
};
uni.showLoading({
title: t('complaint.toast.submitting') // 使
});
setTimeout(() => {
uni.hideLoading();
uni.showToast({
title: t('complaint.toast.success'),
icon: 'success'
});
selectedType.value = {};
imageList.value = [];
complaintContent.value = '';
//
setTimeout(() => {
uni.navigateBack();
}, 1500);
}, 2000);
};
onLoad(() => {
//
});
</script>
<style scoped lang="scss">
::v-deep .uni-picker-action-confirm {
color: #452aa1 !important;
}
.outer-layer {
flex: 1;
background-image: url('@/static/image/mine/1111.png');
background-size: cover;
background-repeat: no-repeat;
}
.complaint-container {
padding: 20rpx 30rpx;
background-color: rgba(255, 255, 255, 0.9);
margin: 20rpx;
border-radius: 16rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
.form-item {
margin-bottom: 40rpx;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-bottom: 20rpx;
}
.picker {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx;
background-color: #f7f7f7;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
}
.upload-area {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.upload-btn {
width: 160rpx;
height: 160rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #f7f7f7;
border-radius: 12rpx;
border: 1rpx dashed #ddd;
}
.upload-text {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
}
.image-wrapper {
width: 160rpx;
height: 160rpx;
position: relative;
border-radius: 12rpx;
overflow: hidden;
}
.uploaded-image {
width: 100%;
height: 100%;
}
.delete-icon {
position: absolute;
top: 8rpx;
right: 8rpx;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
padding: 4rpx;
}
.content-textarea {
width: 100%;
height: 200rpx;
padding: 20rpx;
background-color: #f7f7f7;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
}
.notice-box {
padding: 20rpx;
background-color: #f0f7ff;
border-radius: 12rpx;
margin: 40rpx 0;
}
.notice-title {
font-size: 28rpx;
color: #452aa1;
font-weight: bold;
display: block;
margin-bottom: 10rpx;
}
.notice-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
}
.toggle-btn {
color: #452aa1;
font-size: 24rpx;
}
.notice-content {
font-size: 24rpx;
color: #666;
line-height: 1.6;
padding-bottom: 20rpx;
}
.submit-btn {
margin-top: 40rpx;
background-color: #452aa1;
color: white;
border-radius: 8rpx;
height: 90rpx;
line-height: 90rpx;
font-size: 32rpx;
&[disabled] {
background-color: #f0f0f0;
}
}
</style>

View File

@ -1,328 +1,344 @@
<template>
<div class="outer-layer">
<div>
<tm-navbar :hideBack="false" hideHome title="发起群聊" :leftWidth="320">
</tm-navbar>
</div>
<div class="root">
<div class="w-full pl-[32rpx] pr-[32rpx] mb-[20rpx]" >
<div class="w-full mt-[60rpx] flex justify-center" >
<div v-if="groupActiveIndex === -1" class="avatar-placeholder">
</div>
<div class="mb-[40rpx]" v-else>
<tm-image
:width="192"
:height="192"
:round="12"
:src="avatarImg"
></tm-image>
<div class="create-group-chat-page">
<zPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<customNavbar :title="$t('pageTitle.create.group')"></customNavbar>
</template>
<div class="create-group-chat flex flex-col">
<div class="group-avatar flex items-center justify-center">
<div class="avatar-placeholder" v-if="groupActiveIndex === -1"></div>
<div v-else>
<avatarModule :mode="2" :avatar="avatarImg" :groupType="groupType"
:customStyle="{ width: '192rpx', height: '192rpx' }"></avatarModule>
</div>
</div>
<div class="input-group flex items-center justify-between" >
<div class="input-group flex items-center justify-between">
<div class="input-item">
群名称
</div>
<div class="input-box" >
<tm-input
v-model="groupName"
:followTheme="false"
fontColor="#747474"
placeholderStyle="color: #B4B4B4"
focusColor="#FFF"
:fontSize="28"
:maxlength="20"
:height="40"
:transprent="true"
placeholder="请输入群名称1~20个字"
:padding="[0,0]"
align="right"
>
</tm-input>
<div class="input-box">
<tm-input v-model="groupName" :followTheme="false" fontColor="#747474" placeholderStyle="color: #B4B4B4"
focusColor="#FFF" :fontSize="28" :maxlength="20" :height="40" :transprent="true"
placeholder="请输入群名称1~20个字" :padding="[0, 0]" align="right"></tm-input>
</div>
</div>
<div class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]" >
<div class="flex items-center justify-between" >
<div class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]">
<div class="flex items-center justify-between">
<div class="input-item">
群类型
</div>
<div @click="chooseGroupType" class="left-box" >
<div class="text-[#B4B4B4] text-[28rpx] font-bold" >
<span v-if="groupActiveIndex ===-1">请选择群类型</span>
<span v-else-if="groupActiveIndex ===0">普通群</span>
<span v-else-if="groupActiveIndex ===1">部门群</span>
<span v-else-if="groupActiveIndex ===2">项目群</span>
<div @click="chooseGroupType" class="left-box">
<div class="text-[#B4B4B4] text-[28rpx] font-bold">
<span v-if="groupActiveIndex === -1">请选择群类型</span>
<span v-else-if="groupActiveIndex === 0">普通群</span>
<span v-else-if="groupActiveIndex === 1">部门群</span>
<span v-else-if="groupActiveIndex === 2">项目群</span>
</div>
<div class="ml-[32rpx]" >
<div class="ml-[32rpx]">
<tm-icon :font-size="22" color="#747474" name="tmicon-angle-right"></tm-icon>
</div>
</div>
</div>
<div v-if="depCheckedKeys.length" class="mt-[32rpx]" >
<div
v-for="(item,index) in depCheckedKeys"
class="text-[#747474] text-[28rpx] leading-[40rpx] font-bold"
<div v-if="depCheckedKeys.length && groupActiveIndex === 1" class="mt-[32rpx]">
<div v-for="(item, index) in depCheckedKeys" class="text-[#747474] text-[28rpx] leading-[40rpx] font-bold"
:class="[
index !==0 ? 'mt-[10rpx]':'',
depsNoExpanded&&index>4 ? 'hidden':''
]"
>
index !== 0 ? 'mt-[10rpx]' : '',
depsNoExpanded_1 && index > 4 ? 'hidden' : '',
]">
{{ item.name }}
</div>
<div class="text-[#46299D] text-[28rpx] mt-[20rpx] font-bold flex justify-center" >
<div v-if="depCheckedKeys.length>5" @click="depsNoExpanded = !depsNoExpanded" class="w-[100rpx]">
{{ depsNoExpanded? '展开':'收起' }}
<div class="text-[#46299D] text-[28rpx] mt-[20rpx] font-bold flex justify-center">
<div v-if="depCheckedKeys.length > 5" @click="depsNoExpanded_1 = !depsNoExpanded_1" class="w-[100rpx]">
{{ depsNoExpanded_1 ? '展开' : '收起' }}
</div>
</div>
</div>
</div>
<div v-if="groupActiveIndex===0" class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]" >
<div class="flex items-center justify-between" >
<div v-if="groupActiveIndex === 0 || groupActiveIndex === 2"
class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]">
<div class="flex items-center justify-between">
<div class="input-item">
群成员
</div>
<div @click="chooseMembers" class="left-box" >
<div class="ml-[32rpx] flex items-center" >
<div v-if="!groupAdmins.length" class="text-[#B4B4B4] text-[28rpx] font-bold mr-[32rpx]">全部({{ 0 }})</div>
<div @click="chooseMembers" class="left-box">
<div class="ml-[32rpx] flex items-center">
<div class="text-[#B4B4B4] text-[28rpx] font-bold mr-[32rpx]">
全部({{ allChooseMembers?.length || 0 }})
</div>
<tm-icon :font-size="22" color="#747474" name="tmicon-angle-right"></tm-icon>
</div>
</div>
</div>
<div v-if="groupAdmins.length" class="mt-[32rpx]" >
<div
v-for="(item,index) in groupAdmins"
class="text-[#747474] text-[28rpx] leading-[40rpx] font-bold"
:class="[
index !==0 ? 'mt-[10rpx]':'',
depsNoExpanded&&index>4 ? 'hidden':''
]"
>
{{ item.name }}
</div>
<div class="text-[#46299D] text-[28rpx] mt-[20rpx] font-bold flex justify-center" >
<div v-if="groupAdmins.length>5" @click="depsNoExpanded = !depsNoExpanded" class="w-[100rpx]">
{{ depsNoExpanded? '展开':'收起' }}
</div>
</div>
</div>
<groupMemberList :groupType="3" :is_manager="true" :memberList="allChooseMembers" :memberListsLimit="15"
:hideAddRemoveBtns="true"></groupMemberList>
</div>
<div v-if="groupActiveIndex===1" class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]" >
<div class="flex items-center justify-between" >
<div v-if="groupActiveIndex === 1" class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]">
<div class="flex items-center justify-between">
<div class="input-item">
群管理员
</div>
<div @click="chooseGroupAdmin" class="left-box" >
<div class="ml-[32rpx] flex items-center" >
<div v-if="!groupAdmins.length" class="text-[#B4B4B4] text-[28rpx] font-bold mr-[32rpx]">请选择群管理员</div>
<div @click="chooseGroupAdmin" class="left-box">
<div class="ml-[32rpx] flex items-center">
<div v-if="!groupAdmins.length" class="text-[#B4B4B4] text-[28rpx] font-bold mr-[32rpx]">
请选择群管理员
</div>
<tm-icon :font-size="22" color="#747474" name="tmicon-angle-right"></tm-icon>
</div>
</div>
</div>
<div v-if="groupAdmins.length" class="mt-[32rpx]" >
<div
v-for="(item,index) in groupAdmins"
class="text-[#747474] text-[28rpx] leading-[40rpx] font-bold"
<div v-if="groupAdmins.length" class="mt-[32rpx]">
<div v-for="(item, index) in groupAdmins" class="text-[#747474] text-[28rpx] leading-[40rpx] font-bold"
:class="[
index !==0 ? 'mt-[10rpx]':'',
depsNoExpanded&&index>4 ? 'hidden':''
]"
>
index !== 0 ? 'mt-[10rpx]' : '',
depsNoExpanded_2 && index > 4 ? 'hidden' : '',
]">
{{ item.name }}
</div>
<div class="text-[#46299D] text-[28rpx] mt-[20rpx] font-bold flex justify-center" >
<div v-if="groupAdmins.length>5" @click="depsNoExpanded = !depsNoExpanded" class="w-[100rpx]">
{{ depsNoExpanded? '展开':'收起' }}
<div class="text-[#46299D] text-[28rpx] mt-[20rpx] font-bold flex justify-center">
<div v-if="groupAdmins.length > 5" @click="depsNoExpanded_2 = !depsNoExpanded_2" class="w-[100rpx]">
{{ depsNoExpanded_2 ? '展开' : '收起' }}
</div>
</div>
</div>
</div>
</div>
<div class="h-[162rpx] flex justify-center bg-[#FFFFFF]">
<div class="mt-[14rpx] btnBox" >
<tm-button
@click="handleConfirm"
color="#46299D"
:disabled="confirmBtnStatus"
disabledColor="#E6E6E6"
:margin="[0]"
:shadow="0"
:width="426"
:height="76"
size="large"
label="发起群聊"
>
</tm-button>
</div>
</div>
</div>
<template #bottom>
<customBtn :isBottom="true" :btnText="$t('pageTitle.create.group')" @click="handleConfirm"
:isLoading="isLoading" :disabled="confirmBtnStatus || isLoading"></customBtn>
</template>
</zPaging>
</div>
</template>
<script setup>
import { ref, watch, computed } from "vue";
import { onShow, onLoad } from "@dcloudio/uni-app";
import { useChatList } from "@/store/chatList/index.js";
import { useAuth } from "@/store/auth";
import { useTalkStore, useUserStore } from "@/store";
import addCircle from "@/static/image/chatList/addCircle.png";
import cahtPopover from "@/static/image/chatList/cahtPopover.png";
import zu4992 from "@/static/image/chatList/zu4992@2x.png";
import zu4991 from "@/static/image/chatList/zu4991@2x.png";
import zu4989 from "@/static/image/chatList/zu4989@2x.png";
import { useGroupTypeStore } from "@/store/groupType";
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue'
import groupMemberList from '../chatSettings/components/groupMembersList.vue'
import avatarModule from '@/components/avatar-module/index.vue'
const { groupName,groupActiveIndex,depCheckedKeys,groupAdmins,createDepGroup,resetGroupInfo } = useGroupTypeStore();
const talkStore = useTalkStore();
const userStore = useUserStore();
const { userInfo } = useAuth();
import {
ref,
watch,
computed,
onMounted
} from 'vue'
import {
onShow,
onLoad,
onUnload
} from '@dcloudio/uni-app'
import {
useChatList
} from '@/store/chatList/index.js'
import {
useAuth
} from '@/store/auth'
import {
useTalkStore,
useUserStore,
useGroupStore
} from '@/store'
import addCircle from '@/static/image/chatList/addCircle.png'
import cahtPopover from '@/static/image/chatList/cahtPopover.png'
import {
ServeCreateGroup
} from '@/api/group/index'
import {
useGroupTypeStore
} from '@/store/groupType'
import {
handleSetWebviewStyle
} from '@/utils/common'
const groupChatType = ref('');
const depsNoExpanded = ref(true);
const {
groupName,
groupActiveIndex,
depCheckedKeys,
groupAdmins,
createDepGroup,
resetGroupInfo,
allChooseMembers,
} = useGroupTypeStore()
const talkStore = useTalkStore()
const userStore = useUserStore()
const groupStore = useGroupStore()
const {
userInfo
} = useAuth()
const avatarImg = computed(() => {
let srcT = "";
switch (groupActiveIndex.value) {
case 0:
srcT = zu4992;
break;
case 1:
srcT = zu4989;
break;
case 2:
srcT = zu4991;
break;
default:
srcT = zu4992;
}
return srcT;
})
const groupChatType = ref('')
const depsNoExpanded_1 = ref(true)
const depsNoExpanded_2 = ref(true)
const chooseGroupType = () => {
uni.navigateTo({
url: '/pages/chooseGroupType/index'
onLoad(() => {
groupStore.$reset()
})
}
const chooseGroupAdmin = () => {
uni.navigateTo({
url: '/pages/chooseGroupAdmin/index'
onUnload(() => {
resetGroupInfo();
})
}
const chooseMembers = () => {
uni.navigateTo({
url: '/pages/chooseMembers/index'
onMounted(() => {
handleSetWebviewStyle()
})
}
const handleConfirm = async () => {
if (groupActiveIndex.value === 0) {
}else if(groupActiveIndex.value === 1){
const res = await createDepGroup()
if (res.code === 200) {
resetGroupInfo()
uni.navigateBack()
//
const groupType = computed(() => {
let group_type = ''
switch (groupActiveIndex.value) {
case 0:
group_type = 1
break
case 1:
group_type = 2
break
case 2:
group_type = 3
break
default:
group_type = ''
}
return group_type
})
}else {
//
const chooseGroupType = () => {
uni.navigateTo({
url: '/pages/chooseGroupType/index',
})
}
}
const confirmBtnStatus = computed(() => {
let disabledT = false;
if (groupName.value === "" || !groupActiveIndex.value) {
return true;
const chooseGroupAdmin = () => {
uni.navigateTo({
url: '/pages/chatSettings/groupManage/selectMembers?manageType=admin&isCreateDepGroup=1',
})
}
switch (groupActiveIndex.value) {
case 0:
break;
case 1:
if (!depCheckedKeys.value.length) {
disabledT = true;
const chooseMembers = () => {
uni.navigateTo({
url: '/pages/chooseByDeps/index?chooseMode=2',
})
}
const isLoading = ref(false)
//
const handleConfirm = async () => {
if (isLoading.value) return
isLoading.value = true
try {
let erp_ids = ''
if (allChooseMembers?.value?.length > 0) {
allChooseMembers.value.forEach((ele) => {
if (!erp_ids) {
erp_ids = String(ele.ID)
} else {
erp_ids += ',' + ele.ID
}
})
}
if (!groupAdmins.value.length) {
disabledT = true;
let res = null
if (groupActiveIndex.value === 0) {
//
const params = {
avatar: '',
name: groupName.value,
erp_ids: erp_ids,
type: 1,
profile: '',
}
console.log('普通群参数:', params)
res = await ServeCreateGroup(params)
} else if (groupActiveIndex.value === 1) {
//
res = await createDepGroup()
} else if (groupActiveIndex.value === 2) {
//
const params = {
avatar: '',
name: groupName.value,
erp_ids: erp_ids,
type: 3,
profile: '',
}
console.log('项目群参数:', params)
res = await ServeCreateGroup(params)
}
break;
case 2:
break;
default:
break;
if (res?.code === 200) {
resetGroupInfo()
uni.navigateBack()
}
} catch (err) {
console.error(err)
} finally {
isLoading.value = false
}
}
return disabledT
})
onShow(() => {
depsNoExpanded.value = true;
})
//
const confirmBtnStatus = computed(() => {
return groupActiveIndex.value === -1;
});
onShow(() => {
depsNoExpanded_1.value = true;
depsNoExpanded_2.value = true;
})
</script>
<style scoped lang="scss">
uni-page-body,
page {
height: 100%;
}
.outer-layer {
overflow-y: auto;
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
background-size: cover;
padding-bottom: 0;
display: flex;
flex-direction: column;
}
.root {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.divider{
height: 1rpx;
background-color: #7C7C7C;
opacity: 0.6;
}
.avatar-placeholder {
width: 192rpx;
height: 192rpx;
background-color: #e0e0e0;
border-radius: 50%;
margin-bottom: 40rpx;
}
.input-group {
background-color: #fff;
padding-top: 38rpx;
padding-bottom: 32rpx;
padding-left: 32rpx;
padding-right: 40rpx;
// display: flex;
// align-items: center;
// justify-content: space-between;
}
.input-item {
line-height: 40rpx;
font-size: 28rpx;
color: #000;
font-weight: bold;
}
.input-box {
margin-left: 84rpx;
line-height: 40rpx;
width: 404rpx;
font-weight: bold;
}
.left-box {
display: flex;
align-items: center;
}
.btnBox {
:deep(uni-button[disabled="true"]) {
color: #bebebe !important;
::v-deep .zp-paging-container-content {
height: 100%;
display: flex;
}
.create-group-chat {
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-position: center bottom;
width: 100%;
padding: 0 32rpx 20rpx;
.group-avatar {
padding: 60rpx 0;
.avatar-placeholder {
width: 192rpx;
height: 192rpx;
background-color: #e0e0e0;
border-radius: 50%;
}
}
}
.divider {
height: 1rpx;
background-color: #7c7c7c;
opacity: 0.6;
}
.input-group {
background-color: #fff;
padding: 38rpx 40rpx 32rpx 32rpx;
}
.input-item {
line-height: 40rpx;
font-size: 28rpx;
color: #000;
font-weight: bold;
}
.input-box {
margin-left: 84rpx;
line-height: 40rpx;
width: 404rpx;
font-weight: bold;
}
.left-box {
display: flex;
align-items: center;
}
}
</style>

View File

@ -1,25 +1,34 @@
<template>
<div class="emojiRoot">
<div v-for="(img, key) in emojis" v-html="img" :key="key" @click="onSendEmoticon(1, key, img)"
class="option pointer flex-center" />
<div>
<zPaging :fixed="false" :height="'210px'" :show-scrollbar="false">
<div class="emojiRoot">
<div
v-for="(img, key) in emojis"
v-html="img"
:key="key"
@click="onSendEmoticon(1, key, img)"
class="option pointer flex-center"
/>
</div>
</zPaging>
</div>
</template>
<script setup>
import { ref, reactive, defineProps,defineEmits } from "vue"
import dayjs from "dayjs";
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { ref, reactive, defineProps, defineEmits } from 'vue'
import dayjs from 'dayjs'
import { beautifyTime } from '@/utils/datetime'
import { useTalkStore } from '@/store'
import { useSessionMenu } from '@/hooks'
import { emojis } from '@/utils/emojis'
const props = defineProps({
data: {
type: Object,
default: {},
required: false,
},
});
})
const emit = defineEmits(['on-select'])
const onSendEmoticon = (type, value, img = '') => {
@ -33,21 +42,15 @@ const onSendEmoticon = (type, value, img = '') => {
emit('on-select', { type, value, img })
}
}
</script>
<style lang="scss" scoped>
.emojiRoot {
width: 100%;
height: 420rpx;
padding: 5rpx 32rpx;
display: flex;
justify-content: flex-start;
align-items: center;
flex-wrap: wrap;
overflow: auto;
}
.option {

View File

@ -1,25 +1,39 @@
<template>
<div class="emojiRoot">
<div @click="()=>photoActionsSelect(0)" class="flex flex-col items-center">
<div class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center">
<div
@click="() => photoActionsSelect(0)"
class="flex flex-col items-center"
>
<div
class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center"
>
<tm-image :width="53" :height="44" :src="photoAlbum"></tm-image>
</div>
<div class="mt-[6rpx] text-[#666666] text-[24rpx]">照片</div>
</div>
<div @click="()=>photoActionsSelect(1)" class="flex flex-col items-center">
<div class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center">
<div
@click="() => photoActionsSelect(1)"
class="flex flex-col items-center"
>
<div
class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center"
>
<tm-image :width="53" :height="44" :src="videoImg"></tm-image>
</div>
<div class="mt-[6rpx] text-[#666666] text-[24rpx]">视频</div>
</div>
<div @click="takePhoto" class="flex flex-col items-center">
<div class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center">
<div
class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center"
>
<tm-image :width="53" :height="44" :src="photoGraph"></tm-image>
</div>
<div class="mt-[6rpx] text-[#666666] text-[24rpx]">拍摄</div>
</div>
<div @click="chooseFile" class="flex flex-col items-center">
<div class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center">
<div
class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center"
>
<tm-image :width="53" :height="44" :src="folder"></tm-image>
</div>
<div class="mt-[6rpx] text-[#666666] text-[24rpx]">文件</div>
@ -27,10 +41,15 @@
</div>
</template>
<script setup>
import { ref, reactive, defineProps, defineEmits } from "vue"
import dayjs from "dayjs";
import { ref, reactive, defineProps, defineEmits } from 'vue'
import dayjs from 'dayjs'
import { beautifyTime } from '@/utils/datetime'
import { useDialogueListStore, useDialogueStore, useUserStore,useUploadsStore } from '@/store'
import {
useDialogueListStore,
useDialogueStore,
useUserStore,
useUploadsStore,
} from '@/store'
import { useSessionMenu } from '@/hooks'
import photoAlbum from '@/static/image/chatList/photoAlbum.png'
import photoGraph from '@/static/image/chatList/photoGraph.png'
@ -43,82 +62,167 @@ const props = defineProps({
sendUserInfo: {
type: Object,
default: {},
required: true
required: true,
},
talkParams: {
type: Object,
default: {},
required: true
}
});
required: true,
},
})
const state = reactive({
base64Url: '',
})
const uploadsStore = useUploadsStore()
const { addDialogueRecord, virtualList, updateUploadProgress } = useDialogueListStore()
const {
addDialogueRecord,
virtualList,
updateUploadProgress,
} = useDialogueListStore()
const dialogueStore = useDialogueStore()
const userStore = useUserStore()
const emit = defineEmits(['selectImg'])
const onProgressFn = (progress, id) => {
console.log(progress.loaded / progress.total * 100, 'progress');
console.log((progress.loaded / progress.total) * 100, 'progress')
updateUploadProgress(id, progress.loaded / progress.total * 100)
updateUploadProgress(id, (progress.loaded / progress.total) * 100)
}
const photoActionsSelect = (index) => {
if (index === 0) {
uni.chooseImage({
sourceType: ['album'],
count: 9,
success: async (res) => {
console.log(res,'res');
res.tempFiles.forEach(async (file) => {
let data = await onUploadImageVideo(file, 'image')
emit('selectImg', data)
})
}
})
}else{
if (typeof plus === 'undefined') {
uni.chooseImage({
sourceType: ['album'],
count: 9,
success: async (res) => {
console.log(res, 'res')
res.tempFiles.forEach(async (file) => {
const fileSizeInMB = (file.size / (1024 * 1024)).toFixed(2)
if (fileSizeInMB > 100) {
plus.nativeUI.toast('图片大小不能超过100MB')
return
}
const result = await onUploadImageVideo(file, 'image')
if (result) {
emit('selectImg', result, result.file_num)
}
})
},
})
} else {
plus?.gallery.pick(
(res) => {
console.log(res, 'res')
res.files.reverse()
res.files.forEach(async (filePath) => {
plus?.io?.resolveLocalFileSystemURL(
filePath,
async (entry) => {
entry.file((file) => {
const fileReader = new plus.io.FileReader()
fileReader.readAsDataURL(file)
fileReader.onloadend = async (e) => {
const base64Url = e.target.result
const fileObj = base64ToFile(base64Url)
const fileSizeInMB = (fileObj.size / (1024 * 1024)).toFixed(
2,
)
if (fileSizeInMB > 100) {
plus.nativeUI.toast('图片大小不能超过100MB')
return
}
let data = await onUploadImageVideo(fileObj, 'image')
if (data) {
emit('selectImg', data, data.file_num)
}
}
})
},
(err) => {
console.log(err)
},
)
})
},
(err) => {
console.log(err)
},
{
filter: 'image',
maximum: 9,
multiple: true,
onmaxed: () => {
plus.nativeUI.toast('最多只能选择9张图片')
},
},
)
}
} else {
// let OAWebView = plus.webview.all()
// OAWebView.forEach((webview, index) => {
// if (webview.id === 'webviewId1') {
// webview.evalJS(`getPlusVideoPicker()`)
// }
// })
// return
uni.chooseVideo({
sourceType: ['album'],
compressed: true,
maxDuration: 60,
success: async (res) => {
console.log(res,'res');
let data = await onUploadImageVideo(res.tempFile, 'video',res.tempFilePath)
emit('selectImg', data)
}
console.log(res, 'res')
const fileSizeInMB = (res.tempFile.size / (1024 * 1024)).toFixed(2)
if (fileSizeInMB > 100) {
plus.nativeUI.toast('视频大小不能超过100MB')
return
}
let data = await onUploadImageVideo(
res.tempFile,
'video',
res.tempFilePath,
)
if (data) {
emit('selectImg', data, data.file_num)
}
},
})
}
}
const onUploadImageVideo = async (file, type = 'image',fileUrl) => {
console.log(file, 'file');
const onUploadImageVideo = async (file, type = 'image', fileUrl) => {
console.log('开始上传文件:', file.name)
uploadsStore.updateUploadStatus(true)
return new Promise(async (resolve) => {
if (type === 'image') {
let image = new Image()
image.src = URL.createObjectURL(file)
image.onload = () => {
image.onload = async () => {
const form = new FormData()
form.append('file', file)
form.append("source", "fonchain-chat");
form.append("urlParam", `width=${image.width}&height=${image.height}`);
form.append('source', 'fonchain-chat')
form.append('urlParam', `width=${image.width}&height=${image.height}`)
let randomId = uniqueId()
let newItem = {
avatar: userStore.avatar,
created_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
extra: {
height: image.height,
name: "",
name: '',
size: 0,
url: image.src,
width: image.width
width: image.width,
},
float: "right",
float: 'right',
isCheck: false,
is_mark: 0,
is_read: 0,
is_revoke: 0,
msg_id: randomId,
file_num: randomId,
msg_type: 3,
nickname: userStore.nickname,
receiver_id: dialogueStore.talk.receiver_id,
@ -126,210 +230,372 @@ const onUploadImageVideo = async (file, type = 'image',fileUrl) => {
talk_type: dialogueStore.talk.talk_type,
user_id: userStore.uid,
uploadCurrent: 0,
uploadStatus: 1, // 1 2 3
uploadStatus: 1, // 1 2 3
}
virtualList.value.unshift(newItem)
uploadImg(form, (e) => onProgressFn(e, randomId)).then(({ status, data, msg }) => {
if (status == 0) {
try {
const result = await uploadImg(form, (e) => onProgressFn(e, randomId))
console.log('上传完成,结果:', result)
if (result.status === 0) {
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 2
virtualList.value[index].uploadCurrent = 100
}
//
resolve({
type: 'image',
url: data.ori_url,
url: result.data.ori_url,
size: file.size,
width: image.width,
height: image.height
height: image.height,
file_num: randomId,
})
} else {
uploadsStore.updateUploadStatus(false)
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 3
}
message.error(result.msg)
resolve('')
message.error(msg)
}
})
} catch (error) {
console.error('上传出错:', error)
uploadsStore.updateUploadStatus(false)
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 3
}
message.error('上传失败')
resolve('')
}
}
} else {
uni.getVideoInfo({
src:fileUrl,
success:(resp)=>{
console.log(resp);
form.append('file', file)
form.append("source", "fonchain-chat");
form.append("type", "video");
form.append("urlParam", `width=${resp.width}&height=${resp.height}`);
let randomId = uniqueId()
let newItem = {
avatar: userStore.avatar,
created_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
extra: {
duration: parseInt(resp.duration),
height: resp.height,
name: "",
url: fileUrl,
width: resp.width
},
float: "right",
isCheck: false,
is_mark: 0,
is_read: 0,
is_revoke: 0,
msg_id: randomId,
msg_type: 5,
nickname: userStore.nickname,
receiver_id: dialogueStore.talk.receiver_id,
sequence: -1,
talk_type: dialogueStore.talk.talk_type,
user_id: userStore.uid,
uploadCurrent: 0,
uploadStatus: 1, // 1 2 3
}
virtualList.value.unshift(newItem)
uploadImg(form, (e) => onProgressFn(e, randomId)).then(({ status, data, msg }) => {
if (status == 0) {
console.log(data);
resolve({
type: 'video',
url: data.ori_url,
cover: data.cover_url,
src: fileUrl,
success: async (resp) => {
console.log('视频信息:', resp)
const form = new FormData()
form.append('file', file)
form.append('source', 'fonchain-chat')
form.append('type', 'video')
form.append('urlParam', `width=${resp.width}&height=${resp.height}`)
let randomId = uniqueId()
let newItem = {
avatar: userStore.avatar,
created_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
extra: {
duration: parseInt(resp.duration),
size: file.size
})
} else {
// resolve('')
// message.error(msg)
height: resp.height,
name: '',
url: fileUrl,
width: resp.width,
},
float: 'right',
isCheck: false,
is_mark: 0,
is_read: 0,
is_revoke: 0,
msg_id: randomId,
file_num: randomId,
msg_type: 5,
nickname: userStore.nickname,
receiver_id: dialogueStore.talk.receiver_id,
sequence: -1,
talk_type: dialogueStore.talk.talk_type,
user_id: userStore.uid,
uploadCurrent: 0,
uploadStatus: 1,
}
})
}
virtualList.value.unshift(newItem)
try {
const result = await uploadImg(form, (e) =>
onProgressFn(e, randomId),
)
console.log('视频上传完成,结果:', result)
if (result.status === 0) {
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 2
virtualList.value[index].uploadCurrent = 100
}
resolve({
type: 'video',
url: result.data.ori_url,
cover: result.data.cover_url,
duration: parseInt(resp.duration),
size: file.size,
file_num: randomId,
})
} else {
uploadsStore.updateUploadStatus(false)
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 3
}
message.error(result.msg)
resolve('')
}
} catch (error) {
console.error('视频上传出错:', error)
uploadsStore.updateUploadStatus(false)
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 3
}
message.error('上传失败')
resolve('')
}
},
fail: (error) => {
console.error('获取视频信息失败:', error)
uploadsStore.updateUploadStatus(false)
message.error('获取视频信息失败')
resolve('')
},
})
const form = new FormData()
}
})
}
const base64ToFile = (base64) => {
if (!base64) {
message.warning('您的系统暂不支持发送原图哦')
}
// base64file
const [header, base64String] = base64.split(";base64,");
const imageType = header.split(":")[1];
const byteCharacters = atob(base64String);
const [header, base64String] = base64.split(';base64,')
const imageType = header.split(':')[1]
const byteCharacters = atob(base64String)
const byteArray = new Uint8Array(
Array.from(byteCharacters, (char) => char.charCodeAt(0))
);
return new File(
[new Blob([byteArray], { type: imageType })],
"example.png",
{ type: imageType }
);
Array.from(byteCharacters, (char) => char.charCodeAt(0)),
)
return new File([new Blob([byteArray], { type: imageType })], 'example.png', {
type: imageType,
})
}
const choosePhoto = (filter = 'none', maximum = 9, multiple = true) => {
window.plus?.gallery.pick((res) => {
console.log(res);
res.files.reverse()
res.files.forEach(async (filePath) => {
const suffix = filePath.split('.').pop()?.toLowerCase() || ''
if (['jpg', 'png'].includes(suffix)) {
console.log("进入图片")
window.plus?.io?.resolveLocalFileSystemURL(filePath, async (entry) => {
entry.file((file) => {
const fileReader = new plus.io.FileReader();
fileReader.readAsDataURL(file);
fileReader.onloadend = async (e) => {
const base64Url = e.target.result;
const fileObj = base64ToFile(base64Url);
let data = await onUploadImageVideo(fileObj, 'image')
emit('selectImg', data)
};
})
},
(err) => {
console.log(err);
}
)
}
if (['mp4', 'flv'].includes(suffix)) {
console.log(filePath,"进入视频")
// const localUrl = plus.io.convertLocalFileSystemURL(filePath)
// console.log(localUrl);
plus.io.getVideoInfo({
filePath:filePath,
success:(event)=>{
console.log(event);
},
fail:(err)=>{
console.log(err);
window.plus?.gallery.pick(
(res) => {
console.log(res)
res.files.reverse()
res.files.forEach(async (filePath) => {
const suffix = filePath.split('.').pop()?.toLowerCase() || ''
if (['jpg', 'png'].includes(suffix)) {
console.log('进入图片')
window.plus?.io?.resolveLocalFileSystemURL(
filePath,
async (entry) => {
entry.file((file) => {
const fileReader = new plus.io.FileReader()
fileReader.readAsDataURL(file)
fileReader.onloadend = async (e) => {
const base64Url = e.target.result
const fileObj = base64ToFile(base64Url)
let data = await onUploadImageVideo(fileObj, 'image')
emit('selectImg', data)
}
})
},
(err) => {
console.log(err)
},
)
}
});
// window.plus?.io?.resolveLocalFileSystemURL(localUrl, async (entry) => {
// entry.file((file) => {
// console.log(file,'file');
// const fileReader = new plus.io.FileReader();
// fileReader.readAsDataURL(file);
// fileReader.onloadend = async (e) => {
// const base64Url = e.target.result;
// const fileObj = base64ToFile(base64Url);
// let data = await onUploadImageVideo(fileObj, 'video')
// emit('selectImg', data)
// };
// })
// },
// (err) => {
// console.log(err);
// }
// )
}
})
}, (err) => {
console.log(err);
},
if (['mp4', 'flv'].includes(suffix)) {
console.log(filePath, '进入视频')
// const localUrl = plus.io.convertLocalFileSystemURL(filePath)
// console.log(localUrl);
plus.io.getVideoInfo({
filePath: filePath,
success: (event) => {
console.log(event)
},
fail: (err) => {
console.log(err)
},
})
// window.plus?.io?.resolveLocalFileSystemURL(localUrl, async (entry) => {
// entry.file((file) => {
// console.log(file,'file');
// const fileReader = new plus.io.FileReader();
// fileReader.readAsDataURL(file);
// fileReader.onloadend = async (e) => {
// const base64Url = e.target.result;
// const fileObj = base64ToFile(base64Url);
// let data = await onUploadImageVideo(fileObj, 'video')
// emit('selectImg', data)
// };
// })
// },
// (err) => {
// console.log(err);
// }
// )
}
})
},
(err) => {
console.log(err)
},
{
filter: filter,
maximum: maximum,
multiple: multiple,
}
},
)
}
const takePhoto = () => {
if (typeof plus !== 'undefined') {
getCamera()
} else {
document.addEventListener('plusready', () => {
getCamera()
})
}
}
const chooseFile = () => {
uni.chooseFile({
count: 1,
extension:[''],
success: (res) => {
let randomId = uniqueId()
let newItem = {
avatar: userStore.avatar,
created_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
extra: {
drive: 3,
name: res.tempFiles[0].name,
size: res.tempFiles[0].size,
path: res.tempFilePaths[0],
},
float: "right",
isCheck: false,
is_mark: 0,
is_read: 0,
is_revoke: 0,
msg_id: randomId,
msg_type: 6,
nickname: userStore.nickname,
receiver_id: dialogueStore.talk.receiver_id,
sequence: -1,
talk_type: dialogueStore.talk.talk_type,
user_id: userStore.uid,
uploadCurrent: 0,
uploadStatus: 1, // 1 2 3
}
virtualList.value.unshift(newItem)
uploadsStore.initUploadFile(res.tempFiles[0], props.talkParams,randomId)
}
const getCamera = () => {
const cmr = plus.camera.getCamera()
cmr.captureImage(
(p) => {
plus.io.resolveLocalFileSystemURL(
p,
(entry) => {
compressAndShowImage(entry.toLocalURL(), entry.name)
},
(err) => {
console.log(err)
},
)
},
() => {},
{ index: '2' },
)
}
const compressAndShowImage = (url, filename) => {
const dst = `_doc/upload/${filename}`
plus.zip.compressImage(
{ src: url, dst, quality: 10, overwrite: true },
(zip) => displayImage(zip.target),
(err) => {
console.log(err)
},
)
}
const displayImage = (url) => {
plus.io.resolveLocalFileSystemURL(url, (entry) => {
entry.file((file) => {
const fileReader = new plus.io.FileReader()
fileReader.readAsDataURL(file)
fileReader.onloadend = async (e) => {
state.base64Url = e.target.result
const imageFile = base64ToFile(state.base64Url)
let data = await onUploadImageVideo(imageFile, 'image')
emit('selectImg', data, data.file_num)
}
})
})
}
const chooseFile = () => {
uni.chooseFile({
count: 1,
extension: [''],
success: (res) => {
const fileSizeInMB = (res.tempFiles[0].size / (1024 * 1024)).toFixed(2)
if (fileSizeInMB > 100) {
plus.nativeUI.toast('文件大小不能超过100MB')
return
}
let randomId = uniqueId()
let newItem = {
avatar: userStore.avatar,
created_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
extra: {
drive: 3,
name: res.tempFiles[0].name,
size: res.tempFiles[0].size,
path: res.tempFilePaths[0],
},
float: 'right',
isCheck: false,
is_mark: 0,
is_read: 0,
is_revoke: 0,
msg_id: randomId,
file_num: randomId,
msg_type: 6,
nickname: userStore.nickname,
receiver_id: dialogueStore.talk.receiver_id,
sequence: -1,
talk_type: dialogueStore.talk.talk_type,
user_id: userStore.uid,
uploadCurrent: 0,
uploadStatus: 1, // 1 2 3
}
virtualList.value.unshift(newItem)
uploadsStore.updateUploadStatus(true)
uploadsStore.initUploadFile(
res.tempFiles[0],
props.talkParams,
randomId,
(status, data, msg) => {
if (status === 0) {
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 2
virtualList.value[index].uploadCurrent = 100
}
} else {
uploadsStore.updateUploadStatus(false)
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 3
}
message.error(msg)
}
},
)
},
})
}
</script>
<style lang="scss" scoped>
.emojiRoot {

View File

@ -3,13 +3,7 @@
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<tm-navbar
class="tmNavBar"
:hideBack="false"
hideHome
title=""
:leftWidth="220"
></tm-navbar>
<customNavbar class="tmNavBar"></customNavbar>
</template>
<div class="user-detail-info">
<div class="user-info-head user-info-card">
@ -33,14 +27,17 @@
<span class="text-[40rpx] font-medium user-info-name">
{{ state?.userInfo?.nickname }}
</span>
<span class="text-[28rpx] font-medium user-info-job-num">
<span
class="text-[28rpx] font-medium user-info-job-num"
v-if="state?.userInfo?.job_num"
>
{{ $t('user.info.jobNum') + '' + state?.userInfo?.job_num }}
</span>
</div>
</div>
<div class="user-info-main user-info-card">
<div class="user-info-main-title">
<img src="/src/static/image/mine/ming001@3x.png" />
<img src="@/static/image/mine/ming001@3x.png" />
<span class="text-[28rpx] font-medium">
{{ $t('index.mine.basic') }}
</span>
@ -65,11 +62,43 @@
<customBtn
:isBottom="true"
:btnText="$t('user.detail.sendMsg')"
:subBtnText="$t('user.detail.ringBell')"
:subBtnText="
state.userInfo.sys_id === state.uid
? ''
: $t('user.detail.ringBell')
"
@clickBtn="toTalkUser"
@clickSubBtn="handleCall"
></customBtn>
</template>
</ZPaging>
</div>
<tm-drawer
placement="bottom"
v-model:show="state.isShowPhoneCall"
:hideHeader="true"
:height="416"
:round="6"
>
<div class="do-phone-call">
<div class="do-phone-call-header">
<span>{{ $t('popup.title.phone') }}</span>
<img
src="@/static/image/login/check-circle-filled@3x.png"
@click="hidePhoneCallPopup"
/>
</div>
<div class="do-phone-call-number">
<span>{{ state.phoneNumber }}</span>
</div>
<div class="do-phone-call-btn">
<customBtn
:btnText="$t('do.phone.call')"
@clickBtn="doPhoneCall"
></customBtn>
</div>
</div>
</tm-drawer>
</div>
</template>
<script setup>
@ -78,6 +107,10 @@ import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { onLoad } from '@dcloudio/uni-app'
import { reactive } from 'vue'
import { useTalkStore, useUserStore } from '@/store'
const talkStore = useTalkStore()
const userStore = useUserStore()
import { getUserInfoByClickAvatar } from '@/api/user/index'
import { useI18n } from 'vue-i18n'
@ -87,12 +120,15 @@ const state = reactive({
erpUserId: '', //erpid
userInfo: null, //
userBasicInfos: [], //
isShowPhoneCall: false, //
phoneNumber: '', //
uid: computed(() => userStore.uid), //id
})
onLoad((options) => {
console.log(options)
if (options.erpUserId) {
state.erpUserId = options.erpUserId
state.erpUserId = Number(options.erpUserId)
getUserInfo()
}
})
@ -100,7 +136,7 @@ onLoad((options) => {
//
const getUserInfo = () => {
let params = {
erp_user_id: Number(state.erpUserId),
erp_user_id: state.erpUserId,
}
console.log(params)
const resp = getUserInfoByClickAvatar(params)
@ -166,12 +202,41 @@ const getUserInfo = () => {
value: data.enter_date,
},
]
state.phoneNumber = data.tel_num
} else {
}
})
resp.catch(() => {})
}
//
const handleCall = () => {
state.isShowPhoneCall = true
}
//
const hidePhoneCallPopup = () => {
state.isShowPhoneCall = false
}
//
const toTalkUser = () => {
talkStore.toTalk(1, state.userInfo.sys_id, state.erpUserId)
}
//
const doPhoneCall = () => {
uni.makePhoneCall({
phoneNumber: state.phoneNumber,
success: () => {
console.log('拨号成功')
},
fail: (err) => {
console.error('失败:', err)
},
})
}
</script>
<style scoped lang="scss">
.outer-layer {
@ -284,4 +349,58 @@ const getUserInfo = () => {
}
}
}
.do-phone-call {
.do-phone-call-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 42rpx 0;
position: relative;
span {
font-size: 28rpx;
line-height: 40rpx;
color: #747474;
font-weight: 400;
}
img {
position: absolute;
top: 44rpx;
right: 30rpx;
width: 36rpx;
height: 36rpx;
}
}
.do-phone-call-number {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 32rpx 0;
border-top: 2rpx solid #e7e7e7;
border-bottom: 2rpx solid #e7e7e7;
span {
font-size: 32rpx;
line-height: 44rpx;
color: #1a1a1a;
font-weight: 400;
}
}
.do-phone-call-btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 50rpx 0;
:deep(.custom-btn-class) {
width: 690rpx;
font-size: 32rpx;
line-height: 44rpx;
padding: 18rpx 0;
height: 80rpx;
background: linear-gradient(to right, #674bbc, #46299d);
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
<script lang="ts" setup>
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { ref, onMounted } from 'vue'
import { ServeGetForwardRecords } from '@/api/chat'
import { MessageComponents } from '@/constant/message'
@ -24,8 +25,13 @@ const onLoadData = () => {
}).then((res) => {
if (res.code == 200) {
items.value = res.data.items || []
title.value = [...new Set(items.value.map((v) => v.nickname))].join('、')
//
const uniqueNames = [...new Set(items.value.map(v => v.nickname))];
if (uniqueNames.length <= 2) {
title.value = uniqueNames.join('和');
} else {
title.value = uniqueNames.slice(0, 2).join('和') + '等';
}
}
})
}
@ -42,12 +48,12 @@ onMounted(() => {
</script>
<template>
<div class="outer-layer">
<div>
<tm-navbar :hideBack="false" hideHome :title="`${title}的会话记录`" >
</tm-navbar>
</div>
<div class="main-box">
<div class="forward-record-page">
<zPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<customNavbar :title="`${title}的会话记录`"></customNavbar>
</template>
<div class="main-box">
<div v-if="items.length === 0" class="flex justify-center items-center w-full mt-[200rpx]">
<wd-loading />
</div>
@ -71,16 +77,15 @@ onMounted(() => {
</div>
</div>
</div>
</zPaging>
</div>
</template>
<style lang="less" scoped>
.outer-layer {
overflow-y: auto;
.forward-record-page {
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
background-size: cover;
padding: 0 66rpx 20rpx 50rpx;
display: flex;
flex-direction: column;
}
@ -91,7 +96,7 @@ onMounted(() => {
display: flex;
flex-direction: column;
overflow: auto;
padding-top: 28rpx;
padding: 28rpx 66rpx 20rpx 50rpx;
}
.message-item {

View File

@ -1,48 +1,73 @@
<template>
<div>
<wd-swipe-action>
<wd-swipe-action class="swipe_action">
<div
@click="cellClick"
:class="['chatItem', props.data.is_top === 1 ? 'isTop' : '']"
>
<div class="avatarImg">
<tm-badge
:count="props.data.unread_num"
:count="props.data.is_disturb === 0 ? props.data.unread_num : ''"
:dot="
props.data.is_disturb === 1 && props.data.unread_num
? true
: false
"
:maxCount="99"
class="badge"
color="#D03050"
>
<tm-image
:width="96"
:height="96"
:round="12"
:src="avatarCpt"
></tm-image>
<avatarModule
:mode="props?.data?.group_type === 0 ? 1 : 2"
:avatar="props?.data?.avatar"
:groupType="props?.data?.group_type"
:userName="props?.data?.name"
:customStyle="{
width: '96rpx',
height: '96rpx',
}"
:customTextStyle="{
fontSize: '32rpx',
fontWeight: 'bold',
color: '#fff',
lineHeight: '44rpx',
}"
></avatarModule>
</tm-badge>
</div>
<div class="chatInfo">
<div class="chatInfo_1">
<div class="flex items-center">
<div class="text-[#000000] text-[32rpx] font-bold opacity-90">
{{ props.data.name }}
</div>
<div>
<div v-if="props.data.group_type === 2" class="depTag">
<div class="name_center">
<div
class="text-[#000000] text-[32rpx] font-bold opacity-90 name_text"
>
{{ formatNameText(props.data.name) }}
<span v-if="props.data.talk_type === 2">
{{ props.data.group_member_num }}
</span>
<span v-if="props.data.group_type === 2" class="depTag tag">
部门
</div>
<div v-if="props.data.group_type === 3" class="projectTag">
</span>
<span v-if="props.data.group_type === 3" class="projectTag tag">
项目
</div>
<div v-if="props.data.group_type === 4" class="companyTag">
</span>
<span v-if="props.data.group_type === 4" class="companyTag tag">
公司
</div>
</span>
</div>
</div>
<div class="text-[#000000] text-[28rpx] font-medium opacity-26">
<div
style="flex-shrink: 0;"
class="text-[#000000] text-[28rpx] font-medium opacity-26 ml-[24rpx] time_right"
>
{{ beautifyTime(props.data.updated_at) }}
</div>
</div>
<div class="chatInfo_2 w-full mr-[6rpx]">
<div class="w-full chatInfo_2_1 textEllipsis">
<span v-if="props.data.atsign_num" style="color: red;">
[有人@]
</span>
{{ props.data.msg_text }}
</div>
</div>
@ -50,13 +75,16 @@
</div>
<template #right>
<div class="flex flex-row flex-row-center-end">
<!-- 样式占位 -->
<div style="width: 1px;"></div>
<div
@click="handleTop"
class="w-[156rpx] h-[154rpx] text-[#ffffff] bg-[#F09F1F] flex items-center justify-center"
>
{{ props.data.is_top === 1 ? "取消置顶" : "置顶" }}
{{ props.data.is_top === 1 ? '取消置顶' : '置顶' }}
</div>
<div
@click="handleDelete"
class="w-[156rpx] h-[154rpx] text-[#ffffff] bg-[#CF3050] flex items-center justify-center"
>
删除
@ -71,20 +99,20 @@
</div>
</template>
<script setup>
import { ref, reactive, defineProps,computed } from "vue";
import dayjs from "dayjs";
import { beautifyTime } from "@/utils/datetime";
import { ServeClearTalkUnreadNum } from "@/api/chat";
import { useTalkStore, useDialogueStore } from "@/store";
import { useSessionMenu } from "@/hooks";
import zu4989 from "@/static/image/chatList/zu4989@2x.png";
import zu4991 from "@/static/image/chatList/zu4991@2x.png";
import zu4992 from "@/static/image/chatList/zu4992@2x.png";
import zu5296 from "@/static/image/chatList/zu5296@2x.png";
import avatarModule from '@/components/avatar-module/index.vue'
import { ref, reactive, defineProps, computed } from 'vue'
import dayjs from 'dayjs'
import { beautifyTime } from '@/utils/datetime'
import { ServeClearTalkUnreadNum } from '@/api/chat'
import { useTalkStore, useDialogueStore } from '@/store'
import { useSessionMenu } from '@/hooks'
const talkStore = useTalkStore();
const { onToTopTalk } = useSessionMenu();
const dialogueStore = useDialogueStore();
const talkStore = useTalkStore()
const { onToTopTalk, onRemoveTalk } = useSessionMenu()
const dialogueStore = useDialogueStore()
const dialogueParams = reactive({
unReadNum: computed(() => dialogueStore.unreadNum),
})
const props = defineProps({
data: {
type: Object,
@ -96,59 +124,59 @@ const props = defineProps({
default: -1,
required: true,
},
});
})
const avatarCpt = computed(() => {
let avatar = null;
if (props.data.avatar !== "") {
avatar = props.data.avatar;
} else {
switch (props.data.group_type) {
case 1:
avatar = zu4992;
break;
case 2:
avatar = zu4989;
break;
case 3:
avatar = zu4991;
break;
case 4:
avatar = zu5296;
break;
}
}
return avatar;
});
//
const formatNameText = (text, maxLength = 16) => {
return text.length > maxLength ? `${text.slice(0, maxLength - 1)}...` : text
}
const cellClick = () => {
console.log(props.data);
console.log(props.data)
//
dialogueStore.setDialogue(props.data);
dialogueStore.setDialogue(props.data)
//
if (props.data.unread_num > 0) {
ServeClearTalkUnreadNum({
talk_type: props.data.talk_type,
receiver_id: props.data.receiver_id,
}).then(() => {
ServeClearTalkUnreadNum(
{
talk_type: props.data.talk_type,
receiver_id: props.data.receiver_id,
},
dialogueParams.unReadNum,
).then(() => {
talkStore.updateItem({
index_name: props.data.index_name,
unread_num: 0,
});
});
})
dialogueStore.clearUnreadNum()
})
}
uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + props.data.id,
});
};
url: `/pages/dialog/index?sessionId=${props.data.id}`,
})
}
const handleTop = () => {
console.log(props.data, 1);
onToTopTalk(props.data);
};
console.log(props.data, 1)
onToTopTalk(props.data)
}
//
const handleDelete = () => {
console.log(props.data)
onRemoveTalk(props.data)
}
</script>
<style lang="scss" scoped>
::v-deep .swipe_action {
// border: 1px solid #fff;
// transform: translate3d(1px, 0px, 0px) !important;
}
::v-deep .badge .round-6 {
min-width: 22rpx;
min-height: 22rpx;
}
.chatItem {
width: 100%;
height: 154rpx;
@ -161,11 +189,6 @@ const handleTop = () => {
}
}
.avatarImg {
height: 96rpx;
width: 96rpx;
}
.chatInfo {
flex: 1;
margin-left: 20rpx;
@ -186,47 +209,54 @@ const handleTop = () => {
.chatInfo_2_1 {
font-size: 28rpx;
color: #000000;
opacity: 40%;
color: rgba($color: #000000, $alpha: 0.4);
}
.tag {
display: inline-flex;
align-items: center;
text-align: center;
// margin-left: 10rpx;
margin-top: 4rpx;
vertical-align: top;
height: 38rpx;
line-height: 38rpx;
padding: 0 10rpx;
font-size: 24rpx;
border-radius: 6rpx;
font-weight: bold;
}
.companyTag {
width: 76rpx;
height: 38rpx;
line-height: 38rpx;
border: 1px solid #7a58de;
font-size: 24rpx;
text-align: center;
border-radius: 6rpx;
color: #7a58de;
font-weight: bold;
margin-left: 12rpx;
}
.depTag {
width: 76rpx;
height: 38rpx;
line-height: 38rpx;
border: 1px solid #377ec6;
font-size: 24rpx;
text-align: center;
border-radius: 6rpx;
color: #377ec6;
font-weight: bold;
margin-left: 12rpx;
}
.projectTag {
width: 76rpx;
height: 38rpx;
line-height: 38rpx;
border: 1px solid #c1681c;
font-size: 24rpx;
text-align: center;
border-radius: 6rpx;
color: #c1681c;
font-weight: bold;
margin-left: 12rpx;
}
.name_center {
flex: 1;
min-width: 0;
}
.name_text {
display: inline-block;
max-height: 88rpx; //
line-height: 44rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-all;
}
.time_right {
/* white-space: nowrap;
max-width: 146rpx; */
flex: 0 0 auto; /* 不伸缩,保持内容宽度 */
white-space: nowrap;
}
.textEllipsis {
@ -235,6 +265,7 @@ const handleTop = () => {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
word-break: break-all;
}
.divider {

View File

@ -1,101 +1,167 @@
<template>
<div class="outer-layer">
<div>
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="420">
<template v-slot:left>
<div class="flex items-center ml-[48rpx]">
<tm-image
:width="72"
:height="72"
:round="12"
:src="userStore.avatar"
></tm-image>
<div class="ml-[24rpx] text-[36rpx] font-bold">
{{ userStore.nickname }}
</div>
</div>
</template>
<template v-slot:right>
<div class="mr-[48rpx] popoverBox">
<tm-popover position="br" color="#333333" :width="260">
<tm-image
:width="41"
:height="41"
:round="12"
:src="addCircle"
></tm-image>
<template v-slot:label>
<div class="w-full h-[208rpx] pt-[22rpx] pb-[22rpx] pl-[34rpx] pr-[32rpx]" >
<div @click="creatGroupChat" class="flex items-center mb-[30rpx]" >
<div class="mr-[26rpx] flex items-center">
<tm-image
:width="40"
:height="40"
:src="cahtPopover"
></tm-image>
</div>
<div class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold" >发起群聊</div>
</div>
<div class="divider" ></div>
<div class="flex items-center mt-[28rpx]" >
<div class="mr-[26rpx] flex items-center">
<tm-image
:width="40"
:height="40"
:src="zu3289"
></tm-image>
</div>
<div class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold" >
通讯录
</div>
</div>
<ZPaging
class="paging_container"
ref="paging"
refresher-enabled
:refresher-threshold="80"
:refresher-max-angle="0"
:refresher-pull-rate="0.5"
:refresher-fixed-bac-height="80"
refresher-fixed-background="#F9F9FD"
refresher-background="#F9F9FD"
v-model="items"
@query="queryList"
:loading-more-enabled="false"
:refresher-end-bounce-enabled="false"
:auto="true"
:empty-view-show="isEmptyViewShow"
@refresherRefresh="onRefresh"
:show-scrollbar="false"
>
<template #top>
<div>
<tm-navbar
class="index_top_navbar"
:hideBack="false"
hideHome
title=""
:leftWidth="420"
:showStatusBar="false"
>
<template v-slot:left>
<div class="flex items-center ml-[48rpx]">
<!-- <image
class="w-[72rpx] h-[72rpx]"
style="border-radius: 50%;"
:src="userStore.avatar"
mode="scaleToFill"
/> -->
<avatarModule
:mode="1"
:avatar="userStore.avatar"
:groupType="0"
:userName="userStore.nickname"
:customStyle="{ width: '72rpx', height: '72rpx' }"
:customTextStyle="{
fontSize: '32rpx',
fontWeight: 'bold',
color: '#fff',
lineHeight: '44rpx',
}"
></avatarModule>
<div class="ml-[24rpx] text-[36rpx] font-bold">
{{ userStore.nickname }}
</div>
</template>
</tm-popover>
</div>
</template>
<template v-slot:right>
<div class="mr-[48rpx] popoverBox">
<tm-popover position="br" color="#333333" :width="260">
<image
class="w-[48rpx] h-[48rpx]"
style="border-radius: 50%;"
:src="addCircle"
mode="scaleToFill"
/>
<template v-slot:label>
<div
class="w-full h-[208rpx] pt-[22rpx] pb-[32rpx] pl-[14rpx] pr-[12rpx]"
>
<div
@click="creatGroupChat"
class="flex items-center pl-[22rpx] mb-[32rpx]"
>
<div class="mr-[26rpx] flex items-center">
<tm-image
:width="40"
:height="39"
:src="cahtPopover"
></tm-image>
</div>
<div
class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold"
>
发起群聊
</div>
</div>
<div class="divider"></div>
<div
@click="toAddressBookPage"
class="flex items-center pl-[22rpx] mt-[32rpx]"
>
<div class="mr-[26rpx] flex items-center">
<tm-image
:width="40"
:height="43"
:src="zu3289"
></tm-image>
</div>
<div
class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold"
>
通讯录
</div>
</div>
</div>
</template>
</tm-popover>
</div>
</template>
</tm-navbar>
</div>
</template>
<template #refresher="{ refresherStatus }">
<custom-refresher :status="refresherStatus" />
</template>
<div class="content">
<div class="root">
<div class="searchRoot" @click="toSearchPage">
<customInput :disabled="true"></customInput>
</div>
</template>
</tm-navbar>
</div>
<div class="root">
<div class="searchRoot" @click="toSearchPage">
<tm-input
placeholder="请输入…"
color="#F9F9FD"
:round="1"
prefix="tmicon-search"
prefixColor="#46299D"
></tm-input>
<div class="contentRoot">
<chatItem
v-for="(item, index) in items"
:key="item.index_name"
:index="index"
:data="item"
/>
</div>
</div>
</div>
<div class="contentRoot">
<chatItem
v-for="(item, index) in items"
:key="item.index_name"
:index="index"
:data="item"
/>
</div>
</div>
</ZPaging>
</div>
</template>
<script setup>
import { ref, watch, computed } from "vue";
import { onShow, onLoad } from "@dcloudio/uni-app";
import { useChatList } from "@/store/chatList/index.js";
import { useAuth } from "@/store/auth";
import { useTalkStore, useUserStore } from "@/store";
import chatItem from "./components/chatItem.vue";
import addCircle from "@/static/image/chatList/addCircle.png";
import cahtPopover from "@/static/image/chatList/cahtPopover.png";
import zu3289 from "@/static/image/chatList/zu3289@2x.png";
import customInput from '@/components/custom-input/custom-input.vue'
import { ref, watch, computed } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import { ServeClearTalkUnreadNum } from '@/api/chat'
import { useTalkStore, useUserStore, useDialogueStore } from '@/store'
import chatItem from './components/chatItem.vue'
import addCircle from '@/static/image/chatList/addCircle.png'
import cahtPopover from '@/static/image/chatList/cahtPopover.png'
import zu3289 from '@/static/image/chatList/zu3289@2x.png'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { handleSetWebviewStyle } from '@/utils/common'
const paging = ref()
const isEmptyViewShow = ref(false)
const talkStore = useTalkStore()
const userStore = useUserStore()
const dialogueStore = useDialogueStore()
const dialogueParams = reactive({
unReadNum: computed(() => dialogueStore.unreadNum),
})
const { userInfo } = useAuth()
const talkStore = useTalkStore();
const userStore = useUserStore();
const { userInfo } = useAuth();
const topItems = computed(() => talkStore.topItems);
const topItems = computed(() => talkStore.topItems)
const items = computed(() => {
// if (searchKeyword.value.length === 0) {
return talkStore.talkItems;
console.log(talkStore.talkItems)
return talkStore.talkItems
// }
// return talkStore.talkItems.filter((item) => {
@ -103,66 +169,179 @@ const items = computed(() => {
// return keyword.toLowerCase().indexOf(searchKeyword.value.toLowerCase()) != -1
// })
});
})
const queryList = (pageNo, pageSize) => {
// paging.value.complete(res.data.list);
console.log(talkStore)
talkStore
.loadTalkList()
.then(() => {
isEmptyViewShow.value = talkStore.talkItems.length === 0
//
paging.value.complete(talkStore.talkItems)
})
.catch((error) => {
//
console.error('加载失败', error)
paging.value.complete(false)
})
}
//
const onRefresh = () => {
console.log('触发下拉刷新')
talkStore
.loadTalkList()
.then(() => {
//
paging.value.complete(talkStore.talkItems)
})
.catch((error) => {
//
console.error('加载失败', error)
paging.value.complete(false)
})
}
const creatGroupChat = () => {
uni.navigateTo({
url: "/pages/creatGroupChat/index",
});
};
url: '/pages/creatGroupChat/index',
})
}
const toSearchPage = () => {
uni.navigateTo({
url: "/pages/search/index",
});
};
url: '/pages/search/index',
})
}
watch(
() => talkStore,
(newValue, oldValue) => {
console.log(talkStore);
},
{ deep: true, immediate: true }
);
//
const toAddressBookPage = () => {
uni.navigateTo({
url: '/pages/chooseByDeps/index?chooseMode=3',
})
}
/* watch(
() => talkStore,
(newValue, oldValue) => {
// console.log(talkStore)
},
{ deep: true, immediate: true }
); */
onShow(() => {
talkStore.loadTalkList();
});
handleSetWebviewStyle(true)
//
talkStore
.loadTalkList()
.then(() => {
// paging
if (paging.value) {
paging.value.reload()
}
})
.catch((error) => {
console.error('页面显示时数据加载失败', error)
})
})
onLoad((options) => {
console.log(options)
if (options?.openSessionIndexName) {
if (items?.value?.length > 0) {
items.value.forEach((openSession) => {
if (openSession.index_name === options?.openSessionIndexName) {
setTimeout(() => {
dialogueStore.setDialogue(openSession)
if (openSession.unread_num > 0) {
ServeClearTalkUnreadNum(
{
talk_type: openSession.talk_type,
receiver_id: openSession.receiver_id,
},
dialogueParams.unReadNum,
).then(() => {
talkStore.updateItem({
index_name: openSession.index_name,
unread_num: 0,
})
dialogueStore.clearUnreadNum()
})
}
uni.navigateTo({
url: `/pages/dialog/index?sessionId=${openSession.id}`,
})
}, 500)
}
})
}
}
})
</script>
<style scoped lang="scss">
uni-page-body,
page {
height: 100%;
height: 100vh;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
flex-direction: column;
}
.outer-layer {
overflow-y: auto;
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
background-size: cover;
padding: 0 32rpx 20rpx 32rpx;
display: flex;
flex-direction: column;
height: 100%;
}
.paging_container {
// border: 1px solid red;
background: #fff;
margin: 0 32rpx 20rpx 32rpx;
}
::v-deep .index_top_navbar > .statusHeight:first-child {
height: 70px !important;
}
::v-deep .index_top_navbar .statusHeightTop {
height: 70px !important;
}
::v-deep .index_top_navbar .statusHeightTop > .noNvueBorder:first-child {
height: 70px !important;
}
.content {
// overflow: auto;
}
.root {
flex: 1;
padding: 20rpx 0;
}
.searchRoot {
background-color: #fff;
padding: 22rpx 18rpx;
border-bottom: 16rpx rgba(249, 249, 253, 1) solid;
::v-deep .noNvueBorder > .noNvueBorder > .noNvueBorder {
background: #f9f9fd !important;
}
}
.contentRoot {
margin-top: 20rpx;
background-color: #fff;
// min-height: calc(100vh - 180px);
}
.divider{
.divider {
height: 1rpx;
background-color: #7C7C7C;
background-color: #7c7c7c;
opacity: 0.6;
}
.popoverBox {
:deep(.popover-bcc){
transform:translateX(20rpx) translateY(40rpx);
:deep(.popover-bcc) {
transform: translateX(16rpx) translateY(48rpx);
}
}
</style>

View File

@ -9,15 +9,23 @@
: ''
"
>
<div
class="avatar-img"
:class="props?.conditionType ? 'avatar-img-condition' : ''"
>
<img v-if="avatarImg !== 'textImg'" :src="avatarImg" />
<span v-if="avatarImg === 'textImg'" class="text-[32rpx] font-bold">
{{ imgText }}
</span>
</div>
<avatarModule
:mode="props.searchItem?.group_type === 0 ? 1 : 2"
:avatar="avatarImg"
:userName="resultName"
:groupType="props.searchItem?.group_type"
:customStyle="{
width: props?.conditionType ? '64rpx' : '96rpx',
height: props?.conditionType ? '64rpx' : '96rpx',
margin: props?.conditionType ? '0 18rpx 0 0' : '0 20rpx 0 0',
}"
:customTextStyle="{
fontSize: props?.conditionType ? '20rpx' : '32rpx',
fontWeight: 'bold',
color: '#fff',
lineHeight: '44rpx',
}"
></avatarModule>
<div class="result-info">
<div
class="info-name"
@ -75,10 +83,7 @@
</div>
</template>
<script setup>
import zu4992 from '@/static/image/chatList/zu4992@2x.png'
import zu4991 from '@/static/image/chatList/zu4991@2x.png'
import zu4989 from '@/static/image/chatList/zu4989@2x.png'
import zu5296 from '@/static/image/chatList/zu5296@2x.png'
import avatarModule from '@/components/avatar-module/index.vue'
import {
ref,
watch,
@ -91,14 +96,30 @@ import {
import HighlightText from './highLightText.vue'
import { useI18n } from 'vue-i18n'
import { beautifyTime } from '@/utils/datetime'
import { ChatMsgTypeMapping } from '@/constant/message'
const { t } = useI18n()
const props = defineProps({
searchItem: Object | Number,
searchResultKey: String,
searchText: String, //
searchRecordDetail: Boolean, //
pointerIconSrc: String, //
conditionType: Number, //
searchResultKey: {
type: String,
default: '',
},
searchText: {
type: String,
default: '',
}, //
searchRecordDetail: {
type: Boolean,
default: false,
}, //
pointerIconSrc: {
type: String,
default: '',
}, //
conditionType: {
type: Number,
default: 0,
}, //
})
// -
const keyMapping = {
@ -155,6 +176,13 @@ const keyMapping = {
name: 'receiver_name',
group_num: 'group_num',
},
search_by_member_condition: {
avatar: 'avatar',
name: 'nickname',
created_at: 'created_at',
msg_type: 'msg_type',
detailKey: 'chatMessageType',
},
}
//key
const getKeyValue = (keys) => {
@ -167,14 +195,8 @@ const getKeyValue = (keys) => {
//
const avatarImg = computed(() => {
let avatar = getKeyValue(keyMapping[props.searchResultKey]?.avatar)
if (!avatar) {
avatar = groupTypeMapping[props.searchItem?.group_type]?.defaultImg
}
if (props?.conditionType) {
avatar = props.searchItem.avatar
if (!avatar) {
avatar = groupTypeMapping[0]?.defaultImg
}
}
return avatar
})
@ -194,26 +216,19 @@ const imgText = computed(() => {
})
// -groupType
const groupTypeMapping = {
0: {
defaultImg: 'textImg',
},
1: {
defaultImg: zu4992,
},
0: {},
1: {},
2: {
result_type: t('index.mine.department'),
result_type_color: '#377EC6',
defaultImg: zu4989,
},
3: {
result_type: t('index.mine.project'),
result_type_color: '#C1681C',
defaultImg: zu4991,
},
4: {
result_type: t('index.type.company'),
result_type_color: '#7A58DE',
defaultImg: zu5296,
},
}
//
@ -247,6 +262,12 @@ const resultDetail = computed(() => {
case 'extra':
result_detail = props.searchItem?.extra
break
case 'chatMessageType':
result_detail =
props.searchItem?.msg_type === 1
? props.searchItem?.extra?.content
: ChatMsgTypeMapping[props.searchItem?.msg_type]
break
default:
result_detail = ''
}
@ -262,35 +283,6 @@ const resultDetail = computed(() => {
padding: 22rpx 0 24rpx;
border-bottom: 1px solid $theme-border-color;
.avatar-img {
width: 96rpx;
height: 96rpx;
margin: 0 20rpx 0 0;
border-radius: 50%;
overflow: hidden;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background: linear-gradient(to right, #674bbc, #46299d);
flex-shrink: 0;
img {
width: 100%;
height: 100%;
}
span {
color: #fff;
line-height: 44rpx;
}
}
.avatar-img-condition {
width: 64rpx;
height: 64rpx;
margin: 0 18rpx 0 0;
span {
font-size: 20rpx;
}
}
.result-info {
width: 100%;
.info-name {
@ -310,6 +302,7 @@ const resultDetail = computed(() => {
padding: 2rpx 14rpx;
border: 2rpx solid #000;
border-radius: 6rpx;
flex-shrink: 0;
span {
line-height: 34rpx;
}
@ -334,6 +327,7 @@ const resultDetail = computed(() => {
.info-detail-searchRecordDetail {
span {
color: $theme-text;
word-break: break-all;
}
}
}

View File

@ -9,7 +9,7 @@
:default-page-size="props.searchResultPageSize"
:loading-more-default-as-loading="true"
:inside-more="true"
:empty-view-img="'/src/static//image/search/search-no-data.png'"
:empty-view-img="searchNoData"
:empty-view-text="$t('search.hint')"
:empty-view-img-style="{ width: '476rpx', height: '261rpx' }"
:empty-view-title-style="{
@ -19,6 +19,7 @@
'font-size': '28rpx',
'font-weight': 400,
}"
:refresher-enabled="false"
>
<template #top>
<div class="searchRoot">
@ -35,7 +36,10 @@
</span>
</div>
</template>
<div class="search-record-detail" v-if="props.searchRecordDetail">
<div
class="search-record-detail"
v-if="props.searchRecordDetail && !props?.hideFirstRecord"
>
<searchItem
@click="
clickSearchItem(
@ -109,6 +113,7 @@
</div>
</template>
<script setup>
import searchNoData from '@/static/image/search/search-no-data.png'
import customInput from '@/components/custom-input/custom-input.vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
@ -117,6 +122,10 @@ import { useI18n } from 'vue-i18n'
import { ref, reactive, defineEmits, defineProps, onMounted } from 'vue'
import pointerIconSrc from '@/static/image/search/search-item-pointer.png'
import lodash from 'lodash'
import { useUserStore } from '@/store'
const userStore = useUserStore()
const zPaging = ref()
useZPaging(zPaging)
@ -131,17 +140,45 @@ const state = reactive({
searchResultList: [], //
searchResult: null, //
pageNum: 1, //
uid: computed(() => userStore.uid), //id
})
const props = defineProps({
searchResultPageSize: Number, //
listLimit: Boolean, //
apiParams: String, //
searchResultPageSize: {
type: Number,
default: 0,
}, //
listLimit: {
type: Boolean,
default: false,
}, //
apiParams: {
type: String,
default: '',
}, //
apiRequest: Function, //
searchText: String, //
isPagination: Boolean, //
searchRecordDetail: Boolean, //
first_talk_record_infos: Object, //
searchText: {
type: String,
default: '',
}, //
isPagination: {
type: Boolean,
default: false,
}, //
searchRecordDetail: {
type: Boolean,
default: false,
}, //
first_talk_record_infos: {
type: Object,
default() {
return {}
},
}, //
hideFirstRecord: {
type: Boolean,
default: false,
}, ///
})
const { t } = useI18n()
@ -154,13 +191,14 @@ onMounted(() => {
//
const inputSearchText = (e) => {
// console.log(e)
if (e.trim() != state.searchText.trim()) {
state.pageNum = 1
state.searchResult = null //
emits('lastIdChange', 0, 0, 0)
}
state.searchText = e.trim()
if (!e.trim()) {
state.searchResult = null //
emits('lastIdChange', 0, 0, 0)
}
zPaging.value?.reload()
@ -197,15 +235,35 @@ const queryAllSearch = (pageNum, searchResultPageSize) => {
})
}
if ((data.talk_record_infos || []).length > 0) {
let receiverInfo = lodash.cloneDeep(data.talk_record_infos[0])
if (receiverInfo.talk_type === 1) {
//
if (receiverInfo.user_id === state.uid) {
//
}
if (receiverInfo.receiver_id === state.uid) {
//
let temp_id = receiverInfo.receiver_id
let temp_name = receiverInfo.receiver_name
let temp_avatar = receiverInfo.receiver_avatar
receiverInfo.receiver_id = receiverInfo.user_id
receiverInfo.receiver_name = receiverInfo.user_name
receiverInfo.receiver_avatar = receiverInfo.user_avatar
receiverInfo.user_id = temp_id
receiverInfo.user_name = temp_name
receiverInfo.user_avatar = temp_avatar
}
}
state.first_talk_record_infos = Object.assign(
{},
state.first_talk_record_infos,
data.talk_record_infos[0],
receiverInfo,
)
;(data.talk_record_infos || []).forEach((item) => {
item.group_type = 0
})
}
let tempGeneral_infos = Array.isArray(data.general_infos)
? [...data.general_infos]
: data.general_infos
@ -214,6 +272,8 @@ const queryAllSearch = (pageNum, searchResultPageSize) => {
data.group_member_infos || [],
)
data.general_infos = tempGeneral_infos
//
let isEmpty = true
let dataKeys = Object.keys(data)
let paginationKey = ''
@ -223,58 +283,94 @@ const queryAllSearch = (pageNum, searchResultPageSize) => {
isEmpty = false
}
})
if (isEmpty) {
if (pageNum == 1) {
if (pageNum === 1) {
//
state.searchResult = null
zPaging.value?.complete([])
} else {
data = state.searchResult
zPaging.value?.complete([data])
//
zPaging.value?.complete(state.searchResult ? [state.searchResult] : [])
}
} else {
if (props.isPagination) {
if (
paginationKey &&
Array.isArray(
(state?.searchResult && state?.searchResult[paginationKey]) || [],
) &&
((state?.searchResult && state?.searchResult[paginationKey]) || [])
.length > 0
) {
data[paginationKey] = state.searchResult[paginationKey].concat(
data[paginationKey],
)
if (pageNum === 1) {
//
state.searchResult = data
} else {
//
if (
paginationKey &&
Array.isArray(
(state?.searchResult && state?.searchResult[paginationKey]) || [],
)
) {
data[paginationKey] = state.searchResult[paginationKey].concat(
data[paginationKey],
)
}
state.searchResult = data
}
emits(
'lastIdChange',
data.last_id,
data.last_group_id,
data.last_member_id,
data.last_receiver_user_name,
data.last_receiver_group_name,
)
let total = data.count
if (props.searchRecordDetail) {
total = data.group_record_count
if (state?.first_talk_record_infos?.talk_type === 1) {
total = data.user_record_count
} else if (state?.first_talk_record_infos?.talk_type === 2) {
total = data.group_record_count
}
}
zPaging.value?.completeByTotal([data], total)
} else {
state.searchResult = data
zPaging.value?.complete([data])
}
}
state.searchResult = data
} else {
zPaging.value?.complete([])
if (pageNum === 1) {
//
state.searchResult = null
zPaging.value?.complete([])
} else {
//
zPaging.value?.complete(state.searchResult ? [state.searchResult] : [])
}
}
})
resp.catch(() => {
zPaging.value?.complete([])
if (pageNum === 1) {
//
state.searchResult = null
zPaging.value?.complete([])
} else {
//
zPaging.value?.complete(state.searchResult ? [state.searchResult] : [])
}
})
}
//
const cancelSearch = () => {
uni.navigateBack({
delta: 1,
})
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack({
delta: 1,
})
} else {
uni.reLaunch({
url: '/pages/index/index'
})
}
}
//key
@ -342,6 +438,12 @@ const getHasMoreResult = (searchResultKey) => {
}
break
case 'general_infos':
if (
state.searchResult['record_count'] &&
state.searchResult['record_count'] >= 3
) {
has_more_result = t('has_more') + t('chat.type.record')
}
break
default:
}
@ -355,12 +457,41 @@ const toMoreResultPage = (searchResultKey) => {
//
const clickSearchItem = (searchResultKey, searchItem) => {
console.log(searchResultKey, searchItem)
let talk_type = searchItem.talk_type
let receiver_id = searchItem.receiver_id
if (searchResultKey === 'user_infos') {
talk_type = 1
receiver_id = searchItem.id
} else if (searchResultKey === 'combinedGroup') {
talk_type = searchItem.type || 2
receiver_id = searchItem.group_id || searchItem.id
} else if (searchResultKey === 'general_infos') {
if (searchItem.talk_type === 1) {
if (searchItem.user_id === state.uid) {
//
}
if (searchItem.receiver_id === state.uid) {
//
let temp_id = searchItem.receiver_id
let temp_name = searchItem.receiver_name
let temp_avatar = searchItem.receiver_avatar
searchItem.receiver_id = searchItem.user_id
searchItem.receiver_name = searchItem.user_name
searchItem.receiver_avatar = searchItem.user_avatar
searchItem.user_id = temp_id
searchItem.user_name = temp_name
searchItem.user_avatar = temp_avatar
}
}
}
emits(
'clickSearchItem',
state.searchText,
searchResultKey,
searchItem.talk_type,
searchItem.receiver_id,
talk_type,
receiver_id,
encodeURIComponent(JSON.stringify(searchItem)),
)
}
</script>

View File

@ -13,7 +13,19 @@
</template>
<script setup>
import searchList from './components/searchList.vue'
import { ServeSeachQueryAll } from '@/api/search/index'
import { ServeSeachQueryAll, ServeGetSessionId } from '@/api/search/index'
import { onMounted } from 'vue'
import { handleSetWebviewStyle } from '@/utils/common'
import { useDialogueStore, useTalkStore } from '@/store'
import { ServeCreateTalkList } from '@/api/chat/index.js'
import { formatTalkItem } from '@/utils/talk'
const dialogueStore = useDialogueStore()
onMounted(() => {
handleSetWebviewStyle()
})
//
const toMoreResultPage = (searchResultKey, searchText) => {
@ -27,14 +39,49 @@ const toMoreResultPage = (searchResultKey, searchText) => {
}
//
const clickSearchItem = (
const clickSearchItem = async (
searchText,
searchResultKey,
talk_type,
receiver_id,
res,
) => {
console.log(searchResultKey)
if (searchResultKey === 'general_infos') {
const result = JSON.parse(decodeURIComponent(res))
console.log(result)
console.log(talk_type, receiver_id)
const sessionId = await getSessionId(talk_type, receiver_id)
if (searchResultKey === 'user_infos') {
if (useTalkStore().findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
ServeCreateTalkList({
talk_type,
receiver_id,
erp_user_id: result.erp_user_id,
}).then(async ({ code, data }) => {
if (code == 200) {
let item = formatTalkItem(data)
useTalkStore().addItem(item)
}
})
}
dialogueStore.setDialogue({
name: result.nickname,
talk_type: 1,
receiver_id: receiver_id,
})
uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + sessionId,
})
} else if (searchResultKey === 'combinedGroup') {
dialogueStore.setDialogue({
name: result.name || result.group_name,
talk_type: result.type || 2,
receiver_id: result.group_id || result.id,
})
uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + sessionId,
})
} else if (searchResultKey === 'general_infos') {
uni.navigateTo({
url:
'/pages/search/moreResult/moreResultDetail?searchText=' +
@ -46,5 +93,25 @@ const clickSearchItem = (
})
}
}
//Id
const getSessionId = (talk_type, receiver_id) => {
return new Promise((resolve, reject) => {
let params = {
talkType: talk_type,
receiverId: receiver_id,
}
const resp = ServeGetSessionId(params)
console.log(resp)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
resolve(data?.sessionId)
} else {
}
})
resp.catch(() => {})
})
}
</script>
<style scoped lang="scss"></style>

View File

@ -21,14 +21,21 @@ import {
ServeQueryUser,
ServeQueryGroup,
ServeTalkRecord,
ServeGetSessionId,
} from '@/api/search/index'
import { reactive } from 'vue'
import { useDialogueStore, useTalkStore } from '@/store'
import { ServeCreateTalkList } from '@/api/chat/index.js'
import { formatTalkItem } from '@/utils/talk'
const dialogueStore = useDialogueStore()
const state = reactive({
apiRequest: Function,
apiParams: String,
searchText: String,
searchResultKey: String,
apiParams: '',
searchText: '',
searchResultKey: '',
})
onLoad((options) => {
@ -57,6 +64,8 @@ onLoad((options) => {
receiver_id: 0, //
last_group_id: 0, //id
last_member_id: 0, //id
last_receiver_user_name: '', //
last_receiver_group_name: '', //
}),
)
state.apiRequest = ServeTalkRecord
@ -68,11 +77,19 @@ onLoad((options) => {
})
//id
const lastIdChange = (last_id, last_group_id, last_member_id) => {
const lastIdChange = (
last_id,
last_group_id,
last_member_id,
last_receiver_user_name,
last_receiver_group_name,
) => {
let idChanges = {
last_id,
last_group_id,
last_member_id,
last_receiver_user_name,
last_receiver_group_name,
}
state.apiParams = encodeURIComponent(
JSON.stringify(
@ -86,12 +103,79 @@ const lastIdChange = (last_id, last_group_id, last_member_id) => {
}
//
const clickSearchItem = (searchText) => {
if (state.searchResultKey === 'general_infos') {
const clickSearchItem = async (
searchText,
searchResultKey,
talk_type,
receiver_id,
res,
) => {
console.log(state.searchResultKey)
const result = JSON.parse(decodeURIComponent(res))
console.log(result)
console.log(talk_type, receiver_id)
const sessionId = await getSessionId(talk_type, receiver_id)
if (state.searchResultKey === 'user_infos') {
if (useTalkStore().findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
ServeCreateTalkList({
talk_type,
receiver_id,
erp_user_id: result.erp_user_id,
}).then(async ({ code, data }) => {
if (code == 200) {
let item = formatTalkItem(data)
useTalkStore().addItem(item)
}
})
}
dialogueStore.setDialogue({
name: result.nickname,
talk_type: 1,
receiver_id: receiver_id,
})
uni.navigateTo({
url: '/pages/search/moreResult/moreResultDetail?searchText=' + searchText,
url: '/pages/dialog/index?sessionId=' + sessionId,
})
} else if (state.searchResultKey === 'combinedGroup') {
dialogueStore.setDialogue({
name: result.name || result.group_name,
talk_type: result.type || 2,
receiver_id: result.group_id || result.id,
})
uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + sessionId,
})
} else if (state.searchResultKey === 'general_infos') {
uni.navigateTo({
url:
'/pages/search/moreResult/moreResultDetail?searchText=' +
searchText +
'&talk_type=' +
talk_type +
'&receiver_id=' +
receiver_id,
})
}
}
//Id
const getSessionId = (talk_type, receiver_id) => {
return new Promise((resolve, reject) => {
let params = {
talkType: talk_type,
receiverId: receiver_id,
}
const resp = ServeGetSessionId(params)
console.log(resp)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
resolve(data?.sessionId)
} else {
}
})
resp.catch(() => {})
})
}
</script>
<style scoped lang="scss"></style>

View File

@ -7,9 +7,11 @@
:apiRequest="ServeTalkRecord"
:apiParams="state.apiParams"
:searchText="state.searchText"
:hideFirstRecord="state.hideFirstRecord"
:isPagination="true"
:searchRecordDetail="true"
@lastIdChange="lastIdChange"
@clickSearchItem="clickSearchItem"
></searchList>
</div>
</div>
@ -19,10 +21,17 @@ import searchList from '../components/searchList.vue'
import { onLoad } from '@dcloudio/uni-app'
import { ServeTalkRecord } from '@/api/search/index'
import { reactive } from 'vue'
import { useDialogueStore, useUserStore } from '@/store'
import lodash from 'lodash'
const dialogueStore = useDialogueStore()
const userStore = useUserStore()
const state = reactive({
apiParams: String,
searchText: String,
apiParams: '',
searchText: '',
uid: computed(() => userStore.uid), //id
hideFirstRecord: false, ///
})
onLoad((options) => {
@ -48,6 +57,10 @@ onLoad((options) => {
}
console.log(JSON.parse(decodeURIComponent(state.apiParams)))
if (options.hideFirstRecord) {
state.hideFirstRecord = options.hideFirstRecord === '1' ? true : false
}
})
//id
@ -67,5 +80,51 @@ const lastIdChange = (last_id, last_group_id, last_member_id) => {
),
)
}
//
const clickSearchItem = (
searchText,
searchResultKey,
talk_type,
receiver_id,
res,
) => {
console.log(searchResultKey)
let result = JSON.parse(decodeURIComponent(res))
console.log(result)
let receiverInfo = lodash.cloneDeep(result)
if (receiverInfo.talk_type === 1) {
//
if (receiverInfo.user_id === state.uid) {
//
}
if (receiverInfo.receiver_id === state.uid) {
//
let temp_id = receiverInfo.receiver_id
let temp_name = receiverInfo.receiver_name
let temp_avatar = receiverInfo.receiver_avatar
receiverInfo.receiver_id = receiverInfo.user_id
receiverInfo.receiver_name = receiverInfo.user_name
receiverInfo.receiver_avatar = receiverInfo.user_avatar
receiverInfo.user_id = temp_id
receiverInfo.user_name = temp_name
receiverInfo.user_avatar = temp_avatar
}
}
dialogueStore.setDialogue({
name: receiverInfo.receiver_name,
talk_type: talk_type,
receiver_id: receiverInfo.receiver_id,
})
if (searchResultKey === 'talk_record_infos_receiver') {
uni.navigateTo({
url: '/pages/dialog/index',
})
} else {
uni.navigateTo({
url: '/pages/dialog/index?msgInfo=' + res + '&keepDialogInfo=1',
})
}
}
</script>
<style scoped lang="scss"></style>

View File

@ -9,15 +9,10 @@
:auto="false"
:loading-more-default-as-loading="true"
:inside-more="true"
v-model="state.flatList"
>
<template #top v-if="state.showPageTitle">
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
<div class="navBar-title flex flex-col items-center justify-center">
<span class="text-[34rpx] font-medium">
{{ state.pageTitle }}
</span>
</div>
</tm-navbar>
<customNavbar :title="state.pageTitle"></customNavbar>
</template>
<div v-if="state.condition === 'date'" class="search-by-date">
<tm-time-picker
@ -47,7 +42,7 @@
<span class="text-[28rpx] font-regular">
{{ state.selectedMonth }}
</span>
<img src="/src/static/image/search/down-pointer.png" />
<img src="@/static/image/search/down-pointer.png" />
</div>
</tm-time-picker>
<tm-calendar-view
@ -70,7 +65,8 @@
v-if="
state.condition === 'imgAndVideo' ||
state.condition === 'file' ||
state.condition === 'link'
state.condition === 'link' ||
state.condition === 'member'
"
:style="{
padding: state.condition === 'imgAndVideo' ? '0 27rpx' : '',
@ -118,11 +114,27 @@
v-for="(item, index) in conditionItem.monthResultList"
:key="index"
:style="{
border: state.condition === 'imgAndVideo' ? '0' : '',
border:
state.condition === 'imgAndVideo' ||
state.condition === 'member'
? '0'
: '',
padding:
state.condition === 'imgAndVideo' ? '0 0 10rpx' : '',
}"
>
<div
class="condition-result-member"
v-if="state.condition === 'member'"
>
<searchItem
@click="toDialogueByMember(item)"
:searchResultKey="'search_by_member_condition'"
:searchItem="item"
:searchText="state.searchText"
:searchRecordDetail="true"
></searchItem>
</div>
<div
class="condition-result-imgAndVideo"
v-if="state.condition === 'imgAndVideo'"
@ -148,17 +160,27 @@
class="condition-result-imgAndVideo-area"
v-if="item?.extra?.url"
>
<tm-image
preview
:src="
item?.msg_type === 3
? item?.extra?.url
: item?.msg_type === 5
? item?.extra?.cover
: ''
"
model="aspectFill"
/>
<template v-if="item?.msg_type === 3">
<tm-image
preview
:src="item?.extra?.url"
model="aspectFill"
/>
</template>
<template v-else-if="item?.msg_type === 5">
<div
class="video-preview"
@click="onPlay(item?.extra?.url)"
>
<tm-image
:src="item?.extra?.cover"
model="aspectFill"
/>
<div class="play-icon">
<img :src="playCircle" />
</div>
</div>
</template>
</div>
</div>
<div
@ -179,6 +201,7 @@
</div>
<div
class="condition-each-result-attachments"
@click="previewPDF(item)"
v-if="
state.condition === 'file' || state.condition === 'link'
"
@ -189,7 +212,7 @@
v-if="state.condition === 'file'"
/>
<img
src="/src/static/image/search/result-link-icon.png"
src="@/static/image/search/result-link-icon.png"
v-if="state.condition === 'link'"
/>
</div>
@ -244,6 +267,20 @@
</div>
</div>
</ZPaging>
<teleport to="body">
<div v-show="open" class="video-container">
<video
:src="currentVideoUrl"
controls
@fullscreenchange="fullscreenchange"
:id="currentVideoUrl"
playsinline
webkit-playsinline
x5-playsinline
class="fullscreen-video"
></video>
</div>
</teleport>
</div>
</div>
</template>
@ -253,15 +290,16 @@ import fileType_EXCEL from '@/static/image/search/fileType_EXCEL.png'
import fileType_WORD from '@/static/image/search/fileType_WORD.png'
import fileType_PDF from '@/static/image/search/fileType_PDF.png'
import fileType_Files from '@/static/image/search/fileType_Files.png'
import playCircle from '@/static/image/chatList/playCircle@2x.png'
import { fileFormatSize, fileSuffix } from '@/utils/strings'
import searchItem from '../components/searchItem.vue'
import customInput from '@/components/custom-input/custom-input.vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
import { parseTime } from '@/utils/datetime'
import { onMounted, reactive, computed, ref } from 'vue'
import { onMounted, reactive, computed, ref, nextTick } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { ServeTalkDate } from '@/api/search/index'
import { ServeTalkDate, ServeGetSessionId } from '@/api/search/index'
import { ServeFindTalkRecords } from '@/api/chat/index'
import { useDialogueStore } from '@/store'
import { useI18n } from 'vue-i18n'
@ -279,7 +317,6 @@ const dialogueParams = reactive({
let nowDay = new Date().setHours(0, 0, 0, 0)
const state = reactive({
receiver_id: '', //id
pageTitle: '', //
dateStyle: [], //
nowDate: new Date(nowDay), //
@ -295,16 +332,53 @@ const state = reactive({
searchResultList: [], //
cursor: 0, //
msg_type: 0, //
group_member_id: 0, //id
flatList: [], //
})
const videoContext = ref()
const open = ref(false)
const currentVideoUrl = ref('')
const fullscreenchange = (e) => {
if (!e.detail.fullScreen) {
videoContext.value.stop()
videoContext.value.seek(0)
open.value = false
}
}
async function onPlay(url) {
currentVideoUrl.value = url
open.value = true
// DOM
await nextTick()
//
videoContext.value = uni.createVideoContext(url, getCurrentInstance())
setTimeout(() => {
//
videoContext.value.requestFullScreen({ direction: 2 })
//
setTimeout(() => {
videoContext.value.play()
}, 100)
}, 200)
}
onLoad((options) => {
console.log(options)
if (options.receiver_id) {
state.receiver_id = Number(options.receiver_id)
}
if (options.condition) {
state.condition = options.condition
if (options.condition === 'date') {
if (options.condition === 'member') {
state.showPageTitle = true
state.pageTitle = t('search.condition.member')
state.group_member_id = options.groupMemberId
queryAllSearch()
} else if (options.condition === 'date') {
state.showPageTitle = true
state.pageTitle = t('search.condition.date')
ServeQueryTalkDate(parseTime(state.nowDate, '{y}{m}'))
@ -357,8 +431,8 @@ onMounted(() => {
const ServeQueryTalkDate = (month) => {
let params = {
month: month,
talk_type: 2, //12
receiver_id: state.receiver_id, //id
talk_type: dialogueParams.talk_type, //12
receiver_id: dialogueParams.receiver_id, //id
}
const resp = ServeTalkDate(params)
console.log(resp)
@ -393,7 +467,7 @@ const ServeQueryTalkDate = (month) => {
}
//
const selectDate = (e) => {
const selectDate = async (e) => {
if (e == parseTime(state.nowDate, '{y}/{m}/{d}')) {
console.log('==今日')
state.dateStyle = [
@ -419,6 +493,38 @@ const selectDate = (e) => {
},
]
}
const sessionId = await getSessionId(
dialogueParams.talk_type,
dialogueParams.receiver_id,
)
uni.navigateTo({
url:
'/pages/dialog/index?sessionId=' +
sessionId +
'&keepDialogInfo=1' +
'&recordDate=' +
parseTime(e, '{y}-{m}-{d}'),
})
}
//Id
const getSessionId = (talk_type, receiver_id) => {
return new Promise((resolve, reject) => {
let params = {
talkType: talk_type,
receiverId: receiver_id,
}
const resp = ServeGetSessionId(params)
console.log(resp)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
resolve(data?.sessionId)
} else {
}
})
resp.catch(() => {})
})
}
//
@ -449,10 +555,23 @@ const getDArray = (dArray) => {
//
const inputSearchText = (e) => {
state.searchText = e
state.cursor = 0
queryAllSearch()
}
//
const cancelSearch = () => {}
const cancelSearch = () => {
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack({
delta: 1,
})
} else {
uni.reLaunch({
url: '/pages/index/index',
})
}
}
//
const queryAllSearch = () => {
@ -466,7 +585,8 @@ const queryAllSearch = () => {
direction: 'up', //downup
start_time: '',
end_time: '',
group_member_user_id: 0, //id
group_member_user_id: state.group_member_id, //id
file_name: state.msg_type === 6 ? state.searchText : '',
}
console.log(params)
const resp = ServeFindTalkRecords(params)
@ -474,17 +594,20 @@ const queryAllSearch = () => {
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
let dateList = state.searchResultList
// cursor0searchResultList
let dateList = state.cursor === 0 ? [] : state.searchResultList
let noMore = false
if (data?.items?.length > 0) {
data.items.forEach((item) => {
item.dateTime = parseTime(item?.created_at, '{m}/{d}')
item.extra.fileSize = fileFormatSize(item?.extra?.size)
item.extra.typeText = item?.extra?.name
? fileSuffix(item?.extra?.name)
: ''
item.extra.file_avatar = fileTypeAvatar(item?.extra?.typeText)
console.log(item.extra.type)
if (item?.extra) {
item.extra.fileSize = fileFormatSize(item?.extra?.size)
item.extra.typeText = item?.extra?.name
? fileSuffix(item?.extra?.name)
: ''
item.extra.file_avatar = fileTypeAvatar(item?.extra?.typeText)
console.log(item.extra.type)
}
let year = new Date(item.created_at).getFullYear()
let month = new Date(item.created_at).getMonth() + 1
let dateMonth =
@ -517,15 +640,35 @@ const queryAllSearch = () => {
} else {
noMore = true
}
console.log(dateList)
//
state.searchResultList = dateList
// z-paging
state.flatList = dateList.reduce((acc, group) => {
return acc.concat(group.monthResultList)
}, [])
if (state.cursor === 0) {
zPaging.value?.complete(state.flatList)
} else {
zPaging.value?.completeByNoMore(state.flatList, noMore)
}
state.cursor = data?.cursor
zPaging.value?.completeByNoMore(dateList, noMore)
} else {
if (state.cursor === 0) {
state.searchResultList = []
state.flatList = []
}
zPaging.value?.complete([])
}
})
resp.catch(() => {
if (state.cursor === 0) {
state.searchResultList = []
state.flatList = []
}
zPaging.value?.complete([])
})
}
@ -548,6 +691,59 @@ const fileTypeAvatar = (fileType) => {
}
return file_type_avatar
}
const previewPDF = (item) => {
console.log(item)
if (typeof plus !== 'undefined') {
downloadAndOpenFile(item)
} else {
document.addEventListener('plusready', () => {
downloadAndOpenFile(item)
})
}
}
const downloadAndOpenFile = (item) => {
uni.showLoading({ title: '加载中...', mask: true })
const downloadUrl = item?.extra?.path
const options = {
filename: '_doc/downloads/', //
}
const dtask = plus.downloader.createDownload(downloadUrl, options, function (
d,
status,
) {
if (status === 200) {
uni.hideLoading()
const filePath = d.filename
plus.runtime.openFile(
filePath,
{},
function () {},
function (error) {},
)
} else {
uni.hideLoading()
}
})
dtask.start()
}
//
const toDialogueByMember = async (msgInfo) => {
const sessionId = await getSessionId(
dialogueParams.talk_type,
dialogueParams.receiver_id,
)
uni.navigateTo({
url:
'/pages/dialog/index?sessionId=' +
sessionId +
'&keepDialogInfo=1' +
'&msgInfo=' +
encodeURIComponent(JSON.stringify(msgInfo)),
})
}
</script>
<style scoped lang="scss">
.search-by-date {
@ -654,6 +850,7 @@ body::v-deep .round-3 {
span {
line-height: 40rpx;
color: $theme-text;
word-break: break-all;
}
}
.attachment-sub-info {
@ -700,6 +897,26 @@ body::v-deep .round-3 {
width: 164rpx !important;
height: 164rpx !important;
}
.video-preview {
position: relative;
width: 164rpx;
height: 164rpx;
cursor: pointer;
.play-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
align-items: center;
justify-content: center;
img {
width: 80rpx !important;
height: 80rpx !important;
}
}
}
}
}
}
@ -708,4 +925,23 @@ body::v-deep .round-3 {
}
}
}
.video-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: #000;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.fullscreen-video {
width: 100%;
height: 100%;
object-fit: contain;
}
</style>

View File

@ -23,7 +23,7 @@ class WsSocket {
lockReconnect: false,
setTimeout: null, // 计时器对象
time: 3000, // 重连间隔时间
number: 10000000 // 重连次数
number: 20 // 重连次数
}
}

View File

@ -1,5 +1,21 @@
import Request from '@/service/request/index.js'
import {useAuth} from "@/store/auth";
import { createApp } from 'vue';
import XMessage from '@/components/x-message/index.vue';
// 创建消息提示实例
const messageInstance = (() => {
const messageNode = document.createElement('div');
document.body.appendChild(messageNode);
const app = createApp(XMessage);
const instance = app.mount(messageNode);
return {
warning: (msg) => instance.showMessage({ type: 'warning', message: msg }),
error: (msg) => instance.showMessage({ type: 'error', message: msg }),
success: (msg) => instance.showMessage({ type: 'success', message: msg }),
info: (msg) => instance.showMessage({ type: 'info', message: msg })
};
})();
const { token ,refreshToken,userInfo}=useAuth()
let isRefreshing = false;
let refreshSubscribers = [];
@ -24,19 +40,25 @@ const request = new Request({
},
responseInterceptors: async (res) => {
if(res.data.status===1){
message.warning(res.data.msg)
// message.warning(res.data.msg)
messageInstance.warning(res.data.msg)
}
if (res.data.status === 401) {
return getRefreshToken(res);
return
// return getRefreshToken(res);
// uni.navigateTo({
// url:'/pages/login/index'
// })
}
if ([200, 201, 204].includes(res.status)) {
if(res.data.code !== 200 && res.data.code!== 0) {
messageInstance.error(res.data.message || res.data.msg || 'An error occurred.');
}
return res.config.responseType === 'blob' ? res : res;
} else {
/* message.error(res.data.msg || 'An error occurred.');*/
return Promise.reject(new Error(res.data.msg || 'An error occurred.'));
messageInstance.error(res.data.message || res.data.msg || 'An error occurred.');
return Promise.reject(new Error(res.data.message || res.data.msg || 'An error occurred.'));
}
}
}
@ -59,13 +81,14 @@ async function getRefreshToken(response) {
})
return request.request(response.config);
} else {
message.error(res.message || res.msg);
messageInstance.error(res.message || res.msg);
throw new Error(res.message || res.msg);
}
} catch (error) {
uni.navigateTo({
url:'/pages/login/index'
})
// uni.navigateTo({
// url:'/pages/login/index'
// })
throw error
} finally {
isRefreshing = false;
@ -73,9 +96,9 @@ async function getRefreshToken(response) {
refreshSubscribers = [];
}
} else {
uni.navigateTo({
url:'/pages/login/index'
})
// uni.navigateTo({
// url:'/pages/login/index'
// })
throw new Error('No refresh token available.');
}
} else {

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

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