| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301 |
- <script setup lang="ts">
- import type { IContextMenuItem } from '@velofex-core/tabs-ui';
- import type { MenuProps } from 'antdv-next';
- import {
- computed,
- defineComponent,
- h,
- onBeforeUnmount,
- onMounted,
- ref,
- watch,
- } from 'vue';
- import {
- loadLocaleMessages,
- type SupportedLanguagesType,
- } from '@velofex/locales';
- import { preferences, updatePreferences } from '@velofex/preferences';
- import { VbenScrollbar } from '@velofex-core/shadcn-ui';
- import { TabsView } from '@velofex-core/tabs-ui';
- import {
- curUserInfo,
- type curUserInfoPayload,
- leftMenuListFromB,
- } from '@/api/account';
- import defaultCompanyLogo from '@/assets/image/earth_18301626@2x.png';
- import defaultAvatar from '@/assets/image/user.png';
- import { resolveEnterpriseCodeFromLocation } from '@/router/guard';
- import { Dropdown, Menu } from 'antdv-next';
- import HomeDashboardTab from '#/components/home-dashboard-tab.vue';
- import SelectLang from '#/components/select-lang.vue';
- import { $t } from '#/locales';
- const HOME_TAB_KEY = '__home__';
- const HomeTabIcon = defineComponent({
- name: 'HomeTabIcon',
- setup(_, { attrs }) {
- return () =>
- h('i', {
- ...attrs,
- class: ['home-tab-icon', 'icon-dashboard', attrs.class],
- });
- },
- });
- const leftMenuItems = ref<MenuProps['items']>([]);
- const selectedKeys = ref<string[]>([]);
- const userMenuItems = computed<MenuProps['items']>(() => [
- { key: 'Logout', label: $t('home.userMenu.logout') },
- ]);
- const openMenuKeys = ref<string[]>([]);
- const isMobileSidebarOpen = ref(false);
- const userInfo = ref<curUserInfoPayload>();
- const pageTitle = ref('');
- const pageCrumb = ref('');
- const menuMetaByKey = ref<
- Record<string, { crumb: string; iframeSrc: string; title: string }>
- >({});
- const iframeTabs = ref<
- Array<{ crumb: string; iframeSrc: string; key: string; title: string }>
- >([]);
- const activeTabKey = ref('');
- const homeRefreshStamp = ref(Date.now());
- const companyLogoSrc = ref('/Content/Images/company-logo.png');
- const avatarSrc = computed(() => {
- const avatar = userInfo.value?.avatarFileId;
- return avatar ? `/File/Download?fileId=${avatar}` : defaultAvatar;
- });
- const contentTabs = computed(() => {
- return iframeTabs.value.map((tab) => {
- return {
- fullPath: tab.key,
- meta: {
- icon: tab.key === HOME_TAB_KEY ? HomeTabIcon : undefined,
- tabClosable: tab.key !== HOME_TAB_KEY,
- title: tab.title,
- },
- name: tab.title,
- path: tab.key,
- } as any;
- });
- });
- const homeTabMeta = computed(() => ({
- crumb: $t('home.dashboard.crumb'),
- iframeSrc: '',
- icon: HomeTabIcon,
- key: HOME_TAB_KEY,
- title: $t('home.dashboard.tab'),
- }));
- function handleCompanyLogoError() {
- companyLogoSrc.value = defaultCompanyLogo;
- }
- type RawMenuNode = {
- code?: string;
- deleted?: boolean;
- fullName?: string;
- icon?: string;
- iconClass?: string;
- iconColor?: string;
- id?: string;
- isDeleted?: boolean;
- link?: string;
- name?: string;
- subMenuList?: RawMenuNode[];
- };
- function buildMenuIframeSrc(link?: string, id?: string) {
- const linkPart = String(link ?? '').trim();
- if (!linkPart) {
- return '';
- }
- const idPart = String(id ?? '').trim();
- const addMenuIdQuery = (url: string) => {
- if (!idPart) {
- return url;
- }
- const hashIndex = url.indexOf('#');
- const pathAndQuery = hashIndex === -1 ? url : url.slice(0, hashIndex);
- const hashPart = hashIndex === -1 ? '' : url.slice(hashIndex);
- const joiner = pathAndQuery.includes('?') ? '&' : '?';
- return `${pathAndQuery}${joiner}menuId=${idPart}${hashPart}`;
- };
- if (/^https?:\/\//i.test(linkPart)) {
- return addMenuIdQuery(linkPart);
- }
- const normalizedPath = linkPart.startsWith('/') ? linkPart : `/${linkPart}`;
- return addMenuIdQuery(normalizedPath);
- }
- function normalizeMenuIconClass(iconClass?: string) {
- if (!iconClass) {
- return [];
- }
- return iconClass
- .replaceAll(',', ' ')
- .split(' ')
- .map((token) => token.trim())
- .filter(Boolean);
- }
- function renderMenuLabel(node: RawMenuNode, fallbackText: string) {
- const iconClass = node.iconClass || node.icon;
- const text = node.name || node.fullName || fallbackText;
- const normalizedIconClass = normalizeMenuIconClass(iconClass);
- if (normalizedIconClass.length === 0) {
- return text;
- }
- return h('span', { class: 'menu-node-label' }, [
- h('i', {
- class: ['menu-node-icon', ...normalizedIconClass],
- }),
- h('span', { class: 'menu-node-text' }, text),
- ]);
- }
- function mapMenuItems(
- nodes: RawMenuNode[],
- firstLeafPathRef: string[],
- parentPath: string[] = [],
- parentLabelPath: string[] = [],
- metaMap: Record<
- string,
- { crumb: string; iframeSrc: string; title: string }
- > = {},
- ): NonNullable<MenuProps['items']> {
- return nodes
- .filter((node) => !(node.deleted || node.isDeleted))
- .map((node, index) => {
- const key = node.id || node.code || `${node.name ?? 'menu'}-${index}`;
- const labelText = node.name || node.fullName || key;
- const currentPath = [...parentPath, key];
- const currentLabelPath = [...parentLabelPath, labelText];
- metaMap[key] = {
- crumb: currentLabelPath.join(' / '),
- iframeSrc: buildMenuIframeSrc(node.link, node.id),
- title: labelText,
- };
- const children = mapMenuItems(
- node.subMenuList ?? [],
- firstLeafPathRef,
- currentPath,
- currentLabelPath,
- metaMap,
- );
- if (children.length === 0 && firstLeafPathRef.length === 0) {
- firstLeafPathRef.push(...currentPath);
- }
- return {
- key,
- label: renderMenuLabel(node, key),
- children: children.length > 0 ? children : undefined,
- };
- });
- }
- function applyMenuMeta(key: string) {
- const meta = menuMetaByKey.value[key];
- if (!meta) {
- pageTitle.value = '';
- pageCrumb.value = '';
- return;
- }
- pageTitle.value = meta.title;
- pageCrumb.value = meta.crumb;
- }
- function clearActiveTab() {
- activeTabKey.value = '';
- selectedKeys.value = [];
- pageTitle.value = '';
- pageCrumb.value = '';
- }
- function ensureHomeTab() {
- const nextHomeTab = homeTabMeta.value;
- const index = iframeTabs.value.findIndex((tab) => tab.key === HOME_TAB_KEY);
- if (index === -1) {
- iframeTabs.value.unshift(nextHomeTab);
- return;
- }
- iframeTabs.value.splice(index, 1, nextHomeTab);
- }
- function setActiveTab(key: string) {
- activeTabKey.value = key;
- if (key === HOME_TAB_KEY) {
- selectedKeys.value = [];
- pageTitle.value = homeTabMeta.value.title;
- pageCrumb.value = homeTabMeta.value.crumb;
- return;
- }
- selectedKeys.value = [key];
- applyMenuMeta(key);
- }
- function openIframeTab(key: string) {
- const meta = menuMetaByKey.value[key];
- if (!meta?.iframeSrc) {
- return;
- }
- const nextTab = {
- crumb: meta.crumb,
- iframeSrc: meta.iframeSrc,
- key,
- title: meta.title,
- };
- const existingIndex = iframeTabs.value.findIndex((tab) => tab.key === key);
- if (existingIndex === -1) {
- iframeTabs.value.push(nextTab);
- } else {
- iframeTabs.value.splice(existingIndex, 1, nextTab);
- }
- setActiveTab(key);
- }
- async function loadLeftMenuFromB() {
- const { result } = await leftMenuListFromB();
- const firstLeafPath: string[] = [];
- const nextMetaMap: Record<
- string,
- { crumb: string; iframeSrc: string; title: string }
- > = {};
- const items = mapMenuItems(
- result?.subMenuList ?? [],
- firstLeafPath,
- [],
- [],
- nextMetaMap,
- );
- menuMetaByKey.value = nextMetaMap;
- iframeTabs.value = iframeTabs.value.flatMap((tab) => {
- if (tab.key === HOME_TAB_KEY) {
- return [homeTabMeta.value];
- }
- const meta = nextMetaMap[tab.key];
- if (!meta?.iframeSrc) {
- return [];
- }
- return [
- {
- crumb: meta.crumb,
- iframeSrc: meta.iframeSrc,
- key: tab.key,
- title: meta.title,
- },
- ];
- });
- ensureHomeTab();
- leftMenuItems.value = items;
- if (firstLeafPath.length > 0) {
- openMenuKeys.value = items
- .filter(
- (item) =>
- item &&
- (item as any).children &&
- Array.isArray((item as any).children) &&
- (item as any).children.length > 0,
- )
- .map((item) => String(item!.key));
- if (activeTabKey.value && nextMetaMap[activeTabKey.value]?.iframeSrc) {
- setActiveTab(activeTabKey.value);
- } else if (iframeTabs.value.length > 0) {
- setActiveTab(iframeTabs.value[0]!.key);
- }
- } else {
- openMenuKeys.value = [];
- ensureHomeTab();
- setActiveTab(HOME_TAB_KEY);
- }
- }
- async function getCurUserInfo() {
- const { result } = await curUserInfo();
- userInfo.value = result;
- }
- let isRefreshingData = false;
- let refreshQueued = false;
- let isApplyingLocaleFromUserInfo = false;
- let hasInitializedLanguageFromUserInfo = false;
- function resolveLocaleByUserLanguage(
- language?: string,
- ): null | SupportedLanguagesType {
- const value = String(language ?? '').trim();
- if (value === 'en' || value === 'en-US') {
- return 'en-US';
- }
- if (value === 'zh-CN' || value === 'zh') {
- return 'zh-CN';
- }
- return null;
- }
- async function refreshHomeData() {
- if (isRefreshingData) {
- refreshQueued = true;
- return;
- }
- isRefreshingData = true;
- do {
- refreshQueued = false;
- await getCurUserInfo();
- if (!hasInitializedLanguageFromUserInfo) {
- const localeFromUserInfo = resolveLocaleByUserLanguage(
- userInfo.value?.language,
- );
- if (localeFromUserInfo && localeFromUserInfo !== preferences.app.locale) {
- isApplyingLocaleFromUserInfo = true;
- try {
- updatePreferences({
- app: {
- locale: localeFromUserInfo,
- },
- });
- await loadLocaleMessages(localeFromUserInfo);
- } finally {
- isApplyingLocaleFromUserInfo = false;
- }
- }
- hasInitializedLanguageFromUserInfo = true;
- }
- await loadLeftMenuFromB();
- } while (refreshQueued);
- isRefreshingData = false;
- }
- function handleLocaleStorageChange(event: StorageEvent) {
- if (!event.key?.endsWith('-preferences-locale')) {
- return;
- }
- if (event.newValue === event.oldValue) {
- return;
- }
- void refreshHomeData();
- }
- onMounted(async () => {
- await refreshHomeData();
- window.addEventListener('storage', handleLocaleStorageChange);
- });
- onBeforeUnmount(() => {
- window.removeEventListener('storage', handleLocaleStorageChange);
- });
- watch(
- () => preferences.app.locale,
- (locale, previousLocale) => {
- if (locale === previousLocale || isApplyingLocaleFromUserInfo) {
- return;
- }
- void refreshHomeData();
- },
- );
- function handleMenuOpenChange(keys: string[]) {
- openMenuKeys.value = keys;
- }
- function handleLeftMenuClick({ key }: { key: string }) {
- const normalizedKey = String(key);
- openIframeTab(normalizedKey);
- closeMobileSidebar();
- }
- function findMenuPathByKey(
- items: MenuProps['items'],
- targetKey: string,
- parentPath: string[] = [],
- ): null | string[] {
- for (const item of items ?? []) {
- if (!item) {
- continue;
- }
- const currentKey = String((item as any).key ?? '');
- if (!currentKey) {
- continue;
- }
- const currentPath = [...parentPath, currentKey];
- if (currentKey === targetKey) {
- return currentPath;
- }
- const childItems = (item as any).children as MenuProps['items'];
- if (!childItems || childItems.length === 0) {
- continue;
- }
- const matchedPath = findMenuPathByKey(childItems, targetKey, currentPath);
- if (matchedPath) {
- return matchedPath;
- }
- }
- return null;
- }
- function handleDashboardMenuOpen(key: string) {
- const normalizedKey = String(key ?? '');
- if (!normalizedKey) {
- return;
- }
- if (!menuMetaByKey.value[normalizedKey]?.iframeSrc) {
- return;
- }
- const menuPath = findMenuPathByKey(leftMenuItems.value, normalizedKey);
- if (menuPath && menuPath.length > 1) {
- const parentKeys = menuPath.slice(0, -1);
- openMenuKeys.value = [...new Set([...openMenuKeys.value, ...parentKeys])];
- }
- openIframeTab(normalizedKey);
- closeMobileSidebar();
- }
- function handleTabChange(key: string) {
- const normalizedKey = String(key);
- if (normalizedKey !== HOME_TAB_KEY && !menuMetaByKey.value[normalizedKey]) {
- return;
- }
- setActiveTab(normalizedKey);
- }
- function handleTabClose(key: string) {
- const normalizedKey = String(key);
- if (normalizedKey === HOME_TAB_KEY) {
- return;
- }
- const currentIndex = iframeTabs.value.findIndex(
- (tab) => tab.key === normalizedKey,
- );
- if (currentIndex === -1) {
- return;
- }
- iframeTabs.value.splice(currentIndex, 1);
- if (activeTabKey.value !== normalizedKey) {
- return;
- }
- const nextActiveTab =
- iframeTabs.value[currentIndex] ??
- iframeTabs.value[currentIndex - 1] ??
- null;
- if (nextActiveTab) {
- setActiveTab(nextActiveTab.key);
- return;
- }
- clearActiveTab();
- }
- function handleSortTabs(oldIndex: number, newIndex: number) {
- if (oldIndex === newIndex) {
- return;
- }
- const nextTabs = [...iframeTabs.value];
- const movedTabs = nextTabs.splice(oldIndex, 1);
- const movedTab = movedTabs[0];
- if (!movedTab) {
- return;
- }
- nextTabs.splice(newIndex, 0, movedTab);
- iframeTabs.value = nextTabs;
- }
- function addTabRefreshStamp(url: string) {
- const rawUrl = String(url ?? '');
- if (!rawUrl) {
- return rawUrl;
- }
- const hashIndex = rawUrl.indexOf('#');
- const pathAndQuery = hashIndex === -1 ? rawUrl : rawUrl.slice(0, hashIndex);
- const hashPart = hashIndex === -1 ? '' : rawUrl.slice(hashIndex);
- const [pathPart, queryPart = ''] = pathAndQuery.split('?');
- const params = new URLSearchParams(queryPart);
- params.set('__tab_refresh', String(Date.now()));
- const nextQuery = params.toString();
- return `${pathPart}${nextQuery ? `?${nextQuery}` : ''}${hashPart}`;
- }
- function refreshTabByKey(key: string) {
- const normalizedKey = String(key);
- if (normalizedKey === HOME_TAB_KEY) {
- homeRefreshStamp.value = Date.now();
- setActiveTab(HOME_TAB_KEY);
- return;
- }
- const index = iframeTabs.value.findIndex((tab) => tab.key === normalizedKey);
- if (index === -1) {
- return;
- }
- const targetTab = iframeTabs.value[index];
- if (!targetTab) {
- return;
- }
- iframeTabs.value.splice(index, 1, {
- ...targetTab,
- iframeSrc: addTabRefreshStamp(targetTab.iframeSrc),
- });
- setActiveTab(normalizedKey);
- }
- function closeOtherTabsByKey(key: string) {
- const normalizedKey = String(key);
- const targetTab = iframeTabs.value.find((tab) => tab.key === normalizedKey);
- if (!targetTab) {
- return;
- }
- iframeTabs.value =
- normalizedKey === HOME_TAB_KEY
- ? [homeTabMeta.value]
- : [homeTabMeta.value, targetTab];
- setActiveTab(normalizedKey);
- }
- function closeAllTabs() {
- iframeTabs.value = [homeTabMeta.value];
- setActiveTab(HOME_TAB_KEY);
- }
- function createTabContextMenus(tab: { key?: string }) {
- const tabKey = String(tab?.key ?? '');
- const hasTabs = iframeTabs.value.length > 0;
- const isCurrentTabRefreshable =
- hasTabs && tabKey && iframeTabs.value.some((item) => item.key === tabKey);
- const isHomeTab = tabKey === HOME_TAB_KEY;
- const isCurrentTabClosable =
- hasTabs &&
- !isHomeTab &&
- tabKey &&
- iframeTabs.value.some((item) => item.key === tabKey);
- const menus: IContextMenuItem[] = [
- {
- disabled: !isCurrentTabRefreshable,
- handler: () => {
- refreshTabByKey(tabKey);
- },
- key: 'refresh-current',
- text: '刷新当前',
- },
- {
- disabled: !isCurrentTabClosable,
- handler: () => {
- handleTabClose(tabKey);
- },
- key: 'close-current',
- text: '关闭当前',
- },
- {
- disabled: !tabKey || iframeTabs.value.length <= 1,
- handler: () => {
- closeOtherTabsByKey(tabKey);
- },
- key: 'close-other',
- text: '关闭其他',
- },
- {
- disabled: !hasTabs,
- handler: closeAllTabs,
- key: 'close-all',
- text: '关闭全部',
- },
- ];
- return menus;
- }
- function openMobileSidebar() {
- isMobileSidebarOpen.value = true;
- }
- function closeMobileSidebar() {
- isMobileSidebarOpen.value = false;
- }
- function handleUserMenuClick({ key }: { key: string }) {
- if (key === 'Logout') {
- const enterpriseCode = resolveEnterpriseCodeFromLocation();
- const logoutUrl = `/Account/Logout?enterpriseCode=${enterpriseCode}`;
- try {
- if (window.top && window.top !== window) {
- window.top.location.href = logoutUrl;
- return;
- }
- } catch {}
- window.location.href = logoutUrl;
- }
- }
- function handleIframeLoad(event: Event) {
- const iframe = event.target as HTMLIFrameElement | null;
- if (!iframe) {
- return;
- }
- try {
- const doc = iframe.contentDocument;
- if (!doc) {
- return;
- }
- doc.documentElement.style.background = 'transparent';
- if (doc.body) {
- doc.body.style.background = 'transparent';
- }
- } catch {}
- }
- </script>
- <template>
- <div class="enterprise-page">
- <div
- :class="[{ show: isMobileSidebarOpen }]"
- class="mobile-mask"
- @click="closeMobileSidebar"
- ></div>
- <div class="enterprise-shell">
- <aside
- :class="[{ 'mobile-open': isMobileSidebarOpen }]"
- class="left-panel"
- >
- <div class="brand-card">
- <div class="logo-dot">
- <img :src="companyLogoSrc" @error="handleCompanyLogoError" />
- </div>
- <div class="brand-text">{{ userInfo?.enterpriseName }}</div>
- <button
- class="mobile-close-btn"
- type="button"
- @click="closeMobileSidebar"
- >
- x
- </button>
- </div>
- <!-- <div class="menu-scroll-wrap"> -->
- <VbenScrollbar
- :shadow-bottom="false"
- :shadow-top="false"
- class="h-full"
- horizontal
- scroll-bar-class="z-10 hidden "
- shadow
- shadow-left
- shadow-right
- >
- <Menu
- :items="leftMenuItems"
- :open-keys="openMenuKeys"
- :selected-keys="selectedKeys"
- class="enterprise-menu"
- mode="inline"
- @click="(info: any) => handleLeftMenuClick(info)"
- @open-change="handleMenuOpenChange"
- />
- <!-- </div> -->
- </VbenScrollbar>
- </aside>
- <section class="right-panel">
- <header class="top-bar">
- <div class="title-wrap">
- <div class="crumb">{{ pageCrumb }}</div>
- </div>
- <div class="top-actions">
- <button
- class="mobile-toggle-btn"
- type="button"
- @click="openMobileSidebar"
- >
- Menu
- </button>
- <SelectLang />
- <Dropdown
- :menu="{
- items: userMenuItems,
- }"
- placement="bottom"
- @menu-click="(info: any) => handleUserMenuClick(info)"
- >
- <div class="user-avatar cursor-pointer">
- <img :src="avatarSrc" alt="avatar" />
- </div>
- </Dropdown>
- </div>
- </header>
- <div
- :class="{ 'home-active': activeTabKey === HOME_TAB_KEY }"
- class="content-placeholder"
- >
- <template v-if="iframeTabs.length > 0">
- <div class="content-tabs-wrap">
- <TabsView
- :active="activeTabKey"
- :context-menus="createTabContextMenus"
- :draggable="preferences.tabbar.draggable"
- :show-icon="true"
- :style-type="preferences.tabbar.styleType"
- :tabs="contentTabs"
- :wheelable="preferences.tabbar.wheelable"
- @close="handleTabClose"
- @sort-tabs="handleSortTabs"
- @update:active="handleTabChange"
- />
- </div>
- <div
- :class="{ 'home-active': activeTabKey === HOME_TAB_KEY }"
- class="content-iframe-stack"
- >
- <HomeDashboardTab
- v-show="activeTabKey === HOME_TAB_KEY"
- :key="homeRefreshStamp"
- :is-super-admin="userInfo?.isSuperAdmin"
- class="h-full"
- @open-menu="handleDashboardMenuOpen"
- />
- <iframe
- v-for="tab in iframeTabs"
- v-show="tab.key === activeTabKey && tab.key !== HOME_TAB_KEY"
- :key="tab.key"
- :src="tab.iframeSrc"
- border="0"
- class="content-iframe"
- @load="handleIframeLoad"
- ></iframe>
- </div>
- </template>
- <div v-else class="content-empty">暂无可显示内容</div>
- </div>
- </section>
- </div>
- </div>
- </template>
- <style scoped lang="scss">
- @media (width <= 960px) {
- .enterprise-page {
- padding: 10px;
- }
- .enterprise-shell {
- flex-direction: column;
- min-height: calc(100vh - 20px);
- }
- .left-panel {
- position: fixed;
- inset: 0 auto 0 0;
- width: 280px;
- border-right: 1px solid #efebf1;
- transition: transform 0.24s ease;
- transform: translateX(-100%);
- }
- .left-panel.mobile-open {
- transform: translateX(0);
- }
- .mobile-close-btn {
- display: block;
- }
- .right-panel {
- padding: 20px 16px;
- margin: 0;
- border-radius: 0;
- box-shadow: none;
- }
- .right-panel::before {
- border-radius: 0;
- }
- .top-bar {
- flex-direction: column;
- gap: 12px;
- align-items: flex-start;
- }
- .mobile-toggle-btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- }
- }
- .enterprise-page {
- height: 100vh;
- min-height: 100vh;
- overflow: hidden;
- // background: radial-gradient(
- // circle at 78% 88%,
- // rgb(193 233 255 / 35%),
- // transparent 36%
- // ),
- // radial-gradient(circle at 70% 92%, rgb(255 216 199 / 30%), transparent 30%),
- // #f2f0f3;
- background: url('@/assets/image/background.png') no-repeat;
- background-size: cover;
- }
- .enterprise-shell {
- display: flex;
- height: 100vh;
- min-height: 100vh;
- overflow: hidden;
- // background: linear-gradient(180deg, #f8f6f8 0%, #fdf2f7 100%);
- box-shadow: 0 10px 30px rgb(60 34 51 / 8%);
- }
- .mobile-mask {
- position: fixed;
- inset: 0;
- z-index: 20;
- pointer-events: none;
- background: rgb(26 15 23 / 42%);
- opacity: 0;
- transition: opacity 0.2s ease;
- }
- .mobile-mask.show {
- pointer-events: auto;
- opacity: 1;
- }
- .left-panel {
- position: relative;
- z-index: 30;
- display: flex;
- flex-direction: column;
- width: 256px;
- height: 100%;
- min-height: 0;
- padding: 24px 16px;
- overflow: hidden;
- // background: linear-gradient(180deg, #f8f6f8 0%, #fdf2f7 100%);
- border-right: none;
- }
- .menu-scroll-wrap {
- flex: 1;
- min-height: 0;
- padding-right: 4px;
- overflow-y: auto;
- scrollbar-color: #cbb8c3 transparent;
- scrollbar-width: thin;
- }
- .menu-scroll-wrap::-webkit-scrollbar {
- width: 3px;
- }
- .menu-scroll-wrap::-webkit-scrollbar-thumb {
- background-color: #cbb8c3;
- border-radius: 999px;
- }
- .menu-scroll-wrap::-webkit-scrollbar-track {
- background: transparent;
- }
- .brand-card {
- display: flex;
- flex-direction: column;
- gap: 12px;
- align-items: center;
- padding-bottom: 20px;
- margin-bottom: 18px;
- border-bottom: 1px solid #ece6ed;
- }
- .mobile-close-btn {
- display: none;
- width: 28px;
- height: 28px;
- margin-left: auto;
- font-size: 12px;
- line-height: 1;
- color: #7a4860;
- background: #fff;
- border: 1px solid #d8cbd5;
- border-radius: 6px;
- }
- .logo-dot {
- display: grid;
- place-items: center;
- width: 30px;
- height: 30px;
- border-radius: 50%;
- img {
- width: 100%;
- height: 100%;
- object-fit: contain;
- }
- }
- .brand-text {
- font-size: 14px;
- font-weight: 600;
- color: #4f4250;
- }
- .user-avatar {
- display: grid;
- place-items: center;
- width: 32px;
- height: 32px;
- border-radius: 50%;
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- }
- :deep(.enterprise-menu) {
- background: transparent;
- border-inline-end: none;
- }
- :deep(.enterprise-menu .ant-menu-item) {
- height: auto;
- padding: 9px 10px;
- margin: 0 0 6px;
- font-size: 13px;
- line-height: 1.2;
- color: #6f6771;
- border-radius: 8px;
- }
- :deep(.menu-node-label) {
- display: inline-flex;
- gap: 8px;
- align-items: center;
- font-size: 16px;
- }
- :deep(.menu-node-icon) {
- font-size: 14px;
- line-height: 1;
- color: currentcolor;
- }
- :deep(.enterprise-menu .ant-menu-item-selected) {
- font-weight: 600;
- color: #fff;
- background: #8b1648;
- }
- :deep(.enterprise-menu .ant-menu-item-selected .menu-node-icon),
- :deep(.enterprise-menu .ant-menu-item-selected .menu-node-text) {
- color: #fff !important;
- }
- :deep(.enterprise-menu .ant-menu-submenu-selected > .ant-menu-submenu-title) {
- color: #5a4f5a;
- background: transparent;
- }
- :deep(.enterprise-menu .ant-menu-item-selected::after) {
- display: none;
- }
- :deep(.enterprise-menu .ant-menu-sub .ant-menu-item) {
- font-size: 12px;
- }
- :deep(.ant-menu-light.ant-menu-root.ant-menu-inline) {
- border-inline-end: none;
- }
- :deep(.ant-menu-light.ant-menu-inline .ant-menu-sub.ant-menu-inline) {
- background: none;
- }
- :deep(.enterprise-menu .ant-menu-submenu-title) {
- height: auto;
- padding: 8px 10px;
- margin: 0 0 6px;
- font-size: 13px;
- font-weight: 600;
- color: #5a4f5a;
- border-radius: 8px;
- }
- :deep(.enterprise-menu .ant-menu-submenu-title:hover) {
- color: #5a4f5a;
- }
- :deep(.content-tabs-wrap .vben-tabs-content) {
- height: 100%;
- }
- :deep(.content-tabs-wrap .tabs-chrome) {
- height: 100%;
- padding-right: 4px;
- }
- :deep(.content-tabs-wrap .tabs-chrome__item-main) {
- min-width: 72px;
- }
- :deep(.content-tabs-wrap [data-radix-scroll-area-viewport]) {
- overflow-y: hidden !important;
- scrollbar-width: none;
- }
- :deep(.content-tabs-wrap [data-radix-scroll-area-viewport]::-webkit-scrollbar) {
- height: 0;
- }
- :deep(.content-tabs-wrap [data-orientation='horizontal'].hidden) {
- display: flex !important;
- }
- :deep(.content-tabs-wrap [data-orientation='horizontal']) {
- height: 6px;
- padding: 0 2px;
- opacity: 0;
- transition: opacity 0.2s ease;
- }
- :deep(
- .content-tabs-wrap [data-orientation='horizontal'][data-state='visible']
- ) {
- opacity: 1;
- }
- // :deep(.content-tabs-wrap [data-orientation='horizontal'] .bg-border) {
- // background: rgb(139 22 72 / 50%) !important;
- // }
- :deep(.content-tabs-wrap .tabs-chrome__background-content) {
- border-radius: 20px !important;
- }
- :deep(
- .tabs-chrome__item:not(.dragging):hover:not(.is-active)
- .tabs-chrome__background-content
- ) {
- background-color: #c4a7b6 !important;
- }
- :deep(.content-tabs-wrap .is-active) {
- .tabs-chrome__background-content {
- background: #7e0040;
- }
- }
- :deep(.content-tabs-wrap .is-active .tabs-chrome__extra svg) {
- stroke: #fff;
- &:hover {
- background: transparent;
- }
- }
- :deep(.content-tabs-wrap .is-active .text-sm) {
- color: #fff !important;
- }
- :deep(.group.is-active .tabs-chrome__item-main) {
- color: #fff;
- }
- :deep(.content-tabs-wrap .is-active .tabs-chrome__background-before),
- :deep(.content-tabs-wrap .is-active .tabs-chrome__background-after) {
- display: none;
- }
- :deep(.tabs-chrome__item:last-child) {
- margin-right: 0;
- }
- :deep(.content-tabs-wrap .home-tab-icon) {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- line-height: 1;
- // color: #111;
- vertical-align: middle;
- }
- // :deep(.content-tabs-wrap .is-active .home-tab-icon) {
- // color: #111 !important;
- // }
- :deep(.overflow-y-hidden) {
- overflow: hidden;
- }
- .right-panel {
- position: relative;
- display: flex;
- flex: 1;
- flex-direction: column;
- padding: 12px 28px 32px;
- overflow: hidden;
- border-radius: 50px 0 0 50px;
- }
- .right-panel::before {
- position: absolute;
- inset: 0;
- z-index: 0;
- content: '';
- background-color: #fff;
- background-image: url('@/assets/image/bg.png');
- background-repeat: no-repeat;
- background-position: center;
- background-size: cover;
- }
- .right-panel > * {
- position: relative;
- z-index: 1;
- }
- .top-bar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- // padding-bottom: 16px;
- // border-bottom: 1px solid #f0ebf1;
- }
- .title-wrap {
- display: flex;
- flex-direction: row;
- align-items: baseline;
- }
- .crumb {
- margin-left: 8px;
- font-size: 14px;
- color: #462424;
- }
- .top-actions {
- display: flex;
- gap: 10px;
- align-items: center;
- }
- .mobile-toggle-btn {
- display: none;
- height: 32px;
- padding: 0 12px;
- font-size: 12px;
- color: #6f4e60;
- background: #fff;
- border: 1px solid #dcced7;
- border-radius: 16px;
- }
- .content-placeholder {
- display: flex;
- flex: 1;
- flex-direction: column;
- min-height: 0;
- margin-top: 12px;
- overflow: hidden;
- background: transparent;
- }
- .content-placeholder.home-active {
- overflow: visible;
- }
- .content-tabs-wrap {
- flex-shrink: 0;
- height: 42px;
- padding: 4px 0;
- overflow: hidden;
- background: linear-gradient(180deg, #fcfafc 0%, #f7f3f6 100%);
- border-bottom: 1px solid #f0ebf1;
- }
- .content-iframe-stack {
- position: relative;
- flex: 1;
- min-height: 0;
- iframe {
- border-radius: 0 0 0 50px;
- }
- }
- .content-iframe {
- display: block;
- width: 100%;
- height: 100%;
- background: transparent;
- border: 0;
- }
- .content-empty {
- display: grid;
- place-items: center;
- width: 100%;
- height: 100%;
- font-size: 14px;
- color: #8b808a;
- }
- </style>
|