初始化2
This commit is contained in:
571
app/pages/admin/settings.vue
Normal file
571
app/pages/admin/settings.vue
Normal file
@@ -0,0 +1,571 @@
|
||||
<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 === '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: '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)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 解析设置内容
|
||||
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 */ }
|
||||
}
|
||||
|
||||
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-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); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user