Files
sionrui/frontend/app/web-gold/src/App.vue
2026-02-25 23:18:06 +08:00

203 lines
5.3 KiB
Vue

<script setup>
import { RouterView } from 'vue-router'
import { ref, computed, watchEffect } from 'vue'
import SvgSprite from '@/components/icons/SvgSprite.vue'
import { useUserStore } from '@/stores/user'
import tokenManager from '@gold/utils/token-manager'
import zhCN from 'ant-design-vue/es/locale/zh_CN'
// ========================================
// Ant Design Vue 主题配置
// ========================================
const isDark = ref(false)
// 初始化主题
const initTheme = () => {
const stored = localStorage.getItem('theme')
if (stored) {
isDark.value = stored === 'dark'
} else {
isDark.value = window.matchMedia('(prefers-color-scheme: dark)').matches
}
document.documentElement.setAttribute('data-theme', isDark.value ? 'dark' : 'light')
}
// 主题切换
const toggleTheme = () => {
isDark.value = !isDark.value
document.documentElement.setAttribute('data-theme', isDark.value ? 'dark' : 'light')
localStorage.setItem('theme', isDark.value ? 'dark' : 'light')
}
// Ant Design 主题 Token
const themeToken = computed(() => {
const lightToken = {
// 品牌色
colorPrimary: '#3B82F6',
colorSuccess: '#22C55E',
colorWarning: '#F59E0B',
colorError: '#EF4444',
colorInfo: '#3B82F6',
// 背景色
colorBgContainer: '#FFFFFF',
colorBgElevated: '#FFFFFF',
colorBgLayout: '#F9FAFB',
// 边框
colorBorder: '#E5E7EB',
colorBorderSecondary: '#F3F4F6',
// 文字
colorText: '#111827',
colorTextSecondary: '#4B5563',
colorTextTertiary: '#6B7280',
colorTextQuaternary: '#9CA3AF',
// 填充
colorFill: '#F3F4F6',
colorFillSecondary: '#F9FAFB',
// 字体
fontFamily: '-apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif',
fontSize: 14,
// 圆角
borderRadius: 6,
borderRadiusSM: 4,
borderRadiusLG: 8,
// 控件尺寸
controlHeight: 32,
controlHeightSM: 28,
controlHeightLG: 40,
// 动画
motionDurationFast: '0.15s',
motionDurationMid: '0.2s',
motionDurationSlow: '0.3s',
// 阴影
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)',
boxShadowSecondary: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
}
const darkToken = {
colorPrimary: '#60A5FA',
colorBgContainer: '#1E293B',
colorBgElevated: '#334155',
colorBgLayout: '#0F172A',
colorBorder: '#334155',
colorBorderSecondary: '#1E293B',
colorText: '#F1F5F9',
colorTextSecondary: '#94A3B8',
colorTextTertiary: '#64748B',
colorTextQuaternary: '#475569',
colorFill: '#334155',
colorFillSecondary: '#1E293B',
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.4), 0 1px 2px -1px rgba(0, 0, 0, 0.4)',
boxShadowSecondary: '0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -2px rgba(0, 0, 0, 0.4)',
}
return {
token: isDark.value ? { ...lightToken, ...darkToken } : lightToken,
components: {
Button: {
primaryShadow: 'none',
defaultShadow: 'none',
fontWeight: 500,
},
Input: {
paddingBlock: 6,
paddingInline: 12,
activeShadow: '0 0 0 3px rgba(59, 130, 246, 0.15)',
},
Select: {
optionSelectedBg: isDark.value ? 'rgba(96, 165, 250, 0.15)' : '#EFF6FF',
optionActiveBg: isDark.value ? '#334155' : '#F3F4F6',
},
Table: {
headerBg: isDark.value ? '#1E293B' : '#F9FAFB',
rowHoverBg: isDark.value ? '#334155' : '#F9FAFB',
borderColor: isDark.value ? '#334155' : '#E5E7EB',
headerColor: '#4B5563',
},
Card: {
paddingLG: 20,
borderRadiusLG: 12,
},
Modal: {
borderRadiusLG: 12,
},
Menu: {
itemHoverBg: isDark.value ? '#334155' : '#F3F4F6',
itemSelectedBg: isDark.value ? 'rgba(96, 165, 250, 0.15)' : '#EFF6FF',
itemSelectedColor: isDark.value ? '#60A5FA' : '#3B82F6',
},
},
}
})
// 初始化
initTheme()
// 监听系统主题变化
watchEffect(() => {
const media = window.matchMedia('(prefers-color-scheme: dark)')
const handler = (e) => {
if (!localStorage.getItem('theme')) {
isDark.value = e.matches
document.documentElement.setAttribute('data-theme', e.matches ? 'dark' : 'light')
}
}
media.addEventListener('change', handler)
})
// 暴露给模板使用
defineExpose({ toggleTheme, isDark })
</script>
<template>
<a-config-provider :theme="themeToken" :locale="zhCN">
<SvgSprite />
<keep-alive>
<RouterView />
</keep-alive>
</a-config-provider>
</template>
<style>
/* 全局样式保持不变 */
.ant-btn{
align-items: center;
text-align: center;
display: flex;
}
.ant-select-selection-item,
.ant-select-selection-placeholder {
line-height: 30px !important;
color: var(--color-text) !important;
}
.ant-modal-confirm-btns {
display: flex;
justify-content: flex-end;
gap: 8px;
.ant-btn {
margin-left: 0 !important;
}
}
.ant-select-focused .ant-select-selector {
border-color: var(--color-primary) !important;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.1) !important;
}
.ant-select:hover .ant-select-selector {
border-color: var(--color-primary) !important;
}
.ant-select-arrow {
color: var(--color-text-secondary) !important;
}
.ant-modal .ant-modal-footer {
display: flex;
justify-content: end;
}
.ant-tooltip{
z-index: 100
}
</style>