1
Some checks failed
Some checks failed
This commit is contained in:
parent
1d2a7d9a30
commit
44ba8a631c
333
MAINTENANCE.md
333
MAINTENANCE.md
@ -1,7 +1,7 @@
|
|||||||
# cards_web 维护文档
|
# 🛠 维护文档
|
||||||
|
|
||||||
> 📅 文档更新:2026-04-27
|
> 📅 文档更新:2026-06-15
|
||||||
> 📂 项目路径:`D:\project\cards_web`
|
> 📂 项目路径:`D:\project\psi_web`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -12,15 +12,19 @@
|
|||||||
| **项目名称** | cards(图书进销存管理后台) |
|
| **项目名称** | cards(图书进销存管理后台) |
|
||||||
| **版本** | 1.0.0 |
|
| **版本** | 1.0.0 |
|
||||||
| **技术栈** | Vue 3 + Vite 2 + Element Plus + Pinia + Vue Router 4 |
|
| **技术栈** | Vue 3 + Vite 2 + Element Plus + Pinia + Vue Router 4 |
|
||||||
| **源码规模** | 32 个 JS 文件,21 个 Vue 组件 |
|
| **源码规模** | 31 个 API 文件,33 个页面目录,24 个组件文件 |
|
||||||
|
| **路由模式** | History 模式(createWebHistory) |
|
||||||
|
|
||||||
### 核心业务模块
|
### 核心业务模块
|
||||||
|
|
||||||
- **波次管理(wave)** — 核心流程:相机拍照 → 条码识别 → 书籍信息编辑
|
- **波次管理(wave)** — 核心流程:相机拍照 → 条码识别 → 书籍信息编辑 → 波次创建/释放
|
||||||
- **出库管理(outbound)** — 出库单管理(API 层已按 `shipping_order` 表结构重写)
|
- **出库管理(outbound)** — 出库单 CRUD、审核、导出、改库位(基于 `shipping_order` 表结构)
|
||||||
- **盘库管理(stock-check)** — 基于 `stock_check` / `stock_check_item` 表
|
- **盘库管理(stock-check)** — 全盘/抽盘、逐条/批量提交、库存调整
|
||||||
- **采购 / 销售 / 发货单** — 订单全链路
|
- **采购 / 销售 / 发货单** — 订单全链路可追溯
|
||||||
- **商品、库存、供应商、仓库、库位** — 基础数据管理
|
- **商品、库存、供应商、仓库、库位** — 基础数据管理
|
||||||
|
- **分账管理** — 分账规则配置 + 扣款日志查询/导出
|
||||||
|
- **快递/物流** — 物流模板、运费模板、快递面单打印、单号回填
|
||||||
|
- **店铺管理** — 多店铺平台(拼多多、孔夫子、闲鱼)、授权管理
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -31,14 +35,19 @@
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"vue": "^3.2.8",
|
"vue": "^3.2.8",
|
||||||
"vue-router": "^4.0.11",
|
"vue-router": "^4.1.6",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^2.1.7",
|
||||||
"element-plus": "^2.13.7",
|
"element-plus": "^2.13.7",
|
||||||
"axios": "^0.21.1",
|
"axios": "^1.7.9",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"@zxing/library": "^0.21.3",
|
"@zxing/library": "^0.21.3",
|
||||||
|
"jsbarcode": "^3.12.3",
|
||||||
|
"json-bigint": "^1.0.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"vite": "^2.5.2"
|
"sass-embedded": "^1.99.0",
|
||||||
|
"vite": "^2.5.2",
|
||||||
|
"@vitejs/plugin-vue": "^1.6.0",
|
||||||
|
"typescript": "^5.5.4"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -46,9 +55,10 @@
|
|||||||
|
|
||||||
| 文件 | 作用 |
|
| 文件 | 作用 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `vite.config.js` | Vite 构建配置,dev server 代理 |
|
| `vite.config.js` | Vite 构建配置(dev server 代理、中间件、别名) |
|
||||||
| `.env` | 环境变量(API 密钥配置) |
|
| `.env` | 环境变量(API 密钥配置) |
|
||||||
| `tsconfig.json` | TypeScript 配置(但项目实际以 JS 为主) |
|
| `tsconfig.json` | TypeScript 配置(项目以 JS 为主,部分 .ts 文件) |
|
||||||
|
| `package.json` | 依赖管理与脚本 |
|
||||||
|
|
||||||
### 2.3 环境变量(`.env`)
|
### 2.3 环境变量(`.env`)
|
||||||
|
|
||||||
@ -58,6 +68,20 @@ VITE_APP_CLIENT_ID=psi
|
|||||||
VITE_APP_API_SECRET=psi_api_sign_secret
|
VITE_APP_API_SECRET=psi_api_sign_secret
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 2.4 Vite 配置代理
|
||||||
|
|
||||||
|
| 代理规则 | 目标 | 说明 |
|
||||||
|
|---------|------|------|
|
||||||
|
| `/api` | `https://psi.api.buzhiyushu.cn` | 主业务 API(changeOrigin) |
|
||||||
|
| `/api/print` | `https://print.buzhiyushu.cn` | 打印服务(changeOrigin) |
|
||||||
|
|
||||||
|
### 2.5 开发服务器
|
||||||
|
|
||||||
|
- **端口**:5174
|
||||||
|
- **Host**:0.0.0.0
|
||||||
|
- **History API Fallback**:true
|
||||||
|
- **自定义中间件**:`/kongfz-login`(孔夫子旧书网登录代理)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 三、API 架构
|
## 三、API 架构
|
||||||
@ -66,38 +90,73 @@ VITE_APP_API_SECRET=psi_api_sign_secret
|
|||||||
|
|
||||||
| 服务 | 地址 | 用途 |
|
| 服务 | 地址 | 用途 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| **主业务 API** | `192.168.101.213:9090` | 所有业务接口(已在 vite.config.js 中配置代理) |
|
| **主业务 API** | `https://psi.api.buzhiyushu.cn` | 所有业务接口(通过 Vite 代理 `/api`) |
|
||||||
| ~~ES 搜索~~ | ~~`192.168.101.162:9009`~~ | ⚠️ 已废弃,功能已合并到主业务 API |
|
| **打印服务** | `https://print.buzhiyushu.cn` | 打印面单(通过 Vite 代理 `/api/print`) |
|
||||||
|
| **店铺列表** | `https://api.buzhiyushu.cn` | 外部店铺列表/快递回填 |
|
||||||
|
| **店铺详情** | `https://new.taskpool.buzhiyushu.cn:3500` | 外部店铺详情 |
|
||||||
|
| **核价器** | `http://{用户配置的 IP}:{Port}` | 价格查询/配置(直连,不经过代理) |
|
||||||
|
|
||||||
> ⚠️ **2026-04-27 更新**:`/api/es` 代理已从 `vite.config.js` 中移除,现仅保留 `/api` → `192.168.101.213:9090`。
|
> ⚠️ 历史:`/api/es` 代理(192.168.101.162:9009)已于 2026-04-27 移除。
|
||||||
|
|
||||||
### 3.2 请求封装(`src/utils/request.js`)
|
### 3.2 请求封装(`src/utils/request.js`)
|
||||||
|
|
||||||
- **baseURL**:开发环境 `/api`,生产环境通过 `VITE_API_BASE` 或 `192.168.101.213:9090/api`
|
- **baseURL**:开发环境 `/api`,生产环境 `https://psi.api.buzhiyushu.cn/api`
|
||||||
- **签名机制**:所有非 mock 请求默认走签名(`signUtil.generateSign`)
|
- **超时**:10 秒
|
||||||
- **鉴权 Token**:从 `localStorage` 的 `admin_token` 读取,通过 `Bearer` 头部传递
|
- **签名机制**:所有非 mock 请求默认走签名(`signUtil.generateSign`),跳过需设 `X-Need-Sign: false`
|
||||||
- **请求转换**:自动将普通对象转为 FormData(`objectToFormData`)
|
- **鉴权 Token**:从 `localStorage.admin_token` 读取,通过 `Bearer` 头部传递
|
||||||
- **响应处理**:统一处理 code≠200 的错误提示,按 HTTP 状态码跳转登录页
|
- **请求转换**:自动将普通对象转为 FormData(`objectToFormData`,支持嵌套/数组)
|
||||||
- **Mock 模式**:`USE_MOCK = false`(默认关闭),可通过环境变量控制
|
- **大整数**:使用 `json-bigint` 替代 `JSON.parse`
|
||||||
|
- **响应处理**:统一错误提示 + 按 HTTP 状态码跳转登录页
|
||||||
|
- **Mock 模式**:`USE_MOCK = false`(默认关闭),支持商品/卡密/代理/订单/积分的模拟数据
|
||||||
|
- **额外导出**:`fetchCurrentUser()` 获取当前用户信息
|
||||||
|
|
||||||
### 3.3 API 模块列表(`src/api/`)
|
### 3.3 API 模块索引(31 个 API 文件)
|
||||||
|
|
||||||
| 文件 | 对应接口前缀 | 备注 |
|
| 文件 | API 基路径 | 说明 |
|
||||||
|------|-------------|------|
|
|------|-----------|------|
|
||||||
| `barcode.js` | `/barcode/*` | 条码相关 |
|
| `barcode.js` | `/barcode/generate` | 条码生成(套装书条码打印/调试用) |
|
||||||
| `inventory.js` | `/inventory/*` | 库存记录 |
|
| `book.js` | `/getBookInfo`, `/syncBook` | 书籍信息查询/同步 |
|
||||||
| `location.js` | `/location/*` | 库位管理 |
|
| `car.js` | `/car/*` | 小车 CRUD + 小车-店铺关联 |
|
||||||
| `outbound.js` | `/outbound/*` | 出库管理 |
|
| `config.js` | 直连 `http://{ip}:{port}` | 核价器(价格查询、Token 管理、孔夫子登录) |
|
||||||
| `product.js` | `/product/*` | 商品管理 |
|
| `courierConfig.js` | `/config/*` | ⚠️ 快递配置(仅 `createConfig` 完整,其余为桩) |
|
||||||
| `purchase-order.js` | `/purchase-order/*` | 采购订单 |
|
| `dashboard.js` | `/dashboard/statist` | 仪表盘统计(今日/员工) |
|
||||||
| `sales-order.js` | `/sales-order/*` | 销售订单 |
|
| `district.js` | `/district/*` | 省市区数据 + 运费模板 CRUD |
|
||||||
| `shipping-order.js` | `/shipping-order/*` | 发货单 |
|
| `employee.js` | `/admin/employee/*` | 代理 CRUD、积分加减、启用禁用 |
|
||||||
| `stock-check.js` | `/stock-check/*` | 盘库管理 |
|
| `employeeType.js` | `/admin/user-type/*` | 员工类型管理 |
|
||||||
| `supplier.js` | `/supplier/*` | 供应商管理 |
|
| `goodsInfo.js` | `/product_log/save` | 提交异常书目(新旧字段对比,FormData) |
|
||||||
| `warehouse.js` | `/warehouse/*` | 仓库管理 |
|
| `inventory.js` | `/inventory/*` | 库存汇总/明细/变动日志 |
|
||||||
| `wave-task.js` | `/wave/*` | 波次任务 |
|
| `location.js` | `/location/*` | 库位 CRUD、批量生成、Excel 导入 |
|
||||||
|
| `logistics.js` | `/logistics/*` | 物流模板 CRUD |
|
||||||
|
| `login.js` | `/login/:role` | 管理员/代理登录 |
|
||||||
|
| `outbound.js` | `/outbound-order/*` | 出库单 CRUD、审核、导出、改库位 |
|
||||||
|
| `print.js` | — | Lodop 打印方法重导出 |
|
||||||
|
| `product.js` | `/product/*`, `/getSuitBook`, `/ocr` | 商品 CRUD、图片管理、OCR、Excel 导入 |
|
||||||
|
| `purchase-order.js` | `/purchase-order/*`, `/wave/release` | 采购单 CRUD、带波次创建、波次释放 |
|
||||||
|
| `releaseRecord.js` | — | **Mock 数据**,无后端接口 |
|
||||||
|
| `reviewIllegalBook.js` | `/product_log/*` | 异常书目查询/删除 |
|
||||||
|
| `sales-order.js` | `/sales-order/*` | 销售单 CRUD、确认、取消、退货 |
|
||||||
|
| `shipping-order.js` | `/shipping-order/*` + 外部接口 | 发货单 CRUD + 快递面单/回填 |
|
||||||
|
| `shop.js` | `/shop/*` + 外部 API | 店铺 CRUD + 外部列表/详情 |
|
||||||
|
| `split.js` | `/split-account-config/*` | ⚠️ 旧版分账(不完整),建议用 `splitAccount.js` |
|
||||||
|
| `splitAccount.js` | `/split-account-config/*` | 分账配置 CRUD + 启用/禁用(完整版) |
|
||||||
|
| `splitDeductionLog.js` | `/split-account-deduction-log/*` | 分账扣款日志列表/详情/导出 |
|
||||||
|
| `stock-check.js` | `/inventory/stock-check/*` | 盘库单 CRUD、盘点、调整、退货 |
|
||||||
|
| `submIllegalBook.js` | `/product_log/audit` | ⚠️ 文件名 "subm" 应为 "submit",异常书目审核 |
|
||||||
|
| `supplier.js` | `/supplier/*` | 供应商 CRUD |
|
||||||
|
| `warehouse.js` | `/warehouse/*` | 仓库 CRUD |
|
||||||
|
| `wave-task.js` | `/wave/*` | 波次任务列表/详情、波次创建/释放 |
|
||||||
|
|
||||||
> 注意:`wave-task.js` 有 `.bak_20260426_172453` 备份文件,建议清理。
|
### 3.4 外部服务集成一览
|
||||||
|
|
||||||
|
| 服务 | 集成方式 | 说明 |
|
||||||
|
|------|---------|------|
|
||||||
|
| **核价器** | axios 直连 `http://{ip}:{port}` | 价格查询、孔夫子登录、Token 管理 |
|
||||||
|
| **快递打印** | axios 直连 | 面单创建(print.buzhiyushu.cn)、单号回填(api.buzhiyushu.cn) |
|
||||||
|
| **店铺列表** | 外部 axios + JSONbig | api.buzhiyushu.cn 店铺列表 |
|
||||||
|
| **店铺详情** | 外部 axios | new.taskpool.buzhiyushu.cn:3500 |
|
||||||
|
| **孔夫子登录** | Vite 自定义中间件 | 模拟浏览器 POST → 提取 PHPSESSID → 获取用户信息 |
|
||||||
|
| **OCR 识别** | 主 API `/ocr`(跳过签名) | 图片文字识别、提取书名字段 |
|
||||||
|
| **Lodop 打印** | 本地 8000 端口 | C-Lodop 控件,条码打印/调试 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -105,47 +164,88 @@ VITE_APP_API_SECRET=psi_api_sign_secret
|
|||||||
|
|
||||||
### 4.1 路由模式
|
### 4.1 路由模式
|
||||||
|
|
||||||
- **Hash 模式**(`createWebHashHistory`)
|
- **History 模式**(`createWebHistory`)
|
||||||
- URL 格式:`http://host:port/#/path`
|
- URL 格式:`http://host:port/path`
|
||||||
|
- 需要服务端配置 history API fallback(Vite dev 已配置,生产需 Nginx 配置)
|
||||||
|
|
||||||
### 4.2 路由守卫逻辑
|
### 4.2 路由守卫逻辑
|
||||||
|
|
||||||
| 路径 | 鉴权要求 | 说明 |
|
| 步骤 | 逻辑 | 说明 |
|
||||||
|------|---------|------|
|
|------|------|------|
|
||||||
| `/login` | 无 | 登录页,已登录自动跳转 `/dashboard` |
|
| 1 | URL token 注入 | 检测 `?token=xxx` → 解析 JWT payload → 写入 localStorage(嵌入模式) |
|
||||||
| `/dashboard` | `admin_token` 存在 | 仪表盘,入口页 |
|
| 2 | 页面标题 | `document.title = "{title} - 进销存系统"` |
|
||||||
| 其他业务页 | `admin_token` + `role=255` | 仅管理员可访问 |
|
| 3 | 登录页 | 已登录自动跳转 `/dashboard` |
|
||||||
|
| 4 | 仪表盘 | 无 `adminUserInfo` 跳转 `/login` |
|
||||||
|
| 5 | 黑名单鉴权 | 缺少 `meta.isPublic` 的所有页面需登录 |
|
||||||
|
| 6 | 管理员校验 | `requiresAdmin: true` 且 `role !== 255` 跳转 `/dashboard` |
|
||||||
|
|
||||||
### 4.3 角色权限
|
### 4.3 角色权限
|
||||||
|
|
||||||
| role 值 | 身份 | 权限 |
|
| role 值 | 身份 | 权限范围 |
|
||||||
|---------|------|------|
|
|---------|------|---------|
|
||||||
| `255` | 管理员 | 所有页面和操作 |
|
| `255` | 管理员 | 所有页面和操作 |
|
||||||
| `128` | 代理 | 降级为普通用户,仅限基础功能 |
|
| `128` | 代理 | 基础功能(波次、仓库、商品等),无权访问管理页面 |
|
||||||
|
|
||||||
|
### 4.4 路由统计
|
||||||
|
|
||||||
|
- 共 **32 条路由**(含 1 个登录页、1 个布局容器、30 个子页面)
|
||||||
|
- **管理员专用路由**:代理管理、添加代理、员工类型管理、异常书目审核、核价器配置
|
||||||
|
- **隐藏路由**:库位管理(`/location-manager`)、PDA 管理(`/pdaManage`),通过 URL 直接访问但不在侧边栏显示
|
||||||
|
|
||||||
|
### 4.5 新增路由(自上次文档更新以来)
|
||||||
|
|
||||||
|
| 路径 | 名称 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `/sales-order-info-list` | SalesOrderInfoList | 销售单详情列表 |
|
||||||
|
| `/shipping-order-list` | ShippingOrderList | 发货单详情列表 |
|
||||||
|
| `/wave-task` | wave-task | 波次任务 |
|
||||||
|
| `/shop-settings` | shop-settings | 店铺设置 |
|
||||||
|
| `/sorting-settings` | sorting-settings | 分拣设置 |
|
||||||
|
| `/split-config` | split-config | 分账配置 |
|
||||||
|
| `/split-log` | split-log | 分账扣款日志 |
|
||||||
|
| `/courier-config` | courier-config | 快递配置 |
|
||||||
|
| `/pdaManage` | pdaManage | PDA 管理(隐藏) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 五、业务数据模型(关键表结构)
|
## 五、业务数据模型要点
|
||||||
|
|
||||||
### 5.1 盘库模块
|
### 5.1 盘库模块
|
||||||
|
|
||||||
| 表名 | 用途 |
|
| 表名 | 用途 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `stock_check` | 盘库单主表 |
|
| `stock_check` | 盘库单主表(`warehouse_id`、`check_type`、`status`、`remark`) |
|
||||||
| `stock_check_item` | 盘库明细 |
|
| `stock_check_item` | 盘库明细表(`stock_check_id`、`product_id`、`location_id`、`system_quantity`、`actual_quantity`、`status`) |
|
||||||
| **状态流程** | `1`=待盘点 → `2`=盘点中 → `3`=已完成 / `4`=已取消 |
|
|
||||||
| **盘点类型** | `1`=全盘,`2`=抽盘 |
|
|
||||||
|
|
||||||
### 5.2 出库模块
|
**状态流转**:`1`=待盘点 → `2`=盘点中 → `3`=已完成 / `4`=已取消
|
||||||
|
**盘点类型**:`1`=全盘(盘点仓库全部商品),`2`=抽盘(指定部分商品)
|
||||||
|
|
||||||
- 基于 `shipping_order` 表结构(API 层已重写,接口路径 `/shipping-order/*`)
|
### 5.2 出库/发货模块
|
||||||
- 目前无后端 API,前端先行开发待对接
|
|
||||||
|
- 基于 `shipping_order` 表结构
|
||||||
|
- 从销售单 → 出库单 → 发货单,三级流转
|
||||||
|
- 发货单状态:`1`=待发货,`2`=已发货,`3`=已签收,`4`=已取消
|
||||||
|
|
||||||
### 5.3 波次模块
|
### 5.3 波次模块
|
||||||
|
|
||||||
- **波次任务**:`/wave/task/list`、`/wave/task/detail`
|
- 波次任务:`/wave/task/list`、`/wave/task/detail`
|
||||||
- **波次列表**:`/wave/list`
|
- 波次列表(选择器):`/wave/list`
|
||||||
- 支持按波次号扫码查询任务
|
- 支持按波次号扫码查询任务
|
||||||
|
- 波次出库:先 `createWaveOutbound` 创建 → 再 `createWaveOutboundRelease` 释放
|
||||||
|
- 采购带波次:先 `createPurchaseOrderWithWave` 创建 → 再 `releaseWave` 释放
|
||||||
|
|
||||||
|
### 5.4 库存模块
|
||||||
|
|
||||||
|
- 汇总(按商品+仓库聚合):`/inventory/list` → 总量/锁定/可用数量
|
||||||
|
- 明细(按仓库+库位+批次):`/inventory/detail`
|
||||||
|
- 变动日志:`/inventory/log/list` → 支持时间范围、变动类型筛选
|
||||||
|
|
||||||
|
### 5.5 分账模块
|
||||||
|
|
||||||
|
| 概念 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `split-account-config` | 分账规则配置(`rule_value` 为 JSON 对象,含分账比例/固定金额等) |
|
||||||
|
| `split-account-deduction-log` | 扣款执行日志,支持按业务单号/配置名/时间范围查询导出 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -153,21 +253,29 @@ VITE_APP_API_SECRET=psi_api_sign_secret
|
|||||||
|
|
||||||
### 6.1 遗留问题
|
### 6.1 遗留问题
|
||||||
|
|
||||||
| # | 问题 | 说明 | 建议 |
|
| # | 问题 | 文件/区域 | 说明 | 优先级 |
|
||||||
|---|------|------|------|
|
|---|------|----------|------|--------|
|
||||||
| 1 | Vite 2 较旧 | `^2.5.2`,有安全更新 | 建议升级到 Vite 4+ |
|
| 1 | Vite 2 较旧 | `package.json` | `^2.5.2`,有安全更新 | 中 |
|
||||||
| 2 | Dashboard 为空 | `Dashboard.vue` 未实现内容 | 待后端接口接入 |
|
| 2 | TypeScript 未充分利用 | 项目范围 | `tsconfig.json` 存在,仅 `print.ts` 等少数文件使用 TS | 低 |
|
||||||
| 3 | 库位管理路由隐藏 | `location` 路由正常注册,但 UI 可能未展示入口 | 检查侧边栏组件 |
|
| 3 | Dashboard 内容为空 | `Dashboard.vue` | 仅显示基础统计 | 低 |
|
||||||
| 4 | `.bak` 备份文件 | `wave-task.js.bak_20260426_172453` | 确认无问题后可删除 |
|
| 4 | `.bak` 备份文件残留 | 多处 | `camera.vue`(4 个)、`orderList.vue`(3 个)、`shippingOrder.vue` 等 | 低 |
|
||||||
| 5 | 出库模块无后端 | `outbound.js` API 已写好,后端尚未实现 | 等待后端对接 |
|
| 5 | 出库模块待后端对接 | `outbound.js` | API 已写好,后端尚未完全实现 | 中 |
|
||||||
| 6 | TypeScript 未启用 | `tsconfig.json` 存在但项目使用 JS | 统一规范或移除 |
|
| 6 | 发布记录为 Mock | `releaseRecord.js` | 无后端接口,纯前端模拟数据 | 高 |
|
||||||
|
| 7 | `split.js` 不完整 | `src/api/split.js` | 仅 2 个函数,1 个为桩,应使用 `splitAccount.js` | 中 |
|
||||||
|
| 8 | `courierConfig.js` 不完整 | `src/api/courierConfig.js` | 仅 `createConfig` 有实现 | 中 |
|
||||||
|
| 9 | 函数名拼写错误 | `employeeType.js` | `updataEmployeInfo` → 应为 `updateEmployeeInfo` | 低 |
|
||||||
|
| 10 | 文件名不一致 | `submIllegalBook.js` | "subm" → 应为 "submit" | 低 |
|
||||||
|
| 11 | `normalizeListResponse` 重复 | 约 15 个 API 文件 | 各文件独立定义,应抽离公共工具函数 | 中 |
|
||||||
|
| 12 | 旧版 Store 冗余 | `store/index.js` | 与 Pinia store 并存,逐步废弃中 | 低 |
|
||||||
|
| 13 | `goosList.vue` 拼写错误 | `components/wave/goosList.vue` | "goos" → 应为 "goods" | 低 |
|
||||||
|
|
||||||
### 6.2 维护日志
|
### 6.2 维护日志
|
||||||
|
|
||||||
| 日期 | 操作 |
|
| 日期 | 操作 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 2026-04-26 | 首次读取项目,建立基础文档 |
|
| 2026-06-15 | 全面更新技术文档 + 维护文档,补充完整 API 接口表(31 个文件)、路由表(32 条路由)、新增模块(分账、快递、PDA、扫码等) |
|
||||||
| 2026-04-27 | 移除废弃的 ES 搜索服务代理 `192.168.101.162:9009`(功能已合并到主业务 API) |
|
| 2026-06-07 | 新增打印机调试模式:`car.vue` 添加"打印调试"按钮,`suitBookDialog.vue` 套装书首次打印走 `PRINT_SETUP`,`localStorage.printFlag` 控制是否跳过调试 |
|
||||||
|
| 2026-04-27 | 移除废弃的 ES 搜索服务代理 `192.168.101.162:9009`(功能已合并到主业务 API);首次读取项目,建立基础维护文档 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -176,29 +284,55 @@ VITE_APP_API_SECRET=psi_api_sign_secret
|
|||||||
### 7.1 启动开发服务器
|
### 7.1 启动开发服务器
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd D:\project\cards_web
|
cd D:\project\psi_web
|
||||||
npm run dev
|
npm run dev
|
||||||
# 访问 http://localhost:3000
|
# 访问 http://localhost:5174
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7.2 添加新业务模块
|
### 7.2 生产构建
|
||||||
|
|
||||||
1. **API 层**:在 `src/api/` 下新建 `xxx.js`
|
```bash
|
||||||
|
npm run build
|
||||||
|
# 输出到 dist/
|
||||||
|
npm run serve
|
||||||
|
# 预览构建结果
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 添加新业务模块
|
||||||
|
|
||||||
|
1. **API 层**:在 `src/api/` 下新建 `xxx.js`,定义所有接口函数(含 JSDoc)
|
||||||
2. **视图层**:在 `src/views/` 下新建目录和 `.vue` 组件
|
2. **视图层**:在 `src/views/` 下新建目录和 `.vue` 组件
|
||||||
3. **路由注册**:在 `src/router/index.js` 的 `children` 中添加路由对象
|
3. **路由注册**:在 `src/router/index.js` 的 `children` 中添加路由对象
|
||||||
4. **权限控制**:确保 `meta` 中正确设置 `requiresAuth` / `requiresAdmin`
|
4. **权限控制**:确保 `meta` 中正确设置 `requiresAuth` / `requiresAdmin`
|
||||||
|
5. **菜单配置**:在 `AdminLayout.vue` 的侧边栏添加菜单项(如需显示)
|
||||||
|
6. **列表标准化**:实现 `normalizeListResponse()` 标准化返回格式
|
||||||
|
|
||||||
### 7.3 API 签名机制
|
### 7.4 API 开发规范
|
||||||
|
|
||||||
所有业务请求通过 `signUtil.generateSign` 生成签名参数:
|
- 所有业务请求通过 `request` 封装,自动处理签名和 FormData 转换
|
||||||
- GET 请求:`params` 签名
|
- GET 请求:参数通过 `params` 传递,自动签名
|
||||||
- POST/PUT/DELETE:`body` 签名
|
- POST/PUT/DELETE:对象参数自动转为 FormData(嵌套对象转为 `parentKey[childKey]` 格式)
|
||||||
|
- 如需跳过签名,在请求头添加 `X-Need-Sign: false`
|
||||||
|
- 涉及大整数的字段(ID 类)注意使用 `toString()` 比较
|
||||||
|
- 图片/文件上传使用 `multipart/form-data`,删除 `Content-Type` 头让浏览器自动设置
|
||||||
|
|
||||||
如需跳过签名,可在请求头添加 `X-Need-Sign: false`。
|
### 7.5 Mock 数据调试
|
||||||
|
|
||||||
### 7.4 Mock 数据调试
|
如需启用本地 mock 数据进行离线开发:
|
||||||
|
|
||||||
如需启用本地 mock 数据,将 `src/utils/request.js` 中的 `USE_MOCK` 改为 `true`。
|
```javascript
|
||||||
|
// src/utils/request.js
|
||||||
|
const USE_MOCK = true // 改为 true
|
||||||
|
```
|
||||||
|
|
||||||
|
启用的模拟端点:商品列表、卡密管理、代理管理、订单管理、积分记录、卡密购买。
|
||||||
|
|
||||||
|
### 7.6 打印调试
|
||||||
|
|
||||||
|
打印基于 **C-Lodop** 控件(`http://localhost:8000/CLodopfuncs.js`):
|
||||||
|
- 首次打印套装书条码时弹出打印设置对话框(`PRINT_SETUP`)
|
||||||
|
- 确认后 `localStorage.printFlag = '1'`,后续直接静默打印(`PRINT`)
|
||||||
|
- 波次页搜索栏有"打印调试"按钮,使用测试号 `9787000000000-01`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -208,25 +342,42 @@ npm run dev
|
|||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
├── api/ 12 个业务 API 模块 + 1 个 .bak 备份
|
├── api/ 31 个业务 API 模块
|
||||||
├── assets/ 静态资源
|
├── assets/ 静态资源
|
||||||
├── components/ 公共组件(AdminLayout 等)
|
├── components/ 24 个公共组件文件
|
||||||
├── router/ 路由配置(hash 模式)
|
├── router/ 路由配置(history 模式 + 完整路由守卫)
|
||||||
├── store/ Pinia 状态管理
|
├── store/ 1 个 Pinia store + 1 个旧版 reactive store
|
||||||
├── utils/ 工具函数(auth、request、sign、mock)
|
├── utils/ 8 个工具文件/目录(auth、request、sign、mock、打印等)
|
||||||
└── views/ 18 个业务页面目录
|
└── views/ 33 个页面目录
|
||||||
```
|
```
|
||||||
|
|
||||||
### 8.2 需要清理的文件
|
### 8.2 需清理的文件
|
||||||
|
|
||||||
- `src/api/wave-task.js.bak_20260426_172453` — 备份文件,可删除
|
| 文件 | 原因 |
|
||||||
|
|------|------|
|
||||||
|
| `src/api/split.js` | 被 `splitAccount.js` 替代(仅保留 `createSplitConfig` 且用了不同的请求格式) |
|
||||||
|
| `src/store/index.js` | 旧版 store,已被 Pinia 替代 |
|
||||||
|
| `*.bak` 文件(多个) | 备份残留,确认后可删除 |
|
||||||
|
| `src/api/waveTask.js.bak_*` | 波次任务备份文件 |
|
||||||
|
|
||||||
|
### 8.3 关键代码问题
|
||||||
|
|
||||||
|
| 问题 | 位置 | 影响 |
|
||||||
|
|------|------|------|
|
||||||
|
| `updataEmployeInfo` 拼写错误 | `src/api/employeeType.js` | 函数名拼写错误,调用方需使用错误名 |
|
||||||
|
| `submIllegalBook.js` 文件名 | `src/api/` | 文件名与内容不符(实际是 audit 功能) |
|
||||||
|
| `goosList.vue` 组件名 | `src/components/wave/` | 拼写错误 |
|
||||||
|
| `split.js` vs `splitAccount.js` | `src/api/` | 重复文件,实现不一致 |
|
||||||
|
| `normalizeListResponse` 重复定义 | 约 15 个 API 文件 | 代码重复,建议抽离 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 九、联系方式
|
## 九、联系方式
|
||||||
|
|
||||||
- **后端接口负责人**:`192.168.101.213:9090`
|
- **主业务 API**:`https://psi.api.buzhiyushu.cn`(通过 `/api` 代理)
|
||||||
- **主业务接口前缀**:所有 API 通过 `/api` 代理
|
- **打印服务**:`https://print.buzhiyushu.cn`(通过 `/api/print` 代理)
|
||||||
|
- **外部店铺**:`https://api.buzhiyushu.cn` / `https://new.taskpool.buzhiyushu.cn:3500`
|
||||||
|
- **开发服务器**:`http://localhost:5174`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
89
USAGE.md
Normal file
89
USAGE.md
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# 使用文档
|
||||||
|
|
||||||
|
> 📅 文档更新:2026-06-07
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、波次管理 — 套装书条码打印
|
||||||
|
|
||||||
|
### 1.1 功能概述
|
||||||
|
|
||||||
|
波次扫描中遇到**套装书**(主书 + 副书组合)时,选中后会弹出套装书选择对话框,确认后自动打印条码。条码格式为 `{isbn}-{副标识}`(如 `9787000000000-01`)。
|
||||||
|
|
||||||
|
### 1.2 套装书操作流程
|
||||||
|
|
||||||
|
1. 扫描书籍 ISBN → 若为套装书(`is_suit=1`),自动弹出 **"选择套装书"** 对话框
|
||||||
|
2. 从列表中选择对应套装书,或添加自定义项(需填写副ISBN)
|
||||||
|
3. 点击 **"确认选择"** → 自动生成并打印条码
|
||||||
|
|
||||||
|
### 1.3 条码打印 — 调试模式
|
||||||
|
|
||||||
|
首次打印条码时会弹出 **C-Lodop 打印设置对话框**(调试模式),可在其中调整:
|
||||||
|
- 纸张尺寸(默认 60mm × 30mm)
|
||||||
|
- 打印机选择
|
||||||
|
- 打印方向、边距等参数
|
||||||
|
|
||||||
|
**调试模式触发规则:**
|
||||||
|
|
||||||
|
| 场景 | 行为 |
|
||||||
|
|------|------|
|
||||||
|
| 首次打印套装书条码 | 弹出调试对话框 → 确认后永久记录,后续不再弹出 |
|
||||||
|
| 后续打印套装书条码 | 直接打印,不再弹出对话框 |
|
||||||
|
| 手动点击"打印调试"按钮(搜索栏) | 固定使用测试号 `9787000000000-01` 生成条码 → 弹出调试对话框 |
|
||||||
|
|
||||||
|
**重置调试模式:**
|
||||||
|
清除浏览器 localStorage 中的 `printFlag` 即可让调试标记失效,下次打印重新弹出调试框:
|
||||||
|
```
|
||||||
|
localStorage.removeItem('printFlag')
|
||||||
|
```
|
||||||
|
(可在浏览器开发者工具 → Application → Local Storage 中操作)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、波次管理 — 界面说明
|
||||||
|
|
||||||
|
### 2.1 波次页布局
|
||||||
|
|
||||||
|
| 区域 | 内容 |
|
||||||
|
|------|------|
|
||||||
|
| 顶部搜索栏 | ISBN 搜索、仓库选择、小车选择、容量、品相、固定货号、**打印调试按钮** |
|
||||||
|
| 左侧面板 | 拍照区域(Camera 组件) |
|
||||||
|
| 中间面板 | 书籍信息预览(可编辑本数、自设价格) |
|
||||||
|
| 右侧面板 | 照片预览 |
|
||||||
|
| 底部列表 | 已扫描书籍列表 |
|
||||||
|
|
||||||
|
### 2.2 操作步骤
|
||||||
|
|
||||||
|
1. **选择小车** → 在搜索栏下拉框中选择要操作的小车
|
||||||
|
2. **扫描 ISBN** → 在搜索框输入或扫码枪扫描 ISBN
|
||||||
|
3. **拍照(alt+a)** → 拍摄书籍照片
|
||||||
|
4. **创建波次(alt+x)** → 将已扫描的书籍创建为波次任务
|
||||||
|
5. **继续扫描** → 追加更多书籍到已有波次
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、C-Lodop 打印设置
|
||||||
|
|
||||||
|
### 3.1 服务要求
|
||||||
|
|
||||||
|
- 本地需运行 **C-Lodop 打印服务**(默认地址 `http://localhost:8000`)
|
||||||
|
- 如果未启动,点击"打印调试"或打印条码时会提示"加载 C-Lodop 失败"并引导下载
|
||||||
|
|
||||||
|
### 3.2 条码打印机配置
|
||||||
|
|
||||||
|
1. 进入 **打印机管理** 页面
|
||||||
|
2. 在 **"条码打印机"** 下拉框中选择对应打印机
|
||||||
|
3. 点击 **"确定"** 保存(存储于 `localStorage.printer_barcode`)
|
||||||
|
|
||||||
|
### 3.3 常见问题
|
||||||
|
|
||||||
|
| 问题 | 排查 |
|
||||||
|
|------|------|
|
||||||
|
| 点击打印无反应 | 检查 C-Lodop 服务是否运行(浏览器访问 `http://localhost:8000`) |
|
||||||
|
| 提示"请先配置条码打印机" | 进入打印机管理页面选择并保存打印机 |
|
||||||
|
| 条码打印位置偏移 | 使用调试模式打开打印设置对话框,调整边距参数 |
|
||||||
|
| 想重新弹出调试框 | 清除 `localStorage.printFlag` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*本文档随功能更新持续维护*
|
||||||
@ -1,384 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="employee-add">
|
|
||||||
<el-card>
|
|
||||||
<template>
|
|
||||||
<div class="card-header">
|
|
||||||
<span>添加代理</span>
|
|
||||||
<el-button @click="$router.push('/admin/employees')">
|
|
||||||
<el-icon>
|
|
||||||
<Back />
|
|
||||||
</el-icon>返回列表
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-steps :active="activeStep" finish-status="success" simple class="steps">
|
|
||||||
<el-step title="填写信息" />
|
|
||||||
<el-step title="确认信息" />
|
|
||||||
<el-step title="完成" />
|
|
||||||
</el-steps>
|
|
||||||
|
|
||||||
<!-- 第一步:填写信息 -->
|
|
||||||
<div v-if="activeStep === 1" class="step-content">
|
|
||||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" class="add-form">
|
|
||||||
<el-form-item label="姓名" prop="name">
|
|
||||||
<el-input v-model="form.name" placeholder="请输入代理姓名" :prefix-icon="User" clearable />
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="密码" prop="password">
|
|
||||||
<el-input v-model="form.password" type="password" placeholder="请输入密码(至少6位)" :prefix-icon="Lock"
|
|
||||||
show-password clearable />
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="确认密码" prop="confirmPassword">
|
|
||||||
<el-input v-model="form.confirmPassword" type="password" placeholder="请再次输入密码" :prefix-icon="Lock"
|
|
||||||
show-password clearable />
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="手机号" prop="phone">
|
|
||||||
<el-input v-model="form.phone" placeholder="请输入手机号" :prefix-icon="Lock" maxlength="11" clearable
|
|
||||||
show-word-limit />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="nextStep" :loading="checking">
|
|
||||||
下一步,确认信息
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<!-- 工号预览(动态生成) -->
|
|
||||||
<el-card shadow="never" class="preview-card">
|
|
||||||
<template #header>
|
|
||||||
<span>工号预览</span>
|
|
||||||
</template>
|
|
||||||
<div class="preview-content">
|
|
||||||
<div class="preview-item">
|
|
||||||
<span class="label">工号格式:</span>
|
|
||||||
<span class="value">5位数字,自动生成</span>
|
|
||||||
</div>
|
|
||||||
<div class="preview-item">
|
|
||||||
<span class="label">账号格式:</span>
|
|
||||||
<span class="value">dl_ + 工号(如 dl_00001)</span>
|
|
||||||
</div>
|
|
||||||
<div class="preview-item">
|
|
||||||
<span class="label">示例工号:</span>
|
|
||||||
<el-tag size="small">{{ previewEmployeeId }}</el-tag>
|
|
||||||
</div>
|
|
||||||
<div class="preview-item">
|
|
||||||
<span class="label">示例账号:</span>
|
|
||||||
<el-tag size="small" type="success">{{ previewUsername }}</el-tag>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 第二步:确认信息 -->
|
|
||||||
<div v-if="activeStep === 2" class="step-content">
|
|
||||||
<el-descriptions :column="1" border class="confirm-info">
|
|
||||||
<el-descriptions-item label="姓名">{{ form.name }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="工号">{{ previewEmployeeId }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="登录账号">{{ previewUsername }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="手机号">{{ form.phone }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="初始积分">0</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="角色">代理</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="状态">正常</el-descriptions-item>
|
|
||||||
</el-descriptions>
|
|
||||||
|
|
||||||
<div class="step-actions">
|
|
||||||
<el-button @click="prevStep">上一步</el-button>
|
|
||||||
<el-button type="primary" @click="submitForm" :loading="submitting">
|
|
||||||
确认添加
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 第三步:完成 -->
|
|
||||||
<div v-if="activeStep === 3" class="step-content result">
|
|
||||||
<el-result icon="success" :title="`添加成功`" :sub-title="`代理 ${resultData?.name} (${resultData?.username}) 已添加`">
|
|
||||||
<template #extra>
|
|
||||||
<div class="result-info" ref="resultInfoRef">
|
|
||||||
<el-alert type="info" :closable="false" show-icon>
|
|
||||||
<p class="title-center">进销存系统</p>
|
|
||||||
<p>登录网址:https://psi.buzhiyushu.cn/</p>
|
|
||||||
<p>工号:{{ resultData?.employee_id }}</p>
|
|
||||||
<p>账号:{{ resultData?.username }}</p>
|
|
||||||
<p>初始密码:{{ form.password }}</p>
|
|
||||||
<p>手机号:{{ form.phone }}</p>
|
|
||||||
<p style="color: #f56c6c; margin-top: 10px;">请妥善保管账号信息!</p>
|
|
||||||
</el-alert>
|
|
||||||
</div>
|
|
||||||
<div class="result-actions">
|
|
||||||
<el-button type="primary" @click="resetForm">继续添加</el-button>
|
|
||||||
<el-button @click="$router.push('/admin/employees')">查看列表</el-button>
|
|
||||||
<el-button type="success" @click="copyResultInfo">
|
|
||||||
<el-icon>
|
|
||||||
<DocumentCopy />
|
|
||||||
</el-icon>
|
|
||||||
一键复制
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-result>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, reactive, computed, onMounted } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
import { User, Lock, Back, DocumentCopy } from '@element-plus/icons-vue'
|
|
||||||
import request from '@/utils/request'
|
|
||||||
import { copyToClipboard } from '@/utils/clipboard'
|
|
||||||
import { useUserStore } from '@/store/user'
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const userStore = useUserStore()
|
|
||||||
const activeStep = ref(1)
|
|
||||||
const checking = ref(false)
|
|
||||||
const submitting = ref(false)
|
|
||||||
const formRef = ref(null)
|
|
||||||
const resultData = ref(null)
|
|
||||||
const resultInfoRef = ref(null)
|
|
||||||
|
|
||||||
// 表单数据
|
|
||||||
const form = reactive({
|
|
||||||
name: '',
|
|
||||||
password: '',
|
|
||||||
confirmPassword: '',
|
|
||||||
phone: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
// 表单验证规则
|
|
||||||
const validatePass = (rule, value, callback) => {
|
|
||||||
if (value === '') {
|
|
||||||
callback(new Error('请输入密码'))
|
|
||||||
} else if (value.length < 6) {
|
|
||||||
callback(new Error('密码长度不能小于6位'))
|
|
||||||
} else {
|
|
||||||
if (form.confirmPassword !== '') {
|
|
||||||
formRef.value?.validateField('confirmPassword')
|
|
||||||
}
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const validatePass2 = (rule, value, callback) => {
|
|
||||||
if (value === '') {
|
|
||||||
callback(new Error('请再次输入密码'))
|
|
||||||
} else if (value !== form.password) {
|
|
||||||
callback(new Error('两次输入密码不一致'))
|
|
||||||
} else {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rules = {
|
|
||||||
name: [
|
|
||||||
{ required: true, message: '请输入代理姓名', trigger: 'blur' },
|
|
||||||
{ min: 2, max: 20, message: '姓名长度应为2-20个字符', trigger: 'blur' }
|
|
||||||
],
|
|
||||||
password: [
|
|
||||||
{ validator: validatePass, trigger: 'blur' }
|
|
||||||
],
|
|
||||||
confirmPassword: [
|
|
||||||
{ validator: validatePass2, trigger: 'blur' }
|
|
||||||
],
|
|
||||||
phone: [
|
|
||||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
|
||||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 预览工号和账号(模拟)
|
|
||||||
const previewEmployeeId = ref('00001')
|
|
||||||
const previewUsername = computed(() => `dl_${previewEmployeeId.value}`)
|
|
||||||
|
|
||||||
// 获取下一个可用工号
|
|
||||||
const fetchNextEmployeeId = async () => {
|
|
||||||
try {
|
|
||||||
// 这里可以调用接口获取下一个工号
|
|
||||||
// 如果没有接口,使用模拟数据
|
|
||||||
const res = await request.get('/admin/employee/list', { params: { page: 1, page_size: 1 } })
|
|
||||||
if (res.code === 200 && res.data.list.length > 0) {
|
|
||||||
const lastId = res.data.list[0].employee_id
|
|
||||||
const seq = parseInt(lastId) + 1
|
|
||||||
previewEmployeeId.value = String(seq).padStart(5, '0')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// console.error('获取工号失败:', error)
|
|
||||||
// 使用模拟数据
|
|
||||||
previewEmployeeId.value = String(Math.floor(Math.random() * 90000 + 10000)).padStart(5, '0')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下一步
|
|
||||||
const nextStep = async () => {
|
|
||||||
if (!formRef.value) return
|
|
||||||
|
|
||||||
await formRef.value.validate()
|
|
||||||
activeStep.value = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上一步
|
|
||||||
const prevStep = () => {
|
|
||||||
activeStep.value = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交表单
|
|
||||||
const submitForm = async () => {
|
|
||||||
submitting.value = true
|
|
||||||
try {
|
|
||||||
// 动态构建请求参数,fid 来自 login 响应的 id,about_id 来自 login 响应的 about_id
|
|
||||||
const currentInfo = userStore.getAdminInfo()
|
|
||||||
const params = {
|
|
||||||
name: form.name,
|
|
||||||
password: form.password,
|
|
||||||
phone: form.phone,
|
|
||||||
}
|
|
||||||
if (currentInfo?.id) {
|
|
||||||
params.fid = currentInfo.id
|
|
||||||
}
|
|
||||||
if (currentInfo?.about_id) {
|
|
||||||
params.about_id = currentInfo.about_id
|
|
||||||
}
|
|
||||||
const res = await request.post('/admin/employee/add', params)
|
|
||||||
|
|
||||||
if (res.code === 200) {
|
|
||||||
resultData.value = res.data
|
|
||||||
activeStep.value = 3
|
|
||||||
ElMessage.success('添加成功')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// console.error('添加代理失败:', error)
|
|
||||||
ElMessage.error('添加失败,请重试')
|
|
||||||
} finally {
|
|
||||||
submitting.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置表单
|
|
||||||
const resetForm = () => {
|
|
||||||
form.name = ''
|
|
||||||
form.password = ''
|
|
||||||
form.confirmPassword = ''
|
|
||||||
form.phone = ''
|
|
||||||
activeStep.value = 1
|
|
||||||
resultData.value = null
|
|
||||||
fetchNextEmployeeId()
|
|
||||||
}
|
|
||||||
// 一键复制结果信息
|
|
||||||
const copyResultInfo = async () => {
|
|
||||||
if (!resultData.value) return
|
|
||||||
|
|
||||||
const text = `书海寻源代理系统
|
|
||||||
登录网址:https://wallet.buzhiyushu.cn/
|
|
||||||
工号:${resultData.value.employee_id}
|
|
||||||
账号:${resultData.value.username}
|
|
||||||
初始密码:${form.password}
|
|
||||||
手机号:${form.phone}
|
|
||||||
|
|
||||||
请妥善保管账号信息!`
|
|
||||||
|
|
||||||
await copyToClipboard(text, '账号信息已复制到剪贴板')
|
|
||||||
}
|
|
||||||
// 初始化
|
|
||||||
onMounted(() => {
|
|
||||||
fetchNextEmployeeId()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.employee-add {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.steps {
|
|
||||||
margin: 20px 0 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-content {
|
|
||||||
min-height: 300px;
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-form {
|
|
||||||
width: 500px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-card {
|
|
||||||
width: 500px;
|
|
||||||
margin: 30px auto 0;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-content {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-item {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-item .label {
|
|
||||||
width: 80px;
|
|
||||||
color: #666;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-item .value {
|
|
||||||
color: #333;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-info {
|
|
||||||
width: 500px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-actions {
|
|
||||||
margin-top: 30px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-info {
|
|
||||||
margin: 20px 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-actions {
|
|
||||||
margin-top: 20px;
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-descriptions__label) {
|
|
||||||
width: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-center {
|
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,494 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="employee-list">
|
|
||||||
<el-card>
|
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<span>代理列表</span>
|
|
||||||
<div class="header-actions">
|
|
||||||
<el-button type="primary" @click="$router.push('/admin/employees/add')">
|
|
||||||
<el-icon>
|
|
||||||
<Plus />
|
|
||||||
</el-icon>添加代理
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 搜索条件 -->
|
|
||||||
<div class="search-form">
|
|
||||||
<el-form :inline="true" :model="queryParams">
|
|
||||||
<el-form-item label="状态">
|
|
||||||
<el-select v-model="queryParams.status" placeholder="全部状态" clearable style="width: 150px">
|
|
||||||
<el-option label="正常" :value="1" />
|
|
||||||
<el-option label="禁用" :value="0" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="搜索">
|
|
||||||
<el-input v-model="queryParams.keyword" placeholder="工号/姓名/账号" clearable style="width: 200px"
|
|
||||||
:prefix-icon="Search" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="handleSearch">
|
|
||||||
<el-icon>
|
|
||||||
<Search />
|
|
||||||
</el-icon>搜索
|
|
||||||
</el-button>
|
|
||||||
<el-button @click="resetSearch">重置</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 统计卡片 -->
|
|
||||||
<el-row :gutter="20" class="stat-cards">
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-card shadow="hover" class="stat-card">
|
|
||||||
<div class="stat-item">
|
|
||||||
<div class="stat-label">总代理数</div>
|
|
||||||
<div class="stat-value">{{ total }}</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-card shadow="hover" class="stat-card">
|
|
||||||
<div class="stat-item">
|
|
||||||
<div class="stat-label">正常代理</div>
|
|
||||||
<div class="stat-value success">{{ activeCount }}</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-card shadow="hover" class="stat-card">
|
|
||||||
<div class="stat-item">
|
|
||||||
<div class="stat-label">总积分</div>
|
|
||||||
<div class="stat-value warning">{{ totalPoints }}</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-card shadow="hover" class="stat-card">
|
|
||||||
<div class="stat-item">
|
|
||||||
<div class="stat-label">平均积分</div>
|
|
||||||
<div class="stat-value info">{{ averagePoints }}</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<!-- 代理列表 -->
|
|
||||||
<el-table :data="employeeList" v-loading="loading" border style="width: 100%">
|
|
||||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
|
||||||
<el-table-column prop="employee_id" label="工号" width="100" align="center" />
|
|
||||||
<el-table-column prop="username" label="账号" width="150" align="center" />
|
|
||||||
<el-table-column prop="name" label="姓名" width="120" align="center" />
|
|
||||||
<el-table-column prop="phone" label="手机号" width="120" align="center" />
|
|
||||||
<el-table-column prop="score" label="积分" width="100" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span :class="{ 'points-warning': row.score < 100 }">{{ row.score }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small">
|
|
||||||
{{ row.status === 1 ? '正常' : '禁用' }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="code" label="机械码" width="240" align="center" />
|
|
||||||
<el-table-column prop="level_info" label="等级" width="60" align="center" />
|
|
||||||
<el-table-column prop="last_login_at" label="最后登录" width="160" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ row.last_login_at ? formatDate(row.last_login_at) : '-' }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="created_at" label="创建时间" width="160" align="center" >
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ formatDate(row.created_at) }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<!-- <el-table-column label="操作" width="320" fixed="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button type="primary" size="small" @click="showTopUp(row)">
|
|
||||||
<el-icon><Coin /></el-icon>充值
|
|
||||||
</el-button>
|
|
||||||
<el-button type="success" size="small" @click="viewAccess(row)">
|
|
||||||
<el-icon><DataLine /></el-icon>积分记录
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-if="row.status === 1"
|
|
||||||
type="warning"
|
|
||||||
size="small"
|
|
||||||
@click="toggleStatus(row, 'disable')"
|
|
||||||
>
|
|
||||||
<el-icon><Switch /></el-icon>禁用
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-else
|
|
||||||
type="success"
|
|
||||||
size="small"
|
|
||||||
@click="toggleStatus(row, 'enable')"
|
|
||||||
>
|
|
||||||
<el-icon><Check /></el-icon>启用
|
|
||||||
</el-button>
|
|
||||||
<el-button type="danger" size="small" @click="showDeduct(row)">
|
|
||||||
<el-icon><Coin /></el-icon>扣减
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column> -->
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<div class="pagination">
|
|
||||||
<el-pagination v-model:current-page="queryParams.page" v-model:page-size="queryParams.page_size" :total="total"
|
|
||||||
:page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper"
|
|
||||||
@size-change="handleSizeChange" @current-change="handleCurrentChange" />
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<!-- 充值弹窗 -->
|
|
||||||
<el-dialog v-model="topUpVisible" title="积分充值" width="400px">
|
|
||||||
<el-form :model="topUpForm" ref="topUpFormRef" :rules="topUpRules" label-width="80px">
|
|
||||||
<el-form-item label="代理">
|
|
||||||
<span>{{ currentEmployee?.name }} ({{ currentEmployee?.employee_id }})</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="当前积分">
|
|
||||||
<span>{{ currentEmployee?.points }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="充值数量" prop="amount">
|
|
||||||
<el-input-number v-model="topUpForm.amount" :min="1" :max="100000" style="width: 200px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="备注" prop="remark">
|
|
||||||
<el-input v-model="topUpForm.remark" placeholder="选填" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button @click="topUpVisible = false">取消</el-button>
|
|
||||||
<el-button type="primary" @click="submitTopUp" :loading="topUpLoading">确认充值</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
<!-- 扣减弹窗 -->
|
|
||||||
<el-dialog v-model="deductVisible" title="积分扣减" width="400px">
|
|
||||||
<el-form :model="deductForm" ref="deductFormRef" :rules="deductRules" label-width="80px">
|
|
||||||
<el-form-item label="代理">
|
|
||||||
<span>{{ currentEmployee?.name }} ({{ currentEmployee?.employee_id }})</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="当前积分">
|
|
||||||
<span>{{ currentEmployee?.points }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="扣减数量" prop="amount" class="deduct-label">
|
|
||||||
<el-input-number class="deduct-input" v-model="deductForm.amount" :min="1" :max="currentEmployee?.points || 1"
|
|
||||||
style="width: 200px" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="备注" prop="remark">
|
|
||||||
<el-input v-model="deductForm.remark" placeholder="选填" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button @click="deductVisible = false">取消</el-button>
|
|
||||||
<el-button type="danger" @click="submitDeduct" :loading="deductLoading">确认扣减</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, reactive, computed, onMounted } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
||||||
import { Plus, Search, Coin, DataLine, Switch, Check } from '@element-plus/icons-vue'
|
|
||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
const router = useRouter() // 导入 router
|
|
||||||
const loading = ref(false)
|
|
||||||
const employeeList = ref([])
|
|
||||||
const total = ref(0)
|
|
||||||
const topUpVisible = ref(false)
|
|
||||||
const topUpLoading = ref(false)
|
|
||||||
const deductVisible = ref(false)
|
|
||||||
const deductLoading = ref(false)
|
|
||||||
const currentEmployee = ref(null)
|
|
||||||
|
|
||||||
const queryParams = reactive({
|
|
||||||
page: 1,
|
|
||||||
page_size: 20,
|
|
||||||
status: '',
|
|
||||||
keyword: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const topUpForm = reactive({
|
|
||||||
amount: 100,
|
|
||||||
remark: ''
|
|
||||||
})
|
|
||||||
const deductForm = reactive({
|
|
||||||
amount: 100,
|
|
||||||
remark: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const topUpRules = {
|
|
||||||
amount: [
|
|
||||||
{ required: true, message: '请输入充值数量', trigger: 'blur' },
|
|
||||||
{ type: 'number', min: 1, message: '充值数量必须大于0', trigger: 'blur' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const deductRules = {
|
|
||||||
amount: [
|
|
||||||
{ required: true, message: '请输入扣减数量', trigger: 'blur' },
|
|
||||||
{ type: 'number', min: 1, message: '扣减数量必须大于0', trigger: 'blur' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
// 计算统计信息
|
|
||||||
const activeCount = computed(() => {
|
|
||||||
return employeeList.value.filter(e => e.status === 1).length
|
|
||||||
})
|
|
||||||
|
|
||||||
const totalPoints = computed(() => {
|
|
||||||
return employeeList.value.reduce((sum, e) => sum + e.score, 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
const averagePoints = computed(() => {
|
|
||||||
if (employeeList.value.length === 0) return 0
|
|
||||||
return Math.round(totalPoints.value / employeeList.value.length)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 格式化日期
|
|
||||||
const formatDate = (timestamp) => {
|
|
||||||
if (!timestamp) return '-'
|
|
||||||
const date = new Date(timestamp * 1000)
|
|
||||||
const year = date.getFullYear()
|
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
||||||
const day = String(date.getDate()).padStart(2, '0')
|
|
||||||
const hour = String(date.getHours()).padStart(2, '0')
|
|
||||||
const minute = String(date.getMinutes()).padStart(2, '0')
|
|
||||||
return `${year}-${month}-${day} ${hour}:${minute}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取代理列表
|
|
||||||
const fetchEmployeeList = async () => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const params = {
|
|
||||||
page: queryParams.page,
|
|
||||||
page_size: queryParams.page_size
|
|
||||||
}
|
|
||||||
if (queryParams.status !== '') {
|
|
||||||
params.status = queryParams.status
|
|
||||||
}
|
|
||||||
if (queryParams.keyword) {
|
|
||||||
params.keyword = queryParams.keyword
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await request.get('/admin/employee/list', { params })
|
|
||||||
if (res.code === 200) {
|
|
||||||
employeeList.value = res.data.list
|
|
||||||
total.value = res.data.total
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// console.error('获取代理列表失败:', error)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索
|
|
||||||
const handleSearch = () => {
|
|
||||||
queryParams.page = 1
|
|
||||||
fetchEmployeeList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置搜索
|
|
||||||
const resetSearch = () => {
|
|
||||||
queryParams.status = ''
|
|
||||||
queryParams.keyword = ''
|
|
||||||
queryParams.page = 1
|
|
||||||
fetchEmployeeList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分页大小变化
|
|
||||||
const handleSizeChange = (size) => {
|
|
||||||
queryParams.page_size = size
|
|
||||||
fetchEmployeeList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页码变化
|
|
||||||
const handleCurrentChange = (page) => {
|
|
||||||
queryParams.page = page
|
|
||||||
fetchEmployeeList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示充值弹窗
|
|
||||||
const showTopUp = (row) => {
|
|
||||||
currentEmployee.value = row
|
|
||||||
topUpForm.amount = 100
|
|
||||||
topUpForm.remark = ''
|
|
||||||
topUpVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交充值
|
|
||||||
const submitTopUp = async () => {
|
|
||||||
topUpLoading.value = true
|
|
||||||
try {
|
|
||||||
const res = await request.post(`/admin/employee/topup/${currentEmployee.value.employee_id}`, {
|
|
||||||
amount: topUpForm.amount,
|
|
||||||
remark: topUpForm.remark
|
|
||||||
})
|
|
||||||
if (res.code === 200) {
|
|
||||||
ElMessage.success('充值成功')
|
|
||||||
topUpVisible.value = false
|
|
||||||
fetchEmployeeList() // 刷新列表
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// console.error('充值失败:', error)
|
|
||||||
} finally {
|
|
||||||
topUpLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 显示扣减弹窗
|
|
||||||
const showDeduct = (row) => {
|
|
||||||
currentEmployee.value = row
|
|
||||||
deductForm.amount = 100
|
|
||||||
deductForm.remark = ''
|
|
||||||
deductVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交扣减
|
|
||||||
const submitDeduct = async () => {
|
|
||||||
deductLoading.value = true
|
|
||||||
try {
|
|
||||||
const res = await request.post(`/admin/employee/deduct/${currentEmployee.value.employee_id}`, {
|
|
||||||
amount: deductForm.amount,
|
|
||||||
remark: deductForm.remark
|
|
||||||
})
|
|
||||||
if (res.code === 200) {
|
|
||||||
ElMessage.success('扣减成功')
|
|
||||||
deductVisible.value = false
|
|
||||||
fetchEmployeeList() // 刷新列表
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// console.error('扣减失败:', error)
|
|
||||||
} finally {
|
|
||||||
deductLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 切换代理状态
|
|
||||||
const toggleStatus = async (row, action) => {
|
|
||||||
const actionText = action === 'enable' ? '启用' : '禁用'
|
|
||||||
try {
|
|
||||||
await ElMessageBox.confirm(`确定要${actionText}代理 ${row.name} 吗?`, '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
|
|
||||||
const res = await request.post(`/admin/employee/${action}/${row.employee_id}`)
|
|
||||||
if (res.code === 200) {
|
|
||||||
ElMessage.success(`${actionText}成功`)
|
|
||||||
fetchEmployeeList() // 刷新列表
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error !== 'cancel') {
|
|
||||||
// console.error('操作失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查看积分记录
|
|
||||||
const viewAccess = (row) => {
|
|
||||||
// 跳转到积分记录页面,并传递代理ID参数
|
|
||||||
router.push({
|
|
||||||
path: '/admin/access',
|
|
||||||
query: {
|
|
||||||
id: row.id,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化
|
|
||||||
onMounted(() => {
|
|
||||||
fetchEmployeeList()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.employee-list {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
/* 可根据需要保留原有的字体、颜色等样式 */
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-form {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-cards {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-item {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-label {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #909399;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-value {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-value.success {
|
|
||||||
color: #67c23a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-value.warning {
|
|
||||||
color: #e6a23c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-value.info {
|
|
||||||
color: #409eff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination {
|
|
||||||
margin-top: 20px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.points-warning {
|
|
||||||
color: #f56c6c;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deduct-input :deep(.el-input-number__decrease):hover,
|
|
||||||
.deduct-input :deep(.el-input-number__increase):hover {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #f56c6c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deduct-label :deep(.el-form-item__label) {
|
|
||||||
color: #f56c6c;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,213 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="login-container">
|
|
||||||
<div class="login-box">
|
|
||||||
<div class="login-header">
|
|
||||||
<h2>进销存系统</h2>
|
|
||||||
<p>请选择登录角色</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-tabs v-model="activeTab" class="login-tabs">
|
|
||||||
|
|
||||||
<el-tab-pane label="管理员登录" name="admin">
|
|
||||||
<el-form ref="adminFormRef" :model="adminForm" :rules="rules" label-width="0" class="login-form">
|
|
||||||
<el-form-item prop="username">
|
|
||||||
<el-input v-model="adminForm.username" placeholder="请输入账号" :prefix-icon="User"
|
|
||||||
size="large" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="password">
|
|
||||||
<el-input v-model="adminForm.password" type="password" placeholder="请输入密码"
|
|
||||||
:prefix-icon="Lock" size="large" show-password @keyup.enter="handleLogin" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" :loading="loading" class="login-btn" size="large"
|
|
||||||
@click="handleLogin">
|
|
||||||
登录
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-tab-pane>
|
|
||||||
|
|
||||||
<el-tab-pane label="代理登录" name="employee">
|
|
||||||
<el-form ref="employeeFormRef" :model="employeeForm" :rules="rules" label-width="0"
|
|
||||||
class="login-form">
|
|
||||||
<el-form-item prop="username">
|
|
||||||
<el-input v-model="employeeForm.username" placeholder="请输入账号" :prefix-icon="User"
|
|
||||||
size="large" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="password">
|
|
||||||
<el-input v-model="employeeForm.password" type="password" placeholder="请输入密码"
|
|
||||||
:prefix-icon="Lock" size="large" show-password @keyup.enter="handleLogin" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" :loading="loading" class="login-btn" size="large"
|
|
||||||
@click="handleLogin">
|
|
||||||
登录
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-tab-pane>
|
|
||||||
|
|
||||||
</el-tabs>
|
|
||||||
|
|
||||||
<!-- <div class="login-footer">
|
|
||||||
<p>默认账号:代理 init_00001 / 123456</p>
|
|
||||||
<p>管理员 init_00000 / admin123</p>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {
|
|
||||||
ref,
|
|
||||||
reactive
|
|
||||||
} from 'vue'
|
|
||||||
import {
|
|
||||||
useRouter
|
|
||||||
} from 'vue-router'
|
|
||||||
import {
|
|
||||||
ElMessage
|
|
||||||
} from 'element-plus'
|
|
||||||
import {
|
|
||||||
User,
|
|
||||||
Lock
|
|
||||||
} from '@element-plus/icons-vue'
|
|
||||||
import request from '@/utils/request'
|
|
||||||
import {
|
|
||||||
setAdminToken,
|
|
||||||
setAdminUserInfo
|
|
||||||
} from '@/utils/auth'
|
|
||||||
import { useUserStore } from '@/store/user'
|
|
||||||
|
|
||||||
const userStore = useUserStore()
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const activeTab = ref('admin')
|
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
// 添加 ref 引用
|
|
||||||
const employeeFormRef = ref(null)
|
|
||||||
const adminFormRef = ref(null)
|
|
||||||
|
|
||||||
const employeeForm = reactive({
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
about_id:0
|
|
||||||
// username: 'init_00001',
|
|
||||||
// password: '123456'
|
|
||||||
})
|
|
||||||
|
|
||||||
const adminForm = reactive({
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
about_id:0
|
|
||||||
// username: 'init_00000',
|
|
||||||
// password: 'admin123'
|
|
||||||
})
|
|
||||||
|
|
||||||
const rules = {
|
|
||||||
username: [{
|
|
||||||
required: true,
|
|
||||||
message: '请输入账号',
|
|
||||||
trigger: 'blur'
|
|
||||||
}],
|
|
||||||
password: [{
|
|
||||||
required: true,
|
|
||||||
message: '请输入密码',
|
|
||||||
trigger: 'blur'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
min: 6,
|
|
||||||
message: '密码长度不能小于6位',
|
|
||||||
trigger: 'blur'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleLogin = async () => {
|
|
||||||
const formRef = activeTab.value === 'employee' ? employeeFormRef : adminFormRef
|
|
||||||
const formData = activeTab.value === 'employee' ? employeeForm : adminForm
|
|
||||||
|
|
||||||
await formRef.value.validate()
|
|
||||||
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const type = activeTab.value === 'admin' ? '255' : '128'
|
|
||||||
const res = await request.post(`/login/${type}`, { ...formData, type: 1 })
|
|
||||||
|
|
||||||
if (res.code === 200) {
|
|
||||||
setAdminToken(res.data.token)
|
|
||||||
setAdminUserInfo(res.data)
|
|
||||||
userStore.setUserInfoAction(res.data)
|
|
||||||
// 每次登录刷新配置为默认值
|
|
||||||
localStorage.setItem('test_ip', '127.0.0.1')
|
|
||||||
localStorage.setItem('test_port', '8080')
|
|
||||||
ElMessage.success('登录成功')
|
|
||||||
// 所有用户登录后都跳转到仪表盘
|
|
||||||
router.push('/dashboard')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// console.error('登录失败:', error)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.login-container {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-box {
|
|
||||||
width: 400px;
|
|
||||||
padding: 40px;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-header h2 {
|
|
||||||
font-size: 24px;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-header p {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-tabs {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-btn {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-footer {
|
|
||||||
margin-top: 30px;
|
|
||||||
text-align: center;
|
|
||||||
color: #999;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-footer p {
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,475 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="review-page">
|
|
||||||
<el-card class="section-card">
|
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<span>异常书目审核</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 搜索筛选栏 -->
|
|
||||||
<div class="search-bar">
|
|
||||||
<div class="search-item">
|
|
||||||
<span>ISBN:</span>
|
|
||||||
<el-input v-model="searchForm.isbn" placeholder="请输入ISBN" style="width: 180px" clearable />
|
|
||||||
</div>
|
|
||||||
<div class="search-item">
|
|
||||||
<span>状态:</span>
|
|
||||||
<el-select v-model="searchForm.status" placeholder="请选择状态" style="width: 140px" clearable>
|
|
||||||
<el-option label="待审核" value="0" />
|
|
||||||
<el-option label="已通过" value="1" />
|
|
||||||
<el-option label="已驳回" value="2" />
|
|
||||||
</el-select>
|
|
||||||
</div>
|
|
||||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
|
||||||
<el-button @click="handleReset">重置</el-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 数据表格 -->
|
|
||||||
<el-table :data="tableData" v-loading="loading" border stripe style="width: 100%"
|
|
||||||
@selection-change="handleSelectionChange">
|
|
||||||
<el-table-column prop="status" label="状态" width="150" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-tag :type="statusTagType(row.status)" size="small">
|
|
||||||
{{ statusText(row.status) }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="barcode" label="ISBN" width="160" align="center" />
|
|
||||||
<el-table-column label="书名" min-width="200" show-overflow-tooltip align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
|
||||||
<template #reference>
|
|
||||||
<span :class="{ 'diff-cell': isChanged(row.old_name, row.new_name) }">{{ row.old_name }}</span>
|
|
||||||
</template>
|
|
||||||
<div class="diff-content">
|
|
||||||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_name }}</span></div>
|
|
||||||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_name }}</span></div>
|
|
||||||
</div>
|
|
||||||
</el-popover>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="作者" width="150" show-overflow-tooltip align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
|
||||||
<template #reference>
|
|
||||||
<span :class="{ 'diff-cell': isChanged(row.old_author, row.new_author) }">{{ row.old_author }}</span>
|
|
||||||
</template>
|
|
||||||
<div class="diff-content">
|
|
||||||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_author }}</span></div>
|
|
||||||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_author }}</span></div>
|
|
||||||
</div>
|
|
||||||
</el-popover>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="出版社" width="150" show-overflow-tooltip align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
|
||||||
<template #reference>
|
|
||||||
<span :class="{ 'diff-cell': isChanged(row.old_publisher, row.new_publisher) }">{{ row.old_publisher }}</span>
|
|
||||||
</template>
|
|
||||||
<div class="diff-content">
|
|
||||||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_publisher }}</span></div>
|
|
||||||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_publisher }}</span></div>
|
|
||||||
</div>
|
|
||||||
</el-popover>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="套装书" width="150" show-overflow-tooltip align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
|
||||||
<template #reference>
|
|
||||||
<el-tag type="info" size="small" :class="{ 'diff-cell': isChanged(row.old_is_suit, row.new_is_suit) }">
|
|
||||||
{{ row.old_is_suit === 0 ? '非套装书' : '套装书' }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
|
||||||
<div class="diff-content">
|
|
||||||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_is_suit === 0 ? '非套装书' : '套装书' }}</span></div>
|
|
||||||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_is_suit === 0 ? '非套装书' : '套装书' }}</span></div>
|
|
||||||
</div>
|
|
||||||
</el-popover>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="定价" width="150" show-overflow-tooltip align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
|
||||||
<template #reference>
|
|
||||||
<span :class="{ 'diff-cell': isChanged(row.old_price, row.new_price) }">{{ row.old_price }}</span>
|
|
||||||
</template>
|
|
||||||
<div class="diff-content">
|
|
||||||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_price }}</span></div>
|
|
||||||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_price }}</span></div>
|
|
||||||
</div>
|
|
||||||
</el-popover>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="装帧" width="150" show-overflow-tooltip align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
|
||||||
<template #reference>
|
|
||||||
<span :class="{ 'diff-cell': isChanged(row.old_binding_layout, row.new_binding_layout) }">{{ row.old_binding_layout }}</span>
|
|
||||||
</template>
|
|
||||||
<div class="diff-content">
|
|
||||||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_binding_layout }}</span></div>
|
|
||||||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_binding_layout }}</span></div>
|
|
||||||
</div>
|
|
||||||
</el-popover>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="页数" width="150" show-overflow-tooltip align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
|
||||||
<template #reference>
|
|
||||||
<span :class="{ 'diff-cell': isChanged(row.old_page_count, row.new_page_count) }">{{ row.old_page_count }}</span>
|
|
||||||
</template>
|
|
||||||
<div class="diff-content">
|
|
||||||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_page_count }}</span></div>
|
|
||||||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_page_count }}</span></div>
|
|
||||||
</div>
|
|
||||||
</el-popover>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="字数" width="150" show-overflow-tooltip align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
|
||||||
<template #reference>
|
|
||||||
<span :class="{ 'diff-cell': isChanged(row.old_word_count, row.new_word_count) }">{{ row.old_word_count }}</span>
|
|
||||||
</template>
|
|
||||||
<div class="diff-content">
|
|
||||||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_word_count }}</span></div>
|
|
||||||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_word_count }}</span></div>
|
|
||||||
</div>
|
|
||||||
</el-popover>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="出版时间" width="150" show-overflow-tooltip align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
|
||||||
<template #reference>
|
|
||||||
<span :class="{ 'diff-cell': isPubTimeChanged(row.old_publication_time, row.new_publication_time) }">{{ formatPubTime(row.old_publication_time) }}</span>
|
|
||||||
</template>
|
|
||||||
<div class="diff-content">
|
|
||||||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ formatPubTime(row.old_publication_time) }}</span></div>
|
|
||||||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ formatPubTime(row.new_publication_time) }}</span></div>
|
|
||||||
</div>
|
|
||||||
</el-popover>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="提交时间" width="180" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ formatDateTime(row.created_at) }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="180" fixed="right" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button type="primary" @click="handleApprove(row)">
|
|
||||||
查看
|
|
||||||
</el-button>
|
|
||||||
<el-button type="danger" @click="handleReject(row)">
|
|
||||||
删除
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<div class="pagination-wrapper">
|
|
||||||
<el-pagination v-model:current-page="pagination.current" v-model:page-size="pagination.pageSize"
|
|
||||||
:page-sizes="[10, 20, 50, 100]" :total="pagination.total" layout="total, sizes, prev, pager, next, jumper"
|
|
||||||
@size-change="handlePageChange" @current-change="handlePageChange" />
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<!-- 查看详情弹窗 -->
|
|
||||||
<el-dialog v-model="detailVisible" title="调剂记录详情" width="60%" :close-on-click-modal="false">
|
|
||||||
<template v-if="detailData">
|
|
||||||
<el-descriptions :column="2" border>
|
|
||||||
<el-descriptions-item label="ISBN">{{ detailData.isbn }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="书名">{{ detailData.bookName }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="作者">{{ detailData.author }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="出版社">{{ detailData.publisher }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="异常类型">{{ detailData.abnormalType }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="状态">
|
|
||||||
<el-tag :type="statusTagType(detailData.status)" size="small">
|
|
||||||
{{ statusText(detailData.status) }}
|
|
||||||
</el-tag>
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="异常说明" :span="2">
|
|
||||||
{{ detailData.abnormalDesc || '无' }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="提交时间">{{ detailData.createdAt }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item v-if="detailData.reviewTime" label="审核时间">{{ detailData.reviewTime
|
|
||||||
}}</el-descriptions-item>
|
|
||||||
</el-descriptions>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<!-- 审核弹窗 - 使用 SubmIllegalBook 组件 -->
|
|
||||||
<SubmIllegalBook
|
|
||||||
v-model:visible="reviewDialogVisible"
|
|
||||||
:review-data="reviewRecord"
|
|
||||||
:goods="null"
|
|
||||||
@review="handleReviewResult"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
||||||
import request from '@/utils/request'
|
|
||||||
import SubmIllegalBook from '@/components/dialog/submIllegalBook/SubmIllegalBook.vue'
|
|
||||||
|
|
||||||
// 搜索表单
|
|
||||||
const searchForm = reactive({
|
|
||||||
isbn: '',
|
|
||||||
status: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
// 表格数据
|
|
||||||
const tableData = ref<any[]>([])
|
|
||||||
const loading = ref(false)
|
|
||||||
const selectedRows = ref<any[]>([])
|
|
||||||
|
|
||||||
// 分页
|
|
||||||
const pagination = reactive({
|
|
||||||
current: 1,
|
|
||||||
pageSize: 20,
|
|
||||||
total: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
// 详情弹窗
|
|
||||||
const detailVisible = ref(false)
|
|
||||||
const detailData = ref<any>(null)
|
|
||||||
|
|
||||||
// 审核弹窗
|
|
||||||
const reviewDialogVisible = ref(false)
|
|
||||||
const reviewRecord = ref<any>(null)
|
|
||||||
|
|
||||||
// 状态标签样式
|
|
||||||
function statusTagType(status: string): string {
|
|
||||||
const map: Record<string, string> = {
|
|
||||||
0: 'warning',
|
|
||||||
1: 'success',
|
|
||||||
2: 'danger'
|
|
||||||
}
|
|
||||||
return map[status] || 'info'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化出版时间为 yyyy-mm(输入为10位时间戳,秒级)
|
|
||||||
function formatPubTime(time: any): string {
|
|
||||||
if (time === null || time === undefined || time === '') return ''
|
|
||||||
const ts = Number(time)
|
|
||||||
if (isNaN(ts) || ts <= 0) return ''
|
|
||||||
const d = new Date(ts * 1000)
|
|
||||||
const y = d.getFullYear()
|
|
||||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
|
||||||
return `${y}-${m}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化为完整时间 yyyy-mm-dd HH:mm:ss(输入为10位时间戳,秒级)
|
|
||||||
function formatDateTime(time: any): string {
|
|
||||||
if (time === null || time === undefined || time === '') return ''
|
|
||||||
const ts = Number(time)
|
|
||||||
if (isNaN(ts) || ts <= 0) return ''
|
|
||||||
const d = new Date(ts * 1000)
|
|
||||||
const y = d.getFullYear()
|
|
||||||
const M = String(d.getMonth() + 1).padStart(2, '0')
|
|
||||||
const day = String(d.getDate()).padStart(2, '0')
|
|
||||||
const h = String(d.getHours()).padStart(2, '0')
|
|
||||||
const m = String(d.getMinutes()).padStart(2, '0')
|
|
||||||
const s = String(d.getSeconds()).padStart(2, '0')
|
|
||||||
return `${y}-${M}-${day} ${h}:${m}:${s}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断新旧数据是否不同
|
|
||||||
function isChanged(oldVal: any, newVal: any): boolean {
|
|
||||||
// 如果新旧值都是空值,视为相同
|
|
||||||
if ((oldVal === null || oldVal === undefined || oldVal === '') &&
|
|
||||||
(newVal === null || newVal === undefined || newVal === '')) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return String(oldVal) !== String(newVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断出版时间是否不同(处理时间戳空值情况)
|
|
||||||
function isPubTimeChanged(oldTs: any, newTs: any): boolean {
|
|
||||||
return isChanged(formatPubTime(oldTs), formatPubTime(newTs))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 状态文本
|
|
||||||
function statusText(status: string): string {
|
|
||||||
const map: Record<string, string> = {
|
|
||||||
0: '待审核',
|
|
||||||
1: '已通过',
|
|
||||||
2: '已驳回'
|
|
||||||
}
|
|
||||||
return map[status] || status
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询
|
|
||||||
function handleSearch() {
|
|
||||||
pagination.current = 1
|
|
||||||
fetchData()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置
|
|
||||||
function handleReset() {
|
|
||||||
searchForm.isbn = ''
|
|
||||||
searchForm.status = ''
|
|
||||||
handleSearch()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 多选
|
|
||||||
function handleSelectionChange(rows: any[]) {
|
|
||||||
selectedRows.value = rows
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分页变化
|
|
||||||
function handlePageChange() {
|
|
||||||
fetchData()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取数据
|
|
||||||
async function fetchData() {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const res = await request.get('/product_log/list', {
|
|
||||||
params: {
|
|
||||||
barcode: searchForm.isbn || undefined,
|
|
||||||
status: searchForm.status || undefined,
|
|
||||||
page: pagination.current,
|
|
||||||
page_size: pagination.pageSize
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const data = res?.data
|
|
||||||
if (data) {
|
|
||||||
tableData.value = Array.isArray(data.list) ? data.list : Array.isArray(data) ? data : []
|
|
||||||
pagination.total = data.total || 0
|
|
||||||
} else {
|
|
||||||
tableData.value = []
|
|
||||||
pagination.total = 0
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
ElMessage.error('获取列表失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 审核 - 打开审核弹窗
|
|
||||||
function handleApprove(row: any) {
|
|
||||||
reviewRecord.value = row
|
|
||||||
reviewDialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除
|
|
||||||
async function handleReject(row: any) {
|
|
||||||
try {
|
|
||||||
await ElMessageBox.confirm('确认删除该条记录?', '提示', {
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('id', String(row.id))
|
|
||||||
await request.post('/product_log/delete', formData, {
|
|
||||||
headers: { 'Content-Type': 'multipart/form-data' }
|
|
||||||
})
|
|
||||||
ElMessage.success('已删除')
|
|
||||||
fetchData()
|
|
||||||
} catch {
|
|
||||||
// 取消
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 审核结果处理 - 弹窗关闭后刷新列表
|
|
||||||
function handleReviewResult(action: 'approve' | 'reject', record: any) {
|
|
||||||
fetchData()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查看详情
|
|
||||||
function handleViewDetail(row: any) {
|
|
||||||
detailData.value = row
|
|
||||||
detailVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
fetchData()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.review-page {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-card {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-item span {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #606266;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* 新旧数据对比 - 单元格变红 */
|
|
||||||
.diff-cell {
|
|
||||||
color: #f56c6c !important;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Popover 内容样式(全局,因 popover 渲染在 body 下) */
|
|
||||||
.diff-popover {
|
|
||||||
padding: 8px 12px !important;
|
|
||||||
}
|
|
||||||
.diff-content {
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
.diff-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 2px 0;
|
|
||||||
}
|
|
||||||
.diff-label {
|
|
||||||
white-space: nowrap;
|
|
||||||
color: #909399;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.diff-val {
|
|
||||||
color: #303133;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
.diff-row.new .diff-val {
|
|
||||||
color: #e6a23c;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,771 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-card class="shipping-order-manager">
|
|
||||||
<template #header>
|
|
||||||
<div class="card-header">发货单管理</div>
|
|
||||||
</template>
|
|
||||||
<div class="filter-bar">
|
|
||||||
<el-input v-model="searchParams.keyword" placeholder="发货单号" clearable style="width: 200px"
|
|
||||||
@keyup.enter="handleSearch">
|
|
||||||
<template #prefix>
|
|
||||||
<el-icon>
|
|
||||||
<Search />
|
|
||||||
</el-icon>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
<el-select v-model="searchParams.status" placeholder="状态" clearable style="width: 120px">
|
|
||||||
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
||||||
</el-select>
|
|
||||||
<el-select v-model="searchParams.warehouse_id" placeholder="仓库" clearable style="width: 140px">
|
|
||||||
<el-option v-for="item in warehouseOptions" :key="item.id" :label="item.name" :value="item.id" />
|
|
||||||
</el-select>
|
|
||||||
<el-select v-model="searchParams.customer_id" placeholder="客户" clearable filterable style="width: 160px">
|
|
||||||
<el-option v-for="item in customerOptions" :key="item.id" :label="item.name" :value="item.id" />
|
|
||||||
</el-select>
|
|
||||||
<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
|
|
||||||
<el-button :icon="Refresh" @click="resetSearch">重置</el-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-table :data="tableData" v-loading="loading" border stripe style="width: 100%"
|
|
||||||
@expand-change="handleExpandChange">
|
|
||||||
|
|
||||||
<!-- 发货单详情展开行 -->
|
|
||||||
<el-table-column type="expand">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<div v-if="detailCache[row.id]" style="padding: 12px 20px">
|
|
||||||
<h4 style="margin: 0 0 10px; font-size: 14px; color: #303133">发货明细</h4>
|
|
||||||
<el-table :data="detailCache[row.id].items || []" border size="small">
|
|
||||||
<el-table-column prop="product_name" label="商品名称" min-width="120" show-overflow-tooltip align="center" />
|
|
||||||
<el-table-column prop="product_code" label="ISBN/条码" min-width="100" show-overflow-tooltip
|
|
||||||
align="center" />
|
|
||||||
<el-table-column label="库位" min-width="100" align="center">
|
|
||||||
<template #default="{ row: item }">
|
|
||||||
{{ locationMap[item.warehouse_code] || item.warehouse_code || '-' }}##{{
|
|
||||||
locationMap[item.location_name]
|
|
||||||
|| item.location_name || '-' }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="销售单号" min-width="140" align="center">
|
|
||||||
<template #default="{ row: item }">
|
|
||||||
<a v-if="item.sales_order_no" style="color: #409eff; cursor: pointer; text-decoration: underline;"
|
|
||||||
@click.stop="navigateToSalesOrder(item.sales_order_no)">
|
|
||||||
{{ item.sales_order_no }}
|
|
||||||
</a>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="出库单号" min-width="140" align="center">
|
|
||||||
<template #default="{ row: item }">
|
|
||||||
<a v-if="item.outbound_order_no" style="color: #409eff; cursor: pointer; text-decoration: underline;"
|
|
||||||
@click.stop="navigateToOutbound(item.outbound_order_no)">
|
|
||||||
{{ item.outbound_order_no }}
|
|
||||||
</a>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="association_order_no" label="平台单号" min-width="90" align="center">
|
|
||||||
<template #default="{ row: item }">
|
|
||||||
<span style="display: inline-flex; align-items: center; gap: 4px;">
|
|
||||||
<span v-if="item.association_order_no" style="color: #409eff; text-decoration: underline;">{{ item.association_order_no }}</span>
|
|
||||||
<span v-else>-</span>
|
|
||||||
<el-button v-if="item.association_order_no" type="primary" size="small" link
|
|
||||||
@click="copyToClipboard(item.association_order_no, '平台单号已复制')">
|
|
||||||
<el-icon>
|
|
||||||
<CopyDocument />
|
|
||||||
</el-icon>
|
|
||||||
</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="销售时间" min-width="100" align="center">
|
|
||||||
<template #default="{ row: item }">{{ formatDateForSale(item.sales_order_created_at) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="售价" min-width="40" align="center">
|
|
||||||
<template #default="{ row: item }">
|
|
||||||
<span style="color: #e6a23c; font-weight: 600">{{ formatAmount(item.unit_price) }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="quantity" label="出库数量" min-width="60" align="center">
|
|
||||||
<template #default="{ row: item }">
|
|
||||||
<span style="color: #409eff; font-weight: 600">{{ item.quantity }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="快递公司" min-width="100" align="center">
|
|
||||||
<template #default="{ row: item }">{{ logisticsCompanyMap[item.logistics_company] ||
|
|
||||||
item.logistics_company || '-' }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="logistics_no" label="快递单号" min-width="100" align="center">
|
|
||||||
<template #default="{ row: item }">{{ item.logistics_no || '-' }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="receiver_address" label="收货地址" min-width="100" align="center">
|
|
||||||
<template #default="{ row: item }">{{ item.receiver_address || '-' }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
<div v-else style="padding: 20px; text-align: center; color: #909399">
|
|
||||||
<el-icon class="is-loading" style="margin-right: 6px">
|
|
||||||
<Loading />
|
|
||||||
</el-icon>加载中...
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column prop="shipping_no" label="发货单号" min-width="160" show-overflow-tooltip align="center" />
|
|
||||||
<el-table-column label="客户" min-width="160" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<template v-if="row.shop_list && row.shop_list.length > 0">
|
|
||||||
<div v-for="(shop, idx) in row.shop_list" :key="idx">
|
|
||||||
{{ shop.shop_name }}({{ shop.shop_type_text }})
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="status_text" label="状态" min-width="80" show-overflow-tooltip align="center" />
|
|
||||||
<el-table-column prop="operator" label="操作员" min-width="90" align="center">
|
|
||||||
<template #default="{ row }">{{ row.operator || '-' }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="创建时间" min-width="150" align="center">
|
|
||||||
<template #default="{ row }">{{ formatTimestamp(row.created_at) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip align="center">
|
|
||||||
<template #default="{ row }">{{ row.remark || '-' }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" align="center" width="100">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button type="primary" size="small" link @click="handleStartShipping(row)">开始发货</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="" align="center" width="70">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button type="primary" size="small" link>打单</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<div class="pagination-wrapper">
|
|
||||||
<el-pagination v-model:current-page="pagination.current" v-model:page-size="pagination.pageSize"
|
|
||||||
:page-sizes="[10, 20, 50, 100]" :total="pagination.total" layout="total, sizes, prev, pager, next, jumper"
|
|
||||||
@size-change="handleSizeChange" @current-change="handleCurrentChange" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 扫码发货弹窗 -->
|
|
||||||
<el-dialog v-model="scanDialogVisible" title="扫码发货" width="1000px" :close-on-click-modal="false"
|
|
||||||
@opened="onScanDialogOpened" @closed="onScanDialogClosed">
|
|
||||||
<div style="padding: 10px">
|
|
||||||
<p style="margin-bottom: 12px; color: #606266; text-align: center">
|
|
||||||
共 <strong style="color:#409eff">{{ currentScanTotal }}</strong> 件商品,已扫描 <strong style="color:#67c23a">{{
|
|
||||||
currentScanIndex }}</strong> 件
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<el-table :data="scanList" border size="small" style="width: 100%" max-height="340"
|
|
||||||
:row-class-name="scanRowClassName">
|
|
||||||
<el-table-column type="index" label="#" width="40" align="center" />
|
|
||||||
<el-table-column prop="product_name" label="商品名称" min-width="130" show-overflow-tooltip />
|
|
||||||
<el-table-column prop="product_code" label="ISBN" width="120" align="center" />
|
|
||||||
<el-table-column prop="quantity" label="数量" width="60" align="center" />
|
|
||||||
<el-table-column prop="association_order_no" label="平台单号" min-width="150" align="center"
|
|
||||||
show-overflow-tooltip>
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span v-if="row.association_order_no" style="display: inline-flex; align-items: center; gap: 4px;">
|
|
||||||
<span style="color: #409eff; text-decoration: underline;">{{ row.association_order_no }}</span>
|
|
||||||
<el-button type="primary" size="small" link
|
|
||||||
@click="copyToClipboard(row.association_order_no, '平台单号已复制')">
|
|
||||||
<el-icon>
|
|
||||||
<CopyDocument />
|
|
||||||
</el-icon>
|
|
||||||
</el-button>
|
|
||||||
</span>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="快递单号" min-width="120" align="center">
|
|
||||||
<template #default="{ row, $index }">
|
|
||||||
<span v-if="$index < currentScanIndex && row.logistics_no" style="color: #67c23a;">{{ row.logistics_no
|
|
||||||
}}</span>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="售价" width="80" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span style="color: #e6a23c; font-weight: 600">{{ formatAmount(row.unit_price) }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="状态" width="90" align="center">
|
|
||||||
<template #default="{ row, $index }">
|
|
||||||
<template v-if="$index < currentScanIndex || row.logistics_no">
|
|
||||||
<el-tag type="success" size="small">已扫</el-tag>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="$index === processingIndex">
|
|
||||||
<el-tag type="warning" size="small" effect="dark">扫描中</el-tag>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<el-tag type="info" size="small">待扫</el-tag>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<el-input ref="scanInputRef" v-model="scanCode" placeholder="请用扫码枪扫描 ISBN..." @keyup.enter="handleScanSubmit"
|
|
||||||
class="hidden-scan-input" />
|
|
||||||
<p style="margin-top: 8px; color: #c0c4cc; font-size: 12px; text-align: center">请使用扫码枪扫描商品条码</p>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
</el-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref, reactive, onMounted, computed, nextTick } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
import { Search, Refresh, View, Loading, CopyDocument } from '@element-plus/icons-vue'
|
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import axios from 'axios'
|
|
||||||
import { copyToClipboard } from '@/utils/clipboard'
|
|
||||||
import { fetchShippingOrderList, fetchShippingOrderDetail, updateShippingOrderLogistics } from '@/api/shipping-order'
|
|
||||||
import { fetchWarehouseList } from '@/api/warehouse'
|
|
||||||
import { createPrintTask } from '@/api/print'
|
|
||||||
|
|
||||||
/** 状态映射 */
|
|
||||||
const STATUS_MAP: Record<number, { label: string; type: string }> = {
|
|
||||||
1: { label: '已创建', type: 'info' },
|
|
||||||
2: { label: '拣货中', type: 'warning' },
|
|
||||||
3: { label: '已完成', type: 'success' },
|
|
||||||
4: { label: '已取消', type: 'danger' }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'ShippingOrder',
|
|
||||||
setup() {
|
|
||||||
const router = useRouter()
|
|
||||||
const loading = ref<boolean>(false)
|
|
||||||
const tableData = ref<any[]>([])
|
|
||||||
|
|
||||||
/** 导航到销售订单页面 */
|
|
||||||
const navigateToSalesOrder = (salesOrderNo: string) => {
|
|
||||||
router.push({ name: 'sales-order', query: { keyword: salesOrderNo } })
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 导航到出库单页面 */
|
|
||||||
const navigateToOutbound = (outboundOrderNo: string) => {
|
|
||||||
router.push({ name: 'outbound', query: { keyword: outboundOrderNo } })
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusOptions = Object.entries(STATUS_MAP).map(([value, { label }]) => ({
|
|
||||||
value: Number(value),
|
|
||||||
label
|
|
||||||
}))
|
|
||||||
|
|
||||||
const searchParams = reactive<{
|
|
||||||
keyword: string
|
|
||||||
status: number | null
|
|
||||||
warehouse_id: number | null
|
|
||||||
customer_id: number | null
|
|
||||||
}>({
|
|
||||||
keyword: '',
|
|
||||||
status: null,
|
|
||||||
warehouse_id: null,
|
|
||||||
customer_id: null
|
|
||||||
})
|
|
||||||
|
|
||||||
const pagination = reactive({
|
|
||||||
current: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
const detailVisible = ref<boolean>(false)
|
|
||||||
const detailData = ref<any>(null)
|
|
||||||
|
|
||||||
// 下拉选项
|
|
||||||
const warehouseOptions = ref<any[]>([])
|
|
||||||
const customerOptions = ref<any[]>([])
|
|
||||||
|
|
||||||
// ID→名称映射
|
|
||||||
const warehouseMap = ref<Record<string, string>>({})
|
|
||||||
const customerMap = ref<Record<string, string>>({})
|
|
||||||
const salesOrderMap = ref<Record<string, string>>({})
|
|
||||||
const waveTaskMap = ref<Record<string, string>>({})
|
|
||||||
const locationMap = ref<Record<string, string>>({})
|
|
||||||
|
|
||||||
// 详情缓存
|
|
||||||
const detailCache = ref<Record<number, any>>({})
|
|
||||||
|
|
||||||
// ========== 扫码发货相关 ==========
|
|
||||||
const scanDialogVisible = ref(false)
|
|
||||||
const scanCode = ref('')
|
|
||||||
const scanInputRef = ref<any>(null)
|
|
||||||
const scanList = ref<any[]>([]) // 当前发货单的明细列表
|
|
||||||
const currentScanIndex = ref(0) // 当前扫描到的索引
|
|
||||||
const currentScanTotal = ref(0) // 总数量
|
|
||||||
const scanFocusTimer = ref<ReturnType<typeof setInterval> | null>(null)
|
|
||||||
|
|
||||||
const currentScanItem = computed(() => scanList.value[currentScanIndex.value] || null)
|
|
||||||
|
|
||||||
const formatAmount = (amount: number): string => {
|
|
||||||
if (!amount && amount !== 0) return '¥0.00'
|
|
||||||
return '¥' + (Number(amount) / 100).toFixed(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 扫描防并发锁
|
|
||||||
const isProcessing = ref(false)
|
|
||||||
const processingIndex = ref(-1)
|
|
||||||
|
|
||||||
/** 已扫描完成的行添加背景色 */
|
|
||||||
const scanRowClassName = ({ row, rowIndex }: { row: any; rowIndex: number }) => {
|
|
||||||
if (rowIndex < currentScanIndex.value || row.logistics_no) {
|
|
||||||
return 'scan-completed-row'
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 点击 "开始发货" */
|
|
||||||
const handleStartShipping = async (row: any) => {
|
|
||||||
// 优先用缓存,否则重新请求
|
|
||||||
let detail = detailCache.value[row.id]
|
|
||||||
if (!detail || !detail.items) {
|
|
||||||
try {
|
|
||||||
detail = await fetchShippingOrderDetail(row.id)
|
|
||||||
detailCache.value[row.id] = detail
|
|
||||||
} catch (e) {
|
|
||||||
ElMessage.error('加载发货明细失败')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const items = detail?.items || []
|
|
||||||
if (items.length === 0) {
|
|
||||||
ElMessage.warning('该发货单没有明细')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
scanList.value = items.map((item: any) => ({ ...item, shipping_order_id: row.id }))
|
|
||||||
// 根据实际数据设置初始状态:已有物流单号的视为已扫描
|
|
||||||
const firstUnscanned = scanList.value.findIndex((item: any) => !item.logistics_no)
|
|
||||||
currentScanIndex.value = firstUnscanned >= 0 ? firstUnscanned : scanList.value.length
|
|
||||||
currentScanTotal.value = items.length
|
|
||||||
scanCode.value = ''
|
|
||||||
scanDialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 弹窗打开后聚焦并锁定焦点到隐藏 input */
|
|
||||||
const onScanDialogOpened = () => {
|
|
||||||
const focusInput = () => {
|
|
||||||
scanInputRef.value?.focus?.()
|
|
||||||
scanInputRef.value?.$el?.querySelector?.('input')?.focus?.()
|
|
||||||
}
|
|
||||||
// 立刻聚焦一次
|
|
||||||
nextTick(() => { focusInput() })
|
|
||||||
// 每隔 200ms 强制保持焦点(直到弹窗关闭)
|
|
||||||
const intervalId = setInterval(() => {
|
|
||||||
if (!scanDialogVisible.value) {
|
|
||||||
clearInterval(intervalId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 检查当前焦点是否已不在该输入框上,是则重新聚焦
|
|
||||||
const active = document.activeElement
|
|
||||||
const inputEl = scanInputRef.value?.$el?.querySelector?.('input')
|
|
||||||
if (inputEl && active !== inputEl) {
|
|
||||||
inputEl.focus()
|
|
||||||
}
|
|
||||||
}, 200)
|
|
||||||
// dialog 关闭时清理 interval
|
|
||||||
const origClose = onScanDialogClosed
|
|
||||||
const cleanup = () => {
|
|
||||||
clearInterval(intervalId)
|
|
||||||
origClose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 弹窗关闭时清理 */
|
|
||||||
const onScanDialogClosed = () => {
|
|
||||||
scanCode.value = ''
|
|
||||||
scanList.value = []
|
|
||||||
currentScanIndex.value = 0
|
|
||||||
currentScanTotal.value = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 扫码回车处理 */
|
|
||||||
const handleScanSubmit = async () => {
|
|
||||||
const code = scanCode.value.trim()
|
|
||||||
if (!code) return
|
|
||||||
|
|
||||||
// 如果正在处理中,忽略新的扫码
|
|
||||||
if (isProcessing.value) return
|
|
||||||
|
|
||||||
// 锁定
|
|
||||||
isProcessing.value = true
|
|
||||||
processingIndex.value = currentScanIndex.value
|
|
||||||
|
|
||||||
// 在整个发货单明细中查找 ISBN 匹配的商品
|
|
||||||
const matchIndex = scanList.value.findIndex(
|
|
||||||
(item: any) => item.product_code === code
|
|
||||||
)
|
|
||||||
|
|
||||||
if (matchIndex === -1) {
|
|
||||||
ElMessage.warning(`未找到 ISBN 为 ${code} 的商品`)
|
|
||||||
isProcessing.value = false
|
|
||||||
processingIndex.value = -1
|
|
||||||
scanCode.value = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 匹配成功:交换当前扫描位与匹配位(把匹配商品提到当前位置)
|
|
||||||
if (matchIndex !== currentScanIndex.value) {
|
|
||||||
const temp = scanList.value[currentScanIndex.value]
|
|
||||||
scanList.value[currentScanIndex.value] = scanList.value[matchIndex]
|
|
||||||
scanList.value[matchIndex] = temp
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentItem = scanList.value[currentScanIndex.value]
|
|
||||||
if (!currentItem) {
|
|
||||||
isProcessing.value = false
|
|
||||||
processingIndex.value = -1
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const salesPersonId = currentItem.sales_person_id
|
|
||||||
if (!salesPersonId) {
|
|
||||||
ElMessage.warning('当前商品缺少 sales_person_id')
|
|
||||||
isProcessing.value = false
|
|
||||||
processingIndex.value = -1
|
|
||||||
scanCode.value = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 第一步:获取快递账号列表
|
|
||||||
const res = await axios.get('https://api.buzhiyushu.cn/zhishu/fastMail/listApi', {
|
|
||||||
params: { shopId: salesPersonId }
|
|
||||||
})
|
|
||||||
console.log('fastMail/listApi 响应:', res.data)
|
|
||||||
|
|
||||||
// 第二步:提取 fastMailType=1 的成员,调用创建面单接口
|
|
||||||
const data = res.data?.data
|
|
||||||
if (!Array.isArray(data) || data.length === 0) {
|
|
||||||
ElMessage.error('未获取到快递账号配置')
|
|
||||||
isProcessing.value = false
|
|
||||||
processingIndex.value = -1
|
|
||||||
scanCode.value = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const fastMail = data.find((item: any) => item.fastMailType === '1')
|
|
||||||
if (!fastMail || !currentItem.association_order_no) {
|
|
||||||
ElMessage.error('未找到默认快递配置或缺少平台单号')
|
|
||||||
isProcessing.value = false
|
|
||||||
processingIndex.value = -1
|
|
||||||
scanCode.value = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('type', fastMail.type)
|
|
||||||
formData.append('partnerId', fastMail.partnerId)
|
|
||||||
formData.append('secret', fastMail.secret)
|
|
||||||
formData.append('orderSn', currentItem.association_order_no)
|
|
||||||
formData.append('contact', currentItem.warehouse_contact_person)
|
|
||||||
formData.append('phoneNumber', currentItem.warehouse_contact_phone)
|
|
||||||
formData.append('province', currentItem.warehouse_province)
|
|
||||||
formData.append('city', currentItem.warehouse_city)
|
|
||||||
formData.append('area', currentItem.warehouse_district)
|
|
||||||
formData.append('town', currentItem.warehouse_address)
|
|
||||||
const createRes = await axios.post('https://print.buzhiyushu.cn/api/print/createOrderBatch', formData)
|
|
||||||
console.log('createOrderBatch 响应:', createRes.data)
|
|
||||||
|
|
||||||
// 第三步:获取打印PDF信息
|
|
||||||
const createData = await axios.get('https://print.buzhiyushu.cn/api/print/createBmOrderDaYin', {
|
|
||||||
params: {
|
|
||||||
mailno: createRes.data.data[0].mail_no || createRes.data.data[0].mailno,
|
|
||||||
partnerId: fastMail.partnerId,
|
|
||||||
secret: fastMail.secret
|
|
||||||
}
|
|
||||||
})
|
|
||||||
console.log('createBmOrderDaYin 响应:', createData.data)
|
|
||||||
|
|
||||||
const printData = createRes.data.data[0]
|
|
||||||
printData.pdf_info = createData.data.pdfInfo
|
|
||||||
printData.itemList = createRes.data.erpGoodsOrderList[0].itemList;
|
|
||||||
console.log('createBmOrderDaYin 响应:', JSON.stringify(printData))
|
|
||||||
|
|
||||||
// 调用createPrintTask接口创建打印任务并打印(失败会阻断后续回填操作)
|
|
||||||
console.log('正在创建打印任务并打印...')
|
|
||||||
const LODOP = await createPrintTask('yunda', printData) as any
|
|
||||||
LODOP.SET_PRINTER_INDEX(localStorage.getItem('printer_express'));
|
|
||||||
const printResult = LODOP.PRINT()
|
|
||||||
console.log('打印结果:', printResult)
|
|
||||||
|
|
||||||
// 调用updateShippingOrderLogistics更新物流信息(失败会阻断后续回填操作)
|
|
||||||
if (currentItem.sales_order_item_id) {
|
|
||||||
await updateShippingOrderLogistics({
|
|
||||||
shipping_order_id: currentItem.shipping_order_id,
|
|
||||||
sales_order_item_id: currentItem.sales_order_item_id,
|
|
||||||
logistics_company: fastMail.type,
|
|
||||||
logistics_no: createRes.data.data[0].mail_no || createRes.data.data[0].mailno
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('正在回填快递单号并更新状态...')
|
|
||||||
const submitCompany = await axios.post('https://api.buzhiyushu.cn/zhishu/orderExternalGoods/submitCompanyOrder', {
|
|
||||||
code: fastMail.type,
|
|
||||||
orderNo: createRes.data.data[0].mail_no || createRes.data.data[0].mailno,
|
|
||||||
erpOrderId: createRes.data.erpGoodsOrderList[0].id.toString()
|
|
||||||
}, {
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
console.log('回填快递单号 响应:', submitCompany.data)
|
|
||||||
|
|
||||||
// 回填快递单号到当前行
|
|
||||||
scanList.value[currentScanIndex.value].logistics_no = createRes.data.data[0].mail_no || createRes.data.data[0].mailno
|
|
||||||
} catch (err) {
|
|
||||||
console.error('发货处理失败:', err)
|
|
||||||
ElMessage.error('发货处理失败,请重试')
|
|
||||||
isProcessing.value = false
|
|
||||||
processingIndex.value = -1
|
|
||||||
scanCode.value = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解锁,前进到下一个商品
|
|
||||||
isProcessing.value = false
|
|
||||||
processingIndex.value = -1
|
|
||||||
currentScanIndex.value++
|
|
||||||
scanCode.value = ''
|
|
||||||
|
|
||||||
if (currentScanIndex.value >= scanList.value.length) {
|
|
||||||
ElMessage.success('全部扫描完成!')
|
|
||||||
scanDialogVisible.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusLabel = (status: number): string => STATUS_MAP[status]?.label || '未知'
|
|
||||||
const statusTagType = (status: number): string => STATUS_MAP[status]?.type || 'info'
|
|
||||||
|
|
||||||
const formatTimestamp = (timestamp?: number | string | null): string => {
|
|
||||||
if (!timestamp && timestamp !== 0) return '-'
|
|
||||||
return dayjs.unix(Number(timestamp)).format('YYYY-MM-DD HH:mm')
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatDate = (date?: number | string | null): string => {
|
|
||||||
if (!date) return '-'
|
|
||||||
if (typeof date === 'number' && date < 10000000000) {
|
|
||||||
return dayjs.unix(date).format('YYYY-MM-DD')
|
|
||||||
}
|
|
||||||
return dayjs(date).format('YYYY-MM-DD')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const formatDateForSale = (date?: number | string | null): string => {
|
|
||||||
if (!date) return '-'
|
|
||||||
if (typeof date === 'number' && date < 10000000000) {
|
|
||||||
return dayjs.unix(date).format('YYYY-MM-DD HH:mm')
|
|
||||||
}
|
|
||||||
return dayjs(date).format('YYYY-MM-DD HH:mm')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 快递公司映射 */
|
|
||||||
const logisticsCompanyMap: Record<string, string> = {
|
|
||||||
YUNDA: '韵达快递'
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 加载下拉选项和名称映射 */
|
|
||||||
const loadOptions = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
// 加载仓库
|
|
||||||
const warehouseRes = await fetchWarehouseList({ keyword: '', page: 1, pageSize: 9999 })
|
|
||||||
warehouseOptions.value = warehouseRes.list || []
|
|
||||||
const wMap: Record<string, string> = {}
|
|
||||||
for (const w of warehouseRes.list) {
|
|
||||||
wMap[String(w.id)] = w.name || w.code || String(w.id)
|
|
||||||
}
|
|
||||||
warehouseMap.value = wMap
|
|
||||||
|
|
||||||
// TODO: 加载客户、销售订单、波次任务、库位选项(需要对应API)
|
|
||||||
customerOptions.value = []
|
|
||||||
customerMap.value = {}
|
|
||||||
} catch (error) {
|
|
||||||
// console.error('加载选项失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 加载发货单列表 */
|
|
||||||
const loadList = async (): Promise<void> => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const res = await fetchShippingOrderList({
|
|
||||||
check_no: searchParams.keyword,
|
|
||||||
status: searchParams.status || undefined,
|
|
||||||
warehouse_id: searchParams.warehouse_id || undefined,
|
|
||||||
customer_id: searchParams.customer_id || undefined,
|
|
||||||
page: pagination.current,
|
|
||||||
pageSize: pagination.pageSize
|
|
||||||
})
|
|
||||||
tableData.value = res.list || []
|
|
||||||
pagination.total = res.total || 0
|
|
||||||
} catch (error) {
|
|
||||||
// console.error('加载发货单列表失败:', error)
|
|
||||||
ElMessage.error('加载发货单列表失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 搜索 */
|
|
||||||
const handleSearch = (): void => {
|
|
||||||
pagination.current = 1
|
|
||||||
loadList()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重置搜索 */
|
|
||||||
const resetSearch = (): void => {
|
|
||||||
searchParams.keyword = ''
|
|
||||||
searchParams.status = null
|
|
||||||
searchParams.warehouse_id = null
|
|
||||||
searchParams.customer_id = null
|
|
||||||
pagination.current = 1
|
|
||||||
loadList()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 分页大小变化 */
|
|
||||||
const handleSizeChange = (size: number): void => {
|
|
||||||
pagination.pageSize = size
|
|
||||||
loadList()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 页码变化 */
|
|
||||||
const handleCurrentChange = (page: number): void => {
|
|
||||||
pagination.current = page
|
|
||||||
loadList()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 展开行时加载详情 */
|
|
||||||
const handleExpandChange = async (row: any, expandedRows: any[]): Promise<void> => {
|
|
||||||
if (expandedRows.find((r: any) => r.id === row.id) && !detailCache.value[row.id]) {
|
|
||||||
try {
|
|
||||||
const detail = await fetchShippingOrderDetail(row.id)
|
|
||||||
detailCache.value[row.id] = detail
|
|
||||||
} catch (error) {
|
|
||||||
// console.error('加载发货单详情失败:', error)
|
|
||||||
detailCache.value[row.id] = { items: [] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 查看发货单详情 */
|
|
||||||
const handleView = async (row: any): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const detail = await fetchShippingOrderDetail(row.id)
|
|
||||||
detailData.value = detail
|
|
||||||
detailVisible.value = true
|
|
||||||
} catch (error) {
|
|
||||||
// console.error('加载发货单详情失败:', error)
|
|
||||||
ElMessage.error('加载详情失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadOptions()
|
|
||||||
loadList()
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
loading,
|
|
||||||
tableData,
|
|
||||||
statusOptions,
|
|
||||||
searchParams,
|
|
||||||
pagination,
|
|
||||||
detailVisible,
|
|
||||||
detailData,
|
|
||||||
warehouseOptions,
|
|
||||||
customerOptions,
|
|
||||||
warehouseMap,
|
|
||||||
customerMap,
|
|
||||||
salesOrderMap,
|
|
||||||
waveTaskMap,
|
|
||||||
locationMap,
|
|
||||||
detailCache,
|
|
||||||
scanDialogVisible,
|
|
||||||
scanInputRef,
|
|
||||||
scanCode,
|
|
||||||
scanList,
|
|
||||||
currentScanIndex,
|
|
||||||
currentScanTotal,
|
|
||||||
currentScanItem,
|
|
||||||
isProcessing,
|
|
||||||
processingIndex,
|
|
||||||
formatAmount,
|
|
||||||
scanRowClassName,
|
|
||||||
handleStartShipping,
|
|
||||||
onScanDialogOpened,
|
|
||||||
onScanDialogClosed,
|
|
||||||
handleScanSubmit,
|
|
||||||
statusLabel,
|
|
||||||
statusTagType,
|
|
||||||
formatTimestamp,
|
|
||||||
formatDate,
|
|
||||||
formatDateForSale,
|
|
||||||
logisticsCompanyMap,
|
|
||||||
handleSearch,
|
|
||||||
resetSearch,
|
|
||||||
handleSizeChange,
|
|
||||||
handleCurrentChange,
|
|
||||||
handleExpandChange,
|
|
||||||
handleView,
|
|
||||||
navigateToSalesOrder,
|
|
||||||
navigateToOutbound,
|
|
||||||
copyToClipboard,
|
|
||||||
Search,
|
|
||||||
Refresh,
|
|
||||||
View,
|
|
||||||
Loading,
|
|
||||||
CopyDocument
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.shipping-order-manager {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-bar {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-table__expanded-cell) {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 隐藏扫码输入框,仅保留焦点功能 */
|
|
||||||
.hidden-scan-input {
|
|
||||||
position: absolute;
|
|
||||||
left: -9999px;
|
|
||||||
opacity: 0;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 已完成扫描的行背景色 */
|
|
||||||
:deep(.el-table__row.scan-completed-row) {
|
|
||||||
background-color: #ecf5ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-table__row.scan-completed-row:hover > td) {
|
|
||||||
background-color: #ecf5ff;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试连接核价器
|
|
||||||
* @param {string} ip - IP 地址
|
|
||||||
* @param {string} port - 端口
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
export const testConnection = (ip, port) => {
|
|
||||||
const params = new URLSearchParams()
|
|
||||||
params.append('isbn', '0')
|
|
||||||
params.append('out_id', '0')
|
|
||||||
params.append('quality', '0')
|
|
||||||
params.append('query_index', '1')
|
|
||||||
params.append('user_id', '0')
|
|
||||||
return axios.post(`http://${ip}:${port}/api/goods/query`, params.toString(), {
|
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
||||||
timeout: 10000
|
|
||||||
}).then(res => res.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存新价格到核价器
|
|
||||||
* @param {string} ip - IP 地址
|
|
||||||
* @param {string} port - 端口
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
export const saveNewPrice = (ip, port, newPrice, placeholderDownPrice, minShippingFee, minPrice, verifyIndex) => {
|
|
||||||
const formData = new URLSearchParams()
|
|
||||||
formData.append('new_price', newPrice)
|
|
||||||
formData.append('placeholder_down_price', placeholderDownPrice)
|
|
||||||
formData.append('min_shipping_fee', minShippingFee)
|
|
||||||
formData.append('min_price', minPrice)
|
|
||||||
formData.append('query_index', verifyIndex)
|
|
||||||
console.log(formData.toString())
|
|
||||||
return axios.post(`http://${ip}:${port}/api/config/price/set`, formData.toString(), {
|
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
||||||
timeout: 10000
|
|
||||||
}).then(res => res.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 孔夫子旧书网登录(直接请求核价器)
|
|
||||||
* @param {string} username - 孔网用户名
|
|
||||||
* @param {string} password - 孔网密码
|
|
||||||
* @param {string} ip - 核价器 IP
|
|
||||||
* @param {string} port - 核价器端口
|
|
||||||
* @returns {Promise<{code: number, data: {token: string, nickname: string}, message: string}>}
|
|
||||||
*/
|
|
||||||
export const kongfzLogin = (username, password, ip, port) => {
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('username', username)
|
|
||||||
formData.append('password', password)
|
|
||||||
return axios.post(`http://${ip}:${port}/api/kfz/login`, formData, {
|
|
||||||
timeout: 15000
|
|
||||||
}).then(res => res.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量提交 Token 到核价器
|
|
||||||
* @param {Array<{username: string, token: string}>} tokens - 账号 Token 列表
|
|
||||||
* @param {string} ip - 核价器 IP
|
|
||||||
* @param {string} port - 核价器端口
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
export const batchAddTokens = (tokens, ip, port) => {
|
|
||||||
return axios.post(`http://${ip}:${port}/api/token/add`, tokens, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
timeout: 10000
|
|
||||||
}).then(res => res.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从核价器获取已保存的 Token 列表
|
|
||||||
* @param {string} ip - 核价器 IP
|
|
||||||
* @param {string} port - 核价器端口
|
|
||||||
* @returns {Promise<{code: number, data: Array<{Username: string, Token: string, ID: number, IsEnable: boolean}>, message: string}>}
|
|
||||||
*/
|
|
||||||
export const fetchTokenList = (ip, port) => {
|
|
||||||
return axios.post(`http://${ip}:${port}/api/token/list`, {}, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
timeout: 10000
|
|
||||||
}).then(res => res.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从核价器获取config配置
|
|
||||||
* @param {string} ip - 核价器 IP
|
|
||||||
* @param {string} port - 核价器端口
|
|
||||||
*/
|
|
||||||
export const fetchConfig = (ip, port) => {
|
|
||||||
return axios.post(`http://${ip}:${port}/api/config/price/get`, {}, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
timeout: 10000
|
|
||||||
}).then(res => res.data)
|
|
||||||
}
|
|
||||||
@ -1,968 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="config-page">
|
|
||||||
<el-card class="config-card" shadow="always">
|
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="header-left">
|
|
||||||
<el-icon :size="20">
|
|
||||||
<Setting />
|
|
||||||
</el-icon>
|
|
||||||
<span>核价器配置</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-form label-width="80px" label-position="top">
|
|
||||||
<div class="config-row">
|
|
||||||
<el-form-item class="flex-item">
|
|
||||||
<template #label>
|
|
||||||
<span class="label-with-icon">
|
|
||||||
<span>程序所在位置</span>
|
|
||||||
<el-tooltip content="verifyTool的位置" placement="top" trigger="click">
|
|
||||||
<el-icon style="cursor: pointer;">
|
|
||||||
<QuestionFilled />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<el-input v-model="dir" placeholder="如 C:\\verifyTool" clearable @clear="dir = ''" />
|
|
||||||
<el-tooltip content="通过自定义协议启动本机程序" placement="top" trigger="click">
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
:icon="VideoPlay"
|
|
||||||
style="margin-left: 10px"
|
|
||||||
:disabled="!dir"
|
|
||||||
@click="handleOpenExe"
|
|
||||||
>
|
|
||||||
打开程序
|
|
||||||
</el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
<div class="config-row">
|
|
||||||
<!-- IP地址 -->
|
|
||||||
<el-form-item class="flex-item">
|
|
||||||
<template #label>
|
|
||||||
<span class="label-with-icon">
|
|
||||||
<span>IP 地址</span>
|
|
||||||
<el-tooltip content="一般都是 127.0.0.1 如需特殊配置 请联系网管" placement="top" trigger="click">
|
|
||||||
<el-icon style="cursor: pointer;">
|
|
||||||
<QuestionFilled />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<el-input v-model="ip" placeholder="如 192.168.1.1" clearable />
|
|
||||||
</el-form-item>
|
|
||||||
<!-- 端口 -->
|
|
||||||
<el-form-item class="flex-item">
|
|
||||||
<template #label>
|
|
||||||
<span class="label-with-icon">
|
|
||||||
<span>端口</span>
|
|
||||||
<el-tooltip content="默认是8080 但是由于每台电脑的环境都不同 可能会出现端口冲突 在出现问题时候 请第一时间联系网管" placement="top" trigger="click">
|
|
||||||
<el-icon style="cursor: pointer;">
|
|
||||||
<QuestionFilled />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<el-input v-model="port" placeholder="如 8080" clearable />
|
|
||||||
</el-form-item>
|
|
||||||
<!-- 核价位置 -->
|
|
||||||
<el-form-item class="flex-item">
|
|
||||||
<template #label>
|
|
||||||
<span class="label-with-icon">
|
|
||||||
<span>核价位置</span>
|
|
||||||
<el-tooltip content="默认 1 根据实际情况自行填写" placement="top" trigger="click">
|
|
||||||
<el-icon style="cursor: pointer;">
|
|
||||||
<QuestionFilled />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<el-input v-model="verifyIndex" placeholder="请输入核价位置" clearable />
|
|
||||||
</el-form-item>
|
|
||||||
<!-- 核价失败默认价格 -->
|
|
||||||
<el-form-item class="flex-item">
|
|
||||||
<template #label>
|
|
||||||
<span class="label-with-icon">
|
|
||||||
<span>核价失败统一默认价格(元)</span>
|
|
||||||
<el-tooltip content="例:88888 便于再店铺中快速搜索到这些商品 商品修改价格" placement="top" trigger="click">
|
|
||||||
<el-icon style="cursor: pointer;">
|
|
||||||
<QuestionFilled />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<el-input v-model="newPrice" placeholder="请输入核价失败时的默认价格" clearable @input="e => onPriceInput(e, 'newPrice')"
|
|
||||||
@blur="onPriceBlur('newPrice')" />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
<div class="config-row">
|
|
||||||
<!-- 占位降价 -->
|
|
||||||
<el-form-item class="flex-item">
|
|
||||||
<template #label>
|
|
||||||
<span class="label-with-icon">
|
|
||||||
<span>占位降价(元)</span>
|
|
||||||
<el-tooltip content="占位降价金额" placement="top" trigger="click">
|
|
||||||
<el-icon style="cursor: pointer;">
|
|
||||||
<QuestionFilled />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<el-input v-model="placeholderDownPrice" placeholder="请输入占位降价" clearable
|
|
||||||
@input="e => onPriceInput(e, 'placeholderDownPrice')"
|
|
||||||
@blur="enforceMinPlaceholderDownPrice(); onPriceBlur('placeholderDownPrice')" />
|
|
||||||
</el-form-item>
|
|
||||||
<!-- 最低运费 -->
|
|
||||||
<el-form-item class="flex-item">
|
|
||||||
<template #label>
|
|
||||||
<span class="label-with-icon">
|
|
||||||
<span>最低运费(元)</span>
|
|
||||||
<el-tooltip content="订单最低运费金额" placement="top" trigger="click">
|
|
||||||
<el-icon style="cursor: pointer;">
|
|
||||||
<QuestionFilled />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<el-input v-model="minShippingFee" placeholder="请输入最低运费" clearable
|
|
||||||
@input="e => onPriceInput(e, 'minShippingFee')" @blur="onPriceBlur('minShippingFee')" />
|
|
||||||
</el-form-item>
|
|
||||||
<!-- 最低书价 -->
|
|
||||||
<el-form-item class="flex-item">
|
|
||||||
<template #label>
|
|
||||||
<span class="label-with-icon">
|
|
||||||
<span>最低书价(元)</span>
|
|
||||||
<el-tooltip content="单本书籍最低售价" placement="top" trigger="click">
|
|
||||||
<el-icon style="cursor: pointer;">
|
|
||||||
<QuestionFilled />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<el-input v-model="minPrice" placeholder="请输入最低书价" clearable @input="e => onPriceInput(e, 'minPrice')"
|
|
||||||
@blur="onPriceBlur('minPrice')" />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="save-bar">
|
|
||||||
<el-tooltip content="测试核价器服务连接状态" placement="top" trigger="click">
|
|
||||||
<el-button type="primary" :loading="testLoading" @click="handleTest" size="small">测试连接</el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip content="保存当前核价器配置" placement="top" trigger="click">
|
|
||||||
<el-button type="success" @click="handleSave">保存配置</el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<transition name="fade">
|
|
||||||
<div v-if="result" class="result-box">
|
|
||||||
<el-alert :title="result.success ? '✅ 连接成功' : '❌ 连接失败'" :type="result.success ? 'success' : 'error'"
|
|
||||||
:description="result.message" show-icon :closable="false" />
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<el-divider />
|
|
||||||
|
|
||||||
<!-- 账号管理 -->
|
|
||||||
<div class="account-section">
|
|
||||||
<div class="section-title">
|
|
||||||
<el-icon :size="18">
|
|
||||||
<User />
|
|
||||||
</el-icon>
|
|
||||||
<span>绑定孔夫子旧书网账号</span>
|
|
||||||
<el-tooltip content="这里是为了波次提交之后店铺同步商品时候有一个相对准确市场价格(如果没有价格我们将走设置的统一默认价格)" placement="top" trigger="click">
|
|
||||||
<el-icon style="cursor: pointer; margin-left: 8px;">
|
|
||||||
<QuestionFilled />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 有数据 → 表格 + 追加账号按钮 -->
|
|
||||||
<template v-if="savedAccountList.length > 0">
|
|
||||||
<el-table :data="savedAccountList" border stripe size="small" style="width: 100%">
|
|
||||||
<el-table-column prop="username" label="用户名" min-width="140" align="center" />
|
|
||||||
<el-table-column prop="token" label="Token" min-width="260" show-overflow-tooltip align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span class="token-text">{{ row.token }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="80" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-tooltip content="删除该账号绑定" placement="top" trigger="click">
|
|
||||||
<el-button type="danger" link :icon="Delete" @click="deleteSavedAccount(row)">删除</el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<!-- 追加账号按钮 -->
|
|
||||||
<div class="append-bar">
|
|
||||||
<el-button v-if="!showAppendForm" type="primary" plain :icon="Plus" @click="showAppendForm = true">
|
|
||||||
追加账号
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 追加账号输入表单 -->
|
|
||||||
<transition name="fade">
|
|
||||||
<div v-if="showAppendForm" class="account-form">
|
|
||||||
<div v-for="(item, index) in appendAccounts" :key="index" class="account-row">
|
|
||||||
<div class="account-label">
|
|
||||||
追加账号{{ ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'][index] || (index + 1) }}
|
|
||||||
</div>
|
|
||||||
<div class="account-inputs">
|
|
||||||
<!-- 账号输入框 -->
|
|
||||||
<el-form-item :label-width="'0'" style="margin-bottom: 0;">
|
|
||||||
<template #label>
|
|
||||||
<span class="label-with-icon" style="display: none;"></span>
|
|
||||||
</template>
|
|
||||||
<el-input v-model="item.account" placeholder="请输入账号" clearable autocomplete="off" />
|
|
||||||
</el-form-item>
|
|
||||||
<!-- 密码输入框 -->
|
|
||||||
<el-form-item :label-width="'0'" style="margin-bottom: 0;">
|
|
||||||
<el-input v-model="item.password" type="password" placeholder="请输入密码" clearable show-password
|
|
||||||
autocomplete="new-password" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-tooltip v-if="appendAccounts.length > 1" content="删除此条账号输入" placement="top" trigger="click">
|
|
||||||
<el-button type="danger" :icon="Delete" circle
|
|
||||||
@click="removeAppendAccount(index)" />
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="append-form-actions">
|
|
||||||
<el-tooltip content="再增加一组账号密码输入" placement="top" trigger="click">
|
|
||||||
<el-button plain :icon="Plus" @click="addAppendAccount">添加更多</el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip content="绑定当前填写的所有追加账号" placement="top" trigger="click">
|
|
||||||
<el-button type="primary" :loading="appendLoading" :icon="Link"
|
|
||||||
@click="handleAppendBind">绑定追加账号</el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-button @click="cancelAppend">取消</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 无数据 → 输入表单 -->
|
|
||||||
<template v-else>
|
|
||||||
<div class="account-form">
|
|
||||||
<div v-for="(item, index) in accounts" :key="index" class="account-row">
|
|
||||||
<div class="account-label">账号{{ ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'][index] }}</div>
|
|
||||||
<div class="account-inputs">
|
|
||||||
<el-input v-model="item.account" placeholder="请输入账号" clearable autocomplete="off" />
|
|
||||||
<el-input v-model="item.password" type="password" placeholder="请输入密码" clearable show-password
|
|
||||||
autocomplete="new-password" />
|
|
||||||
<el-tooltip v-if="accounts.length > 1" content="删除此条账号输入" placement="top" trigger="click">
|
|
||||||
<el-button type="danger" :icon="Delete" circle
|
|
||||||
@click="removeAccount(index)" />
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<el-tooltip content="再增加一组账号密码输入" placement="top" trigger="click">
|
|
||||||
<el-button type="primary" plain :icon="Plus" @click="addAccount" class="add-btn">添加更多账号</el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 绑定账号按钮(无已绑定账号时显示) -->
|
|
||||||
<div v-if="savedAccountList.length === 0" class="bind-bar">
|
|
||||||
<el-tooltip content="绑定新的核价器账号" placement="top" trigger="click">
|
|
||||||
<el-button type="primary" :loading="bindLoading" :icon="Link" @click="handleBind">绑定账号</el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
import axios from 'axios'
|
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
||||||
import { Delete, Plus, Setting, User, Link, VideoPlay } from '@element-plus/icons-vue'
|
|
||||||
import {
|
|
||||||
testConnection,
|
|
||||||
kongfzLogin,
|
|
||||||
batchAddTokens,
|
|
||||||
fetchTokenList,
|
|
||||||
saveNewPrice,
|
|
||||||
fetchConfig,
|
|
||||||
} from '@/api/config'
|
|
||||||
|
|
||||||
const STORAGE_KEY_FILE_DIR = 'file_dir'
|
|
||||||
const STORAGE_KEY_IP = 'test_ip'
|
|
||||||
const STORAGE_KEY_PORT = 'test_port'
|
|
||||||
const STORAGE_KEY_VERIFY_INDEX = 'verify_index'
|
|
||||||
const STORAGE_KEY_SAVED_ACCOUNTS = 'saved_accounts'
|
|
||||||
|
|
||||||
interface AccountEntry {
|
|
||||||
id?: number
|
|
||||||
account: string
|
|
||||||
token: string
|
|
||||||
username: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FormAccount {
|
|
||||||
account: string
|
|
||||||
password: string
|
|
||||||
token?: string
|
|
||||||
username?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 从 localStorage 读取已保存的账号列表 */
|
|
||||||
function loadSavedAccounts(): AccountEntry[] {
|
|
||||||
try {
|
|
||||||
const raw = localStorage.getItem(STORAGE_KEY_SAVED_ACCOUNTS)
|
|
||||||
return raw ? JSON.parse(raw) : []
|
|
||||||
} catch {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 持久化已保存的账号列表到 localStorage */
|
|
||||||
function saveSavedAccounts(list: AccountEntry[]): void {
|
|
||||||
localStorage.setItem(STORAGE_KEY_SAVED_ACCOUNTS, JSON.stringify(list))
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Config',
|
|
||||||
setup() {
|
|
||||||
// 从本地缓存读取初始值
|
|
||||||
const dir = ref(localStorage.getItem(STORAGE_KEY_FILE_DIR) || '')
|
|
||||||
const ip = ref(localStorage.getItem(STORAGE_KEY_IP) || '127.0.0.1')
|
|
||||||
const port = ref(localStorage.getItem(STORAGE_KEY_PORT) || '8080')
|
|
||||||
const verifyIndex = ref(localStorage.getItem(STORAGE_KEY_VERIFY_INDEX) || '3')
|
|
||||||
const fmt = (v: string | null, d: string) => { const n = parseFloat(v ?? ''); return isNaN(n) ? d : n.toFixed(2) }
|
|
||||||
const newPrice = ref(fmt(localStorage.getItem('new_price'), '9999.00'))
|
|
||||||
const placeholderDownPrice = ref(fmt(localStorage.getItem('placeholder_down_price'), '0.01'))
|
|
||||||
const minShippingFee = ref(fmt(localStorage.getItem('min_shipping_fee'), '3.00'))
|
|
||||||
const minPrice = ref(fmt(localStorage.getItem('min_price'), '1.00'))
|
|
||||||
// 账号密码组(支持多个)
|
|
||||||
const accounts = ref<FormAccount[]>([{ account: '', password: '', token: '', username: '' }])
|
|
||||||
// 已保存的账号 Token 列表(不含密码)
|
|
||||||
const savedAccountList = ref<AccountEntry[]>([])
|
|
||||||
// 追加账号相关
|
|
||||||
const showAppendForm = ref(false)
|
|
||||||
const appendAccounts = ref<FormAccount[]>([{ account: '', password: '', token: '', username: '' }])
|
|
||||||
const appendLoading = ref(false)
|
|
||||||
/** 添加新的账号输入行 */
|
|
||||||
const addAccount = () => {
|
|
||||||
accounts.value.push({ account: '', password: '', token: '', username: '' })
|
|
||||||
}
|
|
||||||
/** 删除账号输入行 */
|
|
||||||
const removeAccount = (index: number) => {
|
|
||||||
accounts.value.splice(index, 1)
|
|
||||||
}
|
|
||||||
/** 添加新的追加账号输入行 */
|
|
||||||
const addAppendAccount = () => {
|
|
||||||
appendAccounts.value.push({ account: '', password: '', token: '', username: '' })
|
|
||||||
}
|
|
||||||
/** 删除追加账号输入行 */
|
|
||||||
const removeAppendAccount = (index: number) => {
|
|
||||||
appendAccounts.value.splice(index, 1)
|
|
||||||
}
|
|
||||||
/** 取消追加账号操作,重置表单 */
|
|
||||||
const cancelAppend = () => {
|
|
||||||
showAppendForm.value = false
|
|
||||||
appendAccounts.value = [{ account: '', password: '', token: '', username: '' }]
|
|
||||||
}
|
|
||||||
/** 占位降价失焦时强制最低值为 0.01 */
|
|
||||||
const enforceMinPlaceholderDownPrice = () => {
|
|
||||||
const val = parseFloat(placeholderDownPrice.value)
|
|
||||||
if (isNaN(val) || val < 0.01) {
|
|
||||||
placeholderDownPrice.value = '0.01'
|
|
||||||
ElMessage.warning('占位降价不能低于 0.01,已自动设为 0.01')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** 金额输入过滤:只允许数字+小数点,最多2位小数,保持光标位置 */
|
|
||||||
const onPriceInput = (value: string, key: string) => {
|
|
||||||
const raw = value
|
|
||||||
const filtered = raw
|
|
||||||
.replace(/[^\d.]/g, '') // 只保留数字和小数点
|
|
||||||
.replace(/^\./, '0.') // 以点开头补0
|
|
||||||
.replace(/\.{2,}/g, '.') // 去重小数点
|
|
||||||
.replace(/^(\d+\.?\d{0,2}).*$/, '$1') // 最多2位小数
|
|
||||||
const map: Record<string, any> = { newPrice, placeholderDownPrice, minShippingFee, minPrice }
|
|
||||||
map[key].value = filtered
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 失焦时自动补全 .00:纯整数 → 末尾补 .00 */
|
|
||||||
const onPriceBlur = (key: string) => {
|
|
||||||
const map: Record<string, any> = { newPrice, placeholderDownPrice, minShippingFee, minPrice }
|
|
||||||
const val = map[key].value as string
|
|
||||||
// 只对纯数字(不含小数点)补 .00
|
|
||||||
if (val && /^\d+$/.test(val)) {
|
|
||||||
map[key].value = val + '.00'
|
|
||||||
} else if (val && /^\d+\.$/.test(val)) {
|
|
||||||
// 如果结尾是小数点,如 "12." → 补成 "12.00"
|
|
||||||
map[key].value = val + '00'
|
|
||||||
} else if (val && /^\d+\.\d$/.test(val)) {
|
|
||||||
// 如果只有一位小数,如 "12.3" → 补成 "12.30"
|
|
||||||
map[key].value = val + '0'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const testLoading = ref(false)
|
|
||||||
const bindLoading = ref(false)
|
|
||||||
const result = ref<{ success: boolean; message: string } | null>(null)
|
|
||||||
|
|
||||||
/** 通过自定义协议启动本地 kfz-goods-pricing.exe */
|
|
||||||
const handleOpenExe = () => {
|
|
||||||
if (!dir.value) {
|
|
||||||
ElMessage.warning('请先设置程序所在位置')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// 调用自定义协议 kfzgs://,launcher.exe 会读取 dir 并启动目标 exe
|
|
||||||
window.location.href = `kfzgs://launch?dir=${encodeURIComponent(dir.value)}`
|
|
||||||
} catch (err: any) {
|
|
||||||
ElMessage.error(`启动失败: ${err.message}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 将单个账号写入已保存列表 */
|
|
||||||
const addToSavedList = (entry: AccountEntry) => {
|
|
||||||
const list = loadSavedAccounts()
|
|
||||||
const idx = list.findIndex(a => a.account === entry.account)
|
|
||||||
if (idx >= 0) {
|
|
||||||
list[idx] = entry
|
|
||||||
} else {
|
|
||||||
list.push(entry)
|
|
||||||
}
|
|
||||||
saveSavedAccounts(list)
|
|
||||||
savedAccountList.value = list
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 删除已保存的账号 */
|
|
||||||
const deleteSavedAccount = async (entry: AccountEntry) => {
|
|
||||||
try {
|
|
||||||
await ElMessageBox.confirm(`确定删除账号「${entry.account}」的 Token 记录吗?`, '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
// 调用删除接口
|
|
||||||
await axios.get(`http://${ip.value}:${port.value}/api/token/delete`, {
|
|
||||||
params: { id: entry.id }
|
|
||||||
})
|
|
||||||
// 同时清除 username 对应的 localStorage key
|
|
||||||
localStorage.removeItem(entry.username)
|
|
||||||
const list = loadSavedAccounts().filter(a => a.account !== entry.account)
|
|
||||||
saveSavedAccounts(list)
|
|
||||||
savedAccountList.value = list
|
|
||||||
ElMessage.success('已删除')
|
|
||||||
} catch {
|
|
||||||
// 取消删除不做任何操作
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 加载已保存的账号列表 */
|
|
||||||
const loadSavedAccountsList = () => {
|
|
||||||
savedAccountList.value = loadSavedAccounts()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 测试核价器连接 */
|
|
||||||
const handleTest = async () => {
|
|
||||||
if (!ip.value) {
|
|
||||||
ElMessage.warning('请输入 IP 地址')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!port.value) {
|
|
||||||
ElMessage.warning('请输入端口号')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
testLoading.value = true
|
|
||||||
result.value = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
const testRes = await testConnection(ip.value, port.value)
|
|
||||||
if (testRes.code === 200) {
|
|
||||||
result.value = {
|
|
||||||
success: true,
|
|
||||||
message: testRes.message
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result.value = {
|
|
||||||
success: false,
|
|
||||||
message: testRes.message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log('测试结果:', testRes)
|
|
||||||
} catch (error: any) {
|
|
||||||
let message = '未知错误'
|
|
||||||
if (error.code === 'ECONNABORTED') {
|
|
||||||
message = '连接超时,请检查地址是否正确或联系服务供应方'
|
|
||||||
} else if (error.message) {
|
|
||||||
message = error.message
|
|
||||||
}
|
|
||||||
|
|
||||||
result.value = {
|
|
||||||
success: false,
|
|
||||||
message
|
|
||||||
}
|
|
||||||
console.log('测试失败:', error)
|
|
||||||
} finally {
|
|
||||||
testLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 绑定账号:登录孔网 + 批量提交 Token 到核价器 + 刷新列表 */
|
|
||||||
const handleBind = async () => {
|
|
||||||
if (!ip.value) {
|
|
||||||
ElMessage.warning('请先填写核价器 IP 地址')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!port.value) {
|
|
||||||
ElMessage.warning('请先填写核价器端口号')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bindLoading.value = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 依次登录每个已填写的账号
|
|
||||||
const successfulAccounts: { username: string; token: string }[] = []
|
|
||||||
for (let i = 0; i < accounts.value.length; i++) {
|
|
||||||
const item = accounts.value[i]
|
|
||||||
if (item.account && item.password) {
|
|
||||||
try {
|
|
||||||
const loginRes = await kongfzLogin(item.account, item.password, ip.value, port.value)
|
|
||||||
console.log('核价器登录响应:', loginRes)
|
|
||||||
if (loginRes?.code === 200 && loginRes?.data) {
|
|
||||||
const { token, nickname: username } = loginRes.data
|
|
||||||
accounts.value[i].token = token
|
|
||||||
accounts.value[i].username = username
|
|
||||||
localStorage.setItem(username, token)
|
|
||||||
addToSavedList({
|
|
||||||
account: item.account,
|
|
||||||
username,
|
|
||||||
token
|
|
||||||
})
|
|
||||||
successfulAccounts.push({ username, token })
|
|
||||||
console.log(`账号 ${item.account} 登录成功,username: ${username}`)
|
|
||||||
} else {
|
|
||||||
console.log(`账号 ${item.account} 登录失败:`, loginRes?.message)
|
|
||||||
}
|
|
||||||
} catch (loginError: any) {
|
|
||||||
console.log(`账号 ${item.account} 登录失败:`, loginError.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将成功的 Token 批量提交到核价器
|
|
||||||
if (successfulAccounts.length > 0) {
|
|
||||||
try {
|
|
||||||
const addRes = await batchAddTokens(successfulAccounts, ip.value, port.value)
|
|
||||||
console.log('token/add 响应:', addRes)
|
|
||||||
} catch (addError: any) {
|
|
||||||
// console.error('token/add 请求失败:', addError.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从服务器重新获取完整列表,同步本地
|
|
||||||
try {
|
|
||||||
const res = await fetchTokenList(ip.value, port.value)
|
|
||||||
const tokenList: { Username: string; Token: string; ID: number; IsEnable: boolean }[] = res?.data
|
|
||||||
if (Array.isArray(tokenList)) {
|
|
||||||
const entries: AccountEntry[] = tokenList
|
|
||||||
.filter(t => t.IsEnable)
|
|
||||||
.map(t => ({
|
|
||||||
id: t.ID,
|
|
||||||
account: t.Username,
|
|
||||||
username: t.Username,
|
|
||||||
token: t.Token
|
|
||||||
}))
|
|
||||||
saveSavedAccounts(entries)
|
|
||||||
savedAccountList.value = entries
|
|
||||||
entries.forEach(e => localStorage.setItem(e.username, e.token))
|
|
||||||
}
|
|
||||||
} catch (fetchErr) {
|
|
||||||
// console.error('获取已保存 Token 列表失败:', fetchErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (successfulAccounts.length > 0) {
|
|
||||||
ElMessage.success(`成功绑定 ${successfulAccounts.length} 个账号`)
|
|
||||||
} else {
|
|
||||||
ElMessage.info('没有成功绑定的账号,请检查账号密码是否正确')
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
bindLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 追加账号绑定:与 handleBind 逻辑一致,但操作 appendAccounts */
|
|
||||||
const handleAppendBind = async () => {
|
|
||||||
if (!ip.value) {
|
|
||||||
ElMessage.warning('请先填写核价器 IP 地址')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!port.value) {
|
|
||||||
ElMessage.warning('请先填写核价器端口号')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
appendLoading.value = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
const successfulAccounts: { username: string; token: string }[] = []
|
|
||||||
for (let i = 0; i < appendAccounts.value.length; i++) {
|
|
||||||
const item = appendAccounts.value[i]
|
|
||||||
if (item.account && item.password) {
|
|
||||||
try {
|
|
||||||
const loginRes = await kongfzLogin(item.account, item.password, ip.value, port.value)
|
|
||||||
if (loginRes?.code === 200 && loginRes?.data) {
|
|
||||||
const { token, nickname: username } = loginRes.data
|
|
||||||
appendAccounts.value[i].token = token
|
|
||||||
appendAccounts.value[i].username = username
|
|
||||||
localStorage.setItem(username, token)
|
|
||||||
addToSavedList({
|
|
||||||
account: item.account,
|
|
||||||
username,
|
|
||||||
token
|
|
||||||
})
|
|
||||||
successfulAccounts.push({ username, token })
|
|
||||||
}
|
|
||||||
} catch (loginError: any) {
|
|
||||||
console.log(`追加账号 ${item.account} 登录失败:`, loginError.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (successfulAccounts.length > 0) {
|
|
||||||
try {
|
|
||||||
await batchAddTokens(successfulAccounts, ip.value, port.value)
|
|
||||||
} catch (addError: any) {
|
|
||||||
// console.error('追加 token/add 请求失败:', addError.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从服务器重新获取完整列表,同步本地
|
|
||||||
try {
|
|
||||||
const res = await fetchTokenList(ip.value, port.value)
|
|
||||||
const tokenList: { Username: string; Token: string; ID: number; IsEnable: boolean }[] = res?.data
|
|
||||||
if (Array.isArray(tokenList)) {
|
|
||||||
const entries: AccountEntry[] = tokenList
|
|
||||||
.filter(t => t.IsEnable)
|
|
||||||
.map(t => ({
|
|
||||||
id: t.ID,
|
|
||||||
account: t.Username,
|
|
||||||
username: t.Username,
|
|
||||||
token: t.Token
|
|
||||||
}))
|
|
||||||
saveSavedAccounts(entries)
|
|
||||||
savedAccountList.value = entries
|
|
||||||
entries.forEach(e => localStorage.setItem(e.username, e.token))
|
|
||||||
}
|
|
||||||
} catch (fetchErr) {
|
|
||||||
// console.error('获取已保存 Token 列表失败:', fetchErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (successfulAccounts.length > 0) {
|
|
||||||
ElMessage.success(`成功追加绑定 ${successfulAccounts.length} 个账号`)
|
|
||||||
} else {
|
|
||||||
ElMessage.info('没有成功绑定的账号,请检查账号密码是否正确')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置追加表单
|
|
||||||
showAppendForm.value = false
|
|
||||||
appendAccounts.value = [{ account: '', password: '', token: '', username: '' }]
|
|
||||||
} finally {
|
|
||||||
appendLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 保存配置 */
|
|
||||||
const handleSave = async () => {
|
|
||||||
if (!ip.value) {
|
|
||||||
ElMessage.warning('请输入 IP 地址')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!port.value) {
|
|
||||||
ElMessage.warning('请输入端口号')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem(STORAGE_KEY_FILE_DIR, dir.value)
|
|
||||||
localStorage.setItem(STORAGE_KEY_IP, ip.value)
|
|
||||||
localStorage.setItem(STORAGE_KEY_PORT, port.value)
|
|
||||||
localStorage.setItem(STORAGE_KEY_VERIFY_INDEX, verifyIndex.value)
|
|
||||||
localStorage.setItem('new_price', newPrice.value)
|
|
||||||
localStorage.setItem('placeholder_down_price', placeholderDownPrice.value)
|
|
||||||
localStorage.setItem('min_shipping_fee', minShippingFee.value)
|
|
||||||
localStorage.setItem('min_price', minPrice.value)
|
|
||||||
console.log('核价配置已保存:', { ip: ip.value, port: port.value, verifyIndex: verifyIndex.value })
|
|
||||||
|
|
||||||
const newpriceNum = parseFloat(newPrice.value)
|
|
||||||
const placeholderDownPriceNum = parseFloat(placeholderDownPrice.value)
|
|
||||||
const minShippingFeeNum = parseFloat(minShippingFee.value)
|
|
||||||
const minPriceNum = parseFloat(minPrice.value)
|
|
||||||
|
|
||||||
if (!isNaN(newpriceNum) && newpriceNum >= 0) {
|
|
||||||
const sendNewPrice = newpriceNum
|
|
||||||
const sendPlaceholderDownPrice = !isNaN(placeholderDownPriceNum) && placeholderDownPriceNum >= 0
|
|
||||||
? placeholderDownPriceNum
|
|
||||||
: 0.01
|
|
||||||
const sendMinShippingFee = !isNaN(minShippingFeeNum) && minShippingFeeNum >= 0
|
|
||||||
? minShippingFeeNum
|
|
||||||
: 3.00
|
|
||||||
const sendMinPrice = !isNaN(minPriceNum) && minPriceNum >= 0
|
|
||||||
? minPriceNum
|
|
||||||
: 1.00
|
|
||||||
try {
|
|
||||||
await saveNewPrice(ip.value, port.value, sendNewPrice, sendPlaceholderDownPrice, sendMinShippingFee, sendMinPrice,verifyIndex.value)
|
|
||||||
console.log('核价价格相关配置已保存')
|
|
||||||
} catch (err: any) {
|
|
||||||
console.log('核价价格相关配置保存失败:', err.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ElMessage.success(`配置已保存:IP=${ip.value}, PORT=${port.value}, 核价位置=${verifyIndex.value}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 从服务器获取已保存的 Token 列表并同步到本地 */
|
|
||||||
const fetchSavedTokens = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const res = await fetchTokenList(ip.value, port.value)
|
|
||||||
const tokenList: { Username: string; Token: string; ID: number; IsEnable: boolean }[] = res?.data
|
|
||||||
if (Array.isArray(tokenList) && tokenList.length > 0) {
|
|
||||||
const entries: AccountEntry[] = tokenList
|
|
||||||
.filter(t => t.IsEnable)
|
|
||||||
.map(t => ({
|
|
||||||
id: t.ID,
|
|
||||||
account: t.Username,
|
|
||||||
username: t.Username,
|
|
||||||
token: t.Token
|
|
||||||
}))
|
|
||||||
saveSavedAccounts(entries)
|
|
||||||
savedAccountList.value = entries
|
|
||||||
// 同时将每个 token 写入单独的 localStorage key
|
|
||||||
entries.forEach(e => localStorage.setItem(e.username, e.token))
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log('获取已保存 Token 列表失败(首次使用可忽略):', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 从核价器拉取价格配置,补全 localStorage 中缺失的字段 */
|
|
||||||
const loadPriceConfig = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const res = await fetchConfig(ip.value, port.value)
|
|
||||||
const data = res?.data
|
|
||||||
if (data) {
|
|
||||||
// 响应字段均为元单位,无需转换
|
|
||||||
const fieldMap: { respKey: string; storeKey: string }[] = [
|
|
||||||
{ respKey: 'QueryIndex', storeKey: 'verify_index' },
|
|
||||||
{ respKey: 'NewPrice', storeKey: 'new_price' },
|
|
||||||
{ respKey: 'PlaceholderDownPrice', storeKey: 'placeholder_down_price' },
|
|
||||||
{ respKey: 'MinShippingFee', storeKey: 'min_shipping_fee' },
|
|
||||||
{ respKey: 'MinPrice', storeKey: 'min_price' },
|
|
||||||
]
|
|
||||||
console.log('从服务器获取的核价配置:', data)
|
|
||||||
for (const f of fieldMap) {
|
|
||||||
const val = (data as any)[f.respKey]
|
|
||||||
if (val !== undefined && val !== null) {
|
|
||||||
localStorage.setItem(f.storeKey, String(val))
|
|
||||||
console.log(`loadPriceConfig: 同步 ${f.storeKey} = ${val}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 同步到页面 ref
|
|
||||||
verifyIndex.value = localStorage.getItem(STORAGE_KEY_VERIFY_INDEX) || verifyIndex.value
|
|
||||||
newPrice.value = fmt(localStorage.getItem('new_price'), '0.00')
|
|
||||||
placeholderDownPrice.value = fmt(localStorage.getItem('placeholder_down_price'), '0.01')
|
|
||||||
minShippingFee.value = fmt(localStorage.getItem('min_shipping_fee'), '3.00')
|
|
||||||
minPrice.value = fmt(localStorage.getItem('min_price'), '1.00')
|
|
||||||
|
|
||||||
// 补上 Port(如果为空)
|
|
||||||
if (!port.value && (data as any).Port) {
|
|
||||||
port.value = String((data as any).Port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log('获取核价器配置失败(首次使用可忽略):', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化时加载已保存的账号列表 + 从服务器同步 + 拉取价格配置
|
|
||||||
onMounted(() => {
|
|
||||||
loadSavedAccountsList()
|
|
||||||
fetchSavedTokens()
|
|
||||||
loadPriceConfig()
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
dir,
|
|
||||||
ip,
|
|
||||||
port,
|
|
||||||
verifyIndex,
|
|
||||||
newPrice,
|
|
||||||
placeholderDownPrice,
|
|
||||||
minShippingFee,
|
|
||||||
minPrice,
|
|
||||||
accounts,
|
|
||||||
addAccount,
|
|
||||||
removeAccount,
|
|
||||||
showAppendForm,
|
|
||||||
appendAccounts,
|
|
||||||
appendLoading,
|
|
||||||
addAppendAccount,
|
|
||||||
removeAppendAccount,
|
|
||||||
cancelAppend,
|
|
||||||
enforceMinPlaceholderDownPrice,
|
|
||||||
onPriceInput,
|
|
||||||
onPriceBlur,
|
|
||||||
handleAppendBind,
|
|
||||||
testLoading,
|
|
||||||
bindLoading,
|
|
||||||
result,
|
|
||||||
savedAccountList,
|
|
||||||
deleteSavedAccount,
|
|
||||||
handleTest,
|
|
||||||
handleBind,
|
|
||||||
handleSave,
|
|
||||||
handleOpenExe,
|
|
||||||
Delete,
|
|
||||||
Plus,
|
|
||||||
Setting,
|
|
||||||
User,
|
|
||||||
Link,
|
|
||||||
VideoPlay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.config-page {
|
|
||||||
padding: 20px;
|
|
||||||
max-width: 860px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-card {
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
|
|
||||||
.flex-item {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.save-bar {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-box {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 账号区域 */
|
|
||||||
.account-section {
|
|
||||||
.section-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.count-tag {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-form {
|
|
||||||
.account-row {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
.account-label {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #606266;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-inputs {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-btn {
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-bar {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.append-bar {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.append-form-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token-text {
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 过渡动画 */
|
|
||||||
.fade-enter-active,
|
|
||||||
.fade-leave-active {
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-enter-from,
|
|
||||||
.fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 响应式 */
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.config-row {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-inputs {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.label-with-icon {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,369 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-popover
|
|
||||||
placement="right-start"
|
|
||||||
:width="360"
|
|
||||||
trigger="hover"
|
|
||||||
:open-delay="500"
|
|
||||||
:close-delay="100"
|
|
||||||
:disabled="!isbn"
|
|
||||||
@show="handleShow"
|
|
||||||
@hide="handleHide"
|
|
||||||
>
|
|
||||||
<template #reference>
|
|
||||||
<span class="isbn-popover-trigger" :class="{ 'is-loading': loading }">
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 加载中 -->
|
|
||||||
<div v-if="loading" class="popover-loading">
|
|
||||||
<el-icon class="is-loading" :size="24"><Loading /></el-icon>
|
|
||||||
<span>正在查询书品信息...</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 查询失败 -->
|
|
||||||
<div v-else-if="error" class="popover-error">
|
|
||||||
<el-icon :size="24" color="#e6a23c"><WarningFilled /></el-icon>
|
|
||||||
<span>{{ error }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 查询成功 → 展示书品信息 -->
|
|
||||||
<div v-else-if="bookData" class="popover-content">
|
|
||||||
<div class="popover-header">
|
|
||||||
<span class="popover-isbn">{{ isbn }}</span>
|
|
||||||
<span v-if="bookData.isSuit" class="suit-badge">套装书</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="popover-body">
|
|
||||||
<!-- 左侧:封面图片 -->
|
|
||||||
<div class="popover-cover">
|
|
||||||
<img
|
|
||||||
v-if="bookData.book_pic?.pddPath"
|
|
||||||
:src="bookData.book_pic.pddPath"
|
|
||||||
alt="封面"
|
|
||||||
class="cover-image"
|
|
||||||
/>
|
|
||||||
<div v-else class="cover-placeholder">
|
|
||||||
<el-icon :size="32"><Picture /></el-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 右侧:书籍详情 -->
|
|
||||||
<div class="popover-info">
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">书名:</span>
|
|
||||||
<span class="info-value info-value-name">{{ bookData.bookName || '未知' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">作者:</span>
|
|
||||||
<span class="info-value">{{ bookData.author || '未知' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">出版社:</span>
|
|
||||||
<span class="info-value">{{ bookData.publisher || '未知' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">出版时间:</span>
|
|
||||||
<span class="info-value">{{ bookData.publishDate || '未知' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">装帧:</span>
|
|
||||||
<span class="info-value">{{ bookData.binding || '未知' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">定价:</span>
|
|
||||||
<span class="info-value info-value-price">¥{{ formatPrice(bookData.price) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">页数:</span>
|
|
||||||
<span class="info-value">{{ bookData.pageCount ?? '未知' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">字数:</span>
|
|
||||||
<span class="info-value">{{ bookData.wordCount ?? '未知' }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ISBN 为空 -->
|
|
||||||
<div v-else class="popover-empty">
|
|
||||||
<span>暂无ISBN</span>
|
|
||||||
</div>
|
|
||||||
</el-popover>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, watch } from 'vue'
|
|
||||||
import { Loading, WarningFilled, Picture } from '@element-plus/icons-vue'
|
|
||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
interface BookPic {
|
|
||||||
localPath?: string
|
|
||||||
pddPath?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BookInfoResult {
|
|
||||||
bookName: string
|
|
||||||
author: string
|
|
||||||
publisher: string
|
|
||||||
publishDate: string
|
|
||||||
binding: string
|
|
||||||
price: number
|
|
||||||
pageCount: number
|
|
||||||
wordCount: number
|
|
||||||
book_pic?: BookPic
|
|
||||||
isSuit: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
/** ISBN 编号 */
|
|
||||||
isbn?: string
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const error = ref<string | null>(null)
|
|
||||||
const bookData = ref<BookInfoResult | null>(null)
|
|
||||||
|
|
||||||
// 缓存已查询过的 ISBN,避免重复请求
|
|
||||||
const cache = new Map<string, BookInfoResult>()
|
|
||||||
|
|
||||||
/** 格式化出版时间:处理年月日格式 */
|
|
||||||
function formatPublishDate(value: string | number | undefined | null): string {
|
|
||||||
if (value == null || value === '') return ''
|
|
||||||
const str = String(value)
|
|
||||||
// 如果已经是 yyyy-mm-dd 格式,直接返回
|
|
||||||
if (/^\d{4}-\d{2}-\d{2}$/.test(str)) return str
|
|
||||||
// 如果是 yyyyMMdd 格式(8位数字)
|
|
||||||
if (/^\d{8}$/.test(str)) {
|
|
||||||
return `${str.slice(0, 4)}-${str.slice(4, 6)}-${str.slice(6, 8)}`
|
|
||||||
}
|
|
||||||
// 如果是 yyyy-mm 格式
|
|
||||||
if (/^\d{4}-\d{2}$/.test(str)) return `${str}-01`
|
|
||||||
// 如果是纯数字时间戳
|
|
||||||
const num = Number(value)
|
|
||||||
if (!isNaN(num) && num > 10000) {
|
|
||||||
const d = new Date(num * 1000)
|
|
||||||
if (!isNaN(d.getTime())) {
|
|
||||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 格式化价格:分 → 元 */
|
|
||||||
function formatPrice(priceInCents: number): string {
|
|
||||||
if (priceInCents == null) return '0.00'
|
|
||||||
return (priceInCents / 100).toFixed(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchBookInfo(isbn: string) {
|
|
||||||
// 检查缓存
|
|
||||||
if (cache.has(isbn)) {
|
|
||||||
bookData.value = cache.get(isbn)!
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.value = true
|
|
||||||
error.value = null
|
|
||||||
bookData.value = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
const payload = await request.get('/getBookInfo', {
|
|
||||||
params: { isbn }
|
|
||||||
})
|
|
||||||
const data = payload?.data
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
error.value = '数据库中暂无该书数据'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const result: BookInfoResult = {
|
|
||||||
bookName: data.book_name || '',
|
|
||||||
author: data.author || '',
|
|
||||||
publisher: data.publisher || '',
|
|
||||||
publishDate: formatPublishDate(data.publication_time),
|
|
||||||
binding: data.binding_layout || '',
|
|
||||||
price: typeof data.fix_price === 'number' ? data.fix_price : 0,
|
|
||||||
pageCount: Number(data.page_count) || 0,
|
|
||||||
wordCount: Number(data.word_count) || 0,
|
|
||||||
book_pic: data.book_pic || undefined,
|
|
||||||
isSuit: data.is_suit === 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写入缓存
|
|
||||||
cache.set(isbn, result)
|
|
||||||
bookData.value = result
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('[goodsPop] 书籍信息查询失败:', err instanceof Error ? err.message : String(err))
|
|
||||||
error.value = '查询失败,请稍后重试'
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleShow() {
|
|
||||||
if (!props.isbn) return
|
|
||||||
fetchBookInfo(props.isbn)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleHide() {
|
|
||||||
// 不做清理,保留上次查询结果以便下次快速展示
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.isbn-popover-trigger {
|
|
||||||
cursor: pointer;
|
|
||||||
border-bottom: 1px dashed #409eff;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
.isbn-popover-trigger:hover {
|
|
||||||
color: #409eff;
|
|
||||||
}
|
|
||||||
.isbn-popover-trigger.is-loading {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 加载状态 */
|
|
||||||
.popover-loading {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
justify-content: center;
|
|
||||||
color: #909399;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
.popover-loading .is-loading {
|
|
||||||
animation: rotating 1.5s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes rotating {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 错误状态 */
|
|
||||||
.popover-error {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
justify-content: center;
|
|
||||||
color: #e6a23c;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 空状态 */
|
|
||||||
.popover-empty {
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
color: #c0c4cc;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 内容主体 */
|
|
||||||
.popover-content {
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
border-bottom: 1px solid #ebeef5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-isbn {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.suit-badge {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 1px 8px;
|
|
||||||
font-size: 11px;
|
|
||||||
color: #e6a23c;
|
|
||||||
background: #fdf6ec;
|
|
||||||
border: 1px solid #f5dab1;
|
|
||||||
border-radius: 4px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-body {
|
|
||||||
display: flex;
|
|
||||||
gap: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 封面 */
|
|
||||||
.popover-cover {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 90px;
|
|
||||||
height: 120px;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid #ebeef5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-placeholder {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: #f5f7fa;
|
|
||||||
color: #c0c4cc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 详情 */
|
|
||||||
.popover-info {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-row {
|
|
||||||
display: flex;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: #909399;
|
|
||||||
width: 56px;
|
|
||||||
text-align: right;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value {
|
|
||||||
flex: 1;
|
|
||||||
color: #303133;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value-name {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #409eff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value-price {
|
|
||||||
color: #f56c6c;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
/** 发货单 API 基础路径 */
|
|
||||||
const API_BASE = '/shipping-order'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标准化列表接口返回的数据格式
|
|
||||||
* @param {Object} payload - 接口返回的原始响应对象
|
|
||||||
* @returns {{ list: Array, total: number }} 标准化后的列表数据
|
|
||||||
*/
|
|
||||||
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 : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取发货单列表(支持分页和筛选)
|
|
||||||
* @param {Object} params - 请求参数
|
|
||||||
* @param {string} [params.check_no] - 搜索关键字(发货单号)
|
|
||||||
* @param {number} [params.status] - 状态筛选
|
|
||||||
* @param {number} [params.customer_id] - 客户ID筛选
|
|
||||||
* @param {number} [params.warehouse_id] - 仓库ID筛选
|
|
||||||
* @param {number} [params.sales_order_id] - 销售订单ID筛选
|
|
||||||
* @param {number} [params.wave_task_id] - 波次任务ID筛选
|
|
||||||
* @param {number} [params.page] - 当前页码
|
|
||||||
* @param {number} [params.pageSize] - 每页条数
|
|
||||||
* @returns {Promise<{ list: Array, total: number }>} 标准化后的发货单列表
|
|
||||||
*/
|
|
||||||
export const fetchShippingOrderList = async ({ check_no, status, customer_id, warehouse_id, sales_order_id, wave_task_id, page, pageSize }) => {
|
|
||||||
const params = {
|
|
||||||
check_no: check_no || undefined,
|
|
||||||
status,
|
|
||||||
customer_id,
|
|
||||||
warehouse_id,
|
|
||||||
sales_order_id,
|
|
||||||
wave_task_id,
|
|
||||||
page,
|
|
||||||
page_size: pageSize
|
|
||||||
}
|
|
||||||
const response = await request.get(`${API_BASE}/list`, { params })
|
|
||||||
return normalizeListResponse(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取单个发货单详情(含明细行)
|
|
||||||
* @param {string|number} id - 发货单ID
|
|
||||||
* @returns {Promise<Object|null>} 发货单详情对象
|
|
||||||
*/
|
|
||||||
export const fetchShippingOrderDetail = async (id) => {
|
|
||||||
const response = await request.get(`${API_BASE}/detail`, { params: { id } })
|
|
||||||
return response?.data || null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成发货单(form-data 格式)
|
|
||||||
* @param {Object} params
|
|
||||||
* @param {number} params.total - 选中的出库单数量
|
|
||||||
* @param {number[]} params.outbound_order_ids - 选中的出库单 ID 数组
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
export const createShippingOrder = async ({ total, outbound_order_ids }) => {
|
|
||||||
// 手动构建 FormData,按照 outbound_order_ids[0]={id} 的格式
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('total', String(total))
|
|
||||||
outbound_order_ids.forEach((id, index) => {
|
|
||||||
formData.append(`order_ids[${index}]`, String(id))
|
|
||||||
})
|
|
||||||
return request.post(`${API_BASE}/create`, formData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新发货单物流信息(form-data 格式)
|
|
||||||
* @param {Object} params
|
|
||||||
* @param {number} params.shipping_order_id - 发货单ID
|
|
||||||
* @param {number} params.sales_order_item_id - 销售订单明细ID
|
|
||||||
* @param {string} params.logistics_company - 物流公司
|
|
||||||
* @param {string} params.logistics_no - 物流单号
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
export const updateShippingOrderLogistics = async ({ shipping_order_id, sales_order_item_id, logistics_company, logistics_no }) => {
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('shipping_order_id', String(shipping_order_id))
|
|
||||||
formData.append('total', '1')
|
|
||||||
formData.append('sales_order_item_id', String(sales_order_item_id))
|
|
||||||
formData.append('logistics_company', logistics_company)
|
|
||||||
formData.append('logistics_no', logistics_no)
|
|
||||||
return request.post(`${API_BASE}/update`, formData)
|
|
||||||
}
|
|
||||||
1
dist/assets/AdminLayout.1bf65154.js
vendored
1
dist/assets/AdminLayout.1bf65154.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/AdminLayout.33c6af45.css
vendored
1
dist/assets/AdminLayout.33c6af45.css
vendored
@ -1 +0,0 @@
|
|||||||
.layout[data-v-2b474e07]{height:100%}.aside[data-v-2b474e07]{background-color:#304156;transition:width .3s;overflow:hidden;display:flex;flex-direction:column}.logo[data-v-2b474e07]{height:60px;line-height:60px;text-align:center;color:#fff;font-size:18px;font-weight:700;background-color:#1f2d3d;white-space:nowrap;overflow:hidden;flex-shrink:0}.logo-collapse[data-v-2b474e07]{font-size:20px}.menu[data-v-2b474e07]{border-right:none;flex:1;overflow-y:auto;overflow-x:hidden}.menu[data-v-2b474e07]::-webkit-scrollbar{width:6px}.menu[data-v-2b474e07]::-webkit-scrollbar-track{background:#304156}.menu[data-v-2b474e07]::-webkit-scrollbar-thumb{background:#4a5568;border-radius:3px}.menu[data-v-2b474e07]::-webkit-scrollbar-thumb:hover{background:#718096}.header[data-v-2b474e07]{background-color:#fff;border-bottom:1px solid #e6e9f0;display:flex;align-items:center;justify-content:space-between;padding:0 20px}.header-left[data-v-2b474e07]{display:flex;align-items:center}.collapse-icon[data-v-2b474e07]{font-size:20px;cursor:pointer;color:#606266}.header-right[data-v-2b474e07]{display:flex;align-items:center;gap:20px}.user-info[data-v-2b474e07]{display:flex;align-items:center;gap:5px;cursor:pointer;color:#606266;font-size:14px;outline:none}.user-info[data-v-2b474e07]:focus{outline:none}.points-badge[data-v-2b474e07]{background-color:#f56c6c;color:#fff;padding:4px 8px;border-radius:4px;font-size:12px}.main[data-v-2b474e07]{background-color:#f0f2f5;padding:10px}
|
|
||||||
1
dist/assets/Car.64ea3d2c.css
vendored
1
dist/assets/Car.64ea3d2c.css
vendored
@ -1 +0,0 @@
|
|||||||
.car-manager[data-v-49a939b7]{padding:0}.card-header[data-v-49a939b7]{font-size:16px;font-weight:600;color:#303133}.section-card[data-v-49a939b7]{margin-bottom:16px}.filter-bar[data-v-49a939b7]{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;align-items:center}.pagination-wrapper[data-v-49a939b7]{margin-top:20px;display:flex;justify-content:flex-end}[data-v-49a939b7] .el-table{border-radius:8px;overflow:hidden}[data-v-49a939b7] .el-dialog__body{padding-top:16px}.dialog-footer[data-v-49a939b7]{display:flex;justify-content:flex-end;gap:12px}.expand-content[data-v-49a939b7]{padding:16px 24px}.expand-header[data-v-49a939b7]{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.expand-title[data-v-49a939b7]{font-size:14px;font-weight:600;color:#303133}.secret-text[data-v-49a939b7]{color:#909399;font-family:monospace;letter-spacing:2px}.label-with-icon[data-v-49a939b7]{display:inline-flex;align-items:center;gap:4px}
|
|
||||||
1
dist/assets/Car.eafe5d13.js
vendored
1
dist/assets/Car.eafe5d13.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/Dashboard.11069b91.css
vendored
1
dist/assets/Dashboard.11069b91.css
vendored
@ -1 +0,0 @@
|
|||||||
.dashboard[data-v-bf7f84f8]{padding:20px;background:#f5f7fa;min-height:calc(100vh - 120px)}.stats-row[data-v-bf7f84f8]{margin-bottom:20px}.stat-card[data-v-bf7f84f8]{border-radius:12px;transition:all .3s ease;border:none}.stat-card[data-v-bf7f84f8]:hover{transform:translateY(-4px);box-shadow:0 12px 24px #0000001a}.stat-card[data-v-bf7f84f8] .el-card__body{padding:20px}.stat-content[data-v-bf7f84f8]{display:flex;align-items:center}.stat-icon[data-v-bf7f84f8]{width:64px;height:64px;border-radius:16px;display:flex;align-items:center;justify-content:center;color:#fff;margin-right:20px;flex-shrink:0}.stat-info[data-v-bf7f84f8]{flex:1}.stat-title[data-v-bf7f84f8]{font-size:14px;color:#909399;margin-bottom:8px}.stat-value[data-v-bf7f84f8]{font-size:32px;font-weight:700;color:#303133;line-height:1.2;margin-bottom:4px}.stat-desc[data-v-bf7f84f8]{font-size:12px;color:#909399}.mt-20[data-v-bf7f84f8]{margin-top:20px}.table-card[data-v-bf7f84f8]{border-radius:12px;border:none}.table-card[data-v-bf7f84f8] .el-card__header{padding:16px 20px;border-bottom:1px solid #ebeef5}.table-card[data-v-bf7f84f8] .el-card__body{padding:0}.card-header[data-v-bf7f84f8]{display:flex;justify-content:space-between;align-items:center}.card-title[data-v-bf7f84f8]{font-size:16px;font-weight:600;color:#303133}.product-name[data-v-bf7f84f8]{color:#606266;font-weight:500}.price[data-v-bf7f84f8]{color:#f56c6c;font-weight:600}.total-count[data-v-bf7f84f8]{color:#409eff;font-weight:600;font-size:15px}[data-v-bf7f84f8] .el-table .el-table__header th{background:#fafafa;color:#606266;font-weight:600}[data-v-bf7f84f8] .el-table .el-table__body td{padding:12px 0}
|
|
||||||
1
dist/assets/Dashboard.c148e71e.js
vendored
1
dist/assets/Dashboard.c148e71e.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/EmployeeAdd.2a39af1a.css
vendored
1
dist/assets/EmployeeAdd.2a39af1a.css
vendored
@ -1 +0,0 @@
|
|||||||
.employee-add[data-v-420844b2]{padding:0}.card-header[data-v-420844b2]{display:flex;justify-content:space-between;align-items:center;font-size:16px;font-weight:600;color:#303133}.steps[data-v-420844b2]{margin:20px 0 40px}.step-content[data-v-420844b2]{min-height:300px;padding:20px 0}.add-form[data-v-420844b2]{width:500px;margin:0 auto}.preview-card[data-v-420844b2]{width:500px;margin:30px auto 0;background-color:#f8f9fa}.preview-content[data-v-420844b2]{padding:10px}.preview-item[data-v-420844b2]{margin-bottom:10px;display:flex;align-items:center}.preview-item .label[data-v-420844b2]{width:80px;color:#666;font-size:13px}.preview-item .value[data-v-420844b2]{color:#333;font-size:13px}.confirm-info[data-v-420844b2]{width:500px;margin:0 auto}.step-actions[data-v-420844b2]{margin-top:30px;text-align:center}.result[data-v-420844b2]{display:flex;justify-content:center}.result-info[data-v-420844b2]{margin:20px 0;text-align:left}.result-actions[data-v-420844b2]{margin-top:20px;display:flex;gap:10px;justify-content:center}[data-v-420844b2] .el-descriptions__label{width:120px}.title-center[data-v-420844b2]{text-align:center;font-weight:700;font-size:16px;margin-bottom:10px}
|
|
||||||
8
dist/assets/EmployeeAdd.d7bb6a12.js
vendored
8
dist/assets/EmployeeAdd.d7bb6a12.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/EmployeeList.fa412065.js
vendored
1
dist/assets/EmployeeList.fa412065.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/EmployeeList.ff9c9877.css
vendored
1
dist/assets/EmployeeList.ff9c9877.css
vendored
@ -1 +0,0 @@
|
|||||||
.employee-list[data-v-2d3c9b96]{padding:0}.card-header[data-v-2d3c9b96]{display:flex;justify-content:space-between;align-items:center;font-size:16px;font-weight:600;color:#303133}.search-form[data-v-2d3c9b96]{margin-bottom:20px;padding:20px;background-color:#f8f9fa;border-radius:4px}.stat-cards[data-v-2d3c9b96]{margin-bottom:20px}.stat-card[data-v-2d3c9b96]{text-align:center}.stat-item[data-v-2d3c9b96]{padding:10px}.stat-label[data-v-2d3c9b96]{font-size:14px;color:#909399;margin-bottom:8px}.stat-value[data-v-2d3c9b96]{font-size:24px;font-weight:700;color:#303133}.stat-value.success[data-v-2d3c9b96]{color:#67c23a}.stat-value.warning[data-v-2d3c9b96]{color:#e6a23c}.stat-value.info[data-v-2d3c9b96]{color:#409eff}.pagination[data-v-2d3c9b96]{margin-top:20px;display:flex;justify-content:flex-end}.points-warning[data-v-2d3c9b96]{color:#f56c6c;font-weight:700}.deduct-input[data-v-2d3c9b96] .el-input-number__decrease:hover,.deduct-input[data-v-2d3c9b96] .el-input-number__increase:hover{color:#fff;background-color:#f56c6c}.deduct-label[data-v-2d3c9b96] .el-form-item__label{color:#f56c6c;font-weight:700}
|
|
||||||
1
dist/assets/Inventory.35285f41.js
vendored
1
dist/assets/Inventory.35285f41.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/Inventory.ff747304.css
vendored
1
dist/assets/Inventory.ff747304.css
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/Location.6e6087ff.css
vendored
1
dist/assets/Location.6e6087ff.css
vendored
@ -1 +0,0 @@
|
|||||||
.location-manager[data-v-633d9fa4]{padding:20px;background-color:#f5f7fa;min-height:100vh}.filter-bar[data-v-633d9fa4]{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;align-items:center;background:white;padding:16px 20px;border-radius:8px;box-shadow:0 1px 4px #0000000d}.pagination-wrapper[data-v-633d9fa4]{margin-top:20px;display:flex;justify-content:flex-end;background:white;padding:14px 20px;border-radius:8px}[data-v-633d9fa4] .el-table{border-radius:8px;overflow:hidden;box-shadow:0 1px 4px #0000000d}[data-v-633d9fa4] .el-dialog__body{padding-top:16px}.dialog-footer[data-v-633d9fa4]{display:flex;justify-content:flex-end;gap:12px}
|
|
||||||
10
dist/assets/Location.a63fd3e2.js
vendored
10
dist/assets/Location.a63fd3e2.js
vendored
File diff suppressed because one or more lines are too long
79
dist/assets/LocationManager.cb0b3008.js
vendored
79
dist/assets/LocationManager.cb0b3008.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/LocationManager.f0252049.css
vendored
1
dist/assets/LocationManager.f0252049.css
vendored
@ -1 +0,0 @@
|
|||||||
.filter-bar[data-v-68216036]{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;align-items:center;background:white;padding:16px 20px;border-radius:8px;box-shadow:0 1px 4px #0000000d}.pagination-wrapper[data-v-68216036]{margin-top:20px;display:flex;justify-content:flex-end;background:white;padding:14px 20px;border-radius:8px}.location-empty[data-v-68216036]{padding:40px 0;text-align:center;color:#999;background:#fafafa;border-radius:8px}[data-v-68216036] .el-table{border-radius:8px;overflow:hidden;box-shadow:0 1px 4px #0000000d}[data-v-68216036] .el-dialog__body{padding-top:16px}.dialog-footer[data-v-68216036]{display:flex;justify-content:flex-end;gap:12px}.location-dialog[data-v-68216036] .el-dialog__header{background:linear-gradient(90deg,#4b8afc,#6db6ff);border-bottom:none;color:#fff;border-radius:12px 12px 0 0}.location-dialog[data-v-68216036] .el-dialog__title{color:#fff;font-size:18px;font-weight:600}.location-dialog[data-v-68216036] .el-dialog__body{padding:24px 32px;background:#f6fbff}.location-dialog[data-v-68216036] .el-form{background:#ffffff;border-radius:16px;padding:16px;box-shadow:inset 0 0 0 1px #3454d114}.location-dialog[data-v-68216036] .el-form-item{margin-bottom:20px}.location-dialog[data-v-68216036] .el-form-item__label{color:#4b5563;font-weight:520}.location-dialog[data-v-68216036] .el-input,.location-dialog[data-v-68216036] .el-input-number,.location-dialog[data-v-68216036] .el-radio-group,.location-dialog[data-v-68216036] .el-switch{border-radius:10px}.location-dialog[data-v-68216036] .el-radio-group{gap:12px;display:flex;flex-wrap:wrap}.location-dialog[data-v-68216036] .el-dialog__footer{padding:18px 32px 24px;background:#ffffff}.batch-code-range-header[data-v-68216036]{display:flex;align-items:center;flex-wrap:wrap;gap:10px;margin-bottom:10px;border-radius:8px;color:#000000a6;font-size:12px;font-weight:600}.range-btn[data-v-68216036]{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border-radius:50%;border:1px solid rgba(16,24,40,.08);background:#ffffff;color:#333;transition:transform .12s ease,box-shadow .12s ease,background .12s ease;box-shadow:0 1px 2px #10182814}.range-btn[data-v-68216036]:hover{transform:translateY(-1px);box-shadow:0 4px 8px #1018281f}.range-btn-add[data-v-68216036]{border-color:#1677ff29;color:#176cff}.range-btn-add[data-v-68216036]:hover{background:#eff5ff}.range-btn-remove[data-v-68216036]{border-color:#ff4d4f29;color:#ff4d4f}.range-btn-remove[data-v-68216036]:hover{background:#fff2f2}.detail-loading[data-v-68216036]{display:flex;align-items:center;justify-content:center;gap:8px;padding:40px 0;color:#409eff}.detail-content[data-v-68216036]{padding:0 10px}.detail-section[data-v-68216036]{margin-bottom:24px}.detail-row[data-v-68216036]{display:flex;padding:8px 0;border-bottom:1px solid #f0f0f0}.detail-row[data-v-68216036]:last-child{border-bottom:none}.detail-label[data-v-68216036]{color:#909399;min-width:80px}.detail-code[data-v-68216036]{font-weight:600;color:#409eff}.status-enabled[data-v-68216036]{color:#67c23a}.status-disabled[data-v-68216036]{color:#f56c6c}.barcode-section[data-v-68216036]{margin-top:20px;padding-top:20px;border-top:1px solid #e4e7ed}.barcode-title[data-v-68216036]{font-size:14px;font-weight:600;color:#303133;margin-bottom:16px;text-align:center}.barcode-loading[data-v-68216036]{display:flex;align-items:center;justify-content:center;gap:8px;padding:30px 0;color:#409eff}.barcode-image-wrapper[data-v-68216036]{display:flex;flex-direction:column;align-items:center;gap:8px}.barcode-image[data-v-68216036]{max-width:100%;height:auto;border:1px solid #e4e7ed;border-radius:4px}.barcode-content-text[data-v-68216036]{font-size:14px;font-weight:600;color:#606266;font-family:Courier New,monospace}.barcode-empty[data-v-68216036]{text-align:center;color:#c0c4cc;padding:30px 0}.label-with-icon[data-v-68216036]{display:inline-flex;align-items:center;gap:4px}
|
|
||||||
1
dist/assets/Login.8ef9a7a5.js
vendored
1
dist/assets/Login.8ef9a7a5.js
vendored
@ -1 +0,0 @@
|
|||||||
import{_ as C,u as L,r as p,a as x,b as d,o as S,c as q,d as f,e as o,w as l,f as _,g as k,h as U,s as A,i as B,E,j as z,l as F}from"./index.a99ea9b6.js";import{r as K}from"./request.3edc855e.js";import{u as N}from"./user.dcd53bf0.js";import"./axios.e318b91e.js";const T={class:"login-container"},j={class:"login-box"},D={__name:"Login",setup(M){const R=N(),I=L(),u=p("admin"),i=p(!1),c=p(null),g=p(null),r=x({username:"",password:"",about_id:0}),n=x({username:"",password:"",about_id:0}),y={username:[{required:!0,message:"\u8BF7\u8F93\u5165\u8D26\u53F7",trigger:"blur"}],password:[{required:!0,message:"\u8BF7\u8F93\u5165\u5BC6\u7801",trigger:"blur"},{min:6,message:"\u5BC6\u7801\u957F\u5EA6\u4E0D\u80FD\u5C0F\u4E8E6\u4F4D",trigger:"blur"}]},m=async()=>{const b=u.value==="employee"?c:g,e=u.value==="employee"?r:n;await b.value.validate(),i.value=!0;try{const t=u.value==="admin"?"255":"128",a=await K.post(`/login/${t}`,{...e,type:1});a.code===200&&(A(a.data.token),B(a.data),R.setUserInfoAction(a.data),localStorage.setItem("test_ip","127.0.0.1"),localStorage.setItem("test_port","8080"),E.success("\u767B\u5F55\u6210\u529F"),I.push("/dashboard"))}catch{}finally{i.value=!1}};return(b,e)=>{const t=d("el-input"),a=d("el-form-item"),v=d("el-button"),w=d("el-form"),V=d("el-tab-pane"),h=d("el-tabs");return S(),q("div",T,[f("div",j,[e[7]||(e[7]=f("div",{class:"login-header"},[f("h2",null,"\u8FDB\u9500\u5B58\u7CFB\u7EDF"),f("p",null,"\u8BF7\u9009\u62E9\u767B\u5F55\u89D2\u8272")],-1)),o(h,{modelValue:u.value,"onUpdate:modelValue":e[4]||(e[4]=s=>u.value=s),class:"login-tabs"},{default:l(()=>[o(V,{label:"\u7BA1\u7406\u5458\u767B\u5F55",name:"admin"},{default:l(()=>[o(w,{ref_key:"adminFormRef",ref:g,model:n,rules:y,"label-width":"0",class:"login-form"},{default:l(()=>[o(a,{prop:"username"},{default:l(()=>[o(t,{modelValue:n.username,"onUpdate:modelValue":e[0]||(e[0]=s=>n.username=s),placeholder:"\u8BF7\u8F93\u5165\u8D26\u53F7","prefix-icon":_(z),size:"large"},null,8,["modelValue","prefix-icon"])]),_:1}),o(a,{prop:"password"},{default:l(()=>[o(t,{modelValue:n.password,"onUpdate:modelValue":e[1]||(e[1]=s=>n.password=s),type:"password",placeholder:"\u8BF7\u8F93\u5165\u5BC6\u7801","prefix-icon":_(F),size:"large","show-password":"",onKeyup:k(m,["enter"])},null,8,["modelValue","prefix-icon"])]),_:1}),o(a,null,{default:l(()=>[o(v,{type:"primary",loading:i.value,class:"login-btn",size:"large",onClick:m},{default:l(()=>[...e[5]||(e[5]=[U(" \u767B\u5F55 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"])]),_:1}),o(V,{label:"\u4EE3\u7406\u767B\u5F55",name:"employee"},{default:l(()=>[o(w,{ref_key:"employeeFormRef",ref:c,model:r,rules:y,"label-width":"0",class:"login-form"},{default:l(()=>[o(a,{prop:"username"},{default:l(()=>[o(t,{modelValue:r.username,"onUpdate:modelValue":e[2]||(e[2]=s=>r.username=s),placeholder:"\u8BF7\u8F93\u5165\u8D26\u53F7","prefix-icon":_(z),size:"large"},null,8,["modelValue","prefix-icon"])]),_:1}),o(a,{prop:"password"},{default:l(()=>[o(t,{modelValue:r.password,"onUpdate:modelValue":e[3]||(e[3]=s=>r.password=s),type:"password",placeholder:"\u8BF7\u8F93\u5165\u5BC6\u7801","prefix-icon":_(F),size:"large","show-password":"",onKeyup:k(m,["enter"])},null,8,["modelValue","prefix-icon"])]),_:1}),o(a,null,{default:l(()=>[o(v,{type:"primary",loading:i.value,class:"login-btn",size:"large",onClick:m},{default:l(()=>[...e[6]||(e[6]=[U(" \u767B\u5F55 ",-1)])]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"])]),_:1})]),_:1},8,["modelValue"])])])}}};var O=C(D,[["__scopeId","data-v-6b005749"]]);export{O as default};
|
|
||||||
1
dist/assets/Login.94f404f3.css
vendored
1
dist/assets/Login.94f404f3.css
vendored
@ -1 +0,0 @@
|
|||||||
.login-container[data-v-6b005749]{height:100%;display:flex;justify-content:center;align-items:center;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%)}.login-box[data-v-6b005749]{width:400px;padding:40px;background:#fff;border-radius:8px;box-shadow:0 2px 12px #0000001a}.login-header[data-v-6b005749]{text-align:center;margin-bottom:30px}.login-header h2[data-v-6b005749]{font-size:24px;color:#333;margin-bottom:10px}.login-header p[data-v-6b005749]{color:#666;font-size:14px}.login-tabs[data-v-6b005749]{margin-bottom:20px}.login-form[data-v-6b005749]{margin-top:20px}.login-btn[data-v-6b005749]{width:100%;margin-top:10px}.login-footer[data-v-6b005749]{margin-top:30px;text-align:center;color:#999;font-size:12px}.login-footer p[data-v-6b005749]{margin:5px 0}
|
|
||||||
1
dist/assets/Outbound.3c07bfdd.css
vendored
1
dist/assets/Outbound.3c07bfdd.css
vendored
@ -1 +0,0 @@
|
|||||||
.outbound-manager[data-v-05a0e3f2]{width:100%}.card-header[data-v-05a0e3f2]{font-size:16px;font-weight:600;color:#303133}.filter-bar[data-v-05a0e3f2]{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:16px}.pagination-wrapper[data-v-05a0e3f2]{display:flex;justify-content:flex-end;margin-top:16px}.items-header[data-v-05a0e3f2]{display:flex;justify-content:flex-end}[data-v-05a0e3f2] .el-table__expanded-cell{padding:0}.outbound-search-bar[data-v-05a0e3f2]{display:flex;align-items:center;gap:12px;margin-bottom:14px}.selected-tip[data-v-05a0e3f2]{font-size:13px;color:#606266}.selected-tip strong[data-v-05a0e3f2]{color:#409eff;font-size:16px}.dialog-pagination-wrapper[data-v-05a0e3f2]{display:flex;justify-content:flex-end;margin-top:12px}.dialog-footer[data-v-05a0e3f2]{display:flex;justify-content:flex-end;gap:10px}
|
|
||||||
1
dist/assets/Outbound.cebe9ed0.js
vendored
1
dist/assets/Outbound.cebe9ed0.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/PrinterManager.0cffe546.js
vendored
1
dist/assets/PrinterManager.0cffe546.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/PrinterManager.d81e43a6.css
vendored
1
dist/assets/PrinterManager.d81e43a6.css
vendored
@ -1 +0,0 @@
|
|||||||
.printer-manager[data-v-6e647c52]{padding:20px}.page-header[data-v-6e647c52]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.page-header h2[data-v-6e647c52]{margin:0;font-size:22px;color:#303133}.printer-card[data-v-6e647c52]{min-height:200px}.printer-form[data-v-6e647c52]{max-width:600px}.printer-row[data-v-6e647c52]{display:flex;align-items:center;gap:12px}.printer-form[data-v-6e647c52] .el-form-item{display:flex;align-items:center;margin-bottom:18px}.printer-form[data-v-6e647c52] .el-form-item__label{align-self:center;line-height:normal}
|
|
||||||
2
dist/assets/Product.7e59cf70.js
vendored
2
dist/assets/Product.7e59cf70.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/Product.bea38b0c.css
vendored
1
dist/assets/Product.bea38b0c.css
vendored
@ -1 +0,0 @@
|
|||||||
.product-list-wrapper[data-v-773aa012]{width:100%}.filter-bar[data-v-773aa012]{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;align-items:center;background:white;padding:16px 20px;border-radius:8px;box-shadow:0 1px 4px #0000000d}.pagination-wrapper[data-v-773aa012]{margin-top:20px;display:flex;justify-content:flex-end;background:white;padding:14px 20px;border-radius:8px}[data-v-773aa012] .el-table{border-radius:8px;overflow:hidden;box-shadow:0 1px 4px #0000000d}.shop-list-cell[data-v-773aa012]{display:flex;flex-direction:column;gap:4px;padding:4px 0}.shop-item[data-v-773aa012]{line-height:1.4}.product-by-location[data-v-2077780a]{width:100%}.filter-bar[data-v-2077780a]{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;align-items:center;background:white;padding:16px 20px;border-radius:8px;box-shadow:0 1px 4px #0000000d}.pagination-wrapper[data-v-2077780a]{margin-top:20px;display:flex;justify-content:flex-end;background:white;padding:14px 20px;border-radius:8px}[data-v-2077780a] .el-table{border-radius:8px;overflow:hidden;box-shadow:0 1px 4px #0000000d}.expand-content[data-v-2077780a]{padding:12px 20px;background:#fafafa}.expand-header[data-v-2077780a]{margin-bottom:12px}.expand-title[data-v-2077780a]{font-weight:600;font-size:14px;color:#303133}.expand-pagination[data-v-2077780a]{margin-top:12px;display:flex;justify-content:flex-end}.product-manager[data-v-2a00a25f]{width:100%}.card-header[data-v-2a00a25f]{font-size:16px;font-weight:600;color:#303133}[data-v-2a00a25f] .el-dialog__body{padding-top:16px}.dialog-footer[data-v-2a00a25f]{display:flex;justify-content:flex-end;gap:12px}
|
|
||||||
1
dist/assets/PurchaseOrder.3af936cc.css
vendored
1
dist/assets/PurchaseOrder.3af936cc.css
vendored
@ -1 +0,0 @@
|
|||||||
.purchase-order-manager[data-v-1b44da8e]{width:100%}.card-header[data-v-1b44da8e]{font-size:16px;font-weight:600;color:#303133}.filter-bar[data-v-1b44da8e]{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;align-items:center;background:white;padding:16px 20px;border-radius:8px;box-shadow:0 1px 4px #0000000d}.pagination-wrapper[data-v-1b44da8e]{margin-top:20px;display:flex;justify-content:flex-end;background:white;padding:14px 20px;border-radius:8px}[data-v-1b44da8e] .el-table{border-radius:8px;overflow:hidden;box-shadow:0 1px 4px #0000000d}[data-v-1b44da8e] .el-dialog__body{padding-top:16px}.dialog-footer[data-v-1b44da8e]{display:flex;justify-content:flex-end;gap:12px}
|
|
||||||
1
dist/assets/PurchaseOrder.c05f9074.js
vendored
1
dist/assets/PurchaseOrder.c05f9074.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/ReviewIllegalBook.2afb2852.css
vendored
1
dist/assets/ReviewIllegalBook.2afb2852.css
vendored
@ -1 +0,0 @@
|
|||||||
.review-page[data-v-221993fc]{padding:0}.card-header[data-v-221993fc]{display:flex;justify-content:space-between;align-items:center}.section-card[data-v-221993fc]{margin-bottom:16px}.search-bar[data-v-221993fc]{display:flex;align-items:center;gap:16px;margin-bottom:16px}.search-item[data-v-221993fc]{display:flex;align-items:center;gap:8px}.search-item span[data-v-221993fc]{font-size:14px;color:#606266;white-space:nowrap}.pagination-wrapper[data-v-221993fc]{display:flex;justify-content:flex-end;margin-top:16px}.diff-cell{color:#f56c6c!important;font-weight:700}.diff-popover{padding:8px 12px!important}.diff-content{font-size:13px;line-height:1.6}.diff-row{display:flex;align-items:flex-start;gap:4px;padding:2px 0}.diff-label{white-space:nowrap;color:#909399;flex-shrink:0}.diff-val{color:#303133;word-break:break-all}.diff-row.new .diff-val{color:#e6a23c;font-weight:700}
|
|
||||||
1
dist/assets/ReviewIllegalBook.8a97f6c7.js
vendored
1
dist/assets/ReviewIllegalBook.8a97f6c7.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/SalesOrder.216c98d0.js
vendored
1
dist/assets/SalesOrder.216c98d0.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/SalesOrder.80865102.css
vendored
1
dist/assets/SalesOrder.80865102.css
vendored
@ -1 +0,0 @@
|
|||||||
@charset "UTF-8";.sales-order-manager[data-v-6514288b]{width:100%}.card-header[data-v-6514288b]{font-size:16px;font-weight:600;color:#303133}.filter-bar[data-v-6514288b]{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:16px}.pagination-wrapper[data-v-6514288b]{display:flex;justify-content:flex-end;margin-top:16px}.items-header[data-v-6514288b]{display:flex;justify-content:flex-end}.total-amount[data-v-6514288b]{display:flex;justify-content:flex-end;align-items:center;margin-top:16px;padding:12px;background:#f5f7fa;border-radius:4px;font-size:14px}.total-amount .amount[data-v-6514288b]{color:#e6a23c;font-weight:600;font-size:18px;margin-left:8px}[data-v-6514288b] .el-table__expanded-cell{padding:0}.outbound-search-bar[data-v-6514288b]{display:flex;align-items:center;gap:16px;margin-bottom:16px}.outbound-search-bar .selected-tip[data-v-6514288b]{font-size:13px;color:#606266}.outbound-search-bar .selected-tip strong[data-v-6514288b]{color:#67c23a;font-size:15px}
|
|
||||||
1
dist/assets/ShippingOrder.5cf2c17e.css
vendored
1
dist/assets/ShippingOrder.5cf2c17e.css
vendored
@ -1 +0,0 @@
|
|||||||
@charset "UTF-8";.shipping-order-manager[data-v-cb65ffee]{width:100%}.card-header[data-v-cb65ffee]{font-size:16px;font-weight:600;color:#303133}.filter-bar[data-v-cb65ffee]{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:16px}.pagination-wrapper[data-v-cb65ffee]{display:flex;justify-content:flex-end;margin-top:16px}[data-v-cb65ffee] .el-table__expanded-cell{padding:0}.hidden-scan-input[data-v-cb65ffee]{position:absolute;left:-9999px;opacity:0;width:1px;height:1px}[data-v-cb65ffee] .el-table__row.scan-completed-row{background-color:#ecf5ff}[data-v-cb65ffee] .el-table__row.scan-completed-row:hover>td{background-color:#ecf5ff}
|
|
||||||
1
dist/assets/ShippingOrder.dab0a7cb.js
vendored
1
dist/assets/ShippingOrder.dab0a7cb.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/Shop.70cbd0b9.css
vendored
1
dist/assets/Shop.70cbd0b9.css
vendored
@ -1 +0,0 @@
|
|||||||
.shop-manager[data-v-6b1a2acb]{width:100%}.card-header[data-v-6b1a2acb]{font-size:16px;font-weight:600;color:#303133}.filter-bar[data-v-6b1a2acb]{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;align-items:center;background:white;padding:16px 20px;border-radius:8px;box-shadow:0 1px 4px #0000000d}.pagination-wrapper[data-v-6b1a2acb]{margin-top:20px;display:flex;justify-content:flex-end;background:white;padding:14px 20px;border-radius:8px}[data-v-6b1a2acb] .el-table{border-radius:8px;overflow:hidden;box-shadow:0 1px 4px #0000000d}[data-v-6b1a2acb] .el-dialog__body{padding-top:16px}.dialog-footer[data-v-6b1a2acb]{display:flex;justify-content:flex-end;gap:12px}.shop-detail-container[data-v-6b1a2acb]{max-height:60vh;overflow-y:auto;padding-right:8px}.detail-section[data-v-6b1a2acb]{margin-bottom:24px}.section-title[data-v-6b1a2acb]{font-size:16px;font-weight:600;color:#303133;padding-left:10px;border-left:4px solid #409eff;margin-bottom:16px;line-height:1.2}.token-placeholder[data-v-6b1a2acb]{font-family:monospace;letter-spacing:2px;color:#909399}[data-v-6b1a2acb] .el-descriptions__label{width:130px;background-color:#fafafa}[data-v-6b1a2acb] .el-descriptions__content{word-break:break-all}.img-list[data-v-6b1a2acb]{display:flex;flex-wrap:wrap;gap:8px}
|
|
||||||
1
dist/assets/Shop.9d76ddfe.js
vendored
1
dist/assets/Shop.9d76ddfe.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/ShopSettings.c3a127ce.css
vendored
1
dist/assets/ShopSettings.c3a127ce.css
vendored
@ -1 +0,0 @@
|
|||||||
.shop-settings[data-v-55d38e90]{max-width:1000px;margin:0 auto;padding:24px;background:#fff;border-radius:16px;box-shadow:0 4px 12px #0000000d;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif}.settings-title[data-v-55d38e90]{font-size:24px;font-weight:600;margin:0 0 20px;color:#1e293b;border-left:4px solid #3b82f6;padding-left:16px}.settings-tabs[data-v-55d38e90]{display:flex;gap:8px;border-bottom:1px solid #e2e8f0;margin-bottom:24px}.tab-btn[data-v-55d38e90]{padding:10px 20px;font-size:15px;font-weight:500;background:none;border:none;cursor:pointer;color:#64748b;border-radius:8px 8px 0 0;transition:all .2s}.tab-btn.active[data-v-55d38e90]{color:#3b82f6;background:#eff6ff;border-bottom:2px solid #3b82f6}.tab-content[data-v-55d38e90]{animation:fadeIn-55d38e90 .2s ease}@keyframes fadeIn-55d38e90{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.setting-section[data-v-55d38e90]{background:#f8fafc;border-radius:16px;padding:20px;margin-bottom:24px;border:1px solid #e2e8f0}.section-title[data-v-55d38e90]{font-size:18px;font-weight:600;margin:0 0 16px;color:#0f172a;padding-left:10px;border-left:3px solid #3b82f6}.setting-grid[data-v-55d38e90]{display:grid;grid-template-columns:repeat(2,1fr);gap:20px}.setting-row[data-v-55d38e90]{display:flex;gap:20px;flex-wrap:wrap}.setting-row.three-cols[data-v-55d38e90]{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}.setting-item[data-v-55d38e90]{flex:1;min-width:160px}.setting-item.full[data-v-55d38e90]{max-width:600px}.setting-label[data-v-55d38e90]{display:block;font-size:14px;font-weight:500;margin-bottom:8px;color:#334155}.setting-input[data-v-55d38e90]{width:100%;padding:8px 12px;border:1px solid #cbd5e1;border-radius:8px;font-size:14px;transition:.2s;background:white}.setting-input[data-v-55d38e90]:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 2px #3b82f633}.price-input-group[data-v-55d38e90]{display:flex;align-items:center;gap:6px}.unit[data-v-55d38e90]{font-size:13px;color:#475569}.field-hint[data-v-55d38e90]{font-size:12px;color:#6c757d;margin-top:6px;line-height:1.4}.field-hint.warning[data-v-55d38e90]{color:#e67e22;background:#fff3e0;padding:6px 10px;border-radius:8px;margin-top:8px}.switch-item[data-v-55d38e90]{margin-bottom:20px}.switch-label[data-v-55d38e90]{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;font-weight:500;color:#1e293b}.switch[data-v-55d38e90]{position:relative;display:inline-block;width:48px;height:24px}.switch input[data-v-55d38e90]{opacity:0;width:0;height:0}.slider[data-v-55d38e90]{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#cbd5e1;transition:.3s;border-radius:34px}.slider[data-v-55d38e90]:before{position:absolute;content:"";height:18px;width:18px;left:3px;bottom:3px;background-color:#fff;transition:.3s;border-radius:50%}input:checked+.slider[data-v-55d38e90]{background-color:#3b82f6}input:checked+.slider[data-v-55d38e90]:before{transform:translate(24px)}.formula-hint[data-v-55d38e90]{background:#eef2ff;padding:12px;border-radius:12px;font-size:13px;color:#1e40af;margin-top:12px;line-height:1.5}.actions-bar[data-v-55d38e90]{display:flex;justify-content:flex-end;gap:16px;margin-top:16px;padding-top:16px;border-top:1px solid #e2e8f0}.btn-primary[data-v-55d38e90]{background:#3b82f6;color:#fff;border:none;padding:8px 24px;border-radius:40px;font-size:14px;font-weight:500;cursor:pointer;transition:.2s}.btn-primary[data-v-55d38e90]:hover{background:#2563eb}.btn-secondary[data-v-55d38e90]{background:white;border:1px solid #cbd5e1;padding:8px 24px;border-radius:40px;font-size:14px;cursor:pointer;transition:.2s}.btn-secondary[data-v-55d38e90]:hover{background:#f1f5f9}@media (max-width: 680px){.setting-grid[data-v-55d38e90],.setting-row.three-cols[data-v-55d38e90]{grid-template-columns:1fr}.shop-settings[data-v-55d38e90]{padding:16px}}
|
|
||||||
2
dist/assets/SortingSettings.2b830c43.js
vendored
2
dist/assets/SortingSettings.2b830c43.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/SortingSettings.ad0f0396.css
vendored
1
dist/assets/SortingSettings.ad0f0396.css
vendored
@ -1 +0,0 @@
|
|||||||
.sorting-settings[data-v-483976f4]{max-width:800px;margin:20px auto;padding:20px;background:#fff;border-radius:8px;box-shadow:0 2px 12px #0000001a}h2[data-v-483976f4]{text-align:center;margin-bottom:24px}.tips-block[data-v-483976f4]{background-color:#f5f7fa;padding:8px 12px;border-radius:4px;color:#606266;font-size:13px;margin-bottom:12px;line-height:1.5;border-left:3px solid #409eff}.field-hint[data-v-483976f4]{font-size:12px;color:#909399;margin-top:4px}.switch-hint[data-v-483976f4]{margin-left:12px;font-size:12px;color:#909399}
|
|
||||||
1
dist/assets/StockCheck.98b52150.js
vendored
1
dist/assets/StockCheck.98b52150.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/StockCheck.ef28971e.css
vendored
1
dist/assets/StockCheck.ef28971e.css
vendored
@ -1 +0,0 @@
|
|||||||
.stock-check-manager[data-v-0e6961ff]{padding:20px;background:#fff;border-radius:4px}.filter-bar[data-v-0e6961ff]{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:16px}.pagination-wrapper[data-v-0e6961ff]{display:flex;justify-content:flex-end;margin-top:16px}.expand-content[data-v-0e6961ff]{padding:12px 20px;background:#fafafa}.expand-actions[data-v-0e6961ff]{margin-top:12px;text-align:right}.text-primary[data-v-0e6961ff]{color:#409eff;font-weight:600}.text-success[data-v-0e6961ff]{color:#67c23a;font-weight:600}.text-warning[data-v-0e6961ff]{color:#e6a23c;font-weight:600}.text-danger[data-v-0e6961ff]{color:#f56c6c;font-weight:600}.text-info[data-v-0e6961ff]{color:#909399;font-weight:600}
|
|
||||||
1
dist/assets/SubmIllegalBook.e6fe5606.css
vendored
1
dist/assets/SubmIllegalBook.e6fe5606.css
vendored
@ -1 +0,0 @@
|
|||||||
.illegal-book-dialog-body[data-v-32aaa3fb]{display:flex;gap:24px;min-height:500px}.illegal-side[data-v-32aaa3fb]{flex:1;padding:16px;border-radius:8px;background-color:#fafafa}.illegal-side-left[data-v-32aaa3fb]{background-color:#f5f7fa;border:1px solid #e4e7ed}.illegal-side-right[data-v-32aaa3fb]{background-color:#fff;border:1px solid #dcdfe6}.illegal-side-title[data-v-32aaa3fb]{font-size:16px;font-weight:600;margin-bottom:16px;padding-bottom:8px;border-bottom:1px solid #e4e7ed;color:#303133}.book-content[data-v-32aaa3fb]{position:relative;overflow:hidden}.book-image-container[data-v-32aaa3fb]{float:left;margin-right:16px;margin-bottom:16px}.book-image[data-v-32aaa3fb]{width:300px;height:300px;overflow:hidden;border-radius:8px;box-shadow:0 4px 12px #0000001a}.book-image img[data-v-32aaa3fb]{max-width:100%;max-height:100%;object-fit:cover}.book-image[data-v-32aaa3fb]{position:relative}.book-image-overlay[data-v-32aaa3fb]{position:absolute;bottom:0;left:0;right:0;height:40px;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px;letter-spacing:1px;transition:height .25s ease;cursor:pointer}.book-image:hover .book-image-overlay[data-v-32aaa3fb]{height:100%;background:rgba(0,0,0,.6)}.book-image-overlay span[data-v-32aaa3fb]{pointer-events:none}.book-image-placeholder[data-v-32aaa3fb]{width:300px;height:300px;display:flex;align-items:center;justify-content:center;border:1px solid #ddd;border-radius:8px;background-color:#f5f5f5}.book-image-placeholder p[data-v-32aaa3fb]{color:#666;text-align:center;margin:0;font-size:14px}.book-info-header[data-v-32aaa3fb]{overflow:hidden}.book-other-fields[data-v-32aaa3fb]{clear:both;display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:12px;margin-top:12px}.form-group[data-v-32aaa3fb]{margin-bottom:12px}.form-group label[data-v-32aaa3fb]{display:block;font-size:14px;font-weight:500;color:#666;margin-bottom:4px}.form-group.inline-label[data-v-32aaa3fb]{display:flex;align-items:center;gap:8px}.form-group.inline-label label[data-v-32aaa3fb]{display:block;flex-shrink:0;width:60px}.form-group.inline-label .form-input[data-v-32aaa3fb],.form-group.inline-label .form-textarea[data-v-32aaa3fb]{flex:1;width:100%;min-width:0}.form-input[data-v-32aaa3fb]{width:100%;padding:8px 12px;border:1px solid #ddd;border-radius:4px;font-size:14px;transition:border-color .3s ease;box-sizing:border-box}.form-input[data-v-32aaa3fb]:focus{outline:none;border-color:#409eff;box-shadow:0 0 0 2px #409eff33}.form-textarea[data-v-32aaa3fb]{width:100%;padding:8px 12px;border:1px solid #ddd;border-radius:4px;font-size:14px;resize:vertical;min-height:60px;transition:border-color .3s ease;box-sizing:border-box}.form-textarea[data-v-32aaa3fb]:focus{outline:none;border-color:#409eff;box-shadow:0 0 0 2px #409eff33}.illegal-side-right .form-input[data-v-32aaa3fb]:not([disabled]),.illegal-side-right .form-textarea[data-v-32aaa3fb]:not([disabled]){border-color:#409eff;background-color:#fff}.illegal-side-right .form-input[data-v-32aaa3fb]:not([disabled]):focus,.illegal-side-right .form-textarea[data-v-32aaa3fb]:not([disabled]):focus{box-shadow:0 0 0 2px #409eff4d}.illegal-side-right .el-select[data-v-32aaa3fb]{width:100%}.illegal-side-right .el-select .el-input__wrapper[data-v-32aaa3fb]{border:1px solid #ddd;border-radius:4px;box-shadow:none;padding:1px 12px;background-color:#fff;min-height:36px}.illegal-side-right .el-select .el-input__wrapper.is-focus[data-v-32aaa3fb]{border-color:#409eff;box-shadow:0 0 0 2px #409eff33}.illegal-side-right .el-select .el-input__inner[data-v-32aaa3fb]{font-size:14px;height:30px}.dialog-footer[data-v-32aaa3fb]{display:flex;justify-content:flex-end;gap:12px}
|
|
||||||
1
dist/assets/SubmIllegalBook.f14ef015.js
vendored
1
dist/assets/SubmIllegalBook.f14ef015.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/Supplier.037ba951.css
vendored
1
dist/assets/Supplier.037ba951.css
vendored
@ -1 +0,0 @@
|
|||||||
.supplier-manager[data-v-56ea2431]{padding:20px;background-color:#f5f7fa;min-height:100vh}.filter-bar[data-v-56ea2431]{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;align-items:center;background:white;padding:16px 20px;border-radius:8px;box-shadow:0 1px 4px #0000000d}.pagination-wrapper[data-v-56ea2431]{margin-top:20px;display:flex;justify-content:flex-end;background:white;padding:14px 20px;border-radius:8px}[data-v-56ea2431] .el-table{border-radius:8px;overflow:hidden;box-shadow:0 1px 4px #0000000d}[data-v-56ea2431] .el-dialog__body{padding-top:16px}.dialog-footer[data-v-56ea2431]{display:flex;justify-content:flex-end;gap:12px}
|
|
||||||
10
dist/assets/Supplier.4f9088c6.js
vendored
10
dist/assets/Supplier.4f9088c6.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/WareHouse.66b015d4.js
vendored
1
dist/assets/WareHouse.66b015d4.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/WareHouse.d122b8bf.css
vendored
1
dist/assets/WareHouse.d122b8bf.css
vendored
@ -1 +0,0 @@
|
|||||||
.warehouse-manager[data-v-12afdc00]{padding:20px;background-color:#f5f7fa;box-sizing:border-box}.filter-bar[data-v-12afdc00]{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;align-items:center;background:white;padding:16px 20px;border-radius:8px;box-shadow:0 1px 4px #0000000d}.pagination-wrapper[data-v-12afdc00]{margin-top:20px;display:flex;justify-content:flex-end;background:white;padding:14px 20px;border-radius:8px}[data-v-12afdc00] .el-table{border-radius:8px;overflow:hidden;box-shadow:0 1px 4px #0000000d}[data-v-12afdc00] .el-table__row.selected-warehouse-row,[data-v-12afdc00] .el-table__row.selected-warehouse-row>td,[data-v-12afdc00] .el-table__fixed-right .el-table__row.selected-warehouse-row td{background-color:#ffe6e6!important}[data-v-12afdc00] .el-table--striped .el-table__row.selected-warehouse-row.el-table__row--striped td,[data-v-12afdc00] .el-table--striped .el-table__row.selected-warehouse-row td{background-color:#ffe6e6!important}[data-v-12afdc00] .el-table__row.selected-warehouse-row:hover>td{background-color:#ffe6e6!important}.dialog-footer[data-v-12afdc00]{display:flex;justify-content:flex-end;gap:12px}.detail-loading[data-v-12afdc00]{display:flex;align-items:center;justify-content:center;padding:40px 0;color:#909399;gap:8px}.detail-content[data-v-12afdc00]{padding:0 10px}.detail-section[data-v-12afdc00]{margin-bottom:20px}.detail-row[data-v-12afdc00]{display:flex;padding:8px 0;border-bottom:1px solid #f0f0f0}.detail-row[data-v-12afdc00]:last-child{border-bottom:none}.detail-label[data-v-12afdc00]{color:#909399;min-width:80px;flex-shrink:0}.detail-code[data-v-12afdc00]{font-weight:600;color:#409eff}.status-enabled[data-v-12afdc00]{color:#67c23a;font-weight:500}.status-disabled[data-v-12afdc00]{color:#f56c6c;font-weight:500}.barcode-section[data-v-12afdc00]{margin-top:20px;padding-top:20px;border-top:1px solid #e4e7ed;text-align:center}.barcode-title[data-v-12afdc00]{font-size:14px;color:#606266;margin-bottom:12px}.barcode-loading[data-v-12afdc00]{display:flex;align-items:center;justify-content:center;padding:20px 0;color:#909399;gap:8px}.barcode-image-wrapper[data-v-12afdc00]{padding:12px;background:#fafafa;border-radius:6px;display:inline-block}.barcode-image[data-v-12afdc00]{max-width:200px;height:auto}.barcode-content-text[data-v-12afdc00]{margin-top:8px;font-size:13px;color:#606266;font-family:Courier New,monospace}.label-with-icon[data-v-12afdc00]{display:inline-flex;align-items:center;gap:4px}.warehouse-page[data-v-6300bf56]{padding:0}.section-card[data-v-6300bf56]{margin-bottom:16px}.section-card[data-v-6300bf56]:last-child{margin-bottom:0}.card-header[data-v-6300bf56]{font-size:16px;font-weight:600;color:#303133}
|
|
||||||
1
dist/assets/Wave.aae67b45.js
vendored
1
dist/assets/Wave.aae67b45.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/Wave.bfd8be47.css
vendored
1
dist/assets/Wave.bfd8be47.css
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/WaveTask.078645b8.css
vendored
1
dist/assets/WaveTask.078645b8.css
vendored
@ -1 +0,0 @@
|
|||||||
.wave-task-manager[data-v-ba0457b6]{width:100%}.card-header[data-v-ba0457b6]{font-size:16px;font-weight:600;color:#303133}.filter-bar[data-v-ba0457b6]{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;align-items:center;background:white;padding:16px 20px;border-radius:8px;box-shadow:0 1px 4px #0000000d}.pagination-wrapper[data-v-ba0457b6]{margin-top:20px;display:flex;justify-content:flex-end;background:white;padding:14px 20px;border-radius:8px}[data-v-ba0457b6] .el-table{border-radius:8px;overflow:hidden;box-shadow:0 1px 4px #0000000d}.expand-content[data-v-ba0457b6]{padding:12px 20px;background:#fafafa}.expand-content[data-v-ba0457b6] .el-table{box-shadow:none}.no-data[data-v-ba0457b6]{text-align:center;color:#909399;padding:20px}.text-danger[data-v-ba0457b6]{color:#f56c6c}.detail-section[data-v-ba0457b6]{margin-top:20px}.detail-section h4[data-v-ba0457b6]{margin-bottom:12px;color:#303133}[data-v-ba0457b6] .el-dialog__body{padding-top:16px}.dialog-footer[data-v-ba0457b6]{display:flex;justify-content:flex-end;gap:12px}
|
|
||||||
1
dist/assets/WaveTask.1d7b659a.js
vendored
1
dist/assets/WaveTask.1d7b659a.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/adminLayout.94cb81b4.css
vendored
Normal file
1
dist/assets/adminLayout.94cb81b4.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.layout[data-v-ab4faab8]{height:100%}.aside[data-v-ab4faab8]{background-color:#304156;transition:width .3s;overflow:hidden;display:flex;flex-direction:column}.logo[data-v-ab4faab8]{height:60px;line-height:60px;text-align:center;color:#fff;font-size:18px;font-weight:700;background-color:#1f2d3d;white-space:nowrap;overflow:hidden;flex-shrink:0}.logo-collapse[data-v-ab4faab8]{font-size:20px}.menu[data-v-ab4faab8]{border-right:none;flex:1;overflow-y:auto;overflow-x:hidden}.menu[data-v-ab4faab8]::-webkit-scrollbar{width:6px}.menu[data-v-ab4faab8]::-webkit-scrollbar-track{background:#304156}.menu[data-v-ab4faab8]::-webkit-scrollbar-thumb{background:#4a5568;border-radius:3px}.menu[data-v-ab4faab8]::-webkit-scrollbar-thumb:hover{background:#718096}.header[data-v-ab4faab8]{background-color:#fff;border-bottom:1px solid #e6e9f0;display:flex;align-items:center;justify-content:space-between;padding:0 20px}.header-left[data-v-ab4faab8]{display:flex;align-items:center}.collapse-icon[data-v-ab4faab8]{font-size:20px;cursor:pointer;color:#606266}.header-right[data-v-ab4faab8]{display:flex;align-items:center;gap:20px}.user-info[data-v-ab4faab8]{display:flex;align-items:center;gap:5px;cursor:pointer;color:#606266;font-size:14px;outline:none}.user-info[data-v-ab4faab8]:focus{outline:none}.points-badge[data-v-ab4faab8]{background-color:#f56c6c;color:#fff;padding:4px 8px;border-radius:4px;font-size:12px}.main[data-v-ab4faab8]{background-color:#f0f2f5;padding:10px}
|
||||||
1
dist/assets/adminLayout.b5059798.js
vendored
Normal file
1
dist/assets/adminLayout.b5059798.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
import{r as t}from"./request.3edc855e.js";const o=async r=>{const e=new FormData;return e.append("content",r),t.post("/barcode/generate",e)};export{o as g};
|
import{r as t}from"./request.92d2eb68.js";const o=async r=>{const e=new FormData;return e.append("content",r),t.post("/barcode/generate",e)};export{o as g};
|
||||||
@ -1 +1 @@
|
|||||||
import{r as a}from"./request.3edc855e.js";const r="/car",c=e=>{const t=e==null?void 0:e.data;return t?Array.isArray(t)?{list:t,total:t.length}:{list:Array.isArray(t.list)?t.list:[],total:typeof t.total=="number"?t.total:Array.isArray(t.list)?t.list.length:0}:{list:[],total:0}},l=async({keyword:e,page:t,pageSize:s})=>{const n={keyword:e||void 0,page:t,page_size:s},o=await a.get(`${r}/list`,{params:n});return c(o)},u=async e=>{const t=await a.get(`${r}/detail/${e}`);return(t==null?void 0:t.data)||null},d=async e=>a.post(`${r}/create`,e),p=async e=>a.put(`${r}/update`,e),y=async e=>{const t=new FormData;return t.append("id",String(e)),a.delete(`${r}/delete`,{data:t})},f=async e=>a.post(`${r}/shop/create`,e),h=async e=>a.post(`${r}/shop/delete`,e);export{u as a,h as b,d as c,y as d,f as e,l as f,p as u};
|
import{r as a}from"./request.92d2eb68.js";const r="/car",c=e=>{const t=e==null?void 0:e.data;return t?Array.isArray(t)?{list:t,total:t.length}:{list:Array.isArray(t.list)?t.list:[],total:typeof t.total=="number"?t.total:Array.isArray(t.list)?t.list.length:0}:{list:[],total:0}},l=async({keyword:e,page:t,pageSize:s})=>{const n={keyword:e||void 0,page:t,page_size:s},o=await a.get(`${r}/list`,{params:n});return c(o)},u=async e=>{const t=await a.get(`${r}/detail/${e}`);return(t==null?void 0:t.data)||null},d=async e=>a.post(`${r}/create`,e),p=async e=>a.put(`${r}/update`,e),y=async e=>{const t=new FormData;return t.append("id",String(e)),a.delete(`${r}/delete`,{data:t})},f=async e=>a.post(`${r}/shop/create`,e),h=async e=>a.post(`${r}/shop/delete`,e);export{u as a,h as b,d as c,y as d,f as e,l as f,p as u};
|
||||||
1
dist/assets/car.3e4d66eb.css
vendored
Normal file
1
dist/assets/car.3e4d66eb.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.car-manager[data-v-5ab815e0]{padding:0}.card-header[data-v-5ab815e0]{font-size:16px;font-weight:600;color:#303133}.section-card[data-v-5ab815e0]{margin-bottom:16px}.filter-bar[data-v-5ab815e0]{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;align-items:center}.pagination-wrapper[data-v-5ab815e0]{margin-top:20px;display:flex;justify-content:flex-end}[data-v-5ab815e0] .el-table{border-radius:8px;overflow:hidden}[data-v-5ab815e0] .el-dialog__body{padding-top:16px}.dialog-footer[data-v-5ab815e0]{display:flex;justify-content:flex-end;gap:12px}.expand-content[data-v-5ab815e0]{padding:16px 24px}.expand-header[data-v-5ab815e0]{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.expand-title[data-v-5ab815e0]{font-size:14px;font-weight:600;color:#303133}.secret-text[data-v-5ab815e0]{color:#909399;font-family:monospace;letter-spacing:2px}.label-with-icon[data-v-5ab815e0]{display:inline-flex;align-items:center;gap:4px}
|
||||||
1
dist/assets/car.51b036d4.js
vendored
Normal file
1
dist/assets/car.51b036d4.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/clipboard.10cdcf7f.js
vendored
1
dist/assets/clipboard.10cdcf7f.js
vendored
@ -1 +0,0 @@
|
|||||||
import{E as t}from"./index.a99ea9b6.js";const n=async(r,o="\u590D\u5236\u6210\u529F")=>{try{if(navigator.clipboard&&navigator.clipboard.writeText)return await navigator.clipboard.writeText(r),t.success(o),!0;const e=document.createElement("textarea");e.value=r,e.style.position="fixed",e.style.opacity="0",document.body.appendChild(e),e.select(),e.setSelectionRange(0,99999);const a=document.execCommand("copy");return document.body.removeChild(e),a?(t.success(o),!0):(t.error("\u590D\u5236\u5931\u8D25"),!1)}catch{return t.error("\u590D\u5236\u5931\u8D25"),!1}};export{n as c};
|
|
||||||
1
dist/assets/clipboard.3ccc9f0a.js
vendored
Normal file
1
dist/assets/clipboard.3ccc9f0a.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
import{E as s}from"./index.887c1a2d.js";const o=async(a,r="\u590D\u5236\u6210\u529F")=>{try{if(navigator.clipboard&&navigator.clipboard.writeText)return await navigator.clipboard.writeText(a),s.success({message:r,customClass:"scan-success-message"}),!0;const e=document.createElement("textarea");e.value=a,e.style.position="fixed",e.style.opacity="0",document.body.appendChild(e),e.select(),e.setSelectionRange(0,99999);const t=document.execCommand("copy");return document.body.removeChild(e),t?(s.success({message:r,customClass:"scan-success-message"}),!0):(s.error({message:"\u590D\u5236\u5931\u8D25",customClass:"scan-error-message"}),!1)}catch{return s.error({message:"\u590D\u5236\u5931\u8D25",customClass:"scan-error-message"}),!1}};export{o as c};
|
||||||
1
dist/assets/config.08364858.js
vendored
Normal file
1
dist/assets/config.08364858.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
import{a as i}from"./axios.e318b91e.js";const w=(t,n)=>{const e=new URLSearchParams;return e.append("isbn","0"),e.append("out_id","0"),e.append("quality","0"),e.append("query_index","1"),e.append("user_id","0"),i.post(`http://${t}:${n}/api/goods/query`,e.toString(),{headers:{"Content-Type":"application/x-www-form-urlencoded"},timeout:1e4}).then(p=>p.data)},$=(t,n,e,p,r,s,d)=>{const o=new URLSearchParams;return o.append("new_price",e),o.append("placeholder_down_price",p),o.append("min_shipping_fee",r),o.append("min_price",s),o.append("query_index",d),console.log(o.toString()),i.post(`http://${t}:${n}/api/config/price/set`,o.toString(),{headers:{"Content-Type":"application/x-www-form-urlencoded"},timeout:1e4}).then(c=>c.data)},y=(t,n,e,p)=>{const r=new FormData;return r.append("username",t),r.append("password",n),i.post(`http://${e}:${p}/api/kfz/login`,r,{timeout:15e3}).then(s=>s.data)},k=(t,n,e)=>i.post(`http://${n}:${e}/api/token/add`,t,{headers:{"Content-Type":"application/json"},timeout:1e4}).then(p=>p.data),S=(t,n)=>i.post(`http://${t}:${n}/api/token/list`,{},{headers:{"Content-Type":"application/json"},timeout:1e4}).then(e=>e.data),q=(t,n)=>i.post(`http://${t}:${n}/api/config/price/get`,{},{headers:{"Content-Type":"application/json"},timeout:1e4}).then(e=>e.data),T=({ip:t,port:n,isbn:e,bookName:p,author:r,publisher:s,isSuit:d,outId:o,quality:c,queryIndex:g,userId:l,placeholderDownPrice:u,minShippingFee:h,minPrice:m})=>{const a=new URLSearchParams;return d?(a.append("book_name",p||""),a.append("author",r||""),a.append("publishing",s||"")):a.append("isbn",e),a.append("out_id",String(o)),a.append("quality",String(c)),a.append("query_index",String(g)),a.append("user_id",String(l)),u&&a.append("placeholder_down_price",u),h&&a.append("min_shipping_fee",h),m&&a.append("min_price",m),i.post(`http://${t}:${n}/api/goods/query`,a.toString(),{headers:{"Content-Type":"application/x-www-form-urlencoded"},timeout:1e4}).then(f=>f.data)},x=(t,n,e)=>i.get(`http://${t}:${n}/api/token/delete`,{params:{id:e}}).then(p=>p.data);export{q as a,k as b,x as d,S as f,y as k,T as q,$ as s,w as t};
|
||||||
1
dist/assets/config.53d6eb34.css
vendored
Normal file
1
dist/assets/config.53d6eb34.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
@charset "UTF-8";.config-page[data-v-9008af20]{padding:20px;max-width:860px;margin:0 auto}.config-card[data-v-9008af20]{border-radius:12px}.card-header[data-v-9008af20]{display:flex;align-items:center;justify-content:space-between}.header-left[data-v-9008af20]{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.config-row[data-v-9008af20]{display:flex;gap:16px}.config-row .flex-item[data-v-9008af20]{flex:1;min-width:0}.save-bar[data-v-9008af20]{display:flex}.result-box[data-v-9008af20]{margin-top:16px}.account-section .section-title[data-v-9008af20]{display:flex;align-items:center;gap:8px;font-size:15px;font-weight:600;color:#303133;margin-bottom:16px}.account-section .count-tag[data-v-9008af20]{margin-left:0}.account-form .account-row[data-v-9008af20]{margin-bottom:16px}.account-form .account-row .account-label[data-v-9008af20]{font-size:14px;color:#606266;margin-bottom:8px;font-weight:500}.account-form .account-row .account-inputs[data-v-9008af20]{display:flex;gap:8px;align-items:center}.add-btn[data-v-9008af20]{margin-top:4px}.bind-bar[data-v-9008af20],.append-bar[data-v-9008af20]{margin-top:16px}.append-form-actions[data-v-9008af20]{display:flex;gap:8px;margin-top:8px}.token-text[data-v-9008af20]{font-family:Courier New,monospace;font-size:12px}.fade-enter-active[data-v-9008af20],.fade-leave-active[data-v-9008af20]{transition:opacity .3s ease}.fade-enter-from[data-v-9008af20],.fade-leave-to[data-v-9008af20]{opacity:0}@media (max-width: 640px){.config-row[data-v-9008af20]{flex-direction:column;gap:0}.account-inputs[data-v-9008af20]{flex-wrap:wrap}}.label-with-icon[data-v-9008af20]{display:inline-flex;align-items:center;gap:4px}
|
||||||
1
dist/assets/config.648ff82b.js
vendored
1
dist/assets/config.648ff82b.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/config.6d1e7ea1.js
vendored
Normal file
1
dist/assets/config.6d1e7ea1.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/config.726d1f52.css
vendored
1
dist/assets/config.726d1f52.css
vendored
@ -1 +0,0 @@
|
|||||||
@charset "UTF-8";.config-page[data-v-8f6b30a0]{padding:20px;max-width:860px;margin:0 auto}.config-card[data-v-8f6b30a0]{border-radius:12px}.card-header[data-v-8f6b30a0]{display:flex;align-items:center;justify-content:space-between}.header-left[data-v-8f6b30a0]{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.config-row[data-v-8f6b30a0]{display:flex;gap:16px}.config-row .flex-item[data-v-8f6b30a0]{flex:1;min-width:0}.save-bar[data-v-8f6b30a0]{display:flex}.result-box[data-v-8f6b30a0]{margin-top:16px}.account-section .section-title[data-v-8f6b30a0]{display:flex;align-items:center;gap:8px;font-size:15px;font-weight:600;color:#303133;margin-bottom:16px}.account-section .count-tag[data-v-8f6b30a0]{margin-left:0}.account-form .account-row[data-v-8f6b30a0]{margin-bottom:16px}.account-form .account-row .account-label[data-v-8f6b30a0]{font-size:14px;color:#606266;margin-bottom:8px;font-weight:500}.account-form .account-row .account-inputs[data-v-8f6b30a0]{display:flex;gap:8px;align-items:center}.add-btn[data-v-8f6b30a0]{margin-top:4px}.bind-bar[data-v-8f6b30a0],.append-bar[data-v-8f6b30a0]{margin-top:16px}.append-form-actions[data-v-8f6b30a0]{display:flex;gap:8px;margin-top:8px}.token-text[data-v-8f6b30a0]{font-family:Courier New,monospace;font-size:12px}.fade-enter-active[data-v-8f6b30a0],.fade-leave-active[data-v-8f6b30a0]{transition:opacity .3s ease}.fade-enter-from[data-v-8f6b30a0],.fade-leave-to[data-v-8f6b30a0]{opacity:0}@media (max-width: 640px){.config-row[data-v-8f6b30a0]{flex-direction:column;gap:0}.account-inputs[data-v-8f6b30a0]{flex-wrap:wrap}}.label-with-icon[data-v-8f6b30a0]{display:inline-flex;align-items:center;gap:4px}
|
|
||||||
1
dist/assets/config.af7eafe9.css
vendored
Normal file
1
dist/assets/config.af7eafe9.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
@charset "UTF-8";.courier-config-page[data-v-c846cf04]{padding:20px;max-width:1000px;margin:0 auto}.config-card[data-v-c846cf04]{border-radius:12px}.card-header[data-v-c846cf04]{display:flex;align-items:center;justify-content:space-between}.header-left[data-v-c846cf04]{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.config-section[data-v-c846cf04]{margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid #ebeef5}.config-section[data-v-c846cf04]:last-of-type{border-bottom:none;margin-bottom:16px}.section-title[data-v-c846cf04]{display:flex;align-items:center;gap:8px;font-size:15px;font-weight:600;color:#303133;margin-bottom:16px}.config-row[data-v-c846cf04]{display:flex;gap:16px;margin-bottom:8px}.config-row .flex-item[data-v-c846cf04],.config-row .flex-item-full[data-v-c846cf04]{flex:1;min-width:0}.switch-tip[data-v-c846cf04]{margin-left:12px;font-size:12px;color:#909399}.save-bar[data-v-c846cf04]{display:flex;gap:12px;margin-top:24px;padding-top:16px;border-top:1px solid #ebeef5}@media (max-width: 768px){.config-row[data-v-c846cf04]{flex-direction:column;gap:0}}
|
||||||
1
dist/assets/config.f83b38c3.js
vendored
Normal file
1
dist/assets/config.f83b38c3.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/dashboard.84519081.css
vendored
Normal file
1
dist/assets/dashboard.84519081.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.dashboard[data-v-4b658a84]{padding:20px;background:#f5f7fa;min-height:calc(100vh - 120px)}.stats-row[data-v-4b658a84]{margin-bottom:20px}.stat-card[data-v-4b658a84]{border-radius:12px;transition:all .3s ease;border:none}.stat-card[data-v-4b658a84]:hover{transform:translateY(-4px);box-shadow:0 12px 24px #0000001a}.stat-card[data-v-4b658a84] .el-card__body{padding:20px}.stat-content[data-v-4b658a84]{display:flex;align-items:center}.stat-icon[data-v-4b658a84]{width:64px;height:64px;border-radius:16px;display:flex;align-items:center;justify-content:center;color:#fff;margin-right:20px;flex-shrink:0}.stat-info[data-v-4b658a84]{flex:1}.stat-title[data-v-4b658a84]{font-size:14px;color:#909399;margin-bottom:8px}.stat-value[data-v-4b658a84]{font-size:32px;font-weight:700;color:#303133;line-height:1.2;margin-bottom:4px}.stat-desc[data-v-4b658a84]{font-size:12px;color:#909399}.mt-20[data-v-4b658a84]{margin-top:20px}.table-card[data-v-4b658a84]{border-radius:12px;border:none}.table-card[data-v-4b658a84] .el-card__header{padding:16px 20px;border-bottom:1px solid #ebeef5}.table-card[data-v-4b658a84] .el-card__body{padding:0}.card-header[data-v-4b658a84]{display:flex;justify-content:space-between;align-items:center}.card-title[data-v-4b658a84]{font-size:16px;font-weight:600;color:#303133}.product-name[data-v-4b658a84]{color:#606266;font-weight:500}.price[data-v-4b658a84]{color:#f56c6c;font-weight:600}.total-count[data-v-4b658a84]{color:#409eff;font-weight:600;font-size:15px}[data-v-4b658a84] .el-table .el-table__header th{background:#fafafa;color:#606266;font-weight:600}[data-v-4b658a84] .el-table .el-table__body td{padding:12px 0}
|
||||||
1
dist/assets/dashboard.fd970744.js
vendored
Normal file
1
dist/assets/dashboard.fd970744.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/employee.896b22b2.js
vendored
Normal file
1
dist/assets/employee.896b22b2.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
import{r as t}from"./request.92d2eb68.js";const p=async e=>t.get("/admin/employee/list",{params:e}),a=async e=>t.post("/admin/employee/add",e),n=async(e,o)=>t.post(`/admin/employee/topup/${e}`,o),m=async(e,o)=>t.post(`/admin/employee/deduct/${e}`,o);export{a,m as d,p as f,n as t};
|
||||||
1
dist/assets/employeeAdd.45227868.css
vendored
Normal file
1
dist/assets/employeeAdd.45227868.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.employee-add[data-v-6f7a1bc9]{padding:0}.card-header[data-v-6f7a1bc9]{display:flex;justify-content:space-between;align-items:center;font-size:16px;font-weight:600;color:#303133}.steps[data-v-6f7a1bc9]{margin:20px 0 40px}.step-content[data-v-6f7a1bc9]{min-height:300px;padding:20px 0}.add-form[data-v-6f7a1bc9]{width:500px;margin:0 auto}.preview-card[data-v-6f7a1bc9]{width:500px;margin:30px auto 0;background-color:#f8f9fa}.preview-content[data-v-6f7a1bc9]{padding:10px}.preview-item[data-v-6f7a1bc9]{margin-bottom:10px;display:flex;align-items:center}.preview-item .label[data-v-6f7a1bc9]{width:80px;color:#666;font-size:13px}.preview-item .value[data-v-6f7a1bc9]{color:#333;font-size:13px}.confirm-info[data-v-6f7a1bc9]{width:500px;margin:0 auto}.step-actions[data-v-6f7a1bc9]{margin-top:30px;text-align:center}.result[data-v-6f7a1bc9]{display:flex;justify-content:center}.result-info[data-v-6f7a1bc9]{margin:20px 0;text-align:left}.result-actions[data-v-6f7a1bc9]{margin-top:20px;display:flex;gap:10px;justify-content:center}[data-v-6f7a1bc9] .el-descriptions__label{width:120px}.title-center[data-v-6f7a1bc9]{text-align:center;font-weight:700;font-size:16px;margin-bottom:10px}
|
||||||
8
dist/assets/employeeAdd.53d2a71c.js
vendored
Normal file
8
dist/assets/employeeAdd.53d2a71c.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/employeeList.672857e3.css
vendored
Normal file
1
dist/assets/employeeList.672857e3.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.employee-list[data-v-43c4e009]{padding:0}.card-header[data-v-43c4e009]{display:flex;justify-content:space-between;align-items:center;font-size:16px;font-weight:600;color:#303133}.search-form[data-v-43c4e009]{margin-bottom:20px;padding:20px;background-color:#f8f9fa;border-radius:4px}.stat-cards[data-v-43c4e009]{margin-bottom:20px}.stat-card[data-v-43c4e009]{text-align:center}.stat-item[data-v-43c4e009]{padding:10px}.stat-label[data-v-43c4e009]{font-size:14px;color:#909399;margin-bottom:8px}.stat-value[data-v-43c4e009]{font-size:24px;font-weight:700;color:#303133}.stat-value.success[data-v-43c4e009]{color:#67c23a}.stat-value.warning[data-v-43c4e009]{color:#e6a23c}.stat-value.info[data-v-43c4e009]{color:#409eff}.pagination[data-v-43c4e009]{margin-top:20px;display:flex;justify-content:flex-end}.points-warning[data-v-43c4e009]{color:#f56c6c;font-weight:700}.deduct-input[data-v-43c4e009] .el-input-number__decrease:hover,.deduct-input[data-v-43c4e009] .el-input-number__increase:hover{color:#fff;background-color:#f56c6c}.deduct-label[data-v-43c4e009] .el-form-item__label{color:#f56c6c;font-weight:700}
|
||||||
1
dist/assets/employeeList.70840dca.js
vendored
Normal file
1
dist/assets/employeeList.70840dca.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/employeeType.4e19d2fa.js
vendored
1
dist/assets/employeeType.4e19d2fa.js
vendored
@ -1 +0,0 @@
|
|||||||
import{r as h,Z as E,b as s,$ as L,o as r,c as _,e as a,w as n,a0 as z,y as f,h as y,d as D,B as M,E as g,C as N}from"./index.a99ea9b6.js";import{r as $}from"./request.3edc855e.js";import"./axios.e318b91e.js";const q=c=>{const t=c==null?void 0:c.data;return t?Array.isArray(t)?{list:t,total:t.length}:{list:Array.isArray(t.list)?t.list:[],total:typeof t.total=="number"?t.total:Array.isArray(t.list)?t.list.length:0}:{list:[],total:0}},R=async()=>{const c=await $.get("/admin/user-type/list");return q(c)},S={key:1},Z={key:1},H={__name:"employeeType",setup(c){const t=h(!1),u=h([]),v=async()=>{t.value=!0;try{const o=await R();u.value=(o.list||[]).map(l=>({...l,_editing:!1}))}catch(o){console.error("\u83B7\u53D6\u5458\u5DE5\u7C7B\u578B\u5217\u8868\u5931\u8D25:",o)}finally{t.value=!1}},k=()=>{u.value.unshift({id:null,name:"",icon:"",check_status:!1,check_num:0,_editing:!0})},b=async o=>{o._editing=!1,o._backup=null,g.success("\u4FDD\u5B58\u6210\u529F")},V=(o,l)=>{if(!o.id){u.value.splice(l,1);return}N.confirm("\u786E\u8BA4\u5220\u9664\u8BE5\u5458\u5DE5\u7C7B\u578B\u5417\uFF1F","\u63D0\u793A",{type:"warning"}).then(()=>{u.value.splice(l,1),g.success("\u5220\u9664\u6210\u529F")}).catch(()=>{})};return E(()=>{v()}),(o,l)=>{const d=s("el-button"),w=s("el-image"),p=s("el-table-column"),C=s("el-input"),x=s("el-switch"),m=s("el-option"),T=s("el-select"),A=s("el-table"),B=L("loading");return r(),_("div",null,[a(d,{type:"primary",onClick:k,style:{"margin-bottom":"10px"}},{default:n(()=>[...l[0]||(l[0]=[y("\u65B0\u589E\u7C7B\u578B",-1)])]),_:1}),z((r(),f(A,{data:u.value,border:"",style:{width:"100%"}},{default:n(()=>[a(p,{prop:"icon",label:"\u56FE\u6807",width:"150",align:"center"},{default:n(({row:e})=>[e.icon?(r(),f(w,{key:0,src:e.icon,style:{width:"40px",height:"40px"}},{error:n(()=>[...l[1]||(l[1]=[D("span",null,"\u6682\u65E0\u56FE\u6807",-1)])]),_:1},8,["src"])):(r(),_("span",S,"\u6682\u65E0\u56FE\u6807"))]),_:1}),a(p,{prop:"name",label:"\u7C7B\u540D",width:"140",align:"center"},{default:n(({row:e})=>[e._editing?(r(),f(C,{key:0,modelValue:e.name,"onUpdate:modelValue":i=>e.name=i,placeholder:"\u8BF7\u8F93\u5165\u540D\u79F0"},null,8,["modelValue","onUpdate:modelValue"])):(r(),_("span",Z,M(e.name),1))]),_:1}),a(p,{prop:"check_status",label:"\u5F00\u542F\u8FC7\u671F\u65F6\u95F4\u6821\u9A8C","min-width":"160","show-overflow-tooltip":"",align:"center"},{default:n(({row:e})=>[a(x,{modelValue:e.check_status,"onUpdate:modelValue":i=>e.check_status=i,"active-color":"#13ce66","inactive-color":"#ff4949"},null,8,["modelValue","onUpdate:modelValue"])]),_:1}),a(p,{prop:"check_num",label:"\u673A\u68B0\u7801\u6821\u9A8C\u7B49\u7EA7","min-width":"200","show-overflow-tooltip":"",align:"center"},{default:n(({row:e})=>[a(T,{modelValue:e.check_num,"onUpdate:modelValue":i=>e.check_num=i,placeholder:"\u8BF7\u9009\u62E9\u673A\u68B0\u7801\u6821\u9A8C\u7B49\u7EA7"},{default:n(()=>[a(m,{label:"\u4E0D\u9650\u5236",value:0}),a(m,{label:"\u5355\u8BBE\u5907",value:1}),a(m,{label:"\u9501\u5B9A\u8BBE\u5907",value:2})]),_:1},8,["modelValue","onUpdate:modelValue"])]),_:1}),a(p,{label:"\u64CD\u4F5C",width:"200",align:"center"},{default:n(({row:e,$index:i})=>[a(d,{type:"success",size:"small",onClick:U=>b(e)},{default:n(()=>[...l[2]||(l[2]=[y("\u4FDD\u5B58",-1)])]),_:1},8,["onClick"]),a(d,{type:"danger",size:"small",onClick:U=>V(e,i)},{default:n(()=>[...l[3]||(l[3]=[y("\u5220\u9664",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"])),[[B,t.value]])])}}};export{H as default};
|
|
||||||
1
dist/assets/employeeType.c6cdd547.js
vendored
Normal file
1
dist/assets/employeeType.c6cdd547.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
import{r as g,$ as E,b as o,a0 as L,o as r,c as _,e as a,w as s,a1 as z,y as f,h as y,d as D,B as M,E as h,C as N}from"./index.887c1a2d.js";import{r as $}from"./request.92d2eb68.js";import"./axios.e318b91e.js";const q=i=>{const t=i==null?void 0:i.data;return t?Array.isArray(t)?{list:t,total:t.length}:{list:Array.isArray(t.list)?t.list:[],total:typeof t.total=="number"?t.total:Array.isArray(t.list)?t.list.length:0}:{list:[],total:0}},R=async()=>{const i=await $.get("/admin/user-type/list");return q(i)},S={key:1},j={key:1},I={__name:"employeeType",setup(i){const t=g(!1),u=g([]),v=async()=>{t.value=!0;try{const n=await R();u.value=(n.list||[]).map(l=>({...l,_editing:!1}))}catch(n){console.error("\u83B7\u53D6\u5458\u5DE5\u7C7B\u578B\u5217\u8868\u5931\u8D25:",n)}finally{t.value=!1}},k=()=>{u.value.unshift({id:null,name:"",icon:"",check_status:!1,check_num:0,_editing:!0})},b=async n=>{n._editing=!1,n._backup=null,h.success({message:"\u4FDD\u5B58\u6210\u529F",customClass:"scan-success-message"})},V=(n,l)=>{if(!n.id){u.value.splice(l,1);return}N.confirm("\u786E\u8BA4\u5220\u9664\u8BE5\u5458\u5DE5\u7C7B\u578B\u5417\uFF1F","\u63D0\u793A",{type:"warning"}).then(()=>{u.value.splice(l,1),h.success({message:"\u5220\u9664\u6210\u529F",customClass:"scan-success-message"})}).catch(()=>{})};return E(()=>{v()}),(n,l)=>{const p=o("el-button"),w=o("el-image"),m=o("el-table-column"),C=o("el-input"),x=o("el-switch"),d=o("el-option"),T=o("el-select"),A=o("el-table"),B=L("loading");return r(),_("div",null,[a(p,{type:"primary",onClick:k,style:{"margin-bottom":"10px"}},{default:s(()=>[...l[0]||(l[0]=[y("\u65B0\u589E\u7C7B\u578B",-1)])]),_:1}),z((r(),f(A,{data:u.value,border:"",style:{width:"100%"}},{default:s(()=>[a(m,{prop:"icon",label:"\u56FE\u6807",width:"150",align:"center"},{default:s(({row:e})=>[e.icon?(r(),f(w,{key:0,src:e.icon,style:{width:"40px",height:"40px"}},{error:s(()=>[...l[1]||(l[1]=[D("span",null,"\u6682\u65E0\u56FE\u6807",-1)])]),_:1},8,["src"])):(r(),_("span",S,"\u6682\u65E0\u56FE\u6807"))]),_:1}),a(m,{prop:"name",label:"\u7C7B\u540D",width:"140",align:"center"},{default:s(({row:e})=>[e._editing?(r(),f(C,{key:0,modelValue:e.name,"onUpdate:modelValue":c=>e.name=c,placeholder:"\u8BF7\u8F93\u5165\u540D\u79F0"},null,8,["modelValue","onUpdate:modelValue"])):(r(),_("span",j,M(e.name),1))]),_:1}),a(m,{prop:"check_status",label:"\u5F00\u542F\u8FC7\u671F\u65F6\u95F4\u6821\u9A8C","min-width":"160","show-overflow-tooltip":"",align:"center"},{default:s(({row:e})=>[a(x,{modelValue:e.check_status,"onUpdate:modelValue":c=>e.check_status=c,"active-color":"#13ce66","inactive-color":"#ff4949"},null,8,["modelValue","onUpdate:modelValue"])]),_:1}),a(m,{prop:"check_num",label:"\u673A\u68B0\u7801\u6821\u9A8C\u7B49\u7EA7","min-width":"200","show-overflow-tooltip":"",align:"center"},{default:s(({row:e})=>[a(T,{modelValue:e.check_num,"onUpdate:modelValue":c=>e.check_num=c,placeholder:"\u8BF7\u9009\u62E9\u673A\u68B0\u7801\u6821\u9A8C\u7B49\u7EA7"},{default:s(()=>[a(d,{label:"\u4E0D\u9650\u5236",value:0}),a(d,{label:"\u5355\u8BBE\u5907",value:1}),a(d,{label:"\u9501\u5B9A\u8BBE\u5907",value:2})]),_:1},8,["modelValue","onUpdate:modelValue"])]),_:1}),a(m,{label:"\u64CD\u4F5C",width:"200",align:"center"},{default:s(({row:e,$index:c})=>[a(p,{type:"success",size:"small",onClick:U=>b(e)},{default:s(()=>[...l[2]||(l[2]=[y("\u4FDD\u5B58",-1)])]),_:1},8,["onClick"]),a(p,{type:"danger",size:"small",onClick:U=>V(e,c)},{default:s(()=>[...l[3]||(l[3]=[y("\u5220\u9664",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"])),[[B,t.value]])])}}};export{I as default};
|
||||||
1
dist/assets/index.03f032f8.js
vendored
1
dist/assets/index.03f032f8.js
vendored
@ -1 +0,0 @@
|
|||||||
import{a7 as C,r as f,b as F,o as i,y as D,w as c,d as o,av as N,z as S,c as l,e as r,f as b,B as a,A as x,_ as $,am as P,aw as z,ax as I}from"./index.a99ea9b6.js";import{r as V}from"./request.3edc855e.js";const G={key:0,class:"popover-loading"},H={key:1,class:"popover-error"},M={key:2,class:"popover-content"},q={class:"popover-header"},T={class:"popover-isbn"},Y={key:0,class:"suit-badge"},j={class:"popover-body"},J={class:"popover-cover"},K=["src"],L={key:1,class:"cover-placeholder"},O={class:"popover-info"},Q={class:"info-row"},R={class:"info-value info-value-name"},U={class:"info-row"},W={class:"info-value"},X={class:"info-row"},Z={class:"info-value"},ss={class:"info-row"},os={class:"info-value"},es={class:"info-row"},ns={class:"info-value"},ts={class:"info-row"},as={class:"info-value info-value-price"},us={class:"info-row"},is={class:"info-value"},ls={class:"info-row"},rs={class:"info-value"},ds={key:3,class:"popover-empty"},cs=C({__name:"index",props:{isbn:{}},setup(_){const g=_,p=f(!1),d=f(null),n=f(null),v=new Map;function w(t){if(t==null||t==="")return"";const s=String(t);if(/^\d{4}-\d{2}-\d{2}$/.test(s))return s;if(/^\d{8}$/.test(s))return`${s.slice(0,4)}-${s.slice(4,6)}-${s.slice(6,8)}`;if(/^\d{4}-\d{2}$/.test(s))return`${s}-01`;const e=Number(t);if(!isNaN(e)&&e>1e4){const u=new Date(e*1e3);if(!isNaN(u.getTime()))return`${u.getFullYear()}-${String(u.getMonth()+1).padStart(2,"0")}-${String(u.getDate()).padStart(2,"0")}`}return s}function A(t){return t==null?"0.00":(t/100).toFixed(2)}async function k(t){if(v.has(t)){n.value=v.get(t);return}p.value=!0,d.value=null,n.value=null;try{const s=await V.get("/getBookInfo",{params:{isbn:t}}),e=s==null?void 0:s.data;if(!e){d.value="\u6570\u636E\u5E93\u4E2D\u6682\u65E0\u8BE5\u4E66\u6570\u636E";return}const u={bookName:e.book_name||"",author:e.author||"",publisher:e.publisher||"",publishDate:w(e.publication_time),binding:e.binding_layout||"",price:typeof e.fix_price=="number"?e.fix_price:0,pageCount:Number(e.page_count)||0,wordCount:Number(e.word_count)||0,book_pic:e.book_pic||void 0,isSuit:e.is_suit===1};v.set(t,u),n.value=u}catch(s){console.warn("[goodsPop] \u4E66\u7C4D\u4FE1\u606F\u67E5\u8BE2\u5931\u8D25:",s instanceof Error?s.message:String(s)),d.value="\u67E5\u8BE2\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"}finally{p.value=!1}}function y(){!g.isbn||k(g.isbn)}function B(){}return(t,s)=>{const e=F("el-icon"),u=F("el-popover");return i(),D(u,{placement:"right-start",width:360,trigger:"hover","open-delay":500,"close-delay":100,disabled:!_.isbn,onShow:y,onHide:B},{reference:c(()=>[o("span",{class:S(["isbn-popover-trigger",{"is-loading":p.value}])},[N(t.$slots,"default",{},void 0,!0)],2)]),default:c(()=>{var h,m,E;return[p.value?(i(),l("div",G,[r(e,{class:"is-loading",size:24},{default:c(()=>[r(b(P))]),_:1}),s[0]||(s[0]=o("span",null,"\u6B63\u5728\u67E5\u8BE2\u4E66\u54C1\u4FE1\u606F...",-1))])):d.value?(i(),l("div",H,[r(e,{size:24,color:"#e6a23c"},{default:c(()=>[r(b(z))]),_:1}),o("span",null,a(d.value),1)])):n.value?(i(),l("div",M,[o("div",q,[o("span",T,a(_.isbn),1),n.value.isSuit?(i(),l("span",Y,"\u5957\u88C5\u4E66")):x("",!0)]),o("div",j,[o("div",J,[(h=n.value.book_pic)!=null&&h.pddPath?(i(),l("img",{key:0,src:n.value.book_pic.pddPath,alt:"\u5C01\u9762",class:"cover-image"},null,8,K)):(i(),l("div",L,[r(e,{size:32},{default:c(()=>[r(b(I))]),_:1})]))]),o("div",O,[o("div",Q,[s[1]||(s[1]=o("span",{class:"info-label"},"\u4E66\u540D\uFF1A",-1)),o("span",R,a(n.value.bookName||"\u672A\u77E5"),1)]),o("div",U,[s[2]||(s[2]=o("span",{class:"info-label"},"\u4F5C\u8005\uFF1A",-1)),o("span",W,a(n.value.author||"\u672A\u77E5"),1)]),o("div",X,[s[3]||(s[3]=o("span",{class:"info-label"},"\u51FA\u7248\u793E\uFF1A",-1)),o("span",Z,a(n.value.publisher||"\u672A\u77E5"),1)]),o("div",ss,[s[4]||(s[4]=o("span",{class:"info-label"},"\u51FA\u7248\u65F6\u95F4\uFF1A",-1)),o("span",os,a(n.value.publishDate||"\u672A\u77E5"),1)]),o("div",es,[s[5]||(s[5]=o("span",{class:"info-label"},"\u88C5\u5E27\uFF1A",-1)),o("span",ns,a(n.value.binding||"\u672A\u77E5"),1)]),o("div",ts,[s[6]||(s[6]=o("span",{class:"info-label"},"\u5B9A\u4EF7\uFF1A",-1)),o("span",as,"\xA5"+a(A(n.value.price)),1)]),o("div",us,[s[7]||(s[7]=o("span",{class:"info-label"},"\u9875\u6570\uFF1A",-1)),o("span",is,a((m=n.value.pageCount)!=null?m:"\u672A\u77E5"),1)]),o("div",ls,[s[8]||(s[8]=o("span",{class:"info-label"},"\u5B57\u6570\uFF1A",-1)),o("span",rs,a((E=n.value.wordCount)!=null?E:"\u672A\u77E5"),1)])])])])):(i(),l("div",ds,[...s[9]||(s[9]=[o("span",null,"\u6682\u65E0ISBN",-1)])]))]}),_:3},8,["disabled"])}}});var vs=$(cs,[["__scopeId","data-v-3c0e5793"]]);export{vs as G};
|
|
||||||
1
dist/assets/index.091007d0.css
vendored
1
dist/assets/index.091007d0.css
vendored
@ -1 +0,0 @@
|
|||||||
.logistics-manager[data-v-8c666032]{padding:16px}.filter-bar[data-v-8c666032]{display:flex;align-items:center;gap:12px;margin-bottom:16px;flex-wrap:wrap}.pagination-wrapper[data-v-8c666032]{display:flex;justify-content:flex-end;margin-top:16px}
|
|
||||||
1
dist/assets/index.1a73854d.js
vendored
1
dist/assets/index.1a73854d.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index.2d1755a2.css
vendored
1
dist/assets/index.2d1755a2.css
vendored
@ -1 +0,0 @@
|
|||||||
.isbn-popover-trigger[data-v-3c0e5793]{cursor:pointer;border-bottom:1px dashed #409eff;transition:all .2s}.isbn-popover-trigger[data-v-3c0e5793]:hover{color:#409eff}.isbn-popover-trigger.is-loading[data-v-3c0e5793]{opacity:.7}.popover-loading[data-v-3c0e5793]{display:flex;align-items:center;gap:8px;padding:20px;justify-content:center;color:#909399;font-size:13px}.popover-loading .is-loading[data-v-3c0e5793]{animation:rotating-3c0e5793 1.5s linear infinite}@keyframes rotating-3c0e5793{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.popover-error[data-v-3c0e5793]{display:flex;align-items:center;gap:8px;padding:20px;justify-content:center;color:#e6a23c;font-size:13px}.popover-empty[data-v-3c0e5793]{padding:20px;text-align:center;color:#c0c4cc;font-size:13px}.popover-content[data-v-3c0e5793]{padding:4px}.popover-header[data-v-3c0e5793]{display:flex;align-items:center;gap:8px;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid #ebeef5}.popover-isbn[data-v-3c0e5793]{font-size:13px;font-weight:600;color:#303133;font-family:Courier New,monospace}.suit-badge[data-v-3c0e5793]{display:inline-block;padding:1px 8px;font-size:11px;color:#e6a23c;background:#fdf6ec;border:1px solid #f5dab1;border-radius:4px;line-height:1.6}.popover-body[data-v-3c0e5793]{display:flex;gap:14px}.popover-cover[data-v-3c0e5793]{flex-shrink:0;width:90px;height:120px;border-radius:4px;overflow:hidden;border:1px solid #ebeef5}.cover-image[data-v-3c0e5793]{width:100%;height:100%;object-fit:cover}.cover-placeholder[data-v-3c0e5793]{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#f5f7fa;color:#c0c4cc}.popover-info[data-v-3c0e5793]{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.info-row[data-v-3c0e5793]{display:flex;font-size:12px;line-height:1.6}.info-label[data-v-3c0e5793]{flex-shrink:0;color:#909399;width:56px;text-align:right;margin-right:4px}.info-value[data-v-3c0e5793]{flex:1;color:#303133;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.info-value-name[data-v-3c0e5793]{font-weight:600;color:#409eff}.info-value-price[data-v-3c0e5793]{color:#f56c6c;font-weight:600}
|
|
||||||
File diff suppressed because one or more lines are too long
1
dist/assets/index.902e5a28.js
vendored
Normal file
1
dist/assets/index.902e5a28.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/index.fdfed103.css
vendored
Normal file
1
dist/assets/index.fdfed103.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.isbn-popover-trigger[data-v-0c73fea4]{cursor:pointer;border-bottom:1px dashed #409eff;transition:all .2s}.isbn-popover-trigger[data-v-0c73fea4]:hover{color:#409eff}.isbn-popover-trigger.is-loading[data-v-0c73fea4]{opacity:.7}.popover-loading[data-v-0c73fea4]{display:flex;align-items:center;gap:8px;padding:20px;justify-content:center;color:#909399;font-size:13px}.popover-loading .is-loading[data-v-0c73fea4]{animation:rotating-0c73fea4 1.5s linear infinite}@keyframes rotating-0c73fea4{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.popover-error[data-v-0c73fea4]{display:flex;align-items:center;gap:8px;padding:20px;justify-content:center;color:#e6a23c;font-size:13px}.popover-empty[data-v-0c73fea4]{padding:20px;text-align:center;color:#c0c4cc;font-size:13px}.popover-content[data-v-0c73fea4]{padding:4px}.popover-header[data-v-0c73fea4]{display:flex;align-items:center;gap:8px;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid #ebeef5}.popover-isbn[data-v-0c73fea4]{font-size:13px;font-weight:600;color:#303133;font-family:Courier New,monospace}.suit-badge[data-v-0c73fea4]{display:inline-block;padding:1px 8px;font-size:11px;color:#e6a23c;background:#fdf6ec;border:1px solid #f5dab1;border-radius:4px;line-height:1.6}.popover-body[data-v-0c73fea4]{display:flex;gap:14px}.popover-cover[data-v-0c73fea4]{flex-shrink:0;width:90px;height:120px;border-radius:4px;overflow:hidden;border:1px solid #ebeef5}.cover-image[data-v-0c73fea4]{width:100%;height:100%;object-fit:cover}.cover-placeholder[data-v-0c73fea4]{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#f5f7fa;color:#c0c4cc}.popover-info[data-v-0c73fea4]{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.info-row[data-v-0c73fea4]{display:flex;font-size:12px;line-height:1.6}.info-label[data-v-0c73fea4]{flex-shrink:0;color:#909399;width:56px;text-align:right;margin-right:4px}.info-value[data-v-0c73fea4]{flex:1;color:#303133;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.info-value-name[data-v-0c73fea4]{font-weight:600;color:#409eff}.info-value-price[data-v-0c73fea4]{color:#f56c6c;font-weight:600}
|
||||||
1
dist/assets/inventory.4310c6c2.css
vendored
Normal file
1
dist/assets/inventory.4310c6c2.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/inventory.46efe4da.js
vendored
Normal file
1
dist/assets/inventory.46efe4da.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
import{r as e}from"./request.92d2eb68.js";const n="/inventory",a=r=>{const t=r==null?void 0:r.data;return t?Array.isArray(t)?{list:t,total:t.length}:Array.isArray(t.list)?{list:t.list,total:typeof t.total=="number"?t.total:t.list.length}:typeof t=="object"?{list:[t],total:1}:{list:[],total:0}:{list:[],total:0}},c=async r=>{const t=await e.get(`${n}/list`,{params:r});return a(t)},y=async r=>{const t=await e.get(`${n}/detail`,{params:r}),s=t==null?void 0:t.data;return s?Array.isArray(s)?{list:s,total:s.length}:Array.isArray(s.list)?{list:s.list,total:typeof s.total=="number"?s.total:s.list.length}:{list:[],total:0}:{list:[],total:0}},u=async r=>{const t=await e.get(`${n}/log/list`,{params:r});return a(t)},f=async({keyword:r,page:t,pageSize:s}={})=>{const i={keyword:r||void 0,page:t,page_size:s},o=await e.get(`${n}/grouped-list`,{params:i});return a(o)},g=async()=>await e.get(`${n}/summary`);export{y as a,f as b,u as c,c as f,g as i};
|
||||||
1
dist/assets/inventory.57d7e7aa.js
vendored
Normal file
1
dist/assets/inventory.57d7e7aa.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/location.5e20e62a.js
vendored
Normal file
1
dist/assets/location.5e20e62a.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
import{r as n}from"./request.92d2eb68.js";const r="/location",c=t=>{const e=t==null?void 0:t.data;return e?Array.isArray(e)?{list:e,total:e.length}:{list:Array.isArray(e.list)?e.list:[],total:typeof e.total=="number"?e.total:Array.isArray(e.list)?e.list.length:0}:{list:[],total:0}},f=async({keyword:t,warehouseId:e,type:a,status:s,page:i,pageSize:o})=>{const d={code:t||void 0,warehouse_id:e||void 0,type:a,status:s,page:i,page_size:o},u=await n.get(`${r}/list`,{params:d});return c(u)},l=async t=>{const e=await n.get(`${r}/detail/${t}`);return(e==null?void 0:e.data)||null},m=async t=>n.post(`${r}/create`,t),y=async t=>n.put(`${r}/update`,t),A=async t=>{const e=new FormData;return Array.isArray(t.ids)&&t.ids.forEach((a,s)=>{e.append(`ids[${s}]`,a)}),n.post(`${r}/delete`,e)},h=async t=>{const e=new FormData;return Array.isArray(t.ids)&&t.ids.forEach((a,s)=>{e.append(`ids[${s}]`,a)}),t.code!==void 0&&e.append("code",t.code),t.type!==void 0&&e.append("type",t.type),t.capacity!==void 0&&e.append("capacity",t.capacity),t.status!==void 0&&e.append("status",t.status),n.put(`${r}/update`,e)},$=async t=>n.post(`${r}/batch-generate`,t),L=async({user_id:t,warehouse_id:e,file:a})=>{const s=new FormData;return s.append("user_id",t),s.append("warehouse_id",e),s.append("file",a),n.post(`${r}/import-from-excel`,s)},g=async({page:t,pageSize:e,keyword:a}={})=>{const s={page:t,page_size:e,code:a||void 0},i=await n.get(`${r}/all-list`,{params:s});return c(i)};export{$ as a,h as b,m as c,A as d,f as e,l as f,g,L as i,y as u};
|
||||||
1
dist/assets/location.7eeab00a.css
vendored
Normal file
1
dist/assets/location.7eeab00a.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.location-manager[data-v-565b3e0e]{padding:20px;background-color:#f5f7fa;min-height:100vh}.filter-bar[data-v-565b3e0e]{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;align-items:center;background:white;padding:16px 20px;border-radius:8px;box-shadow:0 1px 4px #0000000d}.pagination-wrapper[data-v-565b3e0e]{margin-top:20px;display:flex;justify-content:flex-end;background:white;padding:14px 20px;border-radius:8px}[data-v-565b3e0e] .el-table{border-radius:8px;overflow:hidden;box-shadow:0 1px 4px #0000000d}[data-v-565b3e0e] .el-dialog__body{padding-top:16px}.dialog-footer[data-v-565b3e0e]{display:flex;justify-content:flex-end;gap:12px}
|
||||||
1
dist/assets/location.969de7ac.js
vendored
1
dist/assets/location.969de7ac.js
vendored
@ -1 +0,0 @@
|
|||||||
import{r as s}from"./request.3edc855e.js";const n="/location",d=t=>{const e=t==null?void 0:t.data;return e?Array.isArray(e)?{list:e,total:e.length}:{list:Array.isArray(e.list)?e.list:[],total:typeof e.total=="number"?e.total:Array.isArray(e.list)?e.list.length:0}:{list:[],total:0}},p=async({keyword:t,warehouseId:e,type:a,status:r,page:i,pageSize:c})=>{const o={code:t||void 0,warehouse_id:e||void 0,type:a,status:r,page:i,page_size:c},u=await s.get(`${n}/list`,{params:o});return d(u)},A=async t=>{const e=await s.get(`${n}/detail/${t}`);return(e==null?void 0:e.data)||null},y=async t=>s.post(`${n}/create`,t),h=async t=>s.put(`${n}/update`,t),$=async t=>{const e=new FormData;return Array.isArray(t.ids)&&t.ids.forEach((a,r)=>{e.append(`ids[${r}]`,a)}),s.post(`${n}/delete`,e)},l=async t=>{const e=new FormData;return Array.isArray(t.ids)&&t.ids.forEach((a,r)=>{e.append(`ids[${r}]`,a)}),t.code!==void 0&&e.append("code",t.code),t.type!==void 0&&e.append("type",t.type),t.capacity!==void 0&&e.append("capacity",t.capacity),t.status!==void 0&&e.append("status",t.status),s.put(`${n}/update`,e)},m=async t=>s.post(`${n}/batch-generate`,t);export{m as a,l as b,y as c,$ as d,p as e,A as f,h as u};
|
|
||||||
10
dist/assets/location.c9b9c86c.js
vendored
Normal file
10
dist/assets/location.c9b9c86c.js
vendored
Normal file
File diff suppressed because one or more lines are too long
79
dist/assets/locationManager.3fdb9a7d.js
vendored
Normal file
79
dist/assets/locationManager.3fdb9a7d.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/locationManager.953f3ba6.css
vendored
Normal file
1
dist/assets/locationManager.953f3ba6.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.filter-bar[data-v-257183e8]{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:20px;align-items:center;background:white;padding:16px 20px;border-radius:8px;box-shadow:0 1px 4px #0000000d}.pagination-wrapper[data-v-257183e8]{margin-top:20px;display:flex;justify-content:flex-end;background:white;padding:14px 20px;border-radius:8px}.location-empty[data-v-257183e8]{padding:40px 0;text-align:center;color:#999;background:#fafafa;border-radius:8px}[data-v-257183e8] .el-table{border-radius:8px;overflow:hidden;box-shadow:0 1px 4px #0000000d}[data-v-257183e8] .el-dialog__body{padding-top:16px}.dialog-footer[data-v-257183e8]{display:flex;justify-content:flex-end;gap:12px}.location-dialog[data-v-257183e8] .el-dialog__header{background:linear-gradient(90deg,#4b8afc,#6db6ff);border-bottom:none;color:#fff;border-radius:12px 12px 0 0}.location-dialog[data-v-257183e8] .el-dialog__title{color:#fff;font-size:18px;font-weight:600}.location-dialog[data-v-257183e8] .el-dialog__body{padding:24px 32px;background:#f6fbff}.location-dialog[data-v-257183e8] .el-form{background:#ffffff;border-radius:16px;padding:16px;box-shadow:inset 0 0 0 1px #3454d114}.location-dialog[data-v-257183e8] .el-form-item{margin-bottom:20px}.location-dialog[data-v-257183e8] .el-form-item__label{color:#4b5563;font-weight:520}.location-dialog[data-v-257183e8] .el-input,.location-dialog[data-v-257183e8] .el-input-number,.location-dialog[data-v-257183e8] .el-radio-group,.location-dialog[data-v-257183e8] .el-switch{border-radius:10px}.location-dialog[data-v-257183e8] .el-radio-group{gap:12px;display:flex;flex-wrap:wrap}.location-dialog[data-v-257183e8] .el-dialog__footer{padding:18px 32px 24px;background:#ffffff}.batch-code-range-header[data-v-257183e8]{display:flex;align-items:center;flex-wrap:wrap;gap:10px;margin-bottom:10px;border-radius:8px;color:#000000a6;font-size:12px;font-weight:600}.range-btn[data-v-257183e8]{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border-radius:50%;border:1px solid rgba(16,24,40,.08);background:#ffffff;color:#333;transition:transform .12s ease,box-shadow .12s ease,background .12s ease;box-shadow:0 1px 2px #10182814}.range-btn[data-v-257183e8]:hover{transform:translateY(-1px);box-shadow:0 4px 8px #1018281f}.range-btn-add[data-v-257183e8]{border-color:#1677ff29;color:#176cff}.range-btn-add[data-v-257183e8]:hover{background:#eff5ff}.range-btn-remove[data-v-257183e8]{border-color:#ff4d4f29;color:#ff4d4f}.range-btn-remove[data-v-257183e8]:hover{background:#fff2f2}.detail-loading[data-v-257183e8]{display:flex;align-items:center;justify-content:center;gap:8px;padding:40px 0;color:#409eff}.detail-content[data-v-257183e8]{padding:0 10px}.detail-section[data-v-257183e8]{margin-bottom:24px}.detail-row[data-v-257183e8]{display:flex;padding:8px 0;border-bottom:1px solid #f0f0f0}.detail-row[data-v-257183e8]:last-child{border-bottom:none}.detail-label[data-v-257183e8]{color:#909399;min-width:80px}.detail-code[data-v-257183e8]{font-weight:600;color:#409eff}.status-enabled[data-v-257183e8]{color:#67c23a}.status-disabled[data-v-257183e8]{color:#f56c6c}.barcode-section[data-v-257183e8]{margin-top:20px;padding-top:20px;border-top:1px solid #e4e7ed}.barcode-title[data-v-257183e8]{font-size:14px;font-weight:600;color:#303133;margin-bottom:16px;text-align:center}.barcode-loading[data-v-257183e8]{display:flex;align-items:center;justify-content:center;gap:8px;padding:30px 0;color:#409eff}.barcode-image-wrapper[data-v-257183e8]{display:flex;flex-direction:column;align-items:center;gap:8px}.barcode-image[data-v-257183e8]{max-width:100%;height:auto;border:1px solid #e4e7ed;border-radius:4px}.barcode-content-text[data-v-257183e8]{font-size:14px;font-weight:600;color:#606266;font-family:Courier New,monospace}.barcode-empty[data-v-257183e8]{text-align:center;color:#c0c4cc;padding:30px 0}.search-item[data-v-257183e8]{display:flex;align-items:center;gap:8px}.label-with-icon[data-v-257183e8]{display:inline-flex;align-items:center;gap:4px}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user