This commit is contained in:
马梓彭 2026-05-09 17:26:32 +08:00
commit 2b3a08e499
15 changed files with 245 additions and 47 deletions

View File

@ -105,19 +105,31 @@ Page({
});
},
onChooseAvatar() {
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePath = res.tempFiles[0].tempFilePath;
this.setData({
'profile.avatar': tempFilePath
});
// TODO: 上传头像到服务器
}
});
onChooseAvatar(e) {
const tempFilePath = e.detail.avatarUrl;
if (!tempFilePath) return;
// 先展示本地预览
// this.setData({ 'profile.avatar': tempFilePath });
wx.showLoading({ title: '上传中...', mask: true });
auth
.uploadFile(tempFilePath)
.then((url) => {
return auth.updateProfile({ "avatarUrl": url }).then(() => url);
})
.then((url) => {
this.setData({ 'profile.avatar': url });
wx.showToast({ title: '头像更新成功', icon: 'success' });
})
.catch((err) => {
wx.showToast({ title: (err && err.message) || '头像更新失败', icon: 'none' });
// 回滚预览,重新加载 profile
this.loadProfile();
})
.finally(() => {
wx.hideLoading();
});
},
onFetchOpenId() {

View File

@ -15,13 +15,12 @@
</view>
<!-- 头像 + 手机号 -->
<view class="profile-section">
<view class="avatar-wrap" bindtap="onChooseAvatar">
<!-- profile.avatar || -->
<image class="avatar" src="https://q6.itc.cn/q_70/images03/20250306/355fba6a5cb049f5b98c2ed9f03cc5e1.jpeg" mode="aspectFill" />
<button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
<image class="avatar" src="{{profile.avatar || 'https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/event_miniapp/default-avatar.png'}}" mode="aspectFill" />
<view class="camera-icon">
<image src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/event_miniapp/camera.png" mode="aspectFit" />
</view>
</view>
</button>
<text class="phone-text">{{maskedPhone}}</text>
</view>
<!-- 内容区 -->

View File

@ -40,7 +40,7 @@
/* ====== 已登录页面 ====== */
.mine-page {
min-height: 100vh;
background: #f0f2f8;
background: #f5f6f8;
}
/* ====== 蓝色头部 ====== */
@ -78,10 +78,22 @@
margin-bottom: 32rpx;
}
.avatar-wrap {
.avatar-wrap,
.avatar-wrapper {
position: relative;
width: 168rpx;
height: 168rpx;
padding: 0;
margin: 0;
background: transparent;
border: none;
border-radius: 50%;
line-height: 1;
overflow: visible;
}
.avatar-wrapper::after {
border: none;
}
.avatar {
@ -94,8 +106,8 @@
.camera-icon {
position: absolute;
right: 0rpx;
bottom: 0rpx;
right: -4rpx;
bottom: -4rpx;
width: 48rpx;
height: 48rpx;
background: linear-gradient(135deg, #1a57b2 0%, #3a7bd5 100%);
@ -291,6 +303,8 @@
color: #1a1a1a;
font-weight: 500;
letter-spacing: 0.5rpx;
font-size: 26rpx;
margin-left: 10rpx;
}
.func-menu-arrow {
@ -310,7 +324,7 @@
line-height: 96rpx;
background: #fff;
color: #e53935;
font-size: 30rpx;
font-size: 28rpx;
font-weight: 500;
border-radius: 20rpx;
border: none;

View File

@ -83,7 +83,8 @@ Page({
authStatus: 0,
authStatusText: '未认证',
canSubmit: true,
submitBtnText: '提交认证'
submitBtnText: '提交认证',
loading: true
},
_timer: null,
@ -130,6 +131,7 @@ Page({
},
loadAuth() {
this.setData({ loading: true });
return Promise.all([
auth.fetchMyAuth().catch(() => ({})),
auth.fetchMyProfile().catch(() => ({}))
@ -162,10 +164,13 @@ Page({
canSubmit,
submitBtnText
});
}).finally(() => {
this.setData({ loading: false });
});
},
onChooseImage(e) {
if (this.data.authStatus === 2) return;
const kind = e.currentTarget.dataset.kind;
wx.chooseMedia({
count: 1,

View File

@ -9,7 +9,38 @@
<image class="stat-iconb" wx:if="{{activeTab === 2}}" src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/event_miniapp/point-b.png" mode="aspectFit" />
</view>
</view>
<view class="content">
<!-- 骨架屏 -->
<view wx:if="{{loading}}" class="skeleton-wrap">
<view class="skeleton-upload-card">
<view class="skeleton-upload-row">
<view class="skeleton-upload-box skeleton-anim"></view>
<view class="skeleton-upload-box skeleton-anim"></view>
</view>
</view>
<view class="skeleton-form-card">
<view class="skeleton-form-row">
<view class="skeleton-label skeleton-anim"></view>
<view class="skeleton-value skeleton-anim"></view>
</view>
<view class="skeleton-form-row">
<view class="skeleton-label skeleton-anim"></view>
<view class="skeleton-value skeleton-anim"></view>
</view>
<view class="skeleton-form-row">
<view class="skeleton-label skeleton-anim"></view>
<view class="skeleton-value skeleton-anim"></view>
</view>
<view class="skeleton-form-row">
<view class="skeleton-label skeleton-anim"></view>
<view class="skeleton-value skeleton-anim"></view>
</view>
<view class="skeleton-form-row">
<view class="skeleton-label skeleton-anim"></view>
<view class="skeleton-value skeleton-anim"></view>
</view>
</view>
</view>
<view wx:if="{{!loading}}" class="content">
<!-- 上传区 -->
<view class="card upload-card">
<view wx:if="{{activeTab === 1}}" class="upload-row">
@ -81,21 +112,21 @@
<text class="label">性别</text>
<input class="input readonly" value="{{genderOptions[genderIndex].label}}" disabled="{{true}}" />
</view>
<view class="form-row">
<view wx:if="{{authStatus !== 2}}" class="form-row">
<text class="label">联系电话</text>
<input class="input" disabled="{{true}}" value="{{mobile}}" placeholder="手机号" type="number" maxlength="11" />
<view class="send-code {{countdown>0 || codeSending ? 'disabled' : ''}}" bindtap="onSendCodeTap">
{{countdown>0 ? countdown + 's' : '发送验证码'}}
</view>
</view>
<view class="form-row">
<view wx:if="{{authStatus !== 2}}" class="form-row">
<text class="label">验证码</text>
<input class="input" value="{{verificationCode}}" placeholder="请输入验证码" type="number" maxlength="6" bindinput="onCodeInput" />
</view>
</view>
<view class="footer-space"></view>
</view>
<view class="footer">
<view wx:if="{{!loading}}" class="footer">
<button wx:if="{{canSubmit}}" class="submit-btn" loading="{{submitting}}" disabled="{{submitting}}" bindtap="onSubmitTap">
{{submitBtnText}}
</button>

View File

@ -1,5 +1,5 @@
page {
background: #f5f7fa;
background: #f5f6f8;
}
view{
@ -299,6 +299,71 @@ view{
border: 1rpx solid #e5e7eb;
}
/* ===== 骨架屏 ===== */
.skeleton-wrap {
padding: 0 20rpx;
}
.skeleton-upload-card {
background: #ffffff;
border-radius: 16rpx;
padding: 22rpx;
margin-bottom: 18rpx;
}
.skeleton-upload-row {
display: flex;
gap: 18rpx;
}
.skeleton-upload-box {
flex: 1;
height: 240rpx;
border-radius: 16rpx;
}
.skeleton-form-card {
background: #ffffff;
border-radius: 16rpx;
overflow: hidden;
}
.skeleton-form-row {
display: flex;
align-items: center;
padding: 26rpx 22rpx;
border-bottom: 1rpx solid #f3f4f6;
gap: 20rpx;
}
.skeleton-form-row:last-child {
border-bottom: none;
}
.skeleton-label {
width: 140rpx;
height: 32rpx;
border-radius: 8rpx;
flex-shrink: 0;
}
.skeleton-value {
flex: 1;
height: 32rpx;
border-radius: 8rpx;
}
@keyframes skeleton-shimmer {
0% { background-position: -750rpx 0; }
100% { background-position: 750rpx 0; }
}
.skeleton-anim {
background: linear-gradient(90deg, #ebebeb 25%, #f5f5f5 50%, #ebebeb 75%);
background-size: 750rpx 100%;
animation: skeleton-shimmer 1.4s infinite linear;
}
/* ===== OCR 识别中提示 ===== */
.ocr-tip {
display: flex;

View File

@ -54,7 +54,6 @@ Page({
loadProfile() {
if (this.data.loading) return;
this.setData({ loading: true });
auth
.fetchMyProfile()
@ -66,14 +65,14 @@ Page({
certificateCount: p.certificateCount != null ? p.certificateCount : '-'
});
})
.catch(() => {})
.finally(() => this.setData({ loading: false }));
}
,
.catch(() => {});
},
/** 重置并加载 */
resetAndLoad() {
this.setData({ records: [], pageNo: 1, noMore: false });
this.loadProfile();
return this.loadRecords();
},
@ -125,8 +124,7 @@ Page({
this.setData({
records: newRecords,
pageNo: pageNo + 1,
noMore: currentTotal >= total,
certificateCount: total
noMore: currentTotal >= total
});
})
.catch(() => {

View File

@ -196,6 +196,7 @@
align-items: center;
justify-content: center;
padding-top: 240rpx;
padding-bottom: 240rpx;
}
.empty-icon {

View File

@ -28,6 +28,7 @@ function formatOrder(item) {
Page({
data: {
loading: false,
skeleton: true,
noMore: false,
pageNo: 1,
total: 0,
@ -47,7 +48,7 @@ Page({
const pageNo = refresh ? 1 : this.data.pageNo;
if (!refresh && this.data.noMore) return;
this.setData({ loading: true });
this.setData({ loading: true, skeleton: refresh });
try {
const res = await auth.fetchMyOrders({ pageNo, pageSize: PAGE_SIZE });
const rows = (res && res.rows) || [];
@ -59,10 +60,12 @@ Page({
orders: newOrders,
pageNo: pageNo + 1,
total,
noMore
noMore,
skeleton: false
});
} catch (err) {
wx.showToast({ title: err?.message || '加载失败', icon: 'none' });
this.setData({ skeleton: false });
} finally {
this.setData({ loading: false });
}

View File

@ -1,13 +1,21 @@
<view class="order-page">
<!-- <mp-navigation-bar
title="我的订单"
background-color="#ffffff"
color="#333"
bind:click-left="onBack"
left-arrow
></mp-navigation-bar> -->
<!-- 骨架屏 -->
<view class="list" wx:if="{{skeleton}}">
<view class="order-card skeleton-card" wx:for="{{[1,2,3]}}" wx:key="*this">
<view class="card-row card-row--top">
<view class="skeleton-line skeleton-title"></view>
<view class="skeleton-line skeleton-status"></view>
</view>
<view class="card-row">
<view class="skeleton-line skeleton-time"></view>
</view>
<view class="card-row card-row--amount">
<view class="skeleton-line skeleton-amount"></view>
</view>
</view>
</view>
<view class="list">
<view class="list" wx:else>
<!-- 订单卡片列表 -->
<view
class="order-card"
@ -41,7 +49,10 @@
<!-- 加载更多 -->
<view class="list-footer" wx:if="{{orders.length > 0}}">
<text wx:if="{{noMore}}" class="footer-text">没有更多了</text>
<text wx:elif="{{loading}}" class="footer-text">加载中...</text>
<view wx:elif="{{loading}}" class="loading-more">
<view class="loading-spinner"></view>
<text class="footer-text">加载中...</text>
</view>
</view>
</view>
</view>

View File

@ -117,3 +117,47 @@
font-size: 24rpx;
color: #c0c4cc;
}
/* ===== 底部加载动画 ===== */
.loading-more {
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
}
.loading-spinner {
width: 32rpx;
height: 32rpx;
border: 4rpx solid #e0e0e0;
border-top-color: #07c160;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* ===== 骨架屏 ===== */
.skeleton-card {
overflow: hidden;
}
.skeleton-line {
height: 28rpx;
border-radius: 8rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
background-size: 400% 100%;
animation: skeleton-shimmer 1.4s ease infinite;
}
@keyframes skeleton-shimmer {
0% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.skeleton-title { width: 62%; }
.skeleton-status { width: 16%; height: 24rpx; }
.skeleton-time { width: 52%; height: 24rpx; }
.skeleton-amount { width: 38%; height: 24rpx; align-self: flex-end; }

View File

@ -24,6 +24,7 @@ Page({
},
onPullDownRefresh() {
if(this.data.loading) return;
this.loadProfile();
this.resetAndLoad().finally(() => wx.stopPullDownRefresh());
},

View File

@ -31,6 +31,7 @@ Page({
},
onPullDownRefresh() {
this.initData(true).finally(() => wx.stopPullDownRefresh());
},
@ -39,7 +40,8 @@ Page({
this.setData({
registrations: [],
pageNo: 1,
hasMore: true
hasMore: true,
registrationsLoadingText: '加载中...'
});
}
return this.loadRegistrations(reset ? 1 : this.data.pageNo, reset);

View File

@ -266,6 +266,16 @@ async function fetchMyProfile() {
});
}
async function updateProfile(data) {
return request({
path: API.updateProfile,
method: 'POST',
needAuth: true,
data: data,
contentType: 'application/json'
});
}
async function fetchMyAuth() {
return request({
path: API.myAuth,
@ -394,6 +404,7 @@ module.exports = {
registerWithVerification,
resetPassword,
fetchMyProfile,
updateProfile,
fetchMyAuth,
submitMyAuth,
fetchMyRegistrations,

View File

@ -27,6 +27,7 @@ const API = {
/** 小程序 wx.login 的 code 换 openid(按后端实际路径修改) */
wechatOpenidByJsCode: '/api/wechat/jscode2session',
myProfile: '/api/my/profile',
updateProfile: '/api/my/avatar/url',
myAuth: '/api/my/auth',
myRegistrations: '/api/my/registrations',
myPoints: '/api/my/points',