fix: 优化电子围栏绘制!
This commit is contained in:
parent
7a0e0615c8
commit
6e7e065763
@ -13,7 +13,7 @@
|
||||
</svg>
|
||||
<p>视频区域</p>
|
||||
<!-- 绘制蒙版层 -->
|
||||
<div class="drawing-mask" v-if="eventData.fenceArea === '自定义画面'">
|
||||
<div class="drawing-mask" v-if="activeEvent.fenceArea === '自定义画面'">
|
||||
<div class="mask-background"></div>
|
||||
<canvas
|
||||
ref="drawingCanvas"
|
||||
@ -66,7 +66,7 @@
|
||||
<!-- 顶部标题和新增按钮 -->
|
||||
<div class="section-header">
|
||||
<h3>事件绑定与算法绑定</h3>
|
||||
<el-button class="add-event-btn">
|
||||
<el-button class="add-event-btn" @click="addEventGroup">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16">
|
||||
<path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||
</svg>
|
||||
@ -74,19 +74,32 @@
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 单个事件组 -->
|
||||
<div class="event-group">
|
||||
<!-- 事件组列表 -->
|
||||
<div class="event-group-list">
|
||||
<div
|
||||
class="event-group"
|
||||
v-for="(event, index) in eventGroups"
|
||||
:key="index"
|
||||
:class="{ 'active': activeIndex === index }"
|
||||
@click="switchEvent(index)"
|
||||
>
|
||||
<!-- 第一行:事件名称、电子围栏区域、告警等级 -->
|
||||
<div class="form-row">
|
||||
<div class="form-item">
|
||||
<label>事件名称:</label>
|
||||
<el-input v-model="eventData.name" placeholder="船舶登轮" />
|
||||
<el-select v-model="event.name" placeholder="请选择事件类型">
|
||||
<el-option label="船舶靠泊" value="船舶靠泊" />
|
||||
<el-option label="船舶离泊" value="船舶离泊" />
|
||||
<el-option label="人员登轮" value="人员登轮" />
|
||||
<el-option label="人员离轮" value="人员离轮" />
|
||||
<el-option label="电脑弹窗" value="电脑弹窗" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label>电子围栏区域:</label>
|
||||
<el-switch
|
||||
v-model="eventData.fenceArea"
|
||||
v-model="event.fenceArea"
|
||||
active-text="整个监控画面"
|
||||
inactive-text="自定义画面"
|
||||
active-value="整个监控画面"
|
||||
@ -96,7 +109,7 @@
|
||||
|
||||
<div class="form-item">
|
||||
<label>告警等级:</label>
|
||||
<el-select v-model="eventData.level" placeholder="P0紧急告警">
|
||||
<el-select v-model="event.level" placeholder="P0紧急告警">
|
||||
<el-option label="P0紧急告警" value="P0" />
|
||||
<el-option label="P1重要告警" value="P1" />
|
||||
<el-option label="P2一般告警" value="P2" />
|
||||
@ -106,37 +119,42 @@
|
||||
</div>
|
||||
|
||||
<!-- 动态生成的电子围栏配置区域 -->
|
||||
<div class="form-row" v-for="(fence, index) in fenceAreas" :key="index">
|
||||
<div class="form-item">
|
||||
<label>电子围栏名称 {{ index + 1 }}:</label>
|
||||
<el-input v-model="fence.name" placeholder="请输入名称" />
|
||||
<div class="form-row" v-for="(fence, fenceIndex) in event.fenceAreas" :key="fenceIndex">
|
||||
<div class="form-item" style="width: 20%;">
|
||||
<label>电子围栏名称:</label>
|
||||
<el-input v-model="fence.name" placeholder="请输入" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>绑定算法 {{ index + 1 }}:</label>
|
||||
<el-select v-model="fence.algorithm" placeholder="选择算法">
|
||||
<div class="form-item" style="width: 30%;">
|
||||
<label>算法绑定:</label>
|
||||
<el-select v-model="fence.algorithm" placeholder="算法XXX001">
|
||||
<el-option v-for="alg in algorithms"
|
||||
:key="alg.id"
|
||||
:label="alg.name"
|
||||
:value="alg.id" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="form-item" tyle="width: 50%;">
|
||||
<label>算法条件:</label>
|
||||
<label style="width:80%;">{{fence.condition}}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第三行:交叉区域名称、交集算法绑定 -->
|
||||
<div class="form-row">
|
||||
<div class="form-item">
|
||||
<div class="form-item" style="width: 20%;">
|
||||
<label>交叉区域名称:</label>
|
||||
<el-input v-model="eventData.crossAreaName" placeholder="请输入" />
|
||||
<el-input v-model="event.crossAreaName" placeholder="请输入" />
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<div class="form-item" tyle="width: 30%;">
|
||||
<label>交集算法绑定:</label>
|
||||
<el-select v-model="eventData.crossAlgorithm" placeholder="算法XXX001">
|
||||
<el-select v-model="event.crossAlgorithm" placeholder="算法XXX001">
|
||||
<el-option v-for="alg in algorithms" :key="alg.id" :label="alg.name" :value="alg.id" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<div class="editor-footer">
|
||||
@ -203,7 +221,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||
import { ref, watch, onMounted, onBeforeUnmount, nextTick, computed } from 'vue'
|
||||
import { UploadFilled, Delete } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
@ -218,7 +236,6 @@ const canvasInitialized = ref(false)
|
||||
const points = ref([]) // 存储当前图形的点
|
||||
const completedShapes = ref([]) // 存储已完成的图形
|
||||
const isDrawing = ref(false) // 是否正在绘制
|
||||
const fenceAreas = ref([]) // 存储每个电子围栏的配置
|
||||
const selectedShapeIndex = ref(null) // 当前选中的图形索引
|
||||
const drawMode = ref('polygon') // 绘制模式:polygon-多边形,line-直线
|
||||
const mousePos = ref(null) // 鼠标当前位置
|
||||
@ -233,6 +250,10 @@ const uploadDialogVisible = ref(false)
|
||||
const fileList = ref([])
|
||||
const selectedFile = ref(null)
|
||||
|
||||
// 事件组相关状态
|
||||
const eventGroups = ref([])
|
||||
const activeIndex = ref(0)
|
||||
|
||||
const props = defineProps({
|
||||
deviceData: {
|
||||
type: Object,
|
||||
@ -243,74 +264,165 @@ const props = defineProps({
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
|
||||
const algorithms = ref([
|
||||
{ id: 'alg-001', name: '算法XXX001' },
|
||||
{ id: 'alg-002', name: '算法XXX002' },
|
||||
{ id: 'alg-003', name: '算法XXX003' }
|
||||
{ id: 'alg-001', name: '船舶靠泊识别', condition: '识别船舶框体与电子围栏相交则报警靠岸成功' },
|
||||
{ id: 'alg-002', name: '船舶离泊识别', condition: '识别船舶框体与电子围栏远离则报警开始离岸' },
|
||||
{ id: 'alg-003', name: '人员登轮识别', condition: '目标识别人员依次通过电子围栏1、2则报警人员登轮' },
|
||||
{ id: 'alg-004', name: '人员离轮识别', condition: '目标识别人员依次通过电子围栏1、2则报警人员离轮' },
|
||||
{ id: 'alg-005', name: '弹窗识别', condition: '识别个人办公区域内电脑出现弹窗后无第二位员工且弹窗消失则报警' }
|
||||
])
|
||||
|
||||
const eventData = ref({
|
||||
name: props.deviceData.eventName || '船舶登轮',
|
||||
// 当前活动的事件
|
||||
const activeEvent = computed(() => {
|
||||
return eventGroups.value[activeIndex.value] || {}
|
||||
})
|
||||
|
||||
// 初始化事件组
|
||||
const initEventGroups = () => {
|
||||
eventGroups.value = [
|
||||
{
|
||||
name: '船舶靠泊', // 默认设置为船舶靠泊
|
||||
fenceArea: '整个监控画面',
|
||||
level: 'P0',
|
||||
fenceName: '',
|
||||
algorithm: 'alg-001',
|
||||
fenceAreas: [],
|
||||
completedShapes: [],
|
||||
points: [],
|
||||
drawMode: 'polygon',
|
||||
crossAreaName: '',
|
||||
crossAlgorithm: 'alg-001'
|
||||
})
|
||||
|
||||
// 第一组事件数据
|
||||
const eventData1 = ref({
|
||||
name: '船舶登轮',
|
||||
area: '整个监控画面',
|
||||
level: 'P0',
|
||||
fenceName: '',
|
||||
algorithm: 'alg-001',
|
||||
crossAreaName: '',
|
||||
crossAlgorithm: 'alg-001'
|
||||
})
|
||||
|
||||
// 第二组事件数据
|
||||
const eventData2 = ref({
|
||||
name: '船舶登轮',
|
||||
area: '整个监控画面',
|
||||
algorithm: 'alg-001'
|
||||
})
|
||||
|
||||
// 上传视频相关方法
|
||||
const handleUploadClick = () => {
|
||||
uploadDialogVisible.value = true
|
||||
fileList.value = []
|
||||
selectedFile.value = null
|
||||
}
|
||||
|
||||
const handleVideoChange = (file) => {
|
||||
selectedFile.value = file.raw
|
||||
}
|
||||
|
||||
const confirmUpload = () => {
|
||||
if (!selectedFile.value) return
|
||||
|
||||
// 这里可以添加文件上传逻辑
|
||||
console.log('上传文件:', selectedFile.value)
|
||||
|
||||
// 模拟上传成功
|
||||
ElMessage.success('视频上传成功')
|
||||
uploadDialogVisible.value = false
|
||||
}
|
||||
|
||||
const saveEvent = () => {
|
||||
const allEventData = {
|
||||
event1: eventData1.value,
|
||||
event2: eventData2.value,
|
||||
fenceAreas: fenceAreas.value
|
||||
}
|
||||
emit('save', allEventData)
|
||||
]
|
||||
}
|
||||
|
||||
const closeEditor = () => {
|
||||
emit('close')
|
||||
// 添加新的事件组
|
||||
const addEventGroup = () => {
|
||||
eventGroups.value.push({
|
||||
name: '船舶靠泊', // 默认设置为船舶靠泊
|
||||
fenceArea: '整个监控画面',
|
||||
level: 'P0',
|
||||
fenceAreas: [],
|
||||
completedShapes: [],
|
||||
points: [],
|
||||
drawMode: 'polygon',
|
||||
crossAreaName: '',
|
||||
crossAlgorithm: 'alg-001'
|
||||
})
|
||||
// 切换到新添加的事件组
|
||||
switchEvent(eventGroups.value.length - 1)
|
||||
}
|
||||
|
||||
// 切换事件组
|
||||
const switchEvent = (index) => {
|
||||
// 保存当前事件组的绘图状态
|
||||
if (activeEvent.value) {
|
||||
activeEvent.value.points = [...points.value];
|
||||
activeEvent.value.completedShapes = [...completedShapes.value];
|
||||
activeEvent.value.drawMode = drawMode.value;
|
||||
}
|
||||
|
||||
// 切换到新的事件组
|
||||
activeIndex.value = index;
|
||||
|
||||
// 加载新事件组的绘图状态
|
||||
points.value = [...(activeEvent.value.points || [])];
|
||||
completedShapes.value = [...(activeEvent.value.completedShapes || [])];
|
||||
drawMode.value = activeEvent.value.drawMode || 'polygon';
|
||||
|
||||
// 更新显示状态
|
||||
showDrawingCanvas.value = activeEvent.value.fenceArea === '自定义画面';
|
||||
|
||||
// 重绘画布
|
||||
nextTick(() => {
|
||||
if (showDrawingCanvas.value) {
|
||||
initCanvas();
|
||||
}
|
||||
redrawCanvas();
|
||||
});
|
||||
}
|
||||
|
||||
// 保存事件
|
||||
const saveEvent = () => {
|
||||
// 保存当前事件组的绘图状态
|
||||
if (activeEvent.value) {
|
||||
activeEvent.value.points = [...points.value]
|
||||
activeEvent.value.completedShapes = [...completedShapes.value]
|
||||
}
|
||||
|
||||
emit('save', {
|
||||
eventGroups: eventGroups.value
|
||||
})
|
||||
}
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(() => {
|
||||
initEventGroups()
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
// 监听电子围栏区域变化
|
||||
watch(() => activeEvent.value?.name, (newName, oldName) => {
|
||||
if (newName !== oldName) {
|
||||
// 根据事件名称自动设置电子围栏和算法
|
||||
updateEventConfigByType(newName);
|
||||
}
|
||||
})
|
||||
const updateEventConfigByType = (eventType) => {
|
||||
if (!activeEvent.value) return;
|
||||
|
||||
// 保存当前绘图状态
|
||||
const currentPoints = [...points.value];
|
||||
const currentShapes = [...completedShapes.value];
|
||||
|
||||
// 根据事件类型设置默认算法
|
||||
let algorithmId = 'alg-001';
|
||||
let fenceName = '围栏区域';
|
||||
|
||||
switch(eventType) {
|
||||
case '船舶靠泊':
|
||||
algorithmId = 'alg-001';
|
||||
fenceName = '岸边';
|
||||
break;
|
||||
case '船舶离泊':
|
||||
algorithmId = 'alg-002';
|
||||
fenceName = '岸边';
|
||||
break;
|
||||
case '人员登轮':
|
||||
algorithmId = 'alg-003';
|
||||
fenceName = '岸边平面';
|
||||
break;
|
||||
case '人员离轮':
|
||||
algorithmId = 'alg-004';
|
||||
fenceName = '船舶楼梯上层平面';
|
||||
break;
|
||||
case '电脑弹窗':
|
||||
algorithmId = 'alg-005';
|
||||
fenceName = '个人办公区域';
|
||||
break;
|
||||
}
|
||||
|
||||
// 更新交叉区域算法
|
||||
activeEvent.value.crossAlgorithm = algorithmId;
|
||||
|
||||
// 恢复绘图状态
|
||||
points.value = currentPoints;
|
||||
completedShapes.value = currentShapes;
|
||||
|
||||
// 重绘画布
|
||||
redrawCanvas();
|
||||
}
|
||||
|
||||
// 处理窗口大小变化
|
||||
const handleResize = () => {
|
||||
if (showDrawingCanvas.value && canvasInitialized.value) {
|
||||
initCanvas()
|
||||
redrawCanvas()
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化画布
|
||||
const initCanvas = () => {
|
||||
if (!drawingCanvas.value || !videoContainer.value) return
|
||||
|
||||
@ -322,34 +434,6 @@ const initCanvas = () => {
|
||||
canvasInitialized.value = true
|
||||
}
|
||||
|
||||
// 监听电子围栏区域变化
|
||||
watch(() => eventData.value.fenceArea, (newVal) => {
|
||||
showDrawingCanvas.value = newVal === '自定义画面'
|
||||
if (showDrawingCanvas.value) {
|
||||
nextTick(() => {
|
||||
initCanvas()
|
||||
resetDrawing()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 确保组件挂载后初始化
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
// 处理窗口大小变化
|
||||
const handleResize = () => {
|
||||
if (showDrawingCanvas.value && canvasInitialized.value) {
|
||||
initCanvas()
|
||||
redrawCanvas()
|
||||
}
|
||||
}
|
||||
|
||||
// 处理鼠标移动事件
|
||||
const handleMouseMove = (e) => {
|
||||
if (!isDrawing.value || points.value.length === 0) return
|
||||
@ -364,7 +448,7 @@ const handleMouseMove = (e) => {
|
||||
|
||||
// 处理画布点击事件
|
||||
const handleCanvasClick = (e) => {
|
||||
if (eventData.value.fenceArea !== '自定义画面') return
|
||||
if (activeEvent.value?.fenceArea !== '自定义画面') return
|
||||
|
||||
const rect = drawingCanvas.value.getBoundingClientRect()
|
||||
const x = e.clientX - rect.left
|
||||
@ -420,41 +504,170 @@ const handleCanvasClick = (e) => {
|
||||
redrawCanvas()
|
||||
}
|
||||
|
||||
// 完成当前绘制
|
||||
// completeDrawing方法
|
||||
const completeDrawing = () => {
|
||||
if (points.value.length < 2) {
|
||||
isDrawing.value = false
|
||||
points.value = []
|
||||
return
|
||||
isDrawing.value = false;
|
||||
points.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是多边形且点数大于2,闭合多边形
|
||||
if (drawMode.value === 'polygon' && points.value.length > 2) {
|
||||
const firstPoint = points.value[0]
|
||||
const lastPoint = points.value[points.value.length - 1]
|
||||
const firstPoint = points.value[0];
|
||||
const lastPoint = points.value[points.value.length - 1];
|
||||
|
||||
// 确保多边形是闭合的
|
||||
if (firstPoint.x !== lastPoint.x || firstPoint.y !== lastPoint.y) {
|
||||
points.value.push({x: firstPoint.x, y: firstPoint.y})
|
||||
points.value.push({x: firstPoint.x, y: firstPoint.y});
|
||||
}
|
||||
}
|
||||
|
||||
completedShapes.value.push({
|
||||
// 创建新的图形对象
|
||||
const newShape = {
|
||||
type: drawMode.value,
|
||||
points: [...points.value]
|
||||
})
|
||||
};
|
||||
|
||||
// 添加新的电子围栏配置
|
||||
fenceAreas.value.push({
|
||||
name: `${drawMode.value === 'polygon' ? '围栏区域' : '直线'} ${fenceAreas.value.length + 1}`,
|
||||
algorithm: 'alg-001',
|
||||
// 添加到已完成图形数组
|
||||
completedShapes.value.push(newShape);
|
||||
|
||||
// 根据事件名称自动设置电子围栏名称和算法
|
||||
let fenceName = '围栏区域';
|
||||
let algorithmId = 'alg-001';
|
||||
|
||||
if (activeEvent.value.name === '船舶靠泊') {
|
||||
fenceName = '岸边';
|
||||
algorithmId = 'alg-001';
|
||||
} else if (activeEvent.value.name === '船舶离泊') {
|
||||
fenceName = '岸边';
|
||||
algorithmId = 'alg-002';
|
||||
} else if (activeEvent.value.name === '人员登轮') {
|
||||
fenceName = '岸边平面';
|
||||
algorithmId = 'alg-003';
|
||||
} else if (activeEvent.value.name === '人员离轮') {
|
||||
fenceName = '船舶楼梯上层平面';
|
||||
algorithmId = 'alg-004';
|
||||
} else if (activeEvent.value.name === '电脑弹窗') {
|
||||
fenceName = '个人办公区域';
|
||||
algorithmId = 'alg-005';
|
||||
}
|
||||
|
||||
// 添加到当前事件组的电子围栏配置
|
||||
if (activeEvent.value) {
|
||||
activeEvent.value.fenceAreas.push({
|
||||
name: `${fenceName}`,
|
||||
algorithm: algorithmId,
|
||||
points: [...points.value],
|
||||
type: drawMode.value
|
||||
type: drawMode.value,
|
||||
condition: algorithms.value.find(a => a.id === algorithmId)?.condition || ''
|
||||
});
|
||||
|
||||
// 确保completedShapes与fenceAreas同步
|
||||
activeEvent.value.completedShapes = [...completedShapes.value];
|
||||
}
|
||||
|
||||
// 重置当前绘制状态
|
||||
points.value = [];
|
||||
isDrawing.value = false;
|
||||
|
||||
// 立即重绘画布
|
||||
redrawCanvas();
|
||||
}
|
||||
|
||||
// 修改redrawCanvas方法
|
||||
const redrawCanvas = () => {
|
||||
if (!ctx.value || !drawingCanvas.value) return
|
||||
|
||||
// 清除画布
|
||||
ctx.value.clearRect(0, 0, drawingCanvas.value.width, drawingCanvas.value.height)
|
||||
|
||||
// 绘制所有已完成的图形
|
||||
completedShapes.value.forEach((shape, index) => {
|
||||
// 如果是选中的图形,用不同颜色显示
|
||||
if (index === selectedShapeIndex.value) {
|
||||
ctx.value.fillStyle = 'rgba(255, 0, 0, 0.3)'
|
||||
ctx.value.strokeStyle = '#ff0000'
|
||||
} else {
|
||||
ctx.value.fillStyle = 'rgba(124, 208, 255, 0.3)'
|
||||
ctx.value.strokeStyle = '#7cd0ff'
|
||||
}
|
||||
|
||||
// 绘制图形
|
||||
drawShape(shape)
|
||||
})
|
||||
|
||||
// 绘制当前正在绘制的图形
|
||||
if (points.value.length > 0) {
|
||||
// 绘制点
|
||||
points.value.forEach((point, index) => {
|
||||
ctx.value.beginPath()
|
||||
ctx.value.arc(point.x, point.y, 5, 0, Math.PI * 2)
|
||||
ctx.value.fillStyle = index === clickedPointIndex.value ? '#ff0000' : '#ff7cd0'
|
||||
ctx.value.fill()
|
||||
ctx.value.strokeStyle = '#fff'
|
||||
ctx.value.lineWidth = 1
|
||||
ctx.value.stroke()
|
||||
})
|
||||
|
||||
// 绘制连线
|
||||
if (points.value.length > 1 || mousePos.value) {
|
||||
ctx.value.beginPath()
|
||||
ctx.value.moveTo(points.value[0].x, points.value[0].y)
|
||||
|
||||
if (drawMode.value === 'polygon') {
|
||||
for (let i = 1; i < points.value.length; i++) {
|
||||
ctx.value.lineTo(points.value[i].x, points.value[i].y)
|
||||
}
|
||||
} else if (drawMode.value === 'line') {
|
||||
const endPoint = mousePos.value || points.value[1] || points.value[0]
|
||||
ctx.value.lineTo(endPoint.x, endPoint.y)
|
||||
}
|
||||
|
||||
ctx.value.strokeStyle = '#7cd0ff'
|
||||
ctx.value.lineWidth = 2
|
||||
ctx.value.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 修改drawShape方法
|
||||
const drawShape = (shape) => {
|
||||
if (!shape || !shape.points || shape.points.length < 2) return
|
||||
|
||||
ctx.value.beginPath()
|
||||
ctx.value.moveTo(shape.points[0].x, shape.points[0].y)
|
||||
|
||||
for (let i = 1; i < shape.points.length; i++) {
|
||||
ctx.value.lineTo(shape.points[i].x, shape.points[i].y)
|
||||
}
|
||||
|
||||
if (shape.type === 'polygon') {
|
||||
ctx.value.closePath()
|
||||
ctx.value.fill()
|
||||
}
|
||||
|
||||
ctx.value.stroke()
|
||||
|
||||
// 绘制顶点
|
||||
shape.points.forEach(point => {
|
||||
drawPoint(point)
|
||||
})
|
||||
}
|
||||
|
||||
// 修改resetDrawing方法
|
||||
const resetDrawing = () => {
|
||||
points.value = []
|
||||
completedShapes.value = []
|
||||
if (activeEvent.value) {
|
||||
activeEvent.value.fenceAreas = []
|
||||
activeEvent.value.completedShapes = []
|
||||
}
|
||||
selectedShapeIndex.value = null
|
||||
isDrawing.value = false
|
||||
redrawCanvas()
|
||||
if (ctx.value && drawingCanvas.value) {
|
||||
ctx.value.clearRect(0, 0, drawingCanvas.value.width, drawingCanvas.value.height)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理右键点击事件
|
||||
@ -514,7 +727,9 @@ const deleteSelectedShape = () => {
|
||||
// 从已完成图形数组中移除
|
||||
completedShapes.value.splice(selectedShapeIndex.value, 1)
|
||||
// 从电子围栏配置中移除
|
||||
fenceAreas.value.splice(selectedShapeIndex.value, 1)
|
||||
if (activeEvent.value) {
|
||||
activeEvent.value.fenceAreas.splice(selectedShapeIndex.value, 1)
|
||||
}
|
||||
|
||||
selectedShapeIndex.value = null
|
||||
redrawCanvas()
|
||||
@ -525,7 +740,7 @@ const isPointInShape = (point, shape) => {
|
||||
if (shape.type === 'polygon') {
|
||||
return isPointInPolygon(point, shape.points)
|
||||
} else if (shape.type === 'line') {
|
||||
return isPointNearLine(point, shape.points[0, shape.points[1]])
|
||||
return isPointNearLine(point, shape.points[0], shape.points[1])
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -587,88 +802,9 @@ const isPointNearLine = (point, lineStart, lineEnd) => {
|
||||
return distance < 15 // 15像素范围内认为是在直线附近
|
||||
}
|
||||
|
||||
// 重绘画布
|
||||
const redrawCanvas = () => {
|
||||
if (!ctx.value || !drawingCanvas.value) return
|
||||
|
||||
ctx.value.clearRect(0, 0, drawingCanvas.value.width, drawingCanvas.value.height)
|
||||
|
||||
// 绘制所有已完成的图形
|
||||
completedShapes.value.forEach((shape, index) => {
|
||||
// 如果是选中的图形,用不同颜色显示
|
||||
if (index === selectedShapeIndex.value) {
|
||||
ctx.value.fillStyle = 'rgba(255, 0, 0, 0.3)'
|
||||
ctx.value.strokeStyle = '#ff0000'
|
||||
} else {
|
||||
ctx.value.fillStyle = 'rgba(124, 208, 255, 0.3)'
|
||||
ctx.value.strokeStyle = '#7cd0ff'
|
||||
}
|
||||
|
||||
drawShape(shape)
|
||||
})
|
||||
|
||||
// 绘制当前正在绘制的图形
|
||||
if (points.value.length > 0) {
|
||||
// 绘制点(当前绘制中的点用不同颜色显示)
|
||||
points.value.forEach((point, index) => {
|
||||
ctx.value.beginPath()
|
||||
ctx.value.arc(point.x, point.y, 5, 0, Math.PI * 2)
|
||||
// 如果是右键选中的点,用红色显示
|
||||
ctx.value.fillStyle = index === clickedPointIndex.value ? '#ff0000' : '#ff7cd0'
|
||||
ctx.value.fill()
|
||||
ctx.value.strokeStyle = '#fff'
|
||||
ctx.value.lineWidth = 1
|
||||
ctx.value.stroke()
|
||||
})
|
||||
|
||||
// 绘制连线或预览线
|
||||
if (points.value.length > 1 || mousePos.value) {
|
||||
ctx.value.beginPath()
|
||||
ctx.value.moveTo(points.value[0].x, points.value[0].y)
|
||||
|
||||
// 如果是多边形模式,绘制所有点之间的连线
|
||||
if (drawMode.value === 'polygon') {
|
||||
for (let i = 1; i < points.value.length; i++) {
|
||||
ctx.value.lineTo(points.value[i].x, points.value[i].y)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是直线模式,绘制到鼠标当前位置的预览线
|
||||
if (drawMode.value === 'line') {
|
||||
const endPoint = mousePos.value || points.value[1] || points.value[0]
|
||||
ctx.value.lineTo(endPoint.x, endPoint.y)
|
||||
}
|
||||
|
||||
ctx.value.strokeStyle = '#7cd0ff'
|
||||
ctx.value.lineWidth = 2
|
||||
ctx.value.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制图形
|
||||
const drawShape = (shape) => {
|
||||
if (shape.points.length < 2) return
|
||||
|
||||
ctx.value.beginPath()
|
||||
ctx.value.moveTo(shape.points[0].x, shape.points[0].y)
|
||||
|
||||
for (let i = 1; i < shape.points.length; i++) {
|
||||
ctx.value.lineTo(shape.points[i].x, shape.points[i].y)
|
||||
}
|
||||
|
||||
if (shape.type === 'polygon') {
|
||||
ctx.value.closePath()
|
||||
ctx.value.fill()
|
||||
}
|
||||
|
||||
ctx.value.stroke()
|
||||
|
||||
// 绘制图形的顶点
|
||||
shape.points.forEach(point => {
|
||||
drawPoint(point)
|
||||
})
|
||||
}
|
||||
|
||||
// 绘制点
|
||||
const drawPoint = (point) => {
|
||||
@ -681,16 +817,32 @@ const drawPoint = (point) => {
|
||||
ctx.value.stroke()
|
||||
}
|
||||
|
||||
// 重置绘制
|
||||
const resetDrawing = () => {
|
||||
points.value = []
|
||||
completedShapes.value = []
|
||||
fenceAreas.value = []
|
||||
selectedShapeIndex.value = null
|
||||
isDrawing.value = false
|
||||
if (ctx.value && drawingCanvas.value) {
|
||||
ctx.value.clearRect(0, 0, drawingCanvas.value.width, drawingCanvas.value.height)
|
||||
}
|
||||
|
||||
|
||||
// 上传视频相关方法
|
||||
const handleUploadClick = () => {
|
||||
uploadDialogVisible.value = true
|
||||
fileList.value = []
|
||||
selectedFile.value = null
|
||||
}
|
||||
|
||||
const handleVideoChange = (file) => {
|
||||
selectedFile.value = file.raw
|
||||
}
|
||||
|
||||
const confirmUpload = () => {
|
||||
if (!selectedFile.value) return
|
||||
|
||||
// 这里可以添加文件上传逻辑
|
||||
console.log('上传文件:', selectedFile.value)
|
||||
|
||||
// 模拟上传成功
|
||||
ElMessage.success('视频上传成功')
|
||||
uploadDialogVisible.value = false
|
||||
}
|
||||
|
||||
const closeEditor = () => {
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -700,7 +852,6 @@ const resetDrawing = () => {
|
||||
background: #0a1d3c;
|
||||
padding: 20px;
|
||||
width: 100vw;
|
||||
|
||||
}
|
||||
|
||||
.editor-content {
|
||||
@ -851,11 +1002,34 @@ const resetDrawing = () => {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.event-group-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.event-group {
|
||||
background: rgba(5, 18, 42, 0.6);
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.event-group.active {
|
||||
border: 1px solid #00d2ff;
|
||||
}
|
||||
|
||||
.algorithm-condition-hint {
|
||||
margin-top: 15px;
|
||||
padding: 8px 12px;
|
||||
background-color: rgba(255, 77, 79, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.hint-text {
|
||||
color: #ff4d4f;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
@ -944,6 +1118,7 @@ const resetDrawing = () => {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.delete-polygon-btn {
|
||||
|
@ -4,6 +4,11 @@ import path from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
host: '0.0.0.0', // 监听所有网络接口
|
||||
port: 5173, // 默认端口
|
||||
strictPort: true // 如果端口被占用则退出
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
|
Loading…
x
Reference in New Issue
Block a user