feat(aihr): 重构运营后台与租户知识治理
- 重构运营总览、侧栏和内容运营工作台\n- 增加大喇叭、成果审核与运营统计链路\n- 补齐租户、知识空间、分类和知识维护闭环\n- 加固标签页租户上下文、停用租户写保护与迁移预检
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
<template>
|
||||
<div class="navbar" :class="'nav' + navType">
|
||||
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggle-click="toggleSideBar" />
|
||||
<hamburger
|
||||
v-if="navType !== NavTypeEnum.TOP"
|
||||
id="hamburger-container"
|
||||
:is-active="appStore.sidebar.opened"
|
||||
class="hamburger-container"
|
||||
@toggle-click="toggleSideBar"
|
||||
/>
|
||||
|
||||
<breadcrumb v-if="navType == NavTypeEnum.LEFT" id="breadcrumb-container" class="breadcrumb-container" />
|
||||
<top-nav v-if="navType == NavTypeEnum.MIX" id="topmenu-container" class="topmenu-container" />
|
||||
@@ -11,12 +17,15 @@
|
||||
</template>
|
||||
<div class="right-menu flex align-center">
|
||||
<template v-if="appStore.device !== 'mobile'">
|
||||
<el-tag v-if="isSuperAdmin && tenantEnabled && !tenantContextVerified" class="mr-2" type="warning" effect="plain"> 租户上下文未确认 </el-tag>
|
||||
<el-select
|
||||
v-if="userId === 1 && tenantEnabled"
|
||||
v-model="companyName"
|
||||
v-if="isSuperAdmin && tenantEnabled"
|
||||
v-model="selectedTenantId"
|
||||
class="min-w-244px mr-2"
|
||||
clearable
|
||||
filterable
|
||||
:disabled="tenantContextSwitching"
|
||||
:loading="tenantContextSwitching"
|
||||
reserve-keyword
|
||||
:placeholder="proxy.$t('navbar.selectTenant')"
|
||||
@change="dynamicTenantEvent"
|
||||
@@ -34,7 +43,7 @@
|
||||
</el-tooltip>
|
||||
<!-- 消息 -->
|
||||
<el-tooltip :content="proxy.$t('navbar.message')" effect="dark" placement="bottom">
|
||||
<div style="display:flex;align-items:center">
|
||||
<div style="display: flex; align-items: center">
|
||||
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
|
||||
<template #reference>
|
||||
<el-badge :value="newNotice > 0 ? newNotice : ''" :max="99">
|
||||
@@ -99,14 +108,28 @@ import { useUserStore } from '@/store/modules/user';
|
||||
import { useSettingsStore } from '@/store/modules/settings';
|
||||
import { useNoticeStore } from '@/store/modules/notice';
|
||||
import { getTenantList } from '@/api/login';
|
||||
import { dynamicClear, dynamicTenant } from '@/api/system/tenant';
|
||||
import { getDynamicTenant } from '@/api/system/tenant';
|
||||
import { TenantVO } from '@/api/types';
|
||||
import notice from './notice/index.vue';
|
||||
import router from '@/router';
|
||||
import {
|
||||
clearDynamicTenantContext,
|
||||
clearDynamicTenantContextState,
|
||||
dynamicTenantContextVersion,
|
||||
dynamicTenantId,
|
||||
hasDynamicTenant,
|
||||
isTenantContextVersionCurrent,
|
||||
markTenantContextUnverified,
|
||||
restoreDynamicTenantContext,
|
||||
switchDynamicTenantContext,
|
||||
tenantContextVerified,
|
||||
tenantContextSwitching,
|
||||
waitForTenantRequestContextOwnership
|
||||
} from '@/utils/tenant-context';
|
||||
import { ElMessageBoxOptions } from 'element-plus/es/components/message-box/src/message-box.type';
|
||||
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||
import Logo from "@/layout/components/Sidebar/Logo.vue";
|
||||
import TopBar from './TopBar'
|
||||
import Logo from '@/layout/components/Sidebar/Logo.vue';
|
||||
import TopBar from './TopBar';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
@@ -116,14 +139,14 @@ const newNotice = ref(<number>0);
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const userId = ref(userStore.userId);
|
||||
const navType = computed(() => settingsStore.navType);
|
||||
const showLogo = computed(() => settingsStore.sidebarLogo);
|
||||
const isSuperAdmin = computed(() => userStore.roles.includes('superadmin'));
|
||||
|
||||
const companyName = ref(undefined);
|
||||
const selectedTenantId = ref<string | number>();
|
||||
const tenantList = ref<TenantVO[]>([]);
|
||||
// 是否切换了租户
|
||||
const dynamic = ref(false);
|
||||
const dynamic = hasDynamicTenant;
|
||||
// 租户开关
|
||||
const tenantEnabled = ref(true);
|
||||
// 搜索菜单
|
||||
@@ -134,33 +157,97 @@ const openSearchMenu = () => {
|
||||
};
|
||||
|
||||
// 动态切换
|
||||
const dynamicTenantEvent = async (tenantId: string) => {
|
||||
if (companyName.value != null && companyName.value !== '') {
|
||||
await dynamicTenant(tenantId);
|
||||
dynamic.value = true;
|
||||
await proxy?.$router.push('/');
|
||||
await proxy?.$tab.closeAllPage();
|
||||
await proxy?.$tab.refreshPage();
|
||||
const dynamicTenantEvent = async (tenantId?: string | number) => {
|
||||
if (tenantId === undefined || tenantId === null || tenantId === '' || tenantContextSwitching.value) return;
|
||||
const tenant = tenantList.value.find((item) => String(item.tenantId) === String(tenantId));
|
||||
if (!tenant) {
|
||||
selectedTenantId.value = dynamicTenantId.value;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await switchDynamicTenantContext({ tenantId, companyName: tenant.companyName }, '/');
|
||||
} catch {
|
||||
selectedTenantId.value = dynamicTenantId.value;
|
||||
}
|
||||
};
|
||||
|
||||
const dynamicClearEvent = async () => {
|
||||
await dynamicClear();
|
||||
dynamic.value = false;
|
||||
await proxy?.$router.push('/');
|
||||
await proxy?.$tab.closeAllPage();
|
||||
await proxy?.$tab.refreshPage();
|
||||
if (tenantContextSwitching.value) return;
|
||||
try {
|
||||
await clearDynamicTenantContext('/');
|
||||
} catch {
|
||||
selectedTenantId.value = dynamicTenantId.value;
|
||||
}
|
||||
};
|
||||
|
||||
/** 租户列表 */
|
||||
const initTenantList = async () => {
|
||||
const { data } = await getTenantList(true);
|
||||
tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
|
||||
if (tenantEnabled.value) {
|
||||
tenantList.value = data.voList;
|
||||
await waitForTenantRequestContextOwnership();
|
||||
let contextVersion = dynamicTenantContextVersion.value;
|
||||
const isCurrentInit = () => isTenantContextVersionCurrent(contextVersion);
|
||||
let currentTenantId: string | number | null | undefined;
|
||||
let dynamicContextLoaded = false;
|
||||
|
||||
// Read the page-scoped server context first. Without this echo, a restored
|
||||
// dynamic tenant deliberately rejects the tenant list request as stale.
|
||||
if (isSuperAdmin.value) {
|
||||
try {
|
||||
currentTenantId = (await getDynamicTenant()).data;
|
||||
if (!isCurrentInit()) return;
|
||||
restoreDynamicTenantContext(
|
||||
currentTenantId !== undefined && currentTenantId !== null && currentTenantId !== '' ? { tenantId: currentTenantId } : undefined
|
||||
);
|
||||
contextVersion = dynamicTenantContextVersion.value;
|
||||
dynamicContextLoaded = true;
|
||||
} catch {
|
||||
if (!isCurrentInit()) return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await getTenantList(true);
|
||||
if (!isCurrentInit()) return;
|
||||
|
||||
tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
|
||||
if (!tenantEnabled.value || !isSuperAdmin.value) {
|
||||
tenantList.value = [];
|
||||
clearDynamicTenantContextState();
|
||||
return;
|
||||
}
|
||||
|
||||
tenantList.value = data.voList.filter((tenant) => tenant.status === undefined || tenant.status === '0');
|
||||
} catch {
|
||||
if (!isCurrentInit()) return;
|
||||
tenantList.value = [];
|
||||
markTenantContextUnverified();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dynamicContextLoaded) {
|
||||
markTenantContextUnverified();
|
||||
return;
|
||||
}
|
||||
|
||||
const tenant = tenantList.value.find((item) => String(item.tenantId) === String(currentTenantId));
|
||||
if (currentTenantId !== undefined && currentTenantId !== null && currentTenantId !== '' && !tenant) {
|
||||
markTenantContextUnverified();
|
||||
return;
|
||||
}
|
||||
restoreDynamicTenantContext(
|
||||
currentTenantId !== undefined && currentTenantId !== null && currentTenantId !== ''
|
||||
? { tenantId: currentTenantId, companyName: tenant?.companyName }
|
||||
: undefined
|
||||
);
|
||||
};
|
||||
|
||||
watch(
|
||||
dynamicTenantId,
|
||||
(tenantId) => {
|
||||
selectedTenantId.value = tenantId;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
initTenantList
|
||||
});
|
||||
@@ -282,7 +369,6 @@ watch(
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
|
||||
.errLog-container {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
@@ -310,7 +396,9 @@ watch(
|
||||
|
||||
&.hover-effect {
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease, color 0.2s ease;
|
||||
transition:
|
||||
background 0.2s ease,
|
||||
color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--el-fill-color-light);
|
||||
|
||||
Reference in New Issue
Block a user