Compare commits

...

6 Commits

Author SHA1 Message Date
yuanshan
313ccf7988 Merge branch 'zhangyuanshan-20250925' into newmain-20250926 2025-09-30 09:58:06 +08:00
yuanshan
be8d2ca4d4 背景图替换 2025-09-29 16:48:29 +08:00
yuanshan
7b4d234c48 add product-introduction 1920 2025-09-29 15:17:21 +08:00
yuanshan
318f850885 del no use 2025-09-29 09:32:24 +08:00
yuanshan
fd8b03ad3e fix email-alerts 2025-09-28 18:28:22 +08:00
yuanshan
7a7751567d 1920文件样式调整 2025-09-28 17:11:45 +08:00
45 changed files with 4684 additions and 2014 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 874 KiB

After

Width:  |  Height:  |  Size: 712 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,4 @@
<svg width="23" height="22" viewBox="0 0 23 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.6548 8.9774C13.516 8.84467 13.3681 8.72503 13.2156 8.61409L13.2153 8.61433C13.0584 8.46998 12.8453 8.38107 12.6104 8.38107C12.1283 8.38107 11.7375 8.75491 11.7375 9.21604C11.7375 9.42009 11.8142 9.60698 11.9413 9.75202C11.9413 9.75204 11.9413 9.75208 11.9414 9.75211C12.0024 9.82178 12.0751 9.88184 12.1567 9.92939C12.239 9.99432 12.3198 10.0582 12.3953 10.1305L12.4673 10.1994C13.372 11.0637 13.1019 12.5528 12.1972 13.4182L8.33659 17.1098C7.4319 17.9741 5.96108 17.9741 5.05634 17.1098L4.9838 17.0404C4.07904 16.175 4.07904 14.7671 4.9838 13.9039L6.68938 12.273C6.90822 12.1068 7.0487 11.8504 7.0487 11.5624C7.0487 11.0616 6.62422 10.6556 6.10059 10.6556C5.903 10.6556 5.71958 10.7134 5.5677 10.8123C5.56722 10.8114 5.56671 10.8104 5.56621 10.8095L5.54802 10.8257C5.48089 10.8719 5.42019 10.926 5.36776 10.9871L3.59529 12.5735C1.92917 14.1683 1.92917 16.7766 3.59529 18.3691L3.66726 18.4379C5.33337 20.0305 8.05903 20.0305 9.72514 18.4379L13.5846 14.7452C15.2484 13.1516 15.3895 10.6377 13.7257 9.04513L13.6548 8.9774Z" fill="#FF7BAC"/>
<path d="M19.2173 3.56219L19.1454 3.49335C17.4793 1.89968 14.7536 1.89968 13.0875 3.49335L9.22805 7.18608C7.56193 8.77976 7.47018 11.0811 9.1363 12.6758L9.20711 12.7425C9.28278 12.8149 9.36132 12.8831 9.44149 12.9485C9.49952 13.0105 9.56769 13.0636 9.64347 13.1056C9.64405 13.106 9.64465 13.1065 9.64522 13.1069L9.64547 13.1067C9.76626 13.1731 9.90621 13.2113 10.0556 13.2113C10.5107 13.2113 10.8796 12.8584 10.8796 12.4231C10.8796 12.3002 10.8502 12.1839 10.7978 12.0802C10.6888 11.8462 10.4849 11.7038 10.3438 11.5689L10.273 11.5022C9.3683 10.6368 9.71185 9.37962 10.6165 8.51426L14.4783 4.82259C15.3807 3.9572 16.8521 3.9572 17.7568 4.82259L17.8288 4.89033C18.7335 5.75575 18.7335 7.1642 17.8288 8.0285L16.1288 9.65569C15.8975 9.81959 15.7476 10.0825 15.7476 10.3789C15.7476 10.8757 16.1686 11.2785 16.688 11.2785C16.8688 11.2785 17.0376 11.2296 17.1809 11.145C17.182 11.1468 17.183 11.1484 17.1841 11.1502L17.2104 11.1269C17.2917 11.0749 17.3638 11.011 17.4245 10.938L19.2161 9.35669C20.8834 7.76303 20.8835 5.15584 19.2173 3.56219Z" fill="#FF7BAC"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -2,12 +2,16 @@
<div class="custom-echarts">
<div>
<div class="echarts-header">
<div class="echarts-header-title">
<span>FiEE, Inc. Stock Price History</span>
<!-- 标题区域 -->
<div class="title-section">
<div class="title-decoration"></div>
<div class="stock-title">
<span>FiEE, Inc. Stock Price History</span>
</div>
</div>
<div class="echarts-search-area">
<div class="echarts-search-byRange">
<text style="font-size: 0.9rem; font-weight: 400; color: #666666;">
<text style="font-size: 0.9rem; font-weight: 400; color: #666666">
Range
</text>
<div class="search-range-list">
@ -15,6 +19,7 @@
class="search-range-list-each"
v-for="(item, index) in state.searchRange"
:key="index"
:class="{ activeRange: state.activeRange === item }"
@click="changeSearchRange(item)"
>
<span>{{ item }}</span>
@ -23,21 +28,10 @@
</div>
<div class="echarts-search-byDate">
<n-date-picker
v-model:value="state.selectHistoricStartDate"
type="date"
:is-date-disabled="disableAfterDate"
@update:value="changeSearchRangeStartDate"
input-readonly
/>
<!-- <n-icon size="16">
<ArrowForwardOutline />
</n-icon> -->
<span>to</span>
<n-date-picker
v-model:value="state.selectHistoricEndDate"
type="date"
:is-date-disabled="disablePreviousDate"
@update:value="changeSearchRangeEndDate"
v-model:value="state.dateRange"
type="daterange"
:is-date-disabled="isDateDisabled"
@update:value="handleDateRangeChange"
input-readonly
/>
</div>
@ -48,40 +42,38 @@
</div>
</template>
<script setup>
import { onMounted, watch, reactive } from 'vue'
import * as echarts from 'echarts'
import markPointerIcon from '@/assets/image/icon/echarts_markPointer.png'
import axios from 'axios'
import { NDatePicker, NIcon } from 'naive-ui'
import { ArrowForwardOutline } from '@vicons/ionicons5'
import { onMounted, watch, reactive } from "vue";
import * as echarts from "echarts";
import { NDatePicker, NIcon } from "naive-ui";
import { ArrowForwardOutline } from "@vicons/ionicons5";
import axios from "axios";
const state = reactive({
searchRange: ['1m', '3m', 'YTD', '1Y', '5Y', '10Y', 'Max'],
selectHistoricStartDate: '2009-10-07',
selectHistoricEndDate: new Date(),
})
searchRange: ["1m", "3m", "YTD", "1Y", "5Y", "10Y", "Max"],
dateRange: [new Date("2009-10-07").getTime(), new Date().getTime()],
activeRange: "",
});
let myCharts = null
let historicData = []
let xAxisData = []
let myCharts = null;
let historicData = [];
let xAxisData = [];
//eCharts
const initEcharts = (data) => {
historicData = data
historicData = data;
xAxisData = data.map((item) => {
return new Date(item.date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
})
const yAxisData = data.map((item) => item.price)
return new Date(item.date).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
});
const yAxisData = data.map((item) => item.price);
// console.error(xAxisData, yAxisData)
// domecharts
myCharts = echarts.init(document.getElementById('myEcharts'), null, {
renderer: 'canvas',
useDirtyRect: true
})
myCharts = echarts.init(document.getElementById("myEcharts"), null, {
renderer: "canvas",
useDirtyRect: true,
});
//
myCharts.setOption({
animation: false,
@ -91,68 +83,68 @@ const initEcharts = (data) => {
// text: 'FiEE, Inc. Stock Price History',
// },
grid: {
left: '8%', // '2%'
right: '12%', // yylabel
left: "8%", // '2%'
right: "12%", // yylabel
},
tooltip: {
trigger: 'axis',
trigger: "axis",
axisPointer: {
type: 'line',
type: "line",
label: {
backgroundColor: '#6a7985',
backgroundColor: "#6a7985",
},
},
formatter: function (params) {
const p = params[0]
return `<span style="font-size: 1.1rem; font-weight: 600;">${p.axisValue}</span><br/><span style="font-size: 0.9rem; font-weight: 400;">Price: ${p.data}</span>`
const p = params[0];
return `<span style="font-size: 1.1rem; font-weight: 600;">${p.axisValue}</span><br/><span style="font-size: 0.9rem; font-weight: 400;">Price: ${p.data}</span>`;
},
triggerOn: 'mousemove',
triggerOn: "mousemove",
confine: true,
hideDelay: 1500
hideDelay: 1500,
},
xAxis: {
data: xAxisData,
type: 'category',
type: "category",
boundaryGap: false,
inverse: true,
axisLine: {
lineStyle: {
color: '#CCD6EB',
color: "#CCD6EB",
},
},
axisLabel: {
color: '#323232',
fontWeight: 'bold',
interval: 'auto',
hideOverlap: true
color: "#323232",
fontWeight: "bold",
interval: "auto",
hideOverlap: true,
},
},
yAxis: {
type: 'value',
position: 'right',
type: "value",
position: "right",
interval: 25,
// max: 75.0,
show: true,
axisLabel: {
color: '#323232',
fontWeight: 'bold',
color: "#323232",
fontWeight: "bold",
formatter: function (value) {
return value > 0 ? value.toFixed(2) : value
return value > 0 ? value.toFixed(2) : value;
},
},
},
series: [
{
data: yAxisData,
type: 'line',
sampling: 'lttb',
symbol: 'none',
type: "line",
sampling: "lttb",
symbol: "none",
lineStyle: {
color: '#2c6288',
color: "#2c6288",
},
areaStyle: {
color: {
type: 'linear',
type: "linear",
x: 0,
y: 0,
x2: 0,
@ -160,387 +152,391 @@ const initEcharts = (data) => {
colorStops: [
{
offset: 0,
color: '#2c6288',
color: "#2c6288",
},
{
offset: 1,
color: '#F4F6F8',
color: "#F4F6F8",
},
],
},
},
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
symbol: "circle",
symbolSize: 20,
itemStyle: {
color: {
type: "radial",
x: 0.5,
y: 0.5,
r: 0.5,
colorStops: [
{ offset: 0, color: "#CC346C" },
{ offset: 0.4, color: "white" },
{ offset: 0.4, color: "white" },
{ offset: 0.6, color: "rgba(204, 52, 108, 0.30)" },
{ offset: 0.8, color: "rgba(204, 52, 108, 0.30)" },
{ offset: 1, color: "rgba(255, 123, 172, 0)" },
],
},
},
data: [],
},
progressive: 500,
progressiveThreshold: 3000,
large: true,
largeThreshold: 2000
largeThreshold: 2000,
},
],
dataZoom: [
{
type: 'inside',
type: "inside",
},
{
type: 'slider',
type: "slider",
show: true,
dataBackground: {
lineStyle: {
color: '#2C6288',
color: "#2C6288",
},
areaStyle: {
color: {
type: 'linear',
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 1, color: '#2c6288' },
{ offset: 0, color: '#F4F6F8' },
{ offset: 1, color: "#2c6288" },
{ offset: 0, color: "#F4F6F8" },
],
},
},
},
selectedDataBackground: {
lineStyle: {
color: '#2C6288',
color: "#2C6288",
},
areaStyle: {
color: {
type: 'linear',
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 1, color: '#2c6288' },
{ offset: 0, color: '#F4F6F8' },
{ offset: 1, color: "#2c6288" },
{ offset: 0, color: "#F4F6F8" },
],
},
},
},
fillerColor: 'rgba(44, 98, 136, 0.3)',
fillerColor: "rgba(44, 98, 136, 0.3)",
realtime: false,
},
],
})
});
// showTip markPoint
myCharts.on('showTip', function (params) {
myCharts.on("showTip", function (params) {
if (params) {
const dataIndex = params.dataIndex
const x = myCharts.getOption().xAxis[0].data[dataIndex]
const y = myCharts.getOption().series[0].data[dataIndex]
const dataIndex = params.dataIndex;
const x = myCharts.getOption().xAxis[0].data[dataIndex];
const y = myCharts.getOption().series[0].data[dataIndex];
myCharts.setOption({
series: [
{
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
data: [{ coord: [x, y] }],
},
},
],
})
});
}
})
});
// markPoint
myCharts.on('globalout', function () {
myCharts.on("globalout", function () {
myCharts.setOption({
series: [
{
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
data: [],
},
},
],
})
})
myCharts.on('dataZoom', function (params) {
});
});
myCharts.on("dataZoom", function (params) {
// dataZoom
const option = myCharts.getOption()
const xAxisData = option.xAxis[0].data
const dataZoom = option.dataZoom[1] || option.dataZoom[0]
const option = myCharts.getOption();
const xAxisData = option.xAxis[0].data;
const dataZoom = option.dataZoom[1] || option.dataZoom[0];
// dataZoom startValue endValue
let startValue = dataZoom.endValue
let endValue = dataZoom.startValue
let startValue = dataZoom.endValue;
let endValue = dataZoom.startValue;
//
if (typeof startValue === 'number') {
startValue = xAxisData[startValue]
if (typeof startValue === "number") {
startValue = xAxisData[startValue];
}
if (typeof endValue === 'number') {
endValue = xAxisData[endValue]
if (typeof endValue === "number") {
endValue = xAxisData[endValue];
}
//
state.selectHistoricStartDate = new Date(startValue)
state.selectHistoricEndDate = new Date(endValue)
})
}
state.dateRange = [
new Date(startValue).getTime(),
new Date(endValue).getTime(),
];
});
};
onMounted(() => {
getHistoricalData()
})
getHistoricalData();
});
//
const getHistoricalData = async () => {
let now = new Date()
let now = new Date();
let toDate =
now.getFullYear() +
'-' +
String(now.getMonth() + 1).padStart(2, '0') +
'-' +
String(now.getDate()).padStart(2, '0')
"-" +
String(now.getMonth() + 1).padStart(2, "0") +
"-" +
String(now.getDate()).padStart(2, "0");
let url =
'https://common.szjixun.cn/api/stock/history/base/list?from=2009-10-07&to=' +
toDate
const res = await axios.get(url)
"https://common.szjixun.cn/api/stock/history/base/list?from=2009-10-07&to=" +
toDate;
const res = await axios.get(url);
if (res.status === 200) {
if (res.data.status === 0) {
initEcharts(res.data.data)
initEcharts(res.data.data);
}
}
}
};
//
function findClosestDateIndex(data, targetDateStr) {
let left = 0,
right = data.length - 1
const target = new Date(targetDateStr).getTime()
let res = data.length - 1 //
right = data.length - 1;
const target = new Date(targetDateStr).getTime();
let res = data.length - 1; //
while (left <= right) {
const mid = Math.floor((left + right) / 2)
const midTime = new Date(data[mid].date).getTime()
const mid = Math.floor((left + right) / 2);
const midTime = new Date(data[mid].date).getTime();
if (midTime > target) {
left = mid + 1
left = mid + 1;
} else {
res = mid
right = mid - 1
res = mid;
right = mid - 1;
}
}
return res
return res;
}
//
function findClosestDateIndexDescLeft(data, targetDateStr) {
let left = 0,
right = data.length - 1
const target = new Date(targetDateStr).getTime()
let res = -1
right = data.length - 1;
const target = new Date(targetDateStr).getTime();
let res = -1;
while (left <= right) {
const mid = Math.floor((left + right) / 2)
const midTime = new Date(data[mid].date).getTime()
const mid = Math.floor((left + right) / 2);
const midTime = new Date(data[mid].date).getTime();
if (midTime > target) {
left = mid + 1 // mid
left = mid + 1; // mid
} else {
res = mid // mid <= target
right = mid - 1
res = mid; // mid <= target
right = mid - 1;
}
}
return res
return res;
}
//
const changeSearchRange = (range, dateTime) => {
const now = new Date()
let startDate = ''
let endDate = ''
if (range === '1m') {
const last = new Date(now)
last.setMonth(now.getMonth() - 1)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === '3m') {
const last = new Date(now)
last.setMonth(now.getMonth() - 3)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === 'YTD') {
startDate = new Date(now.getFullYear(), 0, 1).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === '1Y') {
const last = new Date(now)
last.setFullYear(now.getFullYear() - 1)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === '5Y') {
const last = new Date(now)
last.setFullYear(now.getFullYear() - 5)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === '10Y') {
const last = new Date(now)
last.setFullYear(now.getFullYear() - 10)
startDate = last.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
endDate = new Date(new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
} else if (range === 'Max') {
startDate = ''
endDate = ''
} else if (range === 'startDateTime') {
startDate = dateTime
endDate = ''
} else if (range === 'endDateTime') {
startDate = ''
endDate = dateTime
state.activeRange = range;
const now = new Date();
let startDate = "";
let endDate = "";
if (range === "1m") {
const last = new Date(now);
last.setMonth(now.getMonth() - 1);
startDate = last.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
endDate = new Date(new Date()).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
} else if (range === "3m") {
const last = new Date(now);
last.setMonth(now.getMonth() - 3);
startDate = last.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
endDate = new Date(new Date()).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
} else if (range === "YTD") {
startDate = new Date(now.getFullYear(), 0, 1).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
endDate = new Date(new Date()).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
} else if (range === "1Y") {
const last = new Date(now);
last.setFullYear(now.getFullYear() - 1);
startDate = last.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
endDate = new Date(new Date()).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
} else if (range === "5Y") {
const last = new Date(now);
last.setFullYear(now.getFullYear() - 5);
startDate = last.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
endDate = new Date(new Date()).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
} else if (range === "10Y") {
const last = new Date(now);
last.setFullYear(now.getFullYear() - 10);
startDate = last.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
endDate = new Date(new Date()).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
} else if (range === "Max") {
startDate = "";
endDate = "";
} else if (range === "startDateTime") {
startDate = dateTime;
endDate = "";
} else if (range === "endDateTime") {
startDate = "";
endDate = dateTime;
}
if (startDate || endDate) {
// historicData xAxisData initEcharts
if (
typeof historicData !== 'undefined' &&
typeof xAxisData !== 'undefined'
typeof historicData !== "undefined" &&
typeof xAxisData !== "undefined"
) {
let startValue = xAxisData[0]
if (startDate) {
const idx = findClosestDateIndex(historicData, startDate)
// historicData[idx].date xAxisData
startValue = new Date(historicData[idx].date).toLocaleDateString(
'en-US',
{
month: 'short',
day: 'numeric',
year: 'numeric',
},
)
}
let endValue = endDate
if (endDate) {
// console.warn(endDate)
const idx = findClosestDateIndexDescLeft(historicData, endDate)
// console.warn(idx)
// historicData[idx].date xAxisData
endValue = new Date(historicData[idx].date).toLocaleDateString(
'en-US',
{
month: 'short',
day: 'numeric',
year: 'numeric',
},
)
// console.warn(endValue)
}
const zoomOptions = {};
let newStartTs = state.dateRange[0];
let newEndTs = state.dateRange[1];
if (startDate) {
myCharts.setOption({
dataZoom: {
endValue: startValue,
},
})
state.selectHistoricStartDate = new Date(startValue)
const idx = findClosestDateIndex(historicData, startDate);
const startValue = new Date(historicData[idx].date).toLocaleDateString(
"en-US",
{
month: "short",
day: "numeric",
year: "numeric",
}
);
zoomOptions.endValue = startValue;
newStartTs = new Date(startValue).getTime();
}
if (endDate) {
myCharts.setOption({
dataZoom: {
startValue: endValue,
},
})
state.selectHistoricEndDate = new Date(endValue)
const idx = findClosestDateIndexDescLeft(historicData, endDate);
const endValue = new Date(historicData[idx].date).toLocaleDateString(
"en-US",
{
month: "short",
day: "numeric",
year: "numeric",
}
);
zoomOptions.startValue = endValue;
newEndTs = new Date(endValue).getTime();
}
if (Object.keys(zoomOptions).length > 0) {
myCharts.setOption({ dataZoom: [zoomOptions, zoomOptions] });
}
state.dateRange = [newStartTs, newEndTs];
}
} else {
myCharts.setOption({
dataZoom: {
startValue: '',
endValue: '',
startValue: "",
endValue: "",
},
})
});
state.selectHistoricStartDate = new Date('2009-10-07')
state.selectHistoricEndDate = new Date()
state.dateRange = [new Date("2009-10-07").getTime(), new Date().getTime()];
}
}
};
// 2009-10-07
const disableAfterDate = (date) => {
return date < new Date('2009-10-06') || date > new Date()
}
//
const isDateDisabled = (ts, type, range) => {
const minDate = new Date("2009-10-06").getTime();
const maxDate = new Date().getTime();
//
const disablePreviousDate = (date) => {
return date < new Date(state.selectHistoricStartDate) || date > new Date()
}
if (ts < minDate || ts > maxDate) {
return true;
}
//
const changeSearchRangeStartDate = (date) => {
// console.error(date)
changeSearchRange(
'startDateTime',
new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
}),
)
}
if (type === "end" && range && range[0]) {
return ts < range[0];
}
//
const changeSearchRangeEndDate = (date) => {
// console.error(date)
changeSearchRange(
'endDateTime',
new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
}),
)
}
return false;
};
//
const handleDateRangeChange = (range) => {
if (range && range[0] && range[1]) {
const startDate = new Date(range[0]).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
const endDate = new Date(range[1]).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
changeSearchRange("startDateTime", startDate);
changeSearchRange("endDateTime", endDate);
}
};
</script>
<style lang="scss" scoped>
.custom-echarts {
@ -550,13 +546,30 @@ const changeSearchRangeEndDate = (date) => {
}
.echarts-header {
.echarts-header-title {
span {
font-size: 2rem;
font-weight: 600;
color: #323232;
}
.title-section {
display: flex;
flex-direction: column;
gap: 16px;
margin-bottom: 32px;
padding: 0 16px;
}
.title-decoration {
width: 58px;
height: 7px;
background: #ff7bac;
margin: auto 0;
margin-top: 43px;
}
.stock-title {
font-family: "PingFang SC", sans-serif;
font-weight: 500;
font-size: 40px;
line-height: 1.4em;
letter-spacing: 3%;
color: #000000;
}
.echarts-search-area {
padding: 2rem 0 0;
display: flex;
@ -586,6 +599,10 @@ const changeSearchRangeEndDate = (date) => {
font-size: 0.9rem;
}
}
.activeRange {
color: #fff;
background: #ff7bac;
}
}
}

View File

@ -2,12 +2,12 @@
<!-- 通用页脚 -->
<div class="custom-footer">
<div class="custom-footer-box">
<span>&copy; 2025 FiEE, Inc. All Rights Reserved.</span>
<div class="footer-links">
<span @click="handleLink('privacyPolicy')">Privacy Policy</span>
<span @click="handleLink('termsOfUse')">Terms of use</span>
<span @click="handleLink('siteMap')">Site Map</span>
</div>
<span>&copy; 2025 FiEE, Inc. All Rights Reserved.</span>
</div>
</div>
</template>
@ -28,19 +28,20 @@ const handleLink = (link) => {
// } else if (link === "siteMap") {
// window.open(siteMap, "_blank");
// }
router.push(link)
router.push(link);
};
</script>
<style scoped lang="scss">
.custom-footer {
width: 100%;
background: #f7f8fa;
border-top: 1px solid #ececec;
background: #f5f5f5;
border-top: 2px solid #dbdbdb;
z-index: 100;
height: 80px;
.custom-footer-box {
max-width: 1700px;
max-width: 932px;
margin: 0 auto;
display: flex;
flex-direction: row;
@ -50,12 +51,11 @@ const handleLink = (link) => {
color: #888;
// font-size: 15px;
font-size: 1.05rem;
padding: 1rem 40px;
text-align: center;
height: 100%;
}
.footer-links {
margin: 0.4rem 0 0;
span {
border-right: 1px solid #d2d2d7;
padding: 0 10px;

View File

@ -48,11 +48,11 @@ export const useHeaderMenuConfig = () => {
label: t("header_menu.financial_information.title"),
key: "financial_information",
children: [
{
label: t("header_menu.financial_information.sec_filings"),
key: "sec_filings",
href: "/secfilings",
},
{
label: t("header_menu.financial_information.sec_filings"),
key: "sec_filings",
href: "/secfilings",
},
{
label: t("header_menu.financial_information.annual_reports"),
key: "annual_reports",
@ -72,12 +72,12 @@ export const useHeaderMenuConfig = () => {
{
label: t("header_menu.stock_information.stock_quote"),
key: "stock_quote",
href:'/stock-quote'
href: '/stock-quote'
},
{
label: t("header_menu.stock_information.historic_stock_price"),
key: "historic_stock_price",
href:'/historic-stock'
href: '/historic-stock'
},
// {
// label: t("header_menu.stock_information.investment_calculator"),
@ -109,14 +109,19 @@ export const useHeaderMenuConfig = () => {
{
label: t("header_menu.investor_resources.ir_contacts"),
key: "ir_contacts",
href:'/contacts'
href: '/contacts'
},
{
label: t("header_menu.investor_resources.email_alerts"),
key: "email_alerts",
href:'/email-alerts'
href: '/email-alerts'
},
],
},
{
label: "Product Introduction",
key: "product-introduction",
href: '/product-introduction',
},
];
};

View File

@ -30,26 +30,31 @@ const routes = [
{
path: "/stock-quote",
name: "stock-quote",
meta: { bg:'url("@/assets/image/bg-stock-quote.png")' },
component: () => import("@/views/stock-quote/index.vue"),
},
{
path: "/historic-stock",
name: "historic-stock",
meta: { bg: 'url("@/assets/image/bg-events-calendar.png")' },
component: () => import("@/views/historic-stock/index.vue"),
},
{
path: "/contacts",
name: "contacts",
meta: { bg: 'url("@/assets/image/bg-contacts.png")' },
component: () => import("@/views/contacts/index.vue"),
},
{
path: "/email-alerts",
name: "email-alerts",
meta: { bg: 'url("@/assets/image/bg-contacts.png")' },
component: () => import("@/views/email-alerts/index.vue"),
},
{
path: "/quarterlyreports",
name: "quarterlyreports",
meta: { bg: 'url("@/assets/image/bg-events-calendar.png")' },
component: () =>
import("@/views/financialinformation/quarterlyreports/index.vue"),
},
@ -68,22 +73,26 @@ const routes = [
{
path: "/annualreports",
name: "AnnualReports",
meta: { bg: 'url("@/assets/image/bg-events-calendar.png")' },
component: () =>
import("@/views/financialinformation/annualreports/index.vue"),
},
{
path: "/press-releases",
name: "press-releases",
meta: { bg: 'url("@/assets/image/bg-events-calendar.png")' },
component: () => import("@/views/press-releases/index.vue"),
},
{
path: "/news",
name: "news",
meta: { bg: 'url("@/assets/image/bg-news.png")' },
component: () => import("@/views/news/index.vue"),
},
{
path: "/events-calendar",
name: "events-calendar",
meta: { bg: 'url("@/assets/image/bg-events-calendar.png")' },
component: () => import("@/views/events-calendar/index.vue"),
},
{
@ -141,6 +150,12 @@ const routes = [
name: "siteMap",
component: () => import("@/views/footerLinks/siteMap/index.vue"),
},
{
path: "/product-introduction",
name: "product-introduction",
meta: { bg: 'null' },
component: () => import("@/views/product-introduction/index.vue"),
},
],
},
{

View File

@ -8,42 +8,125 @@ function copyEmail() {
</script>
<template>
<header className="header"></header>
<main
ref="main"
class="flex-center min-h-80vh rounded-3xl to-accent w-100vw animate-fade-in"
>
<div class="w-full flex flex-col items-center gap-6 py-16 px-8">
<h1
class="text-5xl font-bold text-primary animate-fade-in-down animate-delay-0"
>
Investor Contacts
</h1>
<div
class="text-3xl font-semibold text-gray-800 animate-fade-in-down animate-delay-200"
>
FiEE Inc.
</div>
<div class="text-2xl text-#ff7bac animate-fade-in-down animate-delay-400">
Investor Relations
</div>
<div
class="text-xl text-gray-600 flex items-center gap-2 animate-fade-in-down animate-delay-600"
>
<div class="contact-container">
<!-- Title Section -->
<div class="title-section">
<div class="title-decoration"></div>
<div class="contact-title">Investor Contacts</div>
</div>
<!-- Card Section -->
<div class="contact-card">
<div class="logo-text">FiEE Inc.</div>
<div class="relation-text">Investor Relations</div>
<div class="email-section">
<span>Email:</span>
<span
class="transition-colors duration-300 cursor-pointer text-#00baff hover:text-primary active:text-secondary select-all"
@click="copyEmail"
<span class="email-address" @click="copyEmail"
>fiee@dlkadvisory.com</span
>
</div>
</div>
</main>
<footer></footer>
</div>
</template>
<style scoped lang="scss">
/**** UnoCSS 动画补充(如未全局引入可在 uno.config.js 添加)****/
.contact-container {
max-width: 932px;
margin: 0 auto;
}
.title-section {
display: flex;
flex-direction: column;
gap: 16px;
padding: 0 16px;
margin-bottom: 32px;
}
.title-decoration {
width: 58px;
height: 7px;
background: #ff7bac;
margin: auto 0;
margin-top: 43px;
}
.contact-title {
font-family: "PingFang SC", sans-serif;
font-weight: 500;
font-size: 40px;
line-height: 1.4em;
color: #000000;
}
.contact-card {
display: flex;
width: 932px;
height: 580px;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
background-color: white;
border-radius: 1rem;
background-image: url("@/assets/image/contacts-bg.png");
background-size: 64% auto;
background-position: center;
background-repeat: no-repeat;
box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, 0.1);
animation: fade-in 0.8s cubic-bezier(0.23, 1, 0.32, 1) both;
}
.logo-text {
width: 100%;
text-align: center;
font-feature-settings: "liga" off, "clig" off;
font-family: "PingFang SC";
font-size: 128px;
font-style: normal;
font-weight: 600;
line-height: normal;
letter-spacing: 0.48px;
background: linear-gradient(90deg, #ff7bac 0%, #0ff 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
animation: fade-in-down 0.8s cubic-bezier(0.23, 1, 0.32, 1) both;
animation-delay: 0.2s;
}
.relation-text {
font-size: 1.5rem;
color: black;
font-weight: bold;
animation: fade-in-down 0.8s cubic-bezier(0.23, 1, 0.32, 1) both;
animation-delay: 0.4s;
}
.email-section {
font-size: 1.25rem;
color: #4a5568;
display: flex;
align-items: center;
gap: 0.5rem;
animation: fade-in-down 0.8s cubic-bezier(0.23, 1, 0.32, 1) both;
animation-delay: 0.6s;
}
.email-address {
color: #ff7bac;
}
@keyframes fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fade-in-down {
0% {
opacity: 0;
@ -54,19 +137,4 @@ function copyEmail() {
transform: translateY(0);
}
}
.animate-fade-in-down {
animation: fade-in-down 0.8s cubic-bezier(0.23, 1, 0.32, 1) both;
}
.animate-delay-0 {
animation-delay: 0s;
}
.animate-delay-200 {
animation-delay: 0.2s;
}
.animate-delay-400 {
animation-delay: 0.4s;
}
.animate-delay-600 {
animation-delay: 0.6s;
}
</style>

View File

@ -8,7 +8,6 @@ const form = ref({
email: "",
company: "",
phone: "",
alertType: "all",
});
const submitted = ref(false);
@ -26,126 +25,264 @@ async function handleSubmit(e) {
</script>
<template>
<header class="header"></header>
<main ref="main" class="relative min-h-[80vh] flex-center overflow-hidden">
<!-- 粒子背景 -->
<div class="absolute inset-0 z-0 pointer-events-none animate-bg-move"></div>
<!-- 表单卡片/提交成功卡片 -->
<div
class="relative z-10 w-[480px] max-w-[90vw] p-10 bg-white/80 rounded-3xl shadow-2xl backdrop-blur-md animate-bounce-in"
>
<div class="alerts-container">
<!-- Title Section -->
<!-- 未提交 -->
<div v-if="!submitted" class="title-section">
<div class="title-decoration"></div>
<div class="title">E-Mail Alerts</div>
<div class="subtitle">* Required Fields</div>
</div>
<!-- 已提交 -->
<div v-else class="title-section">
<div class="title-decoration"></div>
<div class="title">Submitted successfully!</div>
<div class="subtitle">The information you submitted is as follows:</div>
</div>
<!-- Form Card -->
<div class="form-card relative">
<template v-if="!submitted">
<h2 class="text-3xl font-bold text-#ff7bac mb-2 tracking-wide">
E-Mail Alerts
</h2>
<p class="text-sm text-gray-500 mb-6">* Required Fields</p>
<form class="space-y-4" @submit="handleSubmit">
<div>
<label class="block text-gray-700 font-semibold mb-1"
>* First Name</label
>
<form class="form-content" @submit="handleSubmit">
<div class="form-group">
<label for="firstName">* First Name</label>
<input
id="firstName"
v-model="form.firstName"
type="text"
class="w-full px-4 py-2 rounded-lg ring-2 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
required
/>
</div>
<div>
<label class="block text-gray-700 font-semibold mb-1"
>* Last Name</label
>
<input
v-model="form.lastName"
type="text"
class="w-full px-4 py-2 rounded-lg ring-2 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
/>
<div class="form-group">
<label for="lastName">* Last Name</label>
<input id="lastName" v-model="form.lastName" type="text" required />
</div>
<div>
<label class="block text-gray-700 font-semibold mb-1"
>* Email</label
>
<input
v-model="form.email"
type="email"
class="w-full px-4 py-2 rounded-lg ring-2 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
/>
<div class="form-group">
<label for="email">* Email</label>
<input id="email" v-model="form.email" type="email" required />
</div>
<div>
<label class="block text-gray-700 font-semibold mb-1"
>* Company</label
>
<input
v-model="form.company"
type="text"
class="w-full px-4 py-2 rounded-lg ring-2 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
/>
<div class="form-group">
<label for="company">* Company</label>
<input id="company" v-model="form.company" type="text" required />
</div>
<div>
<label class="block text-gray-700 font-semibold mb-1">Phone</label>
<input
v-model="form.phone"
type="tel"
class="w-full px-4 py-2 rounded-lg ring-2 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
/>
<div class="form-group">
<label for="phone">Phone</label>
<input id="phone" v-model="form.phone" type="tel" />
</div>
<button
type="submit"
class="w-full py-3 rounded-xl text-white font-bold text-lg active:scale-95 transition-all duration-200 animate-bounce-in animate-delay-200 submit-btn"
>
Submit
</button>
<button type="submit" class="submit-btn">Submit</button>
</form>
</template>
<template v-else>
<div
class="flex flex-col items-center justify-center min-h-[300px] animate-bounce-in"
>
<span
class="i-mdi:check-circle-outline text-green-500 text-5xl mb-4"
></span>
<h2 class="text-2xl font-bold text-#ff7bac mb-2">
Submitted successfully!
</h2>
<div class="text-gray-700 text-base mb-4">
The information you submitted is as follows:
</div>
<div
class="w-full bg-white/80 rounded-xl shadow p-4 space-y-2 text-gray-800"
>
<div>
<span class="font-semibold">First Name</span
>{{ form.firstName }}
<div class="submitted-data">
<div class="submitted-data-content">
<div class="submitted-row">
<span class="label">First Name</span>
<span class="value">{{ form.firstName || "Not filled in" }}</span>
</div>
<div>
<span class="font-semibold">Last Name</span>{{ form.lastName }}
<div class="submitted-row">
<span class="label">Last Name</span>
<span class="value">{{ form.lastName || "Not filled in" }}</span>
</div>
<div>
<span class="font-semibold">Email</span>{{ form.email }}
<div class="submitted-row">
<span class="label">Email</span>
<span class="value">{{ form.email || "Not filled in" }}</span>
</div>
<div>
<span class="font-semibold">Company</span>{{ form.company }}
<div class="submitted-row">
<span class="label">Company</span>
<span class="value">{{ form.company || "Not filled in" }}</span>
</div>
<div>
<span class="font-semibold">Phone</span
>{{ form.phone || "Not filled in" }}
</div>
<div>
<span class="font-semibold">Alert Type</span
>{{
form.alertType === "all" ? "All Alerts" : "Customize Alerts"
}}
<div class="submitted-row">
<span class="label">Phone</span>
<span class="value">{{ form.phone || "Not filled in" }}</span>
</div>
</div>
</div>
<div class="submitted-bg"></div>
</template>
</div>
</main>
<footer></footer>
</div>
</template>
<style scoped lang="scss">
/* 可选:自定义粒子或渐变动画背景 */
.alerts-container {
max-width: 932px;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.title-section {
display: flex;
flex-direction: column;
gap: 16px;
padding: 0 16px;
margin-bottom: 32px;
align-self: center;
}
.title-decoration {
width: 58px;
height: 7px;
background: #ff7bac;
margin: auto 0;
margin-top: 43px;
align-self: center;
}
.title {
font-family: "PingFang SC", sans-serif;
font-weight: 500;
font-size: 40px;
line-height: 56px;
color: #000000;
letter-spacing: 1.2px;
}
.subtitle {
font-family: "PingFang SC", sans-serif;
font-size: 16px;
line-height: 22px;
color: #455363;
letter-spacing: 0.48px;
align-self: center;
}
.form-card {
width: 100%;
background-color: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0px 3px 14px 0px rgba(0, 0, 0, 0.16);
animation: fade-in 0.8s cubic-bezier(0.23, 1, 0.32, 1) both;
display: flex;
align-items: center;
justify-content: center;
}
.form-content {
width: 100%;
display: flex;
flex-direction: column;
gap: 16px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
label {
font-family: "PingFang SC", sans-serif;
font-weight: 500;
font-size: 16px;
line-height: 22px;
color: #000000;
letter-spacing: 0.48px;
}
input {
height: 38px;
border: 1px solid #e0e0e6;
border-radius: 8px;
padding: 0 12px;
font-size: 16px;
outline: none;
transition: border-color 0.3s;
&:focus {
border-color: #ff7bac;
}
}
}
.submit-btn {
background: linear-gradient(to right, #ff7bac, #00ffff);
height: 60px;
background: #ff7bac;
border-radius: 8px;
border: none;
font-family: "PingFang SC", sans-serif;
font-weight: 500;
font-size: 24px;
line-height: 32px;
color: white;
letter-spacing: 1.2px;
cursor: pointer;
transition: opacity 0.3s;
margin-top: 8px; // 16px (from figma form-group gap) + 8px = 24px
&:hover {
opacity: 0.9;
}
}
.success-title {
font-size: 24px;
font-weight: bold;
color: #ff7bac;
margin-bottom: 16px;
}
.success-info {
margin-bottom: 24px;
color: #455363;
}
.submitted-data {
padding: 36px 24px 24px;
border-radius: 16px;
width: 678px;
height: 428px;
flex-shrink: 0;
}
.submitted-bg {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url("@/assets/image/email-alerts-submit.png");
background-repeat: no-repeat;
background-position: bottom;
background-size: 100%;
}
.submitted-data-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 16px 0;
}
.submitted-row {
display: flex;
font-family: "PingFang SC", sans-serif;
font-size: 16px;
line-height: 22px;
letter-spacing: 0.48px;
width: 280px;
}
.label {
font-weight: 500;
color: #000000;
width: 110px;
flex-shrink: 0;
}
.value {
font-weight: 400;
color: #455363;
}
@keyframes fade-in {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@ -1,29 +1,95 @@
<template>
<div class="events-calendar-page">
<customDefaultPage>
<template #content>
<main class="p-[35px] max-w-[1200px] mx-auto">
<div class="title mb-[20px]">
<main class="page-container">
<div class="events-container">
<!-- 标题区域 -->
<div class="title-section">
<div class="title-decoration"></div>
<div class="events-title">
{{ t("events_calendar.title") }}
</div>
<div class="search-container">
</div>
<!-- 搜索区域 -->
<div class="search-container">
<div class="date-picker-wrapper">
<n-date-picker
v-model:value="state.selectedDateValue"
type="date"
class="search-date-picker"
></n-date-picker>
<n-button @click="handleSearch" class="search-button">
{{ t("events_calendar.search.button") }}
</n-button>
placeholder="Select Date"
>
<template #prefix>
<svg
class="calendar-icon"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
>
<g clip-path="url(#clip0_134_3094)">
<path
d="M17.5 3.33398H2.5C2.08333 3.33398 1.66667 3.66732 1.66667 4.08398V17.5007C1.66667 17.9173 2.08333 18.2507 2.5 18.2507H17.5C17.9167 18.2507 18.3333 17.9173 18.3333 17.5007V4.08398C18.3333 3.66732 17.9167 3.33398 17.5 3.33398Z"
stroke="#78777B"
stroke-width="1.25"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M13.3333 1.66602V5.00018"
stroke="#78777B"
stroke-width="1.25"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M6.66669 1.66602V5.00018"
stroke="#78777B"
stroke-width="1.25"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M1.66669 8.33398H18.3334"
stroke="#78777B"
stroke-width="1.25"
stroke-linecap="round"
stroke-linejoin="round"
></path>
</g>
<defs>
<clipPath id="clip0_134_3094">
<rect
width="20"
height="20"
fill="white"
transform="translate(0 0.000976562)"
></rect>
</clipPath>
</defs>
</svg>
</template>
</n-date-picker>
</div>
</main>
</template>
</customDefaultPage>
<n-button @click="handleSearch" class="search-button">
{{ t("events_calendar.search.button") }}
</n-button>
</div>
<!-- 背景图片区域 -->
<div class="background-image-container">
<img
src="@/assets/image/events-calendar-bg.png"
alt="Events Calendar Background"
class="background-image"
/>
</div>
</div>
</main>
</div>
</template>
<script setup>
import customDefaultPage from "@/components/customDefaultPage/index.vue";
import { reactive } from "vue";
import { NDatePicker, NButton } from "naive-ui";
import { useI18n } from "vue-i18n";
@ -41,42 +107,112 @@ const handleSearch = () => {
</script>
<style scoped lang="scss">
.title {
.page-container {
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
.events-container {
max-width: 932px;
margin: 0 auto;
}
.title-section {
display: flex;
flex-direction: column;
gap: 16px;
margin-bottom: 32px;
padding: 0 16px;
}
.title-decoration {
width: 58px;
height: 7px;
background: #ff7bac;
margin: auto 0;
margin-top: 43px;
}
.events-title {
font-family: "PingFang SC", sans-serif;
font-weight: 500;
font-size: 40px;
color: #333;
line-height: 1.4em;
letter-spacing: 3%;
color: #000000;
}
.search-container {
margin-bottom: 20px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 10px;
gap: 32px;
padding: 0 16px;
}
.date-picker-wrapper {
flex: 1;
position: relative;
}
.search-date-picker {
width: 14rem;
width: 100%;
}
:deep(.n-date-picker) {
width: 14rem;
.n-input__input {
padding: 4px 0;
border-radius: 4px;
}
}
:deep(.n-button) {
padding: 20px 16px;
border-radius: 4px;
}
.search-button {
background: #ff7bac;
color: #fff;
:deep(.n-input) {
height: 34px;
border-radius: 3px;
border: 1px solid #e0e0e6;
&:hover {
background: #ff7bac;
color: #fff;
border-color: #ff7bac;
}
}
:deep(.n-input--focus) {
border-color: #ff7bac;
box-shadow: 0 0 0 2px rgba(255, 123, 172, 0.2);
}
.calendar-icon {
margin-left: 12px;
}
.search-button {
height: 34px;
padding: 7px 12px;
color: #fff;
background-color: #ff7bac;
border: none;
border-radius: 3px;
cursor: pointer;
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 1.375em;
letter-spacing: 3%;
min-width: 201px;
&:hover {
background-color: #e66f9a;
}
}
.background-image-container {
background: #fff;
width: 100%;
margin-top: 20px;
box-shadow: 0px 3px 14px 0px rgba(0, 0, 0, 0.16);
border-radius: 16px;
display: flex;
justify-content: center;
align-items: center;
padding: 120px 0;
}
.background-image {
width: 415px;
height: 235px;
flex-shrink: 0;
aspect-ratio: 83/47;
}
</style>

View File

@ -1,9 +1,12 @@
<template>
<div class="page-container">
<div class="financials-container">
<!-- 标题 -->
<div class="financials-title">
{{ t("financialinformation.secfilings.title") }}
<!-- 标题区域 -->
<div class="title-section">
<div class="title-decoration"></div>
<div class="financials-title">
{{ t("financialinformation.secfilings.title") }}
</div>
</div>
<!-- 公司财务概览 -->
@ -34,7 +37,7 @@
<div class="column date">
{{ t("financialinformation.secfilings.annual_reports.date") }}
</div>
<div class="column download"></div>
<div class="column download">view</div>
</div>
<!-- 报告列表 -->
@ -47,25 +50,128 @@
<div class="column file-name">{{ report.fileName }}</div>
<div class="column date">{{ report.date }}</div>
<div class="column download">
<a :href="report.downloadUrl" class="download-link">{{
t("financialinformation.secfilings.annual_reports.view")
}}</a>
<a :href="report.downloadUrl" class="download-link">
<img src="/src/assets/image/icon-link-svg.svg" alt="View" />
</a>
</div>
</div>
</div>
</div>
</section>
<!-- 分页器 -->
<div class="pagination-container">
<div class="pagination-info">
Displaying {{ displayRange.start }} - {{ displayRange.end }} of
{{ state.total }} 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, 20, 50]"
:key="size"
class="page-size-option"
:class="{ active: state.pageSize === size }"
@click="changePageSize(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>
</div>
</template>
<script setup>
import { ref } from "vue";
import { ref, reactive, computed, watch, onMounted, onUnmounted } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
//
const state = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
gotoPage: 1,
});
//
const showPageSizeMenu = ref(false);
//
const annualReports = ref([
const allAnnualReports = ref([
{
fileName: "2024 Annual Report Amendment No.2",
date: "August 20, 2025",
@ -211,6 +317,138 @@ const annualReports = ref([
"https://www.sec.gov/Archives/edgar/data/1467761/000135448810001043/zmtp_10k.htm",
},
]);
//
state.total = allAnnualReports.value.length;
//
const annualReports = computed(() => {
const start = (state.currentPage - 1) * state.pageSize;
const end = start + state.pageSize;
return allAnnualReports.value.slice(start, end);
});
//
const totalPages = computed(() => {
return Math.ceil(state.total / state.pageSize);
});
//
const displayRange = computed(() => {
const start = (state.currentPage - 1) * state.pageSize + 1;
const end = Math.min(state.currentPage * state.pageSize, state.total);
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 changePageSize = (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;
};
//
watch(
() => state.pageSize,
() => {
state.currentPage = 1;
}
);
// goto
watch(
() => state.currentPage,
(newPage) => {
state.gotoPage = newPage;
}
);
//
const handleClickOutside = (event) => {
if (!event.target.closest(".page-size-selector")) {
showPageSizeMenu.value = false;
}
};
//
onMounted(() => {
document.addEventListener("click", handleClickOutside);
});
onUnmounted(() => {
document.removeEventListener("click", handleClickOutside);
});
</script>
<style scoped lang="scss">
@ -219,34 +457,59 @@ const annualReports = ref([
background-position: center;
background-repeat: no-repeat;
}
.financials-container {
max-width: 1200px;
max-width: 932px;
margin: 0 auto;
padding: 20px;
}
.title-section {
display: flex;
flex-direction: column;
gap: 16px;
padding: 0 16px;
margin-bottom: 32px;
}
.title-decoration {
width: 58px;
height: 7px;
background: #ff7bac;
margin: auto 0;
margin-top: 43px;
}
.financials-title {
font-family: "PingFang SC", sans-serif;
font-weight: 500;
font-size: 40px;
text-align: center;
margin-bottom: 60px;
color: #333;
line-height: 1.4em;
letter-spacing: 3%;
color: #000000;
}
.section {
margin-bottom: 60px;
margin-bottom: 16px;
}
.section-title {
font-size: 18px;
margin-bottom: 20px;
color: #333;
font-family: "PingFang SC", sans-serif;
font-weight: 500;
font-size: 24px;
line-height: 1.333em;
letter-spacing: 5%;
color: #000000;
margin-bottom: 12px;
}
.overview-text {
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 1.6;
color: #333;
margin-bottom: 30px;
line-height: 1.375em;
letter-spacing: 3%;
color: #455363;
margin-bottom: 12px;
}
.tabs {
@ -267,40 +530,88 @@ const annualReports = ref([
.reports-table {
width: 100%;
border-collapse: collapse;
background: #ffffff;
border-radius: 16px;
box-shadow: 0px 3px 14px 0px rgba(0, 0, 0, 0.16);
padding: 16px;
}
.table-header {
display: flex;
padding: 10px 0;
border-bottom: 1px solid #ccc;
font-weight: bold;
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;
text-align: center;
}
}
.table-row {
display: flex;
padding: 15px 0;
border-bottom: 1px solid #eee;
padding: 16px 0;
align-items: center;
justify-content: space-between;
}
.reports-list {
// max-height: 600px;
// overflow-y: auto;
}
.column {
&.file-name {
width: 25%;
}
&.date {
width: 25%;
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 {
&.file-name {
width: 420px;
padding: 4px 16px;
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 1.375em;
letter-spacing: 3%;
color: #455363;
text-align: left;
}
&.date {
flex: 1;
padding: 4px 16px;
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 1.375em;
letter-spacing: 3%;
color: #455363;
text-align: left;
}
&.download {
width: 25%;
text-align: right;
margin-right: 50px;
width: 152px;
padding: 4px 16px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
}
@ -310,11 +621,22 @@ const annualReports = ref([
}
.download-link {
color: #0078d7;
display: inline-flex;
align-items: center;
justify-content: center;
width: 23px;
height: 22px;
border-radius: 4px;
text-decoration: none;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
&:hover {
text-decoration: underline;
background: #fff8fb;
}
}
@ -331,4 +653,159 @@ const annualReports = ref([
text-decoration: underline;
}
}
//
.pagination-container {
display: flex;
align-items: center;
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;
top: 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-top: 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;
}
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<header></header>
<main class="p-[35px] max-w-[1200px] mx-auto">
<main class="mx-auto">
<div class="title mb-[20px]">
{{ t("financialinformation.quarterlyreports.title") }}
</div>

View File

@ -1,10 +1,5 @@
<template>
<div class="historic-data-container" style="margin-bottom: 40px">
<!-- <img
src="@/assets/image/historic-stock.png"
alt="1"
style="max-width: 100%; margin: 0 auto"
/> -->
<div class="echarts-container">
<customEcharts></customEcharts>
</div>
@ -85,202 +80,202 @@
</template>
<script setup>
import { NDataTable, NButton, NDropdown, NIcon } from 'naive-ui'
import { reactive, onMounted, h, computed } from 'vue'
import axios from 'axios'
import { NDataTable, NButton, NDropdown, NIcon } from "naive-ui";
import { reactive, onMounted, h, computed } from "vue";
import axios from "axios";
import {
ChevronDownOutline,
ChevronBackOutline,
ChevronForwardOutline,
ArrowUpOutline,
} from '@vicons/ionicons5'
import defaultTableData from '../data'
} from "@vicons/ionicons5";
import defaultTableData from "../data";
// console.log('defaultTableData', defaultTableData)
import customEcharts from '@/components/customEcharts/index.vue'
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' },
]
{ 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: 'Year to Date', key: 'Year to Date' },
{ label: '1 Year', key: '1 Year' },
{ label: '5 Years', key: '5 Years' },
{ label: '10 Years', key: '10 Years' },
{ label: "3 Months", key: "3 Months" },
{ label: "6 Months", key: "6 Months" },
{ label: "Year to Date", key: "Year to Date" },
{ label: "1 Year", key: "1 Year" },
{ label: "5 Years", key: "5 Years" },
{ label: "10 Years", key: "10 Years" },
// { label: 'Full History', key: 'Full History', disabled: true },
]
];
//
const pageSizeOptions = [
{ label: '50', key: 50 },
{ label: '100', key: 100 },
{ label: '500', key: 500 },
{ label: '1000', key: 1000 },
]
{ label: "50", key: 50 },
{ label: "100", key: 100 },
{ label: "500", key: 500 },
{ label: "1000", key: 1000 },
];
const state = reactive({
selectedPeriod: 'Daily',
selectedDuration: '6 Months',
selectedPeriod: "Daily",
selectedDuration: "6 Months",
tableData: [],
currentPage: 1,
pageSize: 50,
})
});
//
const totalPages = computed(() => {
return Math.ceil(state.tableData.length / state.pageSize)
})
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 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',
title: "Date",
key: "date",
align: "left",
fixed: "left",
width: 150,
},
{
title: 'Open',
key: 'open',
align: 'center',
title: "Open",
key: "open",
align: "center",
},
{
title: 'High',
key: 'high',
align: 'center',
title: "High",
key: "high",
align: "center",
},
{
title: 'Low',
key: 'low',
align: 'center',
title: "Low",
key: "low",
align: "center",
},
{
title: 'Close',
key: 'close',
align: 'center',
title: "Close",
key: "close",
align: "center",
},
{
title: 'Adj. Close',
key: 'adjClose',
align: 'center',
title: "Adj. Close",
key: "adjClose",
align: "center",
},
{
title: 'Change',
key: 'change',
align: 'center',
title: "Change",
key: "change",
align: "center",
render(row) {
const value = parseFloat(row.change)
const color = value < 0 ? '#ff4d4f' : value > 0 ? '#52c41a' : ''
return h('span', { style: { color } }, row.change)
const value = parseFloat(row.change);
const color = value < 0 ? "#ff4d4f" : value > 0 ? "#52c41a" : "";
return h("span", { style: { color } }, row.change);
},
},
{
title: 'Volume',
key: 'volume',
align: 'center',
title: "Volume",
key: "volume",
align: "center",
},
]
];
//
const handlePeriodChange = (key) => {
state.selectedPeriod = key
if (key === 'Annual') {
handleDurationChange('Full History')
return
state.selectedPeriod = key;
if (key === "Annual") {
handleDurationChange("Full History");
return;
}
if (key === 'Monthly') {
handleDurationChange('10 Years')
return
if (key === "Monthly") {
handleDurationChange("10 Years");
return;
}
if (key === 'Quarterly') {
handleDurationChange('10 Years')
return
if (key === "Quarterly") {
handleDurationChange("10 Years");
return;
}
getPageData()
}
getPageData();
};
const handleDurationChange = (key) => {
state.selectedDuration = key
state.currentPage = 1
getPageData()
}
state.selectedDuration = key;
state.currentPage = 1;
getPageData();
};
//
const handlePrevPage = () => {
if (state.currentPage === 1) {
return
return;
}
state.currentPage--
}
state.currentPage--;
};
const handleNextPage = () => {
if (state.currentPage >= totalPages.value) {
return
return;
}
state.currentPage++
}
state.currentPage++;
};
const handlePageSizeChange = (size) => {
state.pageSize = size
state.currentPage = 1 //
}
state.pageSize = size;
state.currentPage = 1; //
};
//
const scrollToTop = () => {
//
// 1. 使document.body
document.body.scrollTop = 0
document.body.scrollTop = 0;
// 2. 使document.documentElement (HTML)
document.documentElement.scrollTop = 0
document.documentElement.scrollTop = 0;
// 3. 使scrollIntoView
document.querySelector('.historic-data-container').scrollIntoView({
behavior: 'smooth',
block: 'start',
})
}
document.querySelector(".historic-data-container").scrollIntoView({
behavior: "smooth",
block: "start",
});
};
onMounted(() => {
getPageData()
})
getPageData();
});
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
"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',
new Date(item[0]).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
}),
item[1],
])
]);
// console.log('', calcApiData)
// 使APIdefaultTableDatacloseadjClose
const updatedTableData = defaultTableData.map((tableItem) => {
// API
const matchedApiData = calcApiData.find(
(apiItem) => apiItem[0] === tableItem.date,
)
(apiItem) => apiItem[0] === tableItem.date
);
if (matchedApiData) {
// closeadjClose
@ -288,100 +283,100 @@ const getPageDefaultData = async () => {
...tableItem,
close: matchedApiData[1].toFixed(2),
adjClose: matchedApiData[1].toFixed(2),
}
};
}
return tableItem
})
return tableItem;
});
state.tableData = updatedTableData
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 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')
"-" +
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')
"-" +
String(fromDate.getMonth() + 1).padStart(2, "0") +
"-" +
String(fromDate.getDate()).padStart(2, "0");
// let url = `https://stockanalysis.com/api/symbol/a/OTC-MINM/history?period=${state.selectedPeriod}&range=${range}`
let url =
'https://common.szjixun.cn/api/stock/history/list?from=' +
"https://common.szjixun.cn/api/stock/history/list?from=" +
finalFromDate +
'&to=' +
toDate
const res = await axios.get(url)
"&to=" +
toDate;
const res = await axios.get(url);
// console.error(res)
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',
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) : '',
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) + '%'
: '',
? Number(item.changePercent).toFixed(2) + "%"
: "",
volume: item.volume,
}
})
state.tableData = resultData
};
});
state.tableData = resultData;
}
}
}
};
</script>
<style scoped lang="scss">

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,5 @@
<template>
<div class="historic-data-container" style="margin-bottom: 40px">
<!-- <img
src="@/assets/image/historic-stock-375.png"
alt="1"
style="max-width: 100%; margin: 0 auto"
/> -->
<div class="echarts-container">
<customEcharts></customEcharts>
</div>
@ -83,202 +78,202 @@
</template>
<script setup>
import { NDataTable, NButton, NDropdown, NIcon } from 'naive-ui'
import { reactive, onMounted, h, computed } from 'vue'
import axios from 'axios'
import { NDataTable, NButton, NDropdown, NIcon } from "naive-ui";
import { reactive, onMounted, h, computed } from "vue";
import axios from "axios";
import {
ChevronDownOutline,
ChevronBackOutline,
ChevronForwardOutline,
ArrowUpOutline,
} from '@vicons/ionicons5'
import defaultTableData from '../data'
} from "@vicons/ionicons5";
import defaultTableData from "../data";
// console.log('defaultTableData', defaultTableData)
import customEcharts from '@/components/customEcharts/index.vue'
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' },
]
{ 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: 'Year to Date', key: 'Year to Date' },
{ label: '1 Year', key: '1 Year' },
{ label: '5 Years', key: '5 Years' },
{ label: '10 Years', key: '10 Years' },
{ label: "3 Months", key: "3 Months" },
{ label: "6 Months", key: "6 Months" },
{ label: "Year to Date", key: "Year to Date" },
{ label: "1 Year", key: "1 Year" },
{ label: "5 Years", key: "5 Years" },
{ label: "10 Years", key: "10 Years" },
// { label: 'Full History', key: 'Full History', disabled: true },
]
];
//
const pageSizeOptions = [
{ label: '50', key: 50 },
{ label: '100', key: 100 },
{ label: '500', key: 500 },
{ label: '1000', key: 1000 },
]
{ label: "50", key: 50 },
{ label: "100", key: 100 },
{ label: "500", key: 500 },
{ label: "1000", key: 1000 },
];
const state = reactive({
selectedPeriod: 'Daily',
selectedDuration: '6 Months',
selectedPeriod: "Daily",
selectedDuration: "6 Months",
tableData: [],
currentPage: 1,
pageSize: 50,
})
});
//
const totalPages = computed(() => {
return Math.ceil(state.tableData.length / state.pageSize)
})
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 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',
title: "Date",
key: "date",
align: "left",
fixed: "left",
width: 150,
},
{
title: 'Open',
key: 'open',
align: 'center',
title: "Open",
key: "open",
align: "center",
},
{
title: 'High',
key: 'high',
align: 'center',
title: "High",
key: "high",
align: "center",
},
{
title: 'Low',
key: 'low',
align: 'center',
title: "Low",
key: "low",
align: "center",
},
{
title: 'Close',
key: 'close',
align: 'center',
title: "Close",
key: "close",
align: "center",
},
{
title: 'Adj. Close',
key: 'adjClose',
align: 'center',
title: "Adj. Close",
key: "adjClose",
align: "center",
},
{
title: 'Change',
key: 'change',
align: 'center',
title: "Change",
key: "change",
align: "center",
render(row) {
const value = parseFloat(row.change)
const color = value < 0 ? '#ff4d4f' : value > 0 ? '#52c41a' : ''
return h('span', { style: { color } }, row.change)
const value = parseFloat(row.change);
const color = value < 0 ? "#ff4d4f" : value > 0 ? "#52c41a" : "";
return h("span", { style: { color } }, row.change);
},
},
{
title: 'Volume',
key: 'volume',
align: 'center',
title: "Volume",
key: "volume",
align: "center",
},
]
];
//
const handlePeriodChange = (key) => {
state.selectedPeriod = key
if (key === 'Annual') {
handleDurationChange('Full History')
return
state.selectedPeriod = key;
if (key === "Annual") {
handleDurationChange("Full History");
return;
}
if (key === 'Monthly') {
handleDurationChange('10 Years')
return
if (key === "Monthly") {
handleDurationChange("10 Years");
return;
}
if (key === 'Quarterly') {
handleDurationChange('10 Years')
return
if (key === "Quarterly") {
handleDurationChange("10 Years");
return;
}
getPageData()
}
getPageData();
};
const handleDurationChange = (key) => {
state.selectedDuration = key
state.currentPage = 1
getPageData()
}
state.selectedDuration = key;
state.currentPage = 1;
getPageData();
};
//
const handlePrevPage = () => {
if (state.currentPage === 1) {
return
return;
}
state.currentPage--
}
state.currentPage--;
};
const handleNextPage = () => {
if (state.currentPage >= totalPages.value) {
return
return;
}
state.currentPage++
}
state.currentPage++;
};
const handlePageSizeChange = (size) => {
state.pageSize = size
state.currentPage = 1 //
}
state.pageSize = size;
state.currentPage = 1; //
};
//
const scrollToTop = () => {
//
// 1. 使document.body
document.body.scrollTop = 0
document.body.scrollTop = 0;
// 2. 使document.documentElement (HTML)
document.documentElement.scrollTop = 0
document.documentElement.scrollTop = 0;
// 3. 使scrollIntoView
document.querySelector('.historic-data-container').scrollIntoView({
behavior: 'smooth',
block: 'start',
})
}
document.querySelector(".historic-data-container").scrollIntoView({
behavior: "smooth",
block: "start",
});
};
onMounted(() => {
getPageData()
})
getPageData();
});
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
"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',
new Date(item[0]).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
}),
item[1],
])
]);
// console.log('', calcApiData)
// 使APIdefaultTableDatacloseadjClose
const updatedTableData = defaultTableData.map((tableItem) => {
// API
const matchedApiData = calcApiData.find(
(apiItem) => apiItem[0] === tableItem.date,
)
(apiItem) => apiItem[0] === tableItem.date
);
if (matchedApiData) {
// closeadjClose
@ -286,100 +281,100 @@ const getPageDefaultData = async () => {
...tableItem,
close: matchedApiData[1].toFixed(2),
adjClose: matchedApiData[1].toFixed(2),
}
};
}
return tableItem
})
return tableItem;
});
state.tableData = updatedTableData
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 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')
"-" +
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')
"-" +
String(fromDate.getMonth() + 1).padStart(2, "0") +
"-" +
String(fromDate.getDate()).padStart(2, "0");
// let url = `https://stockanalysis.com/api/symbol/a/OTC-MINM/history?period=${state.selectedPeriod}&range=${range}`
let url =
'https://common.szjixun.cn/api/stock/history/list?from=' +
"https://common.szjixun.cn/api/stock/history/list?from=" +
finalFromDate +
'&to=' +
toDate
const res = await axios.get(url)
"&to=" +
toDate;
const res = await axios.get(url);
// console.error(res)
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',
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) : '',
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) + '%'
: '',
? Number(item.changePercent).toFixed(2) + "%"
: "",
volume: item.volume,
}
})
state.tableData = resultData
};
});
state.tableData = resultData;
}
}
}
};
</script>
<style scoped lang="scss">

View File

@ -1,10 +1,5 @@
<template>
<div class="historic-data-container" style="margin-bottom: 40px">
<!-- <img
src="@/assets/image/historic-stock.png"
alt="1"
style="max-width: 100%; margin: 0 auto"
/> -->
<div class="echarts-container">
<customEcharts></customEcharts>
</div>
@ -85,202 +80,202 @@
</template>
<script setup>
import { NDataTable, NButton, NDropdown, NIcon } from 'naive-ui'
import { reactive, onMounted, h, computed } from 'vue'
import axios from 'axios'
import { NDataTable, NButton, NDropdown, NIcon } from "naive-ui";
import { reactive, onMounted, h, computed } from "vue";
import axios from "axios";
import {
ChevronDownOutline,
ChevronBackOutline,
ChevronForwardOutline,
ArrowUpOutline,
} from '@vicons/ionicons5'
import defaultTableData from '../data'
} from "@vicons/ionicons5";
import defaultTableData from "../data";
// console.log('defaultTableData', defaultTableData)
import customEcharts from '@/components/customEcharts/index.vue'
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' },
]
{ 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: 'Year to Date', key: 'Year to Date' },
{ label: '1 Year', key: '1 Year' },
{ label: '5 Years', key: '5 Years' },
{ label: '10 Years', key: '10 Years' },
{ label: "3 Months", key: "3 Months" },
{ label: "6 Months", key: "6 Months" },
{ label: "Year to Date", key: "Year to Date" },
{ label: "1 Year", key: "1 Year" },
{ label: "5 Years", key: "5 Years" },
{ label: "10 Years", key: "10 Years" },
// { label: 'Full History', key: 'Full History', disabled: true },
]
];
//
const pageSizeOptions = [
{ label: '50', key: 50 },
{ label: '100', key: 100 },
{ label: '500', key: 500 },
{ label: '1000', key: 1000 },
]
{ label: "50", key: 50 },
{ label: "100", key: 100 },
{ label: "500", key: 500 },
{ label: "1000", key: 1000 },
];
const state = reactive({
selectedPeriod: 'Daily',
selectedDuration: '6 Months',
selectedPeriod: "Daily",
selectedDuration: "6 Months",
tableData: [],
currentPage: 1,
pageSize: 50,
})
});
//
const totalPages = computed(() => {
return Math.ceil(state.tableData.length / state.pageSize)
})
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 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',
title: "Date",
key: "date",
align: "left",
fixed: "left",
width: 150,
},
{
title: 'Open',
key: 'open',
align: 'center',
title: "Open",
key: "open",
align: "center",
},
{
title: 'High',
key: 'high',
align: 'center',
title: "High",
key: "high",
align: "center",
},
{
title: 'Low',
key: 'low',
align: 'center',
title: "Low",
key: "low",
align: "center",
},
{
title: 'Close',
key: 'close',
align: 'center',
title: "Close",
key: "close",
align: "center",
},
{
title: 'Adj. Close',
key: 'adjClose',
align: 'center',
title: "Adj. Close",
key: "adjClose",
align: "center",
},
{
title: 'Change',
key: 'change',
align: 'center',
title: "Change",
key: "change",
align: "center",
render(row) {
const value = parseFloat(row.change)
const color = value < 0 ? '#ff4d4f' : value > 0 ? '#52c41a' : ''
return h('span', { style: { color } }, row.change)
const value = parseFloat(row.change);
const color = value < 0 ? "#ff4d4f" : value > 0 ? "#52c41a" : "";
return h("span", { style: { color } }, row.change);
},
},
{
title: 'Volume',
key: 'volume',
align: 'center',
title: "Volume",
key: "volume",
align: "center",
},
]
];
//
const handlePeriodChange = (key) => {
state.selectedPeriod = key
if (key === 'Annual') {
handleDurationChange('Full History')
return
state.selectedPeriod = key;
if (key === "Annual") {
handleDurationChange("Full History");
return;
}
if (key === 'Monthly') {
handleDurationChange('10 Years')
return
if (key === "Monthly") {
handleDurationChange("10 Years");
return;
}
if (key === 'Quarterly') {
handleDurationChange('10 Years')
return
if (key === "Quarterly") {
handleDurationChange("10 Years");
return;
}
getPageData()
}
getPageData();
};
const handleDurationChange = (key) => {
state.selectedDuration = key
state.currentPage = 1
getPageData()
}
state.selectedDuration = key;
state.currentPage = 1;
getPageData();
};
//
const handlePrevPage = () => {
if (state.currentPage === 1) {
return
return;
}
state.currentPage--
}
state.currentPage--;
};
const handleNextPage = () => {
if (state.currentPage >= totalPages.value) {
return
return;
}
state.currentPage++
}
state.currentPage++;
};
const handlePageSizeChange = (size) => {
state.pageSize = size
state.currentPage = 1 //
}
state.pageSize = size;
state.currentPage = 1; //
};
//
const scrollToTop = () => {
//
// 1. 使document.body
document.body.scrollTop = 0
document.body.scrollTop = 0;
// 2. 使document.documentElement (HTML)
document.documentElement.scrollTop = 0
document.documentElement.scrollTop = 0;
// 3. 使scrollIntoView
document.querySelector('.historic-data-container').scrollIntoView({
behavior: 'smooth',
block: 'start',
})
}
document.querySelector(".historic-data-container").scrollIntoView({
behavior: "smooth",
block: "start",
});
};
onMounted(() => {
getPageData()
})
getPageData();
});
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
"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',
new Date(item[0]).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
}),
item[1],
])
]);
// console.log('', calcApiData)
// 使APIdefaultTableDatacloseadjClose
const updatedTableData = defaultTableData.map((tableItem) => {
// API
const matchedApiData = calcApiData.find(
(apiItem) => apiItem[0] === tableItem.date,
)
(apiItem) => apiItem[0] === tableItem.date
);
if (matchedApiData) {
// closeadjClose
@ -288,100 +283,100 @@ const getPageDefaultData = async () => {
...tableItem,
close: matchedApiData[1].toFixed(2),
adjClose: matchedApiData[1].toFixed(2),
}
};
}
return tableItem
})
return tableItem;
});
state.tableData = updatedTableData
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 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')
"-" +
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')
"-" +
String(fromDate.getMonth() + 1).padStart(2, "0") +
"-" +
String(fromDate.getDate()).padStart(2, "0");
// let url = `https://stockanalysis.com/api/symbol/a/OTC-MINM/history?period=${state.selectedPeriod}&range=${range}`
let url =
'https://common.szjixun.cn/api/stock/history/list?from=' +
"https://common.szjixun.cn/api/stock/history/list?from=" +
finalFromDate +
'&to=' +
toDate
const res = await axios.get(url)
"&to=" +
toDate;
const res = await axios.get(url);
// console.error(res)
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',
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) : '',
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) + '%'
: '',
? Number(item.changePercent).toFixed(2) + "%"
: "",
volume: item.volume,
}
})
state.tableData = resultData
};
});
state.tableData = resultData;
}
}
}
};
</script>
<style scoped lang="scss">

View File

@ -2,13 +2,68 @@
import customHeader from "@/components/customHeader/index.vue";
import customFooter from "@/components/customFooter/index.vue";
import { NScrollbar } from "naive-ui";
import { computed } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
function resolveAssetUrl(possiblePath) {
try {
if (possiblePath.startsWith("@/")) {
return new URL(possiblePath.replace("@/", "/src/"), import.meta.url).href;
}
if (possiblePath.startsWith("/src/")) {
return new URL(possiblePath, import.meta.url).href;
}
return possiblePath;
} catch (e) {
return possiblePath;
}
}
const defaultBgUrl = resolveAssetUrl("@/assets/image/bg-pc.png");
const currentBg = computed(() => {
//
// 1) 'url("@/xxx.png") ...' URL backgroundImage cover/center/no-repeat
// 2) '@/xxx.png' '/src/xxx.png'
// 3) '#xxxxxx' 'rgb(...)'
const metaBg = route.meta?.bg;
if (!metaBg) return { backgroundImage: `url(${defaultBgUrl})` };
const value = String(metaBg).trim();
if (value.startsWith("#") || value.startsWith("rgb")) {
return { backgroundColor: value };
}
// url("...") url('...') url(...)
const urlMatch = value.match(/url\(([^)]+)\)/i);
if (urlMatch) {
const rawPath = urlMatch[1].replace(/^['\"]|['\"]$/g, "");
const resolved = resolveAssetUrl(rawPath);
return { backgroundImage: `url(${resolved})` };
}
//
if (
value.endsWith(".png") ||
value.endsWith(".jpg") ||
value.endsWith(".jpeg") ||
value.endsWith(".webp") ||
value.endsWith(".gif") ||
value.endsWith(".svg")
) {
const resolved = resolveAssetUrl(value);
return { backgroundImage: `url(${resolved})` };
}
//
return { background: value };
});
</script>
<template>
<div class="flex flex-col h-screen">
<customHeader></customHeader>
<n-scrollbar
class="bg-[url('@/assets/image/bg-pc.png')] bg-cover bg-center flex-1"
class="bg-cover bg-center bg-no-repeat flex-1"
style="background-size: 100% 100%; background-attachment: fixed"
:style="currentBg"
>
<div>
<router-view />

View File

@ -1,7 +1,7 @@
<template>
<div class="press-releases-page">
<n-infinite-scroll :distance="0" @load="doLoadMore">
<main class="p-[35px] max-w-[1200px] mx-auto">
<main class="mx-auto">
<div class="title mb-[20px]">
{{ t("press_releases.title") }}
</div>

View File

@ -1,79 +1,206 @@
<template>
<div class="press-releases-page">
<n-infinite-scroll :distance="0" @load="doLoadMore">
<main class="p-[35px] max-w-[1200px] mx-auto">
<main class="mx-auto">
<div class="title-section">
<div class="title-decoration"></div>
<div class="title mb-[20px]">
{{ t("press_releases.title") }}
</div>
<div class="search-container">
<n-select
:options="state.selectOptions"
v-model:value="state.selectedValue"
class="search-select"
/>
<n-input
v-model:value="state.inputValue"
type="text"
:placeholder="t('press_releases.search.placeholder')"
class="search-input"
/>
<n-button @click="handleSearch" class="search-button w-[60px]">
{{ t("press_releases.search.button") }}
</n-button>
</div>
<div v-for="(item, idx) in state.filterNewsData" :key="idx">
<div class="news-item mt-[10px]">
<div class="news-item-date">{{ item.date }}</div>
<div
class="news-item-title text-[#0078d7] cursor-pointer"
style="
word-break: break-word;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
"
@click="handleNewClick(item)"
>
{{ item.title }}
</div>
<n-tooltip
trigger="hover"
:disabled="!item.showTooltip"
width="trigger"
>
<template #trigger>
</div>
<div class="search-container">
<n-select
:options="state.selectOptions"
v-model:value="state.selectedValue"
class="search-select"
/>
<input
v-model="state.inputValue"
type="text"
:placeholder="t('press_releases.search.placeholder')"
class="search-input"
/>
<button @click="handleSearch" class="search-button">
{{ t("press_releases.search.button") }}
</button>
</div>
<div class="reports-list">
<div
v-for="(item, idx) in state.filterNewsData"
:key="idx"
class="news-item table-row"
>
<div class="content">
<div class="file-content">
<div class="file-info">
<div class="vertical-line"></div>
<div
:ref="(el) => setTitleRef(el, idx)"
class="news-item-content"
class="news-item-title text-[#000] cursor-pointer"
style="
word-break: break-word;
word-break: break-all;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
"
@click="handleNewClick(item)"
>
{{ item.title }}
</div>
<svg
class="arrow-icon"
width="7"
height="14"
viewBox="0 0 7 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
@click="handleNewClick(item)"
>
<path
d="M1 1L6 7L1 13"
stroke="#FF7BAC"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
<n-tooltip trigger="hover" :disabled="true" width="trigger">
<template #trigger>
<div
:ref="(el) => setTitleRef(el, idx)"
class="news-item-content file-description"
style="
word-break: break-all;
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
"
>
{{ item.summary }}
</div>
</template>
<div slot="content">
{{ item.summary }}
</div>
</template>
<div slot="content">
{{ item.summary }}
</n-tooltip>
<div class="download-section">
<div class="news-item-date">{{ item.date }}</div>
</div>
</n-tooltip>
</div>
</div>
<div class="separator-line"></div>
</div>
</div>
<!-- 分页器 -->
<div class="pagination-container" v-if="state.total > 0">
<div class="pagination-info">
Displaying {{ displayRange.start }} - {{ displayRange.end }} of
{{ state.total }} 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 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, 20, 50]"
:key="size"
class="page-size-option"
:class="{ active: state.pageSize === size }"
@click="changePageSize(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>
</main>
</n-infinite-scroll>
</div>
</main>
</div>
</template>
<script setup>
import customDefaultPage from "@/components/customDefaultPage/index.vue";
import { reactive, onMounted, watch, nextTick, ref } from "vue";
import { NSelect, NInput, NButton, NInfiniteScroll, NTooltip } from "naive-ui";
import {
reactive,
onMounted,
watch,
nextTick,
ref,
computed,
onUnmounted,
} from "vue";
import { NSelect, NInput, NButton, NTooltip } from "naive-ui";
import { useI18n } from "vue-i18n";
import axios from "axios";
@ -94,32 +221,16 @@ const state = reactive({
}),
], //
inputValue: "", //
newsData: [
{
date: "June 3, 2025",
title: "FiEE, Inc. seized market opportunities through 2025 Osaka Expo",
content:
"Hong Kong, 3 June 2025 — FiEE, Inc. (NASDAQ:FIEE) (“FiEE, Inc.” or the “Company”), a technology company integrating IoT, connectivity and AI to redefine brand management solutions in the digital era, is pleased to announce significant business updates....",
},
{
date: "June 2, 2025",
title: "FiEE, Inc. Closes Its First Day of Trading on NASDAQ",
content:
"Hong Kong, 2 June 2025 — FiEE, Inc. (NASDAQ:FIEE) (“FiEE, Inc.” or the “Company”), a technology company integrating IoT, connectivity and AI to redefine brand management solutions in the digital era, commenced...",
},
{
date: "May 30, 2025",
title: "FiEE, Inc. Announces Reinitiation of Trading on Nasdaq",
content:
"Hong Kong, May 30, 2025 — FiEE, Inc. (“FiEE, Inc.” or the “Company”), a technology company integrating IoT, connectivity and AI to redefine brand management solutions...",
},
],
filterNewsData: [],
loading: false, //
hasMore: true, //
currentPage: 1, //
pageSize: 10,
total: 0,
gotoPage: 1,
});
const showPageSizeMenu = ref(false);
const titleRefs = ref([]);
const setTitleRef = (el, idx) => {
@ -139,14 +250,18 @@ const checkAllTitleOverflow = () => {
};
onMounted(() => {
// state.filterNewsData = state.newsData;
getPressReleasesDisplay();
document.addEventListener("click", handleClickOutside);
nextTick(() => {
checkAllTitleOverflow();
});
});
onUnmounted(() => {
document.removeEventListener("click", handleClickOutside);
});
watch(
() => state.filterNewsData,
() => {
@ -159,93 +274,68 @@ watch(
//
const getPressReleasesDisplay = () => {
state.loading = true;
let url = "https://erpapi.fiee.com/api/fiee/pressreleases/display";
let params = {
query: state.inputValue,
page: state.currentPage,
pageSize: 10,
pageSize: state.pageSize,
timeStart: state.selectedValue
? state.selectedValue === "all_years"
? null
: new Date(state.selectedValue).getTime()
: null,
};
// console.log(params)
axios.post(url, params).then((res) => {
// console.log(res)
if (res.status === 200) {
if (res.data.status === 0) {
res.data.data?.data?.forEach((item) => {
item.date = new Date(item.createdAt).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
axios
.post(url, params)
.then((res) => {
if (res.status === 200) {
if (res.data.status === 0) {
res.data.data?.data?.forEach((item) => {
item.date = new Date(item.createdAt).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
});
});
if (state.currentPage === 1) {
state.filterNewsData = res.data.data?.data || [];
} else {
state.filterNewsData = [
...state.filterNewsData,
...(res.data.data?.data || []),
];
}
if (state.filterNewsData.length < (res.data.data?.total || 0)) {
state.hasMore = true;
} else {
state.hasMore = false;
state.total = res.data.data?.total || 0;
}
}
}
});
};
const handleFilter = () => {
//
let filteredData = [...state.newsData];
//
if (state.selectedValue !== "all_years") {
filteredData = filteredData.filter((item) => {
// "May 30, 2025"
const dateMatch = item.date.match(/\b\d{4}\b/);
if (dateMatch) {
const year = dateMatch[0];
return year === state.selectedValue;
}
return false;
})
.finally(() => {
state.loading = false;
});
}
// title content
if (state.inputValue && state.inputValue.trim() !== "") {
const searchText = state.inputValue.toLowerCase().trim();
filteredData = filteredData.filter((item) => {
const titleMatch = item.title.toLowerCase().includes(searchText);
const contentMatch = item.content.toLowerCase().includes(searchText);
return titleMatch || contentMatch;
});
}
state.filterNewsData = filteredData;
};
// watcher
watch(
() => [state.selectedValue, state.inputValue],
() => {
// handleFilter();
state.currentPage = 1;
getPressReleasesDisplay();
}
);
watch(
() => state.pageSize,
() => {
state.currentPage = 1;
getPressReleasesDisplay();
}
);
watch(
() => state.currentPage,
(newPage) => {
state.gotoPage = newPage;
getPressReleasesDisplay();
}
);
const handleSearch = () => {
//
// handleFilter();
state.currentPage = 1;
getPressReleasesDisplay();
// console.log(":", state.filterNewsData);
};
const handleNewClick = (item) => {
@ -257,21 +347,112 @@ const handleNewClick = (item) => {
});
};
//
const doLoadMore = () => {
if (!state.hasMore || state.loading) {
return;
const totalPages = computed(() => {
return Math.ceil(state.total / state.pageSize) || 1;
});
const displayRange = computed(() => {
if (state.total === 0) return { start: 0, end: 0 };
const start = (state.currentPage - 1) * state.pageSize + 1;
const end = Math.min(state.currentPage * state.pageSize, state.total);
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 changePageSize = (size) => {
state.pageSize = size;
};
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) {
for (let i = 1; i <= total; i++) {
pages.push(i);
}
} else {
pages.push(1);
if (current <= 4) {
for (let i = 2; i <= 5; i++) {
pages.push(i);
}
pages.push("...");
pages.push(total);
} else if (current >= total - 3) {
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 handleClickOutside = (event) => {
if (!event.target.closest || !event.target.closest(".page-size-selector")) {
showPageSizeMenu.value = false;
}
// console.log('')
state.loading = true;
state.currentPage++;
getPressReleasesDisplay().finally(() => {
state.loading = false;
});
};
</script>
<style scoped lang="scss">
.press-releases-page {
max-width: 932px;
margin: 0 auto;
}
.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;
}
.title {
font-size: 40px;
color: #333;
@ -283,18 +464,31 @@ const doLoadMore = () => {
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 10px;
gap: 32px;
padding: 0 16px;
}
.search-select {
width: 7rem;
:deep(.n-base-selection) {
padding: 4px 0;
}
width: 201px;
height: 34px;
}
.search-input {
width: 240px;
flex: 1;
height: 34px;
padding: 7px 12px;
border: 1px solid #e0e0e6;
border-radius: 3px;
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 1.375em;
letter-spacing: 3%;
color: #455363;
&::placeholder {
color: #b6b6b6;
}
}
:deep(.n-input) {
@ -318,11 +512,294 @@ const doLoadMore = () => {
border-radius: 4px;
}
.search-button {
height: 34px;
padding: 7px 12px;
min-width: 201px;
background: #ff7bac;
color: #fff;
border: none;
border-radius: 3px;
cursor: pointer;
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 1.375em;
letter-spacing: 3%;
&:hover {
background: #ff7bac;
color: #fff;
}
}
.reports-list {
display: flex;
flex-direction: column;
gap: 4px;
}
.table-row {
display: flex;
flex-direction: column;
position: relative;
border-radius: 8px;
&:last-child {
.separator-line {
display: none;
}
}
}
.content {
flex: 1;
display: flex;
flex-direction: column;
gap: 16px;
}
.table-row .content {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 0;
&:hover {
background: #fff8fb;
}
}
.file-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
padding-right: 16px;
}
.file-info {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex: 1;
}
.news-item-title {
font-family: "PingFang SC", sans-serif;
font-weight: 500;
font-size: 16px;
line-height: 1.375em;
letter-spacing: 3%;
color: #000000;
}
.arrow-icon {
margin-left: auto;
flex-shrink: 0;
cursor: pointer;
}
.vertical-line {
width: 1px;
height: 20px;
background: #ff7bac;
flex-shrink: 0;
}
.file-description {
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 1.375em;
letter-spacing: 3%;
color: #455363;
margin: 0;
padding-left: 17px;
margin-top: 8px;
}
.news-item-date {
font-family: "PingFang SC", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 1.375em;
letter-spacing: 3%;
color: #455363;
}
.download-section {
display: flex;
align-items: center;
justify-content: flex-start;
width: 100%;
padding: 4px 16px;
}
.separator-line {
width: 100%;
height: 1px;
background: repeating-linear-gradient(
to right,
#e6eaee 0px,
#e6eaee 2px,
transparent 2px,
transparent 4px
);
margin-top: 16px;
}
//
.pagination-container {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 30px;
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: 21px;
}
.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;
top: 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-top: 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;
}
.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;
}
}
</style>

View File

@ -0,0 +1,34 @@
<script setup>
import { computed } from "vue";
import { useWindowSize } from "@vueuse/core";
// import size375 from "./size375/index.vue";
// import size768 from "./size768/index.vue";
// import size1440 from "./size1440/index.vue";
import size1920 from "./size1920/index.vue";
import { useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
const router = useRouter();
const { width } = useWindowSize();
const { t } = useI18n();
const viewComponent = computed(() => {
const viewWidth = width.value;
// if (viewWidth <= 450) {
// return size375;
// } else if (viewWidth <= 1100) {
// return size768;
// } else if (viewWidth <= 1500) {
// return size1440;
// } else if (viewWidth <= 1920 || viewWidth > 1920) {
return size1920;
// }
});
</script>
<template>
<component :is="viewComponent" />
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,546 @@
<script setup></script>
<template>
<div class="page-container">
<section class="hero-section relative">
<div class="hero-content">
<div class="hero-title">
More than just a tool<br />
Comprehensive growth solutions, <br />
providing a one-stop solution for content creation,<br />
publishing, analysis, and monetization
</div>
</div>
<div class="core-value-card">
<div class="card-content">
<div class="card-title">Core Value</div>
<div class="card-text">
The FIEE-SAAS platform is a one-stop content operation solution
tailored for creators in the digital era. The platform utilizes
intelligent distribution technology, A1 empowerment tools, and
full-chain services,Assist you in efficiently reaching audiences on
global mainstream platforms such as TikTok, YouTube, and Instagram,
creating a KOL brand effect, unlocking content value, and achieving
sustainable growth.
</div>
</div>
</div>
<img
src="@/assets/image/product-introduction-img2.png"
alt="background"
class="hero-bg-img"
/>
</section>
<section class="features-section">
<div class="section-header">
<div class="decorator-bar"></div>
<div class="section-title">Product Features</div>
</div>
<div class="features-list">
<div class="feature-item">
<div class="feature-title">
<div class="vertical-line"></div>
One-click Synchronous Publishing
</div>
<div class="feature-description">
Synchronize graphic and video content to TikTok, YouTube, and
Instagram platforms at once, saving time on repetitive operations.
</div>
</div>
<div class="feature-item">
<div class="feature-title">
<div class="vertical-line"></div>
Intelligent Scheduled Publishing
</div>
<div class="feature-description">
Plan the content release time in advance, support batch scheduling,
and accurately grasp the optimal release time of each platform.
</div>
</div>
<div class="feature-item">
<div class="feature-title">
<div class="vertical-line"></div>
Unified Management of Multiple Accounts
</div>
<div class="feature-description">
Easily manage multiple accounts on one platform without the need for
repeated login and switching, improving team collaboration
efficiency.
</div>
</div>
<div class="feature-item">
<div class="feature-title">
<div class="vertical-line"></div>
Cloud Content Library
</div>
<div class="feature-description">
Safely store and manage all creative materials, access and use them
anytime, anywhere, and support quick retrieval and reuse.
</div>
</div>
<div class="feature-item">
<div class="feature-title">
<div class="vertical-line"></div>
Basic Data Tracking
</div>
<div class="feature-description">
Visually view the content performance of various platforms,
understand core data indicators, and provide a basis for optimizing
strategies.
</div>
</div>
</div>
</section>
<section class="solutions-section">
<div class="section-header">
<div class="decorator-bar"></div>
<div class="section-title">Value Added Solutions</div>
</div>
<div class="solutions-content">
<div class="solutions-list">
<div class="solution-item">
<img
src="@/assets/image/product-introduction-icon1.png"
alt="KOL Brand Promotion"
class="solution-icon"
/>
<div class="solution-title">
<div class="vertical-line"></div>
KOL Brand Promotion Services
</div>
<div class="solution-description">
Efficiently connect high-quality business cooperation
opportunities and complete the entire process management from
order acceptance to publication within the platform.
</div>
</div>
<div class="solution-item">
<img
src="@/assets/image/product-introduction-icon2.png"
alt="Content Creation Support"
class="solution-icon"
/>
<div class="solution-title">
<div class="vertical-line"></div>
Professional Content Creation Support
</div>
<div class="solution-description">
Connect professional shooting and post production teams for you,
create high-quality "art+story" content, and strengthen IP
influence.
</div>
</div>
<div class="solution-item">
<img
src="@/assets/image/product-introduction-icon3.png"
alt="Account Operation"
class="solution-icon"
/>
<div class="solution-title">
<div class="vertical-line"></div>
Account Operation and Hosting Services
</div>
<div class="solution-description">
From 0 to 1 account positioning, follower growth strategy to
monetization cycle, operation experts provide full cycle running
and hosting services.
</div>
</div>
</div>
<div class="solution-image-container">
<img
src="@/assets/image/product-introduction-img1.png"
alt="Value Added Solutions"
class="solution-image"
/>
</div>
</div>
</section>
<section class="advantages-section">
<div class="advantages-content">
<div class="advantages-header">
<div class="decorator-bar"></div>
<div class="section-title text-white">Our Advantages</div>
</div>
<div class="advantages-list">
<div class="advantage-item">
<div class="advantage-title">
<div class="vertical-line"></div>
Time Saving
</div>
<div class="advantage-description">
Multi platform publishing efficiency improvement, allowing you to
focus on content creation.
</div>
</div>
<div class="advantage-item">
<div class="advantage-title">
<div class="vertical-line"></div>
Safe and Reliable
</div>
<div class="advantage-description">
Enterprise level data encryption and permission control ensure
account and content security.
</div>
</div>
<div class="advantage-item">
<div class="advantage-title">
<div class="vertical-line"></div>
Maintain Consistency
</div>
<div class="advantage-description">
Ensure that brand information is presented uniformly on all
platforms.
</div>
</div>
<div class="advantage-item">
<div class="advantage-title">
<div class="vertical-line"></div>
Data Driven
</div>
<div class="advantage-description">
Optimizing Content Strategies Based on Actual Performance.
</div>
</div>
<div class="advantage-item">
<div class="advantage-title">
<div class="vertical-line"></div>
Easy to Use
</div>
<div class="advantage-description">
Intuitive interface design, no need for professional technical
background.
</div>
</div>
</div>
</div>
</section>
<section class="cta-section">
<img
src="@/assets/image/product-introduction-img5.png"
alt="background"
class="cta-bg-img"
/>
<div class="cta-content">
<div class="cta-text">
<svg
xmlns="http://www.w3.org/2000/svg"
width="60"
height="32"
viewBox="0 0 60 32"
fill="none"
>
<path
d="M42.4968 0.636391C43.3437 -0.21213 44.7165 -0.21213 45.5635 0.636391L59.3648 14.4638C60.2117 15.3123 60.2117 16.6877 59.3648 17.5362L45.5635 31.3636C44.7165 32.2121 43.3437 32.2121 42.4968 31.3636C41.6499 30.5151 41.6499 29.1397 42.4968 28.2912L52.5962 18.1728H2.16868C0.970951 18.1728 0 17.2 0 16C0 14.8 0.970951 13.8272 2.16868 13.8272H52.5962L42.4968 3.70883C41.6499 2.86031 41.6499 1.48491 42.4968 0.636391Z"
fill="#FF7BAC"
/>
</svg>
<div class="cta-title">
Get customized <br />
solutions for free
</div>
</div>
<div class="cta-qr-code">
<img
src="@/assets/image/product-introduction-img6.png"
alt="QR Code"
/>
</div>
</div>
</section>
</div>
</template>
<style scoped>
.page-container {
background-color: #fff;
font-family: "PingFang SC", sans-serif;
/* max-width: 932px; */
margin: 0 auto;
}
.hero-section {
text-align: center;
position: relative;
background-image: url("@/assets/image/product-introduction-img3.png");
background-repeat: no-repeat;
background-size: 100% auto;
background-position: top;
}
.hero-content {
position: relative;
z-index: 2;
}
.hero-title {
font-size: 40px;
font-weight: 500;
line-height: 56px;
letter-spacing: 1.2px;
padding: 153px 0;
color: #000;
z-index: 2;
}
.hero-bg-img {
position: absolute;
bottom: -84px;
left: 0;
width: 100%;
/* height: 100%; */
z-index: 1;
}
.core-value-card {
width: 932px;
padding: 40px 32px;
margin: 0 auto;
background-color: #fff;
border-radius: 16px;
box-shadow: 0px 3px 14px 0px rgba(0, 0, 0, 0.16);
text-align: left;
z-index: 2;
position: relative;
}
.card-content {
display: flex;
flex-direction: column;
gap: 24px;
}
.card-title {
font-size: 40px;
font-weight: 500;
line-height: 56px;
letter-spacing: 1.2px;
}
.card-text {
font-size: 16px;
line-height: 22px;
color: #455363;
letter-spacing: 0.48px;
}
.section-header {
margin-bottom: 32px;
padding: 0 16px;
}
.decorator-bar {
width: 58px;
height: 7px;
background-color: #ff7bac;
margin-bottom: 16px;
}
.section-title {
font-size: 40px;
font-weight: 500;
line-height: 56px;
letter-spacing: 1.2px;
color: #000;
}
.features-section {
padding-top: 200px;
max-width: 932px;
margin: 0 auto;
}
.features-list {
display: flex;
flex-direction: column;
gap: 24px;
}
.feature-item {
display: flex;
flex-direction: column;
gap: 16px;
}
.feature-title {
font-size: 24px;
font-weight: 500;
line-height: 32px;
letter-spacing: 1.2px;
display: flex;
align-items: center;
gap: 16px;
}
.feature-description {
font-size: 16px;
line-height: 22px;
color: #455363;
letter-spacing: 0.48px;
padding: 0 16px;
}
.solutions-section {
padding-top: 80px;
max-width: 932px;
margin: 0 auto;
}
.solutions-content {
display: flex;
gap: 16px;
}
.solutions-list {
display: flex;
flex-direction: column;
gap: 24px;
width: 466px;
}
.solution-item {
text-align: left;
display: flex;
flex-direction: column;
gap: 16px;
}
.solution-icon {
width: 92px;
height: 76px;
padding-left: 16px;
}
.solution-title {
font-size: 20px;
line-height: 28px;
font-weight: 500;
display: flex;
gap: 16px;
align-items: center;
}
.solution-description {
font-size: 16px;
line-height: 22px;
color: #455363;
letter-spacing: 0.48px;
padding: 0 16px;
}
.solution-image-container {
width: 434px;
height: 628px;
border-radius: 16px;
}
.solution-image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 16px;
}
.advantages-section {
margin-top: 80px;
padding: 80px 0;
background-image: url("@/assets/image/product-introduction-img4.png");
background-size: cover;
background-position: center;
color: #fff;
position: relative;
}
.advantages-content {
max-width: 932px;
margin: 0 auto;
display: flex;
gap: 16px;
position: relative;
z-index: 1;
}
.advantages-header {
width: 466px;
padding: 0 16px;
}
.advantages-list {
width: 466px;
display: flex;
flex-direction: column;
gap: 24px;
}
.advantage-item {
display: flex;
flex-direction: column;
gap: 16px;
}
.advantage-title {
font-size: 24px;
font-weight: 500;
line-height: 32px;
letter-spacing: 1.2px;
display: flex;
gap: 16px;
align-items: center;
}
.advantage-description {
font-size: 16px;
line-height: 22px;
letter-spacing: 0.48px;
opacity: 0.7;
}
.text-white {
color: #fff;
}
.cta-section {
padding: 80px 0;
position: relative;
max-width: 932px;
margin: 0 auto;
overflow: hidden;
}
.cta-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 16px;
position: relative;
z-index: 1;
}
.cta-text {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 188px;
}
.cta-arrow {
width: 60px;
height: 32px;
}
.cta-title {
font-size: 40px;
font-weight: 500;
line-height: 56px;
letter-spacing: 1.2px;
}
.cta-qr-code {
width: 188px;
height: 188px;
background-color: #90ffff;
border-radius: 16px;
padding: 14px;
}
.cta-qr-code img {
width: 100%;
height: 100%;
object-fit: contain;
}
.cta-bg-img {
position: absolute;
top: 80px;
left: 201px;
width: 530px;
height: 268px;
opacity: 0.8;
z-index: 0;
}
.vertical-line {
width: 1px;
height: 20px;
background: #ff7bac;
flex-shrink: 0;
}
</style>

View File

@ -1,80 +1,216 @@
<script setup>
import { useStockQuote } from "@/store/stock-quote/index.js";
const { getStockQuate, stockQuote, formatted } = useStockQuote();
console.log(stockQuote);
getStockQuate();
</script>
<template>
<main
ref="main"
class="flex pt-100px flex-col md:flex-row justify-center items-center gap-32 rounded-3xl"
>
<!-- 左侧大号价格 -->
<section
class="flex flex-col items-center justify-center glass-card p-32 rounded-2xl shadow-xl"
>
<div
class="text-9xl font-extrabold text-#ff7bac animate-bg-move select-none drop-shadow-lg"
>
${{ stockQuote.price }}
<main ref="main" class="stock-quote-hero">
<div class="hero-content">
<!-- 标题区域 -->
<div class="title-section">
<div class="title-decoration"></div>
<div class="stock-title">Stock Quote</div>
</div>
<div
class="mt-10 text-3xl text-gray-500 font-semibold tracking-widest mb-10px"
>
NASDAQ: <span class="text-black">FIEE</span>
</div>
<div class="text-gray-500">{{ formatted }}</div>
</section>
<!-- 右侧信息卡片 -->
<section class="grid grid-cols-2 gap-16">
<div class="info-card">
<div class="text-lg text-gray-400">Open</div>
<div class="text-3xl font-bold">{{ stockQuote.open }}</div>
</div>
<div class="info-card">
<div class="text-lg text-gray-400">% Change</div>
<!-- <div class="text-3xl font-bold"
:class="stockQuote.change?.[1]?.startsWith('-') ? 'text-red-500' : (stockQuote.change?.[1]?.startsWith('+') ? 'text-green-500' : '')">
{{ stockQuote.change?.join('') }}</div> -->
<div
class="text-3xl font-bold"
:class="
stockQuote.change?.includes('-') ? 'text-green-500' : 'text-red-500'
"
>
{{ stockQuote.change }}
<section class="quote-layout">
<div class="price-card">
<div class="price-value">${{ stockQuote.price }}</div>
<div class="price-market">NASDAQ: FIEE</div>
<div class="price-time">{{ formatted }}</div>
</div>
</div>
<div class="info-card">
<div class="text-lg text-gray-400">Day's Range</div>
<div class="text-3xl font-bold">{{ stockQuote.daysRange }}</div>
</div>
<div class="info-card">
<div class="text-lg text-gray-400">52-Week Range</div>
<div class="text-3xl font-bold">{{ stockQuote.week52Range }}</div>
</div>
<div class="info-card">
<div class="text-lg text-gray-400">Volume</div>
<div class="text-3xl font-bold">{{ stockQuote.volume }}</div>
</div>
<div class="info-card">
<div class="text-lg text-gray-400">Market Cap</div>
<div class="text-3xl font-bold">{{ stockQuote.marketCap }}</div>
</div>
</section>
<div class="stats-table">
<div class="stats-cell">
<span class="stat-title">Open</span>
<span class="stat-value">{{ stockQuote.open }}</span>
</div>
<div class="stats-cell">
<span class="stat-title">% Change</span>
<span
class="stat-value"
:class="
stockQuote.change
? String(stockQuote.change).includes('-')
? 'negative-change'
: String(stockQuote.change).includes('+')
? 'positive-change'
: 'neutral-change'
: 'neutral-change'
"
>
{{ stockQuote.change }}
</span>
</div>
<div class="stats-cell">
<span class="stat-title">Volume</span>
<span class="stat-value">{{ stockQuote.volume }}</span>
</div>
<div class="stats-cell">
<span class="stat-title">52-Week Range</span>
<span class="stat-value">{{ stockQuote.week52Range }}</span>
</div>
<div class="stats-cell">
<span class="stat-title">Day's Range</span>
<span class="stat-value">{{ stockQuote.daysRange }}</span>
</div>
<div class="stats-cell">
<span class="stat-title">Market Cap</span>
<span class="stat-value">{{ stockQuote.marketCap }}</span>
</div>
</div>
</section>
</div>
</main>
</template>
<style scoped>
/* 玻璃拟态和卡片动画可用 UnoCSS 快捷方式实现,若未配置可加如下样式 */
.glass-card {
backdrop-filter: blur(16px);
background: rgba(255, 255, 255, 0.6);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 32px 0 rgba(255, 123, 172, 0.18);
.stock-quote-hero {
position: relative;
}
.info-card {
@apply glass-card p-6 rounded-xl flex flex-col items-start gap-1 hover:scale-105 transition-transform duration-300;
.hero-content {
max-width: 930px;
margin: 0 auto;
}
.hero-header {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.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;
}
.stock-title {
font-family: "PingFang SC", sans-serif;
font-weight: 500;
font-size: 40px;
line-height: 1.4em;
letter-spacing: 3%;
color: #000000;
}
.gradient-badge {
width: 48px;
height: 4px;
border-radius: 4px;
background: #e62968;
}
.hero-title {
font-size: 32px;
font-weight: 600;
letter-spacing: 0.02em;
color: #0d1b2a;
}
.quote-layout {
display: flex;
flex-direction: row;
margin-top: 10px;
}
.price-card {
width: 466px;
min-height: 466px;
border-radius: 32px;
background: #ffffff;
border: 1px solid #f0f3f8;
box-shadow: 0 12px 32px rgba(16, 46, 86, 0.08);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 16px;
text-align: center;
}
.price-value {
font-size: 110px;
font-weight: 700;
letter-spacing: -0.03em;
background: linear-gradient(90deg, #ff7bac 0%, #0ff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-transform: uppercase;
}
.price-market {
font-size: 22px;
font-weight: 600;
letter-spacing: 0.04em;
color: #0d1b2a;
margin-bottom: 12px;
}
.price-time {
font-size: 18px;
letter-spacing: 0.02em;
color: #5a6775;
}
.stats-table {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-auto-rows: minmax(0, 1fr);
}
.stats-cell {
display: flex;
flex-direction: column;
justify-content: center;
gap: 8px;
padding: 16px 32px;
}
.stat-title {
font-size: 14px;
font-weight: 500;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #73849a;
}
.stat-title {
font-size: 18px;
font-weight: 500;
letter-spacing: 0.03em;
color: #455363;
}
.stat-value {
color: #000;
font-feature-settings: "liga" off, "clig" off;
font-family: "PingFang SC";
font-size: 26px;
font-style: normal;
font-weight: 600;
line-height: 56px; /* 215.385% */
letter-spacing: 1.2px;
}
.positive-change {
color: #00c48c;
}
.negative-change {
color: #cf3050;
}
.neutral-change {
color: #0d1b2a;
}
</style>