feat(msync): 远端 App 打开链路——用户令牌签发拼 URL + 服务离线标记 + /remote 路由
This commit is contained in:
1 parent
7e64bdf38a
commit
a19874da72
5 files changed
+82
-7
No files matched your search
@@ -24,6 +24,11 @@ export interface AppItem {
|
|||||||
InstalledCompanies?: string[]
|
InstalledCompanies?: string[]
|
||||||
UserEnabled?: number
|
UserEnabled?: number
|
||||||
ApplyStatus?: number
|
ApplyStatus?: number
|
||||||
|
/** 远端注入(MSYNC):来源/提供方服务/在线状态 */
|
||||||
|
Source?: string
|
||||||
|
ServiceName?: string
|
||||||
|
ServiceDomain?: string
|
||||||
|
ServiceOnline?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 管理员可安装应用的公司范围项 */
|
/** 管理员可安装应用的公司范围项 */
|
||||||
|
|||||||
@@ -25,10 +25,19 @@ export interface AppFrontendModule {
|
|||||||
FrontendRoute: string
|
FrontendRoute: string
|
||||||
FrontendType: 'vue' | 'legacy'
|
FrontendType: 'vue' | 'legacy'
|
||||||
LegacySrc: string
|
LegacySrc: string
|
||||||
|
/** 远端注入(MSYNC):提供方服务名(打开远端 legacy 页面时签发用户令牌用) */
|
||||||
|
ServiceName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 打开应用的跳转目标(vue 走前端路由;legacy 走对应 LegacyPage 路由) */
|
/** 打开应用的跳转目标(vue 走前端路由;legacy 走对应 LegacyPage 路由) */
|
||||||
export function appOpenTarget(mod: AppFrontendModule): RouteLocationRaw {
|
export function appOpenTarget(mod: AppFrontendModule): RouteLocationRaw {
|
||||||
|
// 远端 legacy(绝对 URL,子服务器托管):走 /remote/:code,带 src+svc 供令牌透传
|
||||||
|
if (mod.FrontendType === 'legacy' && /^https?:\/\//.test(mod.LegacySrc)) {
|
||||||
|
return {
|
||||||
|
path: `/remote/${encodeURIComponent(mod.AppCode)}`,
|
||||||
|
query: { src: mod.LegacySrc, svc: mod.ServiceName || '' },
|
||||||
|
}
|
||||||
|
}
|
||||||
return mod.FrontendRoute || (mod.LegacySrc ? `/${mod.LegacySrc}` : '/app/mine')
|
return mod.FrontendRoute || (mod.LegacySrc ? `/${mod.LegacySrc}` : '/app/mine')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -330,6 +330,13 @@ export const staticRoutes: RouteRecordRaw[] = [
|
|||||||
meta: { title: '运行时调整', icon: 'Operation', requiresAuth: true, menuGuarded: true },
|
meta: { title: '运行时调整', icon: 'Operation', requiresAuth: true, menuGuarded: true },
|
||||||
},
|
},
|
||||||
// === 枢机协律(CreateE):旧页面 iframe 直载(LegacyPage) ===
|
// === 枢机协律(CreateE):旧页面 iframe 直载(LegacyPage) ===
|
||||||
|
// === 远端 App(MSYNC 注入):子服务器托管页面 iframe,query 携带 src+svc 令牌透传 ===
|
||||||
|
{
|
||||||
|
path: 'remote/:appCode',
|
||||||
|
name: 'RemoteApp',
|
||||||
|
component: () => import('@/views/LegacyPage.vue'),
|
||||||
|
meta: { title: '远端应用', requiresAuth: true },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'private/CreateE/shop',
|
path: 'private/CreateE/shop',
|
||||||
name: 'PrivateCreateEShop',
|
name: 'PrivateCreateEShop',
|
||||||
|
|||||||
@@ -2,18 +2,50 @@
|
|||||||
/**
|
/**
|
||||||
* LegacyPage
|
* LegacyPage
|
||||||
*
|
*
|
||||||
* 通用旧页面承载组件:以 iframe 直载 TempLate/pages/ 下的原生 HTML 页面。
|
* 通用旧页面承载组件:以 iframe 直载原生 HTML 页面。
|
||||||
* 用于尚未迁移到 Vue 的历史菜单(如"枢机协律"系列的商店/钱包/充值等)。
|
* 1. 本地旧页面:路由 meta.legacySrc 指定路径(如 /pages/private/CreateE/shop.html)
|
||||||
* 通过路由 meta.legacySrc 指定 iframe 加载的原始 HTML 路径。
|
* 2. 远端 App(MSYNC 注入):路由 query 携带 src(子服务器绝对 URL)+ svc(提供方服务名);
|
||||||
|
* 加载前先向主服务器签发用户级短期令牌,拼到 URL(?token=)透传员工身份,
|
||||||
|
* 子服务器经 /api/msync/user/token/introspect/ 自省还原用户上下文。
|
||||||
*/
|
*/
|
||||||
import { computed, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
import { post } from '@/api/request'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
// 旧页面原始路径(如 /pages/private/CreateE/shop.html),由路由 meta 传入
|
// 本地旧页面:meta.legacySrc;远端:query.src(绝对 URL)
|
||||||
const src = computed(() => (route.meta.legacySrc as string) || '')
|
const src = computed(() => (route.query.src as string) || (route.meta.legacySrc as string) || '')
|
||||||
|
// 远端提供方服务名(签发用户令牌的 AudienceService)
|
||||||
|
const svc = computed(() => (route.query.svc as string) || '')
|
||||||
|
const isRemote = computed(() => /^https?:\/\//.test(src.value))
|
||||||
|
|
||||||
|
const iframeSrc = ref(src.value)
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
|
const tokenError = ref(false)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!isRemote.value || !svc.value) {
|
||||||
|
iframeSrc.value = src.value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 远端:签发用户级短期令牌 → 拼 ?token= 透传员工身份
|
||||||
|
try {
|
||||||
|
const r = await post<{ AccessToken: string }>('/API/MSync/UserToken/Issue', {
|
||||||
|
AudienceService: svc.value,
|
||||||
|
})
|
||||||
|
const token = r?.AccessToken
|
||||||
|
if (!token) {
|
||||||
|
tokenError.value = true
|
||||||
|
} else {
|
||||||
|
const sep = src.value.includes('?') ? '&' : '?'
|
||||||
|
iframeSrc.value = `${src.value}${sep}token=${encodeURIComponent(token)}`
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
tokenError.value = true
|
||||||
|
iframeSrc.value = src.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function onLoad(): void {
|
function onLoad(): void {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -22,10 +54,17 @@ function onLoad(): void {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="legacy-page">
|
<div class="legacy-page">
|
||||||
|
<el-alert
|
||||||
|
v-if="tokenError"
|
||||||
|
title="无法获取用户访问令牌,远端页面可能无法识别您的身份"
|
||||||
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
class="legacy-page-alert"
|
||||||
|
/>
|
||||||
<el-skeleton v-if="loading" :rows="6" animated class="legacy-page-skeleton" />
|
<el-skeleton v-if="loading" :rows="6" animated class="legacy-page-skeleton" />
|
||||||
<iframe
|
<iframe
|
||||||
v-show="!loading"
|
v-show="!loading"
|
||||||
:src="src"
|
:src="iframeSrc"
|
||||||
class="legacy-page-frame"
|
class="legacy-page-frame"
|
||||||
frameborder="0"
|
frameborder="0"
|
||||||
@load="onLoad"
|
@load="onLoad"
|
||||||
@@ -42,6 +81,16 @@ function onLoad(): void {
|
|||||||
background: var(--el-bg-color-page);
|
background: var(--el-bg-color-page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.legacy-page-alert {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 10;
|
||||||
|
width: max-content;
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
.legacy-page-skeleton {
|
.legacy-page-skeleton {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,6 +260,7 @@ function openApp(a: AppItem): void {
|
|||||||
FrontendRoute: a.FrontendRoute || '',
|
FrontendRoute: a.FrontendRoute || '',
|
||||||
FrontendType: (a.FrontendType as 'vue' | 'legacy') || 'vue',
|
FrontendType: (a.FrontendType as 'vue' | 'legacy') || 'vue',
|
||||||
LegacySrc: a.LegacySrc || '',
|
LegacySrc: a.LegacySrc || '',
|
||||||
|
ServiceName: a.ServiceName || '',
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,6 +561,10 @@ onMounted(loadMarket)
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--el-text-color-placeholder);
|
color: var(--el-text-color-placeholder);
|
||||||
}
|
}
|
||||||
|
.app-offline-tag {
|
||||||
|
margin-left: 6px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
.app-desc {
|
.app-desc {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
Reference in new issue
Block a user