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; +}