|
@@ -17,9 +17,9 @@
|
|
<el-input
|
|
<el-input
|
|
ref="input"
|
|
ref="input"
|
|
:readonly="!filterable"
|
|
:readonly="!filterable"
|
|
- :placeholder="displayValue ? undefined : placeholder"
|
|
|
|
|
|
+ :placeholder="currentLabels.length ? undefined : placeholder"
|
|
v-model="inputValue"
|
|
v-model="inputValue"
|
|
- @change="handleInputChange"
|
|
|
|
|
|
+ @change="debouncedInputChange"
|
|
:validate-event="false"
|
|
:validate-event="false"
|
|
:size="size"
|
|
:size="size"
|
|
:disabled="disabled"
|
|
:disabled="disabled"
|
|
@@ -27,7 +27,7 @@
|
|
<template slot="icon">
|
|
<template slot="icon">
|
|
<i
|
|
<i
|
|
key="1"
|
|
key="1"
|
|
- v-if="inputHover && displayValue !== ''"
|
|
|
|
|
|
+ v-if="clearable && inputHover && currentLabels.length"
|
|
class="el-input__icon el-icon-circle-close el-cascader__clearIcon"
|
|
class="el-input__icon el-icon-circle-close el-cascader__clearIcon"
|
|
@click="clearValue"
|
|
@click="clearValue"
|
|
></i>
|
|
></i>
|
|
@@ -39,7 +39,17 @@
|
|
></i>
|
|
></i>
|
|
</template>
|
|
</template>
|
|
</el-input>
|
|
</el-input>
|
|
- <span class="el-cascader__label" v-show="inputValue === ''">{{displayValue}}</span>
|
|
|
|
|
|
+ <span class="el-cascader__label" v-show="inputValue === ''">
|
|
|
|
+ <template v-if="showAllLevels">
|
|
|
|
+ <template v-for="(label, index) in currentLabels">
|
|
|
|
+ {{ label }}
|
|
|
|
+ <span v-if="index < currentLabels.length - 1"> / </span>
|
|
|
|
+ </template>
|
|
|
|
+ </template>
|
|
|
|
+ <template v-else>
|
|
|
|
+ {{ currentLabels[currentLabels.length - 1] }}
|
|
|
|
+ </template>
|
|
|
|
+ </span>
|
|
</span>
|
|
</span>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
@@ -51,6 +61,8 @@ import Popper from 'element-ui/src/utils/vue-popper';
|
|
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
|
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
|
import emitter from 'element-ui/src/mixins/emitter';
|
|
import emitter from 'element-ui/src/mixins/emitter';
|
|
import Locale from 'element-ui/src/mixins/locale';
|
|
import Locale from 'element-ui/src/mixins/locale';
|
|
|
|
+import { t } from 'element-ui/src/locale';
|
|
|
|
+import debounce from 'throttle-debounce/debounce';
|
|
|
|
|
|
const popperMixin = {
|
|
const popperMixin = {
|
|
props: {
|
|
props: {
|
|
@@ -84,17 +96,33 @@ export default {
|
|
type: Array,
|
|
type: Array,
|
|
required: true
|
|
required: true
|
|
},
|
|
},
|
|
|
|
+ props: {
|
|
|
|
+ type: Object,
|
|
|
|
+ default() {
|
|
|
|
+ return {
|
|
|
|
+ children: 'children',
|
|
|
|
+ label: 'label',
|
|
|
|
+ value: 'value',
|
|
|
|
+ disabled: 'disabled'
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+ },
|
|
value: {
|
|
value: {
|
|
type: Array,
|
|
type: Array,
|
|
default() {
|
|
default() {
|
|
return [];
|
|
return [];
|
|
}
|
|
}
|
|
},
|
|
},
|
|
- placeholder: String,
|
|
|
|
|
|
+ placeholder: {
|
|
|
|
+ type: String,
|
|
|
|
+ default() {
|
|
|
|
+ return t('el.cascader.placeholder');
|
|
|
|
+ }
|
|
|
|
+ },
|
|
disabled: Boolean,
|
|
disabled: Boolean,
|
|
clearable: {
|
|
clearable: {
|
|
type: Boolean,
|
|
type: Boolean,
|
|
- default: true
|
|
|
|
|
|
+ default: false
|
|
},
|
|
},
|
|
changeOnSelect: Boolean,
|
|
changeOnSelect: Boolean,
|
|
popperClass: String,
|
|
popperClass: String,
|
|
@@ -103,20 +131,53 @@ export default {
|
|
default: 'click'
|
|
default: 'click'
|
|
},
|
|
},
|
|
filterable: Boolean,
|
|
filterable: Boolean,
|
|
- size: String
|
|
|
|
|
|
+ size: String,
|
|
|
|
+ showAllLevels: {
|
|
|
|
+ type: Boolean,
|
|
|
|
+ default: true
|
|
|
|
+ },
|
|
|
|
+ debounce: {
|
|
|
|
+ type: Number,
|
|
|
|
+ default: 300
|
|
|
|
+ }
|
|
},
|
|
},
|
|
|
|
|
|
data() {
|
|
data() {
|
|
return {
|
|
return {
|
|
currentValue: this.value,
|
|
currentValue: this.value,
|
|
- displayValue: this.value.join('/'),
|
|
|
|
|
|
+ menu: null,
|
|
|
|
+ debouncedInputChange() {},
|
|
menuVisible: false,
|
|
menuVisible: false,
|
|
inputHover: false,
|
|
inputHover: false,
|
|
inputValue: '',
|
|
inputValue: '',
|
|
- flatOptions: this.filterable && this.flattenOptions(this.options)
|
|
|
|
|
|
+ flatOptions: null
|
|
};
|
|
};
|
|
},
|
|
},
|
|
|
|
|
|
|
|
+ computed: {
|
|
|
|
+ labelKey() {
|
|
|
|
+ return this.props.label || 'label';
|
|
|
|
+ },
|
|
|
|
+ valueKey() {
|
|
|
|
+ return this.props.value || 'value';
|
|
|
|
+ },
|
|
|
|
+ childrenKey() {
|
|
|
|
+ return this.props.children || 'children';
|
|
|
|
+ },
|
|
|
|
+ currentLabels() {
|
|
|
|
+ let options = this.options;
|
|
|
|
+ let labels = [];
|
|
|
|
+ this.currentValue.forEach(value => {
|
|
|
|
+ const targetOption = options && options.filter(option => option[this.valueKey] === value)[0];
|
|
|
|
+ if (targetOption) {
|
|
|
|
+ labels.push(targetOption[this.labelKey]);
|
|
|
|
+ options = targetOption[this.childrenKey];
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ return labels;
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
watch: {
|
|
watch: {
|
|
menuVisible(value) {
|
|
menuVisible(value) {
|
|
value ? this.showMenu() : this.hideMenu();
|
|
value ? this.showMenu() : this.hideMenu();
|
|
@@ -125,29 +186,40 @@ export default {
|
|
this.currentValue = value;
|
|
this.currentValue = value;
|
|
},
|
|
},
|
|
currentValue(value) {
|
|
currentValue(value) {
|
|
- this.displayValue = value.join('/');
|
|
|
|
this.dispatch('ElFormItem', 'el.form.change', [value]);
|
|
this.dispatch('ElFormItem', 'el.form.change', [value]);
|
|
},
|
|
},
|
|
- options(value) {
|
|
|
|
- this.menu.options = value;
|
|
|
|
|
|
+ options: {
|
|
|
|
+ deep: true,
|
|
|
|
+ handler(value) {
|
|
|
|
+ if (!this.menu) {
|
|
|
|
+ this.initMenu();
|
|
|
|
+ }
|
|
|
|
+ this.flatOptions = this.flattenOptions(this.options);
|
|
|
|
+ this.menu.options = value;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
},
|
|
},
|
|
|
|
|
|
methods: {
|
|
methods: {
|
|
|
|
+ initMenu() {
|
|
|
|
+ this.menu = new Vue(ElCascaderMenu).$mount();
|
|
|
|
+ this.menu.options = this.options;
|
|
|
|
+ this.menu.props = this.props;
|
|
|
|
+ this.menu.expandTrigger = this.expandTrigger;
|
|
|
|
+ this.menu.changeOnSelect = this.changeOnSelect;
|
|
|
|
+ this.menu.popperClass = this.popperClass;
|
|
|
|
+ this.popperElm = this.menu.$el;
|
|
|
|
+ this.menu.$on('pick', this.handlePick);
|
|
|
|
+ this.menu.$on('activeItemChange', this.handleActiveItemChange);
|
|
|
|
+ },
|
|
showMenu() {
|
|
showMenu() {
|
|
if (!this.menu) {
|
|
if (!this.menu) {
|
|
- this.menu = new Vue(ElCascaderMenu).$mount();
|
|
|
|
- this.menu.options = this.options;
|
|
|
|
- this.menu.expandTrigger = this.expandTrigger;
|
|
|
|
- this.menu.changeOnSelect = this.changeOnSelect;
|
|
|
|
- this.menu.popperClass = this.popperClass;
|
|
|
|
- this.popperElm = this.menu.$el;
|
|
|
|
|
|
+ this.initMenu();
|
|
}
|
|
}
|
|
|
|
|
|
this.menu.value = this.currentValue.slice(0);
|
|
this.menu.value = this.currentValue.slice(0);
|
|
this.menu.visible = true;
|
|
this.menu.visible = true;
|
|
this.menu.options = this.options;
|
|
this.menu.options = this.options;
|
|
- this.menu.$on('pick', this.handlePick);
|
|
|
|
this.updatePopper();
|
|
this.updatePopper();
|
|
this.$nextTick(_ => {
|
|
this.$nextTick(_ => {
|
|
this.menu.inputWidth = this.$refs.input.$el.offsetWidth - 2;
|
|
this.menu.inputWidth = this.$refs.input.$el.offsetWidth - 2;
|
|
@@ -157,6 +229,12 @@ export default {
|
|
this.inputValue = '';
|
|
this.inputValue = '';
|
|
this.menu.visible = false;
|
|
this.menu.visible = false;
|
|
},
|
|
},
|
|
|
|
+ handleActiveItemChange(value) {
|
|
|
|
+ this.$nextTick(_ => {
|
|
|
|
+ this.updatePopper();
|
|
|
|
+ });
|
|
|
|
+ this.$emit('active-item-change', value);
|
|
|
|
+ },
|
|
handlePick(value, close = true) {
|
|
handlePick(value, close = true) {
|
|
this.currentValue = value;
|
|
this.currentValue = value;
|
|
this.$emit('input', value);
|
|
this.$emit('input', value);
|
|
@@ -176,14 +254,14 @@ export default {
|
|
}
|
|
}
|
|
|
|
|
|
let filteredFlatOptions = flatOptions.filter(optionsStack => {
|
|
let filteredFlatOptions = flatOptions.filter(optionsStack => {
|
|
- return optionsStack.some(option => option.label.indexOf(value) > -1);
|
|
|
|
|
|
+ return optionsStack.some(option => new RegExp(value, 'i').test(option[this.labelKey]));
|
|
});
|
|
});
|
|
|
|
|
|
if (filteredFlatOptions.length > 0) {
|
|
if (filteredFlatOptions.length > 0) {
|
|
filteredFlatOptions = filteredFlatOptions.map(optionStack => {
|
|
filteredFlatOptions = filteredFlatOptions.map(optionStack => {
|
|
return {
|
|
return {
|
|
__IS__FLAT__OPTIONS: true,
|
|
__IS__FLAT__OPTIONS: true,
|
|
- value: optionStack.map(item => item.value),
|
|
|
|
|
|
+ value: optionStack.map(item => item[this.valueKey]),
|
|
label: this.renderFilteredOptionLabel(value, optionStack)
|
|
label: this.renderFilteredOptionLabel(value, optionStack)
|
|
};
|
|
};
|
|
});
|
|
});
|
|
@@ -198,8 +276,11 @@ export default {
|
|
this.menu.options = filteredFlatOptions;
|
|
this.menu.options = filteredFlatOptions;
|
|
},
|
|
},
|
|
renderFilteredOptionLabel(inputValue, optionsStack) {
|
|
renderFilteredOptionLabel(inputValue, optionsStack) {
|
|
- return optionsStack.map(({ label }, index) => {
|
|
|
|
- const node = label.indexOf(inputValue) > -1 ? this.highlightKeyword(label, inputValue) : label;
|
|
|
|
|
|
+ return optionsStack.map((option, index) => {
|
|
|
|
+ const label = option[this.labelKey];
|
|
|
|
+ const keywordIndex = label.toLowerCase().indexOf(inputValue.toLowerCase());
|
|
|
|
+ const labelPart = label.slice(keywordIndex, inputValue.length + keywordIndex);
|
|
|
|
+ const node = keywordIndex > -1 ? this.highlightKeyword(label, labelPart) : label;
|
|
return index === 0 ? node : [' / ', node];
|
|
return index === 0 ? node : [' / ', node];
|
|
});
|
|
});
|
|
},
|
|
},
|
|
@@ -215,10 +296,13 @@ export default {
|
|
let flatOptions = [];
|
|
let flatOptions = [];
|
|
options.forEach((option) => {
|
|
options.forEach((option) => {
|
|
const optionsStack = ancestor.concat(option);
|
|
const optionsStack = ancestor.concat(option);
|
|
- if (!option.children) {
|
|
|
|
|
|
+ if (!option[this.childrenKey]) {
|
|
flatOptions.push(optionsStack);
|
|
flatOptions.push(optionsStack);
|
|
} else {
|
|
} else {
|
|
- flatOptions = flatOptions.concat(this.flattenOptions(option.children, optionsStack));
|
|
|
|
|
|
+ if (this.changeOnSelect) {
|
|
|
|
+ flatOptions.push(optionsStack);
|
|
|
|
+ }
|
|
|
|
+ flatOptions = flatOptions.concat(this.flattenOptions(option[this.childrenKey], optionsStack));
|
|
}
|
|
}
|
|
});
|
|
});
|
|
return flatOptions;
|
|
return flatOptions;
|
|
@@ -238,6 +322,16 @@ export default {
|
|
}
|
|
}
|
|
this.menuVisible = !this.menuVisible;
|
|
this.menuVisible = !this.menuVisible;
|
|
}
|
|
}
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ created() {
|
|
|
|
+ this.debouncedInputChange = debounce(this.debounce, value => {
|
|
|
|
+ this.handleInputChange(value);
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ mounted() {
|
|
|
|
+ this.flatOptions = this.flattenOptions(this.options);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
</script>
|
|
</script>
|