feat(需求): ①全站分页pageSize统一默认10+10/20/50/100选项(9页默认值15/20→10+6个兜底常量统一+撞款台账升级OldPagination+商品类目树顶层标准分页) ②修复6页OldPagination缺import分页器不渲染(不符合ASIN/查询ASIN/最低价ASIN/生成记录/店铺密钥/店铺管理)+查询ASIN操作列按钮间距+最低价范围筛选number类型报错 ③e2e断言对齐现状(顶栏h1/移除收起按钮后规格同步)+国家显示中文断言固化

This commit is contained in:
2026-09-07 02:36:35 +08:00
parent 056abcd473
commit 678605e9fb
44 changed files with 590 additions and 175 deletions
@@ -11,13 +11,14 @@ import { invalidAsinDeleteText, invalidAsinLockedGroupId, recordSourceLabel } fr
import { createEmptyInvalidAsinForm, invalidAsinFormFromItem, validateInvalidAsinForm } from '../invalidasin/invalid-asin-form-model.ts'
import { fetchShopManageGroups } from '../shop/shop-manage-api.ts'
import type { ShopGroupOption } from '../shop/shop-dto.ts'
import OldPagination from '@/components/OldPagination.vue'
const session = useAdminSessionStore()
const loading = ref(false)
const rows = ref<InvalidAsinItem[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(15)
const pageSize = ref(10)
const groups = ref<ShopGroupOption[]>([])
const jumpPage = ref('')
const totalPages = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
@@ -29,7 +29,7 @@ const loading = ref(false)
const rows = ref<DedupeTotalItem[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(15)
const pageSize = ref(10)
const filter = reactive(createDedupeTotalFilterState())
const groups = ref<DedupeGroupOption[]>([])
const jumpPage = ref('')
@@ -58,16 +58,21 @@ const searching = computed(() => (keyword.value || '').trim().length > 0)
const dialogTitle = computed(() => (editingId.value == null ? '新增商品类目' : '编辑商品类目'))
const searchTotalPages = computed(() => Math.max(1, Math.ceil(searchTotal.value / searchPageSize.value)))
/** 拉取根级第一页(对齐 admin.js loadProductCategoryChildren(null) 懒加载根)。 */
/** 顶层翻页(服务端分页,10/20/50/100 可选;子级保留懒加载+加载更多对齐旧版)。 */
const rootPage = ref(1)
const rootPageSize = ref(PRODUCT_CATEGORY_DEFAULT_PAGE_SIZE)
const rootTotal = ref(0)
/** 拉取根级当前页(对齐 admin.js loadProductCategoryChildren(null) 懒加载根)。 */
async function loadTree(): Promise<void> {
loading.value = true
try {
const result = await fetchProductCategoryChildren(null, 1, PRODUCT_CATEGORY_DEFAULT_PAGE_SIZE)
const result = await fetchProductCategoryChildren(null, rootPage.value, rootPageSize.value)
const state = { page: result.page, total: result.total, hasMore: result.hasMore, loading: false }
childrenState.set(ROOT_KEY, state)
tree.value = result.hasMore
? [...result.items, makeCategoryLoadMoreNode(null, result.items.length, result.total)]
: result.items
// 顶层当前页直接作为树根(不含合成「加载更多」节点,翻页由 OldPagination 承担)。
tree.value = result.items
rootTotal.value = result.total
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '类目加载失败')
} finally {
@@ -75,6 +80,17 @@ async function loadTree(): Promise<void> {
}
}
function changeRootPage(p: number) {
rootPage.value = p
void loadTree()
}
function changeRootSize(size: number) {
rootPageSize.value = size
rootPage.value = 1
void loadTree()
}
/** 树形表格(对齐旧版 panel-product-categories):客户端展开状态 + 按展开扁平化行集。 */
interface TreeRow {
node: ProductCategoryNode
@@ -420,6 +436,7 @@ onMounted(loadTree)
</div>
<OldPagination v-if="searching" :total="searchTotal" :page="searchPage" :page-size="searchPageSize" @change="runSearch" @size-change="changeSearchSize" />
<OldPagination v-else-if="rootTotal > 0" :total="rootTotal" :page="rootPage" :page-size="rootPageSize" @change="changeRootPage" @size-change="changeRootSize" />
</section>
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="560px">
@@ -14,12 +14,13 @@ import { fetchQueryAsinDeleteImportProgress, startQueryAsinDeleteImport } from '
import { isAllowedExcelImportFile } from './import-progress-model.ts'
import { fetchShopManageGroups } from '../shop/shop-manage-api.ts'
import type { ShopGroupOption } from '../shop/shop-dto.ts'
import OldPagination from '@/components/OldPagination.vue'
const loading = ref(false)
const rows = ref<QueryAsinItem[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(15)
const pageSize = ref(10)
const groups = ref<ShopGroupOption[]>([])
const filter = reactive<QueryAsinFilterState>(createQueryAsinFilterState())
const jumpPage = ref('')
@@ -690,7 +691,11 @@ h3 {
color: #8b9aaa;
}
.asin-col-actions {
text-align: right;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
white-space: nowrap;
background: #ffffff;
box-shadow: -9px 0 12px -10px rgba(31, 48, 66, 0.45);
}
@@ -14,12 +14,13 @@ import { isAllowedExcelImportFile } from './import-progress-model.ts'
import { fetchShopNamesByGroup } from './query-asin-api.ts'
import { fetchShopManageGroups } from '../shop/shop-manage-api.ts'
import type { ShopGroupOption } from '../shop/shop-dto.ts'
import OldPagination from '@/components/OldPagination.vue'
const loading = ref(false)
const rows = ref<SkipPriceItem[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(15)
const pageSize = ref(10)
const groups = ref<ShopGroupOption[]>([])
const filter = reactive<SkipPriceFilterState>(createSkipPriceFilterState())
const jumpPage = ref('')
@@ -748,7 +749,11 @@ h3 {
font-variant-numeric: tabular-nums;
}
.asin-col-actions {
text-align: right;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
white-space: nowrap;
background: #ffffff;
box-shadow: -9px 0 12px -10px rgba(31, 48, 66, 0.45);
}
@@ -1,6 +1,6 @@
/** ASIN 数据中心共享分页/筛选类型(任务 61):与 Java 各列表接口(snake)对齐,纯逻辑。 */
export const ASIN_PAGE_DEFAULT_SIZE = 15
export const ASIN_PAGE_DEFAULT_SIZE = 10
export const ASIN_PAGE_MIN_PAGE = 1
export const ASIN_PAGE_MAX_SIZE = 200
@@ -1,7 +1,7 @@
/** 商品类目树/搜索模型(任务 78):解析 ProductCategoryListVo(嵌套 tree/path/level/childCount),纯逻辑。 */
import { unwrap } from '../../api/envelope.ts'
export const PRODUCT_CATEGORY_DEFAULT_PAGE_SIZE = 20
export const PRODUCT_CATEGORY_DEFAULT_PAGE_SIZE = 10
export interface ProductCategoryNode {
id: number
@@ -11,9 +11,9 @@ export interface SkipPriceFilterState {
asin: string
/** '' 表示不限;否则为 DE/UK/FR/IT/ES 之一。 */
country: string
/** 最低价区间输入原文;空串不限。 */
minimumPriceFrom: string
minimumPriceTo: string
/** 最低价区间输入原文;空串不限(原生 number input v-model 会转 number,需兼容)。 */
minimumPriceFrom: string | number
minimumPriceTo: string | number
}
export interface SkipPriceListParams {
@@ -42,9 +42,9 @@ export function createSkipPriceFilterState(): SkipPriceFilterState {
return { groupId: null, shopName: '', asin: '', country: '', minimumPriceFrom: '', minimumPriceTo: '' }
}
/** 非负可解析价格 → number;空/非法/负数返回 null。 */
export function normalizePriceInput(value: string): number | null {
const trimmed = (value || '').trim()
/** 非负可解析价格 → number;空/非法/负数返回 null。入参兼容 number(原生 number input 的 v-model 会转 number)。 */
export function normalizePriceInput(value: string | number): number | null {
const trimmed = String(value ?? '').trim()
if (!trimmed) return null
if (!/^\d+(\.\d+)?$/.test(trimmed)) return null
const num = Number(trimmed)
@@ -2,7 +2,7 @@
/** 数字人版本管理页 · 像素复刻旧版 admin.html panel-digital-human-version(自绘:面板头+上传、三态状态色、最新★、操作矩阵、数字页码分页)。
* script 逻辑沿用现有 Vue 实现(确认文案/上传进度/下载取链接);确认走原生 confirm 对齐旧版。 */
import { formatDateTime } from '@/utils/datetime'
import { computed, onMounted, ref } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import {
deleteDigitalHumanVersion,
@@ -28,7 +28,7 @@ const loading = ref(false)
const items = ref<DigitalHumanVersion[]>([])
/** 分页(对齐 admin.js:6311 pageSize=20 + 数字页码 + 跳转;Java 列表接口不分页,前端分页呈现)。 */
const pageSize = ref(20)
const pageSize = ref(10)
const page = ref(1)
/** 版本号搜索(客户端过滤)。 */
const versionKeyword = ref('')
@@ -56,9 +56,7 @@ async function load() {
loading.value = true
try {
items.value = (await fetchDigitalHumanVersions()).items
// 数据收缩后页码越界回钳。
const last = Math.max(Math.ceil(items.value.length / pageSize.value), 1)
if (page.value > last) page.value = last
// 页码越界回钳见下方 watch(items)
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '版本列表加载失败')
} finally {
@@ -76,6 +74,12 @@ function changeSize(size: number) {
page.value = 1
}
// 版本号搜索导致数据收缩时回钳页码,避免停在空页。
watch(items, () => {
const last = Math.max(Math.ceil(filteredItems.value.length / pageSize.value), 1)
if (page.value > last) page.value = last
})
function goJump(): void {
const n = Number.parseInt(jumpPage.value, 10)
if (Number.isNaN(n)) {
@@ -9,13 +9,14 @@ import type { HistoryRecordItem } from './history-dto.ts'
import { historyEmptyText, historyErrorText } from './history-feedback.ts'
import { historyPanelTypeLabel } from './history-type.ts'
import { ElMessage } from 'element-plus'
import OldPagination from '@/components/OldPagination.vue'
const loading = ref(false)
const userLoading = ref(false)
const rows = ref<HistoryRecordItem[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(15)
const pageSize = ref(10)
const userOptions = ref<HistoryUserOption[]>([])
const errorText = ref('')
const filter = reactive({ userId: '', timeStart: '', timeEnd: '' })
@@ -2,7 +2,7 @@
import { formatDateTime } from '@/utils/datetime'
/** 软件版本管理页 · 像素复刻旧版 admin.html panel-version(自绘:面板头+上传新版本、版本列表、全量无分页同旧版)。
* script 逻辑沿用现有 Vue 实现(上传弹窗成功留驻含链接)。 */
import { computed, onMounted, ref } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import OldPagination from '@/components/OldPagination.vue'
import { ElMessage } from 'element-plus'
import { fetchSoftwareVersions, uploadSoftwareVersion } from './version-api.ts'
@@ -24,6 +24,10 @@ const pageSize = ref(10)
const pagedVersions = computed(() => filteredItems.value.slice((page.value - 1) * pageSize.value, page.value * pageSize.value))
function changeVersionPage(p: number) { page.value = p }
function changeVersionSize(size: number) { pageSize.value = size; page.value = 1 }
// 版本号搜索导致数据收缩时回钳页码,避免停在空页。
watch(filteredItems, () => {
page.value = Math.min(page.value, Math.max(1, Math.ceil(filteredItems.value.length / pageSize.value)))
})
const uploadVisible = ref(false)
const uploading = ref(false)
@@ -1,7 +1,7 @@
/** 历史生成记录 DTO(任务 121):列表筛选/分页归一与类型边界;与 Java ImageHistoryController
* snake 契约对齐;历史页与后台版本/公开版本接口分离。纯逻辑。 */
export const HISTORY_DEFAULT_PAGE_SIZE = 15
export const HISTORY_DEFAULT_PAGE_SIZE = 10
export const HISTORY_MIN_PAGE = 1
export const HISTORY_MAX_PAGE_SIZE = 200
@@ -8,12 +8,13 @@ import { fetchShopKeyList, submitCreateShopKey, updateShopKey, deleteShopKey } f
import { emptyShopKeyForm, normalizeZiniaoToken } from './shop-key-form-model.ts'
import type { ShopKeyItem } from './shop-dto.ts'
import { whitelistStatusMeta } from './shop-key-model.ts'
import OldPagination from '@/components/OldPagination.vue'
const loading = ref(false)
const rows = ref<ShopKeyItem[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(15)
const pageSize = ref(10)
const jumpPage = ref('')
const totalPages = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
/** 筛选:备注名/紫鸟账号(用户要求店铺密钥列表补筛选)。 */
@@ -15,13 +15,14 @@ import {
} from './shop-manage-api.ts'
import { emptyShopManageForm } from './shop-manage-form-model.ts'
import type { ShopCredential, ShopGroupOption, ShopSummary } from './shop-dto.ts'
import OldPagination from '@/components/OldPagination.vue'
const session = useAdminSessionStore()
const loading = ref(false)
const rows = ref<ShopSummary[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(15)
const pageSize = ref(10)
const jumpPage = ref('')
const totalPages = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
@@ -31,12 +31,12 @@ const scope = ref<Scope>('global')
const selectedGroup = ref('')
const shopGroups = ref<ShopGroups>({ members: {}, shopGroupNames: {} })
const LEDGER_PAGE_SIZE = 20
/** 台账每页条数(全站统一:10/20/50/100 可选,默认 10)。 */
const ledgerPageSize = ref(10)
const ledgerItems = ref<LedgerRow[]>([])
const ledgerTotal = ref(0)
const ledgerPage = ref(1)
const ledgerLoading = ref(false)
const jumpInput = ref('')
const sortState = reactive<{ key: LedgerSortKey | ''; dir: 'asc' | 'desc' }>({ key: 'earliest', dir: 'asc' })
import OldPagination from '@/components/OldPagination.vue'
@@ -143,7 +143,7 @@ const matrixRows = computed(() =>
dupRows.value.slice((matrixPage.value - 1) * matrixPageSize.value, matrixPage.value * matrixPageSize.value),
)
const dupCardPage = ref(1)
const dupCardPageSize = ref(6)
const dupCardPageSize = ref(10)
const dupCardRows = computed(() =>
dupRows.value.slice((dupCardPage.value - 1) * dupCardPageSize.value, dupCardPage.value * dupCardPageSize.value),
)
@@ -152,10 +152,17 @@ const drawerPageSize = ref(10)
const drawerRows = computed(() =>
drawerAggRows.value.slice((drawerPage.value - 1) * drawerPageSize.value, drawerPage.value * drawerPageSize.value),
)
watch([dupRows, drawerOccurrences], () => {
/** 分组说明抽屉:分组统计表客户端分页(10/20/50/100 可选)。 */
const groupStatsPage = ref(1)
const groupStatsPageSize = ref(10)
const pagedGroupStats = computed(() =>
groupStats.value.slice((groupStatsPage.value - 1) * groupStatsPageSize.value, groupStatsPage.value * groupStatsPageSize.value),
)
watch([dupRows, drawerOccurrences, groupStats], () => {
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
groupStatsPage.value = Math.min(groupStatsPage.value, Math.max(1, Math.ceil(groupStats.value.length / groupStatsPageSize.value)))
})
const matrixHint = computed(() =>
@@ -220,7 +227,7 @@ async function loadLedger(page: number, resetSort = false) {
sortState.key = 'earliest'
sortState.dir = 'asc'
}
const result = await fetchDuplicateLedger({ view: '', ...activeFilterParams() }, page, LEDGER_PAGE_SIZE, sortState.key, sortState.dir)
const result = await fetchDuplicateLedger({ view: '', ...activeFilterParams() }, page, ledgerPageSize.value, sortState.key, sortState.dir)
ledgerItems.value = result.items
ledgerTotal.value = result.total
ledgerPage.value = result.page
@@ -349,17 +356,14 @@ function onSort(key: LedgerSortKey) {
loadLedger(1)
}
const totalPages = computed(() => Math.max(1, Math.ceil(ledgerTotal.value / LEDGER_PAGE_SIZE)))
const totalPages = computed(() => Math.max(1, Math.ceil(ledgerTotal.value / ledgerPageSize.value)))
function goPage(page: number) {
if (page < 1 || page > totalPages.value) return
loadLedger(page)
}
function jumpPage() {
const value = parseInt(jumpInput.value, 10)
if (!Number.isNaN(value) && value >= 1 && value <= totalPages.value) {
goPage(value)
jumpInput.value = ''
}
function changeLedgerSize(size: number) {
ledgerPageSize.value = size
loadLedger(1)
}
async function exportXlsx() {
@@ -594,6 +598,7 @@ onMounted(() => {
</div>
</div>
<div v-else class="dup-empty">当前范围下没有撞款ASIN</div>
<OldPagination v-if="dupRows.length > dupCardPageSize" :total="dupRows.length" :page="dupCardPage" :page-size="dupCardPageSize" @change="dupCardPage = $event" @size-change="(s: number) => { dupCardPageSize = s; dupCardPage = 1 }" />
<div class="dup-total"> <b>{{ dupRows.length }}</b> {{ scope === 'group' && selectedGroup ? `分组「${selectedGroup}」组内撞款ASIN` : '全店撞款ASIN' }}</div>
</template>
@@ -631,12 +636,7 @@ onMounted(() => {
调整筛选条件或触发重新分析后再查看
</div>
</div>
<div v-if="ledgerTotal" class="pager">
<span class="total"> <b>{{ ledgerTotal.toLocaleString() }}</b> · {{ ledgerPage }}/{{ totalPages }} </span>
<button class="pg-btn" type="button" :disabled="ledgerPage <= 1" @click="goPage(ledgerPage - 1)">上一页</button>
<button class="pg-btn" type="button" :disabled="ledgerPage >= totalPages" @click="goPage(ledgerPage + 1)">下一页</button>
<span class="jump">跳至 <input type="number" min="1" :max="totalPages" v-model="jumpInput" @keyup.enter="jumpPage" /> 页 <button class="btn btn-ghost btn-sm" type="button" @click="jumpPage">跳转</button></span>
</div>
<OldPagination v-if="ledgerTotal" :total="ledgerTotal" :page="ledgerPage" :page-size="ledgerPageSize" @change="goPage" @size-change="changeLedgerSize" />
</template>
</template>
@@ -679,6 +679,7 @@ onMounted(() => {
</tbody>
</table>
<div v-else class="dup-empty">暂无上架时间明细</div>
<OldPagination v-if="drawerAggRows.length > drawerPageSize" :total="drawerAggRows.length" :page="drawerPage" :page-size="drawerPageSize" @change="drawerPage = $event" @size-change="(s: number) => { drawerPageSize = s; drawerPage = 1 }" />
</div>
</aside>
@@ -696,7 +697,7 @@ onMounted(() => {
<table class="d-table">
<thead><tr><th>分组</th><th>店铺数</th><th>成员店铺</th><th>组内撞款</th></tr></thead>
<tbody>
<tr v-for="group in groupStats" :key="group.name">
<tr v-for="group in pagedGroupStats" :key="group.name">
<td class="cell-strong">{{ group.name }}</td>
<td class="num-cell">{{ group.shopCount }}</td>
<td><template v-for="shop in (shopGroups.members[group.name] || [])" :key="shop"><span class="badge store">{{ shop }}</span></template></td>
@@ -705,6 +706,7 @@ onMounted(() => {
<tr v-if="!groupStats.length"><td colspan="4" class="empty-cell">暂无分组</td></tr>
</tbody>
</table>
<OldPagination v-if="groupStats.length > groupStatsPageSize" :total="groupStats.length" :page="groupStatsPage" :page-size="groupStatsPageSize" @change="groupStatsPage = $event" @size-change="(s: number) => { groupStatsPageSize = s; groupStatsPage = 1 }" />
</div>
</aside>
@@ -929,20 +931,7 @@ onMounted(() => {
.dup-ref .dt-cell { font-size: 11.5px; color: var(--text2); }
.dup-ref .site-chip { border-radius: 5px; padding: 1px 7px; font-size: 11.5px; margin-right: 4px; margin-bottom: 2px; font-weight: 600; display: inline-block; white-space: nowrap; }
/* ---------- 分页 ---------- */
.dup-ref .pager {
display: flex; align-items: center; justify-content: flex-end; gap: 12px; margin-top: 16px;
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 16px;
box-shadow: var(--shadow);
}
.dup-ref .pager .total { color: var(--text2); font-size: 12.5px; margin-right: auto; }
.dup-ref .pager .total b { color: var(--text); font-size: 13px; }
.dup-ref .pager .pg-btn { height: 28px; padding: 0 12px; border: 1px solid var(--border-strong); border-radius: 6px; font-size: 12px; color: var(--text2); background: #fff; }
.dup-ref .pager .pg-btn:hover:not(:disabled) { border-color: var(--primary); color: var(--primary); }
.dup-ref .pager .pg-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.dup-ref .pager .jump { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text2); }
.dup-ref .pager .jump input { width: 52px; height: 28px; border: 1px solid var(--border-strong); border-radius: 6px; text-align: center; }
.dup-ref .pager .jump input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(46, 91, 230, 0.12); }
/* ---------- 分页:台账/分布/矩阵/详情/抽屉统一共享 OldPagination(自带条数选择),无自绘 pager ---------- */
/* ---------- 空态 / 加载 ---------- */
.dup-ref .empty-state { padding: 48px 20px; text-align: center; color: var(--text3); font-size: 13px; }
@@ -18,13 +18,14 @@ import {
import { grantedUserIds, type TaskPermissionItem } from './task-permission.ts'
import { imageVideoDownloadFilename, videoKeysOf } from './image-video-view.ts'
import { useAdminSessionStore } from '@/stores/admin-session'
import OldPagination from '@/components/OldPagination.vue'
const session = useAdminSessionStore()
const loading = ref(false)
const tasks = ref<ImageVideoRow[]>([])
const totalTasks = ref(0)
const page = ref(1)
const pageSize = ref(20)
const pageSize = ref(10)
const jumpPage = ref('')
const totalPages = computed(() => Math.max(1, Math.ceil(totalTasks.value / pageSize.value)))
@@ -39,6 +40,12 @@ const permissionVisible = ref(false)
const permissionItems = ref<TaskPermissionItem[]>([])
const permissionInitial = ref<number[]>([])
const permissionSaving = ref(false)
/** 权限弹窗用户表客户端分页(全量用户在表格内翻页,10/20/50/100 可选)。 */
const permPage = ref(1)
const permPageSize = ref(10)
const pagedPermissionItems = computed(() =>
permissionItems.value.slice((permPage.value - 1) * permPageSize.value, permPage.value * permPageSize.value),
)
const downloading = ref(false)
const downloadingKey = ref('')
@@ -243,6 +250,7 @@ async function openPermission() {
try {
permissionItems.value = await fetchImageVideoPermissionUsers()
permissionInitial.value = grantedUserIds(permissionItems.value)
permPage.value = 1
permissionVisible.value = true
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '权限用户加载失败')
@@ -385,7 +393,7 @@ onMounted(load)
<el-dialog v-model="permissionVisible" title="视频任务记录权限配置" width="620px">
<p class="dim">勾选允许查看视频任务记录的用户仅数据范围当前已授权 {{ permissionGrantedCount }} </p>
<el-table :data="permissionItems" border size="small" max-height="420">
<el-table :data="pagedPermissionItems" border size="small" max-height="420">
<el-table-column prop="id" label="ID" min-width="80" />
<el-table-column prop="username" label="用户名" min-width="180" />
<el-table-column label="授权" min-width="90" align="center">
@@ -394,6 +402,7 @@ onMounted(load)
</template>
</el-table-column>
</el-table>
<OldPagination v-if="permissionItems.length > permPageSize" :total="permissionItems.length" :page="permPage" :page-size="permPageSize" @change="permPage = $event" @size-change="(s: number) => { permPageSize = s; permPage = 1 }" />
<template #footer>
<el-button @click="permissionVisible = false">取消</el-button>
<el-button type="primary" :loading="permissionSaving" @click="savePermission">保存</el-button>
@@ -32,10 +32,9 @@ const loading = ref(false)
const groups = ref<ShopDataTaskGroup[]>([])
const totalShops = ref(0)
const page = ref(1)
// 店铺级一次全载(>200 家店铺时后端按页截断,可上调或恢复底部店铺级分页)。
const pageSize = ref(200)
const pageJump = ref('')
const totalPages = computed(() => Math.max(1, Math.ceil(totalShops.value / pageSize.value)))
// 卡片宫格分页:一行 3 个、一页 9 张卡片(在店铺分页的结果文件内翻页)。
// 卡片宫格分页:一行 3 个、一页 10 张卡片(在店铺分页的结果文件内翻页)。
const cardPage = ref(1)
const cardPageSize = ref(10)
const cardJump = ref('')
@@ -47,6 +46,12 @@ const selection = ref<Set<string>>(new Set())
const permissionVisible = ref(false)
const permissionItems = ref<TaskPermissionItem[]>([])
const permissionSaving = ref(false)
/** 权限弹窗用户表客户端分页(全量用户在表格内翻页,10/20/50/100 可选)。 */
const permPage = ref(1)
const permPageSize = ref(10)
const pagedPermissionItems = computed(() =>
permissionItems.value.slice((permPage.value - 1) * permPageSize.value, permPage.value * permPageSize.value),
)
const busyKey = ref('')
const deletingKey = ref('')
@@ -115,30 +120,6 @@ function reset() {
void load()
}
function changePage(next: number) {
if (next < 1 || next > totalPages.value) return
page.value = next
cardPage.value = 1
selection.value = new Set()
void load()
}
function changeShopSize(size: number) {
pageSize.value = size
page.value = 1
cardPage.value = 1
void load()
}
function goPageJump() {
const n = Number.parseInt(pageJump.value, 10)
if (Number.isNaN(n)) {
ElMessage.warning('请输入页码')
return
}
changePage(Math.min(Math.max(n, 1), totalPages.value))
}
const allResultRows = computed(() => groups.value.flatMap((group) => group.results))
const pagedResultRows = computed(() =>
allResultRows.value.slice((cardPage.value - 1) * cardPageSize.value, cardPage.value * cardPageSize.value),
@@ -261,6 +242,7 @@ function saveBlob(blob: Blob, filename: string) {
async function openPermission() {
try {
permissionItems.value = await fetchShopDataTaskPermissionUsers()
permPage.value = 1
permissionVisible.value = true
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '权限用户加载失败')
@@ -404,7 +386,7 @@ onMounted(load)
<el-dialog v-model="permissionVisible" title="店铺数据记录数据范围授权" width="620px">
<p class="dim">勾选允许查看/操作店铺数据记录的用户仅数据范围当前已授权 {{ grantedCount }} </p>
<el-table :data="permissionItems" border size="small" max-height="420">
<el-table :data="pagedPermissionItems" border size="small" max-height="420">
<el-table-column prop="id" label="ID" min-width="80" />
<el-table-column prop="username" label="用户名" min-width="180" />
<el-table-column label="授权" min-width="90" align="center">
@@ -413,6 +395,7 @@ onMounted(load)
</template>
</el-table-column>
</el-table>
<OldPagination v-if="permissionItems.length > permPageSize" :total="permissionItems.length" :page="permPage" :page-size="permPageSize" @change="permPage = $event" @size-change="(s: number) => { permPageSize = s; permPage = 1 }" />
<template #footer>
<el-button @click="permissionVisible = false">取消</el-button>
<el-button type="primary" :loading="permissionSaving" @click="savePermission">保存</el-button>
@@ -1,6 +1,6 @@
/** 重复检查明细筛选与分页(任务 117):与 /duplicate-check-items 的 snake 参数契约对齐;纯逻辑。 */
export const DUPLICATE_DEFAULT_PAGE_SIZE = 20
export const DUPLICATE_DEFAULT_PAGE_SIZE = 10
export const DUPLICATE_MIN_PAGE_SIZE = 10
export const DUPLICATE_MAX_PAGE_SIZE = 100
export const DUPLICATE_PAGE_MIN = 1
@@ -1,7 +1,7 @@
/** 视频任务筛选与分页(任务 102):图生视频任务列表筛选/分页归一,与 Java AdminImageVideoTaskController
* snake_case 查询契约对齐;纯逻辑。 */
export const IMAGE_VIDEO_DEFAULT_PAGE_SIZE = 20
export const IMAGE_VIDEO_DEFAULT_PAGE_SIZE = 10
export const IMAGE_VIDEO_MIN_PAGE_SIZE = 10
export const IMAGE_VIDEO_MAX_PAGE_SIZE = 100
export const IMAGE_VIDEO_PAGE_MIN = 1
@@ -1,6 +1,6 @@
/** 店铺数据任务筛选与分页(任务 109):与 Java AdminShopDataCrawlTasksController snake 契约对齐;纯逻辑。 */
export const SHOP_DATA_DEFAULT_PAGE_SIZE = 20
export const SHOP_DATA_DEFAULT_PAGE_SIZE = 10
export const SHOP_DATA_MIN_PAGE_SIZE = 10
export const SHOP_DATA_MAX_PAGE_SIZE = 100
export const SHOP_DATA_PAGE_MIN = 1