美文网首页前端框架
vue架构方案分析记录

vue架构方案分析记录

作者: huangxiongbiao | 来源:发表于2018-05-30 10:17 被阅读301次

    一、采用基础技术

    1、基础技术
    vue,router(界面跳转),vuex(状态管理)
    
    2、UI框架
    element-ui
    
    3、网络请求
    axios
    

    二、项目结构分层

    image.png
    build---打包配置的js
    conf---配置打包的ip,port等基础参数
    src--源代码
            api---网络请求数据层
            assets---图片静态资源
            components--基础组件
            router---路由层
            store---状态处理层
            styles---样式
            utils----通用工具
            pages(views)---视图层
    

    三、各层次详解

    1、网络层

    简介:利用axios封装通用的请求类。方法中可以配置请求拦截和返回结果拦截,处理请求权限校验和相关报错的统一处理。另外根据界面分解成api层,利用工具类调用服务端接口返回数据。
    目录参考:


    image.png

    部分参考样例代码:
    工具类

    import axios from 'axios'
    import {Message} from 'element-ui'
    import {MessageBox} from 'element-ui'
    import store from '../store'
    import {getToken, getID, getAccount, getAccountId} from '@/utils/auth'
    axios.defaults.withCredentials=true;
    // 创建axios实例
    const service = axios.create({
      // baseURL: 'http://101.132.107.120:8080', // api的base_url
      timeout: 6000000
    })
    
    // request拦截器
    service.interceptors.request.use(config => {
      if (store.getters.token) {
        config.headers['token'] = store.getters.token // 让每个请求携带自定义token 请根据实际情况自行修改
      }
      if (store.getters.JSESSIONID) {
        config.headers['JSESSIONID'] = store.getters.JSESSIONID
        // if (config.params) {
        //   config.url += '&' + Qs.stringify({'JSESSIONID': store.getters.JSESSIONID})
        // } else {
        //   config.url += '?' + Qs.stringify({'JSESSIONID': store.getters.JSESSIONID})
        // }
      }
      // if (store.getters.account) {
      //   config.headers['account'] = getAccount()
      // }
      if (store.getters.accountId) {
        config.headers['accountId'] = getAccountId()
      }
      config.headers['Test'] = 'zdzc'
      return config
    }, error => {
      // Do something with request error
      console.log(error) // for debug
      Promise.reject(error)
    })
    
    // respone拦截器
    service.interceptors.response.use(
      response => {
        /**
         * code为非20000是抛错 可结合自己业务进行修改
         */
        //  const res = response.data
        consoleLog('response', response.data)// for debug
        // 50008:非法的token; 50012:其他客户端登录了;  50014:Token 过期了;
        // if (response.data.statusCode === 4001) {
        //   MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
        //     confirmButtonText: '重新登录',
        //     cancelButtonText: '取消',
        //     type: 'warning'
        //   }).then(() => {
        //     store.dispatch('FedLogOut').then(() => {
        //       location.reload()// 为了重新实例化vue-router对象 避免bug
        //     })
        //   })
        // } else {
        return response.data
        // }
      },
      error => {
        console.log('err' + error)// for debug
        if (error.response.status === 401) {
          MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
            confirmButtonText: '重新登录',
            cancelButtonText: '取消',
            type: 'warning'
          }).then(() => {
            store.dispatch('FedLogOut').then(() => {
              location.reload()// 为了重新实例化vue-router对象 避免bug
            })
          })
        } else if (error.response.status === 404) {
          //TODO 处理404页面
        } else {
          if (error.response.data.statusCode === 40000) {
            MessageBox.confirm('你的访问授权已过期,请重新登录', '确定登出', {
              confirmButtonText: '重新登录',
              cancelButtonText: '取消',
              type: 'warning'
            }).then(() => {
              store.dispatch('FedLogOut').then(() => {
                location.reload()// 为了重新实例化vue-router对象 避免bug
              })
            })
          } else {
            consoleLog('err', error.response.data)// for debug
            Message({
              message: error.response.data.message,
              type: 'error',
              duration: 5 * 1000
            })
          }
        }
        return Promise.reject(error)
      }
    )
    
    function consoleLog(tag, obj) {
      console.log(tag + ':' + JSON.stringify(obj))// for debug
    }
    
    export default service
    
    

    api使用代码

    import fetch from '@/utils/fetch'
    import Qs from 'qs'
    
    export function login (username, password) {
      return fetch({
        url: '/rest/token/login',
        method: 'post',
        data: Qs.stringify({ username: username, password: password })
      })
    }
    
    export function logout () {
      return fetch({
        url: '/rest/token/logout',
        method: 'post'
      })
    }
    
    
    actions: {
        // 登录
        Login({commit}, userInfo) {
          const username = userInfo.username.trim()
          return new Promise((resolve, reject) => {
            login(username, userInfo.password)
              .then(response => {
                const data = response.data
                setToken(data.token)
                setID(data.JSESSIONID)
                setAccount(data.account)
                setAccountId(data.accountId)
                commit('SET_TOKEN', data.token)
                commit('SET_JSESSIONID', data.JSESSIONID)
                commit('SET_ACCOUNT', data.account)
                commit('SET_ACCOUNT_ID', data.accountId)
                resolve()
              }).catch(error => {
                reject(error)
             })
          })
        },
    
    2、路由层

    简介:根据后台权限动态配置router。项目启动时,从后台获取相应的router数据,然后拼接组装新的router。利用生命钩子函数beforeEach、afterEach处理

    目录参考


    import工具方法,方便router引入component

    module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+
    

    工具使用方法

    const _import = require('./_import_component')
    
    Vue.use(Router)
    
    /**
     * hidden: true                   如果设置 `hidden:true` 将不在侧边栏上显示(默认是false)
     * alwaysShow: true               如果设置 true, 将一直显示, 不管子菜单的个数
     *                                如果未设置, 只有多于1个子菜单时才变为嵌套模式,否则不显示
     * redirect: noredirect           如果设置 `redirect:noredirect` 将不在面包屑导航中显示
     * name:'router-name'             该名称将用来设置 <keep-alive> (必需设置!!!)
     * meta : {
        roles: ['admin','editor']    根据角色控制页面权限 (可以设置多个角色)
        title: 'title'               该名称用来设置子菜单和面包屑导航
        icon: 'svg-name'             侧边栏上的小图标,
        noCache: true                页面是否缓存,默认为false,缓存
      }
     **/
    
    // 不需要权限的路由表
    export const constantRouterMap = [
      {path: '/login', component: _import('/login/index'), hidden: true},
    ]
    

    nprogress 进度加载条
    参考代码:
    启动加载router数据代码

    import router from './router'
    import store from './store'
    import NProgress from 'nprogress' // Progress 进度条
    import 'nprogress/nprogress.css'// Progress 进度条样式
    import {getToken} from '@/utils/auth' // 验权
    
    const whiteList = ['/login']
    router.beforeEach((to, from, next) => {
      NProgress.start()
      if (getToken() || store.getters.token) {
        if (to.path === '/login') {
          next({path: '/'})
        } else {
          if (store.getters.roles.length === 0) {
            store.dispatch('RoleAuth')
              .then(response => {
                const rowRouter = response.data.list
                store.dispatch('GenerateRoutes', {rowRouter}).then(() => {
                  router.addRoutes(store.getters.addRouters)
                  next({...to})
                })
              })
          } else {
            next()
          }
        }
      } else {
        if (whiteList.indexOf(to.path) !== -1) {
          next()
        } else {
          next('/login')
          NProgress.done()
        }
      }
    })
    
    router.afterEach(() => {
      NProgress.done() // 结束Progress
    })
    

    router组装代码

    import { constantRouterMap } from '@/router/index'
    const _import = require('@/router/_import_component')
    const permission = {
      state: {
        routers: constantRouterMap,
        addRouters: []
      },
      mutations: {
        SET_ROUTERS: (state, routers) => {
          var last = [{ path: '*', redirect: '/404', hidden: true }]
          var addup = routers.concat(last)
          state.addRouters = addup
          state.routers = constantRouterMap.concat(addup)
        }
      },
      actions: {
        GenerateRoutes ({ commit }, data) {
          return new Promise(resolve => {
            var userRoutes = data.rowRouter
            var accessedRouters = []
    
            userRoutes.forEach((item) => {
              // 遍历第一层路由
              let userRoutesItem = {}
              var path = item.url
              userRoutesItem.path = item.url
              userRoutesItem.name = item.name
              userRoutesItem.icon = item.meta.icon
              userRoutesItem.noDropdown = item.noDropdown
              userRoutesItem.component = (resolve) => require(['@/views/layout/Layout'], resolve)
    
              // 第一层路由是否有子路由
              if (item.children) {
                let childrenRoute = []
                var firstChild = item.children
    
                userRoutesItem.redirect = path + '/' + firstChild[0].url
    
                // 遍历第一层路由的子路由
                firstChild.forEach((childone) => {
                  let childrenRouteItem = {}
                  childrenRouteItem.path = childone.url
                  childrenRouteItem.name = childone.name
                  if(childone.meta){
                    childrenRouteItem.btnauth = childone.meta.edit
                  }
                  var url = childone.url
                  childrenRouteItem.component = _import(path + '/' + url)
    
                  // 第一层路由子路由是否有子路由
                  if (childone.children) {
                    let childtwoRoute = []
                    var secondChild = childone.children
    
                    // 遍历第一层路由子路由的子路由
                    secondChild.forEach((childtwo) => {
                      let childtwoRouteItem = {}
                      childtwoRouteItem.path = childtwo.url
                      childtwoRouteItem.name = childtwo.name
                      childtwoRouteItem.hidden = childtwo.hidden
                      var lastPath = childtwo.url
                      childtwoRouteItem.component = _import(path + '/' + lastPath)
                      childrenRoute.push(childtwoRouteItem)
                    })
                  }
                  childrenRoute.push(childrenRouteItem)
                })
                userRoutesItem.children = childrenRoute
              } else {
                let childrenRoute = []
                let childrenRouteItem = {}
                userRoutesItem.redirect = item.url + '/index'
                childrenRouteItem.path = 'index'
                childrenRouteItem.component = _import(path + '/index')
                childrenRoute.push(childrenRouteItem)
                userRoutesItem.children = childrenRoute
              }
              accessedRouters.push(userRoutesItem)
            })
            commit('SET_ROUTERS', accessedRouters)
            resolve()
          })
        }
      }
    }
    
    export default permission
    
    
    3、状态处理层

    简介:中心index引入各个文件的store处理,分开处理
    目录:


    image.png

    参考代码:
    index

    import Vue from 'vue'
    import Vuex from 'vuex'
    import app from './modules/app'
    import user from './modules/user'
    import permission from './modules/permission'
    import getters from './getters'
    import 'babel-polyfill'
    
    Vue.use(Vuex)
    
    const store = new Vuex.Store({
      modules: {
        app,
        user,
        permission
      },
      getters
    })
    
    export default store
    
    

    单个实例

    import {login, logout} from '@/api/login'
    import {getRoleAuth, getUserRole} from '@/api/role'
    import {
      getToken,
      setToken,
      removeToken,
      getID,
      setID,
      removeID,
      getAccount,
      setAccount,
      removeAccount,
      getAccountId,
      setAccountId
    } from '@/utils/auth'
    
    const user = {
      state: {
        token: getToken(),
        JSESSIONID: getID(),
        account: getAccount(),
        accountId:getAccountId(),
        name: '',
        avatar: '',
        roles: []
      },
    
      mutations: {
        SET_TOKEN: (state, token) => {
          state.token = token
        },
        SET_JSESSIONID: (state, JSESSIONID) => {
          state.JSESSIONID = JSESSIONID
        },
        SET_ACCOUNT: (state, account) => {
          state.account = account
        },
        SET_ACCOUNT_ID: (state, accountId) => {
          state.accountId = accountId
        },
        SET_NAME: (state, name) => {
          state.name = name
        },
        SET_AVATAR: (state, avatar) => {
          state.avatar = avatar
        },
        SET_ROLES: (state, roles) => {
          state.roles = roles
        }
      },
    
      actions: {
        // 登录
        Login({commit}, userInfo) {
          const username = userInfo.username.trim()
          return new Promise((resolve, reject) => {
            login(username, userInfo.password)
              .then(response => {
                const data = response.data
                setToken(data.token)
                setID(data.JSESSIONID)
                setAccount(data.account)
                setAccountId(data.accountId)
                commit('SET_TOKEN', data.token)
                commit('SET_JSESSIONID', data.JSESSIONID)
                commit('SET_ACCOUNT', data.account)
                commit('SET_ACCOUNT_ID', data.accountId)
                resolve()
              }).catch(error => {
                reject(error)
             })
          })
        },
    
        // 获取/更新用户信息
        RoleAuth({commit}) {
          return new Promise((resolve, reject) => {
            getUserRole()
              .then(response => {
                let userRole = response.data
                commit('SET_ROLES', userRole.role)
                commit('SET_NAME', userRole.name)
                commit('SET_AVATAR', userRole.avatar)
                return resolve(getRoleAuth());
              })
          })
        },
    
        // 登出
        LogOut({commit, state}) {
          return new Promise((resolve, reject) => {
            logout().then(() => {
              commit('SET_TOKEN', '')
              // commit('SET_ROLES', [])
              removeToken()
              removeID()
              removeAccount()
              resolve()
            }).catch(error => {
              reject(error)
            })
          })
        },
    
        // 前端 登出
        FedLogOut({commit}) {
          return new Promise(resolve => {
            commit('SET_TOKEN', '')
            removeToken()
            resolve()
          })
        },
      }
    
    }
    
    export default user
    
    
    4、工具包

    简介:一般存放请求类,校验的正则工具,时间处理等等工具方法

    5、打包配置

    分为开发配置和上线配置,参考
    打包js

    'use strict'
    const utils = require('./utils')
    const webpack = require('webpack')
    const config = require('../config')
    const merge = require('webpack-merge')
    const path = require('path')
    const baseWebpackConfig = require('./webpack.base.conf')
    const CopyWebpackPlugin = require('copy-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
    const portfinder = require('portfinder')
    
    const HOST = process.env.HOST
    const PORT = process.env.PORT && Number(process.env.PORT)
    
    function resolve (dir) {
      return path.join(__dirname, '..', dir)
    }
    
    const devWebpackConfig = merge(baseWebpackConfig, {
      module: {
        rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
      },
      // cheap-module-eval-source-map is faster for development
      devtool: config.dev.devtool,
    
      // these devServer options should be customized in /config/index.js
      devServer: {
        clientLogLevel: 'warning',
        historyApiFallback: {
          rewrites: [
            { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
          ],
        },
        hot: true,
        contentBase: false, // since we use CopyWebpackPlugin.
        compress: true,
        host: HOST || config.dev.host,
        port: PORT || config.dev.port,
        open: config.dev.autoOpenBrowser,
        overlay: config.dev.errorOverlay
          ? { warnings: false, errors: true }
          : false,
        publicPath: config.dev.assetsPublicPath,
        proxy: config.dev.proxyTable,
        quiet: true, // necessary for FriendlyErrorsPlugin
        watchOptions: {
          poll: config.dev.poll,
        }
      },
      plugins: [
        new webpack.DefinePlugin({
          'process.env': require('../config/dev.env')
        }),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
        new webpack.NoEmitOnErrorsPlugin(),
        // https://github.com/ampedandwired/html-webpack-plugin
        new HtmlWebpackPlugin({
          filename: 'index.html',
          template: 'index.html',
          inject: true,
          favicon: resolve('favicon.ico')
        }),
        // copy custom static assets
        new CopyWebpackPlugin([
          {
            from: path.resolve(__dirname, '../static'),
            to: config.dev.assetsSubDirectory,
            ignore: ['.*']
          }
        ])
      ]
    })
    
    module.exports = new Promise((resolve, reject) => {
      portfinder.basePort = process.env.PORT || config.dev.port
      portfinder.getPort((err, port) => {
        if (err) {
          reject(err)
        } else {
          // publish the new Port, necessary for e2e tests
          process.env.PORT = port
          // add port to devServer config
          devWebpackConfig.devServer.port = port
    
          // Add FriendlyErrorsPlugin
          devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
            compilationSuccessInfo: {
              messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
            },
            onErrors: config.dev.notifyOnErrors
            ? utils.createNotifierCallback()
            : undefined
          }))
    
          resolve(devWebpackConfig)
        }
      })
    })
    
    

    打包配置

    'use strict'
    // Template version: 1.3.1
    // see http://vuejs-templates.github.io/webpack for documentation.
    
    const path = require('path')
    
    module.exports = {
      dev: {
    
        // Paths
        assetsSubDirectory: 'static',
        assetsPublicPath: '/',
        proxyTable: {
          // 真实数据接口代理
          '/rest': {
            // target: 'http://192.168.1.53:8080',
            target: 'http://192.168.3.47:8080',
            changeOrigin: true,
            pathRewrite: {
              '^/rest': ''
            }
          }
    
          // Mock数据接口代理:Easy Mock
          // '/rest': {
          //   target: 'https://easy-mock.com/mock/5ac1dd803d15d57d988308ab/dam/rest',
          //   changeOrigin: true,
          //   pathRewrite: {
          //     '^/rest': ''
          //   }
          // }
        },
    
        // Various Dev Server settings
        // host: '192.168.1.53', // can be overwritten by process.env.HOST
        host: '192.168.3.47',
        port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
        autoOpenBrowser: true,
        errorOverlay: true,
        notifyOnErrors: true,
        poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
    
        // Use Eslint Loader?
        // If true, your code will be linted during bundling and
        // linting errors and warnings will be shown in the console.
        useEslint: false,
        // If true, eslint errors and warnings will also be shown in the error overlay
        // in the browser.
        showEslintErrorsInOverlay: false,
    
        /**
         * Source Maps
         */
    
        // https://webpack.js.org/configuration/devtool/#development
        devtool: 'cheap-module-eval-source-map',
    
        // If you have problems debugging vue-files in devtools,
        // set this to false - it *may* help
        // https://vue-loader.vuejs.org/en/options.html#cachebusting
        cacheBusting: true,
    
        cssSourceMap: true
      },
    
      build: {
        // Template for index.html
        index: path.resolve(__dirname, '../dist/index.html'),
    
        // Paths
        assetsRoot: path.resolve(__dirname, '../dist'),
        assetsSubDirectory: 'static',
        assetsPublicPath: '/',
    
        /**
         * Source Maps
         */
    
        productionSourceMap: true,
        // https://webpack.js.org/configuration/devtool/#production
        devtool: '#source-map',
    
        // Gzip off by default as many popular static hosts such as
        // Surge or Netlify already gzip all static assets for you.
        // Before setting to `true`, make sure to:
        // npm install --save-dev compression-webpack-plugin
        productionGzip: false,
        productionGzipExtensions: ['js', 'css'],
    
        // Run the build command with an extra argument to
        // View the bundle analyzer report after build finishes:
        // `npm run build --report`
        // Set to `true` or `false` to always turn it on or off
        bundleAnalyzerReport: process.env.npm_config_report
      }
    }
    
    

    相关文章

      网友评论

        本文标题:vue架构方案分析记录

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