之前写过一篇关vue项目框架搭建的文章,主要是讲项目里需要用到哪些js包,这篇文章主要是讲一个初始vue项目需要具备哪些基本的功能模块,希望对刚开始学习vue的小伙伴有帮助。

创建项目

  1. 安装nodejs
  2. 设置镜像为淘宝镜像
    npm config set registry https://registry.npm.taobao.org
  3. 安装vue-cli 工具
    npm install –global vue-cli
  4. 初始化一个vue项目
    vue init webpack my-project
  5. 安装js依赖包
    npm install
  6. 启动项目
    npm run dev

创建完项目需要考虑些啥

  1. 需要引入哪些js
  2. 目录结构如何规划
  3. 前后端分离之后,数据结构如何定义
  4. 如何做mock开发
  5. 权限如何控制
  6. 如何做统一的错误提示
  7. 如何做ajax请求
  8. 全局组件和全局方法有哪些

1.需要引入哪些js/css?

  • axios ajax编程js
  • mockjs 前后端分离之后,前端mock开发
  • axios-mock-adapter 配合mockjs使用
  • element-ui/iview-ui ui框架
  • font-awesome 一套icon图表css框架,此框架也可不引人,element-ui/iview-ui两个ui框架都自带icon图表,如满足不了再考虑此框架

安装命令: npm install 包名称
如: npm install axios
main.js 引入

import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import Mock from './mock'
import axios from './axios'
import './assets/css/custom.css'
import 'font-awesome/css/font-awesome.min.css'
import MyPlugin from './assets/js/plugin'

Vue.use(ElementUI)
Vue.use(MyPlugin)

Vue.config.productionTip = false

if (process.env.NODE_ENV === 'development') {
  Mock.bootstrap()
}

/* eslint-disable no-new */
var vm = new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: { App }
})

export default vm

2.目录结构如何规划?

  • assets 资源目录
    • css 自定义css的文件目录
    • img 图片目录
    • js 自定义js的文件目录
  • axios ajax编程框架主配置目录
    • index.js axios配置文件及出口
  • mock mock开发主配置目录
    • index.js mock配置文件及出口
  • router 路由目录
    • index.js router配置文件及出口
  • components 全局组件目录
    • index.js 组件配置文件及出口
  • pages 所有业务模块目录
    • common 业务模块目录
      • api 封装业务模块对应的后端api ajax请求的目录
        • index.js api出口文件
      • mock 封装业务模块对应的后端api的mock请求的
        • index.js mock出口文件
      • index.js 业务模块出口文件
      • Common.vue 业务模块的vue文件
  • App.vue 顶级父组件
  • main.js 主配置文件
    这里写图片描述

3.前后端分离之后,数据结构如何定义?

{
  code: 0, // 状态码,0代表后端逻辑正常,通过此状态码可做统一提示
  msg: "提示信息", // 后端逻辑错误时,可将提示信息写在此用于提示用户
  result: {} 或者 []  // 逻辑正常情况下,返给前端的具体数据
}

4.如何做mock开发?

一个总入口配置文件,各业务模块有自己mock配置文件,每次新增业务模块,一定要在总入配置文件中引入业务模块自己的mock配置文件,该业务模块的mock功能才能生效

开启和关闭mock在main.js中

if (process.env.NODE_ENV === 'development') {
  Mock.bootstrap()  // 与后端联调时注释掉即可
}

总入口配置文件,即mock目录下index.js

import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
import Login from '../pages/login/mock' // 引入各个业务模块自己的mock文件

export default {
  /**
   * mock 入口
   */
  bootstrap () {
    let mock = new MockAdapter(axios)
    Login.bootstrap(mock)
  },
  /**
   * mock服务的返回结果集,并打印日志,
   * @param requestType 请求类型
   * @param apiName 接口名称
   * @param apiUrl 接口url
   * @param apiParam 接口参数
   * @param apiResult 接口返回结果的result属性或者是接口返回的完整json对象{code:,msg:,result:}
   * @returns {{code: number, msg: string, result: *}}
   */
  apiResponseJson (requestType, apiName, apiUrl, apiParam, apiResult) {
    let res = {
      code: 0,
      msg: 'success',
      result: apiResult
    }
    if (apiResult.code !== undefined && apiResult.msg !== undefined) {
      res = apiResult
    }
    console.log(apiName + ',url:' + apiUrl + ', 请求类型:' + requestType + ',参数:', apiParam)
    console.log(apiName + ', 返回:', res)
    return res
  },
  /**
   * 打印mock的接口格式,包括接口名称,接口url,接口参数,接口返回,方法会返回mock测试数据json格式
   * @param requestType
   * @param apiName
   * @param apiUrl
   * @param apiParam
   * @param apiResult
   * @returns {[number,*]}
   */
  resolve200 (requestType, apiName, apiUrl, apiParam, apiResult) {
    return [200, this.apiResponseJson(requestType, apiName, apiUrl, apiParam, apiResult)]
  },
  /**
   *
   * @param config
   * @param requestType
   * @param apiName
   * @param apiUrl
   * @param apiResultFunc
   * @returns {Promise}
   */
  replyFunc (config, requestType, apiName, apiUrl, apiResultFunc) {
    let data = config.data
    if (typeof (data) === 'string') {
      data = JSON.parse(data)
    }
    return new Promise((resolve, reject) => {
      resolve(this.resolve200(requestType, apiName, apiUrl, data, apiResultFunc(data)))
    })
  },
  /**
   * mock,post请求,模拟后端正常成功的逻辑,code=0
   * @param mock mock对象
   * @param apiName 接口名称
   * @param apiUrl 接口url
   * @param apiResultFunc 获取接口返回对象的result属性值的回调方法,回调方法的参数就是接口参数,是一个json对象
   */
  post (mock, apiName, apiUrl, apiResultFunc) {
    mock.onPost(apiUrl).reply(config => {
      return this.replyFunc(config, 'post', apiName, apiUrl, apiResultFunc)
    })
  },
  /**
   * mock,get,模拟后端正常成功的逻辑,code=0
   * @param mock mock对象
   * @param apiName 接口名称
   * @param apiUrl 接口url
   * @param apiResultFunc 获取接口返回对象的result属性值的回调方法,回调方法的参数就是接口参数,是一个json对象
   */
  get (mock, apiName, apiUrl, apiResultFunc) {
    mock.onGet(apiUrl).reply(config => {
      return this.replyFunc(config, 'get', apiName, apiUrl, apiResultFunc)
    })
  }
}

业务模块自己的mock文件,即业务模块目录/mock/index.js

import Mock from '../../../mock'
import {LogUrl} from '../api'

const AdminUser = {
  userId: 1,
  userName: 'admin',
  password: '123456',
  name: '某某某',
  sysRouters: [], // 所有是资源的路由
  authRouters: [] // 用户有权限的路由资源
}

export default {
  bootstrap (mock) {
    Mock.post(mock, '登录系统', LogUrl.Login, function (params) {
      console.log('ddd')
      debugger
      if (params.userName !== AdminUser.userName || params.password !== AdminUser.password) {
        debugger
        return {code: 100, msg: '账号或者密码不正确!'}
      }
      return AdminUser
    })

    Mock.post(mock, '退出系统', LogUrl.Logout, function (params) {
      return true
    })
  }
}

5.权限如何控制?

权限分两种:登录权限和资源权限

我的实现方式是登录成功之后就将权限信息加载到前端,然后在前端判断,这样简单些,也可以实时请求后端来判断

登录权限
  1. 登录信息存在localstorage
  2. 登录校验,拦截路由跳转和后端请求response

拦截路由校验登录信息,在router/index.js中

/**
 * 不做登录校验的路由集合
 * @type {[*]}
 */
const unLoginCheckRoutes = ['/noauth', '/test', '/report-design']

/**
 * 拦截路由跳转,校验用户的路由权限,或者做流量监控
 */
router.beforeEach(function (to, from, next) {
  // 是否要做登录及权限校验
  let loginCheck = true
  unLoginCheckRoutes.forEach(r => {
    if (to.path === '/' || (r !== '/' && to.path.startsWith(r))) {
      loginCheck = false
    }
  })
  if (loginCheck) {
    let user = LoginUtil.getLoginUser()
    if (user === null) { // 登录失效,跳转登录页
      VueGlobal.goLoginPage(to.path)
      return
    }
    // 校验路由是否可访问
    if (user.sysRouters.includes(to.path)) { // 判断路由是不是一个资源
      if (user.authRouters.includes(to.path)) { // 有此路由权限的登录用户才能访问
        next()
      } else {
        next('/noauth')
      }
    } else { // 登录用户都能访问的路由资源
      next()
    }
  } else { // 所有用户都能访问的路由资源
    next()
  }
})

拦截后端请求response,校验后端返回状态,判断登录是否失效,在axios/index.js中

// 拦截请求response,在此可做统一的错误提示
axios.interceptors.response.use(function (response) {
  if (response.data && response.status === 200) {
    if (response.data.code === 0) {
      return response
    } else {
      if (response.data.code === 400 || response.data.code === 401) { // 登录token失效,转向登录页
        VueGlobal.goLoginPage()
        return response
      } else {
        VueGlobal.notifyWarn(response.data.msg)
        return Promise.reject(response.data)
      }
    }
  } else {
    VueGlobal.notifyWarn(response.statusText)
    return Promise.reject(new Error(response))
  }
}, function (error) {
  VueGlobal.notifyWarn(error.message)
  return Promise.reject(error)
})
资源权限
  1. 资源可见性
    1.1 给每个资源定义一个唯一ID,命名规范:全路由路径,下划线分割,再加具体功能描述,如add、delete
    1.2 通过v-if控制资源展现
<template>
  <PLayout>
    <PMenu>
      <PMenuItem path="/sys/user" icon="address-book" name="用户管理"  v-if="getLoginUser().authDom && getLoginUser().authDom.sys_user"/>
      <PMenuItem path="/sys/role" icon="address-book" name="角色管理"  v-if="getLoginUser().authDom && getLoginUser().authDom.sys_role"/>
    </PMenu>
  </PLayout>
</template>

<script>
  export default {}
</script>

<style>

</style>
  1. 路由可访问性
    2.1 拦截路由跳转,判断路由可访问性
/**
 * 不做登录校验的路由集合
 * @type {[*]}
 */
const unLoginCheckRoutes = ['/noauth', '/test', '/report-design']

/**
 * 拦截路由跳转,校验用户的路由权限,或者做流量监控
 */
router.beforeEach(function (to, from, next) {
  // 是否要做登录及权限校验
  let loginCheck = true
  unLoginCheckRoutes.forEach(r => {
    if (to.path === '/' || (r !== '/' && to.path.startsWith(r))) {
      loginCheck = false
    }
  })
  if (loginCheck) {
    let user = LoginUtil.getLoginUser()
    if (user === null) { // 登录失效,跳转登录页
      VueGlobal.goLoginPage(to.path)
      return
    }
    // 校验路由是否可访问
    if (user.sysRouters.includes(to.path)) { // 判断路由是不是一个资源
      if (user.authRouters.includes(to.path)) { // 有此路由权限的登录用户才能访问
        next()
      } else {
        next('/noauth')
      }
    } else { // 登录用户都能访问的路由资源
      next()
    }
  } else { // 所有用户都能访问的路由资源
    next()
  }
})

6.如何做统一的错误提示?

利用axios拦截后端api请求response,根据后端返回数据的code状态码判断

// 拦截请求response,在此可做统一的错误提示
axios.interceptors.response.use(function (response) {
  if (response.data && response.status === 200) {
    if (response.data.code === 0) { // 正常逻辑
      return response
    } else {
      if (response.data.code === 400 || response.data.code === 401) { // 登录token失效,转向登录页
        VueGlobal.goLoginPage()
        return response
      } else {
        VueGlobal.notifyWarn(response.data.msg) // 错误提示
        return Promise.reject(response.data)
      }
    }
  } else {
    VueGlobal.notifyWarn(response.statusText) // 错误提示
    return Promise.reject(new Error(response))
  }
}, function (error) {
  VueGlobal.notifyWarn(error.message) // 错误提示
  return Promise.reject(error)
})

7.如何做ajax请求?

  1. 在axios异步编程框架上封装get、post等请求
  2. 在请求上统一加上登录token信息或者其他参数(所有接口都要传的参数),通过拦截request请求
  3. 各个业务模块将自己对应的后端api接口请求全写在api目录下
    axios的配置文件,axios/index.js
import axios from 'axios'
import qs from 'qs'
import LoginUtil from '../assets/js/loginUtils'
import VueGlobal from '../assets/js/vueGlobal'

/**
 * get请求
 * @param url
 * @param params
 */
export const get = (url, params) => { return axios.get(url + qs.stringify(params)).then(res => res.data) }

/**
 * post请求
 * @param url
 * @param params
 */
export const post = (url, params) => { return axios.post(url, params).then(res => res.data) }

/**
 * 下载请求
 * @param url
 * @param params
 */
export const download = (url, params) => { window.location.href = axios.defaults.baseURL + url + '?token=' + LoginUtil.getLoginUser().token + '&' + qs.stringify(params) }

// 1.设置请求头的默认配置
// 前后端联调的时候,后端服务地址
axios.defaults.baseURL = ''
if (process.env.NODE_ENV === 'development') {
  // axios.defaults.baseURL = 'http://127.0.0.1:8080'
}
// axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'

// 拦截request请求
axios.interceptors.request.use(function (config) {
  // 添加登录token或者其他公共参数
  let user = LoginUtil.getLoginUser()
  if (user) {
    if (config.data) {
      config.data.token = user.token
    } else {
      config.data = {token: user.token}
    }
  }
  return config
}, function (error) {
  return Promise.reject(error)
})

// 拦截请求response,在此可做统一的错误提示
axios.interceptors.response.use(function (response) {
  if (response.data && response.status === 200) {
    if (response.data.code === 0) {
      return response
    } else {
      if (response.data.code === 400 || response.data.code === 401) { // 登录token失效
        VueGlobal.goLoginPage()
        return response
      } else {
        VueGlobal.notifyWarn(response.data.msg)
        return Promise.reject(response.data)
      }
    }
  } else {
    VueGlobal.notifyWarn(response.statusText)
    return Promise.reject(new Error(response))
  }
}, function (error) {
  VueGlobal.notifyWarn(error.message)
  return Promise.reject(error)
})

export default axios

业务模块自己的api配置文件,在业务模块目录/api/index.js中

import {get, post} from '../../../axios'

const LogApiPre = '/api'

export const LogUrl = {
  Login: LogApiPre + '/login', // 登录
  Logout: LogApiPre + '/logout' // 退出
}

/**
 * 登录
 * @param params
 */
export const login = (params) => { return post(LogUrl.Login, params) }

/**
 * 退出
 * @param params
 */
export const logout = (params) => { return get(LogUrl.Logout, params) }

8.全局组件和全局方法有哪些?

各个模块都可能用到的功能,就可以封装成全局组件或者全局方法

  • 全局方法
    • 1.获取登录用户
    • 2.消息提示
  • 全局组件
    • 1.header组件
    • 2.menu组件
    • 3.公共按钮,比如返回上级

全局组件和方法需要安装成vue插件,然后再main.js中引入并use

import Component from '../../components'
import VueGlobal from './vueGlobal'
import LoginUtil from './loginUtils'

const PComponents = {
  PHead: Component.PHead,
  PLayout: Component.PLayout,
  PMenu: Component.PNavside.PMenu,
  PMenuItem: Component.PNavside.PMenuItem
}

export default {
  install (Vue) {
    Vue.prototype.goBack = () => { VueGlobal.goBack() }
    Vue.prototype.getLoginUser = () => { return LoginUtil.getLoginUser() }

    // 消息提示
    Vue.prototype.message = (msg) => { VueGlobal.messageInfo(msg) }
    Vue.prototype.messageSuccess = (msg) => { VueGlobal.messageSuccess(msg) }
    Vue.prototype.messageWarn = (msg) => { VueGlobal.messageWarn(msg) }
    Vue.prototype.messageError = (msg) => { VueGlobal.messageError(msg) }

    // 通知提示
    Vue.prototype.notify = (msg) => { VueGlobal.notifyInfo(msg) }
    Vue.prototype.notifySuccess = (msg) => { VueGlobal.notifySuccess(msg) }
    Vue.prototype.notifyWarn = (msg) => { VueGlobal.notifyWarn(msg) }
    Vue.prototype.notifyError = (msg) => { VueGlobal.notifyError(msg) }

    // 弹框提示
    Vue.prototype.alertInfo = (msg) => { VueGlobal.alertInfo(msg) }
    Vue.prototype.alertSuccess = (msg) => { VueGlobal.alertSuccess(msg) }
    Vue.prototype.alertWarn = (msg) => { VueGlobal.alertWarn(msg) }
    Vue.prototype.alertError = (msg) => { VueGlobal.alertError(msg) }

    /**
     * 统一处理promise catch的错误
     * @param error 错误信息
     */
    Vue.prototype.handleError = (error) => { console.warn(error) }

    Object.keys(PComponents).forEach((key) => {
      Vue.component(key, PComponents[key])
    })
  }
}

项目github地址: https://github.com/xiaoping1988/vue-template-proj

Logo

前往低代码交流专区

更多推荐