rebuild(runtime): govern market operations and job truth

This commit is contained in:
leefer
2026-07-30 10:14:29 +08:00
parent 4fc8691eee
commit d8f0dd930c
39 changed files with 2224 additions and 79 deletions
@@ -19,13 +19,21 @@ function number(value: number | null, digits = 2): string {
</div>
<span v-if="run" class="tag">{{ run.items.length }} </span>
</header>
<div v-if="run?.status === 'data_incomplete'" class="notice notice-warning">
<div v-if="!run || run.status === 'pending'" class="screener-empty">
<strong>盘后选股尚未执行</strong>
<span>当日收盘行情定稿后由后台自动计算并归档</span>
</div>
<div v-else-if="run.status === 'running'" class="screener-empty">
<strong>正在计算候选结果</strong>
<span>完成后会自动保存本次结果</span>
</div>
<div v-else-if="run.status === 'data_incomplete'" class="notice notice-warning">
数据尚不完整{{ run.missing_fields.join("") }}
</div>
<div v-else-if="run?.status === 'failed'" class="notice notice-warning">
本次计算失败已隔离该策略不影响其他策略
<div v-else-if="run.status === 'failed'" class="notice notice-warning">
本次计算失败已隔离该策略不影响其他策略{{ run.error_message }}
</div>
<div v-else-if="!run || run.status === 'no_signal' || !run.items.length" class="screener-empty">
<div v-else-if="run.status === 'no_signal' || !run.items.length" class="screener-empty">
<strong>暂无符合条件个股</strong>
<span>完整数据下无信号会保留为空不补造候选</span>
</div>
@@ -9,7 +9,18 @@ const emit = defineEmits<{ track: [run: ScreenerRun, candidate: Candidate] }>();
const selected = ref(props.runs[0]?.strategy_id ?? props.strategies[0]?.id ?? "");
const run = computed(() => props.runs.find((item) => item.strategy_id === selected.value));
const strategy = computed(() => props.strategies.find((item) => item.id === (run.value?.strategy_id ?? selected.value)));
const hasRun = computed(() => Boolean(run.value));
const runState = computed<"idle" | "running" | "failed" | "completed">(() => {
if (!run.value || run.value.status === "pending") return "idle";
if (run.value.status === "running") return "running";
if (run.value.status === "failed") return "failed";
return "completed";
});
const runStateLabel = computed(() => ({
idle: "未执行",
running: "执行中",
failed: "失败",
completed: "已完成",
})[runState.value]);
watch(
() => props.runs,
@@ -26,10 +37,13 @@ watch(
<header class="stage-heading">
<div><span>当前阶段自动候选</span><strong>{{ strategy?.display_name ?? "等待盘后判定" }}</strong></div>
<p>{{ strategy?.description ?? "每日收盘数据定稿后自动生成,结果允许为空。" }}</p>
<span class="tag">盘后自动</span>
<span class="tag" :class="{ warning: runState === 'failed' }">{{ runStateLabel }}</span>
</header>
<div class="stage-flow" aria-label="自动选股流程">
<span :class="{ done: hasRun }">阶段识别</span><i></i><span :class="{ done: hasRun }">策略匹配</span><i></i><span :class="{ done: hasRun }">自动计算</span><i></i><span :class="{ done: hasRun }">结果归档</span>
<span :class="{ done: runState !== 'idle' }">阶段识别</span><i></i>
<span :class="{ done: runState !== 'idle' }">策略匹配</span><i></i>
<span :class="{ done: runState === 'running' || runState === 'completed' }">自动计算</span><i></i>
<span :class="{ done: runState === 'completed' }">结果归档</span>
</div>
<nav v-if="runs.length > 1" class="stage-run-tabs" aria-label="当日阶段策略">
<button v-for="item in runs" :key="item.id" type="button" :class="{ active: selected === item.strategy_id }" @click="selected = item.strategy_id">{{ item.strategy_name }}</button>
@@ -18,6 +18,18 @@ const selected = ref(props.strategies[0]?.id ?? "");
const strategy = computed(() => props.strategies.find((item) => item.id === selected.value) ?? filtered.value[0]);
const run = computed(() => props.runs.find((item) => item.strategy_id === strategy.value?.id));
const regimeLabels: Record<string, string> = { ice: "冰点", repair: "修复", fermentation: "发酵", climax: "高潮", divergence: "分化", retreat: "退潮" };
function statusLabel(strategyId: string): string {
const status = props.runs.find((item) => item.strategy_id === strategyId)?.status;
return ({
pending: "未执行",
running: "执行中",
completed: "有候选",
no_signal: "暂无信号",
data_incomplete: "数据不足",
failed: "计算失败",
} as Record<string, string>)[status ?? "pending"] ?? "未执行";
}
</script>
<template>
@@ -32,7 +44,7 @@ const regimeLabels: Record<string, string> = { ice: "冰点", repair: "修复",
<div class="strategy-items" :class="`is-${view}`">
<button v-for="item in filtered" :key="item.id" type="button" :class="{ active: strategy?.id === item.id }" @click="selected = item.id">
<strong>{{ item.display_name }}</strong><small>{{ item.formula.meta?.category }}</small>
<span :class="['strategy-status', runs.find((run) => run.strategy_id === item.id)?.status]">{{ runs.find((run) => run.strategy_id === item.id)?.status === 'completed' ? '有候选' : runs.find((run) => run.strategy_id === item.id)?.status === 'data_incomplete' ? '数据不足' : '暂无信号' }}</span>
<span :class="['strategy-status', runs.find((run) => run.strategy_id === item.id)?.status]">{{ statusLabel(item.id) }}</span>
</button>
</div>
</aside>