新增首页详情合计页
This commit is contained in:
@@ -77,11 +77,21 @@
|
||||
</div>
|
||||
<div class="metric-row break">
|
||||
<span>炸板</span>
|
||||
<strong>{{ formatRatio(day.stat.break_next_profit_count, day.stat.break_count) }}</strong>
|
||||
<strong>{{
|
||||
formatRatio(
|
||||
day.stat.break_next_profit_count,
|
||||
getProfitDataCount(day.stat, "break"),
|
||||
)
|
||||
}}</strong>
|
||||
</div>
|
||||
<div class="metric-row limit">
|
||||
<span>涨停板</span>
|
||||
<strong>{{ formatRatio(day.stat.limit_next_profit_count, day.stat.limit_count) }}</strong>
|
||||
<strong>{{
|
||||
formatRatio(
|
||||
day.stat.limit_next_profit_count,
|
||||
getProfitDataCount(day.stat, "limit"),
|
||||
)
|
||||
}}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -123,7 +133,7 @@
|
||||
<strong>{{
|
||||
formatRatio(
|
||||
selectedDayStat?.limit_next_profit_count,
|
||||
selectedDayStat?.limit_count,
|
||||
getProfitDataCount(selectedDayStat, "limit"),
|
||||
)
|
||||
}}</strong>
|
||||
</div>
|
||||
@@ -132,58 +142,35 @@
|
||||
<strong>{{
|
||||
formatRatio(
|
||||
selectedDayStat?.break_next_profit_count,
|
||||
selectedDayStat?.break_count,
|
||||
getProfitDataCount(selectedDayStat, "break"),
|
||||
)
|
||||
}}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeDetailTab" class="detail-tabs">
|
||||
<el-tab-pane :label="`涨停板(${limitStocks.length})`" name="limit">
|
||||
<el-table :data="limitStocks" border stripe max-height="520" :default-sort="defaultDetailSort">
|
||||
<el-table-column prop="name" label="股票简称" min-width="120" sortable />
|
||||
<el-table-column prop="ts_code" label="股票代码" min-width="120" sortable />
|
||||
<el-table-column prop="industry" label="行业" min-width="120" sortable show-overflow-tooltip />
|
||||
<el-table-column
|
||||
prop="first_limit_time"
|
||||
label="首次涨停时间"
|
||||
width="130"
|
||||
align="center"
|
||||
sortable
|
||||
:sort-method="sortLimitTimeColumn"
|
||||
<el-tab-pane
|
||||
v-for="tab in detailTabs"
|
||||
:key="tab.name"
|
||||
:label="`${tab.label}(${tab.stocks.length})`"
|
||||
:name="tab.name"
|
||||
>
|
||||
<el-table
|
||||
:data="tab.stocks"
|
||||
border
|
||||
stripe
|
||||
max-height="520"
|
||||
class="detail-stock-table"
|
||||
:default-sort="defaultDetailSort"
|
||||
:row-class-name="getStockRowClassName"
|
||||
>
|
||||
<el-table-column v-if="tab.showStatus" prop="status" label="状态" width="86" align="center" sortable>
|
||||
<template #default="{ row }">
|
||||
{{ formatLimitTime(row.first_limit_time) }}
|
||||
<span :class="['stock-status', row.status === 'break' ? 'break' : 'limit']">
|
||||
{{ formatStatus(row.status) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="open_times" label="炸板次数" width="100" align="center" sortable>
|
||||
<template #default="{ row }">
|
||||
{{ formatOpenTimes(row.open_times) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="t_high" label="T日最高" width="100" align="right" sortable>
|
||||
<template #default="{ row }">{{ formatPrice(row.t_high) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="t_close" label="T日收盘" width="100" align="right" sortable>
|
||||
<template #default="{ row }">{{ formatPrice(row.t_close) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="next_high" label="T+1最高" width="100" align="right" sortable>
|
||||
<template #default="{ row }">{{ formatPrice(row.next_high) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="next_intraday_profitable" label="T+1盈利" width="100" align="center" sortable>
|
||||
<template #default="{ row }">
|
||||
{{ formatProfitFlag(row.next_intraday_profitable) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="next_high_profit_pct" label="最高收益" width="110" align="right" sortable>
|
||||
<template #default="{ row }">
|
||||
{{ formatSignedRate(row.next_high_profit_pct) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="`炸板(${breakStocks.length})`" name="break">
|
||||
<el-table :data="breakStocks" border stripe max-height="520" :default-sort="defaultDetailSort">
|
||||
<el-table-column prop="name" label="股票简称" min-width="120" sortable />
|
||||
<el-table-column prop="ts_code" label="股票代码" min-width="120" sortable />
|
||||
<el-table-column prop="industry" label="行业" min-width="120" sortable show-overflow-tooltip />
|
||||
@@ -281,6 +268,13 @@ const detailDialogTitle = computed(() => {
|
||||
const detailStocks = computed(() =>
|
||||
Array.isArray(selectedDayStat.value?.stocks) ? selectedDayStat.value.stocks : [],
|
||||
);
|
||||
const totalStocks = computed(() => {
|
||||
const stocks = Array.isArray(selectedDayStat.value?.total_stocks)
|
||||
? selectedDayStat.value.total_stocks
|
||||
: detailStocks.value;
|
||||
|
||||
return sortStocksByLimitTime(stocks);
|
||||
});
|
||||
const limitStocks = computed(() =>
|
||||
sortStocksByLimitTime(
|
||||
detailStocks.value.filter((stock) => stock?.status === "limit"),
|
||||
@@ -291,6 +285,26 @@ const breakStocks = computed(() =>
|
||||
detailStocks.value.filter((stock) => stock?.status === "break"),
|
||||
),
|
||||
);
|
||||
const detailTabs = computed(() => [
|
||||
{
|
||||
name: "total",
|
||||
label: "合计",
|
||||
stocks: totalStocks.value,
|
||||
showStatus: true,
|
||||
},
|
||||
{
|
||||
name: "limit",
|
||||
label: "涨停板",
|
||||
stocks: limitStocks.value,
|
||||
showStatus: false,
|
||||
},
|
||||
{
|
||||
name: "break",
|
||||
label: "炸板",
|
||||
stocks: breakStocks.value,
|
||||
showStatus: false,
|
||||
},
|
||||
]);
|
||||
const statMap = computed(() => {
|
||||
const map = new Map();
|
||||
|
||||
@@ -371,7 +385,11 @@ const openDayDetail = (day) => {
|
||||
if (!day?.stat) return;
|
||||
|
||||
selectedDay.value = day;
|
||||
activeDetailTab.value = limitStocks.value.length ? "limit" : "break";
|
||||
activeDetailTab.value = totalStocks.value.length
|
||||
? "total"
|
||||
: limitStocks.value.length
|
||||
? "limit"
|
||||
: "break";
|
||||
detailVisible.value = true;
|
||||
};
|
||||
|
||||
@@ -399,11 +417,14 @@ const fetchMonthData = async (withLoading = true) => {
|
||||
};
|
||||
|
||||
const sortStocksByLimitTime = (stocks) =>
|
||||
[...stocks].sort(
|
||||
(left, right) =>
|
||||
[...stocks].sort((left, right) => {
|
||||
const timeDiff =
|
||||
getLimitTimeSortValue(left?.first_limit_time) -
|
||||
getLimitTimeSortValue(right?.first_limit_time),
|
||||
);
|
||||
getLimitTimeSortValue(right?.first_limit_time);
|
||||
|
||||
if (timeDiff !== 0) return timeDiff;
|
||||
return String(left?.ts_code || "").localeCompare(String(right?.ts_code || ""));
|
||||
});
|
||||
|
||||
const sortLimitTimeColumn = (left, right) =>
|
||||
getLimitTimeSortValue(left?.first_limit_time) -
|
||||
@@ -445,6 +466,14 @@ const formatCount = (value) => {
|
||||
const formatRatio = (numerator, denominator) =>
|
||||
`${formatCount(numerator)}/${formatCount(denominator)}`;
|
||||
|
||||
const getProfitDataCount = (stat, type) => {
|
||||
if (!stat) return 0;
|
||||
|
||||
const nextDataKey = type === "break" ? "break_next_data_count" : "limit_next_data_count";
|
||||
const fallbackKey = type === "break" ? "break_count" : "limit_count";
|
||||
return stat[nextDataKey] ?? stat[fallbackKey];
|
||||
};
|
||||
|
||||
const formatPrice = (value) => {
|
||||
if (value === null || value === undefined || value === "") return "--";
|
||||
|
||||
@@ -466,6 +495,12 @@ const formatProfitFlag = (value) => {
|
||||
return "--";
|
||||
};
|
||||
|
||||
const formatStatus = (value) => {
|
||||
if (value === "limit") return "封板";
|
||||
if (value === "break") return "炸板";
|
||||
return "--";
|
||||
};
|
||||
|
||||
const formatLimitTime = (value) => {
|
||||
if (value === null || value === undefined || value === "") return "--";
|
||||
return String(value);
|
||||
@@ -477,6 +512,9 @@ const formatOpenTimes = (value) => {
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) ? number : "--";
|
||||
};
|
||||
|
||||
const getStockRowClassName = ({ row }) =>
|
||||
row?.status === "break" ? "break-stock-row" : "";
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -787,6 +825,39 @@ const formatOpenTimes = (value) => {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.stock-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 44px;
|
||||
height: 24px;
|
||||
padding: 0 8px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.stock-status.limit {
|
||||
background: #eaf2ff;
|
||||
color: #2563a8;
|
||||
}
|
||||
|
||||
.stock-status.break {
|
||||
background: #fee2e2;
|
||||
color: #b4232c;
|
||||
}
|
||||
|
||||
:deep(.detail-stock-table .break-stock-row td.el-table__cell) {
|
||||
background: #fff1f2 !important;
|
||||
color: #b4232c;
|
||||
}
|
||||
|
||||
:deep(.detail-stock-table .break-stock-row:hover > td.el-table__cell) {
|
||||
background: #ffe4e6 !important;
|
||||
}
|
||||
|
||||
:deep(.second-calendar-detail-dialog .el-dialog__body) {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user