feat(app): 应用市场详情抽屉加遮罩并内联安装管理
- 抽屉改为带遮罩阴影,点击遮罩即关闭;宽度 420 -> 560 - 将「管理」弹窗内容(全租户状态/公司级勾选/保存)内联进抽屉 - 卡片「管理」按钮改为打开抽屉,移除独立管理弹窗 - 打开抽屉预取安装数据,保存后自动刷新勾选与「已装」标记 - 修复 loadMarket 重建列表后 detailApp 引用失效
This commit is contained in:
1 parent
efd7f98522
commit
cdfd6f827d
1 file changed
+95
-124
+95
-124
@@ -25,6 +25,11 @@ const isAdmin = computed(() => userStore.isAdmin)
|
||||
const market = ref<AppItem[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
/** 应用详情抽屉:是否展开 */
|
||||
const detailVisible = ref(false)
|
||||
/** 当前在抽屉中查看的应用 */
|
||||
const detailApp = ref<AppItem | null>(null)
|
||||
|
||||
/** 可管理公司列表(仅管理员) */
|
||||
const manageableCompanies = ref<CompanyScopeItem[]>([])
|
||||
/** 权限范围:all=跨租户 / tenant=租户内 / company=指定公司 */
|
||||
@@ -50,6 +55,10 @@ async function loadMarket(): Promise<void> {
|
||||
try {
|
||||
const r = await appApi.market()
|
||||
market.value = r.list
|
||||
// 抽屉打开时同步指向最新数据(loadMarket 会重建列表对象,旧引用会失效)
|
||||
if (detailApp.value) {
|
||||
detailApp.value = r.list.find((x) => x.AppUUID === detailApp.value!.AppUUID) || detailApp.value
|
||||
}
|
||||
} catch { /* 拦截器已提示 */ } finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -108,34 +117,20 @@ async function onConfirmInstall(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/** 租户管理员:安装/停用企业功能。
|
||||
* 管理范围内已装(含非本公司)→ 打开管理弹窗(公司级开关);未装 → 打开安装弹窗 */
|
||||
async function toggleInstall(a: AppItem): Promise<void> {
|
||||
try {
|
||||
if (hasManageableInstall(a)) {
|
||||
await openManageDialog(a)
|
||||
return
|
||||
}
|
||||
await openInstallDialog(a)
|
||||
} catch { /* 拦截器已提示或取消 */ }
|
||||
}
|
||||
|
||||
// ==================== 管理弹窗(公司级安装/停用) ====================
|
||||
const manageDialogVisible = ref(false)
|
||||
const manageDialogLoading = ref(false)
|
||||
// ==================== 安装管理(公司级安装/停用,内联于应用详情抽屉) ====================
|
||||
const manageLoading = ref(false)
|
||||
const manageTargetApp = ref<AppItem | null>(null)
|
||||
/** 可管理公司列表(含各公司安装状态 Installed) */
|
||||
const manageCompaniesList = ref<CompanyScopeItem[]>([])
|
||||
/** 当前勾选(已装)的公司 UUID 列表 */
|
||||
const manageSelected = ref<string[]>([])
|
||||
|
||||
/** 打开管理弹窗:拉取该应用在各可管理公司的安装状态 */
|
||||
async function openManageDialog(a: AppItem): Promise<void> {
|
||||
/** 拉取该应用在各可管理公司的安装状态(抽屉内联「安装管理」) */
|
||||
async function LoadManageData(a: AppItem): Promise<void> {
|
||||
manageTargetApp.value = a
|
||||
manageSelected.value = []
|
||||
manageCompaniesList.value = []
|
||||
manageDialogVisible.value = true
|
||||
manageDialogLoading.value = true
|
||||
manageLoading.value = true
|
||||
try {
|
||||
const r = await appApi.manageCompanies(a.AppUUID)
|
||||
manageCompaniesList.value = r.Companies || []
|
||||
@@ -144,7 +139,7 @@ async function openManageDialog(a: AppItem): Promise<void> {
|
||||
.map((c) => c.CompanyUUID)
|
||||
manageScope.value = r.Scope || ''
|
||||
} catch { /* 拦截器已提示 */ } finally {
|
||||
manageDialogLoading.value = false
|
||||
manageLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,14 +156,15 @@ async function stopTenantInstall(): Promise<void> {
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
manageDialogLoading.value = true
|
||||
manageLoading.value = true
|
||||
try {
|
||||
await appApi.uninstall(a.AppUUID, [], true) // TenantOnly=仅停用全租户(公司级记录不动)
|
||||
ElMessage.success('已停用全租户安装')
|
||||
manageDialogVisible.value = false
|
||||
await loadMarket()
|
||||
// 抽屉内联管理:刷新公司级安装状态,保持勾选与「已装」标记一致
|
||||
if (detailVisible.value && detailApp.value) await LoadManageData(detailApp.value)
|
||||
} catch { /* 拦截器已提示 */ } finally {
|
||||
manageDialogLoading.value = false
|
||||
manageLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,15 +184,16 @@ async function saveCompanyManage(): Promise<void> {
|
||||
ElMessage.info('公司安装状态未变化')
|
||||
return
|
||||
}
|
||||
manageDialogLoading.value = true
|
||||
manageLoading.value = true
|
||||
try {
|
||||
if (toInstall.length) await appApi.install(a.AppUUID, toInstall)
|
||||
if (toUninstall.length) await appApi.uninstall(a.AppUUID, toUninstall)
|
||||
ElMessage.success(`已更新 ${toInstall.length + toUninstall.length} 家公司的安装状态`)
|
||||
manageDialogVisible.value = false
|
||||
await loadMarket()
|
||||
// 抽屉内联管理:刷新公司级安装状态,保持勾选与「已装」标记一致
|
||||
if (detailVisible.value && detailApp.value) await LoadManageData(detailApp.value)
|
||||
} catch { /* 拦截器已提示 */ } finally {
|
||||
manageDialogLoading.value = false
|
||||
manageLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,14 +273,13 @@ function openConfig(a: AppItem): void {
|
||||
}
|
||||
|
||||
// ==================== 应用详情抽屉(仿 VSCode 扩展详情:右侧展开) ====================
|
||||
const detailVisible = ref(false)
|
||||
/** 当前在抽屉中查看的应用 */
|
||||
const detailApp = ref<AppItem | null>(null)
|
||||
|
||||
/** 点击卡片主体:右侧抽屉展示 icon / 名称 / 简介 / 安装管理 */
|
||||
/** 点击卡片主体:右侧抽屉展示 icon / 名称 / 简介 / 安装管理;管理员打开时预取安装管理数据 */
|
||||
function openDetail(a: AppItem): void {
|
||||
detailApp.value = a
|
||||
detailVisible.value = true
|
||||
// 管理员且已有安装 → 抽屉内直接内联「安装管理」,打开即预取公司级安装数据
|
||||
if (isAdmin.value && hasManageableInstall(a)) void LoadManageData(a)
|
||||
}
|
||||
|
||||
/** 抽屉内「打开应用」:先收抽屉再跳转(页面被 tab 缓存,避免返回时抽屉仍开着) */
|
||||
@@ -414,10 +410,9 @@ onMounted(loadMarket)
|
||||
<!-- 企业功能:管理员安装/管理(公司级);普通用户提示 -->
|
||||
<template v-if="a.AppType === 'enterprise'">
|
||||
<el-button v-if="isAdmin && (isAppUsable(a) || hasManageableInstall(a))" size="small" type="info" plain @click="openConfig(a)">配置</el-button>
|
||||
<el-button v-if="isAdmin" size="small" type="primary"
|
||||
:plain="hasManageableInstall(a)" @click="toggleInstall(a)">
|
||||
{{ hasManageableInstall(a) ? '管理' : '安装' }}
|
||||
</el-button>
|
||||
<!-- 已装:打开详情抽屉(安装管理内联于抽屉);未装:打开安装弹窗 -->
|
||||
<el-button v-if="isAdmin && hasManageableInstall(a)" size="small" type="primary" plain @click="openDetail(a)">管理</el-button>
|
||||
<el-button v-else-if="isAdmin" size="small" type="primary" @click="openInstallDialog(a)">安装</el-button>
|
||||
<el-tag v-else size="small" type="info" effect="plain">由企业管理员开通</el-tag>
|
||||
</template>
|
||||
<!-- 个人应用:启用/停用/申请 -->
|
||||
@@ -435,10 +430,9 @@ onMounted(loadMarket)
|
||||
<template v-else>
|
||||
<template v-if="isAdmin">
|
||||
<el-button v-if="isAppUsable(a) || hasManageableInstall(a)" size="small" type="info" plain @click="openConfig(a)">配置</el-button>
|
||||
<el-button size="small" type="primary"
|
||||
:plain="hasManageableInstall(a)" @click="toggleInstall(a)">
|
||||
{{ hasManageableInstall(a) ? '管理' : '安装' }}
|
||||
</el-button>
|
||||
<!-- 已装:打开详情抽屉(安装管理内联于抽屉);未装:打开安装弹窗 -->
|
||||
<el-button v-if="hasManageableInstall(a)" size="small" type="primary" plain @click="openDetail(a)">管理</el-button>
|
||||
<el-button v-else size="small" type="primary" @click="openInstallDialog(a)">安装</el-button>
|
||||
</template>
|
||||
<el-button v-else size="small" :type="a.UserEnabled ? 'danger' : 'primary'"
|
||||
:plain="!!a.UserEnabled" @click="toggleEnable(a)">
|
||||
@@ -456,8 +450,8 @@ onMounted(loadMarket)
|
||||
v-model="detailVisible"
|
||||
:title="detailApp?.AppName || '应用详情'"
|
||||
direction="rtl"
|
||||
:size="420"
|
||||
:modal="false"
|
||||
:size="560"
|
||||
:modal="true"
|
||||
class="app-detail-drawer"
|
||||
>
|
||||
<template v-if="detailApp">
|
||||
@@ -492,20 +486,6 @@ onMounted(loadMarket)
|
||||
>{{ statusRowText(detailApp) }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="isAdmin && detailApp.InstalledCompanies?.length" class="detail-section">
|
||||
<div class="detail-section-title">管理范围内已装公司</div>
|
||||
<div class="detail-companies">
|
||||
<el-tag
|
||||
v-for="n in detailApp.InstalledCompanies"
|
||||
:key="n"
|
||||
size="small"
|
||||
type="success"
|
||||
effect="plain"
|
||||
class="app-installed-tag"
|
||||
>{{ n }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-section-title">安装与使用</div>
|
||||
<div class="detail-actions">
|
||||
@@ -518,10 +498,13 @@ onMounted(loadMarket)
|
||||
plain
|
||||
@click="OpenConfigFromDetail"
|
||||
>配置</el-button>
|
||||
<el-button v-if="isAdmin" type="primary" :plain="hasManageableInstall(detailApp)" @click="toggleInstall(detailApp)">
|
||||
{{ hasManageableInstall(detailApp) ? '管理' : '安装' }}
|
||||
</el-button>
|
||||
<el-tag v-else type="info" effect="plain">由企业管理员开通</el-tag>
|
||||
<!-- 已装:安装管理已内联在下方「安装管理」,无需再点按钮开弹窗 -->
|
||||
<el-button
|
||||
v-if="isAdmin && !hasManageableInstall(detailApp)"
|
||||
type="primary"
|
||||
@click="openInstallDialog(detailApp)"
|
||||
>安装</el-button>
|
||||
<el-tag v-if="!isAdmin" type="info" effect="plain">由企业管理员开通</el-tag>
|
||||
</template>
|
||||
<!-- 个人应用:启用/停用/申请 -->
|
||||
<template v-else-if="detailApp.AppType === 'personal'">
|
||||
@@ -542,9 +525,8 @@ onMounted(loadMarket)
|
||||
<template v-else>
|
||||
<template v-if="isAdmin">
|
||||
<el-button v-if="isAppUsable(detailApp) || hasManageableInstall(detailApp)" type="info" plain @click="OpenConfigFromDetail">配置</el-button>
|
||||
<el-button type="primary" :plain="hasManageableInstall(detailApp)" @click="toggleInstall(detailApp)">
|
||||
{{ hasManageableInstall(detailApp) ? '管理' : '安装' }}
|
||||
</el-button>
|
||||
<!-- 已装:安装管理已内联在下方「安装管理」 -->
|
||||
<el-button v-if="!hasManageableInstall(detailApp)" type="primary" @click="openInstallDialog(detailApp)">安装</el-button>
|
||||
</template>
|
||||
<el-button
|
||||
v-else
|
||||
@@ -575,6 +557,54 @@ onMounted(loadMarket)
|
||||
</div>
|
||||
<p class="detail-tip">应用配置项仍在独立页面中打开(与卡片上的「配置」一致)。</p>
|
||||
</div>
|
||||
|
||||
<!-- 安装管理(管理员 · 已装):直接内联在抽屉中,免去再开弹窗,便于随手管理 -->
|
||||
<div v-if="isAdmin && hasManageableInstall(detailApp)" class="detail-section">
|
||||
<div class="detail-section-title">安装管理</div>
|
||||
<div v-loading="manageLoading">
|
||||
<!-- 全租户安装状态 -->
|
||||
<div class="gm-tenant-row">
|
||||
<el-tag :type="detailApp.TenantInstalled ? 'success' : 'info'" size="small">
|
||||
{{ detailApp.TenantInstalled ? '全租户已安装(租户全员可用)' : '未全租户安装' }}
|
||||
</el-tag>
|
||||
<el-button
|
||||
v-if="detailApp.TenantInstalled"
|
||||
size="small"
|
||||
type="danger"
|
||||
plain
|
||||
:loading="manageLoading"
|
||||
@click="stopTenantInstall"
|
||||
>停用全租户</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 公司级安装状态 -->
|
||||
<div class="gm-company-title">公司级安装(勾选=该公司已安装)</div>
|
||||
<el-empty
|
||||
v-if="!manageCompaniesList.length && !manageLoading"
|
||||
description="您没有可管理的公司(或当前无公司权限)"
|
||||
:image-size="60"
|
||||
/>
|
||||
<div v-else class="gm-company-list">
|
||||
<el-checkbox-group v-model="manageSelected">
|
||||
<el-checkbox
|
||||
v-for="c in manageCompaniesList"
|
||||
:key="c.CompanyUUID"
|
||||
:value="c.CompanyUUID"
|
||||
class="gm-company-item"
|
||||
>
|
||||
{{ c.CompanyName }}{{ c.CompanyShort ? `(${c.CompanyShort})` : '' }}
|
||||
<el-tag v-if="c.Installed === 1" size="small" type="success" effect="plain" class="gm-installed-tag">已装</el-tag>
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
<p v-if="manageScope === 'company'" class="gs-scope-tip">您仅有指定公司的管理权限,只能为授权公司管理。</p>
|
||||
<p v-else-if="manageScope === 'tenant'" class="gs-scope-tip">您可管理本租户下全部公司。</p>
|
||||
<p v-else-if="manageScope === 'all'" class="gs-scope-tip">您是跨租户管理员,可管理全部公司。</p>
|
||||
</div>
|
||||
<div class="detail-actions detail-manage-actions">
|
||||
<el-button type="primary" :loading="manageLoading" @click="saveCompanyManage">保存公司状态</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
@@ -651,64 +681,6 @@ onMounted(loadMarket)
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 管理弹窗:公司级安装/停用 -->
|
||||
<el-dialog
|
||||
v-model="manageDialogVisible"
|
||||
:title="`管理「${manageTargetApp?.AppName || ''}」`"
|
||||
width="560px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div v-loading="manageDialogLoading">
|
||||
<!-- 全租户安装状态 -->
|
||||
<div class="gm-tenant-row">
|
||||
<div class="gm-tenant-info">
|
||||
<el-tag :type="manageTargetApp?.TenantInstalled ? 'success' : 'info'" size="small">
|
||||
{{ manageTargetApp?.TenantInstalled ? '全租户已安装(租户全员可用)' : '未全租户安装' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="manageTargetApp?.TenantInstalled"
|
||||
size="small"
|
||||
type="danger"
|
||||
plain
|
||||
:loading="manageDialogLoading"
|
||||
@click="stopTenantInstall"
|
||||
>
|
||||
停用全租户
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 公司级安装状态 -->
|
||||
<div class="gm-company-title">公司级安装(勾选=该公司已安装)</div>
|
||||
<el-empty
|
||||
v-if="!manageCompaniesList.length && !manageDialogLoading"
|
||||
description="您没有可管理的公司(或当前无公司权限)"
|
||||
:image-size="60"
|
||||
/>
|
||||
<div v-else class="gm-company-list">
|
||||
<el-checkbox-group v-model="manageSelected">
|
||||
<el-checkbox
|
||||
v-for="c in manageCompaniesList"
|
||||
:key="c.CompanyUUID"
|
||||
:value="c.CompanyUUID"
|
||||
class="gm-company-item"
|
||||
>
|
||||
{{ c.CompanyName }}{{ c.CompanyShort ? `(${c.CompanyShort})` : '' }}
|
||||
<el-tag v-if="c.Installed === 1" size="small" type="success" effect="plain" class="gm-installed-tag">已装</el-tag>
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
<p v-if="manageScope === 'company'" class="gs-scope-tip">您仅有指定公司的管理权限,只能为授权公司管理。</p>
|
||||
<p v-else-if="manageScope === 'tenant'" class="gs-scope-tip">您可管理本租户下全部公司。</p>
|
||||
<p v-else-if="manageScope === 'all'" class="gs-scope-tip">您是跨租户管理员,可管理全部公司。</p>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="manageDialogVisible = false">关闭</el-button>
|
||||
<el-button type="primary" :loading="manageDialogLoading" @click="saveCompanyManage">保存公司状态</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 应用-职务绑定弹窗:直接职务绑定 / 虚拟职务绑定 -->
|
||||
<el-dialog v-model="bindDialogVisible" title="应用-职务绑定" width="900px" destroy-on-close append-to-body>
|
||||
<p class="gs-bind-tip">
|
||||
@@ -1027,16 +999,15 @@ onMounted(loadMarket)
|
||||
color: var(--el-text-color-regular);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.detail-companies {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.detail-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
/* 抽屉内联「安装管理」的保存按钮行 */
|
||||
.detail-manage-actions {
|
||||
margin-top: 14px;
|
||||
}
|
||||
.detail-tip {
|
||||
margin: 10px 0 0;
|
||||
font-size: 12px;
|
||||
|
||||
Reference in new issue
Block a user