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: { toolStore: {
title: 'Tool Store', title: 'Tool Store',
createFromToolStore: 'Create from Tool Store', createFromToolStore: 'Create from Tool Store',
internal: 'Built in system',
recommend: 'Recommended', recommend: 'Recommended',
webSearch: 'Web Search', webSearch: 'Web Search',
databaseQuery: 'Database Query', databaseQuery: 'Database Query',

View File

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

View File

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

View File

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

View File

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