vue 快速入门 系列 —— 使用 vue-cli 3 搭建一个项目(上)

时间:2024-12-13 00:05:20

其他章节请看:

vue 快速入门 系列

使用 vue-cli 3 搭建一个项目(上)

前面我们已经学习了一个成熟的脚手架(vue-cli),笔者希望通过这个脚手架快速搭建系统(或项目)。而展开搭建最好的方法是向优秀的项目学习,依葫芦画瓢。

这里通过研究 vue-admin-template 项目,逐一引入 element-uiaxiosmockiconfontnprogress权限控制布局多环境(.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 一个现在主流并且很好用的请求库 支持Promise
  • js-cookie 一个轻量的JavaScript库来处理cookie
  • normalize.css 格式化css
  • nprogress 轻量的全局进度条控制
  • vuex 官方状态管理
  • vue-router 官方路由
  • iconfont 图标字体
  • 权限控制
  • lint

Tip:vue-cli webpack模板:

  • 这个模板是 vue-cli verison 2.* 的主要模板
  • Vue-cli 3 包含此模板提供的所有功能(以及更多功能)
  • Vue-cli 3 来了,此模板现在被视为已弃用

下载项目并启动:

> git clone https://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 > vue-admin-template@4.4.0 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-templatemyself-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-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 axios@0.18.1

新建 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 中模拟的数据,至此,表明 axiosmock 都已生效。

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
// https://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: https://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

其他章节请看:

vue 快速入门 系列