import React, { useState, useEffect, useRef, Suspense } from 'react' import axios from 'axios' import { MoonStarsIcon, SunIcon, TuneIcon, CheckIcon, CloseIcon, ExchangeIcon, SparkIcon, RefreshIcon, BuildingIcon } from './Icons' import { useAuth, hasPermission } from '../auth' import { useBranding } from '../branding' /* 这两块只有系统管理账号会看,按需加载:设置本身在外壳里是静态引入的, 跟着首屏一起下发等于给所有人多下载十几 KB。 */ const AIConfigSettings = React.lazy(() => import('./AIConfigSettings')) const OrgBrandingSettings = React.lazy(() => import('./OrgBrandingSettings')) /* 设置分类。带 permission 的项只对有该权限点的人显示 —— 智能配置里有接口地址与密钥,组织品牌是系统管理动作,都不该摆在业务员眼前。 */ const CATEGORIES = [ { key: 'appearance', label: '外观与主题', icon: TuneIcon }, { key: 'organization', label: '组织与品牌', icon: BuildingIcon, permission: 'branding.manage' }, { key: 'import-export', label: '数据交换', icon: ExchangeIcon }, { key: 'ai-config', label: '智能配置', icon: SparkIcon, permission: 'ai.manage' }, { key: 'system-update', label: '系统与更新', icon: RefreshIcon } ] function Settings({ isOpen, onClose, theme, onThemeChange, reducedMotion, onReducedMotionChange }) { const { user } = useAuth() const [activeCategory, setActiveCategory] = useState('appearance') const visibleCategories = CATEGORIES.filter(item => !item.permission || hasPermission(user, item.permission)) /* 打开着设置时权限被收回(换了角色),当前分类可能已经不可见,退回第一项 */ const current = visibleCategories.some(item => item.key === activeCategory) ? activeCategory : visibleCategories[0]?.key const modalRef = useRef(null) useEffect(() => { if (!isOpen) return const previousFocus = document.activeElement const previousOverflow = document.body.style.overflow document.body.style.overflow = 'hidden' const modal = modalRef.current const focusable = () => [...modal.querySelectorAll('button, input, select, textarea, a[href], [tabindex="0"]')].filter(el => !el.disabled && el.getClientRects().length) modal.querySelector('.close-btn')?.focus() const onKeyDown = event => { if (event.key === 'Escape') { event.preventDefault(); onClose() } if (event.key === 'Tab') { const items = focusable() const first = items[0], last = items[items.length - 1] if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last?.focus() } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first?.focus() } } } modal.addEventListener('keydown', onKeyDown) return () => { document.body.style.overflow = previousOverflow modal.removeEventListener('keydown', onKeyDown) previousFocus?.focus() } }, [isOpen, onClose]) if (!isOpen) return null return (
正在载入…
选择适合工作环境的显示模式,设置会自动保存在本机。
管理业务数据的导入、导出与本地备份。
支持 CSV 和 Excel 格式文件
建议定期备份数据库
查看当前版本与运行环境,并从官方仓库检查是否有新版本。
服务启动于 {fmtTime(info.startedAt)}
}默认抓取官方仓库,也支持填完整地址(https://github.com/owner/repo)。
上次检查:{fmtTime(result.checkedAt)} {result.cached ? '(结果来自缓存)' : ''}
)} {checking && (发布于 {fmtTime(result.publishedAt)}
} {(result.url || result.notes) && ({result.notes}}
>
)}
{!checking && !error && result?.ok && !result.hasUpdate && (
<>
{result.note}
} > )}