refactor(map): branchOffice Camera 对接

master
LiuJiaNan 2026-07-23 17:29:25 +08:00
parent 97792143b0
commit da80837605
4 changed files with 232 additions and 95 deletions

View File

@ -0,0 +1,41 @@
import { Spin } from "antd";
import { useEffect, useState } from "react";
import AliPlayer from "zy-react-library/components/Video/AliPlayer";
function VideoItem({ cameraNumber, videoName, getFixedCameraPlayUrl }) {
const [loading, setLoading] = useState(true);
const [playUrl, setPlayUrl] = useState("");
useEffect(() => {
let isUnmounted = false;
const loadPlayUrl = async () => {
setLoading(true);
setPlayUrl("");
const { data = {}, success } = await getFixedCameraPlayUrl({ indexCode: cameraNumber });
if (!isUnmounted && success)
setPlayUrl(data?.url || "");
if (!isUnmounted)
setLoading(false);
};
loadPlayUrl();
return () => {
isUnmounted = true;
};
}, [cameraNumber]);
return (
<div className="branch-office-camera-modal__video">
<div className="branch-office-camera-modal__video-name">{videoName}</div>
<Spin spinning={loading}>
{playUrl
? <AliPlayer source={playUrl} height="100%" isLive />
: <div className="branch-office-camera-modal__empty">暂无视频</div>}
</Spin>
</div>
);
}
export default VideoItem;

View File

@ -1,95 +1,133 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { useBoolean } from "ahooks";
import { Modal } from "antd";
import { useState } from "react";
import AliPlayer from "zy-react-library/components/Video/AliPlayer";
import { Button, Modal, Spin } from "antd";
import { useContext, useEffect, useState } from "react";
import useDictionary from "zy-react-library/hooks/useDictionary";
import { NS_BI_STATISTICS } from "~/enumerate/namespace";
import { Context } from "~/pages/Container/Map/js/context";
import VideoItem from "./VideoItem";
import "./index.less";
function Camera() {
const gridList = [
{ title: "四宫格", value: 4, gridColumnCount: 2, gridRowCount: 2 },
{ title: "六宫格", value: 6, gridColumnCount: 3, gridRowCount: 2 },
{ title: "九宫格", value: 9, gridColumnCount: 3, gridRowCount: 3 },
{ title: "单画面", value: 1, gridColumnCount: 1, gridRowCount: 1 },
];
function Camera(props) {
const { portArea, currentBranchOffice } = useContext(Context);
const { getDictionary, loading: dictionaryLoading } = useDictionary();
const [state, { setFalse }] = useBoolean(true);
const [parentList, setParentList] = useState([
{ title: "一级一级一级一级一级一级一级一级一级一级一级一级一级一级一级一级" },
{ title: "二级" },
{ title: "三级" },
]);
const [parentCurrentIndex, setParentCurrentIndex] = useState(0);
const [childList, setChildList] = useState([
{ title: "一级_一一级_一一级_一一级_一一级_一一级_一一级_一一级_一一级_一一级_一" },
{ title: "一级_二" },
{ title: "一级_三" },
{ title: "一级_四" },
{ title: "一级_五" },
{ title: "一级_六" },
]);
const [childCurrentIndex, setChildCurrentIndex] = useState(0);
const [videoList, setVideoList] = useState([
{ src: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4" },
{ src: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4" },
{ src: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4" },
{ src: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4" },
{ src: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4" },
{ src: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4" },
{ src: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4" },
{ src: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4" },
{ src: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4" },
]);
const [currentGrid, setCurrentGrid] = useState(9);
const [levelList, setLevelList] = useState([]);
const [currentLevel, setCurrentLevel] = useState("");
const [currentPage, setCurrentPage] = useState(0);
const [videoList, setVideoList] = useState([]);
const onChangeParentList = (index) => {
if (index === parentCurrentIndex)
return;
setParentCurrentIndex(index);
setChildCurrentIndex(0);
const loadData = async () => {
const [dictionaryData, { data = [] }] = await Promise.all([
getDictionary({ dictValue: "videoLevel" }),
props.getFixedCameraVideoInfoPage({
portArea,
corpinfoId: currentBranchOffice,
pageIndex: 1,
pageSize: 9999,
}),
]);
setLevelList(dictionaryData);
setVideoList(data);
};
const onChangeChildList = (index) => {
if (index === childCurrentIndex)
useEffect(() => {
loadData();
}, []);
const onChangeCurrentGrid = (value) => {
if (value === currentGrid)
return;
setChildCurrentIndex(index);
setCurrentGrid(value);
setCurrentPage(0);
};
const onChangeChildList = (value) => {
if (value === currentLevel)
return;
setCurrentLevel(value);
setCurrentPage(0);
};
// 先按左侧等级筛选,再按当前宫格数量截取当前页,只有这些视频格会请求播放地址。
const filteredVideoList = videoList.filter(item => !currentLevel || item.videoLevel === currentLevel);
const pageCount = Math.ceil(filteredVideoList.length / currentGrid);
const currentVideoList = filteredVideoList.slice(currentPage * currentGrid, (currentPage + 1) * currentGrid);
const currentGridConfig = gridList.find(item => item.value === currentGrid);
return (
<Modal
open={state}
mask={{ closable: false }}
footer={null}
footer={[
<Button key="cancel" onClick={setFalse}>取消</Button>,
]}
width={1500}
onCancel={setFalse}
title="视频巡屏"
classNames={{ wrapper: "map_content_branch_office_camera_modal map_bi_model" }}
classNames={{ wrapper: "branch-office-camera-modal map_bi_model" }}
>
<div className="container">
<div className="parent_tabs">
{parentList.map((item, index) => (
<div className="branch-office-camera-modal__container">
<Spin spinning={dictionaryLoading}>
<div className="branch-office-camera-modal__level-tabs">
{[{ dictValue: "", dictLabel: "全部" }, ...levelList].map(item => (
<div
key={index}
className={`tab ${parentCurrentIndex === index ? "active" : ""}`}
key={item.dictValue}
className={`branch-office-camera-modal__tab ${currentLevel === item.dictValue ? "branch-office-camera-modal__tab--active" : ""}`}
onClick={() => {
onChangeParentList(index);
onChangeChildList(item.dictValue);
}}
>
{item.dictLabel}
</div>
))}
</div>
</Spin>
<div className="branch-office-camera-modal__content">
<div className="branch-office-camera-modal__grid-tabs">
{gridList.map(item => (
<div
key={item.value}
className={`branch-office-camera-modal__tab ${currentGrid === item.value ? "branch-office-camera-modal__tab--active" : ""}`}
onClick={() => {
onChangeCurrentGrid(item.value);
}}
>
{item.title}
</div>
))}
</div>
<div className="info">
<div className="child_tabs">
{childList.map((item, index) => (
<Spin spinning={props.biStatistics.getFixedCameraVideoInfoPageLoading}>
<div
key={index}
className={`tab ${childCurrentIndex === index ? "active" : ""}`}
onClick={() => {
onChangeChildList(index);
className="branch-office-camera-modal__videos"
style={{
gridTemplateColumns: `repeat(${currentGridConfig.gridColumnCount}, 1fr)`,
gridTemplateRows: `repeat(${currentGridConfig.gridRowCount}, minmax(0, 1fr))`,
}}
>
{item.title}
{currentVideoList.length
? currentVideoList.map(item => (
<VideoItem
key={item.cameraNumber}
cameraNumber={item.cameraNumber}
videoName={item.videoName}
getFixedCameraPlayUrl={props.getFixedCameraPlayUrl}
/>
))
: <div className="branch-office-camera-modal__empty">暂无视频</div>}
</div>
))}
</div>
<div className="videos">
{videoList.map((item, index) => (
<div key={index} className="video">
<AliPlayer source={item.src} height="150px" isLive />
</div>
))}
</Spin>
<div className="branch-office-camera-modal__pagination">
<Button disabled={currentPage === 0} onClick={() => setCurrentPage(currentPage - 1)}>上一组</Button>
<Button disabled={currentPage >= pageCount - 1} type="primary" onClick={() => setCurrentPage(currentPage + 1)}>下一组</Button>
</div>
</div>
</div>
@ -97,4 +135,4 @@ function Camera() {
);
}
export default Camera;
export default Connect([NS_BI_STATISTICS], true)(Camera);

View File

@ -1,12 +1,15 @@
.map_content_branch_office_camera_modal {
.container {
.parent_tabs {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 14px;
.branch-office-camera-modal {
.branch-office-camera-modal__container {
.tab {
display: flex;
gap: 20px;
.branch-office-camera-modal__level-tabs {
max-height: 500px;
overflow-y: auto;
.branch-office-camera-modal__tab {
margin-top: 5px;
background-color: #0B1544;
border: 1px solid #0c1957;
border-radius: 10px 10px 10px 2px;
@ -19,25 +22,26 @@
text-align: center;
cursor: pointer;
&.active {
&:first-child {
margin-top: 0;
}
&.branch-office-camera-modal__tab--active {
background-color: #1537CC;
border: 1px solid rgba(0, 180, 255, 0.61);
}
}
}
.info {
margin-top: 12px;
.branch-office-camera-modal__content {
flex: 1;
.branch-office-camera-modal__grid-tabs {
display: flex;
gap: 25px;
align-items: center;
gap: 10px;
.child_tabs {
width: 240px;
background-color: rgba(35, 55, 140, 0.15);
border-radius: 10px;
padding: 50px 20px;
.tab {
.branch-office-camera-modal__tab {
background-color: #0B1544;
color: #fff;
border-radius: 10px 2px;
@ -45,26 +49,22 @@
padding: 10px;
text-align: center;
cursor: pointer;
margin-top: 17px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
border: 1px solid #0c1957;
&:first-child {
margin-top: 0;
}
&.active {
&.branch-office-camera-modal__tab--active {
background-color: #1537CC;
border: 1px solid rgba(0, 180, 255, 0.61);
}
}
}
.videos {
.branch-office-camera-modal__videos {
margin-top: 20px;
height: 632px;
background-color: rgba(35, 55, 140, 0.149);
flex: 1;
border: 1px solid rgb(9, 23, 93);
border-radius: 10px;
display: grid;
@ -72,10 +72,58 @@
gap: 20px 27px;
padding: 35px 29px;
.video {
.branch-office-camera-modal__video {
border: 1px solid #294695;
border-radius: 2px;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
.branch-office-camera-modal__video-name {
height: 24px;
flex: none;
padding: 0 8px;
overflow: hidden;
color: #fff;
font-size: 12px;
line-height: 24px;
text-overflow: ellipsis;
white-space: nowrap;
}
.@{ant-prefix}-spin-nested-loading {
flex: 1;
min-height: 0;
.@{ant-prefix}-spin-container {
height: 100%;
}
}
.branch-office-camera-modal__empty {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
}
}
> .branch-office-camera-modal__empty {
min-height: 150px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
}
}
.branch-office-camera-modal__pagination {
display: flex;
justify-content: center;
gap: 12px;
margin-top: 16px;
}
}
}

View File

@ -40,6 +40,10 @@
&-close {
color: #fff;
&:hover {
color: #fff;
}
}
&-header {
@ -123,6 +127,12 @@
&-empty-description {
color: #fff !important;
}
&-btn{
&:disabled {
color: #fff !important;
}
}
}
}