Merge branch 'master' into feat/AIChat

This commit is contained in:
OrionMark
2025-05-18 19:52:39 +08:00
31 changed files with 828 additions and 254 deletions

View File

@@ -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="FeaturedHelloGitHub" 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:

View File

@@ -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="FeaturedHelloGitHub" 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
public/msgAction/like.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/msgAction/rose.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

152
src-tauri/Cargo.lock generated
View File

@@ -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]]

View File

@@ -160,6 +160,11 @@ onUnmounted(() => {
font-size: 12px;
}
img {
user-select: none;
-webkit-user-select: none;
}
input,
button,
a {

View File

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

View File

@@ -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)

View File

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

View File

@@ -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">在线群聊成员&nbsp;{{ 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">
在线群聊成员&nbsp;
{{ 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))

View File

@@ -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('删除表情失败')

View File

@@ -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
View 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
}
}

View File

@@ -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
}
}

View File

@@ -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(() => {

View File

@@ -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)
}
// 批量获取徽章信息

View File

@@ -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 {

View File

@@ -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']

View File

@@ -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)
}

View File

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

View File

@@ -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)
}
}
},