Compare commits

...

8 Commits

31 changed files with 2091 additions and 548 deletions

View File

@ -1,3 +1,3 @@
GENERATE_SOURCEMAP=true
PUBLIC_URL=/tsg
REACT_APP_API_URL=http://local.gunshiiot.com:18083
REACT_APP_API_URL=http://223.75.53.141:83

View File

@ -1,3 +1,3 @@
GENERATE_SOURCEMAP=false
PUBLIC_URL=/tsg
REACT_APP_API_URL=http://local.gunshiiot.com:18083
REACT_APP_API_URL=http://223.75.53.141:83

1
.gitignore vendored
View File

@ -24,6 +24,7 @@ yarn-debug.log*
yarn-error.log*
yarn.lock
build.7z
build1.zip
#ai
.serena/

View File

@ -13,56 +13,124 @@ import { currentBreadcrumbs } from '../../models/auth/_';
import {page} from '../../service/warn';
import { qryBasicWatershedDetailApi } from "../../service/preplan";
import moment from "moment"
import { httppost2 } from '../../utils/request';
import { httppostAuth } from '../../utils/request';
import apiurl from '../../service/apiurl';
const { Header, Content, Sider } = Layout;
const meunObj:any = {
'home':'基本情况',
'fxzb':'防汛准备',
'sxfd':'思想发动',
'fxkhgzh':'防汛抗旱工作会',
'fbzrh':'防办主任会',
'fxpxb':'防汛培训班',
'fxtj':'防汛图件',
'zzjg':'组织机构',
'zq':'政区',
'gc':'工程',
'gczx':'工程整修',
'home':'水库一张图',
'sq':'四全',
'qfg':'全覆盖',
'zcdjxx':'注册登记信息',
'qys':'全要素',
'kqys':'库区要素',
'gcys':'工程要素',
'xyys': '下游要素',
'qth':"全天候",
'sksq':"水库水情",
'hdsq': "河道水情",
'ssyq':'实时水情',
'trsq':'土壤墒情',
'skyh':'水库溢洪',
'spjk':'视频监控',
'ytygc':'已投运工程',
'zjgc':'在建工程',
'hdqz':'河道清障',
'yaxb':'预案修编',
'zqya':'政区预案',
'ytygcya':'已投运工程预案',
'zjgcya':'在建工程预案',
'hsddya':'洪水调度方案',
'qxdw':'抢险队伍',
'qxwl':'抢险物料',
'jczw':'检测站网',
'yqz':'雨情站',
'sqz':'水情站',
'zbb':'值班表',
'txl':'通讯录',
'ysgzq':'雨水工灾情',
'ssyq':'实时雨情',
'sssq':'实时水情',
'ssgq':'实时工情',
'yxqk':'运行情况',
'gcxq':'工程险情',
'dbaq':'大坝安全监测数据',
'sszq':'实时灾情',
'fxdd':'防汛调度',
'dqxsfx':'当前形势分析',
'tqyb':'天气预报',
'qzq':'全周期',
'gcdsj':'工程大事记',
'qzqda':'全周期档案',
'sz':'四制',
'gltx':'管理体系',
'zzjgck':'组织机构查看',
'zrrgl': '责任人管理',
'pxgl':'培训管理',
'pxjhgl':'培训计划管理',
'pxjlgl':'培训记录管理',
'szzf':'水政执法',
'ajdj':'案件登记',
'ajtj':'案件统计',
'clyj':'处理依据',
'jdkh':'监督考核',
'khtj':'考核统计',
'khrwgl':'考核任务管理',
'khwtzg':'考核问题整改',
'khzbgl':'考核指标管理',
'khmbgl':'考核模版管理',
'zdgl':'制度管理',
'flfg':'法律法规',
'zsk':'知识库',
'ddfa':'调度方案库',
'ywgz':'业务规则库',
'gcaq':'工程安全知识库',
'sy':'四预',
'fhxzfx': '防洪形式',
'tqyb': '天气预报',
'hsyb':'洪水预报',
'skhs':'水库洪水',
'hdhs':'河道洪水',
'ddjc':'调度决策',
'yjxy':'应急响应',
'ddzl':'调度指令',
'videoSurveillance':'视频监控',
'fxdp':'防汛大屏',
'hyybjs':'洪水预报计算',
'ybfagl':'预报方案管理',
'csgl': '参数管理',
'hsyj':'洪水预警',
'yjxx':'预警信息',
'gzpz': '规则配置',
'hsyy':'洪水预演',
'fxya':'防汛预案',
'ddgc':'调度规程',
'qxdw':'抢险队伍',
'qxwl': '抢险物料',
'sg':"四管",
'xcxj':"巡查巡检",
'xcrw':"巡查任务",
'xjwtcl':"巡查问题处理",
'xjxpz':"巡检项配置",
'aqgl':"安全管理",
'fxgkqd':"风险管控清单",
'aqyhpc':"安全隐患排查",
'aqjcgl':"安全检查管理",
'aqsgdj':"安全事故登记",
'aqjdtz':"安全鉴定台帐",
'cxjgtz':"除险加固台帐",
'byfz':"白蚁防治",
'bypc':"白蚁监测",
'byxc':"防治宣传",
'zmjk':"闸门监控",
'wxyh':"维修养护",
'zbgl':"值班管理",
'zbb':'值班表',
'zbrz':'值班日志',
'btbb':'报表管理',
'sdjyrbb':'时段降雨日报表',
'rjylnbb':'日降雨量年报表',
'sdswbb':'时段水位日报表',
'rjswbb':'日均水位年报表',
'dbaq':'告警管理',
'aigj':'AI告警',
'gbyj':'广播预警',
'gcaqjc':'工程安全监测',
'bzt':'布置图',
'gcaqfx':'工程安全分析',
'jrx':'浸润线',
'gcaqyj':'工程安全预警',
'yhyj':'隐患预警',
'yjgzpz':'预警规则配置',
'sjtjcx':'数据统计查询',
'sjlr':'人工监测数据录入',
'czcx':'测值查询',
'syjx':'渗压监测',
'sljx':'渗流监测',
'wyjx':'位移监测',
'ndsytjb':'年度渗压统计表',
'ndsltjb':'年度渗流统计表',
'ndwytjb':'年度位移统计表',
'szydd':'水资源调度',
'gsnlfx':'供水能力分析',
'diaodu':'调度记录',
'gstjfx':'供水统计分析',
'dxnjyzl':'典型年降雨资料',
'skzfzl': '水库蒸发资料',
'sys':'系统管理',
'user':'用户管理',
'department':'部门管理',
'role':'角色管理',
'menuM':'菜单管理',
'loginLog':'登录日志',
}
@ -113,31 +181,25 @@ const DashboardLayout: React.FC = () => {
// 这个方法是统计菜单点击情况的
// useEffect(()=>{
// (async()=>{
// const list = location.pathname.split('/')
// let menu1:any = meunObj?.[list[2]]
// let menu2:any = meunObj?.[list[3]]
// let menu3:any = meunObj?.[list[4]]
// if(menu1==='基本情况'){
// menu2 = '基本情况'
// }
// if(menu1==='视频监控'){
// menu2 = '视频监控'
// }
// if(menu1==='防汛大屏'){
// menu2 = '防汛大屏'
// }
useEffect(()=>{
(async()=>{
const list = location.pathname.split('/')
let menu1:any = meunObj?.[list[2]] //一级菜单
let menu2:any = meunObj?.[list[3]] //二级菜单
let menu3:any = meunObj?.[list[4]] //三级菜单
if(menu1==='水库一张图'){
menu2 = '水库一张图'
}
// const res = await httppost2(apiurl.setMenu,{
// createId:localStorage.getItem('userId'),
// loginType:0,
// menu1:menu1,
// menu2:menu2,
// menu3:menu3
// })
// })()
// },[location.pathname])
const res = await httppostAuth(apiurl.setMenu,{
createId:localStorage.getItem('userId'),
loginType:0,
menu1:menu1,
menu2:menu2,
menu3:menu3
})
})()
},[location.pathname])
const menuIndexes = useMemo(() => findMenu(menu, pathname), [menu, pathname]);

View File

@ -2,6 +2,7 @@ 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插件视频播放
@ -23,10 +24,16 @@ const HFivePlayer = ({ wsUrl, playerID, size }) => {
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: `ezopen://open.ys7.com/${wsUrl.indexCode}/1.live`,
url: ezUrl,
// plugin: ["talk"], // 加载插件talk-对讲
width: parentRef.current?.offsetWidth,
height: parentRef.current?.offsetHeight,
@ -133,6 +140,13 @@ const HFivePlayer = ({ wsUrl, playerID, size }) => {
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,

View File

@ -49,6 +49,15 @@ const apiurl = {
view:baseFileView
},
systemM: {
action: {
todayData: service_fxdd + "/userLoginLog/todayCount",
activeCount: service_fxdd + "/userLoginLog/userCount",
userCount: service_fxdd + "/userLoginLog/visitCount",
hotData:service_fxdd + "/visitMenuLog/count"
},
yhxwrz:{
page:service_fxdd + "/visitMenuLog/page",
},
userM: {
updatePassword:service_xyt + '/system/user/profile/updatePwd'
}
@ -226,6 +235,17 @@ const apiurl = {
},
sy: {
yjxx: {
page: service_fxdd + "/warningRule/info/page",
},
yjxxpz: {
page: service_fxdd + "/warningRule/page",
edit: service_fxdd + '/warningRule/update',
save: service_fxdd + '/warningRule/insert',
delete: service_fxdd + "/warningRule/del/",
}
},
// 防汛准备
fxzb1: {
ddgc: {
@ -539,9 +559,12 @@ const apiurl = {
},
zfzl: {
list: service_fxdd + "/gateValveReal/list",
list1: service_fxdd + "/gate/list",
historypage: service_fxdd + '/gateValveReal/log/page',
historypage1: service_fxdd + '/gate/page/history',
historyList: service_fxdd + '/gateValveReal/log/loglist',
historyPageExport: service_fxdd + '/gateValveReal/log/exp',
historyPageExport1: service_fxdd + '/gate/exp',
swInfo:service_fxdd + '/reservoir/water/waterInfo',
krlist: service_fxdd + "/reservoir/water/data",
info: service_fxdd + "/attGateValve/detail",
@ -570,6 +593,7 @@ const apiurl = {
spjk1: {
aiWarn: {
page: service_fxdd + "/stImgWarnR/page",
page1: service_fxdd + "/iscaiEvent/page",
list: service_fxdd + "/attCctvBase/list",
controler:service_fxdd + "/attCctvBase/control"
}

View File

@ -21,6 +21,7 @@ function request(url, options,type) {
const opt = { ...options };
opt.headers = opt.headers || {};
opt.headers.Accept = 'application/json';
// opt.headers.Authorization = "Bearer" + ' ' + localStorage.getItem('access_token');
// opt.credentials = opt.credentials || 'include';
return fetch(url, opt)
@ -398,6 +399,21 @@ export function httppost2(url, data = {}) {
return send(url, options);
}
export function httppostAuth(url, data = {}) {
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'adcd': localStorage.getItem('ADCD6'),
"gs-token": localStorage.getItem('access_token'),
"Authorization":"Bearer" + ' ' + localStorage.getItem('access_token')
},
body: JSON.stringify(data),
};
return send(url, options);
}
export function httppost5(url, data = {}) {
const options = {
method: 'POST',

View File

@ -21,6 +21,8 @@ import Fxya from './fxzb/fxya'
import Hsybjs from './fxzb/hsybjs'
import Ybfagl from './fxzb/ybfagl'
import Csgl from './fxzb/csgl'
import Yjxx from './fxzb/yjxx'
import Gzpz from './fxzb/gzpz'
import Qxdw_Gc from './fxzb/qxdw/gc/index.js'
import Qxwl_Gc from './fxzb/qxwl/gc'
@ -121,6 +123,8 @@ import Ywgz from './sz/ywgz'
import Gcaq from './sz/khzbgl'
//系统管理
import SystemPage from './systemMange'
import Yhxwrz from './yhxwrz'
import Yhxwfx from './yhxwfx'
const HomePage = lazy(() => import('./Home'))
@ -162,6 +166,10 @@ const AppRouters: React.FC = () => {
{ path: 'sy/hsyb/ybfagl', element: <Ybfagl /> },
{ path: 'sy/hsyb/csgl', element: <Csgl /> },
// 四预-洪水预警
{ path: 'sy/hsyj/yjxx', element: <Yjxx/> },
{ path: 'sy/hsyj/gzpz', element: <Gzpz /> },
// 调度规程
{ path: 'sy/ddgc', element: <Ddgc /> },
@ -298,6 +306,8 @@ const AppRouters: React.FC = () => {
{ path: 'sys/role', element: <SystemPage src={'/mgr/home/role'}/> },
{ path: 'sys/menuM', element: <SystemPage src={'/mgr/home/menuM'}/> },
{ path: 'sys/loginLog', element: <SystemPage src={'/mgr/home/loginLog'}/> },
{ path: 'sys/yhxwrz', element: <Yhxwrz /> },
{ path: 'sys/yhxwfx', element: <Yhxwfx /> },
],
},
{ path: '/login', element: <LoginPage /> },

View File

@ -121,7 +121,7 @@ const Page = ({ mySetTms }) => {
{ key==='位移告警'?<Table_wy data={dataObj.shiftWarn} onCancel={()=>setOpen(false)}/>:null }
{ key==='渗压告警'?<Table_sy data={dataObj.pressWarn} onCancel={()=>setOpen(false)}/>:null }
{ key==='渗流告警'?<Table_sl data={dataObj.flowWarn} onCancel={()=>setOpen(false)}/>:null }
{key === 'AI告警' ? <Table_AI /> : null}
{key === 'AI告警' ? <Table_AI tms={tms} /> : null}
{ key==='白蚁告警'?<Table_by data={dataObj.byWarn} onCancel={()=>setOpen(false)}/>:null }
</div>
</Modal>

View File

@ -10,20 +10,20 @@ import apiurl from "../../../../service/apiurl";
import moment from 'moment';
const Page = () => {
const Page = ({ tms }) => {
const { tableProps, search, refresh } = usePageTable(createCrudService('/gunshiApp/tsg/rescue/goods/page/query').find_noCode,{});
// const { tableProps, search, refresh } = usePageTable(createCrudService('/gunshiApp/tsg/rescue/goods/page/query').find_noCode,{});
useEffect(()=>{
const params = {
search: {}
};
search(params)
}, [])
// useEffect(()=>{
// const params = {
// search: {}
// };
// search(params)
// }, [])
return (
<div className="ant-card-body" style={{padding:"0 10px",height:'600px',overflowY:'auto'}}>
<AiWarn/>
<AiWarn tm={tms}/>
{/* <div>时间:{moment().format('YYYY-MM-DD HH:mm:ss')} 至 {moment().format('YYYY-MM-DD HH:mm:ss')}</div> */}
</div>
)

177
src/views/fxzb/gzpz/form.js Normal file
View File

@ -0,0 +1,177 @@
import React, { useEffect, useState, useMemo, useRef } from 'react';
import { Form, Button, Input, Row, Upload, Col, Table, DatePicker, InputNumber, message, Image, Modal, Radio, Popconfirm } from 'antd';
import { DeleteOutlined, VideoCameraOutlined } from '@ant-design/icons';
import { formItemLayout, btnItemLayout } from '../../../components/crud/FormLayoutProps';
import apiurl from '../../../service/apiurl';
import NormalSelect from '../../../components/Form/NormalSelect';
import moment from 'moment';
const ModalForm = ({ mode, record, onEdit, onSave, onSimilarSave }) => {
const [form] = Form.useForm();
const onfinish = (values) => {
const userId = localStorage.getItem('userId')
values.userId = userId
values.createTime = values.createTime ? moment(values.createTime).format("YYYY-MM-DD 00:00:00") : undefined
if (mode === 'edit') {
onEdit(apiurl.sy.yjxxpz.edit, { ...record, ...values })
}
if (mode === 'save') {
onSave(apiurl.sy.yjxxpz.save, values)
}
}
useEffect(() => {
if (mode == "save") {
const name = localStorage.getItem('userName')
form.setFieldValue("createName", name)
form.setFieldValue("createTime", moment())
}
}, [mode])
return (
<>
<Form
form={form}
{...formItemLayout}
onFinish={onfinish}
initialValues={record}
>
<Row>
<Col span={12}>
<Form.Item
label="规则名称"
name="ruleName"
rules={[{ required: true }]}
>
<Input style={{ width: '100%' }} allowClear disabled={mode === 'view'} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="预警等级"
name="warningLevel"
rules={[{ required: true }]}
>
<NormalSelect
options={[
{ label: '蓝色', value: 0 },
{ label: '黄色', value: 1 },
{ label: '橙色', value: 2 },
{ label: '红色', value: 3 }
]}
allowClear={true}
style={{ width: '100%' }}
disabled={mode === 'view'}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="启用状态"
name="status"
rules={[{ required: true }]}
>
<Radio.Group disabled={mode === 'view'}>
<Radio value={0}>未启用</Radio>
<Radio value={1}>启用</Radio>
</Radio.Group>
</Form.Item>
</Col>
</Row>
<Row>
<Col span={12}>
<Form.Item
label="规则配置"
name="durationHours"
rules={[{ required: true, message: '请选择对象' }]}
>
<NormalSelect
allowClear
style={{ width: '100%' }}
disabled={mode === 'view'}
placeholder="请选择对象"
options={[
{ label: '未来1h水库预报水位', value: 1 },
{ label: '未来3h水库预报水位', value: 3 },
{ label: '未来6h水库预报水位', value: 6 },
{ label: '未来12h水库预报水位', value: 12 },
{ label: '未来24h水库预报水位', value: 24 },
]}
/>
</Form.Item>
</Col>
<Col span={4}>
<Form.Item
label=""
name="operator"
rules={[{ required: true, message: '请选择关系' }]}
>
<NormalSelect
allowClear
style={{ width: '100%' }}
disabled={mode === 'view'}
placeholder="请选择关系"
options={[
{ label: '>', value: '>' },
{ label: '≥', value: '>=' },
]}
/>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label=""
name="type"
rules={[{ required: true, message: '请选择阈值' }]}
>
<NormalSelect
allowClear
style={{ width: '100%' }}
disabled={mode === 'view'}
placeholder="请选择阈值"
options={[
{ label: '汛限水位(109.00m)', value: 0 },
{ label: '设计洪水位(111.89m)', value: 1 },
{ label: '校核洪水位(113.06m)', value: 2 },
]}
/>
</Form.Item>
</Col>
</Row>
<Row>
<Col span={12}>
<Form.Item
label="创建人"
name="createName"
>
<Input disabled={true} style={{ width: '100%' }} allowClear />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="创建时间"
name="createTime"
getValueFromEvent={(e, dateString) => dateString}
getValueProps={(value) => ({ value: value ? moment(value) : undefined })}
>
<DatePicker disabled={true} style={{ width: '100%' }} allowClear format={"YYYY-MM-DD HH:mm:ss"} />
</Form.Item>
</Col>
</Row>
{
mode === 'view' ? null : (
<>
<Form.Item {...btnItemLayout}>
<Button type="primary" htmlType="submit">
{mode === 'save' ? '提交' : '修改'}
</Button>
</Form.Item>
</>
)
}
</Form>
</>
);
}
export default ModalForm;

View File

@ -0,0 +1,109 @@
import React, { Fragment, useRef, useMemo,useEffect,useState } from 'react';
import BasicCrudModal from '../../../components/crud/BasicCrudModal';
import { Table, Card, Modal, Form, Input, Button, Row,Col, Timeline, message, Tabs,Image } from 'antd';
import {FileWordOutlined,FilePdfOutlined,FileZipOutlined,FileExcelOutlined } from '@ant-design/icons';
import { useSelector } from 'react-redux';
import ToolBar from './toolbar';
import ModalForm from './form';
import apiurl from '../../../service/apiurl';
import usePageTable from '../../../components/crud/usePageTable2';
import { createCrudService } from '../../../components/crud/_';
import {CrudOpRender_text} from '../../../components/crud/CrudOpRender';
import moment from 'moment';
const url = "http://223.75.53.141:9100/gs-tsg"
const Page = () => {
const role = useSelector(state => state.auth.role);
const editBtn = role?.rule?.find(item => item.menuName == "编辑")|| true;
const viewBtn = role?.rule?.find(item => item.menuName == "查看")|| true;
const delBtn = role?.rule?.find(item => item.menuName == "删除")|| true;
const levelObj = {
0: "蓝色",
1: "黄色",
2: "橙色",
3: "红色",
}
const statusObj = {
0: "未启用",
1: "启用",
}
const refModal = useRef();
const [searchVal, setSearchVal] = useState(false)
const columns = [
{ title: '序号', key: 'inx', dataIndex: 'inx', width: 60, align: "center" },
{ title: '规则名称', key: 'ruleName', dataIndex: 'ruleName', width: 140, align: "center", },
{ title: '预警等级', key: 'warningLevel', dataIndex: 'warningLevel', width: 100, align: "center", render: (v) => <span>{levelObj[v]}</span> },
{ title: '状态', key: 'status', dataIndex: 'status', width: 100,align:"center",render: (v) => <span>{statusObj[v]}</span> },
{ title: '规则描述', key: 'ruleDesc', dataIndex: 'ruleDesc', width: 300, align: "center", },
{ title: '创建时间', key: 'createTime', dataIndex: 'createTime', width: 140, align: "center"},
{ title: '创建人', key: 'createName', dataIndex: 'createName', width: 140, align: "center"},
{
title: '操作', key: 'operation', width: 100, fixed: 'right', align: 'center',
render: (value, row, index) => (<CrudOpRender_text edit={true} del={true} command={(cmd) => () => command(cmd)(row)} />)
},
];
const width = useMemo(() => columns.reduce((total, cur) => total + (cur.width), 0), [columns]);
const command = (type) => (params) => {
if (type === 'save') {
refModal.current.showSave();
} else if (type === 'edit') {
refModal.current.showEdit({ ...params });
} else if (type === 'view') {
refModal.current.showView(params);
} else if (type === 'del') {
refModal.current.onDeleteGet(apiurl.sy.yjxxpz.delete + `${params.id}`);
}
}
const { tableProps, search, refresh } = usePageTable(createCrudService(apiurl.sy.yjxxpz.page).find_noCode);
/**
* @description 处理成功的回调
*/
const successCallback = () => {
refresh()
}
useEffect(() => {
const params = {
search: {
...searchVal,
}
};
search(params)
}, [searchVal])
return (
<>
<div className='content-root clearFloat xybm' style={{paddingRight:"0",paddingBottom:"0"}}>
<div className='lf CrudAdcdTreeTableBox' style={{width:"100%",overflowY:"auto"}}>
<Card className='nonebox'>
<ToolBar
setSearchVal={setSearchVal}
onSave={command('save')}
role={role}
/>
</Card>
<div className="ant-card-body" style={{ padding: "20px 0 0 0" }}>
<Table columns={columns} rowKey="inx" {...tableProps} scroll={{ x: width , y: "calc( 100vh - 400px )"}}/>
</div>
</div>
<BasicCrudModal
width={1000}
ref={refModal}
title=""
component={ModalForm}
onCrudSuccess={successCallback}
// onCrudSuccess={()=>{refresh({addvcd:localStorage.getItem('ADCD6')})}}
/>
</div>
</>
);
}
export default Page;

View File

@ -0,0 +1,57 @@
import React, { useEffect,useState } from 'react';
import { Form, Input, Button, DatePicker } from 'antd';
import moment from 'moment';
import NormalSelect from '../../../components/Form/NormalSelect';
const { RangePicker } = DatePicker;
const ToolBar = ({ setSearchVal, onSave, storeData, role }) => {
const addBtn = role?.rule?.find(item => item.menuName == "新增")|| true;
const searchBtn = role?.rule?.find(item => item.menuName == "查询")|| true;
const [form] = Form.useForm();
const onFinish = (values) => {
setSearchVal({...values})
}
return (
<>
<div style={{display:'flex',justifyContent:'space-between'}}>
<Form form={form} className='toolbarBox' layout="inline" onFinish={onFinish}>
<Form.Item label="规则名称" name="ruleName">
<Input allowClear style={{width:200}}/>
</Form.Item>
<Form.Item label="预警等级" name="warningLevel">
<NormalSelect
allowClear
style={{ width: '150px' }}
options={[
{ label: '蓝色', value: 0 },
{ label: '黄色', value: 1 },
{ label: '橙色', value: 2 },
{ label: '红色', value: 3 }
]}
/>
</Form.Item>
{searchBtn ? <Form.Item>
<Button type="primary" htmlType="submit">查询</Button>
</Form.Item> : null }
<Form.Item>
<Button onClick={() => form.resetFields()}>重置</Button>
</Form.Item>
{
(onSave && addBtn) ?
<Form.Item>
<Button onClick={onSave}>新增</Button>
</Form.Item>
:null
}
</Form>
</div>
</>
);
}
export default ToolBar;

149
src/views/fxzb/yjxx/form.js Normal file
View File

@ -0,0 +1,149 @@
import React,{useEffect,useState,useMemo,useRef} from 'react';
import { Form, Button, Input, Row,Upload, Col, Table, DatePicker, InputNumber,message,Image,Modal,Radio ,Popconfirm } from 'antd';
import { DeleteOutlined,VideoCameraOutlined } from '@ant-design/icons';
import { formItemLayout, btnItemLayout } from '../../../components/crud/FormLayoutProps';
import apiurl from '../../../service/apiurl';
import NormalSelect from '../../../components/Form/NormalSelect';
import moment from 'moment';
const ModalForm = ({ mode, record, onEdit, onSave, onSimilarSave }) => {
const [form] = Form.useForm();
const onfinish = (values) => {
const userId = localStorage.getItem('userId')
values.userId = userId
if (mode === 'edit') {
onEdit(apiurl.rcgl.wxyh.edit,{...record,...values})
}
if (mode === 'save') {
onSave(apiurl.rcgl.wxyh.save,values)
}
}
return (
<>
<Form
form={form}
{...formItemLayout}
onFinish={onfinish}
initialValues={record}
>
<Row>
<Col span={12}>
<Form.Item
label="规则名称"
name="ruleName"
rules={[{ required: true }]}
>
<Input style={{ width: '100%' }} allowClear disabled={mode === 'view'} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="预警等级"
name="warningLevel"
rules={[{ required: true }]}
>
<NormalSelect
options={[
{ label: '蓝色', value: 0 },
{ label: '黄色', value: 1 },
{ label: '橙色', value: 2 },
{ label: '红色', value: 3 }
]}
allowClear={true}
style={{ width: '100%' }}
disabled={mode === 'view'}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="启用状态"
name="status"
rules={[{ required: true }]}
>
<Radio.Group disabled={mode === 'view'}>
<Radio value={0}>未启用</Radio>
<Radio value={1}>启用</Radio>
</Radio.Group>
</Form.Item>
</Col>
</Row>
<Row>
<Col span={24}>
<Form.Item
label="规则配置"
required
labelCol={{ span: 3 }}
wrapperCol={{ span: 19 }}
>
<Row gutter={16}>
<Col span={8}>
<Form.Item name="durationHours" rules={[{ required: true }]} noStyle>
<NormalSelect
allowClear
style={{ width: '100%' }}
disabled={mode === 'view'}
placeholder="请选择对象"
options={[
{ label: '未来1h水库预报水位', value: 1 },
{ label: '未来3h水库预报水位', value: 3 },
{ label: '未来6h水库预报水位', value: 6 },
{ label: '未来12h水库预报水位', value: 12 },
{ label: '未来24h水库预报水位', value: 24 },
]}
/>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name="operator" rules={[{ required: true }]} noStyle>
<NormalSelect
allowClear
style={{ width: '100%' }}
disabled={mode === 'view'}
placeholder="请选择关系"
options={[
{ label: '>', value: '>' },
{ label: '≥', value: '>=' },
]}
/>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name="type" rules={[{ required: true }]} noStyle>
<NormalSelect
allowClear
style={{ width: '100%' }}
disabled={mode === 'view'}
placeholder="请选择阈值"
options={[
{ label: '汛限水位(109.00m)', value: 0 },
{ label: '设计洪水位(111.89m)', value: 1 },
{ label: '校核洪水位(113.06m)', value: 2 },
]}
/>
</Form.Item>
</Col>
</Row>
</Form.Item>
</Col>
</Row>
{
mode==='view'?null:(
<>
<Form.Item {...btnItemLayout}>
<Button type="primary" htmlType="submit">
{mode === 'save' ? '提交' : '修改'}
</Button>
</Form.Item>
</>
)
}
</Form>
</>
);
}
export default ModalForm;

View File

@ -0,0 +1,105 @@
import React, { Fragment, useRef, useMemo,useEffect,useState } from 'react';
import BasicCrudModal from '../../../components/crud/BasicCrudModal';
import { Table, Card, Modal, Form, Input, Button, Row,Col, Timeline, message, Tabs,Image } from 'antd';
import {FileWordOutlined,FilePdfOutlined,FileZipOutlined,FileExcelOutlined } from '@ant-design/icons';
import { useSelector } from 'react-redux';
import ToolBar from './toolbar';
import ModalForm from './form';
import apiurl from '../../../service/apiurl';
import usePageTable from '../../../components/crud/usePageTable2';
import { createCrudService } from '../../../components/crud/_';
import {CrudOpRender_text} from '../../../components/crud/CrudOpRender';
import moment from 'moment';
const url = "http://223.75.53.141:9100/gs-tsg"
const Page = () => {
const role = useSelector(state => state.auth.role);
const editBtn = role?.rule?.find(item => item.menuName == "编辑")|| true;
const viewBtn = role?.rule?.find(item => item.menuName == "查看")|| true;
const delBtn = role?.rule?.find(item => item.menuName == "删除")|| true;
const levelObj = {
0: "蓝色",
1: "黄色",
2: "橙色",
3: "红色",
}
const refModal = useRef();
const [searchVal, setSearchVal] = useState(false)
const columns = [
{ title: '序号', key: 'inx', dataIndex: 'inx', width: 60, align: "center" },
{ title: '预警时间', key: 'createTime', dataIndex: 'createTime', width: 140, align: "center", },
{ title: '规则名称', key: 'ruleName', dataIndex: 'ruleName', width: 140, align: "center", },
{ title: '预警等级', key: 'warningLevel', dataIndex: 'warningLevel', width: 100, align: "center", render: (v) => <span>{levelObj[v]}</span> },
{ title: '预警信息', key: 'ruleDesc', dataIndex: 'ruleDesc', width: 300, align: "center", },
{
title: '操作', key: 'operation', width: 100, fixed: 'right', align: 'center',
render: (value, row, index) => (<CrudOpRender_text view={true} command={(cmd) => () => command(cmd)(row)} />)
},
];
const width = useMemo(() => columns.reduce((total, cur) => total + (cur.width), 0), [columns]);
const command = (type) => (params) => {
if (type === 'save') {
refModal.current.showSave();
} else if (type === 'edit') {
refModal.current.showEdit({ ...params });
} else if (type === 'view') {
refModal.current.showView(params);
} else if (type === 'del') {
refModal.current.onDeleteGet(apiurl.sy.yjxxpz.delete + `${params.id}`);
}
}
const { tableProps, search, refresh } = usePageTable(createCrudService(apiurl.sy.yjxxpz.page).find_noCode);
/**
* @description 处理成功的回调
*/
const successCallback = () => {
refresh()
}
useEffect(() => {
if (searchVal) {
const params = {
search: {
...searchVal,
}
};
search(params)
}
}, [searchVal])
return (
<>
<div className='content-root clearFloat xybm' style={{paddingRight:"0",paddingBottom:"0"}}>
<div className='lf CrudAdcdTreeTableBox' style={{width:"100%",overflowY:"auto"}}>
<Card className='nonebox'>
<ToolBar
setSearchVal={setSearchVal}
onSave={command('save')}
role={role}
/>
</Card>
<div className="ant-card-body" style={{ padding: "20px 0 0 0" }}>
<Table columns={columns} rowKey="inx" {...tableProps} scroll={{ x: width , y: "calc( 100vh - 400px )"}}/>
</div>
</div>
<BasicCrudModal
width={1000}
ref={refModal}
title=""
component={ModalForm}
onCrudSuccess={successCallback}
// onCrudSuccess={()=>{refresh({addvcd:localStorage.getItem('ADCD6')})}}
/>
</div>
</>
);
}
export default Page;

View File

@ -0,0 +1,71 @@
import React, { useEffect,useState } from 'react';
import { Form, Input, Button, DatePicker } from 'antd';
import moment from 'moment';
import NormalSelect from '../../../components/Form/NormalSelect';
const { RangePicker } = DatePicker;
const ToolBar = ({ setSearchVal, onSave, storeData, role }) => {
const addBtn = role?.rule?.find(item => item.menuName == "新增")|| true;
const searchBtn = role?.rule?.find(item => item.menuName == "查询")|| true;
const [form] = Form.useForm();
const onFinish = (values) => {
let dateTimeRangeSo;
if (values.tm) {
dateTimeRangeSo = {
start: moment(values.tm[0]).format('YYYY-MM-DD 00:00:00'),
end: moment(values.tm[1]).format('YYYY-MM-DD 23:59:59')
}
}
delete values.tm
setSearchVal({...values, dateTimeRangeSo});
}
useEffect(() => {
let dateTimeRangeSo = {
start: moment().subtract(1,"months").format('YYYY-MM-DD 00:00:00'),
end: moment().format('YYYY-MM-DD 23:59:59')
}
form.setFieldValue("tm", [moment(dateTimeRangeSo.start), moment(dateTimeRangeSo.end)])
setSearchVal({ dateTimeRangeSo })
}, [])
return (
<>
<div style={{display:'flex',justifyContent:'space-between'}}>
<Form form={form} className='toolbarBox' layout="inline" onFinish={onFinish}>
<Form.Item label="预警时间" name="tm">
<RangePicker
allowClear
style={{ width: "300px" }}
format="YYYY-MM-DD"
/>
</Form.Item>
<Form.Item label="预警等级" name="warningLevel">
<NormalSelect
allowClear
style={{ width: '150px' }}
options={[
{ label: '蓝色', value: 0 },
{ label: '黄色', value: 1 },
{ label: '橙色', value: 2 },
{ label: '红色', value: 3 }
]}
/>
</Form.Item>
{searchBtn ? <Form.Item>
<Button type="primary" htmlType="submit">查询</Button>
</Form.Item> : null }
<Form.Item>
<Button onClick={() => form.resetFields()}>重置</Button>
</Form.Item>
</Form>
</div>
</>
);
}
export default ToolBar;

View File

@ -1,11 +1,11 @@
export default function jrxOptions(data = {}, type = "1",typeName='1') {
import pieMonth from "./pieMonth";
export default function jrxOptions(data = {}, type = "1", typeName = '1') {
// debugger
console.log("data",data);
console.log("data", data);
// data.rz = 112
// 根据断面类型确定y轴刻度以及最大和最小
const yMin = type == "3" ? 70 : type == "2" ? 67 :
type == '1' ? 59 : 99;
type == '1' ? 59 : 99;
const yMax = type == "3" ? 129 : type == "2" ? 117
: type == "1" ? 118 : 116;
@ -14,10 +14,10 @@ export default function jrxOptions(data = {}, type = "1",typeName='1') {
const type2 = ["UPD10", "UPD13"];
const type3 = ["UPD2", "UPD5", "UPD8", "UPD17"];
const type4 = ["UPD11", "UPD14"];
const type5 = ["UPD3", "UPD6", "UPD9"];
const type6 = ["UPD12", "UPD15"];
const type7 = ["UPD24", "UPD26","UPD28"]; //UPD24:109.87,UPD26:109.75,UPD28:109.43
const type8 = ["UPD25", "UPD27","UPD29"]; //UPD25:109.87,UPD27:109.75,UPD29:109.43
const type5 = ["UPD3", "UPD6", "UPD12"];
const type6 = ["UPD9", "UPD15"];
const type7 = ["UPD24", "UPD26", "UPD28"]; //UPD24:109.87,UPD26:109.75,UPD28:109.43
const type8 = ["UPD25", "UPD27", "UPD29"]; //UPD25:109.87,UPD27:109.75,UPD29:109.43
// 管底高程
const alltype = typeName == "1" ? type1 :
@ -28,8 +28,7 @@ export default function jrxOptions(data = {}, type = "1",typeName='1') {
typeName == "6" ? type6 :
typeName == "7" ? type7 :
typeName == "8" ? type8 :
[];
[];
// 字体颜色
const textColor = '#666'
const imageUrl370 = `${process.env.PUBLIC_URL}/assets/images/zb370.png `
@ -38,7 +37,7 @@ export default function jrxOptions(data = {}, type = "1",typeName='1') {
const imageUrl010 = `${process.env.PUBLIC_URL}/assets/images/fb010.png `
const imageUrl = type == "1" ? imageUrl130 :
type == "2" ? imageUrl250 :
type == "3" ? imageUrl370 : imageUrl010
type == "3" ? imageUrl370 : imageUrl010
;
const rule = `${process.env.PUBLIC_URL}/assets/images/ruler.png `
@ -59,73 +58,170 @@ export default function jrxOptions(data = {}, type = "1",typeName='1') {
type == "1" ?
[[0, 88], [10, 88], [xValue, data?.rz]]
:
[[0, 102], [4, 102], [xValue, data?.rz]]
[[0, 102], [4, 102], [xValue, data?.rz]]
: [];
// gz1、gz2、gz3、gz4分别为渗压管
const gz1 = data[alltype[0]] ?
type == "3" ? [[typeName =='5' ?47:51, typeName =='5' ?76.56:92.18], [typeName =='5' ?47:51, data[alltype[0]]]] :
type == "2" ? [[typeName =='3' ? 45.5:50, typeName =='3' ?76.76:77.18], [typeName =='3' ? 45.5:50, data[alltype[0]]]] :
type == "1" ? [[44, typeName =='1' ?79.16:77.18], [44, data[alltype[0]]]] :
[[55, typeName =='7'?109.08:108.77], [55, data[alltype[0]]]]
: [];
// const gz1 = data[alltype[0]] ?
// type == "3" ? [[typeName == '5' ? 47 : 51, typeName == '5' ? 90.50 : 81.85], [typeName == '5' ? 47 : 51, data[alltype[0]]]] :
// type == "2" ? [[typeName == '3' ? 45.5 : 50, typeName == '3' ? 84.41 : 83.78], [typeName == '3' ? 45.5 : 50, data[alltype[0]]]] :
// type == "1" ? [[typeName == '1' ? 44 : 51.5, typeName == '1' ? 80.11 : 78.64], [typeName == '1' ? 44 : 51.5, data[alltype[0]]]] :
// [[55, typeName == '7' ? 109.08 : 108.77], [55, data[alltype[0]]]]
// : [];
const gz2 = data[alltype[1]] ?
type == "3" ? [[typeName =='5' ?51:62, typeName =='5' ?77.18:83.50], [typeName =='5' ?51:62, data[alltype[1]]]] :
type == "2" ? [[typeName =='3' ?50:61, typeName =='3' ?77.18:77.50], [typeName =='3' ?50:61, data[alltype[1]]]] :
type == "1" ? [[51.5, typeName =='1' ?77.18:74.5], [51.5, data[alltype[1]]]] :
[[65, typeName =='7'?109.32:109.36], [65, data[alltype[1]]]] :
[];
// const gz2 = data[alltype[1]] ?
// type == "3" ? [[typeName == '5' ? 51 : 62, typeName == '5' ? 90.94 : 84.85], [typeName == '5' ? 51 : 62, data[alltype[1]]]] :
// type == "2" ? [[typeName == '3' ? 50 : 61, typeName == '3' ? 84.04 : 81.05], [typeName == '3' ? 50 : 61, data[alltype[1]]]] :
// type == "1" ? [[typeName == '1' ? 51.5 : 58, typeName == '1' ? 78.61 : 82.75], [typeName == '1' ? 51.5 : 58, data[alltype[1]]]] :
// [[65, typeName == '7' ? 109.32 : 109.36], [65, data[alltype[1]]]] :
// [];
const gz3 = data[alltype[2]] ?
type == "3" ? [[62, 81.50], [62, data[alltype[2]]]] :
type == "2" ? [[61, 80.50], [61, data[alltype[2]]]] :
type == "1" ? [[58, 66.50], [58, data[alltype[2]]]] :
[[77, typeName =='7'?109.01:108.87], [77, data[alltype[2]]]] : [];
// const gz3 = data[alltype[2]] ?
// type == "3" ? [[62, 92.65], [62, data[alltype[2]]]] :
// type == "2" ? [[61, 83.93], [61, data[alltype[2]]]] :
// type == "1" ? [[58, 81.38], [58, data[alltype[2]]]] :
// [[77, typeName == '7' ? 109.01 : 108.87], [77, data[alltype[2]]]] : [];
const gz4 = data[alltype[3]] ?
type == "3" ? [] :
type == "2" ? [[79, 73.50], [79, data[alltype[3]]]] :
type == "1" ? [[79, 72.50], [79, data[alltype[3]]]] :
[[85, 166], [85, data[alltype[3]]]] : [];
// const gz4 = data[alltype[3]] ?
// type == "3" ? [] :
// type == "2" ? [[79, 79.57], [79, data[alltype[3]]]] :
// type == "1" ? [[79, 79.09], [79, data[alltype[3]]]] :
// [[85, 166], [85, data[alltype[3]]]] : [];
// 将渗压管连起来的线
// 心墙中心两点(插入到连线中)
const xCenter = type === "3" ? 49 : (type === "2" ? 47.5 : (type === "1" ? 47.5 : 60));
// 简化规则typeName 为 2/4/6 → 渗压管都在右侧FB0+010/FB0+030typeName 为 7/8不绘制心墙连线
const rightSideOnly = ['2', '4', '6'].includes(typeName);
const skipCenterLine = ['7', '8'].includes(typeName);
// 左/右管取值(右侧优先 alltype[0],无则 alltype[1]
const leftSyg = rightSideOnly ? undefined : data[alltype[0]];
const rightSyg = rightSideOnly ? (data[alltype[0]] ?? data[alltype[1]]) : data[alltype[1]];
let midPts = [];
if (!skipCenterLine && data?.rz) {
if (leftSyg !== undefined && leftSyg !== null) {
const A = leftSyg - 1;
const B = (rightSyg !== undefined && rightSyg !== null) ? (rightSyg + 0.5) : undefined;
if (B !== undefined) {
if (A > B) {
midPts = [[xCenter, A], [xCenter, B]];
} else {
midPts = [[xCenter, leftSyg], [xCenter, rightSyg]];
}
}
} else if (rightSyg !== undefined && rightSyg !== null) {
// 左边无渗压管第一个点为X, 水位-1第二个点为X, 右管+0.5
midPts = [[xCenter, data.rz - 1], [xCenter, rightSyg + 0.5]];
}
}
const baseY1 = type == "3" ? (typeName =='5' ?90.50:81.85)
: type == "2" ? (typeName =='3' ?84.41:83.78)
: type == "1" ? (typeName =='1' ?80.11:78.64)
: (typeName =='7'?109.08:108.77);
const baseY2 = type == "3" ? (typeName =='5' ?90.94:84.85)
: type == "2" ? (typeName =='3' ?84.04:81.05)
: type == "1" ? (typeName =='1' ?78.61:82.75)
: (typeName =='7'?109.32:109.36);
const baseY3 = type == "3" ? 92.65
: type == "2" ? 83.93
: type == "1" ? 81.38
: (typeName =='7'?109.01:108.87);
const baseY4 = type == "3" ? null
: type == "2" ? 79.57
: type == "1" ? 79.09
: 166;
// 各管的 X取你原来的 X
const x1 = type == "3" ? (typeName =='5' ?47:51)
: type == "2" ? (typeName =='3' ? 45.5:50)
: type == "1" ? (typeName =='1' ?44:51.5)
: 53.5;
const x2 = type == "3" ? (typeName =='5' ?51:63)
: type == "2" ? (typeName =='3' ?50:61)
: type == "1" ? (typeName =='1' ?51.5:61)
: 66;
const x3 = type == "3" ? 63
: type == "2" ? 61
: type == "1" ? 61
: 77;
const x4 = type == "3" ? null
: type == "2" ? 79
: type == "1" ? 81
: 85;
// 管口高程(可能为空)
const mouth1 = pieMonth[alltype[0]];
const mouth2 = pieMonth[alltype[1]];
const mouth3 = pieMonth[alltype[2]];
const mouth4 = pieMonth[alltype[3]];
// 蓝色段:从“管口(若有)/管底”到 UPD 值
const gz1 = data[alltype[0]] ? [[x1, baseY1], [x1, data[alltype[0]]]] : [];
const gz2 = data[alltype[1]] ? [[x2, baseY2], [x2, data[alltype[1]]]] : [];
const gz3 = data[alltype[2]] ? [[x3, baseY3], [x3, data[alltype[2]]]] : [];
const gz4 = data[alltype[3]] && x4 !== null ? [[x4, baseY4], [x4, data[alltype[3]]]] : [];
// 灰色段:从“管底”到“管口”(仅当 mouth 存在)
const gk1 = (mouth1 !== null && mouth1 !== undefined) ? [[x1, baseY1], [x1, mouth1]] : [];
const gk2 = (mouth2 !== null && mouth2 !== undefined) ? [[x2, baseY2], [x2, mouth2]] : [];
const gk3 = (mouth3 !== null && mouth3 !== undefined) ? [[x3, baseY3], [x3, mouth3]] : [];
const gk4 = (mouth4 !== null && mouth4 !== undefined && x4 !== null) ? [[x4, baseY4], [x4, mouth4]] : [];
// 将渗压管连起来的线(插入 midPts
const line = data?.rz ?
type == "3" ?
[
[xValue, data?.rz],
[typeName =='5' ?47:51, data[alltype[0]]],
[typeName =='5' ?51:62, data[alltype[1]]],
[62, data[alltype[2]]],
...(rightSideOnly ? midPts : []),
[typeName == '5' ? 47 : 51, data[alltype[0]]],
...(!rightSideOnly ? midPts : []),
[typeName == '5' ? 51 : 63, data[alltype[1]]],
[63, data[alltype[2]]],
] :
type == "2" ?
[
[xValue, data?.rz],
[typeName =='3' ? 45.5:50, data[alltype[0]]],
[typeName =='3' ?50:61, data[alltype[1]]],
...(rightSideOnly ? midPts : []),
[typeName == '3' ? 45.5 : 50, data[alltype[0]]],
...(!rightSideOnly ? midPts : []),
[typeName == '3' ? 50 : 61, data[alltype[1]]],
[61, data[alltype[2]]],
[79, data[alltype[3]]]
] :
type == "1"?
[
[xValue, data?.rz],
[44, data[alltype[0]]],
[51.5, data[alltype[1]]],
[58, data[alltype[2]]],
[79, data[alltype[3]]]
]:
[
[xValue, data?.rz],
[55, data[alltype[0]]],
[65, data[alltype[1]]],
[77, data[alltype[2]]],
[85, data[alltype[3]]]
]
type == "1" ?
[
[xValue, data?.rz],
...(rightSideOnly ? midPts : []),
[typeName == '1' ? 44 : 51.5, data[alltype[0]]],
...(!rightSideOnly ? midPts : []),
[typeName == '1' ? 51.5 : 61, data[alltype[1]]],
[61, data[alltype[2]]],
[81, data[alltype[3]]]
] :
[
[xValue, data?.rz],
...(rightSideOnly ? midPts : []),
[53.5, data[alltype[0]]],
...(!rightSideOnly ? midPts : []),
[66, data[alltype[1]]],
[77, data[alltype[2]]],
[85, data[alltype[3]]]
]
: []
const filteredArray = line.filter(subArray => {
return subArray.every(item => item !== null && item !== undefined);
return subArray.every(item => item !== null && item !== undefined);
});
@ -219,7 +315,7 @@ export default function jrxOptions(data = {}, type = "1",typeName='1') {
},
yAxis: {
type: 'value',
min:yMin,
min: yMin,
max: yMax,
interval: 5,
// data:[155,160,180,190,210],
@ -275,7 +371,7 @@ export default function jrxOptions(data = {}, type = "1",typeName='1') {
origin: "end",
color: 'rgba(0, 128, 255, 0.3)' // 设置区域填充颜色
},
data:[...rz, ...rz1]
data: [...rz, ...rz1]
},
{
@ -322,6 +418,42 @@ export default function jrxOptions(data = {}, type = "1",typeName='1') {
},
data: rz1
},
{
type: 'line',
symbol: 'none',
symbolSize: 10,
z: 1,
itemStyle: { color: 'rgba(154, 160, 166,.4)' },
lineStyle: { color: 'rgba(154, 160, 166,.4)', width: 6 },
data: gk1
},
{
type: 'line',
symbol: 'none',
symbolSize: 10,
z: 1,
itemStyle: { color: 'rgba(154, 160, 166,.4)' },
lineStyle: { color: 'rgba(154, 160, 166,.4)', width: 6 },
data: gk2
},
{
type: 'line',
symbol: 'none',
symbolSize: 10,
z: 1,
itemStyle: { color: 'rgba(154, 160, 166,.4)' },
lineStyle: { color: 'rgba(154, 160, 166,.4)', width: 6 },
data: gk3
},
{
type: 'line',
symbol: 'none',
symbolSize: 10,
z: 1,
itemStyle: { color: 'rgba(154, 160, 166,.4)' },
lineStyle: { color: 'rgba(154, 160, 166,.4)', width: 6 },
data: gk4
},
// 管位
{
type: 'line',
@ -385,15 +517,15 @@ export default function jrxOptions(data = {}, type = "1",typeName='1') {
symbol: 'none',
symbolSize: 10,
z: 1,
smooth: 0.6, // 设置平滑度
smoothMonotone: 'x', // 保持 x 方向的单调性
// smooth: 0.6, // 设置平滑度
// smoothMonotone: 'x', // 保持 x 方向的单调性
itemStyle: {
color: '#5487FF'
},
lineStyle: {
color: '#5487FF',
width: 2,
curveness: 0.5 // 增加曲线程度
width: 2,
// curveness: 0.5 // 增加曲线程度
},
data: filteredArray
},

View File

@ -0,0 +1,40 @@
export default {
// ZB0+130
UPD1: 113.66,
UPD4: 113.66,
UPD7: 105.27,
UPD16: 89.54,
// ZB0+132
UPD10: 113.66,
UPD13: 105.38,
// ZB0+250
UPD2: 113.98,
UPD5: 114.53,
UPD8: 104.45,
UPD17: 91.33,
// ZB0+252
UPD11: 114.36,
UPD14: 104.49,
// ZB0+370
UPD3: 114.12,
UPD6: 114.34,
UPD12: 104.52,
// ZB0+372
UPD9: 114.28,
UPD15: 105.16,
// FB0+010
UPD24: 114.22,
UPD26: 114.44,
UPD28: 112.28,
// FB0+030
UPD25: 114.22,
UPD27: 114.48,
UPD29: 112.28,
};

View File

@ -6,34 +6,34 @@ import { exportFile } from '../../../utils/tools';
import apiurl from '../../../service/apiurl';
import usePageTable from '../../../components/crud/usePageTable2';
import { createCrudService } from '../../../components/crud/_';
export default function ModalContent({zfjkData}) {
export default function ModalContent({ item }) {
const myType = {
// 闸前水位站 2闸后水位站 3流量站
'1': '工作闸门',
'2': '检修闸门',
}
const columns = [
{
title: '序号',
key: 'inx',
dataIndex:'inx',
width: 80,
},
{
title: '闸门名称',
key: 'stcd',
dataIndex:'stcd',
width: 150,
render: (v) => <span>{myType[v]}</span>
},
{
title: '闸阀名称',
key: 'valveName',
dataIndex:'valveName',
title: '当前开度(m)',
key: 'gtop',
dataIndex:'gtop',
width: 150,
},
{
title: '操作内容',
dataIndex:'opContent',
key: 'opContent',
width: 150,
},
{
title: '操作结果',
dataIndex:'status',
key: 'status',
width: 150,
},
{
title: '操作时间',
title: '数据采集时间',
dataIndex:'tm',
key: 'tm',
width: 150,
@ -41,7 +41,7 @@ export default function ModalContent({zfjkData}) {
]
const [searchVal, setSearchVal] = useState(false)
// 闸阀弹框更多数据
const { tableProps, search, refresh } = usePageTable(createCrudService(apiurl.gsxl.zfzl.historypage).find_noCode);
const { tableProps, search, refresh } = usePageTable(createCrudService(apiurl.gsxl.zfzl.historypage1).find_noCode);
const exportExcel = () => {
let params = {
...searchVal,
@ -50,7 +50,7 @@ export default function ModalContent({zfjkData}) {
pageSize:tableProps.pagination.pageSize
}
}
httppost5(apiurl.gsxl.zfzl.historyPageExport, params).then(res => {
httppost5(apiurl.gsxl.zfzl.historyPageExport1, params).then(res => {
exportFile(`闸门操作记录.xlsx`,res.data)
})
}
@ -70,7 +70,7 @@ export default function ModalContent({zfjkData}) {
<ModalToolBar
setSearchVal={setSearchVal}
exportFile={exportExcel}
list={zfjkData}
item={item}
/>
</Card>

View File

@ -4,7 +4,7 @@ import { Form, Input, Button, DatePicker } from 'antd';
import moment from 'moment';
import NormalSelect from '../../../components/Form/NormalSelect';
const { RangePicker } = DatePicker;
const ToolBar = ({ setSearchVal, exportFile,list }) => {
const ToolBar = ({ setSearchVal, exportFile,item }) => {
const optionsType = [
{
label: "今日",
@ -70,27 +70,27 @@ const ToolBar = ({ setSearchVal, exportFile,list }) => {
}
useEffect(() => {
if (list.length > 0) {
if (item) {
let dateTimeSo = {
start: moment().subtract(7, "days").format('YYYY-MM-DD 00:00:00'),
end: moment().format('YYYY-MM-DD 00:00:00')
}
form.setFieldValue("tm", [moment(dateTimeSo.start), moment(dateTimeSo.end)])
form.setFieldValue("valveCode", list[0]?.valveCode)
setSearchVal({dateTimeRangeSo:dateTimeSo, valveCode:list[0]?.valveCode})
form.setFieldValue("stcd", item.stcd)
setSearchVal({dateTimeRangeSo:dateTimeSo, stcd:item.stcd})
}
}, [list])
}, [item])
return (
<>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Form form={form} className='toolbarBox' layout="inline" onFinish={onFinish} onValuesChange={onValuesChange}>
<Form.Item label="闸门名称" name="valveCode">
<Form.Item label="闸门名称" name="stcd">
<NormalSelect
style={{ width: '150px' }}
options={list}
options={[{label:'工作闸门',value:"1"},{label:'检修闸门',value:"2"}]}
allowClear={false}
fieldNames={{label: 'valveName',value: 'valveCode'}}
disabled={true}
/>
</Form.Item>
<Form.Item label="日期范围" name="tm">

View File

@ -17,165 +17,12 @@ import ModalContent from './ModalContent';
const url = "http://223.75.53.141:9100/gs-tsg"
const CanvasW = 1080
const CanvasH = 640
// const waterRatio = 0
const zmobj = {
"hpCode": "HP0074208040002120",
"stcd": "4265630075",
"ctrlType": "PLC",
"ctrlProtocol": "PLC",
"uprzStcd": null,
"dwrzStcd": null,
"flowStcd": null,
"gaType": "waga",
"ctrlPass": null,
"maxHgt": 1.9,
"minHgt": 0,
"name": "五岭包节制闸",
"ghtX": null,
"ghtY": null,
"irrCode": "D00000020",
"irrName": "三干渠",
"engCode": "ENG100076",
"engName": "三干渠管理处",
"orgCode": "A07",
"gaorNum": 3,
"wagaType": "节制闸",
"plcType": null,
"bim": 0,
"vip": 0,
"miu": null,
"lgtd": 112.242945,
"lttd": 30.848166,
"runtime": [
null,
{
"stcd": "4265630075",
"gateNumber": 1,
"realAperture": 376,
"setAperture": 0,
"sensorLever": null,
"altitudeLever": null,
"remoteSignal": 0,
"powerSignal": 0,
"openingSignal": 0,
"closeingSignal": 0,
"errorSignal": 0,
"openedSignal": 0,
"closedSignal": 0,
"tm": "2024-09-25 20:03:26",
"_online": true
},
{
"stcd": "4265630075",
"gateNumber": 2,
"realAperture": 388,
"setAperture": 0,
"sensorLever": null,
"altitudeLever": null,
"remoteSignal": 0,
"powerSignal": 0,
"openingSignal": 0,
"closeingSignal": 0,
"errorSignal": 0,
"openedSignal": 0,
"closedSignal": 0,
"tm": "2024-09-25 20:03:26",
"_online": true
},
{
"stcd": "4265630075",
"gateNumber": 3,
"realAperture": 394,
"setAperture": 0,
"sensorLever": null,
"altitudeLever": null,
"remoteSignal": 0,
"powerSignal": null,
"openingSignal": 0,
"closeingSignal": 0,
"errorSignal": 0,
"openedSignal": 0,
"closedSignal": 0,
"tm": "2024-09-25 20:03:26",
"_online": true
}
],
"real": {
"stcd": "4265630075",
"stationName": "五岭包节制闸",
"z1": null,
"zz1": null,
"z1tm": null,
"z2": null,
"zz2": null,
"z2tm": null,
"hq": null,
"hqtm": null,
"demtl": null
},
"cctvs": [],
"_idx": 88,
"_fav": false,
"_sort": 10086
}
const runtime = [
null,
{
"stcd": "4265630075",
"gateNumber": 1,
"realAperture": 976,
"setAperture": 0,
"sensorLever": null,
"altitudeLever": null,
"remoteSignal": 0,
"powerSignal": 0,
"openingSignal": 0,
"closeingSignal": 0,
"errorSignal": 0,
"openedSignal": 0,
"closedSignal": 0,
"tm": "2024-09-25 20:03:31"
},
{
"stcd": "4265630075",
"gateNumber": 2,
"realAperture": 388,
"setAperture": 0,
"sensorLever": null,
"altitudeLever": null,
"remoteSignal": 0,
"powerSignal": 0,
"openingSignal": 0,
"closeingSignal": 0,
"errorSignal": 0,
"openedSignal": 0,
"closedSignal": 0,
"tm": "2024-09-25 20:03:31"
},
{
"stcd": "4265630075",
"gateNumber": 3,
"realAperture": 394,
"setAperture": 0,
"sensorLever": null,
"altitudeLever": null,
"remoteSignal": 0,
"powerSignal": null,
"openingSignal": 0,
"closeingSignal": 0,
"errorSignal": 0,
"openedSignal": 0,
"closedSignal": 0,
"tm": "2024-09-25 20:03:31"
}
]
const myType = {
// 闸前水位站 2闸后水位站 3流量站
'1': '闸前水位/水深(m)',
'2': '闸后水位/水深(m)',
'3': '流量 (m³/s)',
'1': '工作闸门',
'2': '检修闸门',
}
const Page = () => {
@ -203,15 +50,16 @@ const Page = () => {
const zfColumns = [
{
title: '闸名称',
key: 'valveName',
dataIndex:'valveName',
title: '闸名称',
key: 'stcd',
dataIndex:'stcd',
width: 150,
render: (v) => <span>{myType[v]}</span>
},
{
title: '当前开关状态',
key: 'status',
dataIndex:'status',
title: '当前开度(m)',
key: 'gtop',
dataIndex:'gtop',
width: 150,
},
{
@ -263,9 +111,9 @@ const Page = () => {
const pts = contextCoordinates(xunit, hole);
const eqpnoList = useMemo(() => damList ? new Array(damList.length).fill(0).map((o, index) => index) : [], [damList]);
useEffect(() => {
getList()
}, [])
// useEffect(() => {
// getList()
// }, [])
const getList = async () => {
const { code, data } = await httppost2(apiurl.zmjk.video)
@ -335,9 +183,10 @@ const Page = () => {
// 闸阀监控列表数据
const [zfjkData, setzfjkData] = useState([])
const [rowItem, setRowItem] = useState({})
const getZfjkData = async () => {
try {
const res = await httppost2(apiurl.gsxl.zfzl.list)
const res = await httppost2(apiurl.gsxl.zfzl.list1)
if (res.code == 200) {
setzfjkData(res.data)
}
@ -426,8 +275,8 @@ const Page = () => {
useEffect(() => {
getZfjkData()
getZfjkHistoryData()
getZfjkSwData()
// getZfjkHistoryData()
// getZfjkSwData()
}, [])
@ -438,20 +287,28 @@ const Page = () => {
{/* <Card className='nonebox'>
</Card> */}
<div className="ant-card-body" style={{ padding: "20px 0 0 0" }}>
<div className="sg_zmjk_left">
<div className='sg_zmjk_left_title'>
<div className="sg_zmjk_left" style={{width:'100%'}}>
{/* <div className='sg_zmjk_left_title'>
<Divider type="vertical" style={{ width: 5, background: '#259def', height: 20 }} />
闸阀监控
</div>
</div> */}
<div className='sz_left_up_table'>
<Table
columns={zfColumns}
rowKey={(record) => record.tm}
rowKey={(record) => record.id}
dataSource={zfjkData}
pagination={false}
onRow={
(data)=>({
onClick:()=>{
setOpen(true)
setRowItem(data)
}
})
}
/>
</div>
<div className='sg_zmjk_left_title' style={{ marginTop: 150,display:'flex',justifyContent:'space-between' }}>
{/* <div className='sg_zmjk_left_title' style={{ marginTop: 150,display:'flex',justifyContent:'space-between' }}>
<div><Divider type="vertical" style={{ width: 5, background: '#259def', height: 20 }} />
最近操作记录</div>
<div onClick={() => setOpen(true)}>
@ -468,76 +325,24 @@ const Page = () => {
dataSource={zfjkHistoryListData}
pagination={false}
/>
</div>
</div> */}
</div>
{/* <div className="sg_zmjk_left">
<Stage width={1080} height={640}>
<Sider pts={pts} side="left" />
<Sider pts={pts} side="right" />
<Topper1 pts={pts} type={hole} />
<ZmColumns runtime={damList} zmobj={zmobj} pts={pts} waterRatio={waterRatio} />
<Topper2 pts={pts} waterRatio={waterRatio} />
</Stage>
<div style={{ position: 'absolute', left: 0, top: 20, width: '100%', height: 100, display: 'flex', alignContent: 'center' }}>
<div key="sider1" style={{ flexGrow: 1, width: 100 }}></div>
{
eqpnoList.map(o => (
<div key={o}
onClick={() => {}}
className='o' style={{ flexGrow: 1, width: 100, display: 'flex', justifyContent: 'center', cursor: 'pointer' }}>
<div style={{ width: 80, height: 40, backgroundColor: '#43c4e7', borderRadius: 12, color: '#fff', display: 'flex', justifyContent: 'center', alignItems: 'center', fontSize: 28 }}>#{o+1}</div>
</div>
))
}
<div key="sider2" style={{ flexGrow: 1, width: 100 }}></div>
</div>
<div style={{ position: 'absolute', left: 0, bottom: 20, width: '100%', height: 100, display: 'flex', alignContent: 'center' }}>
<div key="sider1" style={{ flexGrow: 1, width: 100 }}></div>
{
eqpnoList.map(o => (
<div key={o} className='o' style={{ flexGrow: 1, width: 100, display: 'flex', justifyContent: 'center' }}>
<div
onClick={() => {}}
style={{ width: 80, height: 32, border: '1px solid #444', backgroundColor: '#fff', borderRadius: 4, color: '#888', display: 'flex', justifyContent: 'center', alignItems: 'center', fontSize: 18, cursor: 'pointer' }}
>
{renAperture(damList[o]?.realAperture)}
</div>
</div>
))
}
<div key="sider2" style={{ flexGrow: 1, width: 100 }}></div>
</div>
</div> */}
<div className="sg_zmjk_right">
{/* <div className="sg_zmjk_right">
<div className='sg_zmjk_right_video'>
<div className='sg_zmjk_right_video_title'>
<Divider type="vertical" style={{ width: 5, background: '#259def', height: 20 }} />
监控视频</div>
<div className='sg_zmjk_right_video_content'>
{/* <div className='sg_zmjk_right_video_content_left'>
{
videoList.map((item,index)=>(
<div className={index===itemIndex?'sg_zmjk_right_video_content_left_item itemChecked':'sg_zmjk_right_video_content_left_item'} onClick={()=>{setItemIndex(index);getVideoSrc(item.indexCode)}}>
{item.name}
</div>
))
}
</div> */}
<div className='sg_zmjk_right_video_content_right'>
{
videoArr?.src &&
<div
className="content-video"
style={{ width: '100%', height: '100%', cursor: "pointer" }}
onClick={() => {
// if (controlerParams.type == 1) {
// setVideoOpen(true)
// setIsShow(!isShow)
// }
}}
>
<HFivePlayer size={1} wsUrl={videoArr} playerID={'111'} />
{/* <div style={{textAlign:"right"}}>注:单击视频显示/隐藏云台</div> */}
</div>
}
</div>
@ -554,39 +359,11 @@ const Page = () => {
dataSource={zfjkSwData}
pagination={false}
/>
{/* {
list?.map((item)=>{
if(item.type===1){
return (
<div className='sg_zmjk_right_information_content'>
<div>{myType[item.type]}</div>
<div>{item.value||'-'} / {getNum(item.value,data.inEle)}</div>
<div>{item.tm?.slice(5,19)}</div>
</div>
)
}else if(item.type===2){
return (
<div className='sg_zmjk_right_information_content'>
<div>{myType[item.type]}</div>
<div>{item.value||'-'} / {getNum(item.value,data.inEle)}</div>
<div>{item.tm?.slice(5,19)}</div>
</div>
)
}else{
return (
<div className='sg_zmjk_right_information_content'>
<div>{myType[item.type]}</div>
<div>{item.value}</div>
<div>{item.tm?.slice(5,19)}</div>
</div>
)
}
})
} */}
</div>
</div>
{/* <div className='sg_zmjk_right_more' onClick={() => setOpen(true)}>查看更多信息</div> */}
</div>
<div className='sg_zmjk_right_more' onClick={() => setOpen(true)}>查看更多信息</div>
</div> */}
</div>
</div>
</div>
@ -601,28 +378,7 @@ const Page = () => {
}}
>
<div style={{ height: '600px' }}>
<ModalContent zfjkData={zfjkData} />
{/* <Tabs>
<Tabs.TabPane tab="基本信息" key="item-1">
<Descriptions bordered size="small" column={3} >
<Descriptions.Item label="启闭设备类型" style={{ width: '16.5%' }}>{{ 1: '卷扬式', 2: '螺杆式', 3: '凹轮式', 4: '涡轮式', 5: '丝杆式' }?.[data?.hdgrTp] || '-'}</Descriptions.Item>
<Descriptions.Item label="动力类型" style={{ width: '16.5%' }}>{{ 1: '手动', 2: '电动', 3: '手电两用' }?.[data?.pwrTp] || '-'}</Descriptions.Item>
<Descriptions.Item label="水闸类型" style={{ width: '16.5%' }}>{{ 1: '分(泄)洪闸', 2: '节制闸', 3: '排(退)水闸', 4: '引(进)水闸', 5: '挡潮闸', 6: '船闸', 9: '其他' }?.[data?.wagaType] || '-'}</Descriptions.Item>
<Descriptions.Item label="进口高程">{data?.inEle || '-'} m</Descriptions.Item>
<Descriptions.Item label="出口高程">{data?.outEle || '-'} m</Descriptions.Item>
<Descriptions.Item label="闸门孔数">{data?.gaorNum || '-'} </Descriptions.Item>
<Descriptions.Item label="设计流量">{data?.dsfl || '-'} /s</Descriptions.Item>
<Descriptions.Item label="实达流量">{data?.stfl || '-'} /s</Descriptions.Item>
<Descriptions.Item label="闸门尺寸">{data?.gateSize || '-'} m*m</Descriptions.Item>
<Descriptions.Item label="工程等级">{{ 1: '', 2: 'Ⅱ', 3: 'Ⅲ', 4: 'Ⅳ', 5: '' }?.[data?.engGrad] || '-'}</Descriptions.Item>
<Descriptions.Item label="运行状况">{{ 1: '在用良好', 2: '在用故障', 3: '停用' }?.[data?.runStat] || '-'}</Descriptions.Item>
<Descriptions.Item label="建成时间">{data?.compDate || '-'}</Descriptions.Item>
</Descriptions>
</Tabs.TabPane>
<Tabs.TabPane tab="工程图片" key="item-2">
<Image width={800} src={url + data?.files?.filePath} alt='' />
</Tabs.TabPane>
</Tabs> */}
<ModalContent item={rowItem} />
</div>
</Modal>
</>

View File

@ -1,5 +1,7 @@
import React from 'react'
import { Image } from 'antd'
import HFivePlayer from '../../../components/video1Plary'
export default function Card({record}) {
return (
<div className='card-box' style={{border:"1px solid #fff",boxShadow:"0 0 10px #c0c0c0",padding:10,marginBottom:10,marginLeft:10}}>
@ -7,6 +9,10 @@ export default function Card({record}) {
src={record?.imgPath}
style={{width:360,height:220}}
></Image>
{/* <div style={{width:360,height:220}}>
<HFivePlayer size={1} wsUrl={record?.resIndexCode} playerID={record?.id} />
</div> */}
<div style={{display:"flex",alignItems:"center",columnGap:5}}>
<span style={{
padding: "4px 2px",

View File

@ -1,75 +1,128 @@
import React, { Fragment, useRef, useMemo,useEffect,useState } from 'react';
import { Table, Card,Modal,Form,Input,Button,Row,Pagination,message } from 'antd';
import React, { Fragment, useRef, useMemo, useEffect, useState } from 'react';
import { Table, Card, Modal, Form, Input, Button, Row, Pagination, message } from 'antd';
import { useSelector } from 'react-redux';
import ToolBar from './toolbar';
import apiurl from '../../../service/apiurl';
import usePageTable from '../../../components/crud/usePageTable3';
import { createCrudService } from '../../../components/crud/_';
import { httpget2,httppost2 } from '../../../utils/request';
import HFivePlayer from '../../../components/video1Plary';
import CardShow from "./Card"
import "./index.less"
const Page = () => {
const role = useSelector(state => state.auth.role);
const [searchVal, setSearchVal] = useState(false)
const { tableProps, search, refresh } = usePageTable(createCrudService(apiurl.spjk1.aiWarn.page).find_noCode);
const onchange = (page, pageSize) => {
const obj = {
pageNumber: page,
pageSize: pageSize,
}
const searchParams = {
...obj,
// search: {
// ...searchVal,
// }
}
search(searchParams)
import moment from 'moment';
const Page = (props) => {
const tm = props?.tm;
const statusObj = {
0: "未处理",
1: '已处理'
};
const levelStatus = {
1: '低',
2: '中',
3: '高'
}
const role = useSelector(state => state.auth.role);
const columns = [
{ title: '序号', key: 'inx', dataIndex: 'inx', width: 60, align: "center" },
{ title: '事件源名称', key: 'resName', dataIndex: 'resName', width: 150, ellipsis: true },
{
title: '事件类型名称', key: 'eventTypeName', dataIndex: 'eventTypeName', width: 150, ellipsis: true
},
{ title: '事件处理状态', key: 'handleStatus', dataIndex: 'handleStatus', width: 120, render: (v) => <span>{statusObj[v]}</span> },
{ title: '事件等级', key: 'eventLevel', dataIndex: 'eventLevel', width: 100, render: (v) => <span>{levelStatus[v]}</span> },
{ title: '事件开始时间', key: 'startTime', dataIndex: 'startTime', width: 150, render: (v) => <span>{v ? moment(v).format("YYYY-MM-DD HH:mm:ss") : ''}</span> },
{ title: '事件结束时间', key: 'endTime', dataIndex: 'endTime', width: 150, render: (v) => <span>{v ? moment(v).format("YYYY-MM-DD HH:mm:ss") : ''}</span> },
// {
// title: '操作', key: 'opr', dataIndex: 'opr', width: 100, align: "center",
// render:(v,r)=><Button type="link" onClick={()=>replay(r)}>回放</Button>
// },
];
const [searchVal, setSearchVal] = useState(false)
const [replayOpen, setReplayOpen] = useState(false)
const [replayItem, setReplayItem] = useState({})
const [videoSrc, setVideoSrc] = useState('')
const { tableProps, search, refresh } = usePageTable(createCrudService(apiurl.spjk1.aiWarn.page1).find_noCode);
const width = useMemo(() => columns.reduce((total, cur) => total + (cur.width), 0), [columns]);
const replay = (r) => {
setReplayOpen(true);
setReplayItem(r)
getVideoSrc(r)
}
const getVideoSrc = async(data) => {
try {
// 仅获取播放地址,回放时间由播放器拼接
const res = await httpget2(`${apiurl.gsxl.zfzl.videosrc}${data.resIndexCode}`)
setVideoSrc(res.data)
} catch (error) {
console.log(error);
}
}
// 当外部传入 tm[start,end])时,初始化查询条件
useEffect(() => {
if (tm && Array.isArray(tm) && tm[0]) {
setSearchVal({
startTime: moment(tm[0]).format('YYYY-MM-DD HH:mm:ss'),
})
}
}, [tm])
// 路由直接进入 AI 告警页(没有 tm初始化一次默认查询
useEffect(() => {
if (!tm) {
setSearchVal({})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
const params = {
search: {
...searchVal,
}
};
search(params)
// 避免初始化时触发空查询造成二次请求闪烁
if (searchVal !== false) {
search(params)
}
}, [searchVal])
return (
<>
<div className='content-root clearFloat xybm' style={{paddingRight:"0",paddingBottom:"0"}}>
<div className='content-root clearFloat xybm' style={{ paddingRight: "0", paddingBottom: "0" }}>
<div className='lf CrudAdcdTreeTableBox' style={{ width: "100%" }}>
<Card className='nonebox'>
<ToolBar
setSearchVal={setSearchVal}
role={role}
tm={tm}
/>
</Card>
<div className="ant-card-body" style={{padding:"20px 0 0 0",display:"flex",flexWrap:"wrap",maxHeight:650,overflowY:"auto"}}>
{
tableProps.dataSource.length > 0 ?
tableProps.dataSource.map((item,i) => {
return (
<div key={i} >
<CardShow
record={item}
/>
</div>
)
}) : null
}
<div className="ant-card-body" style={{ padding: "20px 0 0 0" }}>
<Table columns={columns} rowKey="inx" {...tableProps} scroll={{ x: width, y: "calc( 100vh - 500px )" }} />
</div>
<div style={{textAlign: "right", marginTop: "25px",marginRight:40}}>
<Pagination
current={tableProps.pagination.current}
total={tableProps.pagination.total}
showTotal={tableProps.pagination.showTotal}
pageSize={tableProps.pagination.pageSize}
showSizeChanger
showQuickJumper
pageSizeOptions={[4, 8, 12, 16]}
onChange={onchange}
/>
</div>
</div>
<Modal
open={replayOpen}
width={800}
title={replayItem?.resName}
onCancel={() => setReplayOpen(false)}
footer={null}
destroyOnClose
>
<div style={{ width: "100%", height: 500 }}>
<HFivePlayer
size={1}
wsUrl={{
src: videoSrc,
indexCode: replayItem?.resIndexCode,
beginTime: replayItem?.startTime,
endTime: replayItem?.endTime,
}}
playerID={replayItem?.id} />
</div>
</Modal>
</div>
</>
);

View File

@ -5,26 +5,22 @@ import moment from 'moment';
import { httppost2 } from '../../../utils/request';
import apiurl from '../../../service/apiurl';
const { RangePicker } = DatePicker;
const ToolBar = ({ setSearchVal, onSave, storeData, role }) => {
const ToolBar = ({ setSearchVal, onSave, storeData, role, tm }) => {
const searchBtn = role?.rule?.find(item => item.menuName == "查询")|| true;
const warnTypes = [
{
label: "人员闯入",
label: "",
value:1
},
{
label: "工程车辆识别",
label: "",
value:2
},
{
label: "漂浮物识别",
label: "",
value:3
},
{
label: "游泳识别",
value:4
},
}
]
const [form] = Form.useForm();
const [codeList, setCodeList] = useState([])
@ -38,46 +34,30 @@ const ToolBar = ({ setSearchVal, onSave, storeData, role }) => {
}
}
const onFinish = (values) => {
let dateSo;
if (values.tm) {
dateSo = {
start: moment(values.tm[0]).format('YYYY-MM-DD 00:00:00'),
end: moment(values.tm[1]).format('YYYY-MM-DD 23:59:59')
}
if (values.startTime) {
values.startTime = moment(values.startTime).format('YYYY-MM-DD HH:mm:ss');
}
delete values.tm
setSearchVal({...values, dateTimeRangeSo:dateSo});
setSearchVal({...values});
}
useEffect(() => {
getStationCode()
let time = [moment().subtract(1,"weeks"),moment()]
let dateSo = {
start:moment(time[0]).format('YYYY-MM-DD 00:00:00'),
end:moment(time[1]).format('YYYY-MM-DD 23:59:59'),
}
form.setFieldValue("tm",time)
setSearchVal({dateTimeRangeSo:dateSo})
}, [])
// 预填开始时间(来自外部 tm
useEffect(() => {
if (tm && Array.isArray(tm) && tm[0]) {
form.setFieldsValue({ startTime: moment(tm[0]) })
}
}, [tm])
return (
<>
<div style={{display:'flex',justifyContent:'space-between'}}>
<Form form={form} className='toolbarBox' layout="inline" onFinish={onFinish} >
<Form.Item label="告警日期" name="tm">
<RangePicker
<Form.Item label="事件开始时间" name="startTime">
<DatePicker
allowClear
style={{ width: "350px" }}
format="YYYY-MM-DD"
style={{ width: "200px" }}
showTime
placeholder='请选择时间'
/>
</Form.Item>
<Form.Item label="告警点位" name="indexCode">
<NormalSelect
allowClear
style={{ width: "150px" }}
options={codeList}
/>
</Form.Item>
<Form.Item label="告警类型" name="type">
<Form.Item label="事件等级" name="eventLevel">
<NormalSelect
allowClear
style={{ width: "150px" }}

View File

@ -0,0 +1,72 @@
export default function hotOption(data) {
const result = data.map(item => {
if (item.menu3 && item.menu3 == '布置图') item.menu2 = '';
return{
...item,
menu: item.menu1 + "-" + item.menu2 + (item?.menu3 ? "-" + item.menu3 : '')
}
})
const maxY = Math.ceil(Math.max(...data.map(item => item.count)))
const minY = Math.floor(Math.min(...data.map(item => item.count)))
return {
grid: {
left:"25%",
top: "0%",
bottom:"0%"
},
tooltip: {
trigger: "axis",
},
calculable: true,
xAxis: {
type: "value",
min:minY,
max: maxY,
axisLine: {
show: false
},
axisTick: {
show: false
},
splitLine: {
show: false
}
},
yAxis: {
type: "category",
inverse: true,
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
// textStyle: {
// color: '#999'
// },
fontSize:14//调整坐标轴字体大小
},
data: result.map(item => item.menu),
},
series: [
{
name: "",
type: "bar",
barWidth:"30%",
label: {
normal: {
show: true,
// position: "insideRight",
},
offset:[100,0]
},
itemStyle: {
color:"#1283e3"
},
data: result.map(item => item.count),
},
],
}
}

254
src/views/yhxwfx/index.js Normal file
View File

@ -0,0 +1,254 @@
import React, { Fragment, useRef, useMemo,useEffect,useState } from 'react';
import { Divider,Tabs,Dropdown,Space} from 'antd';
import ToolBar from './toolbar';
import { DownOutlined,UpOutlined } from '@ant-design/icons';
import apiurl from '../../service/apiurl';
import moment from 'moment';
import { httpget2, httppost2 } from '../../utils/request';
import userBarOption from "./userBarOption"
import userLineOption from "./userLineOption"
import hotOption from './hotOption';
import ReactEcharts from 'echarts-for-react';
import "./index.less"
const Page = () => {
const [searchVal, setSearchVal] = useState(false)
const [todayData, setTodayData] = useState({})
const [activeCount, setActiveCount] = useState()
const [userCountData, setUserCountData] = useState()
const [hotData, setHotData] = useState()
const [tabs, setTabs] = useState({active:0})
const [showMore, setShowMore] = useState(false)
const [height, setHeight] = useState("100%")
const [hotWidth, setHotWidth] = useState("100%")
const dtoption = useMemo(() => {
if (userCountData) {
return userBarOption(userCountData)
}
}, [userCountData])
const lineoption = useMemo(() => {
if (activeCount) {
return userLineOption(activeCount)
}
}, [activeCount])
const hotoption = useMemo(() => {
if (hotData) {
let substrData = [];
if (!showMore) {
substrData = hotData.slice(0, 10)
} else {
substrData = hotData
}
setHeight(((substrData.length) * 10) + "%")
try {
const labels = substrData.map(item => (
(item.menu1 || '') + '-' + (item.menu2 || '') + (item.menu3 ? '-' + item.menu3 : '')
));
const maxLen = Math.max(...labels.map(l => (l || '').length), 0);
const px = Math.min(Math.max(maxLen * 12 + 600, 800), 2400);
setHotWidth(px + 'px');
} catch (e) {}
return hotOption(substrData)
}
}, [hotData,showMore])
// 获取今日数据
const getTodayData = async () => {
try {
const res = await httpget2(apiurl.systemM.action.todayData)
setTodayData(res.data)
} catch (error) {
console.log(error);
}
}
// 获取日活跃数
const getActiveCount = async (params) => {
try {
const {data} = await httppost2(apiurl.systemM.action.activeCount,params)
const {appList,webList} = data
if(appList&&webList){
setActiveCount({appList:appList,webList:webList})
}
} catch (error) {
console.log(error);
}
}
// 获取前十用户活跃数
const getUserActiveCount = async (params) => {
try {
const { data } = await httppost2(apiurl.systemM.action.userCount, params)
const {appList,webList} = data
if(appList&&webList){
setUserCountData({appList:appList,webList:webList})
}
} catch (error) {
console.log(error);
}
}
// 获取热点数据
const getHotData = async (data) => {
try {
const res = await httppost2(apiurl.systemM.action.hotData, data)
setHotData(res.data)
} catch (error) {
console.log(error);
}
}
useEffect(() => {
getTodayData()
}, [])
useEffect(() => {
if (searchVal) {
getActiveCount(searchVal)
getUserActiveCount(searchVal)
}
}, [searchVal])
useEffect(() => {
if (searchVal && tabs) {
const params = {
...searchVal,
loginType:Number(tabs?.active)
}
getHotData(params)
}
}, [searchVal,tabs])
return (
<>
<div className='content-root clearFloat' style={{padding:8,paddingBottom:"0"}}>
<div className='action-top'>
<div className='comomn-title'>
<img alt='' src={`${process.env.PUBLIC_URL}/assets/panelTitle.png`} />
<span>今日数据总览</span>
</div>
<div className='data-panel'>
<div className='panel-item'>
<span className='name'>WEB端访问次数</span>
<p className='value'>{todayData?.web1Count || '-' }</p>
</div>
<Divider type="vertical" style={{fontSize:100,background:"#d7d7d7"}}/>
<div className='panel-item'>
<span className='name'>WEB端浏览次数</span>
<p className='value'>{todayData?.web2Count || '-' }</p>
</div>
<Divider type="vertical" style={{fontSize:100,background:"#d7d7d7"}} />
<div className='panel-item'>
<span className='name'>WEB端平均访问时长</span>
<p className='value'>{todayData?.web3Count || '-' }&nbsp;<span style={{fontSize:16}}>h</span></p>
</div>
<Divider type="vertical" style={{fontSize:100,background:"#d7d7d7"}} />
<div className='panel-item'>
<span className='name'>移动端访问次数</span>
<p className='value'>{todayData?.app1Count || '-'}</p>
</div>
<Divider type="vertical" style={{fontSize:100,background:"#d7d7d7"}}/>
<div className='panel-item'>
<span className='name'>移动端浏览次数</span>
<p className='value'>{todayData?.app2Count || '-'}</p>
</div>
<Divider type="vertical" style={{fontSize:100,background:"#d7d7d7"}}/>
<div className='panel-item'>
<span className='name'>移动端平均访问时长</span>
<p className='value'>{todayData?.app3Count || '-'}&nbsp;<span style={{fontSize:16}}>h</span></p>
</div>
<Divider type="vertical" style={{fontSize:100,background:"#d7d7d7"}} />
</div>
</div>
<div className='action-middle'>
<ToolBar
setSearchVal={setSearchVal}
/>
</div>
<div className='action-bottom'>
<div className='left'>
<div className='left-top'>
<div className='comomn-title'>
<img alt='' src={`${process.env.PUBLIC_URL}/assets/panelTitle.png`} />
<span>访问用户前十</span>
</div>
<div className='left-top-charts'>
<ReactEcharts
option={dtoption || {}}
style={{ width: "100%", height: '100%' }}
notMerge={true}
/>
</div>
</div>
<div className='left-bottom'>
<div className='comomn-title'>
<img alt='' src={`${process.env.PUBLIC_URL}/assets/panelTitle.png`} />
<span>日活跃用户数</span>
</div>
<div className='left-bottom-charts'>
<ReactEcharts
option={lineoption || {}}
style={{ width: "100%", height: '100%' }}
notMerge={true}
/>
</div>
</div>
</div>
<div className='right'>
<div className='right-title'>
<div className='comomn-title'>
<img alt='' src={`${process.env.PUBLIC_URL}/assets/panelTitle.png`} />
<span>功能热度前十</span>
</div>
<Tabs
defaultActiveKey="1"
onChange={(e) => { setTabs({ active: e }); setShowMore(false)}}
items={[
{
label: `web端`,
key: 0,
children: ``,
},
{
label: `移动端`,
key: 1,
children: ``,
},
]}
/>
<div className='more'>
<a onClick={(e) => { e.preventDefault(); setShowMore(!showMore) }}>
<Space>
更多
{!showMore ? <DownOutlined /> :<UpOutlined /> }
</Space>
</a>
</div>
</div>
<div className='right-charts'>
<ReactEcharts
option={hotoption || {}}
style={{ width: hotWidth, height: height }}
notMerge={true}
/>
</div>
</div>
</div>
</div>
</>
);
}
export default Page;

View File

@ -0,0 +1,93 @@
.action-top{
width: 100%;
height: 170px;
background-color: #fff;
.data-panel{
display: flex;
justify-content: center;
column-gap: 45px;
align-items: center;
.panel-item{
display: flex;
flex-direction: column;
align-items: center;
.name{
color: #999999;
font-weight: 400;
font-family: '微软雅黑', sans-serif;
font-size: 16px;
}
.value{
font-size: 28px;
}
}
}
}
.action-middle{
width: 100%;
height: 60px;
margin: 10px 0;
background-color: #fff;
padding-left: 30px;
display: flex;
align-items: center;
}
.action-bottom{
width: 100%;
display: flex;
column-gap: 10px;
.left{
width: 50%;
.left-top{
background-color: #fff;
.left-top-charts{
width: 100%;
height: 250px;
}
}
.left-bottom{
background-color: #fff;
margin-top: 10px;
.left-bottom-charts{
width: 100%;
height: 248px;
}
}
}
.right{
width: 50%;
background-color: #fff;
.right-title{
position: relative;
display: flex;
align-items: center;
column-gap: 20px;
.ant-tabs-top > .ant-tabs-nav, .ant-tabs-bottom > .ant-tabs-nav, .ant-tabs-top > div > .ant-tabs-nav, .ant-tabs-bottom > div > .ant-tabs-nav{
margin: 0;
}
.ant-tabs-top > .ant-tabs-nav::before, .ant-tabs-bottom > .ant-tabs-nav::before, .ant-tabs-top > div > .ant-tabs-nav::before, .ant-tabs-bottom > div > .ant-tabs-nav::before{
border: none;
}
.more{
position: absolute;
right: 6%;
top: 30%;
}
}
.right-charts{
width: 100%;
height: 552px;
overflow-y: auto;
overflow-x: auto;
}
}
}
.comomn-title{
display: flex;
align-items: center;
column-gap: 10px;
padding: 20px 10px;
}

115
src/views/yhxwfx/toolbar.js Normal file
View File

@ -0,0 +1,115 @@
import React, { useEffect,useState } from 'react';
import { Form, Input, Button, DatePicker } from 'antd';
import NormalSelect from '../../components/Form/NormalSelect';
import moment from 'moment';
const { RangePicker } = DatePicker;
const ToolBar = ({ setSearchVal, onSave, storeData }) => {
const optionsType = [
{
label: "今日",
value:1
},
{
label: "近一周",
value:2
},
{
label:"近一月",
value:3
},
{
label:"近三月",
value:4
},
{
label:"近一年",
value:5
},
]
const [form] = Form.useForm();
const onValuesChange = (e) => {
switch (e.ranger) {
case 1:
form.setFieldValue("tm", [moment().startOf("day"), moment()])
setSearchVal({
stm: moment().startOf("day").format("YYYY-MM-DD 00:00:00"),
etm: moment().format("YYYY-MM-DD 23:59:59")
})
break;
case 2:
form.setFieldValue("tm",[moment().subtract(7, 'days'),moment()])
setSearchVal({
stm: moment().subtract(7, 'days').format("YYYY-MM-DD 00:00:00"),
etm: moment().format("YYYY-MM-DD 23:59:59")
})
break;
case 3:
form.setFieldValue("tm",[moment().subtract(1, 'months'),moment()])
setSearchVal({
stm: moment().subtract(1, 'months').format("YYYY-MM-DD 00:00:00"),
etm: moment().format("YYYY-MM-DD 23:59:59")
})
break;
case 4:
form.setFieldValue("tm",[moment().subtract(3, 'months'),moment()])
setSearchVal({
stm: moment().subtract(3, 'months').format("YYYY-MM-DD 00:00:00"),
etm: moment().format("YYYY-MM-DD 23:59:59")
})
break;
case 5:
form.setFieldValue("tm",[moment().subtract(1, 'years'),moment()])
setSearchVal({
stm: moment().subtract(1, 'years').format("YYYY-MM-DD 00:00:00"),
etm: moment().format("YYYY-MM-DD 23:59:59")
})
break;
default:
break;
}
}
useEffect(() => {
form.setFieldValue("tm", [moment().subtract(7, 'days'), moment()])
setSearchVal({
stm: moment().subtract(7, 'days').format("YYYY-MM-DD 00:00:00"),
etm: moment().format("YYYY-MM-DD 23:59:59")
})
}, [])
return (
<>
<div style={{display:'flex',justifyContent:'space-between'}}>
<Form form={form} className='toolbarBox' layout="inline" onValuesChange={onValuesChange}>
<Form.Item label="时间" name="tm">
<RangePicker
allowClear
style={{ width: "350px" }}
format="YYYY-MM-DD"
onChange={e => {
setSearchVal({
stm: e[0].format("YYYY-MM-DD 00:00:00"),
etm: e[1].format("YYYY-MM-DD 23:59:59")
})
}}
/>
</Form.Item>
<Form.Item label="常用时段" name="ranger">
<NormalSelect
allowClear
style={{ width: "150px" }}
options={optionsType}
/>
</Form.Item>
</Form>
</div>
</>
);
}
export default ToolBar;

View File

@ -0,0 +1,87 @@
import { rotate } from "ol/coordinate";
export default function userBarOption(data) {
const appList = data.appList.map(item => ({
...item, appCount: item?.count
}))
const webList = data.webList.map(item => ({
...item, webCount: item?.count
}))
const arr = [...appList,...webList]
const compareLength = appList.length - webList.length;
// 找出长度大的数组
const mapArr1 = compareLength >= 0 ? appList : webList;
// 找出长度小的数组
const mapArr2 = compareLength < 0 ? appList : webList;
const maxY = Math.ceil(Math.max(...arr.map(item => item.count)))
const minY = Math.floor(Math.min(...arr.map(item => item.count)))
const result = mapArr1.map(item => {
let filterData = mapArr2.find(o => item.name == o.name)
return {
...item,
...filterData,
}
})
return {
grid: {
top: "15%",
bottom:"20%"
},
tooltip: {
trigger: "axis",
},
legend: {
show:true
},
calculable: true,
xAxis: [
{
type: "category",
axisLabel:{
interval:0,
rotate:15
},
data:result.map(item => item.name),
},
],
yAxis: [
{
type: "value",
name:"访问次数",
min:minY - 1,
max:maxY + 1,
axisLine: {
show: false
},
axisTick: {
show: false
},
},
],
series: [
{
name: "WEB端",
type: "bar",
barWidth:"13%",
itemStyle: {
color:"#357efe"
},
data:result.map(item => item?.webCount || 0),
},
{
name: "移动端",
type: "bar",
barWidth:"13%",
itemStyle: {
color:"#62dffe"
},
data:result.map(item => item?.appCount || 0),
},
],
}
}

View File

@ -0,0 +1,92 @@
export default function userLineOption(data) {
const appList = data.appList.map(item => ({
...item, appCount: item?.count
}))
const webList = data.webList.map(item => ({
...item, webCount: item?.count
}))
const arr = [...appList,...webList]
const compareLength = appList.length - webList.length;
// 找出长度大的数组
const mapArr1 = compareLength >= 0 ? appList : webList;
// 找出长度小的数组
const mapArr2 = compareLength < 0 ? appList : webList;
const maxY = Math.ceil(Math.max(...arr.map(item => item.count)))
const minY = Math.floor(Math.min(...arr.map(item => item.count)))
const result = mapArr1.map(item => {
let filterData = mapArr2.find(o => item.createDate == o.createDate)
return {
...item,
...filterData,
}
})
return {
grid: {
top: "15%",
bottom:"20%"
},
tooltip: {
trigger: "axis",
},
legend: {
show: true,
top:"0%"
},
calculable: true,
xAxis: [
{
type: "category",
data: result.map(item => item.createDate),
},
],
yAxis: [
{
type: "value",
min:minY - 1,
max:maxY + 1,
axisLine: {
show: false
},
axisTick: {
show: false
},
},
],
series: [
{
name: "WEB端",
type: "line",
smooth:true,
itemStyle: {
color:"#357efe"
},
label: {
normal: {
show: true,
position: "top",
},
},
data: result.map(item => item?.webCount || 0),
},
{
name: "移动端",
type: "line",
smooth:true,
itemStyle: {
color:"#62dffe"
},
label: {
normal: {
show: true,
position: "top",
},
},
data: result.map(item => item?.appCount || 0),
},
],
}
}

68
src/views/yhxwrz/index.js Normal file
View File

@ -0,0 +1,68 @@
import React, { Fragment, useRef, useMemo,useEffect,useState } from 'react';
import { Table, Card, Row, Col, Divider, Empty } from 'antd';
import apiurl from '../../service/apiurl';
import usePageTable from '../../components/crud/usePageTable2';
import { paginate_noCode } from '../../components/crud/_';
// 页面初始默认查询参数
const options = {
search:{
// year:moment().format('YYYY')
},
};
const Page = () => {
const refModal = useRef();
const isRender = useRef(true)
const [searchVal, setSearchVal] = useState({year:options.search.year})
const columns = [
{ title: '序号', key: 'inx', dataIndex: 'inx', width: 100, align:"center" },
{ title: '用户', key: 'name', dataIndex: 'name', width: 200 },
{ title: '页面', key: 'menu1', dataIndex: 'menu1', width: 300, render:(i,row)=>{
return (row.menu1?row.menu1:'')+(row.menu2?('-'+row.menu2):'')+(row.menu3?('-'+row.menu3):'')
}},
{ title: '时间', key: 'createTime', dataIndex: 'createTime', width: 150},
];
const width = useMemo(() => columns.reduce((total, cur) => total + (cur.width), 0), [columns]);
const { tableProps, search, refresh } = usePageTable((params)=>paginate_noCode(apiurl.systemM.yhxwrz.page,params),options);
useEffect(()=>{
// if(isRender.current){
// isRender.current = false
// return
// }
const params = {
search: {
...searchVal
}
};
search(params)
},[searchVal])
return (
<>
<div className='content-root clearFloat xybm' style={{paddingRight:"0",paddingBottom:"0"}}>
<div className='AdcdTreeTableBox'>
{/* <Card className='nonebox'>
<ToolBar setSearchVal={setSearchVal}/>
</Card> */}
<div className="ant-card-body" style={{padding:"20px 0 0 0"}}>
<Table
columns={columns}
rowKey="inx"
{...tableProps}
scroll={{ x: width, y: "calc( 100vh - 400px )" }} />
</div>
</div>
</div>
</>
);
}
export default Page;