feat(admin): 最低价ASIN列表操作列新增整条删除按钮——后端DELETE /skip-price-asins/{id}(权限校验+日志),前端确认弹窗+删空页回退,补单测与对齐测试

This commit is contained in:
2026-09-07 16:05:57 +08:00
parent 0bd4843b8e
commit c5982dd900
6 changed files with 92 additions and 1 deletions
@@ -8,7 +8,7 @@ import { createSkipPriceAsin, fetchSkipPriceList } from './skip-price-api.ts'
import { skipPriceDisplayRows, type SkipPriceItem } from './skip-price-model.ts'
import { asinCountryLabel } from './asin-country.ts'
import { createSkipPriceFilterState, toSkipPriceParams, type SkipPriceFilterState } from './skip-price-filter.ts'
import { deleteSkipPriceCountry, updateSkipPriceCountry } from './skip-price-detail-api.ts'
import { deleteSkipPriceAsin, deleteSkipPriceCountry, updateSkipPriceCountry } from './skip-price-detail-api.ts'
import { fetchSkipPriceDeleteImportProgress, fetchSkipPriceImportProgress, startSkipPriceDeleteImport, startSkipPriceImport } from './skip-price-import-api.ts'
import { isAllowedExcelImportFile } from './import-progress-model.ts'
import { fetchShopNamesByGroup } from './query-asin-api.ts'
@@ -204,6 +204,30 @@ async function saveConfig(): Promise<void> {
}
}
// ---- 整条删除 ----
async function removeRow(item: SkipPriceItem): Promise<void> {
try {
await ElMessageBox.confirm(
`确定删除「${item.shopName}${item.groupName ? `${item.groupName}` : ''}全部站点的最低价 ASIN 配置吗?`,
'删除确认',
{ type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' },
)
} catch {
return
}
try {
await deleteSkipPriceAsin(item.id)
ElMessage.success('删除成功')
// 删除后当前页若已空且非首页,回退一页避免展示空页。
if (rows.value.length === 1 && page.value > 1) {
page.value -= 1
}
await load()
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '删除失败')
}
}
// ---- 导入添加 / 删除导入(group_id 必填) ----
type ImportMode = 'add' | 'delete'
const importVisible = ref(false)
@@ -433,6 +457,7 @@ onMounted(() => {
<template v-if="row.isFirst">
<td :rowspan="row.rowspan" class="asin-col-actions">
<button class="btn btn-sm" type="button" @click="openConfig(row.item as SkipPriceItem)">配置</button>
<button class="btn btn-sm btn-danger" type="button" @click="removeRow(row.item as SkipPriceItem)">删除</button>
</td>
</template>
</tr>
@@ -21,3 +21,8 @@ export async function updateSkipPriceCountry(
export async function deleteSkipPriceCountry(id: number, country: string): Promise<void> {
await http.delete<unknown>(countryUrl(id, country))
}
/** 删除整条最低价 ASIN 记录(该店铺全部站点):DELETE /api/admin/skip-price-asins/{id}。 */
export async function deleteSkipPriceAsin(id: number): Promise<void> {
await http.delete<unknown>(`${SKIP_PRICE_DETAIL_ENDPOINT}/${id}`)
}
@@ -97,3 +97,17 @@ test('align_skip_price_filter_price_range_same_row', () => {
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\}/, '删除端点指向整条记录')
})