vue ---- tagsView

作者: 牛会骑自行车 | 来源:发表于2023-01-12 11:14 被阅读0次
    效果图
    抱歉嘿嘿

    文里的tagList和activeTag全部存sessionStorage里了, 现在想来没那个必要😂存那里和放假仓库里是一回事, 不刷新都在, 刷新就没......已然记不起当时为什么要这么地了...........这里的代码不想改了, 真有用到的那么地倒是也无大碍哈哈哈哈哈哈哈

    功能:
    1. 点击跳转对应页面;
    2. 设置activeTag;
    3. 样式方面: 如果点击左数第二个时, 左数第一个没有完全露出, 让容器滚动使前一个tag露出; 右边同样(之前没有关注过, 写到这里才发现, 没有scrollRight和offsetRight啊😂);
    4. 右键小菜单: 位置跟着鼠标走(功能如图);
    5. 设置固定tag, 关闭全部功能为disabled状态;
    实现

    跳转以及其它本应该加在每个tag上的方法, 我写在了父容器上, 使用srcElement获取到当前元素. contextMenu也写在了父容器中, 定位使用了fixed, 根据鼠标位置来确定. .stop消除冒泡并且设置另外的方法进行占位.

    假仓库: 新建一个js文件, 里面是vue实例. 抛出嘿嘿
    一般用来保存用户信息及过滤完毕的路由和tags. 缺点和vuex一样, 刷新页面, 数据消失.

    上课的时候老师说如果不是超大型项目没有必要使用vuex. 原因我忘却了....不过很多时候不是所有东西都需要储存在cookie或者storage里, 我们委实需要一个类似vuex的东西.

    tag相关内容没有必要像token一样一直保存. 如果当前页面关闭, 它的信息就可以清除了. 但刷新页面时仓库数据消失但我们还是需要这些数据, 所以存在sessionStorage中是最为合适的😊

    假仓库代码 ⬇️ (我有一个问题....原本想把constantRoutes给它export, tag.vue中使用, 但是别的页面可以, tag.vue一用直接页面都出不来了😭)

    import Vue from 'vue';
    import Cookie from "js-cookie";
    import Layout from "@/components/layout";
    import Router, {
        resetRouter
    } from "@/router";
    
    const constantRoutes = [{
        path: '/',
        redirect: '/home',
        component: Layout,
        children: [{
            path: 'home',
            fullPath: '/home',
            name: "views-home",
            meta: {
                title: '牛进喜冲'
            },
            eternalTag: true,
            icon: "fa-bandcamp",
            component: () => import('@/views/home.vue')
        }]
    }, {
        path: '*',
        redirect: "/404"
    }];
    
    export default new Vue({
        data() {
            return {
                tagList: [],
                activeTag: sessionStorage.getItem('NJX_ACTIVE_TAG') || "views-home",
            }
        },
        methods: {
            // 初始化tagList
            initTagList() {
                // 利用递归取出constantRoutes中eternalTag值为true的数据
                let arr = [];
                let getEternal = (list) => {
                    list.map(item => {
                        if(item.eternalTag) {
                            arr.push(item)
                        }
                        if(item.children) {
                            getEternal(item.children);
                        }
                    })
                }
                this.tagList = sessionStorage.getItem('NJX_TAGS') ? JSON.parse(sessionStorage.getItem('NJX_TAGS')) : getEternal(constantRoutes);
            },
            // 保存tagList
            saveTags(list) {
                this.tagList = list;
                sessionStorage.setItem('NJX_TAGS', JSON.stringify(list));
            },
            // 移除tag ---- 右边的小❎
            removeTag(index) {
                // 是否是当前项
                let isCurrentActive = this.activeTag === this.tagList[index].name;
                // 删
                this.tagList.splice(index, 1);
                // 存
                this.saveTags(this.tagList);
                // 是否是数组最后一项
                let isLastOne = index === this.tagList.length;
    
                if(isCurrentActive)  {
                    // 如果是当前项, 又是最后一项, 那么当前元素为新数组的最后一项; 
                    // 如果不是最后一项, index不变
                    let newIndex = isLastOne ? this.tagList.length - 1 : index;
    
                    this.setActiveTag(this.tagList[newIndex].name);
                    // 页面跳转
                    Router.push('/' + this.tagList[newIndex].path);
                }
            },
            // 保存当前tag
            setActiveTag(name) {
                this.activeTag = name;
                sessionStorage.setItem("NJX_ACTIVE_TAG", name);
            },
            // 设置tagList 
            setTags(item) {
                // 列表里有无该TAG: 如果有, 直接设置activeTag即可, 如果没, 将新点击的tag送入tagList中
                let tagList = [...this.tagList];
                let tagIndex = tagList.findIndex((tag) => tag.name === item.name);
    
                if (tagIndex !== -1) {
                    this.activeTag = tagList[tagIndex].name;
                } else {
                    tagList.push(item);
                    this.saveTags(tagList);
                    this.activeTag = tagList[tagList.length - 1].name;
                }
                // 设置当前tag
                this.setActiveTag(this.activeTag);
            },
        },
    })
    

    tag页面 (自己乱玩的, 我把tag抽出来了, 所以在使用时需要先设置容器) (代码中的this.$Store是将刚刚的假仓库写在main.js中, 方便全局使用)
    代码 ⬇️

    <template>
        <div class="tags-wrap" ref="tagWrap" @scroll="contextMenuShow = false">
            <div ref="tagsContainer" class="tags" @click="handleAutoScroll">
                <div
                    :class="['tag-item', activeTag === tag.name ? 'active' : '']"
                    :style="{
                        marginLeft: tagSeam + 'px',
                        marginRight:
                            index === tagList.length - 1 ? tagSeam + 'px' : '0',
                    }"
                    v-for="(tag, index) in tagList"
                    :key="tag.name"
                    @contextmenu.prevent="(event) => showDrop(event, tag)"
                >
                    <span>{{ tag.meta.title }}</span>
                    <i
                        class="fa fa-times"
                        @click.stop="$Store.removeTag(index)"
                        v-if="!tag.eternalTag"
                    ></i>
                </div>
                <div
                    class="context-menu"
                    :style="{ left: menuLeft + 'px', top: menuTop + 'px' }"
                    v-show="contextMenuShow"
                    @click="&quot;&quot;;"
                >
                    <div
                        :class="[
                            'menu-item',
                            tagEternal && menu.eternalDisabled ? 'disabled' : '',
                        ]"
                        v-for="menu in menuList"
                        :key="menu.operative"
                        @click.stop="
                            tagEternal && menu.eternalDisabled
                                ? ''
                                : handleMenuItem(menu)
                        "
                    >
                        {{ menu.label }}
                    </div>
                </div>
            </div>
        </div>
    </template>
    
    <script>
    export default {
        name: "layout-tags",
        computed: {
            activeTag() {
                return this.$Store.activeTag;
            },
        },
        data() {
            return {
                tagSeam: 12,
                contextMenuShow: false,
                menuLeft: 0,
                menuTop: 0,
                menuList: [
                    {
                        label: "刷新",
                        operative: "Refresh",
                    },
                    {
                        label: "关闭",
                        operative: "CloseSelf",
                        // 针对类似“首页”这样, tags栏默认不可关闭的tag
                        eternalDisabled: true,
                    },
                    {
                        label: "关闭其它",
                        operative: "CloseOthers",
                    },
                    {
                        label: "关闭所有",
                        operative: "CloseAll",
                    },
                ],
    
                tagList: this.$Store.tagList,
                tagEternal: false,
                currentTag: null,
            };
        },
        methods: {
            handleAutoScroll(e) {
                const el =
                    e.srcElement.tagName === "SPAN"
                        ? e.srcElement.parentNode
                        : e.srcElement;
    
                const wrap = this.$refs.tagWrap; // 可视区宽度: wrap.clientWidth、左滑距离:  wrap.scrollLeft
                const offsetLeft = el.offsetLeft; // 元素距离容器
    
                // 如果本身是第一个元素, 那么上一个元素就是我自己
                const prevEl = el.previousElementSibling || el;
                const nextEl = el.nextElementSibling || el;
    
                let autoScrollRight =
                    offsetLeft - wrap.scrollLeft <
                    prevEl.offsetWidth + this.tagSeam;
                let autoScrollLeft =
                    wrap.offsetWidth +
                        wrap.scrollLeft -
                        el.offsetLeft -
                        el.offsetWidth <
                    nextEl.offsetWidth + this.tagSeam;
    
                if (autoScrollRight) {
                    wrap.scrollLeft = prevEl.offsetLeft - this.tagSeam;
                }
                if (autoScrollLeft) {
                    // wrap.scrollLeft = 'nextEl.offsetLeft' - 'X'
                    // X = (wrap.offsetWidth - nextEl.offsetWidth - this.tagSeam);
                    wrap.scrollLeft =
                        nextEl.offsetLeft -
                        (wrap.offsetWidth - nextEl.offsetWidth - this.tagSeam);
                }
    
                // 跳转、设置active
                // 1. 找到可以匹配的唯一值
                let title = el.getElementsByTagName("span")[0].innerHTML;
                // 2. 匹配title
                let activeIndex = this.tagList.findIndex(
                    (item) => item.meta.title === title
                );
                let { fullPath: path } = this.tagList[activeIndex];
    
                this.$router.push({
                    path
                });
            },
            showDrop(e, item) {
                // 储存当前tag信息, 一会儿要用
                this.currentTag = item;
                // 根据鼠标位置对contextMenu进行定位
                this.menuLeft = e.clientX + 10;
                this.menuTop = e.clientY + 10;
                // 该tag在tag栏是不是永久展示不可删除
                this.tagEternal = item.eternalTag;
                this.contextMenuShow = true;
                // 点击其它地方, menu消失
                document.addEventListener("click", this.hiddenDrop);
                // 在页面销毁前将事件监听移除
                this.$once("hook:beforeDestroy", () => {
                    document.removeEventListener("click", this.hiddenDrop);
                });
            },
            hiddenDrop() {
                this.contextMenuShow = false;
            },
            // 利用tagList中的operative参数, 将方法进行动态设置
            handleMenuItem(menu) {
                let index = this.$Store.sideBarRoutes.findIndex(
                    (item) => item.name === this.currentTag.name
                );
                let path = this.$Store.sideBarRoutes[index].fullPath;
    
                this["tagFor" + menu.operative]({ menu, index, path });
    
                this.contextMenuShow = false;
            },
            tagForRefresh({ path }) {
                this.$router.replace({
                    path: "/redirect",
                    query: {
                        path,
                    },
                });
            },
            tagForCloseSelf({ index }) {
                this.$Store.removeTag(index);
            },
            tagForCloseOthers({ path }) {
                this.$router.push({
                    path,
                });
    
                this.removeOtherTags(this.currentTag);
            },
            tagForCloseAll() {
                this.removeOtherTags();
            },
    
            removeOtherTags(item) {
                let constantTags = this.getEternal(this.$Store.constantRoutes);
    
                let noPush =
                    !item ||
                    constantTags.findIndex((tag) => tag.name === item.name) !== -1;
    
                this.tagList = noPush
                    ? [...constantTags]
                    : constantTags.concat(item);
                this.$Store.saveTags(this.tagList);
                this.$router.push(
                    !item ? constantTags[0].fullPath : this.currentTag.fullPath
                );
            },
    
            getEternal(list) {
                let constantArr = [];
                let getEternal = (arr) => {
                    arr.map((item) => {
                        if (item.eternalTag) {
                            constantArr.push(item);
                        }
                        if (item.children) {
                            getEternal(item.children);
                        }
                    });
                };
                getEternal(list);
                return constantArr;
            },
            matchTag(item) {
                let activeTag =
                    this.tagList[
                        this.tagList.findIndex((tag) => tag.name === item.name)
                    ].name;
                this.$Store.setActiveTag(activeTag);
            },
        },
        watch: {
            $route: {
                deep: true,
                immediate: true,
                handler(value) {
                    this.matchTag(value);
                },
            },
        },
    };
    </script>
    
    <style lang="scss" scoped>
    @import "@/assets/css/theme.scss";
    // 滚动条
    ::-webkit-scrollbar {
        height: 4px;
    }
    
    ::-webkit-scrollbar-thumb {
        border-radius: 4px;
        background: rgba(97, 101, 155, 0.4);
    }
    
    .tags-wrap {
        height: 36px;
    
        overflow-x: scroll;
        overflow-y: hidden;
    }
    .tags {
        display: inline-block;
        white-space: nowrap;
    
        height: 36px;
    }
    .tag-item {
        display: inline-block;
        height: 24px;
        line-height: 24px;
    
        color: #333;
        font-size: 12px;
        border-radius: 4px;
        background: #fff;
        border: 1px solid #e9eaec;
        padding: 0 10px 0 12px;
        cursor: pointer;
        margin: 6px 0 3px 0;
        transition: all ease-in-out 0.2s;
         // 小closeIcon
        .fa {
            display: inline-block;
            margin-left: 8px;
        }
    }
    
    .tag-item:hover {
        background: #f8f8f8;
    }
    .tag-item.active {
        background: rgba(97, 101, 155, 0.8);
        color: #fff;
        border-color: #8085ac !important;
    }
    .tag-item-active {
        background: #61649f !important;
        color: #fff !important;
    }
    
    .context-menu {
        position: fixed;
        z-index: 9;
        background: #fff;
    
        box-shadow: 2px 2px 3px 0 rgba(128, 118, 163, 0.2);
        border-radius: 4px;
        padding: 5px 0;
    
        font-size: 12px;
        user-select: none;
    }
    .menu-item {
        padding: 4px 16px;
        color: #333;
    }
    .menu-item:hover {
        background: rgba(128, 118, 163, 0.1);
    }
    .menu-item.disabled {
        color: #c0c4cc;
        cursor: not-allowed;
    }
    .menu-item.disabled:hover {
        background: transparent !important;
    }
    </style>
    

    实不相瞒, close的方法是加在每个元素身上的. 后面才写了每个元素的跳转, 正好那个时候刚刚学了通过父元素获取srcElement所以😂有点乱不过功能都完成啦很有趣嘿嘿

    tada~~~一个tag栏就完成啦

    相关文章

      网友评论

        本文标题:vue ---- tagsView

        本文链接:https://www.haomeiwen.com/subject/wzqjcdtx.html