vue3+echarts+websocket分时图与K线图实时推送

news2024/12/12 15:49:07

一、父组件代码:

<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

              >&nbsp;&nbsp; <span class="color1"> DEA:</span>

              <span>{{ KHoverData[9] }}</span

              >&nbsp;&nbsp; <span class="color2"> DIF:</span>

              <span>{{ KHoverData[10] }}</span

              >&nbsp;&nbsp;

            </p>

          </div>

          <div

            v-if="activeIndex !== 0 && currentIndex === 3"

            class="pos-box macd-box"

          >

            <p>

              <span class="color1">K:</span>

              <span>{{ KHoverData[13] }}</span

              >&nbsp;&nbsp; <span class="color2">D:</span>

              <span>{{ KHoverData[11] }}</span

              >&nbsp;&nbsp; <span class="color3">J:</span>

              <span>{{ KHoverData[12] }}</span

              >&nbsp;&nbsp;

            </p>

          </div>

          <div

            v-if="activeIndex !== 0 && currentIndex === 4"

            class="pos-box macd-box"

          >

            <p>

              <span class="color1">RSI6:</span>

              <span>{{ KHoverData[14] }}</span

              >&nbsp;&nbsp; <span class="color2">RSI12:</span>

              <span>{{ KHoverData[15] }}</span

              >&nbsp;&nbsp; <span class="color3">RSI24:</span>

              <span>{{ KHoverData[16] }}</span

              >&nbsp;&nbsp;

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2258310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大模型简单实践

大模型简单实践 最近参加了Datawhale AI冬令营&#xff08;第一期&#xff09;的活动 网站链接 手把手教学&#xff0c;借助Chat-嬛嬛 搭上讯飞星辰MaaS平台&#xff0c;快速训练处一个可以自由聊天的对话机器人。

Linux的基本功能和命令

Linux的基本功能和命令 切换目录 pwd 查询当前目录地址 cd /xxx/xxx 转到目录 cd …/ 回到上一级目录 cd ./ 当前目录 创建、删除文件/文件夹 创建文件\文件夹 touch filename 创建空文件mkdir 创建目录 mkdir -p 目标目录存在也不报错mkdir -p xxx/xxx 递归创建目录…

LLC谐振变换器的工作模态分析

概述 LLC谐振变换器在传统串联LC和并联LC谐振变换器的基础之上进行改进&#xff0c;既有LC串联谐振变换器谐振电容所起到的隔直作用和谐振网络电流随负载轻重而变化&#xff0c;轻载时效率较高的优点。同时又有LC并联谐振变化器可以在空载条件下&#xff0c;对滤波电容的电流脉…

Goby AI 2.0 自动化编写 EXP | Mitel MiCollab 企业协作平台 npm-pwg 任意文件读取漏洞(CVE-2024-41713)

漏洞名称&#xff1a;Mitel MiCollab 企业协作平台 npm-pwg 任意文件读取漏洞(CVE-2024-41713) English Name&#xff1a;Mitel MiCollab /npm-pwg File Read Vulnerability (CVE-2024-41713) CVSS core: 6.8 漏洞描述&#xff1a; Mitel MiCollab 是加拿大 Mitel 公司推出…

视频安防监控平台:Liveweb视频监控管理云平台方案

LiveWeb是深圳市好游科技有限公司开发的一套综合视频汇聚管理平台&#xff0c;可提供多协议&#xff08;RTSP/RTMP/GB28181/海康Ehome/大华&#xff0c;海康SDK等&#xff09;的视频设备接入&#xff0c;支持GB/T28181上下级联&#xff0c;RTSP\RTMP转GB/T28181&#xff0c;云台…

ip地址暴露了怎么办?手机怎样改ip地址以保障安全

在数字化时代,IP地址作为我们连接互联网的“身份证”,其安全性至关重要。然而,有时我们的IP地址可能会因各种原因暴露,从而引发隐私泄露、网络攻击等风险。本文将为您详细解析IP地址暴露后的应对措施,特别是针对手机用户,提供实用的更改IP地址方法,帮助您有效保障网络安…

组合分支预测

前言 这篇文章讨论了几种分支预测的实现方式。具体内容如下&#xff1a; 内容 introduction 这篇文章只考虑预测分支跳转方向&#xff0c;不讨论跳转的目标地址。 Bimodal Branch Prediction 分支行为的特点&#xff1a;大多数程序中的分支指令并不是随机的&#xff0c;通…

爬虫基础之代理的基本原理

在做爬虫的过程中经常会遇到一种情况&#xff0c;就是爬虫最初是正常运行、正常抓取数据的&#xff0c;一切看起来都是那么美好&#xff0c;然而一杯茶的工夫就出现了错误&#xff0c;例如 403 Forbidden&#xff0c;这时打开网页一看&#xff0c;可能会看到“您的IP访问频率太…

数据结构——对顶堆

对顶堆 由一个大根堆和一个小根堆组成&#xff0c;小根堆里面的数永远比大根堆里面的数要大 用途&#xff1a;用于动态维护区间内第k大的数&#xff0c;要比线段树和动态平衡树写起来更简单 比如说我们要维护第k大的数&#xff0c;那么我们肯定是将前k大的数放进小根堆&#…

设计模式之原型模式:深入浅出讲解对象克隆

~犬&#x1f4f0;余~ “我欲贱而贵&#xff0c;愚而智&#xff0c;贫而富&#xff0c;可乎&#xff1f; 曰&#xff1a;其唯学乎” 原型模式概述 在我们的日常生活中&#xff0c;经常会遇到"复制"这样的场景。比如我们在准备文件时&#xff0c;常常会复印一份原件&a…

Elasticsearch Serverless 中的数据流自动分片

作者&#xff1a;来自 Elastic Andrei Dan 在 Elastic Cloud Serverless 中&#xff0c;我们根据索引负载自动为数据流配置最佳分片数量&#xff0c;从而使用户无需摆弄分片。 传统上&#xff0c;用户会更改数据流的分片配置&#xff0c;以处理各种工作负载并充分利用可用资源。…

【Golang】Go语言编程思想(六):Channel,第四节,Select

使用 Select 如果此时我们有多个 channel&#xff0c;我们想从多个 channel 接收数据&#xff0c;谁来的快先输出谁&#xff0c;此时应该怎么做呢&#xff1f;答案是使用 select&#xff1a; package mainimport "fmt"func main() {var c1, c2 chan int // c1 and …

MindSearch深度解析实践

任务要求&#xff1a;在 官方的MindSearch页面 复制Spaces应用到自己的Spaces下&#xff0c;Space 名称中需要包含 MindSearch 关键词&#xff0c;请在必要的步骤以及成功的对话测试结果当中 1.在github codespace中配置环境 conda create -n mindsearch python3.10 -y conda…

【PyQt5教程 二】Qt Designer 信号与槽的使用方法及PyQt5基本小部件说明

目录 一、信号与槽机制&#xff1a; 二、信号与槽使用方法&#xff1a; &#xff08;1&#xff09;使用Qt Designer 的信号与槽编辑器&#xff1a; &#xff08;2&#xff09;使用固定语法直接建立信号槽连接&#xff1a; 三、PyQt小部件及其触发信号&#xff1a; &#x…

基于PHP课堂签到系统的设计与实现

摘 要 随着教育业的迅速发展和学生人数的不断增加&#xff0c;导致在班级登记制度中传统的“点到”方式不能适应学校的实际需要。从而需要设计一个好的课堂签到系统将会对课堂签到管理工作带来事半功倍的效果。文章着重介绍了基于实践应用的班级签到系统的开发流程&#xff0c…

CSS学习记录11

CSS布局 - display属性 display属性是用于控制布局的最终要的CSS属性。display 属性规定是否/如何显示元素。每个HTML元素都有一个默认的display值&#xff0c;具体取决于它的元素类型。大多数元素的默认display值为block 或 inline。 块级元素&#xff08;block element&…

高效利用资源:分布式有状态服务的高可靠性设计

在分布式系统设计中&#xff0c;实现有状态服务的高可靠性通常采用主备切换的方式。当主服务停止工作时&#xff0c;备服务接管任务&#xff0c;例如通过Keepalive实现VIP的切换以保证可用性。然而&#xff0c;这种方式存在资源浪费的问题&#xff0c;因为备服务始终处于空转状…

重生之我在异世界学智力题(2)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言智力题&#xff1a;逃离孤岛智力题&a…

论文浅尝 | SAC-KG:利用大语言模型作为领域知识图谱熟练的自动化构造器(ACL2024)...

笔记整理&#xff1a;杜超超&#xff0c;天津大学硕士&#xff0c;研究方向为自然语言处理、大语言模型 论文链接&#xff1a;https://aclanthology.org/2024.acl-long.238/ 发表会议&#xff1a;ACL 2024 1. 动机 知识图谱&#xff08;KG&#xff09;在各个专业领域的知识密集…

Python机器视觉的学习

一、二值化 1.1 二值化图 二值化图&#xff1a;就是将图像中的像素改成只有两种值&#xff0c;其操作的图像必须是灰度图。 1.2 阈值法 阈值法&#xff08;Thresholding&#xff09;是一种图像分割技术&#xff0c;旨在根据像素的灰度值或颜色值将图像分成不同的区域。该方法…