feat(msync): 远端 App 打开链路——用户令牌签发拼 URL + 服务离线标记 + /remote 路由

This commit is contained in:
TermiNexus committed 2026-08-14 00:33:49 +08:00
1 parent 7e64bdf38a
commit a19874da72
5 files changed
+82 -7

No files matched your search

+5
View File
@@ -24,6 +24,11 @@ export interface AppItem {
InstalledCompanies?: string[]
UserEnabled?: number
ApplyStatus?: number
/** 远端注入(MSYNC):来源/提供方服务/在线状态 */
Source?: string
ServiceName?: string
ServiceDomain?: string
ServiceOnline?: number
}
/** 管理员可安装应用的公司范围项 */
+9
View File
@@ -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')
}
+7
View File
@@ -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',
+56 -7
View File
@@ -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;
}
+5
View File
@@ -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;