vue3+echarts+websocket分时图与K线图实时推送
一、父组件代码:
<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
相关文章:
vue3+echarts+websocket分时图与K线图实时推送
一、父组件代码: <template> <div class"chart-box" v-loading"loading"> <!-- tab导航栏 --> <div class"tab-box"> <div class"tab-list"> <div v-for"(item, index) in tabList…...
Maven常用插件清单
Maven 是一个强大的项目管理和构建工具,它使用插件来执行各种构建生命周期任务。以下是常用的一些 Maven 构建插件及其主要用途: 1. Maven Compiler Plugin 用途:编译Java源代码。配置示例:<build><plugins><plu…...
敏捷项目管理
虽然一直在践行敏捷开发,包括站会、尝试使用Confluence做知识wiki、每周分享等等,当然项目日常使用的coding、禅道、jira 项目管理和 jenkins 、git之类的CICD工具更不必说,但确实没有系统地去学习过敏捷开发管理。昨天被人问到敏捷开发到底是…...
牛客小白月赛107(A~E)
文章目录 A Cidoai的吃饭思路code B Cidoai的听歌思路code C Cidoai的植物思路code D Cidoai的猫猫思路code E Cidoai的可乐思路code 牛客小白月赛107 A Cidoai的吃饭 思路 签到题,按题意模拟即可 code void solve(){int n,a,b,c;cin >> n >> a &g…...
【传感器技术】第6章 压电式传感器,压电材料,压电效应,电压放大器
关注作者了解更多 我的其他CSDN专栏 过程控制系统 工程测试技术 虚拟仪器技术 可编程控制器 工业现场总线 数字图像处理 智能控制 传感器技术 嵌入式系统 复变函数与积分变换 单片机原理 线性代数 大学物理 热工与工程流体力学 数字信号处理 光电融合集成电路…...
基于Python深度学习的【猫狗宠物识别】系统设计实现
一、简介 宠物识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了37种常见的猫狗宠物种类数据集【‘阿比西尼亚猫(Abyssinian)’, ‘孟加拉猫(Bengal)’…...
网站多语言前端翻译translate.js 在vue项目中的使用方法
网站多语言前端翻译translate.js 在vue项目中的使用方法 需求 客户网站,想要多语言版本的,通常的解决办法有两种: 1、最直接的办法:编写两种,或者多种语言版本的网站,也就是一个网站有几种语言࿰…...
HTML技术贴:深入理解与实践
1. 引言 HTML(HyperText Markup Language,超文本标记语言)是构建网页和网上应用的标准标记语言。它定义了网页内容的结构和意义,由一系列元素组成,这些元素告诉浏览器如何展示内容。本技术贴旨在深入探讨HTML的核心技…...
在SQL Server中使用hash join来提高表连接的性能
在SQL Server中使用hash join来提高表连接性能时,需要考虑数据集的大小、索引情况以及查询的具体需求。 在SQL Server中使用hash join来提高表连接性能的情况主要包括以下几种: • 两个表都没有合适的索引:Hash join通常适合当两个表都没有索…...
一键学懂BurpSuite(8)
声明! 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团队无关&#…...
系统思考—战略决策
别用管理上的勤奋,来掩盖经营上的懒惰。 日本一家物业公司,因经营不善,面临生死存亡的危机。老板为了扭转局面,采取了很多管理手段——提高员工积极性,推行业绩与绩效挂钩,实施各种考核制度。然而…...
mybatis,mysql之collection 与 association 不生效问题(仅是个人常犯错误)
因为自己马大哈,经常犯这个错误,原以为是拷贝的代码问题,最后发现是自己的遗漏问题! 看代码,先看不生效的 <resultMap type"Price" id"PriceResult"><id property"priceId&qu…...
探索 Echarts 绘图:数据可视化的奇妙之旅
目录 一、Echarts 初印象 二、搭建 Echarts 绘图环境 三、绘制第一个图表:柱状图的诞生 四、图表的美化与定制:让数据更具吸引力 1. 主题切换:一键变换风格 2. 颜色调整:色彩搭配的艺术 3. 标签与提示框:丰富信…...
Python粉色圣诞树
系列文章 序号直达链接表白系列1Python制作一个无法拒绝的表白界面2Python满屏飘字表白代码3Python无限弹窗满屏表白代码4Python李峋同款可写字版跳动的爱心5Python流星雨代码6Python漂浮爱心代码7Python爱心光波代码8Python普通的玫瑰花代码9Python炫酷的玫瑰花代码10Python多…...
JavaScript逆向时,常用的11个hook
提示:记录工作中遇到的需求及解决办法 文章目录 前言01、dom操作02、Cookie操作03、事件监听操作04、AJAX拦截操作05、函数替换操作06、Header操作07、URL操作08、JSON.stringify操作09、JSON.parse操作10、eval操作11、Function操作前言 在逆向分析JavaScript代码时,开发者…...
嵌入式驱动开发详解15(电容触摸屏gt9147)
文章目录 前言电容触摸屏特点MT触摸消息电容触摸屏协议电容屏触摸时序Type A 触摸点信息上报时序Type B 触摸点信息上报时序 多点触摸所使用到的API函数 驱动部分驱动框图设备树节点修改设备树引脚配置设备节点配置 具体驱动开发I2C驱动框架I2C框架内部实现 参考文献 前言 随着…...
supervision - 好用的计算机视觉 AI 工具库
Supervision库是一款出色的Python计算机视觉低代码工具,其设计初衷在于为用户提供一个便捷且高效的接口,用以处理数据集以及直观地展示检测结果。简化了对象检测、分类、标注、跟踪等计算机视觉的开发流程。开发者仅需加载数据集和模型,就能轻…...
软件安装不成功,一直出现“chrome_elf.dll丢失”问题是什么原因?“chrome_elf.dll丢失”要怎么解决和预防?
软件安装遇阻:“chrome_elf.dll丢失”问题全解析与解决方案 在软件安装与运行的过程中,我们时常会遇到各式各样的错误提示,其中“chrome_elf.dll丢失”便是较为常见的一种。这个错误不仅阻碍了软件的正常安装,也给用户带来了不小…...
10篇--图像噪点消除
概念 何为噪点? 噪点:指图像收到的一些干扰因素,通常是由图像采集设备、传输信道等因素造成的,表现为图像中随机的亮度,也可以理解为有那么一些点的像素值与周围的像素值格格不入。 常见的噪声类型 高斯噪声&#…...
在 Vue 2 中,在 <el-table> 中为每一行动态插入对应的 echart 组件
更新数据结构:确保每一行数据都包含需要绘制图表的数据(例如 demandRespList 和 timeList),以便为每行生成不同的图表。 修改 getTableDataPreview 方法:在获取数据后,您需要为每一行创建对应的图表配置。 在 <el-table-column> 中使用 slot-scope:使用 slot-scop…...
protobuf c++开发快速上手指南
1、环境准备 在c环境使用 protobuf,需要安装protobuf runtime以及protobuf的编译器:protoc,其作用如下表格: 需要安装的环境作用protoc将proto文件编译成c源码protobuf runtime编译c源码需要链接到protobuf库 注意:…...
【HTML】HTML动画时钟
今天分享一个比较有趣的HTML动画时钟,感兴趣的小伙伴可以自行上手体验一番,操作也非常简单,如下: 1. 实操 实践操作步骤: 创建一个文本文件 clock.txt将上述代码粘贴到 clock.txt 中。修改文件后缀,将文…...
2024年全国仿真创新应用大赛 | MWORKS助力“复杂系统数字仿真”赛道,获奖名单公布
2024年全国仿真创新应用大赛全国总决赛于近日圆满落幕。大赛由工业和信息化部人才交流中心主办,以“创新引领,铸就未来”为主题,来自全国的参赛院校、企业、医学科学单位、军事科学单位及仿真领域的科研院所共计422家、近1300余人参加了此次总…...
ionic V6 安装ios所需
npm install capacitor/ios添加ios平台 ruby要求3.0以上 rvm use ruby-3.1.0 --default npx cap add ios打开xcode看看创建的项目 npx cap open ios没有capacitor指定的位置, 估计之前pod(cocoapods)安装搞得Ruby环境很乱了......cocoapods整的我麻了... App/App/capacitor…...
Docker Compose 多应用部署 一键部署
介绍 Docker Compose通过一个单独的docker-compose.yml模板文件(YAML格式)来定义一组相关联的应用容器,帮助我们实现多个相互关联的Docker容器的快速部署。 如:springbootmysqlnginx 如果一个个去部署他会非常的麻烦,这时候可以选择Docker …...
ubuntu20.04安装qt creator
以上三种,选择其一安装即可 回答1: 您可以按照以下步骤在ubuntu 20.04上安装Qt Creator: 打开终端并输入以下命令以更新软件包列表: sudo apt update 安装Qt Creator和Qt库: sudo apt install qtcreator qt5-def…...
经典NLP案例 | 推文评论情绪分析:从数据预处理到模型构建的全面指南
NLP经典案例:推文评论情绪提取 项目背景 “My ridiculous dog is amazing.” [sentiment: positive] 由于所有推文每秒都在传播,很难判断特定推文背后的情绪是否会影响一家公司或一个人的品牌,因为它的病毒式传播(积极࿰…...
蓝卓生态说 | 捷创技术李恺和:把精细管理和精益生产做到极致
成功的产品离不开开放式创新和生态协同的力量。近年来,蓝卓坚持“平台生态"战略,不断加码生态,提出三个层次的开源开放生态计划,举办"春风行动”、“生态沙龙"等系列活动,与生态伙伴共生、共创、共同推…...
启发式搜索算法和优化算法的区别
启发式搜索算法和优化算法在计算机科学中都有广泛的应用,但它们之间存在一些明显的区别。 一、定义与核心思想 启发式搜索算法 定义:启发式搜索算法是一类基于经验和直觉的问题求解方法,通过观察问题的特点,并根据某种指…...
生成树协议STP工作步骤
第一步:选择根桥 优先级比较:首先比较优先级,优先级值越小的是根桥MAC地址比较:如果优先级相同,则比较MAC地址。MAC地址小的是根桥。 MAC地址比较的时候从左往右,一位一位去比 第二步:所有非根…...
批量合并多个Excel到一个文件
工作中,我们经常需要将多个Excel的数据进行合并,很多插件都可以做这个功能。但是今天我们将介绍一个完全免费的独立软件【非插件】,来更加方便的实现这个功能。 准备Excel 这里我们准备了两张待合并的Excel文件 的卢易表 打开的卢易表软件…...
如何在vue中实现父子通信
1.需要用到的组件 父组件 <template><div id"app"><BaseCount :count"count" changeCount"cahngeCount"></BaseCount></div> </template><script> import BaseCount from ./components/BaseCount.v…...
强化学习Q-learning及其在机器人路径规划系统中的应用研究,matlab代码
一、Q-learning 算法概述 Q-learning 是一种无模型的强化学习算法,它允许智能体(agent)在没有环境模型的情况下通过与环境的交互来学习最优策略。Q-learning的核心是学习一个动作价值函数(Q-function),该函…...
【算法】EWMA指数加权移动平均绘制平滑曲线
EWMA(Exponentially Weighted Moving Average,指数加权移动平均)是一种常用的时间序列平滑技术,特别适用于对过去数据给予不同的权重。以下是对EWMA算法的详细介绍: 一、核心思想 EWMA算法的核心思想是通过指数衰减来…...
jenkins harbor安装
Harbor是一个企业级Docker镜像仓库。 文章目录 1. 什么是Docker私有仓库2. Docker有哪些私有仓库3. Harbor简介4. Harbor安装 1. 什么是Docker私有仓库 Docker私有仓库是用于存储和管理Docker镜像的私有存储库。Docker默认会有一个公共的仓库Docker Hub,而与Dock…...
行为树详解(4)——节点参数配置化
【分析】 行为树是否足够灵活强大依赖于足够丰富的各类条件节点和动作节点,在实现这些节点时,不可避免的,节点本身需要有一些参数供配置。 这些参数可以分为静态的固定值的参数以及动态读取设置的参数。 静态参数直接设置为Public即可&…...
在数字孪生开发领域threejs现在的最新版本已经更新到多少了?
在数字孪生开发领域three.js现在的最新版本已经更新到多少了? 在数字孪生开发领域,three.js作为一款强大的JavaScript 3D库,广泛应用于Web3D可视化、智慧城市、智慧园区、数字孪生等多个领域。随着技术的不断进步和需求的日益增长࿰…...
UE材质常用节点
Desaturation 去色 饱和度控制 Panner 贴图流动 快捷键P Append 附加 合并 TexCoord UV平铺大小 快捷键U CustomRotator 旋转贴图 Power 幂 色阶 Mask 遮罩 Lerp 线性插值 快捷键L Abs 绝对值 Sin / Cos 正弦/余弦 Saturate 约束在0-1之间 Add 相加 快捷键A Subtra…...
burp(2)利用java安装burpsuite
BurpSuite安装 burpsuite 2024.10专业版,已经内置java环境,可以直接使用, 支持Windows linux macOS!!! 内置jre环境,无需安装java即可使用!!! bp2024.10下载…...
33.攻防世界upload1
进入场景 看看让上传什么类型的文件 传个木马 把txt后缀改为png 在bp里把png改为php 上传成功 用蚁剑连接 在里面找flag 得到...
17、ConvMixer模型原理及其PyTorch逐行实现
文章目录 1. 重点2. 思维导图 1. 重点 patch embedding : 将图形分割成不重叠的块作为图片样本特征depth wise point wise new conv2d : 将传统的卷积转换成通道隔离卷积和像素空间隔离两个部分,在保证精度下降不多的情况下大大减少参数量 2. 思维导图 后续再整…...
【软件工程】一篇入门UML建模图(状态图、活动图、构件图、部署图)
🌈 个人主页:十二月的猫-CSDN博客 🔥 系列专栏: 🏀软件开发必练内功_十二月的猫的博客-CSDN博客 💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前…...
C# winfrom 异步加载数据不影响窗体UI
文章目录 前言一、背景介绍二、使用BackgroundWorker组件实现异步加载数据2.1 添加BackgroundWorker组件2.2 处理DoWork事件 三、延伸内容3.1 错误处理和进度报告3.2 线程安全 结束语优质源码分享 C# winfrom 异步加载数据不影响窗体UI,在 WinForms 应用程序中&…...
Flutter Navigator2.0的原理和Web端实践
01 背景与动机 在Navigator 2.0推出之前,Flutter主要通过Navigator 1.0和其提供的 API(如push(), pop(), pushNamed()等)来管理页面路由。然而,Navigator 1.0存在一些局限性,如难以实现复杂的页面操作(如移…...
latex设置引用顺序
在 LaTeX 中,引用的顺序通常是由所选择的 参考文献样式(bibliographystyle) 决定的。如果你希望根据引用的顺序排列参考文献,可以选择合适的参考文献样式,并按照以下步骤进行设置。 常见的几种引用顺序设置方式有&…...
有效的括号(字节面试题 最优解)
题目来源 20. 有效的括号 - 力扣(LeetCode) 题目描述 给定一个只包括 (,),{,},[,] 的字符串 s ,判断字符串是否有效。 有效字符串需满足: 左括号必须用相同类型的右括号…...
短视频矩阵源码开发部署全流程解析
在当今的数字化时代,短视频已成为人们娱乐、学习和社交的重要方式。短视频矩阵系统的开发与部署,对于希望在这一领域脱颖而出的企业和个人而言,至关重要。本文将详细阐述短视频矩阵源码的开发与部署流程,并附上部分源代码示例&…...
iOS 环境搭建教程
本文档将详细介绍如何在 macOS 上搭建 iOS 开发环境,以便进行 React Native 开发。(为了保证环境一致 全部在网络通畅的情况下运行) 1. 安装 Homebrew Homebrew 是 macOS 的包管理工具,我们将通过它来安装开发所需的工具。 安装…...
element-ui实现table表格的嵌套(table表格嵌套)功能实现
最近在做电商类型的官网,希望实现的布局如下:有表头和表身,所以我首先想到的就是table表格组件。 表格组件中常见的就是:标题和内容一一对应: 像效果图中的效果,只用基础的表格布局是不行的,因…...
如何使mysql数据库ID从0开始编号——以BiCorpus为例
BiCorpus是北京语言大学韩林涛老师研制一款在线语料库网站,可以通过上传tmx文件,实现在线检索功能,程序在github上开源免费,深受广大网友的喜欢。 在使用过程中,我发现我上传的语言资产经历修改后,mysql的…...