Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48db5c8e6f | ||
|
|
56f73bb24b | ||
|
|
e53994ba56 | ||
|
|
e2cb135124 |
@@ -4,6 +4,7 @@
|
|||||||
// 桌面加载时整页已是手机尺寸但默认贴左。这里只补两件事:
|
// 桌面加载时整页已是手机尺寸但默认贴左。这里只补两件事:
|
||||||
// 1) 宽屏下把页面列居中(见样式);
|
// 1) 宽屏下把页面列居中(见样式);
|
||||||
// 2) 跨越 768 阈值缩放窗口时重载一次,让 px 基准与当前视口一致。
|
// 2) 跨越 768 阈值缩放窗口时重载一次,让 px 基准与当前视口一致。
|
||||||
|
import auth from '@/common/auth.js'
|
||||||
let _wide = null
|
let _wide = null
|
||||||
function adaptWideScreen() {
|
function adaptWideScreen() {
|
||||||
const w = document.documentElement.clientWidth
|
const w = document.documentElement.clientWidth
|
||||||
@@ -20,6 +21,8 @@
|
|||||||
onLaunch: function(e) {
|
onLaunch: function(e) {
|
||||||
console.log('App Launch')
|
console.log('App Launch')
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
|
// xuchao: 冷启动即抓取 URL 上的身份参数(?openId 直传 / ?order 解码码)入缓存,幂等
|
||||||
|
auth.captureIdentity()
|
||||||
adaptWideScreen()
|
adaptWideScreen()
|
||||||
window.addEventListener('resize', adaptWideScreen)
|
window.addEventListener('resize', adaptWideScreen)
|
||||||
// #endif
|
// #endif
|
||||||
|
|||||||
+294
-14
@@ -1,28 +1,297 @@
|
|||||||
// xuchao: 登录层 —— 登录逻辑全部隔离在这里
|
// xuchao: 登录层 —— 登录逻辑全部隔离在这里
|
||||||
// 一期为假登录(开发模式),后端微信登录接口就绪后只改本文件:
|
// 正式流程(公众号菜单直链):openId 走公众号网页授权(OAuth)获取,其他身份数据由 order 解码(getUserInfo)解析
|
||||||
|
// - captureIdentity 冷启动抓 ?openId= / ?order= / ?code=(授权回调)入缓存
|
||||||
|
// - oauthAuthorize 无 openId 时跳公众号授权页,微信带回 code
|
||||||
|
// - code2OpenId code 换 openId(POST h5-api/oauth2MeiKe),写缓存 openId
|
||||||
|
// - loginByOrder POST open-api/meike/getUserInfo {code: order} 解码,写缓存 phone/channel 等(不覆盖已存在的 openId)
|
||||||
// - loginByMock 开发模式:输入测试手机号直接登录
|
// - loginByMock 开发模式:输入测试手机号直接登录
|
||||||
// - loginByWechat 正式流程:用微信网页授权凭证(code)换用户信息
|
// - loginByWechat 备用:微信网页授权凭证(code)换用户信息(短信绑定前置)
|
||||||
// - bindPhoneBySms 正式流程:短信验证码绑定手机号(微信授权拿不到手机号时用)
|
// - bindPhoneBySms 备用:短信验证码绑定手机号
|
||||||
import config from '@/common/config.js'
|
import config from '@/common/config.js'
|
||||||
|
|
||||||
// 是否已登录(本地缓存里有手机号即视为已登录)
|
// userInfo 解码接口
|
||||||
|
const userInfoUrl = 'open-api/meike/getUserInfo'
|
||||||
|
|
||||||
|
// 是否已登录(本地缓存里有手机号或 openId 即视为已登录)
|
||||||
const isLogin = () => {
|
const isLogin = () => {
|
||||||
return !!uni.getStorageSync('phone')
|
return !!(uni.getStorageSync('phone') || uni.getStorageSync('openId'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// xuchao: 登录校验
|
// xuchao: 从 URL 抓取身份参数(?openId=xxx 直传、?order=xxx 需解码、?code=xxx 公众号授权回调)并写入缓存。
|
||||||
// - 开发模式:无登录态时静默用首个测试手机号假登录,不跳登录页(本地直接进业务页)
|
// 兼容两种传法:查询串(?order= 在 # 前)与 hash 内参数(?order= 在 # 后,uni-app H5 hash 路由常见)。
|
||||||
// - 正式流程:跳登录页;登录成功后回跳 redirect 指定的页面
|
// URL 上有参数即覆盖缓存(新入口的菜单直链每次带新解码码,必须拿最新的)。
|
||||||
|
// order 值是 base64(含 + / =):地址栏/网关常把 '+' 编成 %20(空格),而 base64 不含空格,
|
||||||
|
// 解析后需把空格还原为 '+',再在请求侧 encodeURIComponent(+ → %2B)发送。
|
||||||
|
const captureIdentity = () => {
|
||||||
|
// #ifdef H5
|
||||||
|
const q = (location.search || '') + (location.hash || '')
|
||||||
|
const mOpen = q.match(/[?&]openId=([^&]+)/)
|
||||||
|
if (mOpen && mOpen[1]) {
|
||||||
|
uni.setStorageSync('openId', decodeURIComponent(mOpen[1]))
|
||||||
|
}
|
||||||
|
const mOrder = q.match(/[?&]order=([^&]+)/)
|
||||||
|
if (mOrder && mOrder[1]) {
|
||||||
|
const order = decodeURIComponent(mOrder[1]).replace(/\s+/g, '+')
|
||||||
|
uni.setStorageSync('order', order)
|
||||||
|
console.log('[auth] order recovered len=' + order.length + ' hasPlus=' + order.indexOf('+') > -1)
|
||||||
|
}
|
||||||
|
// xuchao: 公众号授权回调 code —— 与已消费的码对比,新码才换 openId
|
||||||
|
const mCode = q.match(/[?&]code=([^&]+)/)
|
||||||
|
if (mCode && mCode[1]) {
|
||||||
|
uni.setStorageSync('oauthCode', decodeURIComponent(mCode[1]))
|
||||||
|
console.log('[auth] oauth code recovered len=' + mCode[1].length)
|
||||||
|
}
|
||||||
|
console.log('[auth] captureIdentity orderInUrl=' + !!mOrder + ' codeInUrl=' + !!mCode + ' href=' + location
|
||||||
|
.href)
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容别名
|
||||||
|
const captureOpenId = captureIdentity
|
||||||
|
|
||||||
|
// xuchao: order 解码码换用户身份 —— POST getUserInfo,code 放请求体
|
||||||
|
// 响应:{data:{phone, openId, isSubscribe, channel}, errcode, errmsg, traceid}(openId 大写,兼容小写 openid)
|
||||||
|
// 带 in-flight 守卫:一次冷启动只发一次,避免重复消费一次性解码码
|
||||||
|
// 注意:openId 以公众号授权(OAuth)为准,这里只解析其他身份数据;openId 为空时才兜底写入
|
||||||
|
let loggingIn = null
|
||||||
|
const loginByOrder = (order) => {
|
||||||
|
if (loggingIn) return loggingIn
|
||||||
|
loggingIn = new Promise((resolve, reject) => {
|
||||||
|
const url = config.apiBaseUrl + '/' + userInfoUrl
|
||||||
|
console.log('[auth] loginByOrder ->', url)
|
||||||
|
uni.request({
|
||||||
|
url: url,
|
||||||
|
method: 'POST',
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
code: order,
|
||||||
|
openId: uni.getStorageSync('openId') || ''
|
||||||
|
},
|
||||||
|
success: (res) => {
|
||||||
|
loggingIn = null
|
||||||
|
const r = res.data || {}
|
||||||
|
console.log('[auth] getUserInfo resp', r)
|
||||||
|
if (r.errcode !== undefined && r.errcode != 0) {
|
||||||
|
reject(new Error(r.errmsg || '解码失败'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 取业务数据:兼容 {data:{...}} 包裹与扁平两种结构
|
||||||
|
const d = (r.data && (r.data.openId || r.data.openid)) ? r.data : r
|
||||||
|
const openId = d.openId || d.openid
|
||||||
|
// openId 以 OAuth 为准:未授权拿到时才用解码结果兜底
|
||||||
|
if (!uni.getStorageSync('openId')) {
|
||||||
|
if (!openId) {
|
||||||
|
reject(new Error('解码结果缺少用户身份'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.setStorageSync('openId', openId)
|
||||||
|
}
|
||||||
|
uni.setStorageSync('phone', d.phone || '')
|
||||||
|
if (d.channel) uni.setStorageSync('channel', d.channel)
|
||||||
|
if (d.userId) uni.setStorageSync('userId', d.userId)
|
||||||
|
// d.isSubscribe=0
|
||||||
|
if (d.isSubscribe !== undefined && d.isSubscribe !== null && d.isSubscribe !==
|
||||||
|
'') {
|
||||||
|
uni.setStorageSync('isSubscribe', d.isSubscribe)
|
||||||
|
}
|
||||||
|
uni.setStorageSync('order', order)
|
||||||
|
uni.setStorageSync('decodedOrder', order)
|
||||||
|
resolve(true)
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
loggingIn = null
|
||||||
|
console.log('[auth] getUserInfo fail', err)
|
||||||
|
reject(new Error('网络异常,请稍后重试'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return loggingIn
|
||||||
|
}
|
||||||
|
|
||||||
|
// xuchao: 公众号网页授权 —— 静默授权(snsapi_base)跳转,redirect_uri 为当前完整 URL(含 order 参数),
|
||||||
|
// 授权完成微信带 code 跳回,captureIdentity 抓到后由 code2OpenId 换 openId。
|
||||||
|
// 授权域名需在公众号后台「网页授权域名」中配置,否则微信会拦截。
|
||||||
|
const oauthAuthorize = () => {
|
||||||
|
// #ifdef H5
|
||||||
|
const redirect = encodeURIComponent(location.href.split('#')[0] + location.hash)
|
||||||
|
const url = 'https://open.weixin.qq.com/connect/oauth2/authorize' +
|
||||||
|
'?appid=' + config.oauthAppid +
|
||||||
|
'&redirect_uri=' + redirect +
|
||||||
|
'&response_type=code&scope=snsapi_base&state=#wechat_redirect'
|
||||||
|
console.log('[auth] oauthAuthorize ->', url)
|
||||||
|
location.replace(url)
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// xuchao: 授权回调 code 换 openId —— POST h5-api/oauth2MeiKe {code} → {data:{openId}}
|
||||||
|
// 带 in-flight 守卫:一次冷启动只换一次
|
||||||
|
let oauthing = null
|
||||||
|
const code2OpenId = (code) => {
|
||||||
|
if (oauthing) return oauthing
|
||||||
|
oauthing = new Promise((resolve, reject) => {
|
||||||
|
const url = config.apiBaseUrl + '/' + config.oauthCode2OpenIdUrl
|
||||||
|
console.log('[auth] code2OpenId ->', url)
|
||||||
|
uni.request({
|
||||||
|
url: url,
|
||||||
|
method: 'POST',
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
code: code
|
||||||
|
},
|
||||||
|
success: (res) => {
|
||||||
|
oauthing = null
|
||||||
|
const r = res.data || {}
|
||||||
|
console.log('[auth] oauth2MeiKe resp', r)
|
||||||
|
if (r.errcode !== undefined && r.errcode != 0) {
|
||||||
|
reject(new Error(r.errmsg || '授权失败'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const d = (r.data && (r.data.openId || r.data.openid)) ? r.data : r
|
||||||
|
const openId = d.openId || d.openid
|
||||||
|
if (!openId) {
|
||||||
|
reject(new Error('授权结果缺少 openId'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.setStorageSync('openId', openId)
|
||||||
|
uni.setStorageSync('decodedOauthCode', code)
|
||||||
|
resolve(true)
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
oauthing = null
|
||||||
|
console.log('[auth] oauth2MeiKe fail', err)
|
||||||
|
reject(new Error('网络异常,请稍后重试'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return oauthing
|
||||||
|
}
|
||||||
|
|
||||||
|
// xuchao: 登录校验(异步)
|
||||||
|
// 优先级(已登录态优先,避免重开页面重复授权/解码):
|
||||||
|
// - 开发模式 → 静默假登录后放行
|
||||||
|
// - 缓存已有 openId → 直接复用,仅当有「新 order」时调 getUserInfo 刷新身份数据,绝不再走公众号授权
|
||||||
|
// - 无 openId 但有授权回调 code → 调 code2OpenId 换 openId;失败提示并停留当前页
|
||||||
|
// - 无 openId 且无 code → 跳公众号授权页(oauthAuthorize)
|
||||||
|
// - order 缺失/解码失败 → Toast 提示,停留当前页(不跳登录页)
|
||||||
const requireLogin = (redirect) => {
|
const requireLogin = (redirect) => {
|
||||||
if (isLogin()) return true
|
captureIdentity()
|
||||||
|
const openId = uni.getStorageSync('openId')
|
||||||
|
const order = uni.getStorageSync('order')
|
||||||
|
const decodedOrder = uni.getStorageSync('decodedOrder') || ''
|
||||||
|
const freshOrder = !!(order && order !== decodedOrder)
|
||||||
|
const oauthCode = uni.getStorageSync('oauthCode')
|
||||||
|
const decodedOauthCode = uni.getStorageSync('decodedOauthCode') || ''
|
||||||
|
const freshCode = !!(oauthCode && oauthCode !== decodedOauthCode)
|
||||||
|
console.log('[auth] requireLogin isLogin=' + isLogin() + ' hasOrder=' + !!order + ' freshOrder=' + freshOrder +
|
||||||
|
' hasCode=' + !!oauthCode + ' freshCode=' + freshCode + ' openId=' + !!openId)
|
||||||
|
// 开发模式:静默假登录
|
||||||
if (config.devMode) {
|
if (config.devMode) {
|
||||||
loginByMock(config.testPhones[0])
|
loginByMock(config.testPhones[0])
|
||||||
return true
|
return Promise.resolve(true)
|
||||||
}
|
}
|
||||||
uni.navigateTo({
|
// 已授权(缓存有 openId):复用登录态,不走授权;仅处理新 order 的身份数据刷新
|
||||||
url: '/pages/index/authorization' + (redirect ? '?redirect=' + redirect : '')
|
if (openId) {
|
||||||
|
return resolveOrder(order, freshOrder)
|
||||||
|
}
|
||||||
|
// 未授权:有授权回调 code → 换 openId
|
||||||
|
if (freshCode) {
|
||||||
|
return code2OpenId(oauthCode).then(() => {
|
||||||
|
// 换到 openId 后,仍可能有新 order 需解码
|
||||||
|
if (isLogin()) return resolveOrder(order, freshOrder)
|
||||||
|
return Promise.resolve(false)
|
||||||
|
}).catch((err) => {
|
||||||
|
uni.removeStorageSync('oauthCode')
|
||||||
|
uni.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: err.message || '授权失败,请稍后重试'
|
||||||
|
})
|
||||||
|
return Promise.resolve(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 未授权且无授权回调 → 跳公众号授权页(页面将重定向,返回 pending)
|
||||||
|
oauthAuthorize()
|
||||||
|
return new Promise(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// xuchao: 权益判断 —— isSubscribe==1 有权益;0 无权益(弹窗阻断,弹窗不可关闭)
|
||||||
|
const hasEntitlement = () => {
|
||||||
|
return uni.getStorageSync('isSubscribe') == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// xuchao: 无权益弹窗 —— 全屏遮罩 + 居中对话框,无取消按钮、确认后重新弹出,实现"不可关闭"
|
||||||
|
// 用原生 DOM 实现(而非 uni.showModal):该 H5 运行时 uni-modal 无内置样式不可见,且原生弹窗无法真正锁死
|
||||||
|
let noRightModalShown = false
|
||||||
|
const showNoRightModal = () => {
|
||||||
|
if (noRightModalShown) return
|
||||||
|
noRightModalShown = true
|
||||||
|
// #ifdef H5
|
||||||
|
const overlay = document.createElement('div')
|
||||||
|
overlay.id = '__mm-no-right-modal'
|
||||||
|
overlay.style.cssText =
|
||||||
|
'position:fixed;top:0;left:0;right:0;bottom:0;z-index:9999;' +
|
||||||
|
'background:rgba(15,23,42,0.55);display:flex;align-items:center;justify-content:center;'
|
||||||
|
const box = document.createElement('div')
|
||||||
|
box.style.cssText =
|
||||||
|
'width:75%;max-width:320px;background:#fff;border-radius:16px;padding:40px 32px 32px;' +
|
||||||
|
'text-align:center;box-shadow:0 24px 48px rgba(15,23,42,0.25);'
|
||||||
|
const title = document.createElement('div')
|
||||||
|
title.textContent = '提示'
|
||||||
|
title.style.cssText = 'font-size:17px;font-weight:600;color:#0F172A;'
|
||||||
|
const content = document.createElement('div')
|
||||||
|
content.textContent = '您暂时没有权益,请获取权益之后重新进入'
|
||||||
|
content.style.cssText = 'font-size:14px;color:#0F172A;line-height:1.6;margin-top:12px;'
|
||||||
|
const btn = document.createElement('div')
|
||||||
|
btn.textContent = '知道了'
|
||||||
|
btn.style.cssText =
|
||||||
|
'margin-top:20px;height:44px;line-height:44px;border-radius:8px;color:#fff;' +
|
||||||
|
'font-size:15px;font-weight:600;background:linear-gradient(135deg,#0052FF,#4D7CFF);' +
|
||||||
|
'cursor:pointer;'
|
||||||
|
// 确认后不关闭,而是重新弹出(保证弹窗锁死)
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
noRightModalShown = false
|
||||||
|
showNoRightModal()
|
||||||
|
})
|
||||||
|
box.appendChild(title)
|
||||||
|
box.appendChild(content)
|
||||||
|
box.appendChild(btn)
|
||||||
|
overlay.appendChild(box)
|
||||||
|
document.body.appendChild(overlay)
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// xuchao: order 解码(其他身份数据)—— 新 order 才调接口;解码后校验权益
|
||||||
|
const resolveOrder = (order, freshOrder) => {
|
||||||
|
if (isLogin() && !freshOrder) {
|
||||||
|
if (!hasEntitlement()) {
|
||||||
|
showNoRightModal()
|
||||||
|
return Promise.resolve(false)
|
||||||
|
}
|
||||||
|
return Promise.resolve(true)
|
||||||
|
}
|
||||||
|
if (!order) {
|
||||||
|
uni.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: '缺少登录凭证'
|
||||||
|
})
|
||||||
|
return Promise.resolve(false)
|
||||||
|
}
|
||||||
|
return loginByOrder(order).then(() => {
|
||||||
|
if (!hasEntitlement()) {
|
||||||
|
showNoRightModal()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}).catch((err) => {
|
||||||
|
uni.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: err.message || '登录失败'
|
||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// xuchao: 开发模式假登录 —— 写入与真实登录相同的缓存数据,后续页面逻辑完全一致
|
// xuchao: 开发模式假登录 —— 写入与真实登录相同的缓存数据,后续页面逻辑完全一致
|
||||||
@@ -30,9 +299,10 @@ const loginByMock = (phone) => {
|
|||||||
uni.setStorageSync('phone', phone)
|
uni.setStorageSync('phone', phone)
|
||||||
uni.setStorageSync('openId', 'mock_openid_' + phone)
|
uni.setStorageSync('openId', 'mock_openid_' + phone)
|
||||||
uni.setStorageSync('userId', '')
|
uni.setStorageSync('userId', '')
|
||||||
|
uni.setStorageSync('isSubscribe', '1')
|
||||||
}
|
}
|
||||||
|
|
||||||
// xuchao: 正式流程占位 —— 后端接口就绪后实现(用 code 换 openId + 用户信息)
|
// xuchao: 备用流程 —— 微信网页授权(code)换用户信息
|
||||||
const loginByWechat = (code) => {
|
const loginByWechat = (code) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// TODO 后端就绪后替换为:POST /auth/oauth2session {code} → {openId, mobile, token}
|
// TODO 后端就绪后替换为:POST /auth/oauth2session {code} → {openId, mobile, token}
|
||||||
@@ -40,7 +310,7 @@ const loginByWechat = (code) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// xuchao: 正式流程占位 —— 短信验证码绑定手机号
|
// xuchao: 备用流程 —— 短信验证码绑定手机号
|
||||||
const bindPhoneBySms = (phone, smsCode) => {
|
const bindPhoneBySms = (phone, smsCode) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// TODO 后端就绪后替换为:短信验证码校验 + 手机号绑定 openId
|
// TODO 后端就绪后替换为:短信验证码校验 + 手机号绑定 openId
|
||||||
@@ -52,13 +322,23 @@ const bindPhoneBySms = (phone, smsCode) => {
|
|||||||
const logout = () => {
|
const logout = () => {
|
||||||
uni.removeStorageSync('phone')
|
uni.removeStorageSync('phone')
|
||||||
uni.removeStorageSync('openId')
|
uni.removeStorageSync('openId')
|
||||||
|
uni.removeStorageSync('order')
|
||||||
|
uni.removeStorageSync('decodedOrder')
|
||||||
|
uni.removeStorageSync('oauthCode')
|
||||||
|
uni.removeStorageSync('decodedOauthCode')
|
||||||
uni.removeStorageSync('userId')
|
uni.removeStorageSync('userId')
|
||||||
|
uni.removeStorageSync('isSubscribe')
|
||||||
uni.removeStorageSync('token')
|
uni.removeStorageSync('token')
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
isLogin,
|
isLogin,
|
||||||
requireLogin,
|
requireLogin,
|
||||||
|
captureIdentity,
|
||||||
|
captureOpenId,
|
||||||
|
oauthAuthorize,
|
||||||
|
code2OpenId,
|
||||||
|
loginByOrder,
|
||||||
loginByMock,
|
loginByMock,
|
||||||
loginByWechat,
|
loginByWechat,
|
||||||
bindPhoneBySms,
|
bindPhoneBySms,
|
||||||
|
|||||||
+7
-3
@@ -3,11 +3,15 @@ export default {
|
|||||||
// 后端测试环境地址
|
// 后端测试环境地址
|
||||||
apiBaseUrl: 'https://dscp-uat.hn12301.net',
|
apiBaseUrl: 'https://dscp-uat.hn12301.net',
|
||||||
// 开发模式:true = 假登录 + 假支付(本地调试用);后端微信接口就绪后改为 false 接真实流程
|
// 开发模式:true = 假登录 + 假支付(本地调试用);后端微信接口就绪后改为 false 接真实流程
|
||||||
devMode: true,
|
devMode: false,
|
||||||
// 产品名
|
// 产品名
|
||||||
appName: '质惠游',
|
appName: '质惠游',
|
||||||
// 测试手机号(测试环境已有数据的账号)
|
// 测试手机号(测试环境已有数据的账号)
|
||||||
testPhones: ['17763797832', '18684839400', '18710146266', '15367920401'],
|
testPhones: ['17763797832', '18684839400', '18710146266', '15367920401'],
|
||||||
// 微信公众号网页授权 appid(公众号信息确定后填入)
|
// 微信公众号 appid —— 微信 JSAPI 支付 appId 兜底(prepayWx 返回为空时使用)
|
||||||
wxAppid: ''
|
wxAppid: 'wxccfb072fa3ff58fa',
|
||||||
|
// 公众号网页授权 appid(OAuth 换 openId 用,与支付 appid 可能不同)
|
||||||
|
oauthAppid: 'wxccfb072fa3ff58fa',
|
||||||
|
// code 换 openId 接口(公众号授权回调 code → openId)
|
||||||
|
oauthCode2OpenIdUrl: 'h5-api/auth/oauth2MeiKe'
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ let queryTicketsOrderPageUrl = 'open-api/tickets/order/queryTicketsOrderPage'
|
|||||||
let cancelTicketsOrderUrl = 'open-api/tickets/order/cancelTicketsOrder'
|
let cancelTicketsOrderUrl = 'open-api/tickets/order/cancelTicketsOrder'
|
||||||
let refundTicketsOrderXghUrl = 'open-api/tickets/order/refundTicketsOrderXgh'
|
let refundTicketsOrderXghUrl = 'open-api/tickets/order/refundTicketsOrderXgh'
|
||||||
// xuchao: 微信预下单接口路径(后端接口就绪后确认,返回微信支付所需参数)
|
// xuchao: 微信预下单接口路径(后端接口就绪后确认,返回微信支付所需参数)
|
||||||
let prepayWxUrl = 'open-api/tickets/order/prepayWx'
|
let prepayWxUrl = 'open-api/tickets/order/prepay'
|
||||||
|
|
||||||
const install = (Vue, vm) => {
|
const install = (Vue, vm) => {
|
||||||
let queryTicketsPage = (params = {}) => vm.$u.post(`${queryTicketsPageUrl}`, params);
|
let queryTicketsPage = (params = {}) => vm.$u.post(`${queryTicketsPageUrl}`, params);
|
||||||
|
|||||||
+25
-8
@@ -27,21 +27,25 @@ const pay = (vm, { orderId, onSuccess, onFail }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 正式流程:微信 JSAPI 支付
|
// 正式流程:微信 JSAPI 支付
|
||||||
if (!isWechatBrowser()) {
|
const openId = uni.getStorageSync('openId')
|
||||||
uni.showToast({ icon: 'none', title: '请在微信中打开完成支付' })
|
if (!openId) {
|
||||||
onFail && onFail({ errMsg: 'not wechat browser' })
|
uni.showToast({ icon: 'none', title: '缺少登录身份,请重新进入' })
|
||||||
|
onFail && onFail({ errMsg: 'no openId' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const openId = uni.getStorageSync('openId')
|
console.log('[pay] prepay orderId=' + orderId + ' openId=' + openId)
|
||||||
vm.$u.api.prepayWx({ orderId, openId }).then(res => {
|
vm.$u.api.prepayWx({ orderId, openId, type: 1 }).then(res => {
|
||||||
if (!res) return
|
if (!res) return
|
||||||
const p = res.data || {}
|
const p = res.data || {}
|
||||||
|
console.log('[pay] prepay resp', p)
|
||||||
// 后端返回微信支付所需参数后,用此桥接唤起支付(微信浏览器内置,无需额外引入)
|
// 后端返回微信支付所需参数后,用此桥接唤起支付(微信浏览器内置,无需额外引入)
|
||||||
|
// appId 优先取后端返回值,为空时用 config.wxAppid 兜底
|
||||||
|
const invokePay = () => {
|
||||||
WeixinJSBridge.invoke('getBrandWCPayRequest', {
|
WeixinJSBridge.invoke('getBrandWCPayRequest', {
|
||||||
appId: p.appId,
|
appId: p.appId || config.wxAppid,
|
||||||
timeStamp: String(p.timeStamp),
|
timeStamp: String(p.timeStamp),
|
||||||
nonceStr: p.nonceStr,
|
nonceStr: p.nonceStr || p.nonce,
|
||||||
package: p.pack || p.packageValue,
|
package: p.packageValue || p.pack || p['package'],
|
||||||
signType: p.signType || 'RSA',
|
signType: p.signType || 'RSA',
|
||||||
paySign: p.paySign
|
paySign: p.paySign
|
||||||
}, bridgeRes => {
|
}, bridgeRes => {
|
||||||
@@ -51,6 +55,19 @@ const pay = (vm, { orderId, onSuccess, onFail }) => {
|
|||||||
onFail && onFail(bridgeRes)
|
onFail && onFail(bridgeRes)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
// 非微信环境 prepay 已拿到参数,唤起桥接这一步才提示需在微信中完成
|
||||||
|
if (!isWechatBrowser()) {
|
||||||
|
uni.showToast({ icon: 'none', title: '请在微信中打开完成支付' })
|
||||||
|
onFail && onFail({ errMsg: 'not wechat browser' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 桥接可能未就绪(冷启动进页立刻支付),挂 WeixinJSBridgeReady 事件等它就绪
|
||||||
|
if (typeof WeixinJSBridge === 'undefined') {
|
||||||
|
document.addEventListener('WeixinJSBridgeReady', invokePay, false)
|
||||||
|
} else {
|
||||||
|
invokePay()
|
||||||
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
onFail && onFail({ errMsg: 'prepay error' })
|
onFail && onFail({ errMsg: 'prepay error' })
|
||||||
})
|
})
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name" : "质惠游",
|
"name" : "质惠游",
|
||||||
"appid" : "",
|
"appid" : "__UNI__4E1C680",
|
||||||
"description" : "质惠游 H5(微信公众号网页)",
|
"description" : "质惠游 H5(微信公众号网页)",
|
||||||
"versionName" : "1.0.0",
|
"versionName" : "1.0.0",
|
||||||
"versionCode" : "100",
|
"versionCode" : "100",
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"vueVersion" : "2",
|
"vueVersion" : "2",
|
||||||
"h5" : {
|
"h5" : {
|
||||||
"router" : {
|
"router" : {
|
||||||
"base" : "/zhy/"
|
"base" : ""
|
||||||
},
|
},
|
||||||
"template" : "index.html",
|
"template" : "index.html",
|
||||||
"devServer" : {
|
"devServer" : {
|
||||||
|
|||||||
@@ -170,7 +170,7 @@
|
|||||||
"contactMobile": "",
|
"contactMobile": "",
|
||||||
"contactName": "",
|
"contactName": "",
|
||||||
"price": "",
|
"price": "",
|
||||||
"orderSource": "Channel_XGH",
|
"orderSource": "Channel_MEIKE",
|
||||||
"mobile": ""
|
"mobile": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,9 +76,11 @@
|
|||||||
},
|
},
|
||||||
onLoad() {
|
onLoad() {
|
||||||
that = this
|
that = this
|
||||||
// xuchao: 未登录则(正式)跳登录页 / (开发)静默假登录
|
// xuchao: 异步登录校验 —— 已登录直接进;未登录时用 URL 上的 order 解码码换身份;
|
||||||
if (!auth.requireLogin('/pages/ticket/scenic')) return
|
// 缺失/失败停留首页 Toast 提示,不跳登录页
|
||||||
this.queryTicketsPageMethod()
|
auth.requireLogin('/pages/ticket/scenic').then(ok => {
|
||||||
|
if (ok) this.queryTicketsPageMethod()
|
||||||
|
})
|
||||||
},
|
},
|
||||||
onReachBottom() {
|
onReachBottom() {
|
||||||
this.queryTicketsPageMethod()
|
this.queryTicketsPageMethod()
|
||||||
|
|||||||
Reference in New Issue
Block a user