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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user