闸门监控

lsf-dev
秦子超 2024-09-26 17:48:01 +08:00
parent 70b19cb2fc
commit 35f59e0f4b
18 changed files with 1259 additions and 1 deletions

View File

@ -50,7 +50,10 @@
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"redux": "^4.2.0",
"typescript": "^4.7.4"
"typescript": "^4.7.4",
"konva": "^8.3.14",
"react-konva": "^18.2.3"
},
"scripts": {
"start": "craco start",

View File

@ -302,6 +302,9 @@ export async function loadMenu(): Promise<MenuItem[]> {
{ id: id(), title: '防治宣传', path: '/mgr/sg/byfz/byxc' },
]
},
{
id: id(), title: '闸门监控', path: '/mgr/sg/zmjk',
},
{
id: id(), title: '维修养护', path: '/mgr/sg/wxyh',
},

View File

@ -24,6 +24,14 @@ const apiurl = {
role: service_xyt + '/system/menu/list'
},
//闸门监控
zmjk: {
getList : service_xyt + '/attGateB/list',
getInformation: service_xyt + '/attGateB/data',
getDamData: service_xyt + '/gatePore/listByStcd',
getVideo: service_xyt + '/gateValveCctvRel/list'
},
// 基本情况
home: {
yq: service_fxdd + '/real/rain/list',//'/stStbprpB/rainfallStationDetails/rainfallList',//雨情

View File

@ -10,6 +10,20 @@ export function changeObjectStringToMoment(obj: { [key: string]: any }, fields:
return ret;
}
export function apertureMeter(val?: any): number | undefined {
if (typeof val !== 'number') {
return undefined
}
return val / 1000;
}
export function renAperture(val?: any) {
if (typeof val !== 'number') {
return '-'
}
return (val / 1000).toFixed(3);
}
export async function base64FromFile(file: File): Promise<string | undefined> {
return new Promise((resolve) => {

View File

@ -48,6 +48,7 @@ import Xjxpz from "./rcgl/xcxj/xjxpz"
import Fzxc from "./rcgl/byfz/fzxc"
import Bypc from "./rcgl/byfz/bypc"
import Zmjk from "./rcgl/zmjk"
import Wxyh from "./rcgl/wxyh"
import Szzb from "./rcgl/szzb"
import Stlljc from "./rcgl/stlljc"
@ -191,6 +192,7 @@ const AppRouters: React.FC = () => {
{ path: 'sg/xcxj/xjxpz', element: <Xjxpz/> },
{ path: 'sg/byfz/byxc', element: <Fzxc /> },
{ path: 'sg/byfz/bypc', element: <Bypc /> },
{ path: 'sg/zmjk', element: <Zmjk /> },
{ path: 'sg/wxyh', element: <Wxyh /> },
{ path: 'sg/aqgl/aqjcgl', element: <Aqjcgl /> },
{ path: 'sg/aqgl/fxgkqd', element: <Fxgkqd /> },

View File

@ -0,0 +1,38 @@
import React from 'react'
import { Shape } from 'react-konva';
const ColorPolygon: React.FC<{
pts: ({ x?: number, y?: number } | [number, number])[]
fill: string;
desc?: string;
opacity?: number;
}> = ({ pts, fill, opacity }) => {
return (
<Shape
fill={fill}
opacity={opacity}
sceneFunc={(context:any, shape:any) => {
context.beginPath();
pts.forEach((p, i) => {
let x:any, y:any;
if (Array.isArray(p)) {
x = p[0];
y = p[1];
} else {
x = p.x;
y = p.y;
}
if (i === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
})
context.closePath();
context.fillStrokeShape(shape)
}}
/>
)
}
export default ColorPolygon

View File

@ -0,0 +1,46 @@
import React from 'react';
import { Layer, Rect } from 'react-konva';
import ColorPolygon from './ColorPolygon';
import { CanvasH, CanvasW, FarBuildingTop, Horizontal, RoofTop, SideRoomSize } from './consts';
import { ControlPts } from './coordinates';
import { RoomWindows } from './Window';
const Sider: React.FC<{
pts: ControlPts;
side: 'left' | 'right';
}> = ({ side, pts }) => {
return (
<Layer
x={side === 'left' ? 0 : CanvasW}
scaleX={side === 'left' ? 1 : -1}
width={CanvasW * 0.5}
>
<ColorPolygon desc='水' fill="#458eab" pts={[[0, Horizontal], [0, CanvasH], [CanvasW * 0.5, CanvasH], [CanvasW * 0.5, Horizontal]]} />
<ColorPolygon desc='远处的墙' fill="#8fa7a7" pts={[[0, FarBuildingTop], pts.C2, pts.C1, [pts.C1.x, FarBuildingTop]]} />
<Rect x={pts.RoomLT.x} y={pts.RoomLT.y} width={SideRoomSize} height={SideRoomSize} fill="#738785" />
<RoomWindows rect={[pts.RoomLT.x, pts.RoomLT.y + 8, pts.RoomRB.x - 16, pts.RoomRB.y]} />
<ColorPolygon desc='房屋侧面' fill="#4a5c5e" pts={[pts.RoomRT, pts.RoomRB, pts.RoomRBFar, pts.RoomRTFar]} />
<Rect x={pts.RoomLT.x - 2} y={pts.RoomLT.y} width={SideRoomSize + 4} height={8} fill="#f2ebcd" />
<ColorPolygon desc='房屋侧面-白条' fill="#f2ebcd" pts={[
[pts.RoomRT.x + 2, pts.RoomRT.y],
[pts.RoomRB.x + 2, pts.RoomRT.y + 8],
[pts.RoomRTFar.x! + 2, pts.RoomRTFar.y! + 8],
[pts.RoomRTFar.x! + 2, pts.RoomRTFar.y!]
]} />
<ColorPolygon desc='房屋底面' fill="#4a5c5e" pts={[pts.RoomLB, pts.RoomLBFar, pts.RoomRBFar, pts.RoomRB]} />
<Rect x={pts.RoomRB.x - 16} y={pts.RoomRB.y - 8 - SideRoomSize} width={16} height={SideRoomSize} fill="#6e3a34" />
<ColorPolygon desc='侧面' fill="#4a5c5e" pts={[pts.L2, pts.B2, pts.B1, pts.L1]} />
<ColorPolygon desc='上盖' fill="#728381" pts={[pts.C2, pts.L2, pts.L1, pts.C1]} />
<ColorPolygon desc='墙' fill="#738b8b" pts={[[pts.C1.x, RoofTop], pts.C1, pts.L1, pts.A1]} />
<ColorPolygon desc='内壁' fill="#334648" pts={[pts.A1, pts.B1, pts.B3, pts.A2]} />
</Layer>
)
}
export default React.memo(Sider)

View File

@ -0,0 +1,105 @@
import React, { useMemo } from 'react';
import { Layer, Rect } from 'react-konva';
import ColorPolygon from './ColorPolygon';
import { Window1, WindowSep } from './Window';
import { TopRoomHeight } from './consts';
import { ControlPts, mirror } from './coordinates';
const Topper1: React.FC<{
pts: ControlPts;
type: number;
}> = ({ pts, type }) => {
const windows = useMemo<{
w: number[][];
h: number[][];
}>(() => {
const x1 = pts.TopRectLB.x;
const y0 = pts.TopRectLB.y - TopRoomHeight
const y1 = pts.TopRectLB.y - TopRoomHeight * 0.75;
const x2 = pts.TopRectRB.x;
const y2 = pts.TopRectLB.y - TopRoomHeight * 0.25;
if (type === 1) {
const u = (x2 - x1) / 10;
return {
w: [
[x1 + u, y1, x1 + 3 * u, y2],
[x1 + 4 * u, y1, x1 + 6 * u, y2],
[x1 + 7 * u, y1, x1 + 9 * u, y2],
],
h: []
}
} else if (type === 2) {
const u = (x2 - x1 - 8) / 14;
return {
w: [
[x1 + u, y1, x1 + 3 * u, y2],
[x1 + 4 * u, y1, x1 + 6 * u, y2],
[x1 + 8 * u + 8, y1, x1 + 10 * u + 8, y2],
[x1 + 11 * u + 8, y1, x1 + 13 * u + 8, y2],
],
h: [
[x1 + 7 * u, y0, x1 + 7 * u + 8, pts.TopRectLB.y],
],
}
} else if (type === 3) {
const xc = (x2 - x1) * 0.5;
const u = (xc - 8) / 8;
return {
w: [
[x1 + u, y1, x1 + 3 * u, y2],
[x1 + 5 * u + 8, y1, x1 + 7 * u + 8, y2],
[x1 + u + xc, y1, x1 + 3 * u + xc, y2],
[x1 + 5 * u + 8 + xc, y1, x1 + 7 * u + 8 + xc, y2],
],
h: [
[x1 + 4 * u, y0, x1 + 4 * u + 8, pts.TopRectLB.y],
[x1 + 4 * u + xc, y0, x1 + 4 * u + 8 + xc, pts.TopRectLB.y],
]
}
} else if (type > 3) {
const xc1 = (x2 - x1) / 3;
const xc2 = xc1 * 2;
const u = (xc1 - 8) / 8;
return {
w: [
[x1 + u, y1, x1 + 3 * u, y2],
[x1 + 5 * u + 8, y1, x1 + 7 * u + 8, y2],
[x1 + u + xc1, y1, x1 + 3 * u + xc1, y2],
[x1 + 5 * u + 8 + xc1, y1, x1 + 7 * u + 8 + xc1, y2],
[x1 + u + xc2, y1, x1 + 3 * u + xc2, y2],
[x1 + 5 * u + 8 + xc2, y1, x1 + 7 * u + 8 + xc2, y2],
],
h: [
[x1 + 4 * u, y0, x1 + 4 * u + 8, pts.TopRectLB.y],
[x1 + 4 * u + xc1, y0, x1 + 4 * u + 8 + xc1, pts.TopRectLB.y],
[x1 + 4 * u + xc2, y0, x1 + 4 * u + 8 + xc2, pts.TopRectLB.y],
]
}
}
return {
w: [], h: []
}
}, [])
return (
<Layer>
<Rect fill='#738b8b' x={pts.TopRectLB.x} y={pts.TopRectLB.y - TopRoomHeight} width={pts.TopRectRB.x - pts.TopRectLB.x} height={TopRoomHeight} />
<ColorPolygon fill='#4a5c5e' pts={[pts.TopRectLB, pts.RoomRBFar, mirror(pts.RoomRBFar), pts.TopRectRB]} />
{
pts.SepsLTLBRBRT.map((s, index) => (
<ColorPolygon key={'a' + index} fill='#334648' pts={s} />
))
}
{
windows.w.map((o, index) => <Window1 rect={o} key={'w' + index} />)
}
{
windows.h.map((o, index) => <WindowSep rect={o} key={'h' + index} />)
}
</Layer>
)
}
export default React.memo(Topper1);

View File

@ -0,0 +1,29 @@
import React from 'react';
import { Layer, Rect } from 'react-konva';
import ColorPolygon from './ColorPolygon';
import { CanvasH, CanvasW, ViewCenter, WaterTop } from './consts';
import { ControlPts, interpolate, intersection, mirror } from './coordinates';
const Topper2: React.FC<{
pts: ControlPts;
waterRatio: number;
}> = ({ pts, waterRatio }) => {
const waterP1 = interpolate(pts.B1, { x: pts.L1.x, y: WaterTop }, waterRatio);
const waterP2 = intersection(waterP1, ViewCenter, { x: 0, y: undefined });
return (
<Layer>
{
pts.SepsFront.map((s, index) => (
<Rect key={index} fill='#738b8b' x={s.x} y={s.y} width={s.w} height={s.h} />
))
}
<ColorPolygon fill='#539cb9' pts={[waterP1, waterP2, mirror(waterP2), mirror(waterP1)]} opacity={0.8} />
<ColorPolygon fill='#2d99ba' pts={[waterP2, mirror(waterP2), [CanvasW, CanvasH], [0, CanvasH]]} opacity={0.8} />
</Layer>
)
}
export default React.memo(Topper2);

View File

@ -0,0 +1,104 @@
import React from 'react'
import { Line, Rect } from 'react-konva';
import ColorPolygon from './ColorPolygon';
const S1 = 4;
export const Window1: React.FC<{
rect: number[];
}> = ({ rect }) => {
const [x1, y1, x2, y2] = rect;
const w1 = (x2 - x1 - S1) / 2;
const w2 = (x2 - x1 - S1 * 2) / 3;
return (
<>
<Rect fill='#b1c3cf' x={x1} y={y1} width={w1} height={16} />
<Rect fill='#b1c3cf' x={x1 + w1 + S1} y={y1} width={w1} height={16} />
<Rect fill='#b1c3cf' x={x1} y={y1 + 16 + S1} width={w2} height={y2 - y1 - 16 - S1} />
<Rect fill='#b1c3cf' x={x1 + w2 + S1} y={y1 + 16 + S1} width={w2} height={y2 - y1 - 16 - S1} />
<Rect fill='#b1c3cf' x={x1 + (w2 + S1) * 2} y={y1 + 16 + S1} width={w2} height={y2 - y1 - 16 - S1} />
</>
)
}
export const WindowSep: React.FC<{
rect: number[];
}> = ({ rect }) => {
const [x1, y1, x2, y2] = rect;
return (
<Rect fill='#b1c3cf' x={x1} y={y1 - 8} width={x2 - x1} height={y2 - y1 + 8} />
)
}
export const RoomWindows: React.FC<{
rect: number[];
}> = ({ rect }) => {
const [x1, y1, x2, y2] = rect;
const u = (x2 - x1) / 10;
const v = (y2 - y1) / 6;
const c1 = x1 - 10 + 1;
const c2 = x1 - 10 + 7 * u;
const dc = (c2 - c1) / 10;
return (
<>
<Rect fill='#c1d3d5' x={x1 + u} y={y1 + v} width={u} height={v} />
<Line stroke='#fff' points={[x1 + u, y1 + v, x1 + 2 * u, y1 + v]} />
<Rect fill='#c1d3d5' x={x1 + u * 2 + 2} y={y1 + v} width={u} height={v} />
<Line stroke='#fff' points={[x1 + u * 2 + 2, y1 + v, x1 + 3 * u + 2, y1 + v]} />
<Rect fill='#c1d3d5' x={x1 + u * 5} y={y1 + v} width={u} height={v} />
<Line stroke='#fff' points={[x1 + u * 5, y1 + v, x1 + 6 * u, y1 + v]} />
<Rect fill='#c1d3d5' x={x1 + u * 6 + 2} y={y1 + v} width={u} height={v} />
<Line stroke='#fff' points={[x1 + u * 6 + 2, y1 + v, x1 + 7 * u + 2, y1 + v]} />
<Rect fill='#c1d3d5' x={x1 + u} y={y1 + v * 4} width={u} height={v} />
<Line stroke='#fff' points={[x1 + u, y1 + v * 4, x1 + 2 * u, y1 + v * 4]} />
<Rect fill='#c1d3d5' x={x1 + u * 2 + 2} y={y1 + v * 4} width={u} height={v} />
<Line stroke='#fff' points={[x1 + u * 2 + 2, y1 + v * 4, x1 + 3 * u + 2, y1 + v * 4]} />
<Rect fill='#c1d3d5' x={x1 + u * 5} y={y1 + v * 4} width={u} height={v} />
<Line stroke='#fff' points={[x1 + u * 5, y1 + v * 4, x1 + 6 * u, y1 + v * 4]} />
<Rect fill='#c1d3d5' x={x1 + u * 6 + 2} y={y1 + v * 4} width={u} height={v} />
<Line stroke='#fff' points={[x1 + u * 6 + 2, y1 + v * 4, x1 + 7 * u + 2, y1 + v * 4]} />
<Rect fill='#8fa7a7' x={x1 - 10} y={y1 + 3 * v} width={7 * u} height={v * 0.6} />
<ColorPolygon
fill='#4a5c5e'
pts={[
[x1 - 10, y1 + 3.6 * v],
[x1 - 10 + 4, y1 + 3.6 * v + 2],
[x1 - 10 + 7 * u + 3, y1 + 3.6 * v + 2],
[x1 - 10 + 7 * u + 3, y1 + 3 * v + 2],
[x1 - 10 + 7 * u, y1 + 3 * v],
[x1 - 10 + 7 * u, y1 + 3.6 * v + 2],
[x1 - 10 + 7 * u, y1 + 3.6 * v],
]}
/>
<Line stroke='#8fa7a7' lineJoin='round' points={[
x1, y1 + 2.5 * v + 2,
x1 - 10 + 4, y1 + 2.5 * v + 2,
x1 - 10, y1 + 2.5 * v, x1 - 10 + 7 * u, y1 + 2.5 * v,
x1 - 10 + 7 * u + 3, y1 + 2.5 * v + 2,
]} />
<Line stroke='#8fa7a7' points={[x1 - 10 + 4, y1 + 2.5 * v + 2, x1 - 10 + 4, y1 + 3 * v]} />
{
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(i => (
<Line key={i} stroke='#8fa7a7' points={[c1 + dc * i, y1 + 2.5 * v, c1 + dc * i, y1 + 3 * v]} />
))
}
</>
)
}

View File

@ -0,0 +1,119 @@
import React from 'react'
import { Circle, Layer, Line, Rect } from 'react-konva';
import ColorPolygon from './ColorPolygon';
import { ViewCenter, WaterTop } from './consts';
import { ControlPts, interpolate, intersection } from './coordinates';
import ZmDec from './ZmDec';
import { useLinearAnim } from './useLinearAnim';
const ZmColumn: React.FC<{
kdMax: number;
gtophgt?: number;
idx: number;
pts: ControlPts;
waterRatio: number;
selected: boolean;
}> = ({ kdMax, gtophgt, idx, pts, waterRatio, selected }) => {
const renKd = gtophgt; //useLinearAnim(gtophgt);
const a = pts.ZmArea[idx];
if (!a || typeof renKd !== 'number') {
return null;
}
const hh1 = (a.lb1.y - a.lt1.y) * 0.5;
const hh2 = (a.lb2.y - a.lt2.y) * 0.5;
const ratio = renKd / kdMax;
const e1 = hh1 * ratio * 0.8;
const e2 = hh2 * ratio * 0.8;
const lineLineLeft = { x: (a.lt1.x + a.lt2.x) * 0.5, y: (a.lt1.y + a.lt2.y) * 0.5 };
const lineLineRight = { x: (a.rt1.x + a.rt2.x) * 0.5, y: (a.rt1.y + a.rt2.y) * 0.5 };
const line1 = interpolate(lineLineLeft, lineLineRight, 0.25);
const line2 = interpolate(lineLineLeft, lineLineRight, 0.75);
const ty1 = a.lt1.y + hh1 - e1;
const ty2 = a.lt2.y + hh2 - e2;
const joiny = (ty1 + ty2) * 0.5;
const waterP1 = interpolate(a.lb0, { x: a.lb0.x, y: WaterTop }, waterRatio);
const waterP1R = { x: a.rb0.x, y: waterP1.y };
const waterP2 = intersection(ViewCenter, waterP1, { x: a.lb1.x, y: undefined });
const waterP2R = { x: a.rb1.x, y: waterP2.y };
const waterP3 = intersection(ViewCenter, waterP1, { x: a.lb3.x, y: undefined });
waterP3.y -= 26;
const waterP3R = { x: a.rb3.x, y: waterP3.y };
if (waterP3.x < waterP2.x) {
waterP3.x = waterP2.x;
}
if (waterP3R.x > waterP2R.x) {
waterP3R.x = waterP2R.x
}
//
const b1 = { x: a.lb0.x, y: a.lb0.y - 32 };
const b6 = { x: a.rb0.x, y: a.rb0.y - 32 };
const b2 = interpolate(b1, b6, 0.4);
const b5 = interpolate(b1, b6, 0.6);
const b3 = { x: b2.x, y: b2.y + 12 };
const b4 = { x: b5.x, y: b5.y + 12 };
const b1_1 = intersection(b1, ViewCenter, { x: a.lb1.x, y: undefined });
const b1_6 = intersection(b6, ViewCenter, { x: a.rb1.x, y: undefined });
const b1_2 = intersection(b2, ViewCenter, { x: undefined, y: b1_1.y });
const b1_5 = intersection(b5, ViewCenter, { x: undefined, y: b1_6.y });
const b1_3 = intersection(b3, ViewCenter, { x: b1_2.x, y: undefined });
const b1_4 = intersection(b4, ViewCenter, { x: b1_5.x, y: undefined });
return (
<>
{
waterP3 && waterP3R ? (
<ColorPolygon fill='#539cb9' pts={[waterP2, waterP2R, waterP3R, waterP3]} opacity={0.8} />
) : null
}
<ColorPolygon
desc="闸门上测"
fill='#afb5b5'
pts={[
[a.lt1.x, ty1],
[a.rt1.x, ty1],
[a.rt2.x, ty2],
[a.lt2.x, ty2],
]}
/>
<Line stroke='#222' strokeWidth={8} lineCap="round" points={[line1.x, line1.y, line1.x, joiny]} />
<Line stroke='#222' strokeWidth={8} lineCap="round" points={[line2.x, line2.y, line2.x, joiny]} />
<Circle fill='#afb5b5' x={line1.x} y={joiny} radius={10} />
<Circle fill='#afb5b5' x={line2.x} y={joiny} radius={10} />
<Line stroke='#222' strokeWidth={12} points={[line1.x, joiny - 8, line1.x, joiny - 10]} />
<Line stroke='#222' strokeWidth={12} points={[line2.x, joiny - 8, line2.x, joiny - 10]} />
<Rect
desc='闸门正面'
fill={selected ? '#95927a' : '#5f6969'}
stroke={selected ? 'orange' : "#222"}
strokeWidth={1} x={a.lt1.x} y={ty1} width={a.rt1.x - a.lt1.x} height={hh1} />
<ZmDec tl={{ x: a.lt1.x, y: ty1 }} rb={{ x: a.rt1.x, y: ty1 + hh1 }} />
<ColorPolygon desc="出水口" fill='#8d9d9b' pts={[b1_1, b1_2, b2, b1]} />
<ColorPolygon desc="出水口" fill='#8d9d9b' pts={[b1_5, b1_6, b6, b5]} />
<ColorPolygon desc="出水口" fill='#8d9d9b' pts={[b1_2, b1_3, b3, b2]} />
<ColorPolygon desc="出水口" fill='#8d9d9b' pts={[b1_3, b1_4, b4, b3]} />
<ColorPolygon desc="出水口" fill='#8d9d9b' pts={[b1_4, b1_5, b5, b4]} />
<ColorPolygon desc="出水口" fill='#728381' pts={[b1, b2, b3, b4, b5, b6, a.rb0, a.lb0]} />
{
waterP1.y < b1.y ? (
<ColorPolygon fill='#539cb9' pts={[waterP1, waterP1R, waterP2R, waterP2]} opacity={0.8} />
) : null
}
</>
)
}
export default React.memo(ZmColumn)

View File

@ -0,0 +1,35 @@
import React, { useMemo } from 'react'
import { Layer } from 'react-konva';
import { ControlPts } from './coordinates';
import ZmColumn from './ZmColumn';
// import { StationItem, GateRuntime } from '../../../models/_/defs';
import { apertureMeter } from '../../../utils/utils';
const ZmColumns: React.FC<{
zmobj: any;
runtime: any;
pts: ControlPts;
selectedId?: string;
waterRatio: number;
}> = ({ zmobj, runtime, pts, waterRatio }) => {
const eqpnoList = useMemo(() => new Array(runtime.length).fill(0).map((o, index) => index), [runtime]);
return (
<Layer>
{
eqpnoList.map((o, index) => (
<ZmColumn
key={o}
gtophgt={apertureMeter(runtime[o]?.realAperture)}
kdMax={2}
pts={pts}
idx={index}
waterRatio={waterRatio}
selected={false}
/>)
)
}
</Layer>
)
}
export default ZmColumns

View File

@ -0,0 +1,60 @@
import React from 'react'
import { Line, Rect } from 'react-konva';
import { Horizontal, ViewCenter } from './consts';
import { XY } from './coordinates'
const INTERVALS = [0, 1, 2];
const ZmDec: React.FC<{
tl: XY;
rb: XY;
}> = ({ tl, rb }) => {
const ux = (rb.x - tl.x) / 12;
const uy = (rb.y - tl.y) / 12;
const ox = tl.x + 3 * ux;
const oy = tl.y + 3 * uy;
return (
<>
{
INTERVALS.map(y => (
<React.Fragment key={y}>
{
INTERVALS.map(x => {
const cx = ox + x * 3 * ux;
const cy = oy + y * 3 * uy;
const l = cx - ux;
const r = cx + ux;
const t = cy - uy;
const b = cy + uy;
return (
<React.Fragment key={x}>
<Rect fill="323737" x={l} y={t} width={2 * ux} height={2 * uy} />
{
cx < ViewCenter.x ? (
<Line points={[l, t, l, b]} stroke="#afb5b5" strokeWidth={1} />
) : (
<Line points={[r, t, r, b]} stroke="#afb5b5" strokeWidth={1} />
)
}
{
cy < Horizontal ? (
<Line points={[l, t, r, t]} stroke="#afb5b5" strokeWidth={1} />
) : (
<Line points={[l, b, r, b]} stroke="#afb5b5" strokeWidth={1} />
)
}
</React.Fragment>
)
})
}
</React.Fragment>
))
}
</>
)
}
export default ZmDec

View File

@ -0,0 +1,16 @@
export const CanvasW = 1080;
export const CanvasH = 640;
export const Horizontal = CanvasH * 1.4 / 3;
export const ViewCenter = { x: CanvasW * 0.5, y: Horizontal };
export const GroundBase = CanvasH * 1.8 / 3;
export const BottomBase = CanvasH * 2.5 / 3;
export const RoofTop = CanvasH * 0.8 / 3;
export const RoofTopFar = RoofTop + 32;
export const FarBuildingTop = RoofTop - 16;
export const SideRoomSize = CanvasH * 0.7 / 3;
export const TopRoomHeight = SideRoomSize - 24;
export const PillarRatio = 0.2;
export const WaterTop = GroundBase;

View File

@ -0,0 +1,180 @@
import { BottomBase, CanvasW, GroundBase, PillarRatio, RoofTop, RoofTopFar, SideRoomSize, ViewCenter } from "./consts";
export type XY = {
x: number;
y: number;
}
export type XYWH = {
x: number;
y: number;
w: number;
h: number;
}
export function intersection(pt1: XY, pt2: XY, result: { x?: number, y?: number }): XY {
const invalid: XY = { x: result.x || 0, y: result.y || 0 };
if (typeof result.x === typeof result.y) {
return invalid;
}
const x1 = pt1.x, y1 = pt1.y, x2 = pt2.x, y2 = pt2.y;
if (typeof result.x === 'number') {
const x3 = result.x;
if (x2 === x1) {
return invalid;
}
result.y = (y2 - y1) * (x3 - x1) / (x2 - x1) + y1;
} else {
const y3 = result.y!;
if (y2 === y1) {
return invalid;
}
result.x = (x2 - x1) * (y3 - y1) / (y2 - y1) + x1;
}
return result as any;
}
export function interpolate(pt1: XY, pt2: XY, ratio: number): XY {
return {
x: pt1.x + (pt2.x - pt1.x) * ratio,
y: pt1.y + (pt2.y - pt1.y) * ratio,
}
}
export function mirror(pt: XY): XY {
return {
x: CanvasW - pt.x,
y: pt.y,
}
}
export type ControlPts = {
C1: XY;
L1: XY;
B1: XY;
C2: XY;
L2: XY;
B2: XY;
A1: XY;
A2: XY;
B3: XY;
RoomLT: XY;
RoomRT: XY;
RoomRB: XY;
RoomLB: XY;
RoomRTFar: XY;
RoomRBFar: XY;
RoomLBFar: XY;
TopRectLB: XY;
TopRectRB: XY;
SepsLTLBRBRT: XY[][];
SepsFront: XYWH[];
ZmArea: {
lt0: XY; lt1: XY; lt2: XY; lt3: XY;
lb0: XY; lb1: XY; lb2: XY; lb3: XY;
rt0: XY; rt1: XY; rt2: XY; rt3: XY;
rb0: XY; rb1: XY; rb2: XY; rb3: XY;
}[];
}
export function contextCoordinates(unitWidth: number, hole: number): ControlPts {
const C1 = { x: unitWidth * 0.6, y: GroundBase };
const L1 = { x: unitWidth, y: GroundBase };
const B1 = { x: unitWidth, y: BottomBase }
const C2 = intersection(ViewCenter, C1, { x: 0, y: undefined });
const L2 = intersection(ViewCenter, L1, { x: 0, y: undefined });
const B2 = intersection(ViewCenter, B1, { x: 0, y: undefined });
const A1 = { x: L1.x, y: RoofTop };
const A2 = intersection(ViewCenter, A1, { x: undefined, y: RoofTopFar });
const B3 = intersection(ViewCenter, B2, { x: A2.x, y: undefined })
const RoomLT = { x: (C1.x + L1.x) * 0.5 - SideRoomSize * 0.5, y: RoofTop - SideRoomSize }
const RoomRT = { x: (C1.x + L1.x) * 0.5 + SideRoomSize * 0.5, y: RoofTop - SideRoomSize }
const RoomRB = { x: (C1.x + L1.x) * 0.5 + SideRoomSize * 0.5, y: RoofTop }
const RoomLB = { x: (C1.x + L1.x) * 0.5 - SideRoomSize * 0.5, y: RoofTop }
const RoomRBFar = intersection(ViewCenter, RoomRB, { x: undefined, y: RoofTopFar });
const RoomRTFar = intersection(ViewCenter, RoomRT, { x: RoomRBFar.x, y: undefined });
const RoomLBFar = { x: RoomRBFar.x! - SideRoomSize, y: RoomRBFar.y };
const TopRectLB = interpolate(RoomRB, RoomRBFar, 0.05);
const TopRectRB = mirror(TopRectLB);
const TopHoleLT = intersection(A1, ViewCenter, { x: undefined, y: TopRectLB.y });
const TopHoleRT = mirror(TopHoleLT);
const HolesWidth = TopHoleRT.x - TopHoleLT.x;
const PillarWidth = (HolesWidth / hole) * PillarRatio;
const HoleWidth = (HolesWidth - PillarWidth * (hole - 1)) / hole;
const SepsLTLBRBRT: XY[][] = [];
const SepsFront: XYWH[] = [];
const ZmArea = [];
for (let i = 0; i < hole; i++) {
const TopBase = TopHoleLT.y;
// 隔断
if (i > 0) {
const frontRight = TopHoleLT.x + (PillarWidth + HoleWidth) * i;
const frontLeft = frontRight - PillarWidth;
SepsFront.push({ x: frontLeft, y: TopBase, w: PillarWidth, h: BottomBase - TopBase });
if (i < hole / 2) {
// 右侧面
const p1 = { x: frontRight, y: TopBase };
const p2 = { x: frontRight, y: BottomBase };
const p4 = intersection(p1, ViewCenter, { x: undefined, y: RoofTopFar });
const p3 = intersection(p2, ViewCenter, { x: p4.x, y: undefined });
SepsLTLBRBRT.push([p1, p2, p3, p4]);
} else if (i > hole / 2) {
// 左侧面
const p1 = { x: frontLeft, y: TopBase };
const p2 = { x: frontLeft, y: BottomBase };
const p4 = intersection(p1, ViewCenter, { x: undefined, y: RoofTopFar });
const p3 = intersection(p2, ViewCenter, { x: p4.x, y: undefined });
SepsLTLBRBRT.push([p1, p2, p3, p4]);
}
}
// 闸门面
{
const ZmTopBaseFront = TopBase + 6;
const ZmTopBaseBack = ZmTopBaseFront + 10;
const lt0 = { x: TopHoleLT.x + (PillarWidth + HoleWidth) * i, y: TopBase };
const lb0 = { x: lt0.x, y: BottomBase };
const rb0 = { x: lt0.x + HoleWidth, y: BottomBase };
const lt1 = intersection(ViewCenter, lt0, { x: undefined, y: ZmTopBaseFront });
const lt2 = intersection(ViewCenter, lt0, { x: undefined, y: ZmTopBaseBack });
const lt3 = intersection(ViewCenter, lt0, { x: undefined, y: RoofTopFar });
const lb1 = intersection(ViewCenter, lb0, { x: lt1.x, y: undefined });
const lb2 = intersection(ViewCenter, lb0, { x: lt2.x, y: undefined });
const lb3 = intersection(ViewCenter, lb0, { x: lt3.x, y: undefined });
const rb1 = intersection(ViewCenter, rb0, { x: undefined, y: lb1.y });
const rb2 = intersection(ViewCenter, rb0, { x: undefined, y: lb2.y });
const rb3 = intersection(ViewCenter, rb0, { x: undefined, y: lb3.y });
const rt0 = { x: rb0.x, y: lt0.y };
const rt1 = { x: rb1.x, y: lt1.y };
const rt2 = { x: rb2.x, y: lt2.y };
const rt3 = { x: rb3.x, y: lt3.y };
ZmArea.push({ lt0, lb0, rb0, rt0, lt1, rt1, lb1, rb1, lt2, lb2, rt2, rb2, lt3, lb3, rt3, rb3 });
}
}
return {
C1, L1, B1, C2, L2, B2, A1, A2, B3,
RoomLT, RoomRT, RoomRB, RoomLB, RoomRTFar, RoomRBFar, RoomLBFar,
TopRectLB, TopRectRB,
SepsLTLBRBRT, SepsFront, ZmArea,
}
}

View File

@ -0,0 +1,350 @@
import React, { useEffect, useMemo, useState } from 'react'
import { Stage } from 'react-konva';
import Sider from './Sider';
import Topper1 from './Topper1';
import Topper2 from './Topper2';
import ZmColumns from './ZmColumns';
import { contextCoordinates } from './coordinates';
import { renAperture } from '../../../utils/utils';
import HFivePlayer from '../../../components/video1Plary'
import './index.less';
import { httpget, httpget2, httppost2 } from '../../../utils/request';
import apiurl from '../../../service/apiurl';
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)',
}
const Page = () => {
const [itemIndex,setItemIndex] = useState(null)
const [data,setData] = useState({})
const [list, setList ] = useState([])
const [damList, setDamList ] = useState([])
const [videoList, setVideoList ] = useState([])
const [videoArr, setvideoArr] = useState({})
const hole = 3;//zmobj.gaorNum;
const xunit = CanvasW / (2 + hole);
const pts = contextCoordinates(xunit, hole);
const eqpnoList = useMemo(() => damList ? new Array(damList.length).fill(0).map((o, index) => index) : [], [damList]);
useEffect(()=>{
getList()
},[])
const getList = async()=>{
const {code, data} = await httppost2(apiurl.zmjk.getList)
if(code!==200){
return
}
const obj = data[0]||{}
getInformation(obj.gateCode)
getDamData(obj.stcd)
getVideo(obj.gateCode)
setData(obj)
}
const getInformation = async(gateCode)=>{
const {code, data} = await httpget2(apiurl.zmjk.getInformation,{gateCode})
if(code!==200){
return
}
setList(data)
}
const getDamData = async(stcd)=>{
const {code, data} = await httpget2(apiurl.zmjk.getDamData,{stcd})
if(code!==200){
return
}
const list = []
data.map((item)=>{
list.push({
...item
// ,realAperture:item.realAperture*1000
})
})
setDamList(list)
}
const getVideo = async(valveCode)=>{
const {code, data} = await httppost2(apiurl.zmjk.getVideo,{valveCode})
if(code!==200){
return
}
setVideoList(data)
}
const getVideoSrc = async (current) => {
const res = await httpget2(`${apiurl.gsxl.zfzl.videosrc}${'32023a7f27d8448fa10511f24e96acff'}`)
if (res.code == 200 && res.data?.length !== 0) {
setvideoArr({src:res.data})
}else{
setvideoArr({})
}
}
return (
<>
<div className='content-root clearFloat xybm sg_zmjk' style={{paddingRight:"0",paddingBottom:"0"}}>
<div className='lf CrudAdcdTreeTableBox' style={{width:"100%",overflowY:"auto"}}>
{/* <Card className='nonebox'>
</Card> */}
<div className="ant-card-body" style={{ padding: "20px 0 0 0" }}>
<dvi 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>
</dvi>
<dvi className="sg_zmjk_right">
<div className='sg_zmjk_right_video'>
<div className='sg_zmjk_right_video_title'>视频监控</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>
</div>
</div>
<div className='sg_zmjk_right_information'>
<div className='sg_zmjk_right_information_title'>监测数据</div>
<div style={{height:'144px',overflowY:'auto',padding:'20px'}}>
{
list?.map((item)=>{
return (
<div className='sg_zmjk_right_information_content'>
<div>{myType[item.type]}</div>
<div>112.079 / 1.279</div>
<div>07-10 12:09:00</div>
</div>
)
})
}
</div>
</div>
<div className='sg_zmjk_right_more'>查看更多信息</div>
</dvi>
</div>
</div>
</div>
</>
);
}
export default Page;

View File

@ -0,0 +1,92 @@
.sg_zmjk{
.ant-card-body{
display: flex;
height: 100%;
.sg_zmjk_left{
top: 30px;
position: relative;
width: 1080px;
height: 640px;
transform: scale(0.9,1);
}
.sg_zmjk_right{
flex: 1;
// width: 40%;
height: 100%;
display: flex;
flex-direction: column;
.sg_zmjk_right_video{
height: 400px;
margin: 30px 20px 30px -30px;
border-radius: 5px;
border: 1px solid #bbb;
display: flex;
flex-direction: column;
.sg_zmjk_right_video_title{
height: 35px;
line-height: 35px;
padding-left: 10px;
border-bottom: 1px solid #bbb;
}
.sg_zmjk_right_video_content{
flex: 1;
display: flex;
width: 100%;
// height: 100%;
.sg_zmjk_right_video_content_left{
overflow-y: auto;
overflow-x: hidden;
padding: 10px;
width: 130px;
height: 363px;
border-right: 1px solid #bbb;
cursor: pointer;
.sg_zmjk_right_video_content_left_item{
width: 110px;
// height: 30px;
margin-bottom: 5px;
// line-height: 30px;
text-align: center;
border: 1px solid #bbb;
}
.itemChecked{
background-color: #1890FF;
color: #ffffff;
}
}
.sg_zmjk_right_video_content_right{
flex: 1;
height: 100%;
padding: 10px;
}
}
}
.sg_zmjk_right_information{
height: 180px;
margin: -10px 20px 30px -30px;
border-radius: 5px;
border: 1px solid #bbb;
.sg_zmjk_right_information_title{
height: 35px;
line-height: 35px;
padding-left: 10px;
border-bottom: 1px solid #bbb;
}
.sg_zmjk_right_information_content{
display: flex;
padding: 2px 10px;
div{
width: 33%;
margin-bottom: 10px;
}
}
}
.sg_zmjk_right_more{
margin: -10px 20px 30px -30px;
cursor: pointer;
}
}
}
}

View File

@ -0,0 +1,54 @@
import { useEffect, useReducer, useRef, useState } from "react";
const DURATION = 900;
const CNT = 5;
const INTERVAL = DURATION / CNT;
export function useLinearAnim(val: number | undefined) {
const ren = useRef(val);
const [_, refresh] = useReducer(s => s + 1, 0);
const alive = useRef<boolean>(true);
useEffect(() => {
return () => {
alive.current = false;
}
}, [])
useEffect(() => {
if (typeof val !== 'number') {
return;
}
if (ren.current == undefined) {
ren.current = val;
}
const len = val - ren.current;
let handle: any = null;
if (len) {
const dv = len / CNT;
let cnt = CNT;
handle = setInterval(() => {
if (cnt > 0 && alive.current) {
cnt--;
ren.current! -= dv;
refresh();
}
}, INTERVAL)
}
return () => {
if (handle) {
clearInterval(handle);
}
}
}, [val]);
return ren.current ?? val;
}