<template>
|
<!-- 全屏进度条容器 -->
|
<div class="progress-container" v-if="progressVisible">
|
<div class="progress-wrapper">
|
<el-progress :percentage="progressPercent" :status="progressStatus" :stroke-width="20" :text-inside="true" />
|
<div v-if="progressPercent < 100" class="progress-text">{{ $t("query.loadingTrackData") }}</div>
|
</div>
|
</div>
|
<div class="table-box" style="background-color: rgb(255 255 255 / 100%)">
|
<div id="mars3dContainer" class="mars3d-container">
|
<Mars3DMap
|
ref="marsMap"
|
v-if="configLoaded"
|
:mapconfig="mapConfigs"
|
@map-loaded="onMapLoaded"
|
:showfence="marsHome.showfence"
|
:showanchor="marsHome.showanchor"
|
:showlayer="marsHome.layer"
|
:mapcolor="marsHome.mapcolor"
|
style="border: none"
|
/>
|
</div>
|
<!-- 侧边栏折叠按钮 -->
|
<div class="sidebar-toggle" @click="toggleSidebar">
|
<el-icon :class="{ 'rotate-icon': isCollapsed }">
|
<ArrowRightBold v-if="isCollapsed" />
|
<ArrowLeftBold v-else />
|
</el-icon>
|
</div>
|
<el-card
|
class="right-card"
|
:style="{
|
'--card-width': isCollapsed ? '0px' : '420px',
|
opacity: isCollapsed ? 0 : 1,
|
pointerEvents: isCollapsed ? 'none' : 'auto'
|
}"
|
>
|
<template v-if="!isCollapsed" #header>
|
<div class="card-header">
|
<b>{{ $t("query.trackQuery") }}</b>
|
<el-tooltip style="cursor: pointer" class="box-item" :content="$t('query.helpTooltip')" placement="top-start">
|
<el-icon style="float: right; cursor: pointer" @click="open = true"><QuestionFilled /></el-icon>
|
</el-tooltip>
|
</div>
|
</template>
|
<el-form
|
ref="ruleFormRef"
|
label-width="50px"
|
label-suffix=" :"
|
:rules="rules"
|
:model="tracks"
|
:hide-required-asterisk="true"
|
>
|
<el-form-item :label="$t('query.map')" label-width="60" prop="layer" ref="ref1">
|
<el-select v-model="tracks.layer" :placeholder="$t('query.selectMapLayer')" @change="handleSelectMap">
|
<el-option :label="$t('Fence.basemap.baiduImage')" value="百度地图" />
|
<el-option v-for="item in mapList" :key="item.value" :label="item.label" :value="item.value" />
|
</el-select>
|
</el-form-item>
|
<el-form-item :label="$t('query.name')" label-width="60" prop="tagid" ref="ref2">
|
<el-input v-model="tracks.tagid" :placeholder="$t('query.inputNameOrId')" clearable></el-input>
|
</el-form-item>
|
<el-form-item :label="$t('query.quickSelect')" label-width="60" prop="kstime" ref="ref3">
|
<el-select :placeholder="$t('query.quickSelect')" v-model="tracks.kstime" @change="HandleTime">
|
<el-option :label="$t('query.last1Hour')" value="近1小时" />
|
<el-option :label="$t('query.last2Hours')" value="近2小时" />
|
<el-option :label="$t('query.last3Hours')" value="近3小时" />
|
<el-option :label="$t('query.last4Hours')" value="近4小时" />
|
<el-option :label="$t('query.last5Hours')" value="近5小时" />
|
</el-select>
|
</el-form-item>
|
<el-form-item :label="$t('query.range')" label-width="60" prop="time" ref="ref4">
|
<el-date-picker
|
v-model="tracks.time"
|
type="datetimerange"
|
:range-separator="$t('SearchForm.rangeSeparator')"
|
:start-placeholder="$t('SearchForm.startTime')"
|
:end-placeholder="$t('SearchForm.endTime')"
|
/>
|
</el-form-item>
|
<el-form-item :label="$t('query.speed')" label-width="61" prop="sleep" ref="ref5">
|
<el-select :placeholder="$t('query.playbackSpeed')" v-model="tracks.sleep" @change="syncSpeedFromSelect">
|
<el-option label="X1" value="1000" />
|
<el-option label="X5" value="500" />
|
<el-option label="X10" value="100" />
|
<el-option label="X30" value="30" />
|
<el-option label="X50" value="50" />
|
<el-option label="X100" value="10" />
|
<el-option label="X200" value="5" />
|
</el-select>
|
</el-form-item>
|
</el-form>
|
|
<el-button @click="ResetFrom" class="custom-active"> {{ $t("Config.reset") }} </el-button>
|
|
<el-button v-show="true" type="danger" ref="ref6" @click="Stop" :disabled="!isPlaybackStarted" class="custom-active">
|
{{ StopText }}
|
</el-button>
|
|
<el-button v-show="true" type="primary" ref="ref6" @click="handleSubmit" class="custom-active">
|
{{ $t("query.playback") }}
|
</el-button>
|
|
<template #footer>
|
<el-descriptions :title="$t('query.playbackInfo')" :column="1" border>
|
<el-descriptions-item :label="$t('query.name')" label-align="right" align="left">{{
|
trackView.baoliu8
|
}}</el-descriptions-item>
|
<el-descriptions-item
|
:label="$t('PersonInfoCard.longitude')"
|
label-align="right"
|
align="left"
|
label-class-name="my-label"
|
class-name="my-content"
|
width="150px"
|
>{{ trackView.baoliu2 }}
|
</el-descriptions-item>
|
<el-descriptions-item :label="$t('PersonInfoCard.latitude')" label-align="right" align="left">{{
|
trackView.baoliu3
|
}}</el-descriptions-item>
|
<el-descriptions-item :label="$t('query.elevationCm')" label-align="right" align="left">
|
{{ trackView.baoliu4 }}
|
</el-descriptions-item>
|
<el-descriptions-item :label="$t('query.status')" label-align="right" align="left">
|
{{ trackView.baoliu9 }}
|
</el-descriptions-item>
|
<el-descriptions-item :label="$t('Config.time')" label-align="right" align="left">
|
{{ trackView.time }}
|
</el-descriptions-item>
|
<el-descriptions-item :label="$t('query.count')" label-align="right" align="left">
|
{{ trackView.count }}/{{ trackNum }}
|
</el-descriptions-item>
|
</el-descriptions>
|
<!-- 添加返回按钮 -->
|
<div style="margin-top: 20px; text-align: left">
|
<el-button @click="goBack">
|
<el-icon><ArrowLeft /></el-icon>
|
<span>{{ $t("query.back") }}</span>
|
</el-button>
|
|
<el-tooltip class="box-item" effect="dark" :content="$t('query.moreSettings')" placement="top-start">
|
<img :src="setting" width="35" @click="openDialog" class="custom-active1" />
|
</el-tooltip>
|
</div>
|
</template>
|
</el-card>
|
|
<!-- 播放速度控制悬浮窗 -->
|
<div v-if="showSpeedControl" class="speed-control-panel">
|
<div class="speed-control-content">
|
<div class="speed-display">
|
<span class="speed-label">{{ $t("query.currentSpeed") }}</span>
|
<span class="speed-value">{{ speedDisplayText }}</span>
|
</div>
|
<el-slider
|
v-model="speedValue"
|
:min="0"
|
:max="6"
|
:step="1"
|
:show-tooltip="false"
|
:marks="speedMarks"
|
@change="handleSpeedChange"
|
/>
|
<div class="control-buttons">
|
<el-button type="warning" size="small" @click="handlePause" class="control-btn" :disabled="!isPlaybackStarted">
|
<el-icon><VideoPause /></el-icon>
|
{{ $t("query.pause") }}
|
</el-button>
|
<el-button type="success" size="small" @click="handleContinue" class="control-btn" :disabled="!isPlaybackStarted">
|
<el-icon><VideoPlay /></el-icon>
|
{{ $t("query.continue") }}
|
</el-button>
|
</div>
|
</div>
|
</div>
|
|
<!-- 显示速度控制按钮 -->
|
<el-button
|
v-if="!showSpeedControl && trackNum"
|
class="show-speed-control-btn"
|
type="primary"
|
circle
|
@click="showSpeedControl = true"
|
>
|
<el-icon><Odometer /></el-icon>
|
</el-button>
|
|
<el-tour v-model="open">
|
<el-tour-step
|
:target="ref1?.$el"
|
:title="$t('query.tour.selectMapTitle')"
|
:description="$t('query.tour.selectMapDesc')"
|
></el-tour-step>
|
<el-tour-step
|
:target="ref2?.$el"
|
:title="$t('query.tour.selectDeviceTitle')"
|
:description="$t('query.tour.selectDeviceDesc')"
|
/>
|
<el-tour-step
|
:target="ref3?.$el"
|
:title="$t('query.tour.quickSelectTitle')"
|
:description="$t('query.tour.quickSelectDesc')"
|
/>
|
<el-tour-step
|
:target="ref4?.$el"
|
:title="$t('query.tour.selectRangeTitle')"
|
:description="$t('query.tour.selectRangeDesc')"
|
/>
|
<el-tour-step
|
:target="ref5?.$el"
|
:title="$t('query.tour.playbackSpeedTitle')"
|
:description="$t('query.tour.playbackSpeedDesc')"
|
/>
|
<el-tour-step
|
:target="ref6?.$el"
|
:title="$t('query.tour.playbackShowTitle')"
|
:description="$t('query.tour.playbackShowDesc')"
|
/>
|
</el-tour>
|
</div>
|
<el-dialog v-model="dialogVisible" :title="$t('query.trackSettings')" width="500px" :close-on-click-modal="false">
|
<el-form :model="formData" label-width="120px" ref="settingsFormRef">
|
<!-- 距离过滤设置 -->
|
<el-form-item
|
:label="$t('query.distanceFilter')"
|
prop="distanceFilter"
|
:rules="[{ required: true, message: $t('query.validation.distanceRequired') }]"
|
>
|
<el-input-number v-model="formData.distanceFilter" :min="0" :max="10000" :step="100" controls-position="right" />
|
<span class="unit">{{ $t("query.meters") }}</span>
|
</el-form-item>
|
|
<!-- 时间过滤设置 -->
|
<el-form-item
|
:label="$t('query.timeFilter')"
|
prop="timeFilter"
|
:rules="[{ required: true, message: $t('query.validation.timeRequired') }]"
|
>
|
<el-input-number v-model="formData.timeFilter" :min="0" :max="1440" :step="10" controls-position="right" />
|
<span class="unit">{{ $t("query.seconds") }}</span>
|
</el-form-item>
|
|
<!-- 轨迹类型选择 -->
|
<el-form-item :label="$t('query.trajectoryType')" prop="trajectoryType">
|
<el-radio-group v-model="formData.trajectoryType">
|
<el-radio v-for="item in trajectoryOptions" :key="item.value" :label="item.value">
|
{{ item.label }}
|
</el-radio>
|
</el-radio-group>
|
</el-form-item>
|
</el-form>
|
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="dialogVisible = false">{{ $t("Config.cancel") }}</el-button>
|
<el-button type="primary" @click="handleConfirm"> {{ $t("Config.sure") }} </el-button>
|
</span>
|
</template>
|
</el-dialog>
|
</template>
|
|
<script setup lang="ts">
|
import { onMounted, ref, reactive, onUnmounted, computed } from "vue";
|
import * as mars3d from "mars3d";
|
import { ElMessage, FormInstance, ButtonInstance } from "element-plus";
|
import Mars3DMap from "../components/screen/Mars3DMap.vue";
|
import "mars3d-space";
|
import { useRouter, useRoute } from "vue-router";
|
import "mars3d-cesium/Build/Cesium/Widgets/widgets.css";
|
import "mars3d/mars3d.css";
|
import { getThreeMapConfig } from "@/api/modules/hxzk/map/threemap";
|
import { FindUserCompanyMap, FindFloorMap } from "@/api/modules/hxzk/map/twomap";
|
import { DepartmentAutocomplete } from "@/api/modules/hxzk/department/department";
|
import { FindTrackView } from "@/api/modules/hxzk/track/track";
|
import { FindOneDepartMent } from "@/api/modules/hxzk/department/department";
|
import { editThreeMap, getThreeMapList } from "@/api/modules/hxzk/map/threemap";
|
|
import setting from "./img/setting.png";
|
import { useI18n } from "vue-i18n";
|
import { Odometer, VideoPause, VideoPlay } from "@element-plus/icons-vue";
|
|
const { t } = useI18n();
|
|
// 轨迹类型选项 - 使用计算属性实现国际化
|
interface TrajectoryOption {
|
value: string;
|
label: string;
|
}
|
|
const trajectoryOptions = computed<TrajectoryOption[]>(() => [
|
{ value: "卫星", label: t("query.satelliteTrajectory") },
|
{ value: "室内", label: t("query.indoorTrajectory") }
|
]);
|
|
const router = useRouter();
|
const route = useRoute();
|
const goBack = () => {
|
router.go(-1);
|
};
|
|
// 表单数据类型
|
interface SettingsForm {
|
distanceFilter: number;
|
timeFilter: number;
|
trajectoryType: string;
|
}
|
|
const progressVisible = ref(false);
|
const progressPercent = ref(0);
|
const progressStatus = ref("");
|
|
// 对话框显示状态
|
const dialogVisible = ref(false);
|
|
// 表单引用
|
const settingsFormRef = ref<FormInstance>();
|
|
// 表单数据
|
const formData = ref<SettingsForm>({
|
distanceFilter: 1000,
|
timeFilter: 30,
|
trajectoryType: "卫星"
|
});
|
|
// 验证规则
|
const rules = reactive({
|
layer: [{ required: true, message: t("query.validation.mapRequired") }],
|
tagid: [{ required: true, message: t("query.validation.nameRequired") }],
|
time: [{ required: true, message: t("query.validation.timeRangeRequired") }],
|
sleep: [{ required: true, message: t("query.validation.speedRequired") }]
|
});
|
|
// 打开对话框
|
const openDialog = async () => {
|
const params = {
|
departmentId: "",
|
pageNum: 1,
|
pageSize: 10
|
};
|
try {
|
const data = await getThreeMapList(params);
|
formData.value.distanceFilter = data.data.list[0].distance;
|
formData.value.timeFilter = data.data.list[0].time;
|
formData.value.trajectoryType = data.data.list[0].tracktype;
|
dialogVisible.value = true;
|
} catch (error) {
|
console.error(t("query.error.requestFailed"), error);
|
}
|
};
|
|
// 确认设置
|
const handleConfirm = () => {
|
settingsFormRef.value?.validate(valid => {
|
if (valid) {
|
const params = {
|
distance: formData.value.distanceFilter,
|
time: formData.value.timeFilter,
|
tracktype: formData.value.trajectoryType
|
};
|
editThreeMap(params);
|
ElMessage.success(t("query.settingsSaved"));
|
dialogVisible.value = false;
|
}
|
});
|
};
|
|
const isCollapsed = ref(false);
|
const toggleSidebar = () => {
|
isCollapsed.value = !isCollapsed.value;
|
};
|
|
const trackView = ref({
|
baoliu2: t("query.none"),
|
baoliu3: t("query.none"),
|
baoliu4: "0",
|
baoliu8: t("query.none"),
|
baoliu9: t("query.none"),
|
time: t("query.none"),
|
count: 0
|
});
|
|
const stratTime = ref<string>("");
|
const stopTime = ref<string>("");
|
const now = new Date();
|
const trackNum = ref("");
|
|
// 引导
|
const open = ref(false);
|
const ref1 = ref<ButtonInstance>();
|
const ref2 = ref<ButtonInstance>();
|
const ref3 = ref<ButtonInstance>();
|
const ref4 = ref<ButtonInstance>();
|
const ref5 = ref<ButtonInstance>();
|
const ref6 = ref<ButtonInstance>();
|
|
//是否越天
|
const isCrossDay = ref(false);
|
// 图层下拉列表
|
const mapList = ref();
|
// 部门下拉列表
|
const bumenList = ref();
|
|
const tracks = ref<{
|
layer: string;
|
tagid: string;
|
time: [string, string] | [];
|
kstime: string;
|
sleep: string;
|
}>({
|
layer: "百度地图",
|
tagid: "",
|
time: [],
|
kstime: "",
|
sleep: "100"
|
});
|
|
// 快速选择时间处理
|
const HandleTime = value => {
|
const hoursMap = {
|
近1小时: 1,
|
近2小时: 2,
|
近3小时: 3,
|
近4小时: 4,
|
近5小时: 5
|
};
|
|
const hours = hoursMap[value];
|
if (hours) {
|
now.setHours(now.getHours() - hours);
|
stratTime.value = now.toISOString();
|
now.setHours(now.getHours() + hours);
|
stopTime.value = now.toISOString();
|
tracks.value.time = [stratTime.value, stopTime.value];
|
}
|
};
|
|
const StopText = ref(t("query.pause"));
|
const StopState = ref(false);
|
const isPlaybackStarted = ref(false);
|
|
// 速度控制面板
|
const showSpeedControl = ref(true);
|
const speedValue = ref(2); // 默认 X10 速度
|
|
// 速度映射关系
|
const speedMap = {
|
0: { value: "1000", label: "X1" },
|
1: { value: "500", label: "X5" },
|
2: { value: "100", label: "X10" },
|
3: { value: "30", label: "X30" },
|
4: { value: "50", label: "X50" },
|
5: { value: "10", label: "X100" },
|
6: { value: "5", label: "X200" }
|
};
|
|
// 速度标记
|
const speedMarks = computed(() => ({
|
0: "X1",
|
1: "X5",
|
2: "X10",
|
3: "X30",
|
4: "X50",
|
5: "X100",
|
6: "X200"
|
}));
|
|
// 当前速度显示文本
|
const speedDisplayText = computed(() => {
|
return speedMap[speedValue.value]?.label || "X10";
|
});
|
|
// 处理速度变化
|
const handleSpeedChange = (value: number | number[]) => {
|
const actualValue = Array.isArray(value) ? value[0] : value;
|
const speedConfig = speedMap[actualValue];
|
if (speedConfig) {
|
tracks.value.sleep = speedConfig.value;
|
ElMessage.success(t("query.speedChanged") + speedConfig.label);
|
}
|
};
|
|
// 处理暂停
|
const handlePause = () => {
|
if (!isPlaybackStarted.value) {
|
ElMessage.warning(t("query.pleaseStartPlayback"));
|
return;
|
}
|
if (!StopState.value) {
|
Stop();
|
}
|
};
|
|
// 处理继续
|
const handleContinue = () => {
|
if (!isPlaybackStarted.value) {
|
ElMessage.warning(t("query.pleaseStartPlayback"));
|
return;
|
}
|
if (StopState.value) {
|
Stop();
|
}
|
};
|
|
// 监听下拉框速度变化,同步到滑块
|
const syncSpeedFromSelect = () => {
|
const sleepValue = tracks.value.sleep;
|
for (let key in speedMap) {
|
if (speedMap[key].value === sleepValue) {
|
speedValue.value = parseInt(key);
|
break;
|
}
|
}
|
};
|
|
let map;
|
let tileLayermap;
|
let graphicLayer;
|
let timeoutId;
|
let plackControl;
|
let isRunning = true;
|
|
const marsHome = ref({
|
id: 0,
|
offlinehide: false,
|
voice: false,
|
showfence: false,
|
showanchor: false,
|
threemap: false,
|
layer: false,
|
image: false,
|
jiankong: true,
|
weixing: false,
|
mapcolor: "#409EFF",
|
showmap: false
|
});
|
|
// 地图配置
|
const mapConfigs = ref({});
|
const configLoaded = ref(false);
|
|
onMounted(() => {
|
// 初始化地图下拉-部门下拉
|
FindUserCompanyMap().then(mapLists => {
|
mapList.value = mapLists;
|
});
|
|
const param = { companyid: "" };
|
DepartmentAutocomplete(param).then(bumenLists => {
|
bumenList.value = bumenLists;
|
});
|
|
// 地图初始化
|
getThreeMapConfig().then(mapConfig => {
|
mapConfigs.value = mapConfig;
|
marsHome.value.id = mapConfig.id;
|
if (mapConfig.showfence == "1") marsHome.value.showfence = true;
|
if (mapConfig.layer == "1") marsHome.value.layer = true;
|
if (mapConfig.showanchor == "1") marsHome.value.showanchor = true;
|
if (mapConfig.threemap == "1") marsHome.value.threemap = true;
|
if (mapConfig.voice == "1") marsHome.value.voice = true;
|
if (mapConfig.showmap == "1") marsHome.value.showmap = true;
|
marsHome.value.mapcolor = mapConfig.mapcolor;
|
configLoaded.value = true;
|
});
|
|
// 初始化时同步速度
|
syncSpeedFromSelect();
|
|
// 处理路由参数(从卡片信息页面跳转过来的情况)
|
if (route.query.tagid) {
|
tracks.value.tagid = route.query.tagid as string;
|
}
|
|
// 优先使用 startTime 和 endTime,如果没有则尝试解析 time
|
if (route.query.startTime && route.query.endTime) {
|
tracks.value.time = [route.query.startTime as string, route.query.endTime as string] as [string, string];
|
} else if (route.query.time) {
|
// 如果 time 是数组格式(从 queryParams 传递过来的)
|
if (Array.isArray(route.query.time)) {
|
tracks.value.time = route.query.time as [string, string];
|
} else if (typeof route.query.time === "string") {
|
// 如果是字符串,尝试解析(格式可能是 JSON)
|
try {
|
const timeArray = JSON.parse(route.query.time);
|
if (Array.isArray(timeArray) && timeArray.length === 2) {
|
tracks.value.time = timeArray as [string, string];
|
}
|
} catch {
|
// 如果不是 JSON,尝试按逗号分割
|
const timeParts = route.query.time.split(",");
|
if (timeParts.length === 2) {
|
tracks.value.time = [timeParts[0].trim(), timeParts[1].trim()] as [string, string];
|
}
|
}
|
}
|
}
|
});
|
|
// 地图初始化
|
const onMapLoaded = (mapInstance: any) => {
|
map = mapInstance;
|
const baseLayerPicker = new mars3d.control.BaseLayerPicker({});
|
map.addControl(baseLayerPicker);
|
graphicLayer = new mars3d.layer.GraphicLayer();
|
map.addLayer(graphicLayer);
|
if (marsHome.value.showmap) {
|
map.scene.globe.show = true;
|
}
|
mapInstance.basemap = 8890;
|
// map.clock.shouldAnimate = true;
|
};
|
|
const baseUrl = import.meta.env.VITE_IP;
|
|
// 选中二维地图
|
const handleSelectMap = value => {
|
const params = { floor: value };
|
FindFloorMap(params).then(maps => {
|
map.removeLayer(tileLayermap);
|
tileLayermap = new mars3d.layer.ImageLayer({
|
name: maps.mapname,
|
url: `${baseUrl}/uploads/map/` + maps.mapname,
|
rectangle: {
|
xmin: maps.baoliu5.split(",")[0],
|
xmax: maps.baoliu6.split(",")[0],
|
ymin: maps.baoliu8.split(",")[1],
|
ymax: maps.baoliu6.split(",")[1]
|
},
|
zIndex: 20
|
});
|
map.addLayer(tileLayermap);
|
|
map.camera.flyTo({
|
destination: Cesium.Cartesian3.fromDegrees(maps.baoliu5.split(",")[0], maps.baoliu8.split(",")[1], 500),
|
duration: 2
|
});
|
});
|
};
|
|
// 重置
|
const ResetFrom = () => {
|
tracks.value.layer = "";
|
tracks.value.time = "";
|
tracks.value.kstime = "";
|
tracks.value.tagid = "";
|
tracks.value.sleep = "";
|
isPlaybackStarted.value = false; // 重置回放状态
|
StopState.value = false; // 重置暂停状态
|
};
|
|
// 提交数据(回放)
|
const ruleFormRef = ref<FormInstance>();
|
const handleSubmit = () => {
|
ruleFormRef.value!.validate(async valid => {
|
if (!valid) return;
|
try {
|
progressVisible.value = true;
|
progressPercent.value = 0;
|
progressStatus.value = "";
|
|
let requestCompleted = false;
|
let res: any = null;
|
|
let progressInterval: number;
|
const startProgress = () => {
|
progressInterval = window.setInterval(() => {
|
if (progressPercent.value < 90) {
|
const increment = Math.min(Math.floor(1 + Math.random() * 5), 90 - progressPercent.value);
|
progressPercent.value += increment;
|
} else if (!requestCompleted) {
|
const increment = Math.min(Math.floor(Math.random()), 99 - progressPercent.value);
|
progressPercent.value += increment;
|
} else {
|
progressPercent.value = Math.min(progressPercent.value + 1, 100);
|
if (progressPercent.value === 100) {
|
clearInterval(progressInterval);
|
}
|
}
|
}, 200);
|
};
|
|
startProgress();
|
|
checkIfCrossDay(tracks.value.time[0], tracks.value.time[1]);
|
if (isCrossDay.value) {
|
clearInterval(progressInterval);
|
progressPercent.value = 100;
|
progressStatus.value = "exception";
|
ElMessage.error({ message: t("query.crossDayError") });
|
setTimeout(() => {
|
progressVisible.value = false;
|
}, 1000);
|
return;
|
}
|
|
res = await FindTrackView(tracks.value);
|
requestCompleted = true;
|
|
if (progressPercent.value >= 90) {
|
} else {
|
progressPercent.value = 90;
|
}
|
|
await new Promise(resolve => {
|
const checkComplete = setInterval(() => {
|
if (progressPercent.value === 100) {
|
clearInterval(checkComplete);
|
resolve(null);
|
}
|
}, 50);
|
});
|
|
progressStatus.value = "success";
|
trackNum.value = res.data.total;
|
map.clock.currentTime = Cesium.JulianDate.fromDate(new Date(res.data.list[0].time));
|
QueryTrack(res.data.list);
|
isPlaybackStarted.value = true; // 标记回放已开始
|
ElMessage.success({ message: res.msg });
|
} catch (error) {
|
if (progressInterval) clearInterval(progressInterval);
|
progressPercent.value = 100;
|
progressStatus.value = "exception";
|
ElMessage.error({ message: t("query.requestFailed") });
|
} finally {
|
setTimeout(() => {
|
progressVisible.value = false;
|
}, 1000);
|
}
|
});
|
};
|
|
const Stop = () => {
|
if (StopState.value) {
|
plackControl.continue();
|
StopText.value = t("query.pause");
|
StopState.value = false;
|
} else {
|
plackControl.stop();
|
StopText.value = t("query.continue");
|
StopState.value = true;
|
}
|
};
|
|
const Icon = ref({
|
icon: "",
|
scale: 0
|
});
|
|
// 检查是否越天
|
const checkIfCrossDay = (startTime, endTime) => {
|
const startDate = new Date(startTime);
|
const endDate = new Date(endTime);
|
|
isCrossDay.value =
|
startDate.getFullYear() !== endDate.getFullYear() ||
|
startDate.getMonth() !== endDate.getMonth() ||
|
startDate.getDate() !== endDate.getDate();
|
};
|
|
const QueryTrack = LocusData => {
|
trackView.value.count = 0;
|
let viewPoints = [{ lng: LocusData[0][0].baoliu2, lat: LocusData[0][0].baoliu3 }];
|
map.setCameraViewList(viewPoints);
|
|
let DqLocusData = [];
|
const params = { pdepartment: LocusData[0][0].baoliu10 };
|
|
FindOneDepartMent(params).then(Icons => {
|
Icon.value.icon = Icons.baoliu3;
|
for (let key in LocusData) {
|
if (LocusData.hasOwnProperty(key)) {
|
let car = new mars3d.graphic.Route({
|
id: LocusData[key][0].tagid + key,
|
name: LocusData[key][0].baoliu8 + key,
|
maxCacheCount: -1,
|
billboard: {
|
image: Icon.value.icon,
|
width: 40,
|
height: 40,
|
pixelOffsetY: -20
|
},
|
polyline: {
|
color: "red",
|
width: 2,
|
clampToGround: true
|
},
|
style: {
|
label: {
|
text: LocusData[key][0].baoliu8 + " " + LocusData[key][0].tagid,
|
font_size: 14,
|
color: "#ffffff",
|
font_weight: "bold",
|
outlineWidth: 3,
|
outline: true,
|
pixelOffsetY: -50,
|
outlineColor: "#black"
|
}
|
},
|
attr: LocusData[key]
|
});
|
graphicLayer.addGraphic(car);
|
|
for (let i = 0; i < LocusData[key].length; i++) {
|
DqLocusData.push(LocusData[key][i]);
|
}
|
DqLocusData.push(1);
|
}
|
}
|
isRunning = true;
|
plackControl = Plack(DqLocusData);
|
});
|
};
|
|
const Plack = LocusData => {
|
let i = 0;
|
let car = new mars3d.graphic.Route({
|
id: 1,
|
name: LocusData[0].baoliu8,
|
maxCacheCount: -1,
|
billboard: {
|
image: Icon.value.icon,
|
width: 40,
|
height: 40,
|
pixelOffsetY: -20
|
},
|
polyline: {
|
color: "red",
|
width: 2,
|
clampToGround: true
|
},
|
style: {
|
label: {
|
text: LocusData[0].baoliu8 + " " + LocusData[0].tagid,
|
font_size: 14,
|
color: "#ffffff",
|
font_weight: "bold",
|
outlineWidth: 3,
|
outline: true,
|
pixelOffsetY: -50,
|
outlineColor: "#black"
|
}
|
},
|
attr: LocusData[0]
|
});
|
|
graphicLayer.addGraphic(car);
|
|
function iterate() {
|
if (!isRunning) return;
|
if (i < LocusData.length) {
|
if (LocusData[i] == 1) {
|
let style = { label: { text: "" } };
|
let models = { scale: 0 };
|
car.model = models;
|
car.style = style;
|
|
car = new mars3d.graphic.Route({
|
id: LocusData[i + 1].tagid + i,
|
name: LocusData[i + 1].baoliu8 + i,
|
maxCacheCount: -1,
|
billboard: {
|
image: Icon.value.icon,
|
width: 40,
|
height: 40,
|
pixelOffsetY: -20
|
},
|
polyline: {
|
color: "red",
|
width: 2,
|
clampToGround: true
|
},
|
style: {
|
label: {
|
text: LocusData[i + 1].baoliu8 + "" + LocusData[i + 1].tagid,
|
font_size: 14,
|
color: "#ffffff",
|
font_weight: "bold",
|
outlineWidth: 3,
|
outline: true,
|
pixelOffsetY: -50,
|
outlineColor: "#black"
|
}
|
},
|
attr: LocusData[i + 1]
|
});
|
graphicLayer.addGraphic(car);
|
} else {
|
let currentData = LocusData[i];
|
let dateString = currentData.time.replace(/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}):/, "$1.");
|
let date = new Date(dateString);
|
let currentTime = Cesium.JulianDate.fromDate(date);
|
let adjustedTime = Cesium.JulianDate.addSeconds(currentTime, -1, new Cesium.JulianDate());
|
map.clock.currentTime = Cesium.JulianDate.fromDate(new Date(adjustedTime));
|
|
let point = new mars3d.LngLatPoint(currentData.baoliu2, currentData.baoliu3, 0);
|
trackView.value.baoliu2 = currentData.baoliu2;
|
trackView.value.baoliu3 = currentData.baoliu3;
|
trackView.value.baoliu4 = parseFloat(currentData.baoliu4).toFixed(2);
|
trackView.value.baoliu9 = currentData.baoliu9;
|
trackView.value.baoliu8 = currentData.baoliu8;
|
trackView.value.time = currentData.time;
|
trackView.value.count = trackView.value.count + 1;
|
car.addDynamicPosition(point, currentData.time);
|
}
|
i++;
|
timeoutId = setTimeout(iterate, tracks.value.sleep);
|
}
|
}
|
|
iterate();
|
|
return {
|
start: function () {
|
if (!isRunning) {
|
isRunning = true;
|
iterate();
|
}
|
},
|
stop: function () {
|
isRunning = false;
|
},
|
shutDown: function () {
|
clearTimeout(timeoutId);
|
isRunning = false;
|
},
|
continue: function () {
|
if (!isRunning) {
|
isRunning = true;
|
setTimeout(iterate, tracks.value.sleep);
|
}
|
}
|
};
|
};
|
|
onUnmounted(() => {
|
console.log("0");
|
});
|
</script>
|
|
<style scoped>
|
/* 样式保持不变 */
|
.table-box {
|
display: flex;
|
flex-direction: row-reverse;
|
height: 100%;
|
}
|
.mars3dContainer {
|
width: 80%;
|
background-color: #f0f0f0;
|
}
|
.right-card {
|
width: var(--card-width);
|
overflow: hidden;
|
transition:
|
opacity 0.1s linear,
|
width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
will-change: transform, opacity;
|
}
|
.right-card::after {
|
display: block;
|
width: 320px;
|
content: "";
|
}
|
#mars3d-timeline {
|
right: 100px !important;
|
bottom: 20px !important;
|
left: 100px !important;
|
padding: 0 10px 0 100px !important;
|
border-radius: 10px !important;
|
}
|
.time {
|
color: black !important;
|
}
|
.sidebar-toggle {
|
position: relative;
|
top: 400px;
|
left: 0;
|
z-index: 1;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
width: 30px;
|
height: 24px;
|
cursor: pointer;
|
background: #ffffff;
|
border: 1px solid #ebeef5;
|
border-radius: 50%;
|
box-shadow: 0 2px 4px rgb(0 0 0 / 12%);
|
transition: all 0.3s ease;
|
transform: translateY(-50%);
|
}
|
.sidebar-toggle:hover {
|
background: #f5f7fa;
|
box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
|
transform: translateY(-50%) scale(1.1);
|
}
|
.sidebar-toggle .el-icon {
|
font-size: 12px;
|
color: #606266;
|
transition: transform 0.3s ease;
|
}
|
.rotate-icon {
|
transform: rotate(180deg);
|
}
|
.settings-container {
|
display: inline-block;
|
}
|
.unit {
|
margin-left: 8px;
|
color: var(--el-text-color-secondary);
|
}
|
.el-input-number {
|
width: 150px;
|
}
|
.progress-container {
|
position: fixed;
|
top: 0;
|
left: 0;
|
z-index: 9999;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
width: 100%;
|
height: 100%;
|
background-color: rgb(0 0 0 / 50%);
|
}
|
.progress-wrapper {
|
width: 60%;
|
max-width: 500px;
|
padding: 30px;
|
text-align: center;
|
background-color: white;
|
border-radius: 8px;
|
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
|
}
|
.progress-text {
|
margin-top: 15px;
|
font-size: 16px;
|
color: #606266;
|
}
|
:deep(.el-progress-bar) {
|
padding-right: 0;
|
margin-right: 0;
|
}
|
:deep(.el-progress__text) {
|
font-size: 16px !important;
|
color: #606266 !important;
|
}
|
.custom-active1 {
|
float: right;
|
cursor: pointer;
|
}
|
|
/* 增强默认 active 效果 */
|
.custom-active:active {
|
opacity: 0.4; /* 点击时稍微变透明 */
|
transform: scale(0.8); /* 点击时稍微缩小 */
|
}
|
|
/* 不同类型按钮的点击效果 */
|
.el-button--primary:active {
|
background: #40d6ff; /* 更深的蓝色 */
|
}
|
.el-button--danger:active {
|
background: #ff0000; /* 更深的红色 */
|
}
|
.el-button--success:active {
|
background: #5a9503; /* 更深的绿色 */
|
}
|
|
/* 速度控制面板样式 */
|
.speed-control-panel {
|
position: fixed;
|
bottom: 40px;
|
left: 60%;
|
z-index: 2000;
|
width: 600px;
|
padding: 20px;
|
background: rgb(255 255 255 / 85%);
|
backdrop-filter: blur(15px);
|
border: 1px solid rgb(255 255 255 / 30%);
|
border-radius: 12px;
|
box-shadow: 0 8px 32px 0 rgb(0 0 0 / 15%);
|
transform: translateX(-50%);
|
animation: slideUp 0.3s ease-out;
|
}
|
|
@keyframes slideUp {
|
from {
|
bottom: -100px;
|
opacity: 0;
|
}
|
to {
|
bottom: 40px;
|
opacity: 1;
|
}
|
}
|
.speed-control-header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 15px;
|
}
|
.speed-control-title {
|
font-size: 16px;
|
font-weight: bold;
|
color: #333333;
|
}
|
.close-icon {
|
font-size: 18px;
|
color: #666666;
|
cursor: pointer;
|
transition: all 0.3s;
|
}
|
.close-icon:hover {
|
color: #ff6b6b;
|
transform: rotate(90deg);
|
}
|
.speed-control-content {
|
padding: 1px 0;
|
}
|
.speed-display {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 1px;
|
}
|
.control-buttons {
|
display: flex;
|
gap: 10px;
|
justify-content: center;
|
margin-top: 15px;
|
}
|
.control-btn {
|
min-width: 80px;
|
border-radius: 20px;
|
}
|
.speed-label {
|
font-size: 14px;
|
color: #666666;
|
}
|
.speed-value {
|
font-size: 24px;
|
font-weight: bold;
|
color: #409eff;
|
text-shadow: 0 2px 4px rgb(0 0 0 / 10%);
|
}
|
|
/* 自定义滑块样式 */
|
.speed-control-panel :deep(.el-slider__runway) {
|
height: 8px;
|
background-color: #e4e7ed;
|
border-radius: 4px;
|
}
|
.speed-control-panel :deep(.el-slider__bar) {
|
height: 8px;
|
background: linear-gradient(90deg, #409eff 0%, #66b1ff 100%);
|
border-radius: 4px;
|
}
|
.speed-control-panel :deep(.el-slider__button) {
|
width: 20px;
|
height: 20px;
|
background: #ffffff;
|
border: 3px solid #409eff;
|
box-shadow: 0 2px 8px rgb(0 0 0 / 20%);
|
}
|
.speed-control-panel :deep(.el-slider__button:hover) {
|
transform: scale(1.2);
|
}
|
.speed-control-panel :deep(.el-slider__marks-text) {
|
margin-top: 10px;
|
font-size: 12px;
|
color: #606266;
|
}
|
|
/* 显示速度控制按钮 */
|
.show-speed-control-btn {
|
position: fixed;
|
right: 30px;
|
bottom: 30px;
|
z-index: 1999;
|
width: 56px;
|
height: 56px;
|
font-size: 24px;
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
border: none;
|
box-shadow: 0 4px 15px 0 rgb(102 126 234 / 75%);
|
animation: pulse 2s infinite;
|
}
|
|
@keyframes pulse {
|
0% {
|
box-shadow: 0 4px 15px 0 rgb(102 126 234 / 75%);
|
}
|
50% {
|
box-shadow: 0 4px 25px 0 rgb(102 126 234 / 100%);
|
}
|
100% {
|
box-shadow: 0 4px 15px 0 rgb(102 126 234 / 75%);
|
}
|
}
|
.show-speed-control-btn:hover {
|
transform: scale(1.1);
|
}
|
</style>
|