feat(extension/crud): CRUD API 新增 DICT(字典列表(下拉选项等场景))、DICT_TREE(字典树列表(树型结构下拉选项等场景))

This commit is contained in:
2025-07-25 21:31:43 +08:00
parent ca33851fbd
commit ecabda6aec
6 changed files with 130 additions and 55 deletions

View File

@@ -39,5 +39,5 @@ public @interface CrudRequestMapping {
/**
* API 列表
*/
Api[] api() default {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE, Api.EXPORT};
Api[] api() default {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.BATCH_DELETE, Api.EXPORT, Api.DICT};
}

View File

@@ -34,6 +34,7 @@ import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.req.IdsReq;
import top.continew.starter.extension.crud.model.resp.IdResp;
import top.continew.starter.extension.crud.model.resp.BasePageResp;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.service.CrudService;
import top.continew.starter.extension.crud.validation.CrudValidationGroup;
@@ -187,4 +188,32 @@ public abstract class AbstractCrudController<S extends CrudService<L, D, Q, C>,
public void export(@Valid Q query, @Valid SortQuery sortQuery, HttpServletResponse response) {
baseService.export(query, sortQuery, response);
}
/**
* 查询字典列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 字典列表信息
*/
@CrudApi(Api.DICT)
@Operation(summary = "查询字典列表", description = "查询字典列表(下拉选项等场景)")
@GetMapping("/dict")
public List<LabelValueResp> dict(@Valid Q query, @Valid SortQuery sortQuery) {
return baseService.dict(query, sortQuery);
}
/**
* 查询字典树列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 字典树列表信息
*/
@CrudApi(Api.DICT_TREE)
@Operation(summary = "查询字典树列表", description = "查询树型结构字典列表(树型结构下拉选项等场景)")
@GetMapping("/dict/tree")
public List<Tree<Long>> dictTree(@Valid Q query, @Valid SortQuery sortQuery) {
return baseService.tree(query, sortQuery, true);
}
}

View File

@@ -68,4 +68,14 @@ public enum Api {
* 批量删除
*/
BATCH_DELETE,
/**
* 字典列表(下拉选项等场景)
*/
DICT,
/**
* 字典树列表(树型结构下拉选项等场景)
*/
DICT_TREE
}

View File

@@ -93,17 +93,6 @@ public interface CrudService<L, D, Q, C> {
*/
D get(Long id);
/**
* 查询字典列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 字典列表信息
* @since 2.1.0
* @see top.continew.starter.extension.crud.annotation.DictModel
*/
List<LabelValueResp> listDict(@Valid Q query, @Valid SortQuery sortQuery);
/**
* 创建
*
@@ -137,4 +126,15 @@ public interface CrudService<L, D, Q, C> {
* @param response 响应对象
*/
void export(@Valid Q query, @Valid SortQuery sortQuery, HttpServletResponse response);
/**
* 查询字典列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 字典列表信息
* @since 2.1.0
* @see top.continew.starter.extension.crud.annotation.DictModel
*/
List<LabelValueResp> dict(@Valid Q query, @Valid SortQuery sortQuery);
}

View File

@@ -19,9 +19,11 @@ package top.continew.starter.extension.crud.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
@@ -33,11 +35,13 @@ import org.springframework.transaction.annotation.Transactional;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.ReflectUtils;
import top.continew.starter.core.util.TreeUtils;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.core.util.validation.ValidationUtils;
import top.continew.starter.data.base.BaseMapper;
import top.continew.starter.data.service.impl.ServiceImpl;
import top.continew.starter.data.util.QueryWrapperHelper;
import top.continew.starter.excel.util.ExcelUtils;
import top.continew.starter.extension.crud.annotation.DictModel;
import top.continew.starter.extension.crud.annotation.TreeField;
import top.continew.starter.extension.crud.autoconfigure.CrudProperties;
import top.continew.starter.extension.crud.autoconfigure.CrudTreeProperties;
@@ -48,8 +52,7 @@ import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.model.resp.PageResp;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.function.Function;
/**
@@ -135,11 +138,6 @@ public class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D,
return detail;
}
@Override
public List<LabelValueResp> listDict(Q query, SortQuery sortQuery) {
return List.of();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long create(C req) {
@@ -175,6 +173,44 @@ public class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D,
ExcelUtils.export(list, "导出数据", detailClass, response);
}
@Override
public List<LabelValueResp> dict(Q query, SortQuery sortQuery) {
DictModel dictModel = entityClass.getDeclaredAnnotation(DictModel.class);
CheckUtils.throwIfNull(dictModel, "请添加并配置 @DictModel 字典结构信息");
List<L> list = this.list(query, sortQuery);
// 解析映射
List<LabelValueResp> respList = new ArrayList<>(list.size());
String labelKey = dictModel.labelKey().contains(StringConstants.DOT)
? CharSequenceUtil.subAfter(dictModel.labelKey(), StringConstants.DOT, true)
: dictModel.labelKey();
String valueKey = dictModel.valueKey().contains(StringConstants.DOT)
? CharSequenceUtil.subAfter(dictModel.valueKey(), StringConstants.DOT, true)
: dictModel.valueKey();
List<String> extraFieldNames = Arrays.stream(dictModel.extraKeys())
.map(extraKey -> extraKey.contains(StringConstants.DOT)
? CharSequenceUtil.subAfter(extraKey, StringConstants.DOT, true)
: extraKey)
.map(CharSequenceUtil::toCamelCase)
.toList();
for (L entity : list) {
LabelValueResp<Object> labelValueResp = new LabelValueResp<>();
labelValueResp.setLabel(Convert.toStr(ReflectUtil.getFieldValue(entity, CharSequenceUtil
.toCamelCase(labelKey))));
labelValueResp.setValue(ReflectUtil.getFieldValue(entity, CharSequenceUtil.toCamelCase(valueKey)));
respList.add(labelValueResp);
if (CollUtil.isEmpty(extraFieldNames)) {
continue;
}
// 额外数据
Map<String, Object> extraMap = MapUtil.newHashMap(dictModel.extraKeys().length);
for (String extraFieldName : extraFieldNames) {
extraMap.put(extraFieldName, ReflectUtil.getFieldValue(entity, extraFieldName));
}
labelValueResp.setExtra(extraMap);
}
return respList;
}
/**
* 查询列表
*

View File

@@ -141,7 +141,42 @@ public class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D,
}
@Override
public List<LabelValueResp> listDict(Q query, SortQuery sortQuery) {
@Transactional(rollbackFor = Exception.class)
public Long create(C req) {
this.beforeCreate(req);
T entity = BeanUtil.copyProperties(req, super.getEntityClass());
baseMapper.insert(entity);
this.afterCreate(req, entity);
return entity.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(C req, Long id) {
this.beforeUpdate(req, id);
T entity = this.getById(id);
BeanUtil.copyProperties(req, entity, CopyOptions.create().ignoreNullValue());
baseMapper.updateById(entity);
this.afterUpdate(req, entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(List<Long> ids) {
this.beforeDelete(ids);
baseMapper.deleteByIds(ids);
this.afterDelete(ids);
}
@Override
public void export(Q query, SortQuery sortQuery, HttpServletResponse response) {
List<D> list = this.list(query, sortQuery, this.getDetailClass());
list.forEach(this::fill);
ExcelUtils.export(list, "导出数据", this.getDetailClass(), response);
}
@Override
public List<LabelValueResp> dict(Q query, SortQuery sortQuery) {
DictModel dictModel = super.getEntityClass().getDeclaredAnnotation(DictModel.class);
CheckUtils.throwIfNull(dictModel, "请添加并配置 @DictModel 字典结构信息");
List<L> list = this.list(query, sortQuery);
@@ -178,41 +213,6 @@ public class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D,
return respList;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long create(C req) {
this.beforeCreate(req);
T entity = BeanUtil.copyProperties(req, super.getEntityClass());
baseMapper.insert(entity);
this.afterCreate(req, entity);
return entity.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(C req, Long id) {
this.beforeUpdate(req, id);
T entity = this.getById(id);
BeanUtil.copyProperties(req, entity, CopyOptions.create().ignoreNullValue());
baseMapper.updateById(entity);
this.afterUpdate(req, entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(List<Long> ids) {
this.beforeDelete(ids);
baseMapper.deleteByIds(ids);
this.afterDelete(ids);
}
@Override
public void export(Q query, SortQuery sortQuery, HttpServletResponse response) {
List<D> list = this.list(query, sortQuery, this.getDetailClass());
list.forEach(this::fill);
ExcelUtils.export(list, "导出数据", this.getDetailClass(), response);
}
/**
* 获取当前列表信息类型
*