学生端-导师测评

parent be3831af
......@@ -45,12 +45,17 @@ export async function initialAssignStudents(param: any) {
// 如果原匹配未完成的任务,新导师需要重新完成
}
// 获取学生班级ID
let studentProfile = await selectOneDataByParam(TABLENAME.用户扩展信息表, { user_id: studentId }, ["class_id"]);
let class_id = studentProfile.data?.class_id || null;
// 创建新的匹配
await addData(TABLENAME.师生匹配表, {
id: randomId(TABLEID.师生匹配表),
student_id: studentId,
teacher_id: teacher_id,
semester_id: semester.id,
class_id: class_id,
match_type: MATCHTYPE.主动选择,
match_time: getMySqlMs(),
is_active: true
......
......@@ -287,6 +287,254 @@ export async function getInteractionDynamic(studentId: string) {
}
/**
* 获取导师评价列表
* @param studentId 学生ID
* @param surveyName 测评标题(可选,模糊搜索)
* @param status 测评状态(可选,0=待测评 1=已测评)
*/
export async function getTeacherSurvey(studentId: string, surveyName?: string, status?: number) {
/**获取当前学期 */
let semester = await getCurrentSemester();
if (!semester) {
return [];
}
/**查当前学生的导师 */
let match = await selectOneDataByParam(TABLENAME.师生匹配表, {
student_id: studentId,
semester_id: semester.id,
is_active: true
});
if (!match.data) {
return [];
}
/**查测评活动列表 */
let surveyParam: any = {
semester_id: semester.id,
is_open: 1
};
let surveyList = await selectDataListByParam(TABLENAME.测评活动表, surveyParam);
if (!surveyList.data || surveyList.data.length === 0) {
return [];
}
let resultList = [];
for (let survey of surveyList.data) {
// 模糊搜索测评标题
if (surveyName && !survey.name.includes(surveyName)) {
continue;
}
// 查是否已提交
let submission = await selectOneDataByParam(TABLENAME.测评提交表, {
survey_id: survey.id,
student_id: studentId
});
let surveyStatus = submission.data && (submission.data as any).id ? 1 : 0;
// 按状态筛选
if (status !== undefined && status !== null && surveyStatus !== status) {
continue;
}
resultList.push({
survey_id: survey.id,
name: survey.name,
start_time: survey.start_time,
end_time: survey.end_time,
status: surveyStatus
});
}
return resultList;
}
/**
* 获取测评详情
* @param studentId 学生ID
* @param surveyId 测评活动ID
*/
export async function surveyInfo(studentId: string, surveyId: number) {
/**查测评活动 */
let survey = await selectOneDataByParam(TABLENAME.测评活动表, { id: surveyId });
if (!survey.data || !(survey.data as any).id) {
throw new BizError(ERRORENUM.未找到数据, '测评不存在');
}
/**查题目列表(按排序号升序) */
let questions = await selectDataListByParam(TABLENAME.测评题目表, {
survey_id: surveyId,
"%orderAsc%": "sort_order"
});
/**查是否已提交 */
let submission = await selectOneDataByParam(TABLENAME.测评提交表, {
survey_id: surveyId,
student_id: studentId
});
let isSubmitted = !!(submission.data && (submission.data as any).id);
/**如果已提交,查答案 */
let answerMap: Map<number, any> = new Map();
if (isSubmitted) {
let answers = await selectDataListByParam(TABLENAME.测评答案表, {
submission_id: (submission.data as any).id
});
for (let ans of answers.data) {
answerMap.set(ans.question_id, ans);
}
}
/**组装题目列表 */
let questionList = (questions.data || []).map((q: any) => {
let answer = null;
let ansRecord = answerMap.get(q.id);
if (ansRecord) {
if (q.question_type === 'text') {
answer = ansRecord.answer_text;
} else {
answer = ansRecord.answer_option_keys;
}
}
return {
question_id: q.id,
question_code: q.question_code,
question_text: q.question_text,
question_type: q.question_type,
options: q.options,
is_required: q.is_required,
sort_order: q.sort_order,
answer: answer
};
});
return {
surveyInfo: {
survey_id: survey.data.id,
name: (survey.data as any).name,
description: (survey.data as any).description,
start_time: (survey.data as any).start_time,
end_time: (survey.data as any).end_time,
is_submitted: isSubmitted
},
questions: questionList
};
}
/**
* 提交导师测评
* @param studentId 学生ID(测评人)
* @param surveyId 测评活动ID
* @param answers 答案数组,格式:[{"1":"[\"A\",\"B\"]"},{"2":"无"}]
*/
export async function surveyTeacher(studentId: string, surveyId: number, answers: object[]) {
/**获取当前学期 */
let semester = await getCurrentSemester();
if (!semester) {
throw new BizError(ERRORENUM.操作失败, '当前没有进行中的学期');
}
/**校验测评存在且开放 */
let survey = await selectOneDataByParam(TABLENAME.测评活动表, { id: surveyId });
if (!survey.data || !(survey.data as any).id) {
throw new BizError(ERRORENUM.未找到数据, '测评不存在');
}
let surveyData = survey.data as any;
if (!surveyData.is_open) {
throw new BizError(ERRORENUM.操作不允许, '测评未开放');
}
/**校验答题时间 */
let now = getMySqlMs();
if (surveyData.start_time && now < surveyData.start_time) {
throw new BizError(ERRORENUM.操作不允许, '测评尚未开始');
}
if (surveyData.end_time && now > surveyData.end_time) {
throw new BizError(ERRORENUM.操作不允许, '测评已结束');
}
/**防重复提交 */
let existSubmission = await selectOneDataByParam(TABLENAME.测评提交表, {
survey_id: surveyId,
student_id: studentId
});
if (existSubmission.data && (existSubmission.data as any).id) {
throw new BizError(ERRORENUM.重复操作, '您已提交过该测评,不可重复提交');
}
/**获取当前导师 */
let match = await selectOneDataByParam(TABLENAME.师生匹配表, {
student_id: studentId,
semester_id: semester.id,
is_active: true
});
if (!match.data) {
throw new BizError(ERRORENUM.未找到数据, '您当前没有导师');
}
let teacherId = (match.data as any).teacher_id;
/**创建提交记录 */
let submission = await addData(TABLENAME.测评提交表, {
survey_id: surveyId,
student_id: studentId,
teacher_id: teacherId,
submit_time: getMySqlMs()
});
let submissionId = (submission as any).data?.id;
if (!submissionId) {
// 自增ID需要重新查
let newSub = await selectOneDataByParam(TABLENAME.测评提交表, {
survey_id: surveyId,
student_id: studentId
});
submissionId = (newSub.data as any).id;
}
/**遍历答案并保存 */
for (let answerItem of answers) {
for (let key in answerItem) {
let questionId = parseInt(key);
let rawValue = (answerItem as any)[key];
// 解析答案值(JSON字符串)
let parsedValue: any;
try {
parsedValue = JSON.parse(rawValue);
} catch (e) {
parsedValue = rawValue;
}
// 查题目类型
let question = await selectOneDataByParam(TABLENAME.测评题目表, { id: questionId });
if (!question.data || !(question.data as any).id) {
continue; // 题目不存在则跳过
}
let questionType = (question.data as any).question_type;
// 根据题目类型保存答案
let answerData: any = {
submission_id: submissionId,
question_id: questionId
};
if (questionType === 'text') {
answerData.answer_text = typeof parsedValue === 'string' ? parsedValue : JSON.stringify(parsedValue);
} else {
answerData.answer_option_keys = Array.isArray(parsedValue) ? parsedValue : [parsedValue];
}
await addData(TABLENAME.测评答案表, answerData);
}
}
return { isSuccess: true };
}
......
......@@ -207,11 +207,16 @@ export async function selectTeacher(studentId: string, teacherId: string) {
throw new BizError(ERRORENUM.操作失败, '该导师名额已满');
}
/**获取学生班级ID */
let studentProfile = await selectOneDataByParam(TABLENAME.用户扩展信息表, { user_id: studentId }, ["class_id"]);
let class_id = studentProfile.data?.class_id || null;
/**创建匹配 */
await addData(TABLENAME.师生匹配表, {
student_id: studentId,
teacher_id: teacherId,
semester_id: semester.id,
class_id: class_id,
match_type: MATCHTYPE.主动选择,
match_time: getMySqlMs(),
is_active: true
......
......@@ -35,6 +35,12 @@ export enum TABLENAME {
消息已读表 = 'notification_read',
用户登录日志表 = 'user_login_log',
角色切换日志表 = 'role_switch_log',
// 测评相关
测评活动表 = 'survey',
测评题目表 = 'survey_question',
测评提交表 = 'survey_submission',
测评答案表 = 'survey_answer',
};
/**
......@@ -57,6 +63,12 @@ export enum TABLEID {
消息已读表 = "NR",
用户登录日志表 = 'LOG',
角色切换日志表 = 'RSL',
// 测评相关
测评活动表 = 'SV',
测评题目表 = 'SQ',
测评提交表 = 'SS',
测评答案表 = 'SA',
};
......@@ -169,6 +169,7 @@ export const TablesConfig = [
student_id: { type: DataTypes.STRING(20), allowNull: false, comment: '学生user_id' },
teacher_id: { type: DataTypes.STRING(20), allowNull: false, comment: '导师user_id' },
semester_id: { type: DataTypes.INTEGER, allowNull: false, comment: '学期ID,关联semester.id' },
class_id: { type: DataTypes.INTEGER, comment: '班级ID,关联class.id' },
match_type: { type: DataTypes.ENUM('主动选择', '抢课分配'), allowNull: false, comment: '选择方式' },
match_time: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW, comment: '匹配时间' },
is_active: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true, comment: '是否当前有效匹配' },
......@@ -480,7 +481,7 @@ export const TablesConfig = [
submission_id: { type: DataTypes.INTEGER, allowNull: false, comment: '关联survey_submission.id' },
question_id: { type: DataTypes.INTEGER, allowNull: false, comment: '关联survey_question.id' },
answer_text: { type: DataTypes.TEXT, allowNull: true, comment: '文本答案,用于文本题(如建议)' },
answer_option_keys: { type: DataTypes.JSON, allowNull: true, comment: '选择题答案,单选存字符串如"A",多选存数组如["A","C","E"]' },
answer_option_keys: { type: DataTypes.JSON, allowNull: true, comment: '选择题答案,单选存字符串如["A"],多选存数组如["A","C","E"]' },
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW }
},
......
......@@ -8,6 +8,7 @@ import * as studentMatchBiz from '../biz/headteacher/studentMatch';
import * as growthMessageSummaryBiz from '../biz/headteacher/growthMessageSummary';
import * as notificationBiz from '../biz/headteacher/notification';
import * as dashboardBiz from '../biz/headteacher/dashboard';
import * as teacherInfoBiz from '../biz/teacher/teacherInfo';
import { checkUser, checkRole, checkClassTeacher } from '../middleware/user';
import { USERROLE } from '../config/enum';
import { eccReqParamater } from '../util/verificationParam';
......@@ -46,6 +47,10 @@ export function setRouter(httpServer) {
/** 获取未读消息通知数量 - notification.ts */
httpServer.post('/api/headteacher/notification/unread/count', checkUser, checkClassTeacher(), asyncHandler(getUnreadNotificationCount));
// ===== 历史导师 =====
/** 查看本班学生历年导师 - teacherInfo.ts */
httpServer.post('/api/headteacher/history/teacher', checkUser, checkClassTeacher(), asyncHandler(getHistoryTeacherForClass));
// ===== 数据看板 =====
/** 班主任首页数据 - dashboard.ts */
httpServer.post('/api/headteacher/dashboard', checkUser, checkClassTeacher(), asyncHandler(headteacherDashboard));
......@@ -260,5 +265,31 @@ async function headteacherDashboard(req, res) {
res.success(result);
}
/**
* 查看本班学生历年导师(班主任端)
* 班主任查看本班所有学生在各个学期匹配过的导师
*
* @param studentId 学生ID(可选,查单个学生;不传则查全班)
* @param semesterId 学期ID(可选,筛选特定学期)
* @param page 页码
* @param pageSize 每页大小
*/
async function getHistoryTeacherForClass(req, res) {
const userInfo = req.userInfo;
const classInfo = req.classInfo; // checkClassTeacher 中间件注入的班级信息
let reqConf = { studentId: 'String', semesterId: 'Number', page: 'Number', pageSize: 'Number' };
const NotMustHaveKeys = ['studentId', 'semesterId', 'page', 'pageSize'];
let { studentId, semesterId, page, pageSize } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
let result = await teacherInfoBiz.getHistoryTeacher(classInfo.classId, {
studentId: studentId || undefined,
semesterId: semesterId || undefined,
page: page || 1,
pageSize: pageSize || 10
});
res.success(result);
}
......@@ -37,6 +37,11 @@ export function setRouter(httpServer) {
/** 获取互动动态列表 - myTeacher.ts */
httpServer.post('/api/student/interaction/dynamic', checkUser, checkRole(USERROLE.学生), asyncHandler(getInteractionDynamic));
/** 导师评价&测评 - myTeacher.ts */
httpServer.post('/api/student/teacher/survey', checkUser, checkRole(USERROLE.学生), asyncHandler(getTeacherSurvey));
httpServer.post('/api/student/survey/info', checkUser, checkRole(USERROLE.学生), asyncHandler(surveyInfo));
httpServer.post('/api/student/survey/teacher', checkUser, checkRole(USERROLE.学生), asyncHandler(surveyTeacher));
// ===== 预约管理 =====
/** 未读预约数量 - appointment.ts */
......@@ -171,6 +176,48 @@ async function getInteractionDynamic(req, res) {
}
/**
* 获取导师评价列表
*/
async function getTeacherSurvey(req, res) {
const userInfo = req.userInfo;
let reqConf = {surveyName:'String', status: "Number"};
const NotMustHaveKeys = ['surveyName', 'status'];
let { surveyName, status } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
let result = await myTeacherBiz.getTeacherSurvey(userInfo.userId, surveyName, status);
res.success({ dataList: result });
}
/**
* 测评详情
* @param req
* @param res
*/
async function surveyInfo(req, res) {
const userInfo = req.userInfo;
let reqConf = {surveyId:'Number'};
const NotMustHaveKeys = [];
let { surveyId } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
let result = await myTeacherBiz.surveyInfo(userInfo.userId, surveyId);
res.success(result);
}
/**
* 导师测评
*/
async function surveyTeacher(req, res) {
const userInfo = req.userInfo;
let reqConf = {surveyId:'Number', answers: '[Object]'};
const NotMustHaveKeys = [];
let { surveyId, answers } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
let result = await myTeacherBiz.surveyTeacher(userInfo.userId, surveyId, answers);
res.success(result);
}
/**
* 未读预约数量
*/
async function getUnreadAppointmentCount(req, res) {
......
......@@ -923,12 +923,25 @@ async function teacherUpdate(req, res) {
}
/**
* 学生历年导师列表
* @param req
* @param res
* 学生历年导师列表(导师端查看单个学生的历史导师)
* @param studentId 学生ID
* @param semesterId 学期ID(可选)
* @param page 页码
* @param pageSize 每页大小
*/
async function getHistoryTeacher(req, res) {
const userInfo = req.userInfo;
let reqConf = { studentId: 'String', semesterId: 'Number', page: 'Number', pageSize: 'Number' };
const NotMustHaveKeys = ['semesterId', 'page', 'pageSize'];
let { studentId, semesterId, page, pageSize } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
let result = await teacherInfoBiz.getHistoryTeacherForStudent(studentId, {
semesterId: semesterId || undefined,
page: page || 1,
pageSize: pageSize || 10
});
res.success(result);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment