Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nanMoMentorPlatform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
node_server
nanMoMentorPlatform
Commits
4544d0b0
Commit
4544d0b0
authored
Jun 24, 2026
by
PC-20251223ZVQQ\Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
学生端-导师测评
parent
be3831af
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
367 additions
and
5 deletions
+367
-5
matchManager.ts
src/biz/admin/matchManager.ts
+5
-0
myTeacher.ts
src/biz/student/myTeacher.ts
+248
-0
selectTeacher.ts
src/biz/student/selectTeacher.ts
+5
-0
teacherInfo.ts
src/biz/teacher/teacherInfo.ts
+0
-0
dbEnum.ts
src/config/dbEnum.ts
+12
-0
mysqlTableConfig.ts
src/config/mysqlTableConfig.ts
+2
-1
headteacher.ts
src/routers/headteacher.ts
+31
-0
student.ts
src/routers/student.ts
+47
-0
teacher.ts
src/routers/teacher.ts
+17
-4
No files found.
src/biz/admin/matchManager.ts
View file @
4544d0b0
...
...
@@ -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
...
...
src/biz/student/myTeacher.ts
View file @
4544d0b0
...
...
@@ -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
};
}
...
...
src/biz/student/selectTeacher.ts
View file @
4544d0b0
...
...
@@ -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
...
...
src/biz/teacher/teacherInfo.ts
View file @
4544d0b0
This diff is collapsed.
Click to expand it.
src/config/dbEnum.ts
View file @
4544d0b0
...
...
@@ -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'
,
};
src/config/mysqlTableConfig.ts
View file @
4544d0b0
...
...
@@ -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
}
},
...
...
src/routers/headteacher.ts
View file @
4544d0b0
...
...
@@ -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
);
}
src/routers/student.ts
View file @
4544d0b0
...
...
@@ -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
)
{
...
...
src/routers/teacher.ts
View file @
4544d0b0
...
...
@@ -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
);
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment