Jelajahi Sumber

feat: ERP店铺增加允许绑定标识,支持批量修改

- t_erp_shop 新增 bind_enabled 字段(默认1允许,0不允许)
- bindDevice/autoBind 过滤不允许绑定的店铺
- 同步时新店铺默认允许绑定,已有店铺不覆盖管理员的设置
- 前端: 新增允许绑定列、复选框、批量允许/禁止绑定按钮

Co-Authored-By: Claude <noreply@anthropic.com>
skyline 1 hari lalu
induk
melakukan
ae0e40cf48

+ 9 - 0
docs/database/add_erp_shop_bind_enabled.sql

@@ -0,0 +1,9 @@
+-- t_erp_shop 增加 bind_enabled 字段,控制是否允许绑定设备
+-- 默认 1(允许),运营可改为 0(不允许)
+
+ALTER TABLE t_erp_shop
+    ADD COLUMN bind_enabled INT DEFAULT 1 COMMENT '是否允许绑定设备:0-不允许,1-允许(默认)'
+    AFTER status;
+
+-- 存量数据回填
+UPDATE t_erp_shop SET bind_enabled = 1 WHERE bind_enabled IS NULL;

+ 4 - 0
haha-admin-web/src/api/erpShop.ts

@@ -29,3 +29,7 @@ export const autoBindErpShop = () =>
 /** 一键解绑所有设备 */
 export const unbindAllErpShop = () =>
   http.request<Result>("post", "/erp-shops/unbind-all");
+
+/** 批量修改允许绑定标识 */
+export const batchUpdateBindEnabled = (erpShopIds: number[], bindEnabled: number) =>
+  http.request<Result>("post", "/erp-shops/batch-bind-enabled", { data: { erpShopIds, bindEnabled } });

+ 78 - 2
haha-admin-web/src/views/erp-shop/index.vue

@@ -6,13 +6,16 @@ import Refresh from "~icons/ep/refresh";
 import Link from "~icons/ep/link";
 import Remove from "~icons/ep/remove";
 import Search from "~icons/ep/search";
+import CircleCheck from "~icons/ep/circle-check";
+import CircleClose from "~icons/ep/circle-close";
 import {
   syncErpShops,
   getErpShopList,
   bindErpShop,
   unbindErpShop,
   autoBindErpShop,
-  unbindAllErpShop
+  unbindAllErpShop,
+  batchUpdateBindEnabled
 } from "@/api/erpShop";
 import { getDeviceList } from "@/api/device";
 
@@ -24,6 +27,7 @@ const loading = ref(false);
 const syncing = ref(false);
 const autoBinding = ref(false);
 const unbindingAll = ref(false);
+const batchUpdating = ref(false);
 
 const form = reactive({
   keyword: "",
@@ -38,6 +42,11 @@ const bindDialogVisible = ref(false);
 const currentErpShop = ref<any>(null);
 const selectedDeviceId = ref<number | null>(null);
 const bindLoading = ref(false);
+const selectedRows = ref<any[]>([]);
+
+function handleSelectionChange(rows: any[]) {
+  selectedRows.value = rows;
+}
 
 async function fetchList() {
   loading.value = true;
@@ -154,6 +163,37 @@ async function handleUnbindAll() {
   }
 }
 
+async function handleBatchBindEnabled(bindEnabled: number) {
+  if (selectedRows.value.length === 0) {
+    ElMessage.warning("请先勾选要操作的店铺");
+    return;
+  }
+  const label = bindEnabled === 1 ? "允许绑定" : "禁止绑定";
+  try {
+    await ElMessageBox.confirm(
+      `确认将 ${selectedRows.value.length} 个店铺设为「${label}」?`,
+      "批量修改",
+      { type: "warning" }
+    );
+  } catch { return; }
+
+  batchUpdating.value = true;
+  try {
+    const ids = selectedRows.value.map((r: any) => r.erpShopId);
+    const res = await batchUpdateBindEnabled(ids, bindEnabled);
+    if (res.code === 200) {
+      ElMessage.success(`已更新 ${res.data} 个店铺`);
+      await fetchList();
+    } else {
+      ElMessage.error(res.message || "操作失败");
+    }
+  } catch {
+    ElMessage.error("操作请求失败");
+  } finally {
+    batchUpdating.value = false;
+  }
+}
+
 function openBindDialog(row: any) {
   currentErpShop.value = row;
   selectedDeviceId.value = row._boundDevice?.id || null;
@@ -205,6 +245,11 @@ async function handleUnbind(row: any) {
   }
 }
 
+function getBindEnabledTag(bindEnabled: number | null) {
+  if (bindEnabled === 0) return { label: "不允许", type: "danger" as const };
+  return { label: "允许", type: "success" as const };
+}
+
 onMounted(() => {
   fetchList();
 });
@@ -254,7 +299,7 @@ onMounted(() => {
       </el-form-item>
     </el-form>
 
-    <!-- 操作按钮 + 表格 -->
+    <!-- 标题 + 操作按钮 + 表格 -->
     <div class="px-6 pt-4">
       <div class="flex items-center justify-between mb-4">
         <h2 class="text-lg font-semibold">ERP 店铺管理</h2>
@@ -281,6 +326,25 @@ onMounted(() => {
           >
             一键解绑
           </el-button>
+          <el-divider direction="vertical" />
+          <el-button
+            type="success"
+            :icon="useRenderIcon(CircleCheck)"
+            :loading="batchUpdating"
+            plain
+            @click="handleBatchBindEnabled(1)"
+          >
+            批量允许绑定
+          </el-button>
+          <el-button
+            type="danger"
+            :icon="useRenderIcon(CircleClose)"
+            :loading="batchUpdating"
+            plain
+            @click="handleBatchBindEnabled(0)"
+          >
+            批量禁止绑定
+          </el-button>
         </div>
       </div>
 
@@ -292,11 +356,23 @@ onMounted(() => {
         style="width: 100%"
         empty-text="暂无数据,请先同步ERP店铺"
         :row-style="{ height: '64px' }"
+        @selection-change="handleSelectionChange"
       >
+        <el-table-column type="selection" width="50" fixed="left" />
         <el-table-column prop="erpShopId" label="ERP店铺ID" width="160" align="center" />
         <el-table-column prop="shopName" label="店铺名称" min-width="180" show-overflow-tooltip />
         <el-table-column prop="nickName" label="卖家账号" min-width="160" show-overflow-tooltip />
         <el-table-column prop="shopCode" label="设备ID(shopCode)" width="180" align="center" show-overflow-tooltip />
+        <el-table-column label="允许绑定" width="100" align="center">
+          <template #default="{ row }">
+            <el-tag
+              :type="getBindEnabledTag(row.bindEnabled).type"
+              size="small"
+            >
+              {{ getBindEnabledTag(row.bindEnabled).label }}
+            </el-tag>
+          </template>
+        </el-table-column>
         <el-table-column label="绑定设备" min-width="180">
           <template #default="{ row }">
             <el-popover

+ 19 - 0
haha-admin/src/main/java/com/haha/admin/controller/ErpShopController.java

@@ -12,6 +12,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -80,6 +81,24 @@ public class ErpShopController {
         return Result.success("解绑成功");
     }
 
+    /**
+     * 批量修改允许绑定标识
+     */
+    @RequirePermission("erp:shop:bind")
+    @Log(module = "ERP店铺管理", operation = OperationType.UPDATE, summary = "批量修改允许绑定标识")
+    @PostMapping("/batch-bind-enabled")
+    public Result<Integer> batchUpdateBindEnabled(@RequestBody Map<String, Object> params) {
+        @SuppressWarnings("unchecked")
+        List<Long> erpShopIds = ((List<Number>) params.get("erpShopIds")).stream()
+                .map(Number::longValue).toList();
+        Integer bindEnabled = (Integer) params.get("bindEnabled");
+        if (erpShopIds == null || erpShopIds.isEmpty() || bindEnabled == null) {
+            return Result.error("参数不能为空");
+        }
+        int count = erpShopService.batchUpdateBindEnabled(erpShopIds, bindEnabled);
+        return Result.success("已更新 " + count + " 个店铺", count);
+    }
+
     /**
      * 一键解绑所有设备:将所有设备的 ERP 店铺绑定全部清除
      */

+ 3 - 0
haha-entity/src/main/java/com/haha/entity/ErpShop.java

@@ -57,6 +57,9 @@ public class ErpShop implements Serializable {
     /** 状态:0-停用,1-启用 */
     private Integer status;
 
+    /** 是否允许绑定设备:0-不允许,1-允许(默认) */
+    private Integer bindEnabled;
+
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
     private LocalDateTime createTime;
 

+ 3 - 0
haha-service/src/main/java/com/haha/service/ErpShopService.java

@@ -21,4 +21,7 @@ public interface ErpShopService extends IService<ErpShop> {
     int unbindAll();
 
     java.util.Map<String, Integer> autoBind();
+
+    /** 批量修改允许绑定标识 */
+    int batchUpdateBindEnabled(java.util.List<Long> erpShopIds, int bindEnabled);
 }

+ 21 - 1
haha-service/src/main/java/com/haha/service/impl/ErpShopServiceImpl.java

@@ -157,6 +157,9 @@ public class ErpShopServiceImpl extends ServiceImpl<ErpShopMapper, ErpShop> impl
         if (erpShop == null) {
             throw new BusinessException(404, "ERP店铺不存在,请先同步ERP店铺数据");
         }
+        if (erpShop.getBindEnabled() != null && erpShop.getBindEnabled() == 0) {
+            throw new BusinessException(400, "该ERP店铺已被设置为不允许绑定");
+        }
 
         List<Device> boundDevices = deviceMapper.selectList(new LambdaQueryWrapper<Device>()
                 .eq(Device::getErpShopId, erpShopId));
@@ -188,6 +191,21 @@ public class ErpShopServiceImpl extends ServiceImpl<ErpShopMapper, ErpShop> impl
         log.info("设备解绑ERP店铺: deviceId={}", device.getDeviceId());
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int batchUpdateBindEnabled(List<Long> erpShopIds, int bindEnabled) {
+        if (erpShopIds == null || erpShopIds.isEmpty()) {
+            return 0;
+        }
+        boolean updated = update(new LambdaUpdateWrapper<ErpShop>()
+                .in(ErpShop::getErpShopId, erpShopIds)
+                .set(ErpShop::getBindEnabled, bindEnabled)
+                .set(ErpShop::getUpdateTime, LocalDateTime.now()));
+        int count = updated ? erpShopIds.size() : 0;
+        log.info("批量修改允许绑定标识: ids={}, bindEnabled={}, count={}", erpShopIds, bindEnabled, count);
+        return count;
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int unbindAll() {
@@ -212,7 +230,8 @@ public class ErpShopServiceImpl extends ServiceImpl<ErpShopMapper, ErpShop> impl
     @Transactional(rollbackFor = Exception.class)
     public Map<String, Integer> autoBind() {
         List<ErpShop> erpShopList = list(new LambdaQueryWrapper<ErpShop>()
-                .eq(ErpShop::getStatus, 1));
+                .eq(ErpShop::getStatus, 1)
+                .ne(ErpShop::getBindEnabled, 0)); // 排除不允许绑定的店铺
         if (erpShopList.isEmpty()) {
             Map<String, Integer> empty = new HashMap<>();
             empty.put("bound", 0);
@@ -330,6 +349,7 @@ public class ErpShopServiceImpl extends ServiceImpl<ErpShopMapper, ErpShop> impl
             erpShop.setServiceExpirationTime(info.getServiceExpirationTime());
             erpShop.setAuthorizationFlag(info.getAuthorizationFlag());
             erpShop.setStatus(info.getStatus() != null ? Integer.parseInt(info.getStatus()) : 1);
+            erpShop.setBindEnabled(1); // 新店铺默认允许绑定
             erpShop.setCreateTime(LocalDateTime.now());
             erpShop.setUpdateTime(LocalDateTime.now());
             save(erpShop);