fix(pl): 对齐移动 H5 证书齐备 2.0 与合同用证提示。

H5 齐备页补齐待完善/完善并关闭,修正会误导归档可跳过额度的文案;合同用证增加新规则只读说明,并补相关单测。
This commit is contained in:
wuzilong committed 2026-11-15 08:04:38 +08:00
1 parent 9f855423d8
commit c5ae2d957c
4 files changed
+219 -21

No files matched your search

@@ -0,0 +1,46 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
const readyTab = readFileSync(
new URL('../src/views/m/pl/plan-cert/ReadyTab.vue', import.meta.url),
'utf8'
)
const contractTab = readFileSync(
new URL('../src/views/m/pl/plan-cert/ContractTab.vue', import.meta.url),
'utf8'
)
const archiveForm = readFileSync(
new URL('../src/views/m/pl/plan-cert/archive-form.vue', import.meta.url),
'utf8'
)
const stopCard = readFileSync(
new URL('../src/views/m/pl/plan/PlanStopReadCard.vue', import.meta.url),
'utf8'
)
test('H5 证书齐备对齐 2.0:待完善列表与完善并关闭,且文案不宣称归档可跳过额度', () => {
assert.match(readyTab, /listPendingFulfill/)
assert.match(readyTab, /certReadyApi\.fulfill/)
assert.match(readyTab, /fulfillBatch/)
assert.match(readyTab, /已标记待完善/)
assert.match(readyTab, /完善并关闭/)
assert.match(readyTab, /归档前仍须/)
assert.doesNotMatch(readyTab, /安排和归档时都不再校验/)
assert.doesNotMatch(readyTab, /也不扣减额度/)
})
test('H5 合同用证页对新规则给出只读提示', () => {
assert.match(contractTab, /客户\+证用品类/)
assert.match(contractTab, /仅供历史查看/)
})
test('H5 建档页使用关联购买证且无证面引用残留', () => {
assert.match(archiveForm, /关联购买证/)
assert.doesNotMatch(archiveForm, /证面引用/)
})
test('H5 段次只读卡展示同市只需购买证', () => {
assert.match(stopCard, /是否同市/)
assert.match(stopCard, /同市只需购买证/)
})
@@ -11,10 +11,14 @@ test('危化品证上传位于运输管理并复用现有 WebView 壳', async ()
const { WORKBENCH_MODULES } = await import(`data:text/javascript,${encodeURIComponent(source)}`)
const transport = WORKBENCH_MODULES.find((m) => m.key === 'pl')
const entry = transport.menus.find((m) => m.code === 'pl-plan-cert-webview')
assert.ok(entry, '运输管理缺少危化品证上传入口')
assert.equal(entry.name, '危化品证上传')
assert.ok(entry, '运输管理缺少运输证件管理入口')
assert.equal(entry.name, '运输证件管理')
assert.equal(entry.perms, 'mini:pl:plan-cert:list')
assert.equal(entry.enabled, true)
assert.ok(
Array.isArray(entry.anyPerms) && entry.anyPerms.includes('mini:pl:quota-cert:list'),
'入口应覆盖证件建档等子权限'
)
const api = await read('miniprogram/config/api.js')
const ast = ts.createSourceFile('api.js', api, ts.ScriptTarget.Latest, true)
let expression
@@ -1,5 +1,8 @@
<template>
<main class="mp-h5-list-content mp-plan-cert-list">
<p class="mp-contract-quota-tip">
新规则已生效:安排选证改为客户+证用品类。本页合同用证仅供历史查看与旧规则计划,请勿再作为新计划的用证操作入口。
</p>
<p v-if="error" class="mp-wv-empty" role="alert">
{{ error }}
<button class="mp-wv-btn" type="button" @click="reload">重试</button>
@@ -127,3 +130,15 @@
)
void init()
</script>
<style scoped>
.mp-contract-quota-tip {
margin: 8px 12px 0;
padding: 10px 12px;
border-radius: 8px;
background: #ecfdf5;
color: #065f46;
font-size: 12px;
line-height: 1.5;
}
</style>
+152 -19
View File
@@ -1,5 +1,24 @@
<template>
<main class="mp-h5-list-content mp-plan-cert-list">
<van-tabs v-model:active="listMode" class="mp-customer-view-tabs" shrink :ellipsis="false">
<van-tab name="mark" title="待标记齐备" />
<van-tab name="fulfill" title="已标记待完善" />
</van-tabs>
<p class="mp-cert-ready-tip">
新规则下证书齐备只放行安排;归档前须上传真证并执行「完善并关闭」。旧规则齐备不进待完善列表。
</p>
<div v-if="listMode === 'fulfill'" class="mp-cert-ready-actions">
<button class="mp-wv-btn" type="button" @click="goUpload('purchase')">上传购买证</button>
<button class="mp-wv-btn" type="button" @click="goUpload('transport')">上传运输证</button>
<button
class="mp-wv-btn mp-wv-btn--primary"
type="button"
:disabled="!selectedIds.length || batching"
@click="fulfillBatch"
>
{{ batching ? '批量完善中…' : `批量完善并关闭${selectedIds.length ? `(${selectedIds.length})` : ''}` }}
</button>
</div>
<p v-if="error" class="mp-wv-empty" role="alert">
{{ error }}
<button class="mp-wv-btn" type="button" @click="reload">重试</button>
@@ -7,14 +26,26 @@
<van-list v-model:loading="loading" :finished="finished" finished-text="" @load="load">
<article v-for="row in rows" :key="row.id" class="mp-wv-card mp-wv-flat-card">
<div class="mp-wv-card__head">
<div class="mp-cert-ready-select" v-if="listMode === 'fulfill'">
<van-checkbox :model-value="selectedIds.includes(row.id)" @click.stop="toggleSelect(row.id)" />
</div>
<div>
<div class="mp-wv-card__no">{{ row.planNo }}</div>
<div class="mp-wv-card__sub">
{{ row.licensePlate || '未安排车辆' }} · {{ row.planDate || '未定日期' }}
</div>
</div>
<span class="status-tag" :class="row.quotaCertWaived ? 'status-tag--success' : 'status-tag--warning'">
{{ row.quotaCertWaived ? '已齐备' : '未齐备' }}
<span
class="status-tag"
:class="row.quotaCertWaived ? 'status-tag--success' : 'status-tag--warning'"
>
{{
listMode === 'fulfill'
? '待完善'
: row.quotaCertWaived
? '已齐备'
: '未齐备'
}}
</span>
</div>
<div class="mp-wv-card__kv">
@@ -31,7 +62,10 @@
<span>确认时间</span><span>{{ formatCertReadyTime(row.quotaCertWaivedTime) }}</span>
</div>
</div>
<div v-if="!row.quotaCertWaived" class="mp-wv-card-actions mp-wv-card-actions--single">
<div
v-if="listMode === 'mark' && !row.quotaCertWaived"
class="mp-wv-card-actions mp-wv-card-actions--single"
>
<button
class="mp-wv-btn mp-wv-btn--primary"
type="button"
@@ -41,9 +75,22 @@
{{ markingId === row.id ? '正在确认…' : '证书齐备' }}
</button>
</div>
<div v-else-if="listMode === 'fulfill'" class="mp-wv-card-actions mp-wv-card-actions--single">
<button
class="mp-wv-btn mp-wv-btn--primary"
type="button"
:disabled="markingId === row.id"
@click="fulfillOne(row)"
>
{{ markingId === row.id ? '正在完善…' : '完善并关闭' }}
</button>
</div>
</article>
</van-list>
<van-empty v-if="!loading && finished && !error && !rows.length" description="暂无需要证书齐备的计划" />
<van-empty
v-if="!loading && finished && !error && !rows.length"
:description="listMode === 'fulfill' ? '暂无待完善证书的计划' : '暂无需要证书齐备的计划'"
/>
</main>
</template>
@@ -59,18 +106,23 @@
planDateFrom: string
planDateTo: string
}>()
const router = useRouter()
const listMode = ref<'mark' | 'fulfill'>('mark')
const rows = ref<CertReadyPlan[]>([])
const loading = ref(false)
const finished = ref(false)
const error = ref('')
const page = ref(1)
const markingId = ref('')
const batching = ref(false)
const selectedIds = ref<string[]>([])
let epoch = 0
function reload() {
epoch++
page.value = 1
rows.value = []
selectedIds.value = []
loading.value = true
finished.value = false
error.value = ''
@@ -81,15 +133,22 @@
const current = epoch
loading.value = true
try {
const data = await certReadyApi.list({
page: page.value,
pageSize: 20,
keyword: props.keyword || undefined,
waived: props.waived || undefined,
followStatusIn: props.followStatus || undefined,
planDateFrom: props.planDateFrom || undefined,
planDateTo: props.planDateTo || undefined
})
const data =
listMode.value === 'fulfill'
? await certReadyApi.listPendingFulfill({
page: page.value,
pageSize: 20,
keyword: props.keyword || undefined
})
: await certReadyApi.list({
page: page.value,
pageSize: 20,
keyword: props.keyword || undefined,
waived: props.waived || undefined,
followStatusIn: props.followStatus || undefined,
planDateFrom: props.planDateFrom || undefined,
planDateTo: props.planDateTo || undefined
})
if (current !== epoch) return
const list = data?.list || []
rows.value = page.value === 1 ? list : rows.value.concat(list)
@@ -108,9 +167,9 @@
async function mark(row: CertReadyPlan) {
try {
await ElMessageBox.confirm(
'确认后,这条运输计划在安排和归档时都不再校验购买证、运输证,也不扣减额度。',
'证书齐备',
{ type: 'warning', confirmButtonText: '确认齐备', cancelButtonText: '取消' }
'确认后可先安排运输;新规则下归档前仍须到「已标记待完善」上传证件并执行完善并关闭。确认人和时间会记在本行。',
'标记齐备',
{ type: 'warning', confirmButtonText: '确认标记', cancelButtonText: '取消' }
)
} catch {
return
@@ -127,11 +186,85 @@
}
}
async function fulfillOne(row: CertReadyPlan) {
markingId.value = row.id
try {
await certReadyApi.fulfill(row.id)
ElMessage.success('已完善并关闭')
reload()
} catch (reason) {
ElMessage.error(certReadyErrorText(reason, '完善并关闭失败'))
} finally {
markingId.value = ''
}
}
function toggleSelect(id: string) {
selectedIds.value = selectedIds.value.includes(id)
? selectedIds.value.filter((item) => item !== id)
: selectedIds.value.concat(id)
}
async function fulfillBatch() {
if (!selectedIds.value.length) return
batching.value = true
try {
const res = await certReadyApi.fulfillBatch(selectedIds.value)
const failed = (res?.results || []).filter((item) => !item.ok)
if (failed.length) {
ElMessage.warning(`部分失败:${failed.map((f) => f.message || f.planId).join(';')}`)
} else {
ElMessage.success('批量完善完成')
}
reload()
} catch (reason) {
ElMessage.error(certReadyErrorText(reason, '批量完善失败'))
} finally {
batching.value = false
}
}
function goUpload(kind: 'purchase' | 'transport') {
void router.push(`/m/pl/plan-cert/archive/${kind}/create`)
}
watch(listMode, () => reload())
watch(
() =>
[props.keyword, props.waived, props.followStatus, props.planDateFrom, props.planDateTo].join(
'\0'
),
[
props.keyword,
listMode.value === 'mark' ? props.waived : '',
listMode.value === 'mark' ? props.followStatus : '',
listMode.value === 'mark' ? props.planDateFrom : '',
listMode.value === 'mark' ? props.planDateTo : ''
].join('\0'),
() => reload()
)
</script>
<style scoped>
.mp-cert-ready-tip {
margin: 8px 12px 0;
padding: 10px 12px;
border-radius: 8px;
background: #ecfdf5;
color: #065f46;
font-size: 12px;
line-height: 1.5;
}
.mp-cert-ready-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 10px 12px 0;
}
.mp-cert-ready-select {
margin-right: 8px;
display: flex;
align-items: flex-start;
padding-top: 2px;
}
.mp-wv-card__head {
align-items: flex-start;
}
</style>