VUE目录架构概述
- mock mock可以不需要后台,自动拦截ajax返回测试数据
- public 公共目录
- src
api 用于存放网络请求文件的目录
index.ts
xxx目录
assets 存放静态文件的目录
components 存放自定义组件的目录
filter 过滤器的使用(例如时间data格式化)
index.ts
icons 图标库引入
svg
index.ts
lang 语言包引入(用于项目中多语言切换)
en.js
zh.js
layout 每个页面的框架
router 路由设置
index.ts
modules 可以减少router下index整体的大小
store vuex的目录
modules 相应的文件存放
getters 获取vuex变量get方式
index.ts
utils 工具目录
views 页面目录
App.vue
main.ts
permisson.ts 权限文件
- .env.development 本地的环境变量
- .env.production 线上的环境变量
- vue.config.js 配置整体的vue项目
- package.json 所有下载的依赖统一管理文件
- README.md 项目介绍文档(一般上传源码管理器使用)
第一步、程序运行入口
VUE运行三件套
一、代码入口
main.js 真个VUE前端代码的入口,目的是初始化一个VUE实例,在实例中用render函数,将App.VUE组件渲染成一个DOM节点(视图);
render: h => h(App) //等价于render (h) { return h(App); }
h 函数就是createElement,是 Vue.js 里面的 函数,这个函数的作用就是生成一个 VNode节点,render 函数得到这个 VNode 节点之后,返回给 Vue.js 的 mount 函数,渲染成真实 DOM 节点,并挂载到根节点上。
注:createElement 函数是用来生成 HTML DOM 元素的,也就是上文中的 generate HTML structures,也就是 Hyperscript,这样作者才把 createElement 简写成 h。
二、App.VUE
app.vue是vue页面资源的首加载项,是主组件,页面入口文件 ,所有页面都是在App.vue下进行切换的。也是整个项目的关键,app.vue负责构建定义及页面组件归集。
归集的概念可以理解成一个根节点、一个载体,后续所有的页面都需要这个载体来承载,如下图所示:

三、permission.js
文件位置在项目目录/src/permission.js文件里,文件的功能用于路由鉴权计算(个人理解:通过业务逻辑实现路由调配)。
鉴权流程如下:

permission.js 鉴权计算业务逻辑实现(主要在router.beforeEach这个方法中)如下:
// 引入vue
import router from './router'
// 引入vuex仓库
import store from './store'
// 引入element-ui的提示组件
import { Message } from 'element-ui'
// 引入进度条、进度条风格
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
// 引入从cookie中获取 Token工具,登录后,token在cookie中
import { getToken } from '@/utils/auth'
// 引入从 获取title工具,用于切换的时候,titile变化
import getPageTitle from '@/utils/get-page-title'
// 配置进度条
NProgress.configure({ showSpinner: false })
// 配置白名单:登录页面(不需要登录即可访问)
const whiteList = ['/login', '/auth-redirect']
// 全局前置路由守卫,核心逻辑都在守卫中
router.beforeEach(async(to, from, next) => {
// 开始进度条
NProgress.start()
//设置页面标题
document.title = getPageTitle(to.meta.title)
// 从cookie中获取token
const hasToken = getToken()
// 如果有token 说明已经登录了
if (hasToken) {
if (to.path === '/login') {
// 如果已经登录,又访问了登录页
next({ path: '/' })//定向放行到后台首页面
NProgress.done() //进度条结束
} else {//你登录了,你去的除了登录页之外的页面
// 在vuex中的仓库store中看你的权限(存在,且不为空)
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) { //权限roles存在,且不为空
next() //放行
} else { //如果没有角色信息,比如刷新了浏览器
try {
//用vuex发起获取用户信息请求
const { roles } = await store.dispatch('user/getInfo')
// 用获取到的用户信息中的权限,传入VUEX方法,生成路由规则
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// console.log("accessRoutes",accessRoutes)
// 将生成的当前用户的路由规则,添加到路由器,addRoutes方法,在user.js中
router.addRoutes(accessRoutes)
// 放行
next({ ...to, replace: true })
} catch (error) {
// 获取权限过程异常,就清除登录状态
await store.dispatch('user/resetToken')
// 提示错误
Message.error(error || 'Has Error')
// 跳转到登录页面
next(`/login?redirect=${to.path}`)
NProgress.done()//进度条结束
}
}
}
} else {//如果没有token
// 你访问的路径是不是白名单路径
if (whiteList.indexOf(to.path) !== -1) {
// 是白名单,就放行
next()
} else {
// 如果不在白名单,就跳转登录页
next(`/login?redirect=${to.path}`)
NProgress.done()//进度条结束
}
}
})
// 全局后置路由守卫,就干了一件事,结束进度条
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
const accessRoutes = await store.dispatch('permission/generateRoutes', data) //作用是根据角色信息,重新调配router/index.js内部的动态路由。
router/index.js 实现动态路由部分,重定向实际路由
第二步、编码进行时
一、layout页面布局
layout布局页面会在router/index.js中进行加载,是一种将页面布局抽象为可复用组件的方式,以便在多个页面之间共享布局逻辑。可通过修改layout不同的组件实现公共框架的客制化

二、store仓库
store会有以下几种属性

可在store/modules目录下创建***.js文件,文件定义并实现需要存储的数据信息,user.js代码如下:
import { login, logout, get_user_info } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import router, { resetRouter } from '@/router'
const state = { //定义要store的数据接口及,数据初始化
token: getToken(),
name: '',
avatar: '',
introduction: '',
roles: '',
language: 'zh-tw',
status: '',
}
const mutations = { //定义state数据的修改操作
SET_TOKEN: (state, token) => {
state.token = token
},
SET_INTRODUCTION: (state, introduction) => {
state.introduction = introduction
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_ROLES: (state, roles) => {
state.roles = roles
},
SET_LANGUAGE: (state, language) => {
state.language = language
},
SET_STATUS: (state, status) => {
state.status = status
},
}
const actions = { //提交状态,调用mutations 方法对数据进行操作
// user login
login({ commit }, userInfo) { //store释放出来的方法,供登录界面时调用,与下面login({ username:.....不是一个概念
const { username, password } = userInfo //userInfo 前端登录界面输入的参数
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => { //调用的API接口
const { data } = response
commit('SET_TOKEN', data.token) //调用mutations 方法 将token存在state数据结构中,页面刷新state数据结构会清空
localStorage.setItem('language', data.lang) //调用浏览器缓存方法,进行数据暂存,刷新页面数据扔保留
localStorage.setItem('roles', data.role)
localStorage.setItem('status', data.status)
localStorage.setItem('avatar', data.avatar)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
setStatus({ commit }, status) {
localStorage.setItem('status', status)
commit('SET_STATUS', status)
},
// get user info
getInfo({ commit, state }) { //暂存数据信息方法,至于如何调用这些方法,下面会介绍
return new Promise((resolve, reject) => {
get_user_info().then(response => {
var data = {
lang : response.data.lang,
roles : response.data.role+'',
status : response.data.status+''
}
commit('SET_LANGUAGE', data.language)
commit('SET_ROLES', data.roles)
commit('SET_STATUS', data.status)
resolve(data)
}).catch(error => {
reject(error)
})
})
},
export default {
namespaced: true,
state,
mutations,
actions
}
将store的数据暴露出来共其他地方使用
getter.js文件,目的是获取state中的数据,但是不会改变数据
const getters = {
sidebar: state => state.app.sidebar,
size: state => state.app.size,
device: state => state.app.device,
visitedViews: state => state.tagsView.visitedViews,
cachedViews: state => state.tagsView.cachedViews,
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name,
introduction: state => state.user.introduction,
roles: state => state.user.roles,
permission_routes: state => state.permission.routes,
errorLogs: state => state.errorLog.logs,
language: state => state.user.language,
status: state => state.user.status,
}
export default getters
index.js文件定义vuex.store
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
Vue.use(Vuex)
const modulesFiles = require.context('./modules', true, /\.js$/)
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
const value = modulesFiles(modulePath)
modules[moduleName] = value.default
return modules
}, {})
const store = new Vuex.Store({
modules,
getters
})
export default store
如果要使用暂存数据则需在页面中加入下面代码:
import { mapGetters } from 'vuex'
computed: {
...mapGetters([
'sidebar',
'avatar',
'device',
'roles',
'status',
])
},
//使用时直接this.status 即可拿到status的值
调用store的暂存数据的方法:
this.$store.dispatch('user/login', this.loginForm) //调用store暴露出来的login方法
const data = await store.dispatch('user/getInfo') //调用store暴露出来的getInfo方法
三、utils拦截器
可以自定义一些方法,然后再页面中加载调用
//如utils/auth.js文件中定义
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
//其他文件调用
import { getToken, setToken, removeToken } from '@/utils/auth'
request 拦截器文件
在API接口文件中加入 import request from '@/utils/request' ,实现请求返回数据拦截
request.js代码实现:
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
timeout: 10000 // request timeout
})
service.interceptors.request.use(
config => {
if (window.maQuickConfig.baseURL) {
config.baseURL = window.maQuickConfig.baseURL
}
config.headers['Content-Type'] = 'application/json;charset=UTF-8'
if (store.getters.token) {
config.headers['Authorization'] = getToken()
config.headers['cdp-language'] = store.getters.language || 'zh-tw'
}
return config
},
error => {
console.log(error) // for debug
return Promise.reject(error)
}
)
service.interceptors.response.use(
response => { //处理请求返回数据,针对不同的报错信息,进行不同的处理机制
const res = response.data
if (res.code !== 0) { //response信息 code如果不为0(即有报错)则进行如下处理
Message({
message: res.msg || 'Error',
type: 'error',
duration: 5 * 1000
})
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
if(res.msg.includes("token")){ //如果错误信息中包含“token”字样,则做如下处理
store.dispatch('user/resetToken').then(() => {
location.reload() //页面刷新
})
}
return Promise.reject(new Error(res.msg || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
四、lang国际化实现
src/components/LangSelect/index.vue 修改国际化代码配置
<template>
<el-dropdown trigger="click" class="international" @command="handleSetLanguage">
<div>
<img class-name="international-icon" v-if="language==='en'" src="../../image/en.png" style="padding-top: 12px">
<img class-name="international-icon" v-else src="../../image/cn.png" style="padding-top: 12px">
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :disabled="language==='zh-cn'" command="zh-cn">
中文简体
</el-dropdown-item>
<el-dropdown-item :disabled="language==='zh-tw'" command="zh-tw">
中文繁體
</el-dropdown-item>
<el-dropdown-item :disabled="language==='en'" command="en">
English
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
import {user_lang} from "@/api/user";
export default {
computed: {
language() {
return this.$store.getters.language //从store中读出language的配置信息,getInfo会拿到lang信息
},
token() {
return this.$store.getters.token ////从store中读出language的配置信息
}
},
methods: {
handleSetLanguage(lang) { //lang: zh-cn zh-tw en 三个参数
if (this.token) {
user_lang({ //调用语言切换接口
lang: lang
}).then(response => {
this.$message.success(response.msg)
this.$i18n.locale = lang
this.$store.dispatch('user/setLanguage', lang)
localStorage.setItem('language', lang)
location.reload()
})
}else{
this.$i18n.locale = lang
this.$store.dispatch('user/setLanguage', lang)
localStorage.setItem('language', lang)
location.reload()
}
}
}
}
</script>
|
src/lang/index.js 国际化入口文件 |
五、views页面编码实现
前端页面编码在src/views/application路径下进行

六、配置文件实现
根目录下vue.config.js文件可进行IP配置,同时也是实现打开mock数据开关
IP配置

通过修改下面数据,可实现是调用mock数据还是IP服务数据

第三步、mock数据模拟
mock目录application-data.js文件产生模拟数据
const Mock = require('mockjs')
let application_list = []
const application_total = Mock.Random.integer(10, 20);
for (var i = 0; i < application_total; i++) {
application_list.push({
id: Mock.Random.increment(),
name: Mock.Random.ctitle(2, 10),
brief: Mock.Random.cparagraph(2, 50),
type: Mock.Random.pick([0, 1]),
status: Mock.Random.pick([3, 4]),
publisherName: Mock.Random.cname(),
publisherNo: Mock.Random.pick(['F', 'H']) + Mock.Random.integer(1000000, 9999999),
publishAt: Mock.Random.date(),
department: Mock.Random.pick(['HWTE', 'QT', 'Sub', 'SW', 'PD']),
version: 'V' + Mock.Random.integer(0, 10),
coverUrl: "@image('200x100', '@color', '#FFF', 'png', '!')",
labels:
/* {
id: Mock.Random.increment(), */
Mock.Random.ctitle(2, 10)
})
}
module.exports = {
application_list,
}
application.js文件实现数据与接口的绑定
const Mock = require('mockjs')
const application_data = require('./application-data.js')
//应用列表
const tool_list = application_data.application_list || []
//应用详情
const tool_detail = application_data.application_detail || []
module.exports = [
//获取应用列表
{
url: '/aps/tool/query', //绑定接口,页面访问该接口可直接获取mock数据
type: 'post',
response: config => { //分页处理
let { page = 1, pageSize = 10 } = config.body
page = page < 1 ? 1 : page
pageSize = pageSize < 1 ? 1 : pageSize
const mock_list = tool_list
const page_list = mock_list.filter((item, index) => index < pageSize * page && index >= pageSize * (page - 1))
return {
code: 0,
data: {
pages: parseInt(pageSize),
count: mock_list.length,
page: page,
pageSize: pageSize,
list: page_list
}
}
}
},
//获取应用详情
{
url: '/aps/tool/details',
type: 'post',
response: _ => {
return {
code: 0,
msg: '',
data: tool_detail //tool_detail mock模拟出来的数据
}
}
},
]
mock index.js文件配置
const Mock = require('mockjs')
const { param2Obj } = require('./utils')
const application = require('./application')
const mocks = [
...application,
]
function mockXHR() {
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
Mock.XHR.prototype.send = function() {
if (this.custom.xhr) {
this.custom.xhr.withCredentials = this.withCredentials || false
if (this.responseType) {
this.custom.xhr.responseType = this.responseType
}
}
this.proxy_send(...arguments)
}
function XHR2ExpressReqWrap(respond) {
return function(options) {
let result = null
if (respond instanceof Function) {
const { body, type, url } = options
result = respond({
method: type,
body: JSON.parse(body),
query: param2Obj(url)
})
} else {
result = respond
}
return Mock.mock(result)
}
}
for (const i of mocks) {
Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
}
}
module.exports = {
mocks,
mockXHR
}
873

被折叠的 条评论
为什么被折叠?



