解决冲突

This commit is contained in:
xhf 2025-02-13 14:29:02 +08:00
commit 8ef2938d10
8 changed files with 631 additions and 297 deletions

View File

@ -1,6 +1,6 @@
<template>
<Dialog v-model="dialogFormVisible" title="节点属性" width="540" class="node-form-dialog">
<el-form :model="form" label-width="auto">
<el-form :model="form" label-width="auto" ref="ruleFormRef">
<el-form-item label="X" prop="locationX" required>
<el-input v-model="form.locationX" placeholder="请输入" />
</el-form-item>
@ -17,8 +17,14 @@
<el-option label="等待点" :value="6" />
</el-select>
</el-form-item>
<div v-if="form.type !== 1 && form.type !== 6">
<el-form-item label="层数" prop="layersNumber" required v-if="form.type === 2">
<div v-if="form.type === 2 || form.type === 3 || form.type === 4">
<el-form-item
label="层数"
prop="layersNumber"
:rules="{ required: true, message: '请输入层数', trigger: 'change' }"
required
v-if="form.type === 2"
>
<el-input-number v-model="form.layersNumber" :min="1" :max="3" />
</el-form-item>
<el-form-item
@ -102,7 +108,7 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button type="primary" @click="submit"> 确认 </el-button>
<el-button type="primary" @click="submit(ruleFormRef)"> 确认 </el-button>
</div>
</template>
</Dialog>
@ -113,6 +119,7 @@ import { reactive, ref } from 'vue'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as MapApi from '@/api/map/map'
const ruleFormRef = ref()
const dialogFormVisible = ref(false)
const message = useMessage() //
@ -124,75 +131,93 @@ const props = defineProps({
})
const form = ref({
type: 1,
type: 1, // 1. 2. 3. 4. 5. 6.
layersNumber: 1, //
locationX: undefined,
locationY: undefined,
locationX: undefined, //x
locationY: undefined, //y
locationDeep: undefined, //
locationWide: undefined, //
direction: 1, //
inDirection: undefined,
outDirection: undefined,
list: []
direction: undefined, //1234
inDirection: undefined, //01
outDirection: undefined, //01
dataList: [], //
dataObj: {}, //
positionMapId: undefined
})
const rules = reactive({
locationX: [{ required: true, message: '请输入X', trigger: 'blur' }],
locationY: [{ required: true, message: '请输入Y', trigger: 'blur' }],
type: [{ required: true, message: '请选择类型', trigger: 'blur' }],
direction: [{ required: true, message: '请输入层数', trigger: 'blur' }],
layersNumber: [{ required: true, message: '请输入层数', trigger: 'blur' }],
id: [{ required: true, message: '请选择设备编号', trigger: 'blur' }]
})
const emit = defineEmits(['submitNodeSuccess'])
const submit = () => {
if (form.value.type === 1 || form.value.type === 6) {
form.value.dataJson = ''
} else if (form.value.type === 2) {
const submit = async (formEl) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (!valid) return
//
let list = []
for (let index = 0; index < form.value.layersNumber; index++) {
list.push({
locationWide: form.value.locationWide || undefined,
locationDeep: form.value.locationDeep || undefined,
direction: form.value.direction || undefined, //
inDirection: form.value.inDirection || undefined, //
outDirection: form.value.outDirection || undefined, //
locationStorey: index + 1 //
})
if (form.value.type === 1 || form.value.type === 5 || form.value.type === 6) {
//dataJson
form.value.dataJson = ''
} else if (form.value.type === 2) {
//
for (let index = 0; index < form.value.layersNumber; index++) {
if (
form.value.dataList.length > 0 &&
form.value.dataList[index] &&
form.value.dataList[index].laneId
) {
form.value.dataList[index].locationWide = form.value.locationWide
form.value.dataList[index].locationDeep = form.value.locationDeep
form.value.dataList[index].direction = form.value.direction //
form.value.dataList[index].inDirection = form.value.inDirection //
form.value.dataList[index].outDirection = form.value.outDirection //
form.value.dataList[index].locationStorey = index + 1 //
} else {
form.value.dataList.push({
positionMapId: props.positionMapId,
locationWide: form.value.locationWide || undefined,
locationDeep: form.value.locationDeep || undefined,
direction: form.value.direction || undefined, //
inDirection: form.value.inDirection || undefined, //
outDirection: form.value.outDirection || undefined, //
locationStorey: index + 1 //
})
}
}
//dataJson
form.value.dataJson = JSON.stringify(form.value.dataList)
} else {
//
form.value.dataObj.positionMapId = props.positionMapId
form.value.dataObj.locationWide = form.value.locationWide
form.value.dataObj.locationDeep = form.value.locationDeep
form.value.dataObj.direction = form.value.direction
form.value.dataObj.inDirection = form.value.inDirection
form.value.dataObj.outDirection = form.value.outDirection
//dataJson
form.value.dataJson = JSON.stringify(form.value.dataObj)
}
form.value.dataJson = JSON.stringify(list)
} else {
let obj = {
id: form.value.id || undefined,
mapId: props.positionMapId,
locationWide: form.value.locationWide || undefined,
locationDeep: form.value.locationDeep || undefined,
direction: form.value.direction || undefined, //
inDirection: form.value.inDirection || undefined, //
outDirection: form.value.outDirection || undefined //
}
form.value.dataJson = JSON.stringify(obj)
}
console.log(form.value, '保存')
emit('submitNodeSuccess', form.value)
dialogFormVisible.value = false
emit('submitNodeSuccess', form.value)
dialogFormVisible.value = false
})
}
const open = (item) => {
form.value = item
form.value.locationX = item.locationX
form.value.locationY = item.locationY
form.value.type = item.type || 1
form.value.layersNumber = item.layersNumber || 1 //1
form.value.positionMapId = props.positionMapId
dialogFormVisible.value = true
}
//
const typeChange = () => {
if (form.value.type === 1 || form.value.type === 6) {
if (form.value.type === 1 || form.value.type === 5 || form.value.type === 6) {
form.value.layersNumber = undefined
form.value.dataJson = ''
} else {
}
}
//
@ -203,16 +228,19 @@ const directionChange = (e) => {
}
}
//
//
const deviceInfo = ref({
positionMapId: '',
deviceType: ''
})
//
const deviceList = ref([])
//
const getDeviceList = async () => {
deviceInfo.value.positionMapId = props.positionMapId
deviceList.value = await MapApi.getDeviceInformationList(deviceInfo.value)
}
//
const deviceTypeChange = () => {
getDeviceList()
}

View File

@ -37,6 +37,29 @@
<script setup>
import { reactive, ref } from 'vue'
const props = defineProps({
inputBoxStyle: {
type: Object,
default: () => {
return {
fontFamily: 'SimSun',
fontSize: '14',
fontColor: '#000000'
}
}
}
})
watch(
() => props.inputBoxStyle,
(val) => {
form.fontFamily = props.inputBoxStyle.fontFamily
form.fontSize = props.inputBoxStyle.fontSize
form.fontColor = props.inputBoxStyle.fontColor
},
{ immediate: false }
)
const form = reactive({
fontFamily: 'SimSun',
fontSize: '14',

View File

@ -41,7 +41,12 @@ import { ref, defineComponent, reactive, nextTick, onMounted } from 'vue'
import * as MapApi from '@/api/map/map'
import WebSocketClient from '../webSocket.js';
const imgUrl = ref('')
const socketClient = ref(null)
const emit = defineEmits(['transmitMapId'])
const list = ref([])
const nowObject = ref(null)
//
@ -62,8 +67,9 @@ const getList = async () => {
}
}
const getPositionMapList = async () => {
let data = await MapApi.getPositionMap()
const getPositionMapListFun = async (positionMapId) => {
console.log(positionMapId)
let data = await MapApi.getPositionMapItemList({positionMapId:positionMapId})
console.log(data)
}
const replaceHttpWithWs = (str) => {
@ -89,17 +95,24 @@ const disconnect = () => {
};
//
const getMapData = async (item) => {
let data = await MapApi.getPositionMapdDwnloadPngBase64({
floor: item.floor,
area: item.area
})
let base64Url = 'data:image/png;base64,'
imgUrl.value = data
nowObject.value = JSON.parse(JSON.stringify(item))
let websoketUrl = `${replaceHttpWithWs(import.meta.env.VITE_BASE_URL)}/infra/ws?type=map&floor=${nowObject.value.floor}&area=${nowObject.value.area}`
console.log(websoketUrl)
linkWebSocket(websoketUrl)
getPositionMapListFun(nowObject.value.id)
emit('transmitMapInfo', {
id: item.id,
floor: item.floor,
area: item.area
})
let data = await MapApi.getPositionMapdDwnloadPngBase64({
floor: item.floor,
area: item.area
})
imgUrl.value = data
}
onMounted(() => {

View File

@ -102,13 +102,7 @@
</div>
</div>
<!-- @mousewheel.prevent="rollImg" -->
<div
class="map-box"
ref="imgWrap"
style="overflow: hidden"
:style="{ cursor: state.cursorStyle }"
>
<div class="map-box" ref="imgWrap" :style="{ cursor: state.cursorStyle }">
<div
class="map-box-inner"
ref="image"
@ -116,11 +110,22 @@
@mousemove.stop="updateDrawSelection"
@mouseup.stop="endDrawSelection"
>
<img :src="imgBgObj.imgUrl" class="map-box-img" id="mapBg" />
<img
:src="imgBgObj.imgUrl"
:style="{
width: imgBgObj.width + 'px',
height: imgBgObj.height + 'px'
}"
id="mapBg"
/>
<div
class="map-box-inner-dot"
@click="mapClick"
:class="state.isShowGrid ? 'grid-show' : ''"
:style="{
width: imgBgObj.width + 'px',
height: imgBgObj.height + 'px'
}"
v-if="interfaceRefreshed"
>
<VueDragResizeRotate
@ -143,31 +148,83 @@
:lock-aspect-ratio="item.lockAspectRatio"
style="border: none"
>
<!-- 1 路径点 -->
<div
class="sdiv"
:style="
currentItemIndex === index ? 'border: 1px dashed #000;box-sizing: border-box;' : ''
"
v-if="item.type === 1"
:style="{
width: item.locationWide + 'px',
height: item.locationDeep + 'px',
border: ' 1px dashed #000',
borderRadius: '50%',
backgroundColor: '#000'
}"
>
<!-- <img
:src="item.img"
alt=""
style="width: 100%; height: 100%"
/> -->
<div
v-if="item.type !== 7"
style="width: 100%; height: 100%; background-color: #000; border-radius: 50%"
></div>
<div
v-if="item.type === 7"
:style="{
fontSize: item.fontSize + 'px',
fontFamily: item.fontFamily,
color: item.fontColor
}"
>
{{ item.text }}
</div>
</div>
<!-- 2 库位点 -->
<img
v-if="item.type === 2"
src="https://api.znkjfw.com/admin-api/infra/file/4/get/库位库存_png_179_1739326653035.png"
alt=""
:style="{
width: item.locationWide + 'px',
height: item.locationDeep + 'px',
border: currentItemIndex === index ? '1px dashed #000' : 'none'
}"
/>
<!-- 3 设备点 -->
<img
v-if="item.type === 3"
src="https://api.znkjfw.com/admin-api/infra/file/4/get/设备点_png_179_1739327151877.png"
alt=""
:style="{
width: item.locationWide + 'px',
height: item.locationDeep + 'px',
border: currentItemIndex === index ? '1px dashed #000' : 'none'
}"
/>
<!-- 4 停车点 -->
<img
v-if="item.type === 4"
src="https://api.znkjfw.com/admin-api/infra/file/4/get/停车场-01_png_179_1739326933020.png"
alt=""
:style="{
width: item.locationWide + 'px',
height: item.locationDeep + 'px',
border: currentItemIndex === index ? '1px dashed #000' : 'none'
}"
/>
<!-- 5 区域变更点 -->
<img
v-if="item.type === 5"
src="https://api.znkjfw.com/admin-api/infra/file/4/get/区域_png_179_1739327151876.png"
alt=""
:style="{
width: item.locationWide + 'px',
height: item.locationDeep + 'px',
border: currentItemIndex === index ? '1px dashed #000' : 'none'
}"
/>
<!-- 6 等待点 -->
<img
v-if="item.type === 6"
src="https://api.znkjfw.com/admin-api/infra/file/4/get/等待点_png_179_1739326991439.png"
alt=""
:style="{
width: item.locationWide + 'px',
height: item.locationDeep + 'px',
border: currentItemIndex === index ? '1px dashed #000' : 'none'
}"
/>
<div
v-if="item.type === 7"
:style="{
color: item.fontColor,
fontSize: item.fontSize + 'px',
fontFamily: item.fontFamily,
border: currentItemIndex === index ? '1px dashed #000' : 'none'
}"
>
{{ item.text }}
</div>
</VueDragResizeRotate>
<!-- 文档 https://github.com/a7650/vue3-draggable-resizable/blob/main/docs/document_zh.md#resizable -->
@ -215,6 +272,7 @@
<textFormToolDialog
ref="textFormToolDialogRef"
v-if="state.textFormToolShow"
:inputBoxStyle="inputBoxStyle"
@textFormSuccess="textFormSuccess"
/>
<!-- 图层选择 -->
@ -278,12 +336,11 @@ const rotateEnd = (angle, item, index) => {
//
const editNodePropertiesRef = ref()
const activatedHandle = (item, index) => {
// console.log('', item, index)
currentItemIndex.value = index
//
if (toolbarSwitchType.value === 'editNode' && item.type !== 7) {
editNodePropertiesRef.value.open(item)
editNodePropertiesRef.value.open(JSON.parse(JSON.stringify(item)))
}
}
//
@ -329,9 +386,11 @@ const backNextStep = () => {
//
const mapClick = (e) => {
//
if (toolbarSwitchType.value === 'drawNodes') {
//
allMapPointInfo.value.push({
positionMapId: imgBgObj.positionMapId, //id
locationX: e.offsetX,
locationY: e.offsetY,
locationDeep: 16,
@ -342,7 +401,9 @@ const mapClick = (e) => {
rotatable: false,
lockAspectRatio: false, //
img: '',
type: 1 //1
type: 1, //1
dataList: [], //
dataObj: {} //
})
currentIndex.value++
allHistoryList.value.push(JSON.parse(JSON.stringify(allMapPointInfo.value)))
@ -350,13 +411,8 @@ const mapClick = (e) => {
//
if (toolbarSwitchType.value === 'text') {
state.showInputBox = true
state.inputBoxStyle = {
fontSize: state.textForm.fontSize,
fontFamily: state.textForm.fontFamily,
fontColor: state.textForm.fontColor,
locationX: e.offsetX,
locationY: e.offsetY
}
state.inputBoxStyle.locationX = e.offsetX
state.inputBoxStyle.locationY = e.offsetY
//
setTimeout(() => {
@ -366,7 +422,6 @@ const mapClick = (e) => {
}
//
const textFormSuccess = (form) => {
state.textForm = JSON.parse(JSON.stringify(form))
state.inputBoxStyle = {
fontSize: `${form.fontSize}`,
fontFamily: `${form.fontFamily}`,
@ -379,7 +434,7 @@ const handleInputEnd = () => {
state.showInputBox = false
allMapPointInfo.value.push({
type: 7, // 7
mapId: '', //id
positionMapId: imgBgObj.positionMapId, //id
locationX: state.inputBoxStyle.locationX, //x
locationY: state.inputBoxStyle.locationY, //y
locationDeep: '', //h
@ -393,44 +448,16 @@ const handleInputEnd = () => {
text: state.inputBoxValue, //
fontColor: state.inputBoxStyle.fontColor, //
fontFamily: state.inputBoxStyle.fontFamily, //
fontSize: state.inputBoxStyle.fontSize
fontSize: state.inputBoxStyle.fontSize,
dataObj: {} //
})
addEditHistory()
state.inputBoxValue = ''
}
}
//
const saveMap = async () => {
//
await saveNodeList()
}
//
const saveNodeList = async () => {
formLoading.value = true
let list = allHistoryList.value[currentIndex.value]
list.forEach((item, index) => {
item.locationX = item.locationX
item.locationY = item.locationY
item.type = item.type || 1
if (item.type === 1) {
item.dataJson = ''
} else {
item.dataJson = item.dataJson
}
})
try {
await MapApi.batchSaveOrEditOrDelMapItem(imgBgObj.positionMapId, list)
message.success('创建成功')
} finally {
formLoading.value = false
}
}
//
const submitNodeSuccess = (item) => {
allMapPointInfo.value[currentItemIndex.value] = item
allHistoryList.value[currentIndex.value][currentItemIndex.value] = item
}
@ -612,14 +639,15 @@ const state = reactive({
drawSelectionAreaBox: { x: 0, y: 0, width: 0, height: 0 }, //
drawSelectionAreaStartPos: { x: 0, y: 0 }, //
drawSelectionAreaSelectedPoints: [], //list
textForm: {
fontFamily: 'SimSun',
fontSize: '14',
fontColor: '#000000'
}, //14#000000
textFormToolShow: false, //
showInputBox: false, //
inputBoxStyle: {}, //
inputBoxStyle: {
fontFamily: 'SimSun',
fontSize: '14',
fontColor: '#000000',
locationX: 0,
locationY: 0
}, //
inputBoxValue: '', //
isShowLayer: false //
})
@ -759,9 +787,21 @@ const toolbarClick = (item) => {
break
case 'larger':
//
if (imgBgObj.width < 10000) {
imgBgObj.width *= 1.2
imgBgObj.height *= 1.2
} else {
message.warning('不能在放大了')
}
break
case 'smaller':
//
if (imgBgObj.width > 500) {
imgBgObj.width *= 0.8
imgBgObj.height *= 0.8
} else {
message.warning('不能在缩小了')
}
break
case 'withdraw':
//
@ -824,22 +864,22 @@ const endDrawSelection = () => {
state.drawSelectionArea = false
state.drawSelectionAreaSelectedPoints = points.filter((point) => {
return (
point.x >=
point.locationX >=
Math.min(
state.drawSelectionAreaStartPos.x,
state.drawSelectionAreaStartPos.x + state.drawSelectionAreaBox.width
) &&
point.x <=
point.locationX <=
Math.max(
state.drawSelectionAreaStartPos.x,
state.drawSelectionAreaStartPos.x + state.drawSelectionAreaBox.width
) &&
point.y >=
point.locationY >=
Math.min(
state.drawSelectionAreaStartPos.y,
state.drawSelectionAreaStartPos.y + state.drawSelectionAreaBox.height
) &&
point.y <=
point.locationY <=
Math.max(
state.drawSelectionAreaStartPos.y,
state.drawSelectionAreaStartPos.y + state.drawSelectionAreaBox.height
@ -849,75 +889,130 @@ const endDrawSelection = () => {
console.log(state.drawSelectionAreaSelectedPoints, '选中的')
}
//
const list = ref([])
const getMapList = async () => {
let data = await MapApi.getPositionMapGetMap()
let mapList = []
for (let key in data) {
mapList.push({
floor: key,
children: data[key]
})
}
list.value = mapList
//
if (data[1][0]) {
getMapData(data[1][0])
}
}
//
//
//
const imgBgObj = reactive({
imgUrl: '',
positionMapId: '',
width: '',
height: ''
height: '',
floor: '',
area: ''
})
const getMapData = async (item) => {
//
const { query } = useRoute() //
//
const getMapList = async () => {
if (query.mapId) {
imgBgObj.positionMapId = query.mapId
imgBgObj.floor = query.floor
imgBgObj.area = query.area
//png
getMapData(imgBgObj)
}
}
//png
const getMapData = async (mapInfo) => {
let data = await MapApi.getPositionMapdDwnloadPngBase64({
floor: item.floor,
area: item.area
floor: mapInfo.floor,
area: mapInfo.area
})
imgBgObj.imgUrl = data
imgBgObj.positionMapId = item.id
//
const img = new Image()
img.src = imgBgObj.imgUrl
imgBgObj.width = img.naturalWidth
imgBgObj.height = img.naturalHeight
console.log(imgBgObj)
getAllNodeList()
//
img.onload = () => {
imgBgObj.width = img.naturalWidth
imgBgObj.height = img.naturalHeight
getAllNodeList()
}
//
img.onerror = () => {
console.error('图片加载失败')
}
}
//
//
const getAllNodeList = async () => {
let list = await MapApi.getPositionMapItemList({
positionMapId: imgBgObj.positionMapId
})
list.forEach((item) => {
if (item.type === 2) {
if (item.type === 1 || item.type === 5 || item.type === 6) {
item.dataObj = {}
item.dataList = []
item.locationDeep = 16
item.locationWide = 16
item.draggable = true
item.resizable = false
item.rotatable = false
item.lockAspectRatio = false
} else if (item.type === 2) {
//
let obj = JSON.parse(item.dataJson)[0]
item.dataList = JSON.parse(item.dataJson)
item.locationDeep = item.dataList[0].locationDeep
item.locationWide = item.dataList[0].locationWide
item.draggable = true
item.resizable = false
item.rotatable = false
item.lockAspectRatio = false
} else if (item.type === 3 || item.type === 4) {
item.dataObj = JSON.parse(item.dataJson)
item.dataList = []
item.locationDeep = item.dataObj.locationDeep
item.locationWide = item.dataObj.locationWide
item.draggable = true
item.resizable = false
item.rotatable = false
item.lockAspectRatio = false
} else if (item.type === 7) {
item.dataObj = JSON.parse(item.dataJson)
item.text = item.dataObj.text
item.fontColor = item.dataObj.fontColor
item.fontFamily = item.dataObj.fontFamily
item.fontSize = item.dataObj.fontSize
item.angle = item.dataObj.angle
item.draggable = true
item.resizable = false
item.rotatable = false
item.lockAspectRatio = false
}
allMapPointInfo.value.push(item)
})
allHistoryList.value[0] = JSON.parse(JSON.stringify(allMapPointInfo.value))
}
// allMapPointInfo.value.push()
//
const saveMap = async () => {
//
await saveNodeList()
//线
}
//
const saveNodeList = async () => {
formLoading.value = true
let list = allHistoryList.value[currentIndex.value]
list.forEach((item) => {
if (item.type === 7) {
item.dataObj.positionMapId = imgBgObj.positionMapId
item.dataObj.text = item.text
item.dataObj.fontColor = item.fontColor
item.dataObj.fontType = item.fontType
item.dataObj.fontFamily = item.fontFamily
item.dataObj.rotatable = item.rotatable
item.dataObj.angle = item.angle
//dataJson
item.dataJson = JSON.stringify(item.dataObj)
}
})
// allMapPointInfo.value = list.map((item) => {
// return {
// ...JSON.parse(item.dataJson)
// }
// })
// allMapPointInfo.value = allMapPointInfo.value
// console.log(allMapPointInfo.value)
allHistoryList.value[0] = JSON.parse(JSON.stringify(allMapPointInfo.value))
console.log(allHistoryList.value[0])
try {
await MapApi.batchSaveOrEditOrDelMapItem(imgBgObj.positionMapId, list)
message.success('创建成功')
} finally {
formLoading.value = false
}
}
onMounted(() => {
@ -929,20 +1024,15 @@ onMounted(() => {
.map-box {
width: 100%;
position: relative;
.map-box-inner {
position: relative;
width: 100%;
.map-box-img {
width: 100%;
height: auto;
}
.map-box-inner-dot {
width: 100%;
position: absolute;
left: 0;
top: 0;
bottom: 0;
left: 0;
}
.grid-show {
@ -955,12 +1045,6 @@ onMounted(() => {
}
}
.sdiv {
width: 100%;
height: 100%;
box-sizing: border-box;
}
.top-tool {
.top-tool-list {
display: flex;
@ -989,6 +1073,7 @@ onMounted(() => {
justify-content: center;
.name {
cursor: pointer;
font-family:
PingFangSC,
PingFang SC;

View File

@ -8,7 +8,7 @@
<div class="main-content">
<!-- <div @click="downAgv">导出zip</div> -->
<!-- 首页 -->
<indexPage ref="indexPageRef" />
<indexPage ref="indexPageRef" @transmitMapInfo="transmitMapInfo" />
</div>
<!-- 新建任务的弹窗 -->
@ -46,9 +46,23 @@ const router = useRouter() // 路由
const editMap = () => {
router.push({
name: 'editMapPageRealTimeMap',
query: {}
query: {
mapId: mapInfo.value.id,
floor: mapInfo.value.floor,
area: mapInfo.value.area
}
})
}
//
const mapInfo = ref({
mapId: '',
floor: '',
area: ''
})
const transmitMapInfo = (map) => {
mapInfo.value = map
}
</script>
<style lang="scss" scoped>

View File

@ -0,0 +1,93 @@
<template>
<div
@mousedown="startSelection"
@mousemove="updateSelection"
@mouseup="endSelection"
style="position: relative; width: 100%; height: 900px"
>
<!-- 绘制框选区域 -->
<div
v-if="isSelecting"
:style="{
position: 'absolute',
left: `${selectionBox.x}px`,
top: `${selectionBox.y}px`,
width: `${selectionBox.width}px`,
height: `${selectionBox.height}px`,
border: '1px solid blue',
backgroundColor: 'rgba(0, 0, 255, 0.1)'
}"
></div>
<!-- 绘制点位 -->
<div
v-for="(point, index) in points"
:key="index"
:style="{
position: 'absolute',
left: `${point.x}px`,
top: `${point.y}px`,
width: '100px',
height: '100px',
backgroundColor: selectedPoints.includes(point) ? 'red' : '#fff'
}"
>设备{{ index }}</div
>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const points = ref([
{ x: 210, y: 210 },
{ x: 330, y: 500 },
{ x: 840, y: 440 },
{ x: 230, y: 400 },
{ x: 750, y: 640 }
])
const isSelecting = ref(false)
const selectionBox = ref({ x: 0, y: 0, width: 0, height: 0 })
const startPos = ref({ x: 0, y: 0 })
const selectedPoints = ref([])
const startSelection = (event) => {
isSelecting.value = true
startPos.value = { x: event.offsetX, y: event.offsetY }
selectionBox.value = { x: event.offsetX, y: event.offsetY, width: 0, height: 0 }
}
const updateSelection = (event) => {
if (isSelecting.value) {
selectionBox.value.width = event.offsetX - startPos.value.x
selectionBox.value.height = event.offsetY - startPos.value.y
}
}
const endSelection = () => {
isSelecting.value = false
selectedPoints.value = points.value.filter((point) => {
return (
point.x >= Math.min(startPos.value.x, startPos.value.x + selectionBox.value.width) &&
point.x <= Math.max(startPos.value.x, startPos.value.x + selectionBox.value.width) &&
point.y >= Math.min(startPos.value.y, startPos.value.y + selectionBox.value.height) &&
point.y <= Math.max(startPos.value.y, startPos.value.y + selectionBox.value.height)
)
})
}
return {
points,
isSelecting,
selectionBox,
selectedPoints,
startSelection,
updateSelection,
endSelection
}
}
}
</script>

View File

@ -1,93 +1,180 @@
<template>
<div
@mousedown="startSelection"
@mousemove="updateSelection"
@mouseup="endSelection"
style="position: relative; width: 100%; height: 900px"
>
<!-- 绘制框选区域 -->
<div
v-if="isSelecting"
:style="{
position: 'absolute',
left: `${selectionBox.x}px`,
top: `${selectionBox.y}px`,
width: `${selectionBox.width}px`,
height: `${selectionBox.height}px`,
border: '1px solid blue',
backgroundColor: 'rgba(0, 0, 255, 0.1)'
}"
></div>
<!-- 绘制点位 -->
<div
v-for="(point, index) in points"
:key="index"
:style="{
position: 'absolute',
left: `${point.x}px`,
top: `${point.y}px`,
width: '100px',
height: '100px',
backgroundColor: selectedPoints.includes(point) ? 'red' : '#fff'
}"
>设备{{ index }}</div
<div>
<!-- SVG 画布 -->
<svg
ref="svg"
width="500"
height="300"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
>
<!-- 绘制所有曲线 -->
<path
v-for="(curve, index) in curves"
:key="index"
:d="getCurvePath(curve)"
stroke="#000"
fill="none"
stroke-width="2"
:class="{ selected: selectedCurve === curve }"
@click="svgClick(curve)"
/>
<!-- 绘制控制点和连线 -->
<line
v-if="selectedCurve"
:x1="selectedCurve.start.x"
:y1="selectedCurve.start.y"
:x2="selectedCurve.control1.x"
:y2="selectedCurve.control1.y"
stroke="gray"
stroke-dasharray="2"
/>
<line
v-if="selectedCurve"
:x1="selectedCurve.end.x"
:y1="selectedCurve.end.y"
:x2="selectedCurve.control2.x"
:y2="selectedCurve.control2.y"
stroke="gray"
stroke-dasharray="2"
/>
<!-- 绘制起点终点和控制点 -->
<circle
v-for="(curve, index) in curves"
:key="'start' + index"
:cx="curve.start.x"
:cy="curve.start.y"
r="5"
fill="red"
@mousedown="startDrag($event, curve, 'start')"
/>
<circle
v-for="(curve, index) in curves"
:key="'end' + index"
:cx="curve.end.x"
:cy="curve.end.y"
r="5"
fill="red"
@mousedown="startDrag($event, curve, 'end')"
/>
<circle
v-if="selectedCurve"
:cx="selectedCurve.control1.x"
:cy="selectedCurve.control1.y"
r="5"
fill="green"
@mousedown="startDrag($event, selectedCurve, 'control1')"
/>
<circle
v-if="selectedCurve"
:cx="selectedCurve.control2.x"
:cy="selectedCurve.control2.y"
r="5"
fill="green"
@mousedown="startDrag($event, selectedCurve, 'control2')"
/>
</svg>
<!-- 显示控制点坐标 -->
<div class="points" v-if="selectedCurve">
<p>起点: ({{ selectedCurve.start.x }}, {{ selectedCurve.start.y }})</p>
<p>控制点 1: ({{ selectedCurve.control1.x }}, {{ selectedCurve.control1.y }})</p>
<p>控制点 2: ({{ selectedCurve.control2.x }}, {{ selectedCurve.control2.y }})</p>
<p>终点: ({{ selectedCurve.end.x }}, {{ selectedCurve.end.y }})</p>
</div>
</div>
</template>
<script>
<script setup>
import { ref } from 'vue'
const svg = ref(null) // SVG
const curves = ref([]) // 线
const selectedCurve = ref(null) // 线
const isDragging = ref(false) //
const dragTarget = ref(null) //
export default {
setup() {
const points = ref([
{ x: 210, y: 210 },
{ x: 330, y: 500 },
{ x: 840, y: 440 },
{ x: 230, y: 400 },
{ x: 750, y: 640 }
])
const isSelecting = ref(false)
const selectionBox = ref({ x: 0, y: 0, width: 0, height: 0 })
const startPos = ref({ x: 0, y: 0 })
const selectedPoints = ref([])
const startSelection = (event) => {
isSelecting.value = true
startPos.value = { x: event.offsetX, y: event.offsetY }
selectionBox.value = { x: event.offsetX, y: event.offsetY, width: 0, height: 0 }
}
const updateSelection = (event) => {
if (isSelecting.value) {
selectionBox.value.width = event.offsetX - startPos.value.x
selectionBox.value.height = event.offsetY - startPos.value.y
}
}
const endSelection = () => {
isSelecting.value = false
selectedPoints.value = points.value.filter((point) => {
return (
point.x >= Math.min(startPos.value.x, startPos.value.x + selectionBox.value.width) &&
point.x <= Math.max(startPos.value.x, startPos.value.x + selectionBox.value.width) &&
point.y >= Math.min(startPos.value.y, startPos.value.y + selectionBox.value.height) &&
point.y <= Math.max(startPos.value.y, startPos.value.y + selectionBox.value.height)
)
})
}
return {
points,
isSelecting,
selectionBox,
selectedPoints,
startSelection,
updateSelection,
endSelection
}
//
const getMousePos = (event) => {
const rect = svg.value.getBoundingClientRect()
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top
}
}
//
const startDrag = (event, curve, target) => {
event.preventDefault()
isDragging.value = true
selectedCurve.value = curve
dragTarget.value = target
}
//
const handleMouseDown = (event) => {
const mousePos = getMousePos(event)
// 线
if (!isDragging.value && curves.value.length === 0) {
const newCurve = {
start: mousePos,
end: mousePos,
control1: { x: mousePos.x + 50, y: mousePos.y - 50 },
control2: { x: mousePos.x + 100, y: mousePos.y - 50 }
}
curves.value.push(newCurve)
selectedCurve.value = newCurve
dragTarget.value = 'end'
isDragging.value = true
}
}
//
const handleMouseMove = (event) => {
if (!isDragging.value || !selectedCurve.value) return
const mousePos = getMousePos(event)
//
if (dragTarget.value === 'start') {
selectedCurve.value.start = mousePos
} else if (dragTarget.value === 'end') {
selectedCurve.value.end = mousePos
} else if (dragTarget.value === 'control1') {
selectedCurve.value.control1 = mousePos
} else if (dragTarget.value === 'control2') {
selectedCurve.value.control2 = mousePos
}
}
//
const handleMouseUp = () => {
isDragging.value = false
dragTarget.value = null
}
// 线
const getCurvePath = (curve) => {
return `M ${curve.start.x} ${curve.start.y} C ${curve.control1.x} ${curve.control1.y}, ${curve.control2.x} ${curve.control2.y}, ${curve.end.x} ${curve.end.y}`
}
const svgClick = (item) => {
console.log(item)
}
</script>
<style scoped>
svg {
border: 1px solid #000;
cursor: crosshair;
}
path.selected {
stroke: blue;
}
.points {
margin-top: 10px;
font-size: 14px;
}
</style>

View File

@ -356,17 +356,8 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
required
label="所选车辆电量"
:prop="`taskDetailList[${index}].electricity`"
:rules="{ required: true, message: '所选车辆电量不能为空', trigger: 'change' }"
>
<el-input
v-model="detailItem.electricity"
placeholder="请输入车辆电量"
:disabled="true"
/>
<el-form-item label="所选车辆电量" label-width="96">
<el-input v-model="detailItem.electricity" :disabled="true" />
</el-form-item>
</el-col>
</el-row>