feat: 人物轨迹图表功能

master
JINGYJ 11 months ago
parent 4511641865
commit d8d4f42230

@ -53,6 +53,8 @@
"antd": "^5.13.2",
"antd-style": "^3.6.1",
"classnames": "^2.5.1",
"echarts": "^5.4.3",
"echarts-for-react": "^3.0.2",
"omit.js": "^2.0.2",
"querystring": "^0.2.1",
"rc-menu": "^9.12.4",

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0H0L16 16V4C16 1.79086 14.2091 0 12 0Z" fill="#E80D0D"/>
<path d="M12 0H0L16 16V4C16 1.79086 14.2091 0 12 0Z" fill="#E64D1F"/>
</svg>

Before

Width:  |  Height:  |  Size: 173 B

After

Width:  |  Height:  |  Size: 173 B

@ -239,7 +239,7 @@ const MenuBar: React.FC<MenuBarProps> = ({ menuData }) => {
marginLeft: 8,
width: 10,
height: 10,
background: '#E80D0D',
background: '#E54D1F',
borderRadius: '50%',
}}
></span>
@ -251,7 +251,7 @@ const MenuBar: React.FC<MenuBarProps> = ({ menuData }) => {
marginLeft: 8,
width: 10,
height: 10,
background: '#E80D0D',
background: '#E54D1F',
borderRadius: '50%',
}}
/>

@ -1,4 +1,3 @@
import { outLogin } from '@/services/ant-design-pro/api';
import { LogoutOutlined } from '@ant-design/icons';
import { history, useModel } from '@umijs/max';
import { Spin } from 'antd';
@ -43,7 +42,7 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ children }) =
* 退 url
*/
const loginOut = async () => {
await outLogin();
// await outLogin();
const { search, pathname } = window.location;
const urlParams = new URL(window.location.href).searchParams;
/** 此方法会跳转到 redirect 参数所在的位置 */

@ -149,8 +149,8 @@ const InvolvedUploadList: React.FC = () => {
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: 16,
height: 256,
paddingTop: 16,
height: 272,
background: '#FAFCFF',
// backgroundImage: 'url(../../../../public/images/involved/involved_jiaobiao.png)',
}}
@ -175,13 +175,18 @@ const InvolvedUploadList: React.FC = () => {
></ImageWithPopover>
<div
style={{
marginTop: 12,
height: 110,
marginTop: 16,
padding: '12px 16px',
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
color: '#666',
fontSize: 12,
alignItems: 'center',
background: '#fff',
color: '#999',
fontSize: 13,
boxShadow: '0px -1px 0px 0px rgba(0,79,178,0.1)',
}}
>
{/* <div>
@ -194,7 +199,14 @@ const InvolvedUploadList: React.FC = () => {
{moment(record.appear_time).format('YYYY-MM-DD hh:mm:ss')}
</span>
</div> */}
<div>
<div
style={{
width: '185px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
:{' '}
<span
style={{
@ -221,7 +233,14 @@ const InvolvedUploadList: React.FC = () => {
{record.video_name_first}
</span>
</div>
<div>
<div
style={{
width: '185px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
:{' '}
<span
style={{
@ -422,6 +441,7 @@ const InvolvedUploadList: React.FC = () => {
<ProList<any>
className="gn"
ghost={true}
style={{ height: 'calc(100vh - 200px)', overflowY: 'scroll', overflowX: 'hidden' }}
itemCardProps={{
ghost: true,
bodyStyle: { padding: 0, margin: 0 },

@ -136,6 +136,16 @@ const OfflineAlarmList: React.FC = () => {
setCurrentRow(record);
}}
>
{/* <Image
width={140}
height={140}
style={{
borderRadius: 4,
}}
preview={false}
src={record.picture_path[0]}
/> */}
<div style={{ position: 'relative' }}>
<Image
width={140}
height={140}
@ -145,6 +155,28 @@ const OfflineAlarmList: React.FC = () => {
preview={false}
src={record.picture_path[0]}
/>
{record.person_number > 1 && (
<span
style={{
display: 'inline-block',
position: 'absolute',
top: '0px',
right: '0px',
width: '64px',
height: '28px',
background: 'rgba(0,0,0,0.6)',
borderRadius: '0px 4px 0px 4px',
color: '#FAAD14',
fontWeight: 'bold',
textAlign: 'center',
fontSize: 12,
lineHeight: '28px',
}}
>
{record.person_number}
</span>
)}
</div>
<div
style={{
paddingTop: 12,
@ -324,7 +356,7 @@ const OfflineAlarmList: React.FC = () => {
right: '5px',
width: '8px',
height: '8px',
background: '#E80D0D',
background: '#E54D1F',
borderRadius: '50%',
}}
/>
@ -445,6 +477,7 @@ const OfflineAlarmList: React.FC = () => {
<ProList<any>
className="gn"
ghost={true}
style={{ height: 'calc(100vh - 240px)', overflowY: 'scroll', overflowX: 'hidden' }}
itemCardProps={{
ghost: true,
bodyStyle: { padding: 0, margin: 0 },

@ -96,7 +96,7 @@ const AlarmList: React.FC = () => {
marginLeft: 5,
width: '10px',
height: '10px',
background: '#E80D0D',
background: '#E54D1F',
borderRadius: '50%',
}}
/>
@ -178,6 +178,16 @@ const AlarmList: React.FC = () => {
setCurrentRow(record);
}}
>
{/* <Image
width={140}
height={140}
style={{
borderRadius: 4,
}}
preview={false}
src={record.picture_path[0]}
/> */}
<div style={{ position: 'relative' }}>
<Image
width={140}
height={140}
@ -187,6 +197,28 @@ const AlarmList: React.FC = () => {
preview={false}
src={record.picture_path[0]}
/>
{record.person_number > 1 && (
<span
style={{
display: 'inline-block',
position: 'absolute',
top: '0px',
right: '0px',
width: '64px',
height: '28px',
background: 'rgba(0,0,0,0.6)',
borderRadius: '0px 4px 0px 4px',
color: '#FAAD14',
fontWeight: 'bold',
textAlign: 'center',
fontSize: 12,
lineHeight: '28px',
}}
>
{record.person_number}
</span>
)}
</div>
<div
style={{
paddingTop: 12,
@ -390,7 +422,7 @@ const AlarmList: React.FC = () => {
right: '5px',
width: '8px',
height: '8px',
background: '#E80D0D',
background: '#E54D1F',
borderRadius: '50%',
}}
/>
@ -481,6 +513,7 @@ const AlarmList: React.FC = () => {
<ProList<any>
className="gn"
ghost={true}
style={{ height: 'calc(100vh - 240px)', overflowY: 'scroll', overflowX: 'hidden' }}
itemCardProps={{
ghost: true,
bodyStyle: { padding: 0, margin: 0 },

@ -1,13 +1,15 @@
import { getInvolvedTravelList } from '@/services/realTime/involved';
import { getInvolvedLastSevenDays, getInvolvedTravelList } from '@/services/realTime/involved';
// import { CloseCircleOutlined } from '@ant-design/icons';
import { ModalForm } from '@ant-design/pro-components';
// import { useIntl } from '@umijs/max';
import { InfoCircleFilled } from '@ant-design/icons';
import { Form, Image, List } from 'antd';
import moment from 'moment';
import VirtualList from 'rc-virtual-list';
import React, { useEffect, useState } from 'react';
import './InvolvedDetails.less';
import styles from './InvolvedDetails.less';
import LineChart from './LineChart';
export type FormValueType = {
target?: string;
template?: string;
@ -32,7 +34,8 @@ const InvolvedDetails: React.FC<UpdateFormProps> = (props) => {
const [currentPageSize, setCurrentPageSize] = useState<number>(10);
const [currentPage, setCurrentPage] = useState<number>(1);
const [total, setTotal] = useState<number>(0);
const ContainerHeight = 630;
const [dataStatistics, setDataStatistics] = useState([]);
const ContainerHeight = 530;
// const content = (
// <div
// style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}
@ -67,7 +70,23 @@ const InvolvedDetails: React.FC<UpdateFormProps> = (props) => {
// const handleOpenChange = (value: boolean) => {
// setOpen(value);
// };
const getLastSevenDays = () => {
const reqParams = {
person_id: props.values.person_id,
// ...rest,
};
getInvolvedLastSevenDays(reqParams)
.then((res) => {
console.log(res);
if (res.data) {
setDataStatistics(res.data);
}
})
.catch(() => {
// setLoading(false);
// setTrajectoryData([]); // 重置trajectoryData数据为空数组
});
};
const appendData = (page: any, pageSize: any) => {
const reqParams = {
page: page,
@ -115,11 +134,13 @@ const InvolvedDetails: React.FC<UpdateFormProps> = (props) => {
setCurrentPage(1);
setCurrentPageSize(10);
appendData(currentPage, currentPageSize);
getLastSevenDays();
setDataStatistics([]);
}
}, [props.updateModalOpen]);
return (
<ModalForm<any>
width={563}
width={640}
// title={
// <div
// style={{
@ -274,6 +295,59 @@ const InvolvedDetails: React.FC<UpdateFormProps> = (props) => {
color: '#333',
}}
>
</div>
<div style={{ width: '100%', height: 287 }}>
<div
style={{
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
marginBottom: 15,
fontWeight: 'bold',
fontSize: 14,
height: 22,
color: '#333',
}}
>
<span
style={{
marginRight: 6,
width: 2,
height: 14,
backgroundColor: '#154DDD',
}}
></span>
<span></span>
<div style={{ color: '#FAAD14', fontWeight: 'normal', marginLeft: 8 }}>
<InfoCircleFilled />
<span style={{ marginLeft: 4 }}></span>
</div>
</div>
<div style={{ width: '100%', height: 250 }}>
<LineChart dataStatistics={dataStatistics}></LineChart>
</div>
</div>
<div
style={{
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
marginBottom: 12,
fontWeight: 'bold',
fontSize: 14,
height: 22,
color: '#333',
}}
>
<span
style={{
marginRight: 6,
width: 2,
height: 14,
backgroundColor: '#154DDD',
}}
></span>
</div>
<List>

@ -0,0 +1,101 @@
import ReactECharts from 'echarts-for-react';
import React, { useEffect, useRef } from 'react';
interface LineChartProps {
dataStatistics: any;
}
const LineChart: React.FC<LineChartProps> = ({ dataStatistics }) => {
const chartRef = useRef(null);
const seriesData: any[] = Array.isArray(dataStatistics.yAxis)
? dataStatistics.yAxis.map((item: any) => {
return {
type: 'line',
name: item.device_name,
symbol: 'circle',
smooth: false,
data: item.value,
};
})
: [];
const option = {
grid: { top: 30, right: 124, bottom: 24, left: 28 },
xAxis: {
type: 'category',
name: '日期',
nameTextStyle: {
// padding: [-100, 10, 10, -30], // 修改单位位置
verticalAlign: 'top',
padding: [7, 0, 0, -15],
color: '#666',
fontSize: 12,
fontWeight: 400,
},
axisLabel: {
interval: 0,
show: true,
fontSize: 12,
color: '#8C8C8C',
},
axisLine: {
show: true,
lineStyle: {
show: true,
color: '#E0E0E0',
},
},
axisTick: {
show: false,
},
data: dataStatistics.xAxis,
},
yAxis: {
type: 'value',
name: '7日出现次数',
nameTextStyle: {
padding: [0, 0, 0, 14], // 修改单位位置
color: '#666',
fontSize: 12,
fontWeight: 400,
},
minInterval: 1,
splitLine: {
lineStyle: {
color: '#E0E0E0',
type: 'dashed',
},
},
},
series: seriesData,
legend: {
show: true,
type: 'scroll',
orient: 'vertical',
top: 10,
right: 30,
icon: 'rect',
// itemWidth: 16,
itemHeight: 4,
// itemGap: 30,
textStyle: {
fontSize: 12,
color: '#333',
padding: [0, 0, 0, 4],
},
},
tooltip: {
show: false,
trigger: 'axis',
},
};
useEffect(() => {
console.log(chartRef.current);
const chartInstance = chartRef.current?.getEchartsInstance();
if (chartInstance) {
// 在这里调用 resize(),但通常你可能不需要这样做,除非有特殊情况
chartInstance.resize();
}
}, []);
return <ReactECharts ref={chartRef} style={{ width: '100%', height: '100%' }} option={option} />;
};
export default LineChart;

@ -94,7 +94,7 @@ const InvolvedList: React.FC = () => {
marginLeft: 5,
width: '10px',
height: '10px',
background: '#E80D0D',
background: '#E54D1F',
borderRadius: '50%',
}}
/>
@ -177,8 +177,8 @@ const InvolvedList: React.FC = () => {
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: 16,
height: 256,
paddingTop: 16,
height: 272,
background: '#FAFCFF',
// backgroundImage: 'url(../../../../public/images/involved/involved_jiaobiao.png)',
}}
@ -203,16 +203,28 @@ const InvolvedList: React.FC = () => {
></ImageWithPopover>
<div
style={{
marginTop: 12,
height: 110,
marginTop: 16,
padding: '12px 16px',
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
color: '#666',
fontSize: 12,
alignItems: 'center',
background: '#fff',
color: '#999',
fontSize: 13,
boxShadow: '0px -1px 0px 0px rgba(0,79,178,0.1)',
}}
>
<div
style={{
width: '185px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
<div>
:{' '}
<span
style={{
@ -239,7 +251,14 @@ const InvolvedList: React.FC = () => {
{record.device_name_first}
</span>
</div>
<div>
<div
style={{
width: '185px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
:{' '}
<span
style={{
@ -462,6 +481,7 @@ const InvolvedList: React.FC = () => {
<ProList<any>
className="gn"
ghost={true}
style={{ height: 'calc(100vh - 200px)', overflowY: 'scroll', overflowX: 'hidden' }}
itemCardProps={{
ghost: true,
bodyStyle: { padding: 0, margin: 0 },

@ -114,3 +114,20 @@ export async function postUploadRecognition(
},
);
}
/** 数据统计列表 */
export async function getInvolvedLastSevenDays(
params: API.SearchInvolvedLastSevenDays,
options?: { [key: string]: any },
) {
return request<API.Response & { data?: API.PageResult; msg?: string }>(`/api/last_seven_days/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
params: {
...params,
},
...(options || {}),
});
}

@ -181,4 +181,8 @@ declare namespace API {
/** 时间 */
start_time?: string;
};
type SearchInvolvedLastSevenDays = {
/** 人员ID */
person_id?: string;
};
}

Loading…
Cancel
Save