diff --git a/packages/biz/src/RealTimeMonitor/RealTimeMonitor.tsx b/packages/biz/src/RealTimeMonitor/RealTimeMonitor.tsx new file mode 100644 index 0000000..73d7dd4 --- /dev/null +++ b/packages/biz/src/RealTimeMonitor/RealTimeMonitor.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { IRecord, VideoPlayerCardProps, ViewLargerImageModalRef } from '@zhst/biz'; +import WindowToggle from './components/WindowToggle'; +import WarningRecordList from './components/WarningRecordList'; + +interface RealTimeMonitorProps { + videoDataSource?: VideoPlayerCardProps[]; + handleWindowClick?: (key?: string) => void; + handleCloseButtonClick?: (key?: string) => void; + selectedWindowKey?: string; + warningDataSource?: IRecord[]; + viewLargerImageModalRef?: React.RefObject; + /* + 处理 图片下载按钮点击事件 + */ + handleDownloadImg?: (imgSrc?: string) => void; + /* + 处理 预警记录卡片点击事件 + */ + onRecordClick?: (record?: IRecord) => void; + /* + 获取选中的 记录 id 用于 判断是否显示 选中样式 + */ + selectedRecordId?: string; + isRecordListLoading?: boolean; + recordListTitle?: string; + style?: React.CSSProperties; + cardStyle?: React.CSSProperties; + imgStyle?: React.CSSProperties; + largeImageTitle?: string; +} + +export const RealTimeMonitor: React.FC = (props) => { + + const { videoDataSource, + handleWindowClick, + handleCloseButtonClick, + selectedWindowKey, + warningDataSource, + viewLargerImageModalRef, + handleDownloadImg, + onRecordClick, + selectedRecordId, + isRecordListLoading, + } = props + + return ( +
+ + +
+ + ); +}; + +export default RealTimeMonitor; \ No newline at end of file diff --git a/packages/biz/src/RealTimeMonitor/components/WarningRecordList/WarningRecordList.tsx b/packages/biz/src/RealTimeMonitor/components/WarningRecordList/WarningRecordList.tsx new file mode 100644 index 0000000..9aba8b9 --- /dev/null +++ b/packages/biz/src/RealTimeMonitor/components/WarningRecordList/WarningRecordList.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { ViewLargerImageModal, WarningRecordCard, IRecord, ViewLargerImageModalRef } from '@zhst/biz'; +import { Empty, Space, Spin } from 'antd'; +import "./index.less" +import { LoadingOutlined } from '@ant-design/icons'; + +interface WarningRecordListProps { + dataSource?: IRecord[]; + viewLargerImageModalRef?: React.RefObject; + /* + 处理 图片下载按钮点击事件 + */ + handleDownloadImg?: (imgSrc?: string) => void; + /* + 处理 预警记录卡片点击事件 + */ + onRecordClick?: (record?: IRecord) => void; + /* + 获取选中的 记录 id 用于 判断是否显示 选中样式 + */ + selectedRecordId?: string; + isRecordListLoading?: boolean; + recordListTitle?: string; + style?: React.CSSProperties; + cardStyle?: React.CSSProperties; + imgStyle?: React.CSSProperties; + largeImageTitle?: string; +} + +const WarningRecordList: React.FC = (props) => { + + const { + dataSource = [], + viewLargerImageModalRef, + selectedRecordId, + handleDownloadImg, + onRecordClick, + isRecordListLoading, + recordListTitle, + style, + cardStyle, + imgStyle, + largeImageTitle + } = props + + return ( +
+
{recordListTitle}
+
+ { + isRecordListLoading ? +
+ } /> +
+ : (dataSource?.length) > 0 ? + + {dataSource?.map((record, index) => { + if (index > 2) return + return ( { onRecordClick?.(record) }} + selectedRecordId={selectedRecordId} + cardStyle={{ width: 300, height: 264, ...cardStyle }} + imgStyle={{ width: 280, height: 169, ...imgStyle }} + />) + } + )} + + : +
+ +
+ } +
+ {/* 弹窗 绑定ref 后可以调用 handleCancel方法关闭弹窗 show方法打开弹窗 */} + +
+ ) +}; + +export default WarningRecordList; \ No newline at end of file diff --git a/packages/biz/src/RealTimeMonitor/components/WarningRecordList/index.less b/packages/biz/src/RealTimeMonitor/components/WarningRecordList/index.less new file mode 100644 index 0000000..e348f82 --- /dev/null +++ b/packages/biz/src/RealTimeMonitor/components/WarningRecordList/index.less @@ -0,0 +1,20 @@ +.zhst-biz-warning-record-list { + display: flex; + flex-direction: column; + border-left: solid 1px #00000026; + width: 320px; + + .header { + width: 100%; + height: 48px; + background-color: #EFF2F4; + padding: 10px 20px; + box-sizing: border-box; + } + + .body { + padding: 10px; + overflow: hidden; + flex: 1; + } +} \ No newline at end of file diff --git a/packages/biz/src/RealTimeMonitor/components/WarningRecordList/index.tsx b/packages/biz/src/RealTimeMonitor/components/WarningRecordList/index.tsx new file mode 100644 index 0000000..3e4aa09 --- /dev/null +++ b/packages/biz/src/RealTimeMonitor/components/WarningRecordList/index.tsx @@ -0,0 +1,2 @@ +import WarningRecordList from './WarningRecordList' +export default WarningRecordList \ No newline at end of file diff --git a/packages/biz/src/RealTimeMonitor/components/WindowToggle/WindowToggle.tsx b/packages/biz/src/RealTimeMonitor/components/WindowToggle/WindowToggle.tsx new file mode 100644 index 0000000..0211f0f --- /dev/null +++ b/packages/biz/src/RealTimeMonitor/components/WindowToggle/WindowToggle.tsx @@ -0,0 +1,71 @@ +import React, { useState } from 'react'; +import { Segmented } from 'antd'; +import { AppstoreOutlined, BarsOutlined } from '@ant-design/icons'; +import { VideoPlayerCard, VideoPlayerCardProps } from '@zhst/biz'; +import './index.less' +import { theme } from 'antd/lib'; + +type Size = 'large' | 'small' + +interface WindowToggleProps { + dataSource?: VideoPlayerCardProps[]; + handleWindowClick?: (key?: string) => void; + handleCloseButtonClick?: (key?: string) => void; + selectedWindowKey?: string; +} + +export const WindowToggle: React.FC = (props) => { + + const { dataSource = [], handleWindowClick, handleCloseButtonClick, selectedWindowKey } = props + const [size, setSize] = useState("large"); + const { useToken } = theme + const { token } = useToken() + + const getLabelStyle = (isSelected: boolean) => ({ + padding: "0 11px", background: "#fff", + ...(isSelected ? { background: token.colorPrimary, color: '#fff' } : {}), + }); + + + return ( +
+ {/* 切换按钮 */} +
+
}, + { value: 'small', label:
}, + ]} + onChange={(value) => { + // 当一个窗口时 默认 selectedkey 第一条数据的 windowkey + if (value === 'large' && dataSource.length > 0) { + const { windowKey } = dataSource[0] + handleWindowClick?.(windowKey) + } + setSize(value as Size) + }} + /> +
+ +
+ { + dataSource?.map((item, index) => { + if (size === "large" && index > 0) return + return ( + ) + }) + } +
+ + ); +}; + +export default WindowToggle; + diff --git a/packages/biz/src/RealTimeMonitor/components/WindowToggle/index.less b/packages/biz/src/RealTimeMonitor/components/WindowToggle/index.less new file mode 100644 index 0000000..61c77bd --- /dev/null +++ b/packages/biz/src/RealTimeMonitor/components/WindowToggle/index.less @@ -0,0 +1,45 @@ +.zhst-biz-window-toggle { + display: flex; + flex-direction: column; + flex: 1; + + .header { + width: 100%; + height: 48px; + background-color: #EFF2F4; + padding: 10px 20px; + box-sizing: border-box; + + .ant-segmented { + padding: 0; + + .ant-segmented-group { + border-radius: 4px; + overflow: hidden; + + .ant-segmented-item { + border-radius: 0; + + .ant-segmented-item-label { + padding: 0; + } + } + } + } + } + + .body { + flex: 1; + width: 100%; + background-color: #E5EAEC; + padding: 10px; + box-sizing: border-box; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + + >div { + margin: 10px; + } + } +} \ No newline at end of file diff --git a/packages/biz/src/RealTimeMonitor/components/WindowToggle/index.tsx b/packages/biz/src/RealTimeMonitor/components/WindowToggle/index.tsx new file mode 100644 index 0000000..0fa9c9a --- /dev/null +++ b/packages/biz/src/RealTimeMonitor/components/WindowToggle/index.tsx @@ -0,0 +1,2 @@ +import WindowToggle from './WindowToggle' +export default WindowToggle \ No newline at end of file diff --git a/packages/biz/src/RealTimeMonitor/demo/base.tsx b/packages/biz/src/RealTimeMonitor/demo/base.tsx new file mode 100644 index 0000000..90338fd --- /dev/null +++ b/packages/biz/src/RealTimeMonitor/demo/base.tsx @@ -0,0 +1,161 @@ + +import React, { useState } from 'react'; +import { IRecord, RealTimeMonitor, VideoPlayerCardProps, useViewLargerImageModal } from '@zhst/biz'; +import { videoData, warningData } from './mock'; +import { Space } from 'antd'; +import dayjs from 'dayjs' +import './index.less' + +export default () => { + + // 必须设置初始数据 用于渲染 4个窗口 + const initialVideoDataSource: VideoPlayerCardProps[] = [ + { + windowKey: 'first-window', + }, + { + windowKey: 'second-window', + }, + { + windowKey: 'third-window', + }, + { + windowKey: 'forth-window', + } + ] + const [videoDataSource, setVideoDataSource] = useState(initialVideoDataSource); + const [warningDataSource, setWarningDataSource] = useState(); + const [selectedWindowKey, setSelectedWindowKey] = useState('first-window'); + const [selectedRecordId, setSelectedRecordId] = React.useState() + const [isRecordListLoading, setIsRecordListLoading] = React.useState(false) + // 把弹窗的ref 拿出来 + const viewLargerImageModalRef = useViewLargerImageModal() + + const handleWindowClick = (key?: string) => { + setSelectedWindowKey(key) + } + + const clearWindowData = (key?: string) => { + // 当关闭窗口时 也要刷新 右侧 预警记录接口 不要忘记 + setVideoDataSource((pre) => { + const newVideoDataSource = pre.map((item) => { + if (item.windowKey === key) { + return { windowKey: key }; + } + return item; // 保持不变 + }); + return newVideoDataSource + }) + } + + const handleDownloadImg = (imgSrc?: string) => { + console.log(imgSrc) + // 可以调用 下面 方法关闭弹窗 + // viewLargerImageModalRef.current?.handleCancel() + } + + const onRecordClick = (record?: IRecord) => { + // 点击的时候把数据 拿过来处理一下传给大图弹框 + const { imgSrc, warningType, boxId, position, cabietId, warningTime, warningTimestamp, warningTimeFormat = 'YYYY-MM-DD HH:mm:ss' } = record || {} + const formattedDate = warningTimestamp ? dayjs(warningTimestamp).format(warningTimeFormat) : ''; + const warningTimeShow = warningTime ? warningTime : formattedDate + //用于渲染右侧的 信息 + const warningData = [ + { label: '预警类型', value: warningType }, + { label: '预警时间', value: warningTimeShow }, + { label: '盒子', value: boxId }, + { label: '点位', value: position }, + { label: '柜子ID', value: cabietId }, + ] + // 调用这个方法打开弹框 + viewLargerImageModalRef?.current?.show({ imgSrc: imgSrc, warningData: warningData }) + + setSelectedRecordId(record?.id) + } + + const mockData = () => { + // 请求时需要对应窗口为loading 状态 + setVideoDataSource((pre) => { + const newVideoDataSource: VideoPlayerCardProps[] = pre.map((item) => { + // 开启loading + if (item.windowKey === selectedWindowKey) { + return { ...item, isWindowLoading: true, title: '' } + } + return item + }); + return newVideoDataSource + }) + + // 模拟 视频数据请求 + setTimeout(() => { + // 对后端返回数据进行处理 组装一套符合属性的 数据 + const newVideoData: VideoPlayerCardProps = { imgSrc: videoData.imgSrc, title: videoData.title, } + setVideoDataSource((pre) => { + const newVideoDataSource: VideoPlayerCardProps[] = pre.map((item) => { + // 传给 选中的视频窗口 + if (item.windowKey === selectedWindowKey) { + return { ...item, ...newVideoData } + } + return item + }); + return newVideoDataSource + }) + + setVideoDataSource((pre) => { + const newVideoDataSource: VideoPlayerCardProps[] = pre.map((item) => { + // 关闭loading + if (item.windowKey === selectedWindowKey) { + return { ...item, isWindowLoading: false } + } + return item + }); + return newVideoDataSource + }) + + }, 3000); + + // 模拟 预警记录数据请求 + + setIsRecordListLoading(true) + + setTimeout(() => { + const newWarningDataSource: IRecord[] = warningData.map(o => { + return { + imgSrc: o.imgSrc, + id: o.id, + warningType: o.warningType, + boxId: o.boxId, + position: o.position, + cabietId: o.cabietId, + //,`柜子ID: ${o.cabietId}` + warningInfo: [`盒子${o.boxId}`, `位置${o.position}`, `柜子ID${o.cabietId}`], + // cabietText: `柜子ID: ${o.cabietId}`, + warningTimestamp: o.warningTimestamp, + } + }) + setWarningDataSource(newWarningDataSource) + setIsRecordListLoading(false) + }, 1000) + + } + + return ( + + + + + + + ) +} diff --git a/packages/biz/src/RealTimeMonitor/demo/index.less b/packages/biz/src/RealTimeMonitor/demo/index.less new file mode 100644 index 0000000..71ea110 --- /dev/null +++ b/packages/biz/src/RealTimeMonitor/demo/index.less @@ -0,0 +1,5 @@ +#realtimemonitor-demo-base { + .dumi-default-previewer-demo>div { + width: 100%; + } +} \ No newline at end of file diff --git a/packages/biz/src/RealTimeMonitor/demo/mock.tsx b/packages/biz/src/RealTimeMonitor/demo/mock.tsx new file mode 100644 index 0000000..4497d61 --- /dev/null +++ b/packages/biz/src/RealTimeMonitor/demo/mock.tsx @@ -0,0 +1,65 @@ +export const videoData = { + imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png', + // videoSrc: 'ws://10.0.0.7:9033/flv/File/test/test_h264_1.mp4.flv?ip=127.0.0.1', + title: `盒子1 点位1` +} + +export const warningData = [ + { + imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png', + id: '156156ewr1', + warningType: '火焰识别', + boxId: '2', + position: '2', + cabietId: '002', + // warningTime: "2023-03-01 ", + warningTimestamp: Date.now(), + // warningTimeFormat:"YYYY-MM-DD" + }, + { + imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png', + id: '156156ewr155', + warningType: '火焰识别', + boxId: '1', + position: '1', + cabietId: '001', + // warningTime: "2023-03-01 ", + warningTimestamp: Date.now(), + // warningTimeFormat:"YYYY-MM-DD" + }, + { + imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png', + id: '156156rew155', + warningType: '火焰识别', + boxId: '3', + position: '3', + cabietId: '004', + // warningTime: "2023-03-01 ", + warningTimestamp: Date.now(), + // warningTimeFormat:"YYYY-MM-DD" + }, + { + imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png', + id: '15615615ew5', + warningType: '火焰识别', + boxId: '4', + position: '4', + cabietId: '004', + // warningTime: "2023-03-01 ", + warningTimestamp: Date.now(), + // warningTimeFormat:"YYYY-MM-DD" + }, + { + imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png', + id: '15615615sdf5', + warningType: '火焰识别', + boxId: '5', + position: '5', + cabietId: '005', + // warningTime: "2023-03-01 ", + warningTimestamp: Date.now(), + // warningTimeFormat:"YYYY-MM-DD" + } +] + + diff --git a/packages/biz/src/RealTimeMonitor/index.md b/packages/biz/src/RealTimeMonitor/index.md new file mode 100644 index 0000000..e18579f --- /dev/null +++ b/packages/biz/src/RealTimeMonitor/index.md @@ -0,0 +1,36 @@ +--- +group: 数据展示 +category: Components +subtitle: 实时监控页面 +title: RealTimeMonitor 实时监控页面 +--- + +# RealTimeMonitor 实时监控页面 + + + + + + +## API + +| 参数 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| videoDataSource | 用于渲染 每个窗口的数据 | VideoPlayerCardProps[] | 需要传一组默认值用于窗口的渲染| - | +| warningDataSource | 用于渲染 预警记录的数据 | WindowProps[] | 需要传一组默认值用于窗口的渲染| - | +| handleWindowClick | 用于获取窗口的 windowKey 方便更新对应窗口数据 | (key?: string) => void; | - | - | +| handleCloseButtonClick | 用于点击窗口关闭按钮的事件 | (key?: string) => void; | - | - | +| selectedWindowKey | 选中的窗口的 key 用于控制 选中边框样式 |string| - | - | +| handleDownloadImg | 用于处理查看大图下载图片的事件 | (key?: string) => void; | - | - | +| onRecordClick | 用于处理预警记录卡片点击事件事件 | (key?: string) => void; | - | - | +| selectedRecordId| 用于判断是否显示选中样式 |string| - | - | +| isRecordListLoading | 判断预警记录列表loading状态 |boolean| - | - | +| recordListTitle | 预警记录列表的标题 | string | - | - | +| viewLargerImageModalRef | 查看大图弹窗的ref | viewLargerImageModalRef={viewLargerImageModalRef}| | - | +| viewLargerImageModalRef | 查看大图弹窗的ref |React.RefObject\| - | - | + + + + + + diff --git a/packages/biz/src/RealTimeMonitor/index.tsx b/packages/biz/src/RealTimeMonitor/index.tsx new file mode 100644 index 0000000..00985f9 --- /dev/null +++ b/packages/biz/src/RealTimeMonitor/index.tsx @@ -0,0 +1,2 @@ +import RealTimeMonitor from './RealTimeMonitor' +export default RealTimeMonitor \ No newline at end of file diff --git a/packages/biz/src/VideoPlayerCard/VideoPlayerCard.tsx b/packages/biz/src/VideoPlayerCard/VideoPlayerCard.tsx new file mode 100644 index 0000000..7499d22 --- /dev/null +++ b/packages/biz/src/VideoPlayerCard/VideoPlayerCard.tsx @@ -0,0 +1,101 @@ +import { Card, Space, CardProps, Spin, Button } from 'antd'; +import { theme } from 'antd/lib'; +import { VideoPlayer, type VideoViewRef } from '@zhst/meta'; +import React, { useState, useEffect, ReactNode, useRef } from 'react'; +import { CloseOutlined, LoadingOutlined } from '@ant-design/icons'; +import './index.less' +export interface VideoPlayerCardProps { + windowKey?: string; + selectedWindowKey?: string; + showType?: 'video' | "image"; + imgSrc?: string; + videoSrc?: string; + cardProps?: CardProps; + errorReasonText?: string; + isWindowLoading?: boolean; + size?: 'large' | 'small'; + title?: string | ReactNode + handleCloseButtonClick?: (key?: string) => void; + handleWindowClick?: (key?: string) => void; +} + +export const VideoPlayerCard: React.FC = (props) => { + + const componentName = `zhst-biz-video-player-card`; + const { showType, imgSrc, videoSrc, cardProps, isWindowLoading, errorReasonText, size, title, handleCloseButtonClick, handleWindowClick, windowKey, selectedWindowKey = '' } = props; + const [cardContent, setCardContent] = useState(null); + const { useToken } = theme + const { token } = useToken() + const videoRef = useRef(null) + const selectedBorderStyle = { + border: `2px solid ${token.colorPrimary}`, boxShadow: " 0px 2px 9px 0px rgba(0,0,0,0.16)" + } + const cardStyle: React.CSSProperties = { + ...(size === 'large' ? { height: 931 } : { height: 456, cursor: 'pointer' }), + ...(size === 'small' && selectedWindowKey === windowKey ? selectedBorderStyle : {}) + }; + const videoPlayerCardStyle = size === 'small' ? { width: "calc(50% - 20px)" } : { flex: 1 } + + useEffect(() => { + if (!isWindowLoading && (videoSrc || imgSrc)) { + let contentElement: JSX.Element | null = null; + if (videoSrc) { + contentElement = ( + + ); + videoRef.current?.setShowCrop(true) + + } else if (imgSrc) { + contentElement = ( + 首帧图 + ); + } + setCardContent(contentElement); + } else { + setCardContent(null) + } + }, [showType, imgSrc, videoSrc, isWindowLoading]); + + return ( +
{ handleWindowClick?.(windowKey) }} style={videoPlayerCardStyle}> + +
{title}
+
+ +
+ } + style={{ display: "flex", flexDirection: "column", borderRadius: 4, overflow: "hidden", ...cardStyle }} + bodyStyle={{ flex: 1 }} + {...cardProps} + > + {cardContent ? ( + <> + {cardContent} + + ) : ( +
+ { + isWindowLoading ? +
+ } /> +
+ : !!errorReasonText && {errorReasonText} + } +
+ )} + {/* 其他内容 */} +
+
+ ); +}; + +export default VideoPlayerCard; + diff --git a/packages/biz/src/VideoPlayerCard/demo/base.tsx b/packages/biz/src/VideoPlayerCard/demo/base.tsx new file mode 100644 index 0000000..7063d94 --- /dev/null +++ b/packages/biz/src/VideoPlayerCard/demo/base.tsx @@ -0,0 +1,19 @@ + +import React from 'react'; +import { VideoPlayerCard, type VideoPlayerCardProps } from '@zhst/biz'; +import { Space } from 'antd'; + +const mockVideoPlayerCardProps: VideoPlayerCardProps = { + showType: 'image', + // videoSrc: 'https://example.com/video.mp4', + imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png', // 如果需要在没有视频时显示图片封面 + size: 'small', +}; + +export default () => { + return ( + + + + ) +} diff --git a/packages/biz/src/VideoPlayerCard/index.less b/packages/biz/src/VideoPlayerCard/index.less new file mode 100644 index 0000000..0f87e3e --- /dev/null +++ b/packages/biz/src/VideoPlayerCard/index.less @@ -0,0 +1,30 @@ +.zhst-biz-video-player-card { + .ant-card-head { + padding: 0 20px; + } + + .ant-card-body { + padding: 0; + overflow: hidden; + border-radius: 0; + + .zhst-image__video-view { + height: 100%; + } + } + + .card-close-button { + .ant-btn { + padding: 0 3px; + height: 22px; + color: #00000073; + } + + .ant-btn:hover { + padding: 0 3px; + height: 22px; + color: #000000e0; + } + } + +} \ No newline at end of file diff --git a/packages/biz/src/VideoPlayerCard/index.md b/packages/biz/src/VideoPlayerCard/index.md new file mode 100644 index 0000000..a888603 --- /dev/null +++ b/packages/biz/src/VideoPlayerCard/index.md @@ -0,0 +1,34 @@ +--- +group: 数据展示 +category: Components +subtitle: 视频播放卡片 +title: VideoPlayerCard 视频播放卡片 +--- + +# VideoPlayerCard 视频播放卡片 + + + + + + +## API + +| 参数 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| windowKey | 每个卡片的唯一标识 | string | - | - | +| selectedWindowKey | 选中的窗口key | string | - | - | +| imgSrc | 图片地址 | string | - | - | +| videoSrc | 视频地址 | string | - | - | +| errorReasonText | 加载失败的错误提示 | string | - | - | +| isWindowLoading | 判断是否显示loading | boolean | - | - | +| size | 设置窗口大小 | 'large' \| 'small' | - | - | +| title | 设置窗口标题 | string \| ReactNode | - | - | +| handleCloseButtonClick | 处理关闭按钮事件 | (key?: string) => void| - | - | +| handleWindowClick | 处理窗口点击事件 | (key?: string) => void| - | - | + + + + + + diff --git a/packages/biz/src/VideoPlayerCard/index.tsx b/packages/biz/src/VideoPlayerCard/index.tsx new file mode 100644 index 0000000..20710eb --- /dev/null +++ b/packages/biz/src/VideoPlayerCard/index.tsx @@ -0,0 +1,3 @@ +import VideoPlayerCard from './VideoPlayerCard' +export type { VideoPlayerCardProps } from './VideoPlayerCard' +export default VideoPlayerCard \ No newline at end of file diff --git a/packages/biz/src/ViewLargerImageModal/ViewLargerImageModal.tsx b/packages/biz/src/ViewLargerImageModal/ViewLargerImageModal.tsx new file mode 100644 index 0000000..f4475bd --- /dev/null +++ b/packages/biz/src/ViewLargerImageModal/ViewLargerImageModal.tsx @@ -0,0 +1,86 @@ +import React, { useImperativeHandle, useRef, useState, forwardRef } from 'react'; +import { Modal, ModalProps, Space, SpaceProps } from 'antd'; +import theme from 'antd/lib/theme'; +import { DownloadOutlined } from '@ant-design/icons'; +import './index.less' + +type ViewLargerImageModalParams = { + imgSrc?: string; + warningData?: { + label?: string; + value?: string; + }[]; +}; + +export interface ViewLargerImageModalRef { + show: (params?: ViewLargerImageModalParams) => void; + handleCancel: () => void; +} + +export interface ViewLargerImageModalProps { + imgStyle?: React.CSSProperties; + downloadImg?: (imgSrc?: string) => void; + title?: string; + downloadText?: string; + modalProps?: ModalProps + spaceProps?: SpaceProps; +} + +export const ViewLargerImageModal = forwardRef( + (props, ref) => { + + const { modalProps, downloadImg, imgStyle, title = '预警大图', downloadText = '下载大图', spaceProps } = props + const { useToken } = theme + const { token } = useToken() + const [open, setOpen] = useState(false); + const [imgSrc, setImgSrc] = useState(); + const [warningData, setWarningData] = useState(); + + const handleCancel = () => { + setOpen(false); + } + + useImperativeHandle(ref, () => { + return { + show: (_params) => { + setOpen(true); + setImgSrc(_params?.imgSrc) + setWarningData(_params?.warningData) + }, + handleCancel + }; + }); + + return ( + + + {title} +
+ {warningData?.map(({ label, value }) => ( +
+ {`${label}: `} + {value} +
+ ))} + {imgSrc && downloadImg &&
downloadImg?.(imgSrc)} >{downloadText}
} +
+
+
+ ); + } +) + +export default ViewLargerImageModal; + +export const useViewLargerImageModal = () => { + return useRef(null); +}; \ No newline at end of file diff --git a/packages/biz/src/ViewLargerImageModal/demo/base.tsx b/packages/biz/src/ViewLargerImageModal/demo/base.tsx new file mode 100644 index 0000000..0668f55 --- /dev/null +++ b/packages/biz/src/ViewLargerImageModal/demo/base.tsx @@ -0,0 +1,94 @@ + +import React from 'react'; +import { ViewLargerImageModal, WarningRecordCard, IRecord, useViewLargerImageModal } from '@zhst/biz'; +import { Space } from 'antd'; +import dayjs from 'dayjs'; + +// 结合预警图列表 演示查看预警大图的使用 例如 后端返回这样的数据结构 +const backEndData = [ + { + imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png', + id: '1561561', + warningType: '火焰识别', + boxId: '2', + position: '2', + cabietId: '002', + // warningTime: "2023-03-01 ", + warningTimestamp: Date.now(), + // warningTimeFormat:"YYYY-MM-DD" + }, + { + imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png', + id: '156156155', + warningType: '火焰识别', + boxId: '1', + position: '1', + cabietId: '001', + // warningTime: "2023-03-01 ", + warningTimestamp: Date.now(), + // warningTimeFormat:"YYYY-MM-DD" + } +] + +// 前端处理数据结构 +const dataSource = backEndData.map(o => { + return { + imgSrc: o.imgSrc, + id: o.id, + warningType: o.warningType, + boxId: o.boxId, + position: o.position, + cabietId: o.cabietId, + //,`柜子ID ${o.cabietId}` + warningInfo: [`盒子${o.boxId}`, `位置${o.position}`], + cabietText: `柜子ID: ${o.cabietId}`, + warningTimestamp: o.warningTimestamp, + } +}) + + + +export default () => { + + const [selectedRecordId, setSelectedRecordId] = React.useState() + + // 把弹窗的ref 拿出来 + const viewLargerImageModalRef = useViewLargerImageModal() + + const handleDownloadImg = () => { + console.log('download') + // 可以调用 下面 方法关闭弹窗 + // viewLargerImageModalRef.current?.handleCancel() + } + + const handleClick = (record?: IRecord) => { + // 点击的时候把数据 拿过来处理一下传给大图弹框 + const { imgSrc, warningType, boxId, position, cabietId, warningTime, warningTimestamp, warningTimeFormat = 'YYYY-MM-DD HH:mm:ss' } = record || {} + const formattedDate = warningTimestamp ? dayjs(warningTimestamp).format(warningTimeFormat) : ''; + const warningTimeShow = warningTime ? warningTime : formattedDate + //用于渲染右侧的 信息 + const warningData = [ + { label: '预警类型', value: warningType }, + { label: '预警时间', value: warningTimeShow }, + { label: '盒子', value: boxId }, + { label: '点位', value: position }, + { label: '柜子ID', value: cabietId }, + ] + // 调用这个方法打开弹框 + viewLargerImageModalRef?.current?.show({ imgSrc: imgSrc, warningData: warningData }) + + setSelectedRecordId(record?.id) + } + + return ( + <> + + { + dataSource?.map((record) => { handleClick(record) }} selectedRecordId={selectedRecordId} />) + } + + {/* 弹窗 绑定ref 后可以调用 handleCancel方法关闭弹窗 show方法打开弹窗 */} + + + ) +} diff --git a/packages/biz/src/ViewLargerImageModal/index.less b/packages/biz/src/ViewLargerImageModal/index.less new file mode 100644 index 0000000..32363d0 --- /dev/null +++ b/packages/biz/src/ViewLargerImageModal/index.less @@ -0,0 +1,62 @@ +.zhst-biz-view-warning-larger-image-modal { + font-family: MicrosoftYaHei; + + .ant-modal-content { + padding: 0; + height: 492px; + border-radius: 6px; + overflow: hidden; + + .ant-modal-close { + top: 14px; + right: 16px; + } + + .ant-modal-header { + height: 48px; + line-height: 48px; + margin-bottom: 0; + + .ant-modal-title { + height: 100%; + line-height: 48px; + font-weight: bold; + padding-left: 20px; + } + } + + .ant-modal-body { + height: 444px; + + >div { + width: 100%; + height: 100%; + align-items: flex-start; + + >div:nth-child(2) { + position: relative; + flex: 1; + box-sizing: border-box; + height: 100%; + padding: 30px 16px; + + .right-context>div { + margin-bottom: 20px; + } + + .right-context .context-key { + font-weight: bold; + } + + .img-download { + position: absolute; + bottom: 0; + cursor: pointer; + } + + } + + } + } + } +} \ No newline at end of file diff --git a/packages/biz/src/ViewLargerImageModal/index.md b/packages/biz/src/ViewLargerImageModal/index.md new file mode 100644 index 0000000..d4ae110 --- /dev/null +++ b/packages/biz/src/ViewLargerImageModal/index.md @@ -0,0 +1,28 @@ +--- +group: 数据展示 +category: Components +subtitle: 查看大图弹窗 +title: ViewLargerImageModal 查看大图弹窗 +--- + +# ViewLargerImageModal 查看大图弹窗 + + + + + + +## API + +| 参数 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| show() |通过 ref 用于开启弹窗 可以将点击的 记录传给弹窗| | | | +| handleCancel() | 通过 ref 用于关闭弹窗 | | | | +| imgSrc | 图片地址 |string | | | +| contextData | 大图显示的数据 | | | | +| imgStyle | 用于修改图片样式 | | | | +| downloadImg | 传入下载图片的方法 | | | | +| title | 弹窗标题 | string | | | +| downloadText | 下载按钮文本 | string | | | +| modalProps | 弹窗属性 | | | | + diff --git a/packages/biz/src/ViewLargerImageModal/index.tsx b/packages/biz/src/ViewLargerImageModal/index.tsx new file mode 100644 index 0000000..2780f86 --- /dev/null +++ b/packages/biz/src/ViewLargerImageModal/index.tsx @@ -0,0 +1,4 @@ +import ViewLargerImageModal, { useViewLargerImageModal } from './ViewLargerImageModal' +export type { ViewLargerImageModalRef, ViewLargerImageModalProps } from './ViewLargerImageModal' +export default ViewLargerImageModal; +export { useViewLargerImageModal }; \ No newline at end of file diff --git a/packages/biz/src/WarningRecordCard/WarningRecordCard.tsx b/packages/biz/src/WarningRecordCard/WarningRecordCard.tsx index 6230bf8..4833868 100644 --- a/packages/biz/src/WarningRecordCard/WarningRecordCard.tsx +++ b/packages/biz/src/WarningRecordCard/WarningRecordCard.tsx @@ -1,16 +1,13 @@ -import { Card, Space, Divider } from 'antd'; +import { Card, Space, Divider, CardProps } from 'antd'; +import { theme } from 'antd/lib'; import React from 'react'; import dayjs from 'dayjs'; import './index.less' - -const componentName = `zhst-biz-warning-record-card`; - export interface IRecord { imgSrc?: string; id?: string; - /** * 预警类型 */ @@ -21,12 +18,28 @@ export interface IRecord { warningInfo?: string[] /* 右侧 柜子id 显示 + /* + 盒子 ID */ - cabietInfo?: string; + boxId: string; + /* + 位置信息 + */ + position: string; + /* + 柜子id + */ + cabietId?: string; /* 直接传格式化好的时间 + /* + 右侧 柜子id 显示 + */ + cabietText?: string; + /* + 直接传格式化好的时间 */ - warningTime?: string ; + warningTime?: string; /* 传格时间戳 */ @@ -43,29 +56,41 @@ export interface WarningRecordCardProps { record?: IRecord; onRecordClick?: (record?: IRecord) => void; style?: React.CSSProperties; + cardProps?: CardProps; + selectedRecordId?: string; + cardStyle?: React.CSSProperties; + imgStyle?: React.CSSProperties; }; export const WarningRecordCard: React.FC = (props) => { - const { record, onRecordClick, style } = props; - const { imgSrc = '', id = '', warningType = '', warningInfo = [], cabietInfo = '', warningTime, warningTimestamp, warningTimeFormat = 'YYYY-MM-DD HH:mm:ss' } = record || {} + const componentName = `zhst-biz-warning-record-card`; + const { record, onRecordClick, style, cardProps, selectedRecordId, cardStyle, imgStyle } = props; + const { imgSrc, id, warningType, warningInfo = [], cabietText, warningTime, warningTimestamp, warningTimeFormat = 'YYYY-MM-DD HH:mm:ss' } = record || {} const formattedDate = warningTimestamp ? dayjs(warningTimestamp).format(warningTimeFormat) : ''; const warningTimeShow = warningTime ? warningTime : formattedDate + const { useToken } = theme + const { token } = useToken() + const selectedBorderStyle = { + border: `2px solid ${token.colorPrimary}`, boxShadow: " 0px 2px 9px 0px rgba(0,0,0,0.16)" + } + const selectedCardStyle: React.CSSProperties = { + ...(selectedRecordId === record?.id ? selectedBorderStyle : {}) + }; const handleClick = () => { onRecordClick?.(record); }; return ( -
- +
} + cover={预警图} + style={{ width: 356, height: 302, padding: 10, borderRadius: 4, ...selectedCardStyle, ...cardStyle }} + {...cardProps} > -
-
{warningType}
+
{warningType}
}> {warningInfo?.map((item, index) => (
@@ -75,9 +100,7 @@ export const WarningRecordCard: React.FC = (props) => {
{warningTimeShow}
- -
{cabietInfo}
- +
{cabietText}
); diff --git a/packages/biz/src/WarningRecordCard/demo/base.tsx b/packages/biz/src/WarningRecordCard/demo/base.tsx index b954ceb..51a8039 100644 --- a/packages/biz/src/WarningRecordCard/demo/base.tsx +++ b/packages/biz/src/WarningRecordCard/demo/base.tsx @@ -1,29 +1,56 @@ import React from 'react'; -import {WarningRecordCard,type IRecord} from '@zhst/biz'; +import { WarningRecordCard } from '@zhst/biz'; import { Space } from 'antd'; -const props = { - record : - { - imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png', - id: '1561561', - warningType: '火焰识别', - warningInfo:['盒子1','点位1'], - cabietInfo: '柜子ID : C001', - // warningTime: "2023-03-01 ", - warningTimestamp :Date.now() , - // warningTimeFormat:"YYYY-MM-DD" - }, - onRecordClick:(record?:IRecord) =>{ - console.log(record) - } -} +// 例如 后端返回这样的数据结构 +const backEndData = [ + { + imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png', + id: '1561561', + warningType: '火焰识别', + boxId: '2', + position: '2', + cabietId: '002', + // warningTime: "2023-03-01 ", + warningTimestamp: Date.now(), + // warningTimeFormat:"YYYY-MM-DD" + }, + { + imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png', + id: '156156155', + warningType: '火焰识别', + boxId: '1', + position: '1', + cabietId: '001', + // warningTime: "2023-03-01 ", + warningTimestamp: Date.now(), + // warningTimeFormat:"YYYY-MM-DD" + } +] + +// 前端处理数据结构 +const dataSource = backEndData.map(o => { + return { + imgSrc: o.imgSrc, + id: o.id, + warningType: o.warningType, + boxId: o.boxId, + position: o.position, + cabietId: o.cabietId, + warningInfo: [`盒子${o.boxId}`, `位置${o.position}`, `柜子ID${o.cabietId}`], + // cabietText: `柜子ID: ${o.cabietId}`, + warningTimestamp: o.warningTimestamp, + } +}) export default () => { + return ( - + { + dataSource?.map((record) => ) + } ) } diff --git a/packages/biz/src/WarningRecordCard/index.less b/packages/biz/src/WarningRecordCard/index.less index a7ca119..7cfba38 100644 --- a/packages/biz/src/WarningRecordCard/index.less +++ b/packages/biz/src/WarningRecordCard/index.less @@ -1,6 +1,8 @@ .zhst-biz-warning-record-card { + cursor: pointer; + .ant-card-body { - padding: 0 !important; + padding: 0; font-family: MicrosoftYaHei; line-height: 19px; display: flex; @@ -9,17 +11,17 @@ .left-context { flex: 1; - > div{ + >div { margin-top: 6px; } - > div:nth-child(1) { - margin-top: 0; + >div:nth-child(1) { + margin-top: 0; } } - - .description{ + + .warning-type { font-weight: bold; } } -} +} \ No newline at end of file diff --git a/packages/biz/src/WarningRecordCard/index.md b/packages/biz/src/WarningRecordCard/index.md index 3d286b3..bb9e648 100644 --- a/packages/biz/src/WarningRecordCard/index.md +++ b/packages/biz/src/WarningRecordCard/index.md @@ -20,12 +20,11 @@ title: WarningRecordCard 预警记录卡片 | id | 数据的唯一id 用于key 传值| string | - | - | | warningType | 预警类型 | string | - | - | | warningInfo | 盒子 点位 柜子 等信息 | string[] | - | - | -| cabietInfo | 右侧 柜子信息 | string | - | - | +| cabietText | 右侧 柜子信息 | string | - | - | | warningTime | 预警时间 格式化后的时间字符串 | string | - | - | -| warningTimestamp | 预警时间戳 | string \| nmbuer | - | - | +| warningTimestamp | 预警时间戳 | string \| number | - | - | | warningTimeFormat | 预警时间格式 | string | YYYY-MM-DD HH:mm:ss | - | - diff --git a/packages/biz/src/boxSelectTree/components/boxPanel/index.tsx b/packages/biz/src/boxSelectTree/components/boxPanel/index.tsx index a8270e5..bf62d74 100644 --- a/packages/biz/src/boxSelectTree/components/boxPanel/index.tsx +++ b/packages/biz/src/boxSelectTree/components/boxPanel/index.tsx @@ -6,6 +6,7 @@ import type { TreeProps, InputProps } from 'antd'; import type { BoxTreeProps } from '../../../tree'; import TreeTransferModal from '../../../treeTransferModal' import BoxTree from '../../../tree'; +// import './index.less' export interface BoxPanelProps { searchInputProps?: InputProps diff --git a/packages/biz/src/index.tsx b/packages/biz/src/index.tsx index 40f9252..88f3a1c 100644 --- a/packages/biz/src/index.tsx +++ b/packages/biz/src/index.tsx @@ -10,3 +10,10 @@ export { default as TreeTransferModal } from './treeTransferModal' export type { TreeTransferModalProps } from './treeTransferModal' export { default as WarningRecordCard } from './WarningRecordCard' export type { WarningRecordCardProps, IRecord } from './WarningRecordCard' +export type { IRecord, WarningRecordCardProps } from './WarningRecordCard' +export type { ViewLargerImageModalRef, ViewLargerImageModalProps } from './ViewLargerImageModal' +export { default as ViewLargerImageModal, useViewLargerImageModal } from './ViewLargerImageModal' +export type { VideoPlayerCardProps } from './VideoPlayerCard' +export { default as VideoPlayerCard } from './VideoPlayerCard' +export { default as RealTimeMonitor } from './RealTimeMonitor' +