|
@@ -23,7 +23,7 @@
|
|
|
<el-option v-for="s in SCENE_OPTIONS" :key="s" :label="s" :value="s" />
|
|
<el-option v-for="s in SCENE_OPTIONS" :key="s" :label="s" :value="s" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <!-- 无 prop: 报备信息是动态行数组不入 form,prop 会触发内置「{field} is required」假校验;必填由 handleSubmit 的 hasValidReport 兜底 -->
|
|
|
|
|
|
|
+ <!-- 无 prop: 报备信息是动态行数组不入 form,prop 会触发内置「{field} is required」假校验;必填由 handleSubmit 兜底 -->
|
|
|
<el-form-item label="场景报备信息" required>
|
|
<el-form-item label="场景报备信息" required>
|
|
|
<div
|
|
<div
|
|
|
v-for="(r, idx) in reportInfos"
|
|
v-for="(r, idx) in reportInfos"
|
|
@@ -32,31 +32,46 @@
|
|
|
>
|
|
>
|
|
|
<el-select
|
|
<el-select
|
|
|
v-model="r.info_type"
|
|
v-model="r.info_type"
|
|
|
- placeholder="报备信息类型"
|
|
|
|
|
|
|
+ :placeholder="form.transfer_scene_name ? '选择信息类型' : '请先选择转账场景'"
|
|
|
style="width: 220px"
|
|
style="width: 220px"
|
|
|
|
|
+ :disabled="!form.transfer_scene_name"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="t in unusedReportTypes(form.transfer_scene_name, r.info_type)"
|
|
|
|
|
+ :key="t"
|
|
|
|
|
+ :label="t"
|
|
|
|
|
+ :value="t"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="r.info_content"
|
|
|
|
|
+ :placeholder="reportTip(r.info_type) || '请选择或输入报备内容'"
|
|
|
|
|
+ style="width: 320px"
|
|
|
filterable
|
|
filterable
|
|
|
allow-create
|
|
allow-create
|
|
|
|
|
+ default-first-option
|
|
|
|
|
+ :disabled="!r.info_type"
|
|
|
|
|
+ :maxlength="256"
|
|
|
>
|
|
>
|
|
|
<el-option
|
|
<el-option
|
|
|
- v-for="o in REPORT_INFO_TYPE_OPTIONS"
|
|
|
|
|
- :key="o.value"
|
|
|
|
|
- :label="o.label"
|
|
|
|
|
- :value="o.value"
|
|
|
|
|
|
|
+ v-for="c in reportContentOptions(r.info_type)"
|
|
|
|
|
+ :key="c"
|
|
|
|
|
+ :label="c"
|
|
|
|
|
+ :value="c"
|
|
|
/>
|
|
/>
|
|
|
</el-select>
|
|
</el-select>
|
|
|
- <el-input v-model="r.info_content" placeholder="报备内容说明" style="width: 300px" />
|
|
|
|
|
- <el-button type="danger" plain @click="reportInfos.splice(idx, 1)">删除</el-button>
|
|
|
|
|
|
|
+ <el-button type="danger" plain @click="removeReportRow(idx)">删除</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
<el-button
|
|
<el-button
|
|
|
type="primary"
|
|
type="primary"
|
|
|
plain
|
|
plain
|
|
|
- @click="reportInfos.push({ info_type: '', info_content: '' })"
|
|
|
|
|
|
|
+ :disabled="!form.transfer_scene_name || unusedReportTypes(form.transfer_scene_name, '').length === 0"
|
|
|
|
|
+ @click="addReportRow"
|
|
|
>
|
|
>
|
|
|
添加报备信息
|
|
添加报备信息
|
|
|
</el-button>
|
|
</el-button>
|
|
|
<div class="form-item-tip">
|
|
<div class="form-item-tip">
|
|
|
- 2026 年新接入商户必填。报备信息类型选「转账场景报备」,内容填写本次转账场景的说明,
|
|
|
|
|
- 如:8 月家政服务人员佣金报酬发放,涉及 126 人,共 5.2 万元
|
|
|
|
|
|
|
+ {{ reportTipText }}
|
|
|
</div>
|
|
</div>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item label="超时时间">
|
|
<el-form-item label="超时时间">
|
|
@@ -183,7 +198,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { computed, onMounted, reactive, ref } from "vue";
|
|
|
|
|
|
|
+import { computed, onMounted, reactive, ref, watch } from "vue";
|
|
|
import BatchPayAPI from "@/api/module_payment/batch";
|
|
import BatchPayAPI from "@/api/module_payment/batch";
|
|
|
import { useEnterpriseStore } from "@/store";
|
|
import { useEnterpriseStore } from "@/store";
|
|
|
import { ElMessage } from "element-plus";
|
|
import { ElMessage } from "element-plus";
|
|
@@ -234,10 +249,115 @@ const IDENTITY_TYPE_OPTIONS = [
|
|
|
{ value: "ALIPAY_OPEN_ID", label: "支付宝账户(OpenID)" },
|
|
{ value: "ALIPAY_OPEN_ID", label: "支付宝账户(OpenID)" },
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
-/** 场景报备信息类型(支付宝 transfer_scene_report_infos.info_type 枚举) */
|
|
|
|
|
-const REPORT_INFO_TYPE_OPTIONS = [
|
|
|
|
|
- { value: "TRANSFER_SCENE_REPORT", label: "转账场景报备" },
|
|
|
|
|
-];
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * 转账场景报备信息(支付宝官方文档「转账场景信息字段说明」0iaxid,2026-04-14 更新)
|
|
|
|
|
+ * info_type 为中文取值,按转账场景级联;info_content 自定义描述,选项为文档示例模板
|
|
|
|
|
+ */
|
|
|
|
|
+interface SceneReportType {
|
|
|
|
|
+ /** info_type 取值(严格按文档示例,不可重复) */
|
|
|
|
|
+ type: string;
|
|
|
|
|
+ /** 文档描述列原文,作内容下拉的引导提示 */
|
|
|
|
|
+ tip: string;
|
|
|
|
|
+ /** 文档示例值,作内容下拉的快捷选项(filterable allow-create 可自定义) */
|
|
|
|
|
+ contentOptions: string[];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const REPORT_SCENE_MAP: Record<string, SceneReportType[]> = {
|
|
|
|
|
+ 现金营销: [
|
|
|
|
|
+ { type: "活动名称", tip: "请在信息内容描述收款方参与活动的名称", contentOptions: ["会员红包"] },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "奖励说明",
|
|
|
|
|
+ tip: "请在信息内容描述收款方因为什么奖励获取这笔资金",
|
|
|
|
|
+ contentOptions: ["新会员入会红包"],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ 企业退款: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "退款原因",
|
|
|
|
|
+ tip: "请在信息内容描述收款方接收当前这笔退款的原因,如商品质量问题退款",
|
|
|
|
|
+ contentOptions: ["商品质量问题退款"],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ 佣金报酬: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "佣金报酬说明",
|
|
|
|
|
+ tip: "请在信息内容描述收款方接收当前这笔款项的原因,如 8 月家政服务报酬",
|
|
|
|
|
+ contentOptions: ["8月家政服务报酬"],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ 业务结算: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "结算款项名称",
|
|
|
|
|
+ tip: "请在信息内容描述收款方接收的款项名称,如材料货款",
|
|
|
|
|
+ contentOptions: ["材料货款"],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ 二手回收: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "回收商品名称",
|
|
|
|
|
+ tip: "请在信息内容描述收款方回收的商品名称,如衣服",
|
|
|
|
|
+ contentOptions: ["衣服"],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ 公益补助: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "公益活动名称",
|
|
|
|
|
+ tip: "请在信息内容描述公益活动在民政部的备案名称",
|
|
|
|
|
+ contentOptions: [],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ 行政补贴和退款: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "补贴/退款类型",
|
|
|
|
|
+ tip: "请在信息内容描述补贴/退款类型,如某地人才补贴",
|
|
|
|
|
+ contentOptions: ["某地人才补贴"],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ 保险理赔: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "业务类型",
|
|
|
|
|
+ tip: "请在信息内容描述这笔转账的业务类型,例如理赔、退保、其他",
|
|
|
|
|
+ contentOptions: ["理赔", "退保", "其他"],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "保险险种",
|
|
|
|
|
+ tip: "请在信息内容描述保险险种及保险产品名称,例如医疗险-某百万医疗保险、意外险-某交通意外险等",
|
|
|
|
|
+ contentOptions: [],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "业务交易订单号",
|
|
|
|
|
+ tip: "请在信息内容描述这笔转账的业务内部交易订单号",
|
|
|
|
|
+ contentOptions: [],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/** 某信息类型的文档引导文案(内容下拉 placeholder 用) */
|
|
|
|
|
+function reportTip(infoType: string): string {
|
|
|
|
|
+ for (const types of Object.values(REPORT_SCENE_MAP)) {
|
|
|
|
|
+ const t = types.find((x) => x.type === infoType);
|
|
|
|
|
+ if (t) return t.tip;
|
|
|
|
|
+ }
|
|
|
|
|
+ return "";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 某信息类型的文档示例选项(内容下拉用) */
|
|
|
|
|
+function reportContentOptions(infoType: string): string[] {
|
|
|
|
|
+ for (const types of Object.values(REPORT_SCENE_MAP)) {
|
|
|
|
|
+ const t = types.find((x) => x.type === infoType);
|
|
|
|
|
+ if (t) return t.contentOptions;
|
|
|
|
|
+ }
|
|
|
|
|
+ return [];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 该场景未被当前行占用的信息类型(文档要求类型不可重复) */
|
|
|
|
|
+function unusedReportTypes(scene: string, excludeType: string): string[] {
|
|
|
|
|
+ const all = (REPORT_SCENE_MAP[scene] ?? []).map((t) => t.type);
|
|
|
|
|
+ const used = reportInfos.value
|
|
|
|
|
+ .map((r) => r.info_type)
|
|
|
|
|
+ .filter((t) => t !== "" && t !== excludeType);
|
|
|
|
|
+ return all.filter((t) => !used.includes(t));
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
const IDENTITY_TYPE_CODES = IDENTITY_TYPE_OPTIONS.map((o) => o.value);
|
|
const IDENTITY_TYPE_CODES = IDENTITY_TYPE_OPTIONS.map((o) => o.value);
|
|
|
|
|
|
|
@@ -254,6 +374,45 @@ const detailRows = ref<DetailRow[]>([]);
|
|
|
const reportInfos = ref<Array<{ info_type: string; info_content: string }>>([]);
|
|
const reportInfos = ref<Array<{ info_type: string; info_content: string }>>([]);
|
|
|
const importFileList = ref<any[]>([]);
|
|
const importFileList = ref<any[]>([]);
|
|
|
|
|
|
|
|
|
|
+/** 选择转账场景后自动生成该场景全部信息类型行(文档:多类型场景需完整传入) */
|
|
|
|
|
+watch(
|
|
|
|
|
+ () => form.transfer_scene_name,
|
|
|
|
|
+ (scene) => {
|
|
|
|
|
+ if (scene) {
|
|
|
|
|
+ reportInfos.value = (REPORT_SCENE_MAP[scene] ?? []).map((t) => ({
|
|
|
|
|
+ info_type: t.type,
|
|
|
|
|
+ info_content: "",
|
|
|
|
|
+ }));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ reportInfos.value = [];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+/** 添加一行未占用的信息类型(文档要求类型不可重复) */
|
|
|
|
|
+function addReportRow() {
|
|
|
|
|
+ if (!form.transfer_scene_name) return;
|
|
|
|
|
+ const unused = unusedReportTypes(form.transfer_scene_name, "");
|
|
|
|
|
+ if (unused.length === 0) return;
|
|
|
|
|
+ reportInfos.value.push({ info_type: unused[0], info_content: "" });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function removeReportRow(idx: number) {
|
|
|
|
|
+ reportInfos.value.splice(idx, 1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 报备区引导文案:场景级联 + 类型级联 + 缺类型提醒 */
|
|
|
|
|
+const reportTipText = computed(() => {
|
|
|
|
|
+ if (!form.transfer_scene_name) {
|
|
|
|
|
+ return "2026 年新接入商户必填。请先在上方选择转账场景,系统将按支付宝文档自动生成该场景需要报备的信息类型。";
|
|
|
|
|
+ }
|
|
|
|
|
+ const types = REPORT_SCENE_MAP[form.transfer_scene_name] ?? [];
|
|
|
|
|
+ const missing = types.filter((t) => !reportInfos.value.some((r) => r.info_type === t.type));
|
|
|
|
|
+ const base = `该场景需报备 ${types.length} 条信息(支付宝文档),已自动生成。`;
|
|
|
|
|
+ if (missing.length) return `${base} 还缺少:${missing.map((t) => t.type).join("、")}`;
|
|
|
|
|
+ return `${base} 请为每条信息填写报备内容说明。`;
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
let detailSeq = 0;
|
|
let detailSeq = 0;
|
|
|
function genOutBizNo() {
|
|
function genOutBizNo() {
|
|
|
detailSeq += 1;
|
|
detailSeq += 1;
|
|
@@ -408,12 +567,22 @@ async function handleSubmit() {
|
|
|
}
|
|
}
|
|
|
const valid = await formRef.value?.validate().catch(() => false);
|
|
const valid = await formRef.value?.validate().catch(() => false);
|
|
|
if (!valid) return;
|
|
if (!valid) return;
|
|
|
- // I3: 场景报备信息必填(后端 DTO 同步 @NotEmpty 校验),至少一条 info_type/info_content 非空
|
|
|
|
|
- const hasValidReport = reportInfos.value.some(
|
|
|
|
|
- (r) => r.info_type.trim() !== "" && r.info_content.trim() !== ""
|
|
|
|
|
|
|
+ // 场景报备信息必填(后端 DTO @NotEmpty 同步校验):每行完整 + 该场景全部信息类型齐全
|
|
|
|
|
+ const sceneTypes = REPORT_SCENE_MAP[form.transfer_scene_name] ?? [];
|
|
|
|
|
+ const missingTypes = sceneTypes.filter(
|
|
|
|
|
+ (t) => !reportInfos.value.some((r) => r.info_type === t.type)
|
|
|
|
|
+ );
|
|
|
|
|
+ if (missingTypes.length) {
|
|
|
|
|
+ ElMessage.warning(
|
|
|
|
|
+ `该转账场景需完整报备:${missingTypes.map((t) => t.type).join("、")}(支付宝文档要求,不可省略)`
|
|
|
|
|
+ );
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const incompleteRow = reportInfos.value.find(
|
|
|
|
|
+ (r) => !r.info_type || !r.info_content.trim()
|
|
|
);
|
|
);
|
|
|
- if (!hasValidReport) {
|
|
|
|
|
- ElMessage.warning("请填写至少一条完整的场景报备信息(类型与内容均必填)");
|
|
|
|
|
|
|
+ if (incompleteRow) {
|
|
|
|
|
+ ElMessage.warning("请为每条场景报备信息填写报备内容说明");
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
if (detailRows.value.length === 0) {
|
|
if (detailRows.value.length === 0) {
|