暂存
This commit is contained in:
parent
746f5d85ec
commit
f40b0e7c94
@ -61,10 +61,6 @@ SELECT
|
||||
FROM t_sys_tenant t
|
||||
JOIN (
|
||||
SELECT 'activity:read' AS code UNION ALL
|
||||
SELECT 'ai-3d:create' UNION ALL
|
||||
SELECT 'class:create' UNION ALL
|
||||
SELECT 'class:delete' UNION ALL
|
||||
SELECT 'class:update' UNION ALL
|
||||
SELECT 'config:create' UNION ALL
|
||||
SELECT 'config:delete' UNION ALL
|
||||
SELECT 'config:update' UNION ALL
|
||||
@ -73,19 +69,9 @@ JOIN (
|
||||
SELECT 'contest:publish' UNION ALL
|
||||
SELECT 'contest:read' UNION ALL
|
||||
SELECT 'contest:update' UNION ALL
|
||||
SELECT 'department:create' UNION ALL
|
||||
SELECT 'department:delete' UNION ALL
|
||||
SELECT 'department:update' UNION ALL
|
||||
SELECT 'dict:create' UNION ALL
|
||||
SELECT 'dict:delete' UNION ALL
|
||||
SELECT 'dict:update' UNION ALL
|
||||
SELECT 'grade:create' UNION ALL
|
||||
SELECT 'grade:delete' UNION ALL
|
||||
SELECT 'grade:update' UNION ALL
|
||||
SELECT 'homework:create' UNION ALL
|
||||
SELECT 'homework:delete' UNION ALL
|
||||
SELECT 'homework:read' UNION ALL
|
||||
SELECT 'homework:update' UNION ALL
|
||||
SELECT 'judge:create' UNION ALL
|
||||
SELECT 'judge:delete' UNION ALL
|
||||
SELECT 'judge:read' UNION ALL
|
||||
@ -105,14 +91,6 @@ JOIN (
|
||||
SELECT 'role:delete' UNION ALL
|
||||
SELECT 'role:read' UNION ALL
|
||||
SELECT 'role:update' UNION ALL
|
||||
SELECT 'school:create' UNION ALL
|
||||
SELECT 'school:update' UNION ALL
|
||||
SELECT 'student:create' UNION ALL
|
||||
SELECT 'student:delete' UNION ALL
|
||||
SELECT 'student:update' UNION ALL
|
||||
SELECT 'teacher:create' UNION ALL
|
||||
SELECT 'teacher:delete' UNION ALL
|
||||
SELECT 'teacher:update' UNION ALL
|
||||
SELECT 'tenant:create' UNION ALL
|
||||
SELECT 'tenant:delete' UNION ALL
|
||||
SELECT 'tenant:update' UNION ALL
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
package com.lesingle.creation.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 作业状态枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum HomeworkStatusEnum {
|
||||
|
||||
UNPUBLISHED("unpublished", "未发布"),
|
||||
PUBLISHED("published", "已发布");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
public static HomeworkStatusEnum getByCode(String code) {
|
||||
if (code == null) {
|
||||
return UNPUBLISHED;
|
||||
}
|
||||
for (HomeworkStatusEnum status : values()) {
|
||||
if (status.getCode().equals(code)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return UNPUBLISHED;
|
||||
}
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
package com.lesingle.creation.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 作业提交状态枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum HomeworkSubmissionStatusEnum {
|
||||
|
||||
PENDING("pending", "待评审"),
|
||||
REVIEWED("reviewed", "已评审"),
|
||||
REJECTED("rejected", "已拒绝");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
public static HomeworkSubmissionStatusEnum getByCode(String code) {
|
||||
if (code == null) {
|
||||
return PENDING;
|
||||
}
|
||||
for (HomeworkSubmissionStatusEnum status : values()) {
|
||||
if (status.getCode().equals(code)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return PENDING;
|
||||
}
|
||||
}
|
||||
@ -5,8 +5,10 @@ import com.lesingle.creation.common.core.Result;
|
||||
import com.lesingle.creation.common.security.UserPrincipal;
|
||||
import com.lesingle.creation.dto.registration.*;
|
||||
import com.lesingle.creation.service.ContestRegistrationService;
|
||||
import com.lesingle.creation.service.UserService;
|
||||
import com.lesingle.creation.vo.registration.RegistrationStatsVO;
|
||||
import com.lesingle.creation.vo.registration.RegistrationVO;
|
||||
import com.lesingle.creation.vo.user.UserListVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -25,6 +27,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
public class ContestRegistrationController {
|
||||
|
||||
private final ContestRegistrationService registrationService;
|
||||
private final UserService userService;
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "创建报名")
|
||||
@ -57,6 +60,21 @@ public class ContestRegistrationController {
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/candidate-users")
|
||||
@Operation(summary = "报名场景:按角色分页查询可选用户(teacher/student)")
|
||||
@PreAuthorize("hasAnyAuthority('contest:read','contest:register','contest:update','registration:read','user:read')")
|
||||
public Result<Page<UserListVO>> pageCandidateUsers(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestParam String roleCode,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "10") int pageSize) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Page<UserListVO> result =
|
||||
userService.pageCandidateUsersByRoleCode(tenantId, roleCode, keyword, page, pageSize);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/my/{contestId}")
|
||||
@Operation(summary = "获取用户在某活动中的报名记录")
|
||||
@PreAuthorize("hasAuthority('contest:read')")
|
||||
|
||||
@ -1,97 +0,0 @@
|
||||
package com.lesingle.creation.controller;
|
||||
|
||||
import com.lesingle.creation.common.core.Result;
|
||||
import com.lesingle.creation.common.security.UserPrincipal;
|
||||
import com.lesingle.creation.dto.department.CreateDepartmentDTO;
|
||||
import com.lesingle.creation.dto.department.UpdateDepartmentDTO;
|
||||
import com.lesingle.creation.service.DepartmentService;
|
||||
import com.lesingle.creation.vo.department.DepartmentVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 部门管理控制器
|
||||
*/
|
||||
@Tag(name = "部门管理", description = "部门 CRUD 和树形结构接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/departments")
|
||||
@RequiredArgsConstructor
|
||||
public class DepartmentController {
|
||||
|
||||
private final DepartmentService departmentService;
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "创建部门")
|
||||
@PreAuthorize("hasAuthority('department:create')")
|
||||
public Result<DepartmentVO> create(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestBody @Valid CreateDepartmentDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long creatorId = userPrincipal.getUserId();
|
||||
DepartmentVO result = departmentService.create(dto, tenantId, creatorId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "查询部门列表")
|
||||
@PreAuthorize("hasAuthority('department:read')")
|
||||
public Result<List<DepartmentVO>> list(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestParam(required = false) Long parentId) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
List<DepartmentVO> result = departmentService.list(tenantId, parentId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/tree")
|
||||
@Operation(summary = "查询部门树形结构")
|
||||
@PreAuthorize("hasAuthority('department:read')")
|
||||
public Result<List<DepartmentVO>> tree(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
List<DepartmentVO> result = departmentService.tree(tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取部门详情")
|
||||
@PreAuthorize("hasAuthority('department:read')")
|
||||
public Result<DepartmentVO> getDetail(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
DepartmentVO result = departmentService.getDetail(id, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@Operation(summary = "更新部门")
|
||||
@PreAuthorize("hasAuthority('department:update')")
|
||||
public Result<DepartmentVO> update(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id,
|
||||
@RequestBody @Valid UpdateDepartmentDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long modifierId = userPrincipal.getUserId();
|
||||
DepartmentVO result = departmentService.update(id, dto, tenantId, modifierId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除部门")
|
||||
@PreAuthorize("hasAuthority('department:delete')")
|
||||
public Result<Void> delete(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
departmentService.delete(id, tenantId);
|
||||
return Result.success(null);
|
||||
}
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
package com.lesingle.creation.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.lesingle.creation.common.core.Result;
|
||||
import com.lesingle.creation.common.security.UserPrincipal;
|
||||
import com.lesingle.creation.dto.grade.CreateGradeDTO;
|
||||
import com.lesingle.creation.dto.grade.UpdateGradeDTO;
|
||||
import com.lesingle.creation.service.GradeService;
|
||||
import com.lesingle.creation.vo.grade.GradeVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 年级管理控制器
|
||||
*/
|
||||
@Tag(name = "年级管理", description = "年级 CRUD 接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/grades")
|
||||
@RequiredArgsConstructor
|
||||
public class GradeController {
|
||||
|
||||
private final GradeService gradeService;
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "创建年级")
|
||||
@PreAuthorize("hasAuthority('grade:create')")
|
||||
public Result<GradeVO> create(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestBody @Valid CreateGradeDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long creatorId = userPrincipal.getUserId();
|
||||
GradeVO result = gradeService.create(dto, tenantId, creatorId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "分页查询年级列表")
|
||||
@PreAuthorize("hasAuthority('grade:read')")
|
||||
public Result<Page<GradeVO>> pageQuery(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(required = false, defaultValue = "10") Integer pageSize) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Page<GradeVO> result = gradeService.pageQuery(pageNum, pageSize, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取年级详情")
|
||||
@PreAuthorize("hasAuthority('grade:read')")
|
||||
public Result<GradeVO> getDetail(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
GradeVO result = gradeService.getDetail(id, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@Operation(summary = "更新年级")
|
||||
@PreAuthorize("hasAuthority('grade:update')")
|
||||
public Result<GradeVO> update(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id,
|
||||
@RequestBody @Valid UpdateGradeDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long modifierId = userPrincipal.getUserId();
|
||||
GradeVO result = gradeService.update(id, dto, tenantId, modifierId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除年级")
|
||||
@PreAuthorize("hasAuthority('grade:delete')")
|
||||
public Result<Void> delete(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
gradeService.delete(id, tenantId);
|
||||
return Result.success(null);
|
||||
}
|
||||
}
|
||||
@ -1,292 +0,0 @@
|
||||
package com.lesingle.creation.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.lesingle.creation.common.core.Result;
|
||||
import com.lesingle.creation.dto.homework.*;
|
||||
import com.lesingle.creation.vo.school.ClassTreeNodeVO;
|
||||
import com.lesingle.creation.service.HomeworkService;
|
||||
import com.lesingle.creation.vo.homework.HomeworkDetailVO;
|
||||
import com.lesingle.creation.vo.homework.HomeworkListVO;
|
||||
import com.lesingle.creation.vo.homework.ReviewRuleVO;
|
||||
import com.lesingle.creation.vo.homework.SubmissionVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import com.lesingle.creation.common.security.UserPrincipal;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 作业管理控制器
|
||||
*/
|
||||
@Tag(name = "作业管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/homeworks")
|
||||
@RequiredArgsConstructor
|
||||
public class HomeworkController {
|
||||
|
||||
private final HomeworkService homeworkService;
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "创建作业")
|
||||
@PreAuthorize("hasAuthority('homework:create')")
|
||||
public Result<HomeworkDetailVO> create(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestBody @Validated CreateHomeworkDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long creatorId = userPrincipal.getUserId();
|
||||
HomeworkDetailVO result = homeworkService.create(dto, tenantId, creatorId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@Operation(summary = "更新作业")
|
||||
@PreAuthorize("hasAuthority('homework:update')")
|
||||
public Result<HomeworkDetailVO> update(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id,
|
||||
@RequestBody @Validated UpdateHomeworkDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
HomeworkDetailVO result = homeworkService.update(id, dto, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/publish")
|
||||
@Operation(summary = "发布作业")
|
||||
@PreAuthorize("hasAuthority('homework:publish')")
|
||||
public Result<HomeworkDetailVO> publish(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
HomeworkDetailVO result = homeworkService.publish(id, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除作业")
|
||||
@PreAuthorize("hasAuthority('homework:delete')")
|
||||
public Result<Void> delete(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
homeworkService.delete(id, tenantId);
|
||||
return Result.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取作业详情")
|
||||
@PreAuthorize("hasAuthority('homework:read')")
|
||||
public Result<HomeworkDetailVO> getDetail(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
HomeworkDetailVO result = homeworkService.getDetail(id, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页查询作业列表")
|
||||
@PreAuthorize("hasAuthority('homework:read')")
|
||||
public Result<Page<HomeworkListVO>> pageQuery(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@ModelAttribute HomeworkQueryDTO queryDTO) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Page<HomeworkListVO> result = homeworkService.pageQuery(queryDTO, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "提交作业")
|
||||
public Result<SubmissionVO> submit(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestBody @Validated SubmitHomeworkDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long studentId = userPrincipal.getUserId();
|
||||
SubmissionVO result = homeworkService.submit(dto, tenantId, studentId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@PostMapping("/review")
|
||||
@Operation(summary = "批改作业")
|
||||
@PreAuthorize("hasAuthority('homework:review')")
|
||||
public Result<SubmissionVO> review(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestBody @Validated ReviewHomeworkDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long reviewerId = userPrincipal.getUserId();
|
||||
SubmissionVO result = homeworkService.review(dto, tenantId, reviewerId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/{homeworkId}/submissions")
|
||||
@Operation(summary = "获取作业提交列表")
|
||||
@PreAuthorize("hasAuthority('homework:review')")
|
||||
public Result<List<SubmissionVO>> getSubmissions(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long homeworkId) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
List<SubmissionVO> result = homeworkService.getSubmissions(homeworkId, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@PostMapping("/review-rules")
|
||||
@Operation(summary = "创建评审规则")
|
||||
@PreAuthorize("hasAuthority('homework:review-rule:create')")
|
||||
public Result<ReviewRuleVO> createReviewRule(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestParam String ruleName,
|
||||
@RequestParam(required = false, defaultValue = "custom") String ruleType,
|
||||
@RequestParam(required = false) java.math.BigDecimal totalScore,
|
||||
@RequestParam(required = false) String description) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
ReviewRuleVO result = homeworkService.createReviewRule(
|
||||
ruleName, ruleType, totalScore, description, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/review-rules")
|
||||
@Operation(summary = "获取评审规则列表")
|
||||
public Result<List<ReviewRuleVO>> getReviewRules(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
List<ReviewRuleVO> result = homeworkService.getReviewRules(tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/review-rules/select")
|
||||
@Operation(summary = "获取可选的评审规则")
|
||||
public Result<List<ReviewRuleVO>> getReviewRulesForSelect(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
List<ReviewRuleVO> result = homeworkService.getReviewRulesForSelect(tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@PutMapping("/review-rules/{id}")
|
||||
@Operation(summary = "更新评审规则")
|
||||
@PreAuthorize("hasAuthority('homework:review-rule:update')")
|
||||
public Result<ReviewRuleVO> updateReviewRule(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id,
|
||||
@RequestBody @Validated UpdateReviewRuleDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
ReviewRuleVO result = homeworkService.updateReviewRule(id, dto, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@DeleteMapping("/review-rules/{id}")
|
||||
@Operation(summary = "删除评审规则")
|
||||
@PreAuthorize("hasAuthority('homework:review-rule:delete')")
|
||||
public Result<Void> deleteReviewRule(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
homeworkService.deleteReviewRule(id, tenantId);
|
||||
return Result.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/my")
|
||||
@Operation(summary = "我的作业列表(学生端)")
|
||||
public Result<Page<HomeworkListVO>> getMyHomeworks(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@ModelAttribute HomeworkQueryDTO queryDTO) {
|
||||
Long userId = userPrincipal.getUserId();
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Page<HomeworkListVO> result = homeworkService.getMyHomeworks(queryDTO, userId, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/unpublish")
|
||||
@Operation(summary = "取消发布作业")
|
||||
@PreAuthorize("hasAuthority('homework:publish')")
|
||||
public Result<HomeworkDetailVO> unpublish(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
HomeworkDetailVO result = homeworkService.unpublish(id, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/submissions")
|
||||
@Operation(summary = "获取提交记录列表")
|
||||
@PreAuthorize("hasAuthority('homework:review')")
|
||||
public Result<Page<SubmissionVO>> getSubmissionsList(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@ModelAttribute SubmissionQueryDTO queryDTO) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Page<SubmissionVO> result = homeworkService.getSubmissionsList(queryDTO, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/submissions/{id}")
|
||||
@Operation(summary = "获取提交记录详情")
|
||||
@PreAuthorize("hasAuthority('homework:review')")
|
||||
public Result<SubmissionVO> getSubmissionDetail(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
SubmissionVO result = homeworkService.getSubmissionDetail(id, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/submissions/class-tree")
|
||||
@Operation(summary = "获取班级树结构")
|
||||
@PreAuthorize("hasAuthority('homework:review')")
|
||||
public Result<List<ClassTreeNodeVO>> getClassTree(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
List<ClassTreeNodeVO> result = homeworkService.getClassTree(tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/submissions/my/{homeworkId}")
|
||||
@Operation(summary = "获取我的提交记录")
|
||||
public Result<SubmissionVO> getMySubmission(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long homeworkId) {
|
||||
Long userId = userPrincipal.getUserId();
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
SubmissionVO result = homeworkService.getMySubmission(homeworkId, userId, tenantId);
|
||||
return result != null ? Result.success(result) : Result.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/scores")
|
||||
@Operation(summary = "提交评分")
|
||||
@PreAuthorize("hasAuthority('homework:review')")
|
||||
public Result<SubmissionVO> createScore(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestBody @Validated CreateScoreDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long reviewerId = userPrincipal.getUserId();
|
||||
SubmissionVO result = homeworkService.createScore(dto, tenantId, reviewerId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@PostMapping("/scores/{submissionId}/violation")
|
||||
@Operation(summary = "标记作品违规")
|
||||
@PreAuthorize("hasAuthority('homework:review')")
|
||||
public Result<Void> markViolation(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long submissionId,
|
||||
@RequestParam(required = false) String reason) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
homeworkService.markViolation(submissionId, reason, tenantId);
|
||||
return Result.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/scores/{submissionId}/reset")
|
||||
@Operation(summary = "重置评分")
|
||||
@PreAuthorize("hasAuthority('homework:review')")
|
||||
public Result<Void> resetScore(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long submissionId) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
homeworkService.resetScore(submissionId, tenantId);
|
||||
return Result.success(null);
|
||||
}
|
||||
}
|
||||
@ -1,89 +0,0 @@
|
||||
package com.lesingle.creation.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.lesingle.creation.common.core.Result;
|
||||
import com.lesingle.creation.common.security.UserPrincipal;
|
||||
import com.lesingle.creation.dto.schoolclass.CreateClassDTO;
|
||||
import com.lesingle.creation.dto.schoolclass.UpdateClassDTO;
|
||||
import com.lesingle.creation.service.SchoolClassService;
|
||||
import com.lesingle.creation.vo.schoolclass.ClassVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 班级管理控制器
|
||||
*/
|
||||
@Tag(name = "班级管理", description = "班级 CRUD 接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/classes")
|
||||
@RequiredArgsConstructor
|
||||
public class SchoolClassController {
|
||||
|
||||
private final SchoolClassService schoolClassService;
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "创建班级")
|
||||
@PreAuthorize("hasAuthority('class:create')")
|
||||
public Result<ClassVO> create(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestBody @Valid CreateClassDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long creatorId = userPrincipal.getUserId();
|
||||
ClassVO result = schoolClassService.create(dto, tenantId, creatorId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "分页查询班级列表")
|
||||
@PreAuthorize("hasAuthority('class:read')")
|
||||
public Result<Page<ClassVO>> pageQuery(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) Long gradeId,
|
||||
@RequestParam(required = false) Integer type) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Page<ClassVO> result = schoolClassService.pageQuery(pageNum, pageSize, tenantId, gradeId, type);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取班级详情")
|
||||
@PreAuthorize("hasAuthority('class:read')")
|
||||
public Result<ClassVO> getDetail(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
ClassVO result = schoolClassService.getDetail(id, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@Operation(summary = "更新班级")
|
||||
@PreAuthorize("hasAuthority('class:update')")
|
||||
public Result<ClassVO> update(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id,
|
||||
@RequestBody @Valid UpdateClassDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long modifierId = userPrincipal.getUserId();
|
||||
ClassVO result = schoolClassService.update(id, dto, tenantId, modifierId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除班级")
|
||||
@PreAuthorize("hasAuthority('class:delete')")
|
||||
public Result<Void> delete(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
schoolClassService.delete(id, tenantId);
|
||||
return Result.success(null);
|
||||
}
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
package com.lesingle.creation.controller;
|
||||
|
||||
import com.lesingle.creation.common.core.Result;
|
||||
import com.lesingle.creation.common.security.UserPrincipal;
|
||||
import com.lesingle.creation.dto.school.CreateSchoolDTO;
|
||||
import com.lesingle.creation.dto.school.UpdateSchoolDTO;
|
||||
import com.lesingle.creation.service.SchoolService;
|
||||
import com.lesingle.creation.vo.school.SchoolVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 学校管理控制器
|
||||
*/
|
||||
@Tag(name = "学校管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/schools")
|
||||
@RequiredArgsConstructor
|
||||
public class SchoolController {
|
||||
|
||||
private final SchoolService schoolService;
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "创建学校")
|
||||
@PreAuthorize("hasAuthority('school:create')")
|
||||
public Result<SchoolVO> create(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestBody @Validated CreateSchoolDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long creatorId = userPrincipal.getUserId();
|
||||
SchoolVO result = schoolService.create(dto, tenantId, creatorId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "获取学校信息(未创建时 data 为 null)")
|
||||
@PreAuthorize("hasAuthority('school:read')")
|
||||
public Result<SchoolVO> get(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
SchoolVO result = schoolService.getByTenantId(tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@Operation(summary = "更新学校")
|
||||
@PreAuthorize("hasAuthority('school:update')")
|
||||
public Result<SchoolVO> update(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestBody @Validated UpdateSchoolDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long modifierId = userPrincipal.getUserId();
|
||||
SchoolVO result = schoolService.update(dto, tenantId, modifierId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
@Operation(summary = "删除学校")
|
||||
@PreAuthorize("hasAuthority('school:delete')")
|
||||
public Result<Void> delete(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
schoolService.delete(tenantId);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
package com.lesingle.creation.controller;
|
||||
|
||||
import com.lesingle.creation.common.core.Result;
|
||||
import com.lesingle.creation.common.security.UserPrincipal;
|
||||
import com.lesingle.creation.dto.student.CreateStudentDTO;
|
||||
import com.lesingle.creation.dto.student.UpdateStudentDTO;
|
||||
import com.lesingle.creation.service.StudentService;
|
||||
import com.lesingle.creation.vo.student.StudentVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 学生管理控制器
|
||||
*/
|
||||
@Tag(name = "学生管理", description = "学生 CRUD 和查询接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/students")
|
||||
@RequiredArgsConstructor
|
||||
public class StudentController {
|
||||
|
||||
private final StudentService studentService;
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "创建学生")
|
||||
@PreAuthorize("hasAuthority('student:create')")
|
||||
public Result<StudentVO> create(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestBody @Valid CreateStudentDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long creatorId = userPrincipal.getUserId();
|
||||
StudentVO result = studentService.create(dto, tenantId, creatorId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "查询学生列表")
|
||||
@PreAuthorize("hasAuthority('student:read')")
|
||||
public Result<List<StudentVO>> list(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) Long classId) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
List<StudentVO> result = studentService.list(tenantId, page, pageSize, classId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取学生详情")
|
||||
@PreAuthorize("hasAuthority('student:read')")
|
||||
public Result<StudentVO> getDetail(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
StudentVO result = studentService.getDetail(id, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/user/{userId}")
|
||||
@Operation(summary = "根据用户 ID 获取学生信息")
|
||||
@PreAuthorize("hasAuthority('student:read')")
|
||||
public Result<StudentVO> getByUserId(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long userId) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
StudentVO result = studentService.getByUserId(userId, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@Operation(summary = "更新学生")
|
||||
@PreAuthorize("hasAuthority('student:update')")
|
||||
public Result<StudentVO> update(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id,
|
||||
@RequestBody @Valid UpdateStudentDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long modifierId = userPrincipal.getUserId();
|
||||
StudentVO result = studentService.update(id, dto, tenantId, modifierId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除学生")
|
||||
@PreAuthorize("hasAuthority('student:delete')")
|
||||
public Result<Void> delete(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
studentService.delete(id, tenantId);
|
||||
return Result.success(null);
|
||||
}
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
package com.lesingle.creation.controller;
|
||||
|
||||
import com.lesingle.creation.common.core.Result;
|
||||
import com.lesingle.creation.common.security.UserPrincipal;
|
||||
import com.lesingle.creation.dto.teacher.CreateTeacherDTO;
|
||||
import com.lesingle.creation.dto.teacher.UpdateTeacherDTO;
|
||||
import com.lesingle.creation.service.TeacherService;
|
||||
import com.lesingle.creation.vo.teacher.TeacherVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 教师管理控制器
|
||||
*/
|
||||
@Tag(name = "教师管理", description = "教师 CRUD 和查询接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/teachers")
|
||||
@RequiredArgsConstructor
|
||||
public class TeacherController {
|
||||
|
||||
private final TeacherService teacherService;
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "创建教师")
|
||||
@PreAuthorize("hasAuthority('teacher:create')")
|
||||
public Result<TeacherVO> create(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestBody @Valid CreateTeacherDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long creatorId = userPrincipal.getUserId();
|
||||
TeacherVO result = teacherService.create(dto, tenantId, creatorId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "查询教师列表")
|
||||
@PreAuthorize("hasAuthority('teacher:read')")
|
||||
public Result<List<TeacherVO>> list(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@RequestParam(required = false) Long departmentId,
|
||||
@RequestParam(required = false) String nickname,
|
||||
@RequestParam(required = false) String username) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
List<TeacherVO> result = teacherService.list(tenantId, departmentId, nickname, username);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取教师详情")
|
||||
@PreAuthorize("hasAuthority('teacher:read')")
|
||||
public Result<TeacherVO> getDetail(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
TeacherVO result = teacherService.getDetail(id, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/user/{userId}")
|
||||
@Operation(summary = "根据用户 ID 获取教师信息")
|
||||
@PreAuthorize("hasAuthority('teacher:read')")
|
||||
public Result<TeacherVO> getByUserId(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long userId) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
TeacherVO result = teacherService.getByUserId(userId, tenantId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@Operation(summary = "更新教师")
|
||||
@PreAuthorize("hasAuthority('teacher:update')")
|
||||
public Result<TeacherVO> update(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id,
|
||||
@RequestBody @Valid UpdateTeacherDTO dto) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
Long modifierId = userPrincipal.getUserId();
|
||||
TeacherVO result = teacherService.update(id, dto, tenantId, modifierId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除教师")
|
||||
@PreAuthorize("hasAuthority('teacher:delete')")
|
||||
public Result<Void> delete(
|
||||
@AuthenticationPrincipal UserPrincipal userPrincipal,
|
||||
@PathVariable Long id) {
|
||||
Long tenantId = userPrincipal.getTenantId();
|
||||
teacherService.delete(id, tenantId);
|
||||
return Result.success(null);
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
package com.lesingle.creation.dto.department;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 创建部门 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "创建部门请求")
|
||||
public class CreateDepartmentDTO {
|
||||
|
||||
@NotBlank(message = "部门名称不能为空")
|
||||
@Schema(description = "部门名称")
|
||||
private String name;
|
||||
|
||||
@NotBlank(message = "部门编码不能为空")
|
||||
@Schema(description = "部门编码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "父部门 ID")
|
||||
private Long parentId;
|
||||
|
||||
@Schema(description = "部门描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort = 0;
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
package com.lesingle.creation.dto.department;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 更新部门 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "更新部门请求")
|
||||
public class UpdateDepartmentDTO {
|
||||
|
||||
@Schema(description = "部门名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "部门编码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "父部门 ID")
|
||||
private Long parentId;
|
||||
|
||||
@Schema(description = "部门描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "有效状态:1-有效,2-失效")
|
||||
private Integer validState;
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
package com.lesingle.creation.dto.grade;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 创建年级 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "创建年级请求")
|
||||
public class CreateGradeDTO {
|
||||
|
||||
@NotBlank(message = "年级名称不能为空")
|
||||
@Schema(description = "年级名称")
|
||||
private String name;
|
||||
|
||||
@NotBlank(message = "年级编码不能为空")
|
||||
@Schema(description = "年级编码")
|
||||
private String code;
|
||||
|
||||
@NotNull(message = "年级级别不能为空")
|
||||
@Schema(description = "年级级别")
|
||||
private Integer level;
|
||||
|
||||
@Schema(description = "年级描述")
|
||||
private String description;
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
package com.lesingle.creation.dto.grade;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 更新年级 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "更新年级请求")
|
||||
public class UpdateGradeDTO {
|
||||
|
||||
@Schema(description = "年级名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "年级编码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "年级级别")
|
||||
private Integer level;
|
||||
|
||||
@Schema(description = "年级描述")
|
||||
private String description;
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
package com.lesingle.creation.dto.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 创建作业请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "创建作业请求")
|
||||
public class CreateHomeworkDTO {
|
||||
|
||||
@NotBlank(message = "作业名称不能为空")
|
||||
@Schema(description = "作业名称", example = "创意绘画练习")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "作业内容(富文本)")
|
||||
private String content;
|
||||
|
||||
@NotNull(message = "提交开始时间不能为空")
|
||||
@Schema(description = "提交开始时间")
|
||||
private LocalDateTime submitStartTime;
|
||||
|
||||
@NotNull(message = "提交结束时间不能为空")
|
||||
@Schema(description = "提交结束时间")
|
||||
private LocalDateTime submitEndTime;
|
||||
|
||||
@Schema(description = "附件列表(JSON 数组)")
|
||||
private String attachments;
|
||||
|
||||
@Schema(description = "公开范围(班级 ID 数组 JSON)")
|
||||
private String publishScope;
|
||||
|
||||
@Schema(description = "评审规则 ID")
|
||||
private Long reviewRuleId;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
package com.lesingle.creation.dto.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创建评分请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "创建评分请求")
|
||||
public class CreateScoreDTO {
|
||||
|
||||
@NotNull(message = "提交 ID 不能为空")
|
||||
@Schema(description = "提交 ID")
|
||||
private Long submissionId;
|
||||
|
||||
@Schema(description = "维度评分列表")
|
||||
private List<DimensionScoreDTO> dimensionScores;
|
||||
|
||||
@Schema(description = "评语")
|
||||
private String comments;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
package com.lesingle.creation.dto.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 维度评分 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "维度评分")
|
||||
public class DimensionScoreDTO {
|
||||
|
||||
@NotBlank(message = "维度名称不能为空")
|
||||
@Schema(description = "维度名称")
|
||||
private String name;
|
||||
|
||||
@NotNull(message = "得分不能为空")
|
||||
@Schema(description = "得分")
|
||||
private BigDecimal score;
|
||||
|
||||
@Schema(description = "满分")
|
||||
private BigDecimal maxScore;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
package com.lesingle.creation.dto.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 作业查询 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "作业查询参数")
|
||||
public class HomeworkQueryDTO {
|
||||
|
||||
@Schema(description = "页码", example = "1")
|
||||
private Integer pageNum = 1;
|
||||
|
||||
@Schema(description = "每页数量", example = "10")
|
||||
private Integer pageSize = 10;
|
||||
|
||||
@Schema(description = "作业名称关键字")
|
||||
private String nameKeyword;
|
||||
|
||||
@Schema(description = "作业状态:unpublished/published")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "有效状态:1-有效,2-失效")
|
||||
private Integer validState;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
package com.lesingle.creation.dto.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 评审维度 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "评审维度")
|
||||
public class ReviewDimensionDTO {
|
||||
|
||||
@Schema(description = "维度名称")
|
||||
@NotBlank(message = "维度名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "满分")
|
||||
@NotNull(message = "满分不能为空")
|
||||
private BigDecimal maxScore;
|
||||
|
||||
@Schema(description = "维度描述")
|
||||
private String description;
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
package com.lesingle.creation.dto.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 批改作业请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "批改作业请求")
|
||||
public class ReviewHomeworkDTO {
|
||||
|
||||
@NotNull(message = "提交 ID 不能为空")
|
||||
@Schema(description = "提交 ID")
|
||||
private Long submissionId;
|
||||
|
||||
@Schema(description = "批改分数")
|
||||
private BigDecimal score;
|
||||
|
||||
@Schema(description = "批改评语")
|
||||
private String comment;
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
package com.lesingle.creation.dto.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 提交记录查询 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "提交记录查询参数")
|
||||
public class SubmissionQueryDTO {
|
||||
|
||||
@Schema(description = "页码", example = "1")
|
||||
private Integer pageNum = 1;
|
||||
|
||||
@Schema(description = "每页大小", example = "10")
|
||||
private Integer pageSize = 10;
|
||||
|
||||
@Schema(description = "作业 ID")
|
||||
private Long homeworkId;
|
||||
|
||||
@Schema(description = "作品编号")
|
||||
private String workNo;
|
||||
|
||||
@Schema(description = "作品名称")
|
||||
private String workName;
|
||||
|
||||
@Schema(description = "学生账号")
|
||||
private String studentAccount;
|
||||
|
||||
@Schema(description = "学生姓名")
|
||||
private String studentName;
|
||||
|
||||
@Schema(description = "提交状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "班级 ID 列表")
|
||||
private java.util.List<Long> classIds;
|
||||
|
||||
@Schema(description = "年级 ID")
|
||||
private Long gradeId;
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
package com.lesingle.creation.dto.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 提交作业请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "提交作业请求")
|
||||
public class SubmitHomeworkDTO {
|
||||
|
||||
@NotNull(message = "作业 ID 不能为空")
|
||||
@Schema(description = "作业 ID")
|
||||
private Long homeworkId;
|
||||
|
||||
@Schema(description = "提交内容")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "提交附件列表(JSON 数组)")
|
||||
private String attachments;
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
package com.lesingle.creation.dto.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 更新作业请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "更新作业请求")
|
||||
public class UpdateHomeworkDTO {
|
||||
|
||||
@NotBlank(message = "作业名称不能为空")
|
||||
@Schema(description = "作业名称", example = "创意绘画练习")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "作业内容(富文本)")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "提交开始时间")
|
||||
private LocalDateTime submitStartTime;
|
||||
|
||||
@Schema(description = "提交结束时间")
|
||||
private LocalDateTime submitEndTime;
|
||||
|
||||
@Schema(description = "附件列表(JSON 数组)")
|
||||
private String attachments;
|
||||
|
||||
@Schema(description = "公开范围(班级 ID 数组 JSON)")
|
||||
private String publishScope;
|
||||
|
||||
@Schema(description = "评审规则 ID")
|
||||
private Long reviewRuleId;
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
package com.lesingle.creation.dto.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 更新评审规则请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "更新评审规则请求")
|
||||
public class UpdateReviewRuleDTO {
|
||||
|
||||
@Schema(description = "规则名称")
|
||||
private String ruleName;
|
||||
|
||||
@Schema(description = "规则描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "评审维度列表")
|
||||
private List<ReviewDimensionDTO> dimensions;
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
package com.lesingle.creation.dto.school;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 创建学校 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "创建学校请求")
|
||||
public class CreateSchoolDTO {
|
||||
|
||||
@Schema(description = "学校地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "联系电话")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "校长姓名")
|
||||
private String principal;
|
||||
|
||||
@Schema(description = "建校时间")
|
||||
private LocalDate established;
|
||||
|
||||
@Schema(description = "学校描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "学校 Logo URL")
|
||||
private String logo;
|
||||
|
||||
@Schema(description = "学校网站")
|
||||
private String website;
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
package com.lesingle.creation.dto.school;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 更新学校 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "更新学校请求")
|
||||
public class UpdateSchoolDTO {
|
||||
|
||||
@Schema(description = "学校地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "联系电话")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "校长姓名")
|
||||
private String principal;
|
||||
|
||||
@Schema(description = "建校时间")
|
||||
private LocalDate established;
|
||||
|
||||
@Schema(description = "学校描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "学校 Logo URL")
|
||||
private String logo;
|
||||
|
||||
@Schema(description = "学校网站")
|
||||
private String website;
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
package com.lesingle.creation.dto.student;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 创建学生 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "创建学生请求")
|
||||
public class CreateStudentDTO {
|
||||
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
|
||||
@NotBlank(message = "昵称不能为空")
|
||||
@Schema(description = "昵称")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "头像 URL")
|
||||
private String avatar;
|
||||
|
||||
@NotNull(message = "行政班级 ID 不能为空")
|
||||
@Schema(description = "行政班级 ID")
|
||||
private Long classId;
|
||||
|
||||
@Schema(description = "学号")
|
||||
private String studentNo;
|
||||
|
||||
@Schema(description = "联系电话")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "身份证号")
|
||||
private String idCard;
|
||||
|
||||
@Schema(description = "性别:1-男,2-女")
|
||||
private Integer gender;
|
||||
|
||||
@Schema(description = "出生日期")
|
||||
private LocalDate birthDate;
|
||||
|
||||
@Schema(description = "入学日期")
|
||||
private LocalDate enrollmentDate;
|
||||
|
||||
@Schema(description = "家长姓名")
|
||||
private String parentName;
|
||||
|
||||
@Schema(description = "家长电话")
|
||||
private String parentPhone;
|
||||
|
||||
@Schema(description = "家庭地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "学生描述")
|
||||
private String description;
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
package com.lesingle.creation.dto.student;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 更新学生 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "更新学生请求")
|
||||
public class UpdateStudentDTO {
|
||||
|
||||
@Schema(description = "昵称")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "头像 URL")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "行政班级 ID")
|
||||
private Long classId;
|
||||
|
||||
@Schema(description = "学号")
|
||||
private String studentNo;
|
||||
|
||||
@Schema(description = "联系电话")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "身份证号")
|
||||
private String idCard;
|
||||
|
||||
@Schema(description = "性别:1-男,2-女")
|
||||
private Integer gender;
|
||||
|
||||
@Schema(description = "出生日期")
|
||||
private LocalDate birthDate;
|
||||
|
||||
@Schema(description = "入学日期")
|
||||
private LocalDate enrollmentDate;
|
||||
|
||||
@Schema(description = "家长姓名")
|
||||
private String parentName;
|
||||
|
||||
@Schema(description = "家长电话")
|
||||
private String parentPhone;
|
||||
|
||||
@Schema(description = "家庭地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "学生描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "有效状态:1-有效,2-失效")
|
||||
private Integer validState;
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
package com.lesingle.creation.dto.teacher;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 创建教师 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "创建教师请求")
|
||||
public class CreateTeacherDTO {
|
||||
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
|
||||
@NotBlank(message = "昵称不能为空")
|
||||
@Schema(description = "昵称")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "头像 URL")
|
||||
private String avatar;
|
||||
|
||||
@NotNull(message = "部门 ID 不能为空")
|
||||
@Schema(description = "部门 ID")
|
||||
private Long departmentId;
|
||||
|
||||
@Schema(description = "工号")
|
||||
private String employeeNo;
|
||||
|
||||
@Schema(description = "联系电话")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "身份证号")
|
||||
private String idCard;
|
||||
|
||||
@Schema(description = "性别:1-男,2-女")
|
||||
private Integer gender;
|
||||
|
||||
@Schema(description = "出生日期")
|
||||
private LocalDate birthDate;
|
||||
|
||||
@Schema(description = "入职日期")
|
||||
private LocalDate hireDate;
|
||||
|
||||
@Schema(description = "任教科目")
|
||||
private String subject;
|
||||
|
||||
@Schema(description = "职称")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "教师描述")
|
||||
private String description;
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
package com.lesingle.creation.dto.teacher;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 更新教师 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "更新教师请求")
|
||||
public class UpdateTeacherDTO {
|
||||
|
||||
@Schema(description = "昵称")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "头像 URL")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "部门 ID")
|
||||
private Long departmentId;
|
||||
|
||||
@Schema(description = "工号")
|
||||
private String employeeNo;
|
||||
|
||||
@Schema(description = "联系电话")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "身份证号")
|
||||
private String idCard;
|
||||
|
||||
@Schema(description = "性别:1-男,2-女")
|
||||
private Integer gender;
|
||||
|
||||
@Schema(description = "出生日期")
|
||||
private LocalDate birthDate;
|
||||
|
||||
@Schema(description = "入职日期")
|
||||
private LocalDate hireDate;
|
||||
|
||||
@Schema(description = "任教科目")
|
||||
private String subject;
|
||||
|
||||
@Schema(description = "职称")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "教师描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "有效状态:1-有效,2-失效")
|
||||
private Integer validState;
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
package com.lesingle.creation.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.lesingle.creation.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 部门表实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("t_biz_department")
|
||||
public class Department extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 租户 ID
|
||||
*/
|
||||
@TableField("tenant_id")
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
@TableField("name")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 部门编码(在租户内唯一)
|
||||
*/
|
||||
@TableField("code")
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 父部门 ID(支持树形结构)
|
||||
*/
|
||||
@TableField("parent_id")
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 部门描述
|
||||
*/
|
||||
@TableField("description")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
@TableField("sort")
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 有效状态:1-有效,2-失效
|
||||
*/
|
||||
@TableField("valid_state")
|
||||
private Integer validState;
|
||||
|
||||
/**
|
||||
* 子部门列表(非数据库字段)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<Department> children;
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
package com.lesingle.creation.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.lesingle.creation.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 年级表实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("t_biz_grade")
|
||||
public class Grade extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 租户 ID
|
||||
*/
|
||||
@TableField("tenant_id")
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 年级名称(如:一年级、二年级)
|
||||
*/
|
||||
@TableField("name")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 年级编码(在租户内唯一,如:grade_1, grade_2)
|
||||
*/
|
||||
@TableField("code")
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 年级级别(用于排序,如:1, 2, 3)
|
||||
*/
|
||||
@TableField("level")
|
||||
private Integer level;
|
||||
|
||||
/**
|
||||
* 年级描述
|
||||
*/
|
||||
@TableField("description")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 有效状态:1-有效,2-失效
|
||||
*/
|
||||
@TableField("valid_state")
|
||||
private Integer validState;
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
package com.lesingle.creation.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.lesingle.creation.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 作业表实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("t_biz_homework")
|
||||
public class Homework extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 租户 ID
|
||||
*/
|
||||
@TableField("tenant_id")
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 作业名称
|
||||
*/
|
||||
@TableField("name")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 作业内容(富文本)
|
||||
*/
|
||||
@TableField("content")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 作业状态:unpublished/published
|
||||
*/
|
||||
@TableField("status")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
@TableField("publish_time")
|
||||
private java.time.LocalDateTime publishTime;
|
||||
|
||||
/**
|
||||
* 提交开始时间
|
||||
*/
|
||||
@TableField("submit_start_time")
|
||||
private java.time.LocalDateTime submitStartTime;
|
||||
|
||||
/**
|
||||
* 提交结束时间
|
||||
*/
|
||||
@TableField("submit_end_time")
|
||||
private java.time.LocalDateTime submitEndTime;
|
||||
|
||||
/**
|
||||
* 附件列表(JSON 格式)
|
||||
*/
|
||||
@TableField("attachments")
|
||||
private String attachments;
|
||||
|
||||
/**
|
||||
* 公开范围(班级 ID 数组 JSON 格式)
|
||||
*/
|
||||
@TableField("publish_scope")
|
||||
private String publishScope;
|
||||
|
||||
/**
|
||||
* 评审规则 ID
|
||||
*/
|
||||
@TableField("review_rule_id")
|
||||
private Long reviewRuleId;
|
||||
|
||||
/**
|
||||
* 有效状态:1-有效,2-失效
|
||||
*/
|
||||
@TableField("valid_state")
|
||||
private Integer validState;
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
package com.lesingle.creation.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.lesingle.creation.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 作业评审维度实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("t_biz_homework_review_dimension")
|
||||
public class HomeworkReviewDimension extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 规则 ID
|
||||
*/
|
||||
@TableField("rule_id")
|
||||
private Long ruleId;
|
||||
|
||||
/**
|
||||
* 租户 ID
|
||||
*/
|
||||
@TableField("tenant_id")
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 维度名称
|
||||
*/
|
||||
@TableField("name")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 满分
|
||||
*/
|
||||
@TableField("max_score")
|
||||
private BigDecimal maxScore;
|
||||
|
||||
/**
|
||||
* 维度描述
|
||||
*/
|
||||
@TableField("description")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 排序顺序
|
||||
*/
|
||||
@TableField("sort_order")
|
||||
private Integer sortOrder;
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
package com.lesingle.creation.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.lesingle.creation.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 作业评审规则表实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("t_biz_homework_review_rule")
|
||||
public class HomeworkReviewRule extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 租户 ID
|
||||
*/
|
||||
@TableField("tenant_id")
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 规则名称
|
||||
*/
|
||||
@TableField("rule_name")
|
||||
private String ruleName;
|
||||
|
||||
/**
|
||||
* 规则类型:default/custom
|
||||
*/
|
||||
@TableField("rule_type")
|
||||
private String ruleType;
|
||||
|
||||
/**
|
||||
* 总分
|
||||
*/
|
||||
@TableField("total_score")
|
||||
private BigDecimal totalScore;
|
||||
|
||||
/**
|
||||
* 规则描述
|
||||
*/
|
||||
@TableField("description")
|
||||
private String description;
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
package com.lesingle.creation.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.lesingle.creation.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 作业评分表实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("t_biz_homework_score")
|
||||
public class HomeworkScore extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 租户 ID
|
||||
*/
|
||||
@TableField("tenant_id")
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 提交 ID
|
||||
*/
|
||||
@TableField("submission_id")
|
||||
private Long submissionId;
|
||||
|
||||
/**
|
||||
* 作业 ID
|
||||
*/
|
||||
@TableField("homework_id")
|
||||
private Long homeworkId;
|
||||
|
||||
/**
|
||||
* 评分人 ID
|
||||
*/
|
||||
@TableField("reviewer_id")
|
||||
private Long reviewerId;
|
||||
|
||||
/**
|
||||
* 得分
|
||||
*/
|
||||
@TableField("score")
|
||||
private BigDecimal score;
|
||||
|
||||
/**
|
||||
* 评语
|
||||
*/
|
||||
@TableField("comment")
|
||||
private String comment;
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
package com.lesingle.creation.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.lesingle.creation.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 作业提交表实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("t_biz_homework_submission")
|
||||
public class HomeworkSubmission extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 租户 ID
|
||||
*/
|
||||
@TableField("tenant_id")
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 作业 ID
|
||||
*/
|
||||
@TableField("homework_id")
|
||||
private Long homeworkId;
|
||||
|
||||
/**
|
||||
* 学生用户 ID
|
||||
*/
|
||||
@TableField("student_id")
|
||||
private Long studentId;
|
||||
|
||||
/**
|
||||
* 学生姓名
|
||||
*/
|
||||
@TableField("student_name")
|
||||
private String studentName;
|
||||
|
||||
/**
|
||||
* 提交内容
|
||||
*/
|
||||
@TableField("content")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 提交附件列表(JSON 格式)
|
||||
*/
|
||||
@TableField("attachments")
|
||||
private String attachments;
|
||||
|
||||
/**
|
||||
* 提交状态:submitted/reviewing/returned
|
||||
*/
|
||||
@TableField("status")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 提交时间
|
||||
*/
|
||||
@TableField("submit_time")
|
||||
private java.time.LocalDateTime submitTime;
|
||||
|
||||
/**
|
||||
* 批改时间
|
||||
*/
|
||||
@TableField("review_time")
|
||||
private java.time.LocalDateTime reviewTime;
|
||||
|
||||
/**
|
||||
* 批改人 ID
|
||||
*/
|
||||
@TableField("reviewer_id")
|
||||
private Long reviewerId;
|
||||
|
||||
/**
|
||||
* 批改评语
|
||||
*/
|
||||
@TableField("review_comment")
|
||||
private String reviewComment;
|
||||
|
||||
/**
|
||||
* 批改分数
|
||||
*/
|
||||
@TableField("review_score")
|
||||
private BigDecimal reviewScore;
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
package com.lesingle.creation.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.lesingle.creation.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 学校信息表实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("t_biz_school")
|
||||
public class School extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 租户 ID(一对一)
|
||||
*/
|
||||
@TableField("tenant_id")
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 学校地址
|
||||
*/
|
||||
@TableField("address")
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
@TableField("phone")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 校长姓名
|
||||
*/
|
||||
@TableField("principal")
|
||||
private String principal;
|
||||
|
||||
/**
|
||||
* 建校时间
|
||||
*/
|
||||
@TableField("established")
|
||||
private LocalDate established;
|
||||
|
||||
/**
|
||||
* 学校描述
|
||||
*/
|
||||
@TableField("description")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 学校 Logo URL
|
||||
*/
|
||||
@TableField("logo")
|
||||
private String logo;
|
||||
|
||||
/**
|
||||
* 学校网站
|
||||
*/
|
||||
@TableField("website")
|
||||
private String website;
|
||||
|
||||
/**
|
||||
* 有效状态:1-有效,2-失效
|
||||
*/
|
||||
@TableField("valid_state")
|
||||
private Integer validState;
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
package com.lesingle.creation.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.lesingle.creation.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 班级表实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("t_biz_class")
|
||||
public class SchoolClass extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 租户 ID
|
||||
*/
|
||||
@TableField("tenant_id")
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 年级 ID
|
||||
*/
|
||||
@TableField("grade_id")
|
||||
private Long gradeId;
|
||||
|
||||
/**
|
||||
* 班级名称(如:一年级 1 班、二年级 2 班)
|
||||
*/
|
||||
@TableField("name")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 班级编码(在租户内唯一)
|
||||
*/
|
||||
@TableField("code")
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 班级类型:1-行政班级(教学班级),2-兴趣班
|
||||
*/
|
||||
@TableField("type")
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 班级容量(可选)
|
||||
*/
|
||||
@TableField("capacity")
|
||||
private Integer capacity;
|
||||
|
||||
/**
|
||||
* 班级描述
|
||||
*/
|
||||
@TableField("description")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 有效状态:1-有效,2-失效
|
||||
*/
|
||||
@TableField("valid_state")
|
||||
private Integer validState;
|
||||
}
|
||||
@ -1,102 +0,0 @@
|
||||
package com.lesingle.creation.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.lesingle.creation.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 学生表实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("t_biz_student")
|
||||
public class Student extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 用户 ID(一对一)
|
||||
*/
|
||||
@TableField("user_id")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 租户 ID
|
||||
*/
|
||||
@TableField("tenant_id")
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 行政班级 ID
|
||||
*/
|
||||
@TableField("class_id")
|
||||
private Long classId;
|
||||
|
||||
/**
|
||||
* 学号(在租户内唯一)
|
||||
*/
|
||||
@TableField("student_no")
|
||||
private String studentNo;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
@TableField("phone")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 身份证号
|
||||
*/
|
||||
@TableField("id_card")
|
||||
private String idCard;
|
||||
|
||||
/**
|
||||
* 性别:1-男,2-女
|
||||
*/
|
||||
@TableField("gender")
|
||||
private Integer gender;
|
||||
|
||||
/**
|
||||
* 出生日期
|
||||
*/
|
||||
@TableField("birth_date")
|
||||
private LocalDate birthDate;
|
||||
|
||||
/**
|
||||
* 入学日期
|
||||
*/
|
||||
@TableField("enrollment_date")
|
||||
private LocalDate enrollmentDate;
|
||||
|
||||
/**
|
||||
* 家长姓名
|
||||
*/
|
||||
@TableField("parent_name")
|
||||
private String parentName;
|
||||
|
||||
/**
|
||||
* 家长电话
|
||||
*/
|
||||
@TableField("parent_phone")
|
||||
private String parentPhone;
|
||||
|
||||
/**
|
||||
* 家庭地址
|
||||
*/
|
||||
@TableField("address")
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 学生描述
|
||||
*/
|
||||
@TableField("description")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 有效状态:1-有效,2-失效
|
||||
*/
|
||||
@TableField("valid_state")
|
||||
private Integer validState;
|
||||
}
|
||||
@ -1,96 +0,0 @@
|
||||
package com.lesingle.creation.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.lesingle.creation.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 教师表实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("t_biz_teacher")
|
||||
public class Teacher extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 用户 ID(一对一)
|
||||
*/
|
||||
@TableField("user_id")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 租户 ID
|
||||
*/
|
||||
@TableField("tenant_id")
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 部门 ID
|
||||
*/
|
||||
@TableField("department_id")
|
||||
private Long departmentId;
|
||||
|
||||
/**
|
||||
* 工号(在租户内唯一)
|
||||
*/
|
||||
@TableField("employee_no")
|
||||
private String employeeNo;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
@TableField("phone")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 身份证号
|
||||
*/
|
||||
@TableField("id_card")
|
||||
private String idCard;
|
||||
|
||||
/**
|
||||
* 性别:1-男,2-女
|
||||
*/
|
||||
@TableField("gender")
|
||||
private Integer gender;
|
||||
|
||||
/**
|
||||
* 出生日期
|
||||
*/
|
||||
@TableField("birth_date")
|
||||
private LocalDate birthDate;
|
||||
|
||||
/**
|
||||
* 入职日期
|
||||
*/
|
||||
@TableField("hire_date")
|
||||
private LocalDate hireDate;
|
||||
|
||||
/**
|
||||
* 任教科目(可选,如:语文、数学)
|
||||
*/
|
||||
@TableField("subject")
|
||||
private String subject;
|
||||
|
||||
/**
|
||||
* 职称(可选,如:高级教师、一级教师)
|
||||
*/
|
||||
@TableField("title")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 教师描述
|
||||
*/
|
||||
@TableField("description")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 有效状态:1-有效,2-失效
|
||||
*/
|
||||
@TableField("valid_state")
|
||||
private Integer validState;
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
package com.lesingle.creation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.lesingle.creation.entity.Department;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 部门 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface DepartmentMapper extends BaseMapper<Department> {
|
||||
|
||||
/**
|
||||
* 根据租户 ID 和部门编码查询部门
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @param code 部门编码
|
||||
* @return 部门实体
|
||||
*/
|
||||
@Select("SELECT * FROM t_biz_department WHERE tenant_id = #{tenantId} AND code = #{code} AND deleted = 0")
|
||||
Department selectByCode(@Param("tenantId") Long tenantId, @Param("code") String code);
|
||||
|
||||
/**
|
||||
* 根据租户 ID 查询部门列表
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @return 部门列表
|
||||
*/
|
||||
@Select("SELECT * FROM t_biz_department WHERE tenant_id = #{tenantId} AND deleted = 0 ORDER BY sort ASC")
|
||||
List<Department> selectByTenantId(@Param("tenantId") Long tenantId);
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
package com.lesingle.creation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.lesingle.creation.entity.Grade;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 年级 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface GradeMapper extends BaseMapper<Grade> {
|
||||
|
||||
/**
|
||||
* 根据租户 ID 和年级编码查询年级
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @param code 年级编码
|
||||
* @return 年级实体
|
||||
*/
|
||||
@Select("SELECT * FROM t_biz_grade WHERE tenant_id = #{tenantId} AND code = #{code} AND deleted = 0")
|
||||
Grade selectByCode(@Param("tenantId") Long tenantId, @Param("code") String code);
|
||||
|
||||
/**
|
||||
* 根据租户 ID 查询年级列表
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @return 年级列表
|
||||
*/
|
||||
@Select("SELECT * FROM t_biz_grade WHERE tenant_id = #{tenantId} AND deleted = 0 ORDER BY level ASC")
|
||||
List<Grade> selectByTenantId(@Param("tenantId") Long tenantId);
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
package com.lesingle.creation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.lesingle.creation.entity.Homework;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 作业 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface HomeworkMapper extends BaseMapper<Homework> {
|
||||
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
package com.lesingle.creation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.lesingle.creation.entity.HomeworkReviewDimension;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 作业评审维度 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface HomeworkReviewDimensionMapper extends BaseMapper<HomeworkReviewDimension> {
|
||||
|
||||
/**
|
||||
* 根据规则 ID 删除维度
|
||||
*/
|
||||
void deleteByRuleId(@Param("ruleId") Long ruleId);
|
||||
|
||||
/**
|
||||
* 根据规则 ID 查询维度列表
|
||||
*/
|
||||
List<HomeworkReviewDimension> selectByRuleId(@Param("ruleId") Long ruleId);
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
package com.lesingle.creation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.lesingle.creation.entity.HomeworkReviewRule;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 作业评审规则 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface HomeworkReviewRuleMapper extends BaseMapper<HomeworkReviewRule> {
|
||||
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
package com.lesingle.creation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.lesingle.creation.entity.HomeworkScore;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 作业评分 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface HomeworkScoreMapper extends BaseMapper<HomeworkScore> {
|
||||
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
package com.lesingle.creation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.lesingle.creation.entity.HomeworkSubmission;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 作业提交 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface HomeworkSubmissionMapper extends BaseMapper<HomeworkSubmission> {
|
||||
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
package com.lesingle.creation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.lesingle.creation.entity.SchoolClass;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 班级 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface SchoolClassMapper extends BaseMapper<SchoolClass> {
|
||||
|
||||
/**
|
||||
* 根据租户 ID 和班级编码查询班级
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @param code 班级编码
|
||||
* @return 班级实体
|
||||
*/
|
||||
@Select("SELECT * FROM t_biz_class WHERE tenant_id = #{tenantId} AND code = #{code} AND deleted = 0")
|
||||
SchoolClass selectByCode(@Param("tenantId") Long tenantId, @Param("code") String code);
|
||||
|
||||
/**
|
||||
* 根据年级 ID 查询班级列表
|
||||
*
|
||||
* @param gradeId 年级 ID
|
||||
* @return 班级列表
|
||||
*/
|
||||
@Select("SELECT * FROM t_biz_class WHERE grade_id = #{gradeId} AND deleted = 0 ORDER BY name ASC")
|
||||
List<SchoolClass> selectByGradeId(@Param("gradeId") Long gradeId);
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
package com.lesingle.creation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.lesingle.creation.entity.School;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
/**
|
||||
* 学校信息 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface SchoolMapper extends BaseMapper<School> {
|
||||
|
||||
/**
|
||||
* 根据租户 ID 查询学校
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @return 学校实体
|
||||
*/
|
||||
@Select("SELECT * FROM t_biz_school WHERE tenant_id = #{tenantId} AND deleted = 0")
|
||||
School selectByTenantId(@Param("tenantId") Long tenantId);
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
package com.lesingle.creation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.lesingle.creation.entity.Student;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
/**
|
||||
* 学生 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface StudentMapper extends BaseMapper<Student> {
|
||||
|
||||
/**
|
||||
* 根据用户 ID 查询学生
|
||||
*
|
||||
* @param userId 用户 ID
|
||||
* @return 学生实体
|
||||
*/
|
||||
@Select("SELECT * FROM t_biz_student WHERE user_id = #{userId} AND deleted = 0")
|
||||
Student selectByUserId(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 根据租户 ID 和学号查询学生
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @param studentNo 学号
|
||||
* @return 学生实体
|
||||
*/
|
||||
@Select("SELECT * FROM t_biz_student WHERE tenant_id = #{tenantId} AND student_no = #{studentNo} AND deleted = 0")
|
||||
Student selectByStudentNo(@Param("tenantId") Long tenantId, @Param("studentNo") String studentNo);
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
package com.lesingle.creation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.lesingle.creation.entity.Teacher;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
/**
|
||||
* 教师 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface TeacherMapper extends BaseMapper<Teacher> {
|
||||
|
||||
/**
|
||||
* 根据用户 ID 查询教师
|
||||
*
|
||||
* @param userId 用户 ID
|
||||
* @return 教师实体
|
||||
*/
|
||||
@Select("SELECT * FROM t_biz_teacher WHERE user_id = #{userId} AND deleted = 0")
|
||||
Teacher selectByUserId(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 根据租户 ID 和工号查询教师
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @param employeeNo 工号
|
||||
* @return 教师实体
|
||||
*/
|
||||
@Select("SELECT * FROM t_biz_teacher WHERE tenant_id = #{tenantId} AND employee_no = #{employeeNo} AND deleted = 0")
|
||||
Teacher selectByEmployeeNo(@Param("tenantId") Long tenantId, @Param("employeeNo") String employeeNo);
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
package com.lesingle.creation.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.lesingle.creation.entity.Department;
|
||||
import com.lesingle.creation.dto.department.CreateDepartmentDTO;
|
||||
import com.lesingle.creation.dto.department.UpdateDepartmentDTO;
|
||||
import com.lesingle.creation.vo.department.DepartmentVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 部门服务接口
|
||||
*/
|
||||
public interface DepartmentService extends IService<Department> {
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
DepartmentVO create(CreateDepartmentDTO dto, Long tenantId, Long creatorId);
|
||||
|
||||
/**
|
||||
* 查询部门列表(分页)
|
||||
*/
|
||||
List<DepartmentVO> list(Long tenantId, Long parentId);
|
||||
|
||||
/**
|
||||
* 查询部门树形结构
|
||||
*/
|
||||
List<DepartmentVO> tree(Long tenantId);
|
||||
|
||||
/**
|
||||
* 获取部门详情
|
||||
*/
|
||||
DepartmentVO getDetail(Long id, Long tenantId);
|
||||
|
||||
/**
|
||||
* 更新部门
|
||||
*/
|
||||
DepartmentVO update(Long id, UpdateDepartmentDTO dto, Long tenantId, Long modifierId);
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
*/
|
||||
void delete(Long id, Long tenantId);
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
package com.lesingle.creation.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.lesingle.creation.dto.grade.CreateGradeDTO;
|
||||
import com.lesingle.creation.dto.grade.UpdateGradeDTO;
|
||||
import com.lesingle.creation.vo.grade.GradeVO;
|
||||
|
||||
/**
|
||||
* 年级服务接口
|
||||
*/
|
||||
public interface GradeService {
|
||||
|
||||
/**
|
||||
* 创建年级
|
||||
*/
|
||||
GradeVO create(CreateGradeDTO dto, Long tenantId, Long creatorId);
|
||||
|
||||
/**
|
||||
* 分页查询年级列表
|
||||
*/
|
||||
Page<GradeVO> pageQuery(Integer pageNum, Integer pageSize, Long tenantId);
|
||||
|
||||
/**
|
||||
* 获取年级详情
|
||||
*/
|
||||
GradeVO getDetail(Long id, Long tenantId);
|
||||
|
||||
/**
|
||||
* 更新年级
|
||||
*/
|
||||
GradeVO update(Long id, UpdateGradeDTO dto, Long tenantId, Long modifierId);
|
||||
|
||||
/**
|
||||
* 删除年级
|
||||
*/
|
||||
void delete(Long id, Long tenantId);
|
||||
}
|
||||
@ -1,208 +0,0 @@
|
||||
package com.lesingle.creation.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.lesingle.creation.dto.homework.*;
|
||||
import com.lesingle.creation.entity.Homework;
|
||||
import com.lesingle.creation.vo.homework.HomeworkDetailVO;
|
||||
import com.lesingle.creation.vo.homework.HomeworkListVO;
|
||||
import com.lesingle.creation.vo.homework.ReviewRuleVO;
|
||||
import com.lesingle.creation.vo.homework.SubmissionVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 作业服务接口
|
||||
*/
|
||||
public interface HomeworkService extends IService<Homework> {
|
||||
|
||||
/**
|
||||
* 创建作业
|
||||
* @param dto 创建作业请求
|
||||
* @param tenantId 租户 ID
|
||||
* @param creatorId 创建人 ID
|
||||
* @return 作业详情
|
||||
*/
|
||||
HomeworkDetailVO create(CreateHomeworkDTO dto, Long tenantId, Long creatorId);
|
||||
|
||||
/**
|
||||
* 更新作业
|
||||
* @param id 作业 ID
|
||||
* @param dto 更新作业请求
|
||||
* @param tenantId 租户 ID
|
||||
* @return 作业详情
|
||||
*/
|
||||
HomeworkDetailVO update(Long id, UpdateHomeworkDTO dto, Long tenantId);
|
||||
|
||||
/**
|
||||
* 发布作业
|
||||
* @param id 作业 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @return 作业详情
|
||||
*/
|
||||
HomeworkDetailVO publish(Long id, Long tenantId);
|
||||
|
||||
/**
|
||||
* 删除作业
|
||||
* @param id 作业 ID
|
||||
* @param tenantId 租户 ID
|
||||
*/
|
||||
void delete(Long id, Long tenantId);
|
||||
|
||||
/**
|
||||
* 获取作业详情
|
||||
* @param id 作业 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @return 作业详情
|
||||
*/
|
||||
HomeworkDetailVO getDetail(Long id, Long tenantId);
|
||||
|
||||
/**
|
||||
* 分页查询作业列表
|
||||
* @param queryDTO 查询参数
|
||||
* @param tenantId 租户 ID
|
||||
* @return 作业列表
|
||||
*/
|
||||
Page<HomeworkListVO> pageQuery(HomeworkQueryDTO queryDTO, Long tenantId);
|
||||
|
||||
/**
|
||||
* 提交作业
|
||||
* @param dto 提交作业请求
|
||||
* @param tenantId 租户 ID
|
||||
* @param studentId 学生 ID
|
||||
* @return 提交详情
|
||||
*/
|
||||
SubmissionVO submit(SubmitHomeworkDTO dto, Long tenantId, Long studentId);
|
||||
|
||||
/**
|
||||
* 批改作业
|
||||
* @param dto 批改作业请求
|
||||
* @param tenantId 租户 ID
|
||||
* @param reviewerId 批改人 ID
|
||||
* @return 提交详情
|
||||
*/
|
||||
SubmissionVO review(ReviewHomeworkDTO dto, Long tenantId, Long reviewerId);
|
||||
|
||||
/**
|
||||
* 获取作业提交列表
|
||||
* @param homeworkId 作业 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @return 提交列表
|
||||
*/
|
||||
List<SubmissionVO> getSubmissions(Long homeworkId, Long tenantId);
|
||||
|
||||
/**
|
||||
* 创建评审规则
|
||||
* @param ruleName 规则名称
|
||||
* @param ruleType 规则类型
|
||||
* @param totalScore 总分
|
||||
* @param description 规则描述
|
||||
* @param tenantId 租户 ID
|
||||
* @return 评审规则
|
||||
*/
|
||||
ReviewRuleVO createReviewRule(String ruleName, String ruleType,
|
||||
java.math.BigDecimal totalScore, String description, Long tenantId);
|
||||
|
||||
/**
|
||||
* 获取评审规则列表
|
||||
* @param tenantId 租户 ID
|
||||
* @return 评审规则列表
|
||||
*/
|
||||
List<ReviewRuleVO> getReviewRules(Long tenantId);
|
||||
|
||||
/**
|
||||
* 获取可选的评审规则
|
||||
* @param tenantId 租户 ID
|
||||
* @return 评审规则列表
|
||||
*/
|
||||
List<ReviewRuleVO> getReviewRulesForSelect(Long tenantId);
|
||||
|
||||
/**
|
||||
* 更新评审规则
|
||||
* @param id 规则 ID
|
||||
* @param dto 更新请求
|
||||
* @param tenantId 租户 ID
|
||||
* @return 评审规则
|
||||
*/
|
||||
ReviewRuleVO updateReviewRule(Long id, UpdateReviewRuleDTO dto, Long tenantId);
|
||||
|
||||
/**
|
||||
* 删除评审规则
|
||||
* @param id 规则 ID
|
||||
* @param tenantId 租户 ID
|
||||
*/
|
||||
void deleteReviewRule(Long id, Long tenantId);
|
||||
|
||||
/**
|
||||
* 我的作业列表(学生端)
|
||||
* @param queryDTO 查询参数
|
||||
* @param userId 用户 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @return 作业列表
|
||||
*/
|
||||
Page<HomeworkListVO> getMyHomeworks(HomeworkQueryDTO queryDTO, Long userId, Long tenantId);
|
||||
|
||||
/**
|
||||
* 取消发布作业
|
||||
* @param id 作业 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @return 作业详情
|
||||
*/
|
||||
HomeworkDetailVO unpublish(Long id, Long tenantId);
|
||||
|
||||
/**
|
||||
* 获取提交记录列表
|
||||
* @param queryDTO 查询参数
|
||||
* @param tenantId 租户 ID
|
||||
* @return 提交记录列表
|
||||
*/
|
||||
Page<SubmissionVO> getSubmissionsList(SubmissionQueryDTO queryDTO, Long tenantId);
|
||||
|
||||
/**
|
||||
* 获取提交记录详情
|
||||
* @param id 提交 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @return 提交详情
|
||||
*/
|
||||
SubmissionVO getSubmissionDetail(Long id, Long tenantId);
|
||||
|
||||
/**
|
||||
* 获取班级树结构
|
||||
* @param tenantId 租户 ID
|
||||
* @return 班级树
|
||||
*/
|
||||
List<com.lesingle.creation.vo.school.ClassTreeNodeVO> getClassTree(Long tenantId);
|
||||
|
||||
/**
|
||||
* 获取我的提交记录
|
||||
* @param homeworkId 作业 ID
|
||||
* @param userId 用户 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @return 提交详情
|
||||
*/
|
||||
SubmissionVO getMySubmission(Long homeworkId, Long userId, Long tenantId);
|
||||
|
||||
/**
|
||||
* 提交评分
|
||||
* @param dto 评分请求
|
||||
* @param tenantId 租户 ID
|
||||
* @param reviewerId 批改人 ID
|
||||
* @return 提交详情
|
||||
*/
|
||||
SubmissionVO createScore(CreateScoreDTO dto, Long tenantId, Long reviewerId);
|
||||
|
||||
/**
|
||||
* 标记作品违规
|
||||
* @param submissionId 提交 ID
|
||||
* @param reason 原因
|
||||
* @param tenantId 租户 ID
|
||||
*/
|
||||
void markViolation(Long submissionId, String reason, Long tenantId);
|
||||
|
||||
/**
|
||||
* 重置评分
|
||||
* @param submissionId 提交 ID
|
||||
* @param tenantId 租户 ID
|
||||
*/
|
||||
void resetScore(Long submissionId, Long tenantId);
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
package com.lesingle.creation.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.lesingle.creation.dto.schoolclass.CreateClassDTO;
|
||||
import com.lesingle.creation.dto.schoolclass.UpdateClassDTO;
|
||||
import com.lesingle.creation.vo.schoolclass.ClassVO;
|
||||
|
||||
/**
|
||||
* 班级服务接口
|
||||
*/
|
||||
public interface SchoolClassService {
|
||||
|
||||
/**
|
||||
* 创建班级
|
||||
*/
|
||||
ClassVO create(CreateClassDTO dto, Long tenantId, Long creatorId);
|
||||
|
||||
/**
|
||||
* 分页查询班级列表
|
||||
*/
|
||||
Page<ClassVO> pageQuery(Integer pageNum, Integer pageSize, Long tenantId, Long gradeId, Integer type);
|
||||
|
||||
/**
|
||||
* 获取班级详情
|
||||
*/
|
||||
ClassVO getDetail(Long id, Long tenantId);
|
||||
|
||||
/**
|
||||
* 更新班级
|
||||
*/
|
||||
ClassVO update(Long id, UpdateClassDTO dto, Long tenantId, Long modifierId);
|
||||
|
||||
/**
|
||||
* 删除班级
|
||||
*/
|
||||
void delete(Long id, Long tenantId);
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
package com.lesingle.creation.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.lesingle.creation.dto.school.CreateSchoolDTO;
|
||||
import com.lesingle.creation.dto.school.UpdateSchoolDTO;
|
||||
import com.lesingle.creation.entity.School;
|
||||
import com.lesingle.creation.vo.school.SchoolVO;
|
||||
|
||||
/**
|
||||
* 学校服务接口
|
||||
*/
|
||||
public interface SchoolService extends IService<School> {
|
||||
|
||||
/**
|
||||
* 创建学校
|
||||
*
|
||||
* @param dto 创建学校 DTO
|
||||
* @param tenantId 租户 ID
|
||||
* @param creatorId 创建人 ID
|
||||
* @return 学校 VO
|
||||
*/
|
||||
SchoolVO create(CreateSchoolDTO dto, Long tenantId, Long creatorId);
|
||||
|
||||
/**
|
||||
* 根据租户 ID 查询学校
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @return 学校 VO;尚未创建时返回 {@code null}
|
||||
*/
|
||||
SchoolVO getByTenantId(Long tenantId);
|
||||
|
||||
/**
|
||||
* 更新学校
|
||||
*
|
||||
* @param dto 更新学校 DTO
|
||||
* @param tenantId 租户 ID
|
||||
* @param modifierId 修改人 ID
|
||||
* @return 学校 VO
|
||||
*/
|
||||
SchoolVO update(UpdateSchoolDTO dto, Long tenantId, Long modifierId);
|
||||
|
||||
/**
|
||||
* 删除学校
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
*/
|
||||
void delete(Long tenantId);
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
package com.lesingle.creation.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.lesingle.creation.dto.student.CreateStudentDTO;
|
||||
import com.lesingle.creation.dto.student.UpdateStudentDTO;
|
||||
import com.lesingle.creation.entity.Student;
|
||||
import com.lesingle.creation.vo.student.StudentVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 学生服务接口
|
||||
*/
|
||||
public interface StudentService extends IService<Student> {
|
||||
|
||||
/**
|
||||
* 创建学生
|
||||
*
|
||||
* @param dto 创建学生请求
|
||||
* @param tenantId 租户 ID
|
||||
* @param creatorId 创建人 ID
|
||||
* @return 学生 VO
|
||||
*/
|
||||
StudentVO create(CreateStudentDTO dto, Long tenantId, Long creatorId);
|
||||
|
||||
/**
|
||||
* 查询学生列表(分页)
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @param page 页码
|
||||
* @param pageSize 每页大小
|
||||
* @param classId 班级 ID(可选)
|
||||
* @return 学生 VO 列表
|
||||
*/
|
||||
List<StudentVO> list(Long tenantId, Integer page, Integer pageSize, Long classId);
|
||||
|
||||
/**
|
||||
* 获取学生详情
|
||||
*
|
||||
* @param id 学生 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @return 学生 VO
|
||||
*/
|
||||
StudentVO getDetail(Long id, Long tenantId);
|
||||
|
||||
/**
|
||||
* 根据用户 ID 获取学生信息
|
||||
*
|
||||
* @param userId 用户 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @return 学生 VO
|
||||
*/
|
||||
StudentVO getByUserId(Long userId, Long tenantId);
|
||||
|
||||
/**
|
||||
* 更新学生
|
||||
*
|
||||
* @param id 学生 ID
|
||||
* @param dto 更新学生请求
|
||||
* @param tenantId 租户 ID
|
||||
* @param modifierId 修改人 ID
|
||||
* @return 学生 VO
|
||||
*/
|
||||
StudentVO update(Long id, UpdateStudentDTO dto, Long tenantId, Long modifierId);
|
||||
|
||||
/**
|
||||
* 删除学生
|
||||
*
|
||||
* @param id 学生 ID
|
||||
* @param tenantId 租户 ID
|
||||
*/
|
||||
void delete(Long id, Long tenantId);
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
package com.lesingle.creation.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.lesingle.creation.dto.teacher.CreateTeacherDTO;
|
||||
import com.lesingle.creation.dto.teacher.UpdateTeacherDTO;
|
||||
import com.lesingle.creation.entity.Teacher;
|
||||
import com.lesingle.creation.vo.teacher.TeacherVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 教师服务接口
|
||||
*/
|
||||
public interface TeacherService extends IService<Teacher> {
|
||||
|
||||
/**
|
||||
* 创建教师
|
||||
*
|
||||
* @param dto 创建教师请求
|
||||
* @param tenantId 租户 ID
|
||||
* @param creatorId 创建人 ID
|
||||
* @return 教师 VO
|
||||
*/
|
||||
TeacherVO create(CreateTeacherDTO dto, Long tenantId, Long creatorId);
|
||||
|
||||
/**
|
||||
* 查询教师列表
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @param departmentId 部门 ID(可选)
|
||||
* @param nickname 昵称(可选)
|
||||
* @param username 用户名(可选)
|
||||
* @return 教师 VO 列表
|
||||
*/
|
||||
List<TeacherVO> list(Long tenantId, Long departmentId, String nickname, String username);
|
||||
|
||||
/**
|
||||
* 获取教师详情
|
||||
*
|
||||
* @param id 教师 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @return 教师 VO
|
||||
*/
|
||||
TeacherVO getDetail(Long id, Long tenantId);
|
||||
|
||||
/**
|
||||
* 根据用户 ID 获取教师信息
|
||||
*
|
||||
* @param userId 用户 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @return 教师 VO
|
||||
*/
|
||||
TeacherVO getByUserId(Long userId, Long tenantId);
|
||||
|
||||
/**
|
||||
* 更新教师
|
||||
*
|
||||
* @param id 教师 ID
|
||||
* @param dto 更新教师请求
|
||||
* @param tenantId 租户 ID
|
||||
* @param modifierId 修改人 ID
|
||||
* @return 教师 VO
|
||||
*/
|
||||
TeacherVO update(Long id, UpdateTeacherDTO dto, Long tenantId, Long modifierId);
|
||||
|
||||
/**
|
||||
* 删除教师
|
||||
*
|
||||
* @param id 教师 ID
|
||||
* @param tenantId 租户 ID
|
||||
*/
|
||||
void delete(Long id, Long tenantId);
|
||||
}
|
||||
@ -79,4 +79,17 @@ public interface UserService extends IService<User> {
|
||||
* @return 用户统计 VO
|
||||
*/
|
||||
UserStatsVO getStats();
|
||||
|
||||
/**
|
||||
* 报名/组队场景:按租户角色编码分页查询可选用户(仅允许 teacher、student)
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @param roleCode 角色编码,如 teacher、student
|
||||
* @param keyword 用户名/昵称/邮箱/手机号关键字
|
||||
* @param page 页码
|
||||
* @param pageSize 每页条数
|
||||
* @return 分页列表
|
||||
*/
|
||||
Page<UserListVO> pageCandidateUsersByRoleCode(
|
||||
Long tenantId, String roleCode, String keyword, int page, int pageSize);
|
||||
}
|
||||
|
||||
@ -1,189 +0,0 @@
|
||||
package com.lesingle.creation.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.lesingle.creation.dto.department.CreateDepartmentDTO;
|
||||
import com.lesingle.creation.dto.department.UpdateDepartmentDTO;
|
||||
import com.lesingle.creation.entity.Department;
|
||||
import com.lesingle.creation.common.exception.BusinessException;
|
||||
import com.lesingle.creation.mapper.DepartmentMapper;
|
||||
import com.lesingle.creation.service.DepartmentService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.lesingle.creation.vo.department.DepartmentVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 部门服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Department> implements DepartmentService {
|
||||
|
||||
private final DepartmentMapper departmentMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DepartmentVO create(CreateDepartmentDTO dto, Long tenantId, Long creatorId) {
|
||||
// 检查部门编码是否已存在
|
||||
Department existing = departmentMapper.selectByCode(tenantId, dto.getCode());
|
||||
if (existing != null) {
|
||||
throw new BusinessException("部门编码已存在");
|
||||
}
|
||||
|
||||
Department department = new Department();
|
||||
department.setTenantId(tenantId);
|
||||
department.setName(dto.getName());
|
||||
department.setCode(dto.getCode());
|
||||
department.setParentId(dto.getParentId());
|
||||
department.setDescription(dto.getDescription());
|
||||
department.setSort(dto.getSort() != null ? dto.getSort() : 0);
|
||||
department.setValidState(1);
|
||||
|
||||
departmentMapper.insert(department);
|
||||
log.info("创建部门成功,ID={}, 名称={}", department.getId(), dto.getName());
|
||||
|
||||
return convertToVO(department);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DepartmentVO> list(Long tenantId, Long parentId) {
|
||||
LambdaQueryWrapper<Department> wrapper = new LambdaQueryWrapper<Department>()
|
||||
.eq(Department::getTenantId, tenantId)
|
||||
.eq(Department::getDeleted, 0)
|
||||
.eq(parentId != null, Department::getParentId, parentId)
|
||||
.orderByAsc(Department::getSort);
|
||||
|
||||
List<Department> departments = this.list(wrapper);
|
||||
return departments.stream().map(this::convertToVO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DepartmentVO> tree(Long tenantId) {
|
||||
// 查询所有顶级部门(parentId 为 null 或 0)
|
||||
List<Department> allDepartments = this.list(new LambdaQueryWrapper<Department>()
|
||||
.eq(Department::getTenantId, tenantId)
|
||||
.eq(Department::getDeleted, 0)
|
||||
.orderByAsc(Department::getSort));
|
||||
|
||||
// 构建树形结构
|
||||
return buildDepartmentTree(allDepartments, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DepartmentVO getDetail(Long id, Long tenantId) {
|
||||
Department department = this.getById(id);
|
||||
if (department == null || !department.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("部门不存在");
|
||||
}
|
||||
return convertToVO(department);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DepartmentVO update(Long id, UpdateDepartmentDTO dto, Long tenantId, Long modifierId) {
|
||||
Department department = this.getById(id);
|
||||
if (department == null || !department.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("部门不存在");
|
||||
}
|
||||
|
||||
if (dto.getCode() != null) {
|
||||
// 检查新编码是否已被其他部门使用
|
||||
Department existing = departmentMapper.selectByCode(tenantId, dto.getCode());
|
||||
if (existing != null && !existing.getId().equals(id)) {
|
||||
throw new BusinessException("部门编码已存在");
|
||||
}
|
||||
department.setCode(dto.getCode());
|
||||
}
|
||||
if (dto.getName() != null) {
|
||||
department.setName(dto.getName());
|
||||
}
|
||||
if (dto.getParentId() != null) {
|
||||
// 不能将自己设置为父部门
|
||||
if (dto.getParentId().equals(id)) {
|
||||
throw new BusinessException("不能将自己设置为父部门");
|
||||
}
|
||||
department.setParentId(dto.getParentId());
|
||||
}
|
||||
if (dto.getDescription() != null) {
|
||||
department.setDescription(dto.getDescription());
|
||||
}
|
||||
if (dto.getSort() != null) {
|
||||
department.setSort(dto.getSort());
|
||||
}
|
||||
if (dto.getValidState() != null) {
|
||||
department.setValidState(dto.getValidState());
|
||||
}
|
||||
|
||||
this.updateById(department);
|
||||
log.info("更新部门成功,ID={}", id);
|
||||
|
||||
return convertToVO(department);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id, Long tenantId) {
|
||||
Department department = this.getById(id);
|
||||
if (department == null || !department.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("部门不存在");
|
||||
}
|
||||
|
||||
// 检查是否有子部门
|
||||
Long childCount = this.count(new LambdaQueryWrapper<Department>()
|
||||
.eq(Department::getParentId, id)
|
||||
.eq(Department::getDeleted, 0));
|
||||
if (childCount > 0) {
|
||||
throw new BusinessException("存在子部门,无法删除");
|
||||
}
|
||||
|
||||
department.setDeleted(1);
|
||||
this.updateById(department);
|
||||
log.info("删除部门成功,ID={}", id);
|
||||
}
|
||||
|
||||
private DepartmentVO convertToVO(Department department) {
|
||||
DepartmentVO vo = new DepartmentVO();
|
||||
vo.setId(department.getId());
|
||||
vo.setTenantId(department.getTenantId());
|
||||
vo.setName(department.getName());
|
||||
vo.setCode(department.getCode());
|
||||
vo.setParentId(department.getParentId());
|
||||
vo.setDescription(department.getDescription());
|
||||
vo.setSort(department.getSort());
|
||||
vo.setValidState(department.getValidState());
|
||||
vo.setCreateTime(department.getCreateTime());
|
||||
vo.setUpdateTime(department.getUpdateTime());
|
||||
return vo;
|
||||
}
|
||||
|
||||
private List<DepartmentVO> buildDepartmentTree(List<Department> allDepartments, Long parentId) {
|
||||
List<DepartmentVO> tree = allDepartments.stream()
|
||||
.filter(dept -> {
|
||||
if (parentId == null) {
|
||||
return dept.getParentId() == null || dept.getParentId() == 0;
|
||||
}
|
||||
return parentId.equals(dept.getParentId());
|
||||
})
|
||||
.map(this::convertToVO)
|
||||
.sorted(Comparator.comparing(DepartmentVO::getSort))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 递归构建子部门
|
||||
for (DepartmentVO node : tree) {
|
||||
List<DepartmentVO> children = buildDepartmentTree(allDepartments, node.getId());
|
||||
if (!children.isEmpty()) {
|
||||
node.setChildren(children);
|
||||
}
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
}
|
||||
@ -1,152 +0,0 @@
|
||||
package com.lesingle.creation.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.lesingle.creation.dto.grade.CreateGradeDTO;
|
||||
import com.lesingle.creation.dto.grade.UpdateGradeDTO;
|
||||
import com.lesingle.creation.entity.Grade;
|
||||
import com.lesingle.creation.common.exception.BusinessException;
|
||||
import com.lesingle.creation.mapper.GradeMapper;
|
||||
import com.lesingle.creation.service.GradeService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.lesingle.creation.vo.grade.GradeVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 年级服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GradeServiceImpl extends ServiceImpl<GradeMapper, Grade> implements GradeService {
|
||||
|
||||
private final GradeMapper gradeMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public GradeVO create(CreateGradeDTO dto, Long tenantId, Long creatorId) {
|
||||
// 检查年级编码是否已存在
|
||||
Grade existing = gradeMapper.selectByCode(tenantId, dto.getCode());
|
||||
if (existing != null) {
|
||||
throw new BusinessException("年级编码已存在");
|
||||
}
|
||||
|
||||
// 检查年级级别是否已存在
|
||||
Grade existingByLevel = this.getOne(new LambdaQueryWrapper<Grade>()
|
||||
.eq(Grade::getTenantId, tenantId)
|
||||
.eq(Grade::getLevel, dto.getLevel())
|
||||
.eq(Grade::getDeleted, 0));
|
||||
if (existingByLevel != null) {
|
||||
throw new BusinessException("年级级别已存在");
|
||||
}
|
||||
|
||||
Grade grade = new Grade();
|
||||
grade.setTenantId(tenantId);
|
||||
grade.setName(dto.getName());
|
||||
grade.setCode(dto.getCode());
|
||||
grade.setLevel(dto.getLevel());
|
||||
grade.setDescription(dto.getDescription());
|
||||
grade.setValidState(1);
|
||||
|
||||
gradeMapper.insert(grade);
|
||||
log.info("创建年级成功,ID={}, 名称={}", grade.getId(), dto.getName());
|
||||
|
||||
return convertToVO(grade);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<GradeVO> pageQuery(Integer pageNum, Integer pageSize, Long tenantId) {
|
||||
Page<Grade> page = new Page<>(pageNum, pageSize);
|
||||
|
||||
LambdaQueryWrapper<Grade> wrapper = new LambdaQueryWrapper<Grade>()
|
||||
.eq(Grade::getTenantId, tenantId)
|
||||
.eq(Grade::getDeleted, 0)
|
||||
.orderByAsc(Grade::getLevel);
|
||||
|
||||
Page<Grade> resultPage = this.page(page, wrapper);
|
||||
|
||||
Page<GradeVO> voPage = new Page<>(resultPage.getCurrent(), resultPage.getSize(), resultPage.getTotal());
|
||||
voPage.setRecords(resultPage.getRecords().stream().map(this::convertToVO).toList());
|
||||
return voPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GradeVO getDetail(Long id, Long tenantId) {
|
||||
Grade grade = this.getById(id);
|
||||
if (grade == null || !grade.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("年级不存在");
|
||||
}
|
||||
return convertToVO(grade);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public GradeVO update(Long id, UpdateGradeDTO dto, Long tenantId, Long modifierId) {
|
||||
Grade grade = this.getById(id);
|
||||
if (grade == null || !grade.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("年级不存在");
|
||||
}
|
||||
|
||||
if (dto.getName() != null) {
|
||||
grade.setName(dto.getName());
|
||||
}
|
||||
if (dto.getCode() != null) {
|
||||
// 检查新编码是否已被其他年级使用
|
||||
Grade existing = gradeMapper.selectByCode(tenantId, dto.getCode());
|
||||
if (existing != null && !existing.getId().equals(id)) {
|
||||
throw new BusinessException("年级编码已存在");
|
||||
}
|
||||
grade.setCode(dto.getCode());
|
||||
}
|
||||
if (dto.getLevel() != null) {
|
||||
// 检查新级别是否已被其他年级使用
|
||||
Grade existing = this.getOne(new LambdaQueryWrapper<Grade>()
|
||||
.eq(Grade::getTenantId, tenantId)
|
||||
.eq(Grade::getLevel, dto.getLevel())
|
||||
.eq(Grade::getDeleted, 0)
|
||||
.ne(Grade::getId, id));
|
||||
if (existing != null) {
|
||||
throw new BusinessException("年级级别已存在");
|
||||
}
|
||||
grade.setLevel(dto.getLevel());
|
||||
}
|
||||
if (dto.getDescription() != null) {
|
||||
grade.setDescription(dto.getDescription());
|
||||
}
|
||||
|
||||
this.updateById(grade);
|
||||
log.info("更新年级成功,ID={}", id);
|
||||
|
||||
return convertToVO(grade);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id, Long tenantId) {
|
||||
Grade grade = this.getById(id);
|
||||
if (grade == null || !grade.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("年级不存在");
|
||||
}
|
||||
|
||||
grade.setDeleted(1);
|
||||
this.updateById(grade);
|
||||
log.info("删除年级成功,ID={}", id);
|
||||
}
|
||||
|
||||
private GradeVO convertToVO(Grade grade) {
|
||||
GradeVO vo = new GradeVO();
|
||||
vo.setId(grade.getId());
|
||||
vo.setTenantId(grade.getTenantId());
|
||||
vo.setName(grade.getName());
|
||||
vo.setCode(grade.getCode());
|
||||
vo.setLevel(grade.getLevel());
|
||||
vo.setDescription(grade.getDescription());
|
||||
vo.setValidState(grade.getValidState());
|
||||
vo.setCreateTime(grade.getCreateTime());
|
||||
vo.setUpdateTime(grade.getUpdateTime());
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@ -1,633 +0,0 @@
|
||||
package com.lesingle.creation.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.lesingle.creation.common.enums.ValidStateEnum;
|
||||
import com.lesingle.creation.dto.homework.*;
|
||||
import com.lesingle.creation.entity.Homework;
|
||||
import com.lesingle.creation.entity.HomeworkReviewDimension;
|
||||
import com.lesingle.creation.entity.HomeworkReviewRule;
|
||||
import com.lesingle.creation.entity.HomeworkSubmission;
|
||||
import com.lesingle.creation.common.exception.BusinessException;
|
||||
import com.lesingle.creation.mapper.HomeworkMapper;
|
||||
import com.lesingle.creation.mapper.HomeworkReviewDimensionMapper;
|
||||
import com.lesingle.creation.mapper.HomeworkReviewRuleMapper;
|
||||
import com.lesingle.creation.mapper.HomeworkSubmissionMapper;
|
||||
import com.lesingle.creation.service.HomeworkService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.lesingle.creation.vo.homework.HomeworkDetailVO;
|
||||
import com.lesingle.creation.vo.homework.HomeworkListVO;
|
||||
import com.lesingle.creation.vo.homework.ReviewDimensionVO;
|
||||
import com.lesingle.creation.vo.homework.ReviewRuleVO;
|
||||
import com.lesingle.creation.vo.homework.SubmissionVO;
|
||||
import com.lesingle.creation.vo.school.ClassTreeNodeVO;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 作业服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class HomeworkServiceImpl extends ServiceImpl<HomeworkMapper, Homework>
|
||||
implements HomeworkService {
|
||||
|
||||
private final HomeworkSubmissionMapper homeworkSubmissionMapper;
|
||||
private final HomeworkReviewRuleMapper homeworkReviewRuleMapper;
|
||||
private final HomeworkReviewDimensionMapper homeworkReviewDimensionMapper;
|
||||
|
||||
public HomeworkServiceImpl(HomeworkSubmissionMapper homeworkSubmissionMapper,
|
||||
HomeworkReviewRuleMapper homeworkReviewRuleMapper,
|
||||
HomeworkReviewDimensionMapper homeworkReviewDimensionMapper) {
|
||||
this.homeworkSubmissionMapper = homeworkSubmissionMapper;
|
||||
this.homeworkReviewRuleMapper = homeworkReviewRuleMapper;
|
||||
this.homeworkReviewDimensionMapper = homeworkReviewDimensionMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public HomeworkDetailVO create(CreateHomeworkDTO dto, Long tenantId, Long creatorId) {
|
||||
// 验证时间顺序
|
||||
if (dto.getSubmitStartTime().isAfter(dto.getSubmitEndTime())) {
|
||||
throw new BusinessException("提交开始时间不能晚于结束时间");
|
||||
}
|
||||
|
||||
// 检查作业名称是否已存在
|
||||
Homework existing = this.getOne(new LambdaQueryWrapper<Homework>()
|
||||
.eq(Homework::getTenantId, tenantId)
|
||||
.eq(Homework::getName, dto.getName())
|
||||
.eq(Homework::getDeleted, 0));
|
||||
if (existing != null) {
|
||||
throw new BusinessException("作业名称已存在");
|
||||
}
|
||||
|
||||
// 创建作业
|
||||
Homework homework = new Homework();
|
||||
homework.setTenantId(tenantId);
|
||||
homework.setName(dto.getName());
|
||||
homework.setContent(dto.getContent());
|
||||
homework.setStatus("unpublished");
|
||||
homework.setSubmitStartTime(dto.getSubmitStartTime());
|
||||
homework.setSubmitEndTime(dto.getSubmitEndTime());
|
||||
homework.setAttachments(dto.getAttachments());
|
||||
homework.setPublishScope(dto.getPublishScope());
|
||||
homework.setReviewRuleId(dto.getReviewRuleId());
|
||||
homework.setValidState(ValidStateEnum.VALID.getCode());
|
||||
|
||||
this.save(homework);
|
||||
log.info("创建作业成功,ID={}, 名称={}", homework.getId(), dto.getName());
|
||||
|
||||
return convertToDetailVO(homework);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public HomeworkDetailVO update(Long id, UpdateHomeworkDTO dto, Long tenantId) {
|
||||
Homework homework = this.getById(id);
|
||||
if (homework == null || !homework.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("作业不存在");
|
||||
}
|
||||
|
||||
// 已发布的作业不能修改
|
||||
if ("published".equals(homework.getStatus())) {
|
||||
throw new BusinessException("已发布的作业不能修改");
|
||||
}
|
||||
|
||||
homework.setName(dto.getName());
|
||||
homework.setContent(dto.getContent());
|
||||
if (dto.getSubmitStartTime() != null) {
|
||||
homework.setSubmitStartTime(dto.getSubmitStartTime());
|
||||
}
|
||||
if (dto.getSubmitEndTime() != null) {
|
||||
homework.setSubmitEndTime(dto.getSubmitEndTime());
|
||||
}
|
||||
homework.setAttachments(dto.getAttachments());
|
||||
homework.setPublishScope(dto.getPublishScope());
|
||||
if (dto.getReviewRuleId() != null) {
|
||||
homework.setReviewRuleId(dto.getReviewRuleId());
|
||||
}
|
||||
|
||||
this.updateById(homework);
|
||||
log.info("更新作业成功,ID={}", id);
|
||||
|
||||
return convertToDetailVO(homework);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public HomeworkDetailVO publish(Long id, Long tenantId) {
|
||||
Homework homework = this.getById(id);
|
||||
if (homework == null || !homework.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("作业不存在");
|
||||
}
|
||||
|
||||
homework.setStatus("published");
|
||||
homework.setPublishTime(LocalDateTime.now());
|
||||
this.updateById(homework);
|
||||
log.info("发布作业成功,ID={}", id);
|
||||
|
||||
return convertToDetailVO(homework);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id, Long tenantId) {
|
||||
Homework homework = this.getById(id);
|
||||
if (homework == null || !homework.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("作业不存在");
|
||||
}
|
||||
|
||||
this.removeById(id);
|
||||
log.info("删除作业成功,ID={}", id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HomeworkDetailVO getDetail(Long id, Long tenantId) {
|
||||
Homework homework = this.getById(id);
|
||||
if (homework == null || !homework.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("作业不存在");
|
||||
}
|
||||
|
||||
return convertToDetailVO(homework);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<HomeworkListVO> pageQuery(HomeworkQueryDTO queryDTO, Long tenantId) {
|
||||
Page<Homework> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
|
||||
|
||||
LambdaQueryWrapper<Homework> wrapper = new LambdaQueryWrapper<Homework>()
|
||||
.eq(Homework::getTenantId, tenantId)
|
||||
.eq(Homework::getDeleted, 0)
|
||||
.like(queryDTO.getNameKeyword() != null, Homework::getName, queryDTO.getNameKeyword())
|
||||
.eq(queryDTO.getStatus() != null, Homework::getStatus, queryDTO.getStatus())
|
||||
.eq(queryDTO.getValidState() != null, Homework::getValidState, queryDTO.getValidState())
|
||||
.orderByDesc(Homework::getCreateTime);
|
||||
|
||||
Page<Homework> resultPage = this.page(page, wrapper);
|
||||
|
||||
// 转换为 VO
|
||||
List<HomeworkListVO> voList = resultPage.getRecords().stream()
|
||||
.map(this::convertToListVO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Page<HomeworkListVO> voPage = new Page<>(
|
||||
resultPage.getCurrent(),
|
||||
resultPage.getSize(),
|
||||
resultPage.getTotal()
|
||||
);
|
||||
voPage.setRecords(voList);
|
||||
return voPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public SubmissionVO submit(SubmitHomeworkDTO dto, Long tenantId, Long studentId) {
|
||||
Homework homework = this.getById(dto.getHomeworkId());
|
||||
if (homework == null || !homework.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("作业不存在");
|
||||
}
|
||||
|
||||
// 检查是否已提交
|
||||
HomeworkSubmission existing = homeworkSubmissionMapper.selectOne(new LambdaQueryWrapper<HomeworkSubmission>()
|
||||
.eq(HomeworkSubmission::getHomeworkId, dto.getHomeworkId())
|
||||
.eq(HomeworkSubmission::getStudentId, studentId)
|
||||
.eq(HomeworkSubmission::getDeleted, 0));
|
||||
if (existing != null) {
|
||||
throw new BusinessException("您已经提交过该作业");
|
||||
}
|
||||
|
||||
// 检查提交时间
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (now.isBefore(homework.getSubmitStartTime())) {
|
||||
throw new BusinessException("还未到提交开始时间");
|
||||
}
|
||||
if (now.isAfter(homework.getSubmitEndTime())) {
|
||||
throw new BusinessException("已错过提交截止时间");
|
||||
}
|
||||
|
||||
HomeworkSubmission submission = new HomeworkSubmission();
|
||||
submission.setTenantId(tenantId);
|
||||
submission.setHomeworkId(dto.getHomeworkId());
|
||||
submission.setStudentId(studentId);
|
||||
submission.setContent(dto.getContent());
|
||||
submission.setAttachments(dto.getAttachments());
|
||||
submission.setStatus("submitted");
|
||||
submission.setSubmitTime(LocalDateTime.now());
|
||||
|
||||
homeworkSubmissionMapper.insert(submission);
|
||||
log.info("提交作业成功,ID={}, 学生 ID={}", submission.getId(), studentId);
|
||||
|
||||
return convertToSubmissionVO(submission);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public SubmissionVO review(ReviewHomeworkDTO dto, Long tenantId, Long reviewerId) {
|
||||
HomeworkSubmission submission = homeworkSubmissionMapper.selectById(dto.getSubmissionId());
|
||||
if (submission == null || !submission.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("提交记录不存在");
|
||||
}
|
||||
|
||||
submission.setReviewerId(reviewerId);
|
||||
submission.setReviewComment(dto.getComment());
|
||||
submission.setReviewScore(dto.getScore());
|
||||
submission.setReviewTime(LocalDateTime.now());
|
||||
submission.setStatus("reviewing");
|
||||
|
||||
homeworkSubmissionMapper.updateById(submission);
|
||||
log.info("批改作业成功,提交 ID={}, 分数={}", dto.getSubmissionId(), dto.getScore());
|
||||
|
||||
return convertToSubmissionVO(submission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SubmissionVO> getSubmissions(Long homeworkId, Long tenantId) {
|
||||
List<HomeworkSubmission> submissions = homeworkSubmissionMapper.selectList(
|
||||
new LambdaQueryWrapper<HomeworkSubmission>()
|
||||
.eq(HomeworkSubmission::getHomeworkId, homeworkId)
|
||||
.eq(HomeworkSubmission::getTenantId, tenantId)
|
||||
.eq(HomeworkSubmission::getDeleted, 0)
|
||||
.orderByDesc(HomeworkSubmission::getSubmitTime)
|
||||
);
|
||||
|
||||
return submissions.stream()
|
||||
.map(this::convertToSubmissionVO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ReviewRuleVO createReviewRule(String ruleName, String ruleType,
|
||||
java.math.BigDecimal totalScore, String description, Long tenantId) {
|
||||
HomeworkReviewRule rule = new HomeworkReviewRule();
|
||||
rule.setTenantId(tenantId);
|
||||
rule.setRuleName(ruleName);
|
||||
rule.setRuleType(ruleType);
|
||||
rule.setTotalScore(totalScore);
|
||||
rule.setDescription(description);
|
||||
|
||||
homeworkReviewRuleMapper.insert(rule);
|
||||
log.info("创建评审规则成功,ID={}", rule.getId());
|
||||
|
||||
return convertToReviewRuleVO(rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ReviewRuleVO> getReviewRules(Long tenantId) {
|
||||
List<HomeworkReviewRule> rules = homeworkReviewRuleMapper.selectList(
|
||||
new LambdaQueryWrapper<HomeworkReviewRule>()
|
||||
.eq(HomeworkReviewRule::getTenantId, tenantId)
|
||||
.eq(HomeworkReviewRule::getDeleted, 0)
|
||||
.orderByDesc(HomeworkReviewRule::getCreateTime)
|
||||
);
|
||||
|
||||
return rules.stream()
|
||||
.map(this::convertToReviewRuleVO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// ========== 转换方法 ==========
|
||||
|
||||
private HomeworkDetailVO convertToDetailVO(Homework homework) {
|
||||
HomeworkDetailVO vo = new HomeworkDetailVO();
|
||||
vo.setId(homework.getId());
|
||||
vo.setName(homework.getName());
|
||||
vo.setContent(homework.getContent());
|
||||
vo.setStatus(homework.getStatus());
|
||||
vo.setPublishTime(homework.getPublishTime());
|
||||
vo.setSubmitStartTime(homework.getSubmitStartTime());
|
||||
vo.setSubmitEndTime(homework.getSubmitEndTime());
|
||||
vo.setAttachments(homework.getAttachments());
|
||||
vo.setPublishScope(homework.getPublishScope());
|
||||
vo.setReviewRuleId(homework.getReviewRuleId());
|
||||
vo.setCreateTime(homework.getCreateTime());
|
||||
|
||||
// 统计提交人数和批改人数
|
||||
Long submissionCount = homeworkSubmissionMapper.selectCount(
|
||||
new LambdaQueryWrapper<HomeworkSubmission>()
|
||||
.eq(HomeworkSubmission::getHomeworkId, homework.getId())
|
||||
.eq(HomeworkSubmission::getDeleted, 0)
|
||||
);
|
||||
Long reviewedCount = homeworkSubmissionMapper.selectCount(
|
||||
new LambdaQueryWrapper<HomeworkSubmission>()
|
||||
.eq(HomeworkSubmission::getHomeworkId, homework.getId())
|
||||
.eq(HomeworkSubmission::getStatus, "reviewing")
|
||||
.eq(HomeworkSubmission::getDeleted, 0)
|
||||
);
|
||||
vo.setSubmissionCount(submissionCount.intValue());
|
||||
vo.setReviewedCount(reviewedCount.intValue());
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
private HomeworkListVO convertToListVO(Homework homework) {
|
||||
HomeworkListVO vo = new HomeworkListVO();
|
||||
vo.setId(homework.getId());
|
||||
vo.setName(homework.getName());
|
||||
vo.setStatus(homework.getStatus());
|
||||
vo.setPublishTime(homework.getPublishTime());
|
||||
vo.setSubmitStartTime(homework.getSubmitStartTime());
|
||||
vo.setSubmitEndTime(homework.getSubmitEndTime());
|
||||
vo.setCreateTime(homework.getCreateTime());
|
||||
|
||||
// 统计提交人数和批改人数
|
||||
Long submissionCount = homeworkSubmissionMapper.selectCount(
|
||||
new LambdaQueryWrapper<HomeworkSubmission>()
|
||||
.eq(HomeworkSubmission::getHomeworkId, homework.getId())
|
||||
.eq(HomeworkSubmission::getDeleted, 0)
|
||||
);
|
||||
Long reviewedCount = homeworkSubmissionMapper.selectCount(
|
||||
new LambdaQueryWrapper<HomeworkSubmission>()
|
||||
.eq(HomeworkSubmission::getHomeworkId, homework.getId())
|
||||
.eq(HomeworkSubmission::getStatus, "reviewing")
|
||||
.eq(HomeworkSubmission::getDeleted, 0)
|
||||
);
|
||||
vo.setSubmissionCount(submissionCount.intValue());
|
||||
vo.setReviewedCount(reviewedCount.intValue());
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
private SubmissionVO convertToSubmissionVO(HomeworkSubmission submission) {
|
||||
SubmissionVO vo = new SubmissionVO();
|
||||
vo.setId(submission.getId());
|
||||
vo.setHomeworkId(submission.getHomeworkId());
|
||||
vo.setStudentId(submission.getStudentId());
|
||||
vo.setStudentName(submission.getStudentName());
|
||||
vo.setContent(submission.getContent());
|
||||
vo.setAttachments(submission.getAttachments());
|
||||
vo.setStatus(submission.getStatus());
|
||||
vo.setSubmitTime(submission.getSubmitTime());
|
||||
vo.setReviewTime(submission.getReviewTime());
|
||||
vo.setReviewerId(submission.getReviewerId());
|
||||
vo.setReviewComment(submission.getReviewComment());
|
||||
vo.setReviewScore(submission.getReviewScore());
|
||||
return vo;
|
||||
}
|
||||
|
||||
private ReviewRuleVO convertToReviewRuleVO(HomeworkReviewRule rule) {
|
||||
ReviewRuleVO vo = new ReviewRuleVO();
|
||||
vo.setId(rule.getId());
|
||||
vo.setRuleName(rule.getRuleName());
|
||||
vo.setRuleType(rule.getRuleType());
|
||||
vo.setTotalScore(rule.getTotalScore());
|
||||
vo.setDescription(rule.getDescription());
|
||||
|
||||
// 查询关联的维度列表
|
||||
List<HomeworkReviewDimension> dimensions = homeworkReviewDimensionMapper.selectByRuleId(rule.getId());
|
||||
if (dimensions != null && !dimensions.isEmpty()) {
|
||||
vo.setDimensions(dimensions.stream().map(this::convertToReviewDimensionVO).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
private ReviewDimensionVO convertToReviewDimensionVO(HomeworkReviewDimension dimension) {
|
||||
ReviewDimensionVO vo = new ReviewDimensionVO();
|
||||
vo.setId(dimension.getId());
|
||||
vo.setName(dimension.getName());
|
||||
vo.setMaxScore(dimension.getMaxScore());
|
||||
vo.setDescription(dimension.getDescription());
|
||||
vo.setSortOrder(dimension.getSortOrder());
|
||||
return vo;
|
||||
}
|
||||
|
||||
// ========== 新增方法实现 ==========
|
||||
|
||||
@Override
|
||||
public List<ReviewRuleVO> getReviewRulesForSelect(Long tenantId) {
|
||||
// 获取所有有效的评审规则,用于下拉选择
|
||||
List<HomeworkReviewRule> rules = homeworkReviewRuleMapper.selectList(
|
||||
new LambdaQueryWrapper<HomeworkReviewRule>()
|
||||
.eq(HomeworkReviewRule::getTenantId, tenantId)
|
||||
.eq(HomeworkReviewRule::getDeleted, 0)
|
||||
.orderByDesc(HomeworkReviewRule::getCreateTime)
|
||||
);
|
||||
return rules.stream()
|
||||
.map(this::convertToReviewRuleVO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ReviewRuleVO updateReviewRule(Long id, UpdateReviewRuleDTO dto, Long tenantId) {
|
||||
HomeworkReviewRule rule = homeworkReviewRuleMapper.selectById(id);
|
||||
if (rule == null || !rule.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("评审规则不存在");
|
||||
}
|
||||
|
||||
if (dto.getRuleName() != null) {
|
||||
rule.setRuleName(dto.getRuleName());
|
||||
}
|
||||
if (dto.getDescription() != null) {
|
||||
rule.setDescription(dto.getDescription());
|
||||
}
|
||||
// 注意:维度列表需要单独处理
|
||||
if (dto.getDimensions() != null && !dto.getDimensions().isEmpty()) {
|
||||
// 删除原有维度
|
||||
homeworkReviewDimensionMapper.deleteByRuleId(id);
|
||||
// 插入新维度
|
||||
int sortOrder = 0;
|
||||
for (ReviewDimensionDTO dim : dto.getDimensions()) {
|
||||
HomeworkReviewDimension dimension = new HomeworkReviewDimension();
|
||||
dimension.setRuleId(id);
|
||||
dimension.setTenantId(tenantId);
|
||||
dimension.setName(dim.getName());
|
||||
dimension.setMaxScore(dim.getMaxScore());
|
||||
dimension.setDescription(dim.getDescription());
|
||||
dimension.setSortOrder(sortOrder++);
|
||||
homeworkReviewDimensionMapper.insert(dimension);
|
||||
}
|
||||
}
|
||||
|
||||
homeworkReviewRuleMapper.updateById(rule);
|
||||
log.info("更新评审规则成功,ID={}", id);
|
||||
|
||||
return convertToReviewRuleVO(rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteReviewRule(Long id, Long tenantId) {
|
||||
HomeworkReviewRule rule = homeworkReviewRuleMapper.selectById(id);
|
||||
if (rule == null || !rule.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("评审规则不存在");
|
||||
}
|
||||
|
||||
// 删除关联的维度
|
||||
homeworkReviewDimensionMapper.deleteByRuleId(id);
|
||||
// 逻辑删除规则
|
||||
rule.setDeleted(1);
|
||||
homeworkReviewRuleMapper.updateById(rule);
|
||||
log.info("删除评审规则成功,ID={}", id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<HomeworkListVO> getMyHomeworks(HomeworkQueryDTO queryDTO, Long userId, Long tenantId) {
|
||||
Page<Homework> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
|
||||
|
||||
// TODO: 需要根据用户 ID 查询关联的班级/年级,进而查询作业
|
||||
// 这里简化处理:查询所有已发布的作业
|
||||
LambdaQueryWrapper<Homework> wrapper = new LambdaQueryWrapper<Homework>()
|
||||
.eq(Homework::getTenantId, tenantId)
|
||||
.eq(Homework::getDeleted, 0)
|
||||
.eq(Homework::getStatus, "published")
|
||||
.like(queryDTO.getNameKeyword() != null, Homework::getName, queryDTO.getNameKeyword())
|
||||
.orderByDesc(Homework::getCreateTime);
|
||||
|
||||
Page<Homework> resultPage = this.page(page, wrapper);
|
||||
|
||||
List<HomeworkListVO> voList = resultPage.getRecords().stream()
|
||||
.map(this::convertToListVO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Page<HomeworkListVO> voPage = new Page<>(
|
||||
resultPage.getCurrent(),
|
||||
resultPage.getSize(),
|
||||
resultPage.getTotal()
|
||||
);
|
||||
voPage.setRecords(voList);
|
||||
return voPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public HomeworkDetailVO unpublish(Long id, Long tenantId) {
|
||||
Homework homework = this.getById(id);
|
||||
if (homework == null || !homework.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("作业不存在");
|
||||
}
|
||||
|
||||
homework.setStatus("unpublished");
|
||||
this.updateById(homework);
|
||||
log.info("撤销发布作业成功,ID={}", id);
|
||||
|
||||
return convertToDetailVO(homework);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<SubmissionVO> getSubmissionsList(SubmissionQueryDTO queryDTO, Long tenantId) {
|
||||
Page<HomeworkSubmission> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
|
||||
|
||||
LambdaQueryWrapper<HomeworkSubmission> wrapper = new LambdaQueryWrapper<HomeworkSubmission>()
|
||||
.eq(HomeworkSubmission::getTenantId, tenantId)
|
||||
.eq(HomeworkSubmission::getDeleted, 0)
|
||||
.eq(queryDTO.getHomeworkId() != null, HomeworkSubmission::getHomeworkId, queryDTO.getHomeworkId())
|
||||
.like(queryDTO.getStudentName() != null, HomeworkSubmission::getStudentName, queryDTO.getStudentName())
|
||||
.eq(queryDTO.getStatus() != null, HomeworkSubmission::getStatus, queryDTO.getStatus())
|
||||
.orderByDesc(HomeworkSubmission::getSubmitTime);
|
||||
|
||||
Page<HomeworkSubmission> resultPage = homeworkSubmissionMapper.selectPage(page, wrapper);
|
||||
|
||||
List<SubmissionVO> voList = resultPage.getRecords().stream()
|
||||
.map(this::convertToSubmissionVO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Page<SubmissionVO> voPage = new Page<>(
|
||||
resultPage.getCurrent(),
|
||||
resultPage.getSize(),
|
||||
resultPage.getTotal()
|
||||
);
|
||||
voPage.setRecords(voList);
|
||||
return voPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubmissionVO getSubmissionDetail(Long id, Long tenantId) {
|
||||
HomeworkSubmission submission = homeworkSubmissionMapper.selectById(id);
|
||||
if (submission == null || !submission.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("提交记录不存在");
|
||||
}
|
||||
|
||||
return convertToSubmissionVO(submission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<com.lesingle.creation.vo.school.ClassTreeNodeVO> getClassTree(Long tenantId) {
|
||||
// TODO: 需要实现班级树查询逻辑
|
||||
// 这里返回空列表,后续需要根据实际的班级/年级表实现
|
||||
log.warn("getClassTree 方法暂未实现,tenantId={}", tenantId);
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubmissionVO getMySubmission(Long homeworkId, Long userId, Long tenantId) {
|
||||
HomeworkSubmission submission = homeworkSubmissionMapper.selectOne(
|
||||
new LambdaQueryWrapper<HomeworkSubmission>()
|
||||
.eq(HomeworkSubmission::getTenantId, tenantId)
|
||||
.eq(HomeworkSubmission::getHomeworkId, homeworkId)
|
||||
.eq(HomeworkSubmission::getStudentId, userId)
|
||||
.eq(HomeworkSubmission::getDeleted, 0)
|
||||
);
|
||||
|
||||
if (submission == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return convertToSubmissionVO(submission);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public SubmissionVO createScore(CreateScoreDTO dto, Long tenantId, Long reviewerId) {
|
||||
HomeworkSubmission submission = homeworkSubmissionMapper.selectById(dto.getSubmissionId());
|
||||
if (submission == null || !submission.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("提交记录不存在");
|
||||
}
|
||||
|
||||
// TODO: 保存维度评分到 homework_dimension_scores 表
|
||||
// 这里先更新总分和评语
|
||||
if (dto.getDimensionScores() != null && !dto.getDimensionScores().isEmpty()) {
|
||||
java.math.BigDecimal totalScore = dto.getDimensionScores().stream()
|
||||
.map(com.lesingle.creation.dto.homework.DimensionScoreDTO::getScore)
|
||||
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
|
||||
submission.setReviewScore(totalScore);
|
||||
}
|
||||
submission.setReviewComment(dto.getComments());
|
||||
submission.setReviewerId(reviewerId);
|
||||
submission.setStatus("reviewing");
|
||||
submission.setReviewTime(LocalDateTime.now());
|
||||
|
||||
homeworkSubmissionMapper.updateById(submission);
|
||||
log.info("创建评分成功,提交 ID={}, 评审人 ID={}", dto.getSubmissionId(), reviewerId);
|
||||
|
||||
return convertToSubmissionVO(submission);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void markViolation(Long submissionId, String reason, Long tenantId) {
|
||||
HomeworkSubmission submission = homeworkSubmissionMapper.selectById(submissionId);
|
||||
if (submission == null || !submission.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("提交记录不存在");
|
||||
}
|
||||
|
||||
submission.setStatus("violation");
|
||||
submission.setReviewComment("违规:" + reason);
|
||||
homeworkSubmissionMapper.updateById(submission);
|
||||
log.info("标记违规成功,提交 ID={}, 原因={}", submissionId, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void resetScore(Long submissionId, Long tenantId) {
|
||||
HomeworkSubmission submission = homeworkSubmissionMapper.selectById(submissionId);
|
||||
if (submission == null || !submission.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("提交记录不存在");
|
||||
}
|
||||
|
||||
submission.setReviewScore(null);
|
||||
submission.setReviewComment(null);
|
||||
submission.setReviewerId(null);
|
||||
submission.setStatus("submitted");
|
||||
homeworkSubmissionMapper.updateById(submission);
|
||||
log.info("重置评分成功,提交 ID={}", submissionId);
|
||||
}
|
||||
}
|
||||
@ -1,173 +0,0 @@
|
||||
package com.lesingle.creation.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.lesingle.creation.dto.schoolclass.CreateClassDTO;
|
||||
import com.lesingle.creation.dto.schoolclass.UpdateClassDTO;
|
||||
import com.lesingle.creation.entity.Grade;
|
||||
import com.lesingle.creation.entity.SchoolClass;
|
||||
import com.lesingle.creation.common.exception.BusinessException;
|
||||
import com.lesingle.creation.mapper.GradeMapper;
|
||||
import com.lesingle.creation.mapper.SchoolClassMapper;
|
||||
import com.lesingle.creation.service.SchoolClassService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.lesingle.creation.vo.grade.GradeVO;
|
||||
import com.lesingle.creation.vo.schoolclass.ClassVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 班级服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SchoolClassServiceImpl extends ServiceImpl<SchoolClassMapper, SchoolClass> implements SchoolClassService {
|
||||
|
||||
private final SchoolClassMapper schoolClassMapper;
|
||||
private final GradeMapper gradeMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ClassVO create(CreateClassDTO dto, Long tenantId, Long creatorId) {
|
||||
// 检查班级编码是否已存在
|
||||
SchoolClass existing = schoolClassMapper.selectByCode(tenantId, dto.getCode());
|
||||
if (existing != null) {
|
||||
throw new BusinessException("班级编码已存在");
|
||||
}
|
||||
|
||||
// 验证年级是否存在
|
||||
Grade grade = gradeMapper.selectById(dto.getGradeId());
|
||||
if (grade == null) {
|
||||
throw new BusinessException("年级不存在");
|
||||
}
|
||||
|
||||
SchoolClass schoolClass = new SchoolClass();
|
||||
schoolClass.setTenantId(tenantId);
|
||||
schoolClass.setGradeId(dto.getGradeId());
|
||||
schoolClass.setName(dto.getName());
|
||||
schoolClass.setCode(dto.getCode());
|
||||
schoolClass.setType(dto.getType());
|
||||
schoolClass.setCapacity(dto.getCapacity());
|
||||
schoolClass.setDescription(dto.getDescription());
|
||||
schoolClass.setValidState(1);
|
||||
|
||||
schoolClassMapper.insert(schoolClass);
|
||||
log.info("创建班级成功,ID={}, 名称={}", schoolClass.getId(), dto.getName());
|
||||
|
||||
return convertToVO(schoolClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<ClassVO> pageQuery(Integer pageNum, Integer pageSize, Long tenantId, Long gradeId, Integer type) {
|
||||
Page<SchoolClass> page = new Page<>(pageNum, pageSize);
|
||||
|
||||
LambdaQueryWrapper<SchoolClass> wrapper = new LambdaQueryWrapper<SchoolClass>()
|
||||
.eq(SchoolClass::getTenantId, tenantId)
|
||||
.eq(SchoolClass::getDeleted, 0)
|
||||
.eq(gradeId != null, SchoolClass::getGradeId, gradeId)
|
||||
.eq(type != null, SchoolClass::getType, type)
|
||||
.orderByAsc(SchoolClass::getName);
|
||||
|
||||
Page<SchoolClass> resultPage = this.page(page, wrapper);
|
||||
|
||||
Page<ClassVO> voPage = new Page<>(resultPage.getCurrent(), resultPage.getSize(), resultPage.getTotal());
|
||||
voPage.setRecords(resultPage.getRecords().stream().map(this::convertToVO).toList());
|
||||
return voPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassVO getDetail(Long id, Long tenantId) {
|
||||
SchoolClass schoolClass = this.getById(id);
|
||||
if (schoolClass == null || !schoolClass.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("班级不存在");
|
||||
}
|
||||
return convertToVO(schoolClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ClassVO update(Long id, UpdateClassDTO dto, Long tenantId, Long modifierId) {
|
||||
SchoolClass schoolClass = this.getById(id);
|
||||
if (schoolClass == null || !schoolClass.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("班级不存在");
|
||||
}
|
||||
|
||||
if (dto.getGradeId() != null) {
|
||||
// 验证年级是否存在
|
||||
Grade grade = gradeMapper.selectById(dto.getGradeId());
|
||||
if (grade == null) {
|
||||
throw new BusinessException("年级不存在");
|
||||
}
|
||||
schoolClass.setGradeId(dto.getGradeId());
|
||||
}
|
||||
if (dto.getName() != null) {
|
||||
schoolClass.setName(dto.getName());
|
||||
}
|
||||
if (dto.getCode() != null) {
|
||||
// 检查新编码是否已被其他班级使用
|
||||
SchoolClass existing = schoolClassMapper.selectByCode(tenantId, dto.getCode());
|
||||
if (existing != null && !existing.getId().equals(id)) {
|
||||
throw new BusinessException("班级编码已存在");
|
||||
}
|
||||
schoolClass.setCode(dto.getCode());
|
||||
}
|
||||
if (dto.getType() != null) {
|
||||
schoolClass.setType(dto.getType());
|
||||
}
|
||||
if (dto.getCapacity() != null) {
|
||||
schoolClass.setCapacity(dto.getCapacity());
|
||||
}
|
||||
if (dto.getDescription() != null) {
|
||||
schoolClass.setDescription(dto.getDescription());
|
||||
}
|
||||
|
||||
this.updateById(schoolClass);
|
||||
log.info("更新班级成功,ID={}", id);
|
||||
|
||||
return convertToVO(schoolClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id, Long tenantId) {
|
||||
SchoolClass schoolClass = this.getById(id);
|
||||
if (schoolClass == null || !schoolClass.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("班级不存在");
|
||||
}
|
||||
|
||||
schoolClass.setDeleted(1);
|
||||
this.updateById(schoolClass);
|
||||
log.info("删除班级成功,ID={}", id);
|
||||
}
|
||||
|
||||
private ClassVO convertToVO(SchoolClass schoolClass) {
|
||||
ClassVO vo = new ClassVO();
|
||||
vo.setId(schoolClass.getId());
|
||||
vo.setTenantId(schoolClass.getTenantId());
|
||||
vo.setGradeId(schoolClass.getGradeId());
|
||||
vo.setName(schoolClass.getName());
|
||||
vo.setCode(schoolClass.getCode());
|
||||
vo.setType(schoolClass.getType());
|
||||
vo.setCapacity(schoolClass.getCapacity());
|
||||
vo.setDescription(schoolClass.getDescription());
|
||||
vo.setValidState(schoolClass.getValidState());
|
||||
vo.setCreateTime(schoolClass.getCreateTime());
|
||||
vo.setUpdateTime(schoolClass.getUpdateTime());
|
||||
|
||||
// 关联查询年级信息
|
||||
Grade grade = gradeMapper.selectById(schoolClass.getGradeId());
|
||||
if (grade != null) {
|
||||
GradeVO gradeVO = new GradeVO();
|
||||
gradeVO.setId(grade.getId());
|
||||
gradeVO.setName(grade.getName());
|
||||
gradeVO.setCode(grade.getCode());
|
||||
gradeVO.setLevel(grade.getLevel());
|
||||
vo.setGrade(gradeVO);
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@ -1,133 +0,0 @@
|
||||
package com.lesingle.creation.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.lesingle.creation.common.exception.BusinessException;
|
||||
import com.lesingle.creation.dto.school.CreateSchoolDTO;
|
||||
import com.lesingle.creation.dto.school.UpdateSchoolDTO;
|
||||
import com.lesingle.creation.entity.School;
|
||||
import com.lesingle.creation.mapper.SchoolMapper;
|
||||
import com.lesingle.creation.service.SchoolService;
|
||||
import com.lesingle.creation.vo.school.SchoolVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 学校服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SchoolServiceImpl extends ServiceImpl<SchoolMapper, School> implements SchoolService {
|
||||
|
||||
private final SchoolMapper schoolMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public SchoolVO create(CreateSchoolDTO dto, Long tenantId, Long creatorId) {
|
||||
log.info("开始创建学校,租户 ID: {}", tenantId);
|
||||
|
||||
// 检查租户是否已有学校信息
|
||||
School existingSchool = schoolMapper.selectByTenantId(tenantId);
|
||||
if (existingSchool != null) {
|
||||
throw new BusinessException("该租户已存在学校信息");
|
||||
}
|
||||
|
||||
// 创建学校
|
||||
School school = new School();
|
||||
school.setTenantId(tenantId);
|
||||
school.setAddress(dto.getAddress());
|
||||
school.setPhone(dto.getPhone());
|
||||
school.setPrincipal(dto.getPrincipal());
|
||||
school.setEstablished(dto.getEstablished());
|
||||
school.setDescription(dto.getDescription());
|
||||
school.setLogo(dto.getLogo());
|
||||
school.setWebsite(dto.getWebsite());
|
||||
school.setValidState(1);
|
||||
school.setCreateBy(String.valueOf(creatorId));
|
||||
|
||||
schoolMapper.insert(school);
|
||||
log.info("学校创建成功,学校 ID: {}", school.getId());
|
||||
|
||||
return convertToVO(school);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchoolVO getByTenantId(Long tenantId) {
|
||||
log.info("查询学校信息,租户 ID: {}", tenantId);
|
||||
|
||||
School school = schoolMapper.selectByTenantId(tenantId);
|
||||
if (school == null) {
|
||||
log.debug("当前租户尚未创建学校,租户 ID: {}", tenantId);
|
||||
return null;
|
||||
}
|
||||
|
||||
return convertToVO(school);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public SchoolVO update(UpdateSchoolDTO dto, Long tenantId, Long modifierId) {
|
||||
log.info("更新学校信息,租户 ID: {}", tenantId);
|
||||
|
||||
School existingSchool = schoolMapper.selectByTenantId(tenantId);
|
||||
if (existingSchool == null) {
|
||||
throw new BusinessException("学校信息不存在");
|
||||
}
|
||||
|
||||
// 更新学校信息
|
||||
School school = new School();
|
||||
school.setId(existingSchool.getId());
|
||||
school.setAddress(dto.getAddress());
|
||||
school.setPhone(dto.getPhone());
|
||||
school.setPrincipal(dto.getPrincipal());
|
||||
school.setEstablished(dto.getEstablished());
|
||||
school.setDescription(dto.getDescription());
|
||||
school.setLogo(dto.getLogo());
|
||||
school.setWebsite(dto.getWebsite());
|
||||
school.setUpdateBy(String.valueOf(modifierId));
|
||||
|
||||
schoolMapper.updateById(school);
|
||||
log.info("学校更新成功,学校 ID: {}", school.getId());
|
||||
|
||||
return convertToVO(schoolMapper.selectById(school.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long tenantId) {
|
||||
log.info("删除学校信息,租户 ID: {}", tenantId);
|
||||
|
||||
School school = schoolMapper.selectByTenantId(tenantId);
|
||||
if (school == null) {
|
||||
throw new BusinessException("学校信息不存在");
|
||||
}
|
||||
|
||||
// 删除学校(逻辑删除)
|
||||
schoolMapper.deleteById(school.getId());
|
||||
log.info("学校删除成功,学校 ID: {}", school.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 VO
|
||||
*/
|
||||
private SchoolVO convertToVO(School school) {
|
||||
SchoolVO vo = new SchoolVO();
|
||||
vo.setId(school.getId());
|
||||
vo.setTenantId(school.getTenantId());
|
||||
vo.setAddress(school.getAddress());
|
||||
vo.setPhone(school.getPhone());
|
||||
vo.setPrincipal(school.getPrincipal());
|
||||
vo.setEstablished(school.getEstablished());
|
||||
vo.setDescription(school.getDescription());
|
||||
vo.setLogo(school.getLogo());
|
||||
vo.setWebsite(school.getWebsite());
|
||||
vo.setValidState(school.getValidState());
|
||||
vo.setCreateBy(school.getCreateBy());
|
||||
vo.setCreateTime(school.getCreateTime());
|
||||
vo.setUpdateBy(school.getUpdateBy());
|
||||
vo.setUpdateTime(school.getUpdateTime());
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@ -1,293 +0,0 @@
|
||||
package com.lesingle.creation.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.lesingle.creation.dto.student.CreateStudentDTO;
|
||||
import com.lesingle.creation.dto.student.UpdateStudentDTO;
|
||||
import com.lesingle.creation.entity.SchoolClass;
|
||||
import com.lesingle.creation.entity.Student;
|
||||
import com.lesingle.creation.entity.User;
|
||||
import com.lesingle.creation.common.exception.BusinessException;
|
||||
import com.lesingle.creation.mapper.StudentMapper;
|
||||
import com.lesingle.creation.mapper.UserMapper;
|
||||
import com.lesingle.creation.mapper.SchoolClassMapper;
|
||||
import com.lesingle.creation.service.SchoolClassService;
|
||||
import com.lesingle.creation.service.StudentService;
|
||||
import com.lesingle.creation.vo.student.StudentVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 学生服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {
|
||||
|
||||
private final StudentMapper studentMapper;
|
||||
private final UserMapper userMapper;
|
||||
private final SchoolClassService schoolClassService;
|
||||
private final SchoolClassMapper schoolClassMapper;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public StudentVO create(CreateStudentDTO dto, Long tenantId, Long creatorId) {
|
||||
// 检查用户名是否已存在
|
||||
User existingUser = userMapper.selectByUsername(dto.getUsername());
|
||||
if (existingUser != null) {
|
||||
throw new BusinessException("用户名已存在");
|
||||
}
|
||||
|
||||
// 检查学号是否已存在
|
||||
Student existingStudent = studentMapper.selectByStudentNo(tenantId, dto.getStudentNo());
|
||||
if (existingStudent != null) {
|
||||
throw new BusinessException("学号已存在");
|
||||
}
|
||||
|
||||
// 检查班级是否存在
|
||||
SchoolClass schoolClass = schoolClassMapper.selectById(dto.getClassId());
|
||||
if (schoolClass == null || !schoolClass.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("班级不存在");
|
||||
}
|
||||
|
||||
// 创建用户账号
|
||||
User user = new User();
|
||||
user.setTenantId(tenantId);
|
||||
user.setUsername(dto.getUsername());
|
||||
user.setPassword(passwordEncoder.encode(dto.getPassword()));
|
||||
user.setNickname(dto.getNickname());
|
||||
user.setEmail(dto.getEmail());
|
||||
user.setAvatar(dto.getAvatar());
|
||||
user.setUserType("student"); // 学生用户
|
||||
user.setStatus("active");
|
||||
user.setUserSource("admin_created");
|
||||
|
||||
userMapper.insert(user);
|
||||
log.info("创建用户账号成功,ID={}, 用户名={}", user.getId(), dto.getUsername());
|
||||
|
||||
// 创建学生信息
|
||||
Student student = new Student();
|
||||
student.setTenantId(tenantId);
|
||||
student.setUserId(user.getId());
|
||||
student.setClassId(dto.getClassId());
|
||||
student.setStudentNo(dto.getStudentNo());
|
||||
student.setPhone(dto.getPhone());
|
||||
student.setIdCard(dto.getIdCard());
|
||||
student.setGender(dto.getGender());
|
||||
student.setBirthDate(dto.getBirthDate());
|
||||
student.setEnrollmentDate(dto.getEnrollmentDate());
|
||||
student.setParentName(dto.getParentName());
|
||||
student.setParentPhone(dto.getParentPhone());
|
||||
student.setAddress(dto.getAddress());
|
||||
student.setDescription(dto.getDescription());
|
||||
student.setValidState(1);
|
||||
|
||||
studentMapper.insert(student);
|
||||
log.info("创建学生成功,ID={}, 学号={}", student.getId(), dto.getStudentNo());
|
||||
|
||||
return convertToVO(student, user, schoolClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StudentVO> list(Long tenantId, Integer page, Integer pageSize, Long classId) {
|
||||
LambdaQueryWrapper<Student> wrapper = new LambdaQueryWrapper<Student>()
|
||||
.eq(Student::getTenantId, tenantId)
|
||||
.eq(Student::getDeleted, 0)
|
||||
.eq(classId != null, Student::getClassId, classId)
|
||||
.orderByDesc(Student::getCreateTime);
|
||||
|
||||
Page<Student> studentPage = this.page(new Page<>(page, pageSize), wrapper);
|
||||
|
||||
// 转换为 VO
|
||||
return studentPage.getRecords().stream()
|
||||
.map(student -> {
|
||||
User user = userMapper.selectById(student.getUserId());
|
||||
SchoolClass schoolClass = schoolClassMapper.selectById(student.getClassId());
|
||||
return convertToVO(student, user, schoolClass);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public StudentVO getDetail(Long id, Long tenantId) {
|
||||
Student student = this.getById(id);
|
||||
if (student == null || !student.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("学生不存在");
|
||||
}
|
||||
|
||||
User user = userMapper.selectById(student.getUserId());
|
||||
SchoolClass schoolClass = schoolClassMapper.selectById(student.getClassId());
|
||||
|
||||
return convertToVO(student, user, schoolClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StudentVO getByUserId(Long userId, Long tenantId) {
|
||||
Student student = studentMapper.selectByUserId(userId);
|
||||
if (student == null || !student.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("学生不存在");
|
||||
}
|
||||
|
||||
User user = userMapper.selectById(student.getUserId());
|
||||
SchoolClass schoolClass = schoolClassMapper.selectById(student.getClassId());
|
||||
|
||||
return convertToVO(student, user, schoolClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public StudentVO update(Long id, UpdateStudentDTO dto, Long tenantId, Long modifierId) {
|
||||
Student student = this.getById(id);
|
||||
if (student == null || !student.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("学生不存在");
|
||||
}
|
||||
|
||||
// 检查学号是否已被其他学生使用
|
||||
if (dto.getStudentNo() != null) {
|
||||
Student existingStudent = studentMapper.selectByStudentNo(tenantId, dto.getStudentNo());
|
||||
if (existingStudent != null && !existingStudent.getId().equals(id)) {
|
||||
throw new BusinessException("学号已存在");
|
||||
}
|
||||
student.setStudentNo(dto.getStudentNo());
|
||||
}
|
||||
|
||||
// 更新学生信息
|
||||
if (dto.getClassId() != null) {
|
||||
SchoolClass schoolClass = schoolClassMapper.selectById(dto.getClassId());
|
||||
if (schoolClass == null || !schoolClass.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("班级不存在");
|
||||
}
|
||||
student.setClassId(dto.getClassId());
|
||||
}
|
||||
if (dto.getPhone() != null) {
|
||||
student.setPhone(dto.getPhone());
|
||||
}
|
||||
if (dto.getIdCard() != null) {
|
||||
student.setIdCard(dto.getIdCard());
|
||||
}
|
||||
if (dto.getGender() != null) {
|
||||
student.setGender(dto.getGender());
|
||||
}
|
||||
if (dto.getBirthDate() != null) {
|
||||
student.setBirthDate(dto.getBirthDate());
|
||||
}
|
||||
if (dto.getEnrollmentDate() != null) {
|
||||
student.setEnrollmentDate(dto.getEnrollmentDate());
|
||||
}
|
||||
if (dto.getParentName() != null) {
|
||||
student.setParentName(dto.getParentName());
|
||||
}
|
||||
if (dto.getParentPhone() != null) {
|
||||
student.setParentPhone(dto.getParentPhone());
|
||||
}
|
||||
if (dto.getAddress() != null) {
|
||||
student.setAddress(dto.getAddress());
|
||||
}
|
||||
if (dto.getDescription() != null) {
|
||||
student.setDescription(dto.getDescription());
|
||||
}
|
||||
if (dto.getValidState() != null) {
|
||||
student.setValidState(dto.getValidState());
|
||||
}
|
||||
|
||||
// 更新用户信息(如果有)
|
||||
if (dto.getNickname() != null || dto.getEmail() != null || dto.getAvatar() != null) {
|
||||
User user = userMapper.selectById(student.getUserId());
|
||||
if (user != null) {
|
||||
if (dto.getNickname() != null) {
|
||||
user.setNickname(dto.getNickname());
|
||||
}
|
||||
if (dto.getEmail() != null) {
|
||||
user.setEmail(dto.getEmail());
|
||||
}
|
||||
if (dto.getAvatar() != null) {
|
||||
user.setAvatar(dto.getAvatar());
|
||||
}
|
||||
userMapper.updateById(user);
|
||||
}
|
||||
}
|
||||
|
||||
this.updateById(student);
|
||||
log.info("更新学生成功,ID={}", id);
|
||||
|
||||
User user = userMapper.selectById(student.getUserId());
|
||||
SchoolClass schoolClass = schoolClassMapper.selectById(student.getClassId());
|
||||
return convertToVO(student, user, schoolClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id, Long tenantId) {
|
||||
Student student = this.getById(id);
|
||||
if (student == null || !student.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("学生不存在");
|
||||
}
|
||||
|
||||
// 逻辑删除学生
|
||||
student.setDeleted(1);
|
||||
this.updateById(student);
|
||||
|
||||
// 同时禁用关联的用户账号
|
||||
User user = userMapper.selectById(student.getUserId());
|
||||
if (user != null) {
|
||||
user.setStatus("disabled"); // 禁用状态
|
||||
userMapper.updateById(user);
|
||||
}
|
||||
|
||||
log.info("删除学生成功,ID={}, 用户 ID={}", id, student.getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 VO
|
||||
*/
|
||||
private StudentVO convertToVO(Student student, User user, SchoolClass schoolClass) {
|
||||
StudentVO vo = new StudentVO();
|
||||
vo.setId(student.getId());
|
||||
vo.setUserId(student.getUserId());
|
||||
vo.setTenantId(student.getTenantId());
|
||||
vo.setClassId(student.getClassId());
|
||||
vo.setStudentNo(student.getStudentNo());
|
||||
vo.setPhone(student.getPhone());
|
||||
vo.setIdCard(student.getIdCard());
|
||||
vo.setGender(student.getGender());
|
||||
vo.setBirthDate(student.getBirthDate());
|
||||
vo.setEnrollmentDate(student.getEnrollmentDate());
|
||||
vo.setParentName(student.getParentName());
|
||||
vo.setParentPhone(student.getParentPhone());
|
||||
vo.setAddress(student.getAddress());
|
||||
vo.setDescription(student.getDescription());
|
||||
vo.setValidState(student.getValidState());
|
||||
vo.setCreateTime(student.getCreateTime());
|
||||
vo.setUpdateTime(student.getUpdateTime());
|
||||
|
||||
// 用户信息
|
||||
if (user != null) {
|
||||
StudentVO.UserInfo userInfo = new StudentVO.UserInfo();
|
||||
userInfo.setUsername(user.getUsername());
|
||||
userInfo.setNickname(user.getNickname());
|
||||
userInfo.setEmail(user.getEmail());
|
||||
userInfo.setAvatar(user.getAvatar());
|
||||
vo.setUserInfo(userInfo);
|
||||
}
|
||||
|
||||
// 班级信息
|
||||
if (schoolClass != null) {
|
||||
StudentVO.ClassInfo classInfo = new StudentVO.ClassInfo();
|
||||
classInfo.setId(schoolClass.getId());
|
||||
classInfo.setName(schoolClass.getName());
|
||||
classInfo.setCode(schoolClass.getCode());
|
||||
vo.setClassInfo(classInfo);
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@ -1,307 +0,0 @@
|
||||
package com.lesingle.creation.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.lesingle.creation.dto.teacher.CreateTeacherDTO;
|
||||
import com.lesingle.creation.dto.teacher.UpdateTeacherDTO;
|
||||
import com.lesingle.creation.entity.Department;
|
||||
import com.lesingle.creation.entity.Teacher;
|
||||
import com.lesingle.creation.entity.User;
|
||||
import com.lesingle.creation.common.exception.BusinessException;
|
||||
import com.lesingle.creation.mapper.TeacherMapper;
|
||||
import com.lesingle.creation.mapper.UserMapper;
|
||||
import com.lesingle.creation.service.DepartmentService;
|
||||
import com.lesingle.creation.service.TeacherService;
|
||||
import com.lesingle.creation.vo.teacher.TeacherVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 教师服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TeacherServiceImpl extends ServiceImpl<TeacherMapper, Teacher> implements TeacherService {
|
||||
|
||||
private final TeacherMapper teacherMapper;
|
||||
private final UserMapper userMapper;
|
||||
private final DepartmentService departmentService;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TeacherVO create(CreateTeacherDTO dto, Long tenantId, Long creatorId) {
|
||||
// 检查用户名是否已存在
|
||||
User existingUser = userMapper.selectByUsername(dto.getUsername());
|
||||
if (existingUser != null) {
|
||||
throw new BusinessException("用户名已存在");
|
||||
}
|
||||
|
||||
// 检查工号是否已存在
|
||||
Teacher existingTeacher = teacherMapper.selectByEmployeeNo(tenantId, dto.getEmployeeNo());
|
||||
if (existingTeacher != null) {
|
||||
throw new BusinessException("工号已存在");
|
||||
}
|
||||
|
||||
// 检查部门是否存在
|
||||
Department department = departmentService.getById(dto.getDepartmentId());
|
||||
if (department == null || !department.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("部门不存在");
|
||||
}
|
||||
|
||||
// 创建用户账号
|
||||
User user = new User();
|
||||
user.setTenantId(tenantId);
|
||||
user.setUsername(dto.getUsername());
|
||||
user.setPassword(passwordEncoder.encode(dto.getPassword()));
|
||||
user.setNickname(dto.getNickname());
|
||||
user.setEmail(dto.getEmail());
|
||||
user.setAvatar(dto.getAvatar());
|
||||
user.setUserType("teacher"); // 教师用户
|
||||
user.setStatus("active");
|
||||
user.setUserSource("admin_created");
|
||||
|
||||
userMapper.insert(user);
|
||||
log.info("创建用户账号成功,ID={}, 用户名={}", user.getId(), dto.getUsername());
|
||||
|
||||
// 创建教师信息
|
||||
Teacher teacher = new Teacher();
|
||||
teacher.setTenantId(tenantId);
|
||||
teacher.setUserId(user.getId());
|
||||
teacher.setDepartmentId(dto.getDepartmentId());
|
||||
teacher.setEmployeeNo(dto.getEmployeeNo());
|
||||
teacher.setPhone(dto.getPhone());
|
||||
teacher.setIdCard(dto.getIdCard());
|
||||
teacher.setGender(dto.getGender());
|
||||
teacher.setBirthDate(dto.getBirthDate());
|
||||
teacher.setHireDate(dto.getHireDate());
|
||||
teacher.setSubject(dto.getSubject());
|
||||
teacher.setTitle(dto.getTitle());
|
||||
teacher.setDescription(dto.getDescription());
|
||||
teacher.setValidState(1);
|
||||
|
||||
teacherMapper.insert(teacher);
|
||||
log.info("创建教师成功,ID={}, 工号={}", teacher.getId(), dto.getEmployeeNo());
|
||||
|
||||
return convertToVO(teacher, user, department);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TeacherVO> list(Long tenantId, Long departmentId, String nickname, String username) {
|
||||
LambdaQueryWrapper<Teacher> wrapper = new LambdaQueryWrapper<Teacher>()
|
||||
.eq(Teacher::getTenantId, tenantId)
|
||||
.eq(Teacher::getDeleted, 0)
|
||||
.eq(departmentId != null, Teacher::getDepartmentId, departmentId)
|
||||
.orderByDesc(Teacher::getCreateTime);
|
||||
|
||||
List<Teacher> teachers = this.list(wrapper);
|
||||
|
||||
// 过滤用户名和昵称
|
||||
if (username != null || nickname != null) {
|
||||
List<Long> userIds = teachers.stream().map(Teacher::getUserId).collect(Collectors.toList());
|
||||
LambdaQueryWrapper<User> userWrapper = new LambdaQueryWrapper<User>()
|
||||
.in(User::getId, userIds)
|
||||
.eq(User::getDeleted, 0);
|
||||
|
||||
if (username != null && !username.isEmpty()) {
|
||||
userWrapper.like(User::getUsername, username);
|
||||
}
|
||||
if (nickname != null && !nickname.isEmpty()) {
|
||||
userWrapper.like(User::getNickname, nickname);
|
||||
}
|
||||
|
||||
List<User> users = userMapper.selectList(userWrapper);
|
||||
List<Long> validUserIds = users.stream().map(User::getId).collect(Collectors.toList());
|
||||
|
||||
teachers = teachers.stream()
|
||||
.filter(t -> validUserIds.contains(t.getUserId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// 转换为 VO
|
||||
return teachers.stream()
|
||||
.map(teacher -> {
|
||||
User user = userMapper.selectById(teacher.getUserId());
|
||||
Department dept = departmentService.getById(teacher.getDepartmentId());
|
||||
return convertToVO(teacher, user, dept);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TeacherVO getDetail(Long id, Long tenantId) {
|
||||
Teacher teacher = this.getById(id);
|
||||
if (teacher == null || !teacher.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("教师不存在");
|
||||
}
|
||||
|
||||
User user = userMapper.selectById(teacher.getUserId());
|
||||
Department department = departmentService.getById(teacher.getDepartmentId());
|
||||
|
||||
return convertToVO(teacher, user, department);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TeacherVO getByUserId(Long userId, Long tenantId) {
|
||||
Teacher teacher = teacherMapper.selectByUserId(userId);
|
||||
if (teacher == null || !teacher.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("教师不存在");
|
||||
}
|
||||
|
||||
User user = userMapper.selectById(teacher.getUserId());
|
||||
Department department = departmentService.getById(teacher.getDepartmentId());
|
||||
|
||||
return convertToVO(teacher, user, department);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TeacherVO update(Long id, UpdateTeacherDTO dto, Long tenantId, Long modifierId) {
|
||||
Teacher teacher = this.getById(id);
|
||||
if (teacher == null || !teacher.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("教师不存在");
|
||||
}
|
||||
|
||||
// 检查工号是否已被其他教师使用
|
||||
if (dto.getEmployeeNo() != null) {
|
||||
Teacher existingTeacher = teacherMapper.selectByEmployeeNo(tenantId, dto.getEmployeeNo());
|
||||
if (existingTeacher != null && !existingTeacher.getId().equals(id)) {
|
||||
throw new BusinessException("工号已存在");
|
||||
}
|
||||
teacher.setEmployeeNo(dto.getEmployeeNo());
|
||||
}
|
||||
|
||||
// 更新教师信息
|
||||
if (dto.getDepartmentId() != null) {
|
||||
Department department = departmentService.getById(dto.getDepartmentId());
|
||||
if (department == null || !department.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("部门不存在");
|
||||
}
|
||||
teacher.setDepartmentId(dto.getDepartmentId());
|
||||
}
|
||||
if (dto.getPhone() != null) {
|
||||
teacher.setPhone(dto.getPhone());
|
||||
}
|
||||
if (dto.getIdCard() != null) {
|
||||
teacher.setIdCard(dto.getIdCard());
|
||||
}
|
||||
if (dto.getGender() != null) {
|
||||
teacher.setGender(dto.getGender());
|
||||
}
|
||||
if (dto.getBirthDate() != null) {
|
||||
teacher.setBirthDate(dto.getBirthDate());
|
||||
}
|
||||
if (dto.getHireDate() != null) {
|
||||
teacher.setHireDate(dto.getHireDate());
|
||||
}
|
||||
if (dto.getSubject() != null) {
|
||||
teacher.setSubject(dto.getSubject());
|
||||
}
|
||||
if (dto.getTitle() != null) {
|
||||
teacher.setTitle(dto.getTitle());
|
||||
}
|
||||
if (dto.getDescription() != null) {
|
||||
teacher.setDescription(dto.getDescription());
|
||||
}
|
||||
if (dto.getValidState() != null) {
|
||||
teacher.setValidState(dto.getValidState());
|
||||
}
|
||||
|
||||
// 更新用户信息(如果有)
|
||||
if (dto.getNickname() != null || dto.getEmail() != null || dto.getAvatar() != null) {
|
||||
User user = userMapper.selectById(teacher.getUserId());
|
||||
if (user != null) {
|
||||
if (dto.getNickname() != null) {
|
||||
user.setNickname(dto.getNickname());
|
||||
}
|
||||
if (dto.getEmail() != null) {
|
||||
user.setEmail(dto.getEmail());
|
||||
}
|
||||
if (dto.getAvatar() != null) {
|
||||
user.setAvatar(dto.getAvatar());
|
||||
}
|
||||
userMapper.updateById(user);
|
||||
}
|
||||
}
|
||||
|
||||
this.updateById(teacher);
|
||||
log.info("更新教师成功,ID={}", id);
|
||||
|
||||
User user = userMapper.selectById(teacher.getUserId());
|
||||
Department department = departmentService.getById(teacher.getDepartmentId());
|
||||
return convertToVO(teacher, user, department);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id, Long tenantId) {
|
||||
Teacher teacher = this.getById(id);
|
||||
if (teacher == null || !teacher.getTenantId().equals(tenantId)) {
|
||||
throw new BusinessException("教师不存在");
|
||||
}
|
||||
|
||||
// 逻辑删除教师
|
||||
teacher.setDeleted(1);
|
||||
this.updateById(teacher);
|
||||
|
||||
// 同时禁用关联的用户账号
|
||||
User user = userMapper.selectById(teacher.getUserId());
|
||||
if (user != null) {
|
||||
user.setStatus("disabled"); // 禁用状态
|
||||
userMapper.updateById(user);
|
||||
}
|
||||
|
||||
log.info("删除教师成功,ID={}, 用户 ID={}", id, teacher.getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 VO
|
||||
*/
|
||||
private TeacherVO convertToVO(Teacher teacher, User user, Department department) {
|
||||
TeacherVO vo = new TeacherVO();
|
||||
vo.setId(teacher.getId());
|
||||
vo.setUserId(teacher.getUserId());
|
||||
vo.setTenantId(teacher.getTenantId());
|
||||
vo.setDepartmentId(teacher.getDepartmentId());
|
||||
vo.setEmployeeNo(teacher.getEmployeeNo());
|
||||
vo.setPhone(teacher.getPhone());
|
||||
vo.setIdCard(teacher.getIdCard());
|
||||
vo.setGender(teacher.getGender());
|
||||
vo.setBirthDate(teacher.getBirthDate());
|
||||
vo.setHireDate(teacher.getHireDate());
|
||||
vo.setSubject(teacher.getSubject());
|
||||
vo.setTitle(teacher.getTitle());
|
||||
vo.setDescription(teacher.getDescription());
|
||||
vo.setValidState(teacher.getValidState());
|
||||
vo.setCreateTime(teacher.getCreateTime());
|
||||
vo.setUpdateTime(teacher.getUpdateTime());
|
||||
|
||||
// 用户信息
|
||||
if (user != null) {
|
||||
TeacherVO.UserInfo userInfo = new TeacherVO.UserInfo();
|
||||
userInfo.setUsername(user.getUsername());
|
||||
userInfo.setNickname(user.getNickname());
|
||||
userInfo.setEmail(user.getEmail());
|
||||
userInfo.setAvatar(user.getAvatar());
|
||||
vo.setUserInfo(userInfo);
|
||||
}
|
||||
|
||||
// 部门信息
|
||||
if (department != null) {
|
||||
TeacherVO.DepartmentInfo deptInfo = new TeacherVO.DepartmentInfo();
|
||||
deptInfo.setId(department.getId());
|
||||
deptInfo.setName(department.getName());
|
||||
deptInfo.setCode(department.getCode());
|
||||
vo.setDepartment(deptInfo);
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@ -288,6 +288,49 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<UserListVO> pageCandidateUsersByRoleCode(
|
||||
Long tenantId, String roleCode, String keyword, int page, int pageSize) {
|
||||
if (!"teacher".equals(roleCode) && !"student".equals(roleCode)) {
|
||||
throw new BusinessException("不支持的角色类型,仅允许 teacher 或 student");
|
||||
}
|
||||
log.info("报名场景分页查询用户,租户:{},角色:{}", tenantId, roleCode);
|
||||
|
||||
Page<User> userPage = new Page<>(page, pageSize);
|
||||
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(User::getTenantId, tenantId);
|
||||
wrapper.eq(User::getValidState, 1);
|
||||
wrapper.eq(User::getStatus, "enabled");
|
||||
wrapper.apply(
|
||||
"EXISTS (SELECT 1 FROM t_auth_user_role ur "
|
||||
+ "INNER JOIN t_auth_role r ON ur.role_id = r.id "
|
||||
+ "WHERE ur.user_id = t_sys_user.id AND r.tenant_id = {0} AND r.code = {1} "
|
||||
+ "AND r.deleted = 0 AND r.valid_state = 1)",
|
||||
tenantId, roleCode);
|
||||
|
||||
if (StringUtils.hasText(keyword)) {
|
||||
String kw = keyword.trim();
|
||||
wrapper.and(w -> w
|
||||
.like(User::getUsername, kw)
|
||||
.or()
|
||||
.like(User::getNickname, kw)
|
||||
.or()
|
||||
.like(User::getEmail, kw)
|
||||
.or()
|
||||
.like(User::getPhone, kw));
|
||||
}
|
||||
|
||||
wrapper.orderByDesc(User::getCreateTime);
|
||||
Page<User> resultPage = userMapper.selectPage(userPage, wrapper);
|
||||
|
||||
List<UserListVO> voList = resultPage.getRecords().stream()
|
||||
.map(this::convertToListVO)
|
||||
.collect(Collectors.toList());
|
||||
Page<UserListVO> voPage = new Page<>(resultPage.getCurrent(), resultPage.getSize(), resultPage.getTotal());
|
||||
voPage.setRecords(voList);
|
||||
return voPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为详情 VO
|
||||
*/
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
package com.lesingle.creation.vo.department;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 部门响应 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "部门响应")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class DepartmentVO {
|
||||
|
||||
@Schema(description = "部门 ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "租户 ID")
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "部门名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "部门编码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "父部门 ID")
|
||||
private Long parentId;
|
||||
|
||||
@Schema(description = "部门描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "有效状态")
|
||||
private Integer validState;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "子部门列表")
|
||||
private List<DepartmentVO> children;
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
package com.lesingle.creation.vo.grade;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 年级响应 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "年级响应")
|
||||
public class GradeVO {
|
||||
|
||||
@Schema(description = "年级 ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "租户 ID")
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "年级名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "年级编码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "年级级别")
|
||||
private Integer level;
|
||||
|
||||
@Schema(description = "年级描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "有效状态")
|
||||
private Integer validState;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
package com.lesingle.creation.vo.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 作业详情 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "作业详情响应")
|
||||
public class HomeworkDetailVO {
|
||||
|
||||
@Schema(description = "作业 ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "作业名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "作业内容(富文本)")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "作业状态:unpublished/published")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "发布时间")
|
||||
private LocalDateTime publishTime;
|
||||
|
||||
@Schema(description = "提交开始时间")
|
||||
private LocalDateTime submitStartTime;
|
||||
|
||||
@Schema(description = "提交结束时间")
|
||||
private LocalDateTime submitEndTime;
|
||||
|
||||
@Schema(description = "附件列表(JSON 数组)")
|
||||
private String attachments;
|
||||
|
||||
@Schema(description = "公开范围(班级 ID 数组 JSON)")
|
||||
private String publishScope;
|
||||
|
||||
@Schema(description = "评审规则 ID")
|
||||
private Long reviewRuleId;
|
||||
|
||||
@Schema(description = "提交人数")
|
||||
private Integer submissionCount;
|
||||
|
||||
@Schema(description = "已批改人数")
|
||||
private Integer reviewedCount;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
package com.lesingle.creation.vo.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 作业列表 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "作业列表响应")
|
||||
public class HomeworkListVO {
|
||||
|
||||
@Schema(description = "作业 ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "作业名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "作业状态:unpublished/published")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "发布时间")
|
||||
private LocalDateTime publishTime;
|
||||
|
||||
@Schema(description = "提交开始时间")
|
||||
private LocalDateTime submitStartTime;
|
||||
|
||||
@Schema(description = "提交结束时间")
|
||||
private LocalDateTime submitEndTime;
|
||||
|
||||
@Schema(description = "提交人数")
|
||||
private Integer submissionCount;
|
||||
|
||||
@Schema(description = "已批改人数")
|
||||
private Integer reviewedCount;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
package com.lesingle.creation.vo.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 作业评审维度 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "作业评审维度响应")
|
||||
public class ReviewDimensionVO {
|
||||
|
||||
@Schema(description = "维度 ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "维度名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "满分")
|
||||
private BigDecimal maxScore;
|
||||
|
||||
@Schema(description = "维度描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "排序顺序")
|
||||
private Integer sortOrder;
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
package com.lesingle.creation.vo.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 作业评审规则 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "作业评审规则响应")
|
||||
public class ReviewRuleVO {
|
||||
|
||||
@Schema(description = "规则 ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "规则名称")
|
||||
private String ruleName;
|
||||
|
||||
@Schema(description = "规则类型:default/custom")
|
||||
private String ruleType;
|
||||
|
||||
@Schema(description = "总分")
|
||||
private BigDecimal totalScore;
|
||||
|
||||
@Schema(description = "规则描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "评审维度列表")
|
||||
private List<ReviewDimensionVO> dimensions;
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
package com.lesingle.creation.vo.homework;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 作业提交 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "作业提交响应")
|
||||
public class SubmissionVO {
|
||||
|
||||
@Schema(description = "提交 ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "作业 ID")
|
||||
private Long homeworkId;
|
||||
|
||||
@Schema(description = "学生 ID")
|
||||
private Long studentId;
|
||||
|
||||
@Schema(description = "学生姓名")
|
||||
private String studentName;
|
||||
|
||||
@Schema(description = "提交内容")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "提交附件列表(JSON 数组)")
|
||||
private String attachments;
|
||||
|
||||
@Schema(description = "提交状态:submitted/reviewing/returned")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "提交时间")
|
||||
private LocalDateTime submitTime;
|
||||
|
||||
@Schema(description = "批改时间")
|
||||
private LocalDateTime reviewTime;
|
||||
|
||||
@Schema(description = "批改人 ID")
|
||||
private Long reviewerId;
|
||||
|
||||
@Schema(description = "批改评语")
|
||||
private String reviewComment;
|
||||
|
||||
@Schema(description = "批改分数")
|
||||
private BigDecimal reviewScore;
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
package com.lesingle.creation.vo.school;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 班级树节点 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "班级树节点")
|
||||
public class ClassTreeNodeVO {
|
||||
|
||||
@Schema(description = "班级 ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "班级名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "班级编码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "年级 ID")
|
||||
private Long gradeId;
|
||||
|
||||
@Schema(description = "年级名称")
|
||||
private String gradeName;
|
||||
|
||||
@Schema(description = "子节点(小班、中班、大班等)")
|
||||
private List<ClassTreeNodeVO> children;
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
package com.lesingle.creation.vo.school;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 学校信息 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "学校信息响应")
|
||||
public class SchoolVO {
|
||||
|
||||
@Schema(description = "学校 ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "租户 ID")
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "学校地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "联系电话")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "校长姓名")
|
||||
private String principal;
|
||||
|
||||
@Schema(description = "建校时间")
|
||||
private LocalDate established;
|
||||
|
||||
@Schema(description = "学校描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "学校 Logo URL")
|
||||
private String logo;
|
||||
|
||||
@Schema(description = "学校网站")
|
||||
private String website;
|
||||
|
||||
@Schema(description = "有效状态:1-有效,2-失效")
|
||||
private Integer validState;
|
||||
|
||||
@Schema(description = "创建人")
|
||||
private String createBy;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新人")
|
||||
private String updateBy;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
package com.lesingle.creation.vo.schoolclass;
|
||||
|
||||
import com.lesingle.creation.vo.grade.GradeVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 班级响应 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "班级响应")
|
||||
public class ClassVO {
|
||||
|
||||
@Schema(description = "班级 ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "租户 ID")
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "年级 ID")
|
||||
private Long gradeId;
|
||||
|
||||
@Schema(description = "班级名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "班级编码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "班级类型:1-行政班级,2-兴趣班")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "班级容量")
|
||||
private Integer capacity;
|
||||
|
||||
@Schema(description = "班级描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "有效状态")
|
||||
private Integer validState;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "关联年级信息")
|
||||
private GradeVO grade;
|
||||
}
|
||||
@ -1,102 +0,0 @@
|
||||
package com.lesingle.creation.vo.student;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 学生响应 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "学生响应")
|
||||
public class StudentVO {
|
||||
|
||||
@Schema(description = "学生 ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户 ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "租户 ID")
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "行政班级 ID")
|
||||
private Long classId;
|
||||
|
||||
@Schema(description = "学号")
|
||||
private String studentNo;
|
||||
|
||||
@Schema(description = "联系电话")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "身份证号")
|
||||
private String idCard;
|
||||
|
||||
@Schema(description = "性别:1-男,2-女")
|
||||
private Integer gender;
|
||||
|
||||
@Schema(description = "出生日期")
|
||||
private LocalDate birthDate;
|
||||
|
||||
@Schema(description = "入学日期")
|
||||
private LocalDate enrollmentDate;
|
||||
|
||||
@Schema(description = "家长姓名")
|
||||
private String parentName;
|
||||
|
||||
@Schema(description = "家长电话")
|
||||
private String parentPhone;
|
||||
|
||||
@Schema(description = "家庭地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "学生描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "有效状态")
|
||||
private Integer validState;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "用户信息")
|
||||
private UserInfo userInfo;
|
||||
|
||||
@Schema(description = "班级信息")
|
||||
private ClassInfo classInfo;
|
||||
|
||||
/**
|
||||
* 用户信息嵌套 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "用户信息")
|
||||
public static class UserInfo {
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
@Schema(description = "昵称")
|
||||
private String nickname;
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
@Schema(description = "头像 URL")
|
||||
private String avatar;
|
||||
}
|
||||
|
||||
/**
|
||||
* 班级信息嵌套 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "班级信息")
|
||||
public static class ClassInfo {
|
||||
@Schema(description = "班级 ID")
|
||||
private Long id;
|
||||
@Schema(description = "班级名称")
|
||||
private String name;
|
||||
@Schema(description = "班级编码")
|
||||
private String code;
|
||||
}
|
||||
}
|
||||
@ -1,99 +0,0 @@
|
||||
package com.lesingle.creation.vo.teacher;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 教师响应 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "教师响应")
|
||||
public class TeacherVO {
|
||||
|
||||
@Schema(description = "教师 ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户 ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "租户 ID")
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "部门 ID")
|
||||
private Long departmentId;
|
||||
|
||||
@Schema(description = "工号")
|
||||
private String employeeNo;
|
||||
|
||||
@Schema(description = "联系电话")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "身份证号")
|
||||
private String idCard;
|
||||
|
||||
@Schema(description = "性别:1-男,2-女")
|
||||
private Integer gender;
|
||||
|
||||
@Schema(description = "出生日期")
|
||||
private LocalDate birthDate;
|
||||
|
||||
@Schema(description = "入职日期")
|
||||
private LocalDate hireDate;
|
||||
|
||||
@Schema(description = "任教科目")
|
||||
private String subject;
|
||||
|
||||
@Schema(description = "职称")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "教师描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "有效状态")
|
||||
private Integer validState;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "用户信息")
|
||||
private UserInfo userInfo;
|
||||
|
||||
@Schema(description = "部门信息")
|
||||
private DepartmentInfo department;
|
||||
|
||||
/**
|
||||
* 用户信息嵌套 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "用户信息")
|
||||
public static class UserInfo {
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
@Schema(description = "昵称")
|
||||
private String nickname;
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
@Schema(description = "头像 URL")
|
||||
private String avatar;
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门信息嵌套 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "部门信息")
|
||||
public static class DepartmentInfo {
|
||||
@Schema(description = "部门 ID")
|
||||
private Long id;
|
||||
@Schema(description = "部门名称")
|
||||
private String name;
|
||||
@Schema(description = "部门编码")
|
||||
private String code;
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@ spring:
|
||||
datasource:
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://192.168.1.250:3306/lesingle-creation-test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
url: jdbc:mysql://192.168.1.250:3306/lesingle-creation-test-2?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: lesingle-creation-test
|
||||
password: 8ErFZiPBGbyrTHsy
|
||||
druid:
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
-- =========================================================
|
||||
-- 下线:学校组织、作业模块、AI 3D 任务表及相关 RBAC
|
||||
-- 说明:不修改历史脚本;幂等尽量通过 DELETE/UPDATE 可重复执行
|
||||
-- =========================================================
|
||||
|
||||
-- 1) 角色-菜单:学校管理树(id=12)、作业中心树(id=31)
|
||||
DELETE rm FROM t_auth_role_menu rm
|
||||
INNER JOIN t_auth_menu m ON rm.menu_id = m.id
|
||||
WHERE m.id IN (12, 31) OR m.parent_id IN (12, 31);
|
||||
|
||||
-- 2) 租户-菜单:同上
|
||||
DELETE tm FROM t_auth_tenant_menu tm
|
||||
INNER JOIN t_auth_menu m ON tm.menu_id = m.id
|
||||
WHERE m.id IN (12, 31) OR m.parent_id IN (12, 31);
|
||||
|
||||
-- 3) 角色-权限:移除待下线权限码(全租户 permission 行)
|
||||
DELETE rp FROM t_auth_role_permission rp
|
||||
INNER JOIN t_auth_permission p ON rp.permission_id = p.id
|
||||
WHERE p.code IN (
|
||||
'school:create', 'school:read', 'school:update', 'school:delete',
|
||||
'department:create', 'department:read', 'department:update', 'department:delete',
|
||||
'grade:create', 'grade:read', 'grade:update', 'grade:delete',
|
||||
'class:create', 'class:read', 'class:update', 'class:delete',
|
||||
'teacher:create', 'teacher:read', 'teacher:update', 'teacher:delete',
|
||||
'student:create', 'student:read', 'student:update', 'student:delete',
|
||||
'homework:create', 'homework:read', 'homework:update', 'homework:publish', 'homework:delete',
|
||||
'homework:review',
|
||||
'homework:review-rule:create', 'homework:review-rule:update', 'homework:review-rule:delete',
|
||||
'ai-3d:delete'
|
||||
);
|
||||
|
||||
-- 4) 权限行标记失效
|
||||
UPDATE t_auth_permission
|
||||
SET valid_state = 2, deleted = 1, update_time = CURRENT_TIMESTAMP
|
||||
WHERE code IN (
|
||||
'school:create', 'school:read', 'school:update', 'school:delete',
|
||||
'department:create', 'department:read', 'department:update', 'department:delete',
|
||||
'grade:create', 'grade:read', 'grade:update', 'grade:delete',
|
||||
'class:create', 'class:read', 'class:update', 'class:delete',
|
||||
'teacher:create', 'teacher:read', 'teacher:update', 'teacher:delete',
|
||||
'student:create', 'student:read', 'student:update', 'student:delete',
|
||||
'homework:create', 'homework:read', 'homework:update', 'homework:publish', 'homework:delete',
|
||||
'homework:review',
|
||||
'homework:review-rule:create', 'homework:review-rule:update', 'homework:review-rule:delete',
|
||||
'ai-3d:delete'
|
||||
);
|
||||
|
||||
-- 5) 菜单标记删除
|
||||
UPDATE t_auth_menu
|
||||
SET valid_state = 2, deleted = 1, update_time = CURRENT_TIMESTAMP
|
||||
WHERE id IN (12, 31) OR parent_id IN (12, 31);
|
||||
|
||||
-- 6) 业务表(先子后父)
|
||||
DROP TABLE IF EXISTS t_biz_homework_score;
|
||||
DROP TABLE IF EXISTS t_biz_homework_review_dimension;
|
||||
DROP TABLE IF EXISTS t_biz_homework_submission;
|
||||
DROP TABLE IF EXISTS t_biz_homework;
|
||||
DROP TABLE IF EXISTS t_biz_homework_review_rule;
|
||||
|
||||
DROP TABLE IF EXISTS t_biz_student_interest_class;
|
||||
DROP TABLE IF EXISTS t_biz_student;
|
||||
DROP TABLE IF EXISTS t_biz_teacher;
|
||||
DROP TABLE IF EXISTS t_biz_class;
|
||||
DROP TABLE IF EXISTS t_biz_grade;
|
||||
DROP TABLE IF EXISTS t_biz_department;
|
||||
DROP TABLE IF EXISTS t_biz_school;
|
||||
|
||||
DROP TABLE IF EXISTS t_biz_ai3d_task;
|
||||
@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.lesingle.creation.mapper.HomeworkReviewDimensionMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="com.lesingle.creation.entity.HomeworkReviewDimension">
|
||||
<id column="id" property="id"/>
|
||||
<result column="rule_id" property="ruleId"/>
|
||||
<result column="tenant_id" property="tenantId"/>
|
||||
<result column="name" property="name"/>
|
||||
<result column="max_score" property="maxScore"/>
|
||||
<result column="description" property="description"/>
|
||||
<result column="sort_order" property="sortOrder"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<result column="deleted" property="deleted"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 根据规则 ID 删除维度 -->
|
||||
<delete id="deleteByRuleId" parameterType="java.lang.Long">
|
||||
UPDATE t_biz_homework_review_dimension SET deleted = 1 WHERE rule_id = #{ruleId}
|
||||
</delete>
|
||||
|
||||
<!-- 根据规则 ID 查询维度列表 -->
|
||||
<select id="selectByRuleId" resultMap="BaseResultMap">
|
||||
SELECT * FROM t_biz_homework_review_dimension
|
||||
WHERE rule_id = #{ruleId} AND deleted = 0
|
||||
ORDER BY sort_order ASC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@ -1,91 +0,0 @@
|
||||
import request from "@/utils/request";
|
||||
import type { PaginationParams, PaginationResponse } from "@/types/api";
|
||||
|
||||
export interface Class {
|
||||
id: number;
|
||||
tenantId: number;
|
||||
gradeId: number;
|
||||
name: string;
|
||||
code: string;
|
||||
type: number; // 1-行政班级,2-兴趣班
|
||||
capacity?: number;
|
||||
description?: string;
|
||||
validState: number;
|
||||
createBy?: number;
|
||||
updateBy?: number;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
grade?: {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
level: number;
|
||||
};
|
||||
_count?: {
|
||||
students: number;
|
||||
studentInterestClasses: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateClassForm {
|
||||
gradeId: number;
|
||||
name: string;
|
||||
code: string;
|
||||
type: number;
|
||||
capacity?: number;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface UpdateClassForm {
|
||||
gradeId?: number;
|
||||
name?: string;
|
||||
code?: string;
|
||||
type?: number;
|
||||
capacity?: number;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// 获取班级列表
|
||||
export async function getClassesList(
|
||||
params: PaginationParams & { gradeId?: number; type?: number }
|
||||
): Promise<PaginationResponse<Class>> {
|
||||
const response = await request.get<any, PaginationResponse<Class>>("/api/classes", {
|
||||
params,
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
// 获取单个班级详情
|
||||
export async function getClassDetail(id: number): Promise<Class> {
|
||||
const response = await request.get<any, Class>(`/api/classes/${id}`);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 创建班级
|
||||
export async function createClass(data: CreateClassForm): Promise<Class> {
|
||||
const response = await request.post<any, Class>("/api/classes", data);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 更新班级
|
||||
export async function updateClass(
|
||||
id: number,
|
||||
data: UpdateClassForm
|
||||
): Promise<Class> {
|
||||
const response = await request.put<any, Class>(`/api/classes/${id}`, data);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 删除班级
|
||||
export async function deleteClass(id: number): Promise<void> {
|
||||
return await request.delete<any, void>(`/api/classes/${id}`);
|
||||
}
|
||||
|
||||
export const classesApi = {
|
||||
getList: getClassesList,
|
||||
getDetail: getClassDetail,
|
||||
create: createClass,
|
||||
update: updateClass,
|
||||
delete: deleteClass,
|
||||
};
|
||||
|
||||
@ -754,8 +754,38 @@ export const reviewRulesApi = {
|
||||
},
|
||||
};
|
||||
|
||||
/** 报名场景选人(后端 UserListVO,id 即用户 ID) */
|
||||
export interface RegistrationCandidateUser {
|
||||
id: number;
|
||||
tenantId?: number;
|
||||
username: string;
|
||||
nickname: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
gender?: string;
|
||||
avatar?: string;
|
||||
userType?: string;
|
||||
status?: string;
|
||||
createTime?: string;
|
||||
roleNames?: string[];
|
||||
}
|
||||
|
||||
// 报名管理
|
||||
export const registrationsApi = {
|
||||
/** 按角色(teacher/student)分页查询本租户可选用户 */
|
||||
getCandidateUsers: async (params: {
|
||||
roleCode: "teacher" | "student";
|
||||
keyword?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}): Promise<PaginationResponse<RegistrationCandidateUser>> => {
|
||||
const response = await request.get<
|
||||
any,
|
||||
PaginationResponse<RegistrationCandidateUser>
|
||||
>("/api/contests/registrations/candidate-users", { params });
|
||||
return response;
|
||||
},
|
||||
|
||||
// 报名统计
|
||||
getStats: async (contestId?: number): Promise<RegistrationStats> => {
|
||||
const params: any = {};
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
import request from "@/utils/request";
|
||||
import type { PaginationParams, PaginationResponse } from "@/types/api";
|
||||
|
||||
export interface Department {
|
||||
id: number;
|
||||
tenantId: number;
|
||||
name: string;
|
||||
code: string;
|
||||
parentId?: number | null;
|
||||
description?: string;
|
||||
sort: number;
|
||||
validState: number;
|
||||
createBy?: number;
|
||||
updateBy?: number;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
parent?: Department;
|
||||
children?: Department[];
|
||||
_count?: {
|
||||
teachers: number;
|
||||
children: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateDepartmentForm {
|
||||
name: string;
|
||||
code: string;
|
||||
parentId?: number;
|
||||
description?: string;
|
||||
sort?: number;
|
||||
}
|
||||
|
||||
export interface UpdateDepartmentForm {
|
||||
name?: string;
|
||||
code?: string;
|
||||
parentId?: number | null;
|
||||
description?: string;
|
||||
sort?: number;
|
||||
}
|
||||
|
||||
// 获取部门列表
|
||||
export async function getDepartmentsList(
|
||||
params: PaginationParams & { parentId?: number }
|
||||
): Promise<PaginationResponse<Department>> {
|
||||
const response = await request.get<any, PaginationResponse<Department>>("/api/departments", {
|
||||
params,
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
// 获取部门树
|
||||
export async function getDepartmentsTree(): Promise<Department[]> {
|
||||
const response = await request.get<any, Department[]>("/api/departments/tree");
|
||||
return response;
|
||||
}
|
||||
|
||||
// 获取单个部门详情
|
||||
export async function getDepartmentDetail(id: number): Promise<Department> {
|
||||
const response = await request.get<any, Department>(`/api/departments/${id}`);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 创建部门
|
||||
export async function createDepartment(data: CreateDepartmentForm): Promise<Department> {
|
||||
const response = await request.post<any, Department>("/api/departments", data);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 更新部门
|
||||
export async function updateDepartment(
|
||||
id: number,
|
||||
data: UpdateDepartmentForm
|
||||
): Promise<Department> {
|
||||
const response = await request.put<any, Department>(`/api/departments/${id}`, data);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 删除部门
|
||||
export async function deleteDepartment(id: number): Promise<void> {
|
||||
return await request.delete<any, void>(`/api/departments/${id}`);
|
||||
}
|
||||
|
||||
export const departmentsApi = {
|
||||
getList: getDepartmentsList,
|
||||
getTree: getDepartmentsTree,
|
||||
getDetail: getDepartmentDetail,
|
||||
create: createDepartment,
|
||||
update: updateDepartment,
|
||||
delete: deleteDepartment,
|
||||
};
|
||||
|
||||
@ -1,78 +0,0 @@
|
||||
import request from "@/utils/request";
|
||||
import type { PaginationParams, PaginationResponse } from "@/types/api";
|
||||
|
||||
export interface Grade {
|
||||
id: number;
|
||||
tenantId: number;
|
||||
name: string;
|
||||
code: string;
|
||||
level: number;
|
||||
description?: string;
|
||||
validState: number;
|
||||
createBy?: number;
|
||||
updateBy?: number;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
_count?: {
|
||||
classes: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateGradeForm {
|
||||
name: string;
|
||||
code: string;
|
||||
level: number;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface UpdateGradeForm {
|
||||
name?: string;
|
||||
code?: string;
|
||||
level?: number;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// 获取年级列表
|
||||
export async function getGradesList(
|
||||
params: PaginationParams
|
||||
): Promise<PaginationResponse<Grade>> {
|
||||
const response = await request.get<any, PaginationResponse<Grade>>("/api/grades", {
|
||||
params,
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
// 获取单个年级详情
|
||||
export async function getGradeDetail(id: number): Promise<Grade> {
|
||||
const response = await request.get<any, Grade>(`/api/grades/${id}`);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 创建年级
|
||||
export async function createGrade(data: CreateGradeForm): Promise<Grade> {
|
||||
const response = await request.post<any, Grade>("/api/grades", data);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 更新年级
|
||||
export async function updateGrade(
|
||||
id: number,
|
||||
data: UpdateGradeForm
|
||||
): Promise<Grade> {
|
||||
const response = await request.put<any, Grade>(`/api/grades/${id}`, data);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 删除年级
|
||||
export async function deleteGrade(id: number): Promise<void> {
|
||||
return await request.delete<any, void>(`/api/grades/${id}`);
|
||||
}
|
||||
|
||||
export const gradesApi = {
|
||||
getList: getGradesList,
|
||||
getDetail: getGradeDetail,
|
||||
create: createGrade,
|
||||
update: updateGrade,
|
||||
delete: deleteGrade,
|
||||
};
|
||||
|
||||
@ -1,415 +0,0 @@
|
||||
import request from "@/utils/request";
|
||||
import type { PaginationParams, PaginationResponse } from "@/types/api";
|
||||
|
||||
// ==================== 作业相关类型 ====================
|
||||
export interface Homework {
|
||||
id: number;
|
||||
tenantId: number;
|
||||
name: string;
|
||||
content?: string;
|
||||
status: "unpublished" | "published";
|
||||
publishTime?: string;
|
||||
submitStartTime: string;
|
||||
submitEndTime: string;
|
||||
attachments?: HomeworkAttachment[];
|
||||
publishScope?: number[];
|
||||
publishScopeNames?: string[];
|
||||
reviewRuleId?: number;
|
||||
reviewRule?: HomeworkReviewRule;
|
||||
createBy?: number;
|
||||
updateBy?: number;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
validState?: number;
|
||||
_count?: {
|
||||
submissions: number;
|
||||
};
|
||||
// 学生端:我的提交记录
|
||||
submission?: {
|
||||
id: number;
|
||||
workName: string;
|
||||
submitTime: string;
|
||||
totalScore?: number;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface HomeworkAttachment {
|
||||
fileName: string;
|
||||
fileUrl: string;
|
||||
size?: string;
|
||||
}
|
||||
|
||||
export interface CreateHomeworkForm {
|
||||
name: string;
|
||||
content?: string;
|
||||
submitStartTime: string;
|
||||
submitEndTime: string;
|
||||
attachments?: HomeworkAttachment[];
|
||||
publishScope?: number[];
|
||||
reviewRuleId?: number;
|
||||
}
|
||||
|
||||
export interface UpdateHomeworkForm extends Partial<CreateHomeworkForm> {}
|
||||
|
||||
export interface QueryHomeworkParams extends PaginationParams {
|
||||
name?: string;
|
||||
status?: string;
|
||||
submitStartTime?: string;
|
||||
submitEndTime?: string;
|
||||
}
|
||||
|
||||
// ==================== 提交记录相关类型 ====================
|
||||
export interface HomeworkSubmission {
|
||||
id: number;
|
||||
tenantId: number;
|
||||
homeworkId: number;
|
||||
studentId: number;
|
||||
workNo?: string;
|
||||
workName: string;
|
||||
workDescription?: string;
|
||||
files?: any[];
|
||||
attachments?: any[];
|
||||
submitTime: string;
|
||||
status: "pending" | "reviewed" | "rejected";
|
||||
totalScore?: number;
|
||||
createBy?: number;
|
||||
updateBy?: number;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
validState?: number;
|
||||
homework?: {
|
||||
id: number;
|
||||
name: string;
|
||||
reviewRuleId?: number;
|
||||
reviewRule?: HomeworkReviewRule;
|
||||
};
|
||||
student?: {
|
||||
id: number;
|
||||
username: string;
|
||||
nickname: string;
|
||||
student?: {
|
||||
studentNo?: string;
|
||||
class?: {
|
||||
id: number;
|
||||
name: string;
|
||||
grade?: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
scores?: HomeworkScore[];
|
||||
_count?: {
|
||||
scores: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface QuerySubmissionParams extends PaginationParams {
|
||||
homeworkId?: number;
|
||||
workNo?: string;
|
||||
workName?: string;
|
||||
studentAccount?: string;
|
||||
studentName?: string;
|
||||
status?: string;
|
||||
classIds?: number[];
|
||||
gradeId?: number;
|
||||
}
|
||||
|
||||
// ==================== 评审规则相关类型 ====================
|
||||
export interface HomeworkReviewRule {
|
||||
id: number;
|
||||
tenantId: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
criteria: ReviewCriterion[];
|
||||
createBy?: number;
|
||||
updateBy?: number;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
validState?: number;
|
||||
homeworks?: Array<{
|
||||
id: number;
|
||||
name: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ReviewCriterion {
|
||||
name: string;
|
||||
maxScore: number;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface CreateReviewRuleForm {
|
||||
name: string;
|
||||
description?: string;
|
||||
criteria: ReviewCriterion[];
|
||||
}
|
||||
|
||||
// ==================== 评分相关类型 ====================
|
||||
export interface HomeworkScore {
|
||||
id: number;
|
||||
tenantId: number;
|
||||
submissionId: number;
|
||||
reviewerId: number;
|
||||
dimensionScores: DimensionScore[];
|
||||
totalScore: number;
|
||||
comments?: string;
|
||||
scoreTime: string;
|
||||
createBy?: number;
|
||||
updateBy?: number;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
validState?: number;
|
||||
reviewer?: {
|
||||
id: number;
|
||||
nickname: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DimensionScore {
|
||||
name: string;
|
||||
score: number;
|
||||
maxScore: number;
|
||||
}
|
||||
|
||||
export interface CreateScoreForm {
|
||||
submissionId: number;
|
||||
dimensionScores: DimensionScore[];
|
||||
comments?: string;
|
||||
}
|
||||
|
||||
// ==================== 班级树相关类型 ====================
|
||||
export interface ClassTreeNode {
|
||||
id: string | number;
|
||||
name: string;
|
||||
type: "grade" | "class";
|
||||
gradeId?: number;
|
||||
classId?: number;
|
||||
children?: ClassTreeNode[];
|
||||
}
|
||||
|
||||
// ==================== API 函数 ====================
|
||||
|
||||
// 作业管理
|
||||
export const homeworksApi = {
|
||||
// 获取作业列表(教师端)
|
||||
getList: async (
|
||||
params: QueryHomeworkParams
|
||||
): Promise<PaginationResponse<Homework>> => {
|
||||
const response = await request.get<any, PaginationResponse<Homework>>(
|
||||
"/api/homeworks/page",
|
||||
{ params }
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 获取我的作业列表(学生端)
|
||||
getMyList: async (
|
||||
params: QueryHomeworkParams
|
||||
): Promise<PaginationResponse<Homework>> => {
|
||||
const response = await request.get<any, PaginationResponse<Homework>>(
|
||||
"/api/homeworks/my",
|
||||
{ params }
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 获取作业详情
|
||||
getDetail: async (id: number): Promise<Homework> => {
|
||||
const response = await request.get<any, Homework>(
|
||||
`/api/homeworks/${id}`
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 创建作业
|
||||
create: async (data: CreateHomeworkForm): Promise<Homework> => {
|
||||
const response = await request.post<any, Homework>(
|
||||
"/api/homeworks",
|
||||
data
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 更新作业
|
||||
update: async (id: number, data: UpdateHomeworkForm): Promise<Homework> => {
|
||||
const response = await request.put<any, Homework>(
|
||||
`/api/homeworks/${id}`,
|
||||
data
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 发布作业
|
||||
publish: async (id: number, publishScope: number[]): Promise<Homework> => {
|
||||
const response = await request.post<any, Homework>(
|
||||
`/api/homeworks/${id}/publish`,
|
||||
{ publishScope }
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 取消发布作业
|
||||
unpublish: async (id: number): Promise<Homework> => {
|
||||
const response = await request.post<any, Homework>(
|
||||
`/api/homeworks/${id}/unpublish`
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 删除作业
|
||||
delete: async (id: number): Promise<void> => {
|
||||
return await request.delete<any, void>(`/api/homeworks/${id}`);
|
||||
},
|
||||
};
|
||||
|
||||
// 学生提交作业表单
|
||||
export interface SubmitHomeworkForm {
|
||||
homeworkId: number;
|
||||
workName: string;
|
||||
workDescription?: string;
|
||||
files?: HomeworkAttachment[];
|
||||
}
|
||||
|
||||
// 提交记录管理
|
||||
export const submissionsApi = {
|
||||
// 获取提交记录列表
|
||||
getList: async (
|
||||
params: QuerySubmissionParams
|
||||
): Promise<PaginationResponse<HomeworkSubmission>> => {
|
||||
const response = await request.get<
|
||||
any,
|
||||
PaginationResponse<HomeworkSubmission>
|
||||
>("/api/homeworks/submissions", { params });
|
||||
return response;
|
||||
},
|
||||
|
||||
// 获取提交记录详情
|
||||
getDetail: async (id: number): Promise<HomeworkSubmission> => {
|
||||
const response = await request.get<any, HomeworkSubmission>(
|
||||
`/api/homeworks/submissions/${id}`
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 获取班级树结构
|
||||
getClassTree: async (): Promise<ClassTreeNode[]> => {
|
||||
const response = await request.get<any, ClassTreeNode[]>(
|
||||
"/api/homeworks/submissions/class-tree"
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 获取当前用户对某作业的提交记录
|
||||
getMySubmission: async (homeworkId: number): Promise<HomeworkSubmission> => {
|
||||
const response = await request.get<any, HomeworkSubmission>(
|
||||
`/api/homeworks/submissions/my/${homeworkId}`
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 提交作业
|
||||
submit: async (data: SubmitHomeworkForm): Promise<HomeworkSubmission> => {
|
||||
const response = await request.post<any, HomeworkSubmission>(
|
||||
"/api/homeworks/submissions",
|
||||
data
|
||||
);
|
||||
return response;
|
||||
},
|
||||
};
|
||||
|
||||
// 评审规则管理
|
||||
export const reviewRulesApi = {
|
||||
// 获取评审规则列表
|
||||
getList: async (params?: {
|
||||
name?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}): Promise<{
|
||||
list: HomeworkReviewRule[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}> => {
|
||||
const response = await request.get<
|
||||
any,
|
||||
{
|
||||
list: HomeworkReviewRule[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
>("/api/homeworks/review-rules", { params });
|
||||
return response;
|
||||
},
|
||||
|
||||
// 获取所有可用的评审规则(用于选择)
|
||||
getForSelect: async (): Promise<HomeworkReviewRule[]> => {
|
||||
const response = await request.get<any, HomeworkReviewRule[]>(
|
||||
"/api/homeworks/review-rules/select"
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 获取评审规则详情
|
||||
getDetail: async (id: number): Promise<HomeworkReviewRule> => {
|
||||
const response = await request.get<any, HomeworkReviewRule>(
|
||||
`/api/homeworks/review-rules/${id}`
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 创建评审规则
|
||||
create: async (data: CreateReviewRuleForm): Promise<HomeworkReviewRule> => {
|
||||
const response = await request.post<any, HomeworkReviewRule>(
|
||||
"/api/homeworks/review-rules",
|
||||
data
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 更新评审规则
|
||||
update: async (
|
||||
id: number,
|
||||
data: Partial<CreateReviewRuleForm>
|
||||
): Promise<HomeworkReviewRule> => {
|
||||
const response = await request.put<any, HomeworkReviewRule>(
|
||||
`/api/homeworks/review-rules/${id}`,
|
||||
data
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 删除评审规则
|
||||
delete: async (id: number): Promise<void> => {
|
||||
await request.delete(`/api/homeworks/review-rules/${id}`);
|
||||
},
|
||||
};
|
||||
|
||||
// 评分管理
|
||||
export const scoresApi = {
|
||||
// 提交评分
|
||||
create: async (data: CreateScoreForm): Promise<HomeworkScore> => {
|
||||
const response = await request.post<any, HomeworkScore>(
|
||||
"/api/homeworks/scores",
|
||||
data
|
||||
);
|
||||
return response;
|
||||
},
|
||||
|
||||
// 标记作品违规
|
||||
markViolation: async (
|
||||
submissionId: number,
|
||||
reason?: string
|
||||
): Promise<void> => {
|
||||
await request.post(`/api/homeworks/scores/${submissionId}/violation`, {
|
||||
reason,
|
||||
});
|
||||
},
|
||||
|
||||
// 重置评分
|
||||
resetScore: async (submissionId: number): Promise<void> => {
|
||||
await request.post(`/api/homeworks/scores/${submissionId}/reset`);
|
||||
},
|
||||
};
|
||||
@ -1,73 +0,0 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export interface School {
|
||||
id: number;
|
||||
tenantId: number;
|
||||
address?: string;
|
||||
phone?: string;
|
||||
principal?: string;
|
||||
established?: string;
|
||||
description?: string;
|
||||
logo?: string;
|
||||
website?: string;
|
||||
createBy?: number;
|
||||
updateBy?: number;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
tenant?: {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateSchoolForm {
|
||||
address?: string;
|
||||
phone?: string;
|
||||
principal?: string;
|
||||
established?: string;
|
||||
description?: string;
|
||||
logo?: string;
|
||||
website?: string;
|
||||
}
|
||||
|
||||
export interface UpdateSchoolForm {
|
||||
address?: string;
|
||||
phone?: string;
|
||||
principal?: string;
|
||||
established?: string;
|
||||
description?: string;
|
||||
logo?: string;
|
||||
website?: string;
|
||||
}
|
||||
|
||||
// 获取学校信息
|
||||
export async function getSchool(): Promise<School> {
|
||||
const response = await request.get<any, School>("/api/schools");
|
||||
return response;
|
||||
}
|
||||
|
||||
// 创建学校信息
|
||||
export async function createSchool(data: CreateSchoolForm): Promise<School> {
|
||||
const response = await request.post<any, School>("/api/schools", data);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 更新学校信息
|
||||
export async function updateSchool(data: UpdateSchoolForm): Promise<School> {
|
||||
const response = await request.put<any, School>("/api/schools", data);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 删除学校信息
|
||||
export async function deleteSchool(): Promise<void> {
|
||||
return await request.delete<any, void>("/api/schools");
|
||||
}
|
||||
|
||||
export const schoolsApi = {
|
||||
get: getSchool,
|
||||
create: createSchool,
|
||||
update: updateSchool,
|
||||
delete: deleteSchool,
|
||||
};
|
||||
|
||||
@ -1,150 +0,0 @@
|
||||
import request from "@/utils/request";
|
||||
import type { PaginationParams, PaginationResponse } from "@/types/api";
|
||||
|
||||
export interface Student {
|
||||
id: number;
|
||||
userId: number;
|
||||
tenantId: number;
|
||||
classId: number;
|
||||
studentNo?: string;
|
||||
phone?: string;
|
||||
idCard?: string;
|
||||
gender?: number; // 1-男,2-女
|
||||
birthDate?: string;
|
||||
enrollmentDate?: string;
|
||||
parentName?: string;
|
||||
parentPhone?: string;
|
||||
address?: string;
|
||||
description?: string;
|
||||
validState: number;
|
||||
createBy?: number;
|
||||
updateBy?: number;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
user?: {
|
||||
id: number;
|
||||
username: string;
|
||||
nickname: string;
|
||||
email?: string;
|
||||
avatar?: string;
|
||||
validState: number;
|
||||
};
|
||||
class?: {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
type: number;
|
||||
grade?: {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
level: number;
|
||||
};
|
||||
};
|
||||
interestClasses?: Array<{
|
||||
id: number;
|
||||
class: {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
type: number;
|
||||
grade?: {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
level: number;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface CreateStudentForm {
|
||||
username: string;
|
||||
password: string;
|
||||
nickname: string;
|
||||
email?: string;
|
||||
avatar?: string;
|
||||
classId: number;
|
||||
studentNo?: string;
|
||||
phone?: string;
|
||||
idCard?: string;
|
||||
gender?: number;
|
||||
birthDate?: string;
|
||||
enrollmentDate?: string;
|
||||
parentName?: string;
|
||||
parentPhone?: string;
|
||||
address?: string;
|
||||
description?: string;
|
||||
interestClassIds?: number[];
|
||||
}
|
||||
|
||||
export interface UpdateStudentForm {
|
||||
nickname?: string;
|
||||
email?: string;
|
||||
avatar?: string;
|
||||
classId?: number;
|
||||
studentNo?: string;
|
||||
phone?: string;
|
||||
idCard?: string;
|
||||
gender?: number;
|
||||
birthDate?: string;
|
||||
enrollmentDate?: string;
|
||||
parentName?: string;
|
||||
parentPhone?: string;
|
||||
address?: string;
|
||||
description?: string;
|
||||
validState?: number;
|
||||
interestClassIds?: number[];
|
||||
}
|
||||
|
||||
// 获取学生列表
|
||||
export async function getStudentsList(
|
||||
params: PaginationParams & { classId?: number }
|
||||
): Promise<PaginationResponse<Student>> {
|
||||
const response = await request.get<any, PaginationResponse<Student>>("/api/students", {
|
||||
params,
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
// 获取单个学生详情
|
||||
export async function getStudentDetail(id: number): Promise<Student> {
|
||||
const response = await request.get<any, Student>(`/api/students/${id}`);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 根据用户ID获取学生信息
|
||||
export async function getStudentByUserId(userId: number): Promise<Student> {
|
||||
const response = await request.get<any, Student>(`/api/students/user/${userId}`);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 创建学生
|
||||
export async function createStudent(data: CreateStudentForm): Promise<Student> {
|
||||
const response = await request.post<any, Student>("/api/students", data);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 更新学生
|
||||
export async function updateStudent(
|
||||
id: number,
|
||||
data: UpdateStudentForm
|
||||
): Promise<Student> {
|
||||
const response = await request.put<any, Student>(`/api/students/${id}`, data);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 删除学生
|
||||
export async function deleteStudent(id: number): Promise<void> {
|
||||
return await request.delete<any, void>(`/api/students/${id}`);
|
||||
}
|
||||
|
||||
export const studentsApi = {
|
||||
getList: getStudentsList,
|
||||
getDetail: getStudentDetail,
|
||||
getByUserId: getStudentByUserId,
|
||||
create: createStudent,
|
||||
update: updateStudent,
|
||||
delete: deleteStudent,
|
||||
};
|
||||
|
||||
@ -1,123 +0,0 @@
|
||||
import request from "@/utils/request";
|
||||
import type { PaginationParams, PaginationResponse } from "@/types/api";
|
||||
|
||||
export interface Teacher {
|
||||
id: number;
|
||||
userId: number;
|
||||
tenantId: number;
|
||||
departmentId: number;
|
||||
employeeNo?: string;
|
||||
phone?: string;
|
||||
idCard?: string;
|
||||
gender?: number; // 1-男,2-女
|
||||
birthDate?: string;
|
||||
hireDate?: string;
|
||||
subject?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
validState: number;
|
||||
createBy?: number;
|
||||
updateBy?: number;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
user?: {
|
||||
id: number;
|
||||
username: string;
|
||||
nickname: string;
|
||||
email?: string;
|
||||
avatar?: string;
|
||||
validState: number;
|
||||
};
|
||||
department?: {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateTeacherForm {
|
||||
username: string;
|
||||
password: string;
|
||||
nickname: string;
|
||||
email?: string;
|
||||
avatar?: string;
|
||||
departmentId: number;
|
||||
employeeNo?: string;
|
||||
phone?: string;
|
||||
idCard?: string;
|
||||
gender?: number;
|
||||
birthDate?: string;
|
||||
hireDate?: string;
|
||||
subject?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface UpdateTeacherForm {
|
||||
nickname?: string;
|
||||
email?: string;
|
||||
avatar?: string;
|
||||
departmentId?: number;
|
||||
employeeNo?: string;
|
||||
phone?: string;
|
||||
idCard?: string;
|
||||
gender?: number;
|
||||
birthDate?: string;
|
||||
hireDate?: string;
|
||||
subject?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
validState?: number;
|
||||
}
|
||||
|
||||
// 获取教师列表
|
||||
export async function getTeachersList(
|
||||
params: PaginationParams & { departmentId?: number; nickname?: string; username?: string }
|
||||
): Promise<PaginationResponse<Teacher>> {
|
||||
const response = await request.get<any, PaginationResponse<Teacher>>("/api/teachers", {
|
||||
params,
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
// 获取单个教师详情
|
||||
export async function getTeacherDetail(id: number): Promise<Teacher> {
|
||||
const response = await request.get<any, Teacher>(`/api/teachers/${id}`);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 根据用户ID获取教师信息
|
||||
export async function getTeacherByUserId(userId: number): Promise<Teacher> {
|
||||
const response = await request.get<any, Teacher>(`/api/teachers/user/${userId}`);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 创建教师
|
||||
export async function createTeacher(data: CreateTeacherForm): Promise<Teacher> {
|
||||
const response = await request.post<any, Teacher>("/api/teachers", data);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 更新教师
|
||||
export async function updateTeacher(
|
||||
id: number,
|
||||
data: UpdateTeacherForm
|
||||
): Promise<Teacher> {
|
||||
const response = await request.put<any, Teacher>(`/api/teachers/${id}`, data);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 删除教师
|
||||
export async function deleteTeacher(id: number): Promise<void> {
|
||||
return await request.delete<any, void>(`/api/teachers/${id}`);
|
||||
}
|
||||
|
||||
export const teachersApi = {
|
||||
getList: getTeachersList,
|
||||
getDetail: getTeacherDetail,
|
||||
getByUserId: getTeacherByUserId,
|
||||
create: createTeacher,
|
||||
update: updateTeacher,
|
||||
delete: deleteTeacher,
|
||||
};
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { createRouter, createWebHistory } from "vue-router"
|
||||
import type { RouteRecordRaw } from "vue-router"
|
||||
import { nextTick } from "vue"
|
||||
import { useAuthStore } from "@/stores/auth"
|
||||
import { convertMenusToRoutes } from "@/utils/menu"
|
||||
import "@/types/router"
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import type { RouteRecordRaw } from "vue-router";
|
||||
import { nextTick } from "vue";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { convertMenusToRoutes } from "@/utils/menu";
|
||||
import "@/types/router";
|
||||
|
||||
// 基础路由(不需要动态加载的)
|
||||
const baseRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: "/:tenantCode/login",
|
||||
@ -19,7 +18,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
component: () => import("@/views/auth/Login.vue"),
|
||||
meta: { requiresAuth: false },
|
||||
},
|
||||
// ========== 公众端路由 ==========
|
||||
{
|
||||
path: "/p/login",
|
||||
name: "PublicLogin",
|
||||
@ -78,7 +76,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
component: () => import("@/views/public/mine/Favorites.vue"),
|
||||
meta: { title: "我的收藏" },
|
||||
},
|
||||
// ========== 创作与作品库 ==========
|
||||
{
|
||||
path: "create",
|
||||
name: "PublicCreate",
|
||||
@ -111,15 +108,12 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
// ========== 管理端路由 ==========
|
||||
{
|
||||
path: "/:tenantCode",
|
||||
name: "Main",
|
||||
component: () => import("@/layouts/BasicLayout.vue"),
|
||||
// 不设置固定redirect,由路由守卫根据用户菜单动态跳转到第一个可见菜单
|
||||
meta: {},
|
||||
children: [
|
||||
// 创建活动路由(不需要在菜单中显示)
|
||||
{
|
||||
path: "contests/create",
|
||||
name: "ContestsCreate",
|
||||
@ -130,7 +124,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
permissions: ["contest:create"],
|
||||
},
|
||||
},
|
||||
// 超管活动详情路由
|
||||
{
|
||||
path: "contests/:id/overview",
|
||||
name: "SuperContestOverview",
|
||||
@ -140,7 +133,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
// 活动详情路由(不需要在菜单中显示)
|
||||
{
|
||||
path: "contests/:id",
|
||||
name: "ContestsDetail",
|
||||
@ -151,7 +143,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
permissions: ["contest:read", "activity:read"],
|
||||
},
|
||||
},
|
||||
// 编辑活动路由(不需要在菜单中显示)
|
||||
{
|
||||
path: "contests/:id/edit",
|
||||
name: "ContestsEdit",
|
||||
@ -162,7 +153,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
permissions: ["contest:update"],
|
||||
},
|
||||
},
|
||||
// 活动评委管理路由(不需要在菜单中显示)
|
||||
{
|
||||
path: "contests/:id/judges",
|
||||
name: "ContestsJudges",
|
||||
@ -173,7 +163,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
permissions: ["contest:read"],
|
||||
},
|
||||
},
|
||||
// 个人参与报名路由
|
||||
{
|
||||
path: "contests/:id/register/individual",
|
||||
name: "ContestsRegisterIndividual",
|
||||
@ -183,7 +172,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
// 团队参与报名路由
|
||||
{
|
||||
path: "contests/:id/register/team",
|
||||
name: "ContestsRegisterTeam",
|
||||
@ -193,7 +181,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
// 报名管理列表路由
|
||||
{
|
||||
path: "contests/registrations",
|
||||
name: "ContestsRegistrations",
|
||||
@ -204,7 +191,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
permissions: ["registration:read", "contest:read"],
|
||||
},
|
||||
},
|
||||
// 报名记录路由
|
||||
{
|
||||
path: "contests/registrations/:id/records",
|
||||
name: "RegistrationRecords",
|
||||
@ -215,7 +201,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
permissions: ["registration:read", "contest:read"],
|
||||
},
|
||||
},
|
||||
// 评委评审详情路由(从评审任务列表进入)
|
||||
{
|
||||
path: "activities/review/:id",
|
||||
name: "ActivitiesReviewDetail",
|
||||
@ -226,7 +211,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
permissions: ["review:score"],
|
||||
},
|
||||
},
|
||||
// 评审进度详情路由
|
||||
{
|
||||
path: "contests/reviews/:id/progress",
|
||||
name: "ReviewProgressDetail",
|
||||
@ -236,7 +220,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
// 成果发布详情路由
|
||||
{
|
||||
path: "contests/results/:id",
|
||||
name: "ContestsResultsDetail",
|
||||
@ -246,7 +229,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
// 参赛作品详情列表路由
|
||||
{
|
||||
path: "contests/works/:id/list",
|
||||
name: "WorksDetail",
|
||||
@ -257,29 +239,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
permissions: ["work:read"],
|
||||
},
|
||||
},
|
||||
// 作业提交记录路由
|
||||
{
|
||||
path: "homework/submissions",
|
||||
name: "HomeworkSubmissions",
|
||||
component: () => import("@/views/homework/Submissions.vue"),
|
||||
meta: {
|
||||
title: "作业提交记录",
|
||||
requiresAuth: true,
|
||||
permissions: ["homework:read"],
|
||||
},
|
||||
},
|
||||
// 学生作业详情路由
|
||||
{
|
||||
path: "homework/detail/:id",
|
||||
name: "HomeworkDetail",
|
||||
component: () => import("@/views/homework/StudentDetail.vue"),
|
||||
meta: {
|
||||
title: "作业详情",
|
||||
requiresAuth: true,
|
||||
permissions: ["homework:read"],
|
||||
},
|
||||
},
|
||||
// 教师我的指导路由
|
||||
{
|
||||
path: "student-activities/guidance",
|
||||
name: "TeacherGuidance",
|
||||
@ -290,17 +249,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
permissions: ["activity:read"],
|
||||
},
|
||||
},
|
||||
// 评委评审详情页
|
||||
{
|
||||
path: "activities/review/:id",
|
||||
name: "ReviewDetail",
|
||||
component: () => import("@/views/activities/ReviewDetail.vue"),
|
||||
meta: {
|
||||
title: "作品评审",
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
// 预设评语页面
|
||||
{
|
||||
path: "activities/preset-comments",
|
||||
name: "PresetComments",
|
||||
@ -310,7 +258,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
// ========== 数据统计 ==========
|
||||
{
|
||||
path: "analytics/overview",
|
||||
name: "AnalyticsOverview",
|
||||
@ -331,7 +278,6 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
permissions: ["contest:read"],
|
||||
},
|
||||
},
|
||||
// 动态路由将在这里添加
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -345,523 +291,461 @@ const baseRoutes: RouteRecordRaw[] = [
|
||||
name: "NotFound",
|
||||
component: () => import("@/views/error/404.vue"),
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: baseRoutes,
|
||||
})
|
||||
});
|
||||
|
||||
// 标记是否已经添加了动态路由
|
||||
let dynamicRoutesAdded = false
|
||||
// 保存已添加的路由名称,用于清理
|
||||
let addedRouteNames: string[] = []
|
||||
// 保存上次的菜单数据,用于检测变化
|
||||
let lastMenusHash: string = ""
|
||||
let dynamicRoutesAdded = false;
|
||||
let addedRouteNames: string[] = [];
|
||||
let lastMenusHash = "";
|
||||
|
||||
/**
|
||||
* 重置动态路由标记(用于登出或切换用户时)
|
||||
*/
|
||||
export function resetDynamicRoutes(): void {
|
||||
dynamicRoutesAdded = false
|
||||
addedRouteNames = []
|
||||
lastMenusHash = ""
|
||||
dynamicRoutesAdded = false;
|
||||
addedRouteNames = [];
|
||||
lastMenusHash = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加动态路由
|
||||
* @returns Promise,当路由添加完成并生效后 resolve
|
||||
*/
|
||||
export async function addDynamicRoutes(): Promise<void> {
|
||||
const authStore = useAuthStore()
|
||||
const authStore = useAuthStore();
|
||||
|
||||
if (!authStore.menus || authStore.menus.length === 0) {
|
||||
// 如果没有菜单,重置标记
|
||||
if (dynamicRoutesAdded) {
|
||||
resetDynamicRoutes()
|
||||
resetDynamicRoutes();
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算菜单数据的哈希值,用于检测变化
|
||||
const menusHash = JSON.stringify(
|
||||
authStore.menus.map((m) => ({ id: m.id, path: m.path }))
|
||||
)
|
||||
authStore.menus.map((m) => ({ id: m.id, path: m.path })),
|
||||
);
|
||||
|
||||
// 如果菜单数据没有变化且已经添加过路由,直接返回
|
||||
if (dynamicRoutesAdded && menusHash === lastMenusHash) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已经添加过路由,先移除旧路由
|
||||
if (dynamicRoutesAdded && addedRouteNames.length > 0) {
|
||||
addedRouteNames.forEach((routeName) => {
|
||||
if (router.hasRoute(routeName)) {
|
||||
router.removeRoute(routeName)
|
||||
router.removeRoute(routeName);
|
||||
}
|
||||
})
|
||||
addedRouteNames = []
|
||||
});
|
||||
addedRouteNames = [];
|
||||
}
|
||||
|
||||
// 将菜单转换为路由
|
||||
const dynamicRoutes = convertMenusToRoutes(authStore.menus)
|
||||
const dynamicRoutes = convertMenusToRoutes(authStore.menus);
|
||||
|
||||
if (dynamicRoutes.length === 0) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加动态路由到根路由下
|
||||
dynamicRoutes.forEach((route) => {
|
||||
router.addRoute("Main", route)
|
||||
router.addRoute("Main", route);
|
||||
if (route.name) {
|
||||
addedRouteNames.push(route.name as string)
|
||||
addedRouteNames.push(route.name as string);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
dynamicRoutesAdded = true
|
||||
lastMenusHash = menusHash
|
||||
dynamicRoutesAdded = true;
|
||||
lastMenusHash = menusHash;
|
||||
|
||||
// 等待多个 tick,确保路由已完全注册
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
|
||||
// 额外等待一小段时间,确保路由系统完全更新
|
||||
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 50));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从路径中提取租户编码
|
||||
*/
|
||||
function extractTenantCodeFromPath(path: string): string | null {
|
||||
const match = path.match(/^\/([^/]+)/)
|
||||
return match ? match[1] : null
|
||||
const match = path.match(/^\/([^/]+)/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建带租户编码的路径
|
||||
*/
|
||||
function buildPathWithTenantCode(tenantCode: string, path: string): string {
|
||||
// 如果路径已经包含租户编码,直接返回
|
||||
if (path.startsWith(`/${tenantCode}/`)) {
|
||||
return path
|
||||
return path;
|
||||
}
|
||||
// 移除开头的斜杠(如果有)
|
||||
const cleanPath = path.startsWith("/") ? path.slice(1) : path
|
||||
// 如果路径是根路径,返回租户编码根路径(路由守卫会处理跳转到第一个菜单)
|
||||
const cleanPath = path.startsWith("/") ? path.slice(1) : path;
|
||||
if (cleanPath === "" || cleanPath === tenantCode) {
|
||||
return `/${tenantCode}`
|
||||
return `/${tenantCode}`;
|
||||
}
|
||||
return `/${tenantCode}/${cleanPath}`
|
||||
return `/${tenantCode}/${cleanPath}`;
|
||||
}
|
||||
|
||||
router.beforeEach(async (to, _from, next) => {
|
||||
// 公众端路由不走管理端认证逻辑,直接放行
|
||||
if (to.path.startsWith("/p/") || to.path === "/p") {
|
||||
next()
|
||||
return
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const authStore = useAuthStore();
|
||||
const tenantCodeFromUrl = extractTenantCodeFromPath(to.path);
|
||||
|
||||
// 从URL中提取租户编码
|
||||
const tenantCodeFromUrl = extractTenantCodeFromPath(to.path)
|
||||
|
||||
// 如果 token 存在但用户信息不存在,先获取用户信息
|
||||
if (authStore.token && !authStore.user) {
|
||||
try {
|
||||
const userInfo = await authStore.fetchUserInfo()
|
||||
const userInfo = await authStore.fetchUserInfo();
|
||||
|
||||
// 如果获取用户信息失败或用户信息为空,跳转到登录页
|
||||
if (!userInfo) {
|
||||
authStore.logout()
|
||||
authStore.logout();
|
||||
const tenantCode =
|
||||
tenantCodeFromUrl || extractTenantCodeFromPath(to.path)
|
||||
tenantCodeFromUrl || extractTenantCodeFromPath(to.path);
|
||||
if (tenantCode) {
|
||||
next({
|
||||
path: `/${tenantCode}/login`,
|
||||
query: { redirect: to.fullPath },
|
||||
})
|
||||
});
|
||||
} else {
|
||||
next({ name: "LoginFallback", query: { redirect: to.fullPath } })
|
||||
next({ name: "LoginFallback", query: { redirect: to.fullPath } });
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取用户信息后,检查租户编码一致性
|
||||
const userTenantCode = userInfo?.tenantCode
|
||||
const userTenantCode = userInfo?.tenantCode;
|
||||
if (userTenantCode) {
|
||||
// 如果URL中的租户编码与用户信息不一致,更正URL
|
||||
if (tenantCodeFromUrl && tenantCodeFromUrl !== userTenantCode) {
|
||||
const correctedPath = buildPathWithTenantCode(
|
||||
userTenantCode,
|
||||
to.path.replace(`/${tenantCodeFromUrl}`, "")
|
||||
)
|
||||
next({ path: correctedPath, query: to.query, replace: true })
|
||||
return
|
||||
to.path.replace(`/${tenantCodeFromUrl}`, ""),
|
||||
);
|
||||
next({ path: correctedPath, query: to.query, replace: true });
|
||||
return;
|
||||
}
|
||||
// 如果URL中没有租户编码,添加租户编码
|
||||
if (!tenantCodeFromUrl) {
|
||||
const correctedPath = buildPathWithTenantCode(userTenantCode, to.path)
|
||||
next({ path: correctedPath, query: to.query, replace: true })
|
||||
return
|
||||
const correctedPath = buildPathWithTenantCode(
|
||||
userTenantCode,
|
||||
to.path,
|
||||
);
|
||||
next({ path: correctedPath, query: to.query, replace: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 获取用户信息后,添加动态路由并等待生效
|
||||
await addDynamicRoutes()
|
||||
// 保存原始目标路径
|
||||
const targetPath = to.fullPath
|
||||
// 路由已生效,重新解析目标路由
|
||||
const resolved = router.resolve(targetPath)
|
||||
|
||||
// 如果目标是租户根路径(如 /judge、/super),直接跳转到第一个菜单
|
||||
const isRootPath = to.matched.length === 1 && to.matched[0].name === "Main"
|
||||
await addDynamicRoutes();
|
||||
const targetPath = to.fullPath;
|
||||
const resolved = router.resolve(targetPath);
|
||||
|
||||
const isRootPath =
|
||||
to.matched.length === 1 && to.matched[0].name === "Main";
|
||||
if (isRootPath && authStore.menus?.length) {
|
||||
const findFirst = (menus: any[]): string | null => {
|
||||
const findFirst = (menus: { path?: string; component?: string; children?: typeof menus }[]): string | null => {
|
||||
for (const m of menus) {
|
||||
if (m.path && m.component) return m.path.startsWith("/") ? m.path.slice(1) : m.path
|
||||
if (m.children?.length) { const c = findFirst(m.children); if (c) return c }
|
||||
if (m.path && m.component)
|
||||
return m.path.startsWith("/") ? m.path.slice(1) : m.path;
|
||||
if (m.children?.length) {
|
||||
const c = findFirst(m.children);
|
||||
if (c) return c;
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
const first = findFirst(authStore.menus)
|
||||
return null;
|
||||
};
|
||||
const first = findFirst(authStore.menus);
|
||||
if (first) {
|
||||
const tc = tenantCodeFromUrl || authStore.user?.tenantCode
|
||||
if (tc) { next({ path: `/${tc}/${first}`, replace: true }); return }
|
||||
const tc = tenantCodeFromUrl || userInfo.tenantCode;
|
||||
if (tc) {
|
||||
next({ path: `/${tc}/${first}`, replace: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果解析后的路由不是404,说明路由存在,重新导航
|
||||
if (resolved.name !== "NotFound") {
|
||||
next({ path: targetPath, replace: true })
|
||||
next({ path: targetPath, replace: true });
|
||||
} else {
|
||||
// 如果路由不存在,尝试重定向到用户第一个菜单
|
||||
if (authStore.menus && authStore.menus.length > 0) {
|
||||
const findFirstMenuPath = (menus: any[]): string | null => {
|
||||
const findFirstMenuPath = (
|
||||
menus: {
|
||||
path?: string;
|
||||
component?: string;
|
||||
children?: typeof menus;
|
||||
}[],
|
||||
): string | null => {
|
||||
for (const menu of menus) {
|
||||
if (menu.path && menu.component) {
|
||||
// 移除开头的斜杠
|
||||
return menu.path.startsWith("/")
|
||||
? menu.path.slice(1)
|
||||
: menu.path
|
||||
: menu.path;
|
||||
}
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
const childPath = findFirstMenuPath(menu.children)
|
||||
if (childPath) return childPath
|
||||
const childPath = findFirstMenuPath(menu.children);
|
||||
if (childPath) return childPath;
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const firstMenuPath = findFirstMenuPath(authStore.menus)
|
||||
const firstMenuPath = findFirstMenuPath(authStore.menus);
|
||||
if (firstMenuPath) {
|
||||
const user = authStore.user as { tenantCode?: string } | null
|
||||
const userTenantCode = user?.tenantCode
|
||||
const userTenant = userInfo.tenantCode;
|
||||
const tenantCode =
|
||||
tenantCodeFromUrl ||
|
||||
extractTenantCodeFromPath(to.path) ||
|
||||
userTenantCode
|
||||
userTenant;
|
||||
if (tenantCode) {
|
||||
next({ path: `/${tenantCode}/${firstMenuPath}`, replace: true })
|
||||
return
|
||||
next({ path: `/${tenantCode}/${firstMenuPath}`, replace: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果路由不存在,但需要认证,跳转到登录页(而不是404)
|
||||
if (to.meta.requiresAuth === false) {
|
||||
// 路由确实不存在,允许继续(会显示404页面)
|
||||
next()
|
||||
next();
|
||||
} else {
|
||||
const tenantCode =
|
||||
tenantCodeFromUrl || extractTenantCodeFromPath(to.path)
|
||||
tenantCodeFromUrl || extractTenantCodeFromPath(to.path);
|
||||
if (tenantCode) {
|
||||
next({
|
||||
path: `/${tenantCode}/login`,
|
||||
query: { redirect: to.fullPath },
|
||||
})
|
||||
});
|
||||
} else {
|
||||
next({ name: "LoginFallback", query: { redirect: to.fullPath } })
|
||||
next({ name: "LoginFallback", query: { redirect: to.fullPath } });
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
return;
|
||||
} catch (error) {
|
||||
// 获取失败,清除 token 并跳转到登录页
|
||||
console.error("获取用户信息失败:", error)
|
||||
authStore.logout()
|
||||
const tenantCode = tenantCodeFromUrl || extractTenantCodeFromPath(to.path)
|
||||
console.error("获取用户信息失败:", error);
|
||||
authStore.logout();
|
||||
const tenantCode = tenantCodeFromUrl || extractTenantCodeFromPath(to.path);
|
||||
if (tenantCode) {
|
||||
next({
|
||||
path: `/${tenantCode}/login`,
|
||||
query: { redirect: to.fullPath },
|
||||
})
|
||||
});
|
||||
} else {
|
||||
next({ name: "LoginFallback", query: { redirect: to.fullPath } })
|
||||
next({ name: "LoginFallback", query: { redirect: to.fullPath } });
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果 token 不存在,但需要认证,跳转到登录页
|
||||
if (!authStore.token && to.meta.requiresAuth !== false) {
|
||||
const tenantCode = tenantCodeFromUrl || extractTenantCodeFromPath(to.path)
|
||||
const tenantCode = tenantCodeFromUrl || extractTenantCodeFromPath(to.path);
|
||||
if (tenantCode) {
|
||||
next({
|
||||
path: `/${tenantCode}/login`,
|
||||
query: { redirect: to.fullPath },
|
||||
})
|
||||
});
|
||||
} else {
|
||||
next({ name: "LoginFallback", query: { redirect: to.fullPath } })
|
||||
next({ name: "LoginFallback", query: { redirect: to.fullPath } });
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已登录,检查租户编码一致性
|
||||
if (authStore.isAuthenticated && authStore.user) {
|
||||
const userTenantCode = authStore.user.tenantCode
|
||||
const userTenantCode = authStore.user.tenantCode;
|
||||
if (userTenantCode) {
|
||||
// 如果URL中的租户编码与用户信息不一致,更正URL
|
||||
if (tenantCodeFromUrl && tenantCodeFromUrl !== userTenantCode) {
|
||||
const correctedPath = buildPathWithTenantCode(
|
||||
userTenantCode,
|
||||
to.path.replace(`/${tenantCodeFromUrl}`, "")
|
||||
)
|
||||
next({ path: correctedPath, query: to.query, replace: true })
|
||||
return
|
||||
to.path.replace(`/${tenantCodeFromUrl}`, ""),
|
||||
);
|
||||
next({ path: correctedPath, query: to.query, replace: true });
|
||||
return;
|
||||
}
|
||||
// 如果URL中没有租户编码,添加租户编码(排除不需要认证的特殊路由)
|
||||
const skipTenantCodePaths = ["/login", "/403"]
|
||||
const shouldSkipTenantCode = skipTenantCodePaths.some(p => to.path.startsWith(p))
|
||||
const skipTenantCodePaths = ["/login", "/403"];
|
||||
const shouldSkipTenantCode = skipTenantCodePaths.some((p) =>
|
||||
to.path.startsWith(p),
|
||||
);
|
||||
if (!tenantCodeFromUrl && !shouldSkipTenantCode) {
|
||||
const correctedPath = buildPathWithTenantCode(userTenantCode, to.path)
|
||||
next({ path: correctedPath, query: to.query, replace: true })
|
||||
return
|
||||
const correctedPath = buildPathWithTenantCode(userTenantCode, to.path);
|
||||
next({ path: correctedPath, query: to.query, replace: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已登录且有菜单数据,添加或更新动态路由
|
||||
if (authStore.isAuthenticated && authStore.menus.length > 0) {
|
||||
// 保存添加路由前的状态
|
||||
const wasRoutesAdded = dynamicRoutesAdded
|
||||
const wasRoutesAdded = dynamicRoutesAdded;
|
||||
await addDynamicRoutes();
|
||||
|
||||
// 添加或更新动态路由
|
||||
await addDynamicRoutes()
|
||||
|
||||
// 如果这是第一次添加路由,需要重新导航
|
||||
if (!wasRoutesAdded && dynamicRoutesAdded) {
|
||||
// 等待路由完全生效
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
|
||||
// 保存原始目标路径
|
||||
const targetPath = to.fullPath
|
||||
// 路由已生效,重新解析目标路由
|
||||
const resolved = router.resolve(targetPath)
|
||||
const targetPath = to.fullPath;
|
||||
const resolved = router.resolve(targetPath);
|
||||
const isMainRoute = to.name === "Main";
|
||||
|
||||
// 如果访问的是主路由,重定向到第一个菜单
|
||||
const isMainRoute = to.name === "Main"
|
||||
|
||||
console.log('Route guard debug:', {
|
||||
targetPath,
|
||||
resolvedName: resolved.name,
|
||||
resolvedPath: resolved.path,
|
||||
isMainRoute,
|
||||
toName: to.name,
|
||||
toPath: to.path,
|
||||
})
|
||||
|
||||
// 如果解析后的路由不是404,说明路由存在,重新导航
|
||||
if (resolved.name !== "NotFound" && !isMainRoute) {
|
||||
next({ path: targetPath, replace: true })
|
||||
return
|
||||
next({ path: targetPath, replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果路由不存在或是主路由,尝试重定向到用户第一个菜单
|
||||
if (authStore.menus && authStore.menus.length > 0) {
|
||||
const findFirstMenuPath = (menus: any[]): string | null => {
|
||||
const findFirstMenuPath = (
|
||||
menus: {
|
||||
path?: string;
|
||||
component?: string;
|
||||
children?: typeof menus;
|
||||
}[],
|
||||
): string | null => {
|
||||
for (const menu of menus) {
|
||||
if (menu.path && menu.component) {
|
||||
// 移除开头的斜杠
|
||||
return menu.path.startsWith("/") ? menu.path.slice(1) : menu.path
|
||||
return menu.path.startsWith("/")
|
||||
? menu.path.slice(1)
|
||||
: menu.path;
|
||||
}
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
const childPath = findFirstMenuPath(menu.children)
|
||||
if (childPath) return childPath
|
||||
const childPath = findFirstMenuPath(menu.children);
|
||||
if (childPath) return childPath;
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const firstMenuPath = findFirstMenuPath(authStore.menus)
|
||||
const firstMenuPath = findFirstMenuPath(authStore.menus);
|
||||
if (firstMenuPath) {
|
||||
const userTenantCode = authStore.user
|
||||
? (authStore.user.tenantCode as string | undefined)
|
||||
: undefined
|
||||
const userTenantCode = authStore.user?.tenantCode;
|
||||
const tenantCode =
|
||||
tenantCodeFromUrl ||
|
||||
extractTenantCodeFromPath(to.path) ||
|
||||
userTenantCode
|
||||
userTenantCode;
|
||||
if (tenantCode) {
|
||||
// 再次等待,确保路由完全注册
|
||||
await nextTick()
|
||||
next({ path: `/${tenantCode}/${firstMenuPath}`, replace: true })
|
||||
return
|
||||
await nextTick();
|
||||
next({ path: `/${tenantCode}/${firstMenuPath}`, replace: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有任何菜单,跳转到404页面
|
||||
const tenantCodeFor404 =
|
||||
tenantCodeFromUrl ||
|
||||
extractTenantCodeFromPath(to.path) ||
|
||||
(authStore.user
|
||||
? (authStore.user.tenantCode as string | undefined)
|
||||
: undefined)
|
||||
authStore.user?.tenantCode;
|
||||
if (tenantCodeFor404) {
|
||||
next({ path: `/${tenantCodeFor404}/404`, replace: true })
|
||||
next({ path: `/${tenantCodeFor404}/404`, replace: true });
|
||||
} else {
|
||||
next({ name: "NotFound" })
|
||||
next({ name: "NotFound" });
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已登录且有菜单,但路由已添加,检查当前路由是否存在
|
||||
if (
|
||||
authStore.isAuthenticated &&
|
||||
authStore.menus.length > 0 &&
|
||||
dynamicRoutesAdded
|
||||
) {
|
||||
const resolved = router.resolve(to.fullPath)
|
||||
// 如果访问的是 Main 路由(无具体子路径)或路由不存在,重定向到用户第一个菜单
|
||||
const isMainRouteWithoutChild = to.name === "Main" || to.matched.length === 1 && to.matched[0].name === "Main"
|
||||
const resolved = router.resolve(to.fullPath);
|
||||
const isMainRouteWithoutChild =
|
||||
to.name === "Main" ||
|
||||
(to.matched.length === 1 && to.matched[0].name === "Main");
|
||||
if (
|
||||
(resolved.name === "NotFound" || isMainRouteWithoutChild) &&
|
||||
to.name !== "Login" &&
|
||||
to.name !== "LoginFallback"
|
||||
) {
|
||||
const findFirstMenuPath = (menus: any[]): string | null => {
|
||||
const findFirstMenuPath = (
|
||||
menus: {
|
||||
path?: string;
|
||||
component?: string;
|
||||
children?: typeof menus;
|
||||
}[],
|
||||
): string | null => {
|
||||
for (const menu of menus) {
|
||||
if (menu.path && menu.component) {
|
||||
return menu.path.startsWith("/") ? menu.path.slice(1) : menu.path
|
||||
return menu.path.startsWith("/") ? menu.path.slice(1) : menu.path;
|
||||
}
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
const childPath = findFirstMenuPath(menu.children)
|
||||
if (childPath) return childPath
|
||||
const childPath = findFirstMenuPath(menu.children);
|
||||
if (childPath) return childPath;
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const firstMenuPath = findFirstMenuPath(authStore.menus)
|
||||
const firstMenuPath = findFirstMenuPath(authStore.menus);
|
||||
if (firstMenuPath) {
|
||||
const userTenantCode = authStore.user
|
||||
? (authStore.user.tenantCode as string | undefined)
|
||||
: undefined
|
||||
const userTenantCode = authStore.user?.tenantCode;
|
||||
const tenantCode =
|
||||
tenantCodeFromUrl ||
|
||||
extractTenantCodeFromPath(to.path) ||
|
||||
userTenantCode
|
||||
userTenantCode;
|
||||
if (tenantCode) {
|
||||
next({ path: `/${tenantCode}/${firstMenuPath}`, replace: true })
|
||||
return
|
||||
next({ path: `/${tenantCode}/${firstMenuPath}`, replace: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有任何菜单,跳转到404页面
|
||||
const tenantCodeFor404 =
|
||||
tenantCodeFromUrl ||
|
||||
extractTenantCodeFromPath(to.path) ||
|
||||
(authStore.user
|
||||
? (authStore.user.tenantCode as string | undefined)
|
||||
: undefined)
|
||||
authStore.user?.tenantCode;
|
||||
if (tenantCodeFor404) {
|
||||
next({ path: `/${tenantCodeFor404}/404`, replace: true })
|
||||
return
|
||||
next({ path: `/${tenantCodeFor404}/404`, replace: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否需要认证
|
||||
if (to.meta.requiresAuth !== false) {
|
||||
// 如果没有 token,跳转到登录页
|
||||
if (!authStore.token) {
|
||||
const tenantCode = tenantCodeFromUrl || extractTenantCodeFromPath(to.path)
|
||||
const tenantCode = tenantCodeFromUrl || extractTenantCodeFromPath(to.path);
|
||||
if (tenantCode) {
|
||||
next({
|
||||
path: `/${tenantCode}/login`,
|
||||
query: { redirect: to.fullPath },
|
||||
})
|
||||
});
|
||||
} else {
|
||||
next({ name: "LoginFallback", query: { redirect: to.fullPath } })
|
||||
next({ name: "LoginFallback", query: { redirect: to.fullPath } });
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
// 如果有 token 但没有用户信息,跳转到登录页
|
||||
if (!authStore.user) {
|
||||
const tenantCode = tenantCodeFromUrl || extractTenantCodeFromPath(to.path)
|
||||
const tenantCode = tenantCodeFromUrl || extractTenantCodeFromPath(to.path);
|
||||
if (tenantCode) {
|
||||
next({
|
||||
path: `/${tenantCode}/login`,
|
||||
query: { redirect: to.fullPath },
|
||||
})
|
||||
});
|
||||
} else {
|
||||
next({ name: "LoginFallback", query: { redirect: to.fullPath } })
|
||||
next({ name: "LoginFallback", query: { redirect: to.fullPath } });
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已登录,访问登录页则重定向到首页
|
||||
if (
|
||||
(to.name === "Login" || to.name === "LoginFallback") &&
|
||||
authStore.isAuthenticated
|
||||
) {
|
||||
// 确保动态路由已添加并等待生效
|
||||
if (!dynamicRoutesAdded && authStore.menus.length > 0) {
|
||||
await addDynamicRoutes()
|
||||
await addDynamicRoutes();
|
||||
}
|
||||
// 重定向到带租户编码的根路径(路由守卫会处理跳转到第一个菜单)
|
||||
const userTenantCode = authStore.user?.tenantCode || "default"
|
||||
next({ path: `/${userTenantCode}` })
|
||||
return
|
||||
const userTenantCode = authStore.user?.tenantCode || "default";
|
||||
next({ path: `/${userTenantCode}` });
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理登录页面的租户编码
|
||||
if (to.name === "LoginFallback" && !tenantCodeFromUrl) {
|
||||
// 如果访问的是 /login,但没有租户编码,检查是否有用户信息中的租户编码
|
||||
if (authStore.isAuthenticated && authStore.user?.tenantCode) {
|
||||
const userTenantCode = authStore.user.tenantCode
|
||||
next({ path: `/${userTenantCode}/login`, replace: true })
|
||||
return
|
||||
const userTenantCode = authStore.user.tenantCode;
|
||||
next({ path: `/${userTenantCode}/login`, replace: true });
|
||||
return;
|
||||
}
|
||||
// 如果没有租户编码,允许访问(会显示租户输入框)
|
||||
next()
|
||||
return
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查角色权限
|
||||
const requiredRoles = to.meta.roles
|
||||
const requiredRoles = to.meta.roles as string[] | undefined;
|
||||
if (requiredRoles && requiredRoles.length > 0) {
|
||||
if (!authStore.hasAnyRole(requiredRoles)) {
|
||||
// 没有所需角色,跳转到 403 页面
|
||||
next({ name: "Forbidden" })
|
||||
return
|
||||
next({ name: "Forbidden" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
const requiredPermissions = to.meta.permissions
|
||||
const requiredPermissions = to.meta.permissions as string[] | undefined;
|
||||
if (requiredPermissions && requiredPermissions.length > 0) {
|
||||
if (!authStore.hasAnyPermission(requiredPermissions)) {
|
||||
// 没有所需权限,跳转到 403 页面
|
||||
next({ name: "Forbidden" })
|
||||
return
|
||||
next({ name: "Forbidden" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
next();
|
||||
});
|
||||
|
||||
export default router
|
||||
export default router;
|
||||
|
||||
@ -1,29 +1,16 @@
|
||||
import { h } from "vue"
|
||||
import type { RouteRecordRaw } from "vue-router"
|
||||
import type { MenuProps } from "ant-design-vue"
|
||||
import type { Menu } from "@/api/menus"
|
||||
import * as Icons from "@ant-design/icons-vue"
|
||||
import { h } from "vue";
|
||||
import type { RouteRecordRaw } from "vue-router";
|
||||
import type { MenuProps } from "ant-design-vue";
|
||||
import type { Menu } from "@/api/menus";
|
||||
import * as Icons from "@ant-design/icons-vue";
|
||||
|
||||
/**
|
||||
* 组件路径映射
|
||||
* 将数据库中的组件路径映射到实际的导入函数
|
||||
* 注意:Vite 的动态 import() 不支持路径别名,所以需要在这里预先定义所有组件
|
||||
* 组件路径映射(Vite 需静态分析 import,故在此集中声明)
|
||||
*/
|
||||
// 空布局组件,用于父级菜单
|
||||
const EmptyLayout = () => import("@/layouts/EmptyLayout.vue")
|
||||
const EmptyLayout = () => import("@/layouts/EmptyLayout.vue");
|
||||
|
||||
const componentMap: Record<string, () => Promise<any>> = {
|
||||
// 工作台模块
|
||||
const componentMap: Record<string, () => Promise<unknown>> = {
|
||||
"workbench/Index": () => import("@/views/workbench/Index.vue"),
|
||||
// 学校管理模块
|
||||
"school/schools/Index": () => import("@/views/school/schools/Index.vue"),
|
||||
"school/departments/Index": () =>
|
||||
import("@/views/school/departments/Index.vue"),
|
||||
"school/grades/Index": () => import("@/views/school/grades/Index.vue"),
|
||||
"school/classes/Index": () => import("@/views/school/classes/Index.vue"),
|
||||
"school/teachers/Index": () => import("@/views/school/teachers/Index.vue"),
|
||||
"school/students/Index": () => import("@/views/school/students/Index.vue"),
|
||||
// 活动管理模块
|
||||
"contests/Index": () => import("@/views/contests/Index.vue"),
|
||||
"contests/Activities": () => import("@/views/contests/Activities.vue"),
|
||||
"contests/Create": () => import("@/views/contests/Create.vue"),
|
||||
@ -42,28 +29,18 @@ const componentMap: Record<string, () => Promise<any>> = {
|
||||
"contests/judges/Index": () => import("@/views/contests/judges/Index.vue"),
|
||||
"contests/results/Index": () => import("@/views/contests/results/Index.vue"),
|
||||
"contests/notices/Index": () => import("@/views/contests/notices/Index.vue"),
|
||||
"contests/ReviewRules": () => import("@/views/contests/Index.vue"), // 评审规则临时使用活动列表
|
||||
// 内容管理模块
|
||||
"contests/ReviewRules": () => import("@/views/contests/Index.vue"),
|
||||
"content/WorkReview": () => import("@/views/content/WorkReview.vue"),
|
||||
"content/WorkManagement": () => import("@/views/content/WorkManagement.vue"),
|
||||
"content/TagManagement": () => import("@/views/content/TagManagement.vue"),
|
||||
// 数据统计模块
|
||||
"analytics/Overview": () => import("@/views/analytics/Overview.vue"),
|
||||
"analytics/Review": () => import("@/views/analytics/Review.vue"),
|
||||
// 作业管理模块
|
||||
"homework/Index": () => import("@/views/homework/Index.vue"),
|
||||
"homework/Submissions": () => import("@/views/homework/Submissions.vue"),
|
||||
"homework/ReviewRules": () => import("@/views/homework/ReviewRules.vue"),
|
||||
// 学生端作业模块
|
||||
"homework/StudentList": () => import("@/views/homework/StudentList.vue"),
|
||||
"homework/StudentDetail": () => import("@/views/homework/StudentDetail.vue"),
|
||||
// 我的评审模块(评委)
|
||||
"activities/Guidance": () => import("@/views/activities/Guidance.vue"),
|
||||
"activities/Review": () => import("@/views/activities/Review.vue"),
|
||||
"activities/ReviewDetail": () => import("@/views/activities/ReviewDetail.vue"),
|
||||
"activities/Comments": () => import("@/views/activities/Comments.vue"),
|
||||
"activities/PresetComments": () => import("@/views/activities/PresetComments.vue"),
|
||||
// 系统管理模块
|
||||
"activities/PresetComments": () =>
|
||||
import("@/views/activities/PresetComments.vue"),
|
||||
"system/users/Index": () => import("@/views/system/users/Index.vue"),
|
||||
"system/roles/Index": () => import("@/views/system/roles/Index.vue"),
|
||||
"system/permissions/Index": () =>
|
||||
@ -72,304 +49,244 @@ const componentMap: Record<string, () => Promise<any>> = {
|
||||
"system/tenants/Index": () => import("@/views/system/tenants/Index.vue"),
|
||||
"system/dict/Index": () => import("@/views/system/dict/Index.vue"),
|
||||
"system/config/Index": () => import("@/views/system/config/Index.vue"),
|
||||
"system/logs/Index": () => import("@/views/system/logs/Index.vue"),
|
||||
"system/public-users/Index": () =>
|
||||
import("@/views/system/public-users/Index.vue"),
|
||||
}
|
||||
};
|
||||
|
||||
// 用于兜底加载未在 componentMap 中声明的视图组件(避免 Vite 动态 import 分析警告)
|
||||
const viewComponentModules = import.meta.glob(
|
||||
"@/views/**/*.vue"
|
||||
) as Record<string, () => Promise<any>>
|
||||
"@/views/**/*.vue",
|
||||
) as Record<string, () => Promise<unknown>>;
|
||||
|
||||
/**
|
||||
* 获取图标组件
|
||||
*/
|
||||
/** Ant Design 菜单图标 */
|
||||
export function getIconComponent(iconName: string | null | undefined) {
|
||||
if (!iconName) return null
|
||||
const IconComponent = (Icons as any)[iconName]
|
||||
if (!iconName) return null;
|
||||
const IconComponent = (Icons as Record<string, unknown>)[iconName];
|
||||
if (IconComponent) {
|
||||
return () => h(IconComponent)
|
||||
return () => h(IconComponent as object);
|
||||
}
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从菜单路径生成路由名称(与 convertMenusToRoutes 中的逻辑一致)
|
||||
* 使用菜单ID来区分,避免路由名称冲突(同一路径可能出现在不同菜单树分支)
|
||||
* 路由 name 生成规则(须与 BasicLayout 中 handleMenuClick 一致)
|
||||
*/
|
||||
function getRouteNameFromPath(
|
||||
path: string | null | undefined,
|
||||
menuId: number,
|
||||
isChild: boolean = false
|
||||
): string {
|
||||
if (path) {
|
||||
const baseName = path
|
||||
.split("/")
|
||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join("")
|
||||
// 统一添加 menuId,避免顶级菜单同名(例如多个“/contests”菜单)导致路由被覆盖
|
||||
return `${baseName}${menuId}`
|
||||
.join("");
|
||||
return `${baseName}${menuId}`;
|
||||
}
|
||||
return `Menu${menuId}`
|
||||
return `Menu${menuId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据库菜单转换为 Ant Design Vue Menu 的 items 格式
|
||||
* key 使用路由名称而不是路径
|
||||
* 当父菜单只有一个子菜单时,直接显示子菜单,不显示父级折叠结构
|
||||
*/
|
||||
export function convertMenusToMenuItems(
|
||||
menus: Menu[],
|
||||
isChild: boolean = false
|
||||
): MenuProps["items"] {
|
||||
const result: any[] = []
|
||||
function removeParentPathFromRoutePath(
|
||||
routePath: string,
|
||||
parentPath: string,
|
||||
): string {
|
||||
if (!parentPath) {
|
||||
return routePath;
|
||||
}
|
||||
const normalizedRoutePath = routePath.startsWith("/")
|
||||
? routePath.slice(1)
|
||||
: routePath;
|
||||
const normalizedParentPath = parentPath.startsWith("/")
|
||||
? parentPath.slice(1)
|
||||
: parentPath;
|
||||
|
||||
if (normalizedRoutePath.startsWith(normalizedParentPath + "/")) {
|
||||
return normalizedRoutePath.slice(normalizedParentPath.length + 1);
|
||||
}
|
||||
if (normalizedRoutePath === normalizedParentPath) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return normalizedRoutePath;
|
||||
}
|
||||
|
||||
/** 后端菜单树 → Ant Design Menu items(key 为路由 name) */
|
||||
export function convertMenusToMenuItems(menus: Menu[]): MenuProps["items"] {
|
||||
const result: NonNullable<MenuProps["items"]> = [];
|
||||
|
||||
menus.forEach((menu) => {
|
||||
// 如果只有一个子菜单,直接提升子菜单到当前层级
|
||||
if (menu.children && menu.children.length === 1) {
|
||||
const onlyChild = menu.children[0]
|
||||
const childRouteName = getRouteNameFromPath(onlyChild.path, onlyChild.id, true)
|
||||
const onlyChild = menu.children[0];
|
||||
const childRouteName = getRouteNameFromPath(onlyChild.path, onlyChild.id);
|
||||
|
||||
const item: any = {
|
||||
const item: Record<string, unknown> = {
|
||||
key: childRouteName,
|
||||
label: onlyChild.name,
|
||||
title: onlyChild.name,
|
||||
}
|
||||
};
|
||||
|
||||
// 优先使用父菜单的图标,如果没有则使用子菜单的图标
|
||||
const iconName = menu.icon || onlyChild.icon
|
||||
const iconName = menu.icon || onlyChild.icon;
|
||||
if (iconName) {
|
||||
const IconComponent = getIconComponent(iconName)
|
||||
const IconComponent = getIconComponent(iconName);
|
||||
if (IconComponent) {
|
||||
item.icon = IconComponent
|
||||
item.icon = IconComponent;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果这个唯一的子菜单还有子菜单,继续递归处理
|
||||
if (onlyChild.children && onlyChild.children.length > 0) {
|
||||
item.children = convertMenusToMenuItems(onlyChild.children, true)
|
||||
item.children = convertMenusToMenuItems(onlyChild.children);
|
||||
}
|
||||
|
||||
result.push(item)
|
||||
return
|
||||
result.push(item as unknown as (typeof result)[number]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 正常处理:使用路由名称作为 key
|
||||
const routeName = getRouteNameFromPath(menu.path, menu.id, isChild)
|
||||
const routeName = getRouteNameFromPath(menu.path, menu.id);
|
||||
|
||||
const item: any = {
|
||||
const item: Record<string, unknown> = {
|
||||
key: routeName,
|
||||
label: menu.name,
|
||||
title: menu.name,
|
||||
}
|
||||
};
|
||||
|
||||
// 添加图标
|
||||
if (menu.icon) {
|
||||
const IconComponent = getIconComponent(menu.icon)
|
||||
const IconComponent = getIconComponent(menu.icon);
|
||||
if (IconComponent) {
|
||||
item.icon = IconComponent
|
||||
item.icon = IconComponent;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有多个子菜单,递归处理
|
||||
if (menu.children && menu.children.length > 1) {
|
||||
item.children = convertMenusToMenuItems(menu.children, true)
|
||||
item.children = convertMenusToMenuItems(menu.children);
|
||||
}
|
||||
|
||||
result.push(item)
|
||||
})
|
||||
result.push(item as unknown as (typeof result)[number]);
|
||||
});
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据库菜单转换为 Vue Router 的路由配置
|
||||
* 注意:这些路由会被添加到 /:tenantCode 父路由下,所以不需要再添加 tenantCode 前缀
|
||||
*/
|
||||
/**
|
||||
* 移除路径中与父路径重合的部分
|
||||
*/
|
||||
function removeParentPathFromRoutePath(
|
||||
routePath: string,
|
||||
parentPath: string
|
||||
): string {
|
||||
if (!parentPath) {
|
||||
return routePath
|
||||
}
|
||||
|
||||
// 标准化路径:移除开头的斜杠
|
||||
const normalizedRoutePath = routePath.startsWith("/")
|
||||
? routePath.slice(1)
|
||||
: routePath
|
||||
const normalizedParentPath = parentPath.startsWith("/")
|
||||
? parentPath.slice(1)
|
||||
: parentPath
|
||||
|
||||
// 如果子路径以父路径开头,移除父路径部分
|
||||
if (normalizedRoutePath.startsWith(normalizedParentPath + "/")) {
|
||||
return normalizedRoutePath.slice(normalizedParentPath.length + 1)
|
||||
} else if (normalizedRoutePath === normalizedParentPath) {
|
||||
// 如果路径完全相同,返回空字符串(表示当前路由)
|
||||
return ""
|
||||
}
|
||||
|
||||
return normalizedRoutePath
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 后端菜单树 → 挂到 `/:tenantCode`(Main)下的子路由 */
|
||||
export function convertMenusToRoutes(
|
||||
menus: Menu[],
|
||||
parentPath: string = "",
|
||||
isChild: boolean = false
|
||||
): RouteRecordRaw[] {
|
||||
const routes: RouteRecordRaw[] = []
|
||||
const routes: RouteRecordRaw[] = [];
|
||||
|
||||
menus.forEach((menu) => {
|
||||
// 构建路由路径
|
||||
// 注意:这些路由会被添加到 /:tenantCode 父路由下,所以路径应该是相对路径
|
||||
let routePath = menu.path
|
||||
? menu.path.startsWith("/")
|
||||
? menu.path.slice(1) // 移除开头的斜杠,因为这是相对路径
|
||||
? menu.path.slice(1)
|
||||
: menu.path
|
||||
: `menu-${menu.id}`
|
||||
: `menu-${menu.id}`;
|
||||
|
||||
// 如果有父路径,移除与父路径重合的部分
|
||||
if (parentPath) {
|
||||
routePath = removeParentPathFromRoutePath(routePath, parentPath)
|
||||
routePath = removeParentPathFromRoutePath(routePath, parentPath);
|
||||
}
|
||||
|
||||
// 构建路由名称(与 convertMenusToMenuItems 中的逻辑一致)
|
||||
const routeName = getRouteNameFromPath(menu.path, menu.id, isChild)
|
||||
const routeName = getRouteNameFromPath(menu.path, menu.id);
|
||||
|
||||
// 确定组件加载器
|
||||
let componentLoader: (() => Promise<any>) | undefined
|
||||
let componentLoader: (() => Promise<unknown>) | undefined;
|
||||
if (menu.component) {
|
||||
// 从组件映射中获取导入函数
|
||||
// 如果组件路径以 @/ 开头,说明是完整路径,需要去掉 @/views/ 前缀和 .vue 后缀来匹配
|
||||
let componentKey = menu.component
|
||||
let componentKey = menu.component;
|
||||
if (componentKey.startsWith("@/views/")) {
|
||||
componentKey = componentKey.replace("@/views/", "").replace(".vue", "")
|
||||
componentKey = componentKey
|
||||
.replace("@/views/", "")
|
||||
.replace(".vue", "");
|
||||
} else if (componentKey.endsWith(".vue")) {
|
||||
componentKey = componentKey.replace(".vue", "")
|
||||
componentKey = componentKey.replace(".vue", "");
|
||||
}
|
||||
|
||||
// 从映射中获取组件导入函数
|
||||
const mappedLoader = componentMap[componentKey]
|
||||
const mappedLoader = componentMap[componentKey];
|
||||
if (mappedLoader) {
|
||||
componentLoader = mappedLoader
|
||||
componentLoader = mappedLoader;
|
||||
} else {
|
||||
const componentPath = menu.component
|
||||
console.warn(
|
||||
`组件路径 "${componentPath}" (key: "${componentKey}") 未在 componentMap 中定义,请添加到 menu.ts 的 componentMap 中`
|
||||
)
|
||||
// 如果找不到映射,使用 import.meta.glob 的模块映射进行兜底加载(可被 Vite 分析)
|
||||
let normalizedPath = componentPath.startsWith("@/")
|
||||
? componentPath
|
||||
: `@/views/${componentPath}`
|
||||
let normalizedPath = menu.component.startsWith("@/")
|
||||
? menu.component
|
||||
: `@/views/${menu.component}`;
|
||||
if (!normalizedPath.endsWith(".vue")) {
|
||||
normalizedPath = `${normalizedPath}.vue`
|
||||
normalizedPath = `${normalizedPath}.vue`;
|
||||
}
|
||||
|
||||
const candidatePaths = [
|
||||
normalizedPath,
|
||||
// 兼容部分构建环境下 glob key 可能为 /src/xxx 形式
|
||||
normalizedPath.startsWith("@/views/")
|
||||
? normalizedPath.replace("@/views/", "/src/views/")
|
||||
: normalizedPath.startsWith("@/")
|
||||
? normalizedPath.replace("@/", "/src/")
|
||||
: normalizedPath,
|
||||
]
|
||||
];
|
||||
|
||||
const fallbackLoader =
|
||||
candidatePaths.map((p) => viewComponentModules[p]).find(Boolean) ||
|
||||
undefined
|
||||
const fallbackLoader = candidatePaths
|
||||
.map((p) => viewComponentModules[p])
|
||||
.find(Boolean);
|
||||
|
||||
if (fallbackLoader) {
|
||||
componentLoader = fallbackLoader
|
||||
componentLoader = fallbackLoader;
|
||||
} else {
|
||||
console.warn(
|
||||
`组件路径 "${normalizedPath}" 未找到对应的视图文件(尝试:${candidatePaths.join(
|
||||
", "
|
||||
)}),已跳过渲染该菜单`
|
||||
)
|
||||
componentLoader = undefined
|
||||
`组件未找到,已跳过菜单「${menu.name}」:尝试路径 ${candidatePaths.join(", ")}`,
|
||||
);
|
||||
componentLoader = undefined;
|
||||
}
|
||||
}
|
||||
} else if (menu.children && menu.children.length > 0) {
|
||||
// 如果没有 component 但有子菜单,使用空布局组件来渲染子路由
|
||||
componentLoader = EmptyLayout
|
||||
componentLoader = EmptyLayout;
|
||||
}
|
||||
|
||||
// 如果有子菜单,先处理子菜单
|
||||
let childrenRoutes: RouteRecordRaw[] | undefined
|
||||
let redirectToDefaultChild: string | undefined
|
||||
let childrenRoutes: RouteRecordRaw[] | undefined;
|
||||
let redirectToDefaultChild: string | undefined;
|
||||
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
childrenRoutes = convertMenusToRoutes(
|
||||
menu.children,
|
||||
menu.path
|
||||
? menu.path.startsWith("/")
|
||||
? menu.path.slice(1)
|
||||
: menu.path
|
||||
: parentPath,
|
||||
true // 标记为子菜单
|
||||
)
|
||||
const nextParent = menu.path
|
||||
? menu.path.startsWith("/")
|
||||
? menu.path.slice(1)
|
||||
: menu.path
|
||||
: parentPath;
|
||||
|
||||
childrenRoutes = convertMenusToRoutes(menu.children, nextParent);
|
||||
|
||||
// 如果父菜单没有 component,但子菜单中有路径为空字符串的路由(默认子路由)
|
||||
// 应该让父路由重定向到该子路由
|
||||
if (!menu.component && childrenRoutes.length > 0) {
|
||||
const defaultChild = childrenRoutes.find((child) => child.path === "")
|
||||
const defaultChild = childrenRoutes.find((child) => child.path === "");
|
||||
if (defaultChild && defaultChild.name) {
|
||||
// 重定向到默认子路由(使用路由名称)
|
||||
redirectToDefaultChild = defaultChild.name as string
|
||||
redirectToDefaultChild = defaultChild.name as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果既没有组件也没有子路由,跳过这个菜单(无法渲染)
|
||||
if (!componentLoader && (!childrenRoutes || childrenRoutes.length === 0)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const route: RouteRecordRaw = {
|
||||
const route = {
|
||||
path: routePath,
|
||||
name: routeName,
|
||||
meta: {
|
||||
title: menu.name,
|
||||
requiresAuth: true,
|
||||
// 如果菜单有权限要求,添加到路由meta中
|
||||
...(menu.permission && { permissions: [menu.permission] }),
|
||||
},
|
||||
...(componentLoader && { component: componentLoader }),
|
||||
// 如果有重定向,添加重定向
|
||||
...(componentLoader && {
|
||||
component: componentLoader as () => Promise<object>,
|
||||
}),
|
||||
...(redirectToDefaultChild && {
|
||||
redirect: { name: redirectToDefaultChild },
|
||||
}),
|
||||
// 如果有子菜单,添加子路由
|
||||
...(childrenRoutes &&
|
||||
childrenRoutes.length > 0 && {
|
||||
children: childrenRoutes,
|
||||
}),
|
||||
} as RouteRecordRaw
|
||||
childrenRoutes.length > 0 && { children: childrenRoutes }),
|
||||
} as RouteRecordRaw;
|
||||
|
||||
routes.push(route)
|
||||
})
|
||||
routes.push(route);
|
||||
});
|
||||
|
||||
return routes
|
||||
return routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扁平化菜单树,用于查找特定路径的菜单
|
||||
*/
|
||||
export function flattenMenus(menus: Menu[]): Menu[] {
|
||||
const result: Menu[] = []
|
||||
|
||||
const result: Menu[] = [];
|
||||
menus.forEach((menu) => {
|
||||
result.push(menu)
|
||||
result.push(menu);
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
result.push(...flattenMenus(menu.children))
|
||||
result.push(...flattenMenus(menu.children));
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -157,9 +157,9 @@ import {
|
||||
registrationsApi,
|
||||
type Contest,
|
||||
type ContestRegistration,
|
||||
type RegistrationCandidateUser,
|
||||
} from "@/api/contests"
|
||||
import { useListRequest } from "@/composables/useListRequest"
|
||||
import { type Student } from "@/api/students"
|
||||
import AddParticipantDrawer from "./components/AddParticipantDrawer.vue"
|
||||
import AddTeacherDrawer from "./components/AddTeacherDrawer.vue"
|
||||
|
||||
@ -175,7 +175,7 @@ const currentRegistrationId = ref<number | null>(null)
|
||||
const contest = ref<Contest | null>(null)
|
||||
|
||||
// 选中的参赛人列表
|
||||
const selectedParticipants = ref<Student[]>([])
|
||||
const selectedParticipants = ref<RegistrationCandidateUser[]>([])
|
||||
|
||||
// 使用列表请求组合函数
|
||||
const {
|
||||
@ -245,7 +245,7 @@ const columns: TableColumnsType = [
|
||||
]
|
||||
|
||||
// 获取机构信息
|
||||
const getOrganizationInfo = (record: ContestRegistration | Student): string => {
|
||||
const getOrganizationInfo = (record: ContestRegistration | RegistrationCandidateUser): string => {
|
||||
// 优先从 user.student.class 获取(已报名状态)
|
||||
if ("user" in record && record.user) {
|
||||
const user = record.user as any
|
||||
@ -255,9 +255,8 @@ const getOrganizationInfo = (record: ContestRegistration | Student): string => {
|
||||
}`
|
||||
}
|
||||
}
|
||||
// 从 record.class 获取(待报名状态)
|
||||
if ("class" in record && record.class) {
|
||||
return `${record.class.grade?.name || ""} - ${record.class.name || ""}`
|
||||
if ("roleNames" in record && record.roleNames?.length) {
|
||||
return record.roleNames.join("、")
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
@ -280,14 +279,18 @@ const selectedTeacherUserIds = computed(() => {
|
||||
// 合并显示数据源(已报名的 + 待报名的)
|
||||
const displayDataSource = computed(() => {
|
||||
// 将选中的参赛人转换为表格显示格式
|
||||
const pendingParticipants = selectedParticipants.value.map((student) => ({
|
||||
id: student.id,
|
||||
userId: student.userId,
|
||||
user: student.user,
|
||||
gender: student.gender,
|
||||
phone: student.phone,
|
||||
class: student.class,
|
||||
registrationState: undefined, // 待报名状态
|
||||
const pendingParticipants = selectedParticipants.value.map((u) => ({
|
||||
id: u.id,
|
||||
userId: u.id,
|
||||
user: {
|
||||
nickname: u.nickname,
|
||||
username: u.username,
|
||||
phone: u.phone,
|
||||
gender: u.gender,
|
||||
},
|
||||
gender: u.gender === "male" ? 1 : u.gender === "female" ? 2 : undefined,
|
||||
phone: u.phone,
|
||||
registrationState: undefined,
|
||||
}))
|
||||
|
||||
// 合并已报名和待报名的数据
|
||||
@ -326,11 +329,6 @@ const getStatusText = (status?: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加参赛人表单验证规则
|
||||
const addParticipantRules = {
|
||||
userId: [{ required: true, message: "请选择用户", trigger: "change" }],
|
||||
}
|
||||
|
||||
// 加载活动详情
|
||||
const fetchContestDetail = async () => {
|
||||
try {
|
||||
@ -346,7 +344,7 @@ const handleAddParticipant = () => {
|
||||
}
|
||||
|
||||
// 添加参赛人确认(从组件接收选中的学生)
|
||||
const handleAddParticipantConfirm = (students: Student[]) => {
|
||||
const handleAddParticipantConfirm = (students: RegistrationCandidateUser[]) => {
|
||||
// 合并新选中的参赛人到列表(去重)
|
||||
const existingIds = new Set(selectedParticipants.value.map((p) => p.id))
|
||||
const newStudents = students.filter((s) => !existingIds.has(s.id))
|
||||
@ -387,7 +385,7 @@ const handleAddTeacherConfirm = async (teachers: any[]) => {
|
||||
try {
|
||||
// 批量添加选中的教师
|
||||
const promises = teachers.map((teacher) =>
|
||||
registrationsApi.addTeacher(currentRegistrationId.value!, teacher.userId)
|
||||
registrationsApi.addTeacher(currentRegistrationId.value!, teacher.id)
|
||||
)
|
||||
|
||||
await Promise.all(promises)
|
||||
@ -462,7 +460,7 @@ const handleSubmit = async () => {
|
||||
registrationsApi.create({
|
||||
contestId,
|
||||
registrationType: "individual",
|
||||
userId: student.userId,
|
||||
userId: student.id,
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@ -107,19 +107,19 @@
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'nickname'">
|
||||
{{ record.user?.nickname || record.nickname || "-" }}
|
||||
{{ record.nickname || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'gender'">
|
||||
{{ record.user?.gender === 'male' ? '男' : record.user?.gender === 'female' ? '女' : '-' }}
|
||||
{{ record.gender === 'male' ? '男' : record.gender === 'female' ? '女' : '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'username'">
|
||||
{{ record.user?.username || record.username || "-" }}
|
||||
{{ record.username || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'phone'">
|
||||
{{ record.user?.phone || record.phone || "-" }}
|
||||
{{ record.phone || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'tenant'">
|
||||
{{ record.user?.tenant?.name || "-" }}
|
||||
{{ record.roleNames?.join("、") || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-button type="link" size="small" @click="handleSelectLeader">
|
||||
@ -146,19 +146,19 @@
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'nickname'">
|
||||
{{ record.user?.nickname || record.nickname || "-" }}
|
||||
{{ record.nickname || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'gender'">
|
||||
{{ record.user?.gender === 'male' ? '男' : record.user?.gender === 'female' ? '女' : '-' }}
|
||||
{{ record.gender === 'male' ? '男' : record.gender === 'female' ? '女' : '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'username'">
|
||||
{{ record.user?.username || record.username || "-" }}
|
||||
{{ record.username || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'phone'">
|
||||
{{ record.user?.phone || record.phone || "-" }}
|
||||
{{ record.phone || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'tenant'">
|
||||
{{ record.user?.tenant?.name || "-" }}
|
||||
{{ record.roleNames?.join("、") || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-button type="link" size="small" danger @click="handleRemoveMember(record)">
|
||||
@ -185,23 +185,23 @@
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'nickname'">
|
||||
{{ record.user?.nickname || "-" }}
|
||||
{{ record.nickname || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'gender'">
|
||||
{{ record.gender === 1 ? '男' : record.gender === 2 ? '女' : '-' }}
|
||||
{{ record.gender === 'male' ? '男' : record.gender === 'female' ? '女' : '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'username'">
|
||||
{{ record.user?.username || "-" }}
|
||||
{{ record.username || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'phone'">
|
||||
{{ record.phone || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'tenant'">
|
||||
{{ record.department?.name || "-" }}
|
||||
{{ record.roleNames?.join("、") || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-button
|
||||
v-if="!(currentTeacher && record.userId === currentTeacher.userId)"
|
||||
v-if="!(currentTeacher && record.id === currentTeacher.id)"
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@ -271,19 +271,19 @@
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'nickname'">
|
||||
{{ record.user?.nickname || "-" }}
|
||||
{{ record.nickname || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'gender'">
|
||||
{{ record.user?.gender === 'male' ? '男' : record.user?.gender === 'female' ? '女' : '-' }}
|
||||
{{ record.gender === 'male' ? '男' : record.gender === 'female' ? '女' : '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'username'">
|
||||
{{ record.user?.username || "-" }}
|
||||
{{ record.username || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'phone'">
|
||||
{{ record.user?.phone || "-" }}
|
||||
{{ record.phone || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'tenant'">
|
||||
{{ record.user?.tenant?.name || "-" }}
|
||||
{{ record.roleNames?.join("、") || "-" }}
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
@ -334,19 +334,19 @@
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'nickname'">
|
||||
{{ record.user?.nickname || "-" }}
|
||||
{{ record.nickname || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'gender'">
|
||||
{{ record.gender === 1 ? '男' : record.gender === 2 ? '女' : '-' }}
|
||||
{{ record.gender === 'male' ? '男' : record.gender === 'female' ? '女' : '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'username'">
|
||||
{{ record.user?.username || "-" }}
|
||||
{{ record.username || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'phone'">
|
||||
{{ record.phone || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'tenant'">
|
||||
{{ record.department?.name || "-" }}
|
||||
{{ record.roleNames?.join("、") || "-" }}
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
@ -385,19 +385,22 @@ import { useRoute, useRouter } from "vue-router"
|
||||
import { message } from "ant-design-vue"
|
||||
import { PlusOutlined } from "@ant-design/icons-vue"
|
||||
import type { FormInstance, TableProps } from "ant-design-vue"
|
||||
import { teamsApi, type ContestTeam, type ContestTeamMember } from "@/api/contests"
|
||||
import { studentsApi, type Student } from "@/api/students"
|
||||
import { teachersApi, type Teacher } from "@/api/teachers"
|
||||
import {
|
||||
teamsApi,
|
||||
registrationsApi,
|
||||
type ContestTeam,
|
||||
type ContestTeamMember,
|
||||
type RegistrationCandidateUser,
|
||||
} from "@/api/contests"
|
||||
import { useAuthStore } from "@/stores/auth"
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
const tenantCode = route.params.tenantCode as string
|
||||
const contestId = Number(route.params.id)
|
||||
|
||||
// 当前登录老师信息
|
||||
const currentTeacher = ref<Teacher | null>(null)
|
||||
// 当前登录老师信息(与选人列表同为「用户 ID」维度)
|
||||
const currentTeacher = ref<RegistrationCandidateUser | null>(null)
|
||||
|
||||
// 列表状态
|
||||
const loading = ref(false)
|
||||
@ -423,15 +426,15 @@ const teamRules = {
|
||||
}
|
||||
|
||||
// 选中的成员
|
||||
const selectedLeader = ref<Student | null>(null)
|
||||
const selectedMembers = ref<Student[]>([])
|
||||
const selectedTeachers = ref<Teacher[]>([])
|
||||
const selectedLeader = ref<RegistrationCandidateUser | null>(null)
|
||||
const selectedMembers = ref<RegistrationCandidateUser[]>([])
|
||||
const selectedTeachers = ref<RegistrationCandidateUser[]>([])
|
||||
|
||||
// 选择学生弹框
|
||||
const selectStudentModalVisible = ref(false)
|
||||
const selectStudentTitle = ref("选择学生")
|
||||
const selectStudentType = ref<"leader" | "member" | "teacher">("member")
|
||||
const studentList = ref<Student[]>([])
|
||||
const studentList = ref<RegistrationCandidateUser[]>([])
|
||||
const studentListLoading = ref(false)
|
||||
const studentPagination = reactive({
|
||||
current: 1,
|
||||
@ -443,11 +446,11 @@ const studentSearchParams = reactive({
|
||||
username: "",
|
||||
})
|
||||
const selectedStudentKeys = ref<number[]>([])
|
||||
const selectedStudentRows = ref<Student[]>([])
|
||||
const selectedStudentRows = ref<RegistrationCandidateUser[]>([])
|
||||
|
||||
// 选择老师弹框
|
||||
const selectTeacherModalVisible = ref(false)
|
||||
const teacherList = ref<Teacher[]>([])
|
||||
const teacherList = ref<RegistrationCandidateUser[]>([])
|
||||
const teacherListLoading = ref(false)
|
||||
const teacherPagination = reactive({
|
||||
current: 1,
|
||||
@ -459,7 +462,7 @@ const teacherSearchParams = reactive({
|
||||
username: "",
|
||||
})
|
||||
const selectedTeacherKeys = ref<number[]>([])
|
||||
const selectedTeacherRows = ref<Teacher[]>([])
|
||||
const selectedTeacherRows = ref<RegistrationCandidateUser[]>([])
|
||||
|
||||
// 查看成员弹框
|
||||
const viewMembersModalVisible = ref(false)
|
||||
@ -517,7 +520,7 @@ const viewMemberColumns = [
|
||||
const studentRowSelection = computed<TableProps["rowSelection"]>(() => ({
|
||||
type: selectStudentType.value === "leader" ? "radio" : "checkbox",
|
||||
selectedRowKeys: selectedStudentKeys.value,
|
||||
onChange: (keys: any, rows: Student[]) => {
|
||||
onChange: (keys: any, rows: RegistrationCandidateUser[]) => {
|
||||
selectedStudentKeys.value = keys
|
||||
selectedStudentRows.value = rows
|
||||
},
|
||||
@ -536,13 +539,12 @@ const teacherSelectColumns = [
|
||||
const teacherRowSelection = computed<TableProps["rowSelection"]>(() => ({
|
||||
type: "checkbox",
|
||||
selectedRowKeys: selectedTeacherKeys.value,
|
||||
onChange: (keys: any, rows: Teacher[]) => {
|
||||
onChange: (keys: any, rows: RegistrationCandidateUser[]) => {
|
||||
selectedTeacherKeys.value = keys
|
||||
selectedTeacherRows.value = rows
|
||||
},
|
||||
getCheckboxProps: (record: Teacher) => ({
|
||||
// 当前登录老师不能取消选择
|
||||
disabled: currentTeacher.value && record.userId === currentTeacher.value.userId,
|
||||
getCheckboxProps: (record: RegistrationCandidateUser) => ({
|
||||
disabled: !!(currentTeacher.value && record.id === currentTeacher.value.id),
|
||||
}),
|
||||
}))
|
||||
|
||||
@ -582,13 +584,17 @@ const fetchTeamList = async () => {
|
||||
const fetchStudentList = async () => {
|
||||
studentListLoading.value = true
|
||||
try {
|
||||
const response = await studentsApi.getList({
|
||||
const kw = [studentSearchParams.username, studentSearchParams.nickname]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
.trim()
|
||||
const response = await registrationsApi.getCandidateUsers({
|
||||
roleCode: "student",
|
||||
keyword: kw || undefined,
|
||||
page: studentPagination.current,
|
||||
pageSize: studentPagination.pageSize,
|
||||
nickname: studentSearchParams.nickname || undefined,
|
||||
username: studentSearchParams.username || undefined,
|
||||
})
|
||||
studentList.value = response.list
|
||||
studentList.value = response.records ?? response.list ?? []
|
||||
studentPagination.total = response.total
|
||||
} catch (error) {
|
||||
message.error("获取学生列表失败")
|
||||
@ -601,13 +607,17 @@ const fetchStudentList = async () => {
|
||||
const fetchTeacherList = async () => {
|
||||
teacherListLoading.value = true
|
||||
try {
|
||||
const response = await teachersApi.getList({
|
||||
const kw = [teacherSearchParams.username, teacherSearchParams.nickname]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
.trim()
|
||||
const response = await registrationsApi.getCandidateUsers({
|
||||
roleCode: "teacher",
|
||||
keyword: kw || undefined,
|
||||
page: teacherPagination.current,
|
||||
pageSize: teacherPagination.pageSize,
|
||||
nickname: teacherSearchParams.nickname || undefined,
|
||||
username: teacherSearchParams.username || undefined,
|
||||
})
|
||||
teacherList.value = response.list
|
||||
teacherList.value = response.records ?? response.list ?? []
|
||||
teacherPagination.total = response.total
|
||||
} catch (error) {
|
||||
message.error("获取老师列表失败")
|
||||
@ -713,7 +723,7 @@ const handleSelectStudentConfirm = () => {
|
||||
if (selectStudentType.value === "leader") {
|
||||
if (selectedStudentRows.value.length > 0) {
|
||||
selectedLeader.value = selectedStudentRows.value[0]
|
||||
teamForm.leaderId = selectedLeader.value.userId
|
||||
teamForm.leaderId = selectedLeader.value.id
|
||||
}
|
||||
} else if (selectStudentType.value === "member") {
|
||||
selectedMembers.value = [...selectedStudentRows.value]
|
||||
@ -731,7 +741,7 @@ const handleSelectTeacherConfirm = () => {
|
||||
// 确保当前登录老师始终在列表中
|
||||
let teachers = [...selectedTeacherRows.value]
|
||||
if (currentTeacher.value) {
|
||||
const hasCurrentTeacher = teachers.some(t => t.userId === currentTeacher.value!.userId)
|
||||
const hasCurrentTeacher = teachers.some((t) => t.id === currentTeacher.value!.id)
|
||||
if (!hasCurrentTeacher) {
|
||||
teachers = [currentTeacher.value, ...teachers]
|
||||
}
|
||||
@ -746,18 +756,17 @@ const handleSelectTeacherCancel = () => {
|
||||
}
|
||||
|
||||
// 移除队员
|
||||
const handleRemoveMember = (record: Student) => {
|
||||
selectedMembers.value = selectedMembers.value.filter(m => m.id !== record.id)
|
||||
const handleRemoveMember = (record: RegistrationCandidateUser) => {
|
||||
selectedMembers.value = selectedMembers.value.filter((m) => m.id !== record.id)
|
||||
}
|
||||
|
||||
// 移除指导老师
|
||||
const handleRemoveTeacher = (record: Teacher) => {
|
||||
// 当前登录老师不能移除
|
||||
if (currentTeacher.value && record.userId === currentTeacher.value.userId) {
|
||||
const handleRemoveTeacher = (record: RegistrationCandidateUser) => {
|
||||
if (currentTeacher.value && record.id === currentTeacher.value.id) {
|
||||
message.warning("当前登录老师不能移除")
|
||||
return
|
||||
}
|
||||
selectedTeachers.value = selectedTeachers.value.filter(t => t.id !== record.id)
|
||||
selectedTeachers.value = selectedTeachers.value.filter((t) => t.id !== record.id)
|
||||
}
|
||||
|
||||
// 取消添加队伍
|
||||
@ -778,16 +787,16 @@ const handleTeamSubmit = async () => {
|
||||
teamSubmitLoading.value = true
|
||||
|
||||
const memberIds = [
|
||||
selectedLeader.value.userId,
|
||||
...selectedMembers.value.map(m => m.userId),
|
||||
selectedLeader.value.id,
|
||||
...selectedMembers.value.map((m) => m.id),
|
||||
]
|
||||
|
||||
const teacherIds = selectedTeachers.value.map(t => t.userId)
|
||||
const teacherIds = selectedTeachers.value.map((t) => t.id)
|
||||
|
||||
await teamsApi.create({
|
||||
contestId,
|
||||
teamName: teamForm.teamName,
|
||||
leaderId: selectedLeader.value.userId,
|
||||
leaderId: selectedLeader.value.id,
|
||||
memberIds,
|
||||
teacherIds: teacherIds.length > 0 ? teacherIds : undefined,
|
||||
})
|
||||
@ -837,19 +846,16 @@ const handleBack = () => {
|
||||
|
||||
// 获取当前登录老师信息
|
||||
const fetchCurrentTeacher = async () => {
|
||||
const userId = authStore.user?.id
|
||||
if (!userId) return
|
||||
const u = authStore.user
|
||||
if (!u?.id) return
|
||||
|
||||
// 检查当前用户是否是老师角色
|
||||
const roles = authStore.user?.roles || []
|
||||
const roles = u.roles || []
|
||||
if (!roles.includes("teacher")) return
|
||||
|
||||
try {
|
||||
const teacher = await teachersApi.getByUserId(userId)
|
||||
currentTeacher.value = teacher
|
||||
} catch (error) {
|
||||
// 可能不是老师,忽略错误
|
||||
console.log("当前用户不是老师")
|
||||
currentTeacher.value = {
|
||||
id: u.id,
|
||||
username: u.username,
|
||||
nickname: u.nickname || u.username,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 搜索/筛选区域 -->
|
||||
<div class="search-section">
|
||||
<div class="search-item">
|
||||
<span class="search-label">账号:</span>
|
||||
@ -43,27 +42,8 @@
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<span class="search-label">机构信息:</span>
|
||||
<a-select
|
||||
v-model:value="searchParams.classId"
|
||||
placeholder="年份+班级"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="cls in classOptions"
|
||||
:key="cls.id"
|
||||
:value="cls.id"
|
||||
>
|
||||
{{ cls.grade?.name }} - {{ cls.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
@ -81,21 +61,21 @@
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
{{ record.user?.nickname || "-" }}
|
||||
{{ record.nickname || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'gender'">
|
||||
<a-tag v-if="record.gender === 1" color="blue">男</a-tag>
|
||||
<a-tag v-else-if="record.gender === 2" color="pink">女</a-tag>
|
||||
<a-tag v-if="record.gender === 'male'" color="blue">男</a-tag>
|
||||
<a-tag v-else-if="record.gender === 'female'" color="pink">女</a-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'account'">
|
||||
{{ record.user?.username || "-" }}
|
||||
{{ record.username || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'contact'">
|
||||
{{ record.user?.phone || record.phone || "-" }}
|
||||
{{ record.phone || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'organization'">
|
||||
{{ record.class?.grade?.name }} - {{ record.class?.name }}
|
||||
{{ record.roleNames?.join("、") || "-" }}
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
@ -104,7 +84,7 @@
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button @click="handleCancel">取消</a-button>
|
||||
<a-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||
<a-button type="primary" @click="handleSubmit">
|
||||
确定
|
||||
</a-button>
|
||||
</a-space>
|
||||
@ -113,40 +93,37 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, onMounted } from "vue"
|
||||
import { ref, reactive, watch } from "vue"
|
||||
import { message } from "ant-design-vue"
|
||||
import { SearchOutlined } from "@ant-design/icons-vue"
|
||||
import type { TableColumnsType } from "ant-design-vue"
|
||||
import { studentsApi, type Student } from "@/api/students"
|
||||
import { classesApi, type Class } from "@/api/classes"
|
||||
import {
|
||||
registrationsApi,
|
||||
type RegistrationCandidateUser,
|
||||
} from "@/api/contests"
|
||||
import { useListRequest } from "@/composables/useListRequest"
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
registeredUserIds?: number[] // 已报名的用户ID列表
|
||||
registeredUserIds?: number[]
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: "update:open", value: boolean): void
|
||||
(e: "confirm", students: Student[]): void
|
||||
(e: "confirm", students: RegistrationCandidateUser[]): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const visible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const selectedKeys = ref<number[]>([])
|
||||
const classOptions = ref<Class[]>([])
|
||||
|
||||
// 搜索参数
|
||||
const searchParams = reactive<{
|
||||
username?: string
|
||||
nickname?: string
|
||||
classId?: number
|
||||
}>({})
|
||||
|
||||
// 列表请求
|
||||
const {
|
||||
loading,
|
||||
dataSource,
|
||||
@ -154,116 +131,75 @@ const {
|
||||
handleTableChange,
|
||||
search,
|
||||
fetchList,
|
||||
} = useListRequest<Student, { username?: string; nickname?: string; classId?: number }>({
|
||||
requestFn: studentsApi.getList,
|
||||
} = useListRequest<RegistrationCandidateUser, { username?: string; nickname?: string }>({
|
||||
requestFn: async (params) => {
|
||||
const kw = [params.username, params.nickname].filter(Boolean).join(" ").trim()
|
||||
return registrationsApi.getCandidateUsers({
|
||||
roleCode: "student",
|
||||
keyword: kw || undefined,
|
||||
page: params.page,
|
||||
pageSize: params.pageSize,
|
||||
})
|
||||
},
|
||||
defaultSearchParams: {},
|
||||
defaultPageSize: 10,
|
||||
errorMessage: "获取学生列表失败",
|
||||
immediate: false,
|
||||
})
|
||||
|
||||
// 表格列定义
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: "姓名",
|
||||
key: "name",
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: "性别",
|
||||
key: "gender",
|
||||
width: 80,
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
title: "账号",
|
||||
key: "account",
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: "联系方式",
|
||||
key: "contact",
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: "机构信息",
|
||||
key: "organization",
|
||||
width: 200,
|
||||
},
|
||||
{ title: "姓名", key: "name", width: 120 },
|
||||
{ title: "性别", key: "gender", width: 80, align: "center" },
|
||||
{ title: "账号", key: "account", width: 120 },
|
||||
{ title: "联系方式", key: "contact", width: 120 },
|
||||
{ title: "角色", key: "organization", width: 200 },
|
||||
]
|
||||
|
||||
// 监听 open 变化
|
||||
watch(
|
||||
() => props.open,
|
||||
(newVal) => {
|
||||
visible.value = newVal
|
||||
if (newVal) {
|
||||
// 打开时重置状态
|
||||
selectedKeys.value = []
|
||||
searchParams.username = ""
|
||||
searchParams.nickname = ""
|
||||
searchParams.classId = undefined
|
||||
fetchClasses()
|
||||
fetchList()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 监听 visible 变化,同步到父组件
|
||||
watch(visible, (newVal) => {
|
||||
emit("update:open", newVal)
|
||||
})
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
search({
|
||||
username: searchParams.username || undefined,
|
||||
nickname: searchParams.nickname || undefined,
|
||||
classId: searchParams.classId || undefined,
|
||||
})
|
||||
}
|
||||
|
||||
// 选择变化处理
|
||||
const handleSelectionChange = (
|
||||
selectedRowKeys: number[],
|
||||
selectedRows: Student[]
|
||||
_selectedRows: RegistrationCandidateUser[]
|
||||
) => {
|
||||
selectedKeys.value = selectedRowKeys
|
||||
}
|
||||
|
||||
// 获取复选框属性,禁用已报名的学生
|
||||
const getCheckboxProps = (record: Student) => {
|
||||
const isRegistered = props.registeredUserIds?.includes(record.userId) ?? false
|
||||
return {
|
||||
disabled: isRegistered,
|
||||
}
|
||||
const getCheckboxProps = (record: RegistrationCandidateUser) => {
|
||||
const isRegistered = props.registeredUserIds?.includes(record.id) ?? false
|
||||
return { disabled: isRegistered }
|
||||
}
|
||||
|
||||
// 加载班级列表
|
||||
const fetchClasses = async () => {
|
||||
try {
|
||||
const response = await classesApi.getList({
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
type: 1,
|
||||
})
|
||||
classOptions.value = response.list
|
||||
} catch (error) {
|
||||
console.error("获取班级列表失败:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// 提交
|
||||
const handleSubmit = () => {
|
||||
if (selectedKeys.value.length === 0) {
|
||||
message.warning("请至少选择一个参赛人")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取选中的学生
|
||||
const selectedStudents = dataSource.value.filter((student) =>
|
||||
selectedKeys.value.includes(student.id)
|
||||
const selectedStudents = dataSource.value.filter((row) =>
|
||||
selectedKeys.value.includes(row.id)
|
||||
)
|
||||
|
||||
emit("confirm", selectedStudents)
|
||||
@ -271,18 +207,12 @@ const handleSubmit = () => {
|
||||
selectedKeys.value = []
|
||||
}
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
selectedKeys.value = []
|
||||
searchParams.username = ""
|
||||
searchParams.nickname = ""
|
||||
searchParams.classId = undefined
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchClasses()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@ -338,4 +268,3 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 搜索/筛选区域 -->
|
||||
<div class="search-section">
|
||||
<div class="search-item">
|
||||
<span class="search-label">姓名:</span>
|
||||
@ -30,9 +29,9 @@
|
||||
</a-input>
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<span class="search-label">工号:</span>
|
||||
<span class="search-label">账号:</span>
|
||||
<a-input
|
||||
v-model:value="searchParams.employeeNo"
|
||||
v-model:value="searchParams.username"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
@ -45,7 +44,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-section">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
@ -63,22 +61,16 @@
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
{{ record.user?.nickname || "-" }}
|
||||
{{ record.nickname || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'username'">
|
||||
{{ record.user?.username || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'employeeNo'">
|
||||
{{ record.employeeNo || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'department'">
|
||||
{{ record.department?.name || "-" }}
|
||||
{{ record.username || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'phone'">
|
||||
{{ record.phone || record.user?.phone || "-" }}
|
||||
{{ record.phone || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'subject'">
|
||||
{{ record.subject || "-" }}
|
||||
<template v-else-if="column.key === 'roles'">
|
||||
{{ record.roleNames?.join("、") || "-" }}
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
@ -87,7 +79,7 @@
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button @click="handleCancel">取消</a-button>
|
||||
<a-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||
<a-button type="primary" @click="handleSubmit">
|
||||
确定
|
||||
</a-button>
|
||||
</a-space>
|
||||
@ -96,37 +88,37 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, computed } from "vue"
|
||||
import { ref, reactive, watch } from "vue"
|
||||
import { message } from "ant-design-vue"
|
||||
import { SearchOutlined } from "@ant-design/icons-vue"
|
||||
import type { TableColumnsType } from "ant-design-vue"
|
||||
import { teachersApi, type Teacher } from "@/api/teachers"
|
||||
import {
|
||||
registrationsApi,
|
||||
type RegistrationCandidateUser,
|
||||
} from "@/api/contests"
|
||||
import { useListRequest } from "@/composables/useListRequest"
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
selectedTeacherUserIds?: number[] // 已选择的教师用户ID列表(用于禁用已选择的)
|
||||
selectedTeacherUserIds?: number[]
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: "update:open", value: boolean): void
|
||||
(e: "confirm", teachers: Teacher[]): void
|
||||
(e: "confirm", teachers: RegistrationCandidateUser[]): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const visible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const selectedKeys = ref<number[]>([])
|
||||
|
||||
// 搜索参数
|
||||
const searchParams = reactive<{
|
||||
nickname?: string
|
||||
employeeNo?: string
|
||||
username?: string
|
||||
}>({})
|
||||
|
||||
// 列表请求
|
||||
const {
|
||||
loading,
|
||||
dataSource,
|
||||
@ -134,18 +126,15 @@ const {
|
||||
handleTableChange,
|
||||
search,
|
||||
fetchList,
|
||||
} = useListRequest<Teacher, { nickname?: string; employeeNo?: string }>({
|
||||
} = useListRequest<RegistrationCandidateUser, { nickname?: string; username?: string }>({
|
||||
requestFn: async (params) => {
|
||||
// 将搜索参数转换为API需要的格式
|
||||
const apiParams: any = {
|
||||
page: params.page || 1,
|
||||
pageSize: params.pageSize || 10,
|
||||
}
|
||||
|
||||
// 如果传入了nickname,需要通过user的nickname来搜索
|
||||
// 由于API可能不支持直接搜索nickname,这里先简单处理
|
||||
// 实际使用时可能需要后端支持或前端过滤
|
||||
return teachersApi.getList(apiParams)
|
||||
const kw = [params.nickname, params.username].filter(Boolean).join(" ").trim()
|
||||
return registrationsApi.getCandidateUsers({
|
||||
roleCode: "teacher",
|
||||
keyword: kw || undefined,
|
||||
page: params.page,
|
||||
pageSize: params.pageSize,
|
||||
})
|
||||
},
|
||||
defaultSearchParams: {},
|
||||
defaultPageSize: 10,
|
||||
@ -153,97 +142,58 @@ const {
|
||||
immediate: false,
|
||||
})
|
||||
|
||||
// 表格列定义
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: "姓名",
|
||||
key: "name",
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: "账号",
|
||||
key: "username",
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: "工号",
|
||||
key: "employeeNo",
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: "部门",
|
||||
key: "department",
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: "联系电话",
|
||||
key: "phone",
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: "任教科目",
|
||||
key: "subject",
|
||||
width: 120,
|
||||
},
|
||||
{ title: "姓名", key: "name", width: 120 },
|
||||
{ title: "账号", key: "username", width: 120 },
|
||||
{ title: "联系电话", key: "phone", width: 120 },
|
||||
{ title: "角色", key: "roles", width: 160 },
|
||||
]
|
||||
|
||||
// 监听 open 变化
|
||||
watch(
|
||||
() => props.open,
|
||||
(newVal) => {
|
||||
visible.value = newVal
|
||||
if (newVal) {
|
||||
// 打开时重置状态
|
||||
selectedKeys.value = []
|
||||
searchParams.nickname = ""
|
||||
searchParams.employeeNo = ""
|
||||
searchParams.username = ""
|
||||
fetchList()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 监听 visible 变化,同步到父组件
|
||||
watch(visible, (newVal) => {
|
||||
emit("update:open", newVal)
|
||||
})
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
// 由于API可能不支持直接搜索nickname和employeeNo
|
||||
// 这里先调用列表,实际过滤可能需要后端支持
|
||||
search({
|
||||
nickname: searchParams.nickname || undefined,
|
||||
employeeNo: searchParams.employeeNo || undefined,
|
||||
username: searchParams.username || undefined,
|
||||
})
|
||||
}
|
||||
|
||||
// 选择变化处理
|
||||
const handleSelectionChange = (
|
||||
selectedRowKeys: number[],
|
||||
selectedRows: Teacher[]
|
||||
_selectedRows: RegistrationCandidateUser[]
|
||||
) => {
|
||||
selectedKeys.value = selectedRowKeys
|
||||
}
|
||||
|
||||
// 获取复选框属性,禁用已选择的教师
|
||||
const getCheckboxProps = (record: Teacher) => {
|
||||
const isSelected = props.selectedTeacherUserIds?.includes(record.userId) ?? false
|
||||
return {
|
||||
disabled: isSelected,
|
||||
}
|
||||
const getCheckboxProps = (record: RegistrationCandidateUser) => {
|
||||
const isSelected = props.selectedTeacherUserIds?.includes(record.id) ?? false
|
||||
return { disabled: isSelected }
|
||||
}
|
||||
|
||||
// 提交
|
||||
const handleSubmit = () => {
|
||||
if (selectedKeys.value.length === 0) {
|
||||
message.warning("请至少选择一个指导老师")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取选中的教师
|
||||
const selectedTeachers = dataSource.value.filter((teacher) =>
|
||||
selectedKeys.value.includes(teacher.id)
|
||||
const selectedTeachers = dataSource.value.filter((row) =>
|
||||
selectedKeys.value.includes(row.id)
|
||||
)
|
||||
|
||||
emit("confirm", selectedTeachers)
|
||||
@ -251,12 +201,11 @@ const handleSubmit = () => {
|
||||
selectedKeys.value = []
|
||||
}
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
selectedKeys.value = []
|
||||
searchParams.nickname = ""
|
||||
searchParams.employeeNo = ""
|
||||
searchParams.username = ""
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -313,4 +262,3 @@ const handleCancel = () => {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -1,601 +0,0 @@
|
||||
<template>
|
||||
<div class="homework-page">
|
||||
<a-card class="mb-4">
|
||||
<template #title>作业管理</template>
|
||||
<template #extra>
|
||||
<a-button
|
||||
v-permission="'homework:create'"
|
||||
type="primary"
|
||||
@click="handleAdd"
|
||||
>
|
||||
<template #icon><PlusOutlined /></template>
|
||||
创建作业
|
||||
</a-button>
|
||||
</template>
|
||||
</a-card>
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<a-form
|
||||
:model="searchParams"
|
||||
layout="inline"
|
||||
class="search-form"
|
||||
@finish="handleSearch"
|
||||
>
|
||||
<a-form-item label="作业名称">
|
||||
<a-input
|
||||
v-model:value="searchParams.name"
|
||||
placeholder="请输入作业名称"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="作业状态">
|
||||
<a-select
|
||||
v-model:value="searchParams.status"
|
||||
placeholder="请选择状态"
|
||||
allow-clear
|
||||
style="width: 120px"
|
||||
>
|
||||
<a-select-option value="published">已发布</a-select-option>
|
||||
<a-select-option value="unpublished">未发布</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="提交时间">
|
||||
<a-range-picker
|
||||
v-model:value="submitTimeRange"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
style="width: 280px"
|
||||
@change="handleTimeRangeChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" html-type="submit">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="handleReset">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
{{ record.name }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-tag :color="record.status === 'published' ? 'success' : 'default'">
|
||||
{{ record.status === "published" ? "已发布" : "未发布" }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'publishScope'">
|
||||
<span v-if="record.publishScopeNames && record.publishScopeNames.length > 0">
|
||||
{{ record.publishScopeNames.join("、") }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'submitTime'">
|
||||
<div>
|
||||
{{ formatDateTime(record.submitStartTime) }} ~
|
||||
{{ formatDateTime(record.submitEndTime) }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<!-- 发布/取消发布 -->
|
||||
<a-button
|
||||
v-permission="'homework:update'"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handlePublish(record)"
|
||||
>
|
||||
{{ record.status === "published" ? "取消发布" : "发布" }}
|
||||
</a-button>
|
||||
<!-- 提交记录 -->
|
||||
<a-button
|
||||
v-permission="'homework:read'"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleViewSubmissions(record.id)"
|
||||
>
|
||||
提交记录
|
||||
</a-button>
|
||||
<!-- 编辑 -->
|
||||
<a-button
|
||||
v-permission="'homework:update'"
|
||||
type="link"
|
||||
size="small"
|
||||
:disabled="record.status === 'published'"
|
||||
@click="handleEdit(record)"
|
||||
>
|
||||
编辑
|
||||
</a-button>
|
||||
<!-- 删除 -->
|
||||
<a-popconfirm
|
||||
v-permission="'homework:delete'"
|
||||
title="确定要删除这个作业吗?"
|
||||
@confirm="handleDelete(record.id)"
|
||||
>
|
||||
<a-button type="link" danger size="small">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<!-- 创建/编辑作业抽屉 -->
|
||||
<a-drawer
|
||||
v-model:open="drawerVisible"
|
||||
:title="editingId ? '编辑作业' : '创建作业'"
|
||||
placement="right"
|
||||
width="600px"
|
||||
:footer-style="{ textAlign: 'right' }"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item label="作业名称" name="name">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入作业名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="提交时间" name="submitTimeRange">
|
||||
<a-range-picker
|
||||
v-model:value="formData.submitTimeRange"
|
||||
show-time
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="评审规则" name="reviewRuleId">
|
||||
<a-select
|
||||
v-model:value="formData.reviewRuleId"
|
||||
placeholder="请选择评审规则"
|
||||
allow-clear
|
||||
:options="reviewRuleOptions"
|
||||
:field-names="{ label: 'name', value: 'id' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="作业内容" name="content">
|
||||
<a-textarea
|
||||
v-model:value="formData.content"
|
||||
placeholder="请输入作业内容"
|
||||
:rows="6"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="附件">
|
||||
<a-upload
|
||||
v-model:file-list="formData.fileList"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="customUpload"
|
||||
@remove="handleRemoveFile"
|
||||
>
|
||||
<a-button>
|
||||
<template #icon><UploadOutlined /></template>
|
||||
上传附件
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button @click="drawerVisible = false">取消</a-button>
|
||||
<a-button type="primary" :loading="submitting" @click="handleSave">
|
||||
保存
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="!editingId"
|
||||
type="primary"
|
||||
:loading="submitting"
|
||||
@click="handleSaveAndPublish"
|
||||
>
|
||||
保存并发布
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-drawer>
|
||||
|
||||
<!-- 发布时选择公开范围 -->
|
||||
<a-modal
|
||||
v-model:open="publishModalVisible"
|
||||
title="选择公开范围"
|
||||
@ok="confirmPublish"
|
||||
@cancel="publishModalVisible = false"
|
||||
>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="公开范围" required>
|
||||
<a-tree-select
|
||||
v-model:value="publishScope"
|
||||
:tree-data="classTreeData"
|
||||
tree-checkable
|
||||
:show-checked-strategy="TreeSelect.SHOW_CHILD"
|
||||
placeholder="请选择班级"
|
||||
style="width: 100%"
|
||||
:field-names="{ label: 'name', value: 'id', children: 'children' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive } from "vue"
|
||||
import { useRouter, useRoute } from "vue-router"
|
||||
import { message, TreeSelect } from "ant-design-vue"
|
||||
import type { FormInstance, UploadProps } from "ant-design-vue"
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
ReloadOutlined,
|
||||
UploadOutlined,
|
||||
} from "@ant-design/icons-vue"
|
||||
import { useListRequest } from "@/composables/useListRequest"
|
||||
import {
|
||||
homeworksApi,
|
||||
reviewRulesApi,
|
||||
submissionsApi,
|
||||
type Homework,
|
||||
type QueryHomeworkParams,
|
||||
type HomeworkReviewRule,
|
||||
type HomeworkAttachment,
|
||||
type ClassTreeNode,
|
||||
} from "@/api/homework"
|
||||
import { uploadApi } from "@/api/upload"
|
||||
import dayjs, { Dayjs } from "dayjs"
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const tenantCode = route.params.tenantCode as string
|
||||
|
||||
// 使用列表请求组合函数
|
||||
const {
|
||||
loading,
|
||||
dataSource,
|
||||
pagination,
|
||||
searchParams,
|
||||
fetchList,
|
||||
resetSearch,
|
||||
search,
|
||||
handleTableChange,
|
||||
} = useListRequest<Homework, QueryHomeworkParams>({
|
||||
requestFn: homeworksApi.getList,
|
||||
defaultSearchParams: {} as QueryHomeworkParams,
|
||||
defaultPageSize: 10,
|
||||
errorMessage: "获取作业列表失败",
|
||||
})
|
||||
|
||||
// 时间范围
|
||||
const submitTimeRange = ref<[Dayjs, Dayjs] | null>(null)
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: "序号",
|
||||
key: "index",
|
||||
width: 60,
|
||||
customRender: ({ index }: { index: number }) => index + 1,
|
||||
},
|
||||
{
|
||||
title: "作业名称",
|
||||
key: "name",
|
||||
dataIndex: "name",
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: "作业状态",
|
||||
key: "status",
|
||||
dataIndex: "status",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "公开范围",
|
||||
key: "publishScope",
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: "提交时间",
|
||||
key: "submitTime",
|
||||
width: 350,
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
width: 280,
|
||||
fixed: "right" as const,
|
||||
},
|
||||
]
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateStr?: string) => {
|
||||
if (!dateStr) return "-"
|
||||
return dayjs(dateStr).format("YYYY-MM-DD HH:mm:ss")
|
||||
}
|
||||
|
||||
// 时间范围变化
|
||||
const handleTimeRangeChange = (dates: [Dayjs, Dayjs] | null) => {
|
||||
if (dates) {
|
||||
searchParams.submitStartTime = dates[0].format("YYYY-MM-DD")
|
||||
searchParams.submitEndTime = dates[1].format("YYYY-MM-DD")
|
||||
} else {
|
||||
searchParams.submitStartTime = undefined
|
||||
searchParams.submitEndTime = undefined
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
search()
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
const handleReset = () => {
|
||||
submitTimeRange.value = null
|
||||
resetSearch()
|
||||
}
|
||||
|
||||
// 抽屉相关
|
||||
const drawerVisible = ref(false)
|
||||
const editingId = ref<number | null>(null)
|
||||
const submitting = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const reviewRuleOptions = ref<HomeworkReviewRule[]>([])
|
||||
|
||||
const formData = reactive<{
|
||||
name: string
|
||||
content: string
|
||||
submitTimeRange: [Dayjs, Dayjs] | null
|
||||
reviewRuleId: number | undefined
|
||||
fileList: any[]
|
||||
}>({
|
||||
name: "",
|
||||
content: "",
|
||||
submitTimeRange: null,
|
||||
reviewRuleId: undefined,
|
||||
fileList: [],
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
name: [{ required: true, message: "请输入作业名称" }],
|
||||
submitTimeRange: [{ required: true, message: "请选择提交时间" }],
|
||||
}
|
||||
|
||||
// 加载评审规则
|
||||
const loadReviewRules = async () => {
|
||||
try {
|
||||
const rules = await reviewRulesApi.getForSelect()
|
||||
reviewRuleOptions.value = rules
|
||||
} catch (error) {
|
||||
console.error("加载评审规则失败", error)
|
||||
}
|
||||
}
|
||||
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
editingId.value = null
|
||||
formData.name = ""
|
||||
formData.content = ""
|
||||
formData.submitTimeRange = null
|
||||
formData.reviewRuleId = undefined
|
||||
formData.fileList = []
|
||||
drawerVisible.value = true
|
||||
loadReviewRules()
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const handleEdit = async (record: Homework) => {
|
||||
editingId.value = record.id
|
||||
formData.name = record.name
|
||||
formData.content = record.content || ""
|
||||
formData.submitTimeRange = [
|
||||
dayjs(record.submitStartTime),
|
||||
dayjs(record.submitEndTime),
|
||||
]
|
||||
formData.reviewRuleId = record.reviewRuleId
|
||||
|
||||
// 处理 attachments:可能是数组或 JSON 字符串
|
||||
let attachments: HomeworkAttachment[] = []
|
||||
if (record.attachments) {
|
||||
if (typeof record.attachments === "string") {
|
||||
try {
|
||||
attachments = JSON.parse(record.attachments)
|
||||
} catch (e) {
|
||||
console.error("解析 attachments 失败", e)
|
||||
attachments = []
|
||||
}
|
||||
} else if (Array.isArray(record.attachments)) {
|
||||
attachments = record.attachments
|
||||
}
|
||||
}
|
||||
|
||||
formData.fileList = attachments.map((att, index) => ({
|
||||
uid: `-${index}`,
|
||||
name: att.fileName,
|
||||
status: "done",
|
||||
url: att.fileUrl,
|
||||
}))
|
||||
drawerVisible.value = true
|
||||
loadReviewRules()
|
||||
}
|
||||
|
||||
// 文件上传
|
||||
const beforeUpload: UploadProps["beforeUpload"] = () => {
|
||||
return false
|
||||
}
|
||||
|
||||
const customUpload = async ({ file, onSuccess, onError }: any) => {
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append("file", file)
|
||||
const result = await uploadApi.upload(formData)
|
||||
file.url = result.url
|
||||
onSuccess(result)
|
||||
} catch (error) {
|
||||
onError(error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemoveFile = () => {
|
||||
return true
|
||||
}
|
||||
|
||||
// 保存
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
submitting.value = true
|
||||
|
||||
const attachments = formData.fileList.map((file) => ({
|
||||
fileName: file.name,
|
||||
fileUrl: file.url || file.response?.url,
|
||||
size: file.size?.toString(),
|
||||
}))
|
||||
|
||||
const submitData = {
|
||||
name: formData.name,
|
||||
content: formData.content,
|
||||
submitStartTime: formData.submitTimeRange?.[0]?.toISOString(),
|
||||
submitEndTime: formData.submitTimeRange?.[1]?.toISOString(),
|
||||
reviewRuleId: formData.reviewRuleId,
|
||||
attachments,
|
||||
}
|
||||
|
||||
if (editingId.value) {
|
||||
await homeworksApi.update(editingId.value, submitData)
|
||||
message.success("更新成功")
|
||||
} else {
|
||||
await homeworksApi.create(submitData as any)
|
||||
message.success("创建成功")
|
||||
}
|
||||
|
||||
drawerVisible.value = false
|
||||
fetchList()
|
||||
} catch (error: any) {
|
||||
if (error?.errorFields) return
|
||||
message.error(error?.response?.data?.message || "操作失败")
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存并发布
|
||||
const handleSaveAndPublish = async () => {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
submitting.value = true
|
||||
|
||||
const attachments = formData.fileList.map((file) => ({
|
||||
fileName: file.name,
|
||||
fileUrl: file.url || file.response?.url,
|
||||
size: file.size?.toString(),
|
||||
}))
|
||||
|
||||
const submitData = {
|
||||
name: formData.name,
|
||||
content: formData.content,
|
||||
submitStartTime: formData.submitTimeRange?.[0]?.toISOString(),
|
||||
submitEndTime: formData.submitTimeRange?.[1]?.toISOString(),
|
||||
reviewRuleId: formData.reviewRuleId,
|
||||
attachments,
|
||||
}
|
||||
|
||||
const created = await homeworksApi.create(submitData as any)
|
||||
currentPublishingId.value = created.id
|
||||
drawerVisible.value = false
|
||||
publishModalVisible.value = true
|
||||
loadClassTree()
|
||||
} catch (error: any) {
|
||||
if (error?.errorFields) return
|
||||
message.error(error?.response?.data?.message || "操作失败")
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 发布相关
|
||||
const publishModalVisible = ref(false)
|
||||
const publishScope = ref<number[]>([])
|
||||
const currentPublishingId = ref<number | null>(null)
|
||||
const classTreeData = ref<ClassTreeNode[]>([])
|
||||
|
||||
const loadClassTree = async () => {
|
||||
try {
|
||||
const tree = await submissionsApi.getClassTree()
|
||||
classTreeData.value = tree
|
||||
} catch (error) {
|
||||
console.error("加载班级树失败", error)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePublish = async (record: Homework) => {
|
||||
if (record.status === "published") {
|
||||
// 取消发布
|
||||
try {
|
||||
await homeworksApi.unpublish(record.id)
|
||||
message.success("取消发布成功")
|
||||
fetchList()
|
||||
} catch (error: any) {
|
||||
message.error(error?.response?.data?.message || "操作失败")
|
||||
}
|
||||
} else {
|
||||
// 发布
|
||||
currentPublishingId.value = record.id
|
||||
publishScope.value = []
|
||||
publishModalVisible.value = true
|
||||
loadClassTree()
|
||||
}
|
||||
}
|
||||
|
||||
const confirmPublish = async () => {
|
||||
if (!publishScope.value || publishScope.value.length === 0) {
|
||||
message.warning("请选择公开范围")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await homeworksApi.publish(currentPublishingId.value!, publishScope.value)
|
||||
message.success("发布成功")
|
||||
publishModalVisible.value = false
|
||||
fetchList()
|
||||
} catch (error: any) {
|
||||
message.error(error?.response?.data?.message || "发布失败")
|
||||
}
|
||||
}
|
||||
|
||||
// 查看提交记录
|
||||
const handleViewSubmissions = (id: number) => {
|
||||
router.push(`/${tenantCode}/homework/submissions?homeworkId=${id}`)
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await homeworksApi.delete(id)
|
||||
message.success("删除成功")
|
||||
fetchList()
|
||||
} catch (error: any) {
|
||||
message.error(error?.response?.data?.message || "删除失败")
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-form {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
@ -1,399 +0,0 @@
|
||||
<template>
|
||||
<div class="review-rules-page">
|
||||
<a-card class="mb-4">
|
||||
<template #title>评审规则</template>
|
||||
<template #extra>
|
||||
<a-button
|
||||
v-permission="'homework:update'"
|
||||
type="primary"
|
||||
@click="handleAdd"
|
||||
>
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新增规则
|
||||
</a-button>
|
||||
</template>
|
||||
</a-card>
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<a-form
|
||||
:model="searchParams"
|
||||
layout="inline"
|
||||
class="search-form"
|
||||
@finish="handleSearch"
|
||||
>
|
||||
<a-form-item label="规则名称">
|
||||
<a-input
|
||||
v-model:value="searchName"
|
||||
placeholder="请输入规则名称"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" html-type="submit">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="dataList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:row-selection="rowSelection"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
{{ record.name }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'description'">
|
||||
{{ record.description || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'homeworks'">
|
||||
<template v-if="record.homeworks?.length">
|
||||
<a-tag v-for="hw in record.homeworks" :key="hw.id">
|
||||
{{ hw.name }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button
|
||||
v-permission="'homework:update'"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleEdit(record)"
|
||||
>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
v-permission="'homework:update'"
|
||||
title="确定要删除这个规则吗?"
|
||||
@confirm="handleDelete(record.id)"
|
||||
>
|
||||
<a-button type="link" danger size="small">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<!-- 新增/编辑抽屉 -->
|
||||
<a-drawer
|
||||
v-model:open="drawerVisible"
|
||||
:title="editingId ? '编辑规则' : '新增规则'"
|
||||
placement="right"
|
||||
width="600px"
|
||||
>
|
||||
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
|
||||
<a-form-item label="规则名称" name="name">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入规则名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="规则描述" name="description">
|
||||
<a-textarea
|
||||
v-model:value="formData.description"
|
||||
placeholder="请输入规则描述"
|
||||
:rows="3"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="评分标准" name="criteria">
|
||||
<div class="criteria-list">
|
||||
<div
|
||||
v-for="(criterion, index) in formData.criteria"
|
||||
:key="index"
|
||||
class="criterion-item"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="criterion.name"
|
||||
placeholder="维度名称"
|
||||
style="width: 150px"
|
||||
/>
|
||||
<a-input-number
|
||||
v-model:value="criterion.maxScore"
|
||||
placeholder="满分"
|
||||
:min="1"
|
||||
style="width: 100px"
|
||||
/>
|
||||
<a-input
|
||||
v-model:value="criterion.description"
|
||||
placeholder="描述(可选)"
|
||||
style="flex: 1"
|
||||
/>
|
||||
<a-button
|
||||
type="text"
|
||||
danger
|
||||
@click="removeCriterion(index)"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</div>
|
||||
<a-button type="dashed" block @click="addCriterion">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
添加评分维度
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button @click="drawerVisible = false">取消</a-button>
|
||||
<a-button type="primary" :loading="submitting" @click="handleSave">
|
||||
保存
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, reactive } from "vue";
|
||||
import { message } from "ant-design-vue";
|
||||
import type { FormInstance } from "ant-design-vue";
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
DeleteOutlined,
|
||||
} from "@ant-design/icons-vue";
|
||||
import {
|
||||
reviewRulesApi,
|
||||
type HomeworkReviewRule,
|
||||
type ReviewCriterion,
|
||||
} from "@/api/homework";
|
||||
|
||||
// 列表数据
|
||||
const loading = ref(false);
|
||||
const dataList = ref<HomeworkReviewRule[]>([]);
|
||||
const total = ref(0);
|
||||
const searchName = ref("");
|
||||
const searchParams = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
// 分页
|
||||
const pagination = computed(() => ({
|
||||
current: searchParams.page,
|
||||
pageSize: searchParams.pageSize,
|
||||
total: total.value,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (t: number) => `共 ${t} 条记录`,
|
||||
}));
|
||||
|
||||
// 行选择
|
||||
const selectedRowKeys = ref<number[]>([]);
|
||||
const rowSelection = computed(() => ({
|
||||
selectedRowKeys: selectedRowKeys.value,
|
||||
onChange: (keys: number[]) => {
|
||||
selectedRowKeys.value = keys;
|
||||
},
|
||||
}));
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: "序号",
|
||||
key: "index",
|
||||
width: 60,
|
||||
customRender: ({ index }: { index: number }) => index + 1,
|
||||
},
|
||||
{
|
||||
title: "规则名称",
|
||||
key: "name",
|
||||
dataIndex: "name",
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: "规则描述",
|
||||
key: "description",
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: "关联作业",
|
||||
key: "homeworks",
|
||||
width: 250,
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
width: 150,
|
||||
fixed: "right" as const,
|
||||
},
|
||||
];
|
||||
|
||||
// 加载数据
|
||||
const fetchList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await reviewRulesApi.getList({
|
||||
name: searchName.value || undefined,
|
||||
page: searchParams.page,
|
||||
pageSize: searchParams.pageSize,
|
||||
});
|
||||
dataList.value = res.list;
|
||||
total.value = res.total;
|
||||
} catch (error: any) {
|
||||
message.error(error?.response?.data?.message || "获取列表失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 表格变化
|
||||
const handleTableChange = (pag: any) => {
|
||||
searchParams.page = pag.current;
|
||||
searchParams.pageSize = pag.pageSize;
|
||||
fetchList();
|
||||
};
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
searchParams.page = 1;
|
||||
fetchList();
|
||||
};
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchName.value = "";
|
||||
searchParams.page = 1;
|
||||
fetchList();
|
||||
};
|
||||
|
||||
// 抽屉相关
|
||||
const drawerVisible = ref(false);
|
||||
const editingId = ref<number | null>(null);
|
||||
const submitting = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const formData = reactive<{
|
||||
name: string;
|
||||
description: string;
|
||||
criteria: ReviewCriterion[];
|
||||
}>({
|
||||
name: "",
|
||||
description: "",
|
||||
criteria: [],
|
||||
});
|
||||
|
||||
const formRules = {
|
||||
name: [{ required: true, message: "请输入规则名称" }],
|
||||
criteria: [
|
||||
{
|
||||
validator: async () => {
|
||||
if (formData.criteria.length === 0) {
|
||||
return Promise.reject("请至少添加一个评分维度");
|
||||
}
|
||||
for (const c of formData.criteria) {
|
||||
if (!c.name || !c.maxScore) {
|
||||
return Promise.reject("请完善评分维度信息");
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
editingId.value = null;
|
||||
formData.name = "";
|
||||
formData.description = "";
|
||||
formData.criteria = [{ name: "", maxScore: 100, description: "" }];
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (record: HomeworkReviewRule) => {
|
||||
editingId.value = record.id;
|
||||
formData.name = record.name;
|
||||
formData.description = record.description || "";
|
||||
const criteria = record.criteria;
|
||||
formData.criteria =
|
||||
typeof criteria === "string" ? JSON.parse(criteria) : criteria || [];
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
|
||||
// 添加评分维度
|
||||
const addCriterion = () => {
|
||||
formData.criteria.push({ name: "", maxScore: 100, description: "" });
|
||||
};
|
||||
|
||||
// 移除评分维度
|
||||
const removeCriterion = (index: number) => {
|
||||
formData.criteria.splice(index, 1);
|
||||
};
|
||||
|
||||
// 保存
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
submitting.value = true;
|
||||
|
||||
const submitData = {
|
||||
name: formData.name,
|
||||
description: formData.description,
|
||||
criteria: formData.criteria,
|
||||
};
|
||||
|
||||
if (editingId.value) {
|
||||
await reviewRulesApi.update(editingId.value, submitData);
|
||||
message.success("更新成功");
|
||||
} else {
|
||||
await reviewRulesApi.create(submitData);
|
||||
message.success("创建成功");
|
||||
}
|
||||
|
||||
drawerVisible.value = false;
|
||||
fetchList();
|
||||
} catch (error: any) {
|
||||
if (error?.errorFields) return;
|
||||
message.error(error?.response?.data?.message || "操作失败");
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await reviewRulesApi.delete(id);
|
||||
message.success("删除成功");
|
||||
fetchList();
|
||||
} catch (error: any) {
|
||||
message.error(error?.response?.data?.message || "删除失败");
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-form {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.criteria-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.criterion-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user