From d8eaa16682cf180e9563d6be05fb43057daf6c07 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期一, 12 十月 2026 10:34:14 +0800
Subject: [PATCH] feat(merchant): 后台纳管外部进件商户并支持解绑
---
talent-card-admin/src/views/MerchantSettlementView.vue | 258 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 256 insertions(+), 2 deletions(-)
diff --git a/talent-card-admin/src/views/MerchantSettlementView.vue b/talent-card-admin/src/views/MerchantSettlementView.vue
index 4ba1a12..8705464 100644
--- a/talent-card-admin/src/views/MerchantSettlementView.vue
+++ b/talent-card-admin/src/views/MerchantSettlementView.vue
@@ -1,8 +1,17 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
-import { Refresh, Search } from '@element-plus/icons-vue'
-import { getMerchantSettlement, getMerchantSettlementMerchantTypes, getMerchantSettlements } from '../api/admin'
+import { Plus, Refresh, Search } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
+import {
+ adoptManagedOnboarding,
+ getAdminMerchants,
+ getMerchantSettlement,
+ getMerchantSettlementMerchantTypes,
+ getMerchantSettlements,
+ previewManagedOnboarding,
+} from '../api/admin'
+import { hasAdminPermission } from '../utils/permissions'
type Kind = 'onboardings' | 'bank-cards' | 'settlements'
@@ -202,6 +211,108 @@
}
}
+/**
+ * 纳管外部进件商户。
+ *
+ * 流程刻意分两步:先「核验通道商户」做一次只读查询,把通道侧的商户注册名称、商户简称与主体类型
+ * 展示出来,运营与所选商家核对后**显式勾选确认**才能提交。不做「名称是否对得上」的程序判定——
+ * 我们这边只有一个商家名(常是简称或服务点名),通道那边是工商全称,形态不对应,规则化匹配必然误判。
+ */
+const adoptVisible = ref(false)
+const adoptPreviewing = ref(false)
+const adoptSubmitting = ref(false)
+const adoptConfirmed = ref(false)
+const adoptPreview = ref<Record<string, any>>()
+const adoptForm = reactive({ merchantId: undefined as number | string | undefined, providerHuifuId: '', providerApplyNo: '' })
+const merchantOptions = ref<Array<{ id: number | string; merchantName: string }>>([])
+const merchantLoading = ref(false)
+
+async function searchMerchants(keyword: string) {
+ merchantLoading.value = true
+ try {
+ const result = await getAdminMerchants({ page: 1, size: 20, keyword: keyword?.trim() || undefined })
+ merchantOptions.value = result.records || []
+ } catch {
+ merchantOptions.value = []
+ } finally {
+ merchantLoading.value = false
+ }
+}
+
+function openAdopt() {
+ adoptForm.merchantId = undefined
+ adoptForm.providerHuifuId = ''
+ adoptForm.providerApplyNo = ''
+ adoptPreview.value = undefined
+ adoptConfirmed.value = false
+ adoptVisible.value = true
+ if (merchantOptions.value.length === 0) searchMerchants('')
+}
+
+/** 换了商家或商户号,之前那次核验的对象就不是现在填的了,确认必须重新勾。 */
+function invalidateAdoptPreview() {
+ adoptPreview.value = undefined
+ adoptConfirmed.value = false
+}
+
+async function runAdoptPreview() {
+ if (!adoptForm.merchantId) {
+ ElMessage.warning('请先选择我方商家')
+ return
+ }
+ if (!adoptForm.providerHuifuId.trim()) {
+ ElMessage.warning('请先填写汇付商户号')
+ return
+ }
+ adoptPreviewing.value = true
+ try {
+ adoptPreview.value = await previewManagedOnboarding({
+ merchantId: adoptForm.merchantId,
+ providerHuifuId: adoptForm.providerHuifuId.trim(),
+ })
+ adoptConfirmed.value = false
+ } catch (error: any) {
+ adoptPreview.value = undefined
+ adoptConfirmed.value = false
+ ElMessage.error(error?.message || '核验通道商户失败,请确认商户号后重试')
+ } finally {
+ adoptPreviewing.value = false
+ }
+}
+
+async function submitAdopt() {
+ if (!adoptPreview.value) {
+ ElMessage.warning('请先核验通道商户')
+ return
+ }
+ if (!adoptForm.providerApplyNo.trim()) {
+ ElMessage.warning('请填写汇付侧的入驻申请单号')
+ return
+ }
+ if (!adoptConfirmed.value) {
+ ElMessage.warning('请先核对通道商户名称并勾选确认')
+ return
+ }
+ adoptSubmitting.value = true
+ try {
+ const result = await adoptManagedOnboarding({
+ merchantId: adoptForm.merchantId as number | string,
+ providerHuifuId: adoptForm.providerHuifuId.trim(),
+ providerApplyNo: adoptForm.providerApplyNo.trim(),
+ confirmed: true,
+ })
+ adoptVisible.value = false
+ ElMessage.success('纳管成功,已触发一次进度刷新')
+ await load()
+ // 纳管产出的就是一条进件记录,直接进详情看分阶段进度与待办理项。
+ if (result?.id) router.push({ name: 'merchant-onboarding-detail', params: { id: String(result.id) } })
+ } catch (error: any) {
+ ElMessage.error(error?.message || '纳管失败,请稍后重试')
+ } finally {
+ adoptSubmitting.value = false
+ }
+}
+
watch(() => route.query.kind, () => {
const next = routeKind()
// changeKind 自己写回地址栏也会走到这里,那一次已经查过了,别重复请求。
@@ -265,6 +376,18 @@
<el-button :icon="Refresh" @click="reset">重置</el-button>
</el-form-item>
</el-form>
+ <!-- 纳管入口只放在「商户进件」页签:纳管产出的就是一条进件记录,这个页签正是进件记录的列表,
+ 纳管结果当场可见。位置沿用「商家查询」页「新建」按钮的既有习惯。 -->
+ <div v-if="kind === 'onboardings'" class="settlement-table-tools">
+ <el-button
+ v-if="hasAdminPermission('admin.merchant-settlement.edit')"
+ type="primary"
+ :icon="Plus"
+ @click="openAdopt"
+ >
+ 纳管外部进件商户
+ </el-button>
+ </div>
</section>
<section class="table-panel">
@@ -289,6 +412,12 @@
<el-table-column prop="merchantType" label="商家类型" min-width="110" />
<el-table-column prop="subjectName" label="进件主体" min-width="150" show-overflow-tooltip />
<el-table-column label="主体类型" min-width="105"><template #default="{ row }">{{ subjectTypeLabel(row.subjectType) }}</template></el-table-column>
+ <!-- 来源标记:区分两条进件路径,纳管进来的要一眼看出来。 -->
+ <el-table-column label="来源" min-width="100" align="center">
+ <template #default="{ row }">
+ <el-tag :type="row.source === 'managed' ? 'success' : 'info'" effect="plain">{{ row.source === 'managed' ? '纳管' : '自助进件' }}</el-tag>
+ </template>
+ </el-table-column>
</template>
<template v-else>
<el-table-column prop="id" label="申请id" min-width="100" />
@@ -336,6 +465,125 @@
/>
</div>
</section>
+
+ <el-dialog
+ v-model="adoptVisible"
+ title="纳管外部进件商户"
+ width="640px"
+ :close-on-click-modal="false"
+ destroy-on-close
+ >
+ <p class="adopt-hint">
+ 用于把<b>已在汇付侧完成进件</b>的商户接到我方某个商家上。纳管全程只查询、不下发:不会补开微信、
+ 不会绑定人才端、不会改动支付场景与延迟入账。
+ </p>
+ <el-form label-width="120px" class="adopt-form">
+ <el-form-item label="我方商家" required>
+ <el-select
+ v-model="adoptForm.merchantId"
+ filterable
+ remote
+ reserve-keyword
+ clearable
+ placeholder="输入商家名称搜索"
+ :remote-method="searchMerchants"
+ :loading="merchantLoading"
+ @change="invalidateAdoptPreview"
+ >
+ <el-option
+ v-for="item in merchantOptions"
+ :key="item.id"
+ :label="item.merchantName"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="汇付商户号" required>
+ <el-input v-model="adoptForm.providerHuifuId" clearable placeholder="斗拱侧该商户的 huifu_id" @input="invalidateAdoptPreview" />
+ </el-form-item>
+ <el-form-item label="入驻申请单号" required>
+ <el-input v-model="adoptForm.providerApplyNo" clearable placeholder="汇付控台入驻申请列表里的申请单号" />
+ </el-form-item>
+ <el-form-item label=" ">
+ <el-button type="primary" plain :loading="adoptPreviewing" @click="runAdoptPreview">核验通道商户</el-button>
+ <span class="adopt-tip">卡号不用填,从通道查回来。</span>
+ </el-form-item>
+ </el-form>
+
+ <template v-if="adoptPreview">
+ <el-descriptions :column="1" border size="small" class="adopt-preview">
+ <el-descriptions-item label="我方商家">
+ {{ adoptPreview.merchantName }}<span class="muted">(门店:{{ adoptPreview.storeName }})</span>
+ </el-descriptions-item>
+ <el-descriptions-item label="通道商户注册名称">{{ adoptPreview.channelMerchantName }}</el-descriptions-item>
+ <el-descriptions-item label="通道商户简称">{{ adoptPreview.channelShortName }}</el-descriptions-item>
+ <el-descriptions-item label="通道主体类型">
+ {{ adoptPreview.channelSubjectType === 'enterprise' ? '企业' : '个人' }}
+ <span v-if="adoptPreview.channelCompanyType" class="muted">({{ adoptPreview.channelCompanyType }})</span>
+ </el-descriptions-item>
+ <el-descriptions-item label="结算银行卡">
+ {{ adoptPreview.settlementCardMasked || '-' }}
+ <span v-if="adoptPreview.settlementBankName" class="muted">({{ adoptPreview.settlementBankName }})</span>
+ </el-descriptions-item>
+ </el-descriptions>
+ <!-- 只提示不拦截:连锁各门店共用一个收款主体是允许的,这里列出已绑的商家让运营自己确认。 -->
+ <el-alert
+ v-if="adoptPreview.alreadyManaged"
+ class="adopt-alert"
+ type="success"
+ :closable="false"
+ title="该商家已纳管过这个汇付商户号"
+ description="重复纳管是幂等的,提交会直接返回原来那条进件记录,不会产生第二条。"
+ />
+ <el-alert
+ v-if="adoptPreview.boundMerchants?.length"
+ class="adopt-alert"
+ type="info"
+ :closable="false"
+ :title="`该汇付商户号已绑给:${adoptPreview.boundMerchants.join('、')}`"
+ description="可以继续纳管(连锁各门店共用一个收款主体)。"
+ />
+ <el-alert
+ v-if="adoptPreview.nameWarning"
+ class="adopt-alert"
+ type="warning"
+ :closable="false"
+ title="通道名称与所选商家名完全没有重合字符"
+ :description="adoptPreview.nameWarning"
+ />
+ <el-alert
+ v-if="adoptPreview.loginNotice"
+ class="adopt-alert"
+ type="warning"
+ :closable="false"
+ title="商户端登录"
+ :description="adoptPreview.loginNotice"
+ />
+ <el-alert
+ v-for="blocker in adoptPreview.blockers || []"
+ :key="blocker"
+ class="adopt-alert"
+ type="error"
+ :closable="false"
+ :title="blocker"
+ />
+ <el-checkbox v-model="adoptConfirmed" :disabled="(adoptPreview.blockers || []).length > 0">
+ 我已把通道侧的商户注册名称与简称与所选商家核对一致(纳管后该商家的收款将进入这个收款主体)
+ </el-checkbox>
+ </template>
+
+ <template #footer>
+ <el-button @click="adoptVisible = false">取消</el-button>
+ <el-button
+ type="primary"
+ :loading="adoptSubmitting"
+ :disabled="!adoptPreview || !adoptConfirmed || (adoptPreview.blockers || []).length > 0"
+ @click="submitAdopt"
+ >
+ 提交纳管
+ </el-button>
+ </template>
+ </el-dialog>
</div>
</template>
@@ -352,6 +600,12 @@
.query-form :deep(.el-input), .query-form :deep(.el-select), .query-form :deep(.el-date-editor) { width: 100%; }
.query-actions { justify-content: flex-end; }
.query-actions :deep(.el-form-item__content) { justify-content: flex-end; }
+.settlement-table-tools { margin: 0 0 12px; }
+.adopt-hint { margin: 0 0 16px; color: #606266; font-size: 13px; line-height: 20px; }
+.adopt-form :deep(.el-select) { width: 100%; }
+.adopt-tip { margin-left: 12px; color: #909399; font-size: 12px; }
+.adopt-preview { margin-bottom: 14px; }
+.adopt-alert { margin-bottom: 12px; }
.table-panel { margin-top: 16px; padding: 0 20px 12px; }
.settlement-table { width: 100%; }
.settlement-table :deep(th.el-table__cell) { color: #303133; font-weight: 600; background: #f7f8fa; }
--
Gitblit v1.7.1