diff --git a/admin/client/src/layout/components/Navbar.vue b/admin/client/src/layout/components/Navbar.vue
index 2534892..1fe9b16 100644
--- a/admin/client/src/layout/components/Navbar.vue
+++ b/admin/client/src/layout/components/Navbar.vue
@@ -36,6 +36,35 @@
+
+
+
+
+
+ 深色
+
+
+
+ 橙色
+
+
+
+ 浅白色
+
+
+
@@ -53,13 +82,17 @@ export default {
...mapGetters([
'sidebar',
'avatar',
- 'name'
+ 'name',
+ 'theme'
])
},
methods: {
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
},
+ setTheme(theme) {
+ this.$store.dispatch('settings/setTheme', theme)
+ },
async logout() {
await this.$store.dispatch('user/logout')
this.$router.push(`/login?redirect=${this.$route.fullPath}`)
@@ -150,5 +183,56 @@ export default {
}
}
}
+
+ .theme-switch-wrapper {
+ float: right;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ margin-right: 10px;
+
+ .theme-switch {
+ display: flex;
+ background: rgba(0, 0, 0, 0.05);
+ padding: 3px;
+ border-radius: 20px;
+
+ .theme-item {
+ display: flex;
+ align-items: center;
+ padding: 4px 8px;
+ margin: 0 2px;
+ cursor: pointer;
+ border-radius: 16px;
+ font-size: 12px;
+ color: #606266;
+ transition: all 0.3s;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.1);
+ color: #303133;
+ }
+
+ &.active {
+ background: #fff;
+ color: #303133;
+ box-shadow: 0 2px 6px rgba(0,0,0,0.1);
+ font-weight: 500;
+ }
+
+ .color-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ margin-right: 4px;
+ display: inline-block;
+ }
+
+ .dark-dot { background: #1b2735; }
+ .orange-dot { background: linear-gradient(135deg, #f6d365 0%, #fda085 100%); }
+ .light-dot { background: #f0f2f5; border: 1px solid #ccc; }
+ }
+ }
+ }
}
diff --git a/admin/client/src/layout/components/Sidebar/Logo.vue b/admin/client/src/layout/components/Sidebar/Logo.vue
index fc7e40a..95718c3 100644
--- a/admin/client/src/layout/components/Sidebar/Logo.vue
+++ b/admin/client/src/layout/components/Sidebar/Logo.vue
@@ -42,41 +42,41 @@ export default {
}
.sidebar-logo-container {
- position: relative;
- width: 100%;
- height: 50px;
- line-height: 50px;
- background: #2b333e;
- text-align: center;
- overflow: hidden;
-
- & .sidebar-logo-link {
- height: 100%;
+ position: relative;
width: 100%;
+ height: 50px;
+ line-height: 50px;
+ background: var(--menuBg);
+ text-align: center;
+ overflow: hidden;
- & .sidebar-logo {
- width: 32px;
- height: 32px;
- vertical-align: middle;
- margin-right: 12px;
+ & .sidebar-logo-link {
+ height: 100%;
+ width: 100%;
+
+ & .sidebar-logo {
+ width: 32px;
+ height: 32px;
+ vertical-align: middle;
+ margin-right: 12px;
+ }
+
+ & .sidebar-title {
+ display: inline-block;
+ margin: 0;
+ color: var(--menuText);
+ font-weight: 600;
+ line-height: 50px;
+ font-size: 14px;
+ font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
+ vertical-align: middle;
+ }
}
- & .sidebar-title {
- display: inline-block;
- margin: 0;
- color: #fff;
- font-weight: 600;
- line-height: 50px;
- font-size: 14px;
- font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
- vertical-align: middle;
+ &.collapse {
+ .sidebar-logo {
+ margin-right: 0px;
+ }
}
}
-
- &.collapse {
- .sidebar-logo {
- margin-right: 0px;
- }
- }
-}
diff --git a/admin/client/src/layout/components/Sidebar/index.vue b/admin/client/src/layout/components/Sidebar/index.vue
index fb014a2..cf4ecfc 100644
--- a/admin/client/src/layout/components/Sidebar/index.vue
+++ b/admin/client/src/layout/components/Sidebar/index.vue
@@ -1,5 +1,5 @@
-
+
state.user.avatar,
name: state => state.user.name,
perms: state => state.user.perms,
- permission_routes: state => state.permission.routes
+ permission_routes: state => state.permission.routes,
+ theme: state => state.settings.theme
}
export default getters
diff --git a/admin/client/src/store/modules/settings.js b/admin/client/src/store/modules/settings.js
index aab31a2..07e1969 100644
--- a/admin/client/src/store/modules/settings.js
+++ b/admin/client/src/store/modules/settings.js
@@ -5,7 +5,8 @@ const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
const state = {
showSettings: showSettings,
fixedHeader: fixedHeader,
- sidebarLogo: sidebarLogo
+ sidebarLogo: sidebarLogo,
+ theme: 'dark'
}
const mutations = {
@@ -13,12 +14,18 @@ const mutations = {
if (state.hasOwnProperty(key)) {
state[key] = value
}
+ },
+ SET_THEME: (state, theme) => {
+ state.theme = theme
}
}
const actions = {
changeSetting({ commit }, data) {
commit('CHANGE_SETTING', data)
+ },
+ setTheme({ commit }, theme) {
+ commit('SET_THEME', theme)
}
}
diff --git a/admin/client/src/styles/sidebar.scss b/admin/client/src/styles/sidebar.scss
index 3dad4c3..4c35475 100644
--- a/admin/client/src/styles/sidebar.scss
+++ b/admin/client/src/styles/sidebar.scss
@@ -10,7 +10,7 @@
.sidebar-container {
transition: width 0.28s;
width: $sideBarWidth !important;
- background-color: $menuBg;
+ background-color: var(--menuBg);
height: 100%;
position: fixed;
font-size: 0px;
@@ -67,21 +67,21 @@
.submenu-title-noDropdown,
.el-submenu__title {
&:hover {
- background-color: $menuHover !important;
+ background-color: var(--menuHover) !important;
}
}
.is-active>.el-submenu__title {
- color: $subMenuActiveText !important;
+ color: var(--subMenuActiveText) !important;
}
& .nest-menu .el-submenu>.el-submenu__title,
& .el-submenu .el-menu-item {
min-width: $sideBarWidth !important;
- background-color: $subMenuBg !important;
+ background-color: var(--subMenuBg) !important;
&:hover {
- background-color: $subMenuHover !important;
+ background-color: var(--subMenuHover) !important;
}
}
}
diff --git a/admin/client/src/views/dashboard/components/BarChart.vue b/admin/client/src/views/dashboard/components/BarChart.vue
index 247f5fb..f18e0f9 100644
--- a/admin/client/src/views/dashboard/components/BarChart.vue
+++ b/admin/client/src/views/dashboard/components/BarChart.vue
@@ -25,6 +25,10 @@ export default {
chartData: {
type: Object,
default: () => ({ names: [], counts: [], title: '机构学员' })
+ },
+ theme: {
+ type: String,
+ default: 'dark'
}
},
data() {
@@ -32,6 +36,17 @@ export default {
chart: null
}
},
+ watch: {
+ theme() {
+ this.initChart()
+ },
+ chartData: {
+ deep: true,
+ handler() {
+ this.initChart()
+ }
+ }
+ },
mounted() {
this.$nextTick(() => {
this.initChart()
@@ -46,17 +61,25 @@ export default {
},
methods: {
initChart() {
+ if (this.chart) {
+ this.chart.dispose() // Dispose old instance to apply new theme completely if needed, or just setOption
+ }
this.chart = echarts.init(this.$el, 'macarons')
const names = this.chartData.names || []
const counts = this.chartData.counts || []
const title = this.chartData.title || '机构学员'
+
+ const isDark = this.theme === 'dark';
+ const textColor = isDark ? '#fff' : '#333';
+ const axisLineColor = isDark ? '#fff' : '#333';
+ const splitLineColor = isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
this.chart.setOption({
title: {
text: title,
textStyle: {
- color: '#fff'
+ color: textColor
},
left: 'center'
},
@@ -80,11 +103,11 @@ export default {
alignWithLabel: true
},
axisLabel: {
- color: '#fff'
+ color: axisLineColor
},
axisLine: {
lineStyle: {
- color: '#fff'
+ color: axisLineColor
}
}
}],
@@ -95,16 +118,16 @@ export default {
show: false
},
axisLabel: {
- color: '#fff'
+ color: axisLineColor
},
axisLine: {
lineStyle: {
- color: '#fff'
+ color: axisLineColor
}
},
splitLine: {
lineStyle: {
- color: 'rgba(255, 255, 255, 0.1)'
+ color: splitLineColor
}
}
}],
diff --git a/admin/client/src/views/dashboard/components/LineChart.vue b/admin/client/src/views/dashboard/components/LineChart.vue
index 225f469..085a3ea 100644
--- a/admin/client/src/views/dashboard/components/LineChart.vue
+++ b/admin/client/src/views/dashboard/components/LineChart.vue
@@ -29,6 +29,10 @@ export default {
chartData: {
type: Object,
required: true
+ },
+ theme: {
+ type: String,
+ default: 'dark'
}
},
data() {
@@ -37,6 +41,9 @@ export default {
}
},
watch: {
+ theme() {
+ this.setOptions(this.chartData)
+ },
chartData: {
deep: true,
handler(val) {
@@ -58,10 +65,17 @@ export default {
},
methods: {
initChart() {
+ if (this.chart) {
+ this.chart.dispose()
+ }
this.chart = echarts.init(this.$el, 'macarons')
this.setOptions(this.chartData)
},
setOptions({ expectedData, actualData, xAxis } = {}) {
+ const isDark = this.theme === 'dark';
+ const textColor = isDark ? '#fff' : '#333';
+ const splitLineColor = isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
+
this.chart.setOption({
xAxis: {
data: xAxis && xAxis.length ? xAxis : ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
@@ -70,11 +84,11 @@ export default {
show: false
},
axisLabel: {
- color: '#fff'
+ color: textColor
},
axisLine: {
lineStyle: {
- color: '#fff'
+ color: textColor
}
}
},
@@ -97,23 +111,23 @@ export default {
show: false
},
axisLabel: {
- color: '#fff'
+ color: textColor
},
axisLine: {
lineStyle: {
- color: '#fff'
+ color: textColor
}
},
splitLine: {
lineStyle: {
- color: 'rgba(255, 255, 255, 0.1)'
+ color: splitLineColor
}
}
},
legend: {
data: ['上周', '本周'],
textStyle: {
- color: '#fff'
+ color: textColor
}
},
series: [{
diff --git a/admin/client/src/views/dashboard/components/MapChart.vue b/admin/client/src/views/dashboard/components/MapChart.vue
index 161b26d..dec1a23 100644
--- a/admin/client/src/views/dashboard/components/MapChart.vue
+++ b/admin/client/src/views/dashboard/components/MapChart.vue
@@ -25,6 +25,10 @@ export default {
chartData: {
type: Array,
default: () => []
+ },
+ theme: {
+ type: String,
+ default: 'dark'
}
},
data() {
@@ -33,6 +37,9 @@ export default {
}
},
watch: {
+ theme() {
+ this.setOptions(this.chartData)
+ },
chartData: {
deep: true,
handler(val) {
@@ -54,6 +61,9 @@ export default {
},
methods: {
initChart() {
+ if (this.chart) {
+ this.chart.dispose()
+ }
this.chart = echarts.init(this.$el, 'macarons')
// Check if map is already registered
@@ -76,6 +86,29 @@ export default {
setOptions(data) {
if (!this.chart) return
+ const isDark = this.theme === 'dark';
+ const textColor = isDark ? '#fff' : '#333';
+
+ let areaColor, borderColor, visualMapColor;
+
+ switch (this.theme) {
+ case 'orange':
+ areaColor = '#fdf6ec';
+ borderColor = '#faecd8';
+ visualMapColor = ['#fdf6ec', '#e6a23c'];
+ break;
+ case 'light':
+ areaColor = '#f0f9eb';
+ borderColor = '#fff';
+ visualMapColor = ['#f0f9eb', '#67c23a'];
+ break;
+ default: // dark
+ areaColor = 'rgba(20, 41, 87, 0.6)';
+ borderColor = '#4facfe';
+ visualMapColor = ['#e0ffff', '#006edd'];
+ break;
+ }
+
this.chart.setOption({
backgroundColor: 'transparent',
title: {
@@ -83,7 +116,7 @@ export default {
left: 'center',
top: 20,
textStyle: {
- color: '#fff',
+ color: textColor,
fontSize: 18,
fontWeight: 'bold'
}
@@ -104,11 +137,11 @@ export default {
bottom: '50',
text: ['高', '低'],
textStyle: {
- color: '#fff'
+ color: textColor
},
calculable: true,
inRange: {
- color: ['#e0ffff', '#006edd']
+ color: visualMapColor
}
},
series: [
@@ -120,7 +153,7 @@ export default {
zoom: 1.2,
label: {
show: true,
- color: '#fff',
+ color: textColor,
fontSize: 10,
formatter: function(params) {
if (params.value > 0) {
@@ -132,7 +165,7 @@ export default {
emphasis: {
label: {
show: true,
- color: '#fff'
+ color: textColor
},
itemStyle: {
areaColor: '#fbc2eb',
@@ -141,8 +174,8 @@ export default {
}
},
itemStyle: {
- areaColor: 'rgba(20, 41, 87, 0.6)',
- borderColor: '#4facfe',
+ areaColor: areaColor,
+ borderColor: borderColor,
borderWidth: 1
},
data: data
diff --git a/admin/client/src/views/dashboard/components/PanelGroup.vue b/admin/client/src/views/dashboard/components/PanelGroup.vue
index 7aa814a..d3a6a68 100644
--- a/admin/client/src/views/dashboard/components/PanelGroup.vue
+++ b/admin/client/src/views/dashboard/components/PanelGroup.vue
@@ -1,5 +1,5 @@
-