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;
}