应急指挥 添加全屏功能

master
853931625@qq.com 2026-06-04 10:24:07 +08:00
parent de13be436e
commit df42341a82
7 changed files with 676 additions and 4 deletions

View File

@ -46,7 +46,6 @@ module.exports = {
// 开发服务 // 开发服务
server: { server: {
// 监听端口号 // 监听端口号
port: "8053",
// 服务地址 // 服务地址
host: "127.0.0.1", host: "127.0.0.1",
// 是否自动打开浏览器 // 是否自动打开浏览器

View File

@ -0,0 +1,77 @@
import useUrlState from "@ahooksjs/use-url-state";
import { FullscreenOutlined } from "@ant-design/icons";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { useEventEmitter } from "ahooks";
import { Button, message, Space } from "antd";
import Page from "zy-react-library/components/Page";
import { NS_RESUE } from "~/enumerate/namespace";
import Cesium from "~/pages/Container/Enterprise/EmergencyRescue/Rescue/Command/components/Cesium";
import CommandFeedbackRecords from "~/pages/Container/Enterprise/EmergencyRescue/Rescue/Command/components/CommandFeedbackRecords";
import EventHandlingRecords from "~/pages/Container/Enterprise/EmergencyRescue/Rescue/Command/components/EventHandlingRecords";
import NonContingencyInstructions from "~/pages/Container/Enterprise/EmergencyRescue/Rescue/Command/components/NonContingencyInstructions";
import SelectEmergencyPlan from "~/pages/Container/Enterprise/EmergencyRescue/Rescue/Command/components/SelectEmergencyPlan";
import top from "~/pages/Container/Enterprise/EmergencyRescue/Rescue/Command/images/top.png";
import "./index.less";
function Command(props) {
const [urlState, setUrlState] = useUrlState({
id: "",
planId: "",
planName: "",
responseLevel: "",
responseLevelName: "",
}, { navigateMode: "replace" });
const getCommandEvent = useEventEmitter();
const onComplete = async () => {
const { success } = await props["rescueUpdateStatus"]({ id: urlState.id, isRescueExecuted: 4 });
if (success) {
message.success("完成救援成功");
props.history.goBack();
}
};
const onFullscreen = () => {
const url = new URL(window.location.href);
const containerPath = url.pathname.match(/^(.*\/container)\/.*/i)?.[1];
const basename = process.env.app.basename || "";
const params = new URLSearchParams(window.location.search);
params.set("token", sessionStorage.getItem("token") || "");
url.pathname = containerPath ? `${containerPath}/biRescue` : `${basename}/container/biRescue`;
url.search = params.toString();
window.open(url.toString(), "_blank");
};
return (
<Page
headerTitle="救援指挥"
extraActionButtons={(
<Space>
<Button icon={<FullscreenOutlined />} onClick={onFullscreen}>全屏</Button>
<Button type="primary" onClick={onComplete}>完成救援</Button>
</Space>
)}
>
<div className="command">
<div style={{ backgroundImage: `url(${top})` }} className="top">应急指挥台</div>
<div style={{ padding: "15px 10px", display: "flex", justifyContent: "space-between", gap: 10 }}>
<div style={{ width: 360 }}>
<SelectEmergencyPlan id={urlState.id} setUrlState={setUrlState} urlState={urlState} getCommandEvent={getCommandEvent} />
<CommandFeedbackRecords id={urlState.id} getCommandEvent={getCommandEvent} />
</div>
<div style={{ flex: 1, position: "relative" }}>
<Cesium urlState={urlState} />
</div>
<div style={{ width: 360 }}>
<NonContingencyInstructions id={urlState.id} planId={urlState.planId} getCommandEvent={getCommandEvent} />
<EventHandlingRecords id={urlState.id} planId={urlState.planId} />
</div>
</div>
</div>
</Page>
);
}
export default Connect([NS_RESUE], true)(Command);

View File

@ -0,0 +1,329 @@
.command {
background-color: #E5EDFD;
margin: -20px -20px -31px -20px;
.top {
background-size: 100% 100%;
background-repeat: no-repeat;
height: 66px;
width: 100%;
font-size: 36px;
color: #fff;
text-align: center;
line-height: 66px;
font-weight: bold;
}
.select-emergency-plan {
border-radius: 0 0 4px 4px;
background-image: linear-gradient(to bottom, #DAE9FA, #CFE5FF);
padding: 10px;
border-width: 2px;
border-style: solid;
border-image: linear-gradient(to top, #FFFFFF, #D1E1F9) 1;
.container {
border-radius: 2px;
background-color: #E4F0FF;
border-width: 2px;
border-style: solid;
border-image: linear-gradient(to top, #FFFFFF, #D9EFFD) 1;
box-shadow: inset 0 0 3px 0 rgba(49, 122, 202, 0.48);
padding: 15px;
}
}
.command-feedback-records {
margin-top: 10px;
border-radius: 0 0 4px 4px;
background-image: linear-gradient(to bottom, #DAE9FA, #CFE5FF);
padding: 10px;
border-width: 2px;
border-style: solid;
border-image: linear-gradient(to top, #FFFFFF, #D1E1F9) 1;
.title {
img {
width: 18px;
height: 18px;
}
span {
padding-left: 6px;
color: #222222;
font-size: 14px;
font-weight: bold;
}
}
.container {
padding-left: 20px;
.timeline-item {
position: relative;
padding-left: 20px;
margin-top: 10px;
&::before {
content: '';
position: absolute;
left: 10px;
top: 0;
bottom: -10px;
width: 2px;
background-color: #5B8CFF;
}
&:last-child::before {
bottom: 0;
}
.timeline-dot {
position: absolute;
left: 6px;
top: 15px;
width: 10px;
height: 10px;
border-radius: 50%;
border: 2px solid #5B8CFF;
background-color: #fff;
z-index: 1;
}
.parent {
.timeline-dot {
top: 15px;
}
}
}
.parent, .child {
margin-top: 10px;
border-radius: 2px;
background-color: rgb(255 255 255 / 0.41);
border-width: 1px;
border-style: solid;
border-image: linear-gradient(to top, #FFFFFF, #D9EFFD) 1;
padding: 10px;
.label {
color: #666666;
font-size: 14px;
}
}
}
}
.non-contingency-instructions {
border-radius: 0 0 4px 4px;
background-image: linear-gradient(to bottom, #DAE9FA, #CFE5FF);
padding: 5px;
border-width: 2px;
border-style: solid;
border-image: linear-gradient(to top, #FFFFFF, #D1E1F9) 1;
.title {
img {
width: 18px;
height: 18px;
}
span {
padding-left: 6px;
color: #222222;
font-size: 14px;
font-weight: bold;
}
}
.container {
.subtitle {
margin-left: 10px;
color: #3B445C;
font-size: 14px;
position: relative;
&::before {
content: '';
position: absolute;
left: -10px;
top: 7px;
bottom: 0;
width: 3px;
height: 13px;
background-color: #3796FF;
}
}
.table {
font-size: 13px;
margin-top: 5px;
text-align: center;
.header {
font-weight: bold;
display: grid;
grid-template-columns: 0.8fr 1fr 1fr 0.6fr;
.title {
color: #3B445C;
border: 1px solid #fff;
background-color: #C4E2F8;
}
}
.body {
border-top: none !important;
.row {
display: grid;
grid-template-columns: 0.8fr 1fr 1fr 0.6fr;
.cell {
border: 1px solid #fff;
}
}
}
}
}
}
.event-handling-records {
margin-top: 10px;
border-radius: 0 0 4px 4px;
background-image: linear-gradient(to bottom, #DAE9FA, #CFE5FF);
padding: 5px;
border-width: 2px;
border-style: solid;
border-image: linear-gradient(to top, #FFFFFF, #D1E1F9) 1;
.title {
img {
width: 18px;
height: 18px;
}
span {
padding-left: 6px;
color: #222222;
font-size: 14px;
font-weight: bold;
}
}
.container {
margin-top: 10px;
.time {
color: #222222;
font-weight: bold;
}
.content {
color: #444444;
}
.timeline-item {
position: relative;
padding-left: 20px;
margin-top: 10px;
&::before {
content: '';
position: absolute;
left: 10px;
top: 0;
bottom: -10px;
width: 2px;
background-color: #5B8CFF;
}
&:last-child::before {
bottom: 0;
}
.timeline-dot {
position: absolute;
left: 6px;
top: 7px;
width: 10px;
height: 10px;
border-radius: 50%;
border: 2px solid #5B8CFF;
background-color: #fff;
z-index: 1;
}
}
}
}
.cesium {
.form {
position: absolute;
top: 20px;
left: 20px;
z-index: 1;
width: 100%;
label {
color: #fff;
}
}
.mark-options {
display: flex;
justify-content: space-around;
gap: 25px;
position: absolute;
width: 100%;
bottom: 0;
&-item {
text-align: center;
cursor: pointer;
padding: 10px;
width: 194px;
height: 48px;
color: #3796FF;
font-size: 16px;
font-weight: bold;
position: relative;
background-size: 100% 100%;
background-repeat: no-repeat;
&-children {
position: absolute;
bottom: 55px;
left: 50%;
transform: translateX(-50%);
background-color: #D3E7FF;
width: 80%;
padding: 10px;
border-radius: 0 0 4px 4px;
border-width: 1px;
border-style: solid;
border-image: linear-gradient(to top, #1F89E3, #A2C3EA) 1;
&-item {
text-align: center;
cursor: pointer;
padding: 5px;
font-size: 14px;
font-weight: normal;
color: #000;
border: 1px solid transparent;
&.active{
color: #fff;
background-color: #3796FF;
border-radius: 0 0 4px 4px;
border-width: 1px;
border-style: solid;
border-image: linear-gradient(to top, #1F89E3, #A2C3EA) 1;
}
}
}
}
}
}
}

View File

@ -0,0 +1,66 @@
import useUrlState from "@ahooksjs/use-url-state";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { useEventEmitter, useMount } from "ahooks";
import { Button, message } from "antd";
import { NS_RESUE } from "~/enumerate/namespace";
import Cesium from "~/pages/Container/Enterprise/EmergencyRescue/Rescue/Command/components/Cesium";
import CommandFeedbackRecords from "~/pages/Container/Enterprise/EmergencyRescue/Rescue/Command/components/CommandFeedbackRecords";
import EventHandlingRecords from "~/pages/Container/Enterprise/EmergencyRescue/Rescue/Command/components/EventHandlingRecords";
import NonContingencyInstructions from "~/pages/Container/Enterprise/EmergencyRescue/Rescue/Command/components/NonContingencyInstructions";
import SelectEmergencyPlan from "~/pages/Container/Enterprise/EmergencyRescue/Rescue/Command/components/SelectEmergencyPlan";
import top from "~/pages/Container/Enterprise/EmergencyRescue/Rescue/Command/images/top.png";
import "./Command/index.less";
import "./index.less";
function BiRescue(props) {
const [urlState, setUrlState] = useUrlState({
id: "",
planId: "",
planName: "",
responseLevel: "",
responseLevelName: "",
token: "",
}, { navigateMode: "replace" });
const getCommandEvent = useEventEmitter();
useMount(() => {
if (urlState.token) {
sessionStorage.setItem("token", urlState.token);
}
});
const onComplete = async () => {
const { success } = await props["rescueUpdateStatus"]({ id: urlState.id, isRescueExecuted: 4 });
if (success) {
message.success("完成救援成功");
window.close();
}
};
return (
<div className="bi-rescue">
<div className="command">
<div style={{ backgroundImage: `url(${top})` }} className="top">
应急指挥台
<Button className="bi-rescue-complete" type="primary" onClick={onComplete}>完成救援</Button>
</div>
<div className="bi-rescue-main">
<div className="bi-rescue-side">
<SelectEmergencyPlan id={urlState.id} setUrlState={setUrlState} urlState={urlState} getCommandEvent={getCommandEvent} />
<CommandFeedbackRecords id={urlState.id} getCommandEvent={getCommandEvent} />
</div>
<div className="bi-rescue-map">
<Cesium urlState={urlState} />
</div>
<div className="bi-rescue-side">
<NonContingencyInstructions id={urlState.id} planId={urlState.planId} getCommandEvent={getCommandEvent} />
<EventHandlingRecords id={urlState.id} planId={urlState.planId} />
</div>
</div>
</div>
</div>
);
}
export default Connect([NS_RESUE], true)(BiRescue);

View File

@ -0,0 +1,181 @@
.bi-rescue {
--bi-rescue-page-height: max(720px, 100vh);
--bi-rescue-header-height: 66px;
--bi-rescue-gap: clamp(8px, 0.52vw, 10px);
--bi-rescue-padding-x: clamp(8px, 0.52vw, 10px);
--bi-rescue-padding-y: clamp(8px, 0.93vh, 10px);
--bi-rescue-side-width: 360px;
height: var(--bi-rescue-page-height);
min-width: 1280px;
min-height: 720px;
overflow: auto;
background-color: #E5EDFD;
box-sizing: border-box;
scrollbar-gutter: stable;
* {
box-sizing: border-box;
}
.command {
height: 100%;
margin: 0;
overflow: hidden;
.top {
position: relative;
height: var(--bi-rescue-header-height);
line-height: var(--bi-rescue-header-height);
}
.cesium {
height: 100%;
}
.cesium #map_container {
height: 100% !important;
min-height: 0;
}
.select-emergency-plan,
.command-feedback-records,
.non-contingency-instructions,
.event-handling-records {
width: 100%;
box-sizing: border-box;
border-color: rgba(255, 255, 255, 0.78);
box-shadow: inset 0 0 0 1px rgba(80, 150, 220, 0.08);
}
.select-emergency-plan {
flex: 0 0 200px;
.container {
height: 100%;
}
}
.command-feedback-records,
.event-handling-records {
display: flex;
flex-direction: column;
min-height: 0;
.container {
flex: 1;
min-height: 0;
}
}
.command-feedback-records {
flex: 1 1 auto;
margin-top: var(--bi-rescue-gap);
padding-bottom: 8px;
}
.non-contingency-instructions {
flex: 0 0 185px;
}
.event-handling-records {
flex: 1 1 auto;
margin-top: var(--bi-rescue-gap);
}
.bi-rescue-feedback-list {
height: 100%;
overflow-y: auto;
overflow-x: hidden;
padding-right: 6px;
scrollbar-gutter: stable;
}
.bi-rescue-empty {
height: 100%;
min-height: 180px;
display: flex;
align-items: center;
justify-content: center;
color: #5f7795;
}
.bi-rescue-feedback-list,
.bi-rescue-side,
.event-handling-records .container {
scrollbar-width: thin;
scrollbar-color: rgba(67, 115, 156, 0.65) rgba(210, 230, 250, 0.55);
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background: rgba(210, 230, 250, 0.55);
border-radius: 8px;
}
&::-webkit-scrollbar-thumb {
background: rgba(67, 115, 156, 0.65);
border-radius: 8px;
}
}
}
.bi-rescue-complete {
position: absolute;
top: 50%;
right: 16px;
transform: translateY(-50%);
}
.bi-rescue-main {
display: grid;
grid-template-columns: var(--bi-rescue-side-width) minmax(0, 1fr) var(--bi-rescue-side-width);
grid-template-rows: minmax(0, 1fr);
align-items: stretch;
gap: var(--bi-rescue-gap);
height: calc(var(--bi-rescue-page-height) - var(--bi-rescue-header-height));
padding: var(--bi-rescue-padding-y) var(--bi-rescue-padding-x);
overflow: hidden;
box-sizing: border-box;
}
.bi-rescue-side {
display: flex;
flex-direction: column;
min-height: 0;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
scrollbar-gutter: stable;
}
.bi-rescue-map {
min-width: 0;
min-height: 0;
height: 100%;
position: relative;
overflow: hidden;
}
}
@media (max-width: 1600px) {
.bi-rescue {
--bi-rescue-side-width: 330px;
}
}
@media (max-height: 760px) {
.bi-rescue {
--bi-rescue-page-height: 760px;
}
}
@media (max-width: 1440px) {
.bi-rescue {
--bi-rescue-side-width: 300px;
--bi-rescue-gap: 8px;
--bi-rescue-padding-x: 8px;
}
}

View File

@ -17,7 +17,6 @@ import defaultBillboardImage from "./images/h.png";
export const useCesiumMap = (mackClickEvent) => { export const useCesiumMap = (mackClickEvent) => {
const mapInstanceRef = useRef(null); const mapInstanceRef = useRef(null);
const query = useGetUrlQuery(); const query = useGetUrlQuery();
console.log("query", query);
const loadCesiumMap = async () => { const loadCesiumMap = async () => {
if (!window.Cesium) { if (!window.Cesium) {
if (window?.base?.loadDynamicResource) { if (window?.base?.loadDynamicResource) {

View File

@ -1,7 +1,8 @@
import useUrlState from "@ahooksjs/use-url-state"; import useUrlState from "@ahooksjs/use-url-state";
import { FullscreenOutlined } from "@ant-design/icons";
import { Connect } from "@cqsjjb/jjb-dva-runtime"; import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { useEventEmitter } from "ahooks"; import { useEventEmitter } from "ahooks";
import { Button, message } from "antd"; import { Button, message, Space } from "antd";
import Page from "zy-react-library/components/Page"; import Page from "zy-react-library/components/Page";
import { NS_RESUE } from "~/enumerate/namespace"; import { NS_RESUE } from "~/enumerate/namespace";
import Cesium from "./components/Cesium"; import Cesium from "./components/Cesium";
@ -31,8 +32,28 @@ function Command(props) {
} }
}; };
const onFullscreen = () => {
const url = new URL(window.location.href);
const containerPath = url.pathname.match(/^(.*\/container)\/.*/i)?.[1];
const basename = process.env.app.basename || "";
const params = new URLSearchParams(window.location.search);
params.set("token", sessionStorage.getItem("token") || "");
url.pathname = containerPath ? `${containerPath}/biRescue` : `${basename}/container/biRescue`;
url.search = params.toString();
window.open(url.toString(), "_blank");
};
return ( return (
<Page headerTitle="救援指挥" extraActionButtons={<Button type="primary" onClick={onComplete}>完成救援</Button>}> <Page
headerTitle="救援指挥"
extraActionButtons={(
<Space>
<Button icon={<FullscreenOutlined />} onClick={onFullscreen}>全屏</Button>
<Button type="primary" onClick={onComplete}>完成救援</Button>
</Space>
)}
>
<div className="command"> <div className="command">
<div style={{ backgroundImage: `url(${top})` }} className="top">应急指挥台</div> <div style={{ backgroundImage: `url(${top})` }} className="top">应急指挥台</div>
<div style={{ padding: "15px 10px", display: "flex", justifyContent: "space-between", gap: 10 }}> <div style={{ padding: "15px 10px", display: "flex", justifyContent: "space-between", gap: 10 }}>