10
.env.production
Normal file
@ -0,0 +1,10 @@
|
||||
# 后端服务地址
|
||||
VITE_SERVICE_URL="http://127.0.0.1:9190"
|
||||
# websocket服务地址
|
||||
VITE_WEBSOCKET_URL="ws://127.0.0.1:8090"
|
||||
# 项目标题
|
||||
VITE_APP_TITLE="HuLa—IM"
|
||||
# 项目名称
|
||||
VITE_APP_NAME="HuLa-IM-Tauri"
|
||||
# gitee token
|
||||
VITE_GITEE_TOKEN="0312a213a6b6882beb96f487e75661a6"
|
1
.gitignore
vendored
@ -24,4 +24,3 @@ dist-ssr
|
||||
*.sw?
|
||||
|
||||
src-tauri/target
|
||||
.env.production
|
||||
|
30
CHANGELOG.md
@ -1,3 +1,33 @@
|
||||
# [2.1.0](https://github.com/nongyehong/HuLa-IM-Tauri/compare/v2.0.0...v2.1.0) (2024-09-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **component:** :bug: 修复windows上的样式问题 ([bb6a9d4](https://github.com/nongyehong/HuLa-IM-Tauri/commit/bb6a9d440db4777989d9a922a5135350e2dbf894))
|
||||
* **component:** :bug: 修复系统托盘功能和一些样式问题 ([18277ef](https://github.com/nongyehong/HuLa-IM-Tauri/commit/18277ef0f1ce286b77b91dbc8c6ea8a628eba7d3))
|
||||
* **style:** :bug: 统一修复svg点击时有轮廓问题 ([ce68fa1](https://github.com/nongyehong/HuLa-IM-Tauri/commit/ce68fa134368b34802d5b101a1f98a2493f7120b))
|
||||
* **system:** :bug: 修复mac端右键菜单透明度问题 ([39d795f](https://github.com/nongyehong/HuLa-IM-Tauri/commit/39d795ff655afd699340d3021a0b471c3060b11c))
|
||||
* **system:** :bug: 修复win下窗口高度不一致问题 ([30bb3de](https://github.com/nongyehong/HuLa-IM-Tauri/commit/30bb3de5d10ffea949c32b505f6501b3f7d0f573))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* chatbot删除全部会话功能和右键菜单重命名 ([3426c5f](https://github.com/nongyehong/HuLa-IM-Tauri/commit/3426c5f24fafe66c3543ee8f4172d2dae05740e4))
|
||||
* **component:** :sparkles: 新增插件功能(Bate) ([392b7c9](https://github.com/nongyehong/HuLa-IM-Tauri/commit/392b7c99bd38fd2f298e7732499dc7510e4d286a))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* :zap: 优化mac标签栏 ([a7c587d](https://github.com/nongyehong/HuLa-IM-Tauri/commit/a7c587d74b771e32e3b61eaef2ba5c902c0e4f6f))
|
||||
* **component:** :zap: 升级插件版本内容及其样式 ([8d65ca1](https://github.com/nongyehong/HuLa-IM-Tauri/commit/8d65ca198fa8a01252e0dc7f07f4bd6c796dbfe1))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **system:** 新增mac端弹出框的关闭按钮
|
||||
|
||||
|
||||
|
||||
# [2.0.0](https://github.com/nongyehong/HuLa-IM-Tauri/compare/v1.6.0...v2.0.0) (2024-08-15)
|
||||
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
<title>HuLa</title>
|
||||
|
||||
<!--引入iconpark图标库-->
|
||||
<script defer src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_30895_118.ee039811e5b75b41f6c09e4bb8e9edcd.js"></script>
|
||||
<script defer src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_30895_122.6a2196c3637253b659223f2334a8756e.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
24
package.json
@ -2,7 +2,7 @@
|
||||
"name": "hula-im-tauri",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "v2.0.0",
|
||||
"version": "2.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18.12.0",
|
||||
@ -53,8 +53,8 @@
|
||||
"pinia": "^2.2.1",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"pinia-shared-state": "^0.5.1",
|
||||
"vue": "^3.4.37",
|
||||
"vue-draggable-plus": "^0.4.1",
|
||||
"vue": "^3.5.4",
|
||||
"vue-draggable-plus": "^0.5.3",
|
||||
"vue-router": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -67,11 +67,11 @@
|
||||
"@types/node": "^20.14.14",
|
||||
"@typescript-eslint/eslint-plugin": "7.1.0",
|
||||
"@typescript-eslint/parser": "^7.15.0",
|
||||
"@unocss/preset-uno": "^0.61.9",
|
||||
"@unocss/reset": "^0.61.9",
|
||||
"@unocss/transformer-directives": "^0.61.9",
|
||||
"@unocss/transformer-variant-group": "^0.61.9",
|
||||
"@unocss/vite": "^0.61.9",
|
||||
"@unocss/preset-uno": "^0.62.3",
|
||||
"@unocss/reset": "^0.62.3",
|
||||
"@unocss/transformer-directives": "^0.62.3",
|
||||
"@unocss/transformer-variant-group": "^0.62.3",
|
||||
"@unocss/vite": "^0.62.3",
|
||||
"@vitejs/plugin-vue": "^5.1.2",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.0",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
@ -91,10 +91,10 @@
|
||||
"sass": "1.77.6",
|
||||
"sass-loader": "^14.2.1",
|
||||
"typescript": "^5.5.3",
|
||||
"unplugin-auto-import": "^0.17.8",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "5.4.0",
|
||||
"vue-tsc": "^2.0.29"
|
||||
"unplugin-auto-import": "^0.18.2",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vite": "5.4.3",
|
||||
"vue-tsc": "^2.1.4"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
|
1524
pnpm-lock.yaml
BIN
public/emoji/alien-monster.webp
Normal file
After Width: | Height: | Size: 145 KiB |
BIN
public/emoji/bug.webp
Normal file
After Width: | Height: | Size: 171 KiB |
BIN
public/emoji/comet.webp
Normal file
After Width: | Height: | Size: 168 KiB |
BIN
public/emoji/fire.webp
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
public/emoji/gear.webp
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
public/emoji/hammer-and-wrench.webp
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
public/emoji/lipstick.webp
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
public/emoji/memo.webp
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/emoji/package.webp
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
public/emoji/party-popper.webp
Normal file
After Width: | Height: | Size: 198 KiB |
BIN
public/emoji/recycling-symbol.webp
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
public/emoji/right-arrow-curving-left.webp
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
public/emoji/robot.webp
Normal file
After Width: | Height: | Size: 141 KiB |
BIN
public/emoji/rocket.webp
Normal file
After Width: | Height: | Size: 216 KiB |
BIN
public/emoji/test-tube.webp
Normal file
After Width: | Height: | Size: 1.7 KiB |
2
src-tauri/Cargo.lock
generated
@ -2018,7 +2018,7 @@ checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
|
||||
|
||||
[[package]]
|
||||
name = "hula"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"lazy_static",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "hula"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
description = "hula"
|
||||
authors = ["nongyehong"]
|
||||
license = ""
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"productName": "HuLa",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"identifier": "com.tauri.build",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
|
BIN
src/assets/img/win.png
Normal file
After Width: | Height: | Size: 72 KiB |
@ -76,8 +76,6 @@ const commonTheme: GlobalThemeOverrides = {
|
||||
},
|
||||
Tabs: {
|
||||
tabTextColorSegment: '#707070',
|
||||
tabTextColorActiveSegment: '#13987f',
|
||||
tabTextColorHoverSegment: '#13987f',
|
||||
tabPaddingMediumSegment: '4px'
|
||||
},
|
||||
Popover: {
|
||||
|
@ -120,10 +120,9 @@ import { useChatStore } from '@/stores/chat.ts'
|
||||
const { handleMsgClick } = useMessage()
|
||||
const globalStore = useGlobalStore()
|
||||
const chatStore = useChatStore()
|
||||
const props = defineProps<{
|
||||
const { content } = defineProps<{
|
||||
content: any
|
||||
}>()
|
||||
const { content } = toRefs(props)
|
||||
const item = computed(() => {
|
||||
return useUserInfo(content.value.uid).value
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<!-- 输入框 -->
|
||||
<ContextMenu class="w-full h-110px" @select="$event.click()" :menu="menuList">
|
||||
<n-scrollbar style="max-height: 110px">
|
||||
<n-scrollbar style="max-height: 100px">
|
||||
<div
|
||||
id="message-input"
|
||||
ref="messageInputDom"
|
||||
@ -12,6 +12,7 @@
|
||||
@keydown.exact.enter="inputKeyDown"
|
||||
@keydown.exact.meta.enter="inputKeyDown"
|
||||
@keydown.exact.ctrl.enter="inputKeyDown"></div>
|
||||
<!-- TODO 这里的在win上会有延迟显示的bug (nyh -> 2024-09-01 23:40:44) -->
|
||||
<span
|
||||
v-if="isEntering"
|
||||
@click.stop="messageInputDom.focus()"
|
||||
@ -55,17 +56,8 @@
|
||||
</n-virtual-list>
|
||||
</div>
|
||||
|
||||
<!-- 发送按钮 -->
|
||||
<!-- 发送按钮 TODO 建议不要放在外面会影响视觉效果,可以放在发送按钮里面做提示,发送按钮需要修改一下大小 (nyh -> 2024-09-01 23:41:34) -->
|
||||
<n-flex align="center" justify="space-between" :size="12">
|
||||
<n-flex align="center" :size="4" class="text-(12px #777) tracking-1">
|
||||
<svg class="size-12px"><use href="#Enter"></use></svg>
|
||||
发送/
|
||||
<n-flex align="center" :size="0">
|
||||
{{ type() === 'macos' ? MacOsKeyEnum['⌘'] : WinKeyEnum.ctrl }}
|
||||
<svg class="size-12px"><use href="#Enter"></use></svg>
|
||||
</n-flex>
|
||||
换行
|
||||
</n-flex>
|
||||
<n-config-provider :theme="lightTheme">
|
||||
<n-button-group size="small" class="pr-20px">
|
||||
<n-button
|
||||
@ -90,6 +82,21 @@
|
||||
<svg @click="arrow = false" v-else class="w-22px h-22px mt-2px outline-none">
|
||||
<use href="#up"></use>
|
||||
</svg>
|
||||
<template #action>
|
||||
<n-flex
|
||||
justify="center"
|
||||
align="center"
|
||||
:size="4"
|
||||
class="text-(12px #777) cursor-default tracking-1 select-none">
|
||||
<svg class="size-12px"><use href="#Enter"></use></svg>
|
||||
发送/
|
||||
<n-flex align="center" :size="0">
|
||||
{{ type() === 'macos' ? MacOsKeyEnum['⌘'] : WinKeyEnum.ctrl }}
|
||||
<svg class="size-12px"><use href="#Enter"></use></svg>
|
||||
</n-flex>
|
||||
换行
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-popselect>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
|
@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<!-- 底部栏 -->
|
||||
<main
|
||||
style="box-shadow: var(--shadow-enabled) -4px 4px var(--box-shadow-color)"
|
||||
class="size-full relative z-10 bg-[--right-bg-color] border-t-(1px solid [--line-color]) color-[--icon-color]">
|
||||
<main class="size-full relative z-10 bg-[--right-bg-color] border-t-(1px solid [--line-color]) color-[--icon-color]">
|
||||
<!-- 输入框顶部选项栏 -->
|
||||
<n-flex align="center" justify="space-between" class="p-[10px_22px_5px] select-none">
|
||||
<n-flex align="center" :size="0" class="input-options">
|
||||
@ -159,7 +157,6 @@ onMounted(() => {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
&:hover {
|
||||
color: #13987f;
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<!-- 顶部操作栏和显示用户名 -->
|
||||
<main
|
||||
style="box-shadow: var(--shadow-enabled) 4px 4px var(--box-shadow-color)"
|
||||
class="relative z-30 flex-y-center border-b-(1px solid [--line-color]) justify-between p-[6px_20px_12px] select-none">
|
||||
<n-flex align="center">
|
||||
<span class="color-[--text-color]">{{ activeItem.name }}</span>
|
||||
@ -62,9 +61,9 @@
|
||||
</nav>
|
||||
|
||||
<!-- 侧边选项栏 -->
|
||||
<transition name="sidebar">
|
||||
<Transition name="sidebar">
|
||||
<div v-if="sidebarShow" style="border: 1px solid rgba(90, 90, 90, 0.1)" class="sidebar">
|
||||
<div class="setting-item flex-col-y-center">
|
||||
<div class="box-item flex-col-y-center">
|
||||
<div class="flex-between-center">
|
||||
<p>设为置顶</p>
|
||||
<n-switch size="small" />
|
||||
@ -76,24 +75,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="box-item">
|
||||
<div class="flex-between-center">
|
||||
<p>屏蔽此人</p>
|
||||
<n-switch size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item cursor-pointer" @click="handleDelete('chat-history')">
|
||||
<div class="box-item cursor-pointer" @click="handleDelete('chat-history')">
|
||||
<p>删除聊天记录</p>
|
||||
</div>
|
||||
|
||||
<div class="setting-item flex-x-center cursor-pointer" @click="handleDelete('friends')">
|
||||
<div class="box-item flex-x-center cursor-pointer" @click="handleDelete('friends')">
|
||||
<p class="color-#d03553">删除好友</p>
|
||||
</div>
|
||||
|
||||
<p class="m-[0_auto] text-(12px #13987f) mt-20px cursor-pointer">被骚扰了? 举报该用户</p>
|
||||
</div>
|
||||
</transition>
|
||||
</Transition>
|
||||
</main>
|
||||
|
||||
<!-- 弹出框 -->
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<!-- 头部 -->
|
||||
<ChatHeader :active-item="activeItemRef as any" />
|
||||
<n-flex :size="0" class="h-full">
|
||||
<n-flex :class="{ 'shadow-inner': page.shadow }" :size="0" class="h-full">
|
||||
<n-flex vertical :size="0" class="flex-1 relative">
|
||||
<!-- 中间聊天框内容 -->
|
||||
<ChatMain :active-item="activeItemRef as any" />
|
||||
@ -15,7 +15,11 @@
|
||||
import { MockItem } from '@/services/types.ts'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { setting } from '@/stores/setting.ts'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const settingStore = setting()
|
||||
const { page } = storeToRefs(settingStore)
|
||||
const appWindow = WebviewWindow.getCurrent()
|
||||
const { activeItem } = defineProps<{
|
||||
activeItem?: MockItem
|
||||
|
@ -57,8 +57,7 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getAllTypeEmojis } from '@/utils/Emoji.ts'
|
||||
import { history } from '@/stores/history.ts'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useHistoryStore } from '@/stores/history.ts'
|
||||
|
||||
type EmojiType = {
|
||||
expressionEmojis: EmojiItem
|
||||
@ -70,8 +69,7 @@ interface EmojiItem {
|
||||
value: any[]
|
||||
}
|
||||
|
||||
const historyStore = history()
|
||||
const { emoji } = storeToRefs(historyStore)
|
||||
const { emoji, setEmoji } = useHistoryStore()
|
||||
const activeIndex = ref(0)
|
||||
|
||||
const emit = defineEmits(['emojiHandle'])
|
||||
@ -102,7 +100,7 @@ const emojiRef = reactive<{
|
||||
allEmoji: EmojiType
|
||||
}>({
|
||||
chooseItem: '',
|
||||
historyList: emoji.value,
|
||||
historyList: emoji,
|
||||
allEmoji: emojiObj.value
|
||||
})
|
||||
|
||||
@ -121,7 +119,7 @@ const chooseEmoji = (item: string) => {
|
||||
if (emojiRef.historyList.length > 18) {
|
||||
emojiRef.historyList.splice(18) // 保留前18个元素
|
||||
}
|
||||
historyStore.setEmoji([...emojiRef.historyList])
|
||||
setEmoji([...emojiRef.historyList])
|
||||
emit('emojiHandle', item)
|
||||
return item
|
||||
}
|
||||
|
@ -46,7 +46,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<!-- 是否退到托盘提示框 -->
|
||||
<n-modal v-if="!tips.notTips" v-model:show="tipsRef.show" class="rounded-8px">
|
||||
<n-modal v-if="!tips.notTips && osType === 'windows'" v-model:show="tipsRef.show" class="rounded-8px">
|
||||
<div class="bg-[--bg-popover] w-290px h-full p-6px box-border flex flex-col">
|
||||
<svg @click="tipsRef.show = false" class="size-12px ml-a cursor-pointer select-none">
|
||||
<use href="#close"></use>
|
||||
@ -80,7 +80,7 @@
|
||||
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import Mitt from '@/utils/Bus'
|
||||
import { useWindow } from '@/hooks/useWindow.ts'
|
||||
import { alwaysOnTop } from '@/stores/alwaysOnTop.ts'
|
||||
import { useAlwaysOnTopStore } from '@/stores/alwaysOnTop.ts'
|
||||
import { setting } from '@/stores/setting.ts'
|
||||
import { emit, listen } from '@tauri-apps/api/event'
|
||||
import { CloseBxEnum, EventEnum, MittEnum } from '@/enums'
|
||||
@ -90,30 +90,23 @@ import { exit } from '@tauri-apps/plugin-process'
|
||||
import { type } from '@tauri-apps/plugin-os'
|
||||
|
||||
const appWindow = WebviewWindow.getCurrent()
|
||||
/**
|
||||
* 新版defineProps可以直接结构 { minW, maxW, closeW } 如果需要使用默认值withDefaults的时候使用新版解构方式会报错
|
||||
* @description W结尾为窗口图标是否显示 shrink表示是否收缩图标 shrinkStatus表示是否收缩状态
|
||||
* */
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
minW?: boolean
|
||||
maxW?: boolean
|
||||
closeW?: boolean
|
||||
shrink?: boolean
|
||||
topWinLabel?: string
|
||||
currentLabel?: string
|
||||
shrinkStatus?: boolean
|
||||
}>(),
|
||||
{
|
||||
minW: true,
|
||||
maxW: true,
|
||||
closeW: true,
|
||||
shrink: true,
|
||||
shrinkStatus: true
|
||||
}
|
||||
)
|
||||
const { minW, maxW, closeW, topWinLabel, shrinkStatus } = toRefs(props)
|
||||
const alwaysOnTopStore = alwaysOnTop()
|
||||
const {
|
||||
topWinLabel,
|
||||
minW = true,
|
||||
maxW = true,
|
||||
closeW = true,
|
||||
shrink = true,
|
||||
shrinkStatus = true
|
||||
} = defineProps<{
|
||||
minW?: boolean
|
||||
maxW?: boolean
|
||||
closeW?: boolean
|
||||
shrink?: boolean
|
||||
topWinLabel?: string
|
||||
currentLabel?: string
|
||||
shrinkStatus?: boolean
|
||||
}>()
|
||||
const { getWindowTop, setWindowTop } = useAlwaysOnTopStore()
|
||||
const settingStore = setting()
|
||||
const { tips, escClose } = storeToRefs(settingStore)
|
||||
const { resizeWindow } = useWindow()
|
||||
@ -126,8 +119,8 @@ const tipsRef = reactive({
|
||||
const windowMaximized = ref(false)
|
||||
// 窗口是否置顶状态
|
||||
const alwaysOnTopStatus = computed(() => {
|
||||
if (topWinLabel.value === void 0) return false
|
||||
return alwaysOnTopStore.getWindowTop(topWinLabel.value)
|
||||
if (topWinLabel === void 0) return false
|
||||
return getWindowTop(topWinLabel)
|
||||
})
|
||||
/** 判断当前是windows还是mac系统 */
|
||||
const osType = ref()
|
||||
@ -140,7 +133,6 @@ watchEffect(() => {
|
||||
listen(EventEnum.LOGOUT, async () => {
|
||||
/** 退出账号前把窗口全部关闭 */
|
||||
if (appWindow.label !== 'login') {
|
||||
console.log('logout')
|
||||
await appWindow.close()
|
||||
}
|
||||
})
|
||||
@ -148,7 +140,7 @@ watchEffect(() => {
|
||||
await exit(0)
|
||||
})
|
||||
|
||||
if (escClose.value) {
|
||||
if (escClose.value && type() === 'windows') {
|
||||
window.addEventListener('keydown', (e) => isEsc(e))
|
||||
} else {
|
||||
window.removeEventListener('keydown', (e) => isEsc(e))
|
||||
@ -167,8 +159,8 @@ const restoreWindow = async () => {
|
||||
/** 收缩窗口 */
|
||||
const shrinkWindow = async () => {
|
||||
/**使用mitt给兄弟组件更新*/
|
||||
Mitt.emit(MittEnum.SHRINK_WINDOW, shrinkStatus.value)
|
||||
if (shrinkStatus.value) {
|
||||
Mitt.emit(MittEnum.SHRINK_WINDOW, shrinkStatus)
|
||||
if (shrinkStatus) {
|
||||
await resizeWindow('home', 310, 700)
|
||||
} else {
|
||||
await resizeWindow('home', 960, 700)
|
||||
@ -177,9 +169,9 @@ const shrinkWindow = async () => {
|
||||
|
||||
/** 设置窗口置顶 */
|
||||
const handleAlwaysOnTop = async () => {
|
||||
if (topWinLabel.value !== void 0) {
|
||||
if (topWinLabel !== void 0) {
|
||||
const isTop = !alwaysOnTopStatus.value
|
||||
alwaysOnTopStore.setWindowTop(topWinLabel.value, isTop)
|
||||
setWindowTop(topWinLabel, isTop)
|
||||
await appWindow.setAlwaysOnTop(isTop)
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,11 @@ export enum StoresEnum {
|
||||
/** 历史内容 */
|
||||
HISTORY = 'history',
|
||||
/** 聊天列表 */
|
||||
CHAT_LIST = 'chatList'
|
||||
CHAT_LIST = 'chatList',
|
||||
/** 插件列表 */
|
||||
PLUGINS = 'plugins',
|
||||
/** 侧边栏头部菜单栏 */
|
||||
MENUTOP = 'menuTop'
|
||||
}
|
||||
|
||||
/**
|
||||
@ -257,6 +261,8 @@ export enum WinKeyEnum {
|
||||
|
||||
/** 插件状态 */
|
||||
export enum PluginEnum {
|
||||
/** 已内置 */
|
||||
BUILTIN,
|
||||
/** 已安装 */
|
||||
INSTALLED,
|
||||
/** 下载中 */
|
||||
|
@ -259,7 +259,6 @@ export const useMsgInput = (messageInputDom: Ref) => {
|
||||
|
||||
/** input的keydown事件 */
|
||||
const inputKeyDown = (e: KeyboardEvent) => {
|
||||
console.log(chat.value.sendKey)
|
||||
const isWindows = type() === 'windows'
|
||||
const isEnterKey = e.key === 'Enter'
|
||||
const isCtrlOrMetaKey = isWindows ? e.ctrlKey : e.metaKey
|
||||
|
@ -42,7 +42,11 @@
|
||||
</n-input>
|
||||
|
||||
<!-- 添加面板 -->
|
||||
<n-popover v-model:show="addPanels.show" style="padding: 0" :show-arrow="false" trigger="click">
|
||||
<n-popover
|
||||
v-model:show="addPanels.show"
|
||||
style="padding: 0; background: transparent"
|
||||
:show-arrow="false"
|
||||
trigger="click">
|
||||
<template #trigger>
|
||||
<n-button size="small" secondary style="padding: 0 5px">
|
||||
<template #icon>
|
||||
|
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="flex-1 mt-20px flex-col-x-center justify-between" data-tauri-drag-region>
|
||||
<div ref="actionList" class="flex-1 mt-20px flex-col-x-center justify-between" data-tauri-drag-region>
|
||||
<!-- 上部分操作栏 -->
|
||||
<header class="flex-col-x-center gap-10px color-[--icon-color]">
|
||||
<header ref="header" class="flex-col-x-center gap-10px color-[--icon-color]">
|
||||
<div
|
||||
v-for="(item, index) in itemsTop"
|
||||
v-for="(item, index) in menuTop"
|
||||
:key="index"
|
||||
:class="[
|
||||
{ active: activeUrl === item.url && item.url !== 'dynamic' },
|
||||
@ -16,7 +16,7 @@
|
||||
<n-badge :max="99" :value="item.badge">
|
||||
<svg class="size-22px" @click="tipShow = false">
|
||||
<use
|
||||
:href="`#${activeUrl === item.url || openWindowsList.has(item.url) ? item.iconAction : item.icon}`"></use>
|
||||
:href="`#${activeUrl === item.url || openWindowsList.has(item.url) ? item.iconAction || item.icon : item.icon}`"></use>
|
||||
</svg>
|
||||
</n-badge>
|
||||
</template>
|
||||
@ -25,8 +25,8 @@
|
||||
<!-- 该选项有提示时展示 -->
|
||||
<n-popover style="padding: 12px" v-else-if="item.tip" trigger="manual" v-model:show="tipShow" placement="right">
|
||||
<template #trigger>
|
||||
<n-badge :max="99" :value="item.badge" dot :show="dotShow">
|
||||
<svg class="size-22px" @click="handleTipShow">
|
||||
<n-badge :max="99" :value="item.badge" dot :show="item.dot">
|
||||
<svg class="size-22px" @click="handleTipShow(item)">
|
||||
<use
|
||||
:href="`#${activeUrl === item.url || openWindowsList.has(item.url) ? item.iconAction : item.icon}`"></use>
|
||||
</svg>
|
||||
@ -34,7 +34,7 @@
|
||||
</template>
|
||||
<n-flex align="center" justify="space-between">
|
||||
<p class="select-none">{{ item.tip }}</p>
|
||||
<svg @click="handleTipShow" class="size-12px cursor-pointer"><use href="#close"></use></svg>
|
||||
<svg @click="handleTipShow(item)" class="size-12px cursor-pointer"><use href="#close"></use></svg>
|
||||
</n-flex>
|
||||
</n-popover>
|
||||
<!-- 该选项无提示时展示 -->
|
||||
@ -47,12 +47,24 @@
|
||||
</div>
|
||||
|
||||
<!-- (独立)菜单选项 -->
|
||||
<n-popover style="padding: 8px; margin-left: 4px" :show-arrow="false" trigger="hover" placement="right">
|
||||
<n-popover
|
||||
style="padding: 8px; margin-left: 4px; background: var(--bg-setting-item)"
|
||||
:show-arrow="false"
|
||||
trigger="hover"
|
||||
placement="right">
|
||||
<template #trigger>
|
||||
<svg class="size-22px top-action">
|
||||
<use href="#menu"></use>
|
||||
</svg>
|
||||
</template>
|
||||
<div v-if="excessItems.length">
|
||||
<div
|
||||
v-for="(item, index) in excessItems as any"
|
||||
:key="'excess-' + index"
|
||||
class="p-[6px_10px] rounded-4px cursor-pointer hover:bg-[--setting-item-line]">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</div>
|
||||
<n-flex
|
||||
@click="menuShow = true"
|
||||
class="p-[6px_10px] rounded-4px cursor-pointer hover:bg-[--setting-item-line]"
|
||||
@ -111,7 +123,11 @@
|
||||
</div>
|
||||
|
||||
<!-- 更多选项面板 -->
|
||||
<n-popover v-model:show="settingShow" style="padding: 0" :show-arrow="false" trigger="click">
|
||||
<n-popover
|
||||
v-model:show="settingShow"
|
||||
style="padding: 0; background: transparent"
|
||||
:show-arrow="false"
|
||||
trigger="click">
|
||||
<template #trigger>
|
||||
<svg
|
||||
:class="{ 'color-#13987f': settingShow }"
|
||||
@ -134,30 +150,65 @@
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<DefinePlugins :show="menuShow" @close="(e) => (menuShow = e)" />
|
||||
<DefinePlugins v-model="menuShow" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { itemsBottom, itemsTop, moreList } from '../config.tsx'
|
||||
import { itemsBottom, moreList } from '../config.tsx'
|
||||
import { leftHook } from '../hook.ts'
|
||||
import DefinePlugins from './DefinePlugins.vue'
|
||||
import DefinePlugins from './definePlugins/index.vue'
|
||||
import { useMenuTopStore } from '@/stores/menuTop.ts'
|
||||
import { PluginEnum } from '@/enums'
|
||||
|
||||
const { menuTop } = useMenuTopStore()
|
||||
// const headerRef = useTemplateRef('header')
|
||||
// const actionListRef = useTemplateRef('actionList')
|
||||
const excessItems = ref([]) // 用于存储超出的内容
|
||||
const menuShow = ref(false)
|
||||
const dotShow = ref(false)
|
||||
const { activeUrl, openWindowsList, settingShow, tipShow, pageJumps } = leftHook()
|
||||
|
||||
const handleTipShow = () => {
|
||||
const handleTipShow = (item: any) => {
|
||||
tipShow.value = false
|
||||
dotShow.value = false
|
||||
item.dot = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (tipShow.value) {
|
||||
dotShow.value = true
|
||||
menuTop.filter((item) => {
|
||||
if (item.state !== PluginEnum.BUILTIN) {
|
||||
item.dot = true
|
||||
}
|
||||
})
|
||||
}
|
||||
/** 十秒后关闭提示 */
|
||||
setTimeout(() => {
|
||||
tipShow.value = false
|
||||
}, 5000)
|
||||
|
||||
// // 创建 ResizeObserver 实例并监听 headerRef
|
||||
// const resizeObserver = new ResizeObserver((entries) => {
|
||||
// for (let entry of entries) {
|
||||
// console.log(entry.contentRect.height)
|
||||
// // if (entry.contentRect.height > 220) {
|
||||
// // // 计算需要移动的项数量
|
||||
// // const itemsToMove = Math.floor(itemsTop.value.length / 2) // 例如:移动一半
|
||||
// // excessItems.value = itemsTop.value.splice(-itemsToMove, itemsToMove)
|
||||
// // }
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// // 观察 headerRef 的大小变化
|
||||
// if (headerRef.value) {
|
||||
// resizeObserver.observe(headerRef.value)
|
||||
// }
|
||||
//
|
||||
// if (actionListRef.value) {
|
||||
// resizeObserver.observe(actionListRef.value)
|
||||
// }
|
||||
//
|
||||
// // 在组件卸载时停止观察
|
||||
// onUnmounted(() => {
|
||||
// resizeObserver.disconnect()
|
||||
// })
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
@ -1,283 +0,0 @@
|
||||
<template>
|
||||
<!-- 弹出框 -->
|
||||
<n-modal v-model:show="isShow" :mask-closable="false" class="w-450px border-rd-8px">
|
||||
<div class="bg-[--bg-popover] h-full box-border flex flex-col">
|
||||
<!-- 顶部图片加上操作栏 -->
|
||||
<div class="h-140px relative w-full p-6px box-border">
|
||||
<img
|
||||
class="absolute blur-6px rounded-t-6px z-1 top-0 left-0 w-full h-140px object-cover"
|
||||
src="@/assets/img/dispersion-bg.png"
|
||||
alt="" />
|
||||
<img
|
||||
class="absolute rounded-t-6px z-2 top-0 left-0 w-full h-140px object-cover"
|
||||
src="@/assets/img/dispersion-bg.png"
|
||||
alt="" />
|
||||
|
||||
<div
|
||||
v-if="type() === 'macos'"
|
||||
@click="handleClose"
|
||||
class="mac-close z-10 relative size-13px shadow-inner bg-#ed6a5eff rounded-50% select-none">
|
||||
<svg class="hidden size-7px color-#000 font-bold select-none absolute top-3px left-3px">
|
||||
<use href="#close"></use>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<svg
|
||||
v-if="type() === 'windows'"
|
||||
@click="handleClose"
|
||||
class="z-10 w-12px h-12px ml-a cursor-pointer select-none">
|
||||
<use href="#close"></use>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<n-flex :size="4" align="center" class="p-18px">
|
||||
<p class="text-(16px [--text-color])">插件管理</p>
|
||||
<div class="ml-6px p-[4px_8px] size-fit bg-[--bate-bg] rounded-8px text-(12px [--bate-color] center)">Beta</div>
|
||||
</n-flex>
|
||||
|
||||
<n-scrollbar style="max-height: 320px">
|
||||
<n-flex :size="26" class="z-10 p-[4px_18px] bg-#cc w-full h-280px">
|
||||
<template v-for="(plugin, index) in plugins" :key="index">
|
||||
<n-flex :size="12">
|
||||
<Transition name="fade" mode="out-in">
|
||||
<!-- 未安装和下载中状态 -->
|
||||
<n-flex
|
||||
v-if="plugin.state === PluginEnum.NOT_INSTALLED || plugin.state === PluginEnum.DOWNLOADING"
|
||||
vertical
|
||||
justify="center"
|
||||
align="center"
|
||||
:size="8"
|
||||
class="box bg-#f1f1f1">
|
||||
<svg class="size-38px color-#555"><use :href="`#${plugin.icon}`"></use></svg>
|
||||
<p class="text-(12px #666)">{{ plugin.title }}</p>
|
||||
|
||||
<n-flex
|
||||
@click="handleState(plugin)"
|
||||
class="relative rounded-22px border-(1px solid #4C77BD)"
|
||||
:class="[
|
||||
plugin.state === PluginEnum.DOWNLOADING ? 'downloading' : 'bg-#e0e9fc size-fit p-[4px_8px]'
|
||||
]">
|
||||
<div
|
||||
:style="{
|
||||
width: plugin.state === PluginEnum.DOWNLOADING ? `${plugin.progress * 0.8}px` : 'auto'
|
||||
}"
|
||||
:class="[
|
||||
plugin.progress < 100 ? 'rounded-l-24px rounded-r-0' : 'rounded-24px',
|
||||
plugin.progress > 0 ? 'h-18px border-(1px solid transparent)' : 'h-20px'
|
||||
]"
|
||||
v-if="plugin.state === PluginEnum.DOWNLOADING"
|
||||
class="bg-#8CA9F4">
|
||||
<p class="absolute-center text-(12px #4C77BD)">{{ plugin.progress }}%</p>
|
||||
</div>
|
||||
|
||||
<p v-else class="text-(12px #4C77BD center)">安装</p>
|
||||
</n-flex>
|
||||
|
||||
<!-- 闪光效果 -->
|
||||
<div class="flash"></div>
|
||||
</n-flex>
|
||||
|
||||
<!-- 可卸载状态 -->
|
||||
<n-flex v-else vertical justify="center" align="center" :size="8" class="box colorful">
|
||||
<svg class="size-38px color-#555"><use :href="`#${plugin.iconActive || plugin.icon}`"></use></svg>
|
||||
<p class="text-(12px #666)">{{ plugin.title }}</p>
|
||||
|
||||
<n-flex
|
||||
v-if="plugin.state === PluginEnum.UNINSTALLING"
|
||||
class="relative rounded-22px border-(1px solid #c14053) bg-#f6dfe3 p-[4px_8px]">
|
||||
<p class="text-(12px #c14053 center)">卸载中</p>
|
||||
</n-flex>
|
||||
|
||||
<n-flex
|
||||
v-else
|
||||
class="relative rounded-22px border-(1px solid #4C77BD) bg-#e0e9fc size-fit p-[4px_8px]">
|
||||
<p class="text-(12px #4C77BD center)">{{ plugin.version }}</p>
|
||||
</n-flex>
|
||||
|
||||
<!-- 闪光效果 -->
|
||||
<div class="flash"></div>
|
||||
|
||||
<!-- 插件操作 -->
|
||||
<n-popover
|
||||
v-model:show="plugin.isAction"
|
||||
style="padding: 0"
|
||||
:show-arrow="false"
|
||||
trigger="click"
|
||||
placement="bottom-end">
|
||||
<template #trigger>
|
||||
<svg class="absolute color-#666 right-0 top-0 size-18px rotate-90"><use href="#more"></use></svg>
|
||||
</template>
|
||||
|
||||
<div @click.stop="plugin.isAction = false" class="action-item">
|
||||
<div class="menu-list">
|
||||
<div @click="handleAdd(index)" class="menu-item">
|
||||
<svg class="color-#4C77BD"><use href="#add"></use></svg>
|
||||
<p class="text-#4C77BD">添加侧边栏</p>
|
||||
</div>
|
||||
<div @click="handleDelete(index)" class="menu-item">
|
||||
<svg class="color-#c14053"><use href="#reduce"></use></svg>
|
||||
<p class="text-#c14053">删除</p>
|
||||
</div>
|
||||
<div @click="handleUnload(index)" class="menu-item">
|
||||
<svg><use href="#delete"></use></svg>
|
||||
<p>卸载</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-popover>
|
||||
</n-flex>
|
||||
</Transition>
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-flex>
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</n-modal>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { type } from '@tauri-apps/plugin-os'
|
||||
import { PluginEnum } from '@/enums'
|
||||
import { itemsTop } from '../config.tsx'
|
||||
|
||||
// todo: 这里需要修改和 OPT.L.Common[] 的类型定义
|
||||
type Plugins = {
|
||||
title: string
|
||||
state: PluginEnum
|
||||
version: string
|
||||
isAction: boolean
|
||||
icon: string
|
||||
iconActive: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
const { show } = toRefs(props)
|
||||
const isShow = ref(show.value)
|
||||
const plugins = ref<Plugins[]>([
|
||||
{
|
||||
title: 'HuLa云音乐',
|
||||
state: PluginEnum.NOT_INSTALLED,
|
||||
version: 'v1.0.0-Bate',
|
||||
isAction: false,
|
||||
icon: 'Music'
|
||||
},
|
||||
{
|
||||
title: 'HuLa AI',
|
||||
state: PluginEnum.NOT_INSTALLED,
|
||||
version: 'v2.0.0-Bate',
|
||||
isAction: false,
|
||||
icon: 'robot',
|
||||
icon_active: 'robot-action'
|
||||
}
|
||||
])
|
||||
const emits = defineEmits(['close'])
|
||||
|
||||
watch(show, (newVal) => {
|
||||
isShow.value = newVal
|
||||
})
|
||||
|
||||
const handleClose = () => {
|
||||
isShow.value = false
|
||||
emits('close', isShow.value)
|
||||
}
|
||||
|
||||
const handleState = (plugin) => {
|
||||
if (plugin.state === PluginEnum.INSTALLED) return
|
||||
plugin.state = PluginEnum.DOWNLOADING
|
||||
plugin.progress = 0
|
||||
const interval = setInterval(() => {
|
||||
if (plugin.progress < 100) {
|
||||
plugin.progress += 10
|
||||
} else {
|
||||
clearInterval(interval)
|
||||
plugin.state = PluginEnum.INSTALLED
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const handleUnload = (index: number) => {
|
||||
plugins.value.filter((item, i) => {
|
||||
if (i === index) {
|
||||
item.state = PluginEnum.UNINSTALLING
|
||||
setTimeout(() => {
|
||||
handleDelete(index)
|
||||
item.state = PluginEnum.NOT_INSTALLED
|
||||
}, 2000)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = (index) => {
|
||||
plugins.value.filter((item, i) => {
|
||||
if (i === index) {
|
||||
// 找到 itemsTop 中与 item.icon 匹配的项并删除
|
||||
const itemIndex = itemsTop.value.findIndex((topItem) => topItem.icon === item.icon)
|
||||
if (itemIndex !== -1) {
|
||||
itemsTop.value.splice(itemIndex, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleAdd = (index) => {
|
||||
plugins.value.filter((item, i) => {
|
||||
if (i === index) {
|
||||
// 判断如果itemsTop中已经存在该插件,则不再添加
|
||||
const itemIndex = itemsTop.value.findIndex((topItem) => topItem.icon === item.icon)
|
||||
if (itemIndex !== -1) {
|
||||
return
|
||||
}
|
||||
itemsTop.value.push(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.box {
|
||||
@apply relative select-none custom-shadow cursor-pointer size-fit w-100px h-100px rounded-8px overflow-hidden;
|
||||
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.2));
|
||||
transition: all 0.2s;
|
||||
.flash {
|
||||
position: absolute;
|
||||
left: -130%;
|
||||
top: 0;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-image: linear-gradient(90deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0));
|
||||
transform: skew(-30deg);
|
||||
pointer-events: none;
|
||||
}
|
||||
&:hover .flash {
|
||||
left: 130%;
|
||||
transition: all 0.8s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.downloading {
|
||||
width: 80px;
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
@include menu-item-style();
|
||||
left: -80px;
|
||||
@include menu-list();
|
||||
}
|
||||
|
||||
.colorful {
|
||||
background: linear-gradient(45deg, #fdcbf1 0%, #fdcbf1 1%, #e6dee9 100%);
|
||||
}
|
||||
</style>
|
279
src/layout/left/components/definePlugins/Card.vue
Normal file
@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-scrollbar style="max-height: 280px">
|
||||
<n-flex :size="26" class="z-10 p-[18px_18px_36px_18px] box-border w-full">
|
||||
<template v-for="(plugin, index) in plugins" :key="index">
|
||||
<Transition name="fade" mode="out-in">
|
||||
<!-- 未安装和下载中状态 -->
|
||||
<n-flex
|
||||
v-if="plugin.state === PluginEnum.NOT_INSTALLED || plugin.state === PluginEnum.DOWNLOADING"
|
||||
vertical
|
||||
justify="center"
|
||||
align="center"
|
||||
:size="8"
|
||||
:class="{ 'filter-shadow': page.shadow }"
|
||||
class="box bg-[--info-hover]">
|
||||
<svg class="size-38px color-#777"><use :href="`#${plugin.icon}`"></use></svg>
|
||||
<p class="text-(12px #666)">{{ plugin.title }}</p>
|
||||
|
||||
<!-- 在下载中进度条 -->
|
||||
<n-flex
|
||||
@click="handleState(plugin)"
|
||||
class="relative rounded-22px border-(1px solid #4C77BD)"
|
||||
:class="[
|
||||
plugin.state === PluginEnum.DOWNLOADING ? 'downloading' : 'bg-[--progress-bg] size-fit p-[4px_8px]'
|
||||
]">
|
||||
<div
|
||||
:style="{
|
||||
width: plugin.state === PluginEnum.DOWNLOADING ? `${plugin?.progress * 0.8}px` : 'auto'
|
||||
}"
|
||||
:class="[
|
||||
plugin?.progress < 100 ? 'rounded-l-24px rounded-r-0' : 'rounded-24px',
|
||||
plugin?.progress > 0 ? 'h-18px border-(1px solid transparent)' : 'h-20px'
|
||||
]"
|
||||
v-if="plugin.state === PluginEnum.DOWNLOADING"
|
||||
class="bg-#8CA9F4">
|
||||
<p class="absolute-center text-(12px #4C77BD)">{{ plugin?.progress }}%</p>
|
||||
</div>
|
||||
|
||||
<p v-else class="text-(12px #4C77BD center)">安装</p>
|
||||
</n-flex>
|
||||
|
||||
<!-- 闪光效果 -->
|
||||
<div class="flash"></div>
|
||||
</n-flex>
|
||||
|
||||
<!-- 可卸载状态或内置插件状态 -->
|
||||
<n-flex
|
||||
v-else
|
||||
vertical
|
||||
justify="center"
|
||||
align="center"
|
||||
:size="8"
|
||||
class="box"
|
||||
:class="[
|
||||
plugin.state === PluginEnum.BUILTIN
|
||||
? 'built'
|
||||
: plugin.state === PluginEnum.UNINSTALLING
|
||||
? 'unload'
|
||||
: 'colorful',
|
||||
{
|
||||
'filter-shadow': page.shadow
|
||||
}
|
||||
]">
|
||||
<img v-if="plugin.isAnimate" class="size-38px" :src="`/emoji/${plugin.icon}.webp`" alt="" />
|
||||
<svg v-else class="size-38px color-#555"><use :href="`#${plugin.iconAction || plugin.icon}`"></use></svg>
|
||||
<p class="text-(12px #666)">{{ plugin.title }}</p>
|
||||
|
||||
<n-flex
|
||||
v-if="plugin.state === PluginEnum.UNINSTALLING"
|
||||
class="relative rounded-22px border-(1px solid #c14053) bg-#f6dfe3 p-[4px_8px]">
|
||||
<p class="text-(12px #c14053 center)">卸载中</p>
|
||||
</n-flex>
|
||||
|
||||
<n-flex
|
||||
v-if="plugin.state === PluginEnum.BUILTIN"
|
||||
class="relative rounded-22px border-(1px solid #777) bg-#e3e3e3 size-fit p-[4px_8px]">
|
||||
<p class="text-(12px #777 center)">已内置</p>
|
||||
</n-flex>
|
||||
|
||||
<n-flex
|
||||
v-if="plugin.state === PluginEnum.INSTALLED"
|
||||
class="relative rounded-22px border-(1px solid #4C77BD) bg-#e0e9fc p-[4px_8px]">
|
||||
<p class="text-(12px #4C77BD center)">{{ plugin.version }}</p>
|
||||
</n-flex>
|
||||
|
||||
<!-- 闪光效果 -->
|
||||
<div class="flash"></div>
|
||||
|
||||
<Transition>
|
||||
<svg
|
||||
v-if="plugin.isAdd && plugin.state !== PluginEnum.BUILTIN"
|
||||
class="absolute color-#666 left-2px top-2px size-14px">
|
||||
<use href="#notOnTop"></use>
|
||||
</svg>
|
||||
</Transition>
|
||||
|
||||
<!-- 插件操作 -->
|
||||
<n-popover
|
||||
v-if="plugin.state === PluginEnum.INSTALLED || index === isCurrently"
|
||||
:show="isCurrently === index"
|
||||
style="padding: 0"
|
||||
:show-arrow="false"
|
||||
trigger="click"
|
||||
placement="bottom">
|
||||
<template #trigger>
|
||||
<svg @click.stop="isCurrently = index" class="absolute color-#666 right-0 top-0 size-18px rotate-90">
|
||||
<use href="#more"></use>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<div class="action-item">
|
||||
<div class="menu-list">
|
||||
<div v-if="!plugin.isAdd" @click="handleAdd(plugin)" class="menu-item">
|
||||
<svg class="color-#4C77BD"><use href="#add"></use></svg>
|
||||
<p class="text-#4C77BD">固定侧边栏</p>
|
||||
</div>
|
||||
<div v-else @click="handleDelete(plugin)" class="menu-item">
|
||||
<svg class="color-#c14053"><use href="#reduce"></use></svg>
|
||||
<p class="text-#c14053">取消固定</p>
|
||||
</div>
|
||||
<div @click="handleUnload(plugin)" class="menu-item">
|
||||
<svg><use href="#delete"></use></svg>
|
||||
<p>卸载</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-popover>
|
||||
</n-flex>
|
||||
</Transition>
|
||||
</template>
|
||||
</n-flex>
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { PluginEnum } from '@/enums'
|
||||
import { setting } from '@/stores/setting.ts'
|
||||
import { usePluginsStore } from '@/stores/plugins.ts'
|
||||
import { useMenuTopStore } from '@/stores/menuTop.ts'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const settingStore = setting()
|
||||
const { updatePlugins, plugins } = usePluginsStore()
|
||||
const { menuTop } = useMenuTopStore()
|
||||
const { page } = storeToRefs(settingStore)
|
||||
const isCurrently = ref(-1)
|
||||
|
||||
const handleState = (plugin: STO.Plugins<PluginEnum>) => {
|
||||
if (plugin.state === PluginEnum.INSTALLED) return
|
||||
plugin.state = PluginEnum.DOWNLOADING
|
||||
const interval = setInterval(() => {
|
||||
if (plugin.progress < 100) {
|
||||
plugin.progress += 10
|
||||
} else {
|
||||
clearInterval(interval)
|
||||
plugin.state = PluginEnum.INSTALLED
|
||||
updatePlugins(plugin)
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const handleUnload = (plugin: STO.Plugins<PluginEnum>) => {
|
||||
plugin.state = PluginEnum.UNINSTALLING
|
||||
setTimeout(() => {
|
||||
handleDelete(plugin)
|
||||
plugin.isAdd = false
|
||||
plugin.state = PluginEnum.NOT_INSTALLED
|
||||
updatePlugins(plugin)
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
const handleDelete = (plugin: STO.Plugins<PluginEnum>) => {
|
||||
// 找到 menuTop 中与 item.url 匹配的项并删除
|
||||
const itemIndex = menuTop.findIndex((topItem) => topItem.title === plugin.title)
|
||||
if (itemIndex !== -1) {
|
||||
setTimeout(() => {
|
||||
plugin.isAdd = false
|
||||
updatePlugins(plugin)
|
||||
menuTop.splice(itemIndex, 1)
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAdd = (plugin: STO.Plugins<PluginEnum>) => {
|
||||
// 判断如果itemsTop中已经存在该插件,则不再添加
|
||||
const itemIndex = menuTop.findIndex((topItem) => topItem.title === plugin.title)
|
||||
if (itemIndex !== -1) {
|
||||
return
|
||||
}
|
||||
setTimeout(() => {
|
||||
plugin.isAdd = true
|
||||
updatePlugins(plugin)
|
||||
menuTop.push(plugin)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
const closeMenu = (event: Event) => {
|
||||
const e = event.target as HTMLInputElement
|
||||
if (!e.matches('.action-item')) {
|
||||
isCurrently.value = -1
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('click', closeMenu, true)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('click', closeMenu, true)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.box {
|
||||
@apply relative select-none custom-shadow cursor-pointer size-fit w-100px h-100px rounded-8px overflow-hidden;
|
||||
transition: all 0.2s;
|
||||
.flash {
|
||||
position: absolute;
|
||||
left: -130%;
|
||||
top: 0;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-image: linear-gradient(90deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0));
|
||||
transform: skew(-30deg);
|
||||
pointer-events: none;
|
||||
}
|
||||
&:hover .flash {
|
||||
left: 130%;
|
||||
transition: all 0.8s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.downloading {
|
||||
width: 80px;
|
||||
background: var(--progress-bg);
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
@include menu-item-style();
|
||||
left: -80px;
|
||||
@include menu-list();
|
||||
}
|
||||
|
||||
.colorful {
|
||||
background-image: linear-gradient(45deg, #a8edea 0%, #fed6e3 100%);
|
||||
}
|
||||
|
||||
.built {
|
||||
background-image: linear-gradient(-20deg, #e9defa 0%, #fbfcdb 100%);
|
||||
}
|
||||
|
||||
.unload {
|
||||
background-image: linear-gradient(to top, #feada6 0%, #f5efef 100%);
|
||||
}
|
||||
|
||||
.filter-shadow {
|
||||
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
|
||||
.v-enter-active,
|
||||
.v-leave-active {
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.v-enter-from,
|
||||
.v-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
292
src/layout/left/components/definePlugins/List.vue
Normal file
@ -0,0 +1,292 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-scrollbar style="max-height: 280px" @scroll="handleScroll($event)">
|
||||
<n-flex vertical :size="0" class="z-10 box-border w-full">
|
||||
<template v-for="(plugin, index) in plugins as STO.Plugins<PluginEnum>[]" :key="index">
|
||||
<n-flex align="center" justify="space-between" class="float-block p-[0_20px]">
|
||||
<n-flex :size="14" align="center">
|
||||
<n-flex align="center" justify="center" class="size-48px rounded-50% bg-#7676760f">
|
||||
<Transition mode="out-in">
|
||||
<svg
|
||||
v-if="plugin.state === PluginEnum.NOT_INSTALLED || plugin.state === PluginEnum.DOWNLOADING"
|
||||
class="size-34px color-#666">
|
||||
<use :href="`#${plugin.icon}`"></use>
|
||||
</svg>
|
||||
<template v-else>
|
||||
<img v-if="plugin.isAnimate" class="size-34px" :src="`/emoji/${plugin.icon}.webp`" alt="" />
|
||||
|
||||
<svg v-else class="size-34px color-#666">
|
||||
<use :href="`#${plugin.iconAction || plugin.icon}`"></use>
|
||||
</svg>
|
||||
</template>
|
||||
</Transition>
|
||||
</n-flex>
|
||||
|
||||
<n-flex vertical :size="10">
|
||||
<n-flex align="center" :size="6">
|
||||
<p class="text-(14px #666) pl-4px">{{ plugin.title }}</p>
|
||||
|
||||
<Transition>
|
||||
<svg v-if="plugin.isAdd && plugin.state !== PluginEnum.BUILTIN" class="color-#666 size-14px">
|
||||
<use href="#notOnTop"></use>
|
||||
</svg>
|
||||
</Transition>
|
||||
</n-flex>
|
||||
|
||||
<Transition mode="out-in">
|
||||
<n-flex
|
||||
v-if="plugin.state === PluginEnum.UNINSTALLING"
|
||||
class="relative rounded-22px bg-#f6dfe3 size-fit p-[4px_8px]">
|
||||
<p class="text-(12px #c14053 center)">卸载中</p>
|
||||
</n-flex>
|
||||
|
||||
<n-flex
|
||||
v-else-if="plugin.state === PluginEnum.BUILTIN"
|
||||
class="relative rounded-22px bg-#e3e3e3 size-fit p-[4px_8px]">
|
||||
<p class="text-(12px #777 center)">已内置</p>
|
||||
</n-flex>
|
||||
|
||||
<n-flex v-else class="relative rounded-22px bg-#e0e9fc size-fit p-[4px_8px]">
|
||||
<p class="text-(12px #4C77BD center)">{{ plugin.version }}</p>
|
||||
</n-flex>
|
||||
</Transition>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
|
||||
<!-- 未安装和下载中状态 -->
|
||||
<n-flex
|
||||
v-if="plugin.state === PluginEnum.NOT_INSTALLED || plugin.state === PluginEnum.DOWNLOADING"
|
||||
vertical
|
||||
justify="center"
|
||||
align="center"
|
||||
:size="8"
|
||||
class="box bg-[--left-active-color]">
|
||||
<!-- 在下载中进度条 -->
|
||||
<n-flex
|
||||
@click="handleState(plugin)"
|
||||
align="center"
|
||||
class="relative"
|
||||
:class="[plugin.state === PluginEnum.DOWNLOADING ? 'downloading' : 'bg-[--primary-color] size-full']">
|
||||
<div
|
||||
:style="{
|
||||
width: plugin.state === PluginEnum.DOWNLOADING ? `${plugin.progress * 0.6}px` : 'auto'
|
||||
}"
|
||||
:class="[
|
||||
plugin.progress < 100 ? 'rounded-l-0 rounded-r-0' : 'rounded-2px',
|
||||
plugin.progress > 0 ? 'h-40px border-(1px solid transparent)' : 'h-40px'
|
||||
]"
|
||||
v-if="plugin.state === PluginEnum.DOWNLOADING"
|
||||
class="bg-#8CA9F4">
|
||||
<p class="absolute-center text-(12px #4C77BD)">{{ plugin.progress }}%</p>
|
||||
</div>
|
||||
|
||||
<p v-else class="text-(12px [--chat-text-color] center) w-full">安装</p>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
|
||||
<!-- 卸载中 -->
|
||||
<n-spin v-if="plugin.state === PluginEnum.UNINSTALLING" :stroke="'#c14053'" :size="22" />
|
||||
|
||||
<!-- 插件操作 -->
|
||||
<n-popover
|
||||
v-if="plugin.state === PluginEnum.INSTALLED || index === isCurrently"
|
||||
:show="isCurrently === index"
|
||||
style="padding: 0"
|
||||
:show-arrow="false"
|
||||
trigger="click"
|
||||
placement="bottom">
|
||||
<template #trigger>
|
||||
<svg @click.stop="isCurrently = index" class="size-22px rotate-90">
|
||||
<use href="#more"></use>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<div class="action-item">
|
||||
<div class="menu-list">
|
||||
<div v-if="!plugin.isAdd" @click="handleAdd(plugin)" class="menu-item">
|
||||
<svg class="color-#4C77BD"><use href="#add"></use></svg>
|
||||
<p class="text-#4C77BD">固定侧边栏</p>
|
||||
</div>
|
||||
<div v-else @click="handleDelete(plugin)" class="menu-item">
|
||||
<svg class="color-#c14053"><use href="#reduce"></use></svg>
|
||||
<p class="text-#c14053">取消固定</p>
|
||||
</div>
|
||||
<div @click="handleUnload(plugin)" class="menu-item">
|
||||
<svg><use href="#delete"></use></svg>
|
||||
<p>卸载</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-popover>
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-flex>
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PluginEnum } from '@/enums'
|
||||
import { usePluginsStore } from '@/stores/plugins.ts'
|
||||
import { useMenuTopStore } from '@/stores/menuTop.ts'
|
||||
|
||||
const { plugins, updatePlugins } = usePluginsStore()
|
||||
const { menuTop } = useMenuTopStore()
|
||||
const scrollTop = ref(-1)
|
||||
const itemCount = Object.values(plugins).length
|
||||
const isCurrently = ref(-1)
|
||||
|
||||
const handleScroll = (e: Event) => {
|
||||
const target = e.target as HTMLElement
|
||||
scrollTop.value = target.scrollTop
|
||||
updateHoverClasses()
|
||||
}
|
||||
|
||||
const handleState = (plugin: STO.Plugins<PluginEnum>) => {
|
||||
if (plugin.state === PluginEnum.INSTALLED) return
|
||||
plugin.state = PluginEnum.DOWNLOADING
|
||||
const interval = setInterval(() => {
|
||||
if (plugin.progress < 100) {
|
||||
plugin.progress += 10
|
||||
} else {
|
||||
clearInterval(interval)
|
||||
plugin.state = PluginEnum.INSTALLED
|
||||
updatePlugins(plugin)
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const handleUnload = (plugin: STO.Plugins<PluginEnum>) => {
|
||||
plugin.state = PluginEnum.UNINSTALLING
|
||||
setTimeout(() => {
|
||||
handleDelete(plugin)
|
||||
plugin.isAdd = false
|
||||
plugin.state = PluginEnum.NOT_INSTALLED
|
||||
updatePlugins(plugin)
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
const handleDelete = (plugin: STO.Plugins<PluginEnum>) => {
|
||||
// 找到 menuTop 中与 item.url 匹配的项并删除
|
||||
const itemIndex = menuTop.findIndex((topItem) => topItem.title === plugin.title)
|
||||
if (itemIndex !== -1) {
|
||||
setTimeout(() => {
|
||||
plugin.isAdd = false
|
||||
updatePlugins(plugin)
|
||||
menuTop.splice(itemIndex, 1)
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAdd = (plugin: STO.Plugins<PluginEnum>) => {
|
||||
// 判断如果itemsTop中已经存在该插件,则不再添加
|
||||
const itemIndex = menuTop.findIndex((topItem) => topItem.title === plugin.title)
|
||||
if (itemIndex !== -1) {
|
||||
return
|
||||
}
|
||||
setTimeout(() => {
|
||||
plugin.isAdd = true
|
||||
updatePlugins(plugin)
|
||||
menuTop.push(plugin)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
const closeMenu = (event: Event) => {
|
||||
const e = event.target as HTMLInputElement
|
||||
if (!e.matches('.action-item')) {
|
||||
isCurrently.value = -1
|
||||
}
|
||||
}
|
||||
|
||||
const updateHoverClasses = () => {
|
||||
let styleStr = ''
|
||||
|
||||
for (let i = 0, len = itemCount; i < len - 1; i++) {
|
||||
styleStr += `
|
||||
.float-block:nth-child(${i + 1}):hover~.float-block:last-child::before {
|
||||
--y: calc(var(--height) * ${i} - ${scrollTop.value}px);
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
styleStr += `.float-block:nth-child(${itemCount}):hover::before {
|
||||
--y: calc(var(--height) * ${itemCount - 1} - ${scrollTop.value}px);
|
||||
opacity: .06;
|
||||
}`
|
||||
|
||||
const styleTag = document.getElementById('hover-classes')
|
||||
if (styleTag) styleTag.remove()
|
||||
|
||||
const style = document.createElement('style')
|
||||
style.id = 'hover-classes'
|
||||
style.innerHTML = styleStr
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateHoverClasses()
|
||||
window.addEventListener('click', closeMenu, true)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('click', closeMenu, true)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.float-block {
|
||||
--y: 0;
|
||||
--height: 70px;
|
||||
--surface-2: #767676;
|
||||
|
||||
width: 100%;
|
||||
height: var(--height);
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.float-block:last-child::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
background: var(--surface-2);
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
transform: translateY(var(--y));
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: var(--height);
|
||||
pointer-events: none;
|
||||
transition: all 0.5s cubic-bezier(0.2, 1, 0.2, 1);
|
||||
}
|
||||
|
||||
.float-block:hover ~ .float-block:last-child:before {
|
||||
opacity: 0.06;
|
||||
}
|
||||
|
||||
.box {
|
||||
@apply relative select-none cursor-pointer size-fit w-60px h-40px rounded-8px overflow-hidden;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.downloading {
|
||||
width: 60px;
|
||||
background: var(--progress-bg);
|
||||
}
|
||||
|
||||
.action-item {
|
||||
@include menu-item-style();
|
||||
left: -80px;
|
||||
@include menu-list();
|
||||
}
|
||||
|
||||
.v-enter-active,
|
||||
.v-leave-active {
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.v-enter-from,
|
||||
.v-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
94
src/layout/left/components/definePlugins/index.vue
Normal file
@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<!-- 弹出框 -->
|
||||
<n-modal v-model:show="isShow as boolean" :mask-closable="false" class="w-390px border-rd-8px">
|
||||
<div class="bg-[--bg-popover] h-full box-border flex flex-col">
|
||||
<!-- 顶部图片加上操作栏 -->
|
||||
<div class="h-140px relative w-full p-6px box-border">
|
||||
<img
|
||||
class="absolute blur-6px rounded-t-6px z-1 top-0 left-0 w-full h-140px object-cover"
|
||||
src="@/assets/img/dispersion-bg.png"
|
||||
alt="" />
|
||||
<img
|
||||
class="absolute rounded-t-6px z-2 top-0 left-0 w-full h-140px object-cover"
|
||||
src="@/assets/img/dispersion-bg.png"
|
||||
alt="" />
|
||||
|
||||
<div
|
||||
v-if="type() === 'macos'"
|
||||
@click="isShow = false"
|
||||
class="mac-close z-10 relative size-13px shadow-inner bg-#ed6a5eff rounded-50% select-none">
|
||||
<svg class="hidden size-7px color-#000 font-bold select-none absolute top-3px left-3px">
|
||||
<use href="#close"></use>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<svg
|
||||
v-if="type() === 'windows'"
|
||||
@click="isShow = false"
|
||||
class="z-10 color-#333 w-12px h-12px absolute top-6px right-6px cursor-pointer select-none">
|
||||
<use href="#close"></use>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<n-flex justify="space-between" align="center">
|
||||
<n-flex :size="4" align="center" class="p-18px">
|
||||
<p class="text-(16px [--text-color])">插件管理</p>
|
||||
<div class="ml-6px p-[4px_8px] size-fit bg-[--bate-bg] rounded-8px text-(12px [--bate-color] center)">
|
||||
Beta
|
||||
</div>
|
||||
</n-flex>
|
||||
|
||||
<n-tabs
|
||||
:value="viewMode"
|
||||
:on-update:value="(v) => (viewMode = v)"
|
||||
class="w-76px h-28px mr-22px"
|
||||
type="segment"
|
||||
animated>
|
||||
<n-tab name="card">
|
||||
<template #default>
|
||||
<svg class="size-16px"><use href="#view-grid-card"></use></svg>
|
||||
</template>
|
||||
</n-tab>
|
||||
<n-tab name="list">
|
||||
<template #default>
|
||||
<svg class="size-16px"><use href="#view-grid-list"></use></svg>
|
||||
</template>
|
||||
</n-tab>
|
||||
</n-tabs>
|
||||
</n-flex>
|
||||
|
||||
<Transition name="slide-up" mode="out-in">
|
||||
<Card v-if="viewMode === 'card'" />
|
||||
<List v-else />
|
||||
</Transition>
|
||||
</div>
|
||||
</n-modal>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { type } from '@tauri-apps/plugin-os'
|
||||
import { usePluginsStore } from '@/stores/plugins.ts'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import Card from './Card.vue'
|
||||
import List from './List.vue'
|
||||
|
||||
/** 是否展示插件管理弹窗 */
|
||||
const isShow = defineModel()
|
||||
const { viewMode } = storeToRefs(usePluginsStore())
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.slide-up-enter-active,
|
||||
.slide-up-leave-active {
|
||||
transition: all 0.25s ease-out;
|
||||
}
|
||||
|
||||
.slide-up-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
|
||||
.slide-up-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
</style>
|
@ -1,62 +1,13 @@
|
||||
import { useWindow } from '@/hooks/useWindow.ts'
|
||||
import { MittEnum, ModalEnum } from '@/enums'
|
||||
import { MittEnum, ModalEnum, PluginEnum } from '@/enums'
|
||||
import Mitt from '@/utils/Bus.ts'
|
||||
import { useLogin } from '@/hooks/useLogin.ts'
|
||||
|
||||
const { createWebviewWindow } = useWindow()
|
||||
const { logout } = useLogin()
|
||||
/**
|
||||
* 上半部分操作栏配置
|
||||
* @param url 路由地址
|
||||
* @param icon 图标
|
||||
* @param title 创建新窗口时的标题
|
||||
* @param iconAction 选择后的图标
|
||||
* @param badge 角标
|
||||
* @param tip 提示信息
|
||||
* @param size 窗口大小
|
||||
* @param window 窗口参数
|
||||
* 这里的顶部的操作栏使用pinia写入了localstorage中
|
||||
*/
|
||||
/* todo: 这里需要存入到localStore中, 需要区分固定的和不固定的,固定时有会话和用户列表的,其他都为动态添加 */
|
||||
const itemsTop = ref<OPT.L.Common[]>([
|
||||
{
|
||||
url: 'message',
|
||||
icon: 'message',
|
||||
iconAction: 'message-action'
|
||||
},
|
||||
{
|
||||
url: 'friendsList',
|
||||
icon: 'avatar',
|
||||
iconAction: 'avatar-action'
|
||||
},
|
||||
{
|
||||
url: 'dynamic',
|
||||
icon: 'fire',
|
||||
title: '动态',
|
||||
iconAction: 'fire-action2',
|
||||
size: {
|
||||
width: 840,
|
||||
height: 800
|
||||
},
|
||||
window: {
|
||||
resizable: false
|
||||
}
|
||||
},
|
||||
{
|
||||
url: 'robot',
|
||||
icon: 'robot',
|
||||
title: 'GPT',
|
||||
iconAction: 'robot-action',
|
||||
tip: '机器人新功能在开发中',
|
||||
size: {
|
||||
minWidth: 780,
|
||||
width: 980,
|
||||
height: 800
|
||||
},
|
||||
window: {
|
||||
resizable: true
|
||||
}
|
||||
}
|
||||
])
|
||||
/** 下半部分操作栏配置 */
|
||||
const itemsBottom: OPT.L.Common[] = [
|
||||
{
|
||||
@ -140,4 +91,104 @@ const moreList = ref<OPT.L.MoreList[]>([
|
||||
}
|
||||
])
|
||||
|
||||
export { itemsTop, itemsBottom, moreList }
|
||||
/** 插件列表 */
|
||||
const pluginsList = ref<STO.Plugins<PluginEnum>[]>([
|
||||
{
|
||||
url: 'dynamic',
|
||||
icon: 'fire',
|
||||
title: '动态',
|
||||
isAnimate: true,
|
||||
iconAction: 'fire-action2',
|
||||
state: PluginEnum.BUILTIN,
|
||||
isAdd: true,
|
||||
dot: false,
|
||||
progress: 0,
|
||||
size: {
|
||||
width: 840,
|
||||
height: 800
|
||||
},
|
||||
window: {
|
||||
resizable: false
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'robot',
|
||||
iconAction: 'robot-action',
|
||||
url: 'robot',
|
||||
title: 'ChatBot',
|
||||
isAnimate: true,
|
||||
tip: 'ChatBot新应用上线',
|
||||
state: PluginEnum.NOT_INSTALLED,
|
||||
version: 'v2.0.0-Bate',
|
||||
isAdd: false,
|
||||
dot: true,
|
||||
progress: 0,
|
||||
size: {
|
||||
minWidth: 780,
|
||||
width: 980,
|
||||
height: 800
|
||||
},
|
||||
window: {
|
||||
resizable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'Music',
|
||||
url: 'music',
|
||||
title: 'HuLa云音乐',
|
||||
tip: 'HuLa云音乐开发中,敬请期待',
|
||||
state: PluginEnum.NOT_INSTALLED,
|
||||
version: 'v1.0.0-Alpha',
|
||||
isAdd: false,
|
||||
dot: true,
|
||||
progress: 0,
|
||||
size: {
|
||||
minWidth: 780,
|
||||
width: 980,
|
||||
height: 800
|
||||
},
|
||||
window: {
|
||||
resizable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'UimSlack',
|
||||
url: 'collaboration',
|
||||
title: 'HuLa协作',
|
||||
tip: 'HuLa协作开发中,敬请期待',
|
||||
state: PluginEnum.NOT_INSTALLED,
|
||||
version: 'v1.0.0-Alpha',
|
||||
isAdd: false,
|
||||
dot: true,
|
||||
progress: 0,
|
||||
size: {
|
||||
minWidth: 780,
|
||||
width: 980,
|
||||
height: 800
|
||||
},
|
||||
window: {
|
||||
resizable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'vigo',
|
||||
url: 'collaboration',
|
||||
title: 'HuLa短视频',
|
||||
tip: 'HuLa短视频开发中,敬请期待',
|
||||
state: PluginEnum.NOT_INSTALLED,
|
||||
version: 'v1.0.0-Alpha',
|
||||
isAdd: false,
|
||||
dot: true,
|
||||
progress: 0,
|
||||
size: {
|
||||
minWidth: 780,
|
||||
width: 980,
|
||||
height: 800
|
||||
},
|
||||
window: {
|
||||
resizable: true
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
export { itemsBottom, moreList, pluginsList }
|
||||
|
@ -4,7 +4,6 @@ import { useUserStore } from '@/stores/user.ts'
|
||||
import { useCachedStore } from '@/stores/cached.ts'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onlineStatus } from '@/stores/onlineStatus.ts'
|
||||
import { itemsTop } from '@/layout/left/config.tsx'
|
||||
import { EventEnum, IsYetEnum, MittEnum, MsgEnum, ThemeEnum } from '@/enums'
|
||||
import { BadgeType, UserInfoType } from '@/services/types.ts'
|
||||
import { useChatStore } from '@/stores/chat.ts'
|
||||
@ -17,21 +16,21 @@ import GraphemeSplitter from 'grapheme-splitter'
|
||||
import { delay } from 'lodash-es'
|
||||
import router from '@/router'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { useMenuTopStore } from '@/stores/menuTop.ts'
|
||||
|
||||
export const leftHook = () => {
|
||||
const prefers = matchMedia('(prefers-color-scheme: dark)')
|
||||
const { createWebviewWindow } = useWindow()
|
||||
const settingStore = setting()
|
||||
const { menuTop } = useMenuTopStore()
|
||||
const userStore = useUserStore()
|
||||
const cachedStore = useCachedStore()
|
||||
const { themes, login } = storeToRefs(settingStore)
|
||||
const OLStatusStore = onlineStatus()
|
||||
const { url, title, bgColor } = storeToRefs(OLStatusStore)
|
||||
/**当前选中的元素 默认选中itemsTop的第一项*/
|
||||
const activeUrl = ref<string>(itemsTop.value[0].url)
|
||||
const activeUrl = ref<string>(menuTop[0].url)
|
||||
const settingShow = ref(false)
|
||||
const shrinkStatus = ref(false)
|
||||
const isNewWindows = ref(['dynamic', 'robot', 'mail'])
|
||||
/** 是否展示个人信息浮窗 */
|
||||
const infoShow = ref(false)
|
||||
/** 是否显示上半部分操作栏中的提示 */
|
||||
@ -90,7 +89,7 @@ export const leftHook = () => {
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
itemsTop.value.find((item) => {
|
||||
menuTop.find((item) => {
|
||||
if (item.url === 'message') {
|
||||
item.badge = msgTotal.value
|
||||
}
|
||||
@ -174,8 +173,7 @@ export const leftHook = () => {
|
||||
size?: { width: number; height: number; minWidth?: number },
|
||||
window?: { resizable: boolean }
|
||||
) => {
|
||||
// 判断url是否等于isNewWindows.value数组中的值,如果是,则创建新的窗口
|
||||
if (isNewWindows.value.includes(url)) {
|
||||
if (window) {
|
||||
delay(async () => {
|
||||
await createWebviewWindow(
|
||||
title!,
|
||||
@ -225,7 +223,7 @@ export const leftHook = () => {
|
||||
infoShow.value = false
|
||||
})
|
||||
Mitt.on(MittEnum.UPDATE_MSG_TOTAL, (event) => {
|
||||
itemsTop.value.find((item) => {
|
||||
menuTop.find((item) => {
|
||||
if (item.url === 'message') {
|
||||
item.badge = event as number
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ export const LockScreen = defineComponent(() => {
|
||||
|
||||
/** 检查更新弹窗 */
|
||||
export const CheckUpdate = defineComponent(() => {
|
||||
const url = `https://gitee.com/api/v5/repos/nongyehong/HuLa-IM-Tauri/releases/tags/${pkg.version}?access_token=${import.meta.env.VITE_GITEE_TOKEN}`
|
||||
const url = `https://gitee.com/api/v5/repos/nongyehong/HuLa-IM-Tauri/releases/tags/v${pkg.version}?access_token=${import.meta.env.VITE_GITEE_TOKEN}`
|
||||
/** 项目提交日志记录 */
|
||||
const commitLog = ref<{ message: string; icon: string }[]>([])
|
||||
const loading = ref(false)
|
||||
@ -117,18 +117,32 @@ export const CheckUpdate = defineComponent(() => {
|
||||
/** 版本更新日期 */
|
||||
const versionTime = ref('')
|
||||
|
||||
// const commitTypeMap: { [key: string]: string } = {
|
||||
// feat: 'feat',
|
||||
// fix: 'fix',
|
||||
// docs: 'docs',
|
||||
// style: 'style',
|
||||
// refactor: 'refactor',
|
||||
// perf: 'perf',
|
||||
// test: 'test',
|
||||
// build: 'build',
|
||||
// ci: 'ci',
|
||||
// revert: 'revert',
|
||||
// chore: 'chore'
|
||||
// }
|
||||
|
||||
const commitTypeMap: { [key: string]: string } = {
|
||||
feat: 'feat',
|
||||
fix: 'fix',
|
||||
docs: 'docs',
|
||||
style: 'style',
|
||||
refactor: 'refactor',
|
||||
perf: 'perf',
|
||||
test: 'test',
|
||||
build: 'build',
|
||||
ci: 'ci',
|
||||
revert: 'revert',
|
||||
chore: 'chore'
|
||||
feat: 'comet',
|
||||
fix: 'bug',
|
||||
docs: 'memo',
|
||||
style: 'lipstick',
|
||||
refactor: 'recycling-symbol',
|
||||
perf: 'rocket',
|
||||
test: 'test-tube',
|
||||
build: 'package',
|
||||
ci: 'gear',
|
||||
revert: 'right-arrow-curving-left',
|
||||
chore: 'hammer-and-wrench'
|
||||
}
|
||||
|
||||
const mapCommitType = (commitMessage: string) => {
|
||||
@ -144,7 +158,7 @@ export const CheckUpdate = defineComponent(() => {
|
||||
|
||||
const checkUpdate = () => {
|
||||
const url = `https://gitee.com/api/v5/repos/nongyehong/HuLa-IM-Tauri/tags?access_token=${import.meta.env.VITE_GITEE_TOKEN}&sort=name&direction=desc&page=1&per_page=1`
|
||||
if (lastVersion && lastVersion === pkg.version) {
|
||||
if (lastVersion && lastVersion === `v${pkg.version}`) {
|
||||
window.$message.success('当前已是最新版本')
|
||||
return
|
||||
}
|
||||
@ -153,10 +167,12 @@ export const CheckUpdate = defineComponent(() => {
|
||||
res
|
||||
.json()
|
||||
.then(async (data) => {
|
||||
if (data[0].name === pkg.version) {
|
||||
checkLoading.value = false
|
||||
window.$message.success('当前已是最新版本')
|
||||
lastVersion = pkg.version
|
||||
if (data[0].name === `v${pkg.version}`) {
|
||||
setTimeout(() => {
|
||||
window.$message.success('当前已是最新版本')
|
||||
lastVersion = `v${pkg.version}`
|
||||
checkLoading.value = false
|
||||
}, 600)
|
||||
} else {
|
||||
// TODO 获取最新版本的提交日志,并且更换按钮文字为下载最新版本 (nyh -> 2024-07-11 22:20:33)
|
||||
}
|
||||
@ -193,7 +209,7 @@ export const CheckUpdate = defineComponent(() => {
|
||||
const message = lastColonIndex !== -1 ? commit.substring(lastColonIndex + 1).trim() : commit
|
||||
return {
|
||||
message: message,
|
||||
icon: mapCommitType(commit)!
|
||||
icon: mapCommitType(commit) || 'alien-monster'
|
||||
}
|
||||
})
|
||||
loading.value = false
|
||||
@ -232,7 +248,7 @@ export const CheckUpdate = defineComponent(() => {
|
||||
<NFlex justify={'space-between'} align={'center'}>
|
||||
<NFlex align={'center'} size={10}>
|
||||
<p>当前版本:</p>
|
||||
<p class="text-(24px #909090) font-500">{pkg.version}</p>
|
||||
<p class="text-(24px #909090) font-500">v{pkg.version}</p>
|
||||
</NFlex>
|
||||
<NFlex align={'center'} size={10}>
|
||||
<p class="text-(12px #909090)">版本发布日期:</p>
|
||||
@ -241,15 +257,16 @@ export const CheckUpdate = defineComponent(() => {
|
||||
</NFlex>
|
||||
<p class="text-(14px #909090)">版本更新日志</p>
|
||||
<NScrollbar class="max-h-460px p-[0_10px] box-border">
|
||||
<NTimeline class="p-[0_6px] box-border">
|
||||
<NTimeline class="p-16px box-border">
|
||||
{commitLog.value.map((log, index) => (
|
||||
<NTimelineItem key={index} content={log.message}>
|
||||
{{
|
||||
icon: () => (
|
||||
<NIcon size={20}>
|
||||
<svg>
|
||||
<use href={`#${log.icon}`}></use>
|
||||
</svg>
|
||||
<NIcon size={32}>
|
||||
{/*<svg>*/}
|
||||
{/* <use href={`#${log.icon}`}></use>*/}
|
||||
{/*</svg>*/}
|
||||
<img class="size-32px" src={`/emoji/${log.icon}.webp`} alt="" />
|
||||
</NIcon>
|
||||
)
|
||||
}}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
.left {
|
||||
background: var(--left-bg-color);
|
||||
//background: #64a29c;
|
||||
}
|
||||
|
||||
.top-action,
|
||||
@ -27,8 +28,6 @@
|
||||
|
||||
.setting-item {
|
||||
@include menu-item-style(absolute);
|
||||
left: 58px;
|
||||
bottom: 10px;
|
||||
@include menu-list();
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,24 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { StoresEnum } from '@/enums'
|
||||
|
||||
export const alwaysOnTop = defineStore(StoresEnum.ALWAYS_ON_TOP, {
|
||||
state: (): STO.AlwaysOnTop => ({}),
|
||||
actions: {
|
||||
setWindowTop(key: string, data: boolean) {
|
||||
this.$state[key] = data
|
||||
},
|
||||
getWindowTop(key: string) {
|
||||
return this.$state[key]
|
||||
}
|
||||
interface AlwaysOnTopState {
|
||||
[key: string]: boolean
|
||||
}
|
||||
|
||||
export const useAlwaysOnTopStore = defineStore(StoresEnum.ALWAYS_ON_TOP, () => {
|
||||
const alwaysOnTop = ref<AlwaysOnTopState>({})
|
||||
|
||||
const setWindowTop = (key: string, data: boolean) => {
|
||||
alwaysOnTop.value[key] = data
|
||||
}
|
||||
|
||||
const getWindowTop = (key: string) => {
|
||||
return alwaysOnTop.value[key]
|
||||
}
|
||||
|
||||
return {
|
||||
alwaysOnTop,
|
||||
setWindowTop,
|
||||
getWindowTop
|
||||
}
|
||||
})
|
||||
|
@ -1,16 +1,23 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { StoresEnum } from '@/enums'
|
||||
|
||||
export const history = defineStore(StoresEnum.HISTORY, {
|
||||
state: (): STO.History => ({
|
||||
emoji: []
|
||||
}),
|
||||
actions: {
|
||||
setEmoji(item: string[]) {
|
||||
this.emoji = item
|
||||
export const useHistoryStore = defineStore(
|
||||
StoresEnum.HISTORY,
|
||||
() => {
|
||||
const emoji = ref<string[]>([])
|
||||
|
||||
const setEmoji = (item: string[]) => {
|
||||
emoji.value = item
|
||||
}
|
||||
|
||||
return {
|
||||
emoji,
|
||||
setEmoji
|
||||
}
|
||||
},
|
||||
share: {
|
||||
enable: true
|
||||
{
|
||||
share: {
|
||||
enable: true
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
42
src/stores/menuTop.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { PluginEnum, StoresEnum } from '@/enums'
|
||||
import { usePluginsStore } from '@/stores/plugins.ts'
|
||||
|
||||
export const useMenuTopStore = defineStore(
|
||||
StoresEnum.MENUTOP,
|
||||
() => {
|
||||
const { getPluginType } = usePluginsStore()
|
||||
const pluginType = getPluginType(PluginEnum.BUILTIN)
|
||||
// 初始状态
|
||||
const initialState: OPT.L.Common[] = [
|
||||
{
|
||||
url: 'message',
|
||||
icon: 'message',
|
||||
iconAction: 'message-action'
|
||||
},
|
||||
{
|
||||
url: 'friendsList',
|
||||
icon: 'avatar',
|
||||
iconAction: 'avatar-action'
|
||||
}
|
||||
]
|
||||
|
||||
const menuTop = ref<STO.Plugins<PluginEnum>[]>(initialState as any)
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (!localStorage.getItem(StoresEnum.MENUTOP)) {
|
||||
menuTop.value.push(pluginType)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
menuTop
|
||||
}
|
||||
},
|
||||
{
|
||||
share: {
|
||||
enable: true,
|
||||
initialize: true
|
||||
}
|
||||
}
|
||||
)
|
@ -5,7 +5,7 @@ import Colorthief from 'colorthief'
|
||||
|
||||
const colorthief = new Colorthief()
|
||||
export const onlineStatus = defineStore(StoresEnum.ONLINE_STATUS, {
|
||||
state: (): OPT.Online => ({
|
||||
state: (): STO.OnlineStatus => ({
|
||||
url: '',
|
||||
title: '',
|
||||
bgColor: ''
|
||||
|
71
src/stores/plugins.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { PluginEnum, StoresEnum } from '@/enums'
|
||||
import { pluginsList } from '@/layout/left/config.tsx'
|
||||
|
||||
export const usePluginsStore = defineStore(
|
||||
StoresEnum.PLUGINS,
|
||||
() => {
|
||||
/** 插件内容 */
|
||||
const plugins = ref<STO.Plugins<PluginEnum>[]>([])
|
||||
/** 插件查看模式 */
|
||||
const viewMode = ref<string>('card')
|
||||
|
||||
/**
|
||||
* 设置插件
|
||||
* @param newPlugins 插件数据
|
||||
*/
|
||||
const setPlugins = (newPlugins: STO.Plugins<PluginEnum>[]) => {
|
||||
plugins.value = newPlugins
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取特定类型的插件
|
||||
* @param P 插件类型
|
||||
*/
|
||||
const getPluginType = (P: PluginEnum) => {
|
||||
if (Object.keys(plugins.value).length === 0) {
|
||||
return pluginsList.value[P]
|
||||
} else {
|
||||
return plugins.value[P]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新插件状态
|
||||
* @param P 插件
|
||||
*/
|
||||
const updatePlugins = (P: STO.Plugins<PluginEnum>) => {
|
||||
Object.values(plugins.value).find((item: STO.Plugins<PluginEnum>) => {
|
||||
if (item.title === P.title) {
|
||||
// 修改对应项插件状态
|
||||
item.state = P.state
|
||||
item.isAdd = P.isAdd
|
||||
setPlugins(plugins.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
// 读取本地存储的插件数据
|
||||
if (!localStorage.getItem(StoresEnum.PLUGINS)) {
|
||||
setPlugins(pluginsList.value)
|
||||
} else {
|
||||
Object.assign(pluginsList.value, JSON.parse(localStorage.getItem(StoresEnum.PLUGINS)!))
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
plugins,
|
||||
viewMode,
|
||||
setPlugins,
|
||||
getPluginType,
|
||||
updatePlugins
|
||||
}
|
||||
},
|
||||
{
|
||||
share: {
|
||||
enable: true,
|
||||
initialize: true
|
||||
}
|
||||
}
|
||||
)
|
@ -8,27 +8,22 @@ const badgeCachedList = reactive<Record<number, Partial<CacheBadgeItem>>>({})
|
||||
// TODO 使用indexDB或者把配置写出到文件中,还需要根据每个账号来进行配置 (nyh -> 2024-03-26 01:22:12)
|
||||
export const setting = defineStore(StoresEnum.SETTING, {
|
||||
state: (): STO.Setting => ({
|
||||
/** 主题设置 */
|
||||
themes: {
|
||||
content: '',
|
||||
pattern: ''
|
||||
},
|
||||
/** 是否启用ESC关闭窗口 */
|
||||
escClose: true,
|
||||
lockScreen: {
|
||||
enable: false,
|
||||
password: ''
|
||||
},
|
||||
/** 系统托盘 */
|
||||
tips: {
|
||||
type: CloseBxEnum.HIDE,
|
||||
notTips: false
|
||||
},
|
||||
/** 登录设置 */
|
||||
login: {
|
||||
autoLogin: false,
|
||||
autoStartup: false,
|
||||
/** 用户保存的登录信息 */
|
||||
accountInfo: {
|
||||
account: '',
|
||||
password: '',
|
||||
@ -39,16 +34,12 @@ export const setting = defineStore(StoresEnum.SETTING, {
|
||||
},
|
||||
badgeList: []
|
||||
},
|
||||
/** 聊天设置 */
|
||||
chat: {
|
||||
/** 发送快捷键 */
|
||||
sendKey: 'Enter',
|
||||
/** 是否双击打开独立会话窗口 */
|
||||
isDouble: true
|
||||
},
|
||||
/** 界面设置 */
|
||||
page: {
|
||||
shadow: false,
|
||||
shadow: true,
|
||||
fonts: 'AliFangYuan'
|
||||
}
|
||||
}),
|
||||
|
@ -4,7 +4,6 @@
|
||||
svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
outline: none;
|
||||
}
|
||||
&:hover svg {
|
||||
color: #13987f;
|
||||
@ -13,7 +12,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
.box-item {
|
||||
@apply border-(solid 1px [--line-color]) custom-shadow;
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
|
@ -4,7 +4,8 @@
|
||||
font-weight: 500 !important;
|
||||
caret-color: #13987f !important; /* 改变输入框光标的颜色 */
|
||||
}
|
||||
html, body {
|
||||
html, body, svg {
|
||||
outline: none;
|
||||
overscroll-behavior: none; // 禁止mac的“触底反弹”效果
|
||||
}
|
||||
/**! 主题变量 */
|
||||
@ -91,6 +92,8 @@ html, body {
|
||||
--bate-bg: #d9ccf7;
|
||||
// 卡片hover样式
|
||||
--card-bg: #13987f60;
|
||||
// 安装进度条样式
|
||||
--progress-bg: #e0e9fc;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] {
|
||||
@ -170,6 +173,8 @@ html[data-theme='dark'] {
|
||||
--bate-bg: #403555;
|
||||
// 卡片hover样式
|
||||
--card-bg: #13987f20;
|
||||
// 安装进度条样式
|
||||
--progress-bg: transparent;
|
||||
}
|
||||
/**! end */
|
||||
// 线性动画
|
||||
|
@ -7,7 +7,6 @@
|
||||
background: none; /** 无背景 */
|
||||
border: none; /** 边框样式,可根据需求修改 */
|
||||
border-radius: 0; /** 边框圆角 */
|
||||
outline: none; /** 点击时无轮廓 */
|
||||
min-height: 90px; /** 最小高度 */
|
||||
line-height: 20px; /** 行高 */
|
||||
overflow: auto; /** 内容过多时允许滚动 */
|
||||
@ -25,4 +24,4 @@
|
||||
}
|
||||
.active {
|
||||
background-color: var(--bg-group-hover);
|
||||
}
|
||||
}
|
||||
|
114
src/typings/components.d.ts
vendored
@ -1,66 +1,68 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
ActionBar: (typeof import('./../components/windows/ActionBar.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']
|
||||
ChatMain: (typeof import('./../components/rightBox/chatBox/ChatMain.vue'))['default']
|
||||
ChatSidebar: (typeof import('./../components/rightBox/chatBox/ChatSidebar.vue'))['default']
|
||||
ContextMenu: (typeof import('./../components/common/ContextMenu.vue'))['default']
|
||||
Details: (typeof import('./../components/rightBox/Details.vue'))['default']
|
||||
Emoji: (typeof import('./../components/rightBox/emoji/index.vue'))['default']
|
||||
Image: (typeof import('./../components/rightBox/renderMessage/Image.vue'))['default']
|
||||
InfoPopover: (typeof import('./../components/common/InfoPopover.vue'))['default']
|
||||
MsgInput: (typeof import('./../components/rightBox/MsgInput.vue'))['default']
|
||||
NaiveProvider: (typeof import('./../components/common/NaiveProvider.vue'))['default']
|
||||
NAlert: (typeof import('naive-ui'))['NAlert']
|
||||
NAvatar: (typeof import('naive-ui'))['NAvatar']
|
||||
NAvatarGroup: (typeof import('naive-ui'))['NAvatarGroup']
|
||||
NBadge: (typeof import('naive-ui'))['NBadge']
|
||||
NButton: (typeof import('naive-ui'))['NButton']
|
||||
NButtonGroup: (typeof import('naive-ui'))['NButtonGroup']
|
||||
NCheckbox: (typeof import('naive-ui'))['NCheckbox']
|
||||
NCollapse: (typeof import('naive-ui'))['NCollapse']
|
||||
NCollapseItem: (typeof import('naive-ui'))['NCollapseItem']
|
||||
NConfigProvider: (typeof import('naive-ui'))['NConfigProvider']
|
||||
NDialogProvider: (typeof import('naive-ui'))['NDialogProvider']
|
||||
NDropdown: (typeof import('naive-ui'))['NDropdown']
|
||||
NEllipsis: (typeof import('naive-ui'))['NEllipsis']
|
||||
NFlex: (typeof import('naive-ui'))['NFlex']
|
||||
NIcon: (typeof import('naive-ui'))['NIcon']
|
||||
NIconWrapper: (typeof import('naive-ui'))['NIconWrapper']
|
||||
NImage: (typeof import('naive-ui'))['NImage']
|
||||
NImageGroup: (typeof import('naive-ui'))['NImageGroup']
|
||||
NInput: (typeof import('naive-ui'))['NInput']
|
||||
NLoadingBarProvider: (typeof import('naive-ui'))['NLoadingBarProvider']
|
||||
NMessageProvider: (typeof import('naive-ui'))['NMessageProvider']
|
||||
NModal: (typeof import('naive-ui'))['NModal']
|
||||
NModalProvider: (typeof import('naive-ui'))['NModalProvider']
|
||||
NNotificationProvider: (typeof import('naive-ui'))['NNotificationProvider']
|
||||
NPopconfirm: (typeof import('naive-ui'))['NPopconfirm']
|
||||
NPopover: (typeof import('naive-ui'))['NPopover']
|
||||
NPopselect: (typeof import('naive-ui'))['NPopselect']
|
||||
NQrCode: (typeof import('naive-ui'))['NQrCode']
|
||||
NRadio: (typeof import('naive-ui'))['NRadio']
|
||||
NScrollbar: (typeof import('naive-ui'))['NScrollbar']
|
||||
NSelect: (typeof import('naive-ui'))['NSelect']
|
||||
NSkeleton: (typeof import('naive-ui'))['NSkeleton']
|
||||
NSwitch: (typeof import('naive-ui'))['NSwitch']
|
||||
NTabPane: (typeof import('naive-ui'))['NTabPane']
|
||||
NTabs: (typeof import('naive-ui'))['NTabs']
|
||||
NTooltip: (typeof import('naive-ui'))['NTooltip']
|
||||
NVirtualList: (typeof import('naive-ui'))['NVirtualList']
|
||||
RenderMessage: (typeof import('./../components/rightBox/renderMessage/index.vue'))['default']
|
||||
RouterLink: (typeof import('vue-router'))['RouterLink']
|
||||
RouterView: (typeof import('vue-router'))['RouterView']
|
||||
Screenshot: (typeof import('./../components/common/Screenshot.vue'))['default']
|
||||
Text: (typeof import('./../components/rightBox/renderMessage/Text.vue'))['default']
|
||||
ActionBar: typeof import('./../components/windows/ActionBar.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']
|
||||
ChatMain: typeof import('./../components/rightBox/chatBox/ChatMain.vue')['default']
|
||||
ChatSidebar: typeof import('./../components/rightBox/chatBox/ChatSidebar.vue')['default']
|
||||
ContextMenu: typeof import('./../components/common/ContextMenu.vue')['default']
|
||||
Details: typeof import('./../components/rightBox/Details.vue')['default']
|
||||
Emoji: typeof import('./../components/rightBox/emoji/index.vue')['default']
|
||||
Image: typeof import('./../components/rightBox/renderMessage/Image.vue')['default']
|
||||
InfoPopover: typeof import('./../components/common/InfoPopover.vue')['default']
|
||||
MsgInput: typeof import('./../components/rightBox/MsgInput.vue')['default']
|
||||
NaiveProvider: typeof import('./../components/common/NaiveProvider.vue')['default']
|
||||
NAlert: typeof import('naive-ui')['NAlert']
|
||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||
NAvatarGroup: typeof import('naive-ui')['NAvatarGroup']
|
||||
NBadge: typeof import('naive-ui')['NBadge']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NButtonGroup: typeof import('naive-ui')['NButtonGroup']
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NCollapse: typeof import('naive-ui')['NCollapse']
|
||||
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||
NFlex: typeof import('naive-ui')['NFlex']
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
NIconWrapper: typeof import('naive-ui')['NIconWrapper']
|
||||
NImage: typeof import('naive-ui')['NImage']
|
||||
NImageGroup: typeof import('naive-ui')['NImageGroup']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
|
||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||
NModal: typeof import('naive-ui')['NModal']
|
||||
NModalProvider: typeof import('naive-ui')['NModalProvider']
|
||||
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
||||
NPopover: typeof import('naive-ui')['NPopover']
|
||||
NPopselect: typeof import('naive-ui')['NPopselect']
|
||||
NQrCode: typeof import('naive-ui')['NQrCode']
|
||||
NRadio: typeof import('naive-ui')['NRadio']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
NSkeleton: typeof import('naive-ui')['NSkeleton']
|
||||
NSpin: typeof import('naive-ui')['NSpin']
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
NTab: typeof import('naive-ui')['NTab']
|
||||
NTabPane: typeof import('naive-ui')['NTabPane']
|
||||
NTabs: typeof import('naive-ui')['NTabs']
|
||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||
NVirtualList: typeof import('naive-ui')['NVirtualList']
|
||||
RenderMessage: typeof import('./../components/rightBox/renderMessage/index.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
Screenshot: typeof import('./../components/common/Screenshot.vue')['default']
|
||||
Text: typeof import('./../components/rightBox/renderMessage/Text.vue')['default']
|
||||
}
|
||||
}
|
||||
|
47
src/typings/options.d.ts
vendored
@ -2,7 +2,17 @@
|
||||
declare namespace OPT {
|
||||
/** 主页左侧选项 */
|
||||
namespace L {
|
||||
/** 顶部的选项 */
|
||||
/**
|
||||
* 顶部的选项
|
||||
* @param url 链接
|
||||
* @param icon 选项图标
|
||||
* @param title 选项名称
|
||||
* @param iconAction 选项右侧的操作图标
|
||||
* @param badge 选项右侧的角标数字
|
||||
* @param tip 选项提示信息
|
||||
* @param size 选项尺寸
|
||||
* @param window 选项窗口属性
|
||||
*/
|
||||
type Common = {
|
||||
url: string
|
||||
icon: string
|
||||
@ -20,14 +30,24 @@ declare namespace OPT {
|
||||
}
|
||||
}
|
||||
|
||||
/** 更多的选项 */
|
||||
/**
|
||||
* 更多的选项
|
||||
* @param label 选项名称
|
||||
* @param icon 选项图标
|
||||
* @param click 点击事件
|
||||
*/
|
||||
type MoreList = {
|
||||
label: string
|
||||
icon: string
|
||||
click: () => void
|
||||
}
|
||||
|
||||
/** 设置页面的侧边栏选项 */
|
||||
/**
|
||||
* 设置页面的侧边栏选项
|
||||
* @param url 链接
|
||||
* @param label 选项名称
|
||||
* @param icon 选项图标
|
||||
*/
|
||||
type SettingSide = {
|
||||
url: string
|
||||
label: string
|
||||
@ -35,21 +55,36 @@ declare namespace OPT {
|
||||
}
|
||||
}
|
||||
|
||||
/** 右键菜单选项 */
|
||||
/**
|
||||
* 右键菜单选项
|
||||
* @param label 选项名称
|
||||
* @param icon 选项图标
|
||||
* @param click 点击事件
|
||||
*/
|
||||
type RightMenu = {
|
||||
label: string
|
||||
icon: string
|
||||
click?: (...args: any[]) => void
|
||||
} | null
|
||||
|
||||
/** 详情页选项 */
|
||||
/**
|
||||
* 详情页选项
|
||||
* @param url 链接
|
||||
* @param title 标题
|
||||
* @param click 点击事件
|
||||
*/
|
||||
type Details = {
|
||||
url: string
|
||||
title: string
|
||||
click: (...args: any[]) => void
|
||||
}
|
||||
|
||||
/** 在线状态 */
|
||||
/**
|
||||
* 在线状态
|
||||
* @param url 链接
|
||||
* @param title 标题
|
||||
* @param bgColor 背景颜色
|
||||
*/
|
||||
type Online = {
|
||||
url: string
|
||||
title: string
|
||||
|
50
src/typings/stores.d.ts
vendored
@ -1,6 +1,15 @@
|
||||
/** pinia的store的命名空间 */
|
||||
declare namespace STO {
|
||||
/** 设置 */
|
||||
/**
|
||||
* 设置
|
||||
* @param themes 主题设置
|
||||
* @param tips 关闭提示
|
||||
* @param escClose 是否启用ESC关闭窗口
|
||||
* @param lockScreen 是否锁屏
|
||||
* @param login 用户登录设置
|
||||
* @param chat 聊天设置
|
||||
* @param page 界面设置
|
||||
*/
|
||||
type Setting = {
|
||||
/** 主题设置 */
|
||||
themes: {
|
||||
@ -54,21 +63,44 @@ declare namespace STO {
|
||||
}
|
||||
}
|
||||
|
||||
/** 置顶窗口列表 */
|
||||
/**
|
||||
* 置顶窗口列表
|
||||
* @param key 窗口名称
|
||||
*/
|
||||
type AlwaysOnTop = {
|
||||
/** 是否置顶窗口列表 */
|
||||
[key: string]: boolean
|
||||
}
|
||||
|
||||
/** 隐藏窗口列表 */
|
||||
type HideWindow = {
|
||||
/** 是否隐藏窗口列表 */
|
||||
[key: string]: boolean
|
||||
}
|
||||
|
||||
/** 历史内容 */
|
||||
/**
|
||||
* 历史内容
|
||||
* @param emoji 历史消息的emoji列表
|
||||
*/
|
||||
type History = {
|
||||
/** emoji列表 */
|
||||
emoji: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 在线状态
|
||||
* @param OPT.Online 通用在线状态
|
||||
*/
|
||||
type OnlineStatus = OPT.Online
|
||||
|
||||
/**
|
||||
* 插件管理弹窗数据类型
|
||||
* @param state 插件状态
|
||||
* @param version 插件版本
|
||||
* @param isAdd 是否添加侧边栏
|
||||
* @param isAnimate 是否动画效果
|
||||
* @param { OPT.L.Common } 通用默认侧边栏
|
||||
*/
|
||||
type Plugins<T> = {
|
||||
state: T
|
||||
version?: string
|
||||
isAdd: boolean
|
||||
isAnimate?: boolean
|
||||
dot?: boolean
|
||||
progress: number
|
||||
} & OPT.L.Common
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import pkg from '../../package.json'
|
||||
/** 控制台打印版本信息 */
|
||||
export const consolePrint = () => {
|
||||
console.log(
|
||||
`%c 🍀 ${pkg.name} ${pkg.version}`,
|
||||
`%c 🍀 ${pkg.name} v${pkg.version}`,
|
||||
'font-size:20px;border-left: 4px solid #13987f;background: #cef9ec;font-family: Comic Sans MS, cursive;color:#581845;padding:10px;border-radius:4px;',
|
||||
`${pkg.author.url}`
|
||||
)
|
||||
|
@ -2,22 +2,35 @@
|
||||
<main class="login-box rounded-8px size-full select-none">
|
||||
<ActionBar :shrink="false" :max-w="false" />
|
||||
|
||||
<n-flex vertical align="center" justify="center" :size="30" class="size-full">
|
||||
<img class="w-220px h-100px" src="@/assets/logo/hula.png" alt="" />
|
||||
<n-flex vertical align="center" :size="20" class="size-full pt-100px">
|
||||
<div @mousemove="handleMouseMove" @mouseleave="handleMouseLeave" class="box">
|
||||
<div id="computer" class="computer">
|
||||
<img class="w-224px h-158px relative" src="@/assets/img/win.png" alt="" />
|
||||
<div
|
||||
style="background: rgba(80, 80, 80, 0.1)"
|
||||
class="w-170px h-113px backdrop-blur-0 absolute top-9% left-51% transform -translate-x-51% -translate-y-9%"></div>
|
||||
<img
|
||||
class="drop-shadow-md absolute top-30% left-1/2 transform -translate-x-1/2 -translate-y-30% w-140px h-60px"
|
||||
src="@/assets/logo/hula.png"
|
||||
alt="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<n-flex vertical align="center" :size="20">
|
||||
<span class="text-(15px #707070)">版本:{{ _pkg.version }}({{ osArch }})</span>
|
||||
<span class="text-(15px #707070)">版本:v{{ _pkg.version }}({{ osArch }})</span>
|
||||
<span class="text-(15px #707070)">当前设备:{{ osType }}{{ osVersion }}</span>
|
||||
<n-flex vertical class="text-(12px #909090)" :size="8" align="center">
|
||||
<span>Copyright © 2023-2024 nongyehong</span>
|
||||
<span>Copyright © {{ currentYear - 1 }}-{{ currentYear }} nongyehong</span>
|
||||
<span>All Rights Reserved.</span>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import pkg from '~/package.json'
|
||||
import dayjs from 'dayjs'
|
||||
import { type, arch, version } from '@tauri-apps/plugin-os'
|
||||
|
||||
const _pkg = reactive({
|
||||
@ -26,6 +39,36 @@ const _pkg = reactive({
|
||||
const osType = ref()
|
||||
const osArch = ref()
|
||||
const osVersion = ref()
|
||||
// 使用day.js获取当前年份
|
||||
const currentYear = dayjs().year()
|
||||
|
||||
const element = ref<HTMLElement | null>(null)
|
||||
/** 鼠标移动时,对元素进行旋转的指数 */
|
||||
const multiple = 20
|
||||
|
||||
const transformElement = (x: number, y: number) => {
|
||||
if (element.value) {
|
||||
const box = element.value.getBoundingClientRect()
|
||||
const calcX = -(y - box.y - box.height / 2) / multiple
|
||||
const calcY = (x - box.x - box.width / 2) / multiple
|
||||
|
||||
element.value.style.transform = `rotateX(${calcX}deg) rotateY(${calcY}deg)`
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
window.requestAnimationFrame(() => {
|
||||
transformElement(event.clientX, event.clientY)
|
||||
})
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
window.requestAnimationFrame(() => {
|
||||
if (element.value) {
|
||||
element.value.style.transform = 'rotateX(0) rotateY(0)'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
osType.value = type()
|
||||
@ -36,6 +79,7 @@ onMounted(() => {
|
||||
let build_number = Number(parts[2])
|
||||
osVersion.value = build_number > 22000 ? '11' : '10'
|
||||
}
|
||||
element.value = document.getElementById('computer')
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -52,4 +96,20 @@ onMounted(() => {
|
||||
color: #404040;
|
||||
}
|
||||
}
|
||||
|
||||
.box {
|
||||
width: 240px;
|
||||
height: 200px;
|
||||
transform-style: preserve-3d;
|
||||
perspective: 500px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
.computer {
|
||||
position: relative;
|
||||
transition: all 0.2s;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -24,7 +24,7 @@
|
||||
</n-flex>
|
||||
|
||||
<!-- 系统设置 -->
|
||||
<n-flex vertical class="text-(14px [--text-color])" :size="16">
|
||||
<n-flex v-if="type() === 'windows'" vertical class="text-(14px [--text-color])" :size="16">
|
||||
<span class="pl-10px">系统</span>
|
||||
|
||||
<n-flex class="item" :size="15" vertical>
|
||||
@ -50,7 +50,7 @@
|
||||
<span v-if="type() === 'windows'" class="w-full h-1px bg-[--line-color]"></span>
|
||||
|
||||
<!-- ESC关闭面板 -->
|
||||
<n-flex align="center" justify="space-between">
|
||||
<n-flex v-if="type() === 'windows'" align="center" justify="space-between">
|
||||
<span>是否启用ESC关闭窗口</span>
|
||||
|
||||
<n-switch size="small" v-model:value="escClose" />
|
||||
|
@ -2,9 +2,7 @@
|
||||
<!-- 主体内容 -->
|
||||
<!-- // TODO 考虑是否需要添加一个欢迎页面,而不是直接使用聊天窗口 (nyh -> 2024-07-01 10:44:14)-->
|
||||
<main>
|
||||
<div
|
||||
style="box-shadow: var(--shadow-enabled) 4px 4px var(--box-shadow-color)"
|
||||
class="flex truncate p-[8px_20px_14px_20px] justify-between items-center gap-50px">
|
||||
<div class="flex truncate p-[8px_20px_14px_20px] justify-between items-center gap-50px">
|
||||
<n-flex :size="10" vertical class="truncate">
|
||||
<p
|
||||
v-if="!isEdit"
|
||||
@ -40,7 +38,10 @@
|
||||
<div class="h-1px bg-[--line-color]"></div>
|
||||
|
||||
<!-- 聊天信息框 -->
|
||||
<div class="w-full p-[28px_16px] box-border" style="height: calc(100vh - 300px)">
|
||||
<div
|
||||
:class="{ 'shadow-inner': page.shadow }"
|
||||
class="w-full p-[28px_16px] box-border"
|
||||
style="height: calc(100vh - 300px)">
|
||||
<n-flex :size="6">
|
||||
<n-avatar
|
||||
class="rounded-8px"
|
||||
@ -60,11 +61,7 @@
|
||||
|
||||
<div class="h-1px bg-[--line-color]"></div>
|
||||
<!-- 下半部分输入框以及功能栏 -->
|
||||
<n-flex
|
||||
vertical
|
||||
:size="6"
|
||||
style="box-shadow: var(--shadow-enabled) -4px 4px 0 rgba(0, 0, 0, 0.05)"
|
||||
class="size-full p-[14px_22px] box-border">
|
||||
<n-flex vertical :size="6" class="size-full p-[14px_22px] box-border">
|
||||
<n-flex :size="26" class="options">
|
||||
<n-popover v-for="(item, index) in features" :key="index" trigger="hover" :show-arrow="false" placement="top">
|
||||
<template #trigger>
|
||||
@ -84,7 +81,11 @@
|
||||
import MsgInput from '@/components/rightBox/MsgInput.vue'
|
||||
import Mitt from '@/utils/Bus.ts'
|
||||
import { InputInst, NIcon } from 'naive-ui'
|
||||
import { setting } from '@/stores/setting.ts'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const settingStore = setting()
|
||||
const { page } = storeToRefs(settingStore)
|
||||
/** 是否是编辑模式 */
|
||||
const isEdit = ref(false)
|
||||
const inputInstRef = ref<InputInst | null>(null)
|
||||
@ -159,6 +160,7 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
.options {
|
||||
padding-left: 4px;
|
||||
svg {
|
||||
@apply size-24px cursor-pointer outline-none;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ type ChatConfig = {
|
||||
export const content: ChatConfig = {
|
||||
system: [
|
||||
{
|
||||
title: `当前版本:${pkg.version}`,
|
||||
title: `当前版本:v${pkg.version}`,
|
||||
description: '已是最新版本',
|
||||
features: <Button title={'检查更新'} icon={'refresh'} />
|
||||
},
|
||||
|
@ -88,7 +88,7 @@ const handleLoginSuccess = async (e: any) => {
|
||||
scanStatus.value.show = true
|
||||
loadText.value = '登录中...'
|
||||
delay(async () => {
|
||||
await createWebviewWindow('HuLa', 'home', 960, 720, 'login', false)
|
||||
await createWebviewWindow('HuLa', 'home', 960, 720, 'login', true)
|
||||
settingStore.setAccountInfo({
|
||||
avatar: e.avatar,
|
||||
name: e.name,
|
||||
|
@ -38,10 +38,9 @@ export default defineConfig(({ mode }: ConfigEnv) => {
|
||||
/**! 启动时候打印项目信息(不需要可关闭) */
|
||||
atStartup(config, mode),
|
||||
/**
|
||||
* !实验性功能
|
||||
* 开启defineProps解构语法
|
||||
* vue3.5.0已支持解构并具有响应式
|
||||
* */
|
||||
vue({ script: { propsDestructure: true } }),
|
||||
vue(),
|
||||
vueJsx(), // 开启jsx功能
|
||||
unocss(), // 开启unocss
|
||||
AutoImport({
|
||||
|