@@ -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 ? '管理员' : '普通用户' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-button type="primary" link @click="logout">退出登录</el-button>
|
||||
<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>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main>
|
||||
<router-view />
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<HTMLDivElement | 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() {
|
||||
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()
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -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'
|
||||
|
||||
32
web/frontend/src/stores/theme.ts
Normal file
32
web/frontend/src/stores/theme.ts
Normal 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 }
|
||||
})
|
||||
@@ -193,6 +193,6 @@ onMounted(reload)
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: #606266;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -120,6 +120,6 @@ onMounted(async () => {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
.score-table {
|
||||
background: #fff;
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user