refactor: chat left menu

This commit is contained in:
teukkk 2025-06-24 18:15:26 +08:00
parent 089b6900ed
commit 0c54f5ba34
5 changed files with 232 additions and 95 deletions

View File

@ -9,6 +9,7 @@ export default {
toolStore: {
title: 'Tool Store',
createFromToolStore: 'Create from Tool Store',
internal: 'Built in system',
recommend: 'Recommended',
webSearch: 'Web Search',
databaseQuery: 'Database Query',

View File

@ -7,6 +7,7 @@ export default {
toolStore: {
title: '工具商店',
createFromToolStore: '从工具商店创建',
internal: '系统内置',
recommend: '推荐',
webSearch: '联网搜索',
databaseQuery: '数据库查询',

View File

@ -9,6 +9,7 @@ export default {
toolStore: {
title: '工具商店',
createFromToolStore: '從工具商店創建',
internal: '系统内置',
recommend: '推薦',
webSearch: '聯網搜索',
databaseQuery: '數據庫查詢',

View File

@ -4,15 +4,16 @@
:class="classObj"
v-loading="loading"
:style="{
'--el-color-primary': applicationDetail?.custom_theme?.theme_color,
'--el-color-primary-light-9': hexToRgba(applicationDetail?.custom_theme?.theme_color, 0.1),
'--el-color-primary': applicationDetail?.custom_theme?.theme_color,
'--el-color-primary-light-9': hexToRgba(applicationDetail?.custom_theme?.theme_color, 0.1),
}"
>
<div class="flex">
<div class="chat-pc__left border-r">
<div class="p-16-24 pb-0">
<div class="flex align-center mb-16">
<div class="flex">
<div class="flex h-full w-full">
<div class="chat-pc__left">
<el-menu class="w-full h-full" :default-active="currentChatId" :collapse="isPcCollapse" collapse-transition popper-class="chat-pc-popper">
<div style="padding: 16px 18px 0 18px;">
<div class="flex align-center mb-16">
<div class="flex">
<el-avatar
v-if="isAppIcon(applicationDetail?.icon)"
shape="square"
@ -23,70 +24,123 @@
</el-avatar>
<LogoIcon v-else height="28px" style="width: 28px; height: 28px; display: block" />
</div>
<h4>{{ applicationDetail?.name }}</h4>
<h4 v-show="!isPcCollapse">{{ applicationDetail?.name }}</h4>
</div>
<el-button class="add-button w-full primary" @click="newChat">
<el-button v-show="!isPcCollapse" class="add-button w-full primary" @click="newChat">
<AppIcon iconName="app-create-chat"></AppIcon>
<span class="ml-4">{{ $t('chat.createChat') }}</span>
</el-button>
<p class="mt-20 mb-8">{{ $t('chat.history') }}</p>
</div>
<div class="left-height pt-0">
<el-scrollbar>
<div class="p-8 pt-0">
<common-list
:style="{
'--el-color-primary': applicationDetail?.custom_theme?.theme_color,
'--el-color-primary-light-9': hexToRgba(
applicationDetail?.custom_theme?.theme_color,
0.1,
),
}"
:data="chatLogData"
class="mt-8"
v-loading="left_loading"
:defaultActive="currentChatId"
@click="clickListHandle"
@mouseenter="mouseenter"
@mouseleave="mouseId = ''"
>
<template #default="{ row }">
<div class="flex-between">
<span :title="row.abstract">
{{ row.abstract }}
</span>
<div @click.stop v-show="mouseId === row.id && row.id !== 'new'">
<el-dropdown trigger="click" :teleported="false">
<el-icon class="rotate-90 mt-4"><MoreFilled /></el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click.stop="editLogTitle(row)">
<el-icon><EditPen /></el-icon>
{{ $t('common.edit') }}
</el-dropdown-item>
<el-dropdown-item @click.stop="deleteLog(row)">
<el-icon><Delete /></el-icon>
{{ $t('common.delete') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<p v-show="!isPcCollapse" class="mt-20 mb-8">{{ $t('chat.history') }}</p>
</div>
<div v-show="!isPcCollapse" class="left-height pt-0">
<el-scrollbar>
<div class="p-8 pt-0">
<common-list
:style="{
'--el-color-primary': applicationDetail?.custom_theme?.theme_color,
'--el-color-primary-light-9': hexToRgba(
applicationDetail?.custom_theme?.theme_color,
0.1,
),
}"
:data="chatLogData"
class="mt-8"
v-loading="left_loading"
:defaultActive="currentChatId"
@click="clickListHandle"
@mouseenter="mouseenter"
@mouseleave="mouseId = ''"
>
<template #default="{ row }">
<div class="flex-between">
<span :title="row.abstract">
{{ row.abstract }}
</span>
<div @click.stop v-show="mouseId === row.id && row.id !== 'new'">
<el-dropdown trigger="click" :teleported="false">
<el-icon class="rotate-90 mt-4"><MoreFilled /></el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click.stop="editLogTitle(row)">
<el-icon><EditPen /></el-icon>
{{ $t('common.edit') }}
</el-dropdown-item>
<el-dropdown-item @click.stop="deleteLog(row)">
<el-icon><Delete /></el-icon>
{{ $t('common.delete') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</template>
</template>
<template #empty>
<div class="text-center">
<el-text type="info">{{ $t('chat.noHistory') }}</el-text>
<template #empty>
<div class="text-center">
<el-text type="info">{{ $t('chat.noHistory') }}</el-text>
</div>
</template>
</common-list>
</div>
<div v-if="chatLogData?.length" class="gradient-divider lighter mt-8">
<span>{{ $t('chat.only20history') }}</span>
</div>
</el-scrollbar>
</div>
<el-menu-item index="1" v-show="isPcCollapse" @click="newChat">
<AppIcon iconName="app-create-chat"></AppIcon>
<template #title>{{ $t('chat.createChat') }}</template>
</el-menu-item>
<el-sub-menu v-show="isPcCollapse" index="2">
<template #title>
<el-icon>
<location />
</el-icon>
</template>
<el-menu-item-group v-loading="left_loading">
<template #title><span>{{ $t('chat.history') }}</span></template>
<el-menu-item v-for="row in chatLogData" :index="row.id" :key="row.id" @click="clickListHandle(row)">
<div class="flex-between w-full lighter">
<span :title="row.abstract">
{{ row.abstract }}
</span>
<div @click.stop class="flex" v-show="mouseId === row.id && row.id !== 'new'">
<el-dropdown trigger="click" :teleported="false">
<el-icon class="rotate-90 mt-4">
<MoreFilled />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click.stop="editLogTitle(row)">
<el-icon>
<EditPen />
</el-icon>
{{ $t('common.edit') }}
</el-dropdown-item>
<el-dropdown-item @click.stop="deleteLog(row)">
<el-icon>
<Delete />
</el-icon>
{{ $t('common.delete') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
</common-list>
</div>
</el-menu-item>
</el-menu-item-group>
<div v-if="!chatLogData?.length" class="text-center">
<el-text type="info">{{ $t('chat.noHistory') }}</el-text>
</div>
<div v-if="chatLogData?.length" class="gradient-divider lighter mt-8">
<span>{{ $t('chat.only20history') }}</span>
</div>
</el-scrollbar>
</div>
</el-sub-menu>
</el-menu>
<el-button v-if="!common.isMobile()" class="pc-collapse" circle size="small" @click="isPcCollapse = !isPcCollapse">
<el-icon>
<component :is=" isPcCollapse ? 'Fold' : 'Expand'" />
</el-icon>
</el-button>
</div>
<div class="chat-pc__right">
<div class="mb-24 p-16-24 flex-between">
@ -150,7 +204,7 @@
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick, computed } from 'vue'
import { ref, onMounted, nextTick, computed, watch } from 'vue'
import { marked } from 'marked'
import { saveAs } from 'file-saver'
import chatAPI from '@/api/chat/chat'
@ -167,6 +221,12 @@ const { user, chatLog, common } = useStore()
const EditTitleDialogRef = ref()
const isCollapse = ref(false)
const isPcCollapse = ref(false)
watch(()=> common.device, () => {
if(common.isMobile()) {
isPcCollapse.value = false
}
})
const customStyle = computed(() => {
return {
@ -383,7 +443,7 @@ const init = () => {
// ) {
// getChatLog(applicationDetail.value.id)
// }
getChatLog(applicationDetail.value.id)
getChatLog(applicationDetail.value?.id)
}
onMounted(() => {
init()
@ -391,6 +451,7 @@ onMounted(() => {
</script>
<style lang="scss">
.chat-pc {
height: 100%;
overflow: hidden;
background: #eef1f4;
@ -408,11 +469,40 @@ onMounted(() => {
}
&__left {
background:
linear-gradient(187.61deg, rgba(235, 241, 255, 0.5) 39.6%, rgba(231, 249, 255, 0.5) 94.3%),
#eef1f4;
position: relative;
width: 280px;
.el-menu {
background:
linear-gradient(187.61deg, rgba(235, 241, 255, 0.5) 39.6%, rgba(231, 249, 255, 0.5) 94.3%),
#eef1f4;
&:not(.el-menu--collapse) {
width: 280px;
}
.el-menu-item:hover {
background: transparent;
}
&.el-menu--collapse {
.el-menu-item,.el-menu-tooltip__trigger,.el-sub-menu__title {
padding: 0;
}
.el-menu-item .el-menu-tooltip__trigger,.el-sub-menu__title {
position: static;
width: 40px;
height: 40px;
border-radius: 6px;
align-items: center;
justify-content: center;
margin: 0 auto;
}
.el-menu-item:hover .el-menu-tooltip__trigger,.el-sub-menu__title:hover {
background-color: #1F23291A;
}
}
}
.add-button {
border: 1px solid var(--el-color-primary);
@ -421,10 +511,17 @@ onMounted(() => {
.left-height {
height: calc(100vh - 140px);
}
.pc-collapse {
position: absolute;
top: 20px;
right: -12px;
box-shadow: 0px 5px 10px 0px #1F23291A;
}
}
&__right {
width: calc(100% - 280px);
flex: 1;
overflow: hidden;
position: relative;
box-sizing: border-box;
@ -464,6 +561,27 @@ onMounted(() => {
display: none;
}
}
.chat-pc-popper {
.el-menu-item-group__title {
padding-bottom: 16px;
font-weight: 500;
color: var(--app-text-color-secondary);
}
.el-menu-item {
border-radius: 6px;
height: 40px;
margin: 0 8px;
padding-left: 8px;
padding-right: 8px;
&:hover {
background-color: #1F23291A;
}
&.is-active {
background-color: #3370FF1A;
}
}
}
//
.mobile {
.chat-pc {
@ -489,6 +607,9 @@ onMounted(() => {
width: 100%;
z-index: 99;
height: calc(100vh);
.el-menu {
width: 100%;
}
}
}
.collapse {

View File

@ -2,10 +2,12 @@
<el-dialog v-model="dialogVisible" width="1000" append-to-body class="tool-store-dialog" align-center
:close-on-click-modal="false" :close-on-press-escape="false">
<template #header="{ titleId }">
<div class="flex-between mb-8">
<div class="dialog-header flex-between mb-8">
<h4 :id="titleId" class="medium">
{{ $t('views.tool.toolStore.title') }}
</h4>
<el-tag class="store-type default-tag">{{t('views.tool.toolStore.internal')}}</el-tag>
<div class="flex align-center" style="margin-right: 28px;">
<el-input v-model="searchValue" :placeholder="$t('common.search')" prefix-icon="Search" class="w-240 mr-8"
@ -81,11 +83,12 @@ const searchValue = ref('')
const folderId = ref('')
const categories = ref<ToolCategory[]>([
{
id: 'recommend',
title: t('views.tool.toolStore.recommend'),
tools: []
},
//
// {
// id: 'recommend',
// title: t('views.tool.toolStore.recommend'),
// tools: []
// },
{
id: 'web_search',
title: t('views.tool.toolStore.webSearch'),
@ -96,21 +99,21 @@ const categories = ref<ToolCategory[]>([
title: t('views.tool.toolStore.databaseQuery'),
tools: []
},
{
id: 'image',
title: t('views.tool.toolStore.image'),
tools: []
},
{
id: 'developer',
title: t('views.tool.toolStore.developer'),
tools: []
},
{
id: 'communication',
title: t('views.tool.toolStore.communication'),
tools: []
}
// {
// id: 'image',
// title: t('views.tool.toolStore.image'),
// tools: []
// },
// {
// id: 'developer',
// title: t('views.tool.toolStore.developer'),
// tools: []
// },
// {
// id: 'communication',
// title: t('views.tool.toolStore.communication'),
// tools: []
// }
])
const filterList = ref<any>(null)
@ -136,11 +139,11 @@ async function getList() {
} else {
filterList.value = null
categories.value.forEach(category => {
if (category.id === 'recommend') {
category.tools = res.data
} else {
// if (category.id === 'recommend') {
// category.tools = res.data
// } else {
category.tools = res.data.filter((tool: any) => tool.label === category.id)
}
// }
})
}
} catch (error) {
@ -186,6 +189,16 @@ defineExpose({ open })
.el-dialog__header {
padding: 12px 20px 4px 24px;
border-bottom: 1px solid var(--el-border-color-light);
.dialog-header {
position: relative;
.store-type {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
.layout-container__left {