Эх сурвалжийг харах

门店管理,设备门店关联

skyline 3 сар өмнө
parent
commit
b3d1a980ad

+ 22 - 0
haha-admin/src/main/java/com/haha/admin/controller/DeviceController.java

@@ -5,7 +5,9 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.haha.common.vo.Result;
 import com.haha.entity.Device;
+import com.haha.entity.Shop;
 import com.haha.service.DeviceService;
+import com.haha.service.ShopService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -24,6 +26,9 @@ public class DeviceController {
     @Autowired
     private DeviceService deviceService;
 
+    @Autowired
+    private ShopService shopService;
+
     /**
      * 分页查询设备列表
      * @param params 查询参数
@@ -49,6 +54,11 @@ public class DeviceController {
             // 查询条件
             String deviceId = (String) params.get("deviceId");
             String storeName = (String) params.get("storeName");
+            Long shopId = null;
+            Object shopIdObj = params.get("shopId");
+            if (shopIdObj != null && !shopIdObj.toString().isEmpty()) {
+                shopId = Long.valueOf(shopIdObj.toString());
+            }
             Integer status = null;
             Object statusObj = params.get("status");
             if (statusObj != null && !statusObj.toString().isEmpty()) {
@@ -58,6 +68,7 @@ public class DeviceController {
             // 构建查询条件
             LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
             wrapper.like(deviceId != null && !deviceId.isEmpty(), Device::getDeviceId, deviceId)
+                   .eq(shopId != null, Device::getShopId, shopId)
                    .eq(status != null, Device::getStatus, status);
             
             // 按创建时间倒序
@@ -261,6 +272,17 @@ public class DeviceController {
             }
         }
         
+        // 填充门店信息
+        if (device.getShopId() != null && device.getShopName() == null) {
+            Shop shop = shopService.getById(device.getShopId());
+            if (shop != null) {
+                device.setShopName(shop.getName());
+                if (device.getAddress() == null) {
+                    device.setAddress(shop.getAddress());
+                }
+            }
+        }
+        
         // 默认值
         if (device.getShopName() == null) {
             device.setShopName("默认门店");

+ 329 - 0
haha-admin/src/main/java/com/haha/admin/controller/ShopController.java

@@ -0,0 +1,329 @@
+package com.haha.admin.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.haha.common.vo.Result;
+import com.haha.entity.Shop;
+import com.haha.entity.Device;
+import com.haha.service.ShopService;
+import com.haha.service.DeviceService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 门店管理控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/shops")
+public class ShopController {
+
+    @Autowired
+    private ShopService shopService;
+
+    @Autowired
+    private DeviceService deviceService;
+
+    /**
+     * 分页查询门店列表
+     * @param params 查询参数
+     * @return 门店列表
+     */
+    @GetMapping("/list")
+    public Result<Map<String, Object>> list(@RequestParam Map<String, Object> params) {
+        try {
+            int page = 1;
+            int pageSize = 10;
+
+            Object pageObj = params.get("page");
+            if (pageObj != null && !pageObj.toString().isEmpty()) {
+                page = Integer.parseInt(pageObj.toString());
+            }
+
+            Object pageSizeObj = params.get("pageSize");
+            if (pageSizeObj != null && !pageSizeObj.toString().isEmpty()) {
+                pageSize = Integer.parseInt(pageSizeObj.toString());
+            }
+
+            IPage<Shop> shopPage = shopService.getPage(page, pageSize, params);
+
+            Map<String, Object> data = new HashMap<>();
+            data.put("list", shopPage.getRecords());
+            data.put("total", shopPage.getTotal());
+            data.put("pageSize", shopPage.getSize());
+            data.put("currentPage", shopPage.getCurrent());
+
+            return Result.success("查询成功", data);
+
+        } catch (Exception e) {
+            log.error("查询门店列表失败: {}", e.getMessage(), e);
+            return Result.error(500, "查询门店列表失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取门店详情
+     * @param id 门店ID
+     * @return 门店详情
+     */
+    @GetMapping("/{id}")
+    public Result<Shop> getById(@PathVariable Long id) {
+        try {
+            Shop shop = shopService.getShopWithStats(id);
+            if (shop == null) {
+                return Result.error(404, "门店不存在");
+            }
+            return Result.success("查询成功", shop);
+        } catch (Exception e) {
+            log.error("查询门店详情失败: id={}, error={}", id, e.getMessage(), e);
+            return Result.error(500, "查询门店详情失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取门店统计数据
+     * @return 统计数据
+     */
+    @GetMapping("/statistics")
+    public Result<Map<String, Object>> getStatistics() {
+        try {
+            Map<String, Object> statistics = shopService.getStatistics();
+            return Result.success("查询成功", statistics);
+        } catch (Exception e) {
+            log.error("查询门店统计失败: {}", e.getMessage(), e);
+            return Result.error(500, "查询门店统计失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取所有启用的门店(下拉选择用)
+     * @return 门店列表
+     */
+    @GetMapping("/enabled")
+    public Result<List<Map<String, Object>>> getEnabledShops() {
+        try {
+            List<Map<String, Object>> shops = shopService.getAllEnabledShops();
+            return Result.success("查询成功", shops);
+        } catch (Exception e) {
+            log.error("查询启用门店失败: {}", e.getMessage(), e);
+            return Result.error(500, "查询启用门店失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 新增门店
+     * @param shop 门店信息
+     * @return 新增后的门店
+     */
+    @PostMapping
+    public Result<Shop> create(@RequestBody Shop shop) {
+        try {
+            Shop created = shopService.createShop(shop);
+            return Result.success("创建成功", created);
+        } catch (Exception e) {
+            log.error("创建门店失败: {}", e.getMessage(), e);
+            return Result.error(500, "创建门店失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 编辑门店
+     * @param id 门店ID
+     * @param shop 门店信息
+     * @return 编辑后的门店
+     */
+    @PutMapping("/{id}")
+    public Result<Shop> update(@PathVariable Long id, @RequestBody Shop shop) {
+        try {
+            shop.setId(id);
+            Shop updated = shopService.updateShop(shop);
+            return Result.success("更新成功", updated);
+        } catch (Exception e) {
+            log.error("更新门店失败: id={}, error={}", id, e.getMessage(), e);
+            return Result.error(500, "更新门店失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 删除门店
+     * @param id 门店ID
+     * @return 操作结果
+     */
+    @DeleteMapping("/{id}")
+    public Result<String> delete(@PathVariable Long id) {
+        try {
+            // 检查门店是否有设备
+            Shop shop = shopService.getShopWithStats(id);
+            if (shop == null) {
+                return Result.error(404, "门店不存在");
+            }
+            
+            if (shop.getDeviceCount() != null && shop.getDeviceCount() > 0) {
+                return Result.error(400, "该门店下存在设备,无法删除");
+            }
+            
+            shopService.removeById(id);
+            return Result.success("删除成功");
+        } catch (Exception e) {
+            log.error("删除门店失败: id={}, error={}", id, e.getMessage(), e);
+            return Result.error(500, "删除门店失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 切换门店状态
+     * @param id 门店ID
+     * @param params 状态参数
+     * @return 操作结果
+     */
+    @PutMapping("/{id}/status")
+    public Result<String> toggleStatus(@PathVariable Long id, @RequestBody Map<String, Object> params) {
+        try {
+            Integer status = Integer.valueOf(params.get("status").toString());
+            boolean result = shopService.toggleStatus(id, status);
+            
+            if (result) {
+                return Result.success("状态更新成功");
+            } else {
+                return Result.error(500, "状态更新失败");
+            }
+        } catch (Exception e) {
+            log.error("切换门店状态失败: id={}, error={}", id, e.getMessage(), e);
+            return Result.error(500, "切换门店状态失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取门店下的设备列表
+     * @param id 门店ID
+     * @return 设备列表
+     */
+    @GetMapping("/{id}/devices")
+    public Result<List<Map<String, Object>>> getDevices(@PathVariable Long id) {
+        try {
+            List<Map<String, Object>> devices = shopService.getDevicesByShopId(id);
+            return Result.success("查询成功", devices);
+        } catch (Exception e) {
+            log.error("查询门店设备失败: id={}, error={}", id, e.getMessage(), e);
+            return Result.error(500, "查询门店设备失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 将设备关联到门店
+     * @param id 门店ID
+     * @param params 设备ID参数
+     * @return 操作结果
+     */
+    @PostMapping("/{id}/devices")
+    public Result<String> linkDevice(@PathVariable Long id, @RequestBody Map<String, Object> params) {
+        try {
+            Long deviceId = Long.valueOf(params.get("deviceId").toString());
+            boolean result = shopService.linkDevice(id, deviceId);
+            if (result) {
+                return Result.success("关联成功");
+            } else {
+                return Result.error(500, "关联失败");
+            }
+        } catch (Exception e) {
+            log.error("关联设备失败: shopId={}, error={}", id, e.getMessage(), e);
+            return Result.error(500, "关联失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 批量将设备关联到门店
+     * @param id 门店ID
+     * @param params 设备ID列表参数
+     * @return 操作结果
+     */
+    @PostMapping("/{id}/devices/batch")
+    public Result<Map<String, Object>> batchLinkDevices(@PathVariable Long id, @RequestBody Map<String, Object> params) {
+        try {
+            @SuppressWarnings("unchecked")
+            List<Integer> deviceIdInts = (List<Integer>) params.get("deviceIds");
+            List<Long> deviceIds = deviceIdInts.stream()
+                    .map(Integer::longValue)
+                    .toList();
+            
+            int count = shopService.batchLinkDevices(id, deviceIds);
+            
+            Map<String, Object> result = new HashMap<>();
+            result.put("success", count);
+            result.put("total", deviceIds.size());
+            
+            return Result.success("批量关联完成", result);
+        } catch (Exception e) {
+            log.error("批量关联设备失败: shopId={}, error={}", id, e.getMessage(), e);
+            return Result.error(500, "批量关联失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 从门店移除设备关联
+     * @param id 门店ID
+     * @param deviceId 设备ID
+     * @return 操作结果
+     */
+    @DeleteMapping("/{id}/devices/{deviceId}")
+    public Result<String> unlinkDevice(@PathVariable Long id, @PathVariable Long deviceId) {
+        try {
+            boolean result = shopService.unlinkDevice(id, deviceId);
+            if (result) {
+                return Result.success("移除成功");
+            } else {
+                return Result.error(500, "移除失败");
+            }
+        } catch (Exception e) {
+            log.error("移除设备失败: shopId={}, deviceId={}, error={}", id, deviceId, e.getMessage(), e);
+            return Result.error(500, "移除失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 批量移除设备关联
+     * @param id 门店ID
+     * @param params 设备ID列表参数
+     * @return 操作结果
+     */
+    @DeleteMapping("/{id}/devices/batch")
+    public Result<Map<String, Object>> batchUnlinkDevices(@PathVariable Long id, @RequestBody Map<String, Object> params) {
+        try {
+            @SuppressWarnings("unchecked")
+            List<Integer> deviceIdInts = (List<Integer>) params.get("deviceIds");
+            List<Long> deviceIds = deviceIdInts.stream()
+                    .map(Integer::longValue)
+                    .toList();
+            
+            int count = shopService.batchUnlinkDevices(id, deviceIds);
+            
+            Map<String, Object> result = new HashMap<>();
+            result.put("success", count);
+            result.put("total", deviceIds.size());
+            
+            return Result.success("批量移除完成", result);
+        } catch (Exception e) {
+            log.error("批量移除设备失败: shopId={}, error={}", id, e.getMessage(), e);
+            return Result.error(500, "批量移除失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取未关联门店的设备列表
+     * @return 设备列表
+     */
+    @GetMapping("/unlinked-devices")
+    public Result<List<Device>> getUnlinkedDevices() {
+        try {
+            List<Device> devices = deviceService.getUnlinkedDevices();
+            return Result.success("查询成功", devices);
+        } catch (Exception e) {
+            log.error("查询未关联设备失败: error={}", e.getMessage(), e);
+            return Result.error(500, "查询失败: " + e.getMessage());
+        }
+    }
+}

+ 125 - 0
haha-entity/src/main/java/com/haha/entity/Shop.java

@@ -0,0 +1,125 @@
+package com.haha.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 门店实体类
+ */
+@Data
+@TableName("t_shop")
+public class Shop implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 门店编码(唯一标识)
+     */
+    private String shopCode;
+
+    /**
+     * 门店名称
+     */
+    private String name;
+
+    /**
+     * 门店地址
+     */
+    private String address;
+
+    /**
+     * 省份
+     */
+    private String province;
+
+    /**
+     * 城市
+     */
+    private String city;
+
+    /**
+     * 区域
+     */
+    private String district;
+
+    /**
+     * 经度
+     */
+    private Double longitude;
+
+    /**
+     * 纬度
+     */
+    private Double latitude;
+
+    /**
+     * 联系人
+     */
+    private String contactName;
+
+    /**
+     * 联系电话
+     */
+    private String contactPhone;
+
+    /**
+     * 营业时间
+     */
+    private String businessHours;
+
+    /**
+     * 门店状态:0-禁用,1-启用
+     */
+    private Integer status;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    // ========== 以下为非数据库字段,用于前端展示 ==========
+
+    /**
+     * 设备总数
+     */
+    @TableField(exist = false)
+    private Integer deviceCount;
+
+    /**
+     * 在线设备数
+     */
+    @TableField(exist = false)
+    private Integer onlineCount;
+
+    /**
+     * 状态标签
+     */
+    @TableField(exist = false)
+    private String statusLabel;
+
+    /**
+     * 状态颜色
+     */
+    @TableField(exist = false)
+    private String statusColor;
+}

+ 49 - 0
haha-mapper/src/main/java/com/haha/mapper/ShopMapper.java

@@ -0,0 +1,49 @@
+package com.haha.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.haha.entity.Shop;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 门店Mapper
+ */
+@Mapper
+public interface ShopMapper extends BaseMapper<Shop> {
+
+    /**
+     * 查询门店列表(带设备统计)
+     */
+    @Select("SELECT s.*, " +
+            "COUNT(d.id) as device_count, " +
+            "SUM(CASE WHEN d.status = 1 THEN 1 ELSE 0 END) as online_count " +
+            "FROM t_shop s " +
+            "LEFT JOIN t_device d ON s.id = d.shop_id " +
+            "WHERE s.status = 1 " +
+            "GROUP BY s.id " +
+            "ORDER BY s.create_time DESC")
+    List<Map<String, Object>> selectShopsWithDeviceStats();
+
+    /**
+     * 根据ID查询门店(带设备统计)
+     */
+    @Select("SELECT s.*, " +
+            "COUNT(d.id) as device_count, " +
+            "SUM(CASE WHEN d.status = 1 THEN 1 ELSE 0 END) as online_count " +
+            "FROM t_shop s " +
+            "LEFT JOIN t_device d ON s.id = d.shop_id " +
+            "WHERE s.id = #{id} " +
+            "GROUP BY s.id")
+    Map<String, Object> selectShopWithStats(@Param("id") Long id);
+
+    /**
+     * 查询所有启用的门店(用于下拉选择)
+     */
+    @Select("SELECT id, name, address, contact_name, contact_phone " +
+            "FROM t_shop WHERE status = 1 ORDER BY name")
+    List<Map<String, Object>> selectAllEnabledShops();
+}

+ 25 - 0
haha-service/src/main/java/com/haha/service/DeviceService.java

@@ -23,4 +23,29 @@ public interface DeviceService extends IService<Device> {
      * @throws HahaException 开门失败时抛出
      */
     OpenDoorVO scanOpenDoor(String deviceId, Long userId) throws HahaException;
+
+    /**
+     * 将设备关联到门店
+     *
+     * @param deviceId 设备ID
+     * @param shopId    门店ID(null表示取消关联)
+     * @return 是否成功
+     */
+    boolean linkToShop(Long deviceId, Long shopId);
+
+    /**
+     * 批量将设备关联到门店
+     *
+     * @param deviceIds 设备ID列表
+     * @param shopId    门店ID(null表示批量取消关联)
+     * @return 成功关联的数量
+     */
+    int batchLinkToShop(List<Long> deviceIds, Long shopId);
+
+    /**
+     * 获取未关联门店的设备列表
+     *
+     * @return 设备列表
+     */
+    List<Device> getUnlinkedDevices();
 }

+ 115 - 0
haha-service/src/main/java/com/haha/service/ShopService.java

@@ -0,0 +1,115 @@
+package com.haha.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.haha.entity.Shop;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 门店服务接口
+ */
+public interface ShopService extends IService<Shop> {
+
+    /**
+     * 分页查询门店列表
+     *
+     * @param page     页码
+     * @param pageSize 每页条数
+     * @param params   查询参数(name, city, status等)
+     * @return 分页结果
+     */
+    IPage<Shop> getPage(int page, int pageSize, Map<String, Object> params);
+
+    /**
+     * 查询门店详情(带设备统计)
+     *
+     * @param id 门店ID
+     * @return 门店详情
+     */
+    Shop getShopWithStats(Long id);
+
+    /**
+     * 查询所有启用的门店(用于下拉选择)
+     *
+     * @return 门店列表
+     */
+    List<Map<String, Object>> getAllEnabledShops();
+
+    /**
+     * 创建门店
+     *
+     * @param shop 门店信息
+     * @return 创建后的门店
+     */
+    Shop createShop(Shop shop);
+
+    /**
+     * 更新门店
+     *
+     * @param shop 门店信息
+     * @return 更新后的门店
+     */
+    Shop updateShop(Shop shop);
+
+    /**
+     * 切换门店状态
+     *
+     * @param id     门店ID
+     * @param status 状态
+     * @return 是否成功
+     */
+    boolean toggleStatus(Long id, Integer status);
+
+    /**
+     * 获取门店统计信息
+     *
+     * @return 统计信息
+     */
+    Map<String, Object> getStatistics();
+
+    /**
+     * 根据门店ID获取设备列表
+     *
+     * @param shopId 门店ID
+     * @return 设备列表
+     */
+    List<Map<String, Object>> getDevicesByShopId(Long shopId);
+
+    /**
+     * 将设备关联到当前门店
+     *
+     * @param shopId   门店ID
+     * @param deviceId 设备ID
+     * @return 是否成功
+     */
+    boolean linkDevice(Long shopId, Long deviceId);
+
+    /**
+     * 批量将设备关联到当前门店
+     *
+     * @param shopId    门店ID
+     * @param deviceIds 设备ID列表
+     * @return 成功关联的数量
+     */
+    int batchLinkDevices(Long shopId, List<Long> deviceIds);
+
+    /**
+     * 从当前门店移除设备关联
+     *
+     * @param shopId   门店ID
+     * @param deviceId 设备ID
+     * @return 是否成功
+     */
+    boolean unlinkDevice(Long shopId, Long deviceId);
+
+    /**
+     * 批量移除设备关联
+     *
+     * @param shopId    门店ID
+     * @param deviceIds 设备ID列表
+     * @return 成功移除的数量
+     */
+    int batchUnlinkDevices(Long shopId, List<Long> deviceIds);
+}

+ 44 - 0
haha-service/src/main/java/com/haha/service/impl/DeviceServiceImpl.java

@@ -1,5 +1,6 @@
 package com.haha.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.haha.common.exception.BusinessException;
 import com.haha.entity.Device;
@@ -111,4 +112,47 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
             .doorIndex(doorIndex != null ? doorIndex : "A")
             .build();
     }
+
+    @Override
+    public boolean linkToShop(Long deviceId, Long shopId) {
+        Device device = this.getById(deviceId);
+        if (device == null) {
+            throw new BusinessException("设备不存在");
+        }
+        
+        device.setShopId(shopId);
+        device.setUpdateTime(LocalDateTime.now());
+        
+        boolean result = this.updateById(device);
+        if (result) {
+            log.info("设备关联门店成功: deviceId={}, shopId={}", deviceId, shopId);
+        }
+        return result;
+    }
+
+    @Override
+    public int batchLinkToShop(List<Long> deviceIds, Long shopId) {
+        int count = 0;
+        for (Long deviceId : deviceIds) {
+            try {
+                if (linkToShop(deviceId, shopId)) {
+                    count++;
+                }
+            } catch (Exception e) {
+                log.error("设备关联门店失败: deviceId={}, error={}", deviceId, e.getMessage());
+            }
+        }
+        log.info("批量关联门店完成: 总数={}, 成功={}", deviceIds.size(), count);
+        return count;
+    }
+
+    @Override
+    public List<Device> getUnlinkedDevices() {
+        LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
+        wrapper.isNull(Device::getShopId)
+               .or()
+               .eq(Device::getShopId, 0);
+        wrapper.orderByDesc(Device::getCreateTime);
+        return this.list(wrapper);
+    }
 }

+ 354 - 0
haha-service/src/main/java/com/haha/service/impl/ShopServiceImpl.java

@@ -0,0 +1,354 @@
+package com.haha.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.haha.entity.Device;
+import com.haha.entity.Shop;
+import com.haha.mapper.ShopMapper;
+import com.haha.service.DeviceService;
+import com.haha.service.ShopService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 门店服务实现类
+ */
+@Slf4j
+@Service
+public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements ShopService {
+
+    @Autowired
+    private ShopMapper shopMapper;
+
+    @Autowired
+    private DeviceService deviceService;
+
+    @Override
+    public IPage<Shop> getPage(int page, int pageSize, Map<String, Object> params) {
+        Page<Shop> pageParam = new Page<>(page, pageSize);
+        
+        LambdaQueryWrapper<Shop> wrapper = new LambdaQueryWrapper<>();
+        
+        // 门店名称模糊查询
+        String name = (String) params.get("name");
+        wrapper.like(StringUtils.hasText(name), Shop::getName, name);
+        
+        // 城市查询
+        String city = (String) params.get("city");
+        wrapper.eq(StringUtils.hasText(city), Shop::getCity, city);
+        
+        // 状态查询
+        Object statusObj = params.get("status");
+        if (statusObj != null && !statusObj.toString().isEmpty()) {
+            Integer status = Integer.valueOf(statusObj.toString());
+            wrapper.eq(Shop::getStatus, status);
+        }
+        
+        // 按创建时间倒序
+        wrapper.orderByDesc(Shop::getCreateTime);
+        
+        IPage<Shop> shopPage = this.page(pageParam, wrapper);
+        
+        // 填充设备统计信息
+        for (Shop shop : shopPage.getRecords()) {
+            fillShopStats(shop);
+            fillShopLabels(shop);
+        }
+        
+        return shopPage;
+    }
+
+    @Override
+    public Shop getShopWithStats(Long id) {
+        Map<String, Object> map = shopMapper.selectShopWithStats(id);
+        if (map == null || map.isEmpty()) {
+            return null;
+        }
+        
+        Shop shop = new Shop();
+        shop.setId(((Number) map.get("id")).longValue());
+        shop.setShopCode((String) map.get("shop_code"));
+        shop.setName((String) map.get("name"));
+        shop.setAddress((String) map.get("address"));
+        shop.setProvince((String) map.get("province"));
+        shop.setCity((String) map.get("city"));
+        shop.setDistrict((String) map.get("district"));
+        
+        Object longitude = map.get("longitude");
+        if (longitude != null) {
+            shop.setLongitude(((Number) longitude).doubleValue());
+        }
+        Object latitude = map.get("latitude");
+        if (latitude != null) {
+            shop.setLatitude(((Number) latitude).doubleValue());
+        }
+        
+        shop.setContactName((String) map.get("contact_name"));
+        shop.setContactPhone((String) map.get("contact_phone"));
+        shop.setBusinessHours((String) map.get("business_hours"));
+        shop.setStatus(((Number) map.get("status")).intValue());
+        shop.setRemark((String) map.get("remark"));
+        
+        // 设备统计
+        Object deviceCount = map.get("device_count");
+        shop.setDeviceCount(deviceCount != null ? ((Number) deviceCount).intValue() : 0);
+        
+        Object onlineCount = map.get("online_count");
+        shop.setOnlineCount(onlineCount != null ? ((Number) onlineCount).intValue() : 0);
+        
+        fillShopLabels(shop);
+        
+        return shop;
+    }
+
+    @Override
+    public List<Map<String, Object>> getAllEnabledShops() {
+        return shopMapper.selectAllEnabledShops();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Shop createShop(Shop shop) {
+        // 生成门店编码
+        if (!StringUtils.hasText(shop.getShopCode())) {
+            shop.setShopCode("SH" + System.currentTimeMillis());
+        }
+        
+        // 默认状态
+        if (shop.getStatus() == null) {
+            shop.setStatus(1);
+        }
+        
+        shop.setCreateTime(LocalDateTime.now());
+        shop.setUpdateTime(LocalDateTime.now());
+        
+        this.save(shop);
+        
+        fillShopLabels(shop);
+        
+        log.info("创建门店成功: id={}, name={}", shop.getId(), shop.getName());
+        
+        return shop;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Shop updateShop(Shop shop) {
+        Shop existShop = this.getById(shop.getId());
+        if (existShop == null) {
+            throw new RuntimeException("门店不存在");
+        }
+        
+        shop.setUpdateTime(LocalDateTime.now());
+        
+        this.updateById(shop);
+        
+        Shop updated = this.getById(shop.getId());
+        fillShopStats(updated);
+        fillShopLabels(updated);
+        
+        log.info("更新门店成功: id={}, name={}", shop.getId(), shop.getName());
+        
+        return updated;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean toggleStatus(Long id, Integer status) {
+        Shop shop = this.getById(id);
+        if (shop == null) {
+            throw new RuntimeException("门店不存在");
+        }
+        
+        LambdaUpdateWrapper<Shop> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(Shop::getId, id)
+               .set(Shop::getStatus, status)
+               .set(Shop::getUpdateTime, LocalDateTime.now());
+        
+        boolean result = this.update(wrapper);
+        
+        if (result) {
+            log.info("切换门店状态成功: id={}, status={}", id, status);
+        }
+        
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> getStatistics() {
+        Map<String, Object> statistics = new HashMap<>();
+        
+        // 总门店数
+        long total = this.count();
+        statistics.put("total", total);
+        
+        // 启用门店数
+        long enabled = this.lambdaQuery()
+                .eq(Shop::getStatus, 1)
+                .count();
+        statistics.put("enabled", enabled);
+        
+        // 禁用门店数
+        statistics.put("disabled", total - enabled);
+        
+        // 总设备数
+        long totalDevices = deviceService.count();
+        statistics.put("totalDevices", totalDevices);
+        
+        // 在线设备数
+        long onlineDevices = deviceService.lambdaQuery()
+                .eq(Device::getStatus, 1)
+                .count();
+        statistics.put("onlineDevices", onlineDevices);
+        
+        return statistics;
+    }
+
+    @Override
+    public List<Map<String, Object>> getDevicesByShopId(Long shopId) {
+        LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(Device::getShopId, shopId)
+               .orderByDesc(Device::getCreateTime);
+        
+        List<Device> devices = deviceService.list(wrapper);
+        
+        return devices.stream().map(device -> {
+            Map<String, Object> map = new HashMap<>();
+            map.put("id", device.getId());
+            map.put("deviceId", device.getDeviceId());
+            map.put("name", device.getName());
+            map.put("status", device.getStatus());
+            map.put("createTime", device.getCreateTime());
+            return map;
+        }).toList();
+    }
+
+    @Override
+    public boolean linkDevice(Long shopId, Long deviceId) {
+        // 检查门店是否存在
+        Shop shop = this.getById(shopId);
+        if (shop == null) {
+            throw new RuntimeException("门店不存在");
+        }
+        
+        // 检查设备是否存在
+        Device device = deviceService.getById(deviceId);
+        if (device == null) {
+            throw new RuntimeException("设备不存在");
+        }
+        
+        // 如果设备已关联到其他门店,先解除关联
+        if (device.getShopId() != null && !device.getShopId().equals(shopId)) {
+            log.info("设备已关联到其他门店,将解除原关联: deviceId={}, oldShopId={}", deviceId, device.getShopId());
+        }
+        
+        // 关联设备到门店
+        device.setShopId(shopId);
+        device.setUpdateTime(LocalDateTime.now());
+        boolean result = deviceService.updateById(device);
+        
+        if (result) {
+            log.info("设备关联门店成功: deviceId={}, shopId={}", deviceId, shopId);
+        }
+        return result;
+    }
+
+    @Override
+    public int batchLinkDevices(Long shopId, List<Long> deviceIds) {
+        int count = 0;
+        for (Long deviceId : deviceIds) {
+            try {
+                if (linkDevice(shopId, deviceId)) {
+                    count++;
+                }
+            } catch (Exception e) {
+                log.error("设备关联门店失败: deviceId={}, error={}", deviceId, e.getMessage());
+            }
+        }
+        log.info("批量关联设备完成: shopId={}, 总数={}, 成功={}", shopId, deviceIds.size(), count);
+        return count;
+    }
+
+    @Override
+    public boolean unlinkDevice(Long shopId, Long deviceId) {
+        // 检查设备是否属于该门店
+        Device device = deviceService.getById(deviceId);
+        if (device == null) {
+            throw new RuntimeException("设备不存在");
+        }
+        
+        if (!shopId.equals(device.getShopId())) {
+            throw new RuntimeException("该设备不属于当前门店");
+        }
+        
+        // 解除关联
+        device.setShopId(null);
+        device.setUpdateTime(LocalDateTime.now());
+        boolean result = deviceService.updateById(device);
+        
+        if (result) {
+            log.info("设备解除门店关联成功: deviceId={}, shopId={}", deviceId, shopId);
+        }
+        return result;
+    }
+
+    @Override
+    public int batchUnlinkDevices(Long shopId, List<Long> deviceIds) {
+        int count = 0;
+        for (Long deviceId : deviceIds) {
+            try {
+                if (unlinkDevice(shopId, deviceId)) {
+                    count++;
+                }
+            } catch (Exception e) {
+                log.error("设备解除关联失败: deviceId={}, error={}", deviceId, e.getMessage());
+            }
+        }
+        log.info("批量解除关联完成: shopId={}, 总数={}, 成功={}", shopId, deviceIds.size(), count);
+        return count;
+    }
+
+    /**
+     * 填充门店设备统计信息
+     */
+    private void fillShopStats(Shop shop) {
+        // 统计该门店的设备数量
+        long deviceCount = deviceService.lambdaQuery()
+                .eq(Device::getShopId, shop.getId())
+                .count();
+        shop.setDeviceCount((int) deviceCount);
+        
+        // 统计在线设备数量
+        long onlineCount = deviceService.lambdaQuery()
+                .eq(Device::getShopId, shop.getId())
+                .eq(Device::getStatus, 1)
+                .count();
+        shop.setOnlineCount((int) onlineCount);
+    }
+
+    /**
+     * 填充门店状态标签
+     */
+    private void fillShopLabels(Shop shop) {
+        if (shop.getStatus() != null) {
+            if (shop.getStatus() == 1) {
+                shop.setStatusLabel("启用");
+                shop.setStatusColor("success");
+            } else {
+                shop.setStatusLabel("禁用");
+                shop.setStatusColor("danger");
+            }
+        }
+    }
+}