perf(component): ⚡ 优化右键菜单功能

新增添加好友功能
This commit is contained in:
nongyehong
2024-11-04 21:47:36 +08:00
parent c7cdac69ce
commit 7b530297ac
26 changed files with 306 additions and 39 deletions
+1 -1
View File
@@ -35,7 +35,7 @@
],
"definitions": {
"Capability": {
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```",
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
"type": "object",
"required": ["identifier", "permissions"],
"properties": {
+89
View File
@@ -0,0 +1,89 @@
<template>
<n-modal
v-model:show="globalStore.addFriendModalInfo.show"
:mask-closable="false"
class="rounded-8px"
transform-origin="center">
<div class="bg-[--bg-edit] w-380px h-fit box-border flex flex-col">
<n-flex :size="6" vertical>
<div
v-if="type() === 'macos'"
@click="close"
class="mac-close size-13px shadow-inner bg-#ed6a5eff rounded-50% mt-6px select-none absolute left-6px">
<svg class="hidden size-7px color-#000 font-bold select-none absolute top-3px left-3px">
<use href="#close"></use>
</svg>
</div>
<n-flex class="text-(14px --text-color) select-none pt-6px" justify="center">申请加好友</n-flex>
<svg
v-if="type() === 'windows'"
class="size-14px cursor-pointer pt-6px select-none absolute right-6px"
@click="close">
<use href="#close"></use>
</svg>
<span class="h-1px w-full bg-[--line-color]"></span>
</n-flex>
<n-flex vertical justify="center" :size="20" class="p-20px">
<n-flex align="center" justify="center" :size="20">
<n-avatar round size="large" :src="userInfo.avatar" />
<n-flex vertical :size="10">
<p class="text-[--text-color]">{{ userInfo.name }}</p>
<p class="text-(12px [--text-color])">uid: {{ userInfo.uid }}</p>
</n-flex>
</n-flex>
<n-input
v-model:value="requestMsg"
:allow-input="(value: string) => !value.startsWith(' ') && !value.endsWith(' ')"
:autosize="{
minRows: 3,
maxRows: 3
}"
:maxlength="60"
:count-graphemes="countGraphemes"
show-count
type="textarea"
placeholder="输入几句话,对TA说些什么吧" />
<n-button color="#13987f" @click="addFriend">添加好友</n-button>
</n-flex>
</div>
</n-modal>
</template>
<script setup lang="ts">
import { useGlobalStore } from '@/stores/global.ts'
import { type } from '@tauri-apps/plugin-os'
import { useUserInfo } from '@/hooks/useCached.ts'
import { leftHook } from '@/layout/left/hook.ts'
import apis from '@/services/apis.ts'
const globalStore = useGlobalStore()
const { countGraphemes } = leftHook()
const userInfo = ref(useUserInfo(globalStore.addFriendModalInfo.uid).value)
const requestMsg = ref()
const close = () => {
globalStore.addFriendModalInfo.show = false
globalStore.addFriendModalInfo.uid = undefined
}
const addFriend = async () => {
await apis.sendAddFriendRequest({
msg: requestMsg.value,
targetUid: globalStore.addFriendModalInfo.uid as number
})
window.$message.success('已发送好友申请')
close()
}
watch(globalStore.addFriendModalInfo, (val) => {
if (val.show) {
userInfo.value = useUserInfo(val.uid).value
}
})
</script>
<style scoped lang="scss"></style>
+18 -3
View File
@@ -36,8 +36,8 @@
left: `${pos.posX}px`,
top: `${pos.posY}px`
}">
<div v-resize="handleSize" v-if="menu && menu.length > 0" class="menu-list">
<div v-for="(item, index) in menu as any[]" :key="index">
<div v-resize="handleSize" v-if="visibleMenu && visibleMenu.length > 0" class="menu-list">
<div v-for="(item, index) in visibleMenu as any[]" :key="index">
<!-- 禁止的菜单选项需要禁止点击事件 -->
<div class="menu-item-disabled" v-if="item.disabled" @click.prevent="$event.preventDefault()">
<svg><use :href="`#${item.icon}`"></use></svg>
@@ -68,7 +68,11 @@
import { useContextMenu } from '@/hooks/useContextMenu.ts'
import { useViewport } from '@/hooks/useViewport.ts'
const { menu, emoji, specialMenu } = defineProps({
const { content, menu, emoji, specialMenu } = defineProps({
content: {
type: Object,
required: false
},
menu: {
type: Array
},
@@ -80,6 +84,17 @@ const { menu, emoji, specialMenu } = defineProps({
default: () => []
}
})
// 使用计算属性过滤显示的菜单项
const visibleMenu = computed(() => {
return menu?.filter((item: any) => {
// 检查是否有 visible 属性并作为函数调用
if (typeof item.visible === 'function') {
return item.visible(content) // 如果 visible 是函数,则调用它
}
// 如果没有 visible 属性,则默认显示
return true
})
})
/** 判断是否传入了menu */
const isNull = computed(() => menu === void 0)
const containerRef = ref(null)
+59
View File
@@ -0,0 +1,59 @@
<template>
<n-flex vertical class="select-none">
<n-flex align="center" justify="space-between" class="color-[--text-color] px-20px py-10px">
<p class="text-16px">好友通知</p>
<svg class="size-18px cursor-pointer"><use href="#delete"></use></svg>
</n-flex>
<n-flex
vertical
:size="10"
class="p-[0_30px]"
v-for="(item, index) in contactStore.requestFriendsList"
:key="index">
<n-flex align="center" justify="space-between" class="bg-[--center-bg-color] rounded-10px p-20px box-border">
<n-flex align="center" :size="10">
<n-avatar round size="large" :src="useUserInfo(item.uid).value.avatar" class="mr-10px" />
<n-flex vertical :size="12">
<n-flex align="center" :size="10">
<n-popover
trigger="click"
placement="bottom-start"
:show-arrow="false"
style="padding: 0; background: var(--bg-info); backdrop-filter: blur(10px)">
<template #trigger>
<p @click="currentUserId = item.uid" class="text-(14px #13987f) cursor-pointer">
{{ useUserInfo(item.uid).value.name }}
</p>
</template>
<!-- 用户个人信息框 -->
<InfoPopover v-if="currentUserId === item.uid" :uid="item.uid" />
</n-popover>
<p class="text-(14px [--text-color])">请求加为好友</p>
</n-flex>
<p v-show="item.msg" class="text-(12px [--text-color])">留言:{{ item.msg }}</p>
</n-flex>
</n-flex>
<n-button
secondary
v-if="item.status === RequestFriendAgreeStatus.Waiting"
@click="contactStore.onAcceptFriend(item.applyId)">
接受
</n-button>
<span class="text-(12px [--text-color])" v-else>已添加</span>
</n-flex>
</n-flex>
</n-flex>
</template>
<script setup lang="ts">
import { useContactStore } from '@/stores/contacts.ts'
import { useUserInfo } from '@/hooks/useCached.ts'
import { RequestFriendAgreeStatus } from '@/services/types.ts'
const contactStore = useContactStore()
const currentUserId = ref(0)
</script>
<style scoped lang="scss"></style>
+1 -1
View File
@@ -124,7 +124,7 @@ const { content } = defineProps<{
content: any
}>()
const item = computed(() => {
return useUserInfo(content.value.uid).value
return useUserInfo(content.uid).value
})
const footerOptions = ref<OPT.Details[]>([
+5 -2
View File
@@ -91,6 +91,7 @@
<template #trigger>
<ContextMenu
@select="$event.click(item, 'Main')"
:content="item"
:menu="chatStore.isGroup ? optionsList : void 0"
:special-menu="report">
<n-avatar
@@ -122,6 +123,7 @@
:style="item.fromUser.uid === userUid ? 'flex-direction: row-reverse' : ''">
<ContextMenu
@select="$event.click(item)"
:content="item"
:menu="chatStore.isGroup ? optionsList : []"
:special-menu="report">
<n-flex
@@ -173,6 +175,7 @@
</n-flex>
<!-- 气泡样式 -->
<ContextMenu
:content="item"
@contextmenu="handleMacSelect"
@mouseenter="handleMouseEnter(item.message.id)"
@mouseleave="handleMouseLeave"
@@ -575,9 +578,9 @@ onMounted(() => {
})
})
Mitt.on(`${MittEnum.INFO_POPOVER}-Main`, (event: any) => {
selectKey.value = event
selectKey.value = event.uid
infoPopover.value = true
handlePopoverUpdate(event)
handlePopoverUpdate(event.uid)
})
Mitt.on(MittEnum.MSG_BOX_SHOW, (event: any) => {
activeItemRef.value = event.item
@@ -57,7 +57,11 @@
v-model:show="infoPopover"
style="padding: 0; background: var(--bg-info); backdrop-filter: blur(10px)">
<template #trigger>
<ContextMenu @select="$event.click(item, 'Sidebar')" :menu="optionsList" :special-menu="report">
<ContextMenu
:content="item"
@select="$event.click(item, 'Sidebar')"
:menu="optionsList"
:special-menu="report">
<n-flex @click="selectKey = item.uid" :key="item.uid" :size="10" align="center" class="item">
<n-avatar
lazy
@@ -133,9 +137,9 @@ const handleSearch = () => {
onMounted(() => {
Mitt.on(`${MittEnum.INFO_POPOVER}-Sidebar`, (event: any) => {
selectKey.value = event
selectKey.value = event.uid
infoPopover.value = true
handlePopoverUpdate(event)
handlePopoverUpdate(event.uid)
})
})
</script>
+2
View File
@@ -62,6 +62,8 @@ export enum MittEnum {
SHRINK_WINDOW,
/** 详情页面显示 */
DETAILS_SHOW,
/** 好友申请页面显示 */
APPLY_SHOW,
/** 消息列表被清空或者暂无消息 */
NOT_MSG,
/** 回复消息 */
+33 -4
View File
@@ -1,13 +1,18 @@
import { useCommon } from '@/hooks/useCommon.ts'
import { MittEnum, MsgEnum } from '@/enums'
import { MittEnum, MsgEnum, PowerEnum } from '@/enums'
import { MessageType, SessionItem } from '@/services/types.ts'
import Mitt from '@/utils/Bus.ts'
import { useChatStore } from '@/stores/chat.ts'
import apis from '@/services/apis.ts'
import { useContactStore } from '@/stores/contacts'
import { useUserStore } from '@/stores/user'
import { useGlobalStore } from '@/stores/global.ts'
export const useChatMain = (activeItem?: SessionItem) => {
const { removeTag, userUid } = useCommon()
const globalStore = useGlobalStore()
const chatStore = useChatStore()
const userInfo = useUserStore()?.userInfo
// const userInfo = useUserStore()?.userInfo
// const chatMessageList = computed(() => chatStore.chatMessageList)
const messageOptions = computed(() => chatStore.currentMessageOptions)
@@ -80,6 +85,11 @@ export const useChatMain = (activeItem?: SessionItem) => {
return
}
chatStore.updateRecallStatus({ msgId: item.message.id })
},
visible: (item: MessageType) => {
const isCurrentUser = item.fromUser.uid === userUid.value
const isAdmin = userInfo?.power === PowerEnum.ADMIN
return isCurrentUser || isAdmin
}
}
])
@@ -153,7 +163,7 @@ export const useChatMain = (activeItem?: SessionItem) => {
}
])
/** 右键用户信息菜单(群聊的时候显示) */
const optionsList = ref([
const optionsList = ref<OPT.RightMenu[]>([
{
label: '发送信息',
icon: 'message-action',
@@ -170,13 +180,19 @@ export const useChatMain = (activeItem?: SessionItem) => {
label: '查看资料',
icon: 'notes',
click: (item: any, type: string) => {
Mitt.emit(`${MittEnum.INFO_POPOVER}-${type}`, item.key)
// 如果是聊天框内的资料就使用的是消息的key,如果是群聊成员的资料就使用的是uid
const uid = item.uid || item.message.id
Mitt.emit(`${MittEnum.INFO_POPOVER}-${type}`, { uid: uid, type: type })
}
},
{
label: '添加好友',
icon: 'people-plus',
click: () => {}
click: (item: any) => {
globalStore.addFriendModalInfo.show = true
globalStore.addFriendModalInfo.uid = item.uid || item.fromUser.uid
},
visible: (item: any) => canAddFriend(item.uid || item.fromUser.uid)
}
])
/** 举报选项 */
@@ -207,6 +223,19 @@ export const useChatMain = (activeItem?: SessionItem) => {
}
])
/**
* 判断用户是否可以添加好友
* @param uid 用户 ID
* @returns {boolean} 如果可以添加好友返回 true,否则返回 false
*/
const canAddFriend = (uid: number): boolean => {
const contactStore = useContactStore()
const userStore = useUserStore()
const myUid = userStore.userInfo.uid
// 好友和自己不显示添加好友菜单
return !(contactStore.contactsList.some((item) => item.uid === uid) || uid === myUid)
}
/**
* 处理复制事件
* @param content 复制的内容
+15 -1
View File
@@ -1,9 +1,11 @@
<template>
<div class="flex size-full min-w-310px">
<div id="layout" class="flex size-full min-w-310px">
<Left />
<Center />
<Right v-if="!shrinkStatus" />
</div>
<AddFriendsModal />
</template>
<script setup lang="ts">
@@ -13,7 +15,13 @@ import Right from './right/index.vue'
import Mitt from '@/utils/Bus'
import { MittEnum } from '@/enums'
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
import { useGlobalStore } from '@/stores/global.ts'
import { useContactStore } from '@/stores/contacts.ts'
const globalStore = useGlobalStore()
const contactStore = useContactStore()
// 清空未读消息
globalStore.unReadMark.newMsgUnreadCount = 0
const shrinkStatus = ref(false)
/**
* event默认如果没有传递值就为true,所以shrinkStatus的值为false就会发生值的变化
@@ -23,6 +31,12 @@ Mitt.on(MittEnum.SHRINK_WINDOW, (event) => {
shrinkStatus.value = event as boolean
})
onBeforeMount(() => {
// 默认执行一次
contactStore.getContactList(true)
contactStore.getRequestFriendsList(true)
})
onMounted(async () => {
await getCurrentWebviewWindow().show()
})
+8 -1
View File
@@ -43,7 +43,11 @@
</n-flex>
</n-popover>
<!-- 该选项无提示时展示 -->
<n-badge v-else :max="99" :value="item.badge">
<n-badge
v-else
:max="99"
:value="unReadMark.newMsgUnreadCount"
:show="item.icon.includes('message') && unReadMark.newMsgUnreadCount > 0">
<svg class="size-22px">
<use
:href="`#${activeUrl === item.url || openWindowsList.has(item.url) ? item.iconAction : item.icon}`"></use>
@@ -249,11 +253,14 @@ import { PluginEnum, ShowModeEnum } from '@/enums'
import { useSettingStore } from '@/stores/setting.ts'
import { invoke } from '@tauri-apps/api/core'
import { listen } from '@tauri-apps/api/event'
import { useGlobalStore } from '@/stores/global.ts'
const globalStore = useGlobalStore()
const pluginsStore = usePluginsStore()
const { showMode } = storeToRefs(useSettingStore())
const { menuTop } = useMenuTopStore()
const { plugins } = storeToRefs(pluginsStore)
const unReadMark = computed(() => globalStore.unReadMark)
// const headerRef = useTemplateRef('header')
// const actionListRef = useTemplateRef('actionList')
//const { } = toRefs(getCurrentInstance) // 所有菜单的外层div
+1 -5
View File
@@ -120,11 +120,7 @@ export const leftHook = () => {
window.$message.error('改名次数不足')
return
}
apis.modifyUserName(editInfo.value.content.name).then((res: any) => {
if (!res) {
window.$message.error(res)
return
}
apis.modifyUserName(editInfo.value.content.name).then(() => {
// 更新本地缓存的用户信息
login.accountInfo.name = editInfo.value.content.name!
updateCurrentUserCache('name', editInfo.value.content.name) // 更新缓存里面的用户信息
+2 -2
View File
@@ -8,7 +8,7 @@
<!-- 导航选项按钮模块 -->
<ActionList />
<!-- 编辑资料弹窗 -->
<InfoPopover />
<InfoEdit />
<!-- 弹出框 -->
<component :is="componentMap" />
@@ -18,7 +18,7 @@
<script lang="tsx" setup>
import LeftAvatar from './components/LeftAvatar.vue'
import ActionList from './components/ActionList.vue'
import InfoPopover from './components/InfoPopover.vue'
import InfoEdit from './components/InfoEdit.vue'
import Mitt from '@/utils/Bus.ts'
import { lock, LockScreen, CheckUpdate } from './model.tsx'
import { DefineComponent, DefineSetupFnComponent } from 'vue'
+8 -1
View File
@@ -1,12 +1,15 @@
<template>
<main data-tauri-drag-region class="flex-1 bg-[--right-bg-color] h-full w-100vw min-w-600px">
<div class="size-full" style="background: var(--right-theme-bg-color)" data-tauri-drag-region>
<div class="size-full" :style="{ background: isChat ? 'var(--right-theme-bg-color)' : '' }" data-tauri-drag-region>
<ActionBar :current-label="appWindow.label" />
<!-- 需要判断当前路由是否是信息详情界面 -->
<ChatBox :active-item="activeItem" v-if="msgBoxShow && isChat && activeItem !== -1" />
<Details :content="DetailsContent" v-else-if="detailsShow && isDetails" />
<!-- 好友申请列表 -->
<ApplyList v-else-if="DetailsContent && DetailsContent.type === 'apply'" />
<!-- 聊天界面背景图标 -->
<div v-else class="flex-center size-full select-none">
<img
@@ -73,7 +76,11 @@ onMounted(() => {
}
if (isDetails) {
Mitt.on(MittEnum.APPLY_SHOW, (event: any) => {
DetailsContent.value = event.context
})
Mitt.on(MittEnum.DETAILS_SHOW, (event: any) => {
console.log('event', event)
DetailsContent.value = event.context
detailsShow.value = event.detailsShow as boolean
})
+2 -3
View File
@@ -1,8 +1,7 @@
// import { createAxios } from '@/services/request'
import urls from '@/services/urls'
import type {
Response,
// BadgeType,
BadgeType,
CacheBadgeItem,
CacheBadgeReq,
CacheUserItem,
@@ -57,7 +56,7 @@ export default {
/** 获取用户详细信息 */
getUserDetail: () => GET<UserInfoType>(urls.getUserInfoDetail, {}),
/** 获取徽章列表 */
getBadgeList: (): Promise<Response> => GET(urls.getBadgeList),
getBadgeList: (): Promise<BadgeType> => GET(urls.getBadgeList),
/** 设置用户勋章 */
setUserBadge: (badgeId: number) => PUT<void>(urls.setUserBadge, { badgeId }),
/** 修改用户名 */
+11 -3
View File
@@ -1,6 +1,7 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'
import { useSettingStore } from '@/stores/setting.ts'
import Http, { HttpParams } from './http.ts'
import { ServiceResponse } from '@/services/types.ts'
/** 是否是测试环境 */
const isTest = computed(() => {
@@ -179,7 +180,9 @@ const responseInterceptor = async <T>(
const data = await Http(url, httpParams, true)
const resp = data.resp
const serviceData = (await data.data) as ServiceResponse
//检查发送请求是否成功
if (resp.status > 400) {
let message = ''
switch (resp.status) {
@@ -225,9 +228,14 @@ const responseInterceptor = async <T>(
}
return Promise.reject(`err: ${message}, status: ${resp.status}`)
}
const res: any = await data.data
return Promise.resolve(res.data)
console.log(data)
//检查服务端返回是否成功,并且中断请求
if (!serviceData.success) {
window.$message.error(serviceData.errMsg)
return Promise.reject(`http error: ${serviceData.errMsg}`)
}
console.log(data)
return Promise.resolve(serviceData.data)
} catch (err) {
return Promise.reject(`http error: ${err}`)
}
+1 -1
View File
@@ -6,7 +6,7 @@
import { ActEnum, IsYetEnum, MarkEnum, MsgEnum, OnlineEnum, RoomTypeEnum, SexEnum } from '@/enums'
/**响应请求体*/
export type Response = {
export type ServiceResponse = {
/** 成功标识true or false */
success: boolean
/** 错误码 */
+2 -2
View File
@@ -1,8 +1,8 @@
import { URLEnum } from '@/enums'
const { PROD, VITE_SERVICE_URL } = import.meta.env
const { VITE_SERVICE_URL } = import.meta.env
// 本地配置到 .env.development 里面修改。生产配置在 .env.production 里面
const prefix = PROD ? VITE_SERVICE_URL : ''
const prefix = VITE_SERVICE_URL
export default {
getUserInfo: `${prefix + URLEnum.USER}/userInfo`, // 获取用户信息
+9
View File
@@ -11,6 +11,11 @@ export const useContactStore = defineStore('contact', () => {
const requestFriendsList = reactive<RequestFriendItem[]>([])
const contactsOptions = reactive({ isLast: false, isLoading: false, cursor: '' })
const requestFriendsOptions = reactive({ isLast: false, isLoading: false, cursor: '' })
/**
* 获取联系人列表
* @param isFresh 是否刷新
*/
const getContactList = async (isFresh = false) => {
if (!isFresh) {
if (contactsOptions.isLast || contactsOptions.isLoading) return
@@ -45,6 +50,10 @@ export const useContactStore = defineStore('contact', () => {
}
}
/**
* 获取好友申请列表
* @param isFresh 是否刷新
*/
const getRequestFriendsList = async (isFresh = false) => {
if (!isFresh) {
if (requestFriendsOptions.isLast || requestFriendsOptions.isLoading) return
+2 -2
View File
@@ -94,6 +94,7 @@ export const useGroupStore = defineStore('group', () => {
roomId: currentRoomId.value
}
})
console.log(res)
if (!res) return
const data = res
const tempNew = cloneDeep(uniqueUserList(refresh ? data.list : [...data.list, ...userList.value]))
@@ -112,8 +113,7 @@ export const useGroupStore = defineStore('group', () => {
// 获取群成员数量统计
const getCountStatistic = async () => {
const data = await apis.groupDetail({ id: currentRoomId.value })
countInfo.value = data
countInfo.value = await apis.groupDetail({ id: currentRoomId.value })
}
// 加载更多群成员
+4
View File
@@ -8,6 +8,8 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
ActionBar: typeof import('./../components/windows/ActionBar.vue')['default']
AddFriendsModal: typeof import('./../components/common/AddFriendsModal.vue')['default']
ApplyList: typeof import('./../components/rightBox/ApplyList.vue')['default']
ChatBox: typeof import('./../components/rightBox/chatBox/index.vue')['default']
ChatFooter: typeof import('./../components/rightBox/chatBox/ChatFooter.vue')['default']
ChatHeader: typeof import('./../components/rightBox/chatBox/ChatHeader.vue')['default']
@@ -34,6 +36,7 @@ declare module 'vue' {
NDropdown: typeof import('naive-ui')['NDropdown']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NFlex: typeof import('naive-ui')['NFlex']
NGradientText: typeof import('naive-ui')['NGradientText']
NIcon: typeof import('naive-ui')['NIcon']
NIconWrapper: typeof import('naive-ui')['NIconWrapper']
NImage: typeof import('naive-ui')['NImage']
@@ -57,6 +60,7 @@ declare module 'vue' {
NTab: typeof import('naive-ui')['NTab']
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs']
NText: typeof import('naive-ui')['NText']
NTooltip: typeof import('naive-ui')['NTooltip']
NVirtualList: typeof import('naive-ui')['NVirtualList']
RenderMessage: typeof import('./../components/rightBox/renderMessage/index.vue')['default']
+2
View File
@@ -63,11 +63,13 @@ declare namespace OPT {
* @param label 选项名称
* @param icon 选项图标
* @param click 点击事件
* @param visible 显示条件
*/
type RightMenu = {
label: string
icon: string
click?: (...args: any[]) => void
visible?: (...args: any[]) => void
} | null
/**
+20 -1
View File
@@ -1,5 +1,13 @@
<template>
<n-scrollbar style="max-height: calc(100vh - 70px)">
<n-flex
@click="handleApply"
align="center"
justify="space-between"
class="my-10px p-12px hover:(bg-[--list-hover-color] cursor-pointer)">
<div class="text-(14px [--text-color])">好友通知</div>
<svg class="size-16px rotate-270 color-[--text-color]"><use href="#down"></use></svg>
</n-flex>
<n-tabs type="segment" animated class="mt-4px p-[4px_10px_0px_8px]">
<n-tab-pane name="1" tab="好友">
<n-scrollbar style="max-height: calc(100vh - 126px)">
@@ -7,7 +15,9 @@
<ContextMenu @contextmenu="showMenu($event)" @select="handleSelect($event.label)" :menu="menuList">
<n-collapse-item title="我的好友" name="1">
<template #header-extra>
<span class="text-(10px #707070)">0/0</span>
<span class="text-(10px #707070)">
{{ contactStore.contactsList.length }}/{{ contactStore.contactsList.length }}
</span>
</template>
<!-- 用户框 多套一层div来移除默认的右键事件然后覆盖掉因为margin空隙而导致右键可用 -->
@@ -88,6 +98,7 @@ watchEffect(() => {
const handleClick = (index: number, type: number) => {
detailsShow.value = true
activeItem.value = index
console.log(index, type)
const data = {
context: {
type: type,
@@ -106,6 +117,14 @@ const handleSelect = (event: MouseEvent) => {
console.log(event)
}
const handleApply = () => {
Mitt.emit(MittEnum.APPLY_SHOW, {
context: {
type: 'apply'
}
})
}
onUnmounted(() => {
detailsShow.value = false
Mitt.emit(MittEnum.DETAILS_SHOW, detailsShow.value)
+2 -1
View File
@@ -71,6 +71,7 @@ const globalStore = useGlobalStore()
const { userUid } = useCommon()
const scrollbar = ref()
const { handleMsgClick, activeIndex, menuList, specialMenuList, handleMsgDblclick } = useMessage()
// TODO 艾特我提醒
const sessionList = computed(() =>
chatStore.sessionList.map((item) => {
// 最后一条消息内容
@@ -122,7 +123,7 @@ watchEffect(() => {
onBeforeMount(() => {
// 请求回话列表
chatStore.getSessionList(true)
chatStore.getSessionList()
})
onMounted(() => {
@@ -142,8 +142,8 @@ import { invoke } from '@tauri-apps/api/core'
import { emit } from '@tauri-apps/api/event'
const settingStore = useSettingStore()
const { themes, tips, escClose, chat, page } = settingStore
const { showMode } = storeToRefs(settingStore)
const { themes, tips, chat, page } = settingStore
const { showMode, escClose } = storeToRefs(settingStore)
const activeItem = ref<string>(themes.pattern)
const showText = computed({