Web 前端新增暗夜模式切换

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
fish
2026-05-03 15:35:26 +08:00
parent d3ec1de275
commit 8d4bcb4292
7 changed files with 83 additions and 12 deletions

View File

@@ -2,8 +2,10 @@
import { computed } from 'vue' import { computed } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { useThemeStore } from '@/stores/theme'
const auth = useAuthStore() const auth = useAuthStore()
const theme = useThemeStore()
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
@@ -39,7 +41,16 @@ function logout() {
{{ auth.isAdmin ? '管理员' : '普通用户' }} {{ auth.isAdmin ? '管理员' : '普通用户' }}
</el-tag> </el-tag>
</div> </div>
<div class="right">
<el-switch
v-model="theme.isDark"
inline-prompt
active-text=""
inactive-text=""
style="--el-switch-on-color: #2c3e50"
/>
<el-button type="primary" link @click="logout">退出登录</el-button> <el-button type="primary" link @click="logout">退出登录</el-button>
</div>
</el-header> </el-header>
<el-main> <el-main>
<router-view /> <router-view />
@@ -57,6 +68,10 @@ body,
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif; 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 { .app {
height: 100%; height: 100%;
} }
@@ -77,14 +92,19 @@ body,
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
background: #fff; background: var(--el-bg-color);
border-bottom: 1px solid #ebeef5; border-bottom: 1px solid var(--el-border-color-light);
} }
.user { .user {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
} }
.right {
display: flex;
align-items: center;
gap: 16px;
}
.el-menu { .el-menu {
border-right: none !important; border-right: none !important;
} }

View File

@@ -2,12 +2,23 @@
import { onBeforeUnmount, onMounted, ref, watch } from 'vue' import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import type { Candle } from '@/api/candles' import type { Candle } from '@/api/candles'
import { useThemeStore } from '@/stores/theme'
const props = defineProps<{ data: Candle[] }>() const props = defineProps<{ data: Candle[] }>()
const theme = useThemeStore()
const containerRef = ref<HTMLDivElement | null>(null) const containerRef = ref<HTMLDivElement | null>(null)
let chart: echarts.ECharts | null = 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() { function render() {
if (!chart) return if (!chart) return
const dates = props.data.map((c) => c.trade_date) const dates = props.data.map((c) => c.trade_date)
@@ -17,6 +28,7 @@ function render() {
chart.setOption( chart.setOption(
{ {
backgroundColor: 'transparent',
tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } }, tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } },
legend: { data: ['K 线', '持仓量'], top: 0 }, legend: { data: ['K 线', '持仓量'], top: 0 },
grid: [ grid: [
@@ -69,11 +81,9 @@ function resize() {
} }
onMounted(() => { onMounted(() => {
if (containerRef.value) { ensureChart()
chart = echarts.init(containerRef.value)
render() render()
window.addEventListener('resize', resize) window.addEventListener('resize', resize)
}
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
@@ -83,6 +93,13 @@ onBeforeUnmount(() => {
}) })
watch(() => props.data, render, { deep: true }) watch(() => props.data, render, { deep: true })
watch(
() => theme.isDark,
() => {
ensureChart()
render()
},
)
</script> </script>
<template> <template>

View File

@@ -3,6 +3,7 @@ import { createPinia } from 'pinia'
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn' import zhCn from 'element-plus/es/locale/lang/zh-cn'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'

View File

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

View File

@@ -193,6 +193,6 @@ onMounted(reload)
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
color: #606266; color: var(--el-text-color-regular);
} }
</style> </style>

View File

@@ -69,7 +69,8 @@ async function submit() {
.card { .card {
width: 360px; width: 360px;
padding: 36px 32px; padding: 36px 32px;
background: #fff; background: var(--el-bg-color);
color: var(--el-text-color-primary);
border-radius: 8px; border-radius: 8px;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
} }
@@ -79,7 +80,7 @@ async function submit() {
} }
.hint { .hint {
margin: 0 0 24px; margin: 0 0 24px;
color: #909399; color: var(--el-text-color-secondary);
font-size: 12px; font-size: 12px;
text-align: center; text-align: center;
} }

View File

@@ -120,6 +120,6 @@ onMounted(async () => {
padding: 12px 16px; padding: 12px 16px;
} }
.score-table { .score-table {
background: #fff; background: var(--el-bg-color);
} }
</style> </style>