Compare commits
No commits in common. "93295fd4200ff03fed58fef951d3e6d9f31fff1e" and "49696342fa8a5a05ee9ebaab6961bb1807e6b73f" have entirely different histories.
93295fd420
...
49696342fa
Binary file not shown.
|
Before Width: | Height: | Size: 508 B |
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB |
@ -18,10 +18,7 @@
|
||||
</CollapsiblePanel>
|
||||
|
||||
<CollapsiblePanel title="快速响应" subtitle="「力量调度」">
|
||||
<ForceDispatch
|
||||
@start-dispatch="handleStartDispatch"
|
||||
@view-plan="handleViewPlan"
|
||||
/>
|
||||
<ForceDispatch @start-dispatch="handleStartDispatch" />
|
||||
</CollapsiblePanel>
|
||||
</div>
|
||||
|
||||
@ -110,7 +107,7 @@ const handleCloseVideoModal = () => {
|
||||
}
|
||||
|
||||
// 定义对外事件
|
||||
const emit = defineEmits(['start-dispatch', 'view-plan'])
|
||||
const emit = defineEmits(['start-dispatch'])
|
||||
|
||||
/**
|
||||
* 处理力量调度启动事件,向上传递给父组件
|
||||
@ -118,13 +115,6 @@ const emit = defineEmits(['start-dispatch', 'view-plan'])
|
||||
const handleStartDispatch = (payload) => {
|
||||
emit('start-dispatch', payload)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理查看智能应急方案事件,向上传递给父组件
|
||||
*/
|
||||
const handleViewPlan = (plan) => {
|
||||
emit('view-plan', plan)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@ -1,785 +0,0 @@
|
||||
<template>
|
||||
<div class="emergency-plan-content">
|
||||
<!-- 1. 现场指挥部 -->
|
||||
<section class="plan-section">
|
||||
<div class="section-header">
|
||||
<img
|
||||
src="../../assets/images/modal/弹窗title.png"
|
||||
alt=""
|
||||
class="title-icon"
|
||||
/>
|
||||
<h3 class="section-title">现场指挥部</h3>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="form-grid">
|
||||
<div class="form-item">
|
||||
<label>指挥长</label>
|
||||
<el-select
|
||||
v-model="formData.commander"
|
||||
class="custom-select"
|
||||
popper-class="custom-dropdown"
|
||||
>
|
||||
<el-option label="王军" value="王军" />
|
||||
<el-option label="李明" value="李明" />
|
||||
<el-option label="张伟" value="张伟" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>副指挥长</label>
|
||||
<el-select
|
||||
v-model="formData.viceCommander"
|
||||
class="custom-select"
|
||||
popper-class="custom-dropdown"
|
||||
>
|
||||
<el-option label="刘勇" value="刘勇" />
|
||||
<el-option label="陈强" value="陈强" />
|
||||
<el-option label="赵军" value="赵军" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 2. 多路协同处置三级指挥中心 -->
|
||||
<section class="plan-section">
|
||||
<div class="section-header">
|
||||
<img
|
||||
src="../../assets/images/modal/弹窗title.png"
|
||||
alt=""
|
||||
class="title-icon"
|
||||
/>
|
||||
<h3 class="section-title">多路协同处置三级指挥中心</h3>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="form-grid grid-2x2">
|
||||
<div class="form-item">
|
||||
<label>交通管控</label>
|
||||
<el-select
|
||||
v-model="formData.trafficControl"
|
||||
class="custom-select"
|
||||
popper-class="custom-dropdown"
|
||||
>
|
||||
<el-option label="公安交警、交通执法队" value="公安交警、交通执法队" />
|
||||
<el-option label="交通管理局" value="交通管理局" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>交通信息发布</label>
|
||||
<el-select
|
||||
v-model="formData.infoRelease"
|
||||
class="custom-select"
|
||||
popper-class="custom-dropdown"
|
||||
>
|
||||
<el-option label="融媒体中心" value="融媒体中心" />
|
||||
<el-option label="新闻中心" value="新闻中心" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>人车调拨</label>
|
||||
<el-select
|
||||
v-model="formData.vehicleDispatch"
|
||||
class="custom-select"
|
||||
popper-class="custom-dropdown"
|
||||
>
|
||||
<el-option label="xxx消防队" value="xxx消防队" />
|
||||
<el-option label="应急救援队" value="应急救援队" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>人员救援</label>
|
||||
<el-select
|
||||
v-model="formData.personnelRescue"
|
||||
class="custom-select"
|
||||
popper-class="custom-dropdown"
|
||||
>
|
||||
<el-option label="xx医院" value="xx医院" />
|
||||
<el-option label="中心医院" value="中心医院" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. 公路抢通方案 -->
|
||||
<section class="plan-section">
|
||||
<div class="section-header">
|
||||
<img
|
||||
src="../../assets/images/modal/弹窗title.png"
|
||||
alt=""
|
||||
class="title-icon"
|
||||
/>
|
||||
<h3 class="section-title">公路抢通方案</h3>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="form-grid grid-2-cols">
|
||||
<div class="form-item">
|
||||
<label>抢通方式</label>
|
||||
<el-select
|
||||
v-model="formData.clearanceMethod"
|
||||
class="custom-select"
|
||||
popper-class="custom-dropdown"
|
||||
>
|
||||
<el-option
|
||||
label="两端对接抢通,必要时开辟工作面"
|
||||
value="两端对接抢通,必要时开辟工作面"
|
||||
/>
|
||||
<el-option label="单端推进抢通" value="单端推进抢通" />
|
||||
<el-option label="多点同时作业" value="多点同时作业" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>预计抢通时间</label>
|
||||
<el-select
|
||||
v-model="formData.estimatedTime"
|
||||
class="custom-select"
|
||||
popper-class="custom-dropdown"
|
||||
>
|
||||
<el-option label="6小时" value="6小时" />
|
||||
<el-option label="8小时" value="8小时" />
|
||||
<el-option label="12小时" value="12小时" />
|
||||
<el-option label="24小时" value="24小时" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plan-steps">
|
||||
<div v-for="step in clearanceSteps" :key="step.id" class="step-item">
|
||||
<label class="step-label">{{ step.number }}{{ step.title }}</label>
|
||||
<div class="step-content">{{ step.content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. 力量调派方案 -->
|
||||
<section class="plan-section">
|
||||
<div class="section-header">
|
||||
<img
|
||||
src="../../assets/images/modal/弹窗title.png"
|
||||
alt=""
|
||||
class="title-icon"
|
||||
/>
|
||||
<h3 class="section-title">力量调派方案</h3>
|
||||
<button class="add-btn" @click="handleAddPlan">新增</button>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="dispatch-plans">
|
||||
<div
|
||||
v-for="plan in dispatchPlans"
|
||||
:key="plan.id"
|
||||
class="dispatch-card"
|
||||
>
|
||||
<h4 class="plan-name">
|
||||
<span class="plan-icon"></span>
|
||||
{{ plan.name }}
|
||||
</h4>
|
||||
<div class="resource-grid">
|
||||
<div
|
||||
v-for="(resource, index) in plan.resources"
|
||||
:key="index"
|
||||
class="resource-item"
|
||||
>
|
||||
<span class="resource-label">{{ resource.label }}</span>
|
||||
<span class="resource-value">{{ resource.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. 后续处治 -->
|
||||
<section class="plan-section">
|
||||
<div class="section-header">
|
||||
<img
|
||||
src="../../assets/images/modal/弹窗title.png"
|
||||
alt=""
|
||||
class="title-icon"
|
||||
/>
|
||||
<h3 class="section-title">后续处治</h3>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<p class="follow-up-text">{{ followUpText }}</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from "vue";
|
||||
// 导入 Element Plus 组件(如果项目已全局注册则可省略)
|
||||
// import { ElSelect, ElOption } from 'element-plus'
|
||||
|
||||
/**
|
||||
* 表单数据
|
||||
*/
|
||||
const formData = reactive({
|
||||
commander: "王军",
|
||||
viceCommander: "刘勇",
|
||||
trafficControl: "公安交警、交通执法队",
|
||||
infoRelease: "融媒体中心",
|
||||
vehicleDispatch: "xxx消防队",
|
||||
personnelRescue: "xx医院",
|
||||
clearanceMethod: "两端对接抢通,必要时开辟工作面",
|
||||
estimatedTime: "6小时",
|
||||
});
|
||||
|
||||
/**
|
||||
* 公路抢通方案步骤
|
||||
*/
|
||||
const clearanceSteps = [
|
||||
{
|
||||
id: 1,
|
||||
number: "①",
|
||||
title: "评估封控",
|
||||
content:
|
||||
"对现场进行封控,清点各类应急物资提前到达现场,划定作业区与危险区,设立观察哨,全程监控边坡状况。",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
number: "②",
|
||||
title: "边坡排险",
|
||||
content:
|
||||
"在确保安全的前提下,使用长臂挖掘机或人工在安全地域进行清除工作。注意下方施救作业。",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
number: "③",
|
||||
title: "梯形清方",
|
||||
content:
|
||||
'按照机械自上而下,装载机抢先挖入土石堆入车,确土车上车主生,增土车队运输,将受阻路段疏通清理通道。确保抢险不停工,形成高效的"挖一装一运"循环。',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
number: "④",
|
||||
title: "开辟工作面",
|
||||
content:
|
||||
"根据地质情况而规划车,利用挖掘机、装载机完成设备增加的工作面。增加作业效率。",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
number: "⑤",
|
||||
title: "交通疏导",
|
||||
content:
|
||||
"清理出足够疏散车辆的临时性通道,临时道路根据道路情况设置限量所制路牌。在应急抢险下,试行错位通车。",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
number: "⑥",
|
||||
title: "全断面抢通",
|
||||
content: "将剩余集聚废弃物的清理清除,恢复道路原貌状。",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 力量调派预案
|
||||
*/
|
||||
const dispatchPlans = [
|
||||
{
|
||||
id: 1,
|
||||
name: "基地1:xxxxx名称",
|
||||
resources: [
|
||||
{ label: "装载机挖掘机", value: "1台" },
|
||||
{ label: "平板拖车", value: "1台" },
|
||||
{ label: "自卸货车", value: "4台" },
|
||||
{ label: "工程车", value: "1台" },
|
||||
{ label: "人员", value: "15人" },
|
||||
{ label: "标志标牌", value: "5块" },
|
||||
{ label: "铁锹", value: "30件" },
|
||||
{ label: "铁镐", value: "10件" },
|
||||
{ label: "麻袋、砂石袋等", value: "若干" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "基地2:xxxxx名称",
|
||||
resources: [
|
||||
{ label: "装载机挖掘机", value: "1台" },
|
||||
{ label: "平板拖车", value: "1台" },
|
||||
{ label: "自卸货车", value: "4台" },
|
||||
{ label: "工程车", value: "1台" },
|
||||
{ label: "人员", value: "15人" },
|
||||
{ label: "标志标牌", value: "5块" },
|
||||
{ label: "铁锹", value: "30件" },
|
||||
{ label: "铁镐", value: "10件" },
|
||||
{ label: "麻袋、砂石袋等", value: "若干" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 后续处治文本
|
||||
*/
|
||||
const followUpText =
|
||||
'将该处于方案大部分障碍清点,设置"注意落石"等标志牌,进行巡逻管控,进行巡逻处理信号监测统设备。';
|
||||
|
||||
/**
|
||||
* 处理新增预案按钮点击
|
||||
*/
|
||||
const handleAddPlan = () => {
|
||||
console.log("[EmergencyPlanContent] 点击新增预案");
|
||||
// TODO: 后续可以打开新增预案弹窗或添加新预案
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@/styles/mixins.scss" as *;
|
||||
|
||||
.emergency-plan-content {
|
||||
padding: 0;
|
||||
color: var(--text-white);
|
||||
}
|
||||
|
||||
/* 区块样式 */
|
||||
.plan-section {
|
||||
margin-bottom: vh(16);
|
||||
// border: 1px solid rgba(28, 161, 255, 0.3);
|
||||
border-radius: vw(4);
|
||||
overflow: hidden;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 区块头部 */
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: vw(8);
|
||||
padding: vh(2) vw(18);
|
||||
background: #1f497a;
|
||||
|
||||
.title-icon {
|
||||
width: vw(8);
|
||||
height: vh(32);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: fs(18);
|
||||
font-family: SourceHanSansCN-Bold, sans-serif;
|
||||
font-weight: bold;
|
||||
color: var(--text-white);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
margin-left: auto;
|
||||
padding: vh(6) vw(16);
|
||||
background: var(--primary-color);
|
||||
border: none;
|
||||
border-radius: vw(4);
|
||||
color: var(--text-white);
|
||||
font-size: fs(14);
|
||||
font-family: SourceHanSansCN-Medium, sans-serif;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--primary-light);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 区块内容 */
|
||||
.section-body {
|
||||
padding: vh(14) vw(18);
|
||||
}
|
||||
|
||||
/* 表单网格 */
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: vw(16) vh(12);
|
||||
|
||||
&.grid-2x2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
&.grid-2-cols {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表单项 */
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: vw(10);
|
||||
|
||||
label {
|
||||
width: vw(100);
|
||||
font-size: fs(14);
|
||||
font-family: SourceHanSansCN-Regular, sans-serif;
|
||||
color: var(--text-gray);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// Element Plus el-select 自定义样式
|
||||
:deep(.custom-select) {
|
||||
width: 100%;
|
||||
background: #052044 !important;
|
||||
|
||||
// 选择器容器
|
||||
.el-select__wrapper {
|
||||
background: #052044 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
// 输入框包装器
|
||||
.el-input {
|
||||
background: #052044 !important;
|
||||
|
||||
&__wrapper {
|
||||
background-color: #052044 !important;
|
||||
background: #052044 !important;
|
||||
border: 1px solid rgba(28, 161, 255, 0.3) !important;
|
||||
border-radius: vw(4);
|
||||
box-shadow: none !important;
|
||||
padding: vh(6) vw(12);
|
||||
transition: none !important;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--primary-color) !important;
|
||||
background-color: #052044 !important;
|
||||
background: #052044 !important;
|
||||
}
|
||||
|
||||
&.is-focus,
|
||||
&.is-focused {
|
||||
border-color: var(--primary-color) !important;
|
||||
box-shadow: none !important;
|
||||
background-color: #052044 !important;
|
||||
background: #052044 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__inner {
|
||||
color: #fff !important;
|
||||
font-size: fs(13);
|
||||
font-family: SourceHanSansCN-Regular, sans-serif;
|
||||
height: auto;
|
||||
background-color: transparent !important;
|
||||
background: transparent !important;
|
||||
|
||||
// 强制覆盖 placeholder 颜色,确保选中值为纯白色
|
||||
&:not(:placeholder-shown) {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选择器内部的输入框
|
||||
.el-input {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
|
||||
&__inner {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
-webkit-text-fill-color: #fff !important;
|
||||
|
||||
// 覆盖所有可能的状态
|
||||
&[readonly] {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
-webkit-text-fill-color: #fff !important;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
-webkit-text-fill-color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 输入框后缀区域
|
||||
&__suffix {
|
||||
background-color: transparent !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
// 输入框前缀区域
|
||||
&__prefix {
|
||||
background-color: transparent !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 输入框文本
|
||||
.el-select__input {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
// 选中的值文本(优先级最高,放在最前)
|
||||
.el-select__selected-item {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
// placeholder 文本(只针对真正的 placeholder)
|
||||
.el-input__inner::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5) !important;
|
||||
}
|
||||
|
||||
// 只在没有选中值时显示半透明的 placeholder
|
||||
.el-select__placeholder.is-transparent {
|
||||
color: rgba(255, 255, 255, 0.5) !important;
|
||||
}
|
||||
|
||||
// 强制覆盖 input 元素的文本颜色(针对 readonly 状态)
|
||||
input.el-input__inner {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
-webkit-text-fill-color: #fff !important;
|
||||
}
|
||||
|
||||
// 下拉箭头颜色
|
||||
.el-select__caret {
|
||||
color: var(--text-gray) !important;
|
||||
}
|
||||
|
||||
// 清除按钮颜色
|
||||
.el-select__clear {
|
||||
color: var(--text-gray) !important;
|
||||
}
|
||||
|
||||
// 选中的标签(多选时)
|
||||
.el-select__tags {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 方案步骤 */
|
||||
.plan-steps {
|
||||
margin-top: vh(16);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: vh(12);
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: vw(10);
|
||||
|
||||
.step-label {
|
||||
width: vw(100);
|
||||
font-size: fs(14);
|
||||
font-family: SourceHanSansCN-Regular, sans-serif;
|
||||
color: var(--text-gray);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
flex: 1;
|
||||
font-size: fs(13);
|
||||
font-family: SourceHanSansCN-Regular, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-white);
|
||||
padding: vh(8) vw(12);
|
||||
background: #052044;
|
||||
border-radius: vw(4);
|
||||
}
|
||||
}
|
||||
|
||||
/* 调派预案 */
|
||||
.dispatch-plans {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: vh(12);
|
||||
}
|
||||
|
||||
.dispatch-card {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(28, 161, 255, 0.2);
|
||||
border-radius: vw(8);
|
||||
padding: vh(12) vw(14);
|
||||
|
||||
.plan-name {
|
||||
font-size: fs(15);
|
||||
font-family: SourceHanSansCN-Bold, sans-serif;
|
||||
font-weight: bold;
|
||||
color: var(--text-white);
|
||||
margin: 0 0 vh(10) 0;
|
||||
.plan-icon {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border: 2px solid #4fecff;
|
||||
border-radius: 50%;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.resource-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: vw(8) vh(8);
|
||||
}
|
||||
|
||||
.resource-item {
|
||||
display: flex;
|
||||
// flex-direction: column;
|
||||
gap: vh(2);
|
||||
|
||||
.resource-label {
|
||||
font-size: fs(12);
|
||||
font-family: SourceHanSansCN-Regular, sans-serif;
|
||||
color: var(--text-gray);
|
||||
width: vw(100);
|
||||
}
|
||||
|
||||
.resource-value {
|
||||
font-size: fs(13);
|
||||
font-family: SourceHanSansCN-Medium, sans-serif;
|
||||
font-weight: 500;
|
||||
color: var(--text-white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 后续处治文本 */
|
||||
.follow-up-text {
|
||||
font-size: fs(13);
|
||||
font-family: SourceHanSansCN-Regular, sans-serif;
|
||||
line-height: 1.8;
|
||||
color: var(--text-white);
|
||||
margin: 0;
|
||||
padding: vh(10) vw(14);
|
||||
background: #052044;
|
||||
border-radius: vw(4);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
// 全局样式:el-select 输入框背景和文字颜色(强制覆盖)
|
||||
.custom-select {
|
||||
.el-input__wrapper {
|
||||
background-color: #052044 !important;
|
||||
background: #052044 !important;
|
||||
}
|
||||
|
||||
.el-input {
|
||||
background-color: #052044 !important;
|
||||
background: #052044 !important;
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
|
||||
&__inner {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
-webkit-text-fill-color: #fff !important;
|
||||
|
||||
// 覆盖所有可能的状态
|
||||
&[readonly] {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
-webkit-text-fill-color: #fff !important;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
-webkit-text-fill-color: #fff !important;
|
||||
}
|
||||
|
||||
// 强制覆盖 placeholder 颜色,确保选中值为纯白色
|
||||
&:not(:placeholder-shown) {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
-webkit-text-fill-color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 输入框文本
|
||||
.el-select__input {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
// 选中的值(优先级最高,先定义)
|
||||
.el-select__selected-item {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
// placeholder 文本(只针对真正的 placeholder)
|
||||
.el-input__inner::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5) !important;
|
||||
}
|
||||
|
||||
// 只在没有选中值时显示半透明的 placeholder
|
||||
.el-select__placeholder.is-transparent {
|
||||
color: rgba(255, 255, 255, 0.5) !important;
|
||||
}
|
||||
|
||||
// 强制覆盖 input 元素的文本颜色(针对 readonly 状态)
|
||||
input.el-input__inner {
|
||||
color: #fff !important;
|
||||
opacity: 1 !important;
|
||||
-webkit-text-fill-color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 全局样式:下拉面板(挂载在 body 上,需要非 scoped 样式)
|
||||
.custom-dropdown {
|
||||
background: #052044 !important;
|
||||
border: 1px solid rgba(28, 161, 255, 0.3) !important;
|
||||
|
||||
// 下拉列表容器
|
||||
.el-select-dropdown__wrap {
|
||||
background: #052044 !important;
|
||||
}
|
||||
|
||||
// 下拉列表
|
||||
.el-select-dropdown__list {
|
||||
background: #052044 !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// 下拉选项
|
||||
.el-select-dropdown__item {
|
||||
background: #052044 !important;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-family: SourceHanSansCN-Regular, sans-serif;
|
||||
|
||||
&:hover {
|
||||
background: rgba(28, 161, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
background: rgba(28, 161, 255, 0.3) !important;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.is-hovering {
|
||||
background: rgba(28, 161, 255, 0.2) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 空状态文字颜色
|
||||
.el-select-dropdown__empty {
|
||||
color: rgba(179, 204, 226, 1);
|
||||
background: #052044 !important;
|
||||
}
|
||||
|
||||
// 滚动条样式
|
||||
.el-scrollbar__view {
|
||||
background: #052044 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,308 +0,0 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<Transition name="popup-fade">
|
||||
<div
|
||||
v-if="visible"
|
||||
class="stretchable-modal__overlay"
|
||||
@click="handleOverlayClick"
|
||||
>
|
||||
<div
|
||||
class="stretchable-modal__container"
|
||||
:style="containerStyle"
|
||||
@click.stop
|
||||
>
|
||||
<!-- 头部:大弹窗top切图.png -->
|
||||
<div class="stretchable-modal__header">
|
||||
<slot name="header">
|
||||
<h3 class="stretchable-modal__title">{{ title }}</h3>
|
||||
</slot>
|
||||
<button
|
||||
v-if="showClose"
|
||||
class="stretchable-modal__close"
|
||||
@click="handleClose"
|
||||
aria-label="关闭弹窗"
|
||||
>
|
||||
<img
|
||||
src="../../assets/images/modal/closeIcon.png"
|
||||
alt="关闭"
|
||||
class="close-icon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 中间:大弹窗mid切图.png -->
|
||||
<div class="stretchable-modal__body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<!-- 底部:大弹窗bottom切图.png -->
|
||||
<div v-if="$slots.footer" class="stretchable-modal__footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, watch, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
/**
|
||||
* StretchableModal - 三段式可拉伸弹窗组件
|
||||
*
|
||||
* 特性:
|
||||
* - 使用三张切图实现可拉伸的弹窗背景(头部、中间、底部)
|
||||
* - 头部和底部仅支持横向拉伸,中间部分支持全方向拉伸
|
||||
* - 标题居中显示
|
||||
* - 支持多种交互方式关闭(点击遮罩、ESC键、关闭按钮)
|
||||
* - 支持插槽自定义内容
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
/**
|
||||
* 控制弹窗显示/隐藏
|
||||
*/
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* 弹窗标题
|
||||
*/
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 弹窗宽度,支持 CSS 单位或响应式值
|
||||
* @example '800px' | 'clamp(778px, 90vw, 1200px)'
|
||||
*/
|
||||
width: {
|
||||
type: String,
|
||||
default: 'clamp(900px, 85vw, 1400px)'
|
||||
},
|
||||
/**
|
||||
* 弹窗高度,支持 CSS 单位或响应式值
|
||||
* @example '600px' | 'clamp(500px, 80vh, 800px)'
|
||||
*/
|
||||
height: {
|
||||
type: String,
|
||||
default: 'clamp(600px, 85vh, 900px)'
|
||||
},
|
||||
/**
|
||||
* 点击遮罩层是否关闭弹窗
|
||||
*/
|
||||
closeOnClickModal: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* 按 ESC 键是否关闭弹窗
|
||||
*/
|
||||
closeOnPressEscape: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* 是否显示关闭按钮
|
||||
*/
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:visible', 'close', 'open']);
|
||||
|
||||
/**
|
||||
* 计算弹窗容器的样式
|
||||
*/
|
||||
const containerStyle = computed(() => ({
|
||||
width: props.width,
|
||||
height: props.height
|
||||
}));
|
||||
|
||||
/**
|
||||
* 处理遮罩层点击事件
|
||||
*/
|
||||
const handleOverlayClick = () => {
|
||||
if (props.closeOnClickModal) {
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 关闭弹窗
|
||||
*/
|
||||
const handleClose = () => {
|
||||
emit('update:visible', false);
|
||||
emit('close');
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理 ESC 键按下事件
|
||||
*/
|
||||
const handleKeydown = (event) => {
|
||||
if (props.visible && props.closeOnPressEscape && event.key === 'Escape') {
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
// 监听 visible 变化,触发相应事件
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
emit('open');
|
||||
}
|
||||
});
|
||||
|
||||
// 挂载和卸载时添加/移除键盘事件监听
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', handleKeydown);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@/styles/mixins.scss" as *;
|
||||
|
||||
.stretchable-modal {
|
||||
&__overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(vw(5));
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: vw(20);
|
||||
}
|
||||
|
||||
&__container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__header {
|
||||
position: relative;
|
||||
height: vh(66);
|
||||
min-height: vh(66);
|
||||
background: url(../../assets/images/modal/大弹窗top切图.png) repeat-x;
|
||||
background-size: 100% vh(66);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 vw(60);
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: var(--text-white);
|
||||
font-size: fs(20);
|
||||
font-family: SourceHanSansCN-Bold, sans-serif;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__close {
|
||||
position: absolute;
|
||||
top: 34px;
|
||||
right: 34px;
|
||||
transform: translateY(-50%);
|
||||
width: vw(32);
|
||||
height: vw(32);
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 10;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
transform: translateY(-50%) scale(1.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(-50%) scale(0.95);
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
flex: 1;
|
||||
background: url(../../assets/images/modal/大弹窗mid切图.png) repeat;
|
||||
background-size: 100% vh(66);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: vw(20) vw(30);
|
||||
|
||||
// 自定义滚动条样式
|
||||
&::-webkit-scrollbar {
|
||||
width: vw(6);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: vw(3);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--primary-color);
|
||||
border-radius: vw(3);
|
||||
|
||||
&:hover {
|
||||
background: var(--primary-light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
height: vh(66);
|
||||
min-height: vh(66);
|
||||
background: url(../../assets/images/modal/大弹窗bottom切图.png) repeat-x;
|
||||
background-size: 100% vh(66);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 vw(30);
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗淡入淡出动画
|
||||
.popup-fade-enter-active,
|
||||
.popup-fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
.stretchable-modal__container {
|
||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-fade-enter-from,
|
||||
.popup-fade-leave-to {
|
||||
opacity: 0;
|
||||
|
||||
.stretchable-modal__container {
|
||||
transform: scale(0.9);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -20,9 +20,9 @@ export function useDisasterData() {
|
||||
|
||||
// 力量预置信息
|
||||
const forcePreset = ref({
|
||||
equipment: 0,
|
||||
bases: 0,
|
||||
personnel: 0,
|
||||
equipment: 23,
|
||||
bases: 2,
|
||||
personnel: 2124,
|
||||
searchRadius: 30, // km
|
||||
stations: [
|
||||
{
|
||||
@ -150,8 +150,6 @@ export function useDisasterData() {
|
||||
|
||||
// 更新养护站列表
|
||||
if (Array.isArray(stations)) {
|
||||
// 根据养护站列表更新应急基地数
|
||||
forcePreset.value.bases = stations.length
|
||||
forcePreset.value.stations = stations.map((station) => ({
|
||||
id: station.stationId,
|
||||
name: station.stationName?.trim() || '未命名养护站',
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { ref } from 'vue'
|
||||
import * as Cesium from 'cesium'
|
||||
import soldierIcon from '../assets/images/SketchPngfbec927027ff9e49207749ebaafd229429315341fda199251b6dfb1723ff17fb.png'
|
||||
import deviceIcon from '../assets/images/SketchPng860d54f2a31f5f441fc6a88081224f1e98534bf6d5ca1246e420983bdf690380.png'
|
||||
|
||||
/**
|
||||
* 实体动画管理 Composable
|
||||
@ -11,9 +10,6 @@ export function useEntityAnimation() {
|
||||
// 动画实体引用
|
||||
const animatedEntity = ref(null)
|
||||
|
||||
// 多组动画实体引用
|
||||
const animatedEntities = ref([])
|
||||
|
||||
// 动画是否正在运行
|
||||
const isAnimating = ref(false)
|
||||
|
||||
@ -35,49 +31,6 @@ export function useEntityAnimation() {
|
||||
{ x: -1706343.1007708232, y: 5248165.925888667, z: 3187382.186124808 }
|
||||
]
|
||||
|
||||
// 设备组路径坐标
|
||||
const DEVICE_PATH_COORDINATES = [
|
||||
{ x: -1706935.1617290622, y: 5248501.604252841, z: 3186498.2137252213 },
|
||||
{ x: -1706870.4952433936, y: 5248490.611178698, z: 3186558.182971472 },
|
||||
{ x: -1706837.227189178, y: 5248420.334709732, z: 3186690.581380162 },
|
||||
{ x: -1706838.280444158, y: 5248431.37975438, z: 3186708.4743944216 },
|
||||
{ x: -1706773.0696515848, y: 5248373.01216906, z: 3186823.943446862 },
|
||||
{ x: -1706727.805384834, y: 5248323.242719114, z: 3186915.9148211516 },
|
||||
{ x: -1706710.854065587, y: 5248281.031240471, z: 3186987.549384787 },
|
||||
{ x: -1706660.1034110512, y: 5248266.553680115, z: 3187047.6342452955 },
|
||||
{ x: -1706604.7906532797, y: 5248253.292764083, z: 3187082.311741225 },
|
||||
{ x: -1706556.3289747096, y: 5248217.41920621, z: 3187155.538613598 },
|
||||
{ x: -1706516.2888762353, y: 5248217.691641104, z: 3187197.012414378 },
|
||||
{ x: -1706436.9808225571, y: 5248198.704904958, z: 3187267.311000274 },
|
||||
{ x: -1706345.7538517928, y: 5248165.481249246, z: 3187379.4674185305 }
|
||||
]
|
||||
|
||||
// 人员组2路径坐标
|
||||
const PERSONNEL_PATH_COORDINATES_2 = [
|
||||
{ x: -1707020.4106680197, y: 5248239.972166492, z: 3187000.301322692 },
|
||||
{ x: -1706846.822522451, y: 5248275.0303560095, z: 3186986.011771928 },
|
||||
{ x: -1706851.3836389545, y: 5248300.681797178, z: 3186942.7035291386 },
|
||||
{ x: -1706844.580179418, y: 5248304.977808034, z: 3186932.891914393 },
|
||||
{ x: -1706818.9276717228, y: 5248278.222342581, z: 3186976.253684671 },
|
||||
{ x: -1706831.3209432173, y: 5248316.447298632, z: 3186957.2249293313 },
|
||||
{ x: -1706806.3169318594, y: 5248307.832821272, z: 3186919.6375066047 },
|
||||
{ x: -1706805.9829374112, y: 5248319.428155191, z: 3186900.13906115 },
|
||||
{ x: -1706795.069820001, y: 5248321.041829423, z: 3186896.925767256 },
|
||||
{ x: -1706797.8593846853, y: 5248325.909509062, z: 3186906.0008906457 },
|
||||
{ x: -1706806.3364665583, y: 5248330.644777029, z: 3186917.773814682 },
|
||||
{ x: -1706778.3135473165, y: 5248282.550132113, z: 3186963.408730601 },
|
||||
{ x: -1706750.0602398354, y: 5248252.482706784, z: 3187012.7495610164 },
|
||||
{ x: -1706738.0772460685, y: 5248263.416236532, z: 3187023.8067475185 },
|
||||
{ x: -1706691.7246759017, y: 5248270.110156667, z: 3187018.588901565 },
|
||||
{ x: -1706599.6318286993, y: 5248240.149769867, z: 3187087.254778178 },
|
||||
{ x: -1706564.1779400432, y: 5248218.9292086465, z: 3187146.335302665 },
|
||||
{ x: -1706486.5598449395, y: 5248207.86588749, z: 3187216.762631293 },
|
||||
{ x: -1706445.7375556522, y: 5248203.1875622, z: 3187252.8650745875 },
|
||||
{ x: -1706409.1757614242, y: 5248182.077526832, z: 3187307.1294440767 },
|
||||
{ x: -1706408.0269636167, y: 5248192.685664652, z: 3187323.2121423096 },
|
||||
{ x: -1706390.7352810341, y: 5248211.637499602, z: 3187344.1570185754 }
|
||||
]
|
||||
|
||||
/**
|
||||
* 启动应急人员沿路径移动动画
|
||||
* @param {Cesium.Viewer} viewer - Cesium viewer 实例
|
||||
@ -290,175 +243,16 @@ export function useEntityAnimation() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建单个移动实体
|
||||
* @param {Cesium.Viewer} viewer
|
||||
* @param {Array} pathCoordinates - 路径坐标数组
|
||||
* @param {Object} config - 配置
|
||||
* @param {Cesium.JulianDate} startTime
|
||||
* @param {Cesium.JulianDate} stopTime
|
||||
* @returns {Cesium.Entity}
|
||||
*/
|
||||
const createMovingEntity = (viewer, pathCoordinates, config, startTime, stopTime) => {
|
||||
const positionProperty = new Cesium.SampledPositionProperty()
|
||||
const numberOfPoints = pathCoordinates.length
|
||||
const timeInterval = config.duration / (numberOfPoints - 1)
|
||||
|
||||
pathCoordinates.forEach((coord, index) => {
|
||||
const time = Cesium.JulianDate.addSeconds(
|
||||
startTime,
|
||||
index * timeInterval,
|
||||
new Cesium.JulianDate()
|
||||
)
|
||||
const position = new Cesium.Cartesian3(coord.x, coord.y, coord.z)
|
||||
positionProperty.addSample(time, position)
|
||||
})
|
||||
|
||||
const pulseScale = new Cesium.CallbackProperty((time) => {
|
||||
const elapsed = Cesium.JulianDate.secondsDifference(time, startTime)
|
||||
return 1.0 + Math.sin(elapsed * 3) * 0.3
|
||||
}, false)
|
||||
|
||||
const icon = config.type === 'device' ? deviceIcon : soldierIcon
|
||||
const trailColor = config.type === 'device' ? Cesium.Color.ORANGE : Cesium.Color.CYAN
|
||||
|
||||
return viewer.entities.add({
|
||||
availability: new Cesium.TimeIntervalCollection([
|
||||
new Cesium.TimeInterval({ start: startTime, stop: stopTime })
|
||||
]),
|
||||
position: positionProperty,
|
||||
orientation: new Cesium.VelocityOrientationProperty(positionProperty),
|
||||
billboard: {
|
||||
image: icon,
|
||||
width: 48,
|
||||
height: 56,
|
||||
scale: pulseScale,
|
||||
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
||||
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
scaleByDistance: new Cesium.NearFarScalar(1000, 1.5, 50000, 0.8)
|
||||
},
|
||||
path: {
|
||||
resolution: 1,
|
||||
material: new Cesium.PolylineGlowMaterialProperty({
|
||||
glowPower: 0.4,
|
||||
taperPower: 0.5,
|
||||
color: trailColor
|
||||
}),
|
||||
width: 8,
|
||||
leadTime: 0,
|
||||
trailTime: config.duration
|
||||
},
|
||||
properties: {
|
||||
type: config.type === 'device' ? 'animatedDevice' : 'animatedSoldier',
|
||||
name: config.name,
|
||||
department: config.department,
|
||||
isAnimating: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动多组移动动画(设备组 + 人员组2)
|
||||
* @param {Cesium.Viewer} viewer
|
||||
* @param {Object} options
|
||||
*/
|
||||
const startMultipleMovements = (viewer, options = {}) => {
|
||||
if (!viewer) {
|
||||
console.warn('[useEntityAnimation] startMultipleMovements: viewer 为空')
|
||||
return []
|
||||
}
|
||||
|
||||
const config = {
|
||||
duration: options.duration ?? 60
|
||||
}
|
||||
|
||||
console.log('[useEntityAnimation] 启动多组移动动画...')
|
||||
|
||||
const startTime = Cesium.JulianDate.now()
|
||||
const stopTime = Cesium.JulianDate.addSeconds(startTime, config.duration, new Cesium.JulianDate())
|
||||
|
||||
viewer.clock.startTime = startTime.clone()
|
||||
viewer.clock.stopTime = stopTime.clone()
|
||||
viewer.clock.currentTime = startTime.clone()
|
||||
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP
|
||||
viewer.clock.multiplier = 1
|
||||
viewer.clock.shouldAnimate = true
|
||||
|
||||
// 清除之前的动画实体
|
||||
animatedEntities.value.forEach(entity => {
|
||||
if (entity) viewer.entities.remove(entity)
|
||||
})
|
||||
animatedEntities.value = []
|
||||
|
||||
// 创建设备组移动实体
|
||||
const deviceEntity = createMovingEntity(viewer, DEVICE_PATH_COORDINATES, {
|
||||
duration: config.duration,
|
||||
type: 'device',
|
||||
name: '应急设备车',
|
||||
department: '应急装备队'
|
||||
}, startTime, stopTime)
|
||||
animatedEntities.value.push(deviceEntity)
|
||||
|
||||
// 创建人员组2移动实体
|
||||
const personnel2Entity = createMovingEntity(viewer, PERSONNEL_PATH_COORDINATES_2, {
|
||||
duration: config.duration,
|
||||
type: 'soldier',
|
||||
name: '应急救援队员',
|
||||
department: '应急救援队'
|
||||
}, startTime, stopTime)
|
||||
animatedEntities.value.push(personnel2Entity)
|
||||
|
||||
isAnimating.value = true
|
||||
|
||||
const removeListener = viewer.clock.onStop.addEventListener(() => {
|
||||
console.log('[useEntityAnimation] 多组动画已结束')
|
||||
isAnimating.value = false
|
||||
removeListener()
|
||||
})
|
||||
|
||||
console.log('[useEntityAnimation] 已启动 2 组额外移动动画')
|
||||
return animatedEntities.value
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止所有移动动画
|
||||
* @param {Cesium.Viewer} viewer
|
||||
*/
|
||||
const stopAllMovements = (viewer) => {
|
||||
if (!viewer) return
|
||||
|
||||
viewer.clock.shouldAnimate = false
|
||||
|
||||
if (animatedEntity.value) {
|
||||
viewer.entities.remove(animatedEntity.value)
|
||||
animatedEntity.value = null
|
||||
}
|
||||
|
||||
animatedEntities.value.forEach(entity => {
|
||||
if (entity) viewer.entities.remove(entity)
|
||||
})
|
||||
animatedEntities.value = []
|
||||
|
||||
isAnimating.value = false
|
||||
console.log('[useEntityAnimation] 已停止所有移动动画')
|
||||
}
|
||||
|
||||
return {
|
||||
animatedEntity,
|
||||
animatedEntities,
|
||||
isAnimating,
|
||||
startPersonnelMovement,
|
||||
startMultipleMovements,
|
||||
stopPersonnelMovement,
|
||||
stopAllMovements,
|
||||
pauseAnimation,
|
||||
resumeAnimation,
|
||||
getStartPosition,
|
||||
cartesianToLonLat,
|
||||
PERSONNEL_PATH_COORDINATES,
|
||||
DEVICE_PATH_COORDINATES,
|
||||
PERSONNEL_PATH_COORDINATES_2
|
||||
PERSONNEL_PATH_COORDINATES
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -54,10 +54,7 @@
|
||||
v-show="!isLeftPanelCollapsed"
|
||||
class="situational-awareness__panel-column situational-awareness__panel-column--left"
|
||||
>
|
||||
<LeftPanel
|
||||
@start-dispatch="handleStartDispatch"
|
||||
@view-plan="handleViewPlan"
|
||||
/>
|
||||
<LeftPanel @start-dispatch="handleStartDispatch" />
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
@ -167,29 +164,6 @@
|
||||
:center-data="selectedCenter"
|
||||
@close="showCenterDetail = false"
|
||||
/>
|
||||
|
||||
<!-- 智能应急方案弹窗 -->
|
||||
<StretchableModal
|
||||
v-model:visible="showStretchableModal"
|
||||
title="公路灾害现场处置推荐方案"
|
||||
:close-on-click-modal="true"
|
||||
:close-on-press-escape="true"
|
||||
:show-close="true"
|
||||
width="clamp(100px, 50vw, 1400px)"
|
||||
>
|
||||
<!-- 使用应急方案内容组件 -->
|
||||
<EmergencyPlanContent />
|
||||
|
||||
<!-- 底部一键启动按钮 -->
|
||||
<template #footer>
|
||||
<ActionButton
|
||||
text="一键启动"
|
||||
type="primary"
|
||||
size="medium"
|
||||
@click="handleModalStartDispatch"
|
||||
/>
|
||||
</template>
|
||||
</StretchableModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -205,9 +179,6 @@ import PersonnelDetail from "./components/Popups/PersonnelDetail.vue";
|
||||
import EmergencyCenterDetail from "./components/Popups/EmergencyCenterDetail.vue";
|
||||
import MapTooltip from "./components/shared/MapTooltip.vue";
|
||||
import SceneLabel from "./components/SceneLabel.vue";
|
||||
import StretchableModal from "./components/shared/StretchableModal.vue";
|
||||
import ActionButton from "./components/shared/ActionButton.vue";
|
||||
import EmergencyPlanContent from "./components/shared/EmergencyPlanContent.vue";
|
||||
import { useDisasterData } from "./composables/useDisasterData";
|
||||
import { useDualMapCompare } from "./composables/useDualMapCompare";
|
||||
import { useMapMarkers } from "./composables/useMapMarkers";
|
||||
@ -276,7 +247,7 @@ const {
|
||||
const { tooltipState: mapTooltip, showTooltip, hideTooltip, updateTooltipPosition } = useMapTooltip();
|
||||
|
||||
// 实体动画功能
|
||||
const { startPersonnelMovement, startMultipleMovements, stopPersonnelMovement, isAnimating } = useEntityAnimation();
|
||||
const { startPersonnelMovement, stopPersonnelMovement, isAnimating } = useEntityAnimation();
|
||||
|
||||
// 当前显示 tooltip 的实体(用于相机移动时更新位置)
|
||||
const currentTooltipEntity = ref(null);
|
||||
@ -284,16 +255,6 @@ const currentTooltipEntity = ref(null);
|
||||
// 加载动画状态
|
||||
const showLoading = ref(false);
|
||||
|
||||
// 默认相机配置
|
||||
const DEFAULT_CAMERA_VIEW = {
|
||||
lon: 108.011506,
|
||||
lat: 30.175827,
|
||||
height: 5000,
|
||||
heading: 0,
|
||||
pitch: -45,
|
||||
roll: 0,
|
||||
};
|
||||
|
||||
// 范围圈实体
|
||||
const rangeCircleEntity = ref(null);
|
||||
|
||||
@ -592,6 +553,16 @@ onMounted(() => {
|
||||
|
||||
console.log("3D态势感知地图已就绪");
|
||||
|
||||
// 默认相机配置
|
||||
const DEFAULT_CAMERA_VIEW = {
|
||||
lon: 108.011506,
|
||||
lat: 30.175827,
|
||||
height: 5000,
|
||||
heading: 0,
|
||||
pitch: -45,
|
||||
roll: 0,
|
||||
};
|
||||
|
||||
// 默认点加上图标标记,使用图片图标,图标路径为 packages\screen\src\views\3DSituationalAwarenessRefactor\assets\images\应急基地.png
|
||||
const defaultPoint = new Cesium.Entity({
|
||||
position: Cesium.Cartesian3.fromDegrees(
|
||||
@ -657,79 +628,6 @@ onMounted(() => {
|
||||
|
||||
console.log(`[index.vue] 已添加 ${simulatedPoints.length} 个模拟点位`);
|
||||
|
||||
// 添加三组路径动画的起点标记(用于"一键启动")
|
||||
// 路径1起点(原有人员)
|
||||
const path1Start = new Cesium.Cartesian3(-1706079.1327424292, 5247893.165552528, 3187993.9339800295);
|
||||
const path1StartEntity = viewer.entities.add({
|
||||
position: path1Start,
|
||||
billboard: {
|
||||
image: soldierIcon,
|
||||
width: 36,
|
||||
height: 40,
|
||||
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
||||
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
},
|
||||
properties: {
|
||||
type: 'soldier',
|
||||
name: '应急救援人员',
|
||||
department: '应急救援队',
|
||||
location: '待命中',
|
||||
estimatedArrival: '待启动',
|
||||
isPathStartMarker: true,
|
||||
pathId: 'path1'
|
||||
}
|
||||
});
|
||||
|
||||
// 路径2起点(设备组)
|
||||
const path2Start = new Cesium.Cartesian3(-1706935.1617290622, 5248501.604252841, 3186498.2137252213);
|
||||
const path2StartEntity = viewer.entities.add({
|
||||
position: path2Start,
|
||||
billboard: {
|
||||
image: deviceIcon,
|
||||
width: 36,
|
||||
height: 40,
|
||||
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
||||
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
},
|
||||
properties: {
|
||||
type: 'device',
|
||||
name: '应急设备车',
|
||||
deviceType: '应急装备',
|
||||
location: '待命中',
|
||||
estimatedArrival: '待启动',
|
||||
isPathStartMarker: true,
|
||||
pathId: 'path2'
|
||||
}
|
||||
});
|
||||
|
||||
// 路径3起点(人员组2)
|
||||
const path3Start = new Cesium.Cartesian3(-1707020.4106680197, 5248239.972166492, 3187000.301322692);
|
||||
const path3StartEntity = viewer.entities.add({
|
||||
position: path3Start,
|
||||
billboard: {
|
||||
image: soldierIcon,
|
||||
width: 36,
|
||||
height: 40,
|
||||
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
||||
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
},
|
||||
properties: {
|
||||
type: 'soldier',
|
||||
name: '应急救援队员',
|
||||
department: '应急救援队',
|
||||
location: '待命中',
|
||||
estimatedArrival: '待启动',
|
||||
isPathStartMarker: true,
|
||||
pathId: 'path3'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[index.vue] 已添加 3 个路径起点标记');
|
||||
|
||||
|
||||
// camera.setView({
|
||||
// ...DEFAULT_CAMERA_VIEW,
|
||||
// });
|
||||
@ -988,7 +886,6 @@ const handleMapToolChange = async ({ tool, active }) => {
|
||||
// 弹窗状态
|
||||
const showPersonnelDetail = ref(false);
|
||||
const showCenterDetail = ref(false);
|
||||
const showStretchableModal = ref(false);
|
||||
|
||||
// 选中的数据
|
||||
const selectedPersonnel = ref({
|
||||
@ -1081,7 +978,7 @@ const handleMapTooltipClose = () => {
|
||||
};
|
||||
|
||||
// 路径线实体引用
|
||||
const pathLineEntities = ref([]);
|
||||
const pathLineEntity = ref(null);
|
||||
|
||||
/**
|
||||
* 绘制红色路径线
|
||||
@ -1094,13 +991,13 @@ const drawRedPathLine = (viewer) => {
|
||||
}
|
||||
|
||||
// 如果已存在路径线,先移除
|
||||
pathLineEntities.value.forEach(entity => {
|
||||
if (entity) viewer.entities.remove(entity);
|
||||
});
|
||||
pathLineEntities.value = [];
|
||||
if (pathLineEntity.value) {
|
||||
viewer.entities.remove(pathLineEntity.value);
|
||||
pathLineEntity.value = null;
|
||||
}
|
||||
|
||||
// 路径1坐标点(原有人员路径)
|
||||
const pathCoordinates1 = [
|
||||
// 路径坐标点
|
||||
const pathCoordinates = [
|
||||
{ x: -1706079.1327424292, y: 5247893.165552528, z: 3187993.9339800295 },
|
||||
{ x: -1706116.7863268533, y: 5247923.177994122, z: 3187929.297700776 },
|
||||
{ x: -1706131.4939896727, y: 5247956.7916397555, z: 3187865.1250298577 },
|
||||
@ -1114,70 +1011,27 @@ const drawRedPathLine = (viewer) => {
|
||||
{ x: -1706343.1007708232, y: 5248165.925888667, z: 3187382.186124808 }
|
||||
];
|
||||
|
||||
// 路径2坐标点(设备组路径)
|
||||
const pathCoordinates2 = [
|
||||
{ x: -1706935.1617290622, y: 5248501.604252841, z: 3186498.2137252213 },
|
||||
{ x: -1706870.4952433936, y: 5248490.611178698, z: 3186558.182971472 },
|
||||
{ x: -1706837.227189178, y: 5248420.334709732, z: 3186690.581380162 },
|
||||
{ x: -1706838.280444158, y: 5248431.37975438, z: 3186708.4743944216 },
|
||||
{ x: -1706773.0696515848, y: 5248373.01216906, z: 3186823.943446862 },
|
||||
{ x: -1706727.805384834, y: 5248323.242719114, z: 3186915.9148211516 },
|
||||
{ x: -1706710.854065587, y: 5248281.031240471, z: 3186987.549384787 },
|
||||
{ x: -1706660.1034110512, y: 5248266.553680115, z: 3187047.6342452955 },
|
||||
{ x: -1706604.7906532797, y: 5248253.292764083, z: 3187082.311741225 },
|
||||
{ x: -1706556.3289747096, y: 5248217.41920621, z: 3187155.538613598 },
|
||||
{ x: -1706516.2888762353, y: 5248217.691641104, z: 3187197.012414378 },
|
||||
{ x: -1706436.9808225571, y: 5248198.704904958, z: 3187267.311000274 },
|
||||
{ x: -1706345.7538517928, y: 5248165.481249246, z: 3187379.4674185305 }
|
||||
];
|
||||
// 将坐标点转换为 Cartesian3 数组
|
||||
const positions = pathCoordinates.map(coord =>
|
||||
new Cesium.Cartesian3(coord.x, coord.y, coord.z)
|
||||
);
|
||||
|
||||
// 路径3坐标点(人员组2路径)
|
||||
const pathCoordinates3 = [
|
||||
{ x: -1707020.4106680197, y: 5248239.972166492, z: 3187000.301322692 },
|
||||
{ x: -1706846.822522451, y: 5248275.0303560095, z: 3186986.011771928 },
|
||||
{ x: -1706851.3836389545, y: 5248300.681797178, z: 3186942.7035291386 },
|
||||
{ x: -1706844.580179418, y: 5248304.977808034, z: 3186932.891914393 },
|
||||
{ x: -1706818.9276717228, y: 5248278.222342581, z: 3186976.253684671 },
|
||||
{ x: -1706831.3209432173, y: 5248316.447298632, z: 3186957.2249293313 },
|
||||
{ x: -1706806.3169318594, y: 5248307.832821272, z: 3186919.6375066047 },
|
||||
{ x: -1706805.9829374112, y: 5248319.428155191, z: 3186900.13906115 },
|
||||
{ x: -1706795.069820001, y: 5248321.041829423, z: 3186896.925767256 },
|
||||
{ x: -1706797.8593846853, y: 5248325.909509062, z: 3186906.0008906457 },
|
||||
{ x: -1706806.3364665583, y: 5248330.644777029, z: 3186917.773814682 },
|
||||
{ x: -1706778.3135473165, y: 5248282.550132113, z: 3186963.408730601 },
|
||||
{ x: -1706750.0602398354, y: 5248252.482706784, z: 3187012.7495610164 },
|
||||
{ x: -1706738.0772460685, y: 5248263.416236532, z: 3187023.8067475185 },
|
||||
{ x: -1706691.7246759017, y: 5248270.110156667, z: 3187018.588901565 },
|
||||
{ x: -1706599.6318286993, y: 5248240.149769867, z: 3187087.254778178 },
|
||||
{ x: -1706564.1779400432, y: 5248218.9292086465, z: 3187146.335302665 },
|
||||
{ x: -1706486.5598449395, y: 5248207.86588749, z: 3187216.762631293 },
|
||||
{ x: -1706445.7375556522, y: 5248203.1875622, z: 3187252.8650745875 },
|
||||
{ x: -1706409.1757614242, y: 5248182.077526832, z: 3187307.1294440767 },
|
||||
{ x: -1706408.0269636167, y: 5248192.685664652, z: 3187323.2121423096 },
|
||||
{ x: -1706390.7352810341, y: 5248211.637499602, z: 3187344.1570185754 }
|
||||
];
|
||||
|
||||
// 绘制三条路径线
|
||||
const allPaths = [pathCoordinates1, pathCoordinates2, pathCoordinates3];
|
||||
|
||||
allPaths.forEach((pathCoordinates, index) => {
|
||||
const positions = pathCoordinates.map(coord =>
|
||||
new Cesium.Cartesian3(coord.x, coord.y, coord.z)
|
||||
);
|
||||
|
||||
const pathEntity = viewer.entities.add({
|
||||
polyline: {
|
||||
positions: positions,
|
||||
width: 5,
|
||||
material: Cesium.Color.RED,
|
||||
clampToGround: true
|
||||
}
|
||||
});
|
||||
|
||||
pathLineEntities.value.push(pathEntity);
|
||||
// 创建红色路径线实体
|
||||
pathLineEntity.value = viewer.entities.add({
|
||||
polyline: {
|
||||
positions: positions,
|
||||
width: 5,
|
||||
material: Cesium.Color.RED,
|
||||
clampToGround: true,
|
||||
// 添加发光效果使路径更醒目
|
||||
// material: new Cesium.PolylineGlowMaterialProperty({
|
||||
// glowPower: 0.3,
|
||||
// color: Cesium.Color.RED
|
||||
// })
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[index.vue] 已绘制 3 条红色路径线');
|
||||
console.log('[index.vue] 红色路径线已绘制');
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1199,20 +1053,8 @@ const handleStartDispatch = (payload) => {
|
||||
setTimeout(() => {
|
||||
showLoading.value = false;
|
||||
|
||||
// 启动应急人员沿路径移动动画
|
||||
if (mapStore.viewer) {
|
||||
// 移除路径起点标记(因为动画实体会从起点开始显示)
|
||||
const entitiesToRemove = [];
|
||||
mapStore.viewer.entities.values.forEach(entity => {
|
||||
if (entity.properties && entity.properties.isPathStartMarker) {
|
||||
entitiesToRemove.push(entity);
|
||||
}
|
||||
});
|
||||
entitiesToRemove.forEach(entity => {
|
||||
mapStore.viewer.entities.remove(entity);
|
||||
});
|
||||
console.log(`[index.vue] 已移除 ${entitiesToRemove.length} 个路径起点标记`);
|
||||
|
||||
// 启动应急人员沿路径移动动画
|
||||
console.log('[index.vue] 启动应急人员移动动画...');
|
||||
startPersonnelMovement(mapStore.viewer, {
|
||||
duration: 60, // 60秒完成整个路径,移动更清晰可见
|
||||
@ -1220,78 +1062,12 @@ const handleStartDispatch = (payload) => {
|
||||
personnelName: '应急救援人员',
|
||||
department: '应急救援队'
|
||||
});
|
||||
|
||||
// 同时启动多组移动动画(设备组 + 人员组2)
|
||||
startMultipleMovements(mapStore.viewer, {
|
||||
duration: 60
|
||||
});
|
||||
|
||||
// 缓慢拉近到能看到所有3组移动实体的全景位置
|
||||
const { camera } = mapStore.services();
|
||||
|
||||
console.log('[index.vue] 相机缓慢飞向最佳全景位置...');
|
||||
|
||||
// 收集所有3组路径的关键点(起点和终点),用于计算最佳视角
|
||||
const allPathPoints = [
|
||||
// 路径1(原有人员)
|
||||
new Cesium.Cartesian3(-1706079.1327424292, 5247893.165552528, 3187993.9339800295), // 起点
|
||||
new Cesium.Cartesian3(-1706343.1007708232, 5248165.925888667, 3187382.186124808), // 终点
|
||||
// 路径2(设备组)
|
||||
new Cesium.Cartesian3(-1706935.1617290622, 5248501.604252841, 3186498.2137252213), // 起点
|
||||
new Cesium.Cartesian3(-1706345.7538517928, 5248165.481249246, 3187379.4674185305), // 终点
|
||||
// 路径3(人员组2)
|
||||
new Cesium.Cartesian3(-1707020.4106680197, 5248239.972166492, 3187000.301322692), // 起点
|
||||
new Cesium.Cartesian3(-1706390.7352810341, 5248211.637499602, 3187344.1570185754) // 终点
|
||||
];
|
||||
|
||||
// 转换为经纬度格式
|
||||
const trajectoryPoints = allPathPoints.map(point => {
|
||||
const cartographic = Cesium.Cartographic.fromCartesian(point);
|
||||
return {
|
||||
lon: Cesium.Math.toDegrees(cartographic.longitude),
|
||||
lat: Cesium.Math.toDegrees(cartographic.latitude)
|
||||
};
|
||||
});
|
||||
|
||||
// 使用智能聚焦方法飞向能看到所有路径的最佳位置
|
||||
camera.fitBoundsWithTrajectory(trajectoryPoints, {
|
||||
duration: 5, // 5秒缓慢飞行
|
||||
padding: 0.2 // 20% 边距,确保所有点都在视野内
|
||||
});
|
||||
} else {
|
||||
console.warn('[index.vue] 地图viewer未就绪,无法启动动画');
|
||||
}
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理查看智能应急方案事件
|
||||
* 打开智能应急方案弹窗
|
||||
*/
|
||||
const handleViewPlan = (plan) => {
|
||||
console.log('[index.vue] 查看智能应急方案:', plan);
|
||||
showStretchableModal.value = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理弹窗中的一键启动按钮点击事件
|
||||
* 关闭弹窗并执行一键启动功能
|
||||
*/
|
||||
const handleModalStartDispatch = () => {
|
||||
console.log('[index.vue] 弹窗中点击一键启动');
|
||||
|
||||
// 关闭弹窗
|
||||
showStretchableModal.value = false;
|
||||
|
||||
// 执行一键启动逻辑(与handleStartDispatch相同)
|
||||
handleStartDispatch({
|
||||
planName: '智能应急方案',
|
||||
plan: disasterData.forceDispatch.value.plan,
|
||||
responseLevel: disasterData.forceDispatch.value.responseLevel,
|
||||
estimatedClearTime: disasterData.forceDispatch.value.estimatedClearTime
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建或更新范围圈
|
||||
* @param {Cesium.Viewer} viewer - Cesium viewer 实例
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user