增加租户切换功能
This commit is contained in:
1 parent
1685b22e1e
commit
0dc5c2f075
8 files changed
+305
-60
No files matched your search
@@ -69,9 +69,8 @@
|
||||
</template>
|
||||
</el-dropdown>
|
||||
|
||||
<span v-if="companyName" class="company-name" title="当前登录的企业">
|
||||
{{ companyName }}
|
||||
</span>
|
||||
<!-- 当前企业 + 一人多企业时可点击切换 -->
|
||||
<TenantSwitcher />
|
||||
<el-dropdown trigger="click" @command="handleCommand">
|
||||
<span class="el-dropdown-link" style="cursor: pointer;">
|
||||
<img :src="getImageUrl('user')" class="user" />
|
||||
@@ -120,6 +119,7 @@ import { User, SwitchButton, Sunny, Moon, Refresh, Bell, HomeFilled, Loading, Me
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { getMySiteReminders, readAllSiteReminders } from "@/api/sitereminder";
|
||||
import MessageDetailDialog from "./MessageDetailDialog.vue";
|
||||
import TenantSwitcher from "./TenantSwitcher.vue";
|
||||
import { formatTime } from "@/utils/datetime";
|
||||
|
||||
const router = useRouter();
|
||||
@@ -290,11 +290,6 @@ const displayName = computed(() => {
|
||||
return user.account || '';
|
||||
});
|
||||
|
||||
// 当前登录的企业(租户)名称
|
||||
const companyName = computed(() => {
|
||||
return authStore.user?.tenant_name || '';
|
||||
});
|
||||
|
||||
// 当前登录用户的角色 / 部门 / 职位(来自后端 /backend/getCurrentUserProfile)
|
||||
const userProfile = reactive({ department: '', position: '', role_name: '' });
|
||||
|
||||
@@ -579,20 +574,6 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.company-name {
|
||||
max-width: 200px;
|
||||
padding: 4px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border: 1px solid var(--el-color-primary-light-7);
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.el-dropdown-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
<template>
|
||||
<!-- 当前企业 + 切换:企业名右侧带箭头,点击弹出该账号可进入的企业列表 -->
|
||||
<el-dropdown
|
||||
v-if="companyName"
|
||||
trigger="click"
|
||||
class="tenant-switcher"
|
||||
popper-class="tenant-switcher-popper"
|
||||
@command="handleSwitch"
|
||||
>
|
||||
<span class="tenant-chip" :class="[variantClass, 'is-switchable']" :title="`当前企业:${companyName},点击切换`">
|
||||
<el-icon v-if="iconVisible" class="tenant-chip-icon"><OfficeBuilding /></el-icon>
|
||||
<span class="tenant-chip-text">{{ companyName }}</span>
|
||||
<el-icon class="tenant-chip-caret"><ArrowDown /></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item in tenantOptions"
|
||||
:key="item.tid"
|
||||
:command="item.tid"
|
||||
:disabled="isCurrent(item.tid)"
|
||||
>
|
||||
<span class="tenant-option">
|
||||
<span class="tenant-option-name">{{ item.tenant_name }}</span>
|
||||
<el-icon v-if="isCurrent(item.tid)" class="tenant-option-check"><Check /></el-icon>
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="tenantOptions.length === 1" disabled>
|
||||
<span class="tenant-option-tip">当前账号未关联其他企业</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="!tenantOptions.length" :command="RELOAD_COMMAND" :disabled="loading">
|
||||
<span class="tenant-option-tip">
|
||||
{{ loading ? "企业列表加载中…" : "未获取到企业列表,点击重试" }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { ArrowDown, Check, OfficeBuilding } from "@element-plus/icons-vue";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { fetchUserInfo, resolveTenantName, switchTenant } from "@/utils/authClient";
|
||||
|
||||
/**
|
||||
* 当前企业名 + 多企业切换。
|
||||
*
|
||||
* variant:
|
||||
* chip —— 浅色标签样式(顶部栏使用)
|
||||
* plain —— 无底色 + 楼宇图标(home 门户顶部使用)
|
||||
*
|
||||
* 只要拿到企业列表(≥1 家)就渲染「企业名 + 箭头」的可点击下拉,
|
||||
* 只有一家时列表内给出说明;列表为空时退化为纯文本企业名。
|
||||
*/
|
||||
const props = withDefaults(defineProps<{ variant?: "chip" | "plain" }>(), {
|
||||
variant: "chip",
|
||||
});
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
// 下拉里「重新拉取企业列表」的命令值
|
||||
const RELOAD_COMMAND = "__reload__";
|
||||
|
||||
const tenantOptions = ref<Array<{ tid: number | string; tenant_name: string }>>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const companyName = computed(() => authStore.user?.tenant_name || "");
|
||||
const currentTid = computed(() => authStore.user?.tid || "");
|
||||
const variantClass = computed(() => (props.variant === "plain" ? "is-plain" : "is-chip"));
|
||||
const iconVisible = computed(() => props.variant === "plain");
|
||||
|
||||
function isCurrent(tid: number | string) {
|
||||
return !!tid && String(tid) === String(currentTid.value);
|
||||
}
|
||||
|
||||
async function loadTenants() {
|
||||
if (!authStore.token) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
// 用认证中心 userinfo 取「可进入的企业」;同时兜底:本地缓存缺企业名时补上,
|
||||
// 避免顶部企业名为空白(老会话、缓存被清等场景)
|
||||
const info = await fetchUserInfo();
|
||||
const list = Array.isArray(info?.tenants) ? info.tenants : [];
|
||||
tenantOptions.value = list.map((t: any) => ({
|
||||
tid: t.tid,
|
||||
tenant_name: t.tenant_name || `企业${t.tid}`,
|
||||
}));
|
||||
|
||||
const name = resolveTenantName(info);
|
||||
if (name && !companyName.value) {
|
||||
authStore.updateUserInfo({
|
||||
...authStore.user,
|
||||
tid: info?.tid ?? authStore.user?.tid,
|
||||
tenant_name: name,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// 拉取失败不影响展示,下拉里会给出「点击重试」
|
||||
tenantOptions.value = [];
|
||||
console.error("加载可进入企业列表失败:", e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSwitch(tid: number | string) {
|
||||
if (tid === RELOAD_COMMAND) {
|
||||
await loadTenants();
|
||||
return;
|
||||
}
|
||||
if (!tid || isCurrent(tid)) return;
|
||||
const target = tenantOptions.value.find((item) => String(item.tid) === String(tid));
|
||||
try {
|
||||
await switchTenant(tid);
|
||||
ElMessage.success(`已切换到「${target?.tenant_name || "新企业"}」`);
|
||||
// 菜单、按钮权限、模块套餐均按企业隔离,整页重载确保全部按新企业重新初始化
|
||||
setTimeout(() => window.location.reload(), 300);
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || "切换企业失败");
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadTenants);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.tenant-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
max-width: 220px;
|
||||
padding: 4px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border: 1px solid var(--el-color-primary-light-7);
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
&.is-switchable {
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease, background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-8);
|
||||
}
|
||||
}
|
||||
|
||||
.tenant-chip-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tenant-chip-caret {
|
||||
flex: 0 0 auto;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tenant-chip-icon {
|
||||
flex: 0 0 auto;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
// 顶部栏:与右侧圆形操作按钮等高对齐
|
||||
.tenant-chip.is-chip {
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
// home 门户顶部:无底色,图标 + 常规文字色(跟随主题变量自动适配暗色)
|
||||
.tenant-chip.is-plain {
|
||||
gap: 6px;
|
||||
padding: 0;
|
||||
max-width: 260px;
|
||||
font-size: 15px;
|
||||
color: var(--el-text-color-regular);
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
|
||||
&.is-switchable:hover {
|
||||
color: var(--el-color-primary);
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 下拉弹层由 el-dropdown 挂到 body,scoped 样式够不到,故用 popper 类名前缀限定 -->
|
||||
<style lang="less">
|
||||
.tenant-switcher-popper {
|
||||
.tenant-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
min-width: 150px;
|
||||
|
||||
.tenant-option-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tenant-option-check {
|
||||
flex: 0 0 auto;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.tenant-option-tip {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user