Переглянути джерело

feat(report): 新增维修工时统计功能

- 在 application-form.js 中新增维修工时统计相关API接口
- 包括按维修人、按设备分页统计及明细查询接口
- 添加维修工时统计导出功能接口
- 在 BaseForm.vue 中添加执行班组选择功能
- 新增维修工时统计报表页面 RepairWorkHour.vue
- 实现按维修人和按设备两种统计维度切换
- 添加维修工时统计明细弹窗组件 DetailModal
- 集成部门树形选择器用于班组选择
- 实现维修工时数据导出功能
dazhaxie 18 годин тому
батько
коміт
1ebfe9bf86

+ 96 - 0
src/api/report/application-form.js

@@ -216,3 +216,99 @@ export function exportMonthReportMttrMonth (parameter) {
     responseType: 'blob'
   })
 }
+
+/**
+ * 维修工时统计:按维修人分页统计
+ * parameter: { repairUserName, searchRepairStartTime, searchRepairEndTime, pageNum, pageSize }
+ * @param parameter
+ * @returns {*}
+ */
+export function getWorkHourByUser (parameter) {
+  return axios({
+    url: '/report/repair/workhour/user?' + stringify(parameter),
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8'
+    }
+  })
+}
+
+/**
+ * 维修工时统计:按设备分页统计
+ * parameter: { keyword, searchRepairStartTime, searchRepairEndTime, pageNum, pageSize }
+ * @param parameter
+ * @returns {*}
+ */
+export function getWorkHourBySb (parameter) {
+  return axios({
+    url: '/report/repair/workhour/sb?' + stringify(parameter),
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8'
+    }
+  })
+}
+
+/**
+ * 维修工时统计明细:某维修人或某设备已完成维修的报修单列表
+ * parameter: { repairUserId, sbId, searchRepairStartTime, searchRepairEndTime }
+ * @param parameter
+ * @returns {*}
+ */
+export function getWorkHourDetail (parameter) {
+  return axios({
+    url: '/report/repair/workhour/detail?' + stringify(parameter),
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8'
+    }
+  })
+}
+
+/**
+ * 维修工时统计导出:按维修人
+ * @param parameter
+ * @returns {*}
+ */
+export function exportWorkHourUser (parameter) {
+  return axios({
+    url: '/report/repair/workhour/export/user?' + stringify(parameter),
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8'
+    },
+    responseType: 'blob'
+  })
+}
+
+/**
+ * 维修工时统计导出:按设备
+ * @param parameter
+ * @returns {*}
+ */
+export function exportWorkHourSb (parameter) {
+  return axios({
+    url: '/report/repair/workhour/export/sb?' + stringify(parameter),
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8'
+    },
+    responseType: 'blob'
+  })
+}
+
+/**
+ * 维修工时统计明细导出
+ * @param parameter
+ * @returns {*}
+ */
+export function exportWorkHourDetail (parameter) {
+  return axios({
+    url: '/report/repair/workhour/export/detail?' + stringify(parameter),
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8'
+    },
+    responseType: 'blob'
+  })
+}

+ 1 - 0
src/router/generator-platform-routers.js

@@ -341,6 +341,7 @@ const constantRouterComponents = {
   'RepairReportSbInfoFee': () => import('@/views/dashboard/RepairReportSbInfoFee'), // 设备维修费用报表
   'RepairReportMttr': () => import('@/views/dashboard/RepairReportMttr'), // MTTR月统计报表
   'RepairReportTime': () => import('@/views/dashboard/RepairReportTime'), // 维修工时统计报表
+  'RepairWorkHourReport': () => import('@/views/report/repairworkhour/RepairWorkHour'), // 维修工时统计(按维修人/按设备)
   'RepairReportFee': () => import('@/views/dashboard/RepairReportFee'), // 费用按月统计
   'OutStoreStatistics': () => import('@/views/dashboard/OutStoreStatistics'), // 出库统计报表
   'InStoreStatistics': () => import('@/views/dashboard/InStoreStatistics'), // 入库统计报表

+ 40 - 1
src/views/check/checkstandard/modules/BaseForm.vue

@@ -48,7 +48,7 @@
         </row-item>
         <row-item>
           <a-form-item label="执行人方式" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
-            <a-select v-decorator="['checkUserType', { rules: [{ required: true, message: '执行人方式不能为空' }] }]"
+            <a-select @change="handleCheckUserTypeChange" v-decorator="['checkUserType', { rules: [{ required: true, message: '执行人方式不能为空' }] }]"
               placeholder="请选择">
               <a-select-option v-for="(label, value) in checkUserTypeMap" :key="value" :label="label"
                 :value="parseInt(value)">{{ label }}
@@ -56,6 +56,15 @@
             </a-select>
           </a-form-item>
         </row-item>
+        <row-item v-if="currentCheckUserType === 4">
+          <a-form-item label="执行班组" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
+            <a-tree-select style="width: 100%" :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
+              :treeData="deptTree" :treeNodeFilterProp="'title'" :showSearch="true"
+              v-decorator="['checkDeptId', { rules: [{ required: true, message: '请选择执行班组' }] }]" placeholder="请选择班组"
+              @change="handleDeptChange">
+            </a-tree-select>
+          </a-form-item>
+        </row-item>
         <row-item>
           <a-form-item label="计划周期" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol"
             style="margin-bottom:0;">
@@ -264,6 +273,7 @@ import SparePartInfoSelectModalSbInfo from '@/views/sqarepartmanage/sparepartinf
 import SbInfoSelectModal from '@/views/sb/info/modules/SbInfoSelectModal'
 import { queryCheckDetail } from '@/api/check/check-detail'
 import { fetchSbTypeTree } from '@/api/sb/type'
+import { getDeptTree } from '@/api/upms/dept'
 
 export default {
   name: 'BaseCheckStandard',
@@ -290,6 +300,9 @@ export default {
       enableMap: {},
       checkUserTypeMap: {},
       periodTypeMap: {},
+      currentCheckUserType: null,
+      deptTree: [],
+      deptName: null,
       checkImgList: [], // 图片
       checkFileList: [], // 文档
       defaultCheckImgList: [],
@@ -481,6 +494,8 @@ export default {
         this.modalTitle = '添加'
         this.data = []
         this.cacheData = []
+        this.currentCheckUserType = null
+        this.deptName = null
         if (sbId != null) {
           this.sbId = sbId
           const {
@@ -499,6 +514,10 @@ export default {
       }
       this.sbId = record.sbId
       this.level = record.level
+      this.currentCheckUserType = record.checkUserType
+      if (record.checkUserType === 4) {
+        this.loadDeptTree()
+      }
       // 查询列表
       fetchCheckStandard({ id: record.id }).then((res) => {
         this.data = res.data.detailList
@@ -558,6 +577,7 @@ export default {
               'partName',
               'checkUserType',
               'checkUserId',
+              'checkDeptId',
               'periodType',
               'standardHours',
               // 'actionType',
@@ -585,6 +605,10 @@ export default {
         // 上传文件
         values.checkImgList = this.checkImgList
         values.type = this.checkType
+        // 非班组模式时清空班组ID
+        if (values.checkUserType !== 4) {
+          values.checkDeptId = null
+        }
         values.requirementList = this.state.selectedRows
         if (!values.requirementList || values.requirementList.length === 0) {
           this.$message.error('请至少选择一个保养项')
@@ -765,6 +789,21 @@ export default {
       this.state.selectedRowKeys = this.requirementList ? this.requirementList.map((item) => item.id) : []
       this.state.selectedRows = this.requirementList || []
     },
+    handleCheckUserTypeChange(value) {
+      this.currentCheckUserType = value
+      if (value === 4) {
+        this.loadDeptTree()
+      }
+    },
+    handleDeptChange(value, label, extra) {
+      this.deptName = extra ? extra.triggerNode.props.title : null
+    },
+    loadDeptTree() {
+      if (this.deptTree && this.deptTree.length > 0) return
+      getDeptTree({}).then((res) => {
+        this.deptTree = res.data || []
+      })
+    },
   },
 }
 </script>

+ 268 - 0
src/views/report/repairworkhour/RepairWorkHour.vue

@@ -0,0 +1,268 @@
+<template>
+  <div class="page-header-index-wide">
+    <a-card :bordered="false">
+      <a-tabs v-model="activeTab" @change="handleTabChange">
+        <a-tab-pane tab="按维修人统计" key="user"></a-tab-pane>
+        <a-tab-pane tab="按设备统计" key="sb"></a-tab-pane>
+      </a-tabs>
+
+      <div class="table-page-search-wrapper" @keyup.enter="handleSearch">
+        <a-form layout="inline">
+          <a-row :gutter="48">
+            <a-col :md="8" :sm="24" v-if="activeTab === 'user'">
+              <a-form-item label="维修人">
+                <a-input v-model="queryParam.repairUserName" placeholder="请输入维修人姓名" allow-clear/>
+              </a-form-item>
+            </a-col>
+            <a-col :md="8" :sm="24" v-else>
+              <a-form-item label="设备">
+                <a-input v-model="queryParam.keyword" placeholder="请输入设备名称/编号" allow-clear/>
+              </a-form-item>
+            </a-col>
+            <a-col :md="10" :sm="24">
+              <a-form-item label="维修开始时间">
+                <a-range-picker
+                  v-model="dateRange"
+                  format="YYYY-MM-DD"
+                  style="width: 100%"
+                  :placeholder="['开始日期', '结束日期']"/>
+              </a-form-item>
+            </a-col>
+            <a-col :md="6" :sm="24">
+              <span class="table-page-search-submitButtons">
+                <a-button type="primary" @click="handleSearch">查询</a-button>
+                <a-button style="margin-left: 8px" @click="resetSearchForm">重置</a-button>
+              </span>
+            </a-col>
+          </a-row>
+        </a-form>
+      </div>
+
+      <div class="table-operator">
+        <a-button type="primary" icon="download" @click="doExport">导出</a-button>
+      </div>
+
+      <s-table
+        ref="table"
+        size="default"
+        :rowKey="rowKey"
+        :columns="columns"
+        :data="loadData"
+        showPagination="auto"
+      >
+        <span slot="action" slot-scope="record">
+          <template>
+            <a @click="handleDetail(record)">查看明细</a>
+            <a-divider type="vertical"/>
+            <a @click="doExportDetail(record)">导出明细</a>
+          </template>
+        </span>
+      </s-table>
+    </a-card>
+
+    <detail-modal ref="detailModal"/>
+  </div>
+</template>
+
+<script>
+import { STable } from '@/components'
+import DetailModal from './modules/DetailModal'
+import {
+  getWorkHourByUser,
+  getWorkHourBySb,
+  exportWorkHourUser,
+  exportWorkHourSb,
+  exportWorkHourDetail
+} from '@/api/report/application-form'
+
+export default {
+  name: 'RepairWorkHourReport',
+  components: {
+    STable,
+    DetailModal
+  },
+  data () {
+    return {
+      // 当前统计维度:user-按维修人,sb-按设备
+      activeTab: 'user',
+      // 查询参数
+      queryParam: {
+        repairUserName: null,
+        keyword: null
+      },
+      // 维修开始时间范围
+      dateRange: [],
+      // 加载数据方法 必须为 Promise 对象
+      loadData: parameter => {
+        const query = this.buildQuery()
+        parameter = {
+          ...parameter,
+          ...query
+        }
+        const fetchFn = this.activeTab === 'user' ? getWorkHourByUser : getWorkHourBySb
+        return fetchFn(parameter)
+          .then(res => {
+            return res.data
+          })
+      }
+    }
+  },
+  computed: {
+    rowKey () {
+      return this.activeTab === 'user' ? 'repairUserId' : 'sbId'
+    },
+    columns () {
+      const indexColumn = {
+        title: '序号',
+        checked: true,
+        dataIndex: 'index',
+        width: '70px',
+        customRender: (text, record, index) => {
+          const pagination = this.$refs.table && this.$refs.table.localPagination
+          if (pagination) {
+            return `${(pagination.current - 1) * pagination.pageSize + index + 1}`
+          }
+          return `${index + 1}`
+        }
+      }
+      const hourRender = (text) => {
+        return this.BaseTool.Object.isBlank(text) ? '0.00' : Number(text).toFixed(2)
+      }
+      const commonColumns = [
+        {
+          title: '维修次数',
+          checked: true,
+          dataIndex: 'num'
+        },
+        {
+          title: '总维修工时(小时)',
+          checked: true,
+          dataIndex: 'totalRepairHours',
+          customRender: hourRender
+        },
+        {
+          title: '平均工时(小时)',
+          checked: true,
+          dataIndex: 'avgRepairHours',
+          customRender: hourRender
+        },
+        {
+          title: '总停机修复时长(小时)',
+          checked: true,
+          dataIndex: 'totalDealHours',
+          customRender: hourRender
+        },
+        {
+          title: '操作',
+          checked: true,
+          key: 'action',
+          width: '180px',
+          align: 'center',
+          scopedSlots: { customRender: 'action' }
+        }
+      ]
+      if (this.activeTab === 'user') {
+        return [
+          indexColumn,
+          {
+            title: '维修人',
+            checked: true,
+            dataIndex: 'repairUserName'
+          },
+          ...commonColumns
+        ]
+      }
+      return [
+        indexColumn,
+        {
+          title: '设备编号',
+          checked: true,
+          dataIndex: 'sbNo'
+        },
+        {
+          title: '设备名称',
+          checked: true,
+          dataIndex: 'sbName'
+        },
+        ...commonColumns
+      ]
+    }
+  },
+  methods: {
+    buildQuery () {
+      const query = {}
+      if (this.activeTab === 'user') {
+        if (this.BaseTool.String.isNotBlank(this.queryParam.repairUserName)) {
+          query.repairUserName = this.queryParam.repairUserName
+        }
+      } else {
+        if (this.BaseTool.String.isNotBlank(this.queryParam.keyword)) {
+          query.keyword = this.queryParam.keyword
+        }
+      }
+      if (this.dateRange && this.dateRange.length === 2) {
+        query.searchRepairStartTime = this.dateRange[0].format('YYYY-MM-DD') + ' 00:00:00'
+        query.searchRepairEndTime = this.dateRange[1].format('YYYY-MM-DD') + ' 23:59:59'
+      }
+      return query
+    },
+    handleTabChange () {
+      this.$nextTick(() => {
+        this.$refs.table.refresh(true)
+      })
+    },
+    handleSearch () {
+      this.$refs.table.refresh(true)
+    },
+    resetSearchForm () {
+      this.queryParam = {
+        repairUserName: null,
+        keyword: null
+      }
+      this.dateRange = []
+      this.$refs.table.refresh(true)
+    },
+    doExport () {
+      const parameter = this.buildQuery()
+      const exportFn = this.activeTab === 'user' ? exportWorkHourUser : exportWorkHourSb
+      exportFn(parameter).then(file => {
+        this.BaseTool.UPLOAD.downLoadExportExcel(file)
+      })
+    },
+    handleDetail (record) {
+      const modal = this.$refs.detailModal
+      modal.base({
+        type: this.activeTab,
+        record: record,
+        searchRepairStartTime: this.buildQuery().searchRepairStartTime,
+        searchRepairEndTime: this.buildQuery().searchRepairEndTime
+      })
+    },
+    doExportDetail (record) {
+      const parameter = this.buildDetailQuery(record)
+      exportWorkHourDetail(parameter).then(file => {
+        this.BaseTool.UPLOAD.downLoadExportExcel(file)
+      })
+    },
+    buildDetailQuery (record) {
+      const parameter = {}
+      if (this.activeTab === 'user') {
+        parameter.repairUserId = record.repairUserId
+      } else {
+        parameter.sbId = record.sbId
+      }
+      const timeQuery = this.buildQuery()
+      if (timeQuery.searchRepairStartTime) {
+        parameter.searchRepairStartTime = timeQuery.searchRepairStartTime
+      }
+      if (timeQuery.searchRepairEndTime) {
+        parameter.searchRepairEndTime = timeQuery.searchRepairEndTime
+      }
+      return parameter
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+</style>

+ 183 - 0
src/views/report/repairworkhour/modules/DetailModal.vue

@@ -0,0 +1,183 @@
+<template>
+  <a-modal
+    :title="modalTitle"
+    :width="1200"
+    :visible="visible"
+    :confirmLoading="confirmLoading"
+    @cancel="handleCancel"
+  >
+    <title-divider title="维修工单明细" width="110px"></title-divider>
+    <a-table
+      bordered
+      :loading="loading"
+      :data-source="data"
+      :columns="columns"
+      :scroll="{x: 1200, y: BaseTool.Constant.scrollY}"
+      tableLayout="auto"
+      rowKey="id"
+      :pagination="false"
+    >
+      <span slot="hours" slot-scope="text">
+        {{ formatHours(text) }}
+      </span>
+      <span slot="status" slot-scope="text">
+        <badge :text="BaseTool.Object.getField(statusMap,text)" :status="DictCache.COLOR.REPAIR_APPLICATION_FORM_STATUS[text]"/>
+      </span>
+    </a-table>
+    <template slot="footer">
+      <a-button type="primary" icon="download" :loading="confirmLoading" @click="doExportDetail">导出明细</a-button>
+      <a-button style="margin-left: 8px" @click="handleCancel()">返回</a-button>
+    </template>
+  </a-modal>
+</template>
+
+<script>
+import { getWorkHourDetail, exportWorkHourDetail } from '@/api/report/application-form'
+
+export default {
+  name: 'WorkHourDetailModal',
+  data () {
+    return {
+      confirmLoading: false,
+      loading: false,
+      modalTitle: null,
+      visible: false,
+      // 统计维度:user-按维修人,sb-按设备
+      type: 'user',
+      // 查询参数
+      queryParam: {},
+      // 状态字典
+      statusMap: {},
+      data: [],
+      // 表头
+      columns: [
+        {
+          title: '序号',
+          dataIndex: 'index',
+          checked: true,
+          width: '60px',
+          customRender: (text, record, index) => {
+            return `${index + 1}`
+          }
+        },
+        {
+          title: '报修单号',
+          checked: true,
+          width: '140px',
+          dataIndex: 'no'
+        },
+        {
+          title: '设备编号',
+          checked: true,
+          width: '110px',
+          dataIndex: 'sbNo'
+        },
+        {
+          title: '设备名称',
+          checked: true,
+          width: '150px',
+          dataIndex: 'sbName'
+        },
+        {
+          title: '维修人',
+          checked: true,
+          width: '100px',
+          dataIndex: 'repairUserName'
+        },
+        {
+          title: '报修时间',
+          checked: true,
+          width: '160px',
+          dataIndex: 'applyTime'
+        },
+        {
+          title: '维修开始时间',
+          checked: true,
+          width: '160px',
+          dataIndex: 'repairStartTime'
+        },
+        {
+          title: '维修结束时间',
+          checked: true,
+          width: '160px',
+          dataIndex: 'repairEndTime'
+        },
+        {
+          title: '维修耗时(小时)',
+          checked: true,
+          width: '110px',
+          dataIndex: 'repairMinutes',
+          scopedSlots: { customRender: 'hours' }
+        },
+        {
+          title: '停机修复时长(小时)',
+          checked: true,
+          width: '130px',
+          dataIndex: 'dealMinutes',
+          scopedSlots: { customRender: 'hours' }
+        },
+        {
+          title: '报修状态',
+          checked: true,
+          width: '100px',
+          dataIndex: 'status',
+          fixed: 'right',
+          scopedSlots: { customRender: 'status' }
+        }
+      ]
+    }
+  },
+  created () {
+    this.statusMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.REPAIR_APPLICATION_FORM_STATUS)
+  },
+  methods: {
+    base (params) {
+      this.visible = true
+      this.type = params.type
+      const record = params.record
+      this.queryParam = {}
+      if (this.type === 'user') {
+        this.queryParam.repairUserId = record.repairUserId
+        this.modalTitle = '维修工单明细 - ' + (record.repairUserName || '')
+      } else {
+        this.queryParam.sbId = record.sbId
+        this.modalTitle = '维修工单明细 - ' + (record.sbName || record.sbNo || '')
+      }
+      if (params.searchRepairStartTime) {
+        this.queryParam.searchRepairStartTime = params.searchRepairStartTime
+      }
+      if (params.searchRepairEndTime) {
+        this.queryParam.searchRepairEndTime = params.searchRepairEndTime
+      }
+      this.loadData()
+    },
+    loadData () {
+      this.loading = true
+      getWorkHourDetail(this.queryParam)
+        .then(res => {
+          this.data = res.data || []
+        })
+        .finally(() => {
+          this.loading = false
+        })
+    },
+    formatHours (text) {
+      return this.BaseTool.Object.isBlank(text) ? '0.00' : Number(text).toFixed(2)
+    },
+    doExportDetail () {
+      this.confirmLoading = true
+      exportWorkHourDetail(this.queryParam)
+        .then(file => {
+          this.BaseTool.UPLOAD.downLoadExportExcel(file)
+        })
+        .finally(() => {
+          this.confirmLoading = false
+        })
+    },
+    handleCancel () {
+      this.visible = false
+      this.confirmLoading = false
+    }
+  }
+}
+</script>