dep.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. <template>
  2. <div class="attendanceCenterDep">
  3. <h1>考勤统计表</h1>
  4. <div class="depSearch">
  5. <div class="depSearchFlex">
  6. <div class="searBox">
  7. <span class="span">月份:</span>
  8. <el-date-picker v-model="fromParams.month" type="month" placeholder="请选择月份" />
  9. </div>
  10. <div class="searBox">
  11. <span class="span">部门:</span>
  12. <el-tree-select
  13. v-model="fromParams.deptId"
  14. :data="deptList"
  15. :props="defaultProps"
  16. check-strictly
  17. default-expand-all
  18. node-key="id"
  19. placeholder="请选择部门"
  20. :disabled="porps.deptsName !== '公司'"
  21. />
  22. </div>
  23. <div class="searBox">
  24. <span class="span">人员:</span>
  25. <el-input v-model="fromParams.userName" placeholder="请输入人员名称" />
  26. </div>
  27. <div class="searBtns">
  28. <el-button @click="querysClick" type="primary" style="background: #3485ff">
  29. <img src="@/assets/imgs/OA/search.png" class="mr-8px" alt="" />
  30. 查询</el-button
  31. >
  32. <ExportToExcel
  33. v-if="porps.deptsName == '公司'"
  34. :data="excelDataSource"
  35. :file-name="`${moment(fromParams.month).format('YYYY-MM')}考勤统计.xlsx`"
  36. :mergeRanges="mergeRanges"
  37. :colsWidth="colsWidth"
  38. />
  39. <el-button type="success" style="background-color: #05ce9e" v-if="false">
  40. <img src="@/assets/imgs/OA/kq/kqqrIcon.png" class="mr-8px" alt="" />
  41. 考勤确认</el-button
  42. >
  43. </div>
  44. </div>
  45. <el-popover :width="50">
  46. <template #reference>
  47. <div class="depStateBox">
  48. <p>?</p>
  49. </div>
  50. </template>
  51. <template #default>
  52. <div class="depStateContent">
  53. <ul class="">
  54. <li class="flex" v-for="(l, i) in depStateContentList" :key="i">
  55. <div v-if="i == 0" class="flex">
  56. <img src="@/assets/imgs/OA/kq/gouzi.png" alt="" />:
  57. <p>{{ l.name }}</p>
  58. </div>
  59. <div v-else class="flex">
  60. <h4>{{ l.deps }}:</h4>
  61. <p>{{ l.name }}</p>
  62. </div>
  63. </li>
  64. </ul>
  65. </div>
  66. </template>
  67. </el-popover>
  68. </div>
  69. <div class="depTable">
  70. <el-table
  71. v-loading="tableLoading"
  72. :data="
  73. tableData.slice(
  74. (pagesList.pageNo - 1) * pagesList.pageSize,
  75. pagesList.pageSize * pagesList.pageNo
  76. )
  77. "
  78. style="width: 100%; height: 100%"
  79. stripe
  80. >
  81. <!-- <el-table-column type="index" width="50" /> -->
  82. <el-table-column prop="deptName" label="部门" />
  83. <el-table-column prop="nickName" label="姓名" width="80" />
  84. <el-table-column
  85. v-for="(item, index) in tableHeadList"
  86. :key="index"
  87. :label="item.dayOfWeek"
  88. width="30"
  89. :label-class-name="item.isworkday == 1 ? '' : 'workday'"
  90. >
  91. <el-table-column
  92. :label-class-name="item.isworkday == 1 ? '' : 'workday'"
  93. :label="item.day"
  94. width="30"
  95. >
  96. <template #default="scope">
  97. <div class="iconsFlex" v-if="item.isworkday == 1">
  98. <div class="sw" @click="swStateClick(scope.row, item, 1)">
  99. <img
  100. v-if="sco(scope.row, item, 1) == '1'"
  101. src="@/assets/imgs/OA/kq/gouzi.png"
  102. alt=""
  103. />
  104. <div v-else class="spans">
  105. {{ attendanceTypeObj[sco(scope.row, item, 1)] ?? '' }}
  106. </div>
  107. </div>
  108. <div class="xw" @click="swStateClick(scope.row, item, 2)">
  109. <img
  110. v-if="sco(scope.row, item, 2) == '1'"
  111. src="@/assets/imgs/OA/kq/gouzi.png"
  112. alt=""
  113. />
  114. <div v-else class="spans">
  115. {{ attendanceTypeObj[sco(scope.row, item, 1)] ?? '' }}
  116. </div>
  117. </div>
  118. </div>
  119. </template>
  120. </el-table-column>
  121. </el-table-column>
  122. <el-table-column
  123. v-for="(item, index) in columList"
  124. :prop="item.prop"
  125. width="40"
  126. label-class-name="labelModeRl"
  127. :key="index"
  128. >
  129. <template #header>
  130. <span>{{ item.text }}</span>
  131. <span class="m-t-8px font-size-14px c-#989FA8">{{ item.unit }}</span>
  132. </template>
  133. </el-table-column>
  134. </el-table>
  135. </div>
  136. <div class="depPages">
  137. <el-pagination
  138. :current-page="pagesList.pageNo"
  139. :page-size="pagesList.pageSize"
  140. background
  141. layout="total, prev, pager, next, jumper"
  142. :total="pagesList.total"
  143. @current-change="handleCurrentChange"
  144. />
  145. </div>
  146. <DepsUpdataDetail ref="depsDetailRef" @updataInit="updataInit" />
  147. </div>
  148. </template>
  149. <script setup lang="ts">
  150. defineOptions({ name: 'AttendanceDep' })
  151. import * as DeptApi from '@/api/system/dept'
  152. import DepsUpdataDetail from './components/depsUpdataDetail.vue'
  153. import { defaultProps, handleTree } from '@/utils/tree'
  154. import * as MineApi from '@/api/oa/attendanceCenter'
  155. import { arrFlatten, depSort, isArrayDelOrNickname, allDeptsArr, dayOfWeekCall } from './attendAuth'
  156. import moment from 'moment'
  157. import { depsState } from './depsState'
  158. import ExportToExcel from '@/components/ExportToExcel/index.vue'
  159. import { getUserInfo } from '@/utils/tool'
  160. const userInfo = getUserInfo()
  161. const porps = defineProps(['deptsName'])
  162. const pagesList: any = ref({
  163. pageNo: 1,
  164. pageSize: 12,
  165. total: 0
  166. })
  167. const fromParams: any = ref({
  168. deptId: userInfo.deptId ?? '',
  169. month: '',
  170. userName: ''
  171. })
  172. if (porps.deptsName == '公司') {
  173. fromParams.value.deptId = '4e99393c-c0ea-4146-a7fb-56fb8019c477'
  174. }
  175. const deptSort: any = ref([])
  176. const deptList = ref<Tree[]>([]) // 树形结构
  177. const tableData = ref([])
  178. const initTreeDeps = async () => {
  179. DeptApi.getSimpleDeptList().then((res) => {
  180. deptList.value = handleTree(res)
  181. deptSort.value = arrFlatten(res, 'children')
  182. })
  183. }
  184. const depStateContentList = ref()
  185. depStateContentList.value = depsState
  186. const handleCurrentChange = async (page) => {
  187. pagesList.value.pageNo = page
  188. let toMoseMonths: any = []
  189. toMoseMonths[0] = moment().startOf('months').format('YYYY-MM-DD') + ' 00:00:00'
  190. toMoseMonths[1] = moment().endOf('months').format('YYYY-MM-DD') + ' 23:59:59'
  191. // initInsMouth(toMoseMonths)
  192. }
  193. const querysClick = async () => {
  194. // initInsMouth()
  195. pagesList.value.pageNo = 1
  196. let month = fromParams.value.month
  197. let toMoseMonths: any = []
  198. toMoseMonths[0] = moment(month).startOf('months').format('YYYY-MM-DD') + ' 00:00:00'
  199. toMoseMonths[1] = moment(month).endOf('months').format('YYYY-MM-DD') + ' 23:59:59'
  200. initInsMouth(toMoseMonths)
  201. }
  202. const tableLoading = ref(true)
  203. const updataInit = (date) => {
  204. initInsMouth(date)
  205. }
  206. const initInsMouth = async (date: any) => {
  207. tableLoading.value = true
  208. let params = {
  209. attendanceDate: date, // 考勤时间
  210. attendanceStatus: '', // 考勤状态,示例值(2)
  211. attendanceType: '', // 考勤类型(1:上午;2:下午),示例值(1)
  212. otherMinute: '', // 其他考勤状态分钟(除了出勤)
  213. nickname: fromParams.value.userName, //用户昵称
  214. deptId: fromParams.value.deptId //部门ID
  215. }
  216. initWorkDay(date).then((restall) => {
  217. MineApi.getWorkdaySheetList(params).then((res) => {
  218. let namesArr: any = isArrayDelOrNickname(JSON.parse(JSON.stringify(res)))
  219. let resArr: any = res
  220. resArr.forEach((item: any) => {
  221. if (item.attendanceTime) {
  222. item.date = moment(item.attendanceTime).format('YYYY-MM-DD')
  223. } else {
  224. item.date = moment(item.attendanceDate).format('YYYY-MM-DD')
  225. }
  226. })
  227. let arr = allDeptsArr(restall, resArr, namesArr)
  228. let sbArr = depSort(deptSort.value, arr)
  229. // 导出数据
  230. excelDataSource.value = getExcelTable(sbArr)
  231. tableData.value = sbArr
  232. pagesList.value.total = arr.length
  233. tableLoading.value = false
  234. })
  235. })
  236. }
  237. const sco = (row, item, index) => {
  238. let kq = ''
  239. if (index == 1) {
  240. row.attendArray.forEach((k) => {
  241. if (k.date == item.date) {
  242. if (k.swAttendanceStatus) {
  243. kq = k.swAttendanceStatus
  244. }
  245. }
  246. })
  247. } else {
  248. row.attendArray.forEach((k) => {
  249. if (k.date == item.date) {
  250. if (k.xwAttendanceStatus) {
  251. kq = k.xwAttendanceStatus
  252. }
  253. }
  254. })
  255. }
  256. return kq
  257. }
  258. const tableHeadList: any = ref([])
  259. const initWorkDay = async (date: any) => {
  260. let params = {
  261. dateDay: date, // 日期(数组)
  262. dayOfWeek: '', // 星期几
  263. holidayRemark: '', // 节假备注,示例值(国庆节)
  264. isworkday: '', // 是否工作日
  265. year: '', // 年份
  266. month: '', // 月份
  267. week: '' // 周
  268. }
  269. const res = await MineApi.getWorkdayList(params)
  270. let arr = res
  271. arr.forEach((item: any) => {
  272. item.date = moment(item.dateDay).format('YYYY-MM-DD')
  273. item.prop = item.dayOfWeek
  274. item.dayOfWeek = dayOfWeekCall(item.dayOfWeek)
  275. item.day = moment(item.dateDay).format('DD')
  276. })
  277. tableHeadList.value = arr
  278. return await arr
  279. }
  280. const depsDetailRef = ref()
  281. const swStateClick = (row, item, index) => {
  282. //更改考勤
  283. if (porps.deptsName == '公司') {
  284. depsDetailRef.value.initShow(row, item, index, fromParams.value.month)
  285. return
  286. }
  287. }
  288. // 考勤对应的字典
  289. const attendanceTypeObj = {
  290. 1: '√', // 出勤
  291. 2: '迟', // '迟到'
  292. 3: '早', // '早退'
  293. 4: '旷', // '旷工'
  294. 5: '差', // '出差'
  295. 11: '事', // '事假'
  296. 12: '病', // '病假'
  297. 13: '婚', // '婚假'
  298. 14: '产', // '产假'
  299. 15: '陪', // '陪产假'
  300. 19: '丧', // '丧假'
  301. 16: '伤', // '工伤'
  302. 17: '年', // '年假'
  303. 18: '调', // '调休'
  304. 20: '假' // '其他'
  305. }
  306. // 考勤统计
  307. const columList = [
  308. { text: '旷 工', prop: 'kg', unit: '天' },
  309. { text: '迟 到', prop: 'cd', unit: '次' },
  310. { text: '早 退', prop: 'zt', unit: '次' },
  311. { text: '出 差', prop: 'cc', unit: '天' },
  312. { text: '事 假', prop: 'sj', unit: '天' },
  313. { text: '调 休', prop: 'tx', unit: '天' },
  314. { text: '病 假', prop: 'bj', unit: '天' },
  315. { text: '年 假', prop: 'nj', unit: '天' },
  316. { text: '产 假', prop: 'cj', unit: '天' },
  317. { text: '其 他', prop: 'qt', unit: '天' }
  318. ]
  319. // 考勤表格
  320. const excelDataSource = ref<any>()
  321. // 合并的单元格
  322. const mergeRanges = [
  323. { s: { r: 0, c: 0 }, e: { r: 1, c: 0 } }, // 合并第一行的前两列
  324. { s: { r: 0, c: 1 }, e: { r: 1, c: 1 } } // 合并第二行的前两列
  325. ]
  326. // 列宽设置
  327. const colsWidth = [{ wch: 30 }, { wch: 10 }]
  328. // 获取导出的数据表格
  329. const getExcelTable = (attendanceData) => {
  330. const tableHead = [
  331. [
  332. '部门',
  333. '姓名',
  334. ...tableHeadList.value.map((item) => item.day),
  335. '旷工(天)',
  336. '迟到(次)',
  337. '早退(次)',
  338. '出差(天)',
  339. '事假(天)',
  340. '调休(天)',
  341. '病假(天)',
  342. '年假(天)',
  343. '产假(天)',
  344. '其他(天)'
  345. ],
  346. [
  347. '',
  348. '',
  349. ...tableHeadList.value.map((item) => item.dayOfWeek),
  350. '',
  351. '',
  352. '',
  353. '',
  354. '',
  355. '',
  356. '',
  357. '',
  358. '',
  359. ''
  360. ]
  361. ]
  362. const tableBody = attendanceData.map((item) => {
  363. const data = item.attendArray.map((arr, i) => {
  364. // 判断是不是工作日
  365. const isworkday = tableHeadList.value[i].isworkday
  366. if (!isworkday) return ''
  367. // 判断有没有数据
  368. if (!arr.swAttendanceStatus) return ''
  369. return `${attendanceTypeObj[arr.swAttendanceStatus] ?? ''} / ${
  370. attendanceTypeObj[arr.xwAttendanceStatus] ?? ''
  371. }`
  372. })
  373. return [
  374. item.deptName,
  375. item.nickName,
  376. ...data,
  377. item.kg,
  378. item.cd,
  379. item.zt,
  380. item.cc,
  381. item.sj,
  382. item.tx,
  383. item.bj,
  384. item.nj,
  385. item.cj,
  386. item.qt
  387. ]
  388. })
  389. return [...tableHead, ...tableBody]
  390. }
  391. onMounted(() => {
  392. let toMonth = moment().format('YYYY-MM')
  393. fromParams.value.month = toMonth
  394. // 加载部门树
  395. initTreeDeps()
  396. let toMoseMonths: any = []
  397. toMoseMonths[0] = moment().startOf('months').format('YYYY-MM-DD') + ' 00:00:00'
  398. toMoseMonths[1] = moment().endOf('months').format('YYYY-MM-DD') + ' 23:59:59'
  399. initInsMouth(toMoseMonths)
  400. })
  401. </script>
  402. <style lang="scss" scoped>
  403. .attendanceCenterDep {
  404. position: relative;
  405. width: 100%;
  406. height: calc(100% - 15px);
  407. padding: 15px 30px;
  408. margin-top: 15px;
  409. background: #fff;
  410. border-radius: 10px;
  411. h1 {
  412. margin-bottom: 20px;
  413. font-size: 20px;
  414. font-weight: 600;
  415. color: #121518;
  416. }
  417. .depSearch {
  418. display: flex;
  419. width: 100%;
  420. margin-bottom: 20px;
  421. align-items: center;
  422. justify-content: space-between;
  423. .depSearchFlex {
  424. display: flex;
  425. align-items: center;
  426. .searBox {
  427. display: flex;
  428. align-items: center;
  429. margin-right: 25px;
  430. .span {
  431. font-size: 16px;
  432. white-space: nowrap;
  433. }
  434. }
  435. }
  436. .depStateBox {
  437. display: flex;
  438. width: 30px;
  439. height: 30px;
  440. cursor: pointer;
  441. border: 2px solid #e1e4ea;
  442. border-radius: 50%;
  443. align-items: center;
  444. justify-content: center;
  445. p {
  446. font-size: 16px;
  447. font-weight: 600;
  448. color: #e1e4ea;
  449. }
  450. }
  451. }
  452. .depTable {
  453. width: 100%;
  454. height: calc(100% - 140px);
  455. }
  456. .depPages {
  457. position: absolute;
  458. right: 30px;
  459. bottom: 10px;
  460. }
  461. }
  462. :deep(.depTable) {
  463. .el-table {
  464. .workDayClass {
  465. color: pink;
  466. }
  467. .is-group {
  468. height: 80px;
  469. tr {
  470. background-color: #e5f0fb;
  471. th {
  472. font-weight: 500;
  473. color: #233755;
  474. background-color: #e5f0fb;
  475. border-color: #fff;
  476. }
  477. }
  478. }
  479. .cell {
  480. width: 100%;
  481. height: 100%;
  482. padding: 0 !important;
  483. font-size: 16px !important;
  484. text-align: center;
  485. }
  486. .el-table__cell {
  487. // padding: 0;
  488. }
  489. .el-table__body-wrapper {
  490. tr {
  491. td {
  492. border: 0;
  493. }
  494. }
  495. .el-table__row--striped {
  496. td {
  497. background-color: #f7f7f7;
  498. }
  499. }
  500. }
  501. .iconsFlex {
  502. display: flex;
  503. width: 100%;
  504. height: 100%;
  505. align-items: center;
  506. flex-wrap: wrap;
  507. .spans {
  508. width: 100%;
  509. height: 100%;
  510. font-size: 14px !important;
  511. font-weight: 800;
  512. color: #f23c3c;
  513. cursor: pointer;
  514. user-select: none;
  515. }
  516. .sw {
  517. display: flex;
  518. width: 100%;
  519. height: 50%;
  520. user-select: none;
  521. align-items: center;
  522. justify-content: center;
  523. }
  524. .sw:hover {
  525. .spans {
  526. color: #989fa8;
  527. }
  528. }
  529. .xw:hover {
  530. .spans {
  531. color: #989fa8;
  532. }
  533. }
  534. .xw {
  535. display: flex;
  536. width: 100%;
  537. height: 50%;
  538. user-select: none;
  539. align-items: center;
  540. justify-content: center;
  541. }
  542. }
  543. }
  544. .labelModeRl {
  545. writing-mode: vertical-rl;
  546. }
  547. }
  548. :deep(.workday) {
  549. color: #989fa8 !important;
  550. }
  551. </style>