1091 lines
33 KiB
Vue
1091 lines
33 KiB
Vue
<template>
|
||
<ArtTableFullScreen>
|
||
<div class="payment-settings-page" id="table-full-screen">
|
||
<!-- 搜索栏 -->
|
||
<ArtSearchBar
|
||
v-model:filter="searchForm"
|
||
:items="searchFormItems"
|
||
label-width="100"
|
||
:show-expand="false"
|
||
@reset="handleReset"
|
||
@search="handleSearch"
|
||
></ArtSearchBar>
|
||
|
||
<ElCard shadow="never" class="art-table-card">
|
||
<!-- 表格头部 -->
|
||
<ArtTableHeader
|
||
:columnList="columnOptions"
|
||
v-model:columns="columnChecks"
|
||
@refresh="handleRefresh"
|
||
>
|
||
<template #left>
|
||
<ElButton
|
||
type="primary"
|
||
@click="showCreateDialog"
|
||
v-if="hasAuth('payment_settings:create')"
|
||
>新增支付配置</ElButton
|
||
>
|
||
</template>
|
||
</ArtTableHeader>
|
||
|
||
<!-- 表格 -->
|
||
<ArtTable
|
||
ref="tableRef"
|
||
row-key="id"
|
||
:loading="loading"
|
||
:data="configList"
|
||
:currentPage="pagination.page"
|
||
:pageSize="pagination.page_size"
|
||
:total="pagination.total"
|
||
:marginTop="10"
|
||
:actions="getActions"
|
||
:actionsWidth="120"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
>
|
||
<template #default>
|
||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||
</template>
|
||
</ArtTable>
|
||
|
||
<!-- 创建/编辑对话框 -->
|
||
<ElDialog
|
||
v-model="dialogVisible"
|
||
:title="dialogType === 'add' ? '新增支付配置' : '编辑支付配置'"
|
||
width="900px"
|
||
:close-on-click-modal="false"
|
||
@closed="handleDialogClosed"
|
||
>
|
||
<ElForm
|
||
ref="formRef"
|
||
:model="form"
|
||
:rules="rules"
|
||
label-width="140px"
|
||
class="config-form"
|
||
>
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="配置名称" prop="name">
|
||
<ElInput v-model="form.name" placeholder="请输入配置名称" />
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12">
|
||
<ElFormItem label="支付渠道类型" prop="provider_type">
|
||
<ElSelect
|
||
v-model="form.provider_type"
|
||
placeholder="请选择支付渠道类型"
|
||
style="width: 100%"
|
||
@change="handleProviderTypeChange"
|
||
>
|
||
<ElOption label="微信直连" value="wechat" />
|
||
<ElOption label="微信直连V2" value="wechat_v2" />
|
||
<ElOption label="富友支付" value="fuiou" />
|
||
</ElSelect>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElFormItem label="配置描述" prop="description">
|
||
<ElInput
|
||
v-model="form.description"
|
||
type="textarea"
|
||
:rows="2"
|
||
placeholder="请输入配置描述"
|
||
/>
|
||
</ElFormItem>
|
||
|
||
<ElDivider content-position="left">
|
||
<span class="divider-title">小程序配置</span>
|
||
</ElDivider>
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="小程序AppID" prop="miniapp_app_id">
|
||
<ElInput v-model="form.miniapp_app_id" placeholder="请输入小程序AppID" />
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12">
|
||
<ElFormItem label="小程序AppSecret" prop="miniapp_app_secret">
|
||
<ElInput
|
||
v-model="form.miniapp_app_secret"
|
||
type="password"
|
||
show-password
|
||
placeholder="请输入小程序AppSecret"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
|
||
<ElDivider content-position="left">
|
||
<span class="divider-title">公众号配置</span>
|
||
</ElDivider>
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="公众号AppID" prop="oa_app_id">
|
||
<ElInput v-model="form.oa_app_id" placeholder="请输入公众号AppID" />
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12">
|
||
<ElFormItem label="公众号AppSecret" prop="oa_app_secret">
|
||
<ElInput
|
||
v-model="form.oa_app_secret"
|
||
type="password"
|
||
show-password
|
||
placeholder="请输入公众号AppSecret"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="公众号Token" prop="oa_token">
|
||
<ElInput
|
||
v-model="form.oa_token"
|
||
type="password"
|
||
show-password
|
||
placeholder="请输入公众号Token"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12">
|
||
<ElFormItem label="公众号AES Key" prop="oa_aes_key">
|
||
<ElInput
|
||
v-model="form.oa_aes_key"
|
||
type="password"
|
||
show-password
|
||
placeholder="请输入公众号AES加密Key"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElFormItem label="OAuth回调地址" prop="oa_oauth_redirect_url">
|
||
<ElInput v-model="form.oa_oauth_redirect_url" placeholder="请输入OAuth回调地址" />
|
||
</ElFormItem>
|
||
|
||
<ElDivider content-position="left">
|
||
<span class="divider-title">支付宝配置</span>
|
||
</ElDivider>
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="支付宝AppID" prop="ali_app_id">
|
||
<ElInput v-model="form.ali_app_id" placeholder="请输入支付宝AppID" />
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12">
|
||
<ElFormItem label="支付过期分钟数" prop="ali_pay_expire_minutes">
|
||
<ElInputNumber
|
||
v-model="form.ali_pay_expire_minutes"
|
||
:min="1"
|
||
:step="1"
|
||
controls-position="right"
|
||
style="width: 100%"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="异步通知地址" prop="ali_notify_url">
|
||
<ElInput v-model="form.ali_notify_url" placeholder="请输入支付宝异步通知地址" />
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12">
|
||
<ElFormItem label="同步跳转地址" prop="ali_return_url">
|
||
<ElInput v-model="form.ali_return_url" placeholder="请输入支付宝同步跳转地址" />
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElFormItem label="生产环境" prop="ali_production">
|
||
<ElSwitch
|
||
v-model="form.ali_production"
|
||
active-text="生产环境"
|
||
inactive-text="测试环境"
|
||
inline-prompt
|
||
/>
|
||
</ElFormItem>
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="应用私钥" prop="ali_private_key">
|
||
<ElInput
|
||
v-model="form.ali_private_key"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请粘贴支付宝应用私钥内容"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12">
|
||
<ElFormItem label="支付宝公钥" prop="ali_public_key">
|
||
<ElInput
|
||
v-model="form.ali_public_key"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请粘贴支付宝公钥内容"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
|
||
<template v-if="form.provider_type === 'wechat' || form.provider_type === 'wechat_v2'">
|
||
<ElDivider content-position="left">
|
||
<span class="divider-title">微信支付配置</span>
|
||
</ElDivider>
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="微信商户号" prop="wx_mch_id">
|
||
<ElInput v-model="form.wx_mch_id" placeholder="请输入微信商户号" />
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12">
|
||
<ElFormItem label="证书序列号" prop="wx_serial_no">
|
||
<ElInput v-model="form.wx_serial_no" placeholder="请输入微信证书序列号" />
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="APIv2密钥" prop="wx_api_v2_key">
|
||
<ElInput
|
||
v-model="form.wx_api_v2_key"
|
||
type="password"
|
||
show-password
|
||
placeholder="请输入微信APIv2密钥"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12">
|
||
<ElFormItem label="APIv3密钥" prop="wx_api_v3_key">
|
||
<ElInput
|
||
v-model="form.wx_api_v3_key"
|
||
type="password"
|
||
show-password
|
||
placeholder="请输入微信APIv3密钥"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElFormItem label="支付回调地址" prop="wx_notify_url">
|
||
<ElInput v-model="form.wx_notify_url" placeholder="请输入微信支付回调地址" />
|
||
</ElFormItem>
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="支付证书" prop="wx_cert_content">
|
||
<ElInput
|
||
v-model="form.wx_cert_content"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请粘贴PEM格式证书内容,保留当前配置可不修改"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12">
|
||
<ElFormItem label="支付密钥" prop="wx_key_content">
|
||
<ElInput
|
||
v-model="form.wx_key_content"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请粘贴PEM格式密钥内容,保留当前配置可不修改"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
</template>
|
||
|
||
<template v-if="form.provider_type === 'fuiou'">
|
||
<ElDivider content-position="left">
|
||
<span class="divider-title">富友支付配置</span>
|
||
</ElDivider>
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="富友API地址" prop="fy_api_url">
|
||
<ElInput v-model="form.fy_api_url" placeholder="请输入富友API地址" />
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12">
|
||
<ElFormItem label="富友机构号" prop="fy_ins_cd">
|
||
<ElInput v-model="form.fy_ins_cd" placeholder="请输入富友机构号" />
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="富友商户号" prop="fy_mchnt_cd">
|
||
<ElInput v-model="form.fy_mchnt_cd" placeholder="请输入富友商户号" />
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12">
|
||
<ElFormItem label="富友终端号" prop="fy_term_id">
|
||
<ElInput v-model="form.fy_term_id" placeholder="请输入富友终端号" />
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElFormItem label="支付回调地址" prop="fy_notify_url">
|
||
<ElInput v-model="form.fy_notify_url" placeholder="请输入富友支付回调地址" />
|
||
</ElFormItem>
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="富友私钥" prop="fy_private_key">
|
||
<ElInput
|
||
v-model="form.fy_private_key"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请粘贴PEM格式私钥内容,保留当前配置可不修改"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12">
|
||
<ElFormItem label="富友公钥" prop="fy_public_key">
|
||
<ElInput
|
||
v-model="form.fy_public_key"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请粘贴PEM格式公钥内容,保留当前配置可不修改"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
</template>
|
||
</ElForm>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||
<ElButton type="primary" @click="handleSubmit" :loading="submitLoading">
|
||
提交
|
||
</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
</ElCard>
|
||
</div>
|
||
</ArtTableFullScreen>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { h, nextTick, onMounted, reactive, ref } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { PaymentSettingsService } from '@/api/modules'
|
||
import { ElMessage, ElMessageBox, ElSwitch, ElTag } from 'element-plus'
|
||
import type { FormInstance, FormRules } from 'element-plus'
|
||
import type {
|
||
CreatePaymentSettingsRequest,
|
||
PaymentProviderType,
|
||
UpdatePaymentSettingsRequest,
|
||
PaymentSettings,
|
||
PaymentSettingsQueryParams
|
||
} from '@/types/api'
|
||
import type { SearchFormItem } from '@/types'
|
||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||
import { useAuth } from '@/composables/useAuth'
|
||
import { RoutesAlias } from '@/router/routesAlias'
|
||
import { formatDateTime } from '@/utils/business/format'
|
||
import {
|
||
getPaymentMerchantId,
|
||
getPaymentNotifyUrl,
|
||
getPaymentProviderText,
|
||
isMaskedPaymentConfigValue
|
||
} from '@/utils/business/paymentConfig'
|
||
|
||
defineOptions({ name: 'PaymentSettingsList' })
|
||
|
||
type PaymentConfigFormModel = {
|
||
id: number
|
||
name: string
|
||
provider_type: PaymentProviderType
|
||
description: string
|
||
miniapp_app_id: string
|
||
miniapp_app_secret: string
|
||
oa_app_id: string
|
||
oa_app_secret: string
|
||
oa_token: string
|
||
oa_aes_key: string
|
||
oa_oauth_redirect_url: string
|
||
wx_mch_id: string
|
||
wx_api_v2_key: string
|
||
wx_api_v3_key: string
|
||
wx_notify_url: string
|
||
wx_serial_no: string
|
||
wx_cert_content: string
|
||
wx_key_content: string
|
||
ali_app_id: string
|
||
ali_private_key: string
|
||
ali_public_key: string
|
||
ali_notify_url: string
|
||
ali_return_url: string
|
||
ali_pay_expire_minutes: number
|
||
ali_production: boolean
|
||
fy_api_url: string
|
||
fy_ins_cd: string
|
||
fy_mchnt_cd: string
|
||
fy_term_id: string
|
||
fy_notify_url: string
|
||
fy_private_key: string
|
||
fy_public_key: string
|
||
}
|
||
|
||
const textFieldKeys = [
|
||
'description',
|
||
'miniapp_app_id',
|
||
'oa_app_id',
|
||
'oa_oauth_redirect_url',
|
||
'wx_mch_id',
|
||
'wx_notify_url',
|
||
'wx_serial_no',
|
||
'ali_app_id',
|
||
'ali_notify_url',
|
||
'ali_return_url',
|
||
'fy_api_url',
|
||
'fy_ins_cd',
|
||
'fy_mchnt_cd',
|
||
'fy_term_id',
|
||
'fy_notify_url'
|
||
] as const
|
||
|
||
const sensitiveFieldKeys = [
|
||
'miniapp_app_secret',
|
||
'oa_app_secret',
|
||
'oa_token',
|
||
'oa_aes_key',
|
||
'wx_api_v2_key',
|
||
'wx_api_v3_key',
|
||
'wx_cert_content',
|
||
'wx_key_content',
|
||
'ali_private_key',
|
||
'ali_public_key',
|
||
'fy_private_key',
|
||
'fy_public_key'
|
||
] as const
|
||
|
||
const { hasAuth } = useAuth()
|
||
const router = useRouter()
|
||
const canUpdatePaymentSettingsStatus = hasAuth('payment_settings:status')
|
||
|
||
const loading = ref(false)
|
||
const submitLoading = ref(false)
|
||
const tableRef = ref()
|
||
const dialogVisible = ref(false)
|
||
const dialogType = ref<'add' | 'edit'>('add')
|
||
const formRef = ref<FormInstance>()
|
||
const configList = ref<PaymentSettings[]>([])
|
||
const originalFormState = ref<PaymentConfigFormModel | null>(null)
|
||
|
||
const createInitialFormState = (): PaymentConfigFormModel => ({
|
||
id: 0,
|
||
name: '',
|
||
provider_type: 'wechat',
|
||
description: '',
|
||
miniapp_app_id: '',
|
||
miniapp_app_secret: '',
|
||
oa_app_id: '',
|
||
oa_app_secret: '',
|
||
oa_token: '',
|
||
oa_aes_key: '',
|
||
oa_oauth_redirect_url: '',
|
||
wx_mch_id: '',
|
||
wx_api_v2_key: '',
|
||
wx_api_v3_key: '',
|
||
wx_notify_url: '',
|
||
wx_serial_no: '',
|
||
wx_cert_content: '',
|
||
wx_key_content: '',
|
||
ali_app_id: '',
|
||
ali_private_key: '',
|
||
ali_public_key: '',
|
||
ali_notify_url: '',
|
||
ali_return_url: '',
|
||
ali_pay_expire_minutes: 30,
|
||
ali_production: true,
|
||
fy_api_url: '',
|
||
fy_ins_cd: '',
|
||
fy_mchnt_cd: '',
|
||
fy_term_id: '',
|
||
fy_notify_url: '',
|
||
fy_private_key: '',
|
||
fy_public_key: ''
|
||
})
|
||
|
||
const mapConfigToForm = (detail: PaymentSettings): PaymentConfigFormModel => ({
|
||
...createInitialFormState(),
|
||
...detail,
|
||
description: detail.description || '',
|
||
ali_pay_expire_minutes: detail.ali_pay_expire_minutes ?? 30,
|
||
ali_production: detail.ali_production ?? true
|
||
})
|
||
|
||
const trimString = (value: string): string => value.trim()
|
||
const normalizeOptionalString = (value: string): string | undefined => {
|
||
const trimmed = trimString(value)
|
||
return trimmed || undefined
|
||
}
|
||
|
||
const form = reactive<PaymentConfigFormModel>(createInitialFormState())
|
||
|
||
const isWechatProvider = (): boolean =>
|
||
form.provider_type === 'wechat' || form.provider_type === 'wechat_v2'
|
||
|
||
const getStatusText = (value: boolean): string => (value ? '已激活' : '未激活')
|
||
|
||
const requiredValidator =
|
||
(shouldRequire: () => boolean, label: string) =>
|
||
(_rule: unknown, value: unknown, callback: (error?: Error) => void) => {
|
||
if (!shouldRequire()) {
|
||
callback()
|
||
return
|
||
}
|
||
|
||
if (typeof value === 'string') {
|
||
if (!trimString(value)) {
|
||
callback(new Error(`请输入${label}`))
|
||
return
|
||
}
|
||
callback()
|
||
return
|
||
}
|
||
|
||
if (value === undefined || value === null || value === '') {
|
||
callback(new Error(`请输入${label}`))
|
||
return
|
||
}
|
||
|
||
callback()
|
||
}
|
||
|
||
const urlValidator = (_rule: unknown, value: unknown, callback: (error?: Error) => void) => {
|
||
if (!value || typeof value !== 'string') {
|
||
callback()
|
||
return
|
||
}
|
||
|
||
const urlPattern = /^(https?:\/\/)[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:/~+#]*[\w\-@?^=%&/~+#])?$/
|
||
if (!urlPattern.test(value)) {
|
||
callback(new Error('请输入正确的URL格式(http://或https://)'))
|
||
return
|
||
}
|
||
|
||
callback()
|
||
}
|
||
|
||
const expireMinutesValidator = (
|
||
_rule: unknown,
|
||
value: unknown,
|
||
callback: (error?: Error) => void
|
||
) => {
|
||
if (typeof value !== 'number' || Number.isNaN(value) || value < 1) {
|
||
callback(new Error('支付过期分钟数必须大于 0'))
|
||
return
|
||
}
|
||
|
||
callback()
|
||
}
|
||
|
||
const rules = reactive<FormRules>({
|
||
name: [{ required: true, message: '请输入配置名称', trigger: 'blur' }],
|
||
provider_type: [{ required: true, message: '请选择支付渠道类型', trigger: 'change' }],
|
||
wx_mch_id: [{ validator: requiredValidator(isWechatProvider, '微信商户号'), trigger: 'blur' }],
|
||
wx_api_v3_key: [
|
||
{
|
||
validator: requiredValidator(() => form.provider_type === 'wechat', '微信APIv3密钥'),
|
||
trigger: 'blur'
|
||
}
|
||
],
|
||
wx_serial_no: [
|
||
{ validator: requiredValidator(isWechatProvider, '证书序列号'), trigger: 'blur' }
|
||
],
|
||
wx_cert_content: [
|
||
{ validator: requiredValidator(isWechatProvider, '支付证书内容'), trigger: 'blur' }
|
||
],
|
||
wx_key_content: [
|
||
{ validator: requiredValidator(isWechatProvider, '支付密钥内容'), trigger: 'blur' }
|
||
],
|
||
oa_oauth_redirect_url: [{ validator: urlValidator, trigger: 'blur' }],
|
||
wx_notify_url: [{ validator: urlValidator, trigger: 'blur' }],
|
||
ali_notify_url: [{ validator: urlValidator, trigger: 'blur' }],
|
||
ali_return_url: [{ validator: urlValidator, trigger: 'blur' }],
|
||
fy_api_url: [{ validator: urlValidator, trigger: 'blur' }],
|
||
fy_notify_url: [{ validator: urlValidator, trigger: 'blur' }],
|
||
ali_pay_expire_minutes: [{ validator: expireMinutesValidator, trigger: 'change' }]
|
||
})
|
||
|
||
const initialSearchState = {
|
||
provider_type: undefined as PaymentProviderType | undefined,
|
||
is_active: undefined as number | undefined
|
||
}
|
||
|
||
const searchForm = reactive({ ...initialSearchState })
|
||
|
||
const searchFormItems: SearchFormItem[] = [
|
||
{
|
||
label: '支付渠道类型',
|
||
prop: 'provider_type',
|
||
type: 'select',
|
||
placeholder: '请选择支付渠道类型',
|
||
options: [
|
||
{ label: '微信直连', value: 'wechat' },
|
||
{ label: '微信直连V2', value: 'wechat_v2' },
|
||
{ label: '富友支付', value: 'fuiou' }
|
||
],
|
||
config: {
|
||
clearable: true
|
||
}
|
||
},
|
||
{
|
||
label: '激活状态',
|
||
prop: 'is_active',
|
||
type: 'select',
|
||
placeholder: '请选择激活状态',
|
||
options: [
|
||
{ label: '已激活', value: 1 },
|
||
{ label: '未激活', value: 0 }
|
||
],
|
||
config: {
|
||
clearable: true
|
||
}
|
||
}
|
||
]
|
||
|
||
const pagination = reactive({
|
||
page: 1,
|
||
page_size: 20,
|
||
total: 0
|
||
})
|
||
|
||
const columnOptions = [
|
||
{ label: '配置名称', prop: 'name' },
|
||
{ label: '配置描述', prop: 'description' },
|
||
{ label: '支付渠道类型', prop: 'provider_type' },
|
||
{ label: '激活状态', prop: 'is_active' },
|
||
{ label: '商户号', prop: 'merchant_id' },
|
||
{ label: '小程序AppID', prop: 'miniapp_app_id' },
|
||
{ label: '公众号AppID', prop: 'oa_app_id' },
|
||
{ label: '支付宝AppID', prop: 'ali_app_id' },
|
||
{ label: '支付回调地址', prop: 'notify_url' },
|
||
{ label: '创建时间', prop: 'created_at' },
|
||
{ label: '更新时间', prop: 'updated_at' }
|
||
]
|
||
|
||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||
{
|
||
prop: 'name',
|
||
label: '配置名称',
|
||
minWidth: 160,
|
||
showOverflowTooltip: true,
|
||
formatter: (row: PaymentSettings) => {
|
||
return h(
|
||
'span',
|
||
{
|
||
style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;',
|
||
onClick: (e: MouseEvent) => {
|
||
e.stopPropagation()
|
||
handleNameClick(row)
|
||
}
|
||
},
|
||
row.name
|
||
)
|
||
}
|
||
},
|
||
{
|
||
prop: 'description',
|
||
label: '配置描述',
|
||
minWidth: 180,
|
||
showOverflowTooltip: true,
|
||
formatter: (row: PaymentSettings) => row.description || '-'
|
||
},
|
||
{
|
||
prop: 'provider_type',
|
||
label: '支付渠道类型',
|
||
width: 130,
|
||
formatter: (row: PaymentSettings) => getPaymentProviderText(row.provider_type)
|
||
},
|
||
{
|
||
prop: 'is_active',
|
||
label: '激活状态',
|
||
width: 110,
|
||
formatter: (row: PaymentSettings) => {
|
||
if (canUpdatePaymentSettingsStatus) {
|
||
return h(ElSwitch, {
|
||
modelValue: row.is_active,
|
||
activeText: '已激活',
|
||
inactiveText: '未激活',
|
||
inlinePrompt: true,
|
||
'onUpdate:modelValue': (val) => handleStatusChange(row, val)
|
||
})
|
||
}
|
||
|
||
return h(ElTag, { type: row.is_active ? 'success' : 'info' }, () =>
|
||
getStatusText(row.is_active)
|
||
)
|
||
}
|
||
},
|
||
{
|
||
prop: 'merchant_id',
|
||
label: '商户号',
|
||
width: 150,
|
||
showOverflowTooltip: true,
|
||
formatter: (row: PaymentSettings) => getPaymentMerchantId(row)
|
||
},
|
||
{
|
||
prop: 'miniapp_app_id',
|
||
label: '小程序AppID',
|
||
width: 160,
|
||
showOverflowTooltip: true,
|
||
formatter: (row: PaymentSettings) => row.miniapp_app_id || '-'
|
||
},
|
||
{
|
||
prop: 'oa_app_id',
|
||
label: '公众号AppID',
|
||
width: 160,
|
||
showOverflowTooltip: true,
|
||
formatter: (row: PaymentSettings) => row.oa_app_id || '-'
|
||
},
|
||
{
|
||
prop: 'ali_app_id',
|
||
label: '支付宝AppID',
|
||
width: 160,
|
||
showOverflowTooltip: true,
|
||
formatter: (row: PaymentSettings) => row.ali_app_id || '-'
|
||
},
|
||
{
|
||
prop: 'notify_url',
|
||
label: '支付回调地址',
|
||
minWidth: 200,
|
||
showOverflowTooltip: true,
|
||
formatter: (row: PaymentSettings) => getPaymentNotifyUrl(row)
|
||
},
|
||
{
|
||
prop: 'created_at',
|
||
label: '创建时间',
|
||
width: 180,
|
||
formatter: (row: PaymentSettings) => formatDateTime(row.created_at)
|
||
},
|
||
{
|
||
prop: 'updated_at',
|
||
label: '更新时间',
|
||
width: 180,
|
||
formatter: (row: PaymentSettings) => formatDateTime(row.updated_at)
|
||
}
|
||
])
|
||
|
||
const resetForm = () => {
|
||
Object.assign(form, createInitialFormState())
|
||
originalFormState.value = null
|
||
}
|
||
|
||
const getTableData = async () => {
|
||
loading.value = true
|
||
try {
|
||
const params: PaymentSettingsQueryParams = {
|
||
page: pagination.page,
|
||
page_size: pagination.page_size,
|
||
provider_type: searchForm.provider_type,
|
||
is_active: searchForm.is_active !== undefined ? searchForm.is_active === 1 : undefined
|
||
}
|
||
|
||
const res = await PaymentSettingsService.getPaymentSettings(params)
|
||
if (res.code === 0) {
|
||
configList.value = res.data.items || []
|
||
pagination.page = res.data.page || pagination.page
|
||
pagination.page_size = res.data.size || res.data.page_size || pagination.page_size
|
||
pagination.total = res.data.total || 0
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const handleReset = () => {
|
||
Object.assign(searchForm, { ...initialSearchState })
|
||
pagination.page = 1
|
||
getTableData()
|
||
}
|
||
|
||
const handleSearch = () => {
|
||
pagination.page = 1
|
||
getTableData()
|
||
}
|
||
|
||
const handleRefresh = () => {
|
||
getTableData()
|
||
}
|
||
|
||
const handleSizeChange = (newPageSize: number) => {
|
||
pagination.page_size = newPageSize
|
||
getTableData()
|
||
}
|
||
|
||
const handleCurrentChange = (newCurrentPage: number) => {
|
||
pagination.page = newCurrentPage
|
||
getTableData()
|
||
}
|
||
|
||
const showCreateDialog = () => {
|
||
resetForm()
|
||
dialogType.value = 'add'
|
||
dialogVisible.value = true
|
||
nextTick(() => {
|
||
formRef.value?.clearValidate()
|
||
})
|
||
}
|
||
|
||
const showEditDialog = async (row: PaymentSettings) => {
|
||
try {
|
||
const res = await PaymentSettingsService.getPaymentSettingsById(row.id)
|
||
if (res.code === 0 && res.data) {
|
||
const mappedForm = mapConfigToForm(res.data)
|
||
Object.assign(form, mappedForm)
|
||
originalFormState.value = { ...mappedForm }
|
||
dialogType.value = 'edit'
|
||
dialogVisible.value = true
|
||
await nextTick()
|
||
formRef.value?.clearValidate()
|
||
}
|
||
} catch (error) {
|
||
console.error('获取详情失败:', error)
|
||
}
|
||
}
|
||
|
||
const handleProviderTypeChange = () => {
|
||
nextTick(() => {
|
||
formRef.value?.clearValidate()
|
||
})
|
||
}
|
||
|
||
const handleDialogClosed = () => {
|
||
resetForm()
|
||
formRef.value?.clearValidate()
|
||
}
|
||
|
||
const buildCreatePayload = (): CreatePaymentSettingsRequest => {
|
||
const payload: CreatePaymentSettingsRequest = {
|
||
name: trimString(form.name),
|
||
provider_type: form.provider_type,
|
||
ali_pay_expire_minutes: form.ali_pay_expire_minutes,
|
||
ali_production: form.ali_production
|
||
}
|
||
|
||
for (const key of textFieldKeys) {
|
||
const value = normalizeOptionalString(form[key])
|
||
if (value !== undefined) {
|
||
payload[key] = value
|
||
}
|
||
}
|
||
|
||
for (const key of sensitiveFieldKeys) {
|
||
const value = normalizeOptionalString(form[key])
|
||
if (value !== undefined) {
|
||
payload[key] = value
|
||
}
|
||
}
|
||
|
||
return payload
|
||
}
|
||
|
||
const buildUpdatePayload = (): UpdatePaymentSettingsRequest => {
|
||
const initial = originalFormState.value
|
||
if (!initial) return {}
|
||
|
||
const payload: UpdatePaymentSettingsRequest = {}
|
||
const currentName = trimString(form.name)
|
||
const initialName = trimString(initial.name)
|
||
if (currentName !== initialName) {
|
||
payload.name = currentName
|
||
}
|
||
|
||
if (form.provider_type !== initial.provider_type) {
|
||
payload.provider_type = form.provider_type
|
||
}
|
||
|
||
for (const key of textFieldKeys) {
|
||
const currentValue = trimString(form[key])
|
||
const initialValue = trimString(initial[key])
|
||
if (currentValue !== initialValue) {
|
||
payload[key] = currentValue
|
||
}
|
||
}
|
||
|
||
if (form.ali_pay_expire_minutes !== initial.ali_pay_expire_minutes) {
|
||
payload.ali_pay_expire_minutes = form.ali_pay_expire_minutes
|
||
}
|
||
|
||
if (form.ali_production !== initial.ali_production) {
|
||
payload.ali_production = form.ali_production
|
||
}
|
||
|
||
for (const key of sensitiveFieldKeys) {
|
||
const currentValue = trimString(form[key])
|
||
if (!currentValue || isMaskedPaymentConfigValue(currentValue)) {
|
||
continue
|
||
}
|
||
|
||
const initialValue = trimString(initial[key])
|
||
if (currentValue !== initialValue) {
|
||
payload[key] = currentValue
|
||
}
|
||
}
|
||
|
||
return payload
|
||
}
|
||
|
||
const handleSubmit = async () => {
|
||
if (!formRef.value) return
|
||
|
||
try {
|
||
await formRef.value.validate()
|
||
} catch {
|
||
return
|
||
}
|
||
|
||
submitLoading.value = true
|
||
try {
|
||
if (dialogType.value === 'add') {
|
||
await PaymentSettingsService.createPaymentSettings(buildCreatePayload())
|
||
ElMessage.success('创建成功')
|
||
} else {
|
||
const payload = buildUpdatePayload()
|
||
if (Object.keys(payload).length === 0) {
|
||
ElMessage.info('未检测到可提交的变更')
|
||
return
|
||
}
|
||
|
||
await PaymentSettingsService.updatePaymentSettings(form.id, payload)
|
||
ElMessage.success('更新成功')
|
||
}
|
||
|
||
dialogVisible.value = false
|
||
await getTableData()
|
||
} catch (error) {
|
||
console.error(error)
|
||
} finally {
|
||
submitLoading.value = false
|
||
}
|
||
}
|
||
|
||
const handleStatusChange = async (row: PaymentSettings, newStatus: string | number | boolean) => {
|
||
if (!hasAuth('payment_settings:status')) {
|
||
ElMessage.warning('您没有修改激活状态的权限')
|
||
return
|
||
}
|
||
|
||
const newBoolStatus = Boolean(newStatus)
|
||
const oldStatus = row.is_active
|
||
row.is_active = newBoolStatus
|
||
try {
|
||
if (newBoolStatus) {
|
||
await PaymentSettingsService.activatePaymentSettings(row.id)
|
||
ElMessage.success('激活成功')
|
||
} else {
|
||
await PaymentSettingsService.deactivatePaymentSettings(row.id)
|
||
ElMessage.success('停用成功')
|
||
}
|
||
await getTableData()
|
||
} catch (error) {
|
||
row.is_active = oldStatus
|
||
console.error(error)
|
||
}
|
||
}
|
||
|
||
const handleDelete = async (row: PaymentSettings) => {
|
||
try {
|
||
await ElMessageBox.confirm(`确定要删除支付配置"${row.name}"吗?`, '删除确认', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
})
|
||
|
||
await PaymentSettingsService.deletePaymentSettings(row.id)
|
||
ElMessage.success('删除成功')
|
||
await getTableData()
|
||
} catch (error) {
|
||
if (error !== 'cancel') {
|
||
console.error(error)
|
||
}
|
||
}
|
||
}
|
||
|
||
const handleViewDetail = (row: PaymentSettings) => {
|
||
router.push({
|
||
path: `${RoutesAlias.PaymentSettings}/detail/${row.id}`
|
||
})
|
||
}
|
||
|
||
const handleNameClick = (row: PaymentSettings) => {
|
||
if (hasAuth('payment_settings:detail')) {
|
||
handleViewDetail(row)
|
||
} else {
|
||
ElMessage.warning('您没有查看详情的权限')
|
||
}
|
||
}
|
||
|
||
const getActions = (row: PaymentSettings) => {
|
||
const actions: any[] = []
|
||
|
||
if (hasAuth('payment_settings:edit')) {
|
||
actions.push({
|
||
label: '编辑',
|
||
handler: () => showEditDialog(row),
|
||
type: 'primary'
|
||
})
|
||
}
|
||
|
||
if (hasAuth('payment_settings:delete')) {
|
||
actions.push({
|
||
label: '删除',
|
||
handler: () => handleDelete(row),
|
||
type: 'danger'
|
||
})
|
||
}
|
||
|
||
return actions
|
||
}
|
||
|
||
onMounted(() => {
|
||
getTableData()
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.payment-settings-page {
|
||
height: 100%;
|
||
}
|
||
|
||
.config-form {
|
||
max-height: 600px;
|
||
padding-right: 10px;
|
||
overflow-y: auto;
|
||
|
||
.divider-title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
|
||
:deep(.el-divider) {
|
||
margin: 24px 0 20px;
|
||
}
|
||
|
||
:deep(.el-form-item) {
|
||
margin-bottom: 18px;
|
||
}
|
||
|
||
:deep(.el-input__wrapper) {
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
:deep(.el-textarea__inner) {
|
||
transition: all 0.3s ease;
|
||
}
|
||
}
|
||
|
||
// 优化滚动条样式
|
||
.config-form::-webkit-scrollbar {
|
||
width: 6px;
|
||
}
|
||
|
||
.config-form::-webkit-scrollbar-thumb {
|
||
background-color: var(--el-border-color-darker);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.config-form::-webkit-scrollbar-track {
|
||
background-color: var(--el-fill-color-lighter);
|
||
}
|
||
</style>
|