forked from yoone/API
Compare commits
18 Commits
b8f951b3d4
...
2f99e27f0f
| Author | SHA1 | Date |
|---|---|---|
|
|
2f99e27f0f | |
|
|
361b05117c | |
|
|
823967a268 | |
|
|
feeeded13b | |
|
|
2df777b73e | |
|
|
e1891df4f6 | |
|
|
185a786b2e | |
|
|
4e0101e0f2 | |
|
|
8f7f35c538 | |
|
|
8e7ec2372d | |
|
|
a02758a926 | |
|
|
bc1d4de446 | |
|
|
ac4b925aee | |
|
|
72b28e7c2b | |
|
|
d884369915 | |
|
|
0057585da3 | |
|
|
5ca3c94afc | |
|
|
ae34d1fab0 |
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"extends": "./node_modules/mwts/",
|
"extends": "./node_modules/mwts/",
|
||||||
"ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"],
|
"ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings", "scripts"],
|
||||||
"env": {
|
"env": {
|
||||||
"jest": true
|
"jest": true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,7 @@ yarn.lock
|
||||||
**/config.prod.ts
|
**/config.prod.ts
|
||||||
**/config.local.ts
|
**/config.local.ts
|
||||||
container
|
container
|
||||||
|
scripts
|
||||||
|
tmp_uploads/
|
||||||
|
.trae
|
||||||
|
docs
|
||||||
|
|
@ -0,0 +1,254 @@
|
||||||
|
# Area 区域管理 API 文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本文档详细描述了区域管理相关的API接口,包括增删改查操作以及新增的坐标功能.
|
||||||
|
|
||||||
|
## API 接口列表
|
||||||
|
|
||||||
|
### 1. 创建区域
|
||||||
|
|
||||||
|
**请求信息**
|
||||||
|
- URL: `/api/area`
|
||||||
|
- Method: `POST`
|
||||||
|
- Headers: `Authorization: Bearer {token}`
|
||||||
|
|
||||||
|
**请求体 (JSON)**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "欧洲",
|
||||||
|
"latitude": 48.8566,
|
||||||
|
"longitude": 2.3522
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明**
|
||||||
|
- `name`: 区域名称 (必填)
|
||||||
|
- `latitude`: 纬度 (-90 到 90 之间,可选)
|
||||||
|
- `longitude`: 经度 (-180 到 180 之间,可选)
|
||||||
|
|
||||||
|
**响应示例**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "创建成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "欧洲",
|
||||||
|
"latitude": 48.8566,
|
||||||
|
"longitude": 2.3522,
|
||||||
|
"createdAt": "2024-01-01T12:00:00Z",
|
||||||
|
"updatedAt": "2024-01-01T12:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 更新区域
|
||||||
|
|
||||||
|
**请求信息**
|
||||||
|
- URL: `/api/area/{id}`
|
||||||
|
- Method: `PUT`
|
||||||
|
- Headers: `Authorization: Bearer {token}`
|
||||||
|
|
||||||
|
**请求体 (JSON)**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "欧洲区域",
|
||||||
|
"latitude": 48.8566,
|
||||||
|
"longitude": 2.3522
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明**
|
||||||
|
- `name`: 区域名称 (可选)
|
||||||
|
- `latitude`: 纬度 (-90 到 90 之间,可选)
|
||||||
|
- `longitude`: 经度 (-180 到 180 之间,可选)
|
||||||
|
|
||||||
|
**响应示例**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "更新成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "欧洲区域",
|
||||||
|
"latitude": 48.8566,
|
||||||
|
"longitude": 2.3522,
|
||||||
|
"createdAt": "2024-01-01T12:00:00Z",
|
||||||
|
"updatedAt": "2024-01-01T12:30:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 删除区域
|
||||||
|
|
||||||
|
**请求信息**
|
||||||
|
- URL: `/api/area/{id}`
|
||||||
|
- Method: `DELETE`
|
||||||
|
- Headers: `Authorization: Bearer {token}`
|
||||||
|
|
||||||
|
**响应示例**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "删除成功",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 获取区域列表(分页)
|
||||||
|
|
||||||
|
**请求信息**
|
||||||
|
- URL: `/api/area`
|
||||||
|
- Method: `GET`
|
||||||
|
- Headers: `Authorization: Bearer {token}`
|
||||||
|
- Query Parameters:
|
||||||
|
- `currentPage`: 当前页码 (默认 1)
|
||||||
|
- `pageSize`: 每页数量 (默认 10)
|
||||||
|
- `name`: 区域名称(可选,用于搜索)
|
||||||
|
|
||||||
|
**响应示例**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "查询成功",
|
||||||
|
"data": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "欧洲",
|
||||||
|
"latitude": 48.8566,
|
||||||
|
"longitude": 2.3522,
|
||||||
|
"createdAt": "2024-01-01T12:00:00Z",
|
||||||
|
"updatedAt": "2024-01-01T12:00:00Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 获取所有区域
|
||||||
|
|
||||||
|
**请求信息**
|
||||||
|
- URL: `/api/area/all`
|
||||||
|
- Method: `GET`
|
||||||
|
- Headers: `Authorization: Bearer {token}`
|
||||||
|
|
||||||
|
**响应示例**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "查询成功",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "欧洲",
|
||||||
|
"latitude": 48.8566,
|
||||||
|
"longitude": 2.3522,
|
||||||
|
"createdAt": "2024-01-01T12:00:00Z",
|
||||||
|
"updatedAt": "2024-01-01T12:00:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "亚洲",
|
||||||
|
"latitude": 35.6762,
|
||||||
|
"longitude": 139.6503,
|
||||||
|
"createdAt": "2024-01-01T12:10:00Z",
|
||||||
|
"updatedAt": "2024-01-01T12:10:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 根据ID获取区域详情
|
||||||
|
|
||||||
|
**请求信息**
|
||||||
|
- URL: `/api/area/{id}`
|
||||||
|
- Method: `GET`
|
||||||
|
- Headers: `Authorization: Bearer {token}`
|
||||||
|
|
||||||
|
**响应示例**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "查询成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "欧洲",
|
||||||
|
"latitude": 48.8566,
|
||||||
|
"longitude": 2.3522,
|
||||||
|
"createdAt": "2024-01-01T12:00:00Z",
|
||||||
|
"updatedAt": "2024-01-01T12:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 世界地图实现建议
|
||||||
|
|
||||||
|
对于前端实现世界地图并显示区域坐标,推荐以下方案:
|
||||||
|
|
||||||
|
### 1. 使用开源地图库
|
||||||
|
|
||||||
|
- **Leaflet.js**: 轻量级开源地图库,易于集成
|
||||||
|
- **Mapbox**: 提供丰富的地图样式和交互功能
|
||||||
|
- **Google Maps API**: 功能强大但需要API密钥
|
||||||
|
|
||||||
|
### 2. 实现步骤
|
||||||
|
|
||||||
|
1. **获取区域数据**:
|
||||||
|
使用 `/api/area/all` 接口获取所有包含坐标信息的区域
|
||||||
|
|
||||||
|
2. **初始化地图**:
|
||||||
|
```javascript
|
||||||
|
// Leaflet示例
|
||||||
|
const map = L.map('map').setView([0, 0], 2);
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
attribution: '© OpenStreetMap contributors'
|
||||||
|
}).addTo(map);
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **添加区域标记**:
|
||||||
|
```javascript
|
||||||
|
// 假设areas是从API获取的数据
|
||||||
|
areas.forEach(area => {
|
||||||
|
if (area.latitude && area.longitude) {
|
||||||
|
const marker = L.marker([area.latitude, area.longitude]).addTo(map);
|
||||||
|
marker.bindPopup(`<b>${area.name}</b>`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **添加交互功能**:
|
||||||
|
- 点击标记显示区域详情
|
||||||
|
- 搜索和筛选功能
|
||||||
|
- 编辑坐标功能(调用更新API)
|
||||||
|
|
||||||
|
### 3. 坐标输入建议
|
||||||
|
|
||||||
|
在区域管理界面,可以添加以下功能来辅助坐标输入:
|
||||||
|
|
||||||
|
1. 提供搜索框,根据地点名称自动获取坐标
|
||||||
|
2. 集成小型地图,允许用户点击选择位置
|
||||||
|
3. 添加验证,确保输入的坐标在有效范围内
|
||||||
|
|
||||||
|
## 数据模型说明
|
||||||
|
|
||||||
|
### Area 实体
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 描述 | 是否必填 |
|
||||||
|
|--------|------|------|----------|
|
||||||
|
| id | number | 区域ID | 否(自动生成) |
|
||||||
|
| name | string | 区域名称 | 是 |
|
||||||
|
| latitude | number | 纬度(范围:-90 到 90) | 否 |
|
||||||
|
| longitude | number | 经度(范围:-180 到 180) | 否 |
|
||||||
|
| createdAt | Date | 创建时间 | 否(自动生成) |
|
||||||
|
| updatedAt | Date | 更新时间 | 否(自动生成) |
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
API可能返回的错误信息:
|
||||||
|
|
||||||
|
- `区域名称已存在`: 当尝试创建或更新区域名称与现有名称重复时
|
||||||
|
- `区域不存在`: 当尝试更新或删除不存在的区域时
|
||||||
|
- `权限错误`: 当请求缺少有效的授权令牌时
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,49 @@
|
||||||
|
# 数据库迁移指南
|
||||||
|
|
||||||
|
为了支持区域坐标功能,需要执行数据库迁移操作,将新增的 `latitude` 和 `longitude` 字段添加到数据库表中.
|
||||||
|
|
||||||
|
## 执行迁移步骤
|
||||||
|
|
||||||
|
### 1. 生成迁移文件
|
||||||
|
|
||||||
|
运行以下命令生成迁移文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run migration:generate -- ./src/db/migrations/AddCoordinatesToArea
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 检查迁移文件
|
||||||
|
|
||||||
|
生成的迁移文件会自动包含添加 `latitude` 和 `longitude` 字段的SQL语句.您可以检查文件内容确保迁移逻辑正确.
|
||||||
|
|
||||||
|
### 3. 执行迁移
|
||||||
|
|
||||||
|
运行以下命令执行迁移,将更改应用到数据库:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run migration:run
|
||||||
|
```
|
||||||
|
|
||||||
|
## 手动迁移SQL(可选)
|
||||||
|
|
||||||
|
如果需要手动执行SQL,可以使用以下语句:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE `area`
|
||||||
|
ADD COLUMN `latitude` DECIMAL(10,6) NULL AFTER `name`,
|
||||||
|
ADD COLUMN `longitude` DECIMAL(10,6) NULL AFTER `latitude`;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 回滚迁移(如需)
|
||||||
|
|
||||||
|
如果遇到问题,可以使用以下命令回滚迁移:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run typeorm -- migration:revert -d src/db/datasource.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 确保在执行迁移前备份数据库
|
||||||
|
- 迁移不会影响现有数据,新增字段默认为 NULL
|
||||||
|
- 迁移后,可以通过API开始为区域添加坐标信息
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
|
||||||
|
> my-midway-project@1.0.0 dev
|
||||||
|
> cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app.js
|
||||||
|
|
||||||
|
[2J[3J[H
|
||||||
|
[[90m10:37:17 AM[0m] Starting compilation in watch mode...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[90m10:37:19 AM[0m] Found 0 errors. Watching for file changes.
|
||||||
|
|
||||||
|
2025-12-01 10:37:20.106 INFO 58678 [SyncProductJob] start job SyncProductJob
|
||||||
|
2025-12-01 10:37:20.106 INFO 58678 [SyncShipmentJob] start job SyncShipmentJob
|
||||||
|
2025-12-01 10:37:20.109 INFO 58678 [SyncProductJob] complete job SyncProductJob
|
||||||
|
|
||||||
|
[32mNode.js server[0m [2mstarted in[0m 732 ms
|
||||||
|
|
||||||
|
[32m➜[0m Local: [36mhttp://127.0.0.1:[1m7001[0m[36m/[0m[0m
|
||||||
|
[32m➜[0m [2mNetwork: http://192.168.5.100:7001/ [0m
|
||||||
|
|
||||||
|
2025-12-01 10:37:20.110 INFO 58678 [SyncShipmentJob] complete job SyncShipmentJob
|
||||||
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
|
|
@ -15,16 +15,26 @@
|
||||||
"@midwayjs/logger": "^3.1.0",
|
"@midwayjs/logger": "^3.1.0",
|
||||||
"@midwayjs/swagger": "^3.20.2",
|
"@midwayjs/swagger": "^3.20.2",
|
||||||
"@midwayjs/typeorm": "^3.20.0",
|
"@midwayjs/typeorm": "^3.20.0",
|
||||||
|
"@midwayjs/upload": "^3.20.16",
|
||||||
"@midwayjs/validate": "^3.20.2",
|
"@midwayjs/validate": "^3.20.2",
|
||||||
"@woocommerce/woocommerce-rest-api": "^1.0.2",
|
"@woocommerce/woocommerce-rest-api": "^1.0.2",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.13.2",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
|
"csv-parse": "^6.1.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"mysql2": "^3.11.5",
|
"eta": "^4.4.1",
|
||||||
|
"i18n-iso-countries": "^7.14.0",
|
||||||
|
"mysql2": "^3.15.3",
|
||||||
"nodemailer": "^7.0.5",
|
"nodemailer": "^7.0.5",
|
||||||
|
"npm-check-updates": "^19.1.2",
|
||||||
|
"qs": "^6.14.0",
|
||||||
|
"sharp": "^0.33.3",
|
||||||
"swagger-ui-dist": "^5.18.2",
|
"swagger-ui-dist": "^5.18.2",
|
||||||
"typeorm": "^0.3.20",
|
"typeorm": "^0.3.27",
|
||||||
|
"typeorm-extension": "^3.7.2",
|
||||||
|
"wpapi": "^1.2.2",
|
||||||
|
"xlsx": "^0.18.5",
|
||||||
"xml2js": "^0.6.2"
|
"xml2js": "^0.6.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -36,10 +46,15 @@
|
||||||
"dev": "cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app.js",
|
"dev": "cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app.js",
|
||||||
"test": "cross-env NODE_ENV=unittest jest",
|
"test": "cross-env NODE_ENV=unittest jest",
|
||||||
"cov": "jest --coverage",
|
"cov": "jest --coverage",
|
||||||
"lint": "mwts check",
|
"lint": "mwtsc check",
|
||||||
"lint:fix": "mwts fix",
|
"lint:fix": "mwtsc fix",
|
||||||
"ci": "npm run cov",
|
"ci": "npm run cov",
|
||||||
"build": "mwtsc --cleanOutDir"
|
"build": "mwtsc --cleanOutDir",
|
||||||
|
"seed": "ts-node src/db/seed/index.ts",
|
||||||
|
"seed:run": "ts-node ./node_modules/typeorm-extension/bin/cli.cjs seed:run -d src/db/datasource.ts",
|
||||||
|
"typeorm": "ts-node ./node_modules/typeorm/cli.js",
|
||||||
|
"migration:generate": "npm run typeorm -- -d src/db/datasource.ts migration:generate",
|
||||||
|
"migration:run": "npm run typeorm -- migration:run -d src/db/datasource.ts"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -51,6 +66,7 @@
|
||||||
"@midwayjs/mock": "^3.20.11",
|
"@midwayjs/mock": "^3.20.11",
|
||||||
"cross-env": "^10.1.0",
|
"cross-env": "^10.1.0",
|
||||||
"mwtsc": "^1.15.2",
|
"mwtsc": "^1.15.2",
|
||||||
|
"tsx": "^4.20.6",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
# Permutation页面列表显示问题分析和修复方案
|
||||||
|
|
||||||
|
## 问题分析
|
||||||
|
|
||||||
|
经过代码分析,发现了以下几个可能导致列表不显示的问题:
|
||||||
|
|
||||||
|
### 1. API路径不匹配
|
||||||
|
前端代码中引用的API函数名与后端控制器中的路径不一致:
|
||||||
|
- 前端:`productcontrollerGetcategoriesall`、`productcontrollerGetcategoryattributes`、`productcontrollerGetproductlist`
|
||||||
|
- 后端实际的API路径:`/product/categories/all`、`/product/category/:id/attributes`、`/product/list`
|
||||||
|
|
||||||
|
### 2. 数据格式问题
|
||||||
|
- `getCategoryAttributes`返回的数据结构与前端期望的不匹配
|
||||||
|
- 属性值获取逻辑可能存在问题
|
||||||
|
|
||||||
|
### 3. 组合生成逻辑问题
|
||||||
|
- 在生成排列组合时,数据结构和键值对应可能不正确
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
### 后端修复
|
||||||
|
|
||||||
|
1. **修改getCategoryAttributes方法** - 在`/Users/zksu/Developer/work/workcode/API/src/service/product.service.ts`中:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 获取分类下的属性配置
|
||||||
|
async getCategoryAttributes(categoryId: number): Promise<any[]> {
|
||||||
|
const category = await this.categoryModel.findOne({
|
||||||
|
where: { id: categoryId },
|
||||||
|
relations: ['attributes', 'attributes.attributeDict', 'attributes.attributeDict.items'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!category) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化返回,匹配前端期望的数据结构
|
||||||
|
return category.attributes.map(attr => ({
|
||||||
|
id: attr.id,
|
||||||
|
dictId: attr.attributeDict.id,
|
||||||
|
name: attr.attributeDict.name, // 用于generateKeyFromPermutation
|
||||||
|
title: attr.attributeDict.title, // 用于列标题
|
||||||
|
dict: {
|
||||||
|
id: attr.attributeDict.id,
|
||||||
|
name: attr.attributeDict.name,
|
||||||
|
title: attr.attributeDict.title,
|
||||||
|
items: attr.attributeDict.items || []
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **确保dict/items接口可用** - 检查字典项获取接口:
|
||||||
|
|
||||||
|
在`/Users/zksu/Developer/work/workcode/API/src/controller/dict.controller.ts`中添加或确认:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Get('/items')
|
||||||
|
async getDictItems(@Query('dictId') dictId: number) {
|
||||||
|
try {
|
||||||
|
const dict = await this.dictModel.findOne({
|
||||||
|
where: { id: dictId },
|
||||||
|
relations: ['items']
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!dict) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return dict.items || [];
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 前端修复建议
|
||||||
|
|
||||||
|
1. **添加错误处理和调试信息**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 在获取属性值的地方添加错误处理
|
||||||
|
const fetchData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
// 1. Fetch Attributes
|
||||||
|
const attrRes = await productcontrollerGetcategoryattributes({
|
||||||
|
id: categoryId,
|
||||||
|
});
|
||||||
|
console.log('Attributes response:', attrRes); // 调试用
|
||||||
|
const attrs = Array.isArray(attrRes) ? attrRes : attrRes?.data || [];
|
||||||
|
setAttributes(attrs);
|
||||||
|
|
||||||
|
// 2. Fetch Attribute Values (Dict Items)
|
||||||
|
const valuesMap: Record<string, any[]> = {};
|
||||||
|
for (const attr of attrs) {
|
||||||
|
const dictId = attr.dict?.id || attr.dictId;
|
||||||
|
if (dictId) {
|
||||||
|
try {
|
||||||
|
const itemsRes = await request('/dict/items', {
|
||||||
|
params: { dictId },
|
||||||
|
});
|
||||||
|
console.log(`Dict items for ${attr.name}:`, itemsRes); // 调试用
|
||||||
|
valuesMap[attr.name] = itemsRes || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch items for dict ${dictId}:`, error);
|
||||||
|
valuesMap[attr.name] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setAttributeValues(valuesMap);
|
||||||
|
|
||||||
|
// 3. Fetch Existing Products
|
||||||
|
await fetchProducts(categoryId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in fetchData:', error);
|
||||||
|
message.error('获取数据失败');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **修复组合生成逻辑**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 修改generateKeyFromPermutation函数
|
||||||
|
const generateKeyFromPermutation = (perm: any) => {
|
||||||
|
const parts = Object.keys(perm).map((attrName) => {
|
||||||
|
const valItem = perm[attrName];
|
||||||
|
const val = valItem.name || valItem.value; // 兼容不同的数据格式
|
||||||
|
return `${attrName}:${val}`;
|
||||||
|
});
|
||||||
|
return parts.sort().join('|');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修改generateAttributeKey函数
|
||||||
|
const generateAttributeKey = (attrs: any[]) => {
|
||||||
|
const parts = attrs.map((a) => {
|
||||||
|
const key = a.dict?.name || a.dictName || a.name;
|
||||||
|
const val = a.name || a.value;
|
||||||
|
return `${key}:${val}`;
|
||||||
|
});
|
||||||
|
return parts.sort().join('|');
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **添加空状态处理**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 在ProTable中添加空状态提示
|
||||||
|
<ProTable
|
||||||
|
// ... 其他属性
|
||||||
|
locale={{
|
||||||
|
emptyText: permutations.length === 0 && !loading ? '暂无数据,请检查分类属性配置' : '暂无数据'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 调试步骤
|
||||||
|
|
||||||
|
1. **检查网络请求**:
|
||||||
|
- 打开浏览器开发者工具
|
||||||
|
- 检查 `/product/categories/all` 请求是否成功
|
||||||
|
- 检查 `/product/category/:id/attributes` 请求返回的数据格式
|
||||||
|
- 检查 `/dict/items?dictId=:id` 请求是否成功
|
||||||
|
- 检查 `/product/list` 请求是否成功
|
||||||
|
|
||||||
|
2. **检查控制台日志**:
|
||||||
|
- 查看属性数据是否正确加载
|
||||||
|
- 查看属性值是否正确获取
|
||||||
|
- 查看排列组合是否正确生成
|
||||||
|
|
||||||
|
3. **检查数据结构**:
|
||||||
|
- 确认 `attributes` 数组是否正确
|
||||||
|
- 确认 `attributeValues` 对象是否正确填充
|
||||||
|
- 确认 `permutations` 数组是否正确生成
|
||||||
|
|
||||||
|
## 测试验证
|
||||||
|
|
||||||
|
1. 选择一个有属性配置的分类
|
||||||
|
2. 确认属性有对应的字典项
|
||||||
|
3. 检查排列组合是否正确显示
|
||||||
|
4. 验证现有产品匹配是否正确
|
||||||
365
pnpm-lock.yaml
365
pnpm-lock.yaml
|
|
@ -65,12 +65,18 @@ importers:
|
||||||
nodemailer:
|
nodemailer:
|
||||||
specifier: ^7.0.5
|
specifier: ^7.0.5
|
||||||
version: 7.0.10
|
version: 7.0.10
|
||||||
|
npm-check-updates:
|
||||||
|
specifier: ^19.1.2
|
||||||
|
version: 19.1.2
|
||||||
swagger-ui-dist:
|
swagger-ui-dist:
|
||||||
specifier: ^5.18.2
|
specifier: ^5.18.2
|
||||||
version: 5.30.2
|
version: 5.30.2
|
||||||
typeorm:
|
typeorm:
|
||||||
specifier: ^0.3.20
|
specifier: ^0.3.20
|
||||||
version: 0.3.27(mysql2@3.15.3)(reflect-metadata@0.2.2)
|
version: 0.3.27(mysql2@3.15.3)(reflect-metadata@0.2.2)
|
||||||
|
xlsx:
|
||||||
|
specifier: ^0.18.5
|
||||||
|
version: 0.18.5
|
||||||
xml2js:
|
xml2js:
|
||||||
specifier: ^0.6.2
|
specifier: ^0.6.2
|
||||||
version: 0.6.2
|
version: 0.6.2
|
||||||
|
|
@ -84,6 +90,9 @@ importers:
|
||||||
mwtsc:
|
mwtsc:
|
||||||
specifier: ^1.15.2
|
specifier: ^1.15.2
|
||||||
version: 1.15.2
|
version: 1.15.2
|
||||||
|
tsx:
|
||||||
|
specifier: ^4.20.6
|
||||||
|
version: 4.20.6
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.9.3
|
specifier: ^5.9.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
@ -97,6 +106,162 @@ packages:
|
||||||
'@epic-web/invariant@1.0.0':
|
'@epic-web/invariant@1.0.0':
|
||||||
resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==}
|
resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==}
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.25.12':
|
||||||
|
resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.25.12':
|
||||||
|
resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.25.12':
|
||||||
|
resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.25.12':
|
||||||
|
resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.25.12':
|
||||||
|
resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.25.12':
|
||||||
|
resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.25.12':
|
||||||
|
resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
'@hapi/bourne@3.0.0':
|
'@hapi/bourne@3.0.0':
|
||||||
resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==}
|
resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==}
|
||||||
|
|
||||||
|
|
@ -314,6 +479,10 @@ packages:
|
||||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
adler-32@1.3.1:
|
||||||
|
resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
ansi-regex@5.0.1:
|
ansi-regex@5.0.1:
|
||||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
@ -415,6 +584,10 @@ packages:
|
||||||
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
|
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
cfb@1.2.2:
|
||||||
|
resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
chokidar@3.6.0:
|
chokidar@3.6.0:
|
||||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||||
engines: {node: '>= 8.10.0'}
|
engines: {node: '>= 8.10.0'}
|
||||||
|
|
@ -446,6 +619,10 @@ packages:
|
||||||
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
|
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
|
||||||
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
||||||
|
|
||||||
|
codepage@1.15.0:
|
||||||
|
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||||
engines: {node: '>=7.0.0'}
|
engines: {node: '>=7.0.0'}
|
||||||
|
|
@ -488,6 +665,11 @@ packages:
|
||||||
core-util-is@1.0.3:
|
core-util-is@1.0.3:
|
||||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||||
|
|
||||||
|
crc-32@1.2.2:
|
||||||
|
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
create-hash@1.2.0:
|
create-hash@1.2.0:
|
||||||
resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==}
|
resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==}
|
||||||
|
|
||||||
|
|
@ -606,6 +788,11 @@ packages:
|
||||||
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
esbuild@0.25.12:
|
||||||
|
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
escalade@3.2.0:
|
escalade@3.2.0:
|
||||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
@ -651,6 +838,10 @@ packages:
|
||||||
formidable@2.1.5:
|
formidable@2.1.5:
|
||||||
resolution: {integrity: sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==}
|
resolution: {integrity: sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==}
|
||||||
|
|
||||||
|
frac@1.1.2:
|
||||||
|
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
fresh@0.5.2:
|
fresh@0.5.2:
|
||||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
@ -961,6 +1152,11 @@ packages:
|
||||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
npm-check-updates@19.1.2:
|
||||||
|
resolution: {integrity: sha512-FNeFCVgPOj0fz89hOpGtxP2rnnRHR7hD2E8qNU8SMWfkyDZXA/xpgjsL3UMLSo3F/K13QvJDnbxPngulNDDo/g==}
|
||||||
|
engines: {node: '>=20.0.0', npm: '>=8.12.1'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
oauth-1.0a@2.2.6:
|
oauth-1.0a@2.2.6:
|
||||||
resolution: {integrity: sha512-6bkxv3N4Gu5lty4viIcIAnq5GbxECviMBeKR3WX/q87SPQ8E8aursPZUtsXDnxCs787af09WPRBLqYrf/lwoYQ==}
|
resolution: {integrity: sha512-6bkxv3N4Gu5lty4viIcIAnq5GbxECviMBeKR3WX/q87SPQ8E8aursPZUtsXDnxCs787af09WPRBLqYrf/lwoYQ==}
|
||||||
|
|
||||||
|
|
@ -1160,6 +1356,10 @@ packages:
|
||||||
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
|
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
ssf@0.11.2:
|
||||||
|
resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
statuses@1.5.0:
|
statuses@1.5.0:
|
||||||
resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}
|
resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
@ -1228,6 +1428,11 @@ packages:
|
||||||
resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
|
resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
|
||||||
engines: {node: '>=0.6.x'}
|
engines: {node: '>=0.6.x'}
|
||||||
|
|
||||||
|
tsx@4.20.6:
|
||||||
|
resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
type-is@1.6.18:
|
type-is@1.6.18:
|
||||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
@ -1327,6 +1532,14 @@ packages:
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
wmf@1.0.2:
|
||||||
|
resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
|
word@0.3.0:
|
||||||
|
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
wrap-ansi@7.0.0:
|
wrap-ansi@7.0.0:
|
||||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
@ -1338,6 +1551,11 @@ packages:
|
||||||
wrappy@1.0.2:
|
wrappy@1.0.2:
|
||||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||||
|
|
||||||
|
xlsx@0.18.5:
|
||||||
|
resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
|
||||||
|
engines: {node: '>=0.8'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
xml2js@0.6.2:
|
xml2js@0.6.2:
|
||||||
resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
|
resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
|
||||||
engines: {node: '>=4.0.0'}
|
engines: {node: '>=4.0.0'}
|
||||||
|
|
@ -1373,6 +1591,84 @@ snapshots:
|
||||||
|
|
||||||
'@epic-web/invariant@1.0.0': {}
|
'@epic-web/invariant@1.0.0': {}
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.25.12':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@hapi/bourne@3.0.0': {}
|
'@hapi/bourne@3.0.0': {}
|
||||||
|
|
||||||
'@hapi/hoek@9.3.0': {}
|
'@hapi/hoek@9.3.0': {}
|
||||||
|
|
@ -1645,6 +1941,8 @@ snapshots:
|
||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
negotiator: 0.6.3
|
negotiator: 0.6.3
|
||||||
|
|
||||||
|
adler-32@1.3.1: {}
|
||||||
|
|
||||||
ansi-regex@5.0.1: {}
|
ansi-regex@5.0.1: {}
|
||||||
|
|
||||||
ansi-regex@6.2.2: {}
|
ansi-regex@6.2.2: {}
|
||||||
|
|
@ -1735,6 +2033,11 @@ snapshots:
|
||||||
call-bind-apply-helpers: 1.0.2
|
call-bind-apply-helpers: 1.0.2
|
||||||
get-intrinsic: 1.3.0
|
get-intrinsic: 1.3.0
|
||||||
|
|
||||||
|
cfb@1.2.2:
|
||||||
|
dependencies:
|
||||||
|
adler-32: 1.3.1
|
||||||
|
crc-32: 1.2.2
|
||||||
|
|
||||||
chokidar@3.6.0:
|
chokidar@3.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
anymatch: 3.1.3
|
anymatch: 3.1.3
|
||||||
|
|
@ -1779,6 +2082,8 @@ snapshots:
|
||||||
|
|
||||||
co@4.6.0: {}
|
co@4.6.0: {}
|
||||||
|
|
||||||
|
codepage@1.15.0: {}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name: 1.1.4
|
color-name: 1.1.4
|
||||||
|
|
@ -1812,6 +2117,8 @@ snapshots:
|
||||||
|
|
||||||
core-util-is@1.0.3: {}
|
core-util-is@1.0.3: {}
|
||||||
|
|
||||||
|
crc-32@1.2.2: {}
|
||||||
|
|
||||||
create-hash@1.2.0:
|
create-hash@1.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
cipher-base: 1.0.7
|
cipher-base: 1.0.7
|
||||||
|
|
@ -1919,6 +2226,35 @@ snapshots:
|
||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
|
|
||||||
|
esbuild@0.25.12:
|
||||||
|
optionalDependencies:
|
||||||
|
'@esbuild/aix-ppc64': 0.25.12
|
||||||
|
'@esbuild/android-arm': 0.25.12
|
||||||
|
'@esbuild/android-arm64': 0.25.12
|
||||||
|
'@esbuild/android-x64': 0.25.12
|
||||||
|
'@esbuild/darwin-arm64': 0.25.12
|
||||||
|
'@esbuild/darwin-x64': 0.25.12
|
||||||
|
'@esbuild/freebsd-arm64': 0.25.12
|
||||||
|
'@esbuild/freebsd-x64': 0.25.12
|
||||||
|
'@esbuild/linux-arm': 0.25.12
|
||||||
|
'@esbuild/linux-arm64': 0.25.12
|
||||||
|
'@esbuild/linux-ia32': 0.25.12
|
||||||
|
'@esbuild/linux-loong64': 0.25.12
|
||||||
|
'@esbuild/linux-mips64el': 0.25.12
|
||||||
|
'@esbuild/linux-ppc64': 0.25.12
|
||||||
|
'@esbuild/linux-riscv64': 0.25.12
|
||||||
|
'@esbuild/linux-s390x': 0.25.12
|
||||||
|
'@esbuild/linux-x64': 0.25.12
|
||||||
|
'@esbuild/netbsd-arm64': 0.25.12
|
||||||
|
'@esbuild/netbsd-x64': 0.25.12
|
||||||
|
'@esbuild/openbsd-arm64': 0.25.12
|
||||||
|
'@esbuild/openbsd-x64': 0.25.12
|
||||||
|
'@esbuild/openharmony-arm64': 0.25.12
|
||||||
|
'@esbuild/sunos-x64': 0.25.12
|
||||||
|
'@esbuild/win32-arm64': 0.25.12
|
||||||
|
'@esbuild/win32-ia32': 0.25.12
|
||||||
|
'@esbuild/win32-x64': 0.25.12
|
||||||
|
|
||||||
escalade@3.2.0: {}
|
escalade@3.2.0: {}
|
||||||
|
|
||||||
escape-html@1.0.3: {}
|
escape-html@1.0.3: {}
|
||||||
|
|
@ -1967,6 +2303,8 @@ snapshots:
|
||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
qs: 6.14.0
|
qs: 6.14.0
|
||||||
|
|
||||||
|
frac@1.1.2: {}
|
||||||
|
|
||||||
fresh@0.5.2: {}
|
fresh@0.5.2: {}
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
|
|
@ -2313,6 +2651,8 @@ snapshots:
|
||||||
|
|
||||||
normalize-path@3.0.0: {}
|
normalize-path@3.0.0: {}
|
||||||
|
|
||||||
|
npm-check-updates@19.1.2: {}
|
||||||
|
|
||||||
oauth-1.0a@2.2.6: {}
|
oauth-1.0a@2.2.6: {}
|
||||||
|
|
||||||
object-inspect@1.13.4: {}
|
object-inspect@1.13.4: {}
|
||||||
|
|
@ -2494,6 +2834,10 @@ snapshots:
|
||||||
|
|
||||||
sqlstring@2.3.3: {}
|
sqlstring@2.3.3: {}
|
||||||
|
|
||||||
|
ssf@0.11.2:
|
||||||
|
dependencies:
|
||||||
|
frac: 1.1.2
|
||||||
|
|
||||||
statuses@1.5.0: {}
|
statuses@1.5.0: {}
|
||||||
|
|
||||||
statuses@2.0.1: {}
|
statuses@2.0.1: {}
|
||||||
|
|
@ -2582,6 +2926,13 @@ snapshots:
|
||||||
|
|
||||||
tsscmp@1.0.6: {}
|
tsscmp@1.0.6: {}
|
||||||
|
|
||||||
|
tsx@4.20.6:
|
||||||
|
dependencies:
|
||||||
|
esbuild: 0.25.12
|
||||||
|
get-tsconfig: 4.13.0
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.3
|
||||||
|
|
||||||
type-is@1.6.18:
|
type-is@1.6.18:
|
||||||
dependencies:
|
dependencies:
|
||||||
media-typer: 0.3.0
|
media-typer: 0.3.0
|
||||||
|
|
@ -2647,6 +2998,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
isexe: 2.0.0
|
isexe: 2.0.0
|
||||||
|
|
||||||
|
wmf@1.0.2: {}
|
||||||
|
|
||||||
|
word@0.3.0: {}
|
||||||
|
|
||||||
wrap-ansi@7.0.0:
|
wrap-ansi@7.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
|
|
@ -2661,6 +3016,16 @@ snapshots:
|
||||||
|
|
||||||
wrappy@1.0.2: {}
|
wrappy@1.0.2: {}
|
||||||
|
|
||||||
|
xlsx@0.18.5:
|
||||||
|
dependencies:
|
||||||
|
adler-32: 1.3.1
|
||||||
|
cfb: 1.2.2
|
||||||
|
codepage: 1.15.0
|
||||||
|
crc-32: 1.2.2
|
||||||
|
ssf: 0.11.2
|
||||||
|
wmf: 1.0.2
|
||||||
|
word: 0.3.0
|
||||||
|
|
||||||
xml2js@0.6.2:
|
xml2js@0.6.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
sax: 1.4.3
|
sax: 1.4.3
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,750 @@
|
||||||
|
import { ISiteAdapter } from '../interface/site-adapter.interface';
|
||||||
|
import { ShopyyService } from '../service/shopyy.service';
|
||||||
|
import {
|
||||||
|
UnifiedAddressDTO,
|
||||||
|
UnifiedCustomerDTO,
|
||||||
|
UnifiedMediaDTO,
|
||||||
|
UnifiedOrderDTO,
|
||||||
|
UnifiedOrderLineItemDTO,
|
||||||
|
UnifiedPaginationDTO,
|
||||||
|
UnifiedProductDTO,
|
||||||
|
UnifiedProductVariationDTO,
|
||||||
|
UnifiedSearchParamsDTO,
|
||||||
|
UnifiedSubscriptionDTO,
|
||||||
|
UnifiedReviewPaginationDTO,
|
||||||
|
UnifiedReviewDTO,
|
||||||
|
UnifiedWebhookDTO,
|
||||||
|
UnifiedWebhookPaginationDTO,
|
||||||
|
CreateWebhookDTO,
|
||||||
|
UpdateWebhookDTO,
|
||||||
|
} from '../dto/site-api.dto';
|
||||||
|
import {
|
||||||
|
ShopyyCustomer,
|
||||||
|
ShopyyOrder,
|
||||||
|
ShopyyProduct,
|
||||||
|
ShopyyVariant,
|
||||||
|
ShopyyWebhook,
|
||||||
|
} from '../dto/shopyy.dto';
|
||||||
|
import {
|
||||||
|
OrderStatus,
|
||||||
|
} from '../enums/base.enum';
|
||||||
|
export class ShopyyAdapter implements ISiteAdapter {
|
||||||
|
constructor(private site: any, private shopyyService: ShopyyService) {
|
||||||
|
this.mapCustomer = this.mapCustomer.bind(this);
|
||||||
|
this.mapProduct = this.mapProduct.bind(this);
|
||||||
|
this.mapVariation = this.mapVariation.bind(this);
|
||||||
|
this.mapOrder = this.mapOrder.bind(this);
|
||||||
|
this.mapMedia = this.mapMedia.bind(this);
|
||||||
|
// this.mapSubscription = this.mapSubscription.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapMedia(item: any): UnifiedMediaDTO {
|
||||||
|
// 映射媒体项目
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
date_created: item.created_at,
|
||||||
|
date_modified: item.updated_at,
|
||||||
|
source_url: item.src,
|
||||||
|
title: item.alt || '',
|
||||||
|
media_type: '', // Shopyy API未提供,暂时留空
|
||||||
|
mime_type: '', // Shopyy API未提供,暂时留空
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapMediaSearchParams(params: UnifiedSearchParamsDTO): any {
|
||||||
|
const { search, page, per_page } = params;
|
||||||
|
const shopyyParams: any = {
|
||||||
|
page: page || 1,
|
||||||
|
limit: per_page || 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
shopyyParams.query = search;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shopyyParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapProduct(item: ShopyyProduct & { permalink?: string }): UnifiedProductDTO {
|
||||||
|
// 映射产品状态
|
||||||
|
function mapProductStatus(status: number) {
|
||||||
|
return status === 1 ? 'publish' : 'draft';
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name || item.title,
|
||||||
|
type: String(item.product_type ?? ''),
|
||||||
|
status: mapProductStatus(item.status),
|
||||||
|
sku: item.variant?.sku || '',
|
||||||
|
regular_price: String(item.variant?.price ?? ''),
|
||||||
|
sale_price: String(item.special_price ?? ''),
|
||||||
|
price: String(item.price ?? ''),
|
||||||
|
stock_status: item.inventory_tracking === 1 ? 'instock' : 'outofstock',
|
||||||
|
stock_quantity: item.inventory_quantity,
|
||||||
|
images: (item.images || []).map((img: any) => ({
|
||||||
|
id: img.id || 0,
|
||||||
|
src: img.src,
|
||||||
|
name: '',
|
||||||
|
alt: img.alt || '',
|
||||||
|
// 排序
|
||||||
|
position: img.position || '',
|
||||||
|
})),
|
||||||
|
attributes: (item.options || []).map(option => ({
|
||||||
|
id: option.id || 0,
|
||||||
|
name: option.option_name || '',
|
||||||
|
options: (option.values || []).map(value => value.option_value || ''),
|
||||||
|
})),
|
||||||
|
tags: (item.tags || []).map((t: any) => ({
|
||||||
|
id: t.id || 0,
|
||||||
|
name: t.name || '',
|
||||||
|
})),
|
||||||
|
// shopyy叫做专辑
|
||||||
|
categories: item.collections.map((c: any) => ({
|
||||||
|
id: c.id || 0,
|
||||||
|
name: c.title || '',
|
||||||
|
})),
|
||||||
|
variations: item.variants?.map(this.mapVariation.bind(this)) || [],
|
||||||
|
permalink: item.permalink,
|
||||||
|
date_created:
|
||||||
|
typeof item.created_at === 'number'
|
||||||
|
? new Date(item.created_at * 1000).toISOString()
|
||||||
|
: String(item.created_at ?? ''),
|
||||||
|
date_modified:
|
||||||
|
typeof item.updated_at === 'number'
|
||||||
|
? new Date(item.updated_at * 1000).toISOString()
|
||||||
|
: String(item.updated_at ?? ''),
|
||||||
|
raw: item,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapVariation(variant: ShopyyVariant): UnifiedProductVariationDTO {
|
||||||
|
// 映射变体
|
||||||
|
return {
|
||||||
|
id: variant.id,
|
||||||
|
sku: variant.sku || '',
|
||||||
|
regular_price: String(variant.price ?? ''),
|
||||||
|
sale_price: String(variant.special_price ?? ''),
|
||||||
|
price: String(variant.price ?? ''),
|
||||||
|
stock_status:
|
||||||
|
variant.inventory_tracking === 1 ? 'instock' : 'outofstock',
|
||||||
|
stock_quantity: variant.inventory_quantity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
shopyyOrderAutoNextStatusMap = {//订单状态 100 未完成;110 待处理;180 已完成(确认收货); 190 取消;
|
||||||
|
[100]: OrderStatus.PENDING, // 100 未完成 转为 pending
|
||||||
|
[110]: OrderStatus.PROCESSING, // 110 待处理 转为 processing
|
||||||
|
[180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed
|
||||||
|
[190]: OrderStatus.CANCEL // 190 取消 转为 cancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapOrder(item: ShopyyOrder): UnifiedOrderDTO {
|
||||||
|
// 提取账单和送货地址 如果不存在则为空对象
|
||||||
|
const billing = (item as any).billing_address || {};
|
||||||
|
const shipping = (item as any).shipping_address || {};
|
||||||
|
|
||||||
|
// 构建账单地址对象
|
||||||
|
const billingObj: UnifiedAddressDTO = {
|
||||||
|
first_name: billing.first_name || item.firstname || '',
|
||||||
|
last_name: billing.last_name || item.lastname || '',
|
||||||
|
fullname: billing.name || `${item.firstname} ${item.lastname}`.trim(),
|
||||||
|
company: billing.company || '',
|
||||||
|
email: item.customer_email || item.email || '',
|
||||||
|
phone: billing.phone || (item as any).telephone || '',
|
||||||
|
address_1: billing.address1 || item.payment_address || '',
|
||||||
|
address_2: billing.address2 || '',
|
||||||
|
city: billing.city || item.payment_city || '',
|
||||||
|
state: billing.province || item.payment_zone || '',
|
||||||
|
postcode: billing.zip || item.payment_postcode || '',
|
||||||
|
country:
|
||||||
|
billing.country_name ||
|
||||||
|
billing.country_code ||
|
||||||
|
item.payment_country ||
|
||||||
|
'',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 构建送货地址对象
|
||||||
|
const shippingObj: UnifiedAddressDTO = {
|
||||||
|
first_name: shipping.first_name || item.firstname || '',
|
||||||
|
last_name: shipping.last_name || item.lastname || '',
|
||||||
|
fullname: shipping.name || '',
|
||||||
|
company: shipping.company || '',
|
||||||
|
address_1:
|
||||||
|
shipping.address1 ||
|
||||||
|
(typeof item.shipping_address === 'string'
|
||||||
|
? item.shipping_address
|
||||||
|
: '') ||
|
||||||
|
'',
|
||||||
|
address_2: shipping.address2 || '',
|
||||||
|
city: shipping.city || item.shipping_city || '',
|
||||||
|
state: shipping.province || item.shipping_zone || '',
|
||||||
|
postcode: shipping.zip || item.shipping_postcode || '',
|
||||||
|
country:
|
||||||
|
shipping.country_name ||
|
||||||
|
shipping.country_code ||
|
||||||
|
item.shipping_country ||
|
||||||
|
'',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化地址为字符串
|
||||||
|
const formatAddress = (addr: UnifiedAddressDTO) => {
|
||||||
|
return [
|
||||||
|
addr.fullname,
|
||||||
|
addr.company,
|
||||||
|
addr.address_1,
|
||||||
|
addr.address_2,
|
||||||
|
addr.city,
|
||||||
|
addr.state,
|
||||||
|
addr.postcode,
|
||||||
|
addr.country,
|
||||||
|
addr.phone,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(', ');
|
||||||
|
};
|
||||||
|
|
||||||
|
const lineItems: UnifiedOrderLineItemDTO[] = (item.products || []).map(
|
||||||
|
(p: any) => ({
|
||||||
|
id: p.id,
|
||||||
|
name: p.product_title || p.name,
|
||||||
|
product_id: p.product_id,
|
||||||
|
quantity: p.quantity,
|
||||||
|
total: String(p.price ?? ''),
|
||||||
|
sku: p.sku || p.sku_code || '',
|
||||||
|
price: String(p.price ?? ''),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const currencySymbols: Record<string, string> = {
|
||||||
|
'EUR': '€',
|
||||||
|
'USD': '$',
|
||||||
|
'GBP': '£',
|
||||||
|
'JPY': '¥',
|
||||||
|
'AUD': 'A$',
|
||||||
|
'CAD': 'C$',
|
||||||
|
'CHF': 'CHF',
|
||||||
|
'CNY': '¥',
|
||||||
|
'HKD': 'HK$',
|
||||||
|
'NZD': 'NZ$',
|
||||||
|
'SGD': 'S$'
|
||||||
|
// 可以根据需要添加更多货币代码和符号
|
||||||
|
};
|
||||||
|
const originStatus = item.status;
|
||||||
|
item.status = this.shopyyOrderAutoNextStatusMap[originStatus];
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: item.id || item.order_id,
|
||||||
|
number: item.order_number || item.order_sn,
|
||||||
|
status: String(item.status || item.order_status),
|
||||||
|
currency: item.currency_code || item.currency,
|
||||||
|
total: String(item.total_price ?? item.total_amount ?? ''),
|
||||||
|
customer_id: item.customer_id || item.user_id,
|
||||||
|
customer_name:
|
||||||
|
item.customer_name || `${item.firstname} ${item.lastname}`.trim(),
|
||||||
|
email: item.customer_email || item.email,
|
||||||
|
line_items: lineItems,
|
||||||
|
sales: lineItems, // 兼容前端
|
||||||
|
billing: billingObj,
|
||||||
|
shipping: shippingObj,
|
||||||
|
billing_full_address: formatAddress(billingObj),
|
||||||
|
shipping_full_address: formatAddress(shippingObj),
|
||||||
|
payment_method: item.payment_method,
|
||||||
|
shipping_lines: item.fulfillments || [],
|
||||||
|
fee_lines: item.fee_lines || [],
|
||||||
|
coupon_lines: item.coupon_lines || [],
|
||||||
|
date_paid: typeof item.pay_at === 'number'
|
||||||
|
? item.pay_at === 0 ? null : new Date(item.pay_at * 1000).toISOString()
|
||||||
|
: null,
|
||||||
|
|
||||||
|
refunds: [],
|
||||||
|
currency_symbol: (currencySymbols[item.currency] || '$') || '',
|
||||||
|
date_created:
|
||||||
|
typeof item.created_at === 'number'
|
||||||
|
? new Date(item.created_at * 1000).toISOString()
|
||||||
|
: item.date_added ||
|
||||||
|
(typeof item.created_at === 'string' ? item.created_at : ''),
|
||||||
|
date_modified:
|
||||||
|
typeof item.updated_at === 'number'
|
||||||
|
? new Date(item.updated_at * 1000).toISOString()
|
||||||
|
: item.date_updated ||
|
||||||
|
item.last_modified ||
|
||||||
|
(typeof item.updated_at === 'string' ? item.updated_at : ''),
|
||||||
|
raw: item,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private mapCustomer(item: ShopyyCustomer): UnifiedCustomerDTO {
|
||||||
|
// 处理多地址结构
|
||||||
|
const addresses = item.addresses || [];
|
||||||
|
const defaultAddress = item.default_address || (addresses.length > 0 ? addresses[0] : {});
|
||||||
|
|
||||||
|
// 尝试从地址列表中获取billing和shipping
|
||||||
|
// 如果没有明确区分,默认使用默认地址或第一个地址
|
||||||
|
const billingAddress = defaultAddress;
|
||||||
|
const shippingAddress = defaultAddress;
|
||||||
|
|
||||||
|
const billing = {
|
||||||
|
first_name: billingAddress.first_name || item.first_name || '',
|
||||||
|
last_name: billingAddress.last_name || item.last_name || '',
|
||||||
|
fullname: billingAddress.name || `${billingAddress.first_name || item.first_name || ''} ${billingAddress.last_name || item.last_name || ''}`.trim(),
|
||||||
|
company: billingAddress.company || '',
|
||||||
|
email: item.email || '',
|
||||||
|
phone: billingAddress.phone || item.contact || '',
|
||||||
|
address_1: billingAddress.address1 || '',
|
||||||
|
address_2: billingAddress.address2 || '',
|
||||||
|
city: billingAddress.city || '',
|
||||||
|
state: billingAddress.province || '',
|
||||||
|
postcode: billingAddress.zip || '',
|
||||||
|
country: billingAddress.country_name || billingAddress.country_code || item.country?.country_name || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const shipping = {
|
||||||
|
first_name: shippingAddress.first_name || item.first_name || '',
|
||||||
|
last_name: shippingAddress.last_name || item.last_name || '',
|
||||||
|
fullname: shippingAddress.name || `${shippingAddress.first_name || item.first_name || ''} ${shippingAddress.last_name || item.last_name || ''}`.trim(),
|
||||||
|
company: shippingAddress.company || '',
|
||||||
|
address_1: shippingAddress.address1 || '',
|
||||||
|
address_2: shippingAddress.address2 || '',
|
||||||
|
city: shippingAddress.city || '',
|
||||||
|
state: shippingAddress.province || '',
|
||||||
|
postcode: shippingAddress.zip || '',
|
||||||
|
country: shippingAddress.country_name || shippingAddress.country_code || item.country?.country_name || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: item.id || item.customer_id,
|
||||||
|
orders: Number(item.orders_count ?? item.order_count ?? item.orders ?? 0),
|
||||||
|
total_spend: Number(item.total_spent ?? item.total_spend_amount ?? item.total_spend_money ?? 0),
|
||||||
|
first_name: item.first_name || item.firstname || '',
|
||||||
|
last_name: item.last_name || item.lastname || '',
|
||||||
|
fullname: item.fullname || item.customer_name || `${item.first_name || item.firstname || ''} ${item.last_name || item.lastname || ''}`.trim(),
|
||||||
|
email: item.email || item.customer_email || '',
|
||||||
|
phone: item.contact || billing.phone || item.phone || '',
|
||||||
|
billing,
|
||||||
|
shipping,
|
||||||
|
date_created:
|
||||||
|
typeof item.created_at === 'number'
|
||||||
|
? new Date(item.created_at * 1000).toISOString()
|
||||||
|
: (typeof item.created_at === 'string' ? item.created_at : item.date_added || ''),
|
||||||
|
date_modified:
|
||||||
|
typeof item.updated_at === 'number'
|
||||||
|
? new Date(item.updated_at * 1000).toISOString()
|
||||||
|
: (typeof item.updated_at === 'string' ? item.updated_at : item.date_updated || ''),
|
||||||
|
raw: item,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProducts(
|
||||||
|
params: UnifiedSearchParamsDTO
|
||||||
|
): Promise<UnifiedPaginationDTO<UnifiedProductDTO>> {
|
||||||
|
const response = await this.shopyyService.fetchResourcePaged<ShopyyProduct>(
|
||||||
|
this.site,
|
||||||
|
'products/list',
|
||||||
|
params
|
||||||
|
);
|
||||||
|
const { items=[], total, totalPages, page, per_page } = response;
|
||||||
|
const finalItems = items.map((item) => ({
|
||||||
|
...item,
|
||||||
|
permalink: `${this.site.websiteUrl}/products/${item.handle}`,
|
||||||
|
})).map(this.mapProduct.bind(this))
|
||||||
|
return {
|
||||||
|
items: finalItems as UnifiedProductDTO[],
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
page,
|
||||||
|
per_page,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]> {
|
||||||
|
// Shopyy getAllProducts 暂未实现
|
||||||
|
throw new Error('Shopyy getAllProducts 暂未实现');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProduct(id: string | number): Promise<UnifiedProductDTO> {
|
||||||
|
// 使用ShopyyService获取单个产品
|
||||||
|
const product = await this.shopyyService.getProduct(this.site, id);
|
||||||
|
return this.mapProduct(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
||||||
|
const res = await this.shopyyService.createProduct(this.site, data);
|
||||||
|
return this.mapProduct(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateProduct(id: string | number, data: Partial<UnifiedProductDTO>): Promise<boolean> {
|
||||||
|
// Shopyy update returns boolean?
|
||||||
|
// shopyyService.updateProduct returns boolean.
|
||||||
|
// So I can't return the updated product.
|
||||||
|
// I have to fetch it again or return empty/input.
|
||||||
|
// Since getProduct is missing, I'll return input data as UnifiedProductDTO (mock).
|
||||||
|
await this.shopyyService.updateProduct(this.site, String(id), data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateVariation(productId: string | number, variationId: string | number, data: any): Promise<any> {
|
||||||
|
await this.shopyyService.updateVariation(this.site, String(productId), String(variationId), data);
|
||||||
|
return { ...data, id: variationId };
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrderNotes(orderId: string | number): Promise<any[]> {
|
||||||
|
return await this.shopyyService.getOrderNotes(this.site, orderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOrderNote(orderId: string | number, data: any): Promise<any> {
|
||||||
|
return await this.shopyyService.createOrderNote(this.site, orderId, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteProduct(id: string | number): Promise<boolean> {
|
||||||
|
// Use batch delete
|
||||||
|
await this.shopyyService.batchProcessProducts(this.site, { delete: [id] });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async batchProcessProducts(
|
||||||
|
data: { create?: any[]; update?: any[]; delete?: Array<string | number> }
|
||||||
|
): Promise<any> {
|
||||||
|
return await this.shopyyService.batchProcessProducts(this.site, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrders(
|
||||||
|
params: UnifiedSearchParamsDTO
|
||||||
|
): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>> {
|
||||||
|
const { items, total, totalPages, page, per_page } =
|
||||||
|
await this.shopyyService.fetchResourcePaged<any>(
|
||||||
|
this.site,
|
||||||
|
'orders',
|
||||||
|
params
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
items: items.map(this.mapOrder.bind(this)),
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
page,
|
||||||
|
per_page,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
|
||||||
|
const data = await this.shopyyService.getAllOrders(this.site.id,params);
|
||||||
|
return data.map(this.mapOrder.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrder(id: string | number): Promise<UnifiedOrderDTO> {
|
||||||
|
const data = await this.shopyyService.getOrder(this.site.id, String(id));
|
||||||
|
return this.mapOrder(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO> {
|
||||||
|
const createdOrder = await this.shopyyService.createOrder(this.site, data);
|
||||||
|
return this.mapOrder(createdOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateOrder(id: string | number, data: Partial<UnifiedOrderDTO>): Promise<boolean> {
|
||||||
|
return await this.shopyyService.updateOrder(this.site, String(id), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteOrder(id: string | number): Promise<boolean> {
|
||||||
|
return await this.shopyyService.deleteOrder(this.site, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async shipOrder(orderId: string | number, data: {
|
||||||
|
tracking_number?: string;
|
||||||
|
shipping_provider?: string;
|
||||||
|
shipping_method?: string;
|
||||||
|
items?: Array<{
|
||||||
|
order_item_id: number;
|
||||||
|
quantity: number;
|
||||||
|
}>;
|
||||||
|
}): Promise<any> {
|
||||||
|
// 订单发货
|
||||||
|
try {
|
||||||
|
// 更新订单状态为已发货
|
||||||
|
await this.shopyyService.updateOrder(this.site, String(orderId), {
|
||||||
|
status: 'completed',
|
||||||
|
meta_data: [
|
||||||
|
{ key: '_tracking_number', value: data.tracking_number },
|
||||||
|
{ key: '_shipping_provider', value: data.shipping_provider },
|
||||||
|
{ key: '_shipping_method', value: data.shipping_method }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加发货备注
|
||||||
|
const note = `订单已发货${data.tracking_number ? `,物流单号:${data.tracking_number}` : ''}${data.shipping_provider ? `,物流公司:${data.shipping_provider}` : ''}`;
|
||||||
|
await this.shopyyService.createOrderNote(this.site, orderId, { note, customer_note: true });
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
order_id: orderId,
|
||||||
|
shipment_id: `shipment_${orderId}_${Date.now()}`,
|
||||||
|
tracking_number: data.tracking_number,
|
||||||
|
shipping_provider: data.shipping_provider,
|
||||||
|
shipped_at: new Date().toISOString()
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`发货失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelShipOrder(orderId: string | number, data: {
|
||||||
|
reason?: string;
|
||||||
|
shipment_id?: string;
|
||||||
|
}): Promise<any> {
|
||||||
|
// 取消订单发货
|
||||||
|
try {
|
||||||
|
// 将订单状态改回处理中
|
||||||
|
await this.shopyyService.updateOrder(this.site, String(orderId), {
|
||||||
|
status: 'processing',
|
||||||
|
meta_data: [
|
||||||
|
{ key: '_shipment_cancelled', value: 'yes' },
|
||||||
|
{ key: '_shipment_cancelled_reason', value: data.reason }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加取消发货的备注
|
||||||
|
const note = `订单发货已取消${data.reason ? `,原因:${data.reason}` : ''}`;
|
||||||
|
await this.shopyyService.createOrderNote(this.site, orderId, { note, customer_note: true });
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
order_id: orderId,
|
||||||
|
shipment_id: data.shipment_id,
|
||||||
|
reason: data.reason,
|
||||||
|
cancelled_at: new Date().toISOString()
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`取消发货失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSubscriptions(
|
||||||
|
params: UnifiedSearchParamsDTO
|
||||||
|
): Promise<UnifiedPaginationDTO<UnifiedSubscriptionDTO>> {
|
||||||
|
throw new Error('Shopyy does not support subscriptions.');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise<UnifiedSubscriptionDTO[]> {
|
||||||
|
// Shopyy getAllSubscriptions 暂未实现
|
||||||
|
throw new Error('Shopyy getAllSubscriptions 暂未实现');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMedia(
|
||||||
|
params: UnifiedSearchParamsDTO
|
||||||
|
): Promise<UnifiedPaginationDTO<UnifiedMediaDTO>> {
|
||||||
|
const requestParams = this.mapMediaSearchParams(params);
|
||||||
|
const { items, total, totalPages, page, per_page } = await this.shopyyService.fetchResourcePaged<any>(
|
||||||
|
this.site,
|
||||||
|
'media', // Shopyy的媒体API端点可能需要调整
|
||||||
|
requestParams
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
items: items.map(this.mapMedia.bind(this)),
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
page,
|
||||||
|
per_page,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllMedia(params?: UnifiedSearchParamsDTO): Promise<UnifiedMediaDTO[]> {
|
||||||
|
// Shopyy getAllMedia 暂未实现
|
||||||
|
throw new Error('Shopyy getAllMedia 暂未实现');
|
||||||
|
}
|
||||||
|
|
||||||
|
async createMedia(file: any): Promise<UnifiedMediaDTO> {
|
||||||
|
const createdMedia = await this.shopyyService.createMedia(this.site, file);
|
||||||
|
return this.mapMedia(createdMedia);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateMedia(id: string | number, data: any): Promise<UnifiedMediaDTO> {
|
||||||
|
const updatedMedia = await this.shopyyService.updateMedia(this.site, id, data);
|
||||||
|
return this.mapMedia(updatedMedia);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteMedia(id: string | number): Promise<boolean> {
|
||||||
|
return await this.shopyyService.deleteMedia(this.site, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getReviews(
|
||||||
|
params: UnifiedSearchParamsDTO
|
||||||
|
): Promise<UnifiedReviewPaginationDTO> {
|
||||||
|
const requestParams = this.mapReviewSearchParams(params);
|
||||||
|
const { items, total, totalPages, page, per_page } = await this.shopyyService.getReviews(
|
||||||
|
this.site,
|
||||||
|
requestParams
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
items: items.map(this.mapReview),
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
page,
|
||||||
|
per_page,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllReviews(params?: UnifiedSearchParamsDTO): Promise<UnifiedReviewDTO[]> {
|
||||||
|
// Shopyy getAllReviews 暂未实现
|
||||||
|
throw new Error('Shopyy getAllReviews 暂未实现');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getReview(id: string | number): Promise<UnifiedReviewDTO> {
|
||||||
|
const review = await this.shopyyService.getReview(this.site, id);
|
||||||
|
return this.mapReview(review);
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapReview(review: any): UnifiedReviewDTO {
|
||||||
|
// 将ShopYY评论数据映射到统一评论DTO格式
|
||||||
|
return {
|
||||||
|
id: review.id || review.review_id,
|
||||||
|
product_id: review.product_id || review.goods_id,
|
||||||
|
author: review.author_name || review.username || '',
|
||||||
|
email: review.author_email || review.user_email || '',
|
||||||
|
content: review.comment || review.content || '',
|
||||||
|
rating: Number(review.score || review.rating || 0),
|
||||||
|
status: String(review.status || 'approved'),
|
||||||
|
date_created:
|
||||||
|
typeof review.created_at === 'number'
|
||||||
|
? new Date(review.created_at * 1000).toISOString()
|
||||||
|
: String(review.created_at || review.date_added || '')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapReviewSearchParams(params: UnifiedSearchParamsDTO): any {
|
||||||
|
const { search, page, per_page, where } = params;
|
||||||
|
const shopyyParams: any = {
|
||||||
|
page: page || 1,
|
||||||
|
limit: per_page || 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
shopyyParams.search = search;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (where.status) {
|
||||||
|
shopyyParams.status = where.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (product_id) {
|
||||||
|
// shopyyParams.product_id = product_id;
|
||||||
|
// }
|
||||||
|
|
||||||
|
return shopyyParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createReview(data: any): Promise<UnifiedReviewDTO> {
|
||||||
|
const createdReview = await this.shopyyService.createReview(this.site, data);
|
||||||
|
return this.mapReview(createdReview);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateReview(id: string | number, data: any): Promise<UnifiedReviewDTO> {
|
||||||
|
const updatedReview = await this.shopyyService.updateReview(this.site, id, data);
|
||||||
|
return this.mapReview(updatedReview);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteReview(id: string | number): Promise<boolean> {
|
||||||
|
return await this.shopyyService.deleteReview(this.site, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Webhook相关方法
|
||||||
|
private mapWebhook(item: ShopyyWebhook): UnifiedWebhookDTO {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
name: item.webhook_name || `Webhook-${item.id}`,
|
||||||
|
topic: item.event_code || '',
|
||||||
|
delivery_url: item.url|| '',
|
||||||
|
status: 'active',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWebhooks(params: UnifiedSearchParamsDTO): Promise<UnifiedWebhookPaginationDTO> {
|
||||||
|
const { items, total, totalPages, page, per_page } = await this.shopyyService.getWebhooks(this.site, params);
|
||||||
|
return {
|
||||||
|
items: items.map(this.mapWebhook),
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
page,
|
||||||
|
per_page,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllWebhooks(params?: UnifiedSearchParamsDTO): Promise<UnifiedWebhookDTO[]> {
|
||||||
|
// Shopyy getAllWebhooks 暂未实现
|
||||||
|
throw new Error('Shopyy getAllWebhooks 暂未实现');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWebhook(id: string | number): Promise<UnifiedWebhookDTO> {
|
||||||
|
const webhook = await this.shopyyService.getWebhook(this.site, id);
|
||||||
|
return this.mapWebhook(webhook);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createWebhook(data: CreateWebhookDTO): Promise<UnifiedWebhookDTO> {
|
||||||
|
const createdWebhook = await this.shopyyService.createWebhook(this.site, data);
|
||||||
|
return this.mapWebhook(createdWebhook);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateWebhook(id: string | number, data: UpdateWebhookDTO): Promise<UnifiedWebhookDTO> {
|
||||||
|
const updatedWebhook = await this.shopyyService.updateWebhook(this.site, id, data);
|
||||||
|
return this.mapWebhook(updatedWebhook);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteWebhook(id: string | number): Promise<boolean> {
|
||||||
|
return await this.shopyyService.deleteWebhook(this.site, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLinks(): Promise<Array<{title: string, url: string}>> {
|
||||||
|
// ShopYY站点的管理后台链接通常基于apiUrl构建
|
||||||
|
const url = this.site.websiteUrl
|
||||||
|
// 提取基础域名,去掉可能的路径部分
|
||||||
|
const baseUrl = url.replace(/\/api\/.*$/i, '');
|
||||||
|
|
||||||
|
const links = [
|
||||||
|
{ title: '访问网站', url: baseUrl },
|
||||||
|
{ title: '管理后台', url: `${baseUrl}/admin/` },
|
||||||
|
{ title: '订单管理', url: `${baseUrl}/admin/orders.htm` },
|
||||||
|
{ title: '产品管理', url: `${baseUrl}/admin/products.htm` },
|
||||||
|
{ title: '客户管理', url: `${baseUrl}/admin/customers.htm` },
|
||||||
|
{ title: '插件管理', url: `${baseUrl}/admin/apps.htm` },
|
||||||
|
{ title: '店铺设置', url: `${baseUrl}/admin/settings.htm` },
|
||||||
|
{ title: '营销中心', url: `${baseUrl}/admin/marketing.htm` },
|
||||||
|
];
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCustomers(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedCustomerDTO>> {
|
||||||
|
const { items, total, totalPages, page, per_page } =
|
||||||
|
await this.shopyyService.fetchCustomersPaged(this.site, params);
|
||||||
|
return {
|
||||||
|
items: items.map(this.mapCustomer.bind(this)),
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
page,
|
||||||
|
per_page
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllCustomers(params?: UnifiedSearchParamsDTO): Promise<UnifiedCustomerDTO[]> {
|
||||||
|
// Shopyy getAllCustomers 暂未实现
|
||||||
|
throw new Error('Shopyy getAllCustomers 暂未实现');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCustomer(id: string | number): Promise<UnifiedCustomerDTO> {
|
||||||
|
const customer = await this.shopyyService.getCustomer(this.site, id);
|
||||||
|
return this.mapCustomer(customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCustomer(data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
||||||
|
const createdCustomer = await this.shopyyService.createCustomer(this.site, data);
|
||||||
|
return this.mapCustomer(createdCustomer);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCustomer(id: string | number, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
||||||
|
const updatedCustomer = await this.shopyyService.updateCustomer(this.site, id, data);
|
||||||
|
return this.mapCustomer(updatedCustomer);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteCustomer(id: string | number): Promise<boolean> {
|
||||||
|
return await this.shopyyService.deleteCustomer(this.site, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,852 @@
|
||||||
|
import { ISiteAdapter } from '../interface/site-adapter.interface';
|
||||||
|
import {
|
||||||
|
UnifiedMediaDTO,
|
||||||
|
UnifiedOrderDTO,
|
||||||
|
UnifiedPaginationDTO,
|
||||||
|
UnifiedProductDTO,
|
||||||
|
UnifiedSearchParamsDTO,
|
||||||
|
UnifiedSubscriptionDTO,
|
||||||
|
UnifiedCustomerDTO,
|
||||||
|
UnifiedReviewPaginationDTO,
|
||||||
|
UnifiedReviewDTO,
|
||||||
|
UnifiedWebhookDTO,
|
||||||
|
UnifiedWebhookPaginationDTO,
|
||||||
|
CreateWebhookDTO,
|
||||||
|
UpdateWebhookDTO,
|
||||||
|
} from '../dto/site-api.dto';
|
||||||
|
import {
|
||||||
|
WooProduct,
|
||||||
|
WooOrder,
|
||||||
|
WooSubscription,
|
||||||
|
WpMedia,
|
||||||
|
WooCustomer,
|
||||||
|
WooWebhook,
|
||||||
|
WooOrderSearchParams,
|
||||||
|
WooProductSearchParams,
|
||||||
|
} from '../dto/woocommerce.dto';
|
||||||
|
import { Site } from '../entity/site.entity';
|
||||||
|
import { WPService } from '../service/wp.service';
|
||||||
|
|
||||||
|
export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
|
// 构造函数接收站点配置与服务实例
|
||||||
|
constructor(private site: Site, private wpService: WPService) {
|
||||||
|
this.mapProduct = this.mapProduct.bind(this);
|
||||||
|
this.mapReview = this.mapReview.bind(this);
|
||||||
|
this.mapCustomer = this.mapCustomer.bind(this);
|
||||||
|
this.mapMedia = this.mapMedia.bind(this);
|
||||||
|
this.mapOrder = this.mapOrder.bind(this);
|
||||||
|
this.mapWebhook = this.mapWebhook.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 映射 WooCommerce webhook 到统一格式
|
||||||
|
private mapWebhook(webhook: WooWebhook): UnifiedWebhookDTO {
|
||||||
|
return {
|
||||||
|
id: webhook.id.toString(),
|
||||||
|
name: webhook.name,
|
||||||
|
status: webhook.status,
|
||||||
|
topic: webhook.topic,
|
||||||
|
delivery_url: webhook.delivery_url,
|
||||||
|
secret: webhook.secret,
|
||||||
|
api_version: webhook.api_version,
|
||||||
|
date_created: webhook.date_created,
|
||||||
|
date_modified: webhook.date_modified,
|
||||||
|
// metadata: webhook.meta_data || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取站点的 webhooks 列表
|
||||||
|
async getWebhooks(params: UnifiedSearchParamsDTO): Promise<UnifiedWebhookPaginationDTO> {
|
||||||
|
try {
|
||||||
|
const result = await this.wpService.getWebhooks(this.site, params);
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: (result.items as WooWebhook[]).map(this.mapWebhook),
|
||||||
|
total: result.total,
|
||||||
|
page: Number(params.page || 1),
|
||||||
|
per_page: Number(params.per_page || 20),
|
||||||
|
totalPages: result.totalPages,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to get webhooks: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有webhooks
|
||||||
|
async getAllWebhooks(params?: UnifiedSearchParamsDTO): Promise<UnifiedWebhookDTO[]> {
|
||||||
|
try {
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const webhooks = await this.wpService.sdkGetAll(api, 'webhooks', params);
|
||||||
|
return webhooks.map((webhook: any) => this.mapWebhook(webhook));
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to get all webhooks: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取单个 webhook 详情
|
||||||
|
async getWebhook(id: string | number): Promise<UnifiedWebhookDTO> {
|
||||||
|
try {
|
||||||
|
const result = await this.wpService.getWebhook(this.site, id);
|
||||||
|
return this.mapWebhook(result as WooWebhook);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to get webhook: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的 webhook
|
||||||
|
async createWebhook(data: CreateWebhookDTO): Promise<UnifiedWebhookDTO> {
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
name: data.name,
|
||||||
|
status: 'active', // 默认状态为活跃
|
||||||
|
topic: data.topic,
|
||||||
|
delivery_url: data.delivery_url,
|
||||||
|
secret: data.secret,
|
||||||
|
api_version: data.api_version || 'wp/v2',
|
||||||
|
};
|
||||||
|
const result = await this.wpService.createWebhook(this.site, params);
|
||||||
|
return this.mapWebhook(result as WooWebhook);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to create webhook: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新现有的 webhook
|
||||||
|
async updateWebhook(id: string | number, data: UpdateWebhookDTO): Promise<UnifiedWebhookDTO> {
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
...(data.name ? { name: data.name } : {}),
|
||||||
|
...(data.status ? { status: data.status } : {}),
|
||||||
|
...(data.topic ? { topic: data.topic } : {}),
|
||||||
|
...(data.delivery_url ? { delivery_url: data.delivery_url } : {}),
|
||||||
|
...(data.secret ? { secret: data.secret } : {}),
|
||||||
|
...(data.api_version ? { api_version: data.api_version } : {}),
|
||||||
|
};
|
||||||
|
const result = await this.wpService.updateWebhook(this.site, id, params);
|
||||||
|
return this.mapWebhook(result as WooWebhook);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to update webhook: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除指定的 webhook
|
||||||
|
async deleteWebhook(id: string | number): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await this.wpService.deleteWebhook(this.site, id);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to delete webhook: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLinks(): Promise<Array<{title: string, url: string}>> {
|
||||||
|
const baseUrl = this.site.apiUrl;
|
||||||
|
const links = [
|
||||||
|
{ title: '访问网站', url: baseUrl },
|
||||||
|
{ title: '管理后台', url: `${baseUrl}/wp-admin/` },
|
||||||
|
{ title: '订单管理', url: `${baseUrl}/wp-admin/edit.php?post_type=shop_order` },
|
||||||
|
{ title: '产品管理', url: `${baseUrl}/wp-admin/edit.php?post_type=product` },
|
||||||
|
{ title: '客户管理', url: `${baseUrl}/wp-admin/users.php` },
|
||||||
|
{ title: '插件管理', url: `${baseUrl}/wp-admin/plugins.php` },
|
||||||
|
{ title: '主题管理', url: `${baseUrl}/wp-admin/themes.php` },
|
||||||
|
{ title: 'WooCommerce设置', url: `${baseUrl}/wp-admin/admin.php?page=wc-settings` },
|
||||||
|
{ title: 'WooCommerce报告', url: `${baseUrl}/wp-admin/admin.php?page=wc-reports` },
|
||||||
|
];
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
createMedia(file: any): Promise<UnifiedMediaDTO> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
batchProcessOrders?(data: { create?: any[]; update?: any[]; delete?: Array<string | number>; }): Promise<any> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
batchProcessCustomers?(data: { create?: any[]; update?: any[]; delete?: Array<string | number>; }): Promise<any> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private mapProductSearchParams(params: UnifiedSearchParamsDTO): Partial<WooProductSearchParams> {
|
||||||
|
const page = Number(params.page ?? 1);
|
||||||
|
const per_page = Number( params.per_page ?? 20);
|
||||||
|
const where = params.where && typeof params.where === 'object' ? params.where : {};
|
||||||
|
|
||||||
|
const mapped: any = {
|
||||||
|
...(params.search ? { search: params.search } : {}),
|
||||||
|
...(where.status ? { status: where.status } : {}),
|
||||||
|
page,
|
||||||
|
per_page,
|
||||||
|
};
|
||||||
|
|
||||||
|
const toArray = (value: any): any[] => {
|
||||||
|
if (Array.isArray(value)) return value;
|
||||||
|
if (value === undefined || value === null) return [];
|
||||||
|
return String(value).split(',').map(v => v.trim()).filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (where.search_fields ?? where.searchFields) mapped.search_fields = toArray(where.search_fields ?? where.searchFields);
|
||||||
|
if (where.after ?? where.date_created_after ?? where.created_after) mapped.after = String(where.after ?? where.date_created_after ?? where.created_after);
|
||||||
|
if (where.before ?? where.date_created_before ?? where.created_before) mapped.before = String(where.before ?? where.date_created_before ?? where.created_before);
|
||||||
|
if (where.modified_after ?? where.date_modified_after) mapped.modified_after = String(where.modified_after ?? where.date_modified_after);
|
||||||
|
if (where.modified_before ?? where.date_modified_before) mapped.modified_before = String(where.modified_before ?? where.date_modified_before);
|
||||||
|
if (where.dates_are_gmt ?? where.datesAreGmt) mapped.dates_are_gmt = Boolean(where.dates_are_gmt ?? where.datesAreGmt);
|
||||||
|
if (where.exclude ?? where.exclude_ids ?? where.excludedIds) mapped.exclude = toArray(where.exclude ?? where.exclude_ids ?? where.excludedIds);
|
||||||
|
if (where.include ?? where.ids) mapped.include = toArray(where.include ?? where.ids);
|
||||||
|
if (where.offset !== undefined) mapped.offset = Number(where.offset);
|
||||||
|
if (where.parent ?? where.parentId) mapped.parent = toArray(where.parent ?? where.parentId);
|
||||||
|
if (where.parent_exclude ?? where.parentExclude) mapped.parent_exclude = toArray(where.parent_exclude ?? where.parentExclude);
|
||||||
|
if (where.slug) mapped.slug = String(where.slug);
|
||||||
|
if (!mapped.status && (where.status || where.include_status || where.exclude_status || where.includeStatus || where.excludeStatus)) {
|
||||||
|
if (where.include_status ?? where.includeStatus) mapped.include_status = String(where.include_status ?? where.includeStatus);
|
||||||
|
if (where.exclude_status ?? where.excludeStatus) mapped.exclude_status = String(where.exclude_status ?? where.excludeStatus);
|
||||||
|
if (where.status) mapped.status = String(where.status);
|
||||||
|
}
|
||||||
|
if (where.type) mapped.type = String(where.type);
|
||||||
|
if (where.include_types ?? where.includeTypes) mapped.include_types = String(where.include_types ?? where.includeTypes);
|
||||||
|
if (where.exclude_types ?? where.excludeTypes) mapped.exclude_types = String(where.exclude_types ?? where.excludeTypes);
|
||||||
|
if (where.sku) mapped.sku = String(where.sku);
|
||||||
|
if (where.featured ?? where.isFeatured) mapped.featured = Boolean(where.featured ?? where.isFeatured);
|
||||||
|
if (where.category ?? where.categoryId) mapped.category = String(where.category ?? where.categoryId);
|
||||||
|
if (where.tag ?? where.tagId) mapped.tag = String(where.tag ?? where.tagId);
|
||||||
|
if (where.shipping_class ?? where.shippingClass) mapped.shipping_class = String(where.shipping_class ?? where.shippingClass);
|
||||||
|
if (where.attribute ?? where.attributeName) mapped.attribute = String(where.attribute ?? where.attributeName);
|
||||||
|
if (where.attribute_term ?? where.attributeTermId ?? where.attributeTerm) mapped.attribute_term = String(where.attribute_term ?? where.attributeTermId ?? where.attributeTerm);
|
||||||
|
if (where.tax_class ?? where.taxClass) mapped.tax_class = String(where.tax_class ?? where.taxClass);
|
||||||
|
if (where.on_sale ?? where.onSale) mapped.on_sale = Boolean(where.on_sale ?? where.onSale);
|
||||||
|
if (where.min_price ?? where.minPrice) mapped.min_price = String(where.min_price ?? where.minPrice);
|
||||||
|
if (where.max_price ?? where.maxPrice) mapped.max_price = String(where.max_price ?? where.maxPrice);
|
||||||
|
if (where.stock_status ?? where.stockStatus) mapped.stock_status = String(where.stock_status ?? where.stockStatus);
|
||||||
|
if (where.virtual !== undefined) mapped.virtual = Boolean(where.virtual);
|
||||||
|
if (where.downloadable !== undefined) mapped.downloadable = Boolean(where.downloadable);
|
||||||
|
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapOrderSearchParams(params: UnifiedSearchParamsDTO): Partial<WooOrderSearchParams> {
|
||||||
|
// 计算分页参数
|
||||||
|
const page = Number(params.page ?? 1);
|
||||||
|
const per_page = Number( params.per_page ?? 20);
|
||||||
|
// 解析排序参数 支持从 order 对象推导
|
||||||
|
const where = params.where && typeof params.where === 'object' ? params.where : {};
|
||||||
|
|
||||||
|
// if (params.orderBy && typeof params.orderBy === 'object') {
|
||||||
|
// }
|
||||||
|
const mapped: any = {
|
||||||
|
...(params.search ? { search: params.search } : {}),
|
||||||
|
// ...(orderBy ? { orderBy } : {}),
|
||||||
|
page,
|
||||||
|
per_page,
|
||||||
|
};
|
||||||
|
|
||||||
|
const toArray = (value: any): any[] => {
|
||||||
|
if (Array.isArray(value)) return value;
|
||||||
|
if (value === undefined || value === null) return [];
|
||||||
|
return String(value).split(',').map(v => v.trim()).filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toNumber = (value: any): number | undefined => {
|
||||||
|
if (value === undefined || value === null || value === '') return undefined;
|
||||||
|
const n = Number(value);
|
||||||
|
return Number.isFinite(n) ? n : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 时间过滤参数
|
||||||
|
if (where.after ?? where.date_created_after ?? where.created_after) mapped.after = String(where.after ?? where.date_created_after ?? where.created_after);
|
||||||
|
if (where.before ?? where.date_created_before ?? where.created_before) mapped.before = String(where.before ?? where.date_created_before ?? where.created_before);
|
||||||
|
if (where.modified_after ?? where.date_modified_after) mapped.modified_after = String(where.modified_after ?? where.date_modified_after);
|
||||||
|
if (where.modified_before ?? where.date_modified_before) mapped.modified_before = String(where.modified_before ?? where.date_modified_before);
|
||||||
|
if (where.dates_are_gmt ?? where.datesAreGmt) mapped.dates_are_gmt = Boolean(where.dates_are_gmt ?? where.datesAreGmt);
|
||||||
|
|
||||||
|
// 集合过滤参数
|
||||||
|
if (where.exclude) mapped.exclude = toArray(where.exclude);
|
||||||
|
if (where.include) mapped.include = toArray(where.include);
|
||||||
|
if (where.ids) mapped.include = toArray(where.ids);
|
||||||
|
if (toNumber(where.offset) !== undefined) mapped.offset = Number(where.offset);
|
||||||
|
if (where.parent ?? where.parentId) mapped.parent = toArray(where.parent ?? where.parentId);
|
||||||
|
if (where.parent_exclude ?? where.parentExclude) mapped.parent_exclude = toArray(where.parent_exclude ?? where.parentExclude);
|
||||||
|
|
||||||
|
// 状态过滤 参数支持数组或逗号分隔字符串
|
||||||
|
const statusSource = where.status;
|
||||||
|
if (statusSource !== undefined) {
|
||||||
|
mapped.status = Array.isArray(statusSource)
|
||||||
|
? statusSource.map(s => String(s))
|
||||||
|
: String(statusSource).split(',').map(s => s.trim()).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户与产品过滤
|
||||||
|
const customerVal = where.customer ?? where.customer_id;
|
||||||
|
const productVal = where.product ?? where.product_id;
|
||||||
|
const dpVal = where.dp;
|
||||||
|
if (toNumber(customerVal) !== undefined) mapped.customer = Number(customerVal);
|
||||||
|
if (toNumber(productVal) !== undefined) mapped.product = Number(productVal);
|
||||||
|
if (toNumber(dpVal) !== undefined) mapped.dp = Number(dpVal);
|
||||||
|
|
||||||
|
// 创建来源过滤 支持逗号分隔
|
||||||
|
const createdViaVal = where.created_via;
|
||||||
|
if (createdViaVal !== undefined) mapped.created_via = Array.isArray(createdViaVal)
|
||||||
|
? createdViaVal.join(',')
|
||||||
|
: String(createdViaVal);
|
||||||
|
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapCustomerSearchParams(params: UnifiedSearchParamsDTO): Record<string, any> {
|
||||||
|
const page = Number(params.page ?? 1);
|
||||||
|
const per_page = Number(params.per_page ?? 20);
|
||||||
|
const where = params.where && typeof params.where === 'object' ? params.where : {};
|
||||||
|
|
||||||
|
|
||||||
|
const mapped: any = {
|
||||||
|
...(params.search ? { search: params.search } : {}),
|
||||||
|
page,
|
||||||
|
per_page,
|
||||||
|
};
|
||||||
|
|
||||||
|
const toArray = (value: any): any[] => {
|
||||||
|
if (Array.isArray(value)) return value;
|
||||||
|
if (value === undefined || value === null) return [];
|
||||||
|
return String(value).split(',').map(v => v.trim()).filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toNumber = (value: any): number | undefined => {
|
||||||
|
if (value === undefined || value === null || value === '') return undefined;
|
||||||
|
const n = Number(value);
|
||||||
|
return Number.isFinite(n) ? n : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (where.exclude) mapped.exclude = toArray(where.exclude);
|
||||||
|
if (where.include) mapped.include = toArray(where.include);
|
||||||
|
if (where.ids) mapped.include = toArray(where.ids);
|
||||||
|
if (toNumber(where.offset) !== undefined) mapped.offset = Number(where.offset);
|
||||||
|
|
||||||
|
if (where.email) mapped.email = String(where.email);
|
||||||
|
const roleSource = where.role;
|
||||||
|
if (roleSource !== undefined) mapped.role = String(roleSource);
|
||||||
|
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapProduct(item: WooProduct): UnifiedProductDTO {
|
||||||
|
// 将 WooCommerce 产品数据映射为统一产品DTO
|
||||||
|
// 保留常用字段与时间信息以便前端统一展示
|
||||||
|
// https://woocommerce.github.io/woocommerce-rest-api-docs/?javascript#product-properties
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
date_created: item.date_created,
|
||||||
|
date_modified: item.date_modified,
|
||||||
|
type: item.type, // simple grouped external variable
|
||||||
|
status: item.status, // draft pending private publish
|
||||||
|
sku: item.sku,
|
||||||
|
name: item.name,
|
||||||
|
//价格
|
||||||
|
regular_price: item.regular_price,
|
||||||
|
sale_price: item.sale_price,
|
||||||
|
price: item.price,
|
||||||
|
stock_status: item.stock_status,
|
||||||
|
stock_quantity: item.stock_quantity,
|
||||||
|
images: (item.images || []).map((img: any) => ({
|
||||||
|
id: img.id,
|
||||||
|
src: img.src,
|
||||||
|
name: img.name,
|
||||||
|
alt: img.alt,
|
||||||
|
})),
|
||||||
|
categories: (item.categories || []).map((c: any) => ({
|
||||||
|
id: c.id,
|
||||||
|
name: c.name,
|
||||||
|
})),
|
||||||
|
tags: (item.tags || []).map((t: any) => ({
|
||||||
|
id: t.id,
|
||||||
|
name: t.name,
|
||||||
|
})),
|
||||||
|
attributes: (item.attributes || []).map(attr => ({
|
||||||
|
id: attr.id,
|
||||||
|
name: attr.name || '',
|
||||||
|
position: attr.position,
|
||||||
|
visible: attr.visible,
|
||||||
|
variation: attr.variation,
|
||||||
|
options: attr.options || []
|
||||||
|
})),
|
||||||
|
variations: item.variations as any,
|
||||||
|
permalink: item.permalink,
|
||||||
|
raw: item,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private buildFullAddress(addr: any): string {
|
||||||
|
if (!addr) return '';
|
||||||
|
const name = addr.fullname || `${addr.first_name || ''} ${addr.last_name || ''}`.trim();
|
||||||
|
return [
|
||||||
|
name,
|
||||||
|
addr.company,
|
||||||
|
addr.address_1,
|
||||||
|
addr.address_2,
|
||||||
|
addr.city,
|
||||||
|
addr.state,
|
||||||
|
addr.postcode,
|
||||||
|
addr.country,
|
||||||
|
addr.phone
|
||||||
|
].filter(Boolean).join(', ');
|
||||||
|
}
|
||||||
|
private mapOrder(item: WooOrder): UnifiedOrderDTO {
|
||||||
|
// 将 WooCommerce 订单数据映射为统一订单DTO
|
||||||
|
// 包含账单地址与收货地址以及创建与更新时间
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
number: item.number,
|
||||||
|
status: item.status,
|
||||||
|
currency: item.currency,
|
||||||
|
total: item.total,
|
||||||
|
customer_id: item.customer_id,
|
||||||
|
customer_name: `${item.billing?.first_name || ''} ${
|
||||||
|
item.billing?.last_name || ''
|
||||||
|
}`.trim(),
|
||||||
|
refunds: item.refunds?.map?.(refund => ({
|
||||||
|
id: refund.id,
|
||||||
|
reason: refund.reason,
|
||||||
|
total: refund.total,
|
||||||
|
})),
|
||||||
|
email: item.billing?.email || '',
|
||||||
|
line_items: (item.line_items as any[]).map(li => ({
|
||||||
|
...li,
|
||||||
|
productId: li.product_id,
|
||||||
|
})),
|
||||||
|
|
||||||
|
billing: item.billing,
|
||||||
|
shipping: item.shipping,
|
||||||
|
billing_full_address: this.buildFullAddress(item.billing),
|
||||||
|
shipping_full_address: this.buildFullAddress(item.shipping),
|
||||||
|
payment_method: item.payment_method_title,
|
||||||
|
date_created: item.date_created,
|
||||||
|
date_modified: item.date_modified,
|
||||||
|
shipping_lines: item.shipping_lines,
|
||||||
|
fee_lines: item.fee_lines,
|
||||||
|
coupon_lines: item.coupon_lines,
|
||||||
|
raw: item,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapSubscription(item: WooSubscription): UnifiedSubscriptionDTO {
|
||||||
|
// 将 WooCommerce 订阅数据映射为统一订阅DTO
|
||||||
|
// 若缺少创建时间则回退为开始时间
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
status: item.status,
|
||||||
|
customer_id: item.customer_id,
|
||||||
|
billing_period: item.billing_period,
|
||||||
|
billing_interval: item.billing_interval,
|
||||||
|
date_created: item.date_created ?? item.start_date,
|
||||||
|
date_modified: item.date_modified,
|
||||||
|
start_date: item.start_date,
|
||||||
|
next_payment_date: item.next_payment_date,
|
||||||
|
line_items: item.line_items,
|
||||||
|
raw: item,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapMedia(item: WpMedia): UnifiedMediaDTO {
|
||||||
|
// 将 WordPress 媒体数据映射为统一媒体DTO
|
||||||
|
// 兼容不同字段命名的时间信息
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
title:
|
||||||
|
typeof item.title === 'string'
|
||||||
|
? item.title
|
||||||
|
: item.title?.rendered || '',
|
||||||
|
media_type: item.media_type,
|
||||||
|
mime_type: item.mime_type,
|
||||||
|
source_url: item.source_url,
|
||||||
|
date_created: item.date_created ?? item.date,
|
||||||
|
date_modified: item.date_modified ?? item.modified,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProducts(
|
||||||
|
params: UnifiedSearchParamsDTO
|
||||||
|
): Promise<UnifiedPaginationDTO<UnifiedProductDTO>> {
|
||||||
|
// 获取产品列表并使用统一分页结构返回
|
||||||
|
const requestParams = this.mapProductSearchParams(params);
|
||||||
|
const { items, total, totalPages, page, per_page } =
|
||||||
|
await this.wpService.fetchResourcePaged<any>(
|
||||||
|
this.site,
|
||||||
|
'products',
|
||||||
|
requestParams
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
items: items.map(this.mapProduct),
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
page,
|
||||||
|
per_page,
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]> {
|
||||||
|
// 使用sdkGetAll获取所有产品数据,不受分页限制
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const products = await this.wpService.sdkGetAll(api, 'products', params);
|
||||||
|
return products.map((product: any) => this.mapProduct(product));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProduct(id: string | number): Promise<UnifiedProductDTO> {
|
||||||
|
// 获取单个产品详情并映射为统一产品DTO
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const res = await api.get(`products/${id}`);
|
||||||
|
return this.mapProduct(res.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
||||||
|
// 创建产品并返回统一产品DTO
|
||||||
|
const res = await this.wpService.createProduct(this.site, data);
|
||||||
|
return this.mapProduct(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateProduct(id: string | number, data: Partial<UnifiedProductDTO>): Promise<boolean> {
|
||||||
|
// 更新产品并返回统一产品DTO
|
||||||
|
const res = await this.wpService.updateProduct(this.site, String(id), data as any);
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateVariation(productId: string | number, variationId: string | number, data: any): Promise<any> {
|
||||||
|
// 更新变体信息并返回结果
|
||||||
|
const res = await this.wpService.updateVariation(this.site, String(productId), String(variationId), data);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrderNotes(orderId: string | number): Promise<any[]> {
|
||||||
|
// 获取订单备注列表
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const res = await api.get(`orders/${orderId}/notes`);
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOrderNote(orderId: string | number, data: any): Promise<any> {
|
||||||
|
// 创建订单备注
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const res = await api.post(`orders/${orderId}/notes`, data);
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteProduct(id: string | number): Promise<boolean> {
|
||||||
|
// 删除产品
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
try {
|
||||||
|
await api.delete(`products/${id}`, { force: true });
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async batchProcessProducts(
|
||||||
|
data: { create?: any[]; update?: any[]; delete?: Array<string | number> }
|
||||||
|
): Promise<any> {
|
||||||
|
// 批量处理产品增删改
|
||||||
|
return await this.wpService.batchProcessProducts(this.site, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrders(
|
||||||
|
params: UnifiedSearchParamsDTO
|
||||||
|
): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>> {
|
||||||
|
const requestParams = this.mapOrderSearchParams(params);
|
||||||
|
const { items, total, totalPages, page, per_page } =
|
||||||
|
await this.wpService.fetchResourcePaged<any>(this.site, 'orders', requestParams);
|
||||||
|
return {
|
||||||
|
items: items.map(this.mapOrder),
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
page,
|
||||||
|
per_page,
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrder(id: string | number): Promise<UnifiedOrderDTO> {
|
||||||
|
// 获取单个订单详情
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const res = await api.get(`orders/${id}`);
|
||||||
|
return this.mapOrder(res.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
|
||||||
|
// 使用sdkGetAll获取所有订单数据,不受分页限制
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const orders = await this.wpService.sdkGetAll(api, 'orders', params);
|
||||||
|
return orders.map((order: any) => this.mapOrder(order));
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO> {
|
||||||
|
// 创建订单并返回统一订单DTO
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const res = await api.post('orders', data);
|
||||||
|
return this.mapOrder(res.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateOrder(id: string | number, data: Partial<UnifiedOrderDTO>): Promise<boolean> {
|
||||||
|
// 更新订单并返回布尔结果
|
||||||
|
return await this.wpService.updateOrder(this.site, String(id), data as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteOrder(id: string | number): Promise<boolean> {
|
||||||
|
// 删除订单
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
await api.delete(`orders/${id}`, { force: true });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async shipOrder(orderId: string | number, data: {
|
||||||
|
tracking_number?: string;
|
||||||
|
shipping_provider?: string;
|
||||||
|
shipping_method?: string;
|
||||||
|
items?: Array<{
|
||||||
|
order_item_id: number;
|
||||||
|
quantity: number;
|
||||||
|
}>;
|
||||||
|
}): Promise<any> {
|
||||||
|
throw new Error('暂无实现')
|
||||||
|
// 订单发货
|
||||||
|
// const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// // 更新订单状态为已完成
|
||||||
|
// await api.put(`orders/${orderId}`, { status: 'completed' });
|
||||||
|
|
||||||
|
// // 如果提供了物流信息,添加到订单备注
|
||||||
|
// if (data.tracking_number || data.shipping_provider) {
|
||||||
|
// const note = `订单已发货${data.tracking_number ? `,物流单号:${data.tracking_number}` : ''}${data.shipping_provider ? `,物流公司:${data.shipping_provider}` : ''}`;
|
||||||
|
// await api.post(`orders/${orderId}/notes`, { note, customer_note: true });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// success: true,
|
||||||
|
// order_id: orderId,
|
||||||
|
// shipment_id: `shipment_${orderId}_${Date.now()}`,
|
||||||
|
// tracking_number: data.tracking_number,
|
||||||
|
// shipping_provider: data.shipping_provider,
|
||||||
|
// shipped_at: new Date().toISOString()
|
||||||
|
// };
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new Error(`发货失败: ${error.message}`);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelShipOrder(orderId: string | number, data: {
|
||||||
|
reason?: string;
|
||||||
|
shipment_id?: string;
|
||||||
|
}): Promise<any> {
|
||||||
|
throw new Error('暂未实现')
|
||||||
|
// 取消订单发货
|
||||||
|
// const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// // 将订单状态改回处理中
|
||||||
|
// await api.put(`orders/${orderId}`, { status: 'processing' });
|
||||||
|
|
||||||
|
// // 添加取消发货的备注
|
||||||
|
// const note = `订单发货已取消${data.reason ? `,原因:${data.reason}` : ''}`;
|
||||||
|
// await api.post(`orders/${orderId}/notes`, { note, customer_note: true });
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// success: true,
|
||||||
|
// order_id: orderId,
|
||||||
|
// shipment_id: data.shipment_id,
|
||||||
|
// reason: data.reason,
|
||||||
|
// cancelled_at: new Date().toISOString()
|
||||||
|
// };
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new Error(`取消发货失败: ${error.message}`);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSubscriptions(
|
||||||
|
params: UnifiedSearchParamsDTO
|
||||||
|
): Promise<UnifiedPaginationDTO<UnifiedSubscriptionDTO>> {
|
||||||
|
// 获取订阅列表并映射为统一订阅DTO集合
|
||||||
|
const { items, total, totalPages, page, per_page } =
|
||||||
|
await this.wpService.fetchResourcePaged<any>(
|
||||||
|
this.site,
|
||||||
|
'subscriptions',
|
||||||
|
params
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
items: items.map(this.mapSubscription),
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
page,
|
||||||
|
per_page,
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise<UnifiedSubscriptionDTO[]> {
|
||||||
|
// 使用sdkGetAll获取所有订阅数据,不受分页限制
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const subscriptions = await this.wpService.sdkGetAll(api, 'subscriptions', params);
|
||||||
|
return subscriptions.map((subscription: any) => this.mapSubscription(subscription));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMedia(
|
||||||
|
params: UnifiedSearchParamsDTO
|
||||||
|
): Promise<UnifiedPaginationDTO<UnifiedMediaDTO>> {
|
||||||
|
// 获取媒体列表并映射为统一媒体DTO集合
|
||||||
|
const { items, total, totalPages, page, per_page } = await this.wpService.fetchMediaPaged(
|
||||||
|
this.site,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
items: items.map(this.mapMedia.bind(this)),
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
page,
|
||||||
|
per_page,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllMedia(params?: UnifiedSearchParamsDTO): Promise<UnifiedMediaDTO[]> {
|
||||||
|
// 使用sdkGetAll获取所有媒体数据,不受分页限制
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const media = await this.wpService.sdkGetAll(api, 'media', params);
|
||||||
|
return media.map((mediaItem: any) => this.mapMedia(mediaItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapReview(item: any): UnifiedReviewDTO & {raw: any} {
|
||||||
|
// 将 WooCommerce 评论数据映射为统一评论DTO
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
product_id: item.product_id,
|
||||||
|
author: item.reviewer,
|
||||||
|
email: item.reviewer_email,
|
||||||
|
content: item.review,
|
||||||
|
rating: item.rating,
|
||||||
|
status: item.status,
|
||||||
|
date_created: item.date_created,
|
||||||
|
raw: item
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getReviews(
|
||||||
|
params: UnifiedSearchParamsDTO
|
||||||
|
): Promise<UnifiedReviewPaginationDTO> {
|
||||||
|
// 获取评论列表并使用统一分页结构返回
|
||||||
|
const requestParams = this.mapProductSearchParams(params);
|
||||||
|
const { items, total, totalPages, page, per_page } =
|
||||||
|
await this.wpService.fetchResourcePaged<any>(
|
||||||
|
this.site,
|
||||||
|
'products/reviews',
|
||||||
|
requestParams
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
items: items.map(this.mapReview.bind(this)),
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
page,
|
||||||
|
per_page,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllReviews(params?: UnifiedSearchParamsDTO): Promise<UnifiedReviewDTO[]> {
|
||||||
|
// 使用sdkGetAll获取所有评论数据,不受分页限制
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const reviews = await this.wpService.sdkGetAll(api, 'products/reviews', params);
|
||||||
|
return reviews.map((review: any) => this.mapReview(review));
|
||||||
|
}
|
||||||
|
|
||||||
|
async createReview(data: any): Promise<UnifiedReviewDTO> {
|
||||||
|
const res = await this.wpService.createReview(this.site, data);
|
||||||
|
return this.mapReview(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateReview(id: number, data: any): Promise<UnifiedReviewDTO> {
|
||||||
|
const res = await this.wpService.updateReview(this.site, id, data);
|
||||||
|
return this.mapReview(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteReview(id: number): Promise<boolean> {
|
||||||
|
return await this.wpService.deleteReview(this.site, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteMedia(id: string | number): Promise<boolean> {
|
||||||
|
// 删除媒体资源
|
||||||
|
await this.wpService.deleteMedia(Number(this.site.id), Number(id), true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateMedia(id: string | number, data: any): Promise<any> {
|
||||||
|
// 更新媒体信息
|
||||||
|
return await this.wpService.updateMedia(Number(this.site.id), Number(id), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async convertMediaToWebp(ids: Array<string | number>): Promise<{ converted: any[]; failed: any[] }> {
|
||||||
|
// 函数说明 调用服务层将站点的指定媒体批量转换为 webp 并上传
|
||||||
|
const result = await this.wpService.convertMediaToWebp(Number(this.site.id), ids);
|
||||||
|
return result as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapCustomer(item: WooCustomer): UnifiedCustomerDTO {
|
||||||
|
// 将 WooCommerce 客户数据映射为统一客户DTO
|
||||||
|
// 包含基础信息地址信息与时间信息
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
avatar: item.avatar_url,
|
||||||
|
email: item.email,
|
||||||
|
orders: Number(item.orders?? 0),
|
||||||
|
total_spend: Number(item.total_spent ?? 0),
|
||||||
|
first_name: item.first_name,
|
||||||
|
last_name: item.last_name,
|
||||||
|
username: item.username,
|
||||||
|
phone: item.billing?.phone || item.shipping?.phone,
|
||||||
|
billing: item.billing,
|
||||||
|
shipping: item.shipping,
|
||||||
|
date_created: item.date_created,
|
||||||
|
date_modified: item.date_modified,
|
||||||
|
raw: item,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async getCustomers(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedCustomerDTO>> {
|
||||||
|
const requestParams = this.mapCustomerSearchParams(params);
|
||||||
|
const { items, total, totalPages, page, per_page } = await this.wpService.fetchResourcePaged<any>(
|
||||||
|
this.site,
|
||||||
|
'customers',
|
||||||
|
requestParams
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
items: items.map((i: any) => this.mapCustomer(i)),
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
page,
|
||||||
|
per_page,
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllCustomers(params?: UnifiedSearchParamsDTO): Promise<UnifiedCustomerDTO[]> {
|
||||||
|
// 使用sdkGetAll获取所有客户数据,不受分页限制
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const customers = await this.wpService.sdkGetAll(api, 'customers', params);
|
||||||
|
return customers.map((customer: any) => this.mapCustomer(customer));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCustomer(id: string | number): Promise<UnifiedCustomerDTO> {
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const res = await api.get(`customers/${id}`);
|
||||||
|
return this.mapCustomer(res.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCustomer(data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const res = await api.post('customers', data);
|
||||||
|
return this.mapCustomer(res.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCustomer(id: string | number, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
const res = await api.put(`customers/${id}`, data);
|
||||||
|
return this.mapCustomer(res.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteCustomer(id: string | number): Promise<boolean> {
|
||||||
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
|
await api.delete(`customers/${id}`, { force: true });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { MidwayConfig } from '@midwayjs/core';
|
import { MidwayConfig } from '@midwayjs/core';
|
||||||
|
import { join } from 'path';
|
||||||
import { Product } from '../entity/product.entity';
|
import { Product } from '../entity/product.entity';
|
||||||
import { Category } from '../entity/category.entity';
|
|
||||||
import { WpProduct } from '../entity/wp_product.entity';
|
|
||||||
import { Variation } from '../entity/variation.entity';
|
|
||||||
import { User } from '../entity/user.entity';
|
import { User } from '../entity/user.entity';
|
||||||
import { PurchaseOrder } from '../entity/purchase_order.entity';
|
import { PurchaseOrder } from '../entity/purchase_order.entity';
|
||||||
import { PurchaseOrderItem } from '../entity/purchase_order_item.entity';
|
import { PurchaseOrderItem } from '../entity/purchase_order_item.entity';
|
||||||
|
|
@ -11,12 +9,12 @@ import { StockPoint } from '../entity/stock_point.entity';
|
||||||
import { StockRecord } from '../entity/stock_record.entity';
|
import { StockRecord } from '../entity/stock_record.entity';
|
||||||
import { Order } from '../entity/order.entity';
|
import { Order } from '../entity/order.entity';
|
||||||
import { OrderItem } from '../entity/order_item.entity';
|
import { OrderItem } from '../entity/order_item.entity';
|
||||||
import { OrderCoupon } from '../entity/order_copon.entity';
|
import { OrderCoupon } from '../entity/order_coupon.entity';
|
||||||
import { OrderFee } from '../entity/order_fee.entity';
|
import { OrderFee } from '../entity/order_fee.entity';
|
||||||
import { OrderRefund } from '../entity/order_refund.entity';
|
import { OrderRefund } from '../entity/order_refund.entity';
|
||||||
import { OrderRefundItem } from '../entity/order_retund_item.entity';
|
import { OrderRefundItem } from '../entity/order_refund_item.entity';
|
||||||
import { OrderSale } from '../entity/order_sale.entity';
|
import { OrderSale } from '../entity/order_sale.entity';
|
||||||
import { OrderSaleOriginal } from '../entity/order_item_original.entity';
|
import { OrderItemOriginal } from '../entity/order_item_original.entity';
|
||||||
import { OrderShipping } from '../entity/order_shipping.entity';
|
import { OrderShipping } from '../entity/order_shipping.entity';
|
||||||
import { Service } from '../entity/service.entity';
|
import { Service } from '../entity/service.entity';
|
||||||
import { ShippingAddress } from '../entity/shipping_address.entity';
|
import { ShippingAddress } from '../entity/shipping_address.entity';
|
||||||
|
|
@ -26,14 +24,23 @@ import { Shipment } from '../entity/shipment.entity';
|
||||||
import { ShipmentItem } from '../entity/shipment_item.entity';
|
import { ShipmentItem } from '../entity/shipment_item.entity';
|
||||||
import { Transfer } from '../entity/transfer.entity';
|
import { Transfer } from '../entity/transfer.entity';
|
||||||
import { TransferItem } from '../entity/transfer_item.entity';
|
import { TransferItem } from '../entity/transfer_item.entity';
|
||||||
import { Strength } from '../entity/strength.entity';
|
|
||||||
import { Flavors } from '../entity/flavors.entity';
|
|
||||||
import { CustomerTag } from '../entity/customer_tag.entity';
|
import { CustomerTag } from '../entity/customer_tag.entity';
|
||||||
import { Customer } from '../entity/customer.entity';
|
import { Customer } from '../entity/customer.entity';
|
||||||
import { DeviceWhitelist } from '../entity/device_whitelist';
|
import { DeviceWhitelist } from '../entity/device_whitelist';
|
||||||
import { AuthCode } from '../entity/auth_code';
|
import { AuthCode } from '../entity/auth_code';
|
||||||
import { Subscription } from '../entity/subscription.entity';
|
import { Subscription } from '../entity/subscription.entity';
|
||||||
import { Site } from '../entity/site.entity';
|
import { Site } from '../entity/site.entity';
|
||||||
|
import { Dict } from '../entity/dict.entity';
|
||||||
|
import { DictItem } from '../entity/dict_item.entity';
|
||||||
|
import { Template } from '../entity/template.entity';
|
||||||
|
import { Area } from '../entity/area.entity';
|
||||||
|
import { ProductStockComponent } from '../entity/product_stock_component.entity';
|
||||||
|
import { ProductSiteSku } from '../entity/product_site_sku.entity';
|
||||||
|
import { CategoryAttribute } from '../entity/category_attribute.entity';
|
||||||
|
import { Category } from '../entity/category.entity';
|
||||||
|
import DictSeeder from '../db/seeds/dict.seeder';
|
||||||
|
import CategorySeeder from '../db/seeds/category.seeder';
|
||||||
|
import CategoryAttributeSeeder from '../db/seeds/category_attribute.seeder';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// use for cookie sign key, should change to your own and keep security
|
// use for cookie sign key, should change to your own and keep security
|
||||||
|
|
@ -42,11 +49,8 @@ export default {
|
||||||
default: {
|
default: {
|
||||||
entities: [
|
entities: [
|
||||||
Product,
|
Product,
|
||||||
Category,
|
ProductStockComponent,
|
||||||
Strength,
|
ProductSiteSku,
|
||||||
Flavors,
|
|
||||||
WpProduct,
|
|
||||||
Variation,
|
|
||||||
User,
|
User,
|
||||||
PurchaseOrder,
|
PurchaseOrder,
|
||||||
PurchaseOrderItem,
|
PurchaseOrderItem,
|
||||||
|
|
@ -60,7 +64,7 @@ export default {
|
||||||
OrderRefund,
|
OrderRefund,
|
||||||
OrderRefundItem,
|
OrderRefundItem,
|
||||||
OrderSale,
|
OrderSale,
|
||||||
OrderSaleOriginal,
|
OrderItemOriginal,
|
||||||
OrderShipment,
|
OrderShipment,
|
||||||
ShipmentItem,
|
ShipmentItem,
|
||||||
Shipment,
|
Shipment,
|
||||||
|
|
@ -76,15 +80,22 @@ export default {
|
||||||
AuthCode,
|
AuthCode,
|
||||||
Subscription,
|
Subscription,
|
||||||
Site,
|
Site,
|
||||||
|
Dict,
|
||||||
|
DictItem,
|
||||||
|
Template,
|
||||||
|
Area,
|
||||||
|
CategoryAttribute,
|
||||||
|
Category,
|
||||||
],
|
],
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
logging: false,
|
logging: false,
|
||||||
|
seeders: [DictSeeder, CategorySeeder, CategoryAttributeSeeder],
|
||||||
},
|
},
|
||||||
dataSource: {
|
dataSource: {
|
||||||
default: {
|
default: {
|
||||||
type: 'mysql',
|
type: 'mysql',
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: 3306,
|
port: 10014,
|
||||||
username: 'root',
|
username: 'root',
|
||||||
password: 'root',
|
password: 'root',
|
||||||
database: 'inventory',
|
database: 'inventory',
|
||||||
|
|
@ -95,23 +106,12 @@ export default {
|
||||||
// origin: '*', // 允许所有来源跨域请求
|
// origin: '*', // 允许所有来源跨域请求
|
||||||
// allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 允许的 HTTP 方法
|
// allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 允许的 HTTP 方法
|
||||||
// allowHeaders: ['Content-Type', 'Authorization'], // 允许的自定义请求头
|
// allowHeaders: ['Content-Type', 'Authorization'], // 允许的自定义请求头
|
||||||
// credentials: true, // 允许携带凭据(cookies等)
|
// credentials: true, // 允许携带凭据(cookies等)
|
||||||
// },
|
// },
|
||||||
// jwt: {
|
// jwt: {
|
||||||
// secret: 'YOONE2024!@abc',
|
// secret: 'YOONE2024!@abc',
|
||||||
// expiresIn: '7d',
|
// expiresIn: '7d',
|
||||||
// },
|
// },
|
||||||
// wpSite: [
|
|
||||||
// {
|
|
||||||
// id: '2',
|
|
||||||
// wpApiUrl: 'http://localhost:10004',
|
|
||||||
// consumerKey: 'ck_dc9e151e9048c8ed3e27f35ac79d2bf7d6840652',
|
|
||||||
// consumerSecret: 'cs_d05d625d7b0ac05c6d765671d8417f41d9477e38',
|
|
||||||
// siteName: 'Local',
|
|
||||||
// email: 'tom@yoonevape.com',
|
|
||||||
// emailPswd: '',
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
swagger: {
|
swagger: {
|
||||||
auth: {
|
auth: {
|
||||||
name: 'authorization',
|
name: 'authorization',
|
||||||
|
|
@ -128,5 +128,16 @@ export default {
|
||||||
user: 'info@canpouches.com',
|
user: 'info@canpouches.com',
|
||||||
pass: 'WWqQ4aZq4Jrm9uwz',
|
pass: 'WWqQ4aZq4Jrm9uwz',
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
upload: {
|
||||||
|
// mode: 'file', // 默认为file,即上传到服务器临时目录,可以配置为 stream
|
||||||
|
mode: 'file',
|
||||||
|
fileSize: '10mb', // 最大支持的文件大小,默认为 10mb
|
||||||
|
whitelist: ['.csv'], // 支持的文件后缀
|
||||||
|
tmpdir: join(__dirname, '../../tmp_uploads'),
|
||||||
|
cleanTimeout: 5 * 60 * 1000,
|
||||||
|
},
|
||||||
|
koa: {
|
||||||
|
queryParseMode: 'extended',
|
||||||
|
},
|
||||||
} as MidwayConfig;
|
} as MidwayConfig;
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,10 @@ export default {
|
||||||
dataSource: {
|
dataSource: {
|
||||||
default: {
|
default: {
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
|
port: "3306",
|
||||||
username: 'root',
|
username: 'root',
|
||||||
password: '12345678',
|
password: 'root',
|
||||||
|
database: 'inventory',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -25,46 +27,12 @@ export default {
|
||||||
origin: '*', // 允许所有来源跨域请求
|
origin: '*', // 允许所有来源跨域请求
|
||||||
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 允许的 HTTP 方法
|
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 允许的 HTTP 方法
|
||||||
allowHeaders: ['Content-Type', 'Authorization'], // 允许的自定义请求头
|
allowHeaders: ['Content-Type', 'Authorization'], // 允许的自定义请求头
|
||||||
credentials: true, // 允许携带凭据(cookies等)
|
credentials: true, // 允许携带凭据(cookies等)
|
||||||
},
|
},
|
||||||
jwt: {
|
jwt: {
|
||||||
secret: 'YOONE2024!@abc',
|
secret: 'YOONE2024!@abc',
|
||||||
expiresIn: '7d',
|
expiresIn: '7d',
|
||||||
},
|
},
|
||||||
wpSite: [
|
|
||||||
{
|
|
||||||
id: '-1',
|
|
||||||
siteName: 'Admin',
|
|
||||||
email: '2469687281@qq.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
wpApiUrl: 'http://t2-shop.local/',
|
|
||||||
consumerKey: 'ck_a369473a6451dbaec63d19cbfd74a074b2c5f742',
|
|
||||||
consumerSecret: 'cs_0946bbbeea1bfefff08a69e817ac62a48412df8c',
|
|
||||||
siteName: 'Local',
|
|
||||||
email: '2469687281@qq.com',
|
|
||||||
emailPswd: 'lulin91.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
wpApiUrl: 'http://t1-shop.local/',
|
|
||||||
consumerKey: 'ck_a369473a6451dbaec63d19cbfd74a074b2c5f742',
|
|
||||||
consumerSecret: 'cs_0946bbbeea1bfefff08a69e817ac62a48412df8c',
|
|
||||||
siteName: 'Local-test-2',
|
|
||||||
email: '2469687281@qq.com',
|
|
||||||
emailPswd: 'lulin91.',
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// id: '2',
|
|
||||||
// wpApiUrl: 'http://localhost:10004',
|
|
||||||
// consumerKey: 'ck_dc9e151e9048c8ed3e27f35ac79d2bf7d6840652',
|
|
||||||
// consumerSecret: 'cs_d05d625d7b0ac05c6d765671d8417f41d9477e38',
|
|
||||||
// siteName: 'Local',
|
|
||||||
// email: 'tom@yoonevape.com',
|
|
||||||
// emailPswd: 'lulin91.',
|
|
||||||
// },
|
|
||||||
],
|
|
||||||
freightcom: {
|
freightcom: {
|
||||||
url: 'https://customer-external-api.ssd-test.freightcom.com',
|
url: 'https://customer-external-api.ssd-test.freightcom.com',
|
||||||
token: '6zGj1qPTL1jIkbLmgaiYc6SwHUIXJ2t25htUF8uuFYiCg8ILCY6xnBEbvrX1p79L',
|
token: '6zGj1qPTL1jIkbLmgaiYc6SwHUIXJ2t25htUF8uuFYiCg8ILCY6xnBEbvrX1p79L',
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,15 @@ import * as validate from '@midwayjs/validate';
|
||||||
import * as info from '@midwayjs/info';
|
import * as info from '@midwayjs/info';
|
||||||
import * as orm from '@midwayjs/typeorm';
|
import * as orm from '@midwayjs/typeorm';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
// import { DefaultErrorFilter } from './filter/default.filter';
|
import { DefaultErrorFilter } from './filter/default.filter';
|
||||||
// import { NotFoundFilter } from './filter/notfound.filter';
|
import { NotFoundFilter } from './filter/notfound.filter';
|
||||||
import { ReportMiddleware } from './middleware/report.middleware';
|
import { ReportMiddleware } from './middleware/report.middleware';
|
||||||
|
import { QueryNormalizeMiddleware } from './middleware/query-normalize.middleware';
|
||||||
import * as swagger from '@midwayjs/swagger';
|
import * as swagger from '@midwayjs/swagger';
|
||||||
import * as crossDomain from '@midwayjs/cross-domain';
|
import * as crossDomain from '@midwayjs/cross-domain';
|
||||||
import * as cron from '@midwayjs/cron';
|
import * as cron from '@midwayjs/cron';
|
||||||
import * as jwt from '@midwayjs/jwt';
|
import * as jwt from '@midwayjs/jwt';
|
||||||
|
import * as upload from '@midwayjs/upload';
|
||||||
import { USER_KEY } from './decorator/user.decorator';
|
import { USER_KEY } from './decorator/user.decorator';
|
||||||
import { SiteService } from './service/site.service';
|
import { SiteService } from './service/site.service';
|
||||||
import { AuthMiddleware } from './middleware/auth.middleware';
|
import { AuthMiddleware } from './middleware/auth.middleware';
|
||||||
|
|
@ -33,6 +35,7 @@ import { AuthMiddleware } from './middleware/auth.middleware';
|
||||||
crossDomain,
|
crossDomain,
|
||||||
cron,
|
cron,
|
||||||
jwt,
|
jwt,
|
||||||
|
upload,
|
||||||
],
|
],
|
||||||
importConfigs: [join(__dirname, './config')],
|
importConfigs: [join(__dirname, './config')],
|
||||||
})
|
})
|
||||||
|
|
@ -51,9 +54,9 @@ export class MainConfiguration {
|
||||||
|
|
||||||
async onReady() {
|
async onReady() {
|
||||||
// add middleware
|
// add middleware
|
||||||
this.app.useMiddleware([ReportMiddleware, AuthMiddleware]);
|
this.app.useMiddleware([QueryNormalizeMiddleware, ReportMiddleware, AuthMiddleware]);
|
||||||
// add filter
|
// add filter
|
||||||
// this.app.useFilter([NotFoundFilter, DefaultErrorFilter]);
|
this.app.useFilter([NotFoundFilter, DefaultErrorFilter]);
|
||||||
|
|
||||||
this.decoratorService.registerParameterHandler(
|
this.decoratorService.registerParameterHandler(
|
||||||
USER_KEY,
|
USER_KEY,
|
||||||
|
|
@ -78,8 +81,5 @@ export class MainConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const sites = this.app.getConfig('wpSite') || [];
|
|
||||||
await this.siteService.syncFromConfig(sites);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
|
||||||
|
import { Body, Context, Controller, Del, Get, Inject, Param, Post, Put, Query } from '@midwayjs/core';
|
||||||
|
import {
|
||||||
|
ApiBearerAuth,
|
||||||
|
ApiBody,
|
||||||
|
ApiExtension,
|
||||||
|
ApiOkResponse,
|
||||||
|
ApiOperation,
|
||||||
|
ApiTags,
|
||||||
|
} from '@midwayjs/swagger';
|
||||||
|
import { AreaService } from '../service/area.service';
|
||||||
|
import { CreateAreaDTO, QueryAreaDTO, UpdateAreaDTO } from '../dto/area.dto';
|
||||||
|
import { errorResponse, successResponse } from '../utils/response.util';
|
||||||
|
import { Area } from '../entity/area.entity';
|
||||||
|
import * as countries from 'i18n-iso-countries';
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@ApiTags('Area')
|
||||||
|
@Controller('/area')
|
||||||
|
export class AreaController {
|
||||||
|
@Inject()
|
||||||
|
ctx: Context;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
areaService: AreaService;
|
||||||
|
|
||||||
|
@ApiOperation({ summary: '获取国家列表' })
|
||||||
|
@ApiOkResponse({ description: '国家列表' })
|
||||||
|
@Get('/countries')
|
||||||
|
async getCountries() {
|
||||||
|
try {
|
||||||
|
// 注册中文语言包
|
||||||
|
countries.registerLocale(require('i18n-iso-countries/langs/zh.json'));
|
||||||
|
// 获取所有国家的中文名称
|
||||||
|
const countryNames = countries.getNames('zh', { select: 'official' });
|
||||||
|
// 格式化为 { code, name } 的数组
|
||||||
|
const countryList = Object.keys(countryNames).map(code => ({
|
||||||
|
code,
|
||||||
|
name: countryNames[code],
|
||||||
|
}));
|
||||||
|
return successResponse(countryList, '查询成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: '创建区域' })
|
||||||
|
@ApiBody({ type: CreateAreaDTO })
|
||||||
|
@ApiOkResponse({ type: Area, description: '成功创建的区域' })
|
||||||
|
@Post('/')
|
||||||
|
async createArea(@Body() area: CreateAreaDTO) {
|
||||||
|
try {
|
||||||
|
const newArea = await this.areaService.createArea(area);
|
||||||
|
return successResponse(newArea, '创建成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: '更新区域' })
|
||||||
|
@ApiBody({ type: UpdateAreaDTO })
|
||||||
|
@ApiOkResponse({ type: Area, description: '成功更新的区域' })
|
||||||
|
@Put('/:id')
|
||||||
|
async updateArea(@Param('id') id: number, @Body() area: UpdateAreaDTO) {
|
||||||
|
try {
|
||||||
|
const updatedArea = await this.areaService.updateArea(id, area);
|
||||||
|
return successResponse(updatedArea, '更新成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: '删除区域' })
|
||||||
|
@ApiOkResponse({ description: '删除成功' })
|
||||||
|
@Del('/:id')
|
||||||
|
async deleteArea(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
await this.areaService.deleteArea(id);
|
||||||
|
return successResponse(null, '删除成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: '获取区域列表(分页)' })
|
||||||
|
@ApiOkResponse({ type: [Area], description: '区域列表' })
|
||||||
|
@ApiExtension('x-pagination', { currentPage: 1, pageSize: 10, total: 100 })
|
||||||
|
@Get('/')
|
||||||
|
async getAreaList(@Query() query: QueryAreaDTO) {
|
||||||
|
try {
|
||||||
|
const { list, total } = await this.areaService.getAreaList(query);
|
||||||
|
return successResponse({ list, total }, '查询成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: '根据ID获取区域详情' })
|
||||||
|
@ApiOkResponse({ type: Area, description: '区域详情' })
|
||||||
|
@Get('/:id')
|
||||||
|
async getAreaById(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
const area = await this.areaService.getAreaById(id);
|
||||||
|
if (!area) {
|
||||||
|
return errorResponse('区域不存在');
|
||||||
|
}
|
||||||
|
return successResponse(area, '查询成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
import { Controller, Get, Post, Put, Del, Body, Query, Inject, Param } from '@midwayjs/core';
|
||||||
|
import { CategoryService } from '../service/category.service';
|
||||||
|
import { successResponse, errorResponse } from '../utils/response.util';
|
||||||
|
import { ApiOkResponse } from '@midwayjs/swagger';
|
||||||
|
|
||||||
|
@Controller('/category')
|
||||||
|
export class CategoryController {
|
||||||
|
@Inject()
|
||||||
|
categoryService: CategoryService;
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Get('/all')
|
||||||
|
async getAll() {
|
||||||
|
try {
|
||||||
|
const data = await this.categoryService.getAll();
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Get('/')
|
||||||
|
async getList(@Query('current') current = 1, @Query('pageSize') pageSize = 10, @Query('name') name?: string) {
|
||||||
|
try {
|
||||||
|
const data = await this.categoryService.getList({ current, pageSize }, name);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Post('/')
|
||||||
|
async create(@Body() body: any) {
|
||||||
|
try {
|
||||||
|
const data = await this.categoryService.create(body);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Put('/:id')
|
||||||
|
async update(@Param('id') id: number, @Body() body: any) {
|
||||||
|
try {
|
||||||
|
const data = await this.categoryService.update(id, body);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Del('/:id')
|
||||||
|
async delete(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
await this.categoryService.delete(id);
|
||||||
|
return successResponse(null);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Get('/attribute/:categoryId')
|
||||||
|
async getCategoryAttributes(@Param('categoryId') categoryId: number) {
|
||||||
|
try {
|
||||||
|
const data = await this.categoryService.getCategoryAttributes(categoryId);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Post('/attribute')
|
||||||
|
async createCategoryAttribute(@Body() body: { categoryId: number, attributeDictIds: number[] }) {
|
||||||
|
try {
|
||||||
|
const data = await this.categoryService.createCategoryAttribute(body.categoryId, body.attributeDictIds);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Del('/attribute/:id')
|
||||||
|
async deleteCategoryAttribute(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
await this.categoryService.deleteCategoryAttribute(id);
|
||||||
|
return successResponse(null);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,82 +1,98 @@
|
||||||
import {
|
import { Controller, Get, Post, Inject, Query, Body } from '@midwayjs/core';
|
||||||
Body,
|
import { successResponse, errorResponse } from '../utils/response.util';
|
||||||
Context,
|
|
||||||
Controller,
|
|
||||||
Del,
|
|
||||||
Get,
|
|
||||||
Inject,
|
|
||||||
Post,
|
|
||||||
Put,
|
|
||||||
Query,
|
|
||||||
} from '@midwayjs/core';
|
|
||||||
import { CustomerService } from '../service/customer.service';
|
import { CustomerService } from '../service/customer.service';
|
||||||
import { errorResponse, successResponse } from '../utils/response.util';
|
import { QueryCustomerListDTO, CustomerTagDTO } from '../dto/customer.dto';
|
||||||
import { ApiOkResponse } from '@midwayjs/swagger';
|
import { ApiOkResponse } from '@midwayjs/swagger';
|
||||||
import { BooleanRes } from '../dto/reponse.dto';
|
import { UnifiedSearchParamsDTO } from '../dto/site-api.dto';
|
||||||
import { CustomerTagDTO, QueryCustomerListDTO } from '../dto/customer.dto';
|
|
||||||
|
|
||||||
@Controller('/customer')
|
@Controller('/customer')
|
||||||
export class CustomerController {
|
export class CustomerController {
|
||||||
@Inject()
|
|
||||||
ctx: Context;
|
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
customerService: CustomerService;
|
customerService: CustomerService;
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse({ type: Object })
|
||||||
@Get('/list')
|
@Get('/getcustomerlist')
|
||||||
async getCustomerList(@Query() param: QueryCustomerListDTO) {
|
async getCustomerList(@Query() query: QueryCustomerListDTO) {
|
||||||
try {
|
try {
|
||||||
const data = await this.customerService.getCustomerList(param);
|
const result = await this.customerService.getCustomerList(query)
|
||||||
return successResponse(data);
|
return successResponse(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
return errorResponse(error.message);
|
||||||
return errorResponse(error?.message || error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({ type: BooleanRes })
|
@ApiOkResponse({ type: Object })
|
||||||
@Post('/tag/add')
|
@Get('/getcustomerstatisticlist')
|
||||||
async addTag(@Body() dto: CustomerTagDTO) {
|
async getCustomerStatisticList(@Query() query: QueryCustomerListDTO) {
|
||||||
try {
|
try {
|
||||||
await this.customerService.addTag(dto.email, dto.tag);
|
const result = await this.customerService.getCustomerStatisticList(query as any);
|
||||||
return successResponse(true);
|
return successResponse(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({ type: BooleanRes })
|
@ApiOkResponse({ type: Object })
|
||||||
@Del('/tag/del')
|
@Post('/addtag')
|
||||||
async delTag(@Body() dto: CustomerTagDTO) {
|
async addTag(@Body() body: CustomerTagDTO) {
|
||||||
try {
|
try {
|
||||||
await this.customerService.delTag(dto.email, dto.tag);
|
const result = await this.customerService.addTag(body.email, body.tag);
|
||||||
return successResponse(true);
|
return successResponse(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse({ type: Object })
|
||||||
@Get('/tags')
|
@Post('/deltag')
|
||||||
|
async delTag(@Body() body: CustomerTagDTO) {
|
||||||
|
try {
|
||||||
|
const result = await this.customerService.delTag(body.email, body.tag);
|
||||||
|
return successResponse(result);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse({ type: Object })
|
||||||
|
@Get('/gettags')
|
||||||
async getTags() {
|
async getTags() {
|
||||||
try {
|
try {
|
||||||
const data = await this.customerService.getTags();
|
const result = await this.customerService.getTags();
|
||||||
return successResponse(data);
|
return successResponse(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse({ type: Object })
|
||||||
@ApiOkResponse({ type: BooleanRes })
|
@Post('/setrate')
|
||||||
@Put('/rate')
|
async setRate(@Body() body: { id: number; rate: number }) {
|
||||||
async setRate(@Body() params: { id: number; rate: number }) {
|
|
||||||
try {
|
try {
|
||||||
await this.customerService.setRate(params);
|
const result = await this.customerService.setRate({ id: body.id, rate: body.rate });
|
||||||
return successResponse(true);
|
return successResponse(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步客户数据
|
||||||
|
* 从指定站点获取客户数据并保存到本地数据库
|
||||||
|
* 业务逻辑已移到service层,controller只负责参数传递和响应
|
||||||
|
*/
|
||||||
|
@ApiOkResponse({ type: Object })
|
||||||
|
@Post('/sync')
|
||||||
|
async syncCustomers(@Body() body: { siteId: number; params?: UnifiedSearchParamsDTO }) {
|
||||||
|
try {
|
||||||
|
const { siteId, params = {} } = body;
|
||||||
|
|
||||||
|
// 调用service层的同步方法,所有业务逻辑都在service中处理
|
||||||
|
const syncResult = await this.customerService.syncCustomersFromSite(siteId, params);
|
||||||
|
|
||||||
|
return successResponse(syncResult);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,221 @@
|
||||||
|
|
||||||
|
import { Inject, Controller, Get, Post, Put, Del, Query, Body, Param, Files, ContentType } from '@midwayjs/core';
|
||||||
|
import { DictService } from '../service/dict.service';
|
||||||
|
import { CreateDictDTO, UpdateDictDTO, CreateDictItemDTO, UpdateDictItemDTO } from '../dto/dict.dto';
|
||||||
|
import { Validate } from '@midwayjs/validate';
|
||||||
|
import { Context } from '@midwayjs/koa';
|
||||||
|
import { successResponse, errorResponse } from '../utils/response.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典管理
|
||||||
|
* @decorator Controller
|
||||||
|
*/
|
||||||
|
@Controller('/dict')
|
||||||
|
export class DictController {
|
||||||
|
@Inject()
|
||||||
|
dictService: DictService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
ctx: Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量导入字典
|
||||||
|
* @param files 上传的文件
|
||||||
|
*/
|
||||||
|
@Post('/import')
|
||||||
|
@Validate()
|
||||||
|
async importDicts(@Files() files: any) {
|
||||||
|
// 从上传的文件列表中获取第一个文件
|
||||||
|
const file = files[0];
|
||||||
|
// 调用服务层方法处理XLSX文件
|
||||||
|
const result = await this.dictService.importDictsFromXLSX(file.data);
|
||||||
|
// 返回导入结果
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载字典XLSX模板
|
||||||
|
*/
|
||||||
|
@Get('/template')
|
||||||
|
@ContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||||
|
async downloadDictTemplate() {
|
||||||
|
// 设置下载文件的名称
|
||||||
|
this.ctx.set('Content-Disposition', 'attachment; filename=dict-template.xlsx');
|
||||||
|
// 返回XLSX模板内容
|
||||||
|
return this.dictService.getDictXLSXTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个字典及其所有字典项
|
||||||
|
* @param id 字典ID
|
||||||
|
*/
|
||||||
|
@Get('/:id')
|
||||||
|
async getDict(@Param('id') id: number) {
|
||||||
|
// 调用服务层方法,并关联查询字典项
|
||||||
|
return this.dictService.getDict({ id }, ['items']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典列表
|
||||||
|
* @param title 字典标题 (模糊查询)
|
||||||
|
* @param name 字典名称 (模糊查询)
|
||||||
|
*/
|
||||||
|
@Get('/list')
|
||||||
|
async getDicts(@Query('title') title?: string, @Query('name') name?: string) {
|
||||||
|
// 调用服务层方法
|
||||||
|
return this.dictService.getDicts({ title, name });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新字典
|
||||||
|
* @param createDictDTO 字典数据
|
||||||
|
*/
|
||||||
|
@Post('/')
|
||||||
|
@Validate()
|
||||||
|
async createDict(@Body() createDictDTO: CreateDictDTO) {
|
||||||
|
try {
|
||||||
|
// 调用服务层方法
|
||||||
|
const result = await this.dictService.createDict(createDictDTO);
|
||||||
|
return successResponse(result, '字典创建成功');
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '字典创建失败', error?.code || 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新字典
|
||||||
|
* @param id 字典ID
|
||||||
|
* @param updateDictDTO 待更新的字典数据
|
||||||
|
*/
|
||||||
|
@Put('/:id')
|
||||||
|
@Validate()
|
||||||
|
async updateDict(@Param('id') id: number, @Body() updateDictDTO: UpdateDictDTO) {
|
||||||
|
try {
|
||||||
|
// 调用服务层方法
|
||||||
|
const result = await this.dictService.updateDict(id, updateDictDTO);
|
||||||
|
return successResponse(result, '字典更新成功');
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '字典更新失败', error?.code || 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除字典
|
||||||
|
* @param id 字典ID
|
||||||
|
*/
|
||||||
|
@Del('/:id')
|
||||||
|
async deleteDict(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
// 调用服务层方法
|
||||||
|
const result = await this.dictService.deleteDict(id);
|
||||||
|
return successResponse(result, '字典删除成功');
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '字典删除失败', error?.code || 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量导入字典项
|
||||||
|
* @param files 上传的文件
|
||||||
|
* @param body 请求体,包含字典ID
|
||||||
|
*/
|
||||||
|
@Post('/item/import')
|
||||||
|
@Validate()
|
||||||
|
async importDictItems(@Files() files: any, @Body() body: { dictId: number }) {
|
||||||
|
// 获取第一个文件
|
||||||
|
const file = files[0];
|
||||||
|
// 调用服务层方法
|
||||||
|
const result = await this.dictService.importDictItemsFromXLSX(file.data, body.dictId);
|
||||||
|
// 返回结果
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载字典项XLSX模板
|
||||||
|
*/
|
||||||
|
@Get('/item/template')
|
||||||
|
@ContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||||
|
async downloadDictItemTemplate() {
|
||||||
|
// 设置下载文件名
|
||||||
|
this.ctx.set('Content-Disposition', 'attachment; filename=dict-item-template.xlsx');
|
||||||
|
// 返回模板内容
|
||||||
|
return this.dictService.getDictItemXLSXTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典项列表
|
||||||
|
* @param dictId 字典ID (可选)
|
||||||
|
*/
|
||||||
|
@Get('/items')
|
||||||
|
async getDictItems(
|
||||||
|
@Query('dictId') dictId?: number,
|
||||||
|
@Query('name') name?: string,
|
||||||
|
@Query('title') title?: string,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// 调用服务层方法
|
||||||
|
const result = await this.dictService.getDictItems({ dictId, name, title });
|
||||||
|
return successResponse(result, '获取字典项列表成功');
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '获取字典项列表失败', error?.code || 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新字典项
|
||||||
|
* @param createDictItemDTO 字典项数据
|
||||||
|
*/
|
||||||
|
@Post('/item')
|
||||||
|
@Validate()
|
||||||
|
async createDictItem(@Body() createDictItemDTO: CreateDictItemDTO) {
|
||||||
|
try {
|
||||||
|
// 调用服务层方法
|
||||||
|
const result = await this.dictService.createDictItem(createDictItemDTO);
|
||||||
|
return successResponse(result, '字典项创建成功');
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '字典项创建失败', error?.code || 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新字典项
|
||||||
|
* @param id 字典项ID
|
||||||
|
* @param updateDictItemDTO 待更新的字典项数据
|
||||||
|
*/
|
||||||
|
@Put('/item/:id')
|
||||||
|
@Validate()
|
||||||
|
async updateDictItem(@Param('id') id: number, @Body() updateDictItemDTO: UpdateDictItemDTO) {
|
||||||
|
try {
|
||||||
|
// 调用服务层方法
|
||||||
|
const result = await this.dictService.updateDictItem(id, updateDictItemDTO);
|
||||||
|
return successResponse(result, '字典项更新成功');
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '字典项更新失败', error?.code || 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除字典项
|
||||||
|
* @param id 字典项ID
|
||||||
|
*/
|
||||||
|
@Del('/item/:id')
|
||||||
|
async deleteDictItem(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
// 调用服务层方法
|
||||||
|
const result = await this.dictService.deleteDictItem(id);
|
||||||
|
return successResponse(result, '字典项删除成功');
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '字典项删除失败', error?.code || 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典名称获取字典项列表
|
||||||
|
* @param name 字典名称
|
||||||
|
*/
|
||||||
|
@Get('/items-by-name')
|
||||||
|
async getDictItemsByDictName(@Query('name') name: string) {
|
||||||
|
// 调用服务层方法
|
||||||
|
return this.dictService.getDictItemsByDictName(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
|
||||||
|
import { Controller, Get, Inject, Param } from '@midwayjs/core';
|
||||||
|
import { DictService } from '../service/dict.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 国际化语言包
|
||||||
|
*/
|
||||||
|
@Controller('/locales')
|
||||||
|
export class LocaleController {
|
||||||
|
@Inject()
|
||||||
|
dictService: DictService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据语言获取所有翻译项
|
||||||
|
* @param lang 语言代码,例如 zh-CN, en-US
|
||||||
|
* @returns 返回一个包含所有翻译键值对的 JSON 对象
|
||||||
|
*/
|
||||||
|
@Get('/:lang')
|
||||||
|
async getLocale(@Param('lang') lang: string) {
|
||||||
|
// 根据语言代码查找对应的字典
|
||||||
|
const dict = await this.dictService.getDict({ name: lang }, ['items']);
|
||||||
|
|
||||||
|
// 如果字典不存在,则返回空对象
|
||||||
|
if (!dict) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将字典项转换为 key-value 对象
|
||||||
|
const locale = dict.items.reduce((acc, item) => {
|
||||||
|
acc[item.name] = item.title;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { Controller, Get, Inject, Query, Post, Del, Param, Files, Fields, Body } from '@midwayjs/core';
|
||||||
|
import { WPService } from '../service/wp.service';
|
||||||
|
import { successResponse, errorResponse } from '../utils/response.util';
|
||||||
|
|
||||||
|
@Controller('/media')
|
||||||
|
export class MediaController {
|
||||||
|
@Inject()
|
||||||
|
wpService: WPService;
|
||||||
|
|
||||||
|
@Get('/list')
|
||||||
|
async list(
|
||||||
|
@Query('siteId') siteId: number,
|
||||||
|
@Query('page') page: number = 1,
|
||||||
|
@Query('pageSize') pageSize: number = 20
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (!siteId) {
|
||||||
|
return errorResponse('siteId is required');
|
||||||
|
}
|
||||||
|
const result = await this.wpService.getMedia(siteId, page, pageSize);
|
||||||
|
return successResponse(result);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/upload')
|
||||||
|
async upload(@Fields() fields, @Files() files) {
|
||||||
|
try {
|
||||||
|
const siteId = fields.siteId;
|
||||||
|
if (!siteId) {
|
||||||
|
return errorResponse('siteId is required');
|
||||||
|
}
|
||||||
|
if (!files || files.length === 0) {
|
||||||
|
return errorResponse('file is required');
|
||||||
|
}
|
||||||
|
const file = files[0];
|
||||||
|
const result = await this.wpService.createMedia(siteId, file);
|
||||||
|
return successResponse(result);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/update/:id')
|
||||||
|
async update(@Param('id') id: number, @Body() body) {
|
||||||
|
try {
|
||||||
|
const siteId = body.siteId;
|
||||||
|
if (!siteId) {
|
||||||
|
return errorResponse('siteId is required');
|
||||||
|
}
|
||||||
|
// 过滤出需要更新的字段
|
||||||
|
const { title, caption, description, alt_text } = body;
|
||||||
|
const data: any = {};
|
||||||
|
if (title !== undefined) data.title = title;
|
||||||
|
if (caption !== undefined) data.caption = caption;
|
||||||
|
if (description !== undefined) data.description = description;
|
||||||
|
if (alt_text !== undefined) data.alt_text = alt_text;
|
||||||
|
|
||||||
|
const result = await this.wpService.updateMedia(siteId, id, data);
|
||||||
|
return successResponse(result);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Del('/:id')
|
||||||
|
async delete(@Param('id') id: number, @Query('siteId') siteId: number, @Query('force') force: boolean = true) {
|
||||||
|
try {
|
||||||
|
if (!siteId) {
|
||||||
|
return errorResponse('siteId is required');
|
||||||
|
}
|
||||||
|
const result = await this.wpService.deleteMedia(siteId, id, force);
|
||||||
|
return successResponse(result);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,10 +36,10 @@ export class OrderController {
|
||||||
type: BooleanRes,
|
type: BooleanRes,
|
||||||
})
|
})
|
||||||
@Post('/syncOrder/:siteId')
|
@Post('/syncOrder/:siteId')
|
||||||
async syncOrder(@Param('siteId') siteId: string) {
|
async syncOrder(@Param('siteId') siteId: number, @Body() params: Record<string, any>) {
|
||||||
try {
|
try {
|
||||||
await this.orderService.syncOrders(siteId);
|
const result = await this.orderService.syncOrders(siteId, params);
|
||||||
return successResponse(true);
|
return successResponse(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return errorResponse('同步失败');
|
return errorResponse('同步失败');
|
||||||
|
|
@ -51,7 +51,7 @@ export class OrderController {
|
||||||
})
|
})
|
||||||
@Post('/syncOrder/:siteId/order/:orderId')
|
@Post('/syncOrder/:siteId/order/:orderId')
|
||||||
async syncOrderById(
|
async syncOrderById(
|
||||||
@Param('siteId') siteId: string,
|
@Param('siteId') siteId: number,
|
||||||
@Param('orderId') orderId: string
|
@Param('orderId') orderId: string
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -11,36 +11,19 @@ import {
|
||||||
} from '@midwayjs/core';
|
} from '@midwayjs/core';
|
||||||
import { ProductService } from '../service/product.service';
|
import { ProductService } from '../service/product.service';
|
||||||
import { errorResponse, successResponse } from '../utils/response.util';
|
import { errorResponse, successResponse } from '../utils/response.util';
|
||||||
import {
|
import { CreateProductDTO, QueryProductDTO, UpdateProductDTO, BatchUpdateProductDTO, BatchDeleteProductDTO } from '../dto/product.dto';
|
||||||
BatchSetSkuDTO,
|
|
||||||
CreateCategoryDTO,
|
|
||||||
CreateFlavorsDTO,
|
|
||||||
CreateProductDTO,
|
|
||||||
CreateStrengthDTO,
|
|
||||||
QueryCategoryDTO,
|
|
||||||
QueryFlavorsDTO,
|
|
||||||
QueryProductDTO,
|
|
||||||
QueryStrengthDTO,
|
|
||||||
UpdateCategoryDTO,
|
|
||||||
UpdateFlavorsDTO,
|
|
||||||
UpdateProductDTO,
|
|
||||||
UpdateStrengthDTO,
|
|
||||||
} from '../dto/product.dto';
|
|
||||||
import { ApiOkResponse } from '@midwayjs/swagger';
|
import { ApiOkResponse } from '@midwayjs/swagger';
|
||||||
import {
|
import { BooleanRes, ProductListRes, ProductRes, ProductsRes } from '../dto/reponse.dto';
|
||||||
BooleanRes,
|
import { ContentType, Files } from '@midwayjs/core';
|
||||||
ProductCatListRes,
|
import { Context } from '@midwayjs/koa';
|
||||||
ProductCatRes,
|
|
||||||
ProductListRes,
|
|
||||||
ProductRes,
|
|
||||||
ProductsRes,
|
|
||||||
} from '../dto/reponse.dto';
|
|
||||||
|
|
||||||
@Controller('/product')
|
@Controller('/product')
|
||||||
export class ProductController {
|
export class ProductController {
|
||||||
@Inject()
|
@Inject()
|
||||||
productService: ProductService;
|
productService: ProductService;
|
||||||
ProductRes;
|
|
||||||
|
@Inject()
|
||||||
|
ctx: Context;
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({
|
||||||
description: '通过name搜索产品',
|
description: '通过name搜索产品',
|
||||||
|
|
@ -79,12 +62,14 @@ export class ProductController {
|
||||||
async getProductList(
|
async getProductList(
|
||||||
@Query() query: QueryProductDTO
|
@Query() query: QueryProductDTO
|
||||||
): Promise<ProductListRes> {
|
): Promise<ProductListRes> {
|
||||||
const { current = 1, pageSize = 10, name, categoryId } = query;
|
const { current = 1, pageSize = 10, name, brandId, sortField, sortOrder } = query;
|
||||||
try {
|
try {
|
||||||
const data = await this.productService.getProductList(
|
const data = await this.productService.getProductList(
|
||||||
{ current, pageSize },
|
{ current, pageSize },
|
||||||
name,
|
name,
|
||||||
categoryId
|
brandId,
|
||||||
|
sortField,
|
||||||
|
sortOrder
|
||||||
);
|
);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -93,56 +78,146 @@ export class ProductController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({ type: ProductRes })
|
||||||
type: ProductRes,
|
|
||||||
})
|
|
||||||
@Post('/')
|
@Post('/')
|
||||||
async createProduct(@Body() productData: CreateProductDTO) {
|
async createProduct(@Body() productData: CreateProductDTO) {
|
||||||
try {
|
try {
|
||||||
const data = this.productService.createProduct(productData);
|
const data = await this.productService.createProduct(productData);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
// 导出所有产品 CSV
|
||||||
type: ProductRes,
|
@ApiOkResponse()
|
||||||
})
|
@Get('/export')
|
||||||
|
@ContentType('text/csv')
|
||||||
|
async exportProductsCSV() {
|
||||||
|
try {
|
||||||
|
const csv = await this.productService.exportProductsCSV();
|
||||||
|
// 设置下载文件名(附件形式)
|
||||||
|
const date = new Date();
|
||||||
|
const pad = (n: number) => String(n).padStart(2, '0');
|
||||||
|
const name = `products-${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}.csv`;
|
||||||
|
this.ctx.set('Content-Disposition', `attachment; filename=${name}`);
|
||||||
|
return csv;
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导入产品(CSV 文件)
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Post('/import')
|
||||||
|
async importProductsCSV(@Files() files: any) {
|
||||||
|
try {
|
||||||
|
// 条件判断:确保存在文件
|
||||||
|
const file = files?.[0];
|
||||||
|
if (!file) return errorResponse('未接收到上传文件');
|
||||||
|
|
||||||
|
const result = await this.productService.importProductsCSV(file);
|
||||||
|
return successResponse(result);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse({ type: ProductRes })
|
||||||
@Put('/:id')
|
@Put('/:id')
|
||||||
async updateProduct(
|
async updateProduct(@Param('id') id: number, @Body() productData: UpdateProductDTO) {
|
||||||
@Param('id') id: number,
|
|
||||||
@Body() productData: UpdateProductDTO
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const data = this.productService.updateProduct(id, productData);
|
const data = await this.productService.updateProduct(id, productData);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
type: ProductRes,
|
@Put('/batch-update')
|
||||||
})
|
async batchUpdateProduct(@Body() batchUpdateProductDTO: BatchUpdateProductDTO) {
|
||||||
|
try {
|
||||||
|
await this.productService.batchUpdateProduct(batchUpdateProductDTO);
|
||||||
|
return successResponse(true);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
|
@Post('/batch-delete')
|
||||||
|
async batchDeleteProduct(@Body() body: BatchDeleteProductDTO) {
|
||||||
|
try {
|
||||||
|
const result = await this.productService.batchDeleteProduct(body.ids);
|
||||||
|
if (result.failed > 0) {
|
||||||
|
return errorResponse(`成功删除 ${result.success} 个,失败 ${result.failed} 个。首个错误: ${result.errors[0]}`);
|
||||||
|
}
|
||||||
|
return successResponse(true);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse({ type: ProductRes })
|
||||||
@Put('updateNameCn/:id/:nameCn')
|
@Put('updateNameCn/:id/:nameCn')
|
||||||
async updateProductNameCn(
|
async updatenameCn(@Param('id') id: number, @Param('nameCn') nameCn: string) {
|
||||||
@Param('id') id: number,
|
|
||||||
@Param('nameCn') nameCn: string
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const data = this.productService.updateProductNameCn(id, nameCn);
|
const data = await this.productService.updatenameCn(id, nameCn);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据站点SKU查询产品
|
||||||
|
@ApiOkResponse({ type: ProductRes })
|
||||||
|
@Get('/site-sku/:siteSku')
|
||||||
|
async getProductBySiteSku(@Param('siteSku') siteSku: string) {
|
||||||
|
try {
|
||||||
|
const product = await this.productService.findProductBySiteSku(siteSku);
|
||||||
|
return successResponse(product);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error.message || '获取数据失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取产品的站点SKU绑定
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Get('/:id/site-skus')
|
||||||
|
async getProductSiteSkus(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
const data = await this.productService.getProductSiteSkus(id);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
// 覆盖式绑定产品的站点SKU列表
|
||||||
type: BooleanRes,
|
@ApiOkResponse()
|
||||||
})
|
@Post('/:id/site-skus')
|
||||||
|
async bindProductSiteSkus(@Param('id') id: number, @Body() body: { codes: string[] }) {
|
||||||
|
try {
|
||||||
|
const data = await this.productService.bindSiteSkus(id, body?.codes || []);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse({ type: ProductRes })
|
||||||
|
@Get('/:id')
|
||||||
|
async getProductById(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
const product = await this.productService.getProductById(id);
|
||||||
|
return successResponse(product);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error.message || '获取数据失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
@Del('/:id')
|
@Del('/:id')
|
||||||
async deleteProduct(@Param('id') id: number) {
|
async deleteProduct(@Param('id') id: number) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -153,14 +228,43 @@ export class ProductController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
// 获取产品的库存组成
|
||||||
type: ProductCatListRes,
|
@ApiOkResponse()
|
||||||
})
|
@Get('/:id/components')
|
||||||
@Get('/categories')
|
async getProductComponents(@Param('id') id: number) {
|
||||||
async getCategories(@Query() query: QueryCategoryDTO) {
|
|
||||||
const { current = 1, pageSize = 10, name } = query;
|
|
||||||
try {
|
try {
|
||||||
let data = await this.productService.getCategoryList(
|
const data = await this.productService.getProductComponents(id);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 根据 SKU 自动绑定组成(匹配所有相同 SKU 的库存)
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Post('/:id/components/auto')
|
||||||
|
async autoBindComponents(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
const data = await this.productService.autoBindComponentsBySku(id);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用属性接口:分页列表
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Get('/attribute')
|
||||||
|
async getAttributeList(
|
||||||
|
@Query('dictName') dictName: string,
|
||||||
|
@Query('current') current = 1,
|
||||||
|
@Query('pageSize') pageSize = 10,
|
||||||
|
@Query('name') name?: string
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const data = await this.productService.getAttributeList(
|
||||||
|
dictName,
|
||||||
{ current, pageSize },
|
{ current, pageSize },
|
||||||
name
|
name
|
||||||
);
|
);
|
||||||
|
|
@ -170,92 +274,138 @@ export class ProductController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 通用属性接口:全部列表
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Get('/categorieAll')
|
@Get('/attributeAll')
|
||||||
async getCategorieAll() {
|
async getAttributeAll(@Query('dictName') dictName: string) {
|
||||||
try {
|
try {
|
||||||
let data = await this.productService.getCategoryAll();
|
const data = await this.productService.getAttributeAll(dictName);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
// 通用属性接口:创建
|
||||||
type: ProductCatRes,
|
@ApiOkResponse()
|
||||||
})
|
@Post('/attribute')
|
||||||
@Post('/category')
|
async createAttribute(
|
||||||
async createCategory(@Body() categoryData: CreateCategoryDTO) {
|
@Query('dictName') dictName: string,
|
||||||
try {
|
@Body() body: { title: string; name: string }
|
||||||
const hasCategory = await this.productService.hasCategory(
|
|
||||||
categoryData.name
|
|
||||||
);
|
|
||||||
if (hasCategory) {
|
|
||||||
return errorResponse('分类已存在');
|
|
||||||
}
|
|
||||||
let data = await this.productService.createCategory(categoryData);
|
|
||||||
return successResponse(data);
|
|
||||||
} catch (error) {
|
|
||||||
return errorResponse(error?.message || error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOkResponse({
|
|
||||||
type: ProductCatRes,
|
|
||||||
})
|
|
||||||
@Put('/category/:id')
|
|
||||||
async updateCategory(
|
|
||||||
@Param('id') id: number,
|
|
||||||
@Body() categoryData: UpdateCategoryDTO
|
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const hasCategory = await this.productService.hasCategory(
|
// 调用 getOrCreateAttribute 方法,如果不存在则创建,如果存在则返回
|
||||||
categoryData.name
|
const data = await this.productService.getOrCreateAttribute(dictName, body.title, body.name);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用属性接口:更新
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Put('/attribute/:id')
|
||||||
|
async updateAttribute(
|
||||||
|
@Param('id') id: number,
|
||||||
|
@Query('dictName') dictName: string,
|
||||||
|
@Body() body: { title?: string; name?: string }
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (body?.name) {
|
||||||
|
const hasItem = await this.productService.hasAttribute(
|
||||||
|
dictName,
|
||||||
|
body.name,
|
||||||
|
id
|
||||||
);
|
);
|
||||||
if (hasCategory) {
|
if (hasItem) return errorResponse('字典项已存在');
|
||||||
return errorResponse('分类已存在');
|
|
||||||
}
|
}
|
||||||
const data = this.productService.updateCategory(id, categoryData);
|
const data = await this.productService.updateAttribute(id, body);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
// 通用属性接口:删除
|
||||||
type: BooleanRes,
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
})
|
@Del('/attribute/:id')
|
||||||
@Del('/category/:id')
|
async deleteAttribute(@Param('id') id: number) {
|
||||||
async deleteCategory(@Param('id') id: number) {
|
|
||||||
try {
|
try {
|
||||||
const hasProducts = await this.productService.hasProductsInCategory(id);
|
await this.productService.deleteAttribute(id);
|
||||||
if (hasProducts) throw new Error('该分类下有商品,无法删除');
|
return successResponse(true);
|
||||||
const data = await this.productService.deleteCategory(id);
|
|
||||||
return successResponse(data);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/batchSetSku')
|
// 兼容旧接口:品牌
|
||||||
@ApiOkResponse({
|
@ApiOkResponse()
|
||||||
description: '批量设置 sku 的响应结果',
|
@Get('/brandAll')
|
||||||
type: BooleanRes,
|
async compatBrandAll() {
|
||||||
})
|
|
||||||
async batchSetSku(@Body() body: BatchSetSkuDTO) {
|
|
||||||
try {
|
try {
|
||||||
const result = await this.productService.batchSetSku(body.skus);
|
const data = await this.productService.getAttributeAll('brand'); // 返回所有品牌字典项
|
||||||
return successResponse(result, '批量设置 sku 成功');
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error.message, 400);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Get('/flavorsAll')
|
@Get('/brands')
|
||||||
async getFlavorsAll() {
|
async compatBrands(@Query('current') current = 1, @Query('pageSize') pageSize = 10, @Query('name') name?: string) {
|
||||||
try {
|
try {
|
||||||
let data = await this.productService.getFlavorsAll();
|
const data = await this.productService.getAttributeList('brand', { current, pageSize }, name); // 分页品牌列表
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Post('/brand')
|
||||||
|
async compatCreateBrand(@Body() body: { title: string; name: string; image?: string; shortName?: string }) {
|
||||||
|
try {
|
||||||
|
const has = await this.productService.hasAttribute('brand', body.name); // 唯一性校验
|
||||||
|
if (has) return errorResponse('品牌已存在');
|
||||||
|
const data = await this.productService.createAttribute('brand', body); // 创建品牌字典项
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Put('/brand/:id')
|
||||||
|
async compatUpdateBrand(@Param('id') id: number, @Body() body: { title?: string; name?: string; image?: string; shortName?: string }) {
|
||||||
|
try {
|
||||||
|
if (body?.name) {
|
||||||
|
const has = await this.productService.hasAttribute('brand', body.name, id); // 唯一性校验(排除自身)
|
||||||
|
if (has) return errorResponse('品牌已存在');
|
||||||
|
}
|
||||||
|
const data = await this.productService.updateAttribute(id, body); // 更新品牌字典项
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
|
@Del('/brand/:id')
|
||||||
|
async compatDeleteBrand(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
await this.productService.deleteAttribute(id); // 删除品牌字典项
|
||||||
|
return successResponse(true);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧接口:口味
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Get('/flavorsAll')
|
||||||
|
async compatFlavorsAll() {
|
||||||
|
try {
|
||||||
|
const data = await this.productService.getAttributeAll('flavor');
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -264,13 +414,9 @@ export class ProductController {
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Get('/flavors')
|
@Get('/flavors')
|
||||||
async getFlavors(@Query() query: QueryFlavorsDTO) {
|
async compatFlavors(@Query('current') current = 1, @Query('pageSize') pageSize = 10, @Query('name') name?: string) {
|
||||||
const { current = 1, pageSize = 10, name } = query;
|
|
||||||
try {
|
try {
|
||||||
let data = await this.productService.getFlavorsList(
|
const data = await this.productService.getAttributeList('flavor', { current, pageSize }, name);
|
||||||
{ current, pageSize },
|
|
||||||
name
|
|
||||||
);
|
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -279,13 +425,11 @@ export class ProductController {
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Post('/flavors')
|
@Post('/flavors')
|
||||||
async createFlavors(@Body() flavorsData: CreateFlavorsDTO) {
|
async compatCreateFlavors(@Body() body: { title: string; name: string; image?: string; shortName?: string }) {
|
||||||
try {
|
try {
|
||||||
const hasFlavors = await this.productService.hasFlavors(flavorsData.name);
|
const has = await this.productService.hasAttribute('flavor', body.name);
|
||||||
if (hasFlavors) {
|
if (has) return errorResponse('口味已存在');
|
||||||
return errorResponse('分类已存在');
|
const data = await this.productService.createAttribute('flavor', body);
|
||||||
}
|
|
||||||
let data = await this.productService.createFlavors(flavorsData);
|
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -294,42 +438,36 @@ export class ProductController {
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Put('/flavors/:id')
|
@Put('/flavors/:id')
|
||||||
async updateFlavors(
|
async compatUpdateFlavors(@Param('id') id: number, @Body() body: { title?: string; name?: string; image?: string; shortName?: string }) {
|
||||||
@Param('id') id: number,
|
|
||||||
@Body() flavorsData: UpdateFlavorsDTO
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const hasFlavors = await this.productService.hasFlavors(flavorsData.name);
|
if (body?.name) {
|
||||||
if (hasFlavors) {
|
const has = await this.productService.hasAttribute('flavor', body.name, id);
|
||||||
return errorResponse('分类已存在');
|
if (has) return errorResponse('口味已存在');
|
||||||
}
|
}
|
||||||
const data = this.productService.updateFlavors(id, flavorsData);
|
const data = await this.productService.updateAttribute(id, body);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
type: BooleanRes,
|
|
||||||
})
|
|
||||||
@Del('/flavors/:id')
|
@Del('/flavors/:id')
|
||||||
async deleteFlavors(@Param('id') id: number) {
|
async compatDeleteFlavors(@Param('id') id: number) {
|
||||||
try {
|
try {
|
||||||
const hasProducts = await this.productService.hasProductsInFlavors(id);
|
await this.productService.deleteAttribute(id);
|
||||||
if (hasProducts) throw new Error('该分类下有商品,无法删除');
|
return successResponse(true);
|
||||||
const data = await this.productService.deleteFlavors(id);
|
|
||||||
return successResponse(data);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 兼容旧接口:规格
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Get('/strengthAll')
|
@Get('/strengthAll')
|
||||||
async getStrengthAll() {
|
async compatStrengthAll() {
|
||||||
try {
|
try {
|
||||||
let data = await this.productService.getStrengthAll();
|
const data = await this.productService.getAttributeAll('strength');
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -338,13 +476,9 @@ export class ProductController {
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Get('/strength')
|
@Get('/strength')
|
||||||
async getStrength(@Query() query: QueryStrengthDTO) {
|
async compatStrength(@Query('current') current = 1, @Query('pageSize') pageSize = 10, @Query('name') name?: string) {
|
||||||
const { current = 1, pageSize = 10, name } = query;
|
|
||||||
try {
|
try {
|
||||||
let data = await this.productService.getStrengthList(
|
const data = await this.productService.getAttributeList('strength', { current, pageSize }, name);
|
||||||
{ current, pageSize },
|
|
||||||
name
|
|
||||||
);
|
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -353,15 +487,11 @@ export class ProductController {
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Post('/strength')
|
@Post('/strength')
|
||||||
async createStrength(@Body() strengthData: CreateStrengthDTO) {
|
async compatCreateStrength(@Body() body: { title: string; name: string; image?: string; shortName?: string }) {
|
||||||
try {
|
try {
|
||||||
const hasStrength = await this.productService.hasStrength(
|
const has = await this.productService.hasAttribute('strength', body.name);
|
||||||
strengthData.name
|
if (has) return errorResponse('规格已存在');
|
||||||
);
|
const data = await this.productService.createAttribute('strength', body);
|
||||||
if (hasStrength) {
|
|
||||||
return errorResponse('分类已存在');
|
|
||||||
}
|
|
||||||
let data = await this.productService.createStrength(strengthData);
|
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -370,33 +500,182 @@ export class ProductController {
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Put('/strength/:id')
|
@Put('/strength/:id')
|
||||||
async updateStrength(
|
async compatUpdateStrength(@Param('id') id: number, @Body() body: { title?: string; name?: string; image?: string; shortName?: string }) {
|
||||||
@Param('id') id: number,
|
|
||||||
@Body() strengthData: UpdateStrengthDTO
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const hasStrength = await this.productService.hasStrength(
|
if (body?.name) {
|
||||||
strengthData.name
|
const has = await this.productService.hasAttribute('strength', body.name, id);
|
||||||
);
|
if (has) return errorResponse('规格已存在');
|
||||||
if (hasStrength) {
|
|
||||||
return errorResponse('分类已存在');
|
|
||||||
}
|
}
|
||||||
const data = this.productService.updateStrength(id, strengthData);
|
const data = await this.productService.updateAttribute(id, body);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
type: BooleanRes,
|
|
||||||
})
|
|
||||||
@Del('/strength/:id')
|
@Del('/strength/:id')
|
||||||
async deleteStrength(@Param('id') id: number) {
|
async compatDeleteStrength(@Param('id') id: number) {
|
||||||
try {
|
try {
|
||||||
const hasProducts = await this.productService.hasProductsInStrength(id);
|
await this.productService.deleteAttribute(id);
|
||||||
if (hasProducts) throw new Error('该分类下有商品,无法删除');
|
return successResponse(true);
|
||||||
const data = await this.productService.deleteStrength(id);
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧接口:尺寸
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Get('/sizeAll')
|
||||||
|
async compatSizeAll() {
|
||||||
|
try {
|
||||||
|
const data = await this.productService.getAttributeAll('size');
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Get('/size')
|
||||||
|
async compatSize(@Query('current') current = 1, @Query('pageSize') pageSize = 10, @Query('name') name?: string) {
|
||||||
|
try {
|
||||||
|
const data = await this.productService.getAttributeList('size', { current, pageSize }, name);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Post('/size')
|
||||||
|
async compatCreateSize(@Body() body: { title: string; name: string; image?: string; shortName?: string }) {
|
||||||
|
try {
|
||||||
|
const has = await this.productService.hasAttribute('size', body.name);
|
||||||
|
if (has) return errorResponse('尺寸已存在');
|
||||||
|
const data = await this.productService.createAttribute('size', body);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Put('/size/:id')
|
||||||
|
async compatUpdateSize(@Param('id') id: number, @Body() body: { title?: string; name?: string; image?: string; shortName?: string }) {
|
||||||
|
try {
|
||||||
|
if (body?.name) {
|
||||||
|
const has = await this.productService.hasAttribute('size', body.name, id);
|
||||||
|
if (has) return errorResponse('尺寸已存在');
|
||||||
|
}
|
||||||
|
const data = await this.productService.updateAttribute(id, body);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
|
@Del('/size/:id')
|
||||||
|
async compatDeleteSize(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
await this.productService.deleteAttribute(id);
|
||||||
|
return successResponse(true);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有分类
|
||||||
|
@ApiOkResponse({ description: '获取所有分类' })
|
||||||
|
@Get('/categories/all')
|
||||||
|
async getCategoriesAll() {
|
||||||
|
try {
|
||||||
|
const data = await this.productService.getCategoriesAll();
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取分类下的属性配置
|
||||||
|
@ApiOkResponse({ description: '获取分类下的属性配置' })
|
||||||
|
@Get('/category/:id/attributes')
|
||||||
|
async getCategoryAttributes(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
const data = await this.productService.getCategoryAttributes(id);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建分类
|
||||||
|
@ApiOkResponse({ description: '创建分类' })
|
||||||
|
@Post('/category')
|
||||||
|
async createCategory(@Body() body: any) {
|
||||||
|
try {
|
||||||
|
const data = await this.productService.createCategory(body);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新分类
|
||||||
|
@ApiOkResponse({ description: '更新分类' })
|
||||||
|
@Put('/category/:id')
|
||||||
|
async updateCategory(@Param('id') id: number, @Body() body: any) {
|
||||||
|
try {
|
||||||
|
const data = await this.productService.updateCategory(id, body);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除分类
|
||||||
|
@ApiOkResponse({ description: '删除分类' })
|
||||||
|
@Del('/category/:id')
|
||||||
|
async deleteCategory(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
await this.productService.deleteCategory(id);
|
||||||
|
return successResponse(true);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建分类属性
|
||||||
|
@ApiOkResponse({ description: '创建分类属性' })
|
||||||
|
@Post('/category/attribute')
|
||||||
|
async createCategoryAttribute(@Body() body: { categoryId: number; dictId: number }) {
|
||||||
|
try {
|
||||||
|
const data = await this.productService.createCategoryAttribute(body);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除分类属性
|
||||||
|
@ApiOkResponse({ description: '删除分类属性' })
|
||||||
|
@Del('/category/attribute/:id')
|
||||||
|
async deleteCategoryAttribute(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
await this.productService.deleteCategoryAttribute(id);
|
||||||
|
return successResponse(true);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步库存 SKU 到产品单品
|
||||||
|
@ApiOkResponse({ description: '同步库存 SKU 到产品单品' })
|
||||||
|
@Post('/sync-stock')
|
||||||
|
async syncStockToProduct() {
|
||||||
|
try {
|
||||||
|
const data = await this.productService.syncStockToProduct();
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
||||||
import { Body, Controller, Get, Inject, Param, Put, Post, Query } from '@midwayjs/core';
|
import { Body, Controller, Get, Inject, Param, Put, Post, Query } from '@midwayjs/core';
|
||||||
import { ApiOkResponse } from '@midwayjs/swagger';
|
import { ApiOkResponse } from '@midwayjs/swagger';
|
||||||
import { WpSitesResponse } from '../dto/reponse.dto';
|
import { SitesResponse } from '../dto/reponse.dto';
|
||||||
import { errorResponse, successResponse } from '../utils/response.util';
|
import { errorResponse, successResponse } from '../utils/response.util';
|
||||||
import { SiteService } from '../service/site.service';
|
import { SiteService } from '../service/site.service';
|
||||||
import { CreateSiteDTO, DisableSiteDTO, QuerySiteDTO, UpdateSiteDTO } from '../dto/site.dto';
|
import { CreateSiteDTO, DisableSiteDTO, QuerySiteDTO, UpdateSiteDTO } from '../dto/site.dto';
|
||||||
|
|
@ -10,12 +10,12 @@ export class SiteController {
|
||||||
@Inject()
|
@Inject()
|
||||||
siteService: SiteService;
|
siteService: SiteService;
|
||||||
|
|
||||||
@ApiOkResponse({ description: '关联网站', type: WpSitesResponse })
|
@ApiOkResponse({ description: '关联网站', type: SitesResponse })
|
||||||
@Get('/all')
|
@Get('/all')
|
||||||
async all() {
|
async all() {
|
||||||
try {
|
try {
|
||||||
const { items } = await this.siteService.list({ current: 1, pageSize: 1000, isDisabled: false });
|
const { items } = await this.siteService.list({ current: 1, pageSize: 1000, isDisabled: false });
|
||||||
return successResponse(items.map((v: any) => ({ id: v.id, siteName: v.siteName })));
|
return successResponse(items.map((v: any) => ({ id: v.id, name: v.name })));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || '获取失败');
|
return errorResponse(error?.message || '获取失败');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,9 +176,21 @@ export class StockController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查某个 SKU 是否有库存(任一仓库数量大于 0)
|
||||||
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
|
@Get('/has/:sku')
|
||||||
|
async hasStock(@Param('sku') sku: string) {
|
||||||
|
try {
|
||||||
|
const data = await this.stockService.hasStockBySku(sku);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '查询失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({
|
||||||
type: BooleanRes,
|
type: BooleanRes,
|
||||||
description: '更新库存(入库、出库、调整)',
|
description: '更新库存(入库,出库,调整)',
|
||||||
})
|
})
|
||||||
@Post('/update')
|
@Post('/update')
|
||||||
async updateStock(@Body() body: UpdateStockDTO) {
|
async updateStock(@Body() body: UpdateStockDTO) {
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,19 @@ export class SubscriptionController {
|
||||||
@Inject()
|
@Inject()
|
||||||
subscriptionService: SubscriptionService;
|
subscriptionService: SubscriptionService;
|
||||||
|
|
||||||
// 同步订阅:根据站点 ID 拉取并更新本地订阅数据
|
// 同步订阅:根据站点 ID 拉取并更新本地订阅数据
|
||||||
@ApiOkResponse({ type: BooleanRes })
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
@Post('/sync/:siteId')
|
@Post('/sync/:siteId')
|
||||||
async sync(@Param('siteId') siteId: string) {
|
async sync(@Param('siteId') siteId: number) {
|
||||||
try {
|
try {
|
||||||
await this.subscriptionService.syncSubscriptions(siteId);
|
const result = await this.subscriptionService.syncSubscriptions(siteId);
|
||||||
return successResponse(true);
|
return successResponse(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || '同步失败');
|
return errorResponse(error?.message || '同步失败');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 订阅列表:分页 + 筛选
|
// 订阅列表:分页 + 筛选
|
||||||
@ApiOkResponse({ type: SubscriptionListRes })
|
@ApiOkResponse({ type: SubscriptionListRes })
|
||||||
@Get('/list')
|
@Get('/list')
|
||||||
async list(@Query() query: QuerySubscriptionDTO) {
|
async list(@Query() query: QuerySubscriptionDTO) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
import { Inject, Controller, Get, Post, Put, Del, Body, Param, Query } from '@midwayjs/core';
|
||||||
|
import { TemplateService } from '../service/template.service';
|
||||||
|
import { successResponse, errorResponse } from '../utils/response.util';
|
||||||
|
import { CreateTemplateDTO, UpdateTemplateDTO, RenderTemplateDTO } from '../dto/template.dto';
|
||||||
|
import { ApiOkResponse, ApiTags } from '@midwayjs/swagger';
|
||||||
|
import { Template } from '../entity/template.entity';
|
||||||
|
import { BooleanRes } from '../dto/reponse.dto';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @controller TemplateController 模板管理
|
||||||
|
*/
|
||||||
|
@ApiTags('Template')
|
||||||
|
@Controller('/template')
|
||||||
|
export class TemplateController {
|
||||||
|
@Inject()
|
||||||
|
templateService: TemplateService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary 获取模板列表
|
||||||
|
* @description 获取所有可用模板的列表
|
||||||
|
*/
|
||||||
|
@ApiOkResponse({ type: [Template], description: '成功获取模板列表' })
|
||||||
|
@Get('/list')
|
||||||
|
async getTemplateList(@Query() params: any) {
|
||||||
|
// 调用服务层获取列表
|
||||||
|
return this.templateService.getTemplateList(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary 根据名称获取模板
|
||||||
|
* @description 通过模板的唯一名称查找特定模板
|
||||||
|
* @param name 模板名称
|
||||||
|
*/
|
||||||
|
@ApiOkResponse({ type: Template, description: '成功获取模板' })
|
||||||
|
@Get('/:name')
|
||||||
|
async getTemplateByName(@Param('name') name: string) {
|
||||||
|
try {
|
||||||
|
// 调用服务层获取单个模板
|
||||||
|
const data = await this.templateService.getTemplateByName(name);
|
||||||
|
// 返回成功响应
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
// 返回错误响应
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary 创建新模板
|
||||||
|
* @description 创建一个新的模板,用于后续的字符串生成
|
||||||
|
* @param templateData 模板数据
|
||||||
|
*/
|
||||||
|
@ApiOkResponse({ type: Template, description: '成功创建模板' })
|
||||||
|
@Post('/')
|
||||||
|
async createTemplate(@Body() templateData: CreateTemplateDTO) {
|
||||||
|
try {
|
||||||
|
// 调用服务层创建模板
|
||||||
|
const data = await this.templateService.createTemplate(templateData);
|
||||||
|
// 返回成功响应
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
// 返回错误响应
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary 更新现有模板
|
||||||
|
* @description 根据模板 ID 更新一个现有模板的内容
|
||||||
|
* @param id 模板 ID
|
||||||
|
* @param templateData 模板数据
|
||||||
|
*/
|
||||||
|
@ApiOkResponse({ type: Template, description: '成功更新模板' })
|
||||||
|
@Put('/:id')
|
||||||
|
async updateTemplate(
|
||||||
|
@Param('id') id: number,
|
||||||
|
@Body() templateData: UpdateTemplateDTO
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// 调用服务层更新模板
|
||||||
|
const data = await this.templateService.updateTemplate(id, templateData);
|
||||||
|
// 返回成功响应
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
// 返回错误响应
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary 删除模板
|
||||||
|
* @description 根据模板 ID 删除一个模板
|
||||||
|
* @param id 模板 ID
|
||||||
|
*/
|
||||||
|
@ApiOkResponse({ type: BooleanRes, description: '成功删除模板' })
|
||||||
|
@Del('/:id')
|
||||||
|
async deleteTemplate(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
// 调用服务层删除模板
|
||||||
|
const data = await this.templateService.deleteTemplate(id);
|
||||||
|
// 返回成功响应
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
// 返回错误响应
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary 渲染模板
|
||||||
|
* @description 根据模板名称和输入的数据渲染最终字符串
|
||||||
|
* @param name 模板名称
|
||||||
|
* @param data 渲染数据
|
||||||
|
*/
|
||||||
|
@ApiOkResponse({ type: String, description: '成功渲染模板' })
|
||||||
|
@Post('/render/:name')
|
||||||
|
async renderTemplate(
|
||||||
|
@Param('name') name: string,
|
||||||
|
@Body() data: Record<string, any>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// 调用服务层渲染模板
|
||||||
|
const renderedString = await this.templateService.render(name, data);
|
||||||
|
// 返回成功响应
|
||||||
|
return successResponse(renderedString);
|
||||||
|
} catch (error) {
|
||||||
|
// 返回错误响应
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary 回填缺失的测试数据
|
||||||
|
* @description 扫描数据库中所有模板,为缺失 testData 的记录生成并保存测试数据
|
||||||
|
*/
|
||||||
|
@ApiOkResponse({ type: Number, description: '成功回填的数量' })
|
||||||
|
@Post('/backfill-testdata')
|
||||||
|
async backfillTestData() {
|
||||||
|
try {
|
||||||
|
const count = await this.templateService.backfillMissingTestData();
|
||||||
|
return successResponse({ updated: count });
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary 直接渲染模板内容
|
||||||
|
* @description 直接传入模板内容和数据渲染最终字符串,无需保存模板到数据库
|
||||||
|
* @param renderData 包含模板内容和渲染数据的对象
|
||||||
|
*/
|
||||||
|
@ApiOkResponse({ type: String, description: '成功渲染模板' })
|
||||||
|
@Post('/render-direct')
|
||||||
|
async renderTemplateDirect(@Body() renderData: RenderTemplateDTO) {
|
||||||
|
try {
|
||||||
|
// 调用服务层渲染模板内容
|
||||||
|
const renderedString = await this.templateService.renderWithTemplate(
|
||||||
|
renderData.template,
|
||||||
|
renderData.data
|
||||||
|
);
|
||||||
|
// 返回成功响应
|
||||||
|
return successResponse(renderedString);
|
||||||
|
} catch (error) {
|
||||||
|
// 返回错误响应
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -34,16 +34,17 @@ export class UserController {
|
||||||
})
|
})
|
||||||
@Post('/logout')
|
@Post('/logout')
|
||||||
async logout() {
|
async logout() {
|
||||||
// 可选:在这里处理服务端缓存的 token 或 session
|
// 可选:在这里处理服务端缓存的 token 或 session
|
||||||
|
|
||||||
return successResponse(true);
|
return successResponse(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/add')
|
@Post('/add')
|
||||||
async addUser(@Body() body: { username: string; password: string }) {
|
async addUser(@Body() body: { username: string; password: string; email?: string; remark?: string }) {
|
||||||
const { username, password } = body;
|
const { username, password, email, remark } = body;
|
||||||
try {
|
try {
|
||||||
await this.userService.addUser(username, password);
|
// 新增用户 支持邮箱与备注
|
||||||
|
await this.userService.addUser(username, password, remark, email);
|
||||||
return successResponse(true);
|
return successResponse(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
@ -52,21 +53,91 @@ export class UserController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/list')
|
@Get('/list')
|
||||||
async listUsers(@Query() query: { current: number; pageSize: number }) {
|
async listUsers(
|
||||||
const { current = 1, pageSize = 10 } = query;
|
@Query()
|
||||||
return successResponse(await this.userService.listUsers(current, pageSize));
|
query: {
|
||||||
|
current: number;
|
||||||
|
pageSize: number;
|
||||||
|
remark?: string;
|
||||||
|
username?: string;
|
||||||
|
email?: string;
|
||||||
|
isActive?: string;
|
||||||
|
isSuper?: string;
|
||||||
|
isAdmin?: string;
|
||||||
|
sortField?: string;
|
||||||
|
sortOrder?: string;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const { current = 1, pageSize = 10, remark, username, email, isActive, isSuper, isAdmin, sortField, sortOrder } = query;
|
||||||
|
// 将字符串布尔转换为真实布尔
|
||||||
|
const toBool = (v?: string) => (v === undefined ? undefined : v === 'true');
|
||||||
|
// 处理排序方向
|
||||||
|
const order = (sortOrder === 'ascend' || sortOrder === 'ASC') ? 'ASC' : 'DESC';
|
||||||
|
|
||||||
|
// 列表移除密码字段
|
||||||
|
const { items, total } = await this.userService.listUsers(
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
|
{
|
||||||
|
remark,
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
isActive: toBool(isActive),
|
||||||
|
isSuper: toBool(isSuper),
|
||||||
|
isAdmin: toBool(isAdmin),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: sortField,
|
||||||
|
order,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const safeItems = (items || []).map((it: any) => {
|
||||||
|
const { password, ...rest } = it || {};
|
||||||
|
return rest;
|
||||||
|
});
|
||||||
|
return successResponse({ items: safeItems, total, current, pageSize });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/toggleActive')
|
@Post('/toggleActive')
|
||||||
async toggleActive(@Body() body: { userId: number; isActive: boolean }) {
|
async toggleActive(@Body() body: { userId: number; isActive: boolean }) {
|
||||||
return this.userService.toggleUserActive(body.userId, body.isActive);
|
try {
|
||||||
|
// 调用服务层更新启用状态
|
||||||
|
const data = await this.userService.toggleUserActive(body.userId, body.isActive);
|
||||||
|
// 移除密码字段,保证安全
|
||||||
|
const { password, ...safe } = data as any;
|
||||||
|
return successResponse(safe);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '操作失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户(支持用户名/密码/权限/角色更新)
|
||||||
|
@Post('/update/:id')
|
||||||
|
async updateUser(
|
||||||
|
@Body() body: { username?: string; password?: string; email?: string; isSuper?: boolean; isAdmin?: boolean; permissions?: string[]; remark?: string },
|
||||||
|
@Query('id') id?: number
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// 条件判断:优先从路径参数获取 ID(兼容生成的 API 文件为 POST /user/update/:id)
|
||||||
|
const userId = Number((this.ctx?.params?.id ?? id));
|
||||||
|
if (!userId) throw new Error('缺少用户ID');
|
||||||
|
const data = await this.userService.updateUser(userId, body);
|
||||||
|
// 移除密码字段,保证安全
|
||||||
|
const { password, ...safe } = data as any;
|
||||||
|
return successResponse(safe);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '更新失败');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Get()
|
@Get()
|
||||||
async getUser(@User() user) {
|
async getUser(@User() user) {
|
||||||
try {
|
try {
|
||||||
return successResponse(await this.userService.getUser(user.id));
|
// 详情移除密码字段
|
||||||
|
const data = await this.userService.getUser(user.id);
|
||||||
|
const { password, ...safe } = (data as any) || {};
|
||||||
|
return successResponse(safe);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse('获取失败');
|
return errorResponse('获取失败');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { HttpStatus, Inject } from '@midwayjs/core';
|
import { HttpStatus, ILogger, Inject, Logger } from '@midwayjs/core';
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Post,
|
Post,
|
||||||
|
|
@ -9,8 +9,7 @@ import {
|
||||||
} from '@midwayjs/decorator';
|
} from '@midwayjs/decorator';
|
||||||
import { Context } from '@midwayjs/koa';
|
import { Context } from '@midwayjs/koa';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import { WpProductService } from '../service/wp_product.service';
|
|
||||||
import { WPService } from '../service/wp.service';
|
|
||||||
import { SiteService } from '../service/site.service';
|
import { SiteService } from '../service/site.service';
|
||||||
import { OrderService } from '../service/order.service';
|
import { OrderService } from '../service/order.service';
|
||||||
|
|
||||||
|
|
@ -18,11 +17,7 @@ import { OrderService } from '../service/order.service';
|
||||||
export class WebhookController {
|
export class WebhookController {
|
||||||
private secret = 'YOONE24kd$kjcdjflddd';
|
private secret = 'YOONE24kd$kjcdjflddd';
|
||||||
|
|
||||||
@Inject()
|
// 平台服务保留按需注入
|
||||||
private readonly wpProductService: WpProductService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private readonly wpApiService: WPService;
|
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private readonly orderService: OrderService;
|
private readonly orderService: OrderService;
|
||||||
|
|
@ -30,10 +25,13 @@ export class WebhookController {
|
||||||
@Inject()
|
@Inject()
|
||||||
ctx: Context;
|
ctx: Context;
|
||||||
|
|
||||||
|
@Logger()
|
||||||
|
logger: ILogger;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private readonly siteService: SiteService;
|
private readonly siteService: SiteService;
|
||||||
|
|
||||||
// 移除配置中的站点数组,来源统一改为数据库
|
// 移除配置中的站点数组,来源统一改为数据库
|
||||||
|
|
||||||
@Get('/')
|
@Get('/')
|
||||||
async test() {
|
async test() {
|
||||||
|
|
@ -43,16 +41,17 @@ export class WebhookController {
|
||||||
@Post('/woocommerce')
|
@Post('/woocommerce')
|
||||||
async handleWooWebhook(
|
async handleWooWebhook(
|
||||||
@Body() body: any,
|
@Body() body: any,
|
||||||
@Query('siteId') siteId: string,
|
@Query('siteId') siteIdStr: string,
|
||||||
@Headers() header: any
|
@Headers() header: any
|
||||||
) {
|
) {
|
||||||
const signature = header['x-wc-webhook-signature'];
|
const signature = header['x-wc-webhook-signature'];
|
||||||
const topic = header['x-wc-webhook-topic'];
|
const topic = header['x-wc-webhook-topic'];
|
||||||
const source = header['x-wc-webhook-source'];
|
const source = header['x-wc-webhook-source'];
|
||||||
|
const siteId = Number(siteIdStr);
|
||||||
// 从数据库获取站点配置
|
// 从数据库获取站点配置
|
||||||
const site = await this.siteService.get(Number(siteId), true);
|
const site = await this.siteService.get(siteId, true);
|
||||||
|
|
||||||
if (!site || !source.includes(site.apiUrl)) {
|
if (!site || !source?.includes(site.apiUrl)) {
|
||||||
console.log('domain not match');
|
console.log('domain not match');
|
||||||
return {
|
return {
|
||||||
code: HttpStatus.BAD_REQUEST,
|
code: HttpStatus.BAD_REQUEST,
|
||||||
|
|
@ -78,32 +77,10 @@ export class WebhookController {
|
||||||
switch (topic) {
|
switch (topic) {
|
||||||
case 'product.created':
|
case 'product.created':
|
||||||
case 'product.updated':
|
case 'product.updated':
|
||||||
// 变体更新
|
// 不再写入本地,平台事件仅确认接收
|
||||||
if (body.type === 'variation') {
|
|
||||||
const variation = await this.wpApiService.getVariation(
|
|
||||||
site,
|
|
||||||
body.parent_id,
|
|
||||||
body.id
|
|
||||||
);
|
|
||||||
this.wpProductService.syncVariation(
|
|
||||||
siteId,
|
|
||||||
body.parent_id,
|
|
||||||
variation
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const variations =
|
|
||||||
body.type === 'variable'
|
|
||||||
? await this.wpApiService.getVariations(site, body.id)
|
|
||||||
: [];
|
|
||||||
await this.wpProductService.syncProductAndVariations(
|
|
||||||
String(site.id),
|
|
||||||
body,
|
|
||||||
variations
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case 'product.deleted':
|
case 'product.deleted':
|
||||||
await this.wpProductService.delWpProduct(String(site.id), body.id);
|
// 不再写入本地,平台事件仅确认接收
|
||||||
break;
|
break;
|
||||||
case 'order.created':
|
case 'order.created':
|
||||||
case 'order.updated':
|
case 'order.updated':
|
||||||
|
|
|
||||||
|
|
@ -1,200 +0,0 @@
|
||||||
import {
|
|
||||||
Controller,
|
|
||||||
Param,
|
|
||||||
Post,
|
|
||||||
Inject,
|
|
||||||
Get,
|
|
||||||
Query,
|
|
||||||
Put,
|
|
||||||
Body,
|
|
||||||
} from '@midwayjs/core';
|
|
||||||
import { WpProductService } from '../service/wp_product.service';
|
|
||||||
import { errorResponse, successResponse } from '../utils/response.util';
|
|
||||||
import { ApiOkResponse } from '@midwayjs/swagger';
|
|
||||||
import { BooleanRes, WpProductListRes } from '../dto/reponse.dto';
|
|
||||||
import {
|
|
||||||
QueryWpProductDTO,
|
|
||||||
SetConstitutionDTO,
|
|
||||||
UpdateVariationDTO,
|
|
||||||
UpdateWpProductDTO,
|
|
||||||
} from '../dto/wp_product.dto';
|
|
||||||
import { WPService } from '../service/wp.service';
|
|
||||||
import { SiteService } from '../service/site.service';
|
|
||||||
import {
|
|
||||||
ProductsRes,
|
|
||||||
} from '../dto/reponse.dto';
|
|
||||||
@Controller('/wp_product')
|
|
||||||
export class WpProductController {
|
|
||||||
// 移除控制器内的配置站点引用,统一由服务层处理站点数据
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private readonly wpProductService: WpProductService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private readonly wpApiService: WPService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private readonly siteService: SiteService;
|
|
||||||
|
|
||||||
@ApiOkResponse({
|
|
||||||
type: BooleanRes,
|
|
||||||
})
|
|
||||||
@Post('/sync/:siteId')
|
|
||||||
async syncProducts(@Param('siteId') siteId: string) {
|
|
||||||
try {
|
|
||||||
await this.wpProductService.syncSite(siteId);
|
|
||||||
return successResponse(true);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
return errorResponse('同步失败');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOkResponse({
|
|
||||||
type: WpProductListRes,
|
|
||||||
})
|
|
||||||
@Get('/list')
|
|
||||||
async getWpProducts(@Query() query: QueryWpProductDTO) {
|
|
||||||
try {
|
|
||||||
const data = await this.wpProductService.getProductList(query);
|
|
||||||
return successResponse(data);
|
|
||||||
} catch (error) {
|
|
||||||
return errorResponse(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOkResponse({
|
|
||||||
type: BooleanRes,
|
|
||||||
})
|
|
||||||
@Put('/:id/constitution')
|
|
||||||
async setConstitution(
|
|
||||||
@Param('id') id: number,
|
|
||||||
@Body()
|
|
||||||
body: SetConstitutionDTO
|
|
||||||
) {
|
|
||||||
const { isProduct, constitution } = body;
|
|
||||||
try {
|
|
||||||
await this.wpProductService.setConstitution(id, isProduct, constitution);
|
|
||||||
return successResponse(true);
|
|
||||||
} catch (error) {
|
|
||||||
return errorResponse(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOkResponse({
|
|
||||||
type: BooleanRes
|
|
||||||
})
|
|
||||||
@Post('/updateState/:id')
|
|
||||||
async updateWPProductState(
|
|
||||||
@Param('id') id: number,
|
|
||||||
@Body() body: any, // todo
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const res = await this.wpProductService.updateProductStatus(id, body?.status, body?.stock_status);
|
|
||||||
return successResponse(res);
|
|
||||||
} catch (error) {
|
|
||||||
return errorResponse(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新产品接口
|
|
||||||
* @param productId 产品 ID
|
|
||||||
* @param body 更新数据
|
|
||||||
*/
|
|
||||||
@ApiOkResponse({
|
|
||||||
type: BooleanRes,
|
|
||||||
})
|
|
||||||
@Put('/siteId/:siteId/products/:productId')
|
|
||||||
async updateProduct(
|
|
||||||
@Param('siteId') siteId: string,
|
|
||||||
@Param('productId') productId: string,
|
|
||||||
@Body() body: UpdateWpProductDTO
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const isDuplicate = await this.wpProductService.isSkuDuplicate(
|
|
||||||
body.sku,
|
|
||||||
siteId,
|
|
||||||
productId
|
|
||||||
);
|
|
||||||
if (isDuplicate) {
|
|
||||||
return errorResponse('SKU已存在');
|
|
||||||
}
|
|
||||||
const site = await this.siteService.get(Number(siteId), true);
|
|
||||||
const result = await this.wpApiService.updateProduct(
|
|
||||||
site,
|
|
||||||
productId,
|
|
||||||
body
|
|
||||||
);
|
|
||||||
if (result) {
|
|
||||||
this.wpProductService.updateWpProduct(siteId, productId, body);
|
|
||||||
return successResponse(result, '产品更新成功');
|
|
||||||
}
|
|
||||||
return errorResponse('产品更新失败');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新产品失败:', error);
|
|
||||||
return errorResponse(error.message || '产品更新失败');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新变体接口
|
|
||||||
* @param productId 产品 ID
|
|
||||||
* @param variationId 变体 ID
|
|
||||||
* @param body 更新数据
|
|
||||||
*/
|
|
||||||
@Put('/siteId/:siteId/products/:productId/variations/:variationId')
|
|
||||||
async updateVariation(
|
|
||||||
@Param('siteId') siteId: string,
|
|
||||||
@Param('productId') productId: string,
|
|
||||||
@Param('variationId') variationId: string,
|
|
||||||
@Body() body: UpdateVariationDTO
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const isDuplicate = await this.wpProductService.isSkuDuplicate(
|
|
||||||
body.sku,
|
|
||||||
siteId,
|
|
||||||
productId,
|
|
||||||
variationId
|
|
||||||
);
|
|
||||||
if (isDuplicate) {
|
|
||||||
return errorResponse('SKU已存在');
|
|
||||||
}
|
|
||||||
const site = await this.siteService.get(Number(siteId), true);
|
|
||||||
const result = await this.wpApiService.updateVariation(
|
|
||||||
site,
|
|
||||||
productId,
|
|
||||||
variationId,
|
|
||||||
body
|
|
||||||
);
|
|
||||||
if (result) {
|
|
||||||
this.wpProductService.updateWpProductVaritation(
|
|
||||||
siteId,
|
|
||||||
productId,
|
|
||||||
variationId,
|
|
||||||
body
|
|
||||||
);
|
|
||||||
return successResponse(result, '产品变体更新成功');
|
|
||||||
}
|
|
||||||
return errorResponse('变体更新失败');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新变体失败:', error);
|
|
||||||
return errorResponse(error.message || '产品变体更新失败');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOkResponse({
|
|
||||||
description: '通过name搜索产品/订单',
|
|
||||||
type: ProductsRes,
|
|
||||||
})
|
|
||||||
@Get('/search')
|
|
||||||
async searchProducts(@Query('name') name: string) {
|
|
||||||
try {
|
|
||||||
// 调用服务获取产品数据
|
|
||||||
const products = await this.wpProductService.findProductsByName(name);
|
|
||||||
return successResponse(products);
|
|
||||||
} catch (error) {
|
|
||||||
return errorResponse(error.message || '获取数据失败');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { DataSource, DataSourceOptions } from 'typeorm';
|
||||||
|
import { SeederOptions } from 'typeorm-extension';
|
||||||
|
|
||||||
|
|
||||||
|
const options: DataSourceOptions & SeederOptions = {
|
||||||
|
type: 'mysql',
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: 23306,
|
||||||
|
username: 'root',
|
||||||
|
password: '12345678',
|
||||||
|
database: 'inventory',
|
||||||
|
synchronize: true,
|
||||||
|
logging: true,
|
||||||
|
entities: [__dirname + '/../entity/*.ts'],
|
||||||
|
migrations: ['src/db/migrations/**/*.ts'],
|
||||||
|
seeds: ['src/db/seeds/**/*.ts'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AppDataSource = new DataSource(options);
|
||||||
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class ProductDictItemManyToMany1764238434984 implements MigrationInterface {
|
||||||
|
name = 'ProductDictItemManyToMany1764238434984'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`CREATE TABLE \`product_attributes_dict_item\` (\`productId\` int NOT NULL, \`dictItemId\` int NOT NULL, INDEX \`IDX_592cdbdaebfec346c202ffb82c\` (\`productId\`), INDEX \`IDX_406c1da5b6de45fecb7967c3ec\` (\`dictItemId\`), PRIMARY KEY (\`productId\`, \`dictItemId\`)) ENGINE=InnoDB`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product\` DROP COLUMN \`brandId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product\` DROP COLUMN \`flavorsId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product\` DROP COLUMN \`strengthId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product\` DROP COLUMN \`humidity\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product\` ADD \`sku\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product\` ADD UNIQUE INDEX \`IDX_34f6ca1cd897cc926bdcca1ca3\` (\`sku\`)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product_attributes_dict_item\` ADD CONSTRAINT \`FK_592cdbdaebfec346c202ffb82ca\` FOREIGN KEY (\`productId\`) REFERENCES \`product\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product_attributes_dict_item\` ADD CONSTRAINT \`FK_406c1da5b6de45fecb7967c3ec0\` FOREIGN KEY (\`dictItemId\`) REFERENCES \`dict_item\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product_attributes_dict_item\` DROP FOREIGN KEY \`FK_406c1da5b6de45fecb7967c3ec0\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product_attributes_dict_item\` DROP FOREIGN KEY \`FK_592cdbdaebfec346c202ffb82ca\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product\` DROP INDEX \`IDX_34f6ca1cd897cc926bdcca1ca3\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product\` DROP COLUMN \`sku\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product\` ADD \`humidity\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product\` ADD \`strengthId\` int NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product\` ADD \`flavorsId\` int NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product\` ADD \`brandId\` int NOT NULL`);
|
||||||
|
await queryRunner.query(`DROP INDEX \`IDX_406c1da5b6de45fecb7967c3ec\` ON \`product_attributes_dict_item\``);
|
||||||
|
await queryRunner.query(`DROP INDEX \`IDX_592cdbdaebfec346c202ffb82c\` ON \`product_attributes_dict_item\``);
|
||||||
|
await queryRunner.query(`DROP TABLE \`product_attributes_dict_item\``);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class Area1764294088896 implements MigrationInterface {
|
||||||
|
name = 'Area1764294088896'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
// await queryRunner.query(`DROP INDEX \`IDX_4ca3fbc46d2dbf393ff4ebddbb\` ON \`site\``);
|
||||||
|
// await queryRunner.query(`CREATE TABLE \`area\` (\`id\` int NOT NULL AUTO_INCREMENT, \`name\` varchar(255) NOT NULL, \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updatedAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), UNIQUE INDEX \`IDX_644ffaf8fbde4db798cb47712f\` (\`name\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
|
||||||
|
// await queryRunner.query(`CREATE TABLE \`stock_point_areas_area\` (\`stockPointId\` int NOT NULL, \`areaId\` int NOT NULL, INDEX \`IDX_07d2db2150151e2ef341d2f1de\` (\`stockPointId\`), INDEX \`IDX_92707ea81fc19dc707dba24819\` (\`areaId\`), PRIMARY KEY (\`stockPointId\`, \`areaId\`)) ENGINE=InnoDB`);
|
||||||
|
// await queryRunner.query(`CREATE TABLE \`site_areas_area\` (\`siteId\` int NOT NULL, \`areaId\` int NOT NULL, INDEX \`IDX_926a14ac4c91f38792831acd2a\` (\`siteId\`), INDEX \`IDX_7c26c582048e3ecd3cd5938cb9\` (\`areaId\`), PRIMARY KEY (\`siteId\`, \`areaId\`)) ENGINE=InnoDB`);
|
||||||
|
// await queryRunner.query(`ALTER TABLE \`site\` DROP COLUMN \`name\``);
|
||||||
|
// await queryRunner.query(`ALTER TABLE `product` ADD `promotionPrice` decimal(10,2) NOT NULL DEFAULT '0.00'`);
|
||||||
|
// await queryRunner.query(`ALTER TABLE `product` ADD `source` int NOT NULL DEFAULT '0'`);
|
||||||
|
// await queryRunner.query(`ALTER TABLE \`site\` ADD \`token\` varchar(255) NULL`);
|
||||||
|
// await queryRunner.query(`ALTER TABLE `site` ADD `name` varchar(255) NOT NULL`);
|
||||||
|
// await queryRunner.query(`ALTER TABLE \`site\` ADD UNIQUE INDEX \`IDX_9669a09fcc0eb6d2794a658f64\` (\`name\`)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`stock_point_areas_area\` ADD CONSTRAINT \`FK_07d2db2150151e2ef341d2f1de1\` FOREIGN KEY (\`stockPointId\`) REFERENCES \`stock_point\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`stock_point_areas_area\` ADD CONSTRAINT \`FK_92707ea81fc19dc707dba24819c\` FOREIGN KEY (\`areaId\`) REFERENCES \`area\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`site_areas_area\` ADD CONSTRAINT \`FK_926a14ac4c91f38792831acd2a6\` FOREIGN KEY (\`siteId\`) REFERENCES \`site\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`site_areas_area\` ADD CONSTRAINT \`FK_7c26c582048e3ecd3cd5938cb9f\` FOREIGN KEY (\`areaId\`) REFERENCES \`area\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE \`site_areas_area\` DROP FOREIGN KEY \`FK_7c26c582048e3ecd3cd5938cb9f\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`site_areas_area\` DROP FOREIGN KEY \`FK_926a14ac4c91f38792831acd2a6\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`stock_point_areas_area\` DROP FOREIGN KEY \`FK_92707ea81fc19dc707dba24819c\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`stock_point_areas_area\` DROP FOREIGN KEY \`FK_07d2db2150151e2ef341d2f1de1\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`site\` DROP INDEX \`IDX_9669a09fcc0eb6d2794a658f64\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`site\` DROP COLUMN \`name\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`site\` DROP COLUMN \`token\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product\` DROP COLUMN \`source\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`product\` DROP COLUMN \`promotionPrice\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`site\` ADD \`name\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`DROP INDEX \`IDX_7c26c582048e3ecd3cd5938cb9\` ON \`site_areas_area\``);
|
||||||
|
await queryRunner.query(`DROP INDEX \`IDX_926a14ac4c91f38792831acd2a\` ON \`site_areas_area\``);
|
||||||
|
await queryRunner.query(`DROP TABLE \`site_areas_area\``);
|
||||||
|
await queryRunner.query(`DROP INDEX \`IDX_92707ea81fc19dc707dba24819\` ON \`stock_point_areas_area\``);
|
||||||
|
await queryRunner.query(`DROP INDEX \`IDX_07d2db2150151e2ef341d2f1de\` ON \`stock_point_areas_area\``);
|
||||||
|
await queryRunner.query(`DROP TABLE \`stock_point_areas_area\``);
|
||||||
|
await queryRunner.query(`DROP INDEX \`IDX_644ffaf8fbde4db798cb47712f\` ON \`area\``);
|
||||||
|
await queryRunner.query(`DROP TABLE \`area\``);
|
||||||
|
await queryRunner.query(`CREATE UNIQUE INDEX \`IDX_4ca3fbc46d2dbf393ff4ebddbb\` ON \`site\` (\`name\`)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class ProductStock1764299629279 implements MigrationInterface {
|
||||||
|
name = 'ProductStock1764299629279'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`CREATE TABLE \`order_item_original\` (\`id\` int NOT NULL AUTO_INCREMENT, \`order_id\` int NOT NULL, \`name\` varchar(255) NOT NULL, \`siteId\` varchar(255) NOT NULL, \`externalOrderId\` varchar(255) NOT NULL, \`externalOrderItemId\` varchar(255) NULL, \`externalProductId\` varchar(255) NOT NULL, \`externalVariationId\` varchar(255) NOT NULL, \`quantity\` int NOT NULL, \`subtotal\` decimal(10,2) NULL, \`subtotal_tax\` decimal(10,2) NULL, \`total\` decimal(10,2) NULL, \`total_tax\` decimal(10,2) NULL, \`sku\` varchar(255) NULL, \`price\` decimal(10,2) NOT NULL, \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updatedAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD CONSTRAINT \`FK_ca48e4bce0bb8cecd24cc8081e5\` FOREIGN KEY (\`order_id\`) REFERENCES \`order\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP FOREIGN KEY \`FK_ca48e4bce0bb8cecd24cc8081e5\``);
|
||||||
|
await queryRunner.query(`DROP TABLE \`order_item_original\``);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class UpdateDictItemUniqueConstraint1764569947170 implements MigrationInterface {
|
||||||
|
name = 'UpdateDictItemUniqueConstraint1764569947170'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`productId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`isPackage\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalOrderId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalProductId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalVariationId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`subtotal\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`subtotal_tax\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`total\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`total_tax\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`price\` decimal(10,2) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`productId\` int NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`isPackage\` tinyint NOT NULL DEFAULT 0`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` CHANGE \`sku\` \`sku\` varchar(255) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` int NULL`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` CHANGE \`sku\` \`sku\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` int NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`isPackage\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`productId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`price\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`total_tax\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`total\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`subtotal_tax\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`subtotal\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalVariationId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalProductId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalOrderId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`isPackage\` tinyint NOT NULL DEFAULT '0'`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`productId\` int NOT NULL`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class AddTestDataToTemplate1765275715762 implements MigrationInterface {
|
||||||
|
name = 'AddTestDataToTemplate1765275715762'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE \`site_stock_points_stock_point\` DROP FOREIGN KEY \`FK_e93d8c42c9baf5a0dade42c59ae\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`isPackage\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`productId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalOrderId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalProductId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalVariationId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`price\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`subtotal\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`subtotal_tax\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`total\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`total_tax\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`template\` ADD \`testData\` text NULL COMMENT '测试数据JSON'`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalOrderId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalProductId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalVariationId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`subtotal\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`subtotal_tax\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`total\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`total_tax\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`price\` decimal(10,2) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`productId\` int NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`isPackage\` tinyint NOT NULL DEFAULT 0`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` int NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` CHANGE \`sku\` \`sku\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`site_stock_points_stock_point\` ADD CONSTRAINT \`FK_e93d8c42c9baf5a0dade42c59ae\` FOREIGN KEY (\`stockPointId\`) REFERENCES \`stock_point\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE \`site_stock_points_stock_point\` DROP FOREIGN KEY \`FK_e93d8c42c9baf5a0dade42c59ae\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` CHANGE \`sku\` \`sku\` varchar(255) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` int NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`isPackage\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`productId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`price\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`total_tax\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`total\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`subtotal_tax\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`subtotal\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalVariationId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalProductId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalOrderId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`template\` DROP COLUMN \`testData\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`total_tax\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`total\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`subtotal_tax\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`subtotal\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`price\` decimal(10,2) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalVariationId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalProductId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalOrderId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`productId\` int NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`isPackage\` tinyint NOT NULL DEFAULT '0'`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`site_stock_points_stock_point\` ADD CONSTRAINT \`FK_e93d8c42c9baf5a0dade42c59ae\` FOREIGN KEY (\`stockPointId\`) REFERENCES \`stock_point\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class AddSiteDescription1765330208213 implements MigrationInterface {
|
||||||
|
name = 'AddSiteDescription1765330208213'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`productId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`isPackage\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalOrderId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalProductId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`externalVariationId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`subtotal\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`subtotal_tax\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`total\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`total_tax\` decimal(10,2) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`price\` decimal(10,2) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`productId\` int NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`isPackage\` tinyint NOT NULL DEFAULT 0`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` CHANGE \`sku\` \`sku\` varchar(255) NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` int NULL`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` CHANGE \`sku\` \`sku\` varchar(255) NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`siteId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`siteId\` int NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`isPackage\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`productId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`price\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`total_tax\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`total\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`subtotal_tax\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`subtotal\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalVariationId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalProductId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` DROP COLUMN \`externalOrderId\``);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`isPackage\` tinyint NOT NULL DEFAULT '0'`);
|
||||||
|
await queryRunner.query(`ALTER TABLE \`order_item_original\` ADD \`productId\` int NOT NULL`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class UpdateProductTableName1765358400000 implements MigrationInterface {
|
||||||
|
name = 'UpdateProductTableName1765358400000'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
// 1. 使用 try-catch 方式删除外键约束,避免因外键不存在导致迁移失败
|
||||||
|
try {
|
||||||
|
await queryRunner.query("ALTER TABLE `product_attributes_dict_item` DROP FOREIGN KEY `FK_592cdbdaebfec346c202ffb82ca`");
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略外键不存在的错误
|
||||||
|
console.log('Warning: Failed to drop foreign key on product_attributes_dict_item. It may not exist.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await queryRunner.query("ALTER TABLE `product_stock_component` DROP FOREIGN KEY `FK_6fe75663083f572a49e7f46909b`");
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Warning: Failed to drop foreign key on product_stock_component. It may not exist.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await queryRunner.query("ALTER TABLE `product_site_sku` DROP FOREIGN KEY `FK_3b9b7f3d8a6d9f3e2c0b1a4d5e6`");
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Warning: Failed to drop foreign key on product_site_sku. It may not exist.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await queryRunner.query("ALTER TABLE `order_sale` DROP FOREIGN KEY `FK_order_sale_product`");
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Warning: Failed to drop foreign key on order_sale. It may not exist.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 将 product 表重命名为 product_v2
|
||||||
|
await queryRunner.query("ALTER TABLE `product` RENAME TO `product_v2`");
|
||||||
|
|
||||||
|
// 3. 重新创建所有外键约束,引用新的 product_v2 表
|
||||||
|
await queryRunner.query("ALTER TABLE `product_attributes_dict_item` ADD CONSTRAINT `FK_592cdbdaebfec346c202ffb82ca` FOREIGN KEY (`productId`) REFERENCES `product_v2`(`id`) ON DELETE CASCADE ON UPDATE CASCADE");
|
||||||
|
await queryRunner.query("ALTER TABLE `product_stock_component` ADD CONSTRAINT `FK_6fe75663083f572a49e7f46909b` FOREIGN KEY (`productId`) REFERENCES `product_v2`(`id`) ON DELETE CASCADE");
|
||||||
|
await queryRunner.query("ALTER TABLE `product_site_sku` ADD CONSTRAINT `FK_3b9b7f3d8a6d9f3e2c0b1a4d5e6` FOREIGN KEY (`productId`) REFERENCES `product_v2`(`id`) ON DELETE CASCADE");
|
||||||
|
|
||||||
|
// 4. 为 order_sale 表添加外键约束
|
||||||
|
try {
|
||||||
|
await queryRunner.query("ALTER TABLE `order_sale` ADD CONSTRAINT `FK_order_sale_product` FOREIGN KEY (`productId`) REFERENCES `product_v2`(`id`) ON DELETE CASCADE");
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Warning: Failed to add foreign key on order_sale. It may already exist.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
// 回滚操作
|
||||||
|
// 1. 删除外键约束
|
||||||
|
try {
|
||||||
|
await queryRunner.query("ALTER TABLE `product_attributes_dict_item` DROP FOREIGN KEY `FK_592cdbdaebfec346c202ffb82ca`");
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Warning: Failed to drop foreign key on product_attributes_dict_item during rollback.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await queryRunner.query("ALTER TABLE `product_stock_component` DROP FOREIGN KEY `FK_6fe75663083f572a49e7f46909b`");
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Warning: Failed to drop foreign key on product_stock_component during rollback.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await queryRunner.query("ALTER TABLE `product_site_sku` DROP FOREIGN KEY `FK_3b9b7f3d8a6d9f3e2c0b1a4d5e6`");
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Warning: Failed to drop foreign key on product_site_sku during rollback.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await queryRunner.query("ALTER TABLE `order_sale` DROP FOREIGN KEY `FK_order_sale_product`");
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Warning: Failed to drop foreign key on order_sale during rollback.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 将 product_v2 表重命名回 product
|
||||||
|
await queryRunner.query("ALTER TABLE `product_v2` RENAME TO `product`");
|
||||||
|
|
||||||
|
// 3. 重新创建外键约束,引用回原来的 product 表
|
||||||
|
await queryRunner.query("ALTER TABLE `product_attributes_dict_item` ADD CONSTRAINT `FK_592cdbdaebfec346c202ffb82ca` FOREIGN KEY (`productId`) REFERENCES `product`(`id`) ON DELETE CASCADE ON UPDATE CASCADE");
|
||||||
|
await queryRunner.query("ALTER TABLE `product_stock_component` ADD CONSTRAINT `FK_6fe75663083f572a49e7f46909b` FOREIGN KEY (`productId`) REFERENCES `product`(`id`) ON DELETE CASCADE");
|
||||||
|
await queryRunner.query("ALTER TABLE `product_site_sku` ADD CONSTRAINT `FK_3b9b7f3d8a6d9f3e2c0b1a4d5e6` FOREIGN KEY (`productId`) REFERENCES `product`(`id`) ON DELETE CASCADE");
|
||||||
|
|
||||||
|
// 4. 为 order_sale 表重新创建外键约束
|
||||||
|
try {
|
||||||
|
await queryRunner.query("ALTER TABLE `order_sale` ADD CONSTRAINT `FK_order_sale_product` FOREIGN KEY (`productId`) REFERENCES `product`(`id`) ON DELETE CASCADE");
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Warning: Failed to add foreign key on order_sale during rollback. It may already exist.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
import { Seeder, SeederFactoryManager } from 'typeorm-extension';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { Area } from '../../entity/area.entity';
|
||||||
|
|
||||||
|
export default class AreaSeeder implements Seeder {
|
||||||
|
public async run(
|
||||||
|
dataSource: DataSource,
|
||||||
|
factoryManager: SeederFactoryManager
|
||||||
|
): Promise<any> {
|
||||||
|
const areaRepository = dataSource.getRepository(Area);
|
||||||
|
|
||||||
|
const areas = [
|
||||||
|
{ name: 'Australia', code: 'AU' },
|
||||||
|
{ name: 'Canada', code: 'CA' },
|
||||||
|
{ name: 'United States', code: 'US' },
|
||||||
|
{ name: 'Germany', code: 'DE' },
|
||||||
|
{ name: 'Poland', code: 'PL' },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const areaData of areas) {
|
||||||
|
const existingArea = await areaRepository.findOne({
|
||||||
|
where: [
|
||||||
|
{ name: areaData.name },
|
||||||
|
{ code: areaData.code }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
if (!existingArea) {
|
||||||
|
const newArea = areaRepository.create(areaData);
|
||||||
|
await areaRepository.save(newArea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { Seeder } from 'typeorm-extension';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { Category } from '../../entity/category.entity';
|
||||||
|
|
||||||
|
export default class CategorySeeder implements Seeder {
|
||||||
|
public async run(
|
||||||
|
dataSource: DataSource,
|
||||||
|
): Promise<any> {
|
||||||
|
const repository = dataSource.getRepository(Category);
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
{
|
||||||
|
name: 'nicotine-pouches',
|
||||||
|
title: 'Nicotine Pouches',
|
||||||
|
titleCN: '尼古丁袋',
|
||||||
|
sort: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vape',
|
||||||
|
title: 'vape',
|
||||||
|
titleCN: '电子烟',
|
||||||
|
sort: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pouches-can',
|
||||||
|
title: 'Pouches Can',
|
||||||
|
titleCN: '口含烟盒',
|
||||||
|
sort: 3
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const cat of categories) {
|
||||||
|
const existing = await repository.findOne({ where: { name: cat.name } });
|
||||||
|
if (!existing) {
|
||||||
|
await repository.save(cat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { Seeder } from 'typeorm-extension';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { Dict } from '../../entity/dict.entity';
|
||||||
|
import { Category } from '../../entity/category.entity';
|
||||||
|
import { CategoryAttribute } from '../../entity/category_attribute.entity';
|
||||||
|
|
||||||
|
export default class CategoryAttributeSeeder implements Seeder {
|
||||||
|
public async run(
|
||||||
|
dataSource: DataSource,
|
||||||
|
): Promise<any> {
|
||||||
|
const dictRepository = dataSource.getRepository(Dict);
|
||||||
|
const categoryRepository = dataSource.getRepository(Category);
|
||||||
|
const categoryAttributeRepository = dataSource.getRepository(CategoryAttribute);
|
||||||
|
|
||||||
|
// 1. 确保属性字典存在
|
||||||
|
const attributeNames = ['brand', 'strength', 'flavor', 'size', 'humidity'];
|
||||||
|
const attributeDicts: Dict[] = [];
|
||||||
|
|
||||||
|
for (const name of attributeNames) {
|
||||||
|
let dict = await dictRepository.findOne({ where: { name } });
|
||||||
|
if (!dict) {
|
||||||
|
dict = new Dict();
|
||||||
|
dict.name = name;
|
||||||
|
dict.title = name.charAt(0).toUpperCase() + name.slice(1);
|
||||||
|
dict.deletable = false;
|
||||||
|
dict = await dictRepository.save(dict);
|
||||||
|
console.log(`Created Dict: ${name}`);
|
||||||
|
}
|
||||||
|
attributeDicts.push(dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取 'nicotine-pouches' 分类 (由 CategorySeeder 创建)
|
||||||
|
const nicotinePouchesCategory = await categoryRepository.findOne({
|
||||||
|
where: {
|
||||||
|
name: 'nicotine-pouches'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!nicotinePouchesCategory) {
|
||||||
|
console.warn('Category "nicotine-pouches" not found. Skipping attribute linking. Please ensure CategorySeeder runs first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 绑定属性到 'nicotine-pouches' 分类
|
||||||
|
for (const attrDict of attributeDicts) {
|
||||||
|
const existing = await categoryAttributeRepository.findOne({
|
||||||
|
where: {
|
||||||
|
category: { id: nicotinePouchesCategory.id },
|
||||||
|
attributeDict: { id: attrDict.id }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
const link = new CategoryAttribute();
|
||||||
|
link.category = nicotinePouchesCategory;
|
||||||
|
link.attributeDict = attrDict;
|
||||||
|
await categoryAttributeRepository.save(link);
|
||||||
|
console.log(`Linked ${attrDict.name} to ${nicotinePouchesCategory.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
import { Seeder } from 'typeorm-extension';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { Dict } from '../../entity/dict.entity';
|
||||||
|
import { DictItem } from '../../entity/dict_item.entity';
|
||||||
|
|
||||||
|
export default class DictSeeder implements Seeder {
|
||||||
|
/**
|
||||||
|
* 格式化名称为 kebab-case
|
||||||
|
* @param name 需要格式化的名称
|
||||||
|
* @returns 格式化后的名称
|
||||||
|
*/
|
||||||
|
private formatName(name: string): string {
|
||||||
|
// return String(name).replace(/[\_\s.]+/g, '-').toLowerCase();
|
||||||
|
// 只替换空格和下划线
|
||||||
|
return String(name).replace(/[\_\s]+/g, '-').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run(
|
||||||
|
dataSource: DataSource,
|
||||||
|
): Promise<any> {
|
||||||
|
const dictRepository = dataSource.getRepository(Dict);
|
||||||
|
const dictItemRepository = dataSource.getRepository(DictItem);
|
||||||
|
|
||||||
|
const flavorsData = [
|
||||||
|
{ name: 'bellini', title: 'Bellini', titleCn: '贝利尼', shortName: 'BL' },
|
||||||
|
{ name: 'max-polarmint', title: 'Max Polarmint', titleCn: '马克斯薄荷', shortName: 'MP' },
|
||||||
|
{ name: 'blueberry', title: 'Blueberry', titleCn: '蓝莓', shortName: 'BB' },
|
||||||
|
{ name: 'citrus', title: 'Citrus', titleCn: '柑橘', shortName: 'CT' },
|
||||||
|
{ name: 'wintergreen', title: 'Wintergreen', titleCn: '冬绿薄荷', shortName: 'WG' },
|
||||||
|
{ name: 'cool-mint', title: 'COOL MINT', titleCn: '清凉薄荷', shortName: 'CM' },
|
||||||
|
{ name: 'juicy-peach', title: 'JUICY PEACH', titleCn: '多汁蜜桃', shortName: 'JP' },
|
||||||
|
{ name: 'orange', title: 'ORANGE', titleCn: '橙子', shortName: 'OR' },
|
||||||
|
{ name: 'peppermint', title: 'PEPPERMINT', titleCn: '胡椒薄荷', shortName: 'PP' },
|
||||||
|
{ name: 'spearmint', title: 'SPEARMINT', titleCn: '绿薄荷', shortName: 'SM' },
|
||||||
|
{ name: 'strawberry', title: 'STRAWBERRY', titleCn: '草莓', shortName: 'SB' },
|
||||||
|
{ name: 'watermelon', title: 'WATERMELON', titleCn: '西瓜', shortName: 'WM' },
|
||||||
|
{ name: 'coffee', title: 'COFFEE', titleCn: '咖啡', shortName: 'CF' },
|
||||||
|
{ name: 'lemonade', title: 'LEMONADE', titleCn: '柠檬水', shortName: 'LN' },
|
||||||
|
{ name: 'apple-mint', title: 'apple mint', titleCn: '苹果薄荷', shortName: 'AM' },
|
||||||
|
{ name: 'peach', title: 'PEACH', titleCn: '桃子', shortName: 'PC' },
|
||||||
|
{ name: 'mango', title: 'Mango', titleCn: '芒果', shortName: 'MG' },
|
||||||
|
{ name: 'ice-wintergreen', title: 'ICE WINTERGREEN', titleCn: '冰冬绿薄荷', shortName: 'IWG' },
|
||||||
|
{ name: 'pink-lemonade', title: 'Pink Lemonade', titleCn: '粉红柠檬水', shortName: 'PLN' },
|
||||||
|
{ name: 'blackcherry', title: 'Blackcherry', titleCn: '黑樱桃', shortName: 'BC' },
|
||||||
|
{ name: 'fresh-mint', title: 'fresh mint', titleCn: '清新薄荷', shortName: 'FM' },
|
||||||
|
{ name: 'strawberry-lychee', title: 'Strawberry Lychee', titleCn: '草莓荔枝', shortName: 'SBL' },
|
||||||
|
{ name: 'passion-fruit', title: 'Passion Fruit', titleCn: '百香果', shortName: 'PF' },
|
||||||
|
{ name: 'banana-lce', title: 'Banana lce', titleCn: '香蕉冰', shortName: 'BI' },
|
||||||
|
{ name: 'bubblegum', title: 'Bubblegum', titleCn: '泡泡糖', shortName: 'BG' },
|
||||||
|
{ name: 'mango-lce', title: 'Mango lce', titleCn: '芒果冰', shortName: 'MI' },
|
||||||
|
{ name: 'grape-lce', title: 'Grape lce', titleCn: '葡萄冰', shortName: 'GI' },
|
||||||
|
{ name: 'apple', title: 'apple', titleCn: '苹果', shortName: 'AP' },
|
||||||
|
{ name: 'grape', title: 'grape', titleCn: '葡萄', shortName: 'GR' },
|
||||||
|
{ name: 'cherry', title: 'cherry', titleCn: '樱桃', shortName: 'CH' },
|
||||||
|
{ name: 'lemon', title: 'lemon', titleCn: '柠檬', shortName: 'LM' },
|
||||||
|
{ name: 'razz', title: 'razz', titleCn: '覆盆子', shortName: 'RZ' },
|
||||||
|
{ name: 'pineapple', title: 'pineapple', titleCn: '菠萝', shortName: 'PA' },
|
||||||
|
{ name: 'berry', title: 'berry', titleCn: '浆果', shortName: 'BR' },
|
||||||
|
{ name: 'fruit', title: 'fruit', titleCn: '水果', shortName: 'FR' },
|
||||||
|
{ name: 'mint', title: 'mint', titleCn: '薄荷', shortName: 'MT' },
|
||||||
|
{ name: 'menthol', title: 'menthol', titleCn: '薄荷醇', shortName: 'MH' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const brandsData = [
|
||||||
|
{ name: 'yoone', title: 'Yoone', titleCn: '', shortName: 'YN' },
|
||||||
|
{ name: 'white-fox', title: 'White Fox', titleCn: '', shortName: 'WF' },
|
||||||
|
{ name: 'zyn', title: 'ZYN', titleCn: '', shortName: 'ZN' },
|
||||||
|
{ name: 'zonnic', title: 'Zonnic', titleCn: '', shortName: 'ZC' },
|
||||||
|
{ name: 'zolt', title: 'Zolt', titleCn: '', shortName: 'ZT' },
|
||||||
|
{ name: 'velo', title: 'Velo', titleCn: '', shortName: 'VL' },
|
||||||
|
{ name: 'lucy', title: 'Lucy', titleCn: '', shortName: 'LC' },
|
||||||
|
{ name: 'egp', title: 'EGP', titleCn: '', shortName: 'EP' },
|
||||||
|
{ name: 'bridge', title: 'Bridge', titleCn: '', shortName: 'BR' },
|
||||||
|
{ name: 'zex', title: 'ZEX', titleCn: '', shortName: 'ZX' },
|
||||||
|
{ name: 'sesh', title: 'Sesh', titleCn: '', shortName: 'SH' },
|
||||||
|
{ name: 'pablo', title: 'Pablo', titleCn: '', shortName: 'PB' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const strengthsData = [
|
||||||
|
{ name: '2mg', title: '2MG', titleCn: '2毫克', shortName: '2M' },
|
||||||
|
{ name: '3mg', title: '3MG', titleCn: '3毫克', shortName: '3M' },
|
||||||
|
{ name: '4mg', title: '4MG', titleCn: '4毫克', shortName: '4M' },
|
||||||
|
{ name: '6mg', title: '6MG', titleCn: '6毫克', shortName: '6M' },
|
||||||
|
{ name: '6.5mg', title: '6.5MG', titleCn: '6.5毫克', shortName: '6.5M' },
|
||||||
|
{ name: '9mg', title: '9MG', titleCn: '9毫克', shortName: '9M' },
|
||||||
|
{ name: '12mg', title: '12MG', titleCn: '12毫克', shortName: '12M' },
|
||||||
|
{ name: '16.5mg', title: '16.5MG', titleCn: '16.5毫克', shortName: '16.5M' },
|
||||||
|
{ name: '18mg', title: '18MG', titleCn: '18毫克', shortName: '18M' },
|
||||||
|
{ name: '30mg', title: '30MG', titleCn: '30毫克', shortName: '30M' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 初始化语言字典
|
||||||
|
const locales = [
|
||||||
|
{ name: 'zh-cn', title: '简体中文', titleCn: '简体中文', shortName: 'CN' },
|
||||||
|
{ name: 'en-us', title: 'English', titleCn: '英文', shortName: 'EN' },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const locale of locales) {
|
||||||
|
await this.createOrFindDict(dictRepository, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加示例翻译条目
|
||||||
|
const zhDict = await dictRepository.findOne({ where: { name: 'zh-cn' } });
|
||||||
|
const enDict = await dictRepository.findOne({ where: { name: 'en-us' } });
|
||||||
|
|
||||||
|
const translations = [
|
||||||
|
{ name: 'common-save', zh: '保存', en: 'Save' },
|
||||||
|
{ name: 'common-cancel', zh: '取消', en: 'Cancel' },
|
||||||
|
{ name: 'common-success', zh: '操作成功', en: 'Success' },
|
||||||
|
{ name: 'common-failure', zh: '操作失败', en: 'Failure' },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const t of translations) {
|
||||||
|
// 添加中文翻译
|
||||||
|
let item = await dictItemRepository.findOne({ where: { name: t.name, dict: { id: zhDict.id } } });
|
||||||
|
if (!item) {
|
||||||
|
await dictItemRepository.save({ name: t.name, title: t.zh, titleCn: t.zh, shortName: t.zh.substring(0, 2).toUpperCase(), dict: zhDict });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加英文翻译
|
||||||
|
item = await dictItemRepository.findOne({ where: { name: t.name, dict: { id: enDict.id } } });
|
||||||
|
if (!item) {
|
||||||
|
await dictItemRepository.save({ name: t.name, title: t.en, titleCn: t.en, shortName: t.en.substring(0, 2).toUpperCase(), dict: enDict });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const brandDict = await this.createOrFindDict(dictRepository, { name: 'brand', title: '品牌', titleCn: '品牌', shortName: 'BR' });
|
||||||
|
const flavorDict = await this.createOrFindDict(dictRepository, { name: 'flavor', title: '口味', titleCn: '口味', shortName: 'FL' });
|
||||||
|
const strengthDict = await this.createOrFindDict(dictRepository, { name: 'strength', title: '强度', titleCn: '强度', shortName: 'ST' });
|
||||||
|
|
||||||
|
// 遍历品牌数据
|
||||||
|
await this.seedDictItems(dictItemRepository, brandDict, brandsData);
|
||||||
|
|
||||||
|
// 遍历口味数据
|
||||||
|
await this.seedDictItems(dictItemRepository, flavorDict, flavorsData);
|
||||||
|
|
||||||
|
// 遍历强度数据
|
||||||
|
await this.seedDictItems(dictItemRepository, strengthDict, strengthsData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建或查找字典
|
||||||
|
* @param repo DictRepository
|
||||||
|
* @param dictInfo 字典信息
|
||||||
|
* @returns Dict 实例
|
||||||
|
*/
|
||||||
|
private async createOrFindDict(repo: any, dictInfo: { name: string; title: string; titleCn: string; shortName: string }): Promise<Dict> {
|
||||||
|
// 格式化 name
|
||||||
|
const formattedName = this.formatName(dictInfo.name);
|
||||||
|
let dict = await repo.findOne({ where: { name: formattedName } });
|
||||||
|
if (!dict) {
|
||||||
|
// 如果字典不存在,则使用格式化后的 name 创建新字典
|
||||||
|
dict = await repo.save({ name: formattedName, title: dictInfo.title, titleCn: dictInfo.titleCn, shortName: dictInfo.shortName });
|
||||||
|
}
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充字典项
|
||||||
|
* @param repo DictItemRepository
|
||||||
|
* @param dict 字典实例
|
||||||
|
* @param items 字典项数组
|
||||||
|
*/
|
||||||
|
private async seedDictItems(repo: any, dict: Dict, items: { name: string; title: string; titleCn: string; shortName: string }[]): Promise<void> {
|
||||||
|
for (const item of items) {
|
||||||
|
// 格式化 name
|
||||||
|
const formattedName = this.formatName(item.name);
|
||||||
|
const existingItem = await repo.findOne({ where: { name: formattedName, dict: { id: dict.id } } });
|
||||||
|
if (!existingItem) {
|
||||||
|
// 如果字典项不存在,则使用格式化后的 name 创建新字典项
|
||||||
|
await repo.save({ name: formattedName, title: item.title, titleCn: item.titleCn, shortName: item.shortName, dict });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { Seeder, SeederFactoryManager } from 'typeorm-extension';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { Template } from '../../entity/template.entity';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class TemplateSeeder
|
||||||
|
* @description 模板数据填充器,用于在数据库初始化时插入默认的模板数据.
|
||||||
|
*/
|
||||||
|
export default class TemplateSeeder implements Seeder {
|
||||||
|
/**
|
||||||
|
* @method run
|
||||||
|
* @description 执行数据填充操作.如果模板不存在,则创建它;如果存在,则更新它.
|
||||||
|
* @param {DataSource} dataSource - 数据源实例,用于获取 repository.
|
||||||
|
* @param {SeederFactoryManager} factoryManager - Seeder 工厂管理器.
|
||||||
|
*/
|
||||||
|
public async run(
|
||||||
|
dataSource: DataSource,
|
||||||
|
factoryManager: SeederFactoryManager
|
||||||
|
): Promise<any> {
|
||||||
|
// 获取 Template 实体的 repository
|
||||||
|
const templateRepository = dataSource.getRepository(Template);
|
||||||
|
|
||||||
|
const templates = [
|
||||||
|
{
|
||||||
|
name: 'product.sku',
|
||||||
|
value: '<%= it.brand %>-<%=it.category%>-<%= it.flavor %>-<%= it.strength %>-<%= it.humidity %>',
|
||||||
|
description: '产品SKU模板',
|
||||||
|
testData: JSON.stringify({
|
||||||
|
brand: 'Brand',
|
||||||
|
category: 'Category',
|
||||||
|
flavor: 'Flavor',
|
||||||
|
strength: '10mg',
|
||||||
|
humidity: 'Dry',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'product.title',
|
||||||
|
value: '<%= it.brand %> <%= it.flavor %> <%= it.strength %> <%= it.humidity %>',
|
||||||
|
description: '产品标题模板',
|
||||||
|
testData: JSON.stringify({
|
||||||
|
brand: 'Brand',
|
||||||
|
flavor: 'Flavor',
|
||||||
|
strength: '10mg',
|
||||||
|
humidity: 'Dry',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const t of templates) {
|
||||||
|
// 检查模板是否已存在
|
||||||
|
const existingTemplate = await templateRepository.findOne({
|
||||||
|
where: { name: t.name },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingTemplate) {
|
||||||
|
// 如果存在,则更新
|
||||||
|
existingTemplate.value = t.value;
|
||||||
|
existingTemplate.description = t.description;
|
||||||
|
existingTemplate.testData = t.testData;
|
||||||
|
await templateRepository.save(existingTemplate);
|
||||||
|
} else {
|
||||||
|
// 如果不存在,则创建并保存
|
||||||
|
const template = new Template();
|
||||||
|
template.name = t.name;
|
||||||
|
template.value = t.value;
|
||||||
|
template.description = t.description;
|
||||||
|
template.testData = t.testData;
|
||||||
|
await templateRepository.save(template);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
|
import { Rule, RuleType } from '@midwayjs/validate';
|
||||||
|
|
||||||
|
export class CreateAreaDTO {
|
||||||
|
@ApiProperty({ description: '编码' })
|
||||||
|
@Rule(RuleType.string().required())
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateAreaDTO {
|
||||||
|
@ApiProperty({ description: '编码', required: false })
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
code?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class QueryAreaDTO {
|
||||||
|
@ApiProperty({ description: '当前页', required: false, default: 1 })
|
||||||
|
@Rule(RuleType.number().integer().min(1).default(1))
|
||||||
|
currentPage?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '每页数量', required: false, default: 10 })
|
||||||
|
@Rule(RuleType.number().integer().min(1).default(10))
|
||||||
|
pageSize?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '关键词(名称或编码)', required: false })
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
keyword?: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
|
import { Rule, RuleType } from '@midwayjs/validate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量操作错误项
|
||||||
|
*/
|
||||||
|
export interface BatchErrorItem {
|
||||||
|
// 错误项标识(可以是ID、邮箱等)
|
||||||
|
identifier: string;
|
||||||
|
// 错误信息
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量操作结果基础接口
|
||||||
|
*/
|
||||||
|
export interface BatchOperationResult {
|
||||||
|
// 总处理数量
|
||||||
|
total: number;
|
||||||
|
// 成功处理数量
|
||||||
|
processed: number;
|
||||||
|
// 创建数量
|
||||||
|
created?: number;
|
||||||
|
// 更新数量
|
||||||
|
updated?: number;
|
||||||
|
// 删除数量
|
||||||
|
deleted?: number;
|
||||||
|
// 跳过的数量(如数据已存在或无需处理)
|
||||||
|
skipped?: number;
|
||||||
|
// 错误列表
|
||||||
|
errors: BatchErrorItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步操作结果接口
|
||||||
|
*/
|
||||||
|
export interface SyncOperationResult extends BatchOperationResult {
|
||||||
|
// 同步成功数量
|
||||||
|
synced: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量操作错误项DTO
|
||||||
|
*/
|
||||||
|
export class BatchErrorItemDTO {
|
||||||
|
@ApiProperty({ description: '错误项标识(如ID、邮箱等)', type: String })
|
||||||
|
@Rule(RuleType.string().required())
|
||||||
|
identifier: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '错误信息', type: String })
|
||||||
|
@Rule(RuleType.string().required())
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量操作结果基础DTO
|
||||||
|
*/
|
||||||
|
export class BatchOperationResultDTO {
|
||||||
|
@ApiProperty({ description: '总处理数量', type: Number })
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '成功处理数量', type: Number })
|
||||||
|
processed: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建数量', type: Number, required: false })
|
||||||
|
created?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新数量', type: Number, required: false })
|
||||||
|
updated?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '删除数量', type: Number, required: false })
|
||||||
|
deleted?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '跳过的数量', type: Number, required: false })
|
||||||
|
skipped?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '错误列表', type: [BatchErrorItemDTO] })
|
||||||
|
errors: BatchErrorItemDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步操作结果DTO
|
||||||
|
*/
|
||||||
|
export class SyncOperationResultDTO extends BatchOperationResultDTO {
|
||||||
|
@ApiProperty({ description: '同步成功数量', type: Number })
|
||||||
|
synced: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量创建DTO
|
||||||
|
*/
|
||||||
|
export class BatchCreateDTO<T = any> {
|
||||||
|
@ApiProperty({ description: '要创建的数据列表', type: Array })
|
||||||
|
@Rule(RuleType.array().required())
|
||||||
|
items: T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新DTO
|
||||||
|
*/
|
||||||
|
export class BatchUpdateDTO<T = any> {
|
||||||
|
@ApiProperty({ description: '要更新的数据列表', type: Array })
|
||||||
|
@Rule(RuleType.array().required())
|
||||||
|
items: T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除DTO
|
||||||
|
*/
|
||||||
|
export class BatchDeleteDTO {
|
||||||
|
@ApiProperty({ description: '要删除的ID列表', type: [String, Number] })
|
||||||
|
@Rule(RuleType.array().items(RuleType.alternatives().try(RuleType.string(), RuleType.number())).required())
|
||||||
|
ids: Array<string | number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量操作请求DTO(包含增删改)
|
||||||
|
*/
|
||||||
|
export class BatchOperationDTO<T = any> {
|
||||||
|
@ApiProperty({ description: '要创建的数据列表', type: Array, required: false })
|
||||||
|
@Rule(RuleType.array().optional())
|
||||||
|
create?: T[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '要更新的数据列表', type: Array, required: false })
|
||||||
|
@Rule(RuleType.array().optional())
|
||||||
|
update?: T[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '要删除的ID列表', type: [String, Number], required: false })
|
||||||
|
@Rule(RuleType.array().items(RuleType.alternatives().try(RuleType.string(), RuleType.number())).optional())
|
||||||
|
delete?: Array<string | number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页批量操作DTO
|
||||||
|
*/
|
||||||
|
export class PaginatedBatchOperationDTO<T = any> {
|
||||||
|
@ApiProperty({ description: '页码', type: Number, required: false, default: 1 })
|
||||||
|
@Rule(RuleType.number().integer().min(1).optional())
|
||||||
|
page?: number = 1;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '每页数量', type: Number, required: false, default: 100 })
|
||||||
|
@Rule(RuleType.number().integer().min(1).max(1000).optional())
|
||||||
|
pageSize?: number = 100;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '要创建的数据列表', type: Array, required: false })
|
||||||
|
@Rule(RuleType.array().optional())
|
||||||
|
create?: T[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '要更新的数据列表', type: Array, required: false })
|
||||||
|
@Rule(RuleType.array().optional())
|
||||||
|
update?: T[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '要删除的ID列表', type: [String, Number], required: false })
|
||||||
|
@Rule(RuleType.array().items(RuleType.alternatives().try(RuleType.string(), RuleType.number())).optional())
|
||||||
|
delete?: Array<string | number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步参数DTO
|
||||||
|
*/
|
||||||
|
export class SyncParamsDTO {
|
||||||
|
@ApiProperty({ description: '页码', type: Number, required: false, default: 1 })
|
||||||
|
@Rule(RuleType.number().integer().min(1).optional())
|
||||||
|
page?: number = 1;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '每页数量', type: Number, required: false, default: 100 })
|
||||||
|
@Rule(RuleType.number().integer().min(1).max(1000).optional())
|
||||||
|
pageSize?: number = 100;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '开始时间', type: String, required: false })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
startDate?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '结束时间', type: String, required: false })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
endDate?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '强制同步(忽略缓存)', type: Boolean, required: false, default: false })
|
||||||
|
@Rule(RuleType.boolean().optional())
|
||||||
|
force?: boolean = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询DTO
|
||||||
|
*/
|
||||||
|
export class BatchQueryDTO {
|
||||||
|
@ApiProperty({ description: 'ID列表', type: [String, Number] })
|
||||||
|
@Rule(RuleType.array().items(RuleType.alternatives().try(RuleType.string(), RuleType.number())).required())
|
||||||
|
ids: Array<string | number>;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '包含关联数据', type: Boolean, required: false, default: false })
|
||||||
|
@Rule(RuleType.boolean().optional())
|
||||||
|
includeRelations?: boolean = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量操作结果类(泛型支持)
|
||||||
|
*/
|
||||||
|
export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
|
||||||
|
@ApiProperty({ description: '操作成功的数据列表', type: Array })
|
||||||
|
data?: T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步操作结果类(泛型支持)
|
||||||
|
*/
|
||||||
|
export class SyncOperationResultDTOGeneric<T> extends SyncOperationResultDTO {
|
||||||
|
@ApiProperty({ description: '同步成功的数据列表', type: Array })
|
||||||
|
data?: T[];
|
||||||
|
}
|
||||||
|
|
@ -36,3 +36,35 @@ export class CustomerTagDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
tag: string;
|
tag: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CustomerDto {
|
||||||
|
@ApiProperty()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
site_id: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
avatar: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
tags: string[];
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
rate: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
state: string;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CustomerListResponseDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
@ApiProperty({ type: [CustomerDto] })
|
||||||
|
list: CustomerDto[];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { Rule, RuleType } from '@midwayjs/validate';
|
||||||
|
|
||||||
|
// 创建字典的数据传输对象
|
||||||
|
export class CreateDictDTO {
|
||||||
|
@Rule(RuleType.string().required())
|
||||||
|
name: string; // 字典名称
|
||||||
|
|
||||||
|
@Rule(RuleType.string().required())
|
||||||
|
title: string; // 字典标题
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新字典的数据传输对象
|
||||||
|
export class UpdateDictDTO {
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
name?: string; // 字典名称 (可选)
|
||||||
|
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
title?: string; // 字典标题 (可选)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建字典项的数据传输对象
|
||||||
|
export class CreateDictItemDTO {
|
||||||
|
@Rule(RuleType.string().required())
|
||||||
|
name: string; // 字典项名称
|
||||||
|
|
||||||
|
@Rule(RuleType.string().required())
|
||||||
|
title: string; // 字典项标题
|
||||||
|
|
||||||
|
@Rule(RuleType.string().allow('').allow(null))
|
||||||
|
titleCN?: string; // 字典项中文标题 (可选)
|
||||||
|
|
||||||
|
@Rule(RuleType.string().allow('').allow(null))
|
||||||
|
image?: string; // 图片 (可选)
|
||||||
|
|
||||||
|
@Rule(RuleType.string().allow('').allow(null))
|
||||||
|
shortName?: string; // 简称 (可选)
|
||||||
|
|
||||||
|
@Rule(RuleType.number().required())
|
||||||
|
dictId: number; // 所属字典的ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新字典项的数据传输对象
|
||||||
|
export class UpdateDictItemDTO {
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
name?: string; // 字典项名称 (可选)
|
||||||
|
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
title?: string; // 字典项标题 (可选)
|
||||||
|
|
||||||
|
@Rule(RuleType.string().allow('').allow(null))
|
||||||
|
titleCN?: string; // 字典项中文标题 (可选)
|
||||||
|
|
||||||
|
@Rule(RuleType.string().allow(null))
|
||||||
|
value?: string; // 字典项值 (可选)
|
||||||
|
|
||||||
|
@Rule(RuleType.string().allow('').allow(null))
|
||||||
|
image?: string; // 图片 (可选)
|
||||||
|
|
||||||
|
@Rule(RuleType.string().allow('').allow(null))
|
||||||
|
shortName?: string; // 简称 (可选)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ export type PackagingType =
|
||||||
// | PackagingCourierPak
|
// | PackagingCourierPak
|
||||||
// | PackagingEnvelope;
|
// | PackagingEnvelope;
|
||||||
|
|
||||||
// 定义包装类型的枚举,用于 API 文档描述
|
// 定义包装类型的枚举,用于 API 文档描述
|
||||||
export enum PackagingTypeEnum {
|
export enum PackagingTypeEnum {
|
||||||
Pallet = 'pallet',
|
Pallet = 'pallet',
|
||||||
Package = 'package',
|
Package = 'package',
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,8 @@ export class QueryOrderDTO {
|
||||||
externalOrderId: string;
|
externalOrderId: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.number())
|
||||||
siteId: string;
|
siteId: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string().allow(''))
|
@Rule(RuleType.string().allow(''))
|
||||||
|
|
@ -92,7 +92,7 @@ export class QueryOrderDTO {
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
payment_method: string;
|
payment_method: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '仅订阅订单(父订阅订单或包含订阅商品)' })
|
@ApiProperty({ description: '仅订阅订单(父订阅订单或包含订阅商品)' })
|
||||||
@Rule(RuleType.bool().default(false))
|
@Rule(RuleType.bool().default(false))
|
||||||
isSubscriptionOnly?: boolean;
|
isSubscriptionOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -115,8 +115,8 @@ export class QueryOrderSalesDTO {
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.number())
|
||||||
siteId: string;
|
siteId: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
|
|
@ -156,8 +156,8 @@ export class QueryOrderItemDTO {
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string().allow(''))
|
@Rule(RuleType.number().allow(''))
|
||||||
siteId: string;
|
siteId: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string().allow(''))
|
@Rule(RuleType.string().allow(''))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,31 @@
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import { Rule, RuleType } from '@midwayjs/validate';
|
import { Rule, RuleType } from '@midwayjs/validate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 属性输入DTO
|
||||||
|
*/
|
||||||
|
export class AttributeInputDTO {
|
||||||
|
@ApiProperty({ description: '属性字典标识', example: 'brand' })
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
dictName?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '属性值', example: 'ZYN' })
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
value?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '属性ID', example: 1 })
|
||||||
|
@Rule(RuleType.number())
|
||||||
|
id?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '属性名称', example: 'ZYN' })
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '属性显示名称', example: 'ZYN' })
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO 用于创建产品
|
* DTO 用于创建产品
|
||||||
*/
|
*/
|
||||||
|
|
@ -13,162 +38,248 @@ export class CreateProductDTO {
|
||||||
@Rule(RuleType.string().required().empty({ message: '产品名称不能为空' }))
|
@Rule(RuleType.string().required().empty({ message: '产品名称不能为空' }))
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品中文名称', required: false })
|
||||||
|
@Rule(RuleType.string().allow('').optional())
|
||||||
|
nameCn?: string;
|
||||||
|
|
||||||
@ApiProperty({ example: '产品描述', description: '产品描述' })
|
@ApiProperty({ example: '产品描述', description: '产品描述' })
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
@ApiProperty({ example: '1', description: '分类 ID' })
|
@ApiProperty({ example: '产品简短描述', description: '产品简短描述' })
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.string().optional())
|
||||||
categoryId: number;
|
shortDescription?: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ description: '产品 SKU', required: false })
|
||||||
@Rule(RuleType.number())
|
|
||||||
strengthId: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@Rule(RuleType.number())
|
|
||||||
flavorsId: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
humidity: string;
|
sku?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分类ID (DictItem ID)', required: false })
|
||||||
|
@Rule(RuleType.number())
|
||||||
|
categoryId?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '站点 SKU 列表', type: 'array', required: false })
|
||||||
|
@Rule(RuleType.array().items(RuleType.string()).optional())
|
||||||
|
siteSkus?: string[];
|
||||||
|
|
||||||
|
// 通用属性输入(通过 attributes 统一提交品牌/口味/强度/尺寸/干湿等)
|
||||||
|
@ApiProperty({ description: '属性列表', type: 'array' })
|
||||||
|
@Rule(RuleType.array().required())
|
||||||
|
attributes: AttributeInputDTO[];
|
||||||
|
|
||||||
|
// 商品价格
|
||||||
|
@ApiProperty({ description: '价格', example: 99.99, required: false })
|
||||||
|
@Rule(RuleType.number())
|
||||||
|
price?: number;
|
||||||
|
|
||||||
|
// 促销价格
|
||||||
|
@ApiProperty({ description: '促销价格', example: 99.99, required: false })
|
||||||
|
@Rule(RuleType.number())
|
||||||
|
promotionPrice?: number;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 商品类型(默认 single; bundle 需手动设置组成)
|
||||||
|
@ApiProperty({ description: '商品类型', enum: ['single', 'bundle'], default: 'single', required: false })
|
||||||
|
@Rule(RuleType.string().valid('single', 'bundle').default('single'))
|
||||||
|
type?: string;
|
||||||
|
|
||||||
|
// 仅当 type 为 'bundle' 时,才需要提供 components
|
||||||
|
@ApiProperty({ description: '产品组成', type: 'array', required: false })
|
||||||
|
@Rule(
|
||||||
|
RuleType.array()
|
||||||
|
.items(
|
||||||
|
RuleType.object({
|
||||||
|
sku: RuleType.string().required(),
|
||||||
|
quantity: RuleType.number().required(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.when('type', {
|
||||||
|
is: 'bundle',
|
||||||
|
then: RuleType.array().required(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
components?: { sku: string; quantity: number }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO 用于更新产品
|
* DTO 用于更新产品
|
||||||
*/
|
*/
|
||||||
export class UpdateProductDTO extends CreateProductDTO {
|
export class UpdateProductDTO {
|
||||||
@ApiProperty({ example: 'ZYN 6MG WINTERGREEN', description: '产品名称' })
|
@ApiProperty({ example: 'ZYN 6MG WINTERGREEN', description: '产品名称' })
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
name: string;
|
name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品中文名称', required: false })
|
||||||
|
@Rule(RuleType.string().allow('').optional())
|
||||||
|
nameCn?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: '产品描述', description: '产品描述' })
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: '产品简短描述', description: '产品简短描述' })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
shortDescription?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品 SKU', required: false })
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
sku?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分类ID (DictItem ID)', required: false })
|
||||||
|
@Rule(RuleType.number())
|
||||||
|
categoryId?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '站点 SKU 列表', type: 'array', required: false })
|
||||||
|
@Rule(RuleType.array().items(RuleType.string()).optional())
|
||||||
|
siteSkus?: string[];
|
||||||
|
|
||||||
|
// 商品价格
|
||||||
|
@ApiProperty({ description: '价格', example: 99.99, required: false })
|
||||||
|
@Rule(RuleType.number())
|
||||||
|
price?: number;
|
||||||
|
|
||||||
|
// 促销价格
|
||||||
|
@ApiProperty({ description: '促销价格', example: 99.99, required: false })
|
||||||
|
@Rule(RuleType.number())
|
||||||
|
promotionPrice?: number;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 属性更新(可选, 支持增量替换指定字典的属性项)
|
||||||
|
@ApiProperty({ description: '属性列表', type: 'array', required: false })
|
||||||
|
@Rule(RuleType.array())
|
||||||
|
attributes?: AttributeInputDTO[];
|
||||||
|
|
||||||
|
// 商品类型(single 或 bundle)
|
||||||
|
@ApiProperty({ description: '商品类型', enum: ['single', 'bundle'], required: false })
|
||||||
|
@Rule(RuleType.string().valid('single', 'bundle'))
|
||||||
|
type?: string;
|
||||||
|
|
||||||
|
// 仅当 type 为 'bundle' 时,才需要提供 components
|
||||||
|
@ApiProperty({ description: '产品组成', type: 'array', required: false })
|
||||||
|
@Rule(
|
||||||
|
RuleType.array()
|
||||||
|
.items(
|
||||||
|
RuleType.object({
|
||||||
|
sku: RuleType.string().required(),
|
||||||
|
quantity: RuleType.number().required(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.when('type', {
|
||||||
|
is: 'bundle',
|
||||||
|
then: RuleType.array().optional(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
components?: { sku: string; quantity: number }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO 用于批量更新产品属性
|
||||||
|
*/
|
||||||
|
export class BatchUpdateProductDTO {
|
||||||
|
@ApiProperty({ description: '产品ID列表', type: 'array', required: true })
|
||||||
|
@Rule(RuleType.array().items(RuleType.number()).required().min(1))
|
||||||
|
ids: number[];
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'ZYN 6MG WINTERGREEN', description: '产品名称', required: false })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品中文名称', required: false })
|
||||||
|
@Rule(RuleType.string().allow('').optional())
|
||||||
|
nameCn?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: '产品描述', description: '产品描述', required: false })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: '产品简短描述', description: '产品简短描述', required: false })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
shortDescription?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品 SKU', required: false })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
sku?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分类ID (DictItem ID)', required: false })
|
||||||
|
@Rule(RuleType.number().optional())
|
||||||
|
categoryId?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '站点 SKU 列表', type: 'array', required: false })
|
||||||
|
@Rule(RuleType.array().items(RuleType.string()).optional())
|
||||||
|
siteSkus?: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '价格', example: 99.99, required: false })
|
||||||
|
@Rule(RuleType.number().optional())
|
||||||
|
price?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '促销价格', example: 99.99, required: false })
|
||||||
|
@Rule(RuleType.number().optional())
|
||||||
|
promotionPrice?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '属性列表', type: 'array', required: false })
|
||||||
|
@Rule(RuleType.array().optional())
|
||||||
|
attributes?: AttributeInputDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '商品类型', enum: ['single', 'bundle'], required: false })
|
||||||
|
@Rule(RuleType.string().valid('single', 'bundle').optional())
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO 用于批量删除产品
|
||||||
|
*/
|
||||||
|
export class BatchDeleteProductDTO {
|
||||||
|
@ApiProperty({ description: '产品ID列表', type: 'array', required: true })
|
||||||
|
@Rule(RuleType.array().items(RuleType.number()).required().min(1))
|
||||||
|
ids: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO 用于创建分类属性绑定
|
||||||
|
*/
|
||||||
|
export class CreateCategoryAttributeDTO {
|
||||||
|
@ApiProperty({ description: '分类字典项ID', example: 1 })
|
||||||
|
@Rule(RuleType.number().required())
|
||||||
|
categoryItemId: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '属性字典ID列表', example: [2, 3] })
|
||||||
|
@Rule(RuleType.array().items(RuleType.number()).required())
|
||||||
|
attributeDictIds: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO 用于分页查询产品
|
* DTO 用于分页查询产品
|
||||||
*/
|
*/
|
||||||
export class QueryProductDTO {
|
export class QueryProductDTO {
|
||||||
@ApiProperty({ example: '1', description: '页码' })
|
@ApiProperty({ description: '当前页', example: 1 })
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number().default(1))
|
||||||
current: number;
|
current: number;
|
||||||
|
|
||||||
@ApiProperty({ example: '10', description: '每页大小' })
|
@ApiProperty({ description: '每页数量', example: 10 })
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number().default(10))
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
|
|
||||||
@ApiProperty({ example: 'ZYN', description: '关键字' })
|
@ApiProperty({ description: '搜索关键字', required: false })
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
name: string;
|
name?: string;
|
||||||
|
|
||||||
@ApiProperty({ example: '1', description: '分类 ID' })
|
@ApiProperty({ description: '分类ID', required: false })
|
||||||
@Rule(RuleType.string())
|
|
||||||
categoryId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DTO 用于创建分类
|
|
||||||
*/
|
|
||||||
export class CreateCategoryDTO {
|
|
||||||
@ApiProperty({ example: 'ZYN', description: '分类名称', required: true })
|
|
||||||
@Rule(RuleType.string().required().empty({ message: '分类名称不能为空' }))
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@Rule(RuleType.string().required().empty({ message: 'key不能为空' }))
|
|
||||||
unique_key: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DTO 用于更新分类
|
|
||||||
*/
|
|
||||||
export class UpdateCategoryDTO {
|
|
||||||
@ApiProperty({ example: 'ZYN', description: '分类名称' })
|
|
||||||
@Rule(RuleType.string())
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DTO 用于查询分类(支持分页)
|
|
||||||
*/
|
|
||||||
export class QueryCategoryDTO {
|
|
||||||
@ApiProperty({ example: '1', description: '页码' })
|
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number())
|
||||||
current: number; // 页码
|
categoryId?: number;
|
||||||
|
|
||||||
@ApiProperty({ example: '10', description: '每页大小' })
|
@ApiProperty({ description: '品牌ID', required: false })
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number())
|
||||||
pageSize: number; // 每页大小
|
brandId?: number;
|
||||||
|
|
||||||
@ApiProperty({ example: 'ZYN', description: '关键字' })
|
@ApiProperty({ description: '排序字段', required: false })
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
name: string; // 搜索关键字(支持模糊查询)
|
sortField?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '排序方式', required: false })
|
||||||
|
@Rule(RuleType.string().valid('ascend', 'descend'))
|
||||||
|
sortOrder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreateFlavorsDTO {
|
|
||||||
@ApiProperty({ example: 'ZYN', description: '分类名称', required: true })
|
|
||||||
@Rule(RuleType.string().required().empty({ message: '分类名称不能为空' }))
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@Rule(RuleType.string().required().empty({ message: 'key不能为空' }))
|
|
||||||
unique_key: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpdateFlavorsDTO {
|
|
||||||
@ApiProperty({ example: 'ZYN', description: '分类名称' })
|
|
||||||
@Rule(RuleType.string())
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class QueryFlavorsDTO {
|
|
||||||
@ApiProperty({ example: '1', description: '页码' })
|
|
||||||
@Rule(RuleType.number())
|
|
||||||
current: number; // 页码
|
|
||||||
|
|
||||||
@ApiProperty({ example: '10', description: '每页大小' })
|
|
||||||
@Rule(RuleType.number())
|
|
||||||
pageSize: number; // 每页大小
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'ZYN', description: '关键字' })
|
|
||||||
@Rule(RuleType.string())
|
|
||||||
name: string; // 搜索关键字(支持模糊查询)
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CreateStrengthDTO {
|
|
||||||
@ApiProperty({ example: 'ZYN', description: '分类名称', required: true })
|
|
||||||
@Rule(RuleType.string().required().empty({ message: '分类名称不能为空' }))
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@Rule(RuleType.string().required().empty({ message: 'key不能为空' }))
|
|
||||||
unique_key: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpdateStrengthDTO {
|
|
||||||
@ApiProperty({ example: 'ZYN', description: '分类名称' })
|
|
||||||
@Rule(RuleType.string())
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class QueryStrengthDTO {
|
|
||||||
@ApiProperty({ example: '1', description: '页码' })
|
|
||||||
@Rule(RuleType.number())
|
|
||||||
current: number; // 页码
|
|
||||||
|
|
||||||
@ApiProperty({ example: '10', description: '每页大小' })
|
|
||||||
@Rule(RuleType.number())
|
|
||||||
pageSize: number; // 每页大小
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'ZYN', description: '关键字' })
|
|
||||||
@Rule(RuleType.string())
|
|
||||||
name: string; // 搜索关键字(支持模糊查询)
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SkuItemDTO {
|
|
||||||
@ApiProperty({ description: '产品 ID' })
|
|
||||||
productId: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: 'sku 编码' })
|
|
||||||
sku: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BatchSetSkuDTO {
|
|
||||||
@ApiProperty({ description: 'sku 数据列表', type: [SkuItemDTO] })
|
|
||||||
skus: SkuItemDTO[];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import { Category } from '../entity/category.entity';
|
|
||||||
import { Order } from '../entity/order.entity';
|
import { Order } from '../entity/order.entity';
|
||||||
import { Product } from '../entity/product.entity';
|
import { Product } from '../entity/product.entity';
|
||||||
import { StockPoint } from '../entity/stock_point.entity';
|
import { StockPoint } from '../entity/stock_point.entity';
|
||||||
|
|
@ -12,22 +11,20 @@ import { OrderStatusCountDTO } from './order.dto';
|
||||||
import { SiteConfig } from './site.dto';
|
import { SiteConfig } from './site.dto';
|
||||||
import { PurchaseOrderDTO, StockDTO, StockRecordDTO } from './stock.dto';
|
import { PurchaseOrderDTO, StockDTO, StockRecordDTO } from './stock.dto';
|
||||||
import { LoginResDTO } from './user.dto';
|
import { LoginResDTO } from './user.dto';
|
||||||
import { WpProductDTO } from './wp_product.dto';
|
|
||||||
import { OrderSale } from '../entity/order_sale.entity';
|
import { OrderSale } from '../entity/order_sale.entity';
|
||||||
import { Service } from '../entity/service.entity';
|
import { Service } from '../entity/service.entity';
|
||||||
import { RateDTO } from './freightcom.dto';
|
import { RateDTO } from './freightcom.dto';
|
||||||
import { ShippingAddress } from '../entity/shipping_address.entity';
|
import { ShippingAddress } from '../entity/shipping_address.entity';
|
||||||
import { OrderItem } from '../entity/order_item.entity';
|
import { OrderItem } from '../entity/order_item.entity';
|
||||||
import { OrderRefundItem } from '../entity/order_retund_item.entity';
|
import { OrderRefundItem } from '../entity/order_refund_item.entity';
|
||||||
import { OrderNote } from '../entity/order_note.entity';
|
import { OrderNote } from '../entity/order_note.entity';
|
||||||
import { PaymentMethodDTO } from './logistics.dto';
|
import { PaymentMethodDTO } from './logistics.dto';
|
||||||
import { Flavors } from '../entity/flavors.entity';
|
|
||||||
import { Strength } from '../entity/strength.entity';
|
|
||||||
import { Subscription } from '../entity/subscription.entity';
|
import { Subscription } from '../entity/subscription.entity';
|
||||||
|
import { Dict } from '../entity/dict.entity';
|
||||||
|
|
||||||
export class BooleanRes extends SuccessWrapper(Boolean) {}
|
export class BooleanRes extends SuccessWrapper(Boolean) {}
|
||||||
//网站配置返回数据
|
//网站配置返回数据
|
||||||
export class WpSitesResponse extends SuccessArrayWrapper(SiteConfig) {}
|
export class SitesResponse extends SuccessArrayWrapper(SiteConfig) {}
|
||||||
//产品分页数据
|
//产品分页数据
|
||||||
export class ProductPaginatedResponse extends PaginatedWrapper(Product) {}
|
export class ProductPaginatedResponse extends PaginatedWrapper(Product) {}
|
||||||
//产品分页返回数据
|
//产品分页返回数据
|
||||||
|
|
@ -35,27 +32,49 @@ export class ProductListRes extends SuccessWrapper(ProductPaginatedResponse) {}
|
||||||
//产品返回数据
|
//产品返回数据
|
||||||
export class ProductRes extends SuccessWrapper(Product) {}
|
export class ProductRes extends SuccessWrapper(Product) {}
|
||||||
export class ProductsRes extends SuccessArrayWrapper(Product) {}
|
export class ProductsRes extends SuccessArrayWrapper(Product) {}
|
||||||
//产品分类返分页数据
|
//产品品牌返分页数据
|
||||||
export class CategoryPaginatedResponse extends PaginatedWrapper(Category) {}
|
export class BrandPaginatedResponse extends PaginatedWrapper(Dict) {}
|
||||||
export class FlavorsPaginatedResponse extends PaginatedWrapper(Flavors) {}
|
//产品品牌返分页返回数据
|
||||||
export class StrengthPaginatedResponse extends PaginatedWrapper(Strength) {}
|
export class ProductBrandListRes extends SuccessWrapper(
|
||||||
//产品分类返分页返回数据
|
BrandPaginatedResponse
|
||||||
export class ProductCatListRes extends SuccessWrapper(
|
|
||||||
CategoryPaginatedResponse
|
|
||||||
) {}
|
) {}
|
||||||
//产品分类返所有数据
|
//产品品牌返所有数据
|
||||||
export class ProductCatAllRes extends SuccessArrayWrapper(Category) {}
|
export class ProductBrandAllRes extends SuccessArrayWrapper(Dict) {}
|
||||||
//产品分类返回数据
|
//产品品牌返回数据
|
||||||
export class ProductCatRes extends SuccessWrapper(Category) {}
|
export class ProductBrandRes extends SuccessWrapper(Dict) {}
|
||||||
|
|
||||||
//产品分页数据
|
//产品口味返分页数据
|
||||||
export class WpProductPaginatedResponse extends PaginatedWrapper(
|
export class FlavorsPaginatedResponse extends PaginatedWrapper(Dict) {}
|
||||||
WpProductDTO
|
//产品口味返分页返回数据
|
||||||
|
export class ProductFlavorsListRes extends SuccessWrapper(
|
||||||
|
FlavorsPaginatedResponse
|
||||||
) {}
|
) {}
|
||||||
//产品分页返回数据
|
//产品口味返所有数据
|
||||||
export class WpProductListRes extends SuccessWrapper(
|
export class ProductFlavorsAllRes extends SuccessArrayWrapper(Dict) {}
|
||||||
WpProductPaginatedResponse
|
//产品口味返回数据
|
||||||
|
export class ProductFlavorsRes extends SuccessWrapper(Dict) {}
|
||||||
|
|
||||||
|
//产品规格返分页数据
|
||||||
|
export class StrengthPaginatedResponse extends PaginatedWrapper(Dict) {}
|
||||||
|
//产品规格返分页返回数据
|
||||||
|
export class ProductStrengthListRes extends SuccessWrapper(
|
||||||
|
StrengthPaginatedResponse
|
||||||
) {}
|
) {}
|
||||||
|
//产品规格返所有数据
|
||||||
|
export class ProductStrengthAllRes extends SuccessArrayWrapper(Dict) {}
|
||||||
|
//产品规格返返回数据
|
||||||
|
export class ProductStrengthRes extends SuccessWrapper(Dict) {}
|
||||||
|
|
||||||
|
// 产品尺寸返分页数据
|
||||||
|
export class SizePaginatedResponse extends PaginatedWrapper(Dict) {}
|
||||||
|
// 产品尺寸返分页返回数据
|
||||||
|
export class ProductSizeListRes extends SuccessWrapper(
|
||||||
|
SizePaginatedResponse
|
||||||
|
) {}
|
||||||
|
// 产品尺寸返所有数据
|
||||||
|
export class ProductSizeAllRes extends SuccessArrayWrapper(Dict) {}
|
||||||
|
// 产品尺寸返回数据
|
||||||
|
export class ProductSizeRes extends SuccessWrapper(Dict) {}
|
||||||
|
|
||||||
export class LoginRes extends SuccessWrapper(LoginResDTO) {}
|
export class LoginRes extends SuccessWrapper(LoginResDTO) {}
|
||||||
export class StockPaginatedRespone extends PaginatedWrapper(StockDTO) {}
|
export class StockPaginatedRespone extends PaginatedWrapper(StockDTO) {}
|
||||||
|
|
@ -119,7 +138,7 @@ export class PaymentMethodListRes extends SuccessArrayWrapper(
|
||||||
PaymentMethodDTO
|
PaymentMethodDTO
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// 订阅分页数据(列表 + 总数等分页信息)
|
// 订阅分页数据(列表 + 总数等分页信息)
|
||||||
export class SubscriptionPaginatedResponse extends PaginatedWrapper(Subscription) {}
|
export class SubscriptionPaginatedResponse extends PaginatedWrapper(Subscription) {}
|
||||||
// 订阅分页返回数据(统一成功包装)
|
// 订阅分页返回数据(统一成功包装)
|
||||||
export class SubscriptionListRes extends SuccessWrapper(SubscriptionPaginatedResponse) {}
|
export class SubscriptionListRes extends SuccessWrapper(SubscriptionPaginatedResponse) {}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,429 @@
|
||||||
|
// Shopyy 平台原始数据类型定义
|
||||||
|
// 仅包含当前映射逻辑所需字段以保持简洁与类型安全
|
||||||
|
export interface ShopyyTag {
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
// 产品类型
|
||||||
|
export interface ShopyyProduct {
|
||||||
|
// 产品主键
|
||||||
|
id: number;
|
||||||
|
// 产品名称或标题
|
||||||
|
name?: string;
|
||||||
|
title?: string;
|
||||||
|
// 产品类型
|
||||||
|
product_type?: string | number;
|
||||||
|
// 产品状态数值 1为发布 其他为草稿
|
||||||
|
status: number;
|
||||||
|
// 变体信息
|
||||||
|
variant?: {
|
||||||
|
sku?: string;
|
||||||
|
price?: string;
|
||||||
|
};
|
||||||
|
// 价格
|
||||||
|
special_price?: string;
|
||||||
|
price?: string;
|
||||||
|
// 库存追踪标识 1表示跟踪
|
||||||
|
inventory_tracking?: number;
|
||||||
|
// 库存数量
|
||||||
|
inventory_quantity?: number;
|
||||||
|
// 图片列表
|
||||||
|
images?: Array<{
|
||||||
|
id?: number;
|
||||||
|
src: string;
|
||||||
|
alt?: string;
|
||||||
|
position?: string | number;
|
||||||
|
}>;
|
||||||
|
// 主图
|
||||||
|
image?: {
|
||||||
|
src: string;
|
||||||
|
file_name?: string;
|
||||||
|
alt?: string;
|
||||||
|
file_size?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
id?: number;
|
||||||
|
position?: number | string;
|
||||||
|
file_type?: string;
|
||||||
|
};
|
||||||
|
// 标签
|
||||||
|
tags?: ShopyyTag[];
|
||||||
|
// 变体列表
|
||||||
|
variants?: ShopyyVariant[];
|
||||||
|
// 分类集合
|
||||||
|
collections?: Array<{ id?: number; title?: string }>;
|
||||||
|
// 规格选项列表
|
||||||
|
options?: Array<{
|
||||||
|
id?: number;
|
||||||
|
position?: number | string;
|
||||||
|
option_name?: string;
|
||||||
|
values?: Array<{ option_value?: string; id?: number; position?: number }>;
|
||||||
|
}>;
|
||||||
|
// 发布与标识
|
||||||
|
published_at?: string;
|
||||||
|
handle?: string;
|
||||||
|
spu?: string;
|
||||||
|
// 创建与更新时间
|
||||||
|
created_at?: string | number;
|
||||||
|
updated_at?: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变体类型
|
||||||
|
export interface ShopyyVariant {
|
||||||
|
id: number;
|
||||||
|
sku?: string;
|
||||||
|
price?: string;
|
||||||
|
special_price?: string;
|
||||||
|
inventory_tracking?: number;
|
||||||
|
inventory_quantity?: number;
|
||||||
|
available?: number;
|
||||||
|
barcode?: string;
|
||||||
|
weight?: number;
|
||||||
|
image?: { src: string; id?: number; file_name?: string; alt?: string; position?: number | string };
|
||||||
|
position?: number | string;
|
||||||
|
sku_code?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单类型
|
||||||
|
export interface ShopyyOrder {
|
||||||
|
// 主键与外部ID
|
||||||
|
id?: number;
|
||||||
|
order_id?: number;
|
||||||
|
// 订单号
|
||||||
|
order_number?: string;
|
||||||
|
order_sn?: string;
|
||||||
|
// 状态
|
||||||
|
status?: number | string;
|
||||||
|
order_status?: number | string;
|
||||||
|
// 币种
|
||||||
|
currency_code?: string;
|
||||||
|
currency?: string;
|
||||||
|
// 金额
|
||||||
|
total_price?: string | number;
|
||||||
|
total_amount?: string | number;
|
||||||
|
current_total_price?: string | number;
|
||||||
|
current_subtotal_price?: string | number;
|
||||||
|
current_shipping_price?: string | number;
|
||||||
|
current_tax_price?: string | number;
|
||||||
|
current_coupon_price?: string | number;
|
||||||
|
current_payment_price?: string | number;
|
||||||
|
// 客户ID
|
||||||
|
customer_id?: number;
|
||||||
|
user_id?: number;
|
||||||
|
// 客户信息
|
||||||
|
customer_name?: string;
|
||||||
|
firstname?: string;
|
||||||
|
lastname?: string;
|
||||||
|
customer_email?: string;
|
||||||
|
email?: string;
|
||||||
|
// 地址字段
|
||||||
|
billing_address?: {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
name?: string;
|
||||||
|
company?: string;
|
||||||
|
phone?: string;
|
||||||
|
address1?: string;
|
||||||
|
address2?: string;
|
||||||
|
city?: string;
|
||||||
|
province?: string;
|
||||||
|
zip?: string;
|
||||||
|
country_name?: string;
|
||||||
|
country_code?: string;
|
||||||
|
};
|
||||||
|
shipping_address?: {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
name?: string;
|
||||||
|
company?: string;
|
||||||
|
phone?: string;
|
||||||
|
address1?: string;
|
||||||
|
address2?: string;
|
||||||
|
city?: string;
|
||||||
|
province?: string;
|
||||||
|
zip?: string;
|
||||||
|
country_name?: string;
|
||||||
|
country_code?: string;
|
||||||
|
} | string;
|
||||||
|
telephone?: string;
|
||||||
|
payment_address?: string;
|
||||||
|
payment_city?: string;
|
||||||
|
payment_zone?: string;
|
||||||
|
payment_postcode?: string;
|
||||||
|
payment_country?: string;
|
||||||
|
shipping_city?: string;
|
||||||
|
shipping_zone?: string;
|
||||||
|
shipping_postcode?: string;
|
||||||
|
shipping_country?: string;
|
||||||
|
// 订单项集合
|
||||||
|
products?: Array<{
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
product_title?: string;
|
||||||
|
product_id?: number;
|
||||||
|
quantity?: number;
|
||||||
|
price?: string | number;
|
||||||
|
sku?: string;
|
||||||
|
sku_code?: string;
|
||||||
|
}>;
|
||||||
|
// 支付方式
|
||||||
|
payment_method?: string;
|
||||||
|
payment_id?: number;
|
||||||
|
payment_cards?: Array<{
|
||||||
|
store_id?: number;
|
||||||
|
card_len?: number;
|
||||||
|
card_suffix?: number;
|
||||||
|
year?: number;
|
||||||
|
payment_status?: number;
|
||||||
|
created_at?: number;
|
||||||
|
month?: number;
|
||||||
|
updated_at?: number;
|
||||||
|
payment_id?: number;
|
||||||
|
payment_interface?: string;
|
||||||
|
card_prefix?: number;
|
||||||
|
id?: number;
|
||||||
|
order_id?: number;
|
||||||
|
card?: string;
|
||||||
|
transaction_no?: string;
|
||||||
|
}>;
|
||||||
|
fulfillments?: Array<{
|
||||||
|
payment_tracking_status?: number;
|
||||||
|
note?: string;
|
||||||
|
updated_at?: number;
|
||||||
|
courier_code?: string;
|
||||||
|
courier_id?: number;
|
||||||
|
created_at?: number;
|
||||||
|
tracking_number?: string;
|
||||||
|
id?: number;
|
||||||
|
tracking_company?: string;
|
||||||
|
payment_tracking_result?: string;
|
||||||
|
payment_tracking_at?: number;
|
||||||
|
products?: Array<{ order_product_id?: number; quantity?: number; updated_at?: number; created_at?: number; id?: number }>;
|
||||||
|
}>;
|
||||||
|
shipping_zone_plans?: Array<{
|
||||||
|
shipping_price?: number | string;
|
||||||
|
updated_at?: number;
|
||||||
|
created_at?: number;
|
||||||
|
id?: number;
|
||||||
|
shipping_zone_name?: string;
|
||||||
|
shipping_zone_id?: number;
|
||||||
|
shipping_zone_plan_id?: number;
|
||||||
|
shipping_zone_plan_name?: string;
|
||||||
|
}>;
|
||||||
|
transaction?: {
|
||||||
|
note?: string;
|
||||||
|
amount?: number | string;
|
||||||
|
created_at?: number;
|
||||||
|
merchant_id?: string;
|
||||||
|
payment_type?: string;
|
||||||
|
merchant_account?: string;
|
||||||
|
updated_at?: number;
|
||||||
|
payment_id?: number;
|
||||||
|
admin_id?: number;
|
||||||
|
admin_name?: string;
|
||||||
|
id?: number;
|
||||||
|
payment_method?: string;
|
||||||
|
transaction_no?: string;
|
||||||
|
};
|
||||||
|
coupon_code?: string;
|
||||||
|
coupon_name?: string;
|
||||||
|
store_id?: number;
|
||||||
|
visitor_id?: string;
|
||||||
|
currency_rate?: string | number;
|
||||||
|
landing_page?: string;
|
||||||
|
note?: string;
|
||||||
|
admin_note?: string;
|
||||||
|
source_device?: string;
|
||||||
|
checkout_type?: string;
|
||||||
|
version?: string;
|
||||||
|
brand_id?: number;
|
||||||
|
tags?: string[];
|
||||||
|
financial_status?: number;
|
||||||
|
fulfillment_status?: number;
|
||||||
|
// 创建与更新时间可能为时间戳
|
||||||
|
created_at?: number | string;
|
||||||
|
date_added?: string;
|
||||||
|
updated_at?: number | string;
|
||||||
|
date_updated?: string;
|
||||||
|
last_modified?: string;
|
||||||
|
// 支付时间
|
||||||
|
pay_at?: number ;
|
||||||
|
// 配送方式
|
||||||
|
shipping_lines?: Array<ShopyyShippingLineDTO>;
|
||||||
|
// 费用项
|
||||||
|
fee_lines?: Array<ShopyyFeeLineDTO>;
|
||||||
|
// 优惠券项
|
||||||
|
coupon_lines?: Array<ShopyyCouponLineDTO>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class ShopyyShippingLineDTO {
|
||||||
|
// 配送方式DTO用于承载统一配送方式数据
|
||||||
|
id?: string | number;
|
||||||
|
|
||||||
|
method_title?: string;
|
||||||
|
|
||||||
|
method_id?: string;
|
||||||
|
|
||||||
|
total?: string;
|
||||||
|
|
||||||
|
total_tax?: string;
|
||||||
|
|
||||||
|
taxes?: any[];
|
||||||
|
|
||||||
|
meta_data?: any[];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShopyyFeeLineDTO {
|
||||||
|
// 费用项DTO用于承载统一费用项数据
|
||||||
|
id?: string | number;
|
||||||
|
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
tax_class?: string;
|
||||||
|
|
||||||
|
tax_status?: string;
|
||||||
|
|
||||||
|
total?: string;
|
||||||
|
|
||||||
|
total_tax?: string;
|
||||||
|
|
||||||
|
taxes?: any[];
|
||||||
|
|
||||||
|
meta_data?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShopyyCouponLineDTO {
|
||||||
|
// 优惠券项DTO用于承载统一优惠券项数据
|
||||||
|
id?: string | number;
|
||||||
|
code?: string;
|
||||||
|
discount?: string;
|
||||||
|
discount_tax?: string;
|
||||||
|
meta_data?: any[];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户类型
|
||||||
|
export interface ShopyyCustomer {
|
||||||
|
// 主键与兼容ID
|
||||||
|
id?: number;
|
||||||
|
customer_id?: number;
|
||||||
|
// 姓名
|
||||||
|
first_name?: string;
|
||||||
|
firstname?: string;
|
||||||
|
last_name?: string;
|
||||||
|
lastname?: string;
|
||||||
|
fullname?: string;
|
||||||
|
customer_name?: string;
|
||||||
|
// 联系信息
|
||||||
|
email?: string;
|
||||||
|
customer_email?: string;
|
||||||
|
contact?: string;
|
||||||
|
phone?: string;
|
||||||
|
// 地址集合
|
||||||
|
addresses?: any[];
|
||||||
|
default_address?: any;
|
||||||
|
// 国家
|
||||||
|
country?: { country_name?: string };
|
||||||
|
// 统计字段
|
||||||
|
orders_count?: number;
|
||||||
|
order_count?: number;
|
||||||
|
orders?: number;
|
||||||
|
total_spent?: number | string;
|
||||||
|
total_spend_amount?: number | string;
|
||||||
|
total_spend_money?: number | string;
|
||||||
|
// 创建与更新时间可能为时间戳
|
||||||
|
created_at?: number | string;
|
||||||
|
date_added?: string;
|
||||||
|
updated_at?: number | string;
|
||||||
|
date_updated?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 评论类型
|
||||||
|
export interface ShopyyReview {
|
||||||
|
// 主键ID
|
||||||
|
id: number;
|
||||||
|
// 产品ID
|
||||||
|
product_id: number;
|
||||||
|
// 客户ID
|
||||||
|
customer_id: number;
|
||||||
|
// 国家ID
|
||||||
|
country_id: number;
|
||||||
|
// IP地址
|
||||||
|
ip: string;
|
||||||
|
// 评分星级
|
||||||
|
star: number;
|
||||||
|
// 客户名称
|
||||||
|
customer_name: string;
|
||||||
|
// 客户邮箱
|
||||||
|
customer_email: string;
|
||||||
|
// 回复内容
|
||||||
|
reply_content: string;
|
||||||
|
// 评论内容
|
||||||
|
content: string;
|
||||||
|
// 状态 1表示正常
|
||||||
|
status: number;
|
||||||
|
// 是否包含图片 0表示不包含
|
||||||
|
is_image: number;
|
||||||
|
// 图片列表
|
||||||
|
images: any[];
|
||||||
|
// 更新时间戳
|
||||||
|
updated_at: number;
|
||||||
|
// 创建时间戳
|
||||||
|
created_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface ShopyyWebhookEvent {
|
||||||
|
id: number;
|
||||||
|
'event_name': string;
|
||||||
|
'event_code': string;
|
||||||
|
"event_decript": string;
|
||||||
|
isemail_event: number;
|
||||||
|
email_event_file: string;
|
||||||
|
email_event_status: number;
|
||||||
|
is_webhook: number;
|
||||||
|
is_script_event: number;
|
||||||
|
created_at: number;
|
||||||
|
updated_at: number;
|
||||||
|
}
|
||||||
|
export interface ShopyyWebhook {
|
||||||
|
id: number;
|
||||||
|
"webhook_name": string;
|
||||||
|
"url": string;
|
||||||
|
event_id: number;
|
||||||
|
event_name: string;
|
||||||
|
event_code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发货相关DTO
|
||||||
|
export class ShopyyShipOrderItemDTO {
|
||||||
|
order_item_id: number;
|
||||||
|
quantity: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShopyyShipOrderDTO {
|
||||||
|
tracking_number?: string;
|
||||||
|
shipping_provider?: string;
|
||||||
|
shipping_method?: string;
|
||||||
|
items?: ShopyyShipOrderItemDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShopyyCancelShipOrderDTO {
|
||||||
|
reason?: string;
|
||||||
|
shipment_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShopyyBatchShipOrderItemDTO {
|
||||||
|
order_id: string;
|
||||||
|
tracking_number?: string;
|
||||||
|
shipping_provider?: string;
|
||||||
|
shipping_method?: string;
|
||||||
|
items?: ShopyyShipOrderItemDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShopyyBatchShipOrdersDTO {
|
||||||
|
orders: ShopyyBatchShipOrderItemDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,794 @@
|
||||||
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
|
|
||||||
|
export class UnifiedPaginationDTO<T> {
|
||||||
|
// 分页DTO用于承载统一分页信息与列表数据
|
||||||
|
@ApiProperty({ description: '列表数据' })
|
||||||
|
items: T[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '总数', example: 100 })
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '当前页', example: 1 })
|
||||||
|
page: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '每页数量', example: 20 })
|
||||||
|
per_page: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '总页数', example: 5 })
|
||||||
|
totalPages: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分页后的数据', required: false })
|
||||||
|
after?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分页前的数据', required: false })
|
||||||
|
before?: string;
|
||||||
|
}
|
||||||
|
export class UnifiedTagDTO {
|
||||||
|
// 标签DTO用于承载统一标签数据
|
||||||
|
@ApiProperty({ description: '标签ID' })
|
||||||
|
id: number | string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '标签名称' })
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
export class UnifiedCategoryDTO {
|
||||||
|
// 分类DTO用于承载统一分类数据
|
||||||
|
@ApiProperty({ description: '分类ID' })
|
||||||
|
id: number | string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分类名称' })
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
export class UnifiedImageDTO {
|
||||||
|
// 图片DTO用于承载统一图片数据
|
||||||
|
@ApiProperty({ description: '图片ID' })
|
||||||
|
id: number | string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '图片URL' })
|
||||||
|
src: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '图片名称', required: false })
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '替代文本', required: false })
|
||||||
|
alt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedAddressDTO {
|
||||||
|
// 地址DTO用于承载统一地址数据
|
||||||
|
@ApiProperty({ description: '名', required: false })
|
||||||
|
first_name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '姓', required: false })
|
||||||
|
last_name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '全名', required: false })
|
||||||
|
fullname?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '公司', required: false })
|
||||||
|
company?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '地址1', required: false })
|
||||||
|
address_1?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '地址2', required: false })
|
||||||
|
address_2?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '城市', required: false })
|
||||||
|
city?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '省/州', required: false })
|
||||||
|
state?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '邮政编码', required: false })
|
||||||
|
postcode?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '国家', required: false })
|
||||||
|
country?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '邮箱', required: false })
|
||||||
|
email?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '电话', required: false })
|
||||||
|
phone?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedOrderLineItemDTO {
|
||||||
|
// 订单项DTO用于承载统一订单项数据
|
||||||
|
@ApiProperty({ description: '订单项ID' })
|
||||||
|
id: number | string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品名称' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品ID' })
|
||||||
|
product_id: number | string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '变体ID', required: false })
|
||||||
|
variation_id?: number | string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '数量' })
|
||||||
|
quantity: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '总计' })
|
||||||
|
total: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'SKU' })
|
||||||
|
sku: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedProductAttributeDTO {
|
||||||
|
// 产品属性DTO用于承载统一产品属性数据
|
||||||
|
@ApiProperty({ description: '属性ID', required: false })
|
||||||
|
id?: number | string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '属性名称' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '属性位置', example: 0, required: false })
|
||||||
|
position?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '对变体是否可见', example: true, required: false })
|
||||||
|
visible?: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '是否为变体属性', example: true, required: false })
|
||||||
|
variation?: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '属性选项', type: [String] })
|
||||||
|
options: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedProductVariationDTO {
|
||||||
|
// 产品变体DTO用于承载统一产品变体数据
|
||||||
|
@ApiProperty({ description: '变体ID' })
|
||||||
|
id: number | string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '变体SKU' })
|
||||||
|
sku: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '常规价格' })
|
||||||
|
regular_price: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '销售价格' })
|
||||||
|
sale_price: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '当前价格' })
|
||||||
|
price: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '库存状态' })
|
||||||
|
stock_status: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '库存数量' })
|
||||||
|
stock_quantity: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '变体图片', type: () => UnifiedImageDTO, required: false })
|
||||||
|
image?: UnifiedImageDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedProductDTO {
|
||||||
|
// 产品DTO用于承载统一产品数据
|
||||||
|
@ApiProperty({ description: '产品ID' })
|
||||||
|
id: string | number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品名称' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品类型' })
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品状态' })
|
||||||
|
status: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品SKU' })
|
||||||
|
sku: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '常规价格' })
|
||||||
|
regular_price: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '销售价格' })
|
||||||
|
sale_price: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '当前价格' })
|
||||||
|
price: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '库存状态' })
|
||||||
|
stock_status: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '库存数量' })
|
||||||
|
stock_quantity: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品图片', type: () => [UnifiedImageDTO] })
|
||||||
|
images: UnifiedImageDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品标签', type: () => [UnifiedTagDTO], required: false })
|
||||||
|
tags?: UnifiedTagDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品分类', type: () => [UnifiedCategoryDTO], required: false })
|
||||||
|
categories?: UnifiedCategoryDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品属性', type: () => [UnifiedProductAttributeDTO] })
|
||||||
|
attributes: UnifiedProductAttributeDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '产品变体',
|
||||||
|
type: () => [UnifiedProductVariationDTO],
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
variations?: UnifiedProductVariationDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间' })
|
||||||
|
date_created: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新时间' })
|
||||||
|
date_modified: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品链接', required: false })
|
||||||
|
permalink?: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '原始数据(保留备用)',
|
||||||
|
type: 'object',
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
raw?: Record<string, any>;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'ERP产品信息',
|
||||||
|
type: 'object',
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
erpProduct?: {
|
||||||
|
id: number;
|
||||||
|
sku: string;
|
||||||
|
name: string;
|
||||||
|
nameCn?: string;
|
||||||
|
category?: any;
|
||||||
|
attributes?: any[];
|
||||||
|
components?: any[];
|
||||||
|
price: number;
|
||||||
|
promotionPrice: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedOrderDTO {
|
||||||
|
// 订单DTO用于承载统一订单数据
|
||||||
|
@ApiProperty({ description: '订单ID' })
|
||||||
|
id: string | number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '订单号' })
|
||||||
|
number: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '订单状态' })
|
||||||
|
status: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '货币' })
|
||||||
|
currency: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '货币符号' })
|
||||||
|
currency_symbol?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '总金额' })
|
||||||
|
total: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '客户ID' })
|
||||||
|
customer_id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '客户姓名' })
|
||||||
|
customer_name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '客户邮箱' })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '订单项(具体的商品)', type: () => [UnifiedOrderLineItemDTO] })
|
||||||
|
line_items: UnifiedOrderLineItemDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '销售项(兼容前端)',
|
||||||
|
type: () => [UnifiedOrderLineItemDTO],
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
sales?: UnifiedOrderLineItemDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '账单地址', type: () => UnifiedAddressDTO })
|
||||||
|
billing: UnifiedAddressDTO;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '收货地址', type: () => UnifiedAddressDTO })
|
||||||
|
shipping: UnifiedAddressDTO;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '账单地址全称', required: false })
|
||||||
|
billing_full_address?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '收货地址全称', required: false })
|
||||||
|
shipping_full_address?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '支付方式' })
|
||||||
|
payment_method: string;
|
||||||
|
refunds: UnifiedOrderRefundDTO[];
|
||||||
|
@ApiProperty({ description: '创建时间' })
|
||||||
|
date_created: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新时间', required: false })
|
||||||
|
date_modified?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '原始数据', type: 'object', required: false })
|
||||||
|
raw?: Record<string, any>;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '配送方式', type: () => [UnifiedShippingLineDTO], required: false })
|
||||||
|
shipping_lines?: UnifiedShippingLineDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '费用项', type: () => [UnifiedFeeLineDTO], required: false })
|
||||||
|
fee_lines?: UnifiedFeeLineDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '优惠券项', type: () => [UnifiedCouponLineDTO], required: false })
|
||||||
|
coupon_lines?: UnifiedCouponLineDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '支付时间', required: false })
|
||||||
|
date_paid?: string ;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedShippingLineDTO {
|
||||||
|
// 配送方式DTO用于承载统一配送方式数据
|
||||||
|
@ApiProperty({ description: '配送方式ID' })
|
||||||
|
id?: string | number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '配送方式名称' })
|
||||||
|
method_title?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '配送方式实例ID' })
|
||||||
|
method_id?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '配送方式金额' })
|
||||||
|
total?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '配送方式税额' })
|
||||||
|
total_tax?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '配送方式税额详情' })
|
||||||
|
taxes?: any[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '配送方式元数据' })
|
||||||
|
meta_data?: any[];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedFeeLineDTO {
|
||||||
|
// 费用项DTO用于承载统一费用项数据
|
||||||
|
@ApiProperty({ description: '费用项ID' })
|
||||||
|
id?: string | number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '费用项名称' })
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '税率类' })
|
||||||
|
tax_class?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '税率状态' })
|
||||||
|
tax_status?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '总金额' })
|
||||||
|
total?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '总税额' })
|
||||||
|
total_tax?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '税额详情' })
|
||||||
|
taxes?: any[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '元数据' })
|
||||||
|
meta_data?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedCouponLineDTO {
|
||||||
|
// 优惠券项DTO用于承载统一优惠券项数据
|
||||||
|
@ApiProperty({ description: '优惠券项ID' })
|
||||||
|
id?: string | number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '优惠券项代码' })
|
||||||
|
code?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '优惠券项折扣' })
|
||||||
|
discount?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '优惠券项税额' })
|
||||||
|
discount_tax?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '优惠券项元数据' })
|
||||||
|
meta_data?: any[];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedCustomerDTO {
|
||||||
|
// 客户DTO用于承载统一客户数据
|
||||||
|
@ApiProperty({ description: '客户ID' })
|
||||||
|
id: string | number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '头像URL', required: false })
|
||||||
|
avatar?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '邮箱' })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '订单总数', required: false })
|
||||||
|
orders?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '总花费', required: false })
|
||||||
|
total_spend?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间', required: false })
|
||||||
|
date_created?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新时间', required: false })
|
||||||
|
date_modified?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '名', required: false })
|
||||||
|
first_name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '姓', required: false })
|
||||||
|
last_name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '名字', required: false })
|
||||||
|
fullname?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用户名', required: false })
|
||||||
|
username?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '电话', required: false })
|
||||||
|
phone?: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '账单地址',
|
||||||
|
type: () => UnifiedAddressDTO,
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
billing?: UnifiedAddressDTO;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '收货地址',
|
||||||
|
type: () => UnifiedAddressDTO,
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
shipping?: UnifiedAddressDTO;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '原始数据', type: 'object', required: false })
|
||||||
|
raw?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedSubscriptionDTO {
|
||||||
|
// 订阅DTO用于承载统一订阅数据
|
||||||
|
@ApiProperty({ description: '订阅ID' })
|
||||||
|
id: string | number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '订阅状态' })
|
||||||
|
status: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '客户ID' })
|
||||||
|
customer_id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '计费周期' })
|
||||||
|
billing_period: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '计费间隔' })
|
||||||
|
billing_interval: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间', required: false })
|
||||||
|
date_created?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新时间', required: false })
|
||||||
|
date_modified?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '开始时间' })
|
||||||
|
start_date: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '下次支付时间' })
|
||||||
|
next_payment_date: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '订单项', type: () => [UnifiedOrderLineItemDTO] })
|
||||||
|
line_items: UnifiedOrderLineItemDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '原始数据', type: 'object', required: false })
|
||||||
|
raw?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedMediaDTO {
|
||||||
|
// 媒体DTO用于承载统一媒体数据
|
||||||
|
@ApiProperty({ description: '媒体ID' })
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '标题' })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '媒体类型' })
|
||||||
|
media_type: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'MIME类型' })
|
||||||
|
mime_type: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '源URL' })
|
||||||
|
source_url: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间' })
|
||||||
|
date_created: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新时间', required: false })
|
||||||
|
date_modified?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedProductPaginationDTO extends UnifiedPaginationDTO<UnifiedProductDTO> {
|
||||||
|
// 产品分页DTO用于承载产品列表分页数据
|
||||||
|
@ApiProperty({ description: '列表数据', type: () => [UnifiedProductDTO] })
|
||||||
|
items: UnifiedProductDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedOrderPaginationDTO extends UnifiedPaginationDTO<UnifiedOrderDTO> {
|
||||||
|
// 订单分页DTO用于承载订单列表分页数据
|
||||||
|
@ApiProperty({ description: '列表数据', type: () => [UnifiedOrderDTO] })
|
||||||
|
items: UnifiedOrderDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedCustomerPaginationDTO extends UnifiedPaginationDTO<UnifiedCustomerDTO> {
|
||||||
|
// 客户分页DTO用于承载客户列表分页数据
|
||||||
|
@ApiProperty({ description: '列表数据', type: () => [UnifiedCustomerDTO] })
|
||||||
|
items: UnifiedCustomerDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedSubscriptionPaginationDTO extends UnifiedPaginationDTO<UnifiedSubscriptionDTO> {
|
||||||
|
// 订阅分页DTO用于承载订阅列表分页数据
|
||||||
|
@ApiProperty({ description: '列表数据', type: () => [UnifiedSubscriptionDTO] })
|
||||||
|
items: UnifiedSubscriptionDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedMediaPaginationDTO extends UnifiedPaginationDTO<UnifiedMediaDTO> {
|
||||||
|
// 媒体分页DTO用于承载媒体列表分页数据
|
||||||
|
@ApiProperty({ description: '列表数据', type: () => [UnifiedMediaDTO] })
|
||||||
|
items: UnifiedMediaDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedReviewDTO {
|
||||||
|
// 评论DTO用于承载统一评论数据
|
||||||
|
@ApiProperty({ description: '评论ID' })
|
||||||
|
id: number | string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品ID' })
|
||||||
|
product_id: number | string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '评论者' })
|
||||||
|
author: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '评论者邮箱' })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '评论内容' })
|
||||||
|
content: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '评分' })
|
||||||
|
rating: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '状态' })
|
||||||
|
status: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间' })
|
||||||
|
date_created: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新时间', required: false })
|
||||||
|
date_modified?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '原始数据', type: 'object', required: false })
|
||||||
|
raw?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedReviewPaginationDTO extends UnifiedPaginationDTO<UnifiedReviewDTO> {
|
||||||
|
// 评论分页DTO用于承载评论列表分页数据
|
||||||
|
@ApiProperty({ description: '列表数据', type: () => [UnifiedReviewDTO] })
|
||||||
|
items: UnifiedReviewDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CreateReviewDTO {
|
||||||
|
@ApiProperty({ description: '产品ID' })
|
||||||
|
product_id: number | string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '评论内容' })
|
||||||
|
review: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '评论者' })
|
||||||
|
reviewer: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '评论者邮箱' })
|
||||||
|
reviewer_email: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '评分' })
|
||||||
|
rating: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateReviewDTO {
|
||||||
|
@ApiProperty({ description: '评论内容', required: false })
|
||||||
|
review?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '评分', required: false })
|
||||||
|
rating?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '状态', required: false })
|
||||||
|
status?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UploadMediaDTO {
|
||||||
|
@ApiProperty({ description: 'Base64 编码的文件内容' })
|
||||||
|
file: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '文件名' })
|
||||||
|
filename: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedSearchParamsDTO<Where=Record<string, any>> {
|
||||||
|
// 统一查询参数DTO用于承载分页与筛选与排序参数
|
||||||
|
@ApiProperty({ description: '页码', example: 1, required: false })
|
||||||
|
page?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '每页数量', example: 20, required: false })
|
||||||
|
per_page?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '搜索关键词', required: false })
|
||||||
|
search?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '过滤条件对象', type: 'object', required: false })
|
||||||
|
where?: Where;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间后', required: false })
|
||||||
|
after?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间前', required: false })
|
||||||
|
before?: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '排序对象,例如 { "sku": "desc" }',
|
||||||
|
type: 'object',
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
orderBy?: Record<string, 'asc' | 'desc'> | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedWebhookDTO {
|
||||||
|
// Webhook DTO用于承载统一webhook数据
|
||||||
|
@ApiProperty({ description: 'Webhook ID' })
|
||||||
|
id: number | string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '名称' })
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '状态' })
|
||||||
|
status: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '主题/事件' })
|
||||||
|
topic: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '目标URL' })
|
||||||
|
delivery_url: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '秘密密钥' })
|
||||||
|
secret?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间' })
|
||||||
|
date_created?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新时间' })
|
||||||
|
date_modified?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '头部信息' })
|
||||||
|
headers?: Record<string, any>;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'API版本' })
|
||||||
|
api_version?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedWebhookPaginationDTO extends UnifiedPaginationDTO<UnifiedWebhookDTO> {
|
||||||
|
// Webhook分页DTO用于承载webhook列表分页数据
|
||||||
|
@ApiProperty({ description: '列表数据', type: () => [UnifiedWebhookDTO] })
|
||||||
|
items: UnifiedWebhookDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CreateWebhookDTO {
|
||||||
|
// 创建Webhook DTO
|
||||||
|
@ApiProperty({ description: '名称' })
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '主题/事件' })
|
||||||
|
topic: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '目标URL' })
|
||||||
|
delivery_url: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '秘密密钥' })
|
||||||
|
secret?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '头部信息' })
|
||||||
|
headers?: Record<string, any>;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'API版本' })
|
||||||
|
api_version?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateWebhookDTO {
|
||||||
|
// 更新Webhook DTO
|
||||||
|
@ApiProperty({ description: '名称', required: false })
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '状态', required: false })
|
||||||
|
status?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '主题/事件', required: false })
|
||||||
|
topic?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '目标URL', required: false })
|
||||||
|
delivery_url?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '秘密密钥', required: false })
|
||||||
|
secret?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '头部信息', required: false })
|
||||||
|
headers?: Record<string, any>;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'API版本', required: false })
|
||||||
|
api_version?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedOrderRefundDTO {
|
||||||
|
@ApiProperty({ description: '退款ID' })
|
||||||
|
id: number | string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '退款原因' })
|
||||||
|
reason: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '退款金额' })
|
||||||
|
total: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShipOrderItemDTO {
|
||||||
|
@ApiProperty({ description: '订单项ID' })
|
||||||
|
order_item_id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '数量' })
|
||||||
|
quantity: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShipOrderDTO {
|
||||||
|
@ApiProperty({ description: '物流单号', required: false })
|
||||||
|
tracking_number?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '物流公司', required: false })
|
||||||
|
shipping_provider?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '发货方式', required: false })
|
||||||
|
shipping_method?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '发货商品项', type: () => [ShipOrderItemDTO], required: false })
|
||||||
|
items?: ShipOrderItemDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CancelShipOrderDTO {
|
||||||
|
@ApiProperty({ description: '取消原因', required: false })
|
||||||
|
reason?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '发货单ID', required: false })
|
||||||
|
shipment_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BatchShipOrderItemDTO {
|
||||||
|
@ApiProperty({ description: '订单ID' })
|
||||||
|
order_id: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '物流单号', required: false })
|
||||||
|
tracking_number?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '物流公司', required: false })
|
||||||
|
shipping_provider?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '发货方式', required: false })
|
||||||
|
shipping_method?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '发货商品项', type: () => [ShipOrderItemDTO], required: false })
|
||||||
|
items?: ShipOrderItemDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BatchShipOrdersDTO {
|
||||||
|
@ApiProperty({ description: '批量发货订单列表', type: () => [BatchShipOrderItemDTO] })
|
||||||
|
orders: BatchShipOrderItemDTO[];
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,11 @@ export class SiteConfig {
|
||||||
|
|
||||||
@ApiProperty({ description: '站点名' })
|
@ApiProperty({ description: '站点名' })
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
siteName: string;
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '描述' })
|
||||||
|
@Rule(RuleType.string().allow('').optional())
|
||||||
|
description?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '平台类型', enum: ['woocommerce', 'shopyy'] })
|
@ApiProperty({ description: '平台类型', enum: ['woocommerce', 'shopyy'] })
|
||||||
@Rule(RuleType.string().valid('woocommerce', 'shopyy'))
|
@Rule(RuleType.string().valid('woocommerce', 'shopyy'))
|
||||||
|
|
@ -35,15 +39,31 @@ export class CreateSiteDTO {
|
||||||
@Rule(RuleType.string().optional())
|
@Rule(RuleType.string().optional())
|
||||||
apiUrl?: string;
|
apiUrl?: string;
|
||||||
@Rule(RuleType.string().optional())
|
@Rule(RuleType.string().optional())
|
||||||
|
websiteUrl?: string;
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
consumerKey?: string;
|
consumerKey?: string;
|
||||||
@Rule(RuleType.string().optional())
|
@Rule(RuleType.string().optional())
|
||||||
consumerSecret?: string;
|
consumerSecret?: string;
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
token?: string;
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
siteName: string;
|
name: string;
|
||||||
|
@Rule(RuleType.string().allow('').optional())
|
||||||
|
description?: string;
|
||||||
@Rule(RuleType.string().valid('woocommerce', 'shopyy').optional())
|
@Rule(RuleType.string().valid('woocommerce', 'shopyy').optional())
|
||||||
type?: string;
|
type?: string;
|
||||||
@Rule(RuleType.string().optional())
|
@Rule(RuleType.string().optional())
|
||||||
skuPrefix?: string;
|
skuPrefix?: string;
|
||||||
|
|
||||||
|
// 区域
|
||||||
|
@ApiProperty({ description: '区域' })
|
||||||
|
@Rule(RuleType.array().items(RuleType.string()).optional())
|
||||||
|
areas?: string[];
|
||||||
|
|
||||||
|
// 绑定仓库
|
||||||
|
@ApiProperty({ description: '绑定仓库ID列表' })
|
||||||
|
@Rule(RuleType.array().items(RuleType.number()).optional())
|
||||||
|
stockPointIds?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateSiteDTO {
|
export class UpdateSiteDTO {
|
||||||
|
|
@ -54,13 +74,30 @@ export class UpdateSiteDTO {
|
||||||
@Rule(RuleType.string().optional())
|
@Rule(RuleType.string().optional())
|
||||||
consumerSecret?: string;
|
consumerSecret?: string;
|
||||||
@Rule(RuleType.string().optional())
|
@Rule(RuleType.string().optional())
|
||||||
siteName?: string;
|
token?: string;
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
name?: string;
|
||||||
|
@Rule(RuleType.string().allow('').optional())
|
||||||
|
description?: string;
|
||||||
@Rule(RuleType.boolean().optional())
|
@Rule(RuleType.boolean().optional())
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
@Rule(RuleType.string().valid('woocommerce', 'shopyy').optional())
|
@Rule(RuleType.string().valid('woocommerce', 'shopyy').optional())
|
||||||
type?: string;
|
type?: string;
|
||||||
@Rule(RuleType.string().optional())
|
@Rule(RuleType.string().optional())
|
||||||
skuPrefix?: string;
|
skuPrefix?: string;
|
||||||
|
|
||||||
|
// 区域
|
||||||
|
@ApiProperty({ description: '区域' })
|
||||||
|
@Rule(RuleType.array().items(RuleType.string()).optional())
|
||||||
|
areas?: string[];
|
||||||
|
|
||||||
|
// 绑定仓库
|
||||||
|
@ApiProperty({ description: '绑定仓库ID列表' })
|
||||||
|
@Rule(RuleType.array().items(RuleType.number()).optional())
|
||||||
|
stockPointIds?: number[];
|
||||||
|
@ApiProperty({ description: '站点网站URL' })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
websiteUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QuerySiteDTO {
|
export class QuerySiteDTO {
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ export class OrderStatisticsParams {
|
||||||
keyword?: string;
|
keyword?: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string().allow(null))
|
@Rule(RuleType.number().allow(null))
|
||||||
siteId?: string;
|
siteId?: number;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
enum: ['all', 'first_purchase', 'repeat_purchase'],
|
enum: ['all', 'first_purchase', 'repeat_purchase'],
|
||||||
|
|
@ -33,4 +33,9 @@ export class OrderStatisticsParams {
|
||||||
@ApiProperty({ enum: ['all', 'zyn', 'yoone', 'zolt'], default: 'all' })
|
@ApiProperty({ enum: ['all', 'zyn', 'yoone', 'zolt'], default: 'all' })
|
||||||
@Rule(RuleType.string().valid('all', 'zyn', 'yoone', 'zolt'))
|
@Rule(RuleType.string().valid('all', 'zyn', 'yoone', 'zolt'))
|
||||||
brand: string;
|
brand: string;
|
||||||
|
|
||||||
|
@ApiProperty({ enum: ['day', 'week', 'month'], default: 'day' })
|
||||||
|
@Rule(RuleType.string().valid('day', 'week', 'month'))
|
||||||
|
grouping: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,19 @@ export class QueryStockDTO {
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
productName: string;
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
sku: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '按库存点ID排序', required: false })
|
||||||
|
@Rule(RuleType.number().allow(null))
|
||||||
|
sortPointId?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '排序对象,格式如 { productName: "asc", sku: "desc" }', required: false })
|
||||||
|
@Rule(RuleType.object().allow(null))
|
||||||
|
order?: Record<string, 'asc' | 'desc'>;
|
||||||
}
|
}
|
||||||
export class QueryPointDTO {
|
export class QueryPointDTO {
|
||||||
@ApiProperty({ example: '1', description: '页码' })
|
@ApiProperty({ example: '1', description: '页码' })
|
||||||
|
|
@ -46,11 +58,11 @@ export class QueryStockRecordDTO {
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
productSku: string;
|
sku: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
productName: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
|
|
@ -84,7 +96,7 @@ export class QueryPurchaseOrderDTO {
|
||||||
export class StockDTO extends Stock {
|
export class StockDTO extends Stock {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
productName: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|
@ -120,7 +132,7 @@ export class UpdateStockDTO {
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
productSku: string;
|
sku: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number())
|
||||||
|
|
@ -155,6 +167,19 @@ export class CreateStockPointDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
contactPhone: string;
|
contactPhone: string;
|
||||||
|
|
||||||
|
// 区域
|
||||||
|
@ApiProperty({ description: '区域' })
|
||||||
|
@Rule(RuleType.array().items(RuleType.string()).optional())
|
||||||
|
areas?: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '上游仓库点ID' })
|
||||||
|
@Rule(RuleType.number().optional())
|
||||||
|
upStreamStockPointId?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '上游名称' })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
upStreamName?: string;
|
||||||
}
|
}
|
||||||
export class UpdateStockPointDTO extends CreateStockPointDTO {}
|
export class UpdateStockPointDTO extends CreateStockPointDTO {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import { Rule, RuleType } from '@midwayjs/validate';
|
import { Rule, RuleType } from '@midwayjs/validate';
|
||||||
import { SubscriptionStatus } from '../enums/base.enum';
|
import { SubscriptionStatus } from '../enums/base.enum';
|
||||||
|
|
||||||
// 订阅列表查询参数(分页与筛选)
|
// 订阅列表查询参数(分页与筛选)
|
||||||
export class QuerySubscriptionDTO {
|
export class QuerySubscriptionDTO {
|
||||||
// 当前页码(从 1 开始)
|
// 当前页码(从 1 开始)
|
||||||
@ApiProperty({ example: 1, description: '页码' })
|
@ApiProperty({ example: 1, description: '页码' })
|
||||||
@Rule(RuleType.number().default(1))
|
@Rule(RuleType.number().default(1))
|
||||||
current: number;
|
current: number;
|
||||||
|
|
@ -14,23 +14,23 @@ export class QuerySubscriptionDTO {
|
||||||
@Rule(RuleType.number().default(10))
|
@Rule(RuleType.number().default(10))
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
|
|
||||||
// 站点 ID(可选)
|
// 站点 ID(可选)
|
||||||
@ApiProperty({ description: '站点ID' })
|
@ApiProperty({ description: '站点ID' })
|
||||||
@Rule(RuleType.string().allow(''))
|
@Rule(RuleType.string().allow(''))
|
||||||
siteId: string;
|
siteId: string;
|
||||||
|
|
||||||
// 订阅状态筛选(可选),支持枚举值
|
// 订阅状态筛选(可选),支持枚举值
|
||||||
@ApiProperty({ description: '订阅状态', enum: SubscriptionStatus })
|
@ApiProperty({ description: '订阅状态', enum: SubscriptionStatus })
|
||||||
@Rule(RuleType.string().valid(...Object.values(SubscriptionStatus)).allow(''))
|
@Rule(RuleType.string().valid(...Object.values(SubscriptionStatus)).allow(''))
|
||||||
status: SubscriptionStatus | '';
|
status: SubscriptionStatus | '';
|
||||||
|
|
||||||
// 客户邮箱(模糊匹配,可选)
|
// 客户邮箱(模糊匹配,可选)
|
||||||
@ApiProperty({ description: '客户邮箱' })
|
@ApiProperty({ description: '客户邮箱' })
|
||||||
@Rule(RuleType.string().allow(''))
|
@Rule(RuleType.string().allow(''))
|
||||||
customer_email: string;
|
customer_email: string;
|
||||||
|
|
||||||
// 关键字(订阅ID、邮箱等,模糊匹配,可选)
|
// 关键字(订阅ID,邮箱等,模糊匹配,可选)
|
||||||
@ApiProperty({ description: '关键字(订阅ID、邮箱等)' })
|
@ApiProperty({ description: '关键字(订阅ID,邮箱等)' })
|
||||||
@Rule(RuleType.string().allow(''))
|
@Rule(RuleType.string().allow(''))
|
||||||
keyword: string;
|
keyword: string;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
|
import { Rule, RuleType } from '@midwayjs/validate';
|
||||||
|
|
||||||
|
export class CreateTemplateDTO {
|
||||||
|
@ApiProperty({ description: '模板名称', required: true })
|
||||||
|
@Rule(RuleType.string().required())
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板内容', required: true })
|
||||||
|
@Rule(RuleType.string().required())
|
||||||
|
value: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '测试数据JSON', required: false })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
testData?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateTemplateDTO {
|
||||||
|
@ApiProperty({ description: '模板名称', required: true })
|
||||||
|
@Rule(RuleType.string().required())
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板内容', required: true })
|
||||||
|
@Rule(RuleType.string().required())
|
||||||
|
value: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '测试数据JSON', required: false })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
testData?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RenderTemplateDTO {
|
||||||
|
@ApiProperty({ description: '模板内容', required: true })
|
||||||
|
@Rule(RuleType.string().required())
|
||||||
|
template: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '渲染数据', required: true })
|
||||||
|
@Rule(RuleType.object().required())
|
||||||
|
data: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,558 @@
|
||||||
|
// WooCommerce 平台原始数据类型定义
|
||||||
|
// 仅包含当前映射逻辑所需字段以保持简洁与类型安全
|
||||||
|
|
||||||
|
|
||||||
|
// 产品类型
|
||||||
|
export interface WooProduct {
|
||||||
|
// 产品主键
|
||||||
|
id: number;
|
||||||
|
// 创建时间
|
||||||
|
date_created: string;
|
||||||
|
// 创建时间(GMT)
|
||||||
|
date_created_gmt: string;
|
||||||
|
// 更新时间
|
||||||
|
date_modified: string;
|
||||||
|
// 更新时间(GMT)
|
||||||
|
date_modified_gmt: string;
|
||||||
|
// 产品类型 simple grouped external variable
|
||||||
|
type: string;
|
||||||
|
// 产品状态 draft pending private publish
|
||||||
|
status: string;
|
||||||
|
// 是否为特色产品
|
||||||
|
featured: boolean;
|
||||||
|
// 目录可见性选项:visible, catalog, search and hidden. Default is visible.
|
||||||
|
catalog_visibility: string;
|
||||||
|
|
||||||
|
// 常规价格
|
||||||
|
regular_price?: string;
|
||||||
|
// 促销价格
|
||||||
|
sale_price?: string;
|
||||||
|
// 当前价格
|
||||||
|
price?: string;
|
||||||
|
price_html?: string;
|
||||||
|
date_on_sale_from?: string; // Date the product is on sale from.
|
||||||
|
date_on_sale_from_gmt?: string; // Date the product is on sale from (GMT).
|
||||||
|
date_on_sale_to?: string; // Date the product is on sale to.
|
||||||
|
date_on_sale_to_gmt?: string; // Date the product is on sale to (GMT).
|
||||||
|
on_sale: boolean; // Whether the product is on sale.
|
||||||
|
purchasable: boolean; // Whether the product is purchasable.
|
||||||
|
total_sales: number; // Total sales for this product.
|
||||||
|
virtual: boolean; // Whether the product is virtual.
|
||||||
|
downloadable: boolean; // Whether the product is downloadable.
|
||||||
|
downloads: Array<{ id?: number; name?: string; file?: string }>; // Downloadable files for the product.
|
||||||
|
download_limit: number; // Download limit.
|
||||||
|
download_expiry: number; // Download expiry days.
|
||||||
|
external_url: string; // URL of the external product.
|
||||||
|
|
||||||
|
global_unique_id: string; // GTIN, UPC, EAN or ISBN - a unique identifier for each distinct product and service that can be purchased.
|
||||||
|
// 产品SKU
|
||||||
|
sku: string;
|
||||||
|
// 产品名称
|
||||||
|
name: string;
|
||||||
|
// 产品描述
|
||||||
|
description: string;
|
||||||
|
// 产品短描述
|
||||||
|
short_description: string;
|
||||||
|
|
||||||
|
// 产品永久链接
|
||||||
|
permalink: string;
|
||||||
|
// 产品URL路径
|
||||||
|
slug: string;
|
||||||
|
|
||||||
|
// 库存状态
|
||||||
|
stock_status?: 'instock' | 'outofstock' | 'onbackorder';
|
||||||
|
// 库存数量
|
||||||
|
stock_quantity?: number;
|
||||||
|
// 是否管理库存
|
||||||
|
manage_stock?: boolean;
|
||||||
|
// 缺货预定设置 no notify yes
|
||||||
|
backorders?: 'no' | 'notify' | 'yes';
|
||||||
|
// 是否允许缺货预定 只读
|
||||||
|
backorders_allowed?: boolean;
|
||||||
|
// 是否处于缺货预定状态 只读
|
||||||
|
backordered?: boolean;
|
||||||
|
// 是否单独出售
|
||||||
|
sold_individually?: boolean;
|
||||||
|
// 重量
|
||||||
|
weight?: string;
|
||||||
|
// 尺寸
|
||||||
|
dimensions?: { length?: string; width?: string; height?: string };
|
||||||
|
// 是否需要运输 只读
|
||||||
|
shipping_required?: boolean;
|
||||||
|
// 运输是否计税 只读
|
||||||
|
shipping_taxable?: boolean;
|
||||||
|
// 运输类别 slug
|
||||||
|
shipping_class?: string;
|
||||||
|
// 运输类别ID 只读
|
||||||
|
shipping_class_id?: number;
|
||||||
|
// 图片列表
|
||||||
|
images?: Array<{ id: number; src: string; name?: string; alt?: string }>;
|
||||||
|
// 属性列表
|
||||||
|
attributes?: Array<{
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
position?: number;
|
||||||
|
visible?: boolean;
|
||||||
|
variation?: boolean;
|
||||||
|
options?: string[];
|
||||||
|
}>;
|
||||||
|
// 变体列表
|
||||||
|
variations?: number[];
|
||||||
|
// 默认变体属性
|
||||||
|
default_attributes?: Array<{ id?: number; name?: string; option?: string }>;
|
||||||
|
// 允许评论
|
||||||
|
reviews_allowed?: boolean;
|
||||||
|
// 平均评分 只读
|
||||||
|
average_rating?: string;
|
||||||
|
// 评分数量 只读
|
||||||
|
rating_count?: number;
|
||||||
|
// 相关产品ID列表 只读
|
||||||
|
related_ids?: number[];
|
||||||
|
// 追加销售产品ID列表
|
||||||
|
upsell_ids?: number[];
|
||||||
|
// 交叉销售产品ID列表
|
||||||
|
cross_sell_ids?: number[];
|
||||||
|
// 父产品ID
|
||||||
|
parent_id?: number;
|
||||||
|
// 购买备注
|
||||||
|
purchase_note?: string;
|
||||||
|
// 分类列表
|
||||||
|
categories?: Array<{ id: number; name?: string; slug?: string }>;
|
||||||
|
// 标签列表
|
||||||
|
tags?: Array<{ id: number; name?: string; slug?: string }>;
|
||||||
|
// 菜单排序
|
||||||
|
menu_order?: number;
|
||||||
|
// 元数据
|
||||||
|
meta_data?: Array<{ id?: number; key: string; value: any }>;
|
||||||
|
}
|
||||||
|
export interface WooVariation{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单类型
|
||||||
|
export interface WooOrder {
|
||||||
|
// 订单主键
|
||||||
|
id: number;
|
||||||
|
// 父订单ID
|
||||||
|
parent_id?: number;
|
||||||
|
// 订单号
|
||||||
|
number: string;
|
||||||
|
// 订单键 只读
|
||||||
|
order_key?: string;
|
||||||
|
// 创建来源
|
||||||
|
created_via?: string;
|
||||||
|
// WooCommerce版本 只读
|
||||||
|
version?: string;
|
||||||
|
// 状态
|
||||||
|
status: string;
|
||||||
|
// 币种
|
||||||
|
currency: string;
|
||||||
|
// 价格是否含税 只读
|
||||||
|
prices_include_tax?: boolean;
|
||||||
|
// 总金额
|
||||||
|
total: string;
|
||||||
|
// 总税额 只读
|
||||||
|
total_tax?: string;
|
||||||
|
// 折扣总额 只读
|
||||||
|
discount_total?: string;
|
||||||
|
// 折扣税额 只读
|
||||||
|
discount_tax?: string;
|
||||||
|
// 运费总额 只读
|
||||||
|
shipping_total?: string;
|
||||||
|
// 运费税额 只读
|
||||||
|
shipping_tax?: string;
|
||||||
|
// 购物车税额 只读
|
||||||
|
cart_tax?: string;
|
||||||
|
// 客户ID
|
||||||
|
customer_id: number;
|
||||||
|
// 客户IP 只读
|
||||||
|
customer_ip_address?: string;
|
||||||
|
// 客户UA 只读
|
||||||
|
customer_user_agent?: string;
|
||||||
|
// 客户备注
|
||||||
|
customer_note?: string;
|
||||||
|
// 账单信息
|
||||||
|
billing?: {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
email?: string;
|
||||||
|
company?: string;
|
||||||
|
address_1?: string;
|
||||||
|
address_2?: string;
|
||||||
|
city?: string;
|
||||||
|
state?: string;
|
||||||
|
postcode?: string;
|
||||||
|
country?: string;
|
||||||
|
phone?: string;
|
||||||
|
fullname?: string;
|
||||||
|
};
|
||||||
|
// 收货信息
|
||||||
|
shipping?: {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
company?: string;
|
||||||
|
address_1?: string;
|
||||||
|
address_2?: string;
|
||||||
|
city?: string;
|
||||||
|
state?: string;
|
||||||
|
postcode?: string;
|
||||||
|
country?: string;
|
||||||
|
phone?: string;
|
||||||
|
fullname?: string;
|
||||||
|
};
|
||||||
|
// 订单项
|
||||||
|
line_items?: Array<{
|
||||||
|
product_id?: number;
|
||||||
|
variation_id?: number;
|
||||||
|
quantity?: number;
|
||||||
|
subtotal?: string;
|
||||||
|
subtotal_tax?: string;
|
||||||
|
total?: string;
|
||||||
|
total_tax?: string;
|
||||||
|
name?: string;
|
||||||
|
sku?: string;
|
||||||
|
price?: number;
|
||||||
|
meta_data?: Array<{ key: string; value: any }>;
|
||||||
|
[key: string]: any;
|
||||||
|
}>;
|
||||||
|
// 税费行 只读
|
||||||
|
tax_lines?: Array<{
|
||||||
|
id?: number;
|
||||||
|
rate_code?: string;
|
||||||
|
rate_id?: number;
|
||||||
|
label?: string;
|
||||||
|
tax_total?: string;
|
||||||
|
shipping_tax_total?: string;
|
||||||
|
compound?: boolean;
|
||||||
|
meta_data?: any[];
|
||||||
|
}>;
|
||||||
|
// 物流费用行
|
||||||
|
shipping_lines?: Array<{
|
||||||
|
id?: number;
|
||||||
|
method_title?: string;
|
||||||
|
method_id?: string;
|
||||||
|
total?: string;
|
||||||
|
total_tax?: string;
|
||||||
|
taxes?: any[];
|
||||||
|
meta_data?: any[];
|
||||||
|
}>;
|
||||||
|
// 手续费行
|
||||||
|
fee_lines?: Array<{
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
tax_class?: string;
|
||||||
|
tax_status?: string;
|
||||||
|
total?: string;
|
||||||
|
total_tax?: string;
|
||||||
|
taxes?: any[];
|
||||||
|
meta_data?: any[];
|
||||||
|
}>;
|
||||||
|
// 优惠券行
|
||||||
|
coupon_lines?: Array<{
|
||||||
|
id?: number;
|
||||||
|
code?: string;
|
||||||
|
discount?: string;
|
||||||
|
discount_tax?: string;
|
||||||
|
meta_data?: any[];
|
||||||
|
}>;
|
||||||
|
// 退款列表 只读
|
||||||
|
refunds?: Array<WooOrderRefund>;
|
||||||
|
// 支付方式标题
|
||||||
|
payment_method_title?: string;
|
||||||
|
// 支付方式ID
|
||||||
|
payment_method?: string;
|
||||||
|
// 交易ID
|
||||||
|
transaction_id?: string;
|
||||||
|
// 已支付时间
|
||||||
|
date_paid?: string;
|
||||||
|
date_paid_gmt?: string;
|
||||||
|
// 完成时间
|
||||||
|
date_completed?: string;
|
||||||
|
date_completed_gmt?: string;
|
||||||
|
// 购物车hash 只读
|
||||||
|
cart_hash?: string;
|
||||||
|
// 设置为已支付 写入专用
|
||||||
|
set_paid?: boolean;
|
||||||
|
// 元数据
|
||||||
|
meta_data?: Array<{ id?: number; key: string; value: any }>;
|
||||||
|
// 创建与更新时间
|
||||||
|
date_created: string;
|
||||||
|
date_created_gmt?: string;
|
||||||
|
date_modified?: string;
|
||||||
|
date_modified_gmt?: string;
|
||||||
|
}
|
||||||
|
export interface WooOrderRefund {
|
||||||
|
id?: number;
|
||||||
|
reason?: string;
|
||||||
|
total?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅类型
|
||||||
|
export interface WooSubscription {
|
||||||
|
// 订阅主键
|
||||||
|
id: number;
|
||||||
|
// 订阅状态
|
||||||
|
status: string;
|
||||||
|
// 客户ID
|
||||||
|
customer_id: number;
|
||||||
|
// 计费周期
|
||||||
|
billing_period?: string;
|
||||||
|
// 计费间隔
|
||||||
|
billing_interval?: number;
|
||||||
|
// 开始时间
|
||||||
|
start_date?: string;
|
||||||
|
// 下次支付时间
|
||||||
|
next_payment_date?: string;
|
||||||
|
// 订阅项
|
||||||
|
line_items?: any[];
|
||||||
|
// 创建时间
|
||||||
|
date_created?: string;
|
||||||
|
// 更新时间
|
||||||
|
date_modified?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// WordPress 媒体类型
|
||||||
|
export interface WpMedia {
|
||||||
|
// 媒体主键
|
||||||
|
id: number;
|
||||||
|
// 标题可能为字符串或包含rendered的对象
|
||||||
|
title?: { rendered?: string } | string;
|
||||||
|
// 媒体类型
|
||||||
|
media_type?: string;
|
||||||
|
// MIME类型
|
||||||
|
mime_type?: string;
|
||||||
|
// 源地址
|
||||||
|
source_url?: string;
|
||||||
|
// 创建时间兼容date字段
|
||||||
|
date_created?: string;
|
||||||
|
date?: string;
|
||||||
|
// 更新时间兼容modified字段
|
||||||
|
date_modified?: string;
|
||||||
|
modified?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 评论类型
|
||||||
|
export interface WooReview {
|
||||||
|
// 评论ID
|
||||||
|
id: number;
|
||||||
|
// 评论内容
|
||||||
|
review: string;
|
||||||
|
// 评分
|
||||||
|
rating: number;
|
||||||
|
// 评论者
|
||||||
|
reviewer: string;
|
||||||
|
// 评论者邮箱
|
||||||
|
reviewer_email: string;
|
||||||
|
// 状态
|
||||||
|
status: string;
|
||||||
|
// 产品ID
|
||||||
|
product_id: number;
|
||||||
|
// 创建日期
|
||||||
|
date_created: string;
|
||||||
|
// 更新日期
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户类型
|
||||||
|
export interface WooCustomer {
|
||||||
|
// 客户主键
|
||||||
|
id: number;
|
||||||
|
// 头像URL
|
||||||
|
avatar_url?: string;
|
||||||
|
// 邮箱
|
||||||
|
email: string;
|
||||||
|
// 订单总数
|
||||||
|
orders?: number;
|
||||||
|
// 总花费
|
||||||
|
total_spent?: number | string;
|
||||||
|
// 名
|
||||||
|
first_name?: string;
|
||||||
|
// 姓
|
||||||
|
last_name?: string;
|
||||||
|
// 用户名
|
||||||
|
username?: string;
|
||||||
|
// 角色 只读
|
||||||
|
role?: string;
|
||||||
|
// 密码 写入专用
|
||||||
|
password?: string;
|
||||||
|
// 账单信息
|
||||||
|
billing?: {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
email?: string;
|
||||||
|
company?: string;
|
||||||
|
phone?: string;
|
||||||
|
address_1?: string;
|
||||||
|
address_2?: string;
|
||||||
|
city?: string;
|
||||||
|
state?: string;
|
||||||
|
postcode?: string;
|
||||||
|
country?: string;
|
||||||
|
};
|
||||||
|
// 收货信息
|
||||||
|
shipping?: {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
company?: string;
|
||||||
|
phone?: string;
|
||||||
|
address_1?: string;
|
||||||
|
address_2?: string;
|
||||||
|
city?: string;
|
||||||
|
state?: string;
|
||||||
|
postcode?: string;
|
||||||
|
country?: string;
|
||||||
|
};
|
||||||
|
// 是否为付费客户 只读
|
||||||
|
is_paying_customer?: boolean;
|
||||||
|
// 元数据
|
||||||
|
meta_data?: Array<{ id?: number; key: string; value: any }>;
|
||||||
|
// 创建时间
|
||||||
|
date_created?: string;
|
||||||
|
date_created_gmt?: string;
|
||||||
|
// 更新时间
|
||||||
|
date_modified?: string;
|
||||||
|
date_modified_gmt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Webhook类型
|
||||||
|
export interface WooWebhook {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
topic: string;
|
||||||
|
resource: string;
|
||||||
|
event: string;
|
||||||
|
hooks: string;
|
||||||
|
delivery_url: string;
|
||||||
|
secret: string;
|
||||||
|
date_created: string;
|
||||||
|
date_created_gmt: string;
|
||||||
|
date_modified: string;
|
||||||
|
date_modified_gmt: string;
|
||||||
|
api_version: string;
|
||||||
|
meta_data?: Array<{ id?: number; key: string; value: any }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export interface WooOrderSearchParams {
|
||||||
|
context: WooContext;
|
||||||
|
page: number;
|
||||||
|
per_page: number;
|
||||||
|
search: string;
|
||||||
|
after: string;
|
||||||
|
before: string;
|
||||||
|
modified_after: string;
|
||||||
|
modified_before: string;
|
||||||
|
date_are_gmt: boolean;
|
||||||
|
exclude: string[];
|
||||||
|
include: string[];
|
||||||
|
offset: number;
|
||||||
|
order: string;
|
||||||
|
orderby: string;
|
||||||
|
parant: string[];
|
||||||
|
status: (WooOrderStatusSearchParams)[];
|
||||||
|
customer: number;
|
||||||
|
product: number;
|
||||||
|
dp: number;
|
||||||
|
created_via: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export enum WooOrderStatusSearchParams {
|
||||||
|
pending,
|
||||||
|
processing,
|
||||||
|
"on-hold",
|
||||||
|
completed,
|
||||||
|
cancelled,
|
||||||
|
refunded,
|
||||||
|
failed,
|
||||||
|
trash,
|
||||||
|
any
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface WooProductSearchParams extends ListParams {
|
||||||
|
slug: string;
|
||||||
|
status: string[];
|
||||||
|
include_status: string;
|
||||||
|
exclude_status: string;
|
||||||
|
type: string;
|
||||||
|
include_types: string;
|
||||||
|
exclude_types: string;
|
||||||
|
sku: string;
|
||||||
|
featured: boolean;
|
||||||
|
category: string;
|
||||||
|
tag: string;
|
||||||
|
shipping_class: string;
|
||||||
|
attribute: string;
|
||||||
|
attribute_term: string;
|
||||||
|
tax_class: string;
|
||||||
|
on_sale: boolean;
|
||||||
|
min_price: string;
|
||||||
|
max_price: string;
|
||||||
|
stock_status: string;
|
||||||
|
virtual: boolean;
|
||||||
|
downloadable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListParams {
|
||||||
|
context: WooContext;
|
||||||
|
page: number;
|
||||||
|
per_page: number;
|
||||||
|
search: string;
|
||||||
|
search_fields: any[];
|
||||||
|
after: string;
|
||||||
|
before: string;
|
||||||
|
modified_after: string;
|
||||||
|
modified_before: string;
|
||||||
|
date_are_gmt: boolean;
|
||||||
|
exclude: string[];
|
||||||
|
include: string[];
|
||||||
|
offset: number;
|
||||||
|
order: string;
|
||||||
|
orderby: string;
|
||||||
|
parant: string[];
|
||||||
|
parent_exclude: string[];
|
||||||
|
}
|
||||||
|
export enum WooContext {
|
||||||
|
view,
|
||||||
|
edit
|
||||||
|
}
|
||||||
|
export enum WooProductStatusSearchParams {
|
||||||
|
any,
|
||||||
|
draft,
|
||||||
|
pending,
|
||||||
|
private,
|
||||||
|
publish
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发货相关DTO
|
||||||
|
export class WooShipOrderItemDTO {
|
||||||
|
order_item_id: number;
|
||||||
|
quantity: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WooShipOrderDTO {
|
||||||
|
tracking_number?: string;
|
||||||
|
shipping_provider?: string;
|
||||||
|
shipping_method?: string;
|
||||||
|
items?: WooShipOrderItemDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WooCancelShipOrderDTO {
|
||||||
|
reason?: string;
|
||||||
|
shipment_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WooBatchShipOrderItemDTO {
|
||||||
|
order_id: string;
|
||||||
|
tracking_number?: string;
|
||||||
|
shipping_provider?: string;
|
||||||
|
shipping_method?: string;
|
||||||
|
items?: WooShipOrderItemDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WooBatchShipOrdersDTO {
|
||||||
|
orders: WooBatchShipOrderItemDTO[];
|
||||||
|
}
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
|
||||||
import { Variation } from '../entity/variation.entity';
|
|
||||||
import { WpProduct } from '../entity/wp_product.entity';
|
|
||||||
import { Rule, RuleType } from '@midwayjs/validate';
|
|
||||||
import { ProductStatus } from '../enums/base.enum';
|
|
||||||
|
|
||||||
export class VariationDTO extends Variation {}
|
|
||||||
|
|
||||||
export class WpProductDTO extends WpProduct {
|
|
||||||
@ApiProperty({ description: '变体列表', type: VariationDTO, isArray: true })
|
|
||||||
variations?: VariationDTO[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpdateVariationDTO {
|
|
||||||
@ApiProperty({ description: '产品名称' })
|
|
||||||
@Rule(RuleType.string())
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: 'SKU' })
|
|
||||||
@Rule(RuleType.string().allow(''))
|
|
||||||
sku: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '常规价格', type: Number })
|
|
||||||
@Rule(RuleType.number())
|
|
||||||
regular_price: number; // 常规价格
|
|
||||||
|
|
||||||
@ApiProperty({ description: '销售价格', type: Number })
|
|
||||||
@Rule(RuleType.number())
|
|
||||||
sale_price: number; // 销售价格
|
|
||||||
|
|
||||||
@ApiProperty({ description: '是否促销中', type: Boolean })
|
|
||||||
@Rule(RuleType.boolean())
|
|
||||||
on_sale: boolean; // 是否促销中
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpdateWpProductDTO {
|
|
||||||
@ApiProperty({ description: '变体名称' })
|
|
||||||
@Rule(RuleType.string())
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: 'SKU' })
|
|
||||||
@Rule(RuleType.string().allow(''))
|
|
||||||
sku: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '常规价格', type: Number })
|
|
||||||
@Rule(RuleType.number())
|
|
||||||
regular_price: number; // 常规价格
|
|
||||||
|
|
||||||
@ApiProperty({ description: '销售价格', type: Number })
|
|
||||||
@Rule(RuleType.number())
|
|
||||||
sale_price: number; // 销售价格
|
|
||||||
|
|
||||||
@ApiProperty({ description: '是否促销中', type: Boolean })
|
|
||||||
@Rule(RuleType.boolean())
|
|
||||||
on_sale: boolean; // 是否促销中
|
|
||||||
}
|
|
||||||
|
|
||||||
export class QueryWpProductDTO {
|
|
||||||
@ApiProperty({ example: '1', description: '页码' })
|
|
||||||
@Rule(RuleType.number())
|
|
||||||
current: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: '10', description: '每页大小' })
|
|
||||||
@Rule(RuleType.number())
|
|
||||||
pageSize: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'ZYN', description: '产品名' })
|
|
||||||
@Rule(RuleType.string())
|
|
||||||
name?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: '1', description: '站点ID' })
|
|
||||||
@Rule(RuleType.string())
|
|
||||||
siteId?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '产品状态', enum: ProductStatus })
|
|
||||||
@Rule(RuleType.string().valid(...Object.values(ProductStatus)))
|
|
||||||
status?: ProductStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SetConstitutionDTO {
|
|
||||||
@ApiProperty({ type: Boolean })
|
|
||||||
@Rule(RuleType.boolean())
|
|
||||||
isProduct: boolean;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
description: '构成成分',
|
|
||||||
type: 'array',
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
sku: { type: 'string' },
|
|
||||||
quantity: { type: 'number' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@Rule(RuleType.array())
|
|
||||||
constitution: { sku: string; quantity: number }[] | null;
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
|
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity('area')
|
||||||
|
export class Area {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '名称' })
|
||||||
|
@Column()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '编码' })
|
||||||
|
@Column({ unique: true })
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
@ -1,53 +1,39 @@
|
||||||
import {
|
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Column,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
Entity,
|
|
||||||
} from 'typeorm';
|
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
|
import { Product } from './product.entity';
|
||||||
|
import { CategoryAttribute } from './category_attribute.entity';
|
||||||
|
|
||||||
@Entity()
|
@Entity('category')
|
||||||
export class Category {
|
export class Category {
|
||||||
@ApiProperty({
|
@ApiProperty({ description: 'ID' })
|
||||||
example: '1',
|
|
||||||
description: '分类 ID',
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({ description: '分类显示名称' })
|
||||||
example: '分类名称',
|
|
||||||
description: '分类名称',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@Column()
|
@Column()
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分类中文名称' })
|
||||||
|
@Column({ nullable: true })
|
||||||
|
titleCN: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分类唯一标识' })
|
||||||
|
@Column({ unique: true })
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({ description: '排序' })
|
||||||
description: '唯一识别key',
|
@Column({ default: 0 })
|
||||||
type: 'string',
|
sort: number;
|
||||||
required: true,
|
|
||||||
})
|
@OneToMany(() => Product, product => product.category)
|
||||||
@Column()
|
products: Product[];
|
||||||
unique_key: string;
|
|
||||||
|
@OneToMany(() => CategoryAttribute, categoryAttribute => categoryAttribute.category)
|
||||||
|
attributes: CategoryAttribute[];
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '2022-12-12 11:11:11',
|
|
||||||
description: '创建时间',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '2022-12-12 11:11:11',
|
|
||||||
description: '更新时间',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { Entity, PrimaryGeneratedColumn, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
import { Category } from './category.entity';
|
||||||
|
import { Dict } from './dict.entity';
|
||||||
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class CategoryAttribute {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分类' })
|
||||||
|
@ManyToOne(() => Category, { onDelete: 'CASCADE' })
|
||||||
|
@JoinColumn({ name: 'category_id' })
|
||||||
|
category: Category;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '关联的属性字典' })
|
||||||
|
@ManyToOne(() => Dict, { onDelete: 'CASCADE' })
|
||||||
|
@JoinColumn({ name: 'attribute_dict_id' })
|
||||||
|
attributeDict: Dict;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,58 @@
|
||||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
|
||||||
@Entity('customer')
|
@Entity('customer')
|
||||||
export class Customer {
|
export class Customer {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
site_id: number;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
origin_id: string;
|
||||||
|
|
||||||
@Column({ unique: true })
|
@Column({ unique: true })
|
||||||
email: string;
|
email: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
first_name: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
last_name: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
fullname: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
phone: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
avatar: string;
|
||||||
|
|
||||||
|
@Column({ type: 'json', nullable: true })
|
||||||
|
billing: any;
|
||||||
|
|
||||||
|
@Column({ type: 'json', nullable: true })
|
||||||
|
shipping: any;
|
||||||
|
|
||||||
|
@Column({ type: 'json', nullable: true })
|
||||||
|
raw: any;
|
||||||
|
|
||||||
@Column({ default: 0})
|
@Column({ default: 0})
|
||||||
rate: number;
|
rate: number;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
created_at: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updated_at: Date;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
site_created_at: Date;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
site_updated_at: Date;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* @description 字典
|
||||||
|
* @author ZKS
|
||||||
|
* @date 2025-11-27
|
||||||
|
*/
|
||||||
|
import { DictItem } from './dict_item.entity';
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
OneToMany,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Dict {
|
||||||
|
// 主键
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column({comment: '字典显示名称'})
|
||||||
|
title: string;
|
||||||
|
// 字典名称
|
||||||
|
@Column({ unique: true, comment: '字典名称' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
// 字典项
|
||||||
|
@OneToMany(() => DictItem, item => item.dict)
|
||||||
|
items: DictItem[];
|
||||||
|
|
||||||
|
// 是否可删除
|
||||||
|
@Column({ default: true, comment: '是否可删除' })
|
||||||
|
deletable: boolean;
|
||||||
|
// 创建时间
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
// 更新时间
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
/**
|
||||||
|
* @description 字典项
|
||||||
|
* @author ZKS
|
||||||
|
* @date 2025-11-27
|
||||||
|
*/
|
||||||
|
import { Dict } from './dict.entity';
|
||||||
|
import { Product } from './product.entity';
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
Index,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToMany,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
@Index(['name', 'dict'], { unique: true })
|
||||||
|
export class DictItem {
|
||||||
|
// 主键
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
// 字典项名称
|
||||||
|
@Column({ comment: '字典项显示名称' })
|
||||||
|
title: string;
|
||||||
|
// 目前没有单独做国际化, 所以这里先添加 titleCN 用来标注
|
||||||
|
@Column({ comment: '字典项中文名称', nullable: true })
|
||||||
|
titleCN: string;
|
||||||
|
// 唯一标识
|
||||||
|
@Column({ comment: '字典唯一标识名称' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
// 字典项值
|
||||||
|
@Column({ nullable: true, comment: '字典项值' })
|
||||||
|
value?: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true, comment: '图片' })
|
||||||
|
image: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true, comment: '简称' })
|
||||||
|
shortName: string;
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
@Column({ default: 0, comment: '排序' })
|
||||||
|
sort: number;
|
||||||
|
|
||||||
|
// 属于哪个字典
|
||||||
|
@ManyToOne(() => Dict, dict => dict.items)
|
||||||
|
@JoinColumn({ name: 'dict_id' })
|
||||||
|
dict: Dict;
|
||||||
|
|
||||||
|
// 关联的产品
|
||||||
|
@ManyToMany(() => Product, product => product.attributes)
|
||||||
|
products: Product[];
|
||||||
|
|
||||||
|
// 创建时间
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
// 更新时间
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
import {
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Column,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
Entity,
|
|
||||||
} from 'typeorm';
|
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
|
||||||
|
|
||||||
@Entity()
|
|
||||||
export class Flavors {
|
|
||||||
@ApiProperty()
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@Column()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
description: '唯一识别key',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@Column()
|
|
||||||
unique_key: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '2022-12-12 11:11:11',
|
|
||||||
description: '创建时间',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '2022-12-12 11:11:11',
|
|
||||||
description: '更新时间',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
@ -24,9 +24,9 @@ export class Order {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
siteId: string; // 来源站点唯一标识
|
siteId: number; // 来源站点唯一标识
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column()
|
||||||
|
|
@ -178,7 +178,7 @@ export class Order {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({
|
@Column({
|
||||||
type: 'mediumtext', // 设置字段类型为 MEDIUMTEXT
|
type: 'mediumtext', // 设置字段类型为 MEDIUMTEXT
|
||||||
nullable: true, // 可选:是否允许为 NULL
|
nullable: true, // 可选:是否允许为 NULL
|
||||||
})
|
})
|
||||||
@Expose()
|
@Expose()
|
||||||
customer_note: string;
|
customer_note: string;
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ export class OrderCoupon {
|
||||||
orderId: number; // 订单 ID
|
orderId: number; // 订单 ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
siteId: string; // 来源站点唯一标识
|
siteId: number; // 站点ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column()
|
||||||
|
|
@ -22,9 +22,9 @@ export class OrderFee {
|
||||||
orderId: number; // 订单 ID
|
orderId: number; // 订单 ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
siteId: string;
|
siteId: number; // 站点ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column()
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ export class OrderItem {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
siteId: string; // 来源站点唯一标识
|
siteId: number; // 来源站点唯一标识
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column()
|
||||||
|
|
@ -79,17 +79,17 @@ export class OrderItem {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
tax_class?: string; // 税类(来自 line_items.tax_class)
|
tax_class?: string; // 税类(来自 line_items.tax_class)
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ type: 'json', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
taxes?: any[]; // 税明细(来自 line_items.taxes,数组)
|
taxes?: any[]; // 税明细(来自 line_items.taxes,数组)
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ type: 'json', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
meta_data?: any[]; // 行项目元数据(包含订阅相关键值)
|
meta_data?: any[]; // 行项目元数据(包含订阅相关键值)
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
|
|
@ -99,7 +99,7 @@ export class OrderItem {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
global_unique_id?: string; // 全局唯一ID(部分主题/插件会提供)
|
global_unique_id?: string; // 全局唯一ID(部分主题/插件会提供)
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column('decimal', { precision: 10, scale: 2 })
|
@Column('decimal', { precision: 10, scale: 2 })
|
||||||
|
|
@ -109,17 +109,17 @@ export class OrderItem {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ type: 'json', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
image?: { id?: string | number; src?: string }; // 商品图片(对象,包含 id/src)
|
image?: { id?: string | number; src?: string }; // 商品图片(对象,包含 id/src)
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
parent_name?: string; // 父商品名称(组合/捆绑时可能使用)
|
parent_name?: string; // 父商品名称(组合/捆绑时可能使用)
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
bundled_by?: string; // 捆绑来源标识(bundled_by)
|
bundled_by?: string; // 捆绑来源标识(bundled_by)
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
|
|
@ -129,7 +129,7 @@ export class OrderItem {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ type: 'json', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
bundled_items?: any[]; // 捆绑项列表(数组)
|
bundled_items?: any[]; // 捆绑项列表(数组)
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: '2022-12-12 11:11:11',
|
example: '2022-12-12 11:11:11',
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ import {
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { Order } from './order.entity';
|
import { Order } from './order.entity';
|
||||||
|
|
||||||
@Entity('order_sale_original')
|
@Entity('order_item_original')
|
||||||
@Exclude()
|
@Exclude()
|
||||||
export class OrderSaleOriginal {
|
export class OrderItemOriginal {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
@Expose()
|
@Expose()
|
||||||
|
|
@ -27,9 +27,9 @@ export class OrderSaleOriginal {
|
||||||
orderId: number; // 订单 ID
|
orderId: number; // 订单 ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
siteId: string; // 来源站点唯一标识
|
siteId: number; // 站点ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ export class OrderRefund {
|
||||||
orderId: number; // 订单 ID
|
orderId: number; // 订单 ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
siteId: string; // 来源站点唯一标识
|
siteId: number; // 站点ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column()
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ export class OrderRefundItem {
|
||||||
refundId: number; // 订单 refund ID
|
refundId: number; // 订单 refund ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
siteId: string; // 来源站点唯一标识
|
siteId: number; // 站点ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column()
|
||||||
|
|
@ -9,10 +9,14 @@ import {
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
// import { Product } from './product.entity';
|
||||||
|
|
||||||
@Entity('order_sale')
|
@Entity('order_sale')
|
||||||
@Exclude()
|
@Exclude()
|
||||||
export class OrderSale {
|
export class OrderSale {
|
||||||
|
// @ManyToOne(() => Product, { onDelete: 'CASCADE' })
|
||||||
|
// @JoinColumn({ name: 'productId' })
|
||||||
|
// product: Product;
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
@Expose()
|
@Expose()
|
||||||
|
|
@ -24,9 +28,9 @@ export class OrderSale {
|
||||||
orderId: number; // 订单 ID
|
orderId: number; // 订单 ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
siteId: string; // 来源站点唯一标识
|
siteId: number; // 来源站点唯一标识
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ export class OrderShipping {
|
||||||
orderId: number; // 订单 ID
|
orderId: number; // 订单 ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
siteId: string;
|
siteId: number; // 站点ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column()
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,19 @@ import {
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
|
ManyToMany,
|
||||||
|
JoinTable,
|
||||||
|
OneToMany,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
|
import { DictItem } from './dict_item.entity';
|
||||||
|
import { ProductStockComponent } from './product_stock_component.entity';
|
||||||
|
import { ProductSiteSku } from './product_site_sku.entity';
|
||||||
|
import { Category } from './category.entity';
|
||||||
|
|
||||||
@Entity()
|
@Entity('product')
|
||||||
export class Product {
|
export class Product {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: '1',
|
example: '1',
|
||||||
|
|
@ -18,6 +27,14 @@ export class Product {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'sku'})
|
||||||
|
@Column({ unique: true })
|
||||||
|
sku: string;
|
||||||
|
// 类型 主要用来区分混装和单品 单品死
|
||||||
|
@ApiProperty({ description: '类型' })
|
||||||
|
@Column({ length: 16, default: 'single' })
|
||||||
|
type: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: 'ZYN 6MG WINTERGREEN',
|
example: 'ZYN 6MG WINTERGREEN',
|
||||||
description: '产品名称',
|
description: '产品名称',
|
||||||
|
|
@ -27,33 +44,55 @@ export class Product {
|
||||||
@Column()
|
@Column()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ description: '产品中文名称' })
|
||||||
@Column({ default: ''})
|
@Column({ default: '' })
|
||||||
nameCn: string;
|
nameCn: string;
|
||||||
|
|
||||||
@ApiProperty({ example: '产品描述', description: '产品描述', type: 'string' })
|
@ApiProperty({ example: '产品简短描述', description: '产品简短描述' })
|
||||||
|
@Column({ nullable: true })
|
||||||
|
shortDescription?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: '产品描述', description: '产品描述' })
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
@ApiProperty({ example: '1', description: '分类 ID', type: 'number' })
|
// 商品价格
|
||||||
@Column()
|
@ApiProperty({ description: '价格', example: 99.99 })
|
||||||
categoryId: number;
|
@Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })
|
||||||
|
price: number;
|
||||||
|
|
||||||
@ApiProperty()
|
// 促销价格
|
||||||
@Column()
|
@ApiProperty({ description: '促销价格', example: 99.99 })
|
||||||
flavorsId: number;
|
@Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })
|
||||||
|
promotionPrice: number;
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@Column()
|
|
||||||
strengthId: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@Column()
|
|
||||||
humidity: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: 'sku', type: 'string' })
|
|
||||||
@Column({ nullable: true })
|
// 分类关联
|
||||||
sku?: string;
|
@ManyToOne(() => Category, category => category.products)
|
||||||
|
@JoinColumn({ name: 'categoryId' })
|
||||||
|
category: Category;
|
||||||
|
|
||||||
|
@ManyToMany(() => DictItem, dictItem => dictItem.products, {
|
||||||
|
cascade: true,
|
||||||
|
})
|
||||||
|
@JoinTable()
|
||||||
|
attributes: DictItem[];
|
||||||
|
|
||||||
|
// 产品的库存组成,一对多关系(使用独立表)
|
||||||
|
@ApiProperty({ description: '库存组成', type: ProductStockComponent, isArray: true })
|
||||||
|
@OneToMany(() => ProductStockComponent, (component) => component.product, { cascade: true })
|
||||||
|
components: ProductStockComponent[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '站点 SKU 列表', type: ProductSiteSku, isArray: true })
|
||||||
|
@OneToMany(() => ProductSiteSku, (siteSku) => siteSku.product, { cascade: true })
|
||||||
|
siteSkus: ProductSiteSku[];
|
||||||
|
|
||||||
|
// 来源
|
||||||
|
@ApiProperty({ description: '来源', example: '1' })
|
||||||
|
@Column({ default: 0 })
|
||||||
|
source: number;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: '2022-12-12 11:11:11',
|
example: '2022-12-12 11:11:11',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import {
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Entity,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
|
import { Product } from './product.entity';
|
||||||
|
|
||||||
|
@Entity('product_site_sku')
|
||||||
|
export class ProductSiteSku {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '站点 SKU' })
|
||||||
|
@Column({ length: 100, comment: '站点 SKU' })
|
||||||
|
siteSku: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Product, product => product.siteSkus, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn({ name: 'productId' })
|
||||||
|
product: Product;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
productId: number;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
|
import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
import { Product } from './product.entity';
|
||||||
|
|
||||||
|
@Entity('product_stock_component')
|
||||||
|
export class ProductStockComponent {
|
||||||
|
@ApiProperty({ type: Number })
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ type: Number })
|
||||||
|
@Column()
|
||||||
|
productId: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '组件所关联的 SKU', type: 'string' })
|
||||||
|
@Column({ type: 'varchar', length: 64 })
|
||||||
|
sku: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: Number, description: '组成数量' })
|
||||||
|
@Column({ type: 'int', default: 1 })
|
||||||
|
quantity: number;
|
||||||
|
|
||||||
|
// 多对一,组件隶属于一个产品
|
||||||
|
@ManyToOne(() => Product, (product) => product.components, { onDelete: 'CASCADE' })
|
||||||
|
product: Product;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间' })
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新时间' })
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
@ -10,11 +10,11 @@ export class PurchaseOrderItem {
|
||||||
|
|
||||||
@ApiProperty({ type: String })
|
@ApiProperty({ type: String })
|
||||||
@Column()
|
@Column()
|
||||||
productSku: string;
|
sku: string;
|
||||||
|
|
||||||
@ApiProperty({ type: String })
|
@ApiProperty({ type: String })
|
||||||
@Column()
|
@Column()
|
||||||
productName: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty({ type: Number })
|
@ApiProperty({ type: Number })
|
||||||
@Column()
|
@Column()
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,47 @@
|
||||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
import { Area } from './area.entity';
|
||||||
|
import { StockPoint } from './stock_point.entity';
|
||||||
|
|
||||||
@Entity('site')
|
@Entity('site')
|
||||||
export class Site {
|
export class Site {
|
||||||
@PrimaryGeneratedColumn({ type: 'int' })
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
@Column({ length: 255, nullable: true })
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
@Column({ name: 'website_url', length: 255, nullable: true })
|
||||||
consumerKey: string;
|
websiteUrl: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
@Column({ length: 255, nullable: true })
|
||||||
consumerSecret: string;
|
consumerKey?: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 255, unique: true })
|
@Column({ length: 255, nullable: true })
|
||||||
siteName: string;
|
consumerSecret?: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 32, default: 'woocommerce' })
|
@Column({ nullable: true })
|
||||||
type: string; // 平台类型:woocommerce | shopyy
|
token?: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 64, nullable: true })
|
@Column({ length: 255, unique: true })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ length: 255, nullable: true })
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@Column({ length: 32, default: 'woocommerce' })
|
||||||
|
type: string; // 平台类型:woocommerce | shopyy
|
||||||
|
|
||||||
|
@Column({ length: 64, nullable: true })
|
||||||
skuPrefix: string;
|
skuPrefix: string;
|
||||||
|
|
||||||
@Column({ type: 'tinyint', default: 0 })
|
@Column({ default: false })
|
||||||
isDisabled: number;
|
isDisabled: boolean;
|
||||||
|
|
||||||
|
@ManyToMany(() => Area)
|
||||||
|
@JoinTable()
|
||||||
|
areas: Area[];
|
||||||
|
|
||||||
|
@ManyToMany(() => StockPoint, stockPoint => stockPoint.sites)
|
||||||
|
@JoinTable()
|
||||||
|
stockPoints: StockPoint[];
|
||||||
}
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ export class Stock {
|
||||||
|
|
||||||
@ApiProperty({ type: String })
|
@ApiProperty({ type: String })
|
||||||
@Column()
|
@Column()
|
||||||
productSku: string;
|
sku: string;
|
||||||
|
|
||||||
@ApiProperty({ type: Number })
|
@ApiProperty({ type: Number })
|
||||||
@Column()
|
@Column()
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,12 @@ import {
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
|
ManyToMany,
|
||||||
|
JoinTable,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { Shipment } from './shipment.entity';
|
import { Shipment } from './shipment.entity';
|
||||||
|
import { Area } from './area.entity';
|
||||||
|
import { Site } from './site.entity';
|
||||||
|
|
||||||
@Entity('stock_point')
|
@Entity('stock_point')
|
||||||
export class StockPoint extends BaseEntity {
|
export class StockPoint extends BaseEntity {
|
||||||
|
|
@ -51,7 +55,7 @@ export class StockPoint extends BaseEntity {
|
||||||
@Column({ default: 'uniuni' })
|
@Column({ default: 'uniuni' })
|
||||||
upStreamName: string;
|
upStreamName: string;
|
||||||
|
|
||||||
@Column()
|
@Column({ default: 0 })
|
||||||
upStreamStockPointId: number;
|
upStreamStockPointId: number;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
|
|
@ -72,4 +76,11 @@ export class StockPoint extends BaseEntity {
|
||||||
|
|
||||||
@DeleteDateColumn()
|
@DeleteDateColumn()
|
||||||
deletedAt: Date; // 软删除时间
|
deletedAt: Date; // 软删除时间
|
||||||
|
|
||||||
|
@ManyToMany(() => Area)
|
||||||
|
@JoinTable()
|
||||||
|
areas: Area[];
|
||||||
|
|
||||||
|
@ManyToMany(() => Site, site => site.stockPoints)
|
||||||
|
sites: Site[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export class StockRecord {
|
||||||
|
|
||||||
@ApiProperty({ type: String })
|
@ApiProperty({ type: String })
|
||||||
@Column()
|
@Column()
|
||||||
productSku: string;
|
sku: string;
|
||||||
|
|
||||||
@ApiProperty({ type: StockRecordOperationType })
|
@ApiProperty({ type: StockRecordOperationType })
|
||||||
@Column({ type: 'enum', enum: StockRecordOperationType })
|
@Column({ type: 'enum', enum: StockRecordOperationType })
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
import {
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Column,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
Entity,
|
|
||||||
} from 'typeorm';
|
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
|
||||||
|
|
||||||
@Entity()
|
|
||||||
export class Strength {
|
|
||||||
@ApiProperty()
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@Column()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
description: '唯一识别key',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@Column()
|
|
||||||
unique_key: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '2022-12-12 11:11:11',
|
|
||||||
description: '创建时间',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '2022-12-12 11:11:11',
|
|
||||||
description: '更新时间',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
@ -12,68 +12,68 @@ import { SubscriptionStatus } from '../enums/base.enum';
|
||||||
@Entity('subscription')
|
@Entity('subscription')
|
||||||
@Exclude()
|
@Exclude()
|
||||||
export class Subscription {
|
export class Subscription {
|
||||||
// 本地主键,自增 ID
|
// 本地主键,自增 ID
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
@Expose()
|
@Expose()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
// 站点唯一标识,用于区分不同来源站点
|
// 站点唯一标识,用于区分不同来源站点
|
||||||
@ApiProperty({ description: '来源站点唯一标识' })
|
@ApiProperty({ description: '来源站点唯一标识' })
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
siteId: string;
|
siteId: number;
|
||||||
|
|
||||||
// WooCommerce 订阅的原始 ID(字符串化),用于幂等更新
|
// WooCommerce 订阅的原始 ID(字符串化),用于幂等更新
|
||||||
@ApiProperty({ description: 'WooCommerce 订阅 ID' })
|
@ApiProperty({ description: 'WooCommerce 订阅 ID' })
|
||||||
@Column()
|
@Column()
|
||||||
@Expose()
|
@Expose()
|
||||||
externalSubscriptionId: string;
|
externalSubscriptionId: string;
|
||||||
|
|
||||||
// 订阅状态(active/cancelled/on-hold 等)
|
// 订阅状态(active/cancelled/on-hold 等)
|
||||||
@ApiProperty({ type: SubscriptionStatus })
|
@ApiProperty({ type: SubscriptionStatus })
|
||||||
@Column({ type: 'enum', enum: SubscriptionStatus })
|
@Column({ type: 'enum', enum: SubscriptionStatus })
|
||||||
@Expose()
|
@Expose()
|
||||||
status: SubscriptionStatus;
|
status: SubscriptionStatus;
|
||||||
|
|
||||||
// 货币代码,例如 USD/CAD
|
// 货币代码,例如 USD/CAD
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ default: '' })
|
@Column({ default: '' })
|
||||||
@Expose()
|
@Expose()
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
// 总金额,保留两位小数
|
// 总金额,保留两位小数
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column('decimal', { precision: 10, scale: 2, default: 0 })
|
@Column('decimal', { precision: 10, scale: 2, default: 0 })
|
||||||
@Expose()
|
@Expose()
|
||||||
total: number;
|
total: number;
|
||||||
|
|
||||||
// 计费周期(day/week/month/year)
|
// 计费周期(day/week/month/year)
|
||||||
@ApiProperty({ description: '计费周期 e.g. day/week/month/year' })
|
@ApiProperty({ description: '计费周期 e.g. day/week/month/year' })
|
||||||
@Column({ default: '' })
|
@Column({ default: '' })
|
||||||
@Expose()
|
@Expose()
|
||||||
billing_period: string;
|
billing_period: string;
|
||||||
|
|
||||||
// 计费周期间隔(例如 1/3/12)
|
// 计费周期间隔(例如 1/3/12)
|
||||||
@ApiProperty({ description: '计费周期间隔 e.g. 1/3/12' })
|
@ApiProperty({ description: '计费周期间隔 e.g. 1/3/12' })
|
||||||
@Column({ type: 'int', default: 0 })
|
@Column({ type: 'int', default: 0 })
|
||||||
@Expose()
|
@Expose()
|
||||||
billing_interval: number;
|
billing_interval: number;
|
||||||
|
|
||||||
// 客户 ID(WooCommerce 用户 ID)
|
// 客户 ID(WooCommerce 用户 ID)
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ type: 'int', default: 0 })
|
@Column({ type: 'int', default: 0 })
|
||||||
@Expose()
|
@Expose()
|
||||||
customer_id: number;
|
customer_id: number;
|
||||||
|
|
||||||
// 客户邮箱(从 billing.email 或 customer_email 提取)
|
// 客户邮箱(从 billing.email 或 customer_email 提取)
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ default: '' })
|
@Column({ default: '' })
|
||||||
@Expose()
|
@Expose()
|
||||||
customer_email: string;
|
customer_email: string;
|
||||||
|
|
||||||
// 父订单/订阅 ID(如有)
|
// 父订单/订阅 ID(如有)
|
||||||
@ApiProperty({ description: '父订单/父订阅ID(如有)' })
|
@ApiProperty({ description: '父订单/父订阅ID(如有)' })
|
||||||
@Column({ type: 'int', default: 0 })
|
@Column({ type: 'int', default: 0 })
|
||||||
@Expose()
|
@Expose()
|
||||||
parent_id: number;
|
parent_id: number;
|
||||||
|
|
@ -102,25 +102,25 @@ export class Subscription {
|
||||||
@Expose()
|
@Expose()
|
||||||
end_date: Date;
|
end_date: Date;
|
||||||
|
|
||||||
// 商品项(订阅行项目)
|
// 商品项(订阅行项目)
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ type: 'json', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
line_items: any[];
|
line_items: any[];
|
||||||
|
|
||||||
// 额外元数据(键值对)
|
// 额外元数据(键值对)
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ type: 'json', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
meta_data: any[];
|
meta_data: any[];
|
||||||
|
|
||||||
// 创建时间(数据库自动生成)
|
// 创建时间(数据库自动生成)
|
||||||
@ApiProperty({ example: '2022-12-12 11:11:11', description: '创建时间', required: true })
|
@ApiProperty({ example: '2022-12-12 11:11:11', description: '创建时间', required: true })
|
||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
@Expose()
|
@Expose()
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
// 更新时间(数据库自动生成)
|
// 更新时间(数据库自动生成)
|
||||||
@ApiProperty({ example: '2022-12-12 11:11:11', description: '更新时间', required: true })
|
@ApiProperty({ example: '2022-12-12 11:11:11', description: '更新时间', required: true })
|
||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
@Expose()
|
@Expose()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
@Entity('template')
|
||||||
|
export class Template {
|
||||||
|
@ApiProperty({ type: 'number' })
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'string' })
|
||||||
|
@Column({ unique: true })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'string' })
|
||||||
|
@Column('text')
|
||||||
|
value: string;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true ,name:"描述"})
|
||||||
|
@Column('text',{nullable: true,comment: "描述"})
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'string', nullable: true, description: '测试数据JSON' })
|
||||||
|
@Column('text', { nullable: true, comment: '测试数据JSON' })
|
||||||
|
testData?: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: true,
|
||||||
|
description: '是否可删除',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@Column({ default: true })
|
||||||
|
deletable: boolean;
|
||||||
|
@ApiProperty({
|
||||||
|
example: '2022-12-12 11:11:11',
|
||||||
|
description: '创建时间',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: '2022-12-12 11:11:11',
|
||||||
|
description: '更新时间',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
@ -9,11 +9,11 @@ export class TransferItem {
|
||||||
|
|
||||||
@ApiProperty({ type: String })
|
@ApiProperty({ type: String })
|
||||||
@Column()
|
@Column()
|
||||||
productSku: string;
|
sku: string;
|
||||||
|
|
||||||
@ApiProperty({ type: String })
|
@ApiProperty({ type: String })
|
||||||
@Column()
|
@Column()
|
||||||
productName: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty({ type: Number })
|
@ApiProperty({ type: Number })
|
||||||
@Column()
|
@Column()
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,14 @@ export class User {
|
||||||
password: string;
|
password: string;
|
||||||
|
|
||||||
// @Column() // 默认角色为管理员
|
// @Column() // 默认角色为管理员
|
||||||
// roleId: number; // 角色 (如:admin, editor, viewer)
|
// roleId: number; // 角色 (如:admin, editor, viewer)
|
||||||
|
|
||||||
@Column({ type: 'simple-array', nullable: true })
|
@Column({ type: 'simple-array', nullable: true })
|
||||||
permissions: string[]; // 自定义权限 (如:['user:add', 'user:edit'])
|
permissions: string[]; // 自定义权限 (如:['user:add', 'user:edit'])
|
||||||
|
|
||||||
|
// 新增邮箱字段,可选且唯一
|
||||||
|
@Column({ unique: true, nullable: true })
|
||||||
|
email?: string;
|
||||||
|
|
||||||
@Column({ default: false })
|
@Column({ default: false })
|
||||||
isSuper: boolean; // 超级管理员
|
isSuper: boolean; // 超级管理员
|
||||||
|
|
@ -28,4 +32,8 @@ export class User {
|
||||||
|
|
||||||
@Column({ default: true })
|
@Column({ default: true })
|
||||||
isActive: boolean; // 用户是否启用
|
isActive: boolean; // 用户是否启用
|
||||||
|
|
||||||
|
// 备注字段(可选)
|
||||||
|
@Column({ nullable: true })
|
||||||
|
remark?: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
import {
|
|
||||||
Entity,
|
|
||||||
Column,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Unique,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
} from 'typeorm';
|
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
|
||||||
|
|
||||||
@Entity('variation')
|
|
||||||
@Unique(['siteId', 'externalProductId', 'externalVariationId']) // 确保变体的唯一性
|
|
||||||
export class Variation {
|
|
||||||
@ApiProperty({
|
|
||||||
example: '1',
|
|
||||||
description: 'ID',
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '1',
|
|
||||||
description: 'wp网站ID',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@Column()
|
|
||||||
siteId: string; // 来源站点唯一标识
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '1',
|
|
||||||
description: 'wp产品ID',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@Column()
|
|
||||||
externalProductId: string; // WooCommerce 产品 ID
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '1',
|
|
||||||
description: 'wp变体ID',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@Column()
|
|
||||||
externalVariationId: string; // WooCommerce 变体 ID
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '1',
|
|
||||||
description: '对应WP产品表的ID',
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@Column()
|
|
||||||
productId: number; // 对应WP产品表的 ID
|
|
||||||
|
|
||||||
@ApiProperty({ description: 'sku', type: 'string' })
|
|
||||||
@Column({ nullable: true })
|
|
||||||
sku?: string; // sku 编码
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
description: '变体名称',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@Column()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '常规价格', type: Number })
|
|
||||||
@Column('decimal', { precision: 10, scale: 2, nullable: true })
|
|
||||||
regular_price: number; // 常规价格
|
|
||||||
|
|
||||||
@ApiProperty({ description: '销售价格', type: Number })
|
|
||||||
@Column('decimal', { precision: 10, scale: 2, nullable: true })
|
|
||||||
sale_price: number; // 销售价格
|
|
||||||
|
|
||||||
@ApiProperty({ description: '是否促销中', type: Boolean })
|
|
||||||
@Column({ nullable: true, type: Boolean })
|
|
||||||
on_sale: boolean; // 是否促销中
|
|
||||||
|
|
||||||
@ApiProperty({ description: '是否删除', type: Boolean })
|
|
||||||
@Column({ nullable: true, type: Boolean , default: false })
|
|
||||||
on_delete: boolean; // 是否删除
|
|
||||||
|
|
||||||
@Column({ type: 'json', nullable: true })
|
|
||||||
attributes: Record<string, any>; // 变体的属性
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '2022-12-12 11:11:11',
|
|
||||||
description: '创建时间',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '2022-12-12 11:11:11',
|
|
||||||
description: '更新时间',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: Date;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
description: '变体构成成分',
|
|
||||||
type: 'array',
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
sku: { type: 'string' },
|
|
||||||
quantity: { type: 'number' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@Column('json', { nullable: true, comment: '变体构成成分' })
|
|
||||||
constitution: { sku: string; quantity: number }[] | null;
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Site } from './site.entity';
|
||||||
import {
|
import {
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
Column,
|
Column,
|
||||||
|
|
@ -5,6 +6,8 @@ import {
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
Unique,
|
Unique,
|
||||||
Entity,
|
Entity,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import { ProductStatus, ProductStockStatus, ProductType } from '../enums/base.enum';
|
import { ProductStatus, ProductStockStatus, ProductType } from '../enums/base.enum';
|
||||||
|
|
@ -22,13 +25,18 @@ export class WpProduct {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: '1',
|
example: 1,
|
||||||
description: 'wp网站ID',
|
description: 'wp网站ID',
|
||||||
type: 'string',
|
type: 'number',
|
||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
@Column()
|
@Column({ type: 'int', nullable: true })
|
||||||
siteId: string;
|
siteId: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '站点信息', type: Site })
|
||||||
|
@ManyToOne(() => Site)
|
||||||
|
@JoinColumn({ name: 'siteId', referencedColumnName: 'id' })
|
||||||
|
site: Site;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: '1',
|
example: '1',
|
||||||
|
|
@ -39,7 +47,7 @@ export class WpProduct {
|
||||||
@Column()
|
@Column()
|
||||||
externalProductId: string;
|
externalProductId: string;
|
||||||
|
|
||||||
@ApiProperty({ description: 'sku', type: 'string' })
|
@ApiProperty({ description: '商店sku', type: 'string' })
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
sku?: string;
|
sku?: string;
|
||||||
|
|
||||||
|
|
@ -53,42 +61,150 @@ export class WpProduct {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '产品状态', enum: ProductStatus })
|
@ApiProperty({ description: '产品状态', enum: ProductStatus })
|
||||||
@Column({ type: 'enum', enum: ProductStatus })
|
@Column({ type: 'enum', enum: ProductStatus, comment: '产品状态: draft, pending, private, publish' })
|
||||||
status: ProductStatus;
|
status: ProductStatus;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '是否为特色产品', type: 'boolean' })
|
||||||
|
@Column({ default: false, comment: '是否为特色产品' })
|
||||||
|
featured: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '目录可见性', type: 'string' })
|
||||||
|
@Column({ default: 'visible', comment: '目录可见性: visible, catalog, search, hidden' })
|
||||||
|
catalog_visibility: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品描述', type: 'string' })
|
||||||
|
@Column({ type: 'text', nullable: true, comment: '产品描述' })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品短描述', type: 'string' })
|
||||||
|
@Column({ type: 'text', nullable: true, comment: '产品短描述' })
|
||||||
|
short_description: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '上下架状态', enum: ProductStockStatus })
|
@ApiProperty({ description: '上下架状态', enum: ProductStockStatus })
|
||||||
@Column({
|
@Column({
|
||||||
name: 'stock_status',
|
name: 'stock_status',
|
||||||
type: 'enum',
|
type: 'enum',
|
||||||
enum: ProductStockStatus,
|
enum: ProductStockStatus,
|
||||||
default: ProductStockStatus.INSTOCK
|
default: ProductStockStatus.INSTOCK,
|
||||||
|
comment: '库存状态: instock, outofstock, onbackorder',
|
||||||
})
|
})
|
||||||
stockStatus: ProductStockStatus;
|
stockStatus: ProductStockStatus;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '库存数量', type: 'number' })
|
||||||
|
@Column({ type: 'int', nullable: true, comment: '库存数量' })
|
||||||
|
stock_quantity: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '允许缺货下单', type: 'string' })
|
||||||
|
@Column({ nullable: true, comment: '允许缺货下单: no, notify, yes' })
|
||||||
|
backorders: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '是否单独出售', type: 'boolean' })
|
||||||
|
@Column({ default: false, comment: '是否单独出售' })
|
||||||
|
sold_individually: boolean;
|
||||||
|
|
||||||
@ApiProperty({ description: '常规价格', type: Number })
|
@ApiProperty({ description: '常规价格', type: Number })
|
||||||
@Column('decimal', { precision: 10, scale: 2, nullable: true })
|
@Column('decimal', { precision: 10, scale: 2, nullable: true, comment: '常规价格' })
|
||||||
regular_price: number; // 常规价格
|
regular_price: number;
|
||||||
|
|
||||||
@ApiProperty({ description: '销售价格', type: Number })
|
@ApiProperty({ description: '销售价格', type: Number })
|
||||||
@Column('decimal', { precision: 10, scale: 2, nullable: true })
|
@Column('decimal', { precision: 10, scale: 2, nullable: true, comment: '销售价格' })
|
||||||
sale_price: number; // 销售价格
|
sale_price: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '促销开始日期', type: 'datetime' })
|
||||||
|
@Column({ type: 'datetime', nullable: true, comment: '促销开始日期' })
|
||||||
|
date_on_sale_from: Date| null;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '促销结束日期', type: 'datetime' })
|
||||||
|
@Column({ type: 'datetime', nullable: true, comment: '促销结束日期' })
|
||||||
|
date_on_sale_to: Date|null;
|
||||||
|
|
||||||
@ApiProperty({ description: '是否促销中', type: Boolean })
|
@ApiProperty({ description: '是否促销中', type: Boolean })
|
||||||
@Column({ nullable: true, type: Boolean })
|
@Column({ nullable: true, type: 'boolean', comment: '是否促销中' })
|
||||||
on_sale: boolean; // 是否促销中
|
on_sale: boolean;
|
||||||
|
|
||||||
@ApiProperty({ description: '是否删除', type: Boolean })
|
@ApiProperty({ description: '税务状态', type: 'string' })
|
||||||
@Column({ nullable: true, type: Boolean , default: false })
|
@Column({ default: 'taxable', comment: '税务状态: taxable, shipping, none' })
|
||||||
on_delete: boolean; // 是否删除
|
tax_status: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '税类', type: 'string' })
|
||||||
|
@Column({ nullable: true, comment: '税类' })
|
||||||
|
tax_class: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({ description: '重量(g)', type: 'number' })
|
||||||
description: '产品类型',
|
@Column('decimal', { precision: 10, scale: 2, nullable: true, comment: '重量(g)' })
|
||||||
enum: ProductType,
|
weight: number;
|
||||||
})
|
|
||||||
@Column({ type: 'enum', enum: ProductType })
|
@ApiProperty({ description: '尺寸(长宽高)', type: 'json' })
|
||||||
|
@Column({ type: 'json', nullable: true, comment: '尺寸' })
|
||||||
|
dimensions: { length: string; width: string; height: string };
|
||||||
|
|
||||||
|
@ApiProperty({ description: '允许评论', type: 'boolean' })
|
||||||
|
@Column({ default: true, comment: '允许客户评论' })
|
||||||
|
reviews_allowed: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '购买备注', type: 'string' })
|
||||||
|
@Column({ nullable: true, comment: '购买备注' })
|
||||||
|
purchase_note: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '菜单排序', type: 'number' })
|
||||||
|
@Column({ default: 0, comment: '菜单排序' })
|
||||||
|
menu_order: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品类型', enum: ProductType })
|
||||||
|
@Column({ type: 'enum', enum: ProductType, comment: '产品类型: simple, grouped, external, variable' })
|
||||||
type: ProductType;
|
type: ProductType;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '父产品ID', type: 'number' })
|
||||||
|
@Column({ default: 0, comment: '父产品ID' })
|
||||||
|
parent_id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '外部产品URL', type: 'string' })
|
||||||
|
@Column({ type: 'text', nullable: true, comment: '外部产品URL' })
|
||||||
|
external_url: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '外部产品按钮文本', type: 'string' })
|
||||||
|
@Column({ nullable: true, comment: '外部产品按钮文本' })
|
||||||
|
button_text: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分组产品', type: 'json' })
|
||||||
|
@Column({ type: 'json', nullable: true, comment: '分组产品' })
|
||||||
|
grouped_products: number[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '追加销售', type: 'json' })
|
||||||
|
@Column({ type: 'json', nullable: true, comment: '追加销售' })
|
||||||
|
upsell_ids: number[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '交叉销售', type: 'json' })
|
||||||
|
@Column({ type: 'json', nullable: true, comment: '交叉销售' })
|
||||||
|
cross_sell_ids: number[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分类', type: 'json' })
|
||||||
|
@Column({ type: 'json', nullable: true, comment: '分类' })
|
||||||
|
categories: { id: number; name: string; slug: string }[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '标签', type: 'json' })
|
||||||
|
@Column({ type: 'json', nullable: true, comment: '标签' })
|
||||||
|
tags: { id: number; name: string; slug: string }[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '图片', type: 'json' })
|
||||||
|
@Column({ type: 'json', nullable: true, comment: '图片' })
|
||||||
|
images: { id: number; src: string; name: string; alt: string }[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品属性', type: 'json' })
|
||||||
|
@Column({ type: 'json', nullable: true, comment: '产品属性' })
|
||||||
|
attributes: { id: number; name: string; position: number; visible: boolean; variation: boolean; options: string[] }[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '默认属性', type: 'json' })
|
||||||
|
@Column({ type: 'json', nullable: true, comment: '默认属性' })
|
||||||
|
default_attributes: { id: number; name: string; option: string }[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'GTIN', type: 'string' })
|
||||||
|
@Column({ nullable: true, comment: 'GTIN, UPC, EAN, or ISBN' })
|
||||||
|
gtin: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '是否删除', type: 'boolean' })
|
||||||
|
@Column({ nullable: true, type: 'boolean', default: false, comment: '是否删除' })
|
||||||
|
on_delete: boolean;
|
||||||
|
|
||||||
@Column({ type: 'json', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
metadata: Record<string, any>; // 产品的其他扩展字段
|
metadata: Record<string, any>; // 产品的其他扩展字段
|
||||||
|
|
@ -108,18 +224,4 @@ export class WpProduct {
|
||||||
})
|
})
|
||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
description: '产品构成成分',
|
|
||||||
type: 'array',
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
sku: { type: 'string' },
|
|
||||||
quantity: { type: 'number' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@Column('json', { nullable: true, comment: '产品构成成分' })
|
|
||||||
constitution: { sku: string; quantity: number }[] | null;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ export enum OrderStatus {
|
||||||
REFUNDED = 'refunded', // 已退款
|
REFUNDED = 'refunded', // 已退款
|
||||||
FAILED = 'failed', // 失败订单
|
FAILED = 'failed', // 失败订单
|
||||||
DRAFT = 'draft', // 草稿
|
DRAFT = 'draft', // 草稿
|
||||||
|
AUTO_DRAFT = 'auto-draft', // 自动草稿(TODO:不知道为什么出现)
|
||||||
|
|
||||||
// TRASH = 'trash',
|
// TRASH = 'trash',
|
||||||
// refund 也就是退款相关的状态
|
// refund 也就是退款相关的状态
|
||||||
RETURN_REQUESTED = 'return-requested', // 已申请退款
|
RETURN_REQUESTED = 'return-requested', // 已申请退款
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,6 @@ export interface IUserOptions {
|
||||||
uid: number;
|
uid: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WpSite {
|
|
||||||
id: string;
|
|
||||||
wpApiUrl: string;
|
|
||||||
consumerKey: string;
|
|
||||||
consumerSecret: string;
|
|
||||||
siteName: string;
|
|
||||||
email: string;
|
|
||||||
emailPswd: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PaginationParams {
|
export interface PaginationParams {
|
||||||
current?: number; // 当前页码
|
current?: number; // 当前页码
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,287 @@
|
||||||
|
// src/interface/platform.interface.ts
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 电商平台抽象接口
|
||||||
|
* 定义所有平台必须实现的通用方法
|
||||||
|
*/
|
||||||
|
export interface IPlatformService {
|
||||||
|
/**
|
||||||
|
* 获取产品列表
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @returns 产品列表数据
|
||||||
|
*/
|
||||||
|
getProducts(site: any): Promise<any[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个产品
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param id 产品ID
|
||||||
|
* @returns 产品数据
|
||||||
|
*/
|
||||||
|
getProduct(site: any, id: number): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取产品变体列表
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param productId 产品ID
|
||||||
|
* @returns 变体列表数据
|
||||||
|
*/
|
||||||
|
getVariations(site: any, productId: number): Promise<any[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取产品变体详情
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param productId 产品ID
|
||||||
|
* @param variationId 变体ID
|
||||||
|
* @returns 变体详情数据
|
||||||
|
*/
|
||||||
|
getVariation(site: any, productId: number, variationId: number): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取订单列表
|
||||||
|
* @param siteId 站点ID
|
||||||
|
* @returns 订单列表数据
|
||||||
|
*/
|
||||||
|
getOrders(siteId: number, params: Record<string, any>): Promise<any[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取订单详情
|
||||||
|
* @param siteId 站点ID
|
||||||
|
* @param orderId 订单ID
|
||||||
|
* @returns 订单详情数据
|
||||||
|
*/
|
||||||
|
getOrder(siteId: number, orderId: string): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取订阅列表(如果平台支持)
|
||||||
|
* @param siteId 站点ID
|
||||||
|
* @returns 订阅列表数据
|
||||||
|
*/
|
||||||
|
getSubscriptions?(siteId: number): Promise<any[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户列表
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @returns 客户列表数据
|
||||||
|
*/
|
||||||
|
getCustomers(site: any): Promise<any[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个客户
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param id 客户ID
|
||||||
|
* @returns 客户数据
|
||||||
|
*/
|
||||||
|
getCustomer(site: any, id: number): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建产品
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param data 产品数据
|
||||||
|
* @returns 创建结果
|
||||||
|
*/
|
||||||
|
createProduct(site: any, data: any): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新产品
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param productId 产品ID
|
||||||
|
* @param data 更新数据
|
||||||
|
* @returns 更新结果
|
||||||
|
*/
|
||||||
|
updateProduct(site: any, productId: string, data: any): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新产品状态
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param productId 产品ID
|
||||||
|
* @param status 产品状态
|
||||||
|
* @param stockStatus 库存状态
|
||||||
|
* @returns 更新结果
|
||||||
|
*/
|
||||||
|
updateProductStatus(site: any, productId: string, status: string, stockStatus: string): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新产品变体
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param productId 产品ID
|
||||||
|
* @param variationId 变体ID
|
||||||
|
* @param data 更新数据
|
||||||
|
* @returns 更新结果
|
||||||
|
*/
|
||||||
|
updateVariation(site: any, productId: string, variationId: string, data: any): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新订单
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param orderId 订单ID
|
||||||
|
* @param data 更新数据
|
||||||
|
* @returns 更新结果
|
||||||
|
*/
|
||||||
|
updateOrder(site: any, orderId: string, data: Record<string, any>): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建物流信息
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param orderId 订单ID
|
||||||
|
* @param data 物流数据
|
||||||
|
* @returns 创建结果
|
||||||
|
*/
|
||||||
|
createShipment(site: any, orderId: string, data: any): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除物流信息
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param orderId 订单ID
|
||||||
|
* @param trackingId 物流跟踪ID
|
||||||
|
* @returns 删除结果
|
||||||
|
*/
|
||||||
|
deleteShipment(site: any, orderId: string, trackingId: string): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量处理产品
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param data 批量操作数据
|
||||||
|
* @returns 处理结果
|
||||||
|
*/
|
||||||
|
batchProcessProducts(site: any, data: { create?: any[]; update?: any[]; delete?: any[] }): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 api 客户端
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @returns api 客户端
|
||||||
|
*/
|
||||||
|
getApiClient(site: any): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户列表
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @returns 客户列表数据
|
||||||
|
*/
|
||||||
|
getCustomers(site: any): Promise<any[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个客户
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param id 客户ID
|
||||||
|
* @returns 客户数据
|
||||||
|
*/
|
||||||
|
getCustomer(site: any, id: number): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评论列表
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @returns 评论列表数据
|
||||||
|
*/
|
||||||
|
getReviews(site: any): Promise<any[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建评论
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param data 评论数据
|
||||||
|
* @returns 创建结果
|
||||||
|
*/
|
||||||
|
createReview(site: any, data: any): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新评论
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param reviewId 评论ID
|
||||||
|
* @param data 更新数据
|
||||||
|
* @returns 更新结果
|
||||||
|
*/
|
||||||
|
updateReview(site: any, reviewId: number, data: any): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除评论
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param reviewId 评论ID
|
||||||
|
* @returns 删除结果
|
||||||
|
*/
|
||||||
|
deleteReview(site: any, reviewId: number): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取分页资源
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param resource 资源类型
|
||||||
|
* @param params 查询参数
|
||||||
|
* @param namespace API命名空间
|
||||||
|
* @returns 分页数据
|
||||||
|
*/
|
||||||
|
fetchResourcePaged<T>(site: any, resource: string, params: Record<string, any>, namespace?: any): Promise<{ items: T[]; total: number; totalPages: number; page: number; per_page: number }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取分页媒体
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns 分页媒体数据
|
||||||
|
*/
|
||||||
|
fetchMediaPaged(site: any, params: Record<string, any>): Promise<{ items: any[]; total: number; totalPages: number; page: number; per_page: number }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除媒体
|
||||||
|
* @param siteId 站点ID
|
||||||
|
* @param mediaId 媒体ID
|
||||||
|
* @param force 是否强制删除
|
||||||
|
* @returns 删除结果
|
||||||
|
*/
|
||||||
|
deleteMedia(siteId: number, mediaId: number, force?: boolean): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新媒体
|
||||||
|
* @param siteId 站点ID
|
||||||
|
* @param mediaId 媒体ID
|
||||||
|
* @param data 更新数据
|
||||||
|
* @returns 更新结果
|
||||||
|
*/
|
||||||
|
updateMedia(siteId: number, mediaId: number, data: any): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换媒体为WebP格式
|
||||||
|
* @param siteId 站点ID
|
||||||
|
* @param mediaIds 媒体ID列表
|
||||||
|
* @returns 转换结果
|
||||||
|
*/
|
||||||
|
convertMediaToWebp(siteId: number, mediaIds: Array<number | string>): Promise<{ converted: any[]; failed: Array<{ id: number | string; error: string }> }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取webhook列表
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns 分页webhook列表
|
||||||
|
*/
|
||||||
|
getWebhooks(site: any, params: any): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个webhook
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param webhookId webhook ID
|
||||||
|
* @returns webhook详情
|
||||||
|
*/
|
||||||
|
getWebhook(site: any, webhookId: string | number): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建webhook
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param data webhook数据
|
||||||
|
* @returns 创建结果
|
||||||
|
*/
|
||||||
|
createWebhook(site: any, data: any): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新webhook
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param webhookId webhook ID
|
||||||
|
* @param data 更新数据
|
||||||
|
* @returns 更新结果
|
||||||
|
*/
|
||||||
|
updateWebhook(site: any, webhookId: string | number, data: any): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除webhook
|
||||||
|
* @param site 站点配置信息
|
||||||
|
* @param webhookId webhook ID
|
||||||
|
* @returns 删除结果
|
||||||
|
*/
|
||||||
|
deleteWebhook(site: any, webhookId: string | number): Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
import {
|
||||||
|
CreateReviewDTO,
|
||||||
|
UpdateReviewDTO,
|
||||||
|
UnifiedMediaDTO,
|
||||||
|
UnifiedOrderDTO,
|
||||||
|
UnifiedPaginationDTO,
|
||||||
|
UnifiedProductDTO,
|
||||||
|
UnifiedReviewDTO,
|
||||||
|
UnifiedSearchParamsDTO,
|
||||||
|
UnifiedSubscriptionDTO,
|
||||||
|
UnifiedCustomerDTO,
|
||||||
|
UnifiedWebhookDTO,
|
||||||
|
UnifiedWebhookPaginationDTO,
|
||||||
|
CreateWebhookDTO,
|
||||||
|
UpdateWebhookDTO,
|
||||||
|
} from '../dto/site-api.dto';
|
||||||
|
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
||||||
|
|
||||||
|
export interface ISiteAdapter {
|
||||||
|
/**
|
||||||
|
* 获取产品列表
|
||||||
|
*/
|
||||||
|
getProducts(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedProductDTO>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有产品
|
||||||
|
*/
|
||||||
|
getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个产品
|
||||||
|
*/
|
||||||
|
getProduct(id: string | number): Promise<UnifiedProductDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取订单列表
|
||||||
|
*/
|
||||||
|
getOrders(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有订单
|
||||||
|
*/
|
||||||
|
getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个订单
|
||||||
|
*/
|
||||||
|
getOrder(id: string | number): Promise<UnifiedOrderDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取订阅列表
|
||||||
|
*/
|
||||||
|
getSubscriptions(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedSubscriptionDTO>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有订阅
|
||||||
|
*/
|
||||||
|
getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise<UnifiedSubscriptionDTO[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取媒体列表
|
||||||
|
*/
|
||||||
|
getMedia(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedMediaDTO>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有媒体
|
||||||
|
*/
|
||||||
|
getAllMedia(params?: UnifiedSearchParamsDTO): Promise<UnifiedMediaDTO[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建媒体
|
||||||
|
*/
|
||||||
|
createMedia(file: any): Promise<UnifiedMediaDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评论列表
|
||||||
|
*/
|
||||||
|
getReviews(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedReviewDTO>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有评论
|
||||||
|
*/
|
||||||
|
getAllReviews(params?: UnifiedSearchParamsDTO): Promise<UnifiedReviewDTO[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建评论
|
||||||
|
*/
|
||||||
|
createReview(data: CreateReviewDTO): Promise<UnifiedReviewDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新评论
|
||||||
|
*/
|
||||||
|
updateReview(id: number, data: UpdateReviewDTO): Promise<UnifiedReviewDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除评论
|
||||||
|
*/
|
||||||
|
deleteReview(id: number): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建产品
|
||||||
|
*/
|
||||||
|
createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新产品
|
||||||
|
*/
|
||||||
|
updateProduct(id: string | number, data: Partial<UnifiedProductDTO>): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新产品变体
|
||||||
|
*/
|
||||||
|
updateVariation(productId: string | number, variationId: string | number, data: any): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取订单备注
|
||||||
|
*/
|
||||||
|
getOrderNotes(orderId: string | number): Promise<any[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建订单备注
|
||||||
|
*/
|
||||||
|
createOrderNote(orderId: string | number, data: any): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除产品
|
||||||
|
*/
|
||||||
|
deleteProduct(id: string | number): Promise<boolean>;
|
||||||
|
|
||||||
|
batchProcessProducts?(data: BatchOperationDTO): Promise<BatchOperationResultDTO>;
|
||||||
|
|
||||||
|
createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO>;
|
||||||
|
updateOrder(id: string | number, data: Partial<UnifiedOrderDTO>): Promise<boolean>;
|
||||||
|
deleteOrder(id: string | number): Promise<boolean>;
|
||||||
|
|
||||||
|
batchProcessOrders?(data: BatchOperationDTO): Promise<BatchOperationResultDTO>;
|
||||||
|
|
||||||
|
getCustomers(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedCustomerDTO>>;
|
||||||
|
getAllCustomers(params?: UnifiedSearchParamsDTO): Promise<UnifiedCustomerDTO[]>;
|
||||||
|
getCustomer(id: string | number): Promise<UnifiedCustomerDTO>;
|
||||||
|
createCustomer(data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO>;
|
||||||
|
updateCustomer(id: string | number, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO>;
|
||||||
|
deleteCustomer(id: string | number): Promise<boolean>;
|
||||||
|
|
||||||
|
batchProcessCustomers?(data: BatchOperationDTO): Promise<BatchOperationResultDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取webhooks列表
|
||||||
|
*/
|
||||||
|
getWebhooks(params: UnifiedSearchParamsDTO): Promise<UnifiedWebhookPaginationDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有webhooks
|
||||||
|
*/
|
||||||
|
getAllWebhooks(params?: UnifiedSearchParamsDTO): Promise<UnifiedWebhookDTO[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个webhook
|
||||||
|
*/
|
||||||
|
getWebhook(id: string | number): Promise<UnifiedWebhookDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建webhook
|
||||||
|
*/
|
||||||
|
createWebhook(data: CreateWebhookDTO): Promise<UnifiedWebhookDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新webhook
|
||||||
|
*/
|
||||||
|
updateWebhook(id: string | number, data: UpdateWebhookDTO): Promise<UnifiedWebhookDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除webhook
|
||||||
|
*/
|
||||||
|
deleteWebhook(id: string | number): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取站点链接列表
|
||||||
|
*/
|
||||||
|
getLinks(): Promise<Array<{title: string, url: string}>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单发货
|
||||||
|
*/
|
||||||
|
shipOrder(orderId: string | number, data: {
|
||||||
|
tracking_number?: string;
|
||||||
|
shipping_provider?: string;
|
||||||
|
shipping_method?: string;
|
||||||
|
items?: Array<{
|
||||||
|
order_item_id: number;
|
||||||
|
quantity: number;
|
||||||
|
}>;
|
||||||
|
}): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消订单发货
|
||||||
|
*/
|
||||||
|
cancelShipOrder(orderId: string | number, data: {
|
||||||
|
reason?: string;
|
||||||
|
shipment_id?: string;
|
||||||
|
}): Promise<any>;
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1 @@
|
||||||
import { FORMAT, ILogger, Logger } from '@midwayjs/core';
|
export {}
|
||||||
import { IJob, Job } from '@midwayjs/cron';
|
|
||||||
|
|
||||||
@Job({
|
|
||||||
cronTime: FORMAT.CRONTAB.EVERY_DAY,
|
|
||||||
runOnInit: true,
|
|
||||||
})
|
|
||||||
export class SyncProductJob implements IJob {
|
|
||||||
@Logger()
|
|
||||||
logger: ILogger;
|
|
||||||
|
|
||||||
onTick() {
|
|
||||||
}
|
|
||||||
onComplete?(result: any) {}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,13 @@ export class AuthMiddleware implements IMiddleware<Context, NextFunction> {
|
||||||
'/webhook/woocommerce',
|
'/webhook/woocommerce',
|
||||||
'/logistics/getTrackingNumber',
|
'/logistics/getTrackingNumber',
|
||||||
'/logistics/getListByTrackingId',
|
'/logistics/getListByTrackingId',
|
||||||
|
'/product/categories/all',
|
||||||
|
'/product/category/1/attributes',
|
||||||
|
'/product/category/2/attributes',
|
||||||
|
'/product/category/3/attributes',
|
||||||
|
'/product/category/4/attributes',
|
||||||
|
'/product/list',
|
||||||
|
'/dict/items',
|
||||||
];
|
];
|
||||||
|
|
||||||
match(ctx: Context) {
|
match(ctx: Context) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
import { Middleware, IMiddleware } from '@midwayjs/core';
|
||||||
|
import { NextFunction, Context } from '@midwayjs/koa';
|
||||||
|
import * as qs from 'qs';
|
||||||
|
|
||||||
|
@Middleware()
|
||||||
|
export class QueryNormalizeMiddleware implements IMiddleware<Context, NextFunction> {
|
||||||
|
// 数值与布尔转换函数,用于将字符串转换为合适的类型
|
||||||
|
private toPrimitive(value: any): any {
|
||||||
|
const s = String(value);
|
||||||
|
if (s === 'true') return true;
|
||||||
|
if (s === 'false') return false;
|
||||||
|
const n = Number(s);
|
||||||
|
return Number.isFinite(n) && s !== '' ? n : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 深度遍历对象并对字符串进行trim
|
||||||
|
private trimDeep(input: any): any {
|
||||||
|
if (input === null || input === undefined) return input;
|
||||||
|
if (typeof input === 'string') return input.trim();
|
||||||
|
if (Array.isArray(input)) return input.map(v => this.trimDeep(v));
|
||||||
|
if (typeof input === 'object') {
|
||||||
|
const out: Record<string, any> = {};
|
||||||
|
for (const key of Object.keys(input)) {
|
||||||
|
out[key] = this.trimDeep((input as any)[key]);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将路径数组对应的值赋到对象中,支持构建嵌套结构与数组
|
||||||
|
private assignByPath(target: Record<string, any>, path: string[], value: any): void {
|
||||||
|
let cur: any = target;
|
||||||
|
for (let i = 0; i < path.length; i++) {
|
||||||
|
const key = path[i];
|
||||||
|
const isLast = i === path.length - 1;
|
||||||
|
if (isLast) {
|
||||||
|
if (key === '') {
|
||||||
|
if (!Array.isArray(cur)) return;
|
||||||
|
cur.push(value);
|
||||||
|
} else {
|
||||||
|
if (cur[key] === undefined) cur[key] = value;
|
||||||
|
else if (Array.isArray(cur[key])) cur[key].push(value);
|
||||||
|
else cur[key] = [cur[key], value];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!cur[key] || typeof cur[key] !== 'object') cur[key] = {};
|
||||||
|
cur = cur[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析可能为 JSON 字符串或鍵值串的输入为对象
|
||||||
|
private parseLooseObject(input: any): Record<string, any> {
|
||||||
|
if (!input) return {};
|
||||||
|
if (typeof input === 'object') return input as Record<string, any>;
|
||||||
|
const str = String(input).trim();
|
||||||
|
try {
|
||||||
|
if (str.startsWith('{') || str.startsWith('[')) {
|
||||||
|
const json = JSON.parse(str);
|
||||||
|
if (json && typeof json === 'object') return json as Record<string, any>;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
const obj: Record<string, any> = {};
|
||||||
|
const pairs = str.split(/[&;,]/).map(s => s.trim()).filter(Boolean);
|
||||||
|
for (const pair of pairs) {
|
||||||
|
const idxEq = pair.indexOf('=');
|
||||||
|
const idxColon = pair.indexOf(':');
|
||||||
|
const idx = idxEq >= 0 ? idxEq : idxColon;
|
||||||
|
if (idx < 0) continue;
|
||||||
|
const key = decodeURIComponent(pair.slice(0, idx)).trim();
|
||||||
|
const valueRaw = decodeURIComponent(pair.slice(idx + 1)).trim();
|
||||||
|
obj[key] = this.toPrimitive(valueRaw);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve() {
|
||||||
|
return async (ctx: Context, next: NextFunction) => {
|
||||||
|
const raw = String((ctx.request as any).querystring || '');
|
||||||
|
const parsed = qs.parse(raw, { allowDots: true, depth: 10, ignoreQueryPrefix: false, comma: true });
|
||||||
|
const query = { ...(ctx.request.query || {}), ...(parsed as any) } as Record<string, any>;
|
||||||
|
const trimmedTop: Record<string, any> = {};
|
||||||
|
for (const k of Object.keys(query)) {
|
||||||
|
const v = (query as any)[k];
|
||||||
|
trimmedTop[k] = typeof v === 'string' ? String(v).trim() : v;
|
||||||
|
}
|
||||||
|
Object.assign(query, trimmedTop);
|
||||||
|
|
||||||
|
// 解析 where 对象,支持 JSON 字符串与括号或点号语法
|
||||||
|
const hasWhereInput = (query as any).where !== undefined;
|
||||||
|
let whereObj: Record<string, any> = this.parseLooseObject((query as any).where);
|
||||||
|
for (const k of Object.keys(query)) {
|
||||||
|
if (k === 'where') continue;
|
||||||
|
if (k.startsWith('where[') || k.startsWith('where.')) {
|
||||||
|
const pathStr = k.replace(/^where\.?/, '').replace(/\]/g, '').replace(/\[/g, '.');
|
||||||
|
const path = pathStr.split('.');
|
||||||
|
const val = this.toPrimitive((query as any)[k]);
|
||||||
|
this.assignByPath(whereObj, path, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const hasWhereBracketKeys = Object.keys(query).some(k => k.startsWith('where[') || k.startsWith('where.'));
|
||||||
|
if (hasWhereInput || hasWhereBracketKeys) (query as any).where = this.trimDeep(whereObj);
|
||||||
|
|
||||||
|
// 解析 order 对象,支持 JSON 字符串与括号或点号语法
|
||||||
|
const hasOrderInput = (query as any).order !== undefined;
|
||||||
|
let orderObj: Record<string, any> = this.parseLooseObject((query as any).order);
|
||||||
|
for (const k of Object.keys(query)) {
|
||||||
|
if (k === 'order') continue;
|
||||||
|
if (k.startsWith('order[') || k.startsWith('order.')) {
|
||||||
|
const pathStr = k.replace(/^order\.?/, '').replace(/\]/g, '').replace(/\[/g, '.');
|
||||||
|
const path = pathStr.split('.');
|
||||||
|
const val = this.toPrimitive((query as any)[k]);
|
||||||
|
this.assignByPath(orderObj, path, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const hasOrderBracketKeys = Object.keys(query).some(k => k.startsWith('order[') || k.startsWith('order.'));
|
||||||
|
if (hasOrderInput || hasOrderBracketKeys) (query as any).order = this.trimDeep(orderObj);
|
||||||
|
|
||||||
|
// 将常见分页参数转换为数字类型
|
||||||
|
if (query.page !== undefined) (query as any).page = Number(query.page);
|
||||||
|
if ((query as any).page_size !== undefined) (query as any).page_size = Number((query as any).page_size);
|
||||||
|
if ((query as any).per_page !== undefined) (query as any).per_page = Number((query as any).per_page);
|
||||||
|
if ((query as any).customer_id !== undefined) (query as any).customer_id = Number((query as any).customer_id);
|
||||||
|
|
||||||
|
ctx.request.query = query as any;
|
||||||
|
(ctx as any).query = query as any;
|
||||||
|
return await next();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getName(): string {
|
||||||
|
return 'queryNormalize';
|
||||||
|
}
|
||||||
|
|
||||||
|
static getPriority(): number {
|
||||||
|
// 优先级靠前,优先处理查询参数
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue