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

feat: 部门考勤,导出数据

qiny 1 рік тому
батько
коміт
ed6c8cfc10

+ 2 - 0
client/package.json

@@ -55,6 +55,7 @@
     "echarts-wordcloud": "^2.1.0",
     "element-plus": "2.3.14",
     "fast-xml-parser": "^4.3.0",
+    "file-saver": "^2.0.5",
     "highlight.js": "^11.8.0",
     "intro.js": "^7.2.0",
     "js-base64": "^3.7.5",
@@ -79,6 +80,7 @@
     "vue-types": "^5.1.1",
     "vuedraggable": "^4.1.0",
     "web-storage-cache": "^1.1.1",
+    "xlsx": "^0.18.5",
     "xml-js": "^1.6.11"
   },
   "devDependencies": {

+ 64 - 0
client/src/components/ExportToExcel/index.vue

@@ -0,0 +1,64 @@
+<template>
+  <el-button type="primary" @click="exportToExcel">
+    <img src="@/assets/imgs/OA/open.png" class="mr-8px" alt="" />
+    导出
+  </el-button>
+</template>
+<script setup lang="ts">
+/**
+ * @description 导出为excel
+ */
+import * as XLSX from 'xlsx'
+import { saveAs } from 'file-saver'
+interface IProp {
+  data: any[][] // 数据 [[表头,表头,表头],[数据,数据,数据]]
+  fileName?: string // 文件名
+  mergeRanges?: {
+    s: { r: number; c: number } // 合并的起始单元格
+    e: { r: number; c: number } // 合并的结束单元格
+  }[] // 合并单元格列表
+  colsWidth?: { wch: number }[] // 列宽
+}
+// 定义组件props
+const props = defineProps<IProp>()
+
+// 导出Excel函数
+const exportToExcel = () => {
+  // 从props获取数据
+  const data = props.data
+
+  // 创建一个工作簿
+  const wb = XLSX.utils.book_new()
+
+  // 创建一个工作表
+  const ws = XLSX.utils.aoa_to_sheet(data)
+
+  // 合并表头的单元格,定义多个合并范围
+  ws['!merges'] = props.mergeRanges ?? []
+
+  // 设置列宽,定义多个列宽
+  ws['!cols'] = props.colsWidth ?? []
+
+  // 将工作表添加到工作簿中
+  XLSX.utils.book_append_sheet(wb, ws, 'Sheet1')
+
+  // 生成Excel文件
+  const wbout = XLSX.write(wb, { type: 'binary', bookType: 'xlsx' })
+
+  // 下载Excel文件
+  saveAs(
+    new Blob([s2ab(wbout)], { type: 'application/octet-stream' }),
+    props.fileName || 'exported_data.xlsx'
+  )
+}
+
+// 将二进制字符串转换为字节数组
+const s2ab = (s: string) => {
+  const buf = new ArrayBuffer(s.length)
+  const view = new Uint8Array(buf)
+  for (let i = 0; i < s.length; i++) view[i] = s.charCodeAt(i) & 0xff
+  return buf
+}
+</script>
+
+<style scoped></style>

+ 138 - 95
client/src/views/OaSystem/attendanceCenter/dep.vue

@@ -17,6 +17,7 @@
             default-expand-all
             node-key="id"
             placeholder="请选择部门"
+            :disabled="porps.deptsName !== '公司'"
           />
         </div>
         <div class="searBox">
@@ -28,10 +29,13 @@
             <img src="@/assets/imgs/OA/search.png" class="mr-8px" alt="" />
             查询</el-button
           >
-          <el-button type="primary">
-            <img src="@/assets/imgs/OA/open.png" class="mr-8px" alt="" />
-            导出</el-button
-          >
+          <ExportToExcel
+            v-if="porps.deptsName == '公司'"
+            :data="excelDataSource"
+            :file-name="`${moment(fromParams.month).format('YYYY-MM')}考勤统计.xlsx`"
+            :mergeRanges="mergeRanges"
+            :colsWidth="colsWidth"
+          />
           <el-button type="success" style="background-color: #05ce9e" v-if="false">
             <img src="@/assets/imgs/OA/kq/kqqrIcon.png" class="mr-8px" alt="" />
             考勤确认</el-button
@@ -98,21 +102,9 @@
                     src="@/assets/imgs/OA/kq/gouzi.png"
                     alt=""
                   />
-                  <div class="spans" v-else-if="sco(scope.row, item, 1) == '2'"> 迟 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 1) == '3'"> 早 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 1) == '4'"> 旷 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 1) == '5'"> 差 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 1) == '11'"> 事 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 1) == '12'"> 病 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 1) == '13'"> 婚 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 1) == '14'"> 产 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 1) == '15'"> 陪 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 1) == '19'"> 丧 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 1) == '16'"> 伤 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 1) == '17'"> 年 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 1) == '18'"> 调 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 1) == '20'"> 他 </div>
-                  <div class="spans" v-else> </div>
+                  <div v-else class="spans">
+                    {{ attendanceTypeObj[sco(scope.row, item, 1)] ?? '' }}
+                  </div>
                 </div>
                 <div class="xw" @click="swStateClick(scope.row, item, 2)">
                   <img
@@ -120,85 +112,24 @@
                     src="@/assets/imgs/OA/kq/gouzi.png"
                     alt=""
                   />
-                  <div class="spans" v-else-if="sco(scope.row, item, 2) == '2'"> 迟 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 2) == '3'"> 早 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 2) == '4'"> 旷 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 2) == '5'"> 差 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 2) == '11'"> 事 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 2) == '12'"> 病 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 2) == '13'"> 婚 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 2) == '14'"> 产 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 2) == '15'"> 陪 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 2) == '19'"> 丧 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 2) == '16'"> 伤 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 2) == '17'"> 年 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 2) == '18'"> 调 </div>
-                  <div class="spans" v-else-if="sco(scope.row, item, 2) == '20'"> 他 </div>
-                  <div class="spans" v-else> </div>
+                  <div v-else class="spans">
+                    {{ attendanceTypeObj[sco(scope.row, item, 1)] ?? '' }}
+                  </div>
                 </div>
               </div>
             </template>
           </el-table-column>
         </el-table-column>
-
-        <el-table-column prop="kg" width="40" label-class-name="labelModeRl">
-          <template #header>
-            <span>旷工</span>
-            <span class="c-#989FA8 font-size-14px m-t-8px">天</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="cd" width="40" label-class-name="labelModeRl">
-          <template #header>
-            <span>迟到</span>
-            <span class="c-#989FA8 font-size-14px m-t-8px">次</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="zt" width="40" label-class-name="labelModeRl">
-          <template #header>
-            <span>早退</span>
-            <span class="c-#989FA8 font-size-14px m-t-8px">次</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="cc" width="40" label-class-name="labelModeRl">
-          <template #header>
-            <span>出差</span>
-            <span class="c-#989FA8 font-size-14px m-t-8px">天</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="sj" width="40" label-class-name="labelModeRl">
-          <template #header>
-            <span>事假</span>
-            <span class="c-#989FA8 font-size-14px m-t-8px">天</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="tx" width="40" label-class-name="labelModeRl">
-          <template #header>
-            <span>调休</span>
-            <span class="c-#989FA8 font-size-14px m-t-8px">天</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="bj" width="40" label-class-name="labelModeRl">
-          <template #header>
-            <span>病假</span>
-            <span class="c-#989FA8 font-size-14px m-t-8px">天</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="nj" width="40" label-class-name="labelModeRl">
-          <template #header>
-            <span>年假</span>
-            <span class="c-#989FA8 font-size-14px m-t-8px">天</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="cj" width="40" label-class-name="labelModeRl">
-          <template #header>
-            <span>产假</span>
-            <span class="c-#989FA8 font-size-14px m-t-8px">天</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="qt" width="40" label-class-name="labelModeRl">
+        <el-table-column
+          v-for="(item, index) in columList"
+          :prop="item.prop"
+          width="40"
+          label-class-name="labelModeRl"
+          :key="index"
+        >
           <template #header>
-            <span>其他</span>
-            <span class="c-#989FA8 font-size-14px m-t-8px"></span>
+            <span>{{ item.text }}</span>
+            <span class="c-#989FA8 font-size-14px m-t-8px">{{ item.unit }}</span>
           </template>
         </el-table-column>
       </el-table>
@@ -226,6 +157,10 @@ import * as MineApi from '@/api/oa/attendanceCenter'
 import { arrFlatten, depSort, isArrayDelOrNickname, allDeptsArr, dayOfWeekCall } from './attendAuth'
 import moment from 'moment'
 import { depsState } from './depsState'
+import ExportToExcel from '@/components/ExportToExcel/index.vue'
+import { getUserInfo } from '@/utils/tool'
+const userInfo = getUserInfo()
+
 const porps = defineProps(['deptsName'])
 
 const pagesList: any = ref({
@@ -234,7 +169,7 @@ const pagesList: any = ref({
   total: 0
 })
 const fromParams: any = ref({
-  deptId: '0a168fe9-2ba8-4302-b6b6-a524c1aef967',
+  deptId: userInfo.deptId ?? '',
   month: '',
   userName: ''
 })
@@ -274,6 +209,7 @@ const tableLoading = ref(true)
 const updataInit = (date) => {
   initInsMouth(date)
 }
+
 const initInsMouth = async (date: any) => {
   tableLoading.value = true
   let params = {
@@ -297,6 +233,10 @@ const initInsMouth = async (date: any) => {
       })
       let arr = allDeptsArr(restall, resArr, namesArr)
       let sbArr = depSort(deptSort.value, arr)
+
+      // 导出数据
+      excelDataSource.value = getExcelTable(sbArr)
+
       tableData.value = sbArr
       pagesList.value.total = arr.length
       tableLoading.value = false
@@ -344,17 +284,120 @@ const initWorkDay = async (date: any) => {
     item.day = moment(item.dateDay).format('DD')
   })
   tableHeadList.value = arr
-
   return await arr
 }
 const depsDetailRef = ref()
 const swStateClick = (row, item, index) => {
   //更改考勤
   if (porps.deptsName == '公司') {
+    depsDetailRef.value.initShow(row, item, index, fromParams.value.month)
     return
   }
-  depsDetailRef.value.initShow(row, item, index, fromParams.value.month)
 }
+// 考勤对应的字典
+const attendanceTypeObj = {
+  1: '√', // 出勤
+  2: '迟', // '迟到'
+  3: '早', // '早退'
+  4: '旷', // '旷工'
+  5: '差', // '出差'
+  11: '事', // '事假'
+  12: '病', // '病假'
+  13: '婚', // '婚假'
+  14: '产', // '产假'
+  15: '陪', // '陪产假'
+  19: '丧', // '丧假'
+  16: '伤', // '工伤'
+  17: '年', // '年假'
+  18: '调', // '调休'
+  20: '假' // '其他'
+}
+// 考勤统计
+const columList = [
+  { text: '旷 工', prop: 'kg', unit: '天' },
+  { text: '迟 到', prop: 'cd', unit: '次' },
+  { text: '早 退', prop: 'zt', unit: '次' },
+  { text: '出 差', prop: 'cc', unit: '天' },
+  { text: '事 假', prop: 'sj', unit: '天' },
+  { text: '调 休', prop: 'tx', unit: '天' },
+  { text: '病 假', prop: 'bj', unit: '天' },
+  { text: '年 假', prop: 'nj', unit: '天' },
+  { text: '产 假', prop: 'cj', unit: '天' },
+  { text: '其 他', prop: 'qt', unit: '天' }
+]
+
+// 考勤表格
+const excelDataSource = ref<any>()
+// 合并的单元格
+const mergeRanges = [
+  { s: { r: 0, c: 0 }, e: { r: 1, c: 0 } }, // 合并第一行的前两列
+  { s: { r: 0, c: 1 }, e: { r: 1, c: 1 } } // 合并第二行的前两列
+]
+// 列宽设置
+const colsWidth = [{ wch: 30 }, { wch: 10 }]
+// 获取导出的数据表格
+const getExcelTable = (attendanceData) => {
+  const tableHead = [
+    [
+      '部门',
+      '姓名',
+      ...tableHeadList.value.map((item) => item.day),
+      '旷工(天)',
+      '迟到(次)',
+      '早退(次)',
+      '出差(天)',
+      '事假(天)',
+      '调休(天)',
+      '病假(天)',
+      '年假(天)',
+      '产假(天)',
+      '其他(天)'
+    ],
+    [
+      '',
+      '',
+      ...tableHeadList.value.map((item) => item.dayOfWeek),
+      '',
+      '',
+      '',
+      '',
+      '',
+      '',
+      '',
+      '',
+      '',
+      ''
+    ]
+  ]
+  const tableBody = attendanceData.map((item) => {
+    const data = item.attendArray.map((arr, i) => {
+      // 判断是不是工作日
+      const isworkday = tableHeadList.value[i].isworkday
+      if (!isworkday) return ''
+      // 判断有没有数据
+      if (!arr.swAttendanceStatus) return ''
+      return `${attendanceTypeObj[arr.swAttendanceStatus] ?? ''} / ${attendanceTypeObj[arr.xwAttendanceStatus] ?? ''}`
+    })
+    return [
+      item.deptName,
+      item.nickName,
+      ...data,
+      item.kg,
+      item.cd,
+      item.zt,
+      item.cc,
+      item.sj,
+      item.tx,
+      item.bj,
+      item.nj,
+      item.cj,
+      item.qt
+    ]
+  })
+
+  return [...tableHead, ...tableBody]
+}
+
 onMounted(() => {
   let toMonth = moment().format('YYYY-MM')
   fromParams.value.month = toMonth