vue 快速入門 系列 —— 使用 vue-cli 3 搭建一個項目(上)
其他章節請看:
使用 vue-cli 3 搭建一個項目(上)
前面我們已經學習了一個成熟的腳手架(vue-cli),筆者希望通過這個腳手架快速搭建系統(或項目)。而展開搭建最好的方法是向優秀的項目學習,依葫蘆畫瓢。
這裡通過研究 vue-admin-template 項目,逐一引入 element-ui
、axios
、mock
、iconfont
、nprogress
、權限控制
、布局
、多環境(.env)
、跨域
、vue.config.js
,一步一步打造我們自己的架構。
Tip: vue-element-admin
是一個優秀的後台前端解決方案,把平時用到的一些組件或者經驗分享給大家。而 vue-admin-template
就是它的一個簡易版本。
注:由於篇幅過長,決定將文本拆分為上下兩篇
模板項目 – vue-admin-template
vue-admin-template
以 vue-cli webpack 模板為基礎開發,並引入如下依賴:
element-ui
餓了么出品的 vue pc UI框架axios
一個現在主流並且很好用的請求庫 支持Promisejs-cookie
一個輕量的JavaScript庫來處理cookienormalize.css
格式化cssnprogress
輕量的全局進度條控制vuex
官方狀態管理vue-router
官方路由iconfont
圖標字體- 權限控制
- lint
Tip:vue-cli webpack模板:
- 這個模板是 vue-cli verison 2.* 的主要模板
Vue-cli 3
包含此模板提供的所有功能(以及更多功能)Vue-cli 3
來了,此模板現在被視為已棄用
下載項目並啟動:
> git clone //github.com/PanJiaChen/vue-admin-template.git vue-admin-template
> cd vue-admin-template
vue-admin-template> npm i
vue-admin-template> npm run dev
> [email protected] dev
> vue-cli-service serve
...
創建項目
我們的項目 – myself-vue-admin-template
通過 vue-cli 創建項目
// 項目預設 `[Vue 2] less`, `babel`, `router`, `vuex`, `eslint`
$ vue create myself-vue-admin-template
目錄結構如下:
myself-vue-admin-template
- mode_modules
- public
- favicon.ico
- index.html
- src
- assets
- logo.png
- components
- HelloWorld.vue
- router
- index.js
- store
- index.js
- views
- Aobut.vue
- Home.vue
- App.vue
- mains.js
- .browerslistrc
- .editorconfig
- .eslintrc.js
- .gitignore
- babel.config.js
- package-lock.json
- package.json
- README.md
我們的項目 Vs 模板項目
項目 vue-admin-template
比 myself-vue-admin-template
多了如下目錄和文件,其他都相同:
vue-admin-template
+ build
+ mock
+ src/api
+ src/icons
+ src/layout
+ src/styles
+ src/utils
+ src/permission.js
+ src/settings.js
+ .env.development
+ .env.production
+ .env.staging
+ .travis.yml
+ jest.config.js
+ jsconfig.json
+ postcss.config.js
+ README-zh.md
+ vue.config.js
使用的 @vue/cli
都是 4.x :
// myself-vue-admin-template
"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",
// vue-admin-template
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.4",
"@vue/cli-plugin-eslint": "4.4.4",
"@vue/cli-plugin-unit-jest": "4.4.4",
"@vue/cli-service": "4.4.4",
"@vue/test-utils": "1.0.0-beta.29",
element-ui
模板項目如何使用 element-ui
// package.json
"dependencies": {
"element-ui": "2.13.2",
}
// main.js
// ps: 無關代碼未展示
import Vue from 'vue'
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 App from './App'
// set ElementUI lang to EN
Vue.use(ElementUI, { locale })
// 如果想要中文版 element-ui,按如下方式聲明
// Vue.use(ElementUI)
new Vue({
el: '#app',
render: h => h(App)
})
- 這裡引入 Element 是完整引入,另一種是按需引入
- Element 組件內部默認使用中文,這裡使用了英文
- element 的國際化其實就是對 element 中組件的國際化(查看文件
node_modules/element-ui/lib/locale/lang/en
就清楚了)
- element 的國際化其實就是對 element 中組件的國際化(查看文件
添加 element-ui
思路如下:
- 完整引入
element
- 無需提供翻譯,默認使用中文
- 利用 vue-cli 提供的插件安裝 element-ui
通過 vue-cli 直接安裝
myself-vue-admin-template> vue add vue-cli-plugin-element
📦 Installing vue-cli-plugin-element...
✔ Successfully installed plugin: vue-cli-plugin-element
// 配置
? How do you want to import Element? Fully import
? Do you wish to overwrite Element's SCSS variables? No
? Choose the locale you want to load zh-CN
✔ Successfully invoked generator for plugin: vue-cli-plugin-element
注:也可以使用 vue-cli GUI 的方式安裝插件 vue-cli-plugin-element
接着通過 git status
就能查看 vue-cli 替我們修改的代碼:
myself-vue-admin-template> git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: package-lock.json
modified: package.json
modified: src/App.vue
modified: src/main.js
Untracked files:
(use "git add <file>..." to include in what will be committed)
src/plugins/
no changes added to commit (use "git add" and/or "git commit -a")
核心代碼與模板項目中的相同,只是將 element 的引入封裝到了 plugins/element.js
文件中。
啟動服務,頁面顯示:
...
if Element is successfully added to this project, you'll see an <el-button> below // {1}
...
我們會在行({1})下一行看見一個 element 的按鈕,說明 element-ui 引入成功。
axios
模板項目如何使用 axios
// package.json
"dependencies": {
"axios": "0.18.1",
}
對 axios 進行封裝:
// src/utils/request.js
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
...
},
error => {
...
}
)
// response interceptor
service.interceptors.response.use(
response => {
const res = response.data
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// to re-login
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(() => {
...
})
}
return Promise.reject(new Error(res.message || '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
// api/table.js
import request from '@/utils/request'
export function getList(params) {
return request({
url: '/vue-admin-template/table/list',
method: 'get',
params
})
}
// views/table/index.vue
<script>
import { getList } from '@/api/table'
...
</script>
添加 axios
vue-cli 安裝插件
myself-vue-admin-template> vue add vue-cli-plugin-axios
📦 Installing vue-cli-plugin-axios...
...
✔ Successfully installed plugin: vue-cli-plugin-axios
\myself-vue-admin-template> git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: package-lock.json
modified: package.json
modified: src/main.js
Untracked files:
(use "git add <file>..." to include in what will be committed)
src/plugins/axios.js
其中 axios.js
中的 Plugin
在 vscode 中提示已棄用,所以乾脆把模板項目
中有關 axios
的搬過來
照搬模板項目中的 axios
Tip: 先將 vue-cli 安裝 axios 的代碼還原
myself-vue-admin-template> npm i -D [email protected]
新建 request.js
(來自模板項目 utils/request
,注釋掉和權限相關的代碼):
// utils/request.js
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
// import store from '@/store'
// import { getToken } from '@/utils/auth'
// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
// if (store.getters.token) {
// // let each request carry token
// // ['X-Token'] is a custom headers key
// // please modify it according to the actual situation
// config.headers['X-Token'] = getToken()
// }
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* You can also judge the status by HTTP Status Code
*/
response => {
const res = response.data
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// to re-login
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()
// })
})
}
return Promise.reject(new Error(res.message || '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
Tip: VUE_APP_BASE_API
請查閱本篇的 多環境->base_url
新建 table.js
,定義一個請求:
// api/table.js
import request from '@/utils/request'
export function getList(params) {
return request({
url: '/vue-admin-template/table/list',
method: 'get',
params
})
}
在 About.vue
中引用 api/table.js
:
// views/About.vue
...
<script>
import { getList } from '@/api/table'
export default {
created() {
this.fetchData()
},
methods: {
fetchData() {
getList().then(response => {
console.log('加載數據', response);
})
}
}
}
</script>
Tip:保存代碼可能會遇到如下信息,可以通過配置 lintOnSave
生產構建時禁用 eslint-loader
myself-vue-admin-template\src\views\About.vue
7:1 error More than 1 blank line not allowed no-multiple-empty-lines
12:10 error Missing space before function parentheses space-before-function-paren
16:14 error Missing space before function parentheses space-before-function-paren
18:38 error Extra semicolon semi
20:7 error Block must not be padded by blank lines padded-blocks
✖ 5 problems (5 errors, 0 warnings)
5 errors and 0 warnings potentially fixable with the `--fix` option.
// vue.config.js
module.exports = {
lintOnSave: process.env.NODE_ENV !== 'production'
}
在 App.vue 中引入 About.vue,重啟服務器,發現頁面(About.vue)會報 404 的錯誤,所以接下來我們得引入 mock。
Tip: 請查閱本篇的 添加 mock
小節。
添加完 mock,接着啟動服務,頁面不會再輸出 404 之類的提示,控制台會輸出 mock 中模擬的數據,至此,表明 axios
和 mock
都已生效。
mock
模板項目如何使用 mock
這裡使用的 npm 包是 mockjs
,將需要攔截的請求統一放在 mock 目錄中,最後在 main.js 中引入 mock。
裏面有關於 mock 缺陷的修復,還有 mock-serve.js,有點複雜。
以下是一些核心代碼:
// main.js
/**
* 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()
}
// mock/index.js
const Mock = require('mockjs')
const { param2Obj } = require('./utils')
const user = require('./user')
const table = require('./table')
const mocks = [
...user,
...table
]
// for front mock
// please use it cautiously, it will redefine XMLHttpRequest,
// which will cause many of your third-party libraries to be invalidated(like progress event).
function mockXHR() {
// mock patch
// //github.com/nuysoft/Mock/issues/300
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
...
}
module.exports = {
mocks,
mockXHR
}
// vue.config.js
devServer: {
before: require('./mock/mock-server.js')
},
// api/table.js
import request from '@/utils/request'
export function getList(params) {
return request({
url: '/vue-admin-template/table/list',
method: 'get',
params
})
}
// views/table/index.vue
import { getList } from '@/api/table'
添加 mock
筆者換一種方式,直接通過 vue-cli 插件安裝:
myself-vue-admin-template> vue add vue-cli-plugin-mock
📦 Installing vue-cli-plugin-mock...
✔ Successfully installed plugin: vue-cli-plugin-mock
修改的文件有:
myself-vue-admin-template> git status
modified: package-lock.json
modified: package.json
Untracked files:
(use "git add <file>..." to include in what will be committed)
mock/
根據 vue-cli-plugin-mock
在 npm 中介紹,這裡 mock 有兩種寫法,自動生成的代碼使用寫法一,筆者為了和模板項目中的相同,將 mock/index.js
改為寫法二。
// 寫法一
module.exports = {
'GET /api/user': {
// obj
id: 1,
username: 'kenny',
sex: 6,
},
...
};
// 寫法二
module.exports = [
{
path: '/api/user',
handler: (req, res) => {
return res.json({ username: 'admin', sex: 5 });
},
},
...
];
mock已經安裝完畢,可以直接使用,具體用法請看上面的 添加 axios
小節。
Tip: 可以將 mock 進一步優化,就像模板項目一樣:
- 將 mock 文件分模塊,統一通過
mock/index.js
整合 - mock 只在開發環境下生效
// vue-admin-template/src/main.js
if (process.env.NODE_ENV === 'production') {
const { mockXHR } = require('../mock')
mockXHR()
}
// vue-admin-template/mock/index.js
const Mock = require('mockjs')
const user = require('./user')
const table = require('./table')
const mocks = [
...user,
...table
]
module.exports = {
mocks,
mockXHR
}
iconfont
以前圖標是用圖片,後來出現了雪碧圖,比如將很多小圖標放在一張圖片中,減少請求。在後來項目中甚至不使用本地圖片,而使用font庫,比如 font awesome、iconfont。
模板項目中的登錄頁,使用了4個圖標
// login/index.vue
<svg-icon icon-class="user"/>
<svg-icon icon-class="password" />
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
添加 iconfont
官網下載並初步體驗
去 iconfont 官網選中2個圖標放到購物車中,然後點擊下載代碼,解壓後,雙擊打開 index.html
則會告訴我們如何使用,我們選擇第三種方式(Symbol)。
將 iconfont 加入項目
新建 SvgIcon.vue
:
// src/components/SvgIcon.vue
<template>
<svg class="svg-icon" aria-hidden="true">
<use :xlink:href="iconName"></use>
</svg>
</template>
<script>
export default {
name: 'icon-svg',
props: {
iconClass: {
type: String,
required: true
}
},
computed: {
iconName() {
return `#icon-${this.iconClass}`
}
}
}
</script>
<style>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
將 iconfont.js
放入 src/utils
文件夾中,並修改 main.js
:
...
import './utils/iconfont.js'
//引入svg組件
import IconSvg from '@/components/SvgIcon'
//全局註冊icon-svg
Vue.component('icon-svg', IconSvg)
使用 icon,例如在 About.vue
中:
<template>
<div class="about">
<h1>This is an about page</h1>
<icon-svg icon-class="password" />
<icon-svg icon-class="user" />
</div>
</template>
改造
iconfont.js
內容如下:
!function(e){var t,n,o,c,i,d='<svg><symbol id="icon-password" viewBox="0 0 1024 1024"><path d="M780.8 354.58H66..." ></path></symbol><symbol id="icon-user" viewBox="0 0 1032 1024"><path d="M494.8704..." ></path></symbol></svg>',...
如果還需要添加第三個圖片,就得修改 iconfont.js
文件,而且需要使用哪個 svg 也不直觀,得看代碼才知道。
模板項目中是直接引入 svg 文件,而且也沒有 iconfont.js 文件,相關的包有兩個:
"devDependencies": {
// 創建 SVG sprites
"svg-sprite-loader": "4.1.3",
// 基於Nodejs的SVG矢量圖形文件優化工具
"svgo": "1.2.2",
},
我們也將 iconfont 改成這種方式,首先將之前的引入 iconfont 的代碼去除,然後通過vue-cli 安裝插件 vue-cli-plugin-svg-sprite
:
myself-vue-admin-template> vue add vue-cli-plugin-svg-sprite
📦 Installing vue-cli-plugin-svg-sprite...
...
✔ Successfully installed plugin: vue-cli-plugin-svg-sprite
? Add SVGO-loader to optimize your icons before the sprite is created? Yes
🚀 Invoking generator for vue-cli-plugin-svg-sprite...
📦 Installing additional dependencies...
插件安裝成功後,我們查看改變的文件:
myself-vue-admin-template> git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: package-lock.json
// 得知安裝了三個包:svgo、svgo-loader、vue-cli-plugin-svg-sprite
modified: package.json
modified: vue.config.js
Untracked files:
(use "git add <file>..." to include in what will be committed)
// svg 封裝的組件
src/components/SvgIcon.vue
Tip:vue.config.js 會添加如下內容:
module.exports = {
// 略
chainWebpack: config => {
config.module
.rule('svg-sprite')
.use('svgo-loader')
.loader('svgo-loader')
}
}
接着我們需要全局註冊 svg 組件:
// main.js
import SvgIcon from '@/components/SvgIcon'// svg component
// register globally
Vue.component('svg-icon', SvgIcon)
最後我們得測試 iconfont 是否生效。
先將 svg 文件下載並保存到 assets/icons
目錄,然後修改 About.vue
:
<template>
<div class="about">
<h1>This is an about page</h1>
<svg-icon name="password" />
<svg-icon name="open" />
</div>
</template>
重啟服務,頁面成功顯示兩張 svg 圖片,至此,我們的 iconfont 就引入成功了。
nprogress
在模板項目中,切換試圖,在頁面頂部會有一個進度條的東西,使用的就是 nprogress(Ajax’y 應用程序的細長進度條。受 Google、YouTube 和 Medium 的啟發)。
模板項目:
"dependencies": {
"nprogress": "0.2.0",
}
添加 nprogress
由於 vue-cli ui 搜索不到 nprogress 相應的插件,所以我們只能通過 npm 老老實實的安裝:
$ npm i -D nprogress
接下來使用 nprogress,給 About.vue
添加如下代碼,重啟服務即可看到效果:
// About.vue
<script>
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
// 通過將其設置為 false 來關閉加載微調器。
NProgress.configure({ showSpinner: false }) // NProgress Configuration
export default {
created () {
// Simply call start() and done() to control the progress bar.
NProgress.start();
// 可以嘗試直接調用 NProgress.done() 或者不執行 NProgress.done()
setTimeout(function(){
NProgress.done()
}, 10000)
},
}
</script>
Tip:直接在模板項目中搜索關鍵字 NProgress
就能找到上面的代碼。NProgress.start() 和 NProgress.done() 出現在模板項目中的 permission.js
文件裏面,並且也在路由中。
normalize.css
normalize.css,CSS 重置的現代替代方案
添加 normalize.css
$ npm i -D normalize.css
在入口文件中引入 normalize.css
// main.js
import 'normalize.css/normalize.css' // A modern alternative to CSS resets
重啟服務,body 的 margin
會重置為 0,說明已生效。
js-cookie
js-cookie
,用於處理 cookie,簡單的、輕量級的 JavaScript API。
模板項目如何使用 js-cookie
相關代碼如下:
// package.json
"dependencies": {
"js-cookie": "2.2.0",
},
// src/utils/auth.js
import Cookies from 'js-cookie'
const TokenKey = 'vue_admin_template_token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
添加 js-cookie
$ npm i -D js-cookie
在 About.vue
中使用一下:
// About.vue
<script>
import Cookies from 'js-cookie'
export default {
created () {
Cookies.set('sex', 'man')
alert(Cookies.get('sex'))
},
}
</script>
重啟服務,頁面彈出 man
則說明成功引入。
其他
npm 必須使用 TLS 1.2 or higher
某天運行 npm i
報錯如下:
npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: //github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/
可以查看這篇文章:npm registry正在棄用TLS 1.0和TLS 1.1
Uncaught (in promise) Error: Redirected when going from
Uncaught (in promise) Error: Redirected when going from “/login” to “/” via a navigation guard.
可以查看這篇文章:Uncaught (in promise) Error: Redirected when going from
其他章節請看: