Просмотр исходного кода

feat(web-admin): add mp webview shell and /m/pl/plan static routes.

Co-authored-by: Cursor <cursoragent@cursor.com>
wuzilong 1 месяц назад
Родитель
Сommit
8fbe0679c9

+ 15 - 0
web-admin/src/api/mp-webview.ts

@@ -0,0 +1,15 @@
+import request from '@/utils/http'
+
+export const mpWebviewApi = {
+  exchange: (ticket: string) =>
+    request.post<{
+      token: string
+      refreshToken: string
+      expiresIn: number
+      userInfo: Api.Auth.UserInfo
+    }>({
+      url: '/web/auth/mp-webview-exchange',
+      data: { ticket },
+      showErrorMessage: false
+    })
+}

+ 91 - 0
web-admin/src/layouts/MpWebviewShell.vue

@@ -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>

+ 42 - 0
web-admin/src/router/routes/staticRoutes.ts

@@ -129,6 +129,48 @@ export const staticRoutes: AppRouteRecordRaw[] = [
     component: () => import('@views/public/duty/index.vue'),
     meta: { title: '值班抢班', isHideTab: true, isPublicMobile: true }
   },
+  {
+    path: '/m/pl/plan',
+    name: 'MpPlPlanList',
+    component: () => import('@views/m/pl/plan/index.vue'),
+    meta: { title: '运输计划', isHideTab: true, isMpWebview: true }
+  },
+  {
+    path: '/m/pl/plan/new',
+    name: 'MpPlPlanNew',
+    component: () => import('@views/m/pl/plan/form.vue'),
+    meta: { title: '新增计划', isHideTab: true, isMpWebview: true }
+  },
+  {
+    path: '/m/pl/plan/paste',
+    name: 'MpPlPlanPaste',
+    component: () => import('@views/m/pl/plan/paste.vue'),
+    meta: { title: '三方人车快速创建', isHideTab: true, isMpWebview: true }
+  },
+  {
+    path: '/m/pl/plan/:id/edit',
+    name: 'MpPlPlanEdit',
+    component: () => import('@views/m/pl/plan/form.vue'),
+    meta: { title: '编辑计划', isHideTab: true, isMpWebview: true }
+  },
+  {
+    path: '/m/pl/plan/:id/arrange',
+    name: 'MpPlPlanArrange',
+    component: () => import('@views/m/pl/plan/form.vue'),
+    meta: { title: '安排运输', isHideTab: true, isMpWebview: true }
+  },
+  {
+    path: '/m/pl/plan/:id/print',
+    name: 'MpPlPlanPrint',
+    component: () => import('@views/m/pl/plan/print.vue'),
+    meta: { title: '打印单据', isHideTab: true, isMpWebview: true }
+  },
+  {
+    path: '/m/pl/plan/:id',
+    name: 'MpPlPlanDetail',
+    component: () => import('@views/m/pl/plan/detail.vue'),
+    meta: { title: '计划详情', isHideTab: true, isMpWebview: true }
+  },
   {
     path: '/:pathMatch(.*)*',
     name: 'Exception404CatchAll',

+ 18 - 0
web-admin/src/views/m/pl/plan/detail.vue

@@ -0,0 +1,18 @@
+<template>
+  <MpWebviewShell>
+    <p class="mp-wv-placeholder">计划详情加载中…</p>
+  </MpWebviewShell>
+</template>
+
+<script setup lang="ts">
+  import MpWebviewShell from '@/layouts/MpWebviewShell.vue'
+</script>
+
+<style scoped>
+  .mp-wv-placeholder {
+    margin: 24px 0 0;
+    font-size: 14px;
+    color: #6b7b74;
+    text-align: center;
+  }
+</style>

+ 18 - 0
web-admin/src/views/m/pl/plan/form.vue

@@ -0,0 +1,18 @@
+<template>
+  <MpWebviewShell>
+    <p class="mp-wv-placeholder">计划表单加载中…</p>
+  </MpWebviewShell>
+</template>
+
+<script setup lang="ts">
+  import MpWebviewShell from '@/layouts/MpWebviewShell.vue'
+</script>
+
+<style scoped>
+  .mp-wv-placeholder {
+    margin: 24px 0 0;
+    font-size: 14px;
+    color: #6b7b74;
+    text-align: center;
+  }
+</style>

+ 18 - 0
web-admin/src/views/m/pl/plan/index.vue

@@ -0,0 +1,18 @@
+<template>
+  <MpWebviewShell>
+    <p class="mp-wv-placeholder">运输计划加载中…</p>
+  </MpWebviewShell>
+</template>
+
+<script setup lang="ts">
+  import MpWebviewShell from '@/layouts/MpWebviewShell.vue'
+</script>
+
+<style scoped>
+  .mp-wv-placeholder {
+    margin: 24px 0 0;
+    font-size: 14px;
+    color: #6b7b74;
+    text-align: center;
+  }
+</style>

+ 18 - 0
web-admin/src/views/m/pl/plan/paste.vue

@@ -0,0 +1,18 @@
+<template>
+  <MpWebviewShell>
+    <p class="mp-wv-placeholder">三方人车快速创建加载中…</p>
+  </MpWebviewShell>
+</template>
+
+<script setup lang="ts">
+  import MpWebviewShell from '@/layouts/MpWebviewShell.vue'
+</script>
+
+<style scoped>
+  .mp-wv-placeholder {
+    margin: 24px 0 0;
+    font-size: 14px;
+    color: #6b7b74;
+    text-align: center;
+  }
+</style>

+ 18 - 0
web-admin/src/views/m/pl/plan/print.vue

@@ -0,0 +1,18 @@
+<template>
+  <MpWebviewShell>
+    <p class="mp-wv-placeholder">打印预览加载中…</p>
+  </MpWebviewShell>
+</template>
+
+<script setup lang="ts">
+  import MpWebviewShell from '@/layouts/MpWebviewShell.vue'
+</script>
+
+<style scoped>
+  .mp-wv-placeholder {
+    margin: 24px 0 0;
+    font-size: 14px;
+    color: #6b7b74;
+    text-align: center;
+  }
+</style>