feat(dealer): 添加经销商拖拽排序功能

- 新增 DealerDragCmd 数据传输对象,用于接收拖拽排序参数
- 在 DealerController 中添加 dragDealer 接口,支持 PATCH 和 PUT 请求
- 在 DealerServiceI 接口及其实现类 DealerServiceImpl 中增加 drag 方法
- 在 DealerGateway 接口及实现类 DealerGatewayImpl 中添加 drag 方法逻辑
- 修改 Dealer 实体类和 DealerDO 数据库映射类,新增 sort 字段用于排序
- 更新 DealerMapper,添加批量重置排序的 SQL 方法 batchResetSort
- 在查询接口中默认按 sort 字段升序排列,确保展示顺序正确
- 新增 DealerDragCmdExe 执行器处理拖拽命令,并调用网关层进行业务处理
- 实现拖拽排序核心算法,包括计算新 sort 值、判断是否需要重整排序等逻辑
This commit is contained in:
shenyifei 2025-11-09 21:02:41 +08:00
parent aa4620b30b
commit ebfdedc01a
11 changed files with 157 additions and 11 deletions

View File

@ -8,6 +8,7 @@ import com.alibaba.cola.dto.SingleResponse;
import com.xunhong.erp.turbo.api.biz.api.DealerServiceI; import com.xunhong.erp.turbo.api.biz.api.DealerServiceI;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerCreateCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerCreateCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDestroyCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDestroyCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDragCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerUpdateCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerUpdateCmd;
import com.xunhong.erp.turbo.api.biz.dto.qry.DealerListQry; import com.xunhong.erp.turbo.api.biz.dto.qry.DealerListQry;
import com.xunhong.erp.turbo.api.biz.dto.qry.DealerPageQry; import com.xunhong.erp.turbo.api.biz.dto.qry.DealerPageQry;
@ -82,4 +83,13 @@ public class DealerController {
dealerService.destroy(dealerDestroyCmd); dealerService.destroy(dealerDestroyCmd);
return Response.buildSuccess(); return Response.buildSuccess();
} }
@SaCheckLogin
// @SaCheckPermission(value = {PermissionConstant.MDB_BUSINESS_DEALER_UPDATE})
@RequestMapping(value = "dragDealer", method = {RequestMethod.PATCH, RequestMethod.PUT})
@Operation(summary = "经销商表拖拽排序", method = "PATCH")
public Response dragDealer(@RequestBody @Validated DealerDragCmd dealerDragCmd) {
dealerService.drag(dealerDragCmd);
return Response.buildSuccess();
}
} }

View File

@ -0,0 +1,21 @@
package com.xunhong.erp.turbo.biz.app.executor.query;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDragCmd;
import com.xunhong.erp.turbo.biz.domain.gateway.DealerGateway;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author shenyifei
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class DealerDragCmdExe {
private final DealerGateway dealerGateway;
public void execute(DealerDragCmd dealerDragCmd) {
dealerGateway.drag(dealerDragCmd);
}
}

View File

@ -3,6 +3,7 @@ package com.xunhong.erp.turbo.biz.app.service;
import com.xunhong.erp.turbo.api.biz.api.DealerServiceI; import com.xunhong.erp.turbo.api.biz.api.DealerServiceI;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerCreateCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerCreateCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDestroyCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDestroyCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDragCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerUpdateCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerUpdateCmd;
import com.xunhong.erp.turbo.api.biz.dto.qry.DealerListQry; import com.xunhong.erp.turbo.api.biz.dto.qry.DealerListQry;
import com.xunhong.erp.turbo.api.biz.dto.qry.DealerPageQry; import com.xunhong.erp.turbo.api.biz.dto.qry.DealerPageQry;
@ -12,6 +13,7 @@ import com.xunhong.erp.turbo.base.dto.PageDTO;
import com.xunhong.erp.turbo.biz.app.executor.cmd.DealerCreateCmdExe; import com.xunhong.erp.turbo.biz.app.executor.cmd.DealerCreateCmdExe;
import com.xunhong.erp.turbo.biz.app.executor.cmd.DealerDestroyCmdExe; import com.xunhong.erp.turbo.biz.app.executor.cmd.DealerDestroyCmdExe;
import com.xunhong.erp.turbo.biz.app.executor.cmd.DealerUpdateCmdExe; import com.xunhong.erp.turbo.biz.app.executor.cmd.DealerUpdateCmdExe;
import com.xunhong.erp.turbo.biz.app.executor.query.DealerDragCmdExe;
import com.xunhong.erp.turbo.biz.app.executor.query.DealerListQryExe; import com.xunhong.erp.turbo.biz.app.executor.query.DealerListQryExe;
import com.xunhong.erp.turbo.biz.app.executor.query.DealerPageQryExe; import com.xunhong.erp.turbo.biz.app.executor.query.DealerPageQryExe;
import com.xunhong.erp.turbo.biz.app.executor.query.DealerShowQryExe; import com.xunhong.erp.turbo.biz.app.executor.query.DealerShowQryExe;
@ -33,10 +35,11 @@ public class DealerServiceImpl implements DealerServiceI {
private final DealerCreateCmdExe dealerCreateCmdExe; private final DealerCreateCmdExe dealerCreateCmdExe;
private final DealerUpdateCmdExe dealerUpdateCmdExe; private final DealerUpdateCmdExe dealerUpdateCmdExe;
private final DealerPageQryExe dealerPageQryExe;
private final DealerListQryExe dealerListQryExe;
private final DealerShowQryExe dealerShowQryExe; private final DealerShowQryExe dealerShowQryExe;
private final DealerDestroyCmdExe dealerDestroyCmdExe; private final DealerDestroyCmdExe dealerDestroyCmdExe;
private final DealerPageQryExe dealerPageQryExe;
private final DealerListQryExe dealerListQryExe;
private final DealerDragCmdExe dealerDragCmdExe;
@Override @Override
public DealerVO create(DealerCreateCmd dealerCreateCmd) { public DealerVO create(DealerCreateCmd dealerCreateCmd) {
@ -67,5 +70,9 @@ public class DealerServiceImpl implements DealerServiceI {
public void destroy(DealerDestroyCmd dealerDestroyCmd) { public void destroy(DealerDestroyCmd dealerDestroyCmd) {
dealerDestroyCmdExe.execute(dealerDestroyCmd); dealerDestroyCmdExe.execute(dealerDestroyCmd);
} }
}
@Override
public void drag(DealerDragCmd dealerDragCmd) {
dealerDragCmdExe.execute(dealerDragCmd);
}
}

View File

@ -88,6 +88,11 @@ public class Dealer extends DTO {
*/ */
private String deliveryTemplate; private String deliveryTemplate;
/**
* 排序
*/
private BigDecimal sort;
/** /**
* 创建时间 * 创建时间
*/ */
@ -99,4 +104,3 @@ public class Dealer extends DTO {
private List<DealerPaymentAccount> dealerPaymentAccountList; private List<DealerPaymentAccount> dealerPaymentAccountList;
} }

View File

@ -3,6 +3,7 @@ package com.xunhong.erp.turbo.biz.domain.gateway;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerCreateCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerCreateCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDestroyCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDestroyCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDragCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerUpdateCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerUpdateCmd;
import com.xunhong.erp.turbo.api.biz.dto.qry.DealerListQry; import com.xunhong.erp.turbo.api.biz.dto.qry.DealerListQry;
import com.xunhong.erp.turbo.api.biz.dto.qry.DealerPageQry; import com.xunhong.erp.turbo.api.biz.dto.qry.DealerPageQry;
@ -26,5 +27,6 @@ public interface DealerGateway {
Dealer show(DealerShowQry dealerShowQry); Dealer show(DealerShowQry dealerShowQry);
void destroy(DealerDestroyCmd dealerDestroyCmd); void destroy(DealerDestroyCmd dealerDestroyCmd);
}
void drag(DealerDragCmd dealerDragCmd);
}

View File

@ -18,6 +18,8 @@ public interface DealerConvert {
@Mapping(target = "dealerPaymentAccountList", source = "dealerPaymentAccountDOList") @Mapping(target = "dealerPaymentAccountList", source = "dealerPaymentAccountDOList")
Dealer toDealer(DealerDO dealerDO); Dealer toDealer(DealerDO dealerDO);
@Mapping(target = "sort", ignore = true)
@Mapping(target = "deliveryTemplate", ignore = true)
@Mapping(target = "dealerPaymentAccountDOList", ignore = true) @Mapping(target = "dealerPaymentAccountDOList", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "updatedAt", ignore = true) @Mapping(target = "updatedAt", ignore = true)
@ -25,6 +27,7 @@ public interface DealerConvert {
@Mapping(target = "createdAt", ignore = true) @Mapping(target = "createdAt", ignore = true)
DealerDO toDealerDO(DealerCreateCmd dealerCreateCmd); DealerDO toDealerDO(DealerCreateCmd dealerCreateCmd);
@Mapping(target = "sort", ignore = true)
@Mapping(target = "dealerPaymentAccountDOList", ignore = true) @Mapping(target = "dealerPaymentAccountDOList", ignore = true)
@Mapping(target = "version", ignore = true) @Mapping(target = "version", ignore = true)
@Mapping(target = "updatedAt", ignore = true) @Mapping(target = "updatedAt", ignore = true)
@ -32,4 +35,3 @@ public interface DealerConvert {
@Mapping(target = "createdAt", ignore = true) @Mapping(target = "createdAt", ignore = true)
void toDealerDO(@MappingTarget DealerDO dealerDO, DealerUpdateCmd dealerUpdateCmd); void toDealerDO(@MappingTarget DealerDO dealerDO, DealerUpdateCmd dealerUpdateCmd);
} }

View File

@ -104,10 +104,15 @@ public class DealerDO extends BaseDO<DealerDO> {
@TableField(value = "delivery_template") @TableField(value = "delivery_template")
private String deliveryTemplate; private String deliveryTemplate;
/**
* 排序
*/
@TableField(value = "sort")
private BigDecimal sort;
/** /**
* 付款账户列表 * 付款账户列表
*/ */
@TableField(exist = false) @TableField(exist = false)
private List<DealerPaymentAccountDO> dealerPaymentAccountDOList; private List<DealerPaymentAccountDO> dealerPaymentAccountDOList;
} }

View File

@ -3,11 +3,13 @@ package com.xunhong.erp.turbo.biz.infrastructure.gateway;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerCreateCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerCreateCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDestroyCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDestroyCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDragCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerUpdateCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerUpdateCmd;
import com.xunhong.erp.turbo.api.biz.dto.qry.DealerListQry; import com.xunhong.erp.turbo.api.biz.dto.qry.DealerListQry;
import com.xunhong.erp.turbo.api.biz.dto.qry.DealerPageQry; import com.xunhong.erp.turbo.api.biz.dto.qry.DealerPageQry;
@ -21,10 +23,12 @@ import com.xunhong.erp.turbo.biz.infrastructure.mapper.DealerMapper;
import com.xunhong.erp.turbo.biz.infrastructure.mapper.DealerPaymentAccountMapper; import com.xunhong.erp.turbo.biz.infrastructure.mapper.DealerPaymentAccountMapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -55,6 +59,7 @@ public class DealerGatewayImpl implements DealerGateway {
queryWrapper.eq(Objects.nonNull(dealerPageQry.getDealerType()), DealerDO::getDealerType, dealerPageQry.getDealerType()); queryWrapper.eq(Objects.nonNull(dealerPageQry.getDealerType()), DealerDO::getDealerType, dealerPageQry.getDealerType());
queryWrapper.eq(Objects.nonNull(dealerPageQry.getStatus()), DealerDO::getStatus, dealerPageQry.getStatus()); queryWrapper.eq(Objects.nonNull(dealerPageQry.getStatus()), DealerDO::getStatus, dealerPageQry.getStatus());
queryWrapper.orderByAsc(DealerDO::getSort);
queryWrapper.orderByDesc(DealerDO::getCreatedAt); queryWrapper.orderByDesc(DealerDO::getCreatedAt);
IPage<DealerDO> page = new Page<>(dealerPageQry.getPageIndex(), dealerPageQry.getPageSize()); IPage<DealerDO> page = new Page<>(dealerPageQry.getPageIndex(), dealerPageQry.getPageSize());
@ -82,6 +87,7 @@ public class DealerGatewayImpl implements DealerGateway {
queryWrapper.eq(Objects.nonNull(dealerListQry.getStatus()), DealerDO::getStatus, dealerListQry.getStatus()); queryWrapper.eq(Objects.nonNull(dealerListQry.getStatus()), DealerDO::getStatus, dealerListQry.getStatus());
queryWrapper.eq(Objects.nonNull(dealerListQry.getDealerType()), DealerDO::getDealerType, dealerListQry.getDealerType()); queryWrapper.eq(Objects.nonNull(dealerListQry.getDealerType()), DealerDO::getDealerType, dealerListQry.getDealerType());
queryWrapper.orderByAsc(DealerDO::getSort);
queryWrapper.orderByDesc(DealerDO::getCreatedAt); queryWrapper.orderByDesc(DealerDO::getCreatedAt);
List<DealerDO> dealerDOList = dealerMapper.selectList(queryWrapper); List<DealerDO> dealerDOList = dealerMapper.selectList(queryWrapper);
return dealerDOList.stream().map(dealerConvert::toDealer).toList(); return dealerDOList.stream().map(dealerConvert::toDealer).toList();
@ -120,5 +126,50 @@ public class DealerGatewayImpl implements DealerGateway {
DealerDO dealerDO = dealerMapper.selectOne(queryWrapper); DealerDO dealerDO = dealerMapper.selectOne(queryWrapper);
dealerDO.deleteById(); dealerDO.deleteById();
} }
}
@Override
@Transactional
public void drag(DealerDragCmd dealerDragCmd) {
reorderTask(dealerDragCmd.getCurrentId(), dealerDragCmd.getPrevId(), dealerDragCmd.getNextId());
}
// 拖拽排序核心方法
public void reorderTask(Long currentId, Long prevId, Long nextId) {
// 获取相邻元素的sort值
Double prevSort = Optional.ofNullable(prevId)
.map(id -> dealerMapper.selectById(id).getSort().doubleValue())
.orElse(null);
Double nextSort = Optional.ofNullable(nextId)
.map(id -> dealerMapper.selectById(id).getSort().doubleValue())
.orElse(null);
// 计算新sort值
Double newSort = calculateNewSort(prevSort, nextSort);
// 检查是否需要重整排序
if (needResetSort(prevSort, nextSort)) {
dealerMapper.batchResetSort(dealerMapper.selectById(currentId).getDealerId());
} else {
// 更新当前任务sort值
dealerMapper.update(null, new LambdaUpdateWrapper<DealerDO>()
.eq(DealerDO::getDealerId, currentId)
.set(DealerDO::getSort, newSort)
);
}
}
private Double calculateNewSort(Double prevSort, Double nextSort) {
if (prevSort == null && nextSort != null) {
return nextSort - 1000; // 插入到开头
} else if (nextSort == null && prevSort != null) {
return prevSort + 1000; // 插入到末尾
} else if (prevSort == null && nextSort == null) {
return 0.0;
} else {
return (prevSort + nextSort) / 2; // 插入中间
}
}
private boolean needResetSort(Double prevSort, Double nextSort) {
return prevSort != null && nextSort != null
&& (nextSort - prevSort) < 1.0; // 判断间隙是否耗尽
}
}

View File

@ -3,11 +3,19 @@ package com.xunhong.erp.turbo.biz.infrastructure.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xunhong.erp.turbo.biz.infrastructure.entity.DealerDO; import com.xunhong.erp.turbo.biz.infrastructure.entity.DealerDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
/** /**
* @author shenyifei * @author shenyifei
*/ */
@Mapper @Mapper
public interface DealerMapper extends BaseMapper<DealerDO> { public interface DealerMapper extends BaseMapper<DealerDO> {
// 批量更新排序值用于间隙重整
@Update("UPDATE dealer t " +
"JOIN (SELECT dealer_id, (ROW_NUMBER() OVER (ORDER BY sort) - 1) * 1000 AS new_sort " +
"FROM dealer WHERE dealer_id = #{dealerId}) AS sorted_rows " +
"ON t.dealer_id = sorted_rows.dealer_id " +
"SET t.sort = sorted_rows.new_sort WHERE t.dealer_id = #{dealerId}")
void batchResetSort(@Param("dealerId") Long dealerId);
} }

View File

@ -2,6 +2,7 @@ package com.xunhong.erp.turbo.api.biz.api;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerCreateCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerCreateCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDestroyCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDestroyCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerDragCmd;
import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerUpdateCmd; import com.xunhong.erp.turbo.api.biz.dto.cmd.DealerUpdateCmd;
import com.xunhong.erp.turbo.api.biz.dto.qry.DealerListQry; import com.xunhong.erp.turbo.api.biz.dto.qry.DealerListQry;
import com.xunhong.erp.turbo.api.biz.dto.qry.DealerPageQry; import com.xunhong.erp.turbo.api.biz.dto.qry.DealerPageQry;
@ -26,5 +27,7 @@ public interface DealerServiceI {
DealerVO show(DealerShowQry dealerShowQry); DealerVO show(DealerShowQry dealerShowQry);
void destroy(DealerDestroyCmd dealerDestroyCmd); void destroy(DealerDestroyCmd dealerDestroyCmd);
void drag(DealerDragCmd dealerDragCmd);
} }

View File

@ -0,0 +1,33 @@
package com.xunhong.erp.turbo.api.biz.dto.cmd;
import com.xunhong.erp.turbo.base.dto.Command;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author shenyifei
*/
@Data
@Schema(title = "经销商拖拽")
@EqualsAndHashCode(callSuper = true)
public class DealerDragCmd extends Command {
/**
* 相邻元素
*/
@Schema(title = "相邻元素前")
private Long prevId;
/**
* 相邻元素
*/
@Schema(title = "相邻元素后")
private Long nextId;
/**
* 当前元素
*/
@Schema(title = "当前元素")
private Long currentId;
}