Vue組件開發實例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="SJStyle.css" />
<style>
/* * 由於IE不支持<template>標簽,所以template標簽中的內容在IE下會直接顯示出來。 * 將模板設為隱藏即可解決這個問題,template標簽各瀏覽器支持請參考:http://caniuse.com/#feat=template */ #grid-template, #dialog-template { display: none; } </style>
</head>
<body>
<div id="app">
<!-- 上部查詢框:通過searchQuery過濾 -->
<div class="container">
<div class="form-group">
<label>Search</label>
<input type="text" class="search-input" v-model="searchQuery" />
</div>
</div>
<!-- 下部表格框 -->
<div class="container">
<simple-grid :data-list="people" :columns="columns" :search-key="searchQuery">
</simple-grid>
</div>
</div>
<!-- 父組件simple-grid -->
<template id="grid-template">
<table>
<!-- 表頭,從columns里面取數據 -->
<thead>
<tr>
<th v-for="col in columns"> {{ col.name | capitalize}} </th>
<th>Delete</th>
</tr>
</thead>
<!-- 表體,從people綁定的dataList里面取數據 -->
<tbody>
<!-- 這里有個filterBy過濾器的使用 -->
<tr v-for="(index,entry) in dataList | filterBy searchKey">
<!-- 從columns里面取數據,判斷是否有主鍵,有主鍵就顯示編輯信息;沒有就直接顯示 --> <!-- 此處注意正好columns里的屬性的值col.name,正好要是dataList里的entry的key,所以通過entry[col.name]去獲取值 -->
<td v-for="col in columns">
<span v-if="col.isKey"><a href="javascript:void(0)" @click="openEditItemDialog(entry[col.name])">{{entry[col.name]}}</a></span>
<span v-else>{{entry[col.name]}}</span>
</td>
<!-- 刪除的操作按鈕,傳了整條數據的信息作為參數 -->
<td class="text-center">
<button class="btn-danger" @click="deleteItem(entry)">delete</button>
</td>
</tr>
</tbody>
</table>
<!-- 新建操作按鈕,傳了標題作為參數 -->
<div class="container">
<button class="btn" @click="openNewItemDialog('Create New Item')">Create</button>
</div>
<!-- 此處就是通過props指定父子組件通信的東西,就是讓父組件的內容和子組件的內容對應 -->
<modal-dialog :mode="mode" :title="title" :item="item" :fields="columns" v-on:create-item="createItem" v-on:update-item="updateItem">
</modal-dialog>
</template>
<!-- 子組件modal-dialog -->
<template id="dialog-template">
<div class="dialogs">
<!-- v-bind:class通過show的值去判斷是否新增class名,所以我們通過控制這個show值來達到顯示和隱藏子組件的目的 -->
<div class="dialog" v-bind:class="{ 'dialog-active': show }">
<div class="dialog-content">
<header class="dialog-header">
<h1 class="dialog-title">{{ title }}</h1>
</header>
<div class="dialog-body">
<!-- 通過遍歷fields里的值,來加整個div -->
<div v-for="field in fields" class="form-group">
<label>{{ field.name }}</label>
<!-- 如果column有dataSource屬性就顯示下拉列表 --> <!-- 如果是編輯模式,並且有主鍵屬性,就禁用 -->
<select v-if="field.dataSource" v-model="item[field.name]" :disabled="mode === 2 && field.isKey">
<!-- 遍歷column里的dataSource屬性的值 -->
<option v-for="opt in field.dataSource" :value="opt">{{ opt }}</option>
</select>
<!-- 如果沒有dataSource屬性,就顯示文本框 -->
<input v-else type="text" v-model="item[field.name]" :disabled="mode === 2 && field.isKey">
</div>
</div>
<footer class="dialog-footer">
<div class="form-group">
<label></label>
<button class="btn-save" v-on:click="save">Save</button>
<button class="btn-close" v-on:click="close">Close</button>
</div>
</footer>
</div>
</div>
<div class="dialog-overlay"></div>
</div>
</template>
<script src="vue.js"></script>
<script>
//注冊父組件simple-grid
Vue.component('simple-grid', { template: '#grid-template', //父組件其實也就是vue實例的子組件,所以也要通過props去與vue實例里的數據進行對應綁定:這里dataList綁定people,columns綁定columns,searchKey綁定searchQuery
props: ['dataList', 'columns', 'searchKey'], //定義父組件里的數據
data: function() { return { mode: 0, title: '', keyColumn: '', item: {} } }, //通過鈎子函數去設定name為主鍵,並賦值給keyColumn,就是this.keyColumn就是獲取到主鍵"name"
ready: function() { for(var i = 0; i < this.columns.length; i++) { if(this.columns[i].isKey) { this.keyColumn = this.columns[i]['name'] break; } } }, methods: { //點擊新建按鈕,彈出子組件
openNewItemDialog: function(title) { // 對話框的標題
this.title = title // mode = 1表示新建模式
this.mode = 1
// 初始化this.item
this.item = {} // 廣播事件,showDialog是modal-dialog組件的一個方法,傳入參數true表示顯示對話框
this.$broadcast('showDialog', true) }, //點擊編輯彈出子組件
openEditItemDialog: function(key) { // 根據主鍵查找當前修改的數據
var currentItem = this.findItemByKey(key);//key就是傳過來的name參數 // 修改對話框的標題
this.title = 'Edit Item - ' + key; // mode = 2表示修改模式
this.mode = 2; // 將選中的數據拷貝到this.item
this.item = this.initItemForUpdate(currentItem); // 父組件讓子組件顯示:廣播事件,傳入參數true表示顯示對話框
this.$broadcast('showDialog', true); }, // 就是一個深拷貝函數:彈出修改數據的對話框時,使用對象的深拷貝
initItemForUpdate(p, c) { c = c || {}; for(var i in p) { // 屬性i是否為p對象的自有屬性
if(p.hasOwnProperty(i)) { // 屬性i是否為復雜類型
if(typeof p[i] === 'object') { // 如果p[i]是數組,則創建一個新數組 // 如果p[i]是普通對象,則創建一個新對象
c[i] = Array.isArray(p[i]) ? [] : {}; // 遞歸拷貝復雜類型的屬性
this.initItemForUpdate(p[i], c[i]); } else { // 屬性是基礎類型時,直接拷貝
c[i] = p[i]; } } } return c; }, //該函數的作用就是通過傳過來的name,查找返回那條數據
findItemByKey: function(key) { var keyColumn = this.keyColumn;//獲取當前的主鍵
for(var i = 0; i < this.dataList.length; i++) { if(this.dataList[i][keyColumn] === key) {//this.dataList綁定的是vue實例的people對象
return this.dataList[i] } } }, //判斷新增的item是否已經存在
itemExists: function() { var keyColumn = this.keyColumn;//獲取主鍵列
for(var i = 0; i < this.dataList.length; i++) { //判斷當前數據的主鍵列值是否已經在dataList里面存在
if(this.item[keyColumn] === this.dataList[i][keyColumn]){ return true; } } return false; }, //新建
createItem: function() { var keyColumn = this.keyColumn; if(!this.itemExists()) { // 將item追加到dataList
this.dataList.push(this.item); // 廣播事件,傳入參數false表示隱藏對話框
this.$broadcast('showDialog', false) // 新建完數據后,重置item對象
this.item = {}; } else { alert(keyColumn + ' "' + this.item[keyColumn] + '" is already exists'); } }, //編輯
updateItem: function() { // 獲取主鍵列
var keyColumn = this.keyColumn; for(var i = 0; i < this.dataList.length; i++) { // 根據主鍵查找要修改的數據,然后將this.item數據更新到this.dataList[i]
if(this.dataList[i][keyColumn] === this.item[keyColumn]) { //如果主鍵的值相同,就把當前item里的值賦值給dataList
for(var j in this.item) { this.dataList[i][j] = this.item[j]; } break; } } // 廣播事件,傳入參數false表示隱藏對話框
this.$broadcast('showDialog', false); // 修改完數據后,重置item對象
this.item = {}; }, //刪除
deleteItem: function(entry) { var data = this.dataList;//獲取當前dataList數據
data.forEach(function(item, i) { if(item === entry) { data.splice(i, 1); return; } }) } }, //局部注冊子組件modal-dialog
components: { 'modal-dialog': { template: '#dialog-template', data: function() { return { show: false // 對話框默認是不顯示的
} }, /* 與父組件里面對應的數據: * mode = 1是新增數據模式,mode = 2是修改數據模式 * title表示對話框的標題內容 * fields表示對話框要顯示的數據字段數組 * item是由simple-dialog傳下來,用於綁定表單字段的 */ props: ['mode', 'title', 'fields', 'item'], methods: { //關閉
close: function() { this.show = false;//讓子組件隱藏
}, //保存
save: function() { //如果是新增或編輯,就向父組件發派生事件
if(this.mode === 1) { // 使用$dispatch調用simple-grid的create-item事件
this.$dispatch('create-item'); } else if(this.mode === 2) { // 使用$dispatch調用simple-grid的update-item事件
this.$dispatch('update-item'); } } }, events: { 'showDialog': function(show) {//show接受父組件傳來的參數,決定是顯示還是隱藏對話框
this.show = show; } } } } }) var demo = new Vue({ el: '#app', data: { searchQuery: '', columns: [{ name: 'name', isKey: true }, { name: 'age' }, { name: 'sex', dataSource: ['Male', 'Female'] }], people: [{ name: 'Jack', age: 30, sex: 'Male' }, { name: 'Bill', age: 26, sex: 'Male' }, { name: 'Tracy', age: 22, sex: 'Female' }, { name: 'Chris', age: 36, sex: 'Male' }] } }) </script>
</body>
</html>
css文件:
* { margin: 0; padding: 0; box-sizing: border-box; font-family: Helvetica, simhei, Arial, sans-serif; } html { font-size: 1rem; } body{ margin-top: 100px; } table, td, th { border-collapse: collapse; border-spacing: 0 } table { width: 100%; } td, th { border: 1px solid #bcbcbc; padding: 5px 10px; } th { padding: 10px; font-weight: 400; color: #fff; background: #0090d3; cursor: pointer; } tr:nth-of-type(odd) { background: #fff } tr:nth-of-type(even) { background: #eee } h1{ font-size: 1.5rem; margin-bottom: 2rem; } input { outline: none } input[type=text] { padding: 3px 6px; font-size: 1.2rem; border: 1px solid #ccc; } input[type=text]:focus { border-color: #0090d3; transition: .3s ease-in; } button { display: inline-block; box-sizing: border-box; padding: 10px 30px; background: #0090d3; color: #fff; border: 1px solid #0090d3; border-radius: 3px; outline: 0; transition: .4s ease-out; } button:hover, button:focus { opacity: 0.8; cursor: pointer; transition: .15s ease-in; } #app { margin: 0 auto; max-width: 640px; } .btn-danger{ padding: 5px 15px; border: 1px solid salmon; background: salmon; } .btn-save{ border: 1px solid #0090d3; background: #0090d3; } .btn-close{ border: 1px solid #ccc; background: #ccc; } .container { padding-left: 15px; padding-right: 15px; margin: 10px; } .search-input { width: 80%; } .form-group { margin: 10px; } .form-group > label { display: inline-block; padding-right: 1rem; width: 5rem; text-align: right; } .form-group > input, .form-group > select { display: inline-block; height: 1.8rem; line-height: 1.8rem; } .text-center { text-align: center; } .dialog { width: 480px; position: fixed; left: 50%; top: 2em; transform: translateX(-50%); z-index: 2000; visibility: hidden; backface-visibility: hidden; perspective: 1300px; } .dialog-active { visibility: visible; } .dialog-active .dialog-content { opacity: 1; transform: rotateY(0); } .dialog-active ~ .dialog-overlay { opacity: 1; visibility: visible; } .dialog-content { border-radius: 3px; background: #fff; overflow: hidden; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); transition: .5s ease-in-out; opacity: 0; transform-style: preserve-3d; transform: rotateY(-70deg); } .dialog-header { background: #0090d3; color: #fff; } .dialog-title { margin: 0; font-size: 2em; text-align: center; font-weight: 200; line-height: 2em; } .dialog-body { padding: 2em; } .dialog-footer { margin: 0 2em; padding: 1em 0; border-top: 1px solid rgba(0, 0, 0, 0.1); } .dialog-overlay { content: ""; position: fixed; visibility: hidden; top: 0; left: 0; right: 0; bottom: 0; z-index: 1000; opacity: 0; background: rgba(0, 0, 0, 0.5); transition: all .6s; }