|
@@ -0,0 +1,207 @@
|
|
|
+<template>
|
|
|
+ <span
|
|
|
+ class="el-cascader"
|
|
|
+ :class="{
|
|
|
+ 'is-opened': menuVisible
|
|
|
+ }"
|
|
|
+ @click="menuVisible = !menuVisible"
|
|
|
+ @mouseenter="inputHover = true"
|
|
|
+ @mouseleave="inputHover = false"
|
|
|
+ ref="reference"
|
|
|
+ v-clickoutside="handleClickoutside"
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ :readonly="!showSearch"
|
|
|
+ :placeholder="placeholder"
|
|
|
+ v-model="inputValue"
|
|
|
+ @change="handleInputChange"
|
|
|
+ >
|
|
|
+ <template slot="icon">
|
|
|
+ <i
|
|
|
+ key="1"
|
|
|
+ v-if="inputHover && displayValue !== ''"
|
|
|
+ class="el-input__icon el-icon-circle-close"
|
|
|
+ @click="clearValue"
|
|
|
+ ></i>
|
|
|
+ <i
|
|
|
+ key="2"
|
|
|
+ v-else
|
|
|
+ class="el-input__icon el-icon-caret-bottom"
|
|
|
+ :class="{ 'is-reverse': menuVisible }"
|
|
|
+ ></i>
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ <span class="el-cascader__label" v-show="inputValue === ''">{{displayValue}}</span>
|
|
|
+ </span>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import Vue from 'vue';
|
|
|
+import ElCascaderMenu from './menu';
|
|
|
+import ElInput from 'element-ui/packages/input';
|
|
|
+import Popper from 'element-ui/src/utils/vue-popper';
|
|
|
+import Clickoutside from 'element-ui/src/utils/clickoutside';
|
|
|
+
|
|
|
+const popperMixin = {
|
|
|
+ props: {
|
|
|
+ placement: {
|
|
|
+ type: String,
|
|
|
+ default: 'bottom-start'
|
|
|
+ },
|
|
|
+ appendToBody: Popper.props.appendToBody,
|
|
|
+ offset: Popper.props.offset,
|
|
|
+ boundariesPadding: Popper.props.boundariesPadding,
|
|
|
+ popperOptions: Popper.props.popperOptions
|
|
|
+ },
|
|
|
+ methods: Popper.methods,
|
|
|
+ data: Popper.data,
|
|
|
+ beforeDestroy: Popper.beforeDestroy
|
|
|
+};
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'ElCascader',
|
|
|
+
|
|
|
+ directives: { Clickoutside },
|
|
|
+
|
|
|
+ mixins: [popperMixin],
|
|
|
+
|
|
|
+ components: {
|
|
|
+ ElInput
|
|
|
+ },
|
|
|
+
|
|
|
+ props: {
|
|
|
+ options: {
|
|
|
+ type: Array,
|
|
|
+ required: true
|
|
|
+ },
|
|
|
+ value: {
|
|
|
+ type: Array,
|
|
|
+ default() {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ },
|
|
|
+ placeholder: String,
|
|
|
+ disabled: Boolean,
|
|
|
+ clearable: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ },
|
|
|
+ changeOnSelect: Boolean,
|
|
|
+ popperClass: String,
|
|
|
+ expandTrigger: {
|
|
|
+ type: String,
|
|
|
+ default: 'click'
|
|
|
+ },
|
|
|
+ showSearch: Boolean
|
|
|
+ },
|
|
|
+
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ currentValue: this.value,
|
|
|
+ displayValue: this.value.join('/'),
|
|
|
+ menuVisible: false,
|
|
|
+ inputHover: false,
|
|
|
+ inputValue: '',
|
|
|
+ flatOptions: this.showSearch && this.flattenOptions(this.options)
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ watch: {
|
|
|
+ menuVisible(value) {
|
|
|
+ value ? this.showMenu() : this.hideMenu();
|
|
|
+ },
|
|
|
+ value(value) {
|
|
|
+ this.currentValue = value;
|
|
|
+ },
|
|
|
+ currentValue(value) {
|
|
|
+ this.displayValue = value.join('/');
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ methods: {
|
|
|
+ showMenu() {
|
|
|
+ if (!this.menu) {
|
|
|
+ this.menu = new Vue(ElCascaderMenu).$mount(document.createElement('div'));
|
|
|
+ this.menu.options = this.options;
|
|
|
+ this.menu.expandTrigger = this.expandTrigger;
|
|
|
+ this.menu.changeOnSelect = this.changeOnSelect;
|
|
|
+ this.popperElm = this.menu.$el;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.menu.value = this.currentValue.slice(0);
|
|
|
+ this.menu.visible = true;
|
|
|
+ this.menu.$on('change', this.handlePick);
|
|
|
+ this.updatePopper();
|
|
|
+ },
|
|
|
+ hideMenu() {
|
|
|
+ this.menu.visible = false;
|
|
|
+ this.inputValue = '';
|
|
|
+ },
|
|
|
+ handlePick(value, close = true) {
|
|
|
+ this.currentValue = value;
|
|
|
+ this.$emit('input', value);
|
|
|
+ if (close) {
|
|
|
+ this.menuVisible = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ handleInputChange(value) {
|
|
|
+ const flatOptions = this.flatOptions;
|
|
|
+
|
|
|
+ if (!value) {
|
|
|
+ this.menu.options = this.options;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let filteredFlatOptions = flatOptions.filter(optionsStack => {
|
|
|
+ return optionsStack.some(option => option.label.indexOf(value) > -1);
|
|
|
+ });
|
|
|
+
|
|
|
+ if (filteredFlatOptions.length > 0) {
|
|
|
+ this.menu.options = filteredFlatOptions.map(optionStack => {
|
|
|
+ return {
|
|
|
+ __IS__FLAT__OPTIONS: true,
|
|
|
+ value: optionStack.map(item => item.value),
|
|
|
+ label: this.renderRenderFilteredOption(value, optionStack)
|
|
|
+ };
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ return [{ label: 'notFoundContent', value: 'ANT_CASCADER_NOT_FOUND', disabled: true }];
|
|
|
+ }
|
|
|
+ },
|
|
|
+ renderRenderFilteredOption(inputValue, optionsStack) {
|
|
|
+ return optionsStack.map(({ label }, index) => {
|
|
|
+ const node = label.indexOf(inputValue) > -1 ? this.highlightKeyword(label, inputValue) : label;
|
|
|
+ return index === 0 ? node : [' / ', node];
|
|
|
+ });
|
|
|
+ },
|
|
|
+ highlightKeyword(label, keyword) {
|
|
|
+ const h = this._c;
|
|
|
+ return label.split(keyword)
|
|
|
+ .map((node, index) => index === 0 ? node : [
|
|
|
+ h('span', { class: { 'el-cascader-menu__item__keyword': true }}, [this._v(keyword)]),
|
|
|
+ node
|
|
|
+ ]);
|
|
|
+ },
|
|
|
+ flattenOptions(options, ancestor = []) {
|
|
|
+ let flatOptions = [];
|
|
|
+ options.forEach((option) => {
|
|
|
+ const optionsStack = ancestor.concat(option);
|
|
|
+ if (!option.children) {
|
|
|
+ flatOptions.push(optionsStack);
|
|
|
+ }
|
|
|
+ if (option.children) {
|
|
|
+ flatOptions = flatOptions.concat(this.flattenOptions(option.children, optionsStack));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return flatOptions;
|
|
|
+ },
|
|
|
+ clearValue(ev) {
|
|
|
+ ev.stopPropagation();
|
|
|
+ this.handlePick([], true);
|
|
|
+ },
|
|
|
+ handleClickoutside() {
|
|
|
+ this.menuVisible = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|