import { computed, ref } from 'vue' import { useSystemState } from '../../composables/useSystemState.js' import { useToast } from '../../composables/useToast.js' const SETTINGS_STORAGE_KEY = 'x-financial-settings-draft' const CURRENT_YEAR = new Date().getFullYear() const SECTION_DEFINITIONS = [ { id: 'profile', label: '企业信息', title: '系统基本信息', desc: '公司名称、品牌与版权', longDesc: '统一维护企业名称、系统显示名和版权信息,保存后会直接同步到当前界面的品牌预览。', actionLabel: '保存企业信息' }, { id: 'admin', label: '管理员安全', title: '管理员账号与安全策略', desc: '账号、密码与登录安全', longDesc: '管理最高权限管理员的账号、密码和登录安全策略,密码类字段仅用于本次填写,不会进入浏览器草稿。', actionLabel: '保存安全设置' }, { id: 'llm', label: '大语言模型', title: '模型接入配置', desc: '供应商、模型与推理策略', longDesc: '配置 AI 助手与识别流程依赖的大模型接入信息,并维护推理模式、知识检索和输出行为。', actionLabel: '保存模型配置' }, { id: 'logs', label: '日志策略', title: '日志与审计策略', desc: '日志级别、留存与脱敏', longDesc: '定义系统日志级别、留存周期和审计策略,保证后续排障、追溯和安全审计有完整依据。', actionLabel: '保存日志策略' }, { id: 'mail', label: '邮箱设置', title: '邮箱通知配置', desc: 'SMTP 与通知投递策略', longDesc: '维护系统邮件发送配置和通知投递策略,审批、预警和摘要邮件都会依赖这里的设置。', actionLabel: '保存邮箱配置' } ] const LOG_LEVELS = ['DEBUG', 'INFO', 'WARN', 'ERROR'] function normalizeValue(value) { return String(value ?? '').trim() } function buildDefaultState(companyProfile, currentUser) { const companyName = normalizeValue(companyProfile?.name) || 'X-Financial' const companyCode = normalizeValue(companyProfile?.code) || 'XF-001' const adminEmail = normalizeValue(companyProfile?.adminEmail) || normalizeValue(currentUser?.email) || 'admin@example.com' const adminAccount = normalizeValue(currentUser?.username) || 'superadmin' return { companyForm: { companyName, displayName: companyName, companyCode, recordNumber: '', environment: '生产环境', copyright: `Copyright © 2024-${CURRENT_YEAR} ${companyName}. All Rights Reserved.` }, adminForm: { adminAccount, adminEmail, newPassword: '', confirmPassword: '', sessionTimeout: Number(import.meta.env.VITE_AUTH_IDLE_TIMEOUT_MINUTES || 30), noticeEmail: adminEmail, mfaEnabled: true, strongPassword: true, loginAlertEnabled: true }, llmForm: { provider: 'OpenAI Compatible', model: 'gpt-4.1-mini', endpoint: 'https://api.openai.com/v1', embeddingModel: 'text-embedding-3-large', apiKey: '', reasoningMode: 'balanced', maxTokens: 4096, temperature: 0.2, knowledgeEnabled: true, citationEnabled: true }, logForm: { level: 'INFO', retentionDays: 180, archiveCycle: 'weekly', logPath: 'server/logs/app.log', alertEmail: adminEmail, operationAudit: true, loginAudit: true, maskSensitive: true }, mailForm: { smtpHost: 'smtp.exmail.qq.com', port: 465, encryption: 'SSL/TLS', senderName: companyName, senderAddress: adminEmail, username: adminEmail, password: '', alertEnabled: true, digestEnabled: false, digestTime: '09:00', defaultReceiver: adminEmail } } } function readStoredSettings() { if (typeof window === 'undefined') { return null } const raw = window.sessionStorage.getItem(SETTINGS_STORAGE_KEY) if (!raw) { return null } try { return JSON.parse(raw) } catch { return null } } function mergeStoredState(defaults, stored) { return { companyForm: { ...defaults.companyForm, ...(stored?.companyForm || {}) }, adminForm: { ...defaults.adminForm, ...(stored?.adminForm || {}) }, llmForm: { ...defaults.llmForm, ...(stored?.llmForm || {}) }, logForm: { ...defaults.logForm, ...(stored?.logForm || {}) }, mailForm: { ...defaults.mailForm, ...(stored?.mailForm || {}) } } } function sanitizeForStorage(state) { return { companyForm: { ...state.companyForm }, adminForm: { ...state.adminForm, newPassword: '', confirmPassword: '' }, llmForm: { ...state.llmForm, apiKey: '' }, logForm: { ...state.logForm }, mailForm: { ...state.mailForm, password: '' } } } function persistSettings(state) { if (typeof window === 'undefined') { return } window.sessionStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(sanitizeForStorage(state))) } function computeSectionStatus(state) { return { profile: Boolean( normalizeValue(state.companyForm.companyName) && normalizeValue(state.companyForm.displayName) && normalizeValue(state.companyForm.copyright) ), admin: Boolean( normalizeValue(state.adminForm.adminAccount) && normalizeValue(state.adminForm.adminEmail) && Number(state.adminForm.sessionTimeout) >= 5 ), llm: Boolean( normalizeValue(state.llmForm.provider) && normalizeValue(state.llmForm.model) && normalizeValue(state.llmForm.endpoint) ), logs: Boolean( normalizeValue(state.logForm.level) && Number(state.logForm.retentionDays) > 0 && normalizeValue(state.logForm.logPath) ), mail: Boolean( normalizeValue(state.mailForm.smtpHost) && Number(state.mailForm.port) > 0 && normalizeValue(state.mailForm.senderAddress) && normalizeValue(state.mailForm.username) ) } } export default { name: 'SettingsView', setup() { const { toast } = useToast() const { companyProfile, currentUser, updateCompanyProfilePreview } = useSystemState() const defaults = buildDefaultState(companyProfile.value, currentUser.value) const pageState = ref(mergeStoredState(defaults, readStoredSettings())) const activeSection = ref('profile') const sections = SECTION_DEFINITIONS const logLevels = LOG_LEVELS const sectionStatus = computed(() => computeSectionStatus(pageState.value)) const completedSectionCount = computed(() => Object.values(sectionStatus.value).filter(Boolean).length) const activeSectionConfig = computed( () => sections.find((section) => section.id === activeSection.value) || sections[0] ) function activateSection(sectionId) { activeSection.value = sectionId } function toggleBoolean(formKey, field) { pageState.value[formKey][field] = !pageState.value[formKey][field] } function saveProfileSection() { const companyForm = pageState.value.companyForm if (!normalizeValue(companyForm.companyName)) { toast('请输入企业名称。') return } if (!normalizeValue(companyForm.displayName)) { toast('请输入系统显示名称。') return } if (!normalizeValue(companyForm.copyright)) { toast('请输入版权信息。') return } updateCompanyProfilePreview({ name: normalizeValue(companyForm.displayName), code: normalizeValue(companyForm.companyCode) }) pageState.value.mailForm.senderName = normalizeValue(companyForm.displayName) persistSettings(pageState.value) toast('企业信息已保存并应用到当前界面预览。') } function saveAdminSection() { const adminForm = pageState.value.adminForm if (!normalizeValue(adminForm.adminAccount)) { toast('请输入管理员账号。') return } if (!normalizeValue(adminForm.adminEmail)) { toast('请输入管理员邮箱。') return } if (Number(adminForm.sessionTimeout) < 5) { toast('会话超时时间不能少于 5 分钟。') return } if (adminForm.newPassword) { if (adminForm.newPassword.length < 5) { toast('管理员密码至少需要 5 位。') return } if (adminForm.newPassword !== adminForm.confirmPassword) { toast('两次输入的管理员密码不一致。') return } } updateCompanyProfilePreview({ adminEmail: normalizeValue(adminForm.adminEmail) }) persistSettings(pageState.value) adminForm.newPassword = '' adminForm.confirmPassword = '' toast('管理员安全设置已保存。') } function saveLlmSection() { const llmForm = pageState.value.llmForm if ( !normalizeValue(llmForm.provider) || !normalizeValue(llmForm.model) || !normalizeValue(llmForm.endpoint) ) { toast('请完整填写模型供应商、模型名称和接口地址。') return } persistSettings(pageState.value) llmForm.apiKey = '' toast('模型配置已保存。') } function saveLogsSection() { const logForm = pageState.value.logForm if (!normalizeValue(logForm.level) || Number(logForm.retentionDays) <= 0) { toast('请填写有效的日志级别和留存天数。') return } if (!normalizeValue(logForm.logPath)) { toast('请输入日志路径。') return } persistSettings(pageState.value) toast('日志策略已保存。') } function saveMailSection() { const mailForm = pageState.value.mailForm if (!normalizeValue(mailForm.smtpHost) || Number(mailForm.port) <= 0) { toast('请填写有效的 SMTP Host 和端口。') return } if (!normalizeValue(mailForm.senderAddress) || !normalizeValue(mailForm.username)) { toast('请填写发件人邮箱和 SMTP 登录账号。') return } persistSettings(pageState.value) mailForm.password = '' toast('邮箱配置已保存。') } function saveActiveSection() { if (activeSection.value === 'profile') { saveProfileSection() return } if (activeSection.value === 'admin') { saveAdminSection() return } if (activeSection.value === 'llm') { saveLlmSection() return } if (activeSection.value === 'logs') { saveLogsSection() return } saveMailSection() } return { activeSection, activeSectionConfig, activateSection, completedSectionCount, logLevels, pageState, saveActiveSection, sectionStatus, sections, toggleBoolean } } }