diff --git a/web/frontend/src/App.vue b/web/frontend/src/App.vue
index 852ab45..491e341 100644
--- a/web/frontend/src/App.vue
+++ b/web/frontend/src/App.vue
@@ -2,8 +2,10 @@
import { computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
+import { useThemeStore } from '@/stores/theme'
const auth = useAuthStore()
+const theme = useThemeStore()
const router = useRouter()
const route = useRoute()
@@ -39,7 +41,16 @@ function logout() {
{{ auth.isAdmin ? '管理员' : '普通用户' }}
- 退出登录
+
+
+ 退出登录
+
@@ -57,6 +68,10 @@ body,
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
+body {
+ background-color: var(--el-bg-color-page);
+ color: var(--el-text-color-primary);
+}
.app {
height: 100%;
}
@@ -77,14 +92,19 @@ body,
display: flex;
justify-content: space-between;
align-items: center;
- background: #fff;
- border-bottom: 1px solid #ebeef5;
+ background: var(--el-bg-color);
+ border-bottom: 1px solid var(--el-border-color-light);
}
.user {
display: flex;
align-items: center;
gap: 10px;
}
+.right {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
.el-menu {
border-right: none !important;
}
diff --git a/web/frontend/src/components/KLineChart.vue b/web/frontend/src/components/KLineChart.vue
index 2613fec..72cfe9a 100644
--- a/web/frontend/src/components/KLineChart.vue
+++ b/web/frontend/src/components/KLineChart.vue
@@ -2,12 +2,23 @@
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import * as echarts from 'echarts'
import type { Candle } from '@/api/candles'
+import { useThemeStore } from '@/stores/theme'
const props = defineProps<{ data: Candle[] }>()
+const theme = useThemeStore()
const containerRef = ref(null)
let chart: echarts.ECharts | null = null
+function ensureChart() {
+ if (!containerRef.value) return
+ if (chart) {
+ chart.dispose()
+ chart = null
+ }
+ chart = echarts.init(containerRef.value, theme.isDark ? 'dark' : undefined)
+}
+
function render() {
if (!chart) return
const dates = props.data.map((c) => c.trade_date)
@@ -17,6 +28,7 @@ function render() {
chart.setOption(
{
+ backgroundColor: 'transparent',
tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } },
legend: { data: ['K 线', '持仓量'], top: 0 },
grid: [
@@ -69,11 +81,9 @@ function resize() {
}
onMounted(() => {
- if (containerRef.value) {
- chart = echarts.init(containerRef.value)
- render()
- window.addEventListener('resize', resize)
- }
+ ensureChart()
+ render()
+ window.addEventListener('resize', resize)
})
onBeforeUnmount(() => {
@@ -83,6 +93,13 @@ onBeforeUnmount(() => {
})
watch(() => props.data, render, { deep: true })
+watch(
+ () => theme.isDark,
+ () => {
+ ensureChart()
+ render()
+ },
+)
diff --git a/web/frontend/src/main.ts b/web/frontend/src/main.ts
index 4f57c49..8d5b520 100644
--- a/web/frontend/src/main.ts
+++ b/web/frontend/src/main.ts
@@ -3,6 +3,7 @@ import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import 'element-plus/dist/index.css'
+import 'element-plus/theme-chalk/dark/css-vars.css'
import App from './App.vue'
import router from './router'
diff --git a/web/frontend/src/stores/theme.ts b/web/frontend/src/stores/theme.ts
new file mode 100644
index 0000000..9adfd72
--- /dev/null
+++ b/web/frontend/src/stores/theme.ts
@@ -0,0 +1,32 @@
+import { defineStore } from 'pinia'
+import { ref, watch } from 'vue'
+
+const STORAGE_KEY = 'trade.theme'
+type Mode = 'dark' | 'light'
+
+function detectInitial(): boolean {
+ const saved = localStorage.getItem(STORAGE_KEY) as Mode | null
+ if (saved === 'dark') return true
+ if (saved === 'light') return false
+ return window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false
+}
+
+function apply(isDark: boolean) {
+ document.documentElement.classList.toggle('dark', isDark)
+}
+
+export const useThemeStore = defineStore('theme', () => {
+ const isDark = ref(detectInitial())
+ apply(isDark.value)
+
+ watch(isDark, (v) => {
+ apply(v)
+ localStorage.setItem(STORAGE_KEY, v ? 'dark' : 'light')
+ })
+
+ function toggle() {
+ isDark.value = !isDark.value
+ }
+
+ return { isDark, toggle }
+})
diff --git a/web/frontend/src/views/AdminUsersView.vue b/web/frontend/src/views/AdminUsersView.vue
index ad58df4..57ad2cb 100644
--- a/web/frontend/src/views/AdminUsersView.vue
+++ b/web/frontend/src/views/AdminUsersView.vue
@@ -193,6 +193,6 @@ onMounted(reload)
display: flex;
justify-content: space-between;
align-items: center;
- color: #606266;
+ color: var(--el-text-color-regular);
}
diff --git a/web/frontend/src/views/LoginView.vue b/web/frontend/src/views/LoginView.vue
index 5a0a6d2..7645cbc 100644
--- a/web/frontend/src/views/LoginView.vue
+++ b/web/frontend/src/views/LoginView.vue
@@ -69,7 +69,8 @@ async function submit() {
.card {
width: 360px;
padding: 36px 32px;
- background: #fff;
+ background: var(--el-bg-color);
+ color: var(--el-text-color-primary);
border-radius: 8px;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
}
@@ -79,7 +80,7 @@ async function submit() {
}
.hint {
margin: 0 0 24px;
- color: #909399;
+ color: var(--el-text-color-secondary);
font-size: 12px;
text-align: center;
}
diff --git a/web/frontend/src/views/ScoresView.vue b/web/frontend/src/views/ScoresView.vue
index 8333451..b32bdc3 100644
--- a/web/frontend/src/views/ScoresView.vue
+++ b/web/frontend/src/views/ScoresView.vue
@@ -120,6 +120,6 @@ onMounted(async () => {
padding: 12px 16px;
}
.score-table {
- background: #fff;
+ background: var(--el-bg-color);
}