feat: 小程序连接服务器IP + 同步未提交开发
- miniprogram/config.js: BASE_URL 指向 http://192.227.237.8:3001 - 动作标题中文化(nameZh),搜索匹配 name/nameZh/nameEn - 新增计划/收藏/我的 等页面与组件 - 后端 Docker 化(Dockerfile/.dockerignore)与翻译脚本 - .gitignore 补充 .DS_Store
This commit is contained in:
129
miniprogram/pages/profile/profile.js
Normal file
129
miniprogram/pages/profile/profile.js
Normal file
@@ -0,0 +1,129 @@
|
||||
const app = getApp();
|
||||
const auth = require('../../utils/auth');
|
||||
const favSvc = require('../../services/favorite');
|
||||
const planSvc = require('../../services/plan');
|
||||
|
||||
const EMOJIS = ['💪', '🏋️', '🔥', '🏃', '🧘', '🤸', '🥊', '🚴', '⚡', '🌟', '🐯', '🦁'];
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 20,
|
||||
loggedIn: false,
|
||||
profile: { nickname: '', avatar: '💪' },
|
||||
counts: { fav: 0, plans: 0, favPlans: 0 },
|
||||
loading: false,
|
||||
// 编辑态
|
||||
showEmojiPicker: false,
|
||||
emojis: EMOJIS,
|
||||
draftNickname: '',
|
||||
draftAvatar: '💪'
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 20 });
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
loadData() {
|
||||
if (!auth.isLogin()) {
|
||||
this.setData({ loggedIn: false });
|
||||
return;
|
||||
}
|
||||
const profile = auth.getProfile();
|
||||
this.setData({ loggedIn: true, profile, loading: true });
|
||||
Promise.all([
|
||||
favSvc.listExercises().catch(() => []),
|
||||
planSvc.listMine().catch(() => []),
|
||||
favSvc.listPlans().catch(() => [])
|
||||
]).then(([fav, plans, favPlans]) => {
|
||||
this.setData({
|
||||
counts: {
|
||||
fav: (fav || []).length,
|
||||
plans: (plans || []).length,
|
||||
favPlans: (favPlans || []).length
|
||||
},
|
||||
loading: false
|
||||
});
|
||||
}).catch(() => {
|
||||
this.setData({ loading: false });
|
||||
});
|
||||
},
|
||||
|
||||
onLogin() {
|
||||
auth.login().then(() => {
|
||||
this.loadData();
|
||||
wx.showToast({ title: '登录成功', icon: 'success' });
|
||||
}).catch(() => {});
|
||||
},
|
||||
|
||||
onEdit() {
|
||||
const cur = this.data.profile;
|
||||
wx.showModal({
|
||||
title: '修改昵称',
|
||||
editable: true,
|
||||
placeholderText: '输入昵称',
|
||||
content: cur.nickname || '',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.setData({
|
||||
draftNickname: (res.content || '').trim() || cur.nickname,
|
||||
draftAvatar: cur.avatar || '💪',
|
||||
showEmojiPicker: true
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onPickEmoji(e) {
|
||||
this.setData({ draftAvatar: e.currentTarget.dataset.emoji });
|
||||
},
|
||||
|
||||
onCancelEdit() {
|
||||
this.setData({ showEmojiPicker: false });
|
||||
},
|
||||
|
||||
onConfirmEdit() {
|
||||
const nickname = (this.data.draftNickname || '').trim();
|
||||
const avatar = this.data.draftAvatar;
|
||||
if (!nickname) {
|
||||
wx.showToast({ title: '昵称不能为空', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
wx.showLoading({ title: '保存中…', mask: true });
|
||||
auth.updateProfile({ nickname, avatar }).then((p) => {
|
||||
wx.hideLoading();
|
||||
this.setData({ profile: p, showEmojiPicker: false });
|
||||
wx.showToast({ title: '已保存', icon: 'success' });
|
||||
}).catch(() => {
|
||||
wx.hideLoading();
|
||||
});
|
||||
},
|
||||
|
||||
onLogout() {
|
||||
wx.showModal({
|
||||
title: '退出登录',
|
||||
content: '确定要退出当前账号吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
auth.logout();
|
||||
this.setData({
|
||||
loggedIn: false,
|
||||
profile: { nickname: '', avatar: '💪' },
|
||||
counts: { fav: 0, plans: 0, favPlans: 0 }
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
goFav() { wx.navigateTo({ url: '/pages/favorites/favorites' }); },
|
||||
goPlans() { wx.navigateTo({ url: '/pages/my-plans/my-plans' }); },
|
||||
goFavPlans() { wx.navigateTo({ url: '/pages/plan-favorites/plan-favorites' }); },
|
||||
goOfficial() { wx.navigateTo({ url: '/pages/collection/collection' }); },
|
||||
|
||||
noop() {}
|
||||
});
|
||||
8
miniprogram/pages/profile/profile.json
Normal file
8
miniprogram/pages/profile/profile.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"navbar": "/components/navbar/index",
|
||||
"avatar": "/components/avatar/index",
|
||||
"bottom-nav": "/components/bottom-nav/index"
|
||||
}
|
||||
}
|
||||
87
miniprogram/pages/profile/profile.wxml
Normal file
87
miniprogram/pages/profile/profile.wxml
Normal file
@@ -0,0 +1,87 @@
|
||||
<view class="page">
|
||||
<navbar title="我的" />
|
||||
|
||||
<!-- 未登录 -->
|
||||
<view wx:if="{{!loggedIn}}" class="login">
|
||||
<view class="login__logo">💪</view>
|
||||
<view class="login__title">FITCOACH 智能健身教练</view>
|
||||
<view class="login__sub">登录后同步你的收藏与训练计划</view>
|
||||
<button class="login__btn" bindtap="onLogin">微信登录</button>
|
||||
</view>
|
||||
|
||||
<!-- 已登录 -->
|
||||
<view wx:else class="center">
|
||||
<view class="profile">
|
||||
<view class="profile__head">
|
||||
<avatar avatar="{{profile.avatar}}" size="{{120}}" />
|
||||
<view class="profile__info">
|
||||
<view class="profile__name">{{profile.nickname || '健身达人'}}</view>
|
||||
<view class="profile__edit" bindtap="onEdit">编辑 ›</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="stats">
|
||||
<view class="stat" bindtap="goFav">
|
||||
<view class="stat__n">{{counts.fav}}</view>
|
||||
<view class="stat__l">收藏动作</view>
|
||||
</view>
|
||||
<view class="stat" bindtap="goPlans">
|
||||
<view class="stat__n">{{counts.plans}}</view>
|
||||
<view class="stat__l">我的计划</view>
|
||||
</view>
|
||||
<view class="stat" bindtap="goFavPlans">
|
||||
<view class="stat__n">{{counts.favPlans}}</view>
|
||||
<view class="stat__l">收藏计划</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu card">
|
||||
<view class="cell" bindtap="goFav">
|
||||
<text class="cell__icon">❤️</text>
|
||||
<text class="cell__label">我的收藏动作</text>
|
||||
<text class="cell__arrow">›</text>
|
||||
</view>
|
||||
<view class="cell" bindtap="goPlans">
|
||||
<text class="cell__icon">🗂️</text>
|
||||
<text class="cell__label">我的训练计划</text>
|
||||
<text class="cell__arrow">›</text>
|
||||
</view>
|
||||
<view class="cell" bindtap="goFavPlans">
|
||||
<text class="cell__icon">⭐</text>
|
||||
<text class="cell__label">收藏的官方计划</text>
|
||||
<text class="cell__arrow">›</text>
|
||||
</view>
|
||||
<view class="cell" bindtap="goOfficial">
|
||||
<text class="cell__icon">📋</text>
|
||||
<text class="cell__label">官方训练计划</text>
|
||||
<text class="cell__arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="logout" bindtap="onLogout">退出登录</view>
|
||||
</view>
|
||||
|
||||
<!-- 编辑头像弹层 -->
|
||||
<view wx:if="{{showEmojiPicker}}" class="sheet" bindtap="onCancelEdit">
|
||||
<view class="sheet__panel" catchtap="noop">
|
||||
<view class="sheet__title">选择头像</view>
|
||||
<scroll-view scroll-x class="emoji-row">
|
||||
<view
|
||||
wx:for="{{emojis}}"
|
||||
wx:key="*this"
|
||||
class="emoji {{draftAvatar === item ? 'emoji--active' : ''}}"
|
||||
data-emoji="{{item}}"
|
||||
bindtap="onPickEmoji">{{item}}</view>
|
||||
</scroll-view>
|
||||
<view class="sheet__nick">{{draftNickname || '未命名'}}</view>
|
||||
<view class="sheet__actions">
|
||||
<view class="sheet__btn sheet__btn--ghost" bindtap="onCancelEdit">取消</view>
|
||||
<view class="sheet__btn sheet__btn--gold" bindtap="onConfirmEdit">保存</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view style="height: 160rpx;"></view>
|
||||
<bottom-nav active="mine" />
|
||||
</view>
|
||||
84
miniprogram/pages/profile/profile.wxss
Normal file
84
miniprogram/pages/profile/profile.wxss
Normal file
@@ -0,0 +1,84 @@
|
||||
.page { min-height: 100vh; background: var(--bg-grad); }
|
||||
|
||||
/* 未登录 */
|
||||
.login {
|
||||
min-height: 70vh; display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center; padding: 60rpx;
|
||||
}
|
||||
.login__logo {
|
||||
width: 160rpx; height: 160rpx; border-radius: 999rpx;
|
||||
background: var(--gold-soft); border: 1rpx solid var(--gold-line);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 88rpx;
|
||||
}
|
||||
.login__title { font-size: 38rpx; font-weight: 700; margin-top: 32rpx; color: var(--text); }
|
||||
.login__sub { font-size: 26rpx; color: var(--text-2); margin-top: 14rpx; }
|
||||
.login__btn {
|
||||
margin-top: 56rpx; width: 420rpx;
|
||||
background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%);
|
||||
color: #0E1110; font-weight: 700; font-size: 32rpx;
|
||||
border-radius: 999rpx; padding: 24rpx;
|
||||
}
|
||||
.login__btn::after { border: none; }
|
||||
|
||||
/* 个人中心 */
|
||||
.center { padding: 32rpx; }
|
||||
.profile {
|
||||
background: linear-gradient(135deg, rgba(217,179,108,0.14), var(--surface));
|
||||
border: 1rpx solid var(--gold-line);
|
||||
border-radius: var(--radius); padding: 36rpx;
|
||||
}
|
||||
.profile__head { display: flex; align-items: center; gap: 28rpx; }
|
||||
.profile__info { flex: 1; min-width: 0; }
|
||||
.profile__name { font-size: 40rpx; font-weight: 700; color: var(--gold); }
|
||||
.profile__edit { font-size: 26rpx; color: var(--text-2); margin-top: 10rpx; }
|
||||
|
||||
.stats { display: flex; margin-top: 36rpx; }
|
||||
.stat { flex: 1; display: flex; flex-direction: column; align-items: center; }
|
||||
.stat + .stat { border-left: 1rpx solid var(--line); }
|
||||
.stat__n { font-size: 44rpx; font-weight: 700; color: var(--text); }
|
||||
.stat__l { font-size: 24rpx; color: var(--text-2); margin-top: 8rpx; }
|
||||
|
||||
.menu { margin-top: 28rpx; padding: 8rpx 28rpx; }
|
||||
.cell {
|
||||
display: flex; align-items: center; gap: 24rpx;
|
||||
padding: 30rpx 0; border-bottom: 1rpx solid var(--line);
|
||||
}
|
||||
.cell:last-child { border-bottom: none; }
|
||||
.cell__icon { font-size: 38rpx; width: 48rpx; text-align: center; }
|
||||
.cell__label { flex: 1; font-size: 30rpx; color: var(--text); }
|
||||
.cell__arrow { font-size: 40rpx; color: var(--gold); line-height: 1; }
|
||||
|
||||
.logout {
|
||||
margin-top: 40rpx; text-align: center;
|
||||
font-size: 28rpx; color: var(--text-3);
|
||||
}
|
||||
|
||||
/* 头像选择弹层 */
|
||||
.sheet {
|
||||
position: fixed; inset: 0; z-index: 80;
|
||||
background: rgba(0,0,0,0.5);
|
||||
display: flex; align-items: flex-end;
|
||||
}
|
||||
.sheet__panel {
|
||||
width: 100%; background: var(--surface);
|
||||
border-top-left-radius: 32rpx; border-top-right-radius: 32rpx;
|
||||
padding: 36rpx 32rpx calc(36rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
.sheet__title { font-size: 30rpx; font-weight: 700; color: var(--text); }
|
||||
.emoji-row { white-space: nowrap; margin-top: 24rpx; }
|
||||
.emoji {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 88rpx; height: 88rpx; margin-right: 16rpx;
|
||||
border-radius: 20rpx; background: var(--surface-2);
|
||||
border: 1rpx solid var(--line); font-size: 48rpx;
|
||||
}
|
||||
.emoji--active { border-color: var(--gold); background: var(--gold-soft); }
|
||||
.sheet__nick { margin-top: 24rpx; font-size: 28rpx; color: var(--text-2); text-align: center; }
|
||||
.sheet__actions { display: flex; gap: 20rpx; margin-top: 28rpx; }
|
||||
.sheet__btn {
|
||||
flex: 1; text-align: center; padding: 24rpx; border-radius: 999rpx;
|
||||
font-size: 30rpx; font-weight: 700;
|
||||
}
|
||||
.sheet__btn--ghost { background: var(--surface-2); color: var(--text); border: 1rpx solid var(--line); }
|
||||
.sheet__btn--gold { background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); color: #0E1110; }
|
||||
Reference in New Issue
Block a user