编辑地图 实时地图优化

This commit is contained in:
yyy 2025-06-06 18:43:40 +08:00
parent d8c5e5a3f9
commit 587f299f38
6 changed files with 668 additions and 1198 deletions

View File

@ -738,7 +738,7 @@ onBeforeUnmount(() => {
.map-box-container {
width: 100%;
height: calc(100vh - 370px);
overflow-x: hidden;
overflow-x: auto;
overflow-y: auto;
scrollbar-width: thin; /* Firefox */
scrollbar-color: #888 #f1f1f1;

File diff suppressed because it is too large Load Diff

View File

@ -2073,22 +2073,27 @@ const startFromPoint = (index, event) => {
if (toolbarSwitchType.value === 'clickDrawRoute') {
let list = state.allMapPointInfo
const point = list[index]
if (point.id) {
state.startDrawPoint = point //
if (point?.id) {
state.startDrawPoint = { ...point } //
state.startDrawPointIndex = index
state.isDrawing = true
event.preventDefault() //
event.preventDefault()
} else {
message.warning('选择的节点未保存')
//
state.startDrawPointIndex = -1 //
state.startDrawPoint = null
state.isDrawing = false
state.currentDrawX = 0
state.currentDrawY = 0
resetDrawState()
}
}
}
//
const resetDrawState = () => {
state.startDrawPointIndex = -1
state.startDrawPoint = null
state.isDrawing = false
state.currentDrawX = 0
state.currentDrawY = 0
}
//
const startDrawSelection = (event) => {
if (
@ -2146,6 +2151,71 @@ const updateDrawSelection = (event) => {
}
//
const endDrawSelection = (event) => {
if (toolbarSwitchType.value === 'clickDrawRoute') {
if (state.isDrawing) {
const endPointIndex = findClosestPoint(state.currentDrawX, state.currentDrawY)
if (endPointIndex !== null && endPointIndex !== state.startDrawPointIndex) {
let list = state.allMapPointInfo
const endPoint = list[endPointIndex]
if (!endPoint?.id) {
message.warning('选择的节点未保存')
resetDrawState()
return
}
// 线
if (checkRouteDuplicate(state.startDrawPoint, endPoint, state.mapRouteList)) {
message.warning('此路线已存在')
} else {
// 线
const newRoute = {
isSelected: false,
startingPointId: state.startDrawPoint.id,
endPointId: endPoint.id,
startPointX: state.startDrawPoint.locationX,
startPointY: state.startDrawPoint.locationY,
endPointX: endPoint.locationX,
endPointY: endPoint.locationY,
actualStartPointX: state.startDrawPoint.actualLocationX,
actualStartPointY: state.startDrawPoint.actualLocationY,
actualEndPointX: endPoint.actualLocationX,
actualEndPointY: endPoint.actualLocationY,
actualBeginControlX: '',
actualBeginControlY: '',
actualEndControlX: '',
actualEndControlY: '',
beginControlX: 0,
beginControlY: 0,
endControlX: 0,
endControlY: 0,
expansionZoneFront: 0,
expansionZoneAfter: 0,
expansionZoneLeft: 0,
expansionZoneRight: 0,
method: 0,
direction: 2,
forwardSpeedLimit: 1.5,
reverseSpeedLimit: 0.4,
toward: 0,
beginWidth: state.startDrawPoint.locationWidePx,
beginHigh: state.startDrawPoint.locationDeepPx,
endWidth: endPoint.locationWidePx,
endHigh: endPoint.locationDeepPx,
startingSortNum: state.startDrawPoint.sortNum,
endPointSortNum: endPoint.sortNum
}
state.mapRouteList.push(newRoute)
addEditHistory()
}
}
resetDrawState()
}
event.preventDefault()
return
}
if (
toolbarSwitchType.value === 'createLineLibrary' ||
toolbarSwitchType.value === 'createRegion' ||
@ -2159,112 +2229,37 @@ const endDrawSelection = (event) => {
event.preventDefault() //
return
}
if (toolbarSwitchType.value === 'clickDrawRoute') {
if (state.isDrawing) {
//
const endPointIndex = findClosestPoint(state.currentDrawX, state.currentDrawY)
if (endPointIndex !== null && endPointIndex !== state.startDrawPointIndex) {
let list = state.allMapPointInfo
const endPoint = list[endPointIndex]
//
if (!endPoint.id) {
message.warning('选择的节点未保存')
//
state.startDrawPointIndex = -1 //
state.startDrawPoint = null
state.isDrawing = false
state.currentDrawX = 0
state.currentDrawY = 0
return
}
const newLine = {
startPointX: state.startDrawPoint.locationX,
startPointY: state.startDrawPoint.locationY,
endPointX: endPoint.locationX,
endPointY: endPoint.locationY
}
// 线
const isDuplicate = state.mapRouteList.some(
(line) =>
(line.startPointX === newLine.startPointX &&
line.startPointY === newLine.startPointY &&
line.endPointX === newLine.endPointX &&
line.endPointY === newLine.endPointY) ||
(line.startPointX === newLine.endPointX &&
line.startPointY === newLine.endPointY &&
line.endPointX === newLine.startPointX &&
line.endPointY === newLine.startPointY)
)
if (isDuplicate) {
message.warning('此路线已存在')
} else {
// 线
state.mapRouteList.push({
isSelected: false, //
startingPointId: state.startDrawPoint.id,
endPointId: endPoint.id,
startPointX: state.startDrawPoint.locationX, //
startPointY: state.startDrawPoint.locationY, //
endPointX: endPoint.locationX, //
endPointY: endPoint.locationY, //
actualStartPointX: state.startDrawPoint.actualLocationX, //x
actualStartPointY: state.startDrawPoint.actualLocationY, //y
actualEndPointX: endPoint.actualLocationX, //x
actualEndPointY: endPoint.actualLocationY, //y
actualBeginControlX: '', //x
actualBeginControlY: '', //y
actualEndControlX: '', //x
actualEndControlY: '', //y
beginControlX: 0, //x
beginControlY: 0, //y
endControlX: 0, //x
endControlY: 0, //y
expansionZoneFront: 0, //
expansionZoneAfter: 0, //
expansionZoneLeft: 0, //
expansionZoneRight: 0, //
method: 0, // 0.线 1.线2.线3.线 4.线
direction: 2, // 1. 2.,
forwardSpeedLimit: 1.5, //
reverseSpeedLimit: 0.4, //
toward: 0, // ( 0: 1: 2: 3:)
beginWidth: state.startDrawPoint.locationWidePx, //
beginHigh: state.startDrawPoint.locationDeepPx, //
endWidth: endPoint.locationWidePx, //
endHigh: endPoint.locationDeepPx, //
startingSortNum: state.startDrawPoint.sortNum,
endPointSortNum: endPoint.sortNum
})
addEditHistory()
}
}
//
state.startDrawPointIndex = -1 //
state.startDrawPoint = null
state.isDrawing = false
state.currentDrawX = 0
state.currentDrawY = 0
}
event.preventDefault() //
return
}
}
//
const findClosestPoint = (x, y) => {
const list = state.allMapPointInfo
if (!Array.isArray(list) || list.length === 0) return null
const searchRadius = 100
let minDistance = Infinity
let closestIndex = null
let list = state.allMapPointInfo
list.forEach((point, index) => {
const distance = Math.sqrt((point.locationX - x) ** 2 + (point.locationY - y) ** 2)
if (distance < minDistance && distance < point.locationWide) {
// 10
// 使
const potentialPoints = list.filter((point, index) => {
if (!point?.locationX || !point?.locationY) return false
const dx = Math.abs(point.locationX - x)
const dy = Math.abs(point.locationY - y)
return dx <= searchRadius && dy <= searchRadius
})
//
potentialPoints.forEach((point, i) => {
const dx = point.locationX - x
const dy = point.locationY - y
const distance = Math.sqrt(dx * dx + dy * dy)
const captureRadius = point.locationWide || 10
if (distance < minDistance && distance < captureRadius) {
minDistance = distance
closestIndex = index
closestIndex = list.findIndex((p) => p.id === point.id)
}
})
return closestIndex
}
//
@ -3565,6 +3560,17 @@ onMounted(() => {
onUnmounted(() => {
window.removeEventListener('keydown', handleKeyDown)
})
// 线
const checkRouteDuplicate = (startPoint, endPoint, routeList) => {
if (!startPoint?.id || !endPoint?.id) return false
return routeList.some(
(route) =>
(route.startingPointId === startPoint.id && route.endPointId === endPoint.id) ||
(route.startingPointId === endPoint.id && route.endPointId === startPoint.id)
)
}
</script>
<style lang="scss" scoped>

View File

@ -204,7 +204,10 @@ onMounted(() => {
}
.main-content {
box-sizing: border-box;
margin-top: 62px;
height: calc(100vh - 120px) !important;
overflow: auto;
}
}
</style>

View File

@ -1,474 +1,105 @@
<template>
<div class="fence-container">
<div class="svg-container" ref="svgContainer">
<svg
ref="fenceSvg"
@click="handleSvgClick"
@mousedown="startPan"
@mousemove="handleMouseMove"
@mouseup="endPan"
@mouseleave="endPan"
@wheel="handleWheel"
:viewBox="viewBox"
preserveAspectRatio="none"
<div class="map-container">
<div
ref="fullscreenElement"
class="fullscreen-content"
:class="{ 'is-fullscreen': isFullscreen }"
>
<div
v-for="car in cars"
:key="car.id"
class="car"
:style="{ left: car.x + 'px', top: car.y + 'px' }"
>
<!-- 网格背景 -->
<pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="#eee" stroke-width="0.5" />
</pattern>
<rect width="100%" height="100%" fill="url(#grid)" />
<!-- 围栏多边形 -->
<polygon
v-if="fencePoints.length > 2"
:points="fencePointsString"
fill="rgba(231, 76, 60, 0.2)"
stroke="#e74c3c"
stroke-width="2"
/>
<!-- 绘制中的线段 -->
<polyline
v-if="isDrawing && currentPoints.length > 0"
:points="currentPointsString"
fill="none"
stroke="#3498db"
stroke-width="2"
/>
<!-- 顶点标记 -->
<circle
v-for="(point, index) in allPoints"
:key="index"
:cx="point.x"
:cy="point.y"
r="3"
fill="#3498db"
@mousedown.stop="startDrag(index, $event)"
/>
<!-- 测试点 -->
<circle
v-if="testPoint"
:cx="testPoint.x"
:cy="testPoint.y"
r="5"
:fill="isInside ? '#2ecc71' : '#e74c3c'"
/>
<!-- 坐标显示 -->
<text x="10" y="20" font-size="12" fill="#333" v-if="cursorPosition">
X: {{ cursorPosition.x.toFixed(1) }}, Y: {{ cursorPosition.y.toFixed(1) }}
</text>
</svg>
</div>
<div class="controls">
<div class="control-group">
<h3>绘制控制</h3>
<button @click="startDrawing">开始绘制</button>
<button @click="stopDrawing">结束绘制</button>
<button @click="clearDrawing">清空</button>
<button @click="checkRandomPoint">检测随机点</button>
</div>
<div class="control-group">
<h3>视图控制</h3>
<button @click="zoomIn">放大</button>
<button @click="zoomOut">缩小</button>
<button @click="resetView">重置视图</button>
</div>
<div class="control-group">
<h3>数据管理</h3>
<button @click="saveFence">保存围栏</button>
<button @click="loadFence">加载围栏</button>
<img src="@/assets/imgs/indexPage/chache-4备份 7@2x.png" alt="car" class="car-image" />
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { ref, onMounted, onUnmounted } from 'vue'
const svgContainer = ref(null)
const fenceSvg = ref(null)
const isDrawing = ref(false)
const fencePoints = ref([])
const currentPoints = ref([])
const testPoint = ref(null)
const isInside = ref(false)
const draggingIndex = ref(null)
const initialPos = ref(null)
const viewBox = ref('0 0 1000 600')
const svgSize = ref({ width: 1000, height: 600 })
const isPanning = ref(false)
const panStart = ref({ x: 0, y: 0 })
const cursorPosition = ref(null)
const fullscreenElement = ref(null)
const isFullscreen = ref(false)
//
const fencePointsString = computed(() => {
return fencePoints.value.map((p) => `${p.x},${p.y}`).join(' ')
})
// 20
const cars = ref(
Array.from({ length: 20 }, (_, index) => ({
id: index + 1,
x: Math.random() * 800, // x
y: Math.random() * 600, // y
dx: (Math.random() - 0.5) * 6, // x
dy: (Math.random() - 0.5) * 6 // y
}))
)
const currentPointsString = computed(() => {
return currentPoints.value.map((p) => `${p.x},${p.y}`).join(' ')
})
//
let updateInterval
const updateCarPositions = () => {
cars.value = cars.value.map((car) => {
//
let newX = car.x + car.dx
let newY = car.y + car.dy
const allPoints = computed(() => {
return isDrawing.value ? [...fencePoints.value, ...currentPoints.value] : fencePoints.value
})
//
if (newX <= 0 || newX >= 1200) car.dx *= -1
if (newY <= 0 || newY >= 800) car.dy *= -1
//
onMounted(() => {
updateSvgSize()
window.addEventListener('resize', updateSvgSize)
})
onUnmounted(() => {
window.removeEventListener('resize', updateSvgSize)
})
function updateSvgSize() {
const container = svgContainer.value
svgSize.value = {
width: container.clientWidth,
height: container.clientHeight
}
fenceSvg.value.setAttribute('viewBox', viewBox.value)
}
//
const startDrawing = () => {
fencePoints.value = []
currentPoints.value = []
isDrawing.value = true
testPoint.value = null
}
const stopDrawing = () => {
if (currentPoints.value.length > 0) {
fencePoints.value = [...fencePoints.value, ...currentPoints.value]
currentPoints.value = []
}
if (fencePoints.value.length > 2) {
//
if (
fencePoints.value[0].x !== fencePoints.value[fencePoints.value.length - 1].x ||
fencePoints.value[0].y !== fencePoints.value[fencePoints.value.length - 1].y
) {
fencePoints.value.push({ ...fencePoints.value[0] })
return {
...car,
x: newX,
y: newY
}
}
isDrawing.value = false
}
const clearDrawing = () => {
fencePoints.value = []
currentPoints.value = []
isDrawing.value = false
testPoint.value = null
}
const handleSvgClick = (e) => {
if (!isDrawing.value || isPanning.value) return
const svg = fenceSvg.value
const pt = svg.createSVGPoint()
pt.x = e.clientX
pt.y = e.clientY
const cursorPt = pt.matrixTransform(svg.getScreenCTM().inverse())
currentPoints.value.push({
x: cursorPt.x,
y: cursorPt.y
})
}
//
const checkRandomPoint = () => {
if (fencePoints.value.length < 3) {
alert('请先绘制电子围栏')
return
onMounted(() => {
// 10010
updateInterval = setInterval(updateCarPositions, 200)
})
onUnmounted(() => {
//
if (updateInterval) {
clearInterval(updateInterval)
}
const [vbX, vbY, vbWidth, vbHeight] = viewBox.value.split(' ').map(Number)
testPoint.value = {
x: vbX + Math.random() * vbWidth,
y: vbY + Math.random() * vbHeight
}
isInside.value = pointInPolygon(testPoint.value, fencePoints.value)
}
function pointInPolygon(point, polygon) {
const x = point.x
const y = point.y
let inside = false
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const xi = polygon[i].x
const yi = polygon[i].y
const xj = polygon[j].x
const yj = polygon[j].y
const intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi
if (intersect) inside = !inside
}
return inside
}
//
const startDrag = (index, e) => {
e.preventDefault()
draggingIndex.value = index
const svg = fenceSvg.value
const pt = svg.createSVGPoint()
pt.x = e.clientX
pt.y = e.clientY
const cursorPt = pt.matrixTransform(svg.getScreenCTM().inverse())
initialPos.value = {
x: cursorPt.x,
y: cursorPt.y,
index
}
document.addEventListener('mousemove', handleDrag)
document.addEventListener('mouseup', stopDrag)
}
const handleDrag = (e) => {
if (draggingIndex.value === null) return
const svg = fenceSvg.value
const pt = svg.createSVGPoint()
pt.x = e.clientX
pt.y = e.clientY
const cursorPt = pt.matrixTransform(svg.getScreenCTM().inverse())
const points =
isDrawing.value && draggingIndex.value >= fencePoints.value.length
? currentPoints.value
: fencePoints.value
const actualIndex =
isDrawing.value && draggingIndex.value >= fencePoints.value.length
? draggingIndex.value - fencePoints.value.length
: draggingIndex.value
points[actualIndex] = {
x: cursorPt.x,
y: cursorPt.y
}
//
if (
!isDrawing.value &&
fencePoints.value.length > 0 &&
draggingIndex.value === fencePoints.value.length - 1
) {
fencePoints.value[0] = { ...fencePoints.value[fencePoints.value.length - 1] }
}
}
const stopDrag = () => {
draggingIndex.value = null
initialPos.value = null
document.removeEventListener('mousemove', handleDrag)
document.removeEventListener('mouseup', stopDrag)
}
//
const handleWheel = (e) => {
e.preventDefault()
const delta = e.deltaY > 0 ? 0.9 : 1.1
zoomAt(e.clientX, e.clientY, delta)
}
function zoomAt(clientX, clientY, factor) {
const container = svgContainer.value
const rect = container.getBoundingClientRect()
const x = ((clientX - rect.left) / rect.width) * svgSize.value.width
const y = ((clientY - rect.top) / rect.height) * svgSize.value.height
const [vbX, vbY, vbWidth, vbHeight] = viewBox.value.split(' ').map(Number)
const newWidth = vbWidth * factor
const newHeight = vbHeight * factor
const newX = vbX + (x - vbX) * (1 - factor)
const newY = vbY + (y - vbY) * (1 - factor)
//
if (newWidth > svgSize.value.width * 10 || newWidth < svgSize.value.width / 10) return
viewBox.value = `${newX} ${newY} ${newWidth} ${newHeight}`
fenceSvg.value.setAttribute('viewBox', viewBox.value)
}
const zoomIn = () => {
const container = svgContainer.value
const rect = container.getBoundingClientRect()
zoomAt(rect.left + rect.width / 2, rect.top + rect.height / 2, 1.2)
}
const zoomOut = () => {
const container = svgContainer.value
const rect = container.getBoundingClientRect()
zoomAt(rect.left + rect.width / 2, rect.top + rect.height / 2, 0.8)
}
const resetView = () => {
viewBox.value = `0 0 ${svgSize.value.width} ${svgSize.value.height}`
fenceSvg.value.setAttribute('viewBox', viewBox.value)
}
const startPan = (e) => {
if (draggingIndex.value !== null) return
isPanning.value = true
panStart.value = {
x: e.clientX,
y: e.clientY
}
fenceSvg.value.style.cursor = 'grabbing'
}
const handlePan = (e) => {
if (!isPanning.value) return
const [vbX, vbY, vbWidth, vbHeight] = viewBox.value.split(' ').map(Number)
const dx = ((e.clientX - panStart.value.x) / svgSize.value.width) * vbWidth
const dy = ((e.clientY - panStart.value.y) / svgSize.value.height) * vbHeight
viewBox.value = `${vbX - dx} ${vbY - dy} ${vbWidth} ${vbHeight}`
fenceSvg.value.setAttribute('viewBox', viewBox.value)
panStart.value = {
x: e.clientX,
y: e.clientY
}
}
const endPan = () => {
isPanning.value = false
fenceSvg.value.style.cursor = 'crosshair'
}
//
const handleMouseMove = (e) => {
const svg = fenceSvg.value
const pt = svg.createSVGPoint()
pt.x = e.clientX
pt.y = e.clientY
const cursorPt = pt.matrixTransform(svg.getScreenCTM().inverse())
cursorPosition.value = {
x: cursorPt.x,
y: cursorPt.y
}
}
//
const saveFence = () => {
if (fencePoints.value.length < 3) {
alert('请先绘制有效的电子围栏')
return
}
const data = {
points: fencePoints.value,
viewBox: viewBox.value,
svgSize: svgSize.value
}
localStorage.setItem('fenceData', JSON.stringify(data))
alert('围栏已保存')
}
const loadFence = () => {
const data = localStorage.getItem('fenceData')
if (data) {
try {
const parsed = JSON.parse(data)
fencePoints.value = parsed.points
viewBox.value = parsed.viewBox || `0 0 ${svgSize.value.width} ${svgSize.value.height}`
svgSize.value = parsed.svgSize || { width: 1000, height: 600 }
fenceSvg.value.setAttribute('viewBox', viewBox.value)
alert('围栏已加载')
} catch (e) {
alert('加载围栏数据失败')
}
} else {
alert('没有找到保存的围栏数据')
}
}
})
</script>
<style scoped>
.fence-container {
display: flex;
flex-direction: column;
height: 100vh;
.map-container {
position: relative;
}
.svg-container {
flex: 1;
.fullscreen-content {
width: 1200px;
height: 800px;
background-color: #f0f0f0;
position: relative;
border: 1px solid #ccc;
background: #f9f9f9;
overflow: hidden;
}
svg {
.car {
position: absolute;
top: 0;
left: 0;
width: 32px;
height: 32px;
transform: translate(-50%, -50%);
}
.car-image {
width: 100%;
height: 100%;
cursor: crosshair;
object-fit: contain;
}
svg.panning {
cursor: grabbing;
}
.controls {
padding: 10px;
background: #f5f5f5;
display: flex;
gap: 20px;
}
.control-group {
display: flex;
flex-direction: column;
gap: 5px;
}
.control-group h3 {
margin: 0 0 5px 0;
font-size: 14px;
color: #333;
}
button {
padding: 8px 12px;
background: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background: #2980b9;
}
circle {
cursor: move;
/* 全屏模式下的样式 */
.fullscreen-content:fullscreen,
.fullscreen-content:-webkit-full-screen,
.fullscreen-content:-moz-full-screen,
.fullscreen-content:-ms-fullscreen {
width: 100vw;
height: 100vh;
background-color: white;
}
</style>

View File

@ -1,115 +1,117 @@
// websocket.js
class WebSocketClient {
constructor(url) {
this.currentUrl = url;
this.socket = null;
this.heartbeatInterval = null;
this.messageCallback = null;
this.reconnectTimer = null;
this.reconnectAttempts = 0;
this.MAX_RECONNECT_ATTEMPTS = 5;
this.RECONNECT_DELAY = 5000; // 5 秒
this.HEARTBEAT_INTERVAL = 30000; // 30 秒
this.URL_CHECK_INTERVAL = 5000; // 每 5 秒检查一次 URL
this.init();
this.startUrlCheck();
}
constructor(url) {
this.currentUrl = url
this.socket = null
this.heartbeatInterval = null
this.messageCallback = null
this.reconnectTimer = null
this.reconnectAttempts = 0
this.MAX_RECONNECT_ATTEMPTS = 5
this.RECONNECT_DELAY = 5000 // 5 秒
this.HEARTBEAT_INTERVAL = 30000 // 30 秒
this.URL_CHECK_INTERVAL = 5000 // 每 5 秒检查一次 URL
this.init()
this.startUrlCheck()
}
init() {
try {
console.log('尝试创建 WebSocket 连接:', this.currentUrl);
this.socket = new WebSocket(this.currentUrl);
init() {
try {
console.log('尝试创建 WebSocket 连接:', this.currentUrl)
this.socket = new WebSocket(this.currentUrl)
this.socket.onopen = () => {
console.log('WebSocket 连接已建立:', this.currentUrl);
this.startHeartbeat();
this.reconnectAttempts = 0;
};
this.socket.onopen = () => {
console.log('WebSocket 连接已建立:', this.currentUrl)
this.startHeartbeat()
this.reconnectAttempts = 0
}
this.socket.onmessage = (event) => {
if (this.messageCallback) {
this.messageCallback(event.data);
}
};
this.socket.onclose = () => {
console.log('WebSocket 连接已关闭:', this.currentUrl);
this.stopHeartbeat();
this.reconnect();
};
this.socket.onerror = (error) => {
console.error('WebSocket 发生错误:', error);
this.socket.close();
};
} catch (error) {
console.error('创建 WebSocket 连接时出错:', error);
this.socket.onmessage = (event) => {
if (this.messageCallback) {
this.messageCallback(event.data)
}
}
}
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send('ping');
}
}, this.HEARTBEAT_INTERVAL);
}
this.socket.onclose = () => {
console.log('WebSocket 连接已关闭:', this.currentUrl)
this.stopHeartbeat()
this.reconnect()
}
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
onMessage(callback) {
this.messageCallback = callback;
}
send(message) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(message);
} else {
console.error('WebSocket 连接未打开,无法发送消息');
}
}
disconnect() {
this.socket.onerror = (error) => {
console.log('WebSocket 发生错误:', error)
if (this.socket) {
this.stopHeartbeat();
this.socket.close();
this.socket = null;
this.socket.close()
}
}
} catch (error) {
console.log('创建 WebSocket 连接时出错:', error)
}
}
reconnect() {
if (this.reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS) {
this.reconnectTimer = setTimeout(() => {
console.log('尝试重新连接...', this.currentUrl);
this.reconnectAttempts++;
this.init();
}, this.RECONNECT_DELAY);
} else {
console.error('达到最大重连次数,停止重连');
}
}
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send('ping')
}
}, this.HEARTBEAT_INTERVAL)
}
startUrlCheck() {
setInterval(() => {
const newUrl = this.getUpdatedUrl();
if (newUrl && newUrl!== this.currentUrl) {
this.disconnect();
this.currentUrl = newUrl;
this.init();
}
}, this.URL_CHECK_INTERVAL);
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval)
this.heartbeatInterval = null
}
}
// 这个方法需要根据实际情况重写,用于获取最新的 URL
getUpdatedUrl() {
// 这里只是示例,返回 null 表示没有更新的 URL
return null;
onMessage(callback) {
this.messageCallback = callback
}
send(message) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(message)
} else {
console.log('WebSocket 连接未打开,无法发送消息')
}
}
disconnect() {
if (this.socket) {
this.stopHeartbeat()
this.socket.close()
this.socket = null
}
}
reconnect() {
if (this.reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS) {
this.reconnectTimer = setTimeout(() => {
console.log('尝试重新连接...', this.currentUrl)
this.reconnectAttempts++
this.init()
}, this.RECONNECT_DELAY)
} else {
console.log('达到最大重连次数,停止重连')
}
}
startUrlCheck() {
setInterval(() => {
const newUrl = this.getUpdatedUrl()
if (newUrl && newUrl !== this.currentUrl) {
this.disconnect()
this.currentUrl = newUrl
this.init()
}
}, this.URL_CHECK_INTERVAL)
}
// 这个方法需要根据实际情况重写,用于获取最新的 URL
getUpdatedUrl() {
// 这里只是示例,返回 null 表示没有更新的 URL
return null
}
}
export default WebSocketClient;
export default WebSocketClient