|
|
@@ -0,0 +1,91 @@
|
|
|
+<template>
|
|
|
+ <div class="mp-wv-root">
|
|
|
+ <p v-if="phase === 'boot'" class="mp-wv-placeholder">加载中…</p>
|
|
|
+ <p v-else-if="phase === 'need-mini'" class="mp-wv-placeholder">请从山峡化工小程序打开</p>
|
|
|
+ <p v-else-if="phase === 'expired'" class="mp-wv-placeholder">登录已失效</p>
|
|
|
+ <slot v-else />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { onMounted, ref } from 'vue'
|
|
|
+ import { useRoute, useRouter } from 'vue-router'
|
|
|
+ import { useUserStore } from '@/store/modules/user'
|
|
|
+ import { useDictStore } from '@/store/modules/dict'
|
|
|
+ import { mpWebviewApi } from '@/api/mp-webview'
|
|
|
+ import { redirectToMiniLogin, stripMpTicketFromUrl } from '@/utils/mp-webview'
|
|
|
+
|
|
|
+ const JWX_SRC = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js'
|
|
|
+
|
|
|
+ const route = useRoute()
|
|
|
+ const router = useRouter()
|
|
|
+ const userStore = useUserStore()
|
|
|
+ const dictStore = useDictStore()
|
|
|
+ const phase = ref<'boot' | 'need-mini' | 'expired' | 'ready'>('boot')
|
|
|
+
|
|
|
+ function loadJweixin(): Promise<void> {
|
|
|
+ if (typeof window === 'undefined') return Promise.resolve()
|
|
|
+ const wxObj = (window as unknown as { wx?: { miniProgram?: unknown } }).wx
|
|
|
+ if (wxObj?.miniProgram) return Promise.resolve()
|
|
|
+ if (document.querySelector('script[data-mp-jweixin]')) return Promise.resolve()
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ const script = document.createElement('script')
|
|
|
+ script.src = JWX_SRC
|
|
|
+ script.async = true
|
|
|
+ script.setAttribute('data-mp-jweixin', '1')
|
|
|
+ script.onload = () => resolve()
|
|
|
+ script.onerror = () => resolve()
|
|
|
+ document.head.appendChild(script)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ async function bootstrap() {
|
|
|
+ await loadJweixin()
|
|
|
+ const ticket = String(route.query.mpTicket || '').trim()
|
|
|
+ if (ticket) {
|
|
|
+ try {
|
|
|
+ const data = await mpWebviewApi.exchange(ticket)
|
|
|
+ userStore.setToken(data.token, data.refreshToken)
|
|
|
+ userStore.setUserInfo(data.userInfo)
|
|
|
+ userStore.setLoginStatus(true)
|
|
|
+ await dictStore.loadAllDict()
|
|
|
+ const nextQuery = stripMpTicketFromUrl(route.path, { ...route.query })
|
|
|
+ await router.replace({ path: route.path, query: nextQuery as Record<string, string> })
|
|
|
+ phase.value = 'ready'
|
|
|
+ } catch {
|
|
|
+ phase.value = 'expired'
|
|
|
+ redirectToMiniLogin()
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (userStore.isLogin) {
|
|
|
+ phase.value = 'ready'
|
|
|
+ return
|
|
|
+ }
|
|
|
+ phase.value = 'need-mini'
|
|
|
+ }
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ void bootstrap()
|
|
|
+ })
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+ .mp-wv-root {
|
|
|
+ --brand: #0a9f6d;
|
|
|
+ box-sizing: border-box;
|
|
|
+ min-height: 100vh;
|
|
|
+ max-width: 480px;
|
|
|
+ margin: 0 auto;
|
|
|
+ padding: 12px 16px calc(24px + env(safe-area-inset-bottom));
|
|
|
+ color: #1f2a26;
|
|
|
+ background: #f5f8f7;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mp-wv-placeholder {
|
|
|
+ margin: 48px 0 0;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #6b7b74;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+</style>
|