| | |
| | | <template> |
| | | <ul class="btn-list" v-if="waylineAbout.isShow"> |
| | | <!-- 参数按钮 --> |
| | | <li class="s-btn" v-for="(item, index) in btnList" :key="index"> |
| | | <div class="title">{{ item.title }}</div> |
| | | <a-popover placement="leftTop" trigger="click" arrow-point-at-center class="param-popover"> |
| | | <template #title> |
| | | <span>{{ item.title }}</span> |
| | | </template> |
| | | <template #content> |
| | | <div class="popover-content"> |
| | | <div v-for="sParam in item.param" :key="sParam.key" class="content-box"> |
| | | <div>{{ sParam.title }}</div> |
| | | <a-input v-model:value="sParam.value" placeholder="请输入参数" v-if="!sParam.isRadio" /> |
| | | <a-radio-group :options="sParam.options" v-model:value="sParam.value" v-else /> |
| | | <div class="annotation" v-if="sParam.annotation"> |
| | | 注:{{ sParam.annotation }} |
| | | <div class="action-container" v-if="isActionEditShow"> |
| | | <div class="more-btn" @click="toggleMoreAction"> |
| | | <div class="label">更多</div> |
| | | <div class="icon"><EllipsisOutlined /></div> |
| | | </div> |
| | | </div> |
| | | <div class="btn-tool"> |
| | | <a-button @click="resetParamSetting(item)">重置</a-button> |
| | | <a-button type="primary" @click="item.event(item)">确定</a-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <div class="btn" @click="() => saveInitPopoverParam(item.param)" |
| | | :style="{ backgroundColor: item.selected ? '#2D8CF0' : '#323131' }"> |
| | | <img :src="item.icon" alt="icon"> |
| | | </div> |
| | | </a-popover> |
| | | </li> |
| | | <!-- 确认和取消按钮 --> |
| | | <li class="s-btn"> |
| | | <div class="title">取消编辑</div> |
| | | <div class="btn cancel-edit" @click="cencalEdit"> |
| | | <CloseOutlined /> |
| | | </div> |
| | | </li> |
| | | <li class="s-btn"> |
| | | <div class="title">确认编辑</div> |
| | | <div class="btn confirm-edit" @click="confirmEdit"> |
| | | <CheckOutlined /> |
| | | <ul class="action-list" v-if="isMoreActionShow"> |
| | | <li class="action-list_item" v-for="action in actionList" :key="action.key" @click="handleSettingAction(action)"> |
| | | <div class="name">{{ action.name }}</div> |
| | | <div class="icon"> |
| | | <img :src="action.icon" alt="icon" /> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | </div> |
| | | </template> |
| | | <script lang="ts" setup> |
| | | import { ref, computed } from 'vue' |
| | | import { useMyStore } from '/@/store' |
| | | import axios from 'axios' |
| | | import JSZIP from 'jszip' |
| | | import { saveAs } from 'file-saver' |
| | | import { message } from 'ant-design-vue' |
| | | import { cloneDeep } from 'lodash' |
| | | import { |
| | | CloseOutlined, |
| | | CheckOutlined |
| | | } from '@ant-design/icons-vue' |
| | | // 初始化jszip |
| | | const JsZip = new JSZIP() |
| | | |
| | | <script lang="ts" setup> |
| | | import { EllipsisOutlined } from '@ant-design/icons-vue' |
| | | import { actionList } from '/@/utils/cesium/use-map-draw' |
| | | import { useMyStore } from '/@/store' |
| | | import { message } from 'ant-design-vue' |
| | | const store = useMyStore() |
| | | |
| | | const prefix = import.meta.env.VITE_MEDIAPANEL_API_URL |
| | | const isMoreActionShow = ref<boolean>(false) |
| | | |
| | | const getResource = (name: string) => { |
| | | return new URL(`/src/assets/icons/${name}`, import.meta.url).href |
| | | const isActionEditShow = computed(() => store.state.waylineTool.isShow) |
| | | |
| | | const selectedPoint = computed(() => store.state.waylineTool.selectedPoint) |
| | | |
| | | const toggleMoreAction = () => { |
| | | isMoreActionShow.value = !isMoreActionShow.value |
| | | } |
| | | |
| | | interface popoverParamType { |
| | | title: string, |
| | | key: string, |
| | | isRadio?: boolean, |
| | | annotation?: string, |
| | | options?: { |
| | | label: string, |
| | | value: string |
| | | }[], |
| | | value: string |
| | | const handleSettingAction = (action: { key: string }) => { |
| | | console.log(selectedPoint.value) |
| | | if (selectedPoint.value === undefined) return message.warn('请选择要编辑的航点!!') |
| | | } |
| | | |
| | | // 点击按钮list |
| | | interface sBtn { |
| | | key: string, |
| | | icon: string, |
| | | title: string, |
| | | distinguish?: string, |
| | | param: popoverParamType[], |
| | | event: Function, |
| | | selected?: boolean |
| | | } |
| | | |
| | | // 保存当前选择的kmz文件 |
| | | const kmzFileZip = ref() |
| | | // 当前选中的位置 |
| | | const currentPosition = ref() |
| | | const curKmzName = ref<string>('') |
| | | // 当前kmzpath |
| | | const curKmzPath = ref<string>('') |
| | | // popover初始参数 |
| | | const initPopoverParam = ref<popoverParamType[]>([]) |
| | | |
| | | const btnList = ref<sBtn[]>([ |
| | | // 开始录像 |
| | | { |
| | | key: 'startRecord', |
| | | icon: getResource('waylinetool/camera-on.png'), |
| | | title: '开始录像', |
| | | param: [ |
| | | { |
| | | title: '负载挂载位置', |
| | | key: 'payloadPositionIndex', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '飞行器1号挂载位置', value: '0' }, |
| | | { label: '飞行器2号挂载位置', value: '1' }, |
| | | { label: '飞行器3号挂载位置', value: '2' }, |
| | | ], |
| | | annotation: '飞行器1号挂载位置。M300 RTK,M350 RTK机型,对应机身左前方。其它机型,对应主云台。飞行器2号挂载位置。M300 RTK,M350 RTK机型,对应机身右前方。飞行器3号挂载位置。M300 RTK,M350 RTK机型,对应机身上方。', |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '拍摄照片文件后缀', |
| | | key: 'fileSuffix', |
| | | annotation: '为生成媒体文件命名时将额外附带该后缀', |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '拍摄照片存储类型', |
| | | key: 'payloadLensIndex', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '存储变焦镜头拍摄照片', value: 'zoom' }, |
| | | { label: '存储广角镜头拍摄照片', value: 'wide' }, |
| | | { label: '存储红外镜头拍摄照片', value: 'ir' }, |
| | | { label: '存储窄带镜头拍摄照片', value: 'narrow_band' }, |
| | | { label: '全部使用', value: 'zoom, wide, ir, narrow_band' } |
| | | ], |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '是否使用全局存储类型', |
| | | key: 'useGlobalPayloadLensIndex', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '不使用全局设置', value: '0' }, |
| | | { label: '使用全局设置', value: '1' } |
| | | ], |
| | | value: '' |
| | | } |
| | | ], |
| | | event: (obj: sBtn) => { |
| | | paramPopover('startRecord', obj) |
| | | } |
| | | }, |
| | | // 停止录像 |
| | | { |
| | | key: 'stopRecord', |
| | | icon: getResource('waylinetool/camera-off.png'), |
| | | title: '停止录像', |
| | | param: [ |
| | | { |
| | | title: '负载挂载位置', |
| | | key: 'payloadPositionIndex', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '飞行器1号挂载位置', value: '0' }, |
| | | { label: '飞行器2号挂载位置', value: '1' }, |
| | | { label: '飞行器3号挂载位置', value: '2' }, |
| | | ], |
| | | annotation: '飞行器1号挂载位置。M300 RTK,M350 RTK机型,对应机身左前方。其它机型,对应主云台。飞行器2号挂载位置。M300 RTK,M350 RTK机型,对应机身右前方。飞行器3号挂载位置。M300 RTK,M350 RTK机型,对应机身上方。', |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '拍摄照片存储类型', |
| | | key: 'payloadLensIndex', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '存储变焦镜头拍摄照片', value: 'zoom' }, |
| | | { label: '存储广角镜头拍摄照片', value: 'wide' }, |
| | | { label: '存储红外镜头拍摄照片', value: 'ir' }, |
| | | { label: '存储窄带镜头拍摄照片', value: 'narrow_band' }, |
| | | { label: '全部使用', value: 'zoom, wide, ir, narrow_band' } |
| | | ], |
| | | value: '' |
| | | } |
| | | ], |
| | | event: (obj: sBtn) => { |
| | | paramPopover('stopRecord', obj) |
| | | } |
| | | }, |
| | | // 开始等时间隔拍照 |
| | | { |
| | | key: 'time', |
| | | icon: getResource('waylinetool/shoot1.png'), |
| | | title: '开始等时间隔拍照', |
| | | param: [], |
| | | event: (obj: sBtn) => { |
| | | obj.selected = true |
| | | editKmlFile('time') |
| | | } |
| | | }, |
| | | // 开始等距间隔拍照 |
| | | { |
| | | key: 'distance', |
| | | icon: getResource('waylinetool/shoot2.png'), |
| | | title: '开始等距间隔拍照', |
| | | param: [], |
| | | event: (obj: sBtn) => { |
| | | obj.selected = true |
| | | editKmlFile('distance') |
| | | } |
| | | }, |
| | | // 结束间隔拍照 |
| | | { |
| | | key: 'deleteShootType', |
| | | icon: getResource('waylinetool/shoot3.png'), |
| | | title: '结束间隔拍照', |
| | | param: [], |
| | | event: (obj: sBtn) => { |
| | | obj.selected = true |
| | | editKmlFile('deleteShootType') |
| | | } |
| | | }, |
| | | // 悬停 |
| | | { |
| | | key: 'hover', |
| | | icon: getResource('waylinetool/xt.png'), |
| | | title: '悬停', |
| | | param: [ |
| | | { |
| | | title: '飞行器悬停等待时间', |
| | | key: 'hoverTime', |
| | | annotation: '参数需大于0, 单位为秒', |
| | | value: '' |
| | | } |
| | | ], |
| | | event: (obj: sBtn) => { |
| | | paramPopover('hover', obj) |
| | | } |
| | | }, |
| | | // 飞行器偏航角 |
| | | { |
| | | key: 'rotateYaw', |
| | | icon: getResource('waylinetool/droneyaw.png'), |
| | | title: '飞行器偏航角', |
| | | param: [ |
| | | { |
| | | title: '飞行器目标偏航角(相对于地理北)', |
| | | key: 'aircraftHeading', |
| | | annotation: '范围为-180°到180°,飞行器旋转至该目标偏航角。0°为正北方向,90°为正东方向,-90°为正西方向,-180°/180°为正南方向。', |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '飞行器偏航角转动模式', |
| | | key: 'aircraftPathMode', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '顺时针旋转', value: 'clockwise' }, |
| | | { label: '逆时针旋转', value: 'counterClockwise' } |
| | | ], |
| | | value: '' |
| | | } |
| | | ], |
| | | event: (obj: sBtn) => { |
| | | paramPopover('rotateYaw', obj) |
| | | } |
| | | }, |
| | | // 云台偏航角 |
| | | { |
| | | key: 'gimbalRotate', |
| | | distinguish: 'yaw', |
| | | icon: getResource('waylinetool/holderyaw.png'), |
| | | title: '云台偏航角', |
| | | param: [ |
| | | { |
| | | title: '负载挂载位置', |
| | | key: 'payloadPositionIndex', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '飞行器1号挂载位置', value: '0' }, |
| | | { label: '飞行器2号挂载位置', value: '1' }, |
| | | { label: '飞行器3号挂载位置', value: '2' }, |
| | | ], |
| | | annotation: '飞行器1号挂载位置。M300 RTK,M350 RTK机型,对应机身左前方。其它机型,对应主云台。飞行器2号挂载位置。M300 RTK,M350 RTK机型,对应机身右前方。飞行器3号挂载位置。M300 RTK,M350 RTK机型,对应机身上方。', |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '云台转动模式', |
| | | key: 'gimbalRotateMode', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '绝对角度,相对于正北方的角度', value: 'relativeAngle' }, |
| | | { label: '相对角度,相对于飞行器机头的角度', value: 'absoluteAngle' } |
| | | ], |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '是否使能云台Yaw转动', |
| | | key: 'gimbalYawRotateEnable', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '不使能', value: '0' }, |
| | | { label: '使能', value: '1' } |
| | | ], |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '云台Yaw转动角度', |
| | | key: 'gimbalYawRotateAngle', |
| | | annotation: '不同云台可转动范围不同', |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '云台完成转动用时', |
| | | key: 'gimbalRotateTime', |
| | | annotation: '单位为秒', |
| | | value: '' |
| | | } |
| | | ], |
| | | event: (obj: sBtn) => { |
| | | paramPopover('gimbalRotate', obj, { |
| | | gimbalHeadingYawBase: 'north', |
| | | gimbalRotateMode: 'relativeAngle', |
| | | // 上下摆动 pitch xyz中心点为基准 |
| | | gimbalPitchRotateEnable: 0, |
| | | gimbalPitchRotateAngle: 0, |
| | | // 摆动 roll 已x位基准 |
| | | gimbalRollRotateEnable: 0, |
| | | gimbalRollRotateAngle: 0, |
| | | // 左右摆动 yaw xyz中心点为基准 |
| | | gimbalRotateTimeEnable: 0, |
| | | }) |
| | | } |
| | | }, |
| | | // 云台俯仰角 |
| | | { |
| | | key: 'gimbalRotate', |
| | | distinguish: 'pitch', |
| | | icon: getResource('waylinetool/holdertilt.png'), |
| | | title: '云台俯仰角', |
| | | param: [ |
| | | { |
| | | title: '负载挂载位置', |
| | | key: 'payloadPositionIndex', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '飞行器1号挂载位置', value: '0' }, |
| | | { label: '飞行器2号挂载位置', value: '1' }, |
| | | { label: '飞行器3号挂载位置', value: '2' }, |
| | | ], |
| | | annotation: '飞行器1号挂载位置。M300 RTK,M350 RTK机型,对应机身左前方。其它机型,对应主云台。飞行器2号挂载位置。M300 RTK,M350 RTK机型,对应机身右前方。飞行器3号挂载位置。M300 RTK,M350 RTK机型,对应机身上方。', |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '云台转动模式', |
| | | key: 'gimbalRotateMode', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '绝对角度,相对于正北方的角度', value: 'relativeAngle' }, |
| | | { label: '相对角度,相对于飞行器机头的角度', value: 'absoluteAngle' } |
| | | ], |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '是否使能云台Pitch转动', |
| | | key: 'gimbalPitchRotateEnable', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '不使能', value: '0' }, |
| | | { label: '使能', value: '1' } |
| | | ], |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '云台Pitch转动角度', |
| | | key: 'gimbalPitchRotateAngle', |
| | | annotation: '不同云台可转动范围不同', |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '云台完成转动用时', |
| | | key: 'gimbalRotateTime', |
| | | value: '' |
| | | } |
| | | ], |
| | | event: (obj: sBtn) => { |
| | | paramPopover('gimbalRotate', obj, { |
| | | gimbalHeadingYawBase: 'north', |
| | | gimbalRotateMode: 'relativeAngle', |
| | | gimbalRollRotateEnable: 0, |
| | | gimbalRollRotateAngle: 0, |
| | | gimbalYawRotateEnable: 0, |
| | | gimbalYawRotateAngle: 0, |
| | | gimbalRotateTimeEnable: 0, |
| | | }) |
| | | } |
| | | }, |
| | | // 拍照 |
| | | { |
| | | key: 'takePhoto', |
| | | icon: getResource('waylinetool/camera.png'), |
| | | title: '拍照', |
| | | param: [ |
| | | { |
| | | title: '负载挂载位置', |
| | | key: 'payloadPositionIndex', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '飞行器1号挂载位置', value: '0' }, |
| | | { label: '飞行器2号挂载位置', value: '1' }, |
| | | { label: '飞行器3号挂载位置', value: '2' }, |
| | | ], |
| | | annotation: '飞行器1号挂载位置。M300 RTK,M350 RTK机型,对应机身左前方。其它机型,对应主云台。飞行器2号挂载位置。M300 RTK,M350 RTK机型,对应机身右前方。飞行器3号挂载位置。M300 RTK,M350 RTK机型,对应机身上方。', |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '拍摄照片文件后缀', |
| | | key: 'fileSuffix', |
| | | annotation: '为生成媒体文件命名时将额外附带该后缀。', |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '拍摄照片存储类型', |
| | | key: 'payloadLensIndex', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '存储变焦镜头拍摄照片', value: 'zoom' }, |
| | | { label: '存储广角镜头拍摄照片', value: 'wide' }, |
| | | { label: '存储红外镜头拍摄照片', value: 'ir' }, |
| | | { label: '存储窄带镜头拍摄照片', value: 'narrow_band' }, |
| | | { label: '全部使用', value: 'zoom, wide, ir, narrow_band' } |
| | | ], |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '是否使用全局存储类型', |
| | | key: 'useGlobalPayloadLensIndex', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '不使用全局设置', value: '0' }, |
| | | { label: '使用全局设置', value: '1' } |
| | | ], |
| | | value: '' |
| | | } |
| | | ], |
| | | event: (obj: sBtn) => { |
| | | paramPopover('takePhoto', obj) |
| | | } |
| | | }, |
| | | // 相机变焦 |
| | | { |
| | | key: 'zoom', |
| | | icon: getResource('waylinetool/fd.png'), |
| | | title: '相机变焦', |
| | | param: [ |
| | | { |
| | | title: '负载挂载位置', |
| | | key: 'payloadPositionIndex', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '飞行器1号挂载位置', value: '0' }, |
| | | { label: '飞行器2号挂载位置', value: '1' }, |
| | | { label: '飞行器3号挂载位置', value: '2' }, |
| | | ], |
| | | annotation: '飞行器1号挂载位置。M300 RTK,M350 RTK机型,对应机身左前方。其它机型,对应主云台。飞行器2号挂载位置。M300 RTK,M350 RTK机型,对应机身右前方。飞行器3号挂载位置。M300 RTK,M350 RTK机型,对应机身上方。', |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '变焦焦距', |
| | | key: 'focalLength', |
| | | annotation: '单位mm,值>0', |
| | | value: '' |
| | | } |
| | | ], |
| | | event: (obj: sBtn) => { |
| | | paramPopover('zoom', obj) |
| | | } |
| | | }, |
| | | // 创建文件夹 |
| | | { |
| | | key: 'customDirName', |
| | | icon: getResource('waylinetool/create-file.png'), |
| | | title: '创建文件夹', |
| | | param: [ |
| | | { |
| | | title: '负载挂载位置', |
| | | key: 'payloadPositionIndex', |
| | | isRadio: true, |
| | | options: [ |
| | | { label: '飞行器1号挂载位置', value: '0' }, |
| | | { label: '飞行器2号挂载位置', value: '1' }, |
| | | { label: '飞行器3号挂载位置', value: '2' }, |
| | | ], |
| | | annotation: '飞行器1号挂载位置。M300 RTK,M350 RTK机型,对应机身左前方。其它机型,对应主云台。飞行器2号挂载位置。M300 RTK,M350 RTK机型,对应机身右前方。飞行器3号挂载位置。M300 RTK,M350 RTK机型,对应机身上方。', |
| | | value: '' |
| | | }, |
| | | { |
| | | title: '新文件夹的名称', |
| | | key: 'directoryName', |
| | | value: '' |
| | | } |
| | | ], |
| | | event: (obj: sBtn) => { |
| | | paramPopover('customDirName', obj) |
| | | } |
| | | } |
| | | ]) |
| | | |
| | | const waylineAbout = computed(() => { |
| | | return store.state.waylineTool |
| | | }) |
| | | |
| | | /** |
| | | * @description: 读取kmz文件转blob |
| | | * @param {*} kmzPath 文件地址 |
| | | * @return {*} void |
| | | */ |
| | | const readKmzFile = (kmzPath: string) => { |
| | | return axios.get(kmzPath, { responseType: 'arraybuffer' }) |
| | | .then(fileRes => fileRes.data) |
| | | .then(kmzData => JsZip.loadAsync(kmzData)) // 解压kmz文件 |
| | | .then(kmzZip => kmzZip) |
| | | .catch(() => message.error('请求kmz文件失败!!')) |
| | | } |
| | | |
| | | watch(() => store.state.waylineTool.kmzPath, (n: string) => { |
| | | curKmzPath.value = n |
| | | const arr = n.split('/') |
| | | curKmzName.value = decodeURIComponent(arr[arr.length - 1]) |
| | | readKmzFile(n).then(kmzFile => { |
| | | kmzFileZip.value = kmzFile |
| | | }) |
| | | }, { |
| | | deep: true |
| | | }) |
| | | |
| | | watch(() => store.state.waylineTool.position, (position: number) => { |
| | | currentPosition.value = position |
| | | eventLight() |
| | | }, { |
| | | deep: true |
| | | }) |
| | | |
| | | // 事件高亮 |
| | | const eventLight = () => { |
| | | // 已经添加的事件高亮 |
| | | btnList.value.forEach(item => { item.selected = false }) |
| | | kmzFileZip.value.file(/\.kml$/i)[0].async('text').then((content: string) => { |
| | | const regx = /<Placemark>([\s\S]*?)<\/Placemark>/g |
| | | const points = content.match(regx) || [] |
| | | const funcRegx = /<wpml:actionActuatorFunc>([\s\S]*?)<\/wpml:actionActuatorFunc>/g |
| | | const funcs = (points[currentPosition.value] || '').match(funcRegx) |
| | | const funcNameRegx = /<wpml:actionActuatorFunc>(.*?)<\/wpml:actionActuatorFunc>/ |
| | | const shootTypeRegx = /<wpml:shootType>([\s\S]*?)<\/wpml:shootType>/g |
| | | const shootType = (points[currentPosition.value] || '').match(shootTypeRegx) |
| | | const shootTypeValueRegx = /<wpml:shootType>(.*?)<\/wpml:shootType>/ |
| | | |
| | | // 云台正则 |
| | | const actionRegx = /<wpml:action>([\s\S]*?)<\/wpml:action>/g |
| | | const actions = (points[currentPosition.value] || '').match(actionRegx) |
| | | const gimbalPitchRotateAngleRegx = /<wpml:gimbalPitchRotateAngle>(.*?)<\/wpml:gimbalPitchRotateAngle>/ |
| | | const gimbalYawRotateAngleRegx = /<wpml:gimbalYawRotateAngle>(.*?)<\/wpml:gimbalYawRotateAngle>/ |
| | | |
| | | btnList.value.forEach(sBtn => { |
| | | funcs?.forEach((func: string) => { |
| | | const funcName = func.match(funcNameRegx) || [] |
| | | if (funcName[1] === sBtn.key && !sBtn.distinguish) { |
| | | sBtn.selected = true |
| | | } |
| | | }) |
| | | shootType?.forEach((shootType: string) => { |
| | | const typeValue = shootType.match(shootTypeValueRegx) || [] |
| | | if (typeValue[1] === sBtn.key) { |
| | | sBtn.selected = true |
| | | } |
| | | }) |
| | | if (sBtn.distinguish) { |
| | | const action = actions?.find((action: string) => action.includes('<wpml:actionActuatorFunc>gimbalRotate</wpml:actionActuatorFunc>')) |
| | | const pitchAngle = action?.match(gimbalPitchRotateAngleRegx) || [] |
| | | const yawAngle = action?.match(gimbalYawRotateAngleRegx) || [] |
| | | if (!!Number(pitchAngle[1]) && sBtn.distinguish === 'pitch') { |
| | | sBtn.selected = true |
| | | } |
| | | if (!!Number(yawAngle[1]) && sBtn.distinguish === 'yaw') { |
| | | sBtn.selected = true |
| | | } |
| | | } |
| | | }) |
| | | settingDefaultValue(content) |
| | | }) |
| | | } |
| | | |
| | | // popover设置默认值 |
| | | const settingDefaultValue = (content: string) => { |
| | | const regx = /<Placemark>([\s\S]*?)<\/Placemark>/g |
| | | const points = content.match(regx) || [] |
| | | // 当前选中的点 |
| | | const currentPoint = points[currentPosition.value] || '' |
| | | const actionRegx = /<wpml:action>([\s\S]*?)<\/wpml:action>/g |
| | | const actionsKML = currentPoint.match(actionRegx) |
| | | btnList.value.forEach((sBtn: sBtn) => { |
| | | const kml = `<wpml:actionActuatorFunc>${sBtn.key}</wpml:actionActuatorFunc>` |
| | | const action = actionsKML?.find((action: string) => action.includes(kml)) |
| | | sBtn.param.forEach((sParam) => { |
| | | const paramRegex = new RegExp(`<wpml:${sParam.key}>([\\s\\S]*?)<\\/wpml:${sParam.key}>`, 'g') |
| | | const actionParam = action?.match(paramRegex) || [''] |
| | | const infoRegx = new RegExp(`<wpml:${sParam.key}>([\\s\\S]*?)<\\/wpml:${sParam.key}>`) |
| | | const value: string[] = actionParam[0]?.match(infoRegx) || [''] |
| | | sParam.value = value[1] || '' |
| | | }) |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * @description: 创建kml模版 |
| | | * @param {*} actionId 事件id(唯一) |
| | | * @param {*} actionType 事件类型 |
| | | * @param {*} param 事件参数 |
| | | * @return {*} void |
| | | */ |
| | | const createActionKML = (actionId: string, actionType: string, param: any) => { |
| | | let paramKML = '' |
| | | Object.keys(param).forEach((key: string) => { |
| | | paramKML += ` |
| | | <wpml:${key}>${param[key]}</wpml:${key}> |
| | | ` |
| | | }) |
| | | const template = ` |
| | | <wpml:action> |
| | | <wpml:actionId>${actionId}</wpml:actionId> |
| | | <wpml:actionActuatorFunc>${actionType}</wpml:actionActuatorFunc> |
| | | <wpml:actionActuatorFuncParam> |
| | | ${paramKML} |
| | | </wpml:actionActuatorFuncParam> |
| | | </wpml:action> |
| | | ` |
| | | return template |
| | | } |
| | | |
| | | /** |
| | | * @description: Popover参数编辑 |
| | | * @param {*} btnobj: 事件按钮所有参数 |
| | | * @param {*} eventType 事件类型 |
| | | * @param {*} eventParam 事件参数Param |
| | | * @return {*} |
| | | */ |
| | | const paramPopover = (eventType: string, btnobj: sBtn, eventParam?: any) => { |
| | | // 判断参数是否为有存在空的 |
| | | let isParamNull = false |
| | | btnobj.param.some((sParam: any) => { |
| | | isParamNull = sParam.value === '' |
| | | return isParamNull |
| | | }) |
| | | if (isParamNull) return message.warning('确认失败,请检查填写的参数') |
| | | // 参数处理 |
| | | const param = { ...eventParam } |
| | | btnobj.param.forEach((sParam: { key: string, value: string | number }) => { |
| | | param[sParam.key] = sParam.value |
| | | }) |
| | | editKmlFile(eventType, param) |
| | | btnobj.selected = true |
| | | } |
| | | |
| | | const saveInitPopoverParam = (param: popoverParamType[]) => { |
| | | initPopoverParam.value = cloneDeep(param) |
| | | } |
| | | |
| | | const resetParamSetting = (btnParam: sBtn) => { |
| | | btnParam.param = cloneDeep(initPopoverParam.value) |
| | | } |
| | | |
| | | /** |
| | | * @description: 修改template.kml文件 |
| | | * @param {*} eventType 事件类型 |
| | | * @param {*} eventParam 事件参数 |
| | | * @return {*} void |
| | | */ |
| | | const editKmlFile = (eventType: string, eventParam?: any) => { |
| | | // kmzZip文件处理 |
| | | const kmlFile = kmzFileZip.value.file(/\.kml$/i)[0] |
| | | kmlFile.async('text').then((kmlText: string) => { |
| | | const regx = /<Placemark>([\s\S]*?)<\/Placemark>/g |
| | | const points = kmlText.match(regx) || [] |
| | | // 当前要修改的字段 |
| | | // const curSelected: string | any = points?.find(item => item.includes(currentPosition.value.join(','))) |
| | | const curSelected: string | any = points[currentPosition.value] |
| | | // 判断是不是间隔拍照任务 |
| | | if (eventType === 'time' || eventType === 'distance' || eventType === 'deleteShootType') { |
| | | // 判断当前选中的点是否存在shootType |
| | | const shootTypeRegx = /<wpml:shootType>([\s\S]*?)<\/wpml:shootType>/g |
| | | const kmlResult: string[] = curSelected.match(shootTypeRegx) || [] |
| | | const shootTypeKML = kmlResult[0] |
| | | let replacedKML = '' |
| | | // 判断是不是结束间隔拍照任务 |
| | | if (eventType === 'deleteShootType') { |
| | | if (!shootTypeKML) { |
| | | message.warning('暂未设置间隔任务,无法结束!!') |
| | | } else { |
| | | replacedKML = curSelected.replace(shootTypeRegx, '') |
| | | } |
| | | } else { |
| | | // 判断是否已经设置间隔拍照任务 |
| | | if (shootTypeKML) { |
| | | replacedKML = curSelected.replace(shootTypeRegx, `<wpml:shootType>${eventType}</wpml:shootType>`) |
| | | } else { |
| | | // 判断是否存在actionGroup |
| | | const index = curSelected.indexOf('<wpml:actionGroup>') |
| | | if (index === -1) { |
| | | const placeMarkIdx = curSelected.indexOf('</Placemark>') |
| | | replacedKML = curSelected.slice(0, placeMarkIdx) + `<wpml:shootType>${eventType}</wpml:shootType>` + curSelected.slice(placeMarkIdx) |
| | | } else { |
| | | replacedKML = curSelected.slice(0, index) + `<wpml:shootType>${eventType}</wpml:shootType>` + curSelected.slice(index) |
| | | } |
| | | } |
| | | } |
| | | // 将另外两个间隔任务按钮高亮取消 |
| | | btnList.value.forEach((sBtn: sBtn) => { |
| | | if (sBtn.key === 'time' || sBtn.key === 'distance' || sBtn.key === 'deleteShootType') { |
| | | if (sBtn.key !== eventType) { |
| | | sBtn.selected = false |
| | | } |
| | | } |
| | | }) |
| | | // 替换原本KML |
| | | const replacedTemplateKML = kmlText.replace(curSelected, replacedKML) |
| | | JsZip.file('wpmz/template.kml', replacedTemplateKML) |
| | | kmzFileZip.value.file('wpmz/template.kml', replacedTemplateKML) |
| | | return |
| | | } |
| | | // 查找actionGroup |
| | | const actionGroupReg = /<wpml:actionGroup>([\s\S]*?)<\/wpml:actionGroup>/g |
| | | const actionGroup = curSelected.match(actionGroupReg) ? curSelected.match(actionGroupReg)[0] : '' |
| | | // 查找action |
| | | const actionReg = /<wpml:action>([\s\S]*?)<\/wpml:action>/g |
| | | const actions = actionGroup.match(actionReg) || [] |
| | | |
| | | // 判断kml文件中是否存在当前事件 |
| | | const actionIndex = actions.findIndex((action: string) => action.includes(eventType)) |
| | | if (actionIndex === -1) { |
| | | // 创建事件 |
| | | const actionKML = createActionKML(actions.length, eventType, eventParam) |
| | | actions.push(actionKML) |
| | | } else { |
| | | // 移除当前存在的事件 |
| | | actions.splice(actionIndex, 1) |
| | | } |
| | | |
| | | let replacedActionsKML = '' |
| | | // 创建actionGroup |
| | | const actionGroupKML = ` |
| | | <wpml:actionGroup> |
| | | ${actions.join('')} |
| | | </wpml:actionGroup> |
| | | ` |
| | | // 判断是否存在事件组 |
| | | if (!actionGroup) { |
| | | const index = curSelected.indexOf('</Placemark>') |
| | | replacedActionsKML = curSelected.slice(0, index) + actionGroupKML + curSelected.slice(index) |
| | | } else { |
| | | // 替换原有的action |
| | | replacedActionsKML = curSelected.replace(actionGroupReg, actionGroupKML) |
| | | } |
| | | // Placemark代替 |
| | | const replacedTemplateKML = kmlText.replace(curSelected, replacedActionsKML) |
| | | |
| | | JsZip.file('wpmz/template.kml', replacedTemplateKML) |
| | | kmzFileZip.value.file('wpmz/template.kml', replacedTemplateKML) |
| | | }) |
| | | } |
| | | |
| | | const cencalEdit = () => { |
| | | readKmzFile(curKmzPath.value).then(kmzFile => { |
| | | kmzFileZip.value = kmzFile |
| | | eventLight() |
| | | }) |
| | | } |
| | | |
| | | const confirmEdit = () => { |
| | | JsZip.generateAsync({ type: 'blob' }).then(content => { |
| | | saveAs(content, curKmzName.value) |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | kmzFileZip.value = null |
| | | onUnmounted(() => { |
| | | isMoreActionShow.value = false |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .btn-list { |
| | | .action-container { |
| | | .more-btn { |
| | | position: absolute; |
| | | right: 75px; |
| | | top: 50%; |
| | | right: 20px; |
| | | transform: translateY(-50%); |
| | | |
| | | .s-btn { |
| | | margin-bottom: 15px; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | &:last-child { |
| | | margin: 0; |
| | | } |
| | | |
| | | .title { |
| | | width: 130px; |
| | | text-align: right; |
| | | margin-right: 10px; |
| | | color: #fff; |
| | | text-shadow: 2px 2px 2px #000; |
| | | .label { |
| | | width: 60px; |
| | | line-height: 34px; |
| | | font-weight: bold; |
| | | text-align: right; |
| | | color: #fff; |
| | | text-shadow: 2px 2px 2px #000000; |
| | | } |
| | | |
| | | .btn { |
| | | .icon { |
| | | width: 34px; |
| | | height: 34px; |
| | | background-color: #323131; |
| | | color: #fff; |
| | | line-height: 34px; |
| | | text-align: center; |
| | | font-size: 25px; |
| | | margin-left: 7px; |
| | | background-color: rgba(0, 0, 0, .5); |
| | | cursor: pointer; |
| | | } |
| | | } |
| | | .action-list { |
| | | position: absolute; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | right: 120px; |
| | | padding: 0; |
| | | margin: 0; |
| | | list-style-type: none; |
| | | &_item { |
| | | width: fit-content; |
| | | height: 34px; |
| | | display: flex; |
| | | justify-content: center; |
| | | color: #fff; |
| | | margin-top: 10px; |
| | | &:first-child { |
| | | margin: 0; |
| | | } |
| | | .name { |
| | | width: 120px; |
| | | line-height: 34px; |
| | | text-align: right; |
| | | font-weight: bold; |
| | | text-shadow: 2px 2px 2px #000000; |
| | | } |
| | | .icon { |
| | | width: 34px; |
| | | height: 34px; |
| | | padding: 3px; |
| | | margin-left: 7px; |
| | | background-color: rgba(0, 0, 0, .5); |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | img { |
| | | width: 20px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .cancel-edit { |
| | | font-size: 20px; |
| | | color: #fff; |
| | | |
| | | &:hover { |
| | | background-color: #FF4D4F; |
| | | } |
| | | } |
| | | |
| | | .confirm-edit { |
| | | font-size: 20px; |
| | | color: #fff; |
| | | |
| | | &:hover { |
| | | background-color: #1777FF; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .popover-content { |
| | | .content-box { |
| | | margin-bottom: 10px; |
| | | |
| | | .annotation { |
| | | font-size: 12px; |
| | | color: rgb(188, 187, 187); |
| | | max-width: 300px; |
| | | } |
| | | } |
| | | |
| | | .btn-tool { |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 10px; |
| | | |
| | | .ant-btn { |
| | | flex: 1; |
| | | cursor: pointer; |
| | | img { |
| | | width: 25px; |
| | | height: 25px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | :deep() { |
| | | .ant-radio-wrapper { |
| | | display: block; |
| | | max-width: 300px; |
| | | text-wrap: wrap; |
| | | } |
| | | } |
| | | </style> |