Files
gerenjizhang/backend/test-record-sorting.js

297 lines
12 KiB
JavaScript
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.
/**
* 测试脚本:账单排序问题深度验证
*
* 测试目的:
* 1. 验证后端 records 接口的排序逻辑(orderBy: date vs createdAt
* 2. 创建一条新的"兼职"收入记录,检查其date和createdAt字段
* 3. 验证前端日期输入框类型导致的时间丢失问题
* 4. 验证首页"最近记录"的数据同步
*/
import http from 'http';
const BASE_URL = 'http://localhost:3001';
function apiRequest(method, path, body = null) {
return new Promise((resolve, reject) => {
const url = new URL(path, BASE_URL);
const options = {
hostname: url.hostname,
port: url.port,
path: url.pathname + url.search,
method: method,
headers: {
'Content-Type': 'application/json',
}
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
resolve({ status: res.statusCode, body: JSON.parse(data) });
} catch (e) {
resolve({ status: res.statusCode, body: data });
}
});
});
req.on('error', reject);
if (body) {
req.write(JSON.stringify(body));
}
req.end();
});
}
function logSeparator() {
console.log(`\n${'='.repeat(60)}`);
}
async function runTests() {
logSeparator();
console.log('🚀 开始执行账单排序问题深度测试...');
logSeparator();
// ==========================================
// 测试1: 获取现有记录,检查排序逻辑
// ==========================================
const recordsBefore = await apiRequest('GET', '/api/records?userId=1');
console.log('\n📋 【测试1】获取现有记录 - 检查排序逻辑');
console.log('-'.repeat(60));
if (recordsBefore.status !== 200) {
console.log(`❌ API返回错误状态: ${recordsBefore.status}`);
return;
}
const records = recordsBefore.body.data || [];
console.log(`📊 记录总数: ${records.length}`);
console.log(`\n📋 现有记录列表 (按后端排序):`);
records.forEach((r, i) => {
const date = new Date(r.date);
const created = new Date(r.createdAt);
console.log(` [${i + 1}] ${r.category} | ¥${r.amount} | date=${date.toLocaleString('zh-CN')} | createdAt=${created.toLocaleString('zh-CN')}`);
});
// 检查排序
const dates = records.map(r => new Date(r.date).getTime());
const isDateDesc = dates.every((d, i) => i === 0 || dates[i - 1] >= d);
console.log(`\n🔍 后端排序检查:`);
console.log(` 按 date 降序: ${isDateDesc ? '✅ 是' : '❌ 否'}`);
// 检查是否有相同date的记录
const dateGroups = {};
records.forEach(r => {
const dateKey = new Date(r.date).toLocaleDateString('zh-CN');
if (!dateGroups[dateKey]) dateGroups[dateKey] = [];
dateGroups[dateKey].push(r);
});
console.log(`\n 相同日期分组:`);
Object.entries(dateGroups).forEach(([date, recs]) => {
console.log(` ${date}: ${recs.length}条记录`);
recs.forEach(r => {
console.log(` - ${r.category} ¥${r.amount} (createdAt: ${new Date(r.createdAt).toLocaleString('zh-CN')})`);
});
});
// ==========================================
// 测试2: 模拟前端创建"兼职 +50元"记录
// ==========================================
console.log(`\n\n📋 【测试2】模拟前端提交"兼职 +50元 技术支持"`);
console.log('-'.repeat(60));
// 前端日期表单使用 new Date().toISOString().split('T')[0],即只传日期部分
const today = new Date();
const dateOnly = today.toISOString().split('T')[0];
console.log(`📌 模拟前端提交的 date 值: "${dateOnly}" (type=date 格式)`);
console.log(`📌 提交时间: ${today.toLocaleString('zh-CN')}`);
const createResult = await apiRequest('POST', '/api/records', {
userId: 1,
accountId: 1, // 支付宝
type: 'income',
amount: 50,
category: '兼职',
description: '技术支持',
date: dateOnly // 前端只传日期,不传时间
});
let newRecordId = null;
if (createResult.status === 200) {
newRecordId = createResult.body.data.id;
const newRecord = createResult.body.data;
const dateVal = new Date(newRecord.date);
const createdVal = new Date(newRecord.createdAt);
console.log(` ✅ 创建成功`);
console.log(` ID: ${newRecord.id}`);
console.log(` date字段: ${dateVal.toLocaleString('zh-CN')} (ISO: ${newRecord.date})`);
console.log(` createdAt字段: ${createdVal.toLocaleString('zh-CN')} (ISO: ${newRecord.createdAt})`);
console.log(` ⚠️ date 时间为: ${dateVal.getHours()}:${dateVal.getMinutes().toString().padStart(2, '0')}`);
console.log(` 🔴 问题确认: date字段时间部分为 00:00 或 08:00,不是当前实际时间!`);
} else {
console.log(` ❌ 创建失败: ${JSON.stringify(createResult.body)}`);
return;
}
// ==========================================
// 测试3: 验证新记录在列表中的排序位置
// ==========================================
const recordsAfter = await apiRequest('GET', '/api/records?userId=1');
console.log(`\n\n📋 【测试3】验证新记录在列表中的排序位置`);
console.log('-'.repeat(60));
const allRecords = recordsAfter.body.data || [];
console.log(`📊 创建后记录总数: ${allRecords.length}`);
console.log(`\n📋 记录列表 (后端排序结果):`);
allRecords.forEach((r, i) => {
const date = new Date(r.date);
const created = new Date(r.createdAt);
const isNew = r.id === newRecordId ? ' ⬅️【新记录】' : '';
console.log(` [${i + 1}] ${r.category} | ¥${r.amount} | date=${date.toLocaleString('zh-CN')} | createdAt=${created.toLocaleString('zh-CN')}${isNew}`);
});
// 检查新记录位置
const newIndex = allRecords.findIndex(r => r.id === newRecordId);
console.log(`\n🔍 排序位置分析:`);
console.log(` 新记录位置: 第 ${newIndex + 1} 位 (共 ${allRecords.length} 条)`);
console.log(` 预期位置: 第 1 位`);
if (newIndex === 0) {
console.log(` ✅ 新记录在第一位 (后端排序符合预期)`);
} else {
console.log(` ❌ 【BUG确认】新记录不在第一位!`);
console.log(` 根因分析:`);
const firstRecord = allRecords[0];
console.log(` - 第1条记录 date: ${new Date(firstRecord.date).toLocaleString('zh-CN')}`);
console.log(` - 新记录 date: ${new Date(allRecords[newIndex].date).toLocaleString('zh-CN')}`);
console.log(` - 后端按 date 字段降序排序`);
console.log(` - 新记录的date时间部分为 00:00/08:00`);
console.log(` - 当天已有记录的date时间部分更接近当前时间`);
console.log(` - 所以新记录排在后面`);
}
// ==========================================
// 测试4: 前端表单日期类型分析
// ==========================================
console.log(`\n\n📋 【测试4】前端日期输入框类型分析`);
console.log('-'.repeat(60));
console.log(`📌 前端代码分析 (Record/index.tsx):`);
console.log(` 行14: date: new Date().toISOString().split('T')[0]`);
console.log(` 行298-304: <input type="date" id="date" ... />`);
console.log(` 行60: date: new Date().toISOString().split('T')[0] (重置表单)`);
console.log(`\n🔍 问题分析:`);
console.log(` input type: "date" (不是 "datetime-local")`);
console.log(` 默认值格式: "${dateOnly}" (只有日期,无时间)`);
console.log(` 传给后端: date 字段只有日期部分,时间部分为 00:00:00 UTC`);
console.log(` 时区转换: UTC 00:00 → 北京时间 08:00`);
console.log(`\n🔴 【根因确认】`);
console.log(` 使用 type="date" 只保存日期不保存时间`);
console.log(` 同一天的多条记录,date字段时间部分都是 08:00`);
console.log(` 后端按 date 降序排序,同一天内记录顺序不确定`);
console.log(` 新添加的记录不会排在最前面`);
// ==========================================
// 测试5: 首页数据同步验证
// ==========================================
console.log(`\n\n📋 【测试5】首页 Dashboard 数据同步验证`);
console.log('-'.repeat(60));
console.log(`📌 前端代码分析:`);
console.log(` dataStore.ts 行90-101 (createRecord):`);
console.log(` await recordsApi.createRecord(data);`);
console.log(` await get().fetchRecords(); ✅ 调用`);
console.log(` await get().fetchDashboardSummary(); ✅ 调用`);
console.log(`\n Dashboard/index.tsx 行67-69 (最近记录):`);
console.log(` const recentRecords = records`);
console.log(` .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())`);
console.log(` .slice(0, 5);`);
console.log(`\n🔍 问题分析:`);
console.log(` 1. createRecord 后确实调用了 fetchRecords() ✅`);
console.log(` 2. Dashboard 使用了 useDataStore() 响应式订阅 ✅`);
console.log(` 3. 但前端对 records 再次按 date 排序 ❌`);
console.log(` 4. 同样的问题:按 date 排序,新记录时间部分为 08:00`);
console.log(` 5. 首页"最近记录"也不会显示新记录在最前面`);
// ==========================================
// 测试6: 前端排序逻辑对比
// ==========================================
console.log(`\n\n📋 【测试6】前后端排序逻辑对比`);
console.log('-'.repeat(60));
console.log(`📌 后端排序 (index.js 行188-192):`);
console.log(` orderBy: { date: 'desc' }`);
console.log(`\n📌 前端 Record 页面排序 (Record/index.tsx 行28-32):`);
console.log(` .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())`);
console.log(`\n📌 前端 Dashboard 排序 (Dashboard/index.tsx 行67-69):`);
console.log(` .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())`);
console.log(`\n🔍 结论:`);
console.log(` 前后端都使用 date 字段降序排序`);
console.log(` 当 date 时间部分相同时,排序结果不稳定`);
console.log(` 建议改为按 createdAt 排序,保证新添加记录排在最前`);
// ==========================================
// 清理测试数据
// ==========================================
if (newRecordId) {
console.log(`\n\n📋 【清理测试数据】`);
console.log('-'.repeat(60));
const deleteResult = await apiRequest('DELETE', `/api/records/${newRecordId}`);
console.log(` 删除记录 ID: ${newRecordId}`);
console.log(` 结果: ${deleteResult.status === 200 ? '✅ 成功' : '❌ 失败'}`);
}
// ==========================================
// 测试总结
// ==========================================
logSeparator();
console.log('📊 测试总结');
logSeparator();
console.log(`
【问题1】新记录不在账单明细第一个位置
状态: ❌ 确认存在
严重程度: 高
根因: 后端按 date 字段降序排序,新记录date时间部分为00:00/08:00
【问题2】首页"最近记录"没有显示新记录
状态: ❌ 确认存在
严重程度: 高
根因: 前端同样按 date 字段排序,与后端问题一致
【根本原因分析】
1. 前端使用 type="date" 只保存日期,不保存时间
2. 后端按 date 字段降序排序(而非 createdAt)
3. 同一天的记录,date 时间部分相同(均为08:00)
4. SQLite 对相同值的排序结果不稳定
【修复方案对比】
方案A: 后端排序改为 orderBy: { createdAt: 'desc' }
优点: 简单直接,新记录永远排在最前面
缺点: 编辑旧记录时,会跳到最前面(但这是合理行为)
推荐指数: ★★★★★
方案B: 前端改为 type="datetime-local"
优点: 保留完整时间信息
缺点: 用户体验差(需要选择具体时间),不符合记账习惯
推荐指数: ★★
推荐方案: A(修改排序字段为 createdAt
【修复涉及文件】
1. backend/src/index.js 第190行
修改: orderBy: { date: 'desc' } → orderBy: { createdAt: 'desc' }
2. frontend/src/pages/Record/index.tsx 第32行
修改: .sort((a, b) => new Date(b.date)... → .sort((a, b) => new Date(b.createdAt)...)
3. frontend/src/pages/Dashboard/index.tsx 第68行
修改: .sort((a, b) => new Date(b.date)... → .sort((a, b) => new Date(b.createdAt)...)
`);
}
runTests().catch(console.error);