feat: 新增综合查询

This commit is contained in:
lonewolfyx 2025-01-14 11:01:47 +08:00
parent fb2ed70c94
commit 6a2c754811
6 changed files with 677 additions and 2 deletions

View File

@ -32,7 +32,7 @@
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"element-plus": "^2.8.3", "element-plus": "^2.9.3",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"fuse.js": "^6.6.2", "fuse.js": "^6.6.2",
"guodu-data-panel": "^0.0.12", "guodu-data-panel": "^0.0.12",

View File

@ -140,4 +140,14 @@ export const getVisitHourTrend = () => {
method: 'get', method: 'get',
url: '/visiting/visit/stat_hour', url: '/visiting/visit/stat_hour',
}) })
}
// 获取综合查询饼图与趋势图
export const getVisitPieAndTrend = (data) => {
return request({
method: 'post',
url: '/visiting/visit/unify_stat',
data: data,
isEncrypt: false
})
} }

View File

@ -0,0 +1,95 @@
<template>
<el-card shadow="never" header="聚合纬度" v-if="isShow">
<div style="width: 100%;height: 300px;">
<Vechart :option="options"/>
</div>
</el-card>
</template>
<script setup>
import {use} from 'echarts/core';
import {GridComponent, TooltipComponent} from 'echarts/components';
import {BarChart} from 'echarts/charts';
import {CanvasRenderer} from 'echarts/renderers';
import Vechart from 'vue-echarts';
import {getVisitPieAndTrend} from '@/api/RegistVisitApi/RegistVisitApi.js';
defineOptions({
name: 'CategorySummaryDimension'
})
use([GridComponent, BarChart, CanvasRenderer, TooltipComponent]);
const isShow = ref(false)
// const year = Array.from({length: 12}, (_, i) => i + 1)
// const data = Array.from({length: 12}, (_, i) => Math.random() * 1000)
const options = ref({})
const getData = async (queryParams, aggrBar) => {
queryParams.params.aggrBar = aggrBar;
const res = await getVisitPieAndTrend(queryParams)
console.log('bar', res.data.map(item => item.ym))
setChartOptions(res.data.map(item => item.ym ?? item.year), res.data.map(item => item.count))
isShow.value = true;
}
defineExpose({getData})
const setChartOptions = (year, data) => {
options.value = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
},
formatter: '{b} : {c}'
},
grid: {
top: '10%',
left: '3%',
right: '3%',
bottom: '10%',
},
xAxis: {
type: 'category',
data: year,
axisTick: {
show: false
},
axisLine: {
show: false
},
axisPointer: {
type: 'shadow'
},
// axisLabel: {interval: 0, rotate: 30}
},
yAxis: {
type: 'value'
},
series: [
{
// name: 'Direct',
// type: 'bar',
// barWidth: '60%',
data: data,
type: 'bar',
barWidth: '35%',
itemStyle: {
color: '#007fb4'
}
}
]
}
}
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,95 @@
<template>
<el-card shadow="never" header="聚合纬度" class="mb-4" v-if="isShow">
<div style="width: 100%;height: 300px;">
<Vechart :option="options"/>
</div>
</el-card>
</template>
<script setup>
import {use} from 'echarts/core';
import {LegendComponent, TitleComponent, TooltipComponent} from 'echarts/components';
import {PieChart} from 'echarts/charts';
import {CanvasRenderer} from 'echarts/renderers';
import Vechart from 'vue-echarts';
import {LabelLayout} from 'echarts/features';
import {getVisitPieAndTrend} from '@/api/RegistVisitApi/RegistVisitApi.js';
defineOptions({
name: 'LatitudeAggregator'
})
use([
TitleComponent,
TooltipComponent,
LegendComponent,
PieChart,
CanvasRenderer,
LabelLayout
]);
const isShow = ref(false)
const options = ref({})
const getData = async (queryParams, aggrPie) => {
queryParams.params.aggrPie = aggrPie;
const res = await getVisitPieAndTrend(queryParams)
setChartOptions(res.data.map(item => {
return {
...item,
value: item.count
}
}))
isShow.value = true;
}
defineExpose({getData})
const setChartOptions = (data) => {
options.value = {
color: ['#003366', '#006699', '#4cabce', '#e5323e'],
tooltip: {
trigger: 'item',
// axisPointer: {
// type: 'cross',
// crossStyle: {
// color: '#999'
// }
// },
formatter: '{b} : {c}'
},
legend: {
type: 'scroll',
orient: 'vertical',
left: 10,
top: 20,
bottom: 20,
// data: data.legendData
},
series: [
{
name: 'Access From',
type: 'pie',
radius: '60%',
// top: '20%',
data: data,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
}
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,474 @@
<template>
<div class="app-container">
<el-form
ref="queryParamsRef"
:model="queryParams"
label-width="auto"
label-position="left"
>
<el-row :gutter="24">
<el-col :span="6">
<el-form-item label="姓名">
<el-input
v-model="queryParams.visitorName"
clearable
placeholder="请输入来访人姓名"
@change="searchHandle"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="上访时间">
<el-date-picker
v-model="searchTime"
type="daterange"
unlink-panels
range-separator="-"
start-placeholder="开始时间"
end-placeholder="截止时间"
value-format="YYYY-MM-DD"
@change="changeDateTimerHandle"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="电话">
<el-input
v-model="queryParams.visitorMobile"
clearable
placeholder="请输入来访人电话"
@change="searchHandle"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="工作单位或住址">
<el-input
v-model="queryParams.visitorContact"
clearable
placeholder="请输入来访人工作单位或住址"
@change="searchHandle"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="性别">
<el-select
v-model="queryParams.visitorSex"
placeholder="请选择性别"
@change="searchHandle"
>
<el-option
v-for="item in sys_user_sex"
:label="item.label"
:value="item.value"
:key="item.label"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="学历">
<el-select
v-model="queryParams.visitorDegree"
placeholder="请选择学历"
@change="searchHandle"
>
<el-option
v-for="item in people_degree"
:label="item.label"
:value="item.value"
:key="item.label"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="来源">
<el-input
v-model="queryParams.source"
clearable
placeholder="请输入来访人来源"
@change="searchHandle"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="年龄">
<el-input
v-model="queryParams.visitorAge"
clearable
placeholder="请输入来访人年龄"
@change="searchHandle"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="接待人">
<el-input
v-model="queryParams.receptionist"
clearable
placeholder="请输入接待人"
@change="searchHandle"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="接待人类型">
<el-select
v-model="queryParams.receptionistType"
placeholder="请选择接待人类型"
@change="searchHandle"
>
<el-option
v-for="item in receptionist_type"
:label="item.label"
:value="item.value"
:key="item.label"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="联系住址">
<el-input
v-model="queryParams.visitorAddress"
clearable
placeholder="请输入来访人联系住址"
@change="searchHandle"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="身份证号码">
<el-input
v-model="queryParams.visitorCode"
clearable
placeholder="请输入来访人身份证号码"
@change="searchHandle"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="来访类型">
<el-select
v-model="queryParams.type"
placeholder="请选择来访类型"
@change="searchHandle"
>
<el-option
v-for="item in visit_type"
:label="item.label"
:value="item.value"
:key="item.label"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="相关领域">
<el-select
v-model="queryParams.field"
placeholder="请选择领域"
@change="searchHandle"
>
<el-option
v-for="item in visit_field"
:label="item.label"
:value="item.value"
:key="item.label"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="答复形式">
<el-select
v-model="queryParams.replyType"
placeholder="请选择答复形式"
@change="searchHandle"
>
<el-option
v-for="item in visit_reply_type"
:label="item.label"
:value="item.value"
:key="item.label"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="上访级别">
<el-select
v-model="queryParams.level"
placeholder="请选择来访级别"
@change="searchHandle"
>
<el-option
v-for="item in visit_level"
:label="item.label"
:value="item.value"
:key="item.label"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="聚合纬度">
<el-radio-group v-model="aggrPie" @change="aggrPieChange">
<el-radio
v-for="dict in types"
:label="dict.name"
:value="dict.value"
:key="dict.key"
/>
</el-radio-group>
</el-form-item>
<el-form-item label="分类汇总纬度">
<el-radio-group v-model="aggrBar" @change="aggrBarChange">
<el-radio label="月份" value="month"/>
<el-radio label="年份" value="year"/>
</el-radio-group>
</el-form-item>
<el-row :gutter="24">
<el-col :span="2" :offset="20">
<el-form-item>
<el-button type="primary" @click="searchHandle">
<el-icon class="me-1">
<Search/>
</el-icon>
查询
</el-button>
</el-form-item>
</el-col>
<el-col :span="2">
<el-form-item>
<el-button @click="handleRest(queryParamsRef)">
<el-icon class="me-1">
<Refresh/>
</el-icon>
重置
</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-card shadow="never" class="mb-4">
<el-table
ref="tableDataRef"
v-loading="loading"
:data="tableData"
show-overflow-tooltip
>
<el-table-column label="日期" prop="visitTime" width="230">
<template #default="scope">
{{ dayjs(scope.row.visitTime).format('YYYY-MM-DD hh:mm:ss') }}
</template>
</el-table-column>
<el-table-column label="上访人" prop="visitorName" width="150"/>
<el-table-column label="电话" prop="visitorMobile" width="200"/>
<el-table-column label="工作单位或住址" prop="visitorContact" width="230"/>
<el-table-column label="身份证号码" prop="visitorCode" width="230"/>
<el-table-column label="反映事项" prop="demand" width="230"/>
<el-table-column label="处理情况" prop="reply" width="230"/>
<el-table-column label="答复形式" prop="replyType" width="230">
<template #default="scope">
<dict-tag :options="visit_reply_type" :value="scope.row.replyType"/>
</template>
</el-table-column>
<el-table-column label="备注" prop="remark" width="230"/>
<el-table-column fixed="right" align="center" label="操作" width="150">
<template #default="scope">
<el-button
link size="small"
type="warning"
@click="AddEditRegisterVisitRef.showDetail(scope.row)"
>
<el-icon class="me-1">
<View/>
</el-icon>
详情
</el-button>
<el-button link size="small" type="info" @click="printRef.printMeetingForm(scope.row)">
<el-icon class="me-1">
<Printer/>
</el-icon>
打印
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
:total="total"
v-model:page="paging.pageNum"
v-model:limit="paging.pageSize"
@pagination="getLists"
/>
</el-card>
<LatitudeAggregator ref="LatitudeAggregatorRef"/>
<CategorySummaryDimension ref="CategorySummaryDimensionRef"/>
</div>
<AddEditRegisterVisit ref="AddEditRegisterVisitRef"/>
<Print ref="printRef"/>
</template>
<script setup>
import {getCurrentInstance, useTemplateRef} from 'vue';
import dayjs from 'dayjs';
import DictTag from '@/components/DictTag/index.vue';
import {Printer, Refresh, View} from '@element-plus/icons-vue';
import Print from '@/views/PoliceWork/RegisterVisit/print.vue';
import AddEditRegisterVisit from '@/views/PoliceWork/RegisterVisit/components/AddEditRegisterVisit.vue';
import PageEnum from '@/enum/PageEnum.js';
import {getVisitList} from '@/api/RegistVisitApi/RegistVisitApi.js';
import CategorySummaryDimension from '@/views/Composite/components/CategorySummaryDimension.vue';
import LatitudeAggregator from '@/views/Composite/components/LatitudeAggregator.vue';
import {deepClone} from '@/utils/index.js';
const {proxy} = getCurrentInstance();
const {
sys_user_sex, people_degree, visit_type, visit_field, visit_level, visit_reply_type, receptionist_type
} = proxy.useDict('sys_user_sex', 'people_degree', 'visit_type', 'visit_field', 'visit_level', 'visit_reply_type', 'receptionist_type');
const queryParamsRef = useTemplateRef('queryParamsRef')
const AddEditRegisterVisitRef = useTemplateRef('AddEditRegisterVisitRef')
const printRef = useTemplateRef('printRef')
const LatitudeAggregatorRef = useTemplateRef('LatitudeAggregatorRef')
const CategorySummaryDimensionRef = useTemplateRef('CategorySummaryDimensionRef')
const types = [
{key: useId(), name: '来访类型', value: 'type'},
{key: useId(), name: '答复形式', value: 'reply_type'},
{key: useId(), name: '接待人', value: 'receptionist'},
{key: useId(), name: '相关领域', value: 'field'},
{key: useId(), name: '学历', value: 'visitor_degree'},
{key: useId(), name: '上访级别', value: 'level'},
]
const loading = ref(false);
const searchTime = ref([]);
const total = ref(0);
const paging = ref({
pageNum: 1,
pageSize: PageEnum.SIZE,
});
const tableData = ref([])
const queryParams = ref({
visitorName: '',
visitorMobile: '',
visitorContact: '',
visitorSex: '',
visitorDegree: '',
source: '',
visitorAge: '',
receptionist: '',
receptionistType: '',
visitorAddress: '',
visitorCode: '',
type: '',
field: '',
replyType: '',
level: '',
params: {
beginTime: '',
endTime: '',
}
})
//
const aggrPie = ref('type');
//
const aggrBar = ref('month');
onMounted(() => {
searchHandle();
});
//
const getLists = async () => {
loading.value = true;
const res = await getVisitList(queryParams.value, paging.value)
tableData.value = res.rows;
total.value = res.total;
loading.value = false;
};
//
const handleRest = (elForm) => {
elForm.resetFields()
queryParams.value.params.beginTime = '';
queryParams.value.params.endTime = '';
searchHandle()
}
//
const changeDateTimerHandle = (date) => {
if (date) {
queryParams.value = {
...queryParams.value,
params: {
beginTime: date[0],
endTime: date[1]
}
}
} else {
queryParams.value = {
...queryParams.value,
params: {
beginTime: '',
endTime: ''
}
}
}
getLists()
}
//
const searchHandle = async () => {
await getLists();
await aggrPieChange();
await aggrBarChange();
}
const aggrPieChange = async () => {
console.log(LatitudeAggregatorRef.value)
await LatitudeAggregatorRef.value.getData(deepClone(queryParams.value), aggrPie.value);
}
const aggrBarChange = async () => {
await CategorySummaryDimensionRef.value.getData(deepClone(queryParams.value), aggrBar.value);
}
</script>
<style scoped lang="scss">
.aggregate-wrapper {
width: 100%;
box-sizing: border-box;
.aggregate-item {
display: flex;
align-items: flex-start;
flex-direction: row;
flex-wrap: wrap;
.label {
margin-right: 10px;
}
}
}
</style>

View File

@ -63,7 +63,7 @@
</el-button> </el-button>
<el-button type="info" plain @click="uploadRef.showupload()"> <el-button type="info" plain @click="uploadRef.showupload()">
<el-icon class="me-2"> <el-icon class="me-2">
<Upload/> <IconUpload/>
</el-icon> </el-icon>
导入 导入
</el-button> </el-button>
@ -152,6 +152,7 @@ import DictTag from '@/components/DictTag/index.vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import Upload from '@/views/PoliceWork/RegisterVisit/components/upload.vue'; import Upload from '@/views/PoliceWork/RegisterVisit/components/upload.vue';
import Print from '@/views/PoliceWork/RegisterVisit/print.vue'; import Print from '@/views/PoliceWork/RegisterVisit/print.vue';
import {View,Upload as IconUpload} from '@element-plus/icons-vue';
const {proxy} = getCurrentInstance(); const {proxy} = getCurrentInstance();