小程序审核合规:所有登录/绑号入口统一接入协议同意组件

- 新增 PrivacyAgree 通用组件(勾选 + 《用户服务协议》《隐私政策》入口)
- mine 页:微信一键登录/手机号登录/微信一键绑定手机号(getPhoneNumber) 均需先同意协议
- event-detail 页:活动报名登录/短信登录/微信绑号同样强制同意
- scan-login 保持原勾选逻辑,三入口一致满足审核要求
This commit is contained in:
Pine
2026-09-09 01:55:55 +08:00
parent 21d610acf5
commit a4b08d9dc6
4 changed files with 43 additions and 3 deletions
@@ -0,0 +1,22 @@
import { View, Text } from '@tarojs/components';
import Taro from '@tarojs/taro';
import './PrivacyAgree.scss';
/**
* 协议授权同意勾选(审核合规):未同意前禁止登录/绑定手机号等收集用户信息操作。
* 复用组件,保证所有登录入口一致提供《用户服务协议》《隐私政策》入口。
*/
export default function PrivacyAgree({ agreed, onToggle, label }) {
const open = (url) => { Taro.navigateTo({ url }); };
return (
<View className="pa-agree" onClick={onToggle}>
<View className={`pa-box${agreed ? ' on' : ''}`}>{agreed ? '✓' : ''}</View>
<Text className="pa-txt">
{label || '我已阅读并同意'}
<Text className="pa-link" onClick={(e) => { e.stopPropagation(); open('/pages-extra/agreement/index'); }}>用户服务协议</Text>
<Text className="pa-link" onClick={(e) => { e.stopPropagation(); open('/pages-extra/privacy/index'); }}>隐私政策</Text>
</Text>
</View>
);
}
@@ -0,0 +1,5 @@
.pa-agree { display: flex; align-items: flex-start; gap: 12rpx; margin-top: 18rpx; width: 100%; box-sizing: border-box; }
.pa-box { width: 34rpx; height: 34rpx; border-radius: 8rpx; border: 2rpx solid #b8c0dd; display: flex; align-items: center; justify-content: center; font-size: 22rpx; color: #fff; flex-shrink: 0; background: #fff; margin-top: 2rpx; }
.pa-box.on { background: linear-gradient(135deg, #6b8bff, #8f7bff); border-color: transparent; }
.pa-txt { font-size: 24rpx; color: #7a86b0; line-height: 1.6; }
.pa-link { color: #6b8bff; }
@@ -19,6 +19,7 @@ import { isAuthed, getUser, wxLogin, wxPhoneBind, phoneLogin, bindPhone, sendCod
import coverWide from '@/assets/huodong-wide.png';
import Skeleton from '@/components/skeleton';
import './index.scss';
import PrivacyAgree from '@/components/PrivacyAgree';
import { PageHeader } from '@/components/TopInset';
/* 活动报名状态(5 态) */
@@ -70,6 +71,7 @@ export default function EventDetail() {
/* 登录态 & 我的报名状态:登录/绑号后需重渲染,故用 state 而非渲染期一次性读取 */
const [myBook, setMyBook] = useState(null); // {booked, auditStatus, checkinAt}
const [authed, setAuthed] = useState(isAuthed());
const [agree, setAgree] = useState(false); // 协议/隐私同意(登录与绑定手机号前必勾)
const [phoneBound, setPhoneBound] = useState(() => !!(getUser() && getUser().phoneBound));
const [showSms, setShowSms] = useState(false); // 短信兜底开关
const [gatePhone, setGatePhone] = useState('');
@@ -103,6 +105,7 @@ export default function EventDetail() {
/* 微信一键登录:wx.login + 昵称头像 → 后端建/查 wxid 账号 */
const onWxLogin = async () => {
if (!agree) { Taro.showToast({ title: '请先阅读并同意《用户服务协议》和《隐私政策》', icon: 'none' }); return; }
setErr(''); setBusy(true);
try {
let nickName = '', avatarUrl = '';
@@ -117,6 +120,7 @@ export default function EventDetail() {
/* 微信一键绑定手机号(getPhoneNumber 回调 code */
const onWxPhone = async (e) => {
if (!agree) { Taro.showToast({ title: '请先阅读并同意《用户服务协议》和《隐私政策》', icon: 'none' }); return; }
const pcode = e.detail && e.detail.code;
if (!pcode) { setErr('已取消授权绑定'); return; }
setErr(''); setBusy(true);
@@ -130,6 +134,7 @@ export default function EventDetail() {
/* 短信兜底:未登录 → phoneLogin(登录即绑号);已登录未绑号 → bindPhone */
const gateSmsLogin = async () => {
if (!agree) { Taro.showToast({ title: '请先阅读并同意《用户服务协议》和《隐私政策》', icon: 'none' }); return; }
setErr('');
if (!/^1\d{10}$/.test(gatePhone)) { setErr('请输入正确的 11 位手机号'); return; }
if (!gateCode) { setErr('请输入验证码'); return; }
@@ -336,6 +341,7 @@ export default function EventDetail() {
<Text className="ed-form-title">登录后报名</Text>
<Text className="muted gate-tip">报名需登录并绑定手机号用于接收活动通知</Text>
<Button className="btn-main" loading={busy} onClick={onWxLogin}>微信一键登录</Button>
<PrivacyAgree agreed={agree} onToggle={() => setAgree(!agree)} />
{!showSms && (
<Button className="btn-ghost gate-toggle" onClick={() => setShowSms(true)}>用短信验证码登录</Button>
)}
@@ -360,6 +366,7 @@ export default function EventDetail() {
<Text className="ed-form-title">绑定手机号</Text>
<Text className="muted gate-tip">绑定后即可报名用于接收活动通知</Text>
<Button className="btn-main" loading={busy} openType="getPhoneNumber" onGetPhoneNumber={onWxPhone}>微信一键绑定手机号</Button>
<PrivacyAgree agreed={agree} onToggle={() => setAgree(!agree)} />
{!showSms && (
<Button className="btn-ghost gate-toggle" onClick={() => setShowSms(true)}>用短信验证码绑定</Button>
)}
+9 -3
View File
@@ -7,6 +7,7 @@ import { getMyBookings, parseIso } from '@/utils/booking';
import { API_BASE } from '@/utils/api';
import { ensureUploadSize } from '@/utils/upload';
import CountdownBox from '@/components/CountdownBox';
import PrivacyAgree from '@/components/PrivacyAgree';
import './index.scss';
import { PageHeader } from '@/components/TopInset';
@@ -37,6 +38,7 @@ export default function Mine() {
/* 手机号登录 */
const [showPhone, setShowPhone] = useState(false);
const [agree, setAgree] = useState(false); // 协议/隐私同意(登录与绑定手机号前必勾)
const [pPhone, setPPhone] = useState('');
const [pCode, setPCode] = useState('');
const [pCd, setPCd] = useState(0);
@@ -69,6 +71,7 @@ export default function Mine() {
/* 微信一键登录 */
const onWxLogin = async () => {
if (!agree) { Taro.showToast({ title: '请先阅读并同意《用户服务协议》和《隐私政策》', icon: 'none' }); return; }
setErr('');
setBusy(true);
try {
@@ -121,6 +124,7 @@ export default function Mine() {
/* 微信官方一键绑定手机号(getPhoneNumber 回调) */
const onWxPhone = async (e) => {
if (!agree) { Taro.showToast({ title: '请先阅读并同意《用户服务协议》和《隐私政策》', icon: 'none' }); return; }
const detail = (e.detail && e.detail) || {};
const pcode = detail.code;
if (!pcode) { Taro.showToast({ title: '已取消授权', icon: 'none' }); return; }
@@ -150,6 +154,7 @@ export default function Mine() {
};
const doPhoneLogin = async () => {
if (!agree) { Taro.showToast({ title: '请先阅读并同意《用户服务协议》和《隐私政策》', icon: 'none' }); return; }
if (!/^1\d{10}$/.test(pPhone)) { setErr('请输入正确的 11 位手机号'); return; }
if (!pCode) { setErr('请输入验证码'); return; }
setErr(''); setBusy(true);
@@ -242,10 +247,11 @@ export default function Mine() {
if (!user || !isAuthed()) {
return (
<View className="mine">
<PageHeader no="MY" title="我的" sub="个人中心" />
<PageHeader no="MY" title="个人中心" sub="请登录后查看个人中心详情" />
<ScrollView className="mine-scroll" scrollY enhanced showScrollbar={false}>
<View className="mine-card login-card">
<Button className="btn-main wx-btn" loading={busy} onClick={onWxLogin}>微信一键登录</Button>
<PrivacyAgree agreed={agree} onToggle={() => setAgree(!agree)} />
<Text className="muted mine-tip">登录后可绑定手机查看报名场次与活动签到</Text>
<Text className="link" onClick={() => setShowPhone(!showPhone)}>{showPhone ? '收起手机号登录' : '或用手机号登录'}</Text>
</View>
@@ -269,7 +275,7 @@ export default function Mine() {
{err && <Text className="err-text">{err}</Text>}
<View className="mk-foot">
<Text className="mk-foot-line">云南省首个由政府主办的免费大学生创业服务平台</Text>
<Text className="mk-foot-line">云南派音人工智能科技有限公司 && 云南汇浙青创科技有限公司</Text>
<Text className="mk-foot-line">昆明市大学生创业园 · 云超服 YunOPC-Hub</Text>
<Text className="mk-foot-line">像素字体 BubbledotICG-FinePos · CC BY 4.0</Text>
</View>
</ScrollView>
@@ -280,7 +286,7 @@ export default function Mine() {
/* ============ 已登录:资料 + 我的报名 ============ */
return (
<View className="mine">
<PageHeader no="MY" title="我的" sub="个人中心" />
<PageHeader no="MY" title="个人中心" sub="一切都是属于您的个人数据资产" />
<ScrollView className="mine-scroll" scrollY enhanced showScrollbar={false}>
{/* 资料卡 */}