引入 Element Plus 主题定制和主题皮肤 composable,将全局 样式拆分为组件级独立 CSS 文件(侧边栏、顶栏、工作台等), 统一色彩变量和间距规范,重构所有视图和组件样式以适配新 主题系统,优化图表和知识图谱组件视觉表现,提取审计和差 旅报销相关子组件。
283 lines
8.6 KiB
JavaScript
283 lines
8.6 KiB
JavaScript
import { computed, ref } from 'vue'
|
|
|
|
const THEME_SKIN_STORAGE_KEY = 'x-financial-theme-skin'
|
|
const DEFAULT_THEME_SKIN_ID = 'sky'
|
|
|
|
const DEFAULT_SEMANTIC_COLORS = {
|
|
success: '#2f855a',
|
|
successHover: '#276749',
|
|
successActive: '#22543d',
|
|
successSoft: '#f0f7f2',
|
|
successLine: '#cde6d5',
|
|
warning: '#b7791f',
|
|
warningHover: '#975a16',
|
|
warningActive: '#7b4514',
|
|
warningSoft: '#fff8eb',
|
|
warningLine: '#efd9af',
|
|
danger: '#dc2626',
|
|
dangerHover: '#b91c1c',
|
|
dangerActive: '#991b1b',
|
|
dangerSoft: '#fef2f2',
|
|
dangerLine: '#fecaca',
|
|
info: '#475569',
|
|
infoHover: '#334155',
|
|
infoActive: '#1e293b',
|
|
infoSoft: '#f1f5f9',
|
|
infoLine: '#cbd5e1'
|
|
}
|
|
|
|
export const THEME_SKIN_OPTIONS = [
|
|
{
|
|
id: 'sky',
|
|
label: '浅蓝企业',
|
|
desc: '默认皮肤,降低蓝色饱和度,适合财务 SaaS 和审批后台。',
|
|
primary: '#3a7ca5',
|
|
primaryHover: '#2f6d95',
|
|
primaryActive: '#255b7d',
|
|
primarySoft: '#eaf4fa',
|
|
primarySoftStrong: '#d4e8f3',
|
|
secondary: '#4f6f9f',
|
|
chartBlue: '#4f6f9f',
|
|
chartPurple: '#6e7fa6',
|
|
chartAmber: '#b58b4c'
|
|
},
|
|
{
|
|
id: 'blue',
|
|
label: '湖蓝灰',
|
|
desc: '偏灰的湖蓝色,弱化科技感,更适合高密度运营页面。',
|
|
primary: '#477c9e',
|
|
primaryHover: '#3a6a89',
|
|
primaryActive: '#305873',
|
|
primarySoft: '#edf5f8',
|
|
primarySoftStrong: '#d8e8ef',
|
|
secondary: '#5d7288',
|
|
chartBlue: '#477c9e',
|
|
chartPurple: '#77799c',
|
|
chartAmber: '#b28a54'
|
|
},
|
|
{
|
|
id: 'navy',
|
|
label: '稳健蓝',
|
|
desc: '偏金融和管理驾驶舱的稳重蓝,适合长时间办公查看。',
|
|
primary: '#4b6f95',
|
|
primaryHover: '#405f80',
|
|
primaryActive: '#354e69',
|
|
primarySoft: '#eef3f8',
|
|
primarySoftStrong: '#dbe6f0',
|
|
secondary: '#6b7280',
|
|
chartBlue: '#4b6f95',
|
|
chartPurple: '#69769d',
|
|
chartAmber: '#aa8a55'
|
|
},
|
|
{
|
|
id: 'teal',
|
|
label: '雾青',
|
|
desc: '保留绿色倾向但降低鲜艳度,比旧绿色更克制。',
|
|
primary: '#3f827c',
|
|
primaryHover: '#36706b',
|
|
primaryActive: '#2d5c58',
|
|
primarySoft: '#eef8f6',
|
|
primarySoftStrong: '#d8ebe8',
|
|
secondary: '#4f6f9f',
|
|
chartBlue: '#4f7f9f',
|
|
chartPurple: '#708099',
|
|
chartAmber: '#b18a53'
|
|
},
|
|
{
|
|
id: 'legacy-green',
|
|
label: '经典绿',
|
|
desc: '保留旧版系统绿色,适合继续沿用原有品牌记忆。',
|
|
primary: '#10b981',
|
|
primaryHover: '#059669',
|
|
primaryActive: '#047857',
|
|
primarySoft: '#ecfdf5',
|
|
primarySoftStrong: '#d1fae5',
|
|
secondary: '#2563eb',
|
|
chartBlue: '#2563eb',
|
|
chartPurple: '#6d6a9f',
|
|
chartAmber: '#b88a44'
|
|
},
|
|
{
|
|
id: 'sage',
|
|
label: '鼠尾草绿',
|
|
desc: '低饱和灰绿色,比经典绿更安静,适合企业内控场景。',
|
|
primary: '#5f8d72',
|
|
primaryHover: '#517b62',
|
|
primaryActive: '#436653',
|
|
primarySoft: '#f0f7f2',
|
|
primarySoftStrong: '#dcebe0',
|
|
secondary: '#4f6f9f',
|
|
chartBlue: '#4f748f',
|
|
chartPurple: '#7a7898',
|
|
chartAmber: '#a98753'
|
|
},
|
|
{
|
|
id: 'slate',
|
|
label: '石板灰蓝',
|
|
desc: '弱主色方案,适合审计、规则和报表密集页面。',
|
|
primary: '#64748b',
|
|
primaryHover: '#526174',
|
|
primaryActive: '#3f4a5a',
|
|
primarySoft: '#f1f5f9',
|
|
primarySoftStrong: '#e2e8f0',
|
|
secondary: '#3a7ca5',
|
|
chartBlue: '#5d7590',
|
|
chartPurple: '#77748f',
|
|
chartAmber: '#a88955'
|
|
},
|
|
{
|
|
id: 'soft-violet',
|
|
label: '灰紫蓝',
|
|
desc: '保留一点智能系统气质,但用灰度压低 AI 感和饱和度。',
|
|
primary: '#6d6a9f',
|
|
primaryHover: '#5f5b8c',
|
|
primaryActive: '#504c78',
|
|
primarySoft: '#f2f1f8',
|
|
primarySoftStrong: '#e2e0ef',
|
|
secondary: '#477c9e',
|
|
chartBlue: '#4f7495',
|
|
chartPurple: '#6d6a9f',
|
|
chartAmber: '#a98857'
|
|
}
|
|
]
|
|
|
|
const activeThemeSkinId = ref(DEFAULT_THEME_SKIN_ID)
|
|
|
|
function findThemeSkin(id) {
|
|
return THEME_SKIN_OPTIONS.find((skin) => skin.id === id) || THEME_SKIN_OPTIONS[0]
|
|
}
|
|
|
|
function hexToRgb(hex) {
|
|
const normalized = String(hex || '').replace('#', '')
|
|
const value = normalized.length === 3
|
|
? normalized.split('').map((item) => item + item).join('')
|
|
: normalized
|
|
const numberValue = Number.parseInt(value, 16)
|
|
|
|
if (!Number.isFinite(numberValue) || value.length !== 6) {
|
|
return '58, 124, 165'
|
|
}
|
|
|
|
return [
|
|
(numberValue >> 16) & 255,
|
|
(numberValue >> 8) & 255,
|
|
numberValue & 255
|
|
].join(', ')
|
|
}
|
|
|
|
function setVariables(root, variables) {
|
|
Object.entries(variables).forEach(([key, value]) => {
|
|
root.style.setProperty(key, value)
|
|
})
|
|
}
|
|
|
|
function applyThemeSkin(skin) {
|
|
if (typeof document === 'undefined') {
|
|
return
|
|
}
|
|
|
|
const root = document.documentElement
|
|
const primaryRgb = hexToRgb(skin.primary)
|
|
const secondaryRgb = hexToRgb(skin.secondary)
|
|
const successRgb = hexToRgb(DEFAULT_SEMANTIC_COLORS.success)
|
|
const warningRgb = hexToRgb(DEFAULT_SEMANTIC_COLORS.warning)
|
|
const dangerRgb = hexToRgb(DEFAULT_SEMANTIC_COLORS.danger)
|
|
const infoRgb = hexToRgb(DEFAULT_SEMANTIC_COLORS.info)
|
|
|
|
root.dataset.themeSkin = skin.id
|
|
|
|
setVariables(root, {
|
|
'--primary': skin.primary,
|
|
'--primary-hover': skin.primaryHover,
|
|
'--primary-active': skin.primaryActive,
|
|
'--primary-soft': skin.primarySoft,
|
|
'--primary-soft-strong': skin.primarySoftStrong,
|
|
'--primary-rgb': primaryRgb,
|
|
'--secondary': skin.secondary,
|
|
'--secondary-rgb': secondaryRgb,
|
|
'--theme-secondary': skin.secondary,
|
|
'--theme-secondary-rgb': secondaryRgb,
|
|
'--theme-primary': skin.primary,
|
|
'--theme-primary-hover': skin.primaryHover,
|
|
'--theme-primary-active': skin.primaryActive,
|
|
'--theme-primary-soft': skin.primarySoft,
|
|
'--theme-primary-soft-strong': skin.primarySoftStrong,
|
|
'--theme-primary-light-5': 'color-mix(in srgb, var(--theme-primary) 46%, white)',
|
|
'--theme-primary-light-9': 'color-mix(in srgb, var(--theme-primary) 8%, white)',
|
|
'--theme-primary-rgb': primaryRgb,
|
|
'--theme-primary-shadow': `rgba(${primaryRgb}, 0.16)`,
|
|
'--theme-focus-ring': `rgba(${primaryRgb}, 0.12)`,
|
|
'--theme-gradient-primary': `linear-gradient(135deg, ${skin.primary}, ${skin.primaryActive})`,
|
|
'--chart-primary': skin.primary,
|
|
'--chart-primary-rgb': primaryRgb,
|
|
'--chart-blue': skin.chartBlue,
|
|
'--chart-purple': skin.chartPurple,
|
|
'--chart-amber': skin.chartAmber,
|
|
'--success': DEFAULT_SEMANTIC_COLORS.success,
|
|
'--success-hover': DEFAULT_SEMANTIC_COLORS.successHover,
|
|
'--success-active': DEFAULT_SEMANTIC_COLORS.successActive,
|
|
'--success-soft': DEFAULT_SEMANTIC_COLORS.successSoft,
|
|
'--success-line': DEFAULT_SEMANTIC_COLORS.successLine,
|
|
'--success-rgb': successRgb,
|
|
'--warning': DEFAULT_SEMANTIC_COLORS.warning,
|
|
'--warning-hover': DEFAULT_SEMANTIC_COLORS.warningHover,
|
|
'--warning-active': DEFAULT_SEMANTIC_COLORS.warningActive,
|
|
'--warning-soft': DEFAULT_SEMANTIC_COLORS.warningSoft,
|
|
'--warning-line': DEFAULT_SEMANTIC_COLORS.warningLine,
|
|
'--warning-rgb': warningRgb,
|
|
'--danger': DEFAULT_SEMANTIC_COLORS.danger,
|
|
'--danger-hover': DEFAULT_SEMANTIC_COLORS.dangerHover,
|
|
'--danger-active': DEFAULT_SEMANTIC_COLORS.dangerActive,
|
|
'--danger-soft': DEFAULT_SEMANTIC_COLORS.dangerSoft,
|
|
'--danger-line': DEFAULT_SEMANTIC_COLORS.dangerLine,
|
|
'--danger-rgb': dangerRgb,
|
|
'--info': DEFAULT_SEMANTIC_COLORS.info,
|
|
'--info-hover': DEFAULT_SEMANTIC_COLORS.infoHover,
|
|
'--info-active': DEFAULT_SEMANTIC_COLORS.infoActive,
|
|
'--info-soft': DEFAULT_SEMANTIC_COLORS.infoSoft,
|
|
'--info-line': DEFAULT_SEMANTIC_COLORS.infoLine,
|
|
'--info-rgb': infoRgb,
|
|
'--el-color-primary': skin.primary,
|
|
'--el-color-primary-dark-2': skin.primaryActive,
|
|
'--el-color-primary-light-3': skin.primaryHover,
|
|
'--el-color-primary-light-5': 'var(--theme-primary-light-5)',
|
|
'--el-color-primary-light-7': skin.primarySoftStrong,
|
|
'--el-color-primary-light-8': skin.primarySoft,
|
|
'--el-color-primary-light-9': 'var(--theme-primary-light-9)'
|
|
})
|
|
}
|
|
|
|
function readStoredThemeSkinId() {
|
|
if (typeof window === 'undefined') {
|
|
return DEFAULT_THEME_SKIN_ID
|
|
}
|
|
|
|
const stored = window.localStorage.getItem(THEME_SKIN_STORAGE_KEY)
|
|
return findThemeSkin(stored).id
|
|
}
|
|
|
|
export function installThemeSkin() {
|
|
const skin = findThemeSkin(readStoredThemeSkinId())
|
|
activeThemeSkinId.value = skin.id
|
|
applyThemeSkin(skin)
|
|
}
|
|
|
|
export function setThemeSkin(id) {
|
|
const skin = findThemeSkin(id)
|
|
activeThemeSkinId.value = skin.id
|
|
applyThemeSkin(skin)
|
|
|
|
if (typeof window !== 'undefined') {
|
|
window.localStorage.setItem(THEME_SKIN_STORAGE_KEY, skin.id)
|
|
}
|
|
}
|
|
|
|
export function useThemeSkin() {
|
|
return {
|
|
activeThemeSkin: computed(() => findThemeSkin(activeThemeSkinId.value)),
|
|
activeThemeSkinId,
|
|
setThemeSkin,
|
|
themeSkinOptions: THEME_SKIN_OPTIONS
|
|
}
|
|
}
|