diff --git a/examples/components/demo-block.vue b/examples/components/demo-block.vue index 8d03fc501..1f4564b8d 100644 --- a/examples/components/demo-block.vue +++ b/examples/components/demo-block.vue @@ -185,7 +185,8 @@ panel_js: 3, panel_css: 1 }; - const form = document.createElement('form'); + const form = document.getElementById('fiddle-form') || document.createElement('form'); + form.innerHTML = ''; const node = document.createElement('textarea'); form.method = 'post'; @@ -197,6 +198,9 @@ node.value = data[name].toString(); form.appendChild(node.cloneNode()); } + form.setAttribute('id', 'fiddle-form'); + form.style.display = 'none'; + document.body.appendChild(form); form.submit(); } diff --git a/examples/docs/en-US/cascader.md b/examples/docs/en-US/cascader.md index 3eb74a6f2..101bd80d5 100644 --- a/examples/docs/en-US/cascader.md +++ b/examples/docs/en-US/cascader.md @@ -1,73 +1,245 @@ - - ## Cascader -It's used to select from a set of associated data set. Such as province/city/district, company level, and categories. +If the options have a clear hierarchical structure, Cascader can be used to view and select them. ### Basic usage -:::demo +There are two ways to expand child option items. + +:::demo Assigning the `options` attribute to an array of options renders a Cascader. The `expand-trigger` attribute defines how child options are expanded. This example also demonstrates the `change` event, whose parameter is the value of Cascader, an array made up of the values of each selected level. ```html - +
+ Child options expand when clicked (default) + + +
+
+ Child options expand when hovered + + +
+ +``` +::: + +### With default value + +:::demo The default value can be defined with an array. +```html + + -``` -::: - -### Size - -:::demo -```html -
- - - -
- -``` -::: - -### Hover to expand - -Hover to expand the next level options, click to select option. - -:::demo -```html - - +``` +::: + +### Filterable + +Search and select options with a keyword. + +:::demo Adding `filterable` to `el-cascader` enables filtering +```html +
+ Only options of the last level can be selected + +
+
+ Options of all levels can be selected + +
+ + +``` +::: + +### 仅显示最后一级 + +可以仅在输入框中显示选中项最后一级的标签,而不是选中项所在的完整路径。 + +:::demo 属性`show-all-levels`定义了是否显示完整的路径,将其赋值为`false`则仅显示最后一级 +```html + + -``` -::: - -### 尺寸 - -:::demo 提供三种尺寸的级联选择器 -```html -
- - - -
- -``` -::: - -### 移入展开 - -在鼠标移入时就展开下级菜单,完成选择仍需要进行点击。 - -:::demo -```html - - +``` +::: + +### 可搜索 + +可以快捷地搜索选项并选择。 + +:::demo 将`filterable`赋值为`true`即可打开搜索功能。 +```html +
+ 只可选择最后一级菜单的选项 + +
+
+ 可选择任意一级菜单的选项 + +
+ + diff --git a/packages/cascader/src/menu.vue b/packages/cascader/src/menu.vue index ab864df5f..b149bd8a9 100644 --- a/packages/cascader/src/menu.vue +++ b/packages/cascader/src/menu.vue @@ -6,6 +6,7 @@ return { inputWidth: 0, options: [], + props: {}, visible: false, activeValue: [], value: [], @@ -34,6 +35,20 @@ cache: false, get() { const activeValue = this.activeValue; + const configurableProps = ['label', 'value', 'children', 'disabled']; + + const formatOptions = options => { + options.forEach(option => { + if (option.__IS__FLAT__OPTIONS) return; + configurableProps.forEach(prop => { + const value = option[this.props[prop] || prop]; + if (value) option[prop] = value; + }); + if (Array.isArray(option.children)) { + formatOptions(option.children); + } + }); + }; const loadActiveOptions = (options, activeOptions = []) => { const level = activeOptions.length; @@ -48,6 +63,7 @@ return activeOptions; }; + formatOptions(this.options); return loadActiveOptions(this.options); } } @@ -66,7 +82,11 @@ const len = this.activeOptions.length; this.activeValue.splice(menuIndex, len, item.value); this.activeOptions.splice(menuIndex + 1, len, item.children); - if (this.changeOnSelect) this.$emit('pick', this.activeValue, false); + if (this.changeOnSelect) { + this.$emit('pick', this.activeValue, false); + } else { + this.$emit('activeItemChange', this.activeValue); + } } }, @@ -116,7 +136,7 @@ }); let menuStyle = {}; if (isFlat) { - menuStyle.width = this.inputWidth + 'px'; + menuStyle.minWidth = this.inputWidth + 'px'; } return ( diff --git a/packages/theme-default/src/cascader.css b/packages/theme-default/src/cascader.css index 35dc81e7d..5f388ba54 100644 --- a/packages/theme-default/src/cascader.css +++ b/packages/theme-default/src/cascader.css @@ -13,7 +13,7 @@ .el-input__inner { cursor: pointer; background-color: transparent; - z-index: 1; + z-index: var(--index-normal); } .el-input__icon { @@ -34,7 +34,7 @@ top: 0; height: 100%; line-height: 34px; - padding: 0 15px 0 10px; + padding: 0 25px 0 10px; color: var(--input-color); width: 100%; white-space: nowrap; @@ -42,6 +42,11 @@ overflow: hidden; box-sizing: border-box; cursor: pointer; + font-size: 14px; + text-align: left; + span { + color: var(--color-light-silver); + } } @m large { @@ -65,24 +70,23 @@ background: #fff; position: absolute; margin: 5px 0; - z-index: 1001; + z-index: calc(var(--index-normal) + 1); border: var(--select-dropdown-border); border-radius: var(--border-radius-small); - overflow: hidden; box-shadow: var(--select-dropdown-shadow); } @b cascader-menu { display: inline-block; vertical-align: top; - height: 180px; + height: 204px; overflow: auto; border-right: var(--select-dropdown-border); background-color: var(--select-dropdown-background); box-sizing: border-box; margin: 0; - padding: 0; - min-width: 110px; + padding: 6px 0; + min-width: 160px; &:last-child { border-right: 0; @@ -102,13 +106,13 @@ cursor: pointer; @e keyword { - color: var(--color-danger); + font-weight: bold; } @m extensible { &:after { font-family: 'element-icons'; - content: "\e602"; + content: "\e606"; font-size: 12px; transform: scale(0.8); color: rgb(191, 203, 217); @@ -132,7 +136,7 @@ color: var(--color-white); background-color: var(--select-option-selected); - &.hover { + &:hover { background-color: var(--select-option-selected-hover); } } diff --git a/src/locale/lang/pt.js b/src/locale/lang/pt.js index 327234ee5..8efcdcb4e 100644 --- a/src/locale/lang/pt.js +++ b/src/locale/lang/pt.js @@ -12,7 +12,7 @@ export default { startTime: 'Hora de inicio', endDate: 'Data de fim', endTime: 'Hora de fim', - year: 'Ano', + year: '', month1: 'Janeiro', month2: 'Fevereiro', month3: 'Março', diff --git a/test/unit/specs/cascader.spec.js b/test/unit/specs/cascader.spec.js index 007cbb907..54973e7ac 100644 --- a/test/unit/specs/cascader.spec.js +++ b/test/unit/specs/cascader.spec.js @@ -13,6 +13,7 @@ describe('Cascader', () => { ref="cascader" placeholder="请选择" :options="options" + clearable v-model="selectedOptions" > `, @@ -456,6 +457,7 @@ describe('Cascader', () => { placeholder="请选择" :options="options" filterable + :debounce="0" v-model="selectedOptions" > `, @@ -507,7 +509,7 @@ describe('Cascader', () => { const item1 = menuElm.querySelector('.el-cascader-menu__item'); expect(menuElm.children.length).to.be.equal(1); - expect(menuElm.children[0].children.length).to.be.equal(1); + expect(menuElm.children[0].children.length).to.be.equal(3); done(); item1.click(); @@ -521,4 +523,106 @@ describe('Cascader', () => { }, 500); }, 300); }); + it('props', done => { + vm = createVue({ + template: ` + + `, + data() { + return { + options: [{ + label: 'Zhejiang', + cities: [{ + label: 'Hangzhou' + }, { + label: 'NingBo' + }] + }, { + label: 'Jiangsu', + cities: [{ + label: 'Nanjing' + }] + }], + props: { + value: 'label', + children: 'cities' + }, + selectedOptions: [] + }; + } + }, true); + vm.$el.click(); + setTimeout(_ => { + expect(document.body.querySelector('.el-cascader-menus')).to.be.exist; + + const menu = vm.$refs.cascader.menu; + const menuElm = menu.$el; + let items = menuElm.querySelectorAll('.el-cascader-menu__item'); + expect(items.length).to.equal(2); + items[0].click(); + setTimeout(_ => { + items = menuElm.querySelectorAll('.el-cascader-menu__item'); + expect(items.length).to.equal(4); + expect(items[items.length - 1].innerText).to.equal('NingBo'); + done(); + }, 100); + }, 100); + }); + it('show last level', done => { + vm = createVue({ + template: ` + + `, + data() { + return { + options: [{ + value: 'zhejiang', + label: 'Zhejiang', + children: [{ + value: 'hangzhou', + label: 'Hangzhou', + children: [{ + value: 'xihu', + label: 'West Lake' + }] + }, { + value: 'ningbo', + label: 'NingBo', + children: [{ + value: 'jiangbei', + label: 'Jiang Bei' + }] + }] + }, { + value: 'jiangsu', + label: 'Jiangsu', + children: [{ + value: 'nanjing', + label: 'Nanjing', + children: [{ + value: 'zhonghuamen', + label: 'Zhong Hua Men' + }] + }] + }], + selectedOptions: ['zhejiang', 'ningbo', 'jiangbei'] + }; + } + }, true); + setTimeout(_ => { + const span = vm.$el.querySelector('.el-cascader__label'); + expect(span.innerText).to.equal('Jiang Bei'); + done(); + }, 100); + }); });