You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
579 lines
15 KiB
579 lines
15 KiB
<template>
|
|
<div class="market-temperature">
|
|
<div class="container">
|
|
<div class="border3">
|
|
<section class="chart-section">
|
|
<div>
|
|
<div class="trapezoid">
|
|
<span>{{ companyName }}</span>
|
|
<span>{{ stockCode }}</span>
|
|
</div>
|
|
<div ref="KlineCanvs" class="KlineClass"></div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<div class="border4">
|
|
<el-table :data="groupedWDRL" border :row-style="tableRowStyle" header-cell-class-name="table_header"
|
|
:cell-style="tableCellStyle" :column-width="cellWidth">
|
|
<el-table-column v-for="(day, colIndex) in ['一', '二', '三', '四', '五', '六', '日']" :key="colIndex" :label="day">
|
|
<template #default="{ $index: rowIndex }">
|
|
<div v-if="getDayData(rowIndex, colIndex + 1)">
|
|
<p class="WDRL_date">
|
|
{{ formatDate(getDayData(rowIndex, colIndex + 1).date) }}
|
|
<span class="month-display">{{ formatMonth(getDayData(rowIndex, colIndex + 1).date) }}</span>
|
|
</p>
|
|
<p class="WDRL_data">
|
|
<template v-if="isIndexCode">
|
|
<span v-if="getDayData(rowIndex, colIndex + 1).market_temperature">
|
|
{{ getDayData(rowIndex, colIndex + 1).market_temperature }}
|
|
</span>
|
|
</template>
|
|
<template v-else>
|
|
<template v-if="isBothRest(rowIndex, colIndex + 1)">休市</template>
|
|
<template v-else>
|
|
<span v-if="getDayData(rowIndex, colIndex + 1).stock_temperature">
|
|
{{ getDayData(rowIndex, colIndex + 1).stock_temperature }}
|
|
</span>
|
|
<span v-if="shouldShowDivider(rowIndex, colIndex + 1)"> | </span>
|
|
<span v-if="getDayData(rowIndex, colIndex + 1).market_temperature">
|
|
{{ getDayData(rowIndex, colIndex + 1).market_temperature }}
|
|
</span>
|
|
</template>
|
|
</template>
|
|
</p>
|
|
</div>
|
|
<div v-else-if="shouldShowRest(rowIndex, colIndex + 1)">
|
|
<p class="WDRL_date">休市</p>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted, defineExpose, defineProps, onUnmounted, onBeforeUnmount } from 'vue'
|
|
import * as echarts from 'echarts'
|
|
|
|
const props = defineProps({
|
|
companyName: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
stockCode: {
|
|
type: String,
|
|
default: ''
|
|
}
|
|
})
|
|
|
|
const KlineCanvs = ref()
|
|
const WDRL = ref([])
|
|
const klineDataRaw = ref([]) // 用于存储 K 线图数据
|
|
let chartInstance = null // 存储图表实例
|
|
const indexCodes = ['NDX', 'DJIA', 'SPX', 'STI', 'KLSE', 'TSX', 'N225', 'KS11', 'JKSE', '1A0001', 'HSI', 'I63', 'VNINDE']
|
|
const isIndexCode = computed(() => indexCodes.includes(props.code))
|
|
|
|
// 分组 WDRL 数据
|
|
const groupedWDRL = computed(() => {
|
|
const result = []
|
|
for (let i = 0; i < WDRL.value.length; i += 7) {
|
|
result.push(WDRL.value.slice(i, i + 7))
|
|
}
|
|
return result
|
|
})
|
|
|
|
// 获取指定日期的数据
|
|
function getDayData(rowIndex, dayIndex) {
|
|
const weekData = groupedWDRL.value[rowIndex]
|
|
if (weekData && weekData.length >= dayIndex) {
|
|
return weekData[dayIndex - 1] || {}
|
|
}
|
|
return {}
|
|
}
|
|
|
|
// 判断是否显示分隔符
|
|
function shouldShowDivider(rowIndex, dayIndex) {
|
|
const data = getDayData(rowIndex, dayIndex)
|
|
return data?.market_temperature && data?.stock_temperature
|
|
}
|
|
|
|
// 判断是否都休市
|
|
function isBothRest(rowIndex, colIndex) {
|
|
const data = getDayData(rowIndex, colIndex)
|
|
return data && data.stock_temperature === '休市' && data.market_temperature === '休市'
|
|
}
|
|
|
|
// 判断是否显示休市信息
|
|
function shouldShowRest(rowIndex, dayIndex) {
|
|
const data = getDayData(rowIndex, dayIndex)
|
|
if (data && (data.stock_temperature || data.market_temperature)) return false
|
|
const flatIndex = rowIndex * 7 + (dayIndex - 1)
|
|
const targetDay = WDRL.value[flatIndex]
|
|
if (!targetDay || !targetDay.date) return false
|
|
const [year, month, day] = targetDay.date.split('/').map(Number)
|
|
if (!year || !month || !day) return false
|
|
const dateObj = new Date(year, month - 1, day)
|
|
const today = new Date()
|
|
if (dateObj.getMonth() !== today.getMonth() || dateObj.getFullYear() !== today.getFullYear()) return false
|
|
const weekday = dateObj.getDay()
|
|
return weekday >= 1 && weekday <= 5
|
|
}
|
|
|
|
// 格式化月份
|
|
function formatMonth(dateStr) {
|
|
if (!dateStr) return ''
|
|
const month = dateStr.split('/')[1]
|
|
const map = { '01': '一月', '02': '二月', '03': '三月', '04': '四月', '05': '五月', '06': '六月', '07': '七月', '08': '八月', '09': '九月', 10: '十月', 11: '十一月', 12: '十二月' }
|
|
return map[month] || ''
|
|
}
|
|
|
|
// 格式化日期
|
|
function formatDate(dateStr) {
|
|
if (!dateStr) return ''
|
|
return dateStr.split('/')[2]
|
|
}
|
|
|
|
// 设置表格单元格样式
|
|
function tableCellStyle({ row, column, rowIndex, columnIndex }) {
|
|
const data = getDayData(rowIndex, columnIndex + 1)
|
|
let value = isIndexCode.value ? Number(data?.market_temperature) : Number(data?.stock_temperature)
|
|
if (isNaN(value)) return { backgroundColor: '#4b759f', color: 'white' }
|
|
if (value >= 90) return { backgroundColor: '#BD0000', color: 'white' }
|
|
else if (value >= 70) return { backgroundColor: '#FF5638', color: 'white' }
|
|
else if (value >= 50) return { backgroundColor: '#C929E6', color: 'white' }
|
|
else if (value >= 20) return { backgroundColor: '#00AB00', color: 'white' }
|
|
else if (value > 0) return { backgroundColor: '#87CEEB', color: 'white' }
|
|
else return { backgroundColor: '#4b759f', color: 'white' }
|
|
}
|
|
function tableRowStyle() {
|
|
// 动态调整行高
|
|
const containerWidth = document.querySelector('.border4')?.offsetWidth || 0;
|
|
const rowHeight = containerWidth * 0.1; // 根据容器宽度的比例调整行高
|
|
return { height: `${rowHeight}px` };
|
|
}
|
|
// 动态计算单元格宽度
|
|
const containerWidth = document.querySelector('.border4')?.offsetWidth || 0;
|
|
const cellWidth = containerWidth / 7;
|
|
// 初始化图表
|
|
function initChart(raw, klineDataRawValue, WDRLValue) {
|
|
if (!raw || !klineDataRawValue || !WDRLValue) {
|
|
console.error('initChart: raw, klineDataRawValue or WDRLValue is undefined')
|
|
return
|
|
}
|
|
|
|
// 如果已存在图表实例,先销毁
|
|
if (chartInstance) {
|
|
chartInstance.dispose()
|
|
chartInstance = null
|
|
}
|
|
|
|
// 处理 K 线图数据
|
|
const klineData = klineDataRawValue.map(item => {
|
|
const open = item[1]
|
|
const close = item[2]
|
|
const low = item[3]
|
|
const high = item[4]
|
|
return [open, close, low, high]
|
|
})
|
|
|
|
// 计算K线数据的最小值和最大值
|
|
let minPrice = Infinity
|
|
let maxPrice = -Infinity
|
|
klineDataRawValue.forEach(item => {
|
|
const low = item[3]
|
|
const high = item[4]
|
|
minPrice = Math.min(minPrice, low)
|
|
maxPrice = Math.max(maxPrice, high)
|
|
})
|
|
|
|
// 计算小于最小值的整数作为y轴最小值
|
|
const yAxisMin = Math.floor(minPrice)
|
|
// 计算大于最大值的整数作为y轴最大值
|
|
const yAxisMax = Math.ceil(maxPrice)
|
|
|
|
// 温度日历
|
|
WDRL.value = WDRLValue
|
|
klineDataRaw.value = klineDataRawValue
|
|
|
|
const dateLabels = raw.map(item => item[0])
|
|
const marketData = raw.map(item => Math.round(item[1]))
|
|
const stockData = raw.map(item => Math.round(item[2]))
|
|
|
|
// 创建新的图表实例
|
|
chartInstance = echarts.init(KlineCanvs.value)
|
|
chartInstance.setOption({
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
axisPointer: {
|
|
type: 'cross',
|
|
crossStyle: {
|
|
color: '#999',
|
|
width: 1,
|
|
type: 'dashed'
|
|
},
|
|
lineStyle: {
|
|
color: '#999',
|
|
width: 1,
|
|
type: 'dashed'
|
|
}
|
|
},
|
|
formatter: function (params) {
|
|
if (params && params.length > 0) {
|
|
let result = `日期: ${params[0].name}<br/>`
|
|
params.forEach(param => {
|
|
if (param.seriesType === 'candlestick') {
|
|
const open = param.data[1]
|
|
const close = param.data[2]
|
|
const low = param.data[3]
|
|
const high = param.data[4]
|
|
result += `${param.seriesName}<br/>开: ${open}<br/>收: ${close}<br/>低: ${low}<br/>高: ${high}<br/>`
|
|
} else if (param.seriesType === 'line') {
|
|
result += `${param.seriesName}: ${param.value}<br/>`
|
|
}
|
|
})
|
|
return result
|
|
}
|
|
return ''
|
|
}
|
|
},
|
|
legend: { data: ['K线', '市场温度', '股票温度'], textStyle: { color: 'white',fontSize: 18 }},
|
|
xAxis: {
|
|
type: 'category',
|
|
data: dateLabels,
|
|
axisLine: { lineStyle: { color: '#00BFFF' } },
|
|
axisLabel: {
|
|
color: '#FFFFFF',
|
|
fontSize: 12,
|
|
fontWeight: 'bold'
|
|
},
|
|
|
|
axisTick: { lineStyle: { color: '#00BFFF' } },
|
|
axisPointer: {
|
|
show: true,
|
|
type: 'line',
|
|
lineStyle: {
|
|
color: '#999',
|
|
width: 1,
|
|
type: 'dashed'
|
|
},
|
|
label: {
|
|
show: true,
|
|
color: 'black'
|
|
},
|
|
}
|
|
},
|
|
yAxis: [{
|
|
min: yAxisMin,
|
|
max: yAxisMax,
|
|
axisLine: { lineStyle: { color: '#00FF7F' } },
|
|
axisLabel: {
|
|
color: '#FFFFFF',
|
|
fontSize: 12,
|
|
fontWeight: 'bold'
|
|
},
|
|
axisTick: { lineStyle: { color: '#00FF7F' } },
|
|
splitLine: {
|
|
show: false,
|
|
lineStyle: {
|
|
color: '#333333',
|
|
type: 'solid',
|
|
opacity: 0.3
|
|
}
|
|
},
|
|
axisPointer: {
|
|
show: true,
|
|
type: 'line',
|
|
label: {
|
|
show: true,
|
|
color: 'black'
|
|
},
|
|
lineStyle: {
|
|
color: '#999',
|
|
width: 1,
|
|
type: 'dashed'
|
|
}
|
|
}
|
|
}, {
|
|
min: 0,
|
|
max: 100,
|
|
position: 'right',
|
|
axisLabel: {
|
|
color: '#FFFF00',
|
|
fontSize: 12,
|
|
fontWeight: 'bold'
|
|
},
|
|
axisLine: { lineStyle: { color: '#FF1493', width: 2 } },
|
|
axisTick: { lineStyle: { color: '#FF1493' } },
|
|
splitLine: {
|
|
show: false,
|
|
lineStyle: {
|
|
color: '#444444',
|
|
type: 'solid',
|
|
opacity: 0.3
|
|
}
|
|
},
|
|
axisPointer: {
|
|
show: true,
|
|
type: 'line',
|
|
lineStyle: {
|
|
color: '#999',
|
|
width: 1,
|
|
type: 'dashed'
|
|
},
|
|
label: {
|
|
show: true,
|
|
color: 'black'
|
|
},
|
|
}
|
|
}],
|
|
color: ['#f00', 'white'],
|
|
series: [
|
|
{
|
|
name: 'K线',
|
|
type: 'candlestick',
|
|
data: klineData,
|
|
itemStyle: {
|
|
normal: {
|
|
|
|
color: '#00FF00', // 阳线红色
|
|
color0: '#FF0000', // 阴线绿色
|
|
borderColor: '#00FF00', // 阳线边框红色
|
|
borderColor0: '#FF0000' // 阴线边框绿色
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: '市场温度',
|
|
type: 'line',
|
|
yAxisIndex: 1,
|
|
data: marketData
|
|
},
|
|
{
|
|
name: '股票温度',
|
|
type: 'line',
|
|
yAxisIndex: 1,
|
|
data: stockData
|
|
}
|
|
],
|
|
// 添加 dataZoom 组件
|
|
dataZoom: [
|
|
{
|
|
type: 'slider',
|
|
xAxisIndex: 0,
|
|
filterMode: 'filter',
|
|
textStyle: {
|
|
color: 'white'
|
|
}
|
|
},
|
|
{
|
|
type: 'inside',
|
|
xAxisIndex: 0,
|
|
filterMode: 'filter'
|
|
}
|
|
]
|
|
})
|
|
// 监听窗口大小变化
|
|
const resizeHandler = () => {
|
|
if (chartInstance) {
|
|
chartInstance.resize()
|
|
}
|
|
}
|
|
window.addEventListener('resize', resizeHandler)
|
|
|
|
// 存储resize处理器以便后续清理
|
|
if (!window.marketTempResizeHandler) {
|
|
window.marketTempResizeHandler = resizeHandler
|
|
}
|
|
// 初始调整字体大小
|
|
adjustCellFontSize()
|
|
}
|
|
|
|
// 调整单元格字体大小
|
|
function adjustCellFontSize() {
|
|
const table = document.querySelector('.border4 .el-table')
|
|
if (table) {
|
|
const tableWidth = table.offsetWidth
|
|
const cellWidth = tableWidth / 7 // 假设一周 7 天
|
|
const fontSize = Math.min(cellWidth * 0.15, 20) // 根据单元格宽度动态计算字体大小
|
|
const dateElements = document.querySelectorAll('.WDRL_date')
|
|
const dataElements = document.querySelectorAll('.WDRL_data')
|
|
dateElements.forEach(el => {
|
|
el.style.fontSize = `${fontSize}px`
|
|
})
|
|
dataElements.forEach(el => {
|
|
el.style.fontSize = `${fontSize * 0.8}px`
|
|
})
|
|
}
|
|
}
|
|
// 组件卸载时清理资源
|
|
onBeforeUnmount(() => {
|
|
// 销毁图表实例
|
|
if (chartInstance) {
|
|
chartInstance.dispose()
|
|
chartInstance = null
|
|
}
|
|
// 移除窗口resize监听器
|
|
if (window.marketTempResizeHandler) {
|
|
window.removeEventListener('resize', window.marketTempResizeHandler)
|
|
window.marketTempResizeHandler = null
|
|
}
|
|
})
|
|
|
|
defineExpose({ initChart })
|
|
</script>
|
|
|
|
<style scoped>
|
|
.WDRL_date {
|
|
margin-top: 2px;
|
|
text-align: center;
|
|
font-size: 1.6vw;
|
|
font-weight: bold;
|
|
padding-top: 0%;
|
|
position: relative;
|
|
}
|
|
|
|
.month-display {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
font-size: 1vw;
|
|
color: rgb(58, 58, 58);
|
|
}
|
|
|
|
.WDRL_data {
|
|
margin-top: 5px;
|
|
text-align: center;
|
|
font-size: 1vw;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.table_header {
|
|
color: white;
|
|
background: #2a2a2a;
|
|
}
|
|
|
|
.KlineClass {
|
|
width: 100%;
|
|
height: 600px;
|
|
}
|
|
|
|
.market-temperature {
|
|
min-height: 100vh;
|
|
/* background-color: rgb(0, 22, 65); */
|
|
}
|
|
|
|
.container {
|
|
margin: 0 auto;
|
|
/* padding: 20px; */
|
|
max-width: 80vw;
|
|
padding-bottom: 10%;
|
|
}
|
|
|
|
.border3 {
|
|
margin-top: 40px;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin-left: -2rem;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.border4 {
|
|
margin-top: 40px;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
width: 80%;
|
|
margin-left: 8%;
|
|
height: auto;
|
|
overflow: visible;
|
|
}
|
|
|
|
.border4 .el-table {
|
|
height: auto !important;
|
|
max-height: none !important;
|
|
}
|
|
|
|
.border4 .el-table__body-wrapper {
|
|
height: auto !important;
|
|
max-height: none !important;
|
|
overflow: visible !important;
|
|
}
|
|
|
|
.border4 .el-table__body {
|
|
height: auto !important;
|
|
}
|
|
|
|
/* 手机端适配样式 */
|
|
@media only screen and (max-width: 768px) {
|
|
.KlineClass {
|
|
width: 100%;
|
|
height: 300px;
|
|
}
|
|
|
|
.border4 {
|
|
margin-top: 0px;
|
|
border-radius: 8px;
|
|
padding: 0px;
|
|
width: 100%;
|
|
margin-left: 0%;
|
|
height: auto;
|
|
overflow: visible;
|
|
}
|
|
|
|
.border4 .el-table {
|
|
height: auto !important;
|
|
max-height: none !important;
|
|
}
|
|
|
|
.border4 .el-table__body-wrapper {
|
|
height: auto !important;
|
|
max-height: none !important;
|
|
overflow: visible !important;
|
|
}
|
|
|
|
.border4 .el-table__body {
|
|
height: auto !important;
|
|
}
|
|
|
|
.el-table .hidden-columns {
|
|
position: absolute;
|
|
visibility: hidden;
|
|
z-index: -1;
|
|
}
|
|
|
|
.border3 {
|
|
margin-top: 25px;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin-left: -13px;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.WDRL_date {
|
|
font-size: 4.2vw;
|
|
}
|
|
|
|
.month-display {
|
|
font-size: 1.8vw;
|
|
}
|
|
|
|
.WDRL_data {
|
|
font-size: 3vw;
|
|
}
|
|
|
|
.el-table .cell {
|
|
box-sizing: border-box;
|
|
line-height: 23px;
|
|
overflow: hidden;
|
|
overflow-wrap: break-word;
|
|
padding: 0 12px;
|
|
text-overflow: ellipsis;
|
|
white-space: normal;
|
|
text-align: center;
|
|
}
|
|
}
|
|
</style>
|