工单统计
This commit is contained in:
@@ -135,7 +135,7 @@ export default {
|
||||
...mapState(['user']),
|
||||
colConfigs() {
|
||||
return [
|
||||
{prop: "rank", label: '排名', align: "center", width: "100px"},
|
||||
{prop: "rank", label: '排名', align: "center", width: "80px"},
|
||||
{prop: "roomName", label: '群名称', align: "center"},
|
||||
{prop: "ownerName", label: '群主', align: "center", width: "100px"},
|
||||
{prop: "c", label: '触发数', align: "center", width: "100px"},
|
||||
@@ -204,7 +204,7 @@ export default {
|
||||
}
|
||||
|
||||
this.info.ranking.map((item, index)=> {
|
||||
if(index < 6) {
|
||||
if(index < 5) {
|
||||
item.rank = index+1
|
||||
this.tableData.push(item)
|
||||
}
|
||||
|
||||
506
packages/xbot/AppWorkStatistics/AppWorkStatistics.vue
Normal file
506
packages/xbot/AppWorkStatistics/AppWorkStatistics.vue
Normal file
@@ -0,0 +1,506 @@
|
||||
<template>
|
||||
<section class="AppWorkStatistics">
|
||||
<ai-detail list>
|
||||
<ai-title slot="title" title="工单统计">
|
||||
<template #rightBtn>
|
||||
<el-row type="flex" align="middle">
|
||||
<el-cascader ref="cascader1" clearable v-model="totalDeptList" :options="deptOptions" placeholder="所属部门" size="small"
|
||||
:props="defaultProps" :show-all-levels="false" @visible-change="totalDeptSelect"></el-cascader>
|
||||
</el-row>
|
||||
</template>
|
||||
</ai-title>
|
||||
<template #content>
|
||||
<div class="card_list">
|
||||
<div class="card">
|
||||
<h2>累计上报</h2>
|
||||
<p class="color1">{{ totalInfo['累计上报'] || 0 }}</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>今日上报</h2>
|
||||
<p class="color1">{{ totalInfo['今日上报'] || 0 }}</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>办理中</h2>
|
||||
<p class="color1">{{ totalInfo['办理中'] || 0 }}</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>今日办结</h2>
|
||||
<p class="color1">{{ totalInfo['今日办结'] || 0 }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<ai-title slot="title" title="工单分析">
|
||||
<template #rightBtn>
|
||||
<el-row type="flex" align="middle">
|
||||
<span class="shortcut" v-for="(item,i) in timeCheck" :key="i" :class="{active:type==i}"
|
||||
@click="timeChange(i)" v-text="item"/>
|
||||
<el-cascader ref="cascader2" clearable v-model="deptList" :options="deptOptions" placeholder="所属部门" size="small"
|
||||
:props="defaultProps" :show-all-levels="false" @change="deptSelect"></el-cascader>
|
||||
</el-row>
|
||||
</template>
|
||||
</ai-title>
|
||||
<el-row type="flex" class="mar-t4 gap-20">
|
||||
<div class="chartBox fill">
|
||||
<b>事件办结率</b>
|
||||
<div>
|
||||
<div class="end-num" v-if="endData.length">{{endNum}}%</div>
|
||||
<div id="endChart" style="height: 260px; width: 100%;" v-if="endData.length"></div>
|
||||
<ai-empty v-else style="height: 200px; width: 100%;" id="empty"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chartBox fill">
|
||||
<b>工单上报趋势</b>
|
||||
<div>
|
||||
<div id="trendChart" style="height: 260px; width: 100%;" v-if="trendData.length"></div>
|
||||
<ai-empty v-else style="height: 200px; width: 100%;" id="empty"/>
|
||||
</div>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-row type="flex" class="mar-t4 gap-20">
|
||||
<div class="chartBox fill">
|
||||
<div class="title-flex">
|
||||
<b>工单分类</b>
|
||||
<ai-select v-model="eventStatus" :selectList="dict.getDict('xbotEventStatusSearch')" @change="statusChange"/>
|
||||
</div>
|
||||
<div>
|
||||
<div id="typeChart" style="height: 270px; width: 100%;" v-if="typeData.length"></div>
|
||||
<ai-empty v-else style="height: 200px; width: 100%;" id="empty"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chartBox fill">
|
||||
<b>工单排名</b>
|
||||
<div>
|
||||
<ai-table v-if="tableData.length"
|
||||
:tableData="tableData"
|
||||
:col-configs="colConfigs"
|
||||
:isShowPagination="false"
|
||||
style="margin-top: 6px; width: 100%;">
|
||||
</ai-table>
|
||||
<ai-empty v-else style="height: 200px; width: 100%;" id="empty"/>
|
||||
</div>
|
||||
</div>
|
||||
</el-row>
|
||||
<ai-dialog :visible.sync="dialogDate" title="选择时间" width="500px" customFooter>
|
||||
<el-date-picker v-model="timeList" size="small" type="daterange" value-format="yyyy-MM-dd"
|
||||
range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期">
|
||||
</el-date-picker>
|
||||
<el-button slot="footer" @click="selectDete" type="primary">确认</el-button>
|
||||
</ai-dialog>
|
||||
</template>
|
||||
</ai-detail>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from "vuex"
|
||||
import * as echarts from 'echarts';
|
||||
import AiDetail from "dui/packages/layout/AiDetail.vue";
|
||||
import AiTitle from "dui/packages/basic/AiTitle.vue";
|
||||
import "echarts-wordcloud";
|
||||
|
||||
export default {
|
||||
name: "AppWorkStatistics",
|
||||
components: {AiTitle, AiDetail},
|
||||
label: "工单统计",
|
||||
props: {
|
||||
instance: Function,
|
||||
dict: Object,
|
||||
permissions: Function,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultProps: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
checkStrictly: true,
|
||||
},
|
||||
deptOptions: [],
|
||||
info: {},
|
||||
trendData: [],
|
||||
trendChart: null,
|
||||
endData: [],
|
||||
endChart: null,
|
||||
typeData: [],
|
||||
typeChart: null,
|
||||
tableData: [],
|
||||
totalInfo: {},
|
||||
type: '2',
|
||||
timeCheck: ['昨日', '近7天', '近30天', '自定义'],
|
||||
dialog: false,
|
||||
dialogDate: false,
|
||||
timeList: [],
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
totalDeptList: [],
|
||||
deptList: [],
|
||||
time: [],
|
||||
endNum: 0,
|
||||
eventStatus: '',
|
||||
isTypeInit: false, //true分类请求不重新重置其它chart
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
colConfigs() {
|
||||
return [
|
||||
{prop: "rank", label: '排名', align: "center", width: "80px"},
|
||||
{prop: "roomName", label: '群名称', align: "center"},
|
||||
{prop: "ownerName", label: '群主', align: "center", width: "100px"},
|
||||
{prop: "c", label: '工单数', align: "center", width: "100px"},
|
||||
]
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$dict.load('xbotEventStatusSearch')
|
||||
this.getDeptList()
|
||||
this.getTotal()
|
||||
this.getStatistics()
|
||||
},
|
||||
methods: {
|
||||
getTotal() {
|
||||
var totalDepartmentId = this.totalDeptList.length ? this.totalDeptList[this.totalDeptList.length-1] : ''
|
||||
this.instance.post(`/app/appsessionarchivereportinfo/statistics1?departmentId=${totalDepartmentId}`).then(res => {
|
||||
if (res?.data) {
|
||||
console.log(res)
|
||||
this.totalInfo = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
getStatistics() {
|
||||
if(!this.isTypeInit) {
|
||||
this.trendData = [], this.endData = [], this.typeData = [], this.tableData = []
|
||||
}else {
|
||||
this.typeData = []
|
||||
}
|
||||
var departmentId = this.deptList.length ? this.deptList[this.deptList.length-1] : ''
|
||||
this.instance.post('/app/appsessionarchivereportinfo/statistics2', null, {
|
||||
params: {
|
||||
departmentId,
|
||||
type: this.type,
|
||||
startTime: this.startTime,
|
||||
endTime: this.endTime,
|
||||
eventStatus: this.eventStatus
|
||||
}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.info = res.data
|
||||
if(this.isTypeInit) { //只刷新分类
|
||||
this.info.groupList.map((item) => {
|
||||
var i = { name: item.groupName, value: item.totalNum };
|
||||
this.typeData.push(i);
|
||||
})
|
||||
if(this.typeData.length) {
|
||||
this.$nextTick(() => {
|
||||
this.typeChartInit(this.typeData)
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
var trendX = []
|
||||
this.info.trend.map((item) => {
|
||||
trendX.push(item.ymd)
|
||||
this.trendData.push(item.ecount)
|
||||
})
|
||||
if(this.trendData.length) {
|
||||
this.$nextTick(() => {
|
||||
this.trendChartInit(trendX, this.trendData)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Object.keys(res.data.allCountMap).forEach((key) => {
|
||||
var info = {
|
||||
name: key,
|
||||
value: res.data.allCountMap[key]
|
||||
}
|
||||
this.endData.push(info)
|
||||
})
|
||||
if(this.endData.length) {
|
||||
this.$nextTick(() => {
|
||||
this.endChartInit(this.endData)
|
||||
})
|
||||
}
|
||||
var num = res.data.allCountMap['累计办结']/res.data.allCountMap['累计上报']
|
||||
this.endNum = Number(num*100).toFixed(2)
|
||||
|
||||
|
||||
this.info.groupList.map((item) => {
|
||||
var i = { name: item.groupName, value: item.totalNum };
|
||||
this.typeData.push(i);
|
||||
})
|
||||
if(this.typeData.length) {
|
||||
this.$nextTick(() => {
|
||||
this.typeChartInit(this.typeData)
|
||||
})
|
||||
}
|
||||
|
||||
this.info.ranking.map((item, index)=> {
|
||||
if(index < 5) {
|
||||
item.rank = index+1
|
||||
this.tableData.push(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
trendChartInit(xData, yData) {
|
||||
this.trendChart = echarts.init(document.getElementById('trendChart'))
|
||||
let option = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xData
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
grid: {
|
||||
left: '10px',
|
||||
right: '28px',
|
||||
bottom: '14px',
|
||||
top: '30px',
|
||||
containLabel: true
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
type: "plain"
|
||||
},
|
||||
color: '#2891FF',
|
||||
series: [
|
||||
{
|
||||
data: yData,
|
||||
type: 'line'
|
||||
}
|
||||
]
|
||||
}
|
||||
this.trendChart.setOption(option)
|
||||
},
|
||||
endChartInit(data) {
|
||||
this.endChart = echarts.init(document.getElementById('endChart'))
|
||||
var option = {
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['60%', '90%'],
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data,
|
||||
}
|
||||
]
|
||||
}
|
||||
this.endChart.setOption(option)
|
||||
},
|
||||
typeChartInit(data) {
|
||||
this.typeChart = echarts.init(document.getElementById('typeChart'))
|
||||
var option = option = {
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
// legend: {
|
||||
// bottom: '0%',
|
||||
// left: 'center'
|
||||
// },
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: '76%',
|
||||
data,
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
this.typeChart.setOption(option)
|
||||
},
|
||||
getDeptList() {
|
||||
this.instance.post(`/app/wxcp/wxdepartment/listAll`).then((res) => {
|
||||
if (res.code == 0) {
|
||||
this.deptOptions = this.toTree(res.data)
|
||||
}
|
||||
})
|
||||
},
|
||||
toTree(data) {
|
||||
let result = [];
|
||||
if (!Array.isArray(data)) {
|
||||
return result
|
||||
}
|
||||
let map = {};
|
||||
data.forEach(item => {
|
||||
map[item.id] = item;
|
||||
});
|
||||
data.forEach(item => {
|
||||
let parent = map[item.parentid];
|
||||
if (parent) {
|
||||
(parent.children || (parent.children = [])).push(item);
|
||||
} else {
|
||||
result.push(item);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
statusChange() {
|
||||
this.isTypeInit = true
|
||||
this.getStatistics()
|
||||
},
|
||||
totalDeptSelect(val) {
|
||||
if(!val) {
|
||||
this.getTotal()
|
||||
}
|
||||
},
|
||||
deptSelect(val) {
|
||||
this.deptList = val
|
||||
this.isTypeInit = false
|
||||
this.getStatistics()
|
||||
},
|
||||
timeChange(index) {
|
||||
this.type = index
|
||||
if (index == 3) {
|
||||
this.dialogDate = true
|
||||
} else {
|
||||
this.startTime = ''
|
||||
this.endTime = ''
|
||||
this.isTypeInit = false
|
||||
this.getStatistics()
|
||||
}
|
||||
},
|
||||
selectDete() {
|
||||
if (!this.timeList || !this.timeList.length) {
|
||||
return this.$message.error('请选择自定义时间');
|
||||
}
|
||||
this.startTime = this.timeList?.[0]
|
||||
this.endTime = this.timeList?.[1]
|
||||
this.dialogDate = false
|
||||
this.isTypeInit = false
|
||||
this.getStatistics()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AppWorkStatistics {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.shortcut {
|
||||
display: inline-block;
|
||||
width: 70px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #D0D4DC;
|
||||
margin-right: 8px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
color: #2266FF;
|
||||
border: 1px solid #2266FF;
|
||||
}
|
||||
}
|
||||
|
||||
.chartBox {
|
||||
background: #F9F9F9;
|
||||
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.1500);
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
margin-top: 6px;
|
||||
position: relative;
|
||||
|
||||
.title-flex {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.end-num {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -10px 0 0 -70px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
width: 140px;
|
||||
text-align: center;
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.card_list {
|
||||
display: flex;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.card {
|
||||
flex: 1;
|
||||
height: 76px;
|
||||
background: #F9F9F9;
|
||||
border-radius: 2px;
|
||||
margin-right: 20px;
|
||||
padding: 8px 24px;
|
||||
box-sizing: border-box;
|
||||
|
||||
h2 {
|
||||
color: #888888;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 8px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.color1 {
|
||||
color: #2891FF;
|
||||
}
|
||||
|
||||
.color2 {
|
||||
color: #22AA99;
|
||||
}
|
||||
|
||||
.color3 {
|
||||
color: #F8B425;
|
||||
}
|
||||
}
|
||||
|
||||
.card:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
:deep( .el-dialog__footer ) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep( .el-dialog__header ) {
|
||||
border-bottom: 1px solid #DDD;
|
||||
}
|
||||
|
||||
:deep( .ai-detail ) {
|
||||
background: #FFF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user