mirror of
https://gitee.com/HuLaSpark/HuLa.git
synced 2024-11-29 18:28:30 +08:00
perf(views): ⚡ 优化页面收缩功能
优化连接错误提示 优化自动登录时可中断登录
This commit is contained in:
parent
94d2cb1fec
commit
31f7e1732c
@ -7,7 +7,7 @@
|
|||||||
<title>HuLa</title>
|
<title>HuLa</title>
|
||||||
|
|
||||||
<!--引入iconpark图标库-->
|
<!--引入iconpark图标库-->
|
||||||
<script defer src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_30895_78.2ef5ae05e210de3f66b0fe5c58a7a130.js"></script>
|
<script defer src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_30895_79.b97eb7e69b543ed2cec6ad36e086c40b.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -24,10 +24,7 @@
|
|||||||
class="flex flex-col w-full"
|
class="flex flex-col w-full"
|
||||||
:class="[{ 'items-end': item.accountId === userId }, isGroup ? 'gap-18px' : 'gap-2px']">
|
:class="[{ 'items-end': item.accountId === userId }, isGroup ? 'gap-18px' : 'gap-2px']">
|
||||||
<!-- 信息时间(单聊) -->
|
<!-- 信息时间(单聊) -->
|
||||||
<div
|
<div v-if="!isGroup" class="text-(12px #909090) h-12px w-fit select-none" :class="getTimePosition(item)">
|
||||||
v-if="!isGroup"
|
|
||||||
class="text-(12px #909090) h-12px w-fit select-none"
|
|
||||||
:class="item.accountId === userId ? 'pr-42px' : 'pl-42px'">
|
|
||||||
<Transition name="fade">
|
<Transition name="fade">
|
||||||
<span v-if="hoverBubble.key === item.key">
|
<span v-if="hoverBubble.key === item.key">
|
||||||
{{ new Date().toLocaleString() }}
|
{{ new Date().toLocaleString() }}
|
||||||
@ -348,6 +345,12 @@ watchEffect(() => {
|
|||||||
activeItemRef.value = { ...activeItem }
|
activeItemRef.value = { ...activeItem }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 计算单聊时间戳显示的位置 */
|
||||||
|
const getTimePosition = (item: any) => {
|
||||||
|
const pxVal = activeReply.value === item.key ? '68px' : '42px'
|
||||||
|
return item.accountId === userId.value ? `pr-${pxVal}` : `pl-${pxVal}`
|
||||||
|
}
|
||||||
|
|
||||||
// 当鼠标进入时触发的处理函数
|
// 当鼠标进入时触发的处理函数
|
||||||
const handleMouseEnter = (key: any) => {
|
const handleMouseEnter = (key: any) => {
|
||||||
// 设置定时器,在1600毫秒后更新悬浮气泡的key值
|
// 设置定时器,在1600毫秒后更新悬浮气泡的key值
|
||||||
@ -542,13 +545,4 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import '@/styles/scss/chat-main';
|
@import '@/styles/scss/chat-main';
|
||||||
|
|
||||||
.fade-enter-active,
|
|
||||||
.fade-leave-active {
|
|
||||||
transition: opacity 0.4s ease-in-out;
|
|
||||||
}
|
|
||||||
.fade-enter-from,
|
|
||||||
.fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<main data-tauri-drag-region class="resizable select-none" :style="{ width: width + 'px' }">
|
<main
|
||||||
|
data-tauri-drag-region
|
||||||
|
id="center"
|
||||||
|
class="resizable select-none flex flex-col"
|
||||||
|
:style="{ width: `${initWidth}px` }">
|
||||||
|
<div class="resize-handle" @mousedown="initDrag"></div>
|
||||||
<ActionBar
|
<ActionBar
|
||||||
class="absolute right-0"
|
class="absolute right-0"
|
||||||
v-if="shrinkStatus"
|
v-if="shrinkStatus"
|
||||||
@ -48,20 +53,47 @@ import router from '@/router'
|
|||||||
import { MittEnum } from '@/enums'
|
import { MittEnum } from '@/enums'
|
||||||
import { appWindow } from '@tauri-apps/api/window'
|
import { appWindow } from '@tauri-apps/api/window'
|
||||||
|
|
||||||
// const minWidth = 160 // 设置最小宽度
|
/** 设置最小宽度 */
|
||||||
// const maxWidth = 320 // 设置最大宽度
|
const minWidth = 160
|
||||||
const width = ref(250) // 初始化宽度
|
/** 设置最大宽度 */
|
||||||
|
const maxWidth = 300
|
||||||
|
/** 初始化宽度 */
|
||||||
|
const initWidth = ref(250)
|
||||||
|
/** 窗口宽度 */
|
||||||
|
const windowWidth = ref(0)
|
||||||
|
/** 是否拖拽 */
|
||||||
|
const isDrag = ref(true)
|
||||||
|
/** 当前消息 */
|
||||||
|
const currentMsg = ref()
|
||||||
|
|
||||||
// const startX = ref()
|
const startX = ref()
|
||||||
// const startWidth = ref()
|
const startWidth = ref()
|
||||||
const shrinkStatus = ref(false)
|
const shrinkStatus = ref(false)
|
||||||
|
|
||||||
// todo 1.了解这里是怎么实现的 2.修改拖拽放大缩小的事件
|
watchEffect(() => {
|
||||||
Mitt.on(MittEnum.SHRINK_WINDOW, (event) => {
|
if (windowWidth.value >= 310 && windowWidth.value < 800) {
|
||||||
shrinkStatus.value = event as boolean
|
Mitt.emit(MittEnum.SHRINK_WINDOW, true)
|
||||||
width.value = 250
|
const center = document.querySelector('#center')
|
||||||
|
center?.classList.add('flex-1')
|
||||||
|
isDrag.value = false
|
||||||
|
}
|
||||||
|
if (windowWidth.value >= 800) {
|
||||||
|
Mitt.emit(MittEnum.SHRINK_WINDOW, false)
|
||||||
|
if (currentMsg.value) {
|
||||||
|
Mitt.emit(MittEnum.MSG_BOX_SHOW, { msgBoxShow: true, ...currentMsg.value })
|
||||||
|
}
|
||||||
|
const center = document.querySelector('#center')
|
||||||
|
center?.classList.remove('flex-1')
|
||||||
|
isDrag.value = true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 更新窗口宽度 */
|
||||||
|
const updateWindowWidth = async () => {
|
||||||
|
const { width } = await appWindow.innerSize()
|
||||||
|
windowWidth.value = width
|
||||||
|
}
|
||||||
|
|
||||||
const closeMenu = (event: Event) => {
|
const closeMenu = (event: Event) => {
|
||||||
const e = event.target as HTMLInputElement
|
const e = event.target as HTMLInputElement
|
||||||
const route = router.currentRoute.value.path
|
const route = router.currentRoute.value.path
|
||||||
@ -71,42 +103,54 @@ const closeMenu = (event: Event) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
/** 定义一个函数,在鼠标拖动时调用 */
|
||||||
|
const doDrag = (e: MouseEvent) => {
|
||||||
|
// 使用 requestAnimationFrame 来处理动画,确保动画在下一帧渲染前执行
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
// 计算新的宽度
|
||||||
|
const newWidth = startWidth.value + e.clientX - startX.value
|
||||||
|
// 如果新宽度不等于最大宽度,则更新宽度值
|
||||||
|
if (newWidth !== maxWidth) {
|
||||||
|
initWidth.value = clamp(newWidth, minWidth, maxWidth) // 使用 clamp 函数限制宽度值在最小值和最大值之间
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 定义一个函数,用于将数值限制在指定的最小值和最大值之间 */
|
||||||
|
const clamp = (value: number, min: number, max: number) => {
|
||||||
|
return Math.min(Math.max(value, min), max) // 使用 Math.min 和 Math.max 函数来限制数值范围
|
||||||
|
}
|
||||||
|
|
||||||
|
const initDrag = (e: MouseEvent) => {
|
||||||
|
if (!isDrag.value) return
|
||||||
|
startX.value = e.clientX
|
||||||
|
startWidth.value = initWidth.value
|
||||||
|
document.addEventListener('mousemove', doDrag, false)
|
||||||
|
document.addEventListener('mouseup', stopDrag, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopDrag = () => {
|
||||||
|
document.removeEventListener('mousemove', doDrag, false)
|
||||||
|
document.removeEventListener('mouseup', stopDrag, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await updateWindowWidth()
|
||||||
|
Mitt.on(MittEnum.SHRINK_WINDOW, (event) => {
|
||||||
|
shrinkStatus.value = event as boolean
|
||||||
|
})
|
||||||
|
Mitt.on(MittEnum.MSG_BOX_SHOW, (event: any) => {
|
||||||
|
if (!event) return
|
||||||
|
currentMsg.value = event
|
||||||
|
})
|
||||||
|
window.addEventListener('resize', updateWindowWidth)
|
||||||
window.addEventListener('click', closeMenu, true)
|
window.addEventListener('click', closeMenu, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', updateWindowWidth)
|
||||||
window.removeEventListener('click', closeMenu, true)
|
window.removeEventListener('click', closeMenu, true)
|
||||||
})
|
})
|
||||||
// watchEffect(() => {
|
|
||||||
// if (width.value === maxWidth) {
|
|
||||||
// Mitt.emit('shrinkWindow', false)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const initDrag = (e: MouseEvent) => {
|
|
||||||
// startX.value = e.clientX
|
|
||||||
// startWidth.value = width.value
|
|
||||||
//
|
|
||||||
// document.addEventListener('mousemove', doDrag, false)
|
|
||||||
// document.addEventListener('mouseup', stopDrag, false)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// const doDrag = (e: MouseEvent) => {
|
|
||||||
// const newWidth = startWidth.value + e.clientX - startX.value
|
|
||||||
// if (newWidth <= maxWidth && newWidth >= minWidth) {
|
|
||||||
// width.value = newWidth
|
|
||||||
// } else if (newWidth > maxWidth) {
|
|
||||||
// width.value = maxWidth
|
|
||||||
// } else if (newWidth < minWidth) {
|
|
||||||
// width.value = minWidth
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// const stopDrag = () => {
|
|
||||||
// document.removeEventListener('mousemove', doDrag, false)
|
|
||||||
// document.removeEventListener('mouseup', stopDrag, false)
|
|
||||||
// }
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 2px;
|
width: 1px;
|
||||||
cursor: ew-resize;
|
cursor: ew-resize;
|
||||||
background-color: #ccc; /** 可以根据需要更改颜色 */
|
z-index: 9999;
|
||||||
|
background-color: var(--split-color);
|
||||||
}
|
}
|
@ -13,7 +13,6 @@ import Right from './right/index.vue'
|
|||||||
import Mitt from '@/utils/Bus'
|
import Mitt from '@/utils/Bus'
|
||||||
import { MittEnum } from '@/enums'
|
import { MittEnum } from '@/enums'
|
||||||
|
|
||||||
/** todo home窗口创建的时候已经设置了resizable: true,可以调整大小了,但是还是不可以调整大小 */
|
|
||||||
const shrinkStatus = ref(false)
|
const shrinkStatus = ref(false)
|
||||||
/**
|
/**
|
||||||
* event默认如果没有传递值就为true,所以shrinkStatus的值为false就会发生值的变化
|
* event默认如果没有传递值就为true,所以shrinkStatus的值为false就会发生值的变化
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<main data-tauri-drag-region class="left w-60px h-full p-[30px_6px_15px] box-border flex-col-center select-none">
|
<main data-tauri-drag-region class="left min-w-60px h-full p-[30px_6px_15px] box-border flex-col-center select-none">
|
||||||
<!-- 点击时头像内容框 -->
|
<!-- 点击时头像内容框 -->
|
||||||
<n-popover
|
<n-popover
|
||||||
v-model:show="infoShow"
|
v-model:show="infoShow"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="flex-1 bg-[--right-bg-color] h-full w-100vw">
|
<main class="flex-1 bg-[--right-bg-color] h-full w-100vw min-w-600px">
|
||||||
<ActionBar :current-label="appWindow.label" />
|
<ActionBar :current-label="appWindow.label" />
|
||||||
<!-- 需要判断当前路由是否是信息详情界面 -->
|
<!-- 需要判断当前路由是否是信息详情界面 -->
|
||||||
<ChatBox :active-item="activeItem" v-if="msgBoxShow && isChat && activeItem !== -1" />
|
<ChatBox :active-item="activeItem" v-if="msgBoxShow && isChat && activeItem !== -1" />
|
||||||
|
@ -4,13 +4,19 @@ import Mitt from '@/utils/Bus.ts'
|
|||||||
const { VITE_WEBSOCKET_URL } = import.meta.env
|
const { VITE_WEBSOCKET_URL } = import.meta.env
|
||||||
/** websocket连接对象 */
|
/** websocket连接对象 */
|
||||||
let ws: WebSocket
|
let ws: WebSocket
|
||||||
|
/** 尝试重新连接数 */
|
||||||
|
let reconnectAttempts = 0
|
||||||
|
/** 最大重连次数 */
|
||||||
|
const maxReconnectAttempts = 5
|
||||||
|
/** 重连间隔 */
|
||||||
|
const reconnectDelay = 3000
|
||||||
/** 初始化websocket连接 */
|
/** 初始化websocket连接 */
|
||||||
const initWebSocket = () => {
|
const initWebSocket = () => {
|
||||||
ws = new WebSocket(`${VITE_WEBSOCKET_URL}/`)
|
ws = new WebSocket(`${VITE_WEBSOCKET_URL}/`)
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
// 发送心跳
|
// 发送心跳
|
||||||
setInterval(() => {
|
setInterval(async () => {
|
||||||
sendToServer({
|
await sendToServer({
|
||||||
type: WsReqEnum.HEARTBEAT
|
type: WsReqEnum.HEARTBEAT
|
||||||
})
|
})
|
||||||
}, 1000 * 60)
|
}, 1000 * 60)
|
||||||
@ -31,10 +37,23 @@ const initWebSocket = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 尝试重新连接 */
|
||||||
|
const retryConnection = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (reconnectAttempts < maxReconnectAttempts) {
|
||||||
|
reconnectAttempts++
|
||||||
|
initWebSocket()
|
||||||
|
} else {
|
||||||
|
console.error('已达到最大重连次数,放弃重连')
|
||||||
|
}
|
||||||
|
}, reconnectDelay)
|
||||||
|
}
|
||||||
|
|
||||||
// websocket出错重连
|
// websocket出错重连
|
||||||
ws.onerror = () => {
|
ws.onerror = () => {
|
||||||
|
if (ws.readyState !== WebSocket.OPEN) return
|
||||||
// websocket出错重连
|
// websocket出错重连
|
||||||
initWebSocket()
|
retryConnection()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,8 +63,13 @@ const initWebSocket = () => {
|
|||||||
* @param data 传输的json数据对象
|
* @param data 传输的json数据对象
|
||||||
*/
|
*/
|
||||||
const sendToServer = (data: Record<string, any>) => {
|
const sendToServer = (data: Record<string, any>) => {
|
||||||
const json = JSON.stringify(data)
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
ws.send(json)
|
const json = JSON.stringify(data)
|
||||||
|
ws.send(json)
|
||||||
|
return Promise.resolve(true)
|
||||||
|
} else {
|
||||||
|
return Promise.reject('网络连接失败,请检查网络设置')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { initWebSocket, sendToServer }
|
export { initWebSocket, sendToServer }
|
||||||
|
@ -68,7 +68,7 @@ export const setting = defineStore(StoresEnum.SETTING, {
|
|||||||
},
|
},
|
||||||
/** 清空账号信息 */
|
/** 清空账号信息 */
|
||||||
clearAccount() {
|
clearAccount() {
|
||||||
this.login.accountInfo = { account: '', avatar: '', name: '', password: '', uid: '' }
|
this.login.accountInfo.password = ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
share: {
|
share: {
|
||||||
|
@ -95,6 +95,15 @@
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
transition: all 0.4s ease-in-out;
|
transition: all 0.4s ease-in-out;
|
||||||
}
|
}
|
||||||
|
/** 时间搓显示时候的动画 */
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.4s ease-in-out;
|
||||||
|
}
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
/**! 替换ait的字体颜色 */
|
/**! 替换ait的字体颜色 */
|
||||||
:deep(#aitSpan) {
|
:deep(#aitSpan) {
|
||||||
@apply text-inherit cursor-pointer;
|
@apply text-inherit cursor-pointer;
|
||||||
|
@ -59,6 +59,8 @@
|
|||||||
--reply-color: #909090;
|
--reply-color: #909090;
|
||||||
--reply-hover: #505050;
|
--reply-hover: #505050;
|
||||||
--bg-reply-img-count: #e3e3e3;
|
--bg-reply-img-count: #e3e3e3;
|
||||||
|
// 主页面面板分割线样式
|
||||||
|
--split-color: #f1f1f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-theme='dark'] {
|
html[data-theme='dark'] {
|
||||||
@ -121,6 +123,8 @@ html[data-theme='dark'] {
|
|||||||
--reply-color: #e3e3e3;
|
--reply-color: #e3e3e3;
|
||||||
--reply-hover: #b1b1b1;
|
--reply-hover: #b1b1b1;
|
||||||
--bg-reply-img-count: #505050;
|
--bg-reply-img-count: #505050;
|
||||||
|
// 主页面面板分割线样式
|
||||||
|
--split-color: #3b3b3b;
|
||||||
}
|
}
|
||||||
/**! end */
|
/**! end */
|
||||||
// 线性动画
|
// 线性动画
|
||||||
|
2
src/typings/components.d.ts
vendored
2
src/typings/components.d.ts
vendored
@ -50,10 +50,10 @@ declare module 'vue' {
|
|||||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||||
NSelect: typeof import('naive-ui')['NSelect']
|
NSelect: typeof import('naive-ui')['NSelect']
|
||||||
NSkeleton: typeof import('naive-ui')['NSkeleton']
|
NSkeleton: typeof import('naive-ui')['NSkeleton']
|
||||||
|
NSplit: typeof import('naive-ui')['NSplit']
|
||||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||||
NTabPane: typeof import('naive-ui')['NTabPane']
|
NTabPane: typeof import('naive-ui')['NTabPane']
|
||||||
NTabs: typeof import('naive-ui')['NTabs']
|
NTabs: typeof import('naive-ui')['NTabs']
|
||||||
NTag: typeof import('naive-ui')['NTag']
|
|
||||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||||
NVirtualList: typeof import('naive-ui')['NVirtualList']
|
NVirtualList: typeof import('naive-ui')['NVirtualList']
|
||||||
RenderMessage: typeof import('./../components/rightBox/renderMessage/index.vue')['default']
|
RenderMessage: typeof import('./../components/rightBox/renderMessage/index.vue')['default']
|
||||||
|
@ -18,28 +18,26 @@
|
|||||||
|
|
||||||
<!-- 用户框 多套一层div来移除默认的右键事件然后覆盖掉因为margin空隙而导致右键可用 -->
|
<!-- 用户框 多套一层div来移除默认的右键事件然后覆盖掉因为margin空隙而导致右键可用 -->
|
||||||
<div @contextmenu.stop="$event.preventDefault()">
|
<div @contextmenu.stop="$event.preventDefault()">
|
||||||
<div
|
<n-flex
|
||||||
v-slide
|
v-slide
|
||||||
|
:size="10"
|
||||||
@click="handleClick(item.key, 2)"
|
@click="handleClick(item.key, 2)"
|
||||||
:class="{ active: activeItem === item.key }"
|
:class="{ active: activeItem === item.key }"
|
||||||
class="user-box w-full h-75px mb-5px"
|
class="user-box w-full h-75px mb-5px"
|
||||||
v-for="item in friendsList"
|
v-for="item in friendsList"
|
||||||
:key="item.key">
|
:key="item.key">
|
||||||
<div class="flex items-center h-full pl-6px pr-8px gap-10px">
|
<n-flex v-slide align="center" :size="10" class="h-75px pl-6px pr-8px flex-1 truncate">
|
||||||
<n-avatar round bordered :color="'#fff'" :size="44" :src="item.avatar" />
|
<n-avatar round bordered :color="'#fff'" :size="44" :src="item.avatar" fallback-src="/logo.png" />
|
||||||
|
|
||||||
<div class="h-38px flex flex-1 flex-col justify-between">
|
<n-flex vertical justify="space-between" class="h-fit flex-1 truncate">
|
||||||
<div class="text-14px flex-y-center gap-4px">
|
<span class="text-14px leading-tight flex-1 truncate">{{ item.accountName }}</span>
|
||||||
{{ item.accountName }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text w-155px h-14px text-12px flex-y-center gap-4px">
|
<span class="text leading-tight text-12px flex-1 truncate">
|
||||||
<span class="text-12px">[⛅今日天气]</span>
|
[⛅今日天气] 说的很经典哈萨克的哈萨克看到贺卡上
|
||||||
<span class="flex-1 truncate">说的很经典哈萨克的哈萨克看到贺卡上</span>
|
</span>
|
||||||
</div>
|
</n-flex>
|
||||||
</div>
|
</n-flex>
|
||||||
</div>
|
</n-flex>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</n-collapse-item>
|
</n-collapse-item>
|
||||||
<n-collapse-item title="默认分组" name="3">
|
<n-collapse-item title="默认分组" name="3">
|
||||||
@ -57,19 +55,14 @@
|
|||||||
<div
|
<div
|
||||||
@click="handleClick(item.key, 1)"
|
@click="handleClick(item.key, 1)"
|
||||||
:class="{ active: activeItem === item.key }"
|
:class="{ active: activeItem === item.key }"
|
||||||
class="w-full h-75px mb-5px cursor-pointer"
|
class="w-full h-75px mb-5px"
|
||||||
v-for="item in groupChatList"
|
v-for="item in groupChatList"
|
||||||
:key="item.key">
|
:key="item.key">
|
||||||
<!-- 消息框,使用v-slide自定义指令来自动抉择右键菜单位置 -->
|
<n-flex v-slide align="center" :size="10" class="h-75px pl-6px pr-8px flex-1 truncate">
|
||||||
<div v-slide class="flex items-center h-full pl-6px pr-8px gap-10px">
|
<n-avatar round bordered :color="'#fff'" :size="44" :src="item.avatar" fallback-src="/logo.png" />
|
||||||
<n-avatar round bordered :color="'#fff'" :size="44" :src="item.avatar" />
|
|
||||||
|
|
||||||
<div class="h-38px flex flex-1 flex-col justify-center">
|
<span class="text-14px leading-tight flex-1 truncate">{{ item.accountName }}</span>
|
||||||
<div class="flex-between-center">
|
</n-flex>
|
||||||
<span class="text-14px">{{ item.accountName }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
|
@ -17,17 +17,17 @@
|
|||||||
v-for="item in MockList"
|
v-for="item in MockList"
|
||||||
:key="item.key">
|
:key="item.key">
|
||||||
<!-- 消息框,使用v-slide自定义指令来自动抉择右键菜单位置 -->
|
<!-- 消息框,使用v-slide自定义指令来自动抉择右键菜单位置 -->
|
||||||
<n-flex v-slide align="center" :size="10" class="h-75px pl-6px pr-8px">
|
<n-flex v-slide align="center" :size="10" class="h-75px pl-6px pr-8px flex-1">
|
||||||
<n-avatar round bordered :color="'#fff'" :size="44" :src="item.avatar" fallback-src="/logo.png" alt="" />
|
<n-avatar round bordered :color="'#fff'" :size="44" :src="item.avatar" fallback-src="/logo.png" />
|
||||||
|
|
||||||
<n-flex vertical justify="space-between" class="h-38px flex-1">
|
<n-flex vertical justify="space-between" class="h-fit flex-1 truncate">
|
||||||
<n-flex align="center" justify="space-between">
|
<n-flex :size="4" align="center" justify="space-between" class="flex-1 truncate">
|
||||||
<span class="text-14px">{{ item.accountName }}</span>
|
<span class="text-14px leading-tight flex-1 truncate">{{ item.accountName }}</span>
|
||||||
<span class="text text-10px">昨天</span>
|
<span class="text text-10px w-fit truncate text-right">星期一</span>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
|
||||||
<n-flex align="center" justify="space-between">
|
<n-flex align="center" justify="space-between">
|
||||||
<span class="text w-135px text-12px truncate"> 说的很经典哈萨克的哈萨克看到贺卡上 </span>
|
<span class="text flex-1 leading-tight text-12px truncate"> 说的很经典哈萨克的哈萨克看到贺卡上 </span>
|
||||||
|
|
||||||
<!-- 消息提示 -->
|
<!-- 消息提示 -->
|
||||||
<n-badge :value="msgTotal" :max="99" />
|
<n-badge :value="msgTotal" :max="99" />
|
||||||
|
@ -9,7 +9,10 @@
|
|||||||
<n-flex vertical :size="25" v-if="!isAutoLogin">
|
<n-flex vertical :size="25" v-if="!isAutoLogin">
|
||||||
<!-- 头像 -->
|
<!-- 头像 -->
|
||||||
<n-flex justify="center" class="w-full mt-35px">
|
<n-flex justify="center" class="w-full mt-35px">
|
||||||
<img class="w-80px h-80px rounded-50% bg-#fff border-(2px solid #fff)" :src="avatarRef || '/logo.png'" alt="" />
|
<img
|
||||||
|
class="w-80px h-80px rounded-50% bg-#fff border-(2px solid #fff)"
|
||||||
|
:src="info.avatar || '/logo.png'"
|
||||||
|
alt="" />
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
|
||||||
<!-- 登录菜单 -->
|
<!-- 登录菜单 -->
|
||||||
@ -19,7 +22,7 @@
|
|||||||
size="large"
|
size="large"
|
||||||
maxlength="16"
|
maxlength="16"
|
||||||
minlength="6"
|
minlength="6"
|
||||||
v-model:value="accountRef"
|
v-model:value="info.account"
|
||||||
type="text"
|
type="text"
|
||||||
:placeholder="accountPH"
|
:placeholder="accountPH"
|
||||||
@focus="accountPH = ''"
|
@focus="accountPH = ''"
|
||||||
@ -62,7 +65,7 @@
|
|||||||
maxlength="16"
|
maxlength="16"
|
||||||
minlength="6"
|
minlength="6"
|
||||||
size="large"
|
size="large"
|
||||||
v-model:value="passwordRef"
|
v-model:value="info.password"
|
||||||
type="password"
|
type="password"
|
||||||
:placeholder="passwordPH"
|
:placeholder="passwordPH"
|
||||||
@focus="passwordPH = ''"
|
@focus="passwordPH = ''"
|
||||||
@ -116,18 +119,19 @@
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
:disabled="loginDisabled"
|
:disabled="loginDisabled"
|
||||||
class="w-200px mt-12px mb-40px"
|
class="w-200px mt-12px mb-40px"
|
||||||
@click="loginWin"
|
@click="autoLogin"
|
||||||
color="#13987f">
|
color="#13987f">
|
||||||
{{ loginText }}
|
{{ loginText }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
|
||||||
<!-- 顶部操作栏 -->
|
<!-- 底部操作栏 -->
|
||||||
<n-flex justify="center" class="text-14px">
|
<n-flex justify="center" class="text-14px" id="bottomBar">
|
||||||
<div class="color-#13987f cursor-pointer" @click="router.push('/qrCode')">扫码登录</div>
|
<div class="color-#13987f cursor-pointer" @click="router.push('/qrCode')">扫码登录</div>
|
||||||
<div class="w-1px h-14px bg-#ccc"></div>
|
<div class="w-1px h-14px bg-#ccc"></div>
|
||||||
<n-popover trigger="click" :show-checkmark="false" :show-arrow="false">
|
<div v-if="isAutoLogin" class="color-#13987f cursor-pointer">移除账号</div>
|
||||||
|
<n-popover v-else trigger="click" :show-checkmark="false" :show-arrow="false">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<div class="color-#13987f cursor-pointer">更多选项</div>
|
<div class="color-#13987f cursor-pointer">更多选项</div>
|
||||||
</template>
|
</template>
|
||||||
@ -151,10 +155,16 @@ import { useLogin } from '@/hooks/useLogin.ts'
|
|||||||
|
|
||||||
const settingStore = setting()
|
const settingStore = setting()
|
||||||
const { login } = storeToRefs(settingStore)
|
const { login } = storeToRefs(settingStore)
|
||||||
const accountRef = ref()
|
/** 账号信息 */
|
||||||
const passwordRef = ref()
|
const info = ref({
|
||||||
const avatarRef = ref()
|
account: '',
|
||||||
const nameRef = ref()
|
password: '',
|
||||||
|
avatar: '',
|
||||||
|
name: ''
|
||||||
|
})
|
||||||
|
/** 是否中断登录 */
|
||||||
|
const interruptLogin = ref(false)
|
||||||
|
/** 协议 */
|
||||||
const protocol = ref()
|
const protocol = ref()
|
||||||
const loginDisabled = ref(false)
|
const loginDisabled = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@ -199,10 +209,13 @@ const loginText = ref('登录')
|
|||||||
const { createWebviewWindow } = useWindow()
|
const { createWebviewWindow } = useWindow()
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
loginDisabled.value = !(accountRef.value && passwordRef.value && protocol.value)
|
loginDisabled.value = !(info.value.account && info.value.password && protocol.value)
|
||||||
// 清空账号的时候设置默认头像
|
// 清空账号的时候设置默认头像
|
||||||
if (!accountRef.value) {
|
if (!info.value.account) {
|
||||||
avatarRef.value = '/logo.png'
|
info.value.avatar = '/logo.png'
|
||||||
|
}
|
||||||
|
if (interruptLogin.value) {
|
||||||
|
loginDisabled.value = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -217,9 +230,9 @@ const delAccount = (index: number) => {
|
|||||||
if (lengthBeforeDelete === 1 && accountOption.value.length === 0) {
|
if (lengthBeforeDelete === 1 && accountOption.value.length === 0) {
|
||||||
arrowStatus.value = false
|
arrowStatus.value = false
|
||||||
}
|
}
|
||||||
accountRef.value = null
|
info.value.account = ''
|
||||||
passwordRef.value = null
|
info.value.password = ''
|
||||||
avatarRef.value = '/logo.png'
|
info.value.avatar = '/logo.png'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -228,25 +241,26 @@ const delAccount = (index: number) => {
|
|||||||
* */
|
* */
|
||||||
const giveAccount = (item: STO.Setting['login']['accountInfo']) => {
|
const giveAccount = (item: STO.Setting['login']['accountInfo']) => {
|
||||||
const { account, password, avatar, name } = item
|
const { account, password, avatar, name } = item
|
||||||
accountRef.value = account
|
info.value.account = account || ''
|
||||||
passwordRef.value = password
|
info.value.password = password || ''
|
||||||
avatarRef.value = avatar
|
info.value.avatar = avatar
|
||||||
nameRef.value = name
|
info.value.name = name
|
||||||
arrowStatus.value = false
|
arrowStatus.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**登录后创建主页窗口*/
|
/**登录后创建主页窗口*/
|
||||||
const loginWin = () => {
|
const loginWin = () => {
|
||||||
|
if (interruptLogin.value) return
|
||||||
loading.value = true
|
loading.value = true
|
||||||
delay(async () => {
|
delay(async () => {
|
||||||
await createWebviewWindow('HuLa', 'home', 960, 720, 'login', false, true)
|
await createWebviewWindow('HuLa', 'home', 960, 720, 'login', false, true)
|
||||||
loading.value = false
|
loading.value = false
|
||||||
if (!login.value.autoLogin || login.value.accountInfo.password === '') {
|
if (!login.value.autoLogin || login.value.accountInfo.password === '') {
|
||||||
settingStore.setAccountInfo({
|
settingStore.setAccountInfo({
|
||||||
account: accountRef.value,
|
account: info.value.account,
|
||||||
password: passwordRef.value,
|
password: info.value.password,
|
||||||
avatar: avatarRef.value,
|
avatar: info.value.avatar,
|
||||||
name: nameRef.value,
|
name: info.value.name,
|
||||||
uid: '123456'
|
uid: '123456'
|
||||||
})
|
})
|
||||||
await setLoginState()
|
await setLoginState()
|
||||||
@ -254,12 +268,27 @@ const loginWin = () => {
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**监听是否点击了除了下拉框外的其他地方*/
|
/** 自动登录 */
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const autoLogin = () => {
|
||||||
|
interruptLogin.value = false
|
||||||
|
isAutoLogin.value = true
|
||||||
|
// TODO 检查用户网络是否连接 (nyh -> 2024-03-16 12:06:59)
|
||||||
|
loginText.value = '网络连接中'
|
||||||
|
delay(async () => {
|
||||||
|
loginWin()
|
||||||
|
loginText.value = '登录'
|
||||||
|
await setLoginState()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeMenu = (event: MouseEvent) => {
|
||||||
const target = event.target as Element
|
const target = event.target as Element
|
||||||
if (!target.matches('.account-box, .account-box *, .down')) {
|
if (!target.matches('.account-box, .account-box *, .down')) {
|
||||||
arrowStatus.value = false
|
arrowStatus.value = false
|
||||||
}
|
}
|
||||||
|
if (target.matches('#bottomBar *') && isAutoLogin.value) {
|
||||||
|
interruptLogin.value = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@ -267,20 +296,13 @@ onMounted(async () => {
|
|||||||
console.error('设置无状态图标失败:', error)
|
console.error('设置无状态图标失败:', error)
|
||||||
})
|
})
|
||||||
if (login.value.autoLogin && login.value.accountInfo.password !== '') {
|
if (login.value.autoLogin && login.value.accountInfo.password !== '') {
|
||||||
isAutoLogin.value = true
|
autoLogin()
|
||||||
// TODO 检查用户网络是否连接 (nyh -> 2024-03-16 12:06:59)
|
|
||||||
loginText.value = '网络连接中'
|
|
||||||
delay(async () => {
|
|
||||||
loginWin()
|
|
||||||
loginText.value = '登录'
|
|
||||||
await setLoginState()
|
|
||||||
}, 1000)
|
|
||||||
}
|
}
|
||||||
window.addEventListener('click', handleClickOutside, true)
|
window.addEventListener('click', closeMenu, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('click', handleClickOutside, true)
|
window.removeEventListener('click', closeMenu, true)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -14,18 +14,19 @@
|
|||||||
v-else
|
v-else
|
||||||
:size="180"
|
:size="180"
|
||||||
class="rounded-12px relative"
|
class="rounded-12px relative"
|
||||||
:class="{ blur: scanSuccess }"
|
:class="{ blur: scanStatus.show }"
|
||||||
:value="QRCode"
|
:value="QRCode"
|
||||||
icon-src="/logo.png"
|
icon-src="/logo.png"
|
||||||
error-correction-level="H" />
|
error-correction-level="H" />
|
||||||
|
<!-- 二维码状态 -->
|
||||||
<n-flex
|
<n-flex
|
||||||
v-if="scanSuccess"
|
v-if="scanStatus.show"
|
||||||
vertical
|
vertical
|
||||||
:size="12"
|
:size="12"
|
||||||
align="center"
|
align="center"
|
||||||
class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
||||||
<svg class="size-42px"><use href="#success"></use></svg>
|
<svg class="size-42px"><use :href="`#${scanStatus.icon}`"></use></svg>
|
||||||
<span class="text-(16px #e3e3e3)">扫码成功</span>
|
<span class="text-(16px #e3e3e3)">{{ scanStatus.text }}</span>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
|
||||||
@ -33,7 +34,7 @@
|
|||||||
|
|
||||||
<!-- 顶部操作栏 -->
|
<!-- 顶部操作栏 -->
|
||||||
<n-flex justify="center" class="text-14px mt-48px">
|
<n-flex justify="center" class="text-14px mt-48px">
|
||||||
<div class="color-#13987f cursor-pointer" @click="toLogin">账密登录</div>
|
<div class="color-#13987f cursor-pointer" @click="router.push('/login')">账密登录</div>
|
||||||
<div class="w-1px h-14px bg-#ccc"></div>
|
<div class="w-1px h-14px bg-#ccc"></div>
|
||||||
<div class="color-#13987f cursor-pointer">注册账号</div>
|
<div class="color-#13987f cursor-pointer">注册账号</div>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
@ -56,34 +57,60 @@ const { createWebviewWindow } = useWindow()
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const loadText = ref('加载中...')
|
const loadText = ref('加载中...')
|
||||||
const QRCode = ref()
|
const QRCode = ref()
|
||||||
const scanSuccess = ref(false)
|
const scanStatus = ref<{
|
||||||
|
status: 'error' | 'success'
|
||||||
|
icon: 'cloudError' | 'success'
|
||||||
|
text: string
|
||||||
|
show: boolean
|
||||||
|
}>({ status: 'success', icon: 'success', text: '扫码成功', show: false })
|
||||||
|
|
||||||
const toLogin = () => {
|
/** 处理二维码登录 */
|
||||||
router.push('/login')
|
const handleQRCodeLogin = (e: any) => {
|
||||||
|
QRCode.value = e.data.loginUrl
|
||||||
|
loading.value = false
|
||||||
|
loadText.value = '请使用微信扫码登录'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 处理登录成功 */
|
||||||
|
const handleLoginSuccess = async (e: any) => {
|
||||||
|
scanStatus.value.show = true
|
||||||
|
loadText.value = '登录中...'
|
||||||
|
delay(async () => {
|
||||||
|
await createWebviewWindow('HuLa', 'home', 960, 720, 'login', false, true)
|
||||||
|
settingStore.setAccountInfo({
|
||||||
|
avatar: e.data.avatar,
|
||||||
|
name: e.data.name,
|
||||||
|
uid: e.data.uid
|
||||||
|
})
|
||||||
|
await setLoginState()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理失败场景 */
|
||||||
|
const handleError = (e: any) => {
|
||||||
|
loading.value = false
|
||||||
|
scanStatus.value = {
|
||||||
|
status: 'error',
|
||||||
|
icon: 'cloudError',
|
||||||
|
text: e,
|
||||||
|
show: true
|
||||||
|
}
|
||||||
|
loadText.value = '请稍后再试'
|
||||||
|
}
|
||||||
|
|
||||||
// TODO 做一个二维码过期时间重新刷新二维码的功能 (nyh -> 2024-01-27 00:37:18)
|
// TODO 做一个二维码过期时间重新刷新二维码的功能 (nyh -> 2024-01-27 00:37:18)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initWebSocket()
|
initWebSocket()
|
||||||
Mitt.on(WsResEnum.QRCODE_LOGIN, (e: any) => {
|
Mitt.on(WsResEnum.QRCODE_LOGIN, (e: any) => {
|
||||||
QRCode.value = e.data.loginUrl
|
handleQRCodeLogin(e)
|
||||||
loading.value = false
|
|
||||||
loadText.value = '请使用微信扫码登录'
|
|
||||||
})
|
})
|
||||||
Mitt.on(WsResEnum.LOGIN_SUCCESS, (e: any) => {
|
Mitt.on(WsResEnum.LOGIN_SUCCESS, (e: any) => {
|
||||||
scanSuccess.value = true
|
handleLoginSuccess(e)
|
||||||
loadText.value = '登录中...'
|
|
||||||
delay(async () => {
|
|
||||||
await createWebviewWindow('HuLa', 'home', 960, 720, 'login', false, true)
|
|
||||||
settingStore.setAccountInfo({
|
|
||||||
avatar: e.data.avatar,
|
|
||||||
name: e.data.name,
|
|
||||||
uid: e.data.uid
|
|
||||||
})
|
|
||||||
await setLoginState()
|
|
||||||
}, 1000)
|
|
||||||
})
|
})
|
||||||
delay(() => {
|
delay(() => {
|
||||||
sendToServer({ type: WsReqEnum.LOGIN })
|
sendToServer({ type: WsReqEnum.LOGIN }).catch((e) => {
|
||||||
|
handleError(e)
|
||||||
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user