打分列表改为品种打分,移除同步数据功能
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,48 +1,61 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { listContracts, listScores, type Score } from '@/api/scores'
|
||||
import { nextTick, onMounted, reactive, ref, watch, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { listScores, listContracts, type Score } from '@/api/scores'
|
||||
import { runPipeline, type RunResponse } from '@/api/run'
|
||||
import ScoreDetailDrawer from '@/components/ScoreDetailDrawer.vue'
|
||||
import { parseTsCode } from '@/utils/contract'
|
||||
import { useMobile } from '@/composables/useMobile'
|
||||
|
||||
const { isMobile } = useMobile()
|
||||
|
||||
const filter = reactive<{
|
||||
ts_code?: string
|
||||
range: [string, string] | []
|
||||
signal?: string
|
||||
limit: number
|
||||
}>({
|
||||
ts_code: undefined,
|
||||
range: [],
|
||||
signal: undefined,
|
||||
limit: 200,
|
||||
})
|
||||
const EXCHANGES = [
|
||||
{ code: 'ZCE', name: '郑商所' },
|
||||
{ code: 'SHF', name: '上期所' },
|
||||
{ code: 'DCE', name: '大商所' },
|
||||
]
|
||||
|
||||
const contracts = ref<string[]>([])
|
||||
const rows = ref<Score[]>([])
|
||||
const loading = ref(false)
|
||||
const drawerScoreId = ref<number | null>(null)
|
||||
|
||||
async function reload(silent = false) {
|
||||
if (!silent) loading.value = true
|
||||
try {
|
||||
const [start, end] = filter.range || []
|
||||
rows.value = await listScores({
|
||||
ts_code: filter.ts_code,
|
||||
start: start || undefined,
|
||||
end: end || undefined,
|
||||
signal: filter.signal,
|
||||
limit: filter.limit,
|
||||
})
|
||||
} finally {
|
||||
if (!silent) loading.value = false
|
||||
}
|
||||
const SYMBOLS_BY_EXCHANGE: Record<string, string[]> = {
|
||||
ZCE: ['FG', 'SA', 'MA', 'CF'],
|
||||
SHF: ['RB'],
|
||||
DCE: ['M'],
|
||||
}
|
||||
|
||||
function toggleSignal(s: string) {
|
||||
filter.signal = filter.signal === s ? undefined : s
|
||||
reload(true)
|
||||
// 品种打分
|
||||
const selectedExchange = ref('')
|
||||
const selectedSymbol = ref('')
|
||||
const scoring = ref(false)
|
||||
const scoreResult = ref<RunResponse | null>(null)
|
||||
const resultRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const availableSymbols = computed(() => {
|
||||
if (!selectedExchange.value) return []
|
||||
return SYMBOLS_BY_EXCHANGE[selectedExchange.value] || []
|
||||
})
|
||||
|
||||
watch(selectedExchange, () => {
|
||||
selectedSymbol.value = ''
|
||||
})
|
||||
|
||||
async function handleScore() {
|
||||
if (!selectedSymbol.value) {
|
||||
ElMessage.warning('请选择品种')
|
||||
return
|
||||
}
|
||||
scoring.value = true
|
||||
scoreResult.value = null
|
||||
try {
|
||||
const resp = await runPipeline({ symbol: selectedSymbol.value })
|
||||
scoreResult.value = resp
|
||||
ElMessage.success('打分完成')
|
||||
await nextTick()
|
||||
resultRef.value?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
} catch (err: any) {
|
||||
const msg = err?.response?.data?.error || err.message || '请求失败'
|
||||
ElMessage.error(msg)
|
||||
} finally {
|
||||
scoring.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function signalTagType(s: string) {
|
||||
@@ -61,116 +74,245 @@ function signalIcon(s: string) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// 历史查询(折叠)
|
||||
const showHistory = ref(false)
|
||||
const historyFilter = reactive<{
|
||||
ts_code?: string
|
||||
range: [string, string] | []
|
||||
signal?: string
|
||||
limit: number
|
||||
}>({
|
||||
ts_code: undefined,
|
||||
range: [],
|
||||
signal: undefined,
|
||||
limit: 50,
|
||||
})
|
||||
|
||||
const contracts = ref<string[]>([])
|
||||
const historyRows = ref<Score[]>([])
|
||||
const historyLoading = ref(false)
|
||||
const drawerScoreId = ref<number | null>(null)
|
||||
|
||||
async function reloadHistory(silent = false) {
|
||||
if (!silent) historyLoading.value = true
|
||||
try {
|
||||
const [start, end] = historyFilter.range || []
|
||||
historyRows.value = await listScores({
|
||||
ts_code: historyFilter.ts_code,
|
||||
start: start || undefined,
|
||||
end: end || undefined,
|
||||
signal: historyFilter.signal,
|
||||
limit: historyFilter.limit,
|
||||
})
|
||||
} finally {
|
||||
if (!silent) historyLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSignal(s: string) {
|
||||
historyFilter.signal = historyFilter.signal === s ? undefined : s
|
||||
reloadHistory(true)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
contracts.value = await listContracts().catch(() => [])
|
||||
await reload()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<el-card shadow="never" class="filter-card">
|
||||
<!-- 品种打分 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span>品种打分</span>
|
||||
</template>
|
||||
<el-form :inline="!isMobile">
|
||||
<el-form-item label="合约">
|
||||
<el-form-item label="交易所">
|
||||
<el-select
|
||||
v-model="filter.ts_code"
|
||||
placeholder="全部合约"
|
||||
v-model="selectedExchange"
|
||||
placeholder="选择交易所"
|
||||
clearable
|
||||
filterable
|
||||
:style="{ width: isMobile ? '100%' : '200px' }"
|
||||
:style="{ width: isMobile ? '100%' : '160px' }"
|
||||
>
|
||||
<el-option v-for="c in contracts" :key="c" :label="c" :value="c" />
|
||||
<el-option
|
||||
v-for="ex in EXCHANGES"
|
||||
:key="ex.code"
|
||||
:label="ex.name"
|
||||
:value="ex.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期">
|
||||
<el-date-picker
|
||||
v-model="filter.range"
|
||||
type="daterange"
|
||||
value-format="YYYYMMDD"
|
||||
range-separator="→"
|
||||
start-placeholder="起"
|
||||
end-placeholder="止"
|
||||
:style="{ width: isMobile ? '100%' : 'auto' }"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="条数">
|
||||
<el-input-number v-model="filter.limit" :min="10" :max="500" :step="50" />
|
||||
<el-form-item label="品种">
|
||||
<el-select
|
||||
v-model="selectedSymbol"
|
||||
placeholder="选择品种"
|
||||
:disabled="!selectedExchange"
|
||||
:style="{ width: isMobile ? '100%' : '120px' }"
|
||||
>
|
||||
<el-option
|
||||
v-for="s in availableSymbols"
|
||||
:key="s"
|
||||
:label="s"
|
||||
:value="s"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="loading" style="width: 88px" @click="reload">查询</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="快捷" class="signal-item">
|
||||
<el-button-group class="signal-group" :size="isMobile ? 'small' : 'default'">
|
||||
<el-button
|
||||
:type="filter.signal === '强烈看多' ? 'success' : ''"
|
||||
@click="toggleSignal('强烈看多')"
|
||||
>
|
||||
强烈看多
|
||||
</el-button>
|
||||
<el-button
|
||||
:type="filter.signal === '偏多' ? 'primary' : ''"
|
||||
@click="toggleSignal('偏多')"
|
||||
>
|
||||
偏多
|
||||
</el-button>
|
||||
<el-button
|
||||
:type="filter.signal === '偏空' ? 'warning' : ''"
|
||||
@click="toggleSignal('偏空')"
|
||||
>
|
||||
偏空
|
||||
</el-button>
|
||||
<el-button
|
||||
:type="filter.signal === '强烈看空' ? 'danger' : ''"
|
||||
@click="toggleSignal('强烈看空')"
|
||||
>
|
||||
强烈看空
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="scoring"
|
||||
:disabled="!selectedSymbol"
|
||||
@click="handleScore"
|
||||
>
|
||||
打分
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<div class="table-wrapper" v-loading="loading">
|
||||
<el-table :data="rows" stripe class="score-table">
|
||||
<el-table-column prop="trade_date" label="日期" width="100" />
|
||||
<el-table-column label="品种" width="80">
|
||||
<template #default="{ row }">
|
||||
{{ parseTsCode(row.ts_code).symbol }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合约" width="80">
|
||||
<template #default="{ row }">
|
||||
{{ parseTsCode(row.ts_code).contract }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="close" label="收盘" width="90" />
|
||||
<el-table-column prop="oi" label="持仓" width="100" />
|
||||
<el-table-column prop="oi_chg" label="持仓变化" width="100" />
|
||||
<el-table-column prop="short_term" label="短期(7d)" width="90" />
|
||||
<el-table-column prop="medium_term" label="中期(15d)" width="90" />
|
||||
<el-table-column prop="long_term" label="长期(30d)" width="90" />
|
||||
<el-table-column prop="composite" label="综合" width="80">
|
||||
<template #default="{ row }">
|
||||
<strong>{{ row.composite.toFixed(2) }}</strong>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="signal" label="信号" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="signalTagType(row.signal)">{{ signalIcon(row.signal) }} {{ row.signal }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="drawerScoreId = row.id">明细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 打分结果 -->
|
||||
<div v-if="scoreResult" ref="resultRef">
|
||||
<el-card shadow="never" class="result-card">
|
||||
<template #header>
|
||||
<span>打分结果</span>
|
||||
</template>
|
||||
<el-descriptions :column="isMobile ? 1 : 2" border>
|
||||
<el-descriptions-item label="品种">
|
||||
{{ parseTsCode(scoreResult.ts_code).symbol }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="合约">
|
||||
{{ parseTsCode(scoreResult.ts_code).contract }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="日期">{{ scoreResult.trade_date }}</el-descriptions-item>
|
||||
<el-descriptions-item label="收盘">{{ scoreResult.close }}</el-descriptions-item>
|
||||
<el-descriptions-item label="持仓">{{ scoreResult.oi }}</el-descriptions-item>
|
||||
<el-descriptions-item label="短期(7d)">{{ scoreResult.short_term }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中期(15d)">{{ scoreResult.medium_term }}</el-descriptions-item>
|
||||
<el-descriptions-item label="长期(30d)">{{ scoreResult.long_term }}</el-descriptions-item>
|
||||
<el-descriptions-item label="综合">
|
||||
<strong>{{ scoreResult.composite }}</strong>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="信号" :span="isMobile ? 1 : 2">
|
||||
<el-tag :type="signalTagType(scoreResult.signal)">
|
||||
{{ signalIcon(scoreResult.signal) }} {{ scoreResult.signal }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<ScoreDetailDrawer
|
||||
:score-id="drawerScoreId"
|
||||
@close="drawerScoreId = null"
|
||||
/>
|
||||
<!-- 历史查询(折叠) -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="history-header" @click="showHistory = !showHistory">
|
||||
<span>历史打分查询</span>
|
||||
<span>{{ showHistory ? '▲' : '▼' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="showHistory">
|
||||
<el-form :inline="!isMobile">
|
||||
<el-form-item label="合约">
|
||||
<el-select
|
||||
v-model="historyFilter.ts_code"
|
||||
placeholder="全部合约"
|
||||
clearable
|
||||
filterable
|
||||
:style="{ width: isMobile ? '100%' : '200px' }"
|
||||
>
|
||||
<el-option v-for="c in contracts" :key="c" :label="c" :value="c" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期">
|
||||
<el-date-picker
|
||||
v-model="historyFilter.range"
|
||||
type="daterange"
|
||||
value-format="YYYYMMDD"
|
||||
range-separator="→"
|
||||
start-placeholder="起"
|
||||
end-placeholder="止"
|
||||
:style="{ width: isMobile ? '100%' : 'auto' }"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="条数">
|
||||
<el-input-number v-model="historyFilter.limit" :min="10" :max="500" :step="50" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="historyLoading" style="width: 88px" @click="reloadHistory">
|
||||
查询
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="快捷" class="signal-item">
|
||||
<el-button-group class="signal-group" :size="isMobile ? 'small' : 'default'">
|
||||
<el-button
|
||||
:type="historyFilter.signal === '强烈看多' ? 'success' : ''"
|
||||
@click="toggleSignal('强烈看多')"
|
||||
>
|
||||
强烈看多
|
||||
</el-button>
|
||||
<el-button
|
||||
:type="historyFilter.signal === '偏多' ? 'primary' : ''"
|
||||
@click="toggleSignal('偏多')"
|
||||
>
|
||||
偏多
|
||||
</el-button>
|
||||
<el-button
|
||||
:type="historyFilter.signal === '偏空' ? 'warning' : ''"
|
||||
@click="toggleSignal('偏空')"
|
||||
>
|
||||
偏空
|
||||
</el-button>
|
||||
<el-button
|
||||
:type="historyFilter.signal === '强烈看空' ? 'danger' : ''"
|
||||
@click="toggleSignal('强烈看空')"
|
||||
>
|
||||
强烈看空
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="table-wrapper" v-loading="historyLoading">
|
||||
<el-table :data="historyRows" stripe class="score-table">
|
||||
<el-table-column prop="trade_date" label="日期" width="100" />
|
||||
<el-table-column label="品种" width="80">
|
||||
<template #default="{ row }">
|
||||
{{ parseTsCode(row.ts_code).symbol }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合约" width="80">
|
||||
<template #default="{ row }">
|
||||
{{ parseTsCode(row.ts_code).contract }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="close" label="收盘" width="90" />
|
||||
<el-table-column prop="oi" label="持仓" width="100" />
|
||||
<el-table-column prop="oi_chg" label="持仓变化" width="100" />
|
||||
<el-table-column prop="short_term" label="短期(7d)" width="90" />
|
||||
<el-table-column prop="medium_term" label="中期(15d)" width="90" />
|
||||
<el-table-column prop="long_term" label="长期(30d)" width="90" />
|
||||
<el-table-column prop="composite" label="综合" width="80">
|
||||
<template #default="{ row }">
|
||||
<strong>{{ row.composite.toFixed(2) }}</strong>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="signal" label="信号" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="signalTagType(row.signal)">
|
||||
{{ signalIcon(row.signal) }} {{ row.signal }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="drawerScoreId = row.id">明细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<ScoreDetailDrawer :score-id="drawerScoreId" @close="drawerScoreId = null" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -180,6 +322,16 @@ onMounted(async () => {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.result-card {
|
||||
margin-top: 4px;
|
||||
}
|
||||
.history-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.filter-card :deep(.el-card__body) {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user