Sfoglia il codice sorgente

重构人员配置相关接口开发

penghao 3 mesi fa
parent
commit
1444cef4d0

+ 11 - 35
alien-entity/src/main/java/shop/alien/entity/store/StoreStaffConfig.java

@@ -8,10 +8,8 @@ import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import shop.alien.entity.store.dto.StoreStaffFitnessCourseGroup;
 
 import java.util.Date;
-import java.util.List;
 
 /**
  * @Author: fcw
@@ -47,11 +45,11 @@ public class StoreStaffConfig extends Model<StoreStaffConfig> {
     @TableField("staff_image")
     private String staffImage;
 
-    @ApiModelProperty(value = "擅长标签id(字典表proficient_tag的dict_id逗号分隔)")
-    @TableField("proficient_id")
-    private String proficientId;
+    // @ApiModelProperty(value = "擅长标签id(字典表proficient_tag的dict_id逗号分隔)----废弃!")
+    // @TableField("proficient_id")
+    // private String proficientId;
 
-    @ApiModelProperty(value = "擅长标签名称(逗号分隔)")
+    @ApiModelProperty(value = "擅长项目")
     @TableField("proficient_projects")
     private String proficientProjects;
 
@@ -119,40 +117,18 @@ public class StoreStaffConfig extends Model<StoreStaffConfig> {
     @TableField("online_status")
     private Integer onlineStatus;
 
-    @ApiModelProperty(value = "经营板块id(词典表 键为 business_section)")
-    @TableField("business_section")
-    private Integer businessSection;
-
-    // ===== 运动健身业务扩展信息(当 businessSection 对应“运动健身”时生效) =====
-
-    @ApiModelProperty(value = "运动健身-课程信息列表")
-    @TableField(exist = false)
-    private List<StoreStaffFitnessCourse> fitnessCourseList;
-
-    @ApiModelProperty(value = "运动健身-课程分组(前端推荐使用:课程类型下多项目)")
-    @TableField(exist = false)
-    private List<StoreStaffFitnessCourseGroup> fitnessCourseGroupList;
-
-    @ApiModelProperty(value = "运动健身-职业认证列表")
-    @TableField(exist = false)
-    private List<StoreStaffFitnessCertification> fitnessCertificationList;
-
-    @ApiModelProperty(value = "运动健身-荣誉奖项列表")
-    @TableField(exist = false)
-    private List<StoreStaffFitnessCertification> fitnessHonorList;
-
-    @ApiModelProperty(value = "运动健身-从业经历列表")
-    @TableField(exist = false)
-    private List<StoreStaffFitnessExperience> fitnessExperienceList;
-
-    @ApiModelProperty(value = "运动健身-基本信息")
-    @TableField(exist = false)
-    private StoreStaffFitnessBase fitnessBase;
+    // @ApiModelProperty(value = "经营板块id(词典表 键为 business_section)----废弃!")
+    // @TableField("business_section")
+    // private Integer businessSection;
 
     @ApiModelProperty(value = "点赞数量")
     @TableField("like_count")
     private Integer likeCount;
 
+    @ApiModelProperty(value = "标题id")
+    @TableField("title_id")
+    private Integer titleId;
+
     @ApiModelProperty(value = "今日是否有演出(true-有演出,false-无演出)")
     @TableField(exist = false)
     private Boolean hasPerformanceToday;

+ 0 - 3
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffConfigListQueryDto.java

@@ -25,9 +25,6 @@ public class StoreStaffConfigListQueryDto {
     @ApiModelProperty(value = "员工状态(0-待审核 1-审核通过 2-审核拒绝)", example = "1")
     private String status;
 
-    @ApiModelProperty(value = "经营板块id(词典表 键为 business_section)")
-    private Integer businessSection;
-
     @ApiModelProperty(value = "上线状态(0-上线 1-下线)")
     private Integer onlineStatus;
 

+ 47 - 91
alien-store/src/main/java/shop/alien/store/controller/StoreStaffConfigController.java

@@ -1,14 +1,13 @@
 package shop.alien.store.controller;
 
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
-import shop.alien.entity.store.StoreDictionary;
 import shop.alien.entity.store.StoreStaffConfig;
+import shop.alien.entity.store.StoreStaffTitle;
 import shop.alien.entity.store.dto.StoreStaffConfigListQueryDto;
 import shop.alien.entity.store.vo.StaffTitleGroupVo;
 import shop.alien.entity.store.vo.StoreStaffDetailVo;
@@ -17,13 +16,11 @@ import shop.alien.entity.store.vo.StoreStaffFitnessDetailVo;
 import shop.alien.entity.store.vo.StoreStaffPositionCountVo;
 import shop.alien.mapper.StoreDictionaryMapper;
 import shop.alien.store.service.StoreStaffConfigService;
+import shop.alien.store.service.StoreStaffTitleService;
 import shop.alien.store.util.CommonConstant;
 
 import java.io.IOException;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
 
 /**
  * @Author: fcw
@@ -49,7 +46,6 @@ public class StoreStaffConfigController {
             @ApiImplicitParam(name = "page", value = "分页页数", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "size", value = "分页条数", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "status", value = "员工状态(0-待审核 1-审核通过 2-审核拒绝)", dataType = "String", paramType = "query", required = false),
-            @ApiImplicitParam(name = "businessSection", value = "经营板块id(词典表 键为 business_section)", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "onlineStatus", value = "上线状态(0-上线 1-下线)", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "staffPosition", value = "职位", dataType = "String", paramType = "query", required = false)
     })
@@ -140,6 +136,42 @@ public class StoreStaffConfigController {
 
 
     /**
+     * 查询员工职位列表(商家端)
+     *
+     * @param storeId 店铺ID,必填
+     * @return 员工职位名称列表
+     */
+    @ApiOperation("查询员工职位列表(商家端)")
+    @ApiOperationSupport(order = 11)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getStaffPositionList")
+    public R<List<String>> getStaffPositionList(
+            @RequestParam(value = "storeId") Integer storeId) {
+        log.info("查询员工职位列表,参数:storeId={}", storeId);
+
+        try {
+            // 参数校验
+            if (storeId == null || storeId <= 0) {
+                log.warn("查询员工职位列表失败,店铺ID无效:storeId={}", storeId);
+                return R.fail("店铺ID不能为空且必须大于0");
+            }
+
+            List<String> result = storeStaffConfigService.getStaffPositionList(storeId);
+            log.info("查询员工职位列表成功,storeId={},职位数量:{}", storeId, result.size());
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            log.warn("查询员工职位列表失败,参数错误:{}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("查询员工职位列表异常,storeId={},异常信息:{}", storeId, e.getMessage(), e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+
+    /**
      * 员工列表查询接口(用户端)
      * <p>
      * 根据店铺ID查询员工列表,支持按状态和职位筛选,返回结果包含员工基本信息和今日是否有演出标识
@@ -168,7 +200,7 @@ public class StoreStaffConfigController {
             @RequestParam(value = "storeId") Integer storeId,
             @RequestParam(value = "status", required = false) String status,
             @RequestParam(value = "staffPosition", required = false) String staffPosition) {
-        log.info("查询员工列表,参数:page={}, size={}, storeId={}, status={}, staffPosition={}", 
+        log.info("查询员工列表,参数:page={}, size={}, storeId={}, status={}, staffPosition={}",
                 page, size, storeId, status, staffPosition);
 
         try {
@@ -204,8 +236,8 @@ public class StoreStaffConfigController {
             // 调用服务层查询
             IPage<StoreStaffConfig> result = storeStaffConfigService.queryStaffList(
                     normalizedPage, normalizedSize, storeId, status, staffPosition);
-            
-            log.info("查询员工列表成功,店铺ID={},共{}条记录,当前页{}条", 
+
+            log.info("查询员工列表成功,店铺ID={},共{}条记录,当前页{}条",
                     storeId, result.getTotal(), result.getRecords() != null ? result.getRecords().size() : 0);
             return R.data(result);
         } catch (IllegalArgumentException e) {
@@ -245,15 +277,15 @@ public class StoreStaffConfigController {
             }
 
             // 调用服务层查询
-            StoreStaffDetailWithPerformanceVo result = 
+            StoreStaffDetailWithPerformanceVo result =
                     storeStaffConfigService.queryStaffDetailWithPerformance(id);
-            
+
             if (result == null) {
                 log.warn("查询员工详情失败,员工不存在:id={}", id);
                 return R.fail("员工不存在");
             }
 
-            log.info("查询员工详情成功,id={},演出安排数量:{}", 
+            log.info("查询员工详情成功,id={},演出安排数量:{}",
                     id, result.getPerformanceScheduleList() != null ? result.getPerformanceScheduleList().size() : 0);
             return R.data(result);
         } catch (IllegalArgumentException e) {
@@ -345,96 +377,20 @@ public class StoreStaffConfigController {
             // 调用服务层查询
             List<StoreStaffConfig> result = storeStaffConfigService.queryStaffListByTitleId(storeId, titleId);
 
-            log.info("根据标题ID查询员工列表成功,店铺ID={},标题ID={},员工数量:{}", 
+            log.info("根据标题ID查询员工列表成功,店铺ID={},标题ID={},员工数量:{}",
                     storeId, titleId, result != null ? result.size() : 0);
             return R.data(result);
         } catch (IllegalArgumentException e) {
-            log.warn("根据标题ID查询员工列表参数错误,storeId={}, titleId={},错误信息:{}", 
+            log.warn("根据标题ID查询员工列表参数错误,storeId={}, titleId={},错误信息:{}",
                     storeId, titleId, e.getMessage());
             return R.fail(e.getMessage());
         } catch (Exception e) {
-            log.error("根据标题ID查询员工列表异常,storeId={}, titleId={},异常信息:{}", 
+            log.error("根据标题ID查询员工列表异常,storeId={}, titleId={},异常信息:{}",
                     storeId, titleId, e.getMessage(), e);
             return R.fail("根据标题ID查询员工列表失败,请稍后重试");
         }
     }
 
-    /**
-     * 查询擅长类型和标签(用于人员配置)
-     * 返回格式:{types: [{id, dictId, dictDetail, typeDetail, tags: [...]}]}
-     *
-     * @param businessSection 经营板块id(词典表 键为 business_section),可选
-     * @return 擅长类型和标签的树形结构
-     */
-    @ApiOperation("查询擅长类型和标签(商家端)")
-    @ApiOperationSupport(order = 7)
-    @ApiImplicitParams({
-            @ApiImplicitParam(name = "businessSection", value = "经营板块id(词典表 键为 business_section)", dataType = "Integer", paramType = "query", required = false)
-    })
-    @GetMapping("/getProficientTypesAndTags")
-    public R<Map<String, Object>> getProficientTypesAndTags(
-            @RequestParam(value = "businessSection", required = false) Integer businessSection) {
-        log.info("StoreStaffConfigController.getProficientTypesAndTags?businessSection={}", businessSection);
-
-        // 查询经营板块(business_section)
-        LambdaQueryWrapper<StoreDictionary> typeWrapper = new LambdaQueryWrapper<>();
-        typeWrapper.eq(StoreDictionary::getTypeName, "business_section");
-        typeWrapper.eq(StoreDictionary::getDeleteFlag, 0);
-        typeWrapper.isNull(StoreDictionary::getParentId);
-
-        // 如果指定了经营板块,根据 businessSection(dict_id) 过滤
-        if (businessSection != null) {
-            typeWrapper.eq(StoreDictionary::getDictId, String.valueOf(businessSection));
-        }
-
-        List<StoreDictionary> types = storeDictionaryMapper.selectList(typeWrapper);
-
-        // 如果指定了经营板块,只查询对应板块的标签
-        List<Integer> typeIds = types.stream().map(StoreDictionary::getId).collect(Collectors.toList());
-
-        // 查询擅长标签(proficient_tag),其 parent_id 指向 business_section 的主键id
-        LambdaQueryWrapper<StoreDictionary> tagWrapper = new LambdaQueryWrapper<>();
-        tagWrapper.eq(StoreDictionary::getTypeName, "proficient_tag");
-        tagWrapper.eq(StoreDictionary::getDeleteFlag, 0);
-        if (!typeIds.isEmpty()) {
-            tagWrapper.in(StoreDictionary::getParentId, typeIds);
-        }
-        List<StoreDictionary> allTags = storeDictionaryMapper.selectList(tagWrapper);
-
-        // 构建结果:按 parent_id 分组标签
-        Map<Integer, List<StoreDictionary>> tagsByParent = allTags.stream()
-                .filter(tag -> tag.getParentId() != null)
-                .collect(Collectors.groupingBy(StoreDictionary::getParentId));
-
-        // 构建返回结果
-        List<Map<String, Object>> resultList = types.stream().map(type -> {
-            Map<String, Object> typeMap = new HashMap<>();
-            typeMap.put("id", type.getId());
-            typeMap.put("dictId", type.getDictId());
-            typeMap.put("dictDetail", type.getDictDetail());
-            typeMap.put("typeDetail", type.getTypeDetail());
-
-            // 获取该类型下的标签
-            List<StoreDictionary> tags = tagsByParent.getOrDefault(type.getId(), new java.util.ArrayList<>());
-            List<Map<String, Object>> tagList = tags.stream().map(tag -> {
-                Map<String, Object> tagMap = new HashMap<>();
-                tagMap.put("id", tag.getId());
-                tagMap.put("dictId", tag.getDictId());
-                tagMap.put("dictDetail", tag.getDictDetail());
-                return tagMap;
-            }).collect(Collectors.toList());
-            typeMap.put("tags", tagList);
-
-            return typeMap;
-        }).collect(Collectors.toList());
-
-        Map<String, Object> result = new HashMap<>();
-        result.put("types", resultList);
-
-        return R.data(result);
-    }
-
-
     @ApiOperation("获取美食员工列表")
     @ApiOperationSupport(order = 7)
     @ApiImplicitParams({

+ 17 - 17
alien-store/src/main/java/shop/alien/store/controller/StoreStaffTitleController.java

@@ -106,7 +106,7 @@ public class StoreStaffTitleController {
      */
     @ApiOperation("新增员工标题")
     @ApiOperationSupport(order = 2)
-    @PostMapping("/add")
+    @PostMapping("/createTitle")
     public R<String> addStaffTitle(@RequestBody StoreStaffTitle storeStaffTitle) {
         log.info("新增员工标题,参数:{}", storeStaffTitle);
 
@@ -140,7 +140,7 @@ public class StoreStaffTitleController {
      */
     @ApiOperation("更新员工标题")
     @ApiOperationSupport(order = 3)
-    @PostMapping("/update")
+    @PostMapping("/updateTitle")
     public R<String> updateStaffTitle(@RequestBody StoreStaffTitle storeStaffTitle) {
         log.info("更新员工标题,参数:{}", storeStaffTitle);
 
@@ -177,7 +177,7 @@ public class StoreStaffTitleController {
     @ApiImplicitParams({
             @ApiImplicitParam(name = "id", value = "员工标题ID", dataType = "Integer", paramType = "query", required = true)
     })
-    @GetMapping("/delete")
+    @GetMapping("/deleteTitle")
     public R<String> deleteStaffTitle(@RequestParam(value = "id") Integer id) {
         log.info("删除员工标题,参数:id={}", id);
 
@@ -209,42 +209,42 @@ public class StoreStaffTitleController {
     /**
      * 查询员工标题详情
      * <p>
-     * 根据ID查询员工标题详细信息
+     * 根据门店ID查询员工标题详细信息(一个门店只能有一个标题)
      * </p>
      *
-     * @param id 员工标题ID,必填,必须大于0
+     * @param storeId 门店ID,必填,必须大于0
      * @return 员工标题详情
      */
     @ApiOperation("查询员工标题详情")
     @ApiOperationSupport(order = 5)
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "id", value = "员工标题ID", dataType = "Integer", paramType = "query", required = true)
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true)
     })
     @GetMapping("/detail")
-    public R<StoreStaffTitle> getStaffTitleDetail(@RequestParam(value = "id") Integer id) {
-        log.info("查询员工标题详情,参数:id={}", id);
+    public R<StoreStaffTitle> getStaffTitleDetail(@RequestParam(value = "storeId") Integer storeId) {
+        log.info("查询员工标题详情,参数:storeId={}", storeId);
 
         try {
             // 参数校验
-            if (id == null || id <= 0) {
-                log.warn("查询员工标题详情参数校验失败,ID无效:id={}", id);
-                return R.fail("员工标题ID不能为空且必须大于0");
+            if (storeId == null || storeId <= 0) {
+                log.warn("查询员工标题详情参数校验失败,门店ID无效:storeId={}", storeId);
+                return R.fail("门店ID不能为空且必须大于0");
             }
 
-            StoreStaffTitle result = storeStaffTitleService.getStaffTitleDetail(id);
+            StoreStaffTitle result = storeStaffTitleService.getStaffTitleDetail(storeId);
 
             if (result == null) {
-                log.warn("查询员工标题详情失败,记录不存在:id={}", id);
-                return R.fail("员工标题不存在");
+                log.warn("查询员工标题详情失败,记录不存在:storeId={}", storeId);
+                return R.fail("该门店还没有创建标题");
             }
 
-            log.info("查询员工标题详情成功,id={},标题名称={}", id, result.getTitleName());
+            log.info("查询员工标题详情成功,storeId={},id={},标题名称={}", storeId, result.getId(), result.getTitleName());
             return R.data(result);
         } catch (IllegalArgumentException e) {
-            log.warn("查询员工标题详情参数错误,id={},错误信息:{}", id, e.getMessage());
+            log.warn("查询员工标题详情参数错误,storeId={},错误信息:{}", storeId, e.getMessage());
             return R.fail(e.getMessage());
         } catch (Exception e) {
-            log.error("查询员工标题详情异常,id={},异常信息:{}", id, e.getMessage(), e);
+            log.error("查询员工标题详情异常,storeId={},异常信息:{}", storeId, e.getMessage(), e);
             return R.fail("查询员工标题详情失败,请稍后重试");
         }
     }

+ 11 - 0
alien-store/src/main/java/shop/alien/store/service/StoreStaffConfigService.java

@@ -181,4 +181,15 @@ public interface StoreStaffConfigService {
      * @return 员工职位统计列表,每个元素包含职位名称和对应员工数量
      */
     List<StoreStaffPositionCountVo> getStaffPositionCount(Integer storeId);
+
+    /**
+     * 查询指定店铺的员工职位列表
+     * <p>
+     * 查询指定店铺下所有不重复的职位名称列表,只统计未删除的员工
+     * </p>
+     *
+     * @param storeId 店铺ID,必须大于0
+     * @return 员工职位名称列表
+     */
+    List<String> getStaffPositionList(Integer storeId);
 }

+ 3 - 3
alien-store/src/main/java/shop/alien/store/service/StoreStaffTitleService.java

@@ -62,12 +62,12 @@ public interface StoreStaffTitleService extends IService<StoreStaffTitle> {
     /**
      * 查询员工标题详情
      * <p>
-     * 根据ID查询员工标题详细信息
+     * 根据门店ID查询员工标题详细信息(一个门店只能有一个标题)
      * </p>
      *
-     * @param id 员工标题ID,必须大于0
+     * @param storeId 门店ID,必须大于0
      * @return 员工标题详情,如果不存在则返回null
      */
-    StoreStaffTitle getStaffTitleDetail(Integer id);
+    StoreStaffTitle getStaffTitleDetail(Integer storeId);
 }
 

+ 125 - 434
alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffConfigServiceImpl.java

@@ -67,16 +67,6 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
 
     private final StoreStaffConfigMapper storeStaffConfigMapper;
 
-    private final StoreDictionaryMapper storeDictionaryMapper;
-
-    private final StoreStaffFitnessCourseMapper storeStaffFitnessCourseMapper;
-
-    private final StoreStaffFitnessCertificationMapper storeStaffFitnessCertificationMapper;
-
-    private final StoreStaffFitnessExperienceMapper storeStaffFitnessExperienceMapper;
-
-    private final StoreStaffFitnessBaseMapper storeStaffFitnessBaseMapper;
-
     private final StoreInfoMapper storeInfoMapper;
 
     private final AliOSSUtil aliOSSUtil;
@@ -124,7 +114,6 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
         int size = (query == null || query.getSize() == null) ? 10 : query.getSize();
         Integer storeId = query == null ? null : query.getStoreId();
         String status = query == null ? null : query.getStatus();
-        Integer businessSection = query == null ? null : query.getBusinessSection();
         Integer onlineStatus = query == null ? null : query.getOnlineStatus();
         String staffPosition = query == null ? null : query.getStaffPosition();
 
@@ -134,7 +123,6 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
         }
         QueryWrapper<StoreStaffConfig> queryWrapper = new QueryWrapper<>();
         queryWrapper.like(null != status && !status.isEmpty(), "status", status);
-        queryWrapper.eq(businessSection != null, "business_section", businessSection);
         queryWrapper.eq(onlineStatus != null, "online_status", onlineStatus);
         queryWrapper.eq("store_id", storeId);
         queryWrapper.eq(StringUtils.isNotEmpty(staffPosition), "staff_position", staffPosition);
@@ -165,20 +153,20 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
             }
         }
 
-        // businessSection:优先用入参;更新场景若未传则继承库里
-        Integer businessSection = storeStaffConfig.getBusinessSection();
-        if (!isCreate && businessSection == null) {
-            businessSection = existing.getBusinessSection();
-        }
-
         // 新增默认值
         if (isCreate) {
             applyCreateDefaults(storeStaffConfig);
+            // 新增员工时,检查门店是否有标题
+            if (storeStaffConfig.getStoreId() != null) {
+                StoreStaffTitle title = getTitleByStoreId(storeStaffConfig.getStoreId());
+                if (title == null) {
+                    throw new RuntimeException("该门店还没有创建标题,请先创建标题");
+                }
+                // 设置员工的标题ID
+                storeStaffConfig.setTitleId(title.getId());
+            }
         }
 
-        // 规范化擅长标签字段(proficient_id / proficient_projects)
-        normalizeProficientFields(storeStaffConfig, businessSection);
-
         // 先落库,再调用 AI 审核
         int affected = isCreate
                 ? insertStaff(storeStaffConfig)
@@ -189,11 +177,13 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
         }
 
         Integer staffId = isCreate ? storeStaffConfig.getId() : id;
-        if (isFitnessBusinessSection(businessSection)) {
-            saveOrUpdateFitnessDetails(staffId, storeStaffConfig);
+
+        // 新增员工成功后,更新标题的员工数量和 staff_ids
+        if (isCreate && storeStaffConfig.getTitleId() != null) {
+            updateTitleStaffCount(storeStaffConfig.getTitleId());
         }
 
-        // 新增 / 修改成功后,先将状态置为“审核中”(0),清空拒绝原因
+        // 新增 / 修改成功后,先将状态置为"审核中"(0),清空拒绝原因
         StoreStaffConfig auditingUpdate = new StoreStaffConfig();
         auditingUpdate.setId(staffId);
         auditingUpdate.setStatus("0");
@@ -319,9 +309,6 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
         if (staff == null) {
             return null;
         }
-        if (isFitnessBusinessSection(staff.getBusinessSection())) {
-            attachFitnessDetails(staff);
-        }
         return staff;
     }
 
@@ -361,330 +348,13 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
         // 因为实体类使用了 @TableLogic 注解
         StoreStaffConfig staff = storeStaffConfigMapper.selectById(id);
         int deleted = storeStaffConfigMapper.deleteById(id);
-        if (deleted > 0 && staff != null && isFitnessBusinessSection(staff.getBusinessSection())) {
-            cascadeDeleteFitnessDetails(id);
-        }
-        return deleted;
-    }
 
-    private void attachFitnessDetails(StoreStaffConfig staff) {
-        Integer staffId = staff.getId();
-        if (staffId == null) {
-            return;
+        // 删除员工后,更新标题的员工数量和 staff_ids
+        if (deleted > 0 && staff != null && staff.getTitleId() != null) {
+            updateTitleStaffCount(staff.getTitleId());
         }
 
-        List<StoreStaffFitnessCourse> courses = storeStaffFitnessCourseMapper.selectList(
-                new LambdaQueryWrapper<StoreStaffFitnessCourse>()
-                        .eq(StoreStaffFitnessCourse::getStaffId, staffId)
-                        .eq(StoreStaffFitnessCourse::getDeleteFlag, 0)
-                        .orderByDesc(StoreStaffFitnessCourse::getId)
-        );
-        staff.setFitnessCourseList(courses);
-        staff.setFitnessCourseGroupList(buildCourseGroups(courses));
-
-        List<StoreStaffFitnessCertification> certAll = storeStaffFitnessCertificationMapper.selectList(
-                new LambdaQueryWrapper<StoreStaffFitnessCertification>()
-                        .eq(StoreStaffFitnessCertification::getStaffId, staffId)
-                        .eq(StoreStaffFitnessCertification::getDeleteFlag, 0)
-                        .orderByDesc(StoreStaffFitnessCertification::getId)
-        );
-        // 按 type 区分职业认证/荣誉奖项:1-认证 2-荣誉
-        List<StoreStaffFitnessCertification> certList = certAll.stream()
-                .filter(c -> c.getType() != null && c.getType() == 1)
-                .collect(Collectors.toList());
-        List<StoreStaffFitnessCertification> honorList = certAll.stream()
-                .filter(c -> c.getType() != null && c.getType() == 2)
-                .collect(Collectors.toList());
-        staff.setFitnessCertificationList(certList);
-        staff.setFitnessHonorList(honorList);
-
-        List<StoreStaffFitnessExperience> experiences = storeStaffFitnessExperienceMapper.selectList(
-                new LambdaQueryWrapper<StoreStaffFitnessExperience>()
-                        .eq(StoreStaffFitnessExperience::getStaffId, staffId)
-                        .eq(StoreStaffFitnessExperience::getDeleteFlag, 0)
-                        .orderByDesc(StoreStaffFitnessExperience::getId)
-        );
-        staff.setFitnessExperienceList(experiences);
-
-        StoreStaffFitnessBase base = storeStaffFitnessBaseMapper.selectOne(
-                new LambdaQueryWrapper<StoreStaffFitnessBase>()
-                        .eq(StoreStaffFitnessBase::getStaffId, staffId)
-                        .eq(StoreStaffFitnessBase::getDeleteFlag, 0)
-                        .last("limit 1")
-        );
-        staff.setFitnessBase(base);
-    }
-
-    private void cascadeDeleteFitnessDetails(Integer staffId) {
-        if (staffId == null) {
-            return;
-        }
-        storeStaffFitnessCourseMapper.delete(new QueryWrapper<StoreStaffFitnessCourse>().eq("staff_id", staffId));
-        storeStaffFitnessCertificationMapper.delete(new QueryWrapper<StoreStaffFitnessCertification>().eq("staff_id", staffId));
-        storeStaffFitnessExperienceMapper.delete(new QueryWrapper<StoreStaffFitnessExperience>().eq("staff_id", staffId));
-        storeStaffFitnessBaseMapper.delete(new QueryWrapper<StoreStaffFitnessBase>().eq("staff_id", staffId));
-    }
-
-    private void saveOrUpdateFitnessDetails(Integer staffId, StoreStaffConfig staff) {
-        if (staffId == null) {
-            return;
-        }
-
-        // 0) 兼容前端“课程类型分组”入参:优先使用分组字段,自动拍平成 fitnessCourseList 走现有落库逻辑
-        if (staff.getFitnessCourseGroupList() != null) {
-            staff.setFitnessCourseList(flattenCourseGroups(staff.getFitnessCourseGroupList()));
-        }
-
-        // 1) 课程信息:若前端传了列表(包括空列表),以传入为准覆盖
-        if (staff.getFitnessCourseList() != null) {
-            storeStaffFitnessCourseMapper.delete(new QueryWrapper<StoreStaffFitnessCourse>().eq("staff_id", staffId));
-            // 课程类型存 dictDetail(名称)。如传 dictId,则在无歧义时自动转换为 dictDetail。
-            java.util.Set<String> rawTypes = staff.getFitnessCourseList().stream()
-                    .filter(java.util.Objects::nonNull)
-                    .map(StoreStaffFitnessCourse::getCourseType)
-                    .filter(StringUtils::isNotEmpty)
-                    .map(String::trim)
-                    .collect(java.util.stream.Collectors.toSet());
-            java.util.Map<String, String> dictIdToDetail = new java.util.HashMap<>();
-            if (!rawTypes.isEmpty()) {
-                List<StoreDictionary> dicts = storeDictionaryMapper.selectList(
-                        new LambdaQueryWrapper<StoreDictionary>()
-                                .in(StoreDictionary::getTypeName, java.util.Arrays.asList("classOnLineType", "classOffLineType"))
-                                .in(StoreDictionary::getDictId, rawTypes)
-                                .eq(StoreDictionary::getDeleteFlag, 0)
-                );
-                // 仅在一个 dictId 唯一对应一个 dictDetail 时才转换(避免线上/线下 dictId 重复导致歧义)
-                java.util.Map<String, java.util.Set<String>> tmp = new java.util.HashMap<>();
-                for (StoreDictionary d : dicts) {
-                    if (StringUtils.isEmpty(d.getDictId()) || StringUtils.isEmpty(d.getDictDetail())) {
-                        continue;
-                    }
-                    tmp.computeIfAbsent(d.getDictId(), k -> new java.util.HashSet<>()).add(d.getDictDetail());
-                }
-                for (java.util.Map.Entry<String, java.util.Set<String>> e : tmp.entrySet()) {
-                    if (e.getValue().size() == 1) {
-                        dictIdToDetail.put(e.getKey(), e.getValue().iterator().next());
-                    }
-                }
-            }
-            for (StoreStaffFitnessCourse course : staff.getFitnessCourseList()) {
-                if (course == null) {
-                    continue;
-                }
-                course.setId(null);
-                course.setStaffId(staffId);
-                if (course.getDeleteFlag() == null) {
-                    course.setDeleteFlag(0);
-                }
-                if (StringUtils.isNotEmpty(course.getCourseType())) {
-                    String ct = course.getCourseType().trim();
-                    // 若传的是 dictId 且能唯一映射,则替换为 dictDetail(名称)
-                    if (dictIdToDetail.containsKey(ct)) {
-                        course.setCourseType(dictIdToDetail.get(ct));
-                    } else {
-                        course.setCourseType(ct);
-                    }
-                }
-                // 价格字段归一化:0-固定价写 course_price;1-区间价写 min/max
-                if (course.getCoursePriceType() != null) {
-                    if (course.getCoursePriceType() == 0) {
-                        course.setCourseMinPrice(null);
-                        course.setCourseMaxPrice(null);
-                    } else if (course.getCoursePriceType() == 1) {
-                        course.setCoursePrice(null);
-                    }
-                }
-                storeStaffFitnessCourseMapper.insert(course);
-            }
-        }
-
-        // 2) 认证/荣誉:任意一个列表非 null,则覆盖保存(两类共用一张表)
-        if (staff.getFitnessCertificationList() != null || staff.getFitnessHonorList() != null) {
-            storeStaffFitnessCertificationMapper.delete(new QueryWrapper<StoreStaffFitnessCertification>().eq("staff_id", staffId));
-
-            if (staff.getFitnessCertificationList() != null) {
-                for (StoreStaffFitnessCertification cert : staff.getFitnessCertificationList()) {
-                    if (cert == null) {
-                        continue;
-                    }
-                    StoreStaffFitnessCertification row = new StoreStaffFitnessCertification();
-                    row.setId(null);
-                    row.setStaffId(staffId);
-                    row.setType(1);
-                    row.setName(cert.getName());
-                    row.setImgUrls(cert.getImgUrls());
-                    row.setDeleteFlag(0);
-                    storeStaffFitnessCertificationMapper.insert(row);
-                }
-            }
-
-            if (staff.getFitnessHonorList() != null) {
-                for (StoreStaffFitnessCertification honor : staff.getFitnessHonorList()) {
-                    if (honor == null) {
-                        continue;
-                    }
-                    StoreStaffFitnessCertification row = new StoreStaffFitnessCertification();
-                    row.setId(null);
-                    row.setStaffId(staffId);
-                    row.setType(2);
-                    row.setName(honor.getName());
-                    row.setImgUrls(honor.getImgUrls());
-                    row.setDeleteFlag(0);
-                    storeStaffFitnessCertificationMapper.insert(row);
-                }
-            }
-        }
-
-        // 3) 从业经历:若前端传了列表(包括空列表),以传入为准覆盖
-        if (staff.getFitnessExperienceList() != null) {
-            storeStaffFitnessExperienceMapper.delete(new QueryWrapper<StoreStaffFitnessExperience>().eq("staff_id", staffId));
-            for (StoreStaffFitnessExperience exp : staff.getFitnessExperienceList()) {
-                if (exp == null) {
-                    continue;
-                }
-                exp.setId(null);
-                exp.setStaffId(staffId);
-                if (exp.getDeleteFlag() == null) {
-                    exp.setDeleteFlag(0);
-                }
-                storeStaffFitnessExperienceMapper.insert(exp);
-            }
-        }
-
-        // 4) 基本信息:若前端传了对象则 upsert,否则不动
-        if (staff.getFitnessBase() != null) {
-            StoreStaffFitnessBase base = staff.getFitnessBase();
-            base.setStaffId(staffId);
-            if (base.getDeleteFlag() == null) {
-                base.setDeleteFlag(0);
-            }
-            StoreStaffFitnessBase existBase = storeStaffFitnessBaseMapper.selectOne(
-                    new LambdaQueryWrapper<StoreStaffFitnessBase>()
-                            .eq(StoreStaffFitnessBase::getStaffId, staffId)
-                            .eq(StoreStaffFitnessBase::getDeleteFlag, 0)
-                            .last("limit 1")
-            );
-            if (existBase == null) {
-                base.setId(null);
-                storeStaffFitnessBaseMapper.insert(base);
-            } else {
-                base.setId(existBase.getId());
-                storeStaffFitnessBaseMapper.updateById(base);
-            }
-        }
-    }
-
-    /**
-     * 若 proficientProjects 未传,尝试根据 proficientId + businessSection 从字典表计算名称回填
-     * 规则:proficient_tag.parent_id = business_section 主键id;proficient_id 存 proficient_tag.dict_id 逗号分隔
-     */
-    private void normalizeProficientFields(StoreStaffConfig staff, Integer businessSection) {
-        if (staff == null) {
-            return;
-        }
-        if (StringUtils.isEmpty(staff.getProficientId()) || !StringUtils.isEmpty(staff.getProficientProjects()) || businessSection == null) {
-            return;
-        }
-
-        // 查询经营板块(business_section),注意:staff.businessSection 存的是 dict_id
-        StoreDictionary section = storeDictionaryMapper.selectOne(
-                new LambdaQueryWrapper<StoreDictionary>()
-                        .eq(StoreDictionary::getTypeName, "business_section")
-                        .eq(StoreDictionary::getDictId, String.valueOf(businessSection))
-                        .eq(StoreDictionary::getDeleteFlag, 0)
-                        .isNull(StoreDictionary::getParentId)
-                        .last("limit 1")
-        );
-        if (section == null || section.getId() == null) {
-            return;
-        }
-
-        List<String> dictIds = java.util.Arrays.stream(staff.getProficientId().split(","))
-                .map(String::trim)
-                .filter(s -> !s.isEmpty())
-                .collect(Collectors.toList());
-        if (dictIds.isEmpty()) {
-            return;
-        }
-
-        List<StoreDictionary> tags = storeDictionaryMapper.selectList(
-                new LambdaQueryWrapper<StoreDictionary>()
-                        .eq(StoreDictionary::getTypeName, "proficient_tag")
-                        .eq(StoreDictionary::getParentId, section.getId())
-                        .in(StoreDictionary::getDictId, dictIds)
-                        .eq(StoreDictionary::getDeleteFlag, 0)
-        );
-
-        java.util.Map<String, String> idToName = tags.stream()
-                .filter(d -> !StringUtils.isEmpty(d.getDictId()))
-                .collect(Collectors.toMap(StoreDictionary::getDictId, StoreDictionary::getDictDetail, (a, b) -> a));
-
-        List<String> namesInOrder = dictIds.stream()
-                .map(idToName::get)
-                .filter(s -> !StringUtils.isEmpty(s))
-                .collect(Collectors.toList());
-
-        if (!namesInOrder.isEmpty()) {
-            staff.setProficientProjects(String.join(",", namesInOrder));
-        }
-    }
-
-    /**
-     * 判断该经营板块是否为“运动健身”
-     * - 优先按常见 dict_id=7 判断
-     * - 若不一致,再按字典表 dict_detail=运动健身 判断
-     */
-    private boolean isFitnessBusinessSection(Integer businessSection) {
-        if (businessSection == null) {
-            return false;
-        }
-        if (businessSection == 7) {
-            return true;
-        }
-        StoreDictionary section = storeDictionaryMapper.selectOne(
-                new LambdaQueryWrapper<StoreDictionary>()
-                        .eq(StoreDictionary::getTypeName, "business_section")
-                        .eq(StoreDictionary::getDictId, String.valueOf(businessSection))
-                        .eq(StoreDictionary::getDeleteFlag, 0)
-                        .last("limit 1")
-        );
-        return section != null && "运动健身".equals(section.getDictDetail());
-    }
-
-    /**
-     * 将分组结构拍平成明细行结构(用于落库)
-     */
-    private List<StoreStaffFitnessCourse> flattenCourseGroups(List<StoreStaffFitnessCourseGroup> groups) {
-        List<StoreStaffFitnessCourse> flat = new ArrayList<>();
-        if (groups == null) {
-            return flat;
-        }
-        for (StoreStaffFitnessCourseGroup g : groups) {
-            if (g == null) {
-                continue;
-            }
-            String courseType = StringUtils.isNotEmpty(g.getCourseType()) ? g.getCourseType().trim() : null;
-            if (StringUtils.isEmpty(courseType) && StringUtils.isNotEmpty(g.getCourseTypeName())) {
-                courseType = g.getCourseTypeName().trim();
-            }
-            if (g.getItems() == null) {
-                continue;
-            }
-            for (StoreStaffFitnessCourseItem item : g.getItems()) {
-                if (item == null) {
-                    continue;
-                }
-                StoreStaffFitnessCourse row = new StoreStaffFitnessCourse();
-                row.setCourseType(courseType);
-                row.setCourseName(item.getCourseName());
-                row.setCoursePriceType(item.getCoursePriceType());
-                row.setCoursePrice(item.getCoursePrice());
-                row.setCourseMinPrice(item.getCourseMinPrice());
-                row.setCourseMaxPrice(item.getCourseMaxPrice());
-                flat.add(row);
-            }
-        }
-        return flat;
+        return deleted;
     }
 
     /**
@@ -708,73 +378,6 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
                lowerUrl.endsWith(".mov");
     }
 
-    /**
-     * 将明细行结构组装成分组结构(用于前端绑定)
-     * - 优先按 courseType 分组(DB里可能存 dictDetail 或 dictId)
-     * - courseTypeName:能从字典表解析到名称则回填,否则回填为原 courseType
-     */
-    private List<StoreStaffFitnessCourseGroup> buildCourseGroups(List<StoreStaffFitnessCourse> courses) {
-        List<StoreStaffFitnessCourseGroup> groups = new ArrayList<>();
-        if (courses == null || courses.isEmpty()) {
-            return groups;
-        }
-
-        // 收集类型并尝试从字典表解析名称(兼容:DB存 dictId 或 dictDetail)
-        java.util.Set<String> rawTypes = courses.stream()
-                .filter(java.util.Objects::nonNull)
-                .map(StoreStaffFitnessCourse::getCourseType)
-                .filter(StringUtils::isNotEmpty)
-                .map(String::trim)
-                .collect(java.util.stream.Collectors.toSet());
-
-        java.util.Map<String, String> typeToName = new java.util.HashMap<>();
-        if (!rawTypes.isEmpty()) {
-            List<StoreDictionary> dicts = storeDictionaryMapper.selectList(
-                    new LambdaQueryWrapper<StoreDictionary>()
-                            // 兼容历史:文档里是 classOnLineType/classOffLineType;部分环境可能是 courseType
-                            .in(StoreDictionary::getTypeName, java.util.Arrays.asList("classOnLineType", "classOffLineType", "courseType"))
-                            .and(w -> w.in(StoreDictionary::getDictId, rawTypes).or().in(StoreDictionary::getDictDetail, rawTypes))
-                            .eq(StoreDictionary::getDeleteFlag, 0)
-            );
-            for (StoreDictionary d : dicts) {
-                if (d == null) {
-                    continue;
-                }
-                if (StringUtils.isNotEmpty(d.getDictId()) && StringUtils.isNotEmpty(d.getDictDetail())) {
-                    // dictId -> name
-                    typeToName.putIfAbsent(d.getDictId().trim(), d.getDictDetail().trim());
-                    // name -> name(用于 DB 存 dictDetail 的场景)
-                    typeToName.putIfAbsent(d.getDictDetail().trim(), d.getDictDetail().trim());
-                }
-            }
-        }
-
-        java.util.Map<String, StoreStaffFitnessCourseGroup> map = new java.util.LinkedHashMap<>();
-        for (StoreStaffFitnessCourse c : courses) {
-            if (c == null) {
-                continue;
-            }
-            String ct = StringUtils.isNotEmpty(c.getCourseType()) ? c.getCourseType().trim() : "";
-            StoreStaffFitnessCourseGroup g = map.computeIfAbsent(ct, k -> {
-                StoreStaffFitnessCourseGroup ng = new StoreStaffFitnessCourseGroup();
-                ng.setCourseType(k);
-                ng.setCourseTypeName(typeToName.getOrDefault(k, k));
-                ng.setItems(new ArrayList<>());
-                return ng;
-            });
-
-            StoreStaffFitnessCourseItem item = new StoreStaffFitnessCourseItem();
-            item.setCourseName(c.getCourseName());
-            item.setCoursePriceType(c.getCoursePriceType());
-            item.setCoursePrice(c.getCoursePrice());
-            item.setCourseMinPrice(c.getCourseMinPrice());
-            item.setCourseMaxPrice(c.getCourseMaxPrice());
-            g.getItems().add(item);
-        }
-        groups.addAll(map.values());
-        return groups;
-    }
-
     @Override
     public Integer setTopStatus(Integer id, Integer topStatus) {
         if (id == null || topStatus == null) {
@@ -802,6 +405,94 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
         return storeStaffConfigMapper.update(null, lambdaUpdateWrapper);
     }
 
+    @Override
+    public List<String> getStaffPositionList(Integer storeId) {
+        log.info("查询员工职位列表,storeId={}", storeId);
+
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            log.warn("查询员工职位列表失败,店铺ID无效:storeId={}", storeId);
+            throw new IllegalArgumentException("店铺ID不能为空且必须大于0");
+        }
+
+        // 构建查询条件:查询指定店铺下未删除的员工
+        LambdaQueryWrapper<StoreStaffConfig> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreStaffConfig::getStoreId, storeId)
+                .eq(StoreStaffConfig::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE)
+                // 只查询staff_position不为空的记录
+                .isNotNull(StoreStaffConfig::getStaffPosition)
+                .ne(StoreStaffConfig::getStaffPosition, "")
+                .select(StoreStaffConfig::getStaffPosition);
+
+        // 查询所有符合条件的员工
+        List<StoreStaffConfig> staffList = storeStaffConfigMapper.selectList(queryWrapper);
+
+        // 提取所有不重复的职位名称
+        List<String> positionList = staffList.stream()
+                .map(StoreStaffConfig::getStaffPosition)
+                .filter(StringUtils::isNotEmpty)
+                .distinct()
+                .sorted()
+                .collect(Collectors.toList());
+
+        log.info("查询员工职位列表成功,storeId={},职位数量:{}", storeId, positionList.size());
+        return positionList;
+    }
+
+    /**
+     * 根据门店ID查询标题(一个门店只能有一个标题)
+     *
+     * @param storeId 门店ID
+     * @return 标题信息
+     */
+    private StoreStaffTitle getTitleByStoreId(Integer storeId) {
+        if (storeId == null) {
+            return null;
+        }
+
+        // 直接通过 store_id 查询标题
+        LambdaQueryWrapper<StoreStaffTitle> titleWrapper = new LambdaQueryWrapper<>();
+        titleWrapper.eq(StoreStaffTitle::getStoreId, storeId)
+                .eq(StoreStaffTitle::getDeleteFlag, 0)
+                .orderByDesc(StoreStaffTitle::getCreatedTime)
+                .last("limit 1");
+        return storeStaffTitleMapper.selectOne(titleWrapper);
+    }
+
+    /**
+     * 更新标题的员工数量和 staff_ids
+     *
+     * @param titleId 标题ID
+     */
+    private void updateTitleStaffCount(Integer titleId) {
+        if (titleId == null) {
+            return;
+        }
+
+        // 查询该标题下的所有未删除员工
+        LambdaQueryWrapper<StoreStaffConfig> staffWrapper = new LambdaQueryWrapper<>();
+        staffWrapper.eq(StoreStaffConfig::getTitleId, titleId)
+                .eq(StoreStaffConfig::getDeleteFlag, 0)
+                .select(StoreStaffConfig::getId);
+        List<StoreStaffConfig> staffList = storeStaffConfigMapper.selectList(staffWrapper);
+
+        // 计算员工数量和 staff_ids
+        int staffCount = staffList.size();
+        String staffIds = staffList.stream()
+                .map(staff -> String.valueOf(staff.getId()))
+                .collect(Collectors.joining(","));
+
+        // 更新标题
+        LambdaUpdateWrapper<StoreStaffTitle> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(StoreStaffTitle::getId, titleId)
+                .set(StoreStaffTitle::getStaffIds, staffIds)
+                .set(StoreStaffTitle::getStaffCount, staffCount)
+                .set(StoreStaffTitle::getUpdatedTime, new Date());
+
+        storeStaffTitleMapper.update(null, updateWrapper);
+        log.info("StoreStaffConfigServiceImpl.updateTitleStaffCount 更新成功,titleId={}, staffCount={}", titleId, staffCount);
+    }
+
     /**
      * 员工列表查询(用户端)
      * <p>
@@ -831,10 +522,10 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
 
         // 执行查询
         IPage<StoreStaffConfig> result = storeStaffConfigMapper.selectPage(staffPage, queryWrapper);
-        
+
         // 批量填充今日是否有演出字段(性能优化:批量查询)
         fillHasPerformanceTodayBatch(result);
-        
+
         return result;
     }
 
@@ -1263,20 +954,20 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
             }
 
             // 查询演出安排列表
-            List<PerformanceScheduleVo> performanceScheduleList = 
+            List<PerformanceScheduleVo> performanceScheduleList =
                     queryPerformanceScheduleList(id);
 
             // 判断今日是否有演出
             Boolean hasPerformanceToday = checkHasPerformanceToday(id);
 
             // 构建返回对象
-            StoreStaffDetailWithPerformanceVo result = 
+            StoreStaffDetailWithPerformanceVo result =
                     new StoreStaffDetailWithPerformanceVo();
             result.setStaffInfo(staffConfig);
             result.setPerformanceScheduleList(performanceScheduleList);
             result.setHasPerformanceToday(hasPerformanceToday);
 
-            log.info("查询员工详情(包含演出列表)成功,id={},演出安排数量:{},今日是否有演出:{}", 
+            log.info("查询员工详情(包含演出列表)成功,id={},演出安排数量:{},今日是否有演出:{}",
                     id, performanceScheduleList != null ? performanceScheduleList.size() : 0, hasPerformanceToday);
             return result;
         } catch (Exception e) {
@@ -1433,7 +1124,7 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
             // 使用dailyStartTime和dailyEndTime
             Date scheduleStart = combineDateAndLocalTime(currentDate, dailyStartTime);
             Date scheduleEnd = combineDateAndLocalTime(currentDate, dailyEndTime);
-            
+
             // 如果结束时间小于或等于开始时间,说明跨天了
             if (!scheduleEnd.after(scheduleStart)) {
                 java.util.Calendar endCal = java.util.Calendar.getInstance();
@@ -1506,7 +1197,7 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
                 // 使用dailyStartTime和dailyEndTime
                 Date scheduleStart = combineDateAndLocalTime(currentDate, dailyStartTime);
                 Date scheduleEnd = combineDateAndLocalTime(currentDate, dailyEndTime);
-                
+
                 // 如果结束时间小于或等于开始时间,说明跨天了
                 if (!scheduleEnd.after(scheduleStart)) {
                     java.util.Calendar endCal = java.util.Calendar.getInstance();
@@ -1534,9 +1225,9 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
      */
     private PerformanceScheduleVo buildPerformanceScheduleVo(
             BarPerformance performance, Date startTime, Date endTime) {
-        PerformanceScheduleVo schedule = 
+        PerformanceScheduleVo schedule =
                 new PerformanceScheduleVo();
-        
+
         schedule.setPerformanceId(performance.getId());
         schedule.setPerformanceName(performance.getPerformanceName());
 
@@ -1547,17 +1238,17 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
         String dateStr = dateFormat.format(startTime);
         String weekDayStr = getWeekDayStr(startTime);
         String startTimeStr = timeFormat.format(startTime);
-        
+
         // 判断是否跨天
         String endTimeStr;
         java.util.Calendar startCal = java.util.Calendar.getInstance();
         startCal.setTime(startTime);
         java.util.Calendar endCal = java.util.Calendar.getInstance();
         endCal.setTime(endTime);
-        
+
         boolean isNextDay = endCal.get(java.util.Calendar.DAY_OF_YEAR) > startCal.get(java.util.Calendar.DAY_OF_YEAR) ||
                 endCal.get(java.util.Calendar.YEAR) > startCal.get(java.util.Calendar.YEAR);
-        
+
         if (isNextDay) {
             endTimeStr = "次日" + timeFormat.format(endTime);
         } else {
@@ -1600,15 +1291,15 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
 
         java.util.Calendar dateCal = java.util.Calendar.getInstance();
         dateCal.setTime(date);
-        
+
         java.util.Calendar timeCal = java.util.Calendar.getInstance();
         timeCal.setTime(timeSource);
-        
+
         dateCal.set(java.util.Calendar.HOUR_OF_DAY, timeCal.get(java.util.Calendar.HOUR_OF_DAY));
         dateCal.set(java.util.Calendar.MINUTE, timeCal.get(java.util.Calendar.MINUTE));
         dateCal.set(java.util.Calendar.SECOND, timeCal.get(java.util.Calendar.SECOND));
         dateCal.set(java.util.Calendar.MILLISECOND, timeCal.get(java.util.Calendar.MILLISECOND));
-        
+
         return dateCal.getTime();
     }
 
@@ -1629,10 +1320,10 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
             java.time.Instant instant = date.toInstant();
             java.time.ZoneId zoneId = java.time.ZoneId.systemDefault();
             java.time.LocalDate localDate = instant.atZone(zoneId).toLocalDate();
-            
+
             // 合并日期和时间
             java.time.LocalDateTime localDateTime = localDate.atTime(localTime);
-            
+
             // 转换回Date
             return Date.from(localDateTime.atZone(zoneId).toInstant());
         } catch (Exception e) {
@@ -2105,11 +1796,11 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
                 }
             }
 
-            log.info("根据标题ID查询员工列表成功,storeId={}, titleId={},员工数量:{}", 
+            log.info("根据标题ID查询员工列表成功,storeId={}, titleId={},员工数量:{}",
                     storeId, titleId, validStaffList.size());
             return validStaffList;
         } catch (Exception e) {
-            log.error("根据标题ID查询员工列表异常,storeId={}, titleId={},异常信息:{}", 
+            log.error("根据标题ID查询员工列表异常,storeId={}, titleId={},异常信息:{}",
                     storeId, titleId, e.getMessage(), e);
             throw new RuntimeException("根据标题ID查询员工列表失败:" + e.getMessage(), e);
         }

+ 46 - 14
alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffTitleServiceImpl.java

@@ -105,10 +105,6 @@ public class StoreStaffTitleServiceImpl extends ServiceImpl<StoreStaffTitleMappe
             log.warn("新增员工标题失败,标题名称为空");
             throw new IllegalArgumentException("标题名称不能为空");
         }
-        if (StringUtils.isEmpty(storeStaffTitle.getStaffIds())) {
-            log.warn("新增员工标题失败,员工IDs为空");
-            throw new IllegalArgumentException("员工IDs不能为空");
-        }
 
         try {
             // 自动计算员工数量
@@ -204,6 +200,32 @@ public class StoreStaffTitleServiceImpl extends ServiceImpl<StoreStaffTitleMappe
         }
 
         try {
+            // 先查询标题信息,检查是否有员工
+            StoreStaffTitle title = storeStaffTitleMapper.selectById(id);
+            if (title == null) {
+                log.warn("删除员工标题失败,标题不存在:id={}", id);
+                throw new IllegalArgumentException("员工标题不存在");
+            }
+
+            // 校验:如果标题下有员工,不能删除
+            if (title.getStaffCount() != null && title.getStaffCount() > 0) {
+                log.warn("删除员工标题失败,标题下还有员工,不能删除:id={},员工数量={}", id, title.getStaffCount());
+                throw new IllegalArgumentException("该标题下还有员工,无法删除。请先移除标题下的所有员工后再删除");
+            }
+
+            // 双重检查:如果staffIds不为空,也不能删除
+            if (StringUtils.isNotEmpty(title.getStaffIds())) {
+                // 检查staffIds中是否有有效的员工ID
+                List<String> idList = Arrays.stream(title.getStaffIds().split(","))
+                        .map(String::trim)
+                        .filter(s -> !s.isEmpty())
+                        .collect(Collectors.toList());
+                if (!idList.isEmpty()) {
+                    log.warn("删除员工标题失败,标题下还有员工,不能删除:id={},员工IDs={}", id, title.getStaffIds());
+                    throw new IllegalArgumentException("该标题下还有员工,无法删除。请先移除标题下的所有员工后再删除");
+                }
+            }
+
             // 逻辑删除(MyBatis-Plus会自动处理@TableLogic注解)
             int result = storeStaffTitleMapper.deleteById(id);
 
@@ -214,6 +236,9 @@ public class StoreStaffTitleServiceImpl extends ServiceImpl<StoreStaffTitleMappe
             }
 
             return result;
+        } catch (IllegalArgumentException e) {
+            // 重新抛出IllegalArgumentException,让Controller层处理
+            throw e;
         } catch (Exception e) {
             log.error("删除员工标题异常,id={},异常信息:{}", id, e.getMessage(), e);
             throw new RuntimeException("删除员工标题失败:" + e.getMessage(), e);
@@ -223,31 +248,38 @@ public class StoreStaffTitleServiceImpl extends ServiceImpl<StoreStaffTitleMappe
     /**
      * 查询员工标题详情
      *
-     * @param id 员工标题ID,必须大于0
+     * @param storeId 门店ID,必须大于0
      * @return 员工标题详情,如果不存在则返回null
      */
     @Override
-    public StoreStaffTitle getStaffTitleDetail(Integer id) {
-        log.info("查询员工标题详情,参数:id={}", id);
+    public StoreStaffTitle getStaffTitleDetail(Integer storeId) {
+        log.info("查询员工标题详情,参数:storeId={}", storeId);
 
         // 参数校验
-        if (id == null || id <= 0) {
-            log.warn("查询员工标题详情失败,ID无效:id={}", id);
-            throw new IllegalArgumentException("员工标题ID不能为空且必须大于0");
+        if (storeId == null || storeId <= 0) {
+            log.warn("查询员工标题详情失败,门店ID无效:storeId={}", storeId);
+            throw new IllegalArgumentException("门店ID不能为空且必须大于0");
         }
 
         try {
-            StoreStaffTitle result = storeStaffTitleMapper.selectById(id);
+            // 根据门店ID查询标题(一个门店只能有一个标题)
+            LambdaQueryWrapper<StoreStaffTitle> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(StoreStaffTitle::getStoreId, storeId)
+                    .eq(StoreStaffTitle::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE)
+                    .orderByDesc(StoreStaffTitle::getCreatedTime)
+                    .last("limit 1");
+            
+            StoreStaffTitle result = storeStaffTitleMapper.selectOne(queryWrapper);
 
             if (result == null) {
-                log.warn("查询员工标题详情失败,记录不存在:id={}", id);
+                log.warn("查询员工标题详情失败,记录不存在:storeId={}", storeId);
             } else {
-                log.info("查询员工标题详情成功,id={},标题名称={}", id, result.getTitleName());
+                log.info("查询员工标题详情成功,storeId={},id={},标题名称={}", storeId, result.getId(), result.getTitleName());
             }
 
             return result;
         } catch (Exception e) {
-            log.error("查询员工标题详情异常,id={},异常信息:{}", id, e.getMessage(), e);
+            log.error("查询员工标题详情异常,storeId={},异常信息:{}", storeId, e.getMessage(), e);
             throw new RuntimeException("查询员工标题详情失败:" + e.getMessage(), e);
         }
     }

+ 2 - 1
alien-util/src/main/java/shop/alien/util/common/VideoUtils.java

@@ -40,7 +40,8 @@ public class VideoUtils {
         // 如果为本地测试,ffmpegPath地址需求修改为本地安装程序的地址(环境变量不好用)
         String ffmpegPath = "ffmpeg";
         if ("windows".equals(OSUtil.getOsName())) {
-            ffmpegPath = "C:/Program Files (x86)/ffmpeg-6.0/bin/ffmpeg.exe";
+            // ffmpegPath = "C:/Program Files (x86)/ffmpeg-6.0/bin/ffmpeg.exe";
+            ffmpegPath = "D:/project/ext/ffmpeg-6.0/bin/ffmpeg.exe";
         }
 
         // 调用ffmpeg 执行截取命令,需要服务器中安装了ffmpeg并配置了环境变量