fiee-official-website/src/views/historic-stock/size1440/index.vue
2025-10-10 14:50:16 +08:00

894 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="historic-data-container w-[1240px]">
<div class="echarts-container">
<customEcharts></customEcharts>
</div>
<div class="header">
<!-- 标题区域 -->
<div class="title-section">
<div class="title-decoration"></div>
<div class="historic-title">Historical Data</div>
</div>
</div>
<div class="filter-container">
<span class="range-label">Range</span>
<div
v-for="option in durationOptions"
:key="option.key"
class="filter-option"
:class="{ active: state.selectedDuration === option.key }"
@click="handleDurationChange(option.key)"
>
{{
option.label
.replace(" Months", "m")
.replace(" Years", "Y")
.replace(" Year", "Y")
.replace(" to Date", "TD")
}}
</div>
</div>
<!-- reports-table from annualreports -->
<div class="reports-table">
<div class="table-header">
<div
class="column"
v-for="col in columns"
:key="col.key"
:style="{
width: col.width ? col.width + 'px' : 'auto',
flex: col.width ? 'none' : 1,
'text-align': col.align,
}"
>
{{ col.title }}
</div>
</div>
<div class="reports-list">
<div
class="table-row"
v-for="(row, index) in paginatedData"
:key="index"
>
<div
class="column"
v-for="col in columns"
:key="col.key"
:style="{
width: col.width ? col.width + 'px' : 'auto',
flex: col.width ? 'none' : 1,
'text-align': col.align,
}"
>
<span
v-if="col.key === 'change'"
:style="{
color:
parseFloat(row.change) < 0
? '#cf3050'
: parseFloat(row.change) > 0
? '#18a058'
: '',
}"
>
{{ row[col.key] }}
</span>
<span v-else>
{{ row[col.key] }}
</span>
</div>
</div>
</div>
</div>
<!-- pagination-container from annualreports -->
<div class="pagination-container">
<div class="pagination-info">
Displaying {{ displayRange.start }} - {{ displayRange.end }} of
{{ state.tableData.length }} results
</div>
<div class="pagination-controls">
<div class="pagination-buttons">
<button
class="page-btn prev-btn"
:disabled="state.currentPage === 1"
@click="goToPrevPage"
>
<svg width="5" height="9" viewBox="0 0 5 9" fill="none">
<path
d="M4 1L1 4.5L4 8"
stroke="#455363"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</button>
<template v-for="page in getVisiblePages()" :key="page">
<button
v-if="page !== '...'"
class="page-btn"
:class="{ active: page === state.currentPage }"
@click="goToPage(page)"
>
{{ page }}
</button>
<button v-else class="page-btn disabled" disabled>...</button>
</template>
<button
class="page-btn next-btn"
:disabled="state.currentPage === totalPages"
@click="goToNextPage"
>
<svg width="5" height="9" viewBox="0 0 5 9" fill="none">
<path
d="M1 1L4 4.5L1 8"
stroke="#455363"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</button>
</div>
<div class="page-size-selector" @click="togglePageSizeMenu">
<span>{{ state.pageSize }}/page</span>
<svg width="10" height="5" viewBox="0 0 10 5" fill="none">
<path
d="M1 1L5 4L9 1"
stroke="#455363"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<div v-if="showPageSizeMenu" class="page-size-menu">
<div
v-for="size in [10, 50, 100, 500, 1000]"
:key="size"
class="page-size-option"
:class="{ active: state.pageSize === size }"
@click="handlePageSizeChange(size)"
>
{{ size }}/page
</div>
</div>
</div>
<div class="goto-section">
<span>Goto</span>
<input
type="number"
v-model="state.gotoPage"
class="goto-input"
:min="1"
:max="totalPages"
@keyup.enter="handleGoto"
/>
</div>
</div>
</div>
<!-- <div class="back-to-top-link">
<a href="#" @click.prevent="scrollToTop">
Back to Top
<n-icon><arrow-up-outline /></n-icon>
</a>
</div> -->
</div>
</template>
<script setup>
import { NDropdown, NIcon } from "naive-ui";
import { reactive, onMounted, h, computed, ref, watch, onUnmounted } from "vue";
import axios from "axios";
import { ChevronDownOutline, ArrowUpOutline } from "@vicons/ionicons5";
import defaultTableData from "../data";
import customEcharts from "@/components/customEcharts/index.vue";
// 数据筛选选项
const periodOptions = [
{ label: "Daily", key: "Daily" },
{ label: "Weekly", key: "Weekly" },
{ label: "Monthly", key: "Monthly" },
{ label: "Quarterly", key: "Quarterly" },
{ label: "Annual", key: "Annual" },
];
const durationOptions = [
{ label: "3 Months", key: "3 Months" },
{ label: "6 Months", key: "6 Months" },
{ label: "YTD", key: "Year to Date" },
{ label: "1 Year", key: "1 Year" },
{ label: "5 Years", key: "5 Years" },
{ label: "10 Years", key: "10 Years" },
];
// 分页大小选项
const pageSizeOptions = [
{ label: "10", key: 10 },
{ label: "50", key: 50 },
{ label: "100", key: 100 },
{ label: "500", key: 500 },
{ label: "1000", key: 1000 },
];
const state = reactive({
selectedPeriod: "Daily",
selectedDuration: "6 Months",
tableData: [],
currentPage: 1,
pageSize: 10,
gotoPage: 1,
});
const showPageSizeMenu = ref(false);
// 计算总页数
const totalPages = computed(() => {
return Math.ceil(state.tableData.length / state.pageSize);
});
// 计算当前页的数据
const paginatedData = computed(() => {
const start = (state.currentPage - 1) * state.pageSize;
const end = start + state.pageSize;
return state.tableData.slice(start, end);
});
// 表格列定义
const columns = [
{
title: "Date",
key: "date",
align: "left",
fixed: "left",
width: 150,
},
{
title: "Open",
key: "open",
align: "center",
},
{
title: "High",
key: "high",
align: "center",
},
{
title: "Low",
key: "low",
align: "center",
},
{
title: "Close",
key: "close",
align: "center",
},
{
title: "Adj. Close",
key: "adjClose",
align: "center",
width: 115,
},
{
title: "Change",
key: "change",
align: "center",
},
{
title: "Volume",
key: "volume",
align: "center",
},
];
// 处理下拉选项变更
const handlePeriodChange = (key) => {
state.selectedPeriod = key;
if (key === "Annual") {
handleDurationChange("Full History");
return;
}
if (key === "Monthly") {
handleDurationChange("10 Years");
return;
}
if (key === "Quarterly") {
handleDurationChange("10 Years");
return;
}
getPageData();
};
const handleDurationChange = (key) => {
state.selectedDuration = key;
state.currentPage = 1;
getPageData();
};
const displayRange = computed(() => {
const start = (state.currentPage - 1) * state.pageSize + 1;
const end = Math.min(
state.currentPage * state.pageSize,
state.tableData.length
);
return { start, end };
});
const goToPage = (page) => {
if (page >= 1 && page <= totalPages.value) {
state.currentPage = page;
}
};
const goToPrevPage = () => {
if (state.currentPage > 1) {
state.currentPage--;
}
};
const goToNextPage = () => {
if (state.currentPage < totalPages.value) {
state.currentPage++;
}
};
// 处理分页
const handlePageSizeChange = (size) => {
state.pageSize = size;
state.currentPage = 1; // 重置到第一页
};
const handleGoto = () => {
const page = parseInt(state.gotoPage);
if (page >= 1 && page <= totalPages.value) {
state.currentPage = page;
}
};
const togglePageSizeMenu = () => {
showPageSizeMenu.value = !showPageSizeMenu.value;
};
const getVisiblePages = () => {
const current = state.currentPage;
const total = totalPages.value;
const pages = [];
if (total <= 7) {
// 如果总页数小于等于7显示所有页码
for (let i = 1; i <= total; i++) {
pages.push(i);
}
} else {
// 显示第一页
pages.push(1);
if (current <= 4) {
// 当前页在前4页
for (let i = 2; i <= 5; i++) {
pages.push(i);
}
pages.push("...");
pages.push(total);
} else if (current >= total - 3) {
// 当前页在后4页
pages.push("...");
for (let i = total - 4; i <= total; i++) {
pages.push(i);
}
} else {
// 当前页在中间
pages.push("...");
for (let i = current - 1; i <= current + 1; i++) {
pages.push(i);
}
pages.push("...");
pages.push(total);
}
}
return pages;
};
// 回到顶部
const scrollToTop = () => {
// 尝试多种方法
// 1. 使用document.body
document.body.scrollTop = 0;
// 2. 使用document.documentElement (HTML元素)
document.documentElement.scrollTop = 0;
// 3. 使用scrollIntoView
document.querySelector(".historic-data-container").scrollIntoView({
behavior: "smooth",
block: "start",
});
};
onMounted(() => {
getPageData();
document.addEventListener("click", handleClickOutside);
});
onUnmounted(() => {
document.removeEventListener("click", handleClickOutside);
});
const handleClickOutside = (event) => {
if (!event.target.closest(".page-size-selector")) {
showPageSizeMenu.value = false;
}
};
watch(
() => state.pageSize,
() => {
state.currentPage = 1;
}
);
watch(
() => state.currentPage,
(newPage) => {
state.gotoPage = newPage;
}
);
const getPageDefaultData = async () => {
try {
let url =
"https://stockanalysis.com/api/symbol/a/OTC-MINM/history?period=Daily&range=3M";
const res = await axios.get(url);
let originalData = res.data.data;
// 转换为日期格式:"Nov 26, 2024"
let calcApiData = originalData.map((item) => [
new Date(item[0]).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
}),
item[1],
]);
// console.log('接口数据', calcApiData)
// 使用API数据更新defaultTableData中的close和adjClose值
const updatedTableData = defaultTableData.map((tableItem) => {
// 查找对应日期的API数据
const matchedApiData = calcApiData.find(
(apiItem) => apiItem[0] === tableItem.date
);
if (matchedApiData) {
// 更新close和adjClose值
return {
...tableItem,
close: matchedApiData[1].toFixed(2),
adjClose: matchedApiData[1].toFixed(2),
};
}
return tableItem;
});
state.tableData = updatedTableData;
} catch (error) {
// console.error('获取数据失败', error)
}
};
const getPageData = async () => {
let range = "";
let now = new Date();
const last = new Date(now);
last.setMonth(now.getMonth() - 6);
let fromDate = last;
let toDate =
now.getFullYear() +
"-" +
String(now.getMonth() + 1).padStart(2, "0") +
"-" +
String(now.getDate()).padStart(2, "0");
if (state.selectedDuration === "3 Months") {
range = "3M";
const last = new Date(now);
last.setMonth(now.getMonth() - 3);
fromDate = last;
} else if (state.selectedDuration === "6 Months") {
range = "6M";
const last = new Date(now);
last.setMonth(now.getMonth() - 6);
fromDate = last;
} else if (state.selectedDuration === "Year to Date") {
range = "YTD";
fromDate = new Date(now.getFullYear(), 0, 1);
} else if (state.selectedDuration === "1 Year") {
range = "1Y";
const last = new Date(now);
last.setFullYear(now.getFullYear() - 1);
fromDate = last;
} else if (state.selectedDuration === "5 Years") {
range = "5Y";
const last = new Date(now);
last.setFullYear(now.getFullYear() - 5);
fromDate = last;
} else if (state.selectedDuration === "10 Years") {
range = "10Y";
const last = new Date(now);
last.setFullYear(now.getFullYear() - 10);
fromDate = last;
} else if (state.selectedDuration === "Full History") {
range = "Max";
fromDate = new Date("2009-10-07");
}
let finalFromDate =
fromDate.getFullYear() +
"-" +
String(fromDate.getMonth() + 1).padStart(2, "0") +
"-" +
String(fromDate.getDate()).padStart(2, "0");
let url =
"https://common.szjixun.cn/api/stock/history/list?from=" +
finalFromDate +
"&to=" +
toDate;
const res = await axios.get(url);
if (res.status === 200) {
if (res.data.status === 0) {
// 转换为日期格式:"Nov 26, 2024"
let resultData = res.data.data.map((item) => {
return {
date: new Date(item.date).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
}),
open: item.open != null ? Number(item.open).toFixed(2) : "",
high: item.high != null ? Number(item.high).toFixed(2) : "",
low: item.low != null ? Number(item.low).toFixed(2) : "",
close: item.close != null ? Number(item.close).toFixed(2) : "",
adjClose: item.close != null ? Number(item.close).toFixed(2) : "",
change:
item.changePercent != null
? Number(item.changePercent).toFixed(2) + "%"
: "",
volume: item.volume,
};
});
state.tableData = resultData;
}
}
};
</script>
<style scoped lang="scss">
.historic-data-container {
// width: 932px;
margin: 0 auto;
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40px;
.title {
font-size: 40px;
font-weight: bold;
margin: 0;
}
}
.filter-container {
display: flex;
align-items: center;
gap: 16px;
padding: 0 16px;
margin-bottom: 32px;
margin-top: 32px;
.range-label {
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 1.375em;
letter-spacing: 3%;
color: #455363;
}
.filter-option {
padding: 7px 12px;
border-radius: 3px;
background-color: #efefef;
cursor: pointer;
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 1.375em;
color: #000000;
transition: all 0.2s ease;
&:hover {
background-color: #e0e0e0;
}
&.active {
background-color: #ff7bac;
color: #ffffff;
}
}
}
.back-to-top-link {
display: flex;
justify-content: center;
margin-top: 16px;
a {
display: flex;
align-items: center;
gap: 5px;
color: #2563eb;
font-size: 20px;
font-weight: bold;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
.reports-table {
width: 100%;
background: #ffffff;
border-radius: 16px;
box-shadow: 0px 3px 14px 0px rgba(0, 0, 0, 0.16);
padding: 16px;
}
.table-header {
display: flex;
background: #fff0f5;
border-radius: 8px;
padding: 16px 0;
margin-bottom: 4px;
justify-content: space-between;
align-items: center;
.column {
font-family: "PingFang SC", sans-serif;
font-weight: 500;
font-size: 16px;
line-height: 1.375em;
letter-spacing: 3%;
color: #000000;
padding: 0 16px;
}
}
.table-row {
display: flex;
padding: 16px 0;
align-items: center;
justify-content: space-between;
position: relative;
border-radius: 8px;
&:hover {
background: #fff8fb;
}
// &:last-child {
// border-bottom: none;
// }
&:nth-child(even) {
margin: 4px 0;
}
}
.reports-list {
display: flex;
flex-direction: column;
gap: 4px;
}
.column {
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 1.375em;
letter-spacing: 3%;
color: #455363;
padding: 0 16px;
position: relative;
}
.table-row .column:not(:last-child)::after {
content: "";
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
height: 24px;
border-right: 1px dashed #e0e0e6;
}
// 分页器样式
.pagination-container {
display: flex;
align-items: center;
margin-top: 20px;
justify-content: flex-end;
margin-bottom: 30px;
gap: 21px;
}
.pagination-info {
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 1.4375em;
color: #455363;
text-align: right;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-buttons {
display: flex;
align-items: center;
gap: 8px;
}
.page-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: 1px solid #e0e0e6;
border-radius: 3px;
background: #ffffff;
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 14px;
line-height: 1.428em;
color: #455363;
cursor: pointer;
transition: all 0.2s ease;
&:hover:not(:disabled) {
border-color: #ff7bac;
color: #ff7bac;
}
&.active {
border-color: #ff7bac;
color: #ff7bac;
background: #fff0f5;
}
&:disabled {
cursor: not-allowed;
opacity: 0.5;
}
&.disabled {
cursor: not-allowed;
opacity: 0.5;
}
}
.page-size-selector {
position: relative;
display: flex;
align-items: center;
gap: 18px;
padding: 4px 12px;
height: 28px;
border: 1px solid #e0e0e6;
border-radius: 3px;
background: #ffffff;
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 14px;
line-height: 1.428em;
color: #455363;
cursor: pointer;
&:hover {
border-color: #ff7bac;
}
}
.page-size-menu {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
background: #ffffff;
border: 1px solid #e0e0e6;
border-radius: 3px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
margin-bottom: 2px;
}
.page-size-option {
padding: 8px 12px;
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 14px;
line-height: 1.428em;
color: #455363;
cursor: pointer;
transition: background-color 0.2s ease;
&:hover {
background: #fff0f5;
}
&.active {
background: #fff0f5;
color: #ff7bac;
}
}
.goto-section {
display: flex;
align-items: center;
gap: 8px;
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 14px;
line-height: 1.428em;
color: #455363;
margin-right: 16px;
}
.goto-input {
width: 60px;
height: 28px;
padding: 4px 12px;
border: 1px solid #e0e0e6;
border-radius: 3px;
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 14px;
line-height: 1.428em;
color: #455363;
text-align: center;
&:focus {
outline: none;
border-color: #ff7bac;
}
}
.title-section {
display: flex;
flex-direction: column;
gap: 16px;
padding: 0 16px;
}
.title-decoration {
width: 58px;
height: 7px;
background: #ff7bac;
margin: auto 0;
margin-top: 43px;
}
.historic-title {
font-family: "PingFang SC", sans-serif;
font-weight: 500;
font-size: 40px;
line-height: 1.4em;
letter-spacing: 3%;
color: #000000;
}
</style>