Merge remote-tracking branch 'origin/dev'

# Conflicts:
#	pnpm-lock.yaml
This commit is contained in:
nongyehong 2024-04-24 12:08:10 +08:00
commit 9a35547997
83 changed files with 5710 additions and 4577 deletions

View File

@ -35,7 +35,7 @@ module.exports = {
plugins: ['@typescript-eslint', 'vue'],
rules: {
'@typescript-eslint/no-non-null-assertion': 'off',
/* 允许ts使用命名空间 */
/** 允许ts使用命名空间 */
'@typescript-eslint/no-namespace': 'off',
/**
* 使用新vue3.3的defineProps解构语法需要关闭这个校验

4
.husky/commit-msg Normal file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit "$1"

4
.husky/pre-commit Normal file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm run lint:staged

View File

@ -1,85 +1,109 @@
## 1.5.0 (2024-04-21)
* :bug: fix(custom): 修复使用n-virtual-list组件未阻止滚动问题 ([cd9acf9](https://github.com/nongyehong/HuLa-IM-Tauri/commit/cd9acf9))
* :bug: fix(setting): 修复独立窗口无法接收响应式数据 ([c4729d4](https://github.com/nongyehong/HuLa-IM-Tauri/commit/c4729d4))
* :bug: fix(system): 修复艾特框滚动和选中问题 ([76d3c9f](https://github.com/nongyehong/HuLa-IM-Tauri/commit/76d3c9f))
* :bug: fix(system): 修复回复功能的一些问题 ([e1eb827](https://github.com/nongyehong/HuLa-IM-Tauri/commit/e1eb827))
* :bug: fix(system): 修复系统托盘图标切换问题 ([998ab14](https://github.com/nongyehong/HuLa-IM-Tauri/commit/998ab14))
* :bug: fix(system): 修复已知问题 ([0b7878e](https://github.com/nongyehong/HuLa-IM-Tauri/commit/0b7878e))
* :pencil2: docs(style): 变更提交文本内容 ([7ae0c4b](https://github.com/nongyehong/HuLa-IM-Tauri/commit/7ae0c4b))
* :pencil2: docs(style): 更新md文档项目展示内容 ([afa4675](https://github.com/nongyehong/HuLa-IM-Tauri/commit/afa4675))
* :sparkles: feat(custom): 新增表情功能 ([baae17e](https://github.com/nongyehong/HuLa-IM-Tauri/commit/baae17e))
* :sparkles: feat(setting): 新增发送信息快捷键 ([eba6395](https://github.com/nongyehong/HuLa-IM-Tauri/commit/eba6395))
* :sparkles: feat(style): 新增系统托盘无状态图标切换 ([0289f35](https://github.com/nongyehong/HuLa-IM-Tauri/commit/0289f35))
* :sparkles: feat(system): 初步实现回复功能 ([e8164e0](https://github.com/nongyehong/HuLa-IM-Tauri/commit/e8164e0))
* :tada: release(custom): 发布v1.5.0版本 ([93770c1](https://github.com/nongyehong/HuLa-IM-Tauri/commit/93770c1))
* :zap: perf(setting): 优化系统托盘图标在不同窗口下显示不同功能 ([b10d5c0](https://github.com/nongyehong/HuLa-IM-Tauri/commit/b10d5c0))
* :zap: perf(style): 提取复用hooks ([8aeaf76](https://github.com/nongyehong/HuLa-IM-Tauri/commit/8aeaf76))
* :zap: perf(system): 优化艾特框功能 ([62d86fa](https://github.com/nongyehong/HuLa-IM-Tauri/commit/62d86fa))
* :zap: perf(system): 优化回复功能的实现 ([c5109d7](https://github.com/nongyehong/HuLa-IM-Tauri/commit/c5109d7))
* :zap: perf(system): 优化聊天框中常用功能 ([42d2453](https://github.com/nongyehong/HuLa-IM-Tauri/commit/42d2453))
* :zap: perf(system): 优化输入框功能 ([844c251](https://github.com/nongyehong/HuLa-IM-Tauri/commit/844c251))
## 1.4.0 (2024-04-01)
* :bug: fix(system): 使用pinia-shared-state来解决标签页之间store的共享问题 ([54cc2a6](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/54cc2a6))
* :bug: fix(system): 修复已知的问题 ([4b557e2](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/4b557e2))
* :medal: version(system): . add LICENSE. ([a3f4318](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/a3f4318))
* :medal: version(system): 发布1.4.0版本 ([665237d](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/665237d))
* :sparkles: feat(custom): 新增自动登录功能 ([c2fb6d7](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/c2fb6d7))
* :sparkles: feat(setting): 新增关闭窗口提示 ([183a2e7](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/183a2e7))
* :sparkles: feat(style): 新增个人信息框 ([9fbb6f3](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/9fbb6f3))
* :sparkles: feat(style): 新增用户信息框跟随图片背景颜色功能 ([2feb9c0](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/2feb9c0))
* :sparkles: feat(system): 新增群聊侧边栏 ([4f81f50](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/4f81f50))
* :sparkles: feat(system): 新增消息独立窗口功能 ([60939bf](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/60939bf))
* :sparkles: feat(system): 新增在线状态 ([ed7be38](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/ed7be38))
* :sparkles: feat(system): 新增ait功能 ([9a94c17](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/9a94c17))
* :zap: perf(setting): 使用命名空间简化ts类型导入 ([bd96330](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/bd96330))
* :zap: perf(setting): 优化配置 ([d139a36](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/d139a36))
* :zap: perf(style): 完善主题的配置,优化信息列表功能 ([70fb55b](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/70fb55b))
* :zap: perf(system): 优化艾特功能 ([e7cb15a](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/e7cb15a))
* :zap: perf(system): 优化屏幕共享功能 ([5d6c9f3](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/5d6c9f3))
* :zap: perf(system): 优化文件类型消息 ([2eae033](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/2eae033))
* :zap: perf(system): 优化系统托盘图标闪烁 ([5614750](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/5614750))
* :zap: perf(system): 优化系统托盘右键菜单 ([c1563ef](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/c1563ef))
* :bug: fix(system): 使用pinia-shared-state来解决标签页之间store的共享问题 ([54cc2a6](https://github.com/nongyehong/HuLa-IM-Tauri/commit/54cc2a6))
* :bug: fix(system): 修复已知的问题 ([4b557e2](https://github.com/nongyehong/HuLa-IM-Tauri/commit/4b557e2))
* :medal: version(system): . add LICENSE. ([a3f4318](https://github.com/nongyehong/HuLa-IM-Tauri/commit/a3f4318))
* :medal: version(system): 发布1.4.0版本 ([665237d](https://github.com/nongyehong/HuLa-IM-Tauri/commit/665237d))
* :sparkles: feat(custom): 新增自动登录功能 ([c2fb6d7](https://github.com/nongyehong/HuLa-IM-Tauri/commit/c2fb6d7))
* :sparkles: feat(setting): 新增关闭窗口提示 ([183a2e7](https://github.com/nongyehong/HuLa-IM-Tauri/commit/183a2e7))
* :sparkles: feat(style): 新增个人信息框 ([9fbb6f3](https://github.com/nongyehong/HuLa-IM-Tauri/commit/9fbb6f3))
* :sparkles: feat(style): 新增用户信息框跟随图片背景颜色功能 ([2feb9c0](https://github.com/nongyehong/HuLa-IM-Tauri/commit/2feb9c0))
* :sparkles: feat(system): 新增群聊侧边栏 ([4f81f50](https://github.com/nongyehong/HuLa-IM-Tauri/commit/4f81f50))
* :sparkles: feat(system): 新增消息独立窗口功能 ([60939bf](https://github.com/nongyehong/HuLa-IM-Tauri/commit/60939bf))
* :sparkles: feat(system): 新增在线状态 ([ed7be38](https://github.com/nongyehong/HuLa-IM-Tauri/commit/ed7be38))
* :sparkles: feat(system): 新增ait功能 ([9a94c17](https://github.com/nongyehong/HuLa-IM-Tauri/commit/9a94c17))
* :zap: perf(setting): 使用命名空间简化ts类型导入 ([bd96330](https://github.com/nongyehong/HuLa-IM-Tauri/commit/bd96330))
* :zap: perf(setting): 优化配置 ([d139a36](https://github.com/nongyehong/HuLa-IM-Tauri/commit/d139a36))
* :zap: perf(style): 完善主题的配置,优化信息列表功能 ([70fb55b](https://github.com/nongyehong/HuLa-IM-Tauri/commit/70fb55b))
* :zap: perf(system): 优化艾特功能 ([e7cb15a](https://github.com/nongyehong/HuLa-IM-Tauri/commit/e7cb15a))
* :zap: perf(system): 优化屏幕共享功能 ([5d6c9f3](https://github.com/nongyehong/HuLa-IM-Tauri/commit/5d6c9f3))
* :zap: perf(system): 优化文件类型消息 ([2eae033](https://github.com/nongyehong/HuLa-IM-Tauri/commit/2eae033))
* :zap: perf(system): 优化系统托盘图标闪烁 ([5614750](https://github.com/nongyehong/HuLa-IM-Tauri/commit/5614750))
* :zap: perf(system): 优化系统托盘右键菜单 ([c1563ef](https://github.com/nongyehong/HuLa-IM-Tauri/commit/c1563ef))
## 1.3.0-beta (2024-03-12)
* :bug: fix(custom): 修改页面样式没有加载问题,修复打包后静态文件路径问题 ([63ce821](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/63ce821))
* :pencil2: docs(style): 更新README文档 ([a4c1712](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/a4c1712))
* :zap: perf(system): 使用原生event进行窗口通信 ([8adefce](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/8adefce))
* :bug: fix(custom): 修改页面样式没有加载问题,修复打包后静态文件路径问题 ([63ce821](https://github.com/nongyehong/HuLa-IM-Tauri/commit/63ce821))
* :pencil2: docs(style): 更新README文档 ([a4c1712](https://github.com/nongyehong/HuLa-IM-Tauri/commit/a4c1712))
* :zap: perf(system): 使用原生event进行窗口通信 ([8adefce](https://github.com/nongyehong/HuLa-IM-Tauri/commit/8adefce))
## <small>1.2.9-alpha (2024-03-08)</small>
* :bug: fix(custom): 回退vite版本5.0.12导致启动不了端口服务的问题 ([ba99bb2](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/ba99bb2))
* :bug: fix(custom): 修复跨标签页没有判断类型的问题 ([e4acbd5](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/e4acbd5))
* :bug: fix(custom): 修复新建窗口的问题,修复窗口背景的问题 ([f1010e4](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/f1010e4))
* :bug: fix(custom): 修复已知的问题 ([600e224](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/600e224))
* :bug: fix(custom): 修复已知问题 ([648995e](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/648995e))
* :bug: fix(custom): 修复因为配置的问题导致的代码格式化 ([ed4d825](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/ed4d825))
* :lipstick: style(custom): 使用.editorconfig和.gitattributes来约束行分隔符和编辑器设置 ([72808d8](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/72808d8))
* :new: version(custom): 发布v1.0.0-alpha版本 ([d2ac653](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/d2ac653))
* :sparkles: feat(custom): 新增登录页面样式 ([8f0d27e](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/8f0d27e))
* :sparkles: feat(custom): 新增动态页面 ([196d2d1](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/196d2d1))
* :sparkles: feat(custom): 新增好友和群聊详情页 ([b11593d](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/b11593d))
* :sparkles: feat(custom): 新增好友聊天页面设置侧边栏 ([6a1688b](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/6a1688b))
* :sparkles: feat(custom): 新增护眼主题隐藏窗口默认顶部菜单栏优化rust打包配置 ([295bde5](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/295bde5))
* :sparkles: feat(custom): 新增聊天页面的总体样式 ([5e21671](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/5e21671))
* :sparkles: feat(custom): 新增聊天页面页脚显示功能 ([ebd7a41](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/ebd7a41))
* :sparkles: feat(custom): 新增输入框功能 ([009c3b0](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/009c3b0))
* :sparkles: feat(custom): 新增拖拽排序、消息移除功能 ([393a689](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/393a689))
* :sparkles: feat(custom): 新增消息气泡功能(第一版) ([2f2bd45](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/2f2bd45))
* :sparkles: feat(custom): 新增右键菜单功能,修复隐藏顶部菜单栏后无法拖拽的问题 ([1b6e719](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/1b6e719))
* :sparkles: feat(custom): 新增元素平滑上升的vue指令 ([e973799](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/e973799))
* :sparkles: feat(custom): 新增主题切换功能,修复一些已知的问题 ([b6f668f](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/b6f668f))
* :sparkles: feat(custom): 新增unocss及其插件 ([2af6767](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/2af6767))
* :sunflower: update(custom): 更新侧边栏功能显示 ([acc0194](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/acc0194))
* :sunflower: update(custom): 更新登录界面样式 ([2394aa2](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/2394aa2))
* :sunflower: update(custom): 更新登录页面的样式,新增二维码登录页面 ([3fbc6b0](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/3fbc6b0))
* :sunflower: update(custom): 更新登录页面和多窗口的样式 ([be6bc2d](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/be6bc2d))
* :sunflower: update(custom): 更新登录页面样式,更新主页的列表样式 ([ad69ada](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/ad69ada))
* :sunflower: update(custom): 更新好友和群聊详情页面 ([16007a2](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/16007a2))
* :sunflower: update(custom): 更新聊天框页面的图标样式 ([60bc859](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/60bc859))
* :sunflower: update(custom): 更新气泡动画 ([e8dc441](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/e8dc441))
* :sunflower: update(custom): 更新图标,新增系统托盘功能 ([3f1ea39](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/3f1ea39))
* :sunflower: update(custom): 更新图标风格和主题颜色 ([db566cd](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/db566cd))
* :sunflower: update(custom): 更新项目主要依赖版本 ([2a609ff](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/2a609ff))
* :sunflower: update(custom): 更新悬浮按钮样式,修复已知的部分问题 ([1f01b7b](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/1f01b7b))
* :sunflower: update(custom): 更新依赖版本 ([5c26e4e](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/5c26e4e))
* :sunflower: update(custom): 更新用户列表样式 ([80ff121](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/80ff121))
* :sunflower: update(custom): 更新右键菜单到信息和用户列表中 ([487767c](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/487767c))
* :sunflower: update(custom): 更新右键菜单位置抉择 ([070d1e6](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/070d1e6))
* :sunflower: update(custom): 更新右键菜单样式,新增聊天输入框右键菜单 ([4224cbd](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/4224cbd))
* :sunflower: update(custom): 更新主页的收缩功能 ([6f58e78](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/6f58e78))
* :sunflower: update(custom): 更新主页的样式 ([bf94200](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/bf94200))
* :sunflower: update(custom): 更新主页的样式 ([6a39060](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/6a39060))
* :sunflower: update(custom): 更新主页消息列表样式 ([910a6c4](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/910a6c4))
* :sunflower: update(custom): 更新cz-customizable配置 ([43145e0](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/43145e0))
* :sunflower: update(custom): 升级vue、vue-tsc、TS、vite版本 ([27e7bd0](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/27e7bd0))
* init 初始化仓库 ([85a59e8](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/85a59e8))
* init 初始化项目配置 ([0b48de8](https://gitee.com/nongyehong/HuLa-IM-Tauri/commits/0b48de8))
* :bug: fix(custom): 回退vite版本5.0.12导致启动不了端口服务的问题 ([ba99bb2](https://github.com/nongyehong/HuLa-IM-Tauri/commit/ba99bb2))
* :bug: fix(custom): 修复跨标签页没有判断类型的问题 ([e4acbd5](https://github.com/nongyehong/HuLa-IM-Tauri/commit/e4acbd5))
* :bug: fix(custom): 修复新建窗口的问题,修复窗口背景的问题 ([f1010e4](https://github.com/nongyehong/HuLa-IM-Tauri/commit/f1010e4))
* :bug: fix(custom): 修复已知的问题 ([600e224](https://github.com/nongyehong/HuLa-IM-Tauri/commit/600e224))
* :bug: fix(custom): 修复已知问题 ([648995e](https://github.com/nongyehong/HuLa-IM-Tauri/commit/648995e))
* :bug: fix(custom): 修复因为配置的问题导致的代码格式化 ([ed4d825](https://github.com/nongyehong/HuLa-IM-Tauri/commit/ed4d825))
* :lipstick: style(custom): 使用.editorconfig和.gitattributes来约束行分隔符和编辑器设置 ([72808d8](https://github.com/nongyehong/HuLa-IM-Tauri/commit/72808d8))
* :new: version(custom): 发布v1.0.0-alpha版本 ([d2ac653](https://github.com/nongyehong/HuLa-IM-Tauri/commit/d2ac653))
* :sparkles: feat(custom): 新增登录页面样式 ([8f0d27e](https://github.com/nongyehong/HuLa-IM-Tauri/commit/8f0d27e))
* :sparkles: feat(custom): 新增动态页面 ([196d2d1](https://github.com/nongyehong/HuLa-IM-Tauri/commit/196d2d1))
* :sparkles: feat(custom): 新增好友和群聊详情页 ([b11593d](https://github.com/nongyehong/HuLa-IM-Tauri/commit/b11593d))
* :sparkles: feat(custom): 新增好友聊天页面设置侧边栏 ([6a1688b](https://github.com/nongyehong/HuLa-IM-Tauri/commit/6a1688b))
* :sparkles: feat(custom): 新增护眼主题隐藏窗口默认顶部菜单栏优化rust打包配置 ([295bde5](https://github.com/nongyehong/HuLa-IM-Tauri/commit/295bde5))
* :sparkles: feat(custom): 新增聊天页面的总体样式 ([5e21671](https://github.com/nongyehong/HuLa-IM-Tauri/commit/5e21671))
* :sparkles: feat(custom): 新增聊天页面页脚显示功能 ([ebd7a41](https://github.com/nongyehong/HuLa-IM-Tauri/commit/ebd7a41))
* :sparkles: feat(custom): 新增输入框功能 ([009c3b0](https://github.com/nongyehong/HuLa-IM-Tauri/commit/009c3b0))
* :sparkles: feat(custom): 新增拖拽排序、消息移除功能 ([393a689](https://github.com/nongyehong/HuLa-IM-Tauri/commit/393a689))
* :sparkles: feat(custom): 新增消息气泡功能(第一版) ([2f2bd45](https://github.com/nongyehong/HuLa-IM-Tauri/commit/2f2bd45))
* :sparkles: feat(custom): 新增右键菜单功能,修复隐藏顶部菜单栏后无法拖拽的问题 ([1b6e719](https://github.com/nongyehong/HuLa-IM-Tauri/commit/1b6e719))
* :sparkles: feat(custom): 新增元素平滑上升的vue指令 ([e973799](https://github.com/nongyehong/HuLa-IM-Tauri/commit/e973799))
* :sparkles: feat(custom): 新增主题切换功能,修复一些已知的问题 ([b6f668f](https://github.com/nongyehong/HuLa-IM-Tauri/commit/b6f668f))
* :sparkles: feat(custom): 新增unocss及其插件 ([2af6767](https://github.com/nongyehong/HuLa-IM-Tauri/commit/2af6767))
* :sunflower: update(custom): 更新侧边栏功能显示 ([acc0194](https://github.com/nongyehong/HuLa-IM-Tauri/commit/acc0194))
* :sunflower: update(custom): 更新登录界面样式 ([2394aa2](https://github.com/nongyehong/HuLa-IM-Tauri/commit/2394aa2))
* :sunflower: update(custom): 更新登录页面的样式,新增二维码登录页面 ([3fbc6b0](https://github.com/nongyehong/HuLa-IM-Tauri/commit/3fbc6b0))
* :sunflower: update(custom): 更新登录页面和多窗口的样式 ([be6bc2d](https://github.com/nongyehong/HuLa-IM-Tauri/commit/be6bc2d))
* :sunflower: update(custom): 更新登录页面样式,更新主页的列表样式 ([ad69ada](https://github.com/nongyehong/HuLa-IM-Tauri/commit/ad69ada))
* :sunflower: update(custom): 更新好友和群聊详情页面 ([16007a2](https://github.com/nongyehong/HuLa-IM-Tauri/commit/16007a2))
* :sunflower: update(custom): 更新聊天框页面的图标样式 ([60bc859](https://github.com/nongyehong/HuLa-IM-Tauri/commit/60bc859))
* :sunflower: update(custom): 更新气泡动画 ([e8dc441](https://github.com/nongyehong/HuLa-IM-Tauri/commit/e8dc441))
* :sunflower: update(custom): 更新图标,新增系统托盘功能 ([3f1ea39](https://github.com/nongyehong/HuLa-IM-Tauri/commit/3f1ea39))
* :sunflower: update(custom): 更新图标风格和主题颜色 ([db566cd](https://github.com/nongyehong/HuLa-IM-Tauri/commit/db566cd))
* :sunflower: update(custom): 更新项目主要依赖版本 ([2a609ff](https://github.com/nongyehong/HuLa-IM-Tauri/commit/2a609ff))
* :sunflower: update(custom): 更新悬浮按钮样式,修复已知的部分问题 ([1f01b7b](https://github.com/nongyehong/HuLa-IM-Tauri/commit/1f01b7b))
* :sunflower: update(custom): 更新依赖版本 ([5c26e4e](https://github.com/nongyehong/HuLa-IM-Tauri/commit/5c26e4e))
* :sunflower: update(custom): 更新用户列表样式 ([80ff121](https://github.com/nongyehong/HuLa-IM-Tauri/commit/80ff121))
* :sunflower: update(custom): 更新右键菜单到信息和用户列表中 ([487767c](https://github.com/nongyehong/HuLa-IM-Tauri/commit/487767c))
* :sunflower: update(custom): 更新右键菜单位置抉择 ([070d1e6](https://github.com/nongyehong/HuLa-IM-Tauri/commit/070d1e6))
* :sunflower: update(custom): 更新右键菜单样式,新增聊天输入框右键菜单 ([4224cbd](https://github.com/nongyehong/HuLa-IM-Tauri/commit/4224cbd))
* :sunflower: update(custom): 更新主页的收缩功能 ([6f58e78](https://github.com/nongyehong/HuLa-IM-Tauri/commit/6f58e78))
* :sunflower: update(custom): 更新主页的样式 ([bf94200](https://github.com/nongyehong/HuLa-IM-Tauri/commit/bf94200))
* :sunflower: update(custom): 更新主页的样式 ([6a39060](https://github.com/nongyehong/HuLa-IM-Tauri/commit/6a39060))
* :sunflower: update(custom): 更新主页消息列表样式 ([910a6c4](https://github.com/nongyehong/HuLa-IM-Tauri/commit/910a6c4))
* :sunflower: update(custom): 更新cz-customizable配置 ([43145e0](https://github.com/nongyehong/HuLa-IM-Tauri/commit/43145e0))
* :sunflower: update(custom): 升级vue、vue-tsc、TS、vite版本 ([27e7bd0](https://github.com/nongyehong/HuLa-IM-Tauri/commit/27e7bd0))
* init 初始化仓库 ([85a59e8](https://github.com/nongyehong/HuLa-IM-Tauri/commit/85a59e8))
* init 初始化项目配置 ([0b48de8](https://github.com/nongyehong/HuLa-IM-Tauri/commit/0b48de8))

94
commitlint.config.cjs Normal file
View File

@ -0,0 +1,94 @@
/** @type {import('cz-git').UserConfig} */
module.exports = {
// 继承的规则
extends: ["@commitlint/config-conventional"],
// 自定义规则
rules: {
// @see https://commitlint.js.org/#/reference-rules
// 提交类型枚举git提交type必须是以下类型
"type-enum": [
2,
"always",
[
"feat", // 新增功能
"fix", // 修复缺陷
"docs", // 文档变更
"style", // 代码格式(不影响功能,例如空格、分号等格式修正)
"refactor", // 代码重构(不包括 bug 修复、功能新增)
"perf", // 性能优化
"test", // 添加疏漏测试或已有测试改动
"build", // 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)
"ci", // 修改 CI 配置、脚本
"revert", // 回滚 commit
"chore", // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)
],
],
"subject-case": [0], // subject大小写不做校验
},
prompt: {
messages: {
type: "选择你要提交的类型 :",
scope: "选择一个提交范围(可选):",
customScope: "请输入自定义的提交范围 :",
subject: "填写简短精炼的变更描述 :\n",
body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
footerPrefixesSelect: "选择关联issue前缀可选:",
customFooterPrefix: "输入自定义issue前缀 :",
footer: "列举关联issue (可选) 例如: #31, #I3244 :\n",
generatingByAI: "正在通过 AI 生成你的提交简短描述...",
generatedSelectByAI: "选择一个 AI 生成的简短描述:",
confirmCommit: "是否提交或修改commit ?",
},
// prettier-ignore
types: [
{ value: "feat", name: "特性: ✨ 新增功能", emoji: ":sparkles:" },
{ value: "fix", name: "修复: 🐛 修复缺陷", emoji: ":bug:" },
{ value: "docs", name: "文档: 📝 文档变更", emoji: ":memo:" },
{ value: "style", name: "格式: 💄 代码格式(不影响功能,例如空格、分号等格式修正)", emoji: ":lipstick:" },
{ value: "refactor", name: "重构: ♻️ 代码重构(不包括 bug 修复、功能新增)", emoji: ":recycle:" },
{ value: "perf", name: "性能: 🚀 性能优化", emoji: ":zap:" },
{ value: "test", name: "测试: 🧪 添加疏漏测试或已有测试改动", emoji: ":white_check_mark:"},
{ value: "build", name: "构建: 📦️ 构建流程、外部依赖变更(如升级 npm 包、修改 vite 配置等)", emoji: ":package:"},
{ value: "ci", name: "集成: ⚙️ 修改 CI 配置、脚本", emoji: ":ferris_wheel:"},
{ value: "revert", name: "回退: ↩️ 回滚 commit",emoji: ":rewind:"},
{ value: "chore", name: "其他: 🛠️ 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)", emoji: ":hammer:"},
],
useEmoji: true,
emojiAlign: "center",
useAI: false,
aiNumber: 1,
themeColorCode: "38;5;168",
scopes: [],
allowCustomScopes: true,
allowEmptyScopes: true,
customScopesAlign: "bottom",
customScopesAlias: "custom",
emptyScopesAlias: "empty",
upperCaseSubject: false,
markBreakingChangeMode: false,
allowBreakingChanges: ["feat", "fix"],
breaklineNumber: 100,
breaklineChar: "|",
skipQuestions: [],
issuePrefixes: [
{ value: "closed", name: "closed: ISSUES has been processed" },
],
customIssuePrefixAlign: "top",
emptyIssuePrefixAlias: "skip",
customIssuePrefixAlias: "custom",
allowCustomIssuePrefix: true,
allowEmptyIssuePrefix: true,
confirmColorize: true,
maxHeaderLength: Infinity,
maxSubjectLength: Infinity,
minSubjectLength: 0,
scopeOverrides: undefined,
defaultBody: "",
defaultIssues: "",
defaultScope: "",
defaultSubject: "",
},
};

View File

@ -1,79 +0,0 @@
module.exports = {
types: [
{
value: ':sparkles: feat',
name: '✨ feat: 新功能'
},
{
value: ':sunflower: update',
name: '🌻 update: 更新'
},
{
value: ':bug: fix',
name: '🐛 fix: 修复bug'
},
{
value: ':package: build',
name: '📦build: 打包'
},
{
value: ':zap: perf',
name: '⚡️ perf: 性能优化'
},
{
value: ':tada: release',
name: '🎉 release: 发布正式版'
},
{
value: ':medal: version',
name: '🎖version: 发布新版本'
},
{
value: ':lipstick: style',
name: '💄 style: 代码的样式美化'
},
{
value: ':recycle: refactor',
name: '♻️ refactor: 重构'
},
{
value: ':pencil2: docs',
name: '✏️ docs: 文档变更'
},
{
value: ':white_check_mark: test',
name: '✅ test: 测试'
},
{
value: ':rewind: revert',
name: '⏪️ revert: 回退'
},
{
value: ':rocket: chore',
name: '🚀 chore: 构建/工程依赖/工具'
},
{
value: ':construction_worker: ci',
name: '👷 ci: CI相关的变化'
}
],
// 每一步的提示信息
messages: {
type: '请选择提交类型',
scope: '请选择文件修改范围',
subject: '请输入commit标题(必填)',
body: '请输入commit描述, 可通过&换行(选填)',
// breaking: '列出任何BREAKING CHANGES(破坏性修改)(可选)',
// footer: '请输入要关闭的issue(可选)',
confirmCommit: '确定提交此 commit 吗?'
},
// 配置scope可选项mono项目可按子项目维度划分非mono项目可按功能or业务模块划分
scopes: ['custom', 'system', 'style', 'setting'],
// commit描述的换行符
breaklineChar: '&',
skipQuestions: ['breaking', 'footer'],
// 标题首字母大写
upperCaseSubject: true,
// 标题必填
requiredSubject: true
}

View File

@ -7,7 +7,7 @@
<title>HuLa</title>
<!--引入iconpark图标库-->
<script defer src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_30895_77.6625fcfb23b027973a50f66bbc7126de.js"></script>
<script defer src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_30895_78.2ef5ae05e210de3f66b0fe5c58a7a130.js"></script>
</head>
<body>

View File

@ -2,12 +2,15 @@
"name": "hula-im-tauri",
"private": true,
"type": "module",
"version": "v1.4.0",
"packageManager": "pnpm@8.15.4",
"version": "v1.5.0",
"license": "Apache-2.0",
"engines": {
"node": ">=18.12.0",
"pnpm": ">=8.10.0"
},
"repository": {
"url": "https://github.com/nongyehong/HuLa-IM-Tauri.git"
},
"author": {
"name": "nongyehong",
"email": "2439646234@qq.com",
@ -29,9 +32,10 @@
"tauri:build": "tauri build",
"tauri:icon": "tauri icon public/logo.png",
"preinstall": "npx only-allow pnpm",
"commit": "git add . && lint-staged && git-cz && conventional-changelog -p cz-config.cjs -i CHANGELOG.md -s -r 0",
"changelog": "conventional-changelog -p cz-config.cjs -i CHANGELOG.md -s -r 0",
"lint:staged": "lint-staged"
"commit": "git add . && git-cz",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
"lint:staged": "lint-staged",
"prepare": "husky install"
},
"dependencies": {
"@tauri-apps/api": "^1.5.3",
@ -44,37 +48,39 @@
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"pinia-shared-state": "^0.5.1",
"vue": "^3.4.21",
"vue": "^3.4.23",
"vue-draggable-plus": "^0.4.0",
"vue-router": "^4.3.0"
},
"devDependencies": {
"@babel/eslint-parser": "^7.23.3",
"@commitlint/cli": "^19.2.2",
"@commitlint/config-conventional": "^19.2.2",
"@rollup/plugin-terser": "^0.4.4",
"@tauri-apps/cli": "^1.5.11",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.10.4",
"@typescript-eslint/eslint-plugin": "7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"@unocss/preset-uno": "^0.58.7",
"@unocss/reset": "^0.58.7",
"@unocss/transformer-directives": "^0.58.7",
"@unocss/vite": "^0.58.7",
"@unocss/preset-uno": "^0.59.4",
"@unocss/reset": "^0.59.4",
"@unocss/transformer-directives": "^0.59.4",
"@unocss/transformer-variant-group": "^0.59.4",
"@unocss/vite": "^0.59.4",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vueuse/core": "^10.8.0",
"commitizen": "^4.3.0",
"conventional-changelog": "^5.1.0",
"conventional-changelog-cli": "^4.1.0",
"cz-conventional-changelog": "^3.3.0",
"cz-customizable": "^7.0.0",
"cz-git": "^1.9.1",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-vue": "^9.19.2",
"husky": "^9.0.11",
"lint-staged": "^15.2.0",
"only-allow": "^1.2.1",
"oxlint": "^0.2.4",
"prettier": "^3.2.5",
"sass": "^1.71.1",
@ -82,15 +88,12 @@
"typescript": "^5.4.3",
"unplugin-auto-import": "^0.17.2",
"unplugin-vue-components": "^0.26.0",
"vite": "5.2.8",
"vite": "5.2.9",
"vue-tsc": "^2.0.6"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-customizable"
},
"cz-customizable": {
"config": "config/cz-config.cjs"
"path": "node_modules/cz-git"
}
}
}

File diff suppressed because it is too large Load Diff

2
src-tauri/Cargo.lock generated
View File

@ -1574,7 +1574,7 @@ checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
[[package]]
name = "hula"
version = "1.4.0"
version = "1.5.0"
dependencies = [
"serde",
"serde_json",

View File

@ -1,6 +1,6 @@
[package]
name = "hula"
version = "1.4.0"
version = "1.5.0"
description = "hula"
authors = ["nongyehong"]
license = ""

View File

@ -8,7 +8,7 @@
},
"package": {
"productName": "HuLa",
"version": "1.4.0"
"version": "1.5.0"
},
"tauri": {
"updater": {

View File

@ -15,7 +15,7 @@ const settingStore = setting()
const OLStatusStore = onlineStatus()
const { themes } = storeToRefs(settingStore)
/* 禁止图片以及输入框的拖拽 */
/** 禁止图片以及输入框的拖拽 */
const preventDrag = (e: MouseEvent) => {
const event = e.target as HTMLElement
// <img>
@ -26,21 +26,21 @@ const preventDrag = (e: MouseEvent) => {
onMounted(() => {
// initWebSocket()
// /*! 使msi */
// /**! 使msi */
// sendNotification({ title: 'TAURI', body: 'Tauri is awesome!' })
// localStorage
if (!localStorage.getItem(StoresEnum.SETTING)) {
settingStore.initTheme(ThemeEnum.OS)
}
/* 第一次没有选状态的时候随机选中一个状态 */
/** 第一次没有选状态的时候随机选中一个状态 */
if (!localStorage.getItem(StoresEnum.ONLINE_STATUS)) {
OLStatusStore.init()
}
document.documentElement.dataset.theme = themes.value.content
window.addEventListener('dragstart', preventDrag)
/* 开发环境不禁止 */
/** 开发环境不禁止 */
if (process.env.NODE_ENV !== 'development') {
/* 禁用浏览器默认的快捷键 */
/** 禁用浏览器默认的快捷键 */
window.addEventListener('keydown', (e) => {
// ctrl+c ctrl+v ctrl+enter
if (e.ctrlKey && (e.key === 'c' || e.key === 'v' || e.key === 'Enter')) return
@ -48,7 +48,7 @@ onMounted(() => {
e.preventDefault()
}
})
/* 禁止右键菜单 */
/** 禁止右键菜单 */
window.addEventListener('contextmenu', (e) => e.preventDefault(), false)
}
})

View File

@ -2,7 +2,28 @@
<div ref="containerRef">
<slot></slot>
<Teleport to="body">
<Transition @beforeEnter="handleBeforeEnter" @enter="handleEnter" @afterEnter="handleAfterEnter">
<transition-group @beforeEnter="handleBeforeEnter" @enter="handleEnter" @afterEnter="handleAfterEnter">
<!-- 群聊emoji表情菜单 -->
<div
v-if="showMenu && emoji && emoji.length > 0"
class="context-menu"
style="display: flex; height: fit-content"
:style="{
left: `${pos.posX}px`,
top: `${pos.posY - 42}px`
}">
<n-flex
v-for="(item, index) in emoji as any[]"
:key="index"
align="center"
justify="space-between"
class="emoji-list">
<n-flex :size="0" align="center" justify="center" class="emoji-item" @click="handleReplyEmoji(item)">
{{ item.label }}
</n-flex>
</n-flex>
</div>
<!-- 普通右键菜单 -->
<div
v-if="showMenu"
class="context-menu"
@ -10,7 +31,7 @@
left: `${pos.posX}px`,
top: `${pos.posY}px`
}">
<div v-resize="handleSize" v-if="menu.length > 0" class="menu-list">
<div v-resize="handleSize" v-if="menu && menu.length > 0" class="menu-list">
<div v-for="(item, index) in menu as any[]" :key="index">
<!-- 禁止的菜单选项需要禁止点击事件 -->
<div class="menu-item-disabled" v-if="item.disabled" @click.prevent="$event.preventDefault()">
@ -33,7 +54,7 @@
</div>
</div>
</div>
</Transition>
</transition-group>
</Teleport>
</div>
</template>
@ -42,26 +63,30 @@
import { useContextMenu } from '@/hooks/useContextMenu.ts'
import { useViewport } from '@/hooks/useViewport.ts'
const { menu, specialMenu } = defineProps({
const { menu, emoji, specialMenu } = defineProps({
menu: {
type: Array,
default: () => []
type: Array
},
emoji: {
type: Array
},
specialMenu: {
type: Array,
default: () => []
}
})
/** 判断是否传入了menu */
const isNull = computed(() => menu === void 0)
const containerRef = ref(null)
const emit = defineEmits(['select'])
/* 获取鼠标位置和是否显示右键菜单 */
const { x, y, showMenu } = useContextMenu(containerRef)
/* 获取视口的宽高 */
const emit = defineEmits(['select', 'reply-emoji'])
/** 获取鼠标位置和是否显示右键菜单 */
const { x, y, showMenu } = useContextMenu(containerRef, isNull)
/** 获取视口的宽高 */
const { vw, vh } = useViewport()
/* 定义右键菜单尺寸 */
/** 定义右键菜单尺寸 */
const w = ref(0)
const h = ref(0)
/* 计算右键菜单的位置 */
/** 计算右键菜单的位置 */
const pos = computed(() => {
let posX = x.value
let posY = y.value
@ -84,10 +109,20 @@ const handleSize = ({ width, height }: any) => {
h.value = height
}
/* 处理右键菜单点击事件 */
/** 处理右键菜单点击事件 */
const handleClick = (item: string) => {
showMenu.value = false
emit('select', item)
nextTick(() => {
showMenu.value = false
emit('select', item)
})
}
/** 处理回复表情事件 */
const handleReplyEmoji = (item: string) => {
nextTick(() => {
showMenu.value = false
emit('reply-emoji', item)
})
}
const handleBeforeEnter = (el: any) => {
@ -127,6 +162,12 @@ const handleAfterEnter = (el: any) => {
}
.context-menu {
@include menu-item-style();
.emoji-list {
@apply size-fit p-4px select-none;
.emoji-item {
@apply size-28px rounded-4px text-16px cursor-pointer hover:bg-[--emoji-hover];
}
}
.menu-list {
padding: 5px;
display: flex;

View File

@ -8,7 +8,7 @@
round
:color="'#fff'"
:size="80"
:src="info.avatar"
:src="isCurrentUser.avatar"
fallback-src="/logo.png"></n-avatar>
<n-flex :size="5" align="center" style="margin-left: -4px" class="item-hover">
@ -47,17 +47,22 @@
<img
class="size-full rounded-8px box-border p-20px absolute top-0 left-0"
style="filter: blur(28px); opacity: 0.8"
:src="info.avatar"
:src="isCurrentUser.avatar"
alt="" />
</n-flex>
</template>
<script setup lang="ts">
import { MockItem } from '@/services/types.ts'
import { setting } from '@/stores/setting.ts'
import { storeToRefs } from 'pinia'
const { info } = defineProps<{
info: MockItem
info?: MockItem
}>()
const settingStore = setting()
const { login } = storeToRefs(settingStore)
const isCurrentUser = computed(() => (info ? info : login.value.accountInfo))
</script>
<style scoped lang="scss">

View File

@ -25,11 +25,11 @@ import { ThemeEnum } from '@/enums'
const settingStore = setting()
const { themes } = storeToRefs(settingStore)
/*监听深色主题颜色变化*/
/**监听深色主题颜色变化*/
const globalTheme = ref<any>(themes.value.content)
const prefers = matchMedia('(prefers-color-scheme: dark)')
/* 跟随系统主题模式切换主题 */
/** 跟随系统主题模式切换主题 */
const followOS = () => {
globalTheme.value = prefers.matches ? darkTheme : lightTheme
document.documentElement.dataset.theme = prefers.matches ? ThemeEnum.DARK : ThemeEnum.LIGHT
@ -105,7 +105,7 @@ const commonTheme: GlobalThemeOverrides = {
}
}
/* 浅色模式的主题颜色 */
/** 浅色模式的主题颜色 */
const lightThemeOverrides: GlobalThemeOverrides = {
...commonTheme,
Scrollbar: {
@ -114,7 +114,7 @@ const lightThemeOverrides: GlobalThemeOverrides = {
}
}
/* 深色模式的主题颜色 */
/** 深色模式的主题颜色 */
const darkThemeOverrides: GlobalThemeOverrides = {
...commonTheme,
Scrollbar: {

View File

@ -9,9 +9,9 @@
:src="item.avatar"
alt="" />
<span class="text-20px text-[--text-color]">{{ item.accountName }}</span>
<span class="text-(20px [--text-color])">{{ item.accountName }}</span>
<span class="text-14px text-#909090">这个人很高冷,暂时没有留下什么</span>
<span class="text-(14px #909090)">这个人很高冷,暂时没有留下什么</span>
<n-flex align="center" justify="space-between" :size="30" class="text-#606060">
<span>性别</span>
@ -54,8 +54,8 @@
:src="item.avatar"
alt="" />
<n-flex vertical :size="16" justify="space-between" class="text-14px color-#909090">
<span class="text-16px text-[--text-color]">{{ item.accountName }}</span>
<n-flex vertical :size="16" justify="space-between" class="text-(14px #909090)">
<span class="text-(16px [--text-color])">{{ item.accountName }}</span>
<span>群号1235873897182</span>
<span>创建时间2021-01-01</span>
</n-flex>

View File

@ -91,19 +91,19 @@ import { onKeyStroke } from '@vueuse/core'
const settingStore = setting()
const { themes } = storeToRefs(settingStore)
/* 发送按钮旁的箭头 */
/** 发送按钮旁的箭头 */
const arrow = ref(false)
// dom
const messageInputDom = ref()
const activeItem = ref(inject('activeItem') as MockItem)
/* 虚拟列表 */
/** 虚拟列表 */
const virtualListInst = ref<VirtualListInst>()
const { handlePaste } = useCommon()
/* 引入useMsgInput的相关方法 */
/** 引入useMsgInput的相关方法 */
const { inputKeyDown, handleAit, handleInput, send, filteredList, ait, msgInput, chatKey, menuList, selectedAitKey } =
useMsgInput(messageInputDom)
/* 当切换聊天对象时,重新获取焦点 */
/** 当切换聊天对象时,重新获取焦点 */
watch(activeItem, () => {
nextTick(() => {
const inputDiv = document.getElementById('message-input')
@ -111,16 +111,16 @@ watch(activeItem, () => {
})
})
/* 当ait人员列表发生变化的时候始终select第一个 */
/** 当ait人员列表发生变化的时候始终select第一个 */
watch(filteredList, (newList) => {
if (newList.length > 0) {
/* 先设置滚动条滚动到第一个 */
/** 先设置滚动条滚动到第一个 */
virtualListInst.value?.scrollTo({ key: newList[0].key })
selectedAitKey.value = newList[0].key
}
})
/* 处理键盘上下键切换提及项 */
/** 处理键盘上下键切换提及项 */
const handleAitKeyChange = (direction: 1 | -1) => {
const currentIndex = filteredList.value.findIndex((item) => item.key === selectedAitKey.value)
const newIndex = Math.max(0, Math.min(currentIndex + direction, filteredList.value.length - 1))
@ -130,7 +130,7 @@ const handleAitKeyChange = (direction: 1 | -1) => {
}
const closeMenu = (event: any) => {
/* 需要判断点击如果不是.context-menu类的元素的时候menu才会关闭 */
/** 需要判断点击如果不是.context-menu类的元素的时候menu才会关闭 */
if (!event.target.matches('#message-input, #message-input *')) {
ait.value = false
}
@ -158,11 +158,11 @@ onMounted(() => {
inputDiv?.focus()
})
// TODO itemset@ (nyh -> 2024-04-09 01:03:59)
/* 当不是独立窗口的时候也就是组件与组件之间进行通信然后监听信息对话的变化 */
/** 当不是独立窗口的时候也就是组件与组件之间进行通信然后监听信息对话的变化 */
Mitt.on(MittEnum.MSG_BOX_SHOW, (event: any) => {
activeItem.value = event.item
})
/* 这里使用的是窗口之间的通信来监听信息对话的变化 */
/** 这里使用的是窗口之间的通信来监听信息对话的变化 */
listen('aloneData', (event: any) => {
activeItem.value = { ...event.payload.item }
})
@ -173,7 +173,7 @@ onUnmounted(() => {
window.removeEventListener('click', closeMenu, true)
})
/* 导出组件方法和属性 */
/** 导出组件方法和属性 */
defineExpose({ messageInputDom })
</script>

View File

@ -50,7 +50,7 @@
</n-popover>
<n-popover trigger="hover" :show-arrow="false" placement="bottom">
<template #trigger>
<svg @click="open({ accept: 'image/*' })" class="mr-18px"><use href="#photo"></use></svg>
<svg @click="open({ accept: 'image/**' })" class="mr-18px"><use href="#photo"></use></svg>
</template>
<span>图片</span>
</n-popover>

View File

@ -86,15 +86,15 @@
<p class="color-#d03553">删除好友</p>
</div>
<p class="m-[0_auto] text-#13987f text-12px mt-20px cursor-pointer">被骚扰了?&nbsp;&nbsp;举报该用户</p>
<p class="m-[0_auto] text-(12px #13987f) mt-20px cursor-pointer">被骚扰了?&nbsp;&nbsp;举报该用户</p>
</div>
</transition>
</main>
<!-- 弹出框 -->
<n-modal v-model:show="modalShow" class="w-350px border-rd-8px">
<n-modal v-model:show="modalShow" class="w-350px rounded-8px">
<div class="bg-[--bg-popover] w-360px h-full p-6px box-border flex flex-col">
<svg @click="modalShow = false" class="w-12px h-12px ml-a cursor-pointer select-none">
<svg @click="modalShow = false" class="size-12px ml-a cursor-pointer select-none">
<use href="#close"></use>
</svg>
<div class="flex flex-col gap-30px p-[22px_10px_10px_22px] select-none">
@ -121,9 +121,9 @@ import { emit, listen } from '@tauri-apps/api/event'
// 使useDisplayMedia
const { stream, start, stop } = useDisplayMedia()
/* 提醒框标题 */
/** 提醒框标题 */
const tips = ref()
/* 提醒框的选项 */
/** 提醒框的选项 */
const tipsOptions = ref(false)
const modalShow = ref(false)
const sidebarShow = ref(false)
@ -153,7 +153,7 @@ const handleMedia = () => {
//
peerConnection.setLocalDescription(offer)
emit(EventEnum.SHARE_SCREEN)
/* 当需要给独立窗口传输数据的时候需要先监听窗口的创建完毕事件 */
/** 当需要给独立窗口传输数据的时候需要先监听窗口的创建完毕事件 */
listen('SharedScreenWin', async () => {
await emit('offer', offer)
})
@ -163,7 +163,7 @@ const handleMedia = () => {
})
}
/* 删除操作二次提醒 */
/** 删除操作二次提醒 */
const handleDelete = (label: string) => {
modalShow.value = true
if (label === 'friends') {
@ -182,7 +182,7 @@ const handleClick = () => {
}
const closeMenu = (event: any) => {
/* 点击非侧边栏元素时,关闭侧边栏,但点击弹出框元素、侧边栏图标、还有侧边栏里面的元素时不关闭 */
/** 点击非侧边栏元素时,关闭侧边栏,但点击弹出框元素、侧边栏图标、还有侧边栏里面的元素时不关闭 */
if (!event.target.matches('.sidebar, .sidebar *, .n-modal-mask, .options-box *, .n-modal *') && !modalShow.value) {
sidebarShow.value = false
}

View File

@ -16,7 +16,7 @@
:key="item.key"
class="flex-y-center min-h-58px"
:class="[
[activeItem.type === RoomTypeEnum.GROUP ? 'p-[18px_20px]' : 'chat-single p-[4px_20px_10px_20px]'],
[isGroup ? 'p-[18px_20px]' : 'chat-single p-[4px_20px_10px_20px]'],
{ 'active-reply': activeReply === item.key }
]">
<!-- 好友或者群聊的信息 -->
@ -37,13 +37,14 @@
<n-popover
@update:show="handlePopoverUpdate(item.key)"
trigger="click"
placement="right-start"
placement="right"
:show-arrow="false"
v-model:show="infoPopover"
style="padding: 0; background: var(--bg-info); backdrop-filter: blur(10px)">
<template #trigger>
<ContextMenu
@select="$event.click(item)"
:menu="activeItem.type === RoomTypeEnum.GROUP ? optionsList : []"
@select="$event.click(item, 'Main')"
:menu="isGroup ? optionsList : void 0"
:special-menu="report">
<n-avatar
lazy
@ -62,7 +63,7 @@
</ContextMenu>
</template>
<!-- 用户个人信息框 -->
<InfoPopover :info="activeItemRef" />
<InfoPopover v-if="selectKey === item.key" :info="item.accountId !== userId ? activeItemRef : void 0" />
</n-popover>
<n-flex
vertical
@ -70,11 +71,8 @@
:size="8"
class="color-[--text-color] flex-1"
:class="item.accountId === userId ? 'items-end mr-10px' : ''">
<ContextMenu
@select="$event.click(item)"
:menu="activeItem.type === RoomTypeEnum.GROUP ? optionsList : []"
:special-menu="report">
<span class="text-12px select-none color-#909090" v-if="activeItem.type === RoomTypeEnum.GROUP">
<ContextMenu @select="$event.click(item)" :menu="isGroup ? optionsList : []" :special-menu="report">
<span class="text-12px select-none color-#909090" v-if="isGroup">
{{ item.value }}
</span>
</ContextMenu>
@ -84,7 +82,9 @@
:data-key="item.accountId === userId ? `U${item.key}` : `Q${item.key}`"
@select="$event.click(item)"
:menu="handleItemType(item.type)"
:emoji="isGroup ? emojiList : []"
:special-menu="specialMenuList"
@reply-emoji="handleEmojiSelect($event.label, item)"
@click="handleMsgClick(item)">
<!-- &lt;!&ndash; 渲染消息内容体 &ndash;&gt;-->
<!-- <RenderMessage :message="message" />-->
@ -162,6 +162,20 @@
{{ item.reply.imgCount }}
</div>
</n-flex>
<!-- 群聊回复emoji表情 -->
<n-flex :size="4" v-if="isGroup && item.emojiList">
<n-flex
:size="2"
align="center"
class="emoji-reply-bubble"
@click.stop="cancelReplyEmoji(item, index)"
v-for="(emoji, index) in item.emojiList"
:key="index">
{{ emoji.label }}
<span class="text-(12px #eee)">{{ emoji.count }}</span>
</n-flex>
</n-flex>
</n-flex>
</div>
</article>
@ -187,7 +201,7 @@
</n-modal>
<!-- 悬浮按钮提示(头部悬浮) // TODO (nyh -> 2024-03-07 01:27:22)-->
<header class="float-header" :class="activeItem.type === RoomTypeEnum.GROUP ? 'right-220px' : 'right-50px'">
<header class="float-header" :class="isGroup ? 'right-220px' : 'right-50px'">
<div class="float-box">
<n-flex justify="space-between" align="center">
<n-icon :color="'#13987f'">
@ -199,10 +213,7 @@
</header>
<!-- 悬浮按钮提示(底部悬浮) -->
<footer
class="float-footer"
v-if="floatFooter && newMsgNum > 0"
:class="activeItem.type === RoomTypeEnum.GROUP ? 'right-220px' : 'right-50px'">
<footer class="float-footer" v-if="floatFooter && newMsgNum > 0" :class="isGroup ? 'right-220px' : 'right-50px'">
<div class="float-box" :class="{ max: newMsgNum > 99 }" @click="scrollBottom">
<n-flex justify="space-between" align="center">
<n-icon :color="newMsgNum > 99 ? '#ce304f' : '#13987f'">
@ -220,7 +231,6 @@ import { EventEnum, MittEnum, MsgEnum, RoomTypeEnum } from '@/enums'
import { MockItem } from '@/services/types.ts'
import Mitt from '@/utils/Bus.ts'
import { invoke } from '@tauri-apps/api/tauri'
import { optionsList, report } from './config.ts'
import { usePopover } from '@/hooks/usePopover.ts'
import { useWindow } from '@/hooks/useWindow.ts'
import { listen } from '@tauri-apps/api/event'
@ -228,21 +238,26 @@ import { useChatMain } from '@/hooks/useChatMain.ts'
import { VirtualListInst } from 'naive-ui'
import { delay } from 'lodash-es'
import { useCommon } from '@/hooks/useCommon.ts'
import { setting } from '@/stores/setting.ts'
import { storeToRefs } from 'pinia'
const { activeItem } = defineProps<{
activeItem: MockItem
}>()
const activeItemRef = ref({ ...activeItem })
const settingStore = setting()
const { login } = storeToRefs(settingStore)
const { createWebviewWindow } = useWindow()
/* 当前点击的用户的key */
const selectKey = ref()
/* 跳转回复消息后选中效果 */
/** 跳转回复消息后选中效果 */
const activeReply = ref(-1)
/* item最小高度用于计算滚动大小和位置 */
const itemSize = computed(() => (activeItem.type === RoomTypeEnum.GROUP ? 98 : 70))
/* 虚拟列表 */
/** 当前信息是否是群聊信息 */
const isGroup = computed(() => activeItem.type === RoomTypeEnum.GROUP)
/** item最小高度用于计算滚动大小和位置 */
const itemSize = computed(() => (isGroup.value ? 98 : 70))
/** 虚拟列表 */
const virtualListInst = ref<VirtualListInst>()
const { handlePopoverUpdate } = usePopover(selectKey, 'image-chat-main')
/** 手动触发Popover显示 */
const infoPopover = ref(false)
const { removeTag } = useCommon()
const {
handleScroll,
@ -258,13 +273,18 @@ const {
modalShow,
userId,
specialMenuList,
itemComputed
itemComputed,
optionsList,
report,
selectKey,
emojiList
} = useChatMain(activeItem)
const { handlePopoverUpdate } = usePopover(selectKey, 'image-chat-main')
// // TextBody
// const textBody = {
// content: '123',
// reply: {
// /* */
// /** */
// },
// urlContentMap: {
// // URL
@ -279,7 +299,7 @@ const {
// body: textBody, // TextBody
// sendTime: Date.now(), //
// messageMark: {
// /* MessageMarkType */
// /** MessageMarkType */
// }
// })
// const message = computed(() => msg.value)
@ -289,12 +309,37 @@ watchEffect(() => {
activeItemRef.value = { ...activeItem }
})
/* 处理回复消息中的 AIT 标签 */
/** 取消回复emoji表情 */
const cancelReplyEmoji = (item: any, index: number) => {
// item.emojiListcount11count-1
if (item.emojiList[index].count === 1) {
item.emojiList.splice(index, 1)
} else {
item.emojiList[index].count--
}
}
/** 处理emoji表情回应 */
const handleEmojiSelect = (label: string, item: any) => {
if (!item.emojiList) {
item.emojiList = [{ label: label, count: 1 }]
} else {
// label+1
const index = item.emojiList.findIndex((item: any) => item.label === label)
if (index > -1) {
item.emojiList[index].count++
} else {
item.emojiList.push({ label: label, count: 1 })
}
}
}
/** 处理回复消息中的 AIT 标签 */
const handleReply = (content: string) => {
return content.includes('id="aitSpan"') ? removeTag(content) : content
}
/* 发送信息 */
/** 发送信息 */
const handleSendMessage = (msg: any) => {
nextTick(() => {
//
@ -311,10 +356,10 @@ const handleSendMessage = (msg: any) => {
}
const index = items.value.length > 0 ? items.value[items.value.length - 1].key : 0
items.value.push({
value: '我',
value: login.value.accountInfo.name,
key: index + 1,
accountId: userId.value,
avatar: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
accountId: login.value.accountInfo.uid,
avatar: login.value.accountInfo.avatar,
content: msg.content,
type: msg.type,
reply: msg.type === MsgEnum.REPLY ? msg.reply : null
@ -323,7 +368,7 @@ const handleSendMessage = (msg: any) => {
})
}
/* 跳转到回复消息 */
/** 跳转到回复消息 */
const jumpToReplyMsg = (key: number) => {
nextTick(() => {
virtualListInst.value?.scrollTo({ key: key })
@ -336,13 +381,13 @@ const jumpToReplyMsg = (key: number) => {
* @param index 下标
* @param id 用户ID
*/
const addToDomUpdateQueue = (index: number, id: number) => {
const addToDomUpdateQueue = (index: number, id: string) => {
// 使 nextTick
nextTick(() => {
if (!floatFooter.value || id === userId.value) {
virtualListInst.value?.scrollTo({ position: 'bottom', debounce: true })
}
/* data-key标识的气泡,添加前缀用于区分用户消息,不然气泡动画会被覆盖 */
/** data-key标识的气泡,添加前缀用于区分用户消息,不然气泡动画会被覆盖 */
const dataKey = id === userId.value ? `U${index + 1}` : `Q${index + 1}`
const lastMessageElement = document.querySelector(`[data-key="${dataKey}"]`) as HTMLElement
if (lastMessageElement) {
@ -358,7 +403,7 @@ const addToDomUpdateQueue = (index: number, id: number) => {
})
}
/* 点击后滚动到底部 */
/** 点击后滚动到底部 */
const scrollBottom = () => {
nextTick(() => {
virtualListInst.value?.scrollTo({ position: 'bottom', behavior: 'instant', debounce: true })
@ -370,7 +415,7 @@ const closeMenu = (event: any) => {
activeBubble.value = -1
}
if (!event.target.matches('.active-reply')) {
/* 解决更替交换回复气泡时候没有触发动画的问题 */
/** 解决更替交换回复气泡时候没有触发动画的问题 */
if (!event.target.matches('.reply-bubble *')) {
nextTick(() => {
const activeReplyElement = document.querySelector('.active-reply') as HTMLElement
@ -387,7 +432,7 @@ const closeMenu = (event: any) => {
}
onMounted(() => {
/*! 启动图标闪烁 需要设置"resources": ["sec-tauri/图标放置的文件夹"]*/
/**! 启动图标闪烁 需要设置"resources": ["sec-tauri/图标放置的文件夹"]*/
invoke('tray_blink', {
isRun: true,
ms: 500,
@ -396,9 +441,14 @@ onMounted(() => {
}).catch((error) => {
console.error('设置图标失败:', error)
})
Mitt.on(MittEnum.SEND_MESSAGE, (event) => {
Mitt.on(MittEnum.SEND_MESSAGE, (event: any) => {
handleSendMessage(event)
})
Mitt.on(`${MittEnum.INFO_POPOVER}-Main`, (event: any) => {
selectKey.value = event
infoPopover.value = true
handlePopoverUpdate(event)
})
Mitt.on(MittEnum.MSG_BOX_SHOW, (event: any) => {
activeItemRef.value = event.item
})

View File

@ -35,11 +35,12 @@
<n-popover
@update:show="handlePopoverUpdate(item.key)"
trigger="click"
placement="left-start"
placement="left"
:show-arrow="false"
v-model:show="infoPopover"
style="padding: 0; background: var(--bg-info); backdrop-filter: blur(10px)">
<template #trigger>
<ContextMenu @select="$event.click(item)" :menu="optionsList" :special-menu="report">
<ContextMenu @select="$event.click(item, 'Sidebar')" :menu="optionsList" :special-menu="report">
<n-flex @click="selectKey = item.key" :key="item.key" :size="10" align="center" class="item">
<n-avatar
lazy
@ -57,30 +58,31 @@
</ContextMenu>
</template>
<!-- 用户个人信息框 -->
<InfoPopover :info="item" />
<InfoPopover v-if="selectKey === item.key" :info="item" />
</n-popover>
</template>
</n-virtual-list>
</main>
</template>
<script setup lang="ts">
import { RoomTypeEnum } from '@/enums'
import { MittEnum, RoomTypeEnum } from '@/enums'
import { MockItem } from '@/services/types.ts'
import { MockList } from '@/mock'
import { InputInst } from 'naive-ui'
import { optionsList, report } from './config.ts'
import { usePopover } from '@/hooks/usePopover.ts'
/* 当前点击的用户的key */
const selectKey = ref()
const isSearch = ref(false)
const searchRef = ref('')
const inputInstRef = ref<InputInst | null>(null)
const { handlePopoverUpdate } = usePopover(selectKey, 'image-chat-sidebar')
import { useChatMain } from '@/hooks/useChatMain.ts'
import Mitt from '@/utils/Bus.ts'
const { activeItem } = defineProps<{
activeItem: MockItem
}>()
const isSearch = ref(false)
const searchRef = ref('')
/** 手动触发Popover显示 */
const infoPopover = ref(false)
const inputInstRef = ref<InputInst | null>(null)
const { optionsList, report, selectKey } = useChatMain(activeItem)
const { handlePopoverUpdate } = usePopover(selectKey, 'image-chat-sidebar')
const handleSearch = () => {
isSearch.value = !isSearch.value
@ -88,6 +90,14 @@ const handleSearch = () => {
inputInstRef.value?.select()
})
}
onMounted(() => {
Mitt.on(`${MittEnum.INFO_POPOVER}-Sidebar`, (event: any) => {
selectKey.value = event
infoPopover.value = true
handlePopoverUpdate(event)
})
})
</script>
<style scoped lang="scss">

View File

@ -1,36 +0,0 @@
// TODO config文件做简单的操作配置如果需求复杂就封装成hooks (nyh -> 2024-03-23 03:35:05)
/* 右键用户信息菜单(单聊的时候显示) */
const optionsList = ref([
{
label: '发送信息',
icon: 'message-action',
click: (item: any) => {
console.log(item)
}
},
{
label: 'TA',
icon: 'aite',
click: () => {}
},
{
label: '查看资料',
icon: 'notes',
click: () => {}
},
{
label: '添加好友',
icon: 'people-plus',
click: () => {}
}
])
/* 举报选项 */
const report = ref([
{
label: '举报',
icon: 'caution',
click: () => {}
}
])
export { optionsList, report }

View File

@ -31,7 +31,7 @@ listen(appWindow.label, (e) => {
})
</script>
<style scoped lang="scss">
/*! 修改naive-ui虚拟列表滚动条的间距 */
/**! 修改naive-ui虚拟列表滚动条的间距 */
:deep(
.n-scrollbar > .n-scrollbar-rail.n-scrollbar-rail--vertical,
.n-scrollbar + .n-scrollbar-rail.n-scrollbar-rail--vertical

View File

@ -128,7 +128,7 @@ const chooseEmoji = (item: string) => {
</script>
<style lang="scss">
/*! 修改naive-ui滚动条的间距 */
/**! 修改naive-ui滚动条的间距 */
.n-scrollbar > .n-scrollbar-rail.n-scrollbar-rail--vertical,
.n-scrollbar + .n-scrollbar-rail.n-scrollbar-rail--vertical {
right: 0;

View File

@ -3,7 +3,7 @@
*/
// 表情
const expressionEmojis =
'😀😄😁😆😅🤣😂🙂🙃😉😊😇🥰😍🤩😘😗😚😙🥲😋😛😜🤪😝🤑🤗🤭🤫🤔🤐🤨😐😑😶😏😒🙄😬🤥😌😔😪🤤😴😷🤒🤕🤢🤮🤧🥵🥶🥴😵🤯🤠🥳🥸😎🤓🧐😕😟🙁😮😯😲😳🥺😦😧😨😰😥😢😭😱😖😣😞😓😩😫🥱😤😡😠🤬😈👿💀💩🤡👹👺👻'
'😀😄😁😆😅🤣😂🙂🙃😉😊😇🫡🫥😶‍🥰😍🤩😘😗😚😙🥲😋😛😜🤪😝🤑🤗🤭🤫🤔🤐🤨😐😑😶😏😒🙄😬🤥😌😔😪🤤😴😷🤒🤕🤢🤮🤧🥵🥶🥴😵🤯🤠🥳🥸😎🤓🧐😕😟🙁😮😯😲😳🥺😦😧😨😰😥😢😭😱😖😣😞😓😩😫🥱😤😡😠🤬😈👿💀💩🤡👹👺👻'
// 小动物
const animalEmojis =

View File

@ -5,12 +5,10 @@
<div v-if="topWinLabel !== void 0" @click="handleAlwaysOnTop" class="hover-box">
<n-popover trigger="hover">
<template #trigger>
<svg
v-if="alwaysOnTopStatus"
class="w-14px h-14px color-[--action-bar-icon-color] outline-none cursor-pointer">
<svg v-if="alwaysOnTopStatus" class="size-14px color-[--action-bar-icon-color] outline-none cursor-pointer">
<use href="#onTop"></use>
</svg>
<svg v-else class="w-16px h-16px color-[--action-bar-icon-color] outline-none cursor-pointer">
<svg v-else class="size-16px color-[--action-bar-icon-color] outline-none cursor-pointer">
<use href="#notOnTop"></use>
</svg>
</template>
@ -20,47 +18,47 @@
</div>
<!-- 收缩页面 -->
<div v-if="shrink" @click="shrinkWindow" class="hover-box">
<svg class="w-16px h-16px color-[--action-bar-icon-color] cursor-pointer"><use href="#left-bar"></use></svg>
<svg class="size-16px color-[--action-bar-icon-color] cursor-pointer"><use href="#left-bar"></use></svg>
</div>
<!-- 最小化 -->
<div v-if="minW" @click="appWindow.minimize()" class="hover-box">
<svg class="w-24px h-24px color-[--action-bar-icon-color] opacity-66 cursor-pointer">
<svg class="size-24px color-[--action-bar-icon-color] opacity-66 cursor-pointer">
<use href="#maximize"></use>
</svg>
</div>
<!-- 最大化 -->
<div v-if="maxW" @click="restoreWindow" class="hover-box">
<svg v-show="!windowMaximized" class="w-18px h-18px color-[--action-bar-icon-color] cursor-pointer">
<svg v-show="!windowMaximized" class="size-18px color-[--action-bar-icon-color] cursor-pointer">
<use href="#rectangle-small"></use>
</svg>
<svg v-show="windowMaximized" class="w-16px h-16px color-[--action-bar-icon-color] cursor-pointer">
<svg v-show="windowMaximized" class="size-16px color-[--action-bar-icon-color] cursor-pointer">
<use href="#internal-reduction"></use>
</svg>
</div>
<!-- 关闭窗口 -->
<div v-if="closeW" @click="handleCloseWin" class="action-close">
<svg class="w-14px h-14px color-[--action-bar-icon-color] cursor-pointer">
<svg class="size-14px color-[--action-bar-icon-color] cursor-pointer">
<use href="#close"></use>
</svg>
</div>
<!-- 是否退到托盘提示框 -->
<n-modal v-if="!tips.notTips" v-model:show="tipsRef.show" class="border-rd-8px">
<n-modal v-if="!tips.notTips" v-model:show="tipsRef.show" class="rounded-8px">
<div class="bg-[--bg-popover] w-290px h-full p-6px box-border flex flex-col">
<svg @click="tipsRef.show = false" class="w-12px h-12px ml-a cursor-pointer select-none">
<svg @click="tipsRef.show = false" class="size-12px ml-a cursor-pointer select-none">
<use href="#close"></use>
</svg>
<n-flex vertical :size="20" class="p-[22px_10px_10px_22px] select-none">
<span class="text-16px">最小化还是直接退出程序?</span>
<label class="text-14px text-#707070 flex gap-6px lh-16px items-center">
<label class="text-(14px #707070) flex gap-6px lh-16px items-center">
<n-radio :checked="tipsRef.type === CloseBxEnum.HIDE" @change="tipsRef.type = CloseBxEnum.HIDE" />
<span>最小化到系统托盘</span>
</label>
<label class="text-14px text-#707070 flex gap-6px lh-16px items-center">
<label class="text-(14px #707070) flex gap-6px lh-16px items-center">
<n-radio :checked="tipsRef.type === CloseBxEnum.CLOSE" @change="tipsRef.type = CloseBxEnum.CLOSE" />
<span>直接退出程序</span>
</label>
<label class="text-12px text-#909090 flex gap-6px justify-end items-center">
<label class="text-(12px #909090) flex gap-6px justify-end items-center">
<n-checkbox size="small" v-model:checked="tipsRef.notTips" />
<span>下次不出现此提示</span>
</label>
@ -133,7 +131,7 @@ watchEffect(() => {
appWindow.setAlwaysOnTop(alwaysOnTopStatus.value as boolean)
}
listen(EventEnum.LOGOUT, async () => {
/* 退出账号前把窗口全部关闭 */
/** 退出账号前把窗口全部关闭 */
if (appWindow.label !== 'login') {
await appWindow.close()
}
@ -149,7 +147,7 @@ watchEffect(() => {
}
})
/* 恢复窗口大小 */
/** 恢复窗口大小 */
const restoreWindow = async () => {
if (windowMaximized.value) {
await appWindow.unmaximize()
@ -158,9 +156,9 @@ const restoreWindow = async () => {
}
}
/* 收缩窗口 */
/** 收缩窗口 */
const shrinkWindow = async () => {
/*使用mitt给兄弟组件更新*/
/**使用mitt给兄弟组件更新*/
Mitt.emit(MittEnum.SHRINK_WINDOW, shrinkStatus.value)
if (shrinkStatus.value) {
await resizeWindow('home', 310, 700)
@ -169,7 +167,7 @@ const shrinkWindow = async () => {
}
}
/* 设置窗口置顶 */
/** 设置窗口置顶 */
const handleAlwaysOnTop = async () => {
if (topWinLabel.value !== void 0) {
const isTop = !alwaysOnTopStatus.value
@ -178,7 +176,7 @@ const handleAlwaysOnTop = async () => {
}
}
/* 点击确定时 */
/** 点击确定时 */
const handleConfirm = async () => {
tips.value.type = tipsRef.type
tips.value.notTips = tipsRef.notTips
@ -192,7 +190,7 @@ const handleConfirm = async () => {
}
}
/* 监听是否按下esc */
/** 监听是否按下esc */
const isEsc = (e: PersistedStateOptions) => {
// esc
if (e.key === 'Escape' && escClose.value) {
@ -207,7 +205,7 @@ const handleResize = () => {
})
}
/* 处理关闭窗口事件 */
/** 处理关闭窗口事件 */
const handleCloseWin = async () => {
if (appWindow.label === 'home') {
if (!tips.value.notTips) {

View File

@ -3,7 +3,7 @@ const DURATION = 300 // 持续时间
const map = new WeakMap() // 弱引用映射
/* 创建观察器 */
/** 创建观察器 */
const ob = new IntersectionObserver((entries: any) => {
for (const entry of entries) {
if (entry.isIntersecting) {

View File

@ -58,8 +58,10 @@ export enum MittEnum {
DETAILS_SHOW = 'detailsShow',
/** 消息列表被清空或者暂无消息 */
NOT_MSG = 'notMsg',
/* 回复消息 */
REPLY_MEG = 'replyMeg'
/** 回复消息 */
REPLY_MEG = 'replyMeg',
/** 手动触发InfoPopover */
INFO_POPOVER = 'infoPopover'
}
/** 主题类型 */
@ -72,7 +74,7 @@ export enum ThemeEnum {
OS = 'os'
}
/* pinia存储的名称 */
/** pinia存储的名称 */
export enum StoresEnum {
/** 置顶 */
ALWAYS_ON_TOP = 'alwaysOnTop',
@ -129,3 +131,21 @@ export enum LimitEnum {
/** 通用限制数量 */
COM_COUNT = 5
}
/** ws请求类型 */
export enum WsReqEnum {
/** 请求登录二维码 */
LOGIN = 1,
/** 心跳包 */
HEARTBEAT = 2,
/** 登录认证 */
AUTHORIZE = 3
}
/** ws响应类型 */
export enum WsResEnum {
/** 二维码登录 */
QRCODE_LOGIN = 'qrcodeLogin',
/** 登录成功 */
LOGIN_SUCCESS = 'loginSuccess'
}

View File

@ -2,31 +2,37 @@ import { useCommon } from '@/hooks/useCommon.ts'
import { MittEnum, MsgEnum } from '@/enums'
import { MockItem } from '@/services/types.ts'
import Mitt from '@/utils/Bus.ts'
import { setting } from '@/stores/setting.ts'
import { storeToRefs } from 'pinia'
export const useChatMain = (activeItem: MockItem) => {
const { removeTag } = useCommon()
/* 选中的气泡消息 */
const settingStore = setting()
const { login } = storeToRefs(settingStore)
/** 选中的气泡消息 */
const activeBubble = ref(-1)
/* 当前登录的用户id */
const userId = ref(10086)
/* 提醒框标题 */
/** 当前登录的用户id */
const userId = ref(login.value.accountInfo.uid)
/** 提醒框标题 */
const tips = ref()
/* 是否显示删除信息的弹窗 */
/** 是否显示删除信息的弹窗 */
const modalShow = ref(false)
/* 需要删除信息的下标 */
/** 需要删除信息的下标 */
const delIndex = ref(0)
/* 悬浮的页脚 */
/** 悬浮的页脚 */
const floatFooter = ref(false)
/* 记录历史消息下标 */
/** 记录历史消息下标 */
const historyIndex = ref(0)
/* 新消息数 */
/** 新消息数 */
const newMsgNum = ref(0)
/* 计算出触发页脚后的历史消息下标 */
/** 当前点击的用户的key */
const selectKey = ref()
/** 计算出触发页脚后的历史消息下标 */
const itemComputed = computed(() => {
return items.value.filter((item) => item.accountId !== userId.value).length
})
/*! 模拟信息列表 */
/**! 模拟信息列表 */
const items = ref(
Array.from({ length: 5 }, (_, i) => ({
value: `${i}安老师`,
@ -45,7 +51,7 @@ export const useChatMain = (activeItem: MockItem) => {
}))
)
/* 通用右键菜单 */
/** 通用右键菜单 */
const commonMenuList = ref<OPT.RightMenu[]>([
{
label: '转发',
@ -61,7 +67,7 @@ export const useChatMain = (activeItem: MockItem) => {
}
}
])
/* 右键消息菜单列表 */
/** 右键消息菜单列表 */
const menuList = ref<OPT.RightMenu[]>([
{
label: '复制',
@ -73,7 +79,7 @@ export const useChatMain = (activeItem: MockItem) => {
},
...commonMenuList.value
])
/* 右键菜单下划线后的列表 */
/** 右键菜单下划线后的列表 */
const specialMenuList = ref<OPT.RightMenu[]>([
{
label: '删除',
@ -85,7 +91,7 @@ export const useChatMain = (activeItem: MockItem) => {
}
}
])
/* 文件类型右键菜单 */
/** 文件类型右键菜单 */
const fileMenuList = ref<OPT.RightMenu[]>([
{
label: '预览',
@ -104,7 +110,7 @@ export const useChatMain = (activeItem: MockItem) => {
}
}
])
/* 图片类型右键菜单 */
/** 图片类型右键菜单 */
const imageMenuList = ref<OPT.RightMenu[]>([
{
label: '添加到表情',
@ -131,6 +137,56 @@ export const useChatMain = (activeItem: MockItem) => {
}
}
])
/** 右键用户信息菜单(群聊的时候显示) */
const optionsList = ref([
{
label: '发送信息',
icon: 'message-action',
click: (item: any) => {
console.log(item)
}
},
{
label: 'TA',
icon: 'aite',
click: () => {}
},
{
label: '查看资料',
icon: 'notes',
click: (item: any, type: string) => {
Mitt.emit(`${MittEnum.INFO_POPOVER}-${type}`, item.key)
}
},
{
label: '添加好友',
icon: 'people-plus',
click: () => {}
}
])
/** 举报选项 */
const report = ref([
{
label: '举报',
icon: 'caution',
click: () => {}
}
])
/** emoji表情菜单 */
const emojiList = ref([
{
label: '👍'
},
{
label: '😆'
},
{
label: '🥳'
},
{
label: '🤯'
}
])
/**
*
@ -162,7 +218,7 @@ export const useChatMain = (activeItem: MockItem) => {
}
}
/* 处理滚动事件(用于页脚显示功能) */
/** 处理滚动事件(用于页脚显示功能) */
const handleScroll = (e: Event) => {
const target = e.target as HTMLElement
// 获取已滚动的距离,即从顶部到当前滚动位置的距离
@ -193,7 +249,7 @@ export const useChatMain = (activeItem: MockItem) => {
return type === MsgEnum.IMAGE ? imageMenuList.value : type === MsgEnum.FILE ? fileMenuList.value : menuList.value
}
/* 删除信息事件 */
/** 删除信息事件 */
const handleConfirm = () => {
// 根据key找到items中对应的下标
const index = items.value.findIndex((item) => item.key === delIndex.value)
@ -201,7 +257,7 @@ export const useChatMain = (activeItem: MockItem) => {
modalShow.value = false
}
/* 点击气泡消息时候监听用户是否按下ctrl+c来复制内容 */
/** 点击气泡消息时候监听用户是否按下ctrl+c来复制内容 */
const handleMsgClick = (item: any) => {
activeBubble.value = item.key
// 启用键盘监听
@ -231,6 +287,10 @@ export const useChatMain = (activeItem: MockItem) => {
modalShow,
userId,
specialMenuList,
itemComputed
itemComputed,
optionsList,
report,
selectKey,
emojiList
}
}

View File

@ -1,10 +1,11 @@
import { LimitEnum, MsgEnum } from '@/enums'
import { Ref } from 'vue'
import { createFileOrVideoDom } from '@/utils/CreateDom.ts'
import { RegExp } from '@/utils/RegExp.ts'
/** 常用工具类 */
export const useCommon = () => {
/* 回复消息 */
/** 回复消息 */
const reply = ref({
accountName: '',
content: '',
@ -152,6 +153,15 @@ export const useCommon = () => {
min-width: 0;
`
let contentBox
const { hyperlinkRegex, foundHyperlinks } = RegExp.isHyperlink(content)
// 判断是否包含超链接
if (foundHyperlinks && foundHyperlinks.length > 0) {
content.replace(hyperlinkRegex, (match: string) => {
reply.value.content = match.startsWith('www.') ? 'https://' + match : match
})
// 去掉content中的标签
content = removeTag(content)
}
// 判断content内容是否是data:image/开头的数组
if (Array.isArray(content)) {
// 获取总共有多少张图片
@ -330,7 +340,7 @@ export const useCommon = () => {
}
}
/* 去除字符串中的元素标记 */
/** 去除字符串中的元素标记 */
const removeTag = (fragment: any) => new DOMParser().parseFromString(fragment, 'text/html').body.textContent || ''
return {

View File

@ -1,6 +1,12 @@
import type { Ref } from 'vue'
export const useContextMenu = (containerRef: Ref) => {
/**
*
* @param containerRef
* @param isNull
*/
export const useContextMenu = (containerRef: Ref, isNull?: Ref<boolean>) => {
const showMenu = ref(false)
const x = ref(0)
const y = ref(0)
@ -8,7 +14,7 @@ export const useContextMenu = (containerRef: Ref) => {
// 禁止滚动的默认行为
const preventDefault = (e: Event) => e.preventDefault()
/*! 解决使用n-virtual-list时右键菜单出现还可以滚动的问题 */
/**! 解决使用n-virtual-list时右键菜单出现还可以滚动的问题 */
const handleVirtualListScroll = (isBan: boolean) => {
const scrollbar_main = document.querySelector('#image-chat-main') as HTMLElement
const scrollbar_sidebar = document.querySelector('#image-chat-sidebar') as HTMLElement
@ -20,6 +26,7 @@ export const useContextMenu = (containerRef: Ref) => {
const handleContextMenu = (e: MouseEvent) => {
e.preventDefault()
e.stopPropagation()
if (isNull?.value) return
handleVirtualListScroll(true)
showMenu.value = true
x.value = e.clientX
@ -28,7 +35,7 @@ export const useContextMenu = (containerRef: Ref) => {
}
const closeMenu = (event: any) => {
/* 需要判断点击如果不是.context-menu类的元素的时候menu才会关闭 */
/** 需要判断点击如果不是.context-menu类的元素的时候menu才会关闭 */
if (!event.target.matches('.context-menu, .context-menu *')) {
handleVirtualListScroll(false)
showMenu.value = false

18
src/hooks/useLogin.ts Normal file
View File

@ -0,0 +1,18 @@
import { emit } from '@tauri-apps/api/event'
import { invoke } from '@tauri-apps/api/tauri'
export const useLogin = () => {
/**
* ()
*/
const setLoginState = async () => {
await emit('login_success')
await invoke('set_main_icon').catch((error) => {
console.error('设置主要图标失败:', error)
})
}
return {
setLoginState
}
}

View File

@ -10,24 +10,24 @@ import { setting } from '@/stores/setting.ts'
import { storeToRefs } from 'pinia'
const { createWebviewWindow, checkWinExist } = useWindow()
/* 建议把此状态存入localStorage中 */
/** 建议把此状态存入localStorage中 */
const activeIndex = ref(-1)
const msgBoxShow = ref(false)
/* 独立窗口的集合 */
/** 独立窗口的集合 */
const aloneWin = ref(new Set())
const shrinkStatus = ref(false)
const itemRef = ref({} as MockItem)
export const useMessage = () => {
const settingStore = setting()
const { chat } = storeToRefs(settingStore)
/* 监听独立窗口关闭事件 */
/** 监听独立窗口关闭事件 */
watchEffect(() => {
Mitt.on(MittEnum.SHRINK_WINDOW, async (event) => {
shrinkStatus.value = event as boolean
})
})
/* 处理点击选中消息 */
/** 处理点击选中消息 */
const handleMsgClick = (item: MockItem) => {
msgBoxShow.value = true
activeIndex.value = item.key
@ -45,7 +45,7 @@ export const useMessage = () => {
}
}
/* 处理双击事件 */
/** 处理双击事件 */
const handleMsgDblclick = (item: MockItem) => {
if (!chat.value.isDouble) return
delay(async () => {
@ -53,7 +53,7 @@ export const useMessage = () => {
}, 300)
}
/* 打开独立窗口 */
/** 打开独立窗口 */
const openAloneWin = async (item: MockItem) => {
itemRef.value = { ...item }
if (activeIndex.value === item.key) {

View File

@ -7,6 +7,7 @@ import { useDebounceFn } from '@vueuse/core'
import Mitt from '@/utils/Bus.ts'
import { MockList } from '@/mock'
import { useCommon } from './useCommon.ts'
import { RegExp } from '@/utils/RegExp.ts'
export const useMsgInput = (messageInputDom: Ref) => {
const { triggerInputEvent, insertNode, getMessageContentType, getEditorRange, imgPaste, removeTag, reply } =
@ -16,9 +17,9 @@ export const useMsgInput = (messageInputDom: Ref) => {
const chatKey = ref(chat.value.sendKey)
const msgInput = ref('')
const ait = ref(false)
/* 艾特后的关键字的key */
/** 艾特后的关键字的key */
const aitKey = ref('')
/* 是否正在输入拼音 */
/** 是否正在输入拼音 */
const isChinese = ref(false)
// 记录编辑器光标的位置
const editorRange = ref<{ range: Range; selection: Selection } | null>(null)
@ -30,9 +31,9 @@ export const useMsgInput = (messageInputDom: Ref) => {
return MockList.value
}
})
/* 记录当前选中的提及项 key */
/** 记录当前选中的提及项 key */
const selectedAitKey = ref(filteredList.value[0]?.key ?? null)
/* 右键菜单列表 */
/** 右键菜单列表 */
const menuList = ref([
{ label: '剪切', icon: 'screenshot', disabled: true },
{ label: '复制', icon: 'copy', disabled: true },
@ -79,16 +80,16 @@ export const useMsgInput = (messageInputDom: Ref) => {
})
onMounted(() => {
/* 正在输入拼音时触发 */
/** 正在输入拼音时触发 */
messageInputDom.value.addEventListener('compositionstart', () => {
isChinese.value = true
})
/* 结束输入拼音时触发 */
/** 结束输入拼音时触发 */
messageInputDom.value.addEventListener('compositionend', (e: CompositionEvent) => {
isChinese.value = false
aitKey.value = e.data
})
/* 监听回复信息的传递 */
/** 监听回复信息的传递 */
Mitt.on(MittEnum.REPLY_MEG, (event: any) => {
if (reply.value.content) {
// TODO 如果已经有就替换原来的内容 (nyh -> 2024-04-18 23:10:56)
@ -105,7 +106,7 @@ export const useMsgInput = (messageInputDom: Ref) => {
})
})
/* 处理发送信息事件 */
/** 处理发送信息事件 */
// TODO 输入框中的内容当我切换消息的时候需要记录之前输入框的内容 (nyh -> 2024-03-01 07:03:43)
const send = () => {
// 判断输入框中的图片或者文件数量是否超过限制
@ -120,10 +121,7 @@ export const useMsgInput = (messageInputDom: Ref) => {
content: msgInput.value,
reply: contentType === MsgEnum.REPLY ? reply.value : null
}
const hyperlinkRegex = /(\b(?:https?:\/\/|www)[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi
const foundHyperlinks = msg.content.match(hyperlinkRegex)
/* 如果是Reply消息需要将消息的样式修改 */
/** 如果是Reply消息需要将消息的样式修改 */
if (msg.type === MsgEnum.REPLY) {
// 先去掉原来的标签
msg.content = removeTag(msg.content)
@ -131,7 +129,8 @@ export const useMsgInput = (messageInputDom: Ref) => {
// TODO 不允许用户删除回复消息中最前面的空格或者标志符号 (nyh -> 2024-04-17 06:39:22)
msg.content = msg.content.replace(/^[\S\s]*\u00A0/, '')
}
/* 判断是否有超链接 */
const { hyperlinkRegex, foundHyperlinks } = RegExp.isHyperlink(msg.content)
/** 判断是否有超链接 */
if (foundHyperlinks && foundHyperlinks.length > 0) {
msg.content = msg.content.replace(hyperlinkRegex, (match) => {
const href = match.startsWith('www.') ? 'https://' + match : match
@ -154,32 +153,32 @@ export const useMsgInput = (messageInputDom: Ref) => {
reply.value = { imgCount: 0, accountName: '', content: '', key: '' }
}
/* 当输入框手动输入值的时候触发input事件(使用vueUse的防抖) */
/** 当输入框手动输入值的时候触发input事件(使用vueUse的防抖) */
const handleInput = useDebounceFn(async (e: Event) => {
const inputElement = e.target as HTMLInputElement
msgInput.value = inputElement.innerHTML
const { range, selection } = getEditorRange()!
/* 获取当前光标所在的节点和文本内容 */
/** 获取当前光标所在的节点和文本内容 */
if (!range || !selection) {
ait.value = false
return
}
/* 获取当前光标所在的节点 */
/** 获取当前光标所在的节点 */
const curNode = range.endContainer
/* 判断当前节点是否是文本节点 */
/** 判断当前节点是否是文本节点 */
if (!curNode || !curNode.textContent || curNode.nodeName !== '#text') {
ait.value = false
return
}
const searchStr = curNode.textContent?.slice(0, selection.focusOffset)
/* 使用正则表达式匹配@符号之后的关键词 */
/** 使用正则表达式匹配@符号之后的关键词 */
const keywords = /@([^@]*)$/.exec(searchStr!)
if (!keywords || keywords.length < 2) {
ait.value = false
aitKey.value = ''
return
}
/* 解构关键词并更新ait和aitKey的值同时将编辑器的范围和选择保存在editorRange中 */
/** 解构关键词并更新ait和aitKey的值同时将编辑器的范围和选择保存在editorRange中 */
const [, keyWord] = keywords
ait.value = true
aitKey.value = keyWord
@ -198,7 +197,7 @@ export const useMsgInput = (messageInputDom: Ref) => {
}
}, 100)
/* input的keydown事件 */
/** input的keydown事件 */
const inputKeyDown = (e: KeyboardEvent) => {
if (msgInput.value === '' || msgInput.value.trim() === '' || ait.value) {
e?.preventDefault()
@ -213,24 +212,24 @@ export const useMsgInput = (messageInputDom: Ref) => {
}
}
/* 处理点击@提及框事件 */
/** 处理点击@提及框事件 */
const handleAit = (item: MockItem) => {
const myEditorRange = editorRange?.value?.range
/* 获取光标所在位置的文本节点 */
/** 获取光标所在位置的文本节点 */
const textNode = myEditorRange?.endContainer
if (!textNode) return
/* 获取光标在所在文本节点中的偏移位置 */
/** 获取光标在所在文本节点中的偏移位置 */
const endOffset = myEditorRange?.endOffset
/* 获取文本节点的值,并将其转换为字符串类型 */
/** 获取文本节点的值,并将其转换为字符串类型 */
const textNodeValue = textNode?.nodeValue as string
/* 使用正则表达式匹配@符号之后获取到的文本节点的值 */
/** 使用正则表达式匹配@符号之后获取到的文本节点的值 */
const expRes = /@([^@]*)$/.exec(textNodeValue)
// 重新聚焦输入框(聚焦到输入框开头)
messageInputDom.value.focus()
const { range } = getEditorRange()!
/* 设置范围的起始位置为文本节点中@符号的位置 */
/** 设置范围的起始位置为文本节点中@符号的位置 */
range?.setStart(textNode, <number>expRes?.index)
/* 设置范围的结束位置为光标的位置 */
/** 设置范围的结束位置为光标的位置 */
range?.setEnd(textNode, endOffset!)
insertNode(MsgEnum.AIT, item.accountName)
triggerInputEvent(messageInputDom.value)

View File

@ -1,8 +1,8 @@
import { Ref } from 'vue'
/*! 这个是暂时用来解决在n-scrollbar中使用n-virtual-list使用n-popover时候滚动出现原生滚动条的方法 */
/**! 这个是暂时用来解决在n-scrollbar中使用n-virtual-list使用n-popover时候滚动出现原生滚动条的方法 */
export const usePopover = (selectKey: Ref<number>, id: string) => {
/*! 暂时使用这些方法来阻止popover显示时候的滚动行为 */
/**! 暂时使用这些方法来阻止popover显示时候的滚动行为 */
// 禁止滚动的默认行为
const preventDefault = (e: Event) => e.preventDefault()
const close = (event: any) => {
@ -32,7 +32,7 @@ export const usePopover = (selectKey: Ref<number>, id: string) => {
onUnmounted(() => {
window.removeEventListener('click', close, true)
})
/*! end */
/**! end */
return {
handlePopoverUpdate

View File

@ -1,7 +1,7 @@
const vw = ref(document.documentElement.clientWidth)
const vh = ref(document.documentElement.clientHeight)
/* 获取视口的宽高 */
/** 获取视口的宽高 */
export const useViewport = () => {
window.addEventListener('resize', () => {
vw.value = document.documentElement.clientWidth

View File

@ -26,7 +26,7 @@ export const useWindow = () => {
minH = 540
) => {
const checkLabel = computed(() => {
/* 如果是打开独立窗口就截取label中的固定label名称 */
/** 如果是打开独立窗口就截取label中的固定label名称 */
if (label.includes('alone')) {
return label.replace(/\d/g, '')
} else {

View File

@ -65,7 +65,7 @@ Mitt.on(MittEnum.SHRINK_WINDOW, (event) => {
const closeMenu = (event: Event) => {
const e = event.target as HTMLInputElement
const route = router.currentRoute.value.path
/* 判断如果点击的搜索框,就关闭消息列表 */
/** 判断如果点击的搜索框,就关闭消息列表 */
if (!e.matches('#search, #search *, #centerList *, #centerList') && route === '/searchDetails') {
router.go(-1)
}

View File

@ -12,5 +12,5 @@
bottom: 0;
width: 2px;
cursor: ew-resize;
background-color: #ccc; /* 可以根据需要更改颜色 */
background-color: #ccc; /** 可以根据需要更改颜色 */
}

View File

@ -13,7 +13,7 @@ import Right from './right/index.vue'
import Mitt from '@/utils/Bus'
import { MittEnum } from '@/enums'
/* todo home窗口创建的时候已经设置了resizable: true,可以调整大小了,但是还是不可以调整大小 */
/** todo home窗口创建的时候已经设置了resizable: true,可以调整大小了,但是还是不可以调整大小 */
const shrinkStatus = ref(false)
/**
* event默认如果没有传递值就为true所以shrinkStatus的值为false就会发生值的变化

View File

@ -45,7 +45,7 @@ const itemsBottom: OPT.L.Bottom[] = [
iconAction: 'collect-action'
}
]
/* 设置列表菜单项 */
/** 设置列表菜单项 */
const moreList = ref<OPT.L.MoreList[]>([
{
label: '检查更新',
@ -77,13 +77,13 @@ const moreList = ref<OPT.L.MoreList[]>([
click: async () => {
// todo 退出账号 需要关闭其他的全部窗口
await createWebviewWindow('登录', 'login', 320, 448, 'home', true, false, 320, 448).then(() => {
/* 给一点延迟,不然创建登录窗口后还没有来得及设置阴影和圆角效果 */
/** 给一点延迟,不然创建登录窗口后还没有来得及设置阴影和圆角效果 */
delay(async () => {
/* 如果图标在闪烁则先暂停闪烁 */
/** 如果图标在闪烁则先暂停闪烁 */
await invoke('tray_blink', { isRun: false }).catch((error) => {
console.error('暂停闪烁失败:', error)
})
/* 通知全部打开的窗口然后关闭 */
/** 通知全部打开的窗口然后关闭 */
await emit(EventEnum.LOGOUT)
await emit('logout_success')
}, 300)

View File

@ -9,13 +9,12 @@
style="padding: 0; background: rgba(255, 255, 255, 0.2); backdrop-filter: blur(10px)">
<template #trigger>
<!-- 头像 -->
<div class="relative w-36px h-36px rounded-50% cursor-pointer">
<n-avatar round :color="'#fff'" :size="36" :src="'https://picsum.photos/140'" fallback-src="/logo.png" />
<div class="relative size-36px rounded-50% cursor-pointer">
<n-avatar round :color="'#fff'" :size="36" :src="login.accountInfo.avatar" fallback-src="/logo.png" />
<div
@click.stop="openContent('在线状态', 'onlineStatus', 320, 480)"
class="bg-[--bg-avatar] text-10px rounded-50% w-12px h-12px absolute bottom--2px right--2px"
style="border: 2px solid var(--bg-avatar)">
class="bg-[--bg-avatar] text-10px rounded-50% size-12px absolute bottom--2px right--2px border-(2px solid [--bg-avatar])">
<img class="rounded-50% size-full" :src="url" alt="" />
</div>
</div>
@ -29,25 +28,24 @@
<!-- 头像以及信息区域 -->
<n-flex justify="space-between" align="center" :size="25">
<n-flex>
<img class="w-68px h-68px rounded-50% select-none" :src="'https://picsum.photos/140'" alt="" />
<img class="size-68px rounded-50% select-none" :src="login.accountInfo.avatar" alt="" />
<n-flex vertical justify="center" :size="10" class="text-[--text-color]">
<span class="text-18px">用户名</span>
<span class="text-12px text-[--info-text-color]">账号 763868126381</span>
<span class="text-18px">{{ login.accountInfo.name }}</span>
<span class="text-(12px [--info-text-color])">账号 {{ login.accountInfo.uid }}</span>
<n-flex
@click="openContent('在线状态', 'onlineStatus', 320, 480)"
:size="5"
align="center"
style="margin-left: -4px"
class="item-hover">
<img class="rounded-50% w-18px h-18px" :src="url" alt="" />
class="item-hover ml--4px">
<img class="rounded-50% size-18px" :src="url" alt="" />
<span>{{ title }}</span>
</n-flex>
</n-flex>
</n-flex>
<n-flex vertical align="center" :size="5" class="item-hover">
<svg class="w-20px h-20px"><use href="#thumbs-up"></use></svg>
<svg class="size-20px"><use href="#thumbs-up"></use></svg>
<span class="text-12px">9999+</span>
</n-flex>
</n-flex>
@ -90,7 +88,7 @@
openWindowsList.has(item.url) ? 'p-[6px_8px] color-#13987f' : 'top-action'
]">
<n-badge :value="item.badge" :max="99">
<svg class="w-22px h-22px">
<svg class="size-22px">
<use
:href="`#${activeUrl === item.url || openWindowsList.has(item.url) ? item.iconAction : item.icon}`"></use>
</svg>
@ -105,14 +103,14 @@
:key="index"
@click="openContent(item.title, item.label)"
:class="openWindowsList.has(item.url.substring(1)) ? 'p-[6px_8px] color-#13987f' : 'bottom-action'">
<svg class="w-22px h-22px">
<svg class="size-22px">
<use :href="`#${openWindowsList.has(item.url.substring(1)) ? item.iconAction : item.icon}`"></use>
</svg>
</div>
<svg
@click="settingShow = !settingShow"
class="more w-22px h-22px relative"
class="more size-22px relative"
:class="{ 'color-#13987f': settingShow }">
<use :href="settingShow ? '#hamburger-button-action' : '#hamburger-button'"></use>
</svg>
@ -147,19 +145,19 @@ import { setting } from '@/stores/setting.ts'
const prefers = matchMedia('(prefers-color-scheme: dark)')
const { createWebviewWindow } = useWindow()
const settingStore = setting()
const { themes } = storeToRefs(settingStore)
const { themes, login } = storeToRefs(settingStore)
const OLStatusStore = onlineStatus()
const { url, title, bgColor } = storeToRefs(OLStatusStore)
/*当前选中的元素 默认选中itemsTop的第一项*/
/**当前选中的元素 默认选中itemsTop的第一项*/
const activeUrl = ref<string>(itemsTop.value[0].url)
const settingShow = ref(false)
const shrinkStatus = ref(false)
const infoShow = ref(false)
const themeColor = ref(themes.value.content === ThemeEnum.DARK ? 'rgba(63,63,63, 0.2)' : 'rgba(241,241,241, 0.2)')
/* 已打开窗口的列表 */
/** 已打开窗口的列表 */
const openWindowsList = ref(new Set())
/* 跟随系统主题模式切换主题 */
/** 跟随系统主题模式切换主题 */
const followOS = () => {
themeColor.value = prefers.matches ? 'rgba(63,63,63, 0.2)' : 'rgba(241,241,241, 0.2)'
}
@ -175,7 +173,7 @@ watchEffect(() => {
Mitt.on(MittEnum.TO_SEND_MSG, (event: any) => {
activeUrl.value = event.url
})
/* 判断是否是跟随系统主题 */
/** 判断是否是跟随系统主题 */
if (themes.value.pattern === ThemeEnum.OS) {
followOS()
prefers.addEventListener('change', followOS)
@ -221,7 +219,7 @@ const closeMenu = (event: any) => {
}
onMounted(async () => {
/* 页面加载的时候默认显示消息列表 */
/** 页面加载的时候默认显示消息列表 */
pageJumps(activeUrl.value)
window.addEventListener('click', closeMenu, true)

View File

@ -38,7 +38,7 @@ const isDetails = computed(() => {
return router.currentRoute.value.path.includes('/friendsList')
})
/* 跟随系统主题模式切换主题 */
/** 跟随系统主题模式切换主题 */
const followOS = () => {
imgTheme.value = prefers.matches ? ThemeEnum.DARK : ThemeEnum.LIGHT
}

View File

@ -25,19 +25,19 @@ export const MockList = ref<MockItem[]>(
key: i,
avatar: `${avatars}?${i}`,
type: type,
accountId: i,
accountId: `${i}`,
accountName: generateRandomString(Math.floor(Math.random() * 10) + 1, type)
}
})
)
/* 模拟照片墙 */
/** 模拟照片墙 */
const imgList = Array.from({ length: 10 }, (_, i) => {
return {
url: `${avatars}?${i}`
}
})
/* 模拟动态内容 */
/** 模拟动态内容 */
export const dynamicList = Array.from({ length: 10 }, (_, i) => {
return {
id: i,

View File

@ -1,7 +1,7 @@
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import { Dynamic, Mail, OnlineStatus, Tray, FriendsList } from './noLazyRouter.ts'
/*! 创建窗口后再跳转页面就会导致样式没有生效所以不能使用懒加载路由的方式,有些页面需要快速响应的就不需要懒加载 */
/**! 创建窗口后再跳转页面就会导致样式没有生效所以不能使用懒加载路由的方式,有些页面需要快速响应的就不需要懒加载 */
const { BASE_URL } = import.meta.env
const routes: Array<RouteRecordRaw> = [
{

View File

@ -11,10 +11,10 @@ const POST = <T>(url: string, params?: any) => request.post<T, Response>(url, pa
// const DELETE = <T>(url: string, params?: any) => request.delete<T, Response>(url, params)
export default {
/*登录 请求*/
/**登录 请求*/
login: (form: login): Promise<Response> => POST(urls.login, form),
/*退出 请求*/
/**退出 请求*/
logout: (): Promise<Response> => GET(urls.logout),
/*获取文章列表*/
/**获取文章列表*/
getArticlePage: (params: parameter): Promise<Response> => GET(urls.articlePage, { params })
}

View File

@ -5,7 +5,7 @@
**/
import { MsgEnum, RCodeEnum, RoomTypeEnum } from '@/enums'
/*响应请求体*/
/**响应请求体*/
export type Response = {
code: RCodeEnum
msg: string
@ -17,19 +17,19 @@ export type Response = {
success: boolean
version: string
}
/*分页搜索*/
/**分页搜索*/
export type parameter = {
pageNum: number
pageSize: number
name: string
}
/*登录类型*/
/**登录类型*/
export type login = {
username: string
password: string
}
/* ===================================================== */
/** ===================================================== */
/** 回复类型 */
export type ReplyType = {
id: number
@ -146,11 +146,11 @@ export type CacheUserItem = {
/** uid */
uid: number
}
/*! 模拟信息数据的类型 */
/**! 模拟信息数据的类型 */
export type MockItem = {
key: number
type: RoomTypeEnum
avatar: string
accountId: number
accountId: string
accountName: string
}

View File

@ -1,10 +1,34 @@
import { WsReqEnum, WsResEnum } from '@/enums'
import Mitt from '@/utils/Bus.ts'
const { VITE_WEBSOCKET_URL } = import.meta.env
/** websocket连接对象 */
let ws: WebSocket
/** 初始化websocket连接 */
const initWebSocket = () => {
ws = new WebSocket(`${VITE_WEBSOCKET_URL}/`)
ws.onopen = () => {
sendToServer({ type: 1 })
// 发送心跳
setInterval(() => {
sendToServer({
type: WsReqEnum.HEARTBEAT
})
}, 1000 * 60)
}
// 监听服务器返回的消息
ws.onmessage = (event: MessageEvent) => {
const data: Record<string, any> = JSON.parse(event.data)
switch (data.type) {
case WsReqEnum.LOGIN:
Mitt.emit(WsResEnum.QRCODE_LOGIN, data)
break
case WsReqEnum.HEARTBEAT:
break
case WsReqEnum.AUTHORIZE:
Mitt.emit(WsResEnum.LOGIN_SUCCESS, data)
break
}
}
// websocket出错重连
@ -24,4 +48,4 @@ const sendToServer = (data: Record<string, any>) => {
ws.send(json)
}
export { initWebSocket }
export { initWebSocket, sendToServer }

View File

@ -12,7 +12,7 @@ export const onlineStatus = defineStore(StoresEnum.ONLINE_STATUS, {
}),
actions: {
init() {
/* 随机获取一个状态 */
/** 随机获取一个状态 */
const index = Math.floor(Math.random() * statusItem.length)
const { url, title } = statusItem[index]
this.title = title

View File

@ -4,46 +4,47 @@ import { CloseBxEnum, StoresEnum, ThemeEnum } from '@/enums'
// TODO 使用indexDB或者把配置写出到文件中还需要根据每个账号来进行配置 (nyh -> 2024-03-26 01:22:12)
export const setting = defineStore(StoresEnum.SETTING, {
state: (): STO.Setting => ({
/* 主题设置 */
/** 主题设置 */
themes: {
content: '',
pattern: ''
},
/* 是否启用ESC关闭窗口 */
/** 是否启用ESC关闭窗口 */
escClose: true,
/* 系统托盘 */
/** 系统托盘 */
tips: {
type: CloseBxEnum.HIDE,
notTips: false
},
/* 登录设置 */
/** 登录设置 */
login: {
autoLogin: false,
autoStartup: false,
/* 用户保存的登录信息 */
/** 用户保存的登录信息 */
accountInfo: {
account: '',
password: '',
name: '',
avatar: ''
avatar: '',
uid: ''
}
},
/* 聊天设置 */
/** 聊天设置 */
chat: {
/* 发送快捷键 */
/** 发送快捷键 */
sendKey: 'Enter',
/* 是否双击打开独立会话窗口 */
/** 是否双击打开独立会话窗口 */
isDouble: true
}
}),
actions: {
/* 初始化主题 */
/** 初始化主题 */
initTheme(theme: string) {
this.themes.content = theme
document.documentElement.dataset.theme = theme
this.themes.pattern = theme
},
/* 切换主题 */
/** 切换主题 */
toggleTheme(theme: string) {
if (theme === ThemeEnum.OS) {
this.themes.pattern = theme
@ -56,18 +57,18 @@ export const setting = defineStore(StoresEnum.SETTING, {
this.themes.pattern = theme
}
},
/* 切换登录设置 */
/** 切换登录设置 */
toggleLogin(autoLogin: boolean, autoStartup: boolean) {
this.login.autoLogin = autoLogin
this.login.autoStartup = autoStartup
},
/* 设置用户保存的登录信息 */
/** 设置用户保存的登录信息 */
setAccountInfo(accountInfo: STO.Setting['login']['accountInfo']) {
this.login.accountInfo = accountInfo
},
/* 清空账号信息 */
/** 清空账号信息 */
clearAccount() {
this.login.accountInfo = { account: '', avatar: '', name: '', password: '' }
this.login.accountInfo = { account: '', avatar: '', name: '', password: '', uid: '' }
}
},
share: {

View File

@ -53,7 +53,7 @@
@apply flex flex-col absolute top-44px right-0 bg-[--bg-chat-drawer] p-22px box-border w-320px h-100vh shadow-[0_14px_14px_rgba(0,0,0,0.35)];
}
/*! 使用vue内置transition做过渡效果 */
/**! 使用vue内置transition做过渡效果 */
// 进入动画的生效状态
.sidebar-enter-active {
animation: slideIn 0.3s ease-in-out;
@ -62,4 +62,4 @@
.sidebar-leave-active {
animation: slideOut 0.3s ease-in-out;
}
/*!end */
/**!end */

View File

@ -1,7 +1,7 @@
/* 气泡样式 */
/** 气泡样式 */
@mixin bubble {
@apply w-fit max-w-35vw min-h-1em p-[8px_12px] text-15px line-height-22px bg-[--bg-bubble] rounded-[2px_18px_18px];
word-break: break-all; /* 强制连续文本换行 */
word-break: break-all; /** 强制连续文本换行 */
&.active {
background-color: var(--bg-bubble-active);
color: var(--text-color);
@ -22,7 +22,7 @@
@apply rounded-[18px_2px_18px_18px] color-#fff;
background-color: var(--bg-active-msg);
}
/*! 气泡动画 */
/**! 气泡动画 */
.bubble-animation {
animation: bubble-twinkle 0.4s ease-out forwards;
}
@ -46,11 +46,11 @@
background-color: rgb(195, 221, 216);
}
}
/* 浮标达到最大值时候的样式 */
/** 浮标达到最大值时候的样式 */
.max:hover {
background-color: #f5dce1;
}
/* 回复气泡的样式 */
/** 回复气泡的样式 */
.reply-bubble {
@apply text-12px text-[--reply-color] bg-[--bg-reply-bubble] rounded-8px p-4px cursor-pointer select-none;
svg,
@ -72,26 +72,30 @@
overflow: hidden;
text-overflow: ellipsis;
}
/* 回复图片的计数器样式 */
/** 回复图片的计数器样式 */
.reply-img-sub {
@apply absolute bottom-8px right-6px color-#13987f bg-[--bg-reply-img-count] p-[2px_4px] rounded-6px text-10px;
}
}
/* 跳转到回复内容时候显示的样式 */
/** emoji回复气泡的样式 */
.emoji-reply-bubble {
@apply relative rounded-8px p-[4px_8px] cursor-pointer select-none bg-#13987F66 text-14px w-fit;
}
/** 跳转到回复内容时候显示的样式 */
.active-reply {
background-color: var(--bg-reply-active);
border-radius: 8px;
margin: 0 8px;
transition: all 0.4s ease-in-out;
}
/* 跳转到回复内容时候的退出动画 */
/** 跳转到回复内容时候的退出动画 */
.reply-exit {
margin: 0;
background-color: inherit;
border-radius: 0;
transition: all 0.4s ease-in-out;
}
/*! 替换ait的字体颜色 */
/**! 替换ait的字体颜色 */
:deep(#aitSpan) {
@apply text-inherit cursor-pointer;
}

View File

@ -12,7 +12,7 @@
}
}
}
/*! 修改naive-ui虚拟列表滚动条的宽度 */
/**! 修改naive-ui虚拟列表滚动条的宽度 */
:deep(
.n-scrollbar > .n-scrollbar-rail.n-scrollbar-rail--vertical,
.n-scrollbar + .n-scrollbar-rail.n-scrollbar-rail--vertical

View File

@ -1,4 +1,4 @@
/*! 可以根据自己的喜好搭配渐变网址https://webgradients.com/ */
/**! 可以根据自己的喜好搭配渐变网址https://webgradients.com/ */
.login-box {
background-color: #d8eee2;
background-image:

View File

@ -1,4 +1,4 @@
/*! 主题变量 */
/**! 主题变量 */
:root {
// 整体布局背景
--left-bg-color: rgba(255, 255, 255, 0.6);
@ -122,7 +122,7 @@ html[data-theme='dark'] {
--reply-hover: #b1b1b1;
--bg-reply-img-count: #505050;
}
/*! end */
/**! end */
// 线性动画
@keyframes linearAnimation {
0% {
@ -164,7 +164,7 @@ html[data-theme='dark'] {
transform: scale(1);
}
}
/*!修改naive-ui的遮罩层样式*/
/**!修改naive-ui的遮罩层样式*/
@mixin mask-style($blur: 4px) {
position: fixed;
left: 0;
@ -180,21 +180,21 @@ html[data-theme='dark'] {
.n-drawer-mask {
@include mask-style(2px);
}
/*!end*/
/**!end*/
/*! 修改naive-ui复选框的样式 */
/**! 修改naive-ui复选框的样式 */
:deep(.n-checkbox .n-checkbox-box) {
border-radius: 50%;
width: 16px;
height: 16px;
}
/*! 修改naive-ui滚动条的间距 */
/**! 修改naive-ui滚动条的间距 */
:deep(.n-scrollbar > .n-scrollbar-rail.n-scrollbar-rail--vertical, .n-scrollbar + .n-scrollbar-rail.n-scrollbar-rail--vertical) {
right: 0;
}
/*! 修改naive-ui Popselect选中的样式 */
/**! 修改naive-ui Popselect选中的样式 */
.n-base-select-menu .n-base-select-option.n-base-select-option--selected {
color: #13987f;
}
@ -202,7 +202,7 @@ html[data-theme='dark'] {
color: #13987f;
}
/*! 通用菜单项目样式 */
/**! 通用菜单项目样式 */
@mixin menu-item-style($position: fixed) {
position: $position;
background: var(--bg-menu);
@ -225,7 +225,7 @@ html[data-theme='dark'] {
overflow: hidden;
}
/*! 通用菜单项样式 */
/**! 通用菜单项样式 */
@mixin menu-list() {
padding: 5px;
display: flex;

View File

@ -1,4 +1,4 @@
/* 修改自定义的关闭和最小化图标的样式 */
/** 修改自定义的关闭和最小化图标的样式 */
:deep(.hover-box) {
@apply w-28px h24px flex-center hover:bg-#e7e7e7;
svg {
@ -10,12 +10,12 @@
color: #404040;
}
}
/* 改变输入框中的位置 */
/** 改变输入框中的位置 */
:deep(.n-input .n-input__input, .n-input .n-input__textarea) {
margin-left: 22px;
}
/* 隐藏naive UI的滚动条 */
/** 隐藏naive UI的滚动条 */
:deep(
.n-scrollbar > .n-scrollbar-rail.n-scrollbar-rail--vertical > .n-scrollbar-rail__scrollbar,
.n-scrollbar + .n-scrollbar-rail.n-scrollbar-rail--vertical > .n-scrollbar-rail__scrollbar

View File

@ -27,7 +27,7 @@
}
}
/*! TransitionGroup过渡样式 */
/**! TransitionGroup过渡样式 */
.fade-move,
.fade-enter-active,
.fade-leave-active {
@ -43,7 +43,7 @@
.fade-leave-active {
position: absolute;
}
/*! end */
/**! end */
:deep(.n-badge .n-badge-sup) {
font-weight: bold;

View File

@ -1,25 +1,24 @@
#message-input {
padding: 4px 24px 4px 4px; /* 输入框内填充 */
font-size: 14px; /* 字体大小 */
color: inherit; /* 继承颜色 */
cursor: text; /* 文本输入光标 */
resize: none; /* 不允许调整尺寸 */
background: none; /* 无背景 */
border: none; /* 边框样式,可根据需求修改 */
border-radius: 0; /* 边框圆角 */
outline: none; /* 点击时无轮廓 */
min-height: 90px; /* 最小高度 */
line-height: 20px; /* 行高 */
overflow: auto; /* 内容过多时允许滚动 */
flex: 1; /* 弹性盒自适应填充可用空间 */
caret-color: #13987f; /* 光标颜色,可根据需求调整 */
white-space: pre-wrap; /* 保留空白符号并正常换行 */
word-break: break-word; /* 在长单词或URL地址内部进行换行 */
padding: 4px 24px 4px 4px; /** 输入框内填充 */
font-size: 14px; /** 字体大小 */
color: inherit; /** 继承颜色 */
cursor: text; /** 文本输入光标 */
resize: none; /** 不允许调整尺寸 */
background: none; /** 无背景 */
border: none; /** 边框样式,可根据需求修改 */
border-radius: 0; /** 边框圆角 */
outline: none; /** 点击时无轮廓 */
min-height: 90px; /** 最小高度 */
line-height: 20px; /** 行高 */
overflow: auto; /** 内容过多时允许滚动 */
flex: 1; /** 弹性盒自适应填充可用空间 */
caret-color: #13987f; /** 光标颜色,可根据需求调整 */
white-space: pre-wrap; /** 保留空白符号并正常换行 */
word-break: break-word; /** 在长单词或URL地址内部进行换行 */
}
.ait {
@apply w-200px h-fit max-h-190px bg-[--center-bg-color] rounded-8px p-[5px_0_5px_5px];
@apply w-200px h-fit max-h-190px bg-[--center-bg-color] rounded-8px p-[5px_0_5px_5px] border-(1px solid [--box-shadow-color]);
box-shadow: 2px 2px 12px 2px var(--box-shadow-color);
border: 1px solid var(--box-shadow-color);
.ait-item {
@apply h-26px text-[--text-color] text-14px p-[5px_0_5px_10px] mr-5px rounded-6px cursor-pointer;
}

View File

@ -1,11 +1,11 @@
/*!使用比较新的View Transitions API来实现主图切换效果*/
/**!使用比较新的View Transitions API来实现主图切换效果*/
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
/* 进入dark模式和退出dark模式时两个图像的位置顺序正好相反 */
/** 进入dark模式和退出dark模式时两个图像的位置顺序正好相反 */
.dark::view-transition-old(root) {
z-index: 1;
}
@ -19,4 +19,4 @@
::view-transition-new(root) {
z-index: 1;
}
/*!end*/
/**!end*/

View File

@ -24,7 +24,6 @@ declare module 'vue' {
NAvatar: typeof import('naive-ui')['NAvatar']
NAvatarGroup: typeof import('naive-ui')['NAvatarGroup']
NBadge: typeof import('naive-ui')['NBadge']
NBlockquote: typeof import('naive-ui')['NBlockquote']
NButton: typeof import('naive-ui')['NButton']
NButtonGroup: typeof import('naive-ui')['NButtonGroup']
NCheckbox: typeof import('naive-ui')['NCheckbox']
@ -44,11 +43,14 @@ declare module 'vue' {
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NNumberAnimation: typeof import('naive-ui')['NNumberAnimation']
NPopover: typeof import('naive-ui')['NPopover']
NPopselect: typeof import('naive-ui')['NPopselect']
NQrCode: typeof import('naive-ui')['NQrCode']
NRadio: typeof import('naive-ui')['NRadio']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSkeleton: typeof import('naive-ui')['NSkeleton']
NSwitch: typeof import('naive-ui')['NSwitch']
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs']

View File

@ -64,7 +64,7 @@ declare module '*.vue' {
const Component: ReturnType<typeof defineComponent>
export default component
}
/*使 window.$message Message TypeScript $message
/**使 window.$message Message TypeScript $message
使 if (window.$message) */
declare interface Window {
$message: ReturnType<typeof useMessage>

View File

@ -6,7 +6,7 @@ interface Document {
startViewTransition?: (callback: () => Promise<void> | void) => ViewTransition
}
/* 通用类型 */
/** 通用类型 */
declare namespace Common {
/**
*
@ -14,7 +14,7 @@ declare namespace Common {
*/
type StrategyAction = [boolean, () => void]
/* 选项数据 */
/** 选项数据 */
type OptionWithKey<K> = { value: K; label: string }
}

View File

@ -1,8 +1,8 @@
/* 选项命名空间 */
/** 选项命名空间 */
declare namespace OPT {
/* 主页左侧选项 */
/** 主页左侧选项 */
namespace L {
/* 顶部的选项 */
/** 顶部的选项 */
type Top = {
url: string
icon: string
@ -10,7 +10,7 @@ declare namespace OPT {
badge?: number
}
/* 底部的选项 */
/** 底部的选项 */
type Bottom = {
title: string
url: string
@ -19,14 +19,14 @@ declare namespace OPT {
iconAction?: string
}
/* 更多的选项 */
/** 更多的选项 */
type MoreList = {
label: string
icon: string
click: () => void
}
/* 设置页面的侧边栏选项 */
/** 设置页面的侧边栏选项 */
type SettingSide = {
url: string
label: string
@ -34,21 +34,21 @@ declare namespace OPT {
}
}
/* 右键菜单选项 */
/** 右键菜单选项 */
type RightMenu = {
label: string
icon: string
click?: (...args: any[]) => void
}
/* 详情页选项 */
/** 详情页选项 */
type Details = {
url: string
title: string
click: (...args: any[]) => void
}
/* 在线状态 */
/** 在线状态 */
type Online = {
url: string
title: string

View File

@ -1,47 +1,48 @@
/* pinia的store的命名空间 */
/** pinia的store的命名空间 */
declare namespace STO {
/* 设置 */
/** 设置 */
type Setting = {
/* 主题设置 */
/** 主题设置 */
themes: {
content: string
pattern: string
}
/* 关闭提示 */
/** 关闭提示 */
tips: {
type: string
/* 不再显示提示 */
/** 不再显示提示 */
notTips: boolean
}
/* 是否启用ESC关闭窗口 */
/** 是否启用ESC关闭窗口 */
escClose: boolean
/* 登录设置 */
/** 登录设置 */
login: {
autoLogin: boolean
autoStartup: boolean
/* 用户保存的登录信息 */
/** 用户保存的登录信息 */
accountInfo: {
account: string
password: string
account?: string
password?: string
name: string
avatar: string
uid: string
}
}
/* 聊天设置 */
/** 聊天设置 */
chat: {
/* 发送快捷键 */
/** 发送快捷键 */
sendKey: string
/* 是否双击打开独立会话窗口 */
/** 是否双击打开独立会话窗口 */
isDouble: boolean
}
}
/* 置顶 */
/** 置顶 */
type AlwaysOnTop = {
[key: string]: boolean
}
/* 历史内容 */
/** 历史内容 */
type History = {
emoji: string[]
}

View File

@ -37,7 +37,7 @@ export const createFileOrVideoDom = (file: File) => {
roundRect(ctx, 0, 0, canvas.width, canvas.height, 8)
ctx.stroke()
/* 文本过长时截取并使用省略号代替 */
/** 文本过长时截取并使用省略号代替 */
function truncateText(text: string, maxWidth: number) {
const ellipsis = '...'
const ellipsisWidth = ctx.measureText(ellipsis).width
@ -68,7 +68,7 @@ export const createFileOrVideoDom = (file: File) => {
currentText = text.substring(0, left) + ellipsis + text.substring(text.length - left, text.length)
return currentText
}
/* 更新canvas的背景颜色和边框 */
/** 更新canvas的背景颜色和边框 */
function updateCanvasBackground(
canvas: HTMLCanvasElement,
ctx: CanvasRenderingContext2D,

View File

@ -31,11 +31,13 @@ export class RegExp {
}
/**
*
* @param val
*
* @param content
* @returns hyperlinkRegex foundHyperlinks
*/
public static isHyperlink(val: string): boolean {
const hyperlinkRegex = /^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- ./?%&=]*)?$/
return hyperlinkRegex.test(val)
public static isHyperlink(content: string) {
const hyperlinkRegex = /(\b(?:https?:\/\/|www)[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi
const foundHyperlinks = content.match(hyperlinkRegex)
return { hyperlinkRegex, foundHyperlinks }
}
}

15
src/utils/Worker.ts Normal file
View File

@ -0,0 +1,15 @@
// 在 Web Worker 中接收消息
self.onmessage = (event) => {
const number = event.data
// 执行一些耗时的操作
const result = calculateSquare(number)
// 将结果发送回主线程
self.postMessage(result)
}
// 一些耗时的操作
const calculateSquare = (number: any) => {
return number * number
}

View File

@ -14,14 +14,14 @@
class="w-450px h-fit rounded-8px bg-[--right-bg-color] p-10px box-border">
<n-flex align="center">
<!-- 用户的头像和用户名以及个签 -->
<img class="w-45px h-45px bg-#ccc rounded-50% select-none" :src="item.avatar" alt="" />
<img class="size-45px bg-#ccc rounded-50% select-none" :src="item.avatar" alt="" />
<n-flex vertical style="flex: 1">
<n-flex justify="space-between" align="center">
<label class="text-14px flex items-center gap-5px">
<span :class="item.isAuth ? 'text-#13987f' : ''">{{ item.user }}</span>
<n-popover trigger="hover" v-if="item.isAuth">
<template #trigger>
<svg class="w-20px h-20px color-#13987f select-none outline-none cursor-pointer">
<svg class="size-20px color-#13987f select-none outline-none cursor-pointer">
<use href="#auth"></use>
</svg>
</template>
@ -29,10 +29,10 @@
</n-popover>
</label>
<span class="text-12px color-#707070">发布于2021-01-01</span>
<span class="text-(12px #707070)">发布于2021-01-01</span>
</n-flex>
<span class="text-12px color-#707070">个性签名</span>
<span class="text-(12px #707070)">个性签名</span>
</n-flex>
<!-- 个签照片墙 -->

View File

@ -7,13 +7,13 @@
<ContextMenu @contextmenu="showMenu($event)" @select="handleSelect($event.label)" :menu="menuList">
<n-collapse-item title="我的设备" name="1">
<template #header-extra>
<p class="text-10px color-#707070">1/1</p>
<p class="text-(10px #707070)">1/1</p>
</template>
<div>可以</div>
</n-collapse-item>
<n-collapse-item title="特别关心" name="2">
<template #header-extra>
<p class="text-10px color-#707070">1/1</p>
<p class="text-(10px #707070)">1/1</p>
</template>
<!-- 用户框 多套一层div来移除默认的右键事件然后覆盖掉因为margin空隙而导致右键可用 -->
@ -26,11 +26,7 @@
v-for="item in friendsList"
:key="item.key">
<div class="flex items-center h-full pl-6px pr-8px gap-10px">
<img
class="w-44px h-44px rounded-50% bg-#fff"
style="border: 1px solid #f1f1f1"
:src="item.avatar"
alt="" />
<img class="size-44px rounded-50% bg-#fff border-(1px solid #f1f1f1)" :src="item.avatar" alt="" />
<div class="h-38px flex flex-1 flex-col justify-between">
<div class="text-14px flex-y-center gap-4px">
@ -38,9 +34,9 @@
</div>
<div
class="text w-155px h-14px font-size-12px flex-y-center gap-4px"
class="text w-155px h-14px text-12px flex-y-center gap-4px"
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
<p class="font-size-12px">[今日天气]</p>
<p class="text-12px">[今日天气]</p>
<p>说的很经典哈萨克的哈萨克看到贺卡上</p>
</div>
</div>
@ -50,7 +46,7 @@
</n-collapse-item>
<n-collapse-item title="默认分组" name="3">
<template #header-extra>
<p class="font-size-10px color-#707070">1/1</p>
<p class="text-(10px #707070)">1/1</p>
</template>
<div>123</div>
@ -68,15 +64,11 @@
:key="item.key">
<!-- 消息框使用v-slide自定义指令来自动抉择右键菜单位置 -->
<div v-slide class="flex items-center h-full pl-6px pr-8px gap-10px">
<img
class="w-44px h-44px rounded-50% bg-#fff"
style="border: 1px solid #f1f1f1"
:src="item.avatar"
alt="" />
<img class="size-44px rounded-50% bg-#fff border-(1px solid #f1f1f1)" :src="item.avatar" alt="" />
<div class="h-38px flex flex-1 flex-col justify-center">
<div class="flex-between-center">
<span class="font-size-14px">{{ item.accountName }}</span>
<span class="text-14px">{{ item.accountName }}</span>
</div>
</div>
</div>
@ -95,7 +87,7 @@ const menuList = ref([
{ label: '重命名该组', icon: 'edit' },
{ label: '删除分组', icon: 'delete' }
])
/* 建议把此状态存入localStorage中 */
/** 建议把此状态存入localStorage中 */
const activeItem = ref(0)
const detailsShow = ref(false)
const shrinkStatus = ref(false)
@ -103,7 +95,7 @@ const shrinkStatus = ref(false)
const friendsList = ref(MockList.value.filter((item) => item.type === 2))
const groupChatList = ref(MockList.value.filter((item) => item.type === 1))
/* 监听独立窗口关闭事件 */
/** 监听独立窗口关闭事件 */
watchEffect(() => {
Mitt.on(MittEnum.SHRINK_WINDOW, async (event) => {
shrinkStatus.value = event as boolean

View File

@ -15,7 +15,7 @@ appWindow.onCloseRequested(async (e) => {
await emit(EventEnum.WIN_CLOSE, e.windowLabel)
})
/*! 创建新窗口然后需要通信传递数据时候需要进行提交一次页面创建成功的事件,否则会接收不到数据 */
/**! 创建新窗口然后需要通信传递数据时候需要进行提交一次页面创建成功的事件,否则会接收不到数据 */
onMounted(async () => {
await emit(EventEnum.ALONE)
})

View File

@ -50,6 +50,7 @@
height="110px"
src="src/assets/img/no_data.svg"
lazy
preview-disabled
:intersection-observer-options="{
root: '#image-no-data'
}" />

View File

@ -6,9 +6,9 @@
<img class="w-220px h-100px" src="@/assets/logo/hula.png" alt="" />
<n-flex vertical align="center" :size="20">
<span class="text-15px text-#707070">版本{{ _pkg.version }}({{ osArch }})</span>
<span class="text-15px text-#707070">当前设备{{ osType }}{{ osVersion }}</span>
<n-flex vertical class="text-12px text-#909090" :size="8" align="center">
<span class="text-(15px #707070)">版本{{ _pkg.version }}({{ osArch }})</span>
<span class="text-(15px #707070)">当前设备{{ osType }}{{ osVersion }}</span>
<n-flex vertical class="text-(12px #909090)" :size="8" align="center">
<span>Copyright © 2023-2024 nongyehong</span>
<span>All Rights Reserved.</span>
</n-flex>

View File

@ -1,7 +1,7 @@
<template>
<n-flex vertical :size="40">
<!-- 通用设置 -->
<n-flex vertical class="text-[--text-color] text-14px" :size="16">
<n-flex vertical class="text-(14px [--text-color])" :size="16">
<span class="pl-10px">外观设置</span>
<n-flex align="center" :size="20" class="item">
<n-flex
@ -24,7 +24,7 @@
</n-flex>
<!-- 系统设置 -->
<n-flex vertical class="text-[--text-color] text-14px" :size="16">
<n-flex vertical class="text-(14px [--text-color])" :size="16">
<span class="pl-10px">系统</span>
<n-flex class="item" :size="15" vertical>
@ -32,16 +32,16 @@
<n-flex align="center" justify="space-between">
<span>关闭主面板</span>
<label class="text-14px text-#707070 flex gap-6px lh-16px items-center">
<label class="text-(14px #707070) flex gap-6px lh-16px items-center">
<n-radio :checked="tips.type === CloseBxEnum.HIDE" @change="tips.type = CloseBxEnum.HIDE" />
<span>最小化到系统托盘</span>
</label>
<label class="text-14px text-#707070 flex gap-6px lh-16px items-center">
<label class="text-(14px #707070) flex gap-6px lh-16px items-center">
<n-radio :checked="tips.type === CloseBxEnum.CLOSE" @change="tips.type = CloseBxEnum.CLOSE" />
<span>直接退出程序</span>
</label>
<label class="text-12px text-#909090 flex gap-6px justify-end items-center">
<label class="text-(12px #909090) flex gap-6px justify-end items-center">
<n-checkbox size="small" v-model:checked="tips.notTips" />
<span>是否关闭提示</span>
</label>
@ -59,7 +59,7 @@
</n-flex>
<!-- 聊天设置 -->
<n-flex vertical class="text-[--text-color] text-14px" :size="16">
<n-flex vertical class="text-(14px [--text-color])" :size="16">
<span class="pl-10px">聊天</span>
<n-flex class="item" :size="15" vertical>
@ -97,7 +97,7 @@ const settingStore = setting()
const { themes, tips, escClose, chat } = storeToRefs(settingStore)
const activeItem = ref<string>(themes.value.pattern)
/* 切换主题 */
/** 切换主题 */
const handleTheme = async (event: MouseEvent, code: string) => {
if (code === themes.value.pattern) return
const x = event.clientX
@ -107,7 +107,7 @@ const handleTheme = async (event: MouseEvent, code: string) => {
let isDark: boolean
settingStore.toggleTheme(code)
/*判断当前浏览器是否支持startViewTransition API*/
/**判断当前浏览器是否支持startViewTransition API*/
if (document.startViewTransition) {
const transition = document.startViewTransition(() => {
isDark = code.includes(ThemeEnum.DARK)

View File

@ -38,7 +38,7 @@ watchEffect(() => {
settingStore.toggleLogin(autoLogin.value, autoStartup.value)
})
/* 清空账号信息 */
/** 清空账号信息 */
const clearInfo = () => {
settingStore.clearAccount()
window.$message.success('密码已清空')

View File

@ -1,4 +1,4 @@
/* 侧边栏选项 */
/** 侧边栏选项 */
const sideOptions = ref<OPT.L.SettingSide[]>([
{
url: '/general',
@ -22,7 +22,7 @@ const sideOptions = ref<OPT.L.SettingSide[]>([
}
])
/* 发送按钮快捷键的选项 */
/** 发送按钮快捷键的选项 */
const sendOptions = [
{
label: '按 Enter 键发送消息',

View File

@ -26,7 +26,7 @@
import router from '@/router'
import { sideOptions } from './config.ts'
/*当前选中的元素 默认选中itemsTop的第一项*/
/**当前选中的元素 默认选中itemsTop的第一项*/
const activeItem = ref<string>(sideOptions.value[0].url)
const title = ref<string>(sideOptions.value[0].label)

View File

@ -43,13 +43,13 @@ import { storeToRefs } from 'pinia'
const OLStatusStore = onlineStatus()
const { url, title, bgColor } = storeToRefs(OLStatusStore)
/* 选中的状态 */
/** 选中的状态 */
const activeItem = reactive({
index: -1,
title: title.value,
url: url.value
})
/* 这里不写入activeItem中是因为v-bind要绑定的值是响应式的 */
/** 这里不写入activeItem中是因为v-bind要绑定的值是响应式的 */
const RGBA = ref(bgColor?.value)
watchEffect(() => {
@ -101,7 +101,7 @@ onMounted(async () => {
color: #404040;
}
}
/* 隐藏naive UI的滚动条 */
/** 隐藏naive UI的滚动条 */
:deep(
.n-scrollbar > .n-scrollbar-rail.n-scrollbar-rail--vertical > .n-scrollbar-rail__scrollbar,
.n-scrollbar + .n-scrollbar-rail.n-scrollbar-rail--vertical > .n-scrollbar-rail__scrollbar

View File

@ -9,11 +9,7 @@
<n-flex vertical :size="25" v-if="!isAutoLogin">
<!-- 头像 -->
<n-flex justify="center" class="w-full mt-35px">
<img
style="border: 2px solid #fff"
class="w-80px h-80px rounded-50% bg-#fff"
:src="avatarRef || '/logo.png'"
alt="" />
<img class="w-80px h-80px rounded-50% bg-#fff border-(2px solid #fff)" :src="avatarRef || '/logo.png'" alt="" />
</n-flex>
<!-- 登录菜单 -->
@ -105,8 +101,7 @@
<!-- 头像 -->
<n-flex justify="center">
<img
style="border: 2px solid #fff"
class="w-110px h-110px rounded-50% bg-#fff"
class="w-110px h-110px rounded-50% bg-#fff border-(2px solid #fff)"
:src="login.accountInfo.avatar || '/logo.png'"
alt="" />
</n-flex>
@ -152,7 +147,7 @@ import { lightTheme } from 'naive-ui'
import { setting } from '@/stores/setting.ts'
import { storeToRefs } from 'pinia'
import { invoke } from '@tauri-apps/api/tauri'
import { emit } from '@tauri-apps/api/event'
import { useLogin } from '@/hooks/useLogin.ts'
const settingStore = setting()
const { login } = storeToRefs(settingStore)
@ -165,36 +160,41 @@ const loginDisabled = ref(false)
const loading = ref(false)
const arrowStatus = ref(false)
const isAutoLogin = ref(false)
/* todo 模拟账号列表 */
const { setLoginState } = useLogin()
/** todo 模拟账号列表 */
const accountOption = ref<STO.Setting['login']['accountInfo'][]>([
{
account: 'hula',
password: '123456',
name: '超级GG帮',
avatar: 'https://picsum.photos/140?1'
avatar: 'https://picsum.photos/140?1',
uid: '123456'
},
{
account: 'hula1',
password: '123456',
name: '二狗子',
avatar: 'https://picsum.photos/140?2'
avatar: 'https://picsum.photos/140?2',
uid: '123456'
},
{
account: 'hula2',
password: '123456',
name: '李山离',
avatar: 'https://picsum.photos/140?3'
avatar: 'https://picsum.photos/140?3',
uid: '123456'
},
{
account: 'hula3',
password: '123456',
name: '牛什么呢',
avatar: 'https://picsum.photos/140?4'
avatar: 'https://picsum.photos/140?4',
uid: '123456'
}
])
const accountPH = ref('输入HuLa账号')
const passwordPH = ref('输入HuLa密码')
/* 登录按钮的文本内容 */
/** 登录按钮的文本内容 */
const loginText = ref('登录')
const { createWebviewWindow } = useWindow()
@ -206,7 +206,7 @@ watchEffect(() => {
}
})
/* 删除账号列表内容 */
/** 删除账号列表内容 */
const delAccount = (index: number) => {
//
if (index < 0 || index >= accountOption.value.length) return
@ -235,17 +235,7 @@ const giveAccount = (item: STO.Setting['login']['accountInfo']) => {
arrowStatus.value = false
}
/**
* 设置登录状态(系统托盘图标系统托盘菜单选项)
*/
const setLoginState = async () => {
await emit('login_success')
await invoke('set_main_icon').catch((error) => {
console.error('设置主要图标失败:', error)
})
}
/*登录后创建主页窗口*/
/**登录后创建主页窗口*/
const loginWin = () => {
loading.value = true
delay(async () => {
@ -256,14 +246,15 @@ const loginWin = () => {
account: accountRef.value,
password: passwordRef.value,
avatar: avatarRef.value,
name: nameRef.value
name: nameRef.value,
uid: '123456'
})
await setLoginState()
}
}, 1000)
}
/*监听是否点击了除了下拉框外的其他地方*/
/**监听是否点击了除了下拉框外的其他地方*/
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as Element
if (!target.matches('.account-box, .account-box *, .down')) {

View File

@ -13,13 +13,23 @@
<n-qr-code
v-else
:size="180"
class="rounded-12px"
class="rounded-12px relative"
:class="{ blur: scanSuccess }"
:value="QRCode"
icon-src="/logo.png"
error-correction-level="H" />
<n-flex
v-if="scanSuccess"
vertical
:size="12"
align="center"
class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<svg class="size-42px"><use href="#success"></use></svg>
<span class="text-(16px #e3e3e3)">扫码成功</span>
</n-flex>
</n-flex>
<n-flex justify="center" class="mt-15px text-14px color-#808080">{{ loadText }}</n-flex>
<n-flex justify="center" class="mt-15px text-(14px #808080)">{{ loadText }}</n-flex>
<!-- 顶部操作栏 -->
<n-flex justify="center" class="text-14px mt-48px">
@ -33,19 +43,47 @@
import router from '@/router'
import { delay } from 'lodash-es'
import { lightTheme } from 'naive-ui'
import { initWebSocket, sendToServer } from '@/services/webSocket.ts'
import { WsReqEnum, WsResEnum } from '@/enums'
import Mitt from '@/utils/Bus.ts'
import { setting } from '@/stores/setting.ts'
import { useLogin } from '@/hooks/useLogin.ts'
import { useWindow } from '@/hooks/useWindow.ts'
const settingStore = setting()
const { setLoginState } = useLogin()
const { createWebviewWindow } = useWindow()
const loading = ref(true)
const loadText = ref('加载中...')
const QRCode = ref('HuLa-IM-Tauri')
const QRCode = ref()
const scanSuccess = ref(false)
const toLogin = () => {
router.push('/login')
}
// TODO (nyh -> 2024-01-27 00:37:18)
onMounted(() => {
delay(() => {
initWebSocket()
Mitt.on(WsResEnum.QRCODE_LOGIN, (e: any) => {
QRCode.value = e.data.loginUrl
loading.value = false
loadText.value = '请使用微信扫码登录'
})
Mitt.on(WsResEnum.LOGIN_SUCCESS, (e: any) => {
scanSuccess.value = true
loadText.value = '登录中...'
delay(async () => {
await createWebviewWindow('HuLa', 'home', 960, 720, 'login', false, true)
settingStore.setAccountInfo({
avatar: e.data.avatar,
name: e.data.name,
uid: e.data.uid
})
await setLoginState()
}, 1000)
})
delay(() => {
sendToServer({ type: WsReqEnum.LOGIN })
}, 1000)
})
</script>

View File

@ -6,7 +6,7 @@
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
/** Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
@ -15,7 +15,7 @@
"jsx": "preserve",
"jsxImportSource": "vue",
/* Linting */
/** Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,

View File

@ -1,6 +1,7 @@
import { defineConfig } from '@unocss/vite'
import presetUno from '@unocss/preset-uno'
import transformerDirectives from '@unocss/transformer-directives'
import transformerDirectives from '@unocss/transformer-directives' // 设置指令
import transformerVariantGroup from '@unocss/transformer-variant-group' // 解决繁琐的多次写前缀的情况
export default defineConfig({
content: {
@ -9,7 +10,7 @@ export default defineConfig({
}
},
presets: [presetUno({ dark: 'class' })],
transformers: [transformerDirectives()],
transformers: [transformerDirectives(), transformerVariantGroup()],
/**
*
* @default '布局样式 - 水平样式 - 垂直样式'

View File

@ -9,7 +9,7 @@ import unocss from '@unocss/vite'
import terser from '@rollup/plugin-terser'
// https://vitejs.dev/config/
/*! 暂时不需要优化前端打包(如开启gzip这些tauri可能解析不了) */
/**! 不需要优化前端打包(如开启gzip) */
export default defineConfig(({ mode }: ConfigEnv) => {
// 获取当前环境的配置,如何设置第三个参数则加载所有变量而不是以“VITE_”前缀的变量
const config = loadEnv(mode, '/')
@ -46,13 +46,13 @@ export default defineConfig(({ mode }: ConfigEnv) => {
imports: ['vue', { 'naive-ui': ['useDialog', 'useMessage', 'useNotification', 'useLoadingBar'] }],
dts: 'src/typings/auto-imports.d.ts'
}),
/*自动导入组件但是不会自动导入jsx和tsx*/
/**自动导入组件但是不会自动导入jsx和tsx*/
Components({
dirs: ['src/components/**'], // 设置需要扫描的目录
resolvers: [NaiveUiResolver()],
dts: 'src/typings/components.d.ts'
}),
/* 压缩代码 */
/** 压缩代码 */
terser({
format: {
comments: false // 移除所有注释