一、采用基础技术
1、基础技术
vue,router(界面跳转),vuex(状态管理)
2、UI框架
element-ui
3、网络请求
axios
二、项目结构分层
build---打包配置的js
conf---配置打包的ip,port等基础参数
src--源代码
api---网络请求数据层
assets---图片静态资源
components--基础组件
router---路由层
store---状态处理层
styles---样式
utils----通用工具
pages(views)---视图层
三、各层次详解
1、网络层
简介:利用axios封装通用的请求类。方法中可以配置请求拦截和返回结果拦截,处理请求权限校验和相关报错的统一处理。另外根据界面分解成api层,利用工具类调用服务端接口返回数据。
目录参考:
部分参考样例代码:
工具类
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' 该名称将用来设置 (必需设置!!!)
* 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处理,分开处理
目录:
参考代码:
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
}
}