重点作业统计对接、地图标点、详情弹窗

master
LiuJiaNan 2026-07-22 14:43:31 +08:00
parent 8b819fa652
commit f4d2d6766e
14 changed files with 523 additions and 215 deletions

View File

@ -32,3 +32,11 @@ export const getEightWorkInfoScreenWorkList = declareRequest(
"biFullLoading",
"Post > @/eightwork/eightworkInfo/screenWorkPage",
);
export const getKeyProjectLargeScreenList = declareRequest(
"biFullLoading",
"Post > @/keyProject/keyProject/largeScreenPage",
);
export const getFixedCameraVideoList = declareRequest(
"biFullLoading",
"Post > @/videopatrol/fixedCamera/getVideoList",
);

View File

@ -36,3 +36,11 @@ export const eightWorkMeasuresLogs = declareRequest(
"biFullLoading",
`Get > /eightwork/measuresLogs/listAll/{workId}`,
);
export const keyProjectInfo = declareRequest(
"biFullLoading",
"Get > /keyProject/keyProject/{id}",
);
export const getFixedCameraPlayUrl = declareRequest(
"biFullLoading",
"Get > /videopatrol/fixedCamera/getPlayUrl",
);

View File

@ -72,3 +72,11 @@ export const getEightWorkInfoScreenWorkTrend = declareRequest(
"portWeiXianEightWorkInfoScreenWorkTrendLoading",
"Post > @/eightwork/eightworkInfo/screenWorkTrend",
);
export const getKeyProjectLargeScreenProjectStatistics = declareRequest(
"portZhongDianKeyProjectLargeScreenProjectStatisticsLoading",
"Post > @/keyProject/keyProject/largeScreenProjectStatistics",
);
export const getFixedCameraVideoLocationStat = declareRequest(
"portZhongDianFixedCameraVideoLocationStatLoading",
"Post > @/videopatrol/fixedCamera/getVideoLocationStat",
);

View File

@ -228,19 +228,23 @@ export const branchOfficeUtilsList = [
},
{
label: "重点工程",
type: "project",
type: "keyProject",
list: [
{
label: "重点工程定位",
check: false,
type: "project",
type: "keyProject",
markIcon: pointIco30,
request: "getKeyProjectLargeScreenList",
titleKey: "projectName",
},
{
label: "视频定位",
check: false,
type: "video",
markIcon: pointIco31,
request: "getFixedCameraVideoList",
titleKey: "videoName",
},
],
},
@ -253,6 +257,8 @@ export const branchOfficeUtilsList = [
check: false,
type: "platCamera",
markIcon: pointIco3,
request: "getFixedCameraVideoList",
titleKey: "videoName",
},
],
},

View File

@ -118,18 +118,26 @@ function BottomUtils(props) {
const check = !list[index].list[index1].check;
if (check) {
if (item1.request) {
let pointData = [];
const requestParams = { portArea, corpinfoId: currentBranchOffice, pageIndex: 1, pageSize: 9999 };
if (dangerWorkTypes.includes(item1.type)) {
requestParams.workType = item1.type;
}
const { data = [] } = await props[item1.request](requestParams);
pointData = [...data];
if (dangerWorkTypes.includes(item1.type)) {
for (let i = 0; i < pointData.length; i++) {
pointData[i].latitude = pointData[i].info.latitude;
pointData[i].longitude = pointData[i].info.longitude;
}
}
if (["platCamera", "video"].includes(item1.type)) {
pointData = [];
for (let i = 0; i < data.length; i++) {
data[i].latitude = data[i].info.latitude;
data[i].longitude = data[i].info.longitude;
pointData.push(...data[i].fixedCameraCOList);
}
}
mapMethods.current.addMarkPoint(data, {
mapMethods.current.addMarkPoint(pointData, {
markType: item1.type,
markIcon: item1.markIcon,
subLabel: item1.label,

View File

@ -353,7 +353,7 @@ export const portUtilsList = [
{
img: bottomImg10,
checkImg: bottomImg10On,
label: "摄像头",
label: "视频巡屏",
type: "camera",
list: [
{
@ -363,6 +363,8 @@ export const portUtilsList = [
img: ico26,
checkImg: ico26On,
markIcon: pointIco3,
request: "getFixedCameraVideoList",
titleKey: "videoName",
},
],
},
@ -370,15 +372,17 @@ export const portUtilsList = [
img: bottomImg12,
checkImg: bottomImg12On,
label: "重点工程",
type: "project",
type: "keyProject",
list: [
{
label: "重点工程定位",
check: false,
type: "project",
type: "keyProject",
img: ico27,
checkImg: ico27On,
markIcon: pointIco30,
request: "getKeyProjectLargeScreenList",
titleKey: "projectName",
},
{
label: "视频定位",
@ -387,6 +391,8 @@ export const portUtilsList = [
img: ico26,
checkImg: ico26On,
markIcon: pointIco31,
request: "getFixedCameraVideoList",
titleKey: "videoName",
},
],
},

View File

@ -0,0 +1,150 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Descriptions, Divider } from "antd";
import { useEffect, useState } from "react";
import PreviewImg from "zy-react-library/components/PreviewImg";
import PreviewPdf from "zy-react-library/components/PreviewPdf";
import PreviewWord from "zy-react-library/components/PreviewWord";
import Table from "zy-react-library/components/Table";
import { UPLOAD_FILE_TYPE_ENUM } from "zy-react-library/enum/uploadFile/gwj";
import useGetFile from "zy-react-library/hooks/useGetFile";
import { getFileSuffix, getLabelName, isEmptyToWhether } from "zy-react-library/utils";
import { NS_BI_MARK_INFO } from "~/enumerate/namespace";
const STATUS = [
{ bianma: "0", name: "暂存" },
{ bianma: "1", name: "未开工" },
{ bianma: "2", name: "开工申请中" },
{ bianma: "4", name: "进行中" },
{ bianma: "5", name: "完工申请中" },
{ bianma: "6", name: "已结束" },
];
const VIDEO_TYPE = [
{ bianma: 1, name: "固定摄像头" },
{ bianma: 2, name: "移动摄像头" },
];
const renderFiles = (files = []) => {
const pdfFiles = [];
const imgFiles = [];
const wordFiles = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (getFileSuffix(file.filePath) === "pdf") {
pdfFiles.push(file);
}
else if (["jpg", "jpeg", "png"].includes(getFileSuffix(file.filePath))) {
imgFiles.push(file);
}
else if (["doc", "docx"].includes(getFileSuffix(file.filePath))) {
wordFiles.push(file);
}
}
return (
<>
{imgFiles.length > 0 && <PreviewImg files={imgFiles} />}
{pdfFiles.length > 0 && <PreviewPdf files={pdfFiles} />}
{wordFiles.length > 0 && <PreviewWord files={wordFiles} />}
</>
);
};
const KeyProject = (props) => {
const { getFile } = useGetFile();
const [info, setInfo] = useState({});
const getData = async () => {
const { data = {} } = await props.keyProjectInfo({ id: props.id });
console.log(data);
const files = await getFile({
eqType: UPLOAD_FILE_TYPE_ENUM["168"],
eqForeignKey: data.keyProjectId,
});
setInfo({
files,
...data,
});
};
useEffect(() => {
getData();
}, []);
return (
<div>
<Divider titlePlacement="start">基本信息</Divider>
<div>
<Descriptions
bordered
column={1}
items={[
{ label: "辖区单位", children: info.jurisdictionDepartmentName },
{ label: "辖区单位负责人", children: info.jurisdictionUserName },
{ label: "主管部门", children: info.masterDepartmentName },
{ label: "是否已有项目作业", children: isEmptyToWhether(info.projectWorkFlag) },
{ label: "重点作业名称", children: info.projectName },
{ label: "重点作业属性", children: info.projectTypeName && info.projectTypeName.join(",") },
{ label: "计划工期开始", children: info.planWorkStartDate },
{ label: "计划竣工时间", children: info.planWorkEndDate },
{ label: "相关方单位", children: info.xgfCorpinfoName },
{ label: "相关方单位负责人", children: info.xgfUserName },
{ label: "相关方单位负责人手机号", children: info.xgfMasterPhone },
{ label: "是否设置监理单位", children: isEmptyToWhether(info.supervisionUnitFlag) },
...(info.supervisionUnitFlag === 0
? []
: [
{ label: "监理单位", children: info.supervisionUnitCorpName },
{ label: "监理单位工程负责人", children: info.supervisionUnitUserName },
{ label: "监理单位工程负责人电话", children: info.supervisionUnitUserPhone },
]),
{ label: "状态", children: getLabelName({ status: info.applyStatus, list: STATUS }) },
{
label: "重点作业定位",
children: (
<>
<span>
经度
{info.longitude || "-"}
</span>
<span>
纬度
{info.latitude || "-"}
</span>
</>
),
},
{ label: "安全协议管理", children: renderFiles(info.files) },
]}
/>
</div>
<Divider orientation="left">绑定视频监控</Divider>
<div>
<Table
dataSource={info.keyProjectCameraList || []}
columns={[
{ title: "视频名称", dataIndex: "cameraName" },
{ title: "视频类型", dataIndex: "cameraType", render: (_, record) => getLabelName({ status: record.cameraType, list: VIDEO_TYPE }) },
]}
disabledResizer
options={false}
pagination={false}
/>
</div>
<Divider orientation="left">处罚人员设置</Divider>
<div>
<Table
dataSource={info.keyProjectPenaltyPersonnelList || []}
columns={[
{ title: "处罚人部门", dataIndex: "departmentName" },
{ title: "处罚人", dataIndex: "userName" },
]}
disabledResizer
options={false}
pagination={false}
/>
</div>
</div>
);
};
export default Connect([NS_BI_MARK_INFO], true)(KeyProject);

View File

@ -0,0 +1,32 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { useEffect, useState } from "react";
import AliPlayer from "zy-react-library/components/Video/AliPlayer";
import { NS_BI_MARK_INFO } from "~/enumerate/namespace";
const VideoPlay = (props) => {
const [playUrl, setPlayUrl] = useState("");
const getData = async () => {
const { data, success } = await props.getFixedCameraPlayUrl({ indexCode: props.cameraNumber });
if (success && data)
setPlayUrl(data.url);
};
useEffect(() => {
getData();
}, []);
return (
<div>
{playUrl
? (
<AliPlayer source={playUrl} height="500px" isLive />
)
: (
<div style={{ color: "#fff", textAlign: "center" }}>暂无视频</div>
)}
</div>
);
};
export default Connect([NS_BI_MARK_INFO], true)(VideoPlay);

View File

@ -0,0 +1,77 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Spin } from "antd";
import { useContext, useEffect, useState } from "react";
import CountUp from "react-countup";
import iconBackground from "~/assets/images/map_bi/content/icobg.png";
import totalProjectIcon from "~/assets/images/map_bi/content/icon25.png";
import startedProjectIcon from "~/assets/images/map_bi/content/icon26.png";
import labelBackground from "~/assets/images/map_bi/content/label.png";
import { NS_BI_STATISTICS } from "~/enumerate/namespace";
import Panel from "~/pages/Container/Map/components/Content/port/Panel";
import { Context } from "~/pages/Container/Map/js/context";
import "./index.less";
const defaultStatistics = [
{ image: totalProjectIcon, label: "重点工程总数", count: 0 },
{ image: startedProjectIcon, label: "重点工程开工数量", count: 0 },
];
/** 负责查询并展示重点工程总量与开工数量。 */
function KeyProjectPanel(props) {
const { portArea } = useContext(Context);
const [statistics, setStatistics] = useState(defaultStatistics);
useEffect(() => {
const loadStatistics = async () => {
const { data = {} } = await props.getKeyProjectLargeScreenProjectStatistics({
portArea,
});
const counts = [data.totalProjectCount, data.startCount];
setStatistics(
defaultStatistics.map((item, index) => ({
...item,
count: counts[index],
})),
);
};
loadStatistics();
}, []);
return (
<Panel title="重点工程" className="port-key-supervision__project">
<Spin
spinning={
props.biStatistics
.portZhongDianKeyProjectLargeScreenProjectStatisticsLoading
}
>
<div className="port-key-project__options">
{statistics.map(item => (
<div key={item.label} className="port-key-project__option">
<div
className="port-key-project__icon"
style={{ backgroundImage: `url(${iconBackground})` }}
>
<img src={item.image} alt="" />
</div>
<div className="port-key-project__info">
<div
className="port-key-project__label"
style={{ backgroundImage: `url(${labelBackground})` }}
>
{item.label}
</div>
<div className="port-key-project__count">
<CountUp end={+item.count} />
</div>
</div>
</div>
))}
</div>
</Spin>
</Panel>
);
}
export default Connect([NS_BI_STATISTICS], true)(KeyProjectPanel);

View File

@ -0,0 +1,85 @@
.port-key-supervision__project {
margin-top: 10px;
background-image: linear-gradient(
to bottom,
rgba(0, 0, 0, 0),
rgba(0, 0, 0, 0.8)
);
}
.port-key-project__options {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
height: 150px;
padding: 10px 15px;
border: 1px solid;
border-top: none;
border-image: linear-gradient(
to bottom,
rgba(58, 122, 149, 0),
rgba(58, 122, 149, 1)
)
1;
}
.port-key-project__option {
display: flex;
align-items: center;
justify-content: space-around;
width: 50%;
}
.port-key-project__icon {
width: 70px;
height: 70px;
margin-top: 5px;
padding-top: 13px;
text-align: center;
background-repeat: no-repeat;
background-size: 100% 100%;
}
.port-key-project__icon img {
width: 25px;
height: 26px;
animation: portKeyProjectSlideY 2s infinite;
}
.port-key-project__info {
flex: 1;
text-align: center;
}
.port-key-project__label {
width: 120px;
height: 24px;
margin-top: 5px;
color: #fff;
font-size: 12px;
line-height: 25px;
text-align: center;
background-repeat: no-repeat;
background-size: 100% 100%;
}
.port-key-project__count {
margin-top: 13px;
font-family:
"PingFang SC", "Helvetica Neue", "Hiragino Sans GB", "Segoe UI",
"Microsoft YaHei", "微软雅黑", sans-serif;
font-weight: bold;
background: linear-gradient(to top, #48bbf0, #fff);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.port-key-project__count span {
font-size: 24px;
}
@keyframes portKeyProjectSlideY {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-5px);
}
100% {
transform: translateY(0);
}
}

View File

@ -0,0 +1,75 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Spin } from "antd";
import { useContext, useEffect, useState } from "react";
import CountUp from "react-countup";
import videoBackground from "~/assets/images/map_bi/content/videoimg1.png";
import { NS_BI_STATISTICS } from "~/enumerate/namespace";
import Panel from "~/pages/Container/Map/components/Content/port/Panel";
import { Context } from "~/pages/Container/Map/js/context";
import "./index.less";
const defaultStatistics = [
{ label: "视频总数", count: 0 },
{ label: "在线数量", count: 0 },
{ label: "离线数量", count: 0 },
];
/** 负责查询并展示摄像头总数、在线与离线统计。 */
function VideoLocationPanel(props) {
const { portArea } = useContext(Context);
const [statistics, setStatistics] = useState(defaultStatistics);
useEffect(() => {
const loadStatistics = async () => {
const { data = {} } = await props.getFixedCameraVideoLocationStat({
portArea,
});
const counts = [data.totalCount, data.onlineCount, data.offlineCount];
setStatistics(
defaultStatistics.map((item, index) => ({
...item,
count: counts[index],
})),
);
};
loadStatistics();
}, []);
return (
<Panel title="视频定位" className="port-key-supervision__video-location">
<Spin
spinning={
props.biStatistics.portZhongDianFixedCameraVideoLocationStatLoading
}
>
<div className="port-video-location__options">
<div className="port-video-location__option">
<div
className="port-video-location__image"
style={{ backgroundImage: `url(${videoBackground})` }}
/>
<div>
{statistics.map(item => (
<div
key={item.label}
className="port-video-location__statistic"
>
<div>
{item.label}
</div>
<div>
<CountUp end={+item.count} />
</div>
</div>
))}
</div>
</div>
</div>
</Spin>
</Panel>
);
}
export default Connect([NS_BI_STATISTICS], true)(VideoLocationPanel);

View File

@ -0,0 +1,43 @@
.port-key-supervision__video-location {
background-image: linear-gradient(
to bottom,
rgba(0, 0, 0, 0),
rgba(0, 0, 0, 0.8)
);
}
.port-video-location__options {
display: flex;
justify-content: space-between;
border: 1px solid;
border-top: none;
border-image: linear-gradient(
to bottom,
rgba(58, 122, 149, 0),
rgba(58, 122, 149, 1)
)
1;
}
.port-video-location__option {
display: flex;
flex-direction: row;
align-items: center;
}
.port-video-location__image {
width: 225px;
height: 160px;
background-repeat: no-repeat;
background-size: 100% 100%;
}
.port-video-location__statistic {
display: flex;
flex: 1;
color: #fff;
font-size: 14px;
line-height: 3;
}
.port-video-location__statistic > div:first-child {
margin-right: 5px;
}
.port-video-location__statistic span {
font-size: 14px;
}

View File

@ -1,74 +1,14 @@
import { useContext, useState } from "react";
import CountUp from "react-countup";
import icobg from "~/assets/images/map_bi/content/icobg.png";
import icon25 from "~/assets/images/map_bi/content/icon25.png";
import icon26 from "~/assets/images/map_bi/content/icon26.png";
import label from "~/assets/images/map_bi/content/label.png";
import videoimg1 from "~/assets/images/map_bi/content/videoimg1.png";
import Title from "~/pages/Container/Map/components/Content/port/Title";
import { Context } from "~/pages/Container/Map/js/context";
import "./index.less";
const ZhongDian = () => {
const { currentPort, portArea, currentBranchOffice } = useContext(Context);
const [block1OptionsList, setBlock1OptionsList] = useState([
{ lable: "视频总数", count: 0 },
{ lable: "在线数量", count: 0 },
{ lable: "离线数量", count: 0 },
]);
const [block2OptionsList, setBlock2OptionsList] = useState([
{ img: icon25, label: "重点工程总数", count: 0 },
{ img: icon26, label: "重点工程开工数量", count: 0 },
]);
import KeyProjectPanel from "./KeyProjectPanel";
import VideoLocationPanel from "./VideoLocationPanel";
/** 重点监管模块仅负责按展示顺序组合业务区块。 */
function ZhongDian() {
return (
<div className="port_zhongdian">
<div className="block1">
<Title title="视频定位" />
<div className="options">
<div className="option">
<div className="leftimg" style={{ backgroundImage: `url(${videoimg1})` }} />
<div>
{block1OptionsList.map((item, index) => (
<div key={index} className="info">
<div className="lable">
{item.lable}
:
</div>
<div className="count">
<CountUp end={+item.count} />
</div>
</div>
))}
</div>
</div>
</div>
</div>
<div className="block2">
<Title title="重点工程" />
<div className="options">
{block2OptionsList.map((item, index) => (
<div key={index} className="option">
<div className="circular" style={{ backgroundImage: `url(${icobg})` }}>
<img src={item.img} alt="" />
</div>
<div className="info">
<div className="title" style={{ backgroundImage: `url(${label})` }}>
{item.label}
</div>
<div className="count">
<CountUp end={+item.count} />
</div>
</div>
</div>
))}
</div>
</div>
<div className="port-key-supervision">
<VideoLocationPanel />
<KeyProjectPanel />
</div>
);
};
}
export default ZhongDian;

View File

@ -1,138 +0,0 @@
.port_zhongdian {
.block1 {
background-image: linear-gradient(to bottom,
rgba(0, 0, 0, 0),
rgba(0, 0, 0, 0.8));
.options {
display: flex;
justify-content: space-between;
border: 1px solid;
border-image: linear-gradient(to bottom,
rgba(58, 122, 149, 0),
rgba(58, 122, 149, 1)) 1;
border-top: none;
.option {
display: flex;
flex-direction: row;
align-items: center;
.leftimg {
width: 225px;
height: 160px;
background-size: 100% 100%;
background-repeat: no-repeat;
}
.info {
flex: 1;
color: #fff;
font-size: 14px;
display: flex;
line-height: 3;
.lable {
margin-right: 5px;
}
.count {
span {
font-size: 14px;
}
}
}
}
}
}
.block2 {
background-image: linear-gradient(to bottom,
rgba(0, 0, 0, 0),
rgba(0, 0, 0, 0.8));
margin-top: 10px;
.options {
padding: 10px 15px;
border: 1px solid;
border-image: linear-gradient(to bottom,
rgba(58, 122, 149, 0),
rgba(58, 122, 149, 1)) 1;
border-top: none;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
position: relative;
height: 150px;
.option {
width: 50%;
display: flex;
align-items: center;
justify-content: space-around;
.circular {
margin-top: 5px;
background-size: 100% 100%;
background-repeat: no-repeat;
width: 70px;
height: 70px;
text-align: center;
padding-top: 13px;
img {
width: 25px;
height: 26px;
animation: zhongdianSlideY 2s infinite;
}
}
.info {
flex: 1;
text-align: center;
.title {
background-size: 100% 100%;
background-repeat: no-repeat;
width: 120px;
height: 24px;
margin-top: 5px;
font-size: 12px;
line-height: 25px;
text-align: center;
color: #fff;
}
.count {
margin-top: 13px;
background: linear-gradient(to top, #48bbf0, #ffffff);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: bold;
font-family: "PingFang SC", "Helvetica Neue", "Hiragino Sans GB",
"Segoe UI", "Microsoft YaHei", "微软雅黑", sans-serif;
span {
font-size: 24px;
}
}
}
}
}
}
@keyframes zhongdianSlideY {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-5px);
}
100% {
transform: translateY(0);
}
}
}