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.

578 lines
15 KiB

3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
2 weeks ago
2 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
2 weeks ago
2 weeks ago
3 weeks ago
3 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
  1. <template>
  2. <div class="market-temperature">
  3. <div class="container">
  4. <div class="border3">
  5. <section class="chart-section">
  6. <div>
  7. <div class="trapezoid">
  8. <span>{{ companyName }}</span>
  9. <span>{{ stockCode }}</span>
  10. </div>
  11. <div ref="KlineCanvs" class="KlineClass"></div>
  12. </div>
  13. </section>
  14. </div>
  15. <div class="border4">
  16. <el-table :data="groupedWDRL" border :row-style="tableRowStyle" header-cell-class-name="table_header"
  17. :cell-style="tableCellStyle" :column-width="cellWidth">
  18. <el-table-column v-for="(day, colIndex) in ['一', '二', '三', '四', '五', '六', '日']" :key="colIndex" :label="day">
  19. <template #default="{ $index: rowIndex }">
  20. <div v-if="getDayData(rowIndex, colIndex + 1)">
  21. <p class="WDRL_date">
  22. {{ formatDate(getDayData(rowIndex, colIndex + 1).date) }}
  23. <span class="month-display">{{ formatMonth(getDayData(rowIndex, colIndex + 1).date) }}</span>
  24. </p>
  25. <p class="WDRL_data">
  26. <template v-if="isIndexCode">
  27. <span v-if="getDayData(rowIndex, colIndex + 1).market_temperature">
  28. {{ getDayData(rowIndex, colIndex + 1).market_temperature }}
  29. </span>
  30. </template>
  31. <template v-else>
  32. <template v-if="isBothRest(rowIndex, colIndex + 1)">休市</template>
  33. <template v-else>
  34. <span v-if="getDayData(rowIndex, colIndex + 1).stock_temperature">
  35. {{ getDayData(rowIndex, colIndex + 1).stock_temperature }}
  36. </span>
  37. <span v-if="shouldShowDivider(rowIndex, colIndex + 1)"> | </span>
  38. <span v-if="getDayData(rowIndex, colIndex + 1).market_temperature">
  39. {{ getDayData(rowIndex, colIndex + 1).market_temperature }}
  40. </span>
  41. </template>
  42. </template>
  43. </p>
  44. </div>
  45. <div v-else-if="shouldShowRest(rowIndex, colIndex + 1)">
  46. <p class="WDRL_date">休市</p>
  47. </div>
  48. </template>
  49. </el-table-column>
  50. </el-table>
  51. </div>
  52. </div>
  53. </div>
  54. </template>
  55. <script setup>
  56. import { ref, computed, onMounted, defineExpose, defineProps, onUnmounted, onBeforeUnmount } from 'vue'
  57. import * as echarts from 'echarts'
  58. const props = defineProps({
  59. companyName: {
  60. type: String,
  61. default: ''
  62. },
  63. stockCode: {
  64. type: String,
  65. default: ''
  66. }
  67. })
  68. const KlineCanvs = ref()
  69. const WDRL = ref([])
  70. const klineDataRaw = ref([]) // 用于存储 K 线图数据
  71. let chartInstance = null // 存储图表实例
  72. const indexCodes = ['NDX', 'DJIA', 'SPX', 'STI', 'KLSE', 'TSX', 'N225', 'KS11', 'JKSE', '1A0001', 'HSI', 'I63', 'VNINDE']
  73. const isIndexCode = computed(() => indexCodes.includes(props.code))
  74. // 分组 WDRL 数据
  75. const groupedWDRL = computed(() => {
  76. const result = []
  77. for (let i = 0; i < WDRL.value.length; i += 7) {
  78. result.push(WDRL.value.slice(i, i + 7))
  79. }
  80. return result
  81. })
  82. // 获取指定日期的数据
  83. function getDayData(rowIndex, dayIndex) {
  84. const weekData = groupedWDRL.value[rowIndex]
  85. if (weekData && weekData.length >= dayIndex) {
  86. return weekData[dayIndex - 1] || {}
  87. }
  88. return {}
  89. }
  90. // 判断是否显示分隔符
  91. function shouldShowDivider(rowIndex, dayIndex) {
  92. const data = getDayData(rowIndex, dayIndex)
  93. return data?.market_temperature && data?.stock_temperature
  94. }
  95. // 判断是否都休市
  96. function isBothRest(rowIndex, colIndex) {
  97. const data = getDayData(rowIndex, colIndex)
  98. return data && data.stock_temperature === '休市' && data.market_temperature === '休市'
  99. }
  100. // 判断是否显示休市信息
  101. function shouldShowRest(rowIndex, dayIndex) {
  102. const data = getDayData(rowIndex, dayIndex)
  103. if (data && (data.stock_temperature || data.market_temperature)) return false
  104. const flatIndex = rowIndex * 7 + (dayIndex - 1)
  105. const targetDay = WDRL.value[flatIndex]
  106. if (!targetDay || !targetDay.date) return false
  107. const [year, month, day] = targetDay.date.split('/').map(Number)
  108. if (!year || !month || !day) return false
  109. const dateObj = new Date(year, month - 1, day)
  110. const today = new Date()
  111. if (dateObj.getMonth() !== today.getMonth() || dateObj.getFullYear() !== today.getFullYear()) return false
  112. const weekday = dateObj.getDay()
  113. return weekday >= 1 && weekday <= 5
  114. }
  115. // 格式化月份
  116. function formatMonth(dateStr) {
  117. if (!dateStr) return ''
  118. const month = dateStr.split('/')[1]
  119. const map = { '01': '一月', '02': '二月', '03': '三月', '04': '四月', '05': '五月', '06': '六月', '07': '七月', '08': '八月', '09': '九月', 10: '十月', 11: '十一月', 12: '十二月' }
  120. return map[month] || ''
  121. }
  122. // 格式化日期
  123. function formatDate(dateStr) {
  124. if (!dateStr) return ''
  125. return dateStr.split('/')[2]
  126. }
  127. // 设置表格单元格样式
  128. function tableCellStyle({ row, column, rowIndex, columnIndex }) {
  129. const data = getDayData(rowIndex, columnIndex + 1)
  130. let value = isIndexCode.value ? Number(data?.market_temperature) : Number(data?.stock_temperature)
  131. if (isNaN(value)) return { backgroundColor: '#4b759f', color: 'white' }
  132. if (value >= 90) return { backgroundColor: '#BD0000', color: 'white' }
  133. else if (value >= 70) return { backgroundColor: '#FF5638', color: 'white' }
  134. else if (value >= 50) return { backgroundColor: '#C929E6', color: 'white' }
  135. else if (value >= 20) return { backgroundColor: '#00AB00', color: 'white' }
  136. else if (value > 0) return { backgroundColor: '#87CEEB', color: 'white' }
  137. else return { backgroundColor: '#4b759f', color: 'white' }
  138. }
  139. function tableRowStyle() {
  140. // 动态调整行高
  141. const containerWidth = document.querySelector('.border4')?.offsetWidth || 0;
  142. const rowHeight = containerWidth * 0.1; // 根据容器宽度的比例调整行高
  143. return { height: `${rowHeight}px` };
  144. }
  145. // 动态计算单元格宽度
  146. const containerWidth = document.querySelector('.border4')?.offsetWidth || 0;
  147. const cellWidth = containerWidth / 7;
  148. // 初始化图表
  149. function initChart(raw, klineDataRawValue, WDRLValue) {
  150. if (!raw || !klineDataRawValue || !WDRLValue) {
  151. console.error('initChart: raw, klineDataRawValue or WDRLValue is undefined')
  152. return
  153. }
  154. // 如果已存在图表实例,先销毁
  155. if (chartInstance) {
  156. chartInstance.dispose()
  157. chartInstance = null
  158. }
  159. // 处理 K 线图数据
  160. const klineData = klineDataRawValue.map(item => {
  161. const open = item[1]
  162. const close = item[2]
  163. const low = item[3]
  164. const high = item[4]
  165. return [open, close, low, high]
  166. })
  167. // 计算K线数据的最小值和最大值
  168. let minPrice = Infinity
  169. let maxPrice = -Infinity
  170. klineDataRawValue.forEach(item => {
  171. const low = item[3]
  172. const high = item[4]
  173. minPrice = Math.min(minPrice, low)
  174. maxPrice = Math.max(maxPrice, high)
  175. })
  176. // 计算小于最小值的整数作为y轴最小值
  177. const yAxisMin = Math.floor(minPrice)
  178. // 计算大于最大值的整数作为y轴最大值
  179. const yAxisMax = Math.ceil(maxPrice)
  180. // 温度日历
  181. WDRL.value = WDRLValue
  182. klineDataRaw.value = klineDataRawValue
  183. const dateLabels = raw.map(item => item[0])
  184. const marketData = raw.map(item => Math.round(item[1]))
  185. const stockData = raw.map(item => Math.round(item[2]))
  186. // 创建新的图表实例
  187. chartInstance = echarts.init(KlineCanvs.value)
  188. chartInstance.setOption({
  189. tooltip: {
  190. trigger: 'axis',
  191. axisPointer: {
  192. type: 'cross',
  193. crossStyle: {
  194. color: '#999',
  195. width: 1,
  196. type: 'dashed'
  197. },
  198. lineStyle: {
  199. color: '#999',
  200. width: 1,
  201. type: 'dashed'
  202. }
  203. },
  204. formatter: function (params) {
  205. if (params && params.length > 0) {
  206. let result = `日期: ${params[0].name}<br/>`
  207. params.forEach(param => {
  208. if (param.seriesType === 'candlestick') {
  209. const open = param.data[1]
  210. const close = param.data[2]
  211. const low = param.data[3]
  212. const high = param.data[4]
  213. result += `${param.seriesName}<br/>开: ${open}<br/>收: ${close}<br/>低: ${low}<br/>高: ${high}<br/>`
  214. } else if (param.seriesType === 'line') {
  215. result += `${param.seriesName}: ${param.value}<br/>`
  216. }
  217. })
  218. return result
  219. }
  220. return ''
  221. }
  222. },
  223. legend: { data: ['K线', '市场温度', '股票温度'], textStyle: { color: 'white',fontSize: 18 }},
  224. xAxis: {
  225. type: 'category',
  226. data: dateLabels,
  227. axisLine: { lineStyle: { color: '#00BFFF' } },
  228. axisLabel: {
  229. color: '#FFFFFF',
  230. fontSize: 12,
  231. fontWeight: 'bold'
  232. },
  233. axisTick: { lineStyle: { color: '#00BFFF' } },
  234. axisPointer: {
  235. show: true,
  236. type: 'line',
  237. lineStyle: {
  238. color: '#999',
  239. width: 1,
  240. type: 'dashed'
  241. },
  242. label: {
  243. show: true,
  244. color: 'black'
  245. },
  246. }
  247. },
  248. yAxis: [{
  249. min: yAxisMin,
  250. max: yAxisMax,
  251. axisLine: { lineStyle: { color: '#00FF7F' } },
  252. axisLabel: {
  253. color: '#FFFFFF',
  254. fontSize: 12,
  255. fontWeight: 'bold'
  256. },
  257. axisTick: { lineStyle: { color: '#00FF7F' } },
  258. splitLine: {
  259. show: false,
  260. lineStyle: {
  261. color: '#333333',
  262. type: 'solid',
  263. opacity: 0.3
  264. }
  265. },
  266. axisPointer: {
  267. show: true,
  268. type: 'line',
  269. label: {
  270. show: true,
  271. color: 'black'
  272. },
  273. lineStyle: {
  274. color: '#999',
  275. width: 1,
  276. type: 'dashed'
  277. }
  278. }
  279. }, {
  280. min: 0,
  281. max: 100,
  282. position: 'right',
  283. axisLabel: {
  284. color: '#FFFF00',
  285. fontSize: 12,
  286. fontWeight: 'bold'
  287. },
  288. axisLine: { lineStyle: { color: '#FF1493', width: 2 } },
  289. axisTick: { lineStyle: { color: '#FF1493' } },
  290. splitLine: {
  291. show: false,
  292. lineStyle: {
  293. color: '#444444',
  294. type: 'solid',
  295. opacity: 0.3
  296. }
  297. },
  298. axisPointer: {
  299. show: true,
  300. type: 'line',
  301. lineStyle: {
  302. color: '#999',
  303. width: 1,
  304. type: 'dashed'
  305. },
  306. label: {
  307. show: true,
  308. color: 'black'
  309. },
  310. }
  311. }],
  312. color: ['#f00', 'white'],
  313. series: [
  314. {
  315. name: 'K线',
  316. type: 'candlestick',
  317. data: klineData,
  318. itemStyle: {
  319. normal: {
  320. color: '#00FF00', // 阳线红色
  321. color0: '#FF0000', // 阴线绿色
  322. borderColor: '#00FF00', // 阳线边框红色
  323. borderColor0: '#FF0000' // 阴线边框绿色
  324. }
  325. }
  326. },
  327. {
  328. name: '市场温度',
  329. type: 'line',
  330. yAxisIndex: 1,
  331. data: marketData
  332. },
  333. {
  334. name: '股票温度',
  335. type: 'line',
  336. yAxisIndex: 1,
  337. data: stockData
  338. }
  339. ],
  340. // 添加 dataZoom 组件
  341. dataZoom: [
  342. {
  343. type: 'slider',
  344. xAxisIndex: 0,
  345. filterMode: 'filter',
  346. textStyle: {
  347. color: 'white'
  348. }
  349. },
  350. {
  351. type: 'inside',
  352. xAxisIndex: 0,
  353. filterMode: 'filter'
  354. }
  355. ]
  356. })
  357. // 监听窗口大小变化
  358. const resizeHandler = () => {
  359. if (chartInstance) {
  360. chartInstance.resize()
  361. }
  362. }
  363. window.addEventListener('resize', resizeHandler)
  364. // 存储resize处理器以便后续清理
  365. if (!window.marketTempResizeHandler) {
  366. window.marketTempResizeHandler = resizeHandler
  367. }
  368. // 初始调整字体大小
  369. adjustCellFontSize()
  370. }
  371. // 调整单元格字体大小
  372. function adjustCellFontSize() {
  373. const table = document.querySelector('.border4 .el-table')
  374. if (table) {
  375. const tableWidth = table.offsetWidth
  376. const cellWidth = tableWidth / 7 // 假设一周 7 天
  377. const fontSize = Math.min(cellWidth * 0.15, 20) // 根据单元格宽度动态计算字体大小
  378. const dateElements = document.querySelectorAll('.WDRL_date')
  379. const dataElements = document.querySelectorAll('.WDRL_data')
  380. dateElements.forEach(el => {
  381. el.style.fontSize = `${fontSize}px`
  382. })
  383. dataElements.forEach(el => {
  384. el.style.fontSize = `${fontSize * 0.8}px`
  385. })
  386. }
  387. }
  388. // 组件卸载时清理资源
  389. onBeforeUnmount(() => {
  390. // 销毁图表实例
  391. if (chartInstance) {
  392. chartInstance.dispose()
  393. chartInstance = null
  394. }
  395. // 移除窗口resize监听器
  396. if (window.marketTempResizeHandler) {
  397. window.removeEventListener('resize', window.marketTempResizeHandler)
  398. window.marketTempResizeHandler = null
  399. }
  400. })
  401. defineExpose({ initChart })
  402. </script>
  403. <style scoped>
  404. .WDRL_date {
  405. margin-top: 2px;
  406. text-align: center;
  407. font-size: 1.6vw;
  408. font-weight: bold;
  409. padding-top: 0%;
  410. position: relative;
  411. }
  412. .month-display {
  413. position: absolute;
  414. top: 0;
  415. right: 0;
  416. font-size: 1vw;
  417. color: rgb(58, 58, 58);
  418. }
  419. .WDRL_data {
  420. margin-top: 5px;
  421. text-align: center;
  422. font-size: 1vw;
  423. font-weight: bold;
  424. }
  425. .table_header {
  426. color: white;
  427. background: #2a2a2a;
  428. }
  429. .KlineClass {
  430. width: 100%;
  431. height: 600px;
  432. }
  433. .market-temperature {
  434. min-height: 100vh;
  435. /* background-color: rgb(0, 22, 65); */
  436. }
  437. .container {
  438. margin: 0 auto;
  439. /* padding: 20px; */
  440. max-width: 80vw;
  441. padding-bottom: 10%;
  442. }
  443. .border3 {
  444. margin-top: 40px;
  445. border-radius: 8px;
  446. padding: 20px;
  447. margin-left: -2rem;
  448. width: 100%;
  449. height: 100%;
  450. }
  451. .border4 {
  452. margin-top: 40px;
  453. border-radius: 8px;
  454. padding: 20px;
  455. width: 80%;
  456. margin-left: 8%;
  457. height: auto;
  458. overflow: visible;
  459. }
  460. .border4 .el-table {
  461. height: auto !important;
  462. max-height: none !important;
  463. }
  464. .border4 .el-table__body-wrapper {
  465. height: auto !important;
  466. max-height: none !important;
  467. overflow: visible !important;
  468. }
  469. .border4 .el-table__body {
  470. height: auto !important;
  471. }
  472. /* 手机端适配样式 */
  473. @media only screen and (max-width: 768px) {
  474. .KlineClass {
  475. width: 100%;
  476. height: 300px;
  477. }
  478. .border4 {
  479. margin-top: 0px;
  480. border-radius: 8px;
  481. padding: 0px;
  482. width: 100%;
  483. margin-left: 0%;
  484. height: auto;
  485. overflow: visible;
  486. }
  487. .border4 .el-table {
  488. height: auto !important;
  489. max-height: none !important;
  490. }
  491. .border4 .el-table__body-wrapper {
  492. height: auto !important;
  493. max-height: none !important;
  494. overflow: visible !important;
  495. }
  496. .border4 .el-table__body {
  497. height: auto !important;
  498. }
  499. .el-table .hidden-columns {
  500. position: absolute;
  501. visibility: hidden;
  502. z-index: -1;
  503. }
  504. .border3 {
  505. margin-top: 25px;
  506. border-radius: 8px;
  507. padding: 20px;
  508. margin-left: -13px;
  509. width: 100%;
  510. height: 100%;
  511. }
  512. .WDRL_date {
  513. font-size: 4.2vw;
  514. }
  515. .month-display {
  516. font-size: 1.8vw;
  517. }
  518. .WDRL_data {
  519. font-size: 3vw;
  520. }
  521. .el-table .cell {
  522. box-sizing: border-box;
  523. line-height: 23px;
  524. overflow: hidden;
  525. overflow-wrap: break-word;
  526. padding: 0 12px;
  527. text-overflow: ellipsis;
  528. white-space: normal;
  529. text-align: center;
  530. }
  531. }
  532. </style>