feat: 支付管理重构 - 移除设置页支付Tab/添加bepusdt/内置图标/按类型区分配置
This commit is contained in:
@@ -4,9 +4,14 @@
|
||||
<el-button type="primary" @click="openAdd">添加通道</el-button>
|
||||
</div>
|
||||
<div class="table-card">
|
||||
<el-table :data="channels">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="通道名称" />
|
||||
<el-table :data="channels" style="width: 100%">
|
||||
<el-table-column prop="id" label="ID" width="60" />
|
||||
<el-table-column label="图标" width="60">
|
||||
<template #default="{ row }">
|
||||
<span class="channel-icon" v-html="getIconSvg(row.icon || row.type)"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="通道名称" min-width="120" />
|
||||
<el-table-column prop="code" label="通道代码" width="120" />
|
||||
<el-table-column prop="type" label="类型" width="100">
|
||||
<template #default="{ row }">
|
||||
@@ -18,7 +23,7 @@
|
||||
{{ (row.fee_rate * 100).toFixed(2) }}%
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额限制" width="180">
|
||||
<el-table-column label="金额限制" width="160">
|
||||
<template #default="{ row }">
|
||||
{{ row.min_amount }} - {{ row.max_amount || '∞' }} 元
|
||||
</template>
|
||||
@@ -30,7 +35,7 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sort_order" label="排序" width="80" />
|
||||
<el-table-column prop="sort_order" label="排序" width="60" />
|
||||
<el-table-column label="操作" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="editChannel(row)">编辑</el-button>
|
||||
@@ -43,7 +48,7 @@
|
||||
</el-table>
|
||||
</div>
|
||||
<el-dialog v-model="showAdd" :title="editing ? '编辑支付通道' : '添加支付通道'" width="600px">
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form-item label="通道名称" required>
|
||||
<el-input v-model="form.name" placeholder="如:支付宝" />
|
||||
</el-form-item>
|
||||
@@ -51,16 +56,27 @@
|
||||
<el-input v-model="form.code" placeholder="如:alipay" :disabled="!!editing" />
|
||||
</el-form-item>
|
||||
<el-form-item label="支付类型" required>
|
||||
<el-select v-model="form.type" placeholder="选择支付类型">
|
||||
<el-option label="支付宝" value="alipay" />
|
||||
<el-option label="微信支付" value="wechat" />
|
||||
<el-option label="银行卡" value="bank" />
|
||||
<el-option label="USDT" value="usdt" />
|
||||
<el-option label="其他" value="other" />
|
||||
<el-select v-model="form.type" placeholder="选择支付类型" @change="onTypeChange">
|
||||
<el-option v-for="t in channelTypes" :key="t.value" :label="t.label" :value="t.value">
|
||||
<span style="display:flex;align-items:center;gap:8px">
|
||||
<span v-html="t.icon"></span>
|
||||
<span>{{ t.label }}</span>
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="图标">
|
||||
<el-input v-model="form.icon" placeholder="图标URL" />
|
||||
<div class="icon-selector">
|
||||
<div
|
||||
v-for="ic in availableIcons"
|
||||
:key="ic.name"
|
||||
class="icon-option"
|
||||
:class="{ active: form.icon === ic.name }"
|
||||
@click="form.icon = ic.name"
|
||||
:title="ic.label"
|
||||
v-html="ic.svg"
|
||||
></div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="手续费率">
|
||||
<el-input-number v-model="form.fee_rate" :min="0" :max="1" :step="0.001" :precision="4" />
|
||||
@@ -72,9 +88,82 @@
|
||||
<el-form-item label="最大金额">
|
||||
<el-input-number v-model="form.max_amount" :min="0" :precision="2" />
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">通道配置</el-divider>
|
||||
|
||||
<template v-if="form.type === 'alipay'">
|
||||
<el-form-item label="AppID">
|
||||
<el-input v-model="channelConfig.alipay_app_id" placeholder="支付宝应用ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="应用私钥">
|
||||
<el-input v-model="channelConfig.alipay_private_key" type="textarea" :rows="3" placeholder="RSA2私钥" />
|
||||
</el-form-item>
|
||||
<el-form-item label="支付宝公钥">
|
||||
<el-input v-model="channelConfig.alipay_public_key" type="textarea" :rows="3" placeholder="支付宝公钥" />
|
||||
</el-form-item>
|
||||
<el-form-item label="回调地址">
|
||||
<el-input v-model="channelConfig.alipay_notify_url" placeholder="支付结果通知URL" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else-if="form.type === 'wechat'">
|
||||
<el-form-item label="AppID">
|
||||
<el-input v-model="channelConfig.wechat_app_id" placeholder="公众号/小程序AppID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商户号">
|
||||
<el-input v-model="channelConfig.wechat_mch_id" placeholder="微信支付商户号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="API密钥">
|
||||
<el-input v-model="channelConfig.wechat_api_key" type="password" show-password placeholder="APIv2/v3密钥" />
|
||||
</el-form-item>
|
||||
<el-form-item label="回调地址">
|
||||
<el-input v-model="channelConfig.wechat_notify_url" placeholder="支付结果通知URL" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else-if="form.type === 'bepusdt'">
|
||||
<el-form-item label="API地址">
|
||||
<el-input v-model="channelConfig.bepusdt_api_url" placeholder="如:https://pay.example.com" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商户ID">
|
||||
<el-input v-model="channelConfig.bepusdt_merchant_id" placeholder="BepUsdt商户ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商户密钥">
|
||||
<el-input v-model="channelConfig.bepusdt_secret_key" type="password" show-password placeholder="商户密钥" />
|
||||
</el-form-item>
|
||||
<el-form-item label="回调地址">
|
||||
<el-input v-model="channelConfig.bepusdt_notify_url" placeholder="支付结果通知URL" />
|
||||
</el-form-item>
|
||||
<el-form-item label="USDT网络">
|
||||
<el-select v-model="channelConfig.bepusdt_network" placeholder="选择网络">
|
||||
<el-option label="TRC20" value="trc20" />
|
||||
<el-option label="ERC20" value="erc20" />
|
||||
<el-option label="BEP20" value="bep20" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else-if="form.type === 'bank'">
|
||||
<el-form-item label="银行名称">
|
||||
<el-input v-model="channelConfig.bank_name" placeholder="如:中国工商银行" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开户人">
|
||||
<el-input v-model="channelConfig.bank_account_name" placeholder="持卡人姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="银行卡号">
|
||||
<el-input v-model="channelConfig.bank_account_number" placeholder="银行卡号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开户行">
|
||||
<el-input v-model="channelConfig.bank_branch" placeholder="开户支行" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<el-form-item label="配置信息">
|
||||
<el-input v-model="form.config" type="textarea" :rows="4" placeholder="JSON格式的配置信息" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-form-item label="排序">
|
||||
<el-input-number v-model="form.sort_order" :min="0" />
|
||||
</el-form-item>
|
||||
@@ -113,18 +202,51 @@ const form = reactive({
|
||||
sort_order: 0
|
||||
})
|
||||
|
||||
const typeNames: Record<string, string> = {
|
||||
alipay: '支付宝',
|
||||
wechat: '微信支付',
|
||||
bank: '银行卡',
|
||||
usdt: 'USDT',
|
||||
other: '其他'
|
||||
const channelConfig = reactive<Record<string, string>>({})
|
||||
|
||||
const channelTypes = [
|
||||
{ value: 'alipay', label: '支付宝', icon: '<svg viewBox="0 0 24 24" width="16" height="16" fill="#1677ff"><rect rx="4" width="24" height="24" fill="#1677ff"/><text x="12" y="17" text-anchor="middle" fill="#fff" font-size="12" font-weight="bold">支</text></svg>' },
|
||||
{ value: 'wechat', label: '微信支付', icon: '<svg viewBox="0 0 24 24" width="16" height="16"><rect rx="4" width="24" height="24" fill="#07c160"/><text x="12" y="17" text-anchor="middle" fill="#fff" font-size="12" font-weight="bold">微</text></svg>' },
|
||||
{ value: 'bepusdt', label: 'BepUsdt', icon: '<svg viewBox="0 0 24 24" width="16" height="16"><rect rx="4" width="24" height="24" fill="#26a17b"/><text x="12" y="17" text-anchor="middle" fill="#fff" font-size="11" font-weight="bold">₮</text></svg>' },
|
||||
{ value: 'bank', label: '银行卡', icon: '<svg viewBox="0 0 24 24" width="16" height="16"><rect rx="4" width="24" height="24" fill="#e6a23c"/><text x="12" y="17" text-anchor="middle" fill="#fff" font-size="12" font-weight="bold">卡</text></svg>' },
|
||||
{ value: 'other', label: '其他', icon: '<svg viewBox="0 0 24 24" width="16" height="16"><rect rx="4" width="24" height="24" fill="#909399"/><text x="12" y="17" text-anchor="middle" fill="#fff" font-size="14" font-weight="bold">…</text></svg>' },
|
||||
]
|
||||
|
||||
const typeNames: Record<string, string> = {}
|
||||
channelTypes.forEach(t => { typeNames[t.value] = t.label })
|
||||
|
||||
const availableIcons = [
|
||||
{ name: 'alipay', label: '支付宝', svg: '<svg viewBox="0 0 24 24" width="28" height="28"><rect rx="4" width="24" height="24" fill="#1677ff"/><text x="12" y="17" text-anchor="middle" fill="#fff" font-size="12" font-weight="bold">支</text></svg>' },
|
||||
{ name: 'wechat', label: '微信', svg: '<svg viewBox="0 0 24 24" width="28" height="28"><rect rx="4" width="24" height="24" fill="#07c160"/><text x="12" y="17" text-anchor="middle" fill="#fff" font-size="12" font-weight="bold">微</text></svg>' },
|
||||
{ name: 'usdt', label: 'USDT', svg: '<svg viewBox="0 0 24 24" width="28" height="28"><rect rx="4" width="24" height="24" fill="#26a17b"/><text x="12" y="17" text-anchor="middle" fill="#fff" font-size="11" font-weight="bold">₮</text></svg>' },
|
||||
{ name: 'bank', label: '银行卡', svg: '<svg viewBox="0 0 24 24" width="28" height="28"><rect rx="4" width="24" height="24" fill="#e6a23c"/><text x="12" y="17" text-anchor="middle" fill="#fff" font-size="12" font-weight="bold">卡</text></svg>' },
|
||||
{ name: 'visa', label: 'VISA', svg: '<svg viewBox="0 0 24 24" width="28" height="28"><rect rx="4" width="24" height="24" fill="#1a1f71"/><text x="12" y="16" text-anchor="middle" fill="#fff" font-size="8" font-weight="bold">VISA</text></svg>' },
|
||||
{ name: 'mastercard', label: 'MasterCard', svg: '<svg viewBox="0 0 24 24" width="28" height="28"><rect rx="4" width="24" height="24" fill="#252525"/><circle cx="9" cy="12" r="6" fill="#eb001b" opacity="0.8"/><circle cx="15" cy="12" r="6" fill="#f79e1b" opacity="0.8"/></svg>' },
|
||||
{ name: 'crypto', label: '加密货币', svg: '<svg viewBox="0 0 24 24" width="28" height="28"><rect rx="4" width="24" height="24" fill="#f7931a"/><text x="12" y="17" text-anchor="middle" fill="#fff" font-size="14" font-weight="bold">₿</text></svg>' },
|
||||
]
|
||||
|
||||
const iconSvgMap: Record<string, string> = {}
|
||||
availableIcons.forEach(ic => { iconSvgMap[ic.name] = ic.svg })
|
||||
|
||||
function getIconSvg(icon: string) {
|
||||
return iconSvgMap[icon] || iconSvgMap[icon] || `<svg viewBox="0 0 24 24" width="28" height="28"><rect rx="4" width="24" height="24" fill="#909399"/><text x="12" y="17" text-anchor="middle" fill="#fff" font-size="12">?</text></svg>`
|
||||
}
|
||||
|
||||
function getTypeName(type: string) {
|
||||
return typeNames[type] || type
|
||||
}
|
||||
|
||||
function onTypeChange(type: string) {
|
||||
const iconMap: Record<string, string> = {
|
||||
alipay: 'alipay',
|
||||
wechat: 'wechat',
|
||||
bepusdt: 'usdt',
|
||||
bank: 'bank',
|
||||
}
|
||||
form.icon = iconMap[type] || ''
|
||||
Object.keys(channelConfig).forEach(k => delete channelConfig[k])
|
||||
}
|
||||
|
||||
async function fetchChannels() {
|
||||
try {
|
||||
const res: any = await adminApi.getPaymentChannels()
|
||||
@@ -137,34 +259,28 @@ async function fetchChannels() {
|
||||
function openAdd() {
|
||||
editing.value = null
|
||||
Object.assign(form, {
|
||||
name: '',
|
||||
code: '',
|
||||
type: 'alipay',
|
||||
icon: '',
|
||||
fee_rate: 0,
|
||||
min_amount: 0,
|
||||
max_amount: 0,
|
||||
config: '',
|
||||
is_enabled: true,
|
||||
sort_order: 0
|
||||
name: '', code: '', type: 'alipay', icon: 'alipay',
|
||||
fee_rate: 0, min_amount: 0, max_amount: 0, config: '',
|
||||
is_enabled: true, sort_order: 0
|
||||
})
|
||||
Object.keys(channelConfig).forEach(k => delete channelConfig[k])
|
||||
showAdd.value = true
|
||||
}
|
||||
|
||||
function editChannel(row: any) {
|
||||
editing.value = row
|
||||
Object.assign(form, {
|
||||
name: row.name,
|
||||
code: row.code,
|
||||
type: row.type,
|
||||
icon: row.icon || '',
|
||||
fee_rate: row.fee_rate || 0,
|
||||
min_amount: row.min_amount || 0,
|
||||
max_amount: row.max_amount || 0,
|
||||
config: row.config || '',
|
||||
is_enabled: row.is_enabled,
|
||||
sort_order: row.sort_order || 0
|
||||
name: row.name, code: row.code, type: row.type, icon: row.icon || '',
|
||||
fee_rate: row.fee_rate || 0, min_amount: row.min_amount || 0, max_amount: row.max_amount || 0,
|
||||
config: row.config || '', is_enabled: row.is_enabled, sort_order: row.sort_order || 0
|
||||
})
|
||||
Object.keys(channelConfig).forEach(k => delete channelConfig[k])
|
||||
if (row.config) {
|
||||
try {
|
||||
const parsed = JSON.parse(row.config)
|
||||
Object.assign(channelConfig, parsed)
|
||||
} catch {}
|
||||
}
|
||||
showAdd.value = true
|
||||
}
|
||||
|
||||
@@ -175,10 +291,14 @@ async function saveChannel() {
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const payload: any = { ...form }
|
||||
if (form.type !== 'other' && Object.keys(channelConfig).length > 0) {
|
||||
payload.config = JSON.stringify(channelConfig)
|
||||
}
|
||||
if (editing.value) {
|
||||
await adminApi.updatePaymentChannel(editing.value.id, form)
|
||||
await adminApi.updatePaymentChannel(editing.value.id, payload)
|
||||
} else {
|
||||
await adminApi.createPaymentChannel(form)
|
||||
await adminApi.createPaymentChannel(payload)
|
||||
}
|
||||
ElMessage.success('保存成功')
|
||||
showAdd.value = false
|
||||
@@ -217,6 +337,43 @@ onMounted(fetchChannels)
|
||||
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; }
|
||||
.table-card { background: #2d2d44; border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 12px; padding: 20px; }
|
||||
|
||||
.channel-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.icon-selector {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.icon-option {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
|
||||
&:hover {
|
||||
border-color: rgba(78, 110, 242, 0.4);
|
||||
background: rgba(78, 110, 242, 0.08);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #4e6ef2;
|
||||
background: rgba(78, 110, 242, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
--el-table-bg-color: transparent;
|
||||
--el-table-tr-bg-color: transparent;
|
||||
@@ -302,4 +459,9 @@ onMounted(fetchChannels)
|
||||
color: #fff;
|
||||
&::placeholder { color: rgba(255, 255, 255, 0.3); }
|
||||
}
|
||||
|
||||
:deep(.el-divider__text) {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
background: #2d2d44;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,25 +14,6 @@
|
||||
<el-form-item><el-button type="primary" @click="saveSettings">保存</el-button></el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="支付设置">
|
||||
<el-form label-width="160px">
|
||||
<el-form-item label="启用支付方式">
|
||||
<el-checkbox-group v-model="enabledPayments">
|
||||
<el-checkbox label="balance">余额支付</el-checkbox>
|
||||
<el-checkbox label="alipay">支付宝</el-checkbox>
|
||||
<el-checkbox label="wechat">微信支付</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-divider content-position="left">支付宝配置</el-divider>
|
||||
<el-form-item label="支付宝AppID"><el-input v-model="alipayConfig.app_id" placeholder="应用ID" /></el-form-item>
|
||||
<el-form-item label="支付宝私钥"><el-input v-model="alipayConfig.private_key" type="textarea" :rows="3" placeholder="应用私钥" /></el-form-item>
|
||||
<el-divider content-position="left">微信支付配置</el-divider>
|
||||
<el-form-item label="微信AppID"><el-input v-model="wechatConfig.app_id" placeholder="公众号/小程序AppID" /></el-form-item>
|
||||
<el-form-item label="微信MchID"><el-input v-model="wechatConfig.mch_id" placeholder="商户号" /></el-form-item>
|
||||
<el-form-item label="微信API密钥"><el-input v-model="wechatConfig.api_key" type="password" show-password placeholder="APIv2密钥" /></el-form-item>
|
||||
<el-form-item><el-button type="primary" @click="savePayment">保存</el-button></el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="SMTP设置">
|
||||
<el-form :model="smtp" label-width="160px">
|
||||
<el-form-item label="SMTP服务器"><el-input v-model="smtp.smtp_host" /></el-form-item>
|
||||
@@ -62,9 +43,6 @@ const settings = reactive<Record<string, any>>({
|
||||
invite_credit_reward: 0
|
||||
})
|
||||
const verifyEnabled = ref('true')
|
||||
const enabledPayments = ref<string[]>(['balance'])
|
||||
const alipayConfig = reactive({ app_id: '', private_key: '' })
|
||||
const wechatConfig = reactive({ app_id: '', mch_id: '', api_key: '' })
|
||||
const smtp = reactive({ smtp_host: '', smtp_port: '587', smtp_user: '', smtp_password: '', smtp_from: '' })
|
||||
|
||||
async function fetchSettings() {
|
||||
@@ -72,20 +50,6 @@ async function fetchSettings() {
|
||||
const data = res.data || {}
|
||||
Object.keys(settings).forEach(k => { if (data[k] !== undefined) settings[k] = data[k] })
|
||||
verifyEnabled.value = data.verification_code_enabled || 'true'
|
||||
|
||||
if (data.enabled_payments) {
|
||||
try {
|
||||
enabledPayments.value = JSON.parse(data.enabled_payments)
|
||||
} catch { enabledPayments.value = ['balance'] }
|
||||
}
|
||||
|
||||
if (data.alipay_config) {
|
||||
try { Object.assign(alipayConfig, JSON.parse(data.alipay_config)) } catch {}
|
||||
}
|
||||
if (data.wechat_config) {
|
||||
try { Object.assign(wechatConfig, JSON.parse(data.wechat_config)) } catch {}
|
||||
}
|
||||
|
||||
Object.keys(smtp).forEach(k => { if (data[k] !== undefined) (smtp as any)[k] = data[k] })
|
||||
}
|
||||
|
||||
@@ -94,15 +58,6 @@ async function saveSettings() {
|
||||
ElMessage.success('保存成功')
|
||||
}
|
||||
|
||||
async function savePayment() {
|
||||
await adminApi.updatePayment({
|
||||
enabled_payments: JSON.stringify(enabledPayments.value),
|
||||
alipay_config: JSON.stringify(alipayConfig),
|
||||
wechat_config: JSON.stringify(wechatConfig)
|
||||
})
|
||||
ElMessage.success('保存成功')
|
||||
}
|
||||
|
||||
async function saveSMTP() {
|
||||
await adminApi.updateSMTP(smtp)
|
||||
ElMessage.success('保存成功')
|
||||
|
||||
Reference in New Issue
Block a user