src/layouts/basic.vue
@@ -1,34 +1,30 @@
<script lang="ts" setup>
import type { NotificationItem } from '..\packages\effects\layouts\src';
import type { NotificationItem } from '@vben/layouts';
import type { SystemTenantApi } from '#/api/system/tenant';
import { computed, onMounted, ref, watch } from 'vue';
import { useAccess } from '..\packages\effects\access\src';
import { AuthenticationLoginExpiredModal, useVbenModal } from '..\packages\effects\common-ui\src';
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '..\packages\constants\src';
import { isTenantEnable, useTabs, useWatermark } from '..\packages\effects\hooks\src';
import { AuthenticationLoginExpiredModal, useVbenModal } from '@vben/common-ui';
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
import { useWatermark } from '@vben/hooks';
import {
  AntdProfileOutlined,
  BookOpenText,
  CircleHelp,
  IconifyIcon,
  SvgGithubIcon,
} from '..\packages\icons\src';
} from '@vben/icons';
import {
  BasicLayout,
  Help,
  LockScreen,
  Notification,
  TenantDropdown,
  UserDropdown,
} from '..\packages\effects\layouts\src';
import { preferences, usePreferences } from '..\packages\preferences\src';
import { useAccessStore, useUserStore } from '..\packages\stores\src';
import { formatDateTime, openWindow } from '..\packages\utils\src';
} from '@vben/layouts';
import { preferences, usePreferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { formatDateTime, openWindow } from '@vben/utils';
import { message, Tooltip } from 'ant-design-vue';
import {
  getUnreadNotifyMessageCount,
@@ -36,18 +32,16 @@
  updateAllNotifyMessageRead,
  updateNotifyMessageRead,
} from '#/api/system/notify/message';
import { getSimpleTenantList } from '#/api/system/tenant';
import { $t } from '#/locales';
import { router } from '#/router';
import { useAuthStore } from '#/store';
import LoginForm from '#/views/_core/authentication/login.vue';
import PortalBrandText from '#/components/portal/portal-brand-text.vue';
const userStore = useUserStore();
const authStore = useAuthStore();
const accessStore = useAccessStore();
const { hasAccessByCodes } = useAccess();
const { destroyWatermark, updateWatermark } = useWatermark();
const { closeOtherTabs, refreshTab } = useTabs();
const notifications = ref<NotificationItem[]>([]);
const unreadCount = ref(0);
@@ -67,30 +61,35 @@
    text: $t('ui.widgets.profile'),
  },
  {
    handler: () => {
      openWindow(VBEN_DOC_URL, {
        target: '_blank',
      });
    },
    icon: BookOpenText,
    text: $t('ui.widgets.document'),
    handler: () => router.push({ name: 'Profile', query: { section: 'resetPwd' } }),
    icon: 'lucide:shield-check',
    text: '密码设置',
  },
  {
    handler: () => {
      openWindow(VBEN_GITHUB_URL, {
        target: '_blank',
      });
    },
    icon: SvgGithubIcon,
    text: 'GitHub',
  },
  {
    handler: () => {
      helpModalApi.open();
    },
    icon: CircleHelp,
    text: $t('ui.widgets.qa'),
  },
  // {
  //   handler: () => {
  //     openWindow(VBEN_DOC_URL, {
  //       target: '_blank',
  //     });
  //   },
  //   icon: BookOpenText,
  //   text: $t('ui.widgets.document'),
  // },
  // {
  //   handler: () => {
  //     openWindow(VBEN_GITHUB_URL, {
  //       target: '_blank',
  //     });
  //   },
  //   icon: SvgGithubIcon,
  //   text: 'GitHub',
  // },
  // {
  //   handler: () => {
  //     helpModalApi.open();
  //   },
  //   icon: CircleHelp,
  //   text: $t('ui.widgets.qa'),
  // },
]);
const avatar = computed(() => {
@@ -157,47 +156,10 @@
  handleNotificationGetUnreadCount();
}
/** 打开 IM 聊天 */
function handleOpenImHome() {
  const { href } = router.resolve({ name: 'ImHome' });
  window.open(href, '_blank');
}
// 租户列表
const tenants = ref<SystemTenantApi.Tenant[]>([]);
const tenantEnable = computed(
  () => hasAccessByCodes(['system:tenant:visit']) && isTenantEnable(),
);
/** 获取租户列表 */
async function handleGetTenantList() {
  if (tenantEnable.value) {
    tenants.value = await getSimpleTenantList();
  }
}
/** 处理租户切换 */
async function handleTenantChange(tenant: SystemTenantApi.Tenant) {
  if (!tenant || !tenant.id) {
    message.error('切换租户失败');
    return;
  }
  // 设置访问租户 ID
  accessStore.setVisitTenantId(tenant.id as number);
  // 关闭其他标签页,只保留当前页
  await closeOtherTabs();
  // 刷新当前页面
  await refreshTab();
  // 提示切换成功
  message.success(`切换当前租户为: ${tenant.name}`);
}
// ========== 初始化 ==========
onMounted(() => {
  // 首次加载未读数量
  handleNotificationGetUnreadCount();
  // 获取租户列表
  handleGetTenantList();
  // 轮询刷新未读数量
  setInterval(
    () => {
@@ -275,9 +237,13 @@
</script>
<template>
  <BasicLayout @clear-preferences-and-logout="handleLogout">
  <BasicLayout class="supply-portal-shell" @clear-preferences-and-logout="handleLogout" @click-logo="router.push('/analytics')">
    <template #logo-text>
      <PortalBrandText />
    </template>
    <template #user-dropdown>
      <UserDropdown
        show-text
        :avatar
        :menus
        :text="userStore.userInfo?.nickname"
@@ -286,6 +252,9 @@
        @logout="handleLogout"
        @clear-preferences-and-logout="handleLogout"
      />
    </template>
    <template #header-right-165>
      <button type="button" class="portal-personal-entry" aria-label="个人中心" title="个人中心" :class="{ 'is-current': router.currentRoute.value.name === 'Profile' }" @click="router.push({ name: 'Profile' })"><IconifyIcon icon="lucide:user-round" /><span>个人中心</span></button>
    </template>
    <template #notification>
      <Notification
@@ -299,26 +268,15 @@
        @on-click="handleClick"
      />
    </template>
    <template #header-right-1>
      <div v-if="tenantEnable">
        <TenantDropdown
          class="mr-2"
          :tenant-list="tenants"
          :visit-tenant-id="accessStore.visitTenantId"
          @success="handleTenantChange"
        />
      </div>
    </template>
    <template #header-right-900>
      <Tooltip title="IM 聊天">
        <button
          class="hover:bg-accent hover:text-accent-foreground mr-1 inline-flex size-8 items-center justify-center rounded-md transition-colors"
          type="button"
          @click="handleOpenImHome"
        >
          <IconifyIcon class="size-4" icon="lucide:message-circle" />
        </button>
      </Tooltip>
    <template v-if="router.hasRoute('MesWorkbench') && accessStore.accessMenus.some((menu) => /生产|MES/i.test(menu.name))" #header-right-170>
      <button
        type="button"
        class="flex cursor-pointer items-center gap-1.5 rounded-md border-none bg-primary/15 px-3 py-1.5 text-sm font-medium text-primary transition-colors hover:bg-primary/25"
        @click="router.push({ name: 'MesWorkbench' })"
      >
        <IconifyIcon icon="lucide:factory" class="size-4" />
        <span>工作台</span>
      </button>
    </template>
    <template #extra>
      <AuthenticationLoginExpiredModal