| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 |
- <script setup>
- import { ref } from 'vue'
- import { onLoad } from '@dcloudio/uni-app'
- import { useUserStore } from '@/stores/user'
- import { DEFAULT_PLACEHOLDER_IMAGE_URL } from '@/utils/staticAsset'
- const userStore = useUserStore()
- const sessionId = ref('')
- const done = ref(false)
- const agreeProtocol = ref(false)
- const isLoading = ref(false)
- const protocolDialogVisible = ref(false)
- const protocolDialogForWechat = ref(false)
- onLoad((options) => {
- const opts = options || {}
- let sid = ''
- if (opts.scene) {
- try {
- sid = decodeURIComponent(String(opts.scene))
- } catch (e) {
- sid = String(opts.scene)
- }
- } else if (opts.sessionId) {
- sid = String(opts.sessionId)
- }
- sid = (sid || '').trim()
- sessionId.value = sid
- if (!sid) return
- userStore.webQrScanned(sid).catch(() => {
- /* 软失败:PC 仍可继续轮询或用户可重试授权 */
- })
- })
- const goUserAgreement = () => {
- uni.navigateTo({ url: '/pages/login/user-agreement' })
- }
- const goPrivacyPolicy = () => {
- uni.navigateTo({ url: '/pages/login/privacy-policy' })
- }
- const toggleProtocolAgreement = () => {
- agreeProtocol.value = !agreeProtocol.value
- }
- const resolveProtocolDialog = (confirmed) => {
- protocolDialogVisible.value = false
- protocolDialogForWechat.value = false
- if (confirmed) {
- agreeProtocol.value = true
- }
- }
- const onWechatGate = () => {
- if (isLoading.value || done.value) return
- protocolDialogForWechat.value = true
- protocolDialogVisible.value = true
- }
- const onAgreeGetPhoneNumber = (e) => {
- agreeProtocol.value = true
- protocolDialogVisible.value = false
- protocolDialogForWechat.value = false
- onGetPhoneNumber(e)
- }
- const onGetPhoneNumber = async (e) => {
- if (isLoading.value || done.value || !sessionId.value) return
- if (!agreeProtocol.value) {
- onWechatGate()
- return
- }
- const detail = (e && e.detail) || {}
- const errMsg = detail.errMsg || ''
- if ((errMsg && errMsg.indexOf('ok') === -1) || !detail.code) {
- uni.showToast({
- title: '需要授权手机号才能完成扫码登录',
- icon: 'none'
- })
- return
- }
- isLoading.value = true
- try {
- const loginRes = await new Promise((resolve, reject) => {
- uni.login({ provider: 'weixin', success: resolve, fail: reject })
- })
- const loginCode = loginRes && loginRes.code
- if (!loginCode) {
- throw new Error('微信登录失败,请重试')
- }
- const result = await userStore.webQrConfirm({
- sessionId: sessionId.value,
- loginCode,
- phoneCode: detail.code
- })
- if (result && result.ok) {
- done.value = true
- uni.showToast({
- title: result.message || '电脑端即将登录,可关闭本页',
- icon: 'none'
- })
- return
- }
- uni.showToast({
- title: (result && result.message) || '授权失败',
- icon: 'none'
- })
- } catch (err) {
- uni.showToast({
- title: (err && err.message) || '授权失败',
- icon: 'none'
- })
- } finally {
- isLoading.value = false
- }
- }
- </script>
- <template>
- <view class="web-qr-page">
- <view class="web-qr-brand">
- <image class="web-qr-brand__logo" :src="DEFAULT_PLACEHOLDER_IMAGE_URL" mode="widthFix" />
- <view class="web-qr-brand__title">网页登录授权</view>
- </view>
- <view v-if="!sessionId" class="web-qr-card web-qr-card--error">
- <text class="web-qr-msg">请从电脑登录页重新扫码</text>
- </view>
- <view v-else-if="done" class="web-qr-card web-qr-card--ok">
- <text class="web-qr-msg web-qr-msg--ok">电脑端即将登录,可关闭本页</text>
- </view>
- <view v-else class="web-qr-card">
- <text class="web-qr-desc">正在登录电脑端管理后台,请授权手机号完成验证</text>
- <view class="footer-line">
- <view class="agree-check" @tap="toggleProtocolAgreement">
- <view class="agree-check__icon" :class="{ 'agree-check__icon--checked': agreeProtocol }">
- <text class="agree-check__tick">✓</text>
- </view>
- </view>
- <text class="footer-text">授权即表示同意</text>
- <text class="link" @tap="goUserAgreement">《用户服务协议》</text>
- <text class="footer-text">和</text>
- <text class="link" @tap="goPrivacyPolicy">《隐私政策》</text>
- </view>
- <button
- v-if="agreeProtocol"
- class="web-qr-btn"
- :disabled="isLoading"
- open-type="getPhoneNumber"
- @getphonenumber="onGetPhoneNumber"
- >
- <text class="web-qr-btn__label">{{ isLoading ? '授权中...' : '授权手机号并登录电脑端' }}</text>
- </button>
- <view
- v-else
- class="web-qr-btn"
- :class="{ 'is-disabled': isLoading }"
- @tap="onWechatGate"
- >
- <text class="web-qr-btn__label">授权手机号并登录电脑端</text>
- </view>
- </view>
- <view
- v-if="protocolDialogVisible"
- class="protocol-dialog-mask"
- @tap="resolveProtocolDialog(false)"
- >
- <view class="protocol-dialog" @tap.stop>
- <text class="protocol-dialog__title">协议确认</text>
- <text class="protocol-dialog__desc">授权前请先同意《用户服务协议》和《隐私政策》</text>
- <view class="protocol-dialog__actions">
- <view
- class="protocol-dialog__btn protocol-dialog__btn--cancel"
- @tap="resolveProtocolDialog(false)"
- >
- 否
- </view>
- <button
- v-if="protocolDialogForWechat"
- class="protocol-dialog__btn protocol-dialog__btn--confirm"
- open-type="getPhoneNumber"
- @getphonenumber="onAgreeGetPhoneNumber"
- >
- 同意并授权
- </button>
- <view
- v-else
- class="protocol-dialog__btn protocol-dialog__btn--confirm"
- @tap="resolveProtocolDialog(true)"
- >
- 是,继续
- </view>
- </view>
- </view>
- </view>
- </view>
- </template>
- <style lang="scss" scoped>
- .web-qr-page {
- min-height: 100vh;
- padding: 120rpx 48rpx 64rpx;
- box-sizing: border-box;
- background: #fff;
- }
- .web-qr-brand {
- text-align: center;
- margin-bottom: 48rpx;
- }
- .web-qr-brand__logo {
- display: block;
- width: 320rpx;
- margin: 0 auto;
- border-radius: 16rpx;
- }
- .web-qr-brand__title {
- margin-top: 24rpx;
- font-size: 34rpx;
- font-weight: 600;
- color: #0a9f6d;
- }
- .web-qr-card {
- background: #f7faf8;
- border: 1px solid #e2ebe6;
- border-radius: 24rpx;
- padding: 40rpx 32rpx;
- }
- .web-qr-card--error .web-qr-msg {
- color: #c45656;
- }
- .web-qr-card--ok {
- background: #e9f9f2;
- border-color: #a7f3d0;
- }
- .web-qr-desc {
- display: block;
- font-size: 28rpx;
- line-height: 1.6;
- color: #334155;
- margin-bottom: 36rpx;
- }
- .web-qr-msg {
- display: block;
- font-size: 28rpx;
- line-height: 1.6;
- text-align: center;
- color: #334155;
- }
- .web-qr-msg--ok {
- color: #087f57;
- font-weight: 500;
- }
- .footer-line {
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
- align-items: center;
- column-gap: 4rpx;
- row-gap: 8rpx;
- margin-bottom: 8rpx;
- }
- .agree-check {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- padding: 8rpx;
- margin-right: 4rpx;
- flex-shrink: 0;
- }
- .agree-check__icon {
- width: 30rpx;
- height: 30rpx;
- border-radius: 50%;
- border: 2rpx solid #c0c4cc;
- box-sizing: border-box;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #fff;
- }
- .agree-check__tick {
- font-size: 18rpx;
- font-weight: 700;
- line-height: 1;
- color: transparent;
- }
- .agree-check__icon--checked {
- border-color: #0a9f6d;
- background: #0a9f6d;
- }
- .agree-check__icon--checked .agree-check__tick {
- color: #fff;
- }
- .footer-text,
- .link {
- font-size: 22rpx;
- line-height: 1.6;
- }
- .footer-text {
- color: #909399;
- }
- .link {
- color: #0a9f6d;
- }
- .web-qr-btn {
- margin-top: 32rpx;
- height: 88rpx;
- border-radius: 44rpx;
- background: #0a9f6d;
- color: #fff;
- font-size: 30rpx;
- font-weight: 500;
- display: flex;
- align-items: center;
- justify-content: center;
- border: none;
- padding: 0;
- line-height: 1;
- width: 100%;
- box-sizing: border-box;
- }
- .web-qr-btn.is-disabled {
- opacity: 0.75;
- }
- .web-qr-btn__label {
- color: #fff;
- font-size: 30rpx;
- font-weight: 500;
- }
- button.web-qr-btn::after {
- border: none;
- }
- .protocol-dialog-mask {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.42);
- z-index: 99;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 40rpx;
- box-sizing: border-box;
- }
- .protocol-dialog {
- width: 100%;
- max-width: 620rpx;
- background: #fff;
- border-radius: 24rpx;
- padding: 36rpx 30rpx 28rpx;
- box-sizing: border-box;
- box-shadow: 0 14rpx 48rpx rgba(0, 0, 0, 0.16);
- }
- .protocol-dialog__title {
- display: block;
- text-align: center;
- font-size: 34rpx;
- font-weight: 700;
- color: #303133;
- }
- .protocol-dialog__desc {
- display: block;
- margin-top: 16rpx;
- text-align: center;
- font-size: 26rpx;
- line-height: 1.6;
- color: #606266;
- }
- .protocol-dialog__actions {
- margin-top: 30rpx;
- display: flex;
- gap: 16rpx;
- }
- .protocol-dialog__btn {
- flex: 1;
- min-height: 78rpx;
- border-radius: 999rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 28rpx;
- font-weight: 600;
- }
- .protocol-dialog__btn--cancel {
- background: #f4f7f6;
- color: #5b6b64;
- border: 1px solid #ddebe5;
- }
- .protocol-dialog__btn--confirm {
- background: #0a9f6d;
- color: #fff;
- border: none;
- margin: 0;
- padding: 0;
- line-height: normal;
- }
- button.protocol-dialog__btn--confirm::after {
- border: none;
- }
- </style>
|