【新增】试卷任务训练、考试、高考模拟前端

This commit is contained in:
YOHO\20373
2025-04-25 15:53:44 +08:00
committed by 陆光LG
parent 5d3bcb3087
commit 9d0498d262
81 changed files with 2996 additions and 19167 deletions

View File

@@ -0,0 +1,197 @@
<template>
<Dialog
v-model="visible"
:title="isUpdate ? '修改考场设置' : '添加考场设置'"
width="460"
center
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="120px"
@submit.prevent=""
>
<el-form-item label="试卷ID" prop="taskId">
<el-input clearable v-model="form.taskId" placeholder="请输入试卷ID" disabled/>
</el-form-item>
<el-form-item label="批次" prop="batch">
<el-input clearable v-model="form.batch" placeholder="请输入批次" disabled/>
</el-form-item>
<el-form-item label="考试开始时间" prop="startTime">
<el-date-picker
v-model="form.startTime"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择考试开始时间"
class="ele-fluid"
/>
</el-form-item>
<el-form-item label="考试结束时间" prop="endTime">
<el-date-picker
v-model="form.endTime"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择考试结束时间"
class="ele-fluid"
/>
</el-form-item>
<el-form-item label="开始前(分钟)允许入场" prop="allowEntry">
<el-input
clearable
v-model="form.allowEntry"
placeholder="请输入允许提前入场的分钟数"
/>
</el-form-item>
<el-form-item label="开始后(分钟)禁止入场" prop="endAllowEntry">
<el-input
clearable
v-model="form.endAllowEntry"
placeholder="请输入开始后禁止入场的分钟数"
/>
</el-form-item>
<el-form-item label="开始后(分钟)允许交卷" prop="allowSubmission">
<el-input
clearable
v-model="form.allowSubmission"
placeholder="请输入允许交卷的分钟数"
/>
</el-form-item>
<el-form-item label="是否启用" prop="status">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.SYS_YES_NO)"
:key="dict.value"
:label="String(dict.value)"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button
type="primary"
:loading="loading"
@click="save"
:disabled="false"
>
保存
</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue'
import { addSession, updateSession } from '@/api/system/session'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
const emit = defineEmits(['done'])
const props = defineProps({
data: Object,
taskId: String,
})
const visible = defineModel({ type: Boolean })
const isUpdate = ref(false)
const loading = ref(false)
const formRef = ref()
// 表单模型
const form = ref({
sessionId: undefined,
taskId: props.taskId ?? '',
batch: '',
startTime: '',
endTime: '',
allowEntry: '',
endAllowEntry: '',
allowSubmission: '',
status: '', // 状态0禁用1启用
})
// 表单校验规则
const rules = reactive({
taskId: [{ required: true, message: '请输入试卷ID', trigger: 'blur' }],
batch: [{ required: true, message: '请输入批次', trigger: 'blur' }],
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
allowEntry: [{ required: true, message: '请输入允许入场时间', trigger: 'blur' }],
endAllowEntry: [{ required: true, message: '请输入禁止入场时间', trigger: 'blur' }],
allowSubmission: [{ required: true, message: '请输入允许交卷时间', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }],
})
// 打开弹窗方法
const open = async (type: 'create' | 'update', data?: any) => {
console.log(type+"isUpdate.valueisUpdate.valueisUpdate.value")
visible.value = true
isUpdate.value = type === 'update'
if (isUpdate.value && data) {
form.value = { ...data }
} else {
form.value = {
sessionId: undefined,
taskId: props.taskId ?? '',
batch: '',
startTime: '',
endTime: '',
allowEntry: '',
endAllowEntry: '',
allowSubmission: '',
status: '',
}
// 设置批次为 当前时间 + 5位随机数
const now = new Date();
const pad = (n) => n.toString().padStart(2, '0');
const datetimeStr = [
now.getFullYear(),
pad(now.getMonth() + 1),
pad(now.getDate()),
pad(now.getHours()),
pad(now.getMinutes()),
pad(now.getSeconds())
].join('');
const randomNum = Math.floor(Math.random() * 100000).toString().padStart(5, '0');
//把batch 赋值 randomNum
// 把 batch 设置为当前时间+随机数
form.value.batch = `${datetimeStr}${randomNum}`;
}
await nextTick()
formRef.value?.clearValidate?.()
}
// 保存按钮
const save = async () => {
await formRef.value.validate()
loading.value = true
try {
const fn = isUpdate.value ? updateSession : addSession
await fn({ ...form.value })
emit('done')
visible.value = false
} finally {
loading.value = false
}
}
defineExpose({ open })
</script>

View File

@@ -0,0 +1,64 @@
<!-- 搜索表单 -->
<template>
<ele-card :body-style="{ paddingBottom: '2px' }">
<el-form label-width="72px" @keyup.enter="search" @submit.prevent="">
<el-row :gutter="8">
<el-col :lg="6" :md="12" :sm="12" :xs="24">
<el-form-item label="批次">
<el-input
clearable
v-model.trim="form.batch"
placeholder="请输入"
/>
</el-form-item>
</el-col>
<el-col :lg="6" :md="12" :sm="12" :xs="24">
<el-form-item label="状态">
<dict-data
code="sys_common_status_other"
type="select"
v-model="form.status"
/>
</el-form-item>
</el-col>
<el-col :lg="6" :md="12" :sm="12" :xs="24">
<el-form-item label-width="16px">
<el-button type="primary" @click="search">查询</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</ele-card>
</template>
<script setup>
import { useFormData } from '@/utils/use-form-data';
const emit = defineEmits(['search']);
/** 表单数据 */
const [form, resetFields] = useFormData({
startTime: '',
batch: '',
endTime: '',
allowEntry: '',
endAllowEntry: '',
allowSubmission: '',
status: '',
});
/** 搜索 */
const search = () => {
emit('search', { ...form });
};
/** 重置 */
const reset = () => {
resetFields();
search();
};
</script>

View File

@@ -0,0 +1,104 @@
<template>
<Dialog v-model="visible" :title="'试卷调整'" width="860" @open="handleOpen" center>
<div>{{rows}}</div>
<div>{{taskId}}</div>
<el-transfer v-model="value" :data="data" />
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" :loading="loading" @click="save">保存</el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import { ref, nextTick } from 'vue';
import { useFormData } from '@/utils/use-form-data';
import { getPaperListByType } from '@/api/system/paper';
import { setSessions } from '@/api/system/session';
import type { FormInstance } from 'element-plus' // ✅ 引入 FormInstance 类型
const message = useMessage();
const props = defineProps({
taskId: String,
rows: {
type: Array,
default: () => []
}
});
interface Option {
key: string;
label: string;
disabled: boolean;
}
const data = ref<Option[]>([]);
const value = ref<string[]>([]);
const emit = defineEmits(['done']);
const visible = defineModel({ type: Boolean });
const loading = ref(false);
// ✅ 给 formRef 添加类型注解
const formRef = ref<FormInstance | null>(null);
const [form, resetFields] = useFormData({
taskId: '',
});
const handleOpen = async () => {
resetFields();
form.taskId = props.taskId;
value.value = [];
try {
const res = await getPaperListByType({
taskId: props.taskId,
rows: props.rows
});
data.value = (res?.paperId || []).map((item: string) => ({
key: item,
label: item,
disabled: false
}));
value.value = res?.selectedIds || [];
} catch (e) {
message.error('获取试卷列表失败');
}
nextTick(() => {
// ✅ 类型安全地调用 clearValidate
formRef.value?.clearValidate?.();
});
};
const handleCancel = () => {
visible.value = false;
};
const save = async () => {
if (!value.value.length) {
message.warning('请至少选择一个试卷');
return;
}
try {
const params = {
selectedIds: value.value,
rows: props.rows
};
await setSessions(params);
message.success('保存成功');
emit('done');
visible.value = false;
} catch (error) {
message.error('保存失败');
}
};
</script>

View File

@@ -0,0 +1,227 @@
<template>
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['system:sms-channel:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增</el-button
>
<el-button
type="warning"
class="ele-btn-icon"
:disabled="!selections.length"
@click="papreSet()"
>
批量分配
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column label="考试批次" align="center" prop="batch" />
<el-table-column label="考试开始时间" align="center" prop="startTime" />
<el-table-column label="考试结束时间" align="center" prop="endTime" />
<el-table-column label="开始考试前分钟,允许入场" align="center" prop="allowEntry" />
<el-table-column label="开始考试后分钟,禁止入场" align="center" prop="endAllowEntry" />
<el-table-column label="开始考试后分钟,允许交卷" align="center" prop="allowSubmission" />
<el-table-column label="是否启用" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.SYS_YES_NO" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openEdit('update', scope.row)"
v-hasPermi="['system:sms-channel:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.sessionId)"
v-hasPermi="['system:sms-channel:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<session-edit v-model="showEdit" :data="current" :task-Id="taskId" @done="reload" ref="taskEditRef" />
<paper-set v-model="showSet" :task-Id="taskId" :rows="selectedRows" @done="reload"/>
</template>
<script lang="ts" setup>
import * as SmsChannelApi from '@/api/system/session';
import SessionSearch from './components/step-search.vue';
import SessionEdit from './components/step-edit.vue';
import PaperSet from './components/step-set.vue';
import { DICT_TYPE } from '@/utils/dict';
defineOptions({ name: 'SystemPaper' });
const props = defineProps({
taskSpecialty: {
type: String,
default: ''
},
taskId: {
type: String,
default: ''
}
})
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const sessionAddRef = ref();
/** 当前编辑数据 */
const current = ref<object>();
/** 是否显示编辑弹窗 */
const showEdit = ref(false);
const showLook = ref(false);
const showAdd = ref(false);
const showSet = ref(false);
const taskEditRef = ref()
const loading = ref(false) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryFormRef = ref() // 搜索的表单
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
signature: undefined,
status: undefined,
createTime: [],
taskId: props.taskId
})
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const res = await SmsChannelApi.pageSessions(queryParams)
console.log(res)
list.value = res
total.value = res.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 表格选中数据 */
const selections = ref([]);
const selectedRows = ref<string[]>([]);
const papreSet = () => {
const rows = selections.value;
if (!rows.length) {
message.error('请至少选择一条数据');
return;
}
selectedRows.value = rows.map((d: any) => d.sessionId); // 保存选中的行数据
showSet.value = true; // 打开弹窗
};
const handleSelectionChange = (rows) => {
selections.value = rows;
}
/** 添加/修改操作 */
const formRef = ref();
const openForm = (type: string, ) => {
showEdit.value = true
console.log( current.value )
taskEditRef.value?.open(type)
}
const openEdit = (type: string, row?: object) => {
showEdit.value = true
current.value = row
console.log( current.value )
taskEditRef.value?.open(type, row)
}
const reload = (where) => {
getList()
};
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await SmsChannelApi.removeSession(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>