Compare commits

..

No commits in common. "main" and "qb" have entirely different histories.
main ... qb

238 changed files with 413 additions and 81499 deletions

3
env/.env.prod vendored
View File

@ -5,6 +5,3 @@ VITE_DELETE_CONSOLE = true
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP = false
VITE_BASEURL = '//appointteam.szjixun.cn'
# 文档查看
VITE_PAGE_URL="https://www.fiee.com"

4
env/.env.test vendored
View File

@ -3,7 +3,3 @@ NODE_ENV = 'test'
# 是否去除console 和 debugger
VITE_DELETE_CONSOLE = false
VITE_BASEURL = '//kid-art-test.szjixun.cn'
# 文档查看
VITE_PAGE_URL="http://172.16.100.22:8045"
# VITE_PAGE_URL="http://192.168.88.50:5878"

View File

@ -1 +0,0 @@
google-site-verification: googledeec2461668656b4.html

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="icon" type="image/svg+xml" href="/src/assets/image/icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>FiEE</title>
</head>

15822
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,15 +12,9 @@
},
"dependencies": {
"@fingerprintjs/fingerprintjs": "^4.4.3",
"@onlyoffice/document-editor-vue": "^1.5.0",
"@unocss/reset": "^0.61.9",
"@vicons/ionicons5": "^0.13.0",
"@vicons/utils": "^0.1.4",
"axios": "^1.7.3",
"cnjm-postcss-px-to-viewport": "^1.0.1",
"countup.js": "^2.8.2",
"dayjs": "^1.11.13",
"echarts": "^5.6.0",
"gsap": "^3.12.5",
"jsdom": "^24.0.0",
"lodash": "^4.17.21",
@ -52,7 +46,6 @@
"unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "^0.27.3",
"vite": "^5.3.4",
"vite-plugin-imagemin": "^0.6.1",
"vite-plugin-vue-devtools": "^7.7.6"
"vite-plugin-imagemin": "^0.6.1"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

BIN
public/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View File

@ -1,10 +1,10 @@
<script setup>
import { ref } from 'vue'
import { ref } from "vue";
import { useI18n } from 'vue-i18n'
import { NConfigProvider, NDropdown } from 'naive-ui'
const { locale } = useI18n()
const primaryColor = ref('#8B59F7')
import { useI18n } from "vue-i18n";
import { NConfigProvider, NDropdown } from "naive-ui";
const { locale } = useI18n();
const primaryColor = ref("#2B69A1");
const themeOverrides = ref({
common: {
primaryColorPressed: primaryColor,
@ -15,7 +15,10 @@ const themeOverrides = ref({
primaryColor: primaryColor,
primaryColorHover: primaryColor,
},
})
});
</script>
<template>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="19" height="19" viewBox="0 0 19 19"><defs><style>.a{fill:#fff;stroke:#707070;}.b{clip-path:url(#a);}.c{fill:#2c2c2c;}</style><clipPath id="a"><rect class="a" width="19" height="19" transform="translate(333.601 660.601)"/></clipPath></defs><g class="b" transform="translate(-333.601 -660.601)"><g transform="translate(333.591 664.087)"><path class="c" d="M19.237,22.964H2.325a.922.922,0,0,1-.922-.922V15.408a.922.922,0,1,1,1.844,0V21.12H18.315V15.408a.922.922,0,1,1,1.844,0v6.634A.922.922,0,0,1,19.237,22.964Z" transform="translate(-1.403 -7.061)"/><path class="c" d="M12.451,13.284c-.509,0-.922-.311-.922-.694V1.332c0-.384.413-.694.922-.694s.922.311.922.694V12.589C13.373,12.973,12.96,13.284,12.451,13.284Z" transform="translate(-3.073 -0.638)"/><path class="c" d="M11.833,20.248a.922.922,0,0,1-.748-.384L7.956,15.51a.922.922,0,1,1,1.5-1.076l2.381,3.313,2.38-3.313a.922.922,0,1,1,1.5,1.076l-3.129,4.355A.921.921,0,0,1,11.833,20.248Z" transform="translate(-2.455 -6.989)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -1,55 +0,0 @@
<script setup>
import { ref, watch, onMounted } from 'vue'
import { CountUp } from 'countup.js'
const props = defineProps({
endVal: {
type: [Number, String],
required: true
},
duration: {
type: Number,
default: 1.2
},
decimals: {
type: Number,
default: 2
},
prefix: {
type: String,
default: ''
},
suffix: {
type: String,
default: ''
}
})
const el = ref(null)
let countUpInstance = null
const start = (val) => {
if (countUpInstance) countUpInstance.update(val)
else {
countUpInstance = new CountUp(el.value, val, {
duration: props.duration,
decimalPlaces: props.decimals,
prefix: props.prefix,
suffix: props.suffix
})
countUpInstance.start()
}
}
onMounted(() => {
start(props.endVal)
})
watch(() => props.endVal, (val) => {
start(val)
})
</script>
<template>
<span ref="el"></span>
</template>

View File

@ -1,38 +0,0 @@
<script setup>
import { computed } from 'vue'
import { useWindowSize } from '@vueuse/core'
import size375 from '@/components/customDefaultPage/size375/index.vue'
import size768 from '@/components/customDefaultPage/size1920/index.vue'
import size1440 from '@/components/customDefaultPage/size1920/index.vue'
import size1920 from '@/components/customDefaultPage/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" v-bind="$attrs">
<template v-for="(_, slot) in $slots" #[slot]="scope">
<slot :name="slot" v-bind="scope" />
</template>
</component>
</template>
<style scoped lang="scss"></style>

View File

@ -1,27 +0,0 @@
<template>
<!-- 通用缺省页 -->
<div class="custom-default-page">
<slot name="content"></slot>
<template v-if="$slots.empty">
<slot name="empty"></slot>
</template>
<template v-else>
<div class="default-empty-icon">
<img
src="@/assets/image/icon/default-empty.png"
alt="empty"
style="width: 476px; height: 262px;"
/>
</div>
</template>
</div>
</template>
<script setup></script>
<style scoped lang="scss">
.default-empty-icon {
display: flex;
justify-content: center;
align-items: center;
margin: 40px 0 24px 0;
}
</style>

View File

@ -1,27 +0,0 @@
<template>
<!-- 通用缺省页 -->
<div class="custom-default-page">
<slot name="content"></slot>
<template v-if="$slots.empty">
<slot name="empty"></slot>
</template>
<template v-else>
<div class="default-empty-icon">
<img
src="@/assets/image/icon/default-empty.png"
alt="empty"
style="width: 350px; height: 198px; margin: 40px 0 0;"
/>
</div>
</template>
</div>
</template>
<script setup></script>
<style scoped lang="scss">
.default-empty-icon {
display: flex;
justify-content: center;
align-items: center;
margin: 40px 0 24px 0;
}
</style>

View File

@ -1,34 +0,0 @@
<script setup>
import { computed } from 'vue'
import { useWindowSize } from '@vueuse/core'
import size375 from '@/components/customEcharts/size375/index.vue'
import size768 from '@/components/customEcharts/size375/index.vue'
import size1440 from '@/components/customEcharts/size1920/index.vue'
import size1920 from '@/components/customEcharts/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 <= 500) {
return size375
} else if (viewWidth <= 960) {
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

@ -1,602 +0,0 @@
<template>
<div class="custom-echarts">
<div>
<div class="echarts-header">
<div class="echarts-header-title">
<span>FiEE, Inc. Stock Price History</span>
</div>
<div class="echarts-search-area">
<div class="echarts-search-byRange">
<text style="font-size: 0.9rem; font-weight: 400; color: #666666;">
Range
</text>
<div class="search-range-list">
<div
class="search-range-list-each"
v-for="(item, index) in state.searchRange"
:key="index"
@click="changeSearchRange(item)"
>
<span>{{ item }}</span>
</div>
</div>
</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"
input-readonly
/>
</div>
</div>
</div>
</div>
<div id="myEcharts" class="myChart"></div>
</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'
const state = reactive({
searchRange: ['1m', '3m', 'YTD', '1Y', '5Y', '10Y', 'Max'],
selectHistoricStartDate: '2009-10-07',
selectHistoricEndDate: new Date(),
})
let myCharts = null
let historicData = []
let xAxisData = []
//eCharts
const initEcharts = (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)
// console.error(xAxisData, yAxisData)
// domecharts
myCharts = echarts.init(document.getElementById('myEcharts'), null, {
renderer: 'canvas',
useDirtyRect: true
})
//
myCharts.setOption({
animation: false,
progressive: 500,
progressiveThreshold: 3000,
// title: {
// text: 'FiEE, Inc. Stock Price History',
// },
grid: {
left: '8%', // '2%'
right: '12%', // yylabel
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
label: {
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>`
},
triggerOn: 'mousemove',
confine: true,
hideDelay: 1500
},
xAxis: {
data: xAxisData,
type: 'category',
boundaryGap: false,
inverse: true,
axisLine: {
lineStyle: {
color: '#CCD6EB',
},
},
axisLabel: {
color: '#323232',
fontWeight: 'bold',
interval: 'auto',
hideOverlap: true
},
},
yAxis: {
type: 'value',
position: 'right',
interval: 25,
// max: 75.0,
show: true,
axisLabel: {
color: '#323232',
fontWeight: 'bold',
formatter: function (value) {
return value > 0 ? value.toFixed(2) : value
},
},
},
series: [
{
data: yAxisData,
type: 'line',
sampling: 'lttb',
symbol: 'none',
lineStyle: {
color: '#2c6288',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#2c6288',
},
{
offset: 1,
color: '#F4F6F8',
},
],
},
},
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
data: [],
},
progressive: 500,
progressiveThreshold: 3000,
large: true,
largeThreshold: 2000
},
],
dataZoom: [
{
type: 'inside',
},
{
type: 'slider',
show: true,
dataBackground: {
lineStyle: {
color: '#2C6288',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 1, color: '#2c6288' },
{ offset: 0, color: '#F4F6F8' },
],
},
},
},
selectedDataBackground: {
lineStyle: {
color: '#2C6288',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 1, color: '#2c6288' },
{ offset: 0, color: '#F4F6F8' },
],
},
},
},
fillerColor: 'rgba(44, 98, 136, 0.3)',
realtime: false,
},
],
})
// showTip markPoint
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]
myCharts.setOption({
series: [
{
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
data: [{ coord: [x, y] }],
},
},
],
})
}
})
// markPoint
myCharts.on('globalout', function () {
myCharts.setOption({
series: [
{
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
data: [],
},
},
],
})
})
myCharts.on('dataZoom', function (params) {
// dataZoom
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
//
if (typeof startValue === 'number') {
startValue = xAxisData[startValue]
}
if (typeof endValue === 'number') {
endValue = xAxisData[endValue]
}
//
state.selectHistoricStartDate = new Date(startValue)
state.selectHistoricEndDate = new Date(endValue)
})
}
onMounted(() => {
getHistoricalData()
})
//
const getHistoricalData = async () => {
let now = new Date()
let toDate =
now.getFullYear() +
'-' +
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)
if (res.status === 200) {
if (res.data.status === 0) {
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 //
while (left <= right) {
const mid = Math.floor((left + right) / 2)
const midTime = new Date(data[mid].date).getTime()
if (midTime > target) {
left = mid + 1
} else {
res = mid
right = mid - 1
}
}
return res
}
//
function findClosestDateIndexDescLeft(data, targetDateStr) {
let left = 0,
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()
if (midTime > target) {
left = mid + 1 // mid
} else {
res = mid // mid <= target
right = mid - 1
}
}
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
}
if (startDate || endDate) {
// historicData xAxisData initEcharts
if (
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)
}
if (startDate) {
myCharts.setOption({
dataZoom: {
endValue: startValue,
},
})
state.selectHistoricStartDate = new Date(startValue)
}
if (endDate) {
myCharts.setOption({
dataZoom: {
startValue: endValue,
},
})
state.selectHistoricEndDate = new Date(endValue)
}
}
} else {
myCharts.setOption({
dataZoom: {
startValue: '',
endValue: '',
},
})
state.selectHistoricStartDate = new Date('2009-10-07')
state.selectHistoricEndDate = new Date()
}
}
// 2009-10-07
const disableAfterDate = (date) => {
return date < new Date('2009-10-06') || date > new Date()
}
//
const disablePreviousDate = (date) => {
return date < new Date(state.selectHistoricStartDate) || date > new Date()
}
//
const changeSearchRangeStartDate = (date) => {
// console.error(date)
changeSearchRange(
'startDateTime',
new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
}),
)
}
//
const changeSearchRangeEndDate = (date) => {
// console.error(date)
changeSearchRange(
'endDateTime',
new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
}),
)
}
</script>
<style lang="scss" scoped>
.custom-echarts {
.myChart {
width: 100%;
height: 25rem;
}
.echarts-header {
.echarts-header-title {
span {
font-size: 2rem;
font-weight: 600;
color: #323232;
}
}
.echarts-search-area {
padding: 2rem 0 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.echarts-search-byRange {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 10px;
.search-range-list {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 10px;
.search-range-list-each {
padding: 5px 10px;
border-radius: 5px;
background-color: #f3f4f6;
cursor: pointer;
span {
font-weight: 600;
font-size: 0.9rem;
}
}
}
}
.echarts-search-byDate {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 0.4rem;
}
}
}
}
</style>

View File

@ -1,604 +0,0 @@
<template>
<div class="custom-echarts">
<div>
<div class="echarts-header">
<div class="echarts-header-title">
<span>FiEE, Inc. Stock Price History</span>
</div>
<div class="echarts-search-area">
<div class="echarts-search-byRange">
<text style="font-size: 0.9rem; font-weight: 400; color: #666666;">
Range
</text>
<div class="search-range-list">
<div
class="search-range-list-each"
v-for="(item, index) in state.searchRange"
:key="index"
@click="changeSearchRange(item)"
>
<span>{{ item }}</span>
</div>
</div>
</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="30">
<ArrowForwardOutline />
</n-icon> -->
<span>to</span>
<n-date-picker
v-model:value="state.selectHistoricEndDate"
type="date"
:is-date-disabled="disablePreviousDate"
@update:value="changeSearchRangeEndDate"
input-readonly
/>
</div>
</div>
</div>
</div>
<div id="myEcharts" class="myChart"></div>
</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 } from 'naive-ui'
import { ArrowForwardOutline } from '@vicons/ionicons5'
const state = reactive({
searchRange: ['1m', '3m', 'YTD', '1Y', '5Y', '10Y', 'Max'],
selectHistoricStartDate: '2009-10-07',
selectHistoricEndDate: new Date(),
})
let myCharts = null
let historicData = []
let xAxisData = []
//eCharts
const initEcharts = (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)
// console.error(xAxisData, yAxisData)
// domecharts
myCharts = echarts.init(document.getElementById('myEcharts'))
//
myCharts.setOption({
animation: false,
progressive: 500,
progressiveThreshold: 3000,
// title: {
// text: 'FiEE, Inc. Stock Price History',
// },
grid: {
left: '8%', // '2%'
right: '15%', // yylabel
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
snap: true,
label: {
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>`
},
confine: true,
hideDelay: 1500
},
xAxis: {
data: xAxisData,
type: 'category',
boundaryGap: false,
inverse: true,
axisLine: {
lineStyle: {
color: '#CCD6EB',
},
},
axisLabel: {
color: '#323232',
fontWeight: 'bold',
// formatter: function (value) {
// return value ? value.split('-')[0] : ''
// },
// interval: function (index, value) {
// if (index === 0) return true;
// const axisData = this && this.axis && this.axis.data ? this.axis.data : [];
// if (!axisData[index - 1]) return true;
// return value.split('-')[0] !== axisData[index - 1].split('-')[0];
// },
},
},
yAxis: {
type: 'value',
position: 'right',
interval: 25,
// max: 75.0,
show: true,
axisLabel: {
color: '#323232',
fontWeight: 'bold',
formatter: function (value) {
return value > 0 ? value.toFixed(2) : value
},
},
},
series: [
{
data: yAxisData,
type: 'line',
sampling: 'lttb',
symbol: 'none',
lineStyle: {
color: '#2c6288',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#2c6288',
},
{
offset: 1,
color: '#F4F6F8',
},
],
},
},
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
data: [],
},
},
],
dataZoom: [
{
type: 'inside',
},
{
type: 'slider',
show: true,
dataBackground: {
lineStyle: {
color: '#2C6288',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 1, color: '#2c6288' },
{ offset: 0, color: '#F4F6F8' },
],
},
},
},
selectedDataBackground: {
lineStyle: {
color: '#2C6288',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 1, color: '#2c6288' },
{ offset: 0, color: '#F4F6F8' },
],
},
},
},
fillerColor: 'rgba(44, 98, 136, 0.3)',
realtime: false,
},
],
})
// showTip markPoint
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]
myCharts.setOption({
series: [
{
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
data: [{ coord: [x, y] }],
},
},
],
})
}
})
// markPoint
myCharts.on('globalout', function () {
myCharts.setOption({
series: [
{
markPoint: {
symbol: 'image://' + markPointerIcon,
symbolSize: 24,
data: [],
},
},
],
})
})
myCharts.on('dataZoom', function (params) {
// dataZoom
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
//
if (typeof startValue === 'number') {
startValue = xAxisData[startValue]
}
if (typeof endValue === 'number') {
endValue = xAxisData[endValue]
}
//
state.selectHistoricStartDate = new Date(startValue)
state.selectHistoricEndDate = new Date(endValue)
})
}
onMounted(() => {
getHistoricalData()
})
//
const getHistoricalData = async () => {
let now = new Date()
let toDate =
now.getFullYear() +
'-' +
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)
if (res.status === 200) {
if (res.data.status === 0) {
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 //
while (left <= right) {
const mid = Math.floor((left + right) / 2)
const midTime = new Date(data[mid].date).getTime()
if (midTime > target) {
left = mid + 1
} else {
res = mid
right = mid - 1
}
}
return res
}
//
function findClosestDateIndexDescLeft(data, targetDateStr) {
let left = 0,
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()
if (midTime > target) {
left = mid + 1 // mid
} else {
res = mid // mid <= target
right = mid - 1
}
}
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
}
if (startDate || endDate) {
// historicData xAxisData initEcharts
if (
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)
}
if (startDate) {
myCharts.setOption({
dataZoom: {
endValue: startValue,
},
})
state.selectHistoricStartDate = new Date(startValue)
}
if (endDate) {
myCharts.setOption({
dataZoom: {
startValue: endValue,
},
})
state.selectHistoricEndDate = new Date(endValue)
}
}
} else {
myCharts.setOption({
dataZoom: {
startValue: '',
endValue: '',
},
})
state.selectHistoricStartDate = new Date('2009-10-07')
state.selectHistoricEndDate = new Date()
}
}
// 2009-10-07
const disableAfterDate = (date) => {
return date < new Date('2009-10-06') || date > new Date()
}
//
const disablePreviousDate = (date) => {
return date < new Date(state.selectHistoricStartDate) || date > new Date()
}
//
const changeSearchRangeStartDate = (date) => {
// console.error(date)
changeSearchRange(
'startDateTime',
new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
}),
)
}
//
const changeSearchRangeEndDate = (date) => {
// console.error(date)
changeSearchRange(
'endDateTime',
new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
}),
)
}
</script>
<style lang="scss" scoped>
.custom-echarts {
.myChart {
width: 100%;
height: 25rem;
}
.echarts-header {
.echarts-header-title {
span {
font-size: 2rem;
font-weight: 600;
color: #323232;
}
}
.echarts-search-area {
padding: 2rem 0 0;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
.echarts-search-byRange {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 0.7rem;
.search-range-list {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 0.7rem;
.search-range-list-each {
padding: 0.2rem 0.3rem;
border-radius: 5px;
background-color: #f3f4f6;
cursor: pointer;
span {
font-weight: 600;
font-size: 0.9rem;
}
}
}
}
.echarts-search-byDate {
padding: 1.5rem 0 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 0.4rem;
}
}
}
}
</style>

View File

@ -1,34 +0,0 @@
<script setup>
import { computed } from 'vue'
import { useWindowSize } from '@vueuse/core'
import size375 from '@/components/customFooter/size375/index.vue'
import size768 from '@/components/customFooter/size768/index.vue'
import size1440 from '@/components/customFooter/size1920/index.vue'
import size1920 from '@/components/customFooter/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 <= 500) {
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

@ -1,69 +0,0 @@
<template>
<!-- 通用页脚 -->
<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>
</div>
</div>
</template>
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
import privacyPolicy from "@/assets/file/footer/FiEE, Inc. _ Privacy policy.pdf";
import termsOfUse from "@/assets/file/footer/FiEE, Inc. _ Terms of Use.pdf";
import siteMap from "@/assets/file/footer/FiEE, Inc. _ Site Map.pdf";
//
const handleLink = (link) => {
// if (link === "privacyPolicy") {
// window.open(privacyPolicy, "_blank");
// } else if (link === "termsOfUse") {
// window.open(termsOfUse, "_blank");
// } else if (link === "siteMap") {
// window.open(siteMap, "_blank");
// }
router.push(link)
};
</script>
<style scoped lang="scss">
.custom-footer {
width: 100%;
background: #f7f8fa;
border-top: 1px solid #ececec;
z-index: 100;
.custom-footer-box {
max-width: 1700px;
margin: 0 auto;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
letter-spacing: 1px;
color: #888;
// font-size: 15px;
font-size: 1.05rem;
padding: 1rem 40px;
text-align: center;
}
.footer-links {
margin: 0.4rem 0 0;
span {
border-right: 1px solid #d2d2d7;
padding: 0 10px;
cursor: pointer;
}
span:nth-last-child(1) {
border: 0;
}
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More