Tasking
-
阅读Vue 组合式 API文档
-
穿梭框
- Attributes:
- 通过
<el-transfer v-model="value" :data="data"></el-transfer>定义一个默认的 Transfer 组件- 只有data情况的渲染
- data和v-model都有的渲染
- 点击左右按钮列表内容变化
- 通过
props.targetOrder控制 Transfer 组件右侧列表元素的排序策略:original(与数据源相同的顺序)/push(新加入的元素排在最后)/unshift (新加入的元素排在最前) - 通过
props.filterable控制 Transfer 组件开启搜索模式 - 通过
props.filterPlaceHolder控制 Transfer 组件搜索框占位符 - 通过
props.filterMethod控制 Transfer 组件自定义搜索 - 通过
props.titles控制 Transfer 组件自定义标题 - 通过
props.buttonTexts控制 Transfer 组件按钮自定义文案 - 通过
props.renderContent控制 Transfer 组件自定义数据项渲染函数 - 通过
props.format控制 Transfer 组件列表顶部勾选状态文案 { noChecked: '{total}', hasChecked: '{total}' } - 通过
props.props控制 Transfer 组件数据源的字段别名 - 通过
props.leftDefaultChecked控制 Transfer 组件初始状态下左侧列表的已勾选项的 key 数组 - 通过
props.rightDefaultChecked控制 Transfer 组件初始状态下右侧列表的已勾选项的 key 数组 - slot:
- left-footer 左侧列表底部内容
- right-footer右侧列表底部内容
- Scoped Slot:
- 自定义数据项的内容,参数为 { option }
- Methods:
- clearQuery 清空某个面板的搜索关键词 参数:'left' / 'right',指定需要清空的面板
- Events:
- change 右侧列表元素变化时触发 回调参数:当前值、数据移动的方向('left' / 'right')、发生移动的数据 key 数组
- left-check-change 左侧列表元素被用户选中 / 取消选中时触发 回调参数:当前被选中的元素的 key 数组、选中状态发生变化的元素的 key 数组
- right-check-change 右侧列表元素被用户选中 / 取消选中时触发 回调参数:当前被选中的元素的 key 数组、选中状态发生变化的元素的 key 数组
单元测试+代码
测试准备
import ElTransfer from '../src1/Transfer.vue'
import { mount } from '@vue/test-utils'
import { toRefs, reactive, nextTick, ref } from 'vue'
const getTestData = () => {
const data = []
for (let i = 1; i <= 15; i++) {
data.push({
key: i,
label: `备选项 ${i}`,
disabled: i % 4 === 0
})
}
return data
}
const createTransfer = (props, opts) => {
return Object.assign({
template: `
<el-transfer :data="testData" ref="transfer" ${props}>
</el-transfer>
`,
components: {
'el-transfer': ElTransfer
},
data() {
return {
testData: getTestData()
}
}
}, opts)
}
通过 <el-transfer v-model="value" :data="data"></el-transfer> 定义一个默认的 Transfer 组件
只有data情况的渲染
测试
describe('Transfer', () => {
it('render data', () => {
const Comp = createTransfer('', {})
const wrapper = mount(Comp)
const transferLeft = wrapper.get('[data-test="transfer-panel-left"]')
expect(transferLeft.findAll('.el-transfer-panel__item')).toHaveLength(15)
})
})
代码
<template>
<div class="el-transfer">
<div class="el-transfer-panel" data-test="transfer-panel-left">
<el-checkbox-group>
<el-checkbox
class="el-transfer-panel__item"
:label="item.key"
:key="item.key"
v-for="item in data"
>
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</template>
<script>
import ElCheckboxGroup from '../../checkbox-group'
import ElCheckbox from '../../checkbox'
export default {
name: 'ElTransfer',
components: {
TransferPanel
},
props: {
data: {
type: Array,
default() {
return []
}
},
modelValue: {
type: Array,
default() {
return []
}
}
}
}
</script>
data和v-model都有的渲染
测试
describe('Transfer', () => {
it('default target list', () => {
const Comp = createTransfer('v-model="value"', {
setup() {
const state = reactive({
value: [1, 4]
})
return toRefs(state)
}
})
const wrapper = mount(Comp)
const transferLeft = wrapper.get('[data-test="transfer-panel-left"]')
const transferRight = wrapper.get('[data-test="transfer-panel-right"]')
expect(transferLeft.findAll('.el-transfer-panel__item')).toHaveLength(13)
expect(transferRight.findAll('.el-transfer-panel__item')).toHaveLength(2)
})
})
代码
<template>
<div class="el-transfer">
<div class="el-transfer-panel" data-test="transfer-panel-left">
<el-checkbox-group>
<el-checkbox
class="el-transfer-panel__item"
:label="item.key"
:key="item.key"
v-for="item in sourceData"
>
</el-checkbox>
</el-checkbox-group>
</div>
<div class="el-transfer-panel" data-test="transfer-panel-right">
<el-checkbox-group>
<el-checkbox
class="el-transfer-panel__item"
:label="item.key"
:key="item.key"
v-for="item in targetData"
>
</el-checkbox>
</el-checkbox-group>
</div>
</template>
<script>
import TransferPanel from './TransferPanel.vue'
export default {
name: 'ElTransfer',
components: {
TransferPanel
},
props: {
data: {
type: Array,
default() {
return []
}
},
modelValue: {
type: Array,
default() {
return []
}
}
},
setup(props, { emit, slots }) {
const { sourceData, targetData } = useTransferData(props)
return {
sourceData,
targetData
}
}
}
const useTransferData = (props) => {
let sourceData = []
let targetData = []
let count = 0
const { data, modelValue } = props
const len = modelValue.length
if (modelValue.length > 0) {
for (let i = 0; i < data.length; i++) {
const item = data[i]
if (count < len && modelValue.indexOf(item.key) > -1) {
targetData.push(item)
count++
continue
}
sourceData.push(item)
}
} else {
sourceData = data
}
return {
sourceData,
targetData
}
}
</script>
点击左右按钮列表内容变化
测试
describe('Transfer', () => {
it('transfer', async () => {
const Comp = createTransfer('v-model="value"', {
setup() {
const state = reactive({
value: [1, 4]
})
return toRefs(state)
}
})
const wrapper = mount(Comp)
const transferLeft = wrapper.get('[data-test="transfer-panel-left"]')
const transferRight = wrapper.get('[data-test="transfer-panel-right"]')
const buttonLeft = wrapper.get('[data-test="transfer__button-left"]')
const buttonRight = wrapper.get('[data-test="transfer__button-right"]')
// 初始左13 右2
expect(transferLeft.findAll('.el-transfer-panel__item')).toHaveLength(13)
expect(transferRight.findAll('.el-transfer-panel__item')).toHaveLength(2)
// 左1选中,点击左按钮 左12 右3
transferLeft.findAll('.el-transfer-panel__item')[0].trigger('click')
await nextTick()
buttonRight.trigger('click')
await nextTick()
expect(transferLeft.findAll('.el-transfer-panel__item')).toHaveLength(12)
expect(transferRight.findAll('.el-transfer-panel__item')).toHaveLength(3)
// 右1 右2选中,点击右按钮 左14 右1
transferRight.findAll('.el-transfer-panel__item')[0].trigger('click')
transferRight.findAll('.el-transfer-panel__item')[1].trigger('click')
await nextTick()
buttonLeft.trigger('click')
await nextTick()
expect(transferLeft.findAll('.el-transfer-panel__item')).toHaveLength(14)
expect(transferRight.findAll('.el-transfer-panel__item')).toHaveLength(1)
})
})
代码
这个阶段我代码中抽离了新的组件
TanrsferPanel.vue
<template>
<div class="el-transfer-panel">
<el-checkbox-group v-model="checked" @change="checkedChangeHandler">
<el-checkbox
class="el-transfer-panel__item"
:label="item.key"
:key="item.key"
v-for="item in data"
>
</el-checkbox>
</el-checkbox-group>
</div>
</template>
<script>
import ElCheckboxGroup from '../../checkbox-group'
import ElCheckbox from '../../checkbox'
import { reactive, toRefs } from 'vue'
export default {
name: 'ElTransferPanel',
emits: ['checked-change'],
components: {
ElCheckboxGroup,
ElCheckbox
},
props: {
data: {
type: Array,
default() {
return []
}
}
},
setup(props, { emit, slots }) {
const state = reactive({
checked: []
})
const checkedChangeHandler = (val) => {
emit('checked-change', val)
}
return {
...toRefs(state),
checkedChangeHandler
}
}
}
</script>
Transfer.vue
<template>
<div class="el-transfer">
<transfer-panel
:data="sourceData"
@checked-change="onSourceCheckedChange"
data-test="transfer-panel-left"
>
</transfer-panel>
<div class="el-transfer__buttons">
<button
class="el-transfer__button"
@click="addToLeft"
data-test="transfer__button-left"
>
</button>
<button
class="el-transfer__button"
@click="addToRight"
data-test="transfer__button-right"
>
</button>
</div>
<transfer-panel
:data="targetData"
@checked-change="onTargetCheckedChange"
data-test="transfer-panel-right"
>
</transfer-panel>
</div>
</template>
<script>
import { reactive, toRefs, computed } from 'vue'
import TransferPanel from './TransferPanel.vue'
import { props } from '../../../src/components/Avatar/src/props'
export default {
name: 'ElTransfer',
emits: [
'update:modelValue'
],
components: {
TransferPanel
},
props: {
data: {
type: Array,
default() {
return []
}
},
modelValue: {
type: Array,
default() {
return []
}
}
},
setup(props, { emit, slots }) {
const {
sourceData,
targetData
} = useTransferData(props)
const {
onSourceCheckedChange,
onTargetCheckedChange,
addToLeft,
addToRight
} = useTransfercheckedChange(props, emit)
return {
sourceData,
targetData,
onSourceCheckedChange,
onTargetCheckedChange,
addToLeft,
addToRight
}
}
}
const useTransferData = (props) => {
const sourceData = computed(() => {
const { data, modelValue } = props
if (modelValue.length === 0) {
return data.slice()
}
return data.filter(item => modelValue.indexOf(item.key) === -1)
})
const targetData = computed(() => {
const { data, modelValue } = props
if (modelValue.length === 0) {
return []
}
return data.filter(item => modelValue.indexOf(item.key) > -1)
})
return {
sourceData,
targetData
}
}
const useTransfercheckedChange = (props, emit) => {
let sourceChecked = []
let targetChecked = []
const onSourceCheckedChange = (val) => {
sourceChecked = val
}
const onTargetCheckedChange = (val) => {
targetChecked = val
}
const addToLeft = () => {
if (targetChecked.length === 0) {
return
}
const { data, modelValue } = props
const currentValue = modelValue.slice()
for (let i = 0; i < targetChecked.length; i++) {
if (currentValue.indexOf(targetChecked[i]) > -1) {
currentValue.splice(i, 1)
}
}
emit('update:modelValue', currentValue)
}
const addToRight = () => {
if (sourceChecked.length === 0) {
return
}
const { data, modelValue } = props
const arr = Array.from(new Set([].concat(modelValue, sourceChecked)))
emit('update:modelValue', arr)
}
return {
onSourceCheckedChange,
onTargetCheckedChange,
addToLeft,
addToRight
}
}
</script>