/*
|
* @Company: hugeInfo
|
* @Author: AI
|
* @Date: 2025-04-17 16:30:00
|
* @LastEditTime: 2025-04-23 16:41:01
|
* @LastEditors: lwh
|
* @Version: 1.0.0
|
* @Description: 工作统计
|
*/
|
import React, { useEffect, useState } from 'react';
|
import { Drawer, Tabs, Button, Select, Modal, Picker, List } from 'dingtalk-design-mobile';
|
import { ArrowOutlined, SettingOutlined, CloseOutlined } from 'dd-icons';
|
import * as $$ from '../../utils/utility';
|
import caseTypeSelect from '../../utils/caseTypeSelect';
|
import NavBarPage from '../../components/NavBarPage';
|
import MyChartBar from './MyChartBar';
|
import MyLTopChartPie from './MyLTopChartPie';
|
import CalendarRangeTwoDay from '../../components/CalendarRangeTwoDay';
|
import { workStatistics_3, workStatistics_4, workStatistics_5,applyClose_1,caseQuery_2 } from '../../assets/img';
|
import './areaIndex.less';
|
import WorkStatisticsTabs from '../../components/WorkStatisticsTabs';
|
import { useHistory, useLocation } from 'react-router-dom';
|
|
// 获取工作统计数据
|
function getWorkStatisticsApi(data) {
|
return $$.ax.request({ url: 'caseInfo/statistics', type: 'get', data, service: 'mediate' });
|
}
|
|
// 获取超时办件数量
|
function getTimeoutTaskApi(data) {
|
return $$.ax.request({ url: 'analysis/timeoutTask', type: 'get', data, service: 'mediate' });
|
}
|
|
// 获取超时办件列表
|
function getTimeoutListApi(data) {
|
return $$.ax.request({ url: 'analysis/pageQuantity', type: 'get', data, service: 'mediate' });
|
}
|
|
const AreaStatistics = () => {
|
const history = useHistory();
|
const location = useLocation();
|
const [currentTab, setCurrentTab] = useState('区域工作统计');
|
|
// 事项来源选项
|
const canalOptions = [
|
{ value: '22_00001-1', label: '大厅来访' },
|
{ value: '22_00001-2', label: '线上来访' },
|
{ value: '22_00001-3', label: '自行排查' },
|
{ value: '22_00001-4', label: '协同推送' }
|
];
|
|
// 事项等级选项
|
const caseLevelOptions = [
|
{ value: '1', label: '一级' },
|
{ value: '2', label: '二级' },
|
{ value: '3', label: '三级' }
|
];
|
|
// 新增:时间类型选项
|
const dateTypeOptions = [
|
{ value: 'custom', label: '自定义' },
|
{ value: 'lastMonth', label: '上月' },
|
{ value: 'lastWeek', label: '上周' },
|
{ value: 'thisMonth', label: '本月' },
|
{ value: 'thisWeek', label: '本周' },
|
{ value: 'thisYear', label: '本年度' },
|
{ value: 'lastYear', label: '上年度' }
|
];
|
|
const [loading, setLoading] = useState(false);
|
const [settingVisible, setSettingVisible] = useState(false);
|
const [datePickerVisible, setDatePickerVisible] = useState(false);
|
const [currentDateType, setCurrentDateType] = useState('');
|
const [activeStatTab, setActiveStatTab] = useState('total');
|
|
// 超时办件状态
|
const [timeoutData, setTimeoutData] = useState({
|
fpcs: 0, // 分派超时数量
|
slcs: 0, // 受理超时数量
|
dbcs: 0 // 督办回复超时数量
|
});
|
const [activeTimeoutTab, setActiveTimeoutTab] = useState(1); // 1-分派超时,2-受理超时,3-督办回复超时
|
const [activeSuperviseTab, setActiveSuperviseTab] = useState(0); // 0-未督办/未回复,1-已督办/已回复
|
const [timeoutList, setTimeoutList] = useState([]); // 超时办件列表
|
const [timeoutPagination, setTimeoutPagination] = useState({
|
page: 1,
|
size: 5,
|
total: 0,
|
totalPages: 0
|
});
|
|
// 查询参数
|
const [queryParams, setQueryParams] = useState({
|
createStart: $$.myTimeFormat(new Date(new Date().getFullYear(), new Date().getMonth(), 1), 'YYYY-MM-DD'),
|
createEnd: $$.myTimeFormat(new Date(), 'YYYY-MM-DD'),
|
closeStart: '',
|
closeEnd: '',
|
canal: '',
|
caseLevel: '',
|
caseType: ''
|
});
|
|
// 统计数据
|
const [statistics, setStatistics] = useState({
|
totalNum: 0,
|
processNum: 0,
|
finishNum: 0,
|
rejectNum: 0,
|
resolveNum: 0,
|
resolveRate: '0',
|
unResolveNum: 0,
|
toDayTotalNum: 0,
|
toDayProcessNum: 0,
|
toDayRejectNum: 0,
|
toDayFinishNum: 0,
|
momTotalRate: '0',
|
yoyTotalRate: '0',
|
momProcessRate: '0',
|
yoyProcessRate: '0',
|
momRejectRate: '0',
|
yoyRejectRate: '0',
|
momFinishNumRate: '0',
|
yoyFinishNumRate: '0',
|
momResolveRate: '0',
|
yoyResolveRate: '0',
|
typeList: [],
|
timeList: [],
|
oneLevelNum: 0, // 一级事项数量
|
twoLevelNum: 0, // 二级事项数量
|
threeLevelNum: 0, // 三级事项数量
|
areaList: []
|
});
|
|
const [dateTypeModalVisible, setDateTypeModalVisible] = useState(false);
|
const [dateTypeTarget, setDateTypeTarget] = useState(''); // 'create' or 'close'
|
|
// 计算时间范围
|
const getDateRangeByType = (type) => {
|
const now = new Date();
|
let start, end;
|
switch (type) {
|
case 'lastMonth': {
|
const year = now.getMonth() === 0 ? now.getFullYear() - 1 : now.getFullYear();
|
const month = now.getMonth() === 0 ? 11 : now.getMonth() - 1;
|
start = new Date(year, month, 1);
|
end = new Date(year, month + 1, 0);
|
break;
|
}
|
case 'thisMonth': {
|
start = new Date(now.getFullYear(), now.getMonth(), 1);
|
end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
break;
|
}
|
case 'lastWeek': {
|
const day = now.getDay() || 7;
|
end = new Date(now.getFullYear(), now.getMonth(), now.getDate() - day);
|
start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - day - 6);
|
break;
|
}
|
case 'thisWeek': {
|
const day = now.getDay() || 7;
|
start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - day + 1);
|
end = new Date(now.getFullYear(), now.getMonth(), now.getDate() - day + 7);
|
break;
|
}
|
case 'thisYear': {
|
start = new Date(now.getFullYear(), 0, 1);
|
end = new Date(now.getFullYear(), 11, 31);
|
break;
|
}
|
case 'lastYear': {
|
start = new Date(now.getFullYear() - 1, 0, 1);
|
end = new Date(now.getFullYear() - 1, 11, 31);
|
break;
|
}
|
default:
|
start = end = now;
|
}
|
return [$$.myTimeFormat(start, 'YYYY-MM-DD'), $$.myTimeFormat(end, 'YYYY-MM-DD')];
|
};
|
|
// 检查值是否以特定字符开头,防止null错误
|
const startsWithSafe = (value, char) => {
|
return value && typeof value === 'string' && value.startsWith(char);
|
};
|
|
// 初始化加载数据
|
useEffect(() => {
|
fetchStatisticsData();
|
fetchTimeoutData();
|
}, []);
|
|
// 监听路由变化
|
useEffect(() => {
|
const path = window.location.pathname;
|
if (path === '/gzdyh/workStatistics') {
|
setCurrentTab('个人工作统计');
|
} else if (path === '/gzdyh/areaStatistics') {
|
setCurrentTab('区域工作统计');
|
}
|
}, [window.location.pathname]);
|
|
// 获取统计数据
|
const fetchStatisticsData = async () => {
|
global.setSpinning(true);
|
setLoading(true);
|
try {
|
const res = await getWorkStatisticsApi({
|
...queryParams,
|
workType: 3
|
});
|
if (res.type && res.data) {
|
// 确保所有需要的字段都存在,避免null错误
|
const data = {
|
...statistics,
|
...res.data,
|
// 确保这些字段不为null
|
momTotalRate: res.data.momTotalRate || '0',
|
yoyTotalRate: res.data.yoyTotalRate || '0',
|
momProcessRate: res.data.momProcessRate || '0',
|
yoyProcessRate: res.data.yoyProcessRate || '0',
|
momRejectRate: res.data.momRejectRate || '0',
|
yoyRejectRate: res.data.yoyRejectRate || '0',
|
momFinishNumRate: res.data.momFinishNumRate || '0',
|
yoyFinishNumRate: res.data.yoyFinishNumRate || '0',
|
momResolveRate: res.data.momResolveRate || '0',
|
yoyResolveRate: res.data.yoyResolveRate || '0',
|
resolveRate: res.data.resolveRate || '0',
|
oneLevelNum: res.data.oneLevelNum || 0, // 确保一级事项数量存在
|
twoLevelNum: res.data.twoLevelNum || 0, // 确保二级事项数量存在
|
threeLevelNum: res.data.threeLevelNum || 0, // 确保三级事项数量存在
|
timeList: Array.isArray(res.data.timeList) ? res.data.timeList : [],
|
typeList: Array.isArray(res.data.typeList) ? res.data.typeList : [],
|
areaList: Array.isArray(res.data.areaList) ? res.data.areaList : []
|
};
|
setStatistics(data);
|
}
|
} catch (error) {
|
console.error('获取工作统计数据失败', error);
|
} finally {
|
setLoading(false);
|
global.setSpinning(false);
|
}
|
};
|
|
// 获取超时办件数量
|
const fetchTimeoutData = async () => {
|
try {
|
const res = await getTimeoutTaskApi(queryParams);
|
if (res.type && res.data) {
|
setTimeoutData({
|
fpcs: res.data.fpcs || 0,
|
slcs: res.data.slcs || 0,
|
dbcs: res.data.dbcs || 0
|
});
|
// 初始化加载分派超时的未督办列表
|
fetchTimeoutList(1, 0, 1);
|
}
|
} catch (error) {
|
console.error('获取超时办件数量失败', error);
|
}
|
};
|
|
// 获取超时办件列表
|
const fetchTimeoutList = async (queryType, turnaroundType, page = 1) => {
|
try {
|
setLoading(true);
|
const params = {
|
...queryParams,
|
queryType,
|
turnaroundType,
|
page,
|
size: timeoutPagination.size
|
};
|
|
const res = await getTimeoutListApi(params);
|
if (res.type && res.data) {
|
// 设置列表数据
|
setTimeoutList(res.data.content || []);
|
// 设置分页信息
|
setTimeoutPagination({
|
...timeoutPagination,
|
page,
|
total: res.data.totalElements || 0,
|
totalPages: res.data.totalPages || 0
|
});
|
}
|
} catch (error) {
|
console.error('获取超时办件列表失败', error);
|
} finally {
|
setLoading(false);
|
}
|
};
|
|
// 打开设置抽屉
|
const handleOpenSetting = () => {
|
setSettingVisible(true);
|
};
|
|
// 关闭设置抽屉
|
const handleCloseSetting = () => {
|
setSettingVisible(false);
|
};
|
|
// 重置筛选条件
|
const handleResetFilter = () => {
|
setQueryParams({
|
createStart: $$.myTimeFormat(new Date(new Date().getFullYear(), new Date().getMonth(), 1), 'YYYY-MM-DD'),
|
createEnd: $$.myTimeFormat(new Date(), 'YYYY-MM-DD'),
|
closeStart: '',
|
closeEnd: '',
|
canal: '',
|
caseLevel: '',
|
caseType: ''
|
});
|
};
|
|
// 应用筛选条件
|
const handleApplyFilter = () => {
|
fetchStatisticsData();
|
fetchTimeoutData(); // 更新超时办件数据
|
setSettingVisible(false);
|
};
|
|
// 处理日期选择
|
const handleOpenDatePicker = (type) => {
|
setCurrentDateType(type);
|
setDatePickerVisible(true);
|
};
|
|
// 处理日期变更
|
const handleDateChange = (dates) => {
|
if (!dates || dates.length !== 2) return;
|
|
const formatDate = (date) => {
|
if (!date || !date.key) return '';
|
return date.key;
|
};
|
|
const formattedStart = formatDate(dates[0]);
|
const formattedEnd = formatDate(dates[1]);
|
|
switch (currentDateType) {
|
case 'create':
|
setQueryParams(prev => ({
|
...prev,
|
createStart: formattedStart,
|
createEnd: formattedEnd
|
}));
|
break;
|
case 'close':
|
setQueryParams(prev => ({
|
...prev,
|
closeStart: formattedStart,
|
closeEnd: formattedEnd
|
}));
|
break;
|
default:
|
break;
|
}
|
setDatePickerVisible(false);
|
};
|
|
// 日历组件操作处理
|
const handleCalendarClick = (type, action) => {
|
if (action === 'onClose') {
|
setDatePickerVisible(false);
|
} else if (action === 'submit') {
|
setDatePickerVisible(false);
|
}
|
};
|
|
// 获取统计时间类型显示文本(当月、当年、当日)
|
const getTimeTypeText = () => {
|
if (!queryParams.createStart || !queryParams.createEnd) return '当日';
|
|
const startDate = new Date(queryParams.createStart);
|
const endDate = new Date(queryParams.createEnd);
|
|
// 检查是否为整月
|
if (
|
startDate.getDate() === 1 &&
|
endDate.getDate() === new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0).getDate() &&
|
startDate.getMonth() === endDate.getMonth() &&
|
startDate.getFullYear() === endDate.getFullYear()
|
) {
|
return '当月';
|
}
|
|
// 检查是否为整年
|
if (
|
startDate.getMonth() === 0 &&
|
startDate.getDate() === 1 &&
|
endDate.getMonth() === 11 &&
|
endDate.getDate() === 31 &&
|
startDate.getFullYear() === endDate.getFullYear()
|
) {
|
return '当年';
|
}
|
|
return '当日';
|
};
|
|
// 判断是否为整月或整年
|
const isFullMonthOrYear = () => {
|
if (!queryParams.createStart || !queryParams.createEnd) return false;
|
|
const startDate = new Date(queryParams.createStart);
|
const endDate = new Date(queryParams.createEnd);
|
|
// 检查是否为整月
|
const isFullMonth =
|
startDate.getDate() === 1 &&
|
endDate.getDate() === new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0).getDate() &&
|
startDate.getMonth() === endDate.getMonth() &&
|
startDate.getFullYear() === endDate.getFullYear();
|
|
// 检查是否为整年
|
const isFullYear =
|
startDate.getMonth() === 0 &&
|
startDate.getDate() === 1 &&
|
endDate.getMonth() === 11 &&
|
endDate.getDate() === 31 &&
|
startDate.getFullYear() === endDate.getFullYear();
|
|
return isFullMonth || isFullYear;
|
};
|
|
// 判断是否为整年
|
const isFullYear = () => {
|
if (!queryParams.createStart || !queryParams.createEnd) return false;
|
|
const startDate = new Date(queryParams.createStart);
|
const endDate = new Date(queryParams.createEnd);
|
|
return (
|
startDate.getMonth() === 0 &&
|
startDate.getDate() === 1 &&
|
endDate.getMonth() === 11 &&
|
endDate.getDate() === 31 &&
|
startDate.getFullYear() === endDate.getFullYear()
|
);
|
};
|
|
// 获取对比文本(月同比/年同比)
|
const getCompareText = () => {
|
return isFullYear() ? '年同比' : '月同比';
|
};
|
|
// 获取环比文本(月环比/年环比)
|
const getRatioText = () => {
|
return isFullYear() ? '年环比' : '月环比';
|
};
|
|
// 获取事项来源文本
|
const getCanalText = () => {
|
if (!queryParams.canal) return '全部';
|
const option = canalOptions.find(item => item.value === queryParams.canal);
|
return option ? option.label : '全部';
|
};
|
|
// 获取筛选条件显示信息
|
const getFilterInfoText = () => {
|
const info = [];
|
|
// 添加登记时间
|
if (queryParams.createStart && queryParams.createEnd) {
|
info.push(`登记时间:${queryParams.createStart.replace(/-/g, '.')}–${queryParams.createEnd.replace(/-/g, '.')}`);
|
}
|
|
// 添加事项来源
|
info.push(`事项来源:${getCanalText()}`);
|
|
return info;
|
};
|
|
// 清除单个筛选条件
|
const clearFilterItem = (type) => {
|
switch (type) {
|
case 'create':
|
setQueryParams(prev => ({
|
...prev,
|
createStart: $$.myTimeFormat(new Date(new Date().getFullYear(), new Date().getMonth(), 1), 'YYYY-MM-DD'),
|
createEnd: $$.myTimeFormat(new Date(), 'YYYY-MM-DD')
|
}));
|
break;
|
case 'close':
|
setQueryParams(prev => ({
|
...prev,
|
closeStart: '',
|
closeEnd: ''
|
}));
|
break;
|
case 'canal':
|
setQueryParams(prev => ({
|
...prev,
|
canal: ''
|
}));
|
break;
|
case 'caseLevel':
|
setQueryParams(prev => ({
|
...prev,
|
caseLevel: ''
|
}));
|
break;
|
case 'caseType':
|
setQueryParams(prev => ({
|
...prev,
|
caseType: ''
|
}));
|
break;
|
case 'area':
|
setQueryParams(prev => ({
|
...prev,
|
queCity: '',
|
queArea: '',
|
queRoad: '',
|
queVillage: ''
|
}));
|
break;
|
default:
|
break;
|
}
|
|
// 清除后立即更新数据
|
setTimeout(() => {
|
fetchStatisticsData();
|
}, 0);
|
};
|
|
// 渲染同比环比的图标和数据
|
const renderRateWithIcon = (rate) => {
|
if (!rate) return '0%';
|
|
// 去掉+/-符号
|
const rateValue = rate.replace(/[+\-]/g, '');
|
const isPositive = !startsWithSafe(rate, '-');
|
|
return (
|
<span className={isPositive ? 'up' : 'down'}>
|
<img src={isPositive ? workStatistics_3 : workStatistics_4} alt="" className="rate-icon" />
|
{rateValue}%
|
</span>
|
);
|
};
|
|
// 渲染化解统计图表
|
const renderChartByTab = () => {
|
// 确保timeList是数组
|
const areaList = Array.isArray(statistics.areaList) ? statistics.areaList : [];
|
|
switch (activeStatTab) {
|
case 'total':
|
return (
|
<MyChartBar
|
data={areaList.map(item => ({
|
caseNum: item.caseNum || 0,
|
name: item.areaName || ''
|
}))}
|
dataAxis={areaList.map(item => item.areaName || '')}
|
barColor={['#5fa6d4', '#1a6fb8']}
|
/>
|
);
|
case 'processing':
|
return (
|
<MyChartBar
|
data={areaList.map(item => ({
|
caseNum: item.resolveingNum || 0,
|
name: item.areaName || ''
|
}))}
|
dataAxis={areaList.map(item => item.areaName || '')}
|
barColor={['#5fa6d4', '#1a6fb8']}
|
/>
|
);
|
case 'finished':
|
return (
|
<MyChartBar
|
data={areaList.map(item => ({
|
resolveNum: item.resolveNum || 0,
|
unResolveNum: item.unResolveNum || 0,
|
name: item.areaName || ''
|
}))}
|
dataAxis={areaList.map(item => item.areaName || '')}
|
barColor={['#08979C', '#F2657D']}
|
isStacked={true}
|
/>
|
);
|
case 'rejected':
|
return (
|
<MyChartBar
|
data={areaList.map(item => ({
|
caseNum: item.rejectNum || 0,
|
name: item.areaName || ''
|
}))}
|
dataAxis={areaList.map(item => item.areaName || '')}
|
barColor={['#5fa6d4', '#1a6fb8']}
|
/>
|
);
|
default:
|
return null;
|
}
|
};
|
|
// 处理超时类型切换
|
const handleTimeoutTabChange = (type) => {
|
setActiveTimeoutTab(type);
|
setActiveSuperviseTab(0); // 重置为未督办/未回复
|
setTimeoutPagination({...timeoutPagination, page: 1}); // 重置分页
|
fetchTimeoutList(type, 0, 1);
|
};
|
|
// 处理督办/回复状态切换
|
const handleSuperviseTabChange = (type) => {
|
setActiveSuperviseTab(type);
|
setTimeoutPagination({...timeoutPagination, page: 1}); // 重置分页
|
fetchTimeoutList(activeTimeoutTab, type, 1);
|
};
|
|
// 处理页码变更
|
const handlePageChange = (newPage) => {
|
fetchTimeoutList(activeTimeoutTab, activeSuperviseTab, newPage);
|
};
|
|
// 处理详情跳转
|
const handleDetailClick = (caseTaskId, caseId) => {
|
window.location.href = `#/gzdyh/flow?caseTaskId=${caseTaskId}&caseId=${caseId}&editShow=false`;
|
};
|
|
// 格式化超时时间
|
const formatTimeLimit = (hours) => {
|
if (!hours && hours !== 0) return '';
|
|
const days = Math.floor(hours / 24);
|
const remainHours = hours % 24;
|
|
if (days > 0 && remainHours > 0) {
|
return `超${days}天${remainHours}小时`;
|
} else if (days > 0) {
|
return `超${days}天`;
|
} else {
|
return `超${remainHours}小时`;
|
}
|
};
|
|
// 打开时间类型弹窗
|
const handleOpenDateTypeModal = (target) => {
|
setDateTypeTarget(target);
|
setDateTypeModalVisible(true);
|
};
|
|
// 选择时间类型
|
const handleDateTypeSelect = (type) => {
|
setDateTypeModalVisible(false);
|
if (type === 'custom') {
|
handleOpenDatePicker(dateTypeTarget);
|
} else {
|
const [start, end] = getDateRangeByType(type);
|
if (dateTypeTarget === 'create') {
|
setQueryParams(prev => ({ ...prev, createStart: start, createEnd: end }));
|
} else if (dateTypeTarget === 'close') {
|
setQueryParams(prev => ({ ...prev, closeStart: start, closeEnd: end }));
|
}
|
}
|
};
|
|
// 在组件内增加一个方法用于获取label
|
const getCaseTypeLabel = (value) => {
|
if (!value) return '';
|
let label = '';
|
caseTypeSelect.caseTypeSelect.forEach(item => {
|
if (item.value === value) {
|
label = item.label;
|
} else if (item.children) {
|
const child = item.children.find(child => child.value === value);
|
if (child) label = child.label;
|
}
|
});
|
return label;
|
};
|
|
return (
|
<NavBarPage
|
title={currentTab}
|
leftContentFunc={() => { history.replace('/gzdyh/home'); }}
|
>
|
<WorkStatisticsTabs />
|
<div className="area-statistics">
|
{/* 筛选条件展示区域 */}
|
<div className="filter-info-section">
|
<div className="filter-info-title">筛选条件</div>
|
<div className="filter-settings" onClick={handleOpenSetting}>
|
<img src={workStatistics_5} alt="" className="settings-icon" />
|
<span>设置</span>
|
</div>
|
</div>
|
|
{/* 筛选条件内容 */}
|
<div className="filter-conditions">
|
{queryParams.createStart && queryParams.createEnd && (
|
<div className="filter-info-item">
|
<span className="filter-info-text">登记时间:{queryParams.createStart.replace(/-/g, '.')}~{queryParams.createEnd.replace(/-/g, '.')}</span>
|
<CloseOutlined className="filter-info-clear" onClick={() => clearFilterItem('create')} />
|
</div>
|
)}
|
{queryParams.closeStart && queryParams.closeEnd && (
|
<div className="filter-info-item">
|
<span className="filter-info-text">办结时间:{queryParams.closeStart.replace(/-/g, '.')}~{queryParams.closeEnd.replace(/-/g, '.')}</span>
|
<CloseOutlined className="filter-info-clear" onClick={() => clearFilterItem('close')} />
|
</div>
|
)}
|
{queryParams.canal && (
|
<div className="filter-info-item">
|
<span className="filter-info-text">事项来源:{getCanalText()}</span>
|
<CloseOutlined className="filter-info-clear" onClick={() => clearFilterItem('canal')} />
|
</div>
|
)}
|
{queryParams.caseLevel && (
|
<div className="filter-info-item">
|
<span className="filter-info-text">事项等级:{caseLevelOptions.find(i=>i.value===queryParams.caseLevel)?.label||''}</span>
|
<CloseOutlined className="filter-info-clear" onClick={() => clearFilterItem('caseLevel')} />
|
</div>
|
)}
|
{queryParams.caseType && (
|
<div className="filter-info-item">
|
<span className="filter-info-text">纠纷类型:{caseTypeSelect.caseTypeSelect.find(i => i.value === queryParams.caseType)?.label || ''}</span>
|
<CloseOutlined className="filter-info-clear" onClick={() => clearFilterItem('caseType')} />
|
</div>
|
)}
|
{(queryParams.queCity||queryParams.queArea||queryParams.queRoad||queryParams.queVillage) && (
|
<div className="filter-info-item">
|
<span className="filter-info-text">问题属地:已选择</span>
|
<CloseOutlined className="filter-info-clear" onClick={() => clearFilterItem('area')} />
|
</div>
|
)}
|
</div>
|
|
|
{/* 工作总览 */}
|
<div className="work-overview-section">
|
<div className="work-overview-title">工作总览</div>
|
<div className="work-overview-cards">
|
{/* 第一行卡片:总登记、化解中、不予受理 */}
|
<div className="card-row">
|
{/* 总登记卡片 - 蓝色 */}
|
<div className="overview-card total-card">
|
<div className="card-day-data">
|
{getTimeTypeText()}+{statistics.toDayTotalNum || 0}
|
</div>
|
{isFullMonthOrYear() && (
|
<div className="card-month-rates">
|
<div className="month-compare">
|
{getCompareText()} {renderRateWithIcon(statistics.yoyTotalRate)}
|
</div>
|
<div className="month-ratio">
|
{getRatioText()} {renderRateWithIcon(statistics.momTotalRate)}
|
</div>
|
</div>
|
)}
|
<div className="card-value">{statistics.totalNum || 0}</div>
|
<div className="card-name">总登记</div>
|
</div>
|
|
{/* 化解中卡片 - 蓝色 */}
|
<div className="overview-card process-card">
|
<div className="card-day-data">
|
{getTimeTypeText()}+{statistics.toDayProcessNum || 0}
|
</div>
|
{isFullMonthOrYear() && (
|
<div className="card-month-rates">
|
<div className="month-compare">
|
{getCompareText()} {renderRateWithIcon(statistics.yoyProcessRate)}
|
</div>
|
<div className="month-ratio">
|
{getRatioText()} {renderRateWithIcon(statistics.momProcessRate)}
|
</div>
|
</div>
|
)}
|
<div className="card-value">{statistics.processNum || 0}</div>
|
<div className="card-name">化解中</div>
|
</div>
|
|
{/* 不予受理卡片 - 粉色 */}
|
<div className="overview-card reject-card">
|
<div className="card-day-data">
|
{getTimeTypeText()}+{statistics.toDayRejectNum || 0}
|
</div>
|
{isFullMonthOrYear() && (
|
<div className="card-month-rates">
|
<div className="month-compare">
|
{getCompareText()} {renderRateWithIcon(statistics.yoyRejectRate)}
|
</div>
|
<div className="month-ratio">
|
{getRatioText()} {renderRateWithIcon(statistics.momRejectRate)}
|
</div>
|
</div>
|
)}
|
<div className="card-value">{statistics.rejectNum || 0}</div>
|
<div className="card-name">不予受理</div>
|
</div>
|
</div>
|
|
{/* 第二行卡片:已结案、化解成功率 */}
|
<div className="card-row">
|
{/* 已结案卡片 - 黄色 */}
|
<div className="overview-card finish-card">
|
<div className="card-left-content">
|
<div className="card-day-data">
|
{getTimeTypeText()}+{statistics.toDayFinishNum || 0}
|
</div>
|
{isFullMonthOrYear() && (
|
<div className="card-month-rates">
|
<div className="month-compare">
|
{getCompareText()} {renderRateWithIcon(statistics.yoyFinishNumRate)}
|
</div>
|
<div className="month-ratio">
|
{getRatioText()} {renderRateWithIcon(statistics.momFinishNumRate)}
|
</div>
|
</div>
|
)}
|
<div className="card-value">{statistics.finishNum || 0}</div>
|
<div className="card-name">已结案</div>
|
</div>
|
<div className="card-right-content">
|
<div className="resolve-success">
|
<div className="resolve-value">{statistics.resolveNum || 0}</div>
|
<div className="resolve-label">化解成功</div>
|
</div>
|
<div className="resolve-fail">
|
<div className="resolve-value">{statistics.unResolveNum || 0}</div>
|
<div className="resolve-label">化解不成功</div>
|
</div>
|
</div>
|
</div>
|
|
{/* 化解成功率卡片 - 绿色 */}
|
<div className="overview-card resolve-rate-card">
|
<div className="rate-value">{statistics.resolveRate || '0'}%</div>
|
{isFullMonthOrYear() && (
|
<div className="card-month-rates">
|
<div className="month-compare">
|
{getCompareText()} {renderRateWithIcon(statistics.yoyResolveRate)}
|
</div>
|
<div className="month-ratio">
|
{getRatioText()} {renderRateWithIcon(statistics.momResolveRate)}
|
</div>
|
</div>
|
)}
|
<div className="rate-label">化解成功率</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
{/* 化解统计区域 */}
|
<div className="work-statistics-section">
|
<div className="work-statistics-section-title">化解统计</div>
|
<div className="work-statistics-tabs">
|
<div className={`tab-item ${activeStatTab === 'total' ? 'active' : ''}`} onClick={() => setActiveStatTab('total')}>
|
总登记
|
</div>
|
<div className={`tab-item ${activeStatTab === 'processing' ? 'active' : ''}`} onClick={() => setActiveStatTab('processing')}>
|
化解中
|
</div>
|
<div className={`tab-item ${activeStatTab === 'finished' ? 'active' : ''}`} onClick={() => setActiveStatTab('finished')}>
|
已结案
|
</div>
|
<div className={`tab-item ${activeStatTab === 'rejected' ? 'active' : ''}`} onClick={() => setActiveStatTab('rejected')}>
|
不予受理
|
</div>
|
</div>
|
<div className="work-statistics-chart">
|
{renderChartByTab()}
|
</div>
|
</div>
|
|
{/* 纠纷类型区域 */}
|
<div className="work-statistics-section">
|
<div className="work-statistics-section-title">纠纷类型</div>
|
<div className="work-statistics-pie">
|
<MyLTopChartPie
|
data={(statistics.typeList || []).map(item => ({
|
name: item.caseTypeName || '',
|
value: item.caseNum || 0,
|
rate: item.caseRate || '0'
|
}))}
|
showInside={true}
|
adjustLegend={true}
|
/>
|
</div>
|
</div>
|
|
{/* 事项等级区域 */}
|
<div className="case-level-section">
|
<div className="case-level-title">事项等级</div>
|
<div className="case-level-list">
|
<div className="case-level-item">
|
<div className="level-name">三级</div>
|
<div className="level-bar-container">
|
<div
|
className="level-bar"
|
style={{
|
width: `${Math.min(100, (statistics.threeLevelNum || 0) / (statistics.totalNum || 1) * 100)}%`
|
}}
|
></div>
|
</div>
|
<div className="level-count">{statistics.threeLevelNum || 0}</div>
|
</div>
|
<div className="case-level-item">
|
<div className="level-name">二级</div>
|
<div className="level-bar-container">
|
<div
|
className="level-bar"
|
style={{
|
width: `${Math.min(100, (statistics.twoLevelNum || 0) / (statistics.totalNum || 1) * 100)}%`
|
}}
|
></div>
|
</div>
|
<div className="level-count">{statistics.twoLevelNum || 0}</div>
|
</div>
|
<div className="case-level-item">
|
<div className="level-name">一级</div>
|
<div className="level-bar-container">
|
<div
|
className="level-bar"
|
style={{
|
width: `${Math.min(100, (statistics.oneLevelNum || 0) / (statistics.totalNum || 1) * 100)}%`
|
}}
|
></div>
|
</div>
|
<div className="level-count">{statistics.oneLevelNum || 0}</div>
|
</div>
|
</div>
|
</div>
|
|
{/* 超时办件区域 */}
|
<div className="timeout-section">
|
<div className="timeout-title">超时办件</div>
|
{/* 类型选项卡 */}
|
<div className="timeout-tabs">
|
<div
|
className={`timeout-tab ${activeTimeoutTab === 1 ? 'active' : ''}`}
|
onClick={() => handleTimeoutTabChange(1)}
|
>
|
分派超时<span className="count">({timeoutData.fpcs})</span>
|
</div>
|
<div
|
className={`timeout-tab ${activeTimeoutTab === 2 ? 'active' : ''}`}
|
onClick={() => handleTimeoutTabChange(2)}
|
>
|
受理超时<span className="count">({timeoutData.slcs})</span>
|
</div>
|
<div
|
className={`timeout-tab ${activeTimeoutTab === 3 ? 'active' : ''}`}
|
onClick={() => handleTimeoutTabChange(3)}
|
>
|
督办回复超时<span className="count">({timeoutData.dbcs})</span>
|
</div>
|
</div>
|
|
{/* 督办/回复状态选项卡 */}
|
<div className="supervise-tabs">
|
<div
|
className={`supervise-tab ${activeSuperviseTab === 0 ? 'active' : ''}`}
|
onClick={() => handleSuperviseTabChange(0)}
|
>
|
{activeTimeoutTab === 3 ? '未回复' : '未督办'}
|
</div>
|
<div
|
className={`supervise-tab ${activeSuperviseTab === 1 ? 'active' : ''}`}
|
onClick={() => handleSuperviseTabChange(1)}
|
>
|
{activeTimeoutTab === 3 ? '已回复' : '已督办'}
|
</div>
|
</div>
|
|
{/* 列表表头 */}
|
<div className="timeout-list-header">
|
<div className="header-seq">序</div>
|
<div className="header-time">流转时间</div>
|
<div className="header-dept">分派部门</div>
|
<div className="header-applicant">申请方</div>
|
<div className="header-action">操作</div>
|
</div>
|
|
{/* 超时列表 */}
|
<div className="timeout-list">
|
{timeoutList.length > 0 ? (
|
timeoutList.map((item, index) => (
|
<div className="timeout-item" key={index}>
|
<div className="item-seq">{(timeoutPagination.page - 1) * timeoutPagination.size + index + 1}</div>
|
<div className="item-time">
|
<span className="time-str">{item.turnaroundTime ? $$.myTimeFormat(new Date(item.turnaroundTime), 'YYYY-MM-DD HH:mm') : ''}</span>
|
{activeSuperviseTab === 0 && (
|
<span className="timeout-tag red">
|
{formatTimeLimit(item.timeLimit)}
|
</span>
|
)}
|
{activeSuperviseTab === 1 && activeTimeoutTab !== 3 && item.supNum > 0 && (
|
<span className="timeout-tag green">已回复</span>
|
)}
|
{activeSuperviseTab === 1 && activeTimeoutTab !== 3 && !(item.supNum > 0) && (
|
<span className="timeout-tag red">未回复</span>
|
)}
|
</div>
|
<div className="item-dept">{item.candeUnitName || '-'}</div>
|
<div className="item-applicant">{item.plaintiffs || '-'}</div>
|
<div className="item-action">
|
<span className="detail-btn" onClick={() => handleDetailClick(item.caseTaskId, item.caseId)}>详情</span>
|
</div>
|
</div>
|
))
|
) : (
|
<div className="area-empty">
|
<img src={caseQuery_2} alt="暂无数据" className="area-empty-image" />
|
<div className="area-empty-text">暂无数据</div>
|
</div>
|
)}
|
</div>
|
|
{/* 分页控制 */}
|
{timeoutList.length > 0 && timeoutPagination.totalPages > 1 && (
|
<div className="pagination">
|
{timeoutPagination.page > 1 && (
|
<div className="pagination-btn" onClick={() => handlePageChange(timeoutPagination.page - 1)}>
|
上一页
|
</div>
|
)}
|
<div className="pagination-info">{timeoutPagination.page}/{timeoutPagination.totalPages}</div>
|
{timeoutPagination.page < timeoutPagination.totalPages && (
|
<div className="pagination-btn" onClick={() => handlePageChange(timeoutPagination.page + 1)}>
|
下一页
|
</div>
|
)}
|
</div>
|
)}
|
</div>
|
|
{/* 设置筛选条件抽屉 */}
|
<Modal
|
visible={settingVisible}
|
onClose={handleCloseSetting}
|
className="custom-filter-modal"
|
position="bottom"
|
maskClosable={true}
|
popup
|
transparent={false}
|
>
|
<div className="custom-filter-content">
|
<div className="filter-item full-row">
|
<div className="filter-label">问题属地</div>
|
<div className="filter-selector"
|
onClick={() => {
|
// 在这里打开地区选择弹窗
|
// 由于地区选择较复杂,可以使用现有的Picker组件或者自定义实现
|
// 这里先简化处理
|
}}
|
>
|
{queryParams.queCity || queryParams.queArea || queryParams.queRoad || queryParams.queVillage
|
? `已选择地区`
|
: '请选择'}
|
</div>
|
</div>
|
<div className="filter-item">
|
<div className="filter-label">登记时间</div>
|
<div
|
className="filter-selector"
|
onClick={() => handleOpenDateTypeModal('create')}
|
>
|
{queryParams.createStart && queryParams.createEnd ?
|
`${queryParams.createStart}~${queryParams.createEnd}` :
|
'请选择日期范围'
|
}
|
</div>
|
</div>
|
|
<div className="filter-item">
|
<div className="filter-label">办结时间</div>
|
<div
|
className="filter-selector"
|
onClick={() => handleOpenDateTypeModal('close')}
|
>
|
{queryParams.closeStart && queryParams.closeEnd ?
|
`${queryParams.closeStart}~${queryParams.closeEnd}` :
|
'请选择日期范围'
|
}
|
</div>
|
</div>
|
|
<div className="filter-item full-row">
|
<div className="filter-label">事项来源</div>
|
<div className="canal-radio-group">
|
<div
|
className={`canal-radio${!queryParams.canal ? ' checked' : ''}`}
|
onClick={() => setQueryParams(prev => ({ ...prev, canal: '' }))}
|
>全部{!queryParams.canal && <img src={applyClose_1} alt="选中" className="audit-radio-checked" />}</div>
|
{canalOptions.map(opt => (
|
<div
|
key={opt.value}
|
className={`canal-radio${queryParams.canal === opt.value ? ' checked' : ''}`}
|
onClick={() => setQueryParams(prev => ({ ...prev, canal: opt.value }))}
|
>{opt.label}{queryParams.canal === opt.value && <img src={applyClose_1} alt="选中" className="audit-radio-checked" />}</div>
|
))}
|
</div>
|
</div>
|
|
|
<div className="filter-item full-row">
|
<div className="filter-label">纠纷类型</div>
|
<Picker
|
data={caseTypeSelect.caseTypeSelect}
|
cols={2}
|
cascade={true}
|
title="纠纷类型"
|
value={queryParams.caseType ? [queryParams.caseType] : []}
|
okText="确定"
|
onChange={valArr => {
|
// valArr为数组,只有一级时长度为1,二级时长度为2
|
const value = valArr[valArr.length - 1];
|
setQueryParams(prev => ({ ...prev, caseType: value }));
|
}}
|
extra={<span className="select-color">请选择</span>}
|
>
|
<List.Item className="filter-selector">
|
{getCaseTypeLabel(queryParams.caseType) || '请选择'}
|
</List.Item>
|
</Picker>
|
</div>
|
</div>
|
|
<div className="custom-filter-buttons">
|
<Button className="filter-reset" onClick={handleResetFilter}>重置</Button>
|
<Button className="filter-apply" type="primary" onClick={handleApplyFilter}>统计</Button>
|
</div>
|
</Modal>
|
|
{/* 日期选择弹窗 */}
|
<Modal
|
visible={datePickerVisible}
|
onClose={() => setDatePickerVisible(false)}
|
className="date-picker-modal"
|
position="bottom"
|
maskClosable={true}
|
popup
|
transparent={false}
|
>
|
<div className="modal-header">
|
<div className="modal-title">选择日期范围</div>
|
<CloseOutlined className="modal-close" onClick={() => setDatePickerVisible(false)} />
|
</div>
|
<CalendarRangeTwoDay
|
CalendaronClick={handleCalendarClick}
|
onClickDate={handleDateChange}
|
/>
|
</Modal>
|
|
{/* 时间类型选择底部弹窗 */}
|
<Modal
|
visible={dateTypeModalVisible}
|
onClose={() => setDateTypeModalVisible(false)}
|
className="date-type-modal"
|
position="bottom"
|
maskClosable={true}
|
popup
|
transparent={false}
|
>
|
<div className="modal-header">
|
<div className="modal-title">选择时间类型</div>
|
<CloseOutlined className="modal-close" onClick={() => setDateTypeModalVisible(false)} />
|
</div>
|
<div className="date-type-modal-content">
|
{dateTypeOptions.map(opt => (
|
<div
|
key={opt.value}
|
className="date-type-modal-item"
|
onClick={() => handleDateTypeSelect(opt.value)}
|
>
|
{opt.label}
|
</div>
|
))}
|
</div>
|
</Modal>
|
</div>
|
</NavBarPage>
|
);
|
};
|
|
export default AreaStatistics;
|