Bläddra i källkod

fix: add course measure

DESKTOP-USV654P\pc 5 dagar sedan
förälder
incheckning
7a935aa942

+ 1 - 1
Makefile

@@ -12,7 +12,7 @@ build:
 
 commit:
 	git add . && \
-	git commit --no-verify -m "fix: change maintenance"
+	git commit --no-verify -m "fix: add course measure"
 
 checkPre:
 	git checkout pre

+ 60 - 0
src/services/apis/CourseClassTimeStatisticsController.ts

@@ -0,0 +1,60 @@
+// @ts-ignore
+/* eslint-disable */
+
+// 该文件自动生成,请勿手动修改!
+import { defHttp } from '/@/utils/http/axios';
+import { ErrorMessageMode } from '/#/axios';
+            // --------------------------------------------------------------------------
+            // Course Class Time Statistics Controller
+            // --------------------------------------------------------------------------
+
+            ;
+
+
+            /** 新增课时统计 POST /courseClasstime/classTimeStatistics */
+export async function postCourseClasstimeClassTimeStatistics(params:API.AddClassTimeStatisticsDto
+,mode: ErrorMessageMode = 'modal'){ return defHttp.post<any>
+        ({url: '/courseClasstime/classTimeStatistics', data:params},{errorMessageMode:mode});}
+/** 修改课时统计 PUT /courseClasstime/classTimeStatistics */
+export async function putCourseClasstimeClassTimeStatistics(params:API.UpdateClassTimeStatisticsDto
+,mode: ErrorMessageMode = 'modal'){ return defHttp.put<any>
+        ({url: '/courseClasstime/classTimeStatistics', data:params},{errorMessageMode:mode});}
+/** 删除课时统计 DELETE /courseClasstime/classTimeStatistics */
+export async function deleteCourseClasstimeClassTimeStatistics(params:string[],mode: ErrorMessageMode = 'modal'){ return defHttp.delete<any>
+        ({url: '/courseClasstime/classTimeStatistics', data:params},{errorMessageMode:mode});}
+/** 修改状态 POST /courseClasstime/classTimeStatistics/change-status */
+export async function postClassTimeStatisticsChangeStatus(params:API.ChangeStatusDto
+,mode: ErrorMessageMode = 'modal'){ return defHttp.post<any>
+        ({url: '/courseClasstime/classTimeStatistics/change-status', data:params},{errorMessageMode:mode});}
+/** 导出 POST /courseClasstime/classTimeStatistics/export-query */
+export async function postClassTimeStatisticsExportQuery(params:API.RefreshStatisticsDto
+,mode: ErrorMessageMode = 'modal'){ return defHttp.download<string>
+        ({url: '/courseClasstime/classTimeStatistics/export-query',responseType:'blob',method:'POST', data:params},{errorMessageMode:mode});}
+/** 导入 POST /courseClasstime/classTimeStatistics/import */
+export async function postClassTimeStatisticsImport(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.post<any>
+        ({url: '/courseClasstime/classTimeStatistics/import',headers:{'Content-Type':'multipart/form-data'}, data:params},{errorMessageMode:mode});}
+/** 根据id查询课时统计信息 GET /courseClasstime/classTimeStatistics/info */
+export async function getClassTimeStatisticsInfo(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<API.ClassTimeStatisticsVo>
+        ({url: '/courseClasstime/classTimeStatistics/info', params:params},{errorMessageMode:mode});}
+/** 查询最新统计的结束日期 GET /courseClasstime/classTimeStatistics/last-date */
+export async function getClassTimeStatisticsLastDate(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<API.LocalDate>
+        ({url: '/courseClasstime/classTimeStatistics/last-date', params:params},{errorMessageMode:mode});}
+/** 修改超工作量基数 POST /courseClasstime/classTimeStatistics/over-workload-number */
+export async function postClassTimeStatisticsOverWorkloadNumber(params:API.UpdateOverworkloadNumberDto
+,mode: ErrorMessageMode = 'modal'){ return defHttp.post<any>
+        ({url: '/courseClasstime/classTimeStatistics/over-workload-number', data:params},{errorMessageMode:mode});}
+/** 修改超工作量基数状态 POST /courseClasstime/classTimeStatistics/over-workload-number-status */
+export async function postClassTimeStatisticsOverWorkloadNumberStatus(params:API.ChangeStatusDto
+,mode: ErrorMessageMode = 'modal'){ return defHttp.post<any>
+        ({url: '/courseClasstime/classTimeStatistics/over-workload-number-status', data:params},{errorMessageMode:mode});}
+/** 课时统计列表(分页) GET /courseClasstime/classTimeStatistics/page */
+export async function getClassTimeStatisticsPage(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<API.PageOutput<API.ClassTimeStatisticsPageVo>>
+        ({url: '/courseClasstime/classTimeStatistics/page', params:params},{errorMessageMode:mode});}
+/** 课程课时统计详情列表 GET /courseClasstime/classTimeStatistics/record-list */
+export async function getClassTimeStatisticsRecordList(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<API.CourseClassTimeStatisticsRecordVo[]>
+        ({url: '/courseClasstime/classTimeStatistics/record-list', params:params},{errorMessageMode:mode});}
+/** 刷新课表课时统计 POST /courseClasstime/classTimeStatistics/refresh-statistics */
+export async function postClassTimeStatisticsRefreshStatistics(params:API.RefreshStatisticsDto
+,mode: ErrorMessageMode = 'modal'){ return defHttp.post<any>
+        ({url: '/courseClasstime/classTimeStatistics/refresh-statistics', data:params},{errorMessageMode:mode});}
+            

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

@@ -33,6 +33,9 @@ export async function getScheduleCurrentWeek(params:any,mode: ErrorMessageMode =
 /** 查询课程接口 GET /schedule/schedule/getCourseNames */
 export async function getScheduleGetCourseNames(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<any>
         ({url: '/schedule/schedule/getCourseNames', params:params},{errorMessageMode:mode});}
+/** 判断登录者是否有查看所有课表的权限 GET /schedule/schedule/is-contains-all */
+export async function getScheduleIsContainsAll(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<any>
+        ({url: '/schedule/schedule/is-contains-all', params:params},{errorMessageMode:mode});}
 /** 获取单点登录地址 GET /schedule/schedule/login-url */
 export async function getScheduleLoginUrl(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<any>
         ({url: '/schedule/schedule/login-url', params:params},{errorMessageMode:mode});}
@@ -43,7 +46,7 @@ export async function getSchedulePreCheck(params:any,mode: ErrorMessageMode = 'm
 export async function getScheduleReceiveMsg(params:any,mode: ErrorMessageMode = 'modal'){ return defHttp.get<API.课表发布消息接收>
         ({url: '/schedule/schedule/receive-msg', params:params},{errorMessageMode:mode});}
 /** 按周导出课表 POST /schedule/schedule/schedule-week-export-query */
-export async function postScheduleScheduleWeekExportQuery(params:API.ScheduleWeekExportQueryDto
+export async function postScheduleScheduleWeekExportQuery(params:API.CourseTableDto
 ,mode: ErrorMessageMode = 'modal'){ return defHttp.download<string>
         ({url: '/schedule/schedule/schedule-week-export-query',responseType:'blob',method:'POST', data:params},{errorMessageMode:mode});}
 /** 获取周次列表 GET /schedule/schedule/week-list */

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

@@ -35,6 +35,7 @@ import * as ClassTimeDeleteController from './ClassTimeDeleteController';
 import * as ClassTimeStatisticsController from './ClassTimeStatisticsController';
 import * as ClassTimeStatisticsSetController from './ClassTimeStatisticsSetController';
 import * as ConcatController from './ConcatController';
+import * as CourseClassTimeStatisticsController from './CourseClassTimeStatisticsController';
 import * as SecondCourseController from './SecondCourseController';
 import * as CourseTableController from './CourseTableController';
 import * as DataboardController from './DataboardController';
@@ -286,6 +287,8 @@ ClassTimeStatisticsController,
 ClassTimeStatisticsSetController,
 /** Concat Controller */
 ConcatController,
+/** Course Class Time Statistics Controller */
+CourseClassTimeStatisticsController,
 /** Second Course Controller */
 SecondCourseController,
 /** Course Table Controller */

+ 81 - 35
src/services/typing.d.ts

@@ -1880,7 +1880,7 @@ year?: number;
 }
 
 type AddClassTimeStatisticsSetDto = {
-/** 类别(1:权重 2:费用设置) */
+/** 类别(1:权重 2:费用设置 3:课时量权重) */
 category?: number;
 /** 权重内容 */
 jsonContent?: string;
@@ -8393,12 +8393,18 @@ endDate?: string;
 id?: string;
 /** 统计月份 */
 month?: number;
+/** 超工作量基数 */
+overWorkloadNumber?: number;
+/** 超工作量基数状态(1:锁定 0:解锁) */
+overWorkloadNumberStatus?: number;
 /** 开始日期 */
 startDate?: string;
 /** 状态(0:已解锁 1:已锁定) */
 status?: number;
 /** 总金额 */
 totalAmount?: number;
+/** 总周次 */
+weeks?: number;
 /** 权重json */
 weightSetJson?: string;
 /** 统计年份 */
@@ -8727,6 +8733,37 @@ issn?: string;
 version?: string;
 }
 
+type CourseClassTimeStatisticsRecordVo = {
+/** 总课时 */
+allClassTime?: string;
+/** 总周次 */
+classTime1?: number;
+/** 周平均课时 */
+classTime2?: string;
+/** 行政工作量 */
+classTime3?: string;
+/** 总平均课时 */
+classTime4?: string;
+/** 工作量得分 */
+classTime5?: string;
+/** 超工作量得分 */
+classTime6?: string;
+/** 早自习 */
+classTime7?: string;
+/** 正课 */
+classTime8?: string;
+/** 晚辅 */
+classTime9?: string;
+/** 聘用类型 */
+employTypeCn?: string;
+/** 主键编号 */
+id?: string;
+/** 姓名 */
+name?: string;
+/** 工号 */
+userName?: string;
+}
+
 type CourseCountListVo = {
 /** 总课时 */
 courseCount?: string;
@@ -8800,6 +8837,39 @@ replaceCount?: number;
 teacherCount?: number;
 }
 
+type CourseTableDto = {
+/** 班级id(课表类型为class时使用) */
+classId?: string;
+/** 课表类型(teacher:教师,class:班级) */
+courseType?: string;
+/** 结束时间 */
+endDate?: string;
+/** 年级id(课表类型为class时使用) */
+gradeId?: string;
+/** 工号(课表类型为teachar时使用) */
+jobNumber?: string;
+/** 专业方向id(课表类型为class时使用) */
+majorSetId?: string;
+/** 当前日期 */
+scheduleDate?: string;
+/** 学期管理 */
+semesterId?: string;
+/** 开始时间 */
+startDate?: string;
+/** 学生id(家长登录时使用) */
+studentId?: string;
+/** 教师id */
+teacherId?: string;
+/** 姓名(课表类型为teachar时使用) */
+teacherName?: string;
+/** 当前日期 */
+toDay?: string;
+/** 周次 */
+week?: number;
+/** 周几 */
+weekDay?: number;
+}
+
 type CourseTableVo = {
 /** 总课时 */
 classHour?: number;
@@ -10485,9 +10555,7 @@ year?: number;
 type ImportConfig = {
 fieldName?: string;
 label?: string;
-/** 是否必填 */
 required?: boolean;
-/** 排序码 */
 sortCode?: number;
 width?: number;
 }
@@ -15184,37 +15252,6 @@ startDate?: string;
 startTime?: string;
 }
 
-type ScheduleWeekExportQueryDto = {
-/** 班级id(课表类型为class时使用) */
-classId?: string;
-/** 课表类型(teacher:教师,class:班级) */
-courseType?: string;
-/** 结束时间 */
-endDate?: string;
-/** 年级id(课表类型为class时使用) */
-gradeId?: string;
-/** 工号(课表类型为teachar时使用) */
-jobNumber?: string;
-/** 专业方向id(课表类型为class时使用) */
-majorSetId?: string;
-/** 学期管理 */
-semesterId?: string;
-/** 开始时间 */
-startDate?: string;
-/** 学生id(家长登录时使用) */
-studentId?: string;
-/** 教师id */
-teacherId?: string;
-/** 姓名(课表类型为teachar时使用) */
-teacherName?: string;
-/** 当前日期 */
-toDay?: string;
-/** 周次 */
-week?: number;
-/** 周几 */
-weekDay?: number;
-}
-
 type ScheduleWeekVo = {
 /** 结束时间 */
 endDate?: string;
@@ -16662,6 +16699,8 @@ remark?: string;
 status?: number;
 /** 奖项id(teacher_award_item) */
 teacherAwardItemId?: string;
+/** 奖项分数 */
+teacherAwardItemScore?: string;
 /** 完整的奖项名称 */
 wholeCompetitionName?: string;
 }
@@ -19217,7 +19256,7 @@ year?: number;
 }
 
 type UpdateClassTimeStatisticsSetDto = {
-/** 类别(1:权重 2:费用设置) */
+/** 类别(1:权重 2:费用设置 3:课时量权重) */
 category?: number;
 /** 主键编号 */
 id?: string;
@@ -19898,6 +19937,13 @@ OrderPriceCount?: number;
 OrderProduct?: OrderProductDto[];
 }
 
+type UpdateOverworkloadNumberDto = {
+/** id */
+id?: string;
+/** 超工作量基数 */
+overWorkloadNumber?: number;
+}
+
 type UpdatePasswordDto = {
 confirmPassword?: string;
 newPassword?: string;

+ 261 - 0
src/views/course/measure/components/detail.vue

@@ -0,0 +1,261 @@
+<template>
+  <BasicModal
+    @ok="handleSubmit"
+    :destroyOnClose="true"
+    :maskClosable="false"
+    v-bind="$attrs"
+    @register="registerModal"
+    :title="getTitle"
+    :width="1002"
+    showFooter
+    :defaultFullscreen="true"
+    :show-cancel-btn="false"
+  >
+    <div class="h-full">
+      <BasicTable @register="registerTable">
+        <template #toolbar>
+          <div class="flex-1 flex items-center">
+            <div class="flex flex-1 items-center justify-center text-lg font-bold">
+              <a-button
+                type="primary"
+                class="mr-[48px]"
+                :disabled="dataSoruce.index === 0"
+                @click="handelNex(0)"
+              >
+                上一个
+              </a-button>
+              <Space>
+                <div>{{ modelRef?.userName }}</div>
+                <div>{{ modelRef?.name }}</div>
+                <div>{{ modelRef?.employTypeCn }}</div>
+              </Space>
+              <a-button
+                type="primary"
+                class="ml-[48px]"
+                :disabled="dataSoruce.index >= dataSoruce.data.length - 1"
+                @click="handelNex(1)"
+              >
+                下一个
+              </a-button>
+            </div>
+            <div class="flex items-center">
+              <Space>
+                <a-button type="primary" @click="handleExport">导出</a-button>
+                <div class="flex items-center">
+                  <div
+                    style="width: 10px; height: 10px; border-radius: 50%; background-color: #facd91"
+                  ></div>
+                  <div class="ml-2">调课</div>
+                </div>
+                <div class="flex items-center">
+                  <div
+                    style="width: 10px; height: 10px; border-radius: 50%; background-color: #ec808d"
+                  ></div>
+                  <div class="ml-2">顶课</div>
+                </div>
+              </Space>
+            </div>
+          </div>
+        </template>
+        <template #allClassTime="{ text }">
+          <div>{{ text }}</div>
+        </template>
+      </BasicTable>
+    </div>
+  </BasicModal>
+</template>
+<script setup lang="ts">
+  import { ref, computed, unref, h, reactive } from 'vue';
+  import { Space } from 'ant-design-vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { useTable, BasicTable, BasicColumn } from '/@/components/Table';
+  import {
+    postClassTimeStatisticsRecordDetailExportQuery,
+    getClassTimeStatisticsRecordDetailInfo,
+  } from '/@/services/apis/ClassTimeStatisticsController';
+  import { formatToDate } from '/@/utils/dateUtil';
+  import { downloadByData } from '/@/utils/file/download';
+
+  const isUpdate = ref(true);
+  const modelRef = ref<Recordable>({});
+  const dataSoruce = reactive({
+    data: [],
+    index: 0,
+  });
+  const emit = defineEmits(['success', 'register']);
+
+  const [registerTable, { setTableData, setColumns, redoHeight }] = useTable({
+    title: '课时统计表',
+    rowKey: 'id',
+    columns: [
+      {
+        title: '课程名称',
+        dataIndex: 'userName',
+        align: 'left',
+        width: 80,
+      },
+    ],
+    useSearchForm: false,
+    showTableSetting: true,
+    bordered: true,
+    immediate: false,
+    canResize: true,
+    pagination: false,
+    showIndexColumn: false,
+    resizeHeightOffset: 80,
+  });
+
+  const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
+    setModalProps({ confirmLoading: true });
+    isUpdate.value = false;
+    dataSoruce.data = data.baseData.data;
+    dataSoruce.index = data.baseData.index;
+
+    await loadData();
+  });
+
+  const loadData = async () => {
+    setModalProps({ confirmLoading: true, loading: true });
+    modelRef.value = dataSoruce.data[dataSoruce.index];
+    const resData = await getClassTimeStatisticsRecordDetailInfo({ id: unref(modelRef).id });
+
+    const columns: BasicColumn[] = [
+      {
+        title: '节次',
+        dataIndex: 'timeName',
+        align: 'left',
+        width: 120,
+      },
+    ];
+    const dataList: Recordable[] = [];
+    try {
+      if (resData) {
+        const { rowTitle, columnTitle, data, allClassTime } = resData;
+
+        const totalRow: Recordable = { timeName: '合计课时' };
+
+        rowTitle.forEach((item, index) => {
+          columns.push({
+            title: item.scheduleDate,
+            dataIndex: `day${index}`,
+            width: 120,
+            customRender: formatDay,
+          });
+        });
+
+        columnTitle.forEach((col) => {
+          let row: Recordable = {
+            timeName: col.type,
+          };
+
+          rowTitle.forEach((item, index) => {
+            const filter = data.filter(
+              (f) =>
+                formatToDate(f.scheduleDate, 'MM.DD') === `${item.scheduleDate}` &&
+                f.type === col.type,
+            );
+
+            const colName = `day${index}`;
+
+            totalRow[colName] = {
+              courseName: item.content === 0 || item.content === '0.0' ? '' : item.content,
+            };
+
+            row[colName] = {};
+            row['total'] = col.content === 0 || col.content === '0.0' ? '' : col.content;
+
+            if (filter.length > 0) {
+              const filterItem = filter[0];
+              if (filterItem.content) {
+                const contents = filterItem.content.split(',');
+                const courseName = contents[0];
+                let className = '';
+                if (contents.length > 1) {
+                  className = contents[1];
+                }
+
+                row[colName]['courseName'] = courseName === '0.0' ? '' : courseName;
+                row[colName]['className'] = className;
+
+                if (filterItem.adjustType === 'course_exchange') {
+                  row[colName]['color'] = '#facd91';
+                }
+                if (filterItem.adjustType === 'course_substitute') {
+                  row[colName]['color'] = '#ec808d';
+                }
+              }
+            }
+          });
+
+          dataList.push(row);
+        });
+
+        totalRow['total'] = allClassTime;
+        dataList.push(totalRow);
+
+        // console.log('dataList', dataList);
+      }
+
+      columns.push({
+        title: '合计(课时)',
+        dataIndex: 'total',
+        width: 90,
+      });
+
+      setColumns(columns);
+      setTableData(dataList);
+
+      setTimeout(() => {
+        redoHeight();
+      }, 500);
+    } finally {
+      setModalProps({ confirmLoading: false, loading: false });
+    }
+  };
+
+  const formatDay = ({ text }) => {
+    let style = {};
+    if (text && text.color) {
+      style['backgroundColor'] = text.color;
+      style['color'] = '#fff';
+    }
+    return h('div', { style: style }, [
+      h('div', null, text?.courseName || ''),
+      h('div', null, text?.className || ''),
+    ]);
+  };
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '课时明细' : '课时明细'));
+  const handleSubmit = async () => {
+    try {
+      closeModal();
+      emit('success');
+    } finally {
+      setModalProps({ confirmLoading: false });
+    }
+  };
+
+  const handelNex = async (type) => {
+    if (type === 0) {
+      if (dataSoruce.index > 0) {
+        dataSoruce.index -= 1;
+      }
+    } else {
+      if (dataSoruce.index < dataSoruce.data.length - 1) {
+        dataSoruce.index += 1;
+      }
+    }
+    await loadData();
+  };
+
+  const handleExport = async () => {
+    setModalProps({ loading: true, confirmLoading: true });
+    downloadByData(
+      (await postClassTimeStatisticsRecordDetailExportQuery({ id: unref(modelRef).id })).data,
+      `课时明细统计表${formatToDate(new Date())}.xlsx`,
+    );
+    setModalProps({ loading: false, confirmLoading: false });
+  };
+</script>
+
+<style scoped lang="less"></style>

+ 237 - 0
src/views/course/measure/components/edit.vue

@@ -0,0 +1,237 @@
+<template>
+  <BasicModal
+    @ok="handleSubmit"
+    :destroyOnClose="true"
+    :maskClosable="false"
+    v-bind="$attrs"
+    @register="registerModal"
+    :title="getTitle"
+    :width="1002"
+    showFooter
+    :defaultFullscreen="true"
+  >
+    <div>
+      <div style="height: 122px">
+        <Descriptions bordered>
+          <Descriptions.Item label="年月">
+            {{ modelRef?.year }}年{{ modelRef?.month }}月
+          </Descriptions.Item>
+          <Descriptions.Item label="时间">
+            {{ formatToDate(modelRef?.startDate) }}~{{ formatToDate(modelRef?.endDate) }}
+          </Descriptions.Item>
+          <Descriptions.Item label="总课时">
+            {{ modelRef?.allClassTime }}
+          </Descriptions.Item>
+          <Descriptions.Item label="总周次">
+            {{ modelRef?.weeks }}
+          </Descriptions.Item>
+          <Descriptions.Item label="超工作量基数">
+            <div class="flex">
+              <InputNumber
+                v-model:value="state.overWorkloadNumber"
+                style="width: 100px"
+                :disabled="state.overWorkloadNumberStatus === 1"
+                :min="0"
+              />
+              <a-button
+                class="ml-2"
+                type="primary"
+                v-if="state.overWorkloadNumberStatus === 0"
+                @click="handelChangeNumber"
+              >
+                修改
+              </a-button>
+              <a-button
+                class="ml-2"
+                type="primary"
+                :disabled="state.overWorkloadNumberStatus === 1"
+                @click="handelStatus"
+              >
+                锁定
+              </a-button>
+            </div>
+          </Descriptions.Item>
+        </Descriptions>
+      </div>
+      <div style="height: calc(100% - 122px)">
+        <BasicTable @register="registerTable" :searchInfo="searchInfo">
+          <template #toolbar>
+            <a-button type="primary" @click="handelRefrresh" :disabled="modelRef?.status === 3">
+              刷新统计
+            </a-button>
+            <a-button type="primary" @click="handelImport">行政工作量导入</a-button>
+            <a-button type="primary" @click="handleWeight">权重设置</a-button>
+            <a-button type="primary" @click="handleExport">导出</a-button>
+          </template>
+        </BasicTable>
+      </div>
+    </div>
+    <FormWeight @register="registerWeightModal" />
+    <FormImport @register="registerImportModal" @success="handleSuccess" />
+  </BasicModal>
+</template>
+<script setup lang="ts">
+  import { ref, computed, unref, reactive } from 'vue';
+  import { BasicModal, useModal, useModalInner } from '/@/components/Modal';
+  import { formDetailSchema } from '../data.config';
+  import {
+    getClassTimeStatisticsInfo,
+    getClassTimeStatisticsRecordList,
+    postClassTimeStatisticsChangeStatus,
+    postClassTimeStatisticsExportQuery,
+    postClassTimeStatisticsImport,
+    postClassTimeStatisticsOverWorkloadNumber,
+    postClassTimeStatisticsOverWorkloadNumberStatus,
+    postClassTimeStatisticsRefreshStatistics,
+  } from '/@/services/apis/CourseClassTimeStatisticsController';
+  import { useTable, BasicTable } from '/@/components/Table';
+  import { Descriptions, InputNumber } from 'ant-design-vue';
+  import FormWeight from './weight.vue';
+  import { formatToDate } from '/@/utils/dateUtil';
+  import { downloadByData } from '/@/utils/file/download';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import FormImport from '/@/views/sys/import/index.vue';
+
+  const isUpdate = ref(true);
+  const modelRef = ref<Recordable>({});
+  const searchInfo = reactive<Recordable>({});
+  const emit = defineEmits(['success', 'register']);
+  const [registerWeightModal, { openModal: openWeightModal }] = useModal();
+
+  const [registerImportModal, { openModal: openImportModal }] = useModal();
+
+  const { createMessage } = useMessage();
+  const state = reactive({
+    status: 0,
+    overWorkloadNumber: 0,
+    overWorkloadNumberStatus: 0,
+  });
+
+  const [registerTable, { reload }] = useTable({
+    api: getClassTimeStatisticsRecordList,
+    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,
+      okText: data.baseData.status === 1 || data.baseData.status === 2 ? '锁定' : '解锁',
+      cancelText: '关闭',
+      okButtonProps: { disabled: data.baseData.status === 0 },
+    });
+    isUpdate.value = false;
+    state.status = data.baseData.status;
+
+    const resData = await getClassTimeStatisticsInfo({ id: data.baseData.id });
+    modelRef.value = { ...resData };
+    state.overWorkloadNumber = resData.overWorkloadNumber || 0;
+    state.overWorkloadNumberStatus = resData.overWorkloadNumberStatus || 0;
+    searchInfo.id = data.baseData.id;
+
+    reload();
+    setModalProps({ confirmLoading: false });
+  });
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '统计明细' : '统计明细'));
+  const handleSubmit = async () => {
+    try {
+      setModalProps({ loading: true, confirmLoading: true });
+      await postClassTimeStatisticsChangeStatus({
+        id: modelRef.value.id,
+        status: state.status === 3 ? 2 : 3,
+      });
+      closeModal();
+
+      emit('success');
+    } finally {
+      setModalProps({ loading: false, confirmLoading: false });
+    }
+  };
+
+  const handleWeight = () => {
+    openWeightModal(true, {
+      isUpdate: true,
+      baseData: {
+        classTimeStatisticsId: unref(modelRef).id,
+        jsonContent: unref(modelRef).weightSetJson,
+      },
+    });
+  };
+
+  const handelRefrresh = async () => {
+    try {
+      setModalProps({ loading: true, confirmLoading: true });
+      await postClassTimeStatisticsRefreshStatistics({ id: unref(modelRef).id });
+      closeModal();
+
+      emit('success');
+    } finally {
+      setModalProps({ loading: false, confirmLoading: false });
+    }
+  };
+
+  const handleExport = async () => {
+    setModalProps({ loading: true, confirmLoading: true });
+    downloadByData(
+      (await postClassTimeStatisticsExportQuery({ id: unref(modelRef).id })).data,
+      `课时量统计${formatToDate(new Date())}.xlsx`,
+    );
+    setModalProps({ loading: false, confirmLoading: false });
+  };
+
+  const handelStatus = async () => {
+    try {
+      setModalProps({ loading: true, confirmLoading: true });
+      await postClassTimeStatisticsOverWorkloadNumberStatus({ id: unref(modelRef).id, status: 1 });
+      state.overWorkloadNumberStatus = 1;
+      createMessage.success('锁定成功');
+    } catch {
+      createMessage.error('锁定失败');
+    } finally {
+      setModalProps({ loading: false, confirmLoading: false });
+    }
+  };
+
+  const handelChangeNumber = async () => {
+    try {
+      setModalProps({ loading: true, confirmLoading: true });
+      await postClassTimeStatisticsOverWorkloadNumber({
+        id: unref(modelRef).id,
+        overWorkloadNumber: state.overWorkloadNumber,
+      });
+      createMessage.success('修改成功');
+    } catch {
+      createMessage.error('修改失败');
+    } finally {
+      setModalProps({ loading: false, confirmLoading: false });
+    }
+  };
+
+  const handelImport = () => {
+    openImportModal(true, {
+      baseData: {
+        title: '行政工作量',
+        upload: postClassTimeStatisticsImport,
+        params: {},
+        errorName: '行政工作量导入错误数据',
+        templatePath:
+          'https://zhxy.cqtlzjzx.com/minio/static/resources/%E8%A1%8C%E6%94%BF%E5%B7%A5%E4%BD%9C%E9%87%8F%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.xlsx',
+        templateName: '行政工作量模板',
+      },
+    });
+  };
+
+  const handleSuccess = () => {
+    reload();
+  };
+</script>
+
+<style scoped lang="less"></style>

+ 104 - 0
src/views/course/measure/components/select.vue

@@ -0,0 +1,104 @@
+<template>
+  <BasicModal
+    @ok="handleSubmit"
+    :destroyOnClose="true"
+    :maskClosable="false"
+    v-bind="$attrs"
+    @register="registerModal"
+    :title="getTitle"
+    :width="1002"
+    showFooter
+  >
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<script setup lang="ts">
+  import { ref, computed, unref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+  import {
+    getClassTimeStatisticsLastDate,
+    postCourseClasstimeClassTimeStatistics,
+  } from '/@/services/apis/CourseClassTimeStatisticsController';
+  import { formatToDate } from '/@/utils/dateUtil';
+
+  const isUpdate = ref(true);
+  const emit = defineEmits(['success', 'register']);
+  const lastDate = ref('');
+
+  const [registerForm, { validate, resetFields, updateSchema }] = useForm({
+    labelWidth: 100,
+    schemas: [
+      {
+        label: '统计月份',
+        field: 'yearMonth',
+        component: 'DatePicker',
+        colProps: { span: 24 },
+        required: true,
+        componentProps: {
+          showTime: false,
+          format: 'YYYY-MM',
+          valueFormat: 'YYYY-MM',
+          getPopupContainer: () => document.body,
+        },
+      },
+      {
+        label: '统计时间',
+        field: 'date',
+        component: 'RangePicker',
+        colProps: { span: 24 },
+        required: true,
+        componentProps: {
+          getPopupContainer: () => document.body,
+          placeholder: ['开始时间', '结束时间'],
+          format: 'YYYY-MM-DD',
+          showTime: false,
+        },
+      },
+    ],
+    showActionButtonGroup: false,
+  });
+
+  const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
+    resetFields();
+    setModalProps({ confirmLoading: false });
+    isUpdate.value = !!data?.isUpdate;
+
+    lastDate.value = await getClassTimeStatisticsLastDate({});
+    if (lastDate.value) {
+      updateSchema({
+        field: 'date',
+        componentProps: {
+          disabledDate: disabledDate,
+        },
+      });
+    }
+  });
+
+  const disabledDate = (current) => {
+    return current && formatToDate(current) <= formatToDate(lastDate.value);
+  };
+
+  const getTitle = computed(() =>
+    !unref(isUpdate) ? '请选择需要统计课时量时间段' : '请选择需要统计课时量时间段',
+  );
+  const handleSubmit = async () => {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      const postParams: Recordable = {
+        year: formatToDate(values['yearMonth'], 'YYYY'),
+        month: formatToDate(values['yearMonth'], 'MM'),
+        startDate: values['date'][0],
+        endDate: values['date'][1],
+      };
+      await postCourseClasstimeClassTimeStatistics(postParams as API.AddClassTimeStatisticsDto);
+      closeModal();
+      emit('success');
+    } finally {
+      setModalProps({ confirmLoading: false });
+    }
+  };
+</script>
+
+<style scoped lang="less"></style>

+ 155 - 0
src/views/course/measure/components/weight.vue

@@ -0,0 +1,155 @@
+<template>
+  <BasicModal
+    @ok="handleSubmit"
+    :destroyOnClose="true"
+    :maskClosable="false"
+    v-bind="$attrs"
+    @register="registerModal"
+    :title="getTitle"
+    :width="1002"
+    showFooter
+  >
+    <BasicForm @register="registerForm">
+      <template #remark>
+        <div>说明:超工作量基数=超工作量得分÷(总平均课时-工作量得分);可以修改</div>
+      </template>
+    </BasicForm>
+  </BasicModal>
+</template>
+<script setup lang="ts">
+  import { ref, computed, unref, reactive } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import {
+    getClassTimeStatisticsSetLastInfo,
+    postClasstimeClassTimeStatisticsSet,
+    putClasstimeClassTimeStatisticsSet,
+  } from '/@/services/apis/ClassTimeStatisticsSetController';
+
+  const isUpdate = ref(true);
+  const category = ref(3);
+  const modelRef = ref<Recordable>({});
+  const emit = defineEmits(['success', 'register']);
+
+  const formConfig = reactive<Recordable[]>([
+    { label: '早自习', field: 'time1', addonAfter: '课时', value: '', span: 12 },
+    { label: '上1', field: 'time2', addonAfter: '课时', value: '', span: 12 },
+    { label: '上2', field: 'time3', addonAfter: '课时', value: '', span: 12 },
+    { label: '上3', field: 'time4', addonAfter: '课时', value: '', span: 12 },
+    { label: '上4', field: 'time5', addonAfter: '课时', value: '', span: 12 },
+    { label: '下1', field: 'time6', addonAfter: '课时', value: '', span: 12 },
+    { label: '下2', field: 'time7', addonAfter: '课时', value: '', span: 12 },
+    { label: '下3', field: 'time8', addonAfter: '课时', value: '', span: 12 },
+    { label: '下4', field: 'time9', addonAfter: '课时', value: '', span: 12 },
+    { label: '晚1', field: 'time10', addonAfter: '课时', value: '', span: 12 },
+    { label: '晚2', field: 'time11', addonAfter: '课时', value: '', span: 12 },
+    { label: '晚3', field: 'time12', addonAfter: '课时', value: '', span: 12 },
+    { label: '工作量得分', field: 'time13', addonAfter: '', value: '', span: 24 },
+    { label: '超工作量得分', field: 'time14', addonAfter: '', value: '', span: 24 },
+  ]);
+
+  const [registerForm, { validate, resetFields, resetSchema, setFieldsValue }] = useForm({
+    labelWidth: 100,
+    schemas: [],
+    showActionButtonGroup: false,
+  });
+
+  const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
+    resetFields();
+    setModalProps({ confirmLoading: false });
+    isUpdate.value = false;
+
+    const formWeightSchema: FormSchema[] = [];
+
+    formConfig.forEach((item) => {
+      formWeightSchema.push({
+        label: item.label,
+        field: item.field,
+        component: 'InputNumber',
+        colProps: { span: item.span },
+        required: true,
+        componentProps: {
+          addonAfter: item.addonAfter,
+          style: { width: '100%' },
+        },
+      });
+    });
+
+    // if (data?.baseData?.jsonContent) {
+    //   formWeightSchema.push({
+    //     label: '超工作量基数',
+    //     field: 'time15',
+    //     component: 'InputNumber',
+    //     colProps: { span: 24 },
+    //     required: true,
+    //     componentProps: {
+    //       style: { width: '100%' },
+    //     },
+    //   });
+
+    //   formWeightSchema.push({
+    //     label: ' ',
+    //     field: 'remark',
+    //     component: 'Title',
+    //     colProps: { span: 24 },
+    //     slot: 'remark',
+    //   });
+    // }
+
+    resetSchema(formWeightSchema);
+
+    if (data?.baseData?.jsonContent) {
+      modelRef.value = { ...data.baseData };
+      const jsonContent = JSON.parse(data.baseData.jsonContent);
+      const values: Recordable = {};
+      jsonContent.forEach((item) => {
+        values[item.field] = item.value;
+      });
+      setFieldsValue(values);
+    } else {
+      const resData = await getClassTimeStatisticsSetLastInfo({ category: category.value });
+      if (resData) {
+        modelRef.value = { ...resData };
+        const jsonContent = JSON.parse(resData.jsonContent);
+        const values: Recordable = {};
+        jsonContent.forEach((item) => {
+          values[item.field] = item.value;
+        });
+        setFieldsValue(values);
+        // isUpdate.value = true;
+      }
+    }
+  });
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '权重设置' : '权重设置'));
+  const handleSubmit = async () => {
+    try {
+      const values = await validate();
+      const jsonContent: Recordable[] = [];
+      setModalProps({ confirmLoading: true });
+
+      formConfig.forEach((item) => {
+        jsonContent.push({ label: item.label, field: item.field, value: values[item.field] });
+      });
+
+      const postParams: Recordable = {
+        category: category.value,
+        jsonContent: JSON.stringify(jsonContent),
+        id: unref(modelRef)?.id,
+      };
+
+      if (unref(isUpdate)) {
+        await putClasstimeClassTimeStatisticsSet(postParams as API.UpdateClassTimeStatisticsSetDto);
+      } else {
+        await postClasstimeClassTimeStatisticsSet(postParams as API.AddClassTimeStatisticsSetDto);
+      }
+
+      closeModal();
+      emit('success');
+    } finally {
+      setModalProps({ confirmLoading: false });
+    }
+  };
+</script>
+
+<style scoped lang="less"></style>

+ 167 - 0
src/views/course/measure/data.config.ts

@@ -0,0 +1,167 @@
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import { formatToDate } from '/@/utils/dateUtil';
+
+export const statusOptions = [
+  { label: '数据统计中', value: 0 },
+  { label: '统计完成', value: 1 },
+  { label: '已解锁', value: 2 },
+  { label: '已锁定', value: 3 },
+];
+
+export const tableColumns: BasicColumn[] = [
+  {
+    title: '年月',
+    dataIndex: 'year',
+    align: 'left',
+    width: 100,
+    customRender: ({ record }) => {
+      return `${record.year}年${record.month}月`;
+    },
+  },
+  {
+    title: '时间',
+    dataIndex: 'startDate',
+    align: 'left',
+    // width: 180,
+    customRender: ({ record }) => {
+      return `${formatToDate(record.startDate)}~${formatToDate(record.endDate)}`;
+    },
+  },
+  {
+    title: '总课时量',
+    dataIndex: 'allClassTime',
+    align: 'left',
+    width: 80,
+  },
+  {
+    title: '总周次',
+    dataIndex: 'weeks',
+    align: 'left',
+    width: 100,
+  },
+  {
+    title: '状态',
+    dataIndex: 'status',
+    align: 'left',
+    width: 90,
+    customRender: ({ text }) => {
+      return statusOptions.find((item) => item.value === text)?.label;
+    },
+  },
+];
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    field: 'date',
+    label: '年月',
+    component: 'DatePicker',
+    colProps: { span: 8 },
+    componentProps: {
+      showTime: false,
+      format: 'YYYY-MM',
+      valueFormat: 'YYYY-MM',
+      getPopupContainer: () => document.body,
+    },
+  },
+];
+
+export const formDetailSchema: BasicColumn[] = [
+  {
+    title: '工号',
+    dataIndex: 'userName',
+    align: 'left',
+    width: 80,
+  },
+  {
+    title: '姓名',
+    dataIndex: 'name',
+    align: 'left',
+  },
+  {
+    title: '聘用类型',
+    dataIndex: 'employTypeCn',
+    align: 'left',
+    width: 80,
+  },
+  {
+    title: '正课课时',
+    dataIndex: 'classTime8',
+    align: 'left',
+    width: 80,
+    customRender: ({ text }) => {
+      return text === 0 ? '' : text;
+    },
+  },
+  {
+    title: '晚辅',
+    dataIndex: 'classTime9',
+    align: 'left',
+    width: 80,
+    customRender: ({ text }) => {
+      return text === 0 ? '' : text;
+    },
+  },
+  {
+    title: '总课时',
+    dataIndex: 'allClassTime',
+    align: 'left',
+    width: 80,
+    customRender: ({ text }) => {
+      return text === 0 ? '' : text;
+    },
+  },
+  {
+    title: '总周次',
+    dataIndex: 'classTime1',
+    align: 'left',
+    width: 80,
+    customRender: ({ text }) => {
+      return text === 0 ? '' : text;
+    },
+  },
+  {
+    title: '周平均课时',
+    dataIndex: 'classTime2',
+    align: 'left',
+    width: 100,
+    customRender: ({ text }) => {
+      return text === 0 ? '' : text;
+    },
+  },
+  {
+    title: '行政工作量',
+    dataIndex: 'classTime3',
+    align: 'left',
+    width: 100,
+    customRender: ({ text }) => {
+      return text === 0 ? '' : text;
+    },
+  },
+  {
+    title: '总平均课时',
+    dataIndex: 'classTime4',
+    align: 'left',
+    width: 100,
+    customRender: ({ text }) => {
+      return text === 0 ? '' : text;
+    },
+  },
+  {
+    title: '工作量得分',
+    dataIndex: 'classTime5',
+    align: 'left',
+    width: 100,
+    customRender: ({ text }) => {
+      return text === 0 ? '' : text;
+    },
+  },
+  {
+    title: '超工作量得分',
+    dataIndex: 'classTime6',
+    align: 'left',
+    width: 110,
+    customRender: ({ text }) => {
+      return text === 0 ? '' : text;
+    },
+  },
+];

+ 132 - 0
src/views/course/measure/index.vue

@@ -0,0 +1,132 @@
+<template>
+  <PageWrapper dense contentFullHeight fixedHeight contentClass="flex">
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button type="primary" @click="handleAdd">新增</a-button>
+        <a-button type="primary" @click="handleWeight">权重设置</a-button>
+      </template>
+      <template #action="{ record }">
+        <TableAction
+          :actions="[
+            {
+              label: '查看',
+              disabled: record.status === 0,
+              onClick: handleView.bind(null, record),
+            },
+            {
+              label: '删除',
+              disabled: record.status === 0 || record.status === 3,
+              color: 'error',
+              onClick: handleDelete.bind(null, record),
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <FormSelect @register="registerSelectModal" @success="handleSuccess" />
+    <FormWeight @register="registerWeightModal" />
+    <FormEdit @register="registerModal" @success="handleSuccess" />
+  </PageWrapper>
+</template>
+
+<script setup lang="ts">
+  import { onMounted } from 'vue';
+  import { PageWrapper } from '/@/components/Page';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table';
+  import { tableColumns, searchFormSchema } from './data.config';
+
+  import { useModal } from '/@/components/Modal';
+  import {
+    deleteCourseClasstimeClassTimeStatistics,
+    getClassTimeStatisticsPage,
+  } from '/@/services/apis/CourseClassTimeStatisticsController';
+  import { formatToDate } from '/@/utils/dateUtil';
+  import FormSelect from './components/select.vue';
+  import FormWeight from './components/weight.vue';
+  import FormEdit from './components/edit.vue';
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  const [registerSelectModal, { openModal: openSelectModal }] = useModal();
+  const [registerWeightModal, { openModal: openWeightModal }] = useModal();
+  const [registerModal, { openModal }] = useModal();
+
+  const [registerTable, { reload }] = useTable({
+    api: getClassTimeStatisticsPage,
+    title: '课时量统计记录',
+    rowKey: 'id',
+    columns: tableColumns,
+    formConfig: {
+      labelWidth: 120,
+      schemas: searchFormSchema,
+    },
+    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 { createConfirm, createMessage } = useMessage();
+  const handleDelete = (record: any) => {
+    createConfirm({
+      iconType: 'warning',
+      title: '温馨提醒',
+      content: '是否删除该记录?',
+      onOk: async () => {
+        try {
+          await deleteCourseClasstimeClassTimeStatistics([record.id]);
+          createMessage.success('删除成功');
+          await reload();
+        } catch (e) {
+          createMessage.error('删除失败');
+        }
+      },
+      okText: '确认',
+      cancelText: '取消',
+    });
+  };
+
+  const handleAdd = () => {
+    openSelectModal(true, {
+      isUpdate: false,
+      baseData: {},
+    });
+  };
+
+  const handleWeight = () => {
+    openWeightModal(true, {
+      isUpdate: false,
+      baseData: {},
+    });
+  };
+
+  const handleView = (record: any) => {
+    openModal(true, {
+      isUpdate: true,
+      baseData: { ...record },
+    });
+  };
+
+  const handleSuccess = () => {
+    reload();
+  };
+
+  onMounted(async () => {});
+</script>
+
+<style scoped lang="less"></style>