feat: 分类浏览体验优化
- 后端 CategoryItem 新增 sample 字段,每个器械/部位分类附首条动作代表图(720 JPG 海报),前端网格无需点进详情即可预览 - 首页『按器械浏览』『按部位浏览』网格 tile 改为图文卡片,展示分类代表图 - filter-bar 内联改为一行可横滑的分类 chips + 末尾『全部类型』入口;点开面板展开全部,选中项自动置顶
This commit is contained in:
@@ -80,6 +80,8 @@ export interface CategoryItem {
|
||||
/** 仅 target 分类会附带其所属部位,便于前端按部位分组 */
|
||||
bodyPart?: string;
|
||||
bodyPartLabel?: string;
|
||||
/** 该分类下首条动作的代表图(JPG 海报),供前端网格预览,无需点进详情即可辨认分类内容 */
|
||||
sample?: string | null;
|
||||
}
|
||||
|
||||
/** 推荐结果项(带相关性评分与推荐理由) */
|
||||
|
||||
@@ -116,12 +116,31 @@ export class ExercisesService implements OnModuleInit {
|
||||
.sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
/** 每个分类取首条动作的代表图(JPG 海报),供前端网格预览 */
|
||||
private sampleBy(getKey: (e: Exercise) => string): Map<string, string> {
|
||||
const map = new Map<string, string>();
|
||||
for (const e of this.exercises) {
|
||||
const k = getKey(e);
|
||||
if (k && !map.has(k) && e.image) map.set(k, e.image);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private withSample(
|
||||
items: CategoryItem[],
|
||||
samples: Map<string, string>,
|
||||
): CategoryItem[] {
|
||||
return items.map((it) => ({ ...it, sample: samples.get(it.value) || null }));
|
||||
}
|
||||
|
||||
getBodyPartCategories(): CategoryItem[] {
|
||||
return this.toCategory(this.countBy((e) => e.bodyPart), require('./data/labels').BODY_PART_LABELS);
|
||||
const items = this.toCategory(this.countBy((e) => e.bodyPart), require('./data/labels').BODY_PART_LABELS);
|
||||
return this.withSample(items, this.sampleBy((e) => e.bodyPart));
|
||||
}
|
||||
|
||||
getEquipmentCategories(): CategoryItem[] {
|
||||
return this.toCategory(this.countBy((e) => e.equipment), require('./data/labels').EQUIPMENT_LABELS);
|
||||
const items = this.toCategory(this.countBy((e) => e.equipment), require('./data/labels').EQUIPMENT_LABELS);
|
||||
return this.withSample(items, this.sampleBy((e) => e.equipment));
|
||||
}
|
||||
|
||||
getTargetCategories(): CategoryItem[] {
|
||||
|
||||
Reference in New Issue
Block a user