Files
fitness-coach/miniprogram/pages/index/index.js
wm b94eb706ff feat: Kinetic Dark 设计语言 + Lucide 图标迁移 + 首页轻量个性化
- 建立 Lucide 子集 iconfont(49 字形),全站 emoji 替换为 fc-icon 组件
- 重塑 Design Token(冷中性近黑底 + Volt 荧光绿 #B6F24E),消除硬编码颜色
- 首页登录后个性化:我的计划 / 我的收藏 / 今日推荐(纯前端复用现有 API)
- 后端 DEFAULT_EMOJI→Lucide 名,PALETTE 对齐 Kinetic Dark 语义色板
- 修复 person-standing 缺失字形;14 个官方组合 seed emoji 全部映射到现有字形
2026-07-22 21:38:56 +08:00

173 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const app = getApp();
const svc = require('../../services/exercise');
const planSvc = require('../../services/plan');
const favSvc = require('../../services/favorite');
const { iconName } = require('../../utils/icon-name');
function greeting() {
const h = new Date().getHours();
if (h < 6) return '夜深了,';
if (h < 11) return '早上好,';
if (h < 13) return '中午好,';
if (h < 18) return '下午好,';
if (h < 22) return '晚上好,';
return '夜深了,';
}
function isLogin() {
return !!wx.getStorageSync('access_token');
}
Page({
data: {
statusBarHeight: 20,
stats: null,
bodyParts: [],
equipment: [],
featured: [],
collections: [],
loading: true,
quickEntries: [
{ key: 'recommend', label: '智能推荐', icon: 'sparkles', url: '/pages/recommend/recommend' },
{ key: 'bodyPart', label: '按部位', icon: 'accessibility', url: '/pages/category/category?dimension=bodyPart' },
{ key: 'equipment', label: '按器械', icon: 'dumbbell', url: '/pages/category/category?dimension=equipment' },
{ key: 'official', label: '官方计划', icon: 'clipboard-list', url: '/pages/collection/collection' },
{ key: 'myPlans', label: '我的计划', icon: 'folder-open', url: '/pages/my-plans/my-plans' },
{ key: 'mine', label: '我的', icon: 'user', url: '/pages/profile/profile' }
],
// —— 轻量个性化(登录后)——
isLogin: false,
greeting: '',
myPlans: [],
myFavorites: [],
todayRecommend: []
},
onLoad() {
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 20 });
this.loadData();
},
onShow() {
// tab 切回:已登录则静默刷新个性化(优先缓存)
if (isLogin()) this.loadPersonal(true);
},
loadData() {
this.setData({ loading: true });
Promise.all([
svc.getStats().catch(() => null),
svc.listExercises({ pageSize: 8 }).catch(() => ({ items: [] })),
svc.listCollections().catch(() => [])
]).then(([stats, list, collections]) => {
const cols = (collections || []).map((c) => ({
...c,
icon: iconName(c.emoji)
}));
this.setData({
stats,
bodyParts: (stats && stats.bodyParts) || [],
equipment: (stats && stats.equipment) || [],
featured: (list && list.items) || [],
collections: cols,
loading: false
});
if (isLogin()) this.loadPersonal(false);
}).catch(() => {
this.setData({ loading: false });
});
},
// 轻量个性化:复用现有 API纯前端聚合silent=切 tab 静默刷新
loadPersonal(silent) {
if (!isLogin()) return;
this.setData({ isLogin: true, greeting: greeting() });
if (!silent) this.setData({ personalLoading: true });
const CACHE_KEY = 'fc_personal_cache';
const cached = wx.getStorageSync(CACHE_KEY);
const now = Date.now();
if (cached && now - (cached.ts || 0) < 5 * 60 * 1000 && !silent) {
this.applyPersonal(cached.data);
return;
}
Promise.all([
planSvc.listMine().catch(() => []),
favSvc.listExercises().catch(() => [])
]).then(([plans, favs]) => {
const planArr = Array.isArray(plans) ? plans : (plans && plans.items) || [];
const favArr = Array.isArray(favs) ? favs : (favs && favs.items) || [];
const data = {
myPlans: planArr.slice(0, 3).map((p) => ({
id: p.id, name: p.name, count: p.exerciseCount || 0
})),
myFavorites: favArr.slice(0, 6),
todayRecommend: (this.data.featured || []).slice(0, 3).map((e) => ({
id: e.id, name: e.displayName || e.name,
reason: '为你精选 · 高效入门'
}))
};
wx.setStorageSync(CACHE_KEY, { ts: now, data });
this.applyPersonal(data);
}).catch(() => {
this.setData({ personalLoading: false });
});
},
applyPersonal(data) {
this.setData({
myPlans: data.myPlans,
myFavorites: data.myFavorites,
todayRecommend: data.todayRecommend,
personalLoading: false
});
},
goRecommend() {
wx.navigateTo({ url: '/pages/recommend/recommend' });
},
onQuick(e) {
wx.navigateTo({ url: e.currentTarget.dataset.url });
},
onSearch() {
wx.navigateTo({ url: '/pages/search/search' });
},
onPlanTap(e) {
const id = e.currentTarget.dataset.id;
if (id) wx.navigateTo({ url: '/pages/plan-detail/plan-detail?id=' + encodeURIComponent(id) });
},
onExercise(e) {
const id = (e.detail && e.detail.id) || (e.currentTarget && e.currentTarget.dataset.id);
if (!id) return;
wx.navigateTo({ url: '/pages/detail/detail?id=' + encodeURIComponent(id) });
},
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)
});
}
});