Files
zhxg_app/components/aiChat/HistoryDrawer.vue
2025-08-13 09:19:28 +08:00

411 lines
9.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- HistoryDrawer.vue -->
<template>
<!-- 抽屉容器visible控制显示 -->
<view v-if="visible" class="drawer-container" @touchmove.stop.prevent>
<!-- 遮罩层点击关闭 -->
<view class="drawer-mask" @click="closeDrawer"></view>
<!-- 抽屉内容区域 -->
<view class="drawer-content">
<!-- 标题区域 -->
<view class="drawer-header">
<text class="title">历史记录</text>
<image src="/static/close.svg" mode="aspectFit" class="close-icon" @click="closeDrawer" />
</view>
<!-- 搜索栏 -->
<view class="search-bar">
<image src="/static/search.svg" mode="aspectFit" class="search-icon" />
<input v-model="searchKeyword" placeholder="搜索聊天记录..." class="search-input" @input="handleSearch" />
<image v-if="searchKeyword" src="/static/clear.svg" class="clear-icon" @click="clearSearch" />
</view>
<!-- 历史列表区域 -->
<scroll-view scroll-y class="history-list">
<!-- 分组渲染历史记录 -->
<block v-for="(group, gIndex) in filteredRecords" :key="gIndex">
<view class="group-title">{{ group.title }}</view>
<view v-for="item in group.list" :key="item.id" class="history-item" @click="onItemClick(item)">
<!-- 日期时间显示 -->
<view class="datetime">
<text class="date">{{ item.date }}</text>
<text class="time">{{ item.time }}</text>
</view>
<!-- 消息内容 -->
<view class="record">
<text class="user-msg" v-html="highlightKeyword(item.content)"></text>
<text class="ai-msg" v-html="highlightKeyword(item.reply)"></text>
</view>
<view class="divider"></view>
</view>
</block>
<!-- 空状态提示 -->
<view v-if="filteredRecords.length === 0" class="empty-tip">
<text>{{ searchKeyword ? '没有找到匹配的记录' : '暂无聊天记录' }}</text>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import {
getHistory
} from '../../api/aiChat/ai_index.js'; // 历史记录API
export default {
name: 'HistoryDrawer',
props: {
visible: Boolean // 控制抽屉显示
},
data() {
return {
historyRecords: [], // 原始历史记录
filteredRecords: [], // 过滤后的历史记录
searchKeyword: '' // 搜索关键词
};
},
watch: {
// 监听visible变化显示时加载记录
visible(newVal) {
if (newVal) this.loadHistoryRecords();
else this.clearSearch();
}
},
methods: {
// 关闭抽屉
closeDrawer() {
this.$emit('close');
},
/* ========== 1. 读本地缓存兜底 ========== */
renderLocal(list) {
// 将本地缓存的消息按日期分组
const groupMap = {};
list.forEach((m, idx) => {
const date = new Date();
const formatted = {
date: this.formatDate(date),
time: this.formatTime(date),
content: m.sender === 'user' ? m.content : '',
reply: m.sender === 'ai' ? m.content : '',
id: idx
};
const title = this.getGroupTitle(date);
(groupMap[title] ||= []).push(formatted);
});
// 转换为数组形式
this.historyRecords = Object.entries(groupMap).map(([t, arr]) => ({
title: t,
list: arr
}));
this.filteredRecords = [...this.historyRecords];
},
/* ========== 2. 加载接口或本地缓存 ========== */
async loadHistoryRecords() {
try {
// 1. 先读本地缓存,立即显示(避免空白)
const local = uni.getStorageSync('chatHistory') || [];
if (local.length) this.renderLocal(local);
// 2. 再调接口,成功后覆盖本地
const res = await getHistory({
conversationId: '5665af64-22b4-4a59-b15f-2126eb056302',
user: '2023429112',
limit: 50
});
console.log('原始返回结构', res);
const list = res?.data?.data || [];
if (!list.length) return; // 接口返回空数组也保留本地
// 处理API返回的数据
const groupMap = {};
list.forEach(item => {
const ts = item.created_at ?? item.create_time ?? item.timestamp ?? 0;
const date = String(ts).length === 13 ? new Date(ts) : new Date(ts * 1000);
const formatted = {
date: this.formatDate(date),
time: this.formatTime(date),
content: item.query || item.question || item.content || '未知内容',
reply: item.answer || item.reply || '暂无回复',
id: item.id || Math.random().toString(36).slice(2)
};
const title = this.getGroupTitle(date);
(groupMap[title] ||= []).push(formatted);
});
// 按时间排序并更新数据
this.historyRecords = Object.entries(groupMap).map(([title, arr]) => ({
title,
list: arr.sort((a, b) => b.timestamp - a.timestamp)
}));
this.filteredRecords = [...this.historyRecords];
} catch (e) {
console.error('历史接口 401已使用本地缓存兜底', e);
// 接口失败时,保留本地缓存(不覆盖)
}
},
// 处理搜索输入
handleSearch() {
const kw = this.searchKeyword.trim().toLowerCase();
if (!kw) return (this.filteredRecords = [...this.historyRecords]);
// 过滤历史记录
this.filteredRecords = this.historyRecords
.map(g => ({
...g,
list: g.list.filter(i =>
i.content.toLowerCase().includes(kw) ||
i.reply.toLowerCase().includes(kw)
)
}))
.filter(g => g.list.length);
},
// 清除搜索
clearSearch() {
this.searchKeyword = '';
this.filteredRecords = [...this.historyRecords];
},
// 高亮搜索关键词
highlightKeyword(text) {
if (!this.searchKeyword || !text) return text;
const kw = this.searchKeyword.trim();
return text.replace(
new RegExp(kw, 'gi'),
`<span class="highlight">${kw}</span>`
);
},
// 获取分组标题(今天/昨天/7天内等
getGroupTitle(date) {
const now = new Date();
const today = new Date(now.setHours(0, 0, 0, 0));
const yesterday = new Date(today).setDate(today.getDate() - 1);
const week = new Date(today).setDate(today.getDate() - 7);
const month = new Date(today).setDate(today.getDate() - 30);
if (date >= today) return '今天';
if (date >= yesterday) return '昨天';
if (date >= week) return '7天内';
if (date >= month) return '30天内';
return '更早';
},
// 格式化日期为YYYY-MM-DD
formatDate(date) {
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date
.getDate()
.toString()
.padStart(2, '0')}`;
},
// 格式化时间为HH:MM
formatTime(date) {
return `${date.getHours().toString().padStart(2, '0')}:${date
.getMinutes()
.toString()
.padStart(2, '0')}`;
},
// 点击历史记录项
onItemClick(item) {
// 移除HTML标签后触发事件
this.$emit('item-click', {
...item,
content: item.content.replace(/<[^>]+>/g, ''),
reply: item.reply.replace(/<[^>]+>/g, '')
});
this.closeDrawer();
}
}
};
</script>
<style scoped>
/* 抽屉容器样式 */
.drawer-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
}
/* 遮罩层样式 */
.drawer-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
}
/* 抽屉内容区域样式 */
.drawer-content {
position: absolute;
top: 0;
left: 0;
width: 66.67%;
height: 100vh;
background-color: #fff;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
}
/* 头部样式 */
.drawer-header {
padding: 15px 15px 10px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #eee;
height: 50px;
box-sizing: border-box;
}
.title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.close-icon {
width: 24px;
height: 24px;
}
/* 搜索栏样式 */
.search-bar {
display: flex;
align-items: center;
padding: 10px 15px;
background-color: #f5f5f5;
border-bottom: 1px solid #eee;
}
.search-icon {
width: 18px;
height: 18px;
margin-right: 8px;
tint-color: #999;
}
.search-input {
flex: 1;
height: 36px;
background-color: #fff;
border-radius: 18px;
padding: 0 15px;
font-size: 14px;
}
.clear-icon {
width: 16px;
height: 16px;
margin-left: 8px;
tint-color: #999;
}
/* 历史列表样式 */
.history-list {
flex: 1;
overflow-y: auto;
padding: 10px 0;
box-sizing: border-box;
}
/* 分组标题样式 */
.group-title {
font-size: 14px;
color: #888;
padding: 12px 20px;
background-color: #f5f5f5;
}
/* 历史记录项样式 */
.history-item {
padding: 15px 20px;
background-color: #fff;
border-bottom: 1px solid #f0f0f0;
}
/* 日期时间样式 */
.datetime {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.date {
font-size: 13px;
color: #666;
}
.time {
font-size: 12px;
color: #999;
}
/* 消息内容样式 */
.record {
font-size: 14px;
line-height: 1.6;
}
.user-msg {
display: block;
color: #333;
margin-bottom: 6px;
padding: 6px 10px;
background-color: #f9f9f9;
border-radius: 6px;
}
.ai-msg {
display: block;
color: #333;
padding: 6px 10px;
background-color: #eef7ff;
border-radius: 6px;
}
/* 关键词高亮样式 */
.highlight {
color: #ff4d4f;
font-weight: bold;
}
/* 分隔线样式 */
.divider {
height: 8px;
background-color: #f9f9f9;
margin-top: 10px;
border-radius: 4px;
}
/* 空状态提示样式 */
.empty-tip {
text-align: center;
color: #999;
font-size: 16px;
margin-top: 50px;
}
/* 修复输入框占位符样式 */
input::-webkit-input-placeholder {
color: #ccc;
font-size: 14px;
}
</style>