zn-admin-vue3-wcs/src/views/mapPage/realTimeMap/editMap.vue

797 lines
19 KiB
Vue

<template>
<div>
<div class="top-tool-list">
<div v-for="item in state.topToolList" :key="item.id" class="top-tool-item">
<el-popover
placement="bottom"
:width="80"
trigger="click"
v-if="item.id === 5 || item.id === 6"
:disabled="currentItemIndex === -1"
>
<template #reference>
<div
class="tool-item"
:class="
toolbarTypeIndex === item.id
? 'tool-active'
: item.isActive
? 'right-tool-active'
: ''
"
@click="toolbarClick(item)"
>
<Icon :icon="item.icon" :size="24" />
<div class="name"> {{ item.name }} </div>
</div>
</template>
<!-- 位置 -->
<el-form :model="state.moveForm" v-if="item.id === 5">
<el-form-item label="X">
<el-input v-model="state.moveForm.x" placeholder="请输入" />
</el-form-item>
<el-form-item label="Y">
<el-input v-model="state.moveForm.y" placeholder="请输入" />
</el-form-item>
<div style="text-align: right">
<el-button size="small" color="#00329F" @click="moveFormSubmit">确认</el-button>
</div>
</el-form>
<!-- 旋转 -->
<el-form :model="state.rotationForm" v-if="item.id === 6">
<el-form-item label="角度">
<el-input v-model="state.rotationForm.angle" placeholder="请输入" />
</el-form-item>
<div style="text-align: right">
<el-button size="small" color="#00329F" @click="rotationFormSubmit">确认</el-button>
</div>
</el-form>
<!-- 字体 -->
<el-form :model="state.rotationForm" v-if="item.id === 13">
<el-form-item label="角度">
<el-input v-model="state.rotationForm.angle" placeholder="请输入" />
</el-form-item>
<div style="text-align: right">
<el-button size="small" color="#00329F" @click="rotationFormSubmit">确认</el-button>
</div>
</el-form>
</el-popover>
<div
v-else
class="tool-item"
:class="
toolbarTypeIndex === item.id ? 'tool-active' : item.isActive ? 'right-tool-active' : ''
"
@click="toolbarClick(item)"
>
<Icon :icon="item.icon" :size="24" />
<div class="name"> {{ item.name }} </div>
</div>
<div class="line" v-if="item.id === 3 || item.id === 10 || item.id === 17"></div>
</div>
</div>
<div class="right-tool-list" v-if="state.isShowToolbar">
<div
v-for="item in state.rightToolList"
:key="item.id"
class="tool-item"
:class="
toolbarTypeIndex === item.id ? 'tool-active' : item.isActive ? 'right-tool-active' : ''
"
@click="toolbarClick(item)"
>
<Icon :icon="item.icon" :size="24" />
<div class="name"> {{ item.name }} </div>
</div>
</div>
</div>
<div
class="map-box"
ref="imgWrap"
@mousewheel.prevent="rollImg"
style="overflow: hidden"
:style="{ cursor: state.cursorStyle }"
>
<div class="map-box-inner" ref="image" @mousedown.prevent="moveImg">
<img :src="imgBgObj.imgUrl" class="map-box-img" id="mapBg" />
<div
class="map-box-inner-dot"
@click="mapClick"
:class="state.isShowGrid ? 'grid-show' : ''"
v-if="test"
>
<VueDragResizeRotate
v-for="(item, index) in allHistoryList[currentIndex]"
:key="index"
:parent="true"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:r="item.angle"
@rotatestop="(degree) => rotateEnd(degree, item, index)"
@dragstop="(x, y) => dragEnd(x, y, item, index)"
@resizestop="(x, y, width, height) => resizeEnd(x, y, width, height, item, index)"
@activated="() => activatedHandle(item, index)"
@deactivated="deactivatedHandle"
:draggable="item.draggable"
:resizable="item.resizable"
:rotatable="item.rotatable"
:lock-aspect-ratio="item.lockAspectRatio"
:handles="['tl', 'tr', 'bl', 'br', 'rot']"
style="border: none"
>
<div
class="sdiv"
:style="
currentItemIndex === index ? 'border: 1px dashed #000;box-sizing: border-box;' : ''
"
>
<img
v-if="item.labelType === 'icon'"
:src="item.img"
alt=""
style="width: 100%; height: 100%"
/>
<div
v-if="item.labelType === 'node'"
style="width: 100%; height: 100%; background-color: #000; border-radius: 50%"
></div>
</div>
</VueDragResizeRotate>
<!-- 文档 https://github.com/a7650/vue3-draggable-resizable/blob/main/docs/document_zh.md#resizable -->
</div>
</div>
</div>
<!-- 节点编辑 -->
<editNodeProperties
ref="editNodePropertiesRef"
:positionMapId="imgBgObj.positionMapId"
@submitNodeSuccess="submitNodeSuccess"
/>
<!-- 文字输入弹窗 -->
<textFormToolDialog ref="textFormToolDialogRef" />
</template>
<script setup>
import { ref, defineComponent, reactive, nextTick, onMounted } from 'vue'
import editNodeProperties from './components-tool/editNodeProperties.vue'
import textFormToolDialog from './components-tool/textFormToolDialog.vue'
import * as MapApi from '@/api/map/map'
import cursorCollection from './cursorCollection'
defineOptions({ name: 'editMapPageRealTimeMap' })
const textFormToolDialogRef = ref() //文字输入弹窗
const message = useMessage() // 消息弹窗
const formLoading = ref(false)
const allHistoryList = ref([]) //全部的历史记录
const currentIndex = ref(0) //用于记录是哪条历史记录的
const currentItemIndex = ref(-1) //用于记录是在编辑那个具体的节点、图标等
const imgBgObj = reactive({
imgUrl: '',
positionMapId: ''
}) //地图背景
const imgList = ref([]) //所有的图标的列表
//获取地图点位
const list = ref([])
const getList = 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][1]) {
getMapData(data[1][1])
}
}
//获取扫描图
const getMapData = async (item) => {
let data = await MapApi.getPositionMapdDwnloadPngBase64({
floor: item.floor,
area: item.area
})
let base64Url = 'data:image/png;base64,'
imgBgObj.imgUrl = data
imgBgObj.positionMapId = item.id
}
// 缩放停止
const test = ref(true)
const resizeEnd = (x, y, w, h, item, index) => {
test.value = false
console.log('缩放', w, h)
nextTick(() => {
imgList.value[index].x = x
imgList.value[index].y = y
imgList.value[index].w = w
imgList.value[index].h = h
addEditHistory()
})
}
// 拖拽停止
const dragEnd = (x, y, item, index) => {
console.log('拖拽')
if (x === item.x && y === item.y) return
imgList.value[index].x = x
imgList.value[index].y = y
addEditHistory()
}
// 旋转
const rotateEnd = (angle, item, index) => {
console.log('旋转')
imgList.value[index].angle = angle
addEditHistory()
}
//选中
const editNodePropertiesRef = ref()
const activatedHandle = (item, index) => {
// console.log('选中', item, index)
currentItemIndex.value = index
if (item.labelType === 'node') {
if (toolbarTypeIndex.value === 23) {
editNodePropertiesRef.value.open(item)
}
}
}
//非选中
const deactivatedHandle = () => {
console.log('取消选中')
}
//添加历史记录
const addEditHistory = () => {
//要判断是不是在最新的记录上修改 如果不是 要把currentIndex之后的记录都删掉 保持当前修改是最新的
if (currentIndex.value !== allHistoryList.value.length - 1) {
allHistoryList.value = allHistoryList.value.splice(0, currentIndex.value)
currentIndex.value = allHistoryList.value.length
allHistoryList.value.push(JSON.parse(JSON.stringify(imgList.value)))
} else {
allHistoryList.value.push(JSON.parse(JSON.stringify(imgList.value)))
currentIndex.value++
console.log(currentIndex.value)
console.log(
allHistoryList.value[currentIndex.value][currentItemIndex.value],
allHistoryList.value[currentIndex.value][currentItemIndex.value]
)
}
test.value = true
console.log(allHistoryList.value, 'allHistoryList')
}
//上一步
const backPreviousStep = () => {
if (currentIndex.value > 0) {
currentIndex.value--
imgList.value = allHistoryList.value[currentIndex.value]
console.log('撤销', allHistoryList.value[currentIndex.value])
} else {
message.warning('没了老铁')
}
}
//下一步
const backNextStep = () => {
if (currentIndex.value < allHistoryList.value.length - 1) {
currentIndex.value++
imgList.value = allHistoryList.value[currentIndex.value]
console.log('重做', allHistoryList.value[currentIndex.value])
} else {
message.warning('没了老铁')
}
}
//地图点击
const mapClick = (e) => {
if (toolbarTypeIndex.value === 22) {
//绘制节点
imgList.value.push({
x: e.offsetX,
y: e.offsetY,
h: 16,
w: 16,
angle: 0,
draggable: false,
resizable: false,
rotatable: false,
lockAspectRatio: false, //横纵比
img: '',
labelType: 'node'
})
currentIndex.value++
allHistoryList.value.push(JSON.parse(JSON.stringify(imgList.value)))
}
}
//保存地图
const saveMap = async () => {
//节点的保存
await saveNodeList()
}
//节点的保存
const saveNodeList = async () => {
formLoading.value = true
let list = allHistoryList.value[currentIndex.value].filter((item) => {
return item.labelType === 'node'
})
list.forEach((item, index) => {
item.locationX = item.locationX || item.x
item.locationY = item.locationY || item.y
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) => {
allHistoryList.value[currentIndex.value][currentItemIndex.value] = item
}
//工具栏点击
const toolbarTypeIndex = ref(0)
const state = reactive({
topToolList: [
{
id: 1,
name: '打开',
icon: 'ep:folder-add',
isActive: false
},
{
id: 2,
name: '保存',
icon: 'ep:folder-checked',
isActive: false
},
{
id: 3,
name: '另存为',
icon: 'ep:folder-opened',
isActive: false
},
{
id: 4,
name: '选择',
icon: 'ep:position',
isActive: false
},
{
id: 5,
name: '移动',
icon: 'ep:rank',
isActive: false
},
{
id: 6,
name: '旋转',
icon: 'ep:folder-add',
isActive: false
},
{
id: 7,
name: '复制',
icon: 'ep:document',
isActive: false
},
{
id: 8,
name: '粘贴',
icon: 'ep:copy-document',
isActive: false
},
{
id: 9,
name: '删除',
icon: 'ep:delete',
isActive: false
},
{
id: 10,
name: '工具',
icon: 'ep:briefcase',
isActive: false
},
{
id: 11,
name: '线库',
icon: 'ep:folder-add',
isActive: false
},
{
id: 12,
name: '区域',
icon: 'ep:folder-add',
isActive: false
},
{
id: 13,
name: '文字',
icon: 'ep:folder-add',
isActive: false
},
{
id: 14,
name: '测距',
icon: 'ep:folder-add',
isActive: false
},
{
id: 15,
name: '图层',
icon: 'ep:folder-add',
isActive: false
},
{
id: 16,
name: '标记',
icon: 'ep:folder-add',
isActive: false
},
{
id: 17,
name: '网格',
icon: 'ep:folder-add',
isActive: false
},
{
id: 18,
name: '放大',
icon: 'ep:zoom-in',
isActive: false
},
{
id: 19,
name: '缩小',
icon: 'ep:zoom-out',
isActive: false
},
{
id: 20,
name: '撤回',
icon: 'ep:top-left',
isActive: false
},
{
id: 21,
name: '重做',
icon: 'ep:top-right',
isActive: false
}
],
rightToolList: [
{
id: 22,
name: '绘制节点',
icon: 'ep:circle-plus-filled',
isActive: false
},
{
id: 23,
name: '编辑节点',
icon: 'ep:circle-plus-filled',
isActive: false
},
{
id: 24,
name: '绘制路线',
icon: 'ep:semi-select',
isActive: false
},
{
id: 25,
name: '编辑路线',
icon: 'ep:semi-select',
isActive: false
}
],
isShowToolbar: false, //工具栏展示隐藏
isShowGrid: false, //网格展示隐藏
moveForm: {
x: '',
y: ''
}, //移动的表单
rotationForm: {
angle: ''
}, //旋转的表单
copyMapItem: '', //复制的值
cursorStyle: 'auto'
})
const toolbarClick = (item) => {
let type = item.id
if (currentItemIndex.value === -1 && (type === 5 || type === 6 || type === 7 || type === 9)) {
message.warning('请先选择要操作的对象')
return
}
if (type === 8 && !state.copyMapItem) {
message.warning('请先复制对象')
return
}
if ((type === 10 || type === 13 || type === 17) && toolbarTypeIndex.value === type) {
toolbarTypeIndex.value = 0
} else {
toolbarTypeIndex.value = type
}
switch (type) {
case 1:
break
case 2:
saveMap()
break
case 3:
break
case 4:
break
case 5:
break
case 6:
break
case 7:
//复制
state.copyMapItem = allHistoryList.value[currentIndex.value][currentItemIndex.value]
break
case 8:
//粘贴
let item = JSON.parse(JSON.stringify(state.copyMapItem))
item.x = item.x + 50
item.y = item.y + 50
imgList.value.push(item)
addEditHistory()
break
case 9:
//删除
imgList.value.splice(currentItemIndex.value, 1)
addEditHistory()
break
case 10:
//工具
if (toolbarTypeIndex.value === 10) {
state.isShowToolbar = true
} else {
state.isShowToolbar = false
}
break
case 11:
break
case 12:
break
case 13:
//文字
textFormToolDialogRef.value.open()
// if (toolbarTypeIndex.value === 13) {
// state.cursorStyle = `url('${cursorCollection.input}'),auto`
// } else {
// state.cursorStyle = `auto`
// }
break
case 14:
break
case 15:
break
case 16:
break
case 17:
//网格
if (toolbarTypeIndex.value === 17) {
state.isShowGrid = true
item.isActive = true
} else {
state.isShowGrid = false
item.isActive = false
}
break
case 18:
break
case 19:
break
case 20:
//上一步
backPreviousStep()
break
case 21:
//下一步
backNextStep()
break
case 22:
drawNodes()
break
case 23:
break
case 24:
break
case 25:
break
}
}
//移动工具表单提交
const moveFormSubmit = () => {
allHistoryList.value[currentIndex.value][currentItemIndex.value].x = state.moveForm.x
allHistoryList.value[currentIndex.value][currentItemIndex.value].y = state.moveForm.y
}
//旋转工具表单提交
const rotationFormSubmit = () => {
allHistoryList.value[currentIndex.value][currentItemIndex.value].angle = state.rotationForm.angle
}
onMounted(() => {
imgList.value = [
{
x: 100,
y: 100,
h: 100,
w: 100,
angle: 13,
draggable: true,
resizable: true,
rotatable: true,
lockAspectRatio: true, //横纵比
img: 'https://sys.znkjfw.com/imgs/process/%E8%AF%B7%E5%81%87.png',
labelType: 'icon'
},
{
x: 454.4,
y: 434.2,
w: 100,
h: 100,
angle: 0,
draggable: true,
resizable: true,
rotatable: true,
lockAspectRatio: false, //横纵比
img: 'https://sys.znkjfw.com/imgs/process/%E8%AF%B7%E5%81%87.png',
labelType: 'icon'
}
]
allHistoryList.value[0] = JSON.parse(JSON.stringify(imgList.value))
getList()
})
</script>
<style lang="scss" scoped>
.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;
}
.grid-show {
background: linear-gradient(-90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px),
linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px);
background-size:
10px 10px,
10px 10px;
}
}
}
.sdiv {
width: 100%;
height: 100%;
box-sizing: border-box;
}
.top-tool-list {
display: flex;
align-items: center;
text-align: center;
background-color: #fff;
margin-top: -20px;
margin-left: -20px;
margin-right: -20px;
padding: 0 14px;
margin-bottom: 20px;
box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 0px 1px;
height: 70px;
.top-tool-item {
display: flex;
align-items: center;
.tool-item {
cursor: pointer;
// cursor: url('https://api.znkjfw.com/admin-api/infra/file/4/get/输入_png_179_1738982726561.png'),
// auto;
width: 52px;
height: 70px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.name {
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 14px;
color: #0d162a;
line-height: 20px;
text-align: center;
font-style: normal;
}
}
.line {
margin: 0 14px;
width: 1px;
height: 47px;
border: 1px solid #cccccc;
}
}
}
.right-tool-list {
position: absolute;
right: 0;
top: 94px;
background-color: #fff;
z-index: 999;
text-align: center;
box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 0px 1px;
.tool-item {
cursor: pointer;
padding: 10px;
.name {
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 14px;
color: #0d162a;
line-height: 20px;
text-align: center;
font-style: normal;
}
}
}
.tool-active {
background: #ebf1ff;
border-bottom: 2px solid #1677ff;
}
.right-tool-active {
background: #ebf1ff;
}
</style>