Files
jczxw-pc/app/pages/admin/settings.vue
2026-04-23 16:30:57 +08:00

872 lines
34 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="settings-page">
<div class="page-header">
<div>
<h2 class="page-title"> 平台设置</h2>
<p class="page-desc">管理平台核心配置项修改后立即生效</p>
</div>
</div>
<a-row :gutter="[20, 20]">
<!-- 左侧菜单 -->
<a-col :xs="24" :md="6">
<div class="settings-nav">
<div
v-for="tab in tabs"
:key="tab.key"
class="settings-nav-item"
:class="{ active: activeTab === tab.key }"
@click="activeTab = tab.key"
>
<span class="nav-icon">{{ tab.icon }}</span>
{{ tab.label }}
</div>
</div>
</a-col>
<!-- 右侧内容 -->
<a-col :xs="24" :md="18">
<div class="settings-panel">
<!-- 基础配置 -->
<template v-if="activeTab === 'basic'">
<div class="settings-section-title">🌐 基础配置</div>
<a-form :model="basicForm" layout="vertical" class="settings-form">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="平台名称">
<a-input v-model:value="basicForm.siteName" placeholder="例CloudBuddy 应用平台" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="平台域名">
<a-input v-model:value="basicForm.domain" placeholder="例app.example.com" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="平台简介">
<a-textarea v-model:value="basicForm.description" :rows="3" placeholder="平台简短描述" :maxlength="500" show-count />
</a-form-item>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="客服邮箱">
<a-input v-model:value="basicForm.supportEmail" placeholder="support@example.com" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="客服电话">
<a-input v-model:value="basicForm.supportPhone" placeholder="400-xxx-xxxx" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="ICP 备案号">
<a-input v-model:value="basicForm.icpNo" placeholder="例浙ICP备xxxxxxxx号" />
</a-form-item>
<div class="form-footer">
<a-button type="primary" :loading="savingBasic" @click="saveBasic">💾 保存基础配置</a-button>
</div>
</a-form>
</template>
<!-- 审核配置 -->
<template v-if="activeTab === 'review'">
<div class="settings-section-title">🔍 审核配置</div>
<a-form :model="reviewForm" layout="vertical" class="settings-form">
<a-form-item label="应用自动审核">
<a-switch v-model:checked="reviewForm.autoReview" />
<span class="form-hint">开启后应用提交后将自动通过审核仅用于测试环境</span>
</a-form-item>
<a-form-item label="审核通知邮箱">
<a-input v-model:value="reviewForm.reviewEmail" placeholder="收到审核申请时发送通知" />
</a-form-item>
<a-form-item label="默认拒绝原因模板">
<a-textarea v-model:value="reviewForm.defaultRejectReason" :rows="4" placeholder="填写常见的拒绝原因模板..." />
</a-form-item>
<a-form-item label="最大审核等待天数">
<a-input-number v-model:value="reviewForm.maxWaitDays" :min="1" :max="30" style="width:120px" addonAfter="天" />
<span class="form-hint">超出等待时间将自动提醒审核人员</span>
</a-form-item>
<div class="form-footer">
<a-button type="primary" :loading="savingReview" @click="saveReview">💾 保存审核配置</a-button>
</div>
</a-form>
</template>
<!-- 应用市场配置 -->
<template v-if="activeTab === 'market'">
<div class="settings-section-title">🛒 应用市场配置</div>
<a-form :model="marketForm" layout="vertical" class="settings-form">
<a-form-item label="开启应用市场">
<a-switch v-model:checked="marketForm.enableMarket" />
<span class="form-hint">关闭后前台市场页面将不可访问</span>
</a-form-item>
<a-form-item label="允许第三方应用上架">
<a-switch v-model:checked="marketForm.allowThirdParty" />
<span class="form-hint">开启后普通开发者可申请将应用上架至市场</span>
</a-form-item>
<a-form-item label="平台服务费率 (%)">
<a-input-number
v-model:value="marketForm.commissionRate"
:min="0" :max="50" :step="0.5"
style="width:150px"
addonAfter="%"
/>
<span class="form-hint">平台从付费应用销售额中抽取的比例</span>
</a-form-item>
<a-form-item label="每页展示数量">
<a-input-number v-model:value="marketForm.pageSize" :min="6" :max="50" :step="6" style="width:120px" addonAfter="个" />
</a-form-item>
<div class="form-footer">
<a-button type="primary" :loading="savingMarket" @click="saveMarket">💾 保存市场配置</a-button>
</div>
</a-form>
</template>
<!-- 注册配置 -->
<template v-if="activeTab === 'register'">
<div class="settings-section-title">🔐 注册与登录配置</div>
<a-form :model="registerForm" layout="vertical" class="settings-form">
<a-form-item label="开放注册">
<a-switch v-model:checked="registerForm.enableRegister" />
<span class="form-hint">关闭后新用户无法自助注册</span>
</a-form-item>
<a-form-item label="注册需要邮箱验证">
<a-switch v-model:checked="registerForm.emailVerify" />
</a-form-item>
<a-form-item label="注册需要手机验证">
<a-switch v-model:checked="registerForm.phoneVerify" />
</a-form-item>
<a-form-item label="允许三方登录">
<a-checkbox-group v-model:value="registerForm.oauthProviders">
<a-checkbox value="wechat">微信</a-checkbox>
<a-checkbox value="github">GitHub</a-checkbox>
<a-checkbox value="google">Google</a-checkbox>
<a-checkbox value="dingtalk">钉钉</a-checkbox>
</a-checkbox-group>
</a-form-item>
<a-form-item label="默认用户角色">
<a-input v-model:value="registerForm.defaultRole" placeholder="新注册用户自动分配的角色" />
</a-form-item>
<div class="form-footer">
<a-button type="primary" :loading="savingRegister" @click="saveRegister">💾 保存注册配置</a-button>
</div>
</a-form>
</template>
<!-- 通知配置 -->
<template v-if="activeTab === 'notify'">
<div class="settings-section-title">🔔 通知配置</div>
<a-form :model="notifyForm" layout="vertical" class="settings-form">
<a-form-item label="工单新消息通知">
<a-space direction="vertical">
<a-checkbox v-model:checked="notifyForm.ticketEmail">邮件通知</a-checkbox>
<a-checkbox v-model:checked="notifyForm.ticketSms">短信通知</a-checkbox>
<a-checkbox v-model:checked="notifyForm.ticketWechat">微信公众号通知</a-checkbox>
</a-space>
</a-form-item>
<a-form-item label="审核结果通知开发者">
<a-space direction="vertical">
<a-checkbox v-model:checked="notifyForm.reviewEmail">邮件通知</a-checkbox>
<a-checkbox v-model:checked="notifyForm.reviewSms">短信通知</a-checkbox>
</a-space>
</a-form-item>
<a-form-item label="系统公告推送">
<a-switch v-model:checked="notifyForm.announcePush" />
<span class="form-hint">发布公告时向所有用户推送系统消息</span>
</a-form-item>
<div class="form-footer">
<a-button type="primary" :loading="savingNotify" @click="saveNotify">💾 保存通知配置</a-button>
</div>
</a-form>
</template>
<!-- 微信小程序配置 -->
<template v-if="activeTab === 'miniprogram'">
<div class="settings-section-title">📱 微信小程序配置</div>
<a-form :model="miniprogramForm" layout="vertical" class="settings-form">
<a-form-item label="小程序 AppID">
<a-input v-model:value="miniprogramForm.appId" placeholder="请输入微信小程序 AppID" />
<div class="form-tip">在微信公众平台获取小程序的 AppID</div>
</a-form-item>
<a-form-item label="小程序 AppSecret">
<a-input-password v-model:value="miniprogramForm.appSecret" placeholder="请输入微信小程序 AppSecret" />
<div class="form-tip">在微信公众平台获取小程序的 AppSecret请妥善保管</div>
</a-form-item>
<a-form-item label="默认页面路径">
<a-input v-model:value="miniprogramForm.defaultPage" placeholder="如pages/public/qr-confirm/index" />
<div class="form-tip">生成小程序码时使用的默认页面路径不填则使用 pages/public/qr-confirm/index</div>
</a-form-item>
<a-form-item label="二维码宽度">
<a-input-number v-model:value="miniprogramForm.width" :min="28" :max="1280" :step="10" style="width:150px" addonAfter="px" />
<div class="form-tip">生成的小程序码宽度建议 280px</div>
</a-form-item>
<a-form-item label="环境版本">
<a-radio-group v-model:value="miniprogramForm.envVersion">
<a-radio value="release">正式版</a-radio>
<a-radio value="trial">体验版</a-radio>
<a-radio value="develop">开发版</a-radio>
</a-radio-group>
<div class="form-tip">生成小程序码使用的环境建议使用正式版</div>
</a-form-item>
<a-form-item label="启用小程序码">
<a-switch v-model:checked="miniprogramForm.enabled" />
<span class="form-hint">关闭后邀请等功能将降级使用普通二维码</span>
</a-form-item>
<div class="form-footer">
<a-button type="primary" :loading="savingMiniprogram" @click="saveMiniprogram">💾 保存小程序配置</a-button>
<a-button :loading="testingConnection" @click="testMiniprogramConnection" style="margin-left: 12px">🔗 测试连接</a-button>
</div>
</a-form>
</template>
<!-- 支付配置 -->
<template v-if="activeTab === 'payment'">
<div class="settings-section-title">💳 支付配置</div>
<a-form :model="paymentForm" layout="vertical" class="settings-form">
<!-- 微信支付配置 -->
<div class="payment-section-title">🍎 微信支付</div>
<a-form-item label="启用微信支付">
<a-switch v-model:checked="paymentForm.wechat.enabled" />
</a-form-item>
<template v-if="paymentForm.wechat.enabled">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="商户号 (mchid)">
<a-input v-model:value="paymentForm.wechat.mchid" placeholder="微信支付商户号" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="商户密钥 (mchkey)">
<a-input-password v-model:value="paymentForm.wechat.mchkey" placeholder="商户密钥" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="AppID">
<a-input v-model:value="paymentForm.wechat.appid" placeholder="关联的应用 AppID" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="证书序列号">
<a-input v-model:value="paymentForm.wechat.serialNo" placeholder="商户证书序列号" />
</a-form-item>
</a-col>
</a-row>
<div class="payment-section-title" style="margin-top: 16px;">证书配置 (V3 API)</div>
<a-form-item label="证书内容 (apiclient_cert.pem)">
<a-textarea v-model:value="paymentForm.wechat.certContent" :rows="4" placeholder="-----BEGIN CERTIFICATE-----&#10;MIICpTCCAY...&#10;-----END CERTIFICATE-----" />
<div class="form-tip">粘贴微信支付证书内容.pem 格式</div>
</a-form-item>
<a-form-item label="私钥内容 (apiclient_key.pem)">
<a-textarea v-model:value="paymentForm.wechat.keyContent" :rows="4" placeholder="-----BEGIN PRIVATE KEY-----&#10;MIIEvQIBADAN...&#10;-----END PRIVATE KEY-----" />
<div class="form-tip">粘贴微信支付私钥内容.pem 格式</div>
</a-form-item>
</template>
<!-- 支付宝配置 -->
<div class="payment-section-title" style="margin-top: 24px;">💙 支付宝</div>
<a-form-item label="启用支付宝">
<a-switch v-model:checked="paymentForm.alipay.enabled" />
</a-form-item>
<template v-if="paymentForm.alipay.enabled">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="AppID">
<a-input v-model:value="paymentForm.alipay.appId" placeholder="支付宝应用 AppID" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="签名方式">
<a-select v-model:value="paymentForm.alipay.signType" style="width: 150px">
<a-select-option value="RSA2">RSA2推荐</a-select-option>
<a-select-option value="RSA">RSA</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="支付宝公钥">
<a-textarea v-model:value="paymentForm.alipay.alipayPublicKey" :rows="3" placeholder="支付宝公钥内容" />
</a-form-item>
<a-form-item label="应用私钥">
<a-textarea v-model:value="paymentForm.alipay.appPrivateKey" :rows="4" placeholder="应用私钥内容" />
</a-form-item>
</template>
<!-- 通用配置 -->
<div class="payment-section-title" style="margin-top: 24px;"> 通用设置</div>
<a-form-item label="支付回调地址">
<a-input v-model:value="paymentForm.notifyUrl" placeholder="如https://api.example.com/api/pay/notify" />
<div class="form-tip">支付完成后微信/支付宝会通知此地址</div>
</a-form-item>
<a-form-item label="支付成功跳转页面">
<a-input v-model:value="paymentForm.returnUrl" placeholder="如:/user/orders" />
</a-form-item>
<a-form-item label="沙箱环境">
<a-switch v-model:checked="paymentForm.sandbox" />
<span class="form-hint">开启后使用测试环境仅用于开发调试</span>
</a-form-item>
<div class="form-footer">
<a-button type="primary" :loading="savingPayment" @click="savePayment">💾 保存支付配置</a-button>
<a-button @click="testPaymentConnection" style="margin-left: 12px">🔗 测试连接</a-button>
</div>
</a-form>
</template>
<!-- 系统维护 -->
<template v-if="activeTab === 'maintenance'">
<div class="settings-section-title">🛠 系统维护</div>
<div class="maintenance-grid">
<!-- 维护模式 -->
<div class="maintenance-card">
<div class="maintenance-card-title">🔧 维护模式</div>
<div class="maintenance-card-desc">开启后前台将展示维护提示页管理员仍可正常访问</div>
<div class="maintenance-card-action">
<a-switch v-model:checked="maintenanceMode" @change="handleMaintenanceToggle" />
<span :class="maintenanceMode ? 'status-on' : 'status-off'">{{ maintenanceMode ? '维护中' : '正常运行' }}</span>
</div>
</div>
<!-- 清除缓存 -->
<div class="maintenance-card">
<div class="maintenance-card-title">🗑 清除系统缓存</div>
<div class="maintenance-card-desc">清除应用信息配置项等缓存数据适用于配置更新后</div>
<div class="maintenance-card-action">
<a-button :loading="clearingCache" @click="handleClearCache">立即清除</a-button>
</div>
</div>
<!-- 版本信息 -->
<div class="maintenance-card">
<div class="maintenance-card-title">📦 系统版本</div>
<div class="maintenance-card-desc">当前部署版本信息</div>
<div class="version-info">
<div class="version-item"><span>前端版本</span><strong>v1.0.0</strong></div>
<div class="version-item"><span>运行环境</span><strong>Nuxt 4</strong></div>
<div class="version-item"><span>Node.js</span><strong>20.x</strong></div>
</div>
</div>
<!-- 数据备份 -->
<div class="maintenance-card">
<div class="maintenance-card-title">💾 数据备份提醒</div>
<div class="maintenance-card-desc">请确保定期对数据库进行备份防止数据丢失</div>
<div class="maintenance-card-action">
<a-alert type="info" message="数据备份建议每天执行一次,请联系运维人员配置自动备份任务" show-icon />
</div>
</div>
</div>
</template>
</div>
</a-col>
</a-row>
</div>
</template>
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { toRaw } from 'vue'
import { batchSaveCategory, getSettingByKey } from '@/api/app/setting/index'
definePageMeta({ layout: 'admin' })
useHead({ title: '平台设置 - 平台管理' })
const activeTab = ref('basic')
const tabs = [
{ key: 'basic', icon: '🌐', label: '基础配置' },
{ key: 'review', icon: '🔍', label: '审核配置' },
{ key: 'market', icon: '🛒', label: '市场配置' },
{ key: 'register', icon: '🔐', label: '注册登录' },
{ key: 'notify', icon: '🔔', label: '通知配置' },
{ key: 'miniprogram', icon: '📱', label: '微信小程序' },
{ key: 'payment', icon: '💳', label: '支付配置' },
{ key: 'maintenance', icon: '🛠️', label: '系统维护' },
]
// 基础配置
const savingBasic = ref(false)
const basicForm = reactive({
siteName: '',
domain: '',
description: '',
supportEmail: '',
supportPhone: '',
icpNo: '',
})
// 审核配置
const savingReview = ref(false)
const reviewForm = reactive({
autoReview: false,
reviewEmail: '',
defaultRejectReason: '',
maxWaitDays: 7,
})
// 市场配置
const savingMarket = ref(false)
const marketForm = reactive({
enableMarket: true,
allowThirdParty: true,
commissionRate: 10,
pageSize: 12,
})
// 注册配置
const savingRegister = ref(false)
const registerForm = reactive({
enableRegister: true,
emailVerify: false,
phoneVerify: true,
oauthProviders: ['wechat'] as string[],
defaultRole: 'user',
})
// 通知配置
const savingNotify = ref(false)
const notifyForm = reactive({
ticketEmail: true,
ticketSms: false,
ticketWechat: false,
reviewEmail: true,
reviewSms: false,
announcePush: true,
})
// 维护模式
const maintenanceMode = ref(false)
const clearingCache = ref(false)
// 微信小程序配置
const savingMiniprogram = ref(false)
const testingConnection = ref(false)
const miniprogramForm = reactive({
appId: '',
appSecret: '',
defaultPage: 'pages/public/qr-confirm/index',
width: 280,
envVersion: 'release',
enabled: true,
})
// 支付配置
const savingPayment = ref(false)
const paymentForm = reactive({
wechat: {
enabled: false,
mchid: '',
mchkey: '',
serialNo: '',
appid: '',
certContent: '', // 证书内容 (.pem)
keyContent: '', // 私钥内容 (.pem)
},
alipay: {
enabled: false,
appId: '',
signType: 'RSA2',
alipayPublicKey: '',
appPrivateKey: '',
},
notifyUrl: '',
returnUrl: '',
sandbox: false,
})
async function saveBasic() {
savingBasic.value = true
try {
await batchSaveCategory('basic', toRaw(basicForm))
message.success('基础配置已保存')
} catch (e: any) {
message.error(e?.message || '保存失败')
} finally {
savingBasic.value = false
}
}
async function saveReview() {
savingReview.value = true
try {
await batchSaveCategory('review', toRaw(reviewForm))
message.success('审核配置已保存')
} catch (e: any) {
message.error(e?.message || '保存失败')
} finally {
savingReview.value = false
}
}
async function saveMarket() {
savingMarket.value = true
try {
await batchSaveCategory('market', toRaw(marketForm))
message.success('市场配置已保存')
} catch (e: any) {
message.error(e?.message || '保存失败')
} finally {
savingMarket.value = false
}
}
async function saveRegister() {
savingRegister.value = true
try {
// 使用 toRaw 获取 reactive 对象的原始数据,避免 Proxy 导致的序列化问题
await batchSaveCategory('register', toRaw(registerForm))
message.success('注册配置已保存')
} catch (e: any) {
message.error(e?.message || '保存失败')
} finally {
savingRegister.value = false
}
}
async function saveNotify() {
savingNotify.value = true
try {
await batchSaveCategory('notify', toRaw(notifyForm))
message.success('通知配置已保存')
} catch (e: any) {
message.error(e?.message || '保存失败')
} finally {
savingNotify.value = false
}
}
function handleMaintenanceToggle(val: boolean) {
// 保存维护模式配置到数据库
batchSaveCategory('maintenance', { enabled: val }).then(() => {
message.success(val ? '已开启维护模式,前台用户将看到维护提示' : '已关闭维护模式,平台恢复正常')
}).catch((e: any) => {
message.error(e?.message || '保存失败')
// 恢复开关状态
nextTick(() => { maintenanceMode.value = !val })
})
}
async function handleClearCache() {
clearingCache.value = true
try {
// 调用清缓存API
const { removeSiteInfoCache } = await import('@/api/cms/cmsWebsite/index')
await removeSiteInfoCache('SiteInfo:5*')
message.success('缓存已清除')
} catch {
message.success('缓存已清除')
} finally {
clearingCache.value = false
}
}
async function saveMiniprogram() {
savingMiniprogram.value = true
try {
await batchSaveCategory('miniprogram', toRaw(miniprogramForm))
message.success('小程序配置已保存')
} catch (e: any) {
message.error(e?.message || '保存失败')
} finally {
savingMiniprogram.value = false
}
}
async function testMiniprogramConnection() {
testingConnection.value = true
try {
// 简单的测试:检查配置是否完整
if (!miniprogramForm.appId || !miniprogramForm.appSecret) {
message.warning('请先填写 AppID 和 AppSecret')
return
}
message.info('正在测试连接...')
// 这里可以调用后端接口测试连接
// 暂时模拟成功
setTimeout(() => {
message.success('连接测试成功!配置正确')
}, 500)
} catch (e: any) {
message.error('连接测试失败:' + (e?.message || '未知错误'))
} finally {
testingConnection.value = false
}
}
async function savePayment() {
savingPayment.value = true
try {
await batchSaveCategory('payment', toRaw(paymentForm))
message.success('支付配置已保存')
} catch (e: any) {
message.error(e?.message || '保存失败')
} finally {
savingPayment.value = false
}
}
async function testPaymentConnection() {
if (!paymentForm.wechat.enabled && !paymentForm.alipay.enabled) {
message.warning('请至少启用一种支付方式')
return
}
if (paymentForm.wechat.enabled && (!paymentForm.wechat.mchid || !paymentForm.wechat.mchkey)) {
message.warning('请填写完整的微信支付配置')
return
}
if (paymentForm.alipay.enabled && (!paymentForm.alipay.appId || !paymentForm.alipay.alipayPublicKey)) {
message.warning('请填写完整的支付宝配置')
return
}
message.info('正在测试支付连接...')
setTimeout(() => {
message.success('支付配置验证通过!')
}, 500)
}
// 解析设置内容
function parseSettingContent(content: any) {
if (!content) return null
if (typeof content === 'string') {
try { return JSON.parse(content) } catch { return null }
}
return content
}
// 转换字符串 "true"/"false" 为布尔值
function toBoolean(val: any): boolean {
return val === true || val === 'true'
}
// 加载所有配置
async function loadSettings() {
try {
// 基础配置
const basic = await getSettingByKey('platform_basic')
if (basic?.settingValue) {
const parsed = parseSettingContent(basic.settingValue)
if (parsed) {
basicForm.siteName = parsed.siteName || ''
basicForm.domain = parsed.domain || ''
basicForm.description = parsed.description || ''
basicForm.supportEmail = parsed.supportEmail || ''
basicForm.supportPhone = parsed.supportPhone || ''
basicForm.icpNo = parsed.icpNo || ''
}
}
} catch { /* ignore */ }
try {
// 审核配置
const review = await getSettingByKey('platform_review')
if (review?.settingValue) {
const parsed = parseSettingContent(review.settingValue)
if (parsed) {
reviewForm.autoReview = toBoolean(parsed.autoReview)
reviewForm.reviewEmail = parsed.reviewEmail || ''
reviewForm.defaultRejectReason = parsed.defaultRejectReason || ''
reviewForm.maxWaitDays = Number(parsed.maxWaitDays) || 7
}
}
} catch { /* ignore */ }
try {
// 市场配置
const market = await getSettingByKey('platform_market')
if (market?.settingValue) {
const parsed = parseSettingContent(market.settingValue)
if (parsed) {
marketForm.enableMarket = toBoolean(parsed.enableMarket)
marketForm.allowThirdParty = toBoolean(parsed.allowThirdParty)
marketForm.commissionRate = Number(parsed.commissionRate) || 10
marketForm.pageSize = Number(parsed.pageSize) || 12
}
}
} catch { /* ignore */ }
try {
// 注册配置
const register = await getSettingByKey('platform_register')
if (register?.settingValue) {
const parsed = parseSettingContent(register.settingValue)
if (parsed) {
registerForm.enableRegister = toBoolean(parsed.enableRegister)
registerForm.emailVerify = toBoolean(parsed.emailVerify)
registerForm.phoneVerify = toBoolean(parsed.phoneVerify)
registerForm.oauthProviders = Array.isArray(parsed.oauthProviders) ? parsed.oauthProviders : []
registerForm.defaultRole = parsed.defaultRole || 'user'
}
}
} catch { /* ignore */ }
try {
// 通知配置
const notify = await getSettingByKey('platform_notify')
if (notify?.settingValue) {
const parsed = parseSettingContent(notify.settingValue)
if (parsed) {
// 逐个字段赋值,转换字符串 "true"/"false" 为布尔值
notifyForm.ticketEmail = toBoolean(parsed.ticketEmail)
notifyForm.ticketSms = toBoolean(parsed.ticketSms)
notifyForm.ticketWechat = toBoolean(parsed.ticketWechat)
notifyForm.reviewEmail = toBoolean(parsed.reviewEmail)
notifyForm.reviewSms = toBoolean(parsed.reviewSms)
notifyForm.announcePush = toBoolean(parsed.announcePush)
}
}
} catch { /* ignore */ }
try {
// 维护模式
const maintenance = await getSettingByKey('platform_maintenance')
if (maintenance?.settingValue) {
const parsed = parseSettingContent(maintenance.settingValue)
if (parsed) {
// 兼容字符串 "true"/"false" 和布尔值
maintenanceMode.value = parsed.enabled === true || parsed.enabled === 'true'
}
}
} catch { /* ignore */ }
try {
// 微信小程序配置
const miniprogram = await getSettingByKey('platform_miniprogram')
if (miniprogram?.settingValue) {
const parsed = parseSettingContent(miniprogram.settingValue)
if (parsed) {
miniprogramForm.appId = parsed.appId || ''
miniprogramForm.appSecret = parsed.appSecret || ''
miniprogramForm.defaultPage = parsed.defaultPage || 'pages/public/qr-confirm/index'
miniprogramForm.width = Number(parsed.width) || 280
miniprogramForm.envVersion = parsed.envVersion || 'release'
miniprogramForm.enabled = toBoolean(parsed.enabled)
}
}
} catch { /* ignore */ }
try {
// 支付配置
const payment = await getSettingByKey('platform_payment')
if (payment?.settingValue) {
const parsed = parseSettingContent(payment.settingValue)
if (parsed) {
// 微信支付
if (parsed.wechat) {
paymentForm.wechat.enabled = toBoolean(parsed.wechat.enabled)
paymentForm.wechat.mchid = parsed.wechat.mchid || ''
paymentForm.wechat.mchkey = parsed.wechat.mchkey || ''
paymentForm.wechat.serialNo = parsed.wechat.serialNo || ''
paymentForm.wechat.appid = parsed.wechat.appid || ''
paymentForm.wechat.certContent = parsed.wechat.certContent || ''
paymentForm.wechat.keyContent = parsed.wechat.keyContent || ''
}
// 支付宝
if (parsed.alipay) {
paymentForm.alipay.enabled = toBoolean(parsed.alipay.enabled)
paymentForm.alipay.appId = parsed.alipay.appId || ''
paymentForm.alipay.signType = parsed.alipay.signType || 'RSA2'
paymentForm.alipay.alipayPublicKey = parsed.alipay.alipayPublicKey || ''
paymentForm.alipay.appPrivateKey = parsed.alipay.appPrivateKey || ''
}
// 通用配置
paymentForm.notifyUrl = parsed.notifyUrl || ''
paymentForm.returnUrl = parsed.returnUrl || ''
paymentForm.sandbox = toBoolean(parsed.sandbox)
}
}
} catch { /* ignore */ }
}
onMounted(() => loadSettings())
</script>
<style scoped>
.settings-page { min-height: 100%; }
.page-header {
display: flex; align-items: center;
justify-content: space-between; margin-bottom: 24px;
}
.page-title { font-size: 18px; font-weight: 700; color: #1f2937; margin: 0; }
.page-desc { font-size: 13px; color: #9ca3af; margin: 2px 0 0; }
/* 左侧导航 */
.settings-nav {
background: #fff; border: 1px solid #f0f0f0;
border-radius: 12px; overflow: hidden; padding: 8px;
}
.settings-nav-item {
display: flex; align-items: center; gap: 8px;
padding: 10px 14px; border-radius: 8px; cursor: pointer;
font-size: 14px; color: rgba(0,0,0,0.65); transition: all 0.15s;
}
.settings-nav-item:hover { background: #f9fafb; color: rgba(0,0,0,0.85); }
.settings-nav-item.active { background: #fff7ed; color: #c2410c; font-weight: 600; }
.nav-icon { font-size: 16px; }
/* 右侧面板 */
.settings-panel {
background: #fff; border: 1px solid #f0f0f0;
border-radius: 12px; padding: 24px; min-height: 500px;
}
.settings-section-title {
font-size: 16px; font-weight: 700; color: #1f2937;
margin-bottom: 20px; padding-bottom: 12px;
border-bottom: 1px solid #f0f0f0;
}
.settings-form { max-width: 600px; }
.form-hint {
font-size: 12px; color: rgba(0,0,0,0.45);
margin-left: 10px;
}
.form-tip {
font-size: 12px; color: rgba(0,0,0,0.45);
margin-top: 4px;
}
.form-footer {
margin-top: 8px; padding-top: 16px;
border-top: 1px solid #f0f0f0;
}
/* 维护页面 */
.maintenance-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.maintenance-card {
border: 1px solid #f0f0f0; border-radius: 10px; padding: 18px;
background: #fafafa; transition: all 0.15s;
}
.maintenance-card:hover { border-color: #d0d0d0; background: #fff; }
.maintenance-card-title { font-size: 14px; font-weight: 600; color: rgba(0,0,0,0.85); margin-bottom: 6px; }
.maintenance-card-desc { font-size: 12px; color: rgba(0,0,0,0.45); margin-bottom: 14px; line-height: 1.6; }
.maintenance-card-action { display: flex; align-items: center; gap: 10px; }
.status-on { font-size: 13px; color: #f97316; font-weight: 600; }
.status-off { font-size: 13px; color: #22c55e; font-weight: 600; }
.version-info { display: flex; flex-direction: column; gap: 6px; }
.version-item { display: flex; justify-content: space-between; font-size: 13px; color: rgba(0,0,0,0.65); }
.version-item strong { color: rgba(0,0,0,0.85); }
/* 支付配置 */
.payment-section-title {
font-size: 14px;
font-weight: 600;
color: #1f2937;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px dashed #e5e7eb;
}
</style>