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);
|
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;
|
updatedAtStart?: string;
|
||||||
// 更新时间范围结束
|
// 更新时间范围结束
|
||||||
updatedAtEnd?: 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) });
|
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) {
|
if (brandId) {
|
||||||
qb.andWhere(qb => {
|
qb.andWhere(qb => {
|
||||||
|
|
@ -2076,4 +2111,111 @@ export class ProductService {
|
||||||
|
|
||||||
return unifiedProduct;
|
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