feat(产品): 添加批量更新产品功能并引入eta模板引擎
添加批量更新产品接口,支持简单字段批量更新和复杂字段逐个更新 引入eta模板引擎依赖并更新eslint配置忽略scripts目录
This commit is contained in:
parent
4bbfa0cc2d
commit
4bb0988034
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,5 @@ scripts/replace_punctuation.js
|
||||||
scripts/test_db_count.d.ts
|
scripts/test_db_count.d.ts
|
||||||
scripts/test_db_count.js
|
scripts/test_db_count.js
|
||||||
scripts/test_db_count.ts
|
scripts/test_db_count.ts
|
||||||
|
ai/test.html
|
||||||
|
ai/wc-product-export-2-12-2025-1764686773307.csv
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"csv-parse": "^6.1.0",
|
"csv-parse": "^6.1.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
"eta": "^4.4.1",
|
||||||
"i18n-iso-countries": "^7.14.0",
|
"i18n-iso-countries": "^7.14.0",
|
||||||
"mysql2": "^3.15.3",
|
"mysql2": "^3.15.3",
|
||||||
"nodemailer": "^7.0.5",
|
"nodemailer": "^7.0.5",
|
||||||
|
|
@ -2278,6 +2279,18 @@
|
||||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/eta": {
|
||||||
|
"version": "4.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eta/-/eta-4.4.1.tgz",
|
||||||
|
"integrity": "sha512-4o6fYxhRmFmO9SJcU9PxBLYPGapvJ/Qha0ZE+Y6UE9QIUd0Wk1qaLISQ6J1bM7nOcWHhs1YmY3mfrfwkJRBTWQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/bgub/eta?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/event-target-shim": {
|
"node_modules/event-target-shim": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"csv-parse": "^6.1.0",
|
"csv-parse": "^6.1.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
"eta": "^4.4.1",
|
||||||
"i18n-iso-countries": "^7.14.0",
|
"i18n-iso-countries": "^7.14.0",
|
||||||
"mysql2": "^3.15.3",
|
"mysql2": "^3.15.3",
|
||||||
"nodemailer": "^7.0.5",
|
"nodemailer": "^7.0.5",
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import {
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
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 { CreateProductDTO, QueryProductDTO, UpdateProductDTO, SetProductComponentsDTO } from '../dto/product.dto';
|
import { CreateProductDTO, QueryProductDTO, UpdateProductDTO, SetProductComponentsDTO, BatchUpdateProductDTO } from '../dto/product.dto';
|
||||||
import { ApiOkResponse } from '@midwayjs/swagger';
|
import { ApiOkResponse } from '@midwayjs/swagger';
|
||||||
import { BooleanRes, ProductListRes, ProductRes, ProductsRes } from '../dto/reponse.dto';
|
import { BooleanRes, ProductListRes, ProductRes, ProductsRes } from '../dto/reponse.dto';
|
||||||
import { ContentType, Files } from '@midwayjs/core';
|
import { ContentType, Files } from '@midwayjs/core';
|
||||||
|
|
@ -145,6 +145,17 @@ export class ProductController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
|
@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: ProductRes })
|
@ApiOkResponse({ type: ProductRes })
|
||||||
@Put('updateNameCn/:id/:nameCn')
|
@Put('updateNameCn/:id/:nameCn')
|
||||||
async updatenameCn(@Param('id') id: number, @Param('nameCn') nameCn: string) {
|
async updatenameCn(@Param('id') id: number, @Param('nameCn') nameCn: string) {
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,52 @@ export class UpdateProductDTO {
|
||||||
type?: string;
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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({ 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: '价格', 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 用于创建分类属性绑定
|
* DTO 用于创建分类属性绑定
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { PaginationParams } from '../interface';
|
||||||
import {
|
import {
|
||||||
CreateProductDTO,
|
CreateProductDTO,
|
||||||
UpdateProductDTO,
|
UpdateProductDTO,
|
||||||
|
BatchUpdateProductDTO,
|
||||||
} from '../dto/product.dto';
|
} from '../dto/product.dto';
|
||||||
import {
|
import {
|
||||||
BrandPaginatedResponse,
|
BrandPaginatedResponse,
|
||||||
|
|
@ -535,6 +536,49 @@ export class ProductService {
|
||||||
return saved;
|
return saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async batchUpdateProduct(
|
||||||
|
batchUpdateProductDTO: BatchUpdateProductDTO
|
||||||
|
): Promise<boolean> {
|
||||||
|
const { ids, ...updateData } = batchUpdateProductDTO;
|
||||||
|
if (!ids || ids.length === 0) {
|
||||||
|
throw new Error('未选择任何产品');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 updateData 中是否有复杂字段 (attributes, categoryId, type, sku)
|
||||||
|
// 如果包含复杂字段,需要复用 updateProduct 的逻辑
|
||||||
|
const hasComplexFields =
|
||||||
|
updateData.attributes !== undefined ||
|
||||||
|
updateData.categoryId !== undefined ||
|
||||||
|
updateData.type !== undefined ||
|
||||||
|
updateData.sku !== undefined;
|
||||||
|
|
||||||
|
if (hasComplexFields) {
|
||||||
|
// 循环调用 updateProduct
|
||||||
|
for (const id of ids) {
|
||||||
|
const updateDTO = new UpdateProductDTO();
|
||||||
|
// 复制属性
|
||||||
|
Object.assign(updateDTO, updateData);
|
||||||
|
await this.updateProduct(id, updateDTO);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 简单字段,直接批量更新以提高性能
|
||||||
|
// UpdateProductDTO 里的简单字段: name, nameCn, description, price, promotionPrice
|
||||||
|
|
||||||
|
const simpleUpdate: any = {};
|
||||||
|
if (updateData.name !== undefined) simpleUpdate.name = updateData.name;
|
||||||
|
if (updateData.nameCn !== undefined) simpleUpdate.nameCn = updateData.nameCn;
|
||||||
|
if (updateData.description !== undefined) simpleUpdate.description = updateData.description;
|
||||||
|
if (updateData.price !== undefined) simpleUpdate.price = updateData.price;
|
||||||
|
if (updateData.promotionPrice !== undefined) simpleUpdate.promotionPrice = updateData.promotionPrice;
|
||||||
|
|
||||||
|
if (Object.keys(simpleUpdate).length > 0) {
|
||||||
|
await this.productModel.update({ id: In(ids) }, simpleUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// 获取产品的库存组成列表(表关联版本)
|
// 获取产品的库存组成列表(表关联版本)
|
||||||
async getProductComponents(productId: number): Promise<any[]> {
|
async getProductComponents(productId: number): Promise<any[]> {
|
||||||
// 条件判断:确保产品存在
|
// 条件判断:确保产品存在
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue