feat(ram): 应用中心安装弹窗支持 RAM 角色授权选择(角色多选→GrantRoles)

This commit is contained in:
TermiNexus committed 2026-08-14 00:03:32 +08:00
1 parent 36afde19da
commit 7e64bdf38a
2 files changed
+72 -10

No files matched your search

+16 -6
View File
@@ -35,6 +35,12 @@ export interface CompanyScopeItem {
Installed?: number
}
/** RAM 角色粒度授权候选(当前租户启用角色) */
export interface RoleScopeItem {
RoleUUID: string
RoleName: string
}
/** 个人授权申请项(租户管理员审批台) */
export interface PendingItem {
UserAppUUID: string
@@ -65,14 +71,18 @@ export const appApi = {
/** 我的应用(当前用户可用) */
myApps: (): Promise<{ list: AppItem[]; total: number }> => get<{ list: AppItem[]; total: number }>('/API/App/MyApps'),
/** 管理员可安装应用的公司列表(按权限范围);
/** 管理员可安装应用的公司列表(按权限范围)+ 租户角色列表(RAM 授权候选);
* 传 AppUUID 时附带各公司安装状态(Installed),供管理弹窗用 */
manageCompanies: (AppUUID?: string): Promise<{ Scope: string; Companies: CompanyScopeItem[] }> =>
get<{ Scope: string; Companies: CompanyScopeItem[] }>('/API/App/ManageCompanies', AppUUID ? { AppUUID } : {}),
manageCompanies: (AppUUID?: string): Promise<{ Scope: string; Companies: CompanyScopeItem[]; Roles: RoleScopeItem[] }> =>
get<{ Scope: string; Companies: CompanyScopeItem[]; Roles: RoleScopeItem[] }>(
'/API/App/ManageCompanies',
AppUUID ? { AppUUID } : {},
),
/** 管理员:安装企业功能(不传 CompanyUUIDs=全租户;传 CompanyUUIDs=指定公司) */
install: (AppUUID: string, CompanyUUIDs?: string[]): Promise<AppItem> =>
post<AppItem>('/API/App/Install', { AppUUID, CompanyUUIDs: CompanyUUIDs || [] }),
/** 管理员:安装企业功能(不传 CompanyUUIDs=全租户;传 CompanyUUIDs=指定公司);
* GrantRoles=RAM 角色粒度授权(可选):指定角色可见该应用(安装范围内) */
install: (AppUUID: string, CompanyUUIDs?: string[], GrantRoles?: string[]): Promise<AppItem> =>
post<AppItem>('/API/App/Install', { AppUUID, CompanyUUIDs: CompanyUUIDs || [], GrantRoles: GrantRoles || [] }),
/** 管理员:停用企业功能(传 CompanyUUIDs=停用指定公司;TenantOnly=仅停用全租户;都不传=停全租户+当前公司) */
uninstall: (AppUUID: string, CompanyUUIDs?: string[], TenantOnly?: boolean): Promise<void> =>
+56 -4
View File
@@ -10,7 +10,7 @@ import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Grid } from '@element-plus/icons-vue'
import { appApi, type AppItem, type CompanyScopeItem } from '@/api/app'
import { appApi, type AppItem, type CompanyScopeItem, type RoleScopeItem } from '@/api/app'
import { getAppModule, appOpenTarget } from '@/apps'
import { useUserStore } from '@/stores/user'
import { setPageTitle } from '@/utils/serverScope'
@@ -28,6 +28,8 @@ const loading = ref(false)
const manageableCompanies = ref<CompanyScopeItem[]>([])
/** 权限范围:all=跨租户 / tenant=租户内 / company=指定公司 */
const manageScope = ref('')
/** 当前租户角色列表(RAM 角色粒度授权候选) */
const tenantRoles = ref<RoleScopeItem[]>([])
function typeLabel(t: string): { text: string; type: 'success' | 'info' | 'warning' } {
if (t === 'enterprise') return { text: '企业功能', type: 'success' }
@@ -60,12 +62,15 @@ const installTargetApp = ref<AppItem | null>(null)
const installScope = ref<'tenant' | 'company'>('tenant')
/** 选中的公司 UUID 列表 */
const selectedCompanies = ref<string[]>([])
/** RAM 角色粒度授权:选中的角色 UUID 列表(不选=全员可见) */
const selectedRoles = ref<string[]>([])
/** 打开安装弹窗:拉取可管理公司 */
/** 打开安装弹窗:拉取可管理公司 + 租户角色(RAM 授权候选) */
async function openInstallDialog(a: AppItem): Promise<void> {
installTargetApp.value = a
installScope.value = 'tenant'
selectedCompanies.value = []
selectedRoles.value = []
installDialogVisible.value = true
if (isAdmin.value && !manageableCompanies.value.length) {
installDialogLoading.value = true
@@ -73,6 +78,7 @@ async function openInstallDialog(a: AppItem): Promise<void> {
const r = await appApi.manageCompanies()
manageableCompanies.value = r.Companies || []
manageScope.value = r.Scope || ''
tenantRoles.value = r.Roles || []
} catch { /* 拦截器已提示 */ } finally {
installDialogLoading.value = false
}
@@ -86,11 +92,14 @@ async function onConfirmInstall(): Promise<void> {
installDialogLoading.value = true
try {
const companyUUIDs = installScope.value === 'company' ? selectedCompanies.value : []
await appApi.install(a.AppUUID, companyUUIDs)
await appApi.install(a.AppUUID, companyUUIDs, selectedRoles.value)
const scopeText = installScope.value === 'company'
? `已为 ${selectedCompanies.value.length} 家公司安装`
: '已全租户安装(全员可用)'
ElMessage.success(`「${a.AppName}」${scopeText}`)
const ramText = selectedRoles.value.length
? `,RAM 授权 ${selectedRoles.value.length} 个角色可见`
: ''
ElMessage.success(`「${a.AppName}」${scopeText}${ramText}`)
installDialogVisible.value = false
await loadMarket()
} catch { /* 拦截器已提示 */ } finally {
@@ -380,6 +389,29 @@ onMounted(loadMarket)
<p v-else-if="manageScope === 'tenant'" class="gs-scope-tip">您可管理本租户下全部公司。</p>
<p v-else-if="manageScope === 'all'" class="gs-scope-tip">您是跨租户管理员,可管理全部公司。</p>
</template>
<!-- RAM 角色粒度授权(可选):勾选角色 → 仅这些角色可见该应用;不选=全员可见 -->
<div class="gs-role-block">
<div class="gs-role-title">RAM 角色授权(可选)</div>
<el-select
v-model="selectedRoles"
multiple
collapse-tags
collapse-tags-tooltip
filterable
clearable
placeholder="不选=安装范围内全员可见"
class="gs-role-select"
>
<el-option
v-for="r in tenantRoles"
:key="r.RoleUUID"
:label="r.RoleName"
:value="r.RoleUUID"
/>
</el-select>
<p class="gs-role-tip">勾选后,仅被授权角色的员工在菜单中可见该应用;其他角色不可见。</p>
</div>
</div>
<template #footer>
@@ -596,6 +628,26 @@ onMounted(loadMarket)
color: var(--el-text-color-secondary);
margin: 10px 0 0;
}
/* RAM 角色授权区 */
.gs-role-block {
margin-top: 16px;
padding-top: 14px;
border-top: 1px solid var(--el-border-color-lighter);
}
.gs-role-title {
font-size: 13px;
font-weight: 600;
color: var(--el-text-color-primary);
margin-bottom: 8px;
}
.gs-role-select {
width: 100%;
}
.gs-role-tip {
font-size: 12px;
color: var(--el-text-color-secondary);
margin: 8px 0 0;
}
/* 管理弹窗 */
.gm-tenant-row {
display: flex;