feat: 新增开发规划
This commit is contained in:
@@ -1,42 +1,59 @@
|
||||
<template>
|
||||
<aside class="w-50 h-full box-border flex flex-col">
|
||||
<div class="flex items-center m-2">
|
||||
<img class="w-10 h-10 rounded-md" src="@assets/images/login/white_logo.png" />
|
||||
<div class="font-bold text-gray-80">YINIAN</div>
|
||||
<RiSideBarLine class="ml-auto cursor-pointer" />
|
||||
<aside :class="['h-full box-border flex flex-col transition-all duration-300', sidebarCollapsed ? 'w-16' : 'w-50']">
|
||||
<div class="flex items-center justify-center m-2">
|
||||
<img v-if="!sidebarCollapsed" class="w-10 h-10 rounded-md" src="@assets/images/login/white_logo.png" />
|
||||
<div v-if="!sidebarCollapsed" class="font-bold text-gray-80">YINIAN</div>
|
||||
<RiSidebarFoldLine
|
||||
v-if="!sidebarCollapsed"
|
||||
class="ml-auto cursor-pointer hover:text-[#2B7FFF]"
|
||||
@click="sidebarCollapsed = true"
|
||||
/>
|
||||
<RiSideBarLine
|
||||
v-else
|
||||
class="cursor-pointer hover:text-[#2B7FFF]"
|
||||
@click="sidebarCollapsed = false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex justify-center m-2 bg-white rounded-lg p-2.5 border-[#E5E8EE] shadow-sm text-center cursor-pointer hover:bg-[#F5F7FA] hover:text-[#2B7FFF] hover:border-[#2B7FFF]"
|
||||
class="flex items-center justify-center m-2 bg-white rounded-lg p-2.5 border-[#E5E8EE] shadow-sm text-center cursor-pointer hover:bg-[#F5F7FA] hover:text-[#2B7FFF] hover:border-[#2B7FFF]"
|
||||
@click="addNewChat">
|
||||
<RiAddLine /> 新对话
|
||||
<RiAddLine />
|
||||
<span v-if="!sidebarCollapsed" class="whitespace-nowrap">新对话</span>
|
||||
</div>
|
||||
|
||||
<div class="overflow-y-auto p-2 ">
|
||||
<ul class="list-none">
|
||||
<li v-for="item in groups" :key="item.conversationId" @click="selectedHistoryMessage(item.conversationId)"
|
||||
:class="[
|
||||
'flex items-center gap-2 p-2 text-gray-600 rounded-lg cursor-pointer transition-colors',
|
||||
item.conversationId === selectedConversationId ? 'bg-white shadow-sm border-[#E5E8EE] py-1.5 relative z-10' : 'hover:bg-gray-200'
|
||||
]">
|
||||
<span class="w-2 h-2 rounded-full bg-[#BEDBFF] flex-none"></span>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="truncate text-sm">{{ item.conversationTitle }}</div>
|
||||
<div v-if="!sidebarCollapsed" class="overflow-y-auto p-2">
|
||||
<template v-for="bucket in sessionBuckets">
|
||||
<div v-if="bucket.sessions.length > 0" :key="bucket.key" class="mb-2 last:mb-0">
|
||||
<div class="px-2 pb-1 text-[11px] font-medium text-gray-400 tracking-tight">
|
||||
{{ bucket.label }}
|
||||
</div>
|
||||
<ul class="list-none">
|
||||
<li v-for="item in bucket.sessions" :key="item.conversationId" @click="selectedHistoryMessage(item.conversationId)"
|
||||
:class="[
|
||||
'flex items-center gap-2 p-2 text-gray-600 rounded-lg cursor-pointer transition-colors',
|
||||
item.conversationId === selectedConversationId ? 'bg-white shadow-sm border-[#E5E8EE] py-1.5 relative z-10' : 'hover:bg-gray-200'
|
||||
]">
|
||||
<span class="w-2 h-2 rounded-full bg-[#BEDBFF] flex-none"></span>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="truncate text-sm">{{ item.conversationTitle }}</div>
|
||||
</div>
|
||||
|
||||
<el-dropdown v-if="item.conversationId === selectedConversationId" placement="bottom-end">
|
||||
<el-icon class="el-icon--right">
|
||||
...
|
||||
</el-icon>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="renameHistoryMessage(item.conversationId)">重命名</el-dropdown-item>
|
||||
<el-dropdown-item @click="deleteHistoryMessage(item.conversationId)">删除</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</li>
|
||||
</ul>
|
||||
<el-dropdown v-if="item.conversationId === selectedConversationId" placement="bottom-end">
|
||||
<el-icon class="el-icon--right">
|
||||
...
|
||||
</el-icon>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="renameHistoryMessage(item.conversationId)">重命名</el-dropdown-item>
|
||||
<el-dropdown-item @click="deleteHistoryMessage(item.conversationId)">删除</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -69,18 +86,46 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, defineEmits } from 'vue'
|
||||
import { RiSideBarLine, RiArrowRightSLine, RiArrowDownSLine } from '@remixicon/vue'
|
||||
import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
|
||||
import { RiSideBarLine, RiSidebarFoldLine, RiArrowDownSLine, RiAddLine } from '@remixicon/vue'
|
||||
import { getSessionList, deleteSession, updateSession } from '../../api/SessionsApi';
|
||||
|
||||
type SessionBucketKey =
|
||||
| 'today'
|
||||
| 'yesterday'
|
||||
| 'withinWeek'
|
||||
| 'withinTwoWeeks'
|
||||
| 'withinMonth'
|
||||
| 'older';
|
||||
|
||||
function getSessionBucket(activityMs: number, nowMs: number): SessionBucketKey {
|
||||
if (!activityMs || activityMs <= 0) return 'older';
|
||||
const now = new Date(nowMs);
|
||||
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
|
||||
const startOfYesterday = startOfToday - 24 * 60 * 60 * 1000;
|
||||
|
||||
if (activityMs >= startOfToday) return 'today';
|
||||
if (activityMs >= startOfYesterday) return 'yesterday';
|
||||
|
||||
const daysAgo = (startOfToday - activityMs) / (24 * 60 * 60 * 1000);
|
||||
if (daysAgo <= 7) return 'withinWeek';
|
||||
if (daysAgo <= 14) return 'withinTwoWeeks';
|
||||
if (daysAgo <= 30) return 'withinMonth';
|
||||
return 'older';
|
||||
}
|
||||
|
||||
const sidebarCollapsed = ref(false)
|
||||
const deleteDialogVisible = ref(false)
|
||||
const renameDialogFormVisible = ref(false)
|
||||
const newMessageName = ref('')
|
||||
const formLabelWidth = '100px'
|
||||
const nowMs = ref(Date.now())
|
||||
let timer: number | undefined
|
||||
|
||||
interface HistoryMessage {
|
||||
conversationId: string;
|
||||
conversationTitle: string;
|
||||
updatedAt?: number;
|
||||
}
|
||||
|
||||
/// 记录选择的历史消息ID
|
||||
@@ -89,6 +134,27 @@ const selectedConversationId = ref<string>('')
|
||||
/// 历史消息分组数据
|
||||
const groups = ref<Array<HistoryMessage>>([])
|
||||
|
||||
const sessionBuckets = computed(() => {
|
||||
const buckets: Array<{ key: SessionBucketKey; label: string; sessions: HistoryMessage[] }> = [
|
||||
{ key: 'today', label: '今天', sessions: [] },
|
||||
{ key: 'yesterday', label: '昨天', sessions: [] },
|
||||
{ key: 'withinWeek', label: '近7天', sessions: [] },
|
||||
{ key: 'withinTwoWeeks', label: '近14天', sessions: [] },
|
||||
{ key: 'withinMonth', label: '近30天', sessions: [] },
|
||||
{ key: 'older', label: '更早', sessions: [] },
|
||||
];
|
||||
const map = {} as Record<SessionBucketKey, typeof buckets[number]>;
|
||||
for (const b of buckets) {
|
||||
map[b.key] = b;
|
||||
}
|
||||
|
||||
for (const session of [...groups.value].sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0))) {
|
||||
const bucketKey = getSessionBucket(session.updatedAt || 0, nowMs.value);
|
||||
map[bucketKey].sessions.push(session);
|
||||
}
|
||||
return buckets;
|
||||
});
|
||||
|
||||
/// 定义事件
|
||||
const emit = defineEmits(['new-chat', 'select-chat'])
|
||||
|
||||
@@ -153,6 +219,13 @@ const submitDelete = async () => {
|
||||
/// 页面加载时获取历史会话列表
|
||||
onMounted(() => {
|
||||
getHistoryConversationList()
|
||||
timer = window.setInterval(() => {
|
||||
nowMs.value = Date.now()
|
||||
}, 60 * 1000)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (timer) clearInterval(timer)
|
||||
})
|
||||
|
||||
/// 获取历史会话列表
|
||||
@@ -162,7 +235,8 @@ const getHistoryConversationList = async () => {
|
||||
// 使用整体赋值替换 push,避免重复累加
|
||||
groups.value = list.sessions.map((item: any) => ({
|
||||
conversationId: item.session_id,
|
||||
conversationTitle: item.title
|
||||
conversationTitle: item.title,
|
||||
updatedAt: item.updated_at ? new Date(item.updated_at).getTime() : Date.now(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user