feat(gerp): 配置页注入「应用订阅方」卡片,订阅应用新增软件管理菜单
- ConfigPanel.vue 重写为可编辑卡片:按租户分组多选提供者/消费者公司(互斥),保存应用级订阅方(初始化引用) - apps/index.ts:subscription 入口改 /subscription/software,注入卡片改「应用订阅方」 - router:新增 /subscription/software(软件管理,与订阅组管理同页不同入口) - Group.vue:按路由切换页面标题(软件管理 / 订阅组管理)
This commit is contained in:
1 parent
3010a116c2
commit
30bd077e93
5 files changed
+159
-100
No files matched your search
@@ -45,6 +45,20 @@ export interface SubscriptionGroupPayload {
|
||||
Consumers: string[]
|
||||
}
|
||||
|
||||
/** 应用级订阅方(应用本身的两方公司,不分具体产品) */
|
||||
export interface SubscriptionAppParties {
|
||||
AppUUID: string
|
||||
Providers: SubscriptionCompanyItem[]
|
||||
Consumers: SubscriptionCompanyItem[]
|
||||
}
|
||||
|
||||
/** 应用级订阅方保存入参(整体重写) */
|
||||
export interface SubscriptionAppPartyPayload {
|
||||
AppUUID: string
|
||||
Providers: string[]
|
||||
Consumers: string[]
|
||||
}
|
||||
|
||||
/** 订阅式软件提供 API 客户端 */
|
||||
export const subscriptionApi = {
|
||||
/** 订阅组(产品)列表(含提供者/消费者公司) */
|
||||
@@ -66,4 +80,12 @@ export const subscriptionApi = {
|
||||
/** 可选公司列表(当前管理员全部可管理公司,含所属租户名) */
|
||||
companies: (): Promise<{ list: SubscriptionCompanyItem[]; total: number; Scope: string }> =>
|
||||
get<{ list: SubscriptionCompanyItem[]; total: number; Scope: string }>('/API/Subscription/Companies'),
|
||||
|
||||
/** 应用级订阅方(应用配置页注入卡片:提供者/消费者公司,不分产品) */
|
||||
appParties: (AppUUID: string): Promise<SubscriptionAppParties> =>
|
||||
get<SubscriptionAppParties>('/API/Subscription/AppParties', { AppUUID }),
|
||||
|
||||
/** 保存应用级订阅方(整体重写) */
|
||||
saveAppParties: (data: SubscriptionAppPartyPayload): Promise<SubscriptionAppParties> =>
|
||||
post<SubscriptionAppParties>('/API/Subscription/AppParty/Save', data),
|
||||
}
|
||||
+5
-6
@@ -165,16 +165,15 @@ const builtin: Record<string, AppFrontendModule> = {
|
||||
AppCode: 'subscription',
|
||||
Title: '订阅式软件提供',
|
||||
Icon: 'Connection',
|
||||
FrontendRoute: '/subscription/group',
|
||||
FrontendRoute: '/subscription/software',
|
||||
FrontendType: 'vue',
|
||||
LegacySrc: '',
|
||||
// 注入到「应用配置页」的自定义区块:展示当前范围作为提供者/消费者参与的产品
|
||||
// 注入到「应用配置页」的自定义卡片:确定本应用自身的提供者/消费者公司(初始化引用)
|
||||
ConfigPanels: [
|
||||
{
|
||||
Key: 'subscription-relation-overview',
|
||||
Title: '订阅关系概览',
|
||||
Sub: '当前范围作为提供者 / 消费者参与的产品(按公司隔离)',
|
||||
CompanyOnly: true,
|
||||
Key: 'subscription-app-party',
|
||||
Title: '应用订阅方',
|
||||
Sub: '确定本应用自身的提供者公司与消费者公司(不分具体产品),作为订阅关系的初始化引用',
|
||||
Component: () => import('@/views/subscription/ConfigPanel.vue'),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -451,6 +451,12 @@ export const staticRoutes: RouteRecordRaw[] = [
|
||||
meta: { title: '绩效管理', icon: 'TrendCharts', requiresAuth: true, menuGuarded: true },
|
||||
},
|
||||
// === 订阅式软件提供(订阅组=产品:提供者/消费者公司) ===
|
||||
{
|
||||
path: 'subscription/software',
|
||||
name: 'SubscriptionSoftware',
|
||||
component: () => import('@/views/subscription/Group.vue'),
|
||||
meta: { title: '软件管理', icon: 'Management', requiresAuth: true, menuGuarded: true },
|
||||
},
|
||||
{
|
||||
path: 'subscription/group',
|
||||
name: 'SubscriptionGroup',
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 订阅式软件提供 · 注入到「应用配置页」的自定义区块
|
||||
* 订阅式软件提供 · 注入到「应用配置页」的自定义卡片
|
||||
*
|
||||
* 由前端应用注册表(src/apps/index.ts 的 subscription.ConfigPanels)挂载,
|
||||
* 配置页把当前范围上下文以 props 传入,本组件只读展示当前公司的订阅关系。
|
||||
* 由前端应用注册表(src/apps/index.ts 的 subscription.ConfigPanels)挂载。
|
||||
* 用途:确定**本应用自身**的提供者公司与消费者公司(不分具体产品),
|
||||
* 作为订阅关系的初始化引用(与「订阅组(产品)」的产品级名单相互独立)。
|
||||
*/
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { subscriptionApi, type SubscriptionRelationItem } from '@/api/subscription'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { subscriptionApi, type SubscriptionCompanyItem } from '@/api/subscription'
|
||||
|
||||
const props = defineProps<{
|
||||
/** 应用 UUID */
|
||||
AppUUID: string
|
||||
/** 应用名 */
|
||||
AppName: string
|
||||
/** '' = 租户默认;否则为公司 UUID */
|
||||
/** '' = 租户默认;否则为公司 UUID(本卡片与范围无关,仅用于文案) */
|
||||
CompanyUUID: string
|
||||
/** 配置管理范围 */
|
||||
Scope: string
|
||||
@@ -22,23 +23,38 @@ const props = defineProps<{
|
||||
ScopeLabel: string
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const relation = ref<SubscriptionRelationItem | null>(null)
|
||||
const saving = ref(false)
|
||||
const companies = ref<SubscriptionCompanyItem[]>([])
|
||||
/** 提供者公司 UUID 列表 */
|
||||
const providers = ref<string[]>([])
|
||||
/** 消费者公司 UUID 列表 */
|
||||
const consumers = ref<string[]>([])
|
||||
|
||||
const hasRelation = computed(
|
||||
() => !!relation.value && (relation.value.ProviderGroups.length > 0 || relation.value.ConsumerGroups.length > 0),
|
||||
)
|
||||
/** 公司按所属租户分组(下拉用 el-option-group 展示) */
|
||||
const companyGroups = computed(() => {
|
||||
const map = new Map<string, { TenantUUID: string; TenantName: string; Companies: SubscriptionCompanyItem[] }>()
|
||||
for (const c of companies.value) {
|
||||
const key = c.TenantUUID || ''
|
||||
if (!map.has(key)) {
|
||||
map.set(key, { TenantUUID: key, TenantName: c.TenantName || '未知租户', Companies: [] })
|
||||
}
|
||||
map.get(key)!.Companies.push(c)
|
||||
}
|
||||
return [...map.values()]
|
||||
})
|
||||
|
||||
async function load(): Promise<void> {
|
||||
if (!props.CompanyUUID) {
|
||||
relation.value = null
|
||||
return
|
||||
}
|
||||
if (!props.AppUUID) return
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await subscriptionApi.relations()
|
||||
relation.value = (r.list || []).find((x) => x.CompanyUUID === props.CompanyUUID) || null
|
||||
const [c, p] = await Promise.all([
|
||||
subscriptionApi.companies(),
|
||||
subscriptionApi.appParties(props.AppUUID),
|
||||
])
|
||||
companies.value = c.list || []
|
||||
providers.value = (p.Providers || []).map((x) => x.CompanyUUID)
|
||||
consumers.value = (p.Consumers || []).map((x) => x.CompanyUUID)
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
@@ -46,96 +62,109 @@ async function load(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.CompanyUUID, load, { immediate: true })
|
||||
async function onSave(): Promise<void> {
|
||||
saving.value = true
|
||||
try {
|
||||
await subscriptionApi.saveAppParties({
|
||||
AppUUID: props.AppUUID,
|
||||
Providers: providers.value,
|
||||
Consumers: consumers.value,
|
||||
})
|
||||
ElMessage.success('应用订阅方已保存')
|
||||
await load()
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-loading="loading" class="sub-cfg-panel">
|
||||
<div class="sub-cfg-tip">
|
||||
当前范围:<b>{{ ScopeLabel }}</b>。订阅关系按<b>产品(订阅组)</b>登记,下述为该范围作为「提供者 / 消费者」参与的产品。
|
||||
<div v-loading="loading" class="sub-party-panel">
|
||||
<div class="sub-party-tip">
|
||||
确定<b>本应用自身</b>的提供者公司与消费者公司(不分具体产品),作为订阅关系的<b>初始化引用</b>;
|
||||
可选范围 = 您有管理权的全部公司。同一家公司不能同时列为两方。
|
||||
</div>
|
||||
<el-empty
|
||||
v-if="!loading && !hasRelation"
|
||||
description="该范围尚未参与任何订阅组(可在「订阅组管理」里新建产品并指定提供者/消费者)"
|
||||
:image-size="60"
|
||||
/>
|
||||
<div v-else-if="relation" class="sub-cfg-body">
|
||||
<div class="sub-cfg-row">
|
||||
<span class="sub-cfg-label">作为提供者</span>
|
||||
<div class="sub-cfg-tags">
|
||||
<el-tag
|
||||
v-for="g in relation.ProviderGroups"
|
||||
:key="g.SubscriptionGroupUUID"
|
||||
size="small"
|
||||
effect="plain"
|
||||
:type="g.GroupStatus ? 'primary' : 'info'"
|
||||
>{{ g.GroupName }}</el-tag>
|
||||
<span v-if="!relation.ProviderGroups.length" class="sub-cfg-empty">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sub-cfg-row">
|
||||
<span class="sub-cfg-label">作为消费者</span>
|
||||
<div class="sub-cfg-tags">
|
||||
<el-tag
|
||||
v-for="g in relation.ConsumerGroups"
|
||||
:key="g.SubscriptionGroupUUID"
|
||||
size="small"
|
||||
type="success"
|
||||
effect="plain"
|
||||
>{{ g.GroupName }}</el-tag>
|
||||
<span v-if="!relation.ConsumerGroups.length" class="sub-cfg-empty">—</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sub-cfg-actions">
|
||||
<el-button size="small" @click="router.push('/subscription/group')">订阅组管理</el-button>
|
||||
<el-button size="small" type="primary" plain @click="router.push('/subscription/relation')">订阅关系</el-button>
|
||||
<el-form label-width="96px" label-position="right" class="sub-party-form">
|
||||
<el-form-item label="提供者公司">
|
||||
<el-select
|
||||
v-model="providers"
|
||||
multiple
|
||||
filterable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="选择一家或多家提供者公司"
|
||||
class="sub-party-select"
|
||||
>
|
||||
<el-option-group
|
||||
v-for="gp in companyGroups"
|
||||
:key="gp.TenantUUID"
|
||||
:label="gp.TenantName || '未知租户'"
|
||||
>
|
||||
<el-option
|
||||
v-for="c in gp.Companies"
|
||||
:key="c.CompanyUUID"
|
||||
:label="c.CompanyName"
|
||||
:value="c.CompanyUUID"
|
||||
:disabled="consumers.includes(c.CompanyUUID)"
|
||||
/>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="消费者公司">
|
||||
<el-select
|
||||
v-model="consumers"
|
||||
multiple
|
||||
filterable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="选择一家或多家消费者公司"
|
||||
class="sub-party-select"
|
||||
>
|
||||
<el-option-group
|
||||
v-for="gp in companyGroups"
|
||||
:key="gp.TenantUUID"
|
||||
:label="gp.TenantName || '未知租户'"
|
||||
>
|
||||
<el-option
|
||||
v-for="c in gp.Companies"
|
||||
:key="c.CompanyUUID"
|
||||
:label="c.CompanyName"
|
||||
:value="c.CompanyUUID"
|
||||
:disabled="providers.includes(c.CompanyUUID)"
|
||||
/>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="sub-party-actions">
|
||||
<el-button type="primary" :loading="saving" @click="onSave">保存应用订阅方</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.sub-cfg-panel {
|
||||
.sub-party-panel {
|
||||
min-height: 60px;
|
||||
}
|
||||
.sub-cfg-tip {
|
||||
.sub-party-tip {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.sub-cfg-body {
|
||||
.sub-party-form {
|
||||
max-width: 640px;
|
||||
}
|
||||
.sub-party-select {
|
||||
width: 100%;
|
||||
}
|
||||
.sub-party-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.sub-cfg-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
.sub-cfg-label {
|
||||
flex-shrink: 0;
|
||||
width: 76px;
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-regular);
|
||||
line-height: 24px;
|
||||
}
|
||||
.sub-cfg-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.sub-cfg-empty {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
line-height: 24px;
|
||||
}
|
||||
.sub-cfg-actions {
|
||||
margin-top: 14px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
max-width: 640px;
|
||||
}
|
||||
</style>
|
||||
@@ -7,6 +7,7 @@
|
||||
* (跨租户管理员可选任意租户的公司)。
|
||||
*/
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { setPageTitle } from '@/utils/serverScope'
|
||||
import {
|
||||
@@ -16,7 +17,9 @@ import {
|
||||
type SubscriptionGroupPayload,
|
||||
} from '@/api/subscription'
|
||||
|
||||
setPageTitle('订阅组管理')
|
||||
// 同一页面挂两个菜单:软件管理(/subscription/software)与 订阅组管理(/subscription/group)
|
||||
const route = useRoute()
|
||||
setPageTitle(route.path.includes('/software') ? '软件管理' : '订阅组管理')
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
Reference in new issue
Block a user