Merge branch 'master' into feat/AIChat
@@ -4,6 +4,10 @@
|
||||
|
||||
<p align="center">An Instant Messaging System Built with Tauri, Vite 6, Vue 3, and TypeScript</p>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://hellogithub.com/repository/743b101346c54f6cb5c20eed2edbaa40" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=743b101346c54f6cb5c20eed2edbaa40&claim_uid=WsQaY6SlnL7qxG3&theme=neutral" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://deepwiki.com/HuLaSpark/HuLa"><img src="https://deepwiki.com/badge.svg" alt=""></a>
|
||||
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2FHuLaSpark%2FHuLa?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2FHuLaSpark%2FHuLa.svg?type=shield" alt=""></a>
|
||||
@@ -219,6 +223,7 @@ Thanks to the following sponsors for their support!
|
||||
| 2025-04-01 | 墨 | ¥88.88 | 微信转账 |
|
||||
| 2025-02-8 | 邓伟 | ¥88 | 微信赞赏码 |
|
||||
| 2025-02-7 | dennis | ¥80 | gitee码云赞赏 |
|
||||
| 2025-05-15 | 孤鸿影 | ¥56 | 微信红包 |
|
||||
| 2025-02-6 | 小二 | ¥62 | 微信转账 |
|
||||
|
||||
> Note: This list is manually updated. If you have sponsored but are not displayed in the list, please contact us by:
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
|
||||
<p align="center">一款基于Tauri、Vite 6、Vue 3 和 TypeScript 构建的即时通讯系统</p>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://hellogithub.com/repository/743b101346c54f6cb5c20eed2edbaa40" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=743b101346c54f6cb5c20eed2edbaa40&claim_uid=WsQaY6SlnL7qxG3&theme=neutral" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://deepwiki.com/HuLaSpark/HuLa"><img src="https://deepwiki.com/badge.svg" alt=""></a>
|
||||
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2FHuLaSpark%2FHuLa?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2FHuLaSpark%2FHuLa.svg?type=shield" alt=""></a>
|
||||
@@ -219,6 +223,7 @@ sudo xattr -r -d com.apple.quarantine /Applications/应用名称.app
|
||||
| 2025-04-01 | 墨 | ¥88.88 | 微信转账 |
|
||||
| 2025-02-8 | 邓伟 | ¥88 | 微信赞赏码 |
|
||||
| 2025-02-7 | dennis | ¥80 | gitee码云赞赏 |
|
||||
| 2025-05-15 | 孤鸿影 | ¥56 | 微信红包 |
|
||||
| 2025-02-6 | 小二 | ¥62 | 微信转账 |
|
||||
|
||||
> 注:该名单为手动更新。如果您已赞助但未显示在列表中,请通过以下方式联系我们:
|
||||
|
||||
BIN
public/msgAction/bomb.png
Normal file
|
After Width: | Height: | Size: 736 KiB |
BIN
public/msgAction/clapping.png
Normal file
|
After Width: | Height: | Size: 483 KiB |
BIN
public/msgAction/enraged-face.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
public/msgAction/exploding-head.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
public/msgAction/face-with-tears-of-joy.png
Normal file
|
After Width: | Height: | Size: 728 KiB |
BIN
public/msgAction/flashlight.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
public/msgAction/heart-on-fire.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
public/msgAction/like.png
Normal file
|
After Width: | Height: | Size: 762 KiB |
BIN
public/msgAction/pocket-money.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/msgAction/rose.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/msgAction/slightly-frowning-face.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
public/msgAction/victory-hand.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
152
src-tauri/Cargo.lock
generated
@@ -54,7 +54,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43"
|
||||
dependencies = [
|
||||
"alsa-sys",
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
@@ -371,7 +371,7 @@ version = "0.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
@@ -391,9 +391,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.0"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -504,7 +504,7 @@ version = "0.18.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"cairo-sys-rs",
|
||||
"glib",
|
||||
"libc",
|
||||
@@ -567,9 +567,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.22"
|
||||
version = "1.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1"
|
||||
checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
@@ -669,7 +669,7 @@ version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"block",
|
||||
"cocoa-foundation",
|
||||
"core-foundation 0.10.0",
|
||||
@@ -685,7 +685,7 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"block",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics-types 0.2.0",
|
||||
@@ -811,7 +811,7 @@ version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics-types 0.2.0",
|
||||
"foreign-types 0.5.0",
|
||||
@@ -835,7 +835,7 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation 0.10.0",
|
||||
"libc",
|
||||
]
|
||||
@@ -1161,7 +1161,7 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a0d569e003ff27784e0e14e4a594048698e0c0f0b66cabcb51511be55a7caa0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"block2 0.6.1",
|
||||
"libc",
|
||||
"objc2 0.6.1",
|
||||
@@ -1173,7 +1173,7 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"objc2 0.6.1",
|
||||
]
|
||||
|
||||
@@ -1365,9 +1365,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.11"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
|
||||
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -1881,7 +1881,7 @@ version = "0.18.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
@@ -2364,7 +2364,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.61.0",
|
||||
"windows-core 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2688,7 +2688,7 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"serde",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
@@ -2798,7 +2798,7 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
@@ -3024,7 +3024,7 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"ndk-sys 0.5.0+25.2.9519653",
|
||||
@@ -3038,7 +3038,7 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"ndk-sys 0.6.0+11769913",
|
||||
@@ -3083,7 +3083,7 @@ version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -3246,7 +3246,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"block2 0.6.1",
|
||||
"libc",
|
||||
"objc2 0.6.1",
|
||||
@@ -3265,7 +3265,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
@@ -3276,7 +3276,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
@@ -3287,7 +3287,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"dispatch2 0.3.0",
|
||||
"objc2 0.6.1",
|
||||
]
|
||||
@@ -3298,7 +3298,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"dispatch2 0.3.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
@@ -3336,7 +3336,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"block2 0.5.1",
|
||||
"libc",
|
||||
"objc2 0.5.2",
|
||||
@@ -3348,7 +3348,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"block2 0.6.1",
|
||||
"libc",
|
||||
"objc2 0.6.1",
|
||||
@@ -3361,7 +3361,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
@@ -3372,7 +3372,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
@@ -3384,7 +3384,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26bb88504b5a050dbba515d2414607bf5e57dd56b107bc5f0351197a3e7bdc5d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.1",
|
||||
@@ -3396,7 +3396,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
@@ -3409,7 +3409,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
@@ -3420,7 +3420,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.1",
|
||||
@@ -3432,7 +3432,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"block2 0.6.1",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
@@ -3505,7 +3505,7 @@ version = "0.10.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"foreign-types 0.3.2",
|
||||
"libc",
|
||||
@@ -4247,7 +4247,7 @@ version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4501,7 +4501,7 @@ version = "0.38.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
@@ -4514,7 +4514,7 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.4",
|
||||
@@ -4692,7 +4692,7 @@ version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation 0.9.4",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
@@ -5151,7 +5151,7 @@ checksum = "0afdd3aa7a629683c2d750c2df343025545087081ab5942593a5288855b1b7a7"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"crc",
|
||||
@@ -5194,7 +5194,7 @@ checksum = "a0bedbe1bbb5e2615ef347a5e9d8cd7680fb63e77d9dafc0f29be15e53f1ebe6"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"byteorder",
|
||||
"crc",
|
||||
"dotenvy",
|
||||
@@ -5443,7 +5443,7 @@ version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys 0.6.0",
|
||||
]
|
||||
@@ -5487,7 +5487,7 @@ version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics 0.24.0",
|
||||
"crossbeam-channel",
|
||||
@@ -5515,7 +5515,7 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
"url",
|
||||
"windows 0.61.1",
|
||||
"windows-core 0.61.0",
|
||||
"windows-core 0.61.1",
|
||||
"windows-version",
|
||||
"x11-dl",
|
||||
]
|
||||
@@ -6803,7 +6803,7 @@ version = "0.31.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"rustix 0.38.44",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
@@ -6815,7 +6815,7 @@ version = "0.32.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
@@ -6827,7 +6827,7 @@ version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
@@ -6951,7 +6951,7 @@ dependencies = [
|
||||
"webview2-com-macros",
|
||||
"webview2-com-sys",
|
||||
"windows 0.61.1",
|
||||
"windows-core 0.61.0",
|
||||
"windows-core 0.61.1",
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
]
|
||||
@@ -6975,7 +6975,7 @@ checksum = "8ae2d11c4a686e4409659d7891791254cf9286d3cfe0eef54df1523533d22295"
|
||||
dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"windows 0.61.1",
|
||||
"windows-core 0.61.0",
|
||||
"windows-core 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7082,7 +7082,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core 0.61.0",
|
||||
"windows-core 0.61.1",
|
||||
"windows-future",
|
||||
"windows-link",
|
||||
"windows-numerics",
|
||||
@@ -7094,7 +7094,7 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
||||
dependencies = [
|
||||
"windows-core 0.61.0",
|
||||
"windows-core 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7118,25 +7118,26 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.0"
|
||||
version = "0.61.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||
checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result 0.3.2",
|
||||
"windows-strings 0.4.0",
|
||||
"windows-result 0.3.3",
|
||||
"windows-strings 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32"
|
||||
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||
dependencies = [
|
||||
"windows-core 0.61.0",
|
||||
"windows-core 0.61.1",
|
||||
"windows-link",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7173,7 +7174,7 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||
dependencies = [
|
||||
"windows-core 0.61.0",
|
||||
"windows-core 0.61.1",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
@@ -7183,7 +7184,7 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
|
||||
dependencies = [
|
||||
"windows-result 0.3.2",
|
||||
"windows-result 0.3.3",
|
||||
"windows-strings 0.3.1",
|
||||
"windows-targets 0.53.0",
|
||||
]
|
||||
@@ -7199,9 +7200,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
|
||||
checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
@@ -7217,9 +7218,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
|
||||
checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
@@ -7322,6 +7323,15 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-version"
|
||||
version = "0.1.4"
|
||||
@@ -7564,7 +7574,7 @@ version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bitflags 2.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7631,7 +7641,7 @@ dependencies = [
|
||||
"webkit2gtk-sys",
|
||||
"webview2-com",
|
||||
"windows 0.61.1",
|
||||
"windows-core 0.61.0",
|
||||
"windows-core 0.61.1",
|
||||
"windows-version",
|
||||
"x11-dl",
|
||||
]
|
||||
@@ -7862,15 +7872,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "2.6.1"
|
||||
version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744"
|
||||
checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"displaydoc",
|
||||
"indexmap 2.9.0",
|
||||
"memchr",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -160,6 +160,11 @@ onUnmounted(() => {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
img {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
a {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<n-popover :delay="500" :duration="0" trigger="hover" :show-arrow="false" placement="top">
|
||||
<template #trigger>
|
||||
<div class="emoji-item" @click="handleReplyEmoji(item)">
|
||||
{{ item.label }}
|
||||
<img :title="item.title" class="size-18px" :src="item.url" :alt="item.title" />
|
||||
</div>
|
||||
</template>
|
||||
<span>{{ item.title }}</span>
|
||||
|
||||
@@ -105,7 +105,15 @@
|
||||
<span class="text-[--info-text-color]">获得的徽章</span>
|
||||
<n-flex>
|
||||
<template v-for="id in isCurrentUser.itemIds" :key="id">
|
||||
<img class="size-38px" :src="useBadgeInfo(id).value.img" alt="" />
|
||||
<n-skeleton v-if="!badgeLoadedMap[id]" text :repeat="1" :width="38" :height="38" circle />
|
||||
<n-avatar
|
||||
v-show="badgeLoadedMap[id]"
|
||||
round
|
||||
:width="38"
|
||||
:height="38"
|
||||
:src="useBadgeInfo(id).value.img"
|
||||
@load="badgeLoadedMap[id] = true"
|
||||
@error="badgeLoadedMap[id] = true" />
|
||||
</template>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
@@ -159,6 +167,8 @@ const contactStore = useContactStore()
|
||||
const userStatusStore = useUserStatusStore()
|
||||
const { stateList } = storeToRefs(userStatusStore)
|
||||
const isCurrentUser = computed(() => useUserInfo(uid).value)
|
||||
/** 头像加载状态 */
|
||||
const badgeLoadedMap = ref<Record<string, boolean>>({})
|
||||
const avatarSrc = computed(() => AvatarUtils.getAvatarUrl(useUserInfo(uid).value.avatar as string))
|
||||
/** 是否是当前登录的用户 */
|
||||
const isCurrentUserUid = computed(() => userUid.value === uid)
|
||||
|
||||
@@ -250,7 +250,7 @@
|
||||
</div>
|
||||
<!-- 信息时间(群聊) -->
|
||||
<Transition name="fade-group">
|
||||
<span v-if="isGroup && hoverBubble.key === item.message.id" class="text-(12px #909090)">
|
||||
<span v-if="isGroup && hoverBubble.key === item.message.id" class="text-(12px #909090) select-none">
|
||||
{{ formatTimestamp(item.message.sendTime, true) }}
|
||||
</span>
|
||||
</Transition>
|
||||
@@ -373,7 +373,7 @@
|
||||
<!-- 根据表情类型获取对应的计数属性名 -->
|
||||
<div class="flex-y-center" v-if="getEmojiCount(item, emoji.value) > 0">
|
||||
<div class="emoji-reply-bubble" @click.stop="cancelReplyEmoji(item, emoji.value)">
|
||||
{{ emoji.label }}
|
||||
<img :title="emoji.title" class="size-18px" :src="emoji.url" :alt="emoji.title" />
|
||||
<span class="text-(12px #eee)">{{ getEmojiCount(item, emoji.value) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,12 +27,20 @@
|
||||
@click="handleOpenAnnoun(announNum === 0 && isAddAnnoun)">
|
||||
<p class="text-(14px --text-color) font-bold">群公告须知</p>
|
||||
<svg class="size-16px rotate-270 color-[--text-color]">
|
||||
<use v-if="announNum === 0 && isAddAnnoun" href="#plus"></use>
|
||||
<use v-if="announNum === 0 && isAddAnnoun && !isLoadingAnnouncement" href="#plus"></use>
|
||||
<use v-else href="#down"></use>
|
||||
</svg>
|
||||
</n-flex>
|
||||
|
||||
<n-scrollbar class="h-74px">
|
||||
<!-- 公告骨架屏 -->
|
||||
<n-flex v-if="isLoadingAnnouncement" class="h-74px">
|
||||
<n-skeleton text class="rounded-4px" :repeat="1" :width="100" />
|
||||
<n-skeleton text class="rounded-4px" :repeat="1" :width="60" />
|
||||
<n-skeleton text class="rounded-4px" :repeat="1" :width="60" />
|
||||
</n-flex>
|
||||
|
||||
<!-- 公告内容 -->
|
||||
<n-scrollbar v-else class="h-74px">
|
||||
<p class="text-(12px #909090) leading-6 line-clamp-4 max-w-99%" v-if="announNum === 0">
|
||||
请不要把重要信息发到该群,网络不是法外之地,请遵守网络规范,否则直接删除。
|
||||
</p>
|
||||
@@ -43,10 +51,16 @@
|
||||
</n-flex>
|
||||
|
||||
<n-flex v-if="!isSearch" align="center" justify="space-between" class="pr-8px pl-8px h-42px">
|
||||
<span class="text-14px">在线群聊成员 {{ groupStore.countInfo.onlineNum }}</span>
|
||||
<svg @click="handleSelect" class="size-14px">
|
||||
<use href="#search"></use>
|
||||
</svg>
|
||||
<n-skeleton v-if="isLoadingMembers" text class="rounded-4px" :width="80" />
|
||||
<template v-else>
|
||||
<span class="text-14px">
|
||||
在线群聊成员
|
||||
{{ groupStore.countInfo.onlineNum }}
|
||||
</span>
|
||||
<svg @click="handleSelect" class="size-14px">
|
||||
<use href="#search"></use>
|
||||
</svg>
|
||||
</template>
|
||||
</n-flex>
|
||||
<!-- 搜索框 -->
|
||||
<n-flex v-else align="center" class="pr-8px h-42px">
|
||||
@@ -71,7 +85,22 @@
|
||||
<!-- // TODO popover显示的时候去改变窗口的大小、当点击了半个选项的时候也会出现原生滚动条 (nyh -> 2024-03-25 05:04:37) -->
|
||||
<!-- // TODO 如果popover显示就先暂时不让滚动,因为在n-scrollbar和n-virtual-list中使用当我点击最后一个选项时候n-popover位置不够导致出现原生滚动条 (nyh -> 2024-03-24 22:46:38) -->
|
||||
<!-- // TODO 如果直接使用n-virtual-list的滚动配上n-popover乎也没有这个bug,但是当点击倒数第二个的时候还是会出现滚动条 (nyh -> 2024-03-25 00:30:53) -->
|
||||
<!-- 骨架屏加载中 -->
|
||||
<div v-if="isLoadingMembers" style="max-height: calc(100vh - 260px); overflow-y: hidden">
|
||||
<n-flex v-for="i in 10" :key="i" align="center" justify="space-between" class="item px-8px py-10px">
|
||||
<n-flex align="center" :size="8" class="flex-1 truncate">
|
||||
<n-skeleton text :repeat="1" :width="26" :height="26" circle />
|
||||
<n-flex vertical :size="2" class="flex-1 truncate ml-8px">
|
||||
<n-skeleton text class="rounded-4px" :width="80" />
|
||||
<n-skeleton text class="rounded-4px" :width="60" />
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</div>
|
||||
|
||||
<!-- 成员列表 -->
|
||||
<n-virtual-list
|
||||
v-else
|
||||
id="image-chat-sidebar"
|
||||
style="max-height: calc(100vh - 260px)"
|
||||
item-resizable
|
||||
@@ -100,13 +129,18 @@
|
||||
justify="space-between"
|
||||
class="item">
|
||||
<n-flex align="center" :size="8" class="flex-1 truncate">
|
||||
<n-avatar
|
||||
round
|
||||
class="grayscale"
|
||||
:class="{ 'grayscale-0': item.activeStatus === OnlineEnum.ONLINE }"
|
||||
:color="'#fff'"
|
||||
:size="26"
|
||||
:src="AvatarUtils.getAvatarUrl(item.avatar)" />
|
||||
<div class="relative inline-flex items-center justify-center">
|
||||
<n-skeleton v-if="!avatarLoadedMap[item.uid]" text :repeat="1" :width="26" :height="26" circle />
|
||||
<n-avatar
|
||||
v-show="avatarLoadedMap[item.uid]"
|
||||
round
|
||||
class="grayscale"
|
||||
:class="{ 'grayscale-0': item.activeStatus === OnlineEnum.ONLINE }"
|
||||
:size="26"
|
||||
:src="AvatarUtils.getAvatarUrl(item.avatar)"
|
||||
@load="avatarLoadedMap[item.uid] = true"
|
||||
@error="avatarLoadedMap[item.uid] = true" />
|
||||
</div>
|
||||
<n-flex vertical :size="2" class="flex-1 truncate">
|
||||
<p :title="item.name" class="text-12px truncate flex-1">{{ item.name }}</p>
|
||||
<n-flex
|
||||
@@ -170,6 +204,12 @@ const globalStore = useGlobalStore()
|
||||
const cachedStore = useCachedStore()
|
||||
const userStore = useUserStore()
|
||||
const { addListener } = useTauriListener()
|
||||
// 当前加载的群聊ID
|
||||
const currentLoadingRoomId = ref('')
|
||||
// 成员列表加载状态
|
||||
const isLoadingMembers = ref(true)
|
||||
// 公告列表加载状态
|
||||
const isLoadingAnnouncement = ref(true)
|
||||
const groupUserList = computed(() => groupStore.userList)
|
||||
const userList = computed(() => {
|
||||
// 先获取所有需要的用户ID
|
||||
@@ -241,6 +281,9 @@ const announList = ref<any[]>([])
|
||||
const announNum = ref(0)
|
||||
const isAddAnnoun = ref(false)
|
||||
|
||||
/** 头像加载状态 */
|
||||
const avatarLoadedMap = ref<Record<string, boolean>>({})
|
||||
|
||||
// 添加一个新的计算属性来合并用户列表
|
||||
const mergedUserList = computed(() => {
|
||||
// 创建一个Map用于去重,使用uid作为key
|
||||
@@ -279,6 +322,11 @@ watch(
|
||||
} else {
|
||||
filteredUserList.value = userList.value
|
||||
}
|
||||
|
||||
// 判断成员列表是否已加载完成
|
||||
if (userList.value.length > 0 && currentLoadingRoomId.value === globalStore.currentSession?.roomId) {
|
||||
isLoadingMembers.value = false
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
@@ -346,6 +394,9 @@ const handleOpenAnnoun = (isAdd: boolean) => {
|
||||
* 加载群公告
|
||||
*/
|
||||
const handleLoadGroupAnnoun = async (roomId: string) => {
|
||||
// 设置公告加载状态为加载中
|
||||
isLoadingAnnouncement.value = true
|
||||
|
||||
// 设置是否可以添加公告
|
||||
isAddAnnoun.value = isLord.value || isAdmin.value || hasBadge6.value!
|
||||
// 获取群公告列表
|
||||
@@ -361,6 +412,9 @@ const handleLoadGroupAnnoun = async (roomId: string) => {
|
||||
}
|
||||
announNum.value = parseInt(data.total)
|
||||
}
|
||||
|
||||
// 加载完成后,关闭骨架屏
|
||||
isLoadingAnnouncement.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,9 +423,6 @@ const handleLoadGroupAnnoun = async (roomId: string) => {
|
||||
const handleInitAnnoun = async () => {
|
||||
// 初始化时获取群公告
|
||||
if (isGroup.value) {
|
||||
if (globalStore.currentSession?.roomId) {
|
||||
await groupStore.getGroupUserList(true, globalStore.currentSession.roomId)
|
||||
}
|
||||
const roomId = globalStore.currentSession?.roomId
|
||||
if (roomId) {
|
||||
await handleLoadGroupAnnoun(roomId)
|
||||
@@ -412,6 +463,29 @@ onMounted(async () => {
|
||||
})
|
||||
)
|
||||
|
||||
// 监听会话变化
|
||||
watch(
|
||||
() => globalStore.currentSession,
|
||||
async (newSession, oldSession) => {
|
||||
if (newSession?.type === RoomTypeEnum.GROUP) {
|
||||
// 如果切换到不同的群聊会话,重置加载状态
|
||||
if (newSession?.roomId !== oldSession?.roomId) {
|
||||
isLoadingMembers.value = true
|
||||
isLoadingAnnouncement.value = true
|
||||
currentLoadingRoomId.value = newSession.roomId
|
||||
|
||||
// 重置群组数据后再加载新的群成员数据
|
||||
groupStore.resetGroupData()
|
||||
await groupStore.getGroupUserList(true, newSession.roomId)
|
||||
|
||||
// 初始化群公告
|
||||
await handleInitAnnoun()
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 初始化时获取当前群组用户的信息
|
||||
if (groupUserList.value.length > 0) {
|
||||
await cachedStore.getBatchUserInfo(groupUserList.value.map((item) => item.uid))
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<n-scrollbar style="max-height: 290px" class="p-[14px_14px_0_14px] box-border w-460px h-290px select-none">
|
||||
<n-scrollbar
|
||||
style="max-height: 290px"
|
||||
class="p-[14px_14px_0_14px] box-border w-460px h-290px select-none"
|
||||
@scroll="activeMenuId = ''">
|
||||
<transition name="fade" mode="out-in">
|
||||
<div :key="activeIndex" class="emoji-content">
|
||||
<!-- 最近使用 -->
|
||||
@@ -49,15 +52,11 @@
|
||||
v-for="(item, index) in currentSeries.emojis"
|
||||
:key="index"
|
||||
@click.stop="chooseEmoji(item.url, 'url')">
|
||||
<n-popover trigger="hover" :delay="500" :duration="0" :show-arrow="false" placement="top">
|
||||
<template #trigger>
|
||||
<n-image
|
||||
preview-disabled
|
||||
:src="item.url"
|
||||
class="size-full object-contain rounded-8px transition duration-300 ease-in-out transform-gpu" />
|
||||
</template>
|
||||
<span>{{ item.name }}</span>
|
||||
</n-popover>
|
||||
<n-image
|
||||
:title="item.name"
|
||||
preview-disabled
|
||||
:src="item.url"
|
||||
class="size-full object-contain rounded-8px transition duration-300 ease-in-out transform-gpu" />
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</div>
|
||||
@@ -74,13 +73,20 @@
|
||||
v-for="(item, index) in emojiStore.emojiList"
|
||||
:key="index"
|
||||
@click.stop="chooseEmoji(item.expressionUrl, 'url')">
|
||||
<n-popover trigger="hover" :delay="600" :duration="0" :show-arrow="false" placement="top">
|
||||
<n-popover
|
||||
trigger="manual"
|
||||
:show="activeMenuId === item.id"
|
||||
:duration="300"
|
||||
:show-arrow="false"
|
||||
placement="top"
|
||||
@clickoutside="activeMenuId = ''">
|
||||
<template #trigger>
|
||||
<n-image
|
||||
width="60"
|
||||
height="60"
|
||||
preview-disabled
|
||||
:src="item.expressionUrl"
|
||||
@contextmenu.prevent="handleContextMenu($event, item)"
|
||||
class="size-full object-contain rounded-8px transition duration-300 ease-in-out transform-gpu" />
|
||||
</template>
|
||||
<n-button quaternary size="tiny" @click.stop="deleteMyEmoji(item.id)">
|
||||
@@ -164,6 +170,8 @@ const emojiStore = useEmojiStore()
|
||||
const emojisBbs = HulaEmojis.MihoyoBbs
|
||||
const activeIndex = ref(lastEmojiTabIndex)
|
||||
const currentSeriesIndex = ref(0)
|
||||
// 设置当前右键点击的表情项ID
|
||||
const activeMenuId = ref('')
|
||||
|
||||
// 生成选项卡数组
|
||||
const tabList = computed<TabItem[]>(() => {
|
||||
@@ -225,6 +233,17 @@ const checkIsUrl = (str: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理右键菜单点击事件
|
||||
* @param event 鼠标事件
|
||||
* @param item 表情项
|
||||
*/
|
||||
const handleContextMenu = (event: MouseEvent, item: any) => {
|
||||
// 阻止原生右键菜单
|
||||
event.preventDefault()
|
||||
activeMenuId.value = item.id
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除我的表情包
|
||||
* @param id 表情包ID
|
||||
@@ -233,6 +252,8 @@ const deleteMyEmoji = async (id: string) => {
|
||||
try {
|
||||
await emojiStore.deleteEmoji(id)
|
||||
window.$message.success('删除表情成功')
|
||||
// 关闭菜单
|
||||
activeMenuId.value = ''
|
||||
} catch (error) {
|
||||
console.error('删除表情失败:', error)
|
||||
window.$message.error('删除表情失败')
|
||||
|
||||
@@ -415,72 +415,72 @@ export const useChatMain = () => {
|
||||
/** emoji表情菜单 */
|
||||
const emojiList = ref([
|
||||
{
|
||||
label: '👍',
|
||||
url: '/msgAction/like.png',
|
||||
value: 1,
|
||||
title: '好赞'
|
||||
},
|
||||
{
|
||||
label: '☹️',
|
||||
url: '/msgAction/slightly-frowning-face.png',
|
||||
value: 2,
|
||||
title: '不满'
|
||||
},
|
||||
{
|
||||
label: '❤️',
|
||||
url: '/msgAction/heart-on-fire.png',
|
||||
value: 3,
|
||||
title: '爱心'
|
||||
},
|
||||
{
|
||||
label: '😡',
|
||||
url: '/msgAction/enraged-face.png',
|
||||
value: 4,
|
||||
title: '愤怒'
|
||||
},
|
||||
{
|
||||
label: '🎉',
|
||||
url: '/emoji/party-popper.webp',
|
||||
value: 5,
|
||||
title: '礼炮'
|
||||
},
|
||||
{
|
||||
label: '🚀',
|
||||
url: '/emoji/rocket.webp',
|
||||
value: 6,
|
||||
title: '火箭'
|
||||
},
|
||||
{
|
||||
label: '😂',
|
||||
url: '/msgAction/face-with-tears-of-joy.png',
|
||||
value: 7,
|
||||
title: '笑哭'
|
||||
},
|
||||
{
|
||||
label: '👏',
|
||||
url: '/msgAction/clapping.png',
|
||||
value: 8,
|
||||
title: '鼓掌'
|
||||
},
|
||||
{
|
||||
label: '🌹',
|
||||
url: '/msgAction/rose.png',
|
||||
value: 9,
|
||||
title: '鲜花'
|
||||
},
|
||||
{
|
||||
label: '💣',
|
||||
url: '/msgAction/bomb.png',
|
||||
value: 10,
|
||||
title: '炸弹'
|
||||
},
|
||||
{
|
||||
label: '🤯',
|
||||
url: '/msgAction/exploding-head.png',
|
||||
value: 11,
|
||||
title: '疑问'
|
||||
},
|
||||
{
|
||||
label: '✌️',
|
||||
url: '/msgAction/victory-hand.png',
|
||||
value: 12,
|
||||
title: '胜利'
|
||||
},
|
||||
{
|
||||
label: '💡',
|
||||
url: '/msgAction/flashlight.png',
|
||||
value: 13,
|
||||
title: '灯光'
|
||||
},
|
||||
{
|
||||
label: '🧧',
|
||||
url: '/msgAction/pocket-money.png',
|
||||
value: 14,
|
||||
title: '红包'
|
||||
}
|
||||
|
||||
138
src/hooks/useReplaceMsg.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import type { MessageType } from '@/services/types'
|
||||
import { MsgEnum, RoomTypeEnum } from '@/enums'
|
||||
import { renderReplyContent } from '@/utils/RenderReplyContent.ts'
|
||||
import { useCommon } from '@/hooks/useCommon.ts'
|
||||
import { useUserInfo } from '@/hooks/useCached.ts'
|
||||
|
||||
/**
|
||||
* 用于处理消息内容展示的hook,包括@提醒和撤回消息处理
|
||||
*/
|
||||
export const useReplaceMsg = () => {
|
||||
const userStore = useUserStore()
|
||||
|
||||
/**
|
||||
* 检查单条消息是否@当前用户
|
||||
* @param message 消息对象
|
||||
* @returns 是否@当前用户
|
||||
*/
|
||||
const checkMessageAtMe = (message: MessageType) => {
|
||||
if (!message?.message?.body?.atUidList || !userStore.userInfo.uid) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 确保类型一致的比较
|
||||
return message.message.body.atUidList.some((atUid: string) => String(atUid) === String(userStore.userInfo.uid))
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查消息是否@当前用户及是否在未读范围内
|
||||
* @param roomId 房间ID
|
||||
* @param roomType 房间类型
|
||||
* @param currentRoomId 当前激活的房间ID
|
||||
* @param messages 消息列表
|
||||
* @param unreadCount 未读消息数量
|
||||
* @returns 是否有人@当前用户
|
||||
*/
|
||||
const checkRoomAtMe = (
|
||||
roomId: string,
|
||||
roomType: RoomTypeEnum,
|
||||
currentRoomId: string,
|
||||
messages: MessageType[],
|
||||
unreadCount: number = 0
|
||||
) => {
|
||||
// 仅在群聊且不是当前会话时才考虑@提醒
|
||||
if (roomType !== RoomTypeEnum.GROUP || roomId === currentRoomId) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 过滤出@当前用户的消息
|
||||
const messagesWithAt = messages.filter(checkMessageAtMe)
|
||||
|
||||
// 检查是否有@我的消息以及是否在未读范围内
|
||||
return messagesWithAt.some((msg) => messages.indexOf(msg) >= messages.length - (unreadCount || 0))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取带有@提醒标记的消息内容
|
||||
* @param isAtMe 是否@当前用户
|
||||
* @param content 原始消息内容
|
||||
* @returns 带有@提醒标记的消息内容
|
||||
*/
|
||||
const getAtMeContent = (isAtMe: boolean, content: string) => {
|
||||
if (!isAtMe) return content
|
||||
|
||||
const atPrefix = '<span class="text-#d5304f mr-4px">[有人@我]</span>'
|
||||
return `${atPrefix}${content}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理撤回消息显示逻辑
|
||||
* @param message 消息对象
|
||||
* @param roomType 房间类型
|
||||
* @param userName 发送消息用户的名称
|
||||
* @returns 格式化后的撤回消息文本
|
||||
*/
|
||||
const formatRecallMessage = (message: MessageType, roomType: RoomTypeEnum, userName: string) => {
|
||||
const { userUid } = useCommon()
|
||||
|
||||
if (roomType === RoomTypeEnum.GROUP) {
|
||||
return `${userName}:撤回了一条消息`
|
||||
} else {
|
||||
return message.fromUser.uid === userUid.value ? '你撤回了一条消息' : '对方撤回了一条消息'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息发送者的用户名
|
||||
* @param message 消息对象
|
||||
* @param defaultName 默认名称(可选)
|
||||
* @returns 发送者用户名
|
||||
*/
|
||||
const getMessageSenderName = (message: MessageType, defaultName: string = '') => {
|
||||
if (!message?.fromUser?.uid) return defaultName
|
||||
return useUserInfo(message.fromUser.uid).value.name || defaultName
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理消息内容,包括撤回消息和@提醒
|
||||
* @param message 消息对象
|
||||
* @param roomType 房间类型
|
||||
* @param userName 发送消息用户的名称(可选,如果不提供会自动从消息中提取)
|
||||
* @param isAtMe 是否有人@我
|
||||
* @returns 格式化后的消息内容
|
||||
*/
|
||||
const formatMessageContent = (
|
||||
message: MessageType,
|
||||
roomType: RoomTypeEnum,
|
||||
userName: string = '',
|
||||
isAtMe: boolean
|
||||
) => {
|
||||
// 如果没有提供用户名,自动从消息中获取
|
||||
const senderName = userName || getMessageSenderName(message)
|
||||
// 判断是否是撤回消息
|
||||
if (message.message?.type === MsgEnum.RECALL) {
|
||||
return formatRecallMessage(message, roomType, senderName)
|
||||
}
|
||||
|
||||
// 正常消息,处理内容
|
||||
const messageContent = renderReplyContent(
|
||||
senderName,
|
||||
message.message?.type,
|
||||
message.message?.body?.content || message.message?.body,
|
||||
roomType
|
||||
) as string
|
||||
|
||||
// 处理@提醒
|
||||
return getAtMeContent(isAtMe, messageContent)
|
||||
}
|
||||
|
||||
return {
|
||||
checkMessageAtMe,
|
||||
checkRoomAtMe,
|
||||
getAtMeContent,
|
||||
formatRecallMessage,
|
||||
formatMessageContent,
|
||||
getMessageSenderName
|
||||
}
|
||||
}
|
||||
@@ -29,14 +29,16 @@
|
||||
<div class="flex-center gap-5px w-full pr-16px pl-16px box-border">
|
||||
<n-input
|
||||
id="search"
|
||||
v-model:value="searchText"
|
||||
@focus="() => handleSearchFocus()"
|
||||
@blur="() => (searchText = '搜索')"
|
||||
@update:value="handleSearchInputChange"
|
||||
class="rounded-6px w-full relative text-12px"
|
||||
style="background: var(--search-bg-color)"
|
||||
:maxlength="20"
|
||||
clearable
|
||||
size="small"
|
||||
:placeholder="searchText">
|
||||
:placeholder="isSearchMode ? '' : '搜索'">
|
||||
<template #prefix>
|
||||
<svg class="w-12px h-12px"><use href="#search"></use></svg>
|
||||
</template>
|
||||
@@ -150,6 +152,8 @@ const isDrag = ref(true)
|
||||
const currentMsg = ref()
|
||||
/** 搜索框文字 */
|
||||
const searchText = ref('搜索')
|
||||
/** 是否处于搜索模式 */
|
||||
const isSearchMode = ref(false)
|
||||
/** 添加面板是否显示 */
|
||||
const addPanels = ref({
|
||||
show: false,
|
||||
@@ -210,6 +214,20 @@ const handleCreateGroup = async () => {
|
||||
const handleSearchFocus = () => {
|
||||
router.push('/searchDetails')
|
||||
searchText.value = ''
|
||||
isSearchMode.value = true
|
||||
|
||||
// 延迟发送当前搜索框的值到SearchDetails组件
|
||||
setTimeout(() => {
|
||||
useMitt.emit('search_input_change', searchText.value)
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// 处理搜索输入变化
|
||||
const handleSearchInputChange = (value: string) => {
|
||||
// 如果处于搜索详情页面,将输入值发送到SearchDetails组件
|
||||
if (isSearchMode.value) {
|
||||
useMitt.emit('search_input_change', value)
|
||||
}
|
||||
}
|
||||
|
||||
const closeMenu = (event: Event) => {
|
||||
@@ -218,6 +236,7 @@ const closeMenu = (event: Event) => {
|
||||
/** 判断如果点击的搜索框,就关闭消息列表 */
|
||||
if (!e.matches('#search, #search *, #centerList *, #centerList') && route === '/searchDetails') {
|
||||
router.go(-1)
|
||||
isSearchMode.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ import { clearListener, initListener, readCountQueue } from '@/utils/ReadCountQu
|
||||
import { type } from '@tauri-apps/plugin-os'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { useCheckUpdate } from '@/hooks/useCheckUpdate'
|
||||
import { UserAttentionType } from '@tauri-apps/api/window'
|
||||
|
||||
const loadingPercentage = ref(10)
|
||||
const loadingText = ref('正在加载应用...')
|
||||
@@ -231,7 +232,6 @@ useMitt.on(WsResponseMessageType.MY_ROOM_INFO_CHANGE, (data: { myName: string; r
|
||||
})
|
||||
useMitt.on(WsResponseMessageType.RECEIVE_MESSAGE, async (data: MessageType) => {
|
||||
chatStore.pushMsg(data)
|
||||
// 接收到通知就设置图标闪烁
|
||||
const username = useUserInfo(data.fromUser.uid).value.name!
|
||||
const home = await WebviewWindow.getByLabel('home')
|
||||
// 当home窗口不显示并且home窗口不是最小化的时候并且不是聚焦窗口的时候
|
||||
@@ -242,20 +242,21 @@ useMitt.on(WsResponseMessageType.RECEIVE_MESSAGE, async (data: MessageType) => {
|
||||
|
||||
// 不是自己发的消息才通知
|
||||
if (data.fromUser.uid !== userUid.value) {
|
||||
useMitt.emit(MittEnum.MESSAGE_ANIMATION, data)
|
||||
// 在windows系统下才发送通知
|
||||
if (type() === 'windows') {
|
||||
await emitTo('tray', 'show_tip')
|
||||
}
|
||||
|
||||
// 获取该消息的会话信息
|
||||
const session = chatStore.sessionList.find((s) => s.roomId === data.message.roomId)
|
||||
|
||||
// 只有非免打扰的会话才发送通知
|
||||
// 只有非免打扰的会话才发送通知和触发图标闪烁
|
||||
if (session && session.muteNotification !== NotificationTypeEnum.NOT_DISTURB) {
|
||||
// 设置图标闪烁
|
||||
useMitt.emit(MittEnum.MESSAGE_ANIMATION, data)
|
||||
// 在windows系统下才发送通知
|
||||
if (type() === 'windows') {
|
||||
await emitTo('tray', 'show_tip')
|
||||
// 请求用户注意窗口
|
||||
home?.requestUserAttention(UserAttentionType.Critical)
|
||||
}
|
||||
|
||||
await emitTo('notify', 'notify_cotent', data)
|
||||
// 请求用户注意窗口
|
||||
// home?.requestUserAttention(UserAttentionType.Critical)
|
||||
const throttleSendNotification = useThrottleFn(() => {
|
||||
sendNotification({
|
||||
title: username,
|
||||
@@ -353,6 +354,19 @@ onMounted(async () => {
|
||||
msgId: 'checkUpdate',
|
||||
duration: CHECK_UPDATE_TIME
|
||||
})
|
||||
|
||||
// 监听home窗口被聚焦的事件,当窗口被聚焦时自动关闭状态栏通知
|
||||
const homeWindow = await WebviewWindow.getByLabel('home')
|
||||
if (homeWindow) {
|
||||
homeWindow.onFocusChanged(({ payload: focused }) => {
|
||||
// 当窗口获得焦点时,关闭通知提示
|
||||
if (focused) {
|
||||
globalStore.setTipVisible(false)
|
||||
// 同时通知通知窗口隐藏
|
||||
emitTo('notify', 'hide_notify')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@@ -78,23 +78,36 @@ export const useCachedStore = defineStore(StoresEnum.CACHED, () => {
|
||||
|
||||
// 收集需要获取的徽章ID
|
||||
const itemIdSet: Set<string> = new Set()
|
||||
const data = await apis.getUserInfoBatch(result)
|
||||
|
||||
for (const item of data || []) {
|
||||
// 更新用户信息缓存
|
||||
userCachedList[item.uid] = {
|
||||
...userCachedList[item.uid], // 保留旧数据
|
||||
...item, // 用新数据覆盖
|
||||
needRefresh: undefined,
|
||||
lastModifyTime: Date.now()
|
||||
// 后端限制每批最多50个用户,需要分批处理
|
||||
const batchSize = 50
|
||||
const totalBatches = Math.ceil(result.length / batchSize)
|
||||
|
||||
for (let batchIndex = 0; batchIndex < totalBatches; batchIndex++) {
|
||||
// 计算当前批次的起始和结束索引
|
||||
const startIndex = batchIndex * batchSize
|
||||
const endIndex = Math.min(startIndex + batchSize, result.length)
|
||||
const batchUsers = result.slice(startIndex, endIndex)
|
||||
|
||||
// 批量获取当前批次的用户信息
|
||||
const batchData = await apis.getUserInfoBatch(batchUsers)
|
||||
|
||||
for (const item of batchData || []) {
|
||||
// 更新用户信息缓存
|
||||
userCachedList[item.uid] = {
|
||||
...userCachedList[item.uid], // 保留旧数据
|
||||
...item, // 用新数据覆盖
|
||||
needRefresh: void 0,
|
||||
lastModifyTime: Date.now()
|
||||
}
|
||||
|
||||
// 收集用户佩戴的徽章ID
|
||||
const wearingItemId = item.wearingItemId
|
||||
wearingItemId && itemIdSet.add(wearingItemId)
|
||||
|
||||
// 从待处理集合中移除已完成的用户ID
|
||||
pendingUids.value.delete(item.uid)
|
||||
}
|
||||
|
||||
// 收集用户佩戴的徽章ID
|
||||
const wearingItemId = item.wearingItemId
|
||||
wearingItemId && itemIdSet.add(wearingItemId)
|
||||
|
||||
// 从待处理集合中移除已完成的用户ID
|
||||
pendingUids.value.delete(item.uid)
|
||||
}
|
||||
|
||||
// 批量获取徽章信息
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** 气泡样式 */
|
||||
@mixin bubble {
|
||||
@apply w-fit select-text min-h-1em p-[8px_12px] text-14px line-height-22px bg-[--bg-bubble] rounded-[2px_18px_18px] custom-shadow;
|
||||
@apply w-fit min-h-1em p-[8px_12px] text-14px line-height-22px bg-[--bg-bubble] rounded-[2px_18px_18px] custom-shadow;
|
||||
max-width: var(--bubble-max-width, 32vw); /** 默认为群聊宽度 */
|
||||
white-space: normal; /** 保留空白符号并正常换行 */
|
||||
word-break: break-all; /** 强制在单词内部进行换行 */
|
||||
@@ -10,6 +10,10 @@
|
||||
-webkit-overflow-wrap: break-all;
|
||||
hyphens: auto; /** 行尾自动断开长单词 */
|
||||
-webkit-hyphens: auto;
|
||||
user-select: text; /** 只允许选中文本 */
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
&.active {
|
||||
background-color: var(--bg-bubble-active);
|
||||
color: var(--text-color);
|
||||
@@ -23,15 +27,43 @@
|
||||
}
|
||||
}
|
||||
.bubble {
|
||||
// &::selection {
|
||||
// background: transparent; /* 设置选中背景为透明 */
|
||||
// }
|
||||
&::selection {
|
||||
background: var(--select-bg-color, rgba(19, 152, 127, 0.3)); /* 设置选中背景颜色 */
|
||||
}
|
||||
@include bubble;
|
||||
|
||||
/* 确保子元素中非文本元素不可选 */
|
||||
img,
|
||||
svg,
|
||||
.emoji,
|
||||
button,
|
||||
[role='button'] {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
}
|
||||
.bubble-oneself {
|
||||
@include bubble;
|
||||
@apply rounded-[18px_2px_18px_18px] color-#fff;
|
||||
background-color: var(--bg-active-msg);
|
||||
|
||||
&::selection {
|
||||
background: var(--select-bg-color-self, rgba(255, 255, 255, 0.3)); /* 自己消息选中背景颜色 */
|
||||
}
|
||||
|
||||
/* 确保子元素中非文本元素不可选 */
|
||||
img,
|
||||
svg,
|
||||
.emoji,
|
||||
button,
|
||||
[role='button'] {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
}
|
||||
/**! 气泡动画 */
|
||||
.bubble-animation {
|
||||
|
||||
1
src/typings/components.d.ts
vendored
@@ -63,6 +63,7 @@ declare module 'vue' {
|
||||
NQrCode: typeof import('naive-ui')['NQrCode']
|
||||
NRadio: typeof import('naive-ui')['NRadio']
|
||||
NResult: typeof import('naive-ui')['NResult']
|
||||
NS: typeof import('naive-ui')['NS']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
NSkeleton: typeof import('naive-ui')['NSkeleton']
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<n-flex
|
||||
v-for="(group, index) in content"
|
||||
:key="index"
|
||||
@click="handleClickMsg(group.id)"
|
||||
@click="handleClickMsg(group)"
|
||||
align="left"
|
||||
:size="10"
|
||||
class="mt-2px p-6px box-border rounded-8px hover:bg-[--tray-hover] cursor-pointer">
|
||||
@@ -21,7 +21,16 @@
|
||||
|
||||
<n-flex class="w-full" align="center" justify="space-between" :size="10">
|
||||
<span class="max-w-150px truncate text-(12px [--text-color])">
|
||||
<span v-if="group.isAtMe" class="text-#d5304f pr-4px">[有人@我]</span>{{ group.latestContent }}
|
||||
<template v-if="group.isAtMe">
|
||||
<span
|
||||
class="text flex-1 leading-tight text-12px truncate"
|
||||
v-html="group.latestContent.replace(':', ':')" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<span
|
||||
class="text flex-1 leading-tight text-12px truncate"
|
||||
v-text="group.latestContent.replace(':', ':')" />
|
||||
</template>
|
||||
</span>
|
||||
|
||||
<!-- 有多少条消息 -->
|
||||
@@ -41,12 +50,13 @@ import { useGlobalStore } from '@/stores/global.ts'
|
||||
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { emitTo, Event, listen } from '@tauri-apps/api/event'
|
||||
import { PhysicalPosition } from '@tauri-apps/api/dpi'
|
||||
import { RoomTypeEnum } from '@/enums'
|
||||
import { useWindow } from '@/hooks/useWindow.ts'
|
||||
import { useTauriListener } from '@/hooks/useTauriListener'
|
||||
import type { MessageType } from '@/services/types.ts'
|
||||
import { useChatStore } from '@/stores/chat.ts'
|
||||
import { AvatarUtils } from '@/utils/AvatarUtils'
|
||||
import { useCommon } from '@/hooks/useCommon.ts'
|
||||
import { useReplaceMsg } from '@/hooks/useReplaceMsg.ts'
|
||||
|
||||
// 定义分组消息的类型
|
||||
type GroupedMessage = {
|
||||
@@ -58,12 +68,14 @@ type GroupedMessage = {
|
||||
name: string
|
||||
timestamp: number
|
||||
isAtMe: boolean
|
||||
top?: boolean // 添加置顶状态属性
|
||||
roomType: number // 房间类型:1=群聊,2=单聊
|
||||
}
|
||||
|
||||
const appWindow = WebviewWindow.getCurrent()
|
||||
const { checkWinExist, resizeWindow } = useWindow()
|
||||
const { userUid } = useCommon()
|
||||
const { pushListeners } = useTauriListener()
|
||||
const { checkMessageAtMe } = useReplaceMsg()
|
||||
const globalStore = useGlobalStore()
|
||||
const chatStore = useChatStore()
|
||||
const { tipVisible } = storeToRefs(globalStore)
|
||||
@@ -88,21 +100,28 @@ const division = () => {
|
||||
return <div class={'h-1px bg-[--line-color] w-full'}></div>
|
||||
}
|
||||
|
||||
// 检查是否@了当前用户
|
||||
const checkIsAtMe = (content: MessageType) => {
|
||||
return content.message.body.atUidList?.includes(userUid.value)
|
||||
}
|
||||
|
||||
// 处理点击消息的逻辑
|
||||
const handleClickMsg = async (uid: string) => {
|
||||
// TODO: 会导致频控触发
|
||||
const handleClickMsg = async (group: any) => {
|
||||
// 打开消息页面
|
||||
await checkWinExist('home')
|
||||
await handleTip()
|
||||
// TODO: 跳转页面还有问题
|
||||
emitTo('home', 'search_to_msg', {
|
||||
uid: uid,
|
||||
roomType: 1
|
||||
})
|
||||
// 找到对应的会话 - 根据roomId而不是消息ID
|
||||
const session = chatStore.sessionList.find((s) => s.roomId === group.roomId)
|
||||
if (session) {
|
||||
// 获取home窗口实例
|
||||
const home = await WebviewWindow.getByLabel('home')
|
||||
|
||||
// 如果当前不在消息页面且在home窗口,则跳转到消息页面
|
||||
await home?.setFocus()
|
||||
emitTo('home', 'search_to_msg', {
|
||||
uid: group.roomType === RoomTypeEnum.SINGLE ? session.id : session.roomId,
|
||||
roomType: group.roomType
|
||||
})
|
||||
// 收起通知面板
|
||||
await handleTip()
|
||||
} else {
|
||||
console.error('找不到对应的会话信息')
|
||||
}
|
||||
}
|
||||
|
||||
// 取消状态栏闪烁
|
||||
@@ -120,7 +139,7 @@ const showWindow = async (event: Event<any>) => {
|
||||
await notifyWindow?.setPosition(
|
||||
new PhysicalPosition(
|
||||
event.payload.position.Physical.x - 120,
|
||||
event.payload.position.Physical.y - outerSize.height
|
||||
event.payload.position.Physical.y - outerSize.height + 8
|
||||
)
|
||||
)
|
||||
await notifyWindow?.show()
|
||||
@@ -149,12 +168,7 @@ const handleMouseLeave = async () => {
|
||||
await hideWindow()
|
||||
}
|
||||
|
||||
// 对消息进行排序的函数
|
||||
const sortMessages = () => {
|
||||
content.value.sort((a, b) => {
|
||||
return b.timestamp - a.timestamp
|
||||
})
|
||||
}
|
||||
// 对消息进行排序的函数 - 现在直接写在事件处理中
|
||||
|
||||
onBeforeMount(async () => {
|
||||
// 确保用户已登录并初始化会话列表
|
||||
@@ -177,6 +191,12 @@ onMounted(async () => {
|
||||
setTimeout(async () => {
|
||||
await hideWindow()
|
||||
}, 300)
|
||||
}),
|
||||
|
||||
// 监听隐藏通知的事件,当主窗口获得焦点时触发
|
||||
appWindow.listen('hide_notify', async () => {
|
||||
// 隐藏所有通知并关闭窗口
|
||||
await handleTip()
|
||||
})
|
||||
])
|
||||
|
||||
@@ -186,16 +206,31 @@ onMounted(async () => {
|
||||
// 窗口显示将由notify_enter事件触发
|
||||
|
||||
// 处理消息内容
|
||||
const message = event.payload
|
||||
const session = chatStore.sessionList.find((s) => s.roomId === message.message.roomId)
|
||||
const existingGroup = content.value.find((group) => group.roomId === message.message.roomId)
|
||||
const isAtMe = checkIsAtMe(message)
|
||||
const msg = event.payload
|
||||
const session = chatStore.sessionList.find((s) => s.roomId === msg.message.roomId)
|
||||
const existingGroup = content.value.find((group) => group.roomId === msg.message.roomId)
|
||||
|
||||
// 使用useReplaceMsg处理消息内容
|
||||
const { formatMessageContent, getMessageSenderName } = useReplaceMsg()
|
||||
const isAtMe = checkMessageAtMe(msg)
|
||||
const currentTime = Date.now()
|
||||
|
||||
// 获取发送者信息
|
||||
const senderName = getMessageSenderName(msg, session?.name || '')
|
||||
|
||||
// 格式化消息内容
|
||||
const formattedContent = formatMessageContent(msg, session?.type || RoomTypeEnum.GROUP, senderName, isAtMe)
|
||||
|
||||
// 获取会话中已有的未读消息数量(排除已在通知中计算过的)
|
||||
let unreadCount = 0
|
||||
if (session && !existingGroup) {
|
||||
unreadCount = session.unreadCount || 0
|
||||
}
|
||||
|
||||
if (existingGroup) {
|
||||
// 如果该房间的消息已存在,更新最新内容和计数
|
||||
existingGroup.id = message.message.id
|
||||
existingGroup.latestContent = message.message.body.content
|
||||
existingGroup.id = msg.message.id
|
||||
existingGroup.latestContent = formattedContent
|
||||
existingGroup.messageCount++
|
||||
existingGroup.timestamp = currentTime
|
||||
existingGroup.isAtMe = isAtMe
|
||||
@@ -206,14 +241,16 @@ onMounted(async () => {
|
||||
} else {
|
||||
// 如果是新的房间,创建新的分组
|
||||
content.value.push({
|
||||
id: message.message.id,
|
||||
roomId: message.message.roomId,
|
||||
latestContent: message.message.body.content,
|
||||
messageCount: 1,
|
||||
id: msg.message.id,
|
||||
roomId: msg.message.roomId,
|
||||
latestContent: formattedContent,
|
||||
messageCount: 1 + unreadCount, // 加上已有的未读消息数量
|
||||
avatar: session?.avatar || '',
|
||||
name: session?.name || '',
|
||||
timestamp: currentTime,
|
||||
isAtMe: isAtMe
|
||||
isAtMe: isAtMe,
|
||||
// 添加房间类型,从session中获取,如果没有则默认为私聊类型
|
||||
roomType: session?.type || RoomTypeEnum.SINGLE
|
||||
})
|
||||
|
||||
// 调整窗口高度,基础高度140,从第二个分组开始每组增加60px,最多4个分组
|
||||
@@ -224,8 +261,15 @@ onMounted(async () => {
|
||||
resizeWindow('notify', 280, newHeight)
|
||||
}
|
||||
|
||||
// 对消息进行排序
|
||||
sortMessages()
|
||||
// 对消息进行排序 - 先按置顶状态排序,再按活跃时间排序
|
||||
content.value.sort((a, b) => {
|
||||
// 1. 先按置顶状态排序(置顶的排在前面)
|
||||
if (a.top && !b.top) return -1
|
||||
if (!a.top && b.top) return 1
|
||||
|
||||
// 2. 在相同置顶状态下,按时间戳降序排序(最新的排在前面)
|
||||
return b.timestamp - a.timestamp
|
||||
})
|
||||
|
||||
msgCount.value = content.value.reduce((acc, group) => acc + group.messageCount, 0)
|
||||
}
|
||||
|
||||
@@ -1,43 +1,245 @@
|
||||
<template>
|
||||
<n-flex :size="14" vertical justify="center" class="p-14px text-(12px #909090)">
|
||||
<p>搜索建议</p>
|
||||
|
||||
<n-flex align="center" class="text-(12px #909090)">
|
||||
<p class="p-6px bg-[--search-color] rounded-8px cursor-pointer">@我</p>
|
||||
<p class="p-6px bg-[--search-color] rounded-8px cursor-pointer">特别关心</p>
|
||||
</n-flex>
|
||||
|
||||
<span class="w-full h-1px bg-[--line-color]"></span>
|
||||
|
||||
<n-flex align="center" justify="space-between">
|
||||
<p class="text-(12px #909090)">历史记录</p>
|
||||
<p class="cursor-pointer text-(12px #13987f)">清除</p>
|
||||
</n-flex>
|
||||
|
||||
<template v-for="(item, _index) in historyList" :key="_index">
|
||||
<n-flex align="center" :size="14" class="p-6px cursor-pointer rounded-8px hover:bg-[--list-hover-color]">
|
||||
<n-avatar :size="38" round bordered :src="AvatarUtils.getAvatarUrl(item.avatar)" />
|
||||
<p class="text-(14px [--text-color])">{{ item.name }}</p>
|
||||
<!-- 搜索结果为空时显示建议和历史记录 -->
|
||||
<template v-if="searchResults.length === 0 && !searchQuery">
|
||||
<!-- 搜索建议 -->
|
||||
<p class="text-(12px #909090)">搜索建议</p>
|
||||
<n-flex align="center" class="text-(12px #909090)">
|
||||
<p class="p-6px bg-[--search-color] rounded-8px cursor-pointer" @click="applySearchTerm('hula')">hula</p>
|
||||
</n-flex>
|
||||
|
||||
<span :class="{ 'mb-10px': historyList.length > 0 }" class="w-full h-1px bg-[--line-color]"></span>
|
||||
|
||||
<!-- 历史记录 -->
|
||||
<n-flex v-if="historyList.length > 0" align="center" justify="space-between">
|
||||
<p class="text-(12px #909090)">历史记录</p>
|
||||
<p class="cursor-pointer text-(12px #13987f)" @click="clearHistory">清除</p>
|
||||
</n-flex>
|
||||
|
||||
<n-flex
|
||||
v-else
|
||||
align="center"
|
||||
:size="14"
|
||||
class="p-6px mb-6px mr-10px cursor-pointer rounded-8px hover:bg-[--list-hover-color]">
|
||||
<n-avatar :size="38" round src="msgAction/clapping.png" />
|
||||
<p class="text-(12px [--chat-text-color]) flex-1 truncate">在搜索框内搜索</p>
|
||||
</n-flex>
|
||||
|
||||
<n-scrollbar style="max-height: calc(100vh - 212px)">
|
||||
<template v-for="(item, _index) in historyList" :key="_index">
|
||||
<n-flex
|
||||
align="center"
|
||||
:size="14"
|
||||
class="p-6px mb-6px mr-10px cursor-pointer rounded-8px hover:bg-[--list-hover-color]"
|
||||
@click="openConversation(item)">
|
||||
<n-avatar :size="38" round bordered :src="AvatarUtils.getAvatarUrl(item.avatar)" />
|
||||
<p class="text-(14px [--text-color]) flex-1 truncate">{{ item.name }}</p>
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-scrollbar>
|
||||
</template>
|
||||
|
||||
<!-- 搜索结果 -->
|
||||
<template v-else-if="searchResults.length > 0">
|
||||
<p class="text-(12px #909090) mb-6px">搜索结果</p>
|
||||
|
||||
<n-scrollbar style="max-height: calc(100vh - 212px)">
|
||||
<template v-for="item in searchResults" :key="item.roomId">
|
||||
<n-flex
|
||||
align="center"
|
||||
:size="14"
|
||||
class="p-6px mb-6px mr-10px cursor-pointer rounded-8px hover:bg-[--list-hover-color]"
|
||||
@click="openConversation(item)">
|
||||
<n-avatar :size="38" round bordered :src="AvatarUtils.getAvatarUrl(item.avatar)" />
|
||||
<p class="text-(14px [--text-color])">{{ item.name }}</p>
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-scrollbar>
|
||||
</template>
|
||||
|
||||
<!-- 没有搜索结果时 -->
|
||||
<template v-else-if="searchQuery && searchResults.length === 0">
|
||||
<div style="height: calc(100vh - 212px)" class="flex-col-center gap-12px">
|
||||
<img class="size-64px" src="/msgAction/exploding-head.png" />
|
||||
<p class="text-(12px [--chat-text-color])">未找到相关结果</p>
|
||||
</div>
|
||||
</template>
|
||||
</n-flex>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { AvatarUtils } from '@/utils/AvatarUtils'
|
||||
const historyList = [
|
||||
{
|
||||
avatar: 'https://picsum.photos/140?1',
|
||||
name: '小瘪三'
|
||||
},
|
||||
{
|
||||
avatar: 'https://picsum.photos/140?2',
|
||||
name: '号啊玉'
|
||||
},
|
||||
{
|
||||
avatar: 'https://picsum.photos/140?3',
|
||||
name: '张三'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
import { useChatStore } from '@/stores/chat.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useCommon } from '@/hooks/useCommon.ts'
|
||||
import { MittEnum, RoomTypeEnum } from '@/enums'
|
||||
import { useMitt } from '@/hooks/useMitt'
|
||||
|
||||
<style scoped></style>
|
||||
type SessionItem = {
|
||||
avatar: string
|
||||
name: string
|
||||
id?: string
|
||||
roomId: string
|
||||
type: number
|
||||
}
|
||||
type HistoryItem = {
|
||||
avatar: string
|
||||
name: string
|
||||
id: string
|
||||
roomId: string
|
||||
type: number
|
||||
timestamp?: number
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const chatStore = useChatStore()
|
||||
const { openMsgSession } = useCommon()
|
||||
// 从路由参数或共享状态中获取搜索查询
|
||||
const searchQuery = ref('')
|
||||
// 搜索结果
|
||||
const searchResults = ref<SessionItem[]>([])
|
||||
// 历史记录 - 使用localStorage存储
|
||||
const HISTORY_STORAGE_KEY = 'HULA_SEARCH_HISTORY'
|
||||
const historyList = ref<HistoryItem[]>([])
|
||||
|
||||
// 监听搜索框输入变化
|
||||
useMitt.on('search_input_change', (value) => {
|
||||
searchQuery.value = value
|
||||
handleSearch(value)
|
||||
})
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = (value: string) => {
|
||||
if (!value) {
|
||||
searchResults.value = []
|
||||
return
|
||||
}
|
||||
// 从chatStore获取会话列表并根据关键字进行过滤
|
||||
const sessionList = chatStore.sessionList
|
||||
// 根据名称和最后一条消息内容进行搜索匹配
|
||||
searchResults.value = sessionList.filter((session) => {
|
||||
// 在名称中搜索
|
||||
const nameMatch = session.name.toLowerCase().includes(value.toLowerCase())
|
||||
// 在最后一条消息中搜索
|
||||
// const lastMsgMatch = session.lastMsg && session.lastMsg.toLowerCase().includes(value.toLowerCase())
|
||||
|
||||
// 返回名称或最后一条消息匹配的结果
|
||||
// return nameMatch || lastMsgMatch
|
||||
|
||||
return nameMatch
|
||||
})
|
||||
// 如果有搜索关键词,这里可以保存到关键词历史记录(当前实现还不保存搜索关键词)
|
||||
if (value) {
|
||||
saveToHistory(value)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用搜索词
|
||||
const applySearchTerm = (term: string) => {
|
||||
searchQuery.value = term
|
||||
handleSearch(term)
|
||||
}
|
||||
|
||||
// 加载历史记录
|
||||
const loadHistory = () => {
|
||||
try {
|
||||
const history = localStorage.getItem(HISTORY_STORAGE_KEY)
|
||||
if (history) {
|
||||
historyList.value = JSON.parse(history)
|
||||
// 按时间戳降序排序,最近访问的放在前面
|
||||
historyList.value.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))
|
||||
// 只保留前10条记录
|
||||
if (historyList.value.length > 10) {
|
||||
historyList.value = historyList.value.slice(0, 10)
|
||||
// 同步到localStorage
|
||||
saveHistoryToStorage()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载历史记录失败:', error)
|
||||
// 如果加载失败,重置为空数组
|
||||
historyList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 将历史记录保存到localStorage
|
||||
const saveHistoryToStorage = () => {
|
||||
try {
|
||||
localStorage.setItem(HISTORY_STORAGE_KEY, JSON.stringify(historyList.value))
|
||||
} catch (error) {
|
||||
console.error('保存历史记录失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存搜索关键词到历史记录
|
||||
const saveToHistory = (term: string) => {
|
||||
console.log(`保存搜索记录: ${term}`)
|
||||
// 可以实现关键词搜索记录的保存
|
||||
}
|
||||
|
||||
// 保存会话到历史记录
|
||||
const saveSessionToHistory = (session: SessionItem) => {
|
||||
// 确保会话有足够的数据
|
||||
if (!session || !session.roomId) return
|
||||
|
||||
// 创建一个历史记录项
|
||||
const historyItem: HistoryItem = {
|
||||
avatar: session.avatar || '',
|
||||
name: session.name || '',
|
||||
id: session.id || session.roomId,
|
||||
roomId: session.roomId,
|
||||
type: session.type || RoomTypeEnum.SINGLE,
|
||||
timestamp: Date.now() // 添加当前时间戳
|
||||
}
|
||||
|
||||
// 检查是否已存在相同的记录
|
||||
const existingIndex = historyList.value.findIndex((item) => item.roomId === session.roomId)
|
||||
|
||||
if (existingIndex !== -1) {
|
||||
// 如果已存在,删除它(稍后会添加到列表头部)
|
||||
historyList.value.splice(existingIndex, 1)
|
||||
}
|
||||
|
||||
// 将新的记录添加到列表头部
|
||||
historyList.value.unshift(historyItem)
|
||||
|
||||
// 限制历史记录数量为10个
|
||||
if (historyList.value.length > 10) {
|
||||
historyList.value = historyList.value.slice(0, 10)
|
||||
}
|
||||
|
||||
// 保存到localStorage
|
||||
saveHistoryToStorage()
|
||||
}
|
||||
|
||||
// 清除历史记录
|
||||
const clearHistory = () => {
|
||||
historyList.value = []
|
||||
// 清除localStorage中的记录
|
||||
localStorage.removeItem(HISTORY_STORAGE_KEY)
|
||||
}
|
||||
|
||||
// 打开会话
|
||||
const openConversation = (item: SessionItem | HistoryItem) => {
|
||||
// 保存会话到历史记录
|
||||
saveSessionToHistory(item)
|
||||
// 保存搜索记录
|
||||
if (searchQuery.value) {
|
||||
saveToHistory(searchQuery.value)
|
||||
}
|
||||
// 返回上一页(关闭搜索页面)
|
||||
router.go(-1)
|
||||
// 打开对应会话
|
||||
const id = item.type === RoomTypeEnum.GROUP ? item.roomId : item.id
|
||||
openMsgSession(id || item.roomId, item.type)
|
||||
// 定位到对应的会话(让聊天列表滚动到选中的会话)
|
||||
nextTick(() => {
|
||||
useMitt.emit(MittEnum.LOCATE_SESSION, { roomId: item.roomId })
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 加载历史记录
|
||||
loadHistory()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -105,19 +105,18 @@
|
||||
</template>
|
||||
<script lang="ts" setup name="message">
|
||||
import { useMessage } from '@/hooks/useMessage.ts'
|
||||
import { MittEnum, MsgEnum, RoomTypeEnum } from '@/enums'
|
||||
import { MittEnum, RoomTypeEnum } from '@/enums'
|
||||
import { IsAllUserEnum, SessionItem } from '@/services/types.ts'
|
||||
import { formatTimestamp } from '@/utils/ComputedTime.ts'
|
||||
import { useChatStore } from '@/stores/chat.ts'
|
||||
import { useGlobalStore } from '@/stores/global.ts'
|
||||
import { useUserInfo } from '@/hooks/useCached.ts'
|
||||
import { renderReplyContent } from '@/utils/RenderReplyContent.ts'
|
||||
import { useReplaceMsg } from '@/hooks/useReplaceMsg.ts'
|
||||
import { useCommon } from '@/hooks/useCommon.ts'
|
||||
import SysNTF from '@/components/common/SystemNotification.tsx'
|
||||
import { AvatarUtils } from '@/utils/AvatarUtils'
|
||||
import { useGroupStore } from '@/stores/group.ts'
|
||||
import { useMitt } from '@/hooks/useMitt'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { useTauriListener } from '@/hooks/useTauriListener'
|
||||
|
||||
@@ -126,8 +125,7 @@ const { addListener } = useTauriListener()
|
||||
const chatStore = useChatStore()
|
||||
const globalStore = useGlobalStore()
|
||||
const groupStore = useGroupStore()
|
||||
const { userUid, openMsgSession } = useCommon()
|
||||
const userStore = useUserStore()
|
||||
const { openMsgSession } = useCommon()
|
||||
const msgScrollbar = useTemplateRef<HTMLElement>('msg-scrollbar')
|
||||
const { handleMsgClick, handleMsgDelete, menuList, specialMenuList, handleMsgDblclick } = useMessage()
|
||||
const currentSession = computed(() => globalStore.currentSession)
|
||||
@@ -156,40 +154,17 @@ const sessionList = computed(() => {
|
||||
}
|
||||
|
||||
if (lastMsg) {
|
||||
const lastMsgUserName = useUserInfo(lastMsg.fromUser.uid)
|
||||
// 使用 useAtMention hook 检查是否有@我的消息
|
||||
const { checkRoomAtMe, formatMessageContent, getMessageSenderName } = useReplaceMsg()
|
||||
|
||||
// 添加@提醒判断 - 修改比较逻辑,确保类型一致
|
||||
const messagesWithAt = messages.filter((msg) =>
|
||||
msg.message?.body?.atUidList?.some((atUid: string) => String(atUid) === String(userStore.userInfo.uid))
|
||||
)
|
||||
// 获取发送者信息
|
||||
const senderName = getMessageSenderName(lastMsg)
|
||||
|
||||
// 检查是否有@我的消息以及是否在未读范围内
|
||||
const isAtMe =
|
||||
item.type === RoomTypeEnum.GROUP &&
|
||||
currentSession.value.roomId !== item.roomId &&
|
||||
messagesWithAt.some((msg) => messages.indexOf(msg) >= messages.length - (item.unreadCount || 0))
|
||||
// 检查是否有@我的消息
|
||||
const isAtMe = checkRoomAtMe(item.roomId, item.type, currentSession.value.roomId, messages, item.unreadCount)
|
||||
|
||||
const atPrefix = isAtMe ? '<span class="text-#d5304f mr-4px">[有人@我]</span>' : ''
|
||||
|
||||
// 获取正常的消息内容
|
||||
const messageContent = renderReplyContent(
|
||||
lastMsgUserName.value.name,
|
||||
lastMsg.message?.type,
|
||||
lastMsg.message?.body?.content || lastMsg.message?.body,
|
||||
item.type
|
||||
) as string
|
||||
|
||||
// 撤回消息直接显示文本,没有HTML
|
||||
LastUserMsg =
|
||||
lastMsg.message?.type === MsgEnum.RECALL
|
||||
? item.type === RoomTypeEnum.GROUP
|
||||
? `${lastMsgUserName.value.name}:撤回了一条消息`
|
||||
: lastMsg.fromUser.uid === userUid.value
|
||||
? '你撤回了一条消息'
|
||||
: '对方撤回了一条消息'
|
||||
: isAtMe
|
||||
? `${atPrefix}${messageContent}` // 有人@我时才保留HTML标签
|
||||
: messageContent // 正常消息内容已在renderReplyContent中被转义
|
||||
// 使用封装后的方法处理消息内容,包括撤回消息和@提醒
|
||||
LastUserMsg = formatMessageContent(lastMsg, item.type, senderName, isAtMe)
|
||||
|
||||
// 返回带有isAtMe标记的对象和修改后的名称
|
||||
return {
|
||||
@@ -232,16 +207,20 @@ watch(
|
||||
if (newVal.type === RoomTypeEnum.GROUP) {
|
||||
// 在这里请求是因为这里一开始选中就会触发,而在chat.ts中则需要切换会话才会触发
|
||||
await groupStore.getCountStatistic()
|
||||
// 同时获取群成员列表,确保首次加载时也能显示群成员
|
||||
await groupStore.getGroupUserList(true, newVal.roomId)
|
||||
// 将群组详情信息传递给handleMsgClick方法
|
||||
handleMsgClick({
|
||||
const sessionItem = {
|
||||
...newVal,
|
||||
memberNum: groupStore.countInfo?.memberNum,
|
||||
remark: groupStore.countInfo?.remark,
|
||||
myName: groupStore.countInfo?.myName
|
||||
})
|
||||
}
|
||||
handleMsgClick(sessionItem)
|
||||
} else {
|
||||
// 非群聊直接传递原始信息
|
||||
handleMsgClick(newVal as SessionItem)
|
||||
const sessionItem = newVal as SessionItem
|
||||
handleMsgClick(sessionItem)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||