【前端vue进阶实战】:从零打造一个流程图、拓扑图项目【Nuxt.js + Element + Vuex】 (三)

前面二,下面我们实现右键菜单、http通信、路由。

本系列教程是用Vue.js + Nuxt.js + Element + Vuex + 开源js绘图库,打造一个属于自己的在线绘图软件,最终效果:http://topology.le5le.com 。如果你觉得好,欢迎给文章和开源库点赞,让我们更有动力去做好!

本系列教程源码地址:Github

目录

右键菜单

右键菜单原理很简单:自定义html的oncontextmenu事件:

<div id="topology-canvas" class="full" @contextmenu="onContextMenu($event)"></div>

屏蔽默认右键菜单事件,计算右键鼠标位置,弹出一个我们自己的div自定义菜单即可

onContextMenu(event) {
      event.preventDefault()
      event.stopPropagation()

      if (event.clientY + 360 < document.body.clientHeight) {
        this.contextmenu = {
          left: event.clientX + 'px',
          top: event.clientY + 'px'
        }
      } else {
        this.contextmenu = {
          left: event.clientX + 'px',
          bottom: document.body.clientHeight - event.clientY + 'px'
        }
      }
    }
<div class="context-menu" v-if="contextmenu.left" :style="this.contextmenu">
     <ul>
           <li>菜单一</li>
             <li>菜单二</li>
             <li>菜单三</li>
         </ul>
</div>

在本项目中,封装了一个右键菜单组件“CanvasContextMenu”,通过父组件,传递canvas实例和选中的属性数据

 props: {
    canvas: {
      type: Object,
      require: true
    },
    props: {
      type: Object,
      require: true
    }
  }

其中,属性props含义为:

props: {
  node: null,       // 选中节点
  line: null,         // 选中连线
  nodes: null,    // 选中多个节点
  multi: false,   // 选中多个节点/连线
  locked: false // 选中对象是否被锁定
}

然后,我们根据菜单事件和属性props来调用canvas的相应接口函数,参考开发文档

http通信

这里,我们不去从零写个后端服务,直接采用topology.le5le.com线上接口服务。

代理配置

首先,我们需要给nuxt.config.js添加http代理配置,这样开发环境下的http请求,自动代理转发给topology.le5le.com,获取到真实数据。

 axios: {
    proxy: true
  },
  proxy: {
    '/api/': 'http://topology.le5le.com/',
    '/image/': 'http://topology.le5le.com/'
  },

其中,proxy的含义是指:所有/api/、/image/开头的请求,自动转发给http://topology.le5le.com/ ,其他的不转发。
通常,我们通过前缀/api/表示这是后端接口请求,而不是静态资源请求;/image/表示静态资源图片请求。

用户登录

1. 右上角添加用户头像和昵称

由于这里,暂时没有多页面共享用户信息,因此,我们直接在layouts/default的顶部导航栏和data里添加用户数据

data() {
    return {
      about: false,
      license: false,
      joinin: false,
      lineNames: ['curve', 'polyline', 'line'],
      arrowTypes: [
        '',
        'triangleSolid',
        'triangle',
        'diamondSolid',
        'diamond',
        'circleSolid',
        'circle',
        'line',
        'lineUp',
        'lineDown'
      ],
      user: null
    }
}
    
    
    
    <el-submenu index="user" v-if="user">
            <template slot="title">
                <el-avatar
                    src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
                    :size="24"
                ></el-avatar>
                {{user.username}}
            </template>
            <el-menu-item @click="onSignOut">退出</el-menu-item>
        </el-submenu>
        <el-menu-item v-if="!user">
            <a @click="onLogin">注册 / 登录</a>
        </el-menu-item>

大型项目,可能需要使用vuex去保存用户数据

2. 实现登录

这里,我们直接省略登录页面,直接跳转到线上登录页面account.le5le.com,共享登录状态。

凡是le5le.com的子域名,通过共享cookie中的token来共享le5le.com的登录状态。首先,我们修改本地电脑的host文件,新增一条local.le5le.com子域名,映射到本地电脑:

127.0.0.1 local.le5le.com

如何修改host文件,请google。

然后,我们把 http://localhost:3000/ 换成 http://local.le5le.com:3000/ 去在浏览器中打开我们的开发页面,这时,我们就可以点击右上角“登录/注册”,去登录。

2.1 第一次打开网页,读取用户是否登录

在le5le.com上,是使用jwt的方式去用户认证的。jwt的token值存储在cookie中,方便子域名共享登录。然后每个http请求headers里面加上Authorization: token值,后端服务就可以认证用户身份。

在第一次打开网页初始化时,只需在请求后端服务/api/user/profile获取用户即可。当接口/api/user/profile返回用户数据,表示用户已登录;当返回401表示未登录。这里,我们先判断了是否存在cookie下的token在请求用户接口。参考default.vue:

async getUser() {
      if (this.$cookies.get('token')) {
        this.user = await this.$axios.$get('/api/user/profile')
      }
},

async和await是成对出现的。函数名前面加async,函数里面就可以使用await表示必须等待await后面的函数执行完得到返回结果,才继续执行后面的代码。通常用于等待异步请求结果,避免回调。即使没有异步也是可以的。

为了每次请求自动把cookie里面的token添加到headers里面,我们需要按照下面操作,写一个http请求的拦截器。

2.2 http拦截器

http拦截器的作用时,每次请求和数据返回时,自动帮我们处理一些全局公用操作。比如这里的身份认证token添加。

2.2.1 新建一个plugins/axios.js插件文件
通过axios插件的方式实现http拦截器。

export default function({ $axios, app, redirect }) {
  // 设置token,添加Authorization到headers
  $axios.setToken(app.$cookies.get('token'))

  $axios.onResponse(response => {
      // 统一错误处理。如果发现error字段,则发送一个notice/error错误消息。在default.vue监听并弹出右上角错误提示框
    if (response.error) {
      app.store.commit('notice/error', { text: response.error })
    }
  })

  $axios.onError(error => {
      // 通过code状态码判断接口是否出错。请自行google学习http状态码
    const code = parseInt(error.response && error.response.status)
    if (code === 401) {
          // 需要登录
      app.store.commit('notice/error', {
        text: '请先登录!'
      })
    } else if (code === 403) {
          // 无权限访问
      redirect('/')
    } else if (code === 500) {
      app.store.commit('notice/error', {
        text: error.response.error || '服务错误,请稍后重试!'
      })
    }
  })
}

2.2.2 nuxt.config.js中配置加载axios插件

plugins: ['@/plugins/element-ui', '@/plugins/axios'],

2.2.3 添加cookie插件
因为我们需要读取cookie,所有安装一个cookie插件,方便使用。

yarn add cookie-universal-nuxt --save

nuxt.config.js中

modules: [
   // Doc: https://axios.nuxtjs.org/usage
   '@nuxtjs/axios',
   'cookie-universal-nuxt'
 ],

然后,可以直接在我们的plugins/axios.js和(default).vue中使用了。

路由,添加一个首页列表页面

nuxt.js不需要去写router.js,约定pages下的vue文件就是路由。比较省事。
我们添加一个新的index.vue首页,把原index.vue改成workspace.vue。
新index.vue:

<template>
 <div class="page-list">
   <div>
     <div class="nav">
       <label>热门图文</label>
     </div>
     <div class="flex wrap">
       <div
         class="topo"
         v-for="(item, index) of data.list"
         :key="index"
         :title="item.desc"
         @click="onOpen(item)"
       >
         <div class="image">
           <img :src="item.image" />
         </div>
         <div class="ph15 pv10">
           <div class="title line one" :title="item.name">{{ item.name }}</div>
           <div class="desc line two mt5" :title="item.desc">{{ item.desc }}</div>
           <div class="flex mt5">
             <div class="full flex middle">
               <el-avatar
                 src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"
                 :size="24"
               ></el-avatar>
               <span class="ml5">{{ item.username }}</span>
             </div>
             <div>
               <span class="hover pointer mr15" title="赞">
                 <i
                   class="iconfont"
                   :class="{'iconfont icon-appreciate':!item.stared, 'iconfont icon-appreciatefill':item.stared}"
                 ></i>
                 <span class="ml5">{{ item.star || 0 }}</span>
               </span>
               <span class="hover pointer" title="收藏">
                 <i
                   class="iconfont"
                   :class="{'iconfont icon-like':!item.favorited, 'iconfont icon-likefill':item.favorited}"
                 ></i>
                 <span class="ml5">{{ item.hot || 0 }}</span>
               </span>
             </div>
           </div>
         </div>
       </div>
     </div>
     <div>
       <el-pagination
         @current-change="getList"
         :current-page="search.pageIndex"
         :page-size="8"
         layout=" prev, pager, next, jumper, total"
         :total="data.count"
       ></el-pagination>
     </div>
   </div>
 </div>
</template>

<script>
export default {
 data() {
   return {
     data: {
       list: [],
       count: 0
     },
     search: {
       pageIndex: 1,
       pageCount: 8
     }
   }
 },
 created() {
   this.getList()
 },
 methods: {
   async getList() {
     this.data = await this.$axios.$get(
       `/api/topologies?pageIndex=${this.search.pageIndex}&pageCount=${this.search.pageCount}`
     )
   },
   onOpen(item) {
     this.$router.push({ path: '/workspace', query: { id: item.id } })
   }
 }
}
</script>

index.vue 主要包含:1. getList获取首页列表数据;2. this.$router.push({ path: '/workspace', query: { id: item.id } }) 打开具体页面

最后

自此,一个麻雀虽小五脏俱全的小项目就完成了,包含:框架搭建、插件、vuex、身份认证、http通信、路由等功能。

整个项目功能细节还不完善,欢迎大家提pr:

完整细节可参考:http://topology.le5le.com/开发文档 。可加入贡献者名单哦!也欢迎加群交流讨论:

topology技术讨论群2

如何贡献

通过GitHub的pr方式:

  1. 阅读开发文档,了解相关属性。
  2. fork仓库到自己名下
  3. 本地修改并提交到自己的git仓库
  4. 在自己的fork仓库找到 “Pull request” 按钮,提交


    file

开源项目不易,欢迎大家一起参与,给【文章GitHub开源库】点星点赞,或资助服务器:

file

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352

推荐阅读更多精彩内容