因公司業(yè)務(wù)需要精堕,去往遠(yuǎn)方開發(fā)項目,大半年來除了加班還是加班倦挂,開發(fā)了三個后臺系統(tǒng)楷拳,使用的vue-element-admin框架,該文章如標(biāo)題俩块,為個人記錄黎休。
vue-element-admin
該框架作者有寫了兩篇webpack教學(xué)文章,其中詳細(xì)分析了為何該框架為何這么配置玉凯,兩篇文章建議仔細(xì)閱讀势腮。得益于webpack 4+vue/cli3+作者做的配置,基本已經(jīng)達(dá)到開箱即用了漫仆,不過多介紹
手摸手捎拯,帶你用合理的姿勢使用webpack4(上)
手摸手,帶你用合理的姿勢使用webpack4(下)
vue-cli不支持 eject 來彈出默認(rèn)配置
當(dāng)初剛開始看的時候想要看一下目前vue-cli究竟做了哪些默認(rèn)配置(官網(wǎng)寫的一般不會完全覆蓋歹啼,所以想自己查看默認(rèn)配置)玄渗,可惜并沒有類似 react 的 eject來彈出
關(guān)于為何沒有可查看以下鏈接,比較贊同官方狸眼,為了一些特定場景而去加大他們的工作量藤树,并且并不能帶來實質(zhì)收益,對到框架開發(fā)來講不合理
Eject from Vue CLI / Export Webpack Config
查看vue/cli默認(rèn)配置
如果不清楚vue/cli做的默認(rèn)配置拓萌,那么遇到問題的時候其實只靠網(wǎng)上搜索很難找到自己遇到的問題岁钓,因此先了解vue/cli的默認(rèn)配置,方便出現(xiàn)問題的時候查看是否有某些配置沒更改微王,進(jìn)入項目屡限,在項目根目錄下
- 運(yùn)行命令,在終端輸出:
開發(fā)環(huán)境:npx vue-cli-service inspect --mode development
生產(chǎn)環(huán)境:npx vue-cli-service inspect --mode production - 運(yùn)行命令炕倘,將輸出導(dǎo)入到 js 文件:
開發(fā)環(huán)境:npx vue-cli-service inspect --mode development >> webpack.config.development.js
生產(chǎn)環(huán)境:npx vue-cli-service inspect --mode production >> webpack.config.production.js
參考地址:
vue-cli-service-inspect
修改插件選項
動態(tài)路由無法分包
該問題直到項目后期才發(fā)現(xiàn)钧大,因此已經(jīng)沒法追溯究竟什么時候開始構(gòu)建的時候分包失敗
由于項目中的菜單需要根據(jù)權(quán)限進(jìn)行管理,角色權(quán)限沒有的菜單欄不會顯示到側(cè)邊欄上罩旋,所以由后臺返回對應(yīng)的頁面級組件地址(字符串啊央,例如'./view/order'
)眶诈,前端使用require()來獲取正式的component(其結(jié)果類似于route中component: './view/order'
)
參考地址:
Webpack-Vue 分片優(yōu)化——為什么使用懶加載() => import() 里面的組件沒有分片打包
后改為映射表,通過路徑來映射前端component瓜饥,再使用import來導(dǎo)入對應(yīng)的component(并非真正原因逝撬,因為vue/cli3默認(rèn)就是可以對動態(tài)路由進(jìn)行分包的,改完之后依舊無效乓土。自行配置spliteChunks后雖然成功分包了宪潮,但是動態(tài)路由也失效了,沒法按需加載趣苏,直接一進(jìn)首頁全部請求下來了)狡相,該記錄僅為讓自己以后如果出現(xiàn)問題還能往這方面考慮
真實預(yù)發(fā)布環(huán)境增加了一個pre環(huán)境(也就是多了一個.env.preprod文件),雖然里面已經(jīng)指定了NODE_ENV=production食磕,然而發(fā)現(xiàn)一個奇怪的bug谣光,就是不管怎樣都無法分包(代碼壓縮等都正常),并且該bug表現(xiàn)為:
- 如果先執(zhí)行build:prod芬为,則正常分包,此時再執(zhí)行build:pre蟀悦,結(jié)果也會分包
- 如果先執(zhí)行build:pre媚朦,則無法分包,此時再執(zhí)行build:prod日戈,結(jié)果也無法分包
- 如果更改一下chunkName询张,再執(zhí)行build:prod,又會正常分包
- 刪除node_modules中的緩存浙炼,執(zhí)行build:prod份氧,也會恢復(fù)正常分包
這里涉及知識盲區(qū),不繼續(xù)深究弯屈,以后技術(shù)深度有所突破能知道原因再來更新此文蜗帜,后來與后臺協(xié)定,預(yù)發(fā)布與正式均使用build:prod资厉,不再區(qū)分環(huán)境(因為對到前端來講其實預(yù)發(fā)布與正式環(huán)境代碼都是同一份)
打包后的app.css巨大
構(gòu)建后發(fā)現(xiàn)app.css包居然達(dá)到15M厅缺,餓了么樣式被重復(fù)打包
優(yōu)化:去除入口文件引入的餓了么UI默認(rèn)樣式(官網(wǎng)有說自定義樣式不需要引入默認(rèn)樣式,僅作提示)宴偿,將vue-element-admin中主題配置文件拆分(因為項目中可能會有很多需要引用主題色變量的湘捎,直接從該配置文件獲取主題相關(guān)配置樣式,會導(dǎo)致餓了么樣式被重復(fù)打包)
參考鏈接:
自定義主題樣式文件多次打包 窄刘,解決方法為Tofandel這名用戶所說的窥妇,不要將變量與自定義導(dǎo)入主題放在一塊
icon-font打包后亂碼,瀏覽器能正常顯示
該問題表現(xiàn)為打包結(jié)果亂碼(為一個空芯方塊)娩践,但代碼在瀏覽器中可正常顯示iconf-font活翩。打開控制臺再刷新烹骨,則第一次icon-font必定展示亂碼(也就是為什么Unicode明文在F12的時候會有一次無法識別)
百度出來的肯定是將dart-sass換成node-sass,但根據(jù)自行查看issue纱新,dart-sass確實會導(dǎo)致編譯為Unicode 明文展氓,但貌似并非底層真正原因。由于時間也是換成node-sass來解決脸爱,先做記錄遇汞,后續(xù)再看issue
用最新的框架,打包出來element的字體圖標(biāo)亂碼了簿废? #3526
頁面刷新有時候elementui 的字體圖標(biāo)會亂碼 #19247
現(xiàn)代瀏覽器type="password"自動填充密碼
查看了很多文章空入,全體方案陣亡,包括:
- 設(shè)置autocomplete
- 通過動態(tài)設(shè)置readonly
- 動態(tài)更改type為password
- 增加兩個autocomplete="off"的隱藏密碼輸入框
不管哪一個方案族檬,在切換成password的時候歪赢,自動填充又會出現(xiàn),通過stackoverflow了解到之前這些方案都行不通了单料,原本打算自行實行模態(tài)密碼框來模擬密碼輸入框(本質(zhì)還是text)
但通過stackoverflow了解到可以將type設(shè)置為text埋凯,通過css設(shè)置text-security: disc;
來模擬密碼輸入框,但這個時候下面也有人說了復(fù)制粘貼會復(fù)制到全都是小圓點(diǎn)扫尖,但密碼本來就不該讓用戶復(fù)制粘貼白对,所以再將密碼框的粘貼事件禁用即可onpaste="return false"
如何阻止瀏覽器自動填充賬號密碼
下面這篇文章我沒嘗試,稍微看了一下也是動態(tài)設(shè)置type和readonly這些方案换怖,但是多管齊下甩恼,不確定是否有用,有興趣的可以自行嘗試
完美解決 element-ui input=password 在瀏覽器會自動填充密碼的問題
封裝后的v-password組件
<template>
<el-input
type="text"
:class="['model-password', {'is-show-password': isShowPassword}]"
v-bind="$attrs"
onpaste="return false"
v-on="$listeners"
>
<slot slot="prefix" name="prefix" />
<slot slot="suffix" name="suffix">
<span class="el-input__icon el-icon-circle-close el-input__clear" @click="clearValue" />
<span class="el-input__icon el-icon-view el-input__clear" @click="toggleType" />
</slot>
<slot slot="prepend" name="prepend" />
<slot slot="append" name="append" />
</el-input>
</template>
<script>
/**
* 現(xiàn)代瀏覽器type="password"自動填充密碼沉颂,查看了很多文章条摸,全體方案陣亡,方案包括:
* 設(shè)置autocomplete
* 通過動態(tài)設(shè)置readonly
* 動態(tài)更改type為password
* 增加兩個autocomplete="off"的隱藏密碼輸入框
* -------------------------------------------------------------------
* 自用的模態(tài)密碼輸入框铸屉,組件特點(diǎn):
* 1. 不出現(xiàn)自動填充賬號密碼彈出層
* 2. 密碼不允許粘貼
* 3. 使用方式完全同 el-input
* 4. type固定為text钉蒲,不可變更,因為password會導(dǎo)致自動填充
*/
export default {
name: 'VPassword',
data () {
return {
isShowPassword: true
}
},
computed: {
},
methods: {
toggleType () {
this.isShowPassword = !this.isShowPassword
},
clearValue () {
this.$emit('input', '')
}
}
}
</script>
<style lang="scss" scoped>
.model-password {
.el-icon-circle-close {
display: none;
}
&:hover {
.el-icon-circle-close {
display: inline-block;
}
}
}
.is-show-password {
::v-deep .el-input__inner {
text-security: disc;
-webkit-text-security:disc;
}
}
</style>
前端文件下載
前端文件下載最簡單的方式是<a></a>
使用download屬性
<a download href="文件下載地址">文件名</a>
但是這種下載方法不能跨域抬探,非同域download會無效子巾。而使用新開頁面下載的方式會有一個彈窗閃一下影響體驗。由于項目中有需要下載第三方文件的需求小压,還有私有桶提供的blob文件流线梗,所以更改為使用文件id請求模擬點(diǎn)擊下載,代碼如下
// 工具函數(shù)
const Tool = {
_getBlob (ret, fileName, file) {
const type = ret.type
const blob = new Blob([ret], { type }) // type必須指定怠益,即使時流文件仪搔,否則火狐下載無后綴
const ie = navigator.userAgent.match(/MSIE\s([\d.]+)/)
const ie11 = navigator.userAgent.match(/Trident\/7.0/) && navigator.userAgent.match(/rv:11/)
const ieEDGE = navigator.userAgent.match(/Edge/g)
const ieVer = (ie ? ie[1] : (ie11 ? 11 : (ieEDGE ? 12 : -1)))
if (ie && ieVer < 10) {
Message.error('您的瀏覽器版本過低,請切換到IE EDGE模式或更換瀏覽器')
}
if (ieVer > -1) {
window.navigator.msSaveBlob(blob, fileName)
} else {
const url = window.URL.createObjectURL(blob)
file.href = url
}
},
/**
* @description 文件下載蜻牢,非流文件(如視頻音頻等)
* @param {String, Array} id 文件id烤咧,如果是批量下載必須傳入數(shù)組id集合
* @param {String} fileName 保存時候的文件名
*/
async downLoad (id, fileName = '') {
const file = document.createElement('a')
const body = document.querySelector('body')
let ret = {}
if (!Array.isArray(id) || !id.length) {
Message('下載參數(shù)異常')
return
}
// 業(yè)務(wù)接口
ret = await fileModel.zipFile(fileName, id)
if (!ret.size || ret.size < 1024) {
Message.error('文件異常偏陪,該文件無法下載')
} else {
fileName = fileName || ret.fileName // 實現(xiàn)前端自定義文件名或使用后臺返回的文件名,需要在request.js補(bǔ)充
this._getBlob(ret, fileName, file)
}
// IE和火狐必須制定下載的格式煮嫌,否則下載后丟失文件后綴笛谦,目前來看會有類型,因為之前沒有content-type?
file.download = fileName
file.style.display = 'none'
body.appendChild(file)
file.click()
body.removeChild(file)
window.URL.revokeObjectURL(file)
}
}
request.js
service.interceptors.response.use(
response => {
const contentType = response.headers['content-type'].toLowerCase()
// 返回請求體是流文件
if (contentType.includes('octet-stream') || contentType.includes('vnd.ms-excel') || contentType.includes('zip')) {
if (response.status === 200) {
// 將后臺返回的晴天球頭文件名填充到響應(yīng)體中
response.data.fileName = window.decodeURI(response.headers['content-disposition'].split('=')[1])
return Promise.resolve(response.data)
} else {
Message({
message: '文件下載失敗昌阿,請稍后嘗試',
type: 'error',
duration: 2 * 1000
})
}
}
}
)
vue-echarts
項目中使用了百度可視化插件ECharts饥脑,本來想直接使用vue-echarts,但用vue-charts一直偶現(xiàn)實例化時必要數(shù)據(jù)無數(shù)據(jù)(options中的數(shù)據(jù))懦冰,導(dǎo)致一直報錯灶轰,github有同樣的issuse但作者并無理會,所以直接放棄刷钢,自行封裝了一個簡易版的v-charts笋颤,options為echarts實例所需的對象
<template>
<div ref="echarts" class="echarts" />
</template>
<script>
/**
* 簡易版 vue-echarts,用vue-charts一直偶現(xiàn)實例化時必要數(shù)據(jù)無數(shù)據(jù)(options中的數(shù)據(jù))内地,導(dǎo)致一直報錯伴澄,github有同樣的issuse但作者并無理會,所以直接放棄
* 該組件不支持響應(yīng)式數(shù)據(jù)阱缓,響應(yīng)式數(shù)據(jù)實現(xiàn)后發(fā)現(xiàn)性能損耗很大(觸發(fā)極為頻繁)秉版,不想跟vue-charts一樣提供props來讓用戶決定,因為大部分使用者將會忽略文檔提示
* 所以直接不支持響應(yīng)式數(shù)據(jù)茬祷,重新渲染的時機(jī)需用戶自行調(diào)用render方法
* echart整體包過大,使用按需引入并蝗,否則整體echarts就九百多K祭犯,優(yōu)化后可減少五百多K,自行根據(jù)需要自行選用按需導(dǎo)入或全量導(dǎo)入
*/
import echarts from 'echarts/lib/echarts'
import 'echarts/lib/chart/bar'
import 'echarts/lib/chart/pie'
import 'echarts/lib/chart/line'
import 'echarts/lib/component/tooltip'
import 'echarts/lib/component/title'
import 'echarts/lib/component/legend'
import 'echarts/lib/component/grid'
import resize from './resize'
export default {
mixins: [resize],
props: {
options: {
type: Object,
default: () => ({})
}
},
data () {
return {
echarts: null
}
},
mounted () {
this.initEcharts()
},
// 暫時留著滚停,不是很確定vue銷毀是否能讓ECherts也銷毀
destroyed () {
this.echart.dispose()
this.echart = null
},
methods: {
initEcharts () {
const echartsDom = this.$refs.echarts
this.echart = echarts.init(echartsDom)
this.echart.setOption(this.options)
},
// 重新繪制圖表
render () {
this.$nextTick(() => {
this.echart.setOption(this.options)
})
}
}
}
</script>
<style lang="scss" scoped>
.echarts {
width: 100%;
height: 100%;
}
</style>
resize.js
import { debounce } from '@/utils'
export default {
data () {
return {
$_sidebarElm: null,
$_resizeHandler: null
}
},
mounted () {
this.initListener()
},
// 假如頁面走緩存沃粗,則離開頁面銷毀
activated () {
if (!this.$_resizeHandler) {
this.initListener()
}
this.resize()
},
deactivated () {
this.destroyListener()
},
beforeDestroy () {
this.destroyListener()
},
methods: {
$_sidebarResizeHandler (e) {
if (e.propertyName === 'width') {
this.$_resizeHandler()
}
},
initListener () {
this.$_resizeHandler = debounce(() => {
this.resize()
}, 100)
window.addEventListener('resize', this.$_resizeHandler)
// 側(cè)邊導(dǎo)航條因為不會觸發(fā)瀏覽器resize,所以需要進(jìn)行事件監(jiān)聽
this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
},
destroyListener () {
window.removeEventListener('resize', this.$_resizeHandler)
this.$_resizeHandler = null
this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
},
resize () {
const { echart } = this
echart && echart.resize()
}
}
}
富文本編輯器Tinymce
網(wǎng)上評測文章很多键畴,不再贅述最盅。主要強(qiáng)調(diào)的是版本,網(wǎng)上有一篇比較通用的文章起惕,但版本是4x的涡贱,下載的時候已經(jīng)是5x,并且tinymce-vue也已經(jīng)到2x惹想,因此不能跟著4x的方式來做问词,本人使用的版本如下:
"tinymce": "5.0.3", "@tinymce/tinymce-vue": "2.0.0"
vue-element-admin原作者里面也有封裝了一個tinymce插件,但使用的CDN的方式通過script插入嘀粱。由于為真實項目激挪,使用這種方式不太合適辰狡,因此自行封裝了一版tinymceEditor組件,圖片自動上傳(可截圖)
完整步驟:
- 下載合適的版本(大部分報錯都是因為tinymce與tinymce-vue不匹配垄分,如果有問題可固定為上述版本)
- public文件夾中新增tinymce文件夾宛篇,將node_modules中
tinymce
里的skins
復(fù)制進(jìn)去,可刪除多余的文件薄湿,僅剩所需的css文件即可
-| public
-| tinymce
-| skins
-| ui
-| oxide
-| content.min.css
-| skin.min.css
- 下載中文包(看個人需要)叫倍,下載鏈接tinymce語言包
- 導(dǎo)入tinymce、tinymce-vue嘿般、zh_CN.js中文包與所需的插件段标,語言包我是放在資源目錄中的,具體根據(jù)項目規(guī)范自行放置導(dǎo)入即可
- 如果項目中tinymce是在彈層當(dāng)中炉奴,下拉菜單會因為層級過低導(dǎo)致不可見逼庞,需要自行調(diào)整z-index,沒有什么好辦法瞻赶,本人解決方案是通過!important來調(diào)整層級的
.tox-tinymce-aux {
z-index: 2021 !important;
}
完整代碼:
<template>
<div class="custom-tinymce">
<tinymceEditor ref="tinymce_editor" v-model="tinyContent" :init="initParams" />
</div>
</template>
<script>
import fileModel from '@/api/file'
import 'tinymce/tinymce'
import tinymceEditor from '@tinymce/tinymce-vue'
// 語言包需要自行下載赛糟,https://liubing.me/goto/https://www.tiny.cloud/get-tiny/language-packages/
import CN from '@/assets/tinymceLangs/zh_CN.js'
import 'tinymce/themes/silver'
import 'tinymce/plugins/lists'// 列表
import 'tinymce/plugins/image'// 插入上傳圖片
import 'tinymce/plugins/media'// 插入視頻
import 'tinymce/plugins/table'// 表格
import 'tinymce/plugins/wordcount'// 字?jǐn)?shù)統(tǒng)計
import 'tinymce/plugins/quickbars' // 快捷工具欄
import 'tinymce/plugins/fullscreen' // 全屏編輯
import 'tinymce/plugins/preview' // 預(yù)覽
import 'tinymce/plugins/autolink' // 自動識別鏈接
import 'tinymce/plugins/charmap' // 特殊字符
import 'tinymce/plugins/searchreplace' // 搜索替換
import 'tinymce/plugins/link'
import 'tinymce/icons/default'
// import 'tinymce/skins/ui/oxide/skin.css' // 雖然能正常運(yùn)行,但插件底層還會去請求靜態(tài)目錄下的css文件砸逊,因此不能采用該做法
import './custom.scss' // 調(diào)整層級的css
export default {
components: {
tinymceEditor
},
props: {
plugins: {
type: [String, Array],
default: 'wordcount quickbars fullscreen preview lists autolink link charmap searchreplace table'
},
toolbar: {
type: [String, Array],
default: 'undo redo | alignleft aligncenter alignright| bullist numlist | charmap bold italic underline strikethrough forecolor backcolor | quickimage link | fullscreen preview table | fontsizeselect | formatselect | fontselect | lineheight'
},
value: {
type: String,
default: ''
}
},
data () {
return {
tinyContent: this.value
}
},
computed: {
// 不直接用tinymce璧南,防止后續(xù)多個實例的場景出現(xiàn)bug
editor () {
return this.$refs.tinymce_editor.editor
},
initParams () {
return {
language_url: CN,
language: 'zh_CN',
height: 500,
plugins: this.plugins, // 菜單欄
toolbar: this.toolbar, // 工具欄
branding: false,
object_resizing: false,
quickbars_insert_toolbar: '', // 這個不配為空,每次換行都會出現(xiàn)快捷工具欄师逸,影響操作
elementpath: false,
// 解決粘貼圖片后司倚,不自動上傳,而是使用base64編碼篓像。
urlconverter_callback: (url, node) => {
if (node === 'img' && url.startsWith('blob:')) {
this.editor.uploadImages()
console.log(url, node)
}
return url
},
// 按照文檔 issue中的說法动知,但是怎么設(shè)置都沒法阻止默認(rèn)圖片變成bolb或base64
// images_upload_url: fileModel.uploadToPublicStatic,
// automatic_uploads: false,
// 插件會將bolb的文件替換未上傳成功的文件,但是失敗的時候會遺留bolb圖片文件员辩,導(dǎo)致文章請求體過大盒粮,所以需要刪除
images_upload_handler: async (blobInfo, succFun) => {
const file = blobInfo.blob()
const formData = new FormData()
formData.append('file', file)
const { flag, data } = await fileModel.uploadToPublicStaticMethod(formData)
if (flag) {
succFun(data)
} else {
// 這里如果用Undo返回上一步不太合理,考慮到網(wǎng)絡(luò)延遲奠滑,上傳圖片后繼續(xù)打字什么的丹皱,可能無法將圖片回退,所以還是走遍歷刪除
const imgElms = Array.from(this.editor.dom.doc.getElementsByTagName('img'))
imgElms.forEach(item => {
if (item.getAttribute('src').startsWith('blob:')) {
this.editor.execCommand('delete', false, item)
}
})
}
},
// 走import或require宋税,線上依然會去請求靜態(tài)目錄中的css文件摊崭,導(dǎo)致有404請求(雖然已經(jīng)打包成bundle,功能樣式都正常杰赛,找不到該插件底層代碼時哪里寫了)
// 靜態(tài)文件均放在public中爽室,并且content_css不指定,線上還會再去請求一個content.css,只能寫死
skin_url: '/tinymce/skins/ui/oxide',
content_css: '/tinymce/skins/ui/oxide/content.min.css',
menu: {
file: { title: 'File', items: ' preview ' },
view: { title: 'View', items: 'preview fullscreen' },
insert: { title: 'Insert', items: 'image link media template codesample inserttable | charmap emoticons hr | pagebreak nonbreaking anchor toc | insertdatetime' },
format: { title: 'Format', items: 'bold italic underline delectline strikethrough superscript subscript codeformat | formats blockformats fontformats fontsizes align lineheight | forecolor backcolor | removeformat' },
tools: { title: 'Tools', items: 'spellchecker spellcheckerlanguage | code wordcount' },
table: { title: 'Table', items: 'inserttable | cell row column | tableprops deletetable' }
},
default_link_target: '_blank',
font_formats: "微軟雅黑='微軟雅黑';宋體='宋體';黑體='黑體';仿宋='仿宋';楷體='楷體';隸書='隸書';幼圓='幼圓';Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings",
fontsize_formats: '12px 14px 16px 18px 24px 36px 48px',
visual: false,
toolbar_mode: 'sliding'
}
}
},
watch: {
tinyContent () {
this.$emit('input', this.tinyContent)
}
},
mounted () {
// tinymce.init({})
}
}
</script>
使用方法
<tinymce v-model="formData.content" />
跨組件阔墩,將按鈕插入到頭部navbar中
如上圖所示嘿架,一開始原型所有按鈕都在下方紅框當(dāng)中,根據(jù)邏輯展示對應(yīng)按鈕(因此開發(fā)的時候都寫在業(yè)務(wù)組件之中)啸箫。
后來設(shè)計出視覺的時候?qū)⑺袇^(qū)塊按鈕轉(zhuǎn)移到頭部navbar當(dāng)中(出現(xiàn)該問題的原因很多耸彪,從流程到相關(guān)人員專業(yè)度等等,這里就不吐槽了)忘苛。此時有一個致命的問題:超級多頁面都有該功能蝉娜,且都有不同的邏輯在其中,如果從框架上進(jìn)行更改扎唾,工作量極大召川。
這里跨越的組件層級已經(jīng)無法通過常規(guī)的傳輸方式來解決(不僅是跨組件,而且是跨了N多個組件文件)胸遇。要解決這個問題的前提是必須保證現(xiàn)有邏輯的運(yùn)行荧呐,原本的v-if或v-show也能正常判斷
因此開發(fā)了一個指令與一個組件,用于該場景纸镊,解決思路是將通過指令倍阐,將原有的節(jié)點(diǎn)插入到navbar中去。由于navbar在系統(tǒng)中僅有一個逗威,所以寫了一個帶ID的空白節(jié)點(diǎn)作為落腳點(diǎn)峰搪,具有該指令的節(jié)點(diǎn)初始化則直接不可見。
/**
* @author yose
* @description 該指令僅適用于頁面沒走緩存(非keep-alive)凯旭,否則需要使用組件sysbtn
*/
const install = function (Vue) {
Vue.directive('sysbtn', {
bind (el) {
el.style.display = 'none'
appendToSysbtn(el, Vue)
},
update (el) {
appendToSysbtn(el, Vue)
},
unbind (el) {
const sysbtnDom = document.getElementById('sys_btn')
if (!sysbtnDom) { return } // 路由路徑為退出/404等非layout界面概耻,無法獲取到該dom節(jié)點(diǎn)
const childrenNodes = Array.from(sysbtnDom.childNodes)
childrenNodes.forEach(item => el.appendChild(item))
}
})
}
function appendToSysbtn (elm, vue) {
vue.prototype.$nextTick().then(() => {
const childrenNodes = Array.from(elm.children)
const sysBtnNode = document.getElementById('sys_btn') // navbar中被插入的節(jié)點(diǎn),上圖最后插入的位置
childrenNodes.forEach(item => sysBtnNode.appendChild(item))
})
}
export default install
如果頁面走緩存(也就是keep-alive),該指令在離開頁面后也無法釋放按鈕(因為不會觸發(fā)unbind)罐呼,所以需要一個功能一致的組件來監(jiān)聽頁面的進(jìn)入activated
與離開deactivated
<template>
<div v-if="!isLeavePage" v-sysbtn>
<slot />
</div>
</template>
<script>
/**
* @author yose
* @description 由于頁面走緩存咐蚯,因此不會執(zhí)行unbind,指令中無法得知是否離開當(dāng)前頁面弄贿,如果是沒有走緩存的頁面級組件,則直接使用v-sysbtn指令即可
*/
export default {
name: 'SysBtn',
data () {
return {
isLeavePage: false
}
},
activated () {
this.isLeavePage = false
},
deactivated () {
this.isLeavePage = true
}
}
</script>
使用方式示例(主要是想說明原來的代碼除了增加v-sysbtn指令矫膨,其余都無需更改就能完成新的視覺需求):
<div v-sysbtn>
<el-button
v-if="isShowBalance"
id="btn_add_deduct"
v-permission="'DrawingOrder-2-orderBaseInformation-addDeductOrAddMoney'"
class="btn-w120 main-plain-btn"
plain
@click="handleAddDeduct"
>訂單加/扣款</el-button>
<el-button
v-if="isWaiting"
id="btn_submit_drawing"
v-permission="'DrawingOrder-2-quotationProcurementPlanDetailList-submitCompleted'"
:loading="submitLoading"
class="btn-w84"
type="primary"
@click="handleSubmit"
>提交完成</el-button>
</div>
組件上
<template slot="buttons">
<el-button type="primary" class="btn-w120 check-btn" @click="getUsers">查詢</el-button>
<sys-btn>
<el-button type="primary" class="btn-w120" @click="handleUser({})">新增</el-button>
</sys-btn>
</template>
主要為提供一個思路差凹,不要僅限于組件間傳值,遇到部分場景不劍走偏鋒侧馅,會導(dǎo)致需求難度極度復(fù)雜危尿,并且耗費(fèi)大量的無意義時間