From 42e51fe9408ac5a965c5ce5906f394dbb98e5bb6 Mon Sep 17 00:00:00 2001 From: xuliangzhan Date: Tue, 25 Feb 2020 17:10:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/select/index.js | 12 + packages/select/src/optgroup.js | 43 ++++ packages/select/src/option.js | 74 ++++++ packages/select/src/select.js | 386 ++++++++++++++++++++++++++++++++ packages/vxe-table.js | 2 +- 5 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 packages/select/index.js create mode 100644 packages/select/src/optgroup.js create mode 100644 packages/select/src/option.js create mode 100644 packages/select/src/select.js diff --git a/packages/select/index.js b/packages/select/index.js new file mode 100644 index 000000000..53adafc89 --- /dev/null +++ b/packages/select/index.js @@ -0,0 +1,12 @@ +import VxeSelect from './src/select' +import VxeOption from './src/option' +import VxeOptgroup from './src/optgroup' + +VxeSelect.install = function (Vue) { + Vue.component(VxeSelect.name, VxeSelect) + Vue.component(VxeOption.name, VxeOption) + Vue.component(VxeOptgroup.name, VxeOptgroup) +} + +export const Select = VxeSelect +export default VxeSelect diff --git a/packages/select/src/optgroup.js b/packages/select/src/optgroup.js new file mode 100644 index 000000000..adb571a6e --- /dev/null +++ b/packages/select/src/optgroup.js @@ -0,0 +1,43 @@ +import { UtilTools } from '../../tools' + +export default { + name: 'VxeOptgroup', + props: { + label: [String, Number], + disabled: Boolean, + size: String + }, + provide () { + return { + $xeoptgroup: this + } + }, + inject: { + $xeselect: { + default: null + } + }, + computed: { + vSize () { + return this.size || this.$parent.size || this.$parent.vSize + } + }, + mounted () { + this.$xeselect.updateStatus() + }, + destroyed () { + this.$xeselect.updateStatus() + }, + render (h) { + return h('div', { + class: 'vxe-optgroup' + }, [ + h('div', { + class: 'vxe-optgroup--title' + }, UtilTools.getFuncText(this.label)), + h('div', { + class: 'vxe-optgroup--wrapper' + }, this.$slots.default) + ]) + } +} diff --git a/packages/select/src/option.js b/packages/select/src/option.js new file mode 100644 index 000000000..f28d41ade --- /dev/null +++ b/packages/select/src/option.js @@ -0,0 +1,74 @@ +import { UtilTools } from '../../tools' + +let optionUniqueId = 0 + +export default { + name: 'VxeOption', + props: { + value: [String, Number], + label: [String, Number], + disabled: Boolean, + size: String + }, + inject: { + $xeselect: { + default: null + }, + $xeoptgroup: { + default: null + } + }, + data () { + return { + id: `option_${++optionUniqueId}` + } + }, + computed: { + vSize () { + return this.size || this.$parent.size || this.$parent.vSize + }, + isDisabled () { + const { $xeoptgroup, disabled } = this + return ($xeoptgroup && $xeoptgroup.disabled) || disabled + } + }, + warch: { + value () { + this.updateView() + } + }, + mounted () { + this.updateView() + }, + destroyed () { + this.updateView() + }, + render (h) { + const { $xeselect, id, isDisabled, value } = this + return h('div', { + class: ['vxe-select-option', { + 'is--disabled': isDisabled, + 'is--checked': $xeselect.value === value, + 'is--hover': $xeselect.currentValue === value + }], + attrs: { + 'data-option-id': id + }, + on: { + click: this.optionEvent, + mouseenter: this.mouseenterEvent + } + }, UtilTools.getFuncText(this.label)) + }, + methods: { + updateView () { + this.$xeselect.updateStatus() + }, + optionEvent (evnt) { + this.$xeselect.changeOptionEvent(evnt, this.value) + }, + mouseenterEvent () { + this.$xeselect.setCurrentOption(this) + } + } +} diff --git a/packages/select/src/select.js b/packages/select/src/select.js new file mode 100644 index 000000000..aae341e93 --- /dev/null +++ b/packages/select/src/select.js @@ -0,0 +1,386 @@ +import VxeInput from '../../input' +import { UtilTools, DomTools, GlobalEvent } from '../../tools' + +function findOffsetOption (groupList, optionValue, isUpArrow) { + let prevOption + let firstOption + let isMatchOption = false + for (let gIndex = 0; gIndex < groupList.length; gIndex++) { + const group = groupList[gIndex] + if (group.children.length) { + for (let index = 0; index < group.children.length; index++) { + const comp = group.children[index] + if (!firstOption) { + firstOption = comp + } + if (isUpArrow) { + if (optionValue === comp.value) { + return { offsetOption: prevOption, firstOption } + } + } else { + if (isMatchOption) { + return { offsetOption: comp, firstOption } + } + if (optionValue === comp.value) { + isMatchOption = true + } + } + prevOption = comp + } + } else { + const comp = group.comp + if (!firstOption) { + firstOption = comp + } + if (isUpArrow) { + if (optionValue === comp.value) { + return { offsetOption: prevOption, firstOption } + } + } else { + if (isMatchOption) { + return { offsetOption: comp, firstOption } + } + if (optionValue === comp.value) { + isMatchOption = true + } + } + prevOption = comp + } + } + return { firstOption } +} + +function findOption (groupList, optionValue) { + for (let gIndex = 0; gIndex < groupList.length; gIndex++) { + const group = groupList[gIndex] + if (group.children.length) { + for (let index = 0; index < group.children.length; index++) { + const comp = group.children[index] + if (optionValue === comp.value) { + return comp + } + } + } else { + if (optionValue === group.comp.value) { + return group.comp + } + } + } +} + +export default { + name: 'VxeSelect', + props: { + value: [String, Number], + clearable: Boolean, + placeholder: String, + disabled: Boolean, + prefixIcon: String, + placement: String, + size: String, + transfer: Boolean + }, + components: { + VxeInput + }, + provide () { + return { + $xeselect: this + } + }, + data () { + return { + // 使用技巧去更新视图 + updateFlag: 0, + panelIndex: 0, + panelStyle: null, + panelPlacement: null, + currentValue: null, + showPanel: false, + animatVisible: false, + isActivated: false + } + }, + computed: { + vSize () { + return this.size || this.$parent.size || this.$parent.vSize + }, + selectLabel () { + if (this.updateFlag) { + const selectOption = findOption(this.getOptions(), this.value) + if (selectOption) { + return selectOption.label + } + } + return '' + } + }, + created () { + this.panelIndex = UtilTools.nextZIndex() + GlobalEvent.on(this, 'mousedown', this.handleGlobalMousedownEvent) + GlobalEvent.on(this, 'keydown', this.handleGlobalKeydownEvent) + GlobalEvent.on(this, 'mousewheel', this.handleGlobalMousewheelEvent) + GlobalEvent.on(this, 'blur', this.handleGlobalBlurEvent) + }, + mounted () { + if (this.transfer) { + document.body.appendChild(this.$refs.panel) + } + }, + beforeDestroy () { + const panelElem = this.$refs.panel + if (panelElem && panelElem.parentNode) { + panelElem.parentNode.removeChild(panelElem) + } + }, + destroyed () { + GlobalEvent.off(this, 'mousedown') + GlobalEvent.off(this, 'keydown') + GlobalEvent.off(this, 'mousewheel') + GlobalEvent.off(this, 'blur') + }, + render (h) { + const { vSize, transfer, isActivated, disabled, clearable, placeholder, selectLabel, animatVisible, showPanel, panelStyle, prefixIcon, panelPlacement } = this + return h('div', { + class: ['vxe-select', { + [`size--${vSize}`]: vSize, + 'is--visivle': showPanel, + 'is--disabled': disabled, + 'is--active': isActivated + }] + }, [ + h('vxe-input', { + ref: 'input', + props: { + clearable, + placeholder, + readonly: true, + disabled: disabled, + type: 'text', + prefixIcon: prefixIcon, + suffixIcon: `vxe-icon--caret-bottom${showPanel ? ' rotate180' : ''}`, + value: selectLabel + }, + on: { + clear: this.clearEvent, + click: this.togglePanelEvent, + focus: this.focusEvent, + 'suffix-click': this.togglePanelEvent + } + }), + h('div', { + ref: 'panel', + class: ['vxe-select-option--panel', { + [`size--${vSize}`]: vSize, + 'is--transfer': transfer, + 'animat--leave': animatVisible, + 'animat--enter': showPanel + }], + attrs: { + 'data-placement': panelPlacement + }, + style: panelStyle + }, [ + h('div', { + class: 'vxe-select-option--wrapper' + }, this.$slots.default) + ]) + ]) + }, + methods: { + getOptions () { + const options = [] + if (!this.disabled) { + this.$children.forEach(option => { + if (!option.isDisabled && option.$xeselect) { + let children = option.$children + if (children.length) { + children = children.filter(option => !option.isDisabled && option.$xeselect && option.$xeoptgroup) + if (children.length) { + options.push({ comp: option, children }) + } + } else { + options.push({ comp: option, children }) + } + } + }) + } + return options + }, + updateStatus () { + this.updateFlag++ + }, + setCurrentOption (option) { + if (option) { + this.currentValue = option.value + this.$nextTick(() => { + DomTools.toView(this.$refs.panel.querySelector(`[data-option-id='${option.id}']`)) + }) + } + }, + clearEvent (params, evnt) { + this.clearValueEvent(evnt, null) + this.hideOptionPanel() + }, + clearValueEvent (evnt, selectValue) { + this.changeEvent(evnt, selectValue) + this.$emit('clear', { value: selectValue }, evnt) + }, + changeEvent (evnt, selectValue) { + if (selectValue !== this.value) { + this.$emit('input', selectValue) + this.$emit('change', { value: selectValue }, evnt) + } + }, + changeOptionEvent (evnt, selectValue) { + this.changeEvent(evnt, selectValue) + this.hideOptionPanel() + }, + handleGlobalMousedownEvent (evnt) { + if (!this.disabled) { + if (this.showPanel && !(DomTools.getEventTargetNode(evnt, this.$el).flag || DomTools.getEventTargetNode(evnt, this.$refs.panel).flag)) { + this.hideOptionPanel() + } + this.isActivated = DomTools.getEventTargetNode(evnt, this.$el).flag || DomTools.getEventTargetNode(evnt, this.$refs.panel).flag + } + }, + handleGlobalKeydownEvent (evnt) { + const { showPanel, currentValue, clearable, disabled } = this + if (!disabled) { + const keyCode = evnt.keyCode + const isTab = keyCode === 9 + const isEnter = keyCode === 13 + const isUpArrow = keyCode === 38 + const isDwArrow = keyCode === 40 + const isDel = keyCode === 46 + if (isTab) { + this.isActivated = false + } + if (showPanel) { + if (isTab) { + this.hideOptionPanel() + } else if (isEnter) { + this.changeOptionEvent(evnt, currentValue) + } else if (isUpArrow || isDwArrow) { + evnt.preventDefault() + const groupList = this.getOptions() + let { offsetOption, firstOption } = findOffsetOption(groupList, currentValue, isUpArrow) + if (!offsetOption && !findOption(groupList, currentValue)) { + offsetOption = firstOption + } + this.setCurrentOption(offsetOption) + } + } else if (isEnter && this.isActivated) { + this.showOptionPanel() + } + if (isDel && clearable && this.isActivated) { + this.clearValueEvent(evnt, null) + } + } + }, + handleGlobalMousewheelEvent (evnt) { + if (!DomTools.getEventTargetNode(evnt, this.$el).flag && !DomTools.getEventTargetNode(evnt, this.$refs.panel).flag) { + this.hideOptionPanel() + } + }, + handleGlobalBlurEvent () { + this.hideOptionPanel() + }, + updateZindex () { + if (this.panelIndex < UtilTools.getLastZIndex()) { + this.panelIndex = UtilTools.nextZIndex() + } + }, + focusEvent () { + if (!this.disabled) { + this.isActivated = true + } + }, + togglePanelEvent (params, evnt) { + evnt.preventDefault() + if (this.showPanel) { + this.hideOptionPanel() + } else { + this.showOptionPanel() + } + }, + showOptionPanel () { + if (!this.disabled) { + this.isActivated = true + this.animatVisible = true + setTimeout(() => { + this.showPanel = true + }, 10) + this.setCurrentOption(findOption(this.getOptions(), this.value)) + this.updateZindex() + this.updatePlacement() + } + }, + hideOptionPanel () { + this.showPanel = false + setTimeout(() => { + this.animatVisible = false + }, 200) + }, + updatePlacement () { + this.$nextTick(() => { + const { $refs, transfer, placement, panelIndex } = this + const inputElem = $refs.input.$el + const panelElem = $refs.panel + const inputHeight = inputElem.offsetHeight + const inputWidth = inputElem.offsetWidth + const panelHeight = panelElem.offsetHeight + const panelStyle = { + zIndex: panelIndex + } + const { boundingTop, boundingLeft, visibleHeight } = DomTools.getAbsolutePos(inputElem) + let panelPlacement = 'bottom' + if (transfer) { + const left = boundingLeft + let top = boundingTop + inputHeight + if (placement === 'top') { + panelPlacement = 'top' + top = boundingTop - panelHeight + } else { + // 如果下面不够放,则向上 + if (top + panelHeight > visibleHeight) { + panelPlacement = 'top' + top = boundingTop - panelHeight + } + // 如果上面不够放,则向下(优先) + if (top < 0) { + panelPlacement = 'bottom' + top = boundingTop + inputHeight + } + } + Object.assign(panelStyle, { + left: `${left}px`, + top: `${top}px`, + minWidth: `${inputWidth}px` + }) + } else { + if (placement === 'top') { + panelPlacement = 'top' + panelStyle.bottom = `${inputHeight}px` + } else { + // 如果下面不够放,则向上 + if (boundingTop + inputHeight + panelHeight > visibleHeight) { + panelPlacement = 'top' + panelStyle.bottom = `${inputHeight}px` + } + } + } + this.panelStyle = panelStyle + this.panelPlacement = panelPlacement + }) + }, + focus () { + this.showOptionPanel() + return this.$nextTick() + }, + blur () { + this.hideOptionPanel() + return this.$nextTick() + } + } +} diff --git a/packages/vxe-table.js b/packages/vxe-table.js index 1cfa760a2..ef143eb79 100644 --- a/packages/vxe-table.js +++ b/packages/vxe-table.js @@ -93,7 +93,7 @@ export * from './button' export * from './modal' export * from './tooltip' export * from './form' -// export * from './select' +export * from './select' export * from './edit' export * from './export' export * from './keyboard'