增加相关功能

This commit is contained in:
2026-09-03 17:54:49 +08:00
parent 44a03186da
commit d8936d9730
30 changed files with 4503 additions and 2412 deletions
@@ -0,0 +1,268 @@
<template>
<div class="chat-sidebar">
<div class="sidebar-header">
<el-button type="primary" class="new-chat-btn" :icon="ChatDotRound" @click="handleNewChat">
新建对话
</el-button>
<el-button :icon="Setting" class="settings-btn" @click="settingsVisible = true" title="AI接入设置" />
</div>
<div class="session-list" v-loading="loading">
<div
v-for="item in list"
:key="item.id"
class="session-item"
:class="{ active: currentId === item.id }"
@click="handleSelect(item)"
>
<el-icon class="session-icon"><ChatDotRound /></el-icon>
<template v-if="editingId === item.id">
<el-input
v-model="editTitle"
size="small"
class="rename-input"
@click.stop
@keyup.enter="saveRename(item)"
@keyup.esc="cancelRename"
@blur="saveRename(item)"
ref="renameInputRef"
/>
</template>
<span v-else class="session-title" :title="item.title">{{ item.title }}</span>
<div class="session-actions">
<el-button
link
type="primary"
class="action-btn"
:icon="Edit"
@click.stop="startRename(item)"
title="重命名"
/>
<el-button
link
type="danger"
class="action-btn"
:icon="Delete"
@click.stop="handleDelete(item)"
title="删除"
/>
</div>
</div>
<el-empty v-if="!loading && list.length === 0" description="暂无对话" :image-size="60" />
</div>
<ProviderDialog v-model="settingsVisible" @change="handleProviderChange" />
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ChatDotRound, Setting, Delete, Edit } from '@element-plus/icons-vue'
import { getChatSessionList, createChatSession, deleteChatSession, updateChatSession } from '@/api/ai'
import ProviderDialog from './providerDialog.vue'
const emit = defineEmits(['select', 'new', 'rename'])
const loading = ref(false)
const list = ref([])
const currentId = ref(null)
const settingsVisible = ref(false)
// 重命名
const editingId = ref(null)
const editTitle = ref('')
const renameInputRef = ref()
onMounted(() => {
fetchList()
})
async function fetchList() {
loading.value = true
try {
const res = await getChatSessionList()
if (res.code === 200) {
list.value = res.data.list || []
}
} catch (e) {
ElMessage.error(e.message || '查询失败')
} finally {
loading.value = false
}
}
function handleSelect(item) {
currentId.value = item.id
emit('select', item)
}
async function handleNewChat() {
try {
const res = await createChatSession({ title: '新对话' })
if (res.code === 200) {
const newSession = { id: res.data.id, title: res.data.title }
list.value.unshift(newSession)
currentId.value = newSession.id
emit('new', newSession)
}
} catch (e) {
ElMessage.error(e.message || '创建失败')
}
}
function startRename(item) {
editingId.value = item.id
editTitle.value = item.title
nextTick(() => {
if (renameInputRef.value) {
const input = renameInputRef.value[0] || renameInputRef.value
if (input && input.focus) input.focus()
if (input && input.select) input.select()
}
})
}
function cancelRename() {
editingId.value = null
editTitle.value = ''
}
async function saveRename(item) {
if (editingId.value !== item.id) return
const newTitle = editTitle.value.trim()
if (!newTitle) {
ElMessage.warning('名称不能为空')
return
}
if (newTitle === item.title) {
cancelRename()
return
}
try {
await updateChatSession(item.id, { title: newTitle })
item.title = newTitle
ElMessage.success('重命名成功')
// 如果是当前会话,通知父组件更新标题
if (currentId.value === item.id) {
emit('rename', { id: item.id, title: newTitle })
}
} catch (e) {
ElMessage.error(e.message || '重命名失败')
} finally {
cancelRename()
}
}
async function handleDelete(item) {
try {
await ElMessageBox.confirm(`确定要删除对话「${item.title}」吗?`, '提示', { type: 'warning' })
await deleteChatSession(item.id)
ElMessage.success('删除成功')
list.value = list.value.filter(s => s.id !== item.id)
if (currentId.value === item.id) {
currentId.value = null
emit('select', null)
}
} catch (e) {
if (e !== 'cancel') {
ElMessage.error(e.message || '删除失败')
}
}
}
function handleProviderChange() {
// 配置变更后不需要特殊处理
}
defineExpose({
fetchList,
setCurrent: (id) => { currentId.value = id }
})
</script>
<style lang="less" scoped>
.chat-sidebar {
display: flex;
flex-direction: column;
height: 100%;
background: var(--el-fill-color-light);
border-right: 1px solid var(--el-border-color-lighter);
}
.sidebar-header {
display: flex;
gap: 8px;
padding: 16px;
border-bottom: 1px solid var(--el-border-color-lighter);
.new-chat-btn {
flex: 1;
}
.settings-btn {
width: 36px;
padding: 0;
}
}
.session-list {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.session-item {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
border-radius: 6px;
cursor: pointer;
margin-bottom: 4px;
transition: background 0.2s;
&:hover {
background: var(--el-fill-color);
}
&.active {
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
.session-icon {
flex-shrink: 0;
font-size: 16px;
}
.session-title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
}
.rename-input {
flex: 1;
}
.session-actions {
display: flex;
gap: 2px;
flex-shrink: 0;
.action-btn {
padding: 0;
opacity: 0.6;
transition: opacity 0.2s;
&:hover {
opacity: 1;
}
}
}
}
</style>