feat: implement dark theme with Element Plus

feature/20260628/refactor-frontend-modernization
Lxy 3 weeks ago
parent aaeba22f66
commit 6762d22d8e

@ -11,8 +11,14 @@ export {}
/* prettier-ignore */ /* prettier-ignore */
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { 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'] HelloWorld: typeof import('./components/HelloWorld.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
Sidebar: typeof import('./components/layout/Sidebar/index.vue')['default']
} }
} }

@ -0,0 +1,114 @@
<template>
<aside class="sidebar">
<div class="sidebar-header">
<h2 class="logo">RuoYi</h2>
</div>
<nav class="sidebar-menu">
<div
v-for="item in menuItems"
:key="item.path"
class="menu-item"
:class="{ active: currentPath === item.path }"
@click="navigate(item.path)"
>
<el-icon v-if="item.icon">
<component :is="item.icon" />
</el-icon>
<span class="menu-text">{{ item.name }}</span>
</div>
</nav>
</aside>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import {
HomeFilled,
Setting,
User,
Menu,
Document,
} from '@element-plus/icons-vue'
const router = useRouter()
const route = useRoute()
const currentPath = ref(route.path)
const menuItems = [
{ path: '/dashboard', name: '仪表盘', icon: HomeFilled },
{ path: '/system', name: '系统管理', icon: Setting },
{ path: '/monitor', name: '系统监控', icon: Menu },
{ path: '/log', name: '日志管理', icon: Document },
{ path: '/user', name: '用户管理', icon: User },
]
function navigate(path: string) {
currentPath.value = path
router.push(path)
}
</script>
<style scoped lang="scss">
.sidebar {
width: var(--sidebar-width);
height: 100vh;
background: var(--bg1);
border-right: 1px solid var(--bd);
display: flex;
flex-direction: column;
transition: width var(--transition-normal);
.sidebar-header {
height: var(--header-height);
display: flex;
align-items: center;
padding: 0 16px;
border-bottom: 1px solid var(--bd);
.logo {
font-size: 18px;
font-weight: 600;
color: var(--blu);
margin: 0;
}
}
.sidebar-menu {
flex: 1;
padding: 8px 0;
overflow-y: auto;
}
.menu-item {
display: flex;
align-items: center;
padding: 12px 16px;
cursor: pointer;
color: var(--t2);
transition: all var(--transition-fast);
gap: 12px;
&:hover {
background: var(--bg3);
color: var(--t1);
}
&.active {
background: rgba(88, 166, 255, 0.1);
color: var(--blu);
border-left: 3px solid var(--blu);
}
.el-icon {
font-size: 18px;
}
.menu-text {
font-size: 14px;
white-space: nowrap;
}
}
}
</style>

@ -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 }
}

@ -1,13 +1,15 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue' import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import '@/styles/variables.scss' import '@/styles/index.scss'
const app = createApp(App) const app = createApp(App)
@ -18,6 +20,6 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.use(createPinia()) app.use(createPinia())
app.use(router) app.use(router)
app.use(ElementPlus) app.use(ElementPlus, { locale: zhCn })
app.mount('#app') app.mount('#app')

@ -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);
}

@ -1,41 +1,56 @@
// demo-dashboard-heatmap.html
:root { :root {
// Background colors //
--bg0: #0d1117; --bg0: #0d1117; //
--bg1: #161b22; --bg1: #161b22; //
--bg2: #1c2333; --bg2: #1c2333; //
--bg3: #2a3040; --bg3: #2a3040; // hover
// Border color //
--bd: #30363d; --bd: #30363d;
// Text colors //
--t1: #e6edf3; --t1: #e6edf3; //
--t2: #8b949e; --t2: #8b949e; //
--tm: #6e7681; --tm: #6e7681; //
// Accent colors //
--red: #f85149; --red: #f85149; //
--grn: #3fb950; --grn: #3fb950; // 绿
--org: #d29922; --org: #d29922; //
--blu: #58a6ff; --blu: #58a6ff; //
// Semantic colors //
--color-success: var(--grn); --color-success: var(--grn);
--color-warning: var(--org); --color-warning: var(--org);
--color-danger: var(--red); --color-danger: var(--red);
--color-info: var(--blu); --color-info: var(--blu);
// Layout //
--header-height: 56px; --header-height: 56px;
--sidebar-width: 220px; --sidebar-width: 220px;
--sidebar-collapsed-width: 64px; --sidebar-collapsed-width: 64px;
// Transitions //
--transition-fast: 0.15s ease; --transition-fast: 0.15s ease;
--transition-normal: 0.3s 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; box-sizing: border-box;
} }
@ -47,7 +62,7 @@ html, body {
color: var(--t1); color: var(--t1);
} }
// Scrollbar styling //
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 8px; width: 8px;
height: 8px; height: 8px;

@ -27,6 +27,13 @@ export default defineConfig(({ mode }) => {
'@': resolve(__dirname, 'src'), '@': resolve(__dirname, 'src'),
}, },
}, },
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";\n`
}
}
},
server: { server: {
port: 80, port: 80,
host: true, host: true,
@ -51,10 +58,16 @@ export default defineConfig(({ mode }) => {
chunkSizeWarningLimit: 1500, chunkSizeWarningLimit: 1500,
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: { manualChunks(id) {
'element-plus': ['element-plus'], if (id.includes('element-plus')) {
'vue-vendor': ['vue', 'vue-router', 'pinia'], return 'element-plus'
'echarts': ['echarts', 'vue-echarts'], }
if (id.includes('node_modules') && (id.includes('vue') || id.includes('vue-router') || id.includes('pinia'))) {
return 'vue-vendor'
}
if (id.includes('echarts')) {
return 'echarts'
}
}, },
}, },
}, },

Loading…
Cancel
Save