feat(strategy): TODO#68-#75八项全做+导航自测——#68灯职责拆分:实走/影子在跑=直达/paper/live/{aid}监控页,实盘在跑=/live/monitor/{aid},未跑=发起;回放灯done=直达/paper/result/{aid}(run_meta.replay.account_id);#69回测任务按实例过滤:backtest_stats加instance_id列(ALTER迁移)+_write_back落列+GET /task?instance=N+任务中心过滤条+策略库「回测历史」按钮;回测灯done=直达最新结果页(run_meta.backtest.task_id按前缀分流),跑过无结果=任务中心过滤;#70模拟盘创建后直接跳列表(不跳结果/监控页);列宽重排(策略/实例150模式96频率60操作250,折行实测消除);#71列内容对齐实盘:策略/实例列显示实例名(instances映射,#id退化)+持仓数列(list_papers顺带count volume>0)+删创建时间列(净值日期已含);#72实盘名自动生成={实例名}_v{YYYYMMDD}{minor}(同实例同日递增,listLives计数);模拟盘名={实例名}·{模式};#73术语统一档案→实例(全局8文件UI文案+后端409提示,精确短语防误伤,spec比喻保留文档);#74MonacoDiff v4:优先monaco原生diff(差异高亮),挂载450ms自检original栏占比<30%自动降级双只读编辑器(滚动联动)——全屏模态下大概率吃到原生diff;#75收益标签与数值同源(retInfoOf:运行ret优先含kind,fallback run_returns,标签跟随数值来源,根治影子1044%实为回测收益挂影子标签);927绿+build绿+dev浏览器自测:策略库6入口(回测历史/模拟历史/实盘历史/回测灯/实走灯直达监控/组合无回放)与模拟盘列表新列全过 [vps]
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
// 版本对比视图(§12.6 代码快照查看):左=历史快照 右=当前代码,各 50% 只读。
|
||||
// 不用 monaco createDiffEditor:0.56 在抽屉等嵌套容器里分栏测量错误
|
||||
// (original 栏被压 ~36px,layout/固定分栏/内联模式均救不回),双只读编辑器最稳。
|
||||
// 版本对比视图(§12.6 代码快照查看):优先 monaco 原生 diff(差异行高亮/跳转),
|
||||
// 运行时自检分栏宽度——若 original 栏被压扁(0.56 在部分嵌套容器里的测量 bug)
|
||||
// 自动降级为双只读编辑器(滚动联动),保证永远可用。
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import * as monaco from 'monaco-editor'
|
||||
|
||||
@@ -18,12 +18,35 @@ const props = withDefaults(defineProps<{
|
||||
language?: string
|
||||
}>(), { language: 'python' })
|
||||
|
||||
const leftEl = ref<HTMLDivElement>()
|
||||
const rightEl = ref<HTMLDivElement>()
|
||||
const el = ref<HTMLDivElement>()
|
||||
const mode = ref<'diff' | 'dual'>('diff')
|
||||
let diffEd: monaco.editor.IStandaloneDiffEditor | null = null
|
||||
let left: monaco.editor.IStandaloneCodeEditor | null = null
|
||||
let right: monaco.editor.IStandaloneCodeEditor | null = null
|
||||
let ro: ResizeObserver | null = null
|
||||
let timer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const OPTIONS: monaco.editor.IStandaloneEditorConstructionOptions = {
|
||||
const THEME: monaco.editor.IStandaloneThemeData = {
|
||||
base: 'vs-dark',
|
||||
inherit: true,
|
||||
rules: [
|
||||
{ token: 'comment', foreground: '4a5868', fontStyle: 'italic' },
|
||||
{ token: 'keyword', foreground: '00e5ff' },
|
||||
{ token: 'string', foreground: 'ffb000' },
|
||||
{ token: 'number', foreground: '2ee68a' },
|
||||
{ token: 'type', foreground: 'b48cff' },
|
||||
{ token: 'function', foreground: '7adfff' },
|
||||
{ token: 'delimiter', foreground: '7a8a9a' },
|
||||
],
|
||||
colors: {
|
||||
'editor.background': '#05080d',
|
||||
'editor.foreground': '#d4e4f0',
|
||||
'editorLineNumber.foreground': '#3a4654',
|
||||
'editorLineNumber.activeForeground': '#00e5ff',
|
||||
'editor.lineHighlightBackground': '#0a1018',
|
||||
},
|
||||
}
|
||||
const BASE_OPTS: monaco.editor.IStandaloneEditorConstructionOptions = {
|
||||
theme: 'sanguo-dark',
|
||||
readOnly: true,
|
||||
fontSize: 12,
|
||||
@@ -31,79 +54,110 @@ const OPTIONS: monaco.editor.IStandaloneEditorConstructionOptions = {
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
lineNumbersMinChars: 4,
|
||||
scrollbar: { alwaysConsumeMouseWheel: false },
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
monaco.editor.defineTheme('sanguo-dark', {
|
||||
base: 'vs-dark',
|
||||
inherit: true,
|
||||
rules: [
|
||||
{ token: 'comment', foreground: '4a5868', fontStyle: 'italic' },
|
||||
{ token: 'keyword', foreground: '00e5ff' },
|
||||
{ token: 'string', foreground: 'ffb000' },
|
||||
{ token: 'number', foreground: '2ee68a' },
|
||||
{ token: 'type', foreground: 'b48cff' },
|
||||
{ token: 'function', foreground: '7adfff' },
|
||||
{ token: 'delimiter', foreground: '7a8a9a' },
|
||||
],
|
||||
colors: {
|
||||
'editor.background': '#05080d',
|
||||
'editor.foreground': '#d4e4f0',
|
||||
'editorLineNumber.foreground': '#3a4654',
|
||||
'editorLineNumber.activeForeground': '#00e5ff',
|
||||
'editor.lineHighlightBackground': '#0a1018',
|
||||
},
|
||||
})
|
||||
if (leftEl.value) left = monaco.editor.create(leftEl.value, { ...OPTIONS, value: props.original, language: props.language })
|
||||
if (rightEl.value) right = monaco.editor.create(rightEl.value, { ...OPTIONS, value: props.modified, language: props.language })
|
||||
// 左右滚动联动:任一侧滚动时同步另一侧
|
||||
left?.onDidScrollChange(() => {
|
||||
if (right && left) {
|
||||
right.setScrollTop(left.getScrollTop())
|
||||
right.setScrollLeft(left.getScrollLeft())
|
||||
}
|
||||
})
|
||||
right?.onDidScrollChange(() => {
|
||||
if (left && right) {
|
||||
left.setScrollTop(right.getScrollTop())
|
||||
left.setScrollLeft(right.getScrollLeft())
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
watch(() => props.original, (v) => left?.setValue(v))
|
||||
watch(() => props.modified, (v) => right?.setValue(v))
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
function disposeAll(): void {
|
||||
const m = diffEd?.getModel()
|
||||
m?.original?.dispose()
|
||||
m?.modified?.dispose()
|
||||
diffEd?.dispose()
|
||||
diffEd = null
|
||||
left?.dispose()
|
||||
right?.dispose()
|
||||
left = null
|
||||
right = null
|
||||
}
|
||||
|
||||
function buildDiff(): void {
|
||||
if (!el.value) return
|
||||
diffEd = monaco.editor.createDiffEditor(el.value, {
|
||||
...BASE_OPTS,
|
||||
renderSideBySide: true,
|
||||
})
|
||||
diffEd.setModel({
|
||||
original: monaco.editor.createModel(props.original, props.language),
|
||||
modified: monaco.editor.createModel(props.modified, props.language),
|
||||
})
|
||||
// 自检:等布局稳定后量 original 栏占比,压扁则降级
|
||||
timer = setTimeout(() => {
|
||||
const box = el.value?.getBoundingClientRect()
|
||||
const orig = el.value?.querySelector('.editor.original')?.getBoundingClientRect()
|
||||
if (!box || !orig || box.width < 50) return
|
||||
if (orig.width < box.width * 0.3) {
|
||||
disposeAll()
|
||||
buildDual()
|
||||
}
|
||||
}, 450)
|
||||
}
|
||||
|
||||
function buildDual(): void {
|
||||
if (!el.value) return
|
||||
mode.value = 'dual'
|
||||
// 双栏 DOM 由模板渲染;此处等下一帧(模板切换后)再建 editor
|
||||
requestAnimationFrame(() => {
|
||||
const l = el.value?.querySelector('.dual-left') as HTMLDivElement | null
|
||||
const r = el.value?.querySelector('.dual-right') as HTMLDivElement | null
|
||||
if (l) left = monaco.editor.create(l, { ...BASE_OPTS, value: props.original, language: props.language })
|
||||
if (r) right = monaco.editor.create(r, { ...BASE_OPTS, value: props.modified, language: props.language })
|
||||
left?.onDidScrollChange(() => {
|
||||
if (right && left) { right.setScrollTop(left.getScrollTop()); right.setScrollLeft(left.getScrollLeft()) }
|
||||
})
|
||||
right?.onDidScrollChange(() => {
|
||||
if (left && right) { left.setScrollTop(right.getScrollTop()); left.setScrollLeft(right.getScrollLeft()) }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
monaco.editor.defineTheme('sanguo-dark', THEME)
|
||||
buildDiff()
|
||||
if (typeof ResizeObserver !== 'undefined' && el.value) {
|
||||
ro = new ResizeObserver(() => diffEd?.layout())
|
||||
ro.observe(el.value)
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => [props.original, props.modified], () => {
|
||||
if (diffEd) {
|
||||
diffEd.setModel({
|
||||
original: monaco.editor.createModel(props.original, props.language),
|
||||
modified: monaco.editor.createModel(props.modified, props.language),
|
||||
})
|
||||
} else {
|
||||
left?.setValue(props.original)
|
||||
right?.setValue(props.modified)
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (timer) clearTimeout(timer)
|
||||
ro?.disconnect()
|
||||
ro = null
|
||||
disposeAll()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="diff-wrap" data-relayout="v3">
|
||||
<!-- diff 模式:单容器由 diff editor 自渲染;dual 模式:模板双栏 -->
|
||||
<div v-show="mode === 'diff'" ref="el" class="diff-host" data-relayout="v4"></div>
|
||||
<div v-if="mode === 'dual'" class="diff-wrap">
|
||||
<div class="pane">
|
||||
<div class="pane-tag mono">快照版本</div>
|
||||
<div ref="leftEl" class="pane-editor"></div>
|
||||
<div class="pane-editor dual-left"></div>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<div class="pane-tag mono pane-cur">当前代码</div>
|
||||
<div ref="rightEl" class="pane-editor"></div>
|
||||
<div class="pane-editor dual-right"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.diff-wrap { display: flex; height: 100%; min-height: 300px; gap: 0; background: #05080d; }
|
||||
.diff-host { height: 100%; min-height: 300px; background: #05080d; }
|
||||
.diff-wrap { display: flex; height: 100%; min-height: 300px; background: #05080d; }
|
||||
.pane { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; border-right: 1px solid #16202e; }
|
||||
.pane:last-child { border-right: none; }
|
||||
.pane-tag {
|
||||
font-size: 10px; color: #64798a; padding: 3px 10px; letter-spacing: 1px;
|
||||
border-bottom: 1px solid #16202e; background: #0a0f17;
|
||||
}
|
||||
.pane-tag { font-size: 10px; color: #64798a; padding: 3px 10px; letter-spacing: 1px; border-bottom: 1px solid #16202e; background: #0a0f17; }
|
||||
.pane-cur { color: #00e5ff; }
|
||||
.pane-editor { flex: 1; min-height: 0; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user