一、父組件代碼:
<template>
? <div class="chart-box" v-loading="loading">
? ? <!-- tab導航欄 -->
? ? <div class="tab-box">
? ? ? <div class="tab-list">
? ? ? ? <div
? ? ? ? ? v-for="(item, index) in tabList"
? ? ? ? ? :key="index"
? ? ? ? ? class="item-tab"
? ? ? ? ? @click="handleClick(index)"
? ? ? ? >
? ? ? ? ? <div :class="tabActive === index ? 'color' : ''" class="tab">
? ? ? ? ? ? {{ item }}
? ? ? ? ? </div>
? ? ? ? ? <div v-if="tabActive === index" class="line-box" />
? ? ? ? </div>
? ? ? </div>
? ? </div>
? ? <!-- k線圖板塊 -->
? ? <div class="Kchart-box" v-if="tabActive === 0">
? ? ? <!-- 導航欄按鈕 -->
? ? ? <div class="btn-options">
? ? ? ? <div
? ? ? ? ? class="btn"
? ? ? ? ? v-for="(item, index) in groupList"
? ? ? ? ? :key="index"
? ? ? ? ? :class="activeIndex === index ? 'color' : ''"
? ? ? ? ? @click="onClickItem(item, index)"
? ? ? ? >
? ? ? ? ? {{ item.name }}
? ? ? ? </div>
? ? ? </div>
? ? ? <!-- k線板塊 -->
? ? ? <div class="kChart">
? ? ? ? <div :style="{ width: '100%' }" class="chart">
? ? ? ? ? <!-- 分時圖 -->
? ? ? ? ? <chartMin
? ? ? ? ? ? v-if="activeIndex === 0"
? ? ? ? ? ? :pre-price="prePrice"
? ? ? ? ? ? :data-list="list"
? ? ? ? ? ? :minDateList1="minDateList1"
? ? ? ? ? ? :digit="digit"
? ? ? ? ? ? :current-index="activeIndex"
? ? ? ? ? ? class="chartMin"
? ? ? ? ? >
? ? ? ? ? </chartMin>
? ? ? ? ? <!-- k線圖 -->
? ? ? ? ? <chartK
? ? ? ? ? ? v-if="activeIndex !== 0"
? ? ? ? ? ? :data-list="listK"
? ? ? ? ? ? :digit="digit"
? ? ? ? ? ? :current-tab="activeIndex"
? ? ? ? ? ? :current-index="currentIndex"
? ? ? ? ? ? class="chartMin"
? ? ? ? ? ? @getHoverData="getHoverData"
? ? ? ? ? >
? ? ? ? ? </chartK>
? ? ? ? ? <div v-if="activeIndex !== 0" class="indexBtn">
? ? ? ? ? ? <span
? ? ? ? ? ? ? :class="{ active: currentIndex === 1 }"
? ? ? ? ? ? ? @click="choseIndex(1)"
? ? ? ? ? ? >
? ? ? ? ? ? ? 成交量
? ? ? ? ? ? </span>
? ? ? ? ? ? <span
? ? ? ? ? ? ? :class="{ active: currentIndex === 2 }"
? ? ? ? ? ? ? @click="choseIndex(2)"
? ? ? ? ? ? >
? ? ? ? ? ? ? MACD
? ? ? ? ? ? </span>
? ? ? ? ? ? <span
? ? ? ? ? ? ? :class="{ active: currentIndex === 3 }"
? ? ? ? ? ? ? @click="choseIndex(3)"
? ? ? ? ? ? >
? ? ? ? ? ? ? KDJ
? ? ? ? ? ? </span>
? ? ? ? ? ? <span
? ? ? ? ? ? ? :class="{ active: currentIndex === 4 }"
? ? ? ? ? ? ? @click="choseIndex(4)"
? ? ? ? ? ? >
? ? ? ? ? ? ? RSI
? ? ? ? ? ? </span>
? ? ? ? ? </div>
? ? ? ? ? <div
? ? ? ? ? ? v-if="activeIndex !== 0 && currentIndex === 1"
? ? ? ? ? ? class="pos-box macd-box"
? ? ? ? ? >
? ? ? ? ? ? <p>
? ? ? ? ? ? ? 成交量(手):
? ? ? ? ? ? ? <span>{{
? ? ? ? ? ? ? ? KHoverData[5] == null ? '' : formatNumUnit(KHoverData[5])
? ? ? ? ? ? ? }}</span>
? ? ? ? ? ? </p>
? ? ? ? ? </div>
? ? ? ? ? <div
? ? ? ? ? ? v-if="activeIndex !== 0 && currentIndex === 2"
? ? ? ? ? ? class="pos-box macd-box"
? ? ? ? ? >
? ? ? ? ? ? <p>
? ? ? ? ? ? ? MACD:
? ? ? ? ? ? ? <span>{{ KHoverData[8] }}</span
? ? ? ? ? ? ? > <span class="color1"> DEA:</span>
? ? ? ? ? ? ? <span>{{ KHoverData[9] }}</span
? ? ? ? ? ? ? > <span class="color2"> DIF:</span>
? ? ? ? ? ? ? <span>{{ KHoverData[10] }}</span
? ? ? ? ? ? ? >
? ? ? ? ? ? </p>
? ? ? ? ? </div>
? ? ? ? ? <div
? ? ? ? ? ? v-if="activeIndex !== 0 && currentIndex === 3"
? ? ? ? ? ? class="pos-box macd-box"
? ? ? ? ? >
? ? ? ? ? ? <p>
? ? ? ? ? ? ? <span class="color1">K:</span>
? ? ? ? ? ? ? <span>{{ KHoverData[13] }}</span
? ? ? ? ? ? ? > <span class="color2">D:</span>
? ? ? ? ? ? ? <span>{{ KHoverData[11] }}</span
? ? ? ? ? ? ? > <span class="color3">J:</span>
? ? ? ? ? ? ? <span>{{ KHoverData[12] }}</span
? ? ? ? ? ? ? >
? ? ? ? ? ? </p>
? ? ? ? ? </div>
? ? ? ? ? <div
? ? ? ? ? ? v-if="activeIndex !== 0 && currentIndex === 4"
? ? ? ? ? ? class="pos-box macd-box"
? ? ? ? ? >
? ? ? ? ? ? <p>
? ? ? ? ? ? ? <span class="color1">RSI6:</span>
? ? ? ? ? ? ? <span>{{ KHoverData[14] }}</span
? ? ? ? ? ? ? > <span class="color2">RSI12:</span>
? ? ? ? ? ? ? <span>{{ KHoverData[15] }}</span
? ? ? ? ? ? ? > <span class="color3">RSI24:</span>
? ? ? ? ? ? ? <span>{{ KHoverData[16] }}</span
? ? ? ? ? ? ? >
? ? ? ? ? ? </p>
? ? ? ? ? </div>
? ? ? ? </div>
? ? ? </div>
? ? </div>
? </div>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import chartMin from './chartMin.vue'
import chartK from './chartk.vue'
import common from '@/utils/common'
import useWebSocket from '@/utils/useWebSocket'
import { WEBSOCKET_URL } from '@/service/config'
import { queryMinDate } from '@/service/stockIndex/index'
const props = defineProps({
? securityId: {
? ? // 證券id
? ? type: [String, Number],
? ? required: true
? },
? symbol: {
? ? // 證券代碼
? ? type: String,
? ? default: ''
? },
? market: {
? ? // 證券市場
? ? type: String,
? ? default: ''
? },
? tagIndex: {
? ? // tab索引
? ? type: Number,
? ? default: null
? }
})
const emit = defineEmits(['getKLineType'])
const minChartList = ref<any>([]) // 分時圖行情數據
const minDateList1 = ref<any>([]) // 分時圖行情數據
const kChartList = ref<any>([]) // k線圖行情數據
const prePrice = ref<any>() // 昨收價
const digit = ref(2) // 小數位
const list = ref<any>([]) // 分時圖數據
const minDateList = ref<any>([]) // 分時圖時間段
const kDateList = ref<any>([]) // K線圖時間段
const listK = ref<any>([]) // k線圖數據
const loading = ref(false) // 加載狀態
const activeIndex = ref(0) // 當前選擇的K線圖tab
const tabActive = ref(0) // 當前選擇的頂部tab
const currentIndex = ref(1) // 當前選擇的指標
const KHoverData = ref<any>([]) // k線hoverdata
const dateType = ref<any>(60) // 獲取時間段類型值
const KlineStock = ref() // K線圖websocket實例
const securityId1 = ref(props.securityId) // 證券id
const market1 = ref<any>(props.market) // 證券市場
const symbol1 = ref<any>(props.symbol) // 證券代碼
const tabList = [
? // 導航欄數據
? 'K線圖'
]
const groupList = [
? {
? ? id: 60,
? ? name: '分時圖'
? },
? {
? ? id: 1,
? ? name: '日K線'
? },
? {
? ? id: 4,
? ? name: '周K線'
? },
? {
? ? id: 7,
? ? name: '月K線'
? },
? {
? ? id: 300,
? ? name: '5分鐘'
? },
? {
? ? id: 1800,
? ? name: '30分鐘'
? },
? {
? ? id: 3600,
? ? name: '60分鐘'
? }
]
//監聽參數值重新渲染數據
watch(
? () => [props.securityId, props.market, props.symbol],
? (newVal, oldVal) => {
? ? if (newVal[0] !== oldVal[0]) {
? ? ? securityId1.value = newVal[0]
? ? }
? ? if (newVal[1] !== oldVal[1]) {
? ? ? market1.value = newVal[1]
? ? }
? ? if (newVal[2] !== oldVal[2]) {
? ? ? symbol1.value = newVal[2]
? ? }
? ? minChartList.value = []
? ? minDateList.value = []
? ? kChartList.value = []
? ? KHoverData.value = []
? ? list.value = []
? ? listK.value = []
? ? tabActive.value = 0
? ? activeIndex.value = 0
? ? currentIndex.value = 1
? ? dateType.value = 60
? ? getMinDate(securityId1.value, dateType.value)
? ? // 關閉連接
? ? closeAllSocket()
? ? // 重新建立連接
? ? webSocketInit()
? },
? { deep: true }
)
//初始化websocket
const webSocketInit = () => {
? KlineStock.value = useWebSocket({
? ? url: `${WEBSOCKET_URL}/api/web_socket/QuotationHub/Subscribe/${
? ? ? market1.value
? ? }/${securityId1.value}/${symbol1.value}/${dateType.value}`,
? ? heartBeatData: ''
? })
? KlineStock.value.connect()
}
//監聽分時圖與K線圖websocket數據推送變更
watch(
? () => KlineStock.value && KlineStock.value.message,
? (res: any) => {
? ? if (res && res.code === 200 && res.data) {
? ? ? if (activeIndex.value === 0) {
? ? ? ? // 判斷分時圖推送數據是否大于1,大于1為歷史數據,否則為最新推送數據
? ? ? ? if (JSON.parse(res.data).length > 1) {
? ? ? ? ? JSON.parse(res.data).forEach((el: any) => {
? ? ? ? ? ? // 判斷數據是否存在分時圖數據中
? ? ? ? ? ? const flag = minChartList.value.some(
? ? ? ? ? ? ? (el1: any) => el1.KData.UT === el.KData.UT
? ? ? ? ? ? )
? ? ? ? ? ? if (!flag) {
? ? ? ? ? ? ? // 不存在則push
? ? ? ? ? ? ? minChartList.value.push(el)
? ? ? ? ? ? }
? ? ? ? ? })
? ? ? ? } else {
? ? ? ? ? // 獲取時間x軸上推送過來的時間點的下標
? ? ? ? ? let i = minDateList1.value.indexOf(JSON.parse(res.data)[0].KData.UT)
? ? ? ? ? if (i > -1) {
? ? ? ? ? ? // 如果時間段小于或等于當前下標則直接push
? ? ? ? ? ? if (minChartList.value.length <= i) {
? ? ? ? ? ? ? minChartList.value.push(JSON.parse(res.data)[0])
? ? ? ? ? ? } else {
? ? ? ? ? ? ? // 如果大于則清空時間段直接賦值
? ? ? ? ? ? ? minChartList.value[i] = JSON.parse(res.data)[0]
? ? ? ? ? ? ? for (let j = i + 1; j < minChartList.value.length; j++) {
? ? ? ? ? ? ? ? minChartList.value[j] = []
? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? }
? ? ? ? }
? ? ? ? refreshMinChart(minChartList.value)
? ? ? } else {
? ? ? ? // 判斷K線圖推送數據是否大于1,大于1為歷史數據,否則為最新推送數據
? ? ? ? if (JSON.parse(res.data).length > 1) {
? ? ? ? ? JSON.parse(res.data).forEach((el: any) => {
? ? ? ? ? ? // 判斷數據是否存在K線圖數據中
? ? ? ? ? ? const flag1 = kChartList.value.some(
? ? ? ? ? ? ? (el1: any) => el1.KData.UT === el.KData.UT
? ? ? ? ? ? )
? ? ? ? ? ? if (!flag1) {
? ? ? ? ? ? ? // 不存在則push
? ? ? ? ? ? ? kChartList.value.push(el)
? ? ? ? ? ? }
? ? ? ? ? })
? ? ? ? } else {
? ? ? ? ? // 取最新數據的最后一條數據
? ? ? ? ? const arr = kChartList.value[kChartList.value.length - 1]
? ? ? ? ? // 判斷時間是否相等
? ? ? ? ? if (arr.KData && arr.KData.UT === JSON.parse(res.data)[0].KData.UT) {
? ? ? ? ? ? // 相等則刪除最后一條,更新新的一條進去
? ? ? ? ? ? kChartList.value.pop()
? ? ? ? ? ? kChartList.value.push(...JSON.parse(res.data))
? ? ? ? ? } else {
? ? ? ? ? ? // 不相等則直接push
? ? ? ? ? ? kChartList.value.push(JSON.parse(res.data)[0])
? ? ? ? ? }
? ? ? ? }
? ? ? ? refreshKChart()
? ? ? }
? ? }
? }
)
// 頂部tab欄切換點擊
const handleClick = (index: number) => {
? tabActive.value = index
? if (tabActive.value === 0) {
? ? dateType.value = 60
? ? emit('getKLineType', dateType.value)
? ? getMinDate(props.securityId, dateType.value)
? ? minChartList.value = []
? ? kChartList.value = []
? ? KHoverData.value = []
? ? // 關閉連接
? ? closeAllSocket()
? ? // 重新建立連接
? ? webSocketInit()
? }
}
// K線圖tab欄切換
const onClickItem = (item: any, index: number) => {
? dateType.value = item.id
? activeIndex.value = index
? emit('getKLineType', dateType.value)
? getMinDate(props.securityId, dateType.value)
? minChartList.value = []
? kChartList.value = []
? KHoverData.value = []
? // 關閉連接
? closeAllSocket()
? // 重新建立連接
? webSocketInit()
}
// 獲取分時圖時間段
const getMinDate = (securityId: any, type: number) => {
? loading.value = true
? securityId = securityId1.value
? type = dateType.value
? minDateList.value = []
? kDateList.value = []
? queryMinDate(securityId, type).then((res: any) => {
? ? if (res.code === 200) {
? ? ? minDateList1.value = res.data
? ? ? // 數據處理(把每一項字符串轉成數組字符串,便于后面行情數據處理—)
? ? ? res.data.map((r: any) => {
? ? ? ? const item = r.split()
? ? ? ? if (activeIndex.value === 0) {
? ? ? ? ? minDateList.value.push(toRaw(item))
? ? ? ? } else {
? ? ? ? ? kDateList.value.push(toRaw(item))
? ? ? ? }
? ? ? })
? ? } else {
? ? ? ElMessage({
? ? ? ? message: res.message,
? ? ? ? type: 'error'
? ? ? })
? ? }
? ? loading.value = false
? })
}
// 刷新分時圖
const refreshMinChart = (data: any) => {
? // 獲取L1Min分時行情
? let lstData: any[] = []
? // 折線數據[utc,cp,cr,pp,avg,ta,tv]
? data.forEach((element: any) => {
? ? const item = [
? ? ? element.KData.UT, // 時間
? ? ? element.KData.CP, // 最新價
? ? ? element.KData.Avg, // 均價
? ? ? element.KData.TV, // 總量
? ? ? element.KData.TA, // 總額
? ? ? element.KData.CR, // 漲跌幅
? ? ? element.KData.PP // 昨收
? ? ]
? ? lstData.push(item)
? })
? list.value = lstData
? prePrice.value = list.value[0][6] // 獲取昨收價確定均線位置
}
// 刷新K線圖
const refreshKChart = () => {
? let lstKData: any[] = []
? // 折線數據
? kChartList.value.forEach((element: any) => {
? ? const item = [
? ? ? element.KData.UT,
? ? ? element.KData.OP, // 開盤值
? ? ? element.KData.CP, // 收盤值
? ? ? element.KData.LP, // 最低值
? ? ? element.KData.HP, // 最高值
? ? ? element.KData.TV, // 總量
? ? ? element.KData.TA, // 總額
? ? ? element.KData.CR, // 漲跌幅
? ? ? element.KIndex.MACD, // mace
? ? ? element.KIndex.DEA, // dea
? ? ? element.KIndex.DIF, // dif
? ? ? element.KIndex.D, // d
? ? ? element.KIndex.J, // j
? ? ? element.KIndex.K, // k
? ? ? element.KIndex.RSI6, // RSI6
? ? ? element.KIndex.RSI12, // RSI12
? ? ? element.KIndex.RSI24, // RSI24
? ? ? element.KData.CG //漲跌
? ? ]
? ? lstKData.push(item)
? })
? listK.value = lstKData
}
// 獲取k線數據
const getHoverData = (data: any) => {
? KHoverData.value = data
}
// 切換指標
const choseIndex = (index: number) => {
? currentIndex.value = index
? KHoverData.value = []
}
// 大數字單位處理(小于10萬不處理)
const formatNumUnit = (value: any) => {
? return common.formatNumUnit(value)
}
const closeAllSocket = () => {
? //斷開全部websocket連接
? KlineStock.value && KlineStock.value.disconnect()
}
onMounted(() => {
? getMinDate(securityId1.value, dateType.value)
? //當前頁面刷新清空
? closeAllSocket()
? webSocketInit()
})
onUnmounted(() => {
? closeAllSocket()
})
</script>
<style lang="less" scoped>
.chart-box {
? .tab-box {
? ? width: 100%;
? ? display: flex;
? ? background-color: #ffffff;
? ? margin-top: 12px;
? ? margin-bottom: 4px;
? ? .tab-list {
? ? ? height: 100%;
? ? ? display: flex;
? ? ? .item-tab {
? ? ? ? height: 100%;
? ? ? ? padding: 0 20px;
? ? ? ? display: flex;
? ? ? ? justify-content: center;
? ? ? ? align-items: center;
? ? ? ? flex-direction: column;
? ? ? ? cursor: pointer;
? ? ? ? position: relative;
? ? ? ? &:first-child {
? ? ? ? ? padding-left: 0;
? ? ? ? }
? ? ? ? .tab {
? ? ? ? ? font-weight: normal;
? ? ? ? ? font-size: 14px;
? ? ? ? ? color: #666666;
? ? ? ? ? position: relative;
? ? ? ? }
? ? ? ? .color {
? ? ? ? ? color: #3a5bb7;
? ? ? ? ? font-weight: 600;
? ? ? ? }
? ? ? ? .line-box {
? ? ? ? ? width: 40px;
? ? ? ? ? height: 3px;
? ? ? ? ? background: #3a5bb7;
? ? ? ? ? position: absolute;
? ? ? ? ? bottom: -9px;
? ? ? ? ? border-radius: 2px 2px 0px 0px;
? ? ? ? }
? ? ? }
? ? }
? }
? .btn-options {
? ? display: flex;
? ? margin: 25px 0 5px;
? ? .btn {
? ? ? padding: 0 15px;
? ? ? height: 24px;
? ? ? background: #f4f7fc;
? ? ? border-radius: 6px;
? ? ? font-weight: 400;
? ? ? font-size: 13px;
? ? ? color: #999999;
? ? ? display: flex;
? ? ? align-items: center;
? ? ? justify-content: center;
? ? ? margin-right: 14px;
? ? ? border: 1px solid #f4f7fc;
? ? ? cursor: pointer;
? ? ? &:hover {
? ? ? ? color: #3a5bb7;
? ? ? }
? ? }
? ? .color {
? ? ? color: #3a5bb7;
? ? ? border: 1px solid #3a5bb7;
? ? ? font-weight: 500;
? ? ? background-color: #ffffff;
? ? }
? }
? .chart {
? ? width: 100%;
? ? height: 360px;
? ? margin-bottom: 16px;
? ? position: relative;
? ? .chartMin {
? ? ? width: 100%;
? ? ? height: 100%;
? ? }
? ? .indexBtn {
? ? ? width: 100%;
? ? ? position: absolute;
? ? ? left: 8%;
? ? ? top: 83.8%;
? ? ? height: 38px;
? ? ? span {
? ? ? ? width: 21%;
? ? ? ? text-align: center;
? ? ? ? display: inline-block;
? ? ? ? line-height: 25px;
? ? ? ? height: 25px;
? ? ? ? border: 1px solid #3a5bb7;
? ? ? ? color: #3a5bb7;
? ? ? ? border-right: none;
? ? ? }
? ? ? span:last-child {
? ? ? ? border-right: 1px solid #3a5bb7;
? ? ? }
? ? ? span:hover,
? ? ? .active {
? ? ? ? cursor: pointer;
? ? ? ? color: #fff;
? ? ? ? background: #3a5bb7;
? ? ? }
? ? }
? ? .pos-box {
? ? ? position: absolute;
? ? }
? ? .macd-box {
? ? ? top: 51.5%;
? ? ? left: 8%;
? ? ? color: #666666;
? ? ? font-size: 12px;
? ? }
? }
? .color1 {
? ? color: #7499e4;
? }
? .color2 {
? ? color: #ff7786;
? }
? .color3 {
? ? color: #339900;
? }
}
</style>
二、chartMin組件代碼:
<template>
? <div class="chart-area no-drag" style="position: relative">
? ? <div id="chartMinline" style="width: 100%; height: 100%" />
? ? <p
? ? ? v-if="tipData"
? ? ? :style="{ left: clientX + 'px', top: clientY + 'px' }"
? ? ? class="echart-tip"
? ? >
? ? ? <span>時間:{{ tipInfo.date }}</span
? ? ? ><br />
? ? ? <span>價格:{{ tipInfo.price }}</span
? ? ? ><br />
? ? ? <span>均價:{{ tipInfo.mittelkurs }}</span
? ? ? ><br />
? ? ? <span>漲跌幅:{{ tipInfo.change }}%</span><br />
? ? ? <span>成交量(手):{{ tipInfo.hand }}</span
? ? ? ><br />
? ? ? <span>成交額:{{ tipInfo.turnover }}</span>
? ? </p>
? </div>
</template>
<script setup lang="ts">
import * as echarts from 'echarts'
import _ from 'lodash'
import common from '@/utils/common'
import { toDecimal } from '@/utils/numberFormat'
const props = defineProps({
? id: {
? ? type: String,
? ? default: 'chartMin'
? },
? // 折線數據
? dataList: {
? ? type: Array,
? ? default: () => []
? },
? // 折線數據
? minDateList1: {
? ? type: Array,
? ? default: () => []
? },
? // 小數位數
? digit: {
? ? type: Number,
? ? default: () => 2
? },
? // 昨收價
? prePrice: {
? ? type: Number,
? ? default: 0
? }
})
var upColor = '#ec0000'
var downColor = '#00da3c'
// 定義圖表
const myChart: any = ref(null)
const minDateList = ref<any>(props.minDateList1) // 分時圖行情數據
const tipData: any = ref() // 浮框信息
const clientX = ref<any>(0) // 距離左右距離
const clientY = ref<any>(0) // 距離上下距離
const leftMax = ref<any>(0) // 左邊Y軸最大值
const leftMin = ref<any>(0) // 左邊Y軸最小值
const rightMax = ref<any>(0) // 右邊Y軸最大值
const rightMin = ref<any>(0) // 右邊Y軸最小值
const leftInterval = ref<any>(0) // 左邊分割數
const rightInterval = ref<any>(0) // 右邊分割數
const chartData = ref<any>(props.dataList) // 折線數據
const prePrice1 = ref<any>(props.prePrice) // 折線數據
// 圖表數據處理
const splitData = (rawData: any) => {
? let categoryData = []
? let allData = []
? let avgValue = []
? let totalVolumeTraded = []
? let totalValueTraded = []
? let changeRatio = []
? for (var i = 0; i < rawData.length; i++) {
? ? categoryData.push(rawData[i][0])
? ? allData.push(rawData[i])
? ? avgValue.push(rawData[i][2])
? ? totalVolumeTraded.push([i, rawData[i][3], rawData[i][5] > 0 ? 1 : -1])
? ? totalValueTraded.push(rawData[i][4])
? ? changeRatio.push(rawData[i][5])
? }
? return {
? ? categoryData,
? ? allData,
? ? avgValue,
? ? totalVolumeTraded,
? ? totalValueTraded,
? ? changeRatio
? }
}
// 使用計算屬性創建tipInfo浮框信息
const tipInfo = computed(() => {
? if (!tipData.value) {
? ? return {
? ? ? date: '--',
? ? ? price: '0.00',
? ? ? change: '0.00',
? ? ? mittelkurs: '0.00',
? ? ? hand: 0,
? ? ? turnover: 0
? ? }
? }
? const info = {
? ? date: tipData.value[0],
? ? price:
? ? ? tipData.value[1] == null
? ? ? ? ? '--'
? ? ? ? : tipData.value[1] == 0
? ? ? ? ? '0.00'
? ? ? ? : toDecimal(tipData.value[1], props.digit, true),
? ? change:
? ? ? tipData.value[5] == null
? ? ? ? ? '--'
? ? ? ? : tipData.value[5] == 0
? ? ? ? ? '0.00'
? ? ? ? : tipData.value[5] > 0
? ? ? ? ? `+${toDecimal(tipData.value[5], 2, true)}`
? ? ? ? : toDecimal(tipData.value[5], 2, true),
? ? mittelkurs:
? ? ? tipData.value[2] == null
? ? ? ? ? '--'
? ? ? ? : tipData.value[2] == 0
? ? ? ? ? '0.00'
? ? ? ? : toDecimal(tipData.value[2], props.digit, true),
? ? hand:
? ? ? tipData.value[3] == null
? ? ? ? ? '--'
? ? ? ? : tipData.value[3] == 0
? ? ? ? ? 0
? ? ? ? : common.formatNumUnit(tipData.value[3]),
? ? turnover:
? ? ? tipData.value[4] == null
? ? ? ? ? '--'
? ? ? ? : tipData.value[4] == 0
? ? ? ? ? 0
? ? ? ? : common.formatNumUnit(tipData.value[4])
? }
? return info
})
//監聽dataList變化,給圖表賦值
watch(
? () => [props.dataList, props.minDateList1, props.prePrice],
? (newValue: any, oldValue: any) => {
? ? if (newValue[0] != oldValue[0]) {
? ? ? // 更新新的圖表數據
? ? ? chartData.value = newValue[0]
? ? }
? ? if (newValue[1] != oldValue[1]) {
? ? ? // 更新新的圖表數據
? ? ? minDateList.value = newValue[1]
? ? }
? ? if (newValue[2] != oldValue[2]) {
? ? ? // 更新新的圖表數據
? ? ? prePrice1.value = newValue[2]
? ? }
? ? tipData.value = null
? ? drawLine() // 重新畫圖
? },
? { deep: true }
)
// 畫圖
const drawLine = () => {
? // 獲取最大值最小值 間隔值
? getMaxMin()
? // 使用getZr添加圖表的整個canvas區域的事件
? myChart.value.getZr().on('mouseover', handleMouseEnterMove)
? myChart.value.getZr().on('mousemove', handleMouseEnterMove)
? const chartOption = getChartOption()
? // 繪制圖表
? myChart.value.setOption(chartOption)
? window.addEventListener('resize', handleResize, false)
}
// 獲取圖表option
const getChartOption = () => {
? // 處理datalist數據
? const data = splitData(toRaw(chartData.value))
? const option = {
? ? color: ['#7499E4', '#FF7786', '#339900'],
? ? legend: {
? ? ? show: true,
? ? ? type: 'plain',
? ? ? icon: 'roundRect',
? ? ? data: ['價格', '均價']
? ? },
? ? grid: [
? ? ? {
? ? ? ? left: 60,
? ? ? ? right: 70,
? ? ? ? top: '6.4%',
? ? ? ? height: '50%'
? ? ? },
? ? ? {
? ? ? ? left: 60,
? ? ? ? right: 70,
? ? ? ? top: '68%',
? ? ? ? height: '30%'
? ? ? }
? ? ],
? ? tooltip: {
? ? ? trigger: 'axis',
? ? ? // 設置浮框不超出容器
? ? ? overflowTooltip: 'none',
? ? ? axisPointer: {
? ? ? ? type: 'line',
? ? ? ? lineStyle: {
? ? ? ? ? type: 'dotted',
? ? ? ? ? color: '#EDE4FF',
? ? ? ? ? width: 2
? ? ? ? }
? ? ? },
? ? ? formatter: function (params: any) {
? ? ? ? const param = params.find((item: any) => item.seriesName == '價格')
? ? ? ? if (param !== undefined && param.data.length > 1) {
? ? ? ? ? tipData.value = param.data
? ? ? ? } else {
? ? ? ? ? tipData.value = null
? ? ? ? }
? ? ? ? return ''
? ? ? }
? ? },
? ? axisPointer: {
? ? ? link: { xAxisIndex: 'all' }
? ? },
? ? xAxis: [
? ? ? {
? ? ? ? type: 'category',
? ? ? ? // 標簽
? ? ? ? axisLabel: {
? ? ? ? ? show: true,
? ? ? ? ? interval: 29,
? ? ? ? ? color: '#333',
? ? ? ? ? showMaxLabel: true
? ? ? ? },
? ? ? ? // 軸線樣式
? ? ? ? axisLine: {
? ? ? ? ? show: false,
? ? ? ? ? lineStyle: {
? ? ? ? ? ? color: '#EDE4FF'
? ? ? ? ? }
? ? ? ? },
? ? ? ? // 坐標軸刻度
? ? ? ? axisTick: {
? ? ? ? ? show: true
? ? ? ? },
? ? ? ? data: minDateList.value
? ? ? },
? ? ? {
? ? ? ? type: 'category',
? ? ? ? gridIndex: 1,
? ? ? ? // 標簽
? ? ? ? axisLabel: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? // 軸線樣式
? ? ? ? axisLine: {
? ? ? ? ? show: false,
? ? ? ? ? lineStyle: {
? ? ? ? ? ? color: '#EDE4FF'
? ? ? ? ? }
? ? ? ? },
? ? ? ? // 坐標軸刻度
? ? ? ? axisTick: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? data: minDateList.value
? ? ? }
? ? ],
? ? yAxis: [
? ? ? {
? ? ? ? type: 'value',
? ? ? ? gridIndex: 0,
? ? ? ? // 坐標軸刻度
? ? ? ? axisTick: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? // 標簽
? ? ? ? axisLabel: {
? ? ? ? ? interval: true,
? ? ? ? ? color: '#666',
? ? ? ? ? formatter: function (value: any) {
? ? ? ? ? ? return toDecimal(value, props.digit, true)
? ? ? ? ? }
? ? ? ? },
? ? ? ? // 軸線樣式
? ? ? ? axisLine: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? // 坐標軸在 grid 區域中的分隔線
? ? ? ? splitLine: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? min: leftMin.value,
? ? ? ? max: leftMax.value,
? ? ? ? interval: leftInterval.value
? ? ? },
? ? ? {
? ? ? ? type: 'value',
? ? ? ? gridIndex: 1,
? ? ? ? // 坐標軸刻度
? ? ? ? axisTick: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? // 標簽
? ? ? ? axisLabel: {
? ? ? ? ? interval: true,
? ? ? ? ? color: '#666',
? ? ? ? ? formatter: function (value: any) {
? ? ? ? ? ? return common.formatNumUnit(value)
? ? ? ? ? }
? ? ? ? },
? ? ? ? // 軸線樣式
? ? ? ? axisLine: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? // 坐標軸在 grid 區域中的分隔線
? ? ? ? splitLine: {
? ? ? ? ? show: false
? ? ? ? }
? ? ? },
? ? ? {
? ? ? ? type: 'value',
? ? ? ? gridIndex: 0,
? ? ? ? position: 'right',
? ? ? ? // 坐標軸刻度
? ? ? ? axisTick: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? // 標簽
? ? ? ? axisLabel: {
? ? ? ? ? interval: true,
? ? ? ? ? color: '#666',
? ? ? ? ? formatter: function (value: any) {
? ? ? ? ? ? return toDecimal(value, 2, true) + '%'
? ? ? ? ? }
? ? ? ? },
? ? ? ? // 軸線樣式
? ? ? ? axisLine: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? // 坐標軸在 grid 區域中的分隔線
? ? ? ? splitLine: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? min: rightMin.value,
? ? ? ? max: rightMax.value,
? ? ? ? interval: rightInterval.value
? ? ? }
? ? ],
? ? series: [
? ? ? {
? ? ? ? name: '價格',
? ? ? ? type: 'line',
? ? ? ? xAxisIndex: 0,
? ? ? ? yAxisIndex: 0,
? ? ? ? showSymbol: false,
? ? ? ? symbolSize: 5,
? ? ? ? smooth: true,
? ? ? ? areaStyle: {
? ? ? ? ? color: {
? ? ? ? ? ? type: 'linear',
? ? ? ? ? ? x: 0,
? ? ? ? ? ? y: 0,
? ? ? ? ? ? x2: 0,
? ? ? ? ? ? y2: 1,
? ? ? ? ? ? colorStops: [
? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? offset: 0,
? ? ? ? ? ? ? ? color: '#D8E0FF' // 0% 處的顏色
? ? ? ? ? ? ? },
? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? offset: 1,
? ? ? ? ? ? ? ? color: '#F9FAFF' // 100% 處的顏色
? ? ? ? ? ? ? }
? ? ? ? ? ? ],
? ? ? ? ? ? global: false // 缺省為 false
? ? ? ? ? }
? ? ? ? },
? ? ? ? data: data.allData,
? ? ? ? lineStyle: {
? ? ? ? ? width: 1
? ? ? ? },
? ? ? ? // 標記線
? ? ? ? markLine: {
? ? ? ? ? silent: true,
? ? ? ? ? symbol: ['none', 'none'],
? ? ? ? ? label: {
? ? ? ? ? ? show: false
? ? ? ? ? },
? ? ? ? ? lineStyle: {
? ? ? ? ? ? color: '#7b7de5',
? ? ? ? ? ? opacity: 0.5,
? ? ? ? ? ? type: 'dot'
? ? ? ? ? },
? ? ? ? ? data: [
? ? ? ? ? ? {
? ? ? ? ? ? ? name: 'Y 軸值為 yAxis 的水平線',
? ? ? ? ? ? ? yAxis: toDecimal(prePrice1.value, props.digit, true)
? ? ? ? ? ? }
? ? ? ? ? ]
? ? ? ? }
? ? ? },
? ? ? {
? ? ? ? name: '均價',
? ? ? ? type: 'line',
? ? ? ? xAxisIndex: 0,
? ? ? ? yAxisIndex: 0,
? ? ? ? showSymbol: false,
? ? ? ? smooth: true,
? ? ? ? symbolSize: 5,
? ? ? ? lineStyle: {
? ? ? ? ? width: 1
? ? ? ? },
? ? ? ? data: data.avgValue
? ? ? },
? ? ? {
? ? ? ? name: '交易量',
? ? ? ? type: 'bar',
? ? ? ? xAxisIndex: 1,
? ? ? ? yAxisIndex: 1,
? ? ? ? data: data.totalVolumeTraded,
? ? ? ? itemStyle: {
? ? ? ? ? color: function (params: any) {
? ? ? ? ? ? let colorList = ''
? ? ? ? ? ? if (params.dataIndex == 0) {
? ? ? ? ? ? ? if (data.allData[0][1] >= prePrice1.value) {
? ? ? ? ? ? ? ? colorList = upColor
? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? colorList = downColor
? ? ? ? ? ? ? }
? ? ? ? ? ? } else {
? ? ? ? ? ? ? if (
? ? ? ? ? ? ? ? data.allData[params.dataIndex][1] >=
? ? ? ? ? ? ? ? data.allData[params.dataIndex - 1][1]
? ? ? ? ? ? ? ) {
? ? ? ? ? ? ? ? colorList = upColor
? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? colorList = downColor
? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? return colorList
? ? ? ? ? }
? ? ? ? }
? ? ? }
? ? ]
? }
? return option
}
const getMaxMin = () => {
? if (chartData.value.length > 0) {
? ? const lstData = chartData.value.filter(
? ? ? (m: any) => m[1] != null && m[1] != undefined
? ? )
? ? const priceList = lstData.map(function (item: any) {
? ? ? return toDecimal(item[1], props.digit, true)
? ? })
? ? const averageList = lstData.map(function (item: any) {
? ? ? return toDecimal(item[2], props.digit, true)
? ? })
? ? const changeRatioList = lstData.map(function (item: any) {
? ? ? return toDecimal(item[5], 2, true)
? ? })
? ? // 左y軸數據
? ? var avgMax
? ? var avgMin
? ? var priceMax
? ? var priceMin = 0
? ? avgMax = getMax(averageList)
? ? avgMin = getMin(averageList)
? ? priceMax = getMax(priceList)
? ? priceMin = getMin(priceList)
? ? // 股票
? ? leftMax.value = Math.max(avgMax, priceMax)
? ? leftMin.value = avgMin == 0 ? priceMin : Math.min(avgMin, priceMin)
? ? const middleLineVal = prePrice1.value
? ? const max = common.numSub(leftMax.value, middleLineVal)
? ? const min = common.numSub(middleLineVal, leftMin.value)
? ? const absMax = Math.max(Math.abs(Number(max)), Math.abs(Number(min)))
? ? if (absMax == 0) {
? ? ? leftMax.value = common.numMul(middleLineVal, 1.05)
? ? ? leftMin.value = common.numMul(middleLineVal, 0.95)
? ? } else {
? ? ? leftMax.value = common.numAdd(middleLineVal, absMax)
? ? ? leftMin.value = common.numSub(middleLineVal, absMax)
? ? }
? ? leftInterval.value = Number(
? ? ? toDecimal(
? ? ? ? common.accDiv(common.numSub(leftMax.value, leftMin.value), 4),
? ? ? ? props.digit + 1,
? ? ? ? true
? ? ? )
? ? )
? ? // 右y軸數據
? ? rightMax.value = getMax(changeRatioList)
? ? rightMin.value = getMin(changeRatioList)
? ? const middleLineVal1 = 0
? ? const max1 = rightMax.value - middleLineVal1
? ? const min1 = middleLineVal1 - rightMin.value
? ? const absMax1 = Math.max(Math.abs(max1), Math.abs(min1))
? ? if (absMax1 == 0) {
? ? ? rightMax.value = middleLineVal1 * 1.05
? ? ? rightMin.value = middleLineVal1 * 0.95
? ? } else {
? ? ? rightMax.value = middleLineVal1 + absMax1
? ? ? rightMin.value = middleLineVal1 - absMax1
? ? }
? ? rightInterval.value = common.accDiv(
? ? ? common.numSub(rightMax.value, rightMin.value),
? ? ? 4
? ? )
? }
}
const getMax = (arr: any) => {
? const maxList = arr.filter((item: any) => item !== '-')
? let Max = 0
? if (maxList.length > 0) {
? ? const max0 = maxList[0]
? ? Max = max0
? ? maxList.forEach((item: any) => {
? ? ? if (Number(item) > Number(Max)) {
? ? ? ? Max = Number(item)
? ? ? }
? ? })
? }
? return Number(Max)
}
const getMin = (arr: any) => {
? const minList = arr.filter((item: any) => item !== '-')
? let Min = 0
? if (minList.length > 0) {
? ? const min0 = minList[0]
? ? Min = min0
? ? minList.forEach((item: any) => {
? ? ? if (Number(item) < Number(Min)) {
? ? ? ? Min = Number(item)
? ? ? }
? ? })
? }
? return Number(Min)
}
const handleResize = () => {
? myChart.value.resize()
}
const handleMouseEnterMove = (params: any) => {
? const { offsetX, offsetY, target, topTarget } = params
? clientX.value = offsetX - 40
? clientY.value = offsetY + 18
? // 移至坐標軸外時target和topTarget都為undefined
? if (!target && !topTarget) {
? ? tipData.value = null
? }
}
onMounted(() => {
? // 基于準備好的dom,初始化echarts實例
? myChart.value = markRaw(echarts.init(document.getElementById('chartMinline')))
? drawLine()
})
onUnmounted(() => {
? window.removeEventListener('resize', handleResize, false)
})
</script>
<style lang="less" scoped>
.echart-tip {
? position: absolute;
? background-color: rgba(38, 43, 81, 0.5);
? font-size: 12px;
? line-height: 16px;
? padding: 5px;
? border-radius: 4px;
? color: #fff;
? z-index: 9;
? min-width: 130px;
? > p {
? ? padding: 0;
? ? margin: 0;
? }
}
</style>
三、chartK組件代碼:
<template>
? <div
? ? class="chart-area no-drag"
? ? style="position: relative"
? ? v-loading="loading"
? >
? ? <div id="chartKline" style="width: 100%; height: 100%" />
? ? <p
? ? ? v-if="tipData"
? ? ? :style="{ left: clientX + 'px', top: clientY + 'px' }"
? ? ? class="echart-tip"
? ? >
? ? ? <span>{{ tipInfo.axisValue }}</span
? ? ? ><br />
? ? ? <span>開盤:{{ tipInfo.opening }}</span
? ? ? ><br />
? ? ? <span>收盤:{{ tipInfo.closing }}</span
? ? ? ><br />
? ? ? <span>最低:{{ tipInfo.bottommost }}</span
? ? ? ><br />
? ? ? <span>最高:{{ tipInfo.highest }}</span
? ? ? ><br />
? ? ? <span>漲跌幅:{{ tipInfo.change }}%</span><br />
? ? ? <span>成交量(手):{{ tipInfo.turnover }}</span
? ? ? ><br />
? ? ? <span>MA5:{{ tipInfo.MA5 }}</span
? ? ? ><br />
? ? ? <span>MA10:{{ tipInfo.MA10 }}</span
? ? ? ><br />
? ? ? <span>MA20:{{ tipInfo.MA20 }}</span
? ? ? ><br />
? ? ? <span>MA30:{{ tipInfo.MA30 }}</span>
? ? </p>
? </div>
</template>
<script setup lang="ts">
import * as echarts from 'echarts'
import common from '@/utils/common'
import { toDecimal } from '@/utils/numberFormat'
const props = defineProps({
? // 指標 1:成交量 2.MACD 3.KDJ
? currentIndex: {
? ? type: Number,
? ? default: 1
? },
? // 折線數據 時間 開盤價 收盤價 最低值 最高值 總量
? dataList: {
? ? type: Array,
? ? default: () => []
? },
? // 小數位數
? digit: {
? ? type: Number,
? ? default: () => 2
? },
? // 當前選擇的K線周期 1:日K 2:周K 3:月K 4:5min 5:30min 6:60min
? currentTab: {
? ? type: Number,
? ? default: () => 1
? }
})
const emit = defineEmits(['getHoverData'])
const upColor = '#ec0000'
const downColor = '#00da3c'
const ma5Color = '#39afe6'
const ma10Color = '#da6ee8'
const ma20Color = '#ffab42'
const ma30Color = '#00940b'
const color1 = '#7499E4'
const color2 = '#FF7786'
const color3 = '#339900'
const dataListTemp = ref<any>(props.dataList) // 備份dataList
const isDrawing = ref(false) // 是否展示圖表
const loading = ref(false) // 是否展示圖表
const clientX = ref<any>(0) // 距離左右距離
const clientY = ref<any>(0) // 距離上下距離
// 定義圖表
const myChart: any = ref(null)
const tipData: any = ref(null) // 浮框信息
const dataZoomY: any = ref(null) // 保存dataZoomY信息
// 圖表數據處理
const splitData = (rawData: any) => {
? const categoryData = []
? const values = []
? const volumes = []
? const MACD = []
? const DEA = []
? const DIF = []
? const D = []
? const J = []
? const K = []
? const RSI6 = []
? const RSI12 = []
? const RSI24 = []
? for (let i = 0; i < rawData.length; i++) {
? ? categoryData.push(rawData[i][0])
? ? values.push(rawData[i].slice(1))
? ? volumes.push([i, rawData[i][5], rawData[i][1] > rawData[i][2] ? 1 : -1])
? ? MACD.push([i, rawData[i][8], rawData[i][8] < 0 ? 1 : -1])
? ? DEA.push(rawData[i][9])
? ? DIF.push(rawData[i][10])
? ? D.push(rawData[i][11])
? ? J.push(rawData[i][12])
? ? K.push(rawData[i][13])
? ? RSI6.push(rawData[i][14])
? ? RSI12.push(rawData[i][15])
? ? RSI24.push(rawData[i][16])
? }
? if (rawData.length <= 70) {
? ? for (let index = 0; index < 70 - rawData.length; index++) {
? ? ? categoryData.push('')
? ? ? values.push([])
? ? ? volumes.push(['', '', ''])
? ? ? MACD.push(['', '', ''])
? ? ? DEA.push(0)
? ? ? DIF.push(0)
? ? ? D.push(0)
? ? ? J.push(0)
? ? ? K.push(0)
? ? ? RSI6.push(0)
? ? ? RSI12.push(0)
? ? ? RSI24.push(0)
? ? }
? }
? return {
? ? categoryData,
? ? values,
? ? volumes,
? ? MACD,
? ? DEA,
? ? DIF,
? ? D,
? ? J,
? ? K,
? ? RSI6,
? ? RSI12,
? ? RSI24
? }
}
// 使用計算屬性創建tipInfo浮框信息
const tipInfo = computed(() => {
? if (!tipData.value) {
? ? return {
? ? ? axisValue: '--',
? ? ? opening: '0.00',
? ? ? closing: '0.00',
? ? ? bottommost: '0.00',
? ? ? highest: '0.00',
? ? ? change: '0.00',
? ? ? turnover: 0,
? ? ? MA5: '--',
? ? ? MA10: '--',
? ? ? MA20: '--',
? ? ? MA30: '--'
? ? }
? }
? const data = tipData.value.data
? const info = {
? ? axisValue: tipData.value.axisValue,
? ? opening:
? ? ? data[1] == null
? ? ? ? ? '--'
? ? ? ? : data[1] == 0
? ? ? ? ? '0.00'
? ? ? ? : toDecimal(data[1], props.digit, true),
? ? closing:
? ? ? data[2] == null
? ? ? ? ? '--'
? ? ? ? : data[2] == 0
? ? ? ? ? '0.00'
? ? ? ? : toDecimal(data[2], props.digit, true),
? ? bottommost:
? ? ? data[3] == null
? ? ? ? ? '--'
? ? ? ? : data[3] == 0
? ? ? ? ? '0.00'
? ? ? ? : toDecimal(data[3], props.digit, true),
? ? highest:
? ? ? data[4] == null
? ? ? ? ? '--'
? ? ? ? : data[4] == 0
? ? ? ? ? '0.00'
? ? ? ? : toDecimal(data[4], props.digit, true),
? ? change:
? ? ? data[7] == null
? ? ? ? ? '--'
? ? ? ? : data[7] == 0
? ? ? ? ? '0.00'
? ? ? ? : data[7] > 0
? ? ? ? ? `+${toDecimal(data[7], props.digit, true)}`
? ? ? ? : toDecimal(data[7], props.digit, true),
? ? turnover:
? ? ? data[5] == null ? '--' : data[5] == 0 ? 0 : common.formatNumUnit(data[5]),
? ? MA5: isNaN(tipData.value.MA5) ? '--' : tipData.value.MA5,
? ? MA10: isNaN(tipData.value.MA10) ? '--' : tipData.value.MA10,
? ? MA20: isNaN(tipData.value.MA20) ? '--' : tipData.value.MA20,
? ? MA30: isNaN(tipData.value.MA30) ? '--' : tipData.value.MA30
? }
? return info
})
//監聽currentIndex與dataList變化,給圖表賦值
watch(
? () => [props.currentIndex, props.dataList, props.currentTab],
? (newValue: any, oldValue: any) => {
? ? if (newValue[0] != oldValue[0]) {
? ? ? initHoverData()
? ? ? drawLine()
? ? }
? ? if (newValue[1] != oldValue[1]) {
? ? ? dataListTemp.value = newValue[1]
? ? ? myChart.value && myChart.value.showLoading()
? ? ? initHoverData()
? ? ? drawLine()
? ? }
? ? if (newValue[2] != oldValue[2]) {
? ? ? resetChartDrawing()
? ? ? initHoverData()
? ? }
? },
? { deep: true }
)
const init = () => {
? // 基于準備好的dom,初始化echarts實例
? myChart.value = markRaw(echarts.init(document.getElementById('chartKline')))
? myChart.value.getZr().on('click', handleEchartsClick)
? // 使用getZr添加圖表的整個canvas區域的事件
? myChart.value.getZr().on('mouseover', handleMouseEnterMove)
? myChart.value.getZr().on('mousemove', handleMouseEnterMove)
? myChart.value.on('dataZoom', (event: any) => {
? ? if (event.batch) {
? ? ? event = event.batch[0]
? ? ? dataZoomY.value = event
? ? } else {
? ? ? const { dataZoomId } = event
? ? ? if (!dataZoomId) {
? ? ? ? return
? ? ? }
? ? ? dataZoomY.value = event
? ? }
? })
? initHoverData()
? drawLine()
? window.addEventListener('resize', handleResize, false)
}
const calculateMA = (dayCount: any, data: any) => {
? const result = []
? for (let i = 0, len = data.categoryData.length; i < len; i++) {
? ? if (i < dayCount - 1) {
? ? ? result.push('-')
? ? ? continue
? ? }
? ? let sum = 0
? ? for (let j = 0; j < dayCount; j++) {
? ? ? sum += Number(data.values[i - j][1])
? ? }
? ? result.push((sum / dayCount).toFixed(props.digit))
? }
? return result
}
const drawLine = () => {
? // 基于準備好的dom,初始化echarts實例
? if (isDrawing.value || !myChart.value) {
? ? setTimeout(() => {
? ? ? drawLine()
? ? })
? ? return
? }
? isDrawing.value = true
? const chartOption = getChartOption()
? // 繪制圖表
? isDrawing.value && myChart.value.setOption(chartOption, true)
? nextTick(() => {
? ? isDrawing.value = false
? ? myChart.value.hideLoading()
? })
}
// 獲取圖表option
const getChartOption = () => {
? loading.value = true
? // 處理datalist數據
? const data = splitData(dataListTemp.value)
? let dataZoomStart = getStart()
? let dataZoomEnd = 100
? if (isDrawing.value && dataZoomY.value) {
? ? const { start, end } = dataZoomY.value
? ? dataZoomStart = start
? ? dataZoomEnd = end
? }
? const option: any = {
? ? animation: false,
? ? legend: {
? ? ? // 圖例控件,點擊圖例控制哪些系列不顯示
? ? ? icon: 'rect',
? ? ? type: 'scroll',
? ? ? itemWidth: 14,
? ? ? itemHeight: 2,
? ? ? right: 30,
? ? ? top: -6,
? ? ? animation: true,
? ? ? fontSize: 12,
? ? ? color: '#999999',
? ? ? pageIconColor: '#999999',
? ? ? selectedMode: false,
? ? ? data: ['MA5', 'MA10', 'MA20', 'MA30']
? ? },
? ? color: [ma5Color, ma5Color, ma10Color, ma20Color, ma30Color],
? ? grid: [
? ? ? {
? ? ? ? left: 60,
? ? ? ? right: 30,
? ? ? ? top: '5.25%',
? ? ? ? height: '40%'
? ? ? },
? ? ? {
? ? ? ? left: 60,
? ? ? ? right: 30,
? ? ? ? top: '58%',
? ? ? ? height: '25%'
? ? ? }
? ? ],
? ? axisPointer: {
? ? ? link: { xAxisIndex: 'all' }, // 綁定兩個圖
? ? ? label: {
? ? ? ? backgroundColor: '#777'
? ? ? }
? ? },
? ? tooltip: {
? ? ? trigger: 'axis',
? ? ? axisPointer: {
? ? ? ? type: 'cross',
? ? ? ? lineStyle: {
? ? ? ? ? color: '#999',
? ? ? ? ? width: 2
? ? ? ? }
? ? ? },
? ? ? extraCssText: 'text-align: left;',
? ? ? formatter: function (params: any) {
? ? ? ? setHoverData(params)
? ? ? ? const param = params.find(
? ? ? ? ? (item: any) =>
? ? ? ? ? ? item.axisIndex === 0 && item.componentSubType === 'candlestick'
? ? ? ? )
? ? ? ? if (param && param.data && param.data.length > 1) {
? ? ? ? ? const MA5Item = params.find((item: any) => item.seriesName == 'MA5')
? ? ? ? ? const MA5 = MA5Item ? toDecimal(MA5Item.data, props.digit, true) : 0
? ? ? ? ? const MA10Item = params.find(
? ? ? ? ? ? (item: any) => item.seriesName === 'MA10'
? ? ? ? ? )
? ? ? ? ? const MA10 = MA10Item
? ? ? ? ? ? ? toDecimal(MA10Item.data, props.digit, true)
? ? ? ? ? ? : 0
? ? ? ? ? const MA20Item = params.find(
? ? ? ? ? ? (item: any) => item.seriesName === 'MA20'
? ? ? ? ? )
? ? ? ? ? const MA20 = MA20Item
? ? ? ? ? ? ? toDecimal(MA20Item.data, props.digit, true)
? ? ? ? ? ? : 0
? ? ? ? ? const MA30Item = params.find(
? ? ? ? ? ? (item: any) => item.seriesName === 'MA30'
? ? ? ? ? )
? ? ? ? ? const MA30 = MA30Item
? ? ? ? ? ? ? toDecimal(MA30Item.data, props.digit, true)
? ? ? ? ? ? : 0
? ? ? ? ? tipData.value = Object.assign({}, param, {
? ? ? ? ? ? MA5,
? ? ? ? ? ? MA10,
? ? ? ? ? ? MA20,
? ? ? ? ? ? MA30
? ? ? ? ? })
? ? ? ? } else {
? ? ? ? ? tipData.value = null
? ? ? ? }
? ? ? ? return ''
? ? ? }
? ? },
? ? xAxis: [
? ? ? {
? ? ? ? type: 'category',
? ? ? ? // 標簽
? ? ? ? axisLabel: {
? ? ? ? ? show: true,
? ? ? ? ? color: '#333'
? ? ? ? },
? ? ? ? // 軸線樣式
? ? ? ? axisLine: {
? ? ? ? ? show: false,
? ? ? ? ? lineStyle: {
? ? ? ? ? ? color: '#333'
? ? ? ? ? }
? ? ? ? },
? ? ? ? // 坐標軸刻度
? ? ? ? axisTick: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? data: data.categoryData
? ? ? },
? ? ? {
? ? ? ? type: 'category',
? ? ? ? gridIndex: 1,
? ? ? ? // 標簽
? ? ? ? axisLabel: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? // 軸線樣式
? ? ? ? axisLine: {
? ? ? ? ? show: false,
? ? ? ? ? lineStyle: {
? ? ? ? ? ? color: '#333'
? ? ? ? ? }
? ? ? ? },
? ? ? ? // 坐標軸刻度
? ? ? ? axisTick: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? // 坐標軸指示器
? ? ? ? axisPointer: {
? ? ? ? ? label: {
? ? ? ? ? ? show: false
? ? ? ? ? }
? ? ? ? },
? ? ? ? data: data.categoryData
? ? ? }
? ? ],
? ? yAxis: [
? ? ? {
? ? ? ? type: 'value',
? ? ? ? gridIndex: 0,
? ? ? ? scale: true,
? ? ? ? splitNumber: 5,
? ? ? ? // 坐標軸刻度
? ? ? ? axisTick: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? // 標簽
? ? ? ? axisLabel: {
? ? ? ? ? interval: true,
? ? ? ? ? color: '#666',
? ? ? ? ? formatter: function (value: any) {
? ? ? ? ? ? return toDecimal(value, props.digit, true)
? ? ? ? ? }
? ? ? ? },
? ? ? ? // 軸線樣式
? ? ? ? axisLine: {
? ? ? ? ? show: false
? ? ? ? }
? ? ? },
? ? ? // 交易量軸
? ? ? {
? ? ? ? type: 'value',
? ? ? ? gridIndex: 1,
? ? ? ? // y軸原點是否不從0開始
? ? ? ? scale: true,
? ? ? ? // 坐標軸刻度
? ? ? ? axisTick: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? // 標簽
? ? ? ? axisLabel: {
? ? ? ? ? interval: true,
? ? ? ? ? color: '#666',
? ? ? ? ? formatter: function (value: any) {
? ? ? ? ? ? return common.formatNumUnit(value)
? ? ? ? ? }
? ? ? ? },
? ? ? ? // 軸線樣式
? ? ? ? axisLine: {
? ? ? ? ? show: false
? ? ? ? },
? ? ? ? // 坐標軸在 grid 區域中的分隔線
? ? ? ? splitLine: {
? ? ? ? ? show: false
? ? ? ? }
? ? ? }
? ? ],
? ? series: [
? ? ? {
? ? ? ? name: 'k線',
? ? ? ? type: 'candlestick',
? ? ? ? itemStyle: {
? ? ? ? ? color: upColor,
? ? ? ? ? color0: downColor,
? ? ? ? ? borderColor: upColor,
? ? ? ? ? borderColor0: downColor
? ? ? ? },
? ? ? ? xAxisIndex: 0,
? ? ? ? yAxisIndex: 0,
? ? ? ? data: data.values,
? ? ? ? lineStyle: {
? ? ? ? ? width: 1
? ? ? ? }
? ? ? },
? ? ? {
? ? ? ? name: '交易量',
? ? ? ? type: 'bar',
? ? ? ? xAxisIndex: 1,
? ? ? ? yAxisIndex: 1,
? ? ? ? data: data.volumes,
? ? ? ? itemStyle: {
? ? ? ? ? color: function (params: any) {
? ? ? ? ? ? let colorList = ''
? ? ? ? ? ? if (params.dataIndex == 0) {
? ? ? ? ? ? ? if (data.values[0][1] >= data.values[0][0]) {
? ? ? ? ? ? ? ? colorList = upColor
? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? colorList = downColor
? ? ? ? ? ? ? }
? ? ? ? ? ? } else {
? ? ? ? ? ? ? if (
? ? ? ? ? ? ? ? data.values[params.dataIndex][1] >=
? ? ? ? ? ? ? ? data.values[params.dataIndex - 1][1]
? ? ? ? ? ? ? ) {
? ? ? ? ? ? ? ? colorList = upColor
? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? colorList = downColor
? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? return colorList
? ? ? ? ? }
? ? ? ? }
? ? ? },
? ? ? {
? ? ? ? name: 'MA5',
? ? ? ? type: 'line',
? ? ? ? data: calculateMA(5, data),
? ? ? ? smooth: true,
? ? ? ? symbol: 'none', // 隱藏選中時有小圓點
? ? ? ? lineStyle: {
? ? ? ? ? opacity: 0.8,
? ? ? ? ? color: ma5Color,
? ? ? ? ? width: 1
? ? ? ? }
? ? ? },
? ? ? {
? ? ? ? name: 'MA10',
? ? ? ? type: 'line',
? ? ? ? data: calculateMA(10, data),
? ? ? ? smooth: true,
? ? ? ? symbol: 'none',
? ? ? ? lineStyle: {
? ? ? ? ? // 標線的樣式
? ? ? ? ? opacity: 0.8,
? ? ? ? ? color: ma10Color,
? ? ? ? ? width: 1
? ? ? ? }
? ? ? },
? ? ? {
? ? ? ? name: 'MA20',
? ? ? ? type: 'line',
? ? ? ? data: calculateMA(20, data),
? ? ? ? smooth: true,
? ? ? ? symbol: 'none',
? ? ? ? lineStyle: {
? ? ? ? ? opacity: 0.8,
? ? ? ? ? width: 1,
? ? ? ? ? color: ma20Color
? ? ? ? }
? ? ? },
? ? ? {
? ? ? ? name: 'MA30',
? ? ? ? type: 'line',
? ? ? ? data: calculateMA(30, data),
? ? ? ? smooth: true,
? ? ? ? symbol: 'none',
? ? ? ? lineStyle: {
? ? ? ? ? opacity: 0.8,
? ? ? ? ? width: 1,
? ? ? ? ? color: ma30Color
? ? ? ? }
? ? ? }
? ? ],
? ? dataZoom: [
? ? ? {
? ? ? ? id: 'dataZoomX',
? ? ? ? type: 'inside',
? ? ? ? xAxisIndex: [0, 1],
? ? ? ? start: dataZoomStart,
? ? ? ? end: dataZoomEnd
? ? ? },
? ? ? {
? ? ? ? id: 'dataZoomY',
? ? ? ? show: true,
? ? ? ? xAxisIndex: [0, 1],
? ? ? ? type: 'slider',
? ? ? ? height: 20, // 設置滑動條的高度
? ? ? ? realtime: true,
? ? ? ? bottom: 7,
? ? ? ? start: dataZoomStart,
? ? ? ? end: dataZoomEnd
? ? ? }
? ? ]
? }
? if (props.currentIndex == 2) {
? ? option.series[1] = {
? ? ? name: 'MACD',
? ? ? type: 'bar',
? ? ? xAxisIndex: 1,
? ? ? yAxisIndex: 1,
? ? ? data: data.MACD,
? ? ? showSymbol: false
? ? }
? ? option.visualMap = {
? ? ? show: false,
? ? ? seriesIndex: 1,
? ? ? dimension: 2,
? ? ? pieces: [
? ? ? ? {
? ? ? ? ? value: 1,
? ? ? ? ? color: downColor
? ? ? ? },
? ? ? ? {
? ? ? ? ? value: -1,
? ? ? ? ? color: upColor
? ? ? ? }
? ? ? ]
? ? }
? ? option.series.push({
? ? ? name: 'DEA',
? ? ? type: 'line',
? ? ? xAxisIndex: 1,
? ? ? yAxisIndex: 1,
? ? ? data: data.DEA,
? ? ? showSymbol: false,
? ? ? lineStyle: {
? ? ? ? color: color1
? ? ? }
? ? })
? ? option.series.push({
? ? ? name: 'DIF',
? ? ? type: 'line',
? ? ? xAxisIndex: 1,
? ? ? yAxisIndex: 1,
? ? ? data: data.DIF,
? ? ? showSymbol: false,
? ? ? lineStyle: {
? ? ? ? color: color2
? ? ? }
? ? })
? } else if (props.currentIndex == 3) {
? ? option.series.push({
? ? ? name: 'K',
? ? ? type: 'line',
? ? ? xAxisIndex: 1,
? ? ? yAxisIndex: 1,
? ? ? data: data.K,
? ? ? showSymbol: false,
? ? ? lineStyle: {
? ? ? ? color: color1
? ? ? }
? ? })
? ? option.series[1] = {
? ? ? name: 'D',
? ? ? type: 'line',
? ? ? xAxisIndex: 1,
? ? ? yAxisIndex: 1,
? ? ? data: data.D,
? ? ? showSymbol: false,
? ? ? lineStyle: {
? ? ? ? color: color2
? ? ? }
? ? }
? ? option.series.push({
? ? ? name: 'J',
? ? ? type: 'line',
? ? ? xAxisIndex: 1,
? ? ? yAxisIndex: 1,
? ? ? data: data.J,
? ? ? showSymbol: false,
? ? ? lineStyle: {
? ? ? ? color: color3
? ? ? }
? ? })
? } else if (props.currentIndex == 4) {
? ? option.series[1] = {
? ? ? name: 'RSI6',
? ? ? type: 'line',
? ? ? xAxisIndex: 1,
? ? ? yAxisIndex: 1,
? ? ? data: data.RSI6,
? ? ? showSymbol: false,
? ? ? lineStyle: {
? ? ? ? color: color1
? ? ? }
? ? }
? ? option.series.push({
? ? ? name: 'RSI12',
? ? ? type: 'line',
? ? ? xAxisIndex: 1,
? ? ? yAxisIndex: 1,
? ? ? data: data.RSI12,
? ? ? showSymbol: false,
? ? ? lineStyle: {
? ? ? ? color: color2
? ? ? }
? ? })
? ? option.series.push({
? ? ? name: 'RSI24',
? ? ? type: 'line',
? ? ? xAxisIndex: 1,
? ? ? yAxisIndex: 1,
? ? ? data: data.RSI24,
? ? ? showSymbol: false,
? ? ? lineStyle: {
? ? ? ? color: color3
? ? ? }
? ? })
? }
? loading.value = false
? return option
}
const setHoverData = (params: any) => {
? const param = params.find(function (item: any) {
? ? return item.componentSubType == 'candlestick'
? })
? if (param !== undefined) {
? ? emit('getHoverData', param.data)
? }
}
const initHoverData = () => {
? const data: any = dataListTemp.value
? if (data.length > 0) {
? ? let arr = [
? ? ? '',
? ? ? '',
? ? ? '',
? ? ? '',
? ? ? '',
? ? ? data[data.length - 1][5],
? ? ? '',
? ? ? '',
? ? ? data[data.length - 1][8],
? ? ? data[data.length - 1][9],
? ? ? data[data.length - 1][10],
? ? ? data[data.length - 1][11],
? ? ? data[data.length - 1][12],
? ? ? data[data.length - 1][13],
? ? ? data[data.length - 1][14],
? ? ? data[data.length - 1][15],
? ? ? data[data.length - 1][16]
? ? ]
? ? emit('getHoverData', arr)
? }
}
// 獲取起始位置
const getStart = () => {
? if (dataListTemp.value && dataListTemp.value.length > 0) {
? ? const start =
? ? ? dataListTemp.value.length > 70
? ? ? ? ? 100 - (70 / dataListTemp.value.length) * 100
? ? ? ? : 0
? ? loading.value = false
? ? return start
? } else {
? ? let start = 0
? ? switch (props.currentTab) {
? ? ? case 1:
? ? ? ? start = 95
? ? ? ? break
? ? ? case 2:
? ? ? ? start = 95
? ? ? ? break
? ? ? case 3:
? ? ? ? start = 95
? ? ? ? break
? ? ? case 4:
? ? ? ? start = 95
? ? ? ? break
? ? ? case 5:
? ? ? ? start = 95
? ? ? ? break
? ? ? case 6:
? ? ? ? start = 95
? ? ? ? break
? ? ? default:
? ? ? ? start = 95
? ? }
? ? loading.value = false
? ? return start
? }
}
const resetChartDrawing = () => {
? dataZoomY.value = null
? isDrawing.value = false
? tipData.value = null
}
const handleResize = () => {
? myChart.value.resize()
}
const handleMouseEnterMove = (params: any) => {
? const { offsetX, offsetY, target, topTarget } = params
? clientX.value = offsetX - 40
? clientY.value = offsetY + 18
? // 移至坐標軸外時target和topTarget都為undefined
? if (!target && !topTarget) {
? ? tipData.value = null
? ? initHoverData()
? }
}
// 點擊事件
const handleEchartsClick = (params: any) => {
? const pointInPixel = [params.offsetX, params.offsetY]
? if (myChart.value.containPixel('grid', pointInPixel)) {
? ? const pointInGrid = myChart.value.convertFromPixel(
? ? ? {
? ? ? ? seriesIndex: 0
? ? ? },
? ? ? pointInPixel
? ? )
? ? const xIndex = pointInGrid[0] // 索引
? ? const handleIndex = Number(xIndex)
? ? const seriesObj = myChart.value.getOption() // 圖表object對象
? }
}
onMounted(() => {
? nextTick(() => {
? ? init()
? })
})
onUnmounted(() => {
? window.removeEventListener('resize', handleResize, false)
})
</script>
<style lang="less" scoped>
.echart-tip {
? position: absolute;
? background-color: rgba(38, 43, 81, 0.5);
? font-size: 12px;
? line-height: 16px;
? padding: 5px;
? border-radius: 4px;
? color: #fff;
? z-index: 9;
? min-width: 130px;
? > p {
? ? padding: 0;
? ? margin: 0;
? }
}
</style>
四、useWebSocket.ts文件代碼:
const DEFAULT_OPTIONS = {
? url: '', // websocket url
? heartBeatData: '', // 你的心跳數據
? heartBeatInterval: 60 * 1000, // 心跳間隔,單位ms
? reconnectInterval: 5000, // 斷線重連間隔,單位ms
? maxReconnectAttempts: 10 // 最大重連次數
}
export const SocketStatus = {
? Connecting: '正在連接...', //表示正在連接,這是初始狀態。
? Connected: '連接已建立', //表示連接已經建立。
? Disconnecting: '連接正在關閉', //表示連接正在關閉。
? Disconnected: '連接已斷開' //表示連接已經關閉
}
const SocketCloseCode = 1000
export default function useWebSocket(options = {}) {
? const state = {
? ? options: { ...DEFAULT_OPTIONS, ...options },
? ? socket: null,
? ? reconnectAttempts: 0,
? ? reconnectTimeout: null,
? ? heartBetaSendTimer: null, // 心跳發送定時器
? ? heartBetaTimeoutTimer: null // 心跳超時定時器
? }
? // 連接狀態
? const status = ref(SocketStatus.Disconnected)
? const message = ref(null)
? const error = ref(null)
? // 連接
? const connect = () => {
? ? disconnect()
? ? status.value = SocketStatus.Connecting
? ? if (!window.navigator.onLine) {
? ? ? setTimeout(() => {
? ? ? ? status.value = SocketStatus.Disconnected
? ? ? }, 500)
? ? ? return
? ? }
? ? //@ts-ignore
? ? state.socket = new WebSocket(state.options.url) as WebSocket
? ? //@ts-ignore
? ? state.socket.onopen = (openEvent:any) => {
? ? ? // console.log('socket連接:', openEvent)
? ? ? state.reconnectAttempts = 0
? ? ? status.value = SocketStatus.Connected
? ? ? error.value = null
? ? ? startHeartBeat()
? ? }
? ? //@ts-ignore
? ? state.socket.onmessage = (msgEvent: any) => {
? ? ? // console.log('socket消息:', msgEvent)
? ? ? // 收到任何數據,重新開始心跳
? ? ? startHeartBeat()
? ? ? const { data } = msgEvent
? ? ? const msg = JSON.parse(data)
? ? ? //心跳數據, 可自行修改
? ? ? if (+msg.msg_id === 0) {
? ? ? ? return
? ? ? }
? ? ? message.value = msg
? ? }
? ? //@ts-ignore
? ? state.socket.onclose = (closeEvent: any) => {
? ? ? // console.log('socket關閉:', closeEvent)
? ? ? status.value = SocketStatus.Disconnected
? ? ? // 非正常關閉,嘗試重連
? ? ? if (closeEvent.code !== SocketCloseCode) {
? ? ? ? reconnect()
? ? ? }
? ? }
? ? //@ts-ignore
? ? state.socket.onerror = (errEvent: any) => {
? ? ? // console.log('socket報錯:', errEvent)
? ? ? status.value = SocketStatus.Disconnected
? ? ? error.value = errEvent
? ? ? // 連接失敗,嘗試重連
? ? ? reconnect()
? ? }
? }
? const disconnect = () => {
? ? //@ts-ignore
? ? if (state.socket && (state.socket.OPEN || state.socket.CONNECTING)) {
? ? ? // console.log('socket斷開連接')
? ? ? status.value = SocketStatus.Disconnecting
? ? ? //@ts-ignore
? ? ? state.socket.onmessage = null
? ? ? //@ts-ignore
? ? ? state.socket.onerror = null
? ? ? //@ts-ignore
? ? ? state.socket.onclose = null
? ? ? // 發送關閉幀給服務端
? ? ? //@ts-ignore
? ? ? state.socket.close(SocketCloseCode, 'normal closure')
? ? ? status.value = SocketStatus.Disconnected
? ? ? state.socket = null
? ? }
? ? stopHeartBeat()
? ? stopReconnect()
? }
? const startHeartBeat = () => {
? ? stopHeartBeat()
? ? onHeartBeat(() => {
? ? ? if (status.value === SocketStatus.Connected) {
? ? ? ? //@ts-ignore
? ? ? ? state.socket.send(state.options.heartBeatData)
? ? ? ? // console.log('socket心跳發送:', state.options.heartBeatData)
? ? ? }
? ? })
? }
? const onHeartBeat = (callback: any) => {
? ? //@ts-ignore
? ? state.heartBetaSendTimer = setTimeout(() => {
? ? ? callback && callback()
? ? ? //@ts-ignore
? ? ? state.heartBetaTimeoutTimer = setTimeout(() => {
? ? ? ? // 心跳超時,直接關閉socket,拋出自定義code=4444, onclose里進行重連
? ? ? ? //@ts-ignore
? ? ? ? state.socket.close(4444, 'heart timeout')
? ? ? }, state.options.heartBeatInterval)
? ? }, state.options.heartBeatInterval)
? }
? const stopHeartBeat = () => {
? ? state.heartBetaSendTimer && clearTimeout(state.heartBetaSendTimer)
? ? state.heartBetaTimeoutTimer && clearTimeout(state.heartBetaTimeoutTimer)
? }
? // 重連
? const reconnect = () => {
? ? if (status.value === SocketStatus.Connected || status.value === SocketStatus.Connecting) {
? ? ? return
? ? }
? ? stopHeartBeat()
? ? if (state.reconnectAttempts < state.options.maxReconnectAttempts) {
? ? ? // console.log('socket重連:', state.reconnectAttempts)
? ? ? // 重連間隔,5秒起步,下次遞增1秒
? ? ? const interval = Math.max(state.options.reconnectInterval, state.reconnectAttempts * 1000)
? ? ? // console.log('間隔時間:', interval)
? ? ? //@ts-ignore
? ? ? state.reconnectTimeout = setTimeout(() => {
? ? ? ? if (status.value !== SocketStatus.Connected && status.value !== SocketStatus.Connecting) {
? ? ? ? ? connect()
? ? ? ? }
? ? ? }, interval)
? ? ? state.reconnectAttempts += 1
? ? } else {
? ? ? status.value = SocketStatus.Disconnected
? ? ? stopReconnect()
? ? }
? }
? // 停止重連
? const stopReconnect = () => {
? ? state.reconnectTimeout && clearTimeout(state.reconnectTimeout)
? }
? return {
? ? status,
? ? message,
? ? error,
? ? connect,
? ? disconnect
? }
}
五、common.ts文件代碼:
// import XLSX from 'xlsx';
import CST from './constant'
import { toDecimal } from './numberFormat'
const common = {
? addDate(date: any, days: any) {
? ? if (days == undefined || days == '') {
? ? ? days = 1
? ? }
? ? // var date = new Date(date)
? ? date.setDate(date.getDate() + days)
? ? const month = date.getMonth() + 1
? ? const day = date.getDate()
? ? return (
? ? ? date.getFullYear() +
? ? ? '/' +
? ? ? this.getFormatDate(month) +
? ? ? '/' +
? ? ? this.getFormatDate(day)
? ? )
? },
? // 小數相減精確算法
? numSub(data1: any, data2: any) {
? ? let num = 0
? ? let num1 = 0
? ? let num2 = 0
? ? let precision = 0 // 精度
? ? try {
? ? ? num1 = data1.toString().split('.')[1].length
? ? } catch (e) {
? ? ? num1 = 0
? ? }
? ? try {
? ? ? num2 = data2.toString().split('.')[1].length
? ? } catch (e) {
? ? ? num2 = 0
? ? }
? ? num = Math.pow(10, Math.max(num1, num2))
? ? precision = num1 >= num2 ? num1 : num2
? ? return ((data1 * num - data2 * num) / num).toFixed(precision)
? },
? // 日期月份/天的顯示,如果是1位數,則在前面加上'0'
? getFormatDate(arg: any) {
? ? if (arg == undefined || arg == '') {
? ? ? return ''
? ? }
? ? let re = arg + ''
? ? if (re.length < 2) {
? ? ? re = '0' + re
? ? }
? ? return re
? },
? isArray: function (obj: any) {
? ? return Object.prototype.toString.call(obj) === '[object Array]'
? },
? isEmpty(obj: any) {
? ? obj = obj + ''
? ? if (
? ? ? typeof obj === 'undefined' ||
? ? ? obj == null ||
? ? ? obj.replace(/(^\s*)|(\s*$)/g, '') === ''
? ? ) {
? ? ? return true
? ? } else {
? ? ? return false
? ? }
? },
? // 小數相加精確算法
? numAdd(arg1: any, arg2: any) {
? ? let r1 = 0
? ? let r2 = 0
? ? let r3 = 0
? ? try {
? ? ? r1 = (arg1 + '').split('.')[1].length
? ? } catch (err) {
? ? ? r1 = 0
? ? }
? ? try {
? ? ? r2 = (arg2 + '').split('.')[1].length
? ? } catch (err) {
? ? ? r2 = 0
? ? }
? ? r3 = Math.pow(10, Math.max(r1, r2))
? ? return (this.numMul(arg1, r3) + this.numMul(arg2, r3)) / r3
? },
? // 判斷小數位數
? getDecLen(value: number) {
? ? if (!value) {
? ? ? return 0
? ? }
? ? const strVal = value.toString()
? ? if (!strVal.includes('.')) {
? ? ? return 0
? ? }
? ? return strVal.split('.')[1].length
? },
? // 兩數相除
? accDiv(num1: any, num2: any) {
? ? let t1, t2
? ? try {
? ? ? t1 = num1.toString().split('.')[1].length
? ? } catch (e) {
? ? ? t1 = 0
? ? }
? ? try {
? ? ? t2 = num2.toString().split('.')[1].length
? ? } catch (e) {
? ? ? t2 = 0
? ? }
? ? const r1 = Number(num1.toString().replace('.', ''))
? ? const r2 = Number(num2.toString().replace('.', ''))
? ? return (r1 / r2) * Math.pow(10, t2 - t1)
? },
? formatDate: function (date: any, format: any) {
? ? let v = ''
? ? if (typeof date === 'string' || typeof date !== 'object') {
? ? ? return
? ? }
? ? const year = date.getFullYear()
? ? const month = date.getMonth() + 1
? ? const day = date.getDate()
? ? const hour = date.getHours()
? ? const minute = date.getMinutes()
? ? const second = date.getSeconds()
? ? const weekDay = date.getDay()
? ? const ms = date.getMilliseconds()
? ? let weekDayString = ''
? ? if (weekDay === 1) {
? ? ? weekDayString = '星期一'
? ? } else if (weekDay === 2) {
? ? ? weekDayString = '星期二'
? ? } else if (weekDay === 3) {
? ? ? weekDayString = '星期三'
? ? } else if (weekDay === 4) {
? ? ? weekDayString = '星期四'
? ? } else if (weekDay === 5) {
? ? ? weekDayString = '星期五'
? ? } else if (weekDay === 6) {
? ? ? weekDayString = '星期六'
? ? } else if (weekDay === 0) {
? ? ? weekDayString = '星期日'
? ? }
? ? v = format
? ? // Year
? ? v = v.replace(/yyyy/g, year)
? ? v = v.replace(/YYYY/g, year)
? ? v = v.replace(/yy/g, (year + '').substring(2, 4))
? ? v = v.replace(/YY/g, (year + '').substring(2, 4))
? ? // Month
? ? const monthStr = '0' + month
? ? v = v.replace(/MM/g, monthStr.substring(monthStr.length - 2))
? ? // Day
? ? const dayStr = '0' + day
? ? v = v.replace(/dd/g, dayStr.substring(dayStr.length - 2))
? ? // hour
? ? const hourStr = '0' + hour
? ? v = v.replace(/HH/g, hourStr.substring(hourStr.length - 2))
? ? v = v.replace(/hh/g, hourStr.substring(hourStr.length - 2))
? ? // minute
? ? const minuteStr = '0' + minute
? ? v = v.replace(/mm/g, minuteStr.substring(minuteStr.length - 2))
? ? // Millisecond
? ? v = v.replace(/sss/g, ms)
? ? v = v.replace(/SSS/g, ms)
? ? // second
? ? const secondStr = '0' + second
? ? v = v.replace(/ss/g, secondStr.substring(secondStr.length - 2))
? ? v = v.replace(/SS/g, secondStr.substring(secondStr.length - 2))
? ? // weekDay
? ? v = v.replace(/E/g, weekDayString)
? ? return v
? },
? /**
? ?* 判斷是否同周,輸入時間date1小于date2
? ?* @param {*} date1
? ?* @param {*} date2
? ?*/
? isSameWeek: function (date1: any, date2: any) {
? ? const day1 = new Date(date1).getDay() == 0 ? 7 : new Date(date1).getDay()
? ? const day2 = new Date(date2).getDay() == 0 ? 7 : new Date(date2).getDay()
? ? const time1 = new Date(date1).getTime()
? ? const time2 = new Date(date2).getTime()
? ? if (day1 >= day2) {
? ? ? return false
? ? } else {
? ? ? return time2 - time1 < 7 * 24 * 3600 * 1000
? ? }
? },
? getUrlKey: function (name: any) {
? ? // eslint-disable-next-line no-sparse-arrays
? ? return (
? ? ? decodeURIComponent(
? ? ? ? //@ts-ignore
? ? ? ? (new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(
? ? ? ? ? location.href
? ? ? ? ? // eslint-disable-next-line no-sparse-arrays
? ? ? ? ) || [, ''])[1].replace(/\+/g, '%20')
? ? ? ) || null
? ? )
? },
? getUrlParam: function (name: any) {
? ? const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)')
? ? const r = window.location.search.substr(1).match(reg)
? ? if (r != null) return unescape(r[2])
? ? return null
? },
? setPorpsReadonly(props: any) {
? ? for (const col in props) {
? ? ? if (props[col].Columns && common.isArray(props[col].Columns)) {
? ? ? ? props[col].require = 'false'
? ? ? ? props[col].isImport = 'false'
? ? ? ? props[col].ReadOnly = 'true'
? ? ? ? props[col].Columns.forEach((e: any) => {
? ? ? ? ? e.readonly = 'true'
? ? ? ? })
? ? ? } else {
? ? ? ? for (const co in props[col]) {
? ? ? ? ? props[col][co].readonly = 'true'
? ? ? ? }
? ? ? }
? ? }
? ? return props
? },
? // 根據表單里的 oldinstanceid 判斷是否是非首次報備的單
? isFirstFormByOldInstanceId(value: any, instanceId: any) {
? ? let isfirst = true
? ? instanceId = instanceId + ''
? ? for (const col in value) {
? ? ? if (!common.isArray(value[col])) {
? ? ? ? if (value[col].oldflowinstanceid) {
? ? ? ? ? if (
? ? ? ? ? ? value[col].oldflowinstanceid !== '' &&
? ? ? ? ? ? value[col].oldflowinstanceid !== instanceId
? ? ? ? ? ) {
? ? ? ? ? ? isfirst = false
? ? ? ? ? ? break
? ? ? ? ? }
? ? ? ? }
? ? ? }
? ? }
? ? return isfirst
? },
? setPropNotFrist(props: any) {
? ? for (const col in props) {
? ? ? // eslint-disable-next-line no-empty
? ? ? if (props[col].Columns && common.isArray(props[col].Columns)) {
? ? ? } else {
? ? ? ? for (const co in props[col]) {
? ? ? ? ? if (props[col][co].objectupdate !== 'true') {
? ? ? ? ? ? props[col][co].readonly = 'true'
? ? ? ? ? }
? ? ? ? }
? ? ? }
? ? }
? ? return props
? },
? /**
? ?* 精確乘
? ?* @param arg1
? ?* @param arg2
? ?* @returns {number}
? ?*/
? numMul(arg1: any, arg2: any) {
? ? const r1 = arg1 + ''
? ? const r2 = arg2 + ''
? ? let r3 = 0
? ? let r4 = 0
? ? try {
? ? ? r3 = r1.split('.')[1].length
? ? } catch (err) {
? ? ? r3 = 0
? ? }
? ? try {
? ? ? r4 = r2.split('.')[1].length
? ? } catch (err) {
? ? ? r4 = 0
? ? }
? ? return (
? ? ? (Number(r1.replace('.', '')) * Number(r2.replace('.', ''))) /
? ? ? Math.pow(10, r4 + r3)
? ? )
? },
? /**
? ?* 精確除
? ?* @param arg1
? ?* @param arg2
? ?* @returns {number}
? ?*/
? numDiv(arg1: any, arg2: any) {
? ? const r1 = arg1 + ''
? ? const r2 = arg2 + ''
? ? let r3 = 0
? ? let r4 = 0
? ? try {
? ? ? r3 = r1.split('.')[1].length
? ? } catch (err) {
? ? ? r3 = 0
? ? }
? ? try {
? ? ? r4 = r2.split('.')[1].length
? ? } catch (err) {
? ? ? r4 = 0
? ? }
? ? return this.numMul(
? ? ? Number(r1.replace('.', '')) / Number(r2.replace('.', '')),
? ? ? Math.pow(10, r4 - r3)
? ? )
? },
? /**
? ?* 精確取余
? ?* @param arg1
? ?* @param arg2
? ?* @returns {number}
? ?*/
? numRem(arg1: any, arg2: any) {
? ? let r1 = 0
? ? let r2 = 0
? ? let r3 = 0
? ? try {
? ? ? r1 = (arg1 + '').split('.')[1].length
? ? } catch (err) {
? ? ? r1 = 0
? ? }
? ? try {
? ? ? r2 = (arg2 + '').split('.')[1].length
? ? } catch (err) {
? ? ? r2 = 0
? ? }
? ? r3 = Math.pow(10, Math.max(r1, r2))
? ? return (this.numMul(arg1, r3) % this.numMul(arg2, r3)) / r3
? },
? formatNumUnit(value_: any) {
? ? const value = Math.abs(value_) // 1
? ? const newValue = ['', '', '']
? ? let fr = 1000
? ? let num = 3
? ? let fm = 1
? ? while (value / fr >= 1) {
? ? ? fr *= 10
? ? ? num += 1
? ? }
? ? if (num <= 4) {
? ? ? // 千
? ? ? newValue[0] = value + ''
? ? } else if (num <= 8) {
? ? ? // 萬
? ? ? fm = 10000
? ? ? if (value % fm === 0) {
? ? ? ? //@ts-ignore
? ? ? ? newValue[0] = parseInt(value / fm) + ''
? ? ? } else {
? ? ? ? //@ts-ignore
? ? ? ? newValue[0] = parseFloat(value / fm).toFixed(2) + ''
? ? ? }
? ? ? // newValue[1] = text1
? ? ? newValue[1] = '萬'
? ? } else if (num <= 16) {
? ? ? // 億
? ? ? fm = 100000000
? ? ? if (value % fm === 0) {
? ? ? ? //@ts-ignore
? ? ? ? newValue[0] = parseInt(value / fm) + ''
? ? ? } else {
? ? ? ? //@ts-ignore
? ? ? ? newValue[0] = parseFloat(value / fm).toFixed(2) + ''
? ? ? }
? ? ? newValue[1] = '億'
? ? }
? ? if (value < 1000) {
? ? ? newValue[0] = value + ''
? ? ? newValue[1] = ''
? ? }
? ? let text = newValue.join('')
? ? if (value_ < 0) {
? ? ? text = '-' + text
? ? }
? ? return text
? },
? // 獲取行情小數位數(最新價、漲跌、買價、賣價)
? getTickDecLen(securityType: any, market: any, plateID: any) {
? ? // 滬深A股 -> 2
? ? if (securityType == CST.SecurityType.Stock) {
? ? ? return 2
? ? }
? ? // 基金 -> 3
? ? if (securityType == CST.SecurityType.Fund) {
? ? ? return 3
? ? }
? ? // 債券 -> 上海市場除國債逆回購,小數點后保留2位小數,國債逆回購3位小數;深圳市場保留3位小數
? ? if (securityType == CST.SecurityType.Bond) {
? ? ? // 深圳市場
? ? ? if (market == CST.Market.SZSE) {
? ? ? ? return 3
? ? ? }
? ? ? // 上海市場
? ? ? if (market == CST.Market.SSE) {
? ? ? ? // 國債逆回購
? ? ? ? if (plateID == CST.PlateID.ZQHG_Bond) {
? ? ? ? ? return 3
? ? ? ? }
? ? ? ? return 3
? ? ? }
? ? }
? ? return 2
? },
? // 轉換成交量單位
? cvtVolumeUnit(volume: any, market: any, securityType: any) {
? ? // 深圳市場
? ? if (market == CST.Market.SZSE) {
? ? ? // 股票、基金、指數
? ? ? if (
? ? ? ? securityType == CST.SecurityType.Stock ||
? ? ? ? securityType == CST.SecurityType.Fund ||
? ? ? ? securityType == CST.SecurityType.Index
? ? ? ) {
? ? ? ? return volume / 100
? ? ? }
? ? ? // 債券
? ? ? if (securityType == CST.SecurityType.Bond) {
? ? ? ? return volume / 10
? ? ? }
? ? }
? ? // 上海市場
? ? if (market == CST.Market.SSE) {
? ? ? // 股票、基金、指數
? ? ? if (
? ? ? ? securityType == CST.SecurityType.Stock ||
? ? ? ? securityType == CST.SecurityType.Fund
? ? ? ) {
? ? ? ? return volume / 100
? ? ? }
? ? }
? ? // 北交所
? ? if (market == CST.Market.BSE) {
? ? ? // 北交所暫不做處理,后臺轉換
? ? ? return volume
? ? }
? ? return volume
? },
? // 千分位 保留digit位 isround四舍五入
? money(value: any, digit = 2, isRround = true) {
? ? let v = toDecimal(value, digit, isRround)
? ? if (v.indexOf(',') == -1) {
? ? ? v = v.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,')
? ? }
? ? return v
? },
? //校驗輸入是否僅包含數字和字母
? isValidAlphanumeric(input: string) {
? ? const alphanumericPattern = /^[a-zA-Z0-9]+$/
? ? return alphanumericPattern.test(input)
? },
? //長度至少為6個字符,必須包含大寫字母、小寫字母、數字,不能包含特殊字符和漢字
? isValidPassword(password: string) {
? ? const passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{6,}$/
? ? return passwordPattern.test(password)
? },
? //驗證手機號碼
? isValidPhoneNumber(phoneNumber: string) {
? ? const phonePattern = /^1[3-9]\d{9}$/
? ? return phonePattern.test(phoneNumber)
? },
? //中文姓名,不超過5個漢字,不包含任何特殊字符或數字
? isValidChineseName(name: string) {
? ? const namePattern = /^[\u4e00-\u9fff]{1,5}$/
? ? return namePattern.test(name)
? }
}
export default common