From 6762d22d8ed900411433742e52c71be440f48bce Mon Sep 17 00:00:00 2001 From: Lxy Date: Sun, 28 Jun 2026 16:24:21 +0800 Subject: [PATCH] feat: implement dark theme with Element Plus --- ruoyi-ui-next/src/components.d.ts | 6 + .../src/components/layout/Sidebar/index.vue | 114 ++++++++++++++++++ ruoyi-ui-next/src/composables/useTheme.ts | 12 ++ ruoyi-ui-next/src/main.ts | 6 +- ruoyi-ui-next/src/styles/index.scss | 28 +++++ ruoyi-ui-next/src/styles/variables.scss | 55 ++++++--- ruoyi-ui-next/vite.config.ts | 23 +++- 7 files changed, 217 insertions(+), 27 deletions(-) create mode 100644 ruoyi-ui-next/src/components/layout/Sidebar/index.vue create mode 100644 ruoyi-ui-next/src/composables/useTheme.ts create mode 100644 ruoyi-ui-next/src/styles/index.scss diff --git a/ruoyi-ui-next/src/components.d.ts b/ruoyi-ui-next/src/components.d.ts index 2d8f0ca..073f3e2 100644 --- a/ruoyi-ui-next/src/components.d.ts +++ b/ruoyi-ui-next/src/components.d.ts @@ -11,8 +11,14 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { + ElButton: typeof import('element-plus/es')['ElButton'] + ElCard: typeof import('element-plus/es')['ElCard'] + ElForm: typeof import('element-plus/es')['ElForm'] + ElFormItem: typeof import('element-plus/es')['ElFormItem'] + ElInput: typeof import('element-plus/es')['ElInput'] HelloWorld: typeof import('./components/HelloWorld.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + Sidebar: typeof import('./components/layout/Sidebar/index.vue')['default'] } } diff --git a/ruoyi-ui-next/src/components/layout/Sidebar/index.vue b/ruoyi-ui-next/src/components/layout/Sidebar/index.vue new file mode 100644 index 0000000..7f54b07 --- /dev/null +++ b/ruoyi-ui-next/src/components/layout/Sidebar/index.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/ruoyi-ui-next/src/composables/useTheme.ts b/ruoyi-ui-next/src/composables/useTheme.ts new file mode 100644 index 0000000..37a7f41 --- /dev/null +++ b/ruoyi-ui-next/src/composables/useTheme.ts @@ -0,0 +1,12 @@ +import { ref } from 'vue' + +export function useTheme() { + const isDark = ref(true) + + function toggleTheme() { + isDark.value = !isDark.value + document.documentElement.setAttribute('theme', isDark.value ? 'dark' : 'light') + } + + return { isDark, toggleTheme } +} diff --git a/ruoyi-ui-next/src/main.ts b/ruoyi-ui-next/src/main.ts index d1538eb..9c46ef2 100644 --- a/ruoyi-ui-next/src/main.ts +++ b/ruoyi-ui-next/src/main.ts @@ -1,13 +1,15 @@ import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' +import 'element-plus/theme-chalk/dark/css-vars.css' import * as ElementPlusIconsVue from '@element-plus/icons-vue' +import zhCn from 'element-plus/es/locale/lang/zh-cn' import App from './App.vue' import router from './router' import { createPinia } from 'pinia' -import '@/styles/variables.scss' +import '@/styles/index.scss' const app = createApp(App) @@ -18,6 +20,6 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.use(createPinia()) app.use(router) -app.use(ElementPlus) +app.use(ElementPlus, { locale: zhCn }) app.mount('#app') diff --git a/ruoyi-ui-next/src/styles/index.scss b/ruoyi-ui-next/src/styles/index.scss new file mode 100644 index 0000000..89912fd --- /dev/null +++ b/ruoyi-ui-next/src/styles/index.scss @@ -0,0 +1,28 @@ +// Global styles (variables.scss is auto-imported via vite.config.ts additionalData) + +// 全局样式 +body { + margin: 0; + background-color: var(--bg0); + color: var(--t1); + font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif; +} + +#app { + min-height: 100vh; +} + +// 滚动条样式 +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-thumb { + background: var(--bd); + border-radius: 3px; +} + +::-webkit-scrollbar-track { + background: var(--bg1); +} diff --git a/ruoyi-ui-next/src/styles/variables.scss b/ruoyi-ui-next/src/styles/variables.scss index f0e16c9..768c583 100644 --- a/ruoyi-ui-next/src/styles/variables.scss +++ b/ruoyi-ui-next/src/styles/variables.scss @@ -1,41 +1,56 @@ +// 深色主题配色(从 demo-dashboard-heatmap.html 提取) :root { - // Background colors - --bg0: #0d1117; - --bg1: #161b22; - --bg2: #1c2333; - --bg3: #2a3040; + // 背景色 + --bg0: #0d1117; // 最深背景 + --bg1: #161b22; // 侧边栏背景 + --bg2: #1c2333; // 卡片背景 + --bg3: #2a3040; // hover 背景 - // Border color + // 边框 --bd: #30363d; - // Text colors - --t1: #e6edf3; - --t2: #8b949e; - --tm: #6e7681; + // 文字 + --t1: #e6edf3; // 主文字 + --t2: #8b949e; // 次要文字 + --tm: #6e7681; // 提示文字 - // Accent colors - --red: #f85149; - --grn: #3fb950; - --org: #d29922; - --blu: #58a6ff; + // 强调色 + --red: #f85149; // 红色(上涨、错误) + --grn: #3fb950; // 绿色(下跌、成功) + --org: #d29922; // 橙色(警告) + --blu: #58a6ff; // 蓝色(链接、主色) - // Semantic colors + // 语义色 --color-success: var(--grn); --color-warning: var(--org); --color-danger: var(--red); --color-info: var(--blu); - // Layout + // 布局 --header-height: 56px; --sidebar-width: 220px; --sidebar-collapsed-width: 64px; - // Transitions + // 过渡动画 --transition-fast: 0.15s ease; --transition-normal: 0.3s ease; } -// Global styles +// Element Plus 深色主题覆盖 +.el-button--primary { + --el-button-bg-color: var(--blu); + --el-button-border-color: var(--blu); +} + +.el-table { + --el-table-bg-color: var(--bg2); + --el-table-tr-bg-color: var(--bg2); + --el-table-header-bg-color: var(--bg1); + --el-table-text-color: var(--t1); + --el-table-border-color: var(--bd); +} + +// 全局样式 * { box-sizing: border-box; } @@ -47,7 +62,7 @@ html, body { color: var(--t1); } -// Scrollbar styling +// 滚动条样式 ::-webkit-scrollbar { width: 8px; height: 8px; diff --git a/ruoyi-ui-next/vite.config.ts b/ruoyi-ui-next/vite.config.ts index 98868b9..3f76a21 100644 --- a/ruoyi-ui-next/vite.config.ts +++ b/ruoyi-ui-next/vite.config.ts @@ -8,7 +8,7 @@ import { resolve } from 'path' // https://vite.dev/config/ export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), '') - + return { plugins: [ vue(), @@ -27,6 +27,13 @@ export default defineConfig(({ mode }) => { '@': resolve(__dirname, 'src'), }, }, + css: { + preprocessorOptions: { + scss: { + additionalData: `@import "@/styles/variables.scss";\n` + } + } + }, server: { port: 80, host: true, @@ -51,10 +58,16 @@ export default defineConfig(({ mode }) => { chunkSizeWarningLimit: 1500, rollupOptions: { output: { - manualChunks: { - 'element-plus': ['element-plus'], - 'vue-vendor': ['vue', 'vue-router', 'pinia'], - 'echarts': ['echarts', 'vue-echarts'], + manualChunks(id) { + if (id.includes('element-plus')) { + return 'element-plus' + } + if (id.includes('node_modules') && (id.includes('vue') || id.includes('vue-router') || id.includes('pinia'))) { + return 'vue-vendor' + } + if (id.includes('echarts')) { + return 'echarts' + } }, }, },