Bladeren bron

feat: 新增工时统计页面,修复考勤展示错误的问题

qiny 1 jaar geleden
bovenliggende
commit
b6bb9d51c9

+ 6 - 8
client/src/components/ExportToExcel/index.vue

@@ -12,7 +12,7 @@ import * as XLSX from 'xlsx'
 import { saveAs } from 'file-saver'
 interface IProp {
   data: any[][] // 数据 [[表头,表头,表头],[数据,数据,数据]]
-  fileName?: string // 文件名
+  fileName?: string // 文件名 ***.xlsx
   mergeRanges?: {
     s: { r: number; c: number } // 合并的起始单元格
     e: { r: number; c: number } // 合并的结束单元格
@@ -41,7 +41,7 @@ const exportToExcel = () => {
   const data = props.data
 
   // 如果有标题,添加标题
-  if (props.title) {
+  if (props.title && !data.some((row) => row.some((cell) => cell?.v === props.title))) {
     // 创建一个包含样式信息的单元格对象(样式未生效,不知道是什么原因)
     const titleCell = { v: props.title, s: titleStyle }
     // 将单元格对象添加到数据中
@@ -54,12 +54,10 @@ const exportToExcel = () => {
   // 创建一个工作表
   const ws = XLSX.utils.aoa_to_sheet(data)
 
-  // 如果有标题, 则在第一行插入标题
-  if (props.title) {
-    // 添加标题并合并单元格
-    const mergeRange = { s: { r: 0, c: 0 }, e: { r: 0, c: data[1].length - 1 } } // 合并单元格范围
-    ws['!merges'] = [mergeRange] // 合并单元格
-  }
+  // 合并单元格
+  const cLength = data[1]?.length ? data[1]?.length - 1 : 1
+  const mergeRange = { s: { r: 0, c: 0 }, e: { r: 0, c: cLength } } // 合并单元格范围
+  ws['!merges'] = [mergeRange] // 合并单元格
 
   // 多个合并范围 合并单元格
   if (props.mergeRanges) {

+ 6 - 1
client/src/components/UserSelect/index.vue

@@ -78,7 +78,11 @@ onMounted(() => {
 // 选中数据发生变化时触发
 const checkOnChange = (data) => {
   const timer = setTimeout(() => {
-    onChange?.(selectUserList.value)
+    if (construction === 'tree') {
+      onChange?.(selectUserList.value)
+    } else {
+      onChange?.(selectUser.value)
+    }
     clearTimeout(timer)
   }, 128)
 }
@@ -92,6 +96,7 @@ const checkOnChange = (data) => {
       placeholder="请选择"
       v-model="selectUser"
       style="width: 100%"
+      @change="checkOnChange"
     >
       <el-option
         v-for="item in simpleUserList"

+ 0 - 8
client/src/router/modules/remaining.ts

@@ -273,14 +273,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
           title: '档案详情'
         }
       },
-      {
-        path: 'projectStatistics',
-        component: () => import('@/views/OaSystem/personnelManagement/ProjectStatistics/index.vue'),
-        name: 'projectStatistics',
-        meta: {
-          title: '项目工时统计'
-        }
-      },
       {
         path: 'dailyLogDetail',
         component: () => import('@/views/OaSystem/personnelManagement/dailyDetail/index.vue'),

+ 24 - 32
client/src/views/OaSystem/attendanceCenter/dep.vue

@@ -97,23 +97,19 @@
             <template #default="scope">
               <div class="iconsFlex" v-if="item.isworkday == 1">
                 <div class="sw" @click="swStateClick(scope.row, item, 1)">
-                  <img
-                    v-if="sco(scope.row, item, 1) == '1'"
-                    src="@/assets/imgs/OA/kq/gouzi.png"
-                    alt=""
-                  />
+                  <div v-if="sco(scope.row, item, 1) == '1'">
+                    <img src="@/assets/imgs/OA/kq/gouzi.png" alt="" />
+                  </div>
                   <div v-else class="spans">
                     {{ attendanceTypeObj[sco(scope.row, item, 1)] ?? '' }}
                   </div>
                 </div>
                 <div class="xw" @click="swStateClick(scope.row, item, 2)">
-                  <img
-                    v-if="sco(scope.row, item, 2) == '1'"
-                    src="@/assets/imgs/OA/kq/gouzi.png"
-                    alt=""
-                  />
+                  <div v-if="sco(scope.row, item, 2) == '1'">
+                    <img src="@/assets/imgs/OA/kq/gouzi.png" alt="" />
+                  </div>
                   <div v-else class="spans">
-                    {{ attendanceTypeObj[sco(scope.row, item, 1)] ?? '' }}
+                    {{ attendanceTypeObj[sco(scope.row, item, 2)] ?? '' }}
                   </div>
                 </div>
               </div>
@@ -248,17 +244,13 @@ const sco = (row, item, index) => {
   if (index == 1) {
     row.attendArray.forEach((k) => {
       if (k.date == item.date) {
-        if (k.swAttendanceStatus) {
-          kq = k.swAttendanceStatus
-        }
+        kq = k.swAttendanceStatus ?? ''
       }
     })
   } else {
     row.attendArray.forEach((k) => {
       if (k.date == item.date) {
-        if (k.xwAttendanceStatus) {
-          kq = k.xwAttendanceStatus
-        }
+        kq = k.xwAttendanceStatus ?? ''
       }
     })
   }
@@ -296,21 +288,21 @@ const swStateClick = (row, item, index) => {
 }
 // 考勤对应的字典
 const attendanceTypeObj = {
-  1: '', // 出勤
-  2: '迟', // '迟到'
-  3: '早', // '早退'
-  4: '旷', // '旷工'
-  5: '差', // '出差'
-  11: '事', // '事假'
-  12: '病', // '病假'
-  13: '婚', // '婚假'
-  14: '产', // '产假'
-  15: '陪', // '陪产假'
-  19: '丧', // '丧假'
-  16: '伤', // '工伤'
-  17: '年', // '年假'
-  18: '调', // '调休'
-  20: '假' // '其他'
+  '1': '', // 出勤
+  '2': '迟', // '迟到'
+  '3': '早', // '早退'
+  '4': '旷', // '旷工'
+  '5': '差', // '出差'
+  '11': '事', // '事假'
+  '12': '病', // '病假'
+  '13': '婚', // '婚假'
+  '14': '产', // '产假'
+  '15': '陪', // '陪产假'
+  '19': '丧', // '丧假'
+  '16': '伤', // '工伤'
+  '17': '年', // '年假'
+  '18': '调', // '调休'
+  '20': '假' // '其他'
 }
 // 考勤统计
 const columList = [

+ 9 - 0
client/src/views/OaSystem/personnelManagement/CompletionRate/index.vue

@@ -105,6 +105,15 @@ const setRateData = (rateTree) => {
           deptId: item.deptId
         })
       }
+    } else if (data.deptName === '北京分公司') {
+      // 因为北京分公司暂无下属部门,以防后期添加,先这样处理
+      rataData.push({
+        deptFather: data.deptName,
+        fatherRate: data.fillRate,
+        deptName: data.deptName,
+        fillRate: data.fillRate,
+        deptId: data.deptId
+      })
     } else {
       rataData.push({
         deptFather: '职能部门',

+ 107 - 34
client/src/views/OaSystem/personnelManagement/ProjectStatistics/projectForm.vue

@@ -1,3 +1,35 @@
+<template>
+  <el-form :inline="true" class="searchBox">
+    <el-form-item label="项目名称:" class="form project-name">
+      <el-input v-model="queryParams.projectName" clearable />
+    </el-form-item>
+    <el-form-item label="人员:" class="form">
+      <UserSelect
+        ref="userSelectRef"
+        construction="simple"
+        :multiple="false"
+        :onChange="onChangeUser"
+      />
+    </el-form-item>
+    <el-form-item label="月份:" class="form">
+      <el-date-picker
+        v-model="queryParams.date"
+        type="month"
+        placeholder="请选择月份"
+        :clearable="false"
+      />
+    </el-form-item>
+    <el-form-item class="search-button">
+      <el-button type="primary" :icon="Search" @click="onSearchHandle">查询</el-button>
+      <ExportToExcel
+        :data="exportData"
+        :file-name="`${moment(queryParams.date).format('YYYY年M月')}项目工时统计.xlsx`"
+        :colsWidth="colsWidth"
+        :title="`${moment(queryParams.date).format('YYYY年M月')}-项目工时统计`"
+      />
+    </el-form-item>
+  </el-form>
+</template>
 <script lang="ts" setup>
 /**
  * @description 查询表单
@@ -5,50 +37,91 @@
 
 import { Search } from '@element-plus/icons-vue'
 import moment from 'moment'
-interface IProp {
-  onSearch?: (any) => any
-}
-const { onSearch } = defineProps<IProp>()
+import request from '@/config/axios'
+import UserSelect from '@/components/UserSelect/index.vue'
+import ExportToExcel from '@/components/ExportToExcel/index.vue'
+import PubsubService from '@/utils/PubsubService'
+
 interface IQuery {
-  type: '周报' | '日报'
+  projectName: string // 项目名称
+  userId: string // 人员
   date: string // 日期
-  project: string // 项目名称
 }
 const queryParams = reactive<IQuery>({
-  type: '日报',
-  date: moment().format('YYYY-MM'),
-  project: ''
+  projectName: '',
+  userId: '',
+  date: moment().format('YYYY-MM')
 })
+
 // 查询
 const onSearchHandle: () => void = () => {
   queryParams.date = moment(queryParams.date).format('YYYY-MM')
-  onSearch?.(queryParams)
+  const params = {
+    year: moment(queryParams.date).format('YYYY'),
+    month: moment(queryParams.date).format('M'),
+    userId: queryParams.userId ?? '',
+    projectName: queryParams.projectName ?? ''
+  }
+
+  // 发布查询事件
+  PubsubService.publish('xmgstj-form-onSearch', params)
+  // onSearch?.(queryParams)
+  // 重置查询
+  getAllData()
+}
+
+// 切换人员
+const onChangeUser = (user) => {
+  queryParams.userId = user
+}
+
+onMounted(() => {
+  getAllData()
+})
+
+/* 导出 */
+
+// 导出头部
+const tableHead = [['人员', '项目名称', '项目编号', '项目工时(小时)']]
+// 列宽设置
+const colsWidth = ref([{ wch: 8 }, { wch: 100 }, { wch: 18 }, { wch: 15 }])
+const exportData = ref(tableHead)
+
+// 获取所有统计数据
+const getAllData = () => {
+  // 发布获取所有统计数据事件
+  PubsubService.publish('xmgstj-form-getAllData', {})
+  request
+    .get({
+      url: '/adm/reportWorkloadStatistics/query-workload-statistics',
+      params: {
+        year: moment(queryParams.date).format('YYYY'),
+        month: moment(queryParams.date).format('M'),
+        userId: queryParams.userId ?? '',
+        projectName: queryParams.projectName ?? '',
+        isPage: false
+      }
+    })
+    .then((res) => {
+      getExportData(res)
+    })
+    .catch(() => {
+      exportData.value = []
+    })
+}
+// 整合导出表格数据
+const getExportData = (data) => {
+  const tableBody = data.map((item) => [
+    item.nickName ?? '',
+    item.xmmc ?? '',
+    item.xmbh ?? '',
+    item.workTime ?? ''
+  ])
+  exportData.value = [...tableHead, ...tableBody]
+  // console.log('exportData.value', exportData.value)
 }
 </script>
-<template>
-  <el-form :inline="true" class="searchBox">
-    <el-form-item label="日志类型:" class="form">
-      <el-select v-model="queryParams.type" placeholder="please select your zone">
-        <el-option label="日报" value="日报" />
-        <el-option label="周报" value="周报" />
-      </el-select>
-    </el-form-item>
-    <el-form-item label="月份:" class="form">
-      <el-date-picker
-        v-model="queryParams.date"
-        type="month"
-        placeholder="请选择月份"
-        :clearable="false"
-      />
-    </el-form-item>
-    <el-form-item label="项目名称:" class="form project-name">
-      <el-input v-model="queryParams.project" />
-    </el-form-item>
-    <el-form-item class="search-button">
-      <el-button type="primary" :icon="Search" @click="onSearchHandle">查询</el-button>
-    </el-form-item>
-  </el-form>
-</template>
+
 <style scoped lang="scss">
 .project-name {
   width: 400px !important;

+ 71 - 31
client/src/views/OaSystem/personnelManagement/ProjectStatistics/projectTable.vue

@@ -1,58 +1,98 @@
-<script lang="ts" setup>
-/**
- * @description 项目表
- */
-interface IProp {
-  dataSource: any[]
-}
-const props = defineProps<IProp>()
-const { dataSource } = toRefs(props)
-
-const tableRef: any = ref(null)
-const tableHeight: any = ref(0)
-onMounted(() => {
-  tableHeight.value = tableRef.value.clientHeight
-})
-const { push } = useRouter()
-// 查看详情
-const jumpToDetail = (row: any) => {
-  push(`/customerDetail`)
-}
-</script>
 <template>
   <div class="tableBox">
     <div class="table" ref="tableRef">
       <el-table
         stripe
-        :data="dataSource ?? []"
+        :data="dataSource"
         style="width: 100%; height: 100%"
         :style="{ height: tableHeight + 'px' }"
+        :load="loading"
         :header-cell-style="{
           background: '#E5F0FB',
           color: '#233755',
           height: '50px'
         }"
       >
-        <el-table-column label="序号" width="60">
+        <el-table-column label="序号" width="60" align="center">
           <template #default="scope">{{ scope.$index + 1 }}</template>
         </el-table-column>
-        <el-table-column :show-overflow-tooltip="true" prop="projectName" label="项目名称" />
-        <el-table-column :show-overflow-tooltip="true" prop="userName" label="人员" width="120" />
-        <el-table-column prop="code" label="项目编号" width="120" />
-        <el-table-column prop="time" label="项目工时" width="100" />
+        <el-table-column :show-overflow-tooltip="true" prop="nickName" label="人员" width="120" />
+        <el-table-column :show-overflow-tooltip="true" prop="xmmc" label="项目名称" />
+        <el-table-column prop="xmbh" label="项目编号" width="250" />
+        <el-table-column prop="workTime" label="项目工时(小时)" width="180" align="center" />
       </el-table>
     </div>
     <div class="pageBox">
       <el-pagination
-        :page-size="10"
+        :page-size="20"
         background
-        layout="total, prev, pager, next, jumper"
-        :total="0"
-        @current-change="() => {}"
+        layout="total, prev, pager, next"
+        :total="pageTotal"
+        :current-page="pageNo"
+        @current-change="currentChange"
       />
     </div>
   </div>
 </template>
+<script lang="ts" setup>
+import moment from 'moment'
+import request from '@/config/axios'
+import PubsubService from '@/utils/PubsubService'
+
+const loading = ref(true)
+const dataSource = ref([])
+const pageNo = ref(1)
+const pageTotal = ref(0)
+
+const tableRef: any = ref(null)
+const tableHeight: any = ref(0)
+onMounted(() => {
+  tableHeight.value = tableRef.value.clientHeight
+  // 接收查询事件
+  PubsubService.subscribe('xmgstj-form-onSearch', (params) => {
+    getDataSource(params)
+  })
+  getDataSource()
+})
+const { push } = useRouter()
+// 查看详情
+const jumpToDetail = (row: any) => {
+  push(`/customerDetail`)
+}
+// 获取数据
+const getDataSource = (params?) => {
+  loading.value = true
+  request
+    .get({
+      url: '/adm/reportWorkloadStatistics/query-workload-statistics',
+      params: {
+        year: params?.year ?? moment().format('YYYY'),
+        month: params?.month ?? moment().format('M'),
+        userId: params?.userId ?? '',
+        projectName: params?.projectName ?? '',
+        pageNo: pageNo.value,
+        pageSize: 20
+      }
+    })
+    .then((res) => {
+      ElMessage.success(`数据查询成功`)
+      loading.value = false
+      const { list, total } = res
+      dataSource.value = list
+      pageTotal.value = total
+      pageNo.value = 1
+    })
+    .catch(() => {
+      ElMessage.error('查询失败,请稍后重试!')
+      loading.value = false
+    })
+}
+// 切换页面
+const currentChange = (page) => {
+  pageNo.value = page
+  getDataSource()
+}
+</script>
 <style scoped lang="scss">
 .titleBox {
   display: flex;