|
|
<template> <div v-if="!isMobile" class="history-record-container" :class="{ collapsed: !isCollapsed, }" > <!-- 收起状态的展开按钮和图标 --> <div v-if="isCollapsed" class="collapsed-container"> <img class="collapsed-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/74430a4ebbb67aefc713bf694147fb2a.png" alt="icon" /> <img class="collapsed-toggle-btn" @click="openHistory" src="https://d31zlh4on95l9h.cloudfront.net/images/b2d784f8607ab65081f5289459581bfe.png" alt="icon" title="打开边栏" /> </div>
<div v-if="isCollapsed" class="collapsed-bottom-container"> <div class="collapsed-bottom-btn" @click="handleFeedbackClick" title="用户反馈" > <img class="collapsed-bottom-feedback" src="https://d31zlh4on95l9h.cloudfront.net/images/41d6e25c19466718d462bcee2f050140.png" alt="icon" /> </div> <div class="collapsed-bottom-btn" @click="handleAnnouncementClick" title="公告" > <img class="collapsed-bottom-announcement" src="https://d31zlh4on95l9h.cloudfront.net/images/c51c7fbb68671729801fb10d65bd7789.png" alt="icon" /> </div> </div>
<!-- 历史记录内容 --> <div class="history-content" v-if="!isCollapsed"> <div class="head-container"> <!-- 标题 --> <div class="history-actions"> <img src="/src/assets/img/homePage/logo.png" alt="Logo" class="logo-img" /> </div> <!-- 折叠/展开按钮 --> <img class="toggle-btn" @click="closeHistory" src="https://d31zlh4on95l9h.cloudfront.net/images/b2d784f8607ab65081f5289459581bfe.png" alt="icon" title="收起边栏" /> </div> <!-- 历史记录列表 --> <div class="history-list"> <!-- 空状态 --> <div v-if="historyRecords.length === 0" class="empty-state"> <div class="empty-icon"> <el-icon class="documentDelete"><DocumentDelete /></el-icon> </div> <p class="empty-text">暂无历史记录</p> </div>
<div v-else v-for="history in categoryHistory" :key="history.name"> <div class="categoryName"> {{ history.name }} </div> <div v-for="record in history.list" :key="record.id" class="history-item" :class="{ active: selectedRecordId === record.id }" > <div class="record-content" @click="selectRecord(record)"> <div class="record-img"> <img :src="marketList[record.stockMarket]" :alt="record.stockMarket" /> </div> <div class="record-msg"> <div class="record-text"> <span class="stock-name">{{ record.stockName || record.stockCode }}</span> <span class="stock-code">({{ record.stockCode }})</span> </div> <div class="record-time"> {{ moment(record.updatedTime).format("YYYY-MM-DD HH:mm:ss") }} </div> </div> </div> <div class="record-actions"> <el-popover class="box-item" placement="right-start" trigger="click" > <template #reference> <el-icon class="more-btn"><MoreFilled /></el-icon> </template> <div class="popover-content"> <div class="popover-item"> <img class="popover-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/9ad3617c94955bcb76e1b11db70bb80b.png" alt="" /> 数据更新时间:{{ moment(record.date).format("D/M/YYYY") }} </div> <div v-if="record.isTop" class="popover-item popover-btn" @click="changeTopStatus(record.isTop, record.id)" > <img class="popover-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/a458305d8275734cc96bf6cad29864bf.png" alt="" /> 取消置顶 </div> <div v-else class="popover-item popover-btn" @click="changeTopStatus(record.isTop, record.id)" > <img class="popover-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/a458305d8275734cc96bf6cad29864bf.png" alt="" /> 置顶 </div> <div class="popover-item popover-btn" @click="deleteRecord(record.id)" > <img class="popover-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/027718d41523375a69e9cac927601cf8.png" alt="" /> 删除 </div> </div> </el-popover> <!-- <button @click="openDetail(record)" title="更多" ></button> --> </div> </div> </div> </div>
<div class="bottom-container"> <div class="bottom-btn" @click="handleFeedbackClick" title="用户反馈"> <img class="bottom-feedback" src="https://d31zlh4on95l9h.cloudfront.net/images/41d6e25c19466718d462bcee2f050140.png" alt="icon" /> </div> <div class="bottom-btn" @click="handleAnnouncementClick" title="公告"> <img class="bottom-announcement" src="https://d31zlh4on95l9h.cloudfront.net/images/c51c7fbb68671729801fb10d65bd7789.png" alt="icon" /> </div> </div> </div> </div>
<div v-else class="mobile-history-record-container" :class="{ mobileCollapsed: !isCollapsed, }" > <!-- 历史记录内容 --> <div class="history-content" v-if="!isCollapsed"> <div class="mobile-head-container"> <!-- 折叠/展开按钮 --> <img class="mobile-toggle-btn" @click="closeHistory" src="https://d31zlh4on95l9h.cloudfront.net/images/37fe3d79a8a700f6c674c9f0e7af066b.png" alt="icon" /> <!-- 标题 --> <div class="mobile-history-actions"> <img src="/src/assets/img/homePage/logo.png" alt="Logo" class="logo-img" /> </div> </div> <!-- 历史记录列表 --> <div class="history-list"> <!-- 空状态 --> <div v-if="historyRecords.length === 0" class="empty-state"> <div class="empty-icon"> <el-icon class="documentDelete"><DocumentDelete /></el-icon> </div> <p class="empty-text">暂无历史记录</p> </div>
<div v-else v-for="history in categoryHistory" :key="history.name"> <div class="categoryName"> {{ history.name }} </div> <div v-for="record in history.list" :key="record.id" class="history-item" :class="{ active: selectedRecordId === record.id }" > <div class="record-content" @click="selectRecord(record)"> <div class="record-img"> <img :src="marketList[record.stockMarket]" :alt="record.stockMarket" /> </div> <div class="record-msg"> <div class="record-text">{{ record.stockCode }}</div> <div class="record-time"> {{ moment(record.updatedTime).format("YYYY-MM-DD HH:mm:ss") }} </div> </div> </div> <div class="record-actions"> <el-popover class="box-item" placement="right-start" trigger="click" > <template #reference> <el-icon class="more-btn"><MoreFilled /></el-icon> </template> <div class="popover-content"> <div class="popover-item"> <img class="popover-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/9ad3617c94955bcb76e1b11db70bb80b.png" alt="" /> 数据更新时间:{{ moment(record.date).format("D/M/YYYY") }} </div> <div v-if="record.isTop" class="popover-item popover-btn" @click="changeTopStatus(record.isTop, record.id)" > <img class="popover-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/a458305d8275734cc96bf6cad29864bf.png" alt="" /> 取消置顶 </div> <div v-else class="popover-item popover-btn" @click="changeTopStatus(record.isTop, record.id)" > <img class="popover-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/a458305d8275734cc96bf6cad29864bf.png" alt="" /> 置顶 </div> <div class="popover-item popover-btn" @click="deleteRecord(record.id)" > <img class="popover-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/027718d41523375a69e9cac927601cf8.png" alt="" /> 删除 </div> </div> </el-popover> <!-- <button @click="openDetail(record)" title="更多" ></button> --> </div> </div> </div> </div>
<div class="mobile-bottom-container"> <div class="mobile-bottom-btn" @click="handleFeedbackClick" title="用户反馈" > <img class="mobile-bottom-feedback" src="https://d31zlh4on95l9h.cloudfront.net/images/41d6e25c19466718d462bcee2f050140.png" alt="icon" /> <div class="mobile-bottom-text">用户反馈</div> </div> <div class="mobile-bottom-btn" @click="handleAnnouncementClick" title="公告" > <img class="mobile-bottom-announcement" src="https://d31zlh4on95l9h.cloudfront.net/images/c51c7fbb68671729801fb10d65bd7789.png" alt="icon" /> <div class="mobile-bottom-text">公告</div> </div> </div> </div> </div>
<el-dialog v-model="deleteDialogVisible" title="永久删除记录" width="500"> <span>删除后,该记录将不可恢复。确认删除吗?</span> <template #footer> <div class="dialog-footer"> <el-button @click="closeDeleteDialog()">取消</el-button> <el-button type="primary" @click="deleteRecordConfirm()"> 删除 </el-button> </div> </template> </el-dialog> </template>
<script setup> import { ref, computed, onMounted, watch } from "vue"; import { getHistoryListAPI, changeTopAPI, deleteRecordAPI, clickRecordAPI, } from "../../api/AIxiaocaishen"; import moment from "moment"; import { ElMessage } from "element-plus"; import { useChatStore } from "../../store/chat"; const chatStore = useChatStore(); import { useRouter } from "vue-router"; const router = useRouter();
// Props
const props = defineProps({ currentType: { type: String, default: "AIchat", // 'AIchat' 或 'AiEmotion'
}, isMobile: { type: Boolean, default: false, }, });
// Emits
const emit = defineEmits([ "selectRecord", "recordAdded", "startNewChat", "showAnnouncement", "showFeedback", ]);
// 响应式数据
const marketList = ref({ cn: "https://d31zlh4on95l9h.cloudfront.net/images/c685daa929d80a03c26841dfa783cc3c.png", usa: "https://d31zlh4on95l9h.cloudfront.net/images/bccbc3058f327f72aa158fa0852dce19.png", hk: "https://d31zlh4on95l9h.cloudfront.net/images/ab050afe6867e9f961561f665ed12d10.png", sg: "https://d31zlh4on95l9h.cloudfront.net/images/90c5ce1edef2235a100e3ee0ad3cac92.png", vi: "https://d31zlh4on95l9h.cloudfront.net/images/59404c85889abd57dfd15040099edc1a.png", th: "https://d31zlh4on95l9h.cloudfront.net/images/31f5433264cf1f84cf550995fa16d86e.png", can: "https://d31zlh4on95l9h.cloudfront.net/images/26382451bfa08e6a419a2190b799dae5.png", my: "https://d31zlh4on95l9h.cloudfront.net/images/7efa8487a1317ed17eacc77b58e0a26d.png", }); const isCollapsed = ref(true); const selectedRecordId = ref(null);
const delObj = ref({}); const deleteDialogVisible = ref(false);
const openDeleteDialog = () => { deleteDialogVisible.value = true; }; const closeDeleteDialog = () => { delObj.value = {}; deleteDialogVisible.value = false; };
const historyRecords = ref([]); const categoryHistory = ref([]); const getHistoryList = async (params) => { try { const result = await getHistoryListAPI(params); historyRecords.value = result.data; let remainingRecords = result.data; // 复制原数组
// 1. 筛选置顶记录
let topList = remainingRecords.filter((record) => record.isTop === 1); remainingRecords = remainingRecords.filter((record) => record.isTop !== 1);
// 2. 筛选今日记录
let todayList = remainingRecords.filter((record) => { const today = moment().format("YYYY-MM-DD"); const recordDate = moment(record.updatedTime).format("YYYY-MM-DD"); return recordDate === today; }); remainingRecords = remainingRecords.filter((record) => { const today = moment().format("YYYY-MM-DD"); const recordDate = moment(record.updatedTime).format("YYYY-MM-DD"); return recordDate !== today; });
// 3. 筛选近3日记录(不包括今日)
let recent3DaysList = remainingRecords.filter((record) => { const threeDaysAgo = moment().subtract(3, "days").startOf("day"); const yesterday = moment().subtract(1, "days").endOf("day"); const recordDate = moment(record.updatedTime); return recordDate.isAfter(threeDaysAgo) && recordDate.isBefore(yesterday); }); remainingRecords = remainingRecords.filter((record) => { const threeDaysAgo = moment().subtract(3, "days").startOf("day"); const yesterday = moment().subtract(1, "days").endOf("day"); const recordDate = moment(record.updatedTime); return !( recordDate.isAfter(threeDaysAgo) && recordDate.isBefore(yesterday) ); });
// 4. 筛选近7日记录(不包括今日和近3日)
let recent7DaysList = remainingRecords.filter((record) => { const sevenDaysAgo = moment().subtract(7, "days").startOf("day"); const recordDate = moment(record.updatedTime); return recordDate.isAfter(sevenDaysAgo); }); remainingRecords = remainingRecords.filter((record) => { const sevenDaysAgo = moment().subtract(7, "days").startOf("day"); const recordDate = moment(record.updatedTime); return !recordDate.isAfter(sevenDaysAgo); });
// 5. 筛选近30日记录(不包括前面已筛选的)
let recent30DaysList = remainingRecords.filter((record) => { const thirtyDaysAgo = moment().subtract(30, "days").startOf("day"); const recordDate = moment(record.updatedTime); return recordDate.isAfter(thirtyDaysAgo); }); remainingRecords = remainingRecords.filter((record) => { const thirtyDaysAgo = moment().subtract(30, "days").startOf("day"); const recordDate = moment(record.updatedTime); return !recordDate.isAfter(thirtyDaysAgo); });
historyRecords.value = result.data;
categoryHistory.value = [ { name: "置顶", list: topList, }, { name: "今日", list: todayList, }, { name: "近3日", list: recent3DaysList, }, { name: "近7日", list: recent7DaysList, }, { name: "近30日", list: recent30DaysList, }, ]; console.log("historyRecords", historyRecords.value); console.log("categoryHistory", categoryHistory.value); } catch (e) { console.error("获取历史记录出错", e); // 确保在出错时historyRecords和categoryHistory仍然是数组
historyRecords.value = []; categoryHistory.value = []; } };
const changeTop = async (param) => { try { await changeTopAPI(param); } catch (e) { console.error("置顶或取消置顶失败", e); } };
const changeTopStatus = async (isTop, id) => { try { if (isTop == 0 && categoryHistory.value[0].list.length >= 3) { console.log("超过置顶上线"); ElMessage.warning("最多置顶三条内容,已达上限!"); return; } await changeTop({ model: props.currentType == "AIchat" ? 1 : 2, recordId: id, isTop: isTop == 1 ? 0 : 1, }); await getHistoryList({ model: props.currentType == "AIchat" ? 1 : 2, token: localStorage.getItem("localToken"), }); } catch (error) { console.error("操作失败:", error); } };
// 方法
const toggleCollapse = () => { isCollapsed.value = !isCollapsed.value; // 保存折叠状态到本地存储
localStorage.setItem("historyRecordCollapsed", isCollapsed.value); };
const backToSelectModel = () => { router.push("/Selectmodel"); };
const openHistory = () => { // getHistoryList({
// model: props.currentType == "AIchat" ? 1 : 2,
// token: localStorage.getItem("localToken"),
// });
isCollapsed.value = false; };
const closeHistory = () => { isCollapsed.value = true; };
const openDetail = (record) => { console.log("record", record); };
const historyData = ref({}); const selectRecord = async (record) => { try { selectedRecordId.value = record.id; const result = await clickRecordAPI({ model: props.currentType == "AIchat" ? 1 : 2, parentId: record.parentId, recordId: record.id, }); if (result && result.data) { historyData.value = result.data; chatStore.dbqbClickRecord = historyData.value; // 构造股票数据对象,保持与现有结构一致
const stockData = { queryText: record.stockCode || record.stockName || '', // 使用记录中的股票代码或名称作为查询文本
stockInfo: { name: result.data.stockData?.stockName || record.stockName || '', code: record.stockCode || '', market: record.stockMarket || 'cn' }, apiData: result.data.stockData || {}, // 图表数据
conclusionData: result.data.wokeFlowData?.One || {}, // 场景应用的结论和音频
timestamp: new Date().toISOString() }; // 通过emit将数据传递给父组件
emit('selectRecord', stockData); console.log('历史记录数据已发送给父组件:', stockData); } else { console.error('历史记录数据格式不正确:', result); } } catch (e) { console.error("获取历史记录数据失败", e); } };
const deleteRecord = (id) => { delObj.value.id = id; openDeleteDialog(); };
const deleteRecordConfirm = async () => { try { const result = await deleteRecordAPI({ model: props.currentType == "AIchat" ? 1 : 2, recordId: delObj.value.id, }); console.log(result.msg); closeDeleteDialog(); await getHistoryList({ model: props.currentType == "AIchat" ? 1 : 2, token: localStorage.getItem("localToken"), }); } catch (e) { console.error("删除失败", e); } };
// 处理公告按钮点击
const handleAnnouncementClick = () => { emit("showAnnouncement"); };
// 处理用户反馈按钮点击
const handleFeedbackClick = () => { emit("showFeedback"); };
watch( () => chatStore.searchRecord, (newVal) => { if (chatStore.searchRecord) { getHistoryList({ model: props.currentType == "AIchat" ? 1 : 2, token: localStorage.getItem("localToken"), }); chatStore.searchRecord = false; } } );
// 暴露方法和状态给父组件
defineExpose({ isCollapsed, toggleCollapse, getHistoryList, });
// 生命周期
onMounted(() => { getHistoryList({ model: props.currentType == "AIchat" ? 1 : 2, token: localStorage.getItem("localToken"), }); }); </script>
<style scoped> .history-record-container { min-width: 40px; width: 3%; position: fixed; left: 0; top: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); border-right: 1px solid rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); z-index: 1000; transition: width 0.3s ease; display: flex; flex-direction: column; align-items: center; /* justify-content: center; */ }
.mobile-history-record-container { width: 0px; position: fixed; left: 0; top: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); border-right: 1px solid rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); z-index: 1000; transition: width 0.3s ease; display: flex; flex-direction: column; align-items: center; }
.collapsed { width: 300px; }
.mobileCollapsed { width: 80vw; }
.toggle-btn { width: 32px; height: 32px; transform: rotate(180deg); border-radius: 6px; color: white; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; z-index: 10; }
.mobile-toggle-btn { width: 10%; height: auto; border-radius: 6px; color: white; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; z-index: 10; }
.toggle-btn:hover { background: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.3); }
.collapsed-container { width: 100%; margin-top: 60px; display: flex; flex-direction: column; align-items: center; gap: 20px; z-index: 1000; }
.collapsed-icon { width: 80%; height: auto; object-fit: contain; }
.collapsed-toggle-btn { width: 80%; height: auto; border-radius: 6px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; color: white; }
.collapsed-toggle-btn:hover { background: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.3); }
.collapsed-bottom-container { width: 100%; height: 16%; margin-top: auto; background-color: rgba(106, 0, 255, 0.2); display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: 30px; z-index: 1000; }
.collapsed-bottom-btn { width: 100%; /* height: 50%; */ display: flex; justify-content: center; cursor: pointer; /* align-items: center; */ }
.collapsed-bottom-feedback { width: 60%; height: auto; } .collapsed-bottom-feedback:hover { transform: scale(1.1); } .collapsed-bottom-announcement { width: 60%; height: auto; } .collapsed-bottom-announcement:hover { transform: scale(1.1); } .history-content { flex: 1; display: flex; flex-direction: column; width: 100%; /* padding: 20px; */ overflow: hidden; }
.head-container { margin-top: 20%; margin-bottom: 10px; width: 100%; display: flex; align-items: center; justify-content: center; }
.mobile-head-container { padding: 5px 0px 5px 20px; /* margin-left: 20px; */ /* width: 100%; */ display: flex; align-items: center; border-bottom: 2px solid #414141; /* justify-content: center; */ }
.history-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); }
.history-actions { display: flex; justify-content: center; align-items: center; }
.mobile-history-actions { margin-left: auto; display: flex; justify-content: center; align-items: center; }
.logo-img { height: auto; width: 70%; object-fit: contain; }
.history-list { flex: 1; overflow-y: auto; scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.3) transparent; }
.history-list::-webkit-scrollbar { width: 6px; }
.history-list::-webkit-scrollbar-track { background: transparent; }
.history-list::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); border-radius: 3px; }
.history-list::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.5); }
.categoryName { color: white; padding: 12px; }
.history-item { background: rgba(255, 255, 255, 0.05); border-radius: 8px; margin-bottom: 8px; padding: 12px; cursor: pointer; transition: all 0.2s ease; border: 1px solid transparent; display: flex; justify-content: center; align-items: center; }
.history-item:hover { background: rgba(255, 255, 255, 0.1); }
.record-content { display: flex; width: 100%; }
.record-img { display: flex; align-items: center; justify-content: center; width: 20%; }
.record-msg { width: 80%; }
.record-text { color: white; font-size: 13px; line-height: 1.4; margin-bottom: 6px; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.stock-name { font-weight: 500; margin-right: 4px; }
.stock-code { color: rgba(255, 255, 255, 0.7); font-size: 12px; font-weight: 400; }
.record-time { color: rgba(255, 255, 255, 0.6); font-size: 11px; }
.record-actions { height: 100%; /* margin-left: 8px; */ transition: opacity 0.2s ease; }
.more-btn { background: rgba(231, 76, 60, 0); border: none; border-radius: 4px; color: white; padding: 4px; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; }
.more-btn:hover { background: rgba(255, 255, 255, 0.3); transform: scale(1.1); } .popover-content { display: flex; flex-direction: column; }
.popover-item { display: flex; align-items: center; padding: 10px; /* justify-content: center; */ }
.popover-btn { cursor: pointer; }
.popover-btn:hover { background: rgba(0, 0, 0, 0.1); }
.popover-icon { margin-right: 5px; }
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 20px; text-align: center; }
.empty-icon { margin-bottom: 16px; opacity: 0.5; }
.documentDelete { color: white; font-size: 5rem; }
.empty-text { color: rgba(255, 255, 255, 0.6); font-size: 14px; margin: 0; }
.bottom-container { width: 100%; height: 16%; margin-top: auto; background-color: rgba(106, 0, 255, 0.2); display: flex; justify-content: space-between; align-items: center; }
.mobile-bottom-container { border-top: 2px solid #414141; width: 100%; height: 16%; margin-top: auto; background-color: rgba(106, 0, 255, 0.2); display: flex; flex-direction: column; justify-content: space-between; align-items: center; }
.bottom-btn { width: 50%; display: flex; justify-content: center; align-items: center; cursor: pointer; }
.mobile-bottom-btn { width: 100%; height: 50%; display: flex; align-items: center; }
.bottom-feedback { width: 30%; height: auto; }
.mobile-bottom-feedback { margin-left: 20px; height: 60%; width: auto; }
.bottom-feedback:hover { transform: scale(1.2); }
.bottom-announcement { width: 30%; height: auto; }
.mobile-bottom-announcement { margin-left: 20px; height: 60%; width: auto; }
.mobile-bottom-text { color: white; margin-left: 10px; font-size: 1.1rem; }
.bottom-announcement:hover { transform: scale(1.2); }
/* 移动端适配 */ @media (max-width: 768px) { .history-content { /* padding: 15px; */ } } </style>
<style> .el-popover { width: auto !important; padding: 0 !important; } </style>
|