1450 lines
58 KiB
Vue
1450 lines
58 KiB
Vue
<template>
|
||
<!-- 参数缺失时的友好提示 -->
|
||
<div v-if="!route.query.id" class="error-container">
|
||
<el-result icon="warning" title="参数缺失" sub-title="缺少必要的参数,无法加载详情页面">
|
||
<template #extra>
|
||
<el-button type="primary" @click="goBack">返回上一页</el-button>
|
||
<el-button @click="goToList">前往列表页面</el-button>
|
||
</template>
|
||
</el-result>
|
||
</div>
|
||
|
||
<!-- 正常内容 -->
|
||
<div v-else class="details-container">
|
||
<!-- 头部导航与操作 -->
|
||
<div class="page-header">
|
||
<div class="header-left">
|
||
<span class="page-title">运单详情</span>
|
||
<el-tag :type="getStatusType(data.baseInfo.status)" class="status-tag" size="large" effect="dark">
|
||
{{ getStatusText(data.baseInfo.status) }}
|
||
</el-tag>
|
||
</div>
|
||
<div class="header-right">
|
||
<el-button @click="goBack">返回</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 基础信息卡片 -->
|
||
<el-card class="section-card" shadow="hover">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span class="header-title">基础信息</span>
|
||
</div>
|
||
</template>
|
||
<el-descriptions :column="4" border size="large">
|
||
<el-descriptions-item label="运单号">{{ data.baseInfo.deliveryNumber || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="卖方">{{ getSupplierName() }}</el-descriptions-item>
|
||
<el-descriptions-item label="买方">{{ getBuyerName() }}</el-descriptions-item>
|
||
<el-descriptions-item label="车牌号">{{ data.baseInfo.licensePlate || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="司机姓名">{{ data.baseInfo.driverName || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="起始地">{{ data.baseInfo.startLocation || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="送达目的地">{{ data.baseInfo.endLocation || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="预计送达时间">{{ data.baseInfo.estimatedDeliveryTime || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="创建时间">{{ data.baseInfo.createTime || '' }}</el-descriptions-item>
|
||
<el-descriptions-item label="司机运费">{{ data.baseInfo.freight ? data.baseInfo.freight + ' 元' : '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="登记设备数量">{{ totalRegisteredDevices }} 个</el-descriptions-item>
|
||
</el-descriptions>
|
||
</el-card>
|
||
|
||
<!-- 预警信息卡片 -->
|
||
<el-card v-if="data.warnInfo && data.warnInfo.length > 0" class="section-card warning-card" shadow="hover">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span class="header-title warning-text">预警信息</span>
|
||
</div>
|
||
</template>
|
||
<div class="warning-list">
|
||
<el-descriptions :column="4" border v-for="(item, index) in data.warnInfo" :key="index" class="warning-item">
|
||
<template v-if="item.warningType == 2">
|
||
<el-descriptions-item label="预警原因">
|
||
<span class="red">{{ item.warningTypeDesc }}</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="预警时间">{{ item.warningTime || '--' }}</el-descriptions-item>
|
||
<el-descriptions-item label="智能耳标数"> {{ totalRegisteredDevices }} 个 </el-descriptions-item>
|
||
<el-descriptions-item label="车内盘点数量"> {{ item.inventoryJbqCount || '--' }} 个 </el-descriptions-item>
|
||
</template>
|
||
<template v-if="item.warningType == 3">
|
||
<el-descriptions-item label="预警原因">
|
||
<span class="red">{{ item.warningTypeDesc || '' }}</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="预警时间">{{ item.warningTime || '' }}</el-descriptions-item>
|
||
<el-descriptions-item label="应行驶距离"> {{ item.expectedDistance || '' }} km </el-descriptions-item>
|
||
<el-descriptions-item label="实际行驶距离"> {{ item.actualDistance || '' }} km </el-descriptions-item>
|
||
</template>
|
||
</el-descriptions>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 装车信息卡片 -->
|
||
<el-card class="section-card" shadow="hover">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span class="header-title">装车信息</span>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 重量信息分组 -->
|
||
<div v-if="hasValue(data.baseInfo.emptyWeight) || hasValue(data.baseInfo.entruckWeight) || hasValue(data.baseInfo.landingEntruckWeight)" class="info-group">
|
||
<div class="card-header">
|
||
<span class="header-title">重量信息</span>
|
||
</div>
|
||
<div class="weight-info-container">
|
||
<!-- 基础重量信息 -->
|
||
<div class="weight-basic-info">
|
||
<el-descriptions :column="3" border size="large">
|
||
<el-descriptions-item v-if="hasValue(data.baseInfo.emptyWeight)" label="空车过磅重量">
|
||
{{ data.baseInfo.emptyWeight }}kg
|
||
</el-descriptions-item>
|
||
<el-descriptions-item v-if="hasValue(data.baseInfo.entruckWeight)" label="装车过磅重量">
|
||
{{ data.baseInfo.entruckWeight }}kg
|
||
</el-descriptions-item>
|
||
<el-descriptions-item v-if="hasValue(data.baseInfo.landingEntruckWeight)" label="落地过磅重量">
|
||
{{ data.baseInfo.landingEntruckWeight }}kg
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</div>
|
||
<!-- 计算重量信息 -->
|
||
<div class="weight-calculated-info">
|
||
<el-descriptions :column="3" border size="large">
|
||
<el-descriptions-item v-if="departureCattleWeight !== null" label="发车牛只重量">
|
||
<span class="calculated-value">{{ departureCattleWeight }}kg</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item v-if="landingCattleWeight !== null" label="落地牛只重量">
|
||
<span class="calculated-value">{{ landingCattleWeight }}kg</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item v-if="weightLoss !== null" label="损耗">
|
||
<span class="calculated-value loss-value" :class="{ 'positive-loss': weightLoss < 0, 'negative-loss': weightLoss > 0 }">
|
||
{{ weightLoss > 0 ? '-' : weightLoss < 0 ? '+' : '' }}{{ Math.abs(weightLoss) }}kg
|
||
</span>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 照片信息分组 -->
|
||
<div v-if="hasValue(data.baseInfo.quarantineTickeyUrl) || hasValue(data.baseInfo.poundListImg) || hasValue(data.baseInfo.emptyVehicleFrontPhoto) || hasValue(data.baseInfo.loadedVehicleFrontPhoto) || hasValue(data.baseInfo.loadedVehicleWeightPhoto) || hasValue(data.baseInfo.driverIdCardPhoto) || hasValue(data.baseInfo.destinationPoundListImg) || hasValue(data.baseInfo.destinationVehicleFrontPhoto)" class="info-group">
|
||
<div class="sub-title">照片信息</div>
|
||
<div class="media-grid">
|
||
<div class="media-item" v-if="data.baseInfo.carFrontPhoto || data.baseInfo.carBehindPhoto">
|
||
<div class="media-label">车身照片</div>
|
||
<div class="media-content">
|
||
<el-image
|
||
v-if="data.baseInfo.carFrontPhoto"
|
||
class="photo-img"
|
||
:src="data.baseInfo.carFrontPhoto"
|
||
fit="cover"
|
||
:preview-src-list="[data.baseInfo.carFrontPhoto]"
|
||
preview-teleported
|
||
/>
|
||
<el-image
|
||
v-if="data.baseInfo.carBehindPhoto"
|
||
class="photo-img"
|
||
:src="data.baseInfo.carBehindPhoto"
|
||
fit="cover"
|
||
:preview-src-list="[data.baseInfo.carBehindPhoto]"
|
||
preview-teleported
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div class="media-item" v-if="hasValue(data.baseInfo.quarantineTickeyUrl)">
|
||
<div class="media-label">检疫票</div>
|
||
<el-image
|
||
class="photo-img"
|
||
:src="data.baseInfo.quarantineTickeyUrl"
|
||
fit="cover"
|
||
:preview-src-list="[data.baseInfo.quarantineTickeyUrl]"
|
||
preview-teleported
|
||
/>
|
||
</div>
|
||
<div class="media-item" v-if="hasValue(data.baseInfo.poundListImg)">
|
||
<div class="media-label">纸质磅单</div>
|
||
<el-image
|
||
class="photo-img"
|
||
:src="data.baseInfo.poundListImg"
|
||
fit="cover"
|
||
:preview-src-list="[data.baseInfo.poundListImg]"
|
||
preview-teleported
|
||
/>
|
||
</div>
|
||
<div class="media-item" v-if="hasValue(data.baseInfo.emptyVehicleFrontPhoto)">
|
||
<div class="media-label">空磅车头照片</div>
|
||
<el-image
|
||
class="photo-img"
|
||
:src="data.baseInfo.emptyVehicleFrontPhoto"
|
||
fit="cover"
|
||
:preview-src-list="[data.baseInfo.emptyVehicleFrontPhoto]"
|
||
preview-teleported
|
||
/>
|
||
</div>
|
||
<div class="media-item" v-if="hasValue(data.baseInfo.loadedVehicleFrontPhoto)">
|
||
<div class="media-label">过重磅车头照片</div>
|
||
<el-image
|
||
class="photo-img"
|
||
:src="data.baseInfo.loadedVehicleFrontPhoto"
|
||
fit="cover"
|
||
:preview-src-list="[data.baseInfo.loadedVehicleFrontPhoto]"
|
||
preview-teleported
|
||
/>
|
||
</div>
|
||
<div class="media-item" v-if="hasValue(data.baseInfo.loadedVehicleWeightPhoto)">
|
||
<div class="media-label">车辆重磅照片</div>
|
||
<el-image
|
||
class="photo-img"
|
||
:src="data.baseInfo.loadedVehicleWeightPhoto"
|
||
fit="cover"
|
||
:preview-src-list="[data.baseInfo.loadedVehicleWeightPhoto]"
|
||
preview-teleported
|
||
/>
|
||
</div>
|
||
<div class="media-item" v-if="hasValue(data.baseInfo.driverIdCardPhoto)">
|
||
<div class="media-label">驾驶员手持身份证</div>
|
||
<el-image
|
||
class="photo-img"
|
||
:src="data.baseInfo.driverIdCardPhoto"
|
||
fit="cover"
|
||
:preview-src-list="[data.baseInfo.driverIdCardPhoto]"
|
||
preview-teleported
|
||
/>
|
||
</div>
|
||
<div class="media-item" v-if="hasValue(data.baseInfo.destinationPoundListImg)">
|
||
<div class="media-label">落地纸质磅单</div>
|
||
<el-image
|
||
class="photo-img"
|
||
:src="data.baseInfo.destinationPoundListImg"
|
||
fit="cover"
|
||
:preview-src-list="[data.baseInfo.destinationPoundListImg]"
|
||
preview-teleported
|
||
/>
|
||
</div>
|
||
<div class="media-item" v-if="hasValue(data.baseInfo.destinationVehicleFrontPhoto)">
|
||
<div class="media-label">落地过重磅车头</div>
|
||
<el-image
|
||
class="photo-img"
|
||
:src="data.baseInfo.destinationVehicleFrontPhoto"
|
||
fit="cover"
|
||
:preview-src-list="[data.baseInfo.destinationVehicleFrontPhoto]"
|
||
preview-teleported
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 视频信息分组 -->
|
||
<div v-if="hasValue(data.baseInfo.emptyWeightVideo) || hasValue(data.baseInfo.entruckWeightVideo) || hasValue(data.baseInfo.entruckVideo) || hasValue(data.baseInfo.controlSlotVideo) || hasValue(data.baseInfo.cattleLoadingCircleVideo) || hasValue(data.baseInfo.unloadCattleVideo) || hasValue(data.baseInfo.destinationWeightVideo)" class="info-group">
|
||
<div class="sub-title">视频信息</div>
|
||
<div class="media-grid">
|
||
<div class="media-item video-item" v-if="hasValue(data.baseInfo.emptyWeightVideo)">
|
||
<div class="media-label">空车过磅视频</div>
|
||
<video controls :src="data.baseInfo.emptyWeightVideo" />
|
||
</div>
|
||
<div class="media-item video-item" v-if="hasValue(data.baseInfo.entruckWeightVideo)">
|
||
<div class="media-label">装车过磅视频</div>
|
||
<video controls :src="data.baseInfo.entruckWeightVideo" />
|
||
</div>
|
||
<div class="media-item video-item" v-if="hasValue(data.baseInfo.entruckVideo)">
|
||
<div class="media-label">装车视频</div>
|
||
<video controls :src="data.baseInfo.entruckVideo" />
|
||
</div>
|
||
<div class="media-item video-item" v-if="hasValue(data.baseInfo.controlSlotVideo)">
|
||
<div class="media-label">控槽视频</div>
|
||
<video controls :src="data.baseInfo.controlSlotVideo" />
|
||
</div>
|
||
<div class="media-item video-item" v-if="hasValue(data.baseInfo.cattleLoadingCircleVideo)">
|
||
<div class="media-label">装完牛绕车一圈</div>
|
||
<video controls :src="data.baseInfo.cattleLoadingCircleVideo" />
|
||
</div>
|
||
<div class="media-item video-item" v-if="hasValue(data.baseInfo.unloadCattleVideo)">
|
||
<div class="media-label">卸牛视频</div>
|
||
<video controls :src="data.baseInfo.unloadCattleVideo" />
|
||
</div>
|
||
<div class="media-item video-item" v-if="hasValue(data.baseInfo.destinationWeightVideo)">
|
||
<div class="media-label">落地过磅视频</div>
|
||
<video controls :src="data.baseInfo.destinationWeightVideo" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 设备信息卡片 -->
|
||
<el-card class="section-card" shadow="hover">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span class="header-title">设备信息</span>
|
||
</div>
|
||
</template>
|
||
<el-tabs v-model="activeDeviceTab" type="border-card">
|
||
<el-tab-pane label="智能主机" name="host">
|
||
<el-table
|
||
:data="data.hostRows"
|
||
border
|
||
stripe
|
||
v-loading="data.hostDataListLoading"
|
||
element-loading-text="数据加载中..."
|
||
style="width: 100%"
|
||
>
|
||
<el-table-column label="智能主机编号" prop="deviceId"></el-table-column>
|
||
<el-table-column label="设备电量" prop="battery">
|
||
<template #default="scope"> {{ scope.row.battery || scope.row.deviceVoltage || '-' }}% </template>
|
||
</el-table-column>
|
||
<el-table-column label="步数" prop="steps">
|
||
<template #default="scope"> {{ scope.row.steps || scope.row.walkSteps || '-' }}步</template>
|
||
</el-table-column>
|
||
<el-table-column label="设备温度" prop="temperature">
|
||
<template #default="scope"> {{ scope.row.temperature || scope.row.deviceTemp || '-' }}℃ </template>
|
||
</el-table-column>
|
||
<el-table-column label="数据最后更新时间" prop="time">
|
||
<template #default="scope"> {{ scope.row.time || scope.row.updateTime || scope.row.createTime || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="220" fixed="right">
|
||
<template #default="scope">
|
||
<el-button link type="primary" @click="hostLogClick(scope.row)">日志</el-button>
|
||
<el-button link type="primary" @click="hostTrackClick(scope.row)">运动轨迹</el-button>
|
||
<el-button link type="primary" @click="hostLocationClick(scope.row)">定位</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="智能项圈" name="collar">
|
||
<el-table
|
||
:data="data.collarRows"
|
||
border
|
||
stripe
|
||
v-loading="data.collarDataListLoading"
|
||
element-loading-text="数据加载中..."
|
||
style="width: 100%"
|
||
>
|
||
<el-table-column label="智能项圈编号" prop="deviceId"></el-table-column>
|
||
<el-table-column label="设备电量" prop="battery">
|
||
<template #default="scope"> {{ scope.row.battery || scope.row.deviceVoltage || '-' }}% </template>
|
||
</el-table-column>
|
||
<el-table-column label="步数" prop="steps">
|
||
<template #default="scope"> {{ scope.row.steps || scope.row.walkSteps || '-' }}步</template>
|
||
</el-table-column>
|
||
<el-table-column label="设备温度" prop="deviceTemp">
|
||
<template #default="scope"> {{ scope.row.deviceTemp || scope.row.temperature || '-' }}℃ </template>
|
||
</el-table-column>
|
||
<el-table-column label="数据最后更新时间" prop="time">
|
||
<template #default="scope"> {{ scope.row.time || scope.row.updateTime || scope.row.createTime || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="180" fixed="right">
|
||
<template #default="scope">
|
||
<el-button link type="primary" @click="collarLogClick(scope.row)">日志</el-button>
|
||
<el-button link type="primary" @click="collarTrackClick(scope.row)">运动轨迹</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="pagination-container">
|
||
<pagination
|
||
v-model:limit="collarForm.pageSize"
|
||
v-model:page="collarForm.pageNum"
|
||
:total="data.collarTotal"
|
||
@pagination="getCollarList"
|
||
/>
|
||
</div>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="智能耳标" name="ear">
|
||
<el-table :data="data.rows" border stripe v-loading="data.dataListLoading" element-loading-text="数据加载中..." style="width: 100%">
|
||
<el-table-column label="智能耳标编号" prop="deviceId"></el-table-column>
|
||
|
||
<el-table-column label="设备电量" prop="battery">
|
||
<template #default="scope"> {{ scope.row.battery || scope.row.deviceVoltage || '-' }}% </template>
|
||
</el-table-column>
|
||
<el-table-column label="步数" prop="walkSteps">
|
||
<template #default="scope"> {{ scope.row.walkSteps || scope.row.steps || '-' }}步</template>
|
||
</el-table-column>
|
||
<el-table-column label="设备温度" prop="deviceTemp">
|
||
<template #default="scope"> {{ scope.row.deviceTemp || scope.row.temperature || '-' }}℃ </template>
|
||
</el-table-column>
|
||
<el-table-column label="数据最后更新时间" prop="updateTime">
|
||
<template #default="scope"> {{ scope.row.updateTime || scope.row.createTime || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="180" fixed="right">
|
||
<template #default="scope">
|
||
<el-button link type="primary" @click="earLogClick(scope.row)">日志</el-button>
|
||
<el-button link type="primary" @click="earTrackClick(scope.row)">运动轨迹</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="pagination-container">
|
||
<pagination v-model:limit="form.pageSize" v-model:page="form.pageNum" :total="data.total" @pagination="getEarList" />
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</el-card>
|
||
|
||
<!-- 查看主机定位 -->
|
||
<el-dialog v-model="data.dialogVisible" title="查看定位" style="width: 650px; padding-bottom: 20px" destroy-on-close>
|
||
<div>
|
||
<baidu-map style="height: 500px" class="map" :zoom="15" :center="data.center" :scroll-wheel-zoom="true" v-if="data.dialogVisible">
|
||
<bm-map-type :map-types="['BMAP_NORMAL_MAP', 'BMAP_HYBRID_MAP']"></bm-map-type>
|
||
<bm-marker :position="data.center" :dragging="true" animation="BMAP_ANIMATION_BOUNCE">
|
||
<bm-label
|
||
:content="data.updateTime"
|
||
:labelStyle="{
|
||
color: '#67c23a',
|
||
fontSize: '12px',
|
||
borderColor: '#fff',
|
||
borderRadius: 10,
|
||
}"
|
||
:offset="{ width: -60, height: 10 }"
|
||
/>
|
||
</bm-marker>
|
||
</baidu-map>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 查看轨迹 -->
|
||
<el-dialog v-model="data.trackDialogVisible" title="查看运动轨迹" style="width: 650px; padding-bottom: 20px" destroy-on-close>
|
||
<div
|
||
v-loading="data.trackLoading"
|
||
element-loading-text="正在加载中..."
|
||
style="height: 450px"
|
||
element-loading-background="rgba(255, 255, 255,1)"
|
||
>
|
||
<div class="empty-box" v-if="data.trajectoryStatus">
|
||
<img style="width: 50%" src="../../assets/images/wuguiji.png" />
|
||
</div>
|
||
<baidu-map
|
||
v-else
|
||
class="map"
|
||
@ready="handler"
|
||
:center="data.centerPoint"
|
||
:zoom="data.trackZoom"
|
||
:dragging="true"
|
||
:auto-resize="true"
|
||
:scroll-wheel-zoom="true"
|
||
style="height: 450px"
|
||
>
|
||
<bm-polyline stroke-color="blue" :path="data.path" :stroke-opacity="0.5" :stroke-weight="3" :editing="false"></bm-polyline>
|
||
<bm-marker :icon="startMarkIcon" :position="{ lng: data.startMark.lng, lat: data.startMark.lat }"></bm-marker>
|
||
<bm-marker :icon="endMarkIcon" :position="{ lng: data.endMark.lng, lat: data.endMark.lat }"></bm-marker>
|
||
</baidu-map>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 日志弹窗:耳标 -->
|
||
<el-dialog v-model="data.earLogDialogVisible" title="设备日志" width="900px" append-to-body destroy-on-close>
|
||
<el-table :data="data.earLogRows" border stripe v-loading="data.logListLoading" element-loading-text="数据加载中..." style="width: 100%">
|
||
<el-table-column label="智能耳标编号" prop="deviceId"></el-table-column>
|
||
<el-table-column label="设备电量" prop="deviceVoltage">
|
||
<template #default="scope"> {{ scope.row.deviceVoltage || scope.row.battery || '-' }}% </template>
|
||
</el-table-column>
|
||
<el-table-column label="步数" prop="walkSteps">
|
||
<template #default="scope"> {{ scope.row.walkSteps || scope.row.steps || '-' }}步</template>
|
||
</el-table-column>
|
||
<el-table-column label="设备温度" prop="deviceTemp">
|
||
<template #default="scope"> {{ scope.row.deviceTemp || scope.row.temperature || '-' }}℃ </template>
|
||
</el-table-column>
|
||
<el-table-column label="数据最后更新时间" prop="updateTime" width="180">
|
||
<template #default="scope"> {{ scope.row.updateTime || scope.row.createTime || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="100">
|
||
<template #default="scope">
|
||
<el-button link type="primary" @click="locateClick(scope.row)">定位</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="pagination-container">
|
||
<pagination v-model:limit="earLogForm.pageSize" v-model:page="earLogForm.pageNum" :total="data.earLogTotal" @pagination="getEarLogList" />
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 日志弹窗:项圈 -->
|
||
<el-dialog v-model="data.collarDialogVisible" title="设备日志" width="900px" append-to-body destroy-on-close>
|
||
<el-table :data="data.collarLogRows" border stripe v-loading="data.logListLoading" element-loading-text="数据加载中..." style="width: 100%">
|
||
<el-table-column label="智能项圈编号" prop="deviceId"></el-table-column>
|
||
<el-table-column label="设备电量" prop="battery">
|
||
<template #default="scope"> {{ scope.row.battery || scope.row.deviceVoltage || '-' }}% </template>
|
||
</el-table-column>
|
||
<el-table-column label="步数" prop="steps">
|
||
<template #default="scope"> {{ scope.row.steps || scope.row.walkSteps || '-' }}步</template>
|
||
</el-table-column>
|
||
<el-table-column label="设备温度" prop="temperature">
|
||
<template #default="scope"> {{ scope.row.temperature || scope.row.deviceTemp || '-' }}℃ </template>
|
||
</el-table-column>
|
||
<el-table-column label="数据最后更新时间" prop="time" width="180">
|
||
<template #default="scope"> {{ scope.row.time || scope.row.updateTime || scope.row.createTime || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="100">
|
||
<template #default="scope">
|
||
<el-button link type="primary" @click="collarlocateClick(scope.row)">定位</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="pagination-container">
|
||
<pagination
|
||
v-model:limit="collarLogForm.pageSize"
|
||
v-model:page="collarLogForm.pageNum"
|
||
:total="data.collarLogTotal"
|
||
@pagination="getCollarLogList"
|
||
/>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 日志弹窗:主机 -->
|
||
<el-dialog v-model="data.hostLogDialogVisible" title="智能主机日志" width="900px" append-to-body destroy-on-close>
|
||
<el-table :data="data.hostLogRows" border stripe v-loading="data.logListLoading" element-loading-text="数据加载中..." style="width: 100%">
|
||
<el-table-column label="智能主机编号" prop="deviceId"></el-table-column>
|
||
<el-table-column label="设备电量" prop="deviceVoltage">
|
||
<template #default="scope"> {{ scope.row.deviceVoltage || scope.row.battery || '-' }}% </template>
|
||
</el-table-column>
|
||
<el-table-column label="步数" prop="walkSteps">
|
||
<template #default="scope"> {{ scope.row.walkSteps || scope.row.steps || '-' }}步</template>
|
||
</el-table-column>
|
||
<el-table-column label="设备温度" prop="deviceTemp">
|
||
<template #default="scope"> {{ scope.row.deviceTemp || scope.row.temperature || '-' }}℃ </template>
|
||
</el-table-column>
|
||
<el-table-column label="小时时间" prop="hourTime" width="180">
|
||
<template #default="scope"> {{ scope.row.hourTime || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="数据最后更新时间" prop="updateTime" width="180">
|
||
<template #default="scope"> {{ scope.row.updateTime || scope.row.createTime || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="100">
|
||
<template #default="scope">
|
||
<el-button link type="primary" @click="hostLocationClick(scope.row)">定位</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-dialog>
|
||
|
||
<TrackDialog ref="TrackDialogRef" />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, onMounted, computed } from 'vue';
|
||
import { useRoute, useRouter } from 'vue-router';
|
||
import { ElMessage } from 'element-plus';
|
||
import { earList, hostLocation, hostTrack, waybillDetail, collarList, collarLogList, earLogList, testDeliveryDevices, pageDeviceList, getEarTagLogs, getCollarLogs, getHostLogs, getEarTagTrajectory, getCollarTrajectory, getHostTrajectory } from '@/api/abroad.js';
|
||
import { vehicleList } from '@/api/userManage.js';
|
||
import startIcon from '../../assets/images/qi.png';
|
||
import endIcon from '../../assets/images/zhong.png';
|
||
import TrackDialog from '../hardware/trackDialog.vue';
|
||
|
||
const route = useRoute();
|
||
const router = useRouter(); // 添加 router
|
||
const TrackDialogRef = ref();
|
||
const activeDeviceTab = ref('host'); // 新增:设备Tabs激活项
|
||
|
||
const startMarkIcon = reactive({
|
||
url: startIcon,
|
||
size: { width: 32, height: 32 },
|
||
opts: { anchor: { width: 16, height: 16 } },
|
||
});
|
||
const endMarkIcon = reactive({
|
||
url: endIcon,
|
||
size: { width: 32, height: 32 },
|
||
opts: { anchor: { width: 16, height: 16 } },
|
||
});
|
||
const data = reactive({
|
||
status: '',
|
||
length: '',
|
||
imgArr: [],
|
||
baseInfo: {
|
||
id: 1,
|
||
createByDesc: '申报人',
|
||
createByPhone: '联系电话',
|
||
rgstJbqNum: '申报数量',
|
||
createTime: '创建时间',
|
||
deliverNo: '申报运单号',
|
||
carNo: '车牌号',
|
||
quarStatusDesc: '入场状态', // 根据status反显
|
||
status: '订单状态 1境外预检 2 已入境待隔离(检疫成功) 3 已入隔离场 4 隔离场出场 ',
|
||
deliverTime: '启运时间',
|
||
// 新增照片字段
|
||
destinationPoundListImg: '',
|
||
destinationVehicleFrontPhoto: '',
|
||
// 新增视频字段
|
||
unloadCattleVideo: '',
|
||
destinationWeightVideo: '',
|
||
},
|
||
warnInfo: [],
|
||
serverIds: '',
|
||
dataListLoading: false,
|
||
rows: [],
|
||
total: 0,
|
||
radio: '1',
|
||
checkStatus: '', // 车内盘点状态
|
||
hgCheckStatus: '', // 海关盘点状态
|
||
dialogVisible: false, // 查看主机弹窗
|
||
center: { lng: 0, lat: 0 },
|
||
updateTime: '', // 主机定位时间
|
||
// 轨迹
|
||
trackDialogVisible: false,
|
||
trackZoom: 15,
|
||
path: [],
|
||
centerPoint: { lng: 0, lat: 0 },
|
||
trackLoading: false,
|
||
trajectoryStatus: true,
|
||
startMark: {},
|
||
endMark: {},
|
||
confirmLoading: false,
|
||
collarDataListLoading: false,
|
||
collarRows: [],
|
||
collarTotal: 0,
|
||
logListLoading: false,
|
||
earLogRows: [],
|
||
earLogDialogVisible: false,
|
||
collarDialogVisible: false,
|
||
hostLogDialogVisible: false,
|
||
earLogTotal: 0,
|
||
collarLogRows: [],
|
||
collarLogTotal: 0,
|
||
hostLogRows: [],
|
||
hostLogTotal: 0,
|
||
deviceId: '', // 耳标编号
|
||
sn: '', // 项圈编号
|
||
// 智能主机相关
|
||
hostDataListLoading: false,
|
||
hostRows: [],
|
||
hostTotal: 0,
|
||
});
|
||
const form = reactive({
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
});
|
||
const collarForm = reactive({
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
});
|
||
// 耳标日志
|
||
const earLogForm = reactive({
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
});
|
||
// 项圈日志
|
||
const collarLogForm = reactive({
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
});
|
||
// 查详情
|
||
const getDetail = () => {
|
||
|
||
if (!route.query.id) {
|
||
console.warn('=== 警告:deliveryId为空,跳过运单详情查询');
|
||
return;
|
||
}
|
||
|
||
waybillDetail(route.query.id)
|
||
.then((res) => {
|
||
|
||
if (res.code === 200) {
|
||
data.baseInfo = res.data.delivery ? res.data.delivery : {};
|
||
data.warnInfo = res.data.warningLog ? res.data.warningLog : {};
|
||
data.serverIds = res.data.serverIds ? res.data.serverIds : [];
|
||
|
||
// 查询车辆照片
|
||
if (data.baseInfo.licensePlate) {
|
||
loadVehiclePhotos();
|
||
}
|
||
|
||
// 使用新的统一API获取智能主机信息
|
||
getHostDeviceInfo();
|
||
} else {
|
||
ElMessage.error(res.msg);
|
||
}
|
||
})
|
||
.catch(() => {});
|
||
};
|
||
|
||
// 查询车辆照片(从司机信息获取)
|
||
const loadVehiclePhotos = () => {
|
||
// 后端已经从delivery/driver信息中获取了车身照片,无需额外前端查询
|
||
// carFrontPhoto和carBehindPhoto应该已经由后端的DeliveryServiceImpl填充
|
||
|
||
};
|
||
|
||
// 智能主机列表查询
|
||
const getHostList = () => {
|
||
if (!route.query.id) {
|
||
console.warn('=== 警告:deliveryId为空,跳过主机列表查询');
|
||
data.hostDataListLoading = false;
|
||
return;
|
||
}
|
||
|
||
data.hostDataListLoading = true;
|
||
pageDeviceList({
|
||
pageNum: 1,
|
||
pageSize: 100, // 获取所有主机设备
|
||
deliveryId: parseInt(route.query.id),
|
||
deviceType: 1, // 智能主机设备类型
|
||
})
|
||
.then((res) => {
|
||
|
||
data.hostDataListLoading = false;
|
||
if (res.code === 200) {
|
||
|
||
// 新API返回的是数组格式,过滤出智能主机设备
|
||
const hostDevices = res.data.filter(device => device.deviceType === 1 || device.deviceType === '1');
|
||
data.hostRows = hostDevices || [];
|
||
data.hostTotal = hostDevices.length || 0;
|
||
|
||
if (hostDevices.length > 0) {
|
||
// 如果有主机设备,取第一个作为主要主机
|
||
data.serverIds = hostDevices[0].deviceId || hostDevices[0].sn || '';
|
||
|
||
} else {
|
||
data.serverIds = '';
|
||
}
|
||
|
||
|
||
} else {
|
||
console.warn('获取主机设备信息失败:', res.msg);
|
||
data.hostRows = [];
|
||
data.hostTotal = 0;
|
||
data.serverIds = '';
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
console.error('获取主机设备信息异常:', err);
|
||
data.hostDataListLoading = false;
|
||
data.hostRows = [];
|
||
data.hostTotal = 0;
|
||
data.serverIds = '';
|
||
});
|
||
};
|
||
|
||
// 获取智能主机信息(保留原有功能)
|
||
const getHostDeviceInfo = () => {
|
||
// 现在直接调用getHostList来获取主机信息
|
||
getHostList();
|
||
};
|
||
|
||
// 查看主机定位
|
||
const locationClick = (item) => {
|
||
getHostLocation(item);
|
||
};
|
||
//
|
||
// 查询主机经纬度
|
||
const getHostLocation = (item) => {
|
||
hostLocation({
|
||
serverDeviceId: data.serverIds,
|
||
})
|
||
.then((res) => {
|
||
if (res.code === 200) {
|
||
data.center.lng = res.data.longitude;
|
||
data.center.lat = res.data.latitude;
|
||
data.updateTime = res.data.updateTime;
|
||
data.dialogVisible = true;
|
||
} else {
|
||
ElMessage.error(res.msg);
|
||
}
|
||
})
|
||
.catch(() => {});
|
||
};
|
||
// 查看运动轨迹
|
||
const trackClick = (item) => {
|
||
data.trackDialogVisible = true;
|
||
getHostTrack(item);
|
||
};
|
||
// 查询主机运动轨迹
|
||
const getHostTrack = (item) => {
|
||
data.trackLoading = true;
|
||
hostTrack({
|
||
deliveryId: data.baseInfo.id,
|
||
serverDeviceId: Number(item),
|
||
})
|
||
.then((res) => {
|
||
data.trackLoading = false;
|
||
if (res.code === 200) {
|
||
if (res.data.length > 0) {
|
||
data.trajectoryStatus = false;
|
||
data.path = [];
|
||
res.data.forEach((item, index) => {
|
||
data.path.unshift({
|
||
lng: item.longitude,
|
||
lat: item.latitude,
|
||
});
|
||
});
|
||
data.startMark = data.path[0]; // 起点
|
||
data.endMark = data.path[data.path.length - 1]; // 终点
|
||
} else {
|
||
data.trajectoryStatus = true;
|
||
data.path = [];
|
||
}
|
||
} else {
|
||
ElMessage.error(res.msg);
|
||
}
|
||
})
|
||
.catch(() => {
|
||
data.trackLoading = false;
|
||
data.trajectoryStatus = true;
|
||
});
|
||
};
|
||
// --------- 智能耳标 -----------
|
||
// 耳标列表查询
|
||
const getEarList = () => {
|
||
if (!route.query.id) {
|
||
console.warn('=== 警告:deliveryId为空,跳过耳标列表查询');
|
||
data.dataListLoading = false;
|
||
return;
|
||
}
|
||
|
||
data.dataListLoading = true;
|
||
pageDeviceList({
|
||
pageNum: form.pageNum,
|
||
pageSize: form.pageSize,
|
||
deliveryId: parseInt(route.query.id),
|
||
deviceType: 2, // 智能耳标设备类型
|
||
})
|
||
.then((res) => {
|
||
|
||
data.dataListLoading = false;
|
||
if (res.code === 200) {
|
||
|
||
// 新API返回的是数组格式,需要过滤出智能耳标设备
|
||
const earDevices = res.data.filter(device => device.deviceType === 2 || device.deviceType === '2');
|
||
data.rows = earDevices || [];
|
||
data.total = earDevices.length || 0;
|
||
|
||
} else {
|
||
ElMessage.error(res.msg);
|
||
data.total = 0;
|
||
}
|
||
})
|
||
.catch(() => {
|
||
data.dataListLoading = false;
|
||
});
|
||
};
|
||
const earLogClick = (row) => {
|
||
|
||
data.deviceId = row.deviceId || row.sn || '';
|
||
data.earLogDialogVisible = true;
|
||
|
||
// 调用新的API获取60分钟间隔的日志数据
|
||
getEarTagLogs({
|
||
deviceId: data.deviceId,
|
||
deliveryId: parseInt(route.query.id)
|
||
}).then((res) => {
|
||
|
||
if (res.code === 200) {
|
||
// 新API返回的是按60分钟分组的日志数据
|
||
data.earLogRows = res.data || [];
|
||
data.earLogTotal = res.data.length || 0;
|
||
|
||
} else {
|
||
ElMessage.error(res.msg || '获取智能耳标日志失败');
|
||
data.earLogRows = [];
|
||
data.earLogTotal = 0;
|
||
}
|
||
}).catch((error) => {
|
||
console.error('获取智能耳标日志异常:', error);
|
||
ElMessage.error('获取智能耳标日志失败');
|
||
data.earLogRows = [];
|
||
data.earLogTotal = 0;
|
||
});
|
||
};
|
||
|
||
// 智能耳标运动轨迹
|
||
const earTrackClick = (row) => {
|
||
|
||
// 调用新的API获取60分钟间隔的轨迹数据
|
||
getEarTagTrajectory({
|
||
deviceId: row.deviceId || row.sn || '',
|
||
deliveryId: parseInt(route.query.id)
|
||
}).then((res) => {
|
||
|
||
if (res.code === 200 && res.data && res.data.length > 0) {
|
||
// 新API返回的是按60分钟分组的轨迹点数据
|
||
const trajectoryPoints = res.data;
|
||
|
||
// 使用TrackDialog显示轨迹
|
||
if (TrackDialogRef.value) {
|
||
const info = {
|
||
deliveryId: route.query.id,
|
||
deviceId: row.deviceId || row.sn || '',
|
||
type: 'order',
|
||
trajectoryPoints: trajectoryPoints // 传递轨迹点数据
|
||
};
|
||
TrackDialogRef.value.onShowTrackDialog(info);
|
||
}
|
||
} else {
|
||
ElMessage.warning('该设备暂无运动轨迹数据');
|
||
}
|
||
}).catch((error) => {
|
||
console.error('获取智能耳标轨迹异常:', error);
|
||
ElMessage.error('获取智能耳标运动轨迹失败');
|
||
});
|
||
};
|
||
|
||
// 智能项圈列表查询
|
||
const getCollarList = () => {
|
||
if (!route.query.id) {
|
||
console.warn('=== 警告:deliveryId为空,跳过项圈列表查询');
|
||
data.collarDataListLoading = false;
|
||
return;
|
||
}
|
||
|
||
data.collarDataListLoading = true;
|
||
pageDeviceList({
|
||
pageNum: collarForm.pageNum,
|
||
pageSize: collarForm.pageSize,
|
||
deliveryId: parseInt(route.query.id),
|
||
deviceType: 4, // 智能项圈设备类型
|
||
})
|
||
.then((res) => {
|
||
|
||
data.collarDataListLoading = false;
|
||
if (res.code === 200) {
|
||
|
||
// 新API返回的是数组格式,需要过滤出智能项圈设备
|
||
const collarDevices = res.data.filter(device => device.deviceType === 4 || device.deviceType === '4');
|
||
data.collarRows = collarDevices || [];
|
||
data.collarTotal = collarDevices.length || 0;
|
||
|
||
} else {
|
||
ElMessage.error(res.msg);
|
||
data.collarTotal = 0;
|
||
}
|
||
})
|
||
.catch(() => {
|
||
data.collarDataListLoading = false;
|
||
data.collarTotal = 0; // 确保total有默认值
|
||
});
|
||
};
|
||
const collarLogClick = (row) => {
|
||
|
||
data.sn = row.sn || row.deviceId || '';
|
||
data.collarDialogVisible = true;
|
||
|
||
// 调用新的API获取60分钟间隔的日志数据
|
||
getCollarLogs({
|
||
deviceId: data.sn,
|
||
deliveryId: parseInt(route.query.id)
|
||
}).then((res) => {
|
||
|
||
if (res.code === 200) {
|
||
// 新API返回的是按60分钟分组的日志数据
|
||
data.collarLogRows = res.data || [];
|
||
data.collarLogTotal = res.data.length || 0;
|
||
|
||
} else {
|
||
ElMessage.error(res.msg || '获取智能项圈日志失败');
|
||
data.collarLogRows = [];
|
||
data.collarLogTotal = 0;
|
||
}
|
||
}).catch((error) => {
|
||
console.error('获取智能项圈日志异常:', error);
|
||
ElMessage.error('获取智能项圈日志失败');
|
||
data.collarLogRows = [];
|
||
data.collarLogTotal = 0;
|
||
});
|
||
};
|
||
const handler = ({ BMap, map }) => {
|
||
// 自动获取展示的比例
|
||
const view = map.getViewport(eval(data.path));
|
||
data.trackZoom = view.zoom;
|
||
data.centerPoint = view.center;
|
||
};
|
||
// 取消按钮
|
||
const cancelClick = () => {
|
||
window.history.go(-1);
|
||
};
|
||
|
||
// 返回上一页
|
||
const goBack = () => {
|
||
window.history.go(-1);
|
||
};
|
||
|
||
// 前往列表页面
|
||
const goToList = () => {
|
||
router.push('/entry/attestation');
|
||
};
|
||
// 弹层
|
||
const locateClick = (row) => {
|
||
data.center.lng = row.longitude;
|
||
data.center.lat = row.latitude;
|
||
data.updateTime = row.updateTime || row.createTime || '';
|
||
data.dialogVisible = true;
|
||
};
|
||
//
|
||
const collarlocateClick = (row) => {
|
||
data.center.lng = row.longitude;
|
||
data.center.lat = row.latitude;
|
||
data.updateTime = row.time || row.updateTime || row.createTime || '';
|
||
data.dialogVisible = true;
|
||
};
|
||
// 耳标日志列表
|
||
const getEarLogList = () => {
|
||
data.logListLoading = true;
|
||
earLogList({
|
||
pageNum: earLogForm.pageNum,
|
||
pageSize: earLogForm.pageSize,
|
||
deliveryId: route.query.id,
|
||
deviceId: data.deviceId,
|
||
})
|
||
.then((res) => {
|
||
data.logListLoading = false;
|
||
if (res.code === 200) {
|
||
data.earLogRows = res.data.rows;
|
||
data.earLogTotal = res.data.total;
|
||
} else {
|
||
ElMessage.error(res.msg);
|
||
}
|
||
})
|
||
.catch(() => {
|
||
data.logListLoading = false;
|
||
data.earLogTotal = 0; // 确保total有默认值
|
||
});
|
||
};
|
||
// 项圈日志列表
|
||
const getCollarLogList = () => {
|
||
data.logListLoading = true;
|
||
collarLogList({
|
||
pageNum: collarLogForm.pageNum,
|
||
pageSize: collarLogForm.pageSize,
|
||
deliveryId: route.query.id,
|
||
deviceId: data.sn,
|
||
})
|
||
.then((res) => {
|
||
data.logListLoading = false;
|
||
if (res.code === 200) {
|
||
data.collarLogRows = res.data.rows;
|
||
data.collarLogTotal = res.data.total;
|
||
} else {
|
||
ElMessage.error(res.msg);
|
||
}
|
||
})
|
||
.catch(() => {
|
||
data.logListLoading = false;
|
||
data.collarLogTotal = 0; // 确保total有默认值
|
||
});
|
||
};
|
||
// 查看运动轨迹
|
||
const collarTrackClick = (row) => {
|
||
|
||
// 调用新的API获取60分钟间隔的轨迹数据
|
||
getCollarTrajectory({
|
||
deviceId: row.sn || row.deviceId || '',
|
||
deliveryId: parseInt(route.query.id)
|
||
}).then((res) => {
|
||
|
||
if (res.code === 200 && res.data && res.data.length > 0) {
|
||
// 新API返回的是按60分钟分组的轨迹点数据
|
||
const trajectoryPoints = res.data;
|
||
|
||
// 使用TrackDialog显示轨迹
|
||
if (TrackDialogRef.value) {
|
||
const info = {
|
||
deliveryId: route.query.id,
|
||
deviceId: row.sn || row.deviceId || '',
|
||
type: 'order',
|
||
trajectoryPoints: trajectoryPoints // 传递轨迹点数据
|
||
};
|
||
TrackDialogRef.value.onShowTrackDialog(info);
|
||
}
|
||
} else {
|
||
ElMessage.warning('该设备暂无运动轨迹数据');
|
||
}
|
||
}).catch((error) => {
|
||
console.error('获取智能项圈轨迹异常:', error);
|
||
ElMessage.error('获取智能项圈运动轨迹失败');
|
||
});
|
||
};
|
||
|
||
// 智能主机操作函数
|
||
const hostLogClick = (row) => {
|
||
|
||
data.deviceId = row.deviceId || row.sn || '';
|
||
data.hostLogDialogVisible = true;
|
||
|
||
// 调用新的API获取60分钟间隔的日志数据
|
||
getHostLogs({
|
||
deviceId: data.deviceId,
|
||
deliveryId: parseInt(route.query.id)
|
||
}).then((res) => {
|
||
|
||
if (res.code === 200) {
|
||
// 新API返回的是按60分钟分组的日志数据
|
||
data.hostLogRows = res.data || [];
|
||
data.hostLogTotal = res.data.length || 0;
|
||
|
||
} else {
|
||
ElMessage.error(res.msg || '获取智能主机日志失败');
|
||
data.hostLogRows = [];
|
||
data.hostLogTotal = 0;
|
||
}
|
||
}).catch((error) => {
|
||
console.error('获取智能主机日志异常:', error);
|
||
ElMessage.error('获取智能主机日志失败');
|
||
data.hostLogRows = [];
|
||
data.hostLogTotal = 0;
|
||
});
|
||
};
|
||
|
||
const hostTrackClick = (row) => {
|
||
|
||
// 调用新的API获取60分钟间隔的轨迹数据
|
||
getHostTrajectory({
|
||
deviceId: row.deviceId || row.sn || '',
|
||
deliveryId: parseInt(route.query.id)
|
||
}).then((res) => {
|
||
|
||
if (res.code === 200 && res.data && res.data.length > 0) {
|
||
// 新API返回的是按60分钟分组的轨迹点数据
|
||
const trajectoryPoints = res.data;
|
||
|
||
// 使用TrackDialog显示轨迹
|
||
if (TrackDialogRef.value) {
|
||
const info = {
|
||
deliveryId: route.query.id,
|
||
deviceId: row.deviceId || row.sn || '',
|
||
type: 'order',
|
||
trajectoryPoints: trajectoryPoints // 传递轨迹点数据
|
||
};
|
||
TrackDialogRef.value.onShowTrackDialog(info);
|
||
}
|
||
} else {
|
||
ElMessage.warning('该设备暂无运动轨迹数据');
|
||
}
|
||
}).catch((error) => {
|
||
console.error('获取智能主机轨迹异常:', error);
|
||
ElMessage.error('获取智能主机运动轨迹失败');
|
||
});
|
||
};
|
||
|
||
const hostLocationClick = (row) => {
|
||
data.center.lng = row.longitude;
|
||
data.center.lat = row.latitude;
|
||
data.updateTime = row.updateTime || row.createTime || '';
|
||
data.dialogVisible = true;
|
||
};
|
||
// 状态文本转换
|
||
// 计算所有绑定设备的总数
|
||
const totalRegisteredDevices = computed(() => {
|
||
const hostCount = data.hostTotal || 0;
|
||
const earCount = data.total || 0;
|
||
const collarCount = data.collarTotal || 0;
|
||
const total = hostCount + earCount + collarCount;
|
||
|
||
return total;
|
||
});
|
||
|
||
const getStatusText = (status) => {
|
||
const statusMap = {
|
||
1: '准备中',
|
||
2: '运输中',
|
||
3: '已结束'
|
||
};
|
||
return statusMap[status] || '未知状态';
|
||
};
|
||
|
||
// 根据状态返回标签类型(颜色)
|
||
const getStatusType = (status) => {
|
||
const typeMap = {
|
||
1: 'info', // 准备中 - 灰色
|
||
2: 'warning', // 运输中 - 橙色
|
||
3: 'success' // 已结束 - 绿色
|
||
};
|
||
return typeMap[status] || 'info';
|
||
};
|
||
|
||
// 判断字段是否有有效值(用于隐藏空值字段)
|
||
// 计算发车牛只重量(装车过磅重量 - 空车过磅重量)
|
||
const departureCattleWeight = computed(() => {
|
||
const emptyWeight = parseFloat(data.baseInfo.emptyWeight);
|
||
const entruckWeight = parseFloat(data.baseInfo.entruckWeight);
|
||
if (!isNaN(emptyWeight) && !isNaN(entruckWeight) && emptyWeight > 0 && entruckWeight > 0) {
|
||
const result = entruckWeight - emptyWeight;
|
||
return result > 0 ? result.toFixed(2) : null;
|
||
}
|
||
return null;
|
||
});
|
||
|
||
// 计算落地牛只重量(落地过磅重量 - 空车过磅重量)
|
||
const landingCattleWeight = computed(() => {
|
||
const emptyWeight = parseFloat(data.baseInfo.emptyWeight);
|
||
const landingWeight = parseFloat(data.baseInfo.landingEntruckWeight);
|
||
if (!isNaN(emptyWeight) && !isNaN(landingWeight) && emptyWeight > 0 && landingWeight > 0) {
|
||
const result = landingWeight - emptyWeight;
|
||
return result > 0 ? result.toFixed(2) : null;
|
||
}
|
||
return null;
|
||
});
|
||
|
||
// 计算损耗(发车牛只重量 - 落地牛只重量)
|
||
// 正数表示损耗(减少),负数表示增重(增加)
|
||
const weightLoss = computed(() => {
|
||
const departure = departureCattleWeight.value;
|
||
const landing = landingCattleWeight.value;
|
||
if (departure !== null && landing !== null) {
|
||
const result = parseFloat(departure) - parseFloat(landing);
|
||
return result.toFixed(2);
|
||
}
|
||
return null;
|
||
});
|
||
|
||
const hasValue = (value) => {
|
||
if (value === null || value === undefined) {
|
||
return false;
|
||
}
|
||
if (typeof value === 'string') {
|
||
return value.trim() !== '';
|
||
}
|
||
return true;
|
||
};
|
||
|
||
// 获取卖方名称:如果是从订单页面进入的,使用订单的卖方;否则显示 '-'
|
||
const getSupplierName = () => {
|
||
// 检查是否从订单页面进入(通过路由参数 fromOrder 判断)
|
||
if (route.query.fromOrder === 'true' && route.query.sellerName) {
|
||
return route.query.sellerName;
|
||
}
|
||
// 不是从订单页面进入的,直接显示 '-'
|
||
return '-';
|
||
};
|
||
|
||
// 获取买方名称:如果是从订单页面进入的,使用订单的买方;否则显示 '-'
|
||
const getBuyerName = () => {
|
||
// 检查是否从订单页面进入(通过路由参数 fromOrder 判断)
|
||
if (route.query.fromOrder === 'true' && route.query.buyerName) {
|
||
return route.query.buyerName;
|
||
}
|
||
// 不是从订单页面进入的,直接显示 '-'
|
||
return '-';
|
||
};
|
||
|
||
onMounted(() => {
|
||
data.id = route.query.id;
|
||
data.status = route.query.status;
|
||
data.length = route.query.length;
|
||
|
||
|
||
// 检查deliveryId是否存在
|
||
if (!route.query.id) {
|
||
console.warn('=== 警告:deliveryId为空,无法加载详情页面');
|
||
ElMessage.error('缺少必要的参数,请从列表页面点击详情按钮进入');
|
||
return;
|
||
}
|
||
|
||
// 检查deliveryId是否存在,存在时才测试设备关联情况
|
||
testDeliveryDevices({ deliveryId: route.query.id })
|
||
.then(res => {
|
||
|
||
})
|
||
.catch(err => {
|
||
console.error('=== 测试设备关联失败:', err);
|
||
});
|
||
|
||
getDetail(); // 查详情
|
||
getHostList(); // 主机列表查询
|
||
getEarList(); // 耳标列表查询
|
||
getCollarList(); // 项圈类别查询
|
||
});
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.error-container {
|
||
padding: 50px;
|
||
text-align: center;
|
||
}
|
||
|
||
.details-container {
|
||
padding: 20px;
|
||
background-color: #f5f7fa;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.page-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
background-color: #fff;
|
||
padding: 16px 24px;
|
||
border-radius: 4px;
|
||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||
|
||
.header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.page-title {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
margin-right: 16px;
|
||
}
|
||
|
||
.status-tag {
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
}
|
||
|
||
.section-card {
|
||
margin-bottom: 20px;
|
||
|
||
:deep(.el-card__header) {
|
||
padding: 15px 20px;
|
||
border-bottom: 1px solid #ebeef5;
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.header-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
position: relative;
|
||
padding-left: 12px;
|
||
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 4px;
|
||
height: 16px;
|
||
background-color: #409eff;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
&.warning-text::before {
|
||
background-color: #f56c6c;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.warning-card {
|
||
border-color: #fde2e2;
|
||
|
||
:deep(.el-card__header) {
|
||
background-color: #fef0f0;
|
||
}
|
||
}
|
||
|
||
.info-group {
|
||
margin-bottom: 30px;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.sub-title {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: #606266;
|
||
margin-bottom: 15px;
|
||
padding-left: 8px;
|
||
border-left: 3px solid #909399;
|
||
}
|
||
}
|
||
|
||
.weight-info-container {
|
||
display: flex;
|
||
flex-direction: row;
|
||
gap: 20px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.weight-basic-info,
|
||
.weight-calculated-info {
|
||
flex: 1;
|
||
min-width: 400px;
|
||
}
|
||
|
||
.weight-calculated-info {
|
||
.calculated-value {
|
||
font-weight: 600;
|
||
font-size: 16px;
|
||
color: #303133;
|
||
}
|
||
|
||
.loss-value {
|
||
&.positive-loss {
|
||
color: #67c23a; // 绿色表示增重
|
||
}
|
||
|
||
&.negative-loss {
|
||
color: #f56c6c; // 红色表示损耗
|
||
}
|
||
}
|
||
}
|
||
|
||
@media (max-width: 1200px) {
|
||
.weight-basic-info,
|
||
.weight-calculated-info {
|
||
min-width: 100%;
|
||
}
|
||
}
|
||
|
||
.media-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||
gap: 20px;
|
||
|
||
.media-item {
|
||
background-color: #f5f7fa;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
border: 1px solid #ebeef5;
|
||
transition: all 0.3s;
|
||
|
||
&:hover {
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.media-label {
|
||
padding: 8px 12px;
|
||
font-size: 13px;
|
||
color: #606266;
|
||
background-color: #fafafa;
|
||
border-bottom: 1px solid #ebeef5;
|
||
text-align: center;
|
||
font-weight: 500;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.photo-img {
|
||
width: 100%;
|
||
height: 160px;
|
||
display: block;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.media-content {
|
||
display: flex;
|
||
|
||
.photo-img {
|
||
width: 50%;
|
||
}
|
||
}
|
||
|
||
video {
|
||
width: 100%;
|
||
height: 160px;
|
||
object-fit: contain;
|
||
background-color: #000;
|
||
}
|
||
}
|
||
}
|
||
|
||
.pagination-container {
|
||
margin-top: 15px;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.red {
|
||
color: #f56c6c;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.empty-box {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 100%;
|
||
}
|
||
</style>
|