Browse Source

feat:添加 考勤统计

DESKTOP-USV654P\pc 4 months ago
parent
commit
fc2e00e8df

+ 2 - 2
.env.development

@@ -15,8 +15,8 @@ VITE_DROP_CONSOLE = false
 
 # 接口地址
 # 如果没有跨域问题,直接在这里配置即可
-VITE_GLOB_API_URL=http://localhost:8080
-#  VITE_GLOB_API_URL=http://10.150.10.139:8888/api
+# VITE_GLOB_API_URL=http://localhost:8080
+  VITE_GLOB_API_URL=http://10.150.10.139:8888/api
 
 #VITE_GLOB_API_URL=http://172.21.92.28:8080
 # 文件上传接口  可选

+ 49 - 0
src/services/apis/AttendanceStatisticsController.ts

@@ -0,0 +1,49 @@
+// @ts-ignore
+/* eslint-disable */
+
+// 该文件自动生成,请勿手动修改!
+import { defHttp } from '/@/utils/http/axios';
+import { ErrorMessageMode } from '/#/axios';
+            // --------------------------------------------------------------------------
+            // Attendance Statistics Controller
+            // --------------------------------------------------------------------------
+
+            ;
+
+
+            /** 新增考勤统计 POST /attendance/statistics */
+export async function postAttendanceStatistics(params:API.AddAttendanceStatisticsDto
+,mode: ErrorMessageMode = 'modal'){ return defHttp.post<any>
+        ({url: '/attendance/statistics', data:params},{errorMessageMode:mode});}
+/** 修改考勤统计 PUT /attendance/statistics */
+export async function putAttendanceStatistics(params:API.UpdateAttendanceStatisticsDto
+,mode: ErrorMessageMode = 'modal'){ return defHttp.put<any>
+        ({url: '/attendance/statistics', data:params},{errorMessageMode:mode});}
+/** 删除考勤统计 DELETE /attendance/statistics */
+export async function deleteAttendanceStatistics(params:string[],mode: ErrorMessageMode = 'modal'){ return defHttp.delete<any>
+        ({url: '/attendance/statistics', data:params},{errorMessageMode:mode});}
+/** 修改状态 POST /attendance/statistics/change-status */
+export async function postStatisticsChangeStatus(params:API.ChangeStatusDto
+,mode: ErrorMessageMode = 'modal'){ return defHttp.post<any>
+        ({url: '/attendance/statistics/change-status', data:params},{errorMessageMode:mode});}
+/** 根据id查询考勤统计信息 GET /attendance/statistics/info */
+export async function getStatisticsInfo(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<API.AttendanceStatisticsVo>
+        ({url: '/attendance/statistics/info', params:params},{errorMessageMode:mode});}
+/** 考勤统计列表(分页) GET /attendance/statistics/page */
+export async function getStatisticsPage(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<API.PageOutput<API.AttendanceStatisticsPageVo>>
+        ({url: '/attendance/statistics/page', params:params},{errorMessageMode:mode});}
+/** 考勤统计详情导出 POST /attendance/statistics/record-export-query */
+export async function postStatisticsRecordExportQuery(params:API.RefreshStatisticsDto
+,mode: ErrorMessageMode = 'modal'){ return defHttp.download<string>
+        ({url: '/attendance/statistics/record-export-query',responseType:'blob',method:'POST', data:params},{errorMessageMode:mode});}
+/** 考情统计详情列表 GET /attendance/statistics/record-list */
+export async function getStatisticsRecordList(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<API.AttendanceStatisticsRecordVo[]>
+        ({url: '/attendance/statistics/record-list', params:params},{errorMessageMode:mode});}
+/** 刷新课时统计 POST /attendance/statistics/refresh-statistics */
+export async function postStatisticsRefreshStatistics(params:API.RefreshStatisticsDto
+,mode: ErrorMessageMode = 'modal'){ return defHttp.post<any>
+        ({url: '/attendance/statistics/refresh-statistics', data:params},{errorMessageMode:mode});}
+/** 考勤规则列表 GET /attendance/statistics/rule-list */
+export async function getStatisticsRuleList(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<API.AttendanceRuleCategoryListVo[]>
+        ({url: '/attendance/statistics/rule-list', params:params},{errorMessageMode:mode});}
+            

+ 1 - 1
src/services/apis/DataboardController.ts

@@ -12,7 +12,7 @@ import { ErrorMessageMode } from '/#/axios';
 
 
             /** 个人考勤 GET /databoard/databoard/attendance-statistics */
-export async function getDataboardAttendanceStatistics(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<API.AttendanceStatisticsVo>
+export async function getDataboardAttendanceStatistics(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<API.AttendanceStatisticsVo_1>
         ({url: '/databoard/databoard/attendance-statistics', params:params},{errorMessageMode:mode});}
 /** 课表统计 GET /databoard/databoard/course-statistics */
 export async function getDataboardCourseStatistics(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<API.CourseStatisticsVo>

+ 4 - 0
src/services/apis/ScheduleController.ts

@@ -14,6 +14,10 @@ import { ErrorMessageMode } from '/#/axios';
             /** 可以调课的课程 GET /schedule/schedule/adjust-list */
 export async function getScheduleAdjustList(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<API.CourseListVo[]>
         ({url: '/schedule/schedule/adjust-list', params:params},{errorMessageMode:mode});}
+/** 调课顶课的作废 POST /schedule/schedule/cancel */
+export async function postScheduleCancel(params:API.WfCourseAdjustDto
+,mode: ErrorMessageMode = 'modal'){ return defHttp.post<any>
+        ({url: '/schedule/schedule/cancel', data:params},{errorMessageMode:mode});}
 /** 根据课表获取教师所教班级 GET /schedule/schedule/class-list */
 export async function getScheduleClassList(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<API.ClassOptionVo[]>
         ({url: '/schedule/schedule/class-list', params:params},{errorMessageMode:mode});}

+ 3 - 0
src/services/apis/index.ts

@@ -16,6 +16,7 @@ import * as WfAssetManageController from './WfAssetManageController';
 import * as WfAssetManageInventoryController from './WfAssetManageInventoryController';
 import * as AttendanceMessageSetController from './AttendanceMessageSetController';
 import * as AttendanceRuleCategoryController from './AttendanceRuleCategoryController';
+import * as AttendanceStatisticsController from './AttendanceStatisticsController';
 import * as StudentAttendanceRecordController from './StudentAttendanceRecordController';
 import * as TeacherAttendanceRecordController from './TeacherAttendanceRecordController';
 import * as DataAuthController from './DataAuthController';
@@ -247,6 +248,8 @@ WfAssetManageInventoryController,
 AttendanceMessageSetController,
 /** Attendance Rule Category Controller */
 AttendanceRuleCategoryController,
+/** Attendance Statistics Controller */
+AttendanceStatisticsController,
 /** Student Attendance Record Controller */
 StudentAttendanceRecordController,
 /** Teacher Attendance Record Controller */

+ 194 - 0
src/services/typing.d.ts

@@ -549,6 +549,50 @@ returnStartTime?: LocalTime_1;
 sortCode?: number;
 }
 
+type AddAttendanceStatisticsDto = {
+/** 应出勤天数 */
+attendanceDays?: number;
+/** 关联考勤规则(attendance_rule_category) */
+attendanceRuleCategoryId?: string;
+/** attendanceStatisticsRecord子表 */
+attendanceStatisticsRecordList?: AddAttendanceStatisticsRecordDto[];
+/** 结束日期 */
+endDate?: string;
+/** 是否需要刷新(0:否 1:是) */
+isNeedRefresh?: number;
+/** 统计月份 */
+month?: number;
+/** 统计名称 */
+name?: string;
+/** 统计人数 */
+personCount?: number;
+/** 开始日期 */
+startDate?: string;
+/** 状态(0:数据统计中 1:统计完成 2:已解锁 3:已锁定) */
+status?: number;
+/** 时间段(1:上午 2:下午) */
+timePeriod?: number;
+/** 统计年份 */
+year?: number;
+}
+
+type AddAttendanceStatisticsRecordDto = {
+/** 旷工次数 */
+absenteeCount?: number;
+/** 课时统计(attendance_statistics) */
+attendanceStatisticsId?: string;
+/** 每天数据 */
+daysData?: string;
+/** 所在部门(存在多个,用“,”隔开) */
+deptIds?: string;
+/** 请假次数 */
+leaveCount?: number;
+/** 正常考勤次数 */
+normalCount?: number;
+/** 教师id(xjr_user) */
+userId?: string;
+}
+
 type AddAttendanceUserRelationDto = {
 /** 所属班级id */
 classId?: string;
@@ -1797,10 +1841,14 @@ sortCode?: number;
 }
 
 type AddClassTimeCalendarDto = {
+/** 覆盖方式 1:选中覆盖 0:全天覆盖 */
+coverType?: number;
 /** 补课日期 */
 replaceDate?: string;
 /** 补班日期 */
 supplementDate?: string;
+/** 补课课程 */
+timePeriod?: string;
 }
 
 type AddClassTimeDeleteDto = {
@@ -1812,6 +1860,8 @@ endDate?: string;
 remark?: string;
 /** 开始日期 */
 startDate?: string;
+/** 删除课程 */
+timePeriod?: string;
 /** 删除类型(xjr_dictionary_item[class_time_delete]) */
 type?: string;
 }
@@ -4133,6 +4183,8 @@ name?: string;
 nickName?: string;
 /** 密码 */
 password?: string;
+/** 岗位id */
+postIds?: string;
 /** 备注 */
 remark?: string;
 /** 排序码 */
@@ -4733,6 +4785,13 @@ status?: number;
 userId?: string;
 }
 
+type AttendanceRuleCategoryListVo = {
+/** 主键编号 */
+id?: string;
+/** 考勤规则名称 */
+name?: string;
+}
+
 type AttendanceRuleCategoryPageVo = {
 /** 考勤人员范围(1:全体 2:指定) */
 attendanceRange?: number;
@@ -4854,7 +4913,84 @@ returnStartTime?: LocalTime;
 sortCode?: number;
 }
 
+type AttendanceStatisticsPageVo = {
+/** 应出勤天数 */
+attendanceDays?: number;
+/** 结束日期 */
+endDate?: string;
+/** 主键编号 */
+id?: string;
+/** 是否需要刷新(0:否 1:是) */
+isNeedRefresh?: number;
+/** 统计月份 */
+month?: number;
+/** 统计名称 */
+name?: string;
+/** 统计人数 */
+personCount?: number;
+/** 关联考勤规则 */
+ruleName?: string;
+/** 开始日期 */
+startDate?: string;
+/** 状态(0:数据统计中 1:统计完成 2:已解锁 3:已锁定) */
+status?: number;
+/** 时间段(1:上午 2:下午) */
+timePeriod?: number;
+/** 统计年份 */
+year?: number;
+}
+
+type AttendanceStatisticsRecordVo = {
+/** 旷工次数 */
+absenteeCount?: number;
+/** 课时统计(attendance_statistics) */
+attendanceStatisticsId?: string;
+/** 每天数据 */
+daysData?: string;
+/** 所在部门(存在多个,用“,”隔开) */
+deptIds?: string;
+/** 部门成 */
+deptName?: string;
+/** 主键编号 */
+id?: string;
+/** 请假次数 */
+leaveCount?: number;
+/** 正常考勤次数 */
+normalCount?: number;
+/** 教师id(xjr_user) */
+userId?: string;
+/** 工号 */
+userName?: string;
+}
+
 type AttendanceStatisticsVo = {
+/** 应出勤天数 */
+attendanceDays?: number;
+/** 结束日期 */
+endDate?: string;
+/** 主键编号 */
+id?: string;
+/** 是否需要刷新(0:否 1:是) */
+isNeedRefresh?: number;
+/** 统计月份 */
+month?: number;
+/** 统计名称 */
+name?: string;
+/** 统计人数 */
+personCount?: number;
+/** 关联考勤规则 */
+ruleName?: string;
+/** 开始日期 */
+startDate?: string;
+/** 状态(0:数据统计中 1:统计完成 2:已解锁 3:已锁定) */
+status?: number;
+/** 时间段(1:上午 2:下午) */
+timePeriod?: number;
+/** 统计年份 */
+year?: number;
+}
+
+type AttendanceStatisticsVo_1 = {
 /** 调课次数 */
 adjustCount?: number;
 /** 总课时 */
@@ -8037,6 +8173,8 @@ yjLeaveSchoolCount?: number;
 }
 
 type ClassTimeCalendarPageVo = {
+/** 覆盖方式 1:选中覆盖 0:全天覆盖 */
+coverType?: number;
 /** 创建时间 */
 createDate?: string;
 /** 创建人 */
@@ -8057,6 +8195,8 @@ replaceDate?: string;
 status?: number;
 /** 补班日期 */
 supplementDate?: string;
+/** 补课课程 */
+timePeriod?: string;
 }
 
 type ClassTimeCalendarVo = {
@@ -8087,6 +8227,8 @@ remark?: string;
 startDate?: string;
 /** 状态(0:生效 1:锁定 2:作废) */
 status?: number;
+/** 删除课程 */
+timePeriod?: string;
 /** 删除类型(xjr_dictionary_item[class_time_delete]) */
 type?: string;
 /** 删除类型-中文 */
@@ -11960,6 +12102,14 @@ total?: number;
 totalPage?: number;
 }
 
+type PageOutput<AttendanceStatisticsPageVo> = {
+currentPage?: number;
+list?: AttendanceStatisticsPageVo[];
+pageSize?: number;
+total?: number;
+totalPage?: number;
+}
+
 type PageOutput<BandingRulePageVo> = {
 currentPage?: number;
 list?: BandingRulePageVo[];
@@ -16184,6 +16334,8 @@ titleColor?: string;
 
 type TableConfig = {
 isMain?: boolean;
+isNullable?: boolean;
+nullable?: boolean;
 pkField?: string;
 pkType?: string;
 relationField?: string;
@@ -17508,6 +17660,35 @@ status?: number;
 stduyStatus?: string;
 }
 
+type UpdateAttendanceStatisticsDto = {
+/** 应出勤天数 */
+attendanceDays?: number;
+/** 关联考勤规则(attendance_rule_category) */
+attendanceRuleCategoryId?: string;
+/** attendanceStatisticsRecord子表 */
+attendanceStatisticsRecordList?: AddAttendanceStatisticsRecordDto[];
+/** 结束日期 */
+endDate?: string;
+/** 主键编号 */
+id?: string;
+/** 是否需要刷新(0:否 1:是) */
+isNeedRefresh?: number;
+/** 统计月份 */
+month?: number;
+/** 统计名称 */
+name?: string;
+/** 统计人数 */
+personCount?: number;
+/** 开始日期 */
+startDate?: string;
+/** 状态(0:数据统计中 1:统计完成 2:已解锁 3:已锁定) */
+status?: number;
+/** 时间段(1:上午 2:下午) */
+timePeriod?: number;
+/** 统计年份 */
+year?: number;
+}
+
 type UpdateBandingClassDto = {
 /** 分班任务id */
 bandingTaskId?: string;
@@ -18768,12 +18949,16 @@ sortCode?: number;
 }
 
 type UpdateClassTimeCalendarDto = {
+/** 覆盖方式 1:选中覆盖 0:全天覆盖 */
+coverType?: number;
 /** 主键编号 */
 id?: string;
 /** 补课日期 */
 replaceDate?: string;
 /** 补班日期 */
 supplementDate?: string;
+/** 补课课程 */
+timePeriod?: string;
 }
 
 type UpdateClassTimeDeleteDto = {
@@ -18787,6 +18972,8 @@ id?: string;
 remark?: string;
 /** 开始日期 */
 startDate?: string;
+/** 删除课程 */
+timePeriod?: string;
 /** 删除类型(xjr_dictionary_item[class_time_delete]) */
 type?: string;
 }
@@ -21056,6 +21243,8 @@ name?: string;
 nickName?: string;
 /** 密码 */
 password?: string;
+/** 岗位id */
+postIds?: string;
 /** 备注 */
 remark?: string;
 /** 排序码 */
@@ -21507,6 +21696,11 @@ status?: number;
 studentId?: string;
 }
 
+type WfCourseAdjustDto = {
+/** 主键编号 */
+id?: string;
+}
+
 type WfHeadTeacherLeavePageVo = {
 /** 申请人 */
 applicantUserId?: string;

+ 2 - 1
src/views/attendance/teacherRecord/data.config.ts

@@ -3,6 +3,7 @@ import { getDataOption } from '/@/api/system/dic';
 import { BasicColumn, FormSchema } from '/@/components/Table';
 import { dateUtil, formatToDate } from '/@/utils/dateUtil';
 
+
 export const tableColumns: BasicColumn[] = [
   // {
   //   title: '时间段',
@@ -193,4 +194,4 @@ export const searchRecordFormSchema: FormSchema[] = [
     },
     colProps: { span: 8 },
   },
-];
+];

+ 2 - 2
src/views/attendance/teacherRecord/index.vue

@@ -3,13 +3,13 @@
     <DeptTree class="w-1/3 xl:w-1/4" @select="handleSelect" />
     <div class="w-2/3 xl:w-3/4 h-full">
       <div class="h-full bg-white">
-        <div class="ml-2" style="height: 50px;">
+        <div class="ml-2" style="height: 50px">
           <Tabs v-model:activeKey="activeKey" @change="handelTabsChange">
             <Tabs.TabPane key="1" tab="教师考勤" />
             <Tabs.TabPane key="2" tab="教师进出记录" />
           </Tabs>
         </div>
-        <div style="height: calc(100% - 50px);">
+        <div style="height: calc(100% - 50px)">
           <TableDetail ref="tableDetailRef" v-show="activeKey === '1'" />
           <TableOutIn ref="tableOutInRef" v-show="activeKey === '2'" />
         </div>

+ 126 - 0
src/views/attendance/teacherStatistics/components/detail.vue

@@ -0,0 +1,126 @@
+<template>
+  <BasicModal
+    :destroyOnClose="true"
+    :maskClosable="false"
+    v-bind="$attrs"
+    @register="registerModal"
+    :title="getTitle"
+    :width="1002"
+    :defaultFullscreen="true"
+    :show-ok-btn="false"
+  >
+    <div>
+      <div style="height: 172px">
+        <Descriptions bordered>
+          <Descriptions.Item label="统计名称">
+            {{ modelRef?.name }}
+          </Descriptions.Item>
+          <Descriptions.Item label="统计年月">
+            {{ modelRef?.year }}年{{ modelRef?.month }}月
+          </Descriptions.Item>
+          <Descriptions.Item label="统计日期">
+            {{ formatToDate(modelRef?.startDate) }}~{{ formatToDate(modelRef?.endDate) }}
+          </Descriptions.Item>
+          <Descriptions.Item label="时间段">
+            {{ timeIntervalOptions.find((item) => item.value === modelRef?.timePeriod)?.label }}
+          </Descriptions.Item>
+          <Descriptions.Item label="关联考勤规则">
+            {{ modelRef?.ruleName }}
+          </Descriptions.Item>
+          <Descriptions.Item label="统计人数(人)">
+            {{ modelRef?.personCount }}
+          </Descriptions.Item>
+          <Descriptions.Item label="应出勤天数">
+            {{ modelRef?.attendanceDays }}
+          </Descriptions.Item>
+        </Descriptions>
+      </div>
+      <div style="height: calc(100% - 172px)">
+        <BasicTable @register="registerTable" :searchInfo="searchInfo">
+          <template #toolbar>
+            <a-button type="primary" @click="handelRefrresh" :disabled="modelRef?.status === 3">
+              刷新统计
+            </a-button>
+            <a-button type="primary" @click="handleExport">导出</a-button>
+          </template>
+        </BasicTable>
+      </div>
+    </div>
+  </BasicModal>
+</template>
+<script setup lang="ts">
+  import { ref, computed, unref, reactive } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { formDetailSchema } from '../data.config';
+  import { useTable, BasicTable } from '/@/components/Table';
+  import { Descriptions } from 'ant-design-vue';
+  import { formatToDate } from '/@/utils/dateUtil';
+  import { downloadByData } from '/@/utils/file/download';
+  import {
+    getStatisticsInfo,
+    getStatisticsRecordList,
+    postStatisticsRecordExportQuery,
+    postStatisticsRefreshStatistics,
+  } from '/@/services/apis/AttendanceStatisticsController';
+  import { timeIntervalOptions } from '../../data.config';
+
+  const isUpdate = ref(true);
+  const modelRef = ref<Recordable>({});
+  const searchInfo = reactive<Recordable>({});
+  const emit = defineEmits(['success', 'register']);
+  const state = reactive({
+    status: 0,
+  });
+
+  const [registerTable, { reload }] = useTable({
+    api: getStatisticsRecordList,
+    title: '考勤统计表',
+    rowKey: 'id',
+    columns: formDetailSchema,
+    useSearchForm: false,
+    showTableSetting: true,
+    bordered: true,
+    immediate: false,
+    canResize: true,
+    pagination: false,
+  });
+
+  const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
+    setModalProps({
+      confirmLoading: true,
+      cancelText: '关闭',
+    });
+    isUpdate.value = false;
+    state.status = data.baseData.status;
+
+    const resData = await getStatisticsInfo({ id: data.baseData.id });
+    modelRef.value = { ...resData };
+    searchInfo.id = data.baseData.id;
+    reload();
+    setModalProps({ confirmLoading: false });
+  });
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '统计明细' : '统计明细'));
+
+  const handelRefrresh = async () => {
+    try {
+      setModalProps({ loading: true, confirmLoading: true });
+      await postStatisticsRefreshStatistics({ id: unref(modelRef).id });
+      closeModal();
+      emit('success');
+    } finally {
+      setModalProps({ loading: false, confirmLoading: false });
+    }
+  };
+
+  const handleExport = async () => {
+    setModalProps({ loading: true, confirmLoading: true });
+    downloadByData(
+      (await postStatisticsRecordExportQuery({ id: unref(modelRef).id })).data,
+      `考勤统计表${formatToDate(new Date())}.xlsx`,
+    );
+    setModalProps({ loading: false, confirmLoading: false });
+  };
+</script>
+
+<style scoped lang="less"></style>

+ 87 - 0
src/views/attendance/teacherStatistics/components/edit.vue

@@ -0,0 +1,87 @@
+<template>
+  <BasicModal
+    @ok="handleSubmit"
+    :destroyOnClose="true"
+    :maskClosable="false"
+    v-bind="$attrs"
+    @register="registerModal"
+    :title="getTitle"
+    :width="1002"
+  >
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<script setup lang="ts">
+  import { ref, computed, unref } from 'vue';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+  import { formStatisticsSchema } from '../data.config';
+  import { formatToDate } from '/@/utils/dateUtil';
+  import {
+    getStatisticsInfo,
+    postAttendanceStatistics,
+    putAttendanceStatistics,
+  } from '/@/services/apis/AttendanceStatisticsController';
+
+  const isUpdate = ref(true);
+  const modelRef = ref<Recordable>({});
+  const emit = defineEmits(['success', 'register']);
+  const { createMessage } = useMessage();
+  const [registerForm, { validate, setFieldsValue, resetFields }] = useForm({
+    labelWidth: 100,
+    schemas: formStatisticsSchema,
+    showActionButtonGroup: false,
+  });
+
+  const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
+    resetFields();
+    setModalProps({ confirmLoading: false });
+    isUpdate.value = !!data?.isUpdate;
+    modelRef.value = { ...data.baseData };
+
+    if (unref(isUpdate)) {
+      const resData = await getStatisticsInfo({ id: data.baseData.id });
+      resData['date'] = [resData['startDate'], resData['endDate']];
+      resData['yearMonth'] = new Date(Number(resData['year']), Number(resData['month']), 1);
+      modelRef.value = { ...resData };
+      setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '新增考勤汇总' : '编辑考勤汇总'));
+  const handleSubmit = async () => {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      const postParams = unref(modelRef);
+
+      Object.assign(postParams, values);
+      postParams['startDate'] = values['date'][0];
+      postParams['endDate'] = values['date'][1];
+      delete postParams['date'];
+
+      postParams['year'] = formatToDate(values['yearMonth'], 'YYYY');
+      postParams['month'] = formatToDate(values['yearMonth'], 'MM');
+      delete postParams['yearMonth'];
+
+      if (unref(isUpdate)) {
+        await putAttendanceStatistics(postParams as API.UpdateAttendanceStatisticsDto);
+      } else {
+        await postAttendanceStatistics(postParams as API.AddAttendanceStatisticsDto);
+      }
+
+      createMessage.success('操作成功');
+      closeModal();
+      emit('success');
+    } catch {
+      // createMessage.error('操作失败');
+    } finally {
+      setModalProps({ confirmLoading: false });
+    }
+  };
+</script>
+
+<style scoped lang="less"></style>

+ 197 - 0
src/views/attendance/teacherStatistics/data.config.ts

@@ -0,0 +1,197 @@
+import { timeIntervalOptions } from '../data.config';
+import { BasicOptionModel } from '/@/api/model/baseModel';
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import { getStatisticsRuleList } from '/@/services/apis/AttendanceStatisticsController';
+import { dateUtil, formatToDate } from '/@/utils/dateUtil';
+
+export const statusOptions: BasicOptionModel[] = [
+  { label: '统计中', value: 0 },
+  { label: '统计完成', value: 1 },
+  { label: '作废', value: 2 },
+];
+export const searchStatisticsFormSchema: FormSchema[] = [
+  {
+    field: 'name',
+    label: '统计名称',
+    component: 'Input',
+    colProps: { span: 8 },
+  },
+  {
+    field: 'date',
+    label: '统计年月',
+    component: 'DatePicker',
+    colProps: { span: 8 },
+    componentProps: {
+      showTime: false,
+      format: 'YYYY-MM',
+      valueFormat: 'YYYY-MM',
+      getPopupContainer: () => document.body,
+    },
+  },
+  {
+    field: 'status',
+    label: '状态',
+    component: 'Select',
+    componentProps: {
+      getPopupContainer: () => document.body,
+      options: statusOptions,
+    },
+    colProps: { span: 8 },
+  },
+];
+
+export const tableStatisticsColumns: BasicColumn[] = [
+  {
+    title: '统计年月',
+    dataIndex: 'month',
+    align: 'left',
+    width: 100,
+    customRender: ({ record }) => {
+      return `${record.year}年${record.month}月`;
+    },
+  },
+  {
+    title: '统计名称',
+    dataIndex: 'name',
+    align: 'left',
+  },
+  {
+    title: '统计时间段',
+    dataIndex: 'startDate',
+    align: 'left',
+    width: 180,
+    customRender: ({ record }) => {
+      return `${formatToDate(record.startDate)} ~ ${formatToDate(record.endDate)}`;
+    },
+  },
+  {
+    title: '关联考勤规则',
+    dataIndex: 'ruleName',
+    align: 'left',
+    width: 150,
+  },
+  {
+    title: '统计人数',
+    dataIndex: 'personCount',
+    align: 'left',
+    width: 80,
+  },
+  {
+    title: '应出勤天数',
+    dataIndex: 'attendanceDays',
+    align: 'left',
+    width: 80,
+  },
+  {
+    title: '状态',
+    dataIndex: 'status',
+    align: 'left',
+    width: 80,
+    customRender: ({ text }) => {
+      return statusOptions.find((item) => item.value === text)?.label;
+    },
+  },
+];
+
+export const formStatisticsSchema: FormSchema[] = [
+  {
+    label: '考勤规则',
+    field: 'attendanceRuleCategoryId',
+    component: 'ApiSelect',
+    colProps: { span: 24 },
+    required: true,
+    componentProps: {
+      api: getStatisticsRuleList,
+      getPopupContainer: () => document.body,
+      valueField: 'id',
+      labelField: 'name',
+      showSearch: true,
+    },
+  },
+  {
+    label: '统计年月',
+    field: 'yearMonth',
+    component: 'DatePicker',
+    colProps: { span: 24 },
+    required: true,
+    componentProps: {
+      showTime: false,
+      format: 'YYYY-MM',
+      valueFormat: 'YYYY-MM',
+      getPopupContainer: () => document.body,
+      disabledDate: (current) => {
+        return current && current > dateUtil().endOf('day');
+      },
+    },
+  },
+  {
+    label: '统计名称',
+    field: 'name',
+    component: 'Input',
+    colProps: { span: 24 },
+    required: true,
+  },
+  {
+    label: '统计时间',
+    field: 'date',
+    component: 'RangePicker',
+    colProps: { span: 24 },
+    required: true,
+    componentProps: {
+      getPopupContainer: () => document.body,
+      placeholder: ['开始时间', '结束时间'],
+      format: 'YYYY-MM-DD',
+      showTime: false,
+    },
+  },
+  {
+    label: '时间段',
+    field: 'timePeriod',
+    required: true,
+    colProps: { span: 24 },
+    component: 'Select',
+    defaultValue: 1,
+    componentProps: {
+      getPopupContainer: () => document.body,
+      options: timeIntervalOptions.filter((row) => row.value === 1 || row.value === 2),
+    },
+  },
+];
+
+export const formDetailSchema: BasicColumn[] = [
+  {
+    title: '教师姓名',
+    dataIndex: 'name',
+    align: 'left',
+    width: 80,
+  },
+  {
+    title: '工号',
+    dataIndex: 'userName',
+    align: 'left',
+  },
+  {
+    title: '部门',
+    dataIndex: 'deptName',
+    align: 'left',
+    width: 180,
+  },
+  {
+    title: '正常考勤次数',
+    dataIndex: 'normalCount',
+    align: 'left',
+    width: 150,
+  },
+  {
+    title: '请假次数',
+    dataIndex: 'leaveCount',
+    align: 'left',
+    width: 150,
+  },
+  {
+    title: '旷工次数',
+    dataIndex: 'absenteeCount',
+    align: 'left',
+    width: 80,
+  },
+];

+ 118 - 0
src/views/attendance/teacherStatistics/index.vue

@@ -0,0 +1,118 @@
+<template>
+  <PageWrapper dense contentFullHeight fixedHeight contentClass="flex">
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button type="primary" @click="handelEdit">新增统计</a-button>
+      </template>
+      <template #action="{ record }">
+        <TableAction
+          :actions="[
+            {
+              label: '查看',
+              disabled: record.status === 0,
+              onClick: handleView.bind(null, record),
+            },
+            {
+              label: '作废',
+              disabled: record.status !== 1,
+              onClick: handleStatus.bind(null, record),
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <EditFrom @register="registerModal" @success="handleSuccess" />
+    <DetailFrom @register="registerDetailModal" @success="handleSuccess" />
+  </PageWrapper>
+</template>
+
+<script setup lang="ts">
+  import { PageWrapper } from '/@/components/Page';
+  import { searchStatisticsFormSchema, tableStatisticsColumns } from './data.config';
+  import { useModal } from '/@/components/Modal';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table';
+  import EditFrom from './components/edit.vue';
+  import DetailFrom from './components/detail.vue';
+  import {
+    getStatisticsPage,
+    postStatisticsChangeStatus,
+  } from '/@/services/apis/AttendanceStatisticsController';
+  import { formatToDate } from '/@/utils/dateUtil';
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  const { createConfirm, createMessage } = useMessage();
+
+  const [registerModal, { openModal }] = useModal();
+  const [registerDetailModal, { openModal: openDetailModal }] = useModal();
+
+  const [registerTable, { reload }] = useTable({
+    api: getStatisticsPage,
+    title: '考勤汇总统计',
+    rowKey: 'id',
+    columns: tableStatisticsColumns,
+    formConfig: {
+      labelWidth: 120,
+      rowProps: {
+        gutter: 16,
+      },
+      schemas: searchStatisticsFormSchema,
+    },
+    useSearchForm: true,
+    showTableSetting: true,
+    bordered: true,
+    immediate: true,
+    canResize: true,
+    actionColumn: {
+      width: 120,
+      title: '操作',
+      dataIndex: 'action',
+      slots: { customRender: 'action' },
+      fixed: 'right',
+    },
+    beforeFetch: async (params) => {
+      if (params['date']) {
+        params['year'] = formatToDate(params['date'], 'YYYY');
+        params['month'] = formatToDate(params['date'], 'MM');
+        delete params['date'];
+      }
+      return params;
+    },
+  });
+
+  const handleSuccess = () => {
+    reload();
+  };
+
+  const handleStatus = (record: any) => {
+    createConfirm({
+      iconType: 'warning',
+      title: '温馨提醒',
+      content: '是否作废该记录?',
+      onOk: async () => {
+        try {
+          await postStatisticsChangeStatus({
+            id: record.id,
+            status: 2,
+          });
+          createMessage.success('作废成功');
+          await reload();
+        } catch (e) {
+          createMessage.error('作废失败');
+        }
+      },
+      okText: '确认',
+      cancelText: '取消',
+    });
+  };
+
+  const handelEdit = () => {
+    openModal(true, {
+      isUpdate: false,
+      baseData: {},
+    });
+  };
+
+  const handleView = (record: any) => {
+    openDetailModal(true, { baseData: { ...record } });
+  };
+</script>