mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-30 16:03:50 +00:00
refactor: 完成多租户架构移除与系统模块重构
1. 移除所有租户相关逻辑:删除租户ID查询参数、租户字段、租户存储相关代码 2. 重构菜单系统:合并platform/system菜单模块,调整菜单作用域为web/app 3. 修复导出接口:统一post请求使用body传参,替换get导出为post 4. 优化权限标识:更新权限路由前缀为module_system 5. 简化菜单过滤逻辑:移除工作区模式判断,直接展示所有菜单 6. 移除租户切换、代签入等无关功能组件与状态 7. 新增用户注册功能与工单导出接口
This commit is contained in:
@@ -11,11 +11,7 @@
|
||||
@touchend="dragFinish"
|
||||
>
|
||||
<!-- 进度条 -->
|
||||
<div
|
||||
class="dv_progress_bar"
|
||||
ref="progressBar"
|
||||
:style="progressBarStyle"
|
||||
></div>
|
||||
<div class="dv_progress_bar" ref="progressBar" :style="progressBarStyle"></div>
|
||||
|
||||
<!-- 提示文本 -->
|
||||
<div class="dv_text" :style="textStyle" ref="messageRef">
|
||||
@@ -295,11 +291,11 @@ defineExpose({
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: move;
|
||||
z-index: 9;
|
||||
|
||||
i {
|
||||
padding-left: 0;
|
||||
|
||||
@@ -6,7 +6,6 @@ export type AuditSearchFormParams = {
|
||||
updated_id?: number | null;
|
||||
created_time?: string[];
|
||||
updated_time?: string[];
|
||||
tenant_id?: number | null;
|
||||
};
|
||||
|
||||
export interface GetAuditSearchFormItemsOptions {
|
||||
@@ -16,17 +15,13 @@ export interface GetAuditSearchFormItemsOptions {
|
||||
updatedByLabel?: string;
|
||||
createdTimeLabel?: string;
|
||||
updatedTimeLabel?: string;
|
||||
tenantIdLabel?: string;
|
||||
createdByPlaceholder?: string;
|
||||
updatedByPlaceholder?: string;
|
||||
tenantIdPlaceholder?: string;
|
||||
/** 日期时间范围 valueFormat,默认 YYYY-MM-DD HH:mm:ss */
|
||||
valueFormat?: string;
|
||||
rangeSeparator?: string;
|
||||
startPlaceholder?: string;
|
||||
endPlaceholder?: string;
|
||||
/** 是否显示租户ID字段,默认 false */
|
||||
showTenantId?: boolean;
|
||||
/** 是否显示创建人字段,默认 true */
|
||||
showCreatedBy?: boolean;
|
||||
/** 是否显示更新人字段,默认 true */
|
||||
@@ -38,7 +33,7 @@ export interface GetAuditSearchFormItemsOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* 常见「创建人 / 更新人 / 创建时间 / 更新时间 / 租户ID」搜索项,供 FaSearchBar `items` 使用。
|
||||
* 常见「创建人 / 更新人 / 创建时间 / 更新时间」搜索项,供 FaSearchBar `items` 使用。
|
||||
* 当 FaSearchBar 设置 `includeAudit` 为 true 时,会自动追加这些字段。
|
||||
* 创建人、更新人插槽由 FaSearchBar 内置提供,也可通过 `#created_id`、`#updated_id` 覆盖。
|
||||
*/
|
||||
@@ -50,7 +45,6 @@ export function getAuditSearchFormItems(
|
||||
const rangeSep = options?.rangeSeparator ?? "至";
|
||||
const sp = options?.startPlaceholder ?? "开始";
|
||||
const ep = options?.endPlaceholder ?? "结束";
|
||||
const showTenantId = options?.showTenantId ?? false;
|
||||
const showCreatedBy = options?.showCreatedBy ?? true;
|
||||
const showUpdatedBy = options?.showUpdatedBy ?? true;
|
||||
const showCreatedTime = options?.showCreatedTime ?? true;
|
||||
@@ -109,17 +103,6 @@ export function getAuditSearchFormItems(
|
||||
span,
|
||||
expandOnly: true, // 标记为展开专属字段
|
||||
},
|
||||
tenant_id: {
|
||||
label: options?.tenantIdLabel ?? "租户ID",
|
||||
key: "tenant_id",
|
||||
type: "input",
|
||||
props: {
|
||||
placeholder: options?.tenantIdPlaceholder ?? "请输入租户ID",
|
||||
style: { width: "100%" },
|
||||
},
|
||||
span,
|
||||
expandOnly: true, // 标记为展开专属字段
|
||||
},
|
||||
};
|
||||
|
||||
const fieldOrder: string[] = [];
|
||||
@@ -127,7 +110,6 @@ export function getAuditSearchFormItems(
|
||||
if (showUpdatedBy) fieldOrder.push("updated_id");
|
||||
if (showCreatedTime) fieldOrder.push("created_time");
|
||||
if (showUpdatedTime) fieldOrder.push("updated_time");
|
||||
if (showTenantId) fieldOrder.push("tenant_id");
|
||||
|
||||
return fieldOrder
|
||||
.map((key) => fieldMap[key])
|
||||
|
||||
@@ -8,14 +8,6 @@
|
||||
: '',
|
||||
]"
|
||||
>
|
||||
<div
|
||||
v-if="isImpersonate"
|
||||
class="h-8 bg-warning/10 flex items-center justify-center gap-2 text-sm text-warning border-b border-warning/20"
|
||||
>
|
||||
<FaSvgIcon icon="ri:shield-alert-line" class="text-warning" />
|
||||
<span>当前为平台管理员代签入模式 · 租户:{{ impersonateTenantName }}</span>
|
||||
<ElButton type="primary" size="small" @click="handleExitImpersonate">退出工作区</ElButton>
|
||||
</div>
|
||||
<div
|
||||
class="relative box-border flex justify-between h-15 leading-15 select-none"
|
||||
:class="[
|
||||
@@ -189,9 +181,6 @@
|
||||
:icon="isDark ? 'ri:sun-fill' : 'ri:moon-line'"
|
||||
/>
|
||||
|
||||
<!-- 租户切换器(全局可见,1步切换) -->
|
||||
<FaTenantSwitcher />
|
||||
|
||||
<!-- 用户头像、菜单 -->
|
||||
<FaUserMenu />
|
||||
</div>
|
||||
@@ -223,7 +212,6 @@ import { mittBus, themeAnimation } from "@utils";
|
||||
import { useCommon } from "@/hooks/core/useCommon";
|
||||
import { useHeaderBar } from "@/hooks/core/useHeaderBar";
|
||||
import FaUserMenu from "./widgets/FaUserMenu.vue";
|
||||
import FaTenantSwitcher from "./widgets/FaTenantSwitcher.vue";
|
||||
|
||||
defineOptions({ name: "FaHeaderBar" });
|
||||
|
||||
@@ -278,16 +266,6 @@ const { visibleMenus: menuList } = storeToRefs(menuStore);
|
||||
const showNotice = ref(false);
|
||||
const notice = ref(null);
|
||||
|
||||
const isImpersonate = computed(() => userStore.info.is_impersonate === true);
|
||||
|
||||
const impersonateTenantName = computed(() => {
|
||||
return userStore.workspaceTenant?.name || userStore.currentTenant?.name || "未知";
|
||||
});
|
||||
|
||||
async function handleExitImpersonate() {
|
||||
await userStore.exitTenantWorkspace();
|
||||
}
|
||||
|
||||
// 菜单类型判断
|
||||
const isLeftMenu = computed(() => menuType.value === MenuTypeEnum.LEFT);
|
||||
const isDualMenu = computed(() => menuType.value === MenuTypeEnum.DUAL_MENU);
|
||||
|
||||
@@ -1,437 +0,0 @@
|
||||
<!-- 工作区模式切换器:平台模式 → 进入租户 | 租户模式 → 返回平台 -->
|
||||
<template>
|
||||
<!-- 平台管理模式:"进入租户"按钮 -->
|
||||
<ElDropdown
|
||||
v-if="isPlatformMode && tenantList.length > 0"
|
||||
trigger="click"
|
||||
placement="bottom-start"
|
||||
:disabled="switching"
|
||||
@command="handleEnterTenant"
|
||||
@visible-change="(v) => (dropdownVisible = v)"
|
||||
popper-class="fa-tenant-dropdown"
|
||||
>
|
||||
<div
|
||||
class="tenant-switcher workspace-entry"
|
||||
:class="{ 'is-active': dropdownVisible, 'is-switching': switching }"
|
||||
title="进入租户工作区"
|
||||
>
|
||||
<FaSvgIcon icon="ri:building-2-fill" class="icon" />
|
||||
<span class="name">进入租户</span>
|
||||
<FaSvgIcon
|
||||
v-if="!switching"
|
||||
icon="ri:arrow-down-s-line"
|
||||
class="arrow"
|
||||
:class="{ rotated: dropdownVisible }"
|
||||
/>
|
||||
<ElIcon v-else class="arrow is-loading"><Loading /></ElIcon>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<div class="dropdown-header">
|
||||
<span class="dropdown-title">选择要进入的租户</span>
|
||||
<ElTag size="small" effect="plain" type="info"> 共 {{ tenantList.length }} 个 </ElTag>
|
||||
</div>
|
||||
<ElDropdownItem v-for="t in tenantList" :key="t.id" :command="t.id" :disabled="switching">
|
||||
<div class="dropdown-item">
|
||||
<div class="item-main">
|
||||
<FaSvgIcon icon="ri:building-2-line" class="item-icon" />
|
||||
<div class="item-text">
|
||||
<div class="item-name">{{ t.name }}</div>
|
||||
<div v-if="t.code" class="item-code">{{ t.code }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<FaSvgIcon icon="ri:arrow-right-s-line" class="item-enter-icon" />
|
||||
</div>
|
||||
</ElDropdownItem>
|
||||
<div class="dropdown-footer-hint">
|
||||
<FaSvgIcon icon="ri:information-line" class="hint-icon" />
|
||||
<span>进入后可查看该租户的数据和菜单</span>
|
||||
</div>
|
||||
<div v-if="switching" class="dropdown-footer">
|
||||
<ElIcon class="is-loading"><Loading /></ElIcon>
|
||||
<span>进入中...</span>
|
||||
</div>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</ElDropdown>
|
||||
|
||||
<!-- 租户工作区模式:显示当前租户 + 返回平台(仅超管可见) -->
|
||||
<ElDropdown
|
||||
v-else-if="!isPlatformMode && isSuperuser"
|
||||
trigger="click"
|
||||
placement="bottom-start"
|
||||
:disabled="switching"
|
||||
@command="handleCommand"
|
||||
@visible-change="(v) => (dropdownVisible = v)"
|
||||
popper-class="fa-tenant-dropdown"
|
||||
>
|
||||
<div
|
||||
class="tenant-switcher"
|
||||
:class="{ 'is-active': dropdownVisible, 'is-switching': switching }"
|
||||
:title="'当前工作区:' + displayTenantName"
|
||||
>
|
||||
<FaSvgIcon icon="ri:building-2-fill" class="icon" />
|
||||
<span class="name">{{ displayTenantName }}</span>
|
||||
<FaSvgIcon
|
||||
v-if="!switching"
|
||||
icon="ri:arrow-down-s-line"
|
||||
class="arrow"
|
||||
:class="{ rotated: dropdownVisible }"
|
||||
/>
|
||||
<ElIcon v-else class="arrow is-loading"><Loading /></ElIcon>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<div class="dropdown-header">
|
||||
<span class="dropdown-title">当前工作区</span>
|
||||
<ElTag size="small" effect="plain" type="success"> 租户 </ElTag>
|
||||
</div>
|
||||
<div class="dropdown-current-info">
|
||||
<FaSvgIcon icon="ri:building-2-fill" class="current-icon" />
|
||||
<div class="current-text">
|
||||
<div class="current-name">{{ displayTenantName }}</div>
|
||||
<div v-if="workspaceTenant?.code" class="current-code">
|
||||
{{ workspaceTenant.code }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown-divider" />
|
||||
<ElDropdownItem command="exitPlatform" :disabled="switching">
|
||||
<div class="dropdown-item exit-action">
|
||||
<FaSvgIcon icon="ri:arrow-go-back-fill" class="item-icon exit-icon" />
|
||||
<div class="item-text">
|
||||
<div class="item-name">返回平台管理</div>
|
||||
<div class="item-code">回到平台管理视角</div>
|
||||
</div>
|
||||
</div>
|
||||
</ElDropdownItem>
|
||||
<div v-if="switching" class="dropdown-footer">
|
||||
<ElIcon class="is-loading"><Loading /></ElIcon>
|
||||
<span>切换中...</span>
|
||||
</div>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</ElDropdown>
|
||||
|
||||
<!-- 普通租户用户:仅显示当前租户名(无下拉) -->
|
||||
<div
|
||||
v-else-if="!isPlatformMode"
|
||||
class="tenant-switcher tenant-display"
|
||||
:title="'当前租户:' + displayTenantName"
|
||||
>
|
||||
<FaSvgIcon icon="ri:building-2-fill" class="icon" />
|
||||
<span class="name">{{ displayTenantName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { useUserStore } from "@stores";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
defineOptions({ name: "FaTenantSwitcher" });
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { tenantList, isPlatformMode, workspaceMode, workspaceTenant, currentTenant, info } =
|
||||
storeToRefs(userStore);
|
||||
|
||||
const dropdownVisible = ref(false);
|
||||
const switching = ref(false);
|
||||
|
||||
/** 是否为超管(判断是否显示"返回平台"入口) */
|
||||
const isSuperuser = computed(() => !!info.value?.is_superuser);
|
||||
|
||||
/** 当前显示的工作区名称 */
|
||||
const displayTenantName = computed(() => {
|
||||
if (isPlatformMode.value) return "平台管理";
|
||||
return (
|
||||
workspaceTenant.value?.name || currentTenant.value?.name || info.value?.tenant_by?.name || "—"
|
||||
);
|
||||
});
|
||||
|
||||
/** 平台模式:进入租户工作区(平台管理员使用代签入) */
|
||||
async function handleEnterTenant(tenantId: number) {
|
||||
if (switching.value) return;
|
||||
const tenant = tenantList.value.find((t) => t.id === tenantId);
|
||||
if (!tenant) return;
|
||||
switching.value = true;
|
||||
dropdownVisible.value = false;
|
||||
try {
|
||||
if (isSuperuser.value) {
|
||||
await userStore.impersonate(tenantId);
|
||||
} else {
|
||||
await userStore.selectTenant(tenantId);
|
||||
}
|
||||
workspaceMode.value = "tenant";
|
||||
workspaceTenant.value = tenant;
|
||||
setTimeout(() => window.location.reload(), 200);
|
||||
} catch {
|
||||
switching.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 租户模式:处理下拉命令 */
|
||||
async function handleCommand(command: string) {
|
||||
if (command === "exitPlatform") {
|
||||
if (switching.value) return;
|
||||
switching.value = true;
|
||||
dropdownVisible.value = false;
|
||||
try {
|
||||
await userStore.exitTenantWorkspace();
|
||||
setTimeout(() => window.location.reload(), 200);
|
||||
} catch {
|
||||
switching.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tenant-switcher {
|
||||
display: inline-flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
max-width: 180px;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
font-size: 13px;
|
||||
|
||||
@media (width <= 640px) {
|
||||
max-width: 120px;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
color: var(--el-text-color-primary);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background: var(--fa-gray-100);
|
||||
border: 1px solid var(--fa-gray-300);
|
||||
border-radius: 6px;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-9);
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-9);
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&.is-switching {
|
||||
cursor: wait;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&.workspace-entry {
|
||||
color: var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-9);
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&.tenant-display {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.icon {
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
transition: transform 0.2s;
|
||||
|
||||
&.rotated {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
/* 全局样式:下拉面板 */
|
||||
.fa-tenant-dropdown {
|
||||
min-width: 240px;
|
||||
max-width: calc(100vw - 20px);
|
||||
padding: 0 !important;
|
||||
|
||||
@media (width <= 640px) {
|
||||
width: 90vw;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item {
|
||||
padding: 0 !important;
|
||||
|
||||
&:not(.is-disabled):hover {
|
||||
background: var(--fa-gray-200) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-header {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.dropdown-title {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
height: 1px;
|
||||
margin: 4px 0;
|
||||
background: var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.dropdown-current-info {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
|
||||
.current-icon {
|
||||
flex-shrink: 0;
|
||||
font-size: 20px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.current-text {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.current-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.current-code {
|
||||
margin-top: 2px;
|
||||
font-size: 11px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border-radius: 0;
|
||||
transition: all 0.15s;
|
||||
|
||||
&.exit-action {
|
||||
&:hover {
|
||||
.exit-icon {
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-footer-hint {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px 12px 8px;
|
||||
font-size: 11px;
|
||||
color: var(--el-text-color-secondary);
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
|
||||
.hint-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.item-main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
flex-shrink: 0;
|
||||
font-size: 15px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.item-enter-icon {
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.exit-icon {
|
||||
flex-shrink: 0;
|
||||
font-size: 15px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.item-text {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.item-code {
|
||||
margin-top: 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 11px;
|
||||
color: var(--el-text-color-secondary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dropdown-footer {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -52,21 +52,6 @@
|
||||
</ul>
|
||||
</ElScrollbar>
|
||||
|
||||
<!-- 工作区模式指示器 -->
|
||||
<div class="workspace-indicator" :class="{ 'is-collapsed': !menuOpen }">
|
||||
<FaSvgIcon
|
||||
:icon="isPlatformMode ? 'ri:shield-user-fill' : 'ri:building-2-fill'"
|
||||
class="indicator-icon"
|
||||
/>
|
||||
<span v-show="menuOpen" class="indicator-label">
|
||||
{{
|
||||
isPlatformMode
|
||||
? "平台管理"
|
||||
: workspaceTenant?.name || currentTenant?.name || sidebarTitle
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<FaIconButton
|
||||
class="switch-btn size-10"
|
||||
icon="ri:arrow-left-right-fill"
|
||||
@@ -141,7 +126,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import AppConfig from "@/config";
|
||||
import { useConfigStore, useSettingsStore, useMenuStore, useUserStore } from "@stores";
|
||||
import { useConfigStore, useSettingsStore, useMenuStore } from "@stores";
|
||||
import { MenuTypeEnum, MenuWidth } from "@/enums/appEnum";
|
||||
import { isIframe, handleMenuJump } from "@utils";
|
||||
import SidebarSubmenu from "./widgets/FaSidebarSubmenu.vue";
|
||||
@@ -158,8 +143,6 @@ const route = useRoute();
|
||||
const router = useRouter();
|
||||
const settingStore = useSettingsStore();
|
||||
const configStore = useConfigStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
/** 租户配置:logo_url / name */
|
||||
const sidebarLogoSrc = computed(() => {
|
||||
const raw = configStore.configData.logo_url?.config_value;
|
||||
@@ -182,8 +165,6 @@ const {
|
||||
showAppLogo,
|
||||
} = storeToRefs(settingStore);
|
||||
|
||||
const { isPlatformMode, workspaceTenant, currentTenant } = storeToRefs(userStore);
|
||||
|
||||
// 组件内部状态
|
||||
const defaultOpenedMenus = ref<string[]>([]);
|
||||
const showMobileModal = ref(false);
|
||||
@@ -560,34 +541,6 @@ watch(menuOpen, (isMenuOpen: boolean) => {
|
||||
.menu-model {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.workspace-indicator {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
padding: 0 20px;
|
||||
color: var(--el-text-color-secondary);
|
||||
border-top: 1px solid var(--fa-card-border);
|
||||
|
||||
.indicator-icon {
|
||||
flex-shrink: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.indicator-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&.is-collapsed {
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (width <= 800px) {
|
||||
|
||||
@@ -9,19 +9,6 @@
|
||||
class="login-page-form mt-4"
|
||||
@keyup.enter="$emit('submit')"
|
||||
>
|
||||
<ElFormItem prop="tenant_name">
|
||||
<ElInput
|
||||
v-model.trim="forgetForm.tenant_name"
|
||||
class="custom-height"
|
||||
clearable
|
||||
:placeholder="$t('login.placeholder.tenantName')"
|
||||
@keyup.enter="$emit('submit')"
|
||||
>
|
||||
<template #prefix>
|
||||
<ElIcon><OfficeBuilding /></ElIcon>
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="username">
|
||||
<ElInput
|
||||
v-model.trim="forgetForm.username"
|
||||
@@ -93,7 +80,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Lock, OfficeBuilding, User } from "@element-plus/icons-vue";
|
||||
import { Lock, User } from "@element-plus/icons-vue";
|
||||
import type { ForgetPasswordForm } from "@/api/module_system/user";
|
||||
import type { FormRules } from "element-plus";
|
||||
|
||||
|
||||
@@ -9,18 +9,6 @@
|
||||
class="login-page-form"
|
||||
@keyup.enter="$emit('submit')"
|
||||
>
|
||||
<ElFormItem prop="tenant_name">
|
||||
<ElInput
|
||||
class="custom-height"
|
||||
v-model.trim="registerForm.tenant_name"
|
||||
clearable
|
||||
:placeholder="$t('login.placeholder.tenantName')"
|
||||
>
|
||||
<template #prefix>
|
||||
<ElIcon><OfficeBuilding /></ElIcon>
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="username">
|
||||
<ElInput
|
||||
class="custom-height"
|
||||
@@ -120,7 +108,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Lock, Message, OfficeBuilding, User } from "@element-plus/icons-vue";
|
||||
import { Lock, Message, User } from "@element-plus/icons-vue";
|
||||
import type { RegisterForm } from "@/api/module_system/user";
|
||||
import type { FormRules } from "element-plus";
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
<div
|
||||
class="auth-top-bar-actions-panel pointer-events-auto flex shrink-0 items-center justify-center gap-1.5 px-2 py-1.5 max-sm:mr-1"
|
||||
>
|
||||
|
||||
<div class="color-picker-expandable relative flex items-center max-sm:hidden!">
|
||||
<div
|
||||
class="color-dots absolute right-0 rounded-full flex items-center gap-2 rounded-5 px-2.5 py-2 pr-9 pl-2.5 opacity-0"
|
||||
@@ -106,38 +105,12 @@
|
||||
class="text-xl text-g-800 transition-colors duration-300"
|
||||
/>
|
||||
</div>
|
||||
<!-- 租户切换 -->
|
||||
<ElDropdown
|
||||
@command="onTenantChange"
|
||||
popper-class="langDropDownStyle"
|
||||
trigger="hover"
|
||||
>
|
||||
<div
|
||||
class="btn tenant-btn auth-top-bar__action h-8 w-8 cursor-pointer flex items-center justify-center transition duration-300"
|
||||
:title="currentTenantName"
|
||||
>
|
||||
<FaSvgIcon icon="ri:building-line" class="text-xl transition-colors duration-300 text-g-800" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<div v-for="t in tenantOptions" :key="t.id" class="lang-btn-item">
|
||||
<ElDropdownItem
|
||||
:command="t.id"
|
||||
:class="{ 'is-selected': t.id === (currentTenantId || 1) }"
|
||||
>
|
||||
<span class="menu-txt">{{ t.name }}</span>
|
||||
<FaSvgIcon icon="ri:check-fill" class="text-base" v-if="t.id === (currentTenantId || 1)" />
|
||||
</ElDropdownItem>
|
||||
</div>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</ElDropdown>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { computed } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useSettingsStore, useUserStore, useConfigStore } from "@stores";
|
||||
@@ -147,7 +120,6 @@ import { languageOptions } from "@/locales";
|
||||
import { LanguageEnum } from "@/enums/appEnum";
|
||||
import AppConfig from "@/config";
|
||||
import { LoginPanelAlign } from "@/components/views/fa-login/composables/useLoginPanelAlign";
|
||||
import AuthAPI from "@/api/module_system/auth";
|
||||
|
||||
defineOptions({ name: "AuthTopBar" });
|
||||
|
||||
@@ -162,7 +134,6 @@ const props = withDefaults(defineProps<Props>(), {});
|
||||
|
||||
interface Emits {
|
||||
"update:panelAlign": [value: LoginPanelAlign];
|
||||
"tenantChange": [tenantId: number];
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
@@ -220,41 +191,6 @@ const changeLanguage = (lang: LanguageEnum) => {
|
||||
userStore.setLanguage(lang);
|
||||
};
|
||||
|
||||
// ── 租户切换 ──
|
||||
interface TenantItem {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
const tenantOptions = ref<TenantItem[]>([]);
|
||||
const currentTenantId = computed(() => {
|
||||
const val = configStore.configData?.tenant_id?.config_value;
|
||||
return val ? Number(val) : undefined;
|
||||
});
|
||||
const currentTenantName = computed(() => {
|
||||
return configStore.configData?.name?.config_value?.trim() || "选择租户";
|
||||
});
|
||||
|
||||
async function fetchTenantOptions() {
|
||||
try {
|
||||
const { data } = await AuthAPI.getTenantOptions();
|
||||
tenantOptions.value = data?.data || [];
|
||||
} catch {
|
||||
tenantOptions.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
function onTenantChange(tenantId: number) {
|
||||
const numId = Number(tenantId);
|
||||
if (numId === currentTenantId.value) return;
|
||||
emit("tenantChange", numId);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchTenantOptions();
|
||||
});
|
||||
|
||||
const changeThemeColor = (color: string) => {
|
||||
if (systemThemeColor.value === color) return;
|
||||
settingStore.setElementTheme(color);
|
||||
|
||||
Reference in New Issue
Block a user