forked from drone/command-center-dashboard

chenyao
2025-04-21 a7729ab954c949489fd6888fdecdd361d617c39e
Merge branch 'master' of http://139.196.74.78:10010/r/drone/command-center-dashboard
25 files modified
3 files renamed
1 files added
1 files deleted
1624 ■■■■ changed files
.env 2 ●●●●● patch | view | raw | blame | history
.env.development 4 ●●●● patch | view | raw | blame | history
.env.production 2 ●●● patch | view | raw | blame | history
.env.test 4 ●●●● patch | view | raw | blame | history
src/api/payload.js 4 ●●●● patch | view | raw | blame | history
src/assets/images/aiNowFly/event/hover.png patch | view | raw | blame | history
src/assets/images/aiNowFly/event/startRecord.png patch | view | raw | blame | history
src/assets/images/aiNowFly/event/takePhoto.png patch | view | raw | blame | history
src/assets/images/home/homeRight/leftTriangle.png patch | view | raw | blame | history
src/components/CurrentTaskDetails/ControlPanel/BaseControl.vue 11 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/ControlPanel/ControlPanel.vue 8 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/CurrentTaskDetails.vue 82 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/RealTimeMap.vue 61 ●●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/TaskDetailsLeft.vue 16 ●●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/TaskDetailsRight.vue 29 ●●●● patch | view | raw | blame | history
src/components/DeviceJobDetails/DeviceJobDetails.vue 7 ●●●●● patch | view | raw | blame | history
src/components/DeviceJobDetails/JobRelatedEvents.vue 40 ●●●● patch | view | raw | blame | history
src/components/UserOperate.vue 2 ●●●●● patch | view | raw | blame | history
src/const/drc.js 9 ●●●●● patch | view | raw | blame | history
src/hooks/useTaskViewInfo/useTaskViewInfo.js 81 ●●●●● patch | view | raw | blame | history
src/hooks/useTaskWayline/useTaskWayline.js 258 ●●●● patch | view | raw | blame | history
src/layout/Header.vue 2 ●●● patch | view | raw | blame | history
src/styles/element-ui.scss 90 ●●●● patch | view | raw | blame | history
src/views/Home/AINowFly.vue 14 ●●●● patch | view | raw | blame | history
src/views/Home/HomeRight/EventOverview.vue 6 ●●●● patch | view | raw | blame | history
src/views/Home/RSide.vue 2 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineLeft/InspectionRaskDetails.vue 6 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/InspectionRaskList.vue 141 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/MachineStatus/MachineStatus.vue 738 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/MachineStatus/MachineTableDetails/DeviceEvent.vue 5 ●●●● patch | view | raw | blame | history
.env
@@ -1,3 +1,5 @@
#开发、测试、生产环境共用环境配置
VITE_APP_API = /api
#调试参数
VITE_APP_DEBUG_KEY = saber
.env.development
@@ -11,5 +11,5 @@
# 航线文件地址
VITE_APP_AIRLINE_URL = https://wrj.shuixiongit.com/minio/cloud-bucket
#系统运维
VITE_APP_ADMIN_URL = 'https://wrj.shuixiongit.com/manage'
#管理后台地址
VITE_APP_ADMIN_URL = 'https://wrj.shuixiongit.com/manage/wel/index'
.env.production
@@ -6,4 +6,4 @@
# ws地址
VITE_APP_WS_API_URL = wss://wrj.shuixiongit.com/drone-wss/api/v1/ws
# 管理后台地址
VITE_APP_ADMIN_URL = 'https://aisky.org.cn/manage'
VITE_APP_ADMIN_URL = 'https://aisky.org.cn/manage/wel/index'
.env.test
@@ -6,5 +6,5 @@
# 航线文件地址
VITE_APP_AIRLINE_URL = https://wrj.shuixiongit.com/minio/cloud-bucket
#系统运维
VITE_APP_ADMIN_URL = 'https://wrj.shuixiongit.com/manage'
#管理后台地址
VITE_APP_ADMIN_URL = 'https://wrj.shuixiongit.com/manage/wel/index'
src/api/payload.js
@@ -59,9 +59,9 @@
  })
}
export function getLiveCapacityApi(workspace_id,params) {
export function getLiveCapacityApi(params) {
  return request({
    url:`/drone-device-core/manage/api/v1/live/capacity${workspace_id}`,
    url:`/drone-device-core/manage/api/v1/live/capacity`,
    method:'get',
    params
  })
src/assets/images/aiNowFly/event/hover.png

src/assets/images/aiNowFly/event/startRecord.png

src/assets/images/aiNowFly/event/takePhoto.png

src/assets/images/home/homeRight/leftTriangle.png
src/components/CurrentTaskDetails/ControlPanel/BaseControl.vue
@@ -31,22 +31,24 @@
import EventBus from '@/event-bus'
const isAutoControl = inject('isAutoControl')
const isBackDock = inject('isBackDock')
const list3 = computed(() => [
    { name: '自动控制', svg: 'autoControl', style: { top: '-70%' }, active: isAutoControl.value, handle: autoControl },
    { name: '自动控制', svg: 'autoControl', style: { top: '-70%' }, active: isAutoControl.value && !isBackDock.value, handle: autoControl },
    //如果是返航, 继续任务 就是自动任务
    //如果是取消返航, 继续任务 就是自动任务
    { name: '继续任务', svg: 'continueTask', style: { left: '70%' }, active: false, handle: autoControl },
    {
        name: '手动控制',
        svg: 'manualControl',
        style: { top: '70%' },
        active: !isAutoControl.value,
        active: !isAutoControl.value && !isBackDock.value,
        handle: manualControl,
    },
    { name: '返航/取消返航', svg: 'turnBack', style: { left: '-70%' }, active: false, handle: turnBack },
    { name: '返航/取消返航', svg: 'turnBack', style: { left: '-70%' }, active: isBackDock.value, handle: turnBack },
])
function autoControl() {
    isAutoControl.value = true
    EventBus.emit('controlPanel-cancelControl')
}
@@ -55,7 +57,6 @@
}
function turnBack() {
    isAutoControl.value = true
    EventBus.emit('controlPanel-returnOrCancelReturn')
}
</script>
src/components/CurrentTaskDetails/ControlPanel/ControlPanel.vue
@@ -158,6 +158,7 @@
const droneSn = inject('droneSn')
const trueAltitude = inject('trueAltitude')
const client_id = inject('client_id')
const isBackDock = inject('isBackDock')
const deviceTopicInfo = ref({
    pubTopic: '',
@@ -324,9 +325,10 @@
function cancelControl() {
    exitController({ client_id: client_id.value, dock_sn: dockSn.value })
        .then(res => {
            flightController.value = false
            deviceTopicInfo.value.subTopic = ''
            deviceTopicInfo.value.pubTopic = ''
            flightController.value = false
            isAutoControl.value = true
            ElMessage.success('退出飞行控制成功')
        })
        .catch(e => {})
@@ -358,13 +360,14 @@
    })
}
const isBackDock = ref(false)
// 返航
async function onBackDock() {
    await returnHome(dockSn?.value)
    ElMessage.success('返航操作成功')
    isBackDock.value = true
    isAutoControl.value = true
}
// 取消返航
@@ -372,6 +375,7 @@
    await returnHomeCancel({ dock_sn: dockSn?.value, client_id: client_id.value })
    ElMessage.success('取消返航成功')
    isBackDock.value = false
    isAutoControl.value = false
}
// 创建mqtt连接
src/components/CurrentTaskDetails/CurrentTaskDetails.vue
@@ -41,8 +41,6 @@
import { pxToRem } from '@/utils/rem'
import LiveVideo from '@/components/LiveVideo.vue'
import { liveStart } from '@/api/home/machineNest'
import { getJobDetails } from '@/api/home/task'
import RealTimeMap from '@/components/CurrentTaskDetails/RealTimeMap.vue'
import ControlPanel from '@/components/CurrentTaskDetails/ControlPanel/ControlPanel.vue'
import TaskDetailsHead from '@/components/CurrentTaskDetails/TaskDetailsHead.vue'
@@ -60,9 +58,8 @@
const isAutoControl = ref(true) //是否自动控制
const lineQuality = ref(1) //1流畅,2标清
const taskDetailsViewer = ref(null) //地图实例
const deviceOsdInfo = computed(() => wsInfo.value?.device_osd)
const dockSn = computed(() => taskDetails?.value?.device_sns?.[0])
const droneSn = computed(() => deviceOsdInfo?.value?.data?.sn)
const dockSn = computed(() => taskDetails?.value?.device_sns?.[0]) //机巢sn
const droneSn = computed(() => wsInfo.value?.device_osd?.data?.sn) //无人机sn
const trueAltitude = ref('') // 真实高度
const isAiLive = ref(false) // 是ai直播
const video_id = ref('') // 直播视频id
@@ -71,29 +68,16 @@
const currentLiveUrl = ref('') // 当前直播地址
const isTakeOff = ref(false) // 是在飞行中
const isMaxMap = ref(false) //是大地图
const client_id = ref('') //是大地图
// 获取机巢直播
const getDeviceLiveUrl = async () => {
    const res = await liveStart(dockSn.value, 2)
    currentLiveUrl.value = res.data.data.rtcs_url
}
//获取相机能力
async function getLiveCapacity() {
    const res = await getLiveCapacityApi(workspace_id.value,{ sn: dockSn.value})
}
const useTaskDetailsCallBack = () => {
    console.log(workspace_id.value,66666666)
    getDeviceLiveUrl()
}
let { taskDetails, workspace_id, getTaskDetails } = useTaskDetails(useTaskDetailsCallBack)
let { wsInfo, removeWS } = useDroneWS(workspace_id) //ws信息,是一个ref对象
const client_id = ref('') //mqtt id
const hasIr = ref(false) //有红外能力
let once = true //第一次触发
const isBackDock = ref(false)
let { taskDetails, workspace_id, getTaskDetails:initTaskDetails } = useTaskDetails(()=> getDeviceLiveUrl())
let { wsInfo } = useDroneWS(workspace_id) //ws信息,是一个ref对象
provide('wsInfo', wsInfo)
provide('isBackDock', isBackDock)
provide('workspace_id', workspace_id)
provide('deviceOsdInfo', deviceOsdInfo)
provide('dockOsdInfo', wsInfo?.value?.dock_osd)
provide('dockSn', dockSn)
provide('droneSn', droneSn)
@@ -105,24 +89,33 @@
provide('isAiLive', isAiLive)
provide('video_id', video_id)
provide('client_id', client_id)
provide('hasIr', hasIr)
let once = true
watch(deviceOsdInfo,()=>{
    if (once){
        // getLiveCapacity()
        once =false
    }
})
// 获取机巢直播
const getDeviceLiveUrl = async () => {
    const res = await liveStart(dockSn.value, 2)
    currentLiveUrl.value = res.data.data.rtcs_url
}
watch(
    wsInfo,
    () => {
        // wsInfo 变化触发
        setCurrentLiveUrl()
    },
    { deep: true }
)
//获取是否有红外功能
async function getLiveCapacity() {
    if (!once) return
    once = false
    const res = await getLiveCapacityApi({ sn: droneSn.value })
    res?.data?.data?.forEach(item => {
        item?.cameras_list?.forEach(item1 => {
            item1?.videos_list?.forEach(item2 => {
                item2?.switch_video_types?.forEach(item3 => {
                    if (item3 === 'ir') {
                        hasIr.value = true
                    }
                })
            })
        })
    })
}
// set Ai直播
const getAiLiveUrl = async () => {
    const res = await getLiveAiLinkApi({
        original_stream_url: `${CURRENT_CONFIG.rtmpURL}${video_id.value.replace(/\//g, '-')}`,
@@ -146,13 +139,13 @@
// 无人机直播画质切换
const changeLineQuality = async () => {
    const res = await updateDroneQualityApi({ video_id: video_id.value, video_quality: lineQuality.value })
    await updateDroneQualityApi({ video_id: video_id.value, video_quality: lineQuality.value })
    ElMessage.success('切换画质成功')
}
// 设置当前直播地址
const setCurrentLiveUrl = async () => {
    const deviceInfo = deviceOsdInfo.value?.data?.host
    const deviceInfo = wsInfo.value?.device_osd?.data?.host
    if (!deviceInfo) return
    const currentIsTakeOff = ![14, 0].includes(deviceInfo?.mode_code)
    // 如果还是之前的状态,不切换
@@ -161,8 +154,11 @@
    isTakeOff.value ? await getDroneLiveUrl() : await getDeviceLiveUrl()
}
watch(() => wsInfo.value?.device_osd, getLiveCapacity)
watch(wsInfo, setCurrentLiveUrl, { deep: true })
onMounted(() => {
    getTaskDetails(props?.id)
    initTaskDetails(props?.id)
    EventBus.on('CurrentTaskDetails-timeStop', changeLineQuality)
    EventBus.on('CurrentTaskDetails-getAiLiveUrl', getAiLiveUrl)
    EventBus.on('CurrentTaskDetails-getDroneLiveUrl', getDroneLiveUrl)
src/components/CurrentTaskDetails/RealTimeMap.vue
@@ -6,9 +6,9 @@
import AmapMercatorTilingScheme from '@/utils/cesium/AmapMercatorTilingScheme'
import { Cartesian3, Terrain, Viewer } from 'cesium'
import { addBlueFilter } from '@/utils/cesium/common'
import CreateFrustum from '@/utils/cesium/frustum/CreateFrustum'
import aircraftGltf from '@/assets/gltf/aircraft.gltf'
import { useTaskWayline } from '@/hooks/useTaskWayline/useTaskWayline'
const { init: initTaskWayline } = useTaskWayline()
const imageryProvider_ammapSL = new Cesium.UrlTemplateImageryProvider({
    url: 'https://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
@@ -25,7 +25,7 @@
const taskDetailsViewer = inject('taskDetailsViewer')
const taskDetails = inject('taskDetails')
const deviceOsdInfo = inject('deviceOsdInfo')
const wsInfo = inject('wsInfo')
const initMap = () => {
    taskDetailsViewer.value = new Viewer('currentTaskMap', {
@@ -55,60 +55,9 @@
    taskDetailsViewer?.value.camera.setView({
        destination: Cartesian3.fromDegrees(115.763819, 28.787374, 5000),
    })
    initTaskWayline(taskDetailsViewer.value, wsInfo, taskDetails)
}
let viewInfoFrustum
// 设置视椎
const setCreateFrustum = () => {
    const host = deviceOsdInfo.value?.data?.host
    if (!host) return
    viewInfoFrustum?.clear()
    if ([14, 0].includes(host.mode_code)) return
    const attitude_head = 180 + host.attitude_head
    const gimbal_pitch = 90 - Number(host?.payloads[0]?.gimbal_pitch) || 0
    viewInfoFrustum = new CreateFrustum(taskDetailsViewer?.value, {
        position: {
            longitude: host.longitude,
            latitude: host.latitude,
            altitude: host.height,
        },
        width: 30,
        height: 30,
        fov: 20.0,
        near: 3.0,
        far: 250.0,
        roll: gimbal_pitch,
        pitch: 0,
        heading: attitude_head,
    })
}
function setAircraftGltf() {
    const host = deviceOsdInfo.value?.data?.host
    const aircraftEntity = taskDetailsViewer.value.entities.getById('aircraftGltf')
    const position = Cesium.Cartesian3.fromDegrees(host.longitude, host.latitude, host.height)
    if (aircraftEntity) {
        aircraftEntity.position = new Cesium.ConstantPositionProperty(position)
        return
    }
    taskDetailsViewer.value.entities.add({
        id: 'aircraftGltf',
        position,
        model: {
            uri: aircraftGltf, // 或 .glb
            scale: 1.0, // 缩放比例
            minimumPixelSize: 64, // 最小像素尺寸(保证模型远处可见)
            maximumScale: 128, // 最大缩放(可选)
        },
    })
}
watch(deviceOsdInfo, () => {
    setCreateFrustum()
    setAircraftGltf()
})
useTaskWayline(taskDetailsViewer, taskDetails)
const removeMap = () => {
    taskDetailsViewer?.value.entities.removeAll()
src/components/CurrentTaskDetails/TaskDetailsLeft.vue
@@ -3,7 +3,7 @@
        <div class="title">负载控制</div>
        <div
            class="singleCol"
            v-for="item in list1"
            v-for="item in list1.filter(i => i.show)"
            @click="cameraType(item)"
            :key="item.key"
            :class="{ active: item.key === cameraParams.camera_type }"
@@ -106,12 +106,14 @@
// 初始化喊话
let globalShout = null
const isRecording = ref(false)
const list1 = ref([
    { name: '广角', key: 'wide' },
    { name: '变焦', key: 'zoom' },
    { name: '红外', key: 'ir' },
])
const hasIr = inject('hasIr')
const list1 = computed(() => {
    return [
        { name: '广角', key: 'wide',show:true },
        { name: '变焦', key: 'zoom', show: true },
        { name: '红外', key: 'ir',show: hasIr.value },
    ]
})
const cameraParams = ref({
    zoom_factor: 0,
    camera_type: 'wide',
src/components/CurrentTaskDetails/TaskDetailsRight.vue
@@ -7,7 +7,10 @@
        <div class="taskInfo">
            <div v-for="item in list" class="itemBox">
                <div class="itemName">{{ item.name }}:</div>
                <div class="itemValue">{{ item.value }}</div>
                <div class="itemValue" v-if="item.name !== '飞行事件'">{{ item.value }}</div>
                <div class="flightEvents" v-else>
                    <img v-for="item in flightEvents" alt="" :src="item.img" :title="item.name"></img>
                </div>
            </div>
        </div>
@@ -17,6 +20,7 @@
<script setup>
import droneImg from '@/assets/images/taskManagement/taskIntermediateContent/droneImg.png'
import BaseControl from '@/components/CurrentTaskDetails/ControlPanel/BaseControl.vue'
import { droneEventList } from '@/const/drc'
const taskDetails = inject('taskDetails')
const workspace_id = inject('workspace_id')
@@ -32,6 +36,7 @@
    { name: '关联算法', value: '', field: 'ai_type_str' },
    { name: '任务描述', value: '', field: 'remark' },
])
const flightEvents = ref([])
watch(
    taskDetails,
@@ -41,6 +46,12 @@
            if (item.name === '任务频次') {
                const { rep_rule_type = '', rep_rule_val = '' } = taskDetails?.value || {}
                item.value = rep_rule_type + ' -- ' + rep_rule_val
            }
            if (item.name === '飞行事件') {
                const { action_modes = [] } = taskDetails?.value || {}
                flightEvents.value = action_modes.flatMap(({ actionActuatorFunc }) =>
                    droneEventList.filter(({ value }) => actionActuatorFunc === value)
                )
            }
        })
    },
@@ -89,7 +100,7 @@
    }
    .taskInfo {
        width: 220px;
        width: 230px;
        height: 520px;
        display: flex;
        overflow: auto;
@@ -111,8 +122,18 @@
            .itemValue {
                font-weight: bold;
                color: #ffffff;
                word-break: break-all;     /* 强制在任意字符断行 */
                white-space: normal;       /* 允许正常换行 */
                word-break: break-all; /* 强制在任意字符断行 */
                white-space: normal; /* 允许正常换行 */
            }
            .flightEvents{
                display: flex;
                flex-wrap: wrap;
                gap: 10px 10px;
                img {
                    width: 30px;
                    height: 30px;
                }
            }
        }
    }
src/components/DeviceJobDetails/DeviceJobDetails.vue
@@ -69,14 +69,14 @@
import DeviceJobDetailsMap from './DeviceJobDetailsMap.vue'
const isShow = defineModel('show')
const infoList = ref([
    { name: '任务编号', value: '', field: 'job info num' },
    { name: '任务编号', value: '', field: 'job_info_num' },
    { name: '任务所属机巢', value: '', field: 'device_names' },
    { name: '任务名称', value: '', field: 'name' },
    { name: '所属单位', value: '', field: 'dept_name' },
    { name: '任务类型', value: '', field: 'industry_type_str' },
    { name: '任务时间', value: '', field: 'begin_time' + ' - ' + 'end_time' },
    { name: '飞行事件', value: '', field: 'event_number' },
    { name: '任务频次', value: '', field: 'rep_rule_type' + ' - ' + 'rep_rule_val' },
    { name: '任务频次', value: '', field: 'rep_rule_type rep_rule_val' },
    { name: '任务描述', value: '', field: 'remark' },
])
// 机巢状态
@@ -128,7 +128,8 @@
            if (item.name === '任务时间') {
                item.value = detailsData.value.begin_time.slice(0, 10) + '-' + detailsData.value.end_time.slice(0, 10)
            } else if (item.name === '任务频次') {
                item.value ===undefined ? '' : detailsData.value.rep_rule_type + ' -- ' + detailsData.value.rep_rule_val
                const { rep_rule_type = '', rep_rule_val = '' } = detailsData?.value || {}
                item.value = rep_rule_type + ' -- ' + rep_rule_val
            } else {
                item.value = detailsData.value?.[item.field] || ''
            }
src/components/DeviceJobDetails/JobRelatedEvents.vue
@@ -2,7 +2,7 @@
    <div class="machineTableDetailsTitle"><span>关联事件</span></div>
    <div class="ztzf-table">
        <el-table
        :row-class-name="tableRowClassName"
            :row-class-name="tableRowClassName"
            :data="list"
            style="width: 100%"
            :row-style="{ height: '38px', fontSize: '14px', 'text-align': 'center' }"
@@ -20,7 +20,6 @@
                    <div class="processing" v-if="scope.row.status === 3">处理中</div>
                    <div class="done" v-if="scope.row.status === 4">已完成</div>
                    <div class="ended" v-if="scope.row.status === 5">已完结</div>
                </template>
            </el-table-column>
            <el-table-column label="操作" width="80">
@@ -31,7 +30,7 @@
        </el-table>
    </div>
    <el-pagination
    class="ztzf-pagination"
        class="ztzf-pagination"
        background
        :page-sizes="[10, 20, 30, 50]"
        v-model:current-page="sizeParams.current"
@@ -49,7 +48,7 @@
const list = ref()
const params = ref({
    wayLineJodInfoId: null,
    way_line_jod_info_id: null,
})
const sizeParams = ref({
    current: 1,
@@ -58,18 +57,17 @@
const store = useStore()
const child_sn = computed(() => store.state.home.singleUavHome.child_sn)
const total = ref(10)
const distribution = row => {
    ElMessage.warning('正在加急开发中...')
}
const examine = row => {
    ElMessage.warning('正在加急开发中...')
    const orderNumber = row.event_num
    const adminUrl = import.meta.env.VITE_APP_ADMIN_URL
    const targetPath = `/tickets/ticket?orderNumber=${orderNumber}`;
    window.open(`${adminUrl}?redirect=${encodeURIComponent(targetPath)}`, '_blank');
}
const wayLineJodInfoId = inject('wayLineJodInfoId')
const getList = () => {
    params.value.wayLineJodInfoId = wayLineJodInfoId.value
    params.value.way_line_jod_info_id = wayLineJodInfoId.value
    getDeviceEventList(params.value, sizeParams.value).then(res => {
        const resData = res?.data?.data || {}
        list.value = resData.records
@@ -111,23 +109,23 @@
}
// 待处理
.pending{
color: #FF7411;
.pending {
    color: #ff7411;
}
// 待审核
.reviewed{
color: #8CFEA7;
.reviewed {
    color: #8cfea7;
}
// 处理中
.processing{
color: #FFC398;
.processing {
    color: #ffc398;
}
// 已完成
.done{
color: #AFD9FB;
.done {
    color: #afd9fb;
}
// 已完结
.ended{
color: #11C4FF;
.ended {
    color: #11c4ff;
}
</style>
src/components/UserOperate.vue
@@ -1,6 +1,7 @@
<script setup>
import { fullscreenToggel } from '@/utils/util';
import { useStore } from 'vuex';
import { ElMessage } from 'element-plus'
const router = useRouter();
const store = useStore();
const dropdownClick = val => {
@@ -20,6 +21,7 @@
}
const info = () =>{
    ElMessage.warning('加急开发中...')
  console.log('点击了个人信息');
}
</script>
src/const/drc.js
@@ -29,3 +29,12 @@
    { min: 315, max: 360, value: '北偏西' },
    { min: 360, max: 360, value: '正北' }
];
import hoverImg from '@/assets/images/aiNowFly/event/hover.png'
import startRecordImg from '@/assets/images/aiNowFly/event/startRecord.png'
import takePhotoImg from '@/assets/images/aiNowFly/event/takePhoto.png'
export const droneEventList = [
    {name:'开始录像',value:'startRecord',img:startRecordImg},
    {name:'悬停',value:'hover',img:hoverImg},
    {name:'拍照',value:'takePhoto',img:takePhotoImg},
]
src/hooks/useTaskViewInfo/useTaskViewInfo.js
File was deleted
src/hooks/useTaskWayline/useTaskWayline.js
@@ -2,11 +2,11 @@
 * @Author: shuishen 1109946754@qq.com
 * @Date: 2025-04-19 14:24:34
 * @LastEditors: shuishen 1109946754@qq.com
 * @LastEditTime: 2025-04-19 15:23:57
 * @LastEditTime: 2025-04-20 17:53:08
 * @FilePath: \command-center-dashboard\src\hooks\useTaskWayline\useTaskWayline.js
 * @Description:
 *
 * Copyright (c) 2025 by shuishen, All Rights Reserved.
 * @Description:
 *
 * Copyright (c) 2025 by shuishen, All Rights Reserved.
 */
import lineImg from '@/assets/images/arrow-right-blue.png'
import rwqfdImg from '@/assets/images/signMachineNest/rwqfd.png'
@@ -18,9 +18,24 @@
import * as Cesium from 'cesium'
import { Cartesian3 } from 'cesium'
import aircraftGltf from '@/assets/gltf/aircraft.gltf'
import CreateFrustum from '@/utils/cesium/frustum/CreateFrustum'
export function useTaskWayline (viewer, taskDetails) {
  const newViewer = unref(viewer)
export function useTaskWayline () {
  let viewer = null
  let deviceOsdInfo = null
  let flighttaskProgressInfo = null
  // watch
  let taskWatch = null
  let flighttaskWatch = null
  let deviceWatch = null
  // 航线位置
  let currentWaylinePostions = []
  // 当前航点 下标
  let currentWaypointIndex = null
  let droneSpeedArr = []
  // 解析kmz文件
  const parsingFiles = async url => {
@@ -29,20 +44,23 @@
    const waylinesXMLJSON = XMLToJSON(waylinesXML)?.['Document']
    const waylinesXMLObj = removeTextKey(waylinesXMLJSON.Folder)
    if (!waylinesXMLObj.Placemark.length) return
    const allPoint = waylinesXMLObj.Placemark.map(item => item.Point.coordinates.split(','))
    flyVisual(allPoint, newViewer)
    // const allPoint = waylinesXMLObj.Placemark.map(item => item.Point.coordinates.split(','))
    // flyVisual(allPoint, viewer)
    drawWayline(waylinesXMLObj)
  }
  const drawWayline = lineObj => {
    const positions = lineObj.Placemark.map(item => {
    currentWaylinePostions = lineObj.Placemark.map(item => {
      const [lon, lat] = item.Point.coordinates.split(',')
      return Cartesian3.fromDegrees(Number(lon), Number(lat))
    })
    removeEntitys()
    // 起点
    newViewer.entities.add({
    viewer.entities.add({
      id: 'drone-job-wayline-start',
      position: positions[0],
      position: currentWaylinePostions[0],
      billboard: {
        image: new Cesium.ConstantProperty(rwqfdImg),
        width: 70,
@@ -51,9 +69,9 @@
    })
    // 终点
    newViewer.entities.add({
    viewer.entities.add({
      id: 'drone-job-wayline-end',
      position: positions[positions.length - 1],
      position: currentWaylinePostions[currentWaylinePostions.length - 1],
      billboard: {
        image: new Cesium.ConstantProperty(endPointImg),
        width: 30,
@@ -63,11 +81,11 @@
    })
    // 路径线
    newViewer.entities.add({
    let polylineEntity = viewer.entities.add({
      id: 'drone-job-wayline-polyline',
      polyline: {
        width: 4,
        positions: positions,
        positions: currentWaylinePostions,
        material: new ImageTrailMaterial({
          color: { alpha: 1, blue: 1, green: 1, red: 1 },
          speed: 20,
@@ -77,27 +95,209 @@
        clampToGround: false,
      },
    })
  }
  watch(taskDetails, () => {
    if (taskDetails.value?.way_lines?.length) {
      parsingFiles(taskDetails.value.way_lines[0].url)
    }
  }, { immediate: true })
  const removeEntitys = () => {
    const entitiesIDs = newViewer?.entities.values.map(i => i.id)
    entitiesIDs.forEach(item => {
      item.includes('drone-job-wayline-') && newViewer?.entities.removeById(item)
    viewer.flyTo(polylineEntity, {
      offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-60), 0),
      duration: 0.5,
    })
  }
  let viewInfoFrustum
  // 设置视椎
  const setCreateFrustum = (host) => {
    if (!host) return
    viewInfoFrustum?.clear()
    const attitude_head = 180 + host?.attitude_head
    const gimbal_pitch = 90 - Number(host?.payloads[0]?.gimbal_pitch) || 0
    viewInfoFrustum = new CreateFrustum(viewer, {
      position: {
        longitude: host?.longitude,
        latitude: host?.latitude,
        altitude: host?.height,
      },
      width: 30,
      height: 30,
      fov: 20.0,
      near: 3.0,
      far: 250.0,
      roll: gimbal_pitch,
      pitch: 0,
      heading: attitude_head,
    })
  }
  function setAircraftGltf () {
    const host = deviceOsdInfo.value?.data?.host
    const aircraftEntity = viewer?.entities.getById('aircraftGltf')
    const position = Cesium.Cartesian3.fromDegrees(host?.longitude, host?.latitude, host?.height)
    if (aircraftEntity) {
      aircraftEntity.position = new Cesium.ConstantPositionProperty(position)
      const homeDistance = Math.floor(host?.home_distance) || 0
      // 距离下一个航点
      const nextPoint = currentWaylinePostions[currentWaypointIndex]
      if (!nextPoint) {
        aircraftEntity.label = {}
        return
      }
      const devicePosition = Cesium.Cartesian3.fromDegrees(
        Number(host.longitude),
        Number(host.latitude),
        0,
      )
      // 距离下个点位的距离
      let distance = Cesium.Cartesian3.distance(
        devicePosition,
        nextPoint,
      )
      // 本次航线平面里程
      let totalDistance = 0
      currentWaylinePostions.map((cartesian3, index) => {
        // 两点之间的距离
        let deviceCurPosition = null
        if (index === 0) {
          deviceCurPosition = devicePosition
        } else {
          deviceCurPosition = currentWaylinePostions[index - 1]
        }
        let distance = Cesium.Cartesian3.distance(
          deviceCurPosition,
          cartesian3,
        )
        totalDistance += distance
        return Math.round(distance)
      })
      // 速度
      let horizontalSpeed = host.horizontal_speed || 0
      if (
        !droneSpeedArr.includes(horizontalSpeed) &&
        horizontalSpeed > 1
      ) {
        droneSpeedArr.push(horizontalSpeed)
      }
      if (horizontalSpeed < 5) {
        horizontalSpeed = 10
      }
      // 预计到达下一个航点时间
      let arrivalTime = distance / horizontalSpeed
      if (arrivalTime === Infinity || isNaN(arrivalTime)) {
        arrivalTime = 0
      }
      if (arrivalTime > 60) {
        const minute = Math.floor(arrivalTime / 60)
        const second = Math.round(arrivalTime % 60)
        arrivalTime = `${minute}m${second}s`
      } else {
        arrivalTime = Math.round(arrivalTime) + 's'
      }
      aircraftEntity.label = new Cesium.LabelGraphics({
        text: `距离机场水平距离:${homeDistance}m\n距离下一个航点:${Math.round(distance)}m\n预计到达下一航点时间:${arrivalTime}\n本次航线平面里程:${Math.round(totalDistance)}m`,
        font: '13px monospace',
        showBackground: true,
        horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        disableDepthTestDistance: Number.POSITIVE_INFINITY,
        pixelOffset: new Cesium.Cartesian2(0, -40),
        show: true,
      })
      return
    }
    viewer?.entities.add({
      id: 'aircraftGltf',
      position,
      label: {},
      model: {
        uri: aircraftGltf, // 或 .glb
        scale: 1.0, // 缩放比例
        minimumPixelSize: 64, // 最小像素尺寸(保证模型远处可见)
        maximumScale: 128, // 最大缩放(可选)
      },
    })
  }
  const mapEntityRemove = () => {
    viewInfoFrustum?.clear()
    viewer?.entities.removeById('aircraftGltf')
  }
  const removeEntitys = () => {
    const entitiesIDs = viewer?.entities.values.map(i => i.id)
    Array.isArray(entitiesIDs) && entitiesIDs.forEach(item => {
      item.includes('drone-job-wayline-') && viewer?.entities.removeById(item)
    })
  }
  const removeWatchs = () => {
    taskWatch?.()
    flighttaskWatch?.()
    deviceWatch?.()
  }
  const init = (v, wsInfo, taskDetails) => {
    viewer = v
    deviceOsdInfo = computed(() => wsInfo.value?.device_osd)
    flighttaskProgressInfo = computed(() => wsInfo.value?.flighttask_progress)
    removeWatchs()
    taskWatch = watch(taskDetails,
      () => {
        if (taskDetails.value?.way_lines?.length) {
          parsingFiles(taskDetails.value.way_lines[0].url)
        }
      },
      { immediate: true }
    )
    flighttaskWatch = watch(flighttaskProgressInfo, async () => {
      const output = flighttaskProgressInfo.value?.data?.output
      currentWaypointIndex = output.ext['current_waypoint_index']
    })
    deviceWatch = watch(deviceOsdInfo, () => {
      const host = deviceOsdInfo.value?.data?.host
      if (!host) return
      if ([14, 0].includes(host?.mode_code)) {
        mapEntityRemove()
        return
      }
      setCreateFrustum(host)
      setAircraftGltf()
    }
    )
  }
  onBeforeUnmount(() => {
    mapEntityRemove()
    removeEntitys()
    removeWatchs()
  })
  return {
    removeEntitys
    init,
    removeEntitys,
    mapEntityRemove,
  }
}
}
src/layout/Header.vue
@@ -52,7 +52,7 @@
const handleClick = ({ path, name }) => {
  if (['系统运维'].includes(name)) {
    window.open(import.meta.env.VITE_APP_ADMIN_URL + '?token=' + localStorage.getItem(ELocalStorageKey.Token), '_blank');
    window.open(import.meta.env.VITE_APP_ADMIN_URL, '_blank');
    return;
  }
  if (!['首页', '任务管理'].includes(name)) return ElMessage.warning('正在加急开发中...');
src/styles/element-ui.scss
@@ -245,7 +245,6 @@
    border: 1px solid !important;
    border-radius: 0px 0px 8px 8px;
    border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1 !important;
    /* 修改箭头样式 */
    .el-popper__arrow {
        &::before {
            background: #0d3556 !important;
@@ -253,23 +252,23 @@
            box-sizing: border-box;
        }
    }
    /* 修改头部背景色 */
    .custom-date-picker .el-picker-panel__header {
        background: linear-gradient(180deg, rgba(13, 53, 86, 0.85) 0%, rgba(1, 35, 80, 0.85) 100%) !important;
        color: #fff !important;
    }
    .el-date-table {
        th {
            color: #fff !important; /* 星期标题颜色 */
            color: #fff !important;
        }
    }
    /* 修改日期单元格悬停/选中状态 */
    .custom-date-picker .el-date-table td:hover,
    .custom-date-picker .el-date-table td.current:not(.disabled) {
        background: rgba(60, 121, 202) !important;
        color: white !important;
    }
    // 头部按钮
    .el-date-table td.start-date span,
    .el-date-table td.start-date span,
    .el-date-table td.end-date span {
@@ -337,33 +336,33 @@
    overflow-x: hidden !important;
    margin-right: 0 !important;
  }
  /* 箭头样式 */
  .el-popper__arrow::before {
    background: #0d3556 !important;
    border: 1px solid #0d3556 !important;
  }
  .el-select {
    --el-select-border-color-hover: rgb(0, 162, 255) !important;
    /* // 修改下拉框hover的默认样式 */
  }
  
  .el-input {
    --el-border-color: rgb(0, 162, 255);
    /* // 修改下拉框的边框 */
  }
  
  .el-select .el-input__wrapper {
    background-color: #012350 !important;
    /* // 外层下拉框背景 */
  }
  
  .el-input__wrapper {
    --el-input-text-color: #012350 !important; /* // 修改外层下拉框的颜色 */
    --el-input-text-color: #012350 !important;
  }
  
  .el-select__popper.el-popper {
    border: rgba(0, 27, 63) !important;
    /* // 修改内部下拉框的边框颜色 */
  }
  
  .el-select-dropdown__wrap {
@@ -371,11 +370,11 @@
    border-radius: 0px 0px 8px 8px;
    border: 1px solid;
    border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1;
    /* // 下拉框背景色 */
  }
  
  .el-select-dropdown__item {
    background: rgba(0, 27, 63) !important; /* // 下拉框选项的颜色 */
    background: #012350 !important;
  }
  
  .el-select-dropdown__item {
@@ -447,11 +446,11 @@
        text-transform: none;
        background: linear-gradient(90deg, #fbfdff 0%, #86d4ff 100%);
        margin-left: 16px;
        -webkit-background-clip: text; /* 背景被裁剪成文字的前景色 */
        -webkit-text-fill-color: transparent; /* 文字填充颜色变透明 */
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
    }
  .el-dialog .el-dialog__header {
        /* margin: 0px !important; */
        padding: 0px !important;
        padding-left: 0px !important;
    }
@@ -459,4 +458,63 @@
        background: #13c6ff !important;
    }
}
/* 时间选择器下拉面板 */
.custom-time-picker {
  /* 整体背景和边框 */
background: linear-gradient( 180deg, rgba(13,53,86,0.85) 0%, rgba(1,35,80,0.85) 100%) !important;
border-radius: 0px 0px 8px 8px;
border: 1px solid !important;
border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1 !important;
  /* 修改箭头样式 */
  .el-popper__arrow::before {
    background: #0D3556 !important;
    border: 1px solid #479DFF !important;
  }
  /* 时间面板头部 */
  .el-time-panel__header {
    background: #012350 !important;
    color: #FFFFFF !important;
    border-bottom: 1px solid rgba(71, 157, 255, 0.3) !important;
  }
  /* 时间选择区域 */
  .el-time-panel__content {
    background: #012350 !important;
    /* 时间数字项 */
    .el-time-spinner__item {
      color: #E6E6E6 !important;
      &:hover {
        background: rgba(71, 157, 255, 0.3) !important;
      }
      &.active {
        color: #479DFF !important;
        font-weight: bold;
      }
    }
  }
  .el-time-panel__footer {
    background: #012350 !important;
    border-top: 1px solid rgba(71, 157, 255, 0.3) !important;
    .el-button {
      color: #fff !important;
      &:hover {
        color: #479DFF !important;
      }
    }
    .el-time-panel__btn.cancel {
      color: #fff !important;
      &:hover {
        color: #fff !important;
      }
    }
  }
}
src/views/Home/AINowFly.vue
@@ -16,14 +16,14 @@
            <!--            todo-->
            <el-form-item label="飞行事件">
                <div class="event">
                    <img src="@/assets/images/aiNowFly/picture-recording.png" alt="">
                    <img src="../../assets/images/aiNowFly/event/startRecord.png" alt="">
                    <div class="img-close" v-show="isPhoto">
                        <img @click="isPhoto=false" class="close" src="@/assets/images/aiNowFly/close.png" alt="">
                        <img src="@/assets/images/aiNowFly/photo.png" alt="">
                        <img src="../../assets/images/aiNowFly/event/takePhoto.png" alt="">
                    </div>
                    <div class="img-close" v-show="isStop">
                        <img @click="isStop=false" class="close" src="@/assets/images/aiNowFly/close.png" alt="">
                        <img src="@/assets/images/aiNowFly/stop.png" alt="">
                        <img src="../../assets/images/aiNowFly/event/hover.png" alt="">
                    </div>
                    <div class="add-event" @click="isShowEvent = !isShowEvent">+
                        <div class="event-select" v-show="isShowEvent">
@@ -159,7 +159,7 @@
      label: {
        text: `预计${item.minute}分钟`,
                font: 'bold 16px Source Han Sans CN',  // 加粗并增大字号
              // fillColor: new Cesium.Color(27/255, 179/255, 255/255, 1.0),
              // fillColor: new Cesium.Color(27/255, 179/255, 255/255, 1.0),
                // fillColor: Cesium.Color.WHITE,
                // style: Cesium.LabelStyle.FILL_AND_OUTLINE,
                // verticalOrigin: Cesium.VerticalOrigin.CENTER,
@@ -354,12 +354,12 @@
    :deep(.el-select__wrapper) {
      background-color: #021740;
      box-shadow: 0 0 0 1px #026AD6;
      &.is-focus {
        box-shadow: 0 0 0 1px #026AD6;
      }
    }
    :deep(.el-input__wrapper.is-disabled) {
      background-color: #021740;
      box-shadow: 0 0 0 1px #026AD6;
@@ -440,7 +440,7 @@
            white-space: nowrap;
        }
    }
    .btn-submit {
        cursor: pointer;
        margin-left: 88px;
src/views/Home/HomeRight/EventOverview.vue
@@ -40,7 +40,7 @@
                <img class="completion-left-img" :src="completionLeft" alt="" />
                <div class="completion-text">事件类型完成率情况</div>
                <div class="completion-separator"></div>
                <img class="completion-left-img" :src="completionLeft" alt="" />
                <img class="completion-left-img" :src="leftTriangle" alt="" />
            </div>
            <div class="chart" ref="echartsRef"></div>
@@ -56,6 +56,7 @@
import overviewImg5 from '@/assets/images/home/homeRight/overview5.png'
import overviewImg6 from '@/assets/images/home/homeRight/overview6.png'
import completionLeft from '@/assets/images/home/homeRight/completionLeft.png'
import leftTriangle from '@/assets/images/home/homeRight/leftTriangle.png'
import * as echarts from 'echarts'
import CommonDateTime from '@/components/CommonDateTime.vue'
import VSelectLoad from '@/directive/selectLoad'
@@ -153,6 +154,9 @@
        width: 2, // 线条宽度
        type: 'solid', // 线条类型
    },
    tooltip: {
        valueFormatter: (value) => value + '%'
    },
    symbol: 'circle', // 数据点符号
    symbolSize: 6, // 数据点符号大小
    emphasis: {
src/views/Home/RSide.vue
@@ -40,6 +40,7 @@
import tc from '@/assets/images/rSide/tc.png'
import tc1 from '@/assets/images/rSide/tc1.png'
import cesiumOperation from '@/utils/cesium-tsa'
import { ElMessage } from 'element-plus'
let logIndex = ref(3)
const enterHover = value => {
@@ -61,6 +62,7 @@
        activeIndex.value = activeIndex.value === 1 ? null : value
    }
    if (value === 2) {
        ElMessage.warning('加急开发中...')
        activeIndex.value = activeIndex.value === 2 ? null : value
    }
}
src/views/SignMachineNest/MachineLeft/InspectionRaskDetails.vue
@@ -109,7 +109,7 @@
        },
    ],
}
import _ from 'lodash'
// chart.setOption(option);
const pieOption = {
@@ -138,7 +138,7 @@
                    tarValue = data[i].value
                }
            }
            let percentage = ((tarValue / total) * 100).toFixed(1)
            let percentage = total ? _.round(((tarValue / total) * 100),1) : 0
            return `${name} ${percentage}%`
        },
    },
@@ -167,7 +167,7 @@
                { value: 0, name: '处理中', itemStyle: { color: '#FFC398' } },
                { value: 0, name: '已完成', itemStyle: { color: '#AFD9FB' } },
                // { value: 0, name: '待分拨', itemStyle: { color: '#E9C81A' } },
                { value: 0, name: '已完结', itemStyle: { color: '#11C4FF' } },
            ],
        },
src/views/SignMachineNest/MachineRight/InspectionRaskList.vue
@@ -24,7 +24,6 @@
                :infinite-scroll-disabled="busy"
                infinite-scroll-immediate="true"
            >
                >
                <div class="item" v-for="(item, index) in tableList">
                    <div class="left" @click="taskClick(item)">
                        <div class="left-t">
@@ -78,7 +77,6 @@
import { useStore } from 'vuex'
import { useTaskWayline } from '@/hooks/useTaskWayline/useTaskWayline'
import { useTaskDetails } from '@/hooks/useTaskDetails/useTaskDetails'
import { useTaskViewInfo } from '@/hooks/useTaskViewInfo/useTaskViewInfo'
import { useDroneWS } from '@/hooks/useDroneWS'
let viewer = null
@@ -211,142 +209,21 @@
    }
}
watch([() => tabIndex.value, () => tableList.value], async ([newTabIndex, newTableList]) => {
    if (newTabIndex == 1 && newTableList.length > 0) {
        await getTaskDetails(newTableList[0].wayline_job_info_id)
    }
})
const { init: initTaskWayline, removeEntitys } = useTaskWayline()
onMounted(async () => {
    viewer = window.$viewer
    await getJobList()
    // tableList.value = [
    //     {
    //         id: 11563,
    //         status: 5,
    //         begin_time: '2025/04/19 05:46:25',
    //         end_time: '2025/04/19 05:46:25',
    //         create_time: '2025-04-19T05:46:36.032+00:00',
    //         name: '智引即飞202504191252',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 340,
    //         job_id: '484a7846-60e0-4ec3-b9fb-272a055c6938',
    //     },
    //     {
    //         id: 11559,
    //         status: 5,
    //         begin_time: '2025/04/19 05:39:42',
    //         end_time: '2025/04/19 05:39:42',
    //         create_time: '2025-04-19T05:39:52.022+00:00',
    //         name: '智引即飞202504191252',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 340,
    //         job_id: 'b8c58bfb-4352-47c5-ba33-242eec8df188',
    //     },
    //     {
    //         id: 11558,
    //         status: 3,
    //         begin_time: '2025/04/19 05:39:17',
    //         end_time: '2025/04/19 05:39:17',
    //         create_time: '2025-04-19T05:39:27.407+00:00',
    //         name: '智引即飞202504191252',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 340,
    //         job_id: '839773ce-bd46-4947-a2ef-d859eaea82f6',
    //     },
    //     {
    //         id: 11557,
    //         status: 5,
    //         begin_time: '2025/04/19 04:52:04',
    //         end_time: '2025/04/19 04:52:04',
    //         create_time: '2025-04-19T04:52:14.503+00:00',
    //         name: '智引即飞202504191252',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 340,
    //         job_id: '58366fd4-8361-456f-8ad9-3b881f933620',
    //     },
    //     {
    //         id: 11556,
    //         status: 5,
    //         begin_time: '2025/04/19 03:45:06',
    //         end_time: '2025/04/19 03:45:06',
    //         create_time: '2025-04-19T03:45:16.522+00:00',
    //         name: '智引即飞202504191145',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 338,
    //         job_id: '39c60fb8-e5c4-4474-912a-c3af3f1b9054',
    //     },
    //     {
    //         id: 11555,
    //         status: 5,
    //         begin_time: '2025/04/19 03:27:55',
    //         end_time: '2025/04/19 03:27:55',
    //         create_time: '2025-04-19T03:28:05.196+00:00',
    //         name: '智引即飞202504191127',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 336,
    //         job_id: '78c42532-45e4-4a01-b698-69bca19286ff',
    //     },
    //     {
    //         id: 11554,
    //         status: 5,
    //         begin_time: '2025/04/19 02:58:53',
    //         end_time: '2025/04/19 02:58:53',
    //         create_time: '2025-04-19T02:59:03.602+00:00',
    //         name: '智引即飞202504191058',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 335,
    //         job_id: 'd3b25edc-ec23-4ad6-800a-0f659a72bff3',
    //     },
    //     {
    //         id: 11553,
    //         status: 5,
    //         begin_time: '2025/04/19 02:52:21',
    //         end_time: '2025/04/19 02:52:21',
    //         create_time: '2025-04-19T02:52:31.415+00:00',
    //         name: '智引即飞202504191052',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 334,
    //         job_id: 'e27090f1-e740-4ae4-8d4d-301354eb0bbc',
    //     },
    //     {
    //         id: 11548,
    //         status: 5,
    //         begin_time: '2025/04/19 02:23:06',
    //         end_time: '2025/04/19 02:23:06',
    //         create_time: '2025-04-19T02:23:16.151+00:00',
    //         name: '智引即飞202504191023',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 332,
    //         job_id: '9e1f7283-fe34-4cfd-b468-cd2b34a96d34',
    //     },
    //     {
    //         id: 11547,
    //         status: 3,
    //         begin_time: '2025/04/19 02:18:24',
    //         end_time: '2025/04/19 02:18:24',
    //         create_time: '2025-04-19T02:18:34.590+00:00',
    //         name: '智引即飞202504191018',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 331,
    //         job_id: '7aa5222c-2f8b-401e-b6ba-d6a550398829',
    //     },
    // ]
    if (tableList.value.length > 0) {
        await getTaskDetails(tableList.value[0].wayline_job_info_id)
    }
    initTaskWayline(viewer, wsInfo, taskDetails)
})
const { removeEntitys } = useTaskWayline(viewer || window.$viewer, taskDetails)
useTaskViewInfo(viewer || window.$viewer, wsInfo, removeEntitys)
</script>
<style lang="scss" scoped>
src/views/SignMachineNest/MachineRight/MachineStatus/MachineStatus.vue
@@ -1,393 +1,403 @@
<!-- 机巢状态 -->
<template>
  <CommonTitle title="机巢状态" @details="detailsFun" />
  <div :style="{ marginLeft: pxToRem(14) }">
    <div class="machine-status">
      <div class="info">
        <img src="../../../../assets/images/signMachineNest/machineRight/wrj.png" alt="">
        <div class="info-right">
          <!-- <div class="name">{{ osdVisible?.callsign || '--' }}</div> -->
          <div class="name">{{ osdVisible?.nickname || '--' }}</div>
          <div class="wz">
            <span class="left">当前位置:</span>
            <span class="right">{{ detailInfo.longitude }},{{ detailInfo.latitude }}</span>
          </div>
          <div class="close-wb">
            <div class="close" :class="AircraftStatus == undefined ? '' : 'other'">
              {{ AircraftStatus == undefined ? '舱内关机' : AircraftStatus }}
            </div>
            <!-- <div class="wb">需要维保</div> -->
          </div>
        </div>
      </div>
      <div class="status">
        <div class="card">
          <div>
            <img src="../../../../assets/images/signMachineNest/machineRight/height.png" alt="">
            <span class="text">实时真高</span>
          </div>
          <div class="text-data">{{ detailInfo.height || '--' }}<span class="text">米</span></div>
        </div>
        <div class="card">
          <div>
            <img src="../../../../assets/images/signMachineNest/machineRight/speed.png" alt="">
            <span class="text">飞行速度</span>
          </div>
          <div class="text-data">{{ detailInfo.horizontal_speed }}<span class="text">米/秒</span></div>
        </div>
        <div class="card">
          <div>
            <img src="../../../../assets/images/signMachineNest/machineRight/signal.png" alt="">
            <span class="text">信号强度</span>
          </div>
          <div class="text-data">{{ detailInfo.quality }}</div>
        </div>
        <div class="card">
          <div>
            <img src="../../../../assets/images/signMachineNest/machineRight/electricity.png" alt="">
            <span class="text">电池电量</span>
          </div>
          <div v-if="drone_charge_state.capacity_percent != 0" class="text-data">
            {{ drone_charge_state.capacity_percent }}<span class="text">%</span>
          </div>
          <div v-else class="text-data">{{ detailInfo.capacity_percent }}<span class="text">%</span></div>
        </div>
        <div class="card">
          <div>
            <img src="../../../../assets/images/signMachineNest/machineRight/distance.png" alt="">
            <span class="text">飞行距离</span>
          </div>
          <div class="text-data">{{ singleTotal.flight_mileage }}<span class="text">km</span></div>
        </div>
        <div class="card">
          <div>
            <img src="../../../../assets/images/signMachineNest/machineRight/duration.png" alt="">
            <span class="text">飞行时长</span>
          </div>
          <div class="text-data">{{ singleTotal.hour_count }}<span class="text">h</span></div>
        </div>
      </div>
    </div>
  </div>
   <MachineTableDetails v-model:show="isShowDetails"/>
    <CommonTitle title="机巢状态" @details="detailsFun" />
    <div :style="{ marginLeft: pxToRem(14) }">
        <div class="machine-status">
            <div class="info">
                <img src="../../../../assets/images/signMachineNest/machineRight/wrj.png" alt="" />
                <div class="info-right">
                    <!-- <div class="name">{{ osdVisible?.callsign || '--' }}</div> -->
                    <div class="name">{{ osdVisible?.nickname || '--' }}</div>
                    <div class="wz">
                        <span class="left">当前位置:</span>
                        <span class="right">{{ detailInfo.longitude }},{{ detailInfo.latitude }}</span>
                    </div>
                    <div class="close-wb">
                        <div class="close" :class="AircraftStatus == undefined ? '' : 'other'">
                            {{ AircraftStatus == undefined ? '舱内关机' : AircraftStatus }}
                        </div>
                        <!-- <div class="wb">需要维保</div> -->
                    </div>
                </div>
            </div>
            <div class="status">
                <div class="card">
                    <div>
                        <img src="../../../../assets/images/signMachineNest/machineRight/height.png" alt="" />
                        <span class="text">实时真高</span>
                    </div>
                    <div class="text-data">
                        {{ detailInfo.height || '--' }}
                        <span class="text">米</span>
                    </div>
                </div>
                <div class="card">
                    <div>
                        <img src="../../../../assets/images/signMachineNest/machineRight/speed.png" alt="" />
                        <span class="text">飞行速度</span>
                    </div>
                    <div class="text-data">
                        {{ detailInfo.horizontal_speed }}
                        <span class="text">米/秒</span>
                    </div>
                </div>
                <div class="card">
                    <div>
                        <img src="../../../../assets/images/signMachineNest/machineRight/signal.png" alt="" />
                        <span class="text">信号强度</span>
                    </div>
                    <div class="text-data">{{ detailInfo.quality }}</div>
                </div>
                <div class="card">
                    <div>
                        <img src="../../../../assets/images/signMachineNest/machineRight/electricity.png" alt="" />
                        <span class="text">电池电量</span>
                    </div>
                    <div v-if="drone_charge_state.capacity_percent != 0" class="text-data">
                        {{ drone_charge_state.capacity_percent }}
                        <span class="text">%</span>
                    </div>
                    <div v-else class="text-data">
                        {{ detailInfo.capacity_percent }}
                        <span class="text">%</span>
                    </div>
                </div>
                <div class="card">
                    <div>
                        <img src="../../../../assets/images/signMachineNest/machineRight/distance.png" alt="" />
                        <span class="text">飞行距离</span>
                    </div>
                    <div class="text-data">
                        {{ singleTotal.flight_mileage }}
                        <span class="text">km</span>
                    </div>
                </div>
                <div class="card">
                    <div>
                        <img src="../../../../assets/images/signMachineNest/machineRight/duration.png" alt="" />
                        <span class="text">飞行时长</span>
                    </div>
                    <div class="text-data">
                        {{ singleTotal.hour_count }}
                        <span class="text">h</span>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <MachineTableDetails v-model:show="isShowDetails" />
</template>
<script setup>
import CommonTitle from '@/components/CommonTitle.vue';
import { EDeviceTypeName } from '@/utils/staticData/enums.js';
import { EModeCode, EDockModeText, EModeText } from '@/utils/staticData/device';
import { getLnglatAltitude } from '@/utils/cesium/mapUtil.js';
import { useStore } from 'vuex';
import MachineTableDetails from '@/views/SignMachineNest/MachineRight/MachineStatus/MachineTableDetails/MachineTableDetails.vue';
import CommonTitle from '@/components/CommonTitle.vue'
import { EDeviceTypeName } from '@/utils/staticData/enums.js'
import { EModeCode, EDockModeText, EModeText } from '@/utils/staticData/device'
import { getLnglatAltitude } from '@/utils/cesium/mapUtil.js'
import { useStore } from 'vuex'
import MachineTableDetails from '@/views/SignMachineNest/MachineRight/MachineStatus/MachineTableDetails/MachineTableDetails.vue'
const store = useStore();
const store = useStore()
// 获取机巢信息
let osdVisible = ref({});//computed(() => store.state.home.osdVisible);
const singleUavHome = computed(() => store.state.home.singleUavHome);
let osdVisible = ref({}) //computed(() => store.state.home.osdVisible);
const singleUavHome = computed(() => store.state.home.singleUavHome)
// 单个机巢统计数据
const singleTotal = computed(() => store.state.home.singleTotal);
const singleTotal = computed(() => store.state.home.singleTotal)
// 是否展示机机巢状态详情
let isShowDetails = ref(false);
let isShowDetails = ref(false)
let str = '--';
let str = '--'
let drone_charge_state = ref({
  capacity_percent: '--',
  state: 0
    capacity_percent: '--',
    state: 0,
})
let AircraftStatus = ref(null);
let AircraftStatus = ref(null)
let detailInfo = ref({
  longitude: '--',
  latitude: '--',
  home_distance: '--',
  quality: '--',
  horizontal_speed: '--',
  height: '--',
  remain_flight_time: '--',
  capacity_percent: '--',
});
let quality = ref(['弱', '较弱', '中等', '较强', '强']);
    longitude: '--',
    latitude: '--',
    home_distance: '--',
    quality: '--',
    horizontal_speed: '--',
    height: '--',
    remain_flight_time: '--',
    capacity_percent: '--',
})
let quality = ref(['弱', '较弱', '中等', '较强', '强'])
let deviceInfo = ref({
  gateway: {
    capacity_percent: str,
    transmission_signal_quality: str,
  },
  dock: {},
  device: {
    gear: -1,
    mode_code: EModeCode.Disconnected,
    height: str,
    home_distance: str,
    horizontal_speed: str,
    vertical_speed: str,
    wind_speed: str,
    wind_direction: str,
    elevation: str,
    position_state: {
      gps_number: str,
      is_fixed: 0,
      rtk_number: str,
    },
    battery: {
      capacity_percent: str,
      landing_power: str,
      remain_flight_time: 0,
      return_home_power: str,
    },
    latitude: 0,
    longitude: 0,
  },
});
let mode_code = ref('已断开连接');
    gateway: {
        capacity_percent: str,
        transmission_signal_quality: str,
    },
    dock: {},
    device: {
        gear: -1,
        mode_code: EModeCode.Disconnected,
        height: str,
        home_distance: str,
        horizontal_speed: str,
        vertical_speed: str,
        wind_speed: str,
        wind_direction: str,
        elevation: str,
        position_state: {
            gps_number: str,
            is_fixed: 0,
            rtk_number: str,
        },
        battery: {
            capacity_percent: str,
            landing_power: str,
            remain_flight_time: 0,
            return_home_power: str,
        },
        latitude: 0,
        longitude: 0,
    },
})
let mode_code = ref('已断开连接')
watch(() => store.state.home.osdVisible, (newValue) => {
  osdVisible.value = newValue;
});
watch(
    () => store.state.home.osdVisible,
    newValue => {
        osdVisible.value = newValue
    }
)
// 监听实时信息
watch(
  () => store.state.home.wsMessage,
  (newValue) => {
    if (newValue.mode_code === 14) return
    if (Object.keys(newValue).length === 0) return
    detailInfo.value.longitude = newValue?.longitude.toFixed(6) || '--';
        detailInfo.value.latitude = newValue?.latitude.toFixed(6) || '--';
    // console.log(window.$viewer)
    getLnglatAltitude(Number(detailInfo.value.longitude), Number(detailInfo.value.latitude),window.$viewer).then((res) => {
      const height = newValue?.height - res?.height;
      //针对西安实时高度进行降低
      const wId = localStorage.getItem('bs_workspace_id');
      if (wId === 'f47ac10b-58cc-4372-a567-0e02b2c3d479') {
        detailInfo.value.height = reduceHeight(height);
      } else {
        detailInfo.value.height = height.toFixed(1) || '--';
      }
    () => store.state.home.wsMessage,
    newValue => {
        if (newValue.mode_code === 14) return
        if (Object.keys(newValue).length === 0) return
        detailInfo.value.longitude = newValue?.longitude.toFixed(6) || '--'
        detailInfo.value.latitude = newValue?.latitude.toFixed(6) || '--'
        // console.log(window.$viewer)
        getLnglatAltitude(Number(detailInfo.value.longitude), Number(detailInfo.value.latitude), window.$viewer).then(
            res => {
                const height = newValue?.height - res?.height
                //针对西安实时高度进行降低
                const wId = localStorage.getItem('bs_workspace_id')
                if (wId === 'f47ac10b-58cc-4372-a567-0e02b2c3d479') {
                    detailInfo.value.height = reduceHeight(height)
                } else {
                    detailInfo.value.height = height.toFixed(1) || '--'
                }
      detailInfo.value.quality = quality.value[newValue?.position_state.quality - 1] || '--';
      detailInfo.value.horizontal_speed = newValue?.horizontal_speed.toFixed(1) || '--';
      detailInfo.value.remain_flight_time = (newValue?.battery.remain_flight_time / 60).toFixed(0) || '--';
      detailInfo.value.capacity_percent = (newValue?.battery.capacity_percent).toFixed(0) || '--';
    });
  },
  { immediate: true, deep: true }
);
                detailInfo.value.quality = quality.value[newValue?.position_state.quality - 1] || '--'
                detailInfo.value.horizontal_speed = newValue?.horizontal_speed.toFixed(1) || '--'
                detailInfo.value.remain_flight_time = (newValue?.battery.remain_flight_time / 60).toFixed(0) || '--'
                detailInfo.value.capacity_percent = (newValue?.battery.capacity_percent).toFixed(0) || '--'
            }
        )
    },
    { immediate: true, deep: true }
)
// 获取最新机场状态
watch(store.state.home.deviceState, (newValue) => {
  // console.log('newValue', newValue);
    if (newValue.currentType === EDeviceTypeName.Dock && newValue?.dockInfo[newValue.currentSn]) {
      // 机场状态
      mode_code.value = EDockModeText[newValue?.dockInfo[newValue.currentSn]?.basic_osd?.mode_code];
      // this.$emit('updateModeCode', mode_code.value);
      // 舱内状态
      AircraftStatus.value =
        EModeText[newValue.deviceInfo[
            deviceInfo.value.dock.basic_osd?.sub_device?.device_sn ??
            osdVisible.sn
          ]?.mode_code
        ];
      // 舱内关机时显示的电量
      let child_sn = newValue?.dockInfo[newValue.currentSn].basic_osd.sub_device?.device_sn;
      // 飞机在线时取飞机中的电量
      if(newValue.deviceInfo[child_sn]) {
        drone_charge_state.value = {
          capacity_percent: newValue.deviceInfo[child_sn].battery.capacity_percent,
          state: newValue.deviceInfo[child_sn].battery.landing_power
        }
      } else{
        // 遥控器这里拿不到值data.drone_charge_state_new == undefined 会一直报错
        if (newValue.drone_charge_state_new) {
          drone_charge_state.value = newValue.drone_charge_state_new;
        }
      }
      if (osdVisible.value.visible && osdVisible.value.is_dock && osdVisible.value.gateway_sn !== '') {
        deviceInfo.value.dock = newValue.dockInfo[osdVisible.value.gateway_sn];
        deviceInfo.value.device =
        newValue.deviceInfo[deviceInfo.value.dock.basic_osd?.sub_device?.device_sn ??
        osdVisible.value.sn
          ];
        // 设备关机即不显示信息
        // 兼容遥控器 关闭无人机 mode_code返回的是14 不是undefined
        if (
          newValue.deviceInfo[
            deviceInfo.value.dock.basic_osd?.sub_device?.device_sn ??
            osdVisible.value.sn
          ]?.mode_code === undefined || newValue.deviceInfo[
            deviceInfo.value.dock.basic_osd?.sub_device?.device_sn ??
            osdVisible.value  .sn
          ]?.mode_code == 14
        ) {
          detailInfo.value = {
            longitude: '--',
            latitude: '--',
            home_distance: '--',
            quality: '--',
            horizontal_speed: '--',
            remain_flight_time: '--',
            height: '--',
            capacity_percent: '--',
          };
        }
      }
    }
  },
  { immediate: true, deep: true }
);
watch(
    store.state.home.deviceState,
    newValue => {
        // console.log('newValue', newValue);
        if (newValue.currentType === EDeviceTypeName.Dock && newValue?.dockInfo[newValue.currentSn]) {
            // 机场状态
            mode_code.value = EDockModeText[newValue?.dockInfo[newValue.currentSn]?.basic_osd?.mode_code]
            // this.$emit('updateModeCode', mode_code.value);
            // 舱内状态
            AircraftStatus.value =
                EModeText[
                    newValue.deviceInfo[deviceInfo.value.dock.basic_osd?.sub_device?.device_sn ?? osdVisible.sn]?.mode_code
                ]
            // 舱内关机时显示的电量
            let child_sn = newValue?.dockInfo[newValue.currentSn].basic_osd?.sub_device?.device_sn
            // 飞机在线时取飞机中的电量
            if (newValue.deviceInfo[child_sn]) {
                drone_charge_state.value = {
                    capacity_percent: newValue.deviceInfo[child_sn].battery.capacity_percent,
                    state: newValue.deviceInfo[child_sn].battery.landing_power,
                }
            } else {
                // 遥控器这里拿不到值data.drone_charge_state_new == undefined 会一直报错
                if (newValue.drone_charge_state_new) {
                    drone_charge_state.value = newValue.drone_charge_state_new
                }
            }
            if (osdVisible.value.visible && osdVisible.value.is_dock && osdVisible.value.gateway_sn !== '') {
                deviceInfo.value.dock = newValue.dockInfo[osdVisible.value.gateway_sn]
                deviceInfo.value.device =
                    newValue.deviceInfo[deviceInfo.value.dock.basic_osd?.sub_device?.device_sn ?? osdVisible.value.sn]
                // 设备关机即不显示信息
                // 兼容遥控器 关闭无人机 mode_code返回的是14 不是undefined
                if (
                    newValue.deviceInfo[deviceInfo.value.dock.basic_osd?.sub_device?.device_sn ?? osdVisible.value.sn]
                        ?.mode_code === undefined ||
                    newValue.deviceInfo[deviceInfo.value.dock.basic_osd?.sub_device?.device_sn ?? osdVisible.value.sn]
                        ?.mode_code == 14
                ) {
                    detailInfo.value = {
                        longitude: '--',
                        latitude: '--',
                        home_distance: '--',
                        quality: '--',
                        horizontal_speed: '--',
                        remain_flight_time: '--',
                        height: '--',
                        capacity_percent: '--',
                    }
                }
            }
        }
    },
    { immediate: true, deep: true }
)
const reduceHeight = (height) => {
  if (!Number(height)) return '--';
  let theFinheight = 0;
  if (height < 120) {
    theFinheight = height;
  } else if (height > 220) {
    theFinheight = height - 110;
  } else if (height > 210) {
    theFinheight = height - 100;
  } else if (height > 200) {
    theFinheight = height - 90;
  } else if (height > 190) {
    theFinheight = height - 80;
  } else if (height > 180) {
    theFinheight = height - 70;
  } else if (height > 170) {
    theFinheight = height - 60;
  } else if (height > 160) {
    theFinheight = height - 50;
  } else if (height > 150) {
    theFinheight = height - 40;
  } else if (height > 140) {
    theFinheight = height - 30;
  } else if (height > 130) {
    theFinheight = height - 20;
  } else if (height > 120) {
    theFinheight = height - 10;
  }
  return theFinheight.toFixed(1);
};
const reduceHeight = height => {
    if (!Number(height)) return '--'
    let theFinheight = 0
    if (height < 120) {
        theFinheight = height
    } else if (height > 220) {
        theFinheight = height - 110
    } else if (height > 210) {
        theFinheight = height - 100
    } else if (height > 200) {
        theFinheight = height - 90
    } else if (height > 190) {
        theFinheight = height - 80
    } else if (height > 180) {
        theFinheight = height - 70
    } else if (height > 170) {
        theFinheight = height - 60
    } else if (height > 160) {
        theFinheight = height - 50
    } else if (height > 150) {
        theFinheight = height - 40
    } else if (height > 140) {
        theFinheight = height - 30
    } else if (height > 130) {
        theFinheight = height - 20
    } else if (height > 120) {
        theFinheight = height - 10
    }
    return theFinheight.toFixed(1)
}
// 详情
const detailsFun = () => {
  isShowDetails.value = true;
};
    isShowDetails.value = true
}
</script>
<style lang="scss" scoped>
  .machine-status {
    width: 390px;
    height: 230px;
    background: linear-gradient(
      270deg,
      #1f3e7a 0%,
      rgba(31, 62, 122, 0.35) 79%,
      rgba(31, 62, 122, 0) 100%
    );
    opacity: 0.85;
    margin: 2px 0 13 0;
.machine-status {
    width: 390px;
    height: 230px;
    background: linear-gradient(270deg, #1f3e7a 0%, rgba(31, 62, 122, 0.35) 79%, rgba(31, 62, 122, 0) 100%);
    opacity: 0.85;
    margin: 2px 0 13 0;
    padding: 8px 0px 20px 18px;
    .info {
      display: flex;
      // justify-content: ce;
      height: 88px;
      display: flex;
      align-items: center;
      img {
        width: 94px;
        height: 88px;
        margin-right: 22px;
      }
      .info-right {
        .name {
          width: 184px;
          height: 31px;
          font-family: YouSheBiaoTiHei, YouSheBiaoTiHei;
          font-weight: 400;
          font-size: 24px;
          color: #0BE5F5;
          line-height: 28px;
          overflow: hidden;          // 添加溢出隐藏
          text-overflow: ellipsis;   // 显示省略号
          white-space: nowrap;       // 不换行
        }
        .wz {
          font-family: Source Han Sans CN, Source Han Sans CN;
          font-size: 14px;
          color: #FFFFFF;
            line-height: 16px;
          .left {
            width: 70px;
            height: 21px;
            font-weight: 400;
            opacity: 0.6;
          }
          .right {
            width: 100px;
            height: 21px;
            font-weight: bold;
          }
        }
        .close-wb {
          display: flex;
          margin-top: 10px;
          text-align: center;
          line-height: 16px;
          .close {
            width: 60px;
            height: 20px;
            line-height: 20px;
            background: rgba(255,178,106,0.2);
            border-radius: 4px 4px 4px 4px;
            border: 1px solid #FFB26A;
            color: #FFB26A;
            margin-right: 10px;
          }
          .other {
            border: 1px solid #8EFFAC;
            color: #8EFFAC;
          }
          .wb {
            width: 60px;
            height: 20px;
            background: rgba(255,106,106,0.2);
            border-radius: 4px 4px 4px 4px;
            border: 1px solid #FF6A6A;
            color: #FF6A6A;
          }
        }
      }
    }
    .status {
      display: flex;
      flex-wrap: wrap;
      gap: 10px 20px;
      margin-top: 18px;
      .card {
        width: calc(33.33% - 14px);
      }
      img {
        width: 16px;
        height: 16px;
        // margin-right: 0px;
        // margin-top: 2px;
      }
      .text {
        width: 56px;
        height: 21px;
        font-family: Source Han Sans CN, Source Han Sans CN;
        font-weight: 400;
        font-size: 14px;
        color: #FFFFFF;
        line-height: 16px;
      }
      .text-data {
        // width: 40px;
        margin-left: 20px;
        height: 23px;
        font-family: YouSheBiaoTiHei, YouSheBiaoTiHei;
        font-weight: 400;
        font-size: 18px;
        color: #0BE5F5;
        line-height: 21px;
      }
    }
 }
    padding: 8px 0px 20px 18px;
    .info {
        display: flex;
        // justify-content: ce;
        height: 88px;
        display: flex;
        align-items: center;
        img {
            width: 94px;
            height: 88px;
            margin-right: 22px;
        }
        .info-right {
            .name {
                width: 184px;
                height: 31px;
                font-family: YouSheBiaoTiHei, YouSheBiaoTiHei;
                font-weight: 400;
                font-size: 24px;
                color: #0be5f5;
                line-height: 28px;
                overflow: hidden; // 添加溢出隐藏
                text-overflow: ellipsis; // 显示省略号
                white-space: nowrap; // 不换行
            }
            .wz {
                font-family: Source Han Sans CN, Source Han Sans CN;
                font-size: 14px;
                color: #ffffff;
                line-height: 16px;
                .left {
                    width: 70px;
                    height: 21px;
                    font-weight: 400;
                    opacity: 0.6;
                }
                .right {
                    width: 100px;
                    height: 21px;
                    font-weight: bold;
                }
            }
            .close-wb {
                display: flex;
                margin-top: 10px;
                text-align: center;
                line-height: 16px;
                .close {
                    width: 60px;
                    height: 20px;
                    line-height: 20px;
                    background: rgba(255, 178, 106, 0.2);
                    border-radius: 4px 4px 4px 4px;
                    border: 1px solid #ffb26a;
                    color: #ffb26a;
                    margin-right: 10px;
                }
                .other {
                    border: 1px solid #8effac;
                    color: #8effac;
                }
                .wb {
                    width: 60px;
                    height: 20px;
                    background: rgba(255, 106, 106, 0.2);
                    border-radius: 4px 4px 4px 4px;
                    border: 1px solid #ff6a6a;
                    color: #ff6a6a;
                }
            }
        }
    }
    .status {
        display: flex;
        flex-wrap: wrap;
        gap: 10px 20px;
        margin-top: 18px;
        .card {
            width: calc(33.33% - 14px);
        }
        img {
            width: 16px;
            height: 16px;
            // margin-right: 0px;
            // margin-top: 2px;
        }
        .text {
            width: 56px;
            height: 21px;
            font-family: Source Han Sans CN, Source Han Sans CN;
            font-weight: 400;
            font-size: 14px;
            color: #ffffff;
            line-height: 16px;
        }
        .text-data {
            // width: 40px;
            margin-left: 20px;
            height: 23px;
            font-family: YouSheBiaoTiHei, YouSheBiaoTiHei;
            font-weight: 400;
            font-size: 18px;
            color: #0be5f5;
            line-height: 21px;
        }
    }
}
</style>
src/views/SignMachineNest/MachineRight/MachineStatus/MachineTableDetails/DeviceEvent.vue
@@ -70,7 +70,10 @@
const total = ref(0)
const distribution = row => {
    ElMessage.warning('正在加急开发中...')
    const orderNumber = row.event_num
    const adminUrl = import.meta.env.VITE_APP_ADMIN_URL
    const targetPath = `/tickets/ticket?orderNumber=${orderNumber}`;
    window.open(`${adminUrl}?redirect=${encodeURIComponent(targetPath)}`, '_blank');
}
const examine = row => {
    ElMessage.warning('正在加急开发中...')