本文是鄙人在進修Vue和Bootstrap過程當中碰到問題解決的一些思緒,重要形貌了項目搭建,組件封裝、獵取、編輯、更新的一步步完成,一些解決方案也沒找到準確的官方API,還請大拿們多多提點。
項目引見
旨在經由過程項目標情勢同時進修Vue和Bootstrap,完成一個在線設置頁面的功用。經由過程Bootstrap封裝好的組件款式供應界面須要的組件,經由過程Vue完成組件狀況變動及頁面襯着。
項目地點
https://github.com/shixia226/bootstrap-vue-designer
項目設想
組件模塊區
供應可用於拖拽到編輯區的一切組件,分種別展現該功用與本進修目標關聯不強,且其重要拖拽功用比較花時間,臨時放置
頁面編輯區
供應一切已添加到頁面的組件的編輯預覽,並供應組件增,刪,排版,選中功用增,刪,排版功用能夠與模板區的拖拽功用連繫,一樣臨時放置
- 組件設置區
供應詳細組件內部狀況檢察及變動功用
項目搭建
基礎的項目搭建,建立
index.html
,index.js
設置好webpack
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>Vue Demo</title> </head> <body> <script src="../index.js"></script> </body> </html>
module.exports = { entry: './index.js', output: { filename: 'index.js' }, module: { rules: [{ test: /^[^.]+\.scss$/, use: [ 'style-loader', 'css-loader', 'sass-loader' ] }, { test: /(\.js|\.vue)$/, exclude: /(node_modules|bower_components)(?!.*webpack-dev-server)/, loader: 'babel-loader', query: { "presets": ["env"] } }] } };
Bootstrap款式引入
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
Vue框架引入
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
運轉
//node webpack-dev-server --port=9926 //Browser http://localhost:9926/
第一個組件Badage
Bootstrap官網例子:
<span class="badge badge-light badge-pill">9</span>
組件剖析
- badge-light 款式能夠替換成badge-primary等,能夠設置成屬性變量用於挑選哪一個色彩;
- badge-pill 款式有和無表現是不一樣的,能夠設置屬性變量用於掌握要不應款式;
- 9 文本內容作為終究的展現內容,能夠設置成屬性變量;
- 組件名取
widget-badge
.
Vue組件封裝
Vue.component('widget-badge', {
template: `<span :class="['badge', theme ? 'badge-' + theme : '', pill ? 'badge-pill' : '']">{{text}}</span>`,
props: ['theme', 'pill', 'text']
});
組件展現
html
<div class="app">
<widget-badge></widget-badge>
</div>
js
new Vue({
el: '.app'
})
組件設置
以上步驟后革新瀏覽器應該是能夠看到組件效果了,但該組件的一切屬性都是在標籤內寫死的,沒法在編輯頁面動態設置
動態屬性
- vue 中 props 屬性是不允許動態變動的,平常都只能變動 data 中的屬性值,所以須要把 props 中的一切可變屬性拷貝一份到 data 中,且定名上不能雷同,所以在此先劃定 data 中的一切屬性都以字母’v’開首;
每一個可變屬性加一個編輯項,對應屬性名
name="vpropA"
, 取值為當前屬性值:value="vpropsA"
,一切的編輯項悉數定義屬性 editor 上。沒找到對應獵取editor屬性值的API,但經由過程剖析vue對象發明能夠經由過程vue實例vm.$options.editor獵取到該定義值,臨時先就這麼用着。
組件封裝變動以下:
Vue.component('widget-badge', {
template: `<span :class="['badge', 'badge-' + vtheme, vpill ? 'badge-pill' : '']">{{vtext}}</span>`,
props: ['theme', 'pill', 'text'],
editor: `
<input name="vtheme" :value="vtheme" />
<input name="vpill" :value="vpill" />
<input name="vtext" :value="vtext" />
`,
data() {
return {
vtheme: this.theme || 'secondary',
vpill: this.pill,
vtext: this.text || 'Badge'
}
}
});
屬性設置面板
- 點擊差別的組件要展現對應的(差別的)設置面板
依據點擊元素獵取所屬vue組件
vue原本就是經由過程狀況更新的體式格局變動dom的,所以很少有dom相干的api,又只得剖析vue實例里的數據,發明$children彷佛就是直接下級組件的一個鳩合,且$children每一項里都又一個$el的屬性對應到現實DOM元素
function getVueCmp(vm, elem) {
let pelems = [],
$root = vm.$el;
while (elem !== $root) {
pelems.push(elem);
elem = elem.parentNode;
}
return getVueCmpByPelem(vm, pelems);
}
function getVueCmpByPelem(vm, pelems) {
let $children = vm.$children;
if ($children) {
for (let i = 0, len = $children.length; i < len; i++) {
let vcmp = $children[i],
$el = vcmp.$el,
idx = pelems.indexOf($el);
if (idx !== -1) {
pelems.length = idx;
return getVueCmpByPelem(vcmp, pelems);
}
}
}
return vm;
}
增添點擊事宜
<div class="app" @click="showPpt">
<widget-badge></widget-badge>
</div>
獵取組件及時數據
依據前面的數據定名劃定規矩直接遍歷$data中一切以字母’v’開首的屬性
function getVueCmpData(vcmp) {
if (!vcmp) return {};
let $data = vcmp.$data,
data = {};
let names = Object.getOwnPropertyNames($data);
for (let i = 0, len = names.length; i < len; i++) {
let name = names[i];
if (name.charAt(0) === 'v') {
data[name.substr(1)] = $data[name];
}
}
return data;
}
數據更新
在vue根節點上設置全局監聽事宜,然後在屬性值中定義$emit要領觸發該監聽事宜
- 根節點設置監聽事宜,並將監聽效果反應到當前選中的組件上
created() {
this.$on('changeppt', function(name, value) {
if (vcmp) {
let names = name.split('.'),
data = vcmp,
len = names.length - 1;
for (let i = 0; i < len; i++) {
data = data[names[i]];
}
data[names[len]] = value;
}
})
}
- 封裝編輯器的輸入框為組件以下:
Vue.component('editor-text', {
template: `<input v-model="vvalue" @change="$root.$emit('changeppt', name, vvalue)">`,
props: ['name', 'value'],
data() {
return {
vvalue: this.value
}
}
})
- 變動編輯器設置以下
{
...
/*
editor: `
<input name="vtheme" :value="vtheme" />
<input name="vpill" :value="vpill" />
<input name="vtext" :value="vtext" />
`,
*/
editor: `
<editor-text name="vtheme" :value="theme" ></editor-text>
<input name="vpill" :value="pill" ></editor-text>
<input name="vtext" :value="text" ></editor-text>
`,
...
}
vue終究初始化變動以下
new Vue({
el: '.app',
data: {
pptCmp: undefined
},
watch: {
pptCmp(vcmp) {
new Vue({
el: '.ppt',
template: '<div class="ppt">' + (vcmp ? vcmp.$options.editor || '' : '') + '</div>',
data() {
return getVueCmpData(vcmp, true);
},
created() {
this.$on('changeppt', function(name, value) {
if (vcmp) {
let names = name.split('.'),
data = vcmp,
len = names.length - 1;
for (let i = 0; i < len; i++) {
data = data[names[i]];
}
data[names[len]] = value;
}
})
}
})
}
},
methods: {
showPpt: function(evt) {
let elem = evt.target;
if (!document.querySelector('.ppt').contains(elem)) {
let vcmp = getVueCmp(this, elem);
if (vcmp === this.$root) {
vcmp = null;
}
this.pptCmp = vcmp;
}
}
}
}