mirror of
https://gitee.com/bootx/dax-pay-ui
synced 2026-08-12 07:25:39 +08:00
feat(admin): 码牌管理页(批量生成/绑定商户/商户列优化)
批量生成批次号布局修复;绑定商户去掉应用选择;列表主显商户名悬停看商户号。
This commit is contained in:
168
apps/daxpay-admin/src/api/payment/device/qrcode.api.ts
Normal file
168
apps/daxpay-admin/src/api/payment/device/qrcode.api.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import type { BaseEntity, PageResult, Result } from '#/types/web';
|
||||
|
||||
import { defHttp } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 支付码牌 API
|
||||
*/
|
||||
export const DeviceQrCodeApi = {
|
||||
/**
|
||||
* 分页查询码牌
|
||||
*/
|
||||
page(
|
||||
params: DeviceQrCodeQuery & { current?: number; size?: number },
|
||||
): Promise<Result<PageResult<DeviceQrCodeResult>>> {
|
||||
return defHttp.get({ url: '/admin/device/qrcode/page', params });
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据 id 查询码牌
|
||||
*/
|
||||
get(id: string): Promise<Result<DeviceQrCodeResult>> {
|
||||
return defHttp.get({ url: '/admin/device/qrcode/get', params: { id } });
|
||||
},
|
||||
|
||||
/**
|
||||
* 新增码牌(快捷绑定商户)
|
||||
*/
|
||||
add(data: DeviceQrCodeParam): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/admin/device/qrcode/add', data });
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量创建空白码牌
|
||||
*/
|
||||
createBatch(data: DeviceQrCodeBatchParam): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/admin/device/qrcode/create-batch', data });
|
||||
},
|
||||
|
||||
/**
|
||||
* 判断批次号是否已存在
|
||||
*/
|
||||
existsByBatchNo(batchNo: string): Promise<Result<boolean>> {
|
||||
return defHttp.get({ url: '/admin/device/qrcode/exists-by-batch-no', params: { batchNo } });
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量绑定商户
|
||||
*/
|
||||
bindMerchant(data: DeviceQrCodeBindMerchantParam): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/admin/device/qrcode/bind-merchant', data });
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量解绑商户
|
||||
*/
|
||||
unbindMerchant(ids: string[]): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/admin/device/qrcode/unbind-merchant', data: ids });
|
||||
},
|
||||
|
||||
/**
|
||||
* 修改码牌
|
||||
*/
|
||||
update(data: DeviceQrCodeParam): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/admin/device/qrcode/update', data });
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除码牌
|
||||
*/
|
||||
delete(id: string): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/admin/device/qrcode/delete', params: { id } });
|
||||
},
|
||||
|
||||
/**
|
||||
* 修改码牌状态(启用/停用)
|
||||
*/
|
||||
changeStatus(id: string, status: string): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/admin/device/qrcode/change-status', params: { id, status } });
|
||||
},
|
||||
};
|
||||
|
||||
/** 支付码牌查询参数 */
|
||||
export interface DeviceQrCodeQuery {
|
||||
/** 码牌编码 */
|
||||
code?: string;
|
||||
/** 码牌名称 */
|
||||
name?: string;
|
||||
/** 批次号 */
|
||||
batchNo?: string;
|
||||
/** 商户号 */
|
||||
mchNo?: string;
|
||||
/** 金额类型 random/fixed */
|
||||
amountType?: string;
|
||||
/** 状态 enabled/disabled */
|
||||
status?: string;
|
||||
}
|
||||
|
||||
/** 支付码牌参数 */
|
||||
export interface DeviceQrCodeParam {
|
||||
/** 主键 */
|
||||
id?: string;
|
||||
/** 码牌名称 */
|
||||
name?: string;
|
||||
/** 商户号 */
|
||||
mchNo?: string;
|
||||
/** 关联应用号(空=商户默认应用) */
|
||||
appId?: string;
|
||||
/** 金额类型 random/fixed */
|
||||
amountType?: string;
|
||||
/** 固定金额(分) */
|
||||
fixedAmount?: number;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 备注 */
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/** 批量创建空白码牌参数 */
|
||||
export interface DeviceQrCodeBatchParam {
|
||||
/** 批次号 */
|
||||
batchNo?: string;
|
||||
/** 创建数量 1-999 */
|
||||
count?: number;
|
||||
/** 码牌名称 */
|
||||
name?: string;
|
||||
/** 金额类型 random/fixed */
|
||||
amountType?: string;
|
||||
/** 固定金额(分) */
|
||||
fixedAmount?: number;
|
||||
/** 状态 enabled/disabled, 空则默认启用 */
|
||||
status?: string;
|
||||
/** 备注 */
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/** 绑定商户参数 */
|
||||
export interface DeviceQrCodeBindMerchantParam {
|
||||
/** 码牌主键列表 */
|
||||
ids: string[];
|
||||
/** 商户号 */
|
||||
mchNo: string;
|
||||
/** 关联应用号 */
|
||||
appId?: string;
|
||||
}
|
||||
|
||||
/** 支付码牌结果 */
|
||||
export interface DeviceQrCodeResult extends BaseEntity {
|
||||
/** 码牌编码 */
|
||||
code?: string;
|
||||
/** 码牌名称 */
|
||||
name?: string;
|
||||
/** 批次号 */
|
||||
batchNo?: string;
|
||||
/** 商户号 */
|
||||
mchNo?: string;
|
||||
/** 商户名称(由 mchNo 翻译) */
|
||||
mchName?: string;
|
||||
/** 关联应用号(空=商户默认应用) */
|
||||
appId?: string;
|
||||
/** 金额类型 random/fixed */
|
||||
amountType?: string;
|
||||
/** 固定金额(分) */
|
||||
fixedAmount?: number;
|
||||
/** 状态 enabled/disabled */
|
||||
status?: string;
|
||||
/** 备注 */
|
||||
remark?: string;
|
||||
}
|
||||
@@ -218,6 +218,11 @@ export const PermCodes = {
|
||||
VIEW: 'device:speaker:view',
|
||||
MANAGE: 'device:speaker:manage',
|
||||
},
|
||||
/** 码牌 menuCode=device:qrcode */
|
||||
QrCode: {
|
||||
VIEW: 'device:qrcode:view',
|
||||
MANAGE: 'device:qrcode:manage',
|
||||
},
|
||||
/** 云打印 menuCode=device:printer */
|
||||
Printer: {
|
||||
VIEW: 'device:printer:view',
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"title": "QR Code Board",
|
||||
"field": {
|
||||
"code": "Code",
|
||||
"name": "Name",
|
||||
"batchNo": "Batch No.",
|
||||
"count": "Count",
|
||||
"mchNo": "Merchant No.",
|
||||
"mchName": "Merchant Name",
|
||||
"merchant": "Merchant",
|
||||
"appId": "Application",
|
||||
"amountType": "Amount Type",
|
||||
"fixedAmount": "Fixed Amount",
|
||||
"status": "Status",
|
||||
"remark": "Remark",
|
||||
"createTime": "Create Time"
|
||||
},
|
||||
"status": {
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled"
|
||||
},
|
||||
"amountType": {
|
||||
"random": "Custom Amount",
|
||||
"fixed": "Fixed Amount"
|
||||
},
|
||||
"defaultApp": "Default App",
|
||||
"unbound": "Unbound",
|
||||
"mchNoTooltip": "Merchant No.: {mchNo}",
|
||||
"batchCreate": "Batch Create",
|
||||
"bindMerchant": "Bind Merchant",
|
||||
"unbindMerchant": "Unbind Merchant",
|
||||
"genBatchNo": "Generate",
|
||||
"batchNoPlaceholder": "Click Generate; used as code prefix",
|
||||
"batchNoExists": "Batch number already exists",
|
||||
"batchCreateSuccess": "Successfully created {count} blank QR codes",
|
||||
"selectRequired": "Please select QR code boards first",
|
||||
"validateName": "Please enter the name",
|
||||
"validateMchNo": "Please select a merchant",
|
||||
"validateBatchNo": "Please enter the batch number",
|
||||
"validateCount": "Please enter the count",
|
||||
"validateAmountType": "Please select an amount type",
|
||||
"validateFixedAmount": "Please enter the fixed amount",
|
||||
"validateStatus": "Please select a status",
|
||||
"pleaseSelectMch": "Please select a merchant",
|
||||
"pleaseSelectApp": "Leave empty to use the merchant default app",
|
||||
"enable": "Enable",
|
||||
"disable": "Disable",
|
||||
"confirmDelete": "Are you sure to delete this QR code board?",
|
||||
"confirmEnable": "Are you sure to enable this QR code board?",
|
||||
"confirmDisable": "Are you sure to disable this QR code board? Payment via scan will be unavailable.",
|
||||
"confirmUnbind": "Unbind selected boards from merchants? They will return to blank inventory.",
|
||||
"viewCode": "View Code",
|
||||
"qrCodeTitle": "QR Code",
|
||||
"qrCodeTip": "Scan to enter the payment page",
|
||||
"download": "Download QR Code",
|
||||
"amountLabel": "Amount"
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"title": "支付码牌",
|
||||
"field": {
|
||||
"code": "码牌编码",
|
||||
"name": "码牌名称",
|
||||
"batchNo": "批次号",
|
||||
"count": "创建数量",
|
||||
"mchNo": "商户号",
|
||||
"mchName": "商户名称",
|
||||
"merchant": "商户",
|
||||
"appId": "关联应用",
|
||||
"amountType": "金额类型",
|
||||
"fixedAmount": "固定金额",
|
||||
"status": "状态",
|
||||
"remark": "备注",
|
||||
"createTime": "创建时间"
|
||||
},
|
||||
"status": {
|
||||
"enabled": "启用",
|
||||
"disabled": "停用"
|
||||
},
|
||||
"amountType": {
|
||||
"random": "自定义金额",
|
||||
"fixed": "固定金额"
|
||||
},
|
||||
"defaultApp": "默认应用",
|
||||
"unbound": "未绑定",
|
||||
"mchNoTooltip": "商户号: {mchNo}",
|
||||
"batchCreate": "批量生成",
|
||||
"bindMerchant": "绑定商户",
|
||||
"unbindMerchant": "解绑商户",
|
||||
"genBatchNo": "生成",
|
||||
"batchNoPlaceholder": "可点生成, 将作为码牌编码前缀",
|
||||
"batchNoExists": "批次号已存在",
|
||||
"batchCreateSuccess": "成功创建 {count} 张空白码牌",
|
||||
"selectRequired": "请先勾选码牌",
|
||||
"validateName": "请输入码牌名称",
|
||||
"validateMchNo": "请选择商户",
|
||||
"validateBatchNo": "请输入批次号",
|
||||
"validateCount": "请输入创建数量",
|
||||
"validateAmountType": "请选择金额类型",
|
||||
"validateFixedAmount": "请输入固定金额",
|
||||
"validateStatus": "请选择状态",
|
||||
"pleaseSelectMch": "请选择商户",
|
||||
"pleaseSelectApp": "不选则使用商户默认应用",
|
||||
"enable": "启用",
|
||||
"disable": "停用",
|
||||
"confirmDelete": "确定要删除该码牌吗?",
|
||||
"confirmEnable": "确定要启用该码牌吗?",
|
||||
"confirmDisable": "确定要停用该码牌吗?扫码将无法支付。",
|
||||
"confirmUnbind": "确定要解绑所选码牌的商户吗?解绑后将回到空白库存。",
|
||||
"viewCode": "查看码牌",
|
||||
"qrCodeTitle": "码牌二维码",
|
||||
"qrCodeTip": "扫码即可进入支付页面",
|
||||
"download": "下载二维码",
|
||||
"amountLabel": "金额"
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableInstance, VxeToolbarInstance } from 'vxe-table';
|
||||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { DeviceQrCodeApi, type DeviceQrCodeResult } from '#/api/payment/device/qrcode.api';
|
||||
import { BQuery, type QueryField } from '#/components/query';
|
||||
import { PermCodes } from '#/constants/perm-codes';
|
||||
import { useMessage } from '#/hooks/useMessage';
|
||||
import { usePermission } from '#/hooks/usePermission';
|
||||
|
||||
import DeviceQrCodeBatchCreate from './DeviceQrCodeBatchCreate.vue';
|
||||
import DeviceQrCodeBindMerchant from './DeviceQrCodeBindMerchant.vue';
|
||||
import DeviceQrCodeEdit from './DeviceQrCodeEdit.vue';
|
||||
|
||||
defineOptions({ name: 'DeviceQrCode' });
|
||||
|
||||
const { confirm, message } = useMessage();
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const loading = ref(false);
|
||||
const xTable = ref<VxeTableInstance>();
|
||||
const xToolbar = ref<VxeToolbarInstance>();
|
||||
const editRef = ref<InstanceType<typeof DeviceQrCodeEdit>>();
|
||||
const batchCreateRef = ref<InstanceType<typeof DeviceQrCodeBatchCreate>>();
|
||||
const bindMerchantRef = ref<InstanceType<typeof DeviceQrCodeBindMerchant>>();
|
||||
|
||||
const queryForm = ref<Record<string, any>>({});
|
||||
// 勾选行
|
||||
const selectedRows = ref<DeviceQrCodeResult[]>([]);
|
||||
|
||||
const queryFields = computed<QueryField[]>(() => [
|
||||
{
|
||||
type: 'string',
|
||||
field: 'mchNo',
|
||||
name: $t('payment.device.qrcode.field.mchNo'),
|
||||
placeholder: $t('common.pleaseInput'),
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
field: 'batchNo',
|
||||
name: $t('payment.device.qrcode.field.batchNo'),
|
||||
placeholder: $t('common.pleaseInput'),
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
field: 'code',
|
||||
name: $t('payment.device.qrcode.field.code'),
|
||||
placeholder: $t('common.pleaseInput'),
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
field: 'name',
|
||||
name: $t('payment.device.qrcode.field.name'),
|
||||
placeholder: $t('common.pleaseInput'),
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
field: 'amountType',
|
||||
name: $t('payment.device.qrcode.field.amountType'),
|
||||
placeholder: $t('common.pleaseSelect'),
|
||||
selectList: [
|
||||
{ label: $t('payment.device.qrcode.amountType.random'), value: 'random' },
|
||||
{ label: $t('payment.device.qrcode.amountType.fixed'), value: 'fixed' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
field: 'status',
|
||||
name: $t('payment.device.qrcode.field.status'),
|
||||
placeholder: $t('common.pleaseSelect'),
|
||||
selectList: [
|
||||
{ label: $t('payment.device.qrcode.status.enabled'), value: 'enabled' },
|
||||
{ label: $t('payment.device.qrcode.status.disabled'), value: 'disabled' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const pageConfig = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const tableData = ref<DeviceQrCodeResult[]>([]);
|
||||
|
||||
// 查看码牌弹窗
|
||||
const codeVisible = ref(false);
|
||||
const currentCode = ref('');
|
||||
|
||||
/**
|
||||
* 同步勾选行
|
||||
*/
|
||||
function handleCheckboxChange() {
|
||||
selectedRows.value = (xTable.value?.getCheckboxRecords() || []) as DeviceQrCodeResult[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询码牌列表
|
||||
*/
|
||||
function queryPage() {
|
||||
loading.value = true;
|
||||
DeviceQrCodeApi.page({
|
||||
current: pageConfig.value.currentPage,
|
||||
size: pageConfig.value.pageSize,
|
||||
...queryForm.value,
|
||||
})
|
||||
.then((res: any) => {
|
||||
tableData.value = res.data.records || [];
|
||||
pageConfig.value.total = Number(res.data.total) || 0;
|
||||
selectedRows.value = [];
|
||||
loading.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
queryForm.value = {};
|
||||
pageConfig.value.currentPage = 1;
|
||||
queryPage();
|
||||
}
|
||||
|
||||
function handlePageChange({ currentPage, pageSize }: any) {
|
||||
pageConfig.value.currentPage = currentPage;
|
||||
pageConfig.value.pageSize = pageSize;
|
||||
queryPage();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
xTable.value?.connectToolbar(xToolbar.value as VxeToolbarInstance);
|
||||
queryPage();
|
||||
});
|
||||
|
||||
function handleAdd() {
|
||||
editRef.value?.show();
|
||||
}
|
||||
|
||||
function handleBatchCreate() {
|
||||
batchCreateRef.value?.show();
|
||||
}
|
||||
|
||||
function handleEdit(row: DeviceQrCodeResult) {
|
||||
editRef.value?.showEdit(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取勾选 id, 无勾选时提示
|
||||
*/
|
||||
function getSelectedIds(): null | string[] {
|
||||
const ids = selectedRows.value.map((row) => row.id!).filter(Boolean);
|
||||
if (ids.length === 0) {
|
||||
message.warning($t('payment.device.qrcode.selectRequired'));
|
||||
return null;
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量绑定商户
|
||||
*/
|
||||
function handleBindMerchant() {
|
||||
const ids = getSelectedIds();
|
||||
if (!ids) {
|
||||
return;
|
||||
}
|
||||
bindMerchantRef.value?.show(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量解绑商户
|
||||
*/
|
||||
function handleUnbindMerchant() {
|
||||
const ids = getSelectedIds();
|
||||
if (!ids) {
|
||||
return;
|
||||
}
|
||||
confirm({
|
||||
content: $t('payment.device.qrcode.confirmUnbind'),
|
||||
onOk() {
|
||||
return DeviceQrCodeApi.unbindMerchant(ids).then(() => {
|
||||
message.success($t('common.operationSuccess'));
|
||||
queryPage();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除码牌
|
||||
*/
|
||||
function handleDelete(row: DeviceQrCodeResult) {
|
||||
confirm({
|
||||
content: $t('payment.device.qrcode.confirmDelete'),
|
||||
onOk() {
|
||||
return DeviceQrCodeApi.delete(row.id!).then(() => {
|
||||
message.success($t('common.operationSuccess'));
|
||||
queryPage();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改状态(启用/停用)
|
||||
*/
|
||||
function handleChangeStatus(row: DeviceQrCodeResult, target: 'disabled' | 'enabled') {
|
||||
confirm({
|
||||
content:
|
||||
target === 'enabled' ? $t('payment.device.qrcode.confirmEnable') : $t('payment.device.qrcode.confirmDisable'),
|
||||
onOk() {
|
||||
return DeviceQrCodeApi.changeStatus(row.id!, target).then(() => {
|
||||
message.success($t('common.operationSuccess'));
|
||||
queryPage();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看码牌编码
|
||||
*/
|
||||
function handleViewCode(row: DeviceQrCodeResult) {
|
||||
currentCode.value = row.code || '';
|
||||
codeVisible.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制码牌编码
|
||||
*/
|
||||
async function handleCopyCode() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(currentCode.value);
|
||||
message.success($t('common.operationSuccess'));
|
||||
} catch {
|
||||
message.error($t('common.operationFail'));
|
||||
}
|
||||
}
|
||||
|
||||
// import 放最后避免循环依赖(参照项目惯例)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="m-3 p-3 bg-background rounded-lg list-page-compact">
|
||||
<a-card>
|
||||
<BQuery :fields="queryFields" :query-params="queryForm" @query="queryPage" @reset="resetQuery" />
|
||||
</a-card>
|
||||
|
||||
<div class="mt-4">
|
||||
<a-card>
|
||||
<vxe-toolbar ref="xToolbar" custom refresh :refresh-options="{ queryMethod: queryPage }">
|
||||
<template #buttons>
|
||||
<a-space>
|
||||
<a-button
|
||||
v-if="hasPermission(PermCodes.Device.QrCode.MANAGE)"
|
||||
type="primary"
|
||||
@click="handleBatchCreate"
|
||||
>{{ $t('payment.device.qrcode.batchCreate') }}</a-button
|
||||
>
|
||||
<a-button v-if="hasPermission(PermCodes.Device.QrCode.MANAGE)" @click="handleAdd">{{
|
||||
$t('common.add')
|
||||
}}</a-button>
|
||||
<a-button
|
||||
v-if="hasPermission(PermCodes.Device.QrCode.MANAGE)"
|
||||
:disabled="selectedRows.length === 0"
|
||||
@click="handleBindMerchant"
|
||||
>{{ $t('payment.device.qrcode.bindMerchant') }}</a-button
|
||||
>
|
||||
<a-button
|
||||
v-if="hasPermission(PermCodes.Device.QrCode.MANAGE)"
|
||||
:disabled="selectedRows.length === 0"
|
||||
danger
|
||||
@click="handleUnbindMerchant"
|
||||
>{{ $t('payment.device.qrcode.unbindMerchant') }}</a-button
|
||||
>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-toolbar>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
:row-config="{ keyField: 'id' }"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
@checkbox-change="handleCheckboxChange"
|
||||
@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="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 }">
|
||||
<span v-if="row.batchNo">{{ row.batchNo }}</span>
|
||||
<span v-else style="color: var(--text-color-placeholder)">-</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<!-- 主显商户名, 悬停展示商户号; 未绑定无 tooltip -->
|
||||
<vxe-column field="mchName" :title="$t('payment.device.qrcode.field.merchant')" :min-width="160">
|
||||
<template #default="{ row }">
|
||||
<a-tooltip v-if="row.mchNo" :title="$t('payment.device.qrcode.mchNoTooltip', { mchNo: row.mchNo })">
|
||||
<a-tag color="blue">{{ row.mchName || row.mchNo }}</a-tag>
|
||||
</a-tooltip>
|
||||
<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')"
|
||||
:min-width="120"
|
||||
align="right"
|
||||
>
|
||||
<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>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="status" :title="$t('payment.device.qrcode.field.status')" :min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<a-tag v-if="row.status === 'enabled'" color="green">
|
||||
{{ $t('payment.device.qrcode.status.enabled') }}
|
||||
</a-tag>
|
||||
<a-tag v-else color="default">
|
||||
{{ $t('payment.device.qrcode.status.disabled') }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column
|
||||
field="createTime"
|
||||
:title="$t('payment.device.qrcode.field.createTime')"
|
||||
:min-width="180"
|
||||
formatter="formatDateTime"
|
||||
/>
|
||||
<vxe-column fixed="right" width="220" :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"
|
||||
size="small"
|
||||
@click="handleEdit(row)"
|
||||
>{{ $t('common.edit') }}</a-button
|
||||
>
|
||||
<a-button
|
||||
v-if="hasPermission(PermCodes.Device.QrCode.MANAGE) && row.status !== 'enabled'"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleChangeStatus(row, 'enabled')"
|
||||
>{{ $t('payment.device.qrcode.enable') }}</a-button
|
||||
>
|
||||
<a-button
|
||||
v-else-if="hasPermission(PermCodes.Device.QrCode.MANAGE) && row.status === 'enabled'"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleChangeStatus(row, 'disabled')"
|
||||
>{{ $t('payment.device.qrcode.disable') }}</a-button
|
||||
>
|
||||
<a-button
|
||||
v-if="hasPermission(PermCodes.Device.QrCode.MANAGE)"
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click="handleDelete(row)"
|
||||
>{{ $t('common.delete') }}</a-button
|
||||
>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<vxe-pager
|
||||
size="medium"
|
||||
:loading="loading"
|
||||
:current-page="pageConfig.currentPage"
|
||||
:page-size="pageConfig.pageSize"
|
||||
:total="pageConfig.total"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<DeviceQrCodeEdit ref="editRef" @ok="queryPage" />
|
||||
<DeviceQrCodeBatchCreate ref="batchCreateRef" @ok="queryPage" />
|
||||
<DeviceQrCodeBindMerchant ref="bindMerchantRef" @ok="queryPage" />
|
||||
|
||||
<!-- 查看码牌弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="codeVisible"
|
||||
:title="$t('payment.device.qrcode.qrCodeTitle')"
|
||||
:width="420"
|
||||
:footer="null"
|
||||
:destroy-on-hidden="true"
|
||||
>
|
||||
<div class="code-modal">
|
||||
<p class="code-tip">{{ $t('payment.device.qrcode.qrCodeTip') }}</p>
|
||||
<div class="code-value">{{ currentCode }}</div>
|
||||
<a-button type="primary" block @click="handleCopyCode">
|
||||
{{ $t('common.copy') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.code-modal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.code-tip {
|
||||
margin: 0;
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.code-value {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: var(--background-color);
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
font-family: monospace;
|
||||
font-size: 15px;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,207 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import {
|
||||
DeviceQrCodeApi,
|
||||
type DeviceQrCodeBatchParam,
|
||||
} from '#/api/payment/device/qrcode.api';
|
||||
import { useMessage } from '#/hooks/useMessage';
|
||||
|
||||
const emit = defineEmits(['ok']);
|
||||
|
||||
const { message } = useMessage();
|
||||
|
||||
const visible = ref(false);
|
||||
const confirmLoading = ref(false);
|
||||
const formRef = ref();
|
||||
// 固定金额展示值(元)
|
||||
const fixedAmountYuan = ref<number | undefined>(undefined);
|
||||
|
||||
const formState = ref<DeviceQrCodeBatchParam>({
|
||||
batchNo: '',
|
||||
count: 10,
|
||||
amountType: 'random',
|
||||
status: 'enabled',
|
||||
});
|
||||
|
||||
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') }],
|
||||
}));
|
||||
|
||||
/**
|
||||
* 一键生成批次号: Q + YYMMDDHHmmss(对齐商业版)
|
||||
*/
|
||||
function genBatchNo() {
|
||||
formState.value.batchNo = `Q${dayjs().format('YYMMDDHHmmss')}`;
|
||||
// 生成后异步校验是否已存在
|
||||
checkBatchNo();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开批量生成弹窗, 默认填入批次号
|
||||
* destroy-on-hidden 时需先挂载表单再赋值, 避免 Form 与 model 时序错位
|
||||
*/
|
||||
async function show() {
|
||||
formState.value = {
|
||||
batchNo: '',
|
||||
count: 10,
|
||||
amountType: 'random',
|
||||
status: 'enabled',
|
||||
};
|
||||
fixedAmountYuan.value = undefined;
|
||||
visible.value = true;
|
||||
await nextTick();
|
||||
formRef.value?.clearValidate?.();
|
||||
// 自动生成批次号
|
||||
genBatchNo();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭弹窗
|
||||
*/
|
||||
function handleCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 失焦/生成后校验批次号是否已存在
|
||||
*/
|
||||
async function checkBatchNo() {
|
||||
const batchNo = formState.value.batchNo?.trim();
|
||||
if (!batchNo) {
|
||||
return;
|
||||
}
|
||||
const { data } = await DeviceQrCodeApi.existsByBatchNo(batchNo);
|
||||
if (data) {
|
||||
message.warning($t('payment.device.qrcode.batchNoExists'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交批量创建
|
||||
*/
|
||||
async function handleOk() {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
} 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)
|
||||
: undefined,
|
||||
};
|
||||
// 再校验一次批次号
|
||||
const { data: exists } = await DeviceQrCodeApi.existsByBatchNo(payload.batchNo!);
|
||||
if (exists) {
|
||||
message.error($t('payment.device.qrcode.batchNoExists'));
|
||||
return;
|
||||
}
|
||||
await DeviceQrCodeApi.createBatch(payload);
|
||||
message.success(
|
||||
$t('payment.device.qrcode.batchCreateSuccess', { count: payload.count ?? 0 }),
|
||||
);
|
||||
handleCancel();
|
||||
emit('ok');
|
||||
} finally {
|
||||
confirmLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ show });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
:title="$t('payment.device.qrcode.batchCreate')"
|
||||
:width="640"
|
||||
:confirm-loading="confirmLoading"
|
||||
:destroy-on-hidden="true"
|
||||
:mask-closable="false"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-spin :spinning="confirmLoading">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="formRules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
class="form-compact"
|
||||
>
|
||||
<a-form-item :label="$t('payment.device.qrcode.field.batchNo')" name="batchNo">
|
||||
<!-- antdv-next: Compact block 占满; 输入框 flex:1 吃掉剩余宽度, 与其它 width:100% 表单项对齐 -->
|
||||
<a-space-compact block style="width: 100%">
|
||||
<a-input
|
||||
v-model:value="formState.batchNo"
|
||||
:placeholder="$t('payment.device.qrcode.batchNoPlaceholder')"
|
||||
style="flex: 1; min-width: 0"
|
||||
@blur="checkBatchNo"
|
||||
/>
|
||||
<a-button type="primary" @click="genBatchNo">
|
||||
{{ $t('payment.device.qrcode.genBatchNo') }}
|
||||
</a-button>
|
||||
</a-space-compact>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('payment.device.qrcode.field.count')" name="count">
|
||||
<a-input-number
|
||||
v-model:value="formState.count"
|
||||
:min="1"
|
||||
:max="999"
|
||||
:precision="0"
|
||||
style="width: 100%"
|
||||
:placeholder="$t('common.pleaseInput')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('payment.device.qrcode.field.name')">
|
||||
<a-input v-model:value="formState.name" :placeholder="$t('common.pleaseInput')" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('payment.device.qrcode.field.amountType')" name="amountType">
|
||||
<a-radio-group v-model:value="formState.amountType" button-style="solid">
|
||||
<a-radio-button value="random">{{ $t('payment.device.qrcode.amountType.random') }}</a-radio-button>
|
||||
<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-input-number
|
||||
v-model:value="fixedAmountYuan"
|
||||
:min="0.01"
|
||||
:step="0.01"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
:placeholder="$t('common.pleaseInput')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('payment.device.qrcode.field.status')" name="status">
|
||||
<a-radio-group v-model:value="formState.status" button-style="solid">
|
||||
<a-radio-button value="enabled">{{ $t('payment.device.qrcode.status.enabled') }}</a-radio-button>
|
||||
<a-radio-button value="disabled">{{ $t('payment.device.qrcode.status.disabled') }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('payment.device.qrcode.field.remark')">
|
||||
<a-textarea v-model:value="formState.remark" :rows="2" :placeholder="$t('common.pleaseInput')" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</a-modal>
|
||||
</template>
|
||||
@@ -0,0 +1,121 @@
|
||||
<script lang="ts" setup>
|
||||
import type { LabelValue } from '#/types/web';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
DeviceQrCodeApi,
|
||||
type DeviceQrCodeBindMerchantParam,
|
||||
} from '#/api/payment/device/qrcode.api';
|
||||
import { MerchantApi } from '#/api/payment/merchant/merchant.api';
|
||||
import { useMessage } from '#/hooks/useMessage';
|
||||
|
||||
const emit = defineEmits(['ok']);
|
||||
|
||||
const { message } = useMessage();
|
||||
|
||||
const visible = ref(false);
|
||||
const confirmLoading = ref(false);
|
||||
const formRef = ref();
|
||||
|
||||
// 待绑定码牌 id
|
||||
const ids = ref<string[]>([]);
|
||||
// 商户下拉
|
||||
const mchOptions = ref<LabelValue[]>([]);
|
||||
|
||||
const formState = ref<{ mchNo?: string }>({
|
||||
mchNo: undefined,
|
||||
});
|
||||
|
||||
const formRules = computed(() => ({
|
||||
mchNo: [{ required: true, message: $t('payment.device.qrcode.validateMchNo') }],
|
||||
}));
|
||||
|
||||
/**
|
||||
* 加载商户下拉
|
||||
*/
|
||||
async function loadMchOptions() {
|
||||
const { data } = await MerchantApi.dropdown();
|
||||
mchOptions.value = data || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开绑定弹窗
|
||||
*/
|
||||
async function show(selectedIds: string[]) {
|
||||
ids.value = selectedIds;
|
||||
formState.value = { mchNo: undefined };
|
||||
formRef.value?.resetFields();
|
||||
visible.value = true;
|
||||
await loadMchOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭弹窗
|
||||
*/
|
||||
function handleCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交绑定: 不传 appId, 后端 resolveApp 取商户默认应用
|
||||
*/
|
||||
async function handleOk() {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
confirmLoading.value = true;
|
||||
try {
|
||||
const payload: DeviceQrCodeBindMerchantParam = {
|
||||
ids: ids.value,
|
||||
mchNo: formState.value.mchNo!,
|
||||
};
|
||||
await DeviceQrCodeApi.bindMerchant(payload);
|
||||
message.success($t('common.operationSuccess'));
|
||||
handleCancel();
|
||||
emit('ok');
|
||||
} finally {
|
||||
confirmLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ show });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
:title="$t('payment.device.qrcode.bindMerchant')"
|
||||
:width="640"
|
||||
:confirm-loading="confirmLoading"
|
||||
:destroy-on-hidden="true"
|
||||
:mask-closable="false"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-spin :spinning="confirmLoading">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="formRules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
class="form-compact"
|
||||
>
|
||||
<a-form-item :label="$t('payment.device.qrcode.field.mchNo')" name="mchNo">
|
||||
<a-select
|
||||
v-model:value="formState.mchNo"
|
||||
:options="mchOptions"
|
||||
:placeholder="$t('payment.device.qrcode.pleaseSelectMch')"
|
||||
show-search
|
||||
option-filter-prop="label"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</a-modal>
|
||||
</template>
|
||||
@@ -0,0 +1,239 @@
|
||||
<script lang="ts" setup>
|
||||
import type { LabelValue } from '#/types/web';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { DeviceQrCodeApi, type DeviceQrCodeParam, type DeviceQrCodeResult } from '#/api/payment/device/qrcode.api';
|
||||
import { MchAppInfoApi, type MchAppInfoResult } from '#/api/payment/merchant/mch-app-info.api';
|
||||
import { MerchantApi } from '#/api/payment/merchant/merchant.api';
|
||||
import { FormEditType } from '#/enums/formEditType';
|
||||
import { useFormEdit } from '#/hooks/useFormEdit';
|
||||
import { useMessage } from '#/hooks/useMessage';
|
||||
|
||||
const emit = defineEmits(['ok']);
|
||||
|
||||
const { message } = useMessage();
|
||||
|
||||
const formRef = ref();
|
||||
|
||||
const { visible, confirmLoading, title, initFormEditType, handleCancel, formEditType } = useFormEdit();
|
||||
|
||||
// 是否编辑模式(编辑时商户/应用只读展示)
|
||||
const isEdit = computed(() => formEditType.value === FormEditType.Edit);
|
||||
|
||||
// 商户下拉选项
|
||||
const mchOptions = ref<LabelValue[]>([]);
|
||||
|
||||
// 应用下拉选项(根据商户联动)
|
||||
const appOptions = ref<MchAppInfoResult[]>([]);
|
||||
|
||||
const formState = ref<DeviceQrCodeParam>({
|
||||
name: '',
|
||||
mchNo: '',
|
||||
amountType: 'random',
|
||||
});
|
||||
|
||||
const formRules = computed(() => {
|
||||
const rules: Record<string, any[]> = {
|
||||
name: [{ required: true, message: $t('payment.device.qrcode.validateName') }],
|
||||
amountType: [{ required: true, message: $t('payment.device.qrcode.validateAmountType') }],
|
||||
};
|
||||
// 新增时商户必填; 编辑不改归属
|
||||
if (!isEdit.value) {
|
||||
rules.mchNo = [{ required: true, message: $t('payment.device.qrcode.validateMchNo') }];
|
||||
}
|
||||
return rules;
|
||||
});
|
||||
|
||||
// 金额类型为固定时, 固定金额必填
|
||||
const isFixedAmount = computed(() => formState.value.amountType === 'fixed');
|
||||
|
||||
// 固定金额展示值(元), 提交时转回分
|
||||
const fixedAmountYuan = ref<number | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* 加载商户下拉
|
||||
*/
|
||||
async function loadMchOptions() {
|
||||
const { data } = await MerchantApi.dropdown();
|
||||
mchOptions.value = data || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 商户切换时重新加载应用列表并清空已选应用
|
||||
*/
|
||||
async function handleMchChange() {
|
||||
formState.value.appId = undefined;
|
||||
appOptions.value = [];
|
||||
if (formState.value.mchNo) {
|
||||
const { data } = await MchAppInfoApi.page({ mchNo: formState.value.mchNo, size: 999 });
|
||||
appOptions.value = data?.records || [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置表单
|
||||
*/
|
||||
function resetForm() {
|
||||
formState.value = {
|
||||
name: '',
|
||||
mchNo: '',
|
||||
amountType: 'random',
|
||||
};
|
||||
fixedAmountYuan.value = undefined;
|
||||
appOptions.value = [];
|
||||
formRef.value?.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开新增弹窗
|
||||
*/
|
||||
function show() {
|
||||
initFormEditType(FormEditType.Add);
|
||||
resetForm();
|
||||
loadMchOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开编辑弹窗(归属只读)
|
||||
*/
|
||||
async function showEdit(record: DeviceQrCodeResult) {
|
||||
initFormEditType(FormEditType.Edit);
|
||||
resetForm();
|
||||
confirmLoading.value = true;
|
||||
try {
|
||||
const { data } = await DeviceQrCodeApi.get(record.id!);
|
||||
const row = data || record;
|
||||
formState.value = {
|
||||
id: row.id!,
|
||||
name: row.name,
|
||||
mchNo: row.mchNo,
|
||||
appId: row.appId,
|
||||
amountType: row.amountType,
|
||||
fixedAmount: row.fixedAmount,
|
||||
remark: row.remark,
|
||||
};
|
||||
fixedAmountYuan.value = row.fixedAmount ? row.fixedAmount / 100 : undefined;
|
||||
} finally {
|
||||
confirmLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存
|
||||
*/
|
||||
async function handleOk() {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
} 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: DeviceQrCodeParam = {
|
||||
...formState.value,
|
||||
fixedAmount: isFixedAmount.value && fixedAmountYuan.value ? Math.round(fixedAmountYuan.value * 100) : undefined,
|
||||
};
|
||||
await (formEditType.value === FormEditType.Edit ? DeviceQrCodeApi.update(payload) : DeviceQrCodeApi.add(payload));
|
||||
message.success($t('common.operationSuccess'));
|
||||
handleCancel();
|
||||
emit('ok');
|
||||
} finally {
|
||||
confirmLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// import 放最后避免循环依赖(参照项目惯例)
|
||||
defineExpose({ show, showEdit });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
:title="title"
|
||||
:width="640"
|
||||
:confirm-loading="confirmLoading"
|
||||
:destroy-on-hidden="true"
|
||||
:mask-closable="false"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-spin :spinning="confirmLoading">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="formRules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
class="form-compact"
|
||||
>
|
||||
<!-- 新增: 可选商户/应用; 编辑: 只读展示归属 -->
|
||||
<a-form-item v-if="!isEdit" :label="$t('payment.device.qrcode.field.mchNo')" name="mchNo">
|
||||
<a-select
|
||||
v-model:value="formState.mchNo"
|
||||
:options="mchOptions"
|
||||
:placeholder="$t('payment.device.qrcode.pleaseSelectMch')"
|
||||
show-search
|
||||
option-filter-prop="label"
|
||||
@change="handleMchChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="!isEdit" :label="$t('payment.device.qrcode.field.appId')">
|
||||
<a-select
|
||||
v-model:value="formState.appId"
|
||||
:options="appOptions"
|
||||
:field-names="{ label: 'appName', value: 'appId' }"
|
||||
:placeholder="$t('payment.device.qrcode.pleaseSelectApp')"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="isEdit" :label="$t('payment.device.qrcode.field.mchNo')">
|
||||
<a-tag v-if="formState.mchNo" color="blue">{{ formState.mchNo }}</a-tag>
|
||||
<a-tag v-else color="default">{{ $t('payment.device.qrcode.unbound') }}</a-tag>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="isEdit" :label="$t('payment.device.qrcode.field.appId')">
|
||||
<a-tag v-if="formState.appId" color="cyan">{{ formState.appId }}</a-tag>
|
||||
<span v-else-if="formState.mchNo" style="color: var(--text-color-placeholder)">
|
||||
{{ $t('payment.device.qrcode.defaultApp') }}
|
||||
</span>
|
||||
<span v-else style="color: var(--text-color-placeholder)">-</span>
|
||||
</a-form-item>
|
||||
<!-- 码牌名称 -->
|
||||
<a-form-item :label="$t('payment.device.qrcode.field.name')" name="name">
|
||||
<a-input v-model:value="formState.name" :placeholder="$t('common.pleaseInput')" />
|
||||
</a-form-item>
|
||||
<!-- 金额类型 -->
|
||||
<a-form-item :label="$t('payment.device.qrcode.field.amountType')" name="amountType">
|
||||
<a-radio-group v-model:value="formState.amountType" button-style="solid">
|
||||
<a-radio-button value="random">{{ $t('payment.device.qrcode.amountType.random') }}</a-radio-button>
|
||||
<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"
|
||||
:min="0.01"
|
||||
:step="0.01"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
:placeholder="$t('common.pleaseInput')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- 备注 -->
|
||||
<a-form-item :label="$t('payment.device.qrcode.field.remark')">
|
||||
<a-textarea v-model:value="formState.remark" :rows="2" :placeholder="$t('common.pleaseInput')" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</a-modal>
|
||||
</template>
|
||||
Reference in New Issue
Block a user