Parcourir la source

feat:商户端人员配置相关接口

penghao il y a 3 jours
Parent
commit
674848da07

+ 250 - 19
alien-store/src/main/java/shop/alien/store/controller/StoreStaffConfigController.java

@@ -1,15 +1,25 @@
 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.dto.StoreStaffConfigListQueryDto;
+import shop.alien.entity.store.vo.StoreStaffDetailVo;
+import shop.alien.entity.store.vo.StoreStaffFitnessDetailVo;
+import shop.alien.mapper.StoreDictionaryMapper;
 import shop.alien.store.service.StoreStaffConfigService;
 
 import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * @Author: fcw
@@ -26,20 +36,33 @@ public class StoreStaffConfigController {
 
     private final StoreStaffConfigService storeStaffConfigService;
 
-    @ApiOperation("员工列表")
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    @ApiOperation("员工列表(商家端)")
     @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true),
+            @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)
+    })
     @GetMapping("/getStaffConfigList")
-    public R<IPage<StoreStaffConfig>> getStaffConfigList(@RequestParam(value = "page", defaultValue = "1") int page,
-                                                         @RequestParam(value = "size", defaultValue = "10") int size,
-                                                         @RequestParam(value = "status", required = false) String status) {
-        log.info("StoreStaffConfigController.getStaffConfigList?status={}", status);
-        return R.data(storeStaffConfigService.getStaffConfigList(page, size, status));
+    public R<IPage<StoreStaffConfig>> getStaffConfigList(StoreStaffConfigListQueryDto query) {
+        Integer storeId = query == null ? null : query.getStoreId();
+        log.info("StoreStaffConfigController.getStaffConfigList?storeId={},query={}", storeId, query);
+        if (storeId == null) {
+            return R.fail("storeId未传入,请检查参数");
+        }
+        return R.data(storeStaffConfigService.getStaffConfigList(query));
     }
 
-    @ApiOperation("新增修改员工")
+    @ApiOperation("新增修改员工(商家端)")
     @ApiOperationSupport(order = 2)
     @PostMapping("/addOrUpdateStaffConfig")
-    public R<String> addOrUpdateStaffConfig(@RequestBody StoreStaffConfig storeStaffConfig) throws IOException, InterruptedException {
+    public R<String> addOrUpdateStaffConfig(@RequestBody StoreStaffConfig storeStaffConfig) {
         log.info("StoreStaffConfigController.addOrUpdateStaffConfig?multipartRequest={}, storeStaffConfig={}", storeStaffConfig);
         int num = storeStaffConfigService.addOrUpdateStaffConfig(storeStaffConfig);
         if (0 == num) {
@@ -60,7 +83,7 @@ public class StoreStaffConfigController {
         return R.data(storeStaffConfigService.audit(id, status, rejectionReason));
     }
 
-    @ApiOperation("员工详情")
+    @ApiOperation("员工详情(商家端)")
     @ApiOperationSupport(order = 2)
     @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "主键id", dataType = "Integer", paramType = "query", required = true)})
     @GetMapping("/getStaffConfigDeatail")
@@ -72,13 +95,13 @@ public class StoreStaffConfigController {
     @ApiOperation("员工导出")
     @ApiOperationSupport(order = 11)
     @GetMapping("/staffConfigExport")
-    public R staffConfigExport(@RequestParam(required = false) String status) throws IOException {
+    public R<String> staffConfigExport(@RequestParam(required = false) String status) throws IOException {
         log.info("PlatformStoreCouponController.staffConfigExport");
         String s = storeStaffConfigService.staffConfigExport(status);
         return R.data(s);
     }
 
-    @ApiOperation("删除员工")
+    @ApiOperation("删除员工(商家端)")
     @ApiOperationSupport(order = 4)
     @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "主键id", dataType = "Integer", paramType = "query", required = true)})
     @GetMapping("/deleteStaffConfig")
@@ -87,7 +110,7 @@ public class StoreStaffConfigController {
         return R.data(storeStaffConfigService.deleteStaffConfig(id));
     }
 
-    @ApiOperation("置顶员工")
+    @ApiOperation("置顶员工(商家端)")
     @ApiOperationSupport(order = 5)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "id", value = "主键id", dataType = "Integer", paramType = "query", required = true),
@@ -98,13 +121,27 @@ public class StoreStaffConfigController {
         log.info("StoreStaffConfigController.setTopStatus?id={},topStatus={}", id, topStatus);
         return R.data(storeStaffConfigService.setTopStatus(id, topStatus));
     }
+
+    @ApiOperation("设置员工上线/下线状态(商家端)")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "主键id", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "onlineStatus", value = "上线状态 0-上线, 1-下线", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/setOnlineStatus")
+    public R<Integer> setOnlineStatus(Integer id, Integer onlineStatus) {
+        log.info("StoreStaffConfigController.setOnlineStatus?id={},onlineStatus={}", id, onlineStatus);
+        return R.data(storeStaffConfigService.setOnlineStatus(id, onlineStatus));
+    }
+
+
     /**
      * 员工列表查询接口(用户端)
      *
-     * @param page    分页页数
-     * @param size    分页条数
-     * @param storeId 店铺ID
-     * @param status  员工状态
+     * @param page    分页页数,默认1
+     * @param size    分页条数,默认10
+     * @param storeId 店铺ID,必填
+     * @param status  员工状态,可选(0-待审核 1-审核通过 2-审核拒绝)
      * @return 员工列表
      */
     @ApiOperation("员工列表查询(用户端)")
@@ -118,11 +155,29 @@ public class StoreStaffConfigController {
     @GetMapping("/queryStaffList")
     public R<IPage<StoreStaffConfig>> queryStaffList(
             @RequestParam(value = "page", defaultValue = "1") Integer page,
-            @RequestParam(value = "size", defaultValue = "100") Integer size,
-            @RequestParam(value = "storeId", required = true) Integer storeId,
+            @RequestParam(value = "size", defaultValue = "10") Integer size,
+            @RequestParam(value = "storeId") Integer storeId,
             @RequestParam(value = "status", required = false) String status) {
-        log.info("StoreStaffConfigController.queryStaffList?page={}&size={}&storeId={}&status={}", page, size, storeId, status);
+        log.info("查询员工列表,参数:page={}, size={}, storeId={}, status={}", page, size, storeId, status);
+
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            log.warn("查询员工列表失败,店铺ID无效:storeId={}", storeId);
+            return R.fail("店铺ID不能为空且必须大于0");
+        }
+        if (page == null || page < 1) {
+            page = 1;
+        }
+        if (size == null || size < 1) {
+            size = 10;
+        }
+        // 限制分页大小,防止查询过多数据
+        if (size > 100) {
+            size = 100;
+        }
+
         IPage<StoreStaffConfig> result = storeStaffConfigService.queryStaffList(page, size, storeId, status);
+        log.info("查询员工列表成功,共{}条记录", result.getTotal());
         return R.data(result);
     }
 
@@ -146,4 +201,180 @@ public class StoreStaffConfigController {
         }
         return R.data(result);
     }
+
+    /**
+     * 查询擅长类型和标签(用于人员配置)
+     * 返回格式:{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({
+            @ApiImplicitParam(name = "page", value = "分页页数", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "size", value = "分页条数", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "staffPosition", value = "员工职位", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "status", value = "员工状态", dataType = "String", paramType = "query", required = false)
+    })
+    @GetMapping("/getFoodStaffConfigList")
+    public R<IPage<StoreStaffConfig>> getFoodStaffConfigList(@RequestParam(value = "page", defaultValue = "1") int page,
+                                                         @RequestParam(value = "size", defaultValue = "10") int size,
+                                                         @RequestParam(value = "storeId", required = true) Integer storeId,
+                                                         @RequestParam(value = "staffPosition", required = false) String staffPosition,
+                                                         @RequestParam(value = "status", required = false) String status) {
+        log.info("StoreStaffConfigController.getFoodStaffConfigList?page={}&size={}&storeId={}&staffPosition={}&status={}", page, size, storeId, staffPosition, status);
+        return R.data(storeStaffConfigService.getFoodStaffConfigList(page, size, storeId, staffPosition, status));
+    }
+
+    /**
+     * 根据ID查询员工详情(包含员工信息和课程列表)
+     *
+     * @param id 员工主键ID
+     * @return 员工详情(包含员工信息和课程列表)
+     */
+    @ApiOperation("根据ID查询员工详情(包含员工信息和课程列表)用户端")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "员工主键ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getStaffDetailWithCourse")
+    public R<StoreStaffDetailVo> getStaffDetailWithCourse(
+            @RequestParam(value = "id") Integer id) {
+        log.info("查询员工详情(包含课程信息),id={}", id);
+
+        try {
+            // 参数校验
+            if (id == null || id <= 0) {
+                log.warn("查询员工详情失败,员工ID无效:{}", id);
+                return R.fail("员工ID不能为空且必须大于0");
+            }
+
+            StoreStaffDetailVo result = storeStaffConfigService.getStaffDetailWithCourse(id);
+
+            if (result == null) {
+                log.warn("查询员工详情失败,员工不存在:id={}", id);
+                return R.fail("员工不存在");
+            }
+
+            log.info("查询员工详情成功,id={},课程数量:{}", id,
+                    result.getCourseList() != null ? result.getCourseList().size() : 0);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("查询员工详情异常,id={},异常信息:{}", id, e.getMessage(), e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 查询健身教练详情(包含员工信息、基本信息和认证/荣誉列表)
+     *
+     * @param id 员工主键ID
+     * @return 健身教练详情(包含员工信息、基本信息和认证/荣誉列表)
+     */
+    @ApiOperation("查询健身教练详情(包含员工信息、基本信息和认证/荣誉列表)用户端")
+    @ApiOperationSupport(order = 9)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "员工主键ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getFitnessCoachDetail")
+    public R<StoreStaffFitnessDetailVo> getFitnessCoachDetail(
+            @RequestParam(value = "id") Integer id) {
+        log.info("查询健身教练详情,id={}", id);
+
+        try {
+            // 参数校验
+            if (id == null || id <= 0) {
+                log.warn("查询健身教练详情失败,员工ID无效:{}", id);
+                return R.fail("员工ID不能为空且必须大于0");
+            }
+
+            StoreStaffFitnessDetailVo result = storeStaffConfigService.getFitnessCoachDetail(id);
+
+            if (result == null) {
+                log.warn("查询健身教练详情失败,员工不存在:id={}", id);
+                return R.fail("员工不存在");
+            }
+
+            log.info("查询健身教练详情成功,id={},认证数量:{},荣誉数量:{}",
+                    id,
+                    result.getCertificationList() != null ? result.getCertificationList().size() : 0,
+                    result.getHonorList() != null ? result.getHonorList().size() : 0);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("查询健身教练详情异常,id={},异常信息:{}", id, e.getMessage(), e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
 }

+ 40 - 5
alien-store/src/main/java/shop/alien/store/service/StoreStaffConfigService.java

@@ -2,24 +2,50 @@ package shop.alien.store.service;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import shop.alien.entity.store.StoreStaffConfig;
+import shop.alien.entity.store.dto.StoreStaffConfigListQueryDto;
 
 import java.io.IOException;
 
 public interface StoreStaffConfigService {
 
-    IPage<StoreStaffConfig> getStaffConfigList(int page, int size, String status);
+    /**
+     * 员工列表查询(商家端)
+     *
+     * @param query 查询条件DTO
+     * @return 员工列表
+     */
+    IPage<StoreStaffConfig> getStaffConfigList(StoreStaffConfigListQueryDto query);
 
+    /**
+     * 新增或更新员工(商家端)
+     *
+     * @param storeStaffConfig 员工信息
+     * @return 新增或更新结果
+     */
     int addOrUpdateStaffConfig(StoreStaffConfig storeStaffConfig);
 
-
+    /**
+     * 审核员工
+     *
+     * @param id 员工ID
+     * @param status 审核状态 0-待审核, 1-审核通过, 2-审核拒绝
+     * @param rejectionReason 拒绝原因(status=2时必填)
+     * @return
+     */
     Integer audit(Integer id, Integer status, String rejectionReason);
 
+    /**
+     * 员工详情查询(商家端)
+     *
+     * @param id 员工ID
+     * @return 员工详情
+     */
     StoreStaffConfig getStaffConfigDeatail(Integer id);
 
     String staffConfigExport(String status) throws IOException;
 
     /**
-     * 删除员工
+     * 删除员工(商家端)
      *
      * @param id
      * @return
@@ -27,7 +53,7 @@ public interface StoreStaffConfigService {
     Integer deleteStaffConfig(Integer id);
 
     /**
-     * 置顶员工
+     * 置顶员工(商家端)
      *
      * @param id 员工ID
      * @param topStatus 置顶状态 0-未置顶, 1-置顶
@@ -35,6 +61,15 @@ public interface StoreStaffConfigService {
      */
     Integer setTopStatus(Integer id, Integer topStatus);
 
+    /**
+     * 设置员工上线/下线状态(商家端)
+     *
+     * @param id 员工ID
+     * @param onlineStatus 上线状态 0-上线, 1-下线
+     * @return
+     */
+    Integer setOnlineStatus(Integer id, Integer onlineStatus);
+
 
     /**
      * 员工列表查询
@@ -48,7 +83,7 @@ public interface StoreStaffConfigService {
     IPage<StoreStaffConfig> queryStaffList(Integer page, Integer size, Integer storeId, String status);
 
     /**
-     * 员工详情查询
+     * 员工详情查询(用户端)
      *
      * @param id 员工主键id
      * @return 员工详情

+ 765 - 54
alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffConfigServiceImpl.java

@@ -1,20 +1,32 @@
 package shop.alien.store.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
-import shop.alien.entity.store.StoreStaffConfig;
-import shop.alien.entity.store.excelVo.StoreInfoExpiredRecordsExcelVo;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.*;
+import shop.alien.entity.store.dto.StoreStaffConfigListQueryDto;
+import shop.alien.entity.store.dto.StoreStaffFitnessCourseGroup;
+import shop.alien.entity.store.dto.StoreStaffFitnessCourseItem;
 import shop.alien.entity.store.excelVo.StoreStaffConfigExcelVo;
 import shop.alien.entity.store.excelVo.util.ExcelGenerator;
-import shop.alien.mapper.StoreStaffConfigMapper;
+import shop.alien.entity.store.vo.StoreStaffDetailVo;
+import shop.alien.entity.store.vo.StoreStaffFitnessDetailVo;
+import shop.alien.mapper.*;
 import shop.alien.store.service.StoreStaffConfigService;
+import shop.alien.store.service.StoreStaffFitnessBaseService;
+import shop.alien.store.service.StoreStaffFitnessCertificationService;
+import shop.alien.store.service.StoreStaffFitnessCourseService;
+import shop.alien.store.util.CommonConstant;
+import shop.alien.store.util.ai.AiContentModerationUtil;
 import shop.alien.util.ali.AliOSSUtil;
 
 import java.io.File;
@@ -23,6 +35,7 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
 /**
  * @Author: fcw
@@ -30,14 +43,45 @@ import java.util.UUID;
  * @Description: 员工管理
  */
 
+@Slf4j
 @Service
 @RequiredArgsConstructor
 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;
 
+    private final StoreStaffFitnessCourseService storeStaffFitnessCourseService;
+
+    private final StoreStaffFitnessBaseService storeStaffFitnessBaseService;
+
+    private final StoreStaffFitnessCertificationService storeStaffFitnessCertificationService;
+
+    private final AiContentModerationUtil aiContentModerationUtil;
+
+    /**
+     * 认证类型:认证
+     */
+    private static final Integer CERTIFICATION_TYPE = 1;
+
+    /**
+     * 认证类型:荣誉
+     */
+    private static final Integer HONOR_TYPE = 2;
+
     @Value("${spring.web.resources.excel-path}")
     private String excelPath;
     @Value("${spring.web.resources.excel-clearing-receipt}")
@@ -48,59 +92,152 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
     private String fileUrl;
 
     @Override
-    public IPage<StoreStaffConfig> getStaffConfigList(int page, int size, String status) {
+    public IPage<StoreStaffConfig> getStaffConfigList(StoreStaffConfigListQueryDto query) {
+        int page = (query == null || query.getPage() == null) ? 1 : query.getPage();
+        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();
+
         IPage<StoreStaffConfig> storePage = new Page<>(page, size);
+        if (storeId == null) {
+            return storePage;
+        }
         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);
+        // 只查询未删除的记录
+        queryWrapper.eq("delete_flag", 0);
         // 排序规则:先按置顶状态降序(置顶的在前),再按置顶时间降序,最后按创建时间降序
         queryWrapper.orderByDesc("top_status", "top_time", "created_time");
         return storeStaffConfigMapper.selectPage(storePage, queryWrapper);
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public int addOrUpdateStaffConfig(StoreStaffConfig storeStaffConfig) {
-        // 判断是新增还是更新:id为null或0或负数表示新增
-        // 注意:新增时不应该传入id,如果传入了非0的id,需要先查询是否存在
+        if (storeStaffConfig == null) {
+            return 0;
+        }
+
         Integer id = storeStaffConfig.getId();
-        
-        if (id == null || id == 0) {
-            // 新增操作:id为null或0
-            Date nowDate = new Date(System.currentTimeMillis());
-            storeStaffConfig.setCreatedTime(nowDate);
-            // 设置删除标记为0(未删除)- 必须设置,否则可能插入失败
-            if (storeStaffConfig.getDeleteFlag() == null) {
-                storeStaffConfig.setDeleteFlag(0);
-            }
-            // 如果状态为空,设置默认状态为待审核(0)
-            if (StringUtils.isEmpty(storeStaffConfig.getStatus())) {
-                storeStaffConfig.setStatus("0");
-            }
-            // 新增时,确保id为null,让数据库自动生成
-            storeStaffConfig.setId(null);
-            return storeStaffConfigMapper.insert(storeStaffConfig);
-        } else {
-            // 更新操作:id不为null且不为0
-            // 先查询记录是否存在,如果不存在则转为新增
-            StoreStaffConfig existing = storeStaffConfigMapper.selectById(id);
+        boolean isCreate = (id == null || id == 0);
+        StoreStaffConfig existing = null;
+
+        if (!isCreate) {
+            existing = storeStaffConfigMapper.selectById(id);
+            // 传了id但库里不存在:按新增处理
             if (existing == null) {
-                // 记录不存在,转为新增操作
-                storeStaffConfig.setId(null); // 重置id,让数据库自动生成
-                Date nowDate = new Date(System.currentTimeMillis());
-                storeStaffConfig.setCreatedTime(nowDate);
-                // 设置删除标记为0(未删除)
-                if (storeStaffConfig.getDeleteFlag() == null) {
-                    storeStaffConfig.setDeleteFlag(0);
-                }
-                // 如果状态为空,设置默认状态为待审核(0)
-                if (StringUtils.isEmpty(storeStaffConfig.getStatus())) {
-                    storeStaffConfig.setStatus("0");
-                }
-                return storeStaffConfigMapper.insert(storeStaffConfig);
-            } else {
-                // 记录存在,执行更新
-                return storeStaffConfigMapper.updateById(storeStaffConfig);
+                isCreate = true;
+                storeStaffConfig.setId(null);
+            }
+        }
+
+        // businessSection:优先用入参;更新场景若未传则继承库里
+        Integer businessSection = storeStaffConfig.getBusinessSection();
+        if (!isCreate && businessSection == null) {
+            businessSection = existing.getBusinessSection();
+        }
+
+        // 新增默认值
+        if (isCreate) {
+            applyCreateDefaults(storeStaffConfig);
+        }
+
+        // 规范化擅长标签字段(proficient_id / proficient_projects)
+        normalizeProficientFields(storeStaffConfig, businessSection);
+
+        // 先落库,再调用 AI 审核
+        int affected = isCreate
+                ? insertStaff(storeStaffConfig)
+                : storeStaffConfigMapper.updateById(storeStaffConfig);
+
+        if (affected <= 0) {
+            return affected;
+        }
+
+        Integer staffId = isCreate ? storeStaffConfig.getId() : id;
+        if (isFitnessBusinessSection(businessSection)) {
+            saveOrUpdateFitnessDetails(staffId, storeStaffConfig);
+        }
+
+        // 新增 / 修改成功后,先将状态置为“审核中”(0),清空拒绝原因
+        StoreStaffConfig auditingUpdate = new StoreStaffConfig();
+        auditingUpdate.setId(staffId);
+        auditingUpdate.setStatus("0");
+        auditingUpdate.setRejectionReason(null);
+        storeStaffConfigMapper.updateById(auditingUpdate);
+
+        // 组装 AI 审核文本和图片
+        StringBuilder textContent = new StringBuilder();
+        if (StringUtils.isNotEmpty(storeStaffConfig.getName())) {
+            textContent.append(storeStaffConfig.getName()).append(" ");
+        }
+        if (StringUtils.isNotEmpty(storeStaffConfig.getStaffPosition())) {
+            textContent.append(storeStaffConfig.getStaffPosition()).append(" ");
+        }
+        if (StringUtils.isNotEmpty(storeStaffConfig.getPersonalIntroduction())) {
+            textContent.append(storeStaffConfig.getPersonalIntroduction());
+        }
+
+        List<String> imageUrls = new ArrayList<>();
+        if (StringUtils.isNotEmpty(storeStaffConfig.getStaffImage())) {
+            imageUrls.add(storeStaffConfig.getStaffImage());
+        }
+        if (StringUtils.isNotEmpty(storeStaffConfig.getBackgroundUrl())) {
+            String[] urls = storeStaffConfig.getBackgroundUrl().split(",");
+            for (String url : urls) {
+                if (StringUtils.isNotEmpty(url.trim())) {
+                    imageUrls.add(url.trim());
+                }
             }
         }
+
+        AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(
+                textContent.toString().trim(), imageUrls
+        );
+
+        // 根据 AI 审核结果更新状态(1:审核通过;2:审核拒绝)
+        StoreStaffConfig auditUpdate = new StoreStaffConfig();
+        auditUpdate.setId(staffId);
+        if (auditResult != null && auditResult.isPassed()) {
+            auditUpdate.setStatus("1");
+            auditUpdate.setRejectionReason(null);
+        } else {
+            String reason = (auditResult != null && StringUtils.isNotEmpty(auditResult.getFailureReason()))
+                    ? auditResult.getFailureReason()
+                    : "审核未通过";
+            log.warn("人员内容审核失败:{}", reason);
+            auditUpdate.setStatus("2");
+            auditUpdate.setRejectionReason(reason);
+        }
+        storeStaffConfigMapper.updateById(auditUpdate);
+
+        return affected;
+    }
+
+    private int insertStaff(StoreStaffConfig staff) {
+        // 新增时,确保id为null,让数据库自增
+        staff.setId(null);
+        return storeStaffConfigMapper.insert(staff);
+    }
+
+    private void applyCreateDefaults(StoreStaffConfig staff) {
+        staff.setCreatedTime(new Date(System.currentTimeMillis()));
+        if (staff.getDeleteFlag() == null) {
+            staff.setDeleteFlag(0);
+        }
+        if (StringUtils.isEmpty(staff.getStatus())) {
+            staff.setStatus("0"); // 待审核
+        }
+        if (staff.getOnlineStatus() == null) {
+            staff.setOnlineStatus(0); // 默认上线
+        }
     }
 
     @Override
@@ -114,7 +251,14 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
 
     @Override
     public StoreStaffConfig getStaffConfigDeatail(Integer id) {
-        return storeStaffConfigMapper.selectById(id);
+        StoreStaffConfig staff = storeStaffConfigMapper.selectById(id);
+        if (staff == null) {
+            return null;
+        }
+        if (isFitnessBusinessSection(staff.getBusinessSection())) {
+            attachFitnessDetails(staff);
+        }
+        return staff;
     }
 
     @Override
@@ -151,7 +295,399 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
     public Integer deleteStaffConfig(Integer id) {
         // 使用 MyBatis-Plus 的逻辑删除,会自动将 deleteFlag 设置为 1
         // 因为实体类使用了 @TableLogic 注解
-        return storeStaffConfigMapper.deleteById(id);
+        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;
+        }
+
+        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;
+    }
+
+    /**
+     * 将明细行结构组装成分组结构(用于前端绑定)
+     * - 优先按 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
@@ -171,33 +707,208 @@ public class StoreStaffConfigServiceImpl implements StoreStaffConfigService {
     }
 
     @Override
+    public Integer setOnlineStatus(Integer id, Integer onlineStatus) {
+        if (id == null || onlineStatus == null) {
+            return 0;
+        }
+        LambdaUpdateWrapper<StoreStaffConfig> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+        lambdaUpdateWrapper.eq(StoreStaffConfig::getId, id);
+        lambdaUpdateWrapper.set(StoreStaffConfig::getOnlineStatus, onlineStatus);
+        return storeStaffConfigMapper.update(null, lambdaUpdateWrapper);
+    }
+
+    @Override
     public IPage<StoreStaffConfig> queryStaffList(Integer page, Integer size, Integer storeId, String status) {
+        // 参数校验
+        if (page == null || page < 1) {
+            page = 1;
+        }
+        if (size == null || size < 1) {
+            size = 10;
+        }
+        if (storeId == null || storeId <= 0) {
+            throw new IllegalArgumentException("店铺ID不能为空且必须大于0");
+        }
+
+        // 构建分页对象
+        IPage<StoreStaffConfig> staffPage = new Page<>(page, size);
+
+        // 使用 LambdaQueryWrapper 构建查询条件,类型安全
+        LambdaQueryWrapper<StoreStaffConfig> queryWrapper = new LambdaQueryWrapper<>();
+
+        // 必须条件:店铺ID和未删除
+        queryWrapper.eq(StoreStaffConfig::getStoreId, storeId)
+                .eq(StoreStaffConfig::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE);
+
+        // 可选条件:员工状态筛选
+        if (StringUtils.isNotEmpty(status)) {
+            queryWrapper.eq(StoreStaffConfig::getStatus, status);
+        }
+
+        // 排序规则:先按置顶状态降序(置顶的在前),再按置顶时间降序,最后按创建时间降序
+        queryWrapper.orderByDesc(StoreStaffConfig::getTopStatus)
+                .orderByDesc(StoreStaffConfig::getTopTime)
+                .orderByDesc(StoreStaffConfig::getCreatedTime);
+
+        return storeStaffConfigMapper.selectPage(staffPage, queryWrapper);
+    }
+
+    @Override
+    public StoreStaffConfig queryStaffDetail(Integer id) {
+        if (id == null || id <= 0) {
+            return null;
+        }
+        QueryWrapper<StoreStaffConfig> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("id", id);
+        queryWrapper.eq("delete_flag", 0);
+        return storeStaffConfigMapper.selectOne(queryWrapper);
+
+
+    }
+
+    @Override
+    public IPage<StoreStaffConfig> getFoodStaffConfigList(int page, int size, Integer storeId, String staffPosition, String status) {
         IPage<StoreStaffConfig> storePage = new Page<>(page, size);
         QueryWrapper<StoreStaffConfig> queryWrapper = new QueryWrapper<>();
         // 按照店铺ID查询
         if (storeId != null && storeId > 0) {
             queryWrapper.eq("store_id", storeId);
         }
-        // 如果状态不为空,则进行精确匹配查询
-//        if (StringUtils.isNotEmpty(status)) {
-//            queryWrapper.eq("status", status);
-//        }
+        // 按照职位查询
+        if (StringUtils.isNotEmpty(staffPosition)) {
+            queryWrapper.eq("staff_position", staffPosition);
+        }
+        // 按照状态查询
+        if (StringUtils.isNotEmpty(status)) {
+            queryWrapper.eq("status", status);
+        }
         // 只查询未删除的记录
         queryWrapper.eq("delete_flag", 0);
-        queryWrapper.orderByDesc("created_time");
+        // 排序规则:先按置顶状态降序(置顶的在前),再按置顶时间降序,最后按创建时间降序
+        queryWrapper.orderByDesc("top_status", "top_time", "created_time");
         return storeStaffConfigMapper.selectPage(storePage, queryWrapper);
     }
 
     @Override
-    public StoreStaffConfig queryStaffDetail(Integer id) {
+    public StoreStaffDetailVo getStaffDetailWithCourse(Integer id) {
+        log.info("查询员工详情(包含课程信息),id={}", id);
+
+        // 参数校验
         if (id == null || id <= 0) {
+            log.warn("查询员工详情失败,员工ID无效:{}", id);
             return null;
         }
-        QueryWrapper<StoreStaffConfig> queryWrapper = new QueryWrapper<>();
-        queryWrapper.eq("id", id);
-        queryWrapper.eq("delete_flag", 0);
-        return storeStaffConfigMapper.selectOne(queryWrapper);
 
+        try {
+            // 查询员工基本信息
+            LambdaQueryWrapper<StoreStaffConfig> staffWrapper = new LambdaQueryWrapper<>();
+            staffWrapper.eq(StoreStaffConfig::getId, id)
+                    .eq(StoreStaffConfig::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE);
+            StoreStaffConfig staffConfig = storeStaffConfigMapper.selectOne(staffWrapper);
+
+            if (staffConfig == null) {
+                log.warn("查询员工详情失败,员工不存在:id={}", id);
+                return null;
+            }
 
+            // 查询关联的课程信息
+            List<shop.alien.entity.store.StoreStaffFitnessCourse> courseList =
+                    storeStaffFitnessCourseService.getListByStaffId(id);
+
+
+            // 根据店铺ID查询店铺名称
+            String storeName = null;
+            if (staffConfig.getStoreId() != null && staffConfig.getStoreId() > 0) {
+                try {
+                    StoreInfo storeInfo = storeInfoMapper.selectById(staffConfig.getStoreId());
+                    if (storeInfo != null) {
+                        storeName = storeInfo.getStoreName();
+                    } else {
+                        log.warn("查询店铺信息失败,店铺不存在:storeId={}", staffConfig.getStoreId());
+                    }
+                } catch (Exception e) {
+                    log.warn("查询店铺信息异常,storeId={},异常信息:{}", staffConfig.getStoreId(), e.getMessage());
+                    // 店铺信息查询失败不影响整体返回,设置为null即可
+                }
+            }
+
+            // 构建返回对象
+            StoreStaffDetailVo detailVo = new StoreStaffDetailVo();
+            detailVo.setStaffInfo(staffConfig);
+            detailVo.setCourseList(courseList);
+
+            log.info("查询员工详情成功,id={},课程数量:{}", id, courseList != null ? courseList.size() : 0);
+            detailVo.setStoreName(storeName);
+
+            log.info("查询员工详情成功,id={},课程数量:{},店铺名称:{}",
+                    id,
+                    courseList != null ? courseList.size() : 0,
+                    storeName);
+            return detailVo;
+        } catch (Exception e) {
+            log.error("查询员工详情异常,id={},异常信息:{}", id, e.getMessage(), e);
+            throw new RuntimeException("查询员工详情异常:" + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public StoreStaffFitnessDetailVo getFitnessCoachDetail(Integer id) {
+        log.info("查询健身教练详情,id={}", id);
+
+        // 参数校验
+        if (id == null || id <= 0) {
+            log.warn("查询健身教练详情失败,员工ID无效:{}", id);
+            return null;
+        }
+
+        try {
+            // 查询员工基本信息
+            LambdaQueryWrapper<StoreStaffConfig> staffWrapper = new LambdaQueryWrapper<>();
+            staffWrapper.eq(StoreStaffConfig::getId, id)
+                    .eq(StoreStaffConfig::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE);
+            StoreStaffConfig staffConfig = storeStaffConfigMapper.selectOne(staffWrapper);
+
+            if (staffConfig == null) {
+                log.warn("查询健身教练详情失败,员工不存在:id={}", id);
+                return null;
+            }
+
+            // 查询员工基本信息(运动健身)
+            shop.alien.entity.store.StoreStaffFitnessBase baseInfo = null;
+            try {
+                shop.alien.entity.result.R<shop.alien.entity.store.StoreStaffFitnessBase> baseResult =
+                        storeStaffFitnessBaseService.getByStaffId(id);
+                if (baseResult != null && shop.alien.entity.result.R.isSuccess(baseResult) && baseResult.getData() != null) {
+                    baseInfo = baseResult.getData();
+                }
+            } catch (Exception e) {
+                log.warn("查询员工基本信息失败,id={},异常信息:{}", id, e.getMessage());
+                // 基本信息查询失败不影响整体返回,设置为null即可
+            }
+
+            // 查询认证列表(type=1)
+            List<shop.alien.entity.store.StoreStaffFitnessCertification> certificationList =
+                    storeStaffFitnessCertificationService.getListByStaffIdAndType(id, CERTIFICATION_TYPE);
+
+            // 查询荣誉列表(type=2)
+            List<shop.alien.entity.store.StoreStaffFitnessCertification> honorList =
+                    storeStaffFitnessCertificationService.getListByStaffIdAndType(id, HONOR_TYPE);
+
+            // 构建返回对象
+            StoreStaffFitnessDetailVo detailVo = new StoreStaffFitnessDetailVo();
+            detailVo.setStaffInfo(staffConfig);
+            detailVo.setBaseInfo(baseInfo);
+            detailVo.setCertificationList(certificationList != null ? certificationList : new ArrayList<>());
+            detailVo.setHonorList(honorList != null ? honorList : new ArrayList<>());
+
+            log.info("查询健身教练详情成功,id={},认证数量:{},荣誉数量:{}",
+                    id,
+                    certificationList != null ? certificationList.size() : 0,
+                    honorList != null ? honorList.size() : 0);
+            return detailVo;
+        } catch (Exception e) {
+            log.error("查询健身教练详情异常,id={},异常信息:{}", id, e.getMessage(), e);
+            throw new RuntimeException("查询健身教练详情异常:" + e.getMessage(), e);
+        }
     }
 }

+ 205 - 0
alien-store/src/main/java/shop/alien/store/util/ai/AiContentModerationUtil.java

@@ -0,0 +1,205 @@
+package shop.alien.store.util.ai;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * 通用图文审核工具类
+ * 调用AI图文审核接口审核文本和图片内容
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AiContentModerationUtil {
+
+    private final RestTemplate restTemplate;
+
+    /**
+     * AI审核接口地址
+     */
+    @Value("${ai.service.moderate-url}")
+    private String moderateUrl;
+
+    /**
+     * 审核结果类
+     */
+    public static class AuditResult {
+        private boolean passed;
+        private String failureReason;
+
+        public AuditResult(boolean passed, String failureReason) {
+            this.passed = passed;
+            this.failureReason = failureReason;
+        }
+
+        public boolean isPassed() {
+            return passed;
+        }
+
+        public String getFailureReason() {
+            return failureReason;
+        }
+    }
+
+    /**
+     * 审核文本和图片内容
+     *
+     * @param text 文本内容
+     * @param imageUrls 图片URL列表
+     * @return 审核结果
+     */
+    public AuditResult auditContent(String text, List<String> imageUrls) {
+        log.info("开始审核内容:text={}, imageCount={}",
+                text, imageUrls != null ? imageUrls.size() : 0);
+
+        try {
+            // 调用审核接口
+            return callModerateApi(text, imageUrls);
+
+        } catch (Exception e) {
+            log.error("审核内容异常", e);
+            return new AuditResult(false, "审核服务异常:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 调用AI审核接口
+     *
+     * @param text 文本内容
+     * @param imageUrls 图片URL列表
+     * @return 审核结果
+     */
+    private AuditResult callModerateApi(String text, List<String> imageUrls) {
+        try {
+            // 构建 form-data 请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+
+            // 构建 form-data 请求体
+            MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
+            if (StringUtils.hasText(text)) {
+                formData.add("text", text);
+            }
+            if (imageUrls != null) {
+                for (String url : imageUrls) {
+                    if (StringUtils.hasText(url)) {
+                        // 支持多张图片:同一个 key 重复多次
+                        formData.add("image_urls", url);
+                    }
+                }
+            }
+
+            HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(formData, headers);
+
+            log.info("调用AI审核接口:url={}, text={}, imageCount={}",
+                    moderateUrl, text, imageUrls != null ? imageUrls.size() : 0);
+
+            // 发送请求
+            ResponseEntity<String> response = restTemplate.postForEntity(moderateUrl, requestEntity, String.class);
+
+            if (response.getStatusCode() == HttpStatus.OK) {
+                String responseBody = response.getBody();
+                log.info("AI审核接口响应:{}", responseBody);
+
+                if (StringUtils.hasText(responseBody)) {
+                    JSONObject jsonResponse = JSONObject.parseObject(responseBody);
+                    return parseAuditResult(jsonResponse);
+                } else {
+                    log.error("AI审核接口返回空响应");
+                    return new AuditResult(false, "审核服务返回空响应");
+                }
+            } else {
+                log.error("AI审核接口调用失败,状态码:{}", response.getStatusCode());
+                return new AuditResult(false, "审核服务调用失败,状态码:" + response.getStatusCode());
+            }
+
+        } catch (Exception e) {
+            log.error("调用AI审核接口异常", e);
+            return new AuditResult(false, "审核服务调用异常:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 解析审核结果
+     *
+     * @param jsonResponse API响应JSON
+     * @return 审核结果
+     */
+    private AuditResult parseAuditResult(JSONObject jsonResponse) {
+        try {
+            // API返回格式:
+            // {
+            //     "results": [
+            //         {
+            //             "filename": "[TEXT INPUT]" 或 URL,
+            //             "flagged": true/false,
+            //             "risk_level": "high"/"safe"等,
+            //             "violation_categories": [...],
+            //             "reason": "原因描述"
+            //         }
+            //     ],
+            //     "summary": "处理摘要"
+            // }
+
+            com.alibaba.fastjson2.JSONArray results = jsonResponse.getJSONArray("results");
+            if (results == null || results.isEmpty()) {
+                log.warn("AI审核接口返回结果为空");
+                return new AuditResult(false, "审核结果为空");
+            }
+
+            // 检查是否有任何项目被标记为违规
+            List<String> violationReasons = new ArrayList<>();
+            boolean hasViolations = false;
+
+            for (int i = 0; i < results.size(); i++) {
+                JSONObject result = results.getJSONObject(i);
+                if (result != null) {
+                    Boolean flagged = result.getBoolean("flagged");
+                    String filename = result.getString("filename");
+
+                    if (flagged != null && flagged) {
+                        hasViolations = true;
+                        // 根据filename判断是文本还是图片违规
+                        if ("[TEXT INPUT]".equals(filename)) {
+                            violationReasons.add("含违规词汇");
+                        } else if (StringUtils.hasText(filename) && filename.startsWith("http")) {
+                            violationReasons.add("图片内容违规");
+                        } else {
+                            violationReasons.add("内容违规");
+                        }
+                    }
+                }
+            }
+
+            if (hasViolations) {
+                // 有违规内容,审核失败
+                String failureReason = String.join("; ", violationReasons);
+                log.warn("AI审核失败:{}", failureReason);
+                return new AuditResult(false, failureReason);
+            } else {
+                // 所有内容都安全,审核通过
+                log.info("AI审核通过:所有内容安全");
+                return new AuditResult(true, null);
+            }
+
+        } catch (Exception e) {
+            log.error("解析审核结果异常", e);
+            return new AuditResult(false, "解析审核结果异常:" + e.getMessage());
+        }
+    }
+}