363 lines
9.5 KiB
Vue
363 lines
9.5 KiB
Vue
<template>
|
|
<div class="page-div">
|
|
<back-button />
|
|
<div class="header">
|
|
<h1 class="title">{{ fredCode }}</h1>
|
|
<p class="subtitle">{{ fredName }}</p>
|
|
</div>
|
|
<div class="chart-header">
|
|
<div class="time-range-buttons">
|
|
<van-button
|
|
v-for="range in timeRanges"
|
|
:key="range.value"
|
|
:plain="selectedTimeRange !== range.value"
|
|
:hairline="selectedTimeRange !== range.value"
|
|
:type="selectedTimeRange === range.value ? 'primary' : 'default'"
|
|
@click="setTimeRange(range.value)"
|
|
style="margin-left: 8px;"
|
|
>{{ range.label }}</van-button>
|
|
</div>
|
|
</div>
|
|
<div class="info-card">
|
|
<div class="chart-container">
|
|
<van-loading v-show="loading" type="spinner" color="#1989fa" class="loading" />
|
|
<div :id="`${fredCode}_chart`" class="chartDiv"></div>
|
|
</div>
|
|
</div>
|
|
<div class="chartItem d-flex align-items-center justify-content-center">
|
|
</div>
|
|
<div class="listDiv">
|
|
<div class="data-list-container" v-if="!loading">
|
|
<div v-if="list.length > 0">
|
|
<div class="list-header">
|
|
<div class="list-item">
|
|
<div class="list-col date-col">日期</div>
|
|
<template v-if="route.params.id">
|
|
<div class="list-col value-col">值(%)</div>
|
|
</template>
|
|
<template v-else>
|
|
<div class="list-col aaa-col">AAA(%)</div>
|
|
<div class="list-col bbb-col">BBB(%)</div>
|
|
<div class="list-col spread-col">BBB-AAA(%)</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<RecycleScroller
|
|
class="list-body"
|
|
:items="list"
|
|
:item-size="1"
|
|
key-field="sysDate"
|
|
>
|
|
<template v-slot="{ item }">
|
|
<div class="list-item">
|
|
<div class="list-col date-col">{{ item.sysDate }}</div>
|
|
<template v-if="route.params.id">
|
|
<div class="list-col value-col">{{ item.fredValue }}</div>
|
|
</template>
|
|
<template v-else>
|
|
<div class="list-col aaa-col">{{ item.AaaFredValue }}</div>
|
|
<div class="list-col bbb-col">{{ item.BbbFredValue }}</div>
|
|
<div class="list-col spread-col">{{ (item.BbbFredValue - item.AaaFredValue).toFixed(2) }}</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</RecycleScroller>
|
|
</div>
|
|
<div class="no-data" v-else>暂无数据</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script setup lang='ts'>
|
|
import BackButton from '@/components/back-button.vue'
|
|
|
|
import { ref, nextTick, onMounted } from 'vue'
|
|
import { RecycleScroller } from 'vue-virtual-scroller'
|
|
|
|
import { useRoute } from 'vue-router'
|
|
import { fredInfoInfo, fredDetailQueryFred, fredDetailQueryAaaAndBbb } from '@/utils/api'
|
|
let fredCode = ref('')
|
|
let fredName = ref('')
|
|
let beginTime = ref('')
|
|
let endTime = ref('')
|
|
let selectedTimeRange = ref('3Y')
|
|
|
|
const timeRanges = [
|
|
{ label: '1M', value: '1M', unit: 'month', offset: -1 },
|
|
{ label: '3M', value: '3M', unit: 'month', offset: -3 },
|
|
{ label: '1Y', value: '1Y', unit: 'year', offset: -1 },
|
|
{ label: '3Y', value: '3Y', unit: 'year', offset: -3 },
|
|
]
|
|
const route = useRoute()
|
|
const init = async () => {
|
|
const id = route.params.id
|
|
if (id) {
|
|
const { data } = await fredInfoInfo(id + '')
|
|
fredCode.value = data.fredCode
|
|
fredName.value = data.fredName
|
|
} else {
|
|
fredCode.value = 'BBB-AAA'
|
|
fredName.value = 'Option-Adjusted Spread'
|
|
}
|
|
setTimeRange('3Y')
|
|
charts.value = [null]
|
|
}
|
|
|
|
import { addOrSubtractTime } from '@/utils'
|
|
const setTimeRange = (range: string) => {
|
|
selectedTimeRange.value = range
|
|
endTime.value = new Date().toISOString().split('T')[0]
|
|
const rangeConfig = timeRanges.find(item => item.value === range)
|
|
if (rangeConfig) {
|
|
beginTime.value = addOrSubtractTime(new Date(), rangeConfig.offset, rangeConfig.unit as 'month' | 'year')
|
|
}
|
|
|
|
disposeAll()
|
|
nextTick(() => {
|
|
const id = route.params.id
|
|
getData(id ? 1 : 0)
|
|
})
|
|
}
|
|
|
|
let loading = ref(true)
|
|
let list = ref<any[]>([])
|
|
import * as echarts from 'echarts'
|
|
import { chartMixins } from '@/mixins/chart-mixins'
|
|
const { charts, destroyedFlag, disposeAll } = chartMixins()
|
|
import { getLineSeries, getBaseOption, getXAxis, getYAxis } from '@/utils/chart'
|
|
const getData = async (index: number) => {
|
|
loading.value = true
|
|
const sysDate: any = []
|
|
let seriesList: any = []
|
|
let color = []
|
|
const legend = {
|
|
data: [] as string[],
|
|
top: '30px',
|
|
show: false
|
|
}
|
|
if (index === 0) {
|
|
const { data } = await fredDetailQueryAaaAndBbb({
|
|
begin: beginTime.value,
|
|
end: endTime.value
|
|
})
|
|
color = ['green', 'blue', 'red']
|
|
legend.show = true
|
|
legend.data = ['AAA', 'BBB', 'BBB-AAA']
|
|
seriesList = [getLineSeries({ name: 'AAA' }), getLineSeries({ name: 'BBB' }), getLineSeries({ name: 'BBB-AAA' })]
|
|
data.forEach((ele: any) => {
|
|
sysDate.push(ele.sysDate)
|
|
seriesList[0].data.push({ value: ele.AaaFredValue })
|
|
seriesList[1].data.push({ value: ele.BbbFredValue })
|
|
seriesList[2].data.push({ value: Math.round((ele.BbbFredValue - ele.AaaFredValue) * 100) / 100 })
|
|
list.value.unshift(ele)
|
|
})
|
|
} else{
|
|
const { data } = await fredDetailQueryFred({
|
|
fredCode: fredCode.value,
|
|
begin: beginTime.value,
|
|
end: endTime.value
|
|
})
|
|
color = ['blue']
|
|
seriesList = [getLineSeries({ name: fredCode.value })]
|
|
data.forEach((ele: any) => {
|
|
sysDate.push(ele.sysDate)
|
|
seriesList[0].data.push({ value: ele.fredValue })
|
|
list.value.unshift(ele)
|
|
})
|
|
}
|
|
|
|
if (!destroyedFlag.value) {
|
|
let chart = echarts.init(document.getElementById(`${fredCode.value}_chart`))
|
|
const option = getBaseOption({
|
|
series: seriesList,
|
|
yAxis: [getYAxis({ axisLabel: '{value} %' })],
|
|
xAxis: [getXAxis({ data: sysDate })],
|
|
tooltipFormatter: function (params: any) {
|
|
const data = Array.isArray(params) ? params : [params]
|
|
let backText = `日期:${data[0].name}<br/>`
|
|
data.forEach((ele) => {
|
|
backText += `<span style="color: ${ele.color}">${ele.seriesName}</span>:<span class="f-b">${ele.value}%</span><br/>`
|
|
})
|
|
return backText
|
|
},
|
|
grid: [{ top: '10px', left: '40px', right: '40px', bottom: '20px' }],
|
|
legend: legend,
|
|
color: color
|
|
})
|
|
chart.setOption(option)
|
|
charts.value[0] = chart
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted (() =>{
|
|
init()
|
|
})
|
|
</script>
|
|
|
|
<style lang='scss' scoped>
|
|
@import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
|
|
.page-div {
|
|
box-sizing: border-box;
|
|
overflow-y: auto;
|
|
scroll-behavior: smooth;
|
|
-webkit-overflow-scrolling: touch;
|
|
padding-top: 102px;
|
|
|
|
.header {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
z-index: 100;
|
|
|
|
@media screen and (min-width: 992px) {
|
|
left: calc(50vw - 496px);
|
|
right: calc(50vw - 496px);
|
|
}
|
|
}
|
|
|
|
.chart-header {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
width: 100%;
|
|
margin-bottom: 16px;
|
|
|
|
.chart-title {
|
|
display: none;
|
|
}
|
|
|
|
.time-range-buttons {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
padding: 0 16px;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
}
|
|
}
|
|
|
|
.chart-container {
|
|
width: 100%;
|
|
height: 30vh;
|
|
@media screen and (min-width: 768px) {
|
|
height: 40vh;
|
|
}
|
|
}
|
|
|
|
.listDiv {
|
|
padding: 0 16px;
|
|
|
|
.data-list-container {
|
|
width: 100%;
|
|
border-radius: 16px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
background-color: #fff;
|
|
max-height: none !important;
|
|
overflow: visible !important;
|
|
height: auto !important;
|
|
-webkit-overflow-scrolling: auto;
|
|
|
|
.list-header {
|
|
background-color: #f5f7fa;
|
|
border-bottom: 1px solid #e8ebed;
|
|
}
|
|
|
|
.list-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 8px 16px;
|
|
border-bottom: 1px solid #e8ebed;
|
|
height: 40px;
|
|
box-sizing: border-box;
|
|
|
|
&:last-child {
|
|
border-bottom: none;
|
|
}
|
|
}
|
|
|
|
.list-body {
|
|
overflow: visible !important;
|
|
max-height: none !important;
|
|
height: auto !important;
|
|
}
|
|
|
|
.list-col {
|
|
flex: 1;
|
|
text-align: center;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
padding: 0 8px;
|
|
font-size: 14px;
|
|
color: #333;
|
|
|
|
&.date-col {
|
|
flex: 0 0 30%;
|
|
text-align: left;
|
|
font-weight: 500;
|
|
}
|
|
|
|
&.value-col,
|
|
&.aaa-col,
|
|
&.bbb-col,
|
|
&.spread-col {
|
|
font-family: monospace;
|
|
}
|
|
|
|
&.spread-col {
|
|
color: #1989fa;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
|
|
.no-data {
|
|
padding: 30px 0;
|
|
text-align: center;
|
|
color: #999;
|
|
background-color: #fafafa;
|
|
}
|
|
}
|
|
}
|
|
|
|
@media screen and (max-width: 768px) {
|
|
.chart-header {
|
|
margin-bottom: 12px;
|
|
|
|
.time-range-buttons {
|
|
padding: 0 12px;
|
|
|
|
button {
|
|
font-size: 12px !important;
|
|
padding: 4px 8px !important;
|
|
}
|
|
}
|
|
}
|
|
|
|
.chart-container {
|
|
height: 25vh;
|
|
}
|
|
|
|
.listDiv {
|
|
padding: 0 12px;
|
|
|
|
.data-list-container {
|
|
.list-item {
|
|
padding: 10px 12px;
|
|
height: 44px;
|
|
}
|
|
|
|
.list-col {
|
|
font-size: 12px;
|
|
padding: 0 4px;
|
|
|
|
&.date-col {
|
|
flex: 0 0 35%;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|