vue+elementui+vuex+sessionStorage实现历史标签菜单的示例代码

一般是有左侧菜单后,然后要在页面上部分添加历史标签菜单需求。
借鉴其他项目,以及网上功能加以组合调整实现
按照标签实现方式步骤来(大致思路):

1,写一个tagNav标签组件

2,在路由文件上每个路由组件都添加meta属性 meta:{title:'组件中文名'}

3,在store的mutation.js文件中写标签的添加/删除方法以及在方法中更新sessionStorage数据

4,在主页面上添加组件以及router-view外层添加keep-alive组件,我这边是main.vue为登录后主要的主页面,其他菜单页面都基于该页面的路由上

5,写一个mixins文件:beforeRouteLeave回调,因为貌似只在这回调中能找到子页面的缓存对象。在main.js中引入该文件并加入到vue.minxin()全局方法中,节省了在每个子页面都写改回调。

6,左侧菜单也要添加路由监听,用于点标签菜单时左侧菜单能定位选中到对应的菜单选项

7,如果点标签菜单是路由重定向菜单,则需要在触发重定向的路由页面添加路由监听获取meta.title的路由属性,然后在页面的created回调中循环存放标签的store数组,并把meta.title设置为当前重定向的标签名

开始代码说明

写一个tagNav组件

<style lang="less" scoped>
@import "./base.less";
.tags-nav {
  display: flex;
  align-items: stretch;
  height: 40px;
  padding: 2px 0;
  background-color: @background-color;
  a {
    margin-right: 1px;
    width: 24px;
  }
  a:hover {
    color: @light-theme-color;
  }
  a:first-of-type {
    margin-right: 4px;
  }
  a,
  .dropdown-btn {
    display: inline-block;
    width:30px;
    height: 36px;
    color: @title-color;
    background-color: @white;
    text-align: center;
    line-height: 36px;
    position: relative;
    z-index: 10;
  }
  .tags-wrapper {
    flex: 1 1 auto;
    position: relative;
    .tags-wrapper-scroll {
      position: absolute;
      top: 0px;
      left: 0;
      z-index: 5;
      height: 36px;
      overflow: visible;
      white-space: nowrap;
      transition: all .3s ease-in-out;
      .tag {
        flex-shrink: 0;
        cursor: pointer;
      }
    }
  }
}
</style>

<template>
  <div class="tags-nav">
    <a href="javascript:void(0)" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  @click="handleScroll('left')">
      <icon name="angle-left"></icon>
    </a>
    <div class="tags-wrapper" ref="tagsWrapper">
      <div class="tags-wrapper-scroll" ref="tagsWrapperScroll" :style="{ left: leftOffset + 'px' }">
        <transition-group name="slide-fade">
          <el-tag
            class="tag slide-fade-item"
            ref="tagsPageOpened"
            v-for="(tag, index) in pageOpenedList"
            :key="'tag_' + index"
            :type="tag.selected ? '': 'info'"
            :closable="tag.name!='basicDevice'"
            :id="tag.name"
            effect="dark"
            :text="tag.name"
            @close="closeTag(index, $event, tag.name)"
            @click="tagSelected(index)"
          >{{tag.title}}</el-tag>
        </transition-group>
      </div>
    </div>
    <a href="javascript:void(0)" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  @click="handleScroll('right')">
      <icon name="angle-right"></icon>
    </a>
    <!-- <el-dropdown class="dropdown-btn" @command="closeTags">
      <span class="el-dropdown-link">
        <icon name="angle-down"></icon>
      </span>
      <el-dropdown-menu slot="dropdown">
        <el-dropdown-item command="closeOthers">关闭其他</el-dropdown-item>
        <el-dropdown-item command="closeAll">关闭所有</el-dropdown-item>
      </el-dropdown-menu>
    </el-dropdown> -->
    <!-- <Dropdown  placement="bottom-end" @on-click="closeTags">
      <a href="javascript:void(0)" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  style="margin-right: 0;">
        <icon name="angle-down"></icon>
      </a>
      <DropdownMenu slot="list">
        <DropdownItem name="closeOthers">关闭其他</DropdownItem>
        <DropdownItem name="closeAll">关闭所有</DropdownItem>
      </DropdownMenu>
    </Dropdown> -->
  </div>
</template>

<script>
export default {
  data () {
    return {
      currentPageName: this.$route.name,
      leftOffset: 0
    }
  },
  props: {
    pageOpenedList: {
      type: Array
    }
  },
  methods: {
    closeTags (action) {
      this.$emit('closeTags', action)
      if (action === 'closeOthers') {
        this.leftOffset = 0
      }
    },
    closeTag (index, event, name) {
      // 移除单个tag,且首页的tag无法移除
      if (index !== 0) {
        this.$emit('closeTags', index,name)
      }
      if (this.currentPageName !== name) {
        this.leftOffset = Math.min(0, this.leftOffset + event.target.parentNode.offsetWidth)
      }
    },
    tagSelected (index) {
      this.$emit('tagSelected', index)
    },
    checkTagIsVisible (tag) {
      let visible = {
        isVisible: false,
        position: 'left'
      }
      const leftDiffValue = tag.offsetLeft + this.leftOffset
      if (leftDiffValue < 0) {
        return visible
      }
      const rightDiffValue = this.$refs.tagsWrapper.offsetWidth - this.leftOffset - tag.offsetWidth - tag.offsetLeft
      if (leftDiffValue >= 0 && rightDiffValue >= 0) {
        visible.isVisible = true
      } else {
        visible.position = 'right'
      }
      return visible
    },
    handleScroll (direaction) {
      // 获取在可视区域临界的tag
      let criticalTag = this.getCriticalTag(direaction)
      switch (direaction) {
        case 'left':
          this.leftOffset = Math.min(this.$refs.tagsWrapper.offsetWidth - criticalTag.$el.offsetLeft, 0)
          break
        case 'right':
          const diffValue1 = -(criticalTag.$el.offsetLeft + criticalTag.$el.clientWidth)
          const diffvalue2 = -(this.$refs.tagsWrapperScroll.offsetWidth - this.$refs.tagsWrapper.offsetWidth)
          this.leftOffset = Math.max(diffValue1, diffvalue2)
          break
        default:
          break
      }
    },
    getCriticalTag (direaction) {
      let criticalTag
      const refsTagList = this.$refs.tagsPageOpened
      for (let tag of refsTagList) {
        // 检查tag是否在可视区
        if (this.checkTagIsVisible(tag.$el).isVisible) {
          criticalTag = tag
          if (direaction === 'left') {
            break
          }
        }
      }
      return criticalTag
    },
    setTagsWrapperScrollPosition (tag) {
      const visible = this.checkTagIsVisible(tag)
      if (!visible.isVisible && visible.position === 'left') {
        // 标签位于可视区域的左侧
        this.leftOffset = -tag.offsetLeft
      } else {
        // 标签位于可视区域的右侧 or 可视区域
        this.leftOffset = Math.min(0, -(tag.offsetWidth + tag.offsetLeft - this.$refs.tagsWrapper.offsetWidth + 4))
      }
    }
  },
  mounted () {
    // 初始化当前打开页面的标签位置
    const refsTag = this.$refs.tagsPageOpened
    setTimeout(() => {
      for (const tag of refsTag) {
        if (tag.text === this.$route.name) {
          const tagNode = tag.$el
          this.setTagsWrapperScrollPosition(tagNode)
          break
        }
      }
    }, 1)
  },
  watch: {
    $route (to) {
      this.currentPageName = to.name
      this.$nextTick(() => {
        const refsTag = this.$refs.tagsPageOpened
        for (const tag of refsTag) {
          if (tag.text === this.$route.name) {
            const tagNode = tag.$el
            this.setTagsWrapperScrollPosition(tagNode)
            break
          }
        }
      })
    }
  }
}
</script>

以及在同层目录下的less文件

// color
@theme1-color: #515a6e;
@theme-color: #2d8cf0;
@light-theme-color: #5cadff;
@dark-theme-color: #2b85e4;
@info-color: #2db7f5;
@success-color: #19be6b;
@warning-color: #ff9900;
@error-color: #ed4014;
@title-color: #17233d;
@content-color: #515a6e;
@sub-color: #808695;
@disabled-color: #c5c8ce;
@border-color: #dcdee2;
@divider-color: #e8eaec;
@background-color: #f8f8f9;
@white: white;

// 间距
@padding: 16px;

// 默认样式
* {
  box-sizing: border-box;
}

a {
  color: @theme-color;
}

a:hover {
  color: @light-theme-color;
}

.dark-a {
  color: @title-color;
}

// 清除float
.clear-float::after {
  display: block;
  clear: both;
  content: "";
  visibility: hidden;
  height: 0;
}

// 动画
.slide-fade-item {
  transition: all 0.1s ease-in-out;
  display: inline-block;
}
.slide-fade-enter, .slide-fade-leave-to
/* .list-complete-leave-active for below version 2.1.8 */ {
  opacity: 0;
  transform: translateX(-10px);
}

// 滚动条样式
.menu-scrollbar::-webkit-scrollbar,
.common-scrollbar::-webkit-scrollbar {
  /*滚动条整体样式*/
  width: 11px;
  /*高宽分别对应横竖滚动条的尺寸*/
  height: 1px;
}

// 滚动条样式1
.menu-scrollbar::-webkit-scrollbar-thumb {
  /*滚动条里面小方块*/
  border-radius: 2px;
  box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
  background: @sub-color;
}

// 滚动条样式2
.common-scrollbar::-webkit-scrollbar-thumb {
  /*滚动条里面小方块*/
  border-radius: 2px;
  box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
  background: @border-color;
}

-----------说明:由于这关闭所有跟关闭其他的功能只是单纯清除tagNav的标签并没关系到路由,所以获取不到清除的页面路由离开事件,只能先关闭这两功能------------

在路由文件route.js中为每个路由添加meta属性

{
        path: 'auditManage',
        name: 'auditManage',
        meta:{title:'审计管理'},
        component: function (resolve) {
          require(['../page/sysConfig/audit/auditManageMain.vue'], resolve);
        },
        redirect: '/main/auditManage/trendStatistics',
        children: [{
          path: 'trendStatistics',
          name: 'trendStatistics',
          meta:{title:'趋势审计'},
          component: function (resolve) {
            require(['../page/sysConfig/audit/auditTrendStatisticsList.vue'], resolve);
          }
        }, {
          path: 'search',
          name: 'search',
          meta:{title:'审计查询'},
          component: function (resolve) {
            require(['../page/sysConfig/audit/auditSearchList.vue'], resolve);
          }
        },

------说明:这是路由片段包含设置的meta属性方式内容,以及路由重定向-------

在store的mutation.js中写标签的添加/删除以及更新sessionStorage方法

[setPageOpenedList](state,params = null){
        // 设置前先读取本地保存的打开列表数据
      state.pageOpenedList = sessionStorage.pageOpenedList
      ? JSON.parse(sessionStorage.pageOpenedList) : [{
        title: '基础设施',
        name: 'basicDevice',
        selected: true
      }]
    if (!params) {
      return
    }
    if (params.index === -1) {
      // 新打开一个页面
      state.pageOpenedList.push({
        title: params.route.meta.title,
        name: params.route.name,
        selected: false
      })
      params.index = state.pageOpenedList.length - 1
    }
    // 更新selected值
    for (let i = 0; i < state.pageOpenedList.length; i++) {
      if (params.index === i) {
        state.pageOpenedList[i].selected = true
      } else {
        state.pageOpenedList[i].selected = false
      }
    }
    // 更新下本地新的打开页面列表数据
    sessionStorage.pageOpenedList = JSON.stringify(state.pageOpenedList)
    },
    // 移除PageOpenedList
    [removePageOpenedList] (state, params = null) {
        if (!params) {
          return
        }
        if (typeof params.action === 'number') {
          state.pageOpenedList.splice(params.action, 1)
        } else {
        //进这里是已经选择删除所有tab,赋值一个初始选中的tab
          state.pageOpenedList = [{
            title: '基础设施',
            name: 'basicDevice',
            selected: true
          }]
          //如果是删除其他的则再加上当前路由下的tab
          if (params.action === 'closeOthers' && params.route.name !== 'basicDevice') {
            state.pageOpenedList[0].selected = false
            state.pageOpenedList.push({
              title: params.route.meta.title,
              name: params.route.name,
              selected: true
            })
          }
        }
        // 更新下本地新的打开页面列表数据
        sessionStorage.pageOpenedList = JSON.stringify(state.pageOpenedList)
      },

------说明:由于有的项目中store写法不太一样,这里的[setPageOpenedList]以及[removePageOpenedList]是mutation-type.js中定义的常量---------

在主页面main.vue中添加标签组件、keep-alive组件、组件的选中/删除方法,监听路由的变化,计算存放的标签list

<div class="a-tag">
            <tag-nav :pageOpenedList="pageOpenedList" ref="tagNavRef" @closeTags="closeTags"
                     @tagSelected="tagSelected"></tag-nav>
          </div>
          <div class="a-product">
            <div class="loading" style="height:100%">
              <keep-alive :max="5">
                <router-view></router-view>
              </keep-alive>
            </div>
          </div>
// 导航标签方法
      closeTags (action,elId) {
        let isRemoveSelected;

        if (typeof action === 'number') { //移除单个
          let elEvent = new Event('click');
          document.getElementById(elId).dispatchEvent(elEvent);
          //移除不管是不是当前的标签都默认设置为是当前的标签
          for (let i = 0; i < this.$store.state.pageOpenedList.length; i++) {
            if (action === i) {
              this.$store.state.pageOpenedList[i].selected = true
            } else {
              this.$store.state.pageOpenedList[i].selected = false
            }
          }

          //并且是当前的标签页
          isRemoveSelected = this.$store.state.pageOpenedList[action].selected

        }
        this.$store.commit('removePageOpenedList', { route: this.$route, action })
        if (isRemoveSelected) {
          // 移除单个tag,导航到最后一个tag的页面
          this.$router.push({
            name: this.$store.state.pageOpenedList[this.$store.state.pageOpenedList.length - 1].name
          })
        } else if (action === 'closeAll') {
          this.$router.push('/main/basicDevice');
        }
      },
      tagSelected (index) {
        if (this.$store.state.pageOpenedList[index].name !== this.$route.name) {
          this.$router.push({
            name: this.$store.state.pageOpenedList[index].name
          })
        }
      },
 computed: {
      pageOpenedList () {
        return this.$store.getters.getPageOpenedList
      },
    },
watch: {
      $route (to) {
        // 路由变化,更新PageOpenedList
        let index = this.$util.indexOfCurrentPageOpened(to.name, this.$store.state.pageOpenedList)
        this.$store.commit('setPageOpenedList', { route: to, index })
      },
   }
// 定位新打开的页面在pageOpenedList中位置
    indexOfCurrentPageOpened(name, pageOpenedList) {
    for (let index = 0; index < pageOpenedList.length; index++) {
      if (pageOpenedList[index].name === name) {
        return index
      }
    }
    return -1
  },

------说明:组件的导入就不贴了,缓存限制最多为5个标签,怕开太多导致浏览器内存爆炸卡死。-------

写一个minxins文件:路由离开回调,并在main.js中全局混入

/**
 * 用于历史标签菜单
 */
export default {

    beforeRouteLeave(to, from, next) {
        console.log("mixins-beforeRouteLeave:",from);
        let flag = true
        this.$store.state.pageOpenedList.forEach(e => {    // pageOpenedList存储打开的tabs的组件路由
          if(from.name == e.name) {
            flag = false
          }
        })
        if(flag && this.$vnode.parent && this.$vnode.parent.componentInstance.cache) {
        //   let key = this.$vnode.key   // 当前关闭的组件名
          var key = (this.$vnode.key == null || this.$vnode.key == undefined) ? this.$vnode.componentOptions.Ctor.cid + (this.$vnode.componentOptions.tag ? `::${this.$vnode.componentOptions.tag}` : '') : this.$vnode.key;
          let cache = this.$vnode.parent.componentInstance.cache  // 缓存的组件
          let keys = this.$vnode.parent.componentInstance.keys  // 缓存的组件名
          if(cache[key] != null) {
            delete cache[key]
            let index = keys.indexOf(key)
            if(index > -1) {
              keys.splice(index, 1)
            }
          }
        }
        next()
      }
  }
//引入混合方法
import beforeLeave from './mixins/beforeLeave'
Vue.mixin(beforeLeave)

------说明:这里主要就是为在每个离开的路由页面都回调这个离开方法,并且判断当前离开的路由是切换到其他标签菜单还是关闭当前菜单标签,如果是关闭则删除对应的缓存---------

左侧菜单也要添加路由监听,以便切换标签菜单时左侧菜单能正确的跳转并选中标签

 handleSelect(index,indexPath){
            this.active = index;
        },
watch:{
        $route (to) {
            console.log("accordion=============",to,to.path);
            //进if判断说明是被重定向的地址,则使用重定向父级菜单路径去跳转,让父级页面自己去重定向
            if(to.matched && to.matched[to.matched.length-1].parent.redirect != undefined){
                this.handleSelect(to.matched[to.matched.length-1].parent.path,null);
            }else {
                this.handleSelect(to.path,null);
            }
        }
    },

-----说明:第一个方法是el-menu选择菜单时触发的方法,watch路由时获取matched属性值去判断------

如果切换的标签是重定向的路由菜单则需要在重定向页面初始化时获取存放标签数组的title设置对应的radio标签(我这边项目的重定向页面基本都是用radio去切换),如果是再切换后的就是缓存的了,那就要从路由上获取title

<div class="tabsBox">
          <el-radio-group class="radioStyle" v-for="item in menus" :key="item.route" v-model="activeName" @change="checkItem(item.route)">
            <el-radio-button :label="item.text" @blur.native="goPath(item.route)"></el-radio-button>
          </el-radio-group>
        </div>
data(){
      return {
        activeName:'参与人',
        menus:[
          {route:"partyManageReport",text:"参与人"},
          {route:"staffManageReport",text:"员工"},
          {route:"partyAccountManageReport",text:"主账号"},
          {route:"partyAccountGroupManageReport",text:"主账号组"},
          {route:"partySubAccountManageReport",text:"从账号(web资产)"},
          {route:"partySubAccountManageD",text:"从账号(基础设施)"}
          ]
      }
    },
    watch:{
        $route (to){
            this.activeName = to.meta.title;
        }
    },
 created(){
      this.$store.state.pageOpenedList.forEach((item)=>{
          if(item.selected){
          this.activeName = item.title;
          }
      })
     }

总结:由于在关闭标签方法中有添加点击事件,把关闭是其他标签时设置为当前标签并关闭,这样才能获取到关闭标签对应的路由离开回调去清除缓存。但这样就会导致关闭其他标签后当前选中的标签页会跳到最后一个标签去。如果要求不是很高,这样也能勉强接受吧。不过感觉不是那么好。有没好的方式关闭其他标签时既能清除对应的缓存当前选中的标签tab又不会跳转到其他的标签选项上…求优化思路。

另关闭所有跟关闭其他选项,还不知道怎么去清除被关闭的标签缓存,如上面所说获取不到关闭时触发路由离开方法,只是单纯重新设置标签数组而已。

第一个是基础标签,所以把关闭按钮去了。

到此这篇关于vue+elementui+vuex+sessionStorage实现历史标签菜单的示例代码的文章就介绍到这了,更多相关vue 历史标签菜单内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue+element-ui表格封装tag标签使用插槽

    我们知道有很多系统都要求表格中添加各种各样的tag,来标记一些属性.在element-ui中添加tag很简单,最重要的就是用到了vue的插槽slot这个特性.首先了解什么是插槽. 插槽 省去官方的复杂讲解和代码,插槽的意思简单来说,就是在子组件的某个地方留一个占位符,当父组件使用这个子组件的时候,可以自定义这个占位符所占地方呈现的样子,可能是一个标题,一个按钮,甚至一个表格,一个表单. 为什么要插槽呢?我们抽离组件的原因就是因为可重复的代码太多了,当使用可复用的组件时,大大减少了复制粘贴.设想有

  • vue+elementui+vuex+sessionStorage实现历史标签菜单的示例代码

    一般是有左侧菜单后,然后要在页面上部分添加历史标签菜单需求. 借鉴其他项目,以及网上功能加以组合调整实现 按照标签实现方式步骤来(大致思路): 1,写一个tagNav标签组件 2,在路由文件上每个路由组件都添加meta属性 meta:{title:'组件中文名'} 3,在store的mutation.js文件中写标签的添加/删除方法以及在方法中更新sessionStorage数据 4,在主页面上添加组件以及router-view外层添加keep-alive组件,我这边是main.vue为登录后主

  • vue elementUI table 自定义表头和行合并的实例代码

    最近项目中做表格比较多,对element表格的使用,只需要传递进去数据,然后写死表头即可渲染. 但现实中应用中,如果写死表头,并且每个组件中写自己的表格,不仅浪费时间而且消耗性能.这个时候需要动态渲染表头. 而官方例子都是写死表头,那么为了满足项目需求,只能自己来研究一下. 1.自定义表头 代码如下,其实就是分了两部分,表格主数据是在TableData对象中,表头的数据保存在headerDatas,headerDatas.label其实就是表头的值,如果表头是"序号",那么header

  • vue + elementUI实现省市县三级联动的方法示例

    本文介绍了vue + elementUI实现省市县三级联动的方法示例,分享给大家,具体如下: 1.首先需要准备省市县json文件,网上有很多可以下载.项目中使用的city.json数据是这样的格式: [ { "value": "110000", "label": "北京市", "children": [ { "value": "110000", "label

  • vue+elementUI动态增加表单项并添加验证的代码详解

    参考elementUI官网以及网上的其他一些资料.话不多说,直接贴代码. htmt部分: <div id="app" style="width: 500px;">         <el-form :model="environmentForm" ref="environmentForm">             <el-row :gutter="24" v-for=&quo

  • Vue + Element UI 实现权限管理系统之菜单功能实现代码

    目录 菜单功能实现 菜单接口封装 菜单管理界面 测试效果 源码下载 菜单功能实现 菜单接口封装 菜单管理是一个对菜单树结构的增删改查操作. 提供一个菜单查询接口,查询整颗菜单树形结构. http/modules/menu.js 添加findMenuTree 接口. import axios from '../axios' /* * 菜单管理模块 */ // 保存 export const save = (data) => { return axios({ url: '/menu/save', m

  • SpringBoot mybatis 实现多级树形菜单的示例代码

    一.前言 iview-admin中提供了 v-org-tree这么一个vue组件可以实现树形菜单,下面小编来提供一下在element-ui中的使用教程(项目见:https://github.com/lison16/v-org-tree) 小编集成了el-dropdown下拉菜单(鼠标左击显示菜单),和右击自定义菜单,两种方式,效果图如下: 二.使用教程 (1)安装依赖 npm install clipboard npm install v-click-outside-x npm install

  • vue-router 基于后端permissions动态生成导航菜单的示例代码

    目录 Vue.js 1.注册全局守卫 2.Vuex状态管理 全局缓存routes 3.路由拦截 4.路由菜单 5.递归菜单vue组件 Vue.js vue-router vuex 1.注册全局守卫 核心逻辑 1.token身份验证(后端) => token失效返回登录页面 2.获取用户权限 3.校验permissions,动态添加路由菜单 router.beforeResolve 注册一个全局守卫.和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路

  • webpack+vuex+axios 跨域请求数据的示例代码

    本文介绍了webpack+vuex+axios 跨域请求数据的示例代码,分享给大家,具体如下: 使用vue-li 构建 webpack项目,修改bulid/config/index.js文件 dev: { env: require('./dev.env'), port: process.env.PORT || 8080, autoOpenBrowser: true, assetsSubDirectory: 'static', assetsPublicPath: '/', proxyTable:

  • vue不操作dom实现图片轮播的示例代码

    本文介绍了vue不操作dom实现图片轮播的示例代码,分享给大家,具体如下: 效果 宽度为1190px且水平居中的轮播盒子: 中间是当前显示的默认尺寸图片: 左右两边是预显示的小尺寸图片: 轮播从右至左,图片逐渐放大. 做普通平滑轮播也可以参照这个思路 html <ul> <li v-for="(demo,index) in demoList" :key="index" :class="{'demo-left':demoStyle(inde

  • vue 使用 vue-pdf 实现pdf在线预览的示例代码

    背景 之前的demo增加了图片预览,于是今天下午追完番剧就突然想到能不能把pdf在线预览也做了,说干就干,刚开始查了很多教程,我发现很多人都在说什么pdf.js这个库,这当然没什么问题,pdf.js的确可以非常完美的实现pdf在线预览的过程,但是感觉这样直接进去有点不太优雅,于是找找看看有没有什么现成的组件,发现有vue-pdf这个组件,虽然说它没有原生那样强大,比如不支持pdf文字复制,打印会乱码,但是我感觉已经足以满足我的需求了.本篇笔记循序渐进,从基础的demo,到一个可用的程度,文末列出

随机推荐