wuchen 2 месяцев назад
Родитель
Сommit
7747fa8229

+ 2 - 2
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreSubExcelVo.java

@@ -15,8 +15,8 @@ public class StoreSubExcelVo {
     @ApiModelProperty(value = "序号")
     private Integer serialNumber;
 
-    @ApiModelProperty(value = "账号ID")
-    @ExcelHeader(value = "账号ID")
+    @ApiModelProperty(value = "账号ID")
+    @ExcelHeader(value = "账号ID")
     private Integer id;
 
     @ApiModelProperty(value = "主账号手机号")

+ 23 - 23
alien-store/src/main/java/shop/alien/store/controller/StoreUserController.java

@@ -225,15 +225,16 @@ public class StoreUserController {
     @ApiImplicitParams({
             @ApiImplicitParam(name = "pageNum", value = "页数", dataType = "int", paramType = "query", required = false),
             @ApiImplicitParam(name = "pageSize", value = "页容", dataType = "int", paramType = "query", required = false),
-            @ApiImplicitParam(name = "id", value = "账号ID", dataType = "String", paramType = "query", required = false),
-            @ApiImplicitParam(name = "phone", value = "联系电话", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "id", value = "账号ID(主账号为自身id,子账号为子账号id)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "phone", value = "联系电话(主账号/子账号自身电话)", dataType = "String", paramType = "query", required = false),
             @ApiImplicitParam(name = "status", value = "状态", dataType = "int", paramType = "query", required = false),
-            @ApiImplicitParam(name = "accountType", value = "账号类型:1-主账号,2-子账号", dataType = "int", paramType = "query", required = true)
+            @ApiImplicitParam(name = "accountType", value = "账号类型:1-主账号,2-子账号", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "mainAccountId", value = "主账号ID(子账号列表时支持模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "mainAccountPhone", value = "主账号联系电话(子账号列表时支持模糊查询)", dataType = "String", paramType = "query", required = false)
     })
-    public R<IPage<StoreUserVo>> getStoreUserList(@RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "10") int pageSize, @RequestParam(required = false) String id, @RequestParam(required = false) String phone, @RequestParam(required = false) Integer status, @RequestParam(required = true) Integer accountType) {
-        log.info("StoreUserController.getStoreUserList?pageNum={},pageSize={},id={},phone={},status={},accountType={}", pageNum, pageSize, id, phone, status, accountType);
-        R<IPage<StoreUserVo>> storeUserVoR = storeUserService.getStoreUserList(pageNum, pageSize, id, phone, status, accountType);
-        return storeUserVoR;
+    public R<IPage<StoreUserVo>> getStoreUserList(@RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "10") int pageSize, @RequestParam(required = false) String id, @RequestParam(required = false) String phone, @RequestParam(required = false) Integer status, @RequestParam(required = true) Integer accountType, @RequestParam(required = false) String mainAccountId, @RequestParam(required = false) String mainAccountPhone) {
+        log.info("StoreUserController.getStoreUserList?pageNum={},pageSize={},id={},phone={},status={},accountType={},mainAccountId={},mainAccountPhone={}", pageNum, pageSize, id, phone, status, accountType, mainAccountId, mainAccountPhone);
+        return storeUserService.getStoreUserList(pageNum, pageSize, id, phone, status, accountType, mainAccountId, mainAccountPhone);
     }
 
     /**
@@ -280,27 +281,21 @@ public class StoreUserController {
         return R.success("初始化成功");
     }
 
-    /**
-     * web端删除商家端用户
-     */
-    @ApiOperation("web端删除商家端用户")
-    @ApiOperationSupport(order = 7)
-    @DeleteMapping("/deleteStoreUser")
-    public R<StoreUserVo> deleteStoreUser(@RequestParam(value = "id") String id) {
-        log.info("StoreUserController.deleteStoreUser?id={}", id);
-        return storeUserService.deleteStoreUser(id);
-    }
 
     /**
-     * web端切换商家端用户
+     * web端切换商家端用户状态(主账号有子账号时禁止禁用;子账号无限制;返回更新后状态供前端及时展示)
      */
-    @ApiOperation("web端切换商家端用户")
+    @ApiOperation("web端切换商家端用户状态(主账号有子账号时禁止禁用;返回更新后状态供前端及时展示)")
     @ApiOperationSupport(order = 7)
     @PutMapping("/switchingStates")
     public R<StoreUserVo> switchingStates(@RequestBody StoreUser storeUser) {
         log.info("StoreUserController.switchingStates?storeUser={}", storeUser);
-        storeUserService.switchingStates(storeUser);
-        return R.success("切换成功");
+        try {
+            return storeUserService.switchingStates(storeUser);
+        } catch (Exception e) {
+            log.warn("StoreUserController.switchingStates 校验或切换失败: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        }
     }
 
     @ApiOperation(value = "web端导出商家端账号相关信息")
@@ -425,8 +420,13 @@ public class StoreUserController {
     @PostMapping("/deleteStoreAccountInfo")
     public R<StoreUserVo> deleteStoreAccountInfo(@RequestBody StoreUserVo storeUserVo) {
         log.info("StoreUserController.deleteStoreAccountInfo?storeUserVo={}", storeUserVo);
-        storeUserService.deleteStoreAccountInfo(storeUserVo);
-        return R.success("删除成功");
+        try {
+            storeUserService.deleteStoreAccountInfo(storeUserVo);
+            return R.success("删除成功");
+        } catch (Exception e) {
+            log.warn("StoreUserController.deleteStoreAccountInfo 校验或删除失败: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        }
     }
 
     /**

+ 4 - 11
alien-store/src/main/java/shop/alien/store/service/StoreUserService.java

@@ -100,7 +100,7 @@ public interface StoreUserService extends IService<StoreUser> {
      *
      * @return boolean
      */
-    R<IPage<StoreUserVo>> getStoreUserList(int pageNum, int pageSize, String id, String phone, Integer status,Integer accountType);
+    R<IPage<StoreUserVo>> getStoreUserList(int pageNum, int pageSize, String id, String phone, Integer status, Integer accountType, String mainAccountId, String mainAccountPhone);
 
 
     /**
@@ -118,18 +118,11 @@ public interface StoreUserService extends IService<StoreUser> {
     void resetStoreUserPassword(StoreUser storeUser);
 
     /**
-     * web端编辑用户列表
+     * web端切换商家端用户状态(主账号底下有子账号时禁止禁用;子账号无限制)
      *
-     * @return boolean
-     */
-    R<StoreUserVo> deleteStoreUser(String id);
-
-    /**
-     * web端切换商家端用户状态
-     *
-     * @return boolean
+     * @return 更新后的用户信息(含最新 status),供前端及时展示
      */
-    void switchingStates(StoreUser storeUser);
+    R<StoreUserVo> switchingStates(StoreUser storeUserParam);
 
 
     /**

+ 304 - 227
alien-store/src/main/java/shop/alien/store/service/impl/StoreUserServiceImpl.java

@@ -271,7 +271,7 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
     }
 
     private void passwordVerification(String phone, String password, String newPassword, String confirmNewPassword) {
-        LambdaQueryWrapper<StoreUser> wrapperFans = new LambdaQueryWrapper<>();
+        LambdaUpdateWrapper<StoreUser> wrapperFans = new LambdaUpdateWrapper<>();
         wrapperFans.eq(StoreUser::getPhone, phone);
         StoreUser storeUser = this.getOne(wrapperFans);
         if (!newPassword.equals(confirmNewPassword)) {
@@ -282,10 +282,9 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
             log.info("该手机号没有注册过账户");
             throw new RuntimeException("该手机号没有注册过账户");
         } else {
-            // 由于password字段使用了EncryptTypeHandler,查询时密码会被自动解密
-            // 所以这里直接比较解密后的密码和用户输入的明文密码
-            String dbPassword = storeUser.getPassword();
-            if (dbPassword == null || dbPassword.isEmpty() || !dbPassword.equals(password)) {
+            wrapperFans.eq(StoreUser::getPassword, password);
+            StoreUser storeUserPw = this.getOne(wrapperFans);
+            if (storeUserPw == null || storeUserPw.getPassword().equals("")) {
                 log.info("原密码错误");
                 throw new RuntimeException("原密码错误");
             }
@@ -404,152 +403,165 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
         return R.data(vo);
     }
 
-    @Override
-    public R<IPage<StoreUserVo>> getStoreUserList(int pageNum, int pageSize, String id, String phone, Integer status,Integer accountType) {
 
+    @Override
+    public R<IPage<StoreUserVo>> getStoreUserList(int pageNum, int pageSize, String id, String phone, Integer status, Integer accountType, String mainAccountId, String mainAccountPhone) {
         IPage<StoreUser> page = new Page<>(pageNum, pageSize);
         IPage<StoreUserVo> storeUserVoIPage = new Page<>();
-        // 查询子账号(accountType == 2)
+        // 子账号分支:以 store_platform_user_role 每条记录为一行(不去重 userId),同一子账号在不同店铺展示多行,每行带主账号 id
         if (accountType == 2) {
-            // 构建子账号查询条件
-            LambdaQueryWrapper<StoreUser> subAccountWrapper = new LambdaQueryWrapper<>();
-            subAccountWrapper.eq(StoreUser::getAccountType, 2) // 子账号类型
-                    .eq(status != null, StoreUser::getStatus, status);
-
-            // 先通过中间表storePlatformUserRole获取所有子账号的userId列表
             LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
+            roleWrapper.eq(StorePlatformUserRole::getDeleteFlag, 0);
             List<StorePlatformUserRole> userRoles = storePlatformUserRoleMapper.selectList(roleWrapper);
-            List<Integer> subAccountUserIds = null;
-            if (!CollectionUtils.isEmpty(userRoles)) {
-                // 提取所有在中间表中的子账号userId
-                subAccountUserIds = userRoles.stream()
-                        .map(StorePlatformUserRole::getUserId)
-                        .distinct()
-                        .collect(Collectors.toList());
-            }
-
-            // 当id不为空时,对ID进行模糊查询(将ID转换为字符串进行模糊匹配)
-            if (StringUtils.isNotEmpty(id)) {
-                // 使用apply方法,将ID转换为字符串进行模糊查询
-                subAccountWrapper.apply("CAST(id AS CHAR) LIKE {0}", "%" + id + "%");
+            if (CollectionUtils.isEmpty(userRoles)) {
+                storeUserVoIPage.setRecords(new ArrayList<>());
+                storeUserVoIPage.setTotal(0);
+                storeUserVoIPage.setCurrent(pageNum);
+                storeUserVoIPage.setSize(pageSize);
+                return R.data(storeUserVoIPage);
             }
 
-            // 当phone不为空时,对子账号和主账号电话进行模糊查询,并关联storePlatformUserRole
-            if (StringUtils.isNotEmpty(phone)) {
-                if (subAccountUserIds != null && !subAccountUserIds.isEmpty()) {
-                    // 先查询主账号phone匹配的主账号ID列表
-                    LambdaQueryWrapper<StoreUser> mainAccountWrapper = new LambdaQueryWrapper<>();
-                    mainAccountWrapper.eq(StoreUser::getAccountType, 1)
-                            .like(StoreUser::getPhone, phone);
-                    List<StoreUser> mainAccounts = storeUserMapper.selectList(mainAccountWrapper);
-                    List<Integer> mainAccountIds = mainAccounts.stream()
-                            .map(StoreUser::getId)
-                            .collect(Collectors.toList());
-
-                    // 限制查询范围:只查询在中间表中存在的子账号
-                    subAccountWrapper.in(StoreUser::getId, subAccountUserIds);
-                    // 查询条件:在中间表范围内的子账号中,子账号phone匹配 OR 主账号phone匹配
-                    subAccountWrapper.and(wrapper -> {
-                        // 子账号本身的phone模糊查询
-                        wrapper.like(StoreUser::getPhone, phone);
-                        // 或者主账号phone匹配(通过subAccountId关联到主账号)
-                        if (!CollectionUtils.isEmpty(mainAccountIds)) {
-                            wrapper.or().in(StoreUser::getSubAccountId, mainAccountIds);
-                        }
-                    });
+            // 主账号ID 模糊:只保留 platform 中 store_id 属于这些主账号店铺的 role 记录
+            if (StringUtils.isNotEmpty(mainAccountId)) {
+                LambdaQueryWrapper<StoreUser> mainById = new LambdaQueryWrapper<>();
+                mainById.eq(StoreUser::getAccountType, 1).eq(StoreUser::getDeleteFlag, 0)
+                        .apply("CAST(id AS CHAR) LIKE {0}", "%" + mainAccountId + "%");
+                List<StoreUser> mains = storeUserMapper.selectList(mainById);
+                List<Integer> storeIds = mains == null ? Collections.emptyList() : mains.stream().map(StoreUser::getStoreId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
+                if (storeIds.isEmpty()) {
+                    userRoles = Collections.emptyList();
                 } else {
-                    // 如果没有中间表数据,先查询主账号phone匹配的主账号ID
-                    LambdaQueryWrapper<StoreUser> mainAccountWrapper = new LambdaQueryWrapper<>();
-                    mainAccountWrapper.eq(StoreUser::getAccountType, 1)
-                            .like(StoreUser::getPhone, phone);
-                    List<StoreUser> mainAccounts = storeUserMapper.selectList(mainAccountWrapper);
-                    List<Integer> mainAccountIds = mainAccounts.stream()
-                            .map(StoreUser::getId)
-                            .collect(Collectors.toList());
-
-                    // 查询条件:子账号phone匹配 OR 主账号phone匹配
-                    if (!CollectionUtils.isEmpty(mainAccountIds)) {
-                        subAccountWrapper.and(wrapper -> {
-                            wrapper.like(StoreUser::getPhone, phone)
-                                    .or().in(StoreUser::getSubAccountId, mainAccountIds);
-                        });
-                    } else {
-                        // 如果没有主账号匹配,只查询子账号phone
-                        subAccountWrapper.like(StoreUser::getPhone, phone);
-                    }
+                    userRoles = userRoles.stream().filter(r -> storeIds.contains(r.getStoreId())).collect(Collectors.toList());
                 }
-            } else {
-                // phone为空时,如果中间表有数据,限制只查询中间表中的子账号
-                if (subAccountUserIds != null && !subAccountUserIds.isEmpty()) {
-                    subAccountWrapper.in(StoreUser::getId, subAccountUserIds);
+            }
+            // 主账号联系电话 模糊:只保留 platform 中 store_id 属于这些主账号店铺的 role 记录
+            if (StringUtils.isNotEmpty(mainAccountPhone) && !userRoles.isEmpty()) {
+                LambdaQueryWrapper<StoreUser> mainByPhone = new LambdaQueryWrapper<>();
+                mainByPhone.eq(StoreUser::getAccountType, 1).eq(StoreUser::getDeleteFlag, 0).like(StoreUser::getPhone, mainAccountPhone);
+                List<StoreUser> mains = storeUserMapper.selectList(mainByPhone);
+                List<Integer> storeIds = mains == null ? Collections.emptyList() : mains.stream().map(StoreUser::getStoreId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
+                if (storeIds.isEmpty()) {
+                    userRoles = Collections.emptyList();
+                } else {
+                    userRoles = userRoles.stream().filter(r -> storeIds.contains(r.getStoreId())).collect(Collectors.toList());
                 }
             }
 
-            subAccountWrapper.orderByDesc(StoreUser::getCreatedTime);
+            if (userRoles.isEmpty()) {
+                storeUserVoIPage.setRecords(new ArrayList<>());
+                storeUserVoIPage.setTotal(0);
+                storeUserVoIPage.setCurrent(pageNum);
+                storeUserVoIPage.setSize(pageSize);
+                return R.data(storeUserVoIPage);
+            }
+
+            // 按子账号 id/phone/status 过滤:批量查 store_user,只保留对应子账号满足条件的 role
+            List<Integer> distinctUserIds = userRoles.stream().map(StorePlatformUserRole::getUserId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
+            List<StoreUser> subAccountList = distinctUserIds.isEmpty() ? Collections.emptyList() : storeUserMapper.selectBatchIds(distinctUserIds);
+            Map<Integer, StoreUser> subAccountMap = subAccountList == null ? new HashMap<>() : subAccountList.stream().filter(u -> u.getDeleteFlag() != null && u.getDeleteFlag() == 0).collect(Collectors.toMap(StoreUser::getId, u -> u, (a, b) -> a));
+            List<StorePlatformUserRole> filteredRoles = new ArrayList<>();
+            for (StorePlatformUserRole role : userRoles) {
+                StoreUser subAccount = subAccountMap.get(role.getUserId());
+                if (subAccount == null) continue;
+                if (StringUtils.isNotEmpty(id) && (subAccount.getId() == null || !String.valueOf(subAccount.getId()).contains(id))) continue;
+                if (StringUtils.isNotEmpty(phone) && (subAccount.getPhone() == null || !subAccount.getPhone().contains(phone))) continue;
+                if (status != null && !status.equals(subAccount.getStatus())) continue;
+                filteredRoles.add(role);
+            }
+            filteredRoles.sort(Comparator.comparing(StorePlatformUserRole::getId, Comparator.nullsLast(Comparator.naturalOrder())));
+
+            int total = filteredRoles.size();
+            storeUserVoIPage.setTotal(total);
+            storeUserVoIPage.setCurrent(pageNum);
+            storeUserVoIPage.setSize(pageSize);
+            int from = (pageNum - 1) * pageSize;
+            if (from >= total) {
+                storeUserVoIPage.setRecords(new ArrayList<>());
+                return R.data(storeUserVoIPage);
+            }
+            int to = Math.min(from + pageSize, total);
+            List<StorePlatformUserRole> pageRoles = filteredRoles.subList(from, to);
+
+            // 批量查本页主账号,避免 N+1
+            List<Integer> pageStoreIds = pageRoles.stream().map(StorePlatformUserRole::getStoreId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
+            Map<Integer, StoreUser> mainAccountMap = new HashMap<>();
+            if (!pageStoreIds.isEmpty()) {
+                LambdaQueryWrapper<StoreUser> mainW = new LambdaQueryWrapper<>();
+                mainW.eq(StoreUser::getAccountType, 1).eq(StoreUser::getDeleteFlag, 0).in(StoreUser::getStoreId, pageStoreIds);
+                List<StoreUser> mains = storeUserMapper.selectList(mainW);
+                if (mains != null) mains.forEach(m -> mainAccountMap.put(m.getStoreId(), m));
+            }
 
-            IPage<StoreUser> subAccountsPage = storeUserMapper.selectPage(page, subAccountWrapper);
-            BeanUtils.copyProperties(subAccountsPage, storeUserVoIPage);
-            
             List<StoreUserVo> resultRecords = new ArrayList<>();
-            for (StoreUser subAccount : subAccountsPage.getRecords()) {
+            for (StorePlatformUserRole role : pageRoles) {
+                StoreUser subAccount = subAccountMap.get(role.getUserId());
+                if (subAccount == null) continue;
                 StoreUserVo storeUserVo = new StoreUserVo();
                 BeanUtils.copyProperties(subAccount, storeUserVo);
-                
-                // 查询主账号信息(通过 subAccountId 关联)
-                if (subAccount.getSubAccountId() != null) {
-                    StoreUser mainAccount = storeUserMapper.selectById(subAccount.getSubAccountId());
-                    if (mainAccount != null) {
-                        // 设置主账号联系电话
-                        storeUserVo.setParentAccountPhone(mainAccount.getPhone());
-                        storeUserVo.setParentAccountId(mainAccount.getId());
-                        storeUserVo.setParentAccountName(mainAccount.getName());
-                    }
+                storeUserVo.setSwitchStatus(subAccount.getStatus() != null && subAccount.getStatus() == 0);
+                storeUserVo.setPhone(subAccount.getPhone());
+                StoreUser mainAccount = role.getStoreId() == null ? null : mainAccountMap.get(role.getStoreId());
+                if (mainAccount != null) {
+                    storeUserVo.setParentAccountId(mainAccount.getId());
+                    storeUserVo.setParentAccountPhone(mainAccount.getPhone());
+                    storeUserVo.setParentAccountName(mainAccount.getName());
                 }
-                
-                // 不返回密码
                 storeUserVo.setPassword(null);
                 storeUserVo.setPayPassword(null);
-                
                 resultRecords.add(storeUserVo);
             }
-            
             storeUserVoIPage.setRecords(resultRecords);
             return R.data(storeUserVoIPage);
         }
 
-        // 查询主账号(accountType == 1)
+        // 主账号分支:直接查库返回最新 status,主账号页面展示子账号数量(按 store_platform_user_role 统计
         LambdaQueryWrapper<StoreUser> storeUserLambdaQueryWrapper = new LambdaQueryWrapper<>();
-        storeUserLambdaQueryWrapper.like(!StringUtils.isEmpty(id), StoreUser::getId, id)
-                .like(!StringUtils.isEmpty(phone), StoreUser::getPhone, phone)
+        storeUserLambdaQueryWrapper.eq(StoreUser::getDeleteFlag, 0)
+                .like(StringUtils.isNotEmpty(id), StoreUser::getId, id)
+                .like(StringUtils.isNotEmpty(phone), StoreUser::getPhone, phone)
                 .eq(status != null, StoreUser::getStatus, status)
-                .eq(StoreUser::getAccountType, accountType)
+                .eq(StoreUser::getAccountType, 1)
                 .orderByDesc(StoreUser::getCreatedTime);
-        
+
         IPage<StoreUser> storeUsers = storeUserMapper.selectPage(page, storeUserLambdaQueryWrapper);
         BeanUtils.copyProperties(storeUsers, storeUserVoIPage);
-        
-        for (StoreUser storeUser : storeUserVoIPage.getRecords()) {
-            // 不返回密码
-            storeUser.setPassword(null);
-            storeUser.setPayPassword(null);
-
-            // 如果是主账号,统计子账号数量
-            if (accountType == 1) {
-                List<StoreUser> childAccounts = getChildAccountsByParentId(String.valueOf(storeUser.getId()));
-                Integer childCount = childAccounts != null ? childAccounts.size() : 0;
-                storeUser.setChildAccountCount(childCount);
-                if (childAccounts != null && !childAccounts.isEmpty()) {
-                    List<String> childPhoneNumbers = childAccounts.stream()
-                            .map(StoreUser::getPhone)
-                            .collect(Collectors.toList());
-                    storeUser.setChildPhoneNumbers(childPhoneNumbers);
-                }
+
+        List<StoreUserVo> resultRecords = new ArrayList<>();
+        for (StoreUser storeUser : storeUsers.getRecords()) {
+            StoreUserVo vo = new StoreUserVo();
+            BeanUtils.copyProperties(storeUser, vo);
+            vo.setPassword(null);
+            vo.setPayPassword(null);
+            vo.setSwitchStatus(storeUser.getStatus() != null && storeUser.getStatus() == 0);
+            int childCount = countSubAccountUserIdsByStoreId(storeUser.getStoreId(), storeUser.getId());
+            vo.setChildAccountCount(childCount);
+            List<Integer> childUserIds = getSubAccountUserIdsByStoreId(storeUser.getStoreId(), storeUser.getId());
+            if (!childUserIds.isEmpty()) {
+                List<StoreUser> childUsers = storeUserMapper.selectBatchIds(childUserIds);
+                vo.setChildPhoneNumbers(childUsers == null ? Collections.emptyList() : childUsers.stream().map(StoreUser::getPhone).collect(Collectors.toList()));
             }
+            resultRecords.add(vo);
         }
-
+        storeUserVoIPage.setRecords(resultRecords);
         return R.data(storeUserVoIPage);
     }
 
+    /** 按 store_platform_user_role 统计该店铺下子账号数量(排除主账号自身,按 distinct user_id 计) */
+    private int countSubAccountUserIdsByStoreId(Integer storeId, Integer excludeUserId) {
+        List<Integer> userIds = getSubAccountUserIdsByStoreId(storeId, excludeUserId);
+        return userIds == null ? 0 : userIds.size();
+    }
+
+    /** 按 store_platform_user_role 查询该店铺下子账号 userId 列表(排除主账号自身) */
+    private List<Integer> getSubAccountUserIdsByStoreId(Integer storeId, Integer excludeUserId) {
+        if (storeId == null) return Collections.emptyList();
+        LambdaQueryWrapper<StorePlatformUserRole> w = new LambdaQueryWrapper<>();
+        w.eq(StorePlatformUserRole::getStoreId, storeId).eq(StorePlatformUserRole::getDeleteFlag, 0);
+        if (excludeUserId != null) w.ne(StorePlatformUserRole::getUserId, excludeUserId);
+        List<StorePlatformUserRole> list = storePlatformUserRoleMapper.selectList(w);
+        return list == null ? Collections.emptyList() : list.stream().map(StorePlatformUserRole::getUserId).distinct().collect(Collectors.toList());
+    }
+
     @Override
     public R<StoreUserVo> editStoreUser(StoreUser storeUser) {
         storeUserMapper.updateById(storeUser);
@@ -567,123 +579,119 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
         storeUserMapper.updateById(storeUserById);
     }
 
-    @Override
-    public R<StoreUserVo> deleteStoreUser(String id) {
-        StoreUser storeUser = storeUserMapper.selectById(id);
-        String phone = storeUser.getPhone();
-        Integer storeId = storeUser.getStoreId();
 
-        // 判断该账号是否关联店铺
-        if (ObjectUtils.isNotEmpty(storeId)) {
-            List<StoreInfo> storeInfos = storeInfoMapper.selectList(new LambdaQueryWrapper<StoreInfo>().eq(StoreInfo::getId, storeId).eq(StoreInfo::getDeleteFlag, 0).eq(StoreInfo::getLogoutFlag, 0));
-            if (ObjectUtils.isNotEmpty(storeInfos)) {
-                return R.fail("请删除店铺后再删除账号");
-            }
-        }
-
-        storeUserMapper.deleteById(id);
-        //删除用户redis中的token
-        baseRedisService.delete("store_" + storeUser.getPhone());
-        //删除该账号的店铺
-        storeInfoMapper.delete(new LambdaQueryWrapper<StoreInfo>().eq(StoreInfo::getId, storeId));
-        //删除该账号的动态
-        lifeUserDynamicsMapper.delete(new LambdaQueryWrapper<LifeUserDynamics>().eq(LifeUserDynamics::getPhoneId, "store_" + phone));
-        //删除该账号关注的信息
-        lifeFansMapper.delete(new LambdaQueryWrapper<LifeFans>().eq(LifeFans::getFansId, "store_" + phone));
-        //删除关注该账号的信息
-        lifeFansMapper.delete(new LambdaQueryWrapper<LifeFans>().eq(LifeFans::getFollowedId, "store_" + phone));
-        //删除该账号的通知信息
-        lifeNoticeMapper.delete(new LambdaQueryWrapper<LifeNotice>().eq(LifeNotice::getReceiverId, "store_" + phone));
-        //删除该账号的发送消息信息
-        lifeMessageMapper.delete(new LambdaQueryWrapper<LifeMessage>().eq(LifeMessage::getSenderId, "store_" + phone));
-        //删除该账号的接受消息信息
-        lifeMessageMapper.delete(new LambdaQueryWrapper<LifeMessage>().eq(LifeMessage::getReceiverId, "store_" + phone));
-
-        return R.success("删除成功");
-    }
 
     @Override
-    public void switchingStates(StoreUser storeUserParam) {
+    public R<StoreUserVo> switchingStates(StoreUser storeUserParam) {
         StoreUser storeUser = storeUserMapper.selectById(storeUserParam.getId());
-        //如果当前为启用,则删除token
-        if (storeUser.getStatus() == 0) {
-            //删除用户redis中的token
+        if (storeUser == null) {
+            throw new RuntimeException("用户不存在");
+        }
+        // 仅当「即将禁用」时校验:主账号且该店铺下在 store_platform_user_role 中有关联子账号则禁止禁用
+        if (storeUser.getStatus() != null && storeUser.getStatus() == 0) {
+            if (storeUser.getAccountType() != null && storeUser.getAccountType() == 1) {
+                int subCount = countSubAccountUserIdsByStoreId(storeUser.getStoreId(), storeUser.getId());
+                if (subCount > 0) {
+                    throw new RuntimeException("请先删除主账号下的子账号后再禁用");
+                }
+            }
+            // 禁用时删除 token
             baseRedisService.delete("store_" + storeUser.getPhone());
         }
-        //根据当前状态切另一个状态
-        storeUser.setStatus(storeUser.getStatus() == 0 ? 1 : 0);
-        storeUserMapper.updateById(storeUser);
+        // 根据当前状态切另一个状态
+        int newStatus = storeUser.getStatus() == 0 ? 1 : 0;
+        LambdaUpdateWrapper<StoreUser> queryWrapper = new LambdaUpdateWrapper<>();
+        queryWrapper.eq(StoreUser::getId, storeUser.getId())
+                .set(StoreUser::getStatus, newStatus);
+        int updateCount = storeUserMapper.update(null, queryWrapper);
+        if (updateCount <= 0) {
+            throw new RuntimeException("更新失败");
+        }
+        // 返回更新后的状态,供前端及时展示
+        StoreUserVo vo = new StoreUserVo();
+        vo.setId(storeUser.getId());
+        vo.setStatus(newStatus);
+        vo.setSwitchStatus(newStatus == 0);
+        return R.data(vo);
     }
 
     @Override
     public String exportExcel(String id, String phone, String status, Integer accountType) throws IOException {
-        // 定义格式化模式
         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
-        if (accountType==1){
-        List<StoreUser> storeUsers = storeUserMapper.selectList(
-                new LambdaQueryWrapper<StoreUser>()
-                        .like(StringUtils.isNotEmpty(id), StoreUser::getId, id)
-                        .like(StringUtils.isNotEmpty(phone), StoreUser::getPhone, phone)
-                        .eq(StringUtils.isNotEmpty(status), StoreUser::getStatus, status)
-                        .eq(StoreUser::getAccountType, 1)
-                        .orderByDesc(StoreUser::getCreatedTime)
-        );
-
-        List<StoreUserExcelVo> storeUserExcelVoList = new ArrayList<>();
-        int serialNumber = 0;
-        for (StoreUser storeUser : storeUsers) {
-            StoreUserExcelVo storeUserExcelVo = new StoreUserExcelVo();
-            storeUserExcelVo.setSerialNumber(++serialNumber);
-
-            Integer currentUserId = storeUser.getId();
-            BeanUtils.copyProperties(storeUser, storeUserExcelVo);
-            Instant instant = storeUser.getCreatedTime().toInstant();
-            // 格式化时间
-            String formattedTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime().format(formatter);
-            storeUserExcelVo.setCreatedTime(formattedTime);
-            //格式化状态
-            storeUserExcelVo.setStatus(storeUser.getStatus() == 0 ? "启用" : "禁用");
-            List<StoreUser> childAccounts = getChildAccountsByParentId(String.valueOf(currentUserId));
-            Integer childCount = childAccounts != null ? childAccounts.size() : 0;
-            storeUserExcelVo.setChildAccountCount(childCount);
-            storeUserExcelVoList.add(storeUserExcelVo);
+        // 主账号导出:与 getStoreUserList 主账号分支一致,子账号数量按 store_platform_user_role 统计
+        if (accountType == 1) {
+            LambdaQueryWrapper<StoreUser> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(StoreUser::getDeleteFlag, 0)
+                    .like(StringUtils.isNotEmpty(id), StoreUser::getId, id)
+                    .like(StringUtils.isNotEmpty(phone), StoreUser::getPhone, phone)
+                    .eq(StringUtils.isNotEmpty(status), StoreUser::getStatus, status)
+                    .eq(StoreUser::getAccountType, 1)
+                    .orderByDesc(StoreUser::getCreatedTime);
+            List<StoreUser> storeUsers = storeUserMapper.selectList(wrapper);
+            List<StoreUserExcelVo> storeUserExcelVoList = new ArrayList<>();
+            int serialNumber = 0;
+            for (StoreUser storeUser : storeUsers) {
+                StoreUserExcelVo vo = new StoreUserExcelVo();
+                vo.setSerialNumber(++serialNumber);
+                BeanUtils.copyProperties(storeUser, vo);
+                vo.setCreatedTime(storeUser.getCreatedTime() != null ? storeUser.getCreatedTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().format(formatter) : null);
+                vo.setStatus(storeUser.getStatus() == null ? "" : (storeUser.getStatus() == 0 ? "启用" : "禁用"));
+                vo.setChildAccountCount(countSubAccountUserIdsByStoreId(storeUser.getStoreId(), storeUser.getId()));
+                storeUserExcelVoList.add(vo);
+            }
+            String fileName = UUID.randomUUID().toString().replace("-", "");
+            String filePath = ExcelGenerator.generateExcel(excelPath + excelGeneratePath + fileName + ".xlsx", storeUserExcelVoList, StoreUserExcelVo.class);
+            return aliOSSUtil.uploadFile(new File(filePath), "excel/" + fileName + ".xlsx");
         }
-        String fileName = UUID.randomUUID().toString().replace("-", "");
-        String filePath = ExcelGenerator.generateExcel(excelPath + excelGeneratePath + fileName + ".xlsx", storeUserExcelVoList, StoreUserExcelVo.class);
-        return aliOSSUtil.uploadFile(new File(filePath), "excel/" + fileName + ".xlsx");
+        // 子账号导出:与 getStoreUserList 子账号分页一致,每条 store_platform_user_role 为一行(不去重),带主账号 id
+        LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
+        roleWrapper.eq(StorePlatformUserRole::getDeleteFlag, 0);
+        List<StorePlatformUserRole> userRoles = storePlatformUserRoleMapper.selectList(roleWrapper);
+        if (CollectionUtils.isEmpty(userRoles)) {
+            String fileName = UUID.randomUUID().toString().replace("-", "");
+            String filePath = ExcelGenerator.generateExcel(excelPath + excelGeneratePath + fileName + ".xlsx", new ArrayList<StoreSubExcelVo>(), StoreSubExcelVo.class);
+            return aliOSSUtil.uploadFile(new File(filePath), "excel/" + fileName + ".xlsx");
         }
-        List<StoreUser> storeUsers = storeUserMapper.selectList(
-                new LambdaQueryWrapper<StoreUser>()
-                        .like(StringUtils.isNotEmpty(id), StoreUser::getId, id)
-                        .like(StringUtils.isNotEmpty(phone), StoreUser::getPhone, phone)
-                        .eq(StringUtils.isNotEmpty(status), StoreUser::getStatus, status)
-                        .eq(StoreUser::getAccountType, 2)
-                        .orderByDesc(StoreUser::getCreatedTime)
-        );
-        List<StoreSubExcelVo> storeUserExcelVoList = new ArrayList<>();
+        List<Integer> distinctUserIds = userRoles.stream().map(StorePlatformUserRole::getUserId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
+        List<StoreUser> subAccountList = distinctUserIds.isEmpty() ? Collections.emptyList() : storeUserMapper.selectBatchIds(distinctUserIds);
+        Map<Integer, StoreUser> subAccountMap = subAccountList == null ? new HashMap<>() : subAccountList.stream().filter(u -> u.getDeleteFlag() != null && u.getDeleteFlag() == 0).collect(Collectors.toMap(StoreUser::getId, u -> u, (a, b) -> a));
+        List<StorePlatformUserRole> filteredRoles = new ArrayList<>();
+        for (StorePlatformUserRole role : userRoles) {
+            StoreUser subAccount = subAccountMap.get(role.getUserId());
+            if (subAccount == null) continue;
+            if (StringUtils.isNotEmpty(id) && (subAccount.getId() == null || !String.valueOf(subAccount.getId()).contains(id))) continue;
+            if (StringUtils.isNotEmpty(phone) && (subAccount.getPhone() == null || !subAccount.getPhone().contains(phone))) continue;
+            if (status != null && !status.equals(subAccount.getStatus())) continue;
+            filteredRoles.add(role);
+        }
+        filteredRoles.sort(Comparator.comparing(StorePlatformUserRole::getId, Comparator.nullsLast(Comparator.naturalOrder())));
+        List<Integer> storeIds = filteredRoles.stream().map(StorePlatformUserRole::getStoreId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
+        Map<Integer, StoreUser> mainAccountMap = new HashMap<>();
+        if (!storeIds.isEmpty()) {
+            LambdaQueryWrapper<StoreUser> mainW = new LambdaQueryWrapper<>();
+            mainW.eq(StoreUser::getAccountType, 1).eq(StoreUser::getDeleteFlag, 0).in(StoreUser::getStoreId, storeIds);
+            List<StoreUser> mains = storeUserMapper.selectList(mainW);
+            if (mains != null) mains.forEach(m -> mainAccountMap.put(m.getStoreId(), m));
+        }
+        List<StoreSubExcelVo> storeSubExcelVoList = new ArrayList<>();
         int serialNumber = 0;
-        for (StoreUser subAccount : storeUsers) {
-            StoreSubExcelVo storeUserExcelVo = new StoreSubExcelVo();
-            storeUserExcelVo.setSerialNumber(++serialNumber);
-            BeanUtils.copyProperties(subAccount, storeUserExcelVo);
-            if (subAccount.getSubAccountId() != null){
-                StoreUser mainAccount = storeUserMapper.selectById(subAccount.getSubAccountId());
-                if (mainAccount != null){
-                    storeUserExcelVo.setId(mainAccount.getId());
-                    storeUserExcelVo.setParentAccountPhone(mainAccount.getPhone());
-                }
+        for (StorePlatformUserRole role : filteredRoles) {
+            StoreUser subAccount = subAccountMap.get(role.getUserId());
+            if (subAccount == null) continue;
+            StoreSubExcelVo vo = new StoreSubExcelVo();
+            vo.setSerialNumber(++serialNumber);
+            vo.setId(subAccount.getId());
+            vo.setPhone(subAccount.getPhone());
+            vo.setCreatedTime(subAccount.getCreatedTime() != null ? subAccount.getCreatedTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().format(formatter) : null);
+            vo.setStatus(subAccount.getStatus() == null ? "" : (subAccount.getStatus() == 0 ? "启用" : "禁用"));
+            StoreUser mainAccount = role.getStoreId() == null ? null : mainAccountMap.get(role.getStoreId());
+            if (mainAccount != null) {
+                vo.setParentAccountPhone(mainAccount.getPhone());
             }
-
-            Instant instant = subAccount.getCreatedTime().toInstant();
-            // 格式化时间
-            String formattedTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime().format(formatter);
-            storeUserExcelVo.setCreatedTime(formattedTime);
-            //格式化状态
-            storeUserExcelVo.setStatus(subAccount.getStatus() == 0 ? "启用" : "禁用");
-            storeUserExcelVoList.add(storeUserExcelVo);
+            storeSubExcelVoList.add(vo);
         }
         String fileName = UUID.randomUUID().toString().replace("-", "");
-        String filePath = ExcelGenerator.generateExcel(excelPath + excelGeneratePath + fileName + ".xlsx", storeUserExcelVoList, StoreSubExcelVo.class);
+        String filePath = ExcelGenerator.generateExcel(excelPath + excelGeneratePath + fileName + ".xlsx", storeSubExcelVoList, StoreSubExcelVo.class);
         return aliOSSUtil.uploadFile(new File(filePath), "excel/" + fileName + ".xlsx");
     }
 
@@ -920,22 +928,91 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
         queryWrapper.eq(StoreUser::getId, storeUserVo.getId());
         queryWrapper.eq(StoreUser::getDeleteFlag, 0);
         StoreUser storeUser = storeUserMapper.selectOne(queryWrapper);
-        // 删除已过注销时间的商家
-        if (storeUser != null) {
-            storeUserMapper.deleteById(storeUserVo.getId());
-            nearMeService.removeGeolocation(Boolean.TRUE, storeUser.getId().toString());
-            String storePhone = "store_" + storeUserVo.getPhone();
-            String key = baseRedisService.getString(storePhone);
-            if (key != null) {
-                //删除用户redis中的token
-                baseRedisService.delete(key);
+        // 主账号删除前校验:底下有店铺或有关联子账号则禁止删除
+        if (storeUser != null && storeUser.getAccountType() != null && storeUser.getAccountType() == 1) {
+            if (storeUser.getStoreId() != null) {
+                throw new RuntimeException("该主账号下存在店铺,禁止删除");
+            }
+            List<StoreUser> subAccounts = getSubAccountsByMainAccountId(storeUser.getId());
+            if (subAccounts != null && !subAccounts.isEmpty()) {
+                throw new RuntimeException("该主账号下存在关联子账号,禁止删除");
+            }
+        }
+        if (storeUser == null) {
+            return;
+        }
+        Integer userId = storeUser.getId();
+        String phone = storeUser.getPhone();
+
+        // 1. 先查询该用户在所有店铺下的子账号数量(未删除的)
+        LambdaQueryWrapper<StorePlatformUserRole> countWrapper = new LambdaQueryWrapper<>();
+        countWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                .eq(StorePlatformUserRole::getDeleteFlag, 0);
+        long subAccountCount = storePlatformUserRoleMapper.selectCount(countWrapper);
+        log.info("用户在所有店铺下的子账号数量: userId={}, count={}", userId, subAccountCount);
+
+        // 2. 逻辑删除 store_platform_user_role 表的记录
+        LambdaUpdateWrapper<StorePlatformUserRole> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                .eq(StorePlatformUserRole::getDeleteFlag, 0)
+                .set(StorePlatformUserRole::getDeleteFlag, 1);
+        storePlatformUserRoleMapper.update(null, updateWrapper);
+
+        // 删除 token
+        String tokenKey = "store_" + phone;
+        baseRedisService.delete(tokenKey);
+
+        // 3. 如果只是一个账号的子账号(删除前只有1个),则需要进一步判断是否为主账号,再决定是否同时逻辑删除 store_user 表
+        if (subAccountCount == 1) {
+            boolean isMainAccount = storeUser.getStoreId() != null && storeUser.getStoreId() > 0
+                    || (storeUser.getAccountType() != null && storeUser.getAccountType() == 1);
+            if (isMainAccount) {
+                // 主账号:物理删除 store_user 及关联数据(主账号校验已通过,无店铺无子账号)
+                nearMeService.removeGeolocation(Boolean.TRUE, userId.toString());
+                storeUserMapper.deleteById(userId);
+                LambdaQueryWrapper<LifeFans> lifeFansWrapper = new LambdaQueryWrapper<LifeFans>()
+                        .eq(LifeFans::getFollowedId, "store_" + phone)
+                        .or().eq(LifeFans::getFansId, "store_" + phone);
+                lifeFansMapper.delete(lifeFansWrapper);
+                if (storeUser.getStoreId() != null) {
+                    storeInfoMapper.deleteById(storeUser.getStoreId());
+                }
+                lifeUserDynamicsMapper.delete(new LambdaQueryWrapper<LifeUserDynamics>().eq(LifeUserDynamics::getPhoneId, "store_" + phone));
+                lifeNoticeMapper.delete(new LambdaQueryWrapper<LifeNotice>().eq(LifeNotice::getReceiverId, "store_" + phone));
+                lifeMessageMapper.delete(new LambdaQueryWrapper<LifeMessage>().eq(LifeMessage::getSenderId, "store_" + phone));
+                lifeMessageMapper.delete(new LambdaQueryWrapper<LifeMessage>().eq(LifeMessage::getReceiverId, "store_" + phone));
+                log.info("主账号仅1条 platform 记录,已物理删除 store_user 及关联数据: userId={}", userId);
+            } else {
+                // 不是主账号,逻辑删除 store_user
+                LambdaUpdateWrapper<StoreUser> userUpdateWrapper = new LambdaUpdateWrapper<>();
+                userUpdateWrapper.eq(StoreUser::getId, userId)
+                        .eq(StoreUser::getDeleteFlag, 0)
+                        .set(StoreUser::getDeleteFlag, 1);
+                int userUpdateResult = storeUserMapper.update(null, userUpdateWrapper);
+                if (userUpdateResult > 0) {
+                    log.info("用户只有一个子账号且不是主账号,已同时逻辑删除 store_user 记录: userId={}", userId);
+                } else {
+                    log.warn("逻辑删除 store_user 记录失败或记录不存在: userId={}", userId);
+                }
             }
-            LambdaQueryWrapper<LifeFans> lifeFansLambdaQueryWrapper = new LambdaQueryWrapper<LifeFans>().eq(LifeFans::getFollowedId, "store_" + storeUser.getPhone())
-                    .or().eq(LifeFans::getFansId, "store_" + storeUser.getPhone());
-            lifeFansMapper.delete(lifeFansLambdaQueryWrapper);
+        } else if (subAccountCount == 0) {
+            // 无 platform 记录,全量物理删除
+            nearMeService.removeGeolocation(Boolean.TRUE, userId.toString());
+            storeUserMapper.deleteById(userId);
+            LambdaQueryWrapper<LifeFans> lifeFansWrapper = new LambdaQueryWrapper<LifeFans>()
+                    .eq(LifeFans::getFollowedId, "store_" + phone)
+                    .or().eq(LifeFans::getFansId, "store_" + phone);
+            lifeFansMapper.delete(lifeFansWrapper);
             if (storeUser.getStoreId() != null) {
                 storeInfoMapper.deleteById(storeUser.getStoreId());
             }
+            lifeUserDynamicsMapper.delete(new LambdaQueryWrapper<LifeUserDynamics>().eq(LifeUserDynamics::getPhoneId, "store_" + phone));
+            lifeNoticeMapper.delete(new LambdaQueryWrapper<LifeNotice>().eq(LifeNotice::getReceiverId, "store_" + phone));
+            lifeMessageMapper.delete(new LambdaQueryWrapper<LifeMessage>().eq(LifeMessage::getSenderId, "store_" + phone));
+            lifeMessageMapper.delete(new LambdaQueryWrapper<LifeMessage>().eq(LifeMessage::getReceiverId, "store_" + phone));
+            log.info("用户无 platform 记录,已物理删除 store_user 及关联数据: userId={}", userId);
+        } else {
+            log.info("用户有多个子账号(count={}),仅删除 store_platform_user_role 记录,不删除 store_user: userId={}", subAccountCount, userId);
         }
     }