task-273(验收反馈): 店铺数据卡片宫格排(一行3个/一页9张+卡片分页)

This commit is contained in:
2026-09-06 01:48:57 +08:00
parent 8add3ceaa4
commit 0315a3a93d
4 changed files with 65 additions and 6 deletions
@@ -30,6 +30,9 @@ const groups = ref<ShopDataTaskGroup[]>([])
const totalShops = ref(0) const totalShops = ref(0)
const page = ref(1) const page = ref(1)
const pageSize = 20 const pageSize = 20
// 卡片宫格分页:一行 3 个、一页 9 张卡片(在店铺分页的结果文件内翻页)。
const cardPage = ref(1)
const cardPageSize = 9
const filter = reactive<ShopDataFilter & { dateRange: string[] }>({ ...createShopDataFilter(), dateRange: [] }) const filter = reactive<ShopDataFilter & { dateRange: string[] }>({ ...createShopDataFilter(), dateRange: [] })
const selection = ref<Set<string>>(new Set()) const selection = ref<Set<string>>(new Set())
@@ -90,8 +93,15 @@ function reset() {
} }
const allResultRows = computed(() => groups.value.flatMap((group) => group.results)) const allResultRows = computed(() => groups.value.flatMap((group) => group.results))
const pagedResultRows = computed(() =>
allResultRows.value.slice((cardPage.value - 1) * cardPageSize, cardPage.value * cardPageSize),
)
const selectionCount = computed(() => countShopDataSelection(selection.value)) const selectionCount = computed(() => countShopDataSelection(selection.value))
function changeCardPage(p: number) {
cardPage.value = p
}
function resultFileCount(): number { function resultFileCount(): number {
return groups.value.reduce((sum, group) => sum + group.results.length, 0) return groups.value.reduce((sum, group) => sum + group.results.length, 0)
} }
@@ -274,12 +284,12 @@ onMounted(load)
:total="totalShops" :total="totalShops"
:page-size="pageSize" :page-size="pageSize"
:current-page="page" :current-page="page"
@current-change="(p: number) => { page = p; selection = new Set(); load() }" @current-change="(p: number) => { page = p; cardPage = 1; selection = new Set(); load() }"
/> />
</div> </div>
<div v-if="allResultRows.length" class="shop-card-grid"> <div v-if="allResultRows.length" class="shop-card-grid">
<article v-for="row in allResultRows" :key="row.resultId || `${row.taskNo}-${row.filename}`" class="shop-card"> <article v-for="row in pagedResultRows" :key="row.resultId || `${row.taskNo}-${row.filename}`" class="shop-card">
<header class="shop-card-head"> <header class="shop-card-head">
<span class="shop-name" :title="row.shopName || '-'">{{ row.shopName || '-' }}</span> <span class="shop-name" :title="row.shopName || '-'">{{ row.shopName || '-' }}</span>
</header> </header>
@@ -335,6 +345,16 @@ onMounted(load)
</div> </div>
</article> </article>
</div> </div>
<div v-if="allResultRows.length > cardPageSize" class="table-footer">
<el-pagination
background
layout="prev, pager, next"
:total="allResultRows.length"
:page-size="cardPageSize"
:current-page="cardPage"
@current-change="changeCardPage"
/>
</div>
<el-empty v-else-if="!loading" description="暂无符合条件的店铺数据任务" /> <el-empty v-else-if="!loading" description="暂无符合条件的店铺数据任务" />
@@ -366,7 +386,7 @@ onMounted(load)
.f-item.wide { width: 340px; } .f-item.wide { width: 340px; }
.f-item.btn-row { flex-direction: row; align-items: flex-end; gap: 8px; width: auto; } .f-item.btn-row { flex-direction: row; align-items: flex-end; gap: 8px; width: auto; }
.shop-summary-row { display: flex; justify-content: space-between; align-items: center; color: var(--el-text-color-secondary); font-size: 13px; } .shop-summary-row { display: flex; justify-content: space-between; align-items: center; color: var(--el-text-color-secondary); font-size: 13px; }
.shop-card-grid { display: flex; flex-direction: column; gap: 14px; } .shop-card-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.shop-card { border: 1px solid var(--el-border-color); border-radius: 12px; background: #fff; box-shadow: 0 1px 2px rgba(39, 67, 94, 0.04), 0 14px 30px -24px rgba(39, 67, 94, 0.28); overflow: hidden; } .shop-card { border: 1px solid var(--el-border-color); border-radius: 12px; background: #fff; box-shadow: 0 1px 2px rgba(39, 67, 94, 0.04), 0 14px 30px -24px rgba(39, 67, 94, 0.28); overflow: hidden; }
.shop-card-head { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--el-border-color-lighter); } .shop-card-head { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--el-border-color-lighter); }
.shop-name { font-weight: 600; color: var(--el-text-color-primary); } .shop-name { font-weight: 600; color: var(--el-text-color-primary); }
@@ -6,8 +6,8 @@ import { readSource } from './helpers.ts'
test('align_shop_data_cards_per_task', () => { test('align_shop_data_cards_per_task', () => {
const page = readSource('src/pages/tasks/ShopDataTasksPage.vue') const page = readSource('src/pages/tasks/ShopDataTasksPage.vue')
// 每任务一张卡:以 resultRow 迭代渲染卡片,不再按店铺聚合。 // 每任务一张卡:以 resultRow 迭代渲染卡片(宫格分页后绑定 pagedResultRows,不再按店铺聚合。
assert.match(page, /v-for="row in allResultRows"/, '逐任务卡片渲染') assert.match(page, /v-for="row in pagedResultRows"/, '逐任务卡片渲染')
assert.doesNotMatch(page, /v-for="group in groups"/, '去掉按店铺聚合卡片') assert.doesNotMatch(page, /v-for="group in groups"/, '去掉按店铺聚合卡片')
// 卡片结构:店铺名头 + 任务号/状态 + 分组/最新/国家/文件 信息行 + 下载文件/删除。 // 卡片结构:店铺名头 + 任务号/状态 + 分组/最新/国家/文件 信息行 + 下载文件/删除。
assert.match(page, /任务 {{ row\.taskNo \|\| row\.resultId }}|任务 {{ row\.taskNo/, '卡片含任务号') assert.match(page, /任务 {{ row\.taskNo \|\| row\.resultId }}|任务 {{ row\.taskNo/, '卡片含任务号')
@@ -0,0 +1,39 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { readSource } from './helpers.ts'
// 验收反馈:店铺数据记录卡片用宫格排(一行 3 个、一页 9 张卡片)+ 分页组件翻页。
const PAGE = 'src/pages/tasks/ShopDataTasksPage.vue'
test('align_shop_data_grid_normal_primary_path', () => {
const src = readSource(PAGE)
assert.match(src, /grid-template-columns:\s*repeat\(3,\s*1fr\)/, '卡片容器应为 3 列宫格')
})
test('align_shop_data_grid_normal_variant_input', () => {
const src = readSource(PAGE)
assert.match(src, /cardPageSize\s*=\s*9|pageSize\s*=\s*9/, '卡片分页每页 9 张')
assert.match(src, /cardPage\b/, '应有卡片页状态')
assert.match(src, /pagedResultRows|cardPageRows/, '卡片应绑定按 9 张切片的数据')
})
test('align_shop_data_grid_boundary_empty_input', () => {
// 卡片区下方有翻页组件(prev/next),与店铺分页并存。
const src = readSource(PAGE)
const count = (src.match(/el-pagination/g) || []).length
assert.ok(count >= 2, '页面应含店铺分页 + 卡片分页两处 el-pagination')
assert.match(src, /layout="prev, pager, next"/, '卡片分页含上一页/下一页')
})
test('align_shop_data_grid_boundary_single_item', () => {
// 卡片渲染绑定分页后的数组而非全量。
const src = readSource(PAGE)
assert.match(src, /v-for="row in pagedResultRows"/, '卡片 v-for 使用分页后数组')
})
test('align_shop_data_grid_repeated_operation_is_idempotent', () => {
// 切店铺页时卡片页重置为第一页,避免空页。
const src = readSource(PAGE)
assert.match(src, /cardPage\s*(?:\.value)?\s*=\s*1/, '切页需重置卡片页到 1')
})
+1 -1
View File
@@ -10,7 +10,7 @@ test('test_task_266_view_normal_primary_path', () => {
// 视图已改为「每条任务一张卡」(对齐 admin.js renderShopDataRecordTable)。 // 视图已改为「每条任务一张卡」(对齐 admin.js renderShopDataRecordTable)。
const page = readSource('src/pages/tasks/ShopDataTasksPage.vue') const page = readSource('src/pages/tasks/ShopDataTasksPage.vue')
assert.match(page, /shop-card-grid/, '需任务卡片网格') assert.match(page, /shop-card-grid/, '需任务卡片网格')
assert.match(page, /v-for="row in allResultRows"/, '逐任务渲染卡片') assert.match(page, /v-for="row in pagedResultRows"/, '逐任务渲染卡片')
assert.match(page, /shop-card-task-no/, '卡片含任务号') assert.match(page, /shop-card-task-no/, '卡片含任务号')
assert.match(page, /暂无符合条件的店铺数据任务/, '空态文案') assert.match(page, /暂无符合条件的店铺数据任务/, '空态文案')
}) })