style(component): 💄 修改GPT页面以及主页侧边栏样式

This commit is contained in:
nongyehong 2024-06-27 23:31:09 +08:00
parent 7260840f4b
commit 5d9794cdb6
8 changed files with 249 additions and 75 deletions

View File

@ -7,7 +7,7 @@
<title>HuLa</title> <title>HuLa</title>
<!--引入iconpark图标库--> <!--引入iconpark图标库-->
<script defer src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_30895_89.d0c512eb4cbfd31bcce2de352ff8d502.js"></script> <script defer src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_30895_93.eea70ae0cbccb2d5c48884bf0db72efb.js"></script>
</head> </head>
<body> <body>

View File

@ -97,6 +97,7 @@ const userList = computed(() => {
}) })
}) })
const isGroup = computed(() => globalStore.currentSession?.type === RoomTypeEnum.GROUP) const isGroup = computed(() => globalStore.currentSession?.type === RoomTypeEnum.GROUP)
/** 是否是搜索模式 */
const isSearch = ref(false) const isSearch = ref(false)
const searchRef = ref('') const searchRef = ref('')
/** 手动触发Popover显示 */ /** 手动触发Popover显示 */

View File

@ -9,8 +9,21 @@
{ active: activeUrl === item.url && item.url !== 'dynamic' }, { active: activeUrl === item.url && item.url !== 'dynamic' },
openWindowsList.has(item.url) ? 'p-[6px_8px] color-#13987f' : 'top-action' openWindowsList.has(item.url) ? 'p-[6px_8px] color-#13987f' : 'top-action'
]" ]"
@click="pageJumps(item.url, item.title, item.size)"> @click="pageJumps(item.url, item.title, item.size, item.window)">
<n-popover style="padding: 12px" v-if="item.tip" trigger="manual" v-model:show="tipShow" placement="right"> <!-- 已经打开窗口时展示 -->
<n-popover :show-arrow="false" v-if="openWindowsList.has(item.url)" trigger="hover" placement="right">
<template #trigger>
<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>
</svg>
</n-badge>
</template>
<p>{{ item.title }} 已打开</p>
</n-popover>
<!-- 该选项有提示时展示 -->
<n-popover style="padding: 12px" v-else-if="item.tip" trigger="manual" v-model:show="tipShow" placement="right">
<template #trigger> <template #trigger>
<n-badge :max="99" :value="item.badge"> <n-badge :max="99" :value="item.badge">
<svg class="size-22px" @click="tipShow = false"> <svg class="size-22px" @click="tipShow = false">
@ -24,6 +37,7 @@
<svg @click="tipShow = false" class="size-12px cursor-pointer"><use href="#close"></use></svg> <svg @click="tipShow = false" class="size-12px cursor-pointer"><use href="#close"></use></svg>
</n-flex> </n-flex>
</n-popover> </n-popover>
<!-- 该选项无提示时展示 -->
<n-badge v-else :max="99" :value="item.badge"> <n-badge v-else :max="99" :value="item.badge">
<svg class="size-22px"> <svg class="size-22px">
<use <use
@ -38,11 +52,42 @@
<div <div
v-for="(item, index) in itemsBottom" v-for="(item, index) in itemsBottom"
:key="index" :key="index"
:class="openWindowsList.has(item.url.substring(1)) ? 'p-[6px_8px] color-#13987f' : 'bottom-action'" :class="openWindowsList.has(item.url) ? 'p-[6px_8px] color-#13987f' : 'bottom-action'"
@click="openContent(item.title, item.label)"> @click="pageJumps(item.url, item.title, item.size, item.window)">
<svg class="size-22px"> <!-- 已经打开窗口时展示 -->
<use :href="`#${openWindowsList.has(item.url.substring(1)) ? item.iconAction : item.icon}`"></use> <n-popover :show-arrow="false" v-if="openWindowsList.has(item.url)" trigger="hover" placement="right">
</svg> <template #trigger>
<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>
</svg>
</n-badge>
</template>
<p>{{ item.title }} 已打开</p>
</n-popover>
<!-- 该选项有提示时展示 -->
<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">
<svg class="size-22px" @click="tipShow = false">
<use
:href="`#${activeUrl === item.url || openWindowsList.has(item.url) ? item.iconAction : item.icon}`"></use>
</svg>
</n-badge>
</template>
<n-flex align="center" justify="space-between">
<p class="select-none">{{ item.tip }}</p>
<svg @click="tipShow = false" class="size-12px cursor-pointer"><use href="#close"></use></svg>
</n-flex>
</n-popover>
<!-- 该选项无提示时展示 -->
<n-badge v-else :max="99" :value="item.badge">
<svg class="size-22px">
<use
:href="`#${activeUrl === item.url || openWindowsList.has(item.url) ? item.iconAction : item.icon}`"></use>
</svg>
</n-badge>
</div> </div>
<svg <svg
@ -70,7 +115,7 @@
import { itemsBottom, itemsTop, moreList } from '../config.ts' import { itemsBottom, itemsTop, moreList } from '../config.ts'
import { leftHook } from '../hook.ts' import { leftHook } from '../hook.ts'
const { activeUrl, openWindowsList, settingShow, tipShow, openContent, pageJumps } = leftHook() const { activeUrl, openWindowsList, settingShow, tipShow, pageJumps } = leftHook()
onMounted(() => { onMounted(() => {
/** 十秒后关闭提示 */ /** 十秒后关闭提示 */

View File

@ -15,8 +15,9 @@ const { createWebviewWindow } = useWindow()
* @param badge * @param badge
* @param tip * @param tip
* @param size * @param size
* @param window
*/ */
const itemsTop = ref<OPT.L.Top[]>([ const itemsTop = ref<OPT.L.Common[]>([
{ {
url: 'message', url: 'message',
icon: 'message', icon: 'message',
@ -31,46 +32,75 @@ const itemsTop = ref<OPT.L.Top[]>([
url: 'dynamic', url: 'dynamic',
icon: 'fire', icon: 'fire',
title: '动态', title: '动态',
iconAction: 'fire-action', iconAction: 'fire-action2',
size: { size: {
width: 840, width: 840,
height: 800 height: 800
},
window: {
isDrag: true,
resizable: false
} }
}, },
{ {
url: 'robot', url: 'robot',
icon: 'Robot', icon: 'robot',
title: 'GPT', title: 'GPT',
iconAction: 'Robot', iconAction: 'robot-action',
tip: '机器人新功能在开发中', tip: '机器人新功能在开发中',
size: { size: {
width: 980, width: 980,
height: 800 height: 800
},
window: {
isDrag: false,
resizable: true
} }
} }
]) ])
/** 下半部分操作栏配置 */ /** 下半部分操作栏配置 */
const itemsBottom: OPT.L.Bottom[] = [ const itemsBottom: OPT.L.Common[] = [
{ {
title: '邮件', title: '邮件',
url: '/mail', url: 'mail',
label: 'mail',
icon: 'mail', icon: 'mail',
iconAction: 'mail-action' iconAction: 'mail-action2',
size: {
width: 840,
height: 600
},
window: {
isDrag: false,
resizable: true
}
}, },
{ {
title: '文件管理器', title: '文件管理器',
url: '/folder', url: 'mail',
label: 'mail',
icon: 'file', icon: 'file',
iconAction: 'file-action' iconAction: 'file-action2',
size: {
width: 840,
height: 600
},
window: {
isDrag: false,
resizable: true
}
}, },
{ {
title: '收藏', title: '收藏',
url: '/collection', url: 'mail',
label: 'mail',
icon: 'collect', icon: 'collect',
iconAction: 'collect-action' iconAction: 'heart',
size: {
width: 840,
height: 600
},
window: {
isDrag: false,
resizable: true
}
} }
] ]
/** 设置列表菜单项 */ /** 设置列表菜单项 */

View File

@ -31,7 +31,7 @@ export const leftHook = () => {
const activeUrl = ref<string>(itemsTop.value[0].url) const activeUrl = ref<string>(itemsTop.value[0].url)
const settingShow = ref(false) const settingShow = ref(false)
const shrinkStatus = ref(false) const shrinkStatus = ref(false)
const isNewWindows = ref(['dynamic', 'robot']) const isNewWindows = ref(['dynamic', 'robot', 'mail'])
/** 是否展示个人信息浮窗 */ /** 是否展示个人信息浮窗 */
const infoShow = ref(false) const infoShow = ref(false)
/** 是否显示上半部分操作栏中的提示 */ /** 是否显示上半部分操作栏中的提示 */
@ -153,16 +153,30 @@ export const leftHook = () => {
} }
/** /**
* *
* @param url * @param url
* @param title * @param title
* @param size * @param size
* @param window
* */ * */
const pageJumps = (url: string, title?: string, size?: { width: number; height: number }) => { const pageJumps = (
url: string,
title?: string,
size?: { width: number; height: number },
window?: { isDrag: boolean; resizable: boolean }
) => {
// 判断url是否等于isNewWindows.value数组中的值如果是则创建新的窗口 // 判断url是否等于isNewWindows.value数组中的值如果是则创建新的窗口
if (isNewWindows.value.includes(url)) { if (isNewWindows.value.includes(url)) {
delay(async () => { delay(async () => {
await createWebviewWindow(title!, url, <number>size?.width, <number>size?.height) await createWebviewWindow(
title!,
url,
<number>size?.width,
<number>size?.height,
'',
window?.isDrag,
window?.resizable
)
}, 300) }, 300)
} else { } else {
activeUrl.value = url activeUrl.value = url

View File

@ -3,7 +3,7 @@ declare namespace OPT {
/** 主页左侧选项 */ /** 主页左侧选项 */
namespace L { namespace L {
/** 顶部的选项 */ /** 顶部的选项 */
type Top = { type Common = {
url: string url: string
icon: string icon: string
title?: string title?: string
@ -14,15 +14,10 @@ declare namespace OPT {
width: number width: number
height: number height: number
} }
} window?: {
isDrag: boolean
/** 底部的选项 */ resizable: boolean
type Bottom = { }
title: string
url: string
label: string
icon: string
iconAction?: string
} }
/** 更多的选项 */ /** 更多的选项 */

View File

@ -34,19 +34,23 @@
</n-flex> </n-flex>
<!-- 会话列表 --> <!-- 会话列表 -->
<n-scrollbar ref="scrollbar" style="max-height: calc(100vh - 266px); padding-right: 8px"> <n-scrollbar ref="scrollbar" style="max-height: calc(100vh - 266px); padding-right: 8px">
<n-flex vertical :size="10" style="padding: 4px"> <VueDraggable v-model="chatList" :animation="150" target=".sort-target">
<TransitionGroup name="list" tag="div" class="flex flex-col-center gap-12px"> <TransitionGroup name="list" tag="div" style="padding: 4px" class="sort-target flex flex-col-center gap-12px">
<n-flex <n-flex
vertical vertical
:size="12" :size="12"
v-for="item in chatList" v-for="item in chatList"
:key="item.id" :key="item.id"
@click="activeItem = item.id" @click="handleActive(item)"
:class="{ 'outline outline-2 outline-#13987f outline-offset-1': activeItem === item.id }" :class="{ 'outline-dashed outline-2 outline-#13987f outline-offset-1': activeItem === item.id }"
class="chat-item"> class="chat-item">
<div class="absolute flex flex-col gap-14px w-full p-[8px_14px] box-border"> <div class="absolute flex flex-col gap-14px w-full p-[8px_14px] box-border">
<n-flex justify="space-between" align="center" :size="0" class="leading-22px"> <n-flex justify="space-between" align="center" :size="0" class="leading-22px">
<p class="text-(14px [--chat-text-color]) font-semibold select-none">{{ item.name }}</p> <n-ellipsis
style="width: calc(100% - 20px)"
class="text-(14px [--chat-text-color]) truncate font-semibold select-none">
{{ item.title }}
</n-ellipsis>
<svg <svg
@click.stop="deleteChat(item)" @click.stop="deleteChat(item)"
class="color-[--chat-text-color] size-20px opacity-0 absolute right-0px top-4px"> class="color-[--chat-text-color] size-20px opacity-0 absolute right-0px top-4px">
@ -60,7 +64,7 @@
</div> </div>
</n-flex> </n-flex>
</TransitionGroup> </TransitionGroup>
</n-flex> </VueDraggable>
</n-scrollbar> </n-scrollbar>
</n-flex> </n-flex>
@ -86,6 +90,8 @@
import { setting } from '@/stores/setting.ts' import { setting } from '@/stores/setting.ts'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { NIcon, VirtualListInst } from 'naive-ui' import { NIcon, VirtualListInst } from 'naive-ui'
import Mitt from '@/utils/Bus.ts'
import { VueDraggable } from 'vue-draggable-plus'
const settingStore = setting() const settingStore = setting()
const { login } = storeToRefs(settingStore) const { login } = storeToRefs(settingStore)
@ -94,29 +100,36 @@ const scrollbar = ref<VirtualListInst>()
const chatList = ref([ const chatList = ref([
{ {
id: 1, id: 1,
name: '新的聊天1', title: '新的聊天1',
time: '2022-01-01 12:00:00' time: '2022-01-01 12:00:00'
}, },
{ {
id: 2, id: 2,
name: '新的聊天2', title: '新的聊天2',
time: '2022-01-01 12:00:00' time: '2022-01-01 12:00:00'
}, },
{ {
id: 3, id: 3,
name: '新的聊天3', title: '新的聊天3',
time: '2022-01-01 12:00:00' time: '2022-01-01 12:00:00'
}, },
{ {
id: 4, id: 4,
name: '新的聊天4', title: '新的聊天4',
time: '2022-01-01 12:00:00' time: '2022-01-01 12:00:00'
} }
]) ])
const handleActive = (item: any) => {
activeItem.value = item.id
nextTick(() => {
Mitt.emit('chat-active', item)
})
}
const add = () => { const add = () => {
const id = chatList.value.length + 1 const id = chatList.value.length + 1
chatList.value.push({ id: id, name: `新的聊天${id}`, time: '2022-01-01 12:00:00' }) chatList.value.push({ id: id, title: `新的聊天${id}`, time: '2022-01-01 12:00:00' })
// //
nextTick(() => { nextTick(() => {
scrollbar.value?.scrollTo({ position: 'bottom', debounce: true }) scrollbar.value?.scrollTo({ position: 'bottom', debounce: true })
@ -124,20 +137,48 @@ const add = () => {
} }
const deleteChat = (item: any) => { const deleteChat = (item: any) => {
// keyitems
const index = chatList.value.indexOf(item) const index = chatList.value.indexOf(item)
if (index > -1) { //
chatList.value.splice(index, 1) if (index !== -1) {
window.$message.success(`已删除 ${item.name}`, { const removeItem = chatList.value.splice(index, 1)[0]
icon: () => h(NIcon, null, { default: () => h('svg', null, [h('use', { href: '#face' })]) }) if (activeItem.value === removeItem.id) {
}) if (index < chatList.value.length) {
// // 使keyactiveItem.value
if (chatList.value.length === 0) { activeItem.value = chatList.value[index].id
nextTick(() => { handleActive(chatList.value[index])
add() } else {
//
if (chatList.value.length === 0) {
nextTick(() => {
add()
//
// TODO (nyh -> 2024-06-27 18:52:11)
activeItem.value = chatList.value[0].id
})
}
//
activeItem.value = chatList.value[chatList.value.length - 1].id
handleActive(chatList.value[chatList.value.length - 1])
}
window.$message.success(`已删除 ${item.title}`, {
icon: () => h(NIcon, null, { default: () => h('svg', null, [h('use', { href: '#face' })]) })
}) })
} }
} }
} }
onMounted(() => {
/** 默认选择第一个聊天内容 */
handleActive(chatList.value[0])
Mitt.on('update-chat-title', (e) => {
chatList.value.filter((item) => {
if (item.id === e.id) {
item.title = e.title
}
})
})
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.gpt-subtitle { .gpt-subtitle {

View File

@ -1,30 +1,42 @@
<template> <template>
<n-flex vertical :size="0" class="flex-1 shadow-md select-none text-[--text-color]"> <n-flex vertical :size="0" class="flex-1 truncate shadow-md select-none text-[--text-color]">
<!-- 右上角操作栏 --> <!-- 右上角操作栏 -->
<ActionBar <ActionBar class="w-full" :shrink="false" :current-label="appWindow.label" :top-win-label="appWindow.label" />
class="w-full"
:max-w="false"
:shrink="false"
:current-label="appWindow.label"
:top-win-label="appWindow.label" />
<!-- 主体内容 --> <!-- 主体内容 -->
<main class="flex-1"> <main>
<n-flex justify="space-between" align="center" :size="0" class="p-[14px_20px]"> <div class="flex flex-1 truncate p-[14px_20px] justify-between items-center gap-50px">
<n-flex :size="10" vertical> <n-flex :size="10" vertical class="truncate">
<p class="text-(20px [--chat-text-color]) font-bold hover:underline cursor-pointer">新的聊天</p> <p
v-if="!isEdit"
@click="handleEdit"
class="text-(22px [--chat-text-color]) truncate font-bold hover:underline cursor-pointer">
{{ currentChat.title }}
</p>
<n-input
v-else
@blur="handleBlur"
ref="inputInstRef"
v-model:value="currentChat.title"
clearable
placeholder="输入标题"
type="text"
size="tiny"
style="width: 200px"
class="h-22px lh-22px rounded-6px">
</n-input>
<p class="text-(14px #707070)">共0条对话</p> <p class="text-(14px #707070)">共0条对话</p>
</n-flex> </n-flex>
<n-flex> <n-flex class="min-w-fit">
<div class="right-btn"> <div class="right-btn" @click="handleEdit">
<svg class="size-18px cursor-pointer"><use href="#edit"></use></svg> <svg><use href="#edit"></use></svg>
</div> </div>
<div class="right-btn"> <div class="right-btn">
<svg class="size-18px cursor-pointer"><use href="#Sharing"></use></svg> <svg><use href="#Sharing"></use></svg>
</div> </div>
</n-flex> </n-flex>
</n-flex> </div>
<div class="h-1px bg-[--line-color]"></div> <div class="h-1px bg-[--line-color]"></div>
<!-- 聊天信息框 --> <!-- 聊天信息框 -->
@ -69,8 +81,18 @@
import { appWindow } from '@tauri-apps/api/window' import { appWindow } from '@tauri-apps/api/window'
import { useWindowState } from '@/hooks/useWindowState.ts' import { useWindowState } from '@/hooks/useWindowState.ts'
import MsgInput from '@/components/rightBox/MsgInput.vue' import MsgInput from '@/components/rightBox/MsgInput.vue'
import Mitt from '@/utils/Bus.ts'
import { InputInst } from 'naive-ui'
useWindowState(appWindow.label) useWindowState(appWindow.label)
/** 是否是编辑模式 */
const isEdit = ref(false)
const inputInstRef = ref<InputInst | null>(null)
/** 当前聊天的标题和id */
const currentChat = ref({
id: 0,
title: ''
})
const features = ref([ const features = ref([
{ {
icon: 'SmilingFace', icon: 'SmilingFace',
@ -85,16 +107,42 @@ const features = ref([
label: '增强插件' label: '增强插件'
}, },
{ {
icon: 'Robot', icon: 'robot-action',
label: 'GPT4' label: 'GPT4'
} }
]) ])
const handleBlur = () => {
isEdit.value = false
if (currentChat.value.title === '') {
currentChat.value.title = '新的聊天'
}
Mitt.emit('update-chat-title', { title: currentChat.value.title, id: currentChat.value.id })
}
const handleEdit = () => {
isEdit.value = true
nextTick(() => {
inputInstRef.value?.select()
})
}
onMounted(() => {
Mitt.on('chat-active', (e) => {
const { title, id } = e
currentChat.value.title = title
currentChat.value.id = id
})
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '@/styles/scss/chat-main'; @import '@/styles/scss/chat-main';
.right-btn { .right-btn {
@apply size-fit bg-[--chat-bt-color] color-[--chat-text-color] rounded-8px shadow-md p-[10px_11px]; @apply size-fit cursor-pointer bg-[--chat-bt-color] color-[--chat-text-color] rounded-8px shadow-md p-[10px_11px];
svg {
@apply size-18px;
}
} }
.options { .options {
svg { svg {