| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655 |
- <template>
- <div v-if="editable"
- v-clickoutside="handleClose"
- class="el-tree-select"
- @click="toggleTree">
- <div v-if="multiple"
- ref="tags"
- :style="{ 'max-width': inputWidth - 32 + 'px' }"
- class="el-tree-select__tags">
- <transition-group @after-leave="resetInputHeight">
- <template v-for="(item,i) in selected">
- <el-tag :key="item.value+i"
- :closable="!selectDisabled"
- :size="collapseTagSize"
- type="info"
- disable-transitions
- @close.stop="deleteTag(item)">
- <span class="el-tree-select__tags-text">{{ displayMode==="name"? item.label:item.path }}</span>
- </el-tag>
- </template>
- </transition-group>
- <input v-if="filterable"
- ref="input"
- v-model="query"
- :disabled="selectDisabled"
- :placeholder="currentPlaceholder"
- :autocomplete="false"
- :style="{ width: inputLength + 'px'}"
- type="text"
- class="el-tree-select__input"
- @focus="handleFocus"
- @keydown.esc.stop.prevent="visible = false"
- @keydown.delete="deletePrevTag"
- @input="e => handleQueryChange(e.target.value)">
- </div>
- <el-input ref="reference"
- v-model="selectedLabel"
- :disabled="selectDisabled"
- :readonly="selectReadonly"
- :validate-event="false"
- :size="selectSize"
- :class="{ 'is-focus': visible }"
- :placeholder="currentPlaceholder"
- type="text"
- @focus="handleFocus"
- @keyup.native="onInputChange"
- @mouseenter.native="inputHovering = true"
- @mouseleave.native="inputHovering = false">
- <template v-if="$slots.prefix"
- slot="prefix">
- <slot name="prefix" />
- </template>
- <i slot="suffix"
- :class="suffixIconClass"
- @click="handleIconClick" />
- </el-input>
- <transition name="el-zoom-in-top">
- <div v-show="visible"
- ref="popper"
- :style="{minWidth: inputWidth + 'px'}"
- class="el-tree-select-dropdown el-popper">
- <el-scrollbar wrap-class="el-tree-select-dropdown__wrap">
- <el-tree ref="tree"
- :data="data"
- :lazy="lazy"
- :load="load"
- :check-on-click-node="checkOnClickNode"
- :props="treeProps"
- :show-checkbox="showCheckbox"
- :expand-on-click-node="false"
- :check-strictly="checkStrictly"
- :filter-node-method="filterNodeMethod"
- :default-checked-keys="checkedKeys"
- :node-key="nodeKey"
- :empty-text="emptyText"
- :current-node-key="currentNodeKey"
- default-expand-all
- highlight-current
- @check="handleCheck"
- @node-click="handleNodeClick">
- <template v-slot="scope">
- <span class="el-tree-node__label">
- <i v-if="icon"
- :class="icon(scope.data)" /> {{ scope.node.label }}
- </span>
- </template>
- </el-tree>
- </el-scrollbar>
- </div>
- </transition>
- </div>
- <!--只读 文本样式-->
- <div v-else
- class="el-tree-select">
- <template v-if="$utils.isNotEmpty(selected)">
- <div v-if="multiple"
- class="el-tree-select__tags_readonly">
- <template v-for="(item,i) in selected">
- <el-tag v-if="$utils.isNotEmpty(item.value)"
- :key="item.value+i"
- type="info"
- disable-transitions
- class="el-tree-select__tags-text ibps-mr-5 ">
- <template v-if="displayMode === 'name'">
- {{ item.label }}
- </template>
- <template v-else>
- {{ item.path }}
- </template>
- </el-tag>
- </template>
- </div>
- <el-tag v-else
- type="info"
- disable-transitions>
- {{ selectedLabel }}
- </el-tag>
- </template>
- </div>
- </template>
- <script>
- // 参考 https://github.com/ElemeFE/element/blob/29e76cda035bb8a951e6792a33ba4ff5056515a0/packages/tree-select/src/main.vue
- // 可能下个版本出现,再进行修复
- // API https://deploy-preview-12104--element.netlify.com/#/zh-CN/component/tree-select
- // import ElInput from 'element-ui/packages/input'
- // import ElTree from 'element-ui/packages/tree/src/tree.vue'
- import Clickoutside from '@/plugins/element-ui/src/utils/clickoutside'
- import Popper from '@/plugins/element-ui/src/utils/vue-popper'
- import { valueEquals } from '@/plugins/element-ui/src/utils/util'
- import { addResizeListener, removeResizeListener } from '@/plugins/element-ui/src/utils/resize-event'
- import emitter from '@/plugins/element-ui/src/mixins/emitter'
- import PopupManager from '@/utils/popup'
- // TODO: 等 vue-popper 合并后,这里还需要做出调整
- const popperMixin = {
- props: {
- placement: {
- type: String,
- default: 'bottom-start'
- },
- appendToBody: false,
- arrowOffset: Popper.props.arrowOffset,
- offset: Popper.props.offset,
- boundariesPadding: Popper.props.boundariesPadding,
- popperOptions: Popper.props.popperOptions,
- visibleArrow: {
- type: Boolean,
- default: true
- }
- },
- methods: Popper.methods,
- data: Popper.data,
- beforeDestroy: Popper.beforeDestroy
- }
- const sizeMap = {
- 'medium': 36,
- 'small': 32,
- 'mini': 28
- }
- export default {
- name: 'ibps-tree-select',
- // components: {
- // ElInput,
- // ElTree
- // },
- directives: { Clickoutside },
- mixins: [popperMixin, emitter],
- provide() {
- return {
- elTreeSelect: this
- }
- },
- inject: {
- elForm: {
- default: ''
- },
- elFormItem: {
- default: ''
- }
- },
- props: {
- data: {
- type: Array,
- required: true
- },
- value: {
- type: [String, Number, Array, Object],
- default: ''
- },
- multiple: Boolean,
- disabled: Boolean,
- readonly: {
- type: Boolean,
- default: false
- },
- readonlyText: { // 只读样式 【text ,original】
- type: String,
- default: 'original',
- validator(val) {
- return ['original', 'text'].indexOf(val) > -1
- }
- },
- clearable: Boolean,
- size: {
- type: String,
- validator(val) {
- return ['medium', 'small', 'mini'].indexOf(val) > -1
- }
- },
- nodeKey: {
- type: String,
- default: 'id'
- },
- props: Object,
- placeholder: {
- type: String,
- default() {
- return '请选择'
- }
- },
- selectMode: { // 选值模式 leaf、any
- type: String,
- default: 'any',
- validator: function (value) {
- return ['any', 'leaf'].indexOf(value) !== -1
- }
- },
- displayMode: { // 显示模式 path 、name
- type: String,
- default: 'name',
- validator: function (value) {
- return ['name', 'path'].indexOf(value) !== -1
- }
- },
- separator: { // 树形选项分隔符
- type: String,
- default: '/'
- },
- warningText: {
- type: String,
- default: '请选择叶子节点'
- },
- allowSelection: Function, // 允许的节点
- lazy: Boolean,
- load: Function,
- showCheckbox: Boolean,
- checkStrictly: Boolean,
- filterable: Boolean,
- filterMethod: Function,
- emptyText: String,
- showCheckedStrategy: { // 显示多选按钮
- type: String,
- default: 'child',
- validator(val) {
- return ['all', 'parent', 'child'].indexOf(val) > -1
- }
- },
- icon: Function
- },
- data() {
- return {
- query: '',
- selectedLabel: '',
- inputLength: 0,
- visible: false,
- inputWidth: 0,
- inputHovering: false,
- treeVisibleOnFocus: false,
- selected: this.multiple ? [] : {},
- checkOnClickNode: false,
- treeProps: {
- label: 'name',
- children: 'children'
- }
- }
- },
- computed: {
- suffixIconClass() {
- let classes = ['el-tree-select__caret', 'el-input__icon']
- const criteria = this.clearable &&
- !this.selectDisabled &&
- this.inputHovering &&
- !this.multiple &&
- this.value !== undefined &&
- this.value !== null &&
- this.value !== ''
- if (criteria) {
- classes = [...classes, 'el-icon-circle-close', 'is-show-close']
- } else {
- classes.push('el-icon-arrow-down')
- if (this.visible) {
- classes.push('is-reverse')
- }
- }
- return classes
- },
- selectDisabled() {
- return this.disabled || (this.elForm || {}).disabled
- },
- selectSize() {
- return this.size || (this.elFormItem || {}).elFormItemSize || (this.$ELEMENT || {}).size
- },
- collapseTagSize() {
- return ['small', 'mini'].indexOf(this.selectSize) > -1
- ? 'mini'
- : 'small'
- },
- editable() {
- return !this.readonly || (this.readonly && this.readonlyText === 'original')
- },
- selectReadonly() {
- const isIE = !this.$isServer && !isNaN(Number(document.documentMode))
- return this.readonly || !this.filterable || this.multiple || (!isIE && !this.visible)
- },
- checkedKeys() {
- if (this.multiple && this.showCheckbox) {
- return this.value || []
- } else {
- return []
- }
- },
- currentNodeKey() {
- if (this.multiple) {
- return this.value && this.value.length > 0 ? this.value[0].value : ''
- } else {
- if (this.value && Array.isArray(this.value)) {
- return this.value[0] || ''
- } else {
- return this.value || ''
- }
- }
- },
- currentPlaceholder() {
- if (this.$utils.isEmpty(this.value)) {
- return this.placeholder
- } else {
- return ''
- }
- },
- labelKey() {
- return this.treeProps['label'] || 'name'
- }
- },
- watch: {
- visible(val) {
- if (val) {
- this.updatePopper()
- if (this.multiple && this.filterable) {
- this.$refs.input && this.$refs.input.focus()
- }
- this.$emit('focus', this)
- if (!this.multiple && this.filterable) {
- this.broadcast('ElInput', 'inputSelect')
- }
- } else {
- this.destroyPopper()
- this.$refs.input && this.$refs.input.blur()
- this.$emit('blur', this)
- if (!this.multiple) {
- this.setSelected()
- }
- if (this.filterable) {
- setTimeout(() => {
- this.handleQueryChange('')
- }, 100)
- }
- }
- },
- value(val, oldVal) {
- if (this.multiple) {
- this.resetInputHeight()
- }
- this.setSelected()
- if (!valueEquals(val, oldVal)) {
- this.dispatch('ElFormItem', 'el.form.change', val)
- }
- },
- data() {
- this.setSelected()
- },
- props: {
- handler(val, oldVal) {
- this.treeProps = val
- },
- deep: true,
- immediate: true
- }
- },
- mounted() {
- if (this.multiple && this.showCheckbox) {
- this.checkOnClickNode = true
- }
- if (this.editable) {
- this.referenceElm = this.$refs.reference.$el
- this.popperElm = this.$refs.popper
- this.inputWidth = this.$refs.reference.$el.getBoundingClientRect().width
- addResizeListener(this.$el, this.handleResize)
- this.setSelected()
- this.fixZIndex()
- }
- },
- beforeDestroy() {
- if (this.editable) {
- removeResizeListener(this.$el, this.handleResize)
- }
- },
- methods: {
- /**
- * zxh 修复zindex 不是最高的被遮住
- */
- fixZIndex() {
- PopupManager.getZIndex()
- },
- handleFocus(event) {
- this.treeVisibleOnFocus = true
- this.visible = true
- this.$emit('focus', event)
- },
- handleClose() {
- this.visible = false
- },
- toggleTree() {
- if (this.selectDisabled) return
- if (this.treeVisibleOnFocus) {
- this.treeVisibleOnFocus = false
- } else {
- this.visible = !this.visible
- }
- },
- handleIconClick(event) {
- if (this.suffixIconClass.indexOf('el-icon-circle-close') > -1) {
- event.stopPropagation()
- this.visible = false
- this.$emit('input', '')
- this.emitChange('')
- this.$emit('clear')
- this.selected = {}
- this.selectedLabel = ''
- }
- },
- emitChange(val) {
- if (!valueEquals(this.value, val)) {
- this.$emit('change', val, this.getNodeData(val))
- }
- },
- handleQueryChange(val) {
- this.$refs.tree && this.$refs.tree.filter(val)
- },
- handleNodeClick(data, node, tree) {
- if (this.showCheckbox) return
- if (typeof this.allowSelection === 'function' && !this.allowSelection(data, node, tree)) {
- return false
- }
- if ((this.selectMode === 'leaf' && !node.isLeaf) || (this.selectMode === 'leaf' && node.data.isLeaf === false)) {
- this.$message.warning(this.warningText)
- return false
- }
- this.setSelectedNode(data, node, tree)
- },
- setSelectedNode(data, node, tree) {
- const value = data[this.nodeKey]
- if (this.multiple) {
- const valueCopy = this.value.slice()
- const index = this.getValueIndex(valueCopy, value)
- if (index > -1) {
- valueCopy.splice(index, 1)
- } else {
- valueCopy.push(value)
- }
- this.$refs.input && this.$refs.input.focus()
- this.$emit('input', valueCopy)
- this.emitChange(valueCopy)
- } else {
- // if (value === this.value) {
- // value = ''
- // }
- this.$emit('input', value)
- this.emitChange(value)
- this.visible = false
- }
- },
- handleCheck(data, info) {
- const { checkedNodes } = info
- let nodes = []
- switch (this.showCheckedStrategy) {
- case 'parent':
- nodes = this.getTreeCheckedParentNodes()
- break
- case 'child':
- nodes = checkedNodes.filter(({ children }) => !(children && children.length))
- break
- default:
- nodes = checkedNodes
- break
- }
- const values = nodes.map((node) => node[this.nodeKey])
- if (this.filterable) this.$refs.input && this.$refs.input.focus()
- this.$emit('input', values)
- this.emitChange(values)
- },
- getTreeCheckedParentNodes() {
- const checkedNodes = []
- const traverse = (node) => {
- const childNodes = node.root ? node.root.childNodes : node.childNodes
- childNodes.forEach(child => {
- if (child.checked && !child.isLeaf) {
- checkedNodes.push({
- label: child.data.label,
- value: child.data.value
- })
- } else {
- traverse(child)
- }
- })
- }
- traverse(this.$refs.tree.store)
- return checkedNodes
- },
- getNodeData(value) {
- let node = null
- console.log('value',value)
- if (Array.isArray(this.data)) {
- const traverse = (arr, lablePrefix = []) => {
- for (let i = 0; i < arr.length; i++) {
- const child = arr[i]
- let labelData = child[this.labelKey]
- if (typeof this.labelKey !== 'string') {
- labelData = this.labelKey(child)
- }
- if (child[this.nodeKey] === value) {
- node = {
- label: labelData,
- value: child[this.nodeKey],
- path: [...lablePrefix, labelData].join(this.separator),
- data: child
- }
- break
- } else if (child.children && child.children.length > 0) {
- traverse(child.children, [...lablePrefix, labelData])
- }
- }
- }
- traverse(this.data)
- }
- return node
- },
- onInputChange() {
- if (this.filterable && this.query !== this.selectedLabel) {
- this.query = this.selectedLabel
- this.handleQueryChange(this.query)
- }
- },
- filterNodeMethod(value, data) {
- if (!value) return true
- this.$nextTick(this.updatePopper)
- if (typeof this.filterMethod === 'function') {
- return this.filterMethod(value, data)
- } else {
- if (data[this.labelKey]) {
- return data[this.labelKey].indexOf(value) !== -1
- } else {
- if (data.label) {
- return data.label.indexOf(value) !== -1
- } else {
- return true
- }
- }
- }
- },
- resetInputHeight() {
- this.$nextTick(() => {
- if (!this.$refs.reference || !this.$refs.tags) return
- const inputEl = this.$refs.reference.$refs.input
- const tags = this.$refs.tags
- let height = sizeMap[this.selectSize] || 40
- if (this.selected.length !== 0) {
- height = Math.max((tags.clientHeight + (tags.clientHeight > height ? 6 : 0)), height)
- }
- inputEl.style.height = `${height}px`
- if (this.visible) {
- this.updatePopper()
- }
- })
- },
- getValueIndex(arr = [], value) {
- let index = -1
- arr.some((item, i) => {
- if (item === value) {
- index = i
- return true
- } else {
- return false
- }
- })
- return index
- },
- deletePrevTag(e) {
- if (e.target.value.length <= 0) {
- this.selected.splice(-1, 1)
- const values = this.selected.map(({ value }) => value)
- this.$emit('input', values)
- this.emitChange(values)
- }
- },
- deleteTag(item) {
- this.selected = this.selected.filter(selectItem => selectItem !== item)
- const values = this.selected.map(({ value }) => value)
- this.$emit('input', values)
- this.emitChange(values)
- },
- resetInputWidth() {
- this.inputWidth = this.$refs.reference && this.$refs.reference ? this.$refs.reference.$el.getBoundingClientRect().width : 0
- },
- handleResize() {
- this.resetInputWidth()
- if (this.multiple) this.resetInputHeight()
- },
- setSelected() {
- if (this.multiple) {
- const result = []
- if (Array.isArray(this.value)) {
- this.value.forEach(value => {
- const node = this.getNodeData(value)
- if (node) result.push(node)
- })
- }
- this.selected = result
- if (result.length === 0) {
- this.selectedLabel = ''
- }
- this.$nextTick(this.resetInputHeight)
- } else {
- const node = this.getNodeData(this.value)
- if (node) {
- this.selected = node
- this.selectedLabel = this.displayMode === 'name' ? node.label : node.path
- if (this.filterable) this.query = this.selectedLabel
- } else {
- this.selected = {}
- this.selectedLabel = ''
- if (this.filterable) this.query = ''
- }
- }
- }
- }
- }
- </script>
- <style lang="scss">
- @import "~@/assets/styles/components/tree-select.scss";
- </style>
|