style: 统一中文标点符号为英文格式并更新数据库配置
refactor: 修改注释和描述中的中文标点符号为英文格式 chore: 更新本地数据库配置的端口、密码和数据库名 docs: 删除迁移指南和排列组合修复文档
This commit is contained in:
parent
2888d62037
commit
a067720c76
|
|
@ -1,49 +0,0 @@
|
|||
# 数据库迁移指南
|
||||
|
||||
为了支持区域坐标功能,需要执行数据库迁移操作,将新增的 `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开始为区域添加坐标信息
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
# 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. 验证现有产品匹配是否正确
|
||||
|
|
@ -405,7 +405,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
}
|
||||
|
||||
mapUpdateOrderParams(data: Partial<UnifiedOrderDTO>): any {
|
||||
// 构建 ShopYY 订单更新参数(仅包含传入的字段)
|
||||
// 构建 ShopYY 订单更新参数(仅包含传入的字段)
|
||||
const params: any = {};
|
||||
|
||||
// 仅当字段存在时才添加到更新参数中
|
||||
|
|
@ -665,7 +665,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
const statusMap = {
|
||||
'pending': '100', // 100 未完成
|
||||
'processing': '110', // 110 待处理
|
||||
'completed': "180", // 180 已完成(确认收货)
|
||||
'completed': "180", // 180 已完成(确认收货)
|
||||
'cancelled': '190', // 190 取消
|
||||
};
|
||||
|
||||
|
|
@ -797,7 +797,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
return status === 'publish' ? 1 : 0;
|
||||
};
|
||||
|
||||
// 构建 ShopYY 产品更新参数(仅包含传入的字段)
|
||||
// 构建 ShopYY 产品更新参数(仅包含传入的字段)
|
||||
const params: any = {};
|
||||
|
||||
// 仅当字段存在时才添加到更新参数中
|
||||
|
|
@ -816,7 +816,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
params.inventory_quantity = data.stock_status === 'instock' ? (data.stock_quantity || 1) : 0;
|
||||
}
|
||||
|
||||
// 添加变体信息(如果存在)
|
||||
// 添加变体信息(如果存在)
|
||||
if (data.variations && data.variations.length > 0) {
|
||||
params.variants = data.variations.map((variation: UnifiedProductVariationDTO) => {
|
||||
const variationParams: any = {};
|
||||
|
|
@ -836,7 +836,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
});
|
||||
}
|
||||
|
||||
// 添加图片信息(如果存在)
|
||||
// 添加图片信息(如果存在)
|
||||
if (data.images && data.images.length > 0) {
|
||||
params.images = data.images.map((image: any) => ({
|
||||
id: image.id,
|
||||
|
|
@ -846,12 +846,12 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
}));
|
||||
}
|
||||
|
||||
// 添加标签信息(如果存在)
|
||||
// 添加标签信息(如果存在)
|
||||
if (data.tags && data.tags.length > 0) {
|
||||
params.tags = data.tags.map((tag: any) => tag.name || '');
|
||||
}
|
||||
|
||||
// 添加分类信息(如果存在)
|
||||
// 添加分类信息(如果存在)
|
||||
if (data.categories && data.categories.length > 0) {
|
||||
params.collections = data.categories.map((category: any) => ({
|
||||
id: category.id,
|
||||
|
|
@ -1121,7 +1121,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
}
|
||||
|
||||
mapUpdateVariationParams(data: Partial<UnifiedProductVariationDTO>): any {
|
||||
// 构建 ShopYY 变体更新参数(仅包含传入的字段)
|
||||
// 构建 ShopYY 变体更新参数(仅包含传入的字段)
|
||||
const params: any = {};
|
||||
|
||||
// 仅当字段存在时才添加到更新参数中
|
||||
|
|
@ -1292,12 +1292,12 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
return stockStatus === 'instock' ? 1 : 0;
|
||||
};
|
||||
|
||||
shopyyOrderStatusMap = {//订单状态 100 未完成;110 待处理;180 已完成(确认收货); 190 取消;
|
||||
shopyyOrderStatusMap = {//订单状态 100 未完成;110 待处理;180 已完成(确认收货); 190 取消;
|
||||
[100]: OrderStatus.PENDING, // 100 未完成 转为 pending
|
||||
[110]: OrderStatus.PROCESSING, // 110 待处理 转为 processing
|
||||
// 已发货
|
||||
|
||||
[180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed
|
||||
[180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed
|
||||
[190]: OrderStatus.CANCEL // 190 取消 转为 cancelled
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -609,7 +609,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
// await api.put(`orders/${orderId}`, { status: 'processing' });
|
||||
|
||||
// // 添加取消履行的备注
|
||||
// const note = `订单履行已取消${data.reason ? `,原因:${data.reason}` : ''}`;
|
||||
// const note = `订单履行已取消${data.reason ? `,原因:${data.reason}` : ''}`;
|
||||
// await api.post(`orders/${orderId}/notes`, { note, customer_note: true });
|
||||
|
||||
// return {
|
||||
|
|
@ -698,14 +698,14 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
}));
|
||||
}
|
||||
|
||||
// 映射变体数据(注意:WooCommerce API 中变体通常通过单独的端点处理)
|
||||
// 映射变体数据(注意:WooCommerce API 中变体通常通过单独的端点处理)
|
||||
// 这里只映射变体的基本信息,具体创建/更新变体需要额外处理
|
||||
if (data.variations && Array.isArray(data.variations)) {
|
||||
// 对于WooProduct类型,variations字段只存储变体ID
|
||||
mapped.variations = data.variations.map(variation => variation.id as number);
|
||||
}
|
||||
|
||||
// 映射下载数据(如果产品是可下载的)
|
||||
// 映射下载数据(如果产品是可下载的)
|
||||
// if (data.downloads && Array.isArray(data.downloads)) {
|
||||
// mapped.downloads = data.downloads.map(download => ({
|
||||
// id: download.id as number,
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ export default {
|
|||
dataSource: {
|
||||
default: {
|
||||
host: 'localhost',
|
||||
port: "3306",
|
||||
port: "23306",
|
||||
username: 'root',
|
||||
password: 'root',
|
||||
database: 'inventory',
|
||||
password: '12345678',
|
||||
database: 'inventory_v2',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ export class MainConfiguration {
|
|||
}
|
||||
|
||||
/**
|
||||
* 初始化数据库(如果不存在则创建)
|
||||
* 初始化数据库(如果不存在则创建)
|
||||
*/
|
||||
private async initializeDatabase(): Promise<void> {
|
||||
// 使用注入的数据库配置
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
// 平台特性:产品导出(特殊CSV,走平台服务)
|
||||
// 平台特性:产品导出(特殊CSV,走平台服务)
|
||||
@Get('/:siteId/links')
|
||||
async getLinks(
|
||||
@Param('siteId') siteId: number
|
||||
|
|
@ -429,7 +429,7 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
// 平台特性:产品导入(特殊CSV,走平台服务)
|
||||
// 平台特性:产品导入(特殊CSV,走平台服务)
|
||||
@Post('/:siteId/products/import-special')
|
||||
@ApiOkResponse({ type: Object })
|
||||
async importProductsSpecial(
|
||||
|
|
@ -443,7 +443,7 @@ export class SiteApiController {
|
|||
const created: any[] = [];
|
||||
const failed: any[] = [];
|
||||
if (site.type === 'woocommerce') {
|
||||
// 解析 CSV 为对象数组(若传入 items 则优先 items)
|
||||
// 解析 CSV 为对象数组(若传入 items 则优先 items)
|
||||
let payloads = items;
|
||||
if (!payloads.length && csvText) {
|
||||
const lines = csvText.split(/\r?\n/).filter(Boolean);
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ const flavorsData = [
|
|||
{ name: 'arctic-mint', title: 'arctic mint', titleCn: '北极薄荷', shortName: 'AR' },
|
||||
{ name: 'baddie-blueberries', title: 'baddie blueberries', titleCn: '时髦蓝莓', shortName: 'BA' },
|
||||
{ name: 'banana', title: 'banana', titleCn: '香蕉', shortName: 'BA' },
|
||||
{ name: 'banana-(solid)', title: 'banana (solid)', titleCn: '香蕉(固体)', shortName: 'BA' },
|
||||
{ name: 'banana-(solid)', title: 'banana (solid)', titleCn: '香蕉(固体)', shortName: 'BA' },
|
||||
{ name: 'banana-berry', title: 'banana berry', titleCn: '香蕉莓果', shortName: 'BA' },
|
||||
{ name: 'banana-berry-melon-ice', title: 'banana berry melon ice', titleCn: '香蕉莓果瓜冰', shortName: 'BA' },
|
||||
{ name: 'banana-blackberry', title: 'banana blackberry', titleCn: '香蕉黑莓', shortName: 'BA' },
|
||||
|
|
@ -137,7 +137,7 @@ const flavorsData = [
|
|||
{ name: 'bangin-blood-orange-iced', title: 'bangin blood orange iced', titleCn: '爆炸血橙冰', shortName: 'BA' },
|
||||
{ name: 'berries-in-the-6ix', title: 'berries in the 6ix', titleCn: '多伦多莓果', shortName: 'BE' },
|
||||
{ name: 'berry-burst', title: 'berry burst', titleCn: '浆果爆发', shortName: 'BE' },
|
||||
{ name: 'berry-burst-(thermal)', title: 'berry burst (thermal)', titleCn: '浆果爆发(热感)', shortName: 'BE' },
|
||||
{ name: 'berry-burst-(thermal)', title: 'berry burst (thermal)', titleCn: '浆果爆发(热感)', shortName: 'BE' },
|
||||
{ name: 'berry-ice', title: 'berry ice', titleCn: '浆果冰', shortName: 'BE' },
|
||||
{ name: 'berry-lime-ice', title: 'berry lime ice', titleCn: '浆果青柠冰', shortName: 'BE' },
|
||||
{ name: 'berry-trio-ice', title: 'berry trio ice', titleCn: '三重浆果冰', shortName: 'BE' },
|
||||
|
|
@ -145,7 +145,7 @@ const flavorsData = [
|
|||
{ name: 'black-cherry', title: 'black cherry', titleCn: '黑樱桃', shortName: 'BL' },
|
||||
{ name: 'blackcherry', title: 'blackcherry', titleCn: '黑樱桃混合', shortName: 'BL' },
|
||||
{ name: 'blackcurrant-ice', title: 'blackcurrant ice', titleCn: '黑加仑冰', shortName: 'BL' },
|
||||
{ name: 'black-currant-ice', title: 'black currant ice', titleCn: '黑加仑冰(空格版)', shortName: 'BL' },
|
||||
{ name: 'black-currant-ice', title: 'black currant ice', titleCn: '黑加仑冰(空格版)', shortName: 'BL' },
|
||||
{ name: 'black-licorice', title: 'black licorice', titleCn: '黑甘草', shortName: 'BL' },
|
||||
{ name: 'black-tea', title: 'black tea', titleCn: '红茶', shortName: 'BL' },
|
||||
{ name: 'blackberry-ice', title: 'blackberry ice', titleCn: '黑莓冰', shortName: 'BL' },
|
||||
|
|
@ -168,7 +168,7 @@ const flavorsData = [
|
|||
{ name: 'blue-razz', title: 'blue razz', titleCn: '蓝覆盆子', shortName: 'BL' },
|
||||
{ name: 'blue-razz-hype', title: 'blue razz hype', titleCn: '蓝覆盆子热情', shortName: 'BL' },
|
||||
{ name: 'blue-razz-ice', title: 'blue razz ice', titleCn: '蓝覆盆子冰', shortName: 'BL' },
|
||||
{ name: 'blue-razz-ice-(solid)', title: 'blue razz ice (solid)', titleCn: '蓝覆盆子冰(固体)', shortName: 'BL' },
|
||||
{ name: 'blue-razz-ice-(solid)', title: 'blue razz ice (solid)', titleCn: '蓝覆盆子冰(固体)', shortName: 'BL' },
|
||||
{ name: 'blue-razz-ice-glace', title: 'blue razz ice glace', titleCn: '蓝覆盆子冰格', shortName: 'BL' },
|
||||
{ name: 'blue-razz-lemon-ice', title: 'blue razz lemon ice', titleCn: '蓝覆盆子柠檬冰', shortName: 'BL' },
|
||||
{ name: 'blue-razz-lemonade', title: 'blue razz lemonade', titleCn: '蓝覆盆子柠檬水', shortName: 'BL' },
|
||||
|
|
@ -196,7 +196,7 @@ const flavorsData = [
|
|||
{ name: 'bumpin-blackcurrant-iced', title: 'bumpin blackcurrant iced', titleCn: '黑加仑热烈冰', shortName: 'BU' },
|
||||
{ name: 'burst-ice', title: 'burst ice', titleCn: '爆炸冰', shortName: 'BU' },
|
||||
{ name: 'bussin-banana-iced', title: 'bussin banana iced', titleCn: '香蕉热烈冰', shortName: 'BU' },
|
||||
{ name: 'bussin-banana-iced', title: 'bussin banana iced', titleCn: '香蕉热烈冰(重复)', shortName: 'BU' },
|
||||
{ name: 'bussin-banana-iced', title: 'bussin banana iced', titleCn: '香蕉热烈冰(重复)', shortName: 'BU' },
|
||||
{ name: 'california-cherry', title: 'california cherry', titleCn: '加州樱桃', shortName: 'CA' },
|
||||
{ name: 'cantaloupe-mango-banana', title: 'cantaloupe mango banana', titleCn: '香瓜芒果香蕉', shortName: 'CA' },
|
||||
{ name: 'caramel', title: 'caramel', titleCn: '焦糖', shortName: 'CA' },
|
||||
|
|
@ -230,7 +230,7 @@ const flavorsData = [
|
|||
{ name: 'citrus-chill', title: 'citrus chill', titleCn: '柑橘清凉', shortName: 'CI' },
|
||||
{ name: 'citrus-smash-ice', title: 'citrus smash ice', titleCn: '柑橘冲击冰', shortName: 'CI' },
|
||||
{ name: 'citrus-sunrise', title: 'citrus sunrise', titleCn: '柑橘日出', shortName: 'CI' },
|
||||
{ name: 'citrus-sunrise-(thermal)', title: 'citrus sunrise (thermal)', titleCn: '柑橘日出(热感)', shortName: 'CI' },
|
||||
{ name: 'citrus-sunrise-(thermal)', title: 'citrus sunrise (thermal)', titleCn: '柑橘日出(热感)', shortName: 'CI' },
|
||||
{ name: 'classic', title: 'classic', titleCn: '经典', shortName: 'CL' },
|
||||
{ name: 'classic-ice', title: 'classic ice', titleCn: '经典冰', shortName: 'CL' },
|
||||
{ name: 'classic-mint-ice', title: 'classic mint ice', titleCn: '经典薄荷冰', shortName: 'CL' },
|
||||
|
|
@ -310,7 +310,7 @@ const flavorsData = [
|
|||
{ name: 'fizzy', title: 'fizzy', titleCn: '汽水', shortName: 'FI' },
|
||||
{ name: 'flavourless', title: 'flavourless', titleCn: '无味', shortName: 'FL' },
|
||||
{ name: 'flippin-fruit-flash', title: 'flippin fruit flash', titleCn: '翻转水果闪电', shortName: 'FL' },
|
||||
{ name: 'flippin-fruit-flash-(rainbow-burst)', title: 'flippin fruit flash (rainbow burst)', titleCn: '翻转水果闪电(彩虹爆发)', shortName: 'FL' },
|
||||
{ name: 'flippin-fruit-flash-(rainbow-burst)', title: 'flippin fruit flash (rainbow burst)', titleCn: '翻转水果闪电(彩虹爆发)', shortName: 'FL' },
|
||||
{ name: 'forest-fruits', title: 'forest fruits', titleCn: '森林水果', shortName: 'FO' },
|
||||
{ name: 'fragrant-grapefruit', title: 'fragrant grapefruit', titleCn: '香气葡萄柚', shortName: 'FR' },
|
||||
{ name: 'freeze', title: 'freeze', titleCn: '冰冻', shortName: 'FR' },
|
||||
|
|
@ -340,14 +340,14 @@ const flavorsData = [
|
|||
{ name: 'fuji-melon-ice', title: 'fuji melon ice', titleCn: '富士瓜冰', shortName: 'FU' },
|
||||
{ name: 'full-charge', title: 'full charge', titleCn: '满电', shortName: 'FU' },
|
||||
{ name: 'gb', title: 'gb', titleCn: '软糖', shortName: 'GB' },
|
||||
{ name: 'gb(gummy-bear)', title: 'gb(gummy bear)', titleCn: '软糖(Gummy Bear)', shortName: 'GB' },
|
||||
{ name: 'gb(gummy-bear)', title: 'gb(gummy bear)', titleCn: '软糖(Gummy Bear)', shortName: 'GB' },
|
||||
{ name: 'gentle-mint', title: 'gentle mint', titleCn: '温和薄荷', shortName: 'GE' },
|
||||
{ name: 'ghost-cola-&-vanilla', title: 'ghost cola & vanilla', titleCn: '幽灵可乐香草', shortName: 'GH' },
|
||||
{ name: 'ghost-cola-ice', title: 'ghost cola ice', titleCn: '幽灵可乐冰', shortName: 'GH' },
|
||||
{ name: 'ghost-mango', title: 'ghost mango', titleCn: '幽灵芒果', shortName: 'GH' },
|
||||
{ name: 'ghost-original', title: 'ghost original', titleCn: '幽灵原味', shortName: 'GH' },
|
||||
{ name: 'ghost-watermelon-ice', title: 'ghost watermelon ice', titleCn: '幽灵西瓜冰', shortName: 'GH' },
|
||||
{ name: 'gnarly-green-d-(green-dew)', title: 'gnarly green d (green dew)', titleCn: '狂野绿 D(绿色露水)', shortName: 'GN' },
|
||||
{ name: 'gnarly-green-d-(green-dew)', title: 'gnarly green d (green dew)', titleCn: '狂野绿 D(绿色露水)', shortName: 'GN' },
|
||||
{ name: 'gold-edition', title: 'gold edition', titleCn: '金版', shortName: 'GO' },
|
||||
{ name: 'grape', title: 'grape', titleCn: '葡萄', shortName: 'GR' },
|
||||
{ name: 'grape-cherry', title: 'grape cherry', titleCn: '葡萄樱桃', shortName: 'GR' },
|
||||
|
|
@ -492,13 +492,13 @@ const flavorsData = [
|
|||
{ name: 'mixed-fruit', title: 'mixed fruit', titleCn: '混合水果', shortName: 'MI' },
|
||||
{ name: 'mocha-ice', title: 'mocha ice', titleCn: '摩卡冰', shortName: 'MO' },
|
||||
{ name: 'morocco-mint', title: 'morocco mint', titleCn: '摩洛哥薄荷', shortName: 'MO' },
|
||||
{ name: 'morocco-mint-(thermal)', title: 'morocco mint (thermal)', titleCn: '摩洛哥薄荷(热感)', shortName: 'MO' },
|
||||
{ name: 'morocco-mint-(thermal)', title: 'morocco mint (thermal)', titleCn: '摩洛哥薄荷(热感)', shortName: 'MO' },
|
||||
{ name: 'mung-beans', title: 'mung beans', titleCn: '绿豆', shortName: 'MU' },
|
||||
{ name: 'nasty-tropic', title: 'nasty tropic', titleCn: '恶搞热带', shortName: 'NA' },
|
||||
{ name: 'nectarine-ice', title: 'nectarine ice', titleCn: '油桃冰', shortName: 'NE' },
|
||||
{ name: 'night-rider', title: 'night rider', titleCn: '夜骑', shortName: 'NI' },
|
||||
{ name: 'nirvana', title: 'nirvana', titleCn: '宁静蓝莓', shortName: 'NI' },
|
||||
{ name: 'north-american-style(root-beer)', title: 'north american style(root beer)', titleCn: '北美风格(根啤)', shortName: 'NO' },
|
||||
{ name: 'north-american-style(root-beer)', title: 'north american style(root beer)', titleCn: '北美风格(根啤)', shortName: 'NO' },
|
||||
{ name: 'northern-blue-razz', title: 'northern blue razz', titleCn: '北方蓝覆盆子', shortName: 'NO' },
|
||||
{ name: 'nutty-virginia', title: 'nutty virginia', titleCn: '坚果弗吉尼亚', shortName: 'NU' },
|
||||
{ name: 'orange', title: 'orange', titleCn: '橙子', shortName: 'OR' },
|
||||
|
|
@ -508,12 +508,12 @@ const flavorsData = [
|
|||
{ name: 'orange-mango-guava', title: 'orange mango guava', titleCn: '橙子芒果番石榴', shortName: 'OR' },
|
||||
{ name: 'orange-mango-pineapple-ice', title: 'orange mango pineapple ice', titleCn: '橙子芒果菠萝冰', shortName: 'OR' },
|
||||
{ name: 'orange-p', title: 'orange p', titleCn: '橙子 P', shortName: 'OR' },
|
||||
{ name: 'orange-p(fanta)', title: 'orange p(fanta)', titleCn: '橙子 P(芬达)', shortName: 'OR' },
|
||||
{ name: 'orange-p(fanta)', title: 'orange p(fanta)', titleCn: '橙子 P(芬达)', shortName: 'OR' },
|
||||
{ name: 'orange-spark', title: 'orange spark', titleCn: '橙色火花', shortName: 'OR' },
|
||||
{ name: 'orange-tangerine', title: 'orange tangerine', titleCn: '橙子柑橘', shortName: 'OR' },
|
||||
{ name: 'original', title: 'original', titleCn: '原味', shortName: 'OR' },
|
||||
{ name: 'packin-peach-berry', title: 'packin peach berry', titleCn: '装满桃浆果', shortName: 'PA' },
|
||||
{ name: 'packin-peach-berry-(popn-peach-berry)', title: 'packin peach berry (popn peach berry)', titleCn: '装满桃浆果(Pop’n 桃浆果)', shortName: 'PA' },
|
||||
{ name: 'packin-peach-berry-(popn-peach-berry)', title: 'packin peach berry (popn peach berry)', titleCn: '装满桃浆果(Pop’n 桃浆果)', shortName: 'PA' },
|
||||
{ name: 'papio', title: 'papio', titleCn: 'Papio', shortName: 'PA' },
|
||||
{ name: 'paradise', title: 'paradise', titleCn: '天堂', shortName: 'PA' },
|
||||
{ name: 'paradise-iced', title: 'paradise iced', titleCn: '天堂冰', shortName: 'PA' },
|
||||
|
|
@ -603,7 +603,7 @@ const flavorsData = [
|
|||
{ name: 'red-fruits', title: 'red fruits', titleCn: '红色水果', shortName: 'RE' },
|
||||
{ name: 'red-lightning', title: 'red lightning', titleCn: '红色闪电', shortName: 'RE' },
|
||||
{ name: 'red-line', title: 'red line', titleCn: '红线', shortName: 'RE' },
|
||||
{ name: 'red-line-(energy-drink)', title: 'red line (energy drink)', titleCn: '红线(能量饮料)', shortName: 'RE' },
|
||||
{ name: 'red-line-(energy-drink)', title: 'red line (energy drink)', titleCn: '红线(能量饮料)', shortName: 'RE' },
|
||||
{ name: 'red-magic', title: 'red magic', titleCn: '红魔', shortName: 'RE' },
|
||||
{ name: 'rich-tobacco', title: 'rich tobacco', titleCn: '浓烈烟草', shortName: 'RI' },
|
||||
{ name: 'root-beer', title: 'root beer', titleCn: '根啤', shortName: 'RO' },
|
||||
|
|
@ -625,8 +625,8 @@ const flavorsData = [
|
|||
{ name: 'sic-strawberry-iced', title: 'sic strawberry iced', titleCn: '意大利草莓冰', shortName: 'SI' },
|
||||
{ name: 'simply-spearmint', title: 'simply spearmint', titleCn: '清爽留兰香', shortName: 'SI' },
|
||||
{ name: 'skc', title: 'skc', titleCn: 'SKC', shortName: 'SK' },
|
||||
{ name: 'skc(skittles-candy)', title: 'skc(skittles candy)', titleCn: 'SKC(彩虹糖)', shortName: 'SK' },
|
||||
{ name: 'slammin-sts-(sour-snap)', title: 'slammin sts (sour snap)', titleCn: '热烈 STS(酸糖)', shortName: 'SL' },
|
||||
{ name: 'skc(skittles-candy)', title: 'skc(skittles candy)', titleCn: 'SKC(彩虹糖)', shortName: 'SK' },
|
||||
{ name: 'slammin-sts-(sour-snap)', title: 'slammin sts (sour snap)', titleCn: '热烈 STS(酸糖)', shortName: 'SL' },
|
||||
{ name: 'slammin-sts-iced', title: 'slammin sts iced', titleCn: '热烈 STS 冰', shortName: 'SL' },
|
||||
{ name: 'smooth', title: 'smooth', titleCn: '顺滑', shortName: 'SM' },
|
||||
{ name: 'smooth-mint', title: 'smooth mint', titleCn: '顺滑薄荷', shortName: 'SM' },
|
||||
|
|
@ -664,7 +664,7 @@ const flavorsData = [
|
|||
{ name: 'strawberry-jasmine-t', title: 'strawberry jasmine t', titleCn: '草莓茉莉茶', shortName: 'ST' },
|
||||
{ name: 'strawberry-jasmine-tea', title: 'strawberry jasmine tea', titleCn: '草莓茉莉茶', shortName: 'ST' },
|
||||
{ name: 'strawberry-kiwi', title: 'strawberry kiwi', titleCn: '草莓奇异果', shortName: 'ST' },
|
||||
{ name: 'strawberry-kiwi-(solid)', title: 'strawberry kiwi (solid)', titleCn: '草莓奇异果(固体)', shortName: 'ST' },
|
||||
{ name: 'strawberry-kiwi-(solid)', title: 'strawberry kiwi (solid)', titleCn: '草莓奇异果(固体)', shortName: 'ST' },
|
||||
{ name: 'strawberry-kiwi-banana-ice', title: 'strawberry kiwi banana ice', titleCn: '草莓奇异果香蕉冰', shortName: 'ST' },
|
||||
{ name: 'strawberry-kiwi-guava-ice', title: 'strawberry kiwi guava ice', titleCn: '草莓奇异果番石榴冰', shortName: 'ST' },
|
||||
{ name: 'strawberry-kiwi-ice', title: 'strawberry kiwi ice', titleCn: '草莓奇异果冰', shortName: 'ST' },
|
||||
|
|
@ -680,10 +680,10 @@ const flavorsData = [
|
|||
{ name: 'strawberry-watermelon', title: 'strawberry watermelon', titleCn: '草莓西瓜', shortName: 'ST' },
|
||||
{ name: 'strawberry-watermelon-ice', title: 'strawberry watermelon ice', titleCn: '草莓西瓜冰', shortName: 'ST' },
|
||||
{ name: 'strawmelon-peach', title: 'strawmelon peach', titleCn: '草莓桃', shortName: 'ST' },
|
||||
{ name: 'strawmelon-peach-(solid)', title: 'strawmelon peach (solid)', titleCn: '草莓桃(固体)', shortName: 'ST' },
|
||||
{ name: 'strawmelon-peach-(solid)', title: 'strawmelon peach (solid)', titleCn: '草莓桃(固体)', shortName: 'ST' },
|
||||
{ name: 'strawnana-orange', title: 'strawnana orange', titleCn: '草莓香蕉橙', shortName: 'ST' },
|
||||
{ name: 'summer-grape', title: 'summer grape', titleCn: '夏日葡萄', shortName: 'SU' },
|
||||
{ name: 'summer-grape-(thermal)', title: 'summer grape (thermal)', titleCn: '夏日葡萄(热感)', shortName: 'SU' },
|
||||
{ name: 'summer-grape-(thermal)', title: 'summer grape (thermal)', titleCn: '夏日葡萄(热感)', shortName: 'SU' },
|
||||
{ name: 'super-sour-blueberry-iced', title: 'super sour blueberry iced', titleCn: '超级酸蓝莓冰', shortName: 'SU' },
|
||||
{ name: 'super-spearmint', title: 'super spearmint', titleCn: '超级留兰香', shortName: 'SU' },
|
||||
{ name: 'super-spearmint-iced', title: 'super spearmint iced', titleCn: '超级留兰香冰', shortName: 'SU' },
|
||||
|
|
@ -704,7 +704,7 @@ const flavorsData = [
|
|||
{ name: 'tropical-orang-ice', title: 'tropical orang ice', titleCn: '热带橙冰', shortName: 'TR' },
|
||||
{ name: 'tropical-prism-blast', title: 'tropical prism blast', titleCn: '热带棱镜爆炸', shortName: 'TR' },
|
||||
{ name: 'tropical-splash', title: 'tropical splash', titleCn: '热带飞溅', shortName: 'TR' },
|
||||
{ name: 'tropical-splash-(solid)', title: 'tropical splash (solid)', titleCn: '热带飞溅(固体)', shortName: 'TR' },
|
||||
{ name: 'tropical-splash-(solid)', title: 'tropical splash (solid)', titleCn: '热带飞溅(固体)', shortName: 'TR' },
|
||||
{ name: 'tropical-storm-ice', title: 'tropical storm ice', titleCn: '热带风暴冰', shortName: 'TR' },
|
||||
{ name: 'tropical-summer', title: 'tropical summer', titleCn: '热带夏日', shortName: 'TR' },
|
||||
{ name: 'tropika', title: 'tropika', titleCn: '热带果', shortName: 'TR' },
|
||||
|
|
@ -728,7 +728,7 @@ const flavorsData = [
|
|||
{ name: 'watermelon-cantaloupe-honeydew-ice', title: 'watermelon cantaloupe honeydew ice', titleCn: '西瓜香瓜蜜瓜冰', shortName: 'WA' },
|
||||
{ name: 'watermelon-g', title: 'watermelon g', titleCn: '西瓜 G', shortName: 'WA' },
|
||||
{ name: 'watermelon-ice', title: 'watermelon ice', titleCn: '西瓜冰', shortName: 'WA' },
|
||||
{ name: 'watermelon-ice-(solid)', title: 'watermelon ice (solid)', titleCn: '西瓜冰(固体)', shortName: 'WA' },
|
||||
{ name: 'watermelon-ice-(solid)', title: 'watermelon ice (solid)', titleCn: '西瓜冰(固体)', shortName: 'WA' },
|
||||
{ name: 'watermelon-lime-ice', title: 'watermelon lime ice', titleCn: '西瓜青柠冰', shortName: 'WA' },
|
||||
{ name: 'watermelon-mango-tango', title: 'watermelon mango tango', titleCn: '西瓜芒果探戈', shortName: 'WA' },
|
||||
{ name: 'watermelona-cg', title: 'watermelona cg', titleCn: '西瓜 CG', shortName: 'WA' },
|
||||
|
|
@ -750,7 +750,7 @@ const flavorsData = [
|
|||
{ name: 'wild-strawberry-watermelon', title: 'wild strawberry watermelon', titleCn: '野生草莓西瓜', shortName: 'WI' },
|
||||
{ name: 'wild-white-grape', title: 'wild white grape', titleCn: '野生白葡萄', shortName: 'WI' },
|
||||
{ name: 'wild-white-grape-ice', title: 'wild white grape ice', titleCn: '野生白葡萄冰', shortName: 'WI' },
|
||||
{ name: 'wild-white-grape-iced', title: 'wild white grape iced', titleCn: '野生白葡萄冰(冷饮)', shortName: 'WI' },
|
||||
{ name: 'wild-white-grape-iced', title: 'wild white grape iced', titleCn: '野生白葡萄冰(冷饮)', shortName: 'WI' },
|
||||
{ name: 'winter-berry-ice', title: 'winter berry ice', titleCn: '冬季浆果冰', shortName: 'WI' },
|
||||
{ name: 'winter-green', title: 'winter green', titleCn: '冬青', shortName: 'WI' },
|
||||
{ name: 'wintergreen', title: 'wintergreen', titleCn: '冬青薄荷', shortName: 'WI' },
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export class UnifiedSearchParamsDTO<Where=Record<string, any>> {
|
|||
* 批量操作错误项
|
||||
*/
|
||||
export interface BatchErrorItem {
|
||||
// 错误项标识(可以是ID、邮箱等)
|
||||
// 错误项标识(可以是ID、邮箱等)
|
||||
identifier: string;
|
||||
// 错误信息
|
||||
error: string;
|
||||
|
|
@ -76,7 +76,7 @@ export interface BatchOperationResult {
|
|||
updated?: number;
|
||||
// 删除数量
|
||||
deleted?: number;
|
||||
// 跳过的数量(如数据已存在或无需处理)
|
||||
// 跳过的数量(如数据已存在或无需处理)
|
||||
skipped?: number;
|
||||
// 错误列表
|
||||
errors: BatchErrorItem[];
|
||||
|
|
@ -101,7 +101,7 @@ export class SyncOperationResult implements BatchOperationResult {
|
|||
* 批量操作错误项DTO
|
||||
*/
|
||||
export class BatchErrorItemDTO {
|
||||
@ApiProperty({ description: '错误项标识(如ID、邮箱等)', type: String })
|
||||
@ApiProperty({ description: '错误项标识(如ID、邮箱等)', type: String })
|
||||
@Rule(RuleType.string().required())
|
||||
identifier: string;
|
||||
|
||||
|
|
@ -164,7 +164,7 @@ export class SyncParamsDTO {
|
|||
@Rule(RuleType.string().optional())
|
||||
endDate?: string;
|
||||
|
||||
@ApiProperty({ description: '强制同步(忽略缓存)', type: Boolean, required: false, default: false })
|
||||
@ApiProperty({ description: '强制同步(忽略缓存)', type: Boolean, required: false, default: false })
|
||||
@Rule(RuleType.boolean().optional())
|
||||
force?: boolean = false;
|
||||
}
|
||||
|
|
@ -183,7 +183,7 @@ export class BatchQueryDTO {
|
|||
}
|
||||
|
||||
/**
|
||||
* 批量操作结果类(泛型支持)
|
||||
* 批量操作结果类(泛型支持)
|
||||
*/
|
||||
export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
|
||||
@ApiProperty({ description: '操作成功的数据列表', type: Array })
|
||||
|
|
@ -191,7 +191,7 @@ export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
|
|||
}
|
||||
|
||||
/**
|
||||
* 同步操作结果类(泛型支持)
|
||||
* 同步操作结果类(泛型支持)
|
||||
*/
|
||||
export class SyncOperationResultDTOGeneric<T> extends SyncOperationResultDTO {
|
||||
@ApiProperty({ description: '同步成功的数据列表', type: Array })
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Rule, RuleType } from '@midwayjs/validate';
|
|||
* 批量操作错误项
|
||||
*/
|
||||
export interface BatchErrorItem {
|
||||
// 错误项标识(可以是ID、邮箱等)
|
||||
// 错误项标识(可以是ID、邮箱等)
|
||||
identifier: string;
|
||||
// 错误信息
|
||||
error: string;
|
||||
|
|
@ -25,7 +25,7 @@ export interface BatchOperationResult {
|
|||
updated?: number;
|
||||
// 删除数量
|
||||
deleted?: number;
|
||||
// 跳过的数量(如数据已存在或无需处理)
|
||||
// 跳过的数量(如数据已存在或无需处理)
|
||||
skipped?: number;
|
||||
// 错误列表
|
||||
errors: BatchErrorItem[];
|
||||
|
|
@ -43,7 +43,7 @@ export interface SyncOperationResult extends BatchOperationResult {
|
|||
* 批量操作错误项DTO
|
||||
*/
|
||||
export class BatchErrorItemDTO {
|
||||
@ApiProperty({ description: '错误项标识(如ID、邮箱等)', type: String })
|
||||
@ApiProperty({ description: '错误项标识(如ID、邮箱等)', type: String })
|
||||
@Rule(RuleType.string().required())
|
||||
identifier: string;
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ export class BatchDeleteDTO {
|
|||
}
|
||||
|
||||
/**
|
||||
* 批量操作请求DTO(包含增删改)
|
||||
* 批量操作请求DTO(包含增删改)
|
||||
*/
|
||||
export class BatchOperationDTO<T = any> {
|
||||
@ApiProperty({ description: '要创建的数据列表', type: Array, required: false })
|
||||
|
|
@ -175,7 +175,7 @@ export class SyncParamsDTO {
|
|||
@Rule(RuleType.string().optional())
|
||||
endDate?: string;
|
||||
|
||||
@ApiProperty({ description: '强制同步(忽略缓存)', type: Boolean, required: false, default: false })
|
||||
@ApiProperty({ description: '强制同步(忽略缓存)', type: Boolean, required: false, default: false })
|
||||
@Rule(RuleType.boolean().optional())
|
||||
force?: boolean = false;
|
||||
}
|
||||
|
|
@ -194,7 +194,7 @@ export class BatchQueryDTO {
|
|||
}
|
||||
|
||||
/**
|
||||
* 批量操作结果类(泛型支持)
|
||||
* 批量操作结果类(泛型支持)
|
||||
*/
|
||||
export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
|
||||
@ApiProperty({ description: '操作成功的数据列表', type: Array })
|
||||
|
|
@ -202,7 +202,7 @@ export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
|
|||
}
|
||||
|
||||
/**
|
||||
* 同步操作结果类(泛型支持)
|
||||
* 同步操作结果类(泛型支持)
|
||||
*/
|
||||
export class SyncOperationResultDTOGeneric<T> extends SyncOperationResultDTO {
|
||||
@ApiProperty({ description: '同步成功的数据列表', type: Array })
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { ApiProperty } from '@midwayjs/swagger';
|
|||
import { UnifiedSearchParamsDTO } from './api.dto';
|
||||
import { Customer } from '../entity/customer.entity';
|
||||
|
||||
// 客户基本信息DTO(用于响应)
|
||||
// 客户基本信息DTO(用于响应)
|
||||
export class CustomerDTO extends Customer{
|
||||
@ApiProperty({ description: '客户ID' })
|
||||
id: number;
|
||||
|
|
@ -163,11 +163,11 @@ export class UpdateCustomerDTO {
|
|||
tags?: string[];
|
||||
}
|
||||
|
||||
// 查询单个客户响应DTO(继承基本信息)
|
||||
// 查询单个客户响应DTO(继承基本信息)
|
||||
export class GetCustomerDTO extends CustomerDTO {
|
||||
// 可以添加额外的详细信息字段
|
||||
}
|
||||
// 客户统计信息DTO(包含订单统计)
|
||||
// 客户统计信息DTO(包含订单统计)
|
||||
export class CustomerStatisticDTO extends CustomerDTO {
|
||||
@ApiProperty({ description: '创建日期' })
|
||||
date_created: Date;
|
||||
|
|
@ -209,7 +209,7 @@ export class CustomerStatisticWhereDTO {
|
|||
customerId?: number;
|
||||
}
|
||||
|
||||
// 客户统计查询参数DTO(继承通用查询参数)
|
||||
// 客户统计查询参数DTO(继承通用查询参数)
|
||||
export type CustomerStatisticQueryParamsDTO = UnifiedSearchParamsDTO<CustomerStatisticWhereDTO>;
|
||||
|
||||
// 客户统计列表响应DTO
|
||||
|
|
@ -259,7 +259,7 @@ export class BatchDeleteCustomerDTO {
|
|||
|
||||
// ====================== 查询操作 ======================
|
||||
|
||||
// 客户查询条件DTO(用于UnifiedSearchParamsDTO的where参数)
|
||||
// 客户查询条件DTO(用于UnifiedSearchParamsDTO的where参数)
|
||||
export class CustomerWhereDTO {
|
||||
@ApiProperty({ description: '邮箱筛选', required: false })
|
||||
email?: string;
|
||||
|
|
@ -284,10 +284,10 @@ export class CustomerWhereDTO {
|
|||
role?: string;
|
||||
}
|
||||
|
||||
// 客户查询参数DTO(继承通用查询参数)
|
||||
// 客户查询参数DTO(继承通用查询参数)
|
||||
export type CustomerQueryParamsDTO = UnifiedSearchParamsDTO<CustomerWhereDTO>;
|
||||
|
||||
// 客户列表响应DTO(参考site-api.dto.ts中的分页格式)
|
||||
// 客户列表响应DTO(参考site-api.dto.ts中的分页格式)
|
||||
export class CustomerListResponseDTO {
|
||||
@ApiProperty({ description: '客户列表', type: [CustomerDTO] })
|
||||
items: CustomerDTO[];
|
||||
|
|
@ -359,6 +359,6 @@ export class SyncCustomersDTO {
|
|||
@ApiProperty({ description: '站点ID' })
|
||||
siteId: number;
|
||||
|
||||
@ApiProperty({ description: '查询参数(支持where和orderBy)', type: UnifiedSearchParamsDTO, required: false })
|
||||
@ApiProperty({ description: '查询参数(支持where和orderBy)', type: UnifiedSearchParamsDTO, required: false })
|
||||
params?: UnifiedSearchParamsDTO<CustomerWhereDTO>;
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ export class ShopyyAllProductQuery {
|
|||
id?: string;
|
||||
/** 商品标题,支持模糊查询 */
|
||||
title?: string;
|
||||
/** 商品状态,例如:上架、下架、删除等(具体值参考 Shopyy 接口文档) */
|
||||
/** 商品状态,例如:上架、下架、删除等(具体值参考 Shopyy 接口文档) */
|
||||
status?: string;
|
||||
/** 商品SKU编码,库存保有单位,精确或模糊匹配 */
|
||||
sku?: string;
|
||||
|
|
@ -34,21 +34,21 @@ export class ShopyyAllProductQuery {
|
|||
variant_price_min?: string;
|
||||
/** 变体价格最大值,筛选变体价格小于等于该值的商品 */
|
||||
variant_price_max?: string;
|
||||
/** 变体划线价(原价)最小值,筛选变体划线价大于等于该值的商品 */
|
||||
/** 变体划线价(原价)最小值,筛选变体划线价大于等于该值的商品 */
|
||||
variant_compare_at_price_min?: string;
|
||||
/** 变体划线价(原价)最大值,筛选变体划线价小于等于该值的商品 */
|
||||
/** 变体划线价(原价)最大值,筛选变体划线价小于等于该值的商品 */
|
||||
variant_compare_at_price_max?: string;
|
||||
/** 变体重量最小值,筛选变体重量大于等于该值的商品(单位参考接口文档) */
|
||||
/** 变体重量最小值,筛选变体重量大于等于该值的商品(单位参考接口文档) */
|
||||
variant_weight_min?: string;
|
||||
/** 变体重量最大值,筛选变体重量小于等于该值的商品(单位参考接口文档) */
|
||||
/** 变体重量最大值,筛选变体重量小于等于该值的商品(单位参考接口文档) */
|
||||
variant_weight_max?: string;
|
||||
/** 商品创建时间最小值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||
/** 商品创建时间最小值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||
created_at_min?: string;
|
||||
/** 商品创建时间最大值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||
/** 商品创建时间最大值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||
created_at_max?: string;
|
||||
/** 商品更新时间最小值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||
/** 商品更新时间最小值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||
updated_at_min?: string;
|
||||
/** 商品更新时间最大值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||
/** 商品更新时间最大值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||
updated_at_max?: string;
|
||||
}
|
||||
// 产品类型
|
||||
|
|
@ -133,9 +133,9 @@ export interface ShopyyVariant {
|
|||
//
|
||||
// 订单查询参数类型
|
||||
export interface ShopyyOrderQuery {
|
||||
// 订单ID集合 多个ID用','联接 例:1,2,3
|
||||
// 订单ID集合 多个ID用','联接 例:1,2,3
|
||||
ids?: string;
|
||||
// 订单状态 100 未完成;110 待处理;180 已完成(确认收货); 190 取消;
|
||||
// 订单状态 100 未完成;110 待处理;180 已完成(确认收货); 190 取消;
|
||||
status?: string;
|
||||
// 物流状态 300 未发货;310 部分发货;320 已发货;330(确认收货)
|
||||
fulfillment_status?: string;
|
||||
|
|
@ -159,9 +159,9 @@ export interface ShopyyOrderQuery {
|
|||
page?: string;
|
||||
// 每页条数
|
||||
limit?: string;
|
||||
// 排序字段(默认id) id=订单ID updated_at=最后更新时间 pay_at=支付时间
|
||||
// 排序字段(默认id) id=订单ID updated_at=最后更新时间 pay_at=支付时间
|
||||
order_field?: string;
|
||||
// 排序方式(默认desc) desc=降序 asc=升序
|
||||
// 排序方式(默认desc) desc=降序 asc=升序
|
||||
order_by?: string;
|
||||
// 订单列表类型
|
||||
group?: string;
|
||||
|
|
@ -513,7 +513,7 @@ export class ShopyyFulfillmentDTO {
|
|||
"tracking_number": string;
|
||||
"courier_code": number;
|
||||
"note": string;
|
||||
"mode": "replace" | 'cover' | null// 模式 replace(替换) cover (覆盖) 空(新增)
|
||||
"mode": "replace" | 'cover' | null// 模式 replace(替换) cover (覆盖) 空(新增)
|
||||
}
|
||||
// https://www.apizza.net/project/e114fb8e628e0f604379f5b26f0d8330/browse
|
||||
export class ShopyPartFulfillmentDTO {
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ export class UnifiedProductAttributeDTO {
|
|||
@ApiProperty({ description: '属性选项', type: [String] })
|
||||
options: string[];
|
||||
|
||||
@ApiProperty({ description: '变体属性值(单个值)', required: false })
|
||||
@ApiProperty({ description: '变体属性值(单个值)', required: false })
|
||||
option?: string;
|
||||
// 这个是属性的父级字典项
|
||||
dict?: Dict;
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ export class QuerySiteDTO {
|
|||
@Rule(RuleType.boolean().optional())
|
||||
isDisabled?: boolean;
|
||||
|
||||
@ApiProperty({ description: '站点ID列表(逗号分隔)', required: false })
|
||||
@ApiProperty({ description: '站点ID列表(逗号分隔)', required: false })
|
||||
@Rule(RuleType.string().optional())
|
||||
ids?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ export interface WooProduct {
|
|||
id: number;
|
||||
// 创建时间
|
||||
date_created: string;
|
||||
// 创建时间(GMT)
|
||||
// 创建时间(GMT)
|
||||
date_created_gmt: string;
|
||||
// 更新时间
|
||||
date_modified: string;
|
||||
// 更新时间(GMT)
|
||||
// 更新时间(GMT)
|
||||
date_modified_gmt: string;
|
||||
// 产品类型 simple grouped external variable
|
||||
type: string;
|
||||
|
|
@ -20,7 +20,7 @@ export interface WooProduct {
|
|||
status: string;
|
||||
// 是否为特色产品
|
||||
featured: boolean;
|
||||
// 目录可见性选项:visible, catalog, search and hidden. Default is visible.
|
||||
// 目录可见性选项:visible, catalog, search and hidden. Default is visible.
|
||||
catalog_visibility: string;
|
||||
|
||||
// 常规价格
|
||||
|
|
@ -130,11 +130,11 @@ export interface WooVariation {
|
|||
id: number;
|
||||
// 创建时间
|
||||
date_created: string;
|
||||
// 创建时间(GMT)
|
||||
// 创建时间(GMT)
|
||||
date_created_gmt: string;
|
||||
// 更新时间
|
||||
date_modified: string;
|
||||
// 更新时间(GMT)
|
||||
// 更新时间(GMT)
|
||||
date_modified_gmt: string;
|
||||
// 变体描述
|
||||
description: string;
|
||||
|
|
@ -150,11 +150,11 @@ export interface WooVariation {
|
|||
price_html?: string;
|
||||
// 促销开始日期
|
||||
date_on_sale_from?: string;
|
||||
// 促销开始日期(GMT)
|
||||
// 促销开始日期(GMT)
|
||||
date_on_sale_from_gmt?: string;
|
||||
// 促销结束日期
|
||||
date_on_sale_to?: string;
|
||||
// 促销结束日期(GMT)
|
||||
// 促销结束日期(GMT)
|
||||
date_on_sale_to_gmt?: string;
|
||||
// 是否在促销中
|
||||
on_sale: boolean;
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export enum OrderStatus {
|
|||
REFUNDED = 'refunded', // 已退款
|
||||
FAILED = 'failed', // 失败订单
|
||||
DRAFT = 'draft', // 草稿
|
||||
AUTO_DRAFT = 'auto-draft', // 自动草稿(TODO:不知道为什么出现)
|
||||
AUTO_DRAFT = 'auto-draft', // 自动草稿(TODO:不知道为什么出现)
|
||||
|
||||
// TRASH = 'trash',
|
||||
// refund 也就是退款相关的状态
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export interface IPlatformService {
|
|||
getOrder(siteId: number, orderId: string): Promise<any>;
|
||||
|
||||
/**
|
||||
* 获取订阅列表(如果平台支持)
|
||||
* 获取订阅列表(如果平台支持)
|
||||
* @param siteId 站点ID
|
||||
* @returns 订阅列表数据
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export class CustomerService {
|
|||
}
|
||||
|
||||
if (typeof dateValue === 'number') {
|
||||
// 处理Unix时间戳(秒或毫秒)
|
||||
// 处理Unix时间戳(秒或毫秒)
|
||||
return new Date(dateValue > 9999999999 ? dateValue : dateValue * 1000);
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ export class CustomerService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 创建或更新客户(upsert)
|
||||
* 创建或更新客户(upsert)
|
||||
* 如果客户存在则更新,不存在则创建
|
||||
*/
|
||||
async upsertCustomer(
|
||||
|
|
@ -157,24 +157,24 @@ export class CustomerService {
|
|||
|
||||
/**
|
||||
* 从站点同步客户数据
|
||||
* 第一步:调用adapter获取站点客户数据
|
||||
* 第二步:通过upsertManyCustomers保存这些客户
|
||||
* 第一步:调用adapter获取站点客户数据
|
||||
* 第二步:通过upsertManyCustomers保存这些客户
|
||||
*/
|
||||
async syncCustomersFromSite(
|
||||
siteId: number,
|
||||
params?: UnifiedSearchParamsDTO
|
||||
): Promise<SyncOperationResult> {
|
||||
try {
|
||||
// 第一步:获取适配器并从站点获取客户数据
|
||||
// 第一步:获取适配器并从站点获取客户数据
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
const siteCustomers = await adapter.getAllCustomers(params || {});
|
||||
|
||||
// 第二步:将站点客户数据转换为客户实体数据
|
||||
// 第二步:将站点客户数据转换为客户实体数据
|
||||
const customersData = siteCustomers.map(siteCustomer => {
|
||||
return this.mapSiteCustomerToCustomer(siteCustomer, siteId);
|
||||
})
|
||||
|
||||
// 第三步:批量upsert客户数据
|
||||
// 第三步:批量upsert客户数据
|
||||
const upsertResult = await this.upsertManyCustomers(customersData);
|
||||
return {
|
||||
total: siteCustomers.length,
|
||||
|
|
@ -192,7 +192,7 @@ export class CustomerService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 获取客户统计列表(包含订单统计信息)
|
||||
* 获取客户统计列表(包含订单统计信息)
|
||||
* 支持分页、搜索和排序功能
|
||||
* 使用原生SQL查询实现复杂的统计逻辑
|
||||
*/
|
||||
|
|
@ -363,7 +363,7 @@ export class CustomerService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 获取纯粹的客户列表(不包含订单统计信息)
|
||||
* 获取纯粹的客户列表(不包含订单统计信息)
|
||||
* 支持基本的分页、搜索和排序功能
|
||||
* 使用TypeORM查询构建器实现
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ export class DictService {
|
|||
}
|
||||
|
||||
// 更新或创建字典项 (Upsert)
|
||||
// 如果字典项已存在(根据 name 和 dictId 判断),则更新;否则创建新的
|
||||
// 如果字典项已存在(根据 name 和 dictId 判断),则更新;否则创建新的
|
||||
async upsertDictItem(dictId: number, itemData: {
|
||||
name: string;
|
||||
title: string;
|
||||
|
|
@ -252,7 +252,7 @@ export class DictService {
|
|||
// 格式化 name
|
||||
const formattedName = this.formatName(itemData.name);
|
||||
|
||||
// 查找是否已存在该字典项(根据 name 和 dictId)
|
||||
// 查找是否已存在该字典项(根据 name 和 dictId)
|
||||
const existingItem = await this.dictItemModel.findOne({
|
||||
where: {
|
||||
name: formattedName,
|
||||
|
|
|
|||
|
|
@ -479,7 +479,7 @@ export class OrderService {
|
|||
// 如果不能更新 ERP 状态,则保留原有的 orderStatus
|
||||
entity.orderStatus = existingOrder.orderStatus;
|
||||
}
|
||||
// 更新订单数据(包括 shipping、billing 等字段)
|
||||
// 更新订单数据(包括 shipping、billing 等字段)
|
||||
await this.orderModel.update(existingOrder.id, entity);
|
||||
entity.id = existingOrder.id;
|
||||
return entity;
|
||||
|
|
@ -2567,8 +2567,8 @@ export class OrderService {
|
|||
* 导出数据为CSV格式
|
||||
* @param {any[]} data 数据数组
|
||||
* @param {Object} options 配置选项
|
||||
* @param {string} [options.type='string'] 输出类型:'string' | 'buffer'
|
||||
* @param {string} [options.fileName] 文件名(仅当需要写入文件时使用)
|
||||
* @param {string} [options.type='string'] 输出类型:'string' | 'buffer'
|
||||
* @param {string} [options.fileName] 文件名(仅当需要写入文件时使用)
|
||||
* @param {boolean} [options.writeFile=false] 是否写入文件
|
||||
* @returns {string|Buffer} 根据type返回字符串或Buffer
|
||||
*/
|
||||
|
|
@ -2617,7 +2617,7 @@ async exportToCsv(data: any[], options: { type?: 'string' | 'buffer'; fileName?:
|
|||
// 获取当前用户目录
|
||||
const userHomeDir = os.homedir();
|
||||
|
||||
// 构建目标路径(下载目录)
|
||||
// 构建目标路径(下载目录)
|
||||
const downloadsDir = path.join(userHomeDir, 'Downloads');
|
||||
|
||||
// 确保下载目录存在
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ export class ProductService {
|
|||
.leftJoinAndSelect('product.attributes', 'attribute')
|
||||
.leftJoinAndSelect('attribute.dict', 'dict')
|
||||
.leftJoinAndSelect('product.category', 'category');
|
||||
// 处理分页参数(支持新旧两种格式)
|
||||
// 处理分页参数(支持新旧两种格式)
|
||||
const page = query.page || 1;
|
||||
const pageSize = query.per_page || 10;
|
||||
|
||||
|
|
@ -393,7 +393,7 @@ export class ProductService {
|
|||
qb.andWhere('product.updatedAt <= :whereUpdatedAtEnd', { whereUpdatedAtEnd: new Date(query.where.updatedAtEnd) });
|
||||
}
|
||||
|
||||
// 品牌过滤(向后兼容)
|
||||
// 品牌过滤(向后兼容)
|
||||
if (brandId) {
|
||||
qb.andWhere(qb => {
|
||||
const subQuery = qb
|
||||
|
|
@ -423,7 +423,7 @@ export class ProductService {
|
|||
});
|
||||
}
|
||||
|
||||
// 分类过滤(向后兼容)
|
||||
// 分类过滤(向后兼容)
|
||||
if (categoryId) {
|
||||
qb.andWhere('product.categoryId = :categoryId', { categoryId });
|
||||
}
|
||||
|
|
@ -443,7 +443,7 @@ export class ProductService {
|
|||
qb.andWhere('product.categoryId IN (:...whereCategoryIds)', { whereCategoryIds: query.where.categoryIds });
|
||||
}
|
||||
|
||||
// 处理排序(支持新旧两种格式)
|
||||
// 处理排序(支持新旧两种格式)
|
||||
if (orderBy) {
|
||||
if (typeof orderBy === 'string') {
|
||||
// 如果orderBy是字符串,尝试解析JSON
|
||||
|
|
@ -1765,7 +1765,7 @@ export class ProductService {
|
|||
}
|
||||
|
||||
|
||||
// 根据ID获取产品详情(包含站点SKU)
|
||||
// 根据ID获取产品详情(包含站点SKU)
|
||||
async getProductById(id: number): Promise<Product> {
|
||||
const product = await this.productModel.findOne({
|
||||
where: { id },
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export class ShopyyService {
|
|||
* @returns 完整URL
|
||||
*/
|
||||
private buildURL(baseUrl: string, endpoint: string): string {
|
||||
// ShopYY API URL格式:https://{shop}.shopyy.com/openapi/{version}/{endpoint}
|
||||
// ShopYY API URL格式:https://{shop}.shopyy.com/openapi/{version}/{endpoint}
|
||||
const base = baseUrl.replace(/\/$/, '');
|
||||
const end = endpoint.replace(/^\//, '');
|
||||
return `${base}/${end}`;
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ export class SiteApiService {
|
|||
const result = await this.upsertProduct(siteId, product);
|
||||
// 判断是创建还是更新
|
||||
if (result && result.id) {
|
||||
// 简单判断:如果产品原本没有ID而现在有了,说明是创建的
|
||||
// 简单判断:如果产品原本没有ID而现在有了,说明是创建的
|
||||
if (!product.id || !product.id.toString().trim()) {
|
||||
results.created.push(result);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ export class WPService implements IPlatformService {
|
|||
}
|
||||
|
||||
|
||||
// 导出 WooCommerce 产品为特殊CSV(平台特性)
|
||||
// 导出 WooCommerce 产品为特殊CSV(平台特性)
|
||||
async exportProductsCsvSpecial(site: any, page: number = 1, pageSize: number = 100): Promise<string> {
|
||||
const list = await this.getProducts(site, { page, per_page: pageSize });
|
||||
const header = ['id','name','type','status','sku','regular_price','sale_price','stock_status','stock_quantity'];
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@
|
|||
### 2. 根据站点SKU查询产品
|
||||
**接口**: `GET /product/site-sku/:siteSku`
|
||||
**功能**: 根据站点SKU代码查询对应的产品信息
|
||||
**返回**: 完整的产品对象(包含站点SKU、分类、属性等关联数据)
|
||||
**返回**: 完整的产品对象(包含站点SKU、分类、属性等关联数据)
|
||||
|
||||
### 3. 根据产品ID获取产品详情
|
||||
**接口**: `GET /product/:id`
|
||||
**功能**: 获取产品的完整详情信息
|
||||
**返回**: 完整的产品对象(包含站点SKU、分类、属性、组成等关联数据)
|
||||
**返回**: 完整的产品对象(包含站点SKU、分类、属性、组成等关联数据)
|
||||
|
||||
### 4. 现有接口的增强
|
||||
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
|
||||
1. **findProductsByName(name: string)**: 现在包含站点SKU数据
|
||||
2. **findProductBySku(sku: string)**: 现在包含站点SKU数据
|
||||
3. **getProductList**: 已经包含站点SKU数据(无需更改)
|
||||
3. **getProductList**: 已经包含站点SKU数据(无需更改)
|
||||
|
||||
## 使用示例
|
||||
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
```javascript
|
||||
// GET /product/site-sku/SITE-SKU-001
|
||||
// 返回完整的产品对象,包含:
|
||||
// - 基本信息(SKU、名称、价格等)
|
||||
// - 基本信息(SKU、名称、价格等)
|
||||
// - 分类信息
|
||||
// - 属性信息
|
||||
// - 站点SKU列表
|
||||
|
|
@ -92,14 +92,14 @@
|
|||
|
||||
## 数据库查询优化
|
||||
|
||||
所有新增和更新的方法都使用了TypeORM的关联查询,确保:
|
||||
所有新增和更新的方法都使用了TypeORM的关联查询,确保:
|
||||
- 一次查询获取所有需要的数据
|
||||
- 避免N+1查询问题
|
||||
- 包含必要的关联关系(分类、属性、站点SKU、组成)
|
||||
- 包含必要的关联关系(分类、属性、站点SKU、组成)
|
||||
|
||||
## 错误处理
|
||||
|
||||
所有方法都包含适当的错误处理:
|
||||
所有方法都包含适当的错误处理:
|
||||
- 产品不存在时抛出明确的错误信息
|
||||
- 站点SKU不存在时抛出明确的错误信息
|
||||
- 控制器层统一处理错误并返回适当的HTTP响应
|
||||
Loading…
Reference in New Issue