fix(autocomplete): popper usage (#429)

* fix(autocomplete): dropdownWidth

* fix: el-popper usage

* test: change test file

* fix: remove storybook doc dir

* test: sleep time
This commit is contained in:
Caaalabash 2020-10-22 14:02:28 +08:00 committed by GitHub
parent 8e95db293c
commit b4c84cc00a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 132 additions and 1343 deletions

View File

@ -50,19 +50,23 @@ describe('Autocomplete.vue', () => {
})
test('triggerOnFocus', async () => {
const wrapper = _mount()
const fetchSuggestions = jest.fn()
const wrapper = _mount({
debounce: 10,
fetchSuggestions,
})
await wrapper.setProps({ triggerOnFocus: false })
await wrapper.find('input').trigger('focus')
await sleep(500)
expect(wrapper.findAll('li').length).toBe(0)
await sleep(30)
expect(fetchSuggestions).toHaveBeenCalledTimes(0)
await wrapper.find('input').trigger('blur')
await wrapper.setProps({ triggerOnFocus: true })
await wrapper.find('input').trigger('focus')
await sleep(500)
expect(wrapper.findAll('li').length).toBe(4)
await sleep(30)
expect(fetchSuggestions).toHaveBeenCalledTimes(1)
})
test('popperClass', async () => {
@ -87,19 +91,14 @@ describe('Autocomplete.vue', () => {
})
test('popperAppendToBody', async () => {
const wrapper = _mount()
await wrapper.setProps({ popperAppendToBody: true })
expect(wrapper.find('.el-popper').exists()).toBe(false)
await wrapper.setProps({ popperAppendToBody: false })
expect(wrapper.find('.el-popper').exists()).toBe(true)
_mount({ popperAppendToBody: false })
expect(document.body.querySelector('.el-popper__mask')).toBeNull()
})
test('debounce / fetchSuggestions', async () => {
const fetchSuggestions = jest.fn()
const wrapper = _mount({
debounce: 500,
debounce: 10,
fetchSuggestions,
})
@ -110,20 +109,23 @@ describe('Autocomplete.vue', () => {
await wrapper.find('input').trigger('focus')
await wrapper.find('input').trigger('blur')
expect(fetchSuggestions).toHaveBeenCalledTimes(0)
await sleep(600)
await sleep(30)
expect(fetchSuggestions).toHaveBeenCalledTimes(1)
await wrapper.find('input').trigger('focus')
await sleep(600)
await sleep(30)
expect(fetchSuggestions).toHaveBeenCalledTimes(2)
})
test('valueKey / modelValue', async () => {
const wrapper = _mount()
const target = wrapper.findComponent({ ref: 'autocomplete' }).vm as any
await target.select({ value: 'Go', tag: 'go' })
expect(wrapper.vm.state).toBe('Go')
await wrapper.setProps({ valueKey: 'tag' })
await wrapper.find('input').trigger('focus')
await sleep(500)
await wrapper.findAll('li')[1].trigger('click')
await target.select({ value: 'Go', tag: 'go' })
expect(wrapper.vm.state).toBe('go')
})
@ -131,36 +133,42 @@ describe('Autocomplete.vue', () => {
const wrapper = _mount({
hideLoading: false,
fetchSuggestions: NOOP,
debounce: 10,
})
await wrapper.find('input').trigger('focus')
await sleep(500)
expect(wrapper.find('.el-icon-loading').exists()).toBe(true)
await sleep(30)
expect(document.body.querySelector('.el-icon-loading')).toBeDefined()
await wrapper.setProps({ hideLoading: true })
expect(wrapper.find('.el-icon-loading').exists()).toBe(false)
expect(document.body.querySelector('.el-icon-loading')).toBeNull()
})
test('selectWhenUnmatched', async () => {
const wrapper = mount(Autocomplete, {
props: {
selectWhenUnmatched: true,
debounce: 10,
},
})
wrapper.vm.highlightedIndex = 0
wrapper.vm.handleKeyEnter()
await sleep(500)
await sleep(30)
expect(wrapper.vm.highlightedIndex).toBe(-1)
})
test('highlightFirstItem', async () => {
const wrapper = _mount({ highlightFirstItem: false })
const wrapper = _mount({
highlightFirstItem: false,
debounce: 10,
})
await wrapper.find('input').trigger('focus')
await sleep(500)
expect(wrapper.find('.highlighted').exists()).toBe(false)
await sleep(30)
expect(document.body.querySelector('.highlighted')).toBeNull()
await wrapper.setProps({ highlightFirstItem: true })
await wrapper.find('input').trigger('focus')
await sleep(500)
expect(wrapper.find('.highlighted').text()).toBe('Java')
await sleep(30)
expect(document.body.querySelector('.highlighted')).toBeDefined()
})
})

View File

@ -1,90 +1,93 @@
<template>
<div
v-clickoutside="close"
class="el-autocomplete"
role="combobox"
aria-haspopup="listbox"
:aria-expanded="suggestionVisible"
:aria-owns="id"
<el-popper
ref="popper"
v-model:visible="suggestionVisible"
:placement="placement"
:popper-class="popperClass"
:append-to-body="popperAppendToBody"
:offset="6"
pure
manual-mode
effect="light"
trigger="click"
>
<el-input
ref="inputRef"
v-bind="$attrs"
:model-value="modelValue"
@input="handleInput"
@change="handleChange"
@focus="handleFocus"
@blur="handleBlur"
@clear="handleClear"
@keydown.up.prevent="highlight(highlightedIndex - 1)"
@keydown.down.prevent="highlight(highlightedIndex + 1)"
@keydown.enter.prevent="handleKeyEnter"
@keydown.tab.prevent="close"
>
<template v-if="$slots.prepend" #prepend>
<slot name="prepend"></slot>
</template>
<template v-if="$slots.append" #append>
<slot name="append"></slot>
</template>
<template v-if="$slots.prefix" #prefix>
<slot name="prefix"></slot>
</template>
<template v-if="$slots.suffix" #suffix>
<slot name="suffix"></slot>
</template>
</el-input>
<el-popper
class="el-autocomplete-suggestion"
:placement="placement"
:popper-class="popperClass"
:append-to-body="popperAppendToBody"
:visible="suggestionVisible"
:show-arrow="false"
pure
effect="light"
trigger="click"
>
<template #trigger>
<transition name="el-zoom-in-top">
<div
v-show="suggestionVisible"
ref="regionRef"
:class="{ 'is-loading': suggestionLoading }"
style="outline: none"
role="region"
<template #trigger>
<div
v-clickoutside="close"
class="el-autocomplete"
role="combobox"
aria-haspopup="listbox"
:aria-expanded="suggestionVisible"
:aria-owns="id"
>
<el-input
ref="inputRef"
v-bind="$attrs"
:model-value="modelValue"
@input="handleInput"
@change="handleChange"
@focus="handleFocus"
@blur="handleBlur"
@clear="handleClear"
@keydown.up.prevent="highlight(highlightedIndex - 1)"
@keydown.down.prevent="highlight(highlightedIndex + 1)"
@keydown.enter.prevent="handleKeyEnter"
@keydown.tab.prevent="close"
>
<template v-if="$slots.prepend" #prepend>
<slot name="prepend"></slot>
</template>
<template v-if="$slots.append" #append>
<slot name="append"></slot>
</template>
<template v-if="$slots.prefix" #prefix>
<slot name="prefix"></slot>
</template>
<template v-if="$slots.suffix" #suffix>
<slot name="suffix"></slot>
</template>
</el-input>
</div>
</template>
<template #default>
<transition name="el-zoom-in-top" @after-leave="doDestroy">
<div
v-show="suggestionVisible"
ref="regionRef"
:class="['el-autocomplete-suggestion', suggestionLoading && 'is-loading']"
:style="{ width: dropdownWidth, outline: 'none' }"
role="region"
>
<el-scrollbar
tag="ul"
wrap-class="el-autocomplete-suggestion__wrap"
view-class="el-autocomplete-suggestion__list"
>
<el-scrollbar
tag="ul"
wrap-class="el-autocomplete-suggestion__wrap"
view-class="el-autocomplete-suggestion__list"
>
<li v-if="suggestionLoading">
<i class="el-icon-loading"></i>
<li v-if="suggestionLoading">
<i class="el-icon-loading"></i>
</li>
<template v-else>
<li
v-for="(item, index) in suggestions"
:id="`${id}-item-${index}`"
:key="index"
:class="{'highlighted': highlightedIndex === index}"
role="option"
:aria-selected="highlightedIndex === index"
@click="select(item)"
>
<slot :item="item">{{ item[valueKey] }}</slot>
</li>
<template v-else>
<li
v-for="(item, index) in suggestions"
:id="`${id}-item-${index}`"
:key="index"
:class="{'highlighted': highlightedIndex === index}"
role="option"
:aria-selected="highlightedIndex === index"
@click="select(item)"
>
<slot :item="item">{{ item[valueKey] }}</slot>
</li>
</template>
</el-scrollbar>
</div>
</transition>
</template>
</el-popper>
</div>
</template>
</el-scrollbar>
</div>
</transition>
</template>
</el-popper>
</template>
<script lang="ts">
import { defineComponent, ref, computed, onMounted, nextTick, PropType } from 'vue'
import { defineComponent, ref, computed, onMounted, nextTick, PropType, watch } from 'vue'
import { NOOP } from '@vue/shared'
import debounce from 'lodash/debounce'
import { ClickOutside } from '@element-plus/directives'
@ -159,11 +162,13 @@ export default defineComponent({
setup(props, ctx) {
const suggestions = ref([])
const highlightedIndex = ref(-1)
const dropdownWidth = ref('')
const activated = ref(false)
const suggestionDisabled = ref(false)
const loading = ref(false)
const inputRef = ref(null)
const regionRef = ref(null)
const popper = ref(null)
const id = computed(() => {
return `el-autocomplete-${generateId()}`
@ -176,6 +181,10 @@ export default defineComponent({
return !props.hideLoading && loading.value
})
watch(suggestionVisible, () => {
dropdownWidth.value = `${inputRef.value.$el.offsetWidth}px`
})
onMounted(() => {
inputRef.value.inputOrTextarea.setAttribute('role', 'textbox')
inputRef.value.inputOrTextarea.setAttribute('aria-autocomplete', 'list')
@ -288,15 +297,20 @@ export default defineComponent({
highlightedIndex.value = index
inputRef.value.inputOrTextarea.setAttribute('aria-activedescendant', `${id.value}-item-${highlightedIndex.value}`)
}
const doDestroy = () => {
popper.value.doDestroy()
}
return {
suggestions,
highlightedIndex,
dropdownWidth,
activated,
suggestionDisabled,
loading,
inputRef,
regionRef,
popper,
id,
suggestionVisible,
@ -313,6 +327,7 @@ export default defineComponent({
focus,
select,
highlight,
doDestroy,
}
},
})

View File

@ -1,35 +0,0 @@
<template>
<div>
<el-color-picker v-model="color" :predefine="predefineColors" @active-change="actChange" />
</div>
</template>
<script>
export default {
data() {
return {
color: '#20A0FF',
predefineColors: [
'#ff4500',
'#ff8c00',
'#ffd700',
'#90ee90',
'#00ced1',
'#1e90ff',
'#c71585',
'rgba(255, 69, 0, 0.68)',
'rgb(255, 120, 0)',
'hsv(51, 100, 98)',
'hsva(120, 40, 94, 0.5)',
'hsl(181, 100%, 37%)',
'hsla(209, 100%, 56%, 0.73)',
],
}
},
methods: {
actChange(colorString) {
console.log(colorString)
},
},
}
</script>

View File

@ -1,5 +0,0 @@
export default {
title: 'Color-picker',
}
export { default as BasicUsage } from './basic.vue'

View File

@ -25,6 +25,8 @@
"@element-plus/timeline": "^0.0.0",
"@element-plus/divider": "^0.0.0",
"@element-plus/icon": "^0.0.0",
"@element-plus/input": "^0.0.0",
"@element-plus/input-number": "^0.0.0",
"@element-plus/layout": "^0.0.0",
"@element-plus/link": "^0.0.0",
"@element-plus/progress": "^0.0.0",

View File

@ -1,42 +0,0 @@
<template>
<div class="wrapper">
<el-button class="item" @click="onClick">Show Message</el-button>
<el-button class="item" @click="onClickVNode">VNode</el-button>
<el-button class="item" @click="onClickGlobal">Global Method</el-button>
<el-button class="item" @click="onClickClose">Close All</el-button>
</div>
</template>
<script lang="ts">
import Message from '../src/message'
import { createVNode } from 'vue'
export default {
methods: {
onClick(): void {
Message({ message: 'This is a message' })
},
onClickVNode(): void {
const h = createVNode
const vnode = h('p', null, [
h('span', null, 'Message can be a '),
h('i', { style: 'color: teal' }, 'VNode'),
])
Message({ message: vnode })
},
onClickGlobal(): void {
this.$message('Using global method')
},
onClickClose(): void {
this.$message.closeAll()
},
},
}
</script>
<style scoped>
.wrapper {
position: absolute;
bottom: 10px;
}
</style>

View File

@ -1,17 +0,0 @@
<template>
<div>
<el-button class="item" @click="onClick">Centered text</el-button>
</div>
</template>
<script lang="ts">
import Message from '../src/message'
export default {
methods: {
onClick(): void {
Message({ message: 'Centered text', center: true })
},
},
}
</script>

View File

@ -1,41 +0,0 @@
<template>
<div class="wrapper">
<el-button class="item" @click="onClickSuccess">success</el-button>
<el-button class="item" @click="onClickWarning">warning</el-button>
<el-button class="item" @click="onClickMessage">message</el-button>
<el-button class="item" @click="onClickError">error</el-button>
</div>
</template>
<script lang="ts">
import Message from '../src/message'
export default {
methods: {
onClickSuccess(): void {
Message({
message: 'Congrats, this is a success message',
type: 'success',
showClose: true,
})
},
onClickWarning(): void {
Message({
message: 'Warning, this is a warning message',
type: 'warning',
showClose: true,
})
},
onClickMessage(): void {
Message({ message: 'This is a message', showClose: true })
},
onClickError(): void {
Message({
message: 'Error, this is an error message',
type: 'error',
showClose: true,
})
},
},
}
</script>

View File

@ -1,20 +0,0 @@
<template>
<div>
<el-button class="item" @click="onClick">Use HTML String</el-button>
</div>
</template>
<script lang="ts">
import Message from '../src/message'
export default {
methods: {
onClick(): void {
Message({
message: '<strong>This is <i>HTML</i><strong>',
dangerouslyUseHTMLString: true,
})
},
},
}
</script>

View File

@ -1,10 +0,0 @@
export default {
title: 'Message',
}
export { default as BaseMessage } from './basic.vue'
export { default as TypeMessages } from './types.vue'
export { default as CloseableMessages } from './closeable.vue'
export { default as CenteredMessages } from './centered.vue'
export { default as HTMLMessages } from './html.vue'
export { default as ManualClose } from './manual_close.vue'

View File

@ -1,40 +0,0 @@
<template>
<div>
<el-button class="item" @click="handleOpen">Show Message</el-button>
<el-button class="item" @click="handleClose">Close Message</el-button>
</div>
</template>
<script lang="ts">
import Message from '../src/message'
import type { IMessageHandle } from '../src/types'
import { ref } from 'vue'
export default {
setup() {
const messageBanner = ref<IMessageHandle>(null)
// methods
const handleOpen = () => {
if (messageBanner.value) return
messageBanner.value = Message({
message: "This won't close automatically",
duration: 0,
})
}
const handleClose = () => {
if (messageBanner.value) {
messageBanner.value.close()
messageBanner.value = null
}
}
return {
messageBanner,
handleOpen,
handleClose,
}
},
}
</script>

View File

@ -1,35 +0,0 @@
<template>
<div class="wrapper">
<el-button class="item" @click="onClickSuccess">success</el-button>
<el-button class="item" @click="onClickWarning">warning</el-button>
<el-button class="item" @click="onClickMessage">message</el-button>
<el-button class="item" @click="onClickError">error</el-button>
</div>
</template>
<script lang="ts">
import Message from '../src/message'
export default {
methods: {
onClickSuccess(): void {
Message({
message: 'Congrats, this is a success message',
type: 'success',
})
},
onClickWarning(): void {
Message({
message: 'Warning, this is a warning message',
type: 'warning',
})
},
onClickMessage(): void {
Message({ message: 'This is a message' })
},
onClickError(): void {
Message({ message: 'Error, this is an error message', type: 'error' })
},
},
}
</script>

View File

@ -1,39 +0,0 @@
<template>
<el-popconfirm
confirm-button-text="OK"
cancel-button-text="NO"
icon="el-icon-info"
icon-color="red"
title="Are you sure to delete this?"
confirm-button-type="success"
cancel-button-type="info"
@confirm="onConfirm"
@cancel="onCancel"
>
<template #reference>
<el-button>delete</el-button>
</template>
</el-popconfirm>
</template>
<script>
export default {
name: 'Basic',
setup(){
const onConfirm = () => {
console.log('Confirm button clicked')
}
const onCancel = () => {
console.log('Cancel button clicked')
}
return {
onConfirm,
onCancel,
}
},
}
</script>
<style scoped>
</style>

View File

@ -1,6 +0,0 @@
export default {
title: 'Popconfirm',
}
export { default as BasicUsage } from './basic.vue'

View File

@ -1,94 +0,0 @@
<template>
<div class="block">
<el-tree
ref="tree"
:data="data"
:props="defaultProps"
show-checkbox
node-key="id"
default-expand-all
/>
<button @click="test">test</button>
</div>
</template>
<script lang='ts'>
import { defineComponent } from 'vue'
export default defineComponent({
data() {
return {
count: 1,
defaultExpandedKeys: [1, 3],
data: [{
id: 1,
label: '一级 1',
children: [{
id: 11,
label: '二级 1-1',
children: [{
id: 111,
label: '三级 1-1',
}],
}],
}, {
id: 2,
label: '一级 2',
children: [{
id: 21,
label: '二级 2-1',
}, {
id: 22,
label: '二级 2-2',
}],
}, {
id: 3,
label: '一级 3',
children: [{
id: 31,
label: '二级 3-1',
}, {
id: 32,
label: '二级 3-2',
}],
}],
defaultProps: {
children: 'children',
label: 'label',
},
}
},
methods: {
loadNode(node, resolve) {
if (node.level === 0) {
return resolve([{ label: 'region1', id: this.count++ }, { label: 'region2', id: this.count++ }])
}
if (node.level > 2) return resolve([])
setTimeout(() => {
resolve([{
label: 'zone' + this.count,
id: this.count++,
}, {
label: 'zone' + this.count,
id: this.count++,
}])
}, 50)
},
handleCheck(...a) {
console.log(...a)
},
test() {
console.log(111)
// this.$refs.tree.updateKeyChildren(1, [{
// id: 111,
// label: ' 1-1'
// }]);
},
handleNodeClick(data) {
console.log(data)
},
},
})
</script>

View File

@ -1,104 +0,0 @@
<template>
<el-tree
:data="data"
node-key="id"
default-expand-all
draggable
:allow-drop="allowDrop"
:allow-drag="allowDrag"
@node-drag-start="handleDragStart"
@node-drag-enter="handleDragEnter"
@node-drag-leave="handleDragLeave"
@node-drag-over="handleDragOver"
@node-drag-end="handleDragEnd"
@node-drop="handleDrop"
/>
</template>
<script>
export default {
data() {
return {
data: [{
id: 1,
label: '一级 1',
children: [{
id: 4,
label: '二级 1-1',
children: [{
id: 9,
label: '三级 1-1-1',
}, {
id: 10,
label: '三级 1-1-2',
}],
}],
}, {
id: 2,
label: '一级 2',
children: [{
id: 5,
label: '二级 2-1',
}, {
id: 6,
label: '二级 2-2',
}],
}, {
id: 3,
label: '一级 3',
children: [{
id: 7,
label: '二级 3-1',
}, {
id: 8,
label: '二级 3-2',
children: [{
id: 11,
label: '三级 3-2-1',
}, {
id: 12,
label: '三级 3-2-2',
}, {
id: 13,
label: '三级 3-2-3',
}],
}],
}],
defaultProps: {
children: 'children',
label: 'label',
},
}
},
methods: {
handleDragStart(node) {
console.log('drag start', node)
},
handleDragEnter(draggingNode, dropNode) {
console.log('tree drag enter: ', dropNode.label)
},
handleDragLeave(draggingNode, dropNode) {
console.log('tree drag leave: ', dropNode.label)
},
handleDragOver(draggingNode, dropNode) {
console.log('tree drag over: ', dropNode.label)
},
handleDragEnd(draggingNode, dropNode, dropType) {
console.log('tree drag end: ', dropNode && dropNode.label, dropType)
},
handleDrop(draggingNode, dropNode, dropType) {
console.log('tree drop: ', dropNode.label, dropType)
},
allowDrop(draggingNode, dropNode, type) {
if (dropNode.data.label === '二级 3-1') {
return type !== 'inner'
} else {
return true
}
},
allowDrag(draggingNode) {
return draggingNode.data.label.indexOf('三级 3-2-2') === -1
},
},
}
</script>

View File

@ -1,60 +0,0 @@
<template>
<el-tree
:props="props"
:load="loadNode"
lazy
show-checkbox
@check-change="handleCheckChange"
/>
</template>
<script>
export default {
data() {
return {
props: {
label: 'name',
children: 'zones',
},
count: 1,
}
},
methods: {
handleCheckChange(data, checked, indeterminate) {
console.log(data, checked, indeterminate)
},
handleNodeClick(data) {
console.log(data)
},
loadNode(node, resolve) {
if (node.level === 0) {
return resolve([{ name: 'region1' }, { name: 'region2' }])
}
if (node.level > 3) return resolve([])
var hasChild
if (node.data.name === 'region1') {
hasChild = true
} else if (node.data.name === 'region2') {
hasChild = false
} else {
hasChild = Math.random() > 0.5
}
setTimeout(() => {
var data
if (hasChild) {
data = [{
name: 'zone' + this.count++,
}, {
name: 'zone' + this.count++,
}]
} else {
data = []
}
resolve(data)
}, 500)
},
},
}
</script>

View File

@ -1,40 +0,0 @@
<template>
<el-tree
:props="props"
:load="loadNode"
lazy
show-checkbox
/>
</template>
<script>
export default {
data() {
return {
props: {
label: 'name',
children: 'zones',
isLeaf: 'leaf',
},
}
},
methods: {
loadNode(node, resolve) {
if (node.level === 0) {
return resolve([{ name: 'region' }])
}
if (node.level > 1) return resolve([])
setTimeout(() => {
const data = [{
name: 'leaf',
leaf: true,
}, {
name: 'zone',
}]
resolve(data)
}, 500)
},
},
}
</script>

View File

@ -1,59 +0,0 @@
<template>
<el-tree
:data="data"
show-checkbox
node-key="id"
:default-expanded-keys="[2, 3]"
:default-checked-keys="[5]"
:props="defaultProps"
check-on-click-node
highlight-current
/>
</template>
<script>
export default {
data() {
return {
data: [{
id: 1,
label: '一级 1',
children: [{
id: 4,
label: '二级 1-1',
children: [{
id: 9,
label: '三级 1-1-1',
}, {
id: 10,
label: '三级 1-1-2',
}],
}],
}, {
id: 2,
label: '一级 2',
children: [{
id: 5,
label: '二级 2-1',
}, {
id: 6,
label: '二级 2-2',
}],
}, {
id: 3,
label: '一级 3',
children: [{
id: 7,
label: '二级 3-1',
}, {
id: 8,
label: '二级 3-2',
}],
}],
defaultProps: {
children: 'children',
label: 'label',
},
}
},
}
</script>

View File

@ -1,49 +0,0 @@
<template>
<el-tree
:data="data"
show-checkbox
node-key="id"
:default-expanded-keys="[2, 3]"
:default-checked-keys="[5]"
/>
</template>
<script>
export default {
data() {
return {
data: [{
id: 1,
label: '一级 2',
children: [{
id: 3,
label: '二级 2-1',
children: [{
id: 4,
label: '三级 3-1-1',
}, {
id: 5,
label: '三级 3-1-2',
disabled: true,
}],
}, {
id: 2,
label: '二级 2-2',
disabled: true,
children: [{
id: 6,
label: '三级 3-2-1',
}, {
id: 7,
label: '三级 3-2-2',
disabled: true,
}],
}],
}],
defaultProps: {
children: 'children',
label: 'label',
},
}
},
}
</script>

View File

@ -1,92 +0,0 @@
<template>
<el-tree
ref="tree"
:data="data"
show-checkbox
default-expand-all
node-key="id"
highlight-current
:props="defaultProps"
check-on-click-node
/>
<div class="buttons">
<el-button @click="getCheckedNodes">通过 node 获取</el-button>
<el-button @click="getCheckedKeys">通过 key 获取</el-button>
<el-button @click="setCheckedNodes">通过 node 设置</el-button>
<el-button @click="setCheckedKeys">通过 key 设置</el-button>
<el-button @click="resetChecked">清空</el-button>
</div>
</template>
<script>
export default {
data() {
return {
data: [{
id: 1,
label: '一级 1',
children: [{
id: 4,
label: '二级 1-1',
children: [{
id: 9,
label: '三级 1-1-1',
}, {
id: 10,
label: '三级 1-1-2',
}],
}],
}, {
id: 2,
label: '一级 2',
children: [{
id: 5,
label: '二级 2-1',
}, {
id: 6,
label: '二级 2-2',
}],
}, {
id: 3,
label: '一级 3',
children: [{
id: 7,
label: '二级 3-1',
}, {
id: 8,
label: '二级 3-2',
}],
}],
defaultProps: {
children: 'children',
label: 'label',
},
}
},
methods: {
getCheckedNodes() {
console.log(this.$refs.tree.getCheckedNodes())
},
getCheckedKeys() {
console.log(this.$refs.tree.getCheckedKeys())
},
setCheckedNodes() {
this.$refs.tree.setCheckedNodes([{
id: 5,
label: '二级 2-1',
}, {
id: 9,
label: '三级 1-1-1',
}])
},
setCheckedKeys() {
this.$refs.tree.setCheckedKeys([3])
},
resetChecked() {
this.$refs.tree.setCheckedKeys([])
},
},
}
</script>

View File

@ -1,127 +0,0 @@
<template>
<div class="custom-tree-container">
<div class="block">
<p>使用 render-content</p>
<el-tree
:data="data"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false"
:render-content="renderContent"
/>
</div>
<div class="block">
<p>使用 scoped slot</p>
<el-tree
:data="data"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false"
>
<template #default="{ node, data: innerData }">
<span class="custom-tree-node">
<span>{{ node.label }}</span>
<span>
<el-button
type="text"
size="mini"
@click="append(innerData)"
>
Append
</el-button>
<el-button
type="text"
size="mini"
@click="remove(node, innerData)"
>
Delete
</el-button>
</span>
</span>
</template>
</el-tree>
</div>
</div>
</template>
<script>
let id = 1000
export default {
data() {
const data = [{
id: 1,
label: '一级 1',
children: [{
id: 4,
label: '二级 1-1',
children: [{
id: 9,
label: '三级 1-1-1',
}, {
id: 10,
label: '三级 1-1-2',
}],
}],
}, {
id: 2,
label: '一级 2',
children: [{
id: 5,
label: '二级 2-1',
}, {
id: 6,
label: '二级 2-2',
}],
}, {
id: 3,
label: '一级 3',
children: [{
id: 7,
label: '二级 3-1',
}, {
id: 8,
label: '二级 3-2',
}],
}]
return {
data: JSON.parse(JSON.stringify(data)),
}
},
methods: {
append(data) {
const newChild = { id: id++, label: 'testtest', children: [] }
if (!data.children) {
data.children = []
}
data.children.push(newChild)
this.data = [...this.data]
},
remove(node, data) {
const parent = node.parent
const children = parent.data.children || parent.data
const index = children.findIndex(d => d.id === data.id)
children.splice(index, 1)
this.data = [...this.data]
},
renderContent(h) {
return h('div', { class: 'custom-tree-node' }, ['haha'])
},
},
}
</script>
<style>
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
</style>

View File

@ -1,78 +0,0 @@
<template>
<input
v-model="filterText"
placeholder="输入关键字进行过滤"
>
<el-tree
ref="tree"
class="filter-tree"
:data="data"
:props="defaultProps"
default-expand-all
:filter-node-method="filterNode"
/>
</template>
<script>
export default {
data() {
return {
filterText: '',
data: [{
id: 1,
label: '一级 1',
children: [{
id: 4,
label: '二级 1-1',
children: [{
id: 9,
label: '三级 1-1-1',
}, {
id: 10,
label: '三级 1-1-2',
}],
}],
}, {
id: 2,
label: '一级 2',
children: [{
id: 5,
label: '二级 2-1',
}, {
id: 6,
label: '二级 2-2',
}],
}, {
id: 3,
label: '一级 3',
children: [{
id: 7,
label: '二级 3-1',
}, {
id: 8,
label: '二级 3-2',
}],
}],
defaultProps: {
children: 'children',
label: 'label',
},
}
},
watch: {
filterText(val) {
this.$refs.tree.filter(val)
},
},
methods: {
filterNode(value, data) {
if (!value) return true
return data.label.indexOf(value) !== -1
},
},
}
</script>

View File

@ -1,62 +0,0 @@
<template>
<el-tree
:data="data"
:props="defaultProps"
accordion
@node-click="handleNodeClick"
/>
</template>
<script>
export default {
data() {
return {
data: [{
label: '一级 1',
children: [{
label: '二级 1-1',
children: [{
label: '三级 1-1-1',
}],
}],
}, {
label: '一级 2',
children: [{
label: '二级 2-1',
children: [{
label: '三级 2-1-1',
}],
}, {
label: '二级 2-2',
children: [{
label: '三级 2-2-1',
}],
}],
}, {
label: '一级 3',
children: [{
label: '二级 3-1',
children: [{
label: '三级 3-1-1',
}],
}, {
label: '二级 3-2',
children: [{
label: '三级 3-2-1',
}],
}],
}],
defaultProps: {
children: 'children',
label: 'label',
},
}
},
methods: {
handleNodeClick(data) {
console.log(data)
},
},
}
</script>

View File

@ -1,6 +0,0 @@
export { default as BasicUsage } from './index.vue'
export default {
title: 'Tree',
}

View File

@ -1,77 +0,0 @@
<template>
<div class="item">
<div class="title">1</div>
<Basic />
</div>
<div class="item">
<div class="title">2</div>
<Basic2 />
</div>
<div class="item">
<div class="title">3</div>
<Basic3 />
</div>
<div class="item">
<div class="title">4</div>
<Basic4 />
</div>
<div class="item">
<div class="title">5</div>
<Basic5 />
</div>
<div class="item">
<div class="title">6</div>
<Basic6 />
</div>
<div class="item">
<div class="title">7</div>
<Basic7 />
</div>
<div class="item">
<div class="title">8</div>
<Basic8 />
</div>
<div class="item">
<div class="title">9</div>
<Basic9 />
</div>
<div class="item">
<div class="title">10</div>
<Basic10 />
</div>
</template>
<script>
import Basic from './basic.vue'
import Basic2 from './basic2.vue'
import Basic3 from './basic3.vue'
import Basic4 from './basic4.vue'
import Basic5 from './basic5.vue'
import Basic6 from './basic6.vue'
import Basic7 from './basic7.vue'
import Basic8 from './basic8.vue'
import Basic9 from './basic9.vue'
import Basic10 from './basic10.vue'
export default {
components: {
Basic,
Basic2,
Basic3,
Basic4,
Basic5,
Basic6,
Basic7,
Basic8,
Basic9,
Basic10,
},
}
</script>
<style>
.item {
border: 2px solid #eee;
padding: 10px;
margin-bottom: 15px;
}
</style>

View File

@ -1,42 +0,0 @@
<template>
<el-upload
class="upload-demo"
action="https://jsonplaceholder.typicode.com/posts/"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:multiple="multiple"
:limit="3"
:on-exceed="handleExceed"
:file-list="fileList"
>
<el-button size="small" type="primary">Click to upload</el-button>
<template #tip>
<div class="el-upload__tip">jpg/png files with a size less than 500kb</div>
</template>
</el-upload>
</template>
<script lang="ts">
export default {
data() {
return {
fileList: [{ name: 'food.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100' }, { name: 'food2.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100' }],
}
},
methods: {
handleRemove(file, fileList) {
console.log(file, fileList)
},
handlePreview(file) {
console.log(file)
},
handleExceed(files, fileList) {
this.$message.warning(`The limit is 3, you selected ${files.length} files this time, add up to ${files.length + fileList.length} totally`)
},
beforeRemove(file, fileList) {
console.log(`Cancel the transfert of ${ file.name } ?`)
console.log(fileList)
},
},
}
</script>

View File

@ -1,17 +0,0 @@
<template>
<el-upload
class="upload-demo"
drag
action="https://jsonplaceholder.typicode.com/posts/"
:on-preview="handlePreview"
:on-remove="handleRemove"
:file-list="fileList"
multiple
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">Drop file here or <em>click to upload</em></div>
<template #tip>
<div class="el-upload__tip">jpg/png files with a size less than 500kb</div>
</template>
</el-upload>
</template>

View File

@ -1,7 +0,0 @@
export default {
title: 'Upload',
}
export { default as BasicUsage } from './basic.vue'
export { default as ManualUpload } from './manual.vue'
export { default as DropToUpload } from './drop-to-upload.vue'

View File

@ -1,32 +0,0 @@
<template>
<el-upload
ref="upload"
class="upload-demo"
action="https://jsonplaceholder.typicode.com/posts/"
:auto-upload="false"
>
<template #trigger>
<el-button size="small" type="primary">select file</el-button>
</template>
<el-button
style="margin-left: 10px;"
size="small"
type="success"
@click="submitUpload"
>
upload to server
</el-button>
<template #tip>
<div class="el-upload__tip">jpg/png files with a size less than 500kb</div>
</template>
</el-upload>
</template>
<script>
export default {
methods: {
submitUpload() {
this.$refs.upload.submit()
},
},
}
</script>