!43 发布v2.5.3

Merge pull request !43 from nongyehong/dev
This commit is contained in:
nongyehong 2024-11-06 07:51:26 +00:00 committed by Gitee
commit 6e81821b46
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
49 changed files with 607 additions and 512 deletions

View File

@ -9,5 +9,6 @@ index.html
src/assets
.eslintrc.cjs
**/config
src-tauri/**
commitlint.config.cjs
auto-imports.d.ts

View File

@ -1,12 +1,26 @@
## [2.5.2](https://github.com/HuLaSpark/HuLa/compare/v2.5.1...v2.5.2) (2024-10-31)
## [2.5.3](https://github.com/HuLaSpark/HuLa/compare/v2.5.2...v2.5.3) (2024-11-06)
### 🐛 Bug Fixes | Bug 修复
* **build:** :bug: 升级wry版本修复mac安装报错问题 ([fefa2f9](https://github.com/HuLaSpark/HuLa/commit/fefa2f970305839064764cd1d82a0d8e557f3148))
* **component:** :bug: 修复聊天框内右键菜单问题 ([e59630b](https://github.com/HuLaSpark/HuLa/commit/e59630b70ed0d245174c97136d502bb63cac03ec))
* **component:** :bug: 修复输入框换行不兼容webkit的问题 ([345d830](https://github.com/HuLaSpark/HuLa/commit/345d83068711df087dd0ba403446c739151a11dd))
* **layout:** :bug: 修复聊天框改变宽度的时候可以选中文本的问题 ([56d79cc](https://github.com/HuLaSpark/HuLa/commit/56d79ccc8ba015a313eabcd938757f35d1d840a4))
* **layout:** :bug: 修复选择了图片不显示在输入框中的bug ([c7cdac6](https://github.com/HuLaSpark/HuLa/commit/c7cdac69ce6fa185489dcb480991e3a268fec99d))
* **service:** :bug: 修复请求接口bug ([f3723d4](https://github.com/HuLaSpark/HuLa/commit/f3723d4e5a2342314ce6e85931a49f1ddfecab0b))
### ⚡️ Performance Improvements | 性能优化
* **component:** :zap: 优化右键菜单功能 ([7b53029](https://github.com/HuLaSpark/HuLa/commit/7b530297ac37122ead00a15864e16a73a5547d04))
## [2.5.2](https://github.com/HuLaSpark/HuLa/compare/v2.5.1...v2.5.2) (2024-10-31)
### 🐛 Bug Fixes | Bug 修复
- **build:** :bug: 升级wry版本修复mac安装报错问题 ([fefa2f9](https://github.com/HuLaSpark/HuLa/commit/fefa2f970305839064764cd1d82a0d8e557f3148))
- **component:** :bug: 修复聊天框内右键菜单问题 ([e59630b](https://github.com/HuLaSpark/HuLa/commit/e59630b70ed0d245174c97136d502bb63cac03ec))
## [2.5.1](https://github.com/HuLaSpark/HuLa/compare/v2.5.0...v2.5.1) (2024-10-29)

View File

@ -1,7 +1,7 @@
{
"name": "hula",
"type": "module",
"version": "2.5.2",
"version": "2.5.3",
"license": "Apache-2.0",
"engines": {
"node": ">=18.12.0",
@ -44,7 +44,6 @@
"@tauri-apps/plugin-os": "2.0.0",
"@tauri-apps/plugin-process": "2.0.0",
"@tauri-apps/plugin-updater": "~2",
"axios": "^1.7.4",
"colorthief": "^2.4.0",
"dayjs": "^1.11.11",
"grapheme-splitter": "^1.0.4",
@ -52,7 +51,7 @@
"mitt": "^3.0.1",
"naive-ui": "^2.40.1",
"pinia": "^2.2.1",
"pinia-plugin-persistedstate": "^3.2.1",
"pinia-plugin-persistedstate": "^4.1.2",
"pinia-shared-state": "^0.5.1",
"vue": "^3.5.11",
"vue-draggable-plus": "^0.5.3",

View File

@ -32,9 +32,6 @@ importers:
'@tauri-apps/plugin-updater':
specifier: ~2
version: 2.0.0
axios:
specifier: ^1.7.4
version: 1.7.4
colorthief:
specifier: ^2.4.0
version: 2.4.0
@ -57,8 +54,8 @@ importers:
specifier: ^2.2.1
version: 2.2.1(typescript@5.6.2)(vue@3.5.11(typescript@5.6.2))
pinia-plugin-persistedstate:
specifier: ^3.2.1
version: 3.2.1(pinia@2.2.1(typescript@5.6.2)(vue@3.5.11(typescript@5.6.2)))
specifier: ^4.1.2
version: 4.1.2(pinia@2.2.1(typescript@5.6.2)(vue@3.5.11(typescript@5.6.2)))(rollup@4.24.3)(webpack-sources@3.2.3)
pinia-shared-state:
specifier: ^0.5.1
version: 0.5.1(pinia@2.2.1(typescript@5.6.2)(vue@3.5.11(typescript@5.6.2)))(vue@3.5.11(typescript@5.6.2))
@ -74,7 +71,7 @@ importers:
devDependencies:
'@babel/eslint-parser':
specifier: ^7.24.7
version: 7.25.1(@babel/core@7.25.2)(eslint@8.57.0)
version: 7.25.1(@babel/core@7.26.0)(eslint@8.57.0)
'@commitlint/cli':
specifier: ^19.3.0
version: 19.4.0(@types/node@20.14.15)(typescript@5.6.2)
@ -173,10 +170,10 @@ importers:
version: 5.6.2
unplugin-auto-import:
specifier: ^0.18.2
version: 0.18.2(@nuxt/kit@3.13.1(rollup@4.24.3)(webpack-sources@3.2.3))(@vueuse/core@10.11.1(vue@3.5.11(typescript@5.6.2)))(rollup@4.24.3)(webpack-sources@3.2.3)
version: 0.18.2(@nuxt/kit@3.14.0(rollup@4.24.3)(webpack-sources@3.2.3))(@vueuse/core@10.11.1(vue@3.5.11(typescript@5.6.2)))(rollup@4.24.3)(webpack-sources@3.2.3)
unplugin-vue-components:
specifier: ^0.27.4
version: 0.27.4(@babel/parser@7.26.2)(@nuxt/kit@3.13.1(rollup@4.24.3)(webpack-sources@3.2.3))(rollup@4.24.3)(vue@3.5.11(typescript@5.6.2))(webpack-sources@3.2.3)
version: 0.27.4(@babel/parser@7.26.2)(@nuxt/kit@3.14.0(rollup@4.24.3)(webpack-sources@3.2.3))(rollup@4.24.3)(vue@3.5.11(typescript@5.6.2))(webpack-sources@3.2.3)
vite:
specifier: 5.4.10
version: 5.4.10(@types/node@20.14.15)(sass@1.77.6)(terser@5.31.5)
@ -891,12 +888,12 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@nuxt/kit@3.13.1':
resolution: {integrity: sha512-FkUL349lp/3nVfTIyws4UDJ3d2jyv5Pk1DC1HQUCOkSloYYMdbRcQAUcb4fe2TCLNWvHM+FhU8jnzGTzjALZYA==}
'@nuxt/kit@3.14.0':
resolution: {integrity: sha512-Gl30WrzX7YSJqkTyOJlG4LkErShkGoHigWF/htFt9Q27Lx9JNCkOpXlEf+rA/vsDlXJeo8mVNRoMhS4Q+0d1Kg==}
engines: {node: ^14.18.0 || >=16.10.0}
'@nuxt/schema@3.13.1':
resolution: {integrity: sha512-ishbhzVGspjshG9AG0hYnKYY6LWXzCtua7OXV7C/DQ2yA7rRcy1xHpzKZUDbIRyxCHHCAcBd8jfHEUmEuhEPrA==}
'@nuxt/schema@3.14.0':
resolution: {integrity: sha512-uLAAS7Za7+JXJg6phAjUecqBUfON/WZN/NbYic7uCM+4LUT8B4M/5WM9zFCZJi1g9Krns5Wr5GmJJPIfaYt0eQ==}
engines: {node: ^14.18.0 || >=16.10.0}
'@octokit/auth-token@4.0.0':
@ -1660,9 +1657,6 @@ packages:
aws4@1.13.1:
resolution: {integrity: sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==}
axios@1.7.4:
resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@ -1732,10 +1726,10 @@ packages:
peerDependencies:
esbuild: '>=0.18'
c12@1.11.2:
resolution: {integrity: sha512-oBs8a4uvSDO9dm8b7OCFW7+dgtVrwmwnrVXYzLm43ta7ep2jCn/0MhoUFygIWtxhyy6+/MG7/agvpY0U1Iemew==}
c12@2.0.1:
resolution: {integrity: sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==}
peerDependencies:
magicast: ^0.3.4
magicast: ^0.3.5
peerDependenciesMeta:
magicast:
optional: true
@ -1763,8 +1757,8 @@ packages:
caniuse-lite@1.0.30001651:
resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==}
caniuse-lite@1.0.30001676:
resolution: {integrity: sha512-Qz6zwGCiPghQXGJvgQAem79esjitvJ+CxSbSQkW9H/UX5hg8XM88d4lp2W+MEQ81j+Hip58Il+jGVdazk1z9cw==}
caniuse-lite@1.0.30001677:
resolution: {integrity: sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==}
caseless@0.12.0:
resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
@ -1788,6 +1782,10 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
chokidar@4.0.1:
resolution: {integrity: sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==}
engines: {node: '>= 14.16.0'}
chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
@ -2115,6 +2113,9 @@ packages:
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
deep-pick-omit@1.2.1:
resolution: {integrity: sha512-2J6Kc/m3irCeqVG42T+SaUMesaK7oGWaedGnQQK/+O0gYc+2SP5bKh/KKTE7d7SJ+GCA9UUE1GRzh6oDe0EnGw==}
default-browser-id@5.0.0:
resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==}
engines: {node: '>=18'}
@ -2197,8 +2198,8 @@ packages:
ecc-jsbn@0.1.2:
resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==}
electron-to-chromium@1.5.49:
resolution: {integrity: sha512-ZXfs1Of8fDb6z7WEYZjXpgIRF6MEu8JdeGA0A40aZq6OQbS+eJpnnV49epZRna2DU/YsEjSQuGtQPPtvt6J65A==}
electron-to-chromium@1.5.52:
resolution: {integrity: sha512-xtoijJTZ+qeucLBDNztDOuQBE1ksqjvNjvqFoST3nGC7fSpqJ+X6BdTBaY5BHG+IhWWmpc6b/KfpeuEDupEPOQ==}
electron-to-chromium@1.5.6:
resolution: {integrity: sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw==}
@ -2514,15 +2515,6 @@ packages:
flatted@3.3.1:
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
follow-redirects@1.15.6:
resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@ -2533,10 +2525,6 @@ packages:
resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==}
engines: {node: '>= 0.12'}
form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
fs-extra@11.2.0:
resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
engines: {node: '>=14.14'}
@ -2806,6 +2794,10 @@ packages:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
ignore@6.0.2:
resolution: {integrity: sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==}
engines: {node: '>= 4'}
immutable@4.3.7:
resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==}
@ -3057,8 +3049,8 @@ packages:
resolution: {integrity: sha512-pmfRbVRs/7khFrSAYnSiJ8C0D5GvzkE4Ey2pAvUcJsw1ly/p+7ut27jbJrjY79BpAJQJ4gXYFtK6d1Aub+9baQ==}
hasBin: true
jiti@2.3.3:
resolution: {integrity: sha512-EX4oNDwcXSivPrw2qKH2LB5PoFxEvgtv2JgwW0bU858HoLQ+kutSvjLMUqBd0PeJYEinLWhoI9Ol0eYMqj/wNQ==}
jiti@2.4.0:
resolution: {integrity: sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g==}
hasBin: true
jpeg-js@0.4.4:
@ -3661,10 +3653,16 @@ packages:
engines: {node: '>=0.10'}
hasBin: true
pinia-plugin-persistedstate@3.2.1:
resolution: {integrity: sha512-MK++8LRUsGF7r45PjBFES82ISnPzyO6IZx3CH5vyPseFLZCk1g2kgx6l/nW8pEBKxxd4do0P6bJw+mUSZIEZUQ==}
pinia-plugin-persistedstate@4.1.2:
resolution: {integrity: sha512-oh4y4lmtXcgbE3BDWTDBUl9F4G1lhtgDI+GnF+cDDZ/fF6wnGYp4TKQ6FCuv9hMLAkNjs6IzADZHwPBYq10ksQ==}
peerDependencies:
pinia: ^2.0.0
'@pinia/nuxt': '>=0.5.0'
pinia: '>=2.0.0'
peerDependenciesMeta:
'@pinia/nuxt':
optional: true
pinia:
optional: true
pinia-shared-state@0.5.1:
resolution: {integrity: sha512-vPgKLrMSVhubQ1TPC+t9Vg2j2cabbJZDybxeG9BxVHrAFw35jb4IBvfFg9JGv+ULKxIdRvvg4u4I0zan2Juqqg==}
@ -3776,6 +3774,10 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
readdirp@4.0.2:
resolution: {integrity: sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==}
engines: {node: '>= 14.16.0'}
rechoir@0.6.2:
resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
engines: {node: '>= 0.10'}
@ -4603,12 +4605,10 @@ snapshots:
'@babel/helper-validator-identifier': 7.25.9
js-tokens: 4.0.0
picocolors: 1.1.1
optional: true
'@babel/compat-data@7.25.2': {}
'@babel/compat-data@7.26.2':
optional: true
'@babel/compat-data@7.26.2': {}
'@babel/core@7.25.2':
dependencies:
@ -4649,11 +4649,10 @@ snapshots:
semver: 6.3.1
transitivePeerDependencies:
- supports-color
optional: true
'@babel/eslint-parser@7.25.1(@babel/core@7.25.2)(eslint@8.57.0)':
'@babel/eslint-parser@7.25.1(@babel/core@7.26.0)(eslint@8.57.0)':
dependencies:
'@babel/core': 7.25.2
'@babel/core': 7.26.0
'@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1
eslint: 8.57.0
eslint-visitor-keys: 2.1.0
@ -4673,7 +4672,6 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25
jsesc: 3.0.2
optional: true
'@babel/helper-annotate-as-pure@7.24.7':
dependencies:
@ -4694,7 +4692,6 @@ snapshots:
browserslist: 4.24.2
lru-cache: 5.1.1
semver: 6.3.1
optional: true
'@babel/helper-create-class-features-plugin@7.25.0(@babel/core@7.25.2)':
dependencies:
@ -4733,7 +4730,6 @@ snapshots:
'@babel/types': 7.26.0
transitivePeerDependencies:
- supports-color
optional: true
'@babel/helper-module-transforms@7.25.2(@babel/core@7.25.2)':
dependencies:
@ -4753,7 +4749,6 @@ snapshots:
'@babel/traverse': 7.25.9
transitivePeerDependencies:
- supports-color
optional: true
'@babel/helper-optimise-call-expression@7.24.7':
dependencies:
@ -4788,20 +4783,17 @@ snapshots:
'@babel/helper-string-parser@7.25.7': {}
'@babel/helper-string-parser@7.25.9':
optional: true
'@babel/helper-string-parser@7.25.9': {}
'@babel/helper-validator-identifier@7.24.7': {}
'@babel/helper-validator-identifier@7.25.7': {}
'@babel/helper-validator-identifier@7.25.9':
optional: true
'@babel/helper-validator-identifier@7.25.9': {}
'@babel/helper-validator-option@7.24.8': {}
'@babel/helper-validator-option@7.25.9':
optional: true
'@babel/helper-validator-option@7.25.9': {}
'@babel/helpers@7.25.0':
dependencies:
@ -4812,7 +4804,6 @@ snapshots:
dependencies:
'@babel/template': 7.25.9
'@babel/types': 7.26.0
optional: true
'@babel/highlight@7.24.7':
dependencies:
@ -4839,7 +4830,6 @@ snapshots:
'@babel/parser@7.26.2':
dependencies:
'@babel/types': 7.26.0
optional: true
'@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.25.2)':
dependencies:
@ -4866,8 +4856,7 @@ snapshots:
dependencies:
regenerator-runtime: 0.14.1
'@babel/standalone@7.26.2':
optional: true
'@babel/standalone@7.26.2': {}
'@babel/template@7.25.0':
dependencies:
@ -4880,7 +4869,6 @@ snapshots:
'@babel/code-frame': 7.26.2
'@babel/parser': 7.26.2
'@babel/types': 7.26.0
optional: true
'@babel/traverse@7.25.3':
dependencies:
@ -4905,7 +4893,6 @@ snapshots:
globals: 11.12.0
transitivePeerDependencies:
- supports-color
optional: true
'@babel/types@7.25.2':
dependencies:
@ -4923,7 +4910,6 @@ snapshots:
dependencies:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
optional: true
'@commitlint/cli@19.4.0(@types/node@20.14.15)(typescript@5.6.2)':
dependencies:
@ -5280,17 +5266,17 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.17.1
'@nuxt/kit@3.13.1(rollup@4.24.3)(webpack-sources@3.2.3)':
'@nuxt/kit@3.14.0(rollup@4.24.3)(webpack-sources@3.2.3)':
dependencies:
'@nuxt/schema': 3.13.1(rollup@4.24.3)(webpack-sources@3.2.3)
c12: 1.11.2
'@nuxt/schema': 3.14.0(rollup@4.24.3)(webpack-sources@3.2.3)
c12: 2.0.1
consola: 3.2.3
defu: 6.1.4
destr: 2.0.3
globby: 14.0.2
hash-sum: 2.0.0
ignore: 5.3.2
jiti: 1.21.6
ignore: 6.0.2
jiti: 2.4.0
klona: 2.0.6
knitwork: 1.1.0
mlly: 1.7.2
@ -5307,10 +5293,10 @@ snapshots:
- rollup
- supports-color
- webpack-sources
optional: true
'@nuxt/schema@3.13.1(rollup@4.24.3)(webpack-sources@3.2.3)':
'@nuxt/schema@3.14.0(rollup@4.24.3)(webpack-sources@3.2.3)':
dependencies:
c12: 2.0.1
compatx: 0.1.8
consola: 3.2.3
defu: 6.1.4
@ -5324,10 +5310,10 @@ snapshots:
unimport: 3.13.1(rollup@4.24.3)(webpack-sources@3.2.3)
untyped: 1.5.1
transitivePeerDependencies:
- magicast
- rollup
- supports-color
- webpack-sources
optional: true
'@octokit/auth-token@4.0.0': {}
@ -5494,7 +5480,6 @@ snapshots:
picomatch: 4.0.2
optionalDependencies:
rollup: 4.24.3
optional: true
'@rollup/rollup-android-arm-eabi@4.24.3':
optional: true
@ -6005,8 +5990,7 @@ snapshots:
acorn@8.12.1: {}
acorn@8.14.0:
optional: true
acorn@8.14.0: {}
add-stream@1.0.0: {}
@ -6148,14 +6132,6 @@ snapshots:
aws4@1.13.1: {}
axios@1.7.4:
dependencies:
follow-redirects: 1.15.6
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
balanced-match@1.0.2: {}
base64-js@1.5.1: {}
@ -6218,11 +6194,10 @@ snapshots:
browserslist@4.24.2:
dependencies:
caniuse-lite: 1.0.30001676
electron-to-chromium: 1.5.49
caniuse-lite: 1.0.30001677
electron-to-chromium: 1.5.52
node-releases: 2.0.18
update-browserslist-db: 1.1.1(browserslist@4.24.2)
optional: true
buffer-from@1.1.2: {}
@ -6240,21 +6215,20 @@ snapshots:
esbuild: 0.23.1
load-tsconfig: 0.2.5
c12@1.11.2:
c12@2.0.1:
dependencies:
chokidar: 3.6.0
chokidar: 4.0.1
confbox: 0.1.8
defu: 6.1.4
dotenv: 16.4.5
giget: 1.2.3
jiti: 1.21.6
jiti: 2.4.0
mlly: 1.7.2
ohash: 1.1.4
pathe: 1.1.2
perfect-debounce: 1.0.0
pkg-types: 1.2.1
rc9: 2.1.2
optional: true
cachedir@2.3.0: {}
@ -6274,8 +6248,7 @@ snapshots:
caniuse-lite@1.0.30001651: {}
caniuse-lite@1.0.30001676:
optional: true
caniuse-lite@1.0.30001677: {}
caseless@0.12.0: {}
@ -6306,15 +6279,17 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
chownr@2.0.0:
optional: true
chokidar@4.0.1:
dependencies:
readdirp: 4.0.2
chownr@2.0.0: {}
ci-info@4.0.0: {}
citty@0.1.6:
dependencies:
consola: 3.2.3
optional: true
cli-boxes@3.0.0: {}
@ -6397,8 +6372,7 @@ snapshots:
array-ify: 1.0.0
dot-prop: 5.3.0
compatx@0.1.8:
optional: true
compatx@0.1.8: {}
computeds@0.0.1: {}
@ -6413,8 +6387,7 @@ snapshots:
confbox@0.1.7: {}
confbox@0.1.8:
optional: true
confbox@0.1.8: {}
config-chain@1.1.13:
dependencies:
@ -6428,8 +6401,7 @@ snapshots:
graceful-fs: 4.2.11
xdg-basedir: 5.1.0
consola@3.2.3:
optional: true
consola@3.2.3: {}
conventional-changelog-angular@7.0.0:
dependencies:
@ -6639,6 +6611,8 @@ snapshots:
deep-is@0.1.4: {}
deep-pick-omit@1.2.1: {}
default-browser-id@5.0.0: {}
default-browser@5.2.1:
@ -6676,8 +6650,7 @@ snapshots:
deprecation@2.3.1: {}
destr@2.0.3:
optional: true
destr@2.0.3: {}
detect-file@1.0.0: {}
@ -6705,8 +6678,7 @@ snapshots:
dependencies:
type-fest: 4.24.0
dotenv@16.4.5:
optional: true
dotenv@16.4.5: {}
duplexer@0.1.2: {}
@ -6715,8 +6687,7 @@ snapshots:
jsbn: 0.1.1
safer-buffer: 2.1.2
electron-to-chromium@1.5.49:
optional: true
electron-to-chromium@1.5.52: {}
electron-to-chromium@1.5.6: {}
@ -6864,8 +6835,7 @@ snapshots:
escalade@3.1.2: {}
escalade@3.2.0:
optional: true
escalade@3.2.0: {}
escape-goat@4.0.0: {}
@ -7172,8 +7142,6 @@ snapshots:
flatted@3.3.1: {}
follow-redirects@1.15.6: {}
for-each@0.3.3:
dependencies:
is-callable: 1.2.7
@ -7186,12 +7154,6 @@ snapshots:
combined-stream: 1.0.8
mime-types: 2.1.35
form-data@4.0.0:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
fs-extra@11.2.0:
dependencies:
graceful-fs: 4.2.11
@ -7208,7 +7170,6 @@ snapshots:
fs-minipass@2.1.0:
dependencies:
minipass: 3.3.6
optional: true
fs.realpath@1.0.0: {}
@ -7291,7 +7252,6 @@ snapshots:
ohash: 1.1.4
pathe: 1.1.2
tar: 6.2.1
optional: true
git-raw-commits@4.0.0:
dependencies:
@ -7435,8 +7395,7 @@ snapshots:
dependencies:
has-symbols: 1.0.3
hash-sum@2.0.0:
optional: true
hash-sum@2.0.0: {}
hasown@2.0.2:
dependencies:
@ -7450,8 +7409,7 @@ snapshots:
dependencies:
parse-passwd: 1.0.0
hookable@5.5.3:
optional: true
hookable@5.5.3: {}
hosted-git-info@7.0.2:
dependencies:
@ -7495,6 +7453,8 @@ snapshots:
ignore@5.3.2: {}
ignore@6.0.2: {}
immutable@4.3.7: {}
import-fresh@3.3.0:
@ -7731,8 +7691,7 @@ snapshots:
jiti@2.0.0-beta.3: {}
jiti@2.3.3:
optional: true
jiti@2.4.0: {}
jpeg-js@0.4.4: {}
@ -7750,8 +7709,7 @@ snapshots:
jsesc@2.5.2: {}
jsesc@3.0.2:
optional: true
jsesc@3.0.2: {}
json-buffer@3.0.1: {}
@ -7794,11 +7752,9 @@ snapshots:
dependencies:
json-buffer: 3.0.1
klona@2.0.6:
optional: true
klona@2.0.6: {}
knitwork@1.1.0:
optional: true
knitwork@1.1.0: {}
ky@1.7.2: {}
@ -7925,7 +7881,6 @@ snapshots:
magic-string@0.30.12:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
optional: true
mdn-data@2.0.30: {}
@ -7975,21 +7930,17 @@ snapshots:
minipass@3.3.6:
dependencies:
yallist: 4.0.0
optional: true
minipass@5.0.0:
optional: true
minipass@5.0.0: {}
minizlib@2.1.2:
dependencies:
minipass: 3.3.6
yallist: 4.0.0
optional: true
mitt@3.0.1: {}
mkdirp@1.0.4:
optional: true
mkdirp@1.0.4: {}
mlly@1.7.1:
dependencies:
@ -8004,10 +7955,8 @@ snapshots:
pathe: 1.1.2
pkg-types: 1.2.1
ufo: 1.5.4
optional: true
mri@1.2.0:
optional: true
mri@1.2.0: {}
mrmime@2.0.0: {}
@ -8068,8 +8017,7 @@ snapshots:
node-bitmap@0.0.1: {}
node-fetch-native@1.6.4:
optional: true
node-fetch-native@1.6.4: {}
node-releases@2.0.18: {}
@ -8101,7 +8049,6 @@ snapshots:
pathe: 1.1.2
pkg-types: 1.2.1
ufo: 1.5.4
optional: true
oauth-sign@0.9.0: {}
@ -8137,8 +8084,7 @@ snapshots:
oblivious-set@1.4.0: {}
ohash@1.1.4:
optional: true
ohash@1.1.4: {}
omggif@1.0.10: {}
@ -8321,8 +8267,7 @@ snapshots:
pathe@1.1.2: {}
perfect-debounce@1.0.0:
optional: true
perfect-debounce@1.0.0: {}
performance-now@2.1.0: {}
@ -8336,9 +8281,19 @@ snapshots:
pidtree@0.6.0: {}
pinia-plugin-persistedstate@3.2.1(pinia@2.2.1(typescript@5.6.2)(vue@3.5.11(typescript@5.6.2))):
pinia-plugin-persistedstate@4.1.2(pinia@2.2.1(typescript@5.6.2)(vue@3.5.11(typescript@5.6.2)))(rollup@4.24.3)(webpack-sources@3.2.3):
dependencies:
'@nuxt/kit': 3.14.0(rollup@4.24.3)(webpack-sources@3.2.3)
deep-pick-omit: 1.2.1
defu: 6.1.4
destr: 2.0.3
optionalDependencies:
pinia: 2.2.1(typescript@5.6.2)(vue@3.5.11(typescript@5.6.2))
transitivePeerDependencies:
- magicast
- rollup
- supports-color
- webpack-sources
pinia-shared-state@0.5.1(pinia@2.2.1(typescript@5.6.2)(vue@3.5.11(typescript@5.6.2)))(vue@3.5.11(typescript@5.6.2)):
dependencies:
@ -8368,7 +8323,6 @@ snapshots:
confbox: 0.1.8
mlly: 1.7.2
pathe: 1.1.2
optional: true
pngjs@3.4.0: {}
@ -8432,7 +8386,6 @@ snapshots:
dependencies:
defu: 6.1.4
destr: 2.0.3
optional: true
rc@1.2.8:
dependencies:
@ -8464,6 +8417,8 @@ snapshots:
dependencies:
picomatch: 2.3.1
readdirp@4.0.2: {}
rechoir@0.6.2:
dependencies:
resolve: 1.22.8
@ -8769,8 +8724,7 @@ snapshots:
safer-buffer: 2.1.2
tweetnacl: 0.14.5
std-env@3.7.0:
optional: true
std-env@3.7.0: {}
stdin-discarder@0.2.2: {}
@ -8862,7 +8816,6 @@ snapshots:
minizlib: 2.1.2
mkdirp: 1.0.4
yallist: 4.0.0
optional: true
terser@5.31.5:
dependencies:
@ -8999,8 +8952,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
uncrypto@0.1.3:
optional: true
uncrypto@0.1.3: {}
unctx@2.3.1(webpack-sources@3.2.3):
dependencies:
@ -9010,7 +8962,6 @@ snapshots:
unplugin: 1.15.0(webpack-sources@3.2.3)
transitivePeerDependencies:
- webpack-sources
optional: true
undici-types@5.26.5: {}
@ -9057,7 +9008,6 @@ snapshots:
transitivePeerDependencies:
- rollup
- webpack-sources
optional: true
uniq@1.0.1: {}
@ -9067,7 +9017,7 @@ snapshots:
unload@2.4.1: {}
unplugin-auto-import@0.18.2(@nuxt/kit@3.13.1(rollup@4.24.3)(webpack-sources@3.2.3))(@vueuse/core@10.11.1(vue@3.5.11(typescript@5.6.2)))(rollup@4.24.3)(webpack-sources@3.2.3):
unplugin-auto-import@0.18.2(@nuxt/kit@3.14.0(rollup@4.24.3)(webpack-sources@3.2.3))(@vueuse/core@10.11.1(vue@3.5.11(typescript@5.6.2)))(rollup@4.24.3)(webpack-sources@3.2.3):
dependencies:
'@antfu/utils': 0.7.10
'@rollup/pluginutils': 5.1.0(rollup@4.24.3)
@ -9078,13 +9028,13 @@ snapshots:
unimport: 3.11.1(rollup@4.24.3)(webpack-sources@3.2.3)
unplugin: 1.14.1(webpack-sources@3.2.3)
optionalDependencies:
'@nuxt/kit': 3.13.1(rollup@4.24.3)(webpack-sources@3.2.3)
'@nuxt/kit': 3.14.0(rollup@4.24.3)(webpack-sources@3.2.3)
'@vueuse/core': 10.11.1(vue@3.5.11(typescript@5.6.2))
transitivePeerDependencies:
- rollup
- webpack-sources
unplugin-vue-components@0.27.4(@babel/parser@7.26.2)(@nuxt/kit@3.13.1(rollup@4.24.3)(webpack-sources@3.2.3))(rollup@4.24.3)(vue@3.5.11(typescript@5.6.2))(webpack-sources@3.2.3):
unplugin-vue-components@0.27.4(@babel/parser@7.26.2)(@nuxt/kit@3.14.0(rollup@4.24.3)(webpack-sources@3.2.3))(rollup@4.24.3)(vue@3.5.11(typescript@5.6.2))(webpack-sources@3.2.3):
dependencies:
'@antfu/utils': 0.7.10
'@rollup/pluginutils': 5.1.0(rollup@4.24.3)
@ -9099,7 +9049,7 @@ snapshots:
vue: 3.5.11(typescript@5.6.2)
optionalDependencies:
'@babel/parser': 7.26.2
'@nuxt/kit': 3.13.1(rollup@4.24.3)(webpack-sources@3.2.3)
'@nuxt/kit': 3.14.0(rollup@4.24.3)(webpack-sources@3.2.3)
transitivePeerDependencies:
- rollup
- supports-color
@ -9118,7 +9068,6 @@ snapshots:
webpack-virtual-modules: 0.6.2
optionalDependencies:
webpack-sources: 3.2.3
optional: true
untyped@1.5.1:
dependencies:
@ -9126,12 +9075,11 @@ snapshots:
'@babel/standalone': 7.26.2
'@babel/types': 7.26.0
defu: 6.1.4
jiti: 2.3.3
jiti: 2.4.0
mri: 1.2.0
scule: 1.3.0
transitivePeerDependencies:
- supports-color
optional: true
update-browserslist-db@1.1.0(browserslist@4.23.3):
dependencies:
@ -9144,7 +9092,6 @@ snapshots:
browserslist: 4.24.2
escalade: 3.2.0
picocolors: 1.1.1
optional: true
update-notifier@7.3.1:
dependencies:
@ -9334,8 +9281,7 @@ snapshots:
yallist@3.1.1: {}
yallist@4.0.0:
optional: true
yallist@4.0.0: {}
yaml@2.5.0: {}

2
src-tauri/Cargo.lock generated
View File

@ -2107,7 +2107,7 @@ checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
[[package]]
name = "hula"
version = "2.5.1"
version = "2.5.3"
dependencies = [
"base64 0.22.1",
"lazy_static",

View File

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

View File

@ -35,7 +35,7 @@
],
"definitions": {
"Capability": {
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```",
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
"type": "object",
"required": ["identifier", "permissions"],
"properties": {

View File

@ -35,7 +35,7 @@
],
"definitions": {
"Capability": {
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```",
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
"type": "object",
"required": ["identifier", "permissions"],
"properties": {

View File

@ -35,7 +35,7 @@
],
"definitions": {
"Capability": {
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```",
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
"type": "object",
"required": ["identifier", "permissions"],
"properties": {

View File

@ -1,6 +1,6 @@
{
"productName": "HuLa",
"version": "2.5.2",
"version": "2.5.3",
"identifier": "com.hula.pc",
"build": {
"beforeDevCommand": "pnpm dev",

View File

@ -1,6 +1,6 @@
{
"productName": "HuLa",
"version": "2.5.2",
"version": "2.5.3",
"identifier": "com.hula.pc",
"build": {
"beforeDevCommand": "pnpm dev",

View File

@ -1,6 +1,6 @@
{
"productName": "HuLa",
"version": "2.5.2",
"version": "2.5.3",
"identifier": "com.hula.pc",
"build": {
"beforeDevCommand": "pnpm dev",

View File

@ -0,0 +1,89 @@
<template>
<n-modal
v-model:show="globalStore.addFriendModalInfo.show"
:mask-closable="false"
class="rounded-8px"
transform-origin="center">
<div class="bg-[--bg-edit] w-380px h-fit box-border flex flex-col">
<n-flex :size="6" vertical>
<div
v-if="type() === 'macos'"
@click="close"
class="mac-close size-13px shadow-inner bg-#ed6a5eff rounded-50% mt-6px select-none absolute left-6px">
<svg class="hidden size-7px color-#000 font-bold select-none absolute top-3px left-3px">
<use href="#close"></use>
</svg>
</div>
<n-flex class="text-(14px --text-color) select-none pt-6px" justify="center">申请加好友</n-flex>
<svg
v-if="type() === 'windows'"
class="size-14px cursor-pointer pt-6px select-none absolute right-6px"
@click="close">
<use href="#close"></use>
</svg>
<span class="h-1px w-full bg-[--line-color]"></span>
</n-flex>
<n-flex vertical justify="center" :size="20" class="p-20px">
<n-flex align="center" justify="center" :size="20">
<n-avatar round size="large" :src="userInfo.avatar" />
<n-flex vertical :size="10">
<p class="text-[--text-color]">{{ userInfo.name }}</p>
<p class="text-(12px [--text-color])">uid: {{ userInfo.uid }}</p>
</n-flex>
</n-flex>
<n-input
v-model:value="requestMsg"
:allow-input="(value: string) => !value.startsWith(' ') && !value.endsWith(' ')"
:autosize="{
minRows: 3,
maxRows: 3
}"
:maxlength="60"
:count-graphemes="countGraphemes"
show-count
type="textarea"
placeholder="输入几句话对TA说些什么吧" />
<n-button color="#13987f" @click="addFriend">添加好友</n-button>
</n-flex>
</div>
</n-modal>
</template>
<script setup lang="ts">
import { useGlobalStore } from '@/stores/global.ts'
import { type } from '@tauri-apps/plugin-os'
import { useUserInfo } from '@/hooks/useCached.ts'
import { leftHook } from '@/layout/left/hook.ts'
import apis from '@/services/apis.ts'
const globalStore = useGlobalStore()
const { countGraphemes } = leftHook()
const userInfo = ref(useUserInfo(globalStore.addFriendModalInfo.uid).value)
const requestMsg = ref()
const close = () => {
globalStore.addFriendModalInfo.show = false
globalStore.addFriendModalInfo.uid = undefined
}
const addFriend = async () => {
await apis.sendAddFriendRequest({
msg: requestMsg.value,
targetUid: globalStore.addFriendModalInfo.uid as number
})
window.$message.success('已发送好友申请')
close()
}
watch(globalStore.addFriendModalInfo, (val) => {
if (val.show) {
userInfo.value = useUserInfo(val.uid).value
}
})
</script>
<style scoped lang="scss"></style>

View File

@ -36,8 +36,8 @@
left: `${pos.posX}px`,
top: `${pos.posY}px`
}">
<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 v-resize="handleSize" v-if="visibleMenu && visibleMenu.length > 0" class="menu-list">
<div v-for="(item, index) in visibleMenu as any[]" :key="index">
<!-- 禁止的菜单选项需要禁止点击事件 -->
<div class="menu-item-disabled" v-if="item.disabled" @click.prevent="$event.preventDefault()">
<svg><use :href="`#${item.icon}`"></use></svg>
@ -68,7 +68,11 @@
import { useContextMenu } from '@/hooks/useContextMenu.ts'
import { useViewport } from '@/hooks/useViewport.ts'
const { menu, emoji, specialMenu } = defineProps({
const { content, menu, emoji, specialMenu } = defineProps({
content: {
type: Object,
required: false
},
menu: {
type: Array
},
@ -80,6 +84,17 @@ const { menu, emoji, specialMenu } = defineProps({
default: () => []
}
})
// 使
const visibleMenu = computed(() => {
return menu?.filter((item: any) => {
// visible
if (typeof item.visible === 'function') {
return item.visible(content) // visible
}
// visible
return true
})
})
/** 判断是否传入了menu */
const isNull = computed(() => menu === void 0)
const containerRef = ref(null)
@ -141,7 +156,7 @@ const handleEnter = (el: any) => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
el.style.height = `${h}px`
el.style.transition = '0.3s'
el.style.transition = '0.2s'
})
})
}

View File

@ -0,0 +1,62 @@
<template>
<n-flex vertical class="select-none">
<n-flex align="center" justify="space-between" class="color-[--text-color] px-20px py-10px">
<p class="text-16px">好友通知</p>
<svg class="size-18px cursor-pointer"><use href="#delete"></use></svg>
</n-flex>
<n-flex
vertical
:size="10"
class="p-[0_30px]"
v-for="(item, index) in contactStore.requestFriendsList"
:key="index">
<n-flex
align="center"
justify="space-between"
class="bg-[--center-bg-color] rounded-10px p-20px box-border border-(1px solid [--bg-popover])">
<n-flex align="center" :size="10">
<n-avatar round size="large" :src="useUserInfo(item.uid).value.avatar" class="mr-10px" />
<n-flex vertical :size="12">
<n-flex align="center" :size="10">
<n-popover
trigger="click"
placement="bottom-start"
:show-arrow="false"
style="padding: 0; background: var(--bg-info); backdrop-filter: blur(10px)">
<template #trigger>
<p @click="currentUserId = item.uid" class="text-(14px #13987f) cursor-pointer">
{{ useUserInfo(item.uid).value.name }}
</p>
</template>
<!-- 用户个人信息框 -->
<InfoPopover v-if="currentUserId === item.uid" :uid="item.uid" />
</n-popover>
<p class="text-(14px [--text-color])">请求加为好友</p>
</n-flex>
<p v-show="item.msg" class="text-(12px [--text-color])">留言{{ item.msg }}</p>
</n-flex>
</n-flex>
<n-button
secondary
v-if="item.status === RequestFriendAgreeStatus.Waiting"
@click="contactStore.onAcceptFriend(item.applyId)">
接受
</n-button>
<span class="text-(12px #64a29c)" v-else>已同意</span>
</n-flex>
</n-flex>
</n-flex>
</template>
<script setup lang="ts">
import { useContactStore } from '@/stores/contacts.ts'
import { useUserInfo } from '@/hooks/useCached.ts'
import { RequestFriendAgreeStatus } from '@/services/types.ts'
const contactStore = useContactStore()
const currentUserId = ref(0)
</script>
<style scoped lang="scss"></style>

View File

@ -52,7 +52,9 @@
</n-flex>
<!-- 群聊详情 -->
<div v-else class="flex flex-col flex-1 mt-60px gap-30px select-none p-[0_40px] box-border">
<div
v-else-if="content.type === RoomTypeEnum.GROUP"
class="flex flex-col flex-1 mt-60px gap-30px select-none p-[0_40px] box-border">
<!-- 群聊头像以及简介 -->
<n-flex align="center" justify="space-between">
<n-flex align="center">
@ -124,7 +126,7 @@ const { content } = defineProps<{
content: any
}>()
const item = computed(() => {
return useUserInfo(content.value.uid).value
return useUserInfo(content.uid).value
})
const footerOptions = ref<OPT.Details[]>([

View File

@ -85,6 +85,16 @@
<div class="pl-20px flex flex-col items-end gap-6px">
<MsgInput ref="MsgInputRef" />
</div>
<div v-if="isGuest" class="fuzzy">
<n-flex align="center" :size="0" class="pb-60px text-(14px [--text-color])">
<p>当前为</p>
<p class="color-#c14053 px-2px">游客模式</p>
<p></p>
<p @click="logout(true)" class="color-#13987f px-4px cursor-pointer">扫码登录</p>
<p>后使用</p>
</n-flex>
</div>
</main>
</template>
@ -94,12 +104,20 @@ import { LimitEnum, MsgEnum } from '@/enums'
import { useCommon } from '@/hooks/useCommon.ts'
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
import { emit } from '@tauri-apps/api/event'
import { useLogin } from '@/hooks/useLogin.ts'
import { useSettingStore } from '@/stores/setting.ts'
const { open, onChange } = useFileDialog()
const { logout } = useLogin()
const { open, onChange, reset } = useFileDialog()
const { login } = useSettingStore()
const MsgInputRef = ref()
const msgInputDom = ref()
const emojiShow = ref()
const { insertNode, triggerInputEvent, getEditorRange, imgPaste, FileOrVideoPaste } = useCommon()
/**
* 是否为游客模式
*/
const isGuest = computed(() => login.accountInfo.token === 'test')
/**
* 选择表情并把表情插入输入框
@ -111,7 +129,7 @@ const emojiHandle = (item: string) => {
const { range } = getEditorRange()!
range?.collapse(false)
//
insertNode(MsgEnum.TEXT, item)
insertNode(MsgEnum.TEXT, item, MsgInputRef.value.messageInputDom)
triggerInputEvent(msgInputDom.value)
}
@ -145,10 +163,13 @@ onChange((files) => {
FileOrVideoPaste(file, MsgEnum.FILE, MsgInputRef.value.messageInputDom)
}
}
reset()
})
onMounted(() => {
msgInputDom.value = MsgInputRef.value.messageInputDom
if (MsgInputRef.value) {
msgInputDom.value = MsgInputRef.value.messageInputDom
}
})
</script>
@ -164,6 +185,13 @@ onMounted(() => {
}
}
.fuzzy {
@apply bg-transparent select-none cursor-default size-full absolute-flex-center;
overflow: hidden;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
:deep(.n-input .n-input-wrapper) {
padding: 0;
}

View File

@ -91,6 +91,7 @@
<template #trigger>
<ContextMenu
@select="$event.click(item, 'Main')"
:content="item"
:menu="chatStore.isGroup ? optionsList : void 0"
:special-menu="report">
<n-avatar
@ -122,6 +123,7 @@
:style="item.fromUser.uid === userUid ? 'flex-direction: row-reverse' : ''">
<ContextMenu
@select="$event.click(item)"
:content="item"
:menu="chatStore.isGroup ? optionsList : []"
:special-menu="report">
<n-flex
@ -173,6 +175,7 @@
</n-flex>
<!-- 气泡样式 -->
<ContextMenu
:content="item"
@contextmenu="handleMacSelect"
@mouseenter="handleMouseEnter(item.message.id)"
@mouseleave="handleMouseLeave"
@ -338,7 +341,7 @@
</template>
<script setup lang="ts">
import { EventEnum, MittEnum, MsgEnum, RoomTypeEnum } from '@/enums'
import { type MessageType, SessionItem } from '@/services/types.ts'
import { SessionItem } from '@/services/types.ts'
import Mitt from '@/utils/Bus.ts'
import { usePopover } from '@/hooks/usePopover.ts'
import { useWindow } from '@/hooks/useWindow.ts'
@ -489,28 +492,28 @@ const jumpToReplyMsg = (key: number) => {
* @param index 下标
* @param id 用户ID
*/
const addToDomUpdateQueue = (index: number, id: number) => {
// 使 nextTick
nextTick(() => {
if (!floatFooter.value || id === userUid.value) {
virtualListInst.value?.scrollTo({ position: 'bottom', debounce: true })
}
/** data-key标识的气泡,添加前缀用于区分用户消息,不然气泡动画会被覆盖 */
const dataKey = id === userUid.value ? `U${index}` : `Q${index}`
const lastMessageElement = document.querySelector(`[data-key="${dataKey}"]`) as HTMLElement
if (lastMessageElement) {
//
lastMessageElement.classList.add('bubble-animation')
//
const handleAnimationEnd = () => {
lastMessageElement.classList.remove('bubble-animation')
lastMessageElement.removeEventListener('animationend', handleAnimationEnd)
}
lastMessageElement.addEventListener('animationend', handleAnimationEnd)
}
})
chatStore.clearNewMsgCount()
}
// const addToDomUpdateQueue = (index: number, id: number) => {
// // 使 nextTick
// nextTick(() => {
// if (!floatFooter.value || id === userUid.value) {
// virtualListInst.value?.scrollTo({ position: 'bottom', debounce: true })
// }
// /** data-key, */
// const dataKey = id === userUid.value ? `U${index}` : `Q${index}`
// const lastMessageElement = document.querySelector(`[data-key="${dataKey}"]`) as HTMLElement
// if (lastMessageElement) {
// //
// lastMessageElement.classList.add('bubble-animation')
// //
// const handleAnimationEnd = () => {
// lastMessageElement.classList.remove('bubble-animation')
// lastMessageElement.removeEventListener('animationend', handleAnimationEnd)
// }
// lastMessageElement.addEventListener('animationend', handleAnimationEnd)
// }
// })
// chatStore.clearNewMsgCount()
// }
/** 点击后滚动到底部 */
const scrollBottom = () => {
@ -534,7 +537,7 @@ const closeMenu = (event: any) => {
if (!event.target.matches('.bubble', 'bubble-oneself')) {
activeBubble.value = -1
// mac
if (isMac.value) {
if (isMac.value && recordEL.value) {
recordEL.value.classList.remove('select-none')
}
}
@ -560,24 +563,15 @@ onMounted(() => {
//
virtualListInst.value?.scrollTo({ position: 'bottom', debounce: true })
})
/**! 启动图标闪烁 需要设置"resources": ["sec-tauri/图标放置的文件夹"]*/
// invoke('tray_blink', {
// isRun: true,
// ms: 500,
// iconPath1: 'tray/msg.png',
// iconPath2: 'tray/msg-sub.png'
// }).catch((error) => {
// console.error(':', error)
// Mitt.on(MittEnum.SEND_MESSAGE, (event: MessageType) => {
// nextTick(() => {
// addToDomUpdateQueue(event.message.id, event.fromUser.uid)
// })
// })
Mitt.on(MittEnum.SEND_MESSAGE, (event: MessageType) => {
nextTick(() => {
addToDomUpdateQueue(event.message.id, event.fromUser.uid)
})
})
Mitt.on(`${MittEnum.INFO_POPOVER}-Main`, (event: any) => {
selectKey.value = event
selectKey.value = event.uid
infoPopover.value = true
handlePopoverUpdate(event)
handlePopoverUpdate(event.uid)
})
Mitt.on(MittEnum.MSG_BOX_SHOW, (event: any) => {
activeItemRef.value = event.item

View File

@ -20,12 +20,13 @@
<n-flex v-if="!isSearch" align="center" justify="space-between" class="pr-8px pl-8px h-42px">
<span class="text-14px">群聊成员&nbsp;{{ userList.length }}</span>
<svg @click="handleSearch" class="size-14px"><use href="#search"></use></svg>
<svg @click="handleSelect" class="size-14px"><use href="#search"></use></svg>
</n-flex>
<!-- 搜索框 -->
<n-flex v-else align="center" class="pr-8px h-42px">
<n-input
@blur="isSearch = false"
:on-input="handleSearch"
@blur="handleBlur"
ref="inputInstRef"
v-model:value="searchRef"
clearable
@ -47,7 +48,7 @@
style="max-height: calc(100vh - 130px)"
item-resizable
:item-size="42"
:items="userList">
:items="filteredUserList">
<template #default="{ item }">
<n-popover
@update:show="handlePopoverUpdate(item.uid)"
@ -57,7 +58,11 @@
v-model:show="infoPopover"
style="padding: 0; background: var(--bg-info); backdrop-filter: blur(10px)">
<template #trigger>
<ContextMenu @select="$event.click(item, 'Sidebar')" :menu="optionsList" :special-menu="report">
<ContextMenu
:content="item"
@select="$event.click(item, 'Sidebar')"
:menu="optionsList"
:special-menu="report">
<n-flex @click="selectKey = item.uid" :key="item.uid" :size="10" align="center" class="item">
<n-avatar
lazy
@ -99,6 +104,7 @@ import { useGroupStore } from '@/stores/group.ts'
import { useUserInfo } from '@/hooks/useCached.ts'
import { useGlobalStore } from '@/stores/global.ts'
import type { UserItem } from '@/services/types.ts'
import { useDebounceFn } from '@vueuse/core'
const groupStore = useGroupStore()
const globalStore = useGlobalStore()
@ -113,6 +119,7 @@ const userList = computed(() => {
}
})
})
const filteredUserList = shallowRef(userList.value)
const isGroup = computed(() => globalStore.currentSession?.type === RoomTypeEnum.GROUP)
/** 是否是搜索模式 */
const isSearch = ref(false)
@ -124,18 +131,35 @@ const isCollapsed = ref(true)
const { optionsList, report, selectKey } = useChatMain()
const { handlePopoverUpdate } = usePopover(selectKey, 'image-chat-sidebar')
const handleSearch = () => {
const handleSelect = () => {
isSearch.value = !isSearch.value
nextTick(() => {
inputInstRef.value?.select()
})
}
/**
* 重置搜索状态
*/
const handleBlur = () => {
isSearch.value = false
searchRef.value = ''
filteredUserList.value = userList.value
}
/**
* 监听搜索输入过滤用户
* @param value 输入值
*/
const handleSearch = useDebounceFn((value: string) => {
filteredUserList.value = userList.value.filter((user) => user.name.toLowerCase().includes(value.toLowerCase()))
}, 10)
onMounted(() => {
Mitt.on(`${MittEnum.INFO_POPOVER}-Sidebar`, (event: any) => {
selectKey.value = event
selectKey.value = event.uid
infoPopover.value = true
handlePopoverUpdate(event)
handlePopoverUpdate(event.uid)
})
})
</script>

View File

@ -84,7 +84,6 @@ import { useAlwaysOnTopStore } from '@/stores/alwaysOnTop.ts'
import { useSettingStore } from '@/stores/setting.ts'
import { emit, listen } from '@tauri-apps/api/event'
import { CloseBxEnum, EventEnum, MittEnum } from '@/enums'
import { PersistedStateOptions } from 'pinia-plugin-persistedstate'
import { exit } from '@tauri-apps/plugin-process'
import { type } from '@tauri-apps/plugin-os'
@ -190,7 +189,7 @@ const handleConfirm = async () => {
}
/** 监听是否按下esc */
const isEsc = (e: PersistedStateOptions) => {
const isEsc = (e: KeyboardEvent) => {
// esc
if (e.key === 'Escape' && escClose.value) {
handleCloseWin()

View File

@ -62,6 +62,8 @@ export enum MittEnum {
SHRINK_WINDOW,
/** 详情页面显示 */
DETAILS_SHOW,
/** 好友申请页面显示 */
APPLY_SHOW,
/** 消息列表被清空或者暂无消息 */
NOT_MSG,
/** 回复消息 */

View File

@ -1,13 +1,19 @@
import { useCommon } from '@/hooks/useCommon.ts'
import { MittEnum, MsgEnum } from '@/enums'
import { MittEnum, MsgEnum, PowerEnum } from '@/enums'
import { MessageType, SessionItem } from '@/services/types.ts'
import Mitt from '@/utils/Bus.ts'
import { useChatStore } from '@/stores/chat.ts'
import apis from '@/services/apis.ts'
import { useContactStore } from '@/stores/contacts'
import { useUserStore } from '@/stores/user'
import { useGlobalStore } from '@/stores/global.ts'
import { isDiffNow } from '@/utils/ComputedTime.ts'
export const useChatMain = (activeItem?: SessionItem) => {
const { removeTag, userUid } = useCommon()
const globalStore = useGlobalStore()
const chatStore = useChatStore()
const userInfo = useUserStore()?.userInfo
// const userInfo = useUserStore()?.userInfo
// const chatMessageList = computed(() => chatStore.chatMessageList)
const messageOptions = computed(() => chatStore.currentMessageOptions)
@ -80,6 +86,14 @@ export const useChatMain = (activeItem?: SessionItem) => {
return
}
chatStore.updateRecallStatus({ msgId: item.message.id })
},
visible: (item: MessageType) => {
// 判断当前选择的信息的发送时间是否超过2分钟
if (isDiffNow({ time: item.message.sendTime, unit: 'minute', diff: 2 })) return
// 判断自己是否是发送者或者是否是管理员
const isCurrentUser = item.fromUser.uid === userUid.value
const isAdmin = userInfo?.power === PowerEnum.ADMIN
return isCurrentUser || isAdmin
}
}
])
@ -153,7 +167,7 @@ export const useChatMain = (activeItem?: SessionItem) => {
}
])
/** 右键用户信息菜单(群聊的时候显示) */
const optionsList = ref([
const optionsList = ref<OPT.RightMenu[]>([
{
label: '发送信息',
icon: 'message-action',
@ -170,13 +184,19 @@ export const useChatMain = (activeItem?: SessionItem) => {
label: '查看资料',
icon: 'notes',
click: (item: any, type: string) => {
Mitt.emit(`${MittEnum.INFO_POPOVER}-${type}`, item.key)
// 如果是聊天框内的资料就使用的是消息的key如果是群聊成员的资料就使用的是uid
const uid = item.uid || item.message.id
Mitt.emit(`${MittEnum.INFO_POPOVER}-${type}`, { uid: uid, type: type })
}
},
{
label: '添加好友',
icon: 'people-plus',
click: () => {}
click: (item: any) => {
globalStore.addFriendModalInfo.show = true
globalStore.addFriendModalInfo.uid = item.uid || item.fromUser.uid
},
visible: (item: any) => canAddFriend(item.uid || item.fromUser.uid)
}
])
/** 举报选项 */
@ -207,6 +227,19 @@ export const useChatMain = (activeItem?: SessionItem) => {
}
])
/**
*
* @param uid ID
* @returns {boolean} true false
*/
const canAddFriend = (uid: number): boolean => {
const contactStore = useContactStore()
const userStore = useUserStore()
const myUid = userStore.userInfo.uid
// 好友和自己不显示添加好友菜单
return !(contactStore.contactsList.some((item) => item.uid === uid) || uid === myUid)
}
/**
*
* @param content
@ -249,12 +282,14 @@ export const useChatMain = (activeItem?: SessionItem) => {
// 计算距离底部的距离
// const distanceFromBottom = scrollHeight - scrollTop.value - clientHeight
// 判断是否滚动到顶部
if (scrollTop.value === 0) {
// 记录顶部最后一条消息的下标
// historyIndex.value = chatMessageList.value[0].message.id
if (messageOptions.value?.isLoading) return
chatStore.loadMore()
}
requestAnimationFrame(async () => {
if (scrollTop.value === 0) {
// 记录顶部最后一条消息的下标
// historyIndex.value = chatMessageList.value[0].message.id
if (messageOptions.value?.isLoading) return
await chatStore.loadMore()
}
})
// // 判断是否大于100
// if (distanceFromBottom > 100) {
// floatFooter.value = true

View File

@ -87,7 +87,7 @@ export const useCommon = () => {
* @param { MsgEnum } type
* @param dom dom节点
*/
const insertNode = (type: MsgEnum, dom: any) => {
const insertNode = (type: MsgEnum, dom: any, target: HTMLElement) => {
const { selection, range } = getEditorRange()!
// 删除选中的内容
range?.deleteContents()
@ -258,7 +258,7 @@ export const useCommon = () => {
range?.insertNode(spaceNode)
range?.collapse(false)
} else {
range?.insertNode(dom)
target.appendChild(dom)
}
// 将光标移到选中范围的最后面
selection?.collapseToEnd()
@ -279,7 +279,7 @@ export const useCommon = () => {
img.style.maxWidth = '140px'
img.style.marginRight = '6px'
// 插入图片
insertNode(MsgEnum.IMAGE, img)
insertNode(MsgEnum.IMAGE, img, dom)
triggerInputEvent(dom)
}
nextTick(() => {}).then(() => {
@ -298,7 +298,7 @@ export const useCommon = () => {
// 使用函数
createFileOrVideoDom(file).then((imgTag) => {
// 将生成的img标签插入到页面中
insertNode(type, imgTag)
insertNode(type, imgTag, dom)
triggerInputEvent(dom)
})
nextTick(() => {}).then(() => {
@ -340,7 +340,7 @@ export const useCommon = () => {
} else {
// 如果没有文件,而是文本,处理纯文本粘贴
const plainText = e.clipboardData.getData('text/plain')
insertNode(MsgEnum.TEXT, plainText)
insertNode(MsgEnum.TEXT, plainText, dom)
triggerInputEvent(dom)
}
}

View File

@ -1,5 +1,4 @@
import { emit } from '@tauri-apps/api/event'
import axios from 'axios'
import { EventEnum } from '@/enums'
import { useWindow } from '@/hooks/useWindow.ts'
@ -15,19 +14,22 @@ export const useLogin = () => {
await emit('login_success')
}
/** 登出账号 */
const logout = async () => {
/**
*
* @param isToQrcode
*/
const logout = async (isToQrcode = false) => {
const { createWebviewWindow } = useWindow()
localStorage.removeItem('USER_INFO')
localStorage.removeItem('TOKEN')
// 清空axios请求头
const instance = axios.create()
instance.defaults.headers.common.Authorization = ''
// todo 退出账号 需要关闭其他的全部窗口
await createWebviewWindow('登录', 'login', 320, 448, 'home', false, 320, 448).then(() => {
emit(EventEnum.LOGOUT)
emit('logout_success')
// 用于跳转到二维码页面
if (isToQrcode) {
localStorage.setItem('isToQrcode', '1')
}
})
}

View File

@ -58,7 +58,7 @@ export const useMsgInput = (messageInputDom: Ref) => {
if (clipboardItem.types.includes('text/plain')) {
// 如果是文本,使用 readText() 读取文本内容
navigator.clipboard.readText().then((text) => {
insertNode(MsgEnum.TEXT, text)
insertNode(MsgEnum.TEXT, text, {} as HTMLElement)
triggerInputEvent(messageInputDom.value)
})
} else if (clipboardItem.types.find((type) => type.startsWith('image/'))) {
@ -122,7 +122,11 @@ export const useMsgInput = (messageInputDom: Ref) => {
if (messageInputDom.value) {
nextTick().then(() => {
messageInputDom.value.focus()
insertNode(MsgEnum.REPLY, { accountName: accountName, content: event.message.body.content })
insertNode(
MsgEnum.REPLY,
{ accountName: accountName, content: event.message.body.content },
{} as HTMLElement
)
triggerInputEvent(messageInputDom.value)
})
}
@ -277,7 +281,7 @@ export const useMsgInput = (messageInputDom: Ref) => {
if (!isWindows && chat.value.sendKey === 'Enter' && e.metaKey && e.key === 'Enter') {
// 就进行换行操作
e.preventDefault()
insertNode(MsgEnum.TEXT, '\n')
insertNode(MsgEnum.TEXT, '\n', {} as HTMLElement)
triggerInputEvent(messageInputDom.value)
}
if (msgInput.value === '' || msgInput.value.trim() === '' || ait.value) {
@ -313,7 +317,7 @@ export const useMsgInput = (messageInputDom: Ref) => {
range?.setStart(textNode, <number>expRes?.index)
/** 设置范围的结束位置为光标的位置 */
range?.setEnd(textNode, endOffset!)
insertNode(MsgEnum.AIT, item.name)
insertNode(MsgEnum.AIT, item.name, {} as HTMLElement)
triggerInputEvent(messageInputDom.value)
ait.value = false
}

View File

@ -168,6 +168,9 @@ const doDrag = (e: MouseEvent) => {
const newWidth = startWidth.value + e.clientX - startX.value
//
if (newWidth !== maxWidth) {
// select-none
const chatMain = document.querySelector('#image-chat-main')
chatMain?.classList.add('select-none')
initWidth.value = clamp(newWidth, minWidth, maxWidth) // 使 clamp
}
})
@ -188,6 +191,9 @@ const initDrag = (e: MouseEvent) => {
}
const stopDrag = () => {
// select-none
const chatMain = document.querySelector('#image-chat-main')
chatMain?.classList.remove('select-none')
document.removeEventListener('mousemove', doDrag, false)
document.removeEventListener('mouseup', stopDrag, false)
isDragging.value = false

View File

@ -1,9 +1,11 @@
<template>
<div class="flex size-full min-w-310px">
<div id="layout" class="flex size-full min-w-310px">
<Left />
<Center />
<Right v-if="!shrinkStatus" />
</div>
<AddFriendsModal />
</template>
<script setup lang="ts">
@ -13,7 +15,13 @@ import Right from './right/index.vue'
import Mitt from '@/utils/Bus'
import { MittEnum } from '@/enums'
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
import { useGlobalStore } from '@/stores/global.ts'
import { useContactStore } from '@/stores/contacts.ts'
const globalStore = useGlobalStore()
const contactStore = useContactStore()
//
globalStore.unReadMark.newMsgUnreadCount = 0
const shrinkStatus = ref(false)
/**
* event默认如果没有传递值就为true所以shrinkStatus的值为false就会发生值的变化
@ -23,6 +31,12 @@ Mitt.on(MittEnum.SHRINK_WINDOW, (event) => {
shrinkStatus.value = event as boolean
})
onBeforeMount(() => {
//
contactStore.getContactList(true)
contactStore.getRequestFriendsList(true)
})
onMounted(async () => {
await getCurrentWebviewWindow().show()
})

View File

@ -43,7 +43,11 @@
</n-flex>
</n-popover>
<!-- 该选项无提示时展示 -->
<n-badge v-else :max="99" :value="item.badge">
<n-badge
v-else
:max="99"
:value="unReadMark.newMsgUnreadCount"
:show="item.icon.includes('message') && unReadMark.newMsgUnreadCount > 0">
<svg class="size-22px">
<use
:href="`#${activeUrl === item.url || openWindowsList.has(item.url) ? item.iconAction : item.icon}`"></use>
@ -249,11 +253,14 @@ import { PluginEnum, ShowModeEnum } from '@/enums'
import { useSettingStore } from '@/stores/setting.ts'
import { invoke } from '@tauri-apps/api/core'
import { listen } from '@tauri-apps/api/event'
import { useGlobalStore } from '@/stores/global.ts'
const globalStore = useGlobalStore()
const pluginsStore = usePluginsStore()
const { showMode } = storeToRefs(useSettingStore())
const { menuTop } = useMenuTopStore()
const { plugins } = storeToRefs(pluginsStore)
const unReadMark = computed(() => globalStore.unReadMark)
// const headerRef = useTemplateRef('header')
// const actionListRef = useTemplateRef('actionList')
//const { } = toRefs(getCurrentInstance) // div

View File

@ -120,11 +120,7 @@ export const leftHook = () => {
window.$message.error('改名次数不足')
return
}
apis.modifyUserName(editInfo.value.content.name).then((res: any) => {
if (!res) {
window.$message.error(res)
return
}
apis.modifyUserName(editInfo.value.content.name).then(() => {
// 更新本地缓存的用户信息
login.accountInfo.name = editInfo.value.content.name!
updateCurrentUserCache('name', editInfo.value.content.name) // 更新缓存里面的用户信息

View File

@ -8,7 +8,7 @@
<!-- 导航选项按钮模块 -->
<ActionList />
<!-- 编辑资料弹窗 -->
<InfoPopover />
<InfoEdit />
<!-- 弹出框 -->
<component :is="componentMap" />
@ -18,7 +18,7 @@
<script lang="tsx" setup>
import LeftAvatar from './components/LeftAvatar.vue'
import ActionList from './components/ActionList.vue'
import InfoPopover from './components/InfoPopover.vue'
import InfoEdit from './components/InfoEdit.vue'
import Mitt from '@/utils/Bus.ts'
import { lock, LockScreen, CheckUpdate } from './model.tsx'
import { DefineComponent, DefineSetupFnComponent } from 'vue'

View File

@ -220,14 +220,12 @@ export const CheckUpdate = defineComponent(() => {
switch (event.event) {
case 'Started':
total.value = event.data.contentLength ? event.data.contentLength : 0
console.log(`started downloading ${event.data.contentLength} bytes`)
break
case 'Progress':
downloaded.value += event.data.chunkLength
percentage.value = parseFloat(((downloaded.value / total.value) * 100 + '').substring(0, 4))
break
case 'Finished':
console.log('download finished')
window.$message.success('安装包下载成功3s后重启并安装')
setTimeout(() => {
updating.value = false

View File

@ -1,16 +1,19 @@
<template>
<main data-tauri-drag-region class="flex-1 bg-[--right-bg-color] h-full w-100vw min-w-600px">
<div class="size-full" style="background: var(--right-theme-bg-color)" data-tauri-drag-region>
<div class="size-full" :style="{ background: isChat ? 'var(--right-theme-bg-color)' : '' }" data-tauri-drag-region>
<ActionBar :current-label="appWindow.label" />
<!-- 需要判断当前路由是否是信息详情界面 -->
<ChatBox :active-item="activeItem" v-if="msgBoxShow && isChat && activeItem !== -1" />
<Details :content="DetailsContent" v-else-if="detailsShow && isDetails" />
<Details :content="DetailsContent" v-else-if="detailsShow && isDetails && DetailsContent.type !== 'apply'" />
<!-- 好友申请列表 -->
<ApplyList v-else-if="DetailsContent && isDetails && DetailsContent.type === 'apply'" />
<!-- 聊天界面背景图标 -->
<div v-else class="flex-center size-full select-none">
<img
v-if="imgTheme === ThemeEnum.DARK && themes.versatile === 'default'"
v-if="imgTheme === ThemeEnum.DARK && themes.versatile === 'default' && !isDetails"
class="w-110px h-100px"
src="@/assets/img/hula_bg_d.svg"
alt="" />
@ -73,6 +76,9 @@ onMounted(() => {
}
if (isDetails) {
Mitt.on(MittEnum.APPLY_SHOW, (event: any) => {
DetailsContent.value = event.context
})
Mitt.on(MittEnum.DETAILS_SHOW, (event: any) => {
DetailsContent.value = event.context
detailsShow.value = event.detailsShow as boolean

View File

@ -1,8 +1,6 @@
// import { createAxios } from '@/services/request'
import urls from '@/services/urls'
import type {
Response,
// BadgeType,
BadgeType,
CacheBadgeItem,
CacheBadgeReq,
CacheUserItem,
@ -22,14 +20,8 @@ import type {
UserItem
} from '@/services/types'
// const request = createAxios()
import request from '@/services/request'
// const GET = <T>(url: string, params?: any) => request.get<T, Response>(url, params)
// const POST = <T>(url: string, params?: any) => request.post<T, Response>(url, params)
// const PUT = <T>(url: string, params?: any) => request.put<T, Response>(url, params)
// const DELETE = <T>(url: string, params?: any) => request.delete<T, Response>(url, params)
const GET = <T>(url: string, params?: any) => request.get<T>(url, params)
const POST = <T>(url: string, params?: any) => request.post<T>(url, params)
const PUT = <T>(url: string, params?: any) => request.put<T>(url, params)
@ -57,7 +49,7 @@ export default {
/** 获取用户详细信息 */
getUserDetail: () => GET<UserInfoType>(urls.getUserInfoDetail, {}),
/** 获取徽章列表 */
getBadgeList: (): Promise<Response> => GET(urls.getBadgeList),
getBadgeList: (): Promise<BadgeType> => GET(urls.getBadgeList),
/** 设置用户勋章 */
setUserBadge: (badgeId: number) => PUT<void>(urls.setUserBadge, { badgeId }),
/** 修改用户名 */
@ -83,7 +75,7 @@ export default {
POST<EmojiItem[]>(urls.sendAddFriendRequest, params),
/** 同意好友申请 */
applyFriendRequest: (params: { applyId: number }) => PUT(urls.sendAddFriendRequest, params),
/** 同意好友申请 */
/** 删除好友 */
deleteFriend: (params: { targetUid: number }) => DELETE(urls.deleteFriend, params),
/** 好友申请未读数 */
newFriendCount: () => GET<{ unReadCount: number }>(urls.newFriendCount),
@ -102,11 +94,11 @@ export default {
/** 删除群成员 */
removeGroupMember: (params: { roomId: number; uid: number }) => DELETE(urls.inviteGroupMember, params),
/** 群组详情 */
groupDetail: (params: { id: number }) => GET<GroupDetailReq>(urls.groupDetail, { params }),
groupDetail: (params: { id: number }) => GET<GroupDetailReq>(urls.groupDetail, params),
/** 会话详情 */
sessionDetail: (params: { id: number }) => GET<SessionItem>(urls.sessionDetail, { params }),
/** 会话详情(联系人列表发消息用) */
sessionDetailWithFriends: (params: { uid: number }) => GET<SessionItem>(urls.sessionDetailWithFriends, { params }),
sessionDetailWithFriends: (params: { uid: number }) => GET<SessionItem>(urls.sessionDetailWithFriends, params),
/** 添加群管理 */
addAdmin: ({ roomId, uidList }: { roomId: number; uidList: number[] }) =>
PUT<boolean>(urls.addAdmin, {

View File

@ -82,77 +82,3 @@ async function Http<T>(
}
export default Http
// import { fetch } from '@tauri-apps/plugin-http';
// import * as qs from 'qs';
// const isAbsoluteURL = (url: string) => {
// return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url);
// };
// const combineURLs = (baseURL: string, relativeURL: string) => {
// return relativeURL
// ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
// : baseURL;
// };
// const buildFullPath = (baseURL: string, requestedURL: string) => {
// if (baseURL && !isAbsoluteURL(requestedURL)) {
// return combineURLs(baseURL, requestedURL);
// }
// return requestedURL;
// };
// const buildURL = (url: string | string[], params: any) => {
// if (!params) {
// return url;
// }
// const serializedParams = qs.stringify(params);
// if (serializedParams) {
// url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
// }
// return url;
// };
// const server = '';
// const baseURL = `${server}/api/`;
// const BODY_TYPE = {
// Form: 'Form',
// Json: 'Json',
// Text: 'Text',
// Bytes: 'Bytes',
// };
// const commonOptions = {
// timeout: 60,
// };
// const http = (url: string, options: any = {}) => {
// const params = { ...options.params };
// if (!options.headers) options.headers = {};
// // todo 可以往 headers 中添加 token 或 cookie 等信息
// if (options?.body) {
// if (options.body.type === BODY_TYPE.Form) {
// options.headers['Content-Type'] = 'multipart/form-data';
// }
// }
// options = { ...commonOptions, ...options };
// let fetchUrl:string = buildFullPath(baseURL, url)
// return fetch(<string>buildURL(fetchUrl, params), options)
// .then((res: any) => {
// const { status, data } = res
// if (status >= 200 && status < 400) {
// return { data };
// }
// return Promise.reject({ status, data });
// })
// .catch((err) => {
// console.error(err);
// return Promise.reject(err);
// });
// };
// export default http;

View File

@ -1,11 +1,6 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'
import { useSettingStore } from '@/stores/setting.ts'
import Http, { HttpParams } from './http.ts'
/** 是否是测试环境 */
const isTest = computed(() => {
return useSettingStore().login.accountInfo.token === 'test'
})
import { ServiceResponse } from '@/services/types.ts'
function getToken() {
let tempToken = ''
@ -26,123 +21,6 @@ function getToken() {
export const computedToken = getToken()
//请求配置
export const createAxios = (config?: AxiosRequestConfig): AxiosInstance => {
const instance = axios.create({
//请求头
// baseURL: import.meta.env.VITE_SERVICE_URL,
baseURL: '/api',
//超时配置
timeout: 10000,
//跨域携带cookie
withCredentials: true,
// 自定义配置覆盖基本配置
...config
})
// 添加请求拦截器
instance.interceptors.request.use(
function (config: any) {
//判断是否有token 根据自己的需求判断
const token = useSettingStore().login.accountInfo.token
if (isTest.value) {
// 如果token为'test',阻止请求并返回一个错误对象
return Promise.reject(
window.$message.create('当前为测试环境,请注意辨别', {
type: 'warning',
closable: true
})
)
}
if (token != void 0) {
// //如果要求携带在参数中
// config.params = Object.assign({}, config.params, token)
// 如果要求携带在请求头中
// config.headers = Object.assign({}, config.headers, operate.uploadParameters())
config.headers['Content-Type'] = 'application/json;charset=utf-8'
// 设置请求头
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
function (error) {
// 请求错误
return Promise.reject(error)
}
)
// 添加响应拦截器
instance.interceptors.response.use(
(response) => {
//返回参数
let res = response.data
// 如果是返回的文件
if (response.config.responseType === 'blob') {
return res
}
// 兼容服务端返回的字符串数据
if (typeof (<string>res) === 'string') {
res = res ? JSON.parse(res) : res
}
return res
},
(error) => {
/***** 接收到异常响应的处理开始 *****/
if (error && error.response) {
// 1.公共错误处理
// 2.根据响应码具体处理
switch (error.response.status) {
case 400:
error.message = '错误请求'
break
case 401:
error.message = '未授权,请重新登录'
break
case 403:
error.message = '拒绝访问'
break
case 404:
error.message = '请求错误,未找到该资源'
window.location.href = '/NotFound'
break
case 405:
error.message = '请求方法未允许'
break
case 408:
error.message = '请求超时'
break
case 500:
error.message = '服务器端出错'
break
case 501:
error.message = '网络未实现'
break
case 502:
error.message = '网络错误'
break
case 503:
error.message = '服务不可用'
break
case 504:
error.message = '网络超时'
break
case 505:
error.message = 'http版本不支持该请求'
break
default:
error.message = `连接错误${error.response.status}`
}
}
if (isTest) return Promise.resolve(error.response)
window.$message.error(error.message)
/***** 处理结束 *****/
return Promise.resolve(error.response)
}
)
return instance
}
// fetch 请求响应拦截器
const responseInterceptor = async <T>(
url: string,
@ -179,7 +57,9 @@ const responseInterceptor = async <T>(
const data = await Http(url, httpParams, true)
const resp = data.resp
const serviceData = (await data.data) as ServiceResponse
console.log(data)
//检查发送请求是否成功
if (resp.status > 400) {
let message = ''
switch (resp.status) {
@ -225,9 +105,12 @@ const responseInterceptor = async <T>(
}
return Promise.reject(`err: ${message}, status: ${resp.status}`)
}
const res: any = await data.data
return Promise.resolve(res.data)
//检查服务端返回是否成功,并且中断请求
if (!serviceData.success) {
window.$message.error(serviceData.errMsg)
return Promise.reject(`http error: ${serviceData.errMsg}`)
}
return Promise.resolve(serviceData.data)
} catch (err) {
return Promise.reject(`http error: ${err}`)
}

View File

@ -6,7 +6,7 @@
import { ActEnum, IsYetEnum, MarkEnum, MsgEnum, OnlineEnum, RoomTypeEnum, SexEnum } from '@/enums'
/**响应请求体*/
export type Response = {
export type ServiceResponse = {
/** 成功标识true or false */
success: boolean
/** 错误码 */

View File

@ -1,8 +1,8 @@
import { URLEnum } from '@/enums'
const { PROD, VITE_SERVICE_URL } = import.meta.env
const { VITE_SERVICE_URL } = import.meta.env
// 本地配置到 .env.development 里面修改。生产配置在 .env.production 里面
const prefix = PROD ? VITE_SERVICE_URL : ''
const prefix = VITE_SERVICE_URL
export default {
getUserInfo: `${prefix + URLEnum.USER}/userInfo`, // 获取用户信息

View File

@ -5,7 +5,6 @@ import type { MarkItemType, MessageType, RevokedMsgType, SessionItem } from '@/s
import { MarkEnum, MsgEnum, RoomTypeEnum } from '@/enums'
import { computedTimeBlock } from '@/utils/ComputedTime.ts'
import { useCachedStore } from '@/stores/cached'
import { useUserStore } from '@/stores/user'
import { useGlobalStore } from '@/stores/global'
import { useGroupStore } from '@/stores/group'
import { useContactStore } from '@/stores/contacts'
@ -18,7 +17,7 @@ let isFirstInit = false
export const useChatStore = defineStore('chat', () => {
const route = useRoute()
const cachedStore = useCachedStore()
const userStore = useUserStore()
// const userStore = useUserStore()
const globalStore = useGlobalStore()
const groupStore = useGroupStore()
const contactStore = useContactStore()
@ -125,7 +124,7 @@ export const useChatStore = defineStore('chat', () => {
if (currentRoomType.value === RoomTypeEnum.GROUP) {
groupStore.getGroupUserList(true)
groupStore.getCountStatistic()
cachedStore.getGroupAtUserBaseInfo()
// cachedStore.getGroupAtUserBaseInfo()
}
}
@ -219,7 +218,7 @@ export const useChatStore = defineStore('chat', () => {
// 请求第一个群成员列表
currentRoomType.value === RoomTypeEnum.GROUP && (await groupStore.getGroupUserList(true))
// 初始化所有用户基本信息
userStore.isSign && (await cachedStore.initAllUserBaseInfo())
// userStore.isSign && (await cachedStore.initAllUserBaseInfo())
// 联系人列表
await contactStore.getContactList(true)
}
@ -279,8 +278,7 @@ export const useChatStore = defineStore('chat', () => {
detailResponse = await apis.sessionDetail({ id: msg.message.roomId })
}
if (detailResponse) {
const data = detailResponse
updateSessionLastActiveTime(msg.message.roomId, data)
updateSessionLastActiveTime(msg.message.roomId, detailResponse)
}
}

View File

@ -11,6 +11,11 @@ export const useContactStore = defineStore('contact', () => {
const requestFriendsList = reactive<RequestFriendItem[]>([])
const contactsOptions = reactive({ isLast: false, isLoading: false, cursor: '' })
const requestFriendsOptions = reactive({ isLast: false, isLoading: false, cursor: '' })
/**
*
* @param isFresh
*/
const getContactList = async (isFresh = false) => {
if (!isFresh) {
if (contactsOptions.isLast || contactsOptions.isLoading) return
@ -45,6 +50,10 @@ export const useContactStore = defineStore('contact', () => {
}
}
/**
*
* @param isFresh
*/
const getRequestFriendsList = async (isFresh = false) => {
if (!isFresh) {
if (requestFriendsOptions.isLast || requestFriendsOptions.isLoading) return

View File

@ -112,8 +112,7 @@ export const useGroupStore = defineStore('group', () => {
// 获取群成员数量统计
const getCountStatistic = async () => {
const data = await apis.groupDetail({ id: currentRoomId.value })
countInfo.value = data
countInfo.value = await apis.groupDetail({ id: currentRoomId.value })
}
// 加载更多群成员

View File

@ -14,6 +14,8 @@
caret-color: #13987f; /** 光标颜色,可根据需求调整 */
white-space: pre-wrap; /** 保留空白符号并正常换行 */
word-break: break-word; /** 在长单词或URL地址内部进行换行 */
-webkit-white-space: pre-wrap; /** 兼容webkit浏览器 */
-webkit-word-break: break-word;
}
.ait {
@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]);

View File

@ -8,6 +8,8 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
ActionBar: typeof import('./../components/windows/ActionBar.vue')['default']
AddFriendsModal: typeof import('./../components/common/AddFriendsModal.vue')['default']
ApplyList: typeof import('./../components/rightBox/ApplyList.vue')['default']
ChatBox: typeof import('./../components/rightBox/chatBox/index.vue')['default']
ChatFooter: typeof import('./../components/rightBox/chatBox/ChatFooter.vue')['default']
ChatHeader: typeof import('./../components/rightBox/chatBox/ChatHeader.vue')['default']
@ -34,6 +36,7 @@ declare module 'vue' {
NDropdown: typeof import('naive-ui')['NDropdown']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NFlex: typeof import('naive-ui')['NFlex']
NGradientText: typeof import('naive-ui')['NGradientText']
NIcon: typeof import('naive-ui')['NIcon']
NIconWrapper: typeof import('naive-ui')['NIconWrapper']
NImage: typeof import('naive-ui')['NImage']
@ -57,6 +60,7 @@ declare module 'vue' {
NTab: typeof import('naive-ui')['NTab']
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs']
NText: typeof import('naive-ui')['NText']
NTooltip: typeof import('naive-ui')['NTooltip']
NVirtualList: typeof import('naive-ui')['NVirtualList']
RenderMessage: typeof import('./../components/rightBox/renderMessage/index.vue')['default']

View File

@ -63,11 +63,13 @@ declare namespace OPT {
* @param label
* @param icon
* @param click
* @param visible
*/
type RightMenu = {
label: string
icon: string
click?: (...args: any[]) => void
visible?: (...args: any[]) => void
} | null
/**

View File

@ -101,7 +101,7 @@
<n-flex v-if="!isLogining && !isWrongPassword" justify="space-around" align="center" :size="0" class="options">
<p class="text-(14px #fefefe)" @click="isUnlockPage = false">返回</p>
<p class="text-(14px #fefefe)" @click="logout">退出登录</p>
<p class="text-(14px #fefefe)" @click="logout()">退出登录</p>
<p class="text-(14px #fefefe)">忘记密码</p>
<p class="text-(14px #fff)" @click="unlock">进入系统</p>
</n-flex>

View File

@ -1,5 +1,13 @@
<template>
<n-scrollbar style="max-height: calc(100vh - 70px)">
<n-flex
@click="handleApply"
align="center"
justify="space-between"
class="my-10px p-12px hover:(bg-[--list-hover-color] cursor-pointer)">
<div class="text-(14px [--text-color])">好友通知</div>
<svg class="size-16px rotate-270 color-[--text-color]"><use href="#down"></use></svg>
</n-flex>
<n-tabs type="segment" animated class="mt-4px p-[4px_10px_0px_8px]">
<n-tab-pane name="1" tab="好友">
<n-scrollbar style="max-height: calc(100vh - 126px)">
@ -7,7 +15,7 @@
<ContextMenu @contextmenu="showMenu($event)" @select="handleSelect($event.label)" :menu="menuList">
<n-collapse-item title="我的好友" name="1">
<template #header-extra>
<span class="text-(10px #707070)">0/0</span>
<span class="text-(10px #707070)"> {{ onlineCount }}/{{ contactStore.contactsList.length }} </span>
</template>
<!-- 用户框 多套一层div来移除默认的右键事件然后覆盖掉因为margin空隙而导致右键可用 -->
@ -25,6 +33,8 @@
bordered
:color="'#fff'"
:size="44"
class="grayscale"
:class="{ 'grayscale-0': item.activeStatus === OnlineEnum.ONLINE }"
:src="useUserInfo(item.uid).value.avatar"
fallback-src="/logo.png" />
@ -33,9 +43,15 @@
useUserInfo(item.uid).value.name
}}</span>
<span class="text leading-tight text-12px flex-1 truncate">
[今日天气] 说的很经典哈萨克的哈萨克看到贺卡上
</span>
<div class="text leading-tight text-12px flex-y-center gap-2px flex-1 truncate">
[
<img
class="size-14px"
:src="`/status/${item.activeStatus === OnlineEnum.ONLINE ? 'online.png' : 'offline.png'}`"
alt="离线" />
{{ item.activeStatus === OnlineEnum.ONLINE ? '在线' : '离线' }}
]
</div>
</n-flex>
</n-flex>
</n-flex>
@ -64,7 +80,7 @@
</template>
<script setup lang="ts">
import Mitt from '@/utils/Bus.ts'
import { MittEnum, RoomTypeEnum } from '@/enums'
import { MittEnum, OnlineEnum, RoomTypeEnum } from '@/enums'
import { useContactStore } from '@/stores/contacts.ts'
import { useUserInfo } from '@/hooks/useCached.ts'
@ -78,6 +94,10 @@ const activeItem = ref(0)
const detailsShow = ref(false)
const shrinkStatus = ref(false)
const contactStore = useContactStore()
/** 统计在线用户人数 */
const onlineCount = computed(() => {
return contactStore.contactsList.filter((item) => item.activeStatus === OnlineEnum.ONLINE).length
})
/** 监听独立窗口关闭事件 */
watchEffect(() => {
Mitt.on(MittEnum.SHRINK_WINDOW, async (event) => {
@ -106,6 +126,15 @@ const handleSelect = (event: MouseEvent) => {
console.log(event)
}
const handleApply = () => {
Mitt.emit(MittEnum.APPLY_SHOW, {
context: {
type: 'apply'
}
})
activeItem.value = 0
}
onUnmounted(() => {
detailsShow.value = false
Mitt.emit(MittEnum.DETAILS_SHOW, detailsShow.value)
@ -126,7 +155,7 @@ onUnmounted(() => {
}
.active {
background: var(--bg-active-msg);
background: var(--msg-active-color);
border-radius: 12px;
color: #fff;
.text {

View File

@ -71,6 +71,7 @@ const globalStore = useGlobalStore()
const { userUid } = useCommon()
const scrollbar = ref()
const { handleMsgClick, activeIndex, menuList, specialMenuList, handleMsgDblclick } = useMessage()
// TODO
const sessionList = computed(() =>
chatStore.sessionList.map((item) => {
//

View File

@ -142,8 +142,8 @@ import { invoke } from '@tauri-apps/api/core'
import { emit } from '@tauri-apps/api/event'
const settingStore = useSettingStore()
const { themes, tips, escClose, chat, page } = settingStore
const { showMode } = storeToRefs(settingStore)
const { themes, tips, chat, page } = settingStore
const { showMode, escClose } = storeToRefs(settingStore)
const activeItem = ref<string>(themes.pattern)
const showText = computed({

View File

@ -264,6 +264,13 @@ const closeMenu = (event: MouseEvent) => {
}
onMounted(async () => {
//
if (localStorage.getItem('isToQrcode')) {
router.push('/qrCode')
await nextTick(() => {
localStorage.removeItem('isToQrcode')
})
}
await getCurrentWebviewWindow().show()
if (login.value.autoLogin && login.value.accountInfo.password !== '') {
autoLogin()