Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vben into dev
commit
67af898baf
|
@ -26,6 +26,7 @@
|
|||
"#/*": "./src/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons-vue": "catalog:",
|
||||
"@form-create/ant-design-vue": "catalog:",
|
||||
"@form-create/antd-designer": "catalog:",
|
||||
"@tinymce/tinymce-vue": "catalog:",
|
||||
|
|
|
@ -68,3 +68,4 @@ export { initSetupVbenForm, useVbenForm, z };
|
|||
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
export type FormSchemaGetter = () => VbenFormSchema[];
|
||||
|
|
|
@ -27,7 +27,7 @@ export function getChannelPage(params: PageParam) {
|
|||
}
|
||||
|
||||
/** 查询支付渠道详情 */
|
||||
export function getChannel(appId: string, code: string) {
|
||||
export function getChannel(appId: number, code: string) {
|
||||
return requestClient.get<PayChannelApi.Channel>('/pay/channel/get', {
|
||||
params: { appId, code },
|
||||
});
|
||||
|
|
|
@ -6,8 +6,13 @@ export namespace PayOrderApi {
|
|||
/** 支付订单信息 */
|
||||
export interface Order {
|
||||
id: number;
|
||||
no: string;
|
||||
price: number;
|
||||
channelFeePrice: number;
|
||||
refundPrice: number;
|
||||
merchantId: number;
|
||||
appId: number;
|
||||
appName: string;
|
||||
channelId: number;
|
||||
channelCode: string;
|
||||
merchantOrderId: string;
|
||||
|
@ -29,7 +34,9 @@ export namespace PayOrderApi {
|
|||
refundAmount: number;
|
||||
channelUserId: string;
|
||||
channelOrderNo: string;
|
||||
channelNotifyData: string;
|
||||
createTime: Date;
|
||||
updateTime: Date;
|
||||
}
|
||||
|
||||
/** 支付订单分页请求 */
|
||||
|
|
|
@ -58,7 +58,7 @@ const props = withDefaults(
|
|||
showDescription: false,
|
||||
},
|
||||
);
|
||||
const emit = defineEmits(['change', 'update:value', 'delete']);
|
||||
const emit = defineEmits(['change', 'update:value', 'delete', 'returnText']);
|
||||
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
|
||||
const isInnerOperate = ref<boolean>(false);
|
||||
const { getStringAccept } = useUploadType({
|
||||
|
@ -125,6 +125,10 @@ const handleRemove = async (file: UploadFile) => {
|
|||
};
|
||||
|
||||
const beforeUpload = async (file: File) => {
|
||||
// 使用现代的Blob.text()方法替代FileReader
|
||||
const fileContent = await file.text();
|
||||
emit('returnText', fileContent);
|
||||
|
||||
const { maxSize, accept } = props;
|
||||
const isAct = checkFileType(file, accept);
|
||||
if (!isAct) {
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
import type { FormSchemaGetter } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
|
||||
|
||||
export const querySchema: FormSchemaGetter = () => [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'name',
|
||||
label: '应用名',
|
||||
componentProps: {
|
||||
placeholder: '请输入应用名',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'status',
|
||||
label: '开启状态',
|
||||
componentProps: {
|
||||
placeholder: '请选择开启状态',
|
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'RangePicker',
|
||||
fieldName: 'createTime',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
placeholder: ['开始日期', '结束日期'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const columns: VxeGridProps['columns'] = [
|
||||
{ type: 'checkbox', width: 60 },
|
||||
{
|
||||
title: '应用标识',
|
||||
field: 'appKey',
|
||||
},
|
||||
{
|
||||
title: '应用名',
|
||||
field: 'name',
|
||||
},
|
||||
{
|
||||
title: '开启状态',
|
||||
field: 'status',
|
||||
slots: {
|
||||
default: 'status',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '支付宝配置',
|
||||
children: [
|
||||
{
|
||||
title: 'APP 支付',
|
||||
slots: {
|
||||
default: 'alipayAppConfig',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'PC 网站支付',
|
||||
slots: {
|
||||
default: 'alipayPCConfig',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'WAP 网站支付',
|
||||
slots: {
|
||||
default: 'alipayWAPConfig',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '扫码支付',
|
||||
slots: {
|
||||
default: 'alipayQrConfig',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '条码支付',
|
||||
slots: {
|
||||
default: 'alipayBarConfig',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '微信配置',
|
||||
children: [
|
||||
{
|
||||
title: '小程序支付',
|
||||
slots: {
|
||||
default: 'wxLiteConfig',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'JSAPI 支付',
|
||||
slots: {
|
||||
default: 'wxPubConfig',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'APP 支付',
|
||||
slots: {
|
||||
default: 'wxAppConfig',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Native 支付',
|
||||
slots: {
|
||||
default: 'wxNativeConfig',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'WAP 网站支付',
|
||||
slots: {
|
||||
default: 'wxWapConfig',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '条码支付',
|
||||
slots: {
|
||||
default: 'wxBarConfig',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '钱包支付配置',
|
||||
field: 'walletConfig',
|
||||
slots: {
|
||||
default: 'walletConfig',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '模拟支付配置',
|
||||
field: 'mockConfig',
|
||||
slots: {
|
||||
default: 'mockConfig',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
minWidth: 160,
|
||||
},
|
||||
];
|
||||
|
||||
export const modalSchema: FormSchemaGetter = () => [
|
||||
{
|
||||
label: '应用编号',
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '应用名',
|
||||
fieldName: 'name',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入应用名',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '应用标识',
|
||||
fieldName: 'appKey',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入应用标识',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '开启状态',
|
||||
fieldName: 'status',
|
||||
component: 'RadioGroup',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '支付结果的回调地址',
|
||||
fieldName: 'orderNotifyUrl',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入支付结果的回调地址',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '退款结果的回调地址',
|
||||
fieldName: 'refundNotifyUrl',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入支付结果的回调地址',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '转账结果的回调地址',
|
||||
fieldName: 'transferNotifyUrl',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入转账结果的回调地址',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
fieldName: 'remark',
|
||||
component: 'Textarea',
|
||||
componentProps: {
|
||||
rows: 3,
|
||||
placeholder: '请输入备注',
|
||||
},
|
||||
},
|
||||
];
|
|
@ -1,31 +1,498 @@
|
|||
<script lang="ts" setup>
|
||||
import { Page } from '@vben/common-ui';
|
||||
import type { VbenFormProps } from '@vben/common-ui';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { h, reactive } from 'vue';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
import { getVxePopupContainer } from '@vben/utils';
|
||||
|
||||
import { CheckOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||
import { Button, Popconfirm, Space, Switch } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import * as PayApi from '#/api/pay/app';
|
||||
import { DocAlert } from '#/components/doc-alert';
|
||||
import { PayChannelEnum } from '#/utils/constants';
|
||||
|
||||
import { columns, querySchema } from './data';
|
||||
import appFrom from './modules/app-form.vue';
|
||||
import aliPayFrom from './modules/channel/alipay-channel-form.vue';
|
||||
import mockFrom from './modules/channel/mock-channel-form.vue';
|
||||
import walletFrom from './modules/channel/wallet-channel-form.vue';
|
||||
import weixinFrom from './modules/channel/weixin-channel-form.vue';
|
||||
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
labelWidth: 100,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
// 处理区间选择器RangePicker时间格式 将一个字段映射为两个字段 搜索/导出会用到
|
||||
// 不需要直接删除
|
||||
// fieldMappingTime: [
|
||||
// [
|
||||
// 'createTime',
|
||||
// ['params[beginTime]', 'params[endTime]'],
|
||||
// ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
|
||||
// ],
|
||||
// ],
|
||||
};
|
||||
|
||||
const gridOptions: VxeGridProps = {
|
||||
checkboxConfig: {
|
||||
// 高亮
|
||||
highlight: true,
|
||||
// 翻页时保留选中状态
|
||||
reserve: true,
|
||||
// 点击行选中
|
||||
// trigger: 'row',
|
||||
},
|
||||
columns,
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
pagerConfig: {},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues = {}) => {
|
||||
return await PayApi.getAppPage({
|
||||
pageNum: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
// 表格全局唯一表示 保存列配置需要用到
|
||||
id: 'pay-app-index',
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
formOptions,
|
||||
gridOptions,
|
||||
});
|
||||
|
||||
const [AppModal, modalApi] = useVbenModal({
|
||||
connectedComponent: appFrom,
|
||||
});
|
||||
|
||||
const [AliPayModal, modalAliPayApi] = useVbenModal({
|
||||
connectedComponent: aliPayFrom,
|
||||
});
|
||||
|
||||
const [MockModal, modalMockApi] = useVbenModal({
|
||||
connectedComponent: mockFrom,
|
||||
});
|
||||
|
||||
const [WalletModal, modalWalletApi] = useVbenModal({
|
||||
connectedComponent: walletFrom,
|
||||
});
|
||||
|
||||
const [WeixinModal, modalWeixinApi] = useVbenModal({
|
||||
connectedComponent: weixinFrom,
|
||||
});
|
||||
|
||||
const handleAdd = () => {
|
||||
modalApi.setData({});
|
||||
modalApi.open();
|
||||
};
|
||||
|
||||
const handleEdit = (row: Required<PayApi.PayAppApi.App>) => {
|
||||
modalApi.setData({ id: row.id });
|
||||
modalApi.open();
|
||||
};
|
||||
|
||||
const handleDelete = async (row: Required<PayApi.PayAppApi.App>) => {
|
||||
await PayApi.deleteApp(row.id);
|
||||
tableApi.query();
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据渠道编码判断渠道列表中是否存在
|
||||
*
|
||||
* @param channels 渠道列表
|
||||
* @param channelCode 渠道编码
|
||||
*/
|
||||
const isChannelExists = (channels: string[], channelCode: string) => {
|
||||
if (!channels) {
|
||||
return false;
|
||||
}
|
||||
return channels.includes(channelCode);
|
||||
};
|
||||
|
||||
const channelParam = reactive({
|
||||
appId: 0, // 应用 ID
|
||||
payCode: '', // 渠道编码
|
||||
});
|
||||
|
||||
const openChannelForm = async (row: PayApi.PayAppApi.App, payCode: string) => {
|
||||
channelParam.appId = row.id || 0;
|
||||
channelParam.payCode = payCode;
|
||||
if (payCode.indexOf('alipay_') === 0) {
|
||||
modalAliPayApi.setData({ id: row.id, payCode });
|
||||
modalAliPayApi.open();
|
||||
return;
|
||||
}
|
||||
if (payCode.indexOf('wx_') === 0) {
|
||||
modalWeixinApi.setData({ id: row.id, payCode });
|
||||
modalWeixinApi.open();
|
||||
return;
|
||||
}
|
||||
if (payCode.indexOf('mock') === 0) {
|
||||
modalMockApi.setData({ id: row.id, payCode });
|
||||
modalMockApi.open();
|
||||
return;
|
||||
}
|
||||
if (payCode.indexOf('wallet') === 0) {
|
||||
modalWalletApi.setData({ id: row.id, payCode });
|
||||
modalWalletApi.open();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page>
|
||||
<Page :auto-content-height="true">
|
||||
<DocAlert title="支付功能开启" url="https://doc.iocoder.cn/pay/build/" />
|
||||
<Button
|
||||
danger
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||
>
|
||||
该功能支持 Vue3 + element-plus 版本!
|
||||
</Button>
|
||||
<br />
|
||||
<Button
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/pay/app/index"
|
||||
>
|
||||
可参考
|
||||
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/pay/app/index
|
||||
代码,pull request 贡献给我们!
|
||||
</Button>
|
||||
<BasicTable>
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button
|
||||
type="primary"
|
||||
v-access:code="['pay:app:create']"
|
||||
@click="handleAdd"
|
||||
>
|
||||
{{ $t('ui.actionTitle.create', ['应用']) }}
|
||||
</a-button>
|
||||
</Space>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<Space>
|
||||
<Button
|
||||
v-access:code="['pay:app:update']"
|
||||
type="link"
|
||||
@click.stop="handleEdit(row)"
|
||||
>
|
||||
{{ $t('ui.actionTitle.edit') }}
|
||||
</Button>
|
||||
<Popconfirm
|
||||
:get-popup-container="getVxePopupContainer"
|
||||
placement="left"
|
||||
v-access:code="['pay:app:delete']"
|
||||
title="确认删除?"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<Button type="link" danger>
|
||||
{{ $t('ui.actionTitle.delete') }}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<Switch
|
||||
v-model:checked="row.status"
|
||||
:checked-value="0"
|
||||
:un-checked-value="1"
|
||||
/>
|
||||
</template>
|
||||
<template #alipayAppConfig="{ row }">
|
||||
<div>
|
||||
<Button
|
||||
size="small"
|
||||
v-if="
|
||||
isChannelExists(row.channelCodes, PayChannelEnum.ALIPAY_APP.code)
|
||||
"
|
||||
type="primary"
|
||||
:icon="h(CheckOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.ALIPAY_APP.code)"
|
||||
/>
|
||||
<Button
|
||||
v-else
|
||||
size="small"
|
||||
type="primary"
|
||||
danger
|
||||
:icon="h(CloseOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.ALIPAY_APP.code)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #alipayPCConfig="{ row }">
|
||||
<div>
|
||||
<Button
|
||||
size="small"
|
||||
v-if="
|
||||
isChannelExists(row.channelCodes, PayChannelEnum.ALIPAY_PC.code)
|
||||
"
|
||||
type="primary"
|
||||
:icon="h(CheckOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.ALIPAY_PC.code)"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
v-else
|
||||
type="primary"
|
||||
danger
|
||||
:icon="h(CloseOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.ALIPAY_PC.code)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #alipayWAPConfig="{ row }">
|
||||
<div>
|
||||
<Button
|
||||
size="small"
|
||||
v-if="
|
||||
isChannelExists(row.channelCodes, PayChannelEnum.ALIPAY_WAP.code)
|
||||
"
|
||||
type="primary"
|
||||
:icon="h(CheckOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.ALIPAY_WAP.code)"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
v-else
|
||||
type="primary"
|
||||
danger
|
||||
:icon="h(CloseOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.ALIPAY_WAP.code)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #alipayQrConfig="{ row }">
|
||||
<div>
|
||||
<Button
|
||||
size="small"
|
||||
v-if="
|
||||
isChannelExists(row.channelCodes, PayChannelEnum.ALIPAY_QR.code)
|
||||
"
|
||||
type="primary"
|
||||
:icon="h(CheckOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.ALIPAY_QR.code)"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
v-else
|
||||
type="primary"
|
||||
danger
|
||||
:icon="h(CloseOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.ALIPAY_QR.code)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #alipayBarConfig="{ row }">
|
||||
<div>
|
||||
<Button
|
||||
size="small"
|
||||
v-if="
|
||||
isChannelExists(row.channelCodes, PayChannelEnum.ALIPAY_BAR.code)
|
||||
"
|
||||
type="primary"
|
||||
:icon="h(CheckOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.ALIPAY_BAR.code)"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
v-else
|
||||
type="primary"
|
||||
danger
|
||||
:icon="h(CloseOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.ALIPAY_BAR.code)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #wxLiteConfig="{ row }">
|
||||
<div>
|
||||
<Button
|
||||
size="small"
|
||||
v-if="
|
||||
isChannelExists(row.channelCodes, PayChannelEnum.WX_LITE.code)
|
||||
"
|
||||
type="primary"
|
||||
:icon="h(CheckOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.WX_LITE.code)"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
v-else
|
||||
type="primary"
|
||||
danger
|
||||
:icon="h(CloseOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.WX_LITE.code)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #wxPubConfig="{ row }">
|
||||
<div>
|
||||
<Button
|
||||
size="small"
|
||||
v-if="isChannelExists(row.channelCodes, PayChannelEnum.WX_PUB.code)"
|
||||
type="primary"
|
||||
:icon="h(CheckOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.WX_PUB.code)"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
v-else
|
||||
type="primary"
|
||||
danger
|
||||
:icon="h(CloseOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.WX_PUB.code)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #wxAppConfig="{ row }">
|
||||
<div>
|
||||
<Button
|
||||
size="small"
|
||||
v-if="isChannelExists(row.channelCodes, PayChannelEnum.WX_APP.code)"
|
||||
type="primary"
|
||||
:icon="h(CheckOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.WX_APP.code)"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
v-else
|
||||
type="primary"
|
||||
danger
|
||||
:icon="h(CloseOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.WX_APP.code)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #wxNativeConfig="{ row }">
|
||||
<div>
|
||||
<Button
|
||||
size="small"
|
||||
v-if="
|
||||
isChannelExists(row.channelCodes, PayChannelEnum.WX_NATIVE.code)
|
||||
"
|
||||
type="primary"
|
||||
:icon="h(CheckOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.WX_NATIVE.code)"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
v-else
|
||||
type="primary"
|
||||
danger
|
||||
:icon="h(CloseOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.WX_NATIVE.code)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #wxWapConfig="{ row }">
|
||||
<div>
|
||||
<Button
|
||||
size="small"
|
||||
v-if="isChannelExists(row.channelCodes, PayChannelEnum.WX_WAP.code)"
|
||||
type="primary"
|
||||
:icon="h(CheckOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.WX_WAP.code)"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
v-else
|
||||
type="primary"
|
||||
danger
|
||||
:icon="h(CloseOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.WX_WAP.code)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #wxBarConfig="{ row }">
|
||||
<div>
|
||||
<Button
|
||||
size="small"
|
||||
v-if="isChannelExists(row.channelCodes, PayChannelEnum.WX_BAR.code)"
|
||||
type="primary"
|
||||
:icon="h(CheckOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.WX_BAR.code)"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
v-else
|
||||
type="primary"
|
||||
danger
|
||||
:icon="h(CloseOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.WX_BAR.code)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #walletConfig="{ row }">
|
||||
<div>
|
||||
<Button
|
||||
size="small"
|
||||
v-if="isChannelExists(row.channelCodes, PayChannelEnum.WALLET.code)"
|
||||
type="primary"
|
||||
:icon="h(CheckOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.WALLET.code)"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
v-else
|
||||
type="primary"
|
||||
danger
|
||||
:icon="h(CloseOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.WALLET.code)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #mockConfig="{ row }">
|
||||
<div>
|
||||
<Button
|
||||
size="small"
|
||||
v-if="isChannelExists(row.channelCodes, PayChannelEnum.MOCK.code)"
|
||||
type="primary"
|
||||
:icon="h(CheckOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.MOCK.code)"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
v-else
|
||||
type="primary"
|
||||
danger
|
||||
:icon="h(CloseOutlined)"
|
||||
shape="circle"
|
||||
@click="openChannelForm(row, PayChannelEnum.MOCK.code)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<AppModal @reload="tableApi.query()" />
|
||||
<AliPayModal @reload="tableApi.query()" />
|
||||
<MockModal @reload="tableApi.query()" />
|
||||
<WalletModal @reload="tableApi.query()" />
|
||||
<WeixinModal @reload="tableApi.query()" />
|
||||
</Page>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import * as PayApi from '#/api/pay/app';
|
||||
|
||||
import { modalSchema } from '../data';
|
||||
|
||||
const emit = defineEmits<{ reload: [] }>();
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const title = computed(() => {
|
||||
return isUpdate.value
|
||||
? $t('ui.actionTitle.edit', '应用')
|
||||
: $t('ui.actionTitle.create', '应用');
|
||||
});
|
||||
|
||||
const [BasicForm, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
// 默认占满两列
|
||||
formItemClass: 'col-span-2',
|
||||
// 默认label宽度 px
|
||||
labelWidth: 160,
|
||||
// 通用配置项 会影响到所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
schema: modalSchema(),
|
||||
showDefaultActions: false,
|
||||
wrapperClass: 'grid-cols-2',
|
||||
});
|
||||
|
||||
const [BasicModal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
onCancel: handleCancel,
|
||||
onConfirm: handleConfirm,
|
||||
onOpenChange: async (isOpen) => {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
modalApi.modalLoading(true);
|
||||
|
||||
const { id } = modalApi.getData() as {
|
||||
id?: number;
|
||||
};
|
||||
isUpdate.value = !!id;
|
||||
|
||||
if (isUpdate.value && id) {
|
||||
const record = await PayApi.getApp(id);
|
||||
await formApi.setValues(record);
|
||||
}
|
||||
|
||||
modalApi.modalLoading(false);
|
||||
},
|
||||
});
|
||||
|
||||
async function handleConfirm() {
|
||||
try {
|
||||
modalApi.modalLoading(true);
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
|
||||
const data = cloneDeep(await formApi.getValues()) as PayApi.PayAppApi.App;
|
||||
await (isUpdate.value ? PayApi.updateApp(data) : PayApi.createApp(data));
|
||||
emit('reload');
|
||||
await handleCancel();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
modalApi.modalLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCancel() {
|
||||
modalApi.close();
|
||||
await formApi.resetForm();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<BasicModal :close-on-click-modal="false" :title="title" class="w-[40%]">
|
||||
<BasicForm />
|
||||
</BasicModal>
|
||||
</template>
|
|
@ -0,0 +1,163 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { Row, Space, Textarea } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import * as ChannelApi from '#/api/pay/channel';
|
||||
import { FileUpload } from '#/components/upload';
|
||||
|
||||
import { modalAliPaySchema } from './data';
|
||||
|
||||
const emit = defineEmits<{ reload: [] }>();
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const title = computed(() => {
|
||||
return isUpdate.value
|
||||
? $t('ui.actionTitle.edit', '应用')
|
||||
: $t('ui.actionTitle.create', '应用');
|
||||
});
|
||||
|
||||
const [BasicForm, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
// 默认占满两列
|
||||
formItemClass: 'col-span-2',
|
||||
// 默认label宽度 px
|
||||
labelWidth: 160,
|
||||
// 通用配置项 会影响到所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
schema: modalAliPaySchema(),
|
||||
showDefaultActions: false,
|
||||
wrapperClass: 'grid-cols-2',
|
||||
});
|
||||
|
||||
const [BasicModal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
onCancel: handleCancel,
|
||||
onConfirm: handleConfirm,
|
||||
onOpenChange: async (isOpen) => {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
modalApi.modalLoading(true);
|
||||
|
||||
const { id, payCode } = modalApi.getData() as {
|
||||
id?: number;
|
||||
payCode?: string;
|
||||
};
|
||||
|
||||
if (id && payCode) {
|
||||
const record = await ChannelApi.getChannel(id, payCode);
|
||||
isUpdate.value = !!record;
|
||||
record.code = payCode;
|
||||
if (isUpdate.value) {
|
||||
record.config = JSON.parse(record.config);
|
||||
await formApi.setValues(record);
|
||||
}
|
||||
}
|
||||
|
||||
modalApi.modalLoading(false);
|
||||
},
|
||||
});
|
||||
|
||||
async function handleConfirm() {
|
||||
try {
|
||||
modalApi.modalLoading(true);
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
|
||||
const data = cloneDeep(
|
||||
await formApi.getValues(),
|
||||
) as ChannelApi.PayChannelApi.Channel;
|
||||
data.config = JSON.stringify(data.config);
|
||||
await (isUpdate.value
|
||||
? ChannelApi.updateChannel(data)
|
||||
: ChannelApi.createChannel(data));
|
||||
emit('reload');
|
||||
await handleCancel();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
modalApi.modalLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCancel() {
|
||||
modalApi.close();
|
||||
await formApi.resetForm();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<BasicModal :close-on-click-modal="false" :title="title" class="w-[40%]">
|
||||
<BasicForm>
|
||||
<template #appCertContent="slotProps">
|
||||
<Space style="width: 100%" direction="vertical">
|
||||
<Row>
|
||||
<Textarea
|
||||
v-bind="slotProps"
|
||||
:rows="8"
|
||||
placeholder="请上传商户公钥应用证书"
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<FileUpload
|
||||
:accept="['crt']"
|
||||
@return-text="
|
||||
(text: string) => {
|
||||
slotProps.setValue(text);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</Row>
|
||||
</Space>
|
||||
</template>
|
||||
<template #alipayPublicCertContent="slotProps">
|
||||
<Space style="width: 100%" direction="vertical">
|
||||
<Row>
|
||||
<Textarea
|
||||
v-bind="slotProps"
|
||||
:rows="8"
|
||||
placeholder="请上传支付宝公钥证书"
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<FileUpload
|
||||
:accept="['.crt']"
|
||||
@return-text="
|
||||
(text: string) => {
|
||||
slotProps.setValue(text);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</Row>
|
||||
</Space>
|
||||
</template>
|
||||
<template #rootCertContent="slotProps">
|
||||
<Space style="width: 100%" direction="vertical">
|
||||
<Row>
|
||||
<Textarea v-bind="slotProps" :rows="8" placeholder="请上传根证书" />
|
||||
</Row>
|
||||
<Row>
|
||||
<FileUpload
|
||||
:accept="['.crt']"
|
||||
@return-text="
|
||||
(text: string) => {
|
||||
slotProps.setValue(text);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</Row>
|
||||
</Space>
|
||||
</template>
|
||||
</BasicForm>
|
||||
</BasicModal>
|
||||
</template>
|
|
@ -0,0 +1,456 @@
|
|||
import type { FormSchemaGetter } from '#/adapter/form';
|
||||
|
||||
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
|
||||
|
||||
export const modalAliPaySchema: FormSchemaGetter = () => [
|
||||
{
|
||||
label: '商户编号',
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '应用编号',
|
||||
fieldName: 'appId',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '渠道编码',
|
||||
fieldName: 'code',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '渠道费率',
|
||||
fieldName: 'feeRate',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入渠道费率',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '开放平台 APPID',
|
||||
fieldName: 'config.appId',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入开放平台 APPID',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '渠道状态',
|
||||
fieldName: 'status',
|
||||
component: 'RadioGroup',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||
},
|
||||
defaultValue: 1,
|
||||
},
|
||||
{
|
||||
label: '网关地址',
|
||||
fieldName: 'config.serverUrl',
|
||||
component: 'RadioGroup',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
value: 'https://openapi.alipay.com/gateway.do',
|
||||
label: '线上环境',
|
||||
},
|
||||
{
|
||||
value: 'https://openapi-sandbox.dl.alipaydev.com/gateway.do',
|
||||
label: '沙箱环境',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '算法类型',
|
||||
fieldName: 'config.signType',
|
||||
component: 'RadioGroup',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
value: 'RSA2',
|
||||
label: 'RSA2',
|
||||
},
|
||||
],
|
||||
},
|
||||
defaultValue: 'RSA2',
|
||||
},
|
||||
{
|
||||
label: '公钥类型',
|
||||
fieldName: 'config.mode',
|
||||
component: 'RadioGroup',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
value: 0,
|
||||
label: '公钥模式',
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
label: '证书模式',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '应用私钥',
|
||||
fieldName: 'config.privateKey',
|
||||
component: 'Textarea',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入应用私钥',
|
||||
rows: 8,
|
||||
},
|
||||
dependencies: {
|
||||
show(values) {
|
||||
return values.config.mode !== undefined;
|
||||
},
|
||||
triggerFields: ['config'],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '支付宝公钥',
|
||||
fieldName: 'config.alipayPublicKey',
|
||||
component: 'Textarea',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入支付宝公钥',
|
||||
rows: 8,
|
||||
},
|
||||
dependencies: {
|
||||
show(values) {
|
||||
return values?.config?.mode === 0;
|
||||
},
|
||||
triggerFields: ['config.mode', 'mode', 'config'],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '商户公钥应用证书',
|
||||
fieldName: 'config.appCertContent',
|
||||
slotName: 'appCertContent',
|
||||
component: 'Textarea',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请上传商户公钥应用证书',
|
||||
rows: 8,
|
||||
},
|
||||
dependencies: {
|
||||
show(values) {
|
||||
return values?.config?.mode === 1;
|
||||
},
|
||||
triggerFields: ['config.mode', 'mode', 'config'],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '支付宝公钥证书',
|
||||
fieldName: 'config.alipayPublicCertContent',
|
||||
slotName: 'alipayPublicCertContent',
|
||||
component: 'Textarea',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请上传支付宝公钥证书',
|
||||
rows: 8,
|
||||
},
|
||||
dependencies: {
|
||||
show(values) {
|
||||
return values?.config?.mode === 1;
|
||||
},
|
||||
triggerFields: ['config.mode', 'mode', 'config'],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '根证书',
|
||||
fieldName: 'config.rootCertContent',
|
||||
slotName: 'rootCertContent',
|
||||
component: 'Textarea',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请上传根证书',
|
||||
rows: 8,
|
||||
},
|
||||
dependencies: {
|
||||
show(values) {
|
||||
return values?.config?.mode === 1;
|
||||
},
|
||||
triggerFields: ['config.mode', 'mode', 'config'],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '接口内容加密方式',
|
||||
fieldName: 'config.encryptType',
|
||||
component: 'RadioGroup',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
value: 'NONE',
|
||||
label: '无加密',
|
||||
},
|
||||
{
|
||||
value: 'AES',
|
||||
label: 'AES',
|
||||
},
|
||||
],
|
||||
},
|
||||
defaultValue: 'NONE',
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
fieldName: 'remark',
|
||||
component: 'Textarea',
|
||||
componentProps: {
|
||||
rows: 3,
|
||||
placeholder: '请输入备注',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const modalMockSchema: FormSchemaGetter = () => [
|
||||
{
|
||||
label: '商户编号',
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '应用编号',
|
||||
fieldName: 'appId',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '渠道状态',
|
||||
fieldName: 'status',
|
||||
component: 'RadioGroup',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||
},
|
||||
defaultValue: 1,
|
||||
},
|
||||
{
|
||||
label: '渠道编码',
|
||||
fieldName: 'code',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '渠道费率',
|
||||
fieldName: 'feeRate',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入渠道费率',
|
||||
},
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
fieldName: 'remark',
|
||||
component: 'Textarea',
|
||||
componentProps: {
|
||||
rows: 3,
|
||||
placeholder: '请输入备注',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const modalWeixinSchema: FormSchemaGetter = () => [
|
||||
{
|
||||
label: '商户编号',
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '应用编号',
|
||||
fieldName: 'appId',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '渠道编码',
|
||||
fieldName: 'code',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '渠道费率',
|
||||
fieldName: 'feeRate',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入渠道费率',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '微信 APPID',
|
||||
fieldName: 'config.appId',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入微信 APPID',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '商户号',
|
||||
fieldName: 'config.mchId',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入商户号',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '渠道状态',
|
||||
fieldName: 'status',
|
||||
component: 'RadioGroup',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||
},
|
||||
defaultValue: 1,
|
||||
},
|
||||
{
|
||||
label: 'API 版本',
|
||||
fieldName: 'config.apiVersion',
|
||||
component: 'RadioGroup',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: 'v2',
|
||||
value: 'v2',
|
||||
},
|
||||
{
|
||||
label: 'v3',
|
||||
value: 'v3',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '商户密钥',
|
||||
fieldName: 'config.mchKey',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入商户密钥',
|
||||
},
|
||||
dependencies: {
|
||||
show(values) {
|
||||
return values?.config?.apiVersion === 'v2';
|
||||
},
|
||||
triggerFields: ['config.mode', 'mode', 'config'],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'apiclient_cert.p12 证书',
|
||||
fieldName: 'config.keyContent',
|
||||
slotName: 'keyContent',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请上传 apiclient_cert.p12 证书',
|
||||
},
|
||||
dependencies: {
|
||||
show(values) {
|
||||
return values?.config?.apiVersion === 'v2';
|
||||
},
|
||||
triggerFields: ['config.mode', 'mode', 'config'],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'API V3 密钥',
|
||||
fieldName: 'config.apiV3Key',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入 API V3 密钥',
|
||||
},
|
||||
dependencies: {
|
||||
show(values) {
|
||||
return values?.config?.apiVersion === 'v3';
|
||||
},
|
||||
triggerFields: ['config.mode', 'mode', 'config'],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'apiclient_key.pem 证书',
|
||||
fieldName: 'config.privateKeyContent',
|
||||
slotName: 'privateKeyContent',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请上传 apiclient_key.pem 证书',
|
||||
},
|
||||
dependencies: {
|
||||
show(values) {
|
||||
return values?.config?.apiVersion === 'v3';
|
||||
},
|
||||
triggerFields: ['config.mode', 'mode', 'config'],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '证书序列号',
|
||||
fieldName: 'config.certSerialNo',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入证书序列号',
|
||||
},
|
||||
dependencies: {
|
||||
show(values) {
|
||||
return values?.config?.apiVersion === 'v3';
|
||||
},
|
||||
triggerFields: ['config.mode', 'mode', 'config'],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
fieldName: 'remark',
|
||||
component: 'Textarea',
|
||||
componentProps: {
|
||||
rows: 3,
|
||||
placeholder: '请输入备注',
|
||||
},
|
||||
},
|
||||
];
|
|
@ -0,0 +1,105 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import * as ChannelApi from '#/api/pay/channel';
|
||||
|
||||
import { modalMockSchema } from './data';
|
||||
|
||||
const emit = defineEmits<{ reload: [] }>();
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const title = computed(() => {
|
||||
return isUpdate.value
|
||||
? $t('ui.actionTitle.edit', '应用')
|
||||
: $t('ui.actionTitle.create', '应用');
|
||||
});
|
||||
|
||||
const [BasicForm, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
// 默认占满两列
|
||||
formItemClass: 'col-span-2',
|
||||
// 默认label宽度 px
|
||||
labelWidth: 160,
|
||||
// 通用配置项 会影响到所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
schema: modalMockSchema(),
|
||||
showDefaultActions: false,
|
||||
wrapperClass: 'grid-cols-2',
|
||||
});
|
||||
|
||||
const [BasicModal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
onCancel: handleCancel,
|
||||
onConfirm: handleConfirm,
|
||||
onOpenChange: async (isOpen) => {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
modalApi.modalLoading(true);
|
||||
|
||||
const { id, payCode } = modalApi.getData() as {
|
||||
id?: number;
|
||||
payCode?: string;
|
||||
};
|
||||
|
||||
if (id && payCode) {
|
||||
let record = await ChannelApi.getChannel(id, payCode);
|
||||
isUpdate.value = !!record;
|
||||
if (isUpdate.value) {
|
||||
record.config = JSON.parse(record.config);
|
||||
} else {
|
||||
record = {
|
||||
feeRate: 0,
|
||||
code: payCode,
|
||||
appId: id,
|
||||
} as ChannelApi.PayChannelApi.Channel;
|
||||
}
|
||||
await formApi.setValues(record);
|
||||
}
|
||||
|
||||
modalApi.modalLoading(false);
|
||||
},
|
||||
});
|
||||
|
||||
async function handleConfirm() {
|
||||
try {
|
||||
modalApi.modalLoading(true);
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
|
||||
const data = cloneDeep(
|
||||
await formApi.getValues(),
|
||||
) as ChannelApi.PayChannelApi.Channel;
|
||||
data.config = JSON.stringify(data.config || { name: 'mock-conf' });
|
||||
await (isUpdate.value
|
||||
? ChannelApi.updateChannel(data)
|
||||
: ChannelApi.createChannel(data));
|
||||
emit('reload');
|
||||
await handleCancel();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
modalApi.modalLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCancel() {
|
||||
modalApi.close();
|
||||
await formApi.resetForm();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<BasicModal :close-on-click-modal="false" :title="title" class="w-[40%]">
|
||||
<BasicForm />
|
||||
</BasicModal>
|
||||
</template>
|
|
@ -0,0 +1,105 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import * as ChannelApi from '#/api/pay/channel';
|
||||
|
||||
import { modalMockSchema } from './data';
|
||||
|
||||
const emit = defineEmits<{ reload: [] }>();
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const title = computed(() => {
|
||||
return isUpdate.value
|
||||
? $t('ui.actionTitle.edit', '应用')
|
||||
: $t('ui.actionTitle.create', '应用');
|
||||
});
|
||||
|
||||
const [BasicForm, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
// 默认占满两列
|
||||
formItemClass: 'col-span-2',
|
||||
// 默认label宽度 px
|
||||
labelWidth: 160,
|
||||
// 通用配置项 会影响到所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
schema: modalMockSchema(),
|
||||
showDefaultActions: false,
|
||||
wrapperClass: 'grid-cols-2',
|
||||
});
|
||||
|
||||
const [BasicModal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
onCancel: handleCancel,
|
||||
onConfirm: handleConfirm,
|
||||
onOpenChange: async (isOpen) => {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
modalApi.modalLoading(true);
|
||||
|
||||
const { id, payCode } = modalApi.getData() as {
|
||||
id?: number;
|
||||
payCode?: string;
|
||||
};
|
||||
|
||||
if (id && payCode) {
|
||||
let record = await ChannelApi.getChannel(id, payCode);
|
||||
isUpdate.value = !!record;
|
||||
if (isUpdate.value) {
|
||||
record.config = JSON.parse(record.config);
|
||||
} else {
|
||||
record = {
|
||||
feeRate: 0,
|
||||
code: payCode,
|
||||
appId: id,
|
||||
} as ChannelApi.PayChannelApi.Channel;
|
||||
}
|
||||
await formApi.setValues(record);
|
||||
}
|
||||
|
||||
modalApi.modalLoading(false);
|
||||
},
|
||||
});
|
||||
|
||||
async function handleConfirm() {
|
||||
try {
|
||||
modalApi.modalLoading(true);
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
|
||||
const data = cloneDeep(
|
||||
await formApi.getValues(),
|
||||
) as ChannelApi.PayChannelApi.Channel;
|
||||
data.config = JSON.stringify(data.config || { name: 'mock-conf' });
|
||||
await (isUpdate.value
|
||||
? ChannelApi.updateChannel(data)
|
||||
: ChannelApi.createChannel(data));
|
||||
emit('reload');
|
||||
await handleCancel();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
modalApi.modalLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCancel() {
|
||||
modalApi.close();
|
||||
await formApi.resetForm();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<BasicModal :close-on-click-modal="false" :title="title" class="w-[40%]">
|
||||
<BasicForm />
|
||||
</BasicModal>
|
||||
</template>
|
|
@ -0,0 +1,148 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { Row, Space, Textarea } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import * as ChannelApi from '#/api/pay/channel';
|
||||
import { FileUpload } from '#/components/upload';
|
||||
|
||||
import { modalWeixinSchema } from './data';
|
||||
|
||||
const emit = defineEmits<{ reload: [] }>();
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const title = computed(() => {
|
||||
return isUpdate.value
|
||||
? $t('ui.actionTitle.edit', '应用')
|
||||
: $t('ui.actionTitle.create', '应用');
|
||||
});
|
||||
|
||||
const [BasicForm, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
// 默认占满两列
|
||||
formItemClass: 'col-span-2',
|
||||
// 默认label宽度 px
|
||||
labelWidth: 160,
|
||||
// 通用配置项 会影响到所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
schema: modalWeixinSchema(),
|
||||
showDefaultActions: false,
|
||||
wrapperClass: 'grid-cols-2',
|
||||
});
|
||||
|
||||
const [BasicModal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
onCancel: handleCancel,
|
||||
onConfirm: handleConfirm,
|
||||
onOpenChange: async (isOpen) => {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
modalApi.modalLoading(true);
|
||||
|
||||
const { id, payCode } = modalApi.getData() as {
|
||||
id?: number;
|
||||
payCode?: string;
|
||||
};
|
||||
|
||||
if (id && payCode) {
|
||||
const record =
|
||||
(await ChannelApi.getChannel(id, payCode)) ||
|
||||
({} as ChannelApi.PayChannelApi.Channel);
|
||||
isUpdate.value = !!record;
|
||||
record.code = payCode;
|
||||
if (isUpdate.value) {
|
||||
record.config = JSON.parse(record.config);
|
||||
}
|
||||
await formApi.setValues(record);
|
||||
}
|
||||
|
||||
modalApi.modalLoading(false);
|
||||
},
|
||||
});
|
||||
|
||||
async function handleConfirm() {
|
||||
try {
|
||||
modalApi.modalLoading(true);
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
|
||||
const data = cloneDeep(
|
||||
await formApi.getValues(),
|
||||
) as ChannelApi.PayChannelApi.Channel;
|
||||
data.config = JSON.stringify(data.config);
|
||||
await (isUpdate.value
|
||||
? ChannelApi.updateChannel(data)
|
||||
: ChannelApi.createChannel(data));
|
||||
emit('reload');
|
||||
await handleCancel();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
modalApi.modalLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCancel() {
|
||||
modalApi.close();
|
||||
await formApi.resetForm();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<BasicModal :close-on-click-modal="false" :title="title" class="w-[40%]">
|
||||
<BasicForm>
|
||||
<template #keyContent="slotProps">
|
||||
<Space style="width: 100%" direction="vertical">
|
||||
<Row>
|
||||
<Textarea
|
||||
v-bind="slotProps"
|
||||
:rows="8"
|
||||
placeholder="请上传 apiclient_cert.p12 证书"
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<FileUpload
|
||||
:accept="['crt']"
|
||||
@return-text="
|
||||
(text: string) => {
|
||||
slotProps.setValue(text);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</Row>
|
||||
</Space>
|
||||
</template>
|
||||
<template #privateKeyContent="slotProps">
|
||||
<Space style="width: 100%" direction="vertical">
|
||||
<Row>
|
||||
<Textarea
|
||||
v-bind="slotProps"
|
||||
:rows="8"
|
||||
placeholder="请上传 apiclient_key.pem 证书"
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<FileUpload
|
||||
:accept="['.crt']"
|
||||
@return-text="
|
||||
(text: string) => {
|
||||
slotProps.setValue(text);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</Row>
|
||||
</Space>
|
||||
</template>
|
||||
</BasicForm>
|
||||
</BasicModal>
|
||||
</template>
|
|
@ -0,0 +1,144 @@
|
|||
import type { FormSchemaGetter } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
|
||||
|
||||
export const querySchema: FormSchemaGetter = () => [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'appId',
|
||||
label: '应用编号',
|
||||
componentProps: {
|
||||
placeholder: '请输入应用编号',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'channelCode',
|
||||
label: '支付渠道',
|
||||
componentProps: {
|
||||
placeholder: '请选择开启状态',
|
||||
options: getDictOptions(DICT_TYPE.PAY_CHANNEL_CODE, 'string'),
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'merchantOrderId',
|
||||
label: '商户单号',
|
||||
componentProps: {
|
||||
placeholder: '请输入商户单号',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'no',
|
||||
label: '支付单号',
|
||||
componentProps: {
|
||||
placeholder: '请输入支付单号',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'channelOrderNo',
|
||||
label: '渠道单号',
|
||||
componentProps: {
|
||||
placeholder: '请输入渠道单号',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'status',
|
||||
label: '支付状态',
|
||||
componentProps: {
|
||||
placeholder: '请选择支付状态',
|
||||
options: getDictOptions(DICT_TYPE.PAY_ORDER_STATUS, 'number'),
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'RangePicker',
|
||||
fieldName: 'createTime',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
placeholder: ['开始日期', '结束日期'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const columns: VxeGridProps['columns'] = [
|
||||
{ type: 'checkbox', width: 60 },
|
||||
{
|
||||
title: '编号',
|
||||
field: 'id',
|
||||
},
|
||||
{
|
||||
title: '支付金额',
|
||||
field: 'price',
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
return `¥${(row.price || 0 / 100).toFixed(2)}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '退款金额',
|
||||
field: 'refundPrice',
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
return `¥${(row.refundPrice || 0 / 100).toFixed(2)}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '手续金额',
|
||||
field: 'channelFeePrice',
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
return `¥${(row.channelFeePrice || 0 / 100).toFixed(2)}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '订单号',
|
||||
field: 'no',
|
||||
slots: {
|
||||
default: 'no',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '支付状态',
|
||||
field: 'status',
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.PAY_ORDER_STATUS },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '支付渠道',
|
||||
field: 'channelCode',
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.PAY_CHANNEL_CODE },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '支付时间',
|
||||
field: 'successTime',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
title: '支付应用',
|
||||
field: 'appName',
|
||||
},
|
||||
{
|
||||
title: '商品标题',
|
||||
field: 'subject',
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
minWidth: 80,
|
||||
},
|
||||
];
|
|
@ -1,13 +1,89 @@
|
|||
<script lang="ts" setup>
|
||||
import { Page } from '@vben/common-ui';
|
||||
import type { VbenFormProps } from '@vben/common-ui';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Tag } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import * as OrderApi from '#/api/pay/order';
|
||||
import { DocAlert } from '#/components/doc-alert';
|
||||
|
||||
import { columns, querySchema } from './data';
|
||||
import detailFrom from './modules/order-detail.vue';
|
||||
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
labelWidth: 100,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
// 处理区间选择器RangePicker时间格式 将一个字段映射为两个字段 搜索/导出会用到
|
||||
// 不需要直接删除
|
||||
// fieldMappingTime: [
|
||||
// [
|
||||
// 'createTime',
|
||||
// ['params[beginTime]', 'params[endTime]'],
|
||||
// ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
|
||||
// ],
|
||||
// ],
|
||||
};
|
||||
|
||||
const gridOptions: VxeGridProps = {
|
||||
checkboxConfig: {
|
||||
// 高亮
|
||||
highlight: true,
|
||||
// 翻页时保留选中状态
|
||||
reserve: true,
|
||||
// 点击行选中
|
||||
// trigger: 'row',
|
||||
},
|
||||
columns,
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
pagerConfig: {},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues = {}) => {
|
||||
return await OrderApi.getOrderPage({
|
||||
pageNum: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
// 表格全局唯一表示 保存列配置需要用到
|
||||
id: 'pay-order-index',
|
||||
};
|
||||
|
||||
const [BasicTable] = useVbenVxeGrid({
|
||||
formOptions,
|
||||
gridOptions,
|
||||
});
|
||||
|
||||
const [DetailModal, modalDetailApi] = useVbenModal({
|
||||
connectedComponent: detailFrom,
|
||||
});
|
||||
|
||||
const openDetail = (id: number) => {
|
||||
modalDetailApi.setData({
|
||||
id,
|
||||
});
|
||||
modalDetailApi.open();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page>
|
||||
<Page :auto-content-height="true">
|
||||
<DocAlert
|
||||
title="支付宝支付接入"
|
||||
url="https://doc.iocoder.cn/pay/alipay-pay-demo/"
|
||||
|
@ -20,23 +96,29 @@ import { DocAlert } from '#/components/doc-alert';
|
|||
title="微信小程序支付接入"
|
||||
url="https://doc.iocoder.cn/pay/wx-lite-pay-demo/"
|
||||
/>
|
||||
<Button
|
||||
danger
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||
>
|
||||
该功能支持 Vue3 + element-plus 版本!
|
||||
</Button>
|
||||
<br />
|
||||
<Button
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/pay/order/index"
|
||||
>
|
||||
可参考
|
||||
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/pay/order/index
|
||||
代码,pull request 贡献给我们!
|
||||
</Button>
|
||||
<BasicTable>
|
||||
<template #action="{ row }">
|
||||
<a-button
|
||||
type="link"
|
||||
v-access:code="['pay:order:query']"
|
||||
@click="openDetail(row.id)"
|
||||
>
|
||||
{{ $t('ui.actionTitle.detail') }}
|
||||
</a-button>
|
||||
</template>
|
||||
<template #no="{ row }">
|
||||
<p class="order-font">
|
||||
<Tag size="small" color="blue"> 商户</Tag> {{ row.merchantOrderId }}
|
||||
</p>
|
||||
<p class="order-font" v-if="row.no">
|
||||
<Tag size="small" color="orange">支付</Tag> {{ row.no }}
|
||||
</p>
|
||||
<p class="order-font" v-if="row.channelOrderNo">
|
||||
<Tag size="small" color="green">渠道</Tag>
|
||||
{{ row.channelOrderNo }}
|
||||
</p>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<DetailModal />
|
||||
</Page>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import { Descriptions, Divider, Tag } from 'ant-design-vue';
|
||||
|
||||
import * as OrderApi from '#/api/pay/order';
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
import { DICT_TYPE } from '#/utils/dict';
|
||||
|
||||
const detailData = ref<OrderApi.PayOrderApi.Order>();
|
||||
|
||||
const [BasicModal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
showCancelButton: false,
|
||||
showConfirmButton: false,
|
||||
onOpenChange: async (isOpen) => {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
modalApi.modalLoading(true);
|
||||
|
||||
const { id } = modalApi.getData() as {
|
||||
id: number;
|
||||
};
|
||||
|
||||
detailData.value = await OrderApi.getOrderDetail(id);
|
||||
|
||||
modalApi.modalLoading(false);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<BasicModal :close-on-click-modal="false" title="订单详情" class="w-[700px]">
|
||||
<Descriptions :column="2">
|
||||
<Descriptions.Item label="商户单号">
|
||||
{{ detailData?.merchantOrderId }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="支付单号">
|
||||
{{ detailData?.no }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="应用编号">
|
||||
{{ detailData?.appId }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="应用名称">
|
||||
{{ detailData?.appName }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="支付状态">
|
||||
<DictTag
|
||||
size="small"
|
||||
:type="DICT_TYPE.PAY_ORDER_STATUS"
|
||||
:value="detailData?.status"
|
||||
/>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="支付金额">
|
||||
<Tag color="green" size="small">
|
||||
¥{{ (detailData?.price || 0 / 100.0).toFixed(2) }}
|
||||
</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="手续费">
|
||||
<Tag color="orange" size="small">
|
||||
¥{{ (detailData?.channelFeePrice || 0 / 100.0).toFixed(2) }}
|
||||
</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="手续费比例">
|
||||
{{ (detailData?.channelFeeRate || 0 / 100.0).toFixed(2) }}%
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="支付时间">
|
||||
{{ formatDateTime(detailData?.successTime) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="失效时间">
|
||||
{{ formatDateTime(detailData?.expireTime) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="创建时间">
|
||||
{{ formatDateTime(detailData?.createTime) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="更新时间">
|
||||
{{ formatDateTime(detailData?.updateTime) }}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
<Divider />
|
||||
<Descriptions :column="2">
|
||||
<Descriptions.Item label="商品标题">
|
||||
{{ detailData?.subject }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="商品描述">
|
||||
{{ detailData?.body }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="支付渠道">
|
||||
<DictTag
|
||||
size="small"
|
||||
:type="DICT_TYPE.PAY_CHANNEL_CODE"
|
||||
:value="detailData?.channelCode"
|
||||
/>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="支付 IP">
|
||||
{{ detailData?.userIp }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="渠道单号">
|
||||
<Tag size="small" color="green" v-if="detailData?.channelOrderNo">
|
||||
{{ detailData?.channelOrderNo }}
|
||||
</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="渠道用户">
|
||||
{{ detailData?.channelUserId }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="退款金额" :span="2">
|
||||
<Tag size="small" color="red">
|
||||
¥{{ (detailData?.refundPrice || 0 / 100.0).toFixed(2) }}
|
||||
</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="通知 URL">
|
||||
{{ detailData?.notifyUrl }}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
<Divider />
|
||||
<Descriptions :column="1">
|
||||
<Descriptions.Item label="支付通道异步回调内容">
|
||||
{{ detailData?.channelNotifyData }}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</BasicModal>
|
||||
</template>
|
|
@ -557,4 +557,16 @@ import { z } from '#/adapter/form';
|
|||
|
||||
除了以上内置插槽之外,`schema`属性中每个字段的`fieldName`都可以作为插槽名称,这些字段插槽的优先级高于`component`定义的组件。也就是说,当提供了与`fieldName`同名的插槽时,这些插槽的内容将会作为这些字段的组件,此时`component`的值将会被忽略。
|
||||
|
||||
如果需要使用自定义的插槽名而不是使用`fieldName`,可以在schema中添加`slotName`属性。当提供了`slotName`属性时,将优先使用`slotName`作为插槽名。
|
||||
|
||||
```ts
|
||||
// 使用自定义插槽名的例子
|
||||
{
|
||||
component: 'Textarea',
|
||||
fieldName: 'config.appCertContent',
|
||||
slotName: 'appCertSlot',
|
||||
label: '商户公钥应用证书',
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
|
|
@ -155,7 +155,11 @@ const computedSchema = computed(
|
|||
:rules="cSchema.rules"
|
||||
>
|
||||
<template #default="slotProps">
|
||||
<slot v-bind="slotProps" :name="cSchema.fieldName"> </slot>
|
||||
<slot
|
||||
v-bind="slotProps"
|
||||
:name="cSchema.slotName || cSchema.fieldName"
|
||||
>
|
||||
</slot>
|
||||
</template>
|
||||
</FormField>
|
||||
</template>
|
||||
|
|
|
@ -263,6 +263,8 @@ export interface FormSchema<
|
|||
renderComponentContent?: RenderComponentContentType;
|
||||
/** 字段规则 */
|
||||
rules?: FormSchemaRuleType;
|
||||
/** 自定义插槽名,如果不指定则使用fieldName */
|
||||
slotName?: string;
|
||||
/** 后缀 */
|
||||
suffix?: CustomRenderType;
|
||||
}
|
||||
|
|
|
@ -124,6 +124,14 @@ export class ModalApi {
|
|||
return this.setState({ submitting: isLocked });
|
||||
}
|
||||
|
||||
modalLoading(loading: boolean) {
|
||||
this.store.setState((prev) => ({
|
||||
...prev,
|
||||
confirmLoading: loading,
|
||||
loading,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消操作
|
||||
*/
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
"detail": "详情{0}",
|
||||
"view": "查看{0}",
|
||||
"import": "导入",
|
||||
"export": "导出"
|
||||
"export": "导出",
|
||||
"detail": "详情"
|
||||
},
|
||||
"actionMessage": {
|
||||
"deleteConfirm": "确定删除 {0} 吗?",
|
||||
|
|
|
@ -8,3 +8,31 @@ export function getPopupContainer(node?: HTMLElement): HTMLElement {
|
|||
node?.closest('form') ?? (node?.parentNode as HTMLElement) ?? document.body
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* VxeTable专用弹窗层
|
||||
* 解决问题: https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IB1DM3
|
||||
* 单表格用法跟上面getPopupContainer一样
|
||||
* 一个页面(body下)有多个表格元素 必须先指定ID & ID参数传入该函数
|
||||
* <BasicTable id="xxx" />
|
||||
* getVxePopupContainer="(node) => getVxePopupContainer(node, 'xxx')"
|
||||
* @param _node 触发的元素
|
||||
* @param id 表格唯一id 当页面(该窗口)有>=两个表格 必须提供ID
|
||||
* @returns 挂载节点
|
||||
*/
|
||||
export function getVxePopupContainer(
|
||||
_node?: HTMLElement,
|
||||
id?: string,
|
||||
): HTMLElement {
|
||||
let selector = 'div.vxe-table--body-wrapper.body--wrapper';
|
||||
if (id) {
|
||||
selector = `div#${id} ${selector}`;
|
||||
}
|
||||
// 挂载到vxe-table的滚动区域
|
||||
const vxeTableContainerNode = document.querySelector(selector);
|
||||
if (!vxeTableContainerNode) {
|
||||
console.warn('无法找到vxe-table元素, 将会挂载到body.');
|
||||
return document.body;
|
||||
}
|
||||
return vxeTableContainerNode as HTMLElement;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@ settings:
|
|||
|
||||
catalogs:
|
||||
default:
|
||||
'@ant-design/icons-vue':
|
||||
specifier: ^7.0.1
|
||||
version: 7.0.1
|
||||
'@changesets/changelog-github':
|
||||
specifier: ^0.5.1
|
||||
version: 0.5.1
|
||||
|
@ -683,6 +686,9 @@ importers:
|
|||
|
||||
apps/web-antd:
|
||||
dependencies:
|
||||
'@ant-design/icons-vue':
|
||||
specifier: 'catalog:'
|
||||
version: 7.0.1(vue@3.5.13(typescript@5.8.3))
|
||||
'@form-create/ant-design-vue':
|
||||
specifier: 'catalog:'
|
||||
version: 3.2.22(vue@3.5.13(typescript@5.8.3))
|
||||
|
|
|
@ -40,6 +40,7 @@ catalog:
|
|||
'@tanstack/vue-store': ^0.7.0
|
||||
'@tinymce/tinymce-vue': ^6.1.0
|
||||
'@form-create/ant-design-vue': ^3.2.22
|
||||
'@ant-design/icons-vue': ^7.0.1
|
||||
'@form-create/antd-designer': ^3.2.11
|
||||
'@form-create/naive-ui': ^3.2.22
|
||||
'@types/archiver': ^6.0.3
|
||||
|
|
Loading…
Reference in New Issue