Files
X-Financial/web/src/views/SettingsView.vue

511 lines
22 KiB
Vue
Raw Normal View History

<template>
<section class="settings-page">
<div class="settings-shell panel">
<aside class="settings-nav" aria-label="系统设置分类">
<div class="settings-nav-head">
<span class="nav-kicker">Settings</span>
<h2>系统设置</h2>
<p>已完成 {{ completedSectionCount }} / {{ sections.length }} 项配置敏感字段不会保存在浏览器草稿中</p>
</div>
<nav class="settings-nav-list">
<button
v-for="section in sections"
:key="section.id"
class="settings-nav-item"
:class="{
active: activeSection === section.id,
complete: sectionStatus[section.id]
}"
type="button"
@click="activateSection(section.id)"
>
<span class="nav-item-copy">
<strong>{{ section.label }}</strong>
<small>{{ section.desc }}</small>
</span>
<span class="nav-item-state">
<i :class="sectionStatus[section.id] ? 'mdi mdi-check' : 'mdi mdi-chevron-right'"></i>
</span>
</button>
</nav>
<div class="settings-nav-foot">
<span>当前环境</span>
<strong>{{ pageState.companyForm.environment }}</strong>
</div>
</aside>
<div class="settings-body">
<header class="settings-toolbar">
<div class="settings-toolbar-copy">
<span class="settings-breadcrumb">首页 / 系统设置 / {{ activeSectionConfig.label }}</span>
<h3>{{ activeSectionConfig.title }}</h3>
<p>{{ activeSectionConfig.longDesc }}</p>
</div>
<div class="settings-toolbar-actions">
<span class="section-status" :class="{ complete: sectionStatus[activeSection] }">
<i :class="sectionStatus[activeSection] ? 'mdi mdi-check-decagram' : 'mdi mdi-progress-clock'"></i>
<span>{{ sectionStatus[activeSection] ? '当前项已就绪' : '当前项待补全' }}</span>
</span>
<button class="save-button" type="button" @click="saveActiveSection">
<i class="mdi mdi-content-save-outline"></i>
<span>{{ activeSectionConfig.actionLabel }}</span>
</button>
</div>
</header>
<div class="settings-content">
<template v-if="activeSection === 'profile'">
<section class="settings-card">
<div class="card-head">
<div>
<h4>系统基本信息</h4>
<p>统一维护企业名称显示名称和版权信息保存后左侧品牌名称会立即同步预览</p>
</div>
</div>
<div class="form-grid profile-grid">
<label class="field logo-field">
<span><em>*</em> 系统图标</span>
<div class="logo-tile" aria-hidden="true">
<i class="mdi mdi-domain"></i>
</div>
</label>
<label class="field">
<span><em>*</em> 企业名称</span>
<input v-model="pageState.companyForm.companyName" type="text" placeholder="请输入企业法定名称" />
</label>
<label class="field">
<span>企业编码</span>
<input v-model="pageState.companyForm.companyCode" type="text" placeholder="例如 XF-001" />
</label>
<label class="field field-wide">
<span><em>*</em> 系统显示名称</span>
<input v-model="pageState.companyForm.displayName" type="text" placeholder="请输入系统对内展示名称" />
</label>
<label class="field">
<span>备案号</span>
<input v-model="pageState.companyForm.recordNumber" type="text" placeholder="请输入备案号" />
</label>
<label class="field">
<span>运行环境</span>
<select v-model="pageState.companyForm.environment">
<option value="生产环境">生产环境</option>
<option value="预发布环境">预发布环境</option>
<option value="测试环境">测试环境</option>
</select>
</label>
<label class="field field-full">
<span><em>*</em> 版权信息</span>
<input
v-model="pageState.companyForm.copyright"
type="text"
placeholder="例如 Copyright © 2024-2026 X-Financial. All Rights Reserved."
/>
</label>
</div>
</section>
<section class="settings-card">
<div class="card-head">
<div>
<h4>品牌预览</h4>
<p>用于确认侧边栏品牌页脚版权和系统入口名称的实际展示效果</p>
</div>
</div>
<div class="preview-card">
<div class="preview-icon">
<i class="mdi mdi-domain"></i>
</div>
<div class="preview-copy">
<strong>{{ pageState.companyForm.displayName || '系统显示名称' }}</strong>
<p>{{ pageState.companyForm.companyName || '企业法定名称' }}</p>
<small>{{ pageState.companyForm.copyright || '版权信息将显示在这里' }}</small>
</div>
<span class="preview-badge">{{ pageState.companyForm.environment }}</span>
</div>
</section>
</template>
<template v-else-if="activeSection === 'admin'">
<section class="settings-card">
<div class="card-head">
<div>
<h4>管理员账号</h4>
<p>维护最高权限管理员的登录账户密码和安全通知邮箱</p>
</div>
</div>
<div class="form-grid">
<label class="field">
<span><em>*</em> 管理员账号</span>
<input v-model="pageState.adminForm.adminAccount" type="text" placeholder="请输入管理员账号" />
</label>
<label class="field">
<span><em>*</em> 管理员邮箱</span>
<input v-model="pageState.adminForm.adminEmail" type="email" placeholder="请输入管理员邮箱" />
</label>
<label class="field">
<span>新密码</span>
<input
v-model="pageState.adminForm.newPassword"
type="password"
autocomplete="new-password"
placeholder="至少 5 位"
/>
</label>
<label class="field">
<span>确认密码</span>
<input
v-model="pageState.adminForm.confirmPassword"
type="password"
autocomplete="new-password"
placeholder="再次输入管理员密码"
/>
</label>
</div>
</section>
<section class="settings-card">
<div class="card-head">
<div>
<h4>登录安全策略</h4>
<p>控制会话超时登录提醒和管理员高风险操作的基础安全策略</p>
</div>
</div>
<div class="form-grid compact-grid">
<label class="field">
<span><em>*</em> 会话超时分钟</span>
<input v-model.number="pageState.adminForm.sessionTimeout" type="number" min="5" max="240" />
</label>
<label class="field">
<span>安全通知邮箱</span>
<input v-model="pageState.adminForm.noticeEmail" type="email" placeholder="用于接收安全提醒" />
</label>
</div>
<div class="switch-group">
<button class="switch-row" type="button" @click="toggleBoolean('adminForm', 'mfaEnabled')">
<span class="switch-copy">
<strong>开启双因素验证</strong>
<small>要求管理员使用附加验证步骤登录后台</small>
</span>
<span class="switch" :class="{ active: pageState.adminForm.mfaEnabled }"><i></i></span>
</button>
<button class="switch-row" type="button" @click="toggleBoolean('adminForm', 'strongPassword')">
<span class="switch-copy">
<strong>启用强密码策略</strong>
<small>管理员密码修改时需要满足强度要求</small>
</span>
<span class="switch" :class="{ active: pageState.adminForm.strongPassword }"><i></i></span>
</button>
<button class="switch-row" type="button" @click="toggleBoolean('adminForm', 'loginAlertEnabled')">
<span class="switch-copy">
<strong>异常登录提醒</strong>
<small>检测到高风险登录时向安全通知邮箱发送告警</small>
</span>
<span class="switch" :class="{ active: pageState.adminForm.loginAlertEnabled }"><i></i></span>
</button>
</div>
</section>
</template>
<template v-else-if="activeSection === 'llm'">
<section class="settings-card">
<div class="card-head">
<div>
<h4>模型接入</h4>
<p>配置大语言模型的供应商模型名称和接入地址用于 AI 助手与识别流程</p>
</div>
</div>
<div class="form-grid">
<label class="field">
<span><em>*</em> 供应商</span>
<select v-model="pageState.llmForm.provider">
<option value="OpenAI Compatible">OpenAI Compatible</option>
<option value="Azure OpenAI">Azure OpenAI</option>
<option value="Ollama">Ollama</option>
<option value="自定义网关">自定义网关</option>
</select>
</label>
<label class="field">
<span><em>*</em> 模型名称</span>
<input v-model="pageState.llmForm.model" type="text" placeholder="请输入主模型名称" />
</label>
<label class="field field-full">
<span><em>*</em> 接口地址</span>
<input v-model="pageState.llmForm.endpoint" type="text" placeholder="请输入兼容接口地址" />
</label>
<label class="field">
<span>Embedding 模型</span>
<input v-model="pageState.llmForm.embeddingModel" type="text" placeholder="请输入向量模型名称" />
</label>
<label class="field">
<span>API Key</span>
<input v-model="pageState.llmForm.apiKey" type="password" autocomplete="off" placeholder="保存后不会保留在草稿中" />
</label>
</div>
</section>
<section class="settings-card">
<div class="card-head">
<div>
<h4>推理与知识策略</h4>
<p>控制响应质量输出长度以及知识库引用回溯等增强能力</p>
</div>
</div>
<div class="form-grid compact-grid">
<label class="field">
<span>推理模式</span>
<select v-model="pageState.llmForm.reasoningMode">
<option value="balanced">平衡</option>
<option value="quality">优先质量</option>
<option value="latency">优先速度</option>
</select>
</label>
<label class="field">
<span>最大 Token</span>
<input v-model.number="pageState.llmForm.maxTokens" type="number" min="512" step="256" />
</label>
<label class="field field-full">
<span>Temperature</span>
<div class="range-shell">
<input v-model.number="pageState.llmForm.temperature" type="range" min="0" max="1" step="0.1" />
<strong>{{ pageState.llmForm.temperature.toFixed(1) }}</strong>
</div>
</label>
</div>
<div class="switch-group">
<button class="switch-row" type="button" @click="toggleBoolean('llmForm', 'knowledgeEnabled')">
<span class="switch-copy">
<strong>启用知识库检索</strong>
<small>允许模型在回答时结合制度知识库和业务文档</small>
</span>
<span class="switch" :class="{ active: pageState.llmForm.knowledgeEnabled }"><i></i></span>
</button>
<button class="switch-row" type="button" @click="toggleBoolean('llmForm', 'citationEnabled')">
<span class="switch-copy">
<strong>输出引用来源</strong>
<small> AI 助手回答中附带依据与来源提示</small>
</span>
<span class="switch" :class="{ active: pageState.llmForm.citationEnabled }"><i></i></span>
</button>
</div>
</section>
</template>
<template v-else-if="activeSection === 'logs'">
<section class="settings-card">
<div class="card-head">
<div>
<h4>日志级别与留存</h4>
<p>定义系统记录粒度归档周期和告警接收人方便后续审计与排障</p>
</div>
</div>
<div class="chip-row">
<button
v-for="level in logLevels"
:key="level"
class="level-chip"
:class="{ active: pageState.logForm.level === level }"
type="button"
@click="pageState.logForm.level = level"
>
{{ level }}
</button>
</div>
<div class="form-grid">
<label class="field">
<span><em>*</em> 留存天数</span>
<input v-model.number="pageState.logForm.retentionDays" type="number" min="7" />
</label>
<label class="field">
<span>归档周期</span>
<select v-model="pageState.logForm.archiveCycle">
<option value="daily">按天归档</option>
<option value="weekly">按周归档</option>
<option value="monthly">按月归档</option>
</select>
</label>
<label class="field field-full">
<span><em>*</em> 日志路径</span>
<input v-model="pageState.logForm.logPath" type="text" placeholder="例如 server/logs/app.log" />
</label>
<label class="field field-full">
<span>告警邮箱</span>
<input v-model="pageState.logForm.alertEmail" type="email" placeholder="用于接收日志异常提醒" />
</label>
</div>
</section>
<section class="settings-card">
<div class="card-head">
<div>
<h4>审计策略</h4>
<p>决定是否记录关键操作登录行为以及是否对敏感字段进行脱敏处理</p>
</div>
</div>
<div class="switch-group">
<button class="switch-row" type="button" @click="toggleBoolean('logForm', 'operationAudit')">
<span class="switch-copy">
<strong>记录关键操作日志</strong>
<small>保存配置修改审批动作和账户管理等重要事件</small>
</span>
<span class="switch" :class="{ active: pageState.logForm.operationAudit }"><i></i></span>
</button>
<button class="switch-row" type="button" @click="toggleBoolean('logForm', 'loginAudit')">
<span class="switch-copy">
<strong>记录登录审计</strong>
<small>追踪登录来源登录结果和异常登录行为</small>
</span>
<span class="switch" :class="{ active: pageState.logForm.loginAudit }"><i></i></span>
</button>
<button class="switch-row" type="button" @click="toggleBoolean('logForm', 'maskSensitive')">
<span class="switch-copy">
<strong>敏感字段脱敏</strong>
<small>日志写入时自动隐藏密码密钥与认证令牌</small>
</span>
<span class="switch" :class="{ active: pageState.logForm.maskSensitive }"><i></i></span>
</button>
</div>
</section>
</template>
<template v-else>
<section class="settings-card">
<div class="card-head">
<div>
<h4>SMTP 基础配置</h4>
<p>维护系统发信地址认证账号和加密方式用于审批提醒与系统通知</p>
</div>
</div>
<div class="form-grid">
<label class="field">
<span><em>*</em> SMTP Host</span>
<input v-model="pageState.mailForm.smtpHost" type="text" placeholder="请输入 SMTP Host" />
</label>
<label class="field">
<span><em>*</em> 端口</span>
<input v-model.number="pageState.mailForm.port" type="number" min="1" max="65535" />
</label>
<label class="field">
<span>加密方式</span>
<select v-model="pageState.mailForm.encryption">
<option value="SSL/TLS">SSL/TLS</option>
<option value="STARTTLS">STARTTLS</option>
<option value="None"></option>
</select>
</label>
<label class="field">
<span>发件人名称</span>
<input v-model="pageState.mailForm.senderName" type="text" placeholder="请输入发件人名称" />
</label>
<label class="field">
<span><em>*</em> 发件人邮箱</span>
<input v-model="pageState.mailForm.senderAddress" type="email" placeholder="请输入发件人邮箱" />
</label>
<label class="field">
<span><em>*</em> 登录账号</span>
<input v-model="pageState.mailForm.username" type="text" placeholder="请输入 SMTP 登录账号" />
</label>
<label class="field field-full">
<span>SMTP 密码</span>
<input v-model="pageState.mailForm.password" type="password" autocomplete="off" placeholder="保存后不会保留在草稿中" />
</label>
</div>
</section>
<section class="settings-card">
<div class="card-head">
<div>
<h4>通知策略</h4>
<p>控制是否启用邮件通知日报摘要以及默认接收邮箱</p>
</div>
</div>
<div class="switch-group">
<button class="switch-row" type="button" @click="toggleBoolean('mailForm', 'alertEnabled')">
<span class="switch-copy">
<strong>启用系统通知</strong>
<small>审批异常告警和系统事件可通过邮件触达用户</small>
</span>
<span class="switch" :class="{ active: pageState.mailForm.alertEnabled }"><i></i></span>
</button>
<button class="switch-row" type="button" @click="toggleBoolean('mailForm', 'digestEnabled')">
<span class="switch-copy">
<strong>启用日报摘要</strong>
<small>按固定时间发送系统运行与待办摘要</small>
</span>
<span class="switch" :class="{ active: pageState.mailForm.digestEnabled }"><i></i></span>
</button>
</div>
<div class="form-grid compact-grid">
<label class="field">
<span>摘要发送时间</span>
<input v-model="pageState.mailForm.digestTime" type="time" />
</label>
<label class="field">
<span>默认接收邮箱</span>
<input v-model="pageState.mailForm.defaultReceiver" type="email" placeholder="请输入默认接收邮箱" />
</label>
</div>
</section>
</template>
</div>
</div>
</div>
</section>
</template>
<script src="./scripts/SettingsView.js"></script>
<style scoped src="../assets/styles/views/settings-view.css"></style>