关键词:

  • Webpack
  • vue-cli
  • Axios
  • vuex
  • router
  • login

技术选型:

由于公司现有项目多以后台管理项目为主,且以后大多项目为针对原有项目的重构,因此,选择一款成熟的后台管理项目框架能够极大的提高开发效率。这里公司选用的是vue-element-admin,文档内容详尽,可拓展性强,上手较快。

所以,对应的的前端主技术框架选用Vue,配套UI库选用element-ui。所用到的一些技术栈和第三方库类见下表展示:

技术

说明

版本

Vue-CLI-3

Vue脚手架工具

3.2.3

Vue

渐进式JavaScript 框架

2.5.17

Vue-router

官方的路由管理器

3.0.1

Vuex

全局状态管理模式

3.0.1

Axios

基于promise 的HTTP 库

0.18.0

Element-UI

前端UI组件库

2.8.2

echarts

百度开发的基于Javascript 的数据可视化图表库。

4.2.1

Mockjs

接口写好之前,模拟数据

1.0.1-beta3

Js-cookie

一款封装好的cookie插件

2.2.0

SCSS

CSS预处理器

eslint

语法规则和代码风格的检查工具

4.19.1

Iconfont

国内功能很强大且图标内容很丰富的矢量图标库

Lodop(CLodop

第三方打印控件

 开发工具

系统

描述

VScode

主开发工具

Notepad++

临时单文件编辑[windows]

Svn

代码版本管理工具

Eclipse

处理报表

Webpack:

WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。

开发环境的启动项目时,实际就是通过webpack打包后启动的,只不过是打包的开发环境.

npm run dev的指令,实际执行额命令是npm run webpack-dev-server --inline --progress --config build/webpack.dev.conf.js

关于webpack相关具体内容见:vue+element UI 学习总结笔记(二十)--Webpack配置

开发环境、运行环境的区别?

vue脚手架的使用:

vue-cli:

全局安装vue-cli:

npm install vue-cli -g

初始化项目:

vue init <template-name> <project-name>

vue-cli参考文档:vue-cli详解

ps:

package.json:

每个项目的根目录下面,一般都有一个package.json文件,定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。npm install命令根据这个配置文件,自动下载所需的模块,也就是配置项目所需的运行和开发环境。

我们项目是先github下来的一个基本框架?

ps:main.js  是主入口文件,实例化vue

Axios:

Axios 是一个基于 promise 的 HTTP 库,简单的讲就是可以发送get、post请求。

我们项目中,request.js直接封装的,感谢花裤衩。

每次请求中,我们都带了token.

获取token的方法在auth.js当中。

ps:一篇Axios入门教程

我们的项目也创建自己的axios实例:

import axios from 'axios'
import Adapter from 'axios-mock-adapter'
import { get } from 'lodash'
import util from '@/libs/util'
import { errorLog, errorCreate } from './tools'

/**
 * @description 创建请求实例
 */
function createService () {
  // 创建一个 axios 实例
  const service = axios.create()
  // 请求拦截
  service.interceptors.request.use(
    config => config,
    error => {
      // 发送失败
      console.log(error)
      return Promise.reject(error)
    }
  )
  // 响应拦截
  service.interceptors.response.use(
    response => {
      // dataAxios 是 axios 返回数据中的 data
      const dataAxios = response.data
      // 这个状态码是和后端约定的
      const { code } = dataAxios
      // 根据 code 进行判断
      if (code === undefined) {
        // 如果没有 code 代表这不是项目后端开发的接口 比如可能是 D2Admin 请求最新版本
        return dataAxios
      } else {
        // 有 code 代表这是一个后端接口 可以进行进一步的判断
        switch (code) {
          case 0:
            // [ 示例 ] code === 0 代表没有错误
            // TODO 可能结果还需要code和msg进行后续处理,所以去掉.data返回全部结果
            // return dataAxios.data
            return dataAxios
          case 'xxx':
            // [ 示例 ] 其它和后台约定的 code
            errorCreate(`[ code: xxx ] ${dataAxios.msg}: ${response.config.url}`)
            break
          default:
            // 不是正确的 code
            errorCreate(`${dataAxios.msg}: ${response.config.url}`)
            break
        }
      }
    },
    error => {
      const status = get(error, 'response.status')
      switch (status) {
        case 400: error.message = '请求错误'; break
        case 401: error.message = '未授权,请登录'; break
        case 403: error.message = '拒绝访问'; break
        case 404: error.message = `请求地址出错`; break
        case 408: error.message = '请求超时'; break
        case 500: error.message = '服务器内部错误'; break
        case 501: error.message = '服务未实现'; break
        case 502: error.message = '网关错误'; break
        case 503: error.message = '服务不可用'; break
        case 504: error.message = '网关超时'; break
        case 505: error.message = 'HTTP版本不受支持'; break
        case 506: error.message = '调用His异常,未查询到挂号患者'; break
        default: break
      }
      errorLog(error)
      return Promise.reject(error)
    }
  )
  return service
}

/**
 * @description 创建请求方法
 * @param {Object} service axios 实例
 */
function createRequestFunction (service) {
  return function (config) {
    const token = util.cookies.get('token')
    const configDefault = {
      headers: {
        Authorization: token,
        'Content-Type': get(config, 'headers.Content-Type', 'application/json')
      },
      timeout: 500000,
      baseURL: process.env.VUE_APP_API,
      data: {}
    }
    return service(Object.assign(configDefault, config))
  }
}

// 用于真实网络请求的实例和请求方法
export const service = createService()
export const request = createRequestFunction(service)

// 用于模拟网络请求的实例和请求方法
export const serviceForMock = createService()
export const requestForMock = createRequestFunction(serviceForMock)

// 网络请求数据模拟工具
export const mock = new Adapter(serviceForMock)

试用封装的axios 服务实例:

//引入 request 就是axios服务的实例
import {request} from '@/api/service'
...
       request({
        url: '/testurl',
        method: 'post',
        data: {
          param1: 1,
          param1: 2,
        }
      }).then(ret => {
        if(ret.data != null){
          .....
        }
      })

值得注意的是:我们可能存在ajax请求嵌套的情况,我们自然的想法是:改为链式结构。

不过更好的办法可能是async+await

而 async/await 是一种建立在Promise之上的编写异步或非阻塞代码的新方法。async 是异步的意思,而 await 是 async wait的简写,即异步等待。

所以从语义上就很好理解 async 用于声明一个 函数 是异步的,而await 用于等待一个异步方法执行完成。

那么想要同步使用数据的话,就可以使用 async+await 。

 说明:async函数返回的是一个 Promise 对象。async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中  一个直接量,async 会把这个直接量通过promise.solve() 封装成 Promise 对象。

如果 async 函数没有返回值, 它会返回 promise.solve(underfined)。

await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,await 可以等任意表达式的结果)。

如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。

如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

ps:axios 进行同步请求(async+await)

下面的代码实现同步请求嵌套方法:先查询库存,再去删除,查询库存与删除都是异步方法。

        let p=this.fun_query_stock(row)
        p.then(async ret => {
          if (ret.data != null) {
            let  _that = this
            await fun_del_item(ret.data).then(function (response) {
              // 删除表格中数据
              _that.list1.splice(index, 1)
            })
              .catch(function (error) {
                _that.$message.error('删除失败')
              });
          } else {
            // 删除表格中数据
            this.list1.splice(index, 1)
          }
        })

.....
//其中fun_query_stock查询是否有库存
    fun_query_stock(row) {
      return request({
        url: '/stock/querystocks',
        method: 'post',
        data: {
          itemId: row.itemId,
        }
      })
    },


//删除库存

export function fun_del_item(id) {
  return request({
    url: '/stock/delete',
    method: 'post',
    params: { id }
  })
}

 这只是async+await一种用法

当采取 async+await的时候,错误的处理有2种方式:

  1. try{}catch() 方式
  2. promise的.then() .catch()  (上面的代码采取的是这种方式)

强调一点的是async+await是语法糖,重点还是理解promise方式的应用

更深入了解相关编码方式可以参考如下链接:

async/await的使用方法 

vue 中使用 async/await 将 axios 异步请求同步化处理

JavaScript async/await: The Good Part, Pitfalls and How to Use

vuex

参考(抄袭):vuex的简单使用

作用:

vuex解决了组件之间共享同一状态的麻烦问题。当我们的应用遇到多个组件共享状态时,会需要:

  1. 多个组件依赖于同一状态。传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。这需要你去学习下,vue编码中多个组件之间的通讯的做法。

  2. 来自不同组件的行为需要变更同一状态。我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。

以上的这些模式非常脆弱,通常会导致无法维护的代码。来自官网的一句话:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态。这里的关键在集中式存储管理。这意味着本来需要共享状态的更新是需要组件之间通讯的,而现在有了vuex,就组件就都和store通讯了。问题就自然解决了。

这就是为什么官网再次会提到Vuex构建大型应用的价值。如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex

什么可以用:

(需要牢记的一张图) 

  • state (类似存储全局变量的数据)
    getters (提供用来获取state数据的方法)
    actions (提供跟后台接口打交道的方法,并调用mutations提供的方法)
    mutations (提供存储设置state数据的方法)

我理解action中的方法,类似事件,只是在vuex中注册,需要通过dispatch触发这个方法

mutation中的方法,类似事件,只是在vuex中注册,需要通过commit触发这个方法

mutation中的type,类似事件名称

更详细内容参见:vue+element UI 学习总结笔记(十七)_vuex在项目中的应用

                               什么是vuex?vuex如何使用?

使用步骤:

1. 搭建vue脚手架,安装vuex依赖

2. 项目目录src下新建store目录和store.js文件(文件名随便取)

3、编写index.js文件

4、main.js引入这个文件

登录:主要完成用户验证等

验证规则:

验证规则和elemnet -ui挂钩:

我们是通过点击按钮验证的。

登录的时候,由于和后台交互需要时间,所以按钮上给个提示

当然 有个loading属性相配合,具体可以查阅element-ui相关文档

登录页面要保留用户一些选择项目,避免用户每次都重新选择:

我们是通过localstorage保存的相关信息。

至于登录的验证,肯定得调用后台接口,我们把调用放到vuex的action方法中。毕竟,登录信息应该对全局是可见的,具体实现方式就不展开了。

路由:

本段涉及:什么是路由,路由的使用步骤 动态路由  路由懒加载

路由是根据不同的 url 地址展示不同的内容或页面。

要使用router,必须先搞个路由地图。

静态路由:

1、定义组件

    const Foo = {
        template:'<div>foo</div>'
        };
    const Bar = {
        template:'<div>bar</div>'
    }   

2、定义路由地图

const routes = [
        {
            path:'/foo',
            component:Foo,
        },{
            path:'/bar',
            component:Bar
        }
    ];

3、根据路由地图创建路由实例:

const router = new VueRouter({
        routes:routes//可以简写 routes:routes
    })

4、路由实例注入到根实例:

    const app = new Vue({
        router
    }).$mount('#app')

5、 整个应用都具有路由功能,项目可以根据自己创建的路由,导航到到不同的页面

<div id="app">
        <h1>hello app</h1>
        <p>
            <!-- 使用router-link组件来导航 -->
            <router-link to="/foo">Go to Foo</router-link>
            <router-link to="/bar">Go to Bar</router-link>
        </p>

        <!-- 
            路由出口路由匹配到的在这里
         -->
         <router-view></router-view>
    </div>

总结一下:route 是一条路由,routes 是一组路由,router是一个机制,相当于管理者,他来管理路由

通过注入路由器,我们可以在任何组件内通过 this.$router 访问路由器,也可以通过 this.$route 访问当前路由.

官方文档通篇都常使用 router 实例。留意一下 this.$router 和 router 使用起来完全一样。我们使用 this.$router 的原因是我们并不想在每个独立需要封装路由的组件中都导入路由。

路由的编程式导航:

静态方式下,使用router-link组件来导航,router-link根据“导航地图”,“渲染”相应router-view。

编程式导航,就是用router.push(...)代替rout-link组件,事实上,router-link其实调用的就是router.push(...)方法。

嵌套路由

实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件.

从main.js中,可见,最外层是#app

import Vue from 'vue'
import App from './App'
......

new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})

app.vue如下:

<template>
  <div id="app">
    <!-- <div class="main-app">123</div> -->
    <router-view/>
  </div>
</template>
<script>
export default {
  name: 'App',
  data() {
    return {}
  }
}
</script>


从router中可见到,"/"即为Layout:

//路由
{
  path: '/',
  component: Layout,
  redirect: '/dashboard',
  name: 'Dashboard',
  hidden: true,
  children: [{
    path: 'dashboard',
    component: () => import('@/views/dashboard/index')
  }]
}

Layout.vue结构如下:

<template>
  <div :class="classObj" class="app-wrapper">
    <div v-if="disabled" class="shield" />
    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
    <head-top/>
    <sidebar class="sidebar-container" />
    <div class="main-container">
      <navbar class="toggle-sidebar" />
      <app-main/>
    </div>
    <foot/>
  </div>
</template>

<script>
import { Navbar, Sidebar, AppMain } from './components'
import HeadTop from '@/components/headTop'
import Foot from '@/components/foot'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'
import Bus from '@/utils/Bus'
import { getCodeLabelDictionary } from '@/api/CommonApi'
import { getNowDate } from '@/api/login'

export default {
  name: 'Layout',
  components: {
    Navbar,
    Sidebar,
    AppMain,
    HeadTop,
    Foot
  },
  mixins: [ResizeMixin],
  computed: {
    ...
  },
  watch: {
    ...
  },
  created() {
    ...
  },

  methods: {
    ...
  }
}
</script>
<style lang="scss">
    ...
</style>

Layout.vue当中,可以看出,有左侧导航栏的区域了。

我们是动态路由和静态路由混用的:

import Vue from 'vue'
import Router from 'vue-router'


Vue.use(Router)

/* Layout */
import Layout from '../views/layout/Layout'

//静态路由部分
export const constantRouterMap = [{
  path: '/login',
  component: () => import(`@/views/login/index`),
  hidden: true
},
{
  path: '/404',
  component: () => import('@/views/404'),
  hidden: true
},
{
  path: '/',
  component: Layout,
  redirect: '/dashboard',
  name: 'Dashboard',
  hidden: true,
  children: [{
    path: 'dashboard',
    component: () => import('@/views/dashboard/index')
  }]
}
]

export default new Router({
  // mode: 'history', //后端支持可开
  scrollBehavior: () => ({
    y: 0
  }),
  routes: constantRouterMap
})

// 异步挂载的路由
// 动态需要根据权限加载的路由表
export const asyncRouterMap = [
]

// component: () => import('@/components/permision'),

参考1:vue-router八个重要知识点应用图解

参考2:vue-router 基本使用

参考3:vue-router 快速入门

ps:嵌套路由与路由参数的比较,我前端同事给的解释如下:

嵌套路由的表现形式在url上是这样的 :   /path/pathtwo/paththree
路由参数在url上表现出是这样的: /path/pathtwo/paththree/154/887,这种形式需要在路由的path配置的时候写成 /path/pathtwo/paththree/:appid/:productid
或者以查询字符串形式带问号的那种:/path/pathtwo/paththree?appid=154&productid=887

如果路由参数以非查询字符串形式表示,确实可能和嵌套路由表现出的地址一样 。但是两者在路由router中的path是完全不一样的 。

如 /path/:paramsone/:paramstwo   ,结果paramsone的值为'pathtwo' ,paramstwo  的值为paththree' ,那么最终的路由地址是
 /path/pathtwo/paththree,
而如果此时你正好也写了一个子路由,path为  :/path/pathtwo/paththree

这两个url看起来一样 ,但是匹配的path和组件页面是完全不一样的 。前者匹配的是/path对应的页面 ,后面的paramsone,paramstwo  只不过是路由里面的参数 。
后者匹配的就是/path/pathtwo/paththree对应的页面 ,且无路由参数 。

参考:vue-router+vuex实现加载动态路由和菜单

1、路由 什么时候 什么地方从后端请求的

permission.js中有个守护路由router.beforeEach方法,是取得路由的核心代码。

ps:Promise vs  permission:

Promise:a declaration or assurance that one will do a particular thing or that a particular thing will happen.es6中,用这个词表示是异步函数。

permission:consent; authorization.。

改进点:

后端返回的是该用户可访问的全部菜单 ,并不是全部页面的菜单。
已经与用户绑定了,所以这里的权限过滤并没有起效 。
而且,后端并没有根据用户不同配置不同的角色状态,只是写死了每个登陆的用户都是admin,所以这里的else里永远不会触发,也就是这里的权限过滤还是没有使用。

这种方式是不是值得商榷呢?

2、登录跳转

验证成功就直接跳转首页,跳转的过程,就会触发Navigation Guards,Navigation Guards中,除了验证用户是否登录外,如果是首次登录,还会从后端查询用户的功能菜单,组装成前端的router.


3、路由和左边文件夹导航

如何和左边导航栏关联

导航菜单

工具条

ps:vue生命周期参考文档:

Vue生命周期中mounted和created的区别

例如我们项目中,要给工具条绑定事件,我们一般在created中,写相关代码。这时候,html还没有渲染呢,盗一个生命周期的图如下:

index.html、main.js、app.vue关系:ps:关于Vue中main.js,App.vue,index.html之间关系进行总结

在项目运行中,main.js作为项目的入口文件,运行中,找到其实例需要挂载的位置,即index.html中,刚开始,index.html的挂载点处的内容会被显示,但是随后就被实例中的组件中的模板中的内容所取代,所以我们会看到有那么一瞬间会显示出index.html中正文的内容。

Logo

前往低代码交流专区

更多推荐