feat(home): 实现游客模式与登录流程优化
- 添加游客模式,展示公共内容无需强制登录 - 实现游客模块展示打卡、计划、关于我们等功能介绍 - 添加产品介绍页面显示学习成长工具详情 - 优化登录流程,添加用户协议和隐私政策同意选项 - 更新角色标签将老师改为管理员,学员改为成员 - 添加注销账号功能替代退出登录 - 实现webview页面域名白名单安全验证 - 添加隐私政策和用户协议页面 - 优化页面跳转逻辑和错误提示信息 - 统一项目名称从明缘进阶场改为明缘云道场
This commit is contained in:
@@ -72,7 +72,7 @@
|
||||
</view>
|
||||
<view class="empty" wx:if="{{!groupName}}">
|
||||
<view class="empty-icon">👥</view>
|
||||
<view class="empty-text">暂无小组,请联系老师分配</view>
|
||||
<view class="empty-text">暂无小组,请联系管理员分配</view>
|
||||
</view>
|
||||
<view class="empty" wx:elif="{{group.length === 0}}">
|
||||
<view class="empty-icon">📭</view>
|
||||
|
||||
@@ -16,6 +16,7 @@ Page({
|
||||
displayName: '',
|
||||
encouragement: '',
|
||||
modules: [],
|
||||
introData: null,
|
||||
loading: true,
|
||||
error: '',
|
||||
skeletonItems: [0, 1, 2, 3, 4, 5, 6, 7, 8]
|
||||
@@ -27,12 +28,32 @@ Page({
|
||||
if (!silent) {
|
||||
this.setData({ loading: true, error: '' });
|
||||
}
|
||||
const token = auth.getToken();
|
||||
if (!token) {
|
||||
// 游客模式:展示公开内容,不强制登录
|
||||
this.setData({
|
||||
loading: false,
|
||||
error: '',
|
||||
greeting: '欢迎来到明缘云道场',
|
||||
displayName: '游客',
|
||||
encouragement: '登录后即可打卡、提交计划',
|
||||
modules: this.getGuestModules(),
|
||||
introData: this.getIntroData()
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
return auth.ensureLogin()
|
||||
.then((token) => {
|
||||
if (!token) {
|
||||
// 新用户未绑定手机号,引导去「我的」完成绑定,避免无 token 请求受保护接口导致 401
|
||||
this.setData({ loading: false, error: '请先完成微信绑定后再进入' });
|
||||
setTimeout(() => wx.switchTab({ url: '/pages/profile/profile' }), 500);
|
||||
this.setData({
|
||||
loading: false,
|
||||
error: '',
|
||||
greeting: '欢迎来到明缘云道场',
|
||||
displayName: '游客',
|
||||
encouragement: '登录后即可打卡、提交计划',
|
||||
modules: this.getGuestModules(),
|
||||
introData: this.getIntroData()
|
||||
});
|
||||
return;
|
||||
}
|
||||
return api.request({ url: '/api/v1/pages/home' });
|
||||
@@ -45,7 +66,8 @@ Page({
|
||||
greeting: res.greeting || '',
|
||||
displayName: res.display_name || '',
|
||||
encouragement: res.encouragement || '',
|
||||
modules: (res.modules || []).filter((m) => m.visible)
|
||||
modules: (res.modules || []).filter((m) => m.visible),
|
||||
introData: null
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -53,6 +75,42 @@ Page({
|
||||
});
|
||||
},
|
||||
|
||||
getGuestModules() {
|
||||
return [
|
||||
{ id: 'checkin', icon: '📋', label: '打卡介绍', desc: '每日打卡记录', visible: true, url: '', guest: true },
|
||||
{ id: 'plan', icon: '📝', label: '计划介绍', desc: '周/月/年计划', visible: true, url: '', guest: true },
|
||||
{ id: 'about', icon: 'ℹ️', label: '关于我们', desc: '了解明缘云道场', visible: true, url: '', guest: true }
|
||||
];
|
||||
},
|
||||
|
||||
getIntroData() {
|
||||
return {
|
||||
title: '产品介绍',
|
||||
sections: [
|
||||
{
|
||||
icon: '📚',
|
||||
heading: '学习成长工具',
|
||||
text: '明缘云道场是一款专注于个人成长的效率工具,通过每周打卡与计划管理,帮助你养成良好的学习习惯,实现持续进步。好好学习,天天向上。'
|
||||
},
|
||||
{
|
||||
icon: '✅',
|
||||
heading: '周打卡',
|
||||
text: '每周打卡,支持多种状态记录(正常、迟到、请假、早退、不参加讨论等)。查看小组打卡情况,与同学们互相监督、共同进步。'
|
||||
},
|
||||
{
|
||||
icon: '📝',
|
||||
heading: '计划管理',
|
||||
text: '提供周计划、月计划、年计划三级管理,支持文字与图片上传。清晰追踪每个周期的目标完成情况,让成长有迹可循。'
|
||||
},
|
||||
{
|
||||
icon: '👥',
|
||||
heading: '小组共学',
|
||||
text: '加入学习小组,查看组员的打卡与计划完成情况。在集体的氛围中保持学习动力,与志同道合的伙伴一起成长。'
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.loadHome(true).then(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
@@ -63,12 +121,37 @@ Page({
|
||||
this.loadHome();
|
||||
},
|
||||
|
||||
goLogin() {
|
||||
wx.switchTab({ url: '/pages/profile/profile' });
|
||||
},
|
||||
|
||||
onModuleTap(e) {
|
||||
const url = e.currentTarget.dataset.url;
|
||||
if (!url) return;
|
||||
const token = auth.getToken();
|
||||
const sep = url.indexOf('?') >= 0 ? '&' : '?';
|
||||
const target = url + sep + 'token=' + encodeURIComponent(token);
|
||||
wx.navigateTo({ url: '/pages/webview/webview?url=' + encodeURIComponent(target) });
|
||||
// 游客模式:引导登录
|
||||
if (!token) {
|
||||
wx.showModal({
|
||||
title: '登录提示',
|
||||
content: '登录后使用此功能,是否前往登录?',
|
||||
confirmText: '去登录',
|
||||
confirmColor: '#C67B30',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
wx.switchTab({ url: '/pages/profile/profile' });
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!url) return;
|
||||
|
||||
// 通过 webview redirect 接口:后端验证 token 后设置 HttpOnly cookie,再 302 到目标页
|
||||
// H5 页面内的同域请求会自动携带 cookie,H5 端零改动
|
||||
const redirectUrl = api.BASE_URL + '/api/v1/webview/redirect'
|
||||
+ '?token=' + encodeURIComponent(token)
|
||||
+ '&target=' + encodeURIComponent(url);
|
||||
wx.navigateTo({ url: '/pages/webview/webview?url=' + encodeURIComponent(redirectUrl) });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
<view class="hero-chip" wx:if="{{greeting}}">{{greeting}}</view>
|
||||
<view class="hero-name">{{displayName || '欢迎回来'}}</view>
|
||||
<view class="hero-enc" wx:if="{{encouragement}}">{{encouragement}}</view>
|
||||
<view class="hero-login-tip" wx:if="{{!displayName || displayName === '游客'}}" hover-class="btn-hover" bindtap="goLogin">
|
||||
登录账号解锁完整功能 ›
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="error-bar" wx:if="{{error}}">
|
||||
@@ -37,6 +40,18 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 游客模式:明缘介绍 -->
|
||||
<view class="intro-section" wx:if="{{introData && introData.sections}}">
|
||||
<view class="intro-title">{{introData.title}}</view>
|
||||
<view class="intro-card" wx:for="{{introData.sections}}" wx:key="heading">
|
||||
<view class="intro-icon">{{item.icon}}</view>
|
||||
<view class="intro-body">
|
||||
<view class="intro-heading">{{item.heading}}</view>
|
||||
<view class="intro-text">{{item.text}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="empty" wx:if="{{modules.length === 0 && !error}}">暂无功能入口</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -153,4 +153,56 @@
|
||||
@keyframes shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
.hero-login-tip {
|
||||
margin-top: 24rpx;
|
||||
display: inline-block;
|
||||
padding: 12rpx 28rpx;
|
||||
background: rgba(198, 123, 48, 0.25);
|
||||
border: 1rpx solid rgba(227, 179, 124, 0.4);
|
||||
border-radius: 999rpx;
|
||||
color: #E3B37C;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
/* ---- intro section ---- */
|
||||
.intro-section {
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
.intro-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #2C1810;
|
||||
margin-bottom: 20rpx;
|
||||
padding-left: 4rpx;
|
||||
}
|
||||
.intro-card {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
.intro-icon {
|
||||
font-size: 40rpx;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
line-height: 1;
|
||||
padding-top: 2rpx;
|
||||
}
|
||||
.intro-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.intro-heading {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #C67B30;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
.intro-text {
|
||||
font-size: 25rpx;
|
||||
color: #5A4A3A;
|
||||
line-height: 1.8;
|
||||
}
|
||||
@@ -5,7 +5,6 @@ const MAX_IMAGES = 4;
|
||||
const MAX_IMAGE_SIZE = 1024 * 1024; // 超过 1MB 才压缩
|
||||
const COMPRESS_LEVELS = [80, 60, 45]; // 逐级降低质量,直到满足大小限制
|
||||
const ALLOWED_IMAGE_EXT = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'];
|
||||
const IMAGE_PLACEHOLDER_URL = 'https://picsum.photos/200/200';
|
||||
const TYPE_LABELS = { weekly: '周计划', monthly: '月计划', yearly: '年计划' };
|
||||
|
||||
function errDetail(err) {
|
||||
@@ -136,7 +135,7 @@ Page({
|
||||
if (/^https?:\/\//i.test(path)) return path;
|
||||
const parts = String(path).replace(/^attachments\//, '').split('/');
|
||||
if (parts.length < 4) {
|
||||
return IMAGE_PLACEHOLDER_URL;
|
||||
return '';
|
||||
}
|
||||
return api.STATIC_BASE_URL + '/clock/static/plan/' + parts.slice(0, 4).map(encodeURIComponent).join('/');
|
||||
},
|
||||
|
||||
@@ -27,7 +27,8 @@ Page({
|
||||
onShow() {
|
||||
auth.ensureLogin().then((token) => {
|
||||
if (!token) {
|
||||
wx.switchTab({ url: '/pages/profile/profile' });
|
||||
wx.showToast({ title: '请先登录', icon: 'none' });
|
||||
setTimeout(() => wx.switchTab({ url: '/pages/profile/profile' }), 500);
|
||||
return;
|
||||
}
|
||||
wx.removeStorageSync('plan_overview_dirty');
|
||||
|
||||
18
pages/privacy/privacy.js
Normal file
18
pages/privacy/privacy.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const TITLES = {
|
||||
privacy: '隐私政策',
|
||||
agreement: '用户协议'
|
||||
};
|
||||
|
||||
Page({
|
||||
data: {
|
||||
type: 'privacy',
|
||||
title: '隐私政策'
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const type = options.type || 'privacy';
|
||||
const title = TITLES[type] || '隐私政策';
|
||||
this.setData({ type, title });
|
||||
wx.setNavigationBarTitle({ title });
|
||||
}
|
||||
});
|
||||
4
pages/privacy/privacy.json
Normal file
4
pages/privacy/privacy.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "隐私政策",
|
||||
"usingComponents": {}
|
||||
}
|
||||
133
pages/privacy/privacy.wxml
Normal file
133
pages/privacy/privacy.wxml
Normal file
@@ -0,0 +1,133 @@
|
||||
<view class="page">
|
||||
<scroll-view class="content" scroll-y>
|
||||
|
||||
<!-- 隐私政策 -->
|
||||
<block wx:if="{{type === 'privacy'}}">
|
||||
<view class="section">
|
||||
<view class="section-title">一、信息收集范围</view>
|
||||
<view class="section-text">
|
||||
为了向你提供打卡、计划管理等服务,我们会收集以下信息:
|
||||
</view>
|
||||
<view class="section-text">1. 手机号:用于账号注册与登录,验证你的身份;</view>
|
||||
<view class="section-text">2. 头像与昵称:用于个人资料展示;</view>
|
||||
<view class="section-text">3. 你主动提交的打卡记录、计划内容及附件图片;</view>
|
||||
<view class="section-text">4. 设备信息与日志数据:用于排查故障、优化服务。</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">二、信息使用目的</view>
|
||||
<view class="section-text">我们收集的信息仅用于以下目的:</view>
|
||||
<view class="section-text">1. 提供打卡、计划管理、小组统计等核心功能;</view>
|
||||
<view class="section-text">2. 账号安全验证与异常登录保护;</view>
|
||||
<view class="section-text">3. 改进产品体验与服务质量。</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">三、信息存储与安全</view>
|
||||
<view class="section-text">
|
||||
你的个人信息存储于境内服务器,我们采取合理的技术措施保护你的信息安全,包括加密传输、访问控制等。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">四、第三方共享</view>
|
||||
<view class="section-text">
|
||||
我们不会向第三方出售或出租你的个人信息。仅在以下情况下可能共享:
|
||||
</view>
|
||||
<view class="section-text">1. 获得你的明确同意;</view>
|
||||
<view class="section-text">2. 法律法规要求或司法机关依法定程序要求;</view>
|
||||
<view class="section-text">3. 为完成服务所必需的合作方(如云存储服务),且签订了保密协议。</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">五、你的权利</view>
|
||||
<view class="section-text">你有权访问、更正、删除你的个人信息,以及注销账号。你可以通过以下方式行使权利:</view>
|
||||
<view class="section-text">1. 在「我的」页面中查看和修改个人资料;</view>
|
||||
<view class="section-text">2. 在「我的」页面底部申请注销账号;</view>
|
||||
<view class="section-text">3. 联系客服协助处理。</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">六、未成年人保护</view>
|
||||
<view class="section-text">
|
||||
若你是未满 14 周岁的未成年人,请在监护人陪同下阅读本政策,并在监护人同意的情况下使用我们的服务。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">七、联系方式</view>
|
||||
<view class="section-text">如有任何疑问或投诉,请通过以下方式联系我们:</view>
|
||||
<view class="section-text">客服邮箱:[此处填写客服邮箱]</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">八、政策更新</view>
|
||||
<view class="section-text">
|
||||
我们可能会适时更新本隐私政策,更新后的政策将在本页面公布。重大变更会通过小程序内通知等方式告知你。
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 用户协议 -->
|
||||
<block wx:if="{{type === 'agreement'}}">
|
||||
<view class="section">
|
||||
<view class="section-title">一、服务说明</view>
|
||||
<view class="section-text">
|
||||
明缘云道场小程序(以下简称"本小程序")为用户提供打卡记录、计划管理、小组统计等学习辅助服务。使用本小程序即表示你同意本协议的全部条款。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">二、账号注册与使用</view>
|
||||
<view class="section-text">1. 你需通过手机号验证注册账号,并确保所提供信息真实准确;</view>
|
||||
<view class="section-text">2. 你应妥善保管账号信息,对账号下的所有行为负责;</view>
|
||||
<view class="section-text">3. 如发现账号异常,请及时联系客服处理。</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">三、用户行为规范</view>
|
||||
<view class="section-text">你在使用本服务时,不得从事以下行为:</view>
|
||||
<view class="section-text">1. 发布违法、违规、侵犯他人权益的内容;</view>
|
||||
<view class="section-text">2. 恶意刷量、作弊、伪造打卡记录;</view>
|
||||
<view class="section-text">3. 干扰或破坏本服务的正常运行;</view>
|
||||
<view class="section-text">4. 未经授权访问他人账号或数据。</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">四、内容与知识产权</view>
|
||||
<view class="section-text">
|
||||
你上传的内容(包括文字、图片等)的知识产权归你所有。你授予本小程序为提供服务所必需的使用权限。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">五、服务变更与终止</view>
|
||||
<view class="section-text">
|
||||
我们可能根据业务需要调整或终止部分服务,并会提前通知用户。如因你违反本协议,我们有权暂停或终止对你的服务。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">六、免责声明</view>
|
||||
<view class="section-text">
|
||||
因不可抗力、系统故障、网络原因等不可归责于我方的事由导致服务中断或数据丢失的,我们不承担责任,但会尽力协助恢复。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">七、协议修改</view>
|
||||
<view class="section-text">
|
||||
我们可能根据需要修改本协议,修改后的协议将在本页面公布。继续使用服务即视为同意修改后的协议。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">八、联系方式</view>
|
||||
<view class="section-text">如有疑问,请联系客服:</view>
|
||||
<view class="section-text">客服邮箱:[此处填写客服邮箱]</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view class="footer">最后更新日期:2026年9月</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
40
pages/privacy/privacy.wxss
Normal file
40
pages/privacy/privacy.wxss
Normal file
@@ -0,0 +1,40 @@
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #FDFBF7;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content {
|
||||
height: 100vh;
|
||||
padding: 32rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #2C1810;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.section-text {
|
||||
font-size: 26rpx;
|
||||
color: #5A4A3A;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
color: #B6A996;
|
||||
font-size: 22rpx;
|
||||
padding: 40rpx 0 60rpx;
|
||||
}
|
||||
@@ -2,15 +2,15 @@ const api = require('../../utils/api');
|
||||
const auth = require('../../utils/auth');
|
||||
|
||||
const ROLE_LABELS = {
|
||||
student: '学员',
|
||||
student: '成员',
|
||||
member: '成员',
|
||||
teacher: '老师',
|
||||
teacher: '管理员',
|
||||
leader: '组长',
|
||||
'group_leader': '组长',
|
||||
admin: '管理员'
|
||||
};
|
||||
const LOGIN_TYPE_LABELS = {
|
||||
wechat: '微信',
|
||||
wechat: '微信授权',
|
||||
phone: '手机号',
|
||||
admin: '管理员'
|
||||
};
|
||||
@@ -27,11 +27,15 @@ Page({
|
||||
roleLabel: '',
|
||||
loginTypeLabel: '',
|
||||
classGroups: [],
|
||||
avatarUploading: false
|
||||
avatarUploading: false,
|
||||
privacyAgreed: false
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.setData({ phoneError: '' });
|
||||
this.setData({
|
||||
phoneError: '',
|
||||
privacyAgreed: !!wx.getStorageSync('privacy_agreed')
|
||||
});
|
||||
this.loadPage();
|
||||
},
|
||||
|
||||
@@ -114,16 +118,17 @@ Page({
|
||||
showPhoneLogin() {
|
||||
this.setData({
|
||||
view: 'phoneLogin',
|
||||
phoneLoading: false
|
||||
phoneLoading: false,
|
||||
privacyAgreed: !!wx.getStorageSync('privacy_agreed')
|
||||
});
|
||||
},
|
||||
|
||||
roleLabel(role) {
|
||||
return ROLE_LABELS[role] || role || '学员';
|
||||
return ROLE_LABELS[role] || role || '成员';
|
||||
},
|
||||
|
||||
loginTypeLabel(type) {
|
||||
return LOGIN_TYPE_LABELS[type] || type || '微信';
|
||||
return LOGIN_TYPE_LABELS[type] || type || '手机号';
|
||||
},
|
||||
|
||||
fullUrl(rel) {
|
||||
@@ -138,7 +143,6 @@ Page({
|
||||
|
||||
onChooseAvatar(e) {
|
||||
const path = e.detail && e.detail.avatarUrl;
|
||||
console.log('[avatar] chooseAvatar ->', path);
|
||||
if (!path) {
|
||||
wx.showToast({ title: '未选择头像', icon: 'none' });
|
||||
return;
|
||||
@@ -150,7 +154,6 @@ Page({
|
||||
name: 'file',
|
||||
header: { Authorization: 'Bearer ' + auth.getToken() },
|
||||
success: (res) => {
|
||||
console.log('[avatar] upload success', res.statusCode, res.data);
|
||||
let data = {};
|
||||
try {
|
||||
data = JSON.parse(res.data);
|
||||
@@ -170,8 +173,6 @@ Page({
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('[avatar] upload fail', err);
|
||||
console.error('[avatar] filePath', path);
|
||||
wx.showToast({ title: '上传失败', icon: 'none' });
|
||||
},
|
||||
complete: () => {
|
||||
@@ -184,6 +185,10 @@ Page({
|
||||
if (this.data.phoneLoading) {
|
||||
return;
|
||||
}
|
||||
if (!this.data.privacyAgreed) {
|
||||
wx.showToast({ title: '请先阅读并同意用户协议和隐私政策', icon: 'none', duration: 2000 });
|
||||
return;
|
||||
}
|
||||
const phoneCode = e.detail && e.detail.code;
|
||||
if (!phoneCode) {
|
||||
const message = '未授权手机号,请点击上方按钮重试';
|
||||
@@ -205,19 +210,48 @@ Page({
|
||||
this.loadPage();
|
||||
},
|
||||
|
||||
logout() {
|
||||
togglePrivacy() {
|
||||
const agreed = !this.data.privacyAgreed;
|
||||
this.setData({ privacyAgreed: agreed });
|
||||
if (agreed) {
|
||||
wx.setStorageSync('privacy_agreed', true);
|
||||
} else {
|
||||
wx.removeStorageSync('privacy_agreed');
|
||||
}
|
||||
},
|
||||
|
||||
openPrivacy(e) {
|
||||
const type = e.currentTarget.dataset.type || 'privacy';
|
||||
wx.navigateTo({ url: '/pages/privacy/privacy?type=' + type });
|
||||
},
|
||||
|
||||
deleteAccount() {
|
||||
wx.showModal({
|
||||
title: '退出登录',
|
||||
content: '确定要退出当前账号吗?',
|
||||
confirmText: '退出',
|
||||
title: '注销账号',
|
||||
content: '确定要注销账号吗?注销后将清除本机登录信息,需要重新登录。',
|
||||
confirmText: '注销',
|
||||
confirmColor: '#c0392b',
|
||||
success: (res) => {
|
||||
if (!res.confirm) {
|
||||
return;
|
||||
}
|
||||
api.request({ url: '/api/v1/auth/logout', method: 'POST' }).catch(() => {});
|
||||
auth.clearToken();
|
||||
wx.reLaunch({ url: '/pages/home/home' });
|
||||
if (!res.confirm) return;
|
||||
wx.showModal({
|
||||
title: '再次确认',
|
||||
content: '注销后本机登录状态将被清除,你可以使用手机号重新登录。确定继续吗?',
|
||||
confirmText: '确认注销',
|
||||
confirmColor: '#c0392b',
|
||||
success: (res2) => {
|
||||
if (!res2.confirm) return;
|
||||
// 仅清除前端登录信息
|
||||
try {
|
||||
api.request({ url: '/api/v1/auth/logout', method: 'POST' }).catch(() => {});
|
||||
} catch (e) {}
|
||||
auth.clearToken();
|
||||
wx.removeStorageSync('privacy_agreed');
|
||||
wx.showToast({ title: '已注销', icon: 'success' });
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({ url: '/pages/home/home' });
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
<view class="login-wrap">
|
||||
<view class="login-card">
|
||||
<view class="login-icon">👤</view>
|
||||
<view class="login-title">微信手机号一键登录</view>
|
||||
<view class="login-desc">未找到你的账号,请使用微信手机号登录。手机号仅用于账号绑定,不会公开展示。</view>
|
||||
<view class="login-title">手机号快捷登录</view>
|
||||
<view class="login-desc">未找到你的账号,请使用手机号登录。手机号仅用于账号绑定,不会公开展示。</view>
|
||||
<button
|
||||
class="btn-phone btn-primary"
|
||||
open-type="getPhoneNumber"
|
||||
@@ -29,7 +29,18 @@
|
||||
loading="{{phoneLoading}}"
|
||||
disabled="{{phoneLoading}}"
|
||||
hover-class="btn-hover"
|
||||
>{{phoneLoading ? '登录中...' : '微信手机号一键登录'}}</button>
|
||||
>{{phoneLoading ? '登录中...' : '手机号快捷登录'}}</button>
|
||||
<view class="privacy-row">
|
||||
<view class="checkbox {{privacyAgreed ? 'checked' : ''}}" bindtap="togglePrivacy">
|
||||
<text wx:if="{{privacyAgreed}}" class="check-icon">✓</text>
|
||||
</view>
|
||||
<view class="privacy-text">
|
||||
我已阅读并同意
|
||||
<text class="link" data-type="agreement" catchtap="openPrivacy">《用户协议》</text>
|
||||
和
|
||||
<text class="link" data-type="privacy" catchtap="openPrivacy">《隐私政策》</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="error" wx:if="{{phoneError}}">{{phoneError}}</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -91,7 +102,7 @@
|
||||
</view>
|
||||
|
||||
<view class="card classlist-card" wx:if="{{classGroups.length}}">
|
||||
<view class="info-title">班级分组</view>
|
||||
<view class="info-title">小组分组</view>
|
||||
<view class="class-row" wx:for="{{classGroups}}" wx:key="class_id">
|
||||
<text class="info-label">{{item.period || item.class_code}}</text>
|
||||
<text class="info-value">{{item.group}} · {{item.role}}</text>
|
||||
@@ -99,7 +110,8 @@
|
||||
</view>
|
||||
|
||||
<view class="bottom-actions">
|
||||
<button class="btn-logout" hover-class="btn-hover" bindtap="logout">退出登录</button>
|
||||
<button class="btn-delete" hover-class="btn-hover" bindtap="deleteAccount">注销账号</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</block>
|
||||
@@ -186,9 +186,52 @@ button::after { border: none; }
|
||||
margin-top: 48rpx;
|
||||
padding-top: 24rpx;
|
||||
}
|
||||
.btn-logout {
|
||||
/* ---- privacy row ---- */
|
||||
.privacy-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-top: 28rpx;
|
||||
text-align: left;
|
||||
}
|
||||
.checkbox {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border: 2rpx solid #D8C7B4;
|
||||
border-radius: 6rpx;
|
||||
margin-right: 14rpx;
|
||||
margin-top: 2rpx;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
}
|
||||
.checkbox.checked {
|
||||
background: #C67B30;
|
||||
border-color: #C67B30;
|
||||
}
|
||||
.check-icon {
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
}
|
||||
.privacy-text {
|
||||
flex: 1;
|
||||
font-size: 24rpx;
|
||||
color: #8B7A6A;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.privacy-text .link {
|
||||
color: #C67B30;
|
||||
}
|
||||
|
||||
/* ---- delete account ---- */
|
||||
.btn-delete {
|
||||
background: #fff;
|
||||
color: #c0392b;
|
||||
border: 1rpx solid #f0b4ae;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
/* ---- profile name edit ---- */
|
||||
|
||||
@@ -1,8 +1,47 @@
|
||||
const api = require('../../utils/api');
|
||||
|
||||
// 允许加载的域名白名单
|
||||
const ALLOWED_HOSTS = ['m.muguaclub.cn', 'muguaclub.cn'];
|
||||
|
||||
function isAllowedUrl(url) {
|
||||
if (!url) return false;
|
||||
try {
|
||||
// 简单解析 hostname
|
||||
const match = url.match(/^https?:\/\/([^/:?#]+)/i);
|
||||
if (!match) return false;
|
||||
const host = match[1].toLowerCase();
|
||||
return ALLOWED_HOSTS.some((h) => host === h || host.endsWith('.' + h));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Page({
|
||||
data: { url: '' },
|
||||
data: {
|
||||
url: '',
|
||||
invalid: false
|
||||
},
|
||||
onLoad(options) {
|
||||
if (options.url) {
|
||||
this.setData({ url: decodeURIComponent(options.url) });
|
||||
if (!options.url) {
|
||||
this.setData({ invalid: true });
|
||||
return;
|
||||
}
|
||||
const url = decodeURIComponent(options.url);
|
||||
if (!isAllowedUrl(url)) {
|
||||
console.warn('[webview] blocked url:', url);
|
||||
this.setData({ invalid: true, url: '' });
|
||||
return;
|
||||
}
|
||||
this.setData({ url, invalid: false });
|
||||
},
|
||||
|
||||
onRetry() {
|
||||
if (this.data.url) {
|
||||
// 触发重新加载
|
||||
this.setData({ url: '' });
|
||||
setTimeout(() => {
|
||||
this.setData({ url: this.data._url || '' });
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
<web-view src="{{url}}" wx:if="{{url}}"></web-view>
|
||||
<view class="page">
|
||||
<web-view src="{{url}}" wx:if="{{url && !invalid}}"></web-view>
|
||||
<view class="error-page" wx:if="{{invalid}}">
|
||||
<view class="error-icon">!</view>
|
||||
<view class="error-title">无法加载该页面</view>
|
||||
<view class="error-desc">该页面不在允许访问的域名范围内</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #FDFBF7;
|
||||
}
|
||||
|
||||
.error-page {
|
||||
padding-top: 200rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.error-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
line-height: 120rpx;
|
||||
margin: 0 auto 32rpx;
|
||||
border-radius: 50%;
|
||||
background: #fdecea;
|
||||
color: #c0392b;
|
||||
font-size: 64rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.error-title {
|
||||
color: #2C1810;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.error-desc {
|
||||
color: #8B7A6A;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user