0.0.10-beta03 • Published 12 months ago

vue3-vanilla-tab v0.0.10-beta03

Weekly downloads
-
License
-
Repository
-
Last release
12 months ago

安装

npm install vue3-vanilla-tab

使用

view.ts:

import { IViewList, ViewTypeEnum } from 'vue3-vanilla-tab';

const views: IViewList = [
    {
        id: '001',
        name: 'Home',
        title: '首页',
        type: ViewTypeEnum.MENU,
        path: '/src/views/page-home.vue',
    },
    {
        id: '002',
        name: 'Test',
        title: '测试',
        type: ViewTypeEnum.MENU,
        path: '/src/views/page-test.vue'
    },
    {
        id: '003',
        title: '测试',
        type: ViewTypeEnum.DIR,
        children: [
            {
                id: '004',
                name: 'Test2',
                title: '测试2',
                type: ViewTypeEnum.MENU,
                path: '/src/views/page-test2.vue'
            },
        ]
    },
    {
        id: '005',
        title: '内联框架测试',
        type: ViewTypeEnum.IFRAME,
        link: 'http://139.186.198.135/'
    }
];

export default views;

main.ts:

import { createApp } from 'vue';
import App from './App.vue';

import views from './views';
import 'vue3-vanilla-tab/style.css';
import { createDynamicTabs } from 'vue3-vanilla-tab';

const vanillaTab = createVanillaTab({
    modules: import.meta.glob('/src/views/page-*.vue'),
    cache: {
        encryption: {
            enable: false,
            key: '_11111020002111@',
            iv: '@11111000302111_'
        }
    },
    homeView: (viewTree) => viewTree[0]
});

createApp(App).use(vanillaTab).mount('#app');

// 登录成功时获取后端菜单或常量菜单调用此方法初始化
vanillaTab.initDynamicViews(views);

// 退出系统时记得释放缓存
// vanillaTab.destory();

dynamic-crumbs.vue面包屑组件(参考):

<template>
    <a-breadcrumb>
        <a-breadcrumb-item v-for="crumb in getCrumbs">
            <a>{{ crumb.title }}</a>
            <template #overlay v-if="crumb.children && crumb.children.length > 0">
                <a-menu @click="onContextMenuClick">
                    <template v-for="item in crumb?.children">
                        <a-menu-item v-if="item.type !== ViewTypeEnum.DIR" :key="item.id">
                            <a>{{ item.title }}</a>
                        </a-menu-item>
                    </template>
                </a-menu>
            </template>
        </a-breadcrumb-item>
    </a-breadcrumb>
</template>

<script setup lang="ts" name="DynamicCrumbs">
import { message } from 'ant-design-vue';
import { useAttribute, useTab, ViewTypeEnum } from 'vue3-vanilla-tab';

const {
    getCrumbs,
    getViewList,
} = useAttribute();
const { openTab } = useTab();

const onContextMenuClick = ({ key }) => {
    try {
        const findView = getViewList.value.find(item => item.id == key);
        if (findView) {
            const { id, props, title } = findView;
            openTab(id, props as any, { title });
        }
    } catch (error) {
        if (error) {
            message.error(error.message);
        }
    }
}

</script>

dynamic-tabs.vue动态标签页组件(参考):

<template>
    <a-tabs class="dynamic-tabs" type="editable-card" hide-add :activeKey="getActiveTab?.id" @tabClick="onTabSelect"
        @edit="onTabClose">
        <template #rightExtra>
            <div :style="{ height: '100%', display: 'flex', alignItems: 'center' }">
                <a-space :style="{ marginRight: '8px' }" :size="4">
                    <a-button shape="circle" @click="refreshTabAll()">
                        <template #icon>
                            <RetweetOutlined />
                        </template>
                    </a-button>
                </a-space>
            </div>
        </template>
        <a-tab-pane v-for="(tab, index) in getTabList" :key="tab.id" :closable="index > 0">
            <template #tab>
                <a-dropdown :trigger="['contextmenu']">
                    <div>{{ tab.title }}</div>
                    <template #overlay>
                        <a-menu @click="({ key: menuKey }) => onContextMenuClick(tab.id, menuKey)">
                            <a-menu-item key="refresh">
                                <ReloadOutlined :style="{ marginRight: '8px' }" />
                                <span>刷新</span>
                            </a-menu-item>
                            <a-menu-item key="close-left">
                                <ArrowLeftOutlined :style="{ marginRight: '8px' }" />
                                <span>关闭左侧</span>
                            </a-menu-item>
                            <a-menu-item key="close-right">
                                <ArrowRightOutlined :style="{ marginRight: '8px' }" />
                                <span>关闭右侧</span>
                            </a-menu-item>
                            <a-menu-item key="close-other">
                                <CloseOutlined :style="{ marginRight: '8px' }" />
                                <span>关闭其它</span>
                            </a-menu-item>
                            <a-menu-divider />
                            <a-menu-item key="close-all">
                                <RollbackOutlined :style="{ marginRight: '8px' }" />
                                <span>全部关闭</span>
                            </a-menu-item>
                            <a-menu-item key="refresh-all">
                                <RetweetOutlined :style="{ marginRight: '8px' }" />
                                <span>全部刷新</span>
                            </a-menu-item>
                        </a-menu>
                    </template>
                </a-dropdown>
            </template>
        </a-tab-pane>
    </a-tabs>
</template>

<script setup lang="ts" name="DynamicTabs">
import { message } from 'ant-design-vue';
import { useAttribute, useTab } from 'vue3-vanilla-tab';

const {
    getTabList,
    getActiveTab,
} = useAttribute();
const {
    openTab,
    closeTab,
    refreshTab,
    refreshTabAll,
    closeTabsByLeft,
    closeTabsByRight,
    closeTabsByOther,
    closeTabByAll,
} = useTab();

/**
 * 事件 - tab页右键菜单使用
 * @param tabKey tab页id(uuid)
 * @param menuEvent 自定义菜单事件
 */
const onContextMenuClick = async (tabKey: string, menuEvent: string) => {
    try {
        switch (menuEvent) {
            case 'refresh':
                refreshTab(tabKey);
                break;
            case 'close-left':
                closeTabsByLeft(tabKey);
                break;
            case 'close-right':
                closeTabsByRight(tabKey);
                break;
            case 'close-other':
                closeTabsByOther(tabKey);
                break;
            case 'close-all':
                closeTabByAll();
                break;
            case 'refresh-all':
                refreshTabAll();
                break;
        }
    } catch (error) {
        if (error) {
            message.error(error.message);
        }
    }
}

/**
 * 事件 - tab页被点击调用
 * @param tabKey tab页id(uuid)
 */
const onTabSelect = (tabKey: string) => {
    try {
        const findTab = getTabList.value.find(tab => tab.id === tabKey);
        if (findTab) {
            const { viewId, props, title } = findTab;
            openTab(viewId, props, { title });
        }
    } catch (error) {
        if (error) {
            message.error(error.message);
        }
    }
}

/**
 * 事件 - tab页被关闭时调用
 * @param tabKey tab页id(uuid)
 */
const onTabClose = (tabKey: string) => {
    try {
        closeTab(tabKey);
    } catch (error) {
        if (error) {
            message.error(error.message);
        }
    }
}

</script>

dynamic-menu.vue动态菜单组件(参考):

<template>
    <a-menu mode="inline" :openKeys="getOpenMenuKeys" :selectedKeys="[getActiveTab?.viewId]" @select="onMenuItemSelect">
        <template v-for="view in getViewTree">
            <!--menu-->
            <template v-if="view.children.length <= 0">
                <a-menu-item :key="view?.id" v-if="!view.hidden">
                    <template #icon>
                        <component v-if="view.icon" :is="view.icon" />
                    </template>
                    <span>{{ view.title }}</span>
                </a-menu-item>
            </template>
            <!--subMenu-->
            <template v-else>
                <DynamicMenuItem :view="view" />
            </template>
        </template>
    </a-menu>
</template>

<script lang="ts" setup name="DynamicMenu">
import { message } from 'ant-design-vue';
import DynamicMenuItem from './item.vue';
import { useTab, useAttribute } from 'vue3-vanilla-tab';

const {
    getOpenMenuKeys,
    getActiveTab,
    getViewTree,
    getViewList,
} = useAttribute();
const { openTab } = useTab();

const onMenuItemSelect = ({ key }) => {
    try {
        const findView = getViewList.value.find(item => item.id == key);
        if (findView) {
            const { id, props, title } = findView;
            openTab(id, props as any, { title });
        }
    } catch (error) {
        message.error(error.message);
    }
}

</script>

<!-- DynamicMenuItem -->

<template>
    <a-sub-menu :key="view.id">
        <template #icon>
            <component v-if="view.icon" :is="view.icon" />
        </template>
        <template #title>{{ view.title }}</template>
        <template v-for="item in view.children">
            <template v-if="item.children.length <= 0">
                <a-menu-item :key="item.id" v-if="!item.hidden">
                    <template #icon>
                        <component v-if="item.icon" :is="item.icon" />
                    </template>
                    {{ item.title }}
                </a-menu-item>
            </template>
            <template v-else>
                <DynamicMenuItem :view="item" />
            </template>
        </template>
    </a-sub-menu>
</template>

<script lang="ts" setup>
import DynamicMenuItem from './item.vue';
import { toRefs } from 'vue';
import { View } from 'vue3-vanilla-tab';

const props = defineProps<{
    view: View
}>();

const { view } = toRefs(props);

</script>

container.vue容器组件(参考):

<template>
    <article>
        <header>
            <DynamicCrumbs />
        </header>
        <aside>
            <DynamicMenu />
        </aside>
        <nav>
            <DynamicTabs />
        </nav>
        <main>
            <DynamicContent />
        </main>
    </article>
</template>

<script setup lang="ts">
import DynamicCrumbs from './dynamic-crumbs.vue';
import DynamicTabs from './dynamic-tabs.vue';
import DynamicMenu from './dynamic-menu/index.vue';
import { DynamicContent } from 'vue3-vanilla-tab';
</script>

选项

export interface ITabConfig {
    /**
     * 缓存配置
     */
    cache?: ICacheConfig,
    /**
     * 最大缓存tab页数量
     */
    maxKeepTab?: number;
    /**
     * 组件模块
     */
    modules: Record<string, () => Promise<unknown>>;
    /**
     * 组件资源配置
     */
    source?: Partial<AsyncComponentOptions>,
    /**
     * 过渡动画配置
     */
    animate?: TransitionEnum | string;
    /**
     * 默认加载首页
     */
    homeView: (viewTree: ViewList) => View;
}

export interface ICacheConfig {
    /**
     * 缓存方式
     */
    storage?: Storage;
    /**
     * 缓存前缀
     */
    prefixKey?: string;
    /**
     * 缓存超时时间
     */
    timeout?: number;
    /**
     * 缓存加密配置
     */
    encryption?: {
        /**
         * 是否启用
         */
        enable: boolean;
        /**
         * 密钥(16位)
         */
        key: string;
        /**
         * 偏移量(16位)
         */
        iv: string;
    };
}

export enum TransitionEnum {
    /**
     * 渐变
     */
    ZOOM_FADE = 'zoom-fade',
    /**
     * 闪现
     */
    ZOOM_OUT = 'zoom-out',
    /**
     * 滑动
     */
    FADE_SLIDE = 'fade-slide',
    /**
     * 消退
     */
    FADE = 'fade',
    /**
     * 底部消退
     */
    FADE_BOTTOM = 'fade-bottom',
    /**
     * 缩放消退
     */
    FADE_SCALE = 'fade-scale'
}

菜单设计参考

export class View {
    /**
     * 主键
     */
    id?: string;
    /**
     * 菜单名称
     */
    title: string;
    /**
     * 菜单类型
     */
    type: ViewTypeEnum;
    /**
     * 组件名称
     */
    name?: string;
    /**
     * 组件路径
     */
    path?: string;
    /**
     * 是否显示在菜单栏
     */
    hidden?: boolean;
    /**
     * 是否进行缓存
     */
    cache?: boolean;
    /**
     * 是否打开多标签
     */
    single?: boolean;
    /**
     * 菜单图标
     */
    icon?: string;
    /**
     * 组件参数
     */
    props?: string;
    /**
     * 超链接
     */
    link?: string;
}

export enum ViewTypeEnum {
    /**
     * 目录
     */
    DIR,
    /**
     * 菜单
     */
    MENU,
    /**
     * 内链
     */
    IFRAME,
    /**
     * 外链
     */
    LINK
}

示例

AntDesignVue组件库Demo 点我访问

小技巧

  • 怎么在vue3中获取当前标签页的Id值? const tabId = getCurrentInstance().vnode.key as string;

更新日志

0.0.10-beta03

  • 修复Dropdown,Menu组件动画失效问题。

0.0.10-beta02

  • 修复打开内联或外联时报错组件未注册问题。
  • DynamicTabs实例中导出hasCacheViews, hasCacheOpenTabs属性分别用于判断是否已经缓存视图列表和打开的标签页。

0.0.10-beta01

  • 将antd组件库中以下组件集成,用法与组件库保持一致。
    • Tabs,
    • TabPane,
    • Dropdown,
    • Menu,
    • MenuItem,
    • SubMenu,
    • MenuDivider
    • Breadcrumb,
    • BreadcrumbItem,
    • BreadcrumbSeparator

0.0.10

  • useTab中新增setTabProps方法用于设置当前标签页的参数。

0.0.9

  • useTab中新增getTabById, getParentTabById, refreshParentTab方法分别用于通过tab页的Id查到当前Tab页,当前Tab页的父级Tab页和刷新指定标签页的父级标签页。
  • useTab中新增redirectTab方法用于关闭指定/当前标签页后重定向到指定标签页。
  • useTab中新增closeTabAndRefreshParentTab方法用于关闭指定/当前标签页后同时刷新父级标签页

0.0.8

  • closeTab不能关闭首页。
  • IMenuType类型中name改为非必填,未填入时自动将id作为name。

0.0.7

  • 优化DynamicTabs实例中initDynamicViews方法,自动清空之前打开的标签页和视图树后重新装载。
  • 移除utils中getComponentName方法。
  • 移除错误前缀vue3-vanilla-tab error, 待做成配置项
  • useAttribute中新增isCacheViews, isCacheOpenTabs, getShowViewList三个属性分别用于判断是否已经缓存视图列表,已经打开的标签页和hidden属性为false的视图列表。
  • DynamicTabs实例中新增findViewByViewId, openTab方法用于通过视图ID找到对应的视图和打开标签页(拦截openTab方法可以做打开之前的校验)。
  • 导出eachTree, findTreeNode方法分别用于遍历树和查找树节点。

0.0.6

  • DynamicTabs实例中新增getTabById, getComponentNameByViewId, getComponentByName, getComponentByViewId方法。
  • useAttribute中新增getVueApp, getVueComponents分别用于获取vue实例和全局组件。

0.0.5

  • 将openTab方法返回值改为tabId,用于closeTab方法关闭标签页。
  • 修复openTab方法传入props字符串不是标准的json格式报错问题。

0.0.4

  • 将getCacheItem, setCacheItem, removeCacheItem, clearCache从useTab中抽离组成新的useCache导出。
  • 导出decodeByBase64, encryptByBase64, encryptByMd5三个方法。
0.0.10-beta03

12 months ago

0.0.11

12 months ago

0.0.10-beta02

12 months ago

0.0.10-beta01

12 months ago

0.0.10

1 year ago

0.0.9

1 year ago

0.0.8

1 year ago

0.0.7

1 year ago

0.0.6

1 year ago

0.0.5

1 year ago

0.0.4

1 year ago

0.0.3

1 year ago

0.0.2

1 year ago

0.0.1

1 year ago