mirror of
https://gitee.com/bootx/dax-pay-ui
synced 2026-08-11 23:25:33 +08:00
refactor(admin): 码牌列表优化并将表单手写校验统一为 form rules
- 码牌列表合并金额类型/固定金额列, 编码链接查看码牌 - 码牌编辑/批量创建固定金额绑定 formState 并用 rules 校验 - 退款/渠道授权/令牌/协议复制/交易调试私钥改为 form 校验
This commit is contained in:
@@ -25,8 +25,14 @@
|
||||
const channelMchNo = ref('');
|
||||
// 服务商通道商户配置(用于展示当前令牌 / 子商户号)
|
||||
const isvConfig = ref<AlipayIsvChannelMerchantConfig>({});
|
||||
// 新令牌输入
|
||||
const newAuthToken = ref('');
|
||||
// 手动设置表单
|
||||
const formRef = ref();
|
||||
const formState = ref({ appAuthToken: '' });
|
||||
const formRules = {
|
||||
appAuthToken: [
|
||||
{ required: true, whitespace: true, message: $t('payment.merchant.channelMerchant.appAuthTokenRequired') },
|
||||
],
|
||||
};
|
||||
// 当前 Tab: manual | agent
|
||||
const activeTab = ref<'agent' | 'manual'>('manual');
|
||||
// 代运营授权链接
|
||||
@@ -67,7 +73,7 @@
|
||||
/** 打开抽屉 */
|
||||
function open(mchNo: string) {
|
||||
channelMchNo.value = mchNo;
|
||||
newAuthToken.value = '';
|
||||
formState.value = { appAuthToken: '' };
|
||||
authUrl.value = '';
|
||||
callbackUrl.value = '';
|
||||
activeTab.value = 'manual';
|
||||
@@ -82,9 +88,11 @@
|
||||
}
|
||||
|
||||
/** 保存新令牌(二次确认后提交) */
|
||||
function handleSave() {
|
||||
if (!newAuthToken.value.trim()) {
|
||||
message.warning($t('payment.merchant.channelMerchant.appAuthTokenRequired'));
|
||||
async function handleSave() {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
} catch {
|
||||
// 校验失败: 表单已显示错误提示
|
||||
return;
|
||||
}
|
||||
confirm({
|
||||
@@ -96,7 +104,7 @@
|
||||
saving.value = true;
|
||||
return AlipayIsvChannelMerchantApi.updateAppAuthToken({
|
||||
channelMchNo: channelMchNo.value,
|
||||
appAuthToken: newAuthToken.value.trim(),
|
||||
appAuthToken: formState.value.appAuthToken.trim(),
|
||||
})
|
||||
.then(() => {
|
||||
message.success($t('payment.merchant.channelMerchant.appAuthTokenUpdateSuccess'));
|
||||
@@ -177,7 +185,7 @@
|
||||
<a-tabs v-model:active-key="activeTab" class="auth-token-tabs">
|
||||
<!-- 国际化:手动设置 -->
|
||||
<a-tab-pane key="manual" :tab="$t('payment.merchant.channelMerchant.tabManualToken')">
|
||||
<a-form layout="vertical" class="pt-2">
|
||||
<a-form ref="formRef" layout="vertical" class="pt-2" :model="formState" :rules="formRules">
|
||||
<!-- 国际化:当前令牌 -->
|
||||
<a-form-item :label="$t('payment.merchant.channelMerchant.appAuthTokenCurrent')">
|
||||
<div class="current-token-value">
|
||||
@@ -185,9 +193,9 @@
|
||||
</div>
|
||||
</a-form-item>
|
||||
<!-- 国际化:新令牌 -->
|
||||
<a-form-item :label="$t('payment.merchant.channelMerchant.appAuthTokenNew')">
|
||||
<a-form-item :label="$t('payment.merchant.channelMerchant.appAuthTokenNew')" name="appAuthToken">
|
||||
<a-input
|
||||
v-model:value="newAuthToken"
|
||||
v-model:value="formState.appAuthToken"
|
||||
:placeholder="$t('payment.merchant.channelMerchant.appAuthTokenNewPlaceholder')"
|
||||
allow-clear
|
||||
/>
|
||||
|
||||
@@ -36,11 +36,17 @@
|
||||
const authResult = ref<AuthResult>({});
|
||||
|
||||
/** 微信支付表单 */
|
||||
const formRef = ref();
|
||||
const form = reactive({
|
||||
mchNo: '',
|
||||
channelMchNo: '',
|
||||
capability: '',
|
||||
mchNo: undefined as string | undefined,
|
||||
channelMchNo: undefined as string | undefined,
|
||||
capability: undefined as string | undefined,
|
||||
});
|
||||
const formRules = {
|
||||
mchNo: [{ required: true, message: $t('payment.develop.auth.form.rule.mchNo') }],
|
||||
channelMchNo: [{ required: true, message: $t('payment.develop.auth.form.rule.channelMchNo') }],
|
||||
capability: [{ required: true, message: $t('payment.develop.auth.form.rule.capability') }],
|
||||
};
|
||||
|
||||
/** 微信小程序表单(端类型: merchant 商户端 / admin 运营端) */
|
||||
const wechatMiniForm = reactive({
|
||||
@@ -136,8 +142,8 @@
|
||||
|
||||
/** 商户变更: 刷新通道商户候选 */
|
||||
function merchantChange() {
|
||||
form.channelMchNo = '';
|
||||
form.capability = '';
|
||||
form.channelMchNo = undefined;
|
||||
form.capability = undefined;
|
||||
channelMchNoOptions.value = [];
|
||||
capabilityOptions.value = [];
|
||||
if (!form.mchNo) return;
|
||||
@@ -147,7 +153,7 @@
|
||||
|
||||
/** 通道商户变更: 重置能力并重载能力候选 */
|
||||
function channelMchNoChange() {
|
||||
form.capability = '';
|
||||
form.capability = undefined;
|
||||
capabilityOptions.value = [];
|
||||
if (form.channelMchNo) {
|
||||
loadCapabilityCandidates(form.channelMchNo);
|
||||
@@ -181,16 +187,10 @@
|
||||
}
|
||||
// 微信支付: 表单校验
|
||||
if (authType.value === 'wechatChannel') {
|
||||
if (!form.mchNo) {
|
||||
message.warning($t('payment.develop.auth.form.rule.mchNo'));
|
||||
return;
|
||||
}
|
||||
if (!form.channelMchNo) {
|
||||
message.warning($t('payment.develop.auth.form.rule.channelMchNo'));
|
||||
return;
|
||||
}
|
||||
if (!form.capability) {
|
||||
message.warning($t('payment.develop.auth.form.rule.capability'));
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
} catch {
|
||||
// 校验失败: 表单已显示错误提示
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -303,8 +303,14 @@
|
||||
|
||||
<!-- 微信支付:商户参数表单 -->
|
||||
<div v-if="authType === 'wechatChannel'" class="form-panel">
|
||||
<a-form layout="vertical" class="channel-auth-form">
|
||||
<a-form-item :label="$t('payment.develop.auth.form.mchNo')">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
layout="vertical"
|
||||
class="channel-auth-form"
|
||||
:model="form"
|
||||
:rules="formRules"
|
||||
>
|
||||
<a-form-item :label="$t('payment.develop.auth.form.mchNo')" name="mchNo">
|
||||
<a-select
|
||||
v-model:value="form.mchNo"
|
||||
:options="mchNoOptions"
|
||||
@@ -315,7 +321,7 @@
|
||||
@change="merchantChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('payment.develop.auth.form.channelMchNo')">
|
||||
<a-form-item :label="$t('payment.develop.auth.form.channelMchNo')" name="channelMchNo">
|
||||
<a-select
|
||||
v-model:value="form.channelMchNo"
|
||||
:options="channelMchNoOptions"
|
||||
@@ -326,7 +332,7 @@
|
||||
@change="channelMchNoChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('payment.develop.auth.form.capability')" class="form-item-last">
|
||||
<a-form-item :label="$t('payment.develop.auth.form.capability')" name="capability" class="form-item-last">
|
||||
<a-select
|
||||
v-model:value="form.capability"
|
||||
:options="capabilityOptions"
|
||||
|
||||
@@ -30,6 +30,12 @@
|
||||
// 支付模式: route=路由模式(商户+方式+应用动态匹配) direct=直传模式(通道商户+能力直接决定)
|
||||
const routeMode = ref<'direct' | 'route'>('route');
|
||||
|
||||
// 私钥独立存储(标签+弹窗交互, 校验通过 form 自定义规则挂接)
|
||||
const privateKey = ref('');
|
||||
const privateKeyVisible = ref(false);
|
||||
const privateKeyInput = ref('');
|
||||
const loading = ref(false);
|
||||
|
||||
// ===== 表单校验规则(按模式动态生成) =====
|
||||
const formRules = computed<Record<string, any[]>>(() => {
|
||||
// 通用必填字段
|
||||
@@ -38,6 +44,16 @@
|
||||
bizOrderNo: [{ required: true, message: $t('payment.develop.trade.rule.bizOrderNo') }],
|
||||
amount: [{ required: true, message: $t('payment.develop.trade.rule.amount') }],
|
||||
title: [{ required: true, message: $t('payment.develop.trade.rule.title') }],
|
||||
// 私钥存独立 ref, 用自定义校验挂到 form 上
|
||||
privateKey: [
|
||||
{
|
||||
validator: async () => {
|
||||
if (!privateKey.value) {
|
||||
return Promise.reject(new Error($t('payment.develop.trade.msg.inputPrivateKey')));
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
// 路由模式: 支付方式必填
|
||||
if (routeMode.value === 'route') {
|
||||
@@ -62,11 +78,6 @@
|
||||
capability: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
const privateKey = ref('');
|
||||
const privateKeyVisible = ref(false);
|
||||
const privateKeyInput = ref('');
|
||||
const loading = ref(false);
|
||||
const signPreviewLoading = ref(false);
|
||||
|
||||
// ===== 下拉选项 =====
|
||||
@@ -203,6 +214,8 @@
|
||||
localStorage.removeItem(PRIVATE_KEY_STORAGE_KEY);
|
||||
}
|
||||
privateKeyVisible.value = false;
|
||||
// 清除私钥字段校验态
|
||||
formRef.value?.clearValidate?.(['privateKey']);
|
||||
message.success($t('payment.develop.trade.privateKey.savedTip'));
|
||||
}
|
||||
|
||||
@@ -214,6 +227,7 @@
|
||||
onOk() {
|
||||
privateKey.value = '';
|
||||
localStorage.removeItem(PRIVATE_KEY_STORAGE_KEY);
|
||||
formRef.value?.clearValidate?.(['privateKey']);
|
||||
message.success($t('payment.develop.trade.privateKey.clearedTip'));
|
||||
},
|
||||
});
|
||||
@@ -247,8 +261,10 @@
|
||||
|
||||
/** 生成签名预览(内联展示, 不弹结果) */
|
||||
async function handleSignPreview() {
|
||||
if (!privateKey.value) {
|
||||
message.warning($t('payment.develop.trade.msg.inputPrivateKey'));
|
||||
try {
|
||||
await formRef.value?.validateFields(['privateKey']);
|
||||
} catch {
|
||||
// 校验失败: 表单已显示错误提示
|
||||
return;
|
||||
}
|
||||
signPreviewLoading.value = true;
|
||||
@@ -279,17 +295,13 @@
|
||||
// ===== 提交 =====
|
||||
/** 发起真实支付调试 */
|
||||
async function handlePay() {
|
||||
// 表单字段校验
|
||||
// 表单字段校验(含私钥自定义规则)
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
} catch {
|
||||
// 校验未通过,字段错误已由表单自动展示
|
||||
return;
|
||||
}
|
||||
if (!privateKey.value) {
|
||||
message.warning($t('payment.develop.trade.msg.inputPrivateKey'));
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data } = await DevelopTradeApi.pay({
|
||||
@@ -380,8 +392,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 私钥状态行 -->
|
||||
<a-form-item :label="$t('payment.develop.trade.field.privateKey')" required>
|
||||
<!-- 私钥状态行(校验挂 form.privateKey 自定义规则, 实际值在 privateKey ref) -->
|
||||
<a-form-item :label="$t('payment.develop.trade.field.privateKey')" name="privateKey">
|
||||
<div class="flex items-center gap-2">
|
||||
<a-tag v-if="privateKey" color="success">
|
||||
<IconifyIcon icon="ant-design:check-circle-outlined" class="mr-0.5" />
|
||||
|
||||
@@ -290,7 +290,18 @@
|
||||
@checkbox-all="handleCheckboxChange"
|
||||
>
|
||||
<vxe-column type="checkbox" width="50" />
|
||||
<vxe-column field="code" :title="$t('payment.device.qrcode.field.code')" :min-width="200" />
|
||||
<!-- 编码可点, 打开查看码牌弹窗(替代操作列单独按钮) -->
|
||||
<vxe-column field="code" :title="$t('payment.device.qrcode.field.code')" :min-width="200">
|
||||
<template #default="{ row }">
|
||||
<a
|
||||
v-if="hasPermission(PermCodes.Device.QrCode.VIEW) && row.code"
|
||||
class="vben-link"
|
||||
@click="handleViewCode(row)"
|
||||
>{{ row.code }}</a
|
||||
>
|
||||
<span v-else>{{ row.code || '-' }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="name" :title="$t('payment.device.qrcode.field.name')" :min-width="140" />
|
||||
<vxe-column field="batchNo" :title="$t('payment.device.qrcode.field.batchNo')" :min-width="140">
|
||||
<template #default="{ row }">
|
||||
@@ -307,27 +318,18 @@
|
||||
<a-tag v-else color="default">{{ $t('payment.device.qrcode.unbound') }}</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<!-- 金额类型 + 固定金额合并: 固定显示金额, 自定义显示类型文案 -->
|
||||
<vxe-column
|
||||
field="amountType"
|
||||
:title="$t('payment.device.qrcode.field.amountType')"
|
||||
:min-width="110"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ $t(`payment.device.qrcode.amountType.${row.amountType}`) }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column
|
||||
field="fixedAmount"
|
||||
:title="$t('payment.device.qrcode.field.fixedAmount')"
|
||||
:title="$t('payment.device.qrcode.amountLabel')"
|
||||
:min-width="120"
|
||||
align="right"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.amountType === 'fixed' && row.fixedAmount">
|
||||
¥{{ (row.fixedAmount / 100).toFixed(2) }}
|
||||
</span>
|
||||
<span v-else style="color: var(--text-color-placeholder)">-</span>
|
||||
<span v-else>{{ $t(`payment.device.qrcode.amountType.${row.amountType || 'random'}`) }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="status" :title="$t('payment.device.qrcode.field.status')" :min-width="100" align="center">
|
||||
@@ -346,19 +348,12 @@
|
||||
:min-width="180"
|
||||
formatter="formatDateTime"
|
||||
/>
|
||||
<vxe-column fixed="right" width="220" :show-overflow="false" :title="$t('common.operation')">
|
||||
<vxe-column fixed="right" width="180" :show-overflow="false" :title="$t('common.operation')">
|
||||
<template #default="{ row }">
|
||||
<a-space :size="2">
|
||||
<template #separator>
|
||||
<a-divider type="vertical" />
|
||||
</template>
|
||||
<a-button
|
||||
v-if="hasPermission(PermCodes.Device.QrCode.VIEW)"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleViewCode(row)"
|
||||
>{{ $t('payment.device.qrcode.viewCode') }}</a-button
|
||||
>
|
||||
<a-button
|
||||
v-if="hasPermission(PermCodes.Device.QrCode.MANAGE)"
|
||||
type="link"
|
||||
|
||||
@@ -18,9 +18,8 @@
|
||||
const visible = ref(false);
|
||||
const confirmLoading = ref(false);
|
||||
const formRef = ref();
|
||||
// 固定金额展示值(元)
|
||||
const fixedAmountYuan = ref<number | undefined>(undefined);
|
||||
|
||||
// formState.fixedAmount 以「元」存储, 提交时再×100转分
|
||||
const formState = ref<DeviceQrCodeBatchParam>({
|
||||
batchNo: '',
|
||||
count: 10,
|
||||
@@ -30,12 +29,26 @@
|
||||
|
||||
const isFixedAmount = computed(() => formState.value.amountType === 'fixed');
|
||||
|
||||
const formRules = computed(() => ({
|
||||
batchNo: [{ required: true, message: $t('payment.device.qrcode.validateBatchNo') }],
|
||||
count: [{ required: true, message: $t('payment.device.qrcode.validateCount') }],
|
||||
amountType: [{ required: true, message: $t('payment.device.qrcode.validateAmountType') }],
|
||||
status: [{ required: true, message: $t('payment.device.qrcode.validateStatus') }],
|
||||
}));
|
||||
const formRules = computed(() => {
|
||||
const rules: Record<string, any[]> = {
|
||||
batchNo: [{ required: true, message: $t('payment.device.qrcode.validateBatchNo') }],
|
||||
count: [{ required: true, message: $t('payment.device.qrcode.validateCount') }],
|
||||
amountType: [{ required: true, message: $t('payment.device.qrcode.validateAmountType') }],
|
||||
status: [{ required: true, message: $t('payment.device.qrcode.validateStatus') }],
|
||||
};
|
||||
// 固定金额类型时金额必填
|
||||
if (isFixedAmount.value) {
|
||||
rules.fixedAmount = [
|
||||
{ required: true, message: $t('payment.device.qrcode.validateFixedAmount') },
|
||||
{
|
||||
type: 'number',
|
||||
min: 0.01,
|
||||
message: $t('payment.device.qrcode.validateFixedAmount'),
|
||||
},
|
||||
];
|
||||
}
|
||||
return rules;
|
||||
});
|
||||
|
||||
/**
|
||||
* 一键生成批次号: Q + YYMMDDHHmmss(对齐商业版)
|
||||
@@ -56,8 +69,8 @@
|
||||
count: 10,
|
||||
amountType: 'random',
|
||||
status: 'enabled',
|
||||
fixedAmount: undefined,
|
||||
};
|
||||
fixedAmountYuan.value = undefined;
|
||||
visible.value = true;
|
||||
await nextTick();
|
||||
formRef.value?.clearValidate?.();
|
||||
@@ -95,18 +108,14 @@
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (isFixedAmount.value && (fixedAmountYuan.value === undefined || fixedAmountYuan.value <= 0)) {
|
||||
message.error($t('payment.device.qrcode.validateFixedAmount'));
|
||||
return;
|
||||
}
|
||||
confirmLoading.value = true;
|
||||
try {
|
||||
const payload: DeviceQrCodeBatchParam = {
|
||||
...formState.value,
|
||||
batchNo: formState.value.batchNo?.trim(),
|
||||
fixedAmount:
|
||||
isFixedAmount.value && fixedAmountYuan.value
|
||||
? Math.round(fixedAmountYuan.value * 100)
|
||||
isFixedAmount.value && formState.value.fixedAmount
|
||||
? Math.round(formState.value.fixedAmount * 100)
|
||||
: undefined,
|
||||
};
|
||||
// 再校验一次批次号
|
||||
@@ -182,9 +191,10 @@
|
||||
<a-radio-button value="fixed">{{ $t('payment.device.qrcode.amountType.fixed') }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="isFixedAmount" :label="$t('payment.device.qrcode.field.fixedAmount')">
|
||||
<!-- 固定金额(仅固定金额类型显示, 表单内以元存储) -->
|
||||
<a-form-item v-if="isFixedAmount" :label="$t('payment.device.qrcode.field.fixedAmount')" name="fixedAmount">
|
||||
<a-input-number
|
||||
v-model:value="fixedAmountYuan"
|
||||
v-model:value="formState.fixedAmount"
|
||||
:min="0.01"
|
||||
:step="0.01"
|
||||
:precision="2"
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
amountType: 'random',
|
||||
});
|
||||
|
||||
// 金额类型为固定时, 固定金额必填
|
||||
const isFixedAmount = computed(() => formState.value.amountType === 'fixed');
|
||||
|
||||
// formState.fixedAmount 以「元」存储, 提交时再×100转分
|
||||
const formRules = computed(() => {
|
||||
const rules: Record<string, any[]> = {
|
||||
name: [{ required: true, message: $t('payment.device.qrcode.validateName') }],
|
||||
@@ -44,15 +48,20 @@
|
||||
if (!isEdit.value) {
|
||||
rules.mchNo = [{ required: true, message: $t('payment.device.qrcode.validateMchNo') }];
|
||||
}
|
||||
// 固定金额类型时金额必填
|
||||
if (isFixedAmount.value) {
|
||||
rules.fixedAmount = [
|
||||
{ required: true, message: $t('payment.device.qrcode.validateFixedAmount') },
|
||||
{
|
||||
type: 'number',
|
||||
min: 0.01,
|
||||
message: $t('payment.device.qrcode.validateFixedAmount'),
|
||||
},
|
||||
];
|
||||
}
|
||||
return rules;
|
||||
});
|
||||
|
||||
// 金额类型为固定时, 固定金额必填
|
||||
const isFixedAmount = computed(() => formState.value.amountType === 'fixed');
|
||||
|
||||
// 固定金额展示值(元), 提交时转回分
|
||||
const fixedAmountYuan = ref<number | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* 加载商户下拉
|
||||
*/
|
||||
@@ -81,8 +90,8 @@
|
||||
name: '',
|
||||
mchNo: '',
|
||||
amountType: 'random',
|
||||
fixedAmount: undefined,
|
||||
};
|
||||
fixedAmountYuan.value = undefined;
|
||||
appOptions.value = [];
|
||||
formRef.value?.resetFields();
|
||||
}
|
||||
@@ -112,10 +121,10 @@
|
||||
mchNo: row.mchNo,
|
||||
appId: row.appId,
|
||||
amountType: row.amountType,
|
||||
fixedAmount: row.fixedAmount,
|
||||
// 分转元展示
|
||||
fixedAmount: row.fixedAmount ? row.fixedAmount / 100 : undefined,
|
||||
remark: row.remark,
|
||||
};
|
||||
fixedAmountYuan.value = row.fixedAmount ? row.fixedAmount / 100 : undefined;
|
||||
} finally {
|
||||
confirmLoading.value = false;
|
||||
}
|
||||
@@ -131,17 +140,15 @@
|
||||
// 校验失败: 表单已显示错误提示
|
||||
return;
|
||||
}
|
||||
// 固定金额校验
|
||||
if (isFixedAmount.value && (fixedAmountYuan.value === undefined || fixedAmountYuan.value <= 0)) {
|
||||
message.error($t('payment.device.qrcode.validateFixedAmount'));
|
||||
return;
|
||||
}
|
||||
confirmLoading.value = true;
|
||||
try {
|
||||
// 金额元转分, 在构造时一次性赋值避免对对象立即修改
|
||||
const payload: DeviceQrCodeParam = {
|
||||
...formState.value,
|
||||
fixedAmount: isFixedAmount.value && fixedAmountYuan.value ? Math.round(fixedAmountYuan.value * 100) : undefined,
|
||||
fixedAmount:
|
||||
isFixedAmount.value && formState.value.fixedAmount
|
||||
? Math.round(formState.value.fixedAmount * 100)
|
||||
: undefined,
|
||||
};
|
||||
await (formEditType.value === FormEditType.Edit ? DeviceQrCodeApi.update(payload) : DeviceQrCodeApi.add(payload));
|
||||
message.success($t('common.operationSuccess'));
|
||||
@@ -218,10 +225,10 @@
|
||||
<a-radio-button value="fixed">{{ $t('payment.device.qrcode.amountType.fixed') }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<!-- 固定金额(仅固定金额类型显示) -->
|
||||
<!-- 固定金额(仅固定金额类型显示, 表单内以元存储) -->
|
||||
<a-form-item v-if="isFixedAmount" :label="$t('payment.device.qrcode.field.fixedAmount')" name="fixedAmount">
|
||||
<a-input-number
|
||||
v-model:value="fixedAmountYuan"
|
||||
v-model:value="formState.fixedAmount"
|
||||
:min="0.01"
|
||||
:step="0.01"
|
||||
:precision="2"
|
||||
|
||||
@@ -45,11 +45,30 @@
|
||||
const refundVisible = ref(false);
|
||||
const refundLoading = ref(false);
|
||||
const refundFetching = ref(false);
|
||||
const refundFormRef = ref();
|
||||
// refundForm.amount 以「元」存储, 提交时再×100转分
|
||||
const refundForm = ref<{ amount: number; orderNo?: string; reason?: string }>({ amount: 0, reason: '' });
|
||||
const refundForm = ref<{ amount?: number; orderNo?: string; reason?: string }>({ amount: undefined, reason: '' });
|
||||
const refundRow = ref<NormalOrderResult | null>(null);
|
||||
// 可退金额(元), 作为退款金额输入框上限
|
||||
const refundableYuan = computed(() => (refundRow.value?.refundableBalance ?? 0) / 100);
|
||||
// 退款表单校验(走 form rules, 不手写 message)
|
||||
const refundRules = computed(() => ({
|
||||
amount: [
|
||||
{ required: true, message: $t('payment.order.action.refundAmountPlaceholder') },
|
||||
{
|
||||
type: 'number',
|
||||
min: 0.01,
|
||||
message: $t('payment.order.action.refundAmountPlaceholder'),
|
||||
},
|
||||
{
|
||||
validator: async (_rule: unknown, value: number) => {
|
||||
if (value != null && value > refundableYuan.value) {
|
||||
return Promise.reject(new Error($t('payment.order.action.refundAmountExceed')));
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
// 业务状态下拉
|
||||
const statusOptions = computed(() =>
|
||||
@@ -291,23 +310,27 @@
|
||||
/**
|
||||
* 提交退款
|
||||
*/
|
||||
function submitRefund() {
|
||||
if (!refundRow.value) return;
|
||||
// 校验退款金额(元)不能超过可退金额(元)
|
||||
if (refundForm.value.amount > refundableYuan.value) {
|
||||
message.error($t('payment.order.action.refundAmountExceed'));
|
||||
async function submitRefund() {
|
||||
if (!refundRow.value) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await refundFormRef.value?.validate();
|
||||
} catch {
|
||||
// 校验失败: 表单已显示错误提示; 拒绝以阻止 modal 关闭
|
||||
return Promise.reject();
|
||||
}
|
||||
// 元转分提交
|
||||
const amountYuan = refundForm.value.amount ?? 0;
|
||||
const param: PayRefundParam = {
|
||||
orderNo: refundForm.value.orderNo,
|
||||
bizOrderNo: refundRow.value.bizOrderNo,
|
||||
amount: Math.round(refundForm.value.amount * 100),
|
||||
amount: Math.round(amountYuan * 100),
|
||||
reason: refundForm.value.reason,
|
||||
};
|
||||
confirm({
|
||||
title: $t('payment.order.action.refundConfirmTitle'),
|
||||
content: $t('payment.order.action.refundConfirmContent', { amount: refundForm.value.amount.toFixed(2) }),
|
||||
content: $t('payment.order.action.refundConfirmContent', { amount: amountYuan.toFixed(2) }),
|
||||
onOk() {
|
||||
refundLoading.value = true;
|
||||
return RefundOrderApi.refund(param)
|
||||
@@ -564,8 +587,8 @@
|
||||
@cancel="handleRefundClose"
|
||||
>
|
||||
<a-spin :spinning="refundFetching">
|
||||
<a-form :label-col="{ span: 6 }">
|
||||
<a-form-item :label="$t('payment.order.action.refundAmountLabel')">
|
||||
<a-form ref="refundFormRef" :model="refundForm" :rules="refundRules" :label-col="{ span: 6 }">
|
||||
<a-form-item :label="$t('payment.order.action.refundAmountLabel')" name="amount">
|
||||
<a-input-number
|
||||
v-model:value="refundForm.amount"
|
||||
:min="0.01"
|
||||
|
||||
@@ -74,9 +74,13 @@
|
||||
|
||||
// 复制到其他端弹窗
|
||||
const copyVisible = ref(false);
|
||||
const copyFormRef = ref();
|
||||
const copySourceId = ref<string>('');
|
||||
const copySourceClientType = ref<string>('');
|
||||
const copyTargetType = ref<string>('');
|
||||
const copyForm = ref<{ clientType?: string }>({ clientType: undefined });
|
||||
const copyRules = {
|
||||
clientType: [{ required: true, message: $t('system.protocol.selectClientType') }],
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
xTable.value?.connectToolbar(xToolbar.value as VxeToolbarInstance);
|
||||
@@ -213,17 +217,19 @@
|
||||
function handleCopy(row: any) {
|
||||
copySourceId.value = row.id;
|
||||
copySourceClientType.value = row.clientType;
|
||||
copyTargetType.value = '';
|
||||
copyForm.value = { clientType: undefined };
|
||||
copyVisible.value = true;
|
||||
}
|
||||
|
||||
/** 确认复制 */
|
||||
function confirmCopy() {
|
||||
if (!copyTargetType.value) {
|
||||
message.warning($t('system.protocol.selectClientType'));
|
||||
return;
|
||||
async function confirmCopy() {
|
||||
try {
|
||||
await copyFormRef.value?.validate();
|
||||
} catch {
|
||||
// 校验失败: 表单已显示错误提示; 拒绝以阻止 modal 关闭
|
||||
return Promise.reject();
|
||||
}
|
||||
UserProtocolApi.copyToClient(copySourceId.value, copyTargetType.value).then(() => {
|
||||
return UserProtocolApi.copyToClient(copySourceId.value, copyForm.value.clientType!).then(() => {
|
||||
message.success($t('common.operationSuccess'));
|
||||
copyVisible.value = false;
|
||||
queryPage();
|
||||
@@ -352,10 +358,16 @@
|
||||
:cancel-text="$t('common.cancelText')"
|
||||
@ok="confirmCopy"
|
||||
>
|
||||
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item :label="$t('system.protocol.clientType')">
|
||||
<a-form
|
||||
ref="copyFormRef"
|
||||
:model="copyForm"
|
||||
:rules="copyRules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<a-form-item :label="$t('system.protocol.clientType')" name="clientType">
|
||||
<a-select
|
||||
v-model:value="copyTargetType"
|
||||
v-model:value="copyForm.clientType"
|
||||
:options="clientTypeOptions.filter((i) => i.value !== copySourceClientType)"
|
||||
:placeholder="$t('system.protocol.selectClientType')"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user