Commit 47a7f21dc601f9ba8526a6c4a60af61e1ca90ccd

Authored by 梁灏
1 parent 2d43f26b

support Cascader

support Cascader
@@ -38,4 +38,6 @@ DropdownItem key 改为 name, Dropdown 的 visible 要使用 @on-visible-change @@ -38,4 +38,6 @@ DropdownItem key 改为 name, Dropdown 的 visible 要使用 @on-visible-change
38 DropdownItem 里,this.$parent.$parent 与1.0 有区别 38 DropdownItem 里,this.$parent.$parent 与1.0 有区别
39 ### Menu 39 ### Menu
40 MenuItem 和 Submenu 的 key 改为了 name 40 MenuItem 和 Submenu 的 key 改为了 name
41 -Menu 的 activeKey 改为 activeName,openKeys 改为 openNames  
42 \ No newline at end of file 41 \ No newline at end of file
  42 +Menu 的 activeKey 改为 activeName,openKeys 改为 openNames
  43 +### Cascader
  44 +Caspanel 的 sublist 从 prop -> data
@@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
29 - [x] Slider 29 - [x] Slider
30 - [ ] DatePicker 30 - [ ] DatePicker
31 - [ ] TimePicker 31 - [ ] TimePicker
32 -- [ ] Cascader 32 +- [x] Cascader
33 - [ ] Transfer 33 - [ ] Transfer
34 - [x] InputNumber 34 - [x] InputNumber
35 - [x] Rate 35 - [x] Rate
@@ -42,6 +42,7 @@ li + li { border-left: solid 1px #bbb; padding-left: 10px; margin-left: 10px; } @@ -42,6 +42,7 @@ li + li { border-left: solid 1px #bbb; padding-left: 10px; margin-left: 10px; }
42 <li><router-link to="/breadcrumb">Breadcrumb</router-link></li> 42 <li><router-link to="/breadcrumb">Breadcrumb</router-link></li>
43 <li><router-link to="/menu">Menu</router-link></li> 43 <li><router-link to="/menu">Menu</router-link></li>
44 <li><router-link to="/spin">Spin</router-link></li> 44 <li><router-link to="/spin">Spin</router-link></li>
  45 + <li><router-link to="/cascader">Cascader</router-link></li>
45 </ul> 46 </ul>
46 </nav> 47 </nav>
47 <router-view></router-view> 48 <router-view></router-view>
@@ -132,6 +132,10 @@ const router = new VueRouter({ @@ -132,6 +132,10 @@ const router = new VueRouter({
132 { 132 {
133 path: '/spin', 133 path: '/spin',
134 component: require('./routers/spin.vue') 134 component: require('./routers/spin.vue')
  135 + },
  136 + {
  137 + path: '/cascader',
  138 + component: require('./routers/cascader.vue')
135 } 139 }
136 ] 140 ]
137 }); 141 });
examples/routers/cascader.vue
1 -<template>  
2 - {{ text }}  
3 - <Cascader :data="data" @on-change="handleChange">  
4 - <a href="javascript:void(0)">选择</a>  
5 - </Cascader> 1 +<!--<template>-->
  2 + <!--<div>-->
  3 + <!--<Cascader :data="data" v-model="value1"></Cascader>-->
  4 + <!--{{ value1 }}-->
  5 + <!--<div @click="c">change</div>-->
  6 + <!--</div>-->
  7 +<!--</template>-->
  8 +<!--<script>-->
  9 + <!--export default {-->
  10 + <!--data () {-->
  11 + <!--return {-->
  12 + <!--value1: [],-->
  13 + <!--data: [{-->
  14 + <!--value: 'beijing',-->
  15 + <!--label: '北京',-->
  16 + <!--children: [-->
  17 + <!--{-->
  18 + <!--value: 'gugong',-->
  19 + <!--label: '故宫'-->
  20 + <!--},-->
  21 + <!--{-->
  22 + <!--value: 'tiantan',-->
  23 + <!--label: '天坛'-->
  24 + <!--},-->
  25 + <!--{-->
  26 + <!--value: 'wangfujing',-->
  27 + <!--label: '王府井'-->
  28 + <!--}-->
  29 + <!--]-->
  30 + <!--}, {-->
  31 + <!--value: 'jiangsu',-->
  32 + <!--label: '江苏',-->
  33 + <!--children: [-->
  34 + <!--{-->
  35 + <!--value: 'nanjing',-->
  36 + <!--label: '南京',-->
  37 + <!--children: [-->
  38 + <!--{-->
  39 + <!--value: 'fuzimiao',-->
  40 + <!--label: '夫子庙',-->
  41 + <!--}-->
  42 + <!--]-->
  43 + <!--},-->
  44 + <!--{-->
  45 + <!--value: 'suzhou',-->
  46 + <!--label: '苏州',-->
  47 + <!--children: [-->
  48 + <!--{-->
  49 + <!--value: 'zhuozhengyuan',-->
  50 + <!--label: '拙政园',-->
  51 + <!--},-->
  52 + <!--{-->
  53 + <!--value: 'shizilin',-->
  54 + <!--label: '狮子林',-->
  55 + <!--}-->
  56 + <!--]-->
  57 + <!--}-->
  58 + <!--],-->
  59 + <!--}]-->
  60 + <!--}-->
  61 + <!--},-->
  62 + <!--methods: {-->
  63 + <!--c () {-->
  64 + <!--this.value1 = ['jiangsu', 'suzhou', 'zhuozhengyuan']-->
  65 + <!--}-->
  66 + <!--}-->
  67 + <!--}-->
  68 +<!--</script>-->
  69 +
6 70
7 - <Row>  
8 - <i-col span="4">  
9 - Disabled <Switch :checked.sync="disabled"></Switch>  
10 - </i-col>  
11 - <i-col span="4">  
12 - <Cascader :data="data" :value.sync="value1" :disabled="disabled"></Cascader>  
13 - </i-col>  
14 - </Row> 71 +<template>
  72 + <div>
  73 + <Cascader :data="data" v-model="value2" change-on-select></Cascader>
  74 + {{ value2 }}
  75 + <div @click="c">change</div>
  76 + </div>
15 </template> 77 </template>
16 <script> 78 <script>
17 export default { 79 export default {
18 data () { 80 data () {
19 return { 81 return {
20 - disabled: false,  
21 - text: '未选择', 82 + value2: [],
22 data: [{ 83 data: [{
23 value: 'beijing', 84 value: 'beijing',
24 label: '北京', 85 label: '北京',
@@ -69,8 +130,8 @@ @@ -69,8 +130,8 @@
69 } 130 }
70 }, 131 },
71 methods: { 132 methods: {
72 - handleChange (value, selectedData) {  
73 - this.text = selectedData.map(o => o.label).join(', '); 133 + c () {
  134 + this.value2 = ['jiangsu', 'suzhou', 'zhuozhengyuan']
74 } 135 }
75 } 136 }
76 } 137 }
src/components/cascader/cascader.vue
@@ -5,39 +5,43 @@ @@ -5,39 +5,43 @@
5 <i-input 5 <i-input
6 readonly 6 readonly
7 :disabled="disabled" 7 :disabled="disabled"
8 - :value.sync="displayRender" 8 + v-model="displayRender"
9 :size="size" 9 :size="size"
10 :placeholder="placeholder"></i-input> 10 :placeholder="placeholder"></i-input>
11 - <Icon type="ios-close" :class="[prefixCls + '-arrow']" v-show="showCloseIcon" @click.stop="clearSelect"></Icon> 11 + <Icon type="ios-close" :class="[prefixCls + '-arrow']" v-show="showCloseIcon" @click.native.stop="clearSelect"></Icon>
12 <Icon type="arrow-down-b" :class="[prefixCls + '-arrow']"></Icon> 12 <Icon type="arrow-down-b" :class="[prefixCls + '-arrow']"></Icon>
13 </slot> 13 </slot>
14 </div> 14 </div>
15 - <Dropdown v-show="visible" transition="slide-up">  
16 - <div>  
17 - <Caspanel  
18 - v-ref:caspanel  
19 - :prefix-cls="prefixCls"  
20 - :data.sync="data"  
21 - :disabled="disabled"  
22 - :change-on-select="changeOnSelect"  
23 - :trigger="trigger"></Caspanel>  
24 - </div>  
25 - </Dropdown> 15 + <transition name="slide-up">
  16 + <Drop v-show="visible">
  17 + <div>
  18 + <Caspanel
  19 + ref="caspanel"
  20 + :prefix-cls="prefixCls"
  21 + :data="data"
  22 + :disabled="disabled"
  23 + :change-on-select="changeOnSelect"
  24 + :trigger="trigger"></Caspanel>
  25 + </div>
  26 + </Drop>
  27 + </transition>
26 </div> 28 </div>
27 </template> 29 </template>
28 <script> 30 <script>
29 import iInput from '../input/input.vue'; 31 import iInput from '../input/input.vue';
30 - import Dropdown from '../select/dropdown.vue'; 32 + import Drop from '../select/dropdown.vue';
31 import Icon from '../icon/icon.vue'; 33 import Icon from '../icon/icon.vue';
32 import Caspanel from './caspanel.vue'; 34 import Caspanel from './caspanel.vue';
33 import clickoutside from '../../directives/clickoutside'; 35 import clickoutside from '../../directives/clickoutside';
34 import { oneOf } from '../../utils/assist'; 36 import { oneOf } from '../../utils/assist';
  37 + import Emitter from '../../mixins/emitter';
35 38
36 const prefixCls = 'ivu-cascader'; 39 const prefixCls = 'ivu-cascader';
37 40
38 export default { 41 export default {
39 name: 'Cascader', 42 name: 'Cascader',
40 - components: { iInput, Dropdown, Icon, Caspanel }, 43 + mixins: [ Emitter ],
  44 + components: { iInput, Drop, Icon, Caspanel },
41 directives: { clickoutside }, 45 directives: { clickoutside },
42 props: { 46 props: {
43 data: { 47 data: {
@@ -92,7 +96,8 @@ @@ -92,7 +96,8 @@
92 visible: false, 96 visible: false,
93 selected: [], 97 selected: [],
94 tmpSelected: [], 98 tmpSelected: [],
95 - updatingValue: false // to fix set value in changeOnSelect type 99 + updatingValue: false, // to fix set value in changeOnSelect type
  100 + currentValue: this.value
96 }; 101 };
97 }, 102 },
98 computed: { 103 computed: {
@@ -107,7 +112,7 @@ @@ -107,7 +112,7 @@
107 ]; 112 ];
108 }, 113 },
109 showCloseIcon () { 114 showCloseIcon () {
110 - return this.value && this.value.length && this.clearable; 115 + return this.currentValue && this.currentValue.length && this.clearable;
111 }, 116 },
112 displayRender () { 117 displayRender () {
113 let label = []; 118 let label = [];
@@ -120,11 +125,12 @@ @@ -120,11 +125,12 @@
120 }, 125 },
121 methods: { 126 methods: {
122 clearSelect () { 127 clearSelect () {
123 - const oldVal = JSON.stringify(this.value);  
124 - this.value = this.selected = this.tmpSelected = []; 128 + const oldVal = JSON.stringify(this.currentValue);
  129 + this.currentValue = this.selected = this.tmpSelected = [];
125 this.handleClose(); 130 this.handleClose();
126 - this.emitValue(this.value, oldVal);  
127 - this.$broadcast('on-clear'); 131 + this.emitValue(this.currentValue, oldVal);
  132 +// this.$broadcast('on-clear');
  133 + this.broadcast('Caspanel', 'on-clear');
128 }, 134 },
129 handleClose () { 135 handleClose () {
130 this.visible = false; 136 this.visible = false;
@@ -139,8 +145,8 @@ @@ -139,8 +145,8 @@
139 }, 145 },
140 onFocus () { 146 onFocus () {
141 this.visible = true; 147 this.visible = true;
142 - if (!this.value.length) {  
143 - this.$broadcast('on-clear'); 148 + if (!this.currentValue.length) {
  149 + this.broadcast('Caspanel', 'on-clear');
144 } 150 }
145 }, 151 },
146 updateResult (result) { 152 updateResult (result) {
@@ -148,25 +154,30 @@ @@ -148,25 +154,30 @@
148 }, 154 },
149 updateSelected (init = false) { 155 updateSelected (init = false) {
150 if (!this.changeOnSelect || init) { 156 if (!this.changeOnSelect || init) {
151 - this.$broadcast('on-find-selected', this.value); 157 + this.broadcast('Caspanel', 'on-find-selected', {
  158 + value: this.currentValue
  159 + });
152 } 160 }
153 }, 161 },
154 emitValue (val, oldVal) { 162 emitValue (val, oldVal) {
155 if (JSON.stringify(val) !== oldVal) { 163 if (JSON.stringify(val) !== oldVal) {
156 - this.$emit('on-change', this.value, JSON.parse(JSON.stringify(this.selected)));  
157 - this.$dispatch('on-form-change', this.value, JSON.parse(JSON.stringify(this.selected))); 164 + this.$emit('on-change', this.currentValue, JSON.parse(JSON.stringify(this.selected)));
  165 + // todo 事件
  166 +// this.$dispatch('on-form-change', this.currentValue, JSON.parse(JSON.stringify(this.selected)));
158 } 167 }
159 } 168 }
160 }, 169 },
161 - ready () { 170 + mounted () {
162 this.updateSelected(true); 171 this.updateSelected(true);
163 - },  
164 - events: {  
165 - // lastValue: is click the final val  
166 - // fromInit: is this emit from update value  
167 - 'on-result-change' (lastValue, changeOnSelect, fromInit) { 172 + this.$on('on-result-change', (params) => {
  173 + // lastValue: is click the final val
  174 + // fromInit: is this emit from update value
  175 + const lastValue = params.lastValue;
  176 + const changeOnSelect = params.changeOnSelect;
  177 + const fromInit = params.fromInit;
  178 +
168 if (lastValue || changeOnSelect) { 179 if (lastValue || changeOnSelect) {
169 - const oldVal = JSON.stringify(this.value); 180 + const oldVal = JSON.stringify(this.currentValue);
170 this.selected = this.tmpSelected; 181 this.selected = this.tmpSelected;
171 182
172 let newVal = []; 183 let newVal = [];
@@ -176,30 +187,37 @@ @@ -176,30 +187,37 @@
176 187
177 if (!fromInit) { 188 if (!fromInit) {
178 this.updatingValue = true; 189 this.updatingValue = true;
179 - this.value = newVal;  
180 - this.emitValue(this.value, oldVal); 190 + this.currentValue = newVal;
  191 + this.emitValue(this.currentValue, oldVal);
181 } 192 }
182 } 193 }
183 if (lastValue && !fromInit) { 194 if (lastValue && !fromInit) {
184 this.handleClose(); 195 this.handleClose();
185 } 196 }
186 - },  
187 - 'on-form-blur' () {  
188 - return false;  
189 - },  
190 - 'on-form-change' () {  
191 - return false;  
192 - } 197 + });
193 }, 198 },
  199 + // todo 事件 这是因为内部的input会触发,应该组织
  200 +// events: {
  201 +// 'on-form-blur' () {
  202 +// return false;
  203 +// },
  204 +// 'on-form-change' () {
  205 +// return false;
  206 +// }
  207 +// },
194 watch: { 208 watch: {
195 visible (val) { 209 visible (val) {
196 if (val) { 210 if (val) {
197 - if (this.value.length) { 211 + if (this.currentValue.length) {
198 this.updateSelected(); 212 this.updateSelected();
199 } 213 }
200 } 214 }
201 }, 215 },
202 - value () { 216 + value (val) {
  217 + this.currentValue = val;
  218 + },
  219 + currentValue () {
  220 + this.$emit('input', this.currentValue);
203 if (this.updatingValue) { 221 if (this.updatingValue) {
204 this.updatingValue = false; 222 this.updatingValue = false;
205 return; 223 return;
src/components/cascader/casitem.vue
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 </template> 3 </template>
4 <script> 4 <script>
5 export default { 5 export default {
  6 + name: 'Casitem',
6 props: { 7 props: {
7 data: Object, 8 data: Object,
8 prefixCls: String, 9 prefixCls: String,
src/components/cascader/caspanel.vue
1 <template> 1 <template>
2 - <ul v-if="data && data.length" :class="[prefixCls + '-menu']">  
3 - <Casitem  
4 - v-for="item in data"  
5 - :prefix-cls="prefixCls"  
6 - :data.sync="item"  
7 - :tmp-item="tmpItem"  
8 - @click.stop="handleClickItem(item)"  
9 - @mouseenter.stop="handleHoverItem(item)"></Casitem>  
10 - </ul><Caspanel v-if="sublist && sublist.length" :prefix-cls="prefixCls" :data.sync="sublist" :disabled="disabled" :trigger="trigger" :change-on-select="changeOnSelect"></Caspanel> 2 + <span>
  3 + <ul v-if="data && data.length" :class="[prefixCls + '-menu']">
  4 + <Casitem
  5 + v-for="item in data"
  6 + :prefix-cls="prefixCls"
  7 + :data="item"
  8 + :tmp-item="tmpItem"
  9 + @click.native.stop="handleClickItem(item)"
  10 + @mouseenter.native.stop="handleHoverItem(item)"></Casitem>
  11 + </ul><Caspanel v-if="sublist && sublist.length" :prefix-cls="prefixCls" :data="sublist" :disabled="disabled" :trigger="trigger" :change-on-select="changeOnSelect"></Caspanel>
  12 + </span>
11 </template> 13 </template>
12 <script> 14 <script>
13 import Casitem from './casitem.vue'; 15 import Casitem from './casitem.vue';
  16 + import Emitter from '../../mixins/emitter';
14 17
15 export default { 18 export default {
16 name: 'Caspanel', 19 name: 'Caspanel',
  20 + mixins: [ Emitter ],
17 components: { Casitem }, 21 components: { Casitem },
18 props: { 22 props: {
19 data: { 23 data: {
@@ -22,12 +26,6 @@ @@ -22,12 +26,6 @@
22 return []; 26 return [];
23 } 27 }
24 }, 28 },
25 - sublist: {  
26 - type: Array,  
27 - default () {  
28 - return [];  
29 - }  
30 - },  
31 disabled: Boolean, 29 disabled: Boolean,
32 changeOnSelect: Boolean, 30 changeOnSelect: Boolean,
33 trigger: String, 31 trigger: String,
@@ -36,9 +34,15 @@ @@ -36,9 +34,15 @@
36 data () { 34 data () {
37 return { 35 return {
38 tmpItem: {}, 36 tmpItem: {},
39 - result: [] 37 + result: [],
  38 + sublist: []
40 }; 39 };
41 }, 40 },
  41 + watch: {
  42 + data () {
  43 + this.sublist = [];
  44 + }
  45 + },
42 methods: { 46 methods: {
43 handleClickItem (item) { 47 handleClickItem (item) {
44 if (this.trigger !== 'click' && item.children) return; 48 if (this.trigger !== 'click' && item.children) return;
@@ -58,10 +62,20 @@ @@ -58,10 +62,20 @@
58 62
59 if (item.children && item.children.length){ 63 if (item.children && item.children.length){
60 this.sublist = item.children; 64 this.sublist = item.children;
61 - this.$dispatch('on-result-change', false, this.changeOnSelect, fromInit); 65 +// this.$dispatch('on-result-change', false, this.changeOnSelect, fromInit);
  66 + this.dispatch('Cascader', 'on-result-change', {
  67 + lastValue: false,
  68 + changeOnSelect: this.changeOnSelect,
  69 + fromInit: fromInit
  70 + });
62 } else { 71 } else {
63 this.sublist = []; 72 this.sublist = [];
64 - this.$dispatch('on-result-change', true, this.changeOnSelect, fromInit); 73 +// this.$dispatch('on-result-change', true, this.changeOnSelect, fromInit);
  74 + this.dispatch('Cascader', 'on-result-change', {
  75 + lastValue: true,
  76 + changeOnSelect: this.changeOnSelect,
  77 + fromInit: fromInit
  78 + });
65 } 79 }
66 }, 80 },
67 updateResult (item) { 81 updateResult (item) {
@@ -84,13 +98,9 @@ @@ -84,13 +98,9 @@
84 } 98 }
85 } 99 }
86 }, 100 },
87 - watch: {  
88 - data () {  
89 - this.sublist = [];  
90 - }  
91 - },  
92 - events: {  
93 - 'on-find-selected' (val) { 101 + mounted () {
  102 + this.$on('on-find-selected', (params) => {
  103 + const val = params.value;
94 let value = [...val]; 104 let value = [...val];
95 for (let i = 0; i < value.length; i++) { 105 for (let i = 0; i < value.length; i++) {
96 for (let j = 0; j < this.data.length; j++) { 106 for (let j = 0; j < this.data.length; j++) {
@@ -98,17 +108,19 @@ @@ -98,17 +108,19 @@
98 this.handleTriggerItem(this.data[j], true); 108 this.handleTriggerItem(this.data[j], true);
99 value.splice(0, 1); 109 value.splice(0, 1);
100 this.$nextTick(() => { 110 this.$nextTick(() => {
101 - this.$broadcast('on-find-selected', value); 111 + this.broadcast('Caspanel', 'on-find-selected', {
  112 + value: value
  113 + });
102 }); 114 });
103 return false; 115 return false;
104 } 116 }
105 } 117 }
106 } 118 }
107 - },  
108 - 'on-clear' () { 119 + });
  120 + this.$on('on-clear', () => {
109 this.sublist = []; 121 this.sublist = [];
110 this.tmpItem = {}; 122 this.tmpItem = {};
111 - } 123 + });
112 } 124 }
113 }; 125 };
114 </script> 126 </script>
src/components/spin/spin.vue
@@ -14,6 +14,7 @@ @@ -14,6 +14,7 @@
14 const prefixCls = 'ivu-spin'; 14 const prefixCls = 'ivu-spin';
15 15
16 export default { 16 export default {
  17 + name: 'Spin',
17 props: { 18 props: {
18 size: { 19 size: {
19 validator (value) { 20 validator (value) {
@@ -9,7 +9,7 @@ import Breadcrumb from &#39;./components/breadcrumb&#39;; @@ -9,7 +9,7 @@ import Breadcrumb from &#39;./components/breadcrumb&#39;;
9 import Button from './components/button'; 9 import Button from './components/button';
10 import Card from './components/card'; 10 import Card from './components/card';
11 import Carousel from './components/carousel'; 11 import Carousel from './components/carousel';
12 -// import Cascader from './components/cascader'; 12 +import Cascader from './components/cascader';
13 import Checkbox from './components/checkbox'; 13 import Checkbox from './components/checkbox';
14 import Circle from './components/circle'; 14 import Circle from './components/circle';
15 import Collapse from './components/collapse'; 15 import Collapse from './components/collapse';
@@ -59,7 +59,7 @@ const iview = { @@ -59,7 +59,7 @@ const iview = {
59 Card, 59 Card,
60 Carousel, 60 Carousel,
61 CarouselItem: Carousel.Item, 61 CarouselItem: Carousel.Item,
62 - // Cascader, 62 + Cascader,
63 Checkbox, 63 Checkbox,
64 CheckboxGroup: Checkbox.Group, 64 CheckboxGroup: Checkbox.Group,
65 iCircle: Circle, 65 iCircle: Circle,
src/mixins/emitter.js
@@ -5,6 +5,7 @@ function broadcast(componentName, eventName, params) { @@ -5,6 +5,7 @@ function broadcast(componentName, eventName, params) {
5 if (name === componentName) { 5 if (name === componentName) {
6 child.$emit.apply(child, [eventName].concat(params)); 6 child.$emit.apply(child, [eventName].concat(params));
7 } else { 7 } else {
  8 + // todo 如果 params 是空数组,接收到的会是 undefined
8 broadcast.apply(child, [componentName, eventName].concat([params])); 9 broadcast.apply(child, [componentName, eventName].concat([params]));
9 } 10 }
10 }); 11 });