diff --git a/miniprogram/app.json b/miniprogram/app.json
index e5a9976..f987ab6 100644
--- a/miniprogram/app.json
+++ b/miniprogram/app.json
@@ -13,6 +13,7 @@
"pages/my-plans/my-plans",
"pages/plan-edit/plan-edit",
"pages/plan-detail/plan-detail",
+ "pages/plan-select/plan-select",
"pages/plan-favorites/plan-favorites"
],
"window": {
diff --git a/miniprogram/pages/collection/collection.js b/miniprogram/pages/collection/collection.js
index 7d02cf0..0dd152f 100644
--- a/miniprogram/pages/collection/collection.js
+++ b/miniprogram/pages/collection/collection.js
@@ -1,10 +1,15 @@
const app = getApp();
const planSvc = require('../../services/plan');
+// 官方训练计划浏览:默认展示全部分级,支持按等级筛选 + 名称搜索(均用现有字段)。
Page({
data: {
statusBarHeight: 20,
groups: [],
+ viewGroups: [],
+ levels: [{ value: 'all', label: '全部' }],
+ filterLevel: 'all',
+ keyword: '',
loading: true
},
@@ -15,20 +20,44 @@ Page({
onShow() {
// 从详情返回(可能收藏/导入变化)时刷新
- if (!this.data.loading) this.load();
+ if (!this.data.loading && !this.data.keyword && this.data.filterLevel === 'all') this.load();
},
load() {
this.setData({ loading: true });
planSvc.officialGroups().then((res) => {
- this.setData({ groups: (res && res.groups) || [], loading: false });
+ const groups = (res && res.groups) || [];
+ const levels = [{ value: 'all', label: '全部' }].concat(
+ groups.map((g) => ({ value: g.level, label: g.label }))
+ );
+ this.setData({ groups, levels, loading: false });
+ this.applyFilter();
}).catch(() => {
- this.setData({ groups: [], loading: false });
+ this.setData({ groups: [], viewGroups: [], levels: [{ value: 'all', label: '全部' }], loading: false });
});
},
- // 计算某计划的跳转地址(供 wxml 使用)
- planTarget(slug) {
- return '/pages/collection-detail/collection-detail?slug=' + encodeURIComponent(slug);
+ applyFilter() {
+ const f = this.data.filterLevel;
+ const k = (this.data.keyword || '').trim().toLowerCase();
+ let groups = (this.data.groups || []).slice();
+ if (f !== 'all') groups = groups.filter((g) => g.level === f);
+ if (k) {
+ groups = groups
+ .map((g) => ({
+ ...g,
+ plans: (g.plans || []).filter((p) => (p.name || '').toLowerCase().includes(k))
+ }))
+ .filter((g) => (g.plans || []).length);
+ }
+ this.setData({ viewGroups: groups });
+ },
+
+ onFilter(e) {
+ this.setData({ filterLevel: e.detail.value }, () => this.applyFilter());
+ },
+
+ onSearch(e) {
+ this.setData({ keyword: e.detail.value }, () => this.applyFilter());
}
});
diff --git a/miniprogram/pages/collection/collection.json b/miniprogram/pages/collection/collection.json
index 66d158e..b4d9ba9 100644
--- a/miniprogram/pages/collection/collection.json
+++ b/miniprogram/pages/collection/collection.json
@@ -3,6 +3,8 @@
"usingComponents": {
"navbar": "/components/navbar/index",
"section-header": "/components/section-header/index",
- "plan-card": "/components/plan-card/index"
+ "plan-card": "/components/plan-card/index",
+ "chip": "/components/chip/index",
+ "fc-icon": "/components/fc-icon/index"
}
}
diff --git a/miniprogram/pages/collection/collection.wxml b/miniprogram/pages/collection/collection.wxml
index 9278f82..57f7604 100644
--- a/miniprogram/pages/collection/collection.wxml
+++ b/miniprogram/pages/collection/collection.wxml
@@ -8,8 +8,33 @@
从新手入门到高级挑战,分级规划你的每一次进阶
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -21,7 +46,7 @@
- 暂无官方计划
+ 没有匹配的计划
加载中…
diff --git a/miniprogram/pages/collection/collection.wxss b/miniprogram/pages/collection/collection.wxss
index dfd3d7a..ef08b5e 100644
--- a/miniprogram/pages/collection/collection.wxss
+++ b/miniprogram/pages/collection/collection.wxss
@@ -8,4 +8,17 @@
.group + .group { margin-top: 36rpx; }
.grid { display: flex; flex-wrap: wrap; gap: 18rpx; }
.grid__item { width: calc((100% - 18rpx) / 2); }
+
+.filterbar { padding: 16rpx 32rpx 4rpx; }
+.filterbar__scroll { white-space: nowrap; }
+
+.searchbar { padding: 12rpx 32rpx; }
+.searchbar__box {
+ display: flex; align-items: center;
+ background: var(--surface); border: 1rpx solid var(--line);
+ border-radius: 999rpx; padding: 20rpx 28rpx;
+}
+.searchbar__input { flex: 1; color: var(--text); font-size: 28rpx; }
+.ph { color: var(--text-3); }
+
.empty { color: var(--text-3); font-size: 26rpx; text-align: center; padding: 80rpx 0; }
diff --git a/miniprogram/pages/detail/detail.js b/miniprogram/pages/detail/detail.js
index c0149af..7ee89f0 100644
--- a/miniprogram/pages/detail/detail.js
+++ b/miniprogram/pages/detail/detail.js
@@ -68,7 +68,9 @@ Page({
}).catch(() => {});
},
- // 底部主按钮:加入计划(真实选择器)
+ // 底部主按钮:加入计划
+ // - 若从某计划内进入(带 planId),直接加入该计划
+ // - 否则进入「加入计划」选择器页,可浏览全部计划并筛选
onAdd() {
if (this.data.planId) {
planSvc.addExercise(this.data.planId, this.data.id).then(() => {
@@ -77,28 +79,13 @@ Page({
}).catch(() => {});
return;
}
- auth.ensureLogin().then(() => planSvc.listMine()).then((res) => {
- const plans = (res && res.items) || res || [];
- if (!plans.length) {
- wx.showModal({
- title: '还没有训练计划',
- content: '先创建一个计划,再把动作加进去吧~',
- confirmText: '去创建',
- cancelText: '稍后',
- success: (r) => { if (r.confirm) wx.navigateTo({ url: '/pages/plan-edit/plan-edit' }); }
- });
- return;
- }
- const names = plans.slice(0, 6).map((p) => p.name);
- wx.showActionSheet({
- itemList: names,
- success: (sheet) => {
- const plan = plans[sheet.tapIndex];
- if (!plan) return;
- planSvc.addExercise(plan.id, this.data.id).then(() => {
- wx.showToast({ title: '已加入「' + plan.name + '」', icon: 'success' });
- }).catch(() => {});
- }
+ auth.ensureLogin().then(() => {
+ const name = this.data.exercise
+ ? (this.data.exercise.displayName || this.data.exercise.name)
+ : '';
+ wx.navigateTo({
+ url: '/pages/plan-select/plan-select?exerciseId=' + encodeURIComponent(this.data.id) +
+ '&name=' + encodeURIComponent(name || '')
});
}).catch(() => {});
},
diff --git a/miniprogram/pages/plan-select/plan-select.js b/miniprogram/pages/plan-select/plan-select.js
new file mode 100644
index 0000000..4c34e7b
--- /dev/null
+++ b/miniprogram/pages/plan-select/plan-select.js
@@ -0,0 +1,92 @@
+const app = getApp();
+const planSvc = require('../../services/plan');
+const auth = require('../../utils/auth');
+const { iconName } = require('../../utils/icon-name');
+
+// 加入计划选择器:从动作详情「加入计划」进入,默认展示我全部计划,
+// 支持 最近创建 / 动作最多 排序与名称搜索,点选即把动作加入该计划并返回。
+Page({
+ data: {
+ statusBarHeight: 20,
+ exerciseId: '',
+ exerciseName: '',
+ allPlans: [],
+ plans: [],
+ filter: 'all', // 'all' | 'recent' | 'count'
+ keyword: '',
+ status: 'loading', // 'loading' | 'list' | 'empty' | 'error'
+ errorMsg: ''
+ },
+
+ onLoad(query) {
+ this.setData({
+ statusBarHeight: app.globalData.statusBarHeight || 20,
+ exerciseId: query.exerciseId || '',
+ exerciseName: query.name || ''
+ });
+ },
+
+ onShow() {
+ if (auth.isLogin()) this.load();
+ else {
+ wx.showToast({ title: '请先登录', icon: 'none' });
+ setTimeout(() => wx.navigateBack(), 600);
+ }
+ },
+
+ load() {
+ this.setData({ status: 'loading', errorMsg: '' });
+ planSvc.listMine().then((res) => {
+ const raw = (res && res.items) || res || [];
+ const plans = raw.map((p) => ({ ...p, icon: iconName(p.emoji, 'folder-open') }));
+ this.setData({ allPlans: plans, status: plans.length ? 'list' : 'empty' });
+ this.applyFilter();
+ }).catch((err) => {
+ const status = err && err.statusCode;
+ const msg = status === 401 ? '登录已失效,请重新登录' : '加载失败,请检查网络后重试';
+ this.setData({ status: 'error', errorMsg: msg });
+ });
+ },
+
+ // 排序 + 名称搜索(纯前端,基于已有字段 createdAt / exerciseCount / name)
+ applyFilter() {
+ const f = this.data.filter;
+ let list = (this.data.allPlans || []).slice();
+ const k = (this.data.keyword || '').trim().toLowerCase();
+ if (k) list = list.filter((p) => (p.name || '').toLowerCase().includes(k));
+ if (f === 'recent') list.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
+ else if (f === 'count') list.sort((a, b) => b.exerciseCount - a.exerciseCount);
+ this.setData({ plans: list });
+ },
+
+ onFilter(e) {
+ this.setData({ filter: e.detail.value }, () => this.applyFilter());
+ },
+
+ onSearch(e) {
+ this.setData({ keyword: e.detail.value }, () => this.applyFilter());
+ },
+
+ onSelect(e) {
+ const id = e.currentTarget.dataset.id;
+ const name = e.currentTarget.dataset.name || '';
+ if (!id || !this.data.exerciseId) return;
+ wx.showLoading({ title: '加入中…', mask: true });
+ planSvc.addExercise(id, this.data.exerciseId).then(() => {
+ wx.hideLoading();
+ wx.showToast({ title: name ? ('已加入「' + name + '」') : '已加入计划', icon: 'success' });
+ setTimeout(() => wx.navigateBack(), 400);
+ }).catch(() => {
+ wx.hideLoading();
+ wx.showToast({ title: '加入失败,请重试', icon: 'none' });
+ });
+ },
+
+ onCreate() {
+ wx.navigateTo({ url: '/pages/plan-edit/plan-edit' });
+ },
+
+ onRetry() {
+ this.load();
+ }
+});
diff --git a/miniprogram/pages/plan-select/plan-select.json b/miniprogram/pages/plan-select/plan-select.json
new file mode 100644
index 0000000..2264ab9
--- /dev/null
+++ b/miniprogram/pages/plan-select/plan-select.json
@@ -0,0 +1,9 @@
+{
+ "navigationStyle": "custom",
+ "usingComponents": {
+ "navbar": "/components/navbar/index",
+ "chip": "/components/chip/index",
+ "plan-card": "/components/plan-card/index",
+ "fc-icon": "/components/fc-icon/index"
+ }
+}
diff --git a/miniprogram/pages/plan-select/plan-select.wxml b/miniprogram/pages/plan-select/plan-select.wxml
new file mode 100644
index 0000000..5bfd544
--- /dev/null
+++ b/miniprogram/pages/plan-select/plan-select.wxml
@@ -0,0 +1,56 @@
+
+
+
+ 将动作「{{exerciseName}}」加入以下计划:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 还没有计划,先创建一个吧
+ + 新建计划
+
+
+
+
+ {{errorMsg}}
+ 重新加载
+
+
+ 加载中…
+
+
+
+
diff --git a/miniprogram/pages/plan-select/plan-select.wxss b/miniprogram/pages/plan-select/plan-select.wxss
new file mode 100644
index 0000000..c10946e
--- /dev/null
+++ b/miniprogram/pages/plan-select/plan-select.wxss
@@ -0,0 +1,35 @@
+.page { min-height: 100vh; background: var(--bg-grad); }
+
+.hint {
+ padding: 16rpx 36rpx 0;
+ font-size: 24rpx;
+ color: var(--gold);
+}
+
+.filterbar { padding: 16rpx 32rpx 4rpx; }
+.filterbar__scroll { white-space: nowrap; }
+
+.searchbar { padding: 12rpx 32rpx; }
+.searchbar__box {
+ display: flex; align-items: center;
+ background: var(--surface); border: 1rpx solid var(--line);
+ border-radius: 999rpx; padding: 20rpx 28rpx;
+}
+.searchbar__input { flex: 1; color: var(--text); font-size: 28rpx; }
+.ph { color: var(--text-3); }
+
+.grid { display: flex; flex-wrap: wrap; gap: 18rpx; }
+.grid__item { width: calc((100% - 18rpx) / 2); }
+
+.empty { color: var(--text-3); font-size: 26rpx; text-align: center; padding: 80rpx 0; }
+
+.empty-box { display: flex; flex-direction: column; align-items: center; padding: 100rpx 40rpx; }
+.empty-box__emoji {
+ width: 160rpx; height: 160rpx; border-radius: 36rpx;
+ background: var(--surface-2); display: flex; align-items: center; justify-content: center;
+}
+.empty-box__t { font-size: 30rpx; color: var(--text-2); margin-top: 36rpx; text-align: center; }
+.empty-box__btn {
+ margin-top: 44rpx; padding: 24rpx 88rpx; border-radius: 999rpx;
+ background: var(--gold); color: var(--accent-on); font-weight: 700; font-size: 30rpx;
+}