index.vue 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. <template>
  2. <view class="web-qr-page">
  3. <view class="web-qr-brand">
  4. <image
  5. class="web-qr-brand__logo"
  6. src="/static/logo.png"
  7. mode="widthFix"
  8. />
  9. <view class="web-qr-brand__title">网页登录授权</view>
  10. </view>
  11. <view v-if="!sessionId" class="web-qr-card web-qr-card--error">
  12. <text class="web-qr-msg">请从电脑登录页重新扫码</text>
  13. </view>
  14. <view v-else-if="done" class="web-qr-card web-qr-card--ok">
  15. <text class="web-qr-msg web-qr-msg--ok">电脑端即将登录,可关闭本页</text>
  16. </view>
  17. <view v-else class="web-qr-card">
  18. <text class="web-qr-desc">
  19. 正在登录电脑端管理后台,请授权手机号完成验证
  20. </text>
  21. <view class="login-agree" @click="toggleAgree">
  22. <view class="login-agree__box" :class="{ 'is-on': agreed }">
  23. <text v-if="agreed" class="login-agree__check">✓</text>
  24. </view>
  25. <view class="login-agree__text">
  26. 已阅读并同意
  27. <text class="login-agree__link" @click.stop="openProtocol('user')">
  28. 《用户协议》
  29. </text>
  30. 和
  31. <text class="login-agree__link" @click.stop="openProtocol('privacy')">
  32. 《隐私政策》
  33. </text>
  34. </view>
  35. </view>
  36. <button
  37. v-if="agreed"
  38. class="web-qr-btn oa-btn-loading"
  39. :class="{ 'is-loading': loading }"
  40. open-type="getPhoneNumber"
  41. :disabled="loading"
  42. @getphonenumber="onGetPhoneNumber"
  43. >
  44. <view class="oa-btn-loading__label">授权手机号并登录电脑端</view>
  45. <view v-if="loading" class="oa-btn-loading__spin" aria-hidden="true" />
  46. </button>
  47. <view
  48. v-else
  49. class="web-qr-btn oa-btn-loading"
  50. :class="{ 'is-loading': loading }"
  51. @click="onWechatGate"
  52. >
  53. <view class="oa-btn-loading__label">授权手机号并登录电脑端</view>
  54. <view v-if="loading" class="oa-btn-loading__spin" aria-hidden="true" />
  55. </view>
  56. </view>
  57. <ConfirmDialog
  58. :show="agreeDialogVisible"
  59. title="请先同意协议"
  60. cancel-text="取消"
  61. confirm-text="同意并授权"
  62. :confirm-open-type="agreeDialogForWechat ? 'getPhoneNumber' : ''"
  63. @update:show="agreeDialogVisible = $event"
  64. @confirm="onAgreeConfirm"
  65. @cancel="onAgreeCancel"
  66. @getphonenumber="onAgreeGetPhoneNumber"
  67. >
  68. <text>授权前需阅读并同意</text>
  69. <text class="confirm-dialog__link" @click.stop="openProtocol('user')">
  70. 《用户协议》
  71. </text>
  72. <text>和</text>
  73. <text class="confirm-dialog__link" @click.stop="openProtocol('privacy')">
  74. 《隐私政策》
  75. </text>
  76. <text>。是否同意并继续?</text>
  77. </ConfirmDialog>
  78. </view>
  79. </template>
  80. <script>
  81. import { webQrScanned, webQrConfirm } from '../../../utils/auth.js'
  82. import ConfirmDialog from '../../../components/ConfirmDialog.vue'
  83. export default {
  84. components: {
  85. ConfirmDialog
  86. },
  87. data() {
  88. return {
  89. sessionId: '',
  90. done: false,
  91. agreed: false,
  92. agreeDialogVisible: false,
  93. agreeDialogForWechat: false,
  94. loading: false
  95. }
  96. },
  97. onLoad(options) {
  98. const opts = options || {}
  99. let sid = ''
  100. if (opts.scene) {
  101. try {
  102. sid = decodeURIComponent(String(opts.scene))
  103. } catch (e) {
  104. sid = String(opts.scene)
  105. }
  106. } else if (opts.sessionId) {
  107. sid = String(opts.sessionId)
  108. }
  109. sid = (sid || '').trim()
  110. this.sessionId = sid
  111. if (!sid) return
  112. webQrScanned(sid).catch(() => {
  113. /* 软失败:PC 仍可继续轮询或用户可重试授权 */
  114. })
  115. },
  116. methods: {
  117. toggleAgree() {
  118. this.agreed = !this.agreed
  119. },
  120. onWechatGate() {
  121. if (this.loading) return
  122. this.agreeDialogForWechat = true
  123. this.agreeDialogVisible = true
  124. },
  125. onAgreeConfirm() {
  126. this.agreed = true
  127. this.agreeDialogVisible = false
  128. this.agreeDialogForWechat = false
  129. },
  130. onAgreeCancel() {
  131. this.agreeDialogVisible = false
  132. this.agreeDialogForWechat = false
  133. },
  134. onAgreeGetPhoneNumber(e) {
  135. this.agreed = true
  136. this.agreeDialogVisible = false
  137. this.agreeDialogForWechat = false
  138. this.onGetPhoneNumber(e)
  139. },
  140. openProtocol(type) {
  141. uni.navigateTo({ url: `/pages/protocol/index?type=${type}` })
  142. },
  143. async onGetPhoneNumber(e) {
  144. if (this.loading || this.done || !this.sessionId) return
  145. if (!this.agreed) {
  146. this.agreeDialogForWechat = true
  147. this.agreeDialogVisible = true
  148. return
  149. }
  150. const detail = (e && e.detail) || {}
  151. const errMsg = detail.errMsg || ''
  152. if ((errMsg && errMsg.indexOf('ok') === -1) || !detail.code) {
  153. uni.showToast({
  154. title: '需要授权手机号才能完成扫码登录',
  155. icon: 'none'
  156. })
  157. return
  158. }
  159. this.loading = true
  160. try {
  161. const loginRes = await new Promise((resolve, reject) => {
  162. uni.login({ provider: 'weixin', success: resolve, fail: reject })
  163. })
  164. const loginCode = loginRes && loginRes.code
  165. if (!loginCode) {
  166. throw new Error('微信登录失败,请重试')
  167. }
  168. await webQrConfirm({
  169. sessionId: this.sessionId,
  170. loginCode,
  171. phoneCode: detail.code
  172. })
  173. this.done = true
  174. } catch (err) {
  175. uni.showToast({
  176. title: (err && err.message) || '授权失败',
  177. icon: 'none'
  178. })
  179. } finally {
  180. this.loading = false
  181. }
  182. }
  183. }
  184. }
  185. </script>
  186. <style scoped>
  187. .web-qr-page {
  188. min-height: 100vh;
  189. padding: 120rpx 48rpx 64rpx;
  190. box-sizing: border-box;
  191. background: #fff;
  192. }
  193. .web-qr-brand {
  194. text-align: center;
  195. margin-bottom: 48rpx;
  196. }
  197. .web-qr-brand__logo {
  198. display: block;
  199. width: 320rpx;
  200. margin: 0 auto;
  201. border-radius: 16rpx;
  202. }
  203. .web-qr-brand__title {
  204. margin-top: 24rpx;
  205. font-size: 34rpx;
  206. font-weight: 600;
  207. color: #0a9f6d;
  208. }
  209. .web-qr-card {
  210. background: #f7faf8;
  211. border: 1px solid #e2ebe6;
  212. border-radius: 24rpx;
  213. padding: 40rpx 32rpx;
  214. }
  215. .web-qr-card--error .web-qr-msg {
  216. color: #c45656;
  217. }
  218. .web-qr-card--ok {
  219. background: #e9f9f2;
  220. border-color: #a7f3d0;
  221. }
  222. .web-qr-desc {
  223. display: block;
  224. font-size: 28rpx;
  225. line-height: 1.6;
  226. color: #334155;
  227. margin-bottom: 36rpx;
  228. }
  229. .web-qr-msg {
  230. display: block;
  231. font-size: 28rpx;
  232. line-height: 1.6;
  233. text-align: center;
  234. color: #334155;
  235. }
  236. .web-qr-msg--ok {
  237. color: #087f57;
  238. font-weight: 500;
  239. }
  240. .web-qr-btn {
  241. margin-top: 32rpx;
  242. height: 88rpx;
  243. border-radius: 44rpx;
  244. background: #0a9f6d;
  245. color: #fff;
  246. font-size: 30rpx;
  247. font-weight: 500;
  248. display: flex;
  249. align-items: center;
  250. justify-content: center;
  251. border: none;
  252. padding: 0;
  253. line-height: 1;
  254. }
  255. .web-qr-btn.is-loading {
  256. opacity: 0.75;
  257. }
  258. button.web-qr-btn::after {
  259. border: none;
  260. }
  261. </style>