import { buildProTableDataSource, sendRequest, showDelConfirm } from '@/utils/antdUtils';
|
import { PageContainer } from '@ant-design/pro-components';
|
import { Button, InputNumber, Select, Space, Form, Card, Row, Col, Statistic, DatePicker } from 'antd';
|
import { useEffect, useState } from 'react';
|
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
|
import { Access, useAccess } from 'umi';
|
import AddAndEdit from './components/addAndEdit';
|
import { getCommunityList, getNextRegion, getStatisticsData } from './service';
|
import ReactECharts from 'echarts-for-react';
|
import moment from 'moment';
|
|
// 同比趋势组件
|
const Trend = ({ value }) => {
|
if (value > 0) {
|
return (
|
<span style={{ color: 'red', marginLeft: 4 }}>
|
<ArrowUpOutlined /> {Math.abs(value)}%
|
</span>
|
);
|
}
|
if (value < 0) {
|
return (
|
<span style={{ color: 'green', marginLeft: 4 }}>
|
<ArrowDownOutlined /> {Math.abs(value)}%
|
</span>
|
);
|
}
|
return <span style={{ marginLeft: 4 }}>0%</span>;
|
};
|
|
const statistics = () => {
|
const [adminLevel, setAdminLevel] = useState(JSON.parse(localStorage.getItem('userInfo')).accountLevel);
|
const [form] = Form.useForm();
|
const [districtOptions, setDistrictOptions] = useState([]);
|
const [streetOptions, setStreetOptions] = useState([]);
|
const [communityOptions, setCommunityOptions] = useState([]);
|
const [dateRange, setDateRange] = useState([]);
|
const [loading, setLoading] = useState(false);
|
const [rankType, setRankType] = useState('top5');
|
|
// 统计数据
|
const [stats, setStats] = useState({
|
total: 0,
|
month: 0,
|
totalMonthRate: 0,
|
processing: 0,
|
reviewing: 0,
|
delayed: 0,
|
finished: 0,
|
overtime: 0,
|
overtimeMonth: 0,
|
overtimeMonthRate: 0,
|
avgTime: 0,
|
avgTimeMonth: 0,
|
avgTimeRate: 0,
|
satisfaction: 0,
|
satisfactionMonth: 0,
|
satisfactionRate: 0,
|
});
|
|
// 图表数据
|
const [chartData, setChartData] = useState([]);
|
const [allTypeData, setAllTypeData] = useState([]);
|
const [pieData, setPieData] = useState([]);
|
|
// 诉求单量统计图表配置
|
const echartsOption = {
|
tooltip: {
|
trigger: 'axis',
|
},
|
legend: {
|
data: ['诉求单量', '诉求办结数'],
|
},
|
grid: {
|
left: '3%',
|
right: '3%',
|
bottom: '10%',
|
containLabel: true,
|
},
|
dataZoom: [
|
{
|
type: 'slider',
|
show: true,
|
startValue: 0,
|
endValue: 10,
|
height: 12,
|
bottom: 0,
|
showDetail: false,
|
showDataShadow: false,
|
fillerColor: '#dbdee5',
|
borderColor: 'transparent',
|
zoomLock: true,
|
brushSelect: false,
|
handleStyle: {
|
opacity: 0
|
}
|
},
|
{
|
type: "inside",
|
zoomOnMouseWheel: false,
|
moveOnMouseMove: true,
|
moveOnMouseWheel: true,
|
},
|
],
|
xAxis: {
|
type: 'category',
|
data: chartData.map(item => item.date),
|
axisLabel: {
|
interval: 'auto',
|
}
|
},
|
yAxis: [
|
{
|
type: 'value',
|
name: '诉求单量',
|
min: 0,
|
},
|
{
|
type: 'value',
|
name: '诉求办结数',
|
min: 0,
|
},
|
],
|
series: [
|
{
|
name: '诉求单量',
|
type: 'bar',
|
data: chartData.map(item => item.count),
|
yAxisIndex: 0,
|
itemStyle: { color: '#3b7cff' },
|
barWidth: 30,
|
},
|
{
|
name: '诉求办结数',
|
type: 'line',
|
data: chartData.map(item => item.finish),
|
yAxisIndex: 1,
|
itemStyle: { color: '#3bcfcf' },
|
lineStyle: { width: 2 },
|
smooth: true,
|
},
|
],
|
};
|
|
// 问题类型排名图表配置
|
const typeRankOption = {
|
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
dataZoom: [
|
{
|
type: 'slider',
|
show: true,
|
startValue: 0,
|
endValue: 4,
|
bottom: 0,
|
showDetail: false,
|
showDataShadow: false,
|
height: '100%',
|
width: 10,
|
fillerColor: '#dbdee5',
|
borderColor: 'transparent',
|
zoomLock: true,
|
orient: 'vertical',
|
brushSelect: false,
|
handleStyle: {
|
opacity: 0
|
}
|
},
|
{
|
type: "inside",
|
zoomOnMouseWheel: false,
|
moveOnMouseMove: true,
|
moveOnMouseWheel: true,
|
orient: 'vertical'
|
},
|
],
|
xAxis: { type: 'value', boundaryGap: [0, 0.01] },
|
yAxis: { type: 'category', data: allTypeData.map(item => item.name) },
|
series: [
|
{
|
name: '数量',
|
type: 'bar',
|
data: allTypeData.map(item => item.value),
|
itemStyle: { color: '#4a90e2' },
|
barWidth: 20,
|
label: { show: true, position: 'right' },
|
},
|
],
|
};
|
|
// 问题评价占比图表配置
|
const pieOption = {
|
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
|
legend: { orient: 'vertical', left: 'right', data: pieData.map(i => i.name) },
|
color: ['#4a90e2', '#6dd400', '#f5a623', '#f44336'],
|
series: [
|
{
|
name: '评价占比',
|
type: 'pie',
|
radius: ['50%', '70%'],
|
avoidLabelOverlap: false,
|
label: { show: false, position: 'center' },
|
emphasis: { label: { show: true, fontSize: 18, fontWeight: 'bold' } },
|
labelLine: { show: false },
|
data: pieData,
|
},
|
],
|
};
|
|
// 处理表单和rank筛选,组装参数并请求接口
|
const handleSearch = async (values) => {
|
setLoading(true);
|
let time = '';
|
if (dateRange && dateRange.length === 2) {
|
time = `${dateRange[0].format('YYYY-MM-DD')} - ${dateRange[1].format('YYYY-MM-DD')}`;
|
}
|
const params = {
|
cityCode: values.cityCode,
|
districtCode: values.districtId,
|
streetId: values.streetId,
|
communityId: values.communityId,
|
time,
|
rank: rankType === 'all' ? undefined : rankType.replace('top', ''),
|
};
|
try {
|
const res = await getStatisticsData(params);
|
const data = res.data.analyticStatisticsOneVo;
|
setStats({
|
total: data.allTotal,
|
month: data.thisMonthTotal,
|
totalMonthRate: data.lastMonthCompareTotal,
|
processing: data.nowTransactTotal,
|
reviewing: data.auditTransactTotal,
|
delayed: data.postponeTransactTotal,
|
finished: data.completeTransactTotal,
|
overtime: data.overtimeTransactTotal,
|
overtimeMonth: data.thisMonthOvertimeTransactTotal,
|
overtimeMonthRate: data.lastMonthOvertimeTransactCompareTotal,
|
avgTime: data.averageTime,
|
avgTimeMonth: data.thisMonthAverageTime,
|
avgTimeRate: data.lastMonthCompareAverageTime,
|
satisfaction: data.satisfactionRate,
|
satisfactionMonth: data.thisMonthSatisfactionRate,
|
satisfactionRate: data.lastMonthCompareSatisfactionRate,
|
});
|
setChartData((res.data.analyticStatisticsTwoVos || []).map(item => ({
|
date: item.time,
|
count: item.allTotal,
|
finish: item.completeTotal,
|
})));
|
setAllTypeData((res.data.analyticStatisticsThreeVos || []).map(item => ({
|
name: item.name,
|
value: item.allTotal,
|
})));
|
const fourVo = res.data.analyticStatisticsFourVo || {};
|
setPieData([
|
{ value: fourVo.greatSatisfactionRate, name: '非常满意' },
|
{ value: fourVo.satisfactionRate, name: '满意' },
|
{ value: fourVo.generalSatisfactionRate, name: '一般' },
|
{ value: fourVo.dissatisfactionRate, name: '不满意' },
|
]);
|
} catch (e) {
|
// 错误处理
|
}
|
setLoading(false);
|
};
|
|
// rankType变化时自动触发表单查询
|
useEffect(() => {
|
form.submit();
|
}, [rankType]);
|
|
useEffect(() => {
|
getCommunityList().then(res => {
|
const { bcRegions, comActs, comStreets } = res.data;
|
if (adminLevel <= 2) {
|
setDistrictOptions(bcRegions);
|
return;
|
}
|
if (adminLevel <= 3) {
|
setStreetOptions(comStreets);
|
return;
|
}
|
setCommunityOptions(comActs);
|
});
|
}, [adminLevel]);
|
|
// 处理区县选择
|
const handleDistrictChange = async (value) => {
|
if (!value) {
|
setStreetOptions([]);
|
setCommunityOptions([]);
|
form.setFieldsValue({ streetId: undefined, communityId: undefined });
|
return;
|
}
|
const res = await getNextRegion({ id: value, type: 1 });
|
setStreetOptions(res.data || []);
|
setCommunityOptions([]);
|
form.setFieldsValue({ streetId: undefined, communityId: undefined });
|
};
|
|
// 处理街道选择
|
const handleStreetChange = async (value) => {
|
if (!value) {
|
setCommunityOptions([]);
|
form.setFieldsValue({ communityId: undefined });
|
return;
|
}
|
const res = await getNextRegion({ id: value, type: 2 });
|
setCommunityOptions(res.data || []);
|
form.setFieldsValue({ communityId: undefined });
|
};
|
|
// 根据管理员级别获取可用的筛选项
|
const getFilterItems = () => {
|
const baseItems = [
|
{
|
name: 'communityId',
|
label: '社区',
|
component: <Select
|
style={{ width: 200 }}
|
options={communityOptions}
|
placeholder="请选择社区"
|
fieldNames={{ label: 'name', value: 'communityId' }}
|
disabled={!form.getFieldValue('streetId')}
|
/>,
|
},
|
];
|
|
if (adminLevel <= 3) {
|
baseItems.unshift({
|
name: 'streetId',
|
label: '街道',
|
component: <Select
|
style={{ width: 200 }}
|
options={streetOptions}
|
placeholder="请选择街道"
|
fieldNames={{ label: 'name', value: 'streetId' }}
|
onChange={handleStreetChange}
|
disabled={!form.getFieldValue('districtId')}
|
/>,
|
});
|
}
|
|
if (adminLevel <= 2) {
|
baseItems.unshift({
|
name: 'districtId',
|
label: '区县',
|
component: <Select
|
style={{ width: 200 }}
|
options={districtOptions}
|
placeholder="请选择区县"
|
fieldNames={{ label: 'regionName', value: 'regionCode' }}
|
onChange={handleDistrictChange}
|
/>,
|
});
|
}
|
|
return baseItems;
|
};
|
|
return (
|
<div>
|
<PageContainer header={{
|
breadcrumb: {},
|
}}
|
title={'统计分析'}
|
>
|
{/* 筛选表单 */}
|
<Card style={{ marginBottom: 24 }}>
|
<Form
|
form={form}
|
layout="inline"
|
style={{ marginBottom: 24 }}
|
onFinish={handleSearch}
|
>
|
{getFilterItems().map((item) => (
|
<Form.Item
|
key={item.name}
|
name={item.name}
|
label={item.label}
|
>
|
{item.component}
|
</Form.Item>
|
))}
|
<Form.Item>
|
<Space>
|
<Button type="primary" onClick={() => form.submit()}>
|
查询
|
</Button>
|
<Button onClick={() => {
|
form.resetFields();
|
setStreetOptions([]);
|
setCommunityOptions([]);
|
}}>
|
重置
|
</Button>
|
</Space>
|
</Form.Item>
|
</Form>
|
</Card>
|
|
{/* 标题 */}
|
<div style={{ fontWeight: 600, fontSize: 18, marginBottom: 16, borderLeft: '4px solid #3b7cff', paddingLeft: 8 }}>
|
诉求单量统计
|
</div>
|
|
{/* 统计卡片区 */}
|
<Row gutter={[16, 16]} wrap={false}>
|
<Col flex="1">
|
<Card style={{ height: 120, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
<Statistic title="诉求单量总计" value={stats.total} />
|
<div>
|
本月 {stats.month}
|
<span style={{ marginLeft: 8 }}>
|
同比上月
|
<Trend value={stats.totalMonthRate} />
|
</span>
|
</div>
|
</Card>
|
</Col>
|
<Col flex="0 0 180px">
|
<Card style={{ height: 120, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
<Statistic title="正在办理" value={stats.processing} />
|
</Card>
|
</Col>
|
<Col flex="0 0 180px">
|
<Card style={{ height: 120, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
<Statistic title="审核中" value={stats.reviewing} />
|
</Card>
|
</Col>
|
<Col flex="0 0 180px">
|
<Card style={{ height: 120, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
<Statistic title="延期办理" value={stats.delayed} />
|
</Card>
|
</Col>
|
<Col flex="0 0 180px">
|
<Card style={{ height: 120, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
<Statistic title="已办结" value={stats.finished} />
|
</Card>
|
</Col>
|
<Col flex="1">
|
<Card style={{ height: 120, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
<Statistic title="超时办理" value={stats.overtime} />
|
<div>
|
本月 {stats.overtimeMonth}
|
<span style={{ marginLeft: 8 }}>
|
同比上月
|
<Trend value={stats.overtimeMonthRate} />
|
</span>
|
</div>
|
</Card>
|
</Col>
|
</Row>
|
|
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
|
<Col>
|
<Card>
|
<Statistic title="平均处理时间" value={stats.avgTime + '天'} />
|
<div>
|
本月 {stats.avgTimeMonth}天
|
<span style={{ marginLeft: 8 }}>
|
同比上月
|
<Trend value={stats.avgTimeRate} />
|
</span>
|
</div>
|
</Card>
|
</Col>
|
<Col>
|
<Card>
|
<Statistic title="总体满意率" value={stats.satisfaction + '%'} />
|
<div>
|
本月 {stats.satisfactionMonth}%
|
<span style={{ marginLeft: 8 }}>
|
同比上月
|
<Trend value={stats.satisfactionRate} />
|
</span>
|
</div>
|
</Card>
|
</Col>
|
</Row>
|
|
{/* 时间筛选区 */}
|
<div style={{ margin: '24px 0' }}>
|
<Space>
|
<DatePicker.RangePicker
|
value={dateRange}
|
onChange={(dates) => {
|
setDateRange(dates);
|
form.setFieldsValue({ time: dates });
|
form.submit();
|
}}
|
/>
|
<Button onClick={() => {
|
const end = moment();
|
const start = moment().subtract(7, 'days');
|
const dates = [start, end];
|
setDateRange(dates);
|
form.setFieldsValue({ time: dates });
|
form.submit();
|
}}>近7天</Button>
|
<Button onClick={() => {
|
const end = moment();
|
const start = moment().subtract(30, 'days');
|
const dates = [start, end];
|
setDateRange(dates);
|
form.setFieldsValue({ time: dates });
|
form.submit();
|
}}>近30天</Button>
|
</Space>
|
</div>
|
|
{/* 图表区 */}
|
<div style={{ background: '#fff', padding: 24 }}>
|
<div style={{ width: '100%', overflowX: 'auto' }}>
|
<div style={{ minWidth: '100%', height: 300 }}>
|
<ReactECharts
|
option={echartsOption}
|
style={{ height: '100%', width: '100%' }}
|
opts={{ renderer: 'svg' }}
|
/>
|
</div>
|
</div>
|
</div>
|
|
{/* 问题类型排名 */}
|
<div style={{ background: '#fff', padding: 24, marginTop: 32 }}>
|
<div style={{ fontWeight: 600, fontSize: 16, marginBottom: 16, borderLeft: '4px solid #3b7cff', paddingLeft: 8 }}>
|
问题类型排名
|
<Select
|
style={{ width: 120, marginLeft: 16 }}
|
value={rankType}
|
onChange={setRankType}
|
options={[
|
{ label: '排名前5', value: 'top5' },
|
{ label: '排名前10', value: 'top10' },
|
{ label: '全部排名', value: 'all' },
|
]}
|
/>
|
</div>
|
<div style={{ maxHeight: 320, overflowY: 'auto' }}>
|
<ReactECharts option={typeRankOption} style={{ height: Math.max(60 * allTypeData.length, 300) }} />
|
</div>
|
</div>
|
|
{/* 问题评价占比 */}
|
<div style={{ background: '#fff', padding: 24, marginTop: 32 }}>
|
<div style={{ fontWeight: 600, fontSize: 16, marginBottom: 16, borderLeft: '4px solid #3b7cff', paddingLeft: 8 }}>
|
问题评价占比
|
</div>
|
<ReactECharts option={pieOption} style={{ height: 300 }} />
|
</div>
|
</PageContainer>
|
</div>
|
);
|
};
|
|
export default statistics;
|