feat(app): 添加课程页面并优化打卡和计划功能

- 新增 pages/course/course 页面,集成 web-view 显示线上课程列表
- 在 app.json 中注册课程页面并添加底部 tabbar 导航
- 优化打卡页面,添加学号显示和改进成员列表布局
- 重构计划页面,添加内容详情弹层和优化加载逻辑
- 添加阿里云 OSS 上传工具模块用于文件上传功能
- 统一姓名和学号在同一行显示,改善 UI 体验
- 修复多个页面中的绑定事件和样式问题
This commit is contained in:
王宏豪
2026-09-20 16:53:36 +08:00
parent 01a7f4ab2d
commit ee3abed69f
13 changed files with 701 additions and 81 deletions

103
pages/course/course.js Normal file
View File

@@ -0,0 +1,103 @@
// 课程中心:全屏 web-view 加载线上课程列表
//
// 注意:课程页部署在 www.muguaclub.cn,与首页 H5 的 m.muguaclub.cn 是不同主机名,
// www 必须单独加入微信公众平台「开发设置 → 业务域名」并在该站点根目录放校验文件,
// 否则真机打开会白屏。
// 登录态:课程页本体是公开的,但产品要求「未登录不进课程页」——未登录时切到「我的」引导登录,
// 因此也不需要走 webview/redirect 换 cookie。
const auth = require('../../utils/auth');
const COURSE_URL = 'https://www.muguaclub.cn/courses/index.html';
Page({
data: {
webviewUrl: '',
webviewError: false,
errorMsg: '',
webviewKey: 0
},
_webviewKey: 0,
_redirecting: false,
onShow() {
this.ensureAccess();
},
// 未登录不进课程页,直接切到「我的」引导登录
ensureAccess() {
if (auth.getToken()) {
this.loadWebview();
return;
}
// token 可能还在 App.onLaunch 发起的登录流程里,复用同一个登录单例再判一次,避免冷启动误跳
return auth.ensureLogin()
.then((token) => {
if (token) {
this.loadWebview();
return;
}
this.denyAccess();
})
.catch(() => {
this.denyAccess();
});
},
// 清掉可能残留的 web-view(如退出登录后切回本 tab),再切到「我的」
denyAccess() {
if (this.data.webviewUrl) {
this.setData({ webviewUrl: '', webviewError: false, errorMsg: '' });
}
if (this._redirecting) {
return;
}
this._redirecting = true;
wx.switchTab({
url: '/pages/profile/profile',
complete: () => {
this._redirecting = false;
}
});
},
loadWebview() {
// key 自增 + 时间戳:强制 web-view 销毁重建,避免切 tab 回来白屏或读到上一次的缓存
this._webviewKey += 1;
this.setData({
webviewUrl: COURSE_URL + '?_t=' + Date.now(),
webviewError: false,
errorMsg: '',
webviewKey: this._webviewKey
});
},
onWebviewError(e) {
this.setData({
webviewError: true,
errorMsg: (e && e.detail && e.detail.errMsg) || '加载失败,请检查网络后重试'
});
},
onRetry() {
this.setData({ webviewUrl: '', webviewError: false, errorMsg: '' });
setTimeout(() => {
this.loadWebview();
}, 60);
},
// ---- 分享 ----
onShareAppMessage() {
return {
title: '明缘云道场 · 全部课程',
path: '/pages/course/course'
};
},
onShareTimeline() {
return {
title: '明缘云道场 · 全部课程',
query: 'from=timeline'
};
}
});

4
pages/course/course.json Normal file
View File

@@ -0,0 +1,4 @@
{
"usingComponents": {},
"navigationBarTitleText": "全部课程"
}

9
pages/course/course.wxml Normal file
View File

@@ -0,0 +1,9 @@
<view class="page">
<web-view src="{{webviewUrl}}" wx:if="{{webviewUrl && !webviewError}}" key="{{webviewKey}}" binderror="onWebviewError"></web-view>
<view class="error-page" wx:if="{{webviewError}}">
<view class="error-icon">!</view>
<view class="error-title">页面加载失败</view>
<view class="error-desc">{{errorMsg}}</view>
<view class="error-retry" hover-class="btn-hover" bindtap="onRetry">重新加载</view>
</view>
</view>

51
pages/course/course.wxss Normal file
View File

@@ -0,0 +1,51 @@
.page {
min-height: 100vh;
background: #FDFBF7;
}
/* ---- 加载失败兜底(web-view 为原生组件,仅在不渲染时才展示) ---- */
.error-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 60rpx;
height: 100vh;
box-sizing: border-box;
}
.error-icon {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
background: #FDEAEA;
color: #C0392B;
font-size: 64rpx;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 32rpx;
}
.error-title {
font-size: 32rpx;
font-weight: 700;
color: #2C1810;
margin-bottom: 16rpx;
}
.error-desc {
font-size: 26rpx;
color: #8B7A6A;
text-align: center;
line-height: 1.6;
margin-bottom: 40rpx;
}
.error-retry {
padding: 20rpx 60rpx;
background: #C67B30;
color: #fff;
font-size: 28rpx;
border-radius: 999rpx;
}
.btn-hover {
opacity: 0.7;
}