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[]
|
||||
UserEnabled?: number
|
||||
ApplyStatus?: number
|
||||
/** 远端注入(MSYNC):来源/提供方服务/在线状态 */
|
||||
Source?: string
|
||||
ServiceName?: string
|
||||
ServiceDomain?: string
|
||||
ServiceOnline?: number
|
||||
}
|
||||
|
||||
/** 管理员可安装应用的公司范围项 */
|
||||
|
||||
@@ -25,10 +25,19 @@ export interface AppFrontendModule {
|
||||
FrontendRoute: string
|
||||
FrontendType: 'vue' | 'legacy'
|
||||
LegacySrc: string
|
||||
/** 远端注入(MSYNC):提供方服务名(打开远端 legacy 页面时签发用户令牌用) */
|
||||
ServiceName?: string
|
||||
}
|
||||
|
||||
/** 打开应用的跳转目标(vue 走前端路由;legacy 走对应 LegacyPage 路由) */
|
||||
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')
|
||||
}
|
||||
|
||||
|
||||
@@ -330,6 +330,13 @@ export const staticRoutes: RouteRecordRaw[] = [
|
||||
meta: { title: '运行时调整', icon: 'Operation', requiresAuth: true, menuGuarded: true },
|
||||
},
|
||||
// === 枢机协律(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',
|
||||
name: 'PrivateCreateEShop',
|
||||
|
||||
@@ -2,18 +2,50 @@
|
||||
/**
|
||||
* LegacyPage
|
||||
*
|
||||
* 通用旧页面承载组件:以 iframe 直载 TempLate/pages/ 下的原生 HTML 页面。
|
||||
* 用于尚未迁移到 Vue 的历史菜单(如"枢机协律"系列的商店/钱包/充值等)。
|
||||
* 通过路由 meta.legacySrc 指定 iframe 加载的原始 HTML 路径。
|
||||
* 通用旧页面承载组件:以 iframe 直载原生 HTML 页面。
|
||||
* 1. 本地旧页面:路由 meta.legacySrc 指定路径(如 /pages/private/CreateE/shop.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 { post } from '@/api/request'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
// 旧页面原始路径(如 /pages/private/CreateE/shop.html),由路由 meta 传入
|
||||
const src = computed(() => (route.meta.legacySrc as string) || '')
|
||||
// 本地旧页面:meta.legacySrc;远端:query.src(绝对 URL)
|
||||
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 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 {
|
||||
loading.value = false
|
||||
@@ -22,10 +54,17 @@ function onLoad(): void {
|
||||
|
||||
<template>
|
||||
<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" />
|
||||
<iframe
|
||||
v-show="!loading"
|
||||
:src="src"
|
||||
:src="iframeSrc"
|
||||
class="legacy-page-frame"
|
||||
frameborder="0"
|
||||
@load="onLoad"
|
||||
@@ -42,6 +81,16 @@ function onLoad(): void {
|
||||
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 {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
@@ -260,6 +260,7 @@ function openApp(a: AppItem): void {
|
||||
FrontendRoute: a.FrontendRoute || '',
|
||||
FrontendType: (a.FrontendType as 'vue' | 'legacy') || 'vue',
|
||||
LegacySrc: a.LegacySrc || '',
|
||||
ServiceName: a.ServiceName || '',
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -560,6 +561,10 @@ onMounted(loadMarket)
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
.app-offline-tag {
|
||||
margin-left: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.app-desc {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
|
||||
Reference in new issue
Block a user