diff --git a/public/static/loading.css b/public/static/loading.css index 618ab10..42e8c3c 100644 --- a/public/static/loading.css +++ b/public/static/loading.css @@ -14,7 +14,7 @@ width: 100px!important; height: 100px!important; border-radius: 50%; - background: var(--theme-primary, #BA4A8F); + background:black; padding: 5px!important; } diff --git a/src/assets/css/color.scss b/src/assets/css/color.scss index 37f2a22..f23e465 100644 --- a/src/assets/css/color.scss +++ b/src/assets/css/color.scss @@ -1,16 +1,24 @@ -$theme-primary: #BA4A8F; -$theme-darker: #C1B2E5; -$theme-dark: #DFD7F2; -$basic-white: #FFFFFF; -$basic-black: #000000; +// 动态主题色配置 +// 这些SCSS变量现在会从CSS变量中获取值,支持动态主题切换 -$text-disabled: #C3C3C3; -$text-basic: #939393; -$text-theme: #BA4A8F; +$theme-primary: var(--theme-primary, #BA4A8F); +$theme-darker: var(--theme-darker, #C1B2E5); +$theme-dark: var(--theme-dark, #DFD7F2); +$theme-success: var(--theme-success, #18a058); +$theme-warning: var(--theme-warning, #f0a020); +$theme-error: var(--theme-error, #d03050); +$theme-info: var(--theme-info, #2080f0); -$border-theme: #C1B2E5; +$basic-white: var(--basic-white, #FFFFFF); +$basic-black: var(--basic-black, #000000); -$input-bg: #777777; +$text-disabled: var(--text-disabled, #C3C3C3); +$text-basic: var(--text-basic, #939393); +$text-theme: var(--text-theme, #BA4A8F); -$btn-primary: #BA4A8F; -$btn-basic: #EEEAF7; +$border-theme: var(--border-theme, #C1B2E5); + +$input-bg: var(--input-bg, #777777); + +$btn-primary: var(--btn-primary, #BA4A8F); +$btn-basic: var(--btn-basic, #EEEAF7); diff --git a/src/assets/css/common.scss b/src/assets/css/common.scss index 6404891..014e8ae 100644 --- a/src/assets/css/common.scss +++ b/src/assets/css/common.scss @@ -1,10 +1,15 @@ @import "color.scss"; // 定义CSS自定义属性(CSS变量) +// 注意:这些默认值会被JavaScript动态覆盖 :root { --theme-primary: #{$theme-primary}; --theme-darker: #{$theme-darker}; --theme-dark: #{$theme-dark}; + --theme-success: #18a058; + --theme-warning: #f0a020; + --theme-error: #d03050; + --theme-info: #2080f0; --basic-white: #{$basic-white}; --basic-black: #{$basic-black}; --text-theme: #{$text-theme}; diff --git a/src/components/ThemeSwitch.vue b/src/components/ThemeSwitch.vue new file mode 100644 index 0000000..e486e33 --- /dev/null +++ b/src/components/ThemeSwitch.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/components/x-n-upload/index.vue b/src/components/x-n-upload/index.vue index 9c06573..4c5a334 100644 --- a/src/components/x-n-upload/index.vue +++ b/src/components/x-n-upload/index.vue @@ -1518,7 +1518,7 @@ const numWidth = computed(() => { .x-upload-download-button:hover { background-color: #e6fffb; - color: #13c2c2; + } .x-upload-delete-button:hover { diff --git a/src/config/theme/README.md b/src/config/theme/README.md new file mode 100644 index 0000000..a4af31d --- /dev/null +++ b/src/config/theme/README.md @@ -0,0 +1,177 @@ +# 主题系统使用文档 + +## 概述 + +这个主题系统可以根据网站URL自动切换主题色,支持多环境和多品牌的主题配置。 + +## 功能特性 + +- 🎨 基于URL自动切换主题 +- 🔄 支持运行时动态切换主题 +- 🎯 同时更新CSS变量和UI框架主题 +- 📱 支持多环境配置(开发、测试、生产) +- 🛠️ 提供完整的工具函数和组件 + +## 配置文件 + +### 主题配置 (`src/config/theme/index.js`) + +```javascript +export const themeConfigs = { + default: { + name: '体制外主题', + primary: '#BA4A8F', + // ... 其他颜色 + }, + main: { + name: '体制内主题', + primary: '#1890FF', + // ... 其他颜色 + } + // ... 更多主题 +}; +``` + +### URL映射规则 + +```javascript +export const urlThemeMapping = { + 'main': ['main.fontree.com', 'fontree-main.com'], + 'default': ['fontree.com', 'www.fontree.com'], + 'test': ['test.fontree.com', 'staging'], + 'dev': ['localhost', '127.0.0.1'] +}; +``` + +## 使用方法 + +### 1. 自动主题切换 + +系统会在应用启动时自动检测URL并应用相应主题,无需手动配置。 + +### 2. 手动切换主题 + +```javascript +import { switchTheme } from '@/utils/theme.js'; + +// 切换到指定主题 +switchTheme('main'); // 切换到体制内主题 +switchTheme('test'); // 切换到测试主题 +``` + +### 3. 在Vue组件中使用 + +```vue + + + +``` + +### 4. 使用主题切换组件 + +```vue + + + +``` + +### 5. 在CSS中使用主题变量 + +```css +.my-component { + background-color: var(--theme-primary); + color: var(--theme-success); + border: 1px solid var(--theme-darker); +} +``` + +### 6. 监听主题变化 + +```javascript +import { onThemeChange } from '@/utils/theme.js'; + +// 监听主题变化 +const unsubscribe = onThemeChange((themeInfo) => { + console.log('主题已切换:', themeInfo.key, themeInfo.config); +}); + +// 取消监听 +unsubscribe(); +``` + +## CSS变量列表 + +系统会自动设置以下CSS变量: + +- `--theme-primary`: 主题主色 +- `--theme-darker`: 主题深色 +- `--theme-dark`: 主题更深色 +- `--theme-success`: 成功色 +- `--theme-warning`: 警告色 +- `--theme-error`: 错误色 +- `--theme-info`: 信息色 + +## 添加新主题 + +1. 在 `themeConfigs` 中添加新主题配置 +2. 在 `urlThemeMapping` 中添加URL匹配规则 +3. 重启应用即可生效 + +```javascript +// 添加新主题 +export const themeConfigs = { + // ... 现有主题 + custom: { + name: '自定义主题', + primary: '#FF6B35', + darker: '#FF8C69', + // ... 其他颜色 + } +}; + +// 添加URL映射 +export const urlThemeMapping = { + // ... 现有映射 + 'custom': ['custom.fontree.com', 'my-custom-domain.com'] +}; +``` + +## 调试和测试 + +在浏览器控制台中可以看到主题切换的日志信息: + +``` +当前URL: http://localhost:3000 +当前hostname: localhost +匹配到主题: dev (开发环境主题) +CSS变量已更新: {primary: "#13C2C2", ...} +主题系统初始化完成: {...} +``` + +## 注意事项 + +1. 主题系统会在应用启动时自动初始化 +2. URL匹配是模糊匹配,会检查hostname和完整URL +3. 如果没有匹配到任何规则,会使用默认主题 +4. 主题切换会同时更新CSS变量和UI框架配置 +5. 所有主题变化都会触发 `themeChanged` 事件 + +## 扩展功能 + +- 可以添加主题持久化存储 +- 可以添加主题预览功能 +- 可以集成用户偏好设置 +- 可以添加主题动画过渡效果 diff --git a/src/config/theme/colors.js b/src/config/theme/colors.js new file mode 100644 index 0000000..c13e368 --- /dev/null +++ b/src/config/theme/colors.js @@ -0,0 +1,98 @@ +/** + * 动态颜色配置 + * 统一管理所有颜色变量,支持主题切换 + */ + +import { themeConfigs } from './index.js'; + +/** + * 获取当前主题的颜色配置 + * @param {string} themeKey 主题键名 + * @returns {Object} 颜色配置对象 + */ +export function getThemeColors(themeKey = 'default') { + const theme = themeConfigs[themeKey] || themeConfigs.default; + + return { + // 主题色系 + 'theme-primary': theme.primary, + 'theme-darker': theme.darker, + 'theme-dark': theme.dark, + 'theme-success': theme.success, + 'theme-warning': theme.warning, + 'theme-error': theme.error, + 'theme-info': theme.info, + + // 基础色系 + 'basic-white': '#FFFFFF', + 'basic-black': '#000000', + + // 文本色系 + 'text-disabled': '#C3C3C3', + 'text-basic': '#939393', + 'text-theme': theme.primary, + + // 边框色系 + 'border-theme': theme.darker, + + // 输入框色系 + 'input-bg': '#777777', + + // 按钮色系 + 'btn-primary': theme.primary, + 'btn-basic': '#EEEAF7' + }; +} + +/** + * 生成CSS变量字符串 + * @param {string} themeKey 主题键名 + * @returns {string} CSS变量定义字符串 + */ +export function generateCSSVariables(themeKey = 'default') { + const colors = getThemeColors(themeKey); + + let cssVars = ':root {\n'; + + Object.entries(colors).forEach(([key, value]) => { + cssVars += ` --${key}: ${value};\n`; + }); + + cssVars += '}\n'; + + return cssVars; +} + +/** + * 生成SCSS变量字符串 + * @param {string} themeKey 主题键名 + * @returns {string} SCSS变量定义字符串 + */ +export function generateSCSSVariables(themeKey = 'default') { + const colors = getThemeColors(themeKey); + + let scssVars = '// 动态生成的SCSS变量 - 请勿手动修改\n'; + scssVars += `// 当前主题: ${themeConfigs[themeKey]?.name || '默认主题'}\n\n`; + + Object.entries(colors).forEach(([key, value]) => { + const scssKey = key.replace(/-/g, '-'); // 保持连字符格式 + scssVars += `$${scssKey}: ${value};\n`; + }); + + return scssVars; +} + +/** + * 应用颜色到DOM + * @param {string} themeKey 主题键名 + */ +export function applyColorsToDOM(themeKey = 'default') { + const colors = getThemeColors(themeKey); + const root = document.documentElement; + + Object.entries(colors).forEach(([key, value]) => { + root.style.setProperty(`--${key}`, value); + }); + + console.log('颜色已应用到DOM:', colors); +} diff --git a/src/config/theme/index.js b/src/config/theme/index.js new file mode 100644 index 0000000..2d6bc2a --- /dev/null +++ b/src/config/theme/index.js @@ -0,0 +1,225 @@ +/** + * 基于URL的主题配置系统 + * 根据网站URL自动切换主题色 + */ + +// 主题配置映射表 +export const themeConfigs = { + // 默认主题(体制外) + default: { + name: '体制外主题', + primary: '#BA4A8F', + darker: '#C1B2E5', + dark: '#DFD7F2', + success: '#18a058', + warning: '#f0a020', + error: '#d03050', + info: '#2080f0' + }, + + // 体制内主题 + main: { + name: '体制内主题', + primary: '#1890FF', + darker: '#40A9FF', + dark: '#91D5FF', + success: '#52c41a', + warning: '#faad14', + error: '#f5222d', + info: '#1890ff' + }, + + // 测试环境主题 + test: { + name: '测试环境主题', + primary: '#722ED1', + darker: '#B37FEB', + dark: '#D3ADF7', + success: '#52c41a', + warning: '#fa8c16', + error: '#f5222d', + info: '#722ed1' + }, + + // 开发环境主题 + dev: { + name: '开发环境主题', + primary: '#BA4A8F', + darker: '#BA4A8F', + dark: '#BA4A8F', + success: '#52c41a', + warning: '#faad14', + error: '#f5222d', + info: '#BA4A8F ' + } +}; + +// URL匹配规则 +export const urlThemeMapping = { + // 生产环境 - 体制内 + 'main': ['main.fontree.com', 'fontree-main.com', 'main-fontree'], + + // 生产环境 - 体制外(默认) + 'default': ['fontree.com', 'www.fontree.com', 'app.fontree.com'], + + // 测试环境 + 'test': ['127.0.0.1', 'fontree-test.com', 'test-fontree', 'staging'], + + // 开发环境 + 'dev': ['localhost', '127.2.0.1', 'dev.fontree.com', 'fontree-dev.com', 'dev-fontree'] +}; + +/** + * 根据当前URL获取主题配置 + * @returns {Object} 主题配置对象 + */ +export function getThemeByUrl() { + const hostname = window.location.hostname; + const href = window.location.href; + + console.log('当前URL:', href); + console.log('当前hostname:', hostname); + + // 遍历URL映射规则 + for (const [themeKey, patterns] of Object.entries(urlThemeMapping)) { + for (const pattern of patterns) { + if (hostname.includes(pattern) || href.includes(pattern)) { + console.log(`匹配到主题: ${themeKey} (${themeConfigs[themeKey].name})`); + return { + key: themeKey, + config: themeConfigs[themeKey] + }; + } + } + } + + // 默认返回体制外主题 + console.log('使用默认主题'); + return { + key: 'default', + config: themeConfigs.default + }; +} + +/** + * 应用主题到CSS变量 + * @param {Object} themeConfig 主题配置 + */ +export async function applyThemeToCSS(themeConfig) { + // 使用新的颜色配置系统 + const { applyColorsToDOM } = await import('./colors.js'); + + // 根据主题配置找到对应的主题键名 + const themeKey = Object.keys(themeConfigs).find(key => + themeConfigs[key].primary === themeConfig.primary + ) || 'default'; + + // 应用所有颜色到DOM + applyColorsToDOM(themeKey); + + console.log('CSS变量已更新:', themeConfig); +} + +/** + * 生成Naive UI主题配置 + * @param {Object} themeConfig 主题配置 + * @returns {Object} Naive UI主题覆盖配置 + */ +export function generateNaiveUITheme(themeConfig) { + return { + common: { + primaryColor: themeConfig.primary, + primaryColorHover: themeConfig.darker, + primaryColorPressed: themeConfig.primary, + primaryColorSuppl: themeConfig.primary, + successColor: themeConfig.success, + warningColor: themeConfig.warning, + errorColor: themeConfig.error, + infoColor: themeConfig.info + }, + Button: { + // Primary按钮的颜色配置 + colorPrimary: themeConfig.primary, + colorHoverPrimary: themeConfig.darker, + colorPressedPrimary: themeConfig.primary, + colorFocusPrimary: themeConfig.primary, + + // Tertiary按钮的颜色配置 + textColor: themeConfig.primary, + textColorHover: themeConfig.darker, + textColorPressed: themeConfig.primary, + textColorFocus: themeConfig.primary, + textColorDisabled: '#c0c4cc', + + // 边框颜色 + borderPrimary: themeConfig.primary, + borderHoverPrimary: themeConfig.darker, + borderPressedPrimary: themeConfig.primary, + borderFocusPrimary: themeConfig.primary, + + // 波纹效果颜色 + rippleColorPrimary: `${themeConfig.primary}1a` + }, + DataTable: { + thColor: themeConfig.primary, + thColorHover: themeConfig.darker, + thTextColor: '#fff', + itemColorActive: themeConfig.primary, + sorterIconColor: '#fff' + }, + Input: { + borderHover: themeConfig.primary, + borderFocus: themeConfig.primary, + boxShadowFocus: `0 0 0 2px ${themeConfig.primary}1a` + }, + Select: { + peers: { + InternalSelection: { + borderHover: themeConfig.primary, + borderFocus: themeConfig.primary, + boxShadowFocus: `0 0 0 2px ${themeConfig.primary}1a` + } + } + }, + // 添加更多组件的主题配置 + Checkbox: { + colorChecked: themeConfig.primary, + colorCheckedHover: themeConfig.darker, + colorCheckedPressed: themeConfig.primary + }, + Radio: { + colorActive: themeConfig.primary, + colorActiveHover: themeConfig.darker + }, + Switch: { + railColorActive: themeConfig.primary, + railColorActiveHover: themeConfig.darker + } + }; +} + +/** + * 初始化主题系统 + */ +export async function initThemeSystem() { + const { key, config } = getThemeByUrl(); + + // 应用CSS变量 + await applyThemeToCSS(config); + + // 存储当前主题信息到全局 + window.__CURRENT_THEME__ = { + key, + config, + naiveUITheme: generateNaiveUITheme(config) + }; + + // 触发自定义事件,通知其他组件主题已更新 + window.dispatchEvent(new CustomEvent('themeChanged', { + detail: { key, config } + })); + + console.log('主题系统初始化完成:', window.__CURRENT_THEME__); + + return window.__CURRENT_THEME__; +} diff --git a/src/main.js b/src/main.js index db27c02..80cd2cf 100644 --- a/src/main.js +++ b/src/main.js @@ -2,17 +2,39 @@ import router from "@/router/index.js"; import { createApp } from "vue"; import App from "./App.vue"; import { registerPlugins } from "./plugins/index.js"; -import { settingConfig } from "@/config/settings/index.js"; -window.process = { - env: { - VUE_APP_API_URL: import.meta.env.VITE_API_URL, - }, -}; +import { initThemeSystem } from "@/config/theme/index.js"; +import store from "@/store/index.js"; -// 动态设置CSS变量值 -document.documentElement.style.setProperty('--theme-primary', settingConfig.themeColor); +// 异步初始化应用 +async function initApp() { + window.process = { + env: { + VUE_APP_API_URL: import.meta.env.VITE_API_URL, + }, + }; -const app = createApp(App); -// 注册插件 -registerPlugins(app); -app.mount("#app"); + // 初始化主题系统(必须在创建应用之前) + const currentTheme = await initThemeSystem(); + + // 更新store中的主题配置 + store.updateTheme(currentTheme.naiveUITheme); + + // 监听主题变化事件 + window.addEventListener('themeChanged', (event) => { + const { key, config } = event.detail; + console.log('主题已切换:', key, config); + + // 更新store中的主题 + if (window.__CURRENT_THEME__) { + store.updateTheme(window.__CURRENT_THEME__.naiveUITheme); + } + }); + + const app = createApp(App); + // 注册插件 + registerPlugins(app); + app.mount("#app"); +} + +// 启动应用 +initApp().catch(console.error); diff --git a/src/store/index.js b/src/store/index.js index 9a10dda..19288ec 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,10 +1,21 @@ import { reactive } from "vue"; import VisitedViewAction from "./modules/visited-view"; import { settingConfig } from "@/config/settings/index.js"; +import { generateNaiveUITheme } from "@/config/theme/index.js"; + +// 获取初始主题色(如果主题系统还未初始化,使用默认值) +const getInitialTheme = () => { + if (window.__CURRENT_THEME__) { + return generateNaiveUITheme(window.__CURRENT_THEME__.config); + } + return null; // 将在main.js中初始化后更新 +}; + const primaryColor = settingConfig.themeColor; const originState = { isCollapse: false, - themeOverrides: { + themeOverrides: getInitialTheme() || { + // 默认主题配置(作为后备) DataTable: { sorterIconColor:'#fff', thColorHover: primaryColor, @@ -48,6 +59,13 @@ const store = { changeDevice(device) { this.state.device = device; }, + + // 更新主题 + updateTheme(themeOverrides) { + this.state.themeOverrides = themeOverrides; + console.log('Store主题已更新:', themeOverrides); + }, + ...VisitedViewAction }; export default store; diff --git a/src/utils/theme.js b/src/utils/theme.js new file mode 100644 index 0000000..ed9239d --- /dev/null +++ b/src/utils/theme.js @@ -0,0 +1,136 @@ +/** + * 主题工具函数 + * 提供主题切换和管理的实用工具 + */ + +import { applyThemeToCSS, generateNaiveUITheme, themeConfigs } from "@/config/theme/index.js"; +import store from "@/store/index.js"; + +/** + * 手动切换主题 + * @param {string} themeKey 主题键名 + */ +export async function switchTheme(themeKey) { + if (!themeConfigs[themeKey]) { + console.error('主题不存在:', themeKey); + return false; + } + + const config = themeConfigs[themeKey]; + + // 应用CSS变量 + await applyThemeToCSS(config); + + // 生成并应用UI框架主题 + const naiveUITheme = generateNaiveUITheme(config); + store.updateTheme(naiveUITheme); + + // 更新全局主题信息 + window.__CURRENT_THEME__ = { + key: themeKey, + config, + naiveUITheme + }; + + // 强制刷新页面样式(确保所有组件都能应用新主题) + document.documentElement.style.display = 'none'; + setTimeout(() => { + document.documentElement.style.display = ''; + }, 0); + + // 触发主题变化事件 + window.dispatchEvent(new CustomEvent('themeChanged', { + detail: { key: themeKey, config } + })); + + console.log('手动切换主题成功:', themeKey, config); + console.log('应用的Naive UI主题:', naiveUITheme); + return true; +} + +/** + * 获取当前主题信息 + * @returns {Object} 当前主题信息 + */ +export function getCurrentTheme() { + return window.__CURRENT_THEME__ || null; +} + +/** + * 获取所有可用主题 + * @returns {Object} 所有主题配置 + */ +export function getAllThemes() { + return themeConfigs; +} + +/** + * 根据颜色值获取对比色(用于文本颜色) + * @param {string} hexColor 十六进制颜色值 + * @returns {string} 对比色(黑色或白色) + */ +export function getContrastColor(hexColor) { + // 移除#号 + const hex = hexColor.replace('#', ''); + + // 转换为RGB + const r = parseInt(hex.substr(0, 2), 16); + const g = parseInt(hex.substr(2, 2), 16); + const b = parseInt(hex.substr(4, 2), 16); + + // 计算亮度 + const brightness = (r * 299 + g * 587 + b * 114) / 1000; + + // 返回对比色 + return brightness > 128 ? '#000000' : '#ffffff'; +} + +/** + * 生成主题色的变体(浅色、深色等) + * @param {string} baseColor 基础颜色 + * @returns {Object} 颜色变体 + */ +export function generateColorVariants(baseColor) { + // 这里可以实现颜色变体生成算法 + // 简单示例: + return { + base: baseColor, + light: baseColor + '40', // 添加透明度 + lighter: baseColor + '20', + dark: baseColor, + darker: baseColor + }; +} + +/** + * 监听主题变化 + * @param {Function} callback 回调函数 + * @returns {Function} 取消监听的函数 + */ +export function onThemeChange(callback) { + const handler = (event) => { + callback(event.detail); + }; + + window.addEventListener('themeChanged', handler); + + // 返回取消监听的函数 + return () => { + window.removeEventListener('themeChanged', handler); + }; +} + +/** + * 创建主题切换的Vue组合式函数 + * @returns {Object} 主题相关的响应式数据和方法 + */ +export function useTheme() { + const currentTheme = getCurrentTheme(); + + return { + currentTheme, + switchTheme, + getAllThemes, + onThemeChange + }; +}