Files
sanguo_vnpy_v2/frontend/src/views/factor/New.vue
T

158 lines
6.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
/**
* 因子分析提交页(终端风重构,方案B 设计稿 v2 拍板):
* 自选因子 × 自选标的池 × 自选区间的自由实验台。
* 要素与旧版零变化:query.factor 预填 / ≥1 因子 + ≥2 标的校验 / 混合分隔粘贴。
*/
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { getFactors, submitFactor, type FactorItem } from '@/api/factor'
import { TEARS_SAMPLE_CODES } from '@/constants/factorSamples'
import FactorPicker from './FactorPicker.vue'
import UniversePicker from './UniversePicker.vue'
const router = useRouter()
const route = useRoute()
const factors = ref<FactorItem[]>([])
const loading = ref(false)
const submitting = ref(false)
const pickerRef = ref<InstanceType<typeof FactorPicker> | null>(null)
const today = new Date().toISOString().slice(0, 10)
const form = ref({
factor_names: [] as string[],
symbols: [...TEARS_SAMPLE_CODES.slice(0, 8)],
start: '2024-01-02',
end: '2026-06-30',
})
const spanDays = computed(() => {
const a = new Date(form.value.start).getTime()
const b = new Date(form.value.end).getTime()
if (!Number.isFinite(a) || !Number.isFinite(b)) return null
return Math.round((b - a) / 864e5)
})
const valid = computed(() => form.value.factor_names.length >= 1 && form.value.symbols.length >= 2)
const validNote = computed(() => {
if (!form.value.factor_names.length) return '待办:至少选 1 个因子'
if (form.value.symbols.length < 2) return '待办:标的池 ≥2 只'
return ''
})
onMounted(async () => {
loading.value = true
try {
factors.value = await getFactors()
// 排行榜详情「自选样本分析」跳入时预填该因子
const qf = route.query.factor as string | undefined
if (qf && factors.value.some((f) => f.name === qf)) {
form.value.factor_names = [qf]
}
pickerRef.value?.hydrate()
} catch {
ElMessage.error('因子列表加载失败')
} finally {
loading.value = false
}
})
async function onSubmit(): Promise<void> {
if (!valid.value) return
submitting.value = true
try {
const tid = await submitFactor({
symbols: form.value.symbols,
factor_names: form.value.factor_names,
start: form.value.start,
end: form.value.end,
})
ElMessage.success('因子分析已提交')
router.push(`/factor/progress/${tid}`)
} catch {
ElMessage.error('提交失败')
} finally {
submitting.value = false
}
}
</script>
<template>
<div v-loading="loading" class="page factor-new">
<div class="page-head">
<div>
<h2 class="page-title">因子分析</h2>
<p class="page-sub">IC 横截面 · 分层 tears 报告 · 自由实验台换池 / 分年 / 多因子并排归这里</p>
</div>
</div>
<div class="pane">
<div class="ph"><span class="pt">因子选择</span><span class="tag">FACTOR PICKER · 支持多选</span></div>
<div class="pb">
<FactorPicker ref="pickerRef" v-model="form.factor_names" :factors="factors" />
</div>
</div>
<div class="pane">
<div class="ph"><span class="pt">标的池与区间</span><span class="tag">UNIVERSE &amp; SPAN</span></div>
<div class="pb">
<div class="formrow">
<div class="flab">标的池</div>
<div class="fbody">
<UniversePicker v-model="form.symbols" />
</div>
</div>
<div class="formrow gap-top">
<div class="flab">分析区间</div>
<div class="fbody">
<div class="dates">
<input v-model="form.start" type="date" :max="form.end">
<span class="sep"></span>
<input v-model="form.end" type="date" :max="today">
<span v-if="spanDays != null && spanDays >= 0" class="span-note">跨度 {{ spanDays }} {{ (spanDays / 365).toFixed(1) }} </span>
</div>
<p class="span-hint">区间前半作因子计算预热后半test 进入 IC 与分层统计</p>
</div>
</div>
</div>
</div>
<div class="submit-bar">
<button type="button" class="btn primary" :disabled="!valid || submitting" @click="onSubmit">
{{ submitting ? '提交中' : '提交分析 ' }}
</button>
<span v-if="validNote" class="valid-note">{{ validNote }}</span>
<span class="hint">提交后进入进度页完成自动跳转结果页</span>
</div>
</div>
</template>
<style scoped>
.factor-new { display: flex; flex-direction: column; gap: 14px; }
.pane { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--r-lg); min-width: 0; }
.pane .ph { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--border-2); flex-wrap: wrap; }
.pane .pt { font-size: 12.5px; font-weight: 700; letter-spacing: .06em; }
.pane .tag { font-family: var(--mono); font-size: 9.5px; color: var(--text-3); letter-spacing: .1em; }
.pane .pb { padding: 14px; }
.formrow { display: flex; align-items: flex-start; gap: 14px; }
.formrow.gap-top { margin-top: 14px; }
.flab { width: 88px; flex-shrink: 0; padding-top: 7px; text-align: right; color: var(--text-2); font-size: 12.5px; }
.fbody { flex: 1; min-width: 0; }
.dates { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.dates input[type='date'] { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-sm); color: var(--text); font-family: var(--mono); font-size: 12px; padding: 6px 10px; color-scheme: dark; }
.dates input[type='date']:focus { outline: 1px solid rgba(0,229,255,.5); }
.sep { color: var(--text-3); font-family: var(--mono); font-size: 11px; }
.span-note { font-family: var(--mono); font-size: 10.5px; color: var(--text-3); }
.span-hint { font-family: var(--mono); font-size: 10.5px; color: var(--text-3); margin: 8px 0 0; }
.submit-bar { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 2px 0; }
.btn { font-size: 12.5px; font-weight: 600; border-radius: var(--r-md); padding: 8px 18px; border: 1px solid var(--border); color: var(--text-2); background: var(--bg-card); transition: all .15s ease; cursor: pointer; }
.btn:hover { color: var(--text); border-color: var(--text-3); }
/* 对齐排行榜「发起新评估」同款(青软底+荧光青字,el-button--primary 观感) */
.btn.primary { color: var(--brand); background: var(--cyan-soft); border-color: rgba(0,229,255,.5); font-weight: 600; letter-spacing: .3px; }
.btn.primary:hover:not(:disabled) { color: var(--brand); background: rgba(0,229,255,.22); border-color: rgba(0,229,255,.65); }
.btn:disabled { opacity: .45; cursor: not-allowed; box-shadow: none; }
.valid-note { font-family: var(--mono); font-size: 11px; color: var(--amber); }
.hint { font-family: var(--mono); font-size: 10.5px; color: var(--text-3); }
</style>