fix(system): code-review 15 项修复(预览基数/维度校验/脱敏穿透/并发版本号等)

- 信息员预览基数改用本人销售额(原误用折算毛利润额,金额算错且口径自相矛盾)
- 产品类别入参与维度值加存在性校验(拼错静默回退默认阈值 30% 会算错轨交工资)
- submit 校验顺序前置查库(未注册 param_code 由 500 修正为 400)
- 金额拒绝 NaN/Infinity(比较抛 InvalidOperation、计算抛溢出均穿透为 500)
- version_no 并发冲突翻译为 400(对齐 user/role service 的 IntegrityError 处理)
- 旧值留痕改取条目生效日时点的值(多未来批次下留痕链不再错位)
- 脱敏红线:phone/mobile/id_card 键名整值抹除 + 手机号正则兼容分隔符写法
- 前端:撤回按钮与导出文件名改本地日期(UTC/本地混算)、维度行内精确「改」入口、
  detailOf 补 catch、抽屉分页 size 生效、blob 错误读出真实 message
- e2e 密码移除硬编码 fallback(缺失即报错退出);按钮文案同步「改」
- 全量回归:后端 406 + 集成 104 + 前端 type-check/build + e2e 7 步 全绿

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
liuwenxinandClaude Code committed 2026-10-13 02:28:20 +08:00
1 parent 6219698a39
commit fbf044f240
9 files changed
+154 -49

No files matched your search

+13 -1
View File
@@ -65,7 +65,19 @@ service.interceptors.response.use(
},
(error: AxiosError<ApiResult>) => {
const status = error.response?.status
const message = error.response?.data?.message ?? '网络异常,请稍后重试'
let message = error.response?.data?.message ?? '网络异常,请稍后重试'
// blob 请求失败时 data 是 Blob(如导出接口返回的 JSON 信封错误),需读出真实 message
if (error.response?.data instanceof Blob) {
message = '导出失败,请稍后重试'
void error.response.data.text().then((text) => {
try {
const body = JSON.parse(text) as { message?: string }
if (body.message) ElMessage.error(body.message)
} catch {
/* 非 JSON 响应,保持兜底提示 */
}
})
}
if (status === 401) {
// 401 严格表示「受保护接口的鉴权失败」。登录接口自身失败返回 400/403,
+35 -11
View File
@@ -192,13 +192,14 @@ const versionVisible = ref(false)
const batches = ref<ParamBatch[]>([])
const batchesTotal = ref(0)
const batchesPage = ref(1)
const batchesPageSize = ref(10)
const batchesLoading = ref(false)
const detailCache = reactive<Record<number, ParamBatchDetail>>({})
async function loadBatches(): Promise<void> {
batchesLoading.value = true
try {
const page = await paramsApi.listBatches({ pageNum: batchesPage.value, pageSize: 10 })
const page = await paramsApi.listBatches({ pageNum: batchesPage.value, pageSize: batchesPageSize.value })
batches.value = page.list
batchesTotal.value = page.total
} catch (error) {
@@ -219,6 +220,12 @@ function changeTypeText(changeType: number): string {
return changeType === 1 ? '立即生效' : '指定日期'
}
/** 本地日期(YYYY-MM-DD):toISOString 是 UTC,北京时间 0–8 点会差一天 */
function localToday(): string {
const d = new Date()
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}
async function handleWithdraw(batch: ParamBatch): Promise<void> {
await confirmAndRun({
confirmText: `确定撤回批次「${batch.version_no}」(${batch.item_count} 项,${batch.effective_date} 起生效)?撤回后这些变更不再生效。`,
@@ -271,7 +278,7 @@ async function doExport(): Promise<void> {
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `param_changes_${new Date().toISOString().slice(0, 10)}.xlsx`
link.download = `param_changes_${localToday()}.xlsx`
link.click()
URL.revokeObjectURL(url)
ElMessage.success('已导出')
@@ -295,6 +302,7 @@ const historyItem = ref<ParamCatalogItem | null>(null)
const historyRows = ref<ParamHistory[]>([])
const historyTotal = ref(0)
const historyPage = ref(1)
const historyPageSize = ref(10)
const historyLoading = ref(false)
function openHistory(item: ParamCatalogItem): void {
@@ -310,7 +318,7 @@ async function loadHistory(): Promise<void> {
try {
const page = await paramsApi.getParamHistory(historyItem.value.param_code, {
pageNum: historyPage.value,
pageSize: 10,
pageSize: historyPageSize.value,
})
historyRows.value = page.list
historyTotal.value = page.total
@@ -445,6 +453,14 @@ onMounted(() => {
>
待改为 {{ pendingOf(item, v.dimension_value)!.new_value }}
</el-tag>
<el-button
link
type="primary"
size="small"
@click="openEdit(item, v.dimension_value)"
>
改
</el-button>
</div>
</template>
<span v-else class="no-value">
@@ -452,8 +468,14 @@ onMounted(() => {
</span>
</td>
<td>
<el-button link type="primary" size="small" @click="openEdit(item, item.values[0]?.dimension_value ?? '')">
修改
<el-button
v-if="!item.values.length"
link
type="primary"
size="small"
@click="openEdit(item, '')"
>
设置
</el-button>
<el-button link type="primary" size="small" @click="openHistory(item)">历史</el-button>
</td>
@@ -614,7 +636,9 @@ onMounted(() => {
<span class="num">{{ item.old_value_text ?? '—' }} → <b>{{ item.value_text }}</b></span>
</div>
</template>
<el-button v-else link size="small" @click="detailOf(row)">加载明细…</el-button>
<el-button v-else link size="small" @click="detailOf(row).catch(() => {})">
加载明细…
</el-button>
</div>
</template>
</el-table-column>
@@ -628,7 +652,7 @@ onMounted(() => {
<el-table-column label="操作" width="80">
<template #default="{ row }">
<el-button
v-if="row.effective_date > new Date().toISOString().slice(0, 10)"
v-if="row.effective_date > localToday()"
link
type="danger"
size="small"
@@ -642,9 +666,9 @@ onMounted(() => {
<AppPagination
:total="batchesTotal"
:page-num="batchesPage"
:page-size="10"
:page-size="batchesPageSize"
@page-change="(p: number) => { batchesPage = p; void loadBatches() }"
@size-change="() => {}"
@size-change="(s: number) => { batchesPageSize = s; batchesPage = 1; void loadBatches() }"
/>
</div>
</el-drawer>
@@ -667,9 +691,9 @@ onMounted(() => {
<AppPagination
:total="historyTotal"
:page-num="historyPage"
:page-size="10"
:page-size="historyPageSize"
@page-change="(p: number) => { historyPage = p; void loadHistory() }"
@size-change="() => {}"
@size-change="(s: number) => { historyPageSize = s; historyPage = 1; void loadHistory() }"
/>
</el-drawer>