task-254(admin.html观感对齐): 去重汇总页对齐(筛选扩充/行编辑删除/导入删除导入轮询/导出)
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
<script setup lang="ts">
|
||||
/** 数据去重总数据(registry):对齐 admin panel-dedupe-total-data —— 扩充筛选(用户名/日期/分组)、行编辑/删除、新增/删除导入(轮询)、导出。 */
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
fetchDedupeTotalList,
|
||||
updateDedupeTotal,
|
||||
deleteDedupeTotal,
|
||||
fetchDedupeTotalGroups,
|
||||
type DedupeGroupOption,
|
||||
} from './dedupe-total-api.ts'
|
||||
import type { DedupeTotalItem } from './dedupe-total-model.ts'
|
||||
import { ASIN_COUNTRY_CODES, asinCountryLabel } from './asin-country.ts'
|
||||
import { createDedupeTotalFilterState, toDedupeListParams } from './dedupe-total-filter.ts'
|
||||
import {
|
||||
fetchDedupeDeleteImportProgress,
|
||||
fetchDedupeImportProgress,
|
||||
startDedupeDeleteImport,
|
||||
startDedupeImport,
|
||||
} from './dedupe-import-api.ts'
|
||||
import { importOutcomeText, importTaskFinished, isAllowedImportFile } from './dedupe-import-model.ts'
|
||||
import type { DedupeImportProgress } from './dedupe-import-model.ts'
|
||||
import { toExportUrl } from './dedupe-total-export.ts'
|
||||
|
||||
const loading = ref(false)
|
||||
const rows = ref<DedupeTotalItem[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = 15
|
||||
const filter = reactive(createDedupeTotalFilterState())
|
||||
const groups = ref<DedupeGroupOption[]>([])
|
||||
|
||||
const editVisible = ref(false)
|
||||
const editSaving = ref(false)
|
||||
const editTarget = ref<DedupeTotalItem | null>(null)
|
||||
const editValue = ref('')
|
||||
const editGroupId = ref<number | null>(null)
|
||||
|
||||
type ImportMode = 'add' | 'delete'
|
||||
const importVisible = ref(false)
|
||||
const importMode = ref<ImportMode>('add')
|
||||
const importGroupId = ref<number | null>(null)
|
||||
const importFile = ref<File | null>(null)
|
||||
const importRunning = ref(false)
|
||||
const importProgress = ref('')
|
||||
|
||||
const exporting = ref(false)
|
||||
|
||||
async function load(): Promise<void> {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await fetchDedupeTotalList(toDedupeListParams(filter, page.value, pageSize))
|
||||
rows.value = result.items
|
||||
total.value = result.total
|
||||
if (result.page >= 1) page.value = result.page
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '去重数据加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function apply(): void {
|
||||
page.value = 1
|
||||
void load()
|
||||
}
|
||||
|
||||
function reset(): void {
|
||||
Object.assign(filter, createDedupeTotalFilterState())
|
||||
page.value = 1
|
||||
void load()
|
||||
}
|
||||
|
||||
function openEdit(row: DedupeTotalItem): void {
|
||||
editTarget.value = row
|
||||
editValue.value = row.dataValue
|
||||
editGroupId.value = row.groupId
|
||||
editVisible.value = true
|
||||
}
|
||||
|
||||
async function saveEdit(): Promise<void> {
|
||||
const value = editValue.value.trim()
|
||||
const target = editTarget.value
|
||||
if (!target) return
|
||||
if (!value) {
|
||||
ElMessage.warning('请填写总数据值')
|
||||
return
|
||||
}
|
||||
if (!editGroupId.value) {
|
||||
ElMessage.warning('请选择分组')
|
||||
return
|
||||
}
|
||||
editSaving.value = true
|
||||
try {
|
||||
await updateDedupeTotal(target.id, value, editGroupId.value)
|
||||
editVisible.value = false
|
||||
ElMessage.success('保存成功')
|
||||
await load()
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '保存失败')
|
||||
} finally {
|
||||
editSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function removeRow(row: DedupeTotalItem): Promise<void> {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除总数据“${row.dataValue}”吗?`, '删除总数据', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '确定删除',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
await deleteDedupeTotal(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
await load()
|
||||
} catch (error) {
|
||||
if (error !== 'cancel' && error !== 'close') {
|
||||
ElMessage.error(error instanceof Error ? error.message : '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetImport(): void {
|
||||
importGroupId.value = null
|
||||
importFile.value = null
|
||||
importProgress.value = ''
|
||||
importRunning.value = false
|
||||
}
|
||||
|
||||
function openImportAdd(): void {
|
||||
importMode.value = 'add'
|
||||
resetImport()
|
||||
importVisible.value = true
|
||||
}
|
||||
|
||||
function openImportDelete(): void {
|
||||
importMode.value = 'delete'
|
||||
resetImport()
|
||||
importVisible.value = true
|
||||
}
|
||||
|
||||
function onPickFile(event: Event): void {
|
||||
const input = event.target as HTMLInputElement
|
||||
importFile.value = input.files?.[0] ?? null
|
||||
}
|
||||
|
||||
function importValid(): boolean {
|
||||
if (!importGroupId.value) {
|
||||
ElMessage.warning('请先选择数据权限分组')
|
||||
return false
|
||||
}
|
||||
if (!importFile.value) {
|
||||
ElMessage.warning('请选择 Excel 文件')
|
||||
return false
|
||||
}
|
||||
if (!isAllowedImportFile(importFile.value.name)) {
|
||||
ElMessage.warning('仅支持 .xlsx/.xls 文件')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
async function pollImport(poll: () => Promise<DedupeImportProgress>): Promise<DedupeImportProgress> {
|
||||
for (let i = 0; i < 300; i += 1) {
|
||||
const progress = await poll()
|
||||
importProgress.value = importOutcomeText(progress)
|
||||
if (importTaskFinished(progress)) return progress
|
||||
await sleep(1000)
|
||||
}
|
||||
throw new Error('查询导入进度超时,请稍后刷新列表确认结果')
|
||||
}
|
||||
|
||||
async function submitImport(): Promise<void> {
|
||||
if (!importValid()) return
|
||||
const file = importFile.value as File
|
||||
const groupId = importGroupId.value as number
|
||||
if (importMode.value === 'delete') {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定按 Excel 中的 ASIN 批量删除匹配的总数据吗?', '删除导入', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '确定删除',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
const mode = importMode.value
|
||||
importRunning.value = true
|
||||
importProgress.value = mode === 'add' ? '正在上传并解析文件…' : '正在上传删除清单…'
|
||||
try {
|
||||
const started =
|
||||
mode === 'add' ? await startDedupeImport(file, groupId) : await startDedupeDeleteImport(file, groupId)
|
||||
const poll = mode === 'add' ? () => fetchDedupeImportProgress(started) : () => fetchDedupeDeleteImportProgress(started)
|
||||
const finished = await pollImport(poll)
|
||||
importVisible.value = false
|
||||
resetImport()
|
||||
if (finished.status === 'success') {
|
||||
ElMessage.success(mode === 'add' ? '导入完成' : '删除导入完成')
|
||||
await load()
|
||||
} else {
|
||||
ElMessage.error(importOutcomeText(finished))
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '导入失败')
|
||||
} finally {
|
||||
importRunning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function doExport(): void {
|
||||
const { startDate, endDate } = filter
|
||||
if (startDate && endDate && startDate > endDate) {
|
||||
ElMessage.warning('开始日期不能晚于结束日期')
|
||||
return
|
||||
}
|
||||
exporting.value = true
|
||||
window.setTimeout(() => {
|
||||
const anchor = document.createElement('a')
|
||||
anchor.href = toExportUrl(filter)
|
||||
document.body.appendChild(anchor)
|
||||
anchor.click()
|
||||
anchor.remove()
|
||||
exporting.value = false
|
||||
ElMessage.success('导出文件已开始下载')
|
||||
}, 120)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void load()
|
||||
fetchDedupeTotalGroups().then((items) => {
|
||||
groups.value = items
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-stack">
|
||||
<div class="page-heading">
|
||||
<div>
|
||||
<h2>数据去重总数据</h2>
|
||||
<p>去重总数据的 ASIN 值台账,可按 ASIN/用户名/分组/国家/日期筛选,支持 Excel 导入与导出。</p>
|
||||
</div>
|
||||
<div class="heading-actions">
|
||||
<el-button type="primary" @click="openImportAdd">新增导入</el-button>
|
||||
<el-button @click="openImportDelete">删除导入</el-button>
|
||||
<el-button :loading="exporting" @click="doExport">导出</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-card shadow="never">
|
||||
<div class="filter-grid">
|
||||
<div class="f-item">
|
||||
<label>ASIN / 关键字</label>
|
||||
<el-input v-model="filter.keyword" placeholder="模糊搜索值" clearable @keyup.enter="apply" />
|
||||
</div>
|
||||
<div class="f-item">
|
||||
<label>用户名(上传人)</label>
|
||||
<el-input v-model="filter.username" placeholder="输入用户名关键字" clearable @keyup.enter="apply" />
|
||||
</div>
|
||||
<div class="f-item">
|
||||
<label>分组</label>
|
||||
<el-select v-model="filter.groupId" placeholder="全部分组" clearable>
|
||||
<el-option v-for="group in groups" :key="group.id" :label="group.name" :value="group.id" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="f-item">
|
||||
<label>国家</label>
|
||||
<el-select v-model="filter.country" placeholder="全部" clearable>
|
||||
<el-option v-for="code in ASIN_COUNTRY_CODES" :key="code" :label="asinCountryLabel(code)" :value="code" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="f-item">
|
||||
<label>开始日期</label>
|
||||
<el-date-picker v-model="filter.startDate" type="date" value-format="YYYY-MM-DD" placeholder="开始日期" style="width: 100%" />
|
||||
</div>
|
||||
<div class="f-item">
|
||||
<label>结束日期</label>
|
||||
<el-date-picker v-model="filter.endDate" type="date" value-format="YYYY-MM-DD" placeholder="结束日期" style="width: 100%" />
|
||||
</div>
|
||||
<div class="f-item btn-row">
|
||||
<el-button type="primary" @click="apply">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<el-table v-loading="loading" :data="rows" stripe border>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="dataValue" label="ASIN" min-width="170" />
|
||||
<el-table-column label="国家" width="100" align="center">
|
||||
<template #default="{ row }">{{ asinCountryLabel((row as DedupeTotalItem).country || '') }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="username" label="用户名" width="140" />
|
||||
<el-table-column label="分组" width="150">
|
||||
<template #default="{ row }">{{ (row as DedupeTotalItem).groupName || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdAt" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openEdit(row as DedupeTotalItem)">编辑</el-button>
|
||||
<el-button link type="danger" @click="removeRow(row as DedupeTotalItem)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="table-footer">
|
||||
<span>共 {{ total.toLocaleString() }} 条</span>
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next, jumper"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:current-page="page"
|
||||
@current-change="(p: number) => { page = p; void load() }"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="editVisible" title="编辑总数据" width="480px">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="总数据值(ASIN)">
|
||||
<el-input v-model="editValue" placeholder="请输入总数据值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分组">
|
||||
<el-select v-model="editGroupId" placeholder="请选择分组" style="width: 100%">
|
||||
<el-option v-for="group in groups" :key="group.id" :label="group.name" :value="group.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="editVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="editSaving" @click="saveEdit">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="importVisible" :title="importMode === 'add' ? '导入去重数据' : '删除导入(按 Excel 批量删除)'" width="520px">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="数据权限分组">
|
||||
<el-select v-model="importGroupId" placeholder="请选择分组" style="width: 100%">
|
||||
<el-option v-for="group in groups" :key="group.id" :label="group.name" :value="group.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Excel 文件">
|
||||
<input type="file" accept=".xlsx,.xls" :disabled="importRunning" @change="onPickFile" />
|
||||
<p class="import-tip">{{ importMode === 'add' ? '上传 Excel 按 ASIN 新增到所选分组,仅支持 .xlsx/.xls。' : 'Excel 第一列 ASIN 将按分组批量删除匹配总数据,仅支持 .xlsx/.xls。' }}</p>
|
||||
</el-form-item>
|
||||
<el-alert v-if="importProgress" :title="importProgress" type="info" :closable="false" show-icon />
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="importRunning" @click="importVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="importRunning" @click="submitImport">
|
||||
{{ importMode === 'add' ? '开始导入' : '开始删除导入' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page-heading { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
|
||||
.heading-actions { display: flex; gap: 8px; flex: none; }
|
||||
.filter-grid { display: flex; flex-wrap: wrap; gap: 12px 18px; }
|
||||
.f-item { display: flex; flex-direction: column; gap: 6px; width: 200px; }
|
||||
.f-item label { color: var(--el-text-color-secondary); font-size: 12px; }
|
||||
.f-item.btn-row { flex-direction: row; align-items: flex-end; gap: 8px; width: auto; }
|
||||
.table-footer { display: flex; justify-content: space-between; align-items: center; padding-top: 14px; }
|
||||
.table-footer span { color: var(--el-text-color-secondary); font-size: 12.5px; }
|
||||
.import-tip { margin: 6px 0 0; color: var(--el-text-color-secondary); font-size: 12px; line-height: 1.6; }
|
||||
</style>
|
||||
@@ -1,12 +1,63 @@
|
||||
/** 去重汇总列表查询适配(任务 62):GET /api/admin/dedupe-total-data + 共享筛选归一。 */
|
||||
import { http } from '@/api/http'
|
||||
import { unwrap } from '@/api/envelope'
|
||||
import { normalizeAsinPageParams, toAsinPageQuery, type AsinListParams } from './asin-filter'
|
||||
import { parseDedupeTotalPage, type DedupeTotalPageResult } from './dedupe-total-model'
|
||||
|
||||
export const DEDUPE_TOTAL_ENDPOINT = '/api/admin/dedupe-total-data'
|
||||
/** 数据权限分组选项源(与店铺/去重共用同一分组集)。 */
|
||||
export const DEDUPE_GROUPS_ENDPOINT = '/api/admin/shop-manage-groups'
|
||||
|
||||
export async function fetchDedupeTotalList(params: Partial<AsinListParams> = {}): Promise<DedupeTotalPageResult> {
|
||||
const normalized = normalizeAsinPageParams(params)
|
||||
const { data } = await http.get<unknown>(DEDUPE_TOTAL_ENDPOINT, { params: toAsinPageQuery(normalized) })
|
||||
return parseDedupeTotalPage(data)
|
||||
}
|
||||
|
||||
export interface DedupeGroupOption {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
/** 编辑单行去重总数据(ASIN 值 + 分组):PUT /dedupe-total-data/{id}。 */
|
||||
export async function updateDedupeTotal(id: number, dataValue: string, groupId: number): Promise<void> {
|
||||
const { data } = await http.put<unknown>(`${DEDUPE_TOTAL_ENDPOINT}/${id}`, {
|
||||
data_value: dataValue,
|
||||
group_id: groupId,
|
||||
})
|
||||
unwrap<unknown>(data)
|
||||
}
|
||||
|
||||
/** 删除单行去重总数据:DELETE /dedupe-total-data/{id}。 */
|
||||
export async function deleteDedupeTotal(id: number): Promise<void> {
|
||||
const { data } = await http.delete<unknown>(`${DEDUPE_TOTAL_ENDPOINT}/${id}`)
|
||||
unwrap<unknown>(data)
|
||||
}
|
||||
|
||||
/** 加载数据权限分组选项:GET /shop-manage-groups → {id,name}[]。 */
|
||||
export async function fetchDedupeTotalGroups(): Promise<DedupeGroupOption[]> {
|
||||
const { data } = await http.get<unknown>(DEDUPE_GROUPS_ENDPOINT)
|
||||
return parseDedupeGroupOptionList(data)
|
||||
}
|
||||
|
||||
/** 归一化分组选项负载(信封 items 或裸数组),缺 id 行丢弃。 */
|
||||
export function parseDedupeGroupOptionList(payload: unknown): DedupeGroupOption[] {
|
||||
const core = unwrap<unknown>(payload)
|
||||
const rawList = Array.isArray(core)
|
||||
? core
|
||||
: core && typeof core === 'object'
|
||||
? (core as Record<string, unknown>).items
|
||||
: []
|
||||
if (!Array.isArray(rawList)) return []
|
||||
const options: DedupeGroupOption[] = []
|
||||
for (const raw of rawList) {
|
||||
if (!raw || typeof raw !== 'object') continue
|
||||
const record = raw as Record<string, unknown>
|
||||
const id = typeof record.id === 'number' ? Math.floor(record.id) : Number(record.group_id)
|
||||
if (!Number.isFinite(id) || id <= 0) continue
|
||||
const nameRaw = record.group_name ?? record.name
|
||||
const name = typeof nameRaw === 'string' ? nameRaw.trim() : ''
|
||||
options.push({ id, name: name || `分组${id}` })
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readSource } from './helpers.ts'
|
||||
|
||||
// module 13 task 254:去重汇总页对齐 admin panel-dedupe-total-data —— 筛选扩充(用户名/分组/日期)、
|
||||
// 行编辑/删除、新增导入/删除导入(轮询)、导出(日期校验+等待) 接线既有孤儿模块。
|
||||
|
||||
test('test_task_254_dedupe_normal_primary_path', () => {
|
||||
const page = readSource('src/pages/asin/DedupeRegistryPage.vue')
|
||||
assert.match(page, /新增导入/, '页头需有新增导入按钮')
|
||||
assert.match(page, /删除导入/, '页头需有删除导入按钮')
|
||||
assert.match(page, /导出/, '页头需有导出入口')
|
||||
assert.match(page, /编辑/, '行内需有编辑操作')
|
||||
assert.match(page, /删除/, '行内需有删除操作')
|
||||
})
|
||||
|
||||
test('test_task_254_dedupe_normal_variant_input', () => {
|
||||
const page = readSource('src/pages/asin/DedupeRegistryPage.vue')
|
||||
assert.match(page, /fetchDedupeTotalGroups/, '需接线分组列表源')
|
||||
assert.match(page, /toDedupeListParams/, '需走共享筛选归一(dedupe-total-filter)')
|
||||
assert.match(page, /importFinished|importTaskFinished/, '需用导入终态判断停止轮询')
|
||||
assert.match(page, /importOutcomeText/, '需展示导入/删除进度文案')
|
||||
})
|
||||
|
||||
test('test_task_254_dedupe_normal_repeated_operation_is_idempotent', () => {
|
||||
const page = readSource('src/pages/asin/DedupeRegistryPage.vue')
|
||||
assert.ok(page.includes('新增导入'))
|
||||
assert.ok(page.includes('新增导入'))
|
||||
})
|
||||
|
||||
test('test_task_254_dedupe_boundary_empty_input', () => {
|
||||
const page = readSource('src/pages/asin/DedupeRegistryPage.vue')
|
||||
assert.match(page, /开始日期/, '筛选需含开始日期')
|
||||
assert.match(page, /结束日期/, '筛选需含结束日期')
|
||||
assert.match(page, /上传人|用户名/, '筛选需含用户名')
|
||||
assert.match(page, /分组/, '筛选需含分组下拉')
|
||||
assert.match(page, /国家/, '筛选需含国家下拉')
|
||||
})
|
||||
|
||||
test('test_task_254_dedupe_boundary_single_item', () => {
|
||||
const page = readSource('src/pages/asin/DedupeRegistryPage.vue')
|
||||
assert.match(page, /开始日期不能晚于结束日期|晚于结束日期/, '导出需做日期区间校验')
|
||||
assert.match(page, /确定删除总数据/, '行删除确认文案应对齐 admin')
|
||||
})
|
||||
|
||||
test('test_task_254_dedupe_boundary_limit_or_missing_field', () => {
|
||||
const page = readSource('src/pages/asin/DedupeRegistryPage.vue')
|
||||
assert.match(page, /\.xlsx|\.xls|xlsx/, '导入需 Excel 文件类型提示')
|
||||
assert.match(page, /mounted|onMounted/, '需首屏加载')
|
||||
})
|
||||
|
||||
test('test_task_254_dedupe_invalid_input_rejected', () => {
|
||||
// 异常:不得出现空操作按钮(无 @click 的页头主按钮)。
|
||||
const page = readSource('src/pages/asin/DedupeRegistryPage.vue')
|
||||
const headerButtons = page.match(/type="primary"[^>]*>([^<]*?)</g) ?? []
|
||||
for (const btn of headerButtons) {
|
||||
const label = btn.match(/>([^<]*?)</)?.[1] ?? ''
|
||||
if (/导入|导出/.test(label)) {
|
||||
assert.ok(page.includes(`openImport`), `导入类按钮必须绑定 @click (${label})`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('test_task_254_dedupe_dependency_failure_returns_actionable_message', () => {
|
||||
// 依赖失败:api 模块须暴露单行更新/删除端点;导入/删除导入复用既有常量(防假接线)。
|
||||
const api = readSource('src/pages/asin/dedupe-total-api.ts')
|
||||
assert.match(api, /updateDedupeTotal|method: 'PUT'|\.put</, '需支持行编辑 PUT')
|
||||
assert.match(api, /deleteDedupeTotal|\.delete</, '需支持行删除 DELETE')
|
||||
const importApi = readSource('src/pages/asin/dedupe-import-api.ts')
|
||||
assert.match(importApi, /DEDUPE_IMPORT_ENDPOINT/, '需复用导入端点常量')
|
||||
assert.match(importApi, /DEDUPE_DELETE_IMPORT_ENDPOINT/, '需复用删除导入端点常量')
|
||||
})
|
||||
Reference in New Issue
Block a user