fix(web): 生成tears按钮任务状态化防重复提交(用户08-30补充) [nas]
痛点:点击生成→progress→返回详情页按钮仍可点,重复生成同类任务。 方案:每因子记task_id(localStorage factorTearsTask.v1),按钮三态—— 无任务「生成tears报告」/进行中「分析进行中·查看进度→」(跳progress复用)/ 已完成「✓查看tears报告→」(跳result复用);failed或tears缺失(修复前旧 失败任务getTearsData探测404)自动清态恢复可重新生成;任务404(已清理)同。 mounted/watch factor刷新态;build绿
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, nextTick, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { getEvalDetail, submitFactor, type EvalDetail } from '@/api/factor'
|
||||
import { getEvalDetail, submitFactor, getTearsData, type EvalDetail } from '@/api/factor'
|
||||
import { getStatus } from '@/api/backtest'
|
||||
import { TEARS_SAMPLE_CODES } from '@/constants/factorSamples'
|
||||
import { useChart } from '@/composables/useChart'
|
||||
import { darkTitle, darkTooltip, darkGrid, darkAxis, BRAND } from '@/utils/echartsDark'
|
||||
@@ -51,13 +52,92 @@ function toggleCompare() {
|
||||
|
||||
// —— 一键生成 tears(方案A):排行榜数据没有分层序列,tears 要跑一次 alphalens 分析
|
||||
// (固定 30 只跨行业样本 × 近 2.5 年,分钟级),跑完结果页即新版 ECharts tears。
|
||||
// 任务状态化(用户 08-30 补充):每因子记住 task_id(localStorage),
|
||||
// 进行中→「查看进度」/已完成→「查看报告」复用任务,不再重复提交;
|
||||
// failed 或 tears 缺失(修复前旧任务)自动清态可重新生成。
|
||||
|
||||
const TEARS_SAMPLE_SPAN = { start: '2024-01-01', end: '2026-06-30' }
|
||||
const TEARS_TASK_KEY = 'factorTearsTask.v1'
|
||||
|
||||
const tearsSubmitting = ref(false)
|
||||
const tearsTaskId = ref('')
|
||||
const tearsBtn = ref<'none' | 'running' | 'view'>('none')
|
||||
|
||||
function tearsTaskMap(): Record<string, string> {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(TEARS_TASK_KEY) || '{}')
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function saveTearsTask(tid: string): void {
|
||||
if (!detail.value) return
|
||||
const map = tearsTaskMap()
|
||||
map[detail.value.factor] = tid
|
||||
try {
|
||||
localStorage.setItem(TEARS_TASK_KEY, JSON.stringify(map))
|
||||
} catch {
|
||||
/* 隐私模式等:不可持久化时仅本页防重 */
|
||||
}
|
||||
}
|
||||
|
||||
function forgetTearsTask(): void {
|
||||
if (!detail.value) return
|
||||
const map = tearsTaskMap()
|
||||
delete map[detail.value.factor]
|
||||
try {
|
||||
localStorage.setItem(TEARS_TASK_KEY, JSON.stringify(map))
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
tearsTaskId.value = ''
|
||||
}
|
||||
|
||||
async function refreshTearsBtn(): Promise<void> {
|
||||
const f = detail.value?.factor
|
||||
if (!f) return
|
||||
const tid = tearsTaskMap()[f]
|
||||
if (!tid) {
|
||||
tearsTaskId.value = ''
|
||||
tearsBtn.value = 'none'
|
||||
return
|
||||
}
|
||||
try {
|
||||
const s = await getStatus(tid)
|
||||
if (s.status === 'running' || s.status === 'pending') {
|
||||
tearsTaskId.value = tid
|
||||
tearsBtn.value = 'running'
|
||||
return
|
||||
}
|
||||
if (s.status === 'done') {
|
||||
tearsTaskId.value = tid
|
||||
tearsBtn.value = 'view'
|
||||
// done 但 tears 缺失(修复前的失败任务)→ 恢复可重新生成
|
||||
try {
|
||||
await getTearsData(tid, f)
|
||||
} catch {
|
||||
forgetTearsTask()
|
||||
tearsBtn.value = 'none'
|
||||
}
|
||||
return
|
||||
}
|
||||
forgetTearsTask() // failed → 可重跑
|
||||
} catch {
|
||||
forgetTearsTask() // 任务不存在(已清理)→ 可重跑
|
||||
}
|
||||
}
|
||||
|
||||
async function generateTears() {
|
||||
if (!detail.value || tearsSubmitting.value) return
|
||||
if (tearsBtn.value === 'running') {
|
||||
router.push(`/factor/progress/${tearsTaskId.value}`)
|
||||
return
|
||||
}
|
||||
if (tearsBtn.value === 'view') {
|
||||
router.push(`/factor/result/${tearsTaskId.value}`)
|
||||
return
|
||||
}
|
||||
tearsSubmitting.value = true
|
||||
try {
|
||||
const tid = await submitFactor({
|
||||
@@ -66,6 +146,7 @@ async function generateTears() {
|
||||
start: TEARS_SAMPLE_SPAN.start,
|
||||
end: TEARS_SAMPLE_SPAN.end,
|
||||
})
|
||||
saveTearsTask(tid)
|
||||
ElMessage.success('tears 分析已发起(30 只样本 · 约 1~3 分钟)')
|
||||
router.push(`/factor/progress/${tid}`)
|
||||
} catch {
|
||||
@@ -190,7 +271,15 @@ function toggleExpr() {
|
||||
expandedExpr.value = !expandedExpr.value
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
onMounted(async () => {
|
||||
await load()
|
||||
void refreshTearsBtn()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => detail.value?.factor,
|
||||
() => { void refreshTearsBtn() },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => [detail.value, period.value],
|
||||
@@ -343,7 +432,10 @@ function pct(val: number | null | undefined): string {
|
||||
<!-- Actions -->
|
||||
<div class="actions">
|
||||
<button class="btn primary" :disabled="tearsSubmitting" @click="generateTears">
|
||||
{{ tearsSubmitting ? '发起中…' : '生成 tears 报告' }}
|
||||
{{ tearsSubmitting ? '发起中…'
|
||||
: tearsBtn === 'running' ? '分析进行中 · 查看进度 →'
|
||||
: tearsBtn === 'view' ? '✓ 查看 tears 报告 →'
|
||||
: '生成 tears 报告' }}
|
||||
</button>
|
||||
<button class="btn" @click="goToNewFactor">自选样本分析</button>
|
||||
<button class="btn" :class="{ chosen: inCompare }" @click="toggleCompare">
|
||||
|
||||
Reference in New Issue
Block a user