Parcourir la source

feat: 评卷员管理

zhangjie il y a 3 semaines
Parent
commit
e42a8cd6c0

+ 3 - 0
src/api/types/mark.ts

@@ -192,6 +192,7 @@ export type MarkTrialListPageParam = PageParams<MarkTrialListFilter>;
 // 评卷员管理
 // 评卷员管理列表:登录名	姓名	科目	分组	状态	已评数量	正在评卷	任务数	绑定班级
 export interface MarkMarkerItem {
+  id: number;
   // 登录名
   loginName: string;
   // 姓名
@@ -221,6 +222,8 @@ export interface MarkMarkerListFilter {
   subject?: number | null;
   // 分组
   group?: string;
+  // 已评卷
+  marked?: boolean;
 }
 export type MarkMarkerListPageParam = PageParams<MarkMarkerListFilter>;
 

+ 425 - 0
src/views/mark/MarkerManage.vue

@@ -0,0 +1,425 @@
+<template>
+  <div class="part-box is-filter">
+    <el-form inline>
+      <el-form-item label="科目">
+        <select-subject v-model="searchModel.subject"></select-subject>
+      </el-form-item>
+      <el-form-item label="分组">
+        <el-input
+          v-model.trim="searchModel.group"
+          placeholder="请输入分组"
+          clearable
+          style="width: 120px"
+        >
+        </el-input>
+      </el-form-item>
+      <el-form-item label="登录名">
+        <el-input
+          v-model.trim="searchModel.loginName"
+          placeholder="请输入登录名"
+          clearable
+          style="width: 150px"
+        >
+        </el-input>
+      </el-form-item>
+      <el-form-item label="姓名">
+        <el-input
+          v-model.trim="searchModel.name"
+          placeholder="请输入姓名"
+          clearable
+          style="width: 120px"
+        >
+        </el-input>
+      </el-form-item>
+      <el-form-item>
+        <el-checkbox v-model.trim="searchModel.marked">已评卷 </el-checkbox>
+      </el-form-item>
+      <el-form-item>
+        <el-space wrap>
+          <el-button type="primary" @click="toPage(1)">查询</el-button>
+          <el-button @click="onImport">导入</el-button>
+          <el-button @click="onBatchEnable(true)">启用</el-button>
+          <el-button @click="onBatchEnable(false)">禁用</el-button>
+          <el-button @click="onBatchRecycle">回收</el-button>
+          <el-button @click="onBatchSetTaskCount">设置评卷数</el-button>
+          <el-button @click="onBatchResetPassword">重置密码</el-button>
+        </el-space>
+      </el-form-item>
+    </el-form>
+  </div>
+
+  <div class="part-box">
+    <el-table
+      class="page-table"
+      :data="dataList"
+      :loading="loading"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" />
+      <el-table-column property="loginName" label="登录名" width="120" />
+      <el-table-column property="name" label="姓名" width="100" />
+      <el-table-column property="subject" label="科目" width="120" />
+      <el-table-column property="group" label="分组" width="100" />
+      <el-table-column label="状态" width="100">
+        <template #default="scope">
+          <el-tag :type="getStatusType(scope.row.status)" size="small">
+            {{ getStatusText(scope.row.status) }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column property="completedCount" label="已评数量" width="100">
+        <template #default="scope">
+          <span>
+            {{ scope.row.completedCount }}
+          </span>
+          <el-button
+            type="danger"
+            size="small"
+            link
+            @click="onResetFinishCount(scope.row)"
+            >重置</el-button
+          >
+        </template>
+      </el-table-column>
+      <el-table-column label="正在评卷" width="100">
+        <template #default="scope">
+          <el-tag :type="scope.row.marking ? 'success' : 'info'" size="small">
+            {{ scope.row.marking ? '是' : '否' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column property="taskCount" label="任务数" width="100" />
+      <el-table-column
+        property="bindClass"
+        label="绑定班级"
+        min-width="150"
+        show-overflow-tooltip
+      >
+        <template #default="scope">
+          {{ scope.row.bindClass || '-' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="300" fixed="right">
+        <template #default="scope">
+          <el-button
+            v-if="scope.row.status === 'disabled'"
+            type="success"
+            size="small"
+            link
+            @click="onEnable(scope.row, true)"
+          >
+            启用
+          </el-button>
+          <el-button
+            v-if="scope.row.status === 'enabled'"
+            type="warning"
+            size="small"
+            link
+            @click="onEnable(scope.row, false)"
+          >
+            禁用
+          </el-button>
+          <el-button
+            type="danger"
+            size="small"
+            link
+            @click="onUnbind(scope.row)"
+          >
+            解绑
+          </el-button>
+          <el-button
+            type="primary"
+            size="small"
+            link
+            @click="onSetTaskCount(scope.row)"
+          >
+            设置评卷数
+          </el-button>
+          <el-button
+            type="info"
+            size="small"
+            link
+            @click="onRecycle(scope.row)"
+          >
+            回收
+          </el-button>
+          <el-button
+            type="warning"
+            size="small"
+            link
+            @click="onResetPassword(scope.row)"
+          >
+            重置密码
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-pagination
+      v-model:current-page="pagination.pageNumber"
+      v-model:page-size="pagination.pageSize"
+      :layout="pagination.layout"
+      :total="pagination.total"
+      @size-change="pageSizeChange"
+      @current-change="toPage"
+    />
+  </div>
+
+  <SetMarkCountDialog
+    ref="setMarkCountDialogRef"
+    :marker-ids="curMarkerIds"
+    @modified="getList"
+  />
+
+  <!-- 导入 -->
+  <ImportDialog
+    ref="importDialogRef"
+    title="导入数据"
+    upload-url="/api/admin/site/import"
+    :format="['xls', 'xlsx']"
+    :download-handle="downloadTemplate"
+    download-filename="评卷员导入模板.xlsx"
+  />
+</template>
+
+<script setup lang="ts">
+  import { reactive, ref, computed } from 'vue';
+  import { ElMessage } from 'element-plus';
+  import {
+    getMarkMarkerList,
+    markMarkerEnable,
+    markMarkerUnbind,
+    markMarkerTaskRecycle,
+    markMarkerResetPassword,
+    markMarkerResetFinishCount,
+  } from '@/api/mark';
+  import { MarkMarkerItem, MarkMarkerListFilter } from '@/api/types/mark';
+  import useTable from '@/hooks/table';
+  import { modalConfirm } from '@/utils/ui';
+
+  import SetMarkCountDialog from './components/SetMarkCountDialog.vue';
+
+  defineOptions({
+    name: 'MarkerManage',
+  });
+
+  const searchModel = reactive<MarkMarkerListFilter>({
+    subject: null,
+    group: '',
+    loginName: '',
+    name: '',
+    marked: false,
+  });
+
+  const setMarkCountDialogRef = ref<InstanceType<typeof SetMarkCountDialog>>();
+  const curMarkerIds = ref<number[]>([]);
+
+  const {
+    dataList,
+    pagination,
+    loading,
+    selectedRows,
+    toPage,
+    getList,
+    pageSizeChange,
+    handleSelectionChange,
+  } = useTable<MarkMarkerItem>(getMarkMarkerList, searchModel, false);
+
+  // 计算选中的评卷员ID列表
+  const selectedMarkerIds = computed(() => {
+    return selectedRows.value.map((row) => row.id);
+  });
+
+  // 获取状态类型
+  function getStatusType(status: string) {
+    const statusMap: Record<string, string> = {
+      enabled: 'success',
+      disabled: 'danger',
+      binding: 'warning',
+    };
+    return statusMap[status] || 'info';
+  }
+
+  // 获取状态文本
+  function getStatusText(status: string) {
+    const statusMap: Record<string, string> = {
+      enabled: '已启用',
+      disabled: '已禁用',
+      binding: '绑定中',
+    };
+    return statusMap[status] || status;
+  }
+
+  // 启用/禁用评卷员
+  async function onEnable(row: MarkMarkerItem, enable: boolean) {
+    const action = enable ? '启用' : '禁用';
+    const confirm = await modalConfirm(
+      `确认${action}评卷员 ${row.name}(${row.loginName}) 吗?`,
+      '提示 '
+    ).catch(() => false);
+    if (!confirm) return;
+
+    try {
+      await markMarkerEnable({ ids: [row.id], enable });
+      ElMessage.success(`${action}成功`);
+      getList();
+    } catch (error) {
+      console.error(`${action}评卷员失败:`, error);
+    }
+  }
+
+  // 批量启用/禁用
+  async function onBatchEnable(enable: boolean) {
+    if (!selectedRows.value.length) {
+      ElMessage.warning('请选择评卷员');
+      return;
+    }
+
+    const action = enable ? '启用' : '禁用';
+    const confirm = await modalConfirm(
+      `确认${action}所选的 ${selectedRows.value.length} 个评卷员吗?`,
+      '提示 '
+    ).catch(() => false);
+    if (!confirm) return;
+    try {
+      await markMarkerEnable({ ids: selectedMarkerIds.value, enable });
+      ElMessage.success(`批量${action}成功`);
+      getList();
+    } catch (error) {
+      console.error(`批量${action}评卷员失败:`, error);
+    }
+  }
+
+  // 解绑评卷员
+  async function onUnbind(row: MarkMarkerItem) {
+    const confirm = await modalConfirm(
+      `确认解绑评卷员 ${row.name}(${row.loginName}) 吗?`,
+      '提示 '
+    ).catch(() => false);
+    if (!confirm) return;
+
+    try {
+      await markMarkerUnbind(row.id);
+      ElMessage.success('解绑成功');
+      getList();
+    } catch (error) {
+      console.error('解绑评卷员失败:', error);
+    }
+  }
+
+  // 设置评卷数量
+  function onSetTaskCount(row: MarkMarkerItem) {
+    curMarkerIds.value = [row.id];
+    setMarkCountDialogRef.value?.open();
+  }
+
+  // 批量设置评卷数量
+  function onBatchSetTaskCount() {
+    curMarkerIds.value = selectedMarkerIds.value;
+    setMarkCountDialogRef.value?.open();
+  }
+
+  // 回收任务
+  async function onRecycle(row: MarkMarkerItem) {
+    const confirm = await modalConfirm(
+      `确认回收评卷员 ${row.name}(${row.loginName}) 的任务吗?`,
+      '提示 '
+    ).catch(() => false);
+    if (!confirm) return;
+
+    try {
+      await markMarkerTaskRecycle([row.id]);
+      ElMessage.success('回收成功');
+      getList();
+    } catch (error) {
+      console.error('回收任务失败:', error);
+    }
+  }
+
+  // 批量回收
+  async function onBatchRecycle() {
+    if (!selectedRows.value.length) {
+      ElMessage.warning('请选择评卷员');
+      return;
+    }
+
+    const confirm = await modalConfirm(
+      `确认回收所选的 ${selectedRows.value.length} 个评卷员的任务吗?`,
+      '提示 '
+    ).catch(() => false);
+    if (!confirm) return;
+
+    try {
+      await markMarkerTaskRecycle(selectedMarkerIds.value);
+      ElMessage.success('批量回收成功');
+      getList();
+    } catch (error) {
+      console.error('批量回收任务失败:', error);
+    }
+  }
+
+  // 重置密码
+  async function onResetPassword(row: MarkMarkerItem) {
+    const confirm = await modalConfirm(
+      `确认重置评卷员 ${row.name}(${row.loginName}) 的密码吗?`,
+      '提示 '
+    ).catch(() => false);
+    if (!confirm) return;
+
+    try {
+      await markMarkerResetPassword([row.id]);
+      ElMessage.success('重置密码成功');
+    } catch (error) {
+      console.error('重置密码失败:', error);
+    }
+  }
+
+  // 批量重置密码
+  async function onBatchResetPassword() {
+    if (!selectedRows.value.length) {
+      ElMessage.warning('请选择评卷员');
+      return;
+    }
+    const confirm = await modalConfirm(
+      `确认重置所选的 ${selectedRows.value.length} 个评卷员的密码吗?`,
+      '提示 '
+    ).catch(() => false);
+    if (!confirm) return;
+
+    try {
+      await markMarkerResetPassword(selectedMarkerIds.value);
+      ElMessage.success('批量重置密码成功');
+    } catch (error) {
+      console.error('批量重置密码失败:', error);
+    }
+  }
+
+  // 重置已评数量
+  async function onResetFinishCount(row: MarkMarkerItem) {
+    const confirm = await modalConfirm(
+      `确认重置评卷员 ${row.name}(${row.loginName}) 的已评数量吗?`,
+      '提示 '
+    ).catch(() => false);
+    if (!confirm) return;
+
+    try {
+      await markMarkerResetFinishCount(row.id);
+      ElMessage.success('重置已评数量成功');
+      getList();
+    } catch (error) {
+      console.error('重置已评数量失败:', error);
+    }
+  }
+
+  // 导入
+  const importDialogRef = ref();
+  const onImport = () => {
+    importDialogRef.value?.open();
+  };
+  async function downloadTemplate() {
+    // const res = await downloadByApi(() => agentTemplate()).catch((e) => {
+    //   Message.error(e || '下载失败,请重新尝试!');
+    // });
+    // if (!res) return;
+    // Message.success('下载成功!');
+  }
+</script>

+ 122 - 0
src/views/mark/components/SetMarkCountDialog.vue

@@ -0,0 +1,122 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    title="设置评卷数量"
+    width="500px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    top="10vh"
+    append-to-body
+    @close="handleClose"
+    @open="modalBeforeOpen"
+  >
+    <el-form
+      ref="formRef"
+      :model="formModel"
+      :rules="rules"
+      label-width="100px"
+    >
+      <el-form-item label="评卷数量" prop="count">
+        <el-input-number
+          v-model="formModel.count"
+          placeholder="请输入评卷数量"
+          :min="0"
+          :max="9999"
+          :step="1"
+          :controls="true"
+          step-strictly
+          style="width: 100%"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="close">取消</el-button>
+      <el-button type="primary" :loading="loading" @click="confirm">
+        确定
+      </el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive } from 'vue';
+  import type { FormInstance, FormRules } from 'element-plus';
+  import { ElMessage } from 'element-plus';
+  import { markMarkerSetTaskCount } from '@/api/mark';
+  import useModal from '@/hooks/modal';
+  import useLoading from '@/hooks/loading';
+  import { objAssign } from '@/utils/utils';
+
+  defineOptions({
+    name: 'SetMarkCountDialog',
+  });
+
+  /* modal */
+  const { visible, open, close } = useModal();
+  defineExpose({ open, close });
+
+  interface Props {
+    markerIds?: string[]; // 评卷员ID列表
+  }
+
+  const props = defineProps<Props>();
+  const emit = defineEmits(['modified']);
+
+  const formRef = ref<FormInstance>();
+
+  interface FormModel {
+    ids: string[];
+    count: number;
+  }
+
+  const initialFormState: FormModel = {
+    ids: [],
+    count: 0,
+  };
+
+  const formModel = reactive<FormModel>({ ...initialFormState });
+
+  const rules: FormRules<keyof FormModel> = {
+    count: [{ required: true, message: '请输入评卷数量', trigger: 'change' }],
+  };
+
+  const handleClose = () => {
+    formRef.value?.resetFields();
+    Object.assign(formModel, initialFormState);
+  };
+
+  /* confirm */
+  const { loading, setLoading } = useLoading();
+  async function confirm() {
+    if (!formRef.value) return;
+
+    const valid = await formRef.value?.validate().catch(() => false);
+    if (!valid) return;
+
+    if (!formModel.ids.length) {
+      ElMessage.warning('请选择评卷员');
+      return;
+    }
+
+    setLoading(true);
+    const datas = objAssign(formModel, {});
+    let res = true;
+    await markMarkerSetTaskCount(datas).catch(() => {
+      res = false;
+    });
+    setLoading(false);
+    if (!res) return;
+    ElMessage.success('设置评卷数量成功!');
+    emit('modified');
+    close();
+  }
+
+  /* init modal */
+  function modalBeforeOpen() {
+    if (props.markerIds && props.markerIds.length > 0) {
+      formModel.ids = [...props.markerIds];
+    } else {
+      formModel.ids = [];
+    }
+  }
+</script>