ai-clone/frontend-ai/utils/history-record.groovy
2026-03-05 14:29:21 +08:00

539 lines
12 KiB
Groovy
Raw Permalink 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.

<template>
<view class="history-container">
<!-- -->
<view class="filter-section">
<view class="filter-bar">
<view
v-for="(filter, index) in filters"
:key="index"
:class="['filter-item', activeFilter === index ? 'active' : '']"
@click="handleFilterChange(index)"
>
{{ filter }}
</view>
</view>
<view class="clear-btn-wrapper">
<button class="clear-btn" @click="clearAll"></button>
</view>
</view>
<!-- 历史列表 -->
<scroll-view scroll-y class="history-list">
<view v-if="loading" class="loading-box">
<text class="loading-text">⏳ 加载中...</text>
</view>
<view v-else-if="currentRecords.length === 0" class="empty-box">
<text class="empty-icon">📋</text>
<text class="empty-text"></text>
</view>
<view v-else>
<view
v-for="(group, date) in groupedRecords"
:key="date"
class="date-group"
>
<view class="date-header">{{ date }}</view>
<view
v-for="record in group"
:key="record.id"
class="history-item"
@click="viewRecord(record)"
>
<view class="item-icon">{{ record.icon }}</view>
<view class="item-content">
<text class="item-title">{{ record.title }}</text>
<text class="item-desc">{{ record.desc }}</text>
<view class="item-footer">
<text class="item-time">{{ record.time }}</text>
<text v-if="record.amount !== 0" :class="['item-amount', record.amount > 0 ? 'refund-amount' : 'deduct-amount']">
{{ record.amount > 0 ? '+' : '' }}{{ record.amount.toFixed(2) }}元
</text>
</view>
<view class="item-action" @click.stop="deleteRecord(record)">
<image src="/static/iconfont/delete.svg" class="delete-icon" mode="aspectFit"></image>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { API_BASE, API_ENDPOINTS, buildURL } from '@/config/api.js';
export default {
data() {
return {
API_BASE,
activeFilter: 0,
filters: ['全部', '今天', '本周', '本月'],
loading: false,
records: [
{
id: 1,
type: 'voice',
icon: '🎤',
title: '创建了声音克隆',
desc: '妈妈的声音',
time: '10:30',
date: '2024-12-04'
},
{
id: 2,
type: 'photo',
icon: '📸',
title: '复活了照片',
desc: '爷爷的照片',
time: '15:20',
date: '2024-12-03'
},
{
id: 3,
type: 'call',
icon: '📞',
title: '进行了AI通话',
desc: '与奶奶的对话',
time: '09:15',
date: '2024-12-03'
},
{
id: 4,
type: 'voice',
icon: '🎤',
title: '语音合成',
desc: '合成了一段语音',
time: '14:30',
date: '2024-12-02'
}
]
};
},
computed: {
currentRecords() {
if (this.activeFilter === 0) {
// 全部
return this.records;
} else if (this.activeFilter === 1) {
// 今天
const today = new Date().toISOString().split('T')[0];
return this.records.filter(r => r.date === today);
} else if (this.activeFilter === 2) {
// 本周
const now = new Date();
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
return this.records.filter(r => {
const recordDate = new Date(r.date);
return recordDate >= weekAgo && recordDate <= now;
});
} else {
// 本月
const now = new Date();
const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
return this.records.filter(r => {
const recordDate = new Date(r.date);
return recordDate >= monthAgo && recordDate <= now;
});
}
},
groupedRecords() {
const groups = {};
this.currentRecords.forEach(record => {
const dateLabel = this.getDateLabel(record.date);
if (!groups[dateLabel]) {
groups[dateLabel] = [];
}
groups[dateLabel].push(record);
});
return groups;
}
},
onLoad() {
this.loadHistory();
},
methods: {
// 加载历史记录
loadHistory() {
this.loading = true;
const userId = uni.getStorageSync('userId') || '';
const token = uni.getStorageSync('token') || '';
uni.request({
url: `${this.API_BASE}/api/history?userId=${userId}&page=0&size=100`,
method: 'GET',
header: {
'X-User-Id': userId,
'Authorization': token ? `Bearer ${token}` : ''
},
success: (res) => {
console.log('[History] 用户ID:', userId);
console.log('[History] API响应:', res.data);
if (res.data && res.data.content) {
// 转换后端数据格式为前端格式过滤掉AI对话记录
this.records = res.data.content
.filter(item => item.actionType !== 'AI_CALL') // 过滤掉AI对话记录
.map(item => {
const date = new Date(item.createdAt);
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return {
id: item.id,
type: item.actionType?.toLowerCase() || 'voice',
icon: this.getIconByType(item.actionType),
title: item.actionTitle || '操作记录',
desc: item.actionDesc || '',
time: `${hours}:${minutes}`,
date: `${year}-${month}-${day}`,
amount: item.amount || 0
};
});
console.log('[History] 转换后的记录:', this.records);
}
},
fail: (err) => {
console.error('[History] 加载失败:', err);
// 保留模拟数据作为后备
},
complete: () => {
this.loading = false;
}
});
},
// 处理筛选变化
handleFilterChange(index) {
console.log('[Filter] 切换筛选:', this.filters[index]);
this.activeFilter = index;
console.log('[Filter] 当前记录数:', this.records.length);
console.log('[Filter] 筛选后记录数:', this.currentRecords.length);
},
// 根据类型获取图标
getIconByType(type) {
const icons = {
'CREATE_VOICE': '🎤',
'SYNTHESIZE': '🎤',
'PHOTO_REVIVAL': '📸',
'VIDEO_CALL': '📞',
'AI_CALL': '💬'
};
return icons[type] || '📋';
},
formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
},
getDateLabel(dateStr) {
const today = this.formatDate(new Date());
const yesterday = this.formatDate(new Date(Date.now() - 24 * 60 * 60 * 1000));
if (dateStr === today) {
return '今天';
} else if (dateStr === yesterday) {
return '昨天';
} else {
return dateStr;
}
},
viewRecord(record) {
uni.showToast({
title: '查看:' + record.title,
icon: 'none'
});
},
deleteRecord(record) {
uni.showModal({
title: '删除记录',
content: `确定要删除"${record.title}"吗?`,
success: (res) => {
if (res.confirm) {
const userId = uni.getStorageSync('userId') || '';
const token = uni.getStorageSync('token') || '';
// 调用后端API删除
uni.request({
url: `${this.API_BASE}/api/history/${record.id}?userId=${userId}`,
method: 'DELETE',
header: {
'X-User-Id': userId,
'Authorization': token ? `Bearer ${token}` : ''
},
success: (apiRes) => {
console.log('[Delete] API响应:', apiRes.data);
if (apiRes.statusCode === 200) {
// 从本地数组中删除
const index = this.records.findIndex(r => r.id === record.id);
if (index > -1) {
this.records.splice(index, 1);
}
uni.showToast({
title: '删除成功',
icon: 'success'
});
} else {
uni.showToast({
title: '删除失败',
icon: 'none'
});
}
},
fail: (err) => {
console.error('[Delete] 删除失败:', err);
uni.showToast({
title: '删除失败',
icon: 'none'
});
}
});
}
}
});
},
clearAll() {
if (this.records.length === 0) {
uni.showToast({
title: '暂无记录',
icon: 'none'
});
return;
}
uni.showModal({
title: '清空历史',
content: '确定要清空所有历史记录吗?',
success: (res) => {
if (res.confirm) {
const userId = uni.getStorageSync('userId') || '';
const token = uni.getStorageSync('token') || '';
// 批量删除所有记录
const deletePromises = this.records.map(record => {
return new Promise((resolve) => {
uni.request({
url: `${this.API_BASE}/api/history/${record.id}?userId=${userId}`,
method: 'DELETE',
header: {
'X-User-Id': userId,
'Authorization': token ? `Bearer ${token}` : ''
},
success: () => resolve(true),
fail: () => resolve(false)
});
});
});
// 等待所有删除完成
Promise.all(deletePromises).then(() => {
this.records = [];
uni.showToast({
title: '已清空',
icon: 'success'
});
});
}
}
});
}
}
};
</script>
<style lang="scss" scoped>
.history-container {
min-height: 100vh;
background: #FDF8F2;
display: flex;
flex-direction: column;
}
/* 筛选区域 */
.filter-section {
padding: 20upx 30upx;
display: flex;
align-items: center;
gap: 20upx;
}
/* 筛选栏 */
.filter-bar {
flex: 1;
display: flex;
background: rgba(255, 255, 255, 0.95);
padding: 20upx;
gap: 20upx;
box-shadow: 0 2upx 8upx rgba(0, 0, 0, 0.05);
border-radius: 32upx;
backdrop-filter: blur(10upx);
border: 2upx solid rgba(255, 255, 255, 0.8);
}
.clear-btn-wrapper {
.clear-btn {
padding: 20upx 32upx;
background: rgba(255, 255, 255, 0.95);
border: 2upx solid #ff6b6b;
border-radius: 24upx;
color: #ff6b6b;
font-size: 26upx;
font-weight: 600;
backdrop-filter: blur(10upx);
transition: all 0.3s;
&:active {
background: #ff6b6b;
color: white;
transform: scale(0.95);
}
}
}
.filter-item {
flex: 1;
text-align: center;
padding: 16upx 0;
font-size: 26upx;
color: #666;
border-radius: 20upx;
background: #f5f5f5;
transition: all 0.3s;
}
.filter-item.active {
background: linear-gradient(135deg, #8B7355 0%, #6D8B8B 100%);
color: white;
font-weight: bold;
}
/* 历史列表 */
.history-list {
flex: 1;
padding: 30upx;
}
.loading-box,
.empty-box {
padding: 200upx 40upx;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}
.loading-text {
font-size: 28upx;
color: #999;
}
.empty-icon {
font-size: 120upx;
margin-bottom: 30upx;
opacity: 0.5;
}
.empty-text {
font-size: 32upx;
color: #666;
}
/* 日期分组 */
.date-group {
margin-bottom: 40upx;
}
.date-header {
font-size: 26upx;
color: #999;
padding: 20upx 0;
font-weight: 600;
}
.history-item {
background: white;
border-radius: 30upx;
padding: 30upx;
margin-bottom: 20upx;
display: flex;
align-items: center;
box-shadow: 0 8upx 30upx rgba(0, 0, 0, 0.08);
transition: all 0.3s;
&:active {
transform: translateX(-8upx);
box-shadow: 0 12upx 40upx rgba(0, 0, 0, 0.12);
}
}
.item-icon {
width: 80upx;
height: 80upx;
border-radius: 50%;
background: linear-gradient(135deg, rgba(139, 115, 85, 0.1) 0%, rgba(109, 139, 139, 0.1) 100%);
display: flex;
align-items: center;
justify-content: center;
font-size: 40upx;
margin-right: 20upx;
}
.item-content {
flex: 1;
display: flex;
flex-direction: column;
.item-title {
font-size: 30upx;
font-weight: bold;
color: #333;
margin-bottom: 8upx;
}
.item-desc {
font-size: 26upx;
color: #666;
margin-bottom: 8upx;
}
.item-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.item-time {
font-size: 22upx;
color: #999;
}
.item-amount {
font-size: 24upx;
color: #ff6b6b;
font-weight: bold;
}
}
.item-action {
padding: 10upx;
margin-right: 20upx;
.action-icon {
font-size: 36upx;
}
}
</style>