daShangDao_psiWebApp/技术文档.md
97694731 44ba8a631c
Some checks failed
CI / build (20.x) (push) Waiting to run
CI / lint (push) Waiting to run
CI / test (push) Waiting to run
CI / deploy-preview (push) Blocked by required conditions
CI / security (push) Waiting to run
CI / build (18.x) (push) Has been cancelled
1
2026-06-15 18:09:39 +08:00

1184 lines
60 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 图书进销存系统 — 技术文档
> **项目名称**Cards图书进销存管理后台
> **版本**1.0.0
> **最后更新**2026-06-15
> **项目路径**`D:\project\psi_web`
---
## 目录
1. [项目概述](#1-项目概述)
2. [技术栈](#2-技术栈)
3. [项目结构](#3-项目结构)
4. [构建与部署](#4-构建与部署)
5. [路由与权限](#5-路由与权限)
6. [API 架构](#6-api-架构)
7. [API 接口详表](#7-api-接口详表)
8. [状态管理](#8-状态管理)
9. [工具函数](#9-工具函数)
10. [外部服务集成](#10-外部服务集成)
11. [核心业务流程](#11-核心业务流程)
12. [打印服务](#12-打印服务)
13. [数据模型](#13-数据模型)
14. [已知问题与维护](#14-已知问题与维护)
---
## 1. 项目概述
本项目是一个面向图书行业的**进销存管理后台系统**,覆盖图书采购入库、库存管理、商品发布、销售出库、发货打单、盘库盘点等全链路业务流程。系统支持多角色(管理员/代理权限控制并集成了核价器、快递打印、OCR 识别、孔夫子旧书网登录等外部服务。
### 核心业务范围
| 模块 | 说明 |
|------|------|
| **波次管理** | 相机扫码 → 条码识别 → 书籍信息编辑 → 波次创建/释放 |
| **订单管理** | 采购订单 → 销售订单 → 出库单 → 发货单,全链路可追溯 |
| **库存管理** | 库存查询(商品维度/仓库维度)、变动记录、盘库盘点 |
| **商品管理** | 商品 CRUD、实拍图片管理、多店铺发布/重试、Excel 导入 |
| **店铺管理** | 多客户店铺(拼多多、孔夫子、闲鱼等)管理、授权状态 |
| **分账管理** | 分账规则配置、扣款日志查询与导出 |
| **快递管理** | 快递配置、物流模板、面单打印(拼多多/通用)、单号回填 |
| **小车管理** | 拣货小车 CRUD、小车-店铺关联 |
| **系统管理** | 代理账号、员工类型、核价器配置、物流模板、打印机管理、PDA 管理 |
---
## 2. 技术栈
### 2.1 前端框架与依赖
| 依赖 | 版本 | 用途 |
|------|------|------|
| `vue` | ^3.2.8 | 前端框架Composition API + `<script setup>` |
| `vue-router` | ^4.1.6 | 前端路由History 模式) |
| `pinia` | ^2.1.7 | 状态管理 |
| `element-plus` | ^2.13.7 | UI 组件库(中文 localesmall 尺寸zIndex 3000 |
| `@element-plus/icons-vue` | ^2.1.0 | 图标库(全局注册所有图标) |
| `axios` | ^1.7.9 | HTTP 客户端 |
| `crypto-js` | ^4.2.0 | 签名算法MD5/SHA256 |
| `json-bigint` | ^1.0.0 | 大整数精度处理(替换默认 JSON.parse |
| `@zxing/library` | ^0.21.3 | 条形码/二维码扫描 |
| `jsbarcode` | ^3.12.3 | 条码生成 |
| `qrcode` | ^1.5.4 | QR 码生成 |
| `@vue/compiler-sfc` | ^3.2.8 | SFC 编译器 |
| `sass-embedded` | ^1.99.0 | CSS 预处理器(嵌入版) |
### 2.2 工程化工具
| 工具 | 版本 | 用途 |
|------|------|------|
| `vite` | ^2.5.2 | 构建工具与开发服务器 |
| `@vitejs/plugin-vue` | ^1.6.0 | Vite Vue 插件 |
| `typescript` | ^5.5.4 | 类型检查(项目以 JS 为主,仅部分 .ts 文件) |
| `sass` | ^1.99.0 | CSS 预处理器dart-sass |
### 2.3 环境变量(`.env`
| 变量 | 值 | 说明 |
|------|-----|------|
| `VITE_APP_API_KEY` | `psi` | API 签名密钥 |
| `VITE_APP_CLIENT_ID` | `psi` | 客户端 ID |
| `VITE_APP_API_SECRET` | `psi_api_sign_secret` | 签名密钥 |
### 2.4 脚本命令
| 命令 | 说明 |
|------|------|
| `npm run dev` | 启动开发服务器,端口 5174 |
| `npm run build` | 生产构建,输出到 `dist/` |
| `npm run serve` | 预览生产构建(`vite preview` |
---
## 3. 项目结构
```
src/
├── api/ # API 层所有后端接口调用集中于此31 个文件)
│ ├── barcode.js # 条码生成
│ ├── book.js # 书籍信息查询/同步
│ ├── car.js # 小车管理CRUD + 小车-店铺关联)
│ ├── config.js # 核价器配置(直连外部 IP:Port非 request 封装)
│ ├── courierConfig.js # 快递配置createConfig 等,仅部分实现)
│ ├── dashboard.js # 仪表盘统计
│ ├── district.js # 行政区划(省/市/区)及运费模板
│ ├── employee.js # 员工/代理管理
│ ├── employeeType.js # 员工类型管理
│ ├── goodsInfo.js # 提交异常书目product_log/save
│ ├── submIllegalBook.js # 异常书目审核/审批product_log/audit
│ ├── reviewIllegalBook.js # 异常书目查询/删除product_log/list + delete
│ ├── inventory.js # 库存记录(汇总/明细/变动日志)
│ ├── location.js # 库位管理CRUD、批量生成、Excel 导入)
│ ├── login.js # 登录(管理员/代理)
│ ├── logistics.js # 物流模板 CRUD
│ ├── outbound.js # 出库管理CRUD、审核、导出、改库位
│ ├── print.js # 打印服务(重导出 Lodop 方法)
│ ├── product.js # 商品管理CRUD、图片、OCR、Excel 导入)
│ ├── purchase-order.js # 采购订单CRUD、带波次创建、波次释放
│ ├── releaseRecord.js # 发布记录(全 Mock 数据)
│ ├── sales-order.js # 销售订单CRUD、确认、取消、退货
│ ├── shop.js # 店铺管理(内部 API + 外部 API 双路径)
│ ├── shipping-order.js # 发货单(内部 CRUD + 外部快递接口)
│ ├── split.js # 分账配置(旧版,不完整,部分 stub
│ ├── splitAccount.js # 分账配置完整版JSON 请求体)
│ ├── splitDeductionLog.js # 分账扣款日志(查询/导出)
│ ├── stock-check.js # 盘库管理CRUD、盘点、调整、退货
│ ├── supplier.js # 供应商 CRUD
│ ├── warehouse.js # 仓库 CRUD
│ └── wave-task.js # 波次任务(任务列表、波次管理、波次释放)
├── components/ # 公共组件24 个文件)
│ ├── AdminLayout.vue # 主布局(侧边栏 + 顶部栏 + 内容区)
│ ├── goodsPop/index.vue # ISBN 悬停浮窗(书品信息预览)
│ ├── locationSelect/index.vue # 库位选择器组件
│ ├── dialog/submIllegalBook/ # 异常书目提交弹窗
│ ├── inventory/byGoods/ # 库存视图-按商品分组
│ ├── inventory/byLocation/ # 库存视图-按库位分组
│ ├── product/byGoods/ # 商品视图-按商品分组
│ ├── product/byLocation/ # 商品视图-按库位分组
│ ├── wareHouse/warehouseList.vue # 仓库列表组件
│ ├── wareHouse/locationManager.vue # 库位管理组件(有独立路由)
│ ├── wareHouse/regionData.js # 行政区划常量数据
│ └── wave/ # 波次相关子组件
│ ├── camera.vue # 相机扫码
│ ├── car.vue # 小车选择(含打印调试按钮)
│ ├── bookInfo.vue # 书籍信息展示
│ ├── goodsInfo.vue # 商品信息展示
│ ├── goosList.vue # 商品列表(注意文件名拼写 "goos"
│ ├── imageCut.vue # 图片裁剪工具
│ ├── ocrDialog.vue # OCR 识别对话框
│ ├── ocrResultDialog.vue # OCR 结果展示对话框
│ └── suitBookDialog.vue # 套装书对话框(含首次打印调试)
├── views/ # 页面级组件33 个页面目录)
│ ├── login/Login.vue # 登录页
│ ├── admin/ # 管理员页面
│ │ ├── Dashboard.vue # 仪表盘
│ │ ├── EmployeeList.vue # 代理管理
│ │ ├── EmployeeAdd.vue # 添加代理
│ │ └── employeeType.vue # 员工类型管理
│ ├── wave/Wave.vue # 波次管理主页面
│ ├── waveTask/WaveTask.vue # 波次任务列表
│ ├── warehouse/ # 仓库管理
│ │ ├── wareHouse.vue # 仓库列表
│ │ └── template.vue # 运费模板管理
│ ├── supplier/Supplier.vue # 供应商列表
│ ├── product/Product.vue # 商品列表
│ ├── purchase-order/ # 采购订单
│ │ └── PurchaseOrder.vue
│ ├── sales-order/ # 销售订单
│ │ ├── SalesOrder.vue # 销售订单
│ │ └── salesInfoList.vue # 销售单详情列表
│ ├── shipping-order/ # 发货单
│ │ ├── ShippingOrder.vue # 发货单管理
│ │ ├── orderList.vue # 发货单详情列表
│ │ └── unshippedOrderList.vue # 未发货订单列表
│ ├── outbound/Outbound.vue # 出库管理
│ ├── inventory/Inventory.vue # 库存记录
│ ├── stock-check/ # 盘库管理
│ │ └── StockCheck.vue
│ ├── shop/Shop.vue # 店铺管理
│ ├── car/Car.vue # 小车管理
│ ├── location/Location.vue # 库位列表
│ ├── config/config.vue # 核价器配置
│ ├── logistics/index.vue # 物流模板
│ ├── releaseRecord/ # 发布记录
│ ├── reviewIllegalBook/ # 异常书目审核
│ ├── printer-manager/ # 打印机管理
│ ├── shop-settings/ # 店铺设置
│ ├── sorting-settings/ # 分拣设置
│ ├── scanner/Scanner.vue # 扫码页
│ ├── pdaManage/ # PDA 管理
│ ├── courier/config.vue # 快递配置
│ └── split/ # 分账管理
│ ├── config/splitConfig.vue # 分账配置
│ └── log/splitLog.vue # 分账扣款日志
├── router/index.js # 路由配置History 模式 + 完整路由守卫)
├── store/ # 状态管理
│ ├── index.js # 旧版 storereactive 方式,逐步废弃)
│ └── user.js # Pinia 用户 store
├── utils/ # 工具函数
│ ├── request.js # Axios 封装拦截器、签名、Mock 系统)
│ ├── auth.js # Token/UserInfo 的 localStorage 读写
│ ├── sign.js # 签名工具MD5/SHA256参数排序展平
│ ├── mock.js # Mock 数据(商品、卡密、代理、订单)
│ ├── daYin/print.ts # Lodop/Clodop 打印服务封装TypeScript
│ ├── daYinPdd/index.ts # 拼多多打印服务
│ ├── pddUpload.ts # 拼多多图片上传
│ ├── clipboard.js # 剪贴板工具
│ └── ServiceManager.js # 服务管理器
├── App.vue # 根组件router-view + SEO footer
└── main.js # 入口文件(注册 Element Plus / Pinia / Router / Icons
```
---
## 4. 构建与部署
### 4.1 开发服务器
```bash
npm run dev
# → http://localhost:5174 host: 0.0.0.0
```
### 4.2 生产构建
```bash
npm run build
# → 输出到 dist/
```
### 4.3 Vite 配置要点(`vite.config.js`
| 配置项 | 值 | 说明 |
|--------|-----|------|
| **插件** | `@vitejs/plugin-vue` | Vue SFC 编译 |
| **代理** | `/api``https://psi.api.buzhiyushu.cn` | 主业务 APIchangeOrigin |
| **代理** | `/api/print``https://print.buzhiyushu.cn` | 打印服务changeOrigin |
| **别名** | `@``./src` | 模块路径别名 |
| **端口** | `5174` | 开发服务器端口 |
| **host** | `0.0.0.0` | 监听所有网络接口 |
| **historyApiFallback** | `true` | History 模式 fallback |
| **自定义中间件** | `/kongfz-login` | 孔夫子登录代理(避免跨域) |
| **SCSS** | `api: 'modern-compiler'` | SCSS 编译模式 |
| **Define** | `__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false'` | 关闭水合警告 |
### 4.4 孔夫子登录代理中间件
Vite 开发服务器注册了 `/kongfz-login` 自定义中间件:
- **方法**:仅 POST
- **流程**:接收 `loginName` + `loginPass` → POST 到 `login.kongfz.com/Pc/Login/account` → 跟随重定向 → 提取 `PHPSESSID` → GET `user.kongfz.com/User/Index/getUserInfo/` 获取昵称
- **返回**`{ code: 200, data: { token, username, nickname } }`
- **错误处理**HTTP 405非 POST`{ code: 405 }`400空参数`{ code: 400 }`500登录失败`{ code: 500, message }`
- **超时**15 秒
- **重定向限制**:最多跟随 5 次重定向
- **User-Agent**`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36`
### 4.5 生产环境 API 地址
- **基础 URL**`https://psi.api.buzhiyushu.cn/api`
- 通过环境变量 `VITE_API_BASE` 可覆盖
---
## 5. 路由与权限
### 5.1 路由模式
`createWebHistory()` — History 模式URL 格式为 `http://host:port/path`
> ⚠️ 注意:本项目的 router 使用 History 模式,而非 Hash 模式。不支持 IE 等旧浏览器。
### 5.2 路由守卫逻辑
`router.beforeEach` 守卫依次执行:
1. **URL 参数登录(嵌入模式)**:检测 `window.location.search` 中的 `token` 参数
- 自动写入 `localStorage.admin_token`
- 设置 `localStorage.embed_mode = 'true'`(隐藏导航栏)
- 解析 JWT Payloadbase64提取 `id`、`username`、`role`、`about_id`
- 用于跨系统嵌入(如从其他系统以 iframe 嵌入本系统)
2. **标题设置**`document.title = "{meta.title} - 进销存系统"`
3. **登录页处理**:已登录用户自动跳转 `/dashboard`
4. **仪表盘鉴权**:无 `adminUserInfo` 跳转 `/login`
5. **黑名单模式鉴权**
- 所有非公开页面(未标记 `meta.isPublic`)均需登录
- `requiresAdmin: true``role !== 255` 跳转 `/dashboard`
- 未登录跳转 `/login`(携带 `redirect` 参数可回跳)
### 5.3 完整路由表
| 路径 | Name | 组件 | 标题 | 权限 | 隐藏 |
|------|------|------|------|------|------|
| `/login` | `Login` | `login.vue` | 登录 | 公开 | — |
| `/` | — | `AdminLayout.vue`(布局) | — | — | 重定向到 `/dashboard` |
| `/dashboard` | `Dashboard` | `dashboard.vue` | 仪表盘 | 需认证 | — |
| `/admin/employees` | `EmployeeList` | `employeeList.vue` | 代理管理 | 管理员 | — |
| `/admin/employees/add` | `EmployeeAdd` | `employeeAdd.vue` | 添加代理 | 管理员 | — |
| `/admin/employee-type` | `EmployeeType` | `employeeType.vue` | 员工类型管理 | 管理员 | — |
| `/wave` | `wave` | `wave.vue` | 波次管理 | 需认证 | — |
| `/wave-task` | `wave-task` | `waveTask.vue` | 波次任务 | 需认证 | — |
| `/warehouse` | `warehouse` | `wareHouse.vue` | 仓库列表 | 需认证 | — |
| `/warehouse/template` | `WarehouseTemplate` | `template.vue` | 运费模板管理 | 需认证 | — |
| `/location-manager` | `location-manager` | `locationManager.vue` | 库位管理 | 需认证 | ✅ 隐藏 |
| `/location` | `location` | `location.vue` | 库位列表 | 需认证 | — |
| `/supplier` | `supplier` | `supplier.vue` | 供应商管理 | 需认证 | — |
| `/product` | `product` | `product.vue` | 商品管理 | 需认证 | — |
| `/purchase-order` | `purchase-order` | `purchaseOrder.vue` | 采购订单 | 需认证 | — |
| `/sales-order` | `sales-order` | `salesOrder.vue` | 销售订单 | 需认证 | — |
| `/sales-order-info-list` | `SalesOrderInfoList` | `salesInfoList.vue` | 销售单详情列表 | 需认证 | — |
| `/outbound` | `outbound` | `outbound.vue` | 出库管理 | 需认证 | — |
| `/shipping-order` | `shipping-order` | `shippingOrder.vue` | 发货单 | 需认证 | — |
| `/shipping-order-list` | `ShippingOrderList` | `orderList.vue` | 发货单详情列表 | 需认证 | — |
| `/inventory` | `inventory` | `inventory.vue` | 库存记录 | 需认证 | — |
| `/stock-check` | `stock-check` | `stockCheck.vue` | 盘库管理 | 需认证 | — |
| `/release-record` | `release-record` | `releaseRecord.vue` | 发布记录 | 需认证 | — |
| `/shop` | `shop` | `shop.vue` | 店铺管理 | 需认证 | — |
| `/shop-settings` | `shop-settings` | `shopSettings.vue` | 店铺设置 | 需认证 | — |
| `/sorting-settings` | `sorting-settings` | `sortingSettings.vue` | 分拣设置 | 需认证 | — |
| `/car` | `car` | `car.vue` | 小车管理 | 需认证 | — |
| `/printer-manager` | `printer-manager` | `printerManager.vue` | 打印机管理 | 需认证 | — |
| `/review-illegal-book` | `reviewIllegalBook` | `reviewIllegalBook.vue` | 异常书目审核 | 管理员 | — |
| `/testUrl` | `testUrl` | `config.vue` | 核价器配置 | 管理员 | — |
| `/split-config` | `split-config` | `splitConfig.vue` | 分账配置 | 需认证 | — |
| `/split-log` | `split-log` | `splitLog.vue` | 分账扣款日志 | 需认证 | — |
| `/courier-config` | `courier-config` | `config.vue`courier | 快递配置 | 需认证 | — |
| `/pdaManage` | `pdaManage` | `pdaManage.vue` | PDA 管理 | 需认证 | ✅ 隐藏 |
> **隐藏路由**`hidden: true` 的路由不会在侧边栏菜单中显示,但可通过 URL 直接访问。
### 5.4 角色权限
| role 值 | 身份 | 说明 |
|---------|------|------|
| `255` | 管理员 | 全部功能可用,包括代理管理、员工类型、核价器配置、异常审核 |
| `128` | 代理 | 仪表盘、波次、仓库、商品等基础功能 |
---
## 6. API 架构
### 6.1 网络拓扑
```
浏览器 ──→ Vite Dev Server (:5174)
├── /api/* ──→ Proxy ──→ https://psi.api.buzhiyushu.cn (主业务 API
├── /api/print/* ──→ Proxy ──→ https://print.buzhiyushu.cn (打印服务)
├── /kongfz-login ──→ Node.js 中间件 ──→ login.kongfz.com (孔夫子登录)
└── axios 直连 ──→ 核价器 http://{ip}:{port} (用户配置)
──→ https://api.buzhiyushu.cn (店铺列表/快递回填)
──→ https://print.buzhiyushu.cn (快递面单打印)
──→ https://new.taskpool.buzhiyushu.cn:3500 (店铺详情)
```
### 6.2 请求封装(`src/utils/request.js`
**创建 Axios 实例:**
- 开发环境 baseURL`/api`(通过 Vite 代理)
- 生产环境 baseURL`https://psi.api.buzhiyushu.cn/api`(可通过 `VITE_API_BASE` 覆盖)
- 超时10 秒
- 使用 `json-bigint` 替换默认 `JSON.parse``transformResponse`),防止后端 19 位大整数精度丢失
**请求拦截器流程:**
```
请求发起
├── 注入 Bearer Token从 localStorage.admin_token 读取)
├── 签名处理
│ ├── 检查 X-Need-Sign 头('false' 则跳过签名)
│ ├── GET 请求 → 对 params 签名
│ ├── POST/PUT/DELETE 请求 → 对 body 签名(先 FormData→Object 再签名)
│ └── 签名后自动注入 app_key、client_id、timestamp、sign_method
└── POST/PUT/DELETE 请求 → 自动对象转 FormDataobjectToFormData
└── 删除 Content-Type 头(让浏览器自动设置为 multipart/form-data
```
**响应拦截器流程:**
```
响应返回
├── Mock 模式USE_MOCK=true→ 匹配 mockApiMap 返回模拟数据
├── HTTP 204 → "请求无响应数据" 错误提示
├── code=200 → 返回 response.data
├── code≠200 → 统一 ElMessage 错误提示code 204 特殊处理)
├── HTTP 401 → 清除对应 token区分 admin/user 端)→ 跳转登录页
├── HTTP 400 → 显示后端返回的错误消息
├── HTTP 403 → "没有权限访问"
├── HTTP 404 → "请求的资源不存在"
├── HTTP 500 → "服务器错误"
└── 网络错误 → "网络错误,请检查网络连接"
```
**objectToFormData 递归转换:**
- 支持嵌套对象 → `parentKey[key]`
- 支持数组 → `parentKey[0]`, `parentKey[1]`
- 支持 File 类型直接附加
- 跳过 `null`/`undefined` 值
**Mock 系统:**
- `USE_MOCK = false`(默认关闭)
- 支持的路由:`/products`、`/admin/cards/*`、`/admin/employee/*`、`/orders`、`/access/*`、`/cards/buy/*`
- 启用后 GET 请求自动匹配 `mockApiMap`,支持路径参数解析
### 6.3 签名机制(`src/utils/sign.js`
`SignUtil` 类(单例导出),配置来自环境变量:
```javascript
{
appKey: 'psi',
clientId: 'psi',
appSecret: 'psi_api_sign_secret'
}
```
**核心方法:**
| 方法 | 说明 |
|------|------|
| `generateSign(params)` | 完整签名流程:过滤空值 → 注入系统参数 → 展平 → 排序 → 计算 MD5 → 返回含 sign 的 params 对象 |
| `generateSignedUrl(baseUrl, params)` | 生成带签名参数的完整 URL |
| `calculateSign(params)` | 核心哈希:拼接 `appSecret + key=value&... + appSecret`MD5 签名后转为大写 |
| `buildParamEntries(params, prefix)` | 递归展平嵌套对象/数组为 `key=value` 条目(使用 `[key]` 下标表示法) |
| `sortKeys(a, b)` | 智能键排序:纯数字段数值比较,混合段分段字母+数字排序 |
**系统自动注入参数:**
| 参数 | 值 |
|------|-----|
| `app_key` | `VITE_APP_API_KEYpsi` |
| `client_id` | `VITE_APP_CLIENT_IDpsi` |
| `timestamp` | 当前时间戳(毫秒) |
| `sign_method` | `md5` |
**跳过签名:** 在请求头中设置 `X-Need-Sign: false`(如 OCR 图片上传、部分外部服务调用)
### 6.4 列表接口标准化
约 15 个 API 模块实现了 `normalizeListResponse()` 工具函数,将后端可能返回的各种列表格式统一为 `{ list: Array, total: number }`
```javascript
const normalizeListResponse = (payload) => {
const data = payload?.data
if (!data) return { list: [], total: 0 }
if (Array.isArray(data)) return { list: data, total: data.length }
return {
list: Array.isArray(data.list) ? data.list : [],
total: typeof data.total === 'number' ? data.total : (data.list?.length || 0)
}
}
```
> ⚠️ 注意:此函数在每个 API 文件中独立定义,存在代码重复,建议未来抽离为公共工具。
### 6.5 大整数精度
使用 `json-bigint` 库替换 `JSON.parse`,在后端返回 JSON 中的 19 位大整数(如订单号、店铺 ID时保持精度。注意
- `json-bigint` 返回的是 `BigNumber` 对象,使用 `.toString()` 获取字符串值
- 部分比较操作可能需要特殊处理
---
## 7. API 接口详表
### 7.1 登录与认证
#### `src/api/login.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `login(type, formData)` | POST | `/login/:role` | `type` = `'admin'`→255/ `'employee'`→128`formData` = `{ username, password }` | 管理员/代理登录,自动注入 `type: 1` |
#### `src/utils/auth.js`
| 函数 | 说明 |
|------|------|
| `logout()` | 清除 `admin_token``admin_userInfo` |
| `setAdminToken(token)` / `getAdminToken()` / `removeAdminToken()` | 管理端 Token 存取 |
| `setAdminUserInfo(userInfo)` / `getAdminUserInfo()` / `removeAdminUserInfo()` | 管理端用户信息存取 |
#### `src/utils/request.js`(额外导出)
| 函数 | 说明 |
|------|------|
| `fetchCurrentUser()` | GET `/user/current`,获取当前用户信息并写入 localStorage |
### 7.2 仪表盘
#### `src/api/dashboard.js`
| 函数 | 方法 | 端点 | 参数 | 返回 | 说明 |
|------|------|------|------|------|------|
| `fetchTodayStats()` | GET | `/dashboard/statist` | 无 | `{ today_inbound, today_outbound, today_sales }` | 今日入库/出库/销售统计 |
| `fetchEmployeeStats()` | GET | `/dashboard/statist` | 无 | `[{ employee_id, employee_name, inbound_count, outbound_count }]` | 各员工今日工作量统计 |
### 7.3 书籍与条码
#### `src/api/book.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `getBookInfo(isbn)` | GET | `/getBookInfo` | `isbn`ISBN 号) | 根据 ISBN 查询书籍信息 |
| `syncBook(params)` | POST | `/syncBook` | `book_name, author, publisher, publication_time, binding_layout, fix_price, isbn, page_count, word_count, book_format, fid, f_isbn, f_book_name, live_image[0], type` | 同步/保存书籍信息JSON 体) |
#### `src/api/barcode.js`
| 函数 | 方法 | 端点 | 参数 | 返回 | 说明 |
|------|------|------|------|------|------|
| `generateBarcode(content)` | POST | `/barcode/generate` | `content`(条码内容) | `{ data: { image_base64 } }` | 生成条码图片 Base64FormData |
### 7.4 商品管理
#### `src/api/product.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchProductList({ keyword, status, shop_id, page, pageSize })` | GET | `/product/list` | 关键词、状态、店铺筛选、分页 | 商品分页列表 |
| `fetchProductListByShop({ shop_id, page, pageSize })` | GET | `/product/list` | 委托 `fetchProductList` | 按店铺筛选商品 |
| `fetchProductDetail(id)` | GET | `/product/detail` | `id` | 商品详情 |
| `createProduct(product)` | POST | `/product/save` | 商品对象 | 创建商品 |
| `saveProduct(product)` | POST | `/product/save` | 含 `name, barcode, price, live_image` | 保存商品(兼容旧接口) |
| `updateProduct(product)` | POST | `/product/save` | 含 `id` | 更新商品(同创建端点) |
| `deleteProduct(product)` | POST | `/product/delete` | 含 `id` | 删除商品 |
| `updateProductLiveImage(id, pictureName, extra)` | POST | `/product/save` | 图片名 + 额外字段 | 更新实拍图Shxy URL |
| `updateProductLiveImageAsPddUrl(id, picUrl, extra)` | POST | `/product/save` | 完整 URL | 用 PDD URL 更新实拍图 |
| `updateProductLiveImageForTest(id, pictureName, extra)` | POST | `/product/save` | 测试用 | 硬编码测试 URL 更新实拍图 |
| `getSuitBook(isbn)` | GET | `/getSuitBook` | `isbn` | 查询套装书 |
| `getProCode({ book_name, author, publisher })` | GET | `/getProCode` | 书名/作者/出版社 | 获取产品编码OCR 模式用) |
| `ocrImage(imageBlob)` | POST | `/ocr` | `imageBlob`(图片 Blob | OCR 识别(跳过签名) |
| `retryProductPublish(productId, shopId, shopType)` | POST | `/product/retry-out-task` | 商品/店铺/类型 | 重试商品发布 |
| `fetchLatestProducts(limit, start, end)` | GET | `/product/list` | 数量限制、时间范围 | 最新商品(仪表盘用) |
| `fetchShopProducts(shopId, page, pageSize)` | GET | `/product/shop-detail` | 店铺 ID 分页 | 店铺商品列表含发布统计 |
| `getBookDetails(id)` | GET | `/product/detail` | `id` | 书籍详情(`fetchProductDetail` 别名) |
| `importProductsByExcel({ userId, warehouse_id, file })` | POST | `/goods/import-from-excel` | 用户/仓库/Excel 文件 | 从 Excel 导入商品 |
### 7.5 店铺管理
#### `src/api/shop.js`
内部 + 外部双路径。外部 API 使用独立 axios 实例 + JSONbig。
| 函数 | 方法 | 端点 | 说明 |
|------|------|------|------|
| `fetchShopList({ keyword, page, pageSize })` | GET | `https://api.buzhiyushu.cn/zhishu/shop/list`(外部) | 从外部 API 获取店铺列表(小车用) |
| `getShopList({ keyword, shop_type, page, pageSize })` | GET | `/shop/list`(内部代理) | 从本地后端获取店铺列表 |
| `fetchShopDetail(id)` | GET | `https://new.taskpool.buzhiyushu.cn:3500/api/shop/get/:id`(外部) | 从外部 API 获取店铺详情 |
| `createShop(shop)` | POST | `/shop/create` | 创建店铺 |
| `updateShop(shop)` | PUT | `/shop/update` | 更新店铺 |
| `deleteShop(shop)` | POST | `/shop/delete` | 删除店铺 |
| `updateShopAuthorize({ id, shop_authorize })` | POST | `/shop/authorize` | 更新授权状态0=未授权 1=已授权 2=已过期) |
| `updateShopStatus({ id, status })` | POST | `/shop/status` | 更新状态0=正常 1=禁用) |
| `getAllShopList()` | GET | `/shop/list` | 获取所有店铺(不分页) |
> 外部 API 使用约 `about_id` 来自 `adminUserInfo`。如果 `about_id == 0` 则替换为 1。
### 7.6 供应商管理
#### `src/api/supplier.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchSupplierList({ keyword, status, page, pageSize })` | GET | `/supplier/list` | 关键词(编码/名称)、状态、分页 | 供应商分页列表 |
| `fetchSupplierDetail(id)` | GET | `/supplier/detail/:id` | ID | 供应商详情 |
| `createSupplier(supplier)` | POST | `/supplier/create` | 供应商对象 | 创建供应商 |
| `updateSupplier(supplier)` | PUT | `/supplier/update` | 含 `id` | 更新供应商 |
| `deleteSupplier(supplier)` | POST | `/supplier/delete` | 含 `id` | 删除供应商 |
### 7.7 仓库管理
#### `src/api/warehouse.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchWarehouseList({ keyword, type, status, page, pageSize })` | GET | `/warehouse/list` | 编码/名称、类型、状态、分页 | 仓库分页列表 |
| `fetchWarehousesByIds({ ids, page, pageSize })` | GET | `/warehouse/list` | `ids[]` 数组 | 按 ID 批量查询 |
| `fetchWarehouseDetail(id)` | GET | `/warehouse/detail/:id` | ID | 仓库详情 |
| `createWarehouse(warehouse)` | POST | `/warehouse/create` | 仓库对象 | 创建仓库 |
| `updateWarehouse(warehouse)` | PUT | `/warehouse/update` | 含 `id` | 更新仓库 |
| `deleteWarehouse(warehouse)` | POST | `/warehouse/delete` | 含 `id` | 删除仓库 |
### 7.8 库位管理
#### `src/api/location.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchLocationList({ keyword, warehouseId, type, status, page, pageSize })` | GET | `/location/list` | 编码code、仓库、类型、状态、分页 | 库位分页列表 |
| `fetchLocationDetail(id)` | GET | `/location/detail/:id` | ID | 库位详情 |
| `createLocation(location)` | POST | `/location/create` | 库位对象 | 创建库位 |
| `updateLocation(location)` | PUT | `/location/update` | 含 `id` | 更新库位 |
| `deleteLocation(location)` | POST | `/location/delete` | `ids[]`FormData | 批量删除库位 |
| `batchUpdateLocations(payload)` | PUT | `/location/update` | `ids[] + { code, type, capacity, status }`FormData | 批量更新库位属性 |
| `batchGenerateLocations(payload)` | POST | `/location/batch-generate` | 仓库/类型/容量/状态/生成规则 | 按规则批量生成库位 |
| `importLocationsFromExcel({ user_id, warehouse_id, file })` | POST | `/location/import-from-excel` | 用户/仓库/Excel 文件FormData | 从 Excel 导入库位 |
| `fetchAllLocations({ page, pageSize, keyword })` | GET | `/location/all-list` | 分页、关键词 | 跨仓库全库位列表 |
### 7.9 小车管理
#### `src/api/car.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchCarList({ keyword, page, pageSize })` | GET | `/car/list` | 编码/名称、分页 | 小车分页列表 |
| `fetchCarDetail(id)` | GET | `/car/detail/:id` | ID | 小车详情 |
| `createCar(carData)` | POST | `/car/create` | FormData | 创建小车 |
| `updateCar(carData)` | PUT | `/car/update` | FormData`id` | 更新小车 |
| `deleteCar(id)` | DELETE | `/car/delete` | FormData`id` | 删除小车 |
| `createCarShop(carShop)` | POST | `/car/shop/create` | 关联数据 | 创建小车-店铺关联 |
| `deleteCarShop(carShop)` | POST | `/car/shop/delete` | 含 `id` | 删除小车-店铺关联 |
### 7.10 波次管理
#### `src/api/waveTask.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchWaveTaskList(params)` | GET | `/wave/task/list` | `task_no, wave_no, page, page_size` | 波次任务列表 |
| `fetchWaveTaskDetail(id)` | GET | `/wave/task/detail` | `id` | 波次任务详情 |
| `deleteWaveTask(data)` | POST | `/delete` | 含 `id` | 删除波次任务 |
| `fetchWaveById(id)` | GET | `/wave/task/detail` | `id` | 按 ID 查波次信息 |
| `fetchWaveTaskByNo(waveNo)` | GET | (两步)先 `/wave/task/list``/wave/task/detail` | 波次号 | 按波次号查任务 |
| `fetchWaveList(params)` | GET | `/wave/list` | `keyword, status, warehouse_id, page, page_size` | 波次列表(选择器用) |
| `createWaveOutbound(outboundOrderId)` | POST | `/wave/outbound/create` | FormData | 创建波次出库 |
| `createWaveOutboundRelease(relatedOrderId, waveId)` | POST | `/wave/outbound/release` | FormData | 释放波次出库 |
| `getWaveStatusById(id)` | GET | `/wave/getWaveStatusById` | `id` | 查询波次状态 |
### 7.11 采购订单
#### `src/api/purchaseOrder.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchPurchaseOrderList({ keyword, status, page, pageSize, sort_by, sort_order })` | GET | `/purchase-order/list` | 单号(po_no)、状态、分页、排序 | 采购单列表 |
| `fetchPurchaseOrderDetail(id)` | GET | `/purchase-order/detail` | `id` | 采购单详情 |
| `createPurchaseOrder(data)` | POST | `/purchase-order/save` | 采购单数据 | 创建采购单 |
| `updatePurchaseOrder(data)` | POST | `/purchase-order/save` | 含 `id` | 更新采购单 |
| `deletePurchaseOrder(data)` | POST | `/purchase-order/delete` | 含 `id` | 删除采购单 |
| `createPurchaseOrderWithWave(data)` | POST | `/purchase-order/create-with-wave` | `warehouse_id, supplier_id, expected_arrival_date, remark, items[], direction` | 创建采购单并生成波次(步骤 1/2 |
| `releaseWave(data)` | POST | `/wave/release` | `wave_id, related_order_id, items[], car_id, car_code` | 释放波次(步骤 2/2 |
### 7.12 销售订单
#### `src/api/salesOrder.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchSalesOrderList({ keyword, status, customer_id, warehouse_id, page, pageSize, sort_by, sort_order })` | GET | `/sales-order/list` | 单号(so_no)、状态、平台、仓库、分页、排序 | 销售单列表 |
| `fetchSalesOrderDetail(id)` | GET | `/sales-order/detail` | `id` | 销售单详情(含明细) |
| `createSalesOrder(order)` | POST | `/sales-order/create` | 含 `so_no, customer_id, warehouse_id, order_date, required_delivery_date, items[]` | 创建销售单 |
| `updateSalesOrder(order)` | PUT | `/sales-order/update` | 含 `id` | 更新销售单 |
| `deleteSalesOrder(id)` | POST | `/sales-order/delete` | `id` | 删除销售单 |
| `confirmSalesOrder(id)` | POST | `/sales-order/confirm` | `id` | 确认销售单 |
| `cancelSalesOrder(id, remark)` | POST | `/sales-order/cancel` | `id, remark` | 取消销售单 |
| `returnSalesOrderItem(data)` | POST | `/stock_check/return` | `sales_order_id, sales_order_item_id, remark` | 销售单退货 |
| `fetchSalesOrderDetails({ page, pageSize })` | GET | `/sales-order/detaillist` | 分页 | 销售单明细汇总列表 |
### 7.13 出库管理
#### `src/api/outbound.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchOutboundList({ out_no, status, warehouse_id, customer_id, sales_order_id, start_date, end_date, page, page_size })` | GET | `/outbound-order/list` | 多种筛选条件 | 出库单分页列表 |
| `fetchOutboundDetail(id)` | GET | `/outbound-order/detail` | `id` | 出库单详情(含明细) |
| `createOutbound(outbound)` | POST | `/outbound-order/create` | `warehouse_id, customer_id, shipping_date, items[]` | 创建出库单 |
| `updateOutbound(outbound)` | PUT | `/outbound-order/update` | 含 `id` | 更新出库单 |
| `deleteOutbound(outbound)` | POST | `/outbound-order/delete` | 含 `id` | 删除出库单 |
| `approveOutbound(id, status, remark)` | POST | `/outbound-order/approve` | ID + 审核状态 + 备注 | 审核出库单 |
| `exportOutbound(id)` | GET | `/outbound-order/export/:id` | IDblob 响应) | 导出出库单 |
| `changeLocation(out_order_item_id, remark)` | POST | `/outbound-order/change-location` | 明细 ID + 备注FormData | 改库位 |
| `createOutboundFromSalesOrder(salesOrderIds, total, remark)` | POST | `/outbound-order/create` | 销售单 IDs + 总数FormData | 从销售单创建出库单 |
### 7.14 发货单
#### `src/api/shippingOrder.js`
内部 + 外部双路径。
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchShippingOrderList(...)` | GET | `/shipping-order/list` | `check_no, status, customer_id, warehouse_id, sales_order_id, wave_task_id, page, pageSize` | 发货单列表 |
| `fetchShippingOrderDetailList(...)` | GET | `/shipping-order/detaillist` | `status(1=待发货 2=已发货 3=已签收 4=已取消), page, pageSize, customer_id, shipping_no, start_date, end_date` | 发货单明细列表(新接口,支持时间范围) |
| `fetchShippingOrderDetail(id)` | GET | `/shipping-order/detail` | `id` | 发货单详情 |
| `createShippingOrder({ total, outbound_order_ids })` | POST | `/shipping-order/create` | 出库单 IDsFormData | 从出库单生成发货单 |
| `updateShippingOrderLogistics(...)` | POST | `/shipping-order/update` | `shipping_order_id, sales_order_item_id, logistics_company, logistics_no`FormData | 更新物流信息 |
| `recycleWaybillNo({ user_id, logistics_no })` | POST | `/logistics/cancel` | 用户 ID + 运单号FormData | 回收/取消运单 |
**外部快递接口:**
| 函数 | 方法 | 端点 | 说明 |
|------|------|------|------|
| `fetchFastMailList(shopId)` | GET | `https://api.buzhiyushu.cn/zhishu/fastMail/listApi?shopId=` | 获取快递账号列表 |
| `createOrderBatch(params)` | POST | `https://print.buzhiyushu.cn/api/print/createOrderBatch` | 创建快递面单FormData |
| `createBmOrderDaYin(params)` | GET | `https://print.buzhiyushu.cn/api/print/createBmOrderDaYin` | 获取打印面单信息 |
| `submitCompanyOrder(params)` | POST | `https://api.buzhiyushu.cn/zhishu/orderExternalGoods/submitCompanyOrder` | 回填快递单号JSON |
| `fetchExpressInfo(waybillNo)` | GET | `https://print.buzhiyushu.cn/api/print/printViewByWaybillNo` | 获取快递信息 |
### 7.15 库存管理
#### `src/api/inventory.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchInventoryList(params)` | GET | `/inventory/list` | `page, page_size, product_id, warehouse_id` | 库存汇总(按商品+仓库聚合,含总量/锁定/可用) |
| `fetchInventoryDetailList(params)` | GET | `/inventory/detail` | `product_id`(必填) | 库存明细(按仓库+库位+批次) |
| `fetchInventoryLogList(params)` | GET | `/inventory/log/list` | `page, page_size, isbn, book_name, warehouse_id, change_type, related_order_no, start_date, end_date` | 库存变动记录 |
| `fetchInventoryStats(warehouse_id)` | — | — | — | **桩函数**,返回 null |
| `fetchGoodsListByLocation({ keyword, page, pageSize })` | GET | `/inventory/grouped-list` | 库位编码/商品名/条码 | 按库位分组的库存 |
| `inventorySummary()` | GET | `/inventory/summary` | 无 | 库存汇总统计 |
### 7.16 盘库管理
#### `src/api/stockCheck.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchStockCheckList(...)` | GET | `/inventory/stock-check/list` | `keyword, warehouse_id, status(1=待盘点 2=盘点中 3=已完成 4=已取消), check_type(1=全盘 2=抽盘), page, pageSize, sort_by, sort_order` | 盘库单列表 |
| `fetchStockCheckDetail(id)` | GET | `/inventory/stock-check/:id` | ID | 盘库单详情(含明细) |
| `fetchStockCheckItems(stock_check_id, ...)` | GET | `/:id/items` | 盘库单 ID、关键词、状态(1=待盘点 2=已盘点) | 盘库明细列表 |
| `createStockCheck(data)` | POST | `.../create` | `warehouse_id, check_type, remark, items[]` | 创建盘库单 |
| `startStockCheck(id)` | POST | `.../:id/start` | ID | 开始盘点 |
| `submitCheckItem(stock_check_id, data)` | POST | `.../:id/check` | `item_id, actual_quantity, remark` | 提交单项盘点结果 |
| `batchSubmitCheckItems(stock_check_id, data)` | POST | `.../:id/check/batch` | `items: [{ item_id, actual_quantity, remark }]` | 批量提交盘点结果 |
| `completeStockCheck(id)` | POST | `.../:id/complete` | ID | 完成盘点 |
| `cancelStockCheck(id, reason)` | POST | `.../:id/cancel` | ID + 原因 | 取消盘库单 |
| `deleteStockCheck(id)` | DELETE | `.../:id` | ID | 删除盘库单(仅待盘点状态) |
| `adjustStockCheck(data)` | POST | `/stock-check/adjust` | `warehouse_id, product_id, location_id, batch_no, quantity, adjust_type(1=加 2=减), remark`FormData | 库存调整 |
| `returnStockCheck(data)` | POST | `/stock-check/return` | 同上FormData | 库存退货 |
### 7.17 物流/运费模板
#### `src/api/logistics.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchLogisticsList(params)` | GET | `/logistics/list` | 查询参数 | 物流模板列表 |
| `fetchLogisticsDetail(id)` | GET | `/logistics/detail/:id` | ID | 模板详情 |
| `createLogistics(data)` | POST | `/logistics/create` | 模板数据FormData | 创建物流模板 |
| `updateLogistics(data)` | PUT | `/logistics/update` | 含 `id` | 更新物流模板 |
| `deleteLogistics(ids)` | POST | `/logistics/delete` | 单个 ID 或 ID 数组FormData | 删除模板 |
#### `src/api/district.js`
| 函数 | 方法 | 端点 | 说明 |
|------|------|------|------|
| `fetchProvinces()` | GET | `/district/province/list` | 获取省份列表 |
| `fetchCitiesByProvinceId(provinceId)` | GET | `/district/city/list/:id` | 根据省份获取城市 |
| `fetchDistrictsByCityId(cityId)` | GET | `/district/area/list/:id` | 根据城市获取区县 |
| `fetchFreightInfo(id)` | GET | `/district/freight/info/:id` | 获取运费模板信息(含发货地址) |
| `createFreightTemplate(data)` | POST | `/district/freight/create` | 创建运费模板 |
| `updateFreightTemplate(data)` | PUT | `/district/freight/update` | 更新运费模板FormData |
### 7.18 代理/员工管理
#### `src/api/employee.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchEmployeeList(params)` | GET | `/admin/employee/list` | `page, page_size, status, keyword` | 代理列表 |
| `addEmployee({ name, password, phone, fid, about_id })` | POST | `/admin/employee/add` | 姓名/密码/电话/上级 ID | 添加代理 |
| `topupEmployee(employeeId, { amount, remark })` | POST | `/admin/employee/topup/:employeeId` | 金额 + 备注 | 代理充值 |
| `deductEmployee(employeeId, { amount, remark })` | POST | `/admin/employee/deduct/:employeeId` | 金额 + 备注 | 代理扣款 |
| `toggleEmployeeStatus(action, employeeId)` | POST | `/admin/employee/:action/:employeeId` | `enable``disable` | 启用/禁用代理 |
#### `src/api/employeeType.js`
| 函数 | 方法 | 端点 | 说明 |
|------|------|------|------|
| `fetchEmployeeTypeList()` | GET | `/admin/user-type/list` | 员工类型列表 |
| `updataEmployeInfo(formdata)` | PUT | `/api/admin/user-type/update` | ⚠️ 函数名拼写错误(应为 update更新员工类型信息 |
### 7.19 异常书目管理
#### 提交异常书目:`src/api/goodsInfo.js`
| 函数 | 方法 | 端点 | 说明 |
|------|------|------|------|
| `saveAbnormalBook(data)` | POST | `/product_log/save` | 提交异常书目记录FormDatamultipart/form-data含新旧字段对比书名、作者、出版社、出版时间、价格、装帧、页码、字数、是否套装、实拍图 |
#### 异常书目审核:`src/api/submIllegalBook.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `auditProductLog(id, status, newFields)` | POST | `/product_log/audit` | `status`: 1=通过 2=驳回;`newFields`: 通过时更新的字段 | 审核异常书目FormData |
#### 异常书目查询/删除:`src/api/reviewIllegalBook.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchProductLogList(params)` | GET | `/product_log/list` | `barcode, status, page, page_size` | 异常书目列表 |
| `deleteProductLog(id)` | POST | `/product_log/delete` | IDFormData | 删除异常书目 |
### 7.20 分账管理
#### 分账配置(完整版):`src/api/splitAccount.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchSplitConfigList({ rule_name, status, page, pageSize })` | GET | `/split-account-config/list` | 规则名、状态、分页 | 分账配置列表 |
| `fetchSplitConfigDetail(id)` | GET | `/split-account-config/detail` | `id` | 配置详情 |
| `createSplitConfig({ rule_name, rule_value, status, description })` | POST | `/split-account-config/create` | 规则名、规则值(JSON)、状态、描述 | 创建分账配置JSON 体) |
| `updateSplitConfig(data)` | POST | `/split-account-config/update` | 含 `id` | 更新分账配置 |
| `deleteSplitConfig(id)` | POST | `/split-account-config/delete` | `id` | 删除分账配置 |
| `toggleSplitConfigStatus(id, status)` | POST | `/split-account-config/update` | `status`: 0=禁用 1=启用 | 启用/禁用配置 |
#### ⚠️ 旧版分账(不完整):`src/api/split.js`
`createSplitConfig`(使用 URLSearchParams非 JSON+ `updateSplitConfig`(空函数体)。建议使用 `splitAccount.js`
#### 分账扣款日志:`src/api/splitDeductionLog.js`
| 函数 | 方法 | 端点 | 参数 | 说明 |
|------|------|------|------|------|
| `fetchSplitLogList(...)` | GET | `/split-account-deduction-log/list` | `business_no, config_name, created_by, start_time, end_time, page, pageSize` | 扣款日志列表 |
| `fetchSplitLogDetail(id)` | GET | `/split-account-deduction-log/detail` | `id` | 日志详情 |
| `exportSplitLog(params)` | GET | `/split-account-deduction-log/export` | 同列表筛选参数blob 响应) | 导出日志 |
### 7.21 快递配置
#### `src/api/courierConfig.js`
⚠️ **不完整**——仅 `createConfig({ key, value })`POST `/config/create`FormData有完整实现`queryConfigList` 仅为 JSDoc 桩。
### 7.22 核价器(外部服务)
#### `src/api/config.js`
所有请求直连用户配置的 `http://{ip}:{port}`,不经过项目代理或签名。
| 函数 | 方法 | 端点 | 请求格式 | 说明 |
|------|------|------|----------|------|
| `testConnection(ip, port)` | POST | `/api/goods/query` | URLSearchParams | 测试连接(`isbn=0&out_id=0&quality=0&query_index=1&user_id=0` |
| `saveNewPrice(ip, port, newPrice, placeholderDownPrice, minShippingFee, minPrice, verifyIndex)` | POST | `/api/config/price/set` | URLSearchParams | 保存价格配置 |
| `kongfzLogin(username, password, ip, port)` | POST | `/api/kfz/login` | FormData15s 超时) | 孔夫子登录(通过核价器) |
| `batchAddTokens(tokens, ip, port)` | POST | `/api/token/add` | JSON Array | 批量提交账号 Token |
| `fetchTokenList(ip, port)` | POST | `/api/token/list` | JSON `{}` | 获取 Token 列表 |
| `fetchConfig(ip, port)` | POST | `/api/config/price/get` | JSON `{}` | 获取价格配置 |
| `queryGoodsPrice({ ip, port, isbn, bookName, author, publisher, isSuit, outId, quality, queryIndex, userId, ... })` | POST | `/api/goods/query` | URLSearchParams | 查询商品价格(套装书传 `book_name+author+publisher` 替代 isbn |
| `deleteToken(ip, port, id)` | GET | `/api/token/delete` | 查询参数 | 删除 Token |
### 7.23 打印服务(重导出)
#### `src/api/print.js`
重导出自 `src/utils/daYin/print.ts`
| 函数 | 说明 |
|------|------|
| `initLodop()` | 初始化 Lodop/Clodop 打印控件 |
| `createPrintTask(...)` | 创建打印任务 |
| `getSimplePrinterList()` | 获取已安装的打印机列表 |
### 7.24 发布记录Mock
#### `src/api/releaseRecord.js`
**全部为 Mock 数据**,无后端接口。包含 4 个模拟店铺(博知书社/pdd、孔夫子旧书网、闲鱼优选、新知图书/pdd
| 函数 | 参数 | 说明 |
|------|------|------|
| `fetchReleaseRecordList({ keyword, shop_type, status, page, pageSize })` | 关键词/店铺类型/发布状态/分页 | Mock 发布记录列表300ms 延迟) |
| `retryRelease(shopId, productId, shopType)` | 店铺/商品/类型 | Mock 重试发布500ms 延迟,始终成功) |
| `fetchShopReleaseDetail(shopId)` | 店铺 ID | Mock 店铺发布详情300ms 延迟) |
---
## 8. 状态管理
### 8.1 Pinia Store`src/store/user.js`)— 推荐使用
| 状态/方法 | 类型 | 说明 |
|-----------|------|------|
| `adminUserInfo` | `ref` | 管理员用户信息(从 localStorage 初始化) |
| `userInfo` | `computed` | adminUserInfo 的别名 |
| `isAdmin` | `computed` | `role === 255` |
| `points` | `computed` | 当前积分 |
| `setUserInfoAction(info)` | 方法 | 更新用户信息并同步 localStorage |
| `updatePoints(newPoints)` | 方法 | 更新积分 |
| `clearAdminUserInfo()` | 方法 | 清除用户信息(登出) |
| `getAdminInfo()` | 方法 | 返回 `adminUserInfo.value` |
### 8.2 旧版 Store`src/store/index.js`
基于 Vue `reactive()` 的简单 store未使用 Pinia
| 状态/方法 | 说明 |
|-----------|------|
| `state.user` | 用户信息 |
| `state.token` | Token |
| `setUser(user)` | 设置用户 |
| `setToken(token)` | 设置 Token |
| `clear()` | 清除状态 |
> ⚠️ **逐步废弃中**,建议迁移到 Pinia store。
### 8.3 认证存储localStorage
`src/utils/auth.js` 统一管理:
| Key | 内容 | 用途 |
|-----|------|------|
| `admin_token` | JWT Token | 管理员 Bearer TokenAuthorization 头) |
| `admin_userInfo` | JSON `{ id, username, role, about_id, points }` | 管理员信息 |
| `admin_expire` | 过期时间 | Token 有效期 |
| `embed_mode` | `"true"` | 嵌入模式标志(隐藏导航栏) |
| `user_token` | JWT Token | 用户端 Token区别于管理端 |
| `user_userInfo` | JSON | 用户端信息 |
| `printFlag` | `"1"` | 打印调试模式标记 |
---
## 9. 工具函数
### 9.1 签名工具(`src/utils/sign.js`
`SignUtil` 单例类:
| 方法 | 说明 |
|------|------|
| `generateSign(params)` | 生成完整签名参数对象(含 app_key、client_id、timestamp、sign_method、sign |
| `generateSignedUrl(baseUrl, params)` | 生成带签名的完整 URL |
| `calculateSign(params)` | 核心 MD5 签名计算 |
| `buildParamEntries(params, prefix)` | 递归展平嵌套对象/数组 |
| `sortKeys(a, b)` | 智能键排序 |
### 9.2 Mock 数据(`src/utils/mock.js`
| 数据集 | 内容 |
|--------|------|
| `mockProducts` | 4 个模拟商品(基础会员卡 100pts 等) |
| `mockCards` | 50 张模拟卡密(随机状态 1/2/3/4 |
| `mockEmployees` | 4 个模拟代理(张三、李四、王五、系统管理员) |
| `mockOrders` | 30 个模拟订单 |
| `mockAccessLogs` | 50 条模拟积分记录70% 收入 + 30% 支出) |
### 9.3 打印服务
| 文件 | 内容 |
|------|------|
| `src/utils/daYin/print.ts` | Lodop/Clodop 打印封装TypeScript`initLodop`, `createPrintTask`, `getSimplePrinterList` |
| `src/utils/daYinPdd/index.ts` | 拼多多打印服务 |
| `src/utils/pddUpload.ts` | 拼多多图片上传 |
### 9.4 其他
| 文件 | 说明 |
|------|------|
| `src/utils/clipboard.js` | 剪贴板操作工具 |
| `src/utils/ServiceManager.js` | 服务管理器 |
---
## 10. 外部服务集成
### 10.1 核价器Pricer
- **方式**:通过 `axios` 直连(不经过项目主 API访问用户配置的 `{ip}:{port}`
- **功能**:查询商品价格、设置价格、孔夫子登录/Token 管理
- **文件**`src/api/config.js`
- **特点**:每个请求动态传入 IP:Port支持 URLSearchParams/JSON/FormData 多种请求格式
### 10.2 快递打印
- **方式**:通过独立 `axios` 实例直连(不经过 request 拦截器签名)
- **服务地址**
- `api.buzhiyushu.cn` — 快递账号列表、回填快递单号
- `print.buzhiyushu.cn` — 创建快递面单、获取打印面单、查询快递信息
- **文件**`src/api/shipping-order.js`
### 10.3 外部店铺 API
- **服务地址**`https://api.buzhiyushu.cn/zhishu/shop/list` / `https://new.taskpool.buzhiyushu.cn:3500/api/shop/get/:id`
- **使用独立 axios 实例** + `json-bigint` 处理大整数
- **文件**`src/api/shop.js`
### 10.4 孔夫子旧书网登录
- **方式**Vite 开发服务器自定义中间件 `/kongfz-login`
- **流程**POST `login.kongfz.com` → 跟随重定向 → 提取 PHPSESSID → 获取用户信息
- **绕过**浏览器跨域限制CORS
- **错误码**400参数为空/ 405非 POST/ 500登录失败
### 10.5 OCR 识别
- **端点**`/ocr`(经过主 API 代理)
- **特殊处理**:设置 `X-Need-Sign: false` 跳过签名,保持 FormData 中 Blob 数据完整性
- **文件**`src/api/product.js``ocrImage()` 方法)
---
## 11. 核心业务流程
### 11.1 波次管理流程
```
相机扫码 (camera.vue)
├── 扫描条码 → 查询书籍信息 (getBookInfo)
├── OCR 识别 → 提取书名/作者/出版社 (ocrImage)
├── 生成条码图片 (generateBarcode)
├── 编辑书籍信息 → 同步到系统 (syncBook)
└── 添加商品 → 创建波次 (createWaveOutbound)
└── 释放波次 → 生成采购/出库任务 (releaseWave)
```
### 11.2 采购到出库到发货全流程
```
创建采购订单 (createPurchaseOrder)
└── 创建带波次采购单 (createPurchaseOrderWithWave)
├── 释放波次 (releaseWave) → 生成波次任务
└── 采购入库 → 库存增加
└── 创建销售订单 (createSalesOrder)
├── 确认销售订单 (confirmSalesOrder)
│ └── 创建出库单 (createOutbound / createOutboundFromSalesOrder)
│ ├── 审核出库单 (approveOutbound)
│ │ └── 生成发货单 (createShippingOrder)
│ │ ├── 填写物流公司/单号 (updateShippingOrderLogistics)
│ │ └── 回填快递单号 (submitCompanyOrder)
│ └── 导出出库单 (exportOutbound)
└── 取消销售订单 (cancelSalesOrder)
```
### 11.3 盘库流程
```
创建盘库单 (createStockCheck) — 全盘/抽盘
└── 开始盘点 (startStockCheck) → 状态变为"盘点中"
└── 逐条/批量提交盘点结果 (submitCheckItem / batchSubmitCheckItems)
└── 完成盘点 (completeStockCheck) → 状态变为"已完成"
└── 库存调整 (adjustStockCheck) — 如有差异
也可中途取消盘库单 (cancelStockCheck)
```
### 11.4 商品发布流程
```
商品录入 → 选择店铺 → 发布商品
└── 查看发布记录(当前为 Mock 数据)
└── 失败重试 (retryProductPublish)
```
### 11.5 异常书目审核流程
```
员工提交异常书目 (saveAbnormalBook)
└── 管理员审核 (auditProductLog)
├── 通过 → 更新书目信息
└── 驳回 → 标记异常状态
```
---
## 12. 打印服务
### 12.1 打印架构
前端打印基于 **C-Lodop** / **Lodop** 控件,运行在 `http://localhost:8000`
| 文件 | 说明 |
|------|------|
| `src/utils/daYin/print.ts` | 核心封装TypeScript`initLodop` 加载 `CLodopfuncs.js``createPrintTask` 创建打印任务,`getSimplePrinterList` 获取打印机列表 |
| `src/utils/daYinPdd/index.ts` | 拼多多打印封装 |
| `src/api/print.js` | 重导出上述 TS 文件的方法 |
### 12.2 打印机调试模式
**套装书条码打印**在首次走 `PRINT_SETUP`(弹出打印设置对话框),确认后 `localStorage.printFlag = '1'`,后续走 `PRINT()` 静默打印。
| 状态 | 打印方式 | 说明 |
|------|---------|------|
| `printFlag` 未设置或不为 `'1'` | `PRINT_SETUP()` | 弹出打印设置对话框,用户调整参数后确认 |
| `printFlag === '1'` | `PRINT()` | 直接静默打印 |
**手动入口**:波次页搜索栏的"打印调试"按钮,使用固定测试号 `9787000000000-01` 生成条码,始终弹出调试框。
---
## 13. 数据模型
### 13.1 盘库模块
| 表名 | 用途 |
|------|------|
| `stock_check` | 盘库单主表 |
| `stock_check_item` | 盘库明细表 |
**盘库单状态**`1`=待盘点 → `2`=盘点中 → `3`=已完成 → `4`=已取消
**盘点类型**`1`=全盘,`2`=抽盘
**明细状态**`1`=待盘点,`2`=已盘点
### 13.2 出库/发货模块
- 基于 `shipping_order` 表结构API 层已按表结构调整)
- 状态:`1`=待发货,`2`=已发货,`3`=已签收,`4`=已取消
### 13.3 波次模块
| 端点 | 用途 |
|------|------|
| `/wave/task/list` | 波次任务列表 |
| `/wave/task/detail` | 波次任务详情 |
| `/wave/list` | 波次列表(选择器用) |
| `/wave/getWaveStatusById` | 波次状态查询 |
### 13.4 库存模块
- **汇总级别**:按商品 + 仓库聚合
- **明细级别**:按仓库 + 库位 + 批次
- **关键字段**:总量、锁定数量、可用数量
### 13.5 分账模块
| 表/概念 | 说明 |
|---------|------|
| `split_account_config` | 分账规则配置rule_name + rule_value JSON |
| `split_account_deduction_log` | 分账扣款日志 |
---
## 14. 已知问题与维护
### 14.1 遗留问题
| # | 问题 | 说明 | 建议 |
|---|------|------|------|
| 1 | Vite 2 版本较旧 | `^2.5.2` 有已知安全更新 | 建议升级到 Vite 4+ |
| 2 | TypeScript 使用不一致 | `tsconfig.json` 存在但项目以 JS 为主 | 统一规范或移除 TS |
| 3 | 发布记录为 Mock 数据 | `releaseRecord.js` 无后端接口 | 待后端接口对接 |
| 4 | 旧版 Store 冗余 | `store/index.js` 与 Pinia 并存 | 可迁移清理 |
| 5 | 备份文件残留 | 多个 `.bak` 文件 | 确认后可删除 |
| 6 | 函数名 typo | `employeeType.js``updataEmployeInfo` → 应为 `updateEmployeeInfo` | 后续修复 |
| 7 | 文件名 typo | `submIllegalBook.js` → 应为 `submitIllegalBook.js` | 后续重命名 |
| 8 | 文件名 typo | `goosList.vue`(组件中)→ 应为 `goodsList.vue` | 后续重命名 |
| 9 | 重复 API 文件 | `split.js`(不完整)与 `splitAccount.js`(完整)并存 | 可删除 `split.js` |
| 10 | 重复 normalizeListResponse | 约 15 个文件各自定义该函数 | 应抽离为公共工具 |
| 11 | 仪表盘内容为空 | `Dashboard.vue` 统计内容待完善 | 待与后端对接更多数据 |
| 12 | courierConfig 不完整 | 仅 `createConfig` 有实现 | 需补充完整 |
| 13 | PDF 打印未集成 | 面单打印依赖外部服务 | 后端接入后完善 |
### 14.2 开发规范
| 规范 | 说明 |
|------|------|
| **API 层** | 所有后端接口调用集中在 `src/api/`,视图层不直接调用 `request` |
| **列表接口** | 统一使用 `normalizeListResponse()` 标准化返回格式 |
| **命名** | API 函数使用 `fetchXxx`(查询)、`createXxx`(新增)、`updateXxx`(更新)、`deleteXxx`(删除) |
| **签名** | 默认所有业务接口走签名,需跳过的设置 `X-Need-Sign: false` |
| **大整数** | 涉及 ID 字段使用 `json-bigint`,比较时注意 BigNumber 类型 |
| **组件命名** | Vue 组件使用 PascalCase 文件名 |
| **路由 meta** | 正确设置 `requiresAuth` / `requiresAdmin` / `hidden` |
### 14.3 添加新模块步骤
1.`src/api/` 下新建 `<module>.js`,定义所有接口函数(含 JSDoc 注释)
2.`src/views/` 下新建页面目录及 `.vue` 组件
3.`src/router/index.js``children` 中添加路由
4. 确保 `meta` 中正确设置 `requiresAuth` / `requiresAdmin`
5.`AdminLayout.vue` 中添加菜单项(如需显示在侧边栏)
6. 实现 `normalizeListResponse()` 标准化列表返回
### 14.4 维护日志
| 日期 | 操作 |
|------|------|
| 2026-06-15 | 更新技术文档,补充完整 API 接口表、路由表、新模块 |
| 2026-06-03 | 初始技术文档创建 |
---
*本文档随项目维护持续更新*