feat(zhst/material): 升级智能柜的算法配置面板

This commit is contained in:
NICE CODE BY DEV 2024-06-28 11:56:15 +08:00
parent 7f64ffe221
commit b5a74de4d9
8 changed files with 175 additions and 130 deletions

View File

@ -1,8 +1,9 @@
import React, { forwardRef, ReactNode, useContext, useImperativeHandle } from 'react';
import { Button, Flex, Image } from "antd";
import React, { forwardRef, memo, ReactNode, useContext, useImperativeHandle } from 'react';
import theme from 'antd/es/theme'
import { CropperImage, ConfigProvider } from '@zhst/meta'
import type { CropperImageProps } from '@zhst/meta'
import { CameraOutlined } from '@ant-design/icons'
import { Flex, Button, Image } from 'antd'
import { VideoPlayer, ConfigProvider } from '@zhst/meta'
import type { VideoViewProps } from '@zhst/meta'
import { AlgorithmConfigImg, ErrorImage } from '../utils/base64Images'
import AlgorithmTable from './components/algorithmTable'
import TimeTemplateTable from './components/timeTemplateTable';
@ -10,10 +11,11 @@ import { AlgorithmTableProps } from './components/algorithmTable/AlgorithmTable'
import { TimeTemplateTableProps } from './components/timeTemplateTable/TimeTemplateTable';
import classNames from 'classnames';
import './index.less'
import { Alert } from 'antd';
const { useToken } = theme
const Title = (props: any) => <h2 style={{ color: 'rgba(0, 0, 0, 0.88)' }} {...props}>{props.children}</h2>
const Title = memo((props: any) => (<h2 style={{ color: 'rgba(0, 0, 0, 0.88)' }} {...props}>{props.children}</h2>))
export interface AlgorithmConfigProps {
onAddAlgorithm?: () => void
@ -43,13 +45,18 @@ export interface AlgorithmConfigProps {
id: string;
name: string;
}[]
algorithmTableProps?: AlgorithmTableProps<any>
timeTemplateTableProps?: TimeTemplateTableProps<any>
cropperImageProps?: CropperImageProps
algorithmTableProps?: Partial<AlgorithmTableProps<any>>
timeTemplateTableProps?: Partial<TimeTemplateTableProps<any>>
selectedKey?: string
rowKey?: string
type: AlgorithmTableProps<any>['tableType']
title?:string; // boxList列表的属性名称【点位列表、盒子列表】
/**
* @description
*/
videoUrl?: string
videoProps?: Partial<VideoViewProps>
errorMessage?: string;
onSelect?: (key: string, info?: any) => void
/**
* /
@ -59,6 +66,12 @@ export interface AlgorithmConfigProps {
*
*/
onLoadMoreButtonClick?: () => void;
/**
* box
* @param data box详情
* @returns
*/
customBoxItemRender?: (data?: any) => void
/**
*
*/
@ -79,15 +92,18 @@ const AlgorithmConfig = forwardRef<AlgorithmConfigRef, AlgorithmConfigProps>((pr
const {
algorithmTableDataSource = [],
timeTemplateDataSource = [],
drawListener,
cropperImageProps = {},
algorithmTableProps,
timeTemplateTableProps,
videoProps,
selectedKey,
customBoxItemRender,
errorMessage,
boxList = [],
type = 'multiple',
type = 'single',
rowKey = 'id',
onSelect, title='盒子名称',
onSelect,
title='盒子名称',
videoUrl,
showLoadMoreButton,
onLoadMoreButtonClick,
customBatchCenterContent,
@ -96,47 +112,46 @@ const AlgorithmConfig = forwardRef<AlgorithmConfigRef, AlgorithmConfigProps>((pr
} = props
const { getPrefixCls } = useContext(ConfigContext);
const componentName = getPrefixCls('material-algo', customizePrefixCls);
const { token } = useToken()
// @ts-ignore
const { type: cropType } = cropperImageProps
useImperativeHandle(ref, () => ({
}))
return (
<Flex className={componentName} style={{ border: `1px solid ${token.colorBorder}`, backgroundColor: token.colorBgBase }}>
<div className={classNames(`${componentName}-left`)} title={title} style={{ position: 'relative', width: '13.9%' }}>
<Flex className={componentName} style={{ backgroundColor: token.colorBgBase }}>
<div className={classNames(`${componentName}-left`)} title={title} style={{ position: 'relative' }}>
<Title className={classNames(`${componentName}-title`)}>{title}</Title>
<div className={classNames(`${componentName}-left-list`)} style={{ borderTop: `1px solid ${token.colorBorder}` }}>
<div className={classNames(`${componentName}-left-list`)}>
{boxList.map(item => {
return (
<p
// @ts-ignore
key={item[rowKey]}
// @ts-ignore
onClick={() => onSelect?.(item[rowKey], item)}
style={{
margin: 0,
padding: `${token.paddingXXS}px ${token.paddingLG}px`,
cursor: 'pointer',
customBoxItemRender?.(item) || (
<p
// @ts-ignore
color: selectedKey === item[rowKey] ? token.colorPrimary : token.colorText,
key={item[rowKey]}
// @ts-ignore
backgroundColor: selectedKey === item[rowKey] ? token.blue1 : token.colorBgBase,
transition: '.2s ease all',
}}
onMouseEnter={(e: any) => {
e.target.style.backgroundColor = token.colorPrimaryBg
e.target.style.color = token.colorPrimary
}}
onMouseLeave={(e: any) => {
// @ts-ignore
if (selectedKey === item[rowKey]) return
e.target.style.color = token.colorText
e.target.style.backgroundColor = null
}}
>{item.name}</p>
onClick={() => onSelect?.(item[rowKey], item)}
style={{
margin: 0,
padding: `${token.paddingXXS}px`,
cursor: 'pointer',
// @ts-ignore
color: selectedKey === item[rowKey] ? token.colorPrimary : token.colorText,
// @ts-ignore
backgroundColor: selectedKey === item[rowKey] ? token.blue1 : token.colorBgBase,
transition: '.2s ease all',
}}
onMouseEnter={(e: any) => {
e.target.style.backgroundColor = token.colorPrimaryBg
e.target.style.color = token.colorPrimary
}}
onMouseLeave={(e: any) => {
// @ts-ignore
if (selectedKey === item[rowKey]) return
e.target.style.color = token.colorText
e.target.style.backgroundColor = null
}}
><CameraOutlined style={{ marginRight: 8 }} />{item.name}</p>
)
)
})}
</div>
@ -146,13 +161,13 @@ const AlgorithmConfig = forwardRef<AlgorithmConfigRef, AlgorithmConfigProps>((pr
</div>
)}
</div>
<div style={{ boxSizing: 'border-box', width: '46.3%', textAlign: 'center', borderLeft: `1px solid ${token.colorBorder}`, borderRight: `1px solid ${token.colorBorder}` }}>
<div className={classNames(`${componentName}-middle`)} style={{ boxSizing: 'border-box', width: '46.3%', textAlign: 'center', }}>
{errorMessage && <Alert style={{ marginBottom: 16 }} message={`错误代码:${errorMessage || '-'}`} type="error" showIcon />}
{/* 单个配置 */}
{type === 'single' ? (
<CropperImage
type="line"
onCropEnd={drawListener}
{...cropperImageProps}
<VideoPlayer
url={videoUrl!}
{...videoProps}
/>
) : (
<div className={classNames(`${componentName}-middle-cont`)}>
@ -175,10 +190,10 @@ const AlgorithmConfig = forwardRef<AlgorithmConfigRef, AlgorithmConfigProps>((pr
)
}
</div>
<div style={{ width: '39.8%' }} >
<div className={classNames(`${componentName}-right`)} >
<div>
<Title className={classNames(`${componentName}-title`)}></Title>
<div style={{ padding: `${token.paddingMD}px ${token.paddingSM}px`, borderTop: `1px solid ${token.colorBorder}`, borderBottom: `1px solid ${token.colorBorder}` }}>
<div >
<TimeTemplateTable
dataSource={timeTemplateDataSource}
{...timeTemplateTableProps}
@ -187,9 +202,10 @@ const AlgorithmConfig = forwardRef<AlgorithmConfigRef, AlgorithmConfigProps>((pr
</div>
<div>
<Title className={classNames(`${componentName}-title`)}></Title>
<div style={{ padding: `${token.paddingMD}px ${token.paddingSM}px`, borderTop: `1px solid ${token.colorBorder}` }}>
<div >
<AlgorithmTable
dataSource={algorithmTableDataSource}
timeTemplateData={timeTemplateDataSource}
tableType={type}
{...algorithmTableProps}
/>

View File

@ -1,27 +1,24 @@
import React, { useContext } from 'react';
import { DeleteFilled, EditFilled, ImportOutlined, PlusCircleFilled } from '@ant-design/icons';
import type { ParamsType, ProColumns, ProTableProps } from '@ant-design/pro-components';
import {
ProTable,
} from '@ant-design/pro-components';
import { Popconfirm, Select, Space, Switch } from 'antd';
import { Button, ButtonProps, Popconfirm, Select, Space, Switch } from 'antd';
import { ConfigProvider } from '@zhst/meta'
import theme from 'antd/es/theme'
import { AnyObject } from 'antd/es/_util/type';
import { SelectProps } from 'antd/lib';
import SchemaFormModal from '../schemaFormModal';
import classNames from 'classnames';
import './index.less'
const { useToken } = theme
const { ConfigContext } = ConfigProvider
export interface AlgorithmTableProps<DataSource, Params extends ParamsType = ParamsType, ValueType = "text"> extends ProTableProps<DataSource, Params, ValueType> {
onAddAlgorithm?: (id?: string, record?: any) => void
onItemSwitch?: (status?: boolean, id?: string, info?: any) => void
onItemEdit?: (values?: any, info?: any) => void
onItemEdit?: (values?: any, type?: 'edit' | 'delete' | 'draw' | 'switch' | 'runCycle', info?: any ) => void
onItemDelete?: (id?: string, info?: any) => void
onSortSelect?: SelectProps['onChange']
onReuse?: ButtonProps['onClick']
onDraw?: (id: any, info?: any) => void
selectedKey?: string
sortList?: {
@ -29,87 +26,90 @@ export interface AlgorithmTableProps<DataSource, Params extends ParamsType = Par
value: string;
}[]
tableType?: 'multiple' | 'single' // 多选/单选
timeTemplateData?: any[]
}
const AlgorithmTable= <DataSource extends AnyObject = AnyObject>(
props: AlgorithmTableProps<DataSource, ParamsType, 'text'>
) => {
const {
onAddAlgorithm,
onItemSwitch,
onItemEdit,
onItemDelete,
onSortSelect,
selectedKey,
onDraw,
tableType = 'multiple',
onReuse,
timeTemplateData = [],
sortList = [],
prefixCls: customizePrefixCls
} = props
const { getPrefixCls } = useContext(ConfigContext);
const componentName = getPrefixCls('material-algo-algoTable', customizePrefixCls);
const { token } = useToken()
const columns: ProColumns<DataSource>[] = [
{
editable: false,
title: '算法名称',
dataIndex: 'templateName',
},
{
disable: true,
title: '运行周期',
key: 'runCycle',
dataIndex: 'runCycle',
valueType: 'select',
valueEnum: {
1: { text: '黑夜' },
0: { text: '白天' },
render: (_, record) => {
return (
<Select
style={{ width: 120 }}
placeholder="运行周期"
defaultValue={record.runCycle}
onChange={value => onItemEdit?.(value, 'runCycle', record)}
>
{timeTemplateData.map(o => (
<Select.Option key={o.id} >{o.templateName}</Select.Option>
))}
</Select>
)
},
},
// {
// title: '算力占用',
// dataIndex: 'powerOccupy',
// },
{
editable: false,
title: '启动状态',
dataIndex: 'status',
render: (_, record) => <Switch defaultChecked={record.status} onChange={_status => onItemEdit?.(_status, 'switch', record)} />
},
{
title: '操作',
key: 'option',
valueType: 'option',
fixed: 'right',
width: '120px',
width: 140,
render: (_DOM, record) => [
<Switch value={record.status} onChange={_status => onItemSwitch?.(_status, record.id, record)} />,
<a onClick={() => onDraw?.(record.id, record)} style={{ display: tableType === 'single' ? 'block' : 'none' }} href="#"><ImportOutlined /></a>,
<SchemaFormModal
type={record.templateType}
onFinish={async (values) => onItemEdit?.(values, record)}
trigger={<a href="#"><EditFilled /></a>}
/>
,
<a onClick={() => onItemEdit?.(record.id, 'draw', record)}></a>,
<a onClick={() => onItemEdit?.(record.id, 'edit', record)}></a>,
<Popconfirm
title="确定删除吗?"
onConfirm={() => onItemDelete?.(record.id, record)}
onConfirm={() => onItemEdit?.(record.id, 'delete', record)}
>
<a href="#"><DeleteFilled /></a>
<a style={{ color: 'red' }}></a>
</Popconfirm>,
],
},
];
console.log('123', 123)
return (
<div className={componentName}>
<Space className={classNames(`${componentName}-top`)} size={16}>
<Select
className={classNames(`${componentName}-top-select`)}
value={selectedKey}
placeholder="新增算法"
onChange={onSortSelect}
options={sortList}
/>
<PlusCircleFilled className={classNames(`${componentName}-top-plus`)} onClick={() => onAddAlgorithm?.(selectedKey)} style={{ color: token.colorPrimary, cursor: 'pointer' }} />
<Button onClick={onReuse}></Button>
</Space>
<ProTable<DataSource>
columns={columns}
bordered
scroll={{ y: 240, x: 600 }}
scroll={{ y: 400, x: 600 }}
dataSource={[]}
cardProps={{
bodyStyle: {

View File

@ -3,7 +3,7 @@
margin-bottom: 12px;
&-select {
width: 320px;
width: 160px;
}
&-plus {

View File

@ -5,8 +5,7 @@ import {
ProTable,
} from '@ant-design/pro-components';
import { AnyObject } from 'antd/es/_util/type';
import { InputNumber } from 'antd';
import './index.less'
export interface TimeTemplateTableProps<DataSource, Params extends ParamsType = ParamsType, ValueType = "text"> extends ProTableProps<DataSource, Params, ValueType> {
onItemBlur?: (value?: number | string, id?: any, record?: any) => void,
}
@ -14,10 +13,6 @@ export interface TimeTemplateTableProps<DataSource, Params extends ParamsType =
const TimeTemplateTable = <DataSource extends AnyObject = AnyObject>(
props: TimeTemplateTableProps<DataSource, ParamsType, 'text'>
) => {
const {
onItemBlur,
} = props
const columns: ProColumns<DataSource>[] = [
{
@ -32,28 +27,17 @@ const TimeTemplateTable = <DataSource extends AnyObject = AnyObject>(
title: '布控星期',
dataIndex: 'arrangeDay',
},
// TODO: 暂时先注释后续在做这个功能
// {
// title: '算力占用',
// dataIndex: 'powerOccupy',
// },
{
title: '配置路数',
key: 'option',
valueType: 'option',
render: (_, record) => <InputNumber value={record.lineNum} onBlur={e => onItemBlur?.(e.target.value, record.id, record)} min={0} />,
},
];
return (
<ProTable<DataSource>
className='time-template-table'
columns={columns}
cardProps={{
bodyStyle: {
padding: 0
}
}}
bordered
scroll={{ y: 95 }}
toolbar={undefined}
rowKey="id"

View File

@ -0,0 +1,3 @@
.time-template-table {
margin-bottom: 32px;
}

View File

@ -2,7 +2,7 @@ import React, { useRef, useState } from 'react';
import { AlgorithmConfig, AlgorithmConfigRef } from '@zhst/material';
import type { AlgorithmConfigProps } from '@zhst/material';
import { Button, Space, Switch } from 'antd';
import { CropperImageRefProps } from '@zhst/meta';
import { CropperImageRefProps, VideoViewRef } from '@zhst/meta';
const algorithmTableDataSource: any = []
@ -20,7 +20,7 @@ for (let i = 0; i < 100; i += 1) {
odRect:{
"x":0.553125,"y":0.29722223,"w":0.048958335,"h":0.2462963
},
runCycle: i % 2 !== 0 ? '白天' : '黑夜',
runCycle: String(i),
creator: Math.floor(Math.random() * 20),
});
timeTemplateDataSource.push({
@ -38,6 +38,8 @@ for (let i = 0; i < 100; i += 1) {
})
}
export const VIDEO_URL = `ws://10.0.0.7:9033/flv/File/test/test_h264_${Math.floor(Math.random() * 6) + 1}.mp4.flv?ip=127.0.0.1`
const demo = () => {
const [algorithmTableList, setAlgorithmTableList] = useState(algorithmTableDataSource)
const [timeTemplateData, setTimeTemplateData] = useState(timeTemplateDataSource)
@ -45,14 +47,15 @@ const demo = () => {
const [selectedKey, setSelectedKey] = useState('1')
const [cropType, setCropType] = useState<'line' | 'rect'>('line')
const [algorithmSelectedKey, setAlgorithmSelectedKey] = useState('1')
const [tableType, setTableType] = useState<AlgorithmConfigProps['type']>('multiple')
const [tableType, setTableType] = useState<AlgorithmConfigProps['type']>('single')
const [editAble, setEditAble] = useState(false)
const algorithmConfigRef = useRef<AlgorithmConfigRef>(null)
const cropperImageRef = useRef<CropperImageRefProps>(null)
const videoRef = useRef<VideoViewRef>(null)
const [videoUrl, setVideoUrl] = useState('')
const [odList, setOdList] = useState([])
// 绘画事件
const handleDraw = (id: any, info: any) => {
console.log('cropperImageRef', cropperImageRef)
setEditAble(true)
setCropType('line')
}
@ -75,15 +78,41 @@ const demo = () => {
selectedKey={selectedKey}
onSelect={key => {
setSelectedKey(key)
setVideoUrl(VIDEO_URL)
setOdList([
{
"id": "123",
"x": 0.5519352,
"y": 0.2965385,
"w": 0.05185461,
"h": 0.24698898,
selectAble: false,
},
{
"id": "456",
"x": 0.58543766,
"y": Math.random(),
"w": 0.052037954,
"h": 0.2664015
}
])
// setTimeTemplateData([])
// setTableList([])
// setBoxList([])
}}
videoUrl={videoUrl}
videoProps={{
ref: videoRef,
onCropChange: (showCrop, normalizationRect) => {
console.log('showCrop', showCrop, normalizationRect)
},
showOD: true,
odList
}}
type={tableType}
// 算法模块
algorithmTableProps={{
onItemSwitch: (status, id) => {
console.log('算法状态 switch 变更')
setAlgorithmTableList((pre: any[]) => {
let arr = pre.map(o => {
if (o.id === id) {
@ -94,30 +123,25 @@ const demo = () => {
return arr
})
},
onItemEdit: async (values, itemInfo) => {
console.log('算法单项编辑表单提交', values, itemInfo)
onItemEdit: async (values, type, info) => {
videoRef.current?.setShowCrop(true)
console.log('算法单项编辑表单提交', videoRef, info)
return true
},
onDraw: (id, info) => handleDraw(id, info),
onItemDelete: (id, itemInfo) => console.log('删除', id, itemInfo),
onAddAlgorithm: (id) => console.log('添加模板', id),
selectedKey: algorithmSelectedKey,
onSortSelect: (value) => setAlgorithmSelectedKey(value),
// onSortSelect: (value) => setAlgorithmSelectedKey(value),
sortList: [
{ label: '白天', value: '1' },
{ label: '黑夜', value: '2' },
{ label: '火焰识别', value: '1' },
{ label: '人脸识别', value: '2' },
]
}}
// 时间模板模块
timeTemplateTableProps={{
onItemBlur: (val, id, itemInfo) => console.log('失焦事件', val, id, itemInfo),
}}
cropperImageProps={{
type: cropType,
editAble,
ref: cropperImageRef,
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'
}}
/>
</Space>
);

View File

@ -1,21 +1,38 @@
.zhst-material-algo {
display: flex;
&-title {
margin: 18px 16px;
margin: 16px 0;
margin-top: 0;
font-size: 14px;
}
&-left {
flex: 0 0 240px;
border-right: 1px solid #e5e5e5;
&-list {
padding-bottom: 36px;
max-height: 612px;
max-height: 892px;
overflow-y: scroll;
box-sizing: 'border-box';
}
}
&-middle {
display: flex;
flex-direction: column;
flex: 0 0 1040px;
margin: 0 24px;
height: auto;
&-cont {
align-items: center;
padding: 84px;
border: 1px solid #e5e5e5;
border-radius: 8px;
overflow: hidden;
}
}
&-right {
height: auto;
}
}

View File

@ -358,6 +358,7 @@ const VideoPlayer = forwardRef<VideoViewRef, VideoViewProps>((props, ref) => {
if (!isReady) return;
if (showCrop) {
// 编辑状态
handlerCropStart = addEventListenerWrapper(corpContainerRef.current, EVENT_CROP_START, () => {
setCropRect(null);
});
@ -372,7 +373,7 @@ const VideoPlayer = forwardRef<VideoViewRef, VideoViewProps>((props, ref) => {
alignRef?.current?.forceAlign?.();
});
let video: any = videoRef.current;
//计算 limitcroppbox
//计算 limit crop box
let scale = Math.min(
video.offsetWidth / video.videoWidth,
video.offsetHeight / video.videoHeight
@ -435,8 +436,7 @@ const VideoPlayer = forwardRef<VideoViewRef, VideoViewProps>((props, ref) => {
height: _element.clientHeight,
backgroundColor: 'transparent'
});
// 判定是否存在od框
// 预览状态
showOD && odList?.forEach(_od => {
cropInsRef?.current?.addShape(_od);
})
@ -447,7 +447,7 @@ const VideoPlayer = forwardRef<VideoViewRef, VideoViewProps>((props, ref) => {
cropInsRef?.current?.destroy?.();
cropInsRef.current = null;
};
}, [showCrop, isReady]);
}, [showCrop, isReady, odList]);
const latestCropRect = useLatest(cropRect);
@ -507,6 +507,7 @@ const VideoPlayer = forwardRef<VideoViewRef, VideoViewProps>((props, ref) => {
// 监听showCrop、cropRect - 监听是否可编辑、绘制的矩形
useEffect(() => {
//计算归一化crop rect
// ! 待优化
let normalizationRect = null;
if (showCrop && cropRect) {
let video: any = videoRef.current;