优化结构
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import service from '@/utils/request'
|
||||
|
||||
export const getIntegrationConfigs = (kind) => service({
|
||||
url: `/integration/configs/${encodeURIComponent(kind)}`,
|
||||
method: 'get'
|
||||
})
|
||||
|
||||
export const saveIntegrationConfig = (kind, provider, data) => service({
|
||||
url: `/integration/configs/${encodeURIComponent(kind)}/${encodeURIComponent(provider)}`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
|
||||
export const deleteIntegrationConfig = (kind, provider) => service({
|
||||
url: `/integration/configs/${encodeURIComponent(kind)}/${encodeURIComponent(provider)}`,
|
||||
method: 'delete'
|
||||
})
|
||||
@@ -1,13 +1,5 @@
|
||||
import service from '@/utils/request'
|
||||
|
||||
export const getPaymentConfigs = () => service({ url: '/payment/configs', method: 'get' })
|
||||
|
||||
export const savePaymentConfig = (data) => service({
|
||||
url: '/payment/config',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
|
||||
export const getPaymentOrders = (params) => service({
|
||||
url: '/payment/orders',
|
||||
method: 'get',
|
||||
|
||||
@@ -1,33 +1,87 @@
|
||||
<template>
|
||||
<div class="payment-config">
|
||||
<el-alert title="密钥字段会由服务端脱敏。编辑时保留 ****** 可继续使用原密钥;保存前请先校验 JSON。" type="warning" :closable="false" class="mb-4" />
|
||||
<div class="kra-table-box">
|
||||
<div class="config-toolbar"><span>已配置 {{ configs.length }} 个支付渠道</span><el-button :loading="loading" icon="refresh" @click="load">刷新</el-button></div>
|
||||
<el-table v-loading="loading" :data="configs" row-key="provider" stripe>
|
||||
<el-table-column prop="provider" label="渠道" width="170" />
|
||||
<el-table-column label="启用" width="100"><template #default="scope"><el-switch v-model="scope.row.enabled" @change="save(scope.row)" /></template></el-table-column>
|
||||
<el-table-column label="配置 JSON" min-width="560"><template #default="scope"><el-input v-model="scope.row.editor" type="textarea" :rows="5" spellcheck="false" /></template></el-table-column>
|
||||
<el-table-column label="校验" width="100"><template #default="scope"><el-tag :type="scope.row.valid === false ? 'danger' : 'success'">{{ scope.row.valid === false ? '格式错误' : '可保存' }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="操作" width="130"><template #default="scope"><el-button type="primary" link icon="check" @click="save(scope.row)">保存</el-button><el-button link @click="pretty(scope.row)">格式化</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
<div class="integration-config-page">
|
||||
<div class="page-heading">
|
||||
<div><h2>支付渠道配置</h2><p>统一管理支付渠道凭证、接口地址和默认交易参数</p></div>
|
||||
<el-button :loading="loading" :icon="Refresh" @click="load">刷新</el-button>
|
||||
</div>
|
||||
<div v-loading="loading" class="config-layout">
|
||||
<aside class="provider-panel">
|
||||
<div class="panel-title">渠道 <span>{{ configs.length }}</span></div>
|
||||
<button v-for="item in configs" :key="item.provider" type="button" class="provider-item" :class="{ active: selected?.provider === item.provider }" @click="select(item)">
|
||||
<span class="provider-copy"><strong>{{ item.name || item.provider }}</strong><small>{{ item.provider }}</small></span>
|
||||
<el-tag :type="item.enabled ? 'success' : 'info'" size="small">{{ item.enabled ? '启用' : '停用' }}</el-tag>
|
||||
</button>
|
||||
</aside>
|
||||
<section v-if="selected" class="editor-panel">
|
||||
<div class="editor-heading">
|
||||
<div><div class="editor-title">{{ selected.name || selected.provider }}</div><div class="editor-subtitle">{{ selected.description || `provider: ${selected.provider}` }}</div></div>
|
||||
<el-switch v-model="selected.enabled" active-text="启用渠道" @change="save" />
|
||||
</div>
|
||||
<el-alert v-if="selected.configured" title="密钥字段已脱敏,保留 ****** 表示继续使用当前密钥。" type="info" :closable="false" class="editor-alert" />
|
||||
<el-alert v-else title="该渠道尚未保存,填写字段后保存即可创建配置。" type="warning" :closable="false" class="editor-alert" />
|
||||
<el-form label-position="top" class="config-form">
|
||||
<div class="field-grid">
|
||||
<el-form-item v-for="field in selected.fields" :key="field.key" :label="field.label" :required="field.required">
|
||||
<template #label><span>{{ field.label }}</span><span class="field-key">{{ field.key }}</span></template>
|
||||
<el-select v-if="field.type === 'select'" v-model="selected.config[field.key]" class="field-control" filterable>
|
||||
<el-option v-for="option in field.options" :key="String(option.value)" :label="option.label" :value="option.value" />
|
||||
</el-select>
|
||||
<el-switch v-else-if="field.type === 'switch'" v-model="selected.config[field.key]" />
|
||||
<el-input v-else-if="field.type === 'textarea'" v-model="selected.config[field.key]" class="field-control" type="textarea" :rows="4" :show-password="field.secret" spellcheck="false" />
|
||||
<el-input v-else v-model="selected.config[field.key]" class="field-control" :type="field.secret ? 'password' : field.type === 'number' ? 'number' : 'text'" :show-password="field.secret" spellcheck="false" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<div class="editor-actions">
|
||||
<el-button type="primary" :loading="saving" :icon="Check" @click="save">保存配置</el-button>
|
||||
<el-button v-if="selected.configured" type="danger" plain :icon="Delete" @click="remove">删除配置</el-button>
|
||||
<el-button text :icon="DocumentCopy" @click="copyConfig">复制 JSON</el-button>
|
||||
</div>
|
||||
</section>
|
||||
<el-empty v-else description="暂无支付渠道" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getPaymentConfigs, savePaymentConfig } from '@/api/payment'
|
||||
import { computed, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Check, Delete, DocumentCopy, Refresh } from '@element-plus/icons-vue'
|
||||
import { deleteIntegrationConfig, getIntegrationConfigs, saveIntegrationConfig } from '@/api/integration'
|
||||
|
||||
const configs = ref([]); const loading = ref(false)
|
||||
const parseConfig = (row, notify = true) => { try { JSON.parse(row.editor || '{}'); row.valid = true; return true } catch { row.valid = false; if (notify) ElMessage.error(`${row.provider} 配置必须是合法 JSON`); return false } }
|
||||
const load = async () => { loading.value = true; try { const res = await getPaymentConfigs(); if (res.code === 0) configs.value = (res.data || []).map((item) => ({ ...item, editor: JSON.stringify(item.config || {}, null, 2), valid: true })) } finally { loading.value = false } }
|
||||
const pretty = (row) => { try { row.editor = JSON.stringify(JSON.parse(row.editor || '{}'), null, 2); row.valid = true } catch { parseConfig(row) } }
|
||||
const save = async (row) => { if (!parseConfig(row)) return; const config = JSON.parse(row.editor || '{}'); const res = await savePaymentConfig({ provider: row.provider, enabled: row.enabled, config }); if (res.code === 0) { ElMessage.success(`${row.provider} 配置保存成功`); await load() } }
|
||||
const configs = ref([]); const selectedProvider = ref(''); const loading = ref(false); const saving = ref(false)
|
||||
const selected = computed(() => configs.value.find((item) => item.provider === selectedProvider.value) || configs.value[0])
|
||||
const select = (item) => { selectedProvider.value = item.provider }
|
||||
const load = async () => { loading.value = true; try { const res = await getIntegrationConfigs('payment'); if (res.code === 0) { configs.value = (res.data || []).map((item) => ({ ...item, config: { ...(item.config || {}) } })); if (!configs.value.some((item) => item.provider === selectedProvider.value)) selectedProvider.value = configs.value[0]?.provider || '' } } finally { loading.value = false } }
|
||||
const save = async () => { if (!selected.value) return; saving.value = true; try { const res = await saveIntegrationConfig('payment', selected.value.provider, { enabled: selected.value.enabled, config: selected.value.config }); if (res.code === 0) { ElMessage.success('支付渠道配置已保存'); await load() } } finally { saving.value = false } }
|
||||
const remove = async () => { if (!selected.value) return; await ElMessageBox.confirm(`确认删除 ${selected.value.name || selected.value.provider} 配置吗?`, '删除配置', { type: 'warning' }); const res = await deleteIntegrationConfig('payment', selected.value.provider); if (res.code === 0) { ElMessage.success('配置已删除'); await load() } }
|
||||
const copyConfig = async () => { if (!selected.value) return; await navigator.clipboard.writeText(JSON.stringify(selected.value.config || {}, null, 2)); ElMessage.success('JSON 已复制') }
|
||||
load()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.config-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; color: var(--el-text-color-secondary); }
|
||||
.payment-config { min-width: 900px; }
|
||||
.integration-config-page { padding: 4px 0 24px; }
|
||||
.page-heading { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }
|
||||
.page-heading h2 { margin: 0; color: var(--el-text-color-primary); font-size: 20px; font-weight: 600; }
|
||||
.page-heading p { margin: 6px 0 0; color: var(--el-text-color-secondary); font-size: 13px; }
|
||||
.config-layout { display: grid; grid-template-columns: 250px minmax(0, 1fr); min-height: 620px; border: 1px solid var(--el-border-color-lighter); background: var(--el-bg-color); }
|
||||
.provider-panel { border-right: 1px solid var(--el-border-color-lighter); padding: 14px 10px; }
|
||||
.panel-title { display: flex; justify-content: space-between; padding: 2px 10px 12px; color: var(--el-text-color-primary); font-size: 14px; font-weight: 600; }
|
||||
.panel-title span { color: var(--el-text-color-secondary); font-weight: 400; }
|
||||
.provider-item { display: flex; align-items: center; justify-content: space-between; width: 100%; min-height: 54px; padding: 9px 10px; border: 0; border-left: 3px solid transparent; background: transparent; color: inherit; text-align: left; cursor: pointer; }
|
||||
.provider-item:hover { background: var(--el-fill-color-light); }
|
||||
.provider-item.active { border-left-color: var(--el-color-primary); background: var(--el-color-primary-light-9); }
|
||||
.provider-copy { display: grid; gap: 3px; min-width: 0; }
|
||||
.provider-copy strong { overflow: hidden; color: var(--el-text-color-primary); font-size: 14px; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.provider-copy small { color: var(--el-text-color-secondary); font-size: 11px; }
|
||||
.editor-panel { min-width: 0; padding: 22px 28px 24px; }
|
||||
.editor-heading { display: flex; align-items: center; justify-content: space-between; gap: 18px; padding-bottom: 18px; border-bottom: 1px solid var(--el-border-color-lighter); }
|
||||
.editor-title { color: var(--el-text-color-primary); font-size: 18px; font-weight: 600; }
|
||||
.editor-subtitle { margin-top: 5px; color: var(--el-text-color-secondary); font-size: 13px; }
|
||||
.editor-alert { margin: 18px 0; }
|
||||
.field-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 22px; }
|
||||
.field-key { margin-left: 8px; color: var(--el-text-color-placeholder); font-size: 11px; font-weight: 400; }
|
||||
.field-control { width: 100%; }
|
||||
.editor-actions { display: flex; align-items: center; gap: 10px; padding-top: 8px; border-top: 1px solid var(--el-border-color-lighter); }
|
||||
@media (max-width: 900px) { .config-layout { grid-template-columns: 1fr; } .provider-panel { border-right: 0; border-bottom: 1px solid var(--el-border-color-lighter); max-height: 260px; overflow-y: auto; } .field-grid { grid-template-columns: 1fr; } .editor-panel { padding: 18px; } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user