feat(web): 前端全局重构专业量化后台(深色+红涨绿跌+设计系统)

- 设计系统 styles/tokens.css: 深色token(#0d1117底/#161b22卡片) + 红涨绿跌
  (--up#f85149/--down#3fb950) + 品牌蓝#1890ff + 间距/圆角/阴影/mono
- Element Plus深色(element-dark.css + dark/css-vars)+reset
- 删Vite脚手架残留(style.css重写/HelloWorld/hero/vite/vue资源)
- Layout重构(深色专业侧边)+Login+图表统一深色(useChart/echartsDark)
- 新建paper/Live.vue实走监控(step状态/净值/持仓/今日信号10s轮询)
- 业务页套.page头, 保留所有功能/路由/api
- 验证: vue-tsc --noEmit  + npm run build 
This commit is contained in:
2026-07-09 23:02:31 +08:00
parent 252deb5ec7
commit 18d4ba2013
29 changed files with 987 additions and 574 deletions
-95
View File
@@ -1,95 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
import viteLogo from '../assets/vite.svg'
import heroImg from '../assets/hero.png'
import vueLogo from '../assets/vue.svg'
const count = ref(0)
</script>
<template>
<section id="center">
<div class="hero">
<img :src="heroImg" class="base" width="170" height="179" alt="" />
<img :src="vueLogo" class="framework" alt="Vue logo" />
<img :src="viteLogo" class="vite" alt="Vite logo" />
</div>
<div>
<h1>Get started</h1>
<p>Edit <code>src/App.vue</code> and save to test <code>HMR</code></p>
</div>
<button type="button" class="counter" @click="count++">
Count is {{ count }}
</button>
</section>
<div class="ticks"></div>
<section id="next-steps">
<div id="docs">
<svg class="icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#documentation-icon"></use>
</svg>
<h2>Documentation</h2>
<p>Your questions, answered</p>
<ul>
<li>
<a href="https://vite.dev/" target="_blank">
<img class="logo" :src="viteLogo" alt="" />
Explore Vite
</a>
</li>
<li>
<a href="https://vuejs.org/" target="_blank">
<img class="button-icon" :src="vueLogo" alt="" />
Learn more
</a>
</li>
</ul>
</div>
<div id="social">
<svg class="icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#social-icon"></use>
</svg>
<h2>Connect with us</h2>
<p>Join the Vite community</p>
<ul>
<li>
<a href="https://github.com/vitejs/vite" target="_blank">
<svg class="button-icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#github-icon"></use>
</svg>
GitHub
</a>
</li>
<li>
<a href="https://chat.vite.dev/" target="_blank">
<svg class="button-icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#discord-icon"></use>
</svg>
Discord
</a>
</li>
<li>
<a href="https://x.com/vite_js" target="_blank">
<svg class="button-icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#x-icon"></use>
</svg>
X.com
</a>
</li>
<li>
<a href="https://bsky.app/profile/vite.dev" target="_blank">
<svg class="button-icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#bluesky-icon"></use>
</svg>
Bluesky
</a>
</li>
</ul>
</div>
</section>
<div class="ticks"></div>
<section id="spacer"></section>
</template>
@@ -1,34 +1,36 @@
<script setup lang="ts">
import { ref, watch, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
import { ref, onMounted, watch } from 'vue'
import type { EChartsCoreOption } from 'echarts'
import type { PnlPoint } from '@/api/backtest'
import { useChart } from '@/composables/useChart'
import { darkTitle, darkTooltip, darkGrid, darkAxis, UP, DOWN } from '@/utils/echartsDark'
const props = defineProps<{ data: PnlPoint[] }>()
const el = ref<HTMLDivElement>()
let chart: echarts.ECharts | null = null
const { setOption } = useChart(el)
function render(): void {
if (!chart || !props.data.length) return
chart.setOption({
title: { text: '每日盈亏', left: 'center' },
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: props.data.map((p) => p.date) },
yAxis: { type: 'value', name: '盈亏' },
if (!props.data.length) return
const option: EChartsCoreOption = {
title: darkTitle('每日盈亏'),
tooltip: darkTooltip(),
grid: darkGrid(),
xAxis: { type: 'category', data: props.data.map((p) => p.date), ...darkAxis() },
yAxis: { type: 'value', name: '盈亏', ...darkAxis() },
series: [{
type: 'bar',
// A 股惯例:红涨绿跌
data: props.data.map((p) => ({ value: p.pnl, itemStyle: { color: p.pnl >= 0 ? '#ee6666' : '#91cc75' } })),
data: props.data.map((p) => ({
value: p.pnl,
itemStyle: { color: p.pnl >= 0 ? UP : DOWN },
})),
}],
}, true)
}
setOption(option)
}
function resize(): void { chart?.resize() }
onMounted(() => {
if (el.value) chart = echarts.init(el.value)
render()
window.addEventListener('resize', resize)
})
onMounted(render)
watch(() => props.data, render, { deep: true })
onUnmounted(() => { window.removeEventListener('resize', resize); chart?.dispose() })
</script>
<template><div ref="el" class="chart-box" /></template>
+23 -18
View File
@@ -1,33 +1,38 @@
<script setup lang="ts">
import { ref, watch, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
import { ref, onMounted, watch } from 'vue'
import type { EChartsCoreOption } from 'echarts'
import type { EquityPoint } from '@/api/backtest'
import { useChart } from '@/composables/useChart'
import { darkTitle, darkTooltip, darkGrid, darkAxis, BRAND } from '@/utils/echartsDark'
const props = defineProps<{ data: EquityPoint[] }>()
const el = ref<HTMLDivElement>()
let chart: echarts.ECharts | null = null
const { setOption } = useChart(el)
function render(): void {
if (!chart || !props.data.length) return
chart.setOption({
title: { text: '资金曲线', left: 'center' },
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: props.data.map((p) => p.date) },
yAxis: { type: 'value', scale: true, name: '权益' },
if (!props.data.length) return
const option: EChartsCoreOption = {
title: darkTitle('资金曲线'),
tooltip: darkTooltip(),
grid: darkGrid(),
color: [BRAND],
xAxis: { type: 'category', data: props.data.map((p) => p.date), ...darkAxis() },
yAxis: { type: 'value', scale: true, name: '权益', ...darkAxis() },
series: [{
type: 'line', name: '权益', smooth: true, areaStyle: { opacity: 0.1 },
type: 'line',
name: '权益',
smooth: true,
showSymbol: false,
lineStyle: { color: BRAND, width: 1.6 },
areaStyle: { color: BRAND, opacity: 0.12 },
data: props.data.map((p) => p.balance),
}],
}, true)
}
setOption(option)
}
function resize(): void { chart?.resize() }
onMounted(() => {
if (el.value) chart = echarts.init(el.value)
render()
window.addEventListener('resize', resize)
})
onMounted(render)
watch(() => props.data, render, { deep: true })
onUnmounted(() => { window.removeEventListener('resize', resize); chart?.dispose() })
</script>
<template><div ref="el" class="chart-box" /></template>
+20 -20
View File
@@ -1,18 +1,20 @@
<script setup lang="ts">
import { ref, watch, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
import { ref, onMounted, watch } from 'vue'
import type { EChartsCoreOption } from 'echarts'
import type { KlineBar, Trade } from '@/api/backtest'
import { useChart } from '@/composables/useChart'
import { darkTitle, darkTooltip, darkGrid, darkAxis, klineItemStyle, UP, DOWN } from '@/utils/echartsDark'
const props = defineProps<{ kline: KlineBar[]; trades: Trade[] }>()
const el = ref<HTMLDivElement>()
let chart: echarts.ECharts | null = null
const { setOption } = useChart(el)
function dateOf(dt: string): string {
return String(dt).slice(0, 10)
}
function render(): void {
if (!chart || !props.kline.length) return
if (!props.kline.length) return
const dates = props.kline.map((k) => dateOf(k.datetime))
const markPoints = props.trades
.map((t) => ({ t, idx: dates.indexOf(dateOf(t.datetime)) }))
@@ -20,31 +22,29 @@ function render(): void {
.map(({ t }) => ({
coord: [dateOf(t.datetime), t.price],
value: `${t.offset === '开' ? '买' : '卖'}${t.volume}`,
itemStyle: { color: t.offset === '开' ? '#ee6666' : '#91cc75' },
itemStyle: { color: t.offset === '开' ? UP : DOWN },
symbol: 'triangle',
symbolSize: 14,
symbolSize: 12,
}))
chart.setOption({
title: { text: 'K线 + 买卖点', left: 'center' },
tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } },
xAxis: { type: 'category', data: dates, scale: true, boundaryGap: false },
yAxis: { scale: true },
const option: EChartsCoreOption = {
title: darkTitle('K线 + 买卖点'),
tooltip: { ...darkTooltip(), axisPointer: { type: 'cross' } },
grid: darkGrid(),
xAxis: { type: 'category', data: dates, scale: true, boundaryGap: false, ...darkAxis() },
yAxis: { type: 'value', scale: true, ...darkAxis() },
series: [{
type: 'candlestick',
itemStyle: klineItemStyle,
// ECharts order: [open, close, lowest, highest]
data: props.kline.map((k) => [k.open, k.close, k.low, k.high]),
markPoint: { data: markPoints, symbol: 'triangle', symbolSize: 14 },
markPoint: { data: markPoints, symbol: 'triangle', symbolSize: 12 },
}],
}, true)
}
setOption(option)
}
function resize(): void { chart?.resize() }
onMounted(() => {
if (el.value) chart = echarts.init(el.value)
render()
window.addEventListener('resize', resize)
})
onMounted(render)
watch(() => [props.kline, props.trades], render, { deep: true })
onUnmounted(() => { window.removeEventListener('resize', resize); chart?.dispose() })
</script>
<template><div ref="el" class="chart-box" /></template>