Edit in JSFiddle

Vue.component('simple-grid', {
			template: '#grid-template',
			props: ['dataList', 'columns', 'searchKey'],
			data: function() {
				return {
					sortKey: '',
					sortOrder: -1,
					mode: 0,
					item: {},
					title: '',
					//currentKey: '',
					test: false //测试是可行的 不过必须是同步状态
				}
			},
			methods: {
				deleteItem: function(entry) {
					var data = this.dataList
					data.forEach(function(item, i) {
						if (item === entry) {
							data.splice(i, 1)
							return
						}
					})
				},
				sortBy: function(key) {
					//alert(key.name)
					this.sortKey = key.name;
					this.sortOrder = this.sortOrder > 0 ? -1 : 1;
				},
				openNewItemDialog: function(title) {
					//测试不用广播打开对话框 //测试是可行的 不过必须是同步状态 //广播利于解耦
					this.test = true;
					//对话框标题
					this.title = title;
					// mode = 1 表示新建模式
					this.mode = 1;
					// 初始化this.item  //原版为空
					this.item = {
						sex: 'Male'
					};
					// 广播事件,showDialog是modal-dialog组件的一个方法,传入参数true表示显示对话框
					//广播比派发简单多了 不用绕一下不错.
					this.$broadcast('showDialog', true);
				},
				openEditItemDialog: function(index, title) {
						// 对话框标题
						this.title = title;
						// 模式2 修改
						this.mode = 2;
						// 修改数据
						this.item = this.dataList[index];
						// 打开对话框
						this.$broadcast('showDialog',true);
						//修改当期key 以便保存的时候用.
						this.currentKey = index;

//下面是曾经测试用的

						// //终于明白API说的 不能检测是啥意思了. 这个可以这么设置,但是页面不能自动刷新
						// //虽然数据变了,但是页面上不显示
						// this.dataList[index] = {
						// 		name: 'elick',
						// 		age: 34,
						// 		sex: 'Male'
						// 	}
						// 	//所以用这种方法可以更新数据数组,那么原本的那些复杂的写法真就不懂干啥用了
						// this.dataList.$set(index, {
						// 	name: 'xwiwi',
						// 	age: '24',
						// 	sex: 'Female'
						// })
						// var aa = this.dataList[index];
						// console.log(aa);
						//

				},
				createItem: function() {
					if(!this.itemExists()){
						alert('这是方法');
						//测试不用广播关闭对话框
						this.test = false;
						//将item追加到dataList
						this.dataList.push(this.item);
						//广播事件, 传入参数false表示隐藏对话框
						this.$broadcast('showDialog', false);
						//新建万数据后,清空item对象
						this.item = {}
					}else{
						alert('已经存在了');
					}
				},
				updateItem: function() {
					//这真见了鬼了,不需要更改dataList他就自动和item关联起来了.
					//this.dataList.$set(this.currentKey,this.item);
					//this.currentKey = '';
					this.item = {}
					this.$broadcast('showDialog',false)
				},
				itemExists: function () {
					for (var i = 0; i < this.dataList.length; i++) {
						if(this.dataList[i].name == this.item.name) return true;
					}
					return false
				}
			},
			events: {
				//这是事件处理器
				'create-item': function() {
					alert('这是事件');
					//测试不用广播关闭对话框
					this.test = false;
					//将item追加到dataList
					this.dataList.push(this.item);
					//广播事件, 传入参数false表示隐藏对话框
					this.$broadcast('showDialog', false);
					//新建万数据后,清空item对象
					this.item = {}
				}
			},
			components: {
				'modal-dialog': {
					template: '#dialog-template',
					data: function() {
						return {
							show: false
						}
					},
					props: ['mode', 'title', 'fields', 'item', 'test'],
					methods: {
						close: function() {
							// this.test = false
							this.show = false
						},
						save: function() {
							if (this.mode === 1) {
								// 使用$dispatch调用simple-grid的create-item方法
								//为什么不在子组件直接处理呢?	//因为数据都在父组件里,并没有把数组穿过来
								//这也没必要因为这是个添加内容的组件,不需要知道那么多
								this.$dispatch('create-item'); //这里的事件'create-item' 是父事件
								//@create-item="createItem" 这两个都是父的东西
							}
							if (this.mode === 2) {
								this.$dispatch('update-item');
							}

						}
					},
					events: {
						// 显示或隐藏对话框
						//这个也是为什么要通过广播呢?
						'showDialog': function(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'
				}]
			}
		})
	<div id="app">
		<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>

	<template id="grid-template">
		<table>
			<thead>
				<tr>
					<th v-for="col in columns" @click="sortBy(col)">
						{{ col.name | capitalize}}
					</th>
					<th>
						Delete
					</th>
				</tr>
			</thead>
			<tbody>
				<tr v-for="(index,entry) in dataList | filterBy searchKey | orderBy sortKey sortOrder">
					<td v-for="col in columns">
						<span v-if="col.isKey"><a href="javascript:void(0)" @click="openEditItemDialog(index,'Edit item '+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>
		<!-- 测试不用广播打开对话, test 必须是 :test.sync 同步模式 这样在子组件关闭的时候,父组件也知道状态  可能广播更科学吧 利于解耦 -->
		<!-- 前面是子后面是父  :fields="columns" :fields 子 columns 父-->
		<!--  @create-item="createItem" 这个绑定事件 就和其他不一样了 两个都是父东西 把父事件 create-item 绑定到 createItem 方法 -->
		<!-- 又看遍文档  -@-> 在子组件上监听自定义事件(当子组件触发 “my-event” 时将调用事件处理器) -->
		<!-- 其实是约定,自定义子组件有这么一个事件, 如果子组件触发,发送了这个事件, 父组件就用 **同名** 的事件处理器,处理这个事件的响应 -->
		<!-- 或者这么理解吧 有助于记忆 绑定子发送的事件 @create-item 到父方法 createItem  这样就又是 子:父 -->
		<modal-dialog :mode="mode" :title="title" :fields="columns" :item="item" :test.sync='test' @create-item="createItem" @update-item="updateItem">
		</modal-dialog>
		<div class="container">
			<button class="btn" @click="openNewItemDialog('Create new item')">Create</button>
		</div>

	</template>

	<template id="dialog-template">
		<div id="dialogs">
			<!-- <div class="dialog" :class="{ 'dialog-active' : test }"> -->
			<div class="dialog" :class="{ 'dialog-active' : show }">
				<div class="dialog-content">
					<header class="dialog-header">
						<h1 class="dialog-title">{{ title }}</h1>
					</header>
					<div v-for="field in fields" class="form-group">
						<label>{{ field.name }}</label>
						<select v-if="field.dataSource" v-model="item[field.name]" :disabled="mode === 2 && field.isKey ">
							<option v-for="opt in field.dataSource" :value="opt"> {{ opt }}</option>
						</select>
						<input v-else type="text" v-model="item[field.name]" :disabled="mode === 2 && field.isKey">
					</div>
					<footer class="dialog-footer">
						<div class="form-group">
							<label></label>
							<button @click="save">Save</button>
							<button @click="close">Close</button>
						</div>
					</footer>
				</div>
			</div>
			<div class="dialog-overlay"></div>
		</div>
	</template>
* {
  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;
}