Commit c463ab87579ca750bc91fb6873f8f0730295bc5d
1 parent
6ff31952
add Cascader
add Cascader
Showing
9 changed files
with
345 additions
and
60 deletions
Show diff stats
src/components/cascader/cascader.vue
1 | 1 | <template> |
2 | - <div :class="[prefixCls]"> | |
2 | + <div :class="classes" v-clickoutside="handleClose"> | |
3 | 3 | <i-input |
4 | 4 | readonly |
5 | 5 | :disabled="disabled" |
6 | 6 | :value.sync="displayRender" |
7 | 7 | :size="size" |
8 | - :placeholder="placeholder"></i-input> | |
8 | + :placeholder="placeholder" | |
9 | + @on-focus="onFocus"></i-input> | |
10 | + <Icon type="ios-close" :class="[prefixCls + '-arrow']" v-show="showCloseIcon" @click.stop="clearSelect"></Icon> | |
11 | + <Icon type="arrow-down-b" :class="[prefixCls + '-arrow']"></Icon> | |
12 | + <Dropdown v-show="visible" transition="slide-up"> | |
13 | + <div> | |
14 | + <Caspanel | |
15 | + :prefix-cls="prefixCls" | |
16 | + :data.sync="data" | |
17 | + :disabled="disabled" | |
18 | + :trigger="trigger" | |
19 | + @on-update-result="updateResult"></Caspanel> | |
20 | + </div> | |
21 | + </Dropdown> | |
9 | 22 | </div> |
10 | 23 | </template> |
11 | 24 | <script> |
12 | 25 | import iInput from '../input/input.vue'; |
13 | 26 | import Dropdown from '../select/dropdown.vue'; |
27 | + import Icon from '../icon/icon.vue'; | |
28 | + import Caspanel from './caspanel.vue'; | |
14 | 29 | import clickoutside from '../../directives/clickoutside'; |
15 | - import { oneOf, MutationObserver } from '../../utils/assist'; | |
30 | + import { oneOf } from '../../utils/assist'; | |
16 | 31 | |
17 | 32 | const prefixCls = 'ivu-cascader'; |
18 | 33 | |
19 | 34 | export default { |
35 | + components: { iInput, Dropdown, Icon, Caspanel }, | |
36 | + directives: { clickoutside }, | |
20 | 37 | props: { |
21 | 38 | data: { |
22 | 39 | type: Array, |
... | ... | @@ -33,7 +50,7 @@ |
33 | 50 | }, |
34 | 51 | clearable: { |
35 | 52 | type: Boolean, |
36 | - default: false | |
53 | + default: true | |
37 | 54 | }, |
38 | 55 | placeholder: { |
39 | 56 | type: String, |
... | ... | @@ -56,18 +73,31 @@ |
56 | 73 | }, |
57 | 74 | renderFormat: { |
58 | 75 | type: Function, |
59 | - default: (label, selectedData) => { | |
60 | - label.join('/'); | |
76 | + default (label, selectedData) { | |
77 | + return label.join('/'); | |
61 | 78 | } |
62 | 79 | } |
63 | 80 | }, |
64 | 81 | data () { |
65 | 82 | return { |
66 | 83 | prefixCls: prefixCls, |
67 | - selected: [] | |
84 | + visible: false, | |
85 | + selected: [], | |
86 | + tmpSelected: [] | |
68 | 87 | } |
69 | 88 | }, |
70 | 89 | computed: { |
90 | + classes () { | |
91 | + return [ | |
92 | + `${prefixCls}`, | |
93 | + { | |
94 | + [`${prefixCls}-show-clear`]: this.showCloseIcon | |
95 | + } | |
96 | + ] | |
97 | + }, | |
98 | + showCloseIcon () { | |
99 | + return this.value && this.value.length && this.clearable; | |
100 | + }, | |
71 | 101 | displayRender () { |
72 | 102 | let label = []; |
73 | 103 | for (let i = 0; i < this.selected.length; i++) { |
... | ... | @@ -78,7 +108,22 @@ |
78 | 108 | } |
79 | 109 | }, |
80 | 110 | methods: { |
81 | - | |
111 | + clearSelect () { | |
112 | + | |
113 | + }, | |
114 | + handleClose () { | |
115 | + this.visible = false; | |
116 | + }, | |
117 | + onFocus () { | |
118 | + this.visible = true; | |
119 | + }, | |
120 | + updateResult (result) { | |
121 | + console.log(JSON.stringify(result)) | |
122 | + this.selected = result; | |
123 | + } | |
124 | + }, | |
125 | + ready () { | |
126 | + | |
82 | 127 | } |
83 | 128 | } |
84 | 129 | </script> |
85 | 130 | \ No newline at end of file | ... | ... |
1 | +<template> | |
2 | + <li :class="classes">{{ data.label }}<i v-if="data.children && data.children.length" class="ivu-icon ivu-icon-ios-arrow-right"></i></li> | |
3 | +</template> | |
4 | +<script> | |
5 | + export default { | |
6 | + props: { | |
7 | + data: Object, | |
8 | + prefixCls: String, | |
9 | + tmpItem: Object | |
10 | + }, | |
11 | + computed: { | |
12 | + classes () { | |
13 | + return [ | |
14 | + `${this.prefixCls}-menu-item`, | |
15 | + { | |
16 | + [`${this.prefixCls}-menu-item-active`]: this.tmpItem.value === this.data.value | |
17 | + } | |
18 | + ] | |
19 | + } | |
20 | + }, | |
21 | + ready () { | |
22 | + | |
23 | + } | |
24 | + } | |
25 | +</script> | |
0 | 26 | \ No newline at end of file | ... | ... |
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" @on-update-result="updateResult"></Caspanel> | |
11 | +</template> | |
12 | +<script> | |
13 | + import Casitem from './casitem.vue'; | |
14 | + import { oneOf } from '../../utils/assist'; | |
15 | + | |
16 | + export default { | |
17 | + name: 'Caspanel', | |
18 | + components: { Casitem }, | |
19 | + props: { | |
20 | + data: { | |
21 | + type: Array, | |
22 | + default () { | |
23 | + return [] | |
24 | + } | |
25 | + }, | |
26 | + sublist: { | |
27 | + type: Array, | |
28 | + default () { | |
29 | + return [] | |
30 | + } | |
31 | + }, | |
32 | + disabled: Boolean, | |
33 | + changeOnSelect: Boolean, | |
34 | + trigger: { | |
35 | + validator (value) { | |
36 | + return oneOf(value, ['click', 'hover']); | |
37 | + } | |
38 | + }, | |
39 | + prefixCls: String | |
40 | + }, | |
41 | + data () { | |
42 | + return { | |
43 | + tmpItem: {}, | |
44 | + result: [] | |
45 | + } | |
46 | + }, | |
47 | + methods: { | |
48 | + handleClickItem (item) { | |
49 | + if (this.trigger !== 'click') return; | |
50 | + this.handleTriggerItem(item); | |
51 | + }, | |
52 | + handleHoverItem (item) { | |
53 | + if (this.trigger !== 'hover') return; | |
54 | + this.handleTriggerItem(item); | |
55 | + }, | |
56 | + handleTriggerItem (item) { | |
57 | + if (item.disabled) return; | |
58 | + | |
59 | + if (item.children && item.children.length){ | |
60 | + this.sublist = item.children; | |
61 | + // todo 实时选择 | |
62 | + } else { | |
63 | + this.sublist = []; | |
64 | + // todo 选择 | |
65 | + } | |
66 | + | |
67 | + // return value back | |
68 | + const backItem = this.getBaseItem(item); | |
69 | + | |
70 | + this.tmpItem = backItem; | |
71 | + this.$emit('on-update-result', [backItem]); | |
72 | + }, | |
73 | + updateResult (item) { | |
74 | + this.result = [this.tmpItem].concat(item); | |
75 | + this.$emit('on-update-result', this.result); | |
76 | + }, | |
77 | + getBaseItem (item) { | |
78 | + let backItem = Object.assign({}, item); | |
79 | + if (backItem.children) { | |
80 | + delete backItem.children; | |
81 | + } | |
82 | + | |
83 | + return backItem; | |
84 | + } | |
85 | + }, | |
86 | + watch: { | |
87 | + data () { | |
88 | + this.sublist = []; | |
89 | + } | |
90 | + }, | |
91 | + ready () { | |
92 | + // todo 初始化时,判断预设的值 | |
93 | + } | |
94 | + } | |
95 | +</script> | |
0 | 96 | \ No newline at end of file | ... | ... |
1 | +@cascader-prefix-cls: ~"@{css-prefix}cascader"; | |
2 | +@cascader-item-prefix-cls: ~"@{css-prefix}cascader-menu-item"; | |
3 | + | |
4 | +.@{cascader-prefix-cls} { | |
5 | + position: relative; | |
6 | + | |
7 | + .@{css-prefix}input{ | |
8 | + display: block; | |
9 | + cursor: pointer; | |
10 | + } | |
11 | + | |
12 | + .@{cascader-prefix-cls}-arrow:nth-of-type(1) { | |
13 | + display: none; | |
14 | + cursor: pointer; | |
15 | + } | |
16 | + | |
17 | + &:hover { | |
18 | + .@{cascader-prefix-cls}-arrow:nth-of-type(1) { | |
19 | + display: inline-block; | |
20 | + } | |
21 | + } | |
22 | + &-show-clear:hover .@{cascader-prefix-cls}-arrow:nth-of-type(2){ | |
23 | + display: none; | |
24 | + } | |
25 | + | |
26 | + &-arrow { | |
27 | + position: absolute; | |
28 | + top: 50%; | |
29 | + right: 8px; | |
30 | + line-height: 1; | |
31 | + margin-top: -6px; | |
32 | + font-size: @font-size-base; | |
33 | + color: @subsidiary-color; | |
34 | + .transition(all @transition-time @ease-in-out); | |
35 | + } | |
36 | + | |
37 | + .@{select-dropdown-prefix-cls} { | |
38 | + padding: 0; | |
39 | + white-space: nowrap; | |
40 | + } | |
41 | + | |
42 | + .select-item(@cascader-prefix-cls, @cascader-item-prefix-cls); | |
43 | + | |
44 | + &-menu{ | |
45 | + display: inline-block; | |
46 | + min-width: 100px; | |
47 | + height: 180px; | |
48 | + margin: 0; | |
49 | + padding: 5px 0; | |
50 | + vertical-align: top; | |
51 | + list-style: none; | |
52 | + border-right: 1px solid @border-color-split; | |
53 | + overflow: auto; | |
54 | + | |
55 | + &:first-child { | |
56 | + | |
57 | + } | |
58 | + &:last-child { | |
59 | + border-right-color: transparent; | |
60 | + margin-right: -1px; | |
61 | + } | |
62 | + &:only-child { | |
63 | + | |
64 | + } | |
65 | + | |
66 | + & &-item{ | |
67 | + position: relative; | |
68 | + padding-right: 24px; | |
69 | + .transition(all @transition-time @ease-in-out); | |
70 | + | |
71 | + i{ | |
72 | + font-size: @font-size-small; | |
73 | + position: absolute; | |
74 | + right: 15px; | |
75 | + top: 50%; | |
76 | + margin-top: -6px; | |
77 | + } | |
78 | + | |
79 | + &-active{ | |
80 | + background-color: @background-color-select-hover; | |
81 | + font-weight: bold; | |
82 | + } | |
83 | + } | |
84 | + } | |
85 | +} | |
0 | 86 | \ No newline at end of file | ... | ... |
src/styles/components/index.less
src/styles/components/select.less
... | ... | @@ -183,50 +183,7 @@ |
183 | 183 | } |
184 | 184 | } |
185 | 185 | |
186 | -.@{select-item-prefix-cls} { | |
187 | - margin: 0; | |
188 | - padding: 7px 16px; | |
189 | - clear: both; | |
190 | - color: @text-color; | |
191 | - font-size: @font-size-small !important; | |
192 | - //border-radius: @btn-border-radius-small; | |
193 | - white-space: nowrap; | |
194 | - cursor: pointer; | |
195 | - .transition(background @transition-time @ease-in-out); | |
196 | - | |
197 | - &:hover{ | |
198 | - background: @background-color-select-hover; | |
199 | - } | |
200 | - | |
201 | - &-focus { | |
202 | - background: @background-color-select-hover; | |
203 | - } | |
204 | - | |
205 | - &-disabled { | |
206 | - color: @btn-disable-color; | |
207 | - cursor: @cursor-disabled; | |
208 | - | |
209 | - &:hover { | |
210 | - color: @btn-disable-color; | |
211 | - background-color: #fff; | |
212 | - cursor: @cursor-disabled; | |
213 | - } | |
214 | - } | |
215 | - | |
216 | - &-selected ,&-selected:hover{ | |
217 | - color: #fff; | |
218 | - background: @selected-color; | |
219 | - } | |
220 | - | |
221 | - &-selected&-focus { | |
222 | - background: shade(@selected-color, 10%); | |
223 | - } | |
224 | -} | |
225 | - | |
226 | -.@{select-prefix-cls}-large .@{select-item-prefix-cls}{ | |
227 | - padding: 7px 16px 8px; | |
228 | - font-size: @font-size-base !important; | |
229 | -} | |
186 | +.select-item(@select-prefix-cls, @select-item-prefix-cls); | |
230 | 187 | |
231 | 188 | .@{select-prefix-cls}-multiple .@{select-item-prefix-cls} { |
232 | 189 | &-selected{ | ... | ... |
src/styles/mixins/index.less
1 | +.select-item(@size-class, @item-class) { | |
2 | + .@{item-class} { | |
3 | + margin: 0; | |
4 | + padding: 7px 16px; | |
5 | + clear: both; | |
6 | + color: @text-color; | |
7 | + font-size: @font-size-small !important; | |
8 | + white-space: nowrap; | |
9 | + list-style: none; | |
10 | + cursor: pointer; | |
11 | + .transition(background @transition-time @ease-in-out); | |
12 | + | |
13 | + &:hover{ | |
14 | + background: @background-color-select-hover; | |
15 | + } | |
16 | + | |
17 | + &-focus { | |
18 | + background: @background-color-select-hover; | |
19 | + } | |
20 | + | |
21 | + &-disabled { | |
22 | + color: @btn-disable-color; | |
23 | + cursor: @cursor-disabled; | |
24 | + | |
25 | + &:hover { | |
26 | + color: @btn-disable-color; | |
27 | + background-color: #fff; | |
28 | + cursor: @cursor-disabled; | |
29 | + } | |
30 | + } | |
31 | + | |
32 | + &-selected ,&-selected:hover{ | |
33 | + color: #fff; | |
34 | + background: @selected-color; | |
35 | + } | |
36 | + | |
37 | + &-selected&-focus { | |
38 | + background: shade(@selected-color, 10%); | |
39 | + } | |
40 | + } | |
41 | + | |
42 | + .@{size-class}-large .@{item-class} { | |
43 | + padding: 7px 16px 8px; | |
44 | + font-size: @font-size-base !important; | |
45 | + } | |
46 | +} | |
0 | 47 | \ No newline at end of file | ... | ... |
test/routers/cascader.vue
1 | 1 | <template> |
2 | - <div style="margin: 150px;width:300px"> | |
3 | - <Cascader></Cascader> | |
2 | + <div style="margin: 50px;width:300px"> | |
3 | + <Cascader :data="data" :value="value"></Cascader> | |
4 | 4 | </div> |
5 | 5 | </template> |
6 | 6 | <script> |
7 | 7 | import { Cascader } from 'iview'; |
8 | 8 | export default { |
9 | 9 | props: { |
10 | - | |
10 | + | |
11 | 11 | }, |
12 | 12 | data () { |
13 | 13 | return { |
14 | - | |
14 | + value: [], | |
15 | + data: [{ | |
16 | + value: 'zhejiang', | |
17 | + label: 'Zhejiang', | |
18 | + children: [{ | |
19 | + value: 'hangzhou', | |
20 | + label: 'Hangzhou' | |
21 | + }], | |
22 | + }, { | |
23 | + value: 'jiangsu', | |
24 | + label: 'Jiangsu', | |
25 | + children: [{ | |
26 | + value: 'nanjing', | |
27 | + label: 'Nanjing', | |
28 | + children: [{ | |
29 | + value: 'zhonghuamen', | |
30 | + label: 'Zhong Hua Men', | |
31 | + children: [{ | |
32 | + value: 'abc', | |
33 | + label: 'ABC' | |
34 | + }] | |
35 | + }] | |
36 | + }, { | |
37 | + value: 'hhh', | |
38 | + label: 'HHH', | |
39 | + children: [{ | |
40 | + value: 'ddd', | |
41 | + label: 'DDD' | |
42 | + }] | |
43 | + }] | |
44 | + }] | |
15 | 45 | } |
16 | 46 | }, |
17 | 47 | computed: { |
18 | - | |
48 | + | |
19 | 49 | }, |
20 | 50 | methods: { |
21 | - | |
51 | + | |
22 | 52 | } |
23 | 53 | } |
24 | 54 | </script> |
25 | 55 | \ No newline at end of file | ... | ... |