Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
018c447227
@ -24,6 +24,7 @@ import xyz.playedu.common.config.UniqueNameGeneratorConfig;
|
||||
public class PlayeduApiApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("start");
|
||||
// 添加编码设置
|
||||
System.setProperty("spring.config.encoding", "UTF-8");
|
||||
ApplicationContext context=SpringApplication.run(PlayeduApiApplication.class, args);
|
||||
|
||||
@ -44,7 +44,7 @@ public class ExceptionController {
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
public JsonResponse serviceExceptionHandler(HttpMessageNotReadableException e) {
|
||||
log.error(e.getMessage());
|
||||
return JsonResponse.error("参数为空", 406);
|
||||
return JsonResponse.error("参数为空1", 406);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ -62,13 +62,13 @@ public class ExceptionController {
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public JsonResponse serviceExceptionHandler(HttpRequestMethodNotSupportedException e) {
|
||||
log.error(e.getMessage());
|
||||
return JsonResponse.error("请求method错误", 400);
|
||||
return JsonResponse.error("请求method错误1", 400);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
public JsonResponse serviceExceptionHandler(MethodArgumentTypeMismatchException e) {
|
||||
log.error(e.getMessage());
|
||||
return JsonResponse.error("请求错误", 400);
|
||||
return JsonResponse.error("请求错误1", 400);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
|
||||
@ -64,16 +64,16 @@ public class LoginController {
|
||||
}
|
||||
|
||||
String limitKey = "admin-login-limit:" + loginRequest.getEmail();
|
||||
Long reqCount = rateLimiterService.current(limitKey, 3600L);
|
||||
if (reqCount > 5 && !playEduConfig.getTesting()) {
|
||||
Long exp = RedisUtil.ttlWithoutPrefix(limitKey);
|
||||
String timeMsg =
|
||||
exp > 60
|
||||
? exp / 60 + MessageUtils.message("分钟")
|
||||
: exp + MessageUtils.message("秒");
|
||||
String msg = MessageUtils.message("您的账号已被锁定,请") + timeMsg + MessageUtils.message("后重试");
|
||||
return JsonResponse.error(msg);
|
||||
}
|
||||
// Long reqCount = rateLimiterService.current(limitKey, 3600L);
|
||||
// if (reqCount > 5 && !playEduConfig.getTesting()) {
|
||||
// Long exp = RedisUtil.ttlWithoutPrefix(limitKey);
|
||||
// String timeMsg =
|
||||
// exp > 60
|
||||
// ? exp / 60 + MessageUtils.message("分钟")
|
||||
// : exp + MessageUtils.message("秒");
|
||||
// String msg = MessageUtils.message("您的账号已被锁定,请") + timeMsg + MessageUtils.message("后重试");
|
||||
// return JsonResponse.error(msg);
|
||||
// }
|
||||
|
||||
String password =
|
||||
HelperUtil.MD5(loginRequest.getPassword() + adminUser.getSalt()).toLowerCase();
|
||||
|
||||
@ -39,6 +39,8 @@ import xyz.playedu.common.util.StringUtil;
|
||||
import xyz.playedu.exam.constants.ExamConstant;
|
||||
import xyz.playedu.exam.domain.*;
|
||||
import xyz.playedu.exam.service.*;
|
||||
import xyz.playedu.jc.domain.Knowledge;
|
||||
import xyz.playedu.jc.service.IKnowledgeService;
|
||||
import xyz.playedu.resource.service.ResourceService;
|
||||
|
||||
@RestController
|
||||
@ -62,6 +64,8 @@ public class QuestionController {
|
||||
|
||||
@Autowired private ResourceService resourceService;
|
||||
|
||||
@Autowired private IKnowledgeService knowledgeService;
|
||||
|
||||
@GetMapping("/index")
|
||||
@BackendPermission(slug = BPermissionConstant.EXAM_QUESTION)
|
||||
@Log(title = "试题-列表", businessType = BusinessTypeConstant.GET)
|
||||
@ -74,6 +78,7 @@ public class QuestionController {
|
||||
String content = MapUtils.getString(params, "content");
|
||||
Integer level = MapUtils.getInteger(params, "level");
|
||||
Integer type = MapUtils.getInteger(params, "type");
|
||||
String knowledgeCode = MapUtils.getString(params, "knowledge_code");
|
||||
|
||||
ExamQuestionPaginateFilter filter = new ExamQuestionPaginateFilter();
|
||||
filter.setSortAlgo(sortAlgo);
|
||||
@ -82,6 +87,7 @@ public class QuestionController {
|
||||
filter.setContent(content);
|
||||
filter.setLevel(level);
|
||||
filter.setType(type);
|
||||
filter.setKnowledgeCode(knowledgeCode);
|
||||
|
||||
if (!backendBus.isSuperAdmin()) { // 非超管只能读取它自己的题库
|
||||
filter.setAdminIds(backendBus.getSameDataPermissionAdminIds());
|
||||
@ -125,6 +131,7 @@ public class QuestionController {
|
||||
String content = MapUtils.getString(params, "content");
|
||||
Integer level = MapUtils.getInteger(params, "level");
|
||||
Integer type = MapUtils.getInteger(params, "type");
|
||||
String knowledgeCode = MapUtils.getString(params, "knowledge_code");
|
||||
|
||||
List<ExamQuestionCategory> questionCategories = new ArrayList<>();
|
||||
if (StringUtil.isNotNull(categoryId)) {
|
||||
@ -147,6 +154,7 @@ public class QuestionController {
|
||||
filter.setContent(content);
|
||||
filter.setLevel(level);
|
||||
filter.setType(type);
|
||||
filter.setKnowledgeCode(knowledgeCode);
|
||||
|
||||
if (!backendBus.isSuperAdmin()) { // 非超管只能读取它自己的题库
|
||||
filter.setAdminIds(backendBus.getSameDataPermissionAdminIds());
|
||||
@ -169,6 +177,7 @@ public class QuestionController {
|
||||
req.getCategoryId(),
|
||||
req.getContent().replaceAll(" ", ""),
|
||||
req.getLevel(),
|
||||
req.getKnowledgeCode(),
|
||||
req.getType(),
|
||||
BCtx.getId());
|
||||
|
||||
@ -198,6 +207,7 @@ public class QuestionController {
|
||||
examQuestion.getId(),
|
||||
req.getContent().replaceAll(" ", ""),
|
||||
req.getLevel(),
|
||||
req.getKnowledgeCode(),
|
||||
req.getType(),
|
||||
req.getCategoryId(),
|
||||
BCtx.getId());
|
||||
@ -1172,4 +1182,54 @@ public class QuestionController {
|
||||
.size());
|
||||
examQuestionCategoryService.updateById(examQuestionCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据题库ID、题型、难度获取关联的知识点列表(去重)
|
||||
* 用于随机试卷配置时的知识点筛选
|
||||
* 返回格式: [{code: "MATH001", name: "导数与微分"}, ...]
|
||||
*
|
||||
* @author menft
|
||||
*/
|
||||
@GetMapping("/knowledge-codes")
|
||||
@BackendPermission(slug = BPermissionConstant.EXAM_QUESTION)
|
||||
public JsonResponse getKnowledgeCodes(@RequestParam HashMap<String, Object> params) {
|
||||
String categoryIds = MapUtils.getString(params, "category_ids");
|
||||
Integer type = MapUtils.getInteger(params, "type");
|
||||
Integer level = MapUtils.getInteger(params, "level");
|
||||
|
||||
if (StringUtil.isEmpty(categoryIds)) {
|
||||
return JsonResponse.data(List.of());
|
||||
}
|
||||
|
||||
// 获取去重的知识点编码
|
||||
List<String> knowledgeCodes =
|
||||
examQuestionService.getDistinctKnowledgeCodes(categoryIds, type, level);
|
||||
if (knowledgeCodes.isEmpty()) {
|
||||
return JsonResponse.data(List.of());
|
||||
}
|
||||
|
||||
// 查询知识点名称
|
||||
List<Knowledge> knowledgeList = knowledgeService.getByKnowledgeCodes(knowledgeCodes);
|
||||
Map<String, String> codeToName =
|
||||
knowledgeList.stream()
|
||||
.collect(
|
||||
Collectors.toMap(
|
||||
Knowledge::getKnowledgeCode,
|
||||
Knowledge::getName,
|
||||
(v1, v2) -> v1));
|
||||
|
||||
// 构建返回结果
|
||||
List<Map<String, String>> result =
|
||||
knowledgeCodes.stream()
|
||||
.map(
|
||||
code -> {
|
||||
Map<String, String> item = new HashMap<>();
|
||||
item.put("code", code);
|
||||
item.put("name", codeToName.getOrDefault(code, code));
|
||||
return item;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return JsonResponse.data(result);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,40 @@
|
||||
package xyz.playedu.api.controller.backend.jc;
|
||||
|
||||
import jnr.ffi.annotations.In;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import xyz.playedu.api.avro.DepartmentDestroyAvro;
|
||||
import xyz.playedu.api.request.backend.DepartmentParentRequest;
|
||||
import xyz.playedu.api.request.backend.DepartmentRequest;
|
||||
import xyz.playedu.api.request.backend.DepartmentSortRequest;
|
||||
import xyz.playedu.common.annotation.BackendPermission;
|
||||
import xyz.playedu.common.annotation.Log;
|
||||
import xyz.playedu.common.constant.BPermissionConstant;
|
||||
import xyz.playedu.common.constant.BusinessTypeConstant;
|
||||
import xyz.playedu.common.constant.CommonConstant;
|
||||
import xyz.playedu.common.constant.TopicConstant;
|
||||
import xyz.playedu.common.context.BCtx;
|
||||
import xyz.playedu.common.domain.Department;
|
||||
import xyz.playedu.common.domain.User;
|
||||
import xyz.playedu.common.exception.NotFoundException;
|
||||
import xyz.playedu.common.types.JsonResponse;
|
||||
import xyz.playedu.common.util.DateUtil;
|
||||
import xyz.playedu.common.util.HelperUtil;
|
||||
import xyz.playedu.common.util.StringUtil;
|
||||
import xyz.playedu.jc.domain.BookChapter;
|
||||
import xyz.playedu.jc.domain.dto.BookChapterDTO;
|
||||
import xyz.playedu.jc.domain.dto.ChapterSortDTO;
|
||||
import xyz.playedu.jc.domain.vo.ChapterTreeVO;
|
||||
import xyz.playedu.jc.service.IBookChapterService;
|
||||
import xyz.playedu.jc.service.IBookDepartmentUserService;
|
||||
import xyz.playedu.jc.service.ITextbookService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@ -22,6 +49,10 @@ public class BookChapterController {
|
||||
|
||||
@Autowired
|
||||
private IBookChapterService bookChapterService;
|
||||
@Autowired
|
||||
private ITextbookService textbookService;
|
||||
@Autowired
|
||||
private IBookDepartmentUserService bookDepartmentUserService;
|
||||
|
||||
/** 获取某教材的章节平铺列表 */
|
||||
@GetMapping("/list")
|
||||
@ -56,11 +87,11 @@ public class BookChapterController {
|
||||
}
|
||||
|
||||
/** 删除章节(可在 Service 里做子节点/内容校验) */
|
||||
@DeleteMapping("/{id}")
|
||||
public JsonResponse delete(@PathVariable("id") Integer id) {
|
||||
bookChapterService.removeChapter(id);
|
||||
return JsonResponse.success();
|
||||
}
|
||||
// @DeleteMapping("/{id}")
|
||||
// public JsonResponse delete(@PathVariable("id") Integer id) {
|
||||
// bookChapterService.removeChapter(id);
|
||||
// return JsonResponse.success();
|
||||
// }
|
||||
|
||||
/** 拖拽排序 / 批量调整层级 */
|
||||
@PostMapping("/sort")
|
||||
@ -69,4 +100,135 @@ public class BookChapterController {
|
||||
bookChapterService.sort(bookId, sorts);
|
||||
return JsonResponse.success();
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/create")
|
||||
@Log(title = "章节-新建", businessType = BusinessTypeConstant.GET)
|
||||
public JsonResponse create(@RequestParam("bookId") Integer bookId) {
|
||||
HashMap<String, Object> data = new HashMap<>();
|
||||
// 查询所有的本地部门
|
||||
data.put("bookChapter", bookChapterService.groupByParentByBookId(bookId));
|
||||
return JsonResponse.data(data);
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@Log(title = "章节-新建", businessType = BusinessTypeConstant.INSERT)
|
||||
public JsonResponse store(@RequestBody @Validated BookChapterDTO req)
|
||||
throws NotFoundException {
|
||||
// 校验同级是否同名部门
|
||||
BookChapter existDepartment =
|
||||
bookChapterService.chunkByNameAndParentId(req.getName(), req.getParentId());
|
||||
if (StringUtil.isNotNull(existDepartment)) {
|
||||
return JsonResponse.error("章节名称已存在");
|
||||
}
|
||||
BookChapter bookChapter = new BookChapter();
|
||||
BeanUtils.copyProperties(req, bookChapter);
|
||||
bookChapterService.create(bookChapter);
|
||||
return JsonResponse.success();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Log(title = "章节-编辑", businessType = BusinessTypeConstant.GET)
|
||||
public JsonResponse edit(@PathVariable Integer id) throws NotFoundException {
|
||||
BookChapter bookChapter = bookChapterService.findOrFail(id);
|
||||
return JsonResponse.data(bookChapter);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@Log(title = "章节-编辑", businessType = BusinessTypeConstant.UPDATE)
|
||||
public JsonResponse update(@PathVariable Integer id, @RequestBody BookChapterDTO req)
|
||||
throws NotFoundException {
|
||||
BookChapter bookChapter = bookChapterService.findOrFail(id);
|
||||
// 校验同级是否同名部门
|
||||
BookChapter existDepartment =
|
||||
bookChapterService.chunkByNameAndParentId(req.getName(), req.getParentId());
|
||||
if (StringUtil.isNotNull(existDepartment) && existDepartment.getId() != id) {
|
||||
return JsonResponse.error("部门名称已存在");
|
||||
}
|
||||
bookChapterService.update(bookChapter, req.getName(), req.getParentId(), req.getSort());
|
||||
return JsonResponse.success();
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
@DeleteMapping("/{id}")
|
||||
public JsonResponse destroy(@PathVariable Integer id) throws NotFoundException {
|
||||
BookChapter department = bookChapterService.findOrFail(id);
|
||||
bookChapterService.destroy(department.getId());
|
||||
|
||||
|
||||
return JsonResponse.success();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@GetMapping("/index")
|
||||
@Log(title = "章节后台-列表", businessType = BusinessTypeConstant.GET)
|
||||
public JsonResponse index(@RequestParam HashMap<String, Object> params) {
|
||||
Integer bookId = MapUtils.getInteger(params, "bookId");
|
||||
if (bookId == null && StringUtil.isNull(bookId)) {
|
||||
return JsonResponse.error("请传入教材id");
|
||||
}
|
||||
HashMap<String, Object> data = new HashMap<>();
|
||||
// 只返回树形章节结构
|
||||
data.put("chapters", bookChapterService.groupByParentByBookId(bookId));
|
||||
return JsonResponse.data(data);
|
||||
//
|
||||
// HashMap<String, Object> data = new HashMap<>();
|
||||
// data.put("departments", bookChapterService.groupByParentByFromScene(fromScene));
|
||||
//
|
||||
// HashMap<Integer, Integer> depUserCount = new HashMap<>();
|
||||
// List<BookChapter> allDepartmentList = bookChapterService.allByFromScene(fromScene);
|
||||
// if (StringUtil.isNotEmpty(allDepartmentList)) {
|
||||
// for (BookChapter dep : allDepartmentList) {
|
||||
// List<Integer> depIds = new ArrayList<>();
|
||||
// depIds.add(dep.getId());
|
||||
// String parentChain = "";
|
||||
// if (StringUtil.isEmpty(dep.getChapterCode())) {
|
||||
// parentChain = dep.getId() + "";
|
||||
// } else {
|
||||
// parentChain = dep.getChapterCode() + "," + dep.getId();
|
||||
// }
|
||||
// // 获取所有子部门ID
|
||||
// List<BookChapter> childDepartmentList =
|
||||
// bookChapterService.getChildDepartmentsByParentChain(
|
||||
// dep.getId(), parentChain);
|
||||
// if (StringUtil.isNotEmpty(childDepartmentList)) {
|
||||
// depIds.addAll(childDepartmentList.stream().map(BookChapter::getId).toList());
|
||||
// }
|
||||
//// List<Integer> departmentUserIds = bookChapterService.getUserIdsByDepIds(depIds);
|
||||
//// depUserCount.put(
|
||||
//// dep.getId(), departmentUserIds.stream().distinct().toList().size());
|
||||
// }
|
||||
// }
|
||||
//// data.put("dep_user_count", depUserCount);
|
||||
//// data.put("user_total", userService.total());
|
||||
// return JsonResponse.data(data);
|
||||
}
|
||||
|
||||
|
||||
@PutMapping("/update/sort")
|
||||
@Log(title = "章节-更新排序", businessType = BusinessTypeConstant.UPDATE)
|
||||
public JsonResponse resort(@RequestBody @Validated DepartmentSortRequest req) {
|
||||
bookChapterService.resetSort(req.getIds());
|
||||
return JsonResponse.success();
|
||||
}
|
||||
|
||||
@PutMapping("/update/parent")
|
||||
@Log(title = "章节-更新父级", businessType = BusinessTypeConstant.UPDATE)
|
||||
public JsonResponse updateParent(@RequestBody @Validated DepartmentParentRequest req)
|
||||
throws NotFoundException {
|
||||
bookChapterService.changeParent(req.getId(), req.getParentId(), req.getIds());
|
||||
return JsonResponse.success();
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/{id}/destroy")
|
||||
@Log(title = "章节-批量删除", businessType = BusinessTypeConstant.DELETE)
|
||||
public JsonResponse preDestroy(@PathVariable Integer id) {
|
||||
HashMap<String, Object> data = new HashMap<>();
|
||||
data.put("children", bookChapterService.listByParentId(id));
|
||||
return JsonResponse.data(data);
|
||||
}
|
||||
|
||||
}
|
||||
@ -3,12 +3,24 @@ package xyz.playedu.api.controller.backend.jc;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import xyz.playedu.common.config.ServerConfig;
|
||||
import xyz.playedu.common.constant.CommonConstant;
|
||||
import xyz.playedu.common.domain.Department;
|
||||
import xyz.playedu.common.domain.User;
|
||||
import xyz.playedu.common.domain.UserGroup;
|
||||
import xyz.playedu.common.types.JsonResponse;
|
||||
import xyz.playedu.common.types.paginate.PaginationResult;
|
||||
import xyz.playedu.common.util.StringUtil;
|
||||
import xyz.playedu.common.util.StringUtils;
|
||||
import xyz.playedu.course.constants.CourseConstant;
|
||||
import xyz.playedu.jc.domain.BookDepartmentUser;
|
||||
import xyz.playedu.jc.domain.JCResource;
|
||||
import xyz.playedu.jc.domain.Textbook;
|
||||
import xyz.playedu.jc.domain.dto.ResourcePageRequestDTO;
|
||||
import xyz.playedu.jc.service.JCIResourceService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/backend/v1/jc/resource")
|
||||
@ -16,6 +28,28 @@ public class JCResourceController {
|
||||
|
||||
@Autowired
|
||||
private JCIResourceService resourceService;
|
||||
@Autowired
|
||||
private ServerConfig serverConfig;
|
||||
|
||||
@GetMapping("/index")
|
||||
public JsonResponse index(@RequestParam HashMap<String, Object> params) {
|
||||
|
||||
/** 调用服务层分页查询方法,Service 层已包含完整的分页信息 */
|
||||
PaginationResult<JCResource> result = resourceService.paginate(params);
|
||||
HashMap<String, Object> data = new HashMap<>();
|
||||
data.put("data", result.getData());
|
||||
data.put("total", result.getTotal());
|
||||
|
||||
String url = serverConfig.getUrl();
|
||||
|
||||
result.getData()
|
||||
.forEach(item -> item.setAllUrl(url+item.getPath()));
|
||||
result.getData()
|
||||
.forEach(item -> item.setUrl(url));
|
||||
|
||||
/** 直接返回 Service 层的结果 */
|
||||
return JsonResponse.data(data);
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public JsonResponse list(@RequestParam(value = "bookId", required = false) Integer bookId,
|
||||
@ -50,9 +84,25 @@ public class JCResourceController {
|
||||
}
|
||||
|
||||
/** 删除资源 */
|
||||
@DeleteMapping("/{id}")
|
||||
public JsonResponse delete(@PathVariable("id") Integer id) {
|
||||
resourceService.removeById(id);
|
||||
// @DeleteMapping("/{id}")
|
||||
// public JsonResponse delete(@PathVariable("id") Integer id) {
|
||||
// resourceService.removeById(id);
|
||||
// return JsonResponse.success();
|
||||
// }
|
||||
|
||||
|
||||
/** 批量删除资源 */
|
||||
@DeleteMapping("/delIds")
|
||||
public JsonResponse delete(@RequestParam(value = "idList", required = false) String idListStr) {
|
||||
if (StringUtils.isNotBlank(idListStr)) {
|
||||
List<Integer> idList = Arrays.stream(idListStr.split(","))
|
||||
.map(String::trim)
|
||||
.filter(str -> !str.isEmpty())
|
||||
.map(Integer::parseInt)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
resourceService.removeByIds(idList);
|
||||
}
|
||||
return JsonResponse.success();
|
||||
}
|
||||
|
||||
|
||||
@ -72,4 +72,20 @@ public class KnowledgeController {
|
||||
knowledgeService.remove(queryWrapper);
|
||||
return JsonResponse.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据知识点编码列表获取知识点详情(用于编辑回显)
|
||||
* @param codes 逗号分隔的知识点编码
|
||||
*/
|
||||
@GetMapping("/byCodes")
|
||||
public JsonResponse getByCodes(@RequestParam("codes") String codes) {
|
||||
if (codes == null || codes.trim().isEmpty()) {
|
||||
return JsonResponse.data(List.of());
|
||||
}
|
||||
String[] codeArray = codes.split(",");
|
||||
LambdaQueryWrapper<Knowledge> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.in(Knowledge::getKnowledgeCode, (Object[]) codeArray);
|
||||
List<Knowledge> list = knowledgeService.list(queryWrapper);
|
||||
return JsonResponse.data(list);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,8 +8,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import xyz.playedu.common.annotation.Log;
|
||||
import xyz.playedu.common.config.ServerConfig;
|
||||
import xyz.playedu.common.constant.BusinessTypeConstant;
|
||||
import xyz.playedu.common.constant.CommonConstant;
|
||||
import xyz.playedu.common.context.FCtx;
|
||||
import xyz.playedu.common.domain.Department;
|
||||
import xyz.playedu.common.domain.Group;
|
||||
import xyz.playedu.common.domain.User;
|
||||
@ -17,11 +19,14 @@ import xyz.playedu.common.domain.UserGroup;
|
||||
import xyz.playedu.common.exception.NotFoundException;
|
||||
import xyz.playedu.common.service.*;
|
||||
import xyz.playedu.common.types.JsonResponse;
|
||||
import xyz.playedu.common.types.mapper.UserCourseHourRecordCourseCountMapper;
|
||||
import xyz.playedu.common.types.paginate.PaginationResult;
|
||||
import xyz.playedu.common.util.StringUtil;
|
||||
import xyz.playedu.course.constants.CourseConstant;
|
||||
import xyz.playedu.course.domain.Course;
|
||||
import xyz.playedu.course.domain.CourseDepartmentUser;
|
||||
import xyz.playedu.course.domain.CourseHour;
|
||||
import xyz.playedu.course.domain.UserCourseRecord;
|
||||
import xyz.playedu.jc.domain.BookDepartmentUser;
|
||||
import xyz.playedu.jc.domain.JCResource;
|
||||
import xyz.playedu.jc.domain.Textbook;
|
||||
@ -58,7 +63,8 @@ public class TextbookController {
|
||||
@Autowired private UserService userService;
|
||||
@Autowired private GroupService groupService;
|
||||
|
||||
|
||||
@Autowired
|
||||
private ServerConfig serverConfig;
|
||||
@GetMapping("/index")
|
||||
public JsonResponse index(@RequestParam HashMap<String, Object> params) {
|
||||
|
||||
@ -68,24 +74,16 @@ public class TextbookController {
|
||||
data.put("data", result.getData());
|
||||
data.put("total", result.getTotal());
|
||||
|
||||
// 课程封面资源ID
|
||||
List<Integer> rids = new ArrayList<>();
|
||||
rids.addAll(result.getData().stream().map(Textbook::getThumb).toList());
|
||||
String url = serverConfig.getUrl();
|
||||
|
||||
result.getData()
|
||||
.forEach(item -> item.setAllUrl(url+item.getThumb()));
|
||||
result.getData()
|
||||
.forEach(item -> item.setUrl(url));
|
||||
|
||||
|
||||
|
||||
List<Integer> bookIds = result.getData().stream().map(Textbook::getId).toList();
|
||||
// data.put("course_category_ids", courseService.getCategoryIdsGroup(courseIds));
|
||||
// data.put("categories", categoryService.id2name());
|
||||
// data.put("departments", departmentService.id2name());
|
||||
|
||||
// Map<Integer, Integer> courseIdRecordCountMap = new HashMap<>();
|
||||
// doGetRecords(courseIds)
|
||||
// .forEach(
|
||||
// (key, value) -> {
|
||||
// courseIdRecordCountMap.put(key, value.size());
|
||||
// });
|
||||
// data.put("records", courseIdRecordCountMap);
|
||||
// 获取签名url
|
||||
data.put("resource_url", jciResourceService.chunksPreSignUrlByIds(rids));
|
||||
|
||||
// 指派范围
|
||||
Map<Integer, Integer> book_user_count = new HashMap<>();
|
||||
@ -203,13 +201,17 @@ public class TextbookController {
|
||||
public JsonResponse edit(@PathVariable(name = "id") Integer id) throws NotFoundException {
|
||||
Textbook textbook = textbookService.findOrFail(id);
|
||||
|
||||
String url = serverConfig.getUrl();
|
||||
textbook.setAllUrl(url + textbook.getThumb());
|
||||
textbook.setUrl(url);
|
||||
|
||||
HashMap<String, Object> data = new HashMap<>();
|
||||
data.put("textbook", textbook);
|
||||
|
||||
List<Integer> rids = new ArrayList<>();
|
||||
rids.add(textbook.getThumb());
|
||||
// 获取签名url
|
||||
data.put("resource_url", jciResourceService.chunksPreSignUrlByIds(rids));
|
||||
// List<Integer> rids = new ArrayList<>();
|
||||
// rids.add(textbook.getThumb());
|
||||
// 获取签名url
|
||||
// data.put("resource_url", jciResourceService.chunksPreSignUrlByIds(rids));
|
||||
|
||||
// 指派范围
|
||||
List<BookDepartmentUser> courseDepartmentUserList =
|
||||
@ -273,6 +275,22 @@ public class TextbookController {
|
||||
return JsonResponse.data(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取教材下拉选择列表(轻量级,仅返回id和title)
|
||||
*/
|
||||
@GetMapping("/selectList")
|
||||
public JsonResponse selectList() {
|
||||
List<Textbook> list = textbookService.list();
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (Textbook textbook : list) {
|
||||
Map<String, Object> item = new HashMap<>();
|
||||
item.put("id", textbook.getId());
|
||||
item.put("title", textbook.getTitle());
|
||||
result.add(item);
|
||||
}
|
||||
return JsonResponse.data(result);
|
||||
}
|
||||
|
||||
// @GetMapping("/{id}")
|
||||
// public JsonResponse detail(@PathVariable("id") Integer id) {
|
||||
// Textbook one = textbookService.getById(id);
|
||||
@ -347,4 +365,4 @@ public class TextbookController {
|
||||
textbookService.updateById(textbook);
|
||||
return JsonResponse.success();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,12 +45,15 @@ public class LocalFileController {
|
||||
// 上传并返回新文件名称
|
||||
String fileName = FileUploadUtils.upload(filePath, file);
|
||||
try {
|
||||
long size = file.getSize(); // 这里拿到文件大小,单位 bytes
|
||||
|
||||
String url = serverConfig.getUrl() + fileName;
|
||||
JSONObject ajax = new JSONObject();
|
||||
ajax.put("url", url);
|
||||
ajax.put("path", fileName);
|
||||
ajax.put("newFileName", FileUploadUtils.getName(fileName));
|
||||
ajax.put("originalFilename", file.getOriginalFilename());
|
||||
ajax.put("size", size);
|
||||
return JsonResponse.data(ajax);
|
||||
} catch (Exception e) {
|
||||
return JsonResponse.error(e.getMessage());
|
||||
|
||||
@ -961,41 +961,53 @@ public class PaperController {
|
||||
JSONObject source = randomRules.getJSONObject("source");
|
||||
List<Integer> categoryIds = source.getBeanList("category_ids", Integer.class);
|
||||
filter.setCategoryIds(categoryIds);
|
||||
// 题型、数量
|
||||
// 题型、数量、难度、知识点
|
||||
JSONObject score = randomRules.getJSONObject("score");
|
||||
JSONObject type1 = score.getJSONObject(ExamConstant.TYPE_1 + "");
|
||||
if (StringUtil.isNotNull(type1)) {
|
||||
filter.setType1Number(type1.getInt("number"));
|
||||
filter.setType1Level(type1.getInt("level"));
|
||||
filter.setType1KnowledgeCodes(type1.getStr("knowledge_codes"));
|
||||
} else {
|
||||
filter.setType1Number(0);
|
||||
}
|
||||
JSONObject type2 = score.getJSONObject(ExamConstant.TYPE_2 + "");
|
||||
if (StringUtil.isNotNull(type2)) {
|
||||
filter.setType2Number(type2.getInt("number"));
|
||||
filter.setType2Level(type2.getInt("level"));
|
||||
filter.setType2KnowledgeCodes(type2.getStr("knowledge_codes"));
|
||||
} else {
|
||||
filter.setType2Number(0);
|
||||
}
|
||||
JSONObject type3 = score.getJSONObject(ExamConstant.TYPE_3 + "");
|
||||
if (StringUtil.isNotNull(type3)) {
|
||||
filter.setType3Number(type3.getInt("number"));
|
||||
filter.setType3Level(type3.getInt("level"));
|
||||
filter.setType3KnowledgeCodes(type3.getStr("knowledge_codes"));
|
||||
} else {
|
||||
filter.setType3Number(0);
|
||||
}
|
||||
JSONObject type4 = score.getJSONObject(ExamConstant.TYPE_4 + "");
|
||||
if (StringUtil.isNotNull(type4)) {
|
||||
filter.setType4Number(type4.getInt("number"));
|
||||
filter.setType4Level(type4.getInt("level"));
|
||||
filter.setType4KnowledgeCodes(type4.getStr("knowledge_codes"));
|
||||
} else {
|
||||
filter.setType4Number(0);
|
||||
}
|
||||
JSONObject type5 = score.getJSONObject(ExamConstant.TYPE_5 + "");
|
||||
if (StringUtil.isNotNull(type5)) {
|
||||
filter.setType5Number(type5.getInt("number"));
|
||||
filter.setType5Level(type5.getInt("level"));
|
||||
filter.setType5KnowledgeCodes(type5.getStr("knowledge_codes"));
|
||||
} else {
|
||||
filter.setType5Number(0);
|
||||
}
|
||||
JSONObject type6 = score.getJSONObject(ExamConstant.TYPE_6 + "");
|
||||
if (StringUtil.isNotNull(type6)) {
|
||||
filter.setType6Number(type6.getInt("number"));
|
||||
filter.setType6Level(type6.getInt("level"));
|
||||
filter.setType6KnowledgeCodes(type6.getStr("knowledge_codes"));
|
||||
} else {
|
||||
filter.setType6Number(0);
|
||||
}
|
||||
|
||||
@ -5,9 +5,11 @@ package xyz.playedu.api.controller.frontend;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
@ -17,12 +19,15 @@ import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import xyz.playedu.api.request.frontend.ChangePasswordRequest;
|
||||
import xyz.playedu.common.annotation.Log;
|
||||
import xyz.playedu.common.caches.CurrentDepIdCache;
|
||||
import xyz.playedu.common.caches.LoginCreditCache;
|
||||
import xyz.playedu.common.constant.BusinessTypeConstant;
|
||||
import xyz.playedu.common.constant.CommonConstant;
|
||||
import xyz.playedu.common.constant.FrontendConstant;
|
||||
import xyz.playedu.common.context.FCtx;
|
||||
import xyz.playedu.common.domain.*;
|
||||
import xyz.playedu.common.exception.NotFoundException;
|
||||
import xyz.playedu.common.exception.ServiceException;
|
||||
import xyz.playedu.common.service.*;
|
||||
import xyz.playedu.common.types.JsonResponse;
|
||||
@ -40,6 +45,10 @@ import xyz.playedu.exam.service.ExamTaskDepartmentUserService;
|
||||
import xyz.playedu.exam.service.ExamTaskUserService;
|
||||
import xyz.playedu.exam.service.StudyTaskDepartmentUserService;
|
||||
import xyz.playedu.exam.service.StudyTaskUserService;
|
||||
import xyz.playedu.jc.domain.Textbook;
|
||||
import xyz.playedu.jc.domain.dto.TextbookUserDTO;
|
||||
import xyz.playedu.jc.service.IBookChapterService;
|
||||
import xyz.playedu.jc.service.ITextbookService;
|
||||
import xyz.playedu.resource.domain.Resource;
|
||||
import xyz.playedu.resource.service.ResourceService;
|
||||
import xyz.playedu.resource.service.UploadService;
|
||||
@ -49,47 +58,68 @@ import xyz.playedu.resource.service.UploadService;
|
||||
@Slf4j
|
||||
public class UserController {
|
||||
|
||||
@Autowired private UserService userService;
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired private DepartmentService departmentService;
|
||||
@Autowired
|
||||
private DepartmentService departmentService;
|
||||
|
||||
@Autowired private CourseService courseService;
|
||||
@Autowired
|
||||
private CourseService courseService;
|
||||
|
||||
@Autowired private CourseHourService hourService;
|
||||
@Autowired
|
||||
private CourseHourService hourService;
|
||||
|
||||
@Autowired private UserCourseRecordService userCourseRecordService;
|
||||
@Autowired
|
||||
private UserCourseRecordService userCourseRecordService;
|
||||
|
||||
@Autowired private UserCourseHourRecordService userCourseHourRecordService;
|
||||
@Autowired
|
||||
private UserCourseHourRecordService userCourseHourRecordService;
|
||||
|
||||
@Autowired private UserLearnDurationStatsService userLearnDurationStatsService;
|
||||
@Autowired
|
||||
private UserLearnDurationStatsService userLearnDurationStatsService;
|
||||
|
||||
@Autowired private UploadService uploadService;
|
||||
@Autowired
|
||||
private UploadService uploadService;
|
||||
|
||||
@Autowired private CertUserService certUserService;
|
||||
@Autowired
|
||||
private CertUserService certUserService;
|
||||
|
||||
@Autowired private CertService certService;
|
||||
@Autowired
|
||||
private CertService certService;
|
||||
|
||||
@Autowired private ExamTaskUserService examTaskUserService;
|
||||
@Autowired
|
||||
private ExamTaskUserService examTaskUserService;
|
||||
|
||||
@Autowired private StudyTaskUserService studyTaskUserService;
|
||||
@Autowired
|
||||
private StudyTaskUserService studyTaskUserService;
|
||||
|
||||
@Autowired private ResourceService resourceService;
|
||||
@Autowired
|
||||
private ResourceService resourceService;
|
||||
|
||||
@Autowired private UserUploadImageLogService userUploadImageLogService;
|
||||
@Autowired
|
||||
private UserUploadImageLogService userUploadImageLogService;
|
||||
|
||||
@Autowired private AppConfigService appConfigService;
|
||||
@Autowired
|
||||
private AppConfigService appConfigService;
|
||||
|
||||
@Autowired private CurrentDepIdCache currentDepIdCache;
|
||||
@Autowired
|
||||
private CurrentDepIdCache currentDepIdCache;
|
||||
|
||||
@Autowired private ExamTaskDepartmentUserService examTaskDepartmentUserService;
|
||||
@Autowired
|
||||
private ExamTaskDepartmentUserService examTaskDepartmentUserService;
|
||||
|
||||
@Autowired private StudyTaskDepartmentUserService studyTaskDepartmentUserService;
|
||||
@Autowired
|
||||
private StudyTaskDepartmentUserService studyTaskDepartmentUserService;
|
||||
|
||||
@Autowired private CreditRuleService creditRuleService;
|
||||
@Autowired
|
||||
private CreditRuleService creditRuleService;
|
||||
|
||||
@Autowired private UserCreditDetailService userCreditDetailService;
|
||||
@Autowired
|
||||
private UserCreditDetailService userCreditDetailService;
|
||||
|
||||
@Autowired private LoginCreditCache loginCreditCache;
|
||||
@Autowired
|
||||
private LoginCreditCache loginCreditCache;
|
||||
|
||||
@GetMapping("/detail")
|
||||
public JsonResponse detail() {
|
||||
@ -613,4 +643,112 @@ public class UserController {
|
||||
}
|
||||
return JsonResponse.data(data);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private ITextbookService textbookService;
|
||||
|
||||
@GetMapping("/userByTextBook")
|
||||
public JsonResponse userByTextBook(@RequestParam HashMap<String, Object> params) throws NotFoundException {
|
||||
Integer depId = MapUtils.getInteger(params, "dep_id");
|
||||
if (depId == null || depId == 0) {
|
||||
return JsonResponse.error("部门为空");
|
||||
}
|
||||
|
||||
// Integer categoryId = MapUtils.getInteger(params, "category_id");
|
||||
|
||||
String name = MapUtils.getString(params, "name");
|
||||
String publishTime = MapUtils.getString(params, "publishTime");
|
||||
String major = MapUtils.getString(params, "major");
|
||||
|
||||
List<Integer> userJoinDepIds = userService.getDepIdsByUserId(FCtx.getId());
|
||||
if (userJoinDepIds == null) {
|
||||
return JsonResponse.error("当前学员未加入任何部门");
|
||||
}
|
||||
if (!userJoinDepIds.contains(depId)) {
|
||||
return JsonResponse.error("当前学员未加入所选择部门");
|
||||
}
|
||||
|
||||
HashMap<String, Object> data = new HashMap<>();
|
||||
data.put("learn_course_records", new HashMap<>());
|
||||
|
||||
// 获取所有部门ID
|
||||
List<Integer> depIds = new ArrayList<>();
|
||||
depIds.add(depId);
|
||||
Department department = departmentService.findOrFail(depId);
|
||||
String parentChain = department.getParentChain();
|
||||
if (StringUtil.isNotEmpty(parentChain)) {
|
||||
List<Integer> parentChainList =
|
||||
Arrays.stream(parentChain.split(",")).map(Integer::parseInt).toList();
|
||||
if (StringUtil.isNotEmpty(parentChainList)) {
|
||||
depIds.addAll(parentChainList);
|
||||
}
|
||||
}
|
||||
List<Integer> userIds = new ArrayList<>();
|
||||
userIds.add(FCtx.getId());
|
||||
TextbookUserDTO textbookUserDTO = new TextbookUserDTO();
|
||||
textbookUserDTO.setUserIds(userIds);
|
||||
textbookUserDTO.setDepIds(depIds);
|
||||
textbookUserDTO.setPublishTime(publishTime);
|
||||
textbookUserDTO.setMajor(major);
|
||||
textbookUserDTO.setName(name);
|
||||
// -------- 读取当前学员可以参加的课程 ----------
|
||||
List<Textbook> courses = new ArrayList<>();
|
||||
// 读取部门课
|
||||
List<Textbook> depCourses =
|
||||
textbookService.getDepCoursesAndShow(
|
||||
textbookUserDTO);
|
||||
|
||||
// 汇总到一个list中
|
||||
if (depCourses != null && !depCourses.isEmpty()) {
|
||||
courses.addAll(depCourses);
|
||||
}
|
||||
// 对结果进行去重、排序->按照id去重、排序时间倒序
|
||||
if (!courses.isEmpty()) {
|
||||
courses =
|
||||
courses.stream()
|
||||
.collect(
|
||||
Collectors.collectingAndThen(
|
||||
Collectors.toCollection(
|
||||
() ->
|
||||
new TreeSet<>(
|
||||
Comparator.comparing(
|
||||
Textbook::getId))),
|
||||
ArrayList::new))
|
||||
.stream()
|
||||
.sorted(
|
||||
Comparator.comparing(
|
||||
(Textbook c) ->
|
||||
c.getCreateTime() != null
|
||||
? c.getPublishTime()
|
||||
: new Date(0))
|
||||
.reversed())
|
||||
.toList();
|
||||
}
|
||||
|
||||
data.put("textbook", courses);
|
||||
|
||||
// List<Integer> courseIds = courses.stream().map(Textbook::getId).toList();
|
||||
|
||||
|
||||
return JsonResponse.data(data);
|
||||
}
|
||||
@Autowired
|
||||
private IBookChapterService bookChapterService;
|
||||
@GetMapping("/index")
|
||||
@Log(title = "章节后台-列表", businessType = BusinessTypeConstant.GET)
|
||||
public JsonResponse index(@RequestParam HashMap<String, Object> params) {
|
||||
Integer bookId = MapUtils.getInteger(params, "bookId");
|
||||
if (bookId == null && StringUtil.isNull(bookId)) {
|
||||
return JsonResponse.error("请传入教材id");
|
||||
}
|
||||
HashMap<String, Object> data = new HashMap<>();
|
||||
// 只返回树形章节结构
|
||||
data.put("chapters", bookChapterService.groupByParentByBookId(bookId));
|
||||
return JsonResponse.data(data);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -9,7 +9,10 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import xyz.playedu.common.config.PlatformConfig;
|
||||
import xyz.playedu.common.util.FileUploadUtils;
|
||||
|
||||
@Configuration
|
||||
@Slf4j
|
||||
@ -36,4 +39,17 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
||||
.allowedHeaders("*")
|
||||
.maxAge(1_296_000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
// profile = platform.config.profile,比如:/data/platform
|
||||
String profile = PlatformConfig.getProfile();
|
||||
if (!profile.endsWith("/")) {
|
||||
profile = profile + "/";
|
||||
}
|
||||
|
||||
// /profile/** 访问的是磁盘上的 profile 目录
|
||||
registry.addResourceHandler(FileUploadUtils.RESOURCE_PREFIX + "/**")
|
||||
.addResourceLocations("file:" + profile);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,9 @@ public class ExamQuestionRequest implements Serializable {
|
||||
@NotNull(message = "level参数为空")
|
||||
private Integer level;
|
||||
|
||||
@JsonProperty("knowledge_code")
|
||||
private String knowledgeCode;
|
||||
|
||||
@NotNull(message = "type参数为空")
|
||||
private Integer type;
|
||||
}
|
||||
|
||||
@ -12,14 +12,26 @@ public class ExamQuestionFilter {
|
||||
private List<Integer> categoryIds;
|
||||
|
||||
private Integer type1Number;
|
||||
private Integer type1Level;
|
||||
private String type1KnowledgeCodes;
|
||||
|
||||
private Integer type2Number;
|
||||
private Integer type2Level;
|
||||
private String type2KnowledgeCodes;
|
||||
|
||||
private Integer type3Number;
|
||||
private Integer type3Level;
|
||||
private String type3KnowledgeCodes;
|
||||
|
||||
private Integer type4Number;
|
||||
private Integer type4Level;
|
||||
private String type4KnowledgeCodes;
|
||||
|
||||
private Integer type5Number;
|
||||
private Integer type5Level;
|
||||
private String type5KnowledgeCodes;
|
||||
|
||||
private Integer type6Number;
|
||||
private Integer type6Level;
|
||||
private String type6KnowledgeCodes;
|
||||
}
|
||||
|
||||
@ -17,6 +17,8 @@ public class ExamQuestionPaginateFilter {
|
||||
|
||||
private String categoryId;
|
||||
|
||||
private String knowledgeCode;
|
||||
|
||||
private List<Integer> adminIds;
|
||||
|
||||
private String sortField;
|
||||
|
||||
@ -75,4 +75,9 @@ public class JCResource {
|
||||
|
||||
@TableField("tenant_id")
|
||||
private String tenantId;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String url;
|
||||
@TableField(exist = false)
|
||||
private String allUrl;
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import xyz.playedu.framework.tenant.core.db.TenantBaseDO;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@ -38,7 +37,7 @@ public class Knowledge extends TenantBaseDO {
|
||||
private String knowledgeCode;
|
||||
|
||||
/** 知识点介绍 */
|
||||
@TableField("desc")
|
||||
@TableField("`desc`")
|
||||
private String desc;
|
||||
|
||||
/** 层级 */
|
||||
|
||||
@ -26,7 +26,7 @@ public class Textbook extends TenantBaseDO {
|
||||
|
||||
/** 封面地址 */
|
||||
@TableField("thumb")
|
||||
private Integer thumb;
|
||||
private String thumb;
|
||||
|
||||
/** 简介 */
|
||||
@TableField("short_desc")
|
||||
@ -54,5 +54,9 @@ public class Textbook extends TenantBaseDO {
|
||||
/** 发布时间 */
|
||||
@TableField("publish_time")
|
||||
private Date publishTime;
|
||||
@TableField(exist = false)
|
||||
private String url;
|
||||
@TableField(exist = false)
|
||||
private String allUrl;
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2023 杭州白书科技有限公司
|
||||
*/
|
||||
package xyz.playedu.jc.domain.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @Author 杭州白书科技有限公司
|
||||
*
|
||||
* @create 2023/2/19 10:42
|
||||
*/
|
||||
@Data
|
||||
public class BookChapterDTO implements Serializable {
|
||||
@Serial private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotBlank(message = "name参数为空")
|
||||
@Length(min = 1, max = 64, message = "名称长度在1-64个字符之间")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("parentId")
|
||||
@NotNull(message = "parentId参数为空")
|
||||
private Integer parentId;
|
||||
|
||||
@JsonProperty("bookId")
|
||||
@NotNull(message = "bookId参数为空")
|
||||
private Integer bookId;
|
||||
|
||||
@NotNull(message = "sort参数为空")
|
||||
private Integer sort;
|
||||
}
|
||||
@ -26,7 +26,7 @@ public class TextbookRequestDTO {
|
||||
|
||||
/** 封面地址 */
|
||||
@TableField("thumb")
|
||||
private Integer thumb;
|
||||
private String thumb;
|
||||
|
||||
/** 简介 */
|
||||
@TableField("short_desc")
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
package xyz.playedu.jc.domain.dto;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* 对应表:
|
||||
*/
|
||||
@Data
|
||||
public class TextbookUserDTO {
|
||||
|
||||
|
||||
// /** 教材名称 */
|
||||
// @TableField("title")
|
||||
// private String title;
|
||||
//
|
||||
// /** 封面地址 */
|
||||
// @TableField("thumb")
|
||||
// private String thumb;
|
||||
|
||||
/** 简介 */
|
||||
// @TableField("short_desc")
|
||||
// private String shortDesc;
|
||||
|
||||
/** 学科专业信息 */
|
||||
@TableField("major")
|
||||
private String major;
|
||||
|
||||
// /** 作者 */
|
||||
// @TableField("author")
|
||||
// private String author;
|
||||
//
|
||||
// /** ISBN 或教材编号 */
|
||||
// @TableField("isbn")
|
||||
// private String isbn;
|
||||
|
||||
|
||||
/** 出版社 */
|
||||
// @TableField("publish_unit")
|
||||
// private String publishUnit;
|
||||
|
||||
/** 发布时间 */
|
||||
private String publishTime;
|
||||
|
||||
private String name;
|
||||
|
||||
private List<Integer> depIds;
|
||||
|
||||
private List<Integer> userIds;
|
||||
|
||||
}
|
||||
@ -1,7 +1,12 @@
|
||||
package xyz.playedu.jc.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import xyz.playedu.course.domain.CourseDepartmentUser;
|
||||
import xyz.playedu.jc.domain.BookDepartmentUser;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface BookDepartmentUserMapper extends BaseMapper<BookDepartmentUser> {
|
||||
List<BookDepartmentUser> chunksByDepIdsOrUserIdsOrGroupIds(
|
||||
List<Integer> depIds, List<Integer> userIds, List<Integer> groupIds);
|
||||
}
|
||||
@ -1,11 +1,14 @@
|
||||
package xyz.playedu.jc.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import xyz.playedu.common.domain.Department;
|
||||
import xyz.playedu.common.exception.NotFoundException;
|
||||
import xyz.playedu.jc.domain.BookChapter;
|
||||
import xyz.playedu.jc.domain.dto.ChapterSortDTO;
|
||||
import xyz.playedu.jc.domain.vo.ChapterTreeVO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 教材章节 Service
|
||||
@ -28,4 +31,34 @@ public interface IBookChapterService extends IService<BookChapter> {
|
||||
|
||||
/** 删除章节时做子节点/内容校验(需要的话) */
|
||||
void removeChapter(Integer id);
|
||||
|
||||
// List<Integer> getUserIdsByDepIds(List<Integer> depIds);
|
||||
|
||||
|
||||
Map<Integer, List<BookChapter>> groupByParentByBookId(Integer bookId);
|
||||
|
||||
List<BookChapter> allByFromScene(Integer fromScene);
|
||||
List<BookChapter> getChildDepartmentsByParentChain(Integer parentId, String parentChain);
|
||||
void destroy(Integer id) throws NotFoundException;
|
||||
|
||||
List<BookChapter> listByParentId(Integer id);
|
||||
|
||||
|
||||
void changeParent(Integer id, Integer parentId, List<Integer> ids) throws NotFoundException;
|
||||
|
||||
void resetSort(List<Integer> ids);
|
||||
|
||||
BookChapter findOrFail(Integer id) throws NotFoundException;
|
||||
|
||||
String childrenParentChain(BookChapter bookChapter);
|
||||
|
||||
void update(BookChapter bookChapter, String name, Integer parentId, Integer sort)
|
||||
throws NotFoundException;
|
||||
|
||||
BookChapter chunkByNameAndParentId(String name, Integer parentId);
|
||||
|
||||
BookChapter create(BookChapter bookChapter)
|
||||
throws NotFoundException;
|
||||
|
||||
String compParentChain(Integer parentId) throws NotFoundException;
|
||||
}
|
||||
@ -12,4 +12,6 @@ public interface IBookDepartmentUserService extends IService<BookDepartmentUser>
|
||||
List<BookDepartmentUser> chunksByBookIds(List<Integer> bookIds);
|
||||
|
||||
List<BookDepartmentUser> chunksByCourseId(Integer courseId);
|
||||
|
||||
List<Integer> getCourseIdsByDepIdsOrUserIds(List<Integer> depIds, List<Integer> userIds);
|
||||
}
|
||||
@ -20,5 +20,12 @@ public interface IKnowledgeService extends IService<Knowledge> {
|
||||
JSONObject getByIdVo(Integer id);
|
||||
JSONObject stuGetByIdVo(Integer id);
|
||||
|
||||
|
||||
/**
|
||||
* 根据知识点编码列表批量查询知识点
|
||||
*
|
||||
* @param codes 知识点编码列表
|
||||
* @return 知识点列表
|
||||
* @author menft
|
||||
*/
|
||||
List<Knowledge> getByKnowledgeCodes(List<String> codes);
|
||||
}
|
||||
|
||||
@ -3,10 +3,13 @@ package xyz.playedu.jc.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import xyz.playedu.common.exception.NotFoundException;
|
||||
import xyz.playedu.common.types.paginate.PaginationResult;
|
||||
import xyz.playedu.course.domain.Course;
|
||||
import xyz.playedu.jc.domain.JCResource;
|
||||
import xyz.playedu.jc.domain.Textbook;
|
||||
import xyz.playedu.jc.domain.dto.TextbookUserDTO;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 教材 Service
|
||||
@ -17,4 +20,7 @@ public interface ITextbookService extends IService<Textbook> {
|
||||
|
||||
Textbook findOrFail(Integer id) throws NotFoundException;
|
||||
|
||||
List<Textbook> getDepCoursesAndShow(
|
||||
TextbookUserDTO textbookUserDTO);
|
||||
|
||||
}
|
||||
@ -2,13 +2,17 @@ package xyz.playedu.jc.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import xyz.playedu.common.exception.NotFoundException;
|
||||
import xyz.playedu.common.types.paginate.PaginationResult;
|
||||
import xyz.playedu.course.domain.Course;
|
||||
import xyz.playedu.jc.domain.JCResource;
|
||||
import xyz.playedu.jc.domain.Textbook;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface JCIResourceService extends IService<JCResource> {
|
||||
Map<Integer, String> chunksPreSignUrlByIds(List<Integer> ids);
|
||||
Map<Integer, String> chunksPreSignUrlByIds(List<String> ids);
|
||||
PaginationResult<JCResource> paginate(HashMap<String, Object> params);
|
||||
|
||||
}
|
||||
|
||||
@ -3,16 +3,17 @@ package xyz.playedu.jc.service.impl;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
import xyz.playedu.common.domain.Department;
|
||||
import xyz.playedu.common.exception.NotFoundException;
|
||||
import xyz.playedu.common.util.StringUtil;
|
||||
import xyz.playedu.jc.domain.BookChapter;
|
||||
import xyz.playedu.jc.domain.dto.ChapterSortDTO;
|
||||
import xyz.playedu.jc.domain.vo.ChapterTreeVO;
|
||||
import xyz.playedu.jc.mapper.BookChapterMapper;
|
||||
import xyz.playedu.jc.service.IBookChapterService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 教材章节 Service 实现
|
||||
@ -95,4 +96,227 @@ public class BookChapterServiceImpl
|
||||
//todo 校验是否有子节点 / 内容
|
||||
removeById(id);
|
||||
}
|
||||
@Override
|
||||
public Map<Integer, List<BookChapter>> groupByParentByBookId(Integer bookId) {
|
||||
return list(query().getWrapper().eq("book_id", bookId).orderByAsc("sort"))
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(BookChapter::getParentId));
|
||||
|
||||
// return list(
|
||||
// query()
|
||||
// .getWrapper()
|
||||
// .eq(BookChapter::getBookId, bookId) // ✅ 插入条件
|
||||
// .orderByAsc(BookChapter::getParentId)
|
||||
// )
|
||||
// .stream()
|
||||
// .collect(Collectors.groupingBy(BookChapter::getParentId));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// @Override
|
||||
// public List<Integer> getUserIdsByDepIds(List<Integer> depIds) {
|
||||
// if (StringUtil.isEmpty(depIds)) {
|
||||
// return new ArrayList<>();
|
||||
// }
|
||||
// return list(query().getWrapper().in("dep_id", depIds)).stream()
|
||||
// .map(UserDepartment::getUserId)
|
||||
// .toList();
|
||||
// }
|
||||
|
||||
@Override
|
||||
public List<BookChapter> allByFromScene(Integer fromScene) {
|
||||
if (StringUtil.isNull(fromScene)) {
|
||||
return list(query().getWrapper().orderByAsc("sort"));
|
||||
} else {
|
||||
return list(query().getWrapper().eq("from_scene", fromScene).orderByAsc("sort"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BookChapter> getChildDepartmentsByParentChain(Integer parentId, String parentChain) {
|
||||
if (StringUtil.isEmpty(parentChain)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return list(
|
||||
query().getWrapper()
|
||||
.eq("parent_id", parentId)
|
||||
.or()
|
||||
.likeRight("parent_chain", parentChain + ","));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(Integer id) throws NotFoundException {
|
||||
BookChapter bookChapter = getById(id);
|
||||
if (bookChapter != null) {
|
||||
updateParentChain(bookChapter.getChapterCode(), childrenParentChain(bookChapter));
|
||||
removeById(bookChapter.getId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<BookChapter> listByParentId(Integer id) {
|
||||
return list(query().getWrapper().eq("parent_id", id).orderByAsc("sort"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeParent(Integer id, Integer parentId, List<Integer> ids)
|
||||
throws NotFoundException {
|
||||
BookChapter bookChapter = findOrFail(id);
|
||||
update(bookChapter, bookChapter.getName(), parentId, bookChapter.getSort());
|
||||
// 重置排序
|
||||
resetSort(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetSort(List<Integer> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<BookChapter> departments = new ArrayList<>();
|
||||
int sortVal = 0;
|
||||
for (Integer idItem : ids) {
|
||||
Integer finalSortVal = ++sortVal;
|
||||
departments.add(
|
||||
new BookChapter() {
|
||||
{
|
||||
setId(idItem);
|
||||
setSort(finalSortVal);
|
||||
}
|
||||
});
|
||||
}
|
||||
updateBatchById(departments);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void update(BookChapter bookChapter, String name, Integer parentId, Integer sort)
|
||||
throws NotFoundException {
|
||||
// 计算该部门作为其它子部门的parentChain值
|
||||
String childrenChainPrefix = childrenParentChain(bookChapter);
|
||||
|
||||
BookChapter data = new BookChapter();
|
||||
data.setId(bookChapter.getId());
|
||||
data.setName(name);
|
||||
// data.setFromScene(bookChapter.getFromScene());
|
||||
|
||||
if (!bookChapter.getParentId().equals(parentId)) {
|
||||
data.setParentId(parentId);
|
||||
if (parentId.equals(0)) { // 重置一级部门
|
||||
data.setChapterCode("");
|
||||
} else {
|
||||
BookChapter parentBookChapter = findOrFail(parentId);
|
||||
data.setChapterCode(childrenParentChain(parentBookChapter));
|
||||
}
|
||||
}
|
||||
if (!bookChapter.getSort().equals(sort)) { // 更换部门排序值
|
||||
data.setSort(sort);
|
||||
}
|
||||
|
||||
// 提交更换
|
||||
updateById(data);
|
||||
|
||||
bookChapter = getById(bookChapter.getId());
|
||||
updateParentChain(childrenParentChain(bookChapter), childrenChainPrefix);
|
||||
}
|
||||
|
||||
|
||||
private void updateParentChain(String newChildrenPC, String oldChildrenPC) {
|
||||
List<BookChapter> children =
|
||||
list(query().getWrapper().like("chapter_code", oldChildrenPC + "%"));
|
||||
if (children.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<BookChapter> updateRows = new ArrayList<>();
|
||||
for (BookChapter tmpDepartment : children) {
|
||||
BookChapter tmpUpdateDepartment = new BookChapter();
|
||||
tmpUpdateDepartment.setId(tmpDepartment.getId());
|
||||
|
||||
// parentChain计算
|
||||
String pc = newChildrenPC;
|
||||
if (!tmpDepartment.getChapterCode().equals(oldChildrenPC)) {
|
||||
pc =
|
||||
tmpDepartment
|
||||
.getChapterCode()
|
||||
.replaceFirst(
|
||||
oldChildrenPC + ",",
|
||||
newChildrenPC.isEmpty()
|
||||
? newChildrenPC
|
||||
: newChildrenPC + ',');
|
||||
}
|
||||
tmpUpdateDepartment.setChapterCode(pc);
|
||||
|
||||
// parentId计算
|
||||
int parentId = 0;
|
||||
if (pc != null && !pc.isEmpty()) {
|
||||
String[] parentIds = pc.split(",");
|
||||
parentId = Integer.parseInt(parentIds[parentIds.length - 1]);
|
||||
}
|
||||
tmpUpdateDepartment.setParentId(parentId);
|
||||
|
||||
updateRows.add(tmpUpdateDepartment);
|
||||
}
|
||||
updateBatchById(updateRows);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 章节id查询
|
||||
* @param id
|
||||
* @return
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
@Override
|
||||
public BookChapter findOrFail(Integer id) throws NotFoundException {
|
||||
BookChapter bookChapter = getById(id);
|
||||
if (bookChapter == null) {
|
||||
throw new NotFoundException("部门不存在");
|
||||
}
|
||||
return bookChapter;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String childrenParentChain(BookChapter bookChapter) {
|
||||
String prefix = bookChapter.getId() + "";
|
||||
if (bookChapter.getChapterCode() != null && !bookChapter.getChapterCode().isEmpty()) {
|
||||
prefix = bookChapter.getChapterCode() + "," + prefix;
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BookChapter chunkByNameAndParentId(String name, Integer parentId) {
|
||||
return getOne(query().getWrapper().eq("name", name).eq("parent_id", parentId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BookChapter create(BookChapter bookChapter)
|
||||
throws NotFoundException {
|
||||
String parentChain = "";
|
||||
if (bookChapter.getParentId() != 0) {
|
||||
parentChain = compParentChain(bookChapter.getParentId());
|
||||
}
|
||||
bookChapter.setChapterCode(parentChain);
|
||||
save(bookChapter);
|
||||
|
||||
return bookChapter;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String compParentChain(Integer parentId) throws NotFoundException {
|
||||
String parentChain = "";
|
||||
if (parentId != 0) {
|
||||
BookChapter bookChapter = getById(parentId);
|
||||
if (bookChapter == null) {
|
||||
throw new NotFoundException("父级部门不存在");
|
||||
}
|
||||
String pc = bookChapter.getChapterCode();
|
||||
parentChain = pc == null || pc.isEmpty() ? parentId + "" : pc + "," + parentId;
|
||||
}
|
||||
return parentChain;
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,25 @@
|
||||
package xyz.playedu.jc.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import xyz.playedu.common.domain.UserGroup;
|
||||
import xyz.playedu.common.service.UserGroupService;
|
||||
import xyz.playedu.common.util.StringUtil;
|
||||
import xyz.playedu.course.domain.CourseDepartmentUser;
|
||||
import xyz.playedu.jc.domain.BookDepartmentUser;
|
||||
import xyz.playedu.jc.mapper.BookDepartmentUserMapper;
|
||||
import xyz.playedu.jc.service.IBookDepartmentUserService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class BookDepartmentUserServiceImpl
|
||||
extends ServiceImpl<BookDepartmentUserMapper, BookDepartmentUser>
|
||||
implements IBookDepartmentUserService {
|
||||
@Autowired
|
||||
private UserGroupService userGroupService;
|
||||
|
||||
@Override
|
||||
public void removeByBookId(Integer bookId) {
|
||||
@ -29,4 +36,22 @@ public class BookDepartmentUserServiceImpl
|
||||
return list(query().getWrapper().eq("book_id", bookId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> getCourseIdsByDepIdsOrUserIds(
|
||||
List<Integer> depIds, List<Integer> userIds) {
|
||||
|
||||
List<Integer> groupIds = new ArrayList<>();
|
||||
List<UserGroup> userGroupList = userGroupService.chunksByUserIds(userIds);
|
||||
if (StringUtil.isNotEmpty(userGroupList)) {
|
||||
groupIds.addAll(userGroupList.stream().map(UserGroup::getGroupId).toList());
|
||||
}
|
||||
List<BookDepartmentUser> departmentUserList =
|
||||
getBaseMapper().chunksByDepIdsOrUserIdsOrGroupIds(depIds, userIds, groupIds);
|
||||
if (StringUtil.isEmpty(departmentUserList)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return departmentUserList.stream().map(BookDepartmentUser::getBookId).toList();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,22 +1,31 @@
|
||||
package xyz.playedu.jc.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import xyz.playedu.common.exception.NotFoundException;
|
||||
import xyz.playedu.common.service.AppConfigService;
|
||||
import xyz.playedu.common.types.paginate.PaginationResult;
|
||||
import xyz.playedu.common.util.S3Util;
|
||||
import xyz.playedu.common.util.StringUtil;
|
||||
import xyz.playedu.course.domain.Course;
|
||||
import xyz.playedu.jc.domain.JCResource;
|
||||
import xyz.playedu.jc.domain.Textbook;
|
||||
import xyz.playedu.jc.mapper.JCResourceMapper;
|
||||
import xyz.playedu.jc.service.JCIResourceService;
|
||||
import xyz.playedu.resource.domain.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class JCResourceServiceImpl
|
||||
extends ServiceImpl<JCResourceMapper, JCResource>
|
||||
@ -24,9 +33,85 @@ public class JCResourceServiceImpl
|
||||
|
||||
@Autowired
|
||||
private AppConfigService appConfigService;
|
||||
|
||||
@Override
|
||||
public Map<Integer, String> chunksPreSignUrlByIds(List<Integer> ids) {
|
||||
public PaginationResult<JCResource> paginate(HashMap<String, Object> params) {
|
||||
try {
|
||||
/** 获取分页参数,默认第1页,每页10条 */
|
||||
Integer page = MapUtils.getInteger(params, "page", 1);
|
||||
Integer size = MapUtils.getInteger(params, "size", 10);
|
||||
/** 创建分页对象 */
|
||||
Page<JCResource> pageParam = new Page<>(page, size);
|
||||
|
||||
/** 创建 Lambda 条件构造器,用于构建类型安全的查询条件 */
|
||||
LambdaQueryWrapper<JCResource> queryWrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
/** 固定条件:只查询未删除的记录(km_is_del = false) */
|
||||
// queryWrapper.eq(Textbook::getKmIsDel, false);
|
||||
|
||||
/** 动态添加查询条件:资源类型 */
|
||||
if (MapUtils.getString(params, "type") != null && !MapUtils.getString(params, "type").isEmpty()) {
|
||||
queryWrapper.eq(JCResource::getType, MapUtils.getString(params, "type"));
|
||||
}
|
||||
|
||||
/** 添加排序条件:按创建时间降序排列 默认时间降序*/
|
||||
if (MapUtils.getString(params, "sortOrder") != null
|
||||
&& !MapUtils.getString(params, "sortOrder").isEmpty()
|
||||
&& MapUtils.getString(params, "sortFiled") != null
|
||||
&& !MapUtils.getString(params, "sortFiled").isEmpty()
|
||||
) {
|
||||
String sortOrder = MapUtils.getString(params, "sortOrder");
|
||||
String sortFiled = MapUtils.getString(params, "sortFiled");
|
||||
if ("ascend".equals(sortOrder)){
|
||||
if ("name".equals(sortFiled)) {
|
||||
queryWrapper.orderByAsc(JCResource::getName);
|
||||
}else if ("size".equals(sortFiled)){
|
||||
queryWrapper.orderByAsc(JCResource::getSize);
|
||||
}else {
|
||||
queryWrapper.orderByAsc(JCResource::getCreateTime);
|
||||
}
|
||||
}else {
|
||||
if ("name".equals(sortFiled)) {
|
||||
queryWrapper.orderByDesc(JCResource::getName);
|
||||
}else if ("size".equals(sortFiled)){
|
||||
queryWrapper.orderByDesc(JCResource::getSize);
|
||||
}else {
|
||||
queryWrapper.orderByDesc(JCResource::getCreateTime);
|
||||
}
|
||||
}
|
||||
}else {
|
||||
queryWrapper.orderByDesc(JCResource::getCreateTime);
|
||||
}
|
||||
|
||||
/** 执行分页查询 */
|
||||
IPage<JCResource> pageResult = this.page(pageParam, queryWrapper);
|
||||
|
||||
/** 计算总页数 */
|
||||
Long total = pageResult.getTotal();
|
||||
Long pages = (total + size - 1) / size; // 向上取整
|
||||
|
||||
/** 构建返回结果,包含完整的分页信息 */
|
||||
PaginationResult<JCResource> result = new PaginationResult<>();
|
||||
result.setData(pageResult.getRecords());
|
||||
result.setTotal(total);
|
||||
result.setCurrent(page); // 当前页码
|
||||
result.setSize(size); // 每页大小
|
||||
result.setPages(pages); // 总页数
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("分页查询消息失败,参数:{}", params, e);
|
||||
/** 返回空结果 */
|
||||
PaginationResult<JCResource> emptyResult = new PaginationResult<>();
|
||||
emptyResult.setData(new ArrayList<>());
|
||||
emptyResult.setTotal(0L);
|
||||
emptyResult.setCurrent(MapUtils.getInteger(params, "page", 1));
|
||||
emptyResult.setSize(MapUtils.getInteger(params, "size", 10));
|
||||
emptyResult.setPages(0L);
|
||||
return emptyResult;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Map<Integer, String> chunksPreSignUrlByIds(List<String> ids) {
|
||||
S3Util s3Util = new S3Util(appConfigService.getS3Config());
|
||||
|
||||
Map<Integer, String> preSignUrlMap = new HashMap<>();
|
||||
|
||||
@ -47,7 +47,8 @@ public class KnowledgeServiceImpl extends ServiceImpl<KnowledgeMapper, Knowledge
|
||||
public List<Knowledge> listVo(KnowledgeParam param) {
|
||||
//获取知识点卡片
|
||||
LambdaQueryWrapper<Knowledge> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(Knowledge::getBookId, param.getBookId())
|
||||
// 当bookId不为空时才加条件,否则查询所有知识点
|
||||
queryWrapper.eq(param.getBookId() != null, Knowledge::getBookId, param.getBookId())
|
||||
.eq(Knowledge::getIsReal,"1").orderByAsc(Knowledge::getOrderNum);
|
||||
return list(queryWrapper);
|
||||
}
|
||||
@ -216,6 +217,16 @@ public class KnowledgeServiceImpl extends ServiceImpl<KnowledgeMapper, Knowledge
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Knowledge> getByKnowledgeCodes(List<String> codes) {
|
||||
if (codes == null || codes.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
LambdaQueryWrapper<Knowledge> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.in(Knowledge::getKnowledgeCode, codes);
|
||||
return list(queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递增编码
|
||||
* @param code 当前编码
|
||||
|
||||
@ -2,21 +2,32 @@ package xyz.playedu.jc.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import xyz.playedu.common.config.ServerConfig;
|
||||
import xyz.playedu.common.exception.NotFoundException;
|
||||
import xyz.playedu.common.types.paginate.PaginationResult;
|
||||
import xyz.playedu.common.util.StringUtil;
|
||||
import xyz.playedu.course.domain.Course;
|
||||
import xyz.playedu.jc.domain.JCResource;
|
||||
import xyz.playedu.jc.domain.Textbook;
|
||||
import xyz.playedu.jc.domain.dto.TextbookUserDTO;
|
||||
import xyz.playedu.jc.mapper.TextbookMapper;
|
||||
import xyz.playedu.jc.service.IBookDepartmentUserService;
|
||||
import xyz.playedu.jc.service.ITextbookService;
|
||||
import xyz.playedu.knowledge.domain.KnowledgeMessages;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 教材 Service 实现
|
||||
@ -48,11 +59,18 @@ public class TextbookServiceImpl
|
||||
LambdaQueryWrapper<Textbook> queryWrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
/** 固定条件:只查询未删除的记录(km_is_del = false) */
|
||||
// queryWrapper.eq(Textbook::getKmIsDel, false);
|
||||
// queryWrapper.eq(Textbook::getKmIsDel, false);
|
||||
|
||||
/** 动态添加查询条件:会话ID */
|
||||
if (MapUtils.getString(params, "title") != null && !MapUtils.getString(params, "title").isEmpty()) {
|
||||
queryWrapper.eq(Textbook::getTitle, MapUtils.getString(params, "title"));
|
||||
/** 动态添加查询条件:关键词 */
|
||||
String title = MapUtils.getString(params, "title");
|
||||
|
||||
// 动态添加模糊查询条件:name / author / .
|
||||
if (title != null && !title.isEmpty()) {
|
||||
queryWrapper.and(w -> w
|
||||
.like(Textbook::getTitle, title) // 书名模糊
|
||||
.or().like(Textbook::getAuthor, title) // 作者模糊
|
||||
.or().like(Textbook::getMajor, title) // 专业模糊
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -96,4 +114,77 @@ public class TextbookServiceImpl
|
||||
}
|
||||
return textbook;
|
||||
}
|
||||
@Autowired
|
||||
private ServerConfig serverConfig;
|
||||
@Autowired
|
||||
private IBookDepartmentUserService bookDepartmentUserService;
|
||||
@Override
|
||||
public List<Textbook> getDepCoursesAndShow(
|
||||
TextbookUserDTO textbookUserDTO) {
|
||||
if (StringUtil.isEmpty(textbookUserDTO.getDepIds())) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// List<Textbook> res = new ArrayList<>();
|
||||
|
||||
List<Integer> courseIds =
|
||||
bookDepartmentUserService.getCourseIdsByDepIdsOrUserIds(textbookUserDTO.getDepIds(), textbookUserDTO.getUserIds());
|
||||
if (StringUtil.isEmpty(courseIds)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
// if (categoryId != null && categoryId > 0) {
|
||||
// // 获取所有子类
|
||||
// List<Integer> allCategoryIdsList =
|
||||
// categoryService.getChildCategoryIdsByParentId(categoryId + "");
|
||||
// List<Integer> tmpCourseIds =
|
||||
// courseCategoryService.getCourseIdsByCategoryIds(allCategoryIdsList);
|
||||
// if (StringUtil.isEmpty(tmpCourseIds)) {
|
||||
// return new ArrayList<>();
|
||||
// }
|
||||
// courseIds = courseIds.stream().filter(tmpCourseIds::contains).toList();
|
||||
// if (StringUtil.isEmpty(courseIds)) {
|
||||
// return new ArrayList<>();
|
||||
// }
|
||||
// }
|
||||
LambdaQueryWrapper<Textbook> queryWrapper = Wrappers.lambdaQuery();
|
||||
|
||||
// 课程范围
|
||||
queryWrapper.in(Textbook::getId, courseIds);
|
||||
|
||||
// 关键字查询:书名 / 作者 / 专业
|
||||
String keyword = textbookUserDTO.getName();
|
||||
if (keyword != null && !keyword.isEmpty()) {
|
||||
queryWrapper.and(w -> w
|
||||
.like(Textbook::getTitle, keyword)
|
||||
.or().like(Textbook::getAuthor, keyword)
|
||||
.or().like(Textbook::getMajor, keyword)
|
||||
);
|
||||
}
|
||||
|
||||
// 出版年份查询(只传年份,比如 "2024")
|
||||
String publishYearStr = textbookUserDTO.getPublishTime();
|
||||
if (publishYearStr != null && !publishYearStr.isEmpty()) {
|
||||
int year = Integer.parseInt(publishYearStr);
|
||||
LocalDateTime start = LocalDateTime.of(year, 1, 1, 0, 0, 0);
|
||||
LocalDateTime end = start.plusYears(1);
|
||||
|
||||
queryWrapper.ge(Textbook::getPublishTime, start)
|
||||
.lt(Textbook::getPublishTime, end);
|
||||
}
|
||||
|
||||
// 专业单独查询(如果是专门挑专业)
|
||||
String major = textbookUserDTO.getMajor();
|
||||
if (major != null && !major.isEmpty()) {
|
||||
queryWrapper.like(Textbook::getMajor, major);
|
||||
}
|
||||
|
||||
List<Textbook> res = list(queryWrapper);
|
||||
|
||||
for (Textbook re : res) {
|
||||
re.setAllUrl(serverConfig.getUrl() + re.getThumb());
|
||||
re.setUrl(serverConfig.getUrl());
|
||||
}
|
||||
return res;
|
||||
|
||||
}
|
||||
}
|
||||
@ -24,5 +24,22 @@
|
||||
update_time,
|
||||
tenant_id
|
||||
</sql>
|
||||
|
||||
<select id="chunksByDepIdsOrUserIdsOrGroupIds" resultType="xyz.playedu.jc.domain.BookDepartmentUser">
|
||||
SELECT DISTINCT `jc_book_department_user`.*
|
||||
FROM `jc_book_department_user`
|
||||
<where>
|
||||
<if test="depIds != null and !depIds.isEmpty()">
|
||||
OR (`jc_book_department_user`.`range_id` IN (<foreach collection="depIds" item="tmpId" separator=",">
|
||||
#{tmpId}</foreach>) AND `jc_book_department_user`.`type` = 0)
|
||||
</if>
|
||||
<if test="userIds != null and !userIds.isEmpty()">
|
||||
OR (`jc_book_department_user`.`range_id` IN (<foreach collection="userIds" item="tmpId" separator=",">
|
||||
#{tmpId}</foreach>) AND `jc_book_department_user`.`type` = 1)
|
||||
</if>
|
||||
<if test="groupIds != null and !groupIds.isEmpty()">
|
||||
OR (`jc_book_department_user`.`range_id` IN (<foreach collection="groupIds" item="tmpId" separator=",">
|
||||
#{tmpId}</foreach>) AND `jc_book_department_user`.`type` = 2)
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@ -36,6 +36,10 @@ public class ExamQuestion extends TenantBaseDO {
|
||||
/** 难度等级:1-4 */
|
||||
private Integer level;
|
||||
|
||||
/** 知识点CODE(多个用逗号分隔) */
|
||||
@JsonProperty("knowledge_code")
|
||||
private String knowledgeCode;
|
||||
|
||||
/** 内容 */
|
||||
private String content;
|
||||
|
||||
@ -78,6 +82,9 @@ public class ExamQuestion extends TenantBaseDO {
|
||||
&& (this.getLevel() == null
|
||||
? other.getLevel() == null
|
||||
: this.getLevel().equals(other.getLevel()))
|
||||
&& (this.getKnowledgeCode() == null
|
||||
? other.getKnowledgeCode() == null
|
||||
: this.getKnowledgeCode().equals(other.getKnowledgeCode()))
|
||||
&& (this.getContent() == null
|
||||
? other.getContent() == null
|
||||
: this.getContent().equals(other.getContent()))
|
||||
@ -101,6 +108,7 @@ public class ExamQuestion extends TenantBaseDO {
|
||||
result = prime * result + ((getAdminId() == null) ? 0 : getAdminId().hashCode());
|
||||
result = prime * result + ((getType() == null) ? 0 : getType().hashCode());
|
||||
result = prime * result + ((getLevel() == null) ? 0 : getLevel().hashCode());
|
||||
result = prime * result + ((getKnowledgeCode() == null) ? 0 : getKnowledgeCode().hashCode());
|
||||
result = prime * result + ((getContent() == null) ? 0 : getContent().hashCode());
|
||||
result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode());
|
||||
result = prime * result + ((getUpdatedAt() == null) ? 0 : getUpdatedAt().hashCode());
|
||||
@ -119,6 +127,7 @@ public class ExamQuestion extends TenantBaseDO {
|
||||
sb.append(", adminId=").append(adminId);
|
||||
sb.append(", type=").append(type);
|
||||
sb.append(", level=").append(level);
|
||||
sb.append(", knowledgeCode=").append(knowledgeCode);
|
||||
sb.append(", content=").append(content);
|
||||
sb.append(", createdAt=").append(createdAt);
|
||||
sb.append(", updatedAt=").append(updatedAt);
|
||||
|
||||
@ -22,4 +22,6 @@ public interface ExamQuestionMapper extends BaseMapper<ExamQuestion> {
|
||||
Long paginateCount(ExamQuestionPaginateFilter filter);
|
||||
|
||||
List<ExamQuestion> chunksByCategoryIdAndLimit(ExamQuestionFilter filter);
|
||||
|
||||
List<String> getDistinctKnowledgeCodes(String categoryIds, Integer type, Integer level);
|
||||
}
|
||||
|
||||
@ -20,12 +20,18 @@ public interface ExamQuestionService extends IService<ExamQuestion> {
|
||||
PaginationResult<ExamQuestion> paginate(int page, int size, ExamQuestionPaginateFilter filter);
|
||||
|
||||
Integer create(
|
||||
Integer categoryId, String content, Integer level, Integer type, Integer adminId);
|
||||
Integer categoryId,
|
||||
String content,
|
||||
Integer level,
|
||||
String knowledgeCode,
|
||||
Integer type,
|
||||
Integer adminId);
|
||||
|
||||
void update(
|
||||
Integer id,
|
||||
String content,
|
||||
Integer level,
|
||||
String knowledgeCode,
|
||||
Integer type,
|
||||
Integer categoryId,
|
||||
Integer adminId);
|
||||
@ -37,4 +43,11 @@ public interface ExamQuestionService extends IService<ExamQuestion> {
|
||||
List<ExamQuestion> chunksByCategoryId(Integer categoryId);
|
||||
|
||||
List<ExamQuestion> chunksByCategoryIdAndLimit(ExamQuestionFilter filter);
|
||||
|
||||
/**
|
||||
* 根据题库ID、题型、难度获取去重的知识点编码列表
|
||||
*
|
||||
* @author menft
|
||||
*/
|
||||
List<String> getDistinctKnowledgeCodes(String categoryIds, Integer type, Integer level);
|
||||
}
|
||||
|
||||
@ -50,13 +50,19 @@ public class ExamQuestionServiceImpl extends ServiceImpl<ExamQuestionMapper, Exa
|
||||
|
||||
@Override
|
||||
public Integer create(
|
||||
Integer categoryId, String content, Integer level, Integer type, Integer adminId) {
|
||||
Integer categoryId,
|
||||
String content,
|
||||
Integer level,
|
||||
String knowledgeCode,
|
||||
Integer type,
|
||||
Integer adminId) {
|
||||
ExamQuestion examQuestion =
|
||||
new ExamQuestion() {
|
||||
{
|
||||
setCategoryId(categoryId);
|
||||
setAdminId(adminId);
|
||||
setLevel(level);
|
||||
setKnowledgeCode(knowledgeCode);
|
||||
setType(type);
|
||||
setContent(content);
|
||||
setCreatedAt(new Date());
|
||||
@ -72,6 +78,7 @@ public class ExamQuestionServiceImpl extends ServiceImpl<ExamQuestionMapper, Exa
|
||||
Integer id,
|
||||
String content,
|
||||
Integer level,
|
||||
String knowledgeCode,
|
||||
Integer type,
|
||||
Integer categoryId,
|
||||
Integer adminId) {
|
||||
@ -82,6 +89,7 @@ public class ExamQuestionServiceImpl extends ServiceImpl<ExamQuestionMapper, Exa
|
||||
question.setCategoryId(categoryId);
|
||||
question.setContent(content);
|
||||
question.setLevel(level);
|
||||
question.setKnowledgeCode(knowledgeCode);
|
||||
question.setType(type);
|
||||
question.setUpdatedAt(new Date());
|
||||
|
||||
@ -108,4 +116,22 @@ public class ExamQuestionServiceImpl extends ServiceImpl<ExamQuestionMapper, Exa
|
||||
public List<ExamQuestion> chunksByCategoryIdAndLimit(ExamQuestionFilter filter) {
|
||||
return getBaseMapper().chunksByCategoryIdAndLimit(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDistinctKnowledgeCodes(String categoryIds, Integer type, Integer level) {
|
||||
// 获取所有 knowledge_code 字段(可能是逗号分隔的多个值)
|
||||
List<String> rawCodes = getBaseMapper().getDistinctKnowledgeCodes(categoryIds, type, level);
|
||||
if (rawCodes == null || rawCodes.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
// 拆分并去重
|
||||
return rawCodes.stream()
|
||||
.filter(code -> code != null && !code.isEmpty())
|
||||
.flatMap(code -> java.util.Arrays.stream(code.split(",")))
|
||||
.map(String::trim)
|
||||
.filter(code -> !code.isEmpty())
|
||||
.distinct()
|
||||
.sorted()
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
<result property="adminId" column="admin_id" jdbcType="INTEGER"/>
|
||||
<result property="type" column="type" jdbcType="INTEGER"/>
|
||||
<result property="level" column="level" jdbcType="TINYINT"/>
|
||||
<result property="knowledgeCode" column="knowledge_code" jdbcType="VARCHAR"/>
|
||||
<result property="content" column="content" jdbcType="VARCHAR"/>
|
||||
<result property="createdAt" column="created_at" jdbcType="TIMESTAMP"/>
|
||||
<result property="updatedAt" column="updated_at" jdbcType="TIMESTAMP"/>
|
||||
@ -18,7 +19,7 @@
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
id,category_id,admin_id,type,
|
||||
level,content,created_at,
|
||||
level,knowledge_code,content,created_at,
|
||||
updated_at,deleted
|
||||
</sql>
|
||||
|
||||
@ -42,6 +43,9 @@
|
||||
<if test="level != null">
|
||||
AND `exam_question`.`level` = #{level}
|
||||
</if>
|
||||
<if test="knowledgeCode != null and knowledgeCode != ''">
|
||||
AND FIND_IN_SET(#{knowledgeCode}, `exam_question`.`knowledge_code`)
|
||||
</if>
|
||||
</where>
|
||||
|
||||
<if test="sortAlgo == 'asc'">
|
||||
@ -87,6 +91,9 @@
|
||||
<if test="level != null">
|
||||
AND `exam_question`.`level` = #{level}
|
||||
</if>
|
||||
<if test="knowledgeCode != null and knowledgeCode != ''">
|
||||
AND FIND_IN_SET(#{knowledgeCode}, `exam_question`.`knowledge_code`)
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
@ -100,6 +107,16 @@
|
||||
#{categoryId}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="type1Level != null">
|
||||
and `exam_question`.`level` = #{type1Level}
|
||||
</if>
|
||||
<if test="type1KnowledgeCodes != null and type1KnowledgeCodes != ''">
|
||||
and (
|
||||
<foreach collection="type1KnowledgeCodes.split(',')" item="code" separator=" OR ">
|
||||
FIND_IN_SET(#{code}, `exam_question`.`knowledge_code`)
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY RAND()
|
||||
LIMIT #{type1Number})
|
||||
@ -115,6 +132,16 @@
|
||||
#{categoryId}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="type2Level != null">
|
||||
and `exam_question`.`level` = #{type2Level}
|
||||
</if>
|
||||
<if test="type2KnowledgeCodes != null and type2KnowledgeCodes != ''">
|
||||
and (
|
||||
<foreach collection="type2KnowledgeCodes.split(',')" item="code" separator=" OR ">
|
||||
FIND_IN_SET(#{code}, `exam_question`.`knowledge_code`)
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY RAND()
|
||||
LIMIT #{type2Number})
|
||||
@ -130,6 +157,16 @@
|
||||
#{categoryId}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="type3Level != null">
|
||||
and `exam_question`.`level` = #{type3Level}
|
||||
</if>
|
||||
<if test="type3KnowledgeCodes != null and type3KnowledgeCodes != ''">
|
||||
and (
|
||||
<foreach collection="type3KnowledgeCodes.split(',')" item="code" separator=" OR ">
|
||||
FIND_IN_SET(#{code}, `exam_question`.`knowledge_code`)
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY RAND()
|
||||
LIMIT #{type3Number})
|
||||
@ -145,6 +182,16 @@
|
||||
#{categoryId}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="type4Level != null">
|
||||
and `exam_question`.`level` = #{type4Level}
|
||||
</if>
|
||||
<if test="type4KnowledgeCodes != null and type4KnowledgeCodes != ''">
|
||||
and (
|
||||
<foreach collection="type4KnowledgeCodes.split(',')" item="code" separator=" OR ">
|
||||
FIND_IN_SET(#{code}, `exam_question`.`knowledge_code`)
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY RAND()
|
||||
LIMIT #{type4Number})
|
||||
@ -160,6 +207,16 @@
|
||||
#{categoryId}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="type5Level != null">
|
||||
and `exam_question`.`level` = #{type5Level}
|
||||
</if>
|
||||
<if test="type5KnowledgeCodes != null and type5KnowledgeCodes != ''">
|
||||
and (
|
||||
<foreach collection="type5KnowledgeCodes.split(',')" item="code" separator=" OR ">
|
||||
FIND_IN_SET(#{code}, `exam_question`.`knowledge_code`)
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY RAND()
|
||||
LIMIT #{type5Number})
|
||||
@ -175,8 +232,36 @@
|
||||
#{categoryId}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="type6Level != null">
|
||||
and `exam_question`.`level` = #{type6Level}
|
||||
</if>
|
||||
<if test="type6KnowledgeCodes != null and type6KnowledgeCodes != ''">
|
||||
and (
|
||||
<foreach collection="type6KnowledgeCodes.split(',')" item="code" separator=" OR ">
|
||||
FIND_IN_SET(#{code}, `exam_question`.`knowledge_code`)
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY RAND()
|
||||
LIMIT #{type6Number})
|
||||
</select>
|
||||
|
||||
<select id="getDistinctKnowledgeCodes" resultType="java.lang.String">
|
||||
SELECT DISTINCT `knowledge_code`
|
||||
FROM `exam_question`
|
||||
<where>
|
||||
AND `knowledge_code` IS NOT NULL
|
||||
AND `knowledge_code` != ''
|
||||
<if test="categoryIds != null and categoryIds != ''">
|
||||
AND `category_id` IN (${categoryIds})
|
||||
</if>
|
||||
<if test="type != null">
|
||||
AND `type` = #{type}
|
||||
</if>
|
||||
<if test="level != null">
|
||||
AND `level` = #{level}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@ -155,7 +155,7 @@ public class ResourceTranscodeInfoServiceImpl
|
||||
|| timestamp == null
|
||||
|| StringUtil.isEmpty(definition)
|
||||
|| StringUtil.isEmpty(sign)) {
|
||||
throw new ServiceException("参数为空");
|
||||
throw new ServiceException("参数为空2");
|
||||
}
|
||||
|
||||
String str =
|
||||
|
||||
@ -54,7 +54,8 @@ export function questionList(
|
||||
sortAlgo: string,
|
||||
content: string,
|
||||
level: any,
|
||||
type: any
|
||||
type: any,
|
||||
knowledgeCode?: string
|
||||
) {
|
||||
return client.get('/backend/v1/exam/question/index', {
|
||||
category_id,
|
||||
@ -65,15 +66,23 @@ export function questionList(
|
||||
content,
|
||||
level,
|
||||
type,
|
||||
knowledge_code: knowledgeCode,
|
||||
});
|
||||
}
|
||||
|
||||
export function questionStore(category_id: number, content: string, level: any, type: any) {
|
||||
export function questionStore(
|
||||
category_id: number,
|
||||
content: string,
|
||||
level: any,
|
||||
type: any,
|
||||
knowledge_code?: string
|
||||
) {
|
||||
return client.post('/backend/v1/exam/question/create', {
|
||||
category_id,
|
||||
content,
|
||||
level,
|
||||
type,
|
||||
knowledge_code,
|
||||
});
|
||||
}
|
||||
|
||||
@ -86,13 +95,15 @@ export function questionUpdate(
|
||||
category_id: number,
|
||||
content: string,
|
||||
level: any,
|
||||
type: any
|
||||
type: any,
|
||||
knowledge_code?: string
|
||||
) {
|
||||
return client.put(`/backend/v1/exam/question/${id}`, {
|
||||
category_id,
|
||||
content,
|
||||
level,
|
||||
type,
|
||||
knowledge_code,
|
||||
});
|
||||
}
|
||||
|
||||
@ -123,3 +134,17 @@ export function storeBatch(categoryId: number, startLine: number, questions: str
|
||||
export function uploadTxt(params: any) {
|
||||
return client.post('/backend/v1/exam/question/import', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据题库ID、题型、难度获取关联的知识点编码列表
|
||||
* @param categoryIds 题库ID列表(逗号分隔)
|
||||
* @param type 题型
|
||||
* @param level 难度
|
||||
*/
|
||||
export function getKnowledgeCodes(categoryIds: string, type?: number, level?: number | null) {
|
||||
return client.get('/backend/v1/exam/question/knowledge-codes', {
|
||||
category_ids: categoryIds,
|
||||
type: type,
|
||||
level: level,
|
||||
});
|
||||
}
|
||||
|
||||
@ -228,6 +228,25 @@ export function GetChapterContentApi(chapterId: number, bookId: number) {
|
||||
return client.get(`/backend/v1/jc/chapter-content/${chapterId}`, { bookId });
|
||||
}
|
||||
|
||||
/*
|
||||
* Knowledge 知识点
|
||||
* */
|
||||
|
||||
// 获取知识点列表
|
||||
export function getKnowledgeListApi(bookId?: number) {
|
||||
return client.get('/backend/v1/jc/knowledge/list', { bookId: bookId });
|
||||
}
|
||||
|
||||
// 获取教材下拉列表(不分页,用于选择器)
|
||||
export function getTextbookSelectListApi() {
|
||||
return client.get('/backend/v1/jc/textbook/selectList', {});
|
||||
}
|
||||
|
||||
// 根据知识点编码列表获取知识点详情(用于编辑回显)
|
||||
export function getKnowledgeByCodesApi(codes: string) {
|
||||
return client.get('/backend/v1/jc/knowledge/byCodes', { codes });
|
||||
}
|
||||
|
||||
/*
|
||||
* resource List
|
||||
* */
|
||||
|
||||
@ -13,7 +13,7 @@ import {
|
||||
Spin,
|
||||
message,
|
||||
} from 'antd';
|
||||
import { question, resourceCategory } from '../../api';
|
||||
import { question, resourceCategory, textbook } from '../../api';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import styles from './index.module.less';
|
||||
import { TreeQuestion } from '../../compenents';
|
||||
@ -63,6 +63,11 @@ export const AddQuestion = (props: PropsInterface) => {
|
||||
const [selectedIds, setSelectedIds] = useState<any>([]);
|
||||
const [selectVideos, setSelectVideos] = useState<any[]>([]);
|
||||
const [categories, setCategories] = useState<Option[]>([]);
|
||||
const [textbookList, setTextbookList] = useState<any[]>([]); // 教材列表
|
||||
const [selectedTextbookId, setSelectedTextbookId] = useState<number | undefined>(undefined); // 选中的教材ID
|
||||
const [knowledgeCode, setKnowledgeCode] = useState('');
|
||||
const [knowledgeList, setKnowledgeList] = useState<any[]>([]);
|
||||
const [knowledgeLoading, setKnowledgeLoading] = useState(false); // 知识点加载状态
|
||||
const [resourceUrl, setResourceUrl] = useState<ResourceUrlModel>({});
|
||||
const types = [
|
||||
{ label: t('exam.question.choice.label2'), value: 1 },
|
||||
@ -92,10 +97,43 @@ export const AddQuestion = (props: PropsInterface) => {
|
||||
|
||||
const initData = async () => {
|
||||
await getCategory();
|
||||
await getTextbookList();
|
||||
await getList();
|
||||
setInit(false);
|
||||
};
|
||||
|
||||
// 加载教材列表
|
||||
const getTextbookList = async () => {
|
||||
try {
|
||||
const res: any = await textbook.getTextbookSelectListApi();
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
setTextbookList(res.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载教材列表失败:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// 当选择教材时,加载对应的知识点列表
|
||||
const handleTextbookChange = async (textbookId: number | undefined) => {
|
||||
setSelectedTextbookId(textbookId);
|
||||
setKnowledgeCode(''); // 清空已选知识点
|
||||
setKnowledgeList([]);
|
||||
|
||||
if (textbookId) {
|
||||
setKnowledgeLoading(true);
|
||||
try {
|
||||
const res: any = await textbook.getKnowledgeListApi(textbookId);
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
setKnowledgeList(res.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载知识点列表失败:', err);
|
||||
}
|
||||
setKnowledgeLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getCategory = async () => {
|
||||
const res: any = await resourceCategory.resourceCategoryList();
|
||||
const categories = res.data.categories;
|
||||
@ -122,7 +160,8 @@ export const AddQuestion = (props: PropsInterface) => {
|
||||
'',
|
||||
name,
|
||||
level.length === 0 ? '' : level,
|
||||
type.length === 0 ? '' : type
|
||||
type.length === 0 ? '' : type,
|
||||
knowledgeCode || undefined
|
||||
);
|
||||
setResourceUrl(res.data.resource_url);
|
||||
const data = res.data.result.data;
|
||||
@ -152,6 +191,9 @@ export const AddQuestion = (props: PropsInterface) => {
|
||||
setName('');
|
||||
setType([]);
|
||||
setLevel([]);
|
||||
setSelectedTextbookId(undefined);
|
||||
setKnowledgeCode('');
|
||||
setKnowledgeList([]);
|
||||
setSelectedRowKeys([]);
|
||||
setSelectedIds([]);
|
||||
setRefresh(!refresh);
|
||||
@ -380,6 +422,46 @@ export const AddQuestion = (props: PropsInterface) => {
|
||||
placeholder={t('exam.question.detail.namePlaceholder2')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex mb-24">
|
||||
<div className="d-flex mr-16">
|
||||
<Typography.Text>教材</Typography.Text>
|
||||
<Select
|
||||
style={{ width: 150 }}
|
||||
placeholder="请选择教材"
|
||||
value={selectedTextbookId}
|
||||
allowClear
|
||||
showSearch
|
||||
filterOption={(input: string, option: any) =>
|
||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||
}
|
||||
onChange={handleTextbookChange}
|
||||
options={textbookList.map((item: any) => ({
|
||||
label: item.title,
|
||||
value: item.id,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex mr-16">
|
||||
<Typography.Text>知识点</Typography.Text>
|
||||
<Select
|
||||
style={{ width: 150 }}
|
||||
placeholder={selectedTextbookId ? "请选择知识点" : "请先选择教材"}
|
||||
value={knowledgeCode || undefined}
|
||||
disabled={!selectedTextbookId}
|
||||
loading={knowledgeLoading}
|
||||
allowClear
|
||||
showSearch
|
||||
filterOption={(input: string, option: any) =>
|
||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||
}
|
||||
onChange={(value: any) => setKnowledgeCode(value || '')}
|
||||
options={knowledgeList.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.knowledgeCode || item.knowledge_code,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
<Button className="mr-16" onClick={resetList}>
|
||||
{t('commen.reset')}
|
||||
</Button>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import styles from './paper.module.less';
|
||||
import { message, Button, Table, InputNumber, Spin, Modal } from 'antd';
|
||||
import { paper } from '../../../../api';
|
||||
import { message, Button, Table, InputNumber, Spin, Modal, Select } from 'antd';
|
||||
import { paper, question } from '../../../../api';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
@ -10,6 +10,14 @@ import { ExclamationCircleFilled } from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
const { confirm } = Modal;
|
||||
|
||||
// 难度选项
|
||||
const levelOptions = [
|
||||
{ value: null, label: '不限' },
|
||||
{ value: 1, label: '简单' },
|
||||
{ value: 2, label: '中等' },
|
||||
{ value: 3, label: '困难' },
|
||||
];
|
||||
|
||||
interface DataType {
|
||||
id: React.Key;
|
||||
admin_id: number;
|
||||
@ -61,28 +69,49 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
const [choice, setChoice] = useState({
|
||||
number: 0,
|
||||
score: 0,
|
||||
level: null as number | null,
|
||||
knowledge_codes: '' as string,
|
||||
});
|
||||
const [select, setSelect] = useState({
|
||||
number: 0,
|
||||
score: 0,
|
||||
missed_score: 0,
|
||||
level: null as number | null,
|
||||
knowledge_codes: '' as string,
|
||||
});
|
||||
const [input, setInput] = useState({
|
||||
number: 0,
|
||||
score: 0,
|
||||
level: null as number | null,
|
||||
knowledge_codes: '' as string,
|
||||
});
|
||||
const [judge, setJudge] = useState({
|
||||
number: 0,
|
||||
score: 0,
|
||||
level: null as number | null,
|
||||
knowledge_codes: '' as string,
|
||||
});
|
||||
const [qa, setQa] = useState({
|
||||
number: 0,
|
||||
score: 0,
|
||||
level: null as number | null,
|
||||
knowledge_codes: '' as string,
|
||||
});
|
||||
const [cap, setCap] = useState({
|
||||
number: 0,
|
||||
score: 0,
|
||||
missed_score: 0,
|
||||
level: null as number | null,
|
||||
knowledge_codes: '' as string,
|
||||
});
|
||||
// 各题型的知识点选项
|
||||
const [knowledgeOptions, setKnowledgeOptions] = useState<Record<number, any[]>>({
|
||||
1: [],
|
||||
2: [],
|
||||
3: [],
|
||||
4: [],
|
||||
5: [],
|
||||
6: [],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@ -91,6 +120,23 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
setId(Number(result.get('id')));
|
||||
}, [result.get('cid'), result.get('title'), result.get('id')]);
|
||||
|
||||
// 根据题库、题型、难度加载知识点列表
|
||||
const loadKnowledgeCodes = async (type: number, level: number | null) => {
|
||||
if (questions.length === 0) return;
|
||||
try {
|
||||
const res: any = await question.getKnowledgeCodes(questions.join(','), type, level);
|
||||
const items = res.data || [];
|
||||
// 后端返回 [{code: "xxx", name: "yyy"}, ...]
|
||||
const options = items.map((item: { code: string; name: string }) => ({
|
||||
value: item.code,
|
||||
label: item.name,
|
||||
}));
|
||||
setKnowledgeOptions((prev) => ({ ...prev, [type]: options }));
|
||||
} catch (e) {
|
||||
console.error('加载知识点失败', e);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (id === 0) {
|
||||
return;
|
||||
@ -187,6 +233,8 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
setChoice({
|
||||
number: score[1].number,
|
||||
score: score[1].score,
|
||||
level: score[1].level || null,
|
||||
knowledge_codes: score[1].knowledge_codes || '',
|
||||
});
|
||||
}
|
||||
if (score[2]) {
|
||||
@ -194,24 +242,32 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
number: score[2].number,
|
||||
score: score[2].score,
|
||||
missed_score: score[2].missed_score,
|
||||
level: score[2].level || null,
|
||||
knowledge_codes: score[2].knowledge_codes || '',
|
||||
});
|
||||
}
|
||||
if (score[3]) {
|
||||
setInput({
|
||||
number: score[3].number,
|
||||
score: score[3].score,
|
||||
level: score[3].level || null,
|
||||
knowledge_codes: score[3].knowledge_codes || '',
|
||||
});
|
||||
}
|
||||
if (score[4]) {
|
||||
setJudge({
|
||||
number: score[4].number,
|
||||
score: score[4].score,
|
||||
level: score[4].level || null,
|
||||
knowledge_codes: score[4].knowledge_codes || '',
|
||||
});
|
||||
}
|
||||
if (score[5]) {
|
||||
setQa({
|
||||
number: score[5].number,
|
||||
score: score[5].score,
|
||||
level: score[5].level || null,
|
||||
knowledge_codes: score[5].knowledge_codes || '',
|
||||
});
|
||||
}
|
||||
if (score[6]) {
|
||||
@ -219,10 +275,33 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
number: score[6].number,
|
||||
score: score[6].score,
|
||||
missed_score: 0,
|
||||
level: score[6].level || null,
|
||||
knowledge_codes: score[6].knowledge_codes || '',
|
||||
});
|
||||
}
|
||||
setSpinInit(false);
|
||||
setInit(false);
|
||||
|
||||
// 预加载各题型的知识点选项(用于回显)
|
||||
if (arr.length > 0) {
|
||||
const categoryIds = arr.join(',');
|
||||
[1, 2, 3, 4, 5, 6].forEach((type) => {
|
||||
const typeScore = score[type];
|
||||
if (typeScore && typeScore.knowledge_codes) {
|
||||
question
|
||||
.getKnowledgeCodes(categoryIds, type, typeScore.level || null)
|
||||
.then((res: any) => {
|
||||
const items = res.data || [];
|
||||
const options = items.map((item: { code: string; name: string }) => ({
|
||||
value: item.code,
|
||||
label: item.name,
|
||||
}));
|
||||
setKnowledgeOptions((prev) => ({ ...prev, [type]: options }));
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -609,6 +688,42 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
></InputNumber>
|
||||
{t('exam.paper.compose.text4')}
|
||||
</div>
|
||||
<div className="d-flex ml-30">
|
||||
难度
|
||||
<Select
|
||||
style={{ width: 100, marginLeft: 8 }}
|
||||
size="large"
|
||||
options={levelOptions}
|
||||
value={choice.level}
|
||||
onChange={(value) => {
|
||||
const obj = { ...choice };
|
||||
obj.level = value;
|
||||
obj.knowledge_codes = '';
|
||||
setChoice(obj);
|
||||
loadKnowledgeCodes(1, value);
|
||||
}}
|
||||
allowClear
|
||||
placeholder="不限"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex ml-30">
|
||||
知识点
|
||||
<Select
|
||||
style={{ width: 200, marginLeft: 8 }}
|
||||
size="large"
|
||||
mode="multiple"
|
||||
options={knowledgeOptions[1]}
|
||||
value={choice.knowledge_codes ? choice.knowledge_codes.split(',') : []}
|
||||
onChange={(values: string[]) => {
|
||||
const obj = { ...choice };
|
||||
obj.knowledge_codes = values.join(',');
|
||||
setChoice(obj);
|
||||
}}
|
||||
maxTagCount="responsive"
|
||||
placeholder="不限"
|
||||
onFocus={() => loadKnowledgeCodes(1, choice.level)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex">
|
||||
{t('exam.paper.compose.text1')}
|
||||
@ -619,7 +734,7 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
)}
|
||||
{q2 > 0 && (
|
||||
<div className={styles['config-item']}>
|
||||
<div className="d-flex">
|
||||
<div className="d-flex" style={{ flexWrap: 'wrap', gap: '8px 0' }}>
|
||||
<div className={styles['label']}>
|
||||
<span className="c-red">*</span>
|
||||
{t('exam.question.select.label')}({t('exam.paper.compose.text1')}
|
||||
@ -679,6 +794,42 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
></InputNumber>
|
||||
{t('exam.paper.compose.text5')}
|
||||
</div>
|
||||
<div className="d-flex ml-30">
|
||||
难度
|
||||
<Select
|
||||
style={{ width: 100, marginLeft: 8 }}
|
||||
size="large"
|
||||
options={levelOptions}
|
||||
value={select.level}
|
||||
onChange={(value) => {
|
||||
const obj = { ...select };
|
||||
obj.level = value;
|
||||
obj.knowledge_codes = '';
|
||||
setSelect(obj);
|
||||
loadKnowledgeCodes(2, value);
|
||||
}}
|
||||
allowClear
|
||||
placeholder="不限"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex ml-30">
|
||||
知识点
|
||||
<Select
|
||||
style={{ width: 200, marginLeft: 8 }}
|
||||
size="large"
|
||||
mode="multiple"
|
||||
options={knowledgeOptions[2]}
|
||||
value={select.knowledge_codes ? select.knowledge_codes.split(',') : []}
|
||||
onChange={(values: string[]) => {
|
||||
const obj = { ...select };
|
||||
obj.knowledge_codes = values.join(',');
|
||||
setSelect(obj);
|
||||
}}
|
||||
maxTagCount="responsive"
|
||||
placeholder="不限"
|
||||
onFocus={() => loadKnowledgeCodes(2, select.level)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex">
|
||||
{t('exam.paper.compose.text1')}
|
||||
@ -689,7 +840,7 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
)}
|
||||
{q3 > 0 && (
|
||||
<div className={styles['config-item']}>
|
||||
<div className="d-flex">
|
||||
<div className="d-flex" style={{ flexWrap: 'wrap', gap: '8px 0' }}>
|
||||
<div className={styles['label']}>
|
||||
<span className="c-red">*</span>
|
||||
{t('exam.question.input.label')}({t('exam.paper.compose.text1')}
|
||||
@ -730,6 +881,42 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
></InputNumber>
|
||||
{t('exam.paper.compose.text4')}
|
||||
</div>
|
||||
<div className="d-flex ml-30">
|
||||
难度
|
||||
<Select
|
||||
style={{ width: 100, marginLeft: 8 }}
|
||||
size="large"
|
||||
options={levelOptions}
|
||||
value={input.level}
|
||||
onChange={(value) => {
|
||||
const obj = { ...input };
|
||||
obj.level = value;
|
||||
obj.knowledge_codes = '';
|
||||
setInput(obj);
|
||||
loadKnowledgeCodes(3, value);
|
||||
}}
|
||||
allowClear
|
||||
placeholder="不限"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex ml-30">
|
||||
知识点
|
||||
<Select
|
||||
style={{ width: 200, marginLeft: 8 }}
|
||||
size="large"
|
||||
mode="multiple"
|
||||
options={knowledgeOptions[3]}
|
||||
value={input.knowledge_codes ? input.knowledge_codes.split(',') : []}
|
||||
onChange={(values: string[]) => {
|
||||
const obj = { ...input };
|
||||
obj.knowledge_codes = values.join(',');
|
||||
setInput(obj);
|
||||
}}
|
||||
maxTagCount="responsive"
|
||||
placeholder="不限"
|
||||
onFocus={() => loadKnowledgeCodes(3, input.level)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex">
|
||||
{t('exam.paper.compose.text1')}
|
||||
@ -740,7 +927,7 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
)}
|
||||
{q4 > 0 && (
|
||||
<div className={styles['config-item']}>
|
||||
<div className="d-flex">
|
||||
<div className="d-flex" style={{ flexWrap: 'wrap', gap: '8px 0' }}>
|
||||
<div className={styles['label']}>
|
||||
<span className="c-red">*</span>
|
||||
{t('exam.question.judge.label')}({t('exam.paper.compose.text1')}
|
||||
@ -781,6 +968,42 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
></InputNumber>
|
||||
{t('exam.paper.compose.text4')}
|
||||
</div>
|
||||
<div className="d-flex ml-30">
|
||||
难度
|
||||
<Select
|
||||
style={{ width: 100, marginLeft: 8 }}
|
||||
size="large"
|
||||
options={levelOptions}
|
||||
value={judge.level}
|
||||
onChange={(value) => {
|
||||
const obj = { ...judge };
|
||||
obj.level = value;
|
||||
obj.knowledge_codes = '';
|
||||
setJudge(obj);
|
||||
loadKnowledgeCodes(4, value);
|
||||
}}
|
||||
allowClear
|
||||
placeholder="不限"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex ml-30">
|
||||
知识点
|
||||
<Select
|
||||
style={{ width: 200, marginLeft: 8 }}
|
||||
size="large"
|
||||
mode="multiple"
|
||||
options={knowledgeOptions[4]}
|
||||
value={judge.knowledge_codes ? judge.knowledge_codes.split(',') : []}
|
||||
onChange={(values: string[]) => {
|
||||
const obj = { ...judge };
|
||||
obj.knowledge_codes = values.join(',');
|
||||
setJudge(obj);
|
||||
}}
|
||||
maxTagCount="responsive"
|
||||
placeholder="不限"
|
||||
onFocus={() => loadKnowledgeCodes(4, judge.level)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex">
|
||||
{t('exam.paper.compose.text1')}
|
||||
@ -791,7 +1014,7 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
)}
|
||||
{q5 > 0 && (
|
||||
<div className={styles['config-item']}>
|
||||
<div className="d-flex">
|
||||
<div className="d-flex" style={{ flexWrap: 'wrap', gap: '8px 0' }}>
|
||||
<div className={styles['label']}>
|
||||
<span className="c-red">*</span>
|
||||
{t('exam.question.qa.label')}({t('exam.paper.compose.text1')}
|
||||
@ -832,6 +1055,42 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
></InputNumber>
|
||||
{t('exam.paper.compose.text4')}
|
||||
</div>
|
||||
<div className="d-flex ml-30">
|
||||
难度
|
||||
<Select
|
||||
style={{ width: 100, marginLeft: 8 }}
|
||||
size="large"
|
||||
options={levelOptions}
|
||||
value={qa.level}
|
||||
onChange={(value) => {
|
||||
const obj = { ...qa };
|
||||
obj.level = value;
|
||||
obj.knowledge_codes = '';
|
||||
setQa(obj);
|
||||
loadKnowledgeCodes(5, value);
|
||||
}}
|
||||
allowClear
|
||||
placeholder="不限"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex ml-30">
|
||||
知识点
|
||||
<Select
|
||||
style={{ width: 200, marginLeft: 8 }}
|
||||
size="large"
|
||||
mode="multiple"
|
||||
options={knowledgeOptions[5]}
|
||||
value={qa.knowledge_codes ? qa.knowledge_codes.split(',') : []}
|
||||
onChange={(values: string[]) => {
|
||||
const obj = { ...qa };
|
||||
obj.knowledge_codes = values.join(',');
|
||||
setQa(obj);
|
||||
}}
|
||||
maxTagCount="responsive"
|
||||
placeholder="不限"
|
||||
onFocus={() => loadKnowledgeCodes(5, qa.level)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex">
|
||||
{t('exam.paper.compose.text1')}
|
||||
@ -842,7 +1101,7 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
)}
|
||||
{q6 > 0 && (
|
||||
<div className={styles['config-item']}>
|
||||
<div className="d-flex">
|
||||
<div className="d-flex" style={{ flexWrap: 'wrap', gap: '8px 0' }}>
|
||||
<div className={styles['label']}>
|
||||
<span className="c-red">*</span>
|
||||
{t('exam.question.cap.label')}({t('exam.paper.compose.text1')}
|
||||
@ -883,6 +1142,42 @@ export const RendomPaper: React.FC<PropInterface> = ({ type }) => {
|
||||
></InputNumber>
|
||||
{t('exam.paper.compose.text4')}
|
||||
</div>
|
||||
<div className="d-flex ml-30">
|
||||
难度
|
||||
<Select
|
||||
style={{ width: 100, marginLeft: 8 }}
|
||||
size="large"
|
||||
options={levelOptions}
|
||||
value={cap.level}
|
||||
onChange={(value) => {
|
||||
const obj = { ...cap };
|
||||
obj.level = value;
|
||||
obj.knowledge_codes = '';
|
||||
setCap(obj);
|
||||
loadKnowledgeCodes(6, value);
|
||||
}}
|
||||
allowClear
|
||||
placeholder="不限"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex ml-30">
|
||||
知识点
|
||||
<Select
|
||||
style={{ width: 200, marginLeft: 8 }}
|
||||
size="large"
|
||||
mode="multiple"
|
||||
options={knowledgeOptions[6]}
|
||||
value={cap.knowledge_codes ? cap.knowledge_codes.split(',') : []}
|
||||
onChange={(values: string[]) => {
|
||||
const obj = { ...cap };
|
||||
obj.knowledge_codes = values.join(',');
|
||||
setCap(obj);
|
||||
}}
|
||||
maxTagCount="responsive"
|
||||
placeholder="不限"
|
||||
onFocus={() => loadKnowledgeCodes(6, cap.level)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex">
|
||||
{t('exam.paper.compose.text1')}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal, Form, Tabs, Radio, Spin, message } from 'antd';
|
||||
import { Modal, Form, Tabs, Radio, Spin, message, Cascader } from 'antd';
|
||||
import type { TabsProps } from 'antd';
|
||||
import { question } from '../../../../api/index';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { question, textbook } from '../../../../api/index';
|
||||
import { QuestionInput } from '../../../../compenents';
|
||||
import { QChoice } from './choice';
|
||||
import { QSelect } from './select';
|
||||
@ -11,6 +12,15 @@ import { QQa } from './qa';
|
||||
import { QCap } from './cap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
// 级联选择器选项类型
|
||||
interface CascaderOption {
|
||||
value: string | number;
|
||||
label: string;
|
||||
children?: CascaderOption[];
|
||||
isLeaf?: boolean;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
interface PropInterface {
|
||||
id: number;
|
||||
open: boolean;
|
||||
@ -24,6 +34,7 @@ export const QuestionsDetailCreate: React.FC<PropInterface> = ({ id, open, onCan
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [type, setType] = useState('1');
|
||||
const [cascaderOptions, setCascaderOptions] = useState<CascaderOption[]>([]); // 级联选择器选项
|
||||
const [formParams, setFormParams] = useState({
|
||||
v: 'v1',
|
||||
d: {
|
||||
@ -38,6 +49,7 @@ export const QuestionsDetailCreate: React.FC<PropInterface> = ({ id, open, onCan
|
||||
setType('1');
|
||||
form.setFieldsValue({
|
||||
level: 1,
|
||||
knowledge_cascader: [],
|
||||
});
|
||||
setFormParams({
|
||||
v: 'v1',
|
||||
@ -46,10 +58,49 @@ export const QuestionsDetailCreate: React.FC<PropInterface> = ({ id, open, onCan
|
||||
remark: null,
|
||||
},
|
||||
});
|
||||
// 加载教材列表作为级联选择器第一级
|
||||
textbook.getTextbookSelectListApi().then((res: any) => {
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
const options: CascaderOption[] = res.data.map((item: any) => ({
|
||||
value: item.id,
|
||||
label: item.title,
|
||||
isLeaf: false, // 表示有子节点
|
||||
}));
|
||||
setCascaderOptions(options);
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error('加载教材列表失败:', err);
|
||||
});
|
||||
setInit(false);
|
||||
}
|
||||
}, [form, open, id]);
|
||||
|
||||
// 级联选择器动态加载知识点
|
||||
const loadKnowledgeData = (selectedOptions: CascaderOption[]) => {
|
||||
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||
targetOption.loading = true;
|
||||
|
||||
// 加载该教材下的知识点
|
||||
textbook.getKnowledgeListApi(targetOption.value as number).then((res: any) => {
|
||||
targetOption.loading = false;
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
targetOption.children = res.data.map((item: any) => ({
|
||||
value: item.knowledgeCode || item.knowledge_code,
|
||||
label: item.name,
|
||||
isLeaf: true, // 知识点是叶子节点
|
||||
}));
|
||||
} else {
|
||||
targetOption.children = [];
|
||||
}
|
||||
setCascaderOptions([...cascaderOptions]);
|
||||
}).catch((err) => {
|
||||
console.error('加载知识点失败:', err);
|
||||
targetOption.loading = false;
|
||||
targetOption.children = [];
|
||||
setCascaderOptions([...cascaderOptions]);
|
||||
});
|
||||
};
|
||||
|
||||
const items: TabsProps['items'] = [
|
||||
{
|
||||
key: '1',
|
||||
@ -289,8 +340,19 @@ export const QuestionsDetailCreate: React.FC<PropInterface> = ({ id, open, onCan
|
||||
}
|
||||
}
|
||||
const params = JSON.stringify(formParams);
|
||||
// 处理知识点:从级联选择器值中提取知识点编码(第二级的值)
|
||||
let knowledgeCode: string | undefined = undefined;
|
||||
if (values.knowledge_cascader && values.knowledge_cascader.length > 0) {
|
||||
// 级联选择器多选值格式: [[textbook_id, knowledge_code], ...]
|
||||
const codes = values.knowledge_cascader
|
||||
.filter((item: any[]) => item && item.length === 2)
|
||||
.map((item: any[]) => item[1]); // 取第二级的值(知识点编码)
|
||||
if (codes.length > 0) {
|
||||
knowledgeCode = codes.join(',');
|
||||
}
|
||||
}
|
||||
setLoading(true);
|
||||
question.questionStore(id, params, values.level, Number(type)).then((res: any) => {
|
||||
question.questionStore(id, params, values.level, Number(type), knowledgeCode).then((res: any) => {
|
||||
setLoading(false);
|
||||
message.success(t('commen.saveSuccess'));
|
||||
onCancel();
|
||||
@ -413,6 +475,27 @@ export const QuestionsDetailCreate: React.FC<PropInterface> = ({ id, open, onCan
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="关联知识点"
|
||||
name="knowledge_cascader"
|
||||
>
|
||||
<Cascader
|
||||
options={cascaderOptions}
|
||||
loadData={loadKnowledgeData as CascaderProps<CascaderOption>['loadData']}
|
||||
multiple
|
||||
maxTagCount="responsive"
|
||||
placeholder="请选择教材和知识点(可多选)"
|
||||
style={{ width: '100%' }}
|
||||
showCheckedStrategy={Cascader.SHOW_CHILD}
|
||||
showSearch={{
|
||||
filter: (inputValue: string, path: CascaderOption[]) =>
|
||||
path.some(
|
||||
(option) =>
|
||||
(option.label as string).toLowerCase().indexOf(inputValue.toLowerCase()) > -1
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('exam.question.detail.edit.name')}
|
||||
name="content"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal, Form, Radio, Spin, message } from 'antd';
|
||||
import { question } from '../../../../api/index';
|
||||
import { Modal, Form, Radio, Spin, message, Cascader } from 'antd';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { question, textbook } from '../../../../api/index';
|
||||
import { QuestionInput } from '../../../../compenents';
|
||||
import { QChoice } from './choice';
|
||||
import { QSelect } from './select';
|
||||
@ -10,6 +11,15 @@ import { QQa } from './qa';
|
||||
import { QCap } from './cap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
// 级联选择器选项类型
|
||||
interface CascaderOption {
|
||||
value: string | number;
|
||||
label: string;
|
||||
children?: CascaderOption[];
|
||||
isLeaf?: boolean;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
interface PropInterface {
|
||||
id: number;
|
||||
qid: number;
|
||||
@ -25,6 +35,7 @@ export const QuestionsDetailUpdate: React.FC<PropInterface> = ({ id, qid, open,
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [type, setType] = useState('1');
|
||||
const [resourceUrl, setResourceUrl] = useState<ResourceUrlModel>({});
|
||||
const [cascaderOptions, setCascaderOptions] = useState<CascaderOption[]>([]); // 级联选择器选项
|
||||
const [formParams, setFormParams] = useState({
|
||||
v: 'v1',
|
||||
d: {
|
||||
@ -40,21 +51,107 @@ export const QuestionsDetailUpdate: React.FC<PropInterface> = ({ id, qid, open,
|
||||
}
|
||||
}, [form, open, id, qid]);
|
||||
|
||||
const getDetail = () => {
|
||||
question.questionDetail(qid).then((res: any) => {
|
||||
const getDetail = async () => {
|
||||
try {
|
||||
// 1. 加载教材列表作为级联选择器第一级
|
||||
const textbookRes = await textbook.getTextbookSelectListApi();
|
||||
let options: CascaderOption[] = [];
|
||||
if (textbookRes.data && Array.isArray(textbookRes.data)) {
|
||||
options = textbookRes.data.map((item: any) => ({
|
||||
value: item.id,
|
||||
label: item.title,
|
||||
isLeaf: false,
|
||||
}));
|
||||
setCascaderOptions(options);
|
||||
}
|
||||
|
||||
// 2. 加载试题详情
|
||||
const res: any = await question.questionDetail(qid);
|
||||
setResourceUrl(res.data.resource_url);
|
||||
const data = res.data.question;
|
||||
setType(String(data.type));
|
||||
const params = JSON.parse(res.data.question.content);
|
||||
|
||||
// 3. 处理知识点回显
|
||||
let cascaderValue: (string | number)[][] = [];
|
||||
if (data.knowledge_code) {
|
||||
try {
|
||||
// 获取知识点详情(包含bookId)
|
||||
const knowledgeRes: any = await textbook.getKnowledgeByCodesApi(data.knowledge_code);
|
||||
if (
|
||||
knowledgeRes.data &&
|
||||
Array.isArray(knowledgeRes.data) &&
|
||||
knowledgeRes.data.length > 0
|
||||
) {
|
||||
// 获取所有需要预加载的教材ID(去重)
|
||||
const bookIds = [...new Set(knowledgeRes.data.map((k: any) => k.bookId))];
|
||||
|
||||
// 预加载每个教材的知识点列表到级联选项中
|
||||
for (const bookId of bookIds) {
|
||||
const knowledgeListRes: any = await textbook.getKnowledgeListApi(bookId as number);
|
||||
const targetOption = options.find((opt) => opt.value === bookId);
|
||||
if (targetOption && knowledgeListRes.data && Array.isArray(knowledgeListRes.data)) {
|
||||
targetOption.children = knowledgeListRes.data.map((item: any) => ({
|
||||
value: item.knowledgeCode || item.knowledge_code,
|
||||
label: item.name,
|
||||
isLeaf: true,
|
||||
}));
|
||||
}
|
||||
}
|
||||
setCascaderOptions([...options]);
|
||||
|
||||
// 构建级联选择器的值 [[bookId, knowledgeCode], ...]
|
||||
cascaderValue = knowledgeRes.data.map((k: any) => [
|
||||
k.bookId,
|
||||
k.knowledgeCode || k.knowledge_code,
|
||||
]);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载知识点详情失败:', err);
|
||||
}
|
||||
}
|
||||
|
||||
form.setFieldsValue({
|
||||
level: data.level,
|
||||
type: String(data.type),
|
||||
content: params.d.content,
|
||||
remark: params.d.remark,
|
||||
knowledge_cascader: cascaderValue,
|
||||
});
|
||||
setFormParams(params);
|
||||
setInit(false);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('加载数据失败:', err);
|
||||
setInit(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 级联选择器动态加载知识点
|
||||
const loadKnowledgeData = (selectedOptions: CascaderOption[]) => {
|
||||
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||
targetOption.loading = true;
|
||||
|
||||
textbook
|
||||
.getKnowledgeListApi(targetOption.value as number)
|
||||
.then((res: any) => {
|
||||
targetOption.loading = false;
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
targetOption.children = res.data.map((item: any) => ({
|
||||
value: item.knowledgeCode || item.knowledge_code,
|
||||
label: item.name,
|
||||
isLeaf: true,
|
||||
}));
|
||||
} else {
|
||||
targetOption.children = [];
|
||||
}
|
||||
setCascaderOptions([...cascaderOptions]);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('加载知识点失败:', err);
|
||||
targetOption.loading = false;
|
||||
targetOption.children = [];
|
||||
setCascaderOptions([...cascaderOptions]);
|
||||
});
|
||||
};
|
||||
|
||||
const onFinish = (values: any) => {
|
||||
@ -268,12 +365,24 @@ export const QuestionsDetailUpdate: React.FC<PropInterface> = ({ id, qid, open,
|
||||
}
|
||||
}
|
||||
const params = JSON.stringify(formParams);
|
||||
// 处理知识点:从级联选择器值中提取知识点编码(第二级的值)
|
||||
let knowledgeCode: string | undefined = undefined;
|
||||
if (values.knowledge_cascader && values.knowledge_cascader.length > 0) {
|
||||
const codes = values.knowledge_cascader
|
||||
.filter((item: any[]) => item && item.length === 2)
|
||||
.map((item: any[]) => item[1]);
|
||||
if (codes.length > 0) {
|
||||
knowledgeCode = codes.join(',');
|
||||
}
|
||||
}
|
||||
setLoading(true);
|
||||
question.questionUpdate(qid, id, params, values.level, Number(type)).then((res: any) => {
|
||||
setLoading(false);
|
||||
message.success(t('commen.saveSuccess'));
|
||||
onCancel();
|
||||
});
|
||||
question
|
||||
.questionUpdate(qid, id, params, values.level, Number(type), knowledgeCode)
|
||||
.then((res: any) => {
|
||||
setLoading(false);
|
||||
message.success(t('commen.saveSuccess'));
|
||||
onCancel();
|
||||
});
|
||||
};
|
||||
|
||||
const onFinishFailed = (errorInfo: any) => {
|
||||
@ -438,6 +547,26 @@ export const QuestionsDetailUpdate: React.FC<PropInterface> = ({ id, qid, open,
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="关联知识点" name="knowledge_cascader">
|
||||
<Cascader
|
||||
options={cascaderOptions}
|
||||
loadData={loadKnowledgeData as CascaderProps<CascaderOption>['loadData']}
|
||||
multiple
|
||||
maxTagCount="responsive"
|
||||
placeholder="请选择教材和知识点(可多选)"
|
||||
style={{ width: '100%' }}
|
||||
showCheckedStrategy={Cascader.SHOW_CHILD}
|
||||
showSearch={{
|
||||
filter: (inputValue: string, path: CascaderOption[]) =>
|
||||
path.some(
|
||||
(option) =>
|
||||
(option.label as string)
|
||||
.toLowerCase()
|
||||
.indexOf(inputValue.toLowerCase()) > -1
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('exam.question.detail.edit.name')}
|
||||
name="content"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user