feat(exam): 试题关联知识点功能
- 后端:ExamQuestion 实体新增 knowledge_code 字段存储关联知识点 - 后端:TextbookController 新增教材下拉列表、知识点列表接口 - 后端:KnowledgeController 新增按编码列表查询知识点接口 - 前端:试题创建/编辑页面增加教材-知识点两级级联选择器 - 支持多选知识点,编辑时自动回显已关联知识点
This commit is contained in:
parent
a50746d3c5
commit
f7e04de5e5
@ -74,6 +74,7 @@ public class QuestionController {
|
|||||||
String content = MapUtils.getString(params, "content");
|
String content = MapUtils.getString(params, "content");
|
||||||
Integer level = MapUtils.getInteger(params, "level");
|
Integer level = MapUtils.getInteger(params, "level");
|
||||||
Integer type = MapUtils.getInteger(params, "type");
|
Integer type = MapUtils.getInteger(params, "type");
|
||||||
|
String knowledgeCode = MapUtils.getString(params, "knowledge_code");
|
||||||
|
|
||||||
ExamQuestionPaginateFilter filter = new ExamQuestionPaginateFilter();
|
ExamQuestionPaginateFilter filter = new ExamQuestionPaginateFilter();
|
||||||
filter.setSortAlgo(sortAlgo);
|
filter.setSortAlgo(sortAlgo);
|
||||||
@ -82,6 +83,7 @@ public class QuestionController {
|
|||||||
filter.setContent(content);
|
filter.setContent(content);
|
||||||
filter.setLevel(level);
|
filter.setLevel(level);
|
||||||
filter.setType(type);
|
filter.setType(type);
|
||||||
|
filter.setKnowledgeCode(knowledgeCode);
|
||||||
|
|
||||||
if (!backendBus.isSuperAdmin()) { // 非超管只能读取它自己的题库
|
if (!backendBus.isSuperAdmin()) { // 非超管只能读取它自己的题库
|
||||||
filter.setAdminIds(backendBus.getSameDataPermissionAdminIds());
|
filter.setAdminIds(backendBus.getSameDataPermissionAdminIds());
|
||||||
@ -125,6 +127,7 @@ public class QuestionController {
|
|||||||
String content = MapUtils.getString(params, "content");
|
String content = MapUtils.getString(params, "content");
|
||||||
Integer level = MapUtils.getInteger(params, "level");
|
Integer level = MapUtils.getInteger(params, "level");
|
||||||
Integer type = MapUtils.getInteger(params, "type");
|
Integer type = MapUtils.getInteger(params, "type");
|
||||||
|
String knowledgeCode = MapUtils.getString(params, "knowledge_code");
|
||||||
|
|
||||||
List<ExamQuestionCategory> questionCategories = new ArrayList<>();
|
List<ExamQuestionCategory> questionCategories = new ArrayList<>();
|
||||||
if (StringUtil.isNotNull(categoryId)) {
|
if (StringUtil.isNotNull(categoryId)) {
|
||||||
@ -147,6 +150,7 @@ public class QuestionController {
|
|||||||
filter.setContent(content);
|
filter.setContent(content);
|
||||||
filter.setLevel(level);
|
filter.setLevel(level);
|
||||||
filter.setType(type);
|
filter.setType(type);
|
||||||
|
filter.setKnowledgeCode(knowledgeCode);
|
||||||
|
|
||||||
if (!backendBus.isSuperAdmin()) { // 非超管只能读取它自己的题库
|
if (!backendBus.isSuperAdmin()) { // 非超管只能读取它自己的题库
|
||||||
filter.setAdminIds(backendBus.getSameDataPermissionAdminIds());
|
filter.setAdminIds(backendBus.getSameDataPermissionAdminIds());
|
||||||
@ -169,6 +173,7 @@ public class QuestionController {
|
|||||||
req.getCategoryId(),
|
req.getCategoryId(),
|
||||||
req.getContent().replaceAll(" ", ""),
|
req.getContent().replaceAll(" ", ""),
|
||||||
req.getLevel(),
|
req.getLevel(),
|
||||||
|
req.getKnowledgeCode(),
|
||||||
req.getType(),
|
req.getType(),
|
||||||
BCtx.getId());
|
BCtx.getId());
|
||||||
|
|
||||||
@ -198,6 +203,7 @@ public class QuestionController {
|
|||||||
examQuestion.getId(),
|
examQuestion.getId(),
|
||||||
req.getContent().replaceAll(" ", ""),
|
req.getContent().replaceAll(" ", ""),
|
||||||
req.getLevel(),
|
req.getLevel(),
|
||||||
|
req.getKnowledgeCode(),
|
||||||
req.getType(),
|
req.getType(),
|
||||||
req.getCategoryId(),
|
req.getCategoryId(),
|
||||||
BCtx.getId());
|
BCtx.getId());
|
||||||
|
|||||||
@ -72,4 +72,20 @@ public class KnowledgeController {
|
|||||||
knowledgeService.remove(queryWrapper);
|
knowledgeService.remove(queryWrapper);
|
||||||
return JsonResponse.success();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package xyz.playedu.api.controller.backend.jc;
|
package xyz.playedu.api.controller.backend.jc;
|
||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import org.apache.commons.collections4.MapUtils;
|
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -37,7 +36,10 @@ import xyz.playedu.jc.service.ITextbookService;
|
|||||||
import xyz.playedu.jc.service.JCIResourceService;
|
import xyz.playedu.jc.service.JCIResourceService;
|
||||||
import xyz.playedu.knowledge.domain.KnowledgeMessages;
|
import xyz.playedu.knowledge.domain.KnowledgeMessages;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -273,6 +275,22 @@ public class TextbookController {
|
|||||||
return JsonResponse.data(list);
|
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}")
|
// @GetMapping("/{id}")
|
||||||
// public JsonResponse detail(@PathVariable("id") Integer id) {
|
// public JsonResponse detail(@PathVariable("id") Integer id) {
|
||||||
// Textbook one = textbookService.getById(id);
|
// Textbook one = textbookService.getById(id);
|
||||||
@ -347,7 +365,4 @@ public class TextbookController {
|
|||||||
textbookService.updateById(textbook);
|
textbookService.updateById(textbook);
|
||||||
return JsonResponse.success();
|
return JsonResponse.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -24,6 +24,9 @@ public class ExamQuestionRequest implements Serializable {
|
|||||||
@NotNull(message = "level参数为空")
|
@NotNull(message = "level参数为空")
|
||||||
private Integer level;
|
private Integer level;
|
||||||
|
|
||||||
|
@JsonProperty("knowledge_code")
|
||||||
|
private String knowledgeCode;
|
||||||
|
|
||||||
@NotNull(message = "type参数为空")
|
@NotNull(message = "type参数为空")
|
||||||
private Integer type;
|
private Integer type;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,8 @@ public class ExamQuestionPaginateFilter {
|
|||||||
|
|
||||||
private String categoryId;
|
private String categoryId;
|
||||||
|
|
||||||
|
private String knowledgeCode;
|
||||||
|
|
||||||
private List<Integer> adminIds;
|
private List<Integer> adminIds;
|
||||||
|
|
||||||
private String sortField;
|
private String sortField;
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import com.baomidou.mybatisplus.annotation.TableId;
|
|||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import xyz.playedu.framework.tenant.core.db.TenantBaseDO;
|
import xyz.playedu.framework.tenant.core.db.TenantBaseDO;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -38,7 +37,7 @@ public class Knowledge extends TenantBaseDO {
|
|||||||
private String knowledgeCode;
|
private String knowledgeCode;
|
||||||
|
|
||||||
/** 知识点介绍 */
|
/** 知识点介绍 */
|
||||||
@TableField("desc")
|
@TableField("`desc`")
|
||||||
private String desc;
|
private String desc;
|
||||||
|
|
||||||
/** 层级 */
|
/** 层级 */
|
||||||
|
|||||||
@ -47,7 +47,8 @@ public class KnowledgeServiceImpl extends ServiceImpl<KnowledgeMapper, Knowledge
|
|||||||
public List<Knowledge> listVo(KnowledgeParam param) {
|
public List<Knowledge> listVo(KnowledgeParam param) {
|
||||||
//获取知识点卡片
|
//获取知识点卡片
|
||||||
LambdaQueryWrapper<Knowledge> queryWrapper = new LambdaQueryWrapper<>();
|
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);
|
.eq(Knowledge::getIsReal,"1").orderByAsc(Knowledge::getOrderNum);
|
||||||
return list(queryWrapper);
|
return list(queryWrapper);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,10 @@ public class ExamQuestion extends TenantBaseDO {
|
|||||||
/** 难度等级:1-4 */
|
/** 难度等级:1-4 */
|
||||||
private Integer level;
|
private Integer level;
|
||||||
|
|
||||||
|
/** 知识点CODE(多个用逗号分隔) */
|
||||||
|
@JsonProperty("knowledge_code")
|
||||||
|
private String knowledgeCode;
|
||||||
|
|
||||||
/** 内容 */
|
/** 内容 */
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
@ -78,6 +82,9 @@ public class ExamQuestion extends TenantBaseDO {
|
|||||||
&& (this.getLevel() == null
|
&& (this.getLevel() == null
|
||||||
? other.getLevel() == null
|
? other.getLevel() == null
|
||||||
: this.getLevel().equals(other.getLevel()))
|
: this.getLevel().equals(other.getLevel()))
|
||||||
|
&& (this.getKnowledgeCode() == null
|
||||||
|
? other.getKnowledgeCode() == null
|
||||||
|
: this.getKnowledgeCode().equals(other.getKnowledgeCode()))
|
||||||
&& (this.getContent() == null
|
&& (this.getContent() == null
|
||||||
? other.getContent() == null
|
? other.getContent() == null
|
||||||
: this.getContent().equals(other.getContent()))
|
: 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 + ((getAdminId() == null) ? 0 : getAdminId().hashCode());
|
||||||
result = prime * result + ((getType() == null) ? 0 : getType().hashCode());
|
result = prime * result + ((getType() == null) ? 0 : getType().hashCode());
|
||||||
result = prime * result + ((getLevel() == null) ? 0 : getLevel().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 + ((getContent() == null) ? 0 : getContent().hashCode());
|
||||||
result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode());
|
result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode());
|
||||||
result = prime * result + ((getUpdatedAt() == null) ? 0 : getUpdatedAt().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(", adminId=").append(adminId);
|
||||||
sb.append(", type=").append(type);
|
sb.append(", type=").append(type);
|
||||||
sb.append(", level=").append(level);
|
sb.append(", level=").append(level);
|
||||||
|
sb.append(", knowledgeCode=").append(knowledgeCode);
|
||||||
sb.append(", content=").append(content);
|
sb.append(", content=").append(content);
|
||||||
sb.append(", createdAt=").append(createdAt);
|
sb.append(", createdAt=").append(createdAt);
|
||||||
sb.append(", updatedAt=").append(updatedAt);
|
sb.append(", updatedAt=").append(updatedAt);
|
||||||
|
|||||||
@ -20,12 +20,18 @@ public interface ExamQuestionService extends IService<ExamQuestion> {
|
|||||||
PaginationResult<ExamQuestion> paginate(int page, int size, ExamQuestionPaginateFilter filter);
|
PaginationResult<ExamQuestion> paginate(int page, int size, ExamQuestionPaginateFilter filter);
|
||||||
|
|
||||||
Integer create(
|
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(
|
void update(
|
||||||
Integer id,
|
Integer id,
|
||||||
String content,
|
String content,
|
||||||
Integer level,
|
Integer level,
|
||||||
|
String knowledgeCode,
|
||||||
Integer type,
|
Integer type,
|
||||||
Integer categoryId,
|
Integer categoryId,
|
||||||
Integer adminId);
|
Integer adminId);
|
||||||
|
|||||||
@ -50,13 +50,19 @@ public class ExamQuestionServiceImpl extends ServiceImpl<ExamQuestionMapper, Exa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer create(
|
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 =
|
ExamQuestion examQuestion =
|
||||||
new ExamQuestion() {
|
new ExamQuestion() {
|
||||||
{
|
{
|
||||||
setCategoryId(categoryId);
|
setCategoryId(categoryId);
|
||||||
setAdminId(adminId);
|
setAdminId(adminId);
|
||||||
setLevel(level);
|
setLevel(level);
|
||||||
|
setKnowledgeCode(knowledgeCode);
|
||||||
setType(type);
|
setType(type);
|
||||||
setContent(content);
|
setContent(content);
|
||||||
setCreatedAt(new Date());
|
setCreatedAt(new Date());
|
||||||
@ -72,6 +78,7 @@ public class ExamQuestionServiceImpl extends ServiceImpl<ExamQuestionMapper, Exa
|
|||||||
Integer id,
|
Integer id,
|
||||||
String content,
|
String content,
|
||||||
Integer level,
|
Integer level,
|
||||||
|
String knowledgeCode,
|
||||||
Integer type,
|
Integer type,
|
||||||
Integer categoryId,
|
Integer categoryId,
|
||||||
Integer adminId) {
|
Integer adminId) {
|
||||||
@ -82,6 +89,7 @@ public class ExamQuestionServiceImpl extends ServiceImpl<ExamQuestionMapper, Exa
|
|||||||
question.setCategoryId(categoryId);
|
question.setCategoryId(categoryId);
|
||||||
question.setContent(content);
|
question.setContent(content);
|
||||||
question.setLevel(level);
|
question.setLevel(level);
|
||||||
|
question.setKnowledgeCode(knowledgeCode);
|
||||||
question.setType(type);
|
question.setType(type);
|
||||||
question.setUpdatedAt(new Date());
|
question.setUpdatedAt(new Date());
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
<result property="adminId" column="admin_id" jdbcType="INTEGER"/>
|
<result property="adminId" column="admin_id" jdbcType="INTEGER"/>
|
||||||
<result property="type" column="type" jdbcType="INTEGER"/>
|
<result property="type" column="type" jdbcType="INTEGER"/>
|
||||||
<result property="level" column="level" jdbcType="TINYINT"/>
|
<result property="level" column="level" jdbcType="TINYINT"/>
|
||||||
|
<result property="knowledgeCode" column="knowledge_code" jdbcType="VARCHAR"/>
|
||||||
<result property="content" column="content" jdbcType="VARCHAR"/>
|
<result property="content" column="content" jdbcType="VARCHAR"/>
|
||||||
<result property="createdAt" column="created_at" jdbcType="TIMESTAMP"/>
|
<result property="createdAt" column="created_at" jdbcType="TIMESTAMP"/>
|
||||||
<result property="updatedAt" column="updated_at" jdbcType="TIMESTAMP"/>
|
<result property="updatedAt" column="updated_at" jdbcType="TIMESTAMP"/>
|
||||||
@ -18,7 +19,7 @@
|
|||||||
|
|
||||||
<sql id="Base_Column_List">
|
<sql id="Base_Column_List">
|
||||||
id,category_id,admin_id,type,
|
id,category_id,admin_id,type,
|
||||||
level,content,created_at,
|
level,knowledge_code,content,created_at,
|
||||||
updated_at,deleted
|
updated_at,deleted
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
@ -42,6 +43,9 @@
|
|||||||
<if test="level != null">
|
<if test="level != null">
|
||||||
AND `exam_question`.`level` = #{level}
|
AND `exam_question`.`level` = #{level}
|
||||||
</if>
|
</if>
|
||||||
|
<if test="knowledgeCode != null and knowledgeCode != ''">
|
||||||
|
AND FIND_IN_SET(#{knowledgeCode}, `exam_question`.`knowledge_code`)
|
||||||
|
</if>
|
||||||
</where>
|
</where>
|
||||||
|
|
||||||
<if test="sortAlgo == 'asc'">
|
<if test="sortAlgo == 'asc'">
|
||||||
@ -87,6 +91,9 @@
|
|||||||
<if test="level != null">
|
<if test="level != null">
|
||||||
AND `exam_question`.`level` = #{level}
|
AND `exam_question`.`level` = #{level}
|
||||||
</if>
|
</if>
|
||||||
|
<if test="knowledgeCode != null and knowledgeCode != ''">
|
||||||
|
AND FIND_IN_SET(#{knowledgeCode}, `exam_question`.`knowledge_code`)
|
||||||
|
</if>
|
||||||
</where>
|
</where>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|||||||
@ -54,7 +54,8 @@ export function questionList(
|
|||||||
sortAlgo: string,
|
sortAlgo: string,
|
||||||
content: string,
|
content: string,
|
||||||
level: any,
|
level: any,
|
||||||
type: any
|
type: any,
|
||||||
|
knowledgeCode?: string
|
||||||
) {
|
) {
|
||||||
return client.get('/backend/v1/exam/question/index', {
|
return client.get('/backend/v1/exam/question/index', {
|
||||||
category_id,
|
category_id,
|
||||||
@ -65,15 +66,23 @@ export function questionList(
|
|||||||
content,
|
content,
|
||||||
level,
|
level,
|
||||||
type,
|
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', {
|
return client.post('/backend/v1/exam/question/create', {
|
||||||
category_id,
|
category_id,
|
||||||
content,
|
content,
|
||||||
level,
|
level,
|
||||||
type,
|
type,
|
||||||
|
knowledge_code,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,13 +95,15 @@ export function questionUpdate(
|
|||||||
category_id: number,
|
category_id: number,
|
||||||
content: string,
|
content: string,
|
||||||
level: any,
|
level: any,
|
||||||
type: any
|
type: any,
|
||||||
|
knowledge_code?: string
|
||||||
) {
|
) {
|
||||||
return client.put(`/backend/v1/exam/question/${id}`, {
|
return client.put(`/backend/v1/exam/question/${id}`, {
|
||||||
category_id,
|
category_id,
|
||||||
content,
|
content,
|
||||||
level,
|
level,
|
||||||
type,
|
type,
|
||||||
|
knowledge_code,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -229,6 +229,25 @@ export function EditChapterApi(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
* resource List
|
||||||
* */
|
* */
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
Spin,
|
Spin,
|
||||||
message,
|
message,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { question, resourceCategory } from '../../api';
|
import { question, resourceCategory, textbook } from '../../api';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
import styles from './index.module.less';
|
import styles from './index.module.less';
|
||||||
import { TreeQuestion } from '../../compenents';
|
import { TreeQuestion } from '../../compenents';
|
||||||
@ -63,6 +63,11 @@ export const AddQuestion = (props: PropsInterface) => {
|
|||||||
const [selectedIds, setSelectedIds] = useState<any>([]);
|
const [selectedIds, setSelectedIds] = useState<any>([]);
|
||||||
const [selectVideos, setSelectVideos] = useState<any[]>([]);
|
const [selectVideos, setSelectVideos] = useState<any[]>([]);
|
||||||
const [categories, setCategories] = useState<Option[]>([]);
|
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 [resourceUrl, setResourceUrl] = useState<ResourceUrlModel>({});
|
||||||
const types = [
|
const types = [
|
||||||
{ label: t('exam.question.choice.label2'), value: 1 },
|
{ label: t('exam.question.choice.label2'), value: 1 },
|
||||||
@ -92,10 +97,43 @@ export const AddQuestion = (props: PropsInterface) => {
|
|||||||
|
|
||||||
const initData = async () => {
|
const initData = async () => {
|
||||||
await getCategory();
|
await getCategory();
|
||||||
|
await getTextbookList();
|
||||||
await getList();
|
await getList();
|
||||||
setInit(false);
|
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 getCategory = async () => {
|
||||||
const res: any = await resourceCategory.resourceCategoryList();
|
const res: any = await resourceCategory.resourceCategoryList();
|
||||||
const categories = res.data.categories;
|
const categories = res.data.categories;
|
||||||
@ -122,7 +160,8 @@ export const AddQuestion = (props: PropsInterface) => {
|
|||||||
'',
|
'',
|
||||||
name,
|
name,
|
||||||
level.length === 0 ? '' : level,
|
level.length === 0 ? '' : level,
|
||||||
type.length === 0 ? '' : type
|
type.length === 0 ? '' : type,
|
||||||
|
knowledgeCode || undefined
|
||||||
);
|
);
|
||||||
setResourceUrl(res.data.resource_url);
|
setResourceUrl(res.data.resource_url);
|
||||||
const data = res.data.result.data;
|
const data = res.data.result.data;
|
||||||
@ -152,6 +191,9 @@ export const AddQuestion = (props: PropsInterface) => {
|
|||||||
setName('');
|
setName('');
|
||||||
setType([]);
|
setType([]);
|
||||||
setLevel([]);
|
setLevel([]);
|
||||||
|
setSelectedTextbookId(undefined);
|
||||||
|
setKnowledgeCode('');
|
||||||
|
setKnowledgeList([]);
|
||||||
setSelectedRowKeys([]);
|
setSelectedRowKeys([]);
|
||||||
setSelectedIds([]);
|
setSelectedIds([]);
|
||||||
setRefresh(!refresh);
|
setRefresh(!refresh);
|
||||||
@ -380,6 +422,46 @@ export const AddQuestion = (props: PropsInterface) => {
|
|||||||
placeholder={t('exam.question.detail.namePlaceholder2')}
|
placeholder={t('exam.question.detail.namePlaceholder2')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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}>
|
<Button className="mr-16" onClick={resetList}>
|
||||||
{t('commen.reset')}
|
{t('commen.reset')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
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 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 { QuestionInput } from '../../../../compenents';
|
||||||
import { QChoice } from './choice';
|
import { QChoice } from './choice';
|
||||||
import { QSelect } from './select';
|
import { QSelect } from './select';
|
||||||
@ -11,6 +12,15 @@ import { QQa } from './qa';
|
|||||||
import { QCap } from './cap';
|
import { QCap } from './cap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
// 级联选择器选项类型
|
||||||
|
interface CascaderOption {
|
||||||
|
value: string | number;
|
||||||
|
label: string;
|
||||||
|
children?: CascaderOption[];
|
||||||
|
isLeaf?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface PropInterface {
|
interface PropInterface {
|
||||||
id: number;
|
id: number;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -24,6 +34,7 @@ export const QuestionsDetailCreate: React.FC<PropInterface> = ({ id, open, onCan
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
const [type, setType] = useState('1');
|
const [type, setType] = useState('1');
|
||||||
|
const [cascaderOptions, setCascaderOptions] = useState<CascaderOption[]>([]); // 级联选择器选项
|
||||||
const [formParams, setFormParams] = useState({
|
const [formParams, setFormParams] = useState({
|
||||||
v: 'v1',
|
v: 'v1',
|
||||||
d: {
|
d: {
|
||||||
@ -38,6 +49,7 @@ export const QuestionsDetailCreate: React.FC<PropInterface> = ({ id, open, onCan
|
|||||||
setType('1');
|
setType('1');
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
level: 1,
|
level: 1,
|
||||||
|
knowledge_cascader: [],
|
||||||
});
|
});
|
||||||
setFormParams({
|
setFormParams({
|
||||||
v: 'v1',
|
v: 'v1',
|
||||||
@ -46,10 +58,49 @@ export const QuestionsDetailCreate: React.FC<PropInterface> = ({ id, open, onCan
|
|||||||
remark: null,
|
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);
|
setInit(false);
|
||||||
}
|
}
|
||||||
}, [form, open, id]);
|
}, [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'] = [
|
const items: TabsProps['items'] = [
|
||||||
{
|
{
|
||||||
key: '1',
|
key: '1',
|
||||||
@ -289,8 +340,19 @@ export const QuestionsDetailCreate: React.FC<PropInterface> = ({ id, open, onCan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const params = JSON.stringify(formParams);
|
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);
|
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);
|
setLoading(false);
|
||||||
message.success(t('commen.saveSuccess'));
|
message.success(t('commen.saveSuccess'));
|
||||||
onCancel();
|
onCancel();
|
||||||
@ -413,6 +475,27 @@ export const QuestionsDetailCreate: React.FC<PropInterface> = ({ id, open, onCan
|
|||||||
</Radio>
|
</Radio>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</Form.Item>
|
</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
|
<Form.Item
|
||||||
label={t('exam.question.detail.edit.name')}
|
label={t('exam.question.detail.edit.name')}
|
||||||
name="content"
|
name="content"
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Modal, Form, Radio, Spin, message } from 'antd';
|
import { Modal, Form, Radio, Spin, message, Cascader } from 'antd';
|
||||||
import { question } from '../../../../api/index';
|
import type { CascaderProps } from 'antd';
|
||||||
|
import { question, textbook } from '../../../../api/index';
|
||||||
import { QuestionInput } from '../../../../compenents';
|
import { QuestionInput } from '../../../../compenents';
|
||||||
import { QChoice } from './choice';
|
import { QChoice } from './choice';
|
||||||
import { QSelect } from './select';
|
import { QSelect } from './select';
|
||||||
@ -10,6 +11,15 @@ import { QQa } from './qa';
|
|||||||
import { QCap } from './cap';
|
import { QCap } from './cap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
// 级联选择器选项类型
|
||||||
|
interface CascaderOption {
|
||||||
|
value: string | number;
|
||||||
|
label: string;
|
||||||
|
children?: CascaderOption[];
|
||||||
|
isLeaf?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface PropInterface {
|
interface PropInterface {
|
||||||
id: number;
|
id: number;
|
||||||
qid: number;
|
qid: number;
|
||||||
@ -25,6 +35,7 @@ export const QuestionsDetailUpdate: React.FC<PropInterface> = ({ id, qid, open,
|
|||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
const [type, setType] = useState('1');
|
const [type, setType] = useState('1');
|
||||||
const [resourceUrl, setResourceUrl] = useState<ResourceUrlModel>({});
|
const [resourceUrl, setResourceUrl] = useState<ResourceUrlModel>({});
|
||||||
|
const [cascaderOptions, setCascaderOptions] = useState<CascaderOption[]>([]); // 级联选择器选项
|
||||||
const [formParams, setFormParams] = useState({
|
const [formParams, setFormParams] = useState({
|
||||||
v: 'v1',
|
v: 'v1',
|
||||||
d: {
|
d: {
|
||||||
@ -40,20 +51,96 @@ export const QuestionsDetailUpdate: React.FC<PropInterface> = ({ id, qid, open,
|
|||||||
}
|
}
|
||||||
}, [form, open, id, qid]);
|
}, [form, open, id, qid]);
|
||||||
|
|
||||||
const getDetail = () => {
|
const getDetail = async () => {
|
||||||
question.questionDetail(qid).then((res: any) => {
|
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);
|
setResourceUrl(res.data.resource_url);
|
||||||
const data = res.data.question;
|
const data = res.data.question;
|
||||||
setType(String(data.type));
|
setType(String(data.type));
|
||||||
const params = JSON.parse(res.data.question.content);
|
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({
|
form.setFieldsValue({
|
||||||
level: data.level,
|
level: data.level,
|
||||||
type: String(data.type),
|
type: String(data.type),
|
||||||
content: params.d.content,
|
content: params.d.content,
|
||||||
remark: params.d.remark,
|
remark: params.d.remark,
|
||||||
|
knowledge_cascader: cascaderValue,
|
||||||
});
|
});
|
||||||
setFormParams(params);
|
setFormParams(params);
|
||||||
setInit(false);
|
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]);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -268,8 +355,18 @@ export const QuestionsDetailUpdate: React.FC<PropInterface> = ({ id, qid, open,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const params = JSON.stringify(formParams);
|
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);
|
setLoading(true);
|
||||||
question.questionUpdate(qid, id, params, values.level, Number(type)).then((res: any) => {
|
question.questionUpdate(qid, id, params, values.level, Number(type), knowledgeCode).then((res: any) => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
message.success(t('commen.saveSuccess'));
|
message.success(t('commen.saveSuccess'));
|
||||||
onCancel();
|
onCancel();
|
||||||
@ -438,6 +535,27 @@ export const QuestionsDetailUpdate: React.FC<PropInterface> = ({ id, qid, open,
|
|||||||
</Radio>
|
</Radio>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</Form.Item>
|
</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
|
<Form.Item
|
||||||
label={t('exam.question.detail.edit.name')}
|
label={t('exam.question.detail.edit.name')}
|
||||||
name="content"
|
name="content"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user