|
@@ -0,0 +1,616 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="admin-config-page">
|
|
|
|
|
+ <Breadcrumb />
|
|
|
|
|
+
|
|
|
|
|
+ <a-card title="基础配置" class="config-card">
|
|
|
|
|
+ <template #extra>
|
|
|
|
|
+ <a-button type="primary" @click="openCreate">
|
|
|
|
|
+ <template #icon><icon-plus /></template>
|
|
|
|
|
+ 新增方案
|
|
|
|
|
+ </a-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <a-alert type="info" class="hint">
|
|
|
|
|
+ 配置保存或切换当前方案后会清理缓存并即时生效。支付方式、邮件尝试顺序等均在表单内维护。
|
|
|
|
|
+ </a-alert>
|
|
|
|
|
+
|
|
|
|
|
+ <a-tabs v-model:active-key="activeKey" class="config-tabs">
|
|
|
|
|
+ <a-tab-pane v-for="item in configTypes" :key="item.key" :title="item.label" />
|
|
|
|
|
+ </a-tabs>
|
|
|
|
|
+
|
|
|
|
|
+ <a-table :data="currentList" :loading="loading" :pagination="false" row-key="config_name">
|
|
|
|
|
+ <template #columns>
|
|
|
|
|
+ <a-table-column title="方案名称" data-index="config_name" :width="180" />
|
|
|
|
|
+ <a-table-column title="配置摘要">
|
|
|
|
|
+ <template #cell="{ record }">
|
|
|
|
|
+ <span class="summary">{{ formatSummary(record) }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </a-table-column>
|
|
|
|
|
+ <a-table-column title="状态" :width="160">
|
|
|
|
|
+ <template #cell="{ record }">
|
|
|
|
|
+ <a-space>
|
|
|
|
|
+ <a-tag :color="record.enabled ? 'green' : 'gray'">
|
|
|
|
|
+ {{ record.enabled ? '启用' : '停用' }}
|
|
|
|
|
+ </a-tag>
|
|
|
|
|
+ <a-tag v-if="record.is_default" color="arcoblue">当前</a-tag>
|
|
|
|
|
+ </a-space>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </a-table-column>
|
|
|
|
|
+ <a-table-column title="操作" :width="210" fixed="right">
|
|
|
|
|
+ <template #cell="{ record }">
|
|
|
|
|
+ <a-space wrap>
|
|
|
|
|
+ <a-button size="mini" @click="openEdit(record)">编辑</a-button>
|
|
|
|
|
+ <a-button
|
|
|
|
|
+ size="mini"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ :disabled="record.is_default || !record.enabled"
|
|
|
|
|
+ @click="setCurrent(record)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 设为当前
|
|
|
|
|
+ </a-button>
|
|
|
|
|
+ </a-space>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </a-table-column>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </a-table>
|
|
|
|
|
+ </a-card>
|
|
|
|
|
+
|
|
|
|
|
+ <a-modal
|
|
|
|
|
+ v-model:visible="visible"
|
|
|
|
|
+ :title="editing ? '编辑配置方案' : '新增配置方案'"
|
|
|
|
|
+ width="920px"
|
|
|
|
|
+ :modal-style="{ maxWidth: 'calc(100vw - 32px)' }"
|
|
|
|
|
+ @before-ok="save"
|
|
|
|
|
+ >
|
|
|
|
|
+ <a-form :model="form" layout="vertical">
|
|
|
|
|
+ <a-grid :cols="{ xs: 1, sm: 2 }" :col-gap="16">
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="配置类型" required>
|
|
|
|
|
+ <a-select v-model="form.key" :disabled="!!editing" @change="resetConfigValue">
|
|
|
|
|
+ <a-option v-for="item in configTypes" :key="item.key" :value="item.key">
|
|
|
|
|
+ {{ item.label }}
|
|
|
|
|
+ </a-option>
|
|
|
|
|
+ </a-select>
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="方案名称" required>
|
|
|
|
|
+ <a-input v-model="form.name" placeholder="例如:默认配置、备用通道" :disabled="!!editing" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ </a-grid>
|
|
|
|
|
+
|
|
|
|
|
+ <a-space class="switch-row" wrap>
|
|
|
|
|
+ <span class="switch-item"><a-switch v-model="form.enabled" /> 启用方案</span>
|
|
|
|
|
+ <span class="switch-item"><a-switch v-model="form.isDefault" /> 保存后设为当前方案</span>
|
|
|
|
|
+ </a-space>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="form.key === 'pay'" class="form-section">
|
|
|
|
|
+ <div class="section-title">支付平台</div>
|
|
|
|
|
+ <a-grid :cols="{ xs: 1, sm: 2 }" :col-gap="16">
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="易支付网关地址" required>
|
|
|
|
|
+ <a-input v-model="form.pay.url" placeholder="https://pay.example.com" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="商户 PID" required>
|
|
|
|
|
+ <a-input v-model="form.pay.pid" placeholder="请输入易支付商户 ID" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="商户密钥 Key" required>
|
|
|
|
|
+ <a-input-password v-model="form.pay.key" placeholder="请输入易支付通信密钥" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="网页支付返回地址" required>
|
|
|
|
|
+ <a-input v-model="form.pay.return_url" placeholder="https://站点/store/orders/" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="App 支付返回地址">
|
|
|
|
|
+ <a-input v-model="form.pay.uni_return_url" placeholder="runforge://store/orders/" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ </a-grid>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="subhead">
|
|
|
|
|
+ <span>可用付款方式</span>
|
|
|
|
|
+ <a-button size="small" @click="addPayMethod">
|
|
|
|
|
+ <template #icon><icon-plus /></template>
|
|
|
|
|
+ 添加
|
|
|
|
|
+ </a-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="editable-list">
|
|
|
|
|
+ <div v-for="(method, index) in form.pay.methods" :key="index" class="editable-row method-row">
|
|
|
|
|
+ <a-switch v-model="method.enabled" />
|
|
|
|
|
+ <a-select v-model="method.type" placeholder="付款类型" allow-create>
|
|
|
|
|
+ <a-option value="alipay">alipay</a-option>
|
|
|
|
|
+ <a-option value="wxpay">wxpay</a-option>
|
|
|
|
|
+ <a-option value="qqpay">qqpay</a-option>
|
|
|
|
|
+ <a-option value="bank">bank</a-option>
|
|
|
|
|
+ </a-select>
|
|
|
|
|
+ <a-input v-model="method.name" placeholder="前台显示名称" />
|
|
|
|
|
+ <a-button size="mini" status="danger" @click="removePayMethod(index)">删除</a-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="form.key === 'email'" class="form-section">
|
|
|
|
|
+ <div class="subhead">
|
|
|
|
|
+ <span>邮件发送账号</span>
|
|
|
|
|
+ <a-button size="small" @click="addEmailAccount">
|
|
|
|
|
+ <template #icon><icon-plus /></template>
|
|
|
|
|
+ 添加账号
|
|
|
|
|
+ </a-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="editable-list">
|
|
|
|
|
+ <div v-for="(account, index) in form.emails" :key="index" class="email-card">
|
|
|
|
|
+ <div class="email-card-head">
|
|
|
|
|
+ <a-space wrap>
|
|
|
|
|
+ <a-switch v-model="account.enabled" />
|
|
|
|
|
+ <span>账号 {{ index + 1 }}</span>
|
|
|
|
|
+ </a-space>
|
|
|
|
|
+ <a-button size="mini" status="danger" @click="removeEmailAccount(index)">删除</a-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <a-grid :cols="{ xs: 1, sm: 2 }" :col-gap="16">
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="SMTP Host" required>
|
|
|
|
|
+ <a-input v-model="account.host" placeholder="smtp.example.com" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="SMTP 端口" required>
|
|
|
|
|
+ <a-input-number v-model="account.port" :min="1" :max="65535" mode="button" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="邮箱账号" required>
|
|
|
|
|
+ <a-input v-model="account.user" placeholder="service@example.com" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="邮箱密码/授权码" required>
|
|
|
|
|
+ <a-input-password v-model="account.password" placeholder="请输入 SMTP 授权码" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="发件人 From" required>
|
|
|
|
|
+ <a-input v-model="account.from" placeholder="RunForge <service@example.com>" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="尝试顺序">
|
|
|
|
|
+ <a-input-number v-model="account.priority" :min="0" mode="button" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ </a-grid>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="form.key === 'unilogin'" class="form-section">
|
|
|
|
|
+ <div class="section-title">聚合登录</div>
|
|
|
|
|
+ <a-grid :cols="{ xs: 1, sm: 2 }" :col-gap="16">
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="接口地址" required>
|
|
|
|
|
+ <a-input v-model="form.unilogin.url" placeholder="https://login.example.com" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="AppID" required>
|
|
|
|
|
+ <a-input v-model="form.unilogin.appid" placeholder="请输入 AppID" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="AppKey" required>
|
|
|
|
|
+ <a-input-password v-model="form.unilogin.appkey" placeholder="请输入 AppKey" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="启用登录类型">
|
|
|
|
|
+ <a-checkbox-group v-model="form.unilogin.enabledTypes">
|
|
|
|
|
+ <a-checkbox value="qq">QQ</a-checkbox>
|
|
|
|
|
+ <a-checkbox value="wx">微信</a-checkbox>
|
|
|
|
|
+ </a-checkbox-group>
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ </a-grid>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="form.key === 'proxyForwardServer'" class="form-section">
|
|
|
|
|
+ <div class="section-title">代理转发服务</div>
|
|
|
|
|
+ <a-grid :cols="{ xs: 1, sm: 2 }" :col-gap="16">
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="代理服务开关">
|
|
|
|
|
+ <a-switch v-model="form.proxy.enabled" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item>
|
|
|
|
|
+ <a-form-item label="默认超时(毫秒)">
|
|
|
|
|
+ <a-input-number v-model="form.proxy.timeout" :min="1000" :step="1000" mode="button" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ <a-grid-item :span="2">
|
|
|
|
|
+ <a-form-item label="代理服务地址">
|
|
|
|
|
+ <a-input v-model="form.proxy.url" placeholder="http://127.0.0.1:9000" />
|
|
|
|
|
+ </a-form-item>
|
|
|
|
|
+ </a-grid-item>
|
|
|
|
|
+ </a-grid>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </a-form>
|
|
|
|
|
+ </a-modal>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { computed, onMounted, reactive, ref } from 'vue'
|
|
|
|
|
+import { Message } from '@arco-design/web-vue'
|
|
|
|
|
+import { getRuntimeConfigs, saveRuntimeConfig, selectRuntimeConfig } from '@/api/runtimeConfig'
|
|
|
|
|
+
|
|
|
|
|
+const configTypes = [
|
|
|
|
|
+ { key: 'pay', label: '支付配置' },
|
|
|
|
|
+ { key: 'email', label: '邮件配置' },
|
|
|
|
|
+ { key: 'unilogin', label: '聚合登录' },
|
|
|
|
|
+ { key: 'proxyForwardServer', label: '代理配置' }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+const activeKey = ref('pay')
|
|
|
|
|
+const allConfigs = ref([])
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const visible = ref(false)
|
|
|
|
|
+const editing = ref(null)
|
|
|
|
|
+const originalValue = ref({})
|
|
|
|
|
+
|
|
|
|
|
+const form = reactive({
|
|
|
|
|
+ key: 'pay',
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ enabled: true,
|
|
|
|
|
+ isDefault: false,
|
|
|
|
|
+ pay: emptyPayConfig(),
|
|
|
|
|
+ emails: [],
|
|
|
|
|
+ unilogin: emptyUniLoginConfig(),
|
|
|
|
|
+ proxy: emptyProxyConfig()
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const currentList = computed(() => allConfigs.value.filter(item => item.config_key === activeKey.value))
|
|
|
|
|
+
|
|
|
|
|
+function emptyPayConfig() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ url: '',
|
|
|
|
|
+ pid: '',
|
|
|
|
|
+ key: '',
|
|
|
|
|
+ return_url: '',
|
|
|
|
|
+ uni_return_url: '',
|
|
|
|
|
+ methods: [
|
|
|
|
|
+ { type: 'alipay', name: '支付宝', enabled: true },
|
|
|
|
|
+ { type: 'wxpay', name: '微信支付', enabled: true }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function emptyEmailConfig() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ host: '',
|
|
|
|
|
+ port: 465,
|
|
|
|
|
+ user: '',
|
|
|
|
|
+ password: '',
|
|
|
|
|
+ from: '',
|
|
|
|
|
+ priority: 0,
|
|
|
|
|
+ enabled: true
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function emptyUniLoginConfig() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ url: '',
|
|
|
|
|
+ appid: '',
|
|
|
|
|
+ appkey: '',
|
|
|
|
|
+ enabledTypes: ['qq', 'wx']
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function emptyProxyConfig() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ enabled: false,
|
|
|
|
|
+ url: '',
|
|
|
|
|
+ timeout: 120000
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resetConfigValue() {
|
|
|
|
|
+ form.pay = emptyPayConfig()
|
|
|
|
|
+ form.emails = [emptyEmailConfig()]
|
|
|
|
|
+ form.unilogin = emptyUniLoginConfig()
|
|
|
|
|
+ form.proxy = emptyProxyConfig()
|
|
|
|
|
+ originalValue.value = {}
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function load() {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getRuntimeConfigs()
|
|
|
|
|
+ if (res?.code === 0) {
|
|
|
|
|
+ allConfigs.value = res.data || []
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Message.error(res?.msg || '获取配置失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openCreate() {
|
|
|
|
|
+ editing.value = null
|
|
|
|
|
+ Object.assign(form, {
|
|
|
|
|
+ key: activeKey.value,
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ enabled: true,
|
|
|
|
|
+ isDefault: false
|
|
|
|
|
+ })
|
|
|
|
|
+ resetConfigValue()
|
|
|
|
|
+ form.key = activeKey.value
|
|
|
|
|
+ visible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openEdit(record) {
|
|
|
|
|
+ editing.value = record
|
|
|
|
|
+ originalValue.value = cloneValue(record.config_value)
|
|
|
|
|
+ Object.assign(form, {
|
|
|
|
|
+ key: record.config_key,
|
|
|
|
|
+ name: record.config_name,
|
|
|
|
|
+ enabled: !!record.enabled,
|
|
|
|
|
+ isDefault: !!record.is_default
|
|
|
|
|
+ })
|
|
|
|
|
+ fillFormValue(record.config_key, record.config_value)
|
|
|
|
|
+ visible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function fillFormValue(key, value) {
|
|
|
|
|
+ const data = cloneValue(value)
|
|
|
|
|
+ if (key === 'pay') {
|
|
|
|
|
+ form.pay = {
|
|
|
|
|
+ ...emptyPayConfig(),
|
|
|
|
|
+ ...data,
|
|
|
|
|
+ methods: Array.isArray(data.methods) && data.methods.length
|
|
|
|
|
+ ? data.methods.map(item => ({ enabled: item.enabled !== false, ...item }))
|
|
|
|
|
+ : emptyPayConfig().methods
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (key === 'email') {
|
|
|
|
|
+ form.emails = Array.isArray(data) && data.length
|
|
|
|
|
+ ? data.map((item, index) => ({ ...emptyEmailConfig(), priority: index, enabled: item.enabled !== false, ...item }))
|
|
|
|
|
+ : [emptyEmailConfig()]
|
|
|
|
|
+ } else if (key === 'unilogin') {
|
|
|
|
|
+ form.unilogin = { ...emptyUniLoginConfig(), ...data }
|
|
|
|
|
+ } else if (key === 'proxyForwardServer') {
|
|
|
|
|
+ form.proxy = { ...emptyProxyConfig(), ...data }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function cloneValue(value) {
|
|
|
|
|
+ if (value == null) return {}
|
|
|
|
|
+ return JSON.parse(JSON.stringify(value))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function addPayMethod() {
|
|
|
|
|
+ form.pay.methods.push({ type: '', name: '', enabled: true })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function removePayMethod(index) {
|
|
|
|
|
+ form.pay.methods.splice(index, 1)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function addEmailAccount() {
|
|
|
|
|
+ form.emails.push({ ...emptyEmailConfig(), priority: form.emails.length })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function removeEmailAccount(index) {
|
|
|
|
|
+ form.emails.splice(index, 1)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function buildValue() {
|
|
|
|
|
+ if (form.key === 'pay') {
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...(editing.value && typeof originalValue.value === 'object' ? originalValue.value : {}),
|
|
|
|
|
+ url: String(form.pay.url || '').trim(),
|
|
|
|
|
+ pid: String(form.pay.pid || '').trim(),
|
|
|
|
|
+ key: String(form.pay.key || '').trim(),
|
|
|
|
|
+ return_url: String(form.pay.return_url || '').trim(),
|
|
|
|
|
+ uni_return_url: String(form.pay.uni_return_url || '').trim(),
|
|
|
|
|
+ methods: form.pay.methods
|
|
|
|
|
+ .filter(item => item.enabled !== false && String(item.type || '').trim())
|
|
|
|
|
+ .map(item => ({
|
|
|
|
|
+ type: String(item.type).trim(),
|
|
|
|
|
+ name: String(item.name || item.type).trim()
|
|
|
|
|
+ }))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (form.key === 'email') {
|
|
|
|
|
+ return form.emails
|
|
|
|
|
+ .filter(item => item.enabled !== false && item.host && item.user)
|
|
|
|
|
+ .map((item, index) => ({
|
|
|
|
|
+ host: String(item.host || '').trim(),
|
|
|
|
|
+ port: Number(item.port) || 465,
|
|
|
|
|
+ user: String(item.user || '').trim(),
|
|
|
|
|
+ password: String(item.password || '').trim(),
|
|
|
|
|
+ from: String(item.from || item.user || '').trim(),
|
|
|
|
|
+ priority: Number(item.priority ?? index)
|
|
|
|
|
+ }))
|
|
|
|
|
+ .sort((a, b) => a.priority - b.priority)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (form.key === 'unilogin') {
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...(editing.value && typeof originalValue.value === 'object' ? originalValue.value : {}),
|
|
|
|
|
+ url: String(form.unilogin.url || '').trim(),
|
|
|
|
|
+ appid: String(form.unilogin.appid || '').trim(),
|
|
|
|
|
+ appkey: String(form.unilogin.appkey || '').trim(),
|
|
|
|
|
+ enabledTypes: form.unilogin.enabledTypes || []
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...(editing.value && typeof originalValue.value === 'object' ? originalValue.value : {}),
|
|
|
|
|
+ enabled: !!form.proxy.enabled,
|
|
|
|
|
+ url: String(form.proxy.url || '').trim(),
|
|
|
|
|
+ timeout: Number(form.proxy.timeout) || 120000
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function validateValue(value) {
|
|
|
|
|
+ if (!form.name.trim()) return '请输入方案名称'
|
|
|
|
|
+ if (form.key === 'pay') {
|
|
|
|
|
+ if (!value.url || !value.pid || !value.key || !value.return_url) return '请完整填写支付平台配置'
|
|
|
|
|
+ if (!value.methods.length) return '请至少启用一种付款方式'
|
|
|
|
|
+ }
|
|
|
|
|
+ if (form.key === 'email' && !value.length) return '请至少配置一个可用邮箱账号'
|
|
|
|
|
+ if (form.key === 'unilogin' && (!value.url || !value.appid || !value.appkey)) return '请完整填写聚合登录配置'
|
|
|
|
|
+ if (form.key === 'proxyForwardServer' && value.enabled && !value.url) return '启用代理时必须填写代理服务地址'
|
|
|
|
|
+ return ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function save() {
|
|
|
|
|
+ const value = buildValue()
|
|
|
|
|
+ const error = validateValue(value)
|
|
|
|
|
+ if (error) {
|
|
|
|
|
+ Message.error(error)
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const res = await saveRuntimeConfig({
|
|
|
|
|
+ key: form.key,
|
|
|
|
|
+ name: form.name.trim(),
|
|
|
|
|
+ value,
|
|
|
|
|
+ enabled: form.enabled,
|
|
|
|
|
+ isDefault: form.isDefault
|
|
|
|
|
+ })
|
|
|
|
|
+ if (res?.code !== 0) {
|
|
|
|
|
+ Message.error(res?.msg || '保存失败')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ Message.success('保存成功,已即时生效')
|
|
|
|
|
+ await load()
|
|
|
|
|
+ return true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function setCurrent(record) {
|
|
|
|
|
+ const res = await selectRuntimeConfig(record.config_key, record.config_name)
|
|
|
|
|
+ if (res?.code === 0) {
|
|
|
|
|
+ Message.success('切换成功')
|
|
|
|
|
+ await load()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Message.error(res?.msg || '切换失败')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function formatSummary(record) {
|
|
|
|
|
+ const value = record.config_value
|
|
|
|
|
+ if (record.config_key === 'pay') {
|
|
|
|
|
+ const count = Array.isArray(value?.methods) ? value.methods.length : 0
|
|
|
|
|
+ return `${value?.url || '未填写网关'},${count} 种付款方式`
|
|
|
|
|
+ }
|
|
|
|
|
+ if (record.config_key === 'email') {
|
|
|
|
|
+ return `${Array.isArray(value) ? value.length : 0} 个 SMTP 账号`
|
|
|
|
|
+ }
|
|
|
|
|
+ if (record.config_key === 'unilogin') {
|
|
|
|
|
+ return `${value?.url || '未填写接口'},${(value?.enabledTypes || ['qq', 'wx']).join(' / ')}`
|
|
|
|
|
+ }
|
|
|
|
|
+ if (record.config_key === 'proxyForwardServer') {
|
|
|
|
|
+ return `${value?.enabled === false ? '停用' : '启用'},${value?.url || '未填写地址'}`
|
|
|
|
|
+ }
|
|
|
|
|
+ return '-'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(load)
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.admin-config-page {
|
|
|
|
|
+ padding-bottom: 24px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.config-card {
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.hint {
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.config-tabs {
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary {
|
|
|
|
|
+ color: var(--color-text-2);
|
|
|
|
|
+ word-break: break-all;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.switch-row {
|
|
|
|
|
+ margin-bottom: 18px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.switch-item {
|
|
|
|
|
+ color: var(--color-text-2);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.form-section {
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ border: 1px solid var(--color-border-2);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ background: var(--color-fill-1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.section-title,
|
|
|
|
|
+.subhead {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 14px;
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: var(--color-text-1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.editable-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.editable-row {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: auto minmax(150px, 1fr) minmax(160px, 1fr) auto;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.email-card {
|
|
|
|
|
+ padding: 14px;
|
|
|
|
|
+ border: 1px solid var(--color-border-2);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ background: var(--color-bg-2);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.email-card-head {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@media (max-width: 640px) {
|
|
|
|
|
+ .editable-row {
|
|
|
|
|
+ grid-template-columns: 1fr;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .email-card-head {
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|