mirror of
https://gitee.com/HuLaSpark/HuLa.git
synced 2024-11-29 18:28:30 +08:00
style(component): 💄 修改GPT页面以及主页侧边栏样式
This commit is contained in:
parent
7260840f4b
commit
5d9794cdb6
@ -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>
|
||||
|
@ -97,6 +97,7 @@ const userList = computed(() => {
|
||||
})
|
||||
})
|
||||
const isGroup = computed(() => globalStore.currentSession?.type === RoomTypeEnum.GROUP)
|
||||
/** 是否是搜索模式 */
|
||||
const isSearch = ref(false)
|
||||
const searchRef = ref('')
|
||||
/** 手动触发Popover显示 */
|
||||
|
@ -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(() => {
|
||||
/** 十秒后关闭提示 */
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
/** 设置列表菜单项 */
|
||||
|
@ -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
|
||||
|
15
src/typings/options.d.ts
vendored
15
src/typings/options.d.ts
vendored
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/** 更多的选项 */
|
||||
|
@ -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) => {
|
||||
// 根据key找到items中对应的下标
|
||||
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) {
|
||||
// 需要使用新的索引位置找到key更新activeItem.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 {
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user