mirror of
https://gitee.com/bootx/dax-pay-ui
synced 2026-08-12 23:45:39 +08:00
feat(admin): 应用级聚合扫码配置页面
新增聚合扫码配置 API/页面与权限码, 应用卡片入口联动。
This commit is contained in:
67
apps/daxpay-admin/src/api/payment/merchant/aggregate.api.ts
Normal file
67
apps/daxpay-admin/src/api/payment/merchant/aggregate.api.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { BaseEntity, LabelValue, Result } from '#/types/web';
|
||||
|
||||
import { defHttp } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 聚合扫码配置 API
|
||||
* 对接后端 GatewayAggregateConfigAdminController
|
||||
*/
|
||||
export const AggregateConfigApi = {
|
||||
/** 按应用查询聚合扫码配置 */
|
||||
getByAppId(appId: string): Promise<Result<AggregateConfigResult>> {
|
||||
return defHttp.get({
|
||||
url: '/admin/gateway/aggregate-config/get-by-app-id',
|
||||
params: { appId },
|
||||
});
|
||||
},
|
||||
|
||||
/** 保存或更新聚合扫码配置 */
|
||||
saveOrUpdate(data: AggregateConfigParam): Promise<Result<void>> {
|
||||
return defHttp.post({
|
||||
url: '/admin/gateway/aggregate-config/save-or-update',
|
||||
data,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/** 聚合扫码配置结果 */
|
||||
export interface AggregateConfigResult extends BaseEntity {
|
||||
appId?: string;
|
||||
mchNo?: string;
|
||||
/** 配置深度: auto/method/direct */
|
||||
level?: string;
|
||||
autoLaunch?: boolean;
|
||||
scenes?: AggregateSceneResult[];
|
||||
}
|
||||
|
||||
/** 场景配置结果 */
|
||||
export interface AggregateSceneResult {
|
||||
/** 场景编码: wechat_pay/alipay/union_pay/douyin */
|
||||
scene?: string;
|
||||
/** 支付方式(METHOD 模式) */
|
||||
method?: string;
|
||||
/** 通道商户号(DIRECT 模式) */
|
||||
channelMchNo?: string;
|
||||
/** 支付能力(DIRECT 模式) */
|
||||
capability?: string;
|
||||
}
|
||||
|
||||
/** 聚合扫码配置参数 */
|
||||
export interface AggregateConfigParam {
|
||||
mchNo: string;
|
||||
appId: string;
|
||||
level: string;
|
||||
autoLaunch?: boolean;
|
||||
scenes?: AggregateSceneParam[];
|
||||
}
|
||||
|
||||
/** 场景配置参数 */
|
||||
export interface AggregateSceneParam {
|
||||
scene?: string;
|
||||
method?: string;
|
||||
channelMchNo?: string;
|
||||
capability?: string;
|
||||
}
|
||||
|
||||
// re-export LabelValue 供消费方就近引用
|
||||
export type { LabelValue };
|
||||
@@ -69,6 +69,11 @@ export const PermCodes = {
|
||||
VIEW: 'merchant:app:route:view',
|
||||
MANAGE: 'merchant:app:route:manage',
|
||||
},
|
||||
/** 聚合扫码配置 menuCode=merchant:gateway-aggregate */
|
||||
GatewayAggregate: {
|
||||
VIEW: 'merchant:gateway-aggregate:view',
|
||||
MANAGE: 'merchant:gateway-aggregate:update',
|
||||
},
|
||||
/** 门店 menuCode=merchant:store */
|
||||
Store: {
|
||||
VIEW: 'merchant:store:view',
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"aggregate": {
|
||||
"title": "Aggregate QR Pay Config",
|
||||
"editModeLabel": "Config Level",
|
||||
"autoLaunch": "Auto Launch",
|
||||
"autoLaunchHint": "Automatically initiate payment after scanning, no manual click needed",
|
||||
"autoModeHint": "System auto-derives payment method by scan environment, resolved via basic routing. No manual config needed.",
|
||||
"methodModeHint": "Manually select payment method per scene, resolved via scene routing.",
|
||||
"directModeHint": "Directly specify channel merchant and capability per scene, bypassing routing.",
|
||||
"scene": "Scene",
|
||||
"method": "Payment Method",
|
||||
"methodPlaceholder": "Select payment method",
|
||||
"channelMerchant": "Channel Merchant",
|
||||
"channelMerchantPlaceholder": "Select channel merchant",
|
||||
"capability": "Capability",
|
||||
"capabilityPlaceholder": "Select capability",
|
||||
"autoDerived": "Auto-derived",
|
||||
"saveConfirm": "Are you sure to save the aggregate QR pay config?",
|
||||
"missingAppContext": "Please access aggregate QR config from app management",
|
||||
"notSelected": "Not selected",
|
||||
"levelActive": "Active",
|
||||
"scenes": {
|
||||
"wechat_pay": "WeChat",
|
||||
"alipay": "Alipay",
|
||||
"union_pay": "UnionPay",
|
||||
"douyin": "Douyin"
|
||||
},
|
||||
"modeAuto": "Auto",
|
||||
"modeMethod": "By Method",
|
||||
"modeDirect": "Direct"
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
"validationAppName": "Please enter app name",
|
||||
"validationStatus": "Please select status",
|
||||
"payRoute": "Channel Routing",
|
||||
"aggregateScan": "Aggregate QR",
|
||||
"notifyConfig": "Notification Config"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"aggregate": {
|
||||
"title": "聚合扫码配置",
|
||||
"editModeLabel": "配置模式",
|
||||
"autoLaunch": "自动拉起支付",
|
||||
"autoLaunchHint": "扫码后自动发起支付,无需用户手动点击",
|
||||
"autoModeHint": "系统按扫码环境自动推导支付方式,走路由基础模式解析,无需手动配置",
|
||||
"methodModeHint": "每个场景手动选择支付方式,走路由场景模式解析",
|
||||
"directModeHint": "每个场景直接指定通道商户号和支付能力,跳过路由直定通道",
|
||||
"scene": "场景",
|
||||
"method": "支付方式",
|
||||
"methodPlaceholder": "请选择支付方式",
|
||||
"channelMerchant": "通道商户号",
|
||||
"channelMerchantPlaceholder": "请选择通道商户",
|
||||
"capability": "支付能力",
|
||||
"capabilityPlaceholder": "请选择支付能力",
|
||||
"autoDerived": "系统自动推导",
|
||||
"saveConfirm": "确定要保存聚合扫码配置吗?",
|
||||
"missingAppContext": "请从应用管理进入聚合扫码配置",
|
||||
"notSelected": "未选择",
|
||||
"levelActive": "生效中",
|
||||
"scenes": {
|
||||
"wechat_pay": "微信环境",
|
||||
"alipay": "支付宝环境",
|
||||
"union_pay": "云闪付环境",
|
||||
"douyin": "抖音环境"
|
||||
},
|
||||
"modeAuto": "自动模式",
|
||||
"modeMethod": "方式模式",
|
||||
"modeDirect": "精确模式"
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
"validationAppName": "请输入应用名称",
|
||||
"validationStatus": "请选择状态",
|
||||
"payRoute": "通道路由",
|
||||
"aggregateScan": "聚合扫码",
|
||||
"notifyConfig": "通知配置"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,450 @@
|
||||
<script lang="ts" setup>
|
||||
import type { LabelValue } from '#/types/web';
|
||||
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { IconifyIcon } from '@vben-core/icons';
|
||||
|
||||
import { MchAppInfoApi, type MchAppInfoResult } from '#/api/payment/merchant/mch-app-info.api';
|
||||
import {
|
||||
AggregateConfigApi,
|
||||
type AggregateConfigResult,
|
||||
type AggregateSceneParam,
|
||||
} from '#/api/payment/merchant/aggregate.api';
|
||||
import { PayRouteApi } from '#/api/payment/route/pay-route.api';
|
||||
import RouteQueryMissingState from '#/components/route/RouteQueryMissingState.vue';
|
||||
import { useMessage } from '#/hooks/useMessage';
|
||||
import { normalizeRouteQueryValue, useRequiredRouteQuery } from '#/hooks/useRequiredRouteQuery';
|
||||
|
||||
import { AGGREGATE_LEVEL, AGGREGATE_SCENES, type AggregateLevel } from './shared/constants';
|
||||
|
||||
defineOptions({ name: 'AggregateScanConfig' });
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { confirm, message } = useMessage();
|
||||
|
||||
// 路由参数校验: mchNo + appId 必传
|
||||
const routeContext = useRequiredRouteQuery({
|
||||
keys: ['mchNo', 'appId'],
|
||||
messageKey: computed(() =>
|
||||
normalizeRouteQueryValue(route.query.mchNo)
|
||||
? 'payment.merchant.aggregate.aggregate.missingAppContext'
|
||||
: 'payment.common.route.missingMchNo',
|
||||
),
|
||||
fallbackPath: computed(() => {
|
||||
const no = normalizeRouteQueryValue(route.query.mchNo);
|
||||
return no ? { path: '/payment/merchant/app', query: { mchNo: no } } : '/payment/merchant';
|
||||
}),
|
||||
});
|
||||
|
||||
const mchNo = computed(() => routeContext.query.value.mchNo);
|
||||
const appId = computed(() => routeContext.query.value.appId);
|
||||
|
||||
// 页面状态
|
||||
const loading = ref(false);
|
||||
const editing = ref(false);
|
||||
const appInfo = ref<MchAppInfoResult>({});
|
||||
const config = ref<AggregateConfigResult>({});
|
||||
|
||||
// 编辑态
|
||||
const editLevel = ref<AggregateLevel>(AGGREGATE_LEVEL.AUTO);
|
||||
const autoLaunch = ref(false);
|
||||
// 每场景的编辑数据: scene → { method, channelMchNo, capability }
|
||||
const sceneForm = ref<Record<string, AggregateSceneParam>>({});
|
||||
|
||||
// 候选数据
|
||||
const methodDirectory = ref<Record<string, LabelValue[]>>({}); // provider → 方式列表
|
||||
const channelMchMap = ref<Record<string, LabelValue[]>>({}); // scene → 通道商户列表
|
||||
const capabilityMap = ref<Record<string, LabelValue[]>>({}); // scene → 能力列表
|
||||
|
||||
// 生效模式(服务端)
|
||||
const effectiveLevel = computed(() => config.value.level || AGGREGATE_LEVEL.AUTO);
|
||||
const isLevelActive = computed(() => editLevel.value === effectiveLevel.value);
|
||||
|
||||
// 模式提示
|
||||
const modeHint = computed(() => {
|
||||
const key = editLevel.value;
|
||||
if (key === AGGREGATE_LEVEL.AUTO) {
|
||||
return $t('payment.merchant.aggregate.aggregate.autoModeHint');
|
||||
}
|
||||
if (key === AGGREGATE_LEVEL.METHOD) {
|
||||
return $t('payment.merchant.aggregate.aggregate.methodModeHint');
|
||||
}
|
||||
return $t('payment.merchant.aggregate.aggregate.directModeHint');
|
||||
});
|
||||
|
||||
/** 场景国际化名 */
|
||||
function sceneLabel(scene: string) {
|
||||
return $t(`payment.merchant.aggregate.aggregate.scenes.${scene}`);
|
||||
}
|
||||
|
||||
/** 初始化每场景编辑数据 */
|
||||
function initSceneForm() {
|
||||
const form: Record<string, AggregateSceneParam> = {};
|
||||
for (const sc of AGGREGATE_SCENES) {
|
||||
const serverScene = config.value.scenes?.find((s) => s.scene === sc.scene);
|
||||
form[sc.scene] = {
|
||||
scene: sc.scene,
|
||||
method: serverScene?.method || '',
|
||||
channelMchNo: serverScene?.channelMchNo || '',
|
||||
capability: serverScene?.capability || '',
|
||||
};
|
||||
}
|
||||
sceneForm.value = form;
|
||||
}
|
||||
|
||||
/** 获取某场景编辑数据 */
|
||||
function getSceneData(scene: string) {
|
||||
if (!sceneForm.value[scene]) {
|
||||
sceneForm.value[scene] = { scene, method: '', channelMchNo: '', capability: '' };
|
||||
}
|
||||
return sceneForm.value[scene]!;
|
||||
}
|
||||
|
||||
/** METHOD 模式: 某渠道下可选支付方式 */
|
||||
function methodOptions(provider: string) {
|
||||
return methodDirectory.value[provider] || [];
|
||||
}
|
||||
|
||||
/** DIRECT 模式: 某场景可选通道商户 */
|
||||
function channelMchOptions(scene: string) {
|
||||
return channelMchMap.value[scene] || [];
|
||||
}
|
||||
|
||||
/** DIRECT 模式: 某场景可选能力 */
|
||||
function capabilityOptions(scene: string) {
|
||||
return capabilityMap.value[scene] || [];
|
||||
}
|
||||
|
||||
/** 加载支付方式目录(METHOD 模式用) */
|
||||
async function loadMethodDirectory() {
|
||||
const { data } = await PayRouteApi.listMethodDirectoryFlat();
|
||||
const map: Record<string, LabelValue[]> = {};
|
||||
for (const item of data || []) {
|
||||
if (!map[item.provider]) {
|
||||
map[item.provider] = [];
|
||||
}
|
||||
map[item.provider]!.push({ label: item.methodLabel || item.method, value: item.method });
|
||||
}
|
||||
methodDirectory.value = map;
|
||||
}
|
||||
|
||||
/** 加载通道商户候选(DIRECT 模式用) */
|
||||
async function loadChannelMchCandidates() {
|
||||
const map: Record<string, LabelValue[]> = {};
|
||||
await Promise.all(
|
||||
AGGREGATE_SCENES.map(async (sc) => {
|
||||
const { data } = await PayRouteApi.listSceneChannelMchCandidates({
|
||||
appId: appId.value,
|
||||
provider: sc.provider,
|
||||
});
|
||||
map[sc.scene] = data || [];
|
||||
}),
|
||||
);
|
||||
channelMchMap.value = map;
|
||||
}
|
||||
|
||||
/** 某场景选中通道商户后加载能力候选 */
|
||||
async function loadCapabilityForScene(scene: string, channelMchNo: string) {
|
||||
const sc = AGGREGATE_SCENES.find((s) => s.scene === scene);
|
||||
if (!sc || !channelMchNo) {
|
||||
capabilityMap.value = { ...capabilityMap.value, [scene]: [] };
|
||||
return;
|
||||
}
|
||||
const { data } = await PayRouteApi.listSceneCapabilityCandidates({
|
||||
appId: appId.value,
|
||||
provider: sc.provider,
|
||||
method: sc.defaultMethod,
|
||||
channelMchNo,
|
||||
});
|
||||
capabilityMap.value = { ...capabilityMap.value, [scene]: data || [] };
|
||||
}
|
||||
|
||||
/** 加载应用信息 */
|
||||
async function loadAppInfo() {
|
||||
if (!mchNo.value || !appId.value) return;
|
||||
const { data } = await MchAppInfoApi.page({ mchNo: mchNo.value, current: 1, size: 200 });
|
||||
const app = data?.records?.find((a) => a.appId === appId.value);
|
||||
appInfo.value = app || {};
|
||||
}
|
||||
|
||||
/** 加载配置 */
|
||||
async function loadConfig() {
|
||||
if (!appId.value) return;
|
||||
loading.value = true;
|
||||
const { data } = await AggregateConfigApi.getByAppId(appId.value);
|
||||
config.value = data || {};
|
||||
editLevel.value = (effectiveLevel.value as AggregateLevel) || AGGREGATE_LEVEL.AUTO;
|
||||
autoLaunch.value = config.value.autoLaunch || false;
|
||||
initSceneForm();
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
/** 返回应用列表 */
|
||||
function handleBack() {
|
||||
router.push({ path: '/payment/merchant/app', query: { mchNo: mchNo.value } });
|
||||
}
|
||||
|
||||
/** 进入编辑 */
|
||||
async function startEdit() {
|
||||
editing.value = true;
|
||||
initSceneForm();
|
||||
// 按模式加载候选
|
||||
if (editLevel.value === AGGREGATE_LEVEL.METHOD) {
|
||||
await loadMethodDirectory();
|
||||
} else if (editLevel.value === AGGREGATE_LEVEL.DIRECT) {
|
||||
await loadChannelMchCandidates();
|
||||
// 对已选通道商户的场景加载能力候选
|
||||
for (const sc of AGGREGATE_SCENES) {
|
||||
const sd = getSceneData(sc.scene);
|
||||
if (sd.channelMchNo) {
|
||||
await loadCapabilityForScene(sc.scene, sd.channelMchNo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存 */
|
||||
function save() {
|
||||
// 按模式校验
|
||||
if (editLevel.value === AGGREGATE_LEVEL.METHOD) {
|
||||
for (const sc of AGGREGATE_SCENES) {
|
||||
if (!getSceneData(sc.scene).method) {
|
||||
message.error(
|
||||
$t('payment.merchant.aggregate.aggregate.methodPlaceholder') +
|
||||
': ' +
|
||||
sceneLabel(sc.scene),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (editLevel.value === AGGREGATE_LEVEL.DIRECT) {
|
||||
for (const sc of AGGREGATE_SCENES) {
|
||||
const sd = getSceneData(sc.scene);
|
||||
if (!sd.channelMchNo) {
|
||||
message.error(
|
||||
$t('payment.merchant.aggregate.aggregate.channelMerchantPlaceholder') +
|
||||
': ' +
|
||||
sceneLabel(sc.scene),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
confirm({
|
||||
content: $t('payment.merchant.aggregate.aggregate.saveConfirm'),
|
||||
async onOk() {
|
||||
const scenes =
|
||||
editLevel.value === AGGREGATE_LEVEL.AUTO
|
||||
? []
|
||||
: Object.values(sceneForm.value);
|
||||
await AggregateConfigApi.saveOrUpdate({
|
||||
mchNo: mchNo.value,
|
||||
appId: appId.value,
|
||||
level: editLevel.value,
|
||||
autoLaunch: autoLaunch.value,
|
||||
scenes,
|
||||
});
|
||||
message.success($t('common.operationSuccess'));
|
||||
editing.value = false;
|
||||
await loadConfig();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 取消编辑 */
|
||||
function cancel() {
|
||||
editing.value = false;
|
||||
initSceneForm();
|
||||
autoLaunch.value = config.value.autoLaunch || false;
|
||||
editLevel.value = effectiveLevel.value as AggregateLevel;
|
||||
}
|
||||
|
||||
// 编辑模式下切换模式时加载候选
|
||||
watch(editLevel, async (level, prev) => {
|
||||
if (!editing.value || level === prev) return;
|
||||
if (level === AGGREGATE_LEVEL.METHOD) {
|
||||
await loadMethodDirectory();
|
||||
} else if (level === AGGREGATE_LEVEL.DIRECT) {
|
||||
await loadChannelMchCandidates();
|
||||
}
|
||||
});
|
||||
|
||||
/** DIRECT 模式: 通道商户变更时加载能力候选并清空旧值 */
|
||||
async function onChannelMchChange(scene: string, channelMchNo: any) {
|
||||
const sd = getSceneData(scene);
|
||||
sd.channelMchNo = channelMchNo || '';
|
||||
sd.capability = '';
|
||||
if (channelMchNo) {
|
||||
await loadCapabilityForScene(scene, channelMchNo);
|
||||
} else {
|
||||
capabilityMap.value = { ...capabilityMap.value, [scene]: [] };
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (!routeContext.isValid.value) return;
|
||||
await loadAppInfo();
|
||||
await loadConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouteQueryMissingState
|
||||
v-if="!routeContext.isValid"
|
||||
:description="$t('payment.merchant.aggregate.aggregate.missingAppContext')"
|
||||
:back-text="$t('payment.merchant.workbench.workbench.backToList')"
|
||||
@back="routeContext.goFallback"
|
||||
/>
|
||||
<div v-else class="m-4">
|
||||
<a-card variant="borderless" class="rounded-xl shadow-sm">
|
||||
<template #title>
|
||||
<div class="flex items-center gap-2">
|
||||
<a-button type="text" @click="handleBack">
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ant-design:arrow-left-outlined" />
|
||||
</template>
|
||||
</a-button>
|
||||
<span class="text-lg font-bold">{{ $t('payment.merchant.aggregate.aggregate.title') }}</span>
|
||||
<span v-if="appInfo.appName" class="text-sm text-muted-foreground">
|
||||
({{ appInfo.appName }})
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #extra>
|
||||
<div class="flex gap-2">
|
||||
<a-button v-if="!editing" type="primary" @click="startEdit">
|
||||
{{ $t('common.edit') }}
|
||||
</a-button>
|
||||
<template v-else>
|
||||
<a-button type="primary" @click="save">{{ $t('common.save') }}</a-button>
|
||||
<a-button @click="cancel">{{ $t('common.cancel') }}</a-button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<!-- 模式切换 -->
|
||||
<div class="mb-4 flex items-center gap-3">
|
||||
<span class="text-sm font-medium">{{ $t('payment.merchant.aggregate.aggregate.editModeLabel') }}</span>
|
||||
<a-radio-group v-model:value="editLevel" button-style="solid" :disabled="!editing">
|
||||
<a-radio-button :value="AGGREGATE_LEVEL.AUTO">
|
||||
{{ $t('payment.merchant.aggregate.aggregate.modeAuto') }}
|
||||
</a-radio-button>
|
||||
<a-radio-button :value="AGGREGATE_LEVEL.METHOD">
|
||||
{{ $t('payment.merchant.aggregate.aggregate.modeMethod') }}
|
||||
</a-radio-button>
|
||||
<a-radio-button :value="AGGREGATE_LEVEL.DIRECT">
|
||||
{{ $t('payment.merchant.aggregate.aggregate.modeDirect') }}
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-tag v-if="isLevelActive && !editing" color="green">
|
||||
{{ $t('payment.merchant.aggregate.aggregate.levelActive') }}
|
||||
</a-tag>
|
||||
</div>
|
||||
|
||||
<!-- 模式提示 -->
|
||||
<a-alert :message="modeHint" type="info" show-icon class="mb-4" />
|
||||
|
||||
<!-- 自动拉起 -->
|
||||
<div class="mb-4">
|
||||
<a-checkbox v-model:checked="autoLaunch" :disabled="!editing">
|
||||
{{ $t('payment.merchant.aggregate.aggregate.autoLaunch') }}
|
||||
</a-checkbox>
|
||||
<span class="ml-2 text-xs text-muted-foreground">
|
||||
{{ $t('payment.merchant.aggregate.aggregate.autoLaunchHint') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 场景配置 -->
|
||||
<div class="scene-list">
|
||||
<div v-for="sc in AGGREGATE_SCENES" :key="sc.scene" class="scene-row">
|
||||
<div class="scene-label">
|
||||
<span class="font-medium">{{ sceneLabel(sc.scene) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- AUTO 模式: 只读提示 -->
|
||||
<div v-if="editLevel === AGGREGATE_LEVEL.AUTO" class="scene-content">
|
||||
<a-tag color="blue">
|
||||
{{ $t('payment.merchant.aggregate.aggregate.autoDerived') }}: {{ sc.defaultMethod }}
|
||||
</a-tag>
|
||||
</div>
|
||||
|
||||
<!-- METHOD 模式: 选支付方式 -->
|
||||
<div v-else-if="editLevel === AGGREGATE_LEVEL.METHOD" class="scene-content">
|
||||
<a-select
|
||||
:value="getSceneData(sc.scene).method"
|
||||
:options="methodOptions(sc.provider)"
|
||||
:placeholder="$t('payment.merchant.aggregate.aggregate.methodPlaceholder')"
|
||||
:disabled="!editing"
|
||||
allow-clear
|
||||
style="width: 240px"
|
||||
@change="(val: any) => (getSceneData(sc.scene).method = val)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- DIRECT 模式: 选通道商户 + 能力 -->
|
||||
<div v-else class="scene-content direct-content">
|
||||
<a-select
|
||||
:value="getSceneData(sc.scene).channelMchNo"
|
||||
:options="channelMchOptions(sc.scene)"
|
||||
:placeholder="$t('payment.merchant.aggregate.aggregate.channelMerchantPlaceholder')"
|
||||
:disabled="!editing"
|
||||
allow-clear
|
||||
style="width: 240px"
|
||||
@change="(val: any) => onChannelMchChange(sc.scene, val)"
|
||||
/>
|
||||
<a-select
|
||||
:value="getSceneData(sc.scene).capability"
|
||||
:options="capabilityOptions(sc.scene)"
|
||||
:placeholder="$t('payment.merchant.aggregate.aggregate.capabilityPlaceholder')"
|
||||
:disabled="!editing || !getSceneData(sc.scene).channelMchNo"
|
||||
allow-clear
|
||||
style="width: 240px"
|
||||
@change="(val: any) => (getSceneData(sc.scene).capability = val)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.scene-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.scene-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.scene-label {
|
||||
flex-shrink: 0;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.scene-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.direct-content {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,34 @@
|
||||
// 聚合扫码配置深度(与后端 AggregateConfigLevelEnum 对齐)
|
||||
export const AGGREGATE_LEVEL = {
|
||||
AUTO: 'auto',
|
||||
METHOD: 'method',
|
||||
DIRECT: 'direct',
|
||||
} as const;
|
||||
|
||||
export type AggregateLevel = (typeof AGGREGATE_LEVEL)[keyof typeof AGGREGATE_LEVEL];
|
||||
|
||||
/**
|
||||
* 聚合扫码场景定义
|
||||
* scene: 后端 CashierSceneEnum 编码
|
||||
* provider: 对应的支付渠道(PayProviderEnum),用于过滤支付方式候选
|
||||
* defaultMethod: L1 自动推导的默认支付方式(与后端 SceneMethodDefaultResolver 对齐)
|
||||
*/
|
||||
export interface AggregateSceneConfig {
|
||||
scene: string;
|
||||
provider: string;
|
||||
defaultMethod: string;
|
||||
}
|
||||
|
||||
export const AGGREGATE_SCENES: AggregateSceneConfig[] = [
|
||||
{ scene: 'wechat_pay', provider: 'wechat', defaultMethod: 'wechat_jsapi' },
|
||||
{ scene: 'alipay', provider: 'alipay', defaultMethod: 'alipay_jsapi' },
|
||||
{ scene: 'union_pay', provider: 'union_pay', defaultMethod: 'union_jsapi' },
|
||||
{ scene: 'douyin', provider: 'douyin', defaultMethod: 'douyin_jsapi' },
|
||||
];
|
||||
|
||||
// 模式显示名
|
||||
export const LEVEL_DISPLAY: Record<AggregateLevel, string> = {
|
||||
auto: '自动模式',
|
||||
method: '方式模式',
|
||||
direct: '精确模式',
|
||||
};
|
||||
@@ -40,6 +40,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
/** 进入聚合扫码配置 */
|
||||
function goAggregateScan() {
|
||||
router.push({
|
||||
path: '/payment/merchant/aggregate',
|
||||
query: { mchNo: props.mchNo, appId: props.record.appId },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更多操作菜单
|
||||
*/
|
||||
@@ -117,6 +125,21 @@
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="hasPermission(PermCodes.Merchant.GatewayAggregate.VIEW)"
|
||||
:title="$t('payment.merchant.app.app.aggregateScan')"
|
||||
>
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
class="!h-6 !w-6 !min-w-6 !p-0 !text-muted-foreground hover:!text-primary"
|
||||
@click="goAggregateScan"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ant-design:qrcode-outlined" class="text-sm" />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="hasPermission(PermCodes.Merchant.App.MANAGE)" :title="$t('payment.merchant.app.app.edit')">
|
||||
<a-button
|
||||
type="text"
|
||||
|
||||
Reference in New Issue
Block a user