diff --git a/package.json b/package.json index e2fc421..5e75836 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "crypto-js": "^4.1.1", "echarts": "^4.9.0", "echarts-for-react": "3.0.2", + "ezuikit-js": "8.0.4-beta.1", "http-proxy-middleware": "^2.0.6", "moment": "^2.29.4", "react": "^18.2.0", diff --git a/public/assets/images/blue2.png b/public/assets/images/blue2.png new file mode 100644 index 0000000..a50c903 Binary files /dev/null and b/public/assets/images/blue2.png differ diff --git a/public/assets/images/card/header.png b/public/assets/images/card/header.png deleted file mode 100644 index 04aa698..0000000 Binary files a/public/assets/images/card/header.png and /dev/null differ diff --git a/public/assets/images/card/smallCard.png b/public/assets/images/card/smallCard.png deleted file mode 100644 index 01186e3..0000000 Binary files a/public/assets/images/card/smallCard.png and /dev/null differ diff --git a/public/assets/images/card/textbg.png b/public/assets/images/card/textbg.png deleted file mode 100644 index 2856b43..0000000 Binary files a/public/assets/images/card/textbg.png and /dev/null differ diff --git a/public/assets/images/crt0.png b/public/assets/images/crt0.png new file mode 100644 index 0000000..3b17f5a Binary files /dev/null and b/public/assets/images/crt0.png differ diff --git a/public/assets/images/crt1.png b/public/assets/images/crt1.png new file mode 100644 index 0000000..99cd28e Binary files /dev/null and b/public/assets/images/crt1.png differ diff --git a/public/assets/images/crt2.png b/public/assets/images/crt2.png new file mode 100644 index 0000000..120e1c8 Binary files /dev/null and b/public/assets/images/crt2.png differ diff --git a/public/assets/images/crt3.png b/public/assets/images/crt3.png new file mode 100644 index 0000000..6cd6f7b Binary files /dev/null and b/public/assets/images/crt3.png differ diff --git a/public/assets/images/crt4.png b/public/assets/images/crt4.png new file mode 100644 index 0000000..d39f155 Binary files /dev/null and b/public/assets/images/crt4.png differ diff --git a/public/assets/images/crt5.png b/public/assets/images/crt5.png new file mode 100644 index 0000000..f600f78 Binary files /dev/null and b/public/assets/images/crt5.png differ diff --git a/public/assets/images/crt6.png b/public/assets/images/crt6.png new file mode 100644 index 0000000..d96e2e3 Binary files /dev/null and b/public/assets/images/crt6.png differ diff --git a/public/assets/images/crt7.png b/public/assets/images/crt7.png new file mode 100644 index 0000000..03106cc Binary files /dev/null and b/public/assets/images/crt7.png differ diff --git a/public/assets/images/crt8.png b/public/assets/images/crt8.png new file mode 100644 index 0000000..dec5b25 Binary files /dev/null and b/public/assets/images/crt8.png differ diff --git a/public/assets/images/dam.png b/public/assets/images/dam.png new file mode 100644 index 0000000..1efe721 Binary files /dev/null and b/public/assets/images/dam.png differ diff --git a/public/assets/images/fangda.png b/public/assets/images/fangda.png new file mode 100644 index 0000000..a45cbdf Binary files /dev/null and b/public/assets/images/fangda.png differ diff --git a/public/assets/images/head/header.png b/public/assets/images/head/header.png deleted file mode 100644 index ca5c339..0000000 Binary files a/public/assets/images/head/header.png and /dev/null differ diff --git a/public/assets/images/head/menu1.png b/public/assets/images/head/menu1.png deleted file mode 100644 index aee3475..0000000 Binary files a/public/assets/images/head/menu1.png and /dev/null differ diff --git a/public/assets/images/head/menu2.png b/public/assets/images/head/menu2.png deleted file mode 100644 index 10eb348..0000000 Binary files a/public/assets/images/head/menu2.png and /dev/null differ diff --git a/public/assets/images/head/time.png b/public/assets/images/head/time.png deleted file mode 100644 index 67aa348..0000000 Binary files a/public/assets/images/head/time.png and /dev/null differ diff --git a/public/assets/images/head/user.png b/public/assets/images/head/user.png deleted file mode 100644 index 9a3ed12..0000000 Binary files a/public/assets/images/head/user.png and /dev/null differ diff --git a/public/assets/images/jk.png b/public/assets/images/jk.png new file mode 100644 index 0000000..cf1e22b Binary files /dev/null and b/public/assets/images/jk.png differ diff --git a/public/assets/images/no-video.png b/public/assets/images/no-video.png new file mode 100644 index 0000000..a7bf846 Binary files /dev/null and b/public/assets/images/no-video.png differ diff --git a/public/assets/images/orange.png b/public/assets/images/orange.png new file mode 100644 index 0000000..fb77183 Binary files /dev/null and b/public/assets/images/orange.png differ diff --git a/public/assets/images/qiujiG.png b/public/assets/images/qiujiG.png new file mode 100644 index 0000000..9447f03 Binary files /dev/null and b/public/assets/images/qiujiG.png differ diff --git a/public/assets/images/red1.png b/public/assets/images/red1.png new file mode 100644 index 0000000..fd79cd9 Binary files /dev/null and b/public/assets/images/red1.png differ diff --git a/public/assets/images/ruler.png b/public/assets/images/ruler.png new file mode 100644 index 0000000..2cf88a1 Binary files /dev/null and b/public/assets/images/ruler.png differ diff --git a/public/assets/images/suoxiao.png b/public/assets/images/suoxiao.png new file mode 100644 index 0000000..03d4bde Binary files /dev/null and b/public/assets/images/suoxiao.png differ diff --git a/public/assets/images/waterz.png b/public/assets/images/waterz.png new file mode 100644 index 0000000..67635c2 Binary files /dev/null and b/public/assets/images/waterz.png differ diff --git a/public/assets/images/white1.png b/public/assets/images/white1.png new file mode 100644 index 0000000..b006ca3 Binary files /dev/null and b/public/assets/images/white1.png differ diff --git a/src/components/VideoCom/VideoControler/index.js b/src/components/VideoCom/VideoControler/index.js new file mode 100644 index 0000000..74f936f --- /dev/null +++ b/src/components/VideoCom/VideoControler/index.js @@ -0,0 +1,76 @@ +import React,{useEffect,useState} from 'react' +import {Slider} from "antd" +import './index.less' +export default function VideoControler({ selectItem = {},onOperation }) { + const ctrObj = { + 1: {name:"左上",value:'LEFT_UP'}, + 2: {name:"上",value:'UP'}, + 3: {name:"右上",value:'RIGHT_UP'}, + 4: {name:"左",value:'LEFT'}, + 5: {name:"重置",value:'GOTO_PRESET'}, + 6: {name:"右",value:'RIGHT'}, + 7: {name:"左下",value:'LEFT_DOWN'}, + 8: {name:"下",value:'DOWN'}, + 9: {name:"右下",value:'RIGHT_DOWN'}, + } + const ctrBtnArr = Array(9).fill(0).map((item, index) => ({ + title: ctrObj[index + 1]?.name, + value:ctrObj[index + 1]?.value + })) + const marks = { + 10: "1", + 30: "3", + 80: "8", + } + + const [selectData, setSelect] = useState() + useEffect(() => { + if (selectData) { + onOperation({...selectData,speed:selectData?.speed || 30}) + } + }, [selectData]) + + return ( +
+
云台控制
+
+ 当前设备: + {selectItem?.name} +
+ {/*
{selectItem?.name}
*/} +
+
+ {ctrBtnArr.map((item, index) => +
setSelect({...selectData,command:item?.value})}> +
+ +
+
+ )} +
+
+
+ {setSelect({...selectData,speed:e})}} + /> +
+
+
setSelect({...selectData,command:"ZOOM_OUT"})}> + +
+
setSelect({...selectData,command:"ZOOM_IN"})}> + + +
+
+
+

注:云台操作的生效根据网络状况延时约1-10秒

+
+
+ ) +} diff --git a/src/components/VideoCom/VideoControler/index.less b/src/components/VideoCom/VideoControler/index.less new file mode 100644 index 0000000..295297d --- /dev/null +++ b/src/components/VideoCom/VideoControler/index.less @@ -0,0 +1,120 @@ +.controler-wrap{ + width: 100%; + height: 265px; + padding-left: 5px; + color: #fff; + .title{ + line-height: 50px; + font-size: 25px; + font-weight: 900; + color: #fff; + } + .sub-title{ + font-size: 18px; + font-weight:800; + color: #fff; + } + .controler-box{ + display: flex; + height: 170px; + padding: 10px 20px; + background-color: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 4px; + flex-wrap: wrap; + .left{ + width: 135px; + display: flex; + flex-wrap: wrap; + .ctr-btn{ + background-color: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 5px; + margin-right: 5px; + margin-bottom: 5px; + width: 40px; + height: 40px; + display: flex; + -webkit-box-pack: center; + justify-content: center; + -webkit-box-align: center; + align-items: center; + cursor: pointer; + transition: all 0.3s; + + &:hover { + background-color: rgba(24, 144, 255, 0.3); + border-color: #1890ff; + } + + img{ + width: 15px; + height: 15px; + filter: invert(1); + } + } + } + .right{ + width:calc(100% - 135px); + .slider-wrap{ + // padding: 10px; + height: 40px; + // margin-bottom: 5px; + + .ant-slider-mark-text { + color: #fff !important ; + } + .ant-slider-rail { + background-color: rgba(255, 255, 255, 0.2); + } + .ant-slider-track { + background-color: #1890ff; + } + .ant-slider-handle { + border-color: #1890ff; + } + + } + .zoom-wrap{ + display: flex; + -webkit-box-pack: justify; + // justify-content: space-between; + width: 100%; + margin-top: -3px; + .zoom-btn{ + display: flex; + justify-content: center; + align-items: center; + background-color: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 5px; + margin-right: 5px; + margin-bottom: 5px; + width: 100%; + height: 40px; + -webkit-box-pack: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s; + + &:hover { + background-color: rgba(24, 144, 255, 0.3); + border-color: #1890ff; + } + + img{ + width: 25px; + height: 25px; + filter: invert(1); + } + } + } + } + .tips{ + margin-top: 0; + margin-bottom: 1rem; + color: rgba(255, 255, 255); + font-size: 12px; + } + } +} diff --git a/src/components/VideoCom/videoPlary.js b/src/components/VideoCom/videoPlary.js new file mode 100644 index 0000000..de3a642 --- /dev/null +++ b/src/components/VideoCom/videoPlary.js @@ -0,0 +1,243 @@ +import { FC, useEffect, useRef, useState } from 'react' +// import styles from './index.module.less' +import { message, Spin } from "antd"; +import EZUIKit from 'ezuikit-js'; +import moment from 'moment'; + +/** + * 海康视频H5插件视频播放 + * @author QC班长 + * @since 20230727 + */ + +//wsUrl [{src:''}] playerId:string size:分屏播放1代表1*1,2代表2*2 最大4*4 +const HFivePlayer = ({ wsUrl, playerID, size }) => { + const [isLoading, setIsLoading] = useState(false) + const player = useRef({})//播放器 + const playerYsy = useRef({}) + const parentRef = useRef(null);//为了设置播放器宽高 + const [width, setWidth] = useState(null); + const [height, setHeight] = useState(null); + const [type, setType] = useState(false); + + + + + const initVideo = () => { + const hasReplayRange = wsUrl?.beginTime && wsUrl?.endTime && wsUrl?.indexCode; + const beginStr = hasReplayRange ? moment(wsUrl.beginTime).format('YYYYMMDDHHmmss') : ''; + const endStr = hasReplayRange ? moment(wsUrl.endTime).format('YYYYMMDDHHmmss') : ''; + const ezUrl = hasReplayRange + ? `ezopen://open.ys7.com/${wsUrl.indexCode}/1.rec?begin=${beginStr}&end=${endStr}` + : `ezopen://open.ys7.com/${wsUrl.indexCode}/1.live`; + playerYsy.current = new EZUIKit.EZUIKitPlayer({ + id: 'player' + playerID, // 视频容器ID + accessToken: wsUrl.src, + url: ezUrl, + // plugin: ["talk"], // 加载插件,talk-对讲 + width: parentRef.current?.offsetWidth, + height: parentRef.current?.offsetHeight, + template: 'simple', + footer: ['hd', 'fullScreen'], + }); + playerYsy.current?.play(); + } + + /** + * 初始化播放器 + */ + const initPlayer = () => { + console.log(document.getElementById('player' + playerID),'play.current'); + // return + player.current = new window.JSPlugin({ + // 需要英文字母开头 必填 + szId: 'player' + playerID, + // 必填,引用H5player.min.js的js相对路径 + szBasePath: '/h5Player/', + // 当容器div#play_window有固定宽高时,可不传iWidth和iHeight,窗口大小将自适应容器宽高 + iWidth: parentRef.current?.offsetWidth, + iHeight: parentRef.current?.offsetHeight, + bSupporDoubleClickFull: true, + // 分屏播放,默认最大分屏4*4 + iMaxSplit: 3, + // iCurrentSplit: 1, + // 样式 + oStyle: { + border: 'rgb(53 116 237)', + borderSelect: '#1d325d', + background: '#1d325d', + } + }) + // 设置播放容器的宽高并监听窗口大小变化 + //初始化插件 + initPlugin() + } + + /** + * 事件初始化 + */ + const initPlugin = () => { + + player.current.JS_SetWindowControlCallback({ + windowEventSelect(iWindIndex) { + // 插件选中窗口回调 + console.log('windowSelect callback: ', iWindIndex) + //点击视频全屏显示 + // wholeFullScreen() + }, + pluginErrorHandler(iWindIndex, iErrorCode, oError) { + // 插件错误回调 + // console.error(`window-${iWindIndex}, errorCode: ${iErrorCode}`, oError) + // message.error('播放失败1:' + VideoPlayerException[iErrorCode]) + setIsLoading(false) + //重新播放 + // initPlayer() + }, + windowEventOver(iWindIndex) { + // 鼠标移过回调 + // console.log('鼠标移过回调', iWindIndex) + }, + windowEventOut(iWindIndex) { + // 鼠标移出回调 + // console.log('鼠标移出回调', iWindIndex) + }, + windowFullScreenChange(bFull) { + // 全屏切换回调 + // console.log('全屏切换回调', bFull) + }, + firstFrameDisplay(iWndIndex, iWidth, iHeight) { + // 首帧显示回调 + // console.log('首帧显示回调', iWndIndex, iWidth, iHeight) + //停止加载 + setIsLoading(false) + }, + performanceLack(iWndIndex) { + // 性能不足回调 + console.log('性能不足回调', iWndIndex) + setIsLoading(false) + + } + }) + console.log(player, '成功'); + //播放 + play() + } + + const handleWindowResize = () => { + console.log(111111); + if (parentRef.current) { + setWidth(parentRef.current?.offsetWidth) + setHeight(parentRef.current?.offsetHeight) + } + console.log(parentRef.current?.offsetHeight, parentRef.current?.offsetWidth); + } + /** + * 播放 + */ + const play = () => { + console.log(wsUrl?.src, '6543'); + // if(!wsUrl?.src) return + if (wsUrl?.src) { + setIsLoading(true) //开始加载 + let preUrl = wsUrl?.src // 播放地址 + // 支持回放时间范围(海康取流地址若后端支持时间参数则追加) + if (wsUrl?.beginTime && wsUrl?.endTime) { + const begin = moment(wsUrl.beginTime).subtract(1,'hours').format('YYYY-MM-DD HH:mm:ss'); + const end = moment(wsUrl.endTime).format('YYYY-MM-DD HH:mm:ss'); + const sep = preUrl.includes('?') ? '&' : '?'; + preUrl = `${preUrl}${sep}beginTime=${begin}&endTime=${end}`; + } + console.log(preUrl); + const param = { + playURL: preUrl, + // 1:高级模式 0:普通模式,高级模式支持所有 + mode: 0 + } + // 当前播放窗口下标 + // let index = 0 + player.current.JS_Play(preUrl, param, 0).then(() => { + // 播放成功回调 + // console.log('播放成功') + }, (err) => { + // console.log('播放失败') + // console.info('JS_Play failed:', err) + setIsLoading(false) + message.error('视频离线,播放失败') + } + ) + + } + } + + useEffect(() => { + if (wsUrl) { + if (wsUrl?.relType == 'ysy') { + initVideo() + } else { + initPlayer() + } + + } + + }, [wsUrl]) + useEffect(() => { + handleWindowResize() + }, [size]) + + const styles = {} + return ( +
+ +
playerYsy.current.fullScreen()}> +
+
+
+
+ +
+ ) +} +export default HFivePlayer + +/** + * 海康威视视频播放异常错误代码常量 + */ +export const VideoPlayerException = { + '0x12f900001': '接口调用参数错误', + '0x12f900002': '不在播放状态', + '0x12f900003': '仅回放支持该功能', + '0x12f900004': '普通模式不支持该功能', + '0x12f900005': '高级模式不支持该功能', + '0x12f900006': '高级模式的解码库加载失败', + '0x12f900008': 'url格式错误', + '0x12f900009': '取流超时错误', + '0x12f900010': '设置或者是获取音量失败,因为没有开启音频的窗口', + '0x12f900011': '设置的音量不在1-100范围', + '0x12f910000': 'websocket连接失败,请检查网络是否通畅,URL是否正确', + '0x12f910010': '取流失败', + '0x12f910011': '流中断,电脑配置过低,程序卡主线程都可能导致流中断', + '0x12f910014': '没有音频数据', + '0x12f910015': '未找到对应websocket,取流套接字被动关闭的报错', + '0x12f910016': 'websocket不在连接状态', + '0x12f910017': '不支持智能信息展示', + '0x12f910018': 'websocket长时间未收到message', + '0x12f910019': 'wss连接失败,原因:端口尚未开通、证书未安装、证书不安全', + '0x12f910020': '单帧回放时不能暂停', + '0x12f910021': '已是最大倍速', + '0x12f910022': '已是最小倍速', + '0x12f910023': 'ws/wss连接超时,默认6s超时时间,原因:网络异常,网络不通', + '0x12f910026': 'jsdecoder1.0解码报错视频编码格式不支持', + '0x12f910027': '后端取流超时,主动关闭连接(设备突然离线或重启,网络传输超时20s)', + '0x12f910028': '设置的缓冲区大小无效,大小0-510241024,不在该范围的报错', + '0x12f910029': '普通模式的报错,码流异常导致黑屏,尝试重新取流', + '0x12f910031': '普通模式下播放卡主会出现', + '0x12f910032': '码流编码格式普通模式下不支持,可切换高级模式尝试播放', + '0x12f920015': '未调用停止录像,再次调用开始录像', + '0x12f920016': '未开启录像调用停止录像接口错误', + '0x12f920017': '紧急录像目标格式不支持,非ps/mp4', + '0x12f920018': '紧急录像文件名为null', + '0x12f930010': '内存不足', + '0x12f930011': '首帧显示之前无法抓图,请稍后重试', + '0x12f950000': '采集音频失败,可能是在非https域下使用对讲导致', + '0x12f950001': '对讲不支持这种音频编码格式', +} \ No newline at end of file diff --git a/src/service/apiurl.js b/src/service/apiurl.js index 5eec320..c847038 100644 --- a/src/service/apiurl.js +++ b/src/service/apiurl.js @@ -10,6 +10,20 @@ const apiurl = { station: { rainlist: service + '/real/rain/list',//雨量站 reservoirlist: service + '/reservoir/water/listV2',//水库水位站 + flowlist: service + '/stFlowR/list', + }, + + spjk: { + page1: service + "/iscaiEvent/page", + page: service + "/stImgWarnR/page", + list: service + "/attCctvBase/list", + controler: service + "/attCctvBase/control", + treeList: service + '/cctvBMenu/list', + treeListById: service + '/xfCctvB/listByMenuId/', + srcData: service + '/attCctvBase/preview/', + videoBystcd: service + '/stbprp/cctv/listByStcd/', + videoList: service + '/attCctvBase/list', + ysyToken: service + '/ysy/getAccessToken' }, sq: { qfg: { @@ -36,7 +50,13 @@ const apiurl = { nearbyHistory:service + '/attResBase/maxRain' //获取历史近几小时数据 }, reservoir: { - list:service + '/screen/monitoring/rsvr' + list: service + '/screen/monitoring/rsvr', + monitor: service + '/reservoir/water/monitor/data', + detail:service + '/reservoir/water/detail' + }, + flow: { + history: service + '/stFlowR/upperDataCheck', + max:service + '/stFlowR/lowerDataCheck' } }, qzq: { diff --git a/src/service/station.js b/src/service/station.js index 6bbe8bf..6c6e20c 100644 --- a/src/service/station.js +++ b/src/service/station.js @@ -33,5 +33,22 @@ export async function reservoirlist(params) { lttd : Number(i.lttd)-1.2, })) + return mapData||[]; +} + +// 流量站 +export async function flowlist(params) { + const {data, code, msg} = await httpget(apiurl.station.flowlist, params) || {}; + if (code !== 200) { + message.error(msg || '请求失败'); + return []; + } + + const mapData = data.map(i=>({ + ...i, + lgtd : Number(i.lgtd)-1, + lttd : Number(i.lttd)-1.2, + })) + return mapData||[]; } \ No newline at end of file diff --git a/src/views/Home/components/Business/SiQuan/components/AllWeatherControl/index.js b/src/views/Home/components/Business/SiQuan/components/AllWeatherControl/index.js index 25293b9..1b88ed4 100644 --- a/src/views/Home/components/Business/SiQuan/components/AllWeatherControl/index.js +++ b/src/views/Home/components/Business/SiQuan/components/AllWeatherControl/index.js @@ -8,12 +8,28 @@ import apiurl from '@/service/apiurl'; import { httpget, httppost } from '@/utils/request'; import './index.less'; import RightPanel from '../ModalComponents/AllWeatherModal/RainMonitor/RightPanel'; +import ReservoirPanel from '../ModalComponents/AllWeatherModal/ReservoirPanel'; +import VideoList from '../ModalComponents/VideoList' +import UAVModal from '../ModalComponents/UAVModal'; const AllWeatherControl = () => { const [reservoirItem, setReservoirItem] = useState({}) const [rainList, setRainList] = useState([]) const [detailVisible, setDetailVisible] = useState(false) + const [reservoirVisible, setReservoirVisible] = useState(false) + const [videoOpen, setVideoOpen] = useState(false) const [selectedStcd, setSelectedStcd] = useState(null) + + // UAV Modal States + const [uavModalVisible, setUavModalVisible] = useState(false); + const [uavActiveTab, setUavActiveTab] = useState('1'); + + const uavTabs = [ + { label: '直播画面', value: '1' }, + { label: '飞行任务', value: '2' }, + { label: '历史记录', value: '3' } + ]; + const rainColumns = [ { title: '站名', @@ -46,10 +62,10 @@ const AllWeatherControl = () => { // Reservoir Data const reservoirData = [ - { label: '主坝坝前', value: reservoirItem?.rz, unit: 'm', upArrow: reservoirItem?.status > 0, underline: true,downArrow:reservoirItem?.status < 0 }, + { label: '主坝坝前', value: reservoirItem?.rz, unit: 'm', upArrow: reservoirItem?.status > 0, underline: true,downArrow:reservoirItem?.status < 0, clickable: true }, { label: '汛限水位', value: reservoirItem?.flLowLimLev, unit: 'm' }, { label: '距汛限', value: reservoirItem?.gapFlLowLimLev, unit: 'm', isNegative: reservoirItem?.gapFlLowLimLev < 0 }, - { label: '副坝坝前', value: reservoirItem?.rz, unit: 'm' }, + { label: '副坝坝前', value: reservoirItem?.rz, unit: 'm', clickable: true,underline: true }, { label: '当前库容', value: reservoirItem?.nowCap, unit: '万m³' }, { label: '有效库容', value: reservoirItem?.effectiveCap, unit: '万m³' }, ]; @@ -80,6 +96,11 @@ const AllWeatherControl = () => { getReservoir() }, []) + const openUavModal = (key) => { + setUavActiveTab(key); + setUavModalVisible(true); + } + return (
{/* 雨情 Section */} @@ -111,7 +132,6 @@ const AllWeatherControl = () => { visible={detailVisible} onClose={() => setDetailVisible(false)} width={'70%'} - bodyStyle={{ background: 'transparent', padding: 12 }} > @@ -130,7 +150,12 @@ const AllWeatherControl = () => {
{ + if (item.clickable) { + setReservoirVisible(true) + } + }} >
{item.value} @@ -142,6 +167,14 @@ const AllWeatherControl = () => {
))}
+ setReservoirVisible(false)} + width={'90%'} + > + +
{/* 无人机 Section */} @@ -151,7 +184,7 @@ const AllWeatherControl = () => { arrow 无人机 - 视频墙 + setVideoOpen(true)}>视频墙
{ style={{ backgroundImage: `url(${wrj})` }} />
-
直播画面
-
巡查任务
+
openUavModal('1')}>直播画面
+
openUavModal('2')}>巡查任务
+ setVideoOpen(false)} + width={'80%'} + > + + + setUavModalVisible(false)} + width={'70%'} + tabs={uavTabs} + activeTab={uavActiveTab} + onTabChange={setUavActiveTab} + > + +
); diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/FlowPanel/RightPanel.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/FlowPanel/RightPanel.js new file mode 100644 index 0000000..64af747 --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/FlowPanel/RightPanel.js @@ -0,0 +1,135 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { DatePicker, Button, Table } from 'antd'; +import { SearchOutlined } from '@ant-design/icons'; +import ReactEcharts from 'echarts-for-react'; +import apiurl from '@/service/apiurl'; +import { httppost } from '@/utils/request'; +import moment from 'moment'; +import flowOption from './chartOption'; +import './index.less'; + +const { RangePicker } = DatePicker; + +export default function RightPanel({ stcd, cleanMode = false }) { + const defaultRange = [ + moment().subtract(7, 'days').set({ minute: 0, second: 0 }), + moment().set({ minute: 0, second: 0 }), + ]; + const [dates, setDates] = useState(defaultRange); + const [historyList, setHistoryList] = useState([]); + const [maxInfo, setMaxInfo] = useState({}) + const [loading, setLoading] = useState(false); + + const columns = [ + { + title: '时间', dataIndex: 'tm', key: 'tm', align: 'center', width: 160, + render: (text) => moment(text).format('YYYY-MM-DD HH:mm') + }, + { + title: '瞬时流量(m³/s)', dataIndex: 'q', key: 'q', align: 'center', + }, + { + title: '累计水量(万m³)', dataIndex: 'totalWater', key: 'totalWater', align: 'center', + }, + ]; + + const getHistoryList = async (params) => { + setLoading(true); + try { + const { data, code } = await httppost(apiurl.sq.qth.flow.history, params); + if (code == 200) { + setHistoryList(data); + setLoading(false); + } + } catch (error) { + console.log(error); + } finally { + setLoading(false); + } + }; + + const getHistoryMax = async (params) => { + try { + const { data, code } = await httppost(apiurl.sq.qth.flow.max, params); + if (code == 200) { + setMaxInfo(data); + } + } catch (error) { + console.log(error); + } + }; + + const handleSearch = () => { + if (!stcd) return; + const params = { + stcd, + dateTimeRangeSo: { + start:dates && dates[0] ? dates[0].format('YYYY-MM-DD HH:mm:ss'):undefined, + end:dates && dates[1] ?dates[1].format('YYYY-MM-DD HH:mm:ss'):undefined + } + }; + getHistoryList(params); + getHistoryMax(params) + }; + + useEffect(() => { + if (stcd) { + handleSearch(); + } + }, [stcd]); + + const option = useMemo(() => flowOption({ data: historyList }), [historyList]); + + return ( +
+
+
数据查询
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
最大瞬时流量(m³/s)
+
累计水量(万m³)
+
+ {maxInfo?.maxQ ?? '-'} + {maxInfo?.maxQTm &&({moment(maxInfo?.maxQTm).format('YYYY-MM-DD HH:mm:ss')})} +
+
{maxInfo?.totalWater ?? '-'}
+
+ + ); +} diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/FlowPanel/chartOption.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/FlowPanel/chartOption.js new file mode 100644 index 0000000..9b8052c --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/FlowPanel/chartOption.js @@ -0,0 +1,76 @@ +import moment from "moment"; + +export default function flowOption({ data }) { + const qList = data.map(o => o.q ?? 0); + const wList = data.map(o => o.totalWater ?? 0); + const tms = data?.map(o=> o.tm ? moment(o.tm).format('YYYY-MM-DD HH:mm'):null) + const maxQ = Math.ceil(Math.max(...qList, 0)) + const minQ = Math.floor(Math.min(...qList, 0)) + const maxW = Math.ceil(Math.max(...wList, 0)) + const minW = Math.floor(Math.min(...wList, 0)) + + + return { + tooltip: { trigger: 'axis' }, + legend: { + show: true, + textStyle: { color: '#fff' }, + data: ['累计水量', '瞬时流量'] + }, + grid: { + top: '15%', left: '2%', right: '2%', bottom: '5%', containLabel: true + }, + xAxis: { + type: 'category', + data: tms, + inverse:true, + axisLabel: { color: '#fff' }, + axisLine: { lineStyle: { color: 'rgba(255,255,255,0.5)' } } + }, + yAxis: [ + { + type: 'value', + name: '累计水量(万m³)', + position: 'left', + axisLabel: { color: '#fff' }, + nameTextStyle: { color: '#fff' }, + splitLine: { show: false }, + min: minW, + max:maxW + }, + { + type: 'value', + name: '瞬时流量(m³/s)', + position: 'right', + axisLabel: { color: '#fff' }, + nameTextStyle: { color: '#fff' }, + splitLine: { + show: true, + lineStyle: { color: 'rgba(255,255,255,0.2)', type: 'dashed' } + }, + min: minQ, + max:maxQ + } + ], + series: [ + { + name: '累计水量', + type: 'line', + yAxisIndex: 0, + data: wList, + itemStyle: { color: '#1890ff' }, // Blue + showSymbol: false, + smooth: true + }, + { + name: '瞬时流量', + type: 'line', + yAxisIndex: 1, + data: qList, + itemStyle: { color: '#52c41a' }, // Green + showSymbol: false, + smooth: true + } + ] + }; +} diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/FlowPanel/index.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/FlowPanel/index.js new file mode 100644 index 0000000..a67cd58 --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/FlowPanel/index.js @@ -0,0 +1,115 @@ +import React, { useEffect, useState } from 'react'; +import { Select } from 'antd'; +import { flowlist } from '@/service/station'; +import RightPanel from './RightPanel'; +import moment from 'moment'; +import './index.less'; + +const FlowPanel = ({ stcd, cleanMode = false }) => { + const [selectList, setSelectList] = useState([]); + const [selected, setSelected] = useState(''); + const [detail, setDetail] = useState({}); + + // 获取站点 + const getStationList = async () => { + try { + // Reusing reservoir list for now + const data = await flowlist({}); + const formattedData = data.map(item => ({ + label: item.stnm, + value: item.stcd, + ...item + })); + setSelectList(formattedData); + + let targetStcd = selected || stcd; + if (targetStcd) { + const item = formattedData.find(i => i.stcd === targetStcd); + if (item) { + setSelected(targetStcd); + setDetail(item); + } else if (formattedData.length > 0) { + setSelected(formattedData[0].stcd); + setDetail(formattedData[0]); + } + } else if (formattedData.length > 0) { + setSelected(formattedData[0].stcd); + setDetail(formattedData[0]); + } + } catch (error) { + console.log(error); + } + }; + + useEffect(() => { + if (stcd) { + setSelected(stcd); + } + }, [stcd]); + + const handleStationChange = (val) => { + setSelected(val); + const item = selectList.find(i => i.value === val); + if (item) { + setDetail(item); + } + }; + + useEffect(() => { + getStationList(); + }, []); + + const { + q,change24Q,avg24Q,total24V + } = detail || {} + const stats = [ + { label: '站点类型', value: '-', unit: '' }, + { label: '实时瞬时流量', value: q ?? '-', unit: 'm³/s' }, + { label: '近24h变幅', value: change24Q ?? '-', unit: 'm³/s' }, + { label: '近24h平均流量', value: avg24Q ?? '-', unit: 'm³/s' }, + { label: '近24h累计水量', value: total24V ?? '-', unit: '万m³' }, + ]; + + return ( +
+
+
+
+
实时流量
+ {!cleanMode && ( +
+ 站点: +
+ +
+
+ +
+
+ + +
+ {bottomStats.slice(0, 7).map((i, idx) =>
{i.label}
)} + {bottomStats.slice(0, 7).map((i, idx) => ( +
+ {i.value} +
+ ))} + {bottomStats.slice(7, 14).map((i, idx) =>
{i.label}
)} + {bottomStats.slice(7, 14).map((i, idx) => ( +
+ {i.value} +
+ ))} +
+ + ); +} diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/ReservoirPanel/drpOption.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/ReservoirPanel/drpOption.js new file mode 100644 index 0000000..484ebaa --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/ReservoirPanel/drpOption.js @@ -0,0 +1,257 @@ +export default function drpOption ({ + data, + afsltdz, + flLowLimLev, + desFloodLev, + calFloodLev +}) { + + // flLowLimLev 汛限水位 desFloodLev 设计水位 calFloodLev校核水位 + const maxVal = Math.max(...data.map(obj => obj.drp)) + // const minVal = Math.min(...data.map(obj => obj.drp)) + const maxSw = Math.ceil(Math.max(...data.map(obj => obj.rz),flLowLimLev,desFloodLev,calFloodLev)) + const minSw = Math.floor(Math.min(...data.map(obj => obj.rz),flLowLimLev,desFloodLev,calFloodLev)) + const maxKr = Math.ceil(Math.max(...data.map(obj => obj.w))) + const minKr = Math.floor(Math.min(...data.map(obj => obj.w))) + return { + tooltip: { + trigger: 'axis' + }, + grid: [ + { + top: '15%', + left: '10%', + right: '8%', + width: '80%', + height: '35%' + }, + { + bottom: '5%', + left: '10%', + right: '8%', + width: '80%', + height: '35%' + } + ], + legend: { + // 显示图例 + show: true, + textStyle: { color: '#fff' }, + // 图例的位置 + // data: ['汛限水位', '设计水位', '校核水位', "降雨量", "水位", "库容"], + data: ['校核水位', '设计水位', '汛限水位', '降雨量', '水位', '库容'] + }, + xAxis: [ + { + gridIndex: 0, + type: 'category', + data: data.map(o => o.tm).reverse(), + + splitLine: { + show: false + }, + axisLabel: { + color: '#fff', + fontSize: 12, + show: false + }, + axisLine: { + lineStyle: { + color: 'rgba(255,255,255,0.5)', + } + }, + axisTick: { + show: false + } + }, + { + gridIndex: 1, + type: 'category', + data: data.map(o => o.tm), + inverse: true, + splitLine: { + show: false + }, + axisLabel: { + color: '#fff', + fontSize: 12, + formatter: val => val.substr('2020-'.length, 11) + }, + axisLine: { + lineStyle: { + color: 'rgba(255,255,255,0.5)', + } + }, + axisTick: { + show: false + } + } + ], + yAxis: [ + { + inverse: true, + gridIndex: 0, + type: 'value', + position: 'left', + name: '降雨量(mm)', + nameLocation: 'start', + nameTextStyle: { + color: '#fff' + }, + axisLabel: { + color: '#fff', + fontSize: 12 + }, + splitLine: { + show: true, + lineStyle: { + color: 'rgba(255,255,255,0.5)', + type: 'dotted' + } + }, + axisLine: { + show: false + }, + axisTick: { + show: false + }, + min: 0, + max: maxVal + }, + { + gridIndex: 1, + type: 'value', + position: 'left', + name: '水位(m)', + nameTextStyle: { + color: '#fff' + }, + splitLine: { + show: true, + lineStyle: { + color: 'rgba(255,255,255,0.5)', + type: 'dotted' + } + }, + axisLabel: { + color: '#fff', + fontSize: 12 + }, + axisLine: { + show: false + }, + axisTick: { + show: false + }, + min: minSw, + max: maxSw + }, + { + gridIndex: 1, + type: 'value', + position: 'right', + name: '库容(万m³)', + nameTextStyle: { + color: '#fff' + }, + splitLine: { + show: false, + lineStyle: { + color: '#07a6ff', + width: 0.25, + type: 'dotted' + } + }, + axisLabel: { + color: '#fff', + fontSize: 12 + }, + axisLine: { + show: false + }, + axisTick: { + show: false + }, + min: minKr, + max: maxKr + } + ], + series: [ + { + xAxisIndex: 1, + yAxisIndex: 1, + name: '校核水位', + type: 'line', + color: '#D9001B', + lineStyle: { + type: 'dashed' + }, + data: data.map(o => calFloodLev), + symbol: 'none' // 设置标记点为'none',即去掉圆点 + }, + { + xAxisIndex: 1, + yAxisIndex: 1, + name: '设计水位', + type: 'line', + color: '#F59A23', + data: data.map(o => desFloodLev), + lineStyle: { + type: 'dashed' + }, + symbol: 'none' // 设置标记点为'none',即去掉圆点 + }, + { + xAxisIndex: 1, + yAxisIndex: 1, + name: '汛限水位', + type: 'line', + color: '#FDDC9F', + data: data.map(o => { + return flLowLimLev + }), + lineStyle: { + type: 'dashed' + }, + symbol: 'none' // 设置标记点为'none',即去掉圆点 + }, + { + name: '降雨量', + type: 'bar', + barWidth: '60%', + data: data.map(o => o.drp).reverse(), + itemStyle: { + color: '#007AFD' + }, + label: { + show: false + } + }, + { + xAxisIndex: 1, + yAxisIndex: 1, + name: '水位', + type: 'line', + symbol: 'none', + color: '#0AE0B5', + label: { + show: false + }, + data: data.map(o => o.rz ? o.rz.toFixed(2):null ) + }, + { + xAxisIndex: 1, + yAxisIndex: 2, + name: '库容', + type: 'line', + color: '#007AFD', + symbol: 'none', + showSymbol: false, + label: { + show: false + }, + data: data.map(o => o.w) + } + ] + } +} diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/ReservoirPanel/index.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/ReservoirPanel/index.js new file mode 100644 index 0000000..ffc82a7 --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/ReservoirPanel/index.js @@ -0,0 +1,133 @@ +import React, { useEffect, useState } from 'react'; +import { Select } from 'antd'; +import apiurl from '@/service/apiurl'; +import { httpget, httppost } from '@/utils/request'; +import { reservoirlist } from '@/service/station'; +import RightPanel from './RightPanel'; +import moment from 'moment'; +import MyImg from './myImg'; +import './index.less'; + +const ReservoirPanel = ({ stcd, cleanMode = false }) => { + const [selectList, setSelectList] = useState([]); + const [selected, setSelected] = useState(''); + const [reservoirDetail, setReservoirDetail] = useState({}); + + // 获取水库站点 + const getReservoirList = async () => { + try { + const data = await reservoirlist({}); + const formattedData = data.map(item => ({ + label: item.stnm, + value: item.stcd, + ...item + })); + setSelectList(formattedData); + + // Determine which station to select + let targetStcd = selected || stcd; + + if (targetStcd) { + const item = formattedData.find(i => i.stcd === targetStcd); + if (item) { + setSelected(targetStcd); + setReservoirDetail(item); + } else if (formattedData.length > 0) { + // Fallback if target not found + setSelected(formattedData[0].stcd); + setReservoirDetail(formattedData[0]); + } + } else if (formattedData.length > 0) { + setSelected(formattedData[0].stcd); + setReservoirDetail(formattedData[0]); + } + } catch (error) { + console.log(error); + } + }; + + useEffect(() => { + if (stcd) { + setSelected(stcd); + } + }, [stcd]); + + const handleStationChange = (val) => { + setSelected(val); + const item = selectList.find(i => i.value === val); + if (item) { + setReservoirDetail(item); + } + }; + + useEffect(() => { + getReservoirList(); + }, []); + + // Data items for the left list + const dataItems = [ + { label: '监测水位', value: reservoirDetail?.rz ??'-', unit: 'm', isPrimary: true }, + { label: '校核洪水位', value: reservoirDetail?.calFloodLev ?? '-', unit: 'm', diff: (reservoirDetail?.calFloodLev && reservoirDetail?.rz) ? (reservoirDetail.rz - reservoirDetail.calFloodLev).toFixed(2) : null }, + { label: '设计洪水位', value: reservoirDetail?.desFloodLev ?? '-', unit: 'm', diff: (reservoirDetail?.desFloodLev && reservoirDetail?.rz) ? (reservoirDetail.rz - reservoirDetail.desFloodLev).toFixed(2) : null }, + { label: '汛限水位', value: reservoirDetail?.flLowLimLev ?? '-', unit: 'm', diff: (reservoirDetail?.flLowLimLev && reservoirDetail?.rz) ? (reservoirDetail.rz - reservoirDetail.flLowLimLev).toFixed(2) : null }, + { label: '死水位', value: reservoirDetail?.deadLev ?? '-', unit: 'm', diff: (reservoirDetail?.deadLev && reservoirDetail?.rz) ? (reservoirDetail.rz - reservoirDetail.deadLev ).toFixed(2) : null }, + { label: '坝顶高程', value: reservoirDetail?.crestElev ?? '-', unit: 'm' }, + { label: '水库当前库容', value: reservoirDetail?.nowCap ?? '-', unit: '万m³' }, + { label: '兴利库容', value: reservoirDetail?.benResCap ?? '-', unit: '万m³' }, + ]; + + return ( +
+
+ {/* Left Side: Stats List */} +
+
+
实时水位
+ {!cleanMode && ( +
+ 站点: +
- -
- -
- - ); -}; - -const FlowPanel = () => { - const columns = [ - { title: '时间', dataIndex: 'tm', key: 'tm', width: 140, align: 'center' }, - { title: '入库(m³/s)', dataIndex: 'qin', key: 'qin', align: 'center' }, - { title: '出库(m³/s)', dataIndex: 'qout', key: 'qout', align: 'center' }, - ]; - const data = []; - const option = { - title: { text: '出入库流量', left: 'center', textStyle: { color: '#fff' } }, - grid: { left: '8%', right: '4%', bottom: '10%', top: '15%' }, - tooltip: { trigger: 'axis' }, - legend: { data: ['入库', '出库'], textStyle: { color: '#fff' } }, - xAxis: { type: 'category', data: data.map(i => i.tm), axisLabel: { color: '#fff' } }, - yAxis: { type: 'value', axisLabel: { color: '#fff' }, splitLine: { show: true, lineStyle: { color: 'rgba(255,255,255,0.2)' } } }, - series: [ - { name: '入库', type: 'line', data: data.map(i => i.qin), lineStyle: { color: '#00a0e9' } }, - { name: '出库', type: 'line', data: data.map(i => i.qout), lineStyle: { color: '#3b7cff' } }, - ], - }; - return ( -
-
-
- -
- -
- - ); -}; - const SafetyPanel = () => { return (
内容待接入
diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/UAVModal/FlightTasks.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/UAVModal/FlightTasks.js new file mode 100644 index 0000000..ad635bc --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/UAVModal/FlightTasks.js @@ -0,0 +1,55 @@ +import React from 'react'; +import { Table } from 'antd'; + +export default function FlightTasks() { + const columns = [ + { + title: '序号', + dataIndex: 'index', + key: 'index', + align: 'center', + width: 80, + render: (text, record, index) => index + 1 + }, + { + title: '名称', + dataIndex: 'name', + key: 'name', + align: 'center', + }, + { + title: '起飞', + key: 'takeoff', + align: 'center', + width: 120, + render: () => 起飞 + }, + { + title: '航线', + key: 'route', + align: 'center', + width: 120, + render: () => 显示 + } + ]; + + const data = [ + { id: 1, name: '视频任务' }, + { id: 2, name: '拍摄' }, + { id: 3, name: '坝体巡检' }, + { id: 4, name: '溢洪道下泄巡查' }, + { id: 5, name: '界桩巡检01' }, + ]; + + return ( +
+
+ + ); +} diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/UAVModal/History.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/UAVModal/History.js new file mode 100644 index 0000000..b61962a --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/UAVModal/History.js @@ -0,0 +1,75 @@ +import React, { useState } from 'react'; +import { Table, DatePicker, Button } from 'antd'; +import moment from 'moment'; + +const { RangePicker } = DatePicker; + +export default function History() { + const [dates, setDates] = useState([]); + + const columns = [ + { + title: '序号', + dataIndex: 'index', + key: 'index', + align: 'center', + width: 80, + render: (text, record, index) => index + 1 + }, + { + title: '名称', + dataIndex: 'taskName', + key: 'taskName', + align: 'center', + }, + { + title: '开始时间', + dataIndex: 'startTime', + key: 'startTime', + align: 'center', + }, + { + title: '结束时间', + dataIndex: 'endTime', + key: 'endTime', + align: 'center', + }, + { + title: '操作', + key: 'action', + align: 'center', + width: 100, + render: () => 回放 + } + ]; + + const data = [ + { id: 1, taskName: '视频任务', startTime: '2025-12-15 09:31:23', endTime: '2025-12-15 09:51:23' }, + { id: 2, taskName: '拍摄', startTime: '2025-12-15 09:31:23', endTime: '2025-12-15 09:51:23' }, + { id: 3, taskName: '坝体巡检', startTime: '2025-12-15 09:31:23', endTime: '2025-12-15 09:51:23' }, + { id: 4, taskName: '溢洪道下泄巡查', startTime: '2025-12-15 09:31:23', endTime: '2025-12-15 09:51:23' }, + { id: 5, taskName: '界桩巡检01', startTime: '2025-12-15 09:31:23', endTime: '2025-12-15 09:51:23' }, + ]; + + return ( +
+
+ 任务时间: + + +
+
+ + ); +} diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/UAVModal/index.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/UAVModal/index.js new file mode 100644 index 0000000..7b370f8 --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/UAVModal/index.js @@ -0,0 +1,30 @@ +import React from 'react'; +import './index.less'; +import FlightTasks from './FlightTasks'; +import History from './History'; +import VideoList from '../VideoList'; + +export default function UAVModal({ activeKey }) { + const renderContent = () => { + switch (activeKey) { + case '1': + return ( +
+ 暂无直播画面 +
+ ); + case '2': + return ; + case '3': + return ; + default: + return null; + } + }; + + return ( +
+ {renderContent()} +
+ ); +} diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/UAVModal/index.less b/src/views/Home/components/Business/SiQuan/components/ModalComponents/UAVModal/index.less new file mode 100644 index 0000000..faae76c --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/UAVModal/index.less @@ -0,0 +1,25 @@ +.uav-modal-container { + height: 100%; + width: 100%; + color: #fff; + + // Common Table Styles for Tabs + .tab-content-wrapper { + height: 100%; + padding: 10px; + // background: rgba(255, 255, 255, 0.05); + // border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 4px; + + .action-link { + color: #1890ff; + cursor: pointer; + margin-right: 10px; + + &:hover { + color: #40a9ff; + text-decoration: underline; + } + } + } +} diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/http.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/http.js new file mode 100644 index 0000000..b76ec48 --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/http.js @@ -0,0 +1,27 @@ +import { httppost,httpget } from "@/utils/request" +import apiUrl from "@/service/apiurl" + +export const treeList = async (params) => { + const res = await httppost(apiUrl.spjk.treeList, params) + return res +} + +export const treeListById = async (params) => { + const res = await httpget(`${apiUrl.spjk.treeListById}${params}`, params) + return res +} + +export const srcData = async (params) => { + const res = await httpget(`${apiUrl.spjk.srcData}${params}`) + return res +} + +export const ysyToken = async (params) => { + const res = await httpget(apiUrl.spjk.ysyToken) + return res +} + +export const videoList = async (params) => { + const res = await httppost(apiUrl.spjk.videoList) + return res +} diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/index.css b/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/index.css new file mode 100644 index 0000000..03526b6 --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/index.css @@ -0,0 +1,80 @@ +.videoList { + height: 100%; +} +.videoList .treeRight { + width: 350px; + height: 100%; + padding: 16px 8px; + background-color: #fff; + box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1); +} +.videoList .treeRight .ant-tree-node-selected .iconSelect { + color: blue; +} +.videoList .treeLeft { + flex: 1; + margin-left: 12px; + background-color: #fff; + height: 100%; + padding: 16px 8px; + box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1); +} +.videoList .treeLeft .splitScreen { + height: 100%; +} +.videoList .treeLeft .splitScreen .borderF { + height: calc(100% - 40px); +} +.videoList .treeLeft .splitScreen .borderFa { + width: 100%; + height: 100%; + background: #f6f9fd; + border: 1px solid rgba(42, 117, 214, 0.2); + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + padding-bottom: 10px; +} +.videoList .treeLeft .splitScreen .borderType { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} +.videoList .treeLeft .splitScreen .borderType .text { + width: 100%; + height: 22px; + font-size: 16px; + font-family: PingFangSC-Regular, PingFang SC; + font-weight: 400; + color: #79a2d7; + text-align: center; +} +.videoList .treeLeft .splitScreen .videoBorder { + border: 3px solid red; +} +.videoList .treeLeft .splitScreen .videoBorder1 { + flex: 1; + height: 100%; +} +.videoList .treeLeft .splitScreen .videoBorder4 { + width: 50%; + height: 50%; +} +.videoList .treeLeft .splitScreen .videoBorder9 { + width: 33%; + height: 33%; +} diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/index.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/index.js new file mode 100644 index 0000000..683d2cf --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/index.js @@ -0,0 +1,157 @@ +import TreeData from "./treeData" +import './index.less' +import SplitScreen from "./splitScreen" +import { useEffect, useState } from "react" +import { treeList, srcData, videoList, ysyToken } from './http' +import VideoControler from "@/components/VideoCom/VideoControler" +import { httppost } from "@/utils/request" +import apiurl from "@/service/apiurl" + + +const VideoList = () => { + const [videoArr, setVideo] = useState([]) + const [count, setCount] = useState(999) + const [indexData, setIndex] = useState(999) + const [size, setSize] = useState(1) + const [treeListData, setTreeData] = useState([]) + const [selectList, setSelectList] = useState() + + const selectedKeys = async (list, node) => { + console.log(node, 'node'); + let src = null + let obj = {} + setSelectList(list[list.length - 1]) + if (indexData === 999) { + if (node.selected) { + console.log(node.node.relType, 'asdkjashkd'); + src = node.node.relType == "ysy" ? (await ysyToken(node.node.indexCode)).data : (await srcData(node.node.indexCode)).data + obj = { ...node.node, src: src } + if (videoArr.length < size) { + setVideo([...videoArr, obj]) + } else { + videoArr[size - 1] = obj + console.log(videoArr, '45678'); + setVideo((videoArr) => { + const newA = [...videoArr] + return newA + }) + } + } else { + let count = videoArr.findIndex(item => item.id === node.node.id) + setVideo(() => { + const newA = [...videoArr] + newA.splice(count, 1) + return newA + }) + } + } else { + if (node.selected) { + src = node.node.relType == 'ysy' ? (await ysyToken(node.node.indexCode)).data : (await srcData(node.node.indexCode)).data + obj = { ...node.node, src: src } + videoArr[indexData] = obj + setVideo((videoArr) => { + const newA = [...videoArr] + return newA + }) + } + + } + + } + const clickIndex = (index, item) => { + setIndex(index) + setSelectList(videoArr[index]) + } + const getType = (size) => { + console.log(size, videoArr.slice(0, size), 'szie'); + setSize(size) + setIndex(999) + setVideo(() => { + const newA = videoArr.slice(0, size) + console.log(newA); + return newA + }) + + } + const getTreeData = async () => { + const res = await treeList() + const res1 = await videoList() + const arr = res1.data.filter(item => item.menuId).map((item, index) => { + item.parentId = item.menuId + item.id = 999 + index + item.isLeaf = true + // item.icon= + return item + }) + const arr1 = [...arr, ...res.data] + console.log("before", arr1); + setTreeData(buildTree(arr1)) + } + function buildTree(data, parentId = "0") { + const tree = []; + for (const item of data) { + if (item.parentId == parentId) { + const children = buildTree(data, item.id); + if (children.length > 0) item.children = children; + tree.push(item) + } + + } + return tree; + } + let timer = null; + // 云台控制 + const onOperation = async (params) => { + let data = { + ...params, + indexCode: selectList?.indexCode, + action: 0 + } + try { + const res = await httppost(apiurl.spjk.controler, data) + if (res.code == 200) { + if (timer) clearTimeout(timer) + timer = setTimeout(() => { + onOperation1(params) + }, 500) + } + } catch (error) { + console.log(error); + } + } + const onOperation1 = async (params) => { + let data = { + ...params, + indexCode: selectList?.indexCode, + action: 1 + } + try { + const res = await httppost(apiurl.spjk.controler, data) + } catch (error) { + console.log(error); + } + } + + useEffect(() => { + getTreeData() + }, []) + return ( +
+
+ + { + selectList && selectList.type == 1 ? +
+ +
+ : null + } +
+
+
+ ) +} +export default VideoList diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/index.less b/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/index.less new file mode 100644 index 0000000..826f544 --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/index.less @@ -0,0 +1,114 @@ +.videoList{ + height: 100%; + .treeRight{ + position: relative; + width: 350px; + height: 100%; + padding: 16px 8px; + color: #fff; + border-radius: 4px; + &.ptz-visible{ + padding-bottom: 260px; + } + .ant-tree-iconEle{ + .iconSelect{ + // color: blue; + width: 25px; + height: 25px; + margin-bottom: 5px; + } + } + + .ant-tree { + background: transparent; + color: #fff; + + .ant-tree-node-content-wrapper { + &:hover { + background-color: rgba(255, 255, 255, 0.1); + } + &.ant-tree-node-selected { + background-color: rgba(24, 144, 255, 0.3); + } + } + .ant-tree-title { + color: #fff; + } + .ant-tree-switcher { + color: #fff; + } + } + } + .treeLeft{ + flex: 1; + margin-left: 12px; + // background-color: rgba(255, 255, 255, 0.05); + color: #fff; + height: 100%; + padding: 16px 8px; + border-radius: 4px; + // border: 1px solid rgba(255, 255, 255, 0.1); + + .splitScreen{ + height: 100%; + .borderF{ + height: 100%; + // padding: 20px; + } + .borderFa{ + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(255, 255, 255, 0.1); + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + padding-bottom: 10px; + } + .borderType{ + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + .text{ + width: 100%; + height: 22px; + font-size: 16px; + font-family: PingFangSC-Regular, PingFang SC; + font-weight: 400; + color: rgba(255, 255, 255, 0.6); + text-align: center; + } + } + .videoBorder{ + border: 2px solid #1890ff; + } + .videoBorder1{ + flex: 1; + height: 100%; + } + .videoBorder4{ + width: 50%; + height: 50%; + } + .videoBorder9{ + width: 33%; + height: 33%; + } + } + + } +} diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/splitScreen.tsx b/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/splitScreen.tsx new file mode 100644 index 0000000..5420b4c --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/splitScreen.tsx @@ -0,0 +1,100 @@ +import TreeData from "./treeData" +import './index.less' +import { Radio, Tabs, Empty } from "antd" +import { useEffect, useRef, useState } from "react"; +import HFivePlayer from "@/components/VideoCom/videoPlary"; +// import HFivePlayer from "./video1"; + +const SplitScreen: React.FC = (props: any) => { + const [size, setSize] = useState(1); + const [list, setList] = useState([]); + const [videoList, setVideoList] = useState([]); + const [num, setNum] = useState(999); + const [styleType, setStyle] = useState(false); + + + const onChange = (e: any) => { + setSize(Number(e)); + handDate(Number(e)) + setNum(999) + }; + const handDate = (size: any) => { + props.getType(size) + console.log(new Array(size).fill(1),'4678999'); + + setList(new Array(size).fill(1)) + setVideoList([]) + } + const clickVideo = (index: any,item:any) => { + if(num == index){ + props.clickIndex(999,item) + setStyle(true) + setNum(999) + }else{ + props.clickIndex(index,item) + setStyle(false) + setNum(index) + } + + } + const getNum = () => { + if (num === 999) { + // return props.videoArr.length - 1 + } else { + return num + } + } + useEffect(() => { + if (props.count !== 999) { + setNum(props.count) + } + }, [props.count]) + useEffect(() => { + + setVideoList(props.videoArr) + console.log(props.videoArr,'props.videoArr'); + + }, [props.videoArr]) + + useEffect(() => { + onChange('4') + }, []) + const items = [ + { key: '1', label: '单屏' }, + { key: '4', label: '四分屏' }, + { key: '9', label: '九分屏' }, + ] + return ( +
+ {/* */} + {/* {props.videoArr} */} +
+ {list.map((item: any, index: any) => { + return
clickVideo(index,item)} + // onClick={()=>clickVideo(index)} + className={[getNum() == index ? 'videoBorder' : null, + size == 1 ? 'videoBorder1' : null, size == 4 ? 'videoBorder4' : null, + size == 9 ? 'videoBorder9' : null,'borderFa'].join(' ')}> + {props.videoArr[index]&&
+
+ {videoList.length&&} +
+
{videoList[index]?.name}
+
} + {!props.videoArr[index]&&
+ +
暂无视频数据
+
} +
+ + })} +
+
+ ) +} +export default SplitScreen \ No newline at end of file diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/treeData.tsx b/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/treeData.tsx new file mode 100644 index 0000000..7739a5b --- /dev/null +++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/VideoList/treeData.tsx @@ -0,0 +1,104 @@ +import { DownOutlined, VideoCameraFilled } from '@ant-design/icons'; +import React, { useEffect, useState } from 'react'; +import { Tree } from 'antd'; + +interface DataNode { + title: string; + key: string; + isLeaf?: boolean; + children?: DataNode[]; +} + +let arr:any=[] +// It's just a simple demo. You can use tree map to optimize update perf. +const updateTreeData = (list: DataNode[], key: React.Key, children: DataNode[]): DataNode[] => + list.map((node: any) => { + if (node.id === key) { + return { + ...node, + children, + }; + } + if (node.children) { + return { + ...node, + children: updateTreeData(node.children, key, children), + }; + } + return node; + }); + +const TreeData = (props: any) => { + console.log(props.treeListData); + + const [checkNode, setCheckNode] = useState([]); + const onSelect = async (selectedKeys: (any | never[]), info: any) => { + if(!info.node.isLeaf && !info.node.selected)return + if(selectedKeys.length < props.size){ + setCheckNode(selectedKeys) + props.selectedKeys(info.selectedNodes,info) + }else{ + setCheckNode((selectedKeys) => { + const newA:any = [...selectedKeys] + newA[Number(props.size)-1]=info.node.id + + const arr =newA.map((item:any)=>{ + let count = info.selectedNodes.findIndex((itemq:any) => itemq.id === item) + if(count !== -1){ + return info.selectedNodes[count] + } + }) + props.selectedKeys(arr,info) + return newA + }) + } + + }; + const iconSelect = (a: any) => { + const type = a.data.type; + // const online = a.data.online + if (type == 1) { + return + + } else if(type ==2) { + return + } + + // if (a.data.isLeaf) { + // if(a.data.type ==1) + // return + // } + } + useEffect(()=>{ + setCheckNode(() => { + const newA = props.videoArr.map((item:any)=>{ + return item?.id + }) + return newA + }) + // setCheckNode(selectedKeys) + },[props.videoArr]) + // useEffect(()=>{}) + return
+ {props.treeListData.length !== 0 && + } +
; +}; + +export default TreeData; \ No newline at end of file diff --git a/src/views/Home/components/Business/SiQuan/index.js b/src/views/Home/components/Business/SiQuan/index.js index 0f850b6..d0ad9ec 100644 --- a/src/views/Home/components/Business/SiQuan/index.js +++ b/src/views/Home/components/Business/SiQuan/index.js @@ -117,7 +117,7 @@ const SiQuan = () => { tabs={modalType === 'monitor' ? tabs : (modalType === 'allweather' ? tabsAllWeather : [])} activeTab={activeTab} onTabChange={handleTabChange} - width={modalType === 'cycle' ? '70%': undefined} + width={modalType === 'cycle' ? '70%':modalType === 'allweather' ? '90%': undefined} > {/* Content changes based on activeTab */}
diff --git a/src/views/Home/components/UI/YearSelect/index.less b/src/views/Home/components/UI/YearSelect/index.less index 434245a..3778481 100644 --- a/src/views/Home/components/UI/YearSelect/index.less +++ b/src/views/Home/components/UI/YearSelect/index.less @@ -6,7 +6,7 @@ input { color: #fff !important; - font-size: 16px; + font-size: 14px; font-weight: normal; // Changed from bold to normal cursor: pointer; }