Files
zhxg_app/components/aiChat/HistoryDrawer.vue

411 lines
9.6 KiB
Vue
Raw Normal View History

2025-08-13 09:19:28 +08:00
<!-- 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>