Vue開發(fā)實(shí)戰(zhàn)-教程篇
一、前言
本文基于開源項目:
https://github1s.com/vuejs/vue
https://vuejs.org/
最近有不少小伙伴私聊廣東靚仔,能不能出一期關(guān)于vue日常項目開發(fā)的文章,廣東靚仔整理了以往開發(fā)過的vue項目,結(jié)合目前業(yè)界主流方案,因此有了這篇文章。
目錄:
自定義Webpack和Babel配置
設(shè)計一個高擴(kuò)展性的路由
可動態(tài)改變的頁面布局
將菜單和路由結(jié)合
精細(xì)化的權(quán)限設(shè)計
使用其他第三方庫
使用Mock數(shù)據(jù)進(jìn)行開發(fā)
引入Axios
管理系統(tǒng)中使用的圖標(biāo)
定制主題及動態(tài)切換主題
國際化
構(gòu)建打包發(fā)布
做好組件的單元測試
二、現(xiàn)有方案
目前業(yè)界有很多現(xiàn)成的解決方案,廣東靚仔列舉了幾個:
Ant Design Pro
D2 Admin截圖
soybean-admin截圖
以上都是比較穩(wěn)定的管理系統(tǒng)解決方案,有興趣的小伙伴可以去看看~下面我們一起來梳理梳理,vue實(shí)際開發(fā)的一些需要考慮到的點(diǎn)。
三、正文
在使用vue開發(fā)我們項目的時候,一般的都是采用現(xiàn)有的開源項目,然后進(jìn)行一些改造(二次開發(fā)),來滿足我們的業(yè)務(wù)需求,這種方案效率是最高的,成本也是最低的。
下面開始講講vue實(shí)際項目開發(fā)需要關(guān)注的模塊,具體內(nèi)容如下所示:
使用Vue CLI 3快速創(chuàng)建項目
腳手架,不是本文要講的重點(diǎn),隨便看看即可~
全局安裝
npm install -g @vue/cli
or
yarn global add @vue/cli
新建項目
vue create my-project
最后啟動項目,看到如下效果:
自定義Webpack和Babel配置
webpack.config.js代碼如下:
let path = require('path');
let webpack = require('webpack');
/*
html-webpack-plugin插件,webpack中生成HTML的插件,
具體可以去這里查看https://www.npmjs.com/package/html-webpack-plugin
*/
let HtmlWebpackPlugin = require('html-webpack-plugin');
/*
webpack插件,提取公共模塊
*/
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
let config = {
//入口文件配置
entry: {
index: path.resolve(__dirname, 'src/js/page/index.js'),
vendors: ['vue', 'vue-router','vue-resource','vuex','element-ui','element-ui/lib/theme-default/index.css'] // 需要進(jìn)行單獨(dú)打包的文件
},
//出口文件配置
output: {
path: path.join(__dirname, 'dist'), //輸出目錄的配置,模板、樣式、腳本、圖片等資源的路徑配置都相對于它
publicPath: '/dist/', //模板、樣式、腳本、圖片等資源對應(yīng)的server上的路徑
filename: 'js/[name].js', //每個頁面對應(yīng)的主js的生成配置
chunkFilename: 'js/[name].asyncChunk.js?'+new Date().getTime() //chunk生成的配置
},
module: {
//加載器
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
scss: 'vue-style-loader!css-loader!sass-loader', // <style lang="scss">
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax' // <style lang="sass">
}
}
},
{
test: /\.html$/,
loader: "raw-loader"
},
{
test: /\.css$/,
loader: 'style-loader!css-loader'
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: ["es2015","stage-0"],
plugins: ['syntax-dynamic-import']
}
},
{
test: /\.scss$/,
loader: 'style-loader!css-loader!sass-loader'
},
{
test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/,
loader: 'file-loader'
},
{
//圖片加載器,雷同file-loader,更適合圖片,可以將較小的圖片轉(zhuǎn)成base64,減少http請求
//如下配置,將小于8192byte的圖片轉(zhuǎn)成base64碼
test: /\.(png|jpg|gif)$/,
loader: 'url-loader?limit=8192&name=images/[hash].[ext]'
}
]
},
//插件
plugins: [
//webpack3.0的范圍提升
new webpack.optimize.ModuleConcatenationPlugin(),
//打包生成html文件,并且將js文件引入進(jìn)來
new HtmlWebpackPlugin({
filename: path.resolve(__dirname, 'dist/html/index.html'), //生成的html存放路徑,相對于path
template: path.resolve(__dirname, 'src/html/index.html'), //ejs模板路徑,前面最好加上loader用于處理
inject: 'body', //js插入的位置,true/'head'/'body'/false
hash: true
}),
//提取功能模塊
new CommonsChunkPlugin({
name: 'vendors', // 將公共模塊提取,生成名為`vendors`的chunk
minChunks: 2, //公共模塊被使用的最小次數(shù)。配置為2,也就是同一個模塊只有被2個以外的頁面同時引用時才會被提取出來作為common chunks
// children:true //如果為true,那么公共組件的所有子依賴都將被選擇進(jìn)來
}),
],
//使用webpack-dev-server,啟動熱刷新插件
devServer: {
contentBase: path.join(__dirname, "/"),
host: 'localhost', //建議寫IP地址,開發(fā)時候電腦的ip地址。localhost我不知道是幻覺還是怎樣,有時候熱刷新不靈敏
port: 9090, //默認(rèn)9090
inline: true, //可以監(jiān)控js變化
hot: true//熱啟動
},
//搜索路徑變量
resolve: {
alias: {
vue: 'vue/dist/vue.js'
},
extensions:['.js','.scss','.vue','.json']// 可以不加后綴, 直接使用 import xx from 'xx' 的語法
}
};
module.exports = config;
設(shè)計一個高擴(kuò)展性的路由
根據(jù)頁面展示結(jié)構(gòu)進(jìn)行抽象,結(jié)合業(yè)務(wù)模塊進(jìn)行的合理層級劃分
router.js代碼如下:
import Vue from 'vue';
import Router from 'vue-router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import NotFound from '../views/404';
Vue.use(Router);
const router = new Router({
mode: 'history',
routes: [
{path:'/',redirect: '/user/login'},
{
path: '/user',
component: {render: h=>h("router-view")},
children: [{
path: 'login',
name: 'index',
component: () =>
import( /* webpackChunkName: "user" */ '../views/User/Login')
},
{
path: 'register',
name: 'news',
component: () =>
import( /* webpackChunkName: "user" */ '../views/User/Register')
},
{
path: '*',
name: '404',
component:NotFound
}
]
}]
})
router.beforeEach((to,form,next)=>{
NProgress.start();
next();
});
router.afterEach(() => {
NProgress.done();
});
export default router
可動態(tài)改變的頁面布局
方案一:
定義好數(shù)據(jù)格式,一個頁面可以把它劃分成多個組件來構(gòu)成,例如一個基本的布局:
header,main,footer。那么就可以劃分成三個組件,為這三個組件添加樣式,屬性,事件。
{
header:{
style:{},
property:{},
event:{}
},
main:{
style:{},
property:{},
event:{}
}
}
當(dāng)數(shù)據(jù)添加進(jìn)去,生成的頁面就應(yīng)該根據(jù)這些數(shù)據(jù)來渲染
方案二:
定義模板,根據(jù)需要切換
var hdInput = {
template: "<div><input/></div>"
};
var hdTextarea = {
template: "<div><textarea></textarea></div>"
};
new Vue({
el: "#hdcms",
components: {hdInput,hdTextarea},
data:{
formType:"hdInput"
}
});
將菜單和路由結(jié)合
具體方案:
1.前端在本地寫好路由表,以及每個路由對應(yīng)的角色,也就是哪些角色可以看到這個菜單/路由;
2.登錄的時候,向后端請求得到登錄用戶的角色(管理者、普通用戶);
3.利用路由攔截,根據(jù)取到的用戶角色,跟本地的路由表進(jìn)行對比,過濾出用戶對應(yīng)的路由,并利用路由進(jìn)行左側(cè)菜單渲染
一、本地寫好路由表
router/index.js
//代碼位置:router/index.js
{
path: '',
component: layout, //整體頁面的布局(包含左側(cè)菜單跟主內(nèi)容區(qū)域)
children: [{
path: 'main',
component: main,
meta: {
title: '首頁', //菜單名稱
roles: ['user', 'admin'], //當(dāng)前菜單哪些角色可以看到
icon: 'el-icon-info' //菜單左側(cè)的icon圖標(biāo)
}
}]
}
二、用戶登錄,獲取用戶的角色
獲取到用戶角色,存放進(jìn)localStorage,然后跳轉(zhuǎn)主頁
//代碼位置:src/components/reLoad.vue
// axios.post('/temp',this.formModel).then(res=>{})
// 我暫時就不模擬了,直接取
let getUserRole = this.formModel.user === 'admin' ? 'admin' : 'user'
localStorage.setItem('userRole', getUserRole)
this.$router.push({
path: '/main'
})
三、路由攔截beforeEach,并過濾出角色對應(yīng)的路由表
關(guān)鍵技術(shù)點(diǎn)addRoutes
//代碼位置:src/permission.js
router.beforeEach((to, from, next) => {
// 取到用戶的角色
let GetRole = localStorage.getItem("userRole")
// 如果登錄了
if (GetRole !== 'unload') {
next() //next()方法后的代碼也會執(zhí)行
// 1.如果路由表 沒根據(jù)角色進(jìn)行篩選,就篩選一次
if (!addRouFlag) {
addRouFlag = true
// 2.根據(jù)用戶的角色、和需要動態(tài)展示的路由,生成符合用戶角色的路由
var getRoutes = baseRoleGetRouters(permissionRouter, GetRole.split(","))
// 3.利用global屬性,讓渲染菜單的組件sideMeuns.vue重新生成左側(cè)菜單
global.antRouter = fixedRouter.concat(getRoutes)
// 4.將生成好的路由addRoutes
router.addRoutes(fixedRouter.concat(getRoutes))
// 5.push之后,會重新進(jìn)入到beforeEach的鉤子里,直接進(jìn)入第一個if判斷
router.push({ path: to.path })
}
} else {
// 用戶沒登錄,跳轉(zhuǎn)到登錄頁面
if (to.path === '/') {
next()
} else {
next('/')
}
}
})
精細(xì)化的權(quán)限設(shè)計
權(quán)限控制是后臺管理系統(tǒng)比較常見的需求,我們需要對某些頁面的添加權(quán)限控制,可以在路由管理中的權(quán)限做一些校驗(yàn)。
一、權(quán)限校驗(yàn)函數(shù)
getCurrentAuthority()函數(shù)用于獲取當(dāng)前用戶權(quán)限,一般來源于后臺數(shù)據(jù)
check()函數(shù)用于權(quán)限的校驗(yàn)匹配
isLogin()函數(shù)用于檢驗(yàn)用戶是否登錄
/**
*權(quán)限校驗(yàn)函數(shù)
* /src/utils,/auth.js演示使用路由管理用戶權(quán)限
**/
// 獲取當(dāng)前用戶權(quán)限
export function getCurrentAuthority(){
return ["user"];
}
//權(quán)限校驗(yàn)
export function check(authority){
const current getCurrentAuthority();
return current.some(item =authority.includes(item));
}
//登錄檢驗(yàn)
export function isLogin(){
const current getcurrentAuthority();
return current ¤t[0]!="guest";
}
二、路由配置元信息
路由配置元信息meta:{ authority: ["admin"] }
/**
* 路由配置元信息
* /src/router/index.js
*/
const routes =
// 省略部分代碼
{
path:"/"
meta:authority:["user","admin"]}
component:()=
import(/*webpackChunkName:"Layout"*/"../layouts/BasicLayout")
//省略部分代碼
},
{
path:"/403",
name:"403",
hideInMenu:true,
component:()=
import(/*webpackChunkName:"exception"*/"@/views/Exception/403")
}
];
三、路由守衛(wèi)router.beforeEach中判斷
/**登出于形到物權(quán)限
* /src/router/index.js
*/
import findLast from "lodash/findLast";
import {check,isLogin} from "../utils/auth";
import {notification} from "ant-design-vue";
// 路由守衛(wèi)判斷權(quán)限
router.beforeEach((to,from,next)=>{
if (to.path I==from.path){
NProgress.start()
}
const record findLast(to.matched,record => record.meta.authority);
if (record && !check(record.meta.authority)){
if (lisLogin()&&to.path !=="/user/login"){
next({
path:"/user/login"
})
} else if(to.path1 !== "/403"){
notification.error({
message:"403",
description:"你設(shè)有訪間權(quán)限,請聯(lián)系管理員"
})
next({
path:"/403"
})
}
NProgress.done();
}
next();
})
使用ECharts、Antv等其他第三方庫
根據(jù)項目要求,按需引入
使用第三方的開源庫,可以提高效率~
使用Mock數(shù)據(jù)進(jìn)行開發(fā)
一、安裝:
npm i mockjs -D
-D: 僅僅在開發(fā)環(huán)境上使用
二、項目引入:
在 main.js 文件中引入mock:import '@/mock'
三、創(chuàng)建mock文件夾
// 引入隨機(jī)函數(shù)
import { Random } from 'mockjs'
// 引入Mock
const Mock = require('mockjs')
const userListData = Mock.mock({
'data|10': [
{
id: () => Random.id(),
nickName: () => Random.cword('零一二三四五六七八九十', 3),
phone: () => Random.integer(11111111111, 99999999999),
tgCount: () => Random.integer(0, 200),
earnings: () => Random.float(2000, 10000, 0, 2),
},
],
})
function userList(res) {
return {
code: 200,
data: userListData.data,
message: '獲取成功',
total: 20,
size: 10,
user_count: 20,
shop_count: 20,
}
}
const shopListData = Mock.mock({
'data|10': [
{
shop_id: () => Random.id(),
shop_name: () => Random.cword('零一二三四五六七八九十', 3),
address: () => Random.city(true),
shop_tel: () => Random.integer(11111111111, 99999999999),
open_date: () => Random.date(),
earnings: () => Random.float(2000, 10000, 0, 2),
},
],
})
function shopList(res) {
return {
code: 200,
data: shopListData.data,
message: '獲取推廣店鋪成功',
total: 20,
size: 10,
earnings_count: 20000,
shopCount: 20,
}
}
export default {
userList,
shopList,
}
四、定義訪問的方法,接口,請求方式,請求參數(shù)
import http from '../../plugins/http'
export const getUserList = (params) => {
return http.get('/api/cuz/userList')
}
export const getShopListById = (id) => {
return http.get(`/api/cuz/shopList/${id}`)
}
五、攔截匹配在api中定義的請求,并對此返回模擬出的假數(shù)據(jù)
// 引入mockjs
import Mock from 'mockjs'
// 引入模板函數(shù)類
import ratings from './modules/ratings'
import cuz from './modules/cuz'
// Mock函數(shù)
const { mock } = Mock
// 設(shè)置延時
Mock.setup({
timeout: 400,
})
// 使用攔截規(guī)則攔截命中的請求,mock(url, post/get, 返回的數(shù)據(jù));
mock(/\/api\/ratings\/list/, 'post', ratings.list)
mock(/\/api\/cuz\/userList/, 'get', cuz.userList)
mock(/\/api\/cuz\/shopList/, 'get', cuz.shopList)
Axios
一、安裝
npm install vue-axios --save
二、main.js引入
import axios from 'axios'
Vue.prototype.$axios = axios //全局注冊,使用方法為:this.$axios
三、使用
<script>
export default{
data(){
return{
userId:666,
token:'',
}
},
created(){
this.$axios({
method:'post',
url:'api',
data:this.qs.stringify({ //這里是發(fā)送給后臺的數(shù)據(jù)
userId:this.userId,
token:this.token,
})
}).then((response) =>{ //這里使用了ES6的語法
console.log(response) //請求成功返回的數(shù)據(jù)
}).catch((error) =>
console.log(error) //請求失敗返回的數(shù)據(jù)
})
}
}
</script>
四、請求攔截器
// http request 攔截器
instance.interceptors.request.use(
config => {
const token = sessionStorage.getItem('token')
if (token ) { // 判斷是否存在token,如果存在的話,則每個http header都加上token
config.headers.authorization = token //請求頭加上token
}
return config
},
err => {
return Promise.reject(err)
})
五、響應(yīng)攔截器
// http response 攔截器
instance.interceptors.response.use(
response => {
//攔截響應(yīng),做統(tǒng)一處理
if (response.data.code) {
switch (response.data.code) {
case 1002:
store.state.isLogin = false
router.replace({
path: 'login',
query: {
redirect: router.currentRoute.fullPath
}
})
}
}
return response
},
//接口錯誤狀態(tài)處理,也就是說無響應(yīng)時的處理
error => {
return Promise.reject(error.response.status) // 返回接口返回的錯誤信息
})
六、在需要的頁面導(dǎo)入就可以使用了
import instance from './axios'
/* 驗(yàn)證登陸 */
export function handleLogin (data) {
return instance.post('/ds/user/login', data)
}
管理系統(tǒng)中使用的圖標(biāo)
項目中的圖標(biāo)需要集中管理起來,方便維護(hù),減少一些圖片重復(fù)引入
如果對安全沒什么特殊要求:推薦使用iconfont
如果對安全有特別要求:把圖標(biāo)統(tǒng)一存放在內(nèi)部服務(wù)
定制主題及動態(tài)切換主題
結(jié)合ElementUI使用
(Tips: 廣東靚仔看到業(yè)界關(guān)于動態(tài)主題大約有6種方案,選了其中一種)
修改ElementUI提供的變量,先根據(jù)實(shí)際情況修改變量值
// 參考:https://element.eleme.cn/#/zh-CN/component/custom-theme
/* 改變主題色變量 */
$--color-primary: #545C64;
$--color-success: #27B6AF;
$--menu-background-color: #1D212A;
$--menu-item-font-color: #B3B8C3;
$--menu-item-hover-fill: #1F2D3D;
$--main-padding: 15px;
/* 改變 icon 字體路徑變量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
// 通用的布局等樣式
@import "../common";
common.scss片段:
// 自定義變量
$---menu--inline-background-color: #13161C !default;
$---index-header-height: 50px !default;
$---padding-common: 15px !default;
$---margin-common: 15px !default;
$---border-line-color: #E6E6E6 !default;
@import "~element-ui/packages/theme-chalk/src/index";
.el-menu-item.is-active {
color: $--color-white;
background-color: $--menu-item-hover-fill;
font-weight: $--font-weight-primary;
}
// .............更多見GitHub源文件
在main.js中引入
// 樣式配置
import './assets/css/main.scss'
動態(tài)主題
定義好模板主題文件,這里列舉了defaut、simple兩個主題
main.scss主要內(nèi)容:
// 實(shí)際樣式引入
.theme-simple {
@import "src/assets/themes/simple/index";
}
.theme-default {
@import "src/assets/themes/default/index";
}
切換主題
改變body的樣式名稱即可,調(diào)用$changeTheme(theme):
const $themeList = [
{
id: 'theme-default',
name: '默認(rèn)主題'
}, {
id: 'theme-simple',
name: '簡單主題'
}
]
Vue.prototype.$changeTheme = function (theme = $themeList[0]) {
const body = document.querySelector('body')
$themeList.forEach(t => {
body.classList.remove(t.id)
})
body.classList.add(theme.id)
store.dispatch('Theme/changeTheme', theme) // 暫時保存到store里面
}
Tips: 圖標(biāo)在主題樣式顯示有點(diǎn)問題,使用font-face兼容下
//***********這塊font定義是為了修復(fù)問題********************
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@font-face {
font-family: 'element-icons';
src: url('#{$--font-path}/element-icons.woff') format('woff'), url('#{$--font-path}/element-icons.ttf') format('truetype');
font-weight: normal;
font-display: auto;
font-style: normal;
}
做好國際化
i18n
一、 使用國際化來更改咱們的項目語言
簡單介紹下i18n如何用
1. 安裝:
//使用yarn
yarn add vue-i18n
//npm
npm i vue-i18n -S
2. 使用:
系統(tǒng)中使用它,必須通過 Vue.use() 明確地安裝 vue-i18n:
src/i18n/index.js
//src/i18n/index.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
// 準(zhǔn)備翻譯的語言環(huán)境信息
const messages = {
en: {
message: {
hello: 'hello world'
}
},
ja: {
message: {
hello: 'こんにちは、世界'
}
}
}
// 通過選項創(chuàng)建 VueI18n 實(shí)例
const i18n = new VueI18n({
locale: 'ja', // 設(shè)置地區(qū)
messages // 設(shè)置地區(qū)信息
})
3. 將i18n實(shí)例掛載在Vue的option中
import Vue from 'vue'
import i18n from "./src/i18n/index.js"
new Vue({
i18n
})
4. 視圖顯示
<div id="app">
<p>{{ $t("message.hello") }}</p>
</div>
------------------------------------
<!-- 展示效果如下 -->
<div id="app">
<p>hello world</p>
</div>
在插值中使用$t函數(shù)就可以了
二、vue-cli項目中使用
1. 創(chuàng)建i18n文件結(jié)構(gòu)
目錄結(jié)構(gòu)如下:
這里列舉了兩種語言分別是:en英文和zh中文
en.js
export default {
table: { // 假如用于翻譯表格
date: "Date",
name: "Name",
address: "Address"
},
menu: {}, // 假如項目中日后還有菜單
tabs: {} // tab切換等
}
zh.js
export default {
table: {
date: "日期",
name: "姓名",
address: "地址"
},
menu: {},
tabs: {}
}
config文件夾下面的index.js,代碼如下(二者都可以):
乞丐版:
import en from './config/en'
import id from './config/id'
import ja from './config/ja'
import ae from './config/ae'
import am from './config/am'
import ca from './config/ca'
import al from './config/al'
.....
至尊版:
import Vue from "vue"
import VueI18n from "vue-i18n"
Vue.use(VueI18n)//注入到所有的子組件
//require.context(path,deep,regExp)
//有3個方法 分別是keys()
// 至尊版
let langFileds = require.context('./config', false, /\.js$/)
let regExp = /\.\/([^\.\/]+)\.([^\.]+)$/ //正則用于匹配 ./en.js中的'en'
// regExp.exec('./en.js')
let messages = {} //聲明一個數(shù)據(jù)模型,對應(yīng)i18n中的message屬性
langFileds.keys().forEach(key => {
let prop = regExp.exec(key)[1] //正則匹配en|zh這樣的值
//messages[prop]相當(dāng)于 messages['en'] = {table:{...}}
messages[prop] = langFileds(key).default
})
console.log(messages);
console.log(langFileds('./en.js'));
let locale = localStorage.getItem('lang') || "zh" //從localstorag中獲取
export default new VueI18n({
locale,//指定語言字段
messages//定義語言字段
})
2. 修改main.js
import Vue from 'vue'
import App from './App.vue'
import ElementUI from "element-ui" // 需要安裝 element-ui
import 'element-ui/lib/theme-chalk/index.css';
Vue.config.productionTip = false
Vue.use(ElementUI)
import i18n from "./i18n" //
new Vue({
render: h => h(App),
i18n // 掛載
}).$mount('#app')
3. 具體使用demo
app.vue
<template>
<div id="app">
<template>
<el-table :data="tableData"
style="width: 100%">
<el-table-column prop="date"
:label="$t('table.date')"
width="180">
</el-table-column>
<el-table-column prop="name"
:label="$t('table.name')"
width="180">
</el-table-column>
<el-table-column prop="address"
:label="$t('table.address')">
</el-table-column>
</el-table>
</template>
<el-button type="primary"
@click="change('zh')">點(diǎn)擊切換中文</el-button>
<el-button type="primary"
@click="change('en')">點(diǎn)擊切換英文</el-button>
<el-button type="primary"
</div>
</template>
<script>
export default {
mounted() {
console.log(this.$i18n.t('table.date'));
},
methods: {
change(lang) { //切換方法
localStorage.setItem('lang', lang)
window.location.reload() //localSotrage是不響應(yīng)的,為了演示效果所以直接調(diào)用刷新
}
},
data() {
return {
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀區(qū)金沙江路 1518 弄'
}]
}
}
}
</script>
<style>
#app {
width: 50%;
}
</style>
構(gòu)建打包發(fā)布
1. 打包配置如下:
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: './',
productionSourceMap: true,
// 默認(rèn)情況下,Gzip 關(guān)閉了許多流行的靜態(tài)主機(jī),例如
// Surge 或 Netlify 已經(jīng)為您壓縮了所有靜態(tài)資產(chǎn)。
// 在設(shè)置為 `true` 之前,請確保:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// 運(yùn)行帶有額外參數(shù)的構(gòu)建命令
// 構(gòu)建完成后查看包分析器報告:
// `npm run build --report`
// 設(shè)置為 `true` 或 `false` 以始終打開或關(guān)閉它
bundleAnalyzerReport: process.env.npm_config_report
}
2. 一般部署,我們會結(jié)合Nginx一起使用
安裝&啟動
# 安裝,安裝完成后使用nginx -v檢查,如果輸出nginx的版本信息表明安裝成功
sudo apt-get install nginx
# 啟動
sudo service nginx start
3. 修改nginx配置
nginx的配置文件就在/etc/nginx文件夾
/etc/nginx/sites-available/default
nginx代理的根目錄是/var/www/html
mkdir /www
echo 'Hello world' > /www/index.html
4. 同步到服務(wù)器
在git-bash或者powershell使用scp指令,如果是linux環(huán)境開發(fā),還可以使用rsync指令:
scp -r dist/* root@117.78.4.26:/www
或
rsync -avr --delete-after dist/* root@117.78.4.26:/www
package.json腳本,方便,提高效率
"scripts": {
"build": "vue-cli-service build",
"push": "yarn build && scp -r dist/* root@117.78.4.26:/www"
},
當(dāng)然啦,對于history、與hash模式,對應(yīng)微調(diào)下即可~
做好組件的單元測試
Vue 的單文件組件使得為組件撰寫隔離的單元測試這件事更加直接
組件的單元測試有很多好處:
提供描述組件行為的文檔
節(jié)省手動測試的時間
減少研發(fā)新特性時產(chǎn)生的 bug
改進(jìn)設(shè)計
促進(jìn)重構(gòu)
一個簡單的Demo:
<template>
<div>
<div class="message">
{{ message }}
</div>
Enter your username: <input v-model="username">
<div
v-if="error"
class="error"
>
Please enter a username with at least seven letters.
</div>
</div>
</template>
<script>
export default {
name: 'Foo',
data () {
return {
message: 'Welcome to the Vue.js cookbook',
username: ''
}
},
computed: {
error () {
return this.username.trim().length < 7
}
}
}
</script>
單元測試,代碼如下:
import { shallowMount } from '@vue/test-utils'
import Foo from './Foo'
const factory = (values = {}) => {
return shallowMount(Foo, {
data () {
return {
...values
}
}
})
}
describe('Foo', () => {
it('renders a welcome message', () => {
const wrapper = factory()
expect(wrapper.find('.message').text()).toEqual("Welcome to the Vue.js cookbook")
})
it('renders an error when username is less than 7 characters', () => {
const wrapper = factory({ username: '' })
expect(wrapper.find('.error').exists()).toBeTruthy()
})
it('renders an error when username is whitespace', () => {
const wrapper = factory({ username: ' '.repeat(7) })
expect(wrapper.find('.error').exists()).toBeTruthy()
})
it('does not render an error when username is 7 characters or more', () => {
const wrapper = factory({ username: 'Lachlan' })
expect(wrapper.find('.error').exists()).toBeFalsy()
})
})
Tips: 工廠函數(shù)將 values 對象合并到了 data 并返回了一個新的 wrapper 實(shí)例。好處有兩個:
1. 不需要在每個測試中重復(fù) const wrapper = shallowMount(Foo)。
2. 當(dāng)我們想為更復(fù)雜的組件在每個測試中偽造或存根一個方法或計算屬性時,你只需要聲明一次即可。
Vue Test Utils 及龐大的 JavaScript 生態(tài)系統(tǒng)提供了大量的工具促進(jìn) 100% 的測試覆蓋率。
推薦閱讀:
https://v1.test-utils.vuejs.org/zh/guides/#%E8%B5%B7%E6%AD%A5
四、總結(jié)
在我們閱讀完官方文檔后,我們一定會進(jìn)行更深層次的學(xué)習(xí),比如看下框架底層是如何運(yùn)行的,以及源碼的閱讀。
這里廣東靚仔給下一些小建議:
在看源碼前,我們先去官方文檔復(fù)習(xí)下框架設(shè)計理念、源碼分層設(shè)計
閱讀下框架官方開發(fā)人員寫的相關(guān)文章
借助框架的調(diào)用棧來進(jìn)行源碼的閱讀,通過這個執(zhí)行流程,我們就完整的對源碼進(jìn)行了一個初步的了解
接下來再對源碼執(zhí)行過程中涉及的所有函數(shù)邏輯梳理一遍
作者:廣東靚仔
歡迎關(guān)注:前端早茶