|
@@ -17,7 +17,11 @@
|
|
|
<div class="col1">
|
|
|
<div class="card">
|
|
|
<div class="title">
|
|
|
- <t-select v-model="sort1" style="width: calc(100% - 50px)">
|
|
|
+ <t-select
|
|
|
+ v-model="sort1"
|
|
|
+ style="width: calc(100% - 50px)"
|
|
|
+ @change="changeSort('CRM', 1)"
|
|
|
+ >
|
|
|
<t-option
|
|
|
value="PENDING"
|
|
|
label="项目预警待处理TOP10"
|
|
@@ -33,12 +37,19 @@
|
|
|
</t-select>
|
|
|
</div>
|
|
|
<div class="chart-wrap">
|
|
|
- <table-loop :data="tableDataHandle(result1)"></table-loop>
|
|
|
+ <table-loop
|
|
|
+ :data="tableDataHandle(result1)"
|
|
|
+ :columns="tableColumns1"
|
|
|
+ ></table-loop>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="card">
|
|
|
<div class="title">
|
|
|
- <t-select v-model="sort2" style="width: calc(100% - 50px)">
|
|
|
+ <t-select
|
|
|
+ v-model="sort2"
|
|
|
+ style="width: calc(100% - 50px)"
|
|
|
+ @change="changeSort('REGION', 2)"
|
|
|
+ >
|
|
|
<t-option
|
|
|
value="PENDING"
|
|
|
label="大区预警待处理TOP10"
|
|
@@ -53,10 +64,20 @@
|
|
|
></t-option>
|
|
|
</t-select>
|
|
|
</div>
|
|
|
+ <div class="chart-wrap">
|
|
|
+ <table-loop
|
|
|
+ :data="tableDataHandle(result2)"
|
|
|
+ :columns="tableColumns2"
|
|
|
+ ></table-loop>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
<div class="card">
|
|
|
<div class="title">
|
|
|
- <t-select v-model="sort3" style="width: calc(100% - 50px)">
|
|
|
+ <t-select
|
|
|
+ v-model="sort3"
|
|
|
+ style="width: calc(100% - 50px)"
|
|
|
+ @change="changeSort('SUPPLIER', 3)"
|
|
|
+ >
|
|
|
<t-option
|
|
|
value="PENDING"
|
|
|
label="供应商预警待处理TOP10"
|
|
@@ -71,16 +92,144 @@
|
|
|
></t-option>
|
|
|
</t-select>
|
|
|
</div>
|
|
|
+ <div class="chart-wrap">
|
|
|
+ <table-loop
|
|
|
+ :data="tableDataHandle(result3)"
|
|
|
+ :columns="tableColumns3"
|
|
|
+ ></table-loop>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="col2">
|
|
|
- <div class="card"></div>
|
|
|
+ <div class="card">
|
|
|
+ <div class="tab-box">
|
|
|
+ <div
|
|
|
+ class="tab"
|
|
|
+ @click="centerGroup = 'REGION'"
|
|
|
+ :class="{ active: centerGroup === 'REGION' }"
|
|
|
+ >按大区</div
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="tab"
|
|
|
+ @click="centerGroup = 'SUPPLIER'"
|
|
|
+ :class="{ active: centerGroup === 'SUPPLIER' }"
|
|
|
+ >按人力供应商</div
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div class="clear-both"></div>
|
|
|
+ <div class="list-wrap">
|
|
|
+ <div
|
|
|
+ class="list-item"
|
|
|
+ v-for="(item, index) in result7"
|
|
|
+ :key="index"
|
|
|
+ >
|
|
|
+ <div class="item-head"> {{ item.region_name }} </div>
|
|
|
+ <div class="item-body">
|
|
|
+ <div class="row1">
|
|
|
+ <div class="grid-item">
|
|
|
+ <div class="label">派单数</div>
|
|
|
+ <p>{{ item.crmNum }}</p>
|
|
|
+ </div>
|
|
|
+ <div class="grid-item">
|
|
|
+ <div class="label">完成进度(%)</div>
|
|
|
+ <p>{{ division(item.finishCrmNum, item.crmNum) }}</p>
|
|
|
+ </div>
|
|
|
+ <div class="grid-item">
|
|
|
+ <div class="label">平均处理时限(小时)</div>
|
|
|
+ <p class="red">{{ (item.avgMinutes / 60).toFixed(2) }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="row2">
|
|
|
+ <div class="label">预警处理进度</div>
|
|
|
+ <div class="process-box">
|
|
|
+ <div class="grid-item">
|
|
|
+ <span>总体</span>
|
|
|
+ <div class="bar-box">
|
|
|
+ <div
|
|
|
+ class="bar"
|
|
|
+ :style="{
|
|
|
+ width:
|
|
|
+ division(
|
|
|
+ item.finishViolationNum + item.finishDelayNum,
|
|
|
+ item.violationNum + item.delayNum
|
|
|
+ ) + '%',
|
|
|
+ }"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+ <span>{{ item.violationNum + item.delayNum }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="grid-item">
|
|
|
+ <span>违规</span>
|
|
|
+ <div class="bar-box">
|
|
|
+ <div
|
|
|
+ class="bar"
|
|
|
+ :style="{
|
|
|
+ width:
|
|
|
+ division(
|
|
|
+ item.finishViolationNum,
|
|
|
+ item.violationNum
|
|
|
+ ) + '%',
|
|
|
+ }"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+ <span>{{ item.violationNum }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="grid-item">
|
|
|
+ <span>延期</span>
|
|
|
+ <div class="bar-box">
|
|
|
+ <div
|
|
|
+ class="bar"
|
|
|
+ :style="{
|
|
|
+ width:
|
|
|
+ division(item.finishDelayNum, item.delayNum) +
|
|
|
+ '%',
|
|
|
+ }"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+ <span>{{ item.delayNum }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
<div class="card"></div>
|
|
|
</div>
|
|
|
<div class="col3">
|
|
|
- <div class="card"></div>
|
|
|
- <div class="card"></div>
|
|
|
- <div class="card"></div>
|
|
|
+ <div class="card">
|
|
|
+ <div class="title">
|
|
|
+ <span>项目考勤异常TOP10</span>
|
|
|
+ </div>
|
|
|
+ <div class="chart-wrap">
|
|
|
+ <table-loop
|
|
|
+ :data="tableDataHandle(result4)"
|
|
|
+ :columns="tableColumns4"
|
|
|
+ ></table-loop>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="card">
|
|
|
+ <div class="title">
|
|
|
+ <span>项目考勤异常TOP10</span>
|
|
|
+ </div>
|
|
|
+ <div class="chart-wrap">
|
|
|
+ <table-loop
|
|
|
+ :data="tableDataHandle(result5)"
|
|
|
+ :columns="tableColumns5"
|
|
|
+ ></table-loop>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="card">
|
|
|
+ <div class="title">
|
|
|
+ <span>供应商考勤异常TOP5</span>
|
|
|
+ </div>
|
|
|
+ <div class="chart-wrap">
|
|
|
+ <table-loop
|
|
|
+ :data="tableDataHandle(result6)"
|
|
|
+ :columns="tableColumns6"
|
|
|
+ ></table-loop>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -88,11 +237,17 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup name="DispatchAnalysis">
|
|
|
-import { ref, computed, watch } from 'vue';
|
|
|
-import SelectServiceUnit from '@/components/common/select-service-unit/index.vue';
|
|
|
+import { ref, computed, watch, onMounted } from 'vue';
|
|
|
import TableLoop from '@/components/common/table-loop/index.vue';
|
|
|
import { useRequest } from 'vue-request';
|
|
|
-import { sopServiceList, warningTop } from '@/api/report';
|
|
|
+import { division } from '@/utils/tool';
|
|
|
+import {
|
|
|
+ sopServiceList,
|
|
|
+ warningTop,
|
|
|
+ attendanceTop,
|
|
|
+ supWarningTrend,
|
|
|
+ sopWarningAnalysis,
|
|
|
+} from '@/api/report';
|
|
|
const curTimeRange = ref([]);
|
|
|
const serviceOptions = ref([]);
|
|
|
|
|
@@ -103,6 +258,7 @@ const timeParams = computed(() => {
|
|
|
};
|
|
|
});
|
|
|
const serviceId = ref('');
|
|
|
+const centerGroup = ref('REGION');
|
|
|
const timeChange = (time) => {
|
|
|
sopServiceList(timeParams.value).then((res) => {
|
|
|
serviceOptions.value = res || [];
|
|
@@ -113,10 +269,140 @@ const sort1 = ref('PENDING');
|
|
|
const sort2 = ref('PENDING');
|
|
|
const sort3 = ref('PENDING');
|
|
|
|
|
|
-const { data: result1, loading: loading1, run: run1 } = useRequest(warningTop); //服务单元概览
|
|
|
+const { data: result1, loading: loading1, run: run1 } = useRequest(warningTop);
|
|
|
+const { data: result2, loading: loading2, run: run2 } = useRequest(warningTop);
|
|
|
+const { data: result3, loading: loading3, run: run3 } = useRequest(warningTop);
|
|
|
+const {
|
|
|
+ data: result4,
|
|
|
+ loading: loading4,
|
|
|
+ run: run4,
|
|
|
+} = useRequest(attendanceTop);
|
|
|
+const {
|
|
|
+ data: result5,
|
|
|
+ loading: loading5,
|
|
|
+ run: run5,
|
|
|
+} = useRequest(attendanceTop);
|
|
|
+const {
|
|
|
+ data: result6,
|
|
|
+ loading: loading6,
|
|
|
+ run: run6,
|
|
|
+} = useRequest(attendanceTop);
|
|
|
+const {
|
|
|
+ data: result7,
|
|
|
+ loading: loading7,
|
|
|
+ run: run7,
|
|
|
+} = useRequest(sopWarningAnalysis);
|
|
|
+const {
|
|
|
+ data: result8,
|
|
|
+ loading: loading8,
|
|
|
+ run: run8,
|
|
|
+} = useRequest(supWarningTrend);
|
|
|
const tableDataHandle = (data) => {
|
|
|
return data || [];
|
|
|
};
|
|
|
+const tableColumns1 = [
|
|
|
+ {
|
|
|
+ prop: 'a',
|
|
|
+ label: '客户名称',
|
|
|
+ style: { width: '60px' },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'b',
|
|
|
+ label: '大区',
|
|
|
+ style: { width: '70px' },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'c',
|
|
|
+ label: '区域协调人',
|
|
|
+ style: { width: '70px' },
|
|
|
+ },
|
|
|
+ { prop: 'd', label: '预警数', style: { width: '60px' } },
|
|
|
+];
|
|
|
+const tableColumns2 = [
|
|
|
+ {
|
|
|
+ prop: 'a',
|
|
|
+ label: '大区',
|
|
|
+ style: { width: '60px' },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'b',
|
|
|
+ label: '大区经理',
|
|
|
+ style: { width: '70px' },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'c',
|
|
|
+ label: '预警数',
|
|
|
+ style: { width: '70px' },
|
|
|
+ },
|
|
|
+ { prop: 'd', label: '均值', style: { width: '60px' } },
|
|
|
+];
|
|
|
+
|
|
|
+const tableColumns3 = [
|
|
|
+ {
|
|
|
+ prop: 'a',
|
|
|
+ label: '供应商',
|
|
|
+ style: { width: '60px' },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'b',
|
|
|
+ label: '大区',
|
|
|
+ style: { width: '70px' },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'c',
|
|
|
+ label: '区域协调人',
|
|
|
+ style: { width: '70px' },
|
|
|
+ },
|
|
|
+ { prop: 'd', label: '预警数', style: { width: '70px' } },
|
|
|
+ { prop: 'e', label: '均值', style: { width: '60px' } },
|
|
|
+];
|
|
|
+
|
|
|
+const tableColumns4 = [
|
|
|
+ {
|
|
|
+ prop: 'a',
|
|
|
+ label: '客户名称',
|
|
|
+ style: { width: '60px' },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'b',
|
|
|
+ label: '考勤对象',
|
|
|
+ style: { width: '70px' },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'c',
|
|
|
+ label: '区域协调人',
|
|
|
+ style: { width: '70px' },
|
|
|
+ },
|
|
|
+ { prop: 'd', label: '异常数', style: { width: '60px' } },
|
|
|
+];
|
|
|
+const tableColumns5 = [
|
|
|
+ {
|
|
|
+ prop: 'a',
|
|
|
+ label: '大区',
|
|
|
+ style: { width: '60px' },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'b',
|
|
|
+ label: '大区经理',
|
|
|
+ style: { width: '70px' },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'c',
|
|
|
+ label: '异常数',
|
|
|
+ style: { width: '70px' },
|
|
|
+ },
|
|
|
+ { prop: 'd', label: '均值', style: { width: '60px' } },
|
|
|
+];
|
|
|
+
|
|
|
+const tableColumns6 = [
|
|
|
+ {
|
|
|
+ prop: 'a',
|
|
|
+ label: '供应商',
|
|
|
+ style: { width: '80px' },
|
|
|
+ },
|
|
|
+ { prop: 'd', label: '异常数', style: { width: '70px' } },
|
|
|
+ { prop: 'e', label: '均值', style: { width: '60px' } },
|
|
|
+];
|
|
|
watch(serviceId, (serviceId) => {
|
|
|
sort1.value = sort2.value = sort3.value = 'PENDING';
|
|
|
run1({
|
|
@@ -124,17 +410,53 @@ watch(serviceId, (serviceId) => {
|
|
|
serviceId,
|
|
|
sort: sort1.value,
|
|
|
});
|
|
|
- run1({
|
|
|
+ run2({
|
|
|
group: 'REGION',
|
|
|
serviceId,
|
|
|
- sort: sort1.value,
|
|
|
+ sort: sort2.value,
|
|
|
});
|
|
|
- run1({
|
|
|
+ run3({
|
|
|
group: 'SUPPLIER',
|
|
|
serviceId,
|
|
|
- sort: sort1.value,
|
|
|
+ sort: sort3.value,
|
|
|
});
|
|
|
+ run4({
|
|
|
+ group: 'CRM',
|
|
|
+ serviceId,
|
|
|
+ ...timeParams.value,
|
|
|
+ });
|
|
|
+ run5({
|
|
|
+ group: 'REGION',
|
|
|
+ serviceId,
|
|
|
+ ...timeParams.value,
|
|
|
+ });
|
|
|
+ run6({
|
|
|
+ group: 'SUPPLIER',
|
|
|
+ serviceId,
|
|
|
+ ...timeParams.value,
|
|
|
+ });
|
|
|
+ run7({
|
|
|
+ group: centerGroup.value,
|
|
|
+ serviceId,
|
|
|
+ });
|
|
|
+ run8({ serviceId });
|
|
|
});
|
|
|
+watch(centerGroup, () => {
|
|
|
+ run7({
|
|
|
+ group: centerGroup.value,
|
|
|
+ serviceId,
|
|
|
+ });
|
|
|
+});
|
|
|
+const changeSort = (group, index) => {
|
|
|
+ const sort =
|
|
|
+ index == 1 ? sort1.value : index == 2 ? sort2.value : sort3.value;
|
|
|
+ const run = index == 1 ? run1 : index == 2 ? run2 : run3;
|
|
|
+ run({
|
|
|
+ group,
|
|
|
+ serviceId,
|
|
|
+ sort,
|
|
|
+ });
|
|
|
+};
|
|
|
</script>
|
|
|
|
|
|
<style lang="less" scoped>
|
|
@@ -180,6 +502,97 @@ watch(serviceId, (serviceId) => {
|
|
|
.card {
|
|
|
&:first-child {
|
|
|
height: calc((100% - 30px) * 2 / 3 + 15px);
|
|
|
+ .list-wrap {
|
|
|
+ height: calc(100% - 42px);
|
|
|
+ overflow: auto;
|
|
|
+ margin-top: 10px;
|
|
|
+ .list-item {
|
|
|
+ .item-head {
|
|
|
+ height: 32px;
|
|
|
+ background: #f7f7f7;
|
|
|
+ padding: 0 10px;
|
|
|
+ line-height: 32px;
|
|
|
+ color: #262626;
|
|
|
+ }
|
|
|
+ .item-body {
|
|
|
+ padding: 4px 8px;
|
|
|
+ .label {
|
|
|
+ color: #8c8c8c;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+ .row2 {
|
|
|
+ margin-top: 5px;
|
|
|
+ .process-box {
|
|
|
+ display: flex;
|
|
|
+ .grid-item {
|
|
|
+ width: 33.33%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ span {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #262626;
|
|
|
+ &:last-child {
|
|
|
+ padding-right: 20px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .bar-box {
|
|
|
+ flex: 1;
|
|
|
+ max-width: 100px;
|
|
|
+ margin: 0 10px;
|
|
|
+ height: 6px;
|
|
|
+ border-radius: 3px;
|
|
|
+ background: #d9d9d9;
|
|
|
+ .bar {
|
|
|
+ background: #4080ff;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .row1 {
|
|
|
+ display: flex;
|
|
|
+ .grid-item {
|
|
|
+ width: 33.33%;
|
|
|
+ p {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #165dff;
|
|
|
+ &.red {
|
|
|
+ color: #f53f3f;
|
|
|
+ }
|
|
|
+ &.green {
|
|
|
+ color: #00b42a;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .tab-box {
|
|
|
+ height: 32px;
|
|
|
+ padding: 5px 8px;
|
|
|
+ background: #f0f0f0;
|
|
|
+ border-radius: 3px;
|
|
|
+ float: left;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ .tab {
|
|
|
+ height: 22px;
|
|
|
+ padding: 0 6px;
|
|
|
+ line-height: 22px;
|
|
|
+ color: #8c8c8c;
|
|
|
+ cursor: pointer;
|
|
|
+ &.active {
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 2px;
|
|
|
+ }
|
|
|
+ &:last-child {
|
|
|
+ margin-left: 5px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
&:last-child {
|
|
|
height: calc((100% - 30px) / 3);
|