- 首页壳 onShow consumeAuthResult:回传基址改为 shareHref(H5 发起授权前 reportLocation 上报的当前地址)|| webUrl || MAMA_HOME —— 此前写死 MAMA_HOME, 宫格 location.href 换应用后(webUrl 数据未变),授权结果被送回 mama-box, 目标应用(孕检日历等)永远收不到 phone,弹层复现 - 首页壳 else 分支的 ?t= 重载同样以 shareHref 为基址(不再把宫格应用用户踢回 mama-box) - 首页壳 submitAuth(原生面板)同步改用当前 H5 地址 - 授权页成功判据:提交了 code 时必须核对 result.phone 非空才写 __AUTH_RESULT__/ 提示成功 —— 配合 box_app 后端 400 如实报错(box_app commit e6b98e7) 需重新发版生效
209 lines
9.1 KiB
Vue
209 lines
9.1 KiB
Vue
<template>
|
||
<view class="auth-page">
|
||
<!-- 顶部说明 -->
|
||
<view class="auth-head">
|
||
<text class="auth-title">授权完善资料</text>
|
||
<text class="auth-sub">用于在应用里展示你的昵称 / 头像 / 手机号</text>
|
||
</view>
|
||
|
||
<!-- 授权表单卡片 -->
|
||
<view class="auth-card">
|
||
<!-- 头像:圆形选择区,点击选择;图片填满圆并裁剪,文字放圆外 -->
|
||
<view class="auth-avatar-wrap">
|
||
<button class="auth-avatar" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
|
||
<image v-if="authAvatar" class="auth-ava" :src="authAvatar" mode="aspectFill" />
|
||
<view v-else class="auth-ava-plus">+</view>
|
||
</button>
|
||
<text class="auth-ava-label">{{ authAvatar ? '点击更换头像' : '点击选择头像' }}</text>
|
||
</view>
|
||
|
||
<view class="auth-field">
|
||
<input class="auth-input" type="nickname" v-model="authNickname" placeholder="输入昵称" maxlength="16" placeholder-class="auth-ph" />
|
||
</view>
|
||
|
||
<button class="auth-btn" open-type="getPhoneNumber" @getphonenumber="onGetPhone">{{ authPhone ? '✅ 已授权手机号' : '获取手机号' }}</button>
|
||
|
||
<view class="auth-actions">
|
||
<button class="auth-cancel" @tap="cancel">取消</button>
|
||
<button class="auth-ok" @tap="submitAuth">保存并返回</button>
|
||
</view>
|
||
</view>
|
||
|
||
<text class="auth-tip">微信授权仅用于在应用内展示你的资料,不会泄露给第三方。</text>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import store from '@/store';
|
||
import { HTTP_REQUEST_URL } from '@/config/app';
|
||
import { LOGIN_STATUS } from '@/config/cache';
|
||
|
||
function base64Decode(str) {
|
||
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||
var output = '';
|
||
var i = 0;
|
||
str = String(str).replace(/[^A-Za-z0-9+/=]/g, '');
|
||
while (i < str.length) {
|
||
var b1 = chars.indexOf(str.charAt(i++));
|
||
var b2 = chars.indexOf(str.charAt(i++));
|
||
var b3 = chars.indexOf(str.charAt(i++));
|
||
var b4 = chars.indexOf(str.charAt(i++));
|
||
output += String.fromCharCode((b1 << 2) | (b2 >> 4));
|
||
if (b3 !== 64) output += String.fromCharCode(((b2 & 15) << 4) | (b3 >> 2));
|
||
if (b4 !== 64) output += String.fromCharCode(((b3 & 3) << 6) | b4);
|
||
}
|
||
return output;
|
||
}
|
||
|
||
function getOpenid() {
|
||
let oid = store.state.app.openid;
|
||
if (oid) return oid;
|
||
try { oid = uni.getStorageSync('OPENID'); } catch (e) {}
|
||
if (oid) return oid;
|
||
try {
|
||
const token = store.state.app.token || uni.getStorageSync(LOGIN_STATUS);
|
||
if (token) {
|
||
let parts = token.split('.');
|
||
if (parts.length === 3) {
|
||
let payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
||
while (payload.length % 4 !== 0) payload += '=';
|
||
const data = JSON.parse(base64Decode(payload));
|
||
oid = data.openid || '';
|
||
if (oid) { store.commit('SET_OPENID', oid); return oid; }
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.warn('[auth] JWT decode openid failed:', e.message);
|
||
}
|
||
return '';
|
||
}
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
authNickname: '',
|
||
authAvatar: '',
|
||
authPhone: false, // 手机号是否已授权(UI 反馈)
|
||
_authCode: '',
|
||
openid: ''
|
||
}
|
||
},
|
||
onLoad() {
|
||
this.openid = getOpenid();
|
||
if (!this.openid) uni.showToast({ title: '未获得 openid,请先登录', icon: 'none' });
|
||
},
|
||
methods: {
|
||
onChooseAvatar(e) {
|
||
const avatarUrl = e.detail.avatarUrl;
|
||
if (!avatarUrl) return uni.showToast({ title: '未获取到头像', icon: 'none' });
|
||
/* 上传到 box_app 公开接口(无鉴权):定制授权页设计为匿名可用,
|
||
不依赖 CRMEB 登录态 —— 用 CRMEB 的 uploadImgs 在退出登录后会 401 弹「请登录」。 */
|
||
uni.showLoading({ title: '上传头像中…' });
|
||
uni.uploadFile({
|
||
url: 'https://mp.yizhezhe.com/api/upload',
|
||
filePath: avatarUrl,
|
||
name: 'file',
|
||
success: (res) => {
|
||
try {
|
||
const d = JSON.parse(res.data);
|
||
if (d.code === 200 && d.result && d.result.url) {
|
||
this.authAvatar = d.result.url;
|
||
} else {
|
||
uni.showToast({ title: '头像上传失败', icon: 'none' });
|
||
}
|
||
} catch (err) {
|
||
uni.showToast({ title: '头像上传失败', icon: 'none' });
|
||
}
|
||
},
|
||
fail: () => uni.showToast({ title: '头像上传失败,请检查网络', icon: 'none' }),
|
||
complete: () => uni.hideLoading()
|
||
});
|
||
},
|
||
onGetPhone(e) {
|
||
if (e.detail.errMsg === 'getPhoneNumber:ok' && e.detail.code) {
|
||
this._authCode = e.detail.code;
|
||
this.authPhone = true;
|
||
} else {
|
||
uni.showToast({ title: '未授权手机号', icon: 'none' });
|
||
}
|
||
},
|
||
submitAuth() {
|
||
if (!this.authNickname && !this.authAvatar && !this._authCode) {
|
||
return uni.showToast({ title: '请至少授权一项', icon: 'none' });
|
||
}
|
||
const openid = getOpenid() || this.openid;
|
||
if (!openid) return uni.showToast({ title: '未获得 openid,请先登录', icon: 'none' });
|
||
const api = 'https://mp.yizhezhe.com/api/mp/public/auth/profile?openid=' + encodeURIComponent(openid) +
|
||
(this._authCode ? '&code=' + encodeURIComponent(this._authCode) : '') +
|
||
(this.authNickname ? '&nickname=' + encodeURIComponent(this.authNickname) : '') +
|
||
(this.authAvatar ? '&avatar=' + encodeURIComponent(this.authAvatar) : '');
|
||
uni.request({
|
||
url: api, method: 'POST', timeout: 10000,
|
||
success: (r) => {
|
||
const d = r.data || {};
|
||
const res = d.result || {};
|
||
// ⚠️ 成功判据必须核对「手机号真的拿到了」:提交了 code 时,
|
||
// 后端解密失败(如 access_token 被商城后端挤掉)会如实返回
|
||
// code!=200;旧版后端也可能 200 但 result.phone 为空。
|
||
// 两种情况都不能写 __AUTH_RESULT__、不能提示成功 —— 否则 H5
|
||
// 收到空 phone 的回传,闸门拦截,用户会陷入「授权成功却又
|
||
// 要求授权」的死循环(2026-09-18 孕检日历实测)。
|
||
const phoneMissing = !!this._authCode && !res.phone;
|
||
if (d.code === 200 && !phoneMissing) {
|
||
uni.setStorageSync('__AUTH_RESULT__', {
|
||
openid: openid,
|
||
phone: res.phone || '',
|
||
nickname: res.userName || this.authNickname || '',
|
||
avatar: res.avatar || this.authAvatar || ''
|
||
});
|
||
uni.showToast({ title: '授权成功', icon: 'success' });
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
}, 600);
|
||
} else {
|
||
uni.showToast({ title: (d.message || '授权保存失败,请稍后重试'), icon: 'none', duration: 3000 });
|
||
}
|
||
},
|
||
fail: () => uni.showToast({ title: '授权请求失败,请检查网络', icon: 'none' })
|
||
});
|
||
},
|
||
cancel() { uni.navigateBack(); }
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 清 button 默认边框/padding(避免撑出卡片 + 消除默认灰底边框)。
|
||
不改 margin —— 统配 margin:0 会压掉各按钮自己的 margin-top,导致元素紧贴。 */
|
||
.auth-page button::after { border: none; }
|
||
.auth-page button { padding: 0; line-height: normal; box-sizing: border-box; border: none; -webkit-appearance: none; }
|
||
.auth-page input { box-sizing: border-box; margin: 0; }
|
||
|
||
.auth-page { min-height: 100vh; background: #F8F3EF; padding: 24px 20px 40px; display: flex; flex-direction: column; align-items: center; }
|
||
.auth-head { text-align: center; margin-top: 16px; }
|
||
.auth-title { font-size: 20px; font-weight: 800; color: #2B2320; display: block; }
|
||
.auth-sub { font-size: 13px; color: #A79C95; margin-top: 8px; display: block; }
|
||
.auth-card { width: 100%; max-width: 340px; background: #fff; border-radius: 18px; padding: 28px 20px 20px; margin-top: 28px; box-shadow: 0 6px 24px rgba(0,0,0,.05); display: flex; flex-direction: column; align-items: center; }
|
||
|
||
/* 头部:圆形头像选择区(图片填满圆并裁剪),说明文字放圆外下方 */
|
||
.auth-avatar-wrap { display: flex; flex-direction: column; align-items: center; }
|
||
.auth-avatar { width: 92px; height: 92px; border-radius: 50%; background: #F3E9E3; display: flex; align-items: center; justify-content: center; overflow: hidden; border: 1px dashed #D9C4B8; flex: 0 0 auto; }
|
||
.auth-ava { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
|
||
.auth-ava-plus { font-size: 30px; color: #C9B7AC; line-height: 1; }
|
||
.auth-ava-label { font-size: 11px; color: #A79C95; margin-top: 8px; }
|
||
|
||
/* 昵称输入 */
|
||
.auth-field { width: 100%; margin-top: 22px; }
|
||
.auth-input { width: 100%; height: 48px; border-radius: 12px; background: #F8F3EF; font-size: 14px; color: #2B2320; padding: 0 14px; box-sizing: border-box; }
|
||
.auth-ph { color: #C9B7AC; }
|
||
|
||
/* 获取手机号按钮 */
|
||
.auth-btn { width: 100%; height: 48px; border-radius: 12px; background: #F8F3EF; color: #2B2320; font-size: 14px; margin-top: 14px; display: flex; align-items: center; justify-content: center; }
|
||
|
||
.auth-actions { display: flex; gap: 12px; margin-top: 24px; width: 100%; }
|
||
.auth-cancel, .auth-ok { flex: 1; height: 46px; border-radius: 12px; font-size: 14px; font-weight: 600; display: flex; align-items: center; justify-content: center; }
|
||
.auth-cancel { background: #F1E6E0; color: #6B615B; }
|
||
.auth-ok { background: #F4593C; color: #fff; }
|
||
.auth-tip { font-size: 11px; color: #A79C95; margin-top: 20px; text-align: center; line-height: 1.5; }
|
||
</style>
|