diff --git a/apps/web-ele/src/components/upload/file-upload.vue b/apps/web-ele/src/components/upload/file-upload.vue index f50e20bd5..39b6d529d 100644 --- a/apps/web-ele/src/components/upload/file-upload.vue +++ b/apps/web-ele/src/components/upload/file-upload.vue @@ -3,12 +3,11 @@ import type { UploadFile, UploadProgressEvent, UploadRequestOptions, + UploadUserFile, } from 'element-plus'; import type { AxiosResponse } from '@vben/request'; -import type { CustomUploadFile } from './typing'; - import type { AxiosProgressEvent } from '#/api/infra/file'; import { ref, toRefs, watch } from 'vue'; @@ -63,7 +62,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(false); const { getStringAccept } = useUploadType({ @@ -73,7 +72,7 @@ const { getStringAccept } = useUploadType({ maxSizeRef: maxSize, }); -const fileList = ref([]); +const fileList = ref([]); const isLtMsg = ref(true); // 文件大小错误提示 const isActMsg = ref(true); // 文件类型错误提示 const isFirstRender = ref(true); // 是否第一次渲染 @@ -100,7 +99,7 @@ watch( name: item.slice(Math.max(0, item.lastIndexOf('/') + 1)), status: UploadResultStatus.DONE, url: item, - } as CustomUploadFile; + } as UploadUserFile; } else if (item && isObject(item)) { const file = item as Record; return { @@ -111,11 +110,11 @@ watch( response: file.response, percentage: file.percentage, size: file.size, - } as CustomUploadFile; + } as UploadUserFile; } return null; }) - .filter(Boolean) as CustomUploadFile[]; + .filter(Boolean) as UploadUserFile[]; } if (!isFirstRender.value) { emit('change', value); @@ -141,6 +140,8 @@ const handleRemove = async (file: UploadFile) => { }; const beforeUpload = async (file: File) => { + const fileContent = await file.text(); + emit('returnText', fileContent); const { maxSize, accept } = props; const isAct = checkFileType(file, accept); if (!isAct) { @@ -175,17 +176,17 @@ async function customRequest(options: UploadRequestOptions) { total: e.total || 0, loaded: e.loaded || 0, lengthComputable: true, - target: e.target as EventTarget, + target: e.event.target as EventTarget, bubbles: false, cancelBubble: false, cancelable: false, composed: false, - currentTarget: e.target as EventTarget, + currentTarget: e.event.target as EventTarget, defaultPrevented: false, eventPhase: 0, isTrusted: true, returnValue: true, - srcElement: e.target as EventTarget, + srcElement: e.event.target as EventTarget, timeStamp: Date.now(), type: 'progress', composedPath: () => [], @@ -193,6 +194,10 @@ async function customRequest(options: UploadRequestOptions) { preventDefault: () => {}, stopImmediatePropagation: () => {}, stopPropagation: () => {}, + NONE: 0, + CAPTURING_PHASE: 1, + AT_TARGET: 2, + BUBBLING_PHASE: 3, }; options.onProgress!(progressEvent); }; diff --git a/apps/web-ele/src/components/upload/index.ts b/apps/web-ele/src/components/upload/index.ts index a66b2fca6..14e57fede 100644 --- a/apps/web-ele/src/components/upload/index.ts +++ b/apps/web-ele/src/components/upload/index.ts @@ -1,2 +1,3 @@ export { default as FileUpload } from './file-upload.vue'; export { default as ImageUpload } from './image-upload.vue'; +export { default as InputUpload } from './input-upload.vue'; diff --git a/apps/web-ele/src/components/upload/input-upload.vue b/apps/web-ele/src/components/upload/input-upload.vue new file mode 100644 index 000000000..c3a1ba96f --- /dev/null +++ b/apps/web-ele/src/components/upload/input-upload.vue @@ -0,0 +1,75 @@ + + diff --git a/apps/web-ele/src/components/upload/typing.ts b/apps/web-ele/src/components/upload/typing.ts index 97ed47749..8f3d5f5cf 100644 --- a/apps/web-ele/src/components/upload/typing.ts +++ b/apps/web-ele/src/components/upload/typing.ts @@ -1,11 +1,17 @@ -import type { UploadStatus } from 'element-plus'; +import type { AxiosResponse } from '@vben/request'; + +import type { AxiosProgressEvent } from '#/api/infra/file'; export type UploadListType = 'picture' | 'picture-card' | 'text'; -export type UploadStatus = 'error' | 'removed' | 'success' | 'uploading'; +export type UploadStatus = + | 'error' + | 'fail' + | 'removed' + | 'success' + | 'uploading'; export enum UploadResultStatus { - DONE = 'success', ERROR = 'error', REMOVED = 'removed', SUCCESS = 'success', @@ -27,11 +33,11 @@ export function convertToUploadStatus( status: UploadResultStatus, ): UploadStatus { switch (status) { - case UploadResultStatus.DONE: { + case UploadResultStatus.SUCCESS: { return 'success'; } case UploadResultStatus.ERROR: { - return 'error'; + return 'fail'; } case UploadResultStatus.REMOVED: { return 'removed'; @@ -44,3 +50,28 @@ export function convertToUploadStatus( } } } + +export interface FileUploadProps { + // 根据后缀,或者其他 + accept?: string[]; + api?: ( + file: File, + onUploadProgress?: AxiosProgressEvent, + ) => Promise>; + // 上传的目录 + directory?: string; + disabled?: boolean; + helpText?: string; + listType?: UploadListType; + // 最大数量的文件,Infinity不限制 + maxNumber?: number; + // 文件最大多少MB + maxSize?: number; + // 是否支持多选 + multiple?: boolean; + // support xxx.xxx.xx + resultField?: string; + // 是否显示下面的描述 + showDescription?: boolean; + value?: string | string[]; +} diff --git a/apps/web-ele/src/views/pay/app/data.ts b/apps/web-ele/src/views/pay/app/data.ts new file mode 100644 index 000000000..d84bd9e94 --- /dev/null +++ b/apps/web-ele/src/views/pay/app/data.ts @@ -0,0 +1,242 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { PayAppApi } from '#/api/pay/app'; + +import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; + +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + 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 function useGridColumns( + onStatusChange?: ( + newStatus: number, + row: T, + ) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 60 }, + { + title: '应用标识', + field: 'appKey', + }, + { + title: '应用名', + field: 'name', + }, + { + field: 'status', + title: '状态', + align: 'center', + cellRender: { + attrs: { beforeChange: onStatusChange }, + name: 'CellSwitch', + props: { + checkedValue: CommonStatusEnum.ENABLE, + unCheckedValue: CommonStatusEnum.DISABLE, + inlinePrompt: true, + }, + }, + }, + { + 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', + }, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + 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: '请输入备注', + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/pay/app/index.vue b/apps/web-ele/src/views/pay/app/index.vue new file mode 100644 index 000000000..89bf83758 --- /dev/null +++ b/apps/web-ele/src/views/pay/app/index.vue @@ -0,0 +1,527 @@ + + + diff --git a/apps/web-ele/src/views/pay/app/modules/app-form.vue b/apps/web-ele/src/views/pay/app/modules/app-form.vue new file mode 100644 index 000000000..4d9419582 --- /dev/null +++ b/apps/web-ele/src/views/pay/app/modules/app-form.vue @@ -0,0 +1,83 @@ + + diff --git a/apps/web-ele/src/views/pay/app/modules/channel-form.vue b/apps/web-ele/src/views/pay/app/modules/channel-form.vue new file mode 100644 index 000000000..732336c71 --- /dev/null +++ b/apps/web-ele/src/views/pay/app/modules/channel-form.vue @@ -0,0 +1,167 @@ + + + diff --git a/apps/web-ele/src/views/pay/app/modules/data.ts b/apps/web-ele/src/views/pay/app/modules/data.ts new file mode 100644 index 000000000..56188df2c --- /dev/null +++ b/apps/web-ele/src/views/pay/app/modules/data.ts @@ -0,0 +1,491 @@ +import type { VbenFormSchema } from '#/adapter/form'; + +import { h } from 'vue'; + +import { InputUpload } from '#/components/upload'; +import { DICT_TYPE, getDictOptions } from '#/utils'; + +export function channelSchema(formType: string): VbenFormSchema[] { + if (formType.includes('alipay_')) { + return [ + { + label: '应用编号', + fieldName: 'appId', + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + }, + { + label: '渠道费率', + fieldName: 'feeRate', + component: 'InputNumber', + rules: 'required', + componentProps: { + placeholder: '请输入渠道费率', + addonAfter: '%', + }, + defaultValue: 0, + }, + { + 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: 0, + }, + { + 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: 1, + label: '公钥模式', + }, + { + value: 2, + label: '证书模式', + }, + ], + }, + }, + { + label: '应用私钥', + fieldName: 'config.privateKey', + component: 'Textarea', + rules: 'required', + componentProps: { + placeholder: '请输入应用私钥', + rows: 8, + }, + }, + { + label: '支付宝公钥', + fieldName: 'config.alipayPublicKey', + component: 'Textarea', + rules: 'required', + componentProps: { + placeholder: '请输入支付宝公钥', + rows: 8, + }, + dependencies: { + show(values) { + return values?.config?.mode === 1; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: '商户公钥应用证书', + fieldName: 'config.appCertContent', + component: h(InputUpload, { + inputType: 'textarea', + textareaProps: { rows: 8, placeholder: '请上传商户公钥应用证书' }, + fileUploadProps: { + accept: ['crt'], + }, + }), + rules: 'required', + dependencies: { + show(values) { + return values?.config?.mode === 2; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: '支付宝公钥证书', + fieldName: 'config.alipayPublicCertContent', + component: h(InputUpload, { + inputType: 'textarea', + textareaProps: { rows: 8, placeholder: '请上传支付宝公钥证书' }, + fileUploadProps: { + accept: ['crt'], + }, + }), + rules: 'required', + dependencies: { + show(values) { + return values?.config?.mode === 2; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: '根证书', + fieldName: 'config.rootCertContent', + component: h(InputUpload, { + inputType: 'textarea', + textareaProps: { rows: 8, placeholder: '请上传根证书' }, + fileUploadProps: { + accept: ['crt'], + }, + }), + rules: 'required', + dependencies: { + show(values) { + return values?.config?.mode === 2; + }, + 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: 'config.encryptKey', + component: 'Input', + rules: 'required', + dependencies: { + show(values) { + return values?.config?.encryptType === 'AES'; + }, + triggerFields: ['config.encryptType', 'encryptType', 'config'], + }, + }, + { + label: '备注', + fieldName: 'remark', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; + } else if (formType.includes('mock') || formType.includes('wallet')) { + return [ + { + 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: 0, + }, + { + label: '渠道编码', + fieldName: 'code', + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + }, + { + label: '渠道费率', + fieldName: 'feeRate', + component: 'InputNumber', + rules: 'required', + componentProps: { + placeholder: '请输入渠道费率', + addonAfter: '%', + }, + defaultValue: 0, + dependencies: { + show: () => false, + triggerFields: [''], + }, + }, + { + label: '备注', + fieldName: 'remark', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; + } else if (formType.includes('wx')) { + return [ + { + label: '应用编号', + fieldName: 'appId', + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + }, + { + label: '渠道编码', + fieldName: 'code', + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + }, + { + label: '渠道费率', + fieldName: 'feeRate', + component: 'InputNumber', + rules: 'required', + componentProps: { + placeholder: '请输入渠道费率', + addonAfter: '%', + }, + defaultValue: 0, + }, + { + label: '微信 APPID', + fieldName: 'config.appId', + help: '前往微信商户平台[https://pay.weixin.qq.com/index.php/extend/merchant_appid/mapay_platform/account_manage]查看 APPID', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入微信 APPID', + }, + }, + { + label: '商户号', + fieldName: 'config.mchId', + help: '前往微信商户平台[https://pay.weixin.qq.com/index.php/extend/pay_setting]查看商户号', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入商户号', + }, + }, + { + label: '渠道状态', + fieldName: 'status', + component: 'RadioGroup', + rules: 'required', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + defaultValue: 0, + }, + { + 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', + component: h(InputUpload, { + inputType: 'textarea', + textareaProps: { + rows: 8, + placeholder: '请上传 apiclient_cert.p12 证书', + }, + fileUploadProps: { + accept: ['p12'], + }, + }), + rules: 'required', + 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', + component: h(InputUpload, { + inputType: 'textarea', + textareaProps: { + rows: 8, + placeholder: '请上传 apiclient_key.pem 证书', + }, + fileUploadProps: { + accept: ['pem'], + }, + }), + rules: 'required', + dependencies: { + show(values) { + return values?.config?.apiVersion === 'v3'; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: '证书序列号', + fieldName: 'config.certSerialNo', + component: 'Input', + help: '前往微信商户平台[https://pay.weixin.qq.com/index.php/core/cert/api_cert#/api-cert-manage]查看证书序列号', + rules: 'required', + componentProps: { + placeholder: '请输入证书序列号', + }, + dependencies: { + show(values) { + return values?.config?.apiVersion === 'v3'; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: 'public_key.pem 证书', + fieldName: 'config.publicKeyContent', + component: h(InputUpload, { + inputType: 'textarea', + textareaProps: { + rows: 8, + placeholder: '请上传 public_key.pem 证书', + }, + fileUploadProps: { + accept: ['pem'], + }, + }), + rules: 'required', + dependencies: { + show(values) { + return values?.config?.apiVersion === 'v3'; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: '公钥 ID', + fieldName: 'config.publicKeyId', + component: 'Input', + help: '微信支付公钥产品简介及使用说明[https://pay.weixin.qq.com/doc/v3/merchant/4012153196]', + rules: 'required', + componentProps: { + placeholder: '请输入公钥 ID', + }, + dependencies: { + show(values) { + return values?.config?.apiVersion === 'v3'; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: '备注', + fieldName: 'remark', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; + } else { + return []; + } +}