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>
<!--引入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>
<body>

View File

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

View File

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

View File

@ -15,8 +15,9 @@ const { createWebviewWindow } = useWindow()
* @param badge
* @param tip
* @param size
* @param window
*/
const itemsTop = ref<OPT.L.Top[]>([
const itemsTop = ref<OPT.L.Common[]>([
{
url: 'message',
icon: 'message',
@ -31,46 +32,75 @@ const itemsTop = ref<OPT.L.Top[]>([
url: 'dynamic',
icon: 'fire',
title: '动态',
iconAction: 'fire-action',
iconAction: 'fire-action2',
size: {
width: 840,
height: 800
},
window: {
isDrag: true,
resizable: false
}
},
{
url: 'robot',
icon: 'Robot',
icon: 'robot',
title: 'GPT',
iconAction: 'Robot',
iconAction: 'robot-action',
tip: '机器人新功能在开发中',
size: {
width: 980,
height: 800
},
window: {
isDrag: false,
resizable: true
}
}
])
/** 下半部分操作栏配置 */
const itemsBottom: OPT.L.Bottom[] = [
const itemsBottom: OPT.L.Common[] = [
{
title: '邮件',
url: '/mail',
label: 'mail',
url: 'mail',
icon: 'mail',
iconAction: 'mail-action'
iconAction: 'mail-action2',
size: {
width: 840,
height: 600
},
window: {
isDrag: false,
resizable: true
}
},
{
title: '文件管理器',
url: '/folder',
label: 'mail',
url: 'mail',
icon: 'file',
iconAction: 'file-action'
iconAction: 'file-action2',
size: {
width: 840,
height: 600
},
window: {
isDrag: false,
resizable: true
}
},
{
title: '收藏',
url: '/collection',
label: 'mail',
url: 'mail',
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 settingShow = ref(false)
const shrinkStatus = ref(false)
const isNewWindows = ref(['dynamic', 'robot'])
const isNewWindows = ref(['dynamic', 'robot', 'mail'])
/** 是否展示个人信息浮窗 */
const infoShow = ref(false)
/** 是否显示上半部分操作栏中的提示 */
@ -153,16 +153,30 @@ export const leftHook = () => {
}
/**
*
*
* @param url
* @param title
* @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数组中的值如果是则创建新的窗口
if (isNewWindows.value.includes(url)) {
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)
} else {
activeUrl.value = url

View File

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

View File

@ -34,19 +34,23 @@
</n-flex>
<!-- 会话列表 -->
<n-scrollbar ref="scrollbar" style="max-height: calc(100vh - 266px); padding-right: 8px">
<n-flex vertical :size="10" style="padding: 4px">
<TransitionGroup name="list" tag="div" class="flex flex-col-center gap-12px">
<VueDraggable v-model="chatList" :animation="150" target=".sort-target">
<TransitionGroup name="list" tag="div" style="padding: 4px" class="sort-target flex flex-col-center gap-12px">
<n-flex
vertical
:size="12"
v-for="item in chatList"
:key="item.id"
@click="activeItem = item.id"
:class="{ 'outline outline-2 outline-#13987f outline-offset-1': activeItem === item.id }"
@click="handleActive(item)"
:class="{ 'outline-dashed outline-2 outline-#13987f outline-offset-1': activeItem === item.id }"
class="chat-item">
<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">
<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
@click.stop="deleteChat(item)"
class="color-[--chat-text-color] size-20px opacity-0 absolute right-0px top-4px">
@ -60,7 +64,7 @@
</div>
</n-flex>
</TransitionGroup>
</n-flex>
</VueDraggable>
</n-scrollbar>
</n-flex>
@ -86,6 +90,8 @@
import { setting } from '@/stores/setting.ts'
import { storeToRefs } from 'pinia'
import { NIcon, VirtualListInst } from 'naive-ui'
import Mitt from '@/utils/Bus.ts'
import { VueDraggable } from 'vue-draggable-plus'
const settingStore = setting()
const { login } = storeToRefs(settingStore)
@ -94,29 +100,36 @@ const scrollbar = ref<VirtualListInst>()
const chatList = ref([
{
id: 1,
name: '新的聊天1',
title: '新的聊天1',
time: '2022-01-01 12:00:00'
},
{
id: 2,
name: '新的聊天2',
title: '新的聊天2',
time: '2022-01-01 12:00:00'
},
{
id: 3,
name: '新的聊天3',
title: '新的聊天3',
time: '2022-01-01 12:00:00'
},
{
id: 4,
name: '新的聊天4',
title: '新的聊天4',
time: '2022-01-01 12:00:00'
}
])
const handleActive = (item: any) => {
activeItem.value = item.id
nextTick(() => {
Mitt.emit('chat-active', item)
})
}
const add = () => {
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(() => {
scrollbar.value?.scrollTo({ position: 'bottom', debounce: true })
@ -124,20 +137,48 @@ const add = () => {
}
const deleteChat = (item: any) => {
// keyitems
const index = chatList.value.indexOf(item)
if (index > -1) {
chatList.value.splice(index, 1)
window.$message.success(`已删除 ${item.name}`, {
icon: () => h(NIcon, null, { default: () => h('svg', null, [h('use', { href: '#face' })]) })
})
//
if (chatList.value.length === 0) {
nextTick(() => {
add()
//
if (index !== -1) {
const removeItem = chatList.value.splice(index, 1)[0]
if (activeItem.value === removeItem.id) {
if (index < chatList.value.length) {
// 使keyactiveItem.value
activeItem.value = chatList.value[index].id
handleActive(chatList.value[index])
} 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>
<style scoped lang="scss">
.gpt-subtitle {

View File

@ -1,30 +1,42 @@
<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
class="w-full"
:max-w="false"
:shrink="false"
:current-label="appWindow.label"
:top-win-label="appWindow.label" />
<ActionBar class="w-full" :shrink="false" :current-label="appWindow.label" :top-win-label="appWindow.label" />
<!-- 主体内容 -->
<main class="flex-1">
<n-flex justify="space-between" align="center" :size="0" class="p-[14px_20px]">
<n-flex :size="10" vertical>
<p class="text-(20px [--chat-text-color]) font-bold hover:underline cursor-pointer">新的聊天</p>
<main>
<div class="flex flex-1 truncate p-[14px_20px] justify-between items-center gap-50px">
<n-flex :size="10" vertical class="truncate">
<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>
</n-flex>
<n-flex>
<div class="right-btn">
<svg class="size-18px cursor-pointer"><use href="#edit"></use></svg>
<n-flex class="min-w-fit">
<div class="right-btn" @click="handleEdit">
<svg><use href="#edit"></use></svg>
</div>
<div class="right-btn">
<svg class="size-18px cursor-pointer"><use href="#Sharing"></use></svg>
<svg><use href="#Sharing"></use></svg>
</div>
</n-flex>
</n-flex>
</div>
<div class="h-1px bg-[--line-color]"></div>
<!-- 聊天信息框 -->
@ -69,8 +81,18 @@
import { appWindow } from '@tauri-apps/api/window'
import { useWindowState } from '@/hooks/useWindowState.ts'
import MsgInput from '@/components/rightBox/MsgInput.vue'
import Mitt from '@/utils/Bus.ts'
import { InputInst } from 'naive-ui'
useWindowState(appWindow.label)
/** 是否是编辑模式 */
const isEdit = ref(false)
const inputInstRef = ref<InputInst | null>(null)
/** 当前聊天的标题和id */
const currentChat = ref({
id: 0,
title: ''
})
const features = ref([
{
icon: 'SmilingFace',
@ -85,16 +107,42 @@ const features = ref([
label: '增强插件'
},
{
icon: 'Robot',
icon: 'robot-action',
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>
<style lang="scss" scoped>
@import '@/styles/scss/chat-main';
.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 {
svg {