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

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-09-14 16:21:37 +08:00
1 parent c0721786ff
commit 8fbe0679c9
8 files changed
+238

No files matched your search

+15
View File
@@ -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
View File
@@ -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>
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>