feat(后台管理): 实体管理列表统一展示创建时间/更新时间
用户管理、菜单管理、不符合ASIN、数据去重总数据、查询ASIN、最低价ASIN、 商品类目、密钥管理共 8 个实体管理列表补齐两列。分组管理/店铺密钥/店铺管理 此前已带创建+修改时间,任务列表与统计报表(撞款监控、密钥用量、日志、 记录与版本)不含实体更新语义,均未改动。 关键点——时间列必须由数据库维护,否则新列是假的: 这些表的更新走 selectById → 改字段 → updateById,实体带着读出的旧 updated_at 一起写回。MySQL 规则是「UPDATE 显式给某列赋值时不触发该列的 ON UPDATE 自动更新」,不禁写就会把旧值写回去,更新时间永远冻结在首次写入 时刻。按 V125(biz_file_result)既有样板,给 7 个实体标注 @TableField(insertStrategy=NEVER, updateStrategy=NEVER)。 - V131:users / columns 补 updated_at(幂等 ADD COLUMN,仿 V125 写法)。 存量行被回填为迁移执行时刻,非真实历史变更时间(历史上无记录,无法还原) - 实体/VO:AdminUserEntity、PermissionMenuEntity、InvalidAsinDataEntity、 DedupeTotalDataEntity、ProductCategoryEntity、QueryAsinEntity、 SkipPriceAsinEntity 加/改写 updatedAt;AdminUserItemVo、PermissionMenuItemVo、 InvalidAsinDataItemVo、DedupeTotalDataItemVo 补 updatedAt; AdminUserSecretRowVo 补 createdAt(行级首次配置时间 = 三模块最早) - 查询ASIN/最低价ASIN 后端 VO 与前端 model 本就有两字段,仅补渲染 - 前端 8 页表格加列,同步修正空态/加载行的 colspan(手写表格,不同步会错位) - 测试:align-query-asin / align-skip-price 原断言「不允许有更新时间列」 (像素复刻旧版),按新需求改为断言两列存在;新增 e2e list-time-columns 覆盖 8 页表头与真实时间值渲染
This commit is contained in:
@@ -220,13 +220,183 @@ const server = createServer((req, res) => {
|
||||
return json(res, {
|
||||
success: true,
|
||||
data: {
|
||||
items: [{ id: 1, username: 'admin', role: 'super_admin', creatorUsername: 'system', createdAt: '2026-01-01 00:00:00' }],
|
||||
items: [{ id: 1, username: 'admin', role: 'super_admin', creatorUsername: 'system', created_at: '2026-03-01 09:15:00', updated_at: '2026-09-18 16:42:30' }],
|
||||
total: 1,
|
||||
},
|
||||
})
|
||||
}
|
||||
if (url === '/api/admin/permission-menus') {
|
||||
return json(res, { success: true, data: { items: [] } })
|
||||
// 菜单管理列表:一条根节点,带创建/更新时间(验收时间列)。
|
||||
return json(res, {
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
name: '用户管理',
|
||||
column_key: 'admin_users',
|
||||
parent_id: null,
|
||||
menu_type: 'admin',
|
||||
route_path: 'account/users',
|
||||
sort_order: 10,
|
||||
created_at: '2026-03-01T09:15:00',
|
||||
updated_at: '2026-09-18T16:42:30',
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
if (url === '/api/admin/invalid-asin-data') {
|
||||
return json(res, {
|
||||
success: true,
|
||||
data: {
|
||||
items: [
|
||||
{
|
||||
id: 1,
|
||||
dataValue: 'B0INVALID1',
|
||||
brand: '测试品牌',
|
||||
groupId: 1,
|
||||
groupName: '测试分组',
|
||||
recordSource: 'MANUAL',
|
||||
createdAt: '2026-03-01 09:15:00',
|
||||
updatedAt: '2026-09-18 16:42:30',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
})
|
||||
}
|
||||
if (url === '/api/admin/dedupe-total-data') {
|
||||
return json(res, {
|
||||
success: true,
|
||||
data: {
|
||||
items: [
|
||||
{
|
||||
id: 1,
|
||||
dataValue: 'B0DEDUPE01',
|
||||
country: 'DE',
|
||||
groupId: 1,
|
||||
groupName: '测试分组',
|
||||
uploaderUserId: 1,
|
||||
username: 'admin',
|
||||
createdAt: '2026-03-01 09:15:00',
|
||||
updatedAt: '2026-09-18 16:42:30',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
})
|
||||
}
|
||||
if (url === '/api/admin/query-asins') {
|
||||
return json(res, {
|
||||
success: true,
|
||||
data: {
|
||||
items: [
|
||||
{
|
||||
id: 1,
|
||||
groupId: 1,
|
||||
groupName: '测试分组',
|
||||
shopName: '测试店铺',
|
||||
asinDe: 'B0QUERY001',
|
||||
asinUk: '',
|
||||
asinFr: '',
|
||||
asinIt: '',
|
||||
asinEs: '',
|
||||
createdAt: '2026-03-01 09:15:00',
|
||||
updatedAt: '2026-09-18 16:42:30',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
})
|
||||
}
|
||||
if (url === '/api/admin/skip-price-asins') {
|
||||
return json(res, {
|
||||
success: true,
|
||||
data: {
|
||||
items: [
|
||||
{
|
||||
id: 1,
|
||||
groupId: 1,
|
||||
groupName: '测试分组',
|
||||
shopName: '测试店铺',
|
||||
asinDe: 'B0SKIP0001',
|
||||
minimumPriceDe: 9.99,
|
||||
asinUk: '',
|
||||
minimumPriceUk: null,
|
||||
asinFr: '',
|
||||
minimumPriceFr: null,
|
||||
asinIt: '',
|
||||
minimumPriceIt: null,
|
||||
asinEs: '',
|
||||
minimumPriceEs: null,
|
||||
createdAt: '2026-03-01 09:15:00',
|
||||
updatedAt: '2026-09-18 16:42:30',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
})
|
||||
}
|
||||
if (url === '/api/admin/product-categories/children') {
|
||||
return json(res, {
|
||||
success: true,
|
||||
data: {
|
||||
items: [
|
||||
{
|
||||
id: 1,
|
||||
parentId: null,
|
||||
name: '护肤品',
|
||||
categoryKey: 'skincare',
|
||||
sortOrder: 10,
|
||||
description: '测试备注',
|
||||
isBuiltin: true,
|
||||
childCount: 0,
|
||||
level: 0,
|
||||
path: '护肤品',
|
||||
createdAt: '2026-03-01 09:15:00',
|
||||
updatedAt: '2026-09-18 16:42:30',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
hasMore: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
if (url === '/api/admin/user-secrets') {
|
||||
const emptyModule = { moduleKey: '', moduleLabel: '', masked: '', full: '', exists: false, checkStatus: 'unknown', checkCode: '', checkMessage: '', checkLatencyMs: null, checkedAt: null, updatedAt: null }
|
||||
return json(res, {
|
||||
success: true,
|
||||
data: {
|
||||
items: [
|
||||
{
|
||||
userId: 1,
|
||||
username: 'admin',
|
||||
groups: [],
|
||||
leaderUsername: '',
|
||||
similarAsin: { ...emptyModule, moduleKey: 'similar-asin', moduleLabel: '货源查询密钥' },
|
||||
appearancePatent: { ...emptyModule, moduleKey: 'appearance-patent', moduleLabel: '外观专利密钥' },
|
||||
proxy: { ...emptyModule, moduleKey: 'proxy', moduleLabel: '代理设置' },
|
||||
status: 'unknown',
|
||||
statusMessage: '',
|
||||
createdAt: '2026-03-01 09:15:00',
|
||||
updatedAt: '2026-09-18 16:42:30',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
pageSize: 15,
|
||||
groupOptions: [],
|
||||
},
|
||||
})
|
||||
}
|
||||
if (url === '/api/admin/shop-manage-groups') {
|
||||
return json(res, { success: true, data: { items: [] } })
|
||||
|
||||
Reference in New Issue
Block a user