| | |
| | | <template> |
| | | <el-calendar |
| | | v-loading="dayTaskLoading" |
| | | v-model="dateValue" |
| | | :range="dateRange" |
| | | @update:model-value="onDateChange" |
| | | > |
| | | <template #header="{ date }"> |
| | | <span>{{ title }}</span> |
| | | <el-space> |
| | | <el-tag>总计:{{ taskStatistic.total }}</el-tag> |
| | | <el-tag>完成:{{ taskStatistic.complete }}</el-tag> |
| | | <el-tag>整改:{{ taskStatistic.changed }}</el-tag> |
| | | </el-space> |
| | | <!-- <span>{{ date }}</span> --> |
| | | <div style="width: 100%"> |
| | | <el-row justify="space-between"> |
| | | <el-space> |
| | | <el-tag type="default" |
| | | >巡查量:{{ |
| | | `${taskStatistic.complete}/${taskStatistic.total}` |
| | | }}</el-tag |
| | | > |
| | | <el-tag type="default" |
| | | >综合整改率:{{ |
| | | formatPercent( |
| | | taskStatistic.changedProblemNum / |
| | | taskStatistic.totalProblemNum |
| | | ) |
| | | }}</el-tag |
| | | > |
| | | <!-- <el-tag type="default">整改:{{ taskStatistic.changed }}</el-tag> --> |
| | | </el-space> |
| | | <el-space> |
| | | <el-text>聚焦用户:</el-text> |
| | | <el-select |
| | | v-model="selectedUsers" |
| | | multiple |
| | | clearable |
| | | collapse-tags |
| | | :max-collapse-tags="1" |
| | | style="width: 150px" |
| | | > |
| | | <el-option |
| | | v-for="user in taskStatistic.progressPerUser" |
| | | :key="user.userId" |
| | | :label="user.userName" |
| | | :value="user.userName" |
| | | /> |
| | | </el-select> |
| | | </el-space> |
| | | </el-row> |
| | | <el-row class="m-t-4"> |
| | | <el-space wrap> |
| | | <el-tag |
| | | type="default" |
| | | v-for="user in taskStatistic.progressPerUser" |
| | | :key="user.userId" |
| | | > |
| | | <!-- {{ |
| | | `${user.userName}:巡查量 ${ |
| | | user.completeTaskNum |
| | | },即时整改率 ${formatPercent( |
| | | user.changedProblemNumOnTime / user.totalProblemNum |
| | | )},平均耗时 ${ |
| | | user.avgInspectionTime ? user.avgInspectionTime : '--' |
| | | }` |
| | | }} --> |
| | | {{ |
| | | `${user.userName}:${ |
| | | user.completeTaskNum |
| | | } | ${formatPercent( |
| | | user.changedProblemNumOnTime / user.totalProblemNum |
| | | )} | ${ |
| | | user.avgInspectionTime ? user.avgInspectionTime : '--' |
| | | }` |
| | | }} |
| | | </el-tag> |
| | | </el-space> |
| | | </el-row> |
| | | </div> |
| | | </template> |
| | | <template #date-cell="{ data }"> |
| | | <div :class="calendarDayClz(data.day)"> |
| | | <div style="background-color: #f8f4f4">{{ getDay(data.day) }}</div> |
| | | <template v-if="computeDayTask(data.day)"> |
| | | <!-- <el-divider></el-divider> --> |
| | | <el-row justify="space-between" class="m-t-16"> |
| | | <el-row |
| | | v-if="computeDayTask(data.day).totalTaskNum > 0" |
| | | justify="space-between" |
| | | > |
| | | <el-space direction="vertical"> |
| | | <el-text size="small">总计</el-text> |
| | | <el-text>{{computeDayTask(data.day).totalTaskNum}}</el-text> |
| | | <el-text size="small" tag="b">巡查量</el-text> |
| | | <el-text size="small" |
| | | >{{ computeDayTask(data.day).completeTaskNum }} / |
| | | {{ computeDayTask(data.day).totalTaskNum }}</el-text |
| | | > |
| | | </el-space> |
| | | <el-space direction="vertical"> |
| | | <el-text size="small">完成</el-text> |
| | | <el-text>{{computeDayTask(data.day).completeTaskNum}}</el-text> |
| | | <el-text size="small" tag="b">综合整改率</el-text> |
| | | <el-text size="small">{{ |
| | | formatPercent( |
| | | computeDayTask(data.day).changedProblemNum / |
| | | computeDayTask(data.day).totalProblemNum |
| | | ) |
| | | }}</el-text> |
| | | </el-space> |
| | | <el-space direction="vertical"> |
| | | <el-text size="small">整改</el-text> |
| | | <el-text>{{computeDayTask(data.day).changedTaskNum}}</el-text> |
| | | </el-space> |
| | | <!-- <el-statistic title="总计" :value="computeDayTask(data.day).totalTaskNum" /> |
| | | <el-statistic title="完成" :value="computeDayTask(data.day).completeTaskNum" /> |
| | | <el-statistic title="整改" :value="computeDayTask(data.day).changedTaskNum" /> --> |
| | | </el-row> |
| | | <!-- <div>任务总计:{{ computeDayTask(data.day).totalTaskNum }}</div> |
| | | <div>任务完成:{{ computeDayTask(data.day).completeTaskNum }}</div> |
| | | <div>任务整改:{{ computeDayTask(data.day).changedTaskNum }}</div> --> |
| | | <div style="border-top: 1px solid #0000002f" class="m-t-4"></div> |
| | | <div |
| | | v-for="item in computeDayTask(data.day).progressPerUser" |
| | | :key="item.userId" |
| | | class="m-t-4" |
| | | > |
| | | <el-row justify="space-between"> |
| | | <el-text |
| | | title="巡查人员" |
| | | size="small" |
| | | :type=" |
| | | selectedUsers.includes(item.userName) ? 'primary' : 'info' |
| | | " |
| | | :tag="selectedUsers.includes(item.userName) ? 'b' : 'span'" |
| | | >{{ item.userName }}</el-text |
| | | > |
| | | </el-row> |
| | | <el-row justify="space-between"> |
| | | <!-- <el-space> --> |
| | | <el-text |
| | | title="巡查量" |
| | | size="small" |
| | | style="text-align: center; flex: 1" |
| | | :type=" |
| | | selectedUsers.includes(item.userName) ? 'primary' : 'info' |
| | | " |
| | | :tag="selectedUsers.includes(item.userName) ? 'b' : 'span'" |
| | | >{{ item.completeTaskNum }}</el-text |
| | | > |
| | | <el-text |
| | | title="即时整改率" |
| | | size="small" |
| | | style="text-align: center; flex: 1" |
| | | :type=" |
| | | selectedUsers.includes(item.userName) ? 'primary' : 'info' |
| | | " |
| | | :tag="selectedUsers.includes(item.userName) ? 'b' : 'span'" |
| | | >{{ |
| | | formatPercent( |
| | | item.changedProblemNumOnTime / item.totalProblemNum |
| | | ) |
| | | }}</el-text |
| | | > |
| | | <el-text |
| | | title="平均耗时" |
| | | size="small" |
| | | style="text-align: center; flex: 1" |
| | | :type=" |
| | | selectedUsers.includes(item.userName) ? 'primary' : 'info' |
| | | " |
| | | :tag="selectedUsers.includes(item.userName) ? 'b' : 'span'" |
| | | >{{ |
| | | timeUtil.formatSecondsToChinese(item.avgInspectionTime) |
| | | }}</el-text |
| | | > |
| | | <!-- </el-space> --> |
| | | </el-row> |
| | | </div> |
| | | </template> |
| | | </div> |
| | | </template> |
| | |
| | | import { ref, computed, onMounted, watch } from 'vue'; |
| | | import taskApi from '@/api/fysp/taskApi'; |
| | | import dayjs from 'dayjs'; |
| | | import timeUtil from '@/utils/time-util'; |
| | | |
| | | const props = defineProps({ |
| | | task: { |
| | | type: Object, |
| | | default: () => {} |
| | | }, |
| | | dayTaskList: { |
| | | type: Array, |
| | | default: () => [] |
| | | } |
| | | }); |
| | | const emit = defineEmits(['dateChange']) |
| | | const emit = defineEmits(['dateChange']); |
| | | // 选中日期 |
| | | const dateValue = ref(new Date()); |
| | | // 日历标题 |
| | | const title = computed(() => { |
| | | if (props.task) { |
| | | // return `${props.task.name}计划`; |
| | | return `巡查计划`; |
| | | } else { |
| | | return ''; |
| | | } |
| | | }); |
| | | const selectedUsers = ref([]); |
| | | // const dateValue = ref(new Date()); |
| | | const dateValue = ref(); |
| | | // 日历范围 |
| | | const startDay = computed(() => dayjs(props.task.starttime)); |
| | | const endDay = computed(() => dayjs(props.task.endtime)); |
| | | const dateRange = computed(() => [startDay.value.toDate(), endDay.value.toDate()]); |
| | | // const dateRange = computed(() => [new Date(2024, 4, 27), new Date(2024, 5, 30)]); |
| | | const dateRange = computed(() => [ |
| | | startDay.value.toDate(), |
| | | endDay.value.toDate() |
| | | ]); |
| | | |
| | | // 日期是否在任务范围内 |
| | | function isDayEnable(day) { |
| | | const _day = dayjs(day); |
| | | return _day.isSameOrAfter(startDay.value, 'day') && _day.isSameOrBefore(endDay.value, 'day'); |
| | | return ( |
| | | _day.isSameOrAfter(startDay.value, 'day') && |
| | | _day.isSameOrBefore(endDay.value, 'day') |
| | | ); |
| | | } |
| | | |
| | | /********************** 日期样式 *********************************/ |
| | | function calendarDayClz(day) { |
| | | return 'calendar-day ' + (isDayEnable(day) ? 'calendar-day-enable' : 'calendar-day-disable'); |
| | | return ( |
| | | 'calendar-day ' + |
| | | (isDayEnable(day) ? 'calendar-day-enable' : 'calendar-day-disable') |
| | | ); |
| | | } |
| | | function getDay(day) { |
| | | return day.split('-').splice(1, 2).join('-'); |
| | |
| | | |
| | | /********************** 任务数据 *********************************/ |
| | | |
| | | // 获取日任务统计信息 |
| | | const dayTaskLoading = ref(false); |
| | | const dayTaskList = ref([]); |
| | | function fetchDayTasks(topTaskId) { |
| | | dayTaskLoading.value = true; |
| | | taskApi |
| | | .fetchDayTasks(topTaskId) |
| | | .then((res) => { |
| | | dayTaskList.value = res; |
| | | }) |
| | | .finally(() => (dayTaskLoading.value = false)); |
| | | } |
| | | watch( |
| | | () => props.dayTaskList, |
| | | (nV, oV) => { |
| | | if (nV && dateValue.value) { |
| | | onDateChange(dateValue.value); |
| | | } |
| | | }, |
| | | { immediate: false } |
| | | ); |
| | | |
| | | // // 获取日任务统计信息 |
| | | // const dayTaskLoading = ref(false); |
| | | // const dayTaskList = ref([]); |
| | | // function fetchDayTasks() { |
| | | // dayTaskLoading.value = true; |
| | | // return taskApi |
| | | // .fetchDayTasks(props.task.tguid) |
| | | // .then((res) => { |
| | | // dayTaskList.value = res; |
| | | // // 如果已选日期存在,在重新获取日任务统计信息后,再次触发点击事件 |
| | | // if (dateValue.value) { |
| | | // onDateChange(dateValue.value); |
| | | // } |
| | | // }) |
| | | // .finally(() => (dayTaskLoading.value = false)); |
| | | // } |
| | | |
| | | // 日任务数据展示 |
| | | const compMap = new Map(); |
| | |
| | | return compMap.get(key).value; |
| | | } |
| | | const result = computed(() => { |
| | | return dayTaskList.value.find((v) => { |
| | | return props.dayTaskList.find((v) => { |
| | | return dayjs(v.date).isSame(dayjs(day)); |
| | | }); |
| | | }); |
| | |
| | | } |
| | | |
| | | function onDateChange(e) { |
| | | const day = dayjs(e).format('YYYY-MM-DD') |
| | | const t = computeDayTask(day) |
| | | console.log(t); |
| | | |
| | | emit('dateChange', t) |
| | | if (isDayEnable(e)) { |
| | | const day = dayjs(e).format('YYYY-MM-DD'); |
| | | const t = computeDayTask(day); |
| | | emit('dateChange', t, day); |
| | | } |
| | | } |
| | | |
| | | // 总任务统计 |
| | | const taskStatistic = computed(() => { |
| | | const res = { total: 0, complete: 0, changed: 0 }; |
| | | dayTaskList.value.forEach((e) => { |
| | | // const resMap = new Map() |
| | | const res = { |
| | | total: 0, |
| | | complete: 0, |
| | | changed: 0, |
| | | changedProblemNum: 0, |
| | | totalProblemNum: 0 |
| | | }; |
| | | const userMap = new Map(); |
| | | props.dayTaskList.forEach((e) => { |
| | | res.total += e.totalTaskNum; |
| | | res.complete += e.completeTaskNum; |
| | | res.changed += e.changedTaskNum; |
| | | res.changedProblemNum += e.changedProblemNum; |
| | | res.totalProblemNum += e.totalProblemNum; |
| | | e.progressPerUser.forEach((user) => { |
| | | if (!userMap.has(user.userId)) { |
| | | userMap.set(user.userId, { |
| | | userName: user.userName, |
| | | completeTaskNum: 0, |
| | | changedProblemNumOnTime: 0, |
| | | totalProblemNum: 0, |
| | | totalInspectionTime: 0, |
| | | dayTaskNum: 0 |
| | | }); |
| | | } |
| | | const userItem = userMap.get(user.userId); |
| | | userItem.completeTaskNum += user.completeTaskNum; |
| | | userItem.changedProblemNumOnTime += user.changedProblemNumOnTime; |
| | | userItem.totalProblemNum += user.totalProblemNum; |
| | | userItem.totalInspectionTime += user.avgInspectionTime; |
| | | userItem.dayTaskNum++; |
| | | }); |
| | | }); |
| | | res.progressPerUser = Array.from(userMap.values()).map((user) => ({ |
| | | ...user, |
| | | completeTaskNum: Math.round(user.completeTaskNum * 100) / 100, |
| | | avgInspectionTime: timeUtil.formatSecondsToChinese( |
| | | user.totalInspectionTime / user.dayTaskNum |
| | | ) |
| | | })); |
| | | return res; |
| | | }); |
| | | |
| | | const formatPercent = (num) => { |
| | | return isNaN(num) ? '0%' : parseInt(num * 100) + '%'; |
| | | }; |
| | | /********************** 初始化 *********************************/ |
| | | |
| | | watch( |
| | | () => props.task, |
| | | (nV) => { |
| | | if (nV.tguid) { |
| | | fetchDayTasks(nV.tguid); |
| | | } |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | // watch( |
| | | // () => props.task, |
| | | // (nV) => { |
| | | // if (nV && nV.tguid) { |
| | | // fetchDayTasks(); |
| | | // } |
| | | // }, |
| | | // { immediate: true } |
| | | // ); |
| | | |
| | | // defineExpose({ fetchDayTasks }); |
| | | </script> |
| | | <style scoped> |
| | | .li-01 { |
| | |
| | | top: 5px; |
| | | } |
| | | |
| | | ::v-deep .el-calendar-table .el-calendar-day { |
| | | :deep(.el-calendar-table .el-calendar-day) { |
| | | height: initial; |
| | | padding: initial; |
| | | } |
| | | |
| | | ::v-deep .el-calendar-table .el-calendar-day:hover { |
| | | :deep(.el-calendar-table .el-calendar-day:hover) { |
| | | background-color: transparent; |
| | | } |
| | | |
| | | /* ::v-deep .el-calendar-table td.is-selected { |
| | | /* :deep(.el-calendar-table td.is-selected) { |
| | | background-color: initial; |
| | | } */ |
| | | |
| | |
| | | border: 1px solid rgb(172, 165, 165); |
| | | border-radius: 50%; */ |
| | | padding: 8px; |
| | | height: var(--el-calendar-cell-width); |
| | | /* min-height: var(--el-calendar-cell-width); */ |
| | | } |
| | | |
| | | .calendar-day-enable { |
| | |
| | | cursor: not-allowed; |
| | | } |
| | | |
| | | /* ::v-deep .el-calendar-table tr td:first-child { |
| | | /* :deep(.el-calendar-table tr td:first-child) { |
| | | border-left: none !important; |
| | | } */ |
| | | |
| | | /* ::v-deep .el-calendar-table tr:first-child td { |
| | | /* :deep(.el-calendar-table tr:first-child td) { |
| | | border-top: none; |
| | | } */ |
| | | |
| | | /* ::v-deep .el-calendar-table td { |
| | | /* :deep(.el-calendar-table td) { |
| | | border-bottom: none; |
| | | border-right: none; |
| | | vertical-align: top; |
| | |
| | | transition: background-color 0.2s ease; |
| | | } */ |
| | | |
| | | ::v-deep .el-calendar-table thead th { |
| | | :deep(.el-calendar-table thead th) { |
| | | padding: 12px 0; |
| | | color: #606266; |
| | | font-weight: normal; |