Commit 47a7f21dc601f9ba8526a6c4a60af61e1ca90ccd

Authored by 梁灏
1 parent 2d43f26b

support Cascader

support Cascader
CHANGE.md
... ... @@ -38,4 +38,6 @@ DropdownItem key 改为 name, Dropdown 的 visible 要使用 @on-visible-change
38 38 DropdownItem 里,this.$parent.$parent 与1.0 有区别
39 39 ### Menu
40 40 MenuItem 和 Submenu 的 key 改为了 name
41   -Menu 的 activeKey 改为 activeName,openKeys 改为 openNames
42 41 \ No newline at end of file
  42 +Menu 的 activeKey 改为 activeName,openKeys 改为 openNames
  43 +### Cascader
  44 +Caspanel 的 sublist 从 prop -> data
... ...
README.md
... ... @@ -29,7 +29,7 @@
29 29 - [x] Slider
30 30 - [ ] DatePicker
31 31 - [ ] TimePicker
32   -- [ ] Cascader
  32 +- [x] Cascader
33 33 - [ ] Transfer
34 34 - [x] InputNumber
35 35 - [x] Rate
... ...
examples/app.vue
... ... @@ -42,6 +42,7 @@ li + li { border-left: solid 1px #bbb; padding-left: 10px; margin-left: 10px; }
42 42 <li><router-link to="/breadcrumb">Breadcrumb</router-link></li>
43 43 <li><router-link to="/menu">Menu</router-link></li>
44 44 <li><router-link to="/spin">Spin</router-link></li>
  45 + <li><router-link to="/cascader">Cascader</router-link></li>
45 46 </ul>
46 47 </nav>
47 48 <router-view></router-view>
... ...
examples/main.js
... ... @@ -132,6 +132,10 @@ const router = new VueRouter({
132 132 {
133 133 path: '/spin',
134 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 77 </template>
16 78 <script>
17 79 export default {
18 80 data () {
19 81 return {
20   - disabled: false,
21   - text: '未选择',
  82 + value2: [],
22 83 data: [{
23 84 value: 'beijing',
24 85 label: '北京',
... ... @@ -69,8 +130,8 @@
69 130 }
70 131 },
71 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 5 <i-input
6 6 readonly
7 7 :disabled="disabled"
8   - :value.sync="displayRender"
  8 + v-model="displayRender"
9 9 :size="size"
10 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 12 <Icon type="arrow-down-b" :class="[prefixCls + '-arrow']"></Icon>
13 13 </slot>
14 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 28 </div>
27 29 </template>
28 30 <script>
29 31 import iInput from '../input/input.vue';
30   - import Dropdown from '../select/dropdown.vue';
  32 + import Drop from '../select/dropdown.vue';
31 33 import Icon from '../icon/icon.vue';
32 34 import Caspanel from './caspanel.vue';
33 35 import clickoutside from '../../directives/clickoutside';
34 36 import { oneOf } from '../../utils/assist';
  37 + import Emitter from '../../mixins/emitter';
35 38  
36 39 const prefixCls = 'ivu-cascader';
37 40  
38 41 export default {
39 42 name: 'Cascader',
40   - components: { iInput, Dropdown, Icon, Caspanel },
  43 + mixins: [ Emitter ],
  44 + components: { iInput, Drop, Icon, Caspanel },
41 45 directives: { clickoutside },
42 46 props: {
43 47 data: {
... ... @@ -92,7 +96,8 @@
92 96 visible: false,
93 97 selected: [],
94 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 103 computed: {
... ... @@ -107,7 +112,7 @@
107 112 ];
108 113 },
109 114 showCloseIcon () {
110   - return this.value && this.value.length && this.clearable;
  115 + return this.currentValue && this.currentValue.length && this.clearable;
111 116 },
112 117 displayRender () {
113 118 let label = [];
... ... @@ -120,11 +125,12 @@
120 125 },
121 126 methods: {
122 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 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 135 handleClose () {
130 136 this.visible = false;
... ... @@ -139,8 +145,8 @@
139 145 },
140 146 onFocus () {
141 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 152 updateResult (result) {
... ... @@ -148,25 +154,30 @@
148 154 },
149 155 updateSelected (init = false) {
150 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 162 emitValue (val, oldVal) {
155 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 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 179 if (lastValue || changeOnSelect) {
169   - const oldVal = JSON.stringify(this.value);
  180 + const oldVal = JSON.stringify(this.currentValue);
170 181 this.selected = this.tmpSelected;
171 182  
172 183 let newVal = [];
... ... @@ -176,30 +187,37 @@
176 187  
177 188 if (!fromInit) {
178 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 194 if (lastValue && !fromInit) {
184 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 208 watch: {
195 209 visible (val) {
196 210 if (val) {
197   - if (this.value.length) {
  211 + if (this.currentValue.length) {
198 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 221 if (this.updatingValue) {
204 222 this.updatingValue = false;
205 223 return;
... ...
src/components/cascader/casitem.vue
... ... @@ -3,6 +3,7 @@
3 3 </template>
4 4 <script>
5 5 export default {
  6 + name: 'Casitem',
6 7 props: {
7 8 data: Object,
8 9 prefixCls: String,
... ...
src/components/cascader/caspanel.vue
1 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 13 </template>
12 14 <script>
13 15 import Casitem from './casitem.vue';
  16 + import Emitter from '../../mixins/emitter';
14 17  
15 18 export default {
16 19 name: 'Caspanel',
  20 + mixins: [ Emitter ],
17 21 components: { Casitem },
18 22 props: {
19 23 data: {
... ... @@ -22,12 +26,6 @@
22 26 return [];
23 27 }
24 28 },
25   - sublist: {
26   - type: Array,
27   - default () {
28   - return [];
29   - }
30   - },
31 29 disabled: Boolean,
32 30 changeOnSelect: Boolean,
33 31 trigger: String,
... ... @@ -36,9 +34,15 @@
36 34 data () {
37 35 return {
38 36 tmpItem: {},
39   - result: []
  37 + result: [],
  38 + sublist: []
40 39 };
41 40 },
  41 + watch: {
  42 + data () {
  43 + this.sublist = [];
  44 + }
  45 + },
42 46 methods: {
43 47 handleClickItem (item) {
44 48 if (this.trigger !== 'click' && item.children) return;
... ... @@ -58,10 +62,20 @@
58 62  
59 63 if (item.children && item.children.length){
60 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 71 } else {
63 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 81 updateResult (item) {
... ... @@ -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 104 let value = [...val];
95 105 for (let i = 0; i < value.length; i++) {
96 106 for (let j = 0; j < this.data.length; j++) {
... ... @@ -98,17 +108,19 @@
98 108 this.handleTriggerItem(this.data[j], true);
99 109 value.splice(0, 1);
100 110 this.$nextTick(() => {
101   - this.$broadcast('on-find-selected', value);
  111 + this.broadcast('Caspanel', 'on-find-selected', {
  112 + value: value
  113 + });
102 114 });
103 115 return false;
104 116 }
105 117 }
106 118 }
107   - },
108   - 'on-clear' () {
  119 + });
  120 + this.$on('on-clear', () => {
109 121 this.sublist = [];
110 122 this.tmpItem = {};
111   - }
  123 + });
112 124 }
113 125 };
114 126 </script>
... ...
src/components/spin/spin.vue
... ... @@ -14,6 +14,7 @@
14 14 const prefixCls = 'ivu-spin';
15 15  
16 16 export default {
  17 + name: 'Spin',
17 18 props: {
18 19 size: {
19 20 validator (value) {
... ...
src/index.js
... ... @@ -9,7 +9,7 @@ import Breadcrumb from &#39;./components/breadcrumb&#39;;
9 9 import Button from './components/button';
10 10 import Card from './components/card';
11 11 import Carousel from './components/carousel';
12   -// import Cascader from './components/cascader';
  12 +import Cascader from './components/cascader';
13 13 import Checkbox from './components/checkbox';
14 14 import Circle from './components/circle';
15 15 import Collapse from './components/collapse';
... ... @@ -59,7 +59,7 @@ const iview = {
59 59 Card,
60 60 Carousel,
61 61 CarouselItem: Carousel.Item,
62   - // Cascader,
  62 + Cascader,
63 63 Checkbox,
64 64 CheckboxGroup: Checkbox.Group,
65 65 iCircle: Circle,
... ...
src/mixins/emitter.js
... ... @@ -5,6 +5,7 @@ function broadcast(componentName, eventName, params) {
5 5 if (name === componentName) {
6 6 child.$emit.apply(child, [eventName].concat(params));
7 7 } else {
  8 + // todo 如果 params 是空数组,接收到的会是 undefined
8 9 broadcast.apply(child, [componentName, eventName].concat([params]));
9 10 }
10 11 });
... ...