feat(plan): 新增「加入计划」选择器页 + 增强官方计划浏览,修复返回不刷新

1) 新增 pages/plan-select(加入我的计划选择器,替换详情页 actionSheet)
   - 默认展示我全部计划(浏览,不再强制搜索),支持 最近创建/动作最多 排序 + 名称搜索
   - 点选即把动作加入该计划并返回;无计划时引导新建
   - 入口接在 detail 页「加入计划」(无 planId 时 navigate 至此)
2) 增强 pages/collection(官方训练计划)
   - 默认展示全部分级,新增 等级筛选 chips + 名称搜索(均用现有字段,不改后端)
   - 筛选/搜索纯前端,viewGroups 驱动渲染
3) 修复 plan-detail 从「添加动作」返回后列表不刷新(上轮已单独提交 7ff55a6)
4) app.json 注册 plan-select 页面
This commit is contained in:
2026-07-23 14:15:41 +08:00
parent 7ff55a6257
commit ff3a120b55
10 changed files with 282 additions and 33 deletions

View File

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