新增低于 前一交易日101% 价格开盘逻辑。新增下载文件接口。
This commit is contained in:
+2
-1
@@ -41,13 +41,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ArrowLeftBold, ArrowRightBold, Histogram, TrendCharts } from '@element-plus/icons-vue'
|
import { ArrowLeftBold, ArrowRightBold, Download, Histogram, TrendCharts } from '@element-plus/icons-vue'
|
||||||
import { fetchTradeCalendar } from '@/composables/useTradeCalendar'
|
import { fetchTradeCalendar } from '@/composables/useTradeCalendar'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const isSidebarCollapsed = ref(false)
|
const isSidebarCollapsed = ref(false)
|
||||||
const iconMap = {
|
const iconMap = {
|
||||||
|
Download,
|
||||||
Histogram,
|
Histogram,
|
||||||
TrendCharts
|
TrendCharts
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,24 @@ export function getLimitBoardOpenLowerGroups(params) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLimitBoardOpenThresholdGroups(params) {
|
||||||
|
return request({
|
||||||
|
url: '/limit-board/open-threshold-groups',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function downloadSecondBoardProfitCsv(params) {
|
||||||
|
return request({
|
||||||
|
url: '/download/limit-board/second-board-profit-csv',
|
||||||
|
method: 'get',
|
||||||
|
params,
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 60000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 新增:获取历史K线
|
// 新增:获取历史K线
|
||||||
export function getKlineData(code) {
|
export function getKlineData(code) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ const routes = [
|
|||||||
name: 'StockLimitBoard',
|
name: 'StockLimitBoard',
|
||||||
component: () => import('@/views/stock/LimitBoard.vue'),
|
component: () => import('@/views/stock/LimitBoard.vue'),
|
||||||
meta: { title: '涨停连板概览', icon: 'Histogram' }
|
meta: { title: '涨停连板概览', icon: 'Histogram' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/stock/download',
|
||||||
|
name: 'StockDownload',
|
||||||
|
component: () => import('@/views/stock/Download.vue'),
|
||||||
|
meta: { title: '下载', icon: 'Download' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ const openKlineDialog = async (row) => {
|
|||||||
xAxis: { type: 'category', data: dates, scale: true, boundaryGap: false },
|
xAxis: { type: 'category', data: dates, scale: true, boundaryGap: false },
|
||||||
yAxis: { scale: true, splitArea: { show: true } },
|
yAxis: { scale: true, splitArea: { show: true } },
|
||||||
dataZoom: [
|
dataZoom: [
|
||||||
{ type: 'inside', start: 80, end: 100 },
|
{ type: 'inside', start: 95, end: 100 },
|
||||||
{ show: true, type: 'slider', top: '92%' }
|
{ show: true, type: 'slider', top: '92%' }
|
||||||
],
|
],
|
||||||
series: [{
|
series: [{
|
||||||
|
|||||||
@@ -0,0 +1,315 @@
|
|||||||
|
<template>
|
||||||
|
<div class="download-page">
|
||||||
|
<section class="download-toolbar">
|
||||||
|
<div class="toolbar-copy">
|
||||||
|
<h1>数据下载</h1>
|
||||||
|
<p>按交易日期范围下载策略明细 CSV。</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="download-panel">
|
||||||
|
<div class="panel-head">
|
||||||
|
<div>
|
||||||
|
<h2>二板早盘次日盈利</h2>
|
||||||
|
<p>下载符合二板早盘开盘阈值条件的股票明细,并查看后一交易日盈利情况。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form :inline="true" class="download-form">
|
||||||
|
<el-form-item label="起始日期">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="startDate"
|
||||||
|
type="date"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
:disabled-date="disableDates"
|
||||||
|
:editable="false"
|
||||||
|
placeholder="选择起始日期"
|
||||||
|
class="date-input"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="结束日期">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="endDate"
|
||||||
|
type="date"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
:disabled-date="disableDates"
|
||||||
|
:editable="false"
|
||||||
|
placeholder="选择结束日期"
|
||||||
|
class="date-input"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:icon="DownloadIcon"
|
||||||
|
:loading="downloading"
|
||||||
|
:disabled="!canDownload"
|
||||||
|
@click="handleDownload"
|
||||||
|
>
|
||||||
|
下载 CSV
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<div class="rule-grid">
|
||||||
|
<div class="rule-item">
|
||||||
|
<span class="rule-label">开盘条件</span>
|
||||||
|
<strong>低于前收 101%</strong>
|
||||||
|
</div>
|
||||||
|
<div class="rule-item">
|
||||||
|
<span class="rule-label">涨停时间</span>
|
||||||
|
<strong>早于 11:00</strong>
|
||||||
|
</div>
|
||||||
|
<div class="rule-item">
|
||||||
|
<span class="rule-label">盈利口径</span>
|
||||||
|
<strong>T 日收盘买入</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { Download as DownloadIcon } from '@element-plus/icons-vue'
|
||||||
|
import { fetchTradeCalendar } from '@/composables/useTradeCalendar'
|
||||||
|
import { downloadSecondBoardProfitCsv } from '@/api/stock'
|
||||||
|
|
||||||
|
const DEFAULT_START_DATE = '2026-01-01'
|
||||||
|
|
||||||
|
const startDate = ref(DEFAULT_START_DATE)
|
||||||
|
const endDate = ref('')
|
||||||
|
const downloading = ref(false)
|
||||||
|
const openDatesSet = ref(new Set())
|
||||||
|
const maxTradeDate = ref('')
|
||||||
|
|
||||||
|
const canDownload = computed(
|
||||||
|
() => Boolean(startDate.value && endDate.value) && startDate.value <= endDate.value
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetchTradeCalendar()
|
||||||
|
const { open_dates: openDates = [], max_date: maxDate = '' } = res || {}
|
||||||
|
|
||||||
|
openDatesSet.value = new Set(openDates)
|
||||||
|
maxTradeDate.value = maxDate
|
||||||
|
endDate.value = maxDate
|
||||||
|
startDate.value = getDefaultStartDate(openDates)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('初始化下载页失败', err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const clampToMaxDate = (date) => {
|
||||||
|
if (!date || !maxTradeDate.value || date <= maxTradeDate.value) return date
|
||||||
|
return maxTradeDate.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDefaultStartDate = (openDates) => {
|
||||||
|
const fallbackDate = clampToMaxDate(DEFAULT_START_DATE)
|
||||||
|
const sortedOpenDates = [...openDates].sort()
|
||||||
|
const validOpenDates = sortedOpenDates.filter(
|
||||||
|
(date) => !maxTradeDate.value || date <= maxTradeDate.value
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!validOpenDates.length || openDatesSet.value.has(fallbackDate)) return fallbackDate
|
||||||
|
|
||||||
|
return (
|
||||||
|
validOpenDates.find((date) => date >= fallbackDate) ||
|
||||||
|
validOpenDates[validOpenDates.length - 1] ||
|
||||||
|
fallbackDate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const disableDates = (time) => {
|
||||||
|
const date = dayjs(time).format('YYYY-MM-DD')
|
||||||
|
if (maxTradeDate.value && date > maxTradeDate.value) return true
|
||||||
|
return openDatesSet.value.size > 0 ? !openDatesSet.value.has(date) : false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
if (!startDate.value || !endDate.value) {
|
||||||
|
ElMessage.warning('请选择下载日期范围')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endDate.value < startDate.value) {
|
||||||
|
ElMessage.warning('结束日期不能早于起始日期')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
downloading.value = true
|
||||||
|
try {
|
||||||
|
const blob = await downloadSecondBoardProfitCsv({
|
||||||
|
start_date: startDate.value,
|
||||||
|
end_date: endDate.value
|
||||||
|
})
|
||||||
|
saveBlob(blob, getDownloadFilename())
|
||||||
|
ElMessage.success('CSV 下载已开始')
|
||||||
|
} catch (err) {
|
||||||
|
console.error('下载二板早盘次日盈利 CSV 失败', err)
|
||||||
|
} finally {
|
||||||
|
downloading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveBlob = (blob, filename) => {
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
|
||||||
|
link.href = url
|
||||||
|
link.download = filename
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDownloadFilename = () => {
|
||||||
|
const start = startDate.value.replaceAll('-', '')
|
||||||
|
const end = endDate.value.replaceAll('-', '')
|
||||||
|
return `limit_second_board_profit_${start}_${end}_daily_detail.csv`
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.download-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #f4f7f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-toolbar,
|
||||||
|
.download-panel {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5edf5;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 8px 22px rgba(31, 42, 55, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-toolbar {
|
||||||
|
padding: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-copy h1 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 36px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0;
|
||||||
|
color: #1f2a37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-copy p,
|
||||||
|
.panel-head p {
|
||||||
|
color: #667085;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-panel {
|
||||||
|
padding: 22px 24px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-head h2 {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0;
|
||||||
|
color: #1f2a37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-form {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px 6px;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-input {
|
||||||
|
width: 156px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-item {
|
||||||
|
min-height: 76px;
|
||||||
|
padding: 14px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #e8eef5;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8fbff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-label {
|
||||||
|
color: #7c8aa5;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-item strong {
|
||||||
|
color: #1f2a37;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper),
|
||||||
|
:deep(.el-input__inner) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 820px) {
|
||||||
|
.download-page {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-toolbar,
|
||||||
|
.download-panel {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-copy h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-form,
|
||||||
|
:deep(.download-form .el-form-item),
|
||||||
|
:deep(.download-form .el-form-item__content),
|
||||||
|
.date-input,
|
||||||
|
:deep(.download-form .el-button) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -90,6 +90,7 @@
|
|||||||
<el-tab-pane label="按板数" name="board" />
|
<el-tab-pane label="按板数" name="board" />
|
||||||
<el-tab-pane label="按板块" name="sector" />
|
<el-tab-pane label="按板块" name="sector" />
|
||||||
<el-tab-pane :label="openLowerTabLabel" name="openLower" />
|
<el-tab-pane :label="openLowerTabLabel" name="openLower" />
|
||||||
|
<el-tab-pane label="连板一下" name="openThreshold" />
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
||||||
<div v-if="displayDetailGroups.length" class="group-list">
|
<div v-if="displayDetailGroups.length" class="group-list">
|
||||||
@@ -147,6 +148,14 @@
|
|||||||
<span>开盘 {{ formatPrice(stock.open) }}</span>
|
<span>开盘 {{ formatPrice(stock.open) }}</span>
|
||||||
<span>前收 {{ formatPrice(stock.previous_close) }}</span>
|
<span>前收 {{ formatPrice(stock.previous_close) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="activeDetailTab === 'openThreshold'"
|
||||||
|
class="stock-open-threshold"
|
||||||
|
>
|
||||||
|
<span>开盘 {{ formatPrice(stock.open) }}</span>
|
||||||
|
<span>前收 {{ formatPrice(stock.previous_close) }}</span>
|
||||||
|
<span>阈值 {{ formatPrice(stock.open_threshold_price) }}</span>
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -180,6 +189,7 @@ import { fetchTradeCalendar } from "@/composables/useTradeCalendar";
|
|||||||
import {
|
import {
|
||||||
getKlineData,
|
getKlineData,
|
||||||
getLimitBoardOpenLowerGroups,
|
getLimitBoardOpenLowerGroups,
|
||||||
|
getLimitBoardOpenThresholdGroups,
|
||||||
getLimitBoardOverview,
|
getLimitBoardOverview,
|
||||||
} from "@/api/stock";
|
} from "@/api/stock";
|
||||||
|
|
||||||
@@ -220,6 +230,17 @@ const openLowerDetail = ref({
|
|||||||
},
|
},
|
||||||
groups: [],
|
groups: [],
|
||||||
});
|
});
|
||||||
|
const openThresholdDetail = ref({
|
||||||
|
trade_date: "",
|
||||||
|
previous_trade_date: "",
|
||||||
|
summary: {
|
||||||
|
limit_count: 0,
|
||||||
|
open_within_threshold_count: 0,
|
||||||
|
open_at_or_above_threshold_count: 0,
|
||||||
|
threshold_ratio: 1.01,
|
||||||
|
},
|
||||||
|
groups: [],
|
||||||
|
});
|
||||||
|
|
||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
const dialogTitle = ref("");
|
const dialogTitle = ref("");
|
||||||
@@ -292,6 +313,8 @@ const displayDetailGroups = computed(() => {
|
|||||||
if (activeDetailTab.value === "sector") return sectorDetailGroups.value;
|
if (activeDetailTab.value === "sector") return sectorDetailGroups.value;
|
||||||
if (activeDetailTab.value === "openLower")
|
if (activeDetailTab.value === "openLower")
|
||||||
return openLowerDisplayGroups.value;
|
return openLowerDisplayGroups.value;
|
||||||
|
if (activeDetailTab.value === "openThreshold")
|
||||||
|
return openThresholdDetail.value.groups;
|
||||||
return detailGroups.value.map((group) => ({
|
return detailGroups.value.map((group) => ({
|
||||||
...group,
|
...group,
|
||||||
key: `board-${group.board_count ?? group.label}`,
|
key: `board-${group.board_count ?? group.label}`,
|
||||||
@@ -313,6 +336,7 @@ const detailModeDescription = computed(
|
|||||||
board: "按连板高度从高到低展示当日涨停股票。",
|
board: "按连板高度从高到低展示当日涨停股票。",
|
||||||
sector: "按所属行业或板块汇总展示当日涨停股票。",
|
sector: "按所属行业或板块汇总展示当日涨停股票。",
|
||||||
openLower: `仅统计当日涨停且前一交易日也涨停的股票,首二一一红色优先,首板进二板黄色随后,前一交易日 ${openLowerDetail.value.previous_trade_date || "--"}。`,
|
openLower: `仅统计当日涨停且前一交易日也涨停的股票,首二一一红色优先,首板进二板黄色随后,前一交易日 ${openLowerDetail.value.previous_trade_date || "--"}。`,
|
||||||
|
openThreshold: `仅统计当日涨停且前一交易日也涨停的股票,并按开盘价是否低于前收 101% 分组,前一交易日 ${openThresholdDetail.value.previous_trade_date || "--"}。`,
|
||||||
})[activeDetailTab.value],
|
})[activeDetailTab.value],
|
||||||
);
|
);
|
||||||
const openLowerTabLabel = computed(() => {
|
const openLowerTabLabel = computed(() => {
|
||||||
@@ -379,9 +403,14 @@ const disableDates = (time) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchOverviewPage = async (tradeDate) => {
|
const fetchOverviewPage = async (tradeDate) => {
|
||||||
const [currentOverview, currentOpenLowerGroups] = await Promise.all([
|
const [
|
||||||
|
currentOverview,
|
||||||
|
currentOpenLowerGroups,
|
||||||
|
currentOpenThresholdGroups,
|
||||||
|
] = await Promise.all([
|
||||||
getLimitBoardOverview({ trade_date: tradeDate }),
|
getLimitBoardOverview({ trade_date: tradeDate }),
|
||||||
getLimitBoardOpenLowerGroups({ trade_date: tradeDate }),
|
getLimitBoardOpenLowerGroups({ trade_date: tradeDate }),
|
||||||
|
getLimitBoardOpenThresholdGroups({ trade_date: tradeDate }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
overview.value = normalizeOverview(currentOverview, tradeDate);
|
overview.value = normalizeOverview(currentOverview, tradeDate);
|
||||||
@@ -389,6 +418,10 @@ const fetchOverviewPage = async (tradeDate) => {
|
|||||||
currentOpenLowerGroups,
|
currentOpenLowerGroups,
|
||||||
tradeDate,
|
tradeDate,
|
||||||
);
|
);
|
||||||
|
openThresholdDetail.value = normalizeOpenThresholdGroups(
|
||||||
|
currentOpenThresholdGroups,
|
||||||
|
tradeDate,
|
||||||
|
);
|
||||||
strengthSeries.value = overview.value.recent_max_board_stats;
|
strengthSeries.value = overview.value.recent_max_board_stats;
|
||||||
detailTradeDate.value = overview.value.trade_date;
|
detailTradeDate.value = overview.value.trade_date;
|
||||||
detailGroups.value = overview.value.groups;
|
detailGroups.value = overview.value.groups;
|
||||||
@@ -402,9 +435,11 @@ const fetchDetailGroups = async (tradeDate) => {
|
|||||||
|
|
||||||
detailLoading.value = true;
|
detailLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const [detailOverview, detailOpenLowerGroups] = await Promise.all([
|
const [detailOverview, detailOpenLowerGroups, detailOpenThresholdGroups] =
|
||||||
|
await Promise.all([
|
||||||
getLimitBoardOverview({ trade_date: tradeDate }),
|
getLimitBoardOverview({ trade_date: tradeDate }),
|
||||||
getLimitBoardOpenLowerGroups({ trade_date: tradeDate }),
|
getLimitBoardOpenLowerGroups({ trade_date: tradeDate }),
|
||||||
|
getLimitBoardOpenThresholdGroups({ trade_date: tradeDate }),
|
||||||
]);
|
]);
|
||||||
const normalized = normalizeOverview(detailOverview, tradeDate);
|
const normalized = normalizeOverview(detailOverview, tradeDate);
|
||||||
detailTradeDate.value = normalized.trade_date;
|
detailTradeDate.value = normalized.trade_date;
|
||||||
@@ -413,6 +448,10 @@ const fetchDetailGroups = async (tradeDate) => {
|
|||||||
detailOpenLowerGroups,
|
detailOpenLowerGroups,
|
||||||
tradeDate,
|
tradeDate,
|
||||||
);
|
);
|
||||||
|
openThresholdDetail.value = normalizeOpenThresholdGroups(
|
||||||
|
detailOpenThresholdGroups,
|
||||||
|
tradeDate,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("刷新涨停分组明细失败", err);
|
console.error("刷新涨停分组明细失败", err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -461,6 +500,28 @@ const normalizeOpenLowerGroups = (data, fallbackTradeDate = "") => ({
|
|||||||
: [],
|
: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const normalizeOpenThresholdGroups = (data, fallbackTradeDate = "") => ({
|
||||||
|
trade_date: data?.trade_date || fallbackTradeDate,
|
||||||
|
previous_trade_date: data?.previous_trade_date || "",
|
||||||
|
summary: {
|
||||||
|
limit_count: data?.summary?.limit_count || 0,
|
||||||
|
open_within_threshold_count:
|
||||||
|
data?.summary?.open_within_threshold_count || 0,
|
||||||
|
open_at_or_above_threshold_count:
|
||||||
|
data?.summary?.open_at_or_above_threshold_count || 0,
|
||||||
|
threshold_ratio: Number(data?.summary?.threshold_ratio || 1.01),
|
||||||
|
},
|
||||||
|
groups: Array.isArray(data?.groups)
|
||||||
|
? data.groups.map((group) => ({
|
||||||
|
key: `open-threshold-${group?.key || group?.label || "group"}`,
|
||||||
|
rawKey: group?.key || "",
|
||||||
|
label: group?.label || "--",
|
||||||
|
count: Number(group?.count || 0),
|
||||||
|
stocks: Array.isArray(group?.stocks) ? group.stocks : [],
|
||||||
|
}))
|
||||||
|
: [],
|
||||||
|
});
|
||||||
|
|
||||||
const handleQuery = async () => {
|
const handleQuery = async () => {
|
||||||
if (!selectedDate.value) return;
|
if (!selectedDate.value) return;
|
||||||
|
|
||||||
@@ -815,7 +876,7 @@ const openKlineDialog = async (stock) => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
dataZoom: [
|
dataZoom: [
|
||||||
{ type: "inside", xAxisIndex: [0, 1], start: 80, end: 100 },
|
{ type: "inside", xAxisIndex: [0, 1], start: 95, end: 100 },
|
||||||
{ show: true, type: "slider", xAxisIndex: [0, 1], top: "90%" },
|
{ show: true, type: "slider", xAxisIndex: [0, 1], top: "90%" },
|
||||||
],
|
],
|
||||||
series: [
|
series: [
|
||||||
@@ -1152,7 +1213,8 @@ const openKlineDialog = async (stock) => {
|
|||||||
.stock-meta,
|
.stock-meta,
|
||||||
.stock-metrics,
|
.stock-metrics,
|
||||||
.stock-limit-time,
|
.stock-limit-time,
|
||||||
.stock-open-compare {
|
.stock-open-compare,
|
||||||
|
.stock-open-threshold {
|
||||||
color: #607289;
|
color: #607289;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
@@ -1166,7 +1228,8 @@ const openKlineDialog = async (stock) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stock-limit-time,
|
.stock-limit-time,
|
||||||
.stock-open-compare {
|
.stock-open-compare,
|
||||||
|
.stock-open-threshold {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -1181,7 +1244,8 @@ const openKlineDialog = async (stock) => {
|
|||||||
color: #7a8aa1;
|
color: #7a8aa1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stock-open-compare span {
|
.stock-open-compare span,
|
||||||
|
.stock-open-threshold span {
|
||||||
color: #526982;
|
color: #526982;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user