feat: init fitness-coach (backend + miniprogram)
- backend: NestJS service with exercise/plan data - miniprogram: WeChat mini program client - exclude node_modules, dist, runtime data-store, local env
This commit is contained in:
88
miniprogram/pages/index/index.js
Normal file
88
miniprogram/pages/index/index.js
Normal file
@@ -0,0 +1,88 @@
|
||||
const app = getApp();
|
||||
const svc = require('../../services/exercise');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 20,
|
||||
stats: null,
|
||||
bodyParts: [],
|
||||
equipment: [],
|
||||
featured: [],
|
||||
collections: [],
|
||||
loading: true,
|
||||
quickEntries: [
|
||||
{ key: 'recommend', label: '智能推荐', emoji: '✨', url: '/pages/recommend/recommend' },
|
||||
{ key: 'bodyPart', label: '按部位', emoji: '💪', url: '/pages/category/category?dimension=bodyPart' },
|
||||
{ key: 'equipment', label: '按器械', emoji: '🏋️', url: '/pages/category/category?dimension=equipment' },
|
||||
{ key: 'plan', label: '训练计划', emoji: '📋', url: '/pages/collection/collection' },
|
||||
{ key: 'home', label: '居家无器械', emoji: '🏠', url: '/pages/collection-detail/collection-detail?slug=home' },
|
||||
{ key: 'search', label: '搜索', emoji: '🔍', url: '/pages/search/search' }
|
||||
]
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 20 });
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
loadData() {
|
||||
this.setData({ loading: true });
|
||||
Promise.all([
|
||||
svc.getStats().catch(() => null),
|
||||
svc.listExercises({ pageSize: 8 }).catch(() => ({ items: [] })),
|
||||
svc.listCollections().catch(() => [])
|
||||
]).then(([stats, list, collections]) => {
|
||||
this.setData({
|
||||
stats,
|
||||
bodyParts: (stats && stats.bodyParts) || [],
|
||||
equipment: (stats && stats.equipment) || [],
|
||||
featured: (list && list.items) || [],
|
||||
collections: collections || [],
|
||||
loading: false
|
||||
});
|
||||
}).catch(() => {
|
||||
this.setData({ loading: false });
|
||||
});
|
||||
},
|
||||
|
||||
goRecommend() {
|
||||
wx.navigateTo({ url: '/pages/recommend/recommend' });
|
||||
},
|
||||
|
||||
onQuick(e) {
|
||||
wx.navigateTo({ url: e.currentTarget.dataset.url });
|
||||
},
|
||||
|
||||
onSearch() {
|
||||
wx.navigateTo({ url: '/pages/search/search' });
|
||||
},
|
||||
|
||||
onScanBodyPart(e) {
|
||||
const { value, label } = e.currentTarget.dataset;
|
||||
wx.navigateTo({
|
||||
url: '/pages/category/category?dimension=bodyPart&value=' +
|
||||
encodeURIComponent(value) + '&title=' + encodeURIComponent(label)
|
||||
});
|
||||
},
|
||||
|
||||
onScanEquipment(e) {
|
||||
const { value, label } = e.currentTarget.dataset;
|
||||
wx.navigateTo({
|
||||
url: '/pages/category/category?dimension=equipment&value=' +
|
||||
encodeURIComponent(value) + '&title=' + encodeURIComponent(label)
|
||||
});
|
||||
},
|
||||
|
||||
onCollection(e) {
|
||||
wx.navigateTo({
|
||||
url: '/pages/collection-detail/collection-detail?slug=' +
|
||||
encodeURIComponent(e.currentTarget.dataset.slug)
|
||||
});
|
||||
},
|
||||
|
||||
onExercise(e) {
|
||||
wx.navigateTo({
|
||||
url: '/pages/detail/detail?id=' + encodeURIComponent(e.detail.id)
|
||||
});
|
||||
}
|
||||
});
|
||||
8
miniprogram/pages/index/index.json
Normal file
8
miniprogram/pages/index/index.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"exercise-card": "/components/exercise-card/index",
|
||||
"section-header": "/components/section-header/index",
|
||||
"bottom-nav": "/components/bottom-nav/index"
|
||||
}
|
||||
}
|
||||
99
miniprogram/pages/index/index.wxml
Normal file
99
miniprogram/pages/index/index.wxml
Normal file
@@ -0,0 +1,99 @@
|
||||
<view class="home" style="padding-top: {{statusBarHeight}}px;">
|
||||
<!-- Hero -->
|
||||
<view class="hero">
|
||||
<view class="hero__eyebrow">FITCOACH · 智能健身教练</view>
|
||||
<view class="hero__title">今天,练点什么?</view>
|
||||
<view class="hero__sub">精准匹配你的目标肌群,让每一次训练都更高效</view>
|
||||
|
||||
<view class="search-pill" bindtap="onSearch">
|
||||
<text class="search-pill__icon">🔍</text>
|
||||
<text class="search-pill__ph">搜索动作 / 器械 / 部位</text>
|
||||
</view>
|
||||
|
||||
<view class="cta" bindtap="goRecommend">
|
||||
<text class="cta__icon">✨</text>
|
||||
<text class="cta__txt">智能推荐</text>
|
||||
<text class="cta__arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 快捷入口 2x3 -->
|
||||
<view class="section quick">
|
||||
<view class="quick__grid">
|
||||
<view
|
||||
wx:for="{{quickEntries}}"
|
||||
wx:key="key"
|
||||
class="quick__tile"
|
||||
data-url="{{item.url}}"
|
||||
bindtap="onQuick">
|
||||
<text class="quick__emoji">{{item.emoji}}</text>
|
||||
<text class="quick__label">{{item.label}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 精选动作 横向滑动 -->
|
||||
<view class="section">
|
||||
<section-header eyebrow="FEATURED" title="精选动作" subtitle="编辑精选 · 高效入门" action="全部" bind:action="goRecommend" />
|
||||
<scroll-view wx:if="{{featured.length}}" scroll-x class="hscroll">
|
||||
<view class="hscroll__item" wx:for="{{featured}}" wx:key="id">
|
||||
<exercise-card exercise="{{item}}" compact bind:tap="onExercise" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view wx:else class="empty">暂无数据</view>
|
||||
</view>
|
||||
|
||||
<!-- 训练组合 横向 -->
|
||||
<view class="section" wx:if="{{collections.length}}">
|
||||
<section-header eyebrow="PROGRAMS" title="训练组合" subtitle="一键开启主题训练" />
|
||||
<scroll-view scroll-x class="chip-scroll">
|
||||
<view
|
||||
wx:for="{{collections}}"
|
||||
wx:key="slug"
|
||||
class="prog"
|
||||
style="background: {{item.color}}22; border-color: {{item.color}}55;"
|
||||
data-slug="{{item.slug}}"
|
||||
bindtap="onCollection">
|
||||
<text class="prog__emoji">{{item.emoji}}</text>
|
||||
<text class="prog__name" style="color: {{item.color}};">{{item.name}}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 按部位浏览 -->
|
||||
<view class="section">
|
||||
<section-header eyebrow="BY BODY PART" title="按部位浏览" subtitle="选择你的目标区域" />
|
||||
<view class="bp-grid">
|
||||
<view
|
||||
wx:for="{{bodyParts}}"
|
||||
wx:key="value"
|
||||
class="bp"
|
||||
data-value="{{item.value}}"
|
||||
data-label="{{item.label}}"
|
||||
bindtap="onScanBodyPart">
|
||||
<text class="bp__label">{{item.label}}</text>
|
||||
<text class="bp__count">{{item.count}} 个动作</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 热门器械 -->
|
||||
<view class="section" wx:if="{{equipment.length}}">
|
||||
<section-header eyebrow="EQUIPMENT" title="热门器械" subtitle="按器械筛选动作" />
|
||||
<scroll-view scroll-x class="chip-scroll">
|
||||
<view
|
||||
wx:for="{{equipment}}"
|
||||
wx:key="value"
|
||||
class="eq"
|
||||
data-value="{{item.value}}"
|
||||
data-label="{{item.label}}"
|
||||
bindtap="onScanEquipment">
|
||||
<text class="eq__name">{{item.label}}</text>
|
||||
<text class="eq__count">{{item.count}}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<view style="height: 160rpx;"></view>
|
||||
<bottom-nav active="home" />
|
||||
</view>
|
||||
78
miniprogram/pages/index/index.wxss
Normal file
78
miniprogram/pages/index/index.wxss
Normal file
@@ -0,0 +1,78 @@
|
||||
.home { min-height: 100vh; background: var(--bg-grad); }
|
||||
|
||||
.hero {
|
||||
padding: 20rpx 40rpx 48rpx;
|
||||
background: linear-gradient(150deg, #1C2A22 0%, #0E1110 70%);
|
||||
border-bottom-left-radius: 48rpx;
|
||||
border-bottom-right-radius: 48rpx;
|
||||
}
|
||||
.hero__eyebrow { color: var(--gold); font-size: 22rpx; letter-spacing: 4rpx; }
|
||||
.hero__title { font-size: 56rpx; font-weight: 700; letter-spacing: 2rpx; margin-top: 14rpx; color: var(--text); }
|
||||
.hero__sub { font-size: 26rpx; color: var(--text-2); margin-top: 14rpx; }
|
||||
|
||||
.search-pill {
|
||||
margin-top: 36rpx;
|
||||
display: flex; align-items: center;
|
||||
background: var(--surface); border: 1rpx solid var(--line);
|
||||
border-radius: 999rpx; padding: 22rpx 30rpx;
|
||||
}
|
||||
.search-pill__icon { font-size: 30rpx; margin-right: 16rpx; }
|
||||
.search-pill__ph { color: var(--text-3); font-size: 28rpx; }
|
||||
|
||||
.cta {
|
||||
margin-top: 24rpx;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%);
|
||||
color: #0E1110; font-weight: 700; font-size: 32rpx;
|
||||
border-radius: 999rpx; padding: 28rpx;
|
||||
box-shadow: 0 12rpx 30rpx rgba(217,179,108,0.28);
|
||||
}
|
||||
.cta__icon { margin-right: 12rpx; }
|
||||
.cta__arrow { margin-left: 12rpx; font-size: 36rpx; }
|
||||
|
||||
.quick__grid { display: flex; flex-wrap: wrap; gap: 18rpx; }
|
||||
.quick__tile {
|
||||
width: calc((100% - 36rpx) / 3);
|
||||
background: var(--surface); border: 1rpx solid var(--line);
|
||||
border-radius: var(--radius); padding: 28rpx 18rpx;
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
position: relative; overflow: hidden;
|
||||
}
|
||||
.quick__tile::after {
|
||||
content: ''; position: absolute; inset: 0;
|
||||
background: radial-gradient(circle at 50% 0%, var(--gold-soft), transparent 70%);
|
||||
}
|
||||
.quick__emoji { font-size: 48rpx; position: relative; z-index: 1; }
|
||||
.quick__label { font-size: 26rpx; margin-top: 12rpx; color: var(--text); position: relative; z-index: 1; }
|
||||
|
||||
.hscroll { white-space: nowrap; margin-top: 8rpx; }
|
||||
.hscroll__item { display: inline-block; width: 300rpx; margin-right: 20rpx; vertical-align: top; }
|
||||
|
||||
.chip-scroll { white-space: nowrap; margin-top: 8rpx; }
|
||||
.prog {
|
||||
display: inline-flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
min-width: 160rpx; padding: 24rpx 28rpx; margin-right: 18rpx;
|
||||
border-radius: var(--radius-sm); border: 1rpx solid;
|
||||
}
|
||||
.prog__emoji { font-size: 44rpx; }
|
||||
.prog__name { font-size: 26rpx; font-weight: 700; margin-top: 10rpx; }
|
||||
|
||||
.bp-grid { display: flex; flex-wrap: wrap; gap: 16rpx; }
|
||||
.bp {
|
||||
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;
|
||||
}
|
||||
.bp__label { font-size: 26rpx; color: var(--text); font-weight: 600; }
|
||||
.bp__count { font-size: 20rpx; color: var(--text-3); margin-top: 6rpx; }
|
||||
|
||||
.eq {
|
||||
display: inline-flex; align-items: center; gap: 12rpx;
|
||||
background: var(--surface-2); border: 1rpx solid var(--line);
|
||||
border-radius: 999rpx; padding: 16rpx 28rpx; margin-right: 16rpx;
|
||||
}
|
||||
.eq__name { font-size: 26rpx; color: var(--text); }
|
||||
.eq__count { font-size: 22rpx; color: var(--gold); }
|
||||
|
||||
.empty { color: var(--text-3); font-size: 26rpx; text-align: center; padding: 40rpx 0; }
|
||||
Reference in New Issue
Block a user