Переглянути джерело

feat(仓库管理): 新增仓库管理功能及API文档

- 添加仓库管理相关的SQL脚本,包含菜单和按钮权限设置。
- 创建API接口文档,详细描述图书激活码管理和仓库管理的各项接口。
- 更新前端代码,支持仓库选择、库存查看和出库操作,增强用户体验。
ylong 2 днів тому
батько
коміт
497595bf24

+ 295 - 0
docs/2026-08-01-API接口文档.md

@@ -0,0 +1,295 @@
+# 2026-08-01 API 接口文档
+
+> 来源: `bk.shuhi.com` Swagger UI - 功能模块  
+> 日期: 2026-08-01  
+> 共 14 个接口
+
+---
+
+## 接口总览
+
+| # | HTTP 方法 | 请求路径 | 描述 | 权限标识 |
+|---|----------|---------|------|---------|
+| 1 | POST | `/activation/bookActivationInfo/cancel` | 出入库记录-作废 | `activation:bookActivationInfo:cancel` |
+| 2 | POST | `/activation/bookActivationInfo/activationAdd` | APP-上报激活码 | `app:activation:add` |
+| 3 | GET | `/activation/bookActivationInfo/pageList` | 分页查询图书激活码管理列表 | `activation:bookActivationInfo:list` |
+| 4 | GET | `/activation/bookActivationInfo/pageInLogs/{isbn}` | 分页查询图书激活码管理-入库明细 | `activation:bookActivationInfo:inLogs` |
+| 5 | GET | `/activation/bookActivationInfo/inOutSum` | 查询出入库记录-汇总 | `activation:bookActivationInfo:inOutSum` |
+| 6 | GET | `/activation/bookActivationInfo/inOutList` | 分页查询出入库记录 | `activation:bookActivationInfo:inOutList` |
+| 7 | GET | `/activation/bookActivationCodeRepository/pagelist` | 分页查询图书激活码仓库列表 | `activation:bookActivationCodeRepository:list` |
+| 8 | GET | `/activation/bookActivationCodeRepository/getInfo/{id}` | 根据主键获取图书激活码仓库详细信息 | `activation:bookActivationCodeRepository:query` |
+| 9 | POST | `/activation/bookActivationInfo/outStock` | 出库 | `activation:bookActivationInfo:outStock` |
+| 10 | POST | `/activation/bookActivationInfo/getStockAllRepository/{isbn}` | 点击库存查看各个仓库库存情况 | `activation:bookActivationInfo:list` |
+| 11 | POST | `/activation/bookActivationInfo/getSellAllRepository/{isbn}` | 点击库存查看各个仓库销量情况 | `activation:bookActivationInfo:list` |
+| 12 | POST | `/activation/bookActivationCodeRepository/update` | 修改图书激活码仓库 | `activation:bookActivationCodeRepository:update` |
+| 13 | POST | `/activation/bookActivationCodeRepository/openClose` | 启用/禁用图书激活码仓库 | `activation:bookActivationCodeRepository:openClose` |
+| 14 | POST | `/activation/bookActivationCodeRepository/add` | 新增图书激活码仓库 | `activation:bookActivationCodeRepository:add` |
+
+---
+
+## 接口详情
+
+### 一、图书激活码管理 (`/activation/bookActivationInfo`)
+
+#### 1. 取消入库记录
+
+- **方法**: `POST`
+- **路径**: `/activation/bookActivationInfo/cancel`
+- **描述**: 出入库记录-作废
+- **权限**: `activation:bookActivationInfo:cancel`
+
+**请求体 (Request Body)**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `id` | integer | 是 | 入库记录 ID |
+| `reason` | string | 否 | 废弃原因 |
+
+---
+
+#### 2. APP上报复激活码
+
+- **方法**: `POST`
+- **路径**: `/activation/bookActivationInfo/activationAdd`
+- **描述**: APP-上报激活码
+- **权限**: `app:activation:add`
+
+**请求体 (Request Body)**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `isbn` | string | 是 | ISBN 编码 |
+| `img` | string | 否 | 图片(Base64 或 URL) |
+
+---
+
+#### 3. 分页查询图书激活码管理列表
+
+- **方法**: `GET`
+- **路径**: `/activation/bookActivationInfo/pageList`
+- **描述**: 分页查询图书激活码管理列表
+- **权限**: `activation:bookActivationInfo:list`
+
+**查询参数 (Query Parameters)**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `repositoryId` | integer | 否 | 仓库 ID |
+| `isbn` | string | 否 | ISBN 编码 |
+| `bookName` | string | 否 | 书名 |
+| `orderField` | integer | 否 | 排序方式:`NULL`-默认 ISBN 排序,`1`-库存降序,`2`-库存升序,`3`-销量降序,`4`-销量升序 |
+
+---
+
+#### 4. 分页查询图书激活码管理-入库明细
+
+- **方法**: `GET`
+- **路径**: `/activation/bookActivationInfo/pageInLogs/{isbn}`
+- **描述**: 分页查询图书激活码管理-入库明细
+- **权限**: `activation:bookActivationInfo:inLogs`
+
+**路径参数 (Path Parameters)**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `isbn` | string | 是 | ISBN 编码 |
+
+---
+
+#### 5. 查询出入库记录-汇总
+
+- **方法**: `GET`
+- **路径**: `/activation/bookActivationInfo/inOutSum`
+- **描述**: 查询出入库记录-汇总(入参只看时间范围)
+- **权限**: `activation:bookActivationInfo:inOutSum`
+
+**查询参数 (Query Parameters)**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `repositoryId` | integer | 否 | 仓库 ID |
+| `isbn` | string | 否 | ISBN 编码 |
+| `bookName` | string | 否 | 书名 |
+| `type` | integer | 否 | 操作类型:`1`-入库,`2`-出库 |
+| `remark` | string | 否 | 备注信息 |
+| `startTime` | string | 否 | 操作时间开始范围(`date-time`) |
+| `endTime` | string | 否 | 操作时间结束范围(`date-time`) |
+
+---
+
+#### 6. 分页查询出入库记录
+
+- **方法**: `GET`
+- **路径**: `/activation/bookActivationInfo/inOutList`
+- **描述**: 分页查询出入库记录
+- **权限**: `activation:bookActivationInfo:inOutList`
+
+**查询参数 (Query Parameters)**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `repositoryId` | integer | 否 | 仓库 ID |
+| `isbn` | string | 否 | ISBN 编码 |
+| `bookName` | string | 否 | 书名 |
+| `type` | integer | 否 | 操作类型:`1`-入库,`2`-出库 |
+| `remark` | string | 否 | 备注信息 |
+| `startTime` | string | 否 | 操作时间开始范围(`date-time`) |
+| `endTime` | string | 否 | 操作时间结束范围(`date-time`) |
+
+---
+
+#### 7. 出库
+
+- **方法**: `POST`
+- **路径**: `/activation/bookActivationInfo/outStock`
+- **描述**: 出库
+- **权限**: `activation:bookActivationInfo:outStock`
+
+**请求体 (Request Body)**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `repositoryId` | integer | 是 | 仓库 ID |
+| `isbn` | string | 是 | ISBN 编码 |
+| `num` | integer | 是 | 出库数量 |
+| `price` | number | 否 | 单价 |
+| `remark` | string | 否 | 备注 |
+
+---
+
+#### 8. 点击库存查看各个仓库库存情况
+
+- **方法**: `POST`
+- **路径**: `/activation/bookActivationInfo/getStockAllRepository/{isbn}`
+- **描述**: 点击库存查看各个仓库库存情况
+- **权限**: `activation:bookActivationInfo:list`
+
+**路径参数 (Path Parameters)**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `isbn` | string | 是 | ISBN 编码 |
+
+---
+
+#### 9. 点击库存查看各个仓库销量情况
+
+- **方法**: `POST`
+- **路径**: `/activation/bookActivationInfo/getSellAllRepository/{isbn}`
+- **描述**: 点击库存查看各个仓库销量情况
+- **权限**: `activation:bookActivationInfo:list`
+
+**路径参数 (Path Parameters)**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `isbn` | string | 是 | ISBN 编码 |
+
+---
+
+### 二、图书激活码仓库管理 (`/activation/bookActivationCodeRepository`)
+
+#### 10. 分页查询图书激活码仓库列表
+
+- **方法**: `GET`
+- **路径**: `/activation/bookActivationCodeRepository/pagelist`
+- **描述**: 分页查询图书激活码仓库列表
+- **权限**: `activation:bookActivationCodeRepository:list`
+
+**查询参数 (Query Parameters)**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `repositoryName` | string | 否 | 仓库名称 |
+| `contactsName` | string | 否 | 联系人 |
+| `mobile` | string | 否 | 手机号 |
+| `status` | string | 否 | 使用状态:`NULL`-全部,`1`-启用,`2`-禁用 |
+
+---
+
+#### 11. 根据主键获取图书激活码仓库详细信息
+
+- **方法**: `GET`
+- **路径**: `/activation/bookActivationCodeRepository/getInfo/{id}`
+- **描述**: 根据主键获取图书激活码仓库详细信息
+- **权限**: `activation:bookActivationCodeRepository:query`
+
+**路径参数 (Path Parameters)**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `id` | integer | 是 | 仓库主键 ID |
+
+---
+
+#### 12. 修改图书激活码仓库
+
+- **方法**: `POST`
+- **路径**: `/activation/bookActivationCodeRepository/update`
+- **描述**: 修改图书激活码仓库
+- **权限**: `activation:bookActivationCodeRepository:update`
+
+**请求体 (Request Body)**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `id` | integer | 是 | 仓库 ID |
+| `repositoryName` | string | 是 | 仓库名称 |
+| `contactsName` | string | 否 | 联系人 |
+| `mobile` | string | 否 | 手机号 |
+| `provinceId` | integer | 否 | 省 ID |
+| `cityId` | integer | 否 | 市 ID |
+| `countyId` | integer | 否 | 县 ID |
+| `addressDetail` | string | 否 | 详细地址 |
+| `status` | string | 否 | 使用状态:`1`-启用,`2`-禁用 |
+
+---
+
+#### 13. 启用/禁用图书激活码仓库
+
+- **方法**: `POST`
+- **路径**: `/activation/bookActivationCodeRepository/openClose`
+- **描述**: 启用/禁用图书激活码仓库
+- **权限**: `activation:bookActivationCodeRepository:openClose`
+
+**请求体 (Request Body)**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `id` | integer | 是 | 仓库 ID |
+| `status` | string | 是 | 使用状态:`1`-启用,`2`-禁用 |
+
+---
+
+#### 14. 新增图书激活码仓库
+
+- **方法**: `POST`
+- **路径**: `/activation/bookActivationCodeRepository/add`
+- **描述**: 新增图书激活码仓库
+- **权限**: `activation:bookActivationCodeRepository:add`
+
+**请求体 (Request Body)**
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `id` | integer | 否 | 新增时为 `NULL` |
+| `repositoryName` | string | 是 | 仓库名称 |
+| `contactsName` | string | 否 | 联系人 |
+| `mobile` | string | 否 | 手机号 |
+| `provinceId` | integer | 否 | 省 ID |
+| `cityId` | integer | 否 | 市 ID |
+| `countyId` | integer | 否 | 县 ID |
+| `addressDetail` | string | 否 | 详细地址 |
+| `status` | string | 否 | 使用状态:`1`-启用,`2`-禁用 |
+
+---
+
+## 统计
+
+| 维度 | 详情 |
+|------|------|
+| 接口总数 | 14 |
+| GET 接口 | 6 |
+| POST 接口 | 8 |
+| 图书激活码管理模块 | 9 个接口 |
+| 图书激活码仓库管理模块 | 5 个接口 |

+ 79 - 0
docs/2026-08-01-仓库管理菜单.sql

@@ -0,0 +1,79 @@
+-- 数据管理 / 仓库管理 菜单及按钮权限
+-- 执行前请确认 menu_id 未被占用:SELECT MAX(menu_id) FROM sys_menu;
+-- 父菜单:数据管理 (menu_id = 2121)
+-- 权限标识与后端接口保持一致
+
+-- 1. 菜单:仓库管理
+INSERT INTO `sys_menu` (
+    `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`,
+    `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`,
+    `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`,
+    `update_time`, `remark`, `use_type`
+) VALUES (
+    2700, '仓库管理', 2121, 5, 'warehouse', 'data/warehouse/index',
+    NULL, '', 1, 0, 'C', '0',
+    '0', 'activation:bookActivationCodeRepository:list', 'HomeOutlined', 'admin', NOW(), '',
+    NULL, '图书激活码仓库管理', 1
+);
+
+-- 2. 按钮:新增
+INSERT INTO `sys_menu` (
+    `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`,
+    `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`,
+    `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`,
+    `update_time`, `remark`, `use_type`
+) VALUES (
+    2701, '新增', 2700, 1, '', NULL,
+    NULL, '', 1, 0, 'F', '0',
+    '0', 'activation:bookActivationCodeRepository:add', '#', 'admin', NOW(), '',
+    NULL, '', 1
+);
+
+-- 3. 按钮:详情/查询
+INSERT INTO `sys_menu` (
+    `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`,
+    `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`,
+    `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`,
+    `update_time`, `remark`, `use_type`
+) VALUES (
+    2702, '详情', 2700, 2, '', NULL,
+    NULL, '', 1, 0, 'F', '0',
+    '0', 'activation:bookActivationCodeRepository:query', '#', 'admin', NOW(), '',
+    NULL, '', 1
+);
+
+-- 4. 按钮:修改
+INSERT INTO `sys_menu` (
+    `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`,
+    `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`,
+    `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`,
+    `update_time`, `remark`, `use_type`
+) VALUES (
+    2703, '修改', 2700, 3, '', NULL,
+    NULL, '', 1, 0, 'F', '0',
+    '0', 'activation:bookActivationCodeRepository:update', '#', 'admin', NOW(), '',
+    NULL, '', 1
+);
+
+-- 5. 按钮:启用/禁用
+INSERT INTO `sys_menu` (
+    `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`,
+    `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`,
+    `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`,
+    `update_time`, `remark`, `use_type`
+) VALUES (
+    2704, '启用/禁用', 2700, 4, '', NULL,
+    NULL, '', 1, 0, 'F', '0',
+    '0', 'activation:bookActivationCodeRepository:openClose', '#', 'admin', NOW(), '',
+    NULL, '', 1
+);
+
+-- 6. 给超级管理员角色授权(role_id = 1,按实际调整)
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, m.menu_id
+FROM `sys_menu` m
+WHERE m.menu_id IN (2700, 2701, 2702, 2703, 2704)
+  AND NOT EXISTS (
+      SELECT 1 FROM `sys_role_menu` rm
+      WHERE rm.role_id = 1 AND rm.menu_id = m.menu_id
+  );

+ 50 - 15
src/views/code/list/components/code-search.vue

@@ -11,33 +11,68 @@
 </template>
 
 <script setup>
-    import {
-        reactive,
-        ref,
-        defineEmits,
-        getCurrentInstance,
-        computed
-    } from 'vue';
+    import { reactive, ref, computed, defineEmits, onMounted } from 'vue';
     import ProSearch from '@/components/CommonPage/ProSearch2.vue';
+    import request from '@/utils/request';
 
-    const { proxy } = getCurrentInstance();
     const emit = defineEmits(['search']);
 
-    const formItems = computed(() => {
-        return [
-            { type: 'input', label: 'ISBN', prop: 'isbn' },
-            { type: 'input', label: '书名', prop: 'bookName' }
-        ];
-    });
+    const warehouseOptions = ref([]);
+
+    const formItems = computed(() => [
+        {
+            type: 'select',
+            label: '请选择仓库',
+            prop: 'repositoryId',
+            options: warehouseOptions.value,
+            props: {
+                clearable: true,
+                filterable: true
+            }
+        },
+        { type: 'input', label: 'ISBN', prop: 'isbn' },
+        { type: 'input', label: '书名', prop: 'bookName' }
+    ]);
 
     const initKeys = reactive({
+        repositoryId: '',
         isbn: '',
         bookName: ''
     });
 
+    function loadWarehouseOptions() {
+        request
+            .get('/activation/bookActivationCodeRepository/pagelist', {
+                params: {
+                    status: '1',
+                    pageNum: 1,
+                    pageSize: 500
+                }
+            })
+            .then((res) => {
+                if (res.data.code === 200) {
+                    const list = res.data.rows || res.data.data || [];
+                    warehouseOptions.value = list.map((item) => ({
+                        label: item.repositoryName,
+                        value: item.id
+                    }));
+                }
+            });
+    }
+
+    onMounted(() => {
+        loadWarehouseOptions();
+    });
+
     const searchRef = ref(null);
     /** 搜索 */
     const search = (data) => {
-        emit('search', { ...data });
+        const opt = warehouseOptions.value.find(
+            (o) => o.value == data.repositoryId
+        );
+        emit('search', {
+            ...data,
+            repositoryName: opt?.label || ''
+        });
     };
 </script>

+ 69 - 33
src/views/code/list/components/in-detail-modal.vue

@@ -1,33 +1,36 @@
 <!-- 入库明细弹窗 -->
 <template>
-    <ele-modal form :width="1200" v-model="visible" title="入库明细">
+    <ele-modal :width="1200" v-model="visible" title="入库明细">
         <common-table
             ref="tableRef"
             :pageConfig="pageConfig"
             :columns="columns"
             :tools="false"
+            :flex-table="false"
             :bodyStyle="{ padding: 0 }"
+            height="50vh"
+            :show-overflow-tooltip="true"
         >
-            <!-- 激活码图片插槽 -->
+            <!-- 激活码图片插槽:避免 el-image 在表格单元格内触发组件更新异常 -->
             <template #urlLink="{ row }">
-                <el-image
+                <img
                     v-if="row.urlLink"
                     :src="row.urlLink"
-                    :preview-src-list="[row.urlLink]"
-                    fit="cover"
-                    style="
-                        width: 80px;
-                        height: 80px;
-                        border-radius: 4px;
-                        cursor: pointer;
-                    "
-                    :preview-teleported="true"
-                    @error="handleImageError"
+                    alt="激活码图片"
+                    class="code-image"
+                    @click="handlePreview(row.urlLink)"
                 />
-                <span v-else class="text-gray-400">暂无图片</span>
+                <span v-else class="no-image">暂无图片</span>
             </template>
         </common-table>
 
+        <el-image-viewer
+            v-if="showViewer"
+            :url-list="previewList"
+            :initial-index="0"
+            @close="showViewer = false"
+        />
+
         <template #footer>
             <el-button @click="handleCancel">关闭</el-button>
         </template>
@@ -47,31 +50,44 @@
     /** 页面组件实例 */
     const tableRef = ref(null);
 
+    /** 图片预览 */
+    const showViewer = ref(false);
+    const previewList = ref([]);
+
     /** 页面配置 */
     const pageConfig = reactive({
-        pageUrl: '',
+        pageUrl: '/activation/bookActivationInfo/pageInLogs',
         fileName: '入库明细',
-        cacheKey: 'inDetailTable'
+        cacheKey: 'inDetailTable',
+        rowKey: 'id',
+        params: {}
     });
 
     /** 表格列配置 */
     const columns = ref([
-        //激活码图片
         {
             label: '激活码图片',
             prop: 'urlLink',
             width: 120,
             align: 'center',
-            slot: 'urlLink'
+            slot: 'urlLink',
+            showOverflowTooltip: false
         },
         { label: 'ISBN', prop: 'isbn', width: 140, align: 'center' },
         { label: '书名', prop: 'bookName', minWidth: 200, align: 'left' },
+        {
+            label: '仓库',
+            prop: 'repositoryName',
+            width: 120,
+            align: 'center',
+            formatter: (row) => row.repositoryName || '-'
+        },
         {
             label: '数量',
             prop: 'num',
             width: 80,
             align: 'center',
-            formatter: (row) => 1
+            formatter: () => 1
         },
         {
             label: '状态',
@@ -102,24 +118,25 @@
         visible.value = false;
     };
 
-    /** 图片加载错误处理 */
-    const handleImageError = (e) => {
-        console.warn('图片加载失败:', e);
+    /** 预览图片 */
+    const handlePreview = (url) => {
+        if (!url) return;
+        previewList.value = [url];
+        showViewer.value = true;
     };
 
     /** 弹窗打开事件 */
     const handleOpen = (isbn) => {
-        if (isbn) {
-            currentIsbn.value = isbn;
-            visible.value = true;
-            // 设置页面URL参数
-            pageConfig.pageUrl = `/activation/bookActivationInfo/pageInLogs/${isbn}`;
-
-            nextTick(() => {
-                // 重新加载数据
-                tableRef.value?.reload();
-            });
-        }
+        if (!isbn) return;
+
+        pageConfig.pageUrl = `/activation/bookActivationInfo/pageInLogs/${isbn}`;
+        currentIsbn.value = isbn;
+        pageConfig.params = { isbn };
+        visible.value = true;
+
+        nextTick(() => {
+            tableRef.value?.reload();
+        });
     };
 
     /** 监听弹窗关闭,清理数据 */
@@ -127,6 +144,8 @@
         if (!newVal) {
             currentIsbn.value = '';
             pageConfig.params = {};
+            showViewer.value = false;
+            previewList.value = [];
         }
     });
 
@@ -134,3 +153,20 @@
         handleOpen
     });
 </script>
+
+<style scoped>
+    .code-image {
+        width: 80px;
+        height: 80px;
+        object-fit: cover;
+        border-radius: 4px;
+        cursor: pointer;
+        display: block;
+        margin: 0 auto;
+    }
+
+    .no-image {
+        color: #c0c4cc;
+        font-size: 12px;
+    }
+</style>

+ 7 - 0
src/views/code/list/components/out-detail-modal.vue

@@ -40,6 +40,13 @@
     const columns = ref([
         { label: 'ISBN', prop: 'isbn', width: 140, align: 'center' },
         { label: '书名', prop: 'bookName', minWidth: 200, align: 'left' },
+        {
+            label: '仓库',
+            prop: 'repositoryName',
+            width: 120,
+            align: 'center',
+            formatter: (row) => row.repositoryName || '-'
+        },
         { label: '数量', prop: 'num', width: 80, align: 'center' },
         { label: '金额', prop: 'totalPrice', width: 100, align: 'center' },
         {

+ 75 - 35
src/views/code/list/components/out-stock-modal.vue

@@ -13,6 +13,14 @@
             :rules="rules"
             label-width="80px"
         >
+            <el-form-item label="出库仓库" prop="repositoryId">
+                <el-input
+                    :model-value="repositoryName"
+                    placeholder="请选择仓库"
+                    readonly
+                />
+            </el-form-item>
+
             <el-form-item label="ISBN" prop="isbn">
                 <el-input
                     v-model="formData.isbn"
@@ -32,13 +40,14 @@
             <el-form-item label="数量" prop="num">
                 <el-input-number
                     v-model="formData.num"
-                    :min="1"
-                    :max="currentBook?.stockNum || 9999"
+                    :min="numMin"
+                    :max="numMax"
                     :precision="0"
                     placeholder="请输入数量"
                     style="width: 200px"
                     @change="handleNumChange"
                 />
+                <span class="stock-tip">当前仓库可用库存:{{ maxStock }}</span>
             </el-form-item>
 
             <el-form-item label="单价" prop="price">
@@ -75,7 +84,7 @@
 </template>
 
 <script setup>
-    import { ref, reactive, watch } from 'vue';
+    import { ref, reactive, computed } from 'vue';
     import { EleMessage } from 'ele-admin-plus/es';
     import request from '@/utils/request';
 
@@ -90,6 +99,7 @@
 
     /** 表单数据 */
     const formData = reactive({
+        repositoryId: null,
         isbn: '',
         bookName: '',
         num: 1,
@@ -97,8 +107,19 @@
         remark: ''
     });
 
+    /** 当前书籍信息 */
+    const currentBook = ref(null);
+    const repositoryName = ref('');
+    const maxStock = computed(() => Number(currentBook.value?.stockNum || 0));
+    // InputNumber 要求 min <= max,库存为 0 时避免 min(1) > max(0)
+    const numMin = computed(() => (maxStock.value > 0 ? 1 : 0));
+    const numMax = computed(() => Math.max(maxStock.value, 0));
+
     /** 表单验证规则 */
     const rules = reactive({
+        repositoryId: [
+            { required: true, message: '请先选择仓库', trigger: 'change' }
+        ],
         num: [
             { required: true, message: '请输入数量', trigger: 'blur' },
             {
@@ -111,13 +132,10 @@
                 validator: (rule, value, callback) => {
                     if (!Number.isInteger(value)) {
                         callback(new Error('数量必须是整数'));
-                    } else if (
-                        currentBook.value &&
-                        value > currentBook.value.stockNum
-                    ) {
+                    } else if (value > maxStock.value) {
                         callback(
                             new Error(
-                                `数量不能大于库存数量${currentBook.value.stockNum}`
+                                '此次出库超出库存数量,请减少数量后重试!'
                             )
                         );
                     } else {
@@ -138,9 +156,6 @@
         ]
     });
 
-    /** 当前书籍信息 */
-    const currentBook = ref(null);
-
     /** 出库结果数据 */
     const outStockResult = ref(null);
 
@@ -154,6 +169,7 @@
         // 重置表单
         formRef.value?.resetFields();
         Object.assign(formData, {
+            repositoryId: null,
             isbn: '',
             bookName: '',
             num: 1,
@@ -161,18 +177,16 @@
             remark: ''
         });
         currentBook.value = null;
+        repositoryName.value = '';
         outStockResult.value = null;
         loading.value = false;
     };
 
     /** 数量变化处理 */
     const handleNumChange = (value) => {
-        // 验证数量不能超过库存
-        if (currentBook.value && value > currentBook.value.stockNum) {
-            EleMessage.warning(
-                `出库数量不能大于库存数量${currentBook.value.stockNum}`
-            );
-            formData.num = currentBook.value.stockNum;
+        if (value > maxStock.value) {
+            EleMessage.warning('此次出库超出库存数量,请减少数量后重试!');
+            formData.num = maxStock.value || 1;
             return;
         }
 
@@ -188,14 +202,22 @@
 
     /** 生成链接 */
     const handleGenerateLink = async () => {
-        // 表单验证
         await formRef.value?.validate();
 
+        if (!formData.repositoryId) {
+            EleMessage.warning('出库只能出当前仓库的,请先选择仓库');
+            return;
+        }
+        if (formData.num > maxStock.value) {
+            EleMessage.warning('此次出库超出库存数量,请减少数量后重试!');
+            return;
+        }
+
         loading.value = true;
 
-        // 调用出库接口
         request
             .post('/activation/bookActivationInfo/outStock', {
+                repositoryId: formData.repositoryId,
                 isbn: formData.isbn,
                 num: formData.num,
                 price: formData.price,
@@ -205,17 +227,18 @@
                 if (response.data.code === 200) {
                     outStockResult.value = response.data.data;
                     EleMessage.success('出库成功');
-
-                    // 关闭当前弹窗
                     visible.value = false;
-
-                    // 触发打开激活码出库单弹窗
                     emit('openActivationModal', {
                         ...formData,
+                        repositoryName: repositoryName.value,
                         ...response.data.data
                     });
+                    emit('success');
                 } else {
-                    EleMessage.error(response.data.msg || '出库失败');
+                    EleMessage.error(
+                        response.data.msg ||
+                            '此次出库超出库存数量,请减少数量后重试!'
+                    );
                 }
             })
             .finally(() => {
@@ -224,21 +247,32 @@
     };
 
     /** 弹窗打开事件 */
-    const handleOpen = (bookInfo) => {
-        if (bookInfo) {
-            currentBook.value = bookInfo;
-            formData.isbn = bookInfo.isbn || '';
-            formData.bookName = bookInfo.bookName || '';
-            formData.num = 1;
-            // 初始数量为1,使用零售价
-            formData.price = bookInfo.retailPrice || 0;
-            formData.remark = '';
-            visible.value = true;
+    const handleOpen = (bookInfo, warehouse) => {
+        if (!warehouse?.id) {
+            EleMessage.warning('出库只能出当前仓库的,请先在上方选择仓库');
+            return;
         }
+        if (!bookInfo) return;
+
+        const stock = Number(bookInfo.stockNum || 0);
+        if (stock <= 0) {
+            EleMessage.warning('当前仓库可用库存为0,无法出库');
+            return;
+        }
+
+        currentBook.value = bookInfo;
+        repositoryName.value = warehouse.name || '';
+        formData.repositoryId = warehouse.id;
+        formData.isbn = bookInfo.isbn || '';
+        formData.bookName = bookInfo.bookName || '';
+        formData.num = 1;
+        formData.price = bookInfo.retailPrice || 0;
+        formData.remark = '';
+        visible.value = true;
     };
 
     /** 定义事件 */
-    const emit = defineEmits(['openActivationModal']);
+    const emit = defineEmits(['openActivationModal', 'success']);
 
     defineExpose({
         handleOpen
@@ -249,4 +283,10 @@
     .el-form-item {
         margin-bottom: 20px;
     }
+
+    .stock-tip {
+        margin-left: 12px;
+        color: #909399;
+        font-size: 13px;
+    }
 </style>

+ 94 - 0
src/views/code/list/components/stock-repository-modal.vue

@@ -0,0 +1,94 @@
+<!-- 各仓库库存弹窗 -->
+<template>
+    <ele-modal form :width="640" v-model="visible" title="库存">
+        <div class="stock-header" v-if="bookInfo">
+            <span class="book-name">{{ bookInfo.bookName || '-' }}</span>
+            <span class="isbn">{{ bookInfo.isbn || '-' }}</span>
+        </div>
+
+        <el-table
+            v-loading="loading"
+            :data="tableData"
+            border
+            stripe
+            style="width: 100%"
+        >
+            <el-table-column type="index" label="序号" width="80" align="center" />
+            <el-table-column
+                prop="repositoryName"
+                label="仓库名称"
+                min-width="200"
+                align="center"
+            />
+            <el-table-column
+                prop="stockNum"
+                label="可用库存"
+                width="140"
+                align="center"
+            >
+                <template #default="{ row }">
+                    {{ row.stockNum ?? row.num ?? 0 }}
+                </template>
+            </el-table-column>
+        </el-table>
+
+        <template #footer>
+            <el-button @click="visible = false">关闭</el-button>
+        </template>
+    </ele-modal>
+</template>
+
+<script setup>
+    import { ref } from 'vue';
+    import { EleMessage } from 'ele-admin-plus/es';
+    import request from '@/utils/request';
+
+    const visible = defineModel({ type: Boolean });
+    const loading = ref(false);
+    const tableData = ref([]);
+    const bookInfo = ref(null);
+
+    async function handleOpen(row) {
+        if (!row?.isbn) {
+            EleMessage.warning('缺少商品ISBN');
+            return;
+        }
+        bookInfo.value = row;
+        visible.value = true;
+        loading.value = true;
+        tableData.value = [];
+        try {
+            const res = await request.post(
+                `/activation/bookActivationInfo/getStockAllRepository/${row.isbn}`
+            );
+            if (res.data.code === 200) {
+                tableData.value = res.data.data || [];
+            } else {
+                EleMessage.error(res.data.msg || '获取库存失败');
+            }
+        } catch (e) {
+            EleMessage.error(e.message || '获取库存失败');
+        } finally {
+            loading.value = false;
+        }
+    }
+
+    defineExpose({ handleOpen });
+</script>
+
+<style scoped>
+    .stock-header {
+        margin-bottom: 16px;
+        font-size: 14px;
+        color: #303133;
+    }
+
+    .book-name {
+        font-weight: 500;
+        margin-right: 12px;
+    }
+
+    .isbn {
+        color: #909399;
+    }
+</style>

+ 28 - 14
src/views/code/list/index.vue

@@ -81,7 +81,7 @@
             <template #stockNum="{ row }">
                 <div
                     class="stock-cell text-primary"
-                    @click="handleViewStockImages(row)"
+                    @click="handleViewStockRepository(row)"
                     style="
                         cursor: pointer;
                         width: 100%;
@@ -135,6 +135,7 @@
             ref="outStockModalRef"
             v-model="outStockVisible"
             @openActivationModal="handleOpenActivationModal"
+            @success="() => reload()"
         />
 
         <!-- 激活码出库单弹窗 -->
@@ -150,10 +151,10 @@
             @success="handlePriceEditSuccess"
         />
 
-        <!-- 库存图片查看弹窗 -->
-        <stock-image-modal
-            ref="stockImageModalRef"
-            v-model="stockImageVisible"
+        <!-- 各仓库库存弹窗 -->
+        <stock-repository-modal
+            ref="stockRepositoryModalRef"
+            v-model="stockRepositoryVisible"
         />
 
         <!-- 商品编辑弹窗 -->
@@ -173,7 +174,7 @@
     import OutStockModal from './components/out-stock-modal.vue';
     import ActivationOutStockModal from './components/activation-out-stock-modal.vue';
     import PriceEditModal from './components/price-edit-modal.vue';
-    import StockImageModal from './components/stock-image-modal.vue';
+    import StockRepositoryModal from './components/stock-repository-modal.vue';
     import booksEdit from '@/views/data/books/components/books-edit.vue';
 
     defineOptions({ name: 'CodeList' });
@@ -184,7 +185,9 @@
     const outStockVisible = ref(false);
     const activationOutStockVisible = ref(false);
     const priceEditVisible = ref(false);
-    const stockImageVisible = ref(false);
+    const stockRepositoryVisible = ref(false);
+    const selectedRepositoryId = ref('');
+    const selectedRepositoryName = ref('');
 
     /** 弹窗组件引用 */
     const outDetailModalRef = ref(null);
@@ -192,7 +195,7 @@
     const outStockModalRef = ref(null);
     const activationOutStockModalRef = ref(null);
     const priceEditModalRef = ref(null);
-    const stockImageModalRef = ref(null);
+    const stockRepositoryModalRef = ref(null);
 
     //编辑图书
     const bookEditRef = ref(null);
@@ -298,6 +301,10 @@
 
     /** 搜索 */
     function reload(where) {
+        if (where && 'repositoryId' in where) {
+            selectedRepositoryId.value = where.repositoryId || '';
+            selectedRepositoryName.value = where.repositoryName || '';
+        }
         pageRef.value?.reload(where);
     }
 
@@ -322,10 +329,17 @@
         EleMessage.info('新增功能开发中...');
     }
 
-    //查看出库明细
+    //出库
     function handleViewDetails(row) {
+        if (!selectedRepositoryId.value) {
+            EleMessage.warning('出库只能出当前仓库的,请先在上方选择仓库');
+            return;
+        }
         if (row && row.isbn) {
-            outStockModalRef.value?.handleOpen(row);
+            outStockModalRef.value?.handleOpen(row, {
+                id: selectedRepositoryId.value,
+                name: selectedRepositoryName.value
+            });
         } else {
             EleMessage.warning('请选择有效的商品');
         }
@@ -336,11 +350,11 @@
         activationOutStockModalRef.value?.handleOpen(data);
     }
 
-    /** 查看库存图片 */
-    const handleViewStockImages = (row) => {
-        stockImageVisible.value = true;
+    /** 点击库存查看各仓库库存 */
+    const handleViewStockRepository = (row) => {
+        stockRepositoryVisible.value = true;
         nextTick(() => {
-            stockImageModalRef.value?.handleOpen(row.isbn);
+            stockRepositoryModalRef.value?.handleOpen(row);
         });
     };
 

+ 1 - 1
src/views/code/record/components/invalid-modal.vue

@@ -1,7 +1,7 @@
 <template>
     <ele-modal
         v-model="visible"
-        title="作废"
+        :title="type == 1 ? '入库作废' : '出库作废'"
         width="500px"
         :destroy-on-close="true"
         @closed="handleClosed"

+ 16 - 4
src/views/code/record/index.vue

@@ -65,7 +65,7 @@
                         link
                         @click="handleViewDetails(row)"
                     >
-                        [查看详情]
+                        [查看]
                     </el-button>
                     <el-button
                         type="danger"
@@ -116,8 +116,13 @@
         request
             .get('/activation/bookActivationInfo/inOutSum', {
                 params: {
-                    startTime: where.startTime || '',
-                    endTime: where.endTime || ''
+                    repositoryId: where.repositoryId || undefined,
+                    isbn: where.isbn || undefined,
+                    bookName: where.bookName || undefined,
+                    type: where.type || undefined,
+                    remark: where.remark || undefined,
+                    startTime: where.startTime || undefined,
+                    endTime: where.endTime || undefined
                 }
             })
             .then((res) => {
@@ -180,12 +185,19 @@
             formatter: (row) => (row.price == null ? '-' : row.price)
         },
         {
-            label: '操作',
+            label: '操作',
             prop: 'operatorName',
             align: 'center',
             width: 120,
             slot: 'operatorName'
         },
+        {
+            label: '仓库',
+            prop: 'repositoryName',
+            align: 'center',
+            minWidth: 140,
+            formatter: (row) => row.repositoryName || '-'
+        },
         {
             columnKey: 'action',
             label: '操作',

+ 44 - 12
src/views/code/record/page-search.vue

@@ -6,24 +6,20 @@
             ref="searchRef"
             @search="search"
             :initKeys="initKeys"
-        >
-            <template #buttons>
-                <el-button type="primary" @click="searchRef.handleSearch()"
-                    >查询</el-button
-                >
-                <el-button @click="searchRef.handleReset()">重置</el-button>
-            </template>
-        </ProSearch>
+        />
     </ele-card>
 </template>
 
 <script setup>
-    import { reactive, ref, defineEmits } from 'vue';
+    import { reactive, ref, computed, defineEmits, onMounted } from 'vue';
     import ProSearch from '@/components/CommonPage/ProSearch2.vue';
+    import request from '@/utils/request';
 
     const emit = defineEmits(['search']);
 
-    const formItems = reactive([
+    const warehouseOptions = ref([]);
+
+    const formItems = computed(() => [
         {
             type: 'input',
             label: 'ISBN',
@@ -41,14 +37,25 @@
             label: '请选择操作类型',
             prop: 'type',
             props: {
-                placeholder: '请选择操作类型'
+                placeholder: '请选择操作类型',
+                clearable: true
             },
             options: [
-                { label: '全部', value: '' },
                 { label: '入库', value: '1' },
                 { label: '出库', value: '2' }
             ]
         },
+        {
+            type: 'select',
+            label: '请选择仓库',
+            prop: 'repositoryId',
+            options: warehouseOptions.value,
+            props: {
+                placeholder: '请选择仓库',
+                clearable: true,
+                filterable: true
+            }
+        },
         {
             type: 'input',
             label: '请输入备注信息',
@@ -116,9 +123,34 @@
         isbn: '',
         bookName: '',
         type: '',
+        repositoryId: '',
         remark: ''
     });
 
+    function loadWarehouseOptions() {
+        request
+            .get('/activation/bookActivationCodeRepository/pagelist', {
+                params: {
+                    status: '1',
+                    pageNum: 1,
+                    pageSize: 500
+                }
+            })
+            .then((res) => {
+                if (res.data.code === 200) {
+                    const list = res.data.rows || res.data.data || [];
+                    warehouseOptions.value = list.map((item) => ({
+                        label: item.repositoryName,
+                        value: item.id
+                    }));
+                }
+            });
+    }
+
+    onMounted(() => {
+        loadWarehouseOptions();
+    });
+
     const searchRef = ref(null);
     /** 搜索 */
     const search = (data) => {

+ 36 - 0
src/views/data/warehouse/components/page-search.vue

@@ -0,0 +1,36 @@
+<!-- 搜索表单 -->
+<template>
+    <ele-card :body-style="{ paddingBottom: '8px' }">
+        <ProSearch
+            :items="formItems"
+            ref="searchRef"
+            @search="search"
+            :initKeys="initKeys"
+        />
+    </ele-card>
+</template>
+
+<script setup>
+    import { reactive, ref, defineEmits } from 'vue';
+    import ProSearch from '@/components/CommonPage/ProSearch2.vue';
+
+    const emit = defineEmits(['search']);
+
+    const formItems = reactive([
+        { type: 'input', label: '仓库联系人', prop: 'contactsName' },
+        { type: 'input', label: '联系人电话', prop: 'mobile' },
+        { type: 'input', label: '仓库名称', prop: 'repositoryName' }
+    ]);
+
+    const initKeys = reactive({
+        contactsName: '',
+        mobile: '',
+        repositoryName: ''
+    });
+
+    const searchRef = ref(null);
+    /** 搜索 */
+    const search = (data) => {
+        emit('search', { ...data });
+    };
+</script>

+ 194 - 0
src/views/data/warehouse/components/warehouse-edit.vue

@@ -0,0 +1,194 @@
+<!-- 新增/编辑仓库 -->
+<template>
+    <simple-form-modal
+        :title="title"
+        :items="formItems"
+        ref="editRef"
+        :baseUrl="baseUrl"
+        labelWidth="120px"
+        width="700px"
+        @success="(data) => emit('success', data)"
+        :type="type"
+    />
+</template>
+
+<script setup>
+    import { reactive, ref, computed, defineEmits, getCurrentInstance } from 'vue';
+    import SimpleFormModal from '@/components/CommonPage/SimpleFormModal.vue';
+    import request from '@/utils/request';
+
+    const { proxy } = getCurrentInstance();
+    const emit = defineEmits(['success']);
+
+    const provinceList = ref([]);
+    const cityList = ref([]);
+    const countyList = ref([]);
+    const title = ref('新增仓库');
+    const type = ref('');
+    const editRef = ref(null);
+
+    const formItems = computed(() => {
+        return [
+            {
+                type: 'input',
+                label: '仓库名称',
+                prop: 'repositoryName',
+                required: true
+            },
+            {
+                type: 'input',
+                label: '联系人',
+                prop: 'contactsName',
+                required: true
+            },
+            {
+                type: 'radio',
+                label: '状态',
+                prop: 'status',
+                required: true,
+                options: [
+                    { label: '启用', value: '1' },
+                    { label: '停用', value: '2' }
+                ]
+            },
+            {
+                type: 'select',
+                label: '省份',
+                prop: 'provinceId',
+                required: true,
+                options: provinceList.value.map((d) => ({
+                    label: d.district,
+                    value: d.id
+                })),
+                props: {
+                    filterable: true,
+                    onChange: (val) => {
+                        cityList.value = [];
+                        countyList.value = [];
+                        if (val) {
+                            getDistrictList(val).then((res) => {
+                                cityList.value = res.data.data || [];
+                            });
+                        }
+                    }
+                }
+            },
+            {
+                type: 'select',
+                label: '所在市',
+                prop: 'cityId',
+                required: true,
+                options: cityList.value.map((d) => ({
+                    label: d.district,
+                    value: d.id
+                })),
+                props: {
+                    filterable: true,
+                    onChange: (val) => {
+                        countyList.value = [];
+                        if (val) {
+                            getDistrictList(val).then((res) => {
+                                countyList.value = res.data.data || [];
+                            });
+                        }
+                    }
+                }
+            },
+            {
+                type: 'select',
+                label: '所在区',
+                prop: 'countyId',
+                required: true,
+                options: countyList.value.map((d) => ({
+                    label: d.district,
+                    value: d.id
+                })),
+                props: {
+                    filterable: true
+                }
+            },
+            {
+                type: 'input',
+                label: '详细地址',
+                prop: 'addressDetail',
+                required: true
+            },
+            {
+                type: 'input',
+                label: '手机号码',
+                prop: 'mobile',
+                required: true,
+                itemProps: {
+                    rules: [
+                        {
+                            pattern: /^1[3-9]\d{9}$/,
+                            message: '请输入正确的手机号码'
+                        }
+                    ]
+                }
+            }
+        ];
+    });
+
+    const baseUrl = reactive({
+        add: '/activation/bookActivationCodeRepository/add',
+        update: '/activation/bookActivationCodeRepository/update'
+    });
+
+    const initParams = {
+        status: '1'
+    };
+
+    function getDistrictList(id = 1) {
+        return proxy.$http.get(`/baseinfo/districtInfo/findInfo/${id}`);
+    }
+
+    async function handleOpen(data = {}, optType) {
+        type.value = optType || '';
+        const isEdit = !!(data && data.id);
+
+        if (optType === 'detail') {
+            title.value = '仓库详情';
+        } else {
+            title.value = isEdit ? '编辑仓库' : '新增仓库';
+        }
+
+        await getDistrictList().then((res) => {
+            provinceList.value = res.data.data || [];
+        });
+
+        let formData = isEdit ? { ...data } : { ...initParams };
+
+        if (isEdit) {
+            try {
+                const res = await request.get(
+                    `/activation/bookActivationCodeRepository/getInfo/${data.id}`
+                );
+                if (res.data.code === 200 && res.data.data) {
+                    formData = { ...res.data.data };
+                    formData.status = String(formData.status ?? '1');
+                }
+            } catch (e) {
+                // 列表行数据兜底
+            }
+
+            if (formData.provinceId) {
+                await getDistrictList(formData.provinceId).then((res) => {
+                    cityList.value = res.data.data || [];
+                });
+            }
+            if (formData.cityId) {
+                await getDistrictList(formData.cityId).then((res) => {
+                    countyList.value = res.data.data || [];
+                });
+            }
+        } else {
+            cityList.value = [];
+            countyList.value = [];
+        }
+
+        editRef.value?.handleOpen(formData);
+    }
+
+    defineExpose({ handleOpen });
+</script>

+ 167 - 0
src/views/data/warehouse/index.vue

@@ -0,0 +1,167 @@
+<template>
+    <ele-page flex-table>
+        <page-search @search="reload" />
+
+        <common-table ref="pageRef" :pageConfig="pageConfig" :columns="columns">
+            <template #toolbar>
+                <el-button
+                    type="primary"
+                    plain
+                    :icon="PlusOutlined"
+                    v-permission="'activation:bookActivationCodeRepository:add'"
+                    @click="handleUpdate()"
+                >
+                    新增
+                </el-button>
+                <el-radio-group
+                    @change="handleStatusChange"
+                    v-model="status"
+                    style="margin-left: 40px"
+                >
+                    <el-radio-button label="全部" value="" />
+                    <el-radio-button label="启用" value="1" />
+                    <el-radio-button label="禁用" value="2" />
+                </el-radio-group>
+            </template>
+
+            <template #status="{ row }">
+                <el-tag :type="row.status == 1 ? 'success' : 'info'">
+                    {{ row.status == 1 ? '启用' : '禁用' }}
+                </el-tag>
+            </template>
+
+            <template #action="{ row }">
+                <div>
+                    <el-button
+                        type="success"
+                        link
+                        v-permission="'activation:bookActivationCodeRepository:query'"
+                        @click="handleUpdate(row)"
+                    >
+                        [详情]
+                    </el-button>
+                    <el-button
+                        :type="row.status == 1 ? 'danger' : 'success'"
+                        link
+                        v-permission="'activation:bookActivationCodeRepository:openClose'"
+                        @click="handleChangeStatus(row)"
+                    >
+                        {{ row.status == 1 ? '[禁用]' : '[启用]' }}
+                    </el-button>
+                </div>
+            </template>
+        </common-table>
+
+        <warehouse-edit ref="editRef" @success="reload()" />
+    </ele-page>
+</template>
+
+<script setup>
+    import { ref, reactive } from 'vue';
+    import { PlusOutlined } from '@/components/icons';
+    import CommonTable from '@/components/CommonPage/CommonTable.vue';
+    import pageSearch from '@/views/data/warehouse/components/page-search.vue';
+    import warehouseEdit from '@/views/data/warehouse/components/warehouse-edit.vue';
+    import request from '@/utils/request';
+
+    defineOptions({ name: 'DataWarehouse' });
+
+    const status = ref('1');
+
+    function handleStatusChange(value) {
+        pageConfig.params = value ? { status: value } : {};
+        pageRef.value?.reload(value ? { status: value } : { status: undefined });
+    }
+
+    /** 表格列配置 */
+    const columns = ref([
+        {
+            label: '仓库名称',
+            prop: 'repositoryName',
+            align: 'center',
+            minWidth: 140
+        },
+        {
+            label: '联系人',
+            prop: 'contactsName',
+            align: 'center',
+            minWidth: 100
+        },
+        {
+            label: '手机号码',
+            prop: 'mobile',
+            align: 'center',
+            minWidth: 120
+        },
+        {
+            label: '详细地址',
+            prop: 'addressDetail',
+            align: 'center',
+            minWidth: 260,
+            formatter: (row) => {
+                const region = [row.provinceName, row.cityName, row.countyName]
+                    .filter(Boolean)
+                    .join('');
+                return `${region}${row.addressDetail || ''}` || '-';
+            }
+        },
+        {
+            label: '仓库状态',
+            prop: 'status',
+            align: 'center',
+            minWidth: 100,
+            slot: 'status'
+        },
+        {
+            columnKey: 'action',
+            label: '操作',
+            width: 220,
+            align: 'center',
+            slot: 'action'
+        }
+    ]);
+
+    const pageRef = ref(null);
+
+    const pageConfig = reactive({
+        pageUrl: '/activation/bookActivationCodeRepository/pagelist',
+        fileName: '仓库管理',
+        cacheKey: 'dataWarehouseTable',
+        params: { status: '1' }
+    });
+
+    function reload(where) {
+        const data = { ...(where || {}) };
+        if (status.value) {
+            data.status = status.value;
+            pageConfig.params = { status: status.value };
+        } else {
+            delete data.status;
+            pageConfig.params = {};
+        }
+        pageRef.value?.reload(data);
+    }
+
+    function handleChangeStatus(row) {
+        const nextStatus = row.status == 1 ? '2' : '1';
+        const message =
+            nextStatus === '2' ? '确认禁用该仓库?' : '确认启用该仓库?';
+        pageRef.value?.messageBoxConfirm({
+            message,
+            title: '禁用-启用',
+            fetch: () =>
+                request.post(
+                    '/activation/bookActivationCodeRepository/openClose',
+                    {
+                        id: row.id,
+                        status: nextStatus
+                    }
+                )
+        });
+    }
+
+    const editRef = ref(null);
+    function handleUpdate(row, optType) {
+        editRef.value?.handleOpen(row, optType);
+    }
+</script>