|
|
@@ -1,18 +1,28 @@
|
|
|
<template>
|
|
|
<div class="app-container">
|
|
|
- <el-alert
|
|
|
- type="info"
|
|
|
- :closable="false"
|
|
|
- style="margin-bottom: 12px"
|
|
|
- title="按所选商户类别填写资料,提交后由服务端建号。必填项按类别分档,漏填时服务端会在提交后一次列全(按中文字段名)—— 照那份清单补齐即可。"
|
|
|
- />
|
|
|
+ <el-alert type="info" :closable="false" style="margin-bottom: 12px" :title="MODE_HINT[mode]" />
|
|
|
|
|
|
- <!-- 商户号是本页唯一的产出:toast 会自己消失,这里**常驻**到下一次提交 -->
|
|
|
+ <!-- 商户号是**建号腿**唯一的产出:toast 会自己消失,这里**常驻**到下一次提交 -->
|
|
|
<el-alert v-if="createdMchNo" type="success" :closable="false" style="margin-bottom: 12px">
|
|
|
<template #title>
|
|
|
提交成功,商户号:
|
|
|
<strong class="mch-no">{{ createdMchNo }}</strong>
|
|
|
</template>
|
|
|
+ <template #default>
|
|
|
+ <!-- 建号后当场发现填错,是更新腿最常见的入口 —— 这里少一次「回头找商户号」 -->
|
|
|
+ <el-button link type="primary" @click="startUpdateFor(createdMchNo)">
|
|
|
+ 资料填错了?改这个商户
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-alert>
|
|
|
+
|
|
|
+ <!-- 更新腿**不回**商户号(`Result<Void>`),所以这里显示的是**请求里那个**,不是响应里的 -->
|
|
|
+ <el-alert v-if="updatedMchNo" type="success" :closable="false" style="margin-bottom: 12px">
|
|
|
+ <template #title>
|
|
|
+ 已提交更新:
|
|
|
+ <strong class="mch-no">{{ updatedMchNo }}</strong>
|
|
|
+ —— 表单里留空的项,上游原值未变。
|
|
|
+ </template>
|
|
|
</el-alert>
|
|
|
|
|
|
<!-- 信封说成功、却没有商户号:既不能说成功(拿不到号),也不能说失败(可能已经建出来了) -->
|
|
|
@@ -39,6 +49,13 @@
|
|
|
</el-alert>
|
|
|
|
|
|
<el-card shadow="never">
|
|
|
+ <!-- ★ 两种模式**共用**这一张表单、这一套级联。不另起页面/弹窗:更新腿的字段集是
|
|
|
+ 建号腿那张表**减去两个**(见 `UPDATE_EXCLUDED_KEYS`),另起一份必然漂移。 -->
|
|
|
+ <el-radio-group v-model="mode" style="margin-bottom: 16px" @change="switchMode">
|
|
|
+ <el-radio-button value="create">建号(新建商户)</el-radio-button>
|
|
|
+ <el-radio-button value="update">更新(改已有商户资料)</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+
|
|
|
<el-form
|
|
|
ref="formRef"
|
|
|
:model="form"
|
|
|
@@ -47,8 +64,18 @@
|
|
|
label-position="right"
|
|
|
v-loading="submitting"
|
|
|
>
|
|
|
- <!-- 商户类别:全部资料的**分档依据**,不进折叠 —— 它是这一页的入口 -->
|
|
|
- <el-form-item label="商户类别" prop="category">
|
|
|
+ <!-- 商户号:**只有更新模式**有 —— 建号时它由服务端产出,不是输入 -->
|
|
|
+ <el-form-item v-if="mode === 'update'" label="商户号" prop="mch_no">
|
|
|
+ <el-input
|
|
|
+ v-model="form.mch_no"
|
|
|
+ placeholder="要改哪个商户就填哪个 —— 只能改本租户的商户号"
|
|
|
+ style="width: 420px"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 商户类别:**只有建号模式**有 —— 更新页的参数表里压根没有它
|
|
|
+ (`merchant_type` 不在那张表里 ⇒ 建号腿「一个 category 派生两个输出」在更新腿不成立) -->
|
|
|
+ <el-form-item v-if="mode === 'create'" label="商户类别" prop="category">
|
|
|
<el-select v-model="form.category" placeholder="请选择商户类别" style="width: 320px">
|
|
|
<el-option
|
|
|
v-for="c in MERCHANT_CATEGORY_OPTIONS"
|
|
|
@@ -69,7 +96,7 @@
|
|
|
|
|
|
<!-- 纯文本组 -->
|
|
|
<el-row v-if="s.kind === 'text'" :gutter="20">
|
|
|
- <el-col v-for="f in s.fields" :key="f.key" :span="12">
|
|
|
+ <el-col v-for="f in visibleFields(s)" :key="f.key" :span="12">
|
|
|
<el-form-item :label="f.label">
|
|
|
<el-input v-model="form[f.key]" />
|
|
|
</el-form-item>
|
|
|
@@ -133,7 +160,7 @@
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
- <el-col v-for="f in s.fields" :key="f.key" :span="12">
|
|
|
+ <el-col v-for="f in visibleFields(s)" :key="f.key" :span="12">
|
|
|
<el-form-item :label="f.label">
|
|
|
<el-input v-model="form[f.key]" />
|
|
|
</el-form-item>
|
|
|
@@ -212,7 +239,7 @@
|
|
|
|
|
|
<!-- 上传组:每格 = 标签 + 图片框(图片框自己就地显示图片本身,不把地址当文本渲染) -->
|
|
|
<div v-else class="upload-grid">
|
|
|
- <div v-for="f in s.fields" :key="f.key" class="upload-cell">
|
|
|
+ <div v-for="f in visibleFields(s)" :key="f.key" class="upload-cell">
|
|
|
<div class="upload-cell__label">{{ f.label }}</div>
|
|
|
<SingleImageUpload
|
|
|
v-model="form[f.key]"
|
|
|
@@ -227,7 +254,9 @@
|
|
|
|
|
|
<div class="form-actions">
|
|
|
<el-button @click="handleReset">重置</el-button>
|
|
|
- <el-button type="primary" :loading="submitting" @click="handleSubmit">提交进件</el-button>
|
|
|
+ <el-button type="primary" :loading="submitting" @click="handleSubmit">
|
|
|
+ {{ mode === "update" ? "提交更新" : "提交进件" }}
|
|
|
+ </el-button>
|
|
|
</div>
|
|
|
</el-form>
|
|
|
</el-card>
|
|
|
@@ -249,6 +278,7 @@ import {
|
|
|
createOnboardingMerchant,
|
|
|
getOnboardingAreas,
|
|
|
getOnboardingMcc,
|
|
|
+ updateOnboardingMerchant,
|
|
|
uploadOnboardingImage,
|
|
|
type OnboardingArea,
|
|
|
type OnboardingLevel,
|
|
|
@@ -422,8 +452,48 @@ const CREATE_KEYS: string[] = [
|
|
|
AREA_FIELDS.county.key,
|
|
|
];
|
|
|
|
|
|
+/**
|
|
|
+ * ★★ 更新腿**不接受**的两个键。发出去**不是**「被服务端忽略」,是 **HTTP 500**
|
|
|
+ * (本仓 `FAIL_ON_UNKNOWN_PROPERTIES` 是 `true`,`JacksonConfig` 绕过了 Spring 的 builder
|
|
|
+ * ⇒ 多一个未知键就是 500,不是 400)。两者都**不在** `LsyMerchantUpdateDTO` 上:
|
|
|
+ * · `merchant_name_business` —— 上游更新页逐字「存在少数字段商户创建后即固定,无法修改,
|
|
|
+ * 如:merchant_name_business, merchant_type 等」;
|
|
|
+ * · `lease_contract` —— 创建页有、更新页没有(更新 DTO 的类 javadoc 里那组
|
|
|
+ * 「创建页有、本页没有」)。
|
|
|
+ * ⇒ **两道各自独立**的防线,两件都要成立:更新模式下这两格**不渲染**
|
|
|
+ * (渲染出来 = 提供一个按下去必定 500 的入口),且 `UPDATE_KEYS` 里也没有它们
|
|
|
+ * (「看不见」挡不住「切模式前已经填进去的值」,见 `buildUpdateBody`)。
|
|
|
+ */
|
|
|
+const UPDATE_EXCLUDED_KEYS: readonly string[] = ["merchant_name_business", "lease_contract"];
|
|
|
+
|
|
|
+/** 更新腿的键白名单 = 建号腿那张表**减去两个**;`mch_no` 另加(它不是建号表单的字段)。 */
|
|
|
+const UPDATE_KEYS: string[] = CREATE_KEYS.filter((k) => !UPDATE_EXCLUDED_KEYS.includes(k));
|
|
|
+
|
|
|
+/**
|
|
|
+ * 本页两种模式 —— **同一张表、同一套级联、两个出口**(`/create` 与 `/update`)。
|
|
|
+ *
|
|
|
+ * ★ 不另起一个页面或弹窗:更新腿的字段集就是建号腿那张表减去两个,另起一份必然漂移,
|
|
|
+ * 而漂移的症状是「同一份资料在两处能填的格不一样」且不报错。
|
|
|
+ */
|
|
|
+const mode = ref<"create" | "update">("create");
|
|
|
+
|
|
|
+const MODE_HINT: Record<"create" | "update", string> = {
|
|
|
+ create:
|
|
|
+ "按所选商户类别填写资料,提交后由服务端建号。必填项按类别分档,漏填时服务端会在提交后一次列全(按中文字段名)—— 照那份清单补齐即可。",
|
|
|
+ update:
|
|
|
+ "更新已有商户的上游资料。★ 上游是增量更新:这里留空 = 不改这一项,不是清空 —— 只想改哪几项就只填哪几项;下拉与图片同理,没动过的不会被提交。",
|
|
|
+};
|
|
|
+
|
|
|
+/** 更新模式下该分区要渲染的字段 —— `SECTIONS` 仍是**唯一**那张表,这里只是投影。 */
|
|
|
+function visibleFields(s: Section): Field[] {
|
|
|
+ return mode.value === "create"
|
|
|
+ ? s.fields
|
|
|
+ : s.fields.filter((f) => !UPDATE_EXCLUDED_KEYS.includes(f.key));
|
|
|
+}
|
|
|
+
|
|
|
function emptyForm(): Record<string, string> {
|
|
|
- const f: Record<string, string> = { category: "" };
|
|
|
+ // `mch_no` 只有更新模式用得上(建号时它是服务端的**产出**);不填它 ⇒ 建号报文不带这个键
|
|
|
+ const f: Record<string, string> = { category: "", mch_no: "" };
|
|
|
for (const key of CREATE_KEYS) {
|
|
|
f[key] = "";
|
|
|
}
|
|
|
@@ -436,6 +506,11 @@ const submitting = ref(false);
|
|
|
const createdMchNo = ref("");
|
|
|
/** ★ 提交被受理、却**没回商户号**:不当成功(拿不到号),也不当失败(可能已经建出来了)。 */
|
|
|
const submitNoMchNo = ref(false);
|
|
|
+/**
|
|
|
+ * 更新腿没有「商户号」这个产出(应答是 `Result<Void>`)⇒ 这里存的是**请求里那个**商户号,
|
|
|
+ * 只用来渲染提交后那条常驻提示。不要把它读成响应里的值。
|
|
|
+ */
|
|
|
+const updatedMchNo = ref("");
|
|
|
const cascadeFailed = ref(false);
|
|
|
|
|
|
/** 默认**全部展开**(理由见模板上的注释)。 */
|
|
|
@@ -452,9 +527,18 @@ const activeSections = ref<string[]>(SECTIONS.map((s) => s.key));
|
|
|
* 而服务端的 400 文案会**一次性**按中文标签列出缺哪几项
|
|
|
* (`LsyOnboardingService` 逐字「进件必填项缺失(共 N 项):…」)⇒ 那份清单才是判据。
|
|
|
*/
|
|
|
-const rules = {
|
|
|
- category: [{ required: true, message: "请选择商户类别", trigger: "change" }],
|
|
|
-};
|
|
|
+const rules = computed(() => {
|
|
|
+ if (mode.value === "update") {
|
|
|
+ // 更新腿**只钉商户号**:其余**一个都不能设成必填** —— 增量语义下「留空 = 不改」是正常用法,
|
|
|
+ // 设了必填就等于把「只想改结算卡」逼成「必须把 41 项重填一遍」,而那些重填的值会真的发上游。
|
|
|
+ return {
|
|
|
+ mch_no: [{ required: true, message: "请输入要更新的商户号", trigger: "blur" }],
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ category: [{ required: true, message: "请选择商户类别", trigger: "change" }],
|
|
|
+ };
|
|
|
+});
|
|
|
|
|
|
// ==================== 级联:省/市/区 ====================
|
|
|
// ★ 每一级的 `parentCode` 都是**上一级选中项的 `area_code`**(如 `"1"`),
|
|
|
@@ -701,6 +785,25 @@ function buildCreateBody(): Record<string, string> {
|
|
|
return body;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * 更新报文的键白名单 —— `mch_no` + `UPDATE_KEYS`。
|
|
|
+ *
|
|
|
+ * ★ 与 `buildCreateBody` 共用「空白字段整个省掉」这条判据(理由见上)。**不共用函数**是因为
|
|
|
+ * 两个出口的键集不同,而且这次的「省掉」还多一层语义:上游是**增量更新**,
|
|
|
+ * 留空 = 保持原值 ⇒ 省掉正是正确行为,不是「少发了」。
|
|
|
+ * ★★ 切模式**不清表单**(见 `switchMode`)⇒ `form` 里此刻可能还留着建号模式打进去的
|
|
|
+ * `merchant_name_business` / `lease_contract` 值。`UPDATE_KEYS` 不含它们,**物理上发不出去** ——
|
|
|
+ * 这就是「白名单」而非 `{ ...form }` 的意义:那道 500 由白名单挡,不靠「用户没填过」这个假设。
|
|
|
+ */
|
|
|
+function buildUpdateBody(): Record<string, string> {
|
|
|
+ const body: Record<string, string> = { mch_no: form.mch_no.trim() };
|
|
|
+ for (const key of UPDATE_KEYS) {
|
|
|
+ const value = (form[key] ?? "").trim();
|
|
|
+ if (value !== "") body[key] = value;
|
|
|
+ }
|
|
|
+ return body;
|
|
|
+}
|
|
|
+
|
|
|
async function handleSubmit() {
|
|
|
try {
|
|
|
await formRef.value?.validate();
|
|
|
@@ -709,6 +812,38 @@ async function handleSubmit() {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ if (mode.value === "update") {
|
|
|
+ await submitUpdate();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ await submitCreate();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 更新腿。与建号腿的三处**有意不同**:
|
|
|
+ * ① 应答是 `Result<Void>` ⇒ **没有**商户号可读,也就没有「没回商户号」那条告警
|
|
|
+ * (那条的语义是「本页唯一的产出没了」,在更新腿没有对应物);
|
|
|
+ * ② 商户号是**输入**(`form.mch_no`)而不是产出,提示里显示的是请求里的那个;
|
|
|
+ * ③ 提交完**不清表单**(与建号腿一致)⇒ 一条常驻提示说明「哪几项没被提交」,
|
|
|
+ * 因为从表单上看不出「留空 = 不改」。
|
|
|
+ */
|
|
|
+async function submitUpdate() {
|
|
|
+ submitting.value = true;
|
|
|
+ updatedMchNo.value = "";
|
|
|
+ try {
|
|
|
+ await updateOnboardingMerchant(buildUpdateBody());
|
|
|
+ // ★ 这里**不读** `res.data?.data?.mch_no` —— 更新腿回的是 `Result<Void>`,`data` 是 `null`,
|
|
|
+ // 读了只会拿到 `undefined` 并把成功显示成「拿不到商户号」。
|
|
|
+ updatedMchNo.value = form.mch_no.trim();
|
|
|
+ } catch {
|
|
|
+ // 失败文案由拦截器统一弹(两道封盖的 400、非本租户的 404、上游失败的 502 都在 `msg` 里)
|
|
|
+ // ⇒ 页面**不重复弹**,也不把它当成功往下走。这里只是不让 rejection 逃逸成未处理的 Promise 拒绝。
|
|
|
+ } finally {
|
|
|
+ submitting.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function submitCreate() {
|
|
|
submitting.value = true;
|
|
|
createdMchNo.value = "";
|
|
|
submitNoMchNo.value = false;
|
|
|
@@ -742,9 +877,38 @@ function handleReset() {
|
|
|
clearLeaf();
|
|
|
createdMchNo.value = "";
|
|
|
submitNoMchNo.value = false;
|
|
|
+ updatedMchNo.value = "";
|
|
|
+ formRef.value?.clearValidate();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 切模式:**不动**表单里已填的值 —— 41 个框,误点一下清空是不可逆的。
|
|
|
+ * 代价是建号模式填的 `merchant_name_business` / `lease_contract` 会留在模型里,
|
|
|
+ * 但那条路径由 `buildUpdateBody` 的**键白名单**堵死(不靠「用户没填过」这个假设)。
|
|
|
+ *
|
|
|
+ * ★ 只清**上一次提交的结果提示**:那是「上一次那个动作」的结论,
|
|
|
+ * 留着会让人以为刚切换到的这个模式已经提交过了。
|
|
|
+ */
|
|
|
+function switchMode() {
|
|
|
+ createdMchNo.value = "";
|
|
|
+ submitNoMchNo.value = false;
|
|
|
+ updatedMchNo.value = "";
|
|
|
formRef.value?.clearValidate();
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * 从「刚建完号」那条常驻提示跳进更新模式,并带上商户号 —— 建号后当场发现填错,
|
|
|
+ * 是更新腿最常见的入口,这里省掉一次「回头把商户号抄过来」。
|
|
|
+ *
|
|
|
+ * ★ 只预填 `mch_no`,**不**把建号表单里那一堆值带过去:更新是增量的,
|
|
|
+ * 带过去等于「按建号时填的再覆盖一遍」,而不是「只改填错的那一项」。
|
|
|
+ */
|
|
|
+function startUpdateFor(mchNo: string) {
|
|
|
+ mode.value = "update";
|
|
|
+ form.mch_no = mchNo;
|
|
|
+ switchMode();
|
|
|
+}
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
// 两组级联与表单无关,进页面就拉(失败由 `loadCascade` 落成页面上那条常驻提示)
|
|
|
loadProvinces();
|