diff --git a/backend/src/exercises/exercises.service.ts b/backend/src/exercises/exercises.service.ts
index bdcce3f..08b716c 100644
--- a/backend/src/exercises/exercises.service.ts
+++ b/backend/src/exercises/exercises.service.ts
@@ -46,8 +46,15 @@ export class ExercisesService implements OnModuleInit {
findAll(query: QueryExercisesDto) {
let list = this.exercises;
- if (query.bodyPart) list = list.filter((e) => e.bodyPart === query.bodyPart);
- if (query.equipment) list = list.filter((e) => e.equipment === query.equipment);
+ // 支持逗号分隔多值(OR 关系),向后兼容单值
+ if (query.bodyPart) {
+ const arr = String(query.bodyPart).split(',').map((s) => s.trim()).filter(Boolean);
+ if (arr.length) list = list.filter((e) => arr.includes(e.bodyPart));
+ }
+ if (query.equipment) {
+ const arr = String(query.equipment).split(',').map((s) => s.trim()).filter(Boolean);
+ if (arr.length) list = list.filter((e) => arr.includes(e.equipment));
+ }
if (query.type) list = list.filter((e) => e.type === query.type);
if (query.target) list = list.filter((e) => e.target === query.target);
if (query.muscleGroup)
diff --git a/miniprogram/pages/category/category.js b/miniprogram/pages/category/category.js
index 4ae43b2..d6148c2 100644
--- a/miniprogram/pages/category/category.js
+++ b/miniprogram/pages/category/category.js
@@ -9,6 +9,11 @@ const DIM_MAP = {
type: 'types'
};
+const SECONDARY_LABEL = {
+ bodyPart: '部位',
+ equipment: '器械'
+};
+
function labelForDim(d) {
return ({
bodyPart: '按部位',
@@ -27,6 +32,12 @@ Page({
value: '',
values: [],
activeValue: '',
+ // 二级维度(多维度筛选):进入器械维度时交叉按部位,其余维度交叉按器械
+ secondaryDim: 'equipment',
+ secondaryKey: 'equipment',
+ secondaryLabel: '器械',
+ secondaryList: [],
+ secondarySelected: [],
exercises: [],
loading: true,
loadingMore: false,
@@ -39,11 +50,15 @@ Page({
onLoad(query) {
const dimension = query.dimension || 'bodyPart';
const value = query.value || '';
+ const secondaryDim = dimension === 'equipment' ? 'bodyPart' : 'equipment';
const title = query.title || labelForDim(dimension);
this.setData({
statusBarHeight: app.globalData.statusBarHeight || 20,
dimension,
value,
+ secondaryDim,
+ secondaryKey: DIM_MAP[secondaryDim] || 'equipment',
+ secondaryLabel: SECONDARY_LABEL[secondaryDim] || '器械',
title
});
this.loadValues();
@@ -52,14 +67,30 @@ Page({
loadValues() {
this.setData({ loading: true });
const key = DIM_MAP[this.data.dimension] || 'body-parts';
- svc.getCategories(key).then((items) => {
+ const secKey = this.data.secondaryKey;
+ Promise.all([
+ svc.getCategories(key),
+ svc.getCategories(secKey).catch(() => [])
+ ]).then(([items, secItems]) => {
const list = (items || []).slice();
- // 头部插入「全部」项:无 value 入参时默认选中,明确当前为「全部」
+ // 头部插入「全部」项:明确当前为「全部」
list.unshift({ value: '', label: '全部' });
const activeValue = this.data.value || '';
+ // 用分类枚举里的干净中文标签回写标题,避免外部传入的标题出现乱码
+ let title = this.data.title;
+ if (activeValue) {
+ const hit = list.find((it) => it.value === activeValue);
+ if (hit && hit.label) title = hit.label;
+ }
+ const secList = (secItems || []).slice().map((it) => ({
+ ...it,
+ active: this.data.secondarySelected.includes(it.value)
+ }));
this.setData({
values: list,
activeValue: activeValue,
+ secondaryList: secList,
+ title,
loading: false
});
this.loadExercises();
@@ -68,10 +99,19 @@ Page({
});
},
+ syncSecondaryActive() {
+ const sel = this.data.secondarySelected;
+ const list = this.data.secondaryList.map((it) => ({ ...it, active: sel.includes(it.value) }));
+ this.setData({ secondaryList: list });
+ },
+
loadExercises() {
this.setData({ loading: true, page: 1 });
const params = { page: 1, pageSize: this.data.pageSize };
params[this.data.dimension] = this.data.activeValue;
+ if (this.data.secondarySelected.length) {
+ params[this.data.secondaryDim] = this.data.secondarySelected.join(',');
+ }
svc.listExercises(params).then((res) => {
const items = (res && res.items) || [];
const total = (res && res.total) || 0;
@@ -96,6 +136,9 @@ Page({
this.setData({ loadingMore: true });
const params = { page: next, pageSize: this.data.pageSize };
params[this.data.dimension] = this.data.activeValue;
+ if (this.data.secondarySelected.length) {
+ params[this.data.secondaryDim] = this.data.secondarySelected.join(',');
+ }
svc.listExercises(params).then((res) => {
const items = (res && res.items) || [];
const total = (res && res.total) || 0;
@@ -118,6 +161,24 @@ Page({
this.setData({ activeValue: value, page: 1 }, () => this.loadExercises());
},
+ onSecondary(e) {
+ const value = e.detail.value;
+ if (!value) return;
+ const cur = this.data.secondarySelected;
+ const next = cur.includes(value) ? cur.filter((v) => v !== value) : cur.concat(value);
+ this.setData({ secondarySelected: next, page: 1 }, () => {
+ this.syncSecondaryActive();
+ this.loadExercises();
+ });
+ },
+
+ onSecondaryClear() {
+ this.setData({ secondarySelected: [], page: 1 }, () => {
+ this.syncSecondaryActive();
+ this.loadExercises();
+ });
+ },
+
onExercise(e) {
if (!e || !e.detail || !e.detail.id) return;
wx.navigateTo({ url: '/pages/detail/detail?id=' + encodeURIComponent(e.detail.id) });
diff --git a/miniprogram/pages/category/category.wxml b/miniprogram/pages/category/category.wxml
index a9441d5..fbf3886 100644
--- a/miniprogram/pages/category/category.wxml
+++ b/miniprogram/pages/category/category.wxml
@@ -2,16 +2,35 @@
-
-
-
-
+
+
+
+
+
+
+
+
+ {{secondaryLabel}}
+ 清除 ({{secondarySelected.length}})
+
+
+
+
+
+
+
{{ loading ? '加载分类中…' : '暂无分类' }}
共 {{total}} 个动作
diff --git a/miniprogram/pages/category/category.wxss b/miniprogram/pages/category/category.wxss
index 7d95a75..30711df 100644
--- a/miniprogram/pages/category/category.wxss
+++ b/miniprogram/pages/category/category.wxss
@@ -3,12 +3,17 @@
.filterbar {
position: sticky;
z-index: 40;
- white-space: nowrap;
- padding: 8rpx 0 4rpx;
+ padding: 8rpx 0 12rpx;
background: var(--bg-grad);
}
+.filterbar__row { white-space: nowrap; }
.filterbar chip { display: inline-block; margin-right: 16rpx; }
.filterbar__spacer { display: inline-block; width: 4rpx; }
+/* 二级维度(多维度筛选):主维度横列下方再叠一条横列,带标签与清除 */
+.filterbar__sub { margin-top: 12rpx; padding-top: 14rpx; border-top: 1rpx solid var(--line); }
+.filterbar__subhead { display: flex; align-items: center; justify-content: space-between; padding: 0 4rpx 10rpx; }
+.filterbar__sublabel { font-size: 24rpx; color: var(--text-2); font-weight: 600; }
+.filterbar__clear { font-size: 22rpx; color: var(--accent); }
/* 加载 / 空态占位:横列区域常驻,避免加载期间整块消失 */
.filterbar__loading {
font-size: 24rpx;
diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml
index 33b5ee2..7b68e5a 100644
--- a/miniprogram/pages/index/index.wxml
+++ b/miniprogram/pages/index/index.wxml
@@ -124,10 +124,10 @@
-
+
-
-
+
+
- {{item.label}}
- {{item.count}}
+ {{item.label}}
+ {{item.count}} 个动作
-
+
diff --git a/miniprogram/pages/index/index.wxss b/miniprogram/pages/index/index.wxss
index 6404334..b3d8e94 100644
--- a/miniprogram/pages/index/index.wxss
+++ b/miniprogram/pages/index/index.wxss
@@ -100,12 +100,15 @@
.bp__label { font-size: 26rpx; color: var(--text); font-weight: 600; }
.bp__count { font-size: 20rpx; color: var(--text-3); margin-top: 6rpx; }
+.eq-grid { display: flex; flex-wrap: wrap; gap: 16rpx; }
.eq {
- display: inline-flex; align-items: center; gap: 12rpx;
- background: var(--surface-2); border: 1rpx solid var(--line);
- border-radius: var(--radius-pill); padding: 16rpx 28rpx; margin-right: 16rpx;
+ width: calc((100% - 48rpx) / 4);
+ background: var(--surface); border: 1rpx solid var(--line);
+ border-radius: var(--radius-sm); padding: 22rpx 12rpx;
+ display: flex; flex-direction: column; align-items: center;
}
-.eq__name { font-size: 26rpx; color: var(--text); }
-.eq__count { font-size: 22rpx; color: var(--accent); }
+.eq:active { background: var(--surface-2); }
+.eq__label { font-size: 26rpx; color: var(--text); font-weight: 600; }
+.eq__count { font-size: 20rpx; color: var(--text-3); margin-top: 6rpx; }
.empty { color: var(--text-3); font-size: var(--fs-sm); text-align: center; padding: 40rpx 0; }