perf(system): 优化系统托盘图标闪烁

This commit is contained in:
nongyehong 2024-03-23 19:52:20 +08:00
parent 4f81f50e13
commit 5614750ead
21 changed files with 373 additions and 224 deletions

18
src-tauri/Cargo.lock generated
View File

@ -1262,6 +1262,7 @@ dependencies = [
"serde_json",
"tauri",
"tauri-build",
"tokio",
"window-shadows",
"window-vibrancy",
]
@ -3088,14 +3089,27 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.35.0"
version = "1.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c"
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
dependencies = [
"backtrace",
"bytes",
"num_cpus",
"pin-project-lite",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.41",
]
[[package]]

View File

@ -25,6 +25,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
window-shadows = "0.2.2" # windows 阴影和圆角
window-vibrancy = "0.4.3" # windows 磨砂背景 tauri-v1.x版本最好使用0.4.3版本
tokio = { version = "1.36.0", features = ["macros", "windows-sys", "rt-multi-thread", "time"] }
[features]
# this feature is used for production builds or when `devPath` points to the filesystem

View File

@ -1,3 +1,4 @@
use std::sync::Mutex;
use tauri::{AppHandle, Manager, Runtime};
use window_shadows::set_shadow;
@ -31,4 +32,58 @@ pub fn set_tray_icon(app: AppHandle) {
#[tauri::command]
pub fn exit(app: AppHandle) {
app.exit(0)
}
pub struct TrayState {
// 用来缓存上一次线程的句柄
pub id: Mutex<Option<tokio::task::JoinHandle<()>>>
}
/// 设置托盘图标闪烁
// TODO 现在只能实现两张图片交换实现闪烁效果 (nyh -> 2024-03-23 17:57:45)
#[tauri::command]
pub fn tray_blink(
app: AppHandle,
state: tauri::State<TrayState>,
is_run: bool, // 是否运行
ms: Option<u64>, // 可选, 时间间隔,单位毫秒
icon_path_1: Option<String>, // 可选图标1
icon_path_2: Option<String>, // 可选图标2
) {
// 如果存在就取消上一次的线程
match state.id.lock().as_deref_mut().map(|x| x.as_mut()) {
Ok(Some(v)) => v.abort(),
_ => (),
}
// 是否停止运行,如果停止,则取消上一次线程后的返回
if !is_run {
return;
}
// 缓存线程 id 到 tauri state
*state.id.lock().unwrap() = Some(tokio::spawn(async move {
let path1 = &icon_path_1.unwrap();
let path2 = &icon_path_2.unwrap();
let mut count = 0;
loop {
// 如果 ms 不存在 则默认 500ms
tokio::time::sleep(std::time::Duration::from_millis(ms.unwrap_or_else(|| 500))).await;
count += 1;
// 两张图片交替显示, 所以对2取模运算
let path = if count % 2 == 0 {
path2
} else {
path1
};
// tauri api 进行图标设置
app
.tray_handle()
.set_icon(tauri::Icon::File(std::path::PathBuf::from(path)))
.unwrap();
}
}));
}

View File

@ -29,6 +29,9 @@ fn red_icon(app: &AppHandle) {
pub fn handler(app: &AppHandle, event: SystemTrayEvent) {
match event {
SystemTrayEvent::LeftClick { .. } => {
// 当我点击闪烁的图标的时候就停止闪烁
let window = app.get_window("tray").unwrap();
window.emit("stop", false).unwrap();
open_home(app);
red_icon(app);
},

View File

@ -1,25 +1,32 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::sync::Mutex;
use tauri::{SystemTray};
use crate::common::window::set_window_attribute;
use crate::common::plugins::{ exit, reset_set_window, set_tray_icon };
use crate::common::plugins::{ exit, reset_set_window, set_tray_icon, tray_blink, TrayState };
mod common;
fn main() {
#[tokio::main]
async fn main() {
let context = tauri::generate_context!();
let system_tray = SystemTray::new();
// 初始化状态
let state = TrayState {
id: Mutex::new(None)
};
tauri::Builder::default()
.setup(|app| {
set_window_attribute(app);
Ok(())
})
.manage(state) // 将状态注入到应用
.menu(tauri::Menu::new())// 使用空菜单来替换默认的操作系统菜单
.system_tray(system_tray)// 将 `tauri.conf.json` 上配置的图标添加到系统托盘
.on_system_tray_event(common::tray::handler) // 注册系统托盘事件处理程序
.invoke_handler(tauri::generate_handler![reset_set_window, set_tray_icon, exit]) // 使用定义的插件
.invoke_handler(tauri::generate_handler![reset_set_window, set_tray_icon, exit, tray_blink]) // 使用定义的插件
.on_window_event(|event| match event.event() {
tauri::WindowEvent::CloseRequested { api, .. } => {
event.window().hide().unwrap();

BIN
src-tauri/tray/msg-sub.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

BIN
src-tauri/tray/msg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

View File

@ -73,7 +73,7 @@
<n-flex vertical :size="20">
<span class="text-[--text-color]">群成员({{ options.length }})</span>
<n-avatar-group :options="options" :size="40" :max="4">
<n-avatar-group :options="options" :size="40" :max="4" expand-on-hover>
<template #avatar="{ option: { name, src } }">
<n-tooltip>
<template #trigger>

View File

@ -21,7 +21,10 @@
:class="item.accountId === userId ? 'flex-row-reverse' : ''"
style="max-width: calc(100% - 54px)">
<!-- 头像 -->
<ContextMenu :menu="optionsList" :special-menu="report">
<ContextMenu
@select="$event.click(item)"
:menu="activeItem.type === RoomTypeEnum.GROUP ? optionsList : []"
:special-menu="report">
<img
:class="item.accountId === userId ? '' : 'mr-10px'"
class="w-34px rounded-50% select-none"
@ -31,7 +34,10 @@
<div
class="flex flex-col gap-8px color-[--text-color]"
:class="item.accountId === userId ? 'items-end mr-10px' : ''">
<ContextMenu :menu="optionsList" :special-menu="report">
<ContextMenu
@select="$event.click(item)"
:menu="activeItem.type === RoomTypeEnum.GROUP ? optionsList : []"
:special-menu="report">
<span class="text-12px select-none color-#909090" v-if="activeItem.type === RoomTypeEnum.GROUP">
{{ item.accountId === userId ? item.value : activeItem.accountName }}
</span>
@ -39,7 +45,7 @@
<!-- 气泡样式 -->
<ContextMenu
:data-key="item.accountId === userId ? `U${item.key}` : `Q${item.key}`"
@select="$event.click(item.key)"
@select="$event.click(item)"
:menu="menuList"
:special-menu="specialMenuList"
@click="handleMsgClick(item)">
@ -136,6 +142,7 @@ import { MockItem } from '@/services/types.ts'
import Mitt from '@/utils/Bus.ts'
import { VirtualListInst } from 'naive-ui'
import { invoke } from '@tauri-apps/api/tauri'
import { optionsList, report } from './config.ts'
const activeBubble = ref(-1)
const userId = ref(10086)
@ -191,54 +198,14 @@ const { activeItem } = defineProps<{
// }
// })
// const message = computed(() => msg.value)
/* 右键用户信息菜单(单聊的时候显示) */
const optionsList = computed(() => {
if (activeItem.type === RoomTypeEnum.GROUP) {
return [
{
label: '发送信息',
icon: 'message-action',
click: () => {}
},
{
label: 'TA',
icon: 'aite',
click: () => {}
},
{
label: '查看资料',
icon: 'notes',
click: () => {}
},
{
label: '添加好友',
icon: 'people-plus',
click: () => {}
}
]
}
})
/* 举报选项 */
const report = computed(() => {
if (activeItem.type === RoomTypeEnum.GROUP) {
return [
{
label: '举报',
icon: 'caution',
click: () => {}
}
]
}
})
/* 右键消息菜单列表 */
const menuList = ref<OPT.RightMenu[]>([
{
label: '复制',
icon: 'copy',
click: (index: number) => {
click: (item: any) => {
//
copyrightComputed.value.copy(index)
copyrightComputed.value.copy(item.key)
}
},
{
@ -391,7 +358,13 @@ const closeMenu = (event: any) => {
}
onMounted(() => {
invoke('set_tray_icon').catch((error) => {
//
invoke('tray_blink', {
isRun: true,
ms: 500,
iconPath1: 'tray/msg.png',
iconPath2: 'tray/msg-sub.png'
}).catch((error) => {
console.error('设置图标失败:', error)
})
Mitt.on(MittEnum.SEND_MESSAGE, (event) => {

View File

@ -1,20 +1,17 @@
<template>
<!--! 这里最好不要使用n-flex,滚动高度会有问题 -->
<main
v-if="activeItem?.type === RoomTypeEnum.GROUP"
style="border-left: 1px solid var(--line-color)"
class="item-box">
<main v-if="activeItem.type === RoomTypeEnum.GROUP" style="border-left: 1px solid var(--line-color)" class="item-box">
<n-flex v-if="!isSearch" align="center" justify="space-between" class="pr-8px pl-8px h-42px">
<span class="text-14px">群聊成员</span>
<svg @click="handleSearch" class="size-14px"><use href="#search"></use></svg>
</n-flex>
<!-- 搜索框 -->
<n-flex v-else align="center" class="pr-8px h-42px">
<n-input
@blur="isSearch = false"
ref="inputInstRef"
v-model:value="searchRef"
autofocus
clearable
placeholder="搜索"
type="text"
size="tiny"
@ -26,16 +23,29 @@
</n-flex>
<n-virtual-list
id="image-scroll-container"
ref="virtualListInst"
style="max-height: calc(100vh - 130px)"
item-resizable
:item-size="42"
:items="MockList">
<template #default="{ item }">
<n-flex :key="item.key" :size="10" align="center" class="item">
<n-avatar lazy round :size="24" :src="item.avatar"></n-avatar>
<span class="text-12px">{{ item.accountName }}</span>
</n-flex>
<ContextMenu @select="$event.click(item)" :menu="optionsList" :special-menu="report">
<n-flex :key="item.key" :size="10" align="center" class="item">
<n-avatar
lazy
round
:color="'#fff'"
:size="24"
:src="item.avatar"
fallback-src="/logo.png"
:render-placeholder="() => null"
:intersection-observer-options="{
root: '#image-scroll-container'
}"></n-avatar>
<span class="text-12px">{{ item.accountName }}</span>
</n-flex>
</ContextMenu>
</template>
</n-virtual-list>
</main>
@ -45,12 +55,13 @@ import { RoomTypeEnum } from '@/enums'
import { MockItem } from '@/services/types.ts'
import { MockList } from '@/mock'
import { InputInst } from 'naive-ui'
import { optionsList, report } from './config.ts'
const isSearch = ref(false)
const searchRef = ref('')
const inputInstRef = ref<InputInst | null>(null)
const { activeItem } = defineProps<{
activeItem?: MockItem
activeItem: MockItem
}>()
const handleSearch = () => {
@ -62,25 +73,5 @@ const handleSearch = () => {
</script>
<style scoped lang="scss">
.item-box {
@apply flex flex-col w-180px h-100vh z-20 p-[12px_0_12px_6px] box-border select-none text-[--text-color];
.item {
height: 42px;
padding-left: 4px;
width: 95%;
box-sizing: border-box;
border-radius: 6px;
&:hover {
cursor: pointer;
background-color: var(--bg-group-hover);
}
}
}
/*! 修改naive-ui虚拟列表滚动条的宽度 */
:deep(
.n-scrollbar > .n-scrollbar-rail.n-scrollbar-rail--vertical,
.n-scrollbar + .n-scrollbar-rail.n-scrollbar-rail--vertical
) {
width: 6px;
}
@import '@/styles/scss/chat-sidebar';
</style>

View File

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

View File

@ -45,8 +45,8 @@
</div>
<!-- 是否退到托盘提示框 -->
<n-modal v-if="!tray.notTips" v-model:show="tray.tips" class="w-350px border-rd-8px">
<div class="bg-[--bg-popover] w-360px h-full p-6px box-border flex flex-col">
<n-modal v-if="!tray.notTips" v-model:show="tray.tips" class="border-rd-8px">
<div class="bg-[--bg-popover] w-290px h-full p-6px box-border flex flex-col">
<svg @click="tray.tips = false" class="w-12px h-12px ml-a cursor-pointer select-none">
<use href="#close"></use>
</svg>

View File

@ -18,6 +18,14 @@ export enum RCodeEnum {
BUSINESS_EXCEPTION = '600'
}
/**URL*/
export enum URLEnum {
/**用户*/
USER = '/user',
/**文章*/
ARTICLE = '/article'
}
/** tauri原生跨窗口通信时传输的类型 */
export enum EventEnum {
/** 主题 */

139
src/hooks/useMessage.ts Normal file
View File

@ -0,0 +1,139 @@
import { useWindow } from '@/hooks/useWindow.ts'
import { emit, listen } from '@tauri-apps/api/event'
import { EventEnum, MittEnum } from '@/enums'
import Mitt from '@/utils/Bus.ts'
import { MockItem } from '@/services/types.ts'
import { delay } from 'lodash-es'
import { MockList } from '@/mock'
import { WebviewWindow } from '@tauri-apps/api/window'
const { createWebviewWindow, checkWinExist } = useWindow()
/* 建议把此状态存入localStorage中 */
const activeItem = ref(-1)
const msgBoxShow = ref(false)
/* 独立窗口的集合 */
const aloneWin = ref(new Set())
const shrinkStatus = ref(false)
const itemRef = ref()
export const useMessage = () => {
/* 监听独立窗口关闭事件 */
watchEffect(() => {
Mitt.on(MittEnum.SHRINK_WINDOW, async (event) => {
shrinkStatus.value = event as boolean
})
})
/* 处理点击选中消息 */
const handleMsgClick = (item: MockItem) => {
msgBoxShow.value = true
activeItem.value = item.key
const data = { msgBoxShow, item }
Mitt.emit(MittEnum.MSG_BOX_SHOW, data)
// 判断是否打开了独立的窗口
if (aloneWin.value.has(EventEnum.ALONE + item.key)) {
checkWinExist(EventEnum.ALONE + item.key).then()
activeItem.value = -1
Mitt.emit(MittEnum.MSG_BOX_SHOW, { item: -1 })
}
// 如果是收缩页面状态点击消息框就直接变成独立窗口
if (shrinkStatus.value) {
openAloneWin(item).then()
}
}
/* 处理双击事件 */
const handleMsgDblclick = (item: MockItem) => {
delay(async () => {
await openAloneWin(item)
}, 300)
}
/* 打开独立窗口 */
const openAloneWin = async (item: MockItem) => {
itemRef.value = { ...item }
if (activeItem.value === item.key) {
activeItem.value = -1
Mitt.emit(MittEnum.MSG_BOX_SHOW, { item: -1 })
}
// TODO 传递用户信息(这里的label最好使用用户唯一的id来代替) (nyh -> 2024-03-18 12:18:10)
await createWebviewWindow(item.accountName, EventEnum.ALONE + item.key, 720, 800, '', true, true, 580)
}
const menuList = ref<OPT.RightMenu[]>([
{
label: '置顶',
icon: 'topping',
click: (item: MockItem) => {
const index = MockList.value.findIndex((e) => e.key === item.key)
// 实现置顶功能
if (index !== 0) {
// 交换元素位置
const temp = MockList.value[index]
MockList.value[index] = MockList.value[0]
MockList.value[0] = temp
}
}
},
{
label: '复制账号',
icon: 'copy',
click: (item: MockItem) => {
window.$message.success(`复制成功${item.key}`)
}
},
{ label: '标记未读', icon: 'message-unread' },
{
label: '打开独立聊天窗口',
icon: 'freezing-line-column',
click: async (item: MockItem) => {
await openAloneWin(item)
}
},
{ label: '设置免打扰', icon: 'close-remind' }
])
const specialMenuList = ref<OPT.RightMenu[]>([
{
label: '从消息列表中移除',
icon: 'delete',
click: (item: MockItem) => {
// 根据key找到items中对应的下标
const index = MockList.value.findIndex((e) => e.key === item.key)
// 删除消息的时候判断是否已经打开了独立窗口
if (aloneWin.value.has(`alone${index}`)) {
const win = WebviewWindow.getByLabel(`alone${index}`)
win?.close()
}
// 如果找到了对应的元素,则移除
if (index !== -1) {
const removeItem = MockList.value.splice(index, 1)[0]
if (activeItem.value === removeItem.key) {
if (index < MockList.value.length) {
// 需要使用新的索引位置找到key更新activeItem.value
activeItem.value = MockList.value[index].key
handleMsgClick(MockList.value[index])
} else {
// 如果我们删除的是最后一个元素,则需要选中前一个元素
activeItem.value = MockList.value[MockList.value.length - 1].key
handleMsgClick(MockList.value[MockList.value.length - 1])
}
}
}
}
},
{ label: '屏蔽此人消息', icon: 'forbid' }
])
onMounted(async () => {
await listen(EventEnum.ALONE, () => {
emit(EventEnum.ALONE + itemRef.value.key, itemRef.value)
if (aloneWin.value.has(EventEnum.ALONE + itemRef.value.key)) return
aloneWin.value.add(EventEnum.ALONE + itemRef.value.key)
})
await listen(EventEnum.WIN_CLOSE, (e) => {
aloneWin.value.delete(e.payload)
})
})
return { activeItem, msgBoxShow, handleMsgClick, handleMsgDblclick, menuList, specialMenuList }
}

View File

@ -1,6 +1,11 @@
<template>
<main data-tauri-drag-region class="resizable select-none" :style="{ width: width + 'px' }">
<ActionBar class="absolute right-0" v-if="shrinkStatus" :shrink-status="!shrinkStatus" :max-w="false" />
<ActionBar
class="absolute right-0"
v-if="shrinkStatus"
:shrink-status="!shrinkStatus"
:max-w="false"
:current-label="appWindow.label" />
<!-- <div class="resize-handle" @mousedown="initDrag"></div>-->
@ -10,10 +15,12 @@
class="mt-30px w-full h-38px flex flex-col items-center">
<div class="flex-center gap-5px w-full pr-16px pl-16px box-border">
<n-input
:on-focus="() => router.push('/searchDetails')"
id="search"
@focus="() => router.push('/searchDetails')"
class="rounded-4px w-full"
style="background: var(--search-bg-color)"
:maxlength="20"
clearable
size="small"
placeholder="搜索">
<template #prefix>
@ -29,7 +36,7 @@
</header>
<!-- 列表 -->
<n-scrollbar style="max-height: calc(100vh - 70px)">
<n-scrollbar style="max-height: calc(100vh - 70px)" id="scrollbar">
<div class="h-full flex-1 p-[4px_10px_0px_8px]">
<router-view />
</div>
@ -41,6 +48,7 @@
import Mitt from '@/utils/Bus.ts'
import router from '@/router'
import { MittEnum } from '@/enums'
import { appWindow } from '@tauri-apps/api/window'
// const minWidth = 160 //
// const maxWidth = 320 //
@ -56,6 +64,21 @@ Mitt.on(MittEnum.SHRINK_WINDOW, (event) => {
width.value = 250
})
const closeMenu = (event: Event) => {
const e = event.target as HTMLInputElement
/* 判断如果点击的不是滚动条和搜索框,就关闭消息列表 */
if (!e.matches('#scrollbar, #scrollbar *, #search *, #search')) {
router.push('/message')
}
}
onMounted(() => {
window.addEventListener('click', closeMenu, true)
})
onUnmounted(() => {
window.removeEventListener('click', closeMenu, true)
})
// watchEffect(() => {
// if (width.value === maxWidth) {
// Mitt.emit('shrinkWindow', false)

View File

@ -0,0 +1,21 @@
.item-box {
@apply flex flex-col w-180px h-100vh z-20 p-[12px_0_12px_6px] box-border select-none text-[--text-color];
.item {
height: 42px;
padding-left: 4px;
width: 95%;
box-sizing: border-box;
border-radius: 6px;
&:hover {
cursor: pointer;
background-color: var(--bg-group-hover);
}
}
}
/*! 修改naive-ui虚拟列表滚动条的宽度 */
:deep(
.n-scrollbar > .n-scrollbar-rail.n-scrollbar-rail--vertical,
.n-scrollbar + .n-scrollbar-rail.n-scrollbar-rail--vertical
) {
width: 6px;
}

View File

@ -46,7 +46,6 @@ declare module 'vue' {
NRadio: typeof import('naive-ui')['NRadio']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSkeleton: typeof import('naive-ui')['NSkeleton']
NSpace: typeof import('naive-ui')['NSpace']
NSwitch: typeof import('naive-ui')['NSwitch']
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs']
@ -56,6 +55,5 @@ declare module 'vue' {
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Text: typeof import('./../components/rightBox/renderMessage/Text.vue')['default']
UserSidebar: typeof import('./../components/rightBox/chatBox/UserSidebar.vue')['default']
}
}

View File

@ -47,6 +47,7 @@ import { invoke } from '@tauri-apps/api/tauri'
import { statusItem } from './home-window/onlineStatus/config.ts'
import { onlineStatus } from '@/stores/onlineStatus.ts'
import { appWindow } from '@tauri-apps/api/window'
import { listen } from '@tauri-apps/api/event'
const { checkWinExist, createWebviewWindow } = useWindow()
const OLStatusStore = onlineStatus()
@ -65,6 +66,15 @@ const exit = async () => {
console.error('退出失败:', error)
})
}
onMounted(() => {
//
listen('stop', async () => {
await invoke('tray_blink', { isRun: false }).catch((error) => {
console.error('暂停闪烁失败:', error)
})
})
})
</script>
<style scoped lang="scss">

View File

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

View File

@ -1,132 +0,0 @@
import { MockList } from '@/mock'
import Mitt from '@/utils/Bus.ts'
import { useWindow } from '@/hooks/useWindow.ts'
import { MockItem } from '@/services/types.ts'
import { emit, listen } from '@tauri-apps/api/event'
import { EventEnum, MittEnum } from '@/enums'
import { WebviewWindow } from '@tauri-apps/api/window'
import { delay } from 'lodash-es'
const { createWebviewWindow, checkWinExist } = useWindow()
/* 建议把此状态存入localStorage中 */
const activeItem = ref(-1)
const msgBoxShow = ref(false)
/* 独立窗口的集合 */
const aloneWin = ref(new Set())
const shrinkStatus = ref(false)
/* 监听独立窗口关闭事件 */
watchEffect(async () => {
await listen(EventEnum.WIN_CLOSE, (e) => {
aloneWin.value.delete(e.payload)
})
Mitt.on(MittEnum.SHRINK_WINDOW, async (event) => {
shrinkStatus.value = event as boolean
})
})
/* 处理点击选中消息 */
const handleMsgClick = (item: MockItem) => {
msgBoxShow.value = true
activeItem.value = item.key
const data = { msgBoxShow, item }
Mitt.emit(MittEnum.MSG_BOX_SHOW, data)
// 判断是否打开了独立的窗口
if (aloneWin.value.has(EventEnum.ALONE + item.key)) {
checkWinExist(EventEnum.ALONE + item.key).then()
activeItem.value = -1
Mitt.emit(MittEnum.MSG_BOX_SHOW, { item: -1 })
}
// 如果是收缩页面状态点击消息框就直接变成独立窗口
if (shrinkStatus.value) {
openAloneWin(item).then()
}
}
/* 处理双击事件 */
const handleMsgDblclick = (item: MockItem) => {
delay(async () => {
await openAloneWin(item)
}, 300)
}
/* 打开独立窗口 */
const openAloneWin = async (item: MockItem) => {
if (activeItem.value === item.key) {
activeItem.value = -1
Mitt.emit(MittEnum.MSG_BOX_SHOW, { item: -1 })
}
// TODO 传递用户信息(这里的label最好使用用户唯一的id来代替) (nyh -> 2024-03-18 12:18:10)
await createWebviewWindow(item.accountName, EventEnum.ALONE + item.key, 720, 800)
await listen(EventEnum.ALONE, () => {
emit(EventEnum.ALONE + item.key, item)
aloneWin.value.add(EventEnum.ALONE + item.key)
})
}
const menuList = ref<OPT.RightMenu[]>([
{
label: '置顶',
icon: 'topping',
click: (item: MockItem) => {
const index = MockList.value.findIndex((e) => e.key === item.key)
// 实现置顶功能
if (index !== 0) {
// 交换元素位置
const temp = MockList.value[index]
MockList.value[index] = MockList.value[0]
MockList.value[0] = temp
}
}
},
{
label: '复制账号',
icon: 'copy',
click: (item: MockItem) => {
window.$message.success(`复制成功${item.key}`)
}
},
{ label: '标记未读', icon: 'message-unread' },
{
label: '打开独立聊天窗口',
icon: 'freezing-line-column',
click: async (item: MockItem) => {
await openAloneWin(item)
}
},
{ label: '设置免打扰', icon: 'close-remind' }
])
const specialMenuList = ref<OPT.RightMenu[]>([
{
label: '从消息列表中移除',
icon: 'delete',
click: (item: MockItem) => {
// 根据key找到items中对应的下标
const index = MockList.value.findIndex((e) => e.key === item.key)
// 删除消息的时候判断是否已经打开了独立窗口
if (aloneWin.value.has(`alone${index}`)) {
const win = WebviewWindow.getByLabel(`alone${index}`)
win?.close()
}
// 如果找到了对应的元素,则移除
if (index !== -1) {
const removeItem = MockList.value.splice(index, 1)[0]
if (activeItem.value === removeItem.key) {
if (index < MockList.value.length) {
// 需要使用新的索引位置找到key更新activeItem.value
activeItem.value = MockList.value[index].key
handleMsgClick(MockList.value[index])
} else {
// 如果我们删除的是最后一个元素,则需要选中前一个元素
activeItem.value = MockList.value[MockList.value.length - 1].key
handleMsgClick(MockList.value[MockList.value.length - 1])
}
}
}
}
},
{ label: '屏蔽此人消息', icon: 'forbid' }
])
export { activeItem, msgBoxShow, handleMsgClick, handleMsgDblclick, menuList, specialMenuList }

View File

@ -45,10 +45,11 @@
import Mitt from '@/utils/Bus.ts'
import { VueDraggable } from 'vue-draggable-plus'
import { MockList } from '@/mock'
import { activeItem, handleMsgClick, menuList, specialMenuList, handleMsgDblclick } from './config.ts'
import { useMessage } from '@/hooks/useMessage.ts'
import { MittEnum } from '@/enums'
const msgTotal = ref(0)
const { activeItem, handleMsgClick, menuList, specialMenuList, handleMsgDblclick } = useMessage()
watchEffect(() => {
Mitt.emit(MittEnum.UPDATE_MSG_TOTAL, msgTotal.value)