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

<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>