1. vue-cli 構(gòu)建項(xiàng)目
- 命令行
# 全局安裝 vue-cli
$ npm install --global vue-clif
# 創(chuàng)建一個(gè)基于 webpack 模板的新項(xiàng)目
$ vue init webpack your-project-name
# 安裝依賴(lài),走你
$ npm install
# 進(jìn)入項(xiàng)目
$ cd your-project-name
# 開(kāi)發(fā)版本打包并運(yùn)行
$ npm run dev
# 線上環(huán)境整個(gè)項(xiàng)目打包 生成 dist 可以直接部署到服務(wù)器上的文件夾
npm run build
2. 項(xiàng)目模板中使用 less 方法
原文地址
vue-cli 構(gòu)建的項(xiàng)目默認(rèn)是不支持 less 的,需要自己添加。
- 首先安裝 less 和 less-loader ,在項(xiàng)目目錄下運(yùn)行如下命令
# npm安裝
$ npm install less less-loader --save-dev
# 或者使用 yarn
$ yarn add less less-loader --dev
- 安裝成功后,打開(kāi)
build/webpack.base.conf.js,在 module.exports = 的對(duì)象的 module.rules 后面添加一段:
module.exports = {
// 此處省略無(wú)數(shù)行,已有的的其他的內(nèi)容
module: {
rules: [
// 此處省略無(wú)數(shù)行,已有的的其他的規(guī)則
{
test: /\.less$/,
loader: "style-loader!css-loader!less-loader",
}
]
}
}
image.png
- 最后在代碼中的 style 標(biāo)簽中 加上 lang="less" 屬性即可
<style scoped lang="less">
</style>
image.png
- 之后在項(xiàng)目中測(cè)試是否成功
npm install less less-loader --save-dev
npm run dev
image.png
- 在瀏覽其中打開(kāi)相應(yīng)頁(yè)面,這個(gè)頁(yè)面是
/根頁(yè)面點(diǎn)擊跳轉(zhuǎn)過(guò)來(lái)的子路由
image.png
image.png
可以看到樣式編譯成功了 哦耶~
3. 在 router 下的路由文件里設(shè)置格式,將頁(yè)面上路由中默認(rèn)顯示的 #/ 給去掉
// 去掉路由中自帶的 #/ 這種東西
mode: 'history',
image.png
- 需要注意的是使用了
history之后需要在服務(wù)器部署時(shí)增加一些配置,具體方法插件下面官方寫(xiě)的配置方法
4. 引入 jquery
- 安裝
npm install jquery --save
- 配置
image.png
image.png
// 先在頂部引入 webpack
const webpack = require('webpack')
// plugins 中添加
new webpack.ProvidePlugin({
'window.jQuery': 'jquery', // 為了兼容其他的插件
jQuery: 'jquery',
$: 'jquery'
})
- 使用
image.png
5. :class 使用表達(dá)式
:class="{'想要改變的類(lèi)名': 判斷條件}
- 示例圖片
image.png
6. DOM 事件修飾符
<!-- 阻止單擊事件繼續(xù)傳播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重載頁(yè)面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修飾符可以串聯(lián) -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修飾符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件監(jiān)聽(tīng)器時(shí)使用事件捕獲模式 -->
<!-- 即元素自身觸發(fā)的事件先在此處處理,然后才交由內(nèi)部元素進(jìn)行處理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只當(dāng)在 event.target 是當(dāng)前元素自身時(shí)觸發(fā)處理函數(shù) -->
<!-- 即事件不是從內(nèi)部元素觸發(fā)的 -->
<div v-on:click.self="doThat">...</div>
- 示例,如下圖所示,這樣寫(xiě)的話點(diǎn)擊了 li 內(nèi)部的元素的話不會(huì)影響 li 的 click 的點(diǎn)擊事件
image.png
7. vue 使用 clipboard 實(shí)現(xiàn)復(fù)制功能
- 安裝依賴(lài) clipboard.js
npm install clipboard --save
- 在需要使用的地方 require 引用
var clipboard = require('clipboard');
- 在頁(yè)面加載后調(diào)用該方法即可
image.png
8. 解決 vue-resource 的跨越問(wèn)題
我這里是 vue-cli 基于 webpack 的項(xiàng)目(注意:在修改了 proxyTable 之后需要在命令行中
npm run dev重新運(yùn)行下項(xiàng)目,否則是不會(huì)有效果的呀~)
- 錯(cuò)誤信息
Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.
- 解決方法:
先找到對(duì)應(yīng)的配置 js 文件中的 proxyTable
image.png
修改相應(yīng)的配置
image.png
再在 main.js 中配置 vue-resource 的格式,不配置的話是無(wú)法向后臺(tái)傳遞參數(shù)的
image.png
在 vue 文件中的使用
data 中綁定相應(yīng)的靜態(tài)配置
image.png
methods 增加相應(yīng)的方法
image.png
mouted 在 data 數(shù)據(jù)掛載到實(shí)例對(duì)象的時(shí)候 ,請(qǐng)求頁(yè)面數(shù)據(jù),實(shí)現(xiàn)頁(yè)面的正常顯示
image.png
9. vue-router 單頁(yè)之間如何在 js 中跳轉(zhuǎn)
- 三種寫(xiě)法
// 字符串
this.$router.push('/home/first')
// 對(duì)象
this.$router.push({ path: '/home/first' })
// 命名的路由
this.$router.push({ name: 'home', params: { userId: wise }})
image.png
10. vuex 實(shí)現(xiàn)組件之間數(shù)據(jù)的傳遞
根據(jù) state 可以實(shí)時(shí)的獲取到數(shù)據(jù)
原文地址
- 安裝
npm install vuex --save
- 在 src 文件夾中新建一個(gè) stroe 文件夾,并在目錄下新建一個(gè) index.js 文件(已有的話請(qǐng)忽略),index.js 文件編輯如下
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
let store = new Vuex.Store({
state: {
formData: {} // 企業(yè)提交數(shù)據(jù)表單對(duì)象
}
});
export default store;
- 在 src 目錄下的 main.js 文件中引入 vuex 文件,并在實(shí)例化時(shí)添加配置
import Vue from 'vue';
import App from './App';
import router from './router';
import store from './store'; // 引入 vuex
Vue.config.productionTip = false;
Vue.http.options.emulateJSON = true;
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store, // 需要在添加
components: { App },
template: '<App/>'
});
- 之后就可以直接在需要的組件中直接引用,引用具體示例如下
image.png
image.png
控制臺(tái)成功輸出
image.png
11. .eslintrc.js 文件 rules 增加設(shè)置
12. vue 表單操作
13. 解決使用 vux 組件庫(kù)時(shí) 與 rem 設(shè)置沖突帶來(lái)的問(wèn)題
- 思路
將之前 rem 計(jì)算的數(shù)值
html font-size: "100px",改到12px,之后連鎖的將 less 中計(jì)算和引用的的值也改下,之后就可以了,盡量做到少量的修改即可
- 將之前的 js 計(jì)算 rem 數(shù)值腳本修改相應(yīng)的數(shù)值
image.png
改過(guò)之后的
image.png
- 修改 less
image.png
改過(guò)之后的
image.png
- 之后就可以是 rem 和 vux 基本正常了
14. 通過(guò) watch 動(dòng)態(tài)的監(jiān)測(cè)路由跳轉(zhuǎn)(跳轉(zhuǎn)時(shí))和 APP.vue 中設(shè)置 created 方法實(shí)時(shí)監(jiān)測(cè) path (刷新時(shí)),來(lái)實(shí)現(xiàn) header 文字的改變
- header.vue
watch: {
'$route' (to, from) {
// 檢測(cè)路由改變 header 內(nèi)容
if (to.name === 'Index') {
this.$store.state.PageTitle = '預(yù)約領(lǐng)號(hào)';
this.$store.state.isShowBack = false;
} else if (to.name === 'PreferentialDescription') {
this.$store.state.PageTitle = '優(yōu)惠說(shuō)明';
this.$store.state.isShowBack = true;
} else if (to.name === 'RuleIntroduction') {
this.$store.state.PageTitle = '規(guī)則簡(jiǎn)介';
this.$store.state.isShowBack = true;
} else if (to.name === 'ReservationSuccess') {
this.$store.state.PageTitle = '預(yù)約排號(hào)';
this.$store.state.isShowBack = true;
}
}
}
15. vue-router spa (單頁(yè))需要的 nginx 配置,防止出現(xiàn) 404 的情況
image.png
照著上方的圖將代碼復(fù)制至服務(wù)器的 nginx.config 配置文件即可
- ssh 遠(yuǎn)程登錄服務(wù)器
ssh username@ipaddress
enter your password
- 查找服務(wù)器中的 nginx 相關(guān)目錄,我這邊是 nginx 服務(wù)器
whereis nginx
image.png
-
/etc/nginx這個(gè)是 nginx 相關(guān)配置的目錄 -
/usr/share/nginx是靜態(tài)文件的目錄 - 進(jìn)入 html 目錄,這個(gè)就是默認(rèn)的存放項(xiàng)目文件的目錄
image.png
- 修改 nginx 默認(rèn)的配置文件
# 首先進(jìn)入配置文件目錄
cd /etc/nginx
ls
# 查看配置文件
cat nginx.config
# 復(fù)制一份原始的配置文件
cp nginx.config nginx.config.back
# 按照上面 vue-router 的需求修改配置文件
vi nginx.config
# 進(jìn)入編輯狀態(tài)
I
# 修改文件
location / {
try_files $uri $uri/ /index.html;
}
# 之后保存并退出
esc
:
wq
# 再次查看是否已修改成功
cat nginx.config
# 重載 nginx 配置文件(必須重載,不然修改的是不會(huì)生效的!)
nginx -s reload
image.png
- 上面的步驟操作完成之后便解決了 vue-router spa 帶來(lái)的刷新頁(yè)面 404 的問(wèn)題了,哦耶~
16. 與后臺(tái) API 進(jìn)行通信時(shí),Content-Type 請(qǐng)求文本格式未統(tǒng)一帶來(lái)的問(wèn)題
- 問(wèn)題截圖
image.png
后臺(tái)返回 415 Unsupported Media Type
對(duì)于當(dāng)前請(qǐng)求的方法和所請(qǐng)求的資源,請(qǐng)求中提交的實(shí)體并不是服務(wù)器中所支持的格式,因此請(qǐng)求被拒絕。
- 產(chǎn)生錯(cuò)誤的原因:
這里使用的是
post請(qǐng)求,后臺(tái)的請(qǐng)求文本格式為Content-Type:application/json;charset=UTF-8
但是這里使用的是默認(rèn)的Content-Type:application/x-www-form-urlencoded所以造成的此次錯(cuò)誤
- 解決方法
在提交數(shù)據(jù)之前使用
JSON的stringify方法將數(shù)據(jù)轉(zhuǎn)換為 json 格式的文本即可,如下圖所示
image.png
JSON.stringiy()
JSON.stringify() 方法是將一個(gè)JavaScript值(對(duì)象或者數(shù)組)轉(zhuǎn)換為一個(gè) JSON字符串,如果指定了replacer是一個(gè)函數(shù),則可以替換值,或者如果指定了replacer是一個(gè)數(shù)組,可選的僅包括指定的屬性。
17. 清除 .vue 文件在 vscode 編輯器中格式化時(shí)默認(rèn)添加的分號(hào)和雙引號(hào)的規(guī)則
image.png
- 問(wèn)題
因?yàn)?
vue-cli項(xiàng)目創(chuàng)建后會(huì)默認(rèn)的增加.eslintrc.jseslint 規(guī)則文件來(lái)幫助我們更好的統(tǒng)一代碼的規(guī)范性,但是現(xiàn)在的趨勢(shì)是省略javascript代碼書(shū)寫(xiě)時(shí)在末尾添加的分號(hào),但是 vscode 編輯器因?yàn)檠b了vetur這個(gè)插件,所以還是會(huì)像之前的那樣默認(rèn)追加,使得項(xiàng)目報(bào) eslint 語(yǔ)法的錯(cuò)誤,單雙引號(hào)也是相同的問(wèn)題
- 解決方法
先安裝擴(kuò)展插件
Prettier - Code formatter
image.png
之后在頂部菜單欄依次操作:【文件】->【首選項(xiàng)】->【設(shè)置】->【用戶(hù)設(shè)置】
最后增加下面的規(guī)則代碼片段
"prettier.singleQuote": true,
"prettier.semi": false
18. 之前的刪掉了,等待更新中......
19. 給 vue 掛載全局方法
- 找到
main.js文件進(jìn)行編輯,這里以axios為例演示
import Vue from 'vue'
import axios from 'axios'
Vue.prototype.axios = axios
- 使用方法 某個(gè)
.vue文件的sccript中如下編輯
Vue.axios.post('url', { name: '' })
.then(response => {
console.log(response)
})
.catch(response => {
console.log(response)
})
.finally(() => (me.loading= false));
20. axios 不兼容 ie 的問(wèn)題解決
- 問(wèn)題描述
image.png
在 IE 瀏覽器下會(huì)報(bào) “Promise”未定義" 的錯(cuò)誤
- 資料
image.png
問(wèn)題解決參考地址
- 解決方法
使用 babel-polyfill 這個(gè)包
- 裝包
yarn add babel-polyfill --dev
- 之后在
main.js文件中引入包即可
import 'babel-polyfill'
image.png
- 測(cè)試可兼容至 IE8+
image.png
- 更新 優(yōu)化進(jìn)階 已經(jīng)過(guò)測(cè)試
參考文章里面有詳細(xì)的解釋?zhuān)篵abel-polyfill使用與性能優(yōu)化
目的是為了打包出更小的體積,下圖是使用新方法打包出的體積,用上面的方法打包出來(lái)是2.64MB
image.png
- 解決方法 將之前引入的
babel-polyfill換成core-js/es6/promise這個(gè)是vue-cli腳手架就有的包無(wú)需再裝了
import 'core-js/es6/promise' // 解決 axios 兼容 IE 問(wèn)題
image.png
21. 組件封裝,這里以 bootstrap 的 modal 模塊為例
- 先寫(xiě)組件,在
src -> components目錄下新建一個(gè)文件夾msgmodal -> index.vue,編輯如下
<template>
<!-- 彈窗 -->
<div class="modal fade" tabindex="-1" role="dialog" id="myModal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<!-- <h5 class="modal-title"></h5> -->
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>{{ modalMsg }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">確定</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">關(guān)閉</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'MsgModal', // 定義的組件名稱(chēng) 使用時(shí)寫(xiě)法:msg-modal
props: ['modalMsg'] // 定義的與父級(jí)通信的屬性值 使用時(shí)寫(xiě)法:modal-msg
}
</script>
<style scoped>
</style>
- 在具體的
.vue文件使用組件,方法如下,這里是用的動(dòng)態(tài)綁定的方法傳遞屬性的值
<!-- template -->
<!-- 彈窗 -->
<msg-modal :modal-msg="modalMsg"></msg-modal>
// script
import MsgModal from '@/components/msgmodal'
export default{
name:'App',
components: { MsgModal },
data () {
return {
// 彈窗信息 在執(zhí)行操作時(shí)使用
modalMsg: ''
}
}
}
- 測(cè)試,已實(shí)現(xiàn)可以實(shí)時(shí)更新內(nèi)容了
image.png
22. 在 router -> index.js 按需引入模塊,優(yōu)化 SPA 頁(yè)面的性能
- 源碼
import Vue from 'vue'
import Router from 'vue-router'
import VueResource from 'vue-resource'
// import index from '@/index'
// import companyapply from '@/companyapply'
// import choosenumber from '@/choosenumber'
// import statelist from '@/statelist'
Vue.use(Router)
Vue.use(VueResource)
export default new Router({
// 去掉路由中自帶的 #/ 這種東西
mode: 'history',
routes: [
{
path: '/',
name: 'index',
component: () => import('@/index')
},
{
path: '/companyapply',
name: 'companyapply',
component: () => import('@/companyapply')
},
{
path: '/choosenumber',
name: 'choosenumber',
component: () => import('@/choosenumber')
},
{
path: '/statelist',
name: 'statelist',
component: () => import('@/statelist')
}
]
})
23. 在 Vue 上掛載 vux 庫(kù)中的 LoadingPlugin 組件
main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import { LoadingPlugin } from 'vux' // 引入 looding 組件
import App from './App'
import store from './store' // 引入 vuex
import router from './router'
Vue.config.productionTip = false
Vue.http.options.emulateJSON = true
Vue.use(LoadingPlugin) // 掛載 loading 組件
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store, // 需要在添加
components: { App },
template: '<App/>'
})
-
.vue文件中具體的使用示例:
// loading
this.$vux.loading.show({
text: '數(shù)據(jù)提交中
})
// 隱藏 loading
this.$vux.loading.hide()
24. 解決:使用 axios 默認(rèn)發(fā)送的是 application/json;charset=UTF-8 這種格式的數(shù)據(jù)后臺(tái)無(wú)法讀取的問(wèn)題
后臺(tái)需要的是
application/x-www-form-urlencoded這樣的數(shù)據(jù)格式
參考地址
- 問(wèn)題截圖
image.png
- 需要的格式截圖
image.png
-
解決方法
- 使用
qs模塊,轉(zhuǎn)換數(shù)據(jù)格式
image.png- 源碼
import qs from 'qs' // 解決 axios 數(shù)據(jù)提交格式與后臺(tái)不一致的問(wèn)題 -> name=hehe&age=10 axios.post(postAPI,qs.stringify(postData)) .then(request => { console.log(request) }) .catch(error => { console.log(error) }) - 使用
25. 化繁為簡(jiǎn)的Watchers
- 場(chǎng)景還原:
created(){
this.fetchPostList()
},
watch: {
searchInputValue(){
this.fetchPostList()
}
}
組件創(chuàng)建的時(shí)候我們獲取一次列表,同時(shí)監(jiān)聽(tīng)input框,每當(dāng)發(fā)生變化的時(shí)候重新獲取一次篩選后的列表這個(gè)場(chǎng)景很常見(jiàn),有沒(méi)有辦法優(yōu)化一下呢?
- 招式解析:
首先,在watchers中,可以直接使用函數(shù)的字面量名稱(chēng);其次,聲明immediate:true表示創(chuàng)建組件時(shí)立馬執(zhí)行一次。
watch: {
searchInputValue:{
handler: 'fetchPostList',
immediate: true
}
}
26. 父子組件通信 -- 子組件可以調(diào)用父組件的方法
- 實(shí)現(xiàn)思路
使用
this.$emit()
以下示例為分頁(yè)組件,下面只是將主要的部分代碼貼出
- 父組件
<template>
<!-- 分頁(yè) -->
<pagination @updatePageData="loadPageData"></pagination>
</template>
<script>
export default{
methods:{
loadPageData:function(){
// do something
}
}
}
<script>
- 上面代碼的說(shuō)明
@updatePageData="loadPageData":傳遞方法時(shí)前面使用@;updatePageData是給子組件使用的 父組件loadPageData方法的別名
- 子組件
export default{
methods:{
pageGo:function(){
const me = this
// 調(diào)用父組件方法
me.$emit('updatePageData')
}
}
}
27. 父子組件通信 -- 子組件可動(dòng)態(tài)獲取父組件的數(shù)據(jù)
- 問(wèn)題描述
由于父組件的數(shù)據(jù)是動(dòng)態(tài)獲取的,而子組件初始化時(shí)如果獲取不到數(shù)據(jù)就
Game Over
- 實(shí)現(xiàn)思路
使用
props傳數(shù)據(jù) ;watch監(jiān)聽(tīng)數(shù)據(jù)
以下示例為分頁(yè)組件,下面只是將主要的部分代碼貼出
- 父組件
<template>
<!-- 分頁(yè) -->
<pagination :parentPageData="pageGetData"></pagination>
</template>
<script>
export default{
data(){
return{
pageGetData:[]
}
},
methods:{
getData:function(){
// ajax 請(qǐng)求之后改變 pageGetData 的數(shù)據(jù)
}
}
}
<script>
- 上面代碼的說(shuō)明
:parentPageData="pageGetData"傳遞方法前面使用:;parentPageData是給子組件使用的 父組件pageGetData數(shù)據(jù)的別名
- 子組件
export default{
props:['parentPageData'], // 父組件數(shù)據(jù) 別名
watch:{
// 監(jiān)聽(tīng)父組件數(shù)據(jù)變化實(shí)時(shí)更新數(shù)據(jù)
parentPageData:{
handler: 'loadPageList',
immediate: true
}
},
methods:{
// 加載頁(yè)面數(shù)據(jù)
loadPageList:function(){
// do something
}
}
}
28. vue 多頁(yè)面開(kāi)發(fā)分頁(yè)組件 有搜索功能
29. Vue 在 .vue 文件的樣式 style 標(biāo)簽中使用 background:url() 引入圖片
參考資料 vue 背景圖引入
- 示例代碼片段
<style scoped>
.loading {
position: fixed;
left: 0;
top: 0;
background: url('~@/assets/img/loading-ball.svg') center center no-repeat #fff;
width: 100vw;
height: 100vh;
z-index: 1000;
}
</style>
30. 頁(yè)面加載數(shù)據(jù)之前增加 loading 動(dòng)畫(huà)
31. 封裝一個(gè) axios 的通用方法
- 思路
自定義一個(gè)函數(shù),將其掛載到
Vue對(duì)象的prototype上面,方便在頁(yè)面使用,因?yàn)?axios一般得和qs模塊配合使用
qs 使用參考地址
image.png
- 在
main.js文件中掛載自定義方法
import Vue from 'vue'
import 'babel-polyfill' // 解決 axios 兼容 IE 問(wèn)題
import qs from 'qs' // 解決 axios 數(shù)據(jù)提交格式與后臺(tái)不一致的問(wèn)題 -> name=hehe&age=10
import axios from 'axios'
// import router from './router'
import '@/assets/css/common.css'
import App from './index.vue'
Vue.config.productionTip = false
/**
* 自定義一個(gè)方法封裝 axios 請(qǐng)求,并將其掛載至 Vue 原型鏈
* @param {string} url axios 請(qǐng)求的地址
* @param {string} dataJson axios 向請(qǐng)求地址發(fā)送的 json 數(shù)據(jù)
* @param {function} sucessFn axios 成功回調(diào)函數(shù)
* @param {function} errorFn axios 失敗回調(diào)函數(shù)
*/
Vue.prototype.axiosFn = function(url, dataJson, sucessFn, errorFn) {
axios
.post(url, qs.stringify(dataJson))
.then(response => {
console.log(response)
sucessFn()
})
.catch(error => {
console.log(error)
errorFn()
})
}
/* eslint-disable no-new */
new Vue({
el: '#app',
// router,
components: { App },
template: '<App/>'
})
- 具體的使用就不寫(xiě)了,只需要在調(diào)用方法
axiosFn時(shí)給其傳相應(yīng)的參數(shù)即可
32. 滿足:點(diǎn)擊當(dāng)前元素時(shí),其子元素不會(huì)被點(diǎn)中,且還會(huì)執(zhí)行回調(diào)事件--修改當(dāng)前樣式
- 思路:
現(xiàn)在的需求是有一個(gè)列表,每個(gè)列表元素也是一個(gè)有子元素的嵌套元素,點(diǎn)擊列表的每個(gè)子元素時(shí)給當(dāng)前列表添加類(lèi)名
正常使用官網(wǎng)提供的 事件處理 — Vue.js 文檔,無(wú)法實(shí)現(xiàn)想要的效果,因?yàn)辄c(diǎn)擊傳遞的$event是當(dāng)前點(diǎn)擊的事件,target無(wú)法只得到列表的每個(gè)元素,相對(duì)其子元素而言就是父級(jí)元素
以前解決該方法的時(shí)候使用的是較為笨的方法:判斷target元素,將其強(qiáng)行指向列表元素這樣子
- 新方法
給每個(gè)列表元素里面的子元素設(shè)置
css屬性pointer-events: none;,這樣的話也無(wú)需給@click添加什么修飾符即可實(shí)現(xiàn)需求
33. 子頁(yè)面中在接口請(qǐng)求的時(shí)候,需要主頁(yè)面 App.vue 中請(qǐng)求到的接口的返回值,特殊情況下會(huì)出現(xiàn) Bug
- 錯(cuò)誤重現(xiàn)
示例圖片
頂部是公用的導(dǎo)航組件,數(shù)據(jù)在App.vue中請(qǐng)求,之后存進(jìn)store中去,因?yàn)槭?keep-alive,所以正常情況下頂部的數(shù)據(jù)在第一個(gè)頁(yè)面載入時(shí)便會(huì)儲(chǔ)存到store中,但是如果是第二個(gè)頁(yè)面(因?yàn)橹挥羞@個(gè)頁(yè)面會(huì)需要上面的聯(lián)系電話的數(shù)據(jù))ctrl+f5強(qiáng)制刷新后,store中的數(shù)據(jù)會(huì)被清空,這樣如果在子頁(yè)面請(qǐng)求時(shí)沒(méi)有及時(shí)的將主頁(yè)面中請(qǐng)求到的數(shù)據(jù)聯(lián)系電話存入store中的話,便會(huì)出現(xiàn)子頁(yè)面請(qǐng)求時(shí)獲取聯(lián)系電話字段為空的情況,主要就是因?yàn)檎?qǐng)求是同步發(fā)生的,加載的延遲很低,應(yīng)該是主頁(yè)面請(qǐng)求完成之后子頁(yè)面再請(qǐng)求這樣子。
- 解決
bug后的源碼示例App.vue文件
<template lang="pug">
#app
.header-box(v-loading="layoutHeaderLoading")
LayoutHeader
.nav-box
LayoutNav
keep-alive
.router-view(v-if="loadGuestRecord")
router-view
</template>
<script>
import LayoutNav from "@/components/LayoutNav";
import LayoutHeader from "@/components/LayoutHeader";
import { layoutHeader } from "@/api";
export default {
components: { LayoutNav, LayoutHeader },
data() {
return {
// 是否加載 loading
layoutHeaderLoading: false,
// 頂部 header 數(shù)據(jù)對(duì)象
headerInformation: {},
// 解決 “聯(lián)系電話”未儲(chǔ)存到 store 中,所產(chǎn)生的 bug
loadGuestRecord: true
};
},
created() {
const me = this;
me.getPageData();
},
watch: {
$route(to, from) {
const me = this;
// 對(duì)路由變化作出響應(yīng)...
to.path === "/guestRecord" &&
!me.$store.getters.getMobile &&
(me.loadGuestRecord = false);
}
},
methods: {
/**
* 獲取頂部 header 數(shù)據(jù)
*/
getPageData() {
const me = this;
me.layoutHeaderLoading = true;
layoutHeader
.getCusUserInfo()
.then(resolve => {
console.log(resolve);
resolve.data.code === 200 &&
(me.headerInformation = resolve.data.data);
me.$store.commit("updateMobile", me.headerInformation.mobile);
})
.catch(error => {
console.log(error);
})
.finally(() => {
me.layoutHeaderLoading = false;
!me.loadGuestRecord && (me.loadGuestRecord = true);
});
}
}
};
</script>
- 解決思路,解釋上面的代碼
主要是
頂部的全局請(qǐng)求.then( 單頁(yè)請(qǐng)求 )這個(gè)樣子的,下面是具體的實(shí)施方案:
在主頁(yè)面 data 中增加一個(gè)布爾變量,默認(rèn)為true,用它來(lái)指令是否 v-if 加載子頁(yè)面 router-view ,watch.$route 中判斷路由以及其他條件,為否 布爾變量 = false,再在主頁(yè)面的請(qǐng)求中 .finally( 布爾變量 = true )
34. router-link 導(dǎo)航接收并傳遞全部的 url 參數(shù)
- 先說(shuō)思路:
在主頁(yè)面
App.vue文件中,接收所有的參數(shù)并將其傳遞給nav導(dǎo)航
- 源碼示例:
- 主文件
App.vue
<template lang="pug">
#app
.header-box(v-loading="layoutHeaderLoading")
LayoutHeader(:header-information="headerInformation")
.nav-box
LayoutNav(:url-query="urlQuery")
keep-alive
.router-view(v-if="loadGuestRecord")
router-view
</template>
<script>
import LayoutNav from "@/components/LayoutNav";
import LayoutHeader from "@/components/LayoutHeader";
import { layoutHeader } from "@/api";
export default {
components: { LayoutNav, LayoutHeader },
data() {
return {
// 是否加載 loading
layoutHeaderLoading: false,
// 頂部 header 數(shù)據(jù)對(duì)象
headerInformation: {},
// 接受頁(yè)面的所有參數(shù),并將其賦值到 router-link 參數(shù)上面
urlQuery: {}
};
},
created() {
const me = this;
me.getPageData();
},
watch: {
$route(to, from) {
const me = this;
me.urlQuery = to.query;
}
}
};
</script>
- 組件
LayoutNav.vue
<template lang="pug">
nav.nav
ul.nav-list
li.nav-item(v-for="(item,index) in routers" :key="item.id")
router-link(:to="{ name: item.link, query: urlQuery }")
.nav-icon
i(:class="item.icon")
h3.nav-title {{ item.name }}
</template>
<script>
import { routers } from "./config.json";
export default {
name: "layoutNav",
props: {
urlQuery: Object
},
data() {
return {
routers: routers
};
}
};
</script>
35. 項(xiàng)目增加權(quán)限
- 思路:
- 首先做的項(xiàng)目是從別的項(xiàng)目中跳轉(zhuǎn)過(guò)來(lái)的,所以有
權(quán)限的需求 - 判斷權(quán)限的主要步驟:
- 先增加一個(gè)
401無(wú)權(quán)限頁(yè)面 - 注冊(cè)一個(gè)全局前置守衛(wèi)
- 在守衛(wèi)中判斷加密字符是否一致,路由跳轉(zhuǎn)至相應(yīng)的頁(yè)面去
- 在
App.vue文件中,使用全局的組件時(shí),增加一個(gè)布爾變量控制是否展示組件,默認(rèn)為false展示組件,true是不展示組件,如果watch.$route.to.path === "/401",這個(gè)布爾變量便為true
- 先增加一個(gè)
36. 使用 beforeRouteLeave 銷(xiāo)毀組件,實(shí)現(xiàn)組件的實(shí)時(shí)化
- 代碼
// 導(dǎo)航離開(kāi)該組件的對(duì)應(yīng)路由時(shí)調(diào)用 [可以訪問(wèn)組件實(shí)例 `this`] https://router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E7%BB%84%E4%BB%B6%E5%86%85%E7%9A%84%E5%AE%88%E5%8D%AB
beforeRouteLeave(to, from, next) {
// 銷(xiāo)毀組件,避免通過(guò) vue-router 再次進(jìn)入時(shí),仍是上次的 history 緩存的狀態(tài)
this.$destroy(true);
next();
}
37. 使用官方提供的 component,根據(jù)條件循環(huán)判斷使用不同的組件
- 代碼(其中
van相關(guān)的組件使用方法請(qǐng)查看 vant 官網(wǎng))
<template lang="pug">
.integrated-query
van-tabs(v-model="tabActive" color="#39a0ff" line-width="40" sticky)
van-tab(v-for="item in tabTitleList" :key="item.value" :title="item.name" :name="item.value")
component(:is="item.value")
</template>
<script>
export default {
name: "integratedQuery",
components: {
Overview: () => import("./components/Overview"),
Product: () => import("./components/Product"),
Accounting: () => import("./components/Accounting"),
Order: () => import("./components/Order"),
Assets: () => import("./components/Assets"),
Discount: () => import("./components/Discount"),
Invoice: () => import("./components/Invoice"),
VirtualNetwork: () => import("./components/VirtualNetwork")
},
data() {
return {
tabTitleList: [
{
name: "總覽",
value: "overview"
},
{
name: "產(chǎn)品",
value: "product"
},
{
name: "賬務(wù)",
value: "accounting"
},
{
name: "訂單",
value: "order"
},
{
name: "資產(chǎn)",
value: "assets"
},
{
name: "優(yōu)惠",
value: "discount"
},
{
name: "發(fā)票",
value: "invoice"
},
{
name: "虛擬網(wǎng)",
value: "virtual-network"
}
],
tabActive: "overview"
};
},
}
</script>
38. 基于 vue-cli 腳手架 官方配置 在配置文件中 vue.config.js 引入樣式預(yù)處理器共享的全局變量。
- 配置方式
module.exports = {
/**
* css 相關(guān)配置
*/
css: {
loaderOptions: {
stylus: {
import: "~@/common/stylus/mixin.styl"
}
}
}
}
39. el-input 使用 @keyup.enter 無(wú)效
需要添加
.native修飾符
- 代碼示例
el-input(placeholder="請(qǐng)輸入內(nèi)容" v-model="dataListQuery.queryKey" size="small" clearable @keyup.enter.native="getDataList(leftTreeSelect.id)")
40. el-table 使用 toggleAllSelection 方法無(wú)效
需要在函數(shù)中,增加一個(gè)參數(shù)
true
- 代碼示例
this.$refs.dialogTable.toggleAllSelection(true)
41. el-select 選中之后,數(shù)據(jù)無(wú)法回顯
- 需求描述
目前是有一個(gè)彈窗,內(nèi)部有一個(gè)表格,表格內(nèi)部的每一條數(shù)據(jù)都是可以單獨(dú)編輯的,有
input也有select
image.png
- 問(wèn)題描述
當(dāng)我選中維表或者賬期下拉選項(xiàng)之后,相對(duì)
v-model綁定的數(shù)據(jù)字段已經(jīng)成功更新了,但是在el-select組件中顯示不出來(lái)字典相應(yīng)的文本
- 問(wèn)題解決
經(jīng)過(guò)幾個(gè)小時(shí)的反復(fù)試驗(yàn),最終確定是由于我綁定的數(shù)據(jù)字段,并不是在數(shù)據(jù)初始化時(shí)(接口獲取的時(shí)候)定義的,所導(dǎo)致的字段無(wú)法及時(shí)更新的問(wèn)題。
- 下面簡(jiǎn)單描述下我具體的錯(cuò)誤方式:
接口獲取完數(shù)據(jù),我并沒(méi)有在這個(gè)時(shí)候增加一些需要初始化的字段,而是選擇在將接口數(shù)據(jù)賦值給彈窗子組件的
form之后,再在彈窗子組件中進(jìn)行了字段的初始化,由于我每次賦值都是使用的ES6對(duì)象解構(gòu)賦值的方式,所以最終導(dǎo)致解構(gòu)出的變量已經(jīng)不是最初始賦值時(shí)的對(duì)象,也就無(wú)法同步更新form表單中的數(shù)據(jù)。
所以最終的解決方法就是,在接口獲取完數(shù)據(jù),當(dāng)時(shí)就增加一些初始化必要的字段。
42. 父組件使用 v-model 傳值給子組件,實(shí)現(xiàn)子組件可以同步更新父組件的 v-model 綁定的值
- 參考資料
- 具體實(shí)現(xiàn)的主要代碼
- 父組件
<template lang="pug">
.content
child-el(v-model="data")
</template>
<script>
import "childEl" from "./components"
export default {
components: {
childEl
},
data() {
return {
data: ""
}
}
}
</script>
- 子組件
<template lang="pug">
.content
.text(@click="handleClick") {{ data }}
</template>
<script>
export default {
model: {
prop: "data",
event: 'change'
},
props: {
data: {
type: String,
default: ""
}
},
methods: {
handleClick() {
this.$emit("change", "測(cè)試文本")
}
}
}
</script>
43. 當(dāng)使用 v-for 循環(huán)生成 dom 時(shí),當(dāng)需要再增加 ref 屬性時(shí),獲取組件時(shí)需要加一個(gè)下標(biāo) [0]
- 代碼
//- 組件容器
.tab-content(v-for='item in navListData', :key='item.value')
.tab-bar {{ item.name }}
component(:is='item.value', :ref='item.value')
const componentsList = ['Overview', 'Volume', 'Industry', 'Monographic']
componentsList.forEach((element) => {
const component = this.$refs[`${element}`]
console.log(component)
component && component.length && component[0].getPageData(this.pagePostData)
})
44. 偶發(fā)事件,在 v-if 判斷較多,層級(jí)嵌套較深內(nèi)部也有 v-if 或者 v-show 時(shí),會(huì)出現(xiàn)祖先級(jí) dom 可以渲染,但是內(nèi)部的所有子級(jí) dom 無(wú)法正常渲染的 bug
這里我暫時(shí)采用的解決方案是將祖先級(jí)元素的
v-if修改為v-show
45. 使用 vuex 進(jìn)行數(shù)據(jù)更新的時(shí)候,如果 val 是沒(méi)有變化的 set,那么是不會(huì)被 watch 捕獲到的
<template lang="pug">
//- 通用的 下載文件過(guò)大,提示彈窗
.download-tip
//- 彈窗內(nèi)容
el-dialog(
title='下載提示',
width='450px',
:visible.sync='dialogVisible',
:append-to-body='true',
@close='SetDownloadTipVisible(false)'
)
.dialog-content
.dialog-details
.dialog-text 由于當(dāng)前數(shù)據(jù)下載量過(guò)大,系統(tǒng)將采取異步下載方式,#[br] 請(qǐng)到 #[el-button.color-blue(type='text', @click='checkDownloadTask') 下載界面] 查看下載進(jìn)度。
.dialog-button
el-button(
type='primary',
size='small',
@click='SetDownloadTipVisible(false)'
) 我知道了
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
name: 'DownloadTip',
data() {
return {
dialogVisible: false
}
},
computed: {
...mapGetters(['downloadTipVisible'])
},
watch: {
// 監(jiān)測(cè) store 中的 downloadTipVisible,實(shí)時(shí) 顯示/關(guān)閉 彈窗
downloadTipVisible: {
handler(val) {
this.dialogVisible = val
}
}
},
methods: {
...mapActions(['SetDownloadTaskVisible', 'SetDownloadTipVisible']),
/**
* 查看 通用下載任務(wù)列表
*/
checkDownloadTask() {
this.SetDownloadTipVisible(false)
this.SetDownloadTaskVisible(true)
}
}
}
</script>
<style lang="stylus" scoped>
.el-dialog__wrapper
>>>
.el-dialog__body
padding 0
.el-dialog__header, .el-dialog__footer
display none
.dialog-content
padding 245px 40px 20px
background url('~@/assets/common/img-download.png') top center no-repeat
background-size 100% auto
text-align center
.dialog-details
color #999
line-height 22px
.el-button--text
padding 0
.dialog-button
padding 30px 0
</style>
以上述代碼為例:
downloadTipVisible是store中的變量,如果它的當(dāng)前的值是false,那么SetDownloadTipVisible(false)時(shí),watch中時(shí)無(wú)法捕獲并及時(shí)更新的,所以需要在store初始化時(shí),將downloadTipVisible設(shè)置為false和dialogVisible一致,這樣它們才能實(shí)時(shí)同步。(2021.7.12 續(xù):但是這樣好像也沒(méi)必要多聲明一個(gè)dialogVisible了 /汗)
45. 利用 .sync 修飾符,讓子組件更新父組件的值,常用于彈窗組件的顯示/隱藏
- 主要的源碼部分示例:
- 父組件
<template lang="pug">
.data-lifecycle-preparedness
.page-content(v-show='!performDetailsShow')
perform-details(
v-if='performDetailsShow',
v-model='detailsData',
:perform-details-show.sync='performDetailsShow'
)
</template>
<script>
export default {
name: 'dataLifecycleIndex',
data() {
return {
performDetailsShow: false,
detailsData: {
key: 'test'
}
}
}
</script>
- 子組件
<template lang="pug">
.perform-details
.page-content
el-button.is-active(
plain,
icon='el-icon-back',
size='small',
@click='backClickHandler'
) 返回
</template>
<script>
export default {
name: 'PerformDetails',
model: {
prop: 'detailsData',
event: 'change'
},
props: {
detailsData: {
type: Object,
default: () => {}
},
performDetailsShow: {
type: Boolean,
default: true
}
},
methods: {
/**
* 返回 按鈕 點(diǎn)擊回調(diào)
*/
backClickHandler() {
this.$emit('update:performDetailsShow', false)
this.$emit('change', {})
}
}
}
</script>
是的,示例中不止使用了
.sync還有常用的v-model
46. 某些特定的時(shí)候,會(huì)有需要同時(shí) watch 兩個(gè)不同的屬性的需求,這時(shí)可以使用 computed 新增屬性來(lái)實(shí)現(xiàn)
- 具體實(shí)現(xiàn)的主要代碼實(shí)例
import { mapGetters, mapActions } from 'vuex'
import commonAPI from '@/api/common'
export default {
computed: {
...mapGetters(['sidebar', 'commonPageData', 'tabInfo', 'activeMenuId']),
// 雖然最終想要的結(jié)果是實(shí)現(xiàn)了,但是 watch 時(shí)會(huì)執(zhí)行兩次,請(qǐng)求兩次接口,因?yàn)?isChangeReport 會(huì)改變兩次
isChangeReport() {
const { $route, activeMenuId } = this
return {
$route,
activeMenuId
}
}
},
watch: {
isChangeReport: {
handler(val) {
console.log(val)
const { $route, activeMenuId } = this
if (activeMenuId && !$route.path.includes('serviceAnalysisCommon')) {
commonAPI.getMenuDetailsData(activeMenuId).then((res) => {
const { description } = res
this.SetCommonPageData({
title: description ? '報(bào)表介紹' : '',
content: description || ''
})
})
} else {
this.SetCommonPageData({
title: '',
content: ''
})
}
},
immediate: true
}
},
methods: {
...mapActions(['SetCommonPageData'])
}
}














































