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>
|
2025-08-14 00:36:04 +08:00
|
|
|
|
import {
|
|
|
|
|
getHistory
|
|
|
|
|
} from '../../api/aiChat/ai_index.js';
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: 'HistoryDrawer',
|
|
|
|
|
props: {
|
|
|
|
|
visible: Boolean
|
|
|
|
|
},
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
historyRecords: [],
|
|
|
|
|
filteredRecords: [],
|
|
|
|
|
searchKeyword: '', // 移除了重复定义
|
|
|
|
|
loading: false // 新增loading状态
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
watch: {
|
|
|
|
|
visible(newVal) {
|
|
|
|
|
if (newVal) this.loadHistoryRecords();
|
|
|
|
|
else this.clearSearch();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
closeDrawer() {
|
|
|
|
|
this.$emit('close');
|
2025-08-13 09:19:28 +08:00
|
|
|
|
},
|
2025-08-14 00:36:04 +08:00
|
|
|
|
|
|
|
|
|
async loadHistoryRecords() {
|
|
|
|
|
this.loading = true;
|
|
|
|
|
try {
|
|
|
|
|
// 1. 获取当前用户学号
|
|
|
|
|
const userNo = uni.getStorageSync('stuNo');
|
|
|
|
|
if (!userNo) throw new Error('未获取到用户学号');
|
|
|
|
|
|
|
|
|
|
// 2. 调用接口获取数据
|
|
|
|
|
const res = await getHistory({
|
|
|
|
|
user: userNo,
|
|
|
|
|
conversationId: '',
|
|
|
|
|
limit: 20
|
|
|
|
|
});
|
|
|
|
|
console.log('接口响应:', res);
|
|
|
|
|
|
|
|
|
|
// 3. 处理响应数据 - 修正数据结构解析
|
|
|
|
|
const list = Array.isArray(res.data?.data) ? res.data.data : [];
|
|
|
|
|
console.log('解析后的数据列表:', list);
|
|
|
|
|
|
|
|
|
|
// 4. 分组处理
|
2025-08-13 09:19:28 +08:00
|
|
|
|
const groupMap = {};
|
2025-08-14 00:36:04 +08:00
|
|
|
|
list.forEach(item => {
|
|
|
|
|
// 修正字段映射
|
|
|
|
|
const timestamp = item.created_at || item.create_time || item.timestamp || Date.now() /
|
|
|
|
|
1000;
|
|
|
|
|
const date = new Date(timestamp * 1000);
|
|
|
|
|
|
|
|
|
|
const record = {
|
|
|
|
|
id: item.id || Math.random().toString(36).slice(2),
|
2025-08-13 09:19:28 +08:00
|
|
|
|
date: this.formatDate(date),
|
|
|
|
|
time: this.formatTime(date),
|
2025-08-14 00:36:04 +08:00
|
|
|
|
content: item.query || item.content || '未知内容',
|
|
|
|
|
reply: item.answer || item.reply || '暂无回复',
|
|
|
|
|
timestamp: timestamp
|
2025-08-13 09:19:28 +08:00
|
|
|
|
};
|
2025-08-14 00:36:04 +08:00
|
|
|
|
|
2025-08-13 09:19:28 +08:00
|
|
|
|
const title = this.getGroupTitle(date);
|
2025-08-14 00:36:04 +08:00
|
|
|
|
(groupMap[title] ||= []).push(record);
|
2025-08-13 09:19:28 +08:00
|
|
|
|
});
|
|
|
|
|
|
2025-08-14 00:36:04 +08:00
|
|
|
|
// 5. 排序并更新数据
|
|
|
|
|
this.historyRecords = Object.entries(groupMap).map(([title, arr]) => ({
|
|
|
|
|
title,
|
|
|
|
|
list: arr.sort((a, b) => b.timestamp - a.timestamp)
|
2025-08-13 09:19:28 +08:00
|
|
|
|
}));
|
2025-08-14 00:36:04 +08:00
|
|
|
|
|
2025-08-13 09:19:28 +08:00
|
|
|
|
this.filteredRecords = [...this.historyRecords];
|
2025-08-14 00:36:04 +08:00
|
|
|
|
console.log('最终处理的历史记录:', this.historyRecords);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('加载失败:', e);
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: `加载失败: ${e.message}`,
|
|
|
|
|
icon: 'none',
|
|
|
|
|
duration: 3000
|
2025-08-13 09:19:28 +08:00
|
|
|
|
});
|
2025-08-14 00:36:04 +08:00
|
|
|
|
this.historyRecords = [];
|
|
|
|
|
} finally {
|
|
|
|
|
this.loading = false;
|
2025-08-13 09:19:28 +08:00
|
|
|
|
}
|
2025-08-14 00:36:04 +08:00
|
|
|
|
},
|
2025-08-13 09:19:28 +08:00
|
|
|
|
|
|
|
|
|
|
2025-08-14 00:36:04 +08:00
|
|
|
|
// 处理搜索输入
|
|
|
|
|
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);
|
|
|
|
|
},
|
2025-08-13 09:19:28 +08:00
|
|
|
|
|
2025-08-14 00:36:04 +08:00
|
|
|
|
// 清除搜索
|
|
|
|
|
clearSearch() {
|
|
|
|
|
this.searchKeyword = '';
|
|
|
|
|
this.filteredRecords = [...this.historyRecords];
|
|
|
|
|
},
|
2025-08-13 09:19:28 +08:00
|
|
|
|
|
2025-08-14 00:36:04 +08:00
|
|
|
|
// 高亮搜索关键词
|
|
|
|
|
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>`
|
|
|
|
|
);
|
|
|
|
|
},
|
2025-08-13 09:19:28 +08:00
|
|
|
|
|
2025-08-14 00:36:04 +08:00
|
|
|
|
// 获取分组标题(今天/昨天/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 '更早';
|
|
|
|
|
},
|
2025-08-13 09:19:28 +08:00
|
|
|
|
|
2025-08-14 00:36:04 +08:00
|
|
|
|
// 格式化日期为YYYY-MM-DD
|
|
|
|
|
formatDate(date) {
|
|
|
|
|
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date
|
|
|
|
|
.getDate()
|
|
|
|
|
.toString()
|
|
|
|
|
.padStart(2, '0')}`;
|
|
|
|
|
},
|
2025-08-13 09:19:28 +08:00
|
|
|
|
|
2025-08-14 00:36:04 +08:00
|
|
|
|
// 格式化时间为HH:MM
|
|
|
|
|
formatTime(date) {
|
|
|
|
|
return `${date.getHours().toString().padStart(2, '0')}:${date
|
|
|
|
|
.getMinutes()
|
|
|
|
|
.toString()
|
|
|
|
|
.padStart(2, '0')}`;
|
|
|
|
|
},
|
2025-08-13 09:19:28 +08:00
|
|
|
|
|
2025-08-14 00:36:04 +08:00
|
|
|
|
// 点击历史记录项
|
|
|
|
|
onItemClick(item) {
|
|
|
|
|
// 移除HTML标签后触发事件
|
|
|
|
|
this.$emit('item-click', {
|
|
|
|
|
...item,
|
|
|
|
|
content: item.content.replace(/<[^>]+>/g, ''),
|
|
|
|
|
reply: item.reply.replace(/<[^>]+>/g, '')
|
|
|
|
|
});
|
|
|
|
|
this.closeDrawer();
|
|
|
|
|
}
|
2025-08-13 09:19:28 +08:00
|
|
|
|
}
|
2025-08-14 00:36:04 +08:00
|
|
|
|
};
|
|
|
|
|
</script>
|
2025-08-13 09:19:28 +08:00
|
|
|
|
|
2025-08-14 00:36:04 +08:00
|
|
|
|
<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;
|
|
|
|
|
}
|
2025-08-13 09:19:28 +08:00
|
|
|
|
</style>
|