feat(需求): ①撞款页「店铺上架分布/撞款覆盖矩阵/撞款详情/明细抽屉」四块补分页(共享 OldPagination,10/20/50/100 可选) ②店铺数据记录去掉顶部店铺级分页、保留底部(店铺 pageSize 提到 200 一次全载)

This commit is contained in:
2026-09-07 00:04:34 +08:00
parent cd6beef388
commit e820811d98
2 changed files with 39 additions and 7 deletions
@@ -4,7 +4,7 @@
* 数据源保持自家后端:/duplicate-check-console(指标/店铺/撞款全集/分组统计) + /duplicate-check-ledger(台账分页) + /duplicate-check-export(Excel)。 * 数据源保持自家后端:/duplicate-check-console(指标/店铺/撞款全集/分组统计) + /duplicate-check-ledger(台账分页) + /duplicate-check-export(Excel)。
* 分组归属来自「店铺管理」真实店铺分组,本页只读展示,仅用于分组撞款范围切换。 * 分组归属来自「店铺管理」真实店铺分组,本页只读展示,仅用于分组撞款范围切换。
*/ */
import { computed, onMounted, reactive, ref } from 'vue' import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useAdminSessionStore } from '@/stores/admin-session' import { useAdminSessionStore } from '@/stores/admin-session'
import type { DuplicateItem, DuplicateOccurrence, DuplicateMetrics, DuplicateShop } from './duplicate-model.ts' import type { DuplicateItem, DuplicateOccurrence, DuplicateMetrics, DuplicateShop } from './duplicate-model.ts'
import { fetchDuplicateConsole, fetchDuplicateLedger, requestDuplicateExportBlob, type LedgerSortKey } from './duplicate-console-api.ts' import { fetchDuplicateConsole, fetchDuplicateLedger, requestDuplicateExportBlob, type LedgerSortKey } from './duplicate-console-api.ts'
@@ -39,6 +39,8 @@ const ledgerLoading = ref(false)
const jumpInput = ref('') const jumpInput = ref('')
const sortState = reactive<{ key: LedgerSortKey | ''; dir: 'asc' | 'desc' }>({ key: 'earliest', dir: 'asc' }) const sortState = reactive<{ key: LedgerSortKey | ''; dir: 'asc' | 'desc' }>({ key: 'earliest', dir: 'asc' })
import OldPagination from '@/components/OldPagination.vue'
const drawerVisible = ref(false) const drawerVisible = ref(false)
const drawerTitle = ref('') const drawerTitle = ref('')
const drawerBrand = ref('') const drawerBrand = ref('')
@@ -127,6 +129,35 @@ function itemBrand(item: DuplicateItem): string {
return '' return ''
} }
// ---- 撞款视图分页(分布/矩阵/撞款详情/抽屉明细;10/20/50/100 可选) ----
const distCount = computed(() => storeDistribution.value.list.length)
const distPage = ref(1)
const distPageSize = ref(10)
const pagedDistBars = computed(() =>
storeDistribution.value.list.slice((distPage.value - 1) * distPageSize.value, distPage.value * distPageSize.value),
)
const matrixPage = ref(1)
const matrixPageSize = ref(10)
const matrixRows = computed(() =>
dupRows.value.slice((matrixPage.value - 1) * matrixPageSize.value, matrixPage.value * matrixPageSize.value),
)
const dupCardPage = ref(1)
const dupCardPageSize = ref(6)
const dupCardRows = computed(() =>
dupRows.value.slice((dupCardPage.value - 1) * dupCardPageSize.value, dupCardPage.value * dupCardPageSize.value),
)
const drawerPage = ref(1)
const drawerPageSize = ref(10)
const drawerRows = computed(() =>
drawerAggRows.value.slice((drawerPage.value - 1) * drawerPageSize.value, drawerPage.value * drawerPageSize.value),
)
watch([dupRows, drawerOccurrences], () => {
matrixPage.value = Math.min(matrixPage.value, Math.max(1, Math.ceil(dupRows.value.length / matrixPageSize.value)))
dupCardPage.value = Math.min(dupCardPage.value, Math.max(1, Math.ceil(dupRows.value.length / dupCardPageSize.value)))
drawerPage.value = 1
})
const matrixHint = computed(() => const matrixHint = computed(() =>
scope.value === 'global' scope.value === 'global'
? '行 = 全店撞款ASIN,列 = 全部店铺;有数字表示该店在售此 ASIN,点击行可看上架时间明细' ? '行 = 全店撞款ASIN,列 = 全部店铺;有数字表示该店在售此 ASIN,点击行可看上架时间明细'
@@ -452,7 +483,7 @@ onMounted(() => {
<span class="strip-note">各店铺在本批站点中的上架记录量含重复扫描</span> <span class="strip-note">各店铺在本批站点中的上架记录量含重复扫描</span>
</div> </div>
<div class="strip-bars"> <div class="strip-bars">
<div v-for="bar in storeDistribution.list" :key="bar.name" class="s-bar"> <div v-for="bar in pagedDistBars" :key="bar.name" class="s-bar">
<div class="s-name"> <div class="s-name">
<span>{{ bar.name }}<span v-if="bar.group" class="s-grp"> · {{ bar.group }}</span></span> <span>{{ bar.name }}<span v-if="bar.group" class="s-grp"> · {{ bar.group }}</span></span>
<b>{{ bar.records.toLocaleString() }}</b> <b>{{ bar.records.toLocaleString() }}</b>
@@ -461,6 +492,7 @@ onMounted(() => {
<div class="s-meta">{{ bar.asins.toLocaleString() }} ASIN</div> <div class="s-meta">{{ bar.asins.toLocaleString() }} ASIN</div>
</div> </div>
</div> </div>
<OldPagination v-if="distCount > distPageSize" :total="distCount" :page="distPage" :page-size="distPageSize" @change="distPage = $event" @size-change="(s: number) => { distPageSize = s; distPage = 1 }" />
</section> </section>
<!-- 主区 Tabs + 操作 --> <!-- 主区 Tabs + 操作 -->
@@ -521,7 +553,7 @@ onMounted(() => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="item in dupRows" :key="item.asin" @click="openDupDrawer(item)"> <tr v-for="item in matrixRows" :key="item.asin" @click="openDupDrawer(item)">
<td><span class="asin-code">{{ item.asin }}</span></td> <td><span class="asin-code">{{ item.asin }}</span></td>
<td><span class="m-brand">{{ itemBrand(item) || '—' }}</span></td> <td><span class="m-brand">{{ itemBrand(item) || '—' }}</span></td>
<td v-for="shop in matrixShops" :key="shop" style="text-align:center"> <td v-for="shop in matrixShops" :key="shop" style="text-align:center">
@@ -538,10 +570,11 @@ onMounted(() => {
调整筛选条件或切换到其他分组查看 调整筛选条件或切换到其他分组查看
</div> </div>
</div> </div>
<OldPagination v-if="dupRows.length > matrixPageSize" :total="dupRows.length" :page="matrixPage" :page-size="matrixPageSize" @change="matrixPage = $event" @size-change="(s: number) => { matrixPageSize = s; matrixPage = 1 }" />
<div class="section-title" style="margin-top:24px">撞款详情 <span class="hint">同一 ASIN 在多家店铺的上架时间明细</span></div> <div class="section-title" style="margin-top:24px">撞款详情 <span class="hint">同一 ASIN 在多家店铺的上架时间明细</span></div>
<div v-if="dupRows.length" class="dup-grid"> <div v-if="dupRows.length" class="dup-grid">
<div v-for="item in dupRows" :key="item.asin" class="dup-card"> <div v-for="item in dupCardRows" :key="item.asin" class="dup-card">
<div class="dup-head"> <div class="dup-head">
<span class="asin-code">{{ item.asin }}</span> <span class="asin-code">{{ item.asin }}</span>
<span class="badge dup">{{ item.shopCount }}店在售</span> <span class="badge dup">{{ item.shopCount }}店在售</span>
@@ -636,7 +669,7 @@ onMounted(() => {
<tr><th>店铺</th><th>分组</th><th>站点</th><th>上架时间按时间升序</th><th>次数</th></tr> <tr><th>店铺</th><th>分组</th><th>站点</th><th>上架时间按时间升序</th><th>次数</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="row in drawerAggRows" :key="row.shopName + '|' + row.country"> <tr v-for="row in drawerRows" :key="row.shopName + '|' + row.country">
<td class="cell-strong">{{ row.shopName }}</td> <td class="cell-strong">{{ row.shopName }}</td>
<td><span class="d-stgrp">{{ row.groupName || '—' }}</span></td> <td><span class="d-stgrp">{{ row.groupName || '—' }}</span></td>
<td><span class="site-chip" :style="{ background: siteColor(row.country) + '1F', color: siteColor(row.country) }">{{ asinCountryLabel(row.country || '') }}</span></td> <td><span class="site-chip" :style="{ background: siteColor(row.country) + '1F', color: siteColor(row.country) }">{{ asinCountryLabel(row.country || '') }}</span></td>
@@ -32,7 +32,7 @@ const loading = ref(false)
const groups = ref<ShopDataTaskGroup[]>([]) const groups = ref<ShopDataTaskGroup[]>([])
const totalShops = ref(0) const totalShops = ref(0)
const page = ref(1) const page = ref(1)
const pageSize = ref(20) const pageSize = ref(200)
const pageJump = ref('') const pageJump = ref('')
const totalPages = computed(() => Math.max(1, Math.ceil(totalShops.value / pageSize.value))) const totalPages = computed(() => Math.max(1, Math.ceil(totalShops.value / pageSize.value)))
// 卡片宫格分页:一行 3 个、一页 9 张卡片(在店铺分页的结果文件内翻页)。 // 卡片宫格分页:一行 3 个、一页 9 张卡片(在店铺分页的结果文件内翻页)。
@@ -338,7 +338,6 @@ onMounted(load)
</div> </div>
<div class="sd-toolbar-side"> <div class="sd-toolbar-side">
<span class="sd-summary"> {{ totalShops }} 家店铺 · 本页 {{ resultFileCount() }} 个结果文件</span> <span class="sd-summary"> {{ totalShops }} 家店铺 · 本页 {{ resultFileCount() }} 个结果文件</span>
<OldPagination v-if="totalShops > 0" :total="totalShops" :page="page" :page-size="pageSize" @change="changePage" @size-change="changeShopSize" />
</div> </div>
</div> </div>