forked from yoone/API
1
0
Fork 0

feat(产品): 添加产品属性过滤和分组功能

- 在 ProductWhereFilter 接口中添加 attributes 字段用于属性过滤
- 新增 getAllProducts 方法支持按品牌过滤产品
- 新增 getProductsGroupedByAttribute 方法实现按属性分组产品
- 在查询构建器中添加属性过滤逻辑
This commit is contained in:
tikkhun 2026-01-15 14:32:43 +08:00
parent fff62d6864
commit bdac4860df
3 changed files with 171 additions and 0 deletions

View File

@ -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);
}
}
}

View File

@ -311,6 +311,8 @@ export interface ProductWhereFilter {
updatedAtStart?: string;
// 更新时间范围结束
updatedAtEnd?: string;
// TODO 使用 attributes 过滤
attributes?: Record<string, string>;
}
/**

View File

@ -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;
}
}