- 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 项目介绍文档(一般上传源码管理器使用)
const state = {
isRefresh:false //初始化刷新 为false
}
const mutations = {
SET_IS_REFRESH:(state: any, isRefresh: Boolean)=>{
state.isRefresh=isRefresh
}
}
const actions = {
setRefresh: ({ commit }: any, isRefresh: Boolean) => {
commit("SET_IS_REFRESH", isRefresh)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
const getters = {
isRefresh:(state:any)=>state.refresh.isRefresh
}
export default getters
index.ts文件 在main.ts记住引入store
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
Vue.use(Vuex)
// https://webpack.js.org/guides/dependency-management/#requirecontext
const modulesFiles = require.context('./modules', true, /\.ts$/)
const modules = modulesFiles.keys().reduce((modules: any, modulePath) => {
// set './app.js' => 'app'
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
permisson.ts 页面会反复调用 beforeEach,直到调用next()
router.beforeEach(to,from,next) to 代表将要访问的路径 from 当前路径 next用于跳转页面
next({"path":"/"})指向路径后代表重定向 会再次访问beforeEach
import router from './router'
import { getToken, removeToken, setToken } from '@/utils/auth';
import { Dialog } from 'vant';
import store from './store';
const whiteList = ['/login']
const env = process.env.NODE_ENV
if(env === "production") {
// 应将layout中navList的to添加到此
const navList = ['/socialRela', '/childrenStatus', '/work', '/education', '/info']
router.beforeEach(async (to, from, next) => {
const userId = getToken()
if (userId) {
} else {
if (whiteList.indexOf(to.path) != -1) {
next()
} else {
next({ path: '/login' })
}
}
})
}
在路由权限这块,有一个笔记就是对于菜单权限的分配,判断角色是否存在,不存在的话获取角色后通过角色获取动态菜单,代码如下(代码仅供参考)
、、、路由判断代码
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) {
next()
} else {
try {
// get user info
// note: roles must be a object array! such as: ['ROLE_ADMIN'] or ,['ROLE_DEVELOPER','ROLE_EDITOR']
const { roles } = await store.dispatch('user/getInfo')
// generate accessible routes map based on roles
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// dynamically add accessible routes
router.addRoutes(accessRoutes)
// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
next({ ...to, replace: true })
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
、、、路由添加参考
import { asyncRoutes, constantRoutes } from '@/router'
/**
* Use meta.role to determine if the current user has permission
* @param roles
* @param route
*/
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
} else {
return true
}
}
/**
* Filter asynchronous routing tables by recursion
* @param routes asyncRoutes
* @param roles
*/
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
const state = {
routes: [],
addRoutes: []
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
generateRoutes({ commit }, roles) {
return new Promise(resolve => {
let accessedRoutes
if (roles.includes('ADMIN')) {
accessedRoutes = asyncRoutes || []
} else {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
在src目录创建icon目录
icon目录分为svg(目录下是svg图片)、svgo.yml 、index.js
svgo.yml
# replace default config
# multipass: true
# full: true
plugins:
# - name
#
# or:
# - name: false
# - name: true
#
# or:
# - name:
# param1: 1
# param2: 2
- removeAttrs:
attrs:
- 'fill'
- 'fill-rule'
index.js 加载svg目录下文件 ,不需要一次次加载,对于代码解释,收藏里require.context
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg component
// register globally
Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)
在vue.config中
chainWebpack(config) {
config.plugins.delete('preload') // TODO: need test
config.plugins.delete('prefetch') // TODO: need test
// set svg-sprite-loader
config.module
.rule('svg')
.exclude.add(resolve('src/assets/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/assets/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
}
然后全局注册SvgIcon组件
那么组件代码(组件定义完整后,最后记得在main.ts引入icon目录 但不需要vue对象里声明,lang需要)
在src创建lang文件目录
en.js tw.js zh.js
其中一个文件的代码
export default {
route: {
"language":"汉语"
}
}
index.js需要下载vue-i18n
import Vue from 'vue'
import VueI18n from 'vue-i18n'
//import Cookies from 'js-cookie'
import elementEnLocale from 'element-ui/lib/locale/lang/en' // element-ui lang
import elementZhLocale from 'element-ui/lib/locale/lang/zh-CN'// element-ui lang
import elementTwLocale from 'element-ui/lib/locale/lang/zh-TW'// element-ui lang
import enLocale from './en'
import zhLocale from './zh'
import twLocale from './tw'
Vue.use(VueI18n)
const messages = {
en: {
...enLocale,
...elementEnLocale
},
zh: {
...zhLocale,
...elementZhLocale
},
tw: {
...twLocale,
...elementTwLocale
}
}
export function getLanguage() {
//此处代码如果有选择可以加
//const chooseLanguage = Cookies.get('language')
//if (chooseLanguage) return chooseLanguage
// if has not choose language
const language = (navigator.language || navigator.browserLanguage).toLowerCase()
const locales = Object.keys(messages)
for (const locale of locales) {
if (language.indexOf(locale) > -1) {
return locale
}
}
return 'en'
}
const i18n = new VueI18n({
// set locale
// options: en | zh | es
locale: getLanguage(),
// set locale messages
messages
})
export default i18n
在main.ts(由于i18n使用到了elementui)
import i81n from './lang'
Vue.use(Element, {
size: Cookies.get('size') || 'medium', // set element-ui default size
i18n: (key, value) => i18n.t(key, value)
})
new Vue({
i18n,
})
在使用页面中 $t('login.title') 判断页面是否有字段this.$te('route.' + title)
{{ new Date(row.createTime) | parseTime('{y}-{m}-{d} {h}:{i}:{s}') }}
/**
* @param {number} time
* @param {string} option
* @returns {string}
*/
export function formatTime(time, option) {
if (('' + time).length === 10) {
time = parseInt(time) * 1000
} else {
time = +time
}
const d = new Date(time)
const now = Date.now()
const diff = (now - d) / 1000
if (diff < 30) {
return '刚刚'
} else if (diff < 3600) {
// less 1 hour
return Math.ceil(diff / 60) + '分钟前'
} else if (diff < 3600 * 24) {
return Math.ceil(diff / 3600) + '小时前'
} else if (diff < 3600 * 24 * 2) {
return '1天前'
}
if (option) {
return parseTime(time, option)
} else {
return (
d.getMonth() +
1 +
'月' +
d.getDate() +
'日' +
d.getHours() +
'时' +
d.getMinutes() +
'分'
)
}
}
全局注册在main.ts
import * as filters from './filters'
Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key])
})
首先安装axios框架,
api->index.ts文件 (封装axios)
import axios from 'axios'
import { Dialog } from 'vant';
import store from '@/store';
// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
timeout: 8000 // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
//do something before request is sent
if (store.getters.token) {
config.headers['Authorization'] = 'Bearer ' + store.getters.token
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
response => {
const res = response.data
// if the custom code is not 200, it is judged as an error.
if (res.status && res.status !== 200) {
Dialog.alert({
message: res.message ,
title: "提示"
})
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
Dialog.alert({
message: error,
title: "服务器提示"
})
return Promise.reject(error)
}
)
export default service
api下新建多个目录,存放网络请求的方法,如
import request from '@/utils/request'
import qs from 'qs'
export function getUserId(query: any) {
return request({
url: "/consumer/user",
method: 'get',
params: query
})
}
// 新增用户基本信息
export function Add(data: any) {
return request({
url: '/consumer/baseInfo/add',
method: 'post',
data: qs.stringify(data)
})
}
以下axios提交方式Content-type:form-data
在使用axios时,注意到配置选项中包含params和data两者因为params是添加到url的请求字符串中的,用于get请求。 (后台@RequestParam)
而data是添加到请求体(body)中的, 用于post请求;(后台@RequestBody)
使用qs:qs的作用将json用&相连并序列化处理,那么需要字符特殊处理的可以使用qs
mock下的index.ts,将所有mock文件注册,如果确认使用就在主目录下的main.ts引入(require('../mock');)
// 首先引入Mock
const Mock = require('mockjs');
// 设置拦截ajax请求的相应时间
Mock.setup({
timeout: '200-600'
});
let configArray = [];
// 使用webpack的require.context()遍历所有mock文件
const files = require.context('./modules', true, /\.ts$/);
files.keys().forEach((key) => {
configArray = configArray.concat(files(key).default);
});
// 注册所有的mock服务
configArray.forEach((item) => {
for (let [path, target] of Object.entries(item)) {
let protocol = path.split('|');
Mock.mock(new RegExp('^' + protocol[1]), protocol[0], target);
}
});
modules下的xx,例如(具体的mock格式Home · nuysoft/Mock Wiki · GitHub):
let demoList =
{
status: 200,
message: 'success',
'data|10': [{
clientId:'@id',
clientName: '@cname',
contactsPhone: '@email',
}]
}
export default {
'get|/consumer/clientele/queryByAccount': demoList
}
import Vue from 'vue';
import tipVue from './Tip.vue';
//将组件转换成对象
const TipVueConstructor = Vue.extend(tipVue);
const initInstance = () => {
//实例化
instance = new TipVueConstructor({
el: document.createElement('div')
});
};
const TipBox = function (options) {
if (!instance) {
initInstance();
}
instance.action = '';
if (!instance.visible && options) {
document.body.appendChild(instance.$el);
var closeTime = options.closeTime || 1000;
Vue.nextTick(() => {
instance.visible = true;
instance.isTip = options.isTip;//isTip为true显示错误提示小黑框
instance.message = options.message
setTimeout(() => {
TipBox.close();
if (typeof options.func == "function") {
options.func();
}
}, closeTime);
});
}
};
export default TipBox;
main.js或全局
Vue.prototype.$tip = TipBox
调用案例:
this.$tip({
isTip: true,
message: "验证码发送成功",
});