forked from yoone/API
feat(产品): 添加产品属性过滤和分组功能
- 在 ProductWhereFilter 接口中添加 attributes 字段用于属性过滤 - 新增 getAllProducts 方法支持按品牌过滤产品 - 新增 getProductsGroupedByAttribute 方法实现按属性分组产品 - 在查询构建器中添加属性过滤逻辑
This commit is contained in:
parent
fff62d6864
commit
bdac4860df
|
|
@ -750,4 +750,31 @@ export class ProductController {
|
|||
return errorResponse(error?.message || error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有产品,支持按品牌过滤
|
||||
@ApiOkResponse({ description: '获取所有产品', type: ProductListRes })
|
||||
@Get('/all')
|
||||
async getAllProducts(@Query('brand') brand?: string) {
|
||||
try {
|
||||
const data = await this.productService.getAllProducts(brand);
|
||||
return successResponse(data);
|
||||
} catch (error) {
|
||||
return errorResponse(error?.message || error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取按属性分组的产品,默认按强度划分
|
||||
@ApiOkResponse({ description: '获取按属性分组的产品' })
|
||||
@Get('/grouped')
|
||||
async getGroupedProducts(
|
||||
@Query('brand') brand?: string,
|
||||
@Query('attribute') attribute: string = 'strength'
|
||||
) {
|
||||
try {
|
||||
const data = await this.productService.getProductsGroupedByAttribute(brand, attribute);
|
||||
return successResponse(data);
|
||||
} catch (error) {
|
||||
return errorResponse(error?.message || error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -311,6 +311,8 @@ export interface ProductWhereFilter {
|
|||
updatedAtStart?: string;
|
||||
// 更新时间范围结束
|
||||
updatedAtEnd?: string;
|
||||
// TODO 使用 attributes 过滤
|
||||
attributes?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -393,6 +393,41 @@ export class ProductService {
|
|||
qb.andWhere('product.updatedAt <= :whereUpdatedAtEnd', { whereUpdatedAtEnd: new Date(query.where.updatedAtEnd) });
|
||||
}
|
||||
|
||||
// 处理属性过滤
|
||||
const attributeFilters = query.where?.attributes || {};
|
||||
Object.entries(attributeFilters).forEach(([attributeName, value], index) => {
|
||||
if (value === 'hasValue') {
|
||||
// 如果值为'hasValue',则过滤出具有该属性的产品
|
||||
qb.andWhere(qb => {
|
||||
const subQuery = qb
|
||||
.subQuery()
|
||||
.select('product_attributes_dict_item.productId')
|
||||
.from('product_attributes_dict_item', 'product_attributes_dict_item')
|
||||
.innerJoin('dict_item', 'dict_item', 'product_attributes_dict_item.dictItemId = dict_item.id')
|
||||
.innerJoin('dict', 'dict', 'dict_item.dictId = dict.id')
|
||||
.where('dict.name = :attributeName', {
|
||||
attributeName,
|
||||
})
|
||||
.getQuery();
|
||||
return 'product.id IN ' + subQuery;
|
||||
});
|
||||
} else if (typeof value === 'number' || !isNaN(Number(value))) {
|
||||
// 如果值是数字,则过滤出该属性等于该值的产品
|
||||
const attributeValueId = Number(value);
|
||||
qb.andWhere(qb => {
|
||||
const subQuery = qb
|
||||
.subQuery()
|
||||
.select('product_attributes_dict_item.productId')
|
||||
.from('product_attributes_dict_item', 'product_attributes_dict_item')
|
||||
.where('product_attributes_dict_item.dictItemId = :attributeValueId', {
|
||||
attributeValueId,
|
||||
})
|
||||
.getQuery();
|
||||
return 'product.id IN ' + subQuery;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 品牌过滤(向后兼容)
|
||||
if (brandId) {
|
||||
qb.andWhere(qb => {
|
||||
|
|
@ -2076,4 +2111,111 @@ export class ProductService {
|
|||
|
||||
return unifiedProduct;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有产品,支持按品牌过滤
|
||||
* @param brand 品牌名称
|
||||
* @returns 所有符合条件的产品
|
||||
*/
|
||||
async getAllProducts(brand?: string): Promise<{ items: Product[], total: number }> {
|
||||
const qb = this.productModel
|
||||
.createQueryBuilder('product')
|
||||
.leftJoinAndSelect('product.attributes', 'attribute')
|
||||
.leftJoinAndSelect('attribute.dict', 'dict')
|
||||
.leftJoinAndSelect('product.category', 'category');
|
||||
|
||||
// 按品牌过滤
|
||||
if (brand) {
|
||||
// 先获取品牌对应的字典项
|
||||
const brandDict = await this.dictModel.findOne({ where: { name: 'brand' } });
|
||||
if (brandDict) {
|
||||
// 查找品牌名称对应的字典项(支持标题和名称匹配)
|
||||
const brandItem = await this.dictItemModel.findOne({
|
||||
where: [
|
||||
{
|
||||
title: brand,
|
||||
dict: { id: brandDict.id }
|
||||
},
|
||||
{
|
||||
name: brand,
|
||||
dict: { id: brandDict.id }
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (brandItem) {
|
||||
qb.andWhere(qb => {
|
||||
const subQuery = qb
|
||||
.subQuery()
|
||||
.select('product_attributes_dict_item.productId')
|
||||
.from('product_attributes_dict_item', 'product_attributes_dict_item')
|
||||
.where('product_attributes_dict_item.dictItemId = :brandId', {
|
||||
brandId: brandItem.id,
|
||||
})
|
||||
.getQuery();
|
||||
return 'product.id IN ' + subQuery;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据类型填充组成信息
|
||||
const items = await qb.getMany();
|
||||
for (const product of items) {
|
||||
if (product.type === 'single') {
|
||||
// 单品不持久化组成,这里仅返回一个基于 SKU 的虚拟组成
|
||||
const component = new ProductStockComponent();
|
||||
component.productId = product.id;
|
||||
component.sku = product.sku;
|
||||
component.quantity = 1;
|
||||
product.components = [component];
|
||||
} else {
|
||||
// 混装商品返回持久化的 SKU 组成
|
||||
product.components = await this.productStockComponentModel.find({
|
||||
where: { productId: product.id },
|
||||
});
|
||||
}
|
||||
|
||||
// 确保属性按强度正确划分,只保留强度相关的属性
|
||||
// 这里根据需求,如果需要可以进一步过滤或重组属性
|
||||
}
|
||||
|
||||
return {
|
||||
items,
|
||||
total: items.length
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取产品按属性值分组,支持按强度划分
|
||||
* @param brand 品牌名称
|
||||
* @returns 按属性值分组的产品
|
||||
*/
|
||||
async getProductsGroupedByAttribute(brand?: string, attributeName: string = 'strength'): Promise<{ [key: string]: Product[] }> {
|
||||
// 首先获取所有产品
|
||||
const { items } = await this.getAllProducts(brand);
|
||||
|
||||
// 按指定属性分组
|
||||
const groupedProducts: { [key: string]: Product[] } = {};
|
||||
|
||||
items.forEach(product => {
|
||||
// 获取产品的指定属性值
|
||||
const attribute = product.attributes.find(attr => attr.dict.name === attributeName);
|
||||
if (attribute) {
|
||||
const attributeValue = attribute.title || attribute.name;
|
||||
if (!groupedProducts[attributeValue]) {
|
||||
groupedProducts[attributeValue] = [];
|
||||
}
|
||||
groupedProducts[attributeValue].push(product);
|
||||
} else {
|
||||
// 如果没有该属性,放入未分组
|
||||
if (!groupedProducts['未分组']) {
|
||||
groupedProducts['未分组'] = [];
|
||||
}
|
||||
groupedProducts['未分组'].push(product);
|
||||
}
|
||||
});
|
||||
|
||||
return groupedProducts;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue