5b8105ec2b
用户管理、菜单管理、不符合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 页表头与真实时间值渲染
116 lines
5.0 KiB
TypeScript
116 lines
5.0 KiB
TypeScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readSource } from './helpers.ts'
|
|
import {
|
|
formatSkipPriceText,
|
|
skipPriceDisplayRows,
|
|
skipPriceRowEntries,
|
|
type SkipPriceItem,
|
|
} from '../src/pages/asin/skip-price-model.ts'
|
|
|
|
/** 对齐 admin.js:4839-4890 buildSkipPriceAsinEntries/renderSkipPriceAsinRows:纵向 rowspan + 最低价两位小数。 */
|
|
|
|
function item(id: number, overrides: Partial<SkipPriceItem> = {}): SkipPriceItem {
|
|
return {
|
|
id,
|
|
groupId: 1,
|
|
groupName: 'A组',
|
|
shopName: `店铺${id}`,
|
|
asinDe: '',
|
|
minimumPriceDe: null,
|
|
asinUk: '',
|
|
minimumPriceUk: null,
|
|
asinFr: '',
|
|
minimumPriceFr: null,
|
|
asinIt: '',
|
|
minimumPriceIt: null,
|
|
asinEs: '',
|
|
minimumPriceEs: null,
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
test('align_skip_price_row_entries_collects_asin_or_price_rows', () => {
|
|
const entries = skipPriceRowEntries(item(1, { asinDe: 'B0DE', minimumPriceFr: 12.5 }))
|
|
assert.deepEqual(entries, [
|
|
{ country: 'DE', asin: 'B0DE', minimumPrice: '' },
|
|
{ country: 'FR', asin: '', minimumPrice: '12.50' },
|
|
])
|
|
})
|
|
|
|
test('align_skip_price_row_entries_empty_shop_yields_single_dash_row', () => {
|
|
assert.deepEqual(skipPriceRowEntries(item(2)), [{ country: null, asin: '', minimumPrice: '' }])
|
|
})
|
|
|
|
test('align_skip_price_price_text_two_decimals', () => {
|
|
// 对齐 formatSkipPriceMinimumPrice:数字→toFixed(2),空→'',非法→原串。
|
|
assert.equal(formatSkipPriceText(3), '3.00')
|
|
assert.equal(formatSkipPriceText(12.5), '12.50')
|
|
assert.equal(formatSkipPriceText(null), '')
|
|
assert.equal(formatSkipPriceText(undefined), '')
|
|
assert.equal(formatSkipPriceText('x' as unknown as number), 'x')
|
|
})
|
|
|
|
test('align_skip_price_display_rows_merge_and_numbering', () => {
|
|
const rows = skipPriceDisplayRows([item(1, { asinDe: 'B0DE', asinUk: 'B0UK' }), item(2)], 1)
|
|
assert.equal(rows.length, 3)
|
|
const [r0, r1, r2] = rows
|
|
assert.equal(r0.rowNo, 1)
|
|
assert.equal(r0.rowspan, 2)
|
|
assert.equal(r0.isFirst, true)
|
|
assert.equal(r0.asin, 'B0DE')
|
|
assert.equal(r1.isFirst, false)
|
|
assert.equal(r1.country, 'UK')
|
|
assert.equal(r2.rowNo, 2)
|
|
assert.equal(r2.country, null)
|
|
assert.equal(new Set(rows.map((r) => r.key)).size, rows.length)
|
|
})
|
|
|
|
test('align_skip_price_display_rows_empty_input', () => {
|
|
assert.deepEqual(skipPriceDisplayRows([], 1), [])
|
|
})
|
|
|
|
test('align_skip_price_create_api_wiring', () => {
|
|
const api = readSource('src/pages/asin/skip-price-api.ts')
|
|
assert.match(api, /createSkipPriceAsin/, '含新增适配函数')
|
|
assert.match(api, /skip-price-asins/, '端点正确')
|
|
assert.match(api, /minimumPriceMappings/, '载荷含逐国最低价映射')
|
|
})
|
|
|
|
test('align_skip_price_page_layout_wiring', () => {
|
|
const page = readSource('src/pages/asin/SkipPricePage.vue')
|
|
assert.match(page, /新增 ASIN/, '顶栏补新增 ASIN 按钮')
|
|
assert.match(page, /:rowspan="row\.rowspan"/, '表格用原生 rowspan 合并(像素复刻旧版)')
|
|
assert.match(page, /CopyText/, 'ASIN 单元格点击复制')
|
|
// 需求变更:后台管理列表统一展示「创建时间 + 更新时间」,覆盖早期「不加更新时间」的像素对齐。
|
|
assert.match(page, /创建时间/, '表格含创建时间列')
|
|
assert.match(page, /更新时间/, '表格含更新时间列')
|
|
assert.match(page, /skipPriceDisplayRows/, '行展开走纯模型')
|
|
assert.match(page, /请完整填写分组、店铺名、国家和 ASIN/, '新增弹窗校验文案对齐')
|
|
assert.match(page, /最低价格式不正确/, '最低价格式校验对齐')
|
|
assert.match(page, /请先选择分组/, '店铺下拉未选分组时占位对齐')
|
|
})
|
|
|
|
test('align_skip_price_filter_price_range_same_row', () => {
|
|
// 验收反馈:筛选条件「最低价」的 ≥/≤ 输入并排同一排(price-range-row),不换行拆开。
|
|
const page = readSource('src/pages/asin/SkipPricePage.vue')
|
|
assert.match(page, /price-range-row/, '最低价范围筛选并排容器存在')
|
|
assert.match(page, /placeholder="最低价 ≥"/, '下限占位文案(旧版)')
|
|
assert.match(page, /placeholder="最高价 ≤"/, '上限占位文案(旧版)')
|
|
assert.ok(!page.includes('label="最低价 ≥"'), '不再有独立的最低价 ≥ 筛选项')
|
|
})
|
|
|
|
test('align_skip_price_action_delete_button_wiring', () => {
|
|
// 操作列新增整条删除:确认弹窗 + DELETE /skip-price-asins/{id} + 删空页回退。
|
|
const page = readSource('src/pages/asin/SkipPricePage.vue')
|
|
assert.match(page, /btn-danger/, '删除按钮使用危险样式')
|
|
assert.match(page, /@click="removeRow/, '按钮绑定整条删除')
|
|
assert.match(page, /ElMessageBox\.confirm/, '删除前二次确认')
|
|
assert.match(page, /确定删除「/, '确认文案含店铺名')
|
|
assert.match(page, /deleteSkipPriceAsin\(item\.id\)/, '调用整条删除 API')
|
|
assert.match(page, /page\.value -= 1/, '删空当前页后回退一页')
|
|
const api = readSource('src/pages/asin/skip-price-detail-api.ts')
|
|
assert.match(api, /deleteSkipPriceAsin/, '导出整条删除函数')
|
|
assert.match(api, /SKIP_PRICE_DETAIL_ENDPOINT}\/\$\{id\}/, '删除端点指向整条记录')
|
|
})
|