From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目
---
src/views/ai/chat/index/components/conversation/ConversationList.vue | 391 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 391 insertions(+), 0 deletions(-)
diff --git a/src/views/ai/chat/index/components/conversation/ConversationList.vue b/src/views/ai/chat/index/components/conversation/ConversationList.vue
new file mode 100644
index 0000000..f73fb27
--- /dev/null
+++ b/src/views/ai/chat/index/components/conversation/ConversationList.vue
@@ -0,0 +1,391 @@
+<!-- AI 瀵硅瘽 -->
+<template>
+ <el-aside
+ width="260px"
+ class="h-100% relative flex flex-col justify-between px-2.5 pt-2.5 pb-0 overflow-hidden"
+ >
+ <!-- 宸﹂《閮細瀵硅瘽 -->
+ <div class="h-100%">
+ <el-button class="w-1/1 py-4.5" type="primary" @click="createConversation">
+ <Icon icon="ep:plus" class="mr-5px" />
+ 鏂板缓瀵硅瘽
+ </el-button>
+
+ <!-- 宸﹂《閮細鎼滅储瀵硅瘽 -->
+ <el-input
+ v-model="searchName"
+ size="large"
+ class="mt-5"
+ placeholder="鎼滅储鍘嗗彶璁板綍"
+ @keyup="searchConversation"
+ >
+ <template #prefix>
+ <Icon icon="ep:search" />
+ </template>
+ </el-input>
+
+ <!-- 宸︿腑闂达細瀵硅瘽鍒楄〃 -->
+ <div class="overflow-auto h-full">
+ <!-- 鎯呭喌涓�锛氬姞杞戒腑 -->
+ <el-empty v-if="loading" description="." :v-loading="loading" />
+ <!-- 鎯呭喌浜岋細鎸夌収 group 鍒嗙粍锛屽睍绀鸿亰澶╀細璇� list 鍒楄〃 -->
+ <div v-for="conversationKey in Object.keys(conversationMap)" :key="conversationKey">
+ <div class="mt-1.25 pt-2.5" v-if="conversationMap[conversationKey].length">
+ <el-text class="mx-1" size="small" tag="b">
+ {{ conversationKey }}
+ </el-text>
+ </div>
+ <div
+ class="mt-1.25"
+ v-for="conversation in conversationMap[conversationKey]"
+ :key="conversation.id"
+ @click="handleConversationClick(conversation.id)"
+ @mouseover="hoverConversationId = conversation.id"
+ @mouseout="hoverConversationId = ''"
+ >
+ <div
+ class="flex flex-row justify-between flex-1 px-1.25 cursor-pointer rounded-1.25 items-center leading-7.5"
+ :style="
+ conversation.id === activeConversationId
+ ? 'background-color: var(--el-color-primary-light-9); border: 1px solid var(--el-color-primary-light-7);'
+ : ''
+ "
+ >
+ <div class="flex flex-row items-center">
+ <img
+ class="w-6.25 h-6.25 rounded-1.25 flex flex-row justify-center"
+ :src="conversation.roleAvatar || roleAvatarDefaultImg"
+ />
+ <span
+ class="py-0.5 px-2.5"
+ style="
+ max-width: 220px;
+ font-size: 14px;
+ font-weight: 400;
+ color: var(--el-text-color-regular);
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ "
+ >
+ {{ conversation.title }}
+ </span>
+ </div>
+ <div
+ class="right-0.5 flex flex-row justify-center"
+ style="color: var(--el-text-color-regular)"
+ v-show="hoverConversationId === conversation.id"
+ >
+ <el-button class="m-0" link @click.stop="handleTop(conversation)">
+ <el-icon title="缃《" v-if="!conversation.pinned"><Top /></el-icon>
+ <el-icon title="缃《" v-if="conversation.pinned"><Bottom /></el-icon>
+ </el-button>
+ <el-button class="m-0" link @click.stop="updateConversationTitle(conversation)">
+ <el-icon title="缂栬緫">
+ <Icon icon="ep:edit" />
+ </el-icon>
+ </el-button>
+ <el-button class="m-0" link @click.stop="deleteChatConversation(conversation)">
+ <el-icon title="鍒犻櫎瀵硅瘽">
+ <Icon icon="ep:delete" />
+ </el-icon>
+ </el-button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <!-- 搴曢儴鍗犱綅 -->
+ <div class="h-160px w-100%"></div>
+ </div>
+ </div>
+
+ <!-- 宸﹀簳閮細宸ュ叿鏍� -->
+ <div
+ class="absolute bottom-0 left-0 right-0 px-5 leading-8.75 flex justify-between items-center"
+ style="
+ background-color: var(--el-fill-color-extra-light);
+ box-shadow: 0 0 1px 1px var(--el-border-color-lighter);
+ color: var(--el-text-color);
+ "
+ >
+ <div
+ class="flex items-center p-0 m-0 cursor-pointer"
+ style="color: var(--el-text-color-regular)"
+ @click="handleRoleRepository"
+ >
+ <Icon icon="ep:user" />
+ <el-text class="ml-1.25" size="small">瑙掕壊浠撳簱</el-text>
+ </div>
+ <div
+ class="flex items-center p-0 m-0 cursor-pointer"
+ style="color: var(--el-text-color-regular)"
+ @click="handleClearConversation"
+ >
+ <Icon icon="ep:delete" />
+ <el-text class="ml-1.25" size="small">娓呯┖鏈疆椤跺璇�</el-text>
+ </div>
+ </div>
+
+ <!-- 瑙掕壊浠撳簱鎶藉眽 -->
+ <el-drawer v-model="roleRepositoryOpen" title="瑙掕壊浠撳簱" size="754px">
+ <RoleRepository />
+ </el-drawer>
+ </el-aside>
+</template>
+
+<script setup lang="ts">
+import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
+import RoleRepository from '../role/RoleRepository.vue'
+import { Bottom, Top } from '@element-plus/icons-vue'
+import roleAvatarDefaultImg from '@/assets/ai/gpt.svg'
+
+const message = useMessage() // 娑堟伅寮圭獥
+
+// 瀹氫箟灞炴��
+const searchName = ref<string>('') // 瀵硅瘽鎼滅储
+const activeConversationId = ref<number | null>(null) // 閫変腑鐨勫璇濓紝榛樿涓� null
+const hoverConversationId = ref<number | null>(null) // 鎮诞涓婂幓鐨勫璇�
+const conversationList = ref([] as ChatConversationVO[]) // 瀵硅瘽鍒楄〃
+const conversationMap = ref<any>({}) // 瀵硅瘽鍒嗙粍 (缃《銆佷粖澶┿�佷笁澶╁墠銆佷竴鏄熸湡鍓嶃�佷竴涓湀鍓�)
+const loading = ref<boolean>(false) // 鍔犺浇涓�
+const loadingTime = ref<any>() // 鍔犺浇涓畾鏃跺櫒
+
+// 瀹氫箟缁勪欢 props
+const props = defineProps({
+ activeId: {
+ type: String || null,
+ required: true
+ }
+})
+
+// 瀹氫箟閽╁瓙
+const emits = defineEmits([
+ 'onConversationCreate',
+ 'onConversationClick',
+ 'onConversationClear',
+ 'onConversationDelete'
+])
+
+/** 鎼滅储瀵硅瘽 */
+const searchConversation = async (e) => {
+ // 鎭㈠鏁版嵁
+ if (!searchName.value.trim().length) {
+ conversationMap.value = await getConversationGroupByCreateTime(conversationList.value)
+ } else {
+ // 杩囨护
+ const filterValues = conversationList.value.filter((item) => {
+ return item.title.includes(searchName.value.trim())
+ })
+ conversationMap.value = await getConversationGroupByCreateTime(filterValues)
+ }
+}
+
+/** 鐐瑰嚮瀵硅瘽 */
+const handleConversationClick = async (id: number) => {
+ // 杩囨护鍑洪�変腑鐨勫璇�
+ const filterConversation = conversationList.value.filter((item) => {
+ return item.id === id
+ })
+ // 鍥炶皟 onConversationClick
+ // noinspection JSVoidFunctionReturnValueUsed
+ const success = emits('onConversationClick', filterConversation[0])
+ // 鍒囨崲瀵硅瘽
+ if (success) {
+ activeConversationId.value = id
+ }
+}
+
+/** 鑾峰彇瀵硅瘽鍒楄〃 */
+const getChatConversationList = async () => {
+ try {
+ // 鍔犺浇涓�
+ loadingTime.value = setTimeout(() => {
+ loading.value = true
+ }, 50)
+
+ // 1.1 鑾峰彇 瀵硅瘽鏁版嵁
+ conversationList.value = await ChatConversationApi.getChatConversationMyList()
+ // 1.2 鎺掑簭
+ conversationList.value.sort((a, b) => {
+ return b.createTime - a.createTime
+ })
+ // 1.3 娌℃湁浠讳綍瀵硅瘽鎯呭喌
+ if (conversationList.value.length === 0) {
+ activeConversationId.value = null
+ conversationMap.value = {}
+ return
+ }
+
+ // 2. 瀵硅瘽鏍规嵁鏃堕棿鍒嗙粍(缃《銆佷粖澶┿�佷竴澶╁墠銆佷笁澶╁墠銆佷竷澶╁墠銆�30 澶╁墠)
+ conversationMap.value = await getConversationGroupByCreateTime(conversationList.value)
+ } finally {
+ // 娓呯悊瀹氭椂鍣�
+ if (loadingTime.value) {
+ clearTimeout(loadingTime.value)
+ }
+ // 鍔犺浇瀹屾垚
+ loading.value = false
+ }
+}
+
+/** 鎸夌収 creteTime 鍒涘缓鏃堕棿锛岃繘琛屽垎缁� */
+const getConversationGroupByCreateTime = async (list: ChatConversationVO[]) => {
+ // 鎺掑簭銆佹寚瀹氥�佹椂闂村垎缁�(浠婂ぉ銆佷竴澶╁墠銆佷笁澶╁墠銆佷竷澶╁墠銆�30澶╁墠)
+ // noinspection NonAsciiCharacters
+ const groupMap = {
+ 缃《: [] as ChatConversationVO[],
+ 浠婂ぉ: [] as ChatConversationVO[],
+ 涓�澶╁墠: [] as ChatConversationVO[],
+ 涓夊ぉ鍓�: [] as ChatConversationVO[],
+ 涓冨ぉ鍓�: [] as ChatConversationVO[],
+ 涓夊崄澶╁墠: [] as ChatConversationVO[]
+ }
+ // 褰撳墠鏃堕棿鐨勬椂闂存埑
+ const now = Date.now()
+ // 瀹氫箟鏃堕棿闂撮殧甯搁噺锛堝崟浣嶏細姣锛�
+ const oneDay = 24 * 60 * 60 * 1000
+ const threeDays = 3 * oneDay
+ const sevenDays = 7 * oneDay
+ const thirtyDays = 30 * oneDay
+ for (const conversation of list) {
+ // 缃《
+ if (conversation.pinned) {
+ groupMap['缃《'].push(conversation)
+ continue
+ }
+ // 璁$畻鏃堕棿宸紙鍗曚綅锛氭绉掞級
+ const diff = now - conversation.createTime
+ // 鏍规嵁鏃堕棿闂撮殧鍒ゆ柇
+ if (diff < oneDay) {
+ groupMap['浠婂ぉ'].push(conversation)
+ } else if (diff < threeDays) {
+ groupMap['涓�澶╁墠'].push(conversation)
+ } else if (diff < sevenDays) {
+ groupMap['涓夊ぉ鍓�'].push(conversation)
+ } else if (diff < thirtyDays) {
+ groupMap['涓冨ぉ鍓�'].push(conversation)
+ } else {
+ groupMap['涓夊崄澶╁墠'].push(conversation)
+ }
+ }
+ return groupMap
+}
+
+/** 鏂板缓瀵硅瘽 */
+const createConversation = async () => {
+ // 1. 鏂板缓瀵硅瘽
+ const conversationId = await ChatConversationApi.createChatConversationMy(
+ {} as unknown as ChatConversationVO
+ )
+ // 2. 鑾峰彇瀵硅瘽鍐呭
+ await getChatConversationList()
+ // 3. 閫変腑瀵硅瘽
+ await handleConversationClick(conversationId)
+ // 4. 鍥炶皟
+ emits('onConversationCreate')
+}
+
+/** 淇敼瀵硅瘽鐨勬爣棰� */
+const updateConversationTitle = async (conversation: ChatConversationVO) => {
+ // 1. 浜屾纭
+ const { value } = await ElMessageBox.prompt('淇敼鏍囬', {
+ inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 鍒ゆ柇闈炵┖锛屼笖闈炵┖鏍�
+ inputErrorMessage: '鏍囬涓嶈兘涓虹┖',
+ inputValue: conversation.title
+ })
+ // 2. 鍙戣捣淇敼
+ await ChatConversationApi.updateChatConversationMy({
+ id: conversation.id,
+ title: value
+ } as ChatConversationVO)
+ message.success('閲嶅懡鍚嶆垚鍔�')
+ // 3. 鍒锋柊鍒楄〃
+ await getChatConversationList()
+ // 4. 杩囨护褰撳墠鍒囨崲鐨�
+ const filterConversationList = conversationList.value.filter((item) => {
+ return item.id === conversation.id
+ })
+ if (filterConversationList.length > 0) {
+ // tip锛氶伩鍏嶅垏鎹㈠璇�
+ if (activeConversationId.value === filterConversationList[0].id) {
+ emits('onConversationClick', filterConversationList[0])
+ }
+ }
+}
+
+/** 鍒犻櫎鑱婂ぉ瀵硅瘽 */
+const deleteChatConversation = async (conversation: ChatConversationVO) => {
+ try {
+ // 鍒犻櫎鐨勪簩娆$‘璁�
+ await message.delConfirm(`鏄惁纭鍒犻櫎瀵硅瘽 - ${conversation.title}?`)
+ // 鍙戣捣鍒犻櫎
+ await ChatConversationApi.deleteChatConversationMy(conversation.id)
+ message.success('瀵硅瘽宸插垹闄�')
+ // 鍒锋柊鍒楄〃
+ await getChatConversationList()
+ // 鍥炶皟
+ emits('onConversationDelete', conversation)
+ } catch {}
+}
+
+/** 娓呯┖瀵硅瘽 */
+const handleClearConversation = async () => {
+ try {
+ await message.confirm('纭鍚庡璇濅細鍏ㄩ儴娓呯┖锛岀疆椤剁殑瀵硅瘽闄ゅ銆�')
+ await ChatConversationApi.deleteChatConversationMyByUnpinned()
+ ElMessage({
+ message: '鎿嶄綔鎴愬姛!',
+ type: 'success'
+ })
+ // 娓呯┖ 瀵硅瘽 鍜� 瀵硅瘽鍐呭
+ activeConversationId.value = null
+ // 鑾峰彇 瀵硅瘽鍒楄〃
+ await getChatConversationList()
+ // 鍥炶皟 鏂规硶
+ emits('onConversationClear')
+ } catch {}
+}
+
+/** 瀵硅瘽缃《 */
+const handleTop = async (conversation: ChatConversationVO) => {
+ // 鏇存柊瀵硅瘽缃《
+ conversation.pinned = !conversation.pinned
+ await ChatConversationApi.updateChatConversationMy(conversation)
+ // 鍒锋柊瀵硅瘽
+ await getChatConversationList()
+}
+
+// ============ 瑙掕壊浠撳簱 ============
+
+/** 瑙掕壊浠撳簱鎶藉眽 */
+const roleRepositoryOpen = ref<boolean>(false) // 瑙掕壊浠撳簱鏄惁鎵撳紑
+const handleRoleRepository = async () => {
+ roleRepositoryOpen.value = !roleRepositoryOpen.value
+}
+
+/** 鐩戝惉閫変腑鐨勫璇� */
+const { activeId } = toRefs(props)
+watch(activeId, async (newValue, oldValue) => {
+ activeConversationId.value = newValue as string
+})
+
+// 瀹氫箟 public 鏂规硶
+defineExpose({ createConversation })
+
+/** 鍒濆鍖� */
+onMounted(async () => {
+ // 鑾峰彇 瀵硅瘽鍒楄〃
+ await getChatConversationList()
+ // 榛樿閫変腑
+ if (props.activeId) {
+ activeConversationId.value = props.activeId
+ } else {
+ // 棣栨榛樿閫変腑绗竴涓�
+ if (conversationList.value.length) {
+ activeConversationId.value = conversationList.value[0].id
+ // 鍥炶皟 onConversationClick
+ await emits('onConversationClick', conversationList.value[0])
+ }
+ }
+})
+</script>
--
Gitblit v1.8.0