feat(订阅式软件提供): 新增「我的订阅」页并动态加载产品定制页
新增 MySubscriptions 页(列出当前用户已订阅产品、提供者与订阅部门);对已声明 RemoteEntry 的产品经 RegisterProductRemoteApp 登记为远端应用,点「打开定制页」走 /remote-app/:code 由 RemoteAppHost 原生渲染;新增 GET /API/Subscription/MySubscriptions 客户端与类型;路由新增 subscription/my。
This commit is contained in:
1 parent
bcd4aca403
commit
5ffadd3f8a
4 files changed
+230
No files matched your search
@@ -103,6 +103,20 @@ export interface SubscriptionMemberSaveResult {
|
||||
total: number
|
||||
}
|
||||
|
||||
/** 我的订阅条目(被订阅者视角;含提供者定制的远端页面入口) */
|
||||
export interface MySubscriptionItem {
|
||||
SubscriptionGroupUUID: string
|
||||
GroupName: string
|
||||
GroupDesc: string
|
||||
GroupStatus: number
|
||||
Providers: SubscriptionCompanyItem[]
|
||||
/** 提供者声明的定制页远端入口(空=提供者尚未提供定制页) */
|
||||
RemoteEntry: string
|
||||
RemoteModule: string
|
||||
/** 我在该产品下订阅的部门名(展示用) */
|
||||
MyDeptNames: string[]
|
||||
}
|
||||
|
||||
/** 订阅式软件提供 API 客户端 */
|
||||
export const subscriptionApi = {
|
||||
/** 订阅组(产品)列表(含提供者/消费者公司) */
|
||||
@@ -166,4 +180,10 @@ export const subscriptionApi = {
|
||||
/** 删除一条订阅成员 */
|
||||
deleteMember: (SubscriptionGroupUUID: string, SubscriptionMemberUUID: string): Promise<void> =>
|
||||
post<void>('/API/Subscription/Member/Delete', { SubscriptionGroupUUID, SubscriptionMemberUUID }),
|
||||
|
||||
// ==================== 我的订阅(被订阅者视角) ====================
|
||||
|
||||
/** 当前登录用户已订阅的产品(含提供者定制页远端入口) */
|
||||
mySubscriptions: (): Promise<{ list: MySubscriptionItem[]; total: number }> =>
|
||||
get<{ list: MySubscriptionItem[]; total: number }>('/API/Subscription/MySubscriptions'),
|
||||
}
|
||||
@@ -40,3 +40,30 @@ export function InstallRemoteAppRoutes(router: Router): void {
|
||||
meta: { title: '远端应用', requiresAuth: true },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 为「产品定制页」登记远端应用模块,返回可直接用于 `/remote-app/:code` 的 AppCode
|
||||
*
|
||||
* 订阅式软件提供场景:提供者子服务器把定制页入口(RemoteEntry)声明到主服务器,
|
||||
* 消费者在「我的订阅」里登记后点开,即由 RemoteAppHost 运行时加载并原生渲染。
|
||||
*/
|
||||
export function RegisterProductRemoteApp(
|
||||
GroupUUID: string,
|
||||
Title: string,
|
||||
RemoteEntry: string,
|
||||
RemoteModule = 'default',
|
||||
): string {
|
||||
const Code = `sub-${GroupUUID}`
|
||||
registerRemoteApp({
|
||||
AppCode: Code,
|
||||
Title: Title || '定制页',
|
||||
Icon: 'Star',
|
||||
FrontendRoute: `/remote-app/${Code}`,
|
||||
FrontendType: 'remote',
|
||||
LegacySrc: '',
|
||||
RemoteEntry,
|
||||
RemoteModule: RemoteModule || 'default',
|
||||
ServiceName: 'gvsd-service-server',
|
||||
})
|
||||
return Code
|
||||
}
|
||||
@@ -469,6 +469,12 @@ export const staticRoutes: RouteRecordRaw[] = [
|
||||
component: () => import('@/views/subscription/Relation.vue'),
|
||||
meta: { title: '订阅关系', icon: 'Share', requiresAuth: true, menuGuarded: true },
|
||||
},
|
||||
{
|
||||
path: 'subscription/my',
|
||||
name: 'SubscriptionMy',
|
||||
component: () => import('@/views/subscription/MySubscriptions.vue'),
|
||||
meta: { title: '我的订阅', icon: 'Star', requiresAuth: true, menuGuarded: true },
|
||||
},
|
||||
// === OA 办公自动化流程(审批/请假/出差/会议/公告/汇报) ===
|
||||
{
|
||||
path: 'oa/approval',
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 我的订阅(被订阅者视角)
|
||||
*
|
||||
* 列出当前登录用户作为订阅成员(部门-员工键)已订阅的产品;
|
||||
* 提供者若已声明定制页远端入口(RemoteEntry),点「打开定制页」即由宿主
|
||||
* 远端微前端运行时加载并**原生渲染**(非 iframe)。
|
||||
*/
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { setPageTitle } from '@/utils/serverScope'
|
||||
import { subscriptionApi, type MySubscriptionItem } from '@/api/subscription'
|
||||
import { RegisterProductRemoteApp } from '@/apps/remoteRoutes'
|
||||
|
||||
setPageTitle('我的订阅')
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const rows = ref<MySubscriptionItem[]>([])
|
||||
|
||||
async function load(): Promise<void> {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await subscriptionApi.mySubscriptions()
|
||||
rows.value = r.list || []
|
||||
// 已有定制页入口的产品先登记为远端应用(点开时可即时渲染)
|
||||
for (const it of rows.value) {
|
||||
if (it.RemoteEntry) {
|
||||
RegisterProductRemoteApp(it.SubscriptionGroupUUID, it.GroupName, it.RemoteEntry, it.RemoteModule)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开产品定制页(登记远端应用 → 跳远端宿主路由原生渲染) */
|
||||
function openCustomPage(row: MySubscriptionItem): void {
|
||||
if (!row.RemoteEntry) {
|
||||
ElMessage.info('该产品暂未提供定制页')
|
||||
return
|
||||
}
|
||||
const code = RegisterProductRemoteApp(
|
||||
row.SubscriptionGroupUUID, row.GroupName, row.RemoteEntry, row.RemoteModule,
|
||||
)
|
||||
router.push({ path: `/remote-app/${code}` })
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-sub-page">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="page-head">
|
||||
<el-icon><Star /></el-icon>
|
||||
<span class="page-title">我的订阅</span>
|
||||
<span class="page-sub">你是以下产品的订阅成员;提供者若已提供定制页,可直接打开</span>
|
||||
<div class="spacer" />
|
||||
<el-button @click="load"><el-icon><Refresh /></el-icon>刷新</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-loading="loading" class="sub-list">
|
||||
<el-empty
|
||||
v-if="!loading && !rows.length"
|
||||
description="你还没有订阅任何产品"
|
||||
:image-size="90"
|
||||
/>
|
||||
<div v-for="r in rows" :key="r.SubscriptionGroupUUID" class="sub-card">
|
||||
<div class="sub-head">
|
||||
<span class="sub-name">{{ r.GroupName }}</span>
|
||||
<el-tag :type="r.GroupStatus ? 'success' : 'info'" size="small" effect="plain">
|
||||
{{ r.GroupStatus ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
<div class="spacer" />
|
||||
<el-button type="primary" :disabled="!r.RemoteEntry" @click="openCustomPage(r)">
|
||||
<el-icon><Link /></el-icon>{{ r.RemoteEntry ? '打开定制页' : '暂无定制页' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="r.GroupDesc" class="sub-desc">{{ r.GroupDesc }}</div>
|
||||
<div class="sub-meta">
|
||||
<span class="meta-label">提供者:</span>
|
||||
<el-tag
|
||||
v-for="c in r.Providers"
|
||||
:key="c.CompanyUUID"
|
||||
size="small"
|
||||
effect="plain"
|
||||
>{{ c.CompanyName }}</el-tag>
|
||||
<span v-if="!r.Providers.length" class="empty-tip">未指定</span>
|
||||
</div>
|
||||
<div class="sub-meta">
|
||||
<span class="meta-label">我的订阅部门:</span>
|
||||
<el-tag
|
||||
v-for="d in r.MyDeptNames"
|
||||
:key="d"
|
||||
size="small"
|
||||
type="info"
|
||||
effect="plain"
|
||||
>{{ d }}</el-tag>
|
||||
<span v-if="!r.MyDeptNames.length" class="empty-tip">—</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.my-sub-page {
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.page-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
.page-title {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.page-sub {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
.sub-list {
|
||||
min-height: 120px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.sub-card {
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
padding: 12px 14px;
|
||||
}
|
||||
.sub-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.sub-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sub-desc {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.sub-meta {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.meta-label {
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
.empty-tip {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user