個人項目記錄

因公司業(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)為:

  1. 如果先執(zhí)行build:prod芬为,則正常分包,此時再執(zhí)行build:pre蟀悦,結(jié)果也會分包
  2. 如果先執(zhí)行build:pre媚朦,則無法分包,此時再執(zhí)行build:prod日戈,結(jié)果也無法分包
  3. 如果更改一下chunkName询张,再執(zhí)行build:prod,又會正常分包
  4. 刪除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"自動填充密碼

查看了很多文章空入,全體方案陣亡,包括:

  1. 設(shè)置autocomplete
  2. 通過動態(tài)設(shè)置readonly
  3. 動態(tài)更改type為password
  4. 增加兩個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組件,圖片自動上傳(可截圖)
完整步驟:

  1. 下載合適的版本(大部分報錯都是因為tinymce與tinymce-vue不匹配垄分,如果有問題可固定為上述版本)
  2. public文件夾中新增tinymce文件夾宛篇,將node_modules中tinymce里的skins復(fù)制進(jìn)去,可刪除多余的文件薄湿,僅剩所需的css文件即可
-| public
  -| tinymce
    -| skins
      -| ui
        -| oxide
          -| content.min.css
          -| skin.min.css
  1. 下載中文包(看個人需要)叫倍,下載鏈接tinymce語言包
  2. 導(dǎo)入tinymce、tinymce-vue嘿般、zh_CN.js中文包與所需的插件段标,語言包我是放在資源目錄中的,具體根據(jù)項目規(guī)范自行放置導(dǎo)入即可
  3. 如果項目中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中

由于后期設(shè)計導(dǎo)致的交互變更

如上圖所示嘿架,一開始原型所有按鈕都在下方紅框當(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)大量的無意義時間

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市馁痴,隨后出現(xiàn)的幾起案子谊娇,更是在濱河造成了極大的恐慌,老刑警劉巖罗晕,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件济欢,死亡現(xiàn)場離奇詭異赠堵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)法褥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門茫叭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人半等,你說我怎么就攤上這事揍愁。” “怎么了杀饵?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵莽囤,是天一觀的道長。 經(jīng)常有香客問我切距,道長朽缎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任蔚舀,我火速辦了婚禮饵沧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赌躺。我一直安慰自己狼牺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布礼患。 她就那樣靜靜地躺著是钥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缅叠。 梳的紋絲不亂的頭發(fā)上悄泥,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機(jī)與錄音肤粱,去河邊找鬼弹囚。 笑死,一個胖子當(dāng)著我的面吹牛领曼,可吹牛的內(nèi)容都是我干的鸥鹉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼庶骄,長吁一口氣:“原來是場噩夢啊……” “哼毁渗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起单刁,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤灸异,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肺樟,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡檐春,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了儡嘶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喇聊。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蹦狂,靈堂內(nèi)的尸體忽然破棺而出誓篱,到底是詐尸還是另有隱情,我是刑警寧澤凯楔,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布窜骄,位于F島的核電站,受9級特大地震影響摆屯,放射性物質(zhì)發(fā)生泄漏邻遏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一虐骑、第九天 我趴在偏房一處隱蔽的房頂上張望准验。 院中可真熱鬧,春花似錦廷没、人聲如沸糊饱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽另锋。三九已至,卻和暖如春狭归,著一層夾襖步出監(jiān)牢的瞬間夭坪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工过椎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留室梅,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓疚宇,卻偏偏與公主長得像亡鼠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子灰嫉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容