vue-admin-template是在码云平台(gitee.com)上,搜索vue admin关键字,出来的结果中,相对说明算是比较多的开源代码,既有Demo的演示网站(https://panjiachen.gitee.io/vue-admin-template),也有系列教程帖子,不过作为一个仅在vue官网(https://cn.vuejs.org/)粗看了一遍教程的Vue小白,在下载了源码之后,即便是对照着admin的手把手系列教程,还是看的一头雾水,里面有太多的基础概念缺失,最终在慕课网,学习了vue的一些基础入门知识,基本大多数免费课程(vuex基础入门,vue-cli全集,vue2.5入门,3小时速成 Vue2.x 核心技术,axios在vue中的使用),对vue的脚手架vue-cli,路由插件vue-route,交互插件axios,前端数据插件vuex,模拟接口mockjs,都有了一些基本概念后(这些也都在之前的学习笔记中有说明),才总算是看懂能够开始看懂一些vue-admin-template的代码了。
有了这些基础后,打算开始学习花裤衩大神的vue-adminplat-template项目,我并不打算通过git克隆下来整个源码后,通过npm install的方式来学习,这种方式感觉适合直接拿了使用,不太适合学习。我打算先通过vue-cli脚手架来建立一个空白项目,然后再逐步的把vue-admin-template中的源码,一步一步的复制到项目中去。
vue create -n vue-admin
不同于初次创建项目,我们为了了解Vue是什么,我们尽可能选择最少的插件,避免更多的困扰,来帮助我们更直观的了解Vue本身,而现在我们已经对Vue,以及那些常用的插件有了一定的了解,现在是为了一个可运行的项目,而不是在当初简单的demo,我们有必须要对脚手架提供的插件有一个基础的了解,并有选择的安装。
Babel:这是一个 JavaScript 转码器,当我们使用新的语法时,旧版本的浏览器可能就无法支持这种新的语法,通过 Babel,我们就可以添加不同的转换规则,从而就可以自动的将新版本的语法糖转换成传统的 JavaScript 语法。
TypeScript:它提供了一些 JavaScript 不支持的强语言特性,例如,类、接口、参数类型约束等等,它使 JavaScript 写起来更像我们的 C# 或是 Java 这种强类型语言,当然最终还是会编译成 js 文件从而让浏览器识别出。
PWA:渐进式的 Web 应用,主要是利用提供的标准化框架,在网页应用中实现和原生应用相近的用户体验,让用户以为自己正在使用的是原生应用,微信的小程序其实就可以看成是一种 PWA 应用的载体。
Router:这个大家应该很熟悉了,在前面的文章中我们也有介绍过,是 Vue 官方的路由管理组件。
Vuex:一个 Vue.js 中的状态管理模式,这里的状态可以简单理解为数据。因为在使用 Vue 的开发中,我们会编写各种组件,有些时候,多个组件之间需要共享相同的数据,以及,各个组件之间的数据传递也比较复杂,所以我们需要一个集中式的状态管理器从而更好的管理数据,方便组件之间的通信。
CSS Pre-processors:CSS 的预处理器,可以让我们以一种编程的方式来写 CSS 文件,当然最终它们都会被编译器编译成标准 css 文件。
Linter / Foramtter:代码格式检查和格式化工具,主要是为了让我们的项目中写的代码可以更好的采用统一的风格。
Unit Testing / E2E Testing:单元测试工具
这次,我们选择这几个基本的插件 Babel,Router,Vuex,CSS Pre-processors,Linter / Foramtter。其中CSS Pre-processors提供4个选择,dart-sass,node-sass,less,stylus,这里我暂时选择了dart-sass。而Linter / Foramtter 也提供了4种选择,都是基于ESLint的不同模式,error prevention only(只提醒错误), Airbnb config,Standard config,Prettier。Airbnb是github上Star最多的,而Standard特点是无分号,不支持修改规则,Prettier的特点是一键改变代码风格,而不需要改变开发风格,而error only则是最基础部分,表示只校验代码质量,提出错误部分。毕竟是初学者,先选择error only,后期可以再追加配置的。
我们使用Vscode来进行编辑开发Vue项目,需要在Vscode中追加这几个相关的扩展,Vue 2 Snippets,ESLint,Vuter,AutoFix Toggle,来调整一下Vscode的首选项配置中,打开setting.json文件,追加相关配置到json文件中
// setting.json
{
.....之前原本的配置,新增部分可以直接追加在后面
//打开文件不覆盖
"workbench.editor.enablePreview": false,
"editor.minimap.enabled": false, //关闭快速预览
// "files.autoSave": "afterDelay", //打开自动保存
"editor.formatOnSave": true, //每次保存自动格式化
// 每次保存的时候将代码按eslint格式进行修复
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"javascript.format.insertSpaceBeforeFunctionParenthesis": true, //让函数(名)和后面的括号之间加个空格
// px to rem 扩展配置
"px-to-rem.px-per-rem": 100, //rem适配
//由于prettier不能格式化vue文件template 所以使用js-beautify-html格式化
"vetur.format.defaultFormatter.html": "js-beautify-html",
"vetur.format.defaultFormatterOptions": {
"js-beautify-html": {
"wrap_line_length": 120,
"wrap_attributes": "auto", // "force-aligned"属性强制折行对齐
"end_with_newline": false
},
"prettier": {
"singleQuote": true, // 单引号替代双引号
"semi": false // 结尾不需要;
}
}
"editor.tabSize": 2,
"eslint.run": "onSave",
}
之后,调整eslint在项目中的配置文件.eslintrc.js,调整配置如下:
// .eslintrc.js
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/essential',
'eslint:recommended'
],
// required to lint *.vue files
plugins: ['vue'],
parserOptions: {
parser: 'babel-eslint'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}
追加eslint的ignore文件,在.eslintrc.js文件同目录下创建.eslintignore文件
# .eslintignore
build/*.js
src/assets
public
dist
在vscode中,vuter插件默认的风格是prettier的,而prettier风格在有些地方是会和eslint发生冲突的,我们需要在项目根目录下补充一个.prettierrc的配置文件
// .prettierrc
{
"printWidth": 300,
"singleQuote" : true,
"tabWidth": 2,
"useTabs": false,
"semi": false,
"trailingComma": "none",
"bracketSpacing": true,
"arrowParens": "avoid"
}
如果你不想使用 ESLint 校验(不推荐取消),只要找到 vue.config.js 文件。 进行如下设置 lintOnSave: false
即可。vue.config.js文件在下面会有说明。
完成了ESLint的相关配置后,下一步就需要把几个Vue开发常用的插件包安装进去,因为是学习vue-admin-template项目的源码,我们就尽可能参考源码的package.json文件中的插件包。这里通过手工指令安装,而不是copy整个package.json文件之后用npm install方式,也是为了进一步熟悉package包的安装区别。
> npm i -S axios element-ui normalize.css nprogress js-cookie path-to-regexp --registry=https://registry.npm.taobao.org
> npm i -D autoprefixer babel-plugin-dynamic-import-node chalk connect mockjs runjs serve-static svg-sprite-loader svgo --registry=https://registry.npm.taobao.org
同时参考源码的package.json文件,补充package.json文件中的内容,最终package.json内容如下:
// package.json
{
"name": "adminplat-vue",
"version": "0.1.0",
"description": "A vue admin study Pan's vue-admin-template source ",
"author": "Myron.Maoyz",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.21.0",
"core-js": "^3.6.5",
"element-ui": "^2.14.1",
"js-cookie": "^2.2.1",
"normalize.css": "^8.0.1",
"nprogress": "^0.2.0",
"path-to-regexp": "^6.2.0",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"autoprefixer": "^10.0.2",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.6.3",
"babel-plugin-dynamic-import-node": "^2.3.3",
"chalk": "^4.1.0",
"connect": "^3.7.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"mockjs": "^1.1.0",
"runjs": "^4.4.2",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"serve-static": "^1.14.1",
"svg-sprite-loader": "^5.0.0",
"svgo": "^1.3.2",
"vue-template-compiler": "^2.6.11"
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"engines": {
"node": ">=14.13",
"npm": ">= 6.14.8"
},
"license": "MIT"
}
其中engines下的node和npm可以根据自己搭建的环境来填写,忘记了的话,可以直接通过node -v以及npm -v来获取版本号
// .eslintrc.js
module.exports = {
root: true,
env: {
browser: true,
node: true,
es6: true,
},
extends: [
'plugin:vue/essential',
'eslint:recommended'
],
// required to lint *.vue files
plugins: ['vue'],
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
rules: {
"vue/max-attributes-per-line": 0,
"vue/singleline-html-element-content-newline": "off",
"vue/multiline-html-element-content-newline":"off",
"vue/name-property-casing": ["error", "PascalCase"],
"vue/no-v-html": "off",
'accessor-pairs': 2,
'arrow-spacing': [2, {
'before': true,
'after': true
}],
'block-spacing': [2, 'always'],
'brace-style': [2, '1tbs', {
'allowSingleLine': true
}],
'camelcase': [0, {
'properties': 'always'
}],
'comma-dangle': [2, 'never'],
'comma-spacing': [2, {
'before': false,
'after': true
}],
'comma-style': [2, 'last'],
'constructor-super': 2,
'curly': [2, 'multi-line'],
'dot-location': [2, 'property'],
'eol-last': 2,
'eqeqeq': ["error", "always", {"null": "ignore"}],
'generator-star-spacing': [2, {
'before': true,
'after': true
}],
'handle-callback-err': [2, '^(err|error)$'],
'indent': [2, 2, {
'SwitchCase': 1
}],
'jsx-quotes': [2, 'prefer-single'],
'key-spacing': [2, {
'beforeColon': false,
'afterColon': true
}],
'keyword-spacing': [2, {
'before': true,
'after': true
}],
'new-cap': [2, {
'newIsCap': true,
'capIsNew': false
}],
'new-parens': 2,
'no-array-constructor': 2,
'no-caller': 2,
'no-console': 'off',
'no-class-assign': 2,
'no-cond-assign': 2,
'no-const-assign': 2,
'no-control-regex': 0,
'no-delete-var': 2,
'no-dupe-args': 2,
'no-dupe-class-members': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-empty-pattern': 2,
'no-eval': 2,
'no-ex-assign': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': [2, 'functions'],
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-func-assign': 2,
'no-implied-eval': 2,
'no-inner-declarations': [2, 'functions'],
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-iterator': 2,
'no-label-var': 2,
'no-labels': [2, {
'allowLoop': false,
'allowSwitch': false
}],
'no-lone-blocks': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-multiple-empty-lines': [2, {
'max': 1
}],
'no-native-reassign': 2,
'no-negated-in-lhs': 2,
'no-new-object': 2,
'no-new-require': 2,
'no-new-symbol': 2,
'no-new-wrappers': 2,
'no-obj-calls': 2,
'no-octal': 2,
'no-octal-escape': 2,
'no-path-concat': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-regex-spaces': 2,
'no-return-assign': [2, 'except-parens'],
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-shadow-restricted-names': 2,
'no-spaced-func': 2,
'no-sparse-arrays': 2,
'no-this-before-super': 2,
'no-throw-literal': 2,
'no-trailing-spaces': 2,
'no-undef': 2,
'no-undef-init': 2,
'no-unexpected-multiline': 2,
'no-unmodified-loop-condition': 2,
'no-unneeded-ternary': [2, {
'defaultAssignment': false
}],
'no-unreachable': 2,
'no-unsafe-finally': 2,
'no-unused-vars': [2, {
'vars': 'all',
'args': 'none'
}],
'no-useless-call': 2,
'no-useless-computed-key': 2,
'no-useless-constructor': 2,
'no-useless-escape': 0,
'no-whitespace-before-property': 2,
'no-with': 2,
'one-var': [2, {
'initialized': 'never'
}],
'operator-linebreak': [2, 'after', {
'overrides': {
'?': 'before',
':': 'before'
}
}],
'padded-blocks': [2, 'never'],
'quotes': [2, 'single', {
'avoidEscape': true,
'allowTemplateLiterals': true
}],
'semi': [2, 'never'],
'semi-spacing': [2, {
'before': false,
'after': true
}],
'space-before-blocks': [2, 'always'],
'space-before-function-paren': 0,
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [2, {
'words': true,
'nonwords': false
}],
'spaced-comment': [2, 'always', {
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
}],
'template-curly-spacing': [2, 'never'],
'use-isnan': 2,
'valid-typeof': 2,
'wrap-iife': [2, 'any'],
'yield-star-spacing': [2, 'both'],
'yoda': [2, 'never'],
'prefer-const': 2,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'object-curly-spacing': [2, 'always', {
objectsInObjects: true
}],
'array-bracket-spacing': [2, 'never']
}
}
# .eslintignore
build/*.js
src/assets
public
dist
// vue.config.js
'use strict'
// If your port is set to 80,
// use administrator privileges to execute the command line.
// For example, Mac: sudo npm run
// You can change the port by the following methods:
// port = 8080 npm run dev OR npm run dev --port = 8080
const port = process.env.port || process.env.npm_config_port || 8080 // dev port
// All configuration item explanations can be find in https://cli.vuejs.org/config/
module.exports = {
/**
* You will need to set publicPath if you plan to deploy your site under a sub path,
* for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
* then publicPath should be set to "/bar/".
* In most cases please use '/' !!!
* Detail: https://cli.vuejs.org/config/#publicpath
*/
publicPath: '/',
outputDir: 'dist',
assetsDir: 'static',
lintOnSave: process.env.NODE_ENV === 'development',
productionSourceMap: false,
devServer: {
port: port,
open: true,
overlay: {
warnings: false,
errors: true
},
before: require('./mock/mock-server.js')
}
}
# .env.production
# just a flag
ENV = 'production'
# base api
VUE_APP_BASE_API = '/prod-api'
# .env.development
# just a flag
ENV = 'development'
# base api
VUE_APP_BASE_API = '/dev-api'
除此之外,在template项目源码中,还有一些别的配置文件,但目前我们暂时用不上,等后期学习到了,再补充。
先来看App.vue文件,不同于vue-cli创建App.vue,template源码中App.vue极为精简,仅保留了初始的框架,这也符合后台管理平台的特征,毕竟进入管理页面前的login页面,和内部页面有很大区别,也都很简单。
再来看一下main.js,源码中的main.js加载了不少插件,来作为全局引用,我们先直接把整个代码copy过来,再细细分析。
// main.js
import Vue from 'vue'
import 'normalize.css/normalize.css' // A modern alternative to CSS resets
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import locale from 'element-ui/lib/locale/lang/en' // lang i18n
import '@/styles/index.scss' // global css
import App from './App'
import store from './store'
import router from './router'
import '@/icons' // icon
import '@/permission' // permission control
/**
* If you don't want to use mock-server
* you want to use MockJs for mock api
* you can execute: mockXHR()
*
* Currently MockJs will be used in the production environment,
* please remove it before going online ! ! !
*/
if (process.env.NODE_ENV === 'production') {
const { mockXHR } = require('../mock')
mockXHR()
}
// set ElementUI lang to EN
Vue.use(ElementUI, { locale })
// 如果想要中文版 element-ui,按如下方式声明
// Vue.use(ElementUI)
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
import router from './router' 这是加载路由,我们默认就有这个目录和文件,但是对比template源码中的路由文件,还是需要大幅度改造的
// router/index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
/**
* Note: sub-menu only appear when route children.length >= 1
* Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
*
* hidden: true if set true, item will not show in the sidebar(default is false)
* alwaysShow: true if set true, will always show the root menu
* if not set alwaysShow, when item has more than one children route,
* it will becomes nested mode, otherwise not show the root menu
* redirect: noRedirect if set noRedirect will no redirect in the breadcrumb
* name:'router-name' the name is used by (must set!!!)
* meta : {
roles: ['admin','editor'] control the page roles (you can set multiple roles)
title: 'title' the name show in sidebar and breadcrumb (recommend set)
icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
}
*/
/**
* constantRoutes
* a base page that does not have permission requirements
* all roles can be accessed
*/
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: 'Dashboard', icon: 'dashboard' }
}]
},
// 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true }
]
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
const router = createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
这里先采用template中的路由文件,但是仅保留了login,404以及dashboard这几个文件的路由,其余的暂时先删除,后期再视情况处理,从路由文件中可以看到,包含了布局文件layout目录,我们先把layout目录也copy过来。
import store from './store' 这是加载前端状态管理,虽然vue-cli创建项目的时候,我们选择了Vuex插件,所以已经存在store目录,以及index.js文件,但是文件中基本都是空的构造函数,而template源码中的store则有多个文件,并且内容也都比较多,暂时我们先copy过来,以便项目能够运行起来,打开login页面。
import '@/icons' // icon 这是加载的svg图标目录,去template源码中看了一下对应的icons/index.js文件
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)
代码没几行,不过第二行就需要加载components下的SvgIcon目录,而打开SvgIcon目录下的index.vue文件,发现其中引用了utils/validate.js文件,干脆把components,util这两个目录页全部都copy过来。
import '@/permission' // permission control 这是前段权限控制处理代码,主要也就是引用了util下的几个文件,以及router,store目录。先把permission.js文件都copy过来。
这样,main.js算是改造好了,template源码中src下的components,icons,layout,router,store,styles,utils这几个目录算是全部都copy过来了,剩下的还有api,assets,views这三个目录还没有搬。assets是用来存放静态资源文件的,我们直接把assets中的资源copy过来就行,没太多的讲究。
template项目源码中,大多数采用的是每一个页面都在views下创建了一个对应的目录名,在目录下在创建index.vue作为默认页面组件。个人不太喜欢这种风格,做个小的改动,单组件页面就直接在views下建立对应的vue文件,多组件页面才建立页面目录这一级,而login,dashboard,404都是单组件页面,先删除vue-cli创建的默认hello和about两个vue文件,再创建login.vue,dashboard.vue,404.vue,相关内容就copy自template中对应文件的内容。注意:在调整了views下组件的名称和路径后,也要记得在\router\index.js中,同步修改对应的路由组件路径。下面是对应需要修改后的结果。
// src\router\index.js
.....
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard'),
meta: { title: 'Dashboard', icon: 'dashboard' }
}]
},
// 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true }
]
.....
启动服务器,提示缺少settings.js文件,原来在src\utils\get-page-title.js以及src\store\modules\settings.js这两个文件中,都有引用src\settings.js文件,我们照搬template项目源码中的文件。
// src\settings.js
module.exports = {
title: 'AdminPlat Vue',
/**
* @type {boolean} true | false
* @description Whether fix the header
*/
fixedHeader: false,
/**
* @type {boolean} true | false
* @description Whether show the logo in sidebar
*/
sidebarLogo: false
}
这里,根据我们的项目名称,将title的值做了替换。之后,启动服务,成功显示登录页面。
但是点击login按钮,却提示404错误,通过网页的调试工具,看到console中提示:POST http://localhost:8080/adminplat-vue/user/login 404 (Not Found),应该是api请求连接没有响应。重新再来审查mock相关的代码,发现main.js中,mock的加载有点问题,居然是默认在production的配置下才加载,而默认的vue的serve的启动模式是development,我们这里把if判断直接注释掉后,login按钮就成功跳转到dashboard页面了。
// main.js
.....
// if (process.env.NODE_ENV === 'production') {
const { mockXHR } = require('../mock')
mockXHR()
// }
.....
这里,建议使用chrom浏览器,并安装vue-devtools扩展,这是针对vue开发的调试工具,帮助我们进行相关的页面调试,在里面可以更直观的看到vuex,router的数据值。vue-devtools怎么使用呢?在成功安装后,可以看到上面图片的右上角有一个vue的小图标,绿色状态表示已经启动,没有启动的情况下会是灰色的。启动vue-devtools后,按f12打开chrome的开发者工具页面,我们就会发现会新增一的Vue的标签栏,如下图所示,其中红框标注的就是用来查看vuex和router数据的选项。
参考资源:
https://www.jianshu.com/p/dd07cca0a48e
https://cloud.tencent.com/developer/article/1477063
https://blog.csdn.net/qianxing111/article/details/107617538
https://blog.csdn.net/ljt123456765/article/details/81356792