新增二板炸板情况
This commit is contained in:
@@ -40,6 +40,24 @@ export function getSecondBoardProfit(params) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLimitBoardBreakNextPerformance(params) {
|
||||||
|
return request({
|
||||||
|
url: '/limit-board/break-next-performance',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function downloadLimitBoardBreakNextPerformanceCsv(params) {
|
||||||
|
return request({
|
||||||
|
url: '/download/limit-board/break-next-performance-csv',
|
||||||
|
method: 'get',
|
||||||
|
params,
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 60000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function downloadSecondBoardProfitCsv(params) {
|
export function downloadSecondBoardProfitCsv(params) {
|
||||||
return request({
|
return request({
|
||||||
url: '/download/limit-board/second-board-profit-csv',
|
url: '/download/limit-board/second-board-profit-csv',
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ const routes = [
|
|||||||
component: () => import('@/views/stock/SecondBoard.vue'),
|
component: () => import('@/views/stock/SecondBoard.vue'),
|
||||||
meta: { title: '二板', icon: 'Download' }
|
meta: { title: '二板', icon: 'Download' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/stock/break-next-performance',
|
||||||
|
name: 'StockBreakNextPerformance',
|
||||||
|
component: () => import('@/views/stock/BreakNextPerformance.vue'),
|
||||||
|
meta: { title: '炸板次日表现', icon: 'TrendCharts' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/stock/download',
|
path: '/stock/download',
|
||||||
redirect: '/stock/second-board'
|
redirect: '/stock/second-board'
|
||||||
|
|||||||
@@ -0,0 +1,780 @@
|
|||||||
|
<template>
|
||||||
|
<div class="break-next-page">
|
||||||
|
<section class="filter-panel">
|
||||||
|
<div class="panel-head">
|
||||||
|
<div>
|
||||||
|
<h2>炸板次日表现</h2>
|
||||||
|
<p>按 T 日范围查询炸板股票,并展示 T+1 交易日表现。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form :inline="true" class="filter-form">
|
||||||
|
<el-form-item label="T日起">
|
||||||
|
<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="T日止">
|
||||||
|
<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 label="筛选">
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<el-checkbox v-model="tOpenGainLt101">
|
||||||
|
T开盘小于1%
|
||||||
|
</el-checkbox>
|
||||||
|
<el-checkbox v-model="breakTimeBefore1100">
|
||||||
|
11点前炸板
|
||||||
|
</el-checkbox>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item class="action-form-item">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:icon="SearchIcon"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="!canQuery"
|
||||||
|
@click="handleQuery(true)"
|
||||||
|
>
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
:icon="ResetIcon"
|
||||||
|
:disabled="loading || downloading"
|
||||||
|
@click="handleReset"
|
||||||
|
>
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
:icon="DownloadIcon"
|
||||||
|
:loading="downloading"
|
||||||
|
:disabled="!canQuery"
|
||||||
|
@click="handleDownload"
|
||||||
|
>
|
||||||
|
下载 CSV
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="table-panel">
|
||||||
|
<div class="table-shell">
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="tableData"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
class="performance-table"
|
||||||
|
style="width: 100%"
|
||||||
|
height="100%"
|
||||||
|
:header-cell-style="{
|
||||||
|
background: '#f8f9fb',
|
||||||
|
color: '#444',
|
||||||
|
textAlign: 'center',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
type="index"
|
||||||
|
label="Id"
|
||||||
|
width="70"
|
||||||
|
fixed="left"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="trade_date"
|
||||||
|
label="T日"
|
||||||
|
width="118"
|
||||||
|
fixed="left"
|
||||||
|
align="center"
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="ts_code"
|
||||||
|
label="股票代码"
|
||||||
|
width="126"
|
||||||
|
fixed="left"
|
||||||
|
align="center"
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="name"
|
||||||
|
label="股票简称"
|
||||||
|
width="112"
|
||||||
|
fixed="left"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="t_pct_chg"
|
||||||
|
label="T涨跌幅"
|
||||||
|
width="92"
|
||||||
|
align="right"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ formatSignedPct(row.t_pct_chg) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="t_open_gain_lt_101"
|
||||||
|
label="T开盘<1%"
|
||||||
|
width="104"
|
||||||
|
align="center"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag
|
||||||
|
:type="row.t_open_gain_lt_101 ? 'success' : 'info'"
|
||||||
|
effect="plain"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ formatBoolean(row.t_open_gain_lt_101) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="first_time"
|
||||||
|
label="炸板时间"
|
||||||
|
width="136"
|
||||||
|
align="center"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ row.first_time || "--" }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="break_time_before_1100"
|
||||||
|
label="11点前炸板"
|
||||||
|
width="104"
|
||||||
|
align="center"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag
|
||||||
|
:type="row.break_time_before_1100 ? 'success' : 'info'"
|
||||||
|
effect="plain"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ formatBoolean(row.break_time_before_1100) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="next_intraday_profitable"
|
||||||
|
label="T+1盘中可盈利"
|
||||||
|
width="120"
|
||||||
|
align="center"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag
|
||||||
|
:type="row.next_intraday_profitable ? 'success' : 'info'"
|
||||||
|
effect="plain"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ formatBoolean(row.next_intraday_profitable) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="next_high_profit_pct"
|
||||||
|
label="T+1最高获利"
|
||||||
|
width="104"
|
||||||
|
align="right"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ formatSignedPct(row.next_high_profit_pct) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="next_open_pct_chg"
|
||||||
|
label="T+1开盘涨幅"
|
||||||
|
width="104"
|
||||||
|
align="right"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ formatSignedPct(row.next_open_pct_chg) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="market"
|
||||||
|
label="市场"
|
||||||
|
width="92"
|
||||||
|
align="center"
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="industry"
|
||||||
|
label="行业"
|
||||||
|
min-width="120"
|
||||||
|
show-overflow-tooltip
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="t_open"
|
||||||
|
label="T开盘"
|
||||||
|
width="88"
|
||||||
|
align="right"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ formatPrice(row.t_open) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="t_open_pct_chg"
|
||||||
|
label="T开盘涨幅"
|
||||||
|
width="104"
|
||||||
|
align="right"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ formatSignedPct(row.t_open_pct_chg) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="t_high"
|
||||||
|
label="T最高"
|
||||||
|
width="88"
|
||||||
|
align="right"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ formatPrice(row.t_high) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="t_close"
|
||||||
|
label="T收盘"
|
||||||
|
width="88"
|
||||||
|
align="right"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ formatPrice(row.t_close) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="has_tushare_break_data"
|
||||||
|
label="炸板"
|
||||||
|
width="108"
|
||||||
|
align="center"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag
|
||||||
|
:type="row.has_tushare_break_data ? 'success' : 'info'"
|
||||||
|
effect="plain"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ row.has_tushare_break_data ? "有" : "无" }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="next_open"
|
||||||
|
label="T+1开盘"
|
||||||
|
width="92"
|
||||||
|
align="right"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ formatPrice(row.next_open) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="next_high"
|
||||||
|
label="T+1最高"
|
||||||
|
width="92"
|
||||||
|
align="right"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ formatPrice(row.next_high) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="next_low"
|
||||||
|
label="T+1最低"
|
||||||
|
width="92"
|
||||||
|
align="right"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ formatPrice(row.next_low) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="next_close"
|
||||||
|
label="T+1收盘"
|
||||||
|
width="92"
|
||||||
|
align="right"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ formatPrice(row.next_close) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="next_low_loss_pct"
|
||||||
|
label="T+1最大亏损"
|
||||||
|
width="104"
|
||||||
|
align="right"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ formatSignedPct(row.next_low_loss_pct) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="next_close_pct_chg"
|
||||||
|
label="T+1收盘涨跌"
|
||||||
|
width="112"
|
||||||
|
align="right"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">{{ formatSignedPct(row.next_close_pct_chg) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pagination-row">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="page"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
:total="total"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:layout="paginationLayout"
|
||||||
|
:pager-count="paginationPagerCount"
|
||||||
|
background
|
||||||
|
@size-change="handlePageSizeChange"
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import {
|
||||||
|
Download as DownloadIcon,
|
||||||
|
RefreshLeft as ResetIcon,
|
||||||
|
Search as SearchIcon,
|
||||||
|
} from "@element-plus/icons-vue";
|
||||||
|
import { fetchTradeCalendar } from "@/composables/useTradeCalendar";
|
||||||
|
import {
|
||||||
|
downloadLimitBoardBreakNextPerformanceCsv,
|
||||||
|
getLimitBoardBreakNextPerformance,
|
||||||
|
} from "@/api/stock";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "BreakNextPerformance",
|
||||||
|
});
|
||||||
|
|
||||||
|
const startDate = ref("");
|
||||||
|
const endDate = ref("");
|
||||||
|
const tOpenGainLt101 = ref(false);
|
||||||
|
const breakTimeBefore1100 = ref(false);
|
||||||
|
const openDatesSet = ref(new Set());
|
||||||
|
const maxTradeDate = ref("");
|
||||||
|
const loading = ref(false);
|
||||||
|
const downloading = ref(false);
|
||||||
|
const tableData = ref([]);
|
||||||
|
const total = ref(0);
|
||||||
|
const page = ref(1);
|
||||||
|
const pageSize = ref(20);
|
||||||
|
const viewportWidth = ref(window.innerWidth);
|
||||||
|
|
||||||
|
const canQuery = computed(
|
||||||
|
() =>
|
||||||
|
Boolean(startDate.value && endDate.value) &&
|
||||||
|
startDate.value <= endDate.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
const paginationLayout = computed(() =>
|
||||||
|
viewportWidth.value <= 820
|
||||||
|
? "total, prev, pager, next"
|
||||||
|
: "total, sizes, prev, pager, next, jumper",
|
||||||
|
);
|
||||||
|
|
||||||
|
const paginationPagerCount = computed(() => (viewportWidth.value <= 820 ? 5 : 7));
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
window.addEventListener("resize", syncViewport);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetchTradeCalendar();
|
||||||
|
const { open_dates: openDates = [], max_date: maxDate = "" } = res || {};
|
||||||
|
openDatesSet.value = new Set(openDates);
|
||||||
|
maxTradeDate.value = maxDate;
|
||||||
|
resetFilters();
|
||||||
|
|
||||||
|
if (canQuery.value) {
|
||||||
|
await handleQuery(true);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("初始化炸板次日表现页失败", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener("resize", syncViewport);
|
||||||
|
});
|
||||||
|
|
||||||
|
const syncViewport = () => {
|
||||||
|
viewportWidth.value = window.innerWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 resetFilters = () => {
|
||||||
|
const latestTradeDate = getLatestTradeDate();
|
||||||
|
startDate.value = latestTradeDate;
|
||||||
|
endDate.value = latestTradeDate;
|
||||||
|
tOpenGainLt101.value = false;
|
||||||
|
breakTimeBefore1100.value = false;
|
||||||
|
page.value = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = async () => {
|
||||||
|
resetFilters();
|
||||||
|
if (canQuery.value) {
|
||||||
|
await handleQuery(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLatestTradeDate = () => {
|
||||||
|
if (!openDatesSet.value.size) return "";
|
||||||
|
const openDates = [...openDatesSet.value].sort();
|
||||||
|
|
||||||
|
if (!maxTradeDate.value) return openDates.at(-1) || "";
|
||||||
|
if (openDatesSet.value.has(maxTradeDate.value)) return maxTradeDate.value;
|
||||||
|
|
||||||
|
return (
|
||||||
|
openDates.filter((date) => date <= maxTradeDate.value).at(-1) ||
|
||||||
|
openDates.at(-1) ||
|
||||||
|
""
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildQueryParams = (withPage = true) => {
|
||||||
|
const params = {
|
||||||
|
tradeStartDate: startDate.value,
|
||||||
|
tradeEndDate: endDate.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (withPage) {
|
||||||
|
params.page = page.value;
|
||||||
|
params.pageSize = pageSize.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tOpenGainLt101.value) {
|
||||||
|
params.tOpenGainLt101 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (breakTimeBefore1100.value) {
|
||||||
|
params.breakTimeBefore1100 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQuery = async (resetPage = false) => {
|
||||||
|
if (!validateDateRange()) return;
|
||||||
|
if (resetPage) page.value = 1;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getLimitBoardBreakNextPerformance(buildQueryParams(true));
|
||||||
|
tableData.value = Array.isArray(res?.list) ? res.list : [];
|
||||||
|
total.value = Number(res?.total || 0);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("加载炸板次日表现列表失败", err);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = async (currentPage) => {
|
||||||
|
page.value = currentPage;
|
||||||
|
await handleQuery(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageSizeChange = async (currentPageSize) => {
|
||||||
|
pageSize.value = currentPageSize;
|
||||||
|
page.value = 1;
|
||||||
|
await handleQuery(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
if (!validateDateRange()) return;
|
||||||
|
|
||||||
|
downloading.value = true;
|
||||||
|
try {
|
||||||
|
const blob = await downloadLimitBoardBreakNextPerformanceCsv(
|
||||||
|
buildQueryParams(false),
|
||||||
|
);
|
||||||
|
saveBlob(blob, getDownloadFilename());
|
||||||
|
ElMessage.success("CSV 下载已开始");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("下载炸板次日表现 CSV 失败", err);
|
||||||
|
} finally {
|
||||||
|
downloading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateDateRange = () => {
|
||||||
|
if (!startDate.value || !endDate.value) {
|
||||||
|
ElMessage.warning("请选择 T 日范围");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endDate.value < startDate.value) {
|
||||||
|
ElMessage.warning("结束日期不能早于起始日期");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openDatesSet.value.size > 0) {
|
||||||
|
if (!openDatesSet.value.has(startDate.value)) {
|
||||||
|
ElMessage.warning("T日起不是交易日");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!openDatesSet.value.has(endDate.value)) {
|
||||||
|
ElMessage.warning("T日止不是交易日");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
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_break_next_performance_${start}_${end}_daily_detail.csv`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatPrice = (value) => {
|
||||||
|
if (value === null || value === undefined || value === "") return "--";
|
||||||
|
const number = Number(value);
|
||||||
|
return Number.isFinite(number) ? number.toFixed(2) : "--";
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatSignedPct = (value) => {
|
||||||
|
if (value === null || value === undefined || value === "") return "--";
|
||||||
|
const number = Number(value);
|
||||||
|
if (!Number.isFinite(number)) return "--";
|
||||||
|
return `${number > 0 ? "+" : ""}${number.toFixed(2)}%`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatBoolean = (value) => {
|
||||||
|
if (value === null || value === undefined) return "--";
|
||||||
|
return value ? "是" : "否";
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.break-next-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
height: 100dvh;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f4f7f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-panel,
|
||||||
|
.table-panel {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5edf5;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 8px 22px rgba(31, 42, 55, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-panel {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 22px 24px 24px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-head {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-head h2 {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0;
|
||||||
|
color: #1f2a37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-head p {
|
||||||
|
margin: 0;
|
||||||
|
color: #667085;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-form {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px 10px;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-input {
|
||||||
|
width: 152px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px 16px;
|
||||||
|
min-height: 32px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.date-input .el-input__wrapper),
|
||||||
|
:deep(.date-input .el-input__inner) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.action-form-item .el-form-item__content) {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-panel {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-shell {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 360px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-row {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.performance-table .el-table__cell) {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.performance-table th.el-table__cell .cell) {
|
||||||
|
padding: 0 4px;
|
||||||
|
line-height: 16px;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.performance-table td.el-table__cell .cell) {
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1180px) {
|
||||||
|
.break-next-page {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-panel,
|
||||||
|
.table-panel {
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 820px) {
|
||||||
|
.break-next-page {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-panel,
|
||||||
|
.table-panel {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.filter-form .el-form-item),
|
||||||
|
:deep(.filter-form .el-form-item__content),
|
||||||
|
.date-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.action-form-item) {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.action-form-item .el-form-item__content) {
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.action-form-item .el-button) {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-shell {
|
||||||
|
min-height: 430px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-row {
|
||||||
|
justify-content: flex-start;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.filter-form {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.action-form-item .el-form-item__content) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.action-form-item .el-button) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -153,7 +153,7 @@
|
|||||||
border
|
border
|
||||||
stripe
|
stripe
|
||||||
class="profit-table"
|
class="profit-table"
|
||||||
style="width: 100%; min-width: 1440px"
|
style="width: 100%; min-width: 1880px"
|
||||||
height="100%"
|
height="100%"
|
||||||
:header-cell-style="{
|
:header-cell-style="{
|
||||||
background: '#f8f9fb',
|
background: '#f8f9fb',
|
||||||
@@ -957,7 +957,7 @@ const formatVolume = (value) => {
|
|||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-height: 360px;
|
min-height: 360px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stock-link {
|
.stock-link {
|
||||||
|
|||||||
Reference in New Issue
Block a user