编辑地图 实时地图优化

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 { .map-box-container {
width: 100%; width: 100%;
height: calc(100vh - 370px); height: calc(100vh - 370px);
overflow-x: hidden; overflow-x: auto;
overflow-y: auto; overflow-y: auto;
scrollbar-width: thin; /* Firefox */ scrollbar-width: thin; /* Firefox */
scrollbar-color: #888 #f1f1f1; 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') { if (toolbarSwitchType.value === 'clickDrawRoute') {
let list = state.allMapPointInfo let list = state.allMapPointInfo
const point = list[index] const point = list[index]
if (point.id) { if (point?.id) {
state.startDrawPoint = point // state.startDrawPoint = { ...point } //
state.startDrawPointIndex = index state.startDrawPointIndex = index
state.isDrawing = true state.isDrawing = true
event.preventDefault() // event.preventDefault()
} else { } else {
message.warning('选择的节点未保存') message.warning('选择的节点未保存')
// resetDrawState()
state.startDrawPointIndex = -1 //
state.startDrawPoint = null
state.isDrawing = false
state.currentDrawX = 0
state.currentDrawY = 0
} }
} }
} }
//
const resetDrawState = () => {
state.startDrawPointIndex = -1
state.startDrawPoint = null
state.isDrawing = false
state.currentDrawX = 0
state.currentDrawY = 0
}
// //
const startDrawSelection = (event) => { const startDrawSelection = (event) => {
if ( if (
@ -2146,6 +2151,71 @@ const updateDrawSelection = (event) => {
} }
// //
const endDrawSelection = (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 ( if (
toolbarSwitchType.value === 'createLineLibrary' || toolbarSwitchType.value === 'createLineLibrary' ||
toolbarSwitchType.value === 'createRegion' || toolbarSwitchType.value === 'createRegion' ||
@ -2159,112 +2229,37 @@ const endDrawSelection = (event) => {
event.preventDefault() // event.preventDefault() //
return 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 findClosestPoint = (x, y) => {
const list = state.allMapPointInfo
if (!Array.isArray(list) || list.length === 0) return null
const searchRadius = 100
let minDistance = Infinity let minDistance = Infinity
let closestIndex = null let closestIndex = null
let list = state.allMapPointInfo
list.forEach((point, index) => { // 使
const distance = Math.sqrt((point.locationX - x) ** 2 + (point.locationY - y) ** 2) const potentialPoints = list.filter((point, index) => {
if (distance < minDistance && distance < point.locationWide) { if (!point?.locationX || !point?.locationY) return false
// 10 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 minDistance = distance
closestIndex = index closestIndex = list.findIndex((p) => p.id === point.id)
} }
}) })
return closestIndex return closestIndex
} }
// //
@ -3565,6 +3560,17 @@ onMounted(() => {
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('keydown', handleKeyDown) 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

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

View File

@ -1,474 +1,105 @@
<template> <template>
<div class="fence-container"> <div class="map-container">
<div class="svg-container" ref="svgContainer"> <div
<svg ref="fullscreenElement"
ref="fenceSvg" class="fullscreen-content"
@click="handleSvgClick" :class="{ 'is-fullscreen': isFullscreen }"
@mousedown="startPan" >
@mousemove="handleMouseMove" <div
@mouseup="endPan" v-for="car in cars"
@mouseleave="endPan" :key="car.id"
@wheel="handleWheel" class="car"
:viewBox="viewBox" :style="{ left: car.x + 'px', top: car.y + 'px' }"
preserveAspectRatio="none"
> >
<!-- 网格背景 --> <img src="@/assets/imgs/indexPage/chache-4备份 7@2x.png" alt="car" class="car-image" />
<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>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, onMounted, onUnmounted } from 'vue'
const svgContainer = ref(null) const fullscreenElement = ref(null)
const fenceSvg = ref(null) const isFullscreen = ref(false)
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)
// // 20
const fencePointsString = computed(() => { const cars = ref(
return fencePoints.value.map((p) => `${p.x},${p.y}`).join(' ') 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
// return {
onMounted(() => { ...car,
updateSvgSize() x: newX,
window.addEventListener('resize', updateSvgSize) y: newY
})
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] })
} }
}
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
}) })
} }
// onMounted(() => {
const checkRandomPoint = () => { // 10010
if (fencePoints.value.length < 3) { updateInterval = setInterval(updateCarPositions, 200)
alert('请先绘制电子围栏') })
return
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> </script>
<style scoped> <style scoped>
.fence-container { .map-container {
display: flex; position: relative;
flex-direction: column;
height: 100vh;
} }
.svg-container { .fullscreen-content {
flex: 1; width: 1200px;
height: 800px;
background-color: #f0f0f0;
position: relative; position: relative;
border: 1px solid #ccc;
background: #f9f9f9;
overflow: hidden; overflow: hidden;
} }
svg { .car {
position: absolute; position: absolute;
top: 0; width: 32px;
left: 0; height: 32px;
transform: translate(-50%, -50%);
}
.car-image {
width: 100%; width: 100%;
height: 100%; height: 100%;
cursor: crosshair; object-fit: contain;
} }
svg.panning { /* 全屏模式下的样式 */
cursor: grabbing; .fullscreen-content:fullscreen,
} .fullscreen-content:-webkit-full-screen,
.fullscreen-content:-moz-full-screen,
.controls { .fullscreen-content:-ms-fullscreen {
padding: 10px; width: 100vw;
background: #f5f5f5; height: 100vh;
display: flex; background-color: white;
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;
} }
</style> </style>

View File

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