新增首页详情合计页
This commit is contained in:
@@ -77,11 +77,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="metric-row break">
|
<div class="metric-row break">
|
||||||
<span>炸板</span>
|
<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>
|
||||||
<div class="metric-row limit">
|
<div class="metric-row limit">
|
||||||
<span>涨停板</span>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -123,7 +133,7 @@
|
|||||||
<strong>{{
|
<strong>{{
|
||||||
formatRatio(
|
formatRatio(
|
||||||
selectedDayStat?.limit_next_profit_count,
|
selectedDayStat?.limit_next_profit_count,
|
||||||
selectedDayStat?.limit_count,
|
getProfitDataCount(selectedDayStat, "limit"),
|
||||||
)
|
)
|
||||||
}}</strong>
|
}}</strong>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,58 +142,35 @@
|
|||||||
<strong>{{
|
<strong>{{
|
||||||
formatRatio(
|
formatRatio(
|
||||||
selectedDayStat?.break_next_profit_count,
|
selectedDayStat?.break_next_profit_count,
|
||||||
selectedDayStat?.break_count,
|
getProfitDataCount(selectedDayStat, "break"),
|
||||||
)
|
)
|
||||||
}}</strong>
|
}}</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-tabs v-model="activeDetailTab" class="detail-tabs">
|
<el-tabs v-model="activeDetailTab" class="detail-tabs">
|
||||||
<el-tab-pane :label="`涨停板(${limitStocks.length})`" name="limit">
|
<el-tab-pane
|
||||||
<el-table :data="limitStocks" border stripe max-height="520" :default-sort="defaultDetailSort">
|
v-for="tab in detailTabs"
|
||||||
<el-table-column prop="name" label="股票简称" min-width="120" sortable />
|
:key="tab.name"
|
||||||
<el-table-column prop="ts_code" label="股票代码" min-width="120" sortable />
|
:label="`${tab.label}(${tab.stocks.length})`"
|
||||||
<el-table-column prop="industry" label="行业" min-width="120" sortable show-overflow-tooltip />
|
:name="tab.name"
|
||||||
<el-table-column
|
|
||||||
prop="first_limit_time"
|
|
||||||
label="首次涨停时间"
|
|
||||||
width="130"
|
|
||||||
align="center"
|
|
||||||
sortable
|
|
||||||
:sort-method="sortLimitTimeColumn"
|
|
||||||
>
|
>
|
||||||
|
<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 }">
|
<template #default="{ row }">
|
||||||
{{ formatLimitTime(row.first_limit_time) }}
|
<span :class="['stock-status', row.status === 'break' ? 'break' : 'limit']">
|
||||||
|
{{ formatStatus(row.status) }}
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</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="name" label="股票简称" min-width="120" sortable />
|
||||||
<el-table-column prop="ts_code" 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="industry" label="行业" min-width="120" sortable show-overflow-tooltip />
|
||||||
@@ -281,6 +268,13 @@ const detailDialogTitle = computed(() => {
|
|||||||
const detailStocks = computed(() =>
|
const detailStocks = computed(() =>
|
||||||
Array.isArray(selectedDayStat.value?.stocks) ? selectedDayStat.value.stocks : [],
|
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(() =>
|
const limitStocks = computed(() =>
|
||||||
sortStocksByLimitTime(
|
sortStocksByLimitTime(
|
||||||
detailStocks.value.filter((stock) => stock?.status === "limit"),
|
detailStocks.value.filter((stock) => stock?.status === "limit"),
|
||||||
@@ -291,6 +285,26 @@ const breakStocks = computed(() =>
|
|||||||
detailStocks.value.filter((stock) => stock?.status === "break"),
|
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 statMap = computed(() => {
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
|
|
||||||
@@ -371,7 +385,11 @@ const openDayDetail = (day) => {
|
|||||||
if (!day?.stat) return;
|
if (!day?.stat) return;
|
||||||
|
|
||||||
selectedDay.value = day;
|
selectedDay.value = day;
|
||||||
activeDetailTab.value = limitStocks.value.length ? "limit" : "break";
|
activeDetailTab.value = totalStocks.value.length
|
||||||
|
? "total"
|
||||||
|
: limitStocks.value.length
|
||||||
|
? "limit"
|
||||||
|
: "break";
|
||||||
detailVisible.value = true;
|
detailVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -399,11 +417,14 @@ const fetchMonthData = async (withLoading = true) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sortStocksByLimitTime = (stocks) =>
|
const sortStocksByLimitTime = (stocks) =>
|
||||||
[...stocks].sort(
|
[...stocks].sort((left, right) => {
|
||||||
(left, right) =>
|
const timeDiff =
|
||||||
getLimitTimeSortValue(left?.first_limit_time) -
|
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) =>
|
const sortLimitTimeColumn = (left, right) =>
|
||||||
getLimitTimeSortValue(left?.first_limit_time) -
|
getLimitTimeSortValue(left?.first_limit_time) -
|
||||||
@@ -445,6 +466,14 @@ const formatCount = (value) => {
|
|||||||
const formatRatio = (numerator, denominator) =>
|
const formatRatio = (numerator, denominator) =>
|
||||||
`${formatCount(numerator)}/${formatCount(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) => {
|
const formatPrice = (value) => {
|
||||||
if (value === null || value === undefined || value === "") return "--";
|
if (value === null || value === undefined || value === "") return "--";
|
||||||
|
|
||||||
@@ -466,6 +495,12 @@ const formatProfitFlag = (value) => {
|
|||||||
return "--";
|
return "--";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatStatus = (value) => {
|
||||||
|
if (value === "limit") return "封板";
|
||||||
|
if (value === "break") return "炸板";
|
||||||
|
return "--";
|
||||||
|
};
|
||||||
|
|
||||||
const formatLimitTime = (value) => {
|
const formatLimitTime = (value) => {
|
||||||
if (value === null || value === undefined || value === "") return "--";
|
if (value === null || value === undefined || value === "") return "--";
|
||||||
return String(value);
|
return String(value);
|
||||||
@@ -477,6 +512,9 @@ const formatOpenTimes = (value) => {
|
|||||||
const number = Number(value);
|
const number = Number(value);
|
||||||
return Number.isFinite(number) ? number : "--";
|
return Number.isFinite(number) ? number : "--";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getStockRowClassName = ({ row }) =>
|
||||||
|
row?.status === "break" ? "break-stock-row" : "";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -787,6 +825,39 @@ const formatOpenTimes = (value) => {
|
|||||||
min-width: 0;
|
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) {
|
:deep(.second-calendar-detail-dialog .el-dialog__body) {
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user