吉安感知网项目-前端
罗广辉
2026-01-29 d3651750941ef1d19fe165b9514cc7e0e89e564a
feat: 位置调整
2 files modified
210 ■■■■ changed files
applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue 20 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue 190 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue
@@ -201,17 +201,17 @@
}
function LeftClickEvent(click) {
    const pos = click.position // Cartesian2 屏幕坐标
    // 屏幕坐标 -> 椭球面坐标(不考虑地形/模型)
    const cartesian3 = viewer.camera.pickEllipsoid(pos, viewer.scene.globe.ellipsoid)
    if (!cartesian3) {
        return { longitude: 112, latitude: 23 }
    const pos = click.position
    // 优先使用 pickPosition 获取贴地坐标(包含地形高度)
    let cartesian3 = viewer.scene.pickPosition(pos)
    // 如果 pickPosition 失败,回退到 pickEllipsoid
    if (!cartesian3 || !Cesium.defined(cartesian3)) {
        cartesian3 = viewer.camera.pickEllipsoid(pos, viewer.scene.globe.ellipsoid)
    }
    // 椭球面坐标 -> 经纬度
    if (!cartesian3) return
    const carto = Cesium.Cartographic.fromCartesian(cartesian3)
    const longitude = Cesium.Math.toDegrees(carto.longitude)
    const latitude = Cesium.Math.toDegrees(carto.latitude)
    // 直接更新 formData
    formData.value.longitude = _.round(longitude, 6)
    formData.value.latitude = _.round(latitude, 6)
    getLocationName(longitude, latitude)
@@ -225,10 +225,11 @@
                width: 20,
                height: 20,
                verticalOrigin: Cesium.VerticalOrigin.CENTER,
                heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
            },
            label: {
                text: `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`,
                ...labelParams
                ...labelParams,
            },
        })
    } else {
@@ -247,10 +248,11 @@
                width: 20,
                height: 20,
                verticalOrigin: Cesium.VerticalOrigin.CENTER,
                heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
            },
            label: {
                text: `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`,
                ...labelParams
                ...labelParams,
            },
        })
    } else {
applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
@@ -15,7 +15,7 @@
            <div class="right-container">
                <div class="header">
                    <span>{{ titleEnum[dialogMode] }}</span>
                    <el-icon class="close-btn" @click.stop="visible = false"><Close /></el-icon>
                </div>
@@ -27,28 +27,12 @@
                            <div class="val">{{ formData.sceneName }}</div>
                        </el-col>
                        <el-col :span="24">
                            <div class="label">指挥点位置</div>
                            <div class="val">{{ formatLocation(formData) }}</div>
                        </el-col>
                        <el-col :span="24">
                            <div class="label">场景类型</div>
                            <div class="val">{{ getDictLabel(formData.sceneType, dictObj.CommandSceneType) }}</div>
                        </el-col>
                        <el-col :span="24">
                            <div class="label">负责人</div>
                            <div class="val">{{ formData.defenseLeader }}</div>
                        </el-col>
                        <el-col :span="24">
                            <div class="label">负责人电话</div>
                            <div class="val">{{ formData.leaderPhone }}</div>
                        </el-col>
                        <el-col :span="24">
                            <div class="label">设备模式</div>
                            <div class="val">{{ getDictLabel(formData.deviceMode, dictObj.deviceMode) }}</div>
                        </el-col>
                        <el-col :span="24">
                            <div class="label">防控面积</div>
                            <div class="val">{{ formatDefenseArea(formData.defenseArea) }}</div>
                        </el-col>
                    </el-row>
@@ -67,55 +51,72 @@
                    </div>
                </div>
                <el-form class="dialog-form" v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly"
                    label-width="96px">
                <el-form
                    class="dialog-form"
                    v-else
                    ref="formRef"
                    :model="formData"
                    :rules="rules"
                    :disabled="readonly"
                    label-width="96px"
                >
                    <el-form-item label="场景名称" prop="sceneName">
                        <el-input class="command-input" v-model="formData.sceneName" maxlength="50"
                            placeholder="请输入" clearable />
                    </el-form-item>
                    <el-form-item label="指挥点位置" prop="longitude">
                        <div class="val">{{ formatLocation(formData) }}</div>
                        <el-input
                            class="command-input"
                            v-model="formData.sceneName"
                            maxlength="50"
                            placeholder="请输入"
                            clearable
                        />
                    </el-form-item>
                    <el-form-item label="场景类型" prop="sceneType">
                        <el-select class="command-select" popper-class="command-select-popper"
                            v-model="formData.sceneType" placeholder="请选择" clearable>
                            <el-option v-for="item in dictObj.CommandSceneType" :key="item.dictKey" :label="item.dictValue"
                                :value="item.dictKey" />
                        <el-select
                            class="command-select"
                            popper-class="command-select-popper"
                            v-model="formData.sceneType"
                            placeholder="请选择"
                            clearable
                        >
                            <el-option
                                v-for="item in dictObj.CommandSceneType"
                                :key="item.dictKey"
                                :label="item.dictValue"
                                :value="item.dictKey"
                            />
                        </el-select>
                    </el-form-item>
                    <el-form-item label="负责人" prop="defenseLeader">
                        <el-input class="command-input" v-model="formData.defenseLeader" maxlength="50"
                            placeholder="请输入" clearable />
                    </el-form-item>
                    <el-form-item label="负责人电话" prop="leaderPhone">
                        <el-input class="command-input" v-model="formData.leaderPhone" maxlength="50"
                            placeholder="请输入" clearable />
                    </el-form-item>
                    <el-form-item label="设备模式" prop="deviceMode">
                        <el-select class="command-select" popper-class="command-select-popper"
                            v-model="formData.deviceMode" placeholder="请选择" clearable>
                            <el-option v-for="item in dictObj.deviceMode" :key="item.dictKey" :label="item.dictValue"
                                :value="item.dictKey" />
                        <el-select
                            class="command-select"
                            popper-class="command-select-popper"
                            v-model="formData.deviceMode"
                            placeholder="请选择"
                            clearable
                        >
                            <el-option
                                v-for="item in dictObj.deviceMode"
                                :key="item.dictKey"
                                :label="item.dictValue"
                                :value="item.dictKey"
                            />
                        </el-select>
                    </el-form-item>
                    <el-form-item label="防控面积" prop="defenseArea">
                        <div class="val">{{ formatDefenseArea(formData.defenseArea) }}</div>
                    </el-form-item>
                    <div class="search-table-container">
                        <div class="search-box">
                            <div class="label">
                                关联区域
                            </div>
                            <div class="label">关联区域</div>
                            <el-input class="command-input" v-model="searchName" placeholder="请输入"
                                clearable></el-input>
                            <el-input class="command-input" v-model="searchName" placeholder="请输入" clearable></el-input>
                        </div>
                        <el-form-item prop="areaDivideIds" label-width="0">
                            <el-table class="command-table" ref="areaTableRef"
                                :data="areaList.filter(item => item.areaName.includes(searchName))" row-key="id"
                                @selection-change="handleAreaSelectionChange">
                            <el-table
                                class="command-table"
                                ref="areaTableRef"
                                :data="areaList.filter(item => item.areaName.includes(searchName))"
                                row-key="id"
                                @selection-change="handleAreaSelectionChange"
                            >
                                <el-table-column type="selection" width="55" :reserve-selection="true" />
                                <el-table-column prop="areaName" show-overflow-tooltip label="区域名称" />
                                <el-table-column prop="areaType" show-overflow-tooltip label="区域类型">
@@ -129,9 +130,17 @@
                </el-form>
                <div class="footer">
                    <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
                    <el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting"
                        :disabled="submitting" @click="handleSubmit">
                    <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">
                        {{ readonly ? '关闭' : '取消' }}
                    </el-button>
                    <el-button
                        color="#284FE3"
                        v-if="!readonly"
                        type="primary"
                        :loading="submitting"
                        :disabled="submitting"
                        @click="handleSubmit"
                    >
                        确定
                    </el-button>
                </div>
@@ -158,14 +167,9 @@
const initForm = () => ({
    sceneName: '', // 场景名称
    sceneType: '', // 场景类型
    defenseLeader: '', // 负责人
    leaderPhone: '', // 负责人电话
    deviceMode: '', // 设备模式
    areaDivideIds: '', // 关联区域ID
    areaCount: 0, // 区域数量
    defenseArea: 0, // 防控面积
    latitude: null, // 指挥点纬度
    longitude: null, // 指挥点经度
})
const emit = defineEmits(['success'])
@@ -190,20 +194,17 @@
const rules = {
    sceneName: fieldRules(true, 50),
    sceneType: fieldRules(true),
    defenseLeader: fieldRules(true, 50),
    leaderPhone: fieldRules(true, 50),
    deviceMode: fieldRules(true),
    longitude: fieldRules(true),
    areaDivideIds: fieldRules(false),
}
// 关闭弹框
function handleCancel () {
function handleCancel() {
    visible.value = false
}
// 提交新增/编辑
async function handleSubmit () {
async function handleSubmit() {
    const isValid = await formRef.value?.validate().catch(() => false)
    if (!isValid) return
    submitting.value = true
@@ -211,7 +212,6 @@
        const ids = selectedAreaRows.value.map(item => item.id)
        formData.value.areaDivideIds = ids.join(',')
        formData.value.areaCount = ids.length
        formData.value.defenseArea = calcDefenseArea(selectedAreaRows.value)
        await fwDefenseSceneSubmitApi(formData.value)
        saveOperationLog({
            requestUri: route.path,
@@ -227,31 +227,30 @@
}
// 加载详情
async function loadDetail () {
async function loadDetail() {
    if (!formData.value.id) return
    const res = await fwDefenseSceneDetailApi({ id: formData.value.id })
    formData.value = res?.data?.data ?? initForm()
}
// 获取区域列表
async function getAreaList () {
async function getAreaList() {
    if (areaList.value.length) return
    const res = await fwAreaDivideListApi({ filterSelected: 1, sceneId: formData.value.id })
    areaList.value = res?.data?.data ?? []
}
// 关联区域变更
async function handleAreaSelectionChange (rows) {
async function handleAreaSelectionChange(rows) {
    const selectedRows = await ensureAreaGeom(rows)
    selectedAreaRows.value = selectedRows
    const ids = selectedRows.map(item => item.id)
    formData.value.areaDivideIds = ids.join(',')
    formData.value.areaCount = ids.length
    formData.value.defenseArea = calcDefenseArea(selectedRows)
}
// 渲染面
function renderingSurface () {
function renderingSurface() {
    console.log(geometricSource, 11111111)
    if (!geometricSource) {
@@ -279,12 +278,7 @@
watch(() => selectedAreaRows.value, renderingSurface)
function calcDefenseArea (rows) {
    const total = rows.reduce((sum, item) => sum + (Number(item.areaSize) || 0), 0)
    return _.round(total, 2)
}
async function ensureAreaGeom (rows) {
async function ensureAreaGeom(rows) {
    const missingRows = rows.filter(row => row?.id && !row.geom)
    if (!missingRows.length) return rows
    const detailList = await Promise.all(
@@ -294,9 +288,7 @@
                .catch(() => null)
        )
    )
    const detailMap = new Map(
        detailList.filter(Boolean).map(item => [String(item.id), item])
    )
    const detailMap = new Map(detailList.filter(Boolean).map(item => [String(item.id), item]))
    if (!detailMap.size) return rows
    areaList.value = areaList.value.map(item => {
        const detail = detailMap.get(String(item.id))
@@ -309,7 +301,7 @@
}
// 同步关联区域
function syncAreaSelection () {
function syncAreaSelection() {
    if (!areaTableRef.value) return
    areaTableRef.value.clearSelection()
    const rows = []
@@ -317,8 +309,8 @@
    const arr = Array.isArray(areaDivideIds)
        ? areaDivideIds.map(item => String(item))
        : String(areaDivideIds || '')
            .split(',')
            .filter(Boolean)
                .split(',')
                .filter(Boolean)
    areaList.value.forEach(row => {
        if (arr.includes(String(row.id))) {
            areaTableRef.value.toggleRowSelection(row, true)
@@ -328,23 +320,11 @@
    selectedAreaRows.value = rows
    if (!rows.length) return
    formData.value.areaCount = rows.length
    formData.value.defenseArea = calcDefenseArea(rows)
}
function formatDefenseArea (value) {
    if (value == null || value === '') return ''
    return `${value}k㎡`
}
// 格式化指挥点位置
function formatLocation (row) {
    if (row?.longitude == null || row?.latitude == null) return ''
    return `${_.round(row.longitude, 6)}, ${_.round(row.latitude, 6)}`
}
let geometricSource
// 初始化地图实例
function initMap () {
function initMap() {
    if (viewer) return
    const map = mapRef.value?.getMap()
    if (!map?.viewer) return
@@ -359,9 +339,14 @@
    }
}
function LeftClickEvent (click) {
function LeftClickEvent(click) {
    const pos = click.position
    const cartesian3 = viewer.camera.pickEllipsoid(pos, viewer.scene.globe.ellipsoid)
    // 优先使用 pickPosition 获取贴地坐标(包含地形高度)
    let cartesian3 = viewer.scene.pickPosition(pos)
    // 如果 pickPosition 失败,回退到 pickEllipsoid
    if (!cartesian3 || !Cesium.defined(cartesian3)) {
        cartesian3 = viewer.camera.pickEllipsoid(pos, viewer.scene.globe.ellipsoid)
    }
    if (!cartesian3) return
    const carto = Cesium.Cartographic.fromCartesian(cartesian3)
    const longitude = Cesium.Math.toDegrees(carto.longitude)
@@ -371,7 +356,7 @@
    setMapPoint(formData.value.longitude, formData.value.latitude)
}
function setMapPoint (longitude, latitude) {
function setMapPoint(longitude, latitude) {
    if (!viewer || longitude == null || latitude == null) return
    if (!redPointEntity) {
        redPointEntity = viewer.entities.add({
@@ -381,6 +366,7 @@
                width: 40,
                height: 56,
                verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 设置图片底部对齐坐标点
                heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, // 贴地
            },
            label: {
                text: `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`,
@@ -391,11 +377,11 @@
                showBackground: true,
                style: Cesium.LabelStyle.FILL_AND_OUTLINE,
                horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
                verticalOrigin: Cesium.VerticalOrigin.CENTER,
                verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                outlineWidth: 1,
                outlineColor: Cesium.Color.WHITE,
                pixelOffset: new Cesium.Cartesian2(0, -72),
                eyeOffset: new Cesium.Cartesian3(0, 0, -20),
                pixelOffset: new Cesium.Cartesian2(0, -60),
                heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, // 贴地
            },
        })
    } else {
@@ -405,7 +391,7 @@
}
// 打开弹框
async function open ({ mode, row } = {}) {
async function open({ mode, row } = {}) {
    dialogMode.value = mode || 'add'
    formData.value = dialogMode.value === 'add' ? initForm() : row
    selectedAreaRows.value = []