从零搭建Vue3.0组件库之Transfer组件
一.定义组件属性
export type Key = string | number
export type DataItem = {
key: Key
label: string
disabled: boolean
}
export type Props = { // 别名
key: string, // key => id;
label: string, // label => desc
disabled: string // disabled => dis
}
export interface TransferProps { // 穿梭框需要的数据
data: DataItem[], // 默认类型
modelValue: Key[], // 当前选中的是
props: Props // 可改名
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
二.定义组件结构
左右panel和中间button结构
<template>
<div class="z-transfer">
<!-- 左边穿梭框 -->
<TransferPanel></TransferPanel>
<div class="z-transfer__buttons">
<z-button type="primary" icon="z-icon-arrow-left-bold"> </z-button>
<z-button type="primary" icon="z-icon-arrow-right-bold"> </z-button>
</div>
<!-- 左边穿梭框 -->
<TransferPanel></TransferPanel>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import TransferPanel from "./transfer-panel";
import ZButton from "@z-ui/button";
export default defineComponent({
name: "ZTransfer",
components: {
TransferPanel,
ZButton,
},
props: {
data: {
type: Array as PropType<DataItem[]>,
default: () => [],
},
modelValue: {
type: Array as PropType<Key[]>,
default: () => [],
},
props: {
type: Object as PropType<Props>,
default: () => ({
label: "label",
key: "key",
disabled: "disabled",
}),
},
},
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
panel面板主要有:面板头部和面板体
<template>
<div class="z-transfer-panel">
<p class="z-transfer-panel__header">
列表
</p>
<div class="z-transfer-panel__body">
----
</div>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
三.实现穿梭功能
1.左右面板数据进行拆分
<!-- 左边穿梭框 -->
<TransferPanel :data="sourceData" :props="props"></TransferPanel>
<!-- 左边穿梭框 -->
<TransferPanel :data="targetData" :props="props"></TransferPanel>
1
2
3
4
2
3
4
setup(props) {
// 1.计算 左右数据
const { propsKey, sourceData, targetData } = useComputedData(props);
return {
sourceData,
targetData,
};
},
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
根据所有数据和key,进行数据的筛查 useComputedData.ts
import { computed } from "@vue/runtime-core";
import { TransferProps } from "./transfer.type";
export const useComputedData = (props: TransferProps) => {
const propsKey = computed(() => props.props.key);
const dataObj = computed(() => {
return props.data.reduce((memo, cur) => {
memo[cur[propsKey.value]] = cur;
return memo
}, {}); // 根据key 映射原来的对象
});
// 通过key 进行数据筛选
const sourceData = computed(() => {
return props.data.filter(item => !props.modelValue.includes(item[propsKey.value]))
});
// 目标数据
const targetData = computed(() => {
return props.modelValue.reduce((arr, cur) => {
const val = dataObj.value[cur]; // 根据key 映射值,存放到数组中
if (val) {
arr.push(val)
}
return arr
}, []);
});
return {
propsKey,
sourceData,
targetData
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2.面板渲染内容
<template>
<div class="z-transfer-panel">
<p class="z-transfer-panel__header">
<z-checkbox></z-checkbox>
</p>
<div class="z-transfer-panel__body">
<z-checkbox-group class="z-transfer-panel__list">
<z-checkbox
class="z-transfer-panel__item"
v-for="item in data"
:key="item[keyProp]"
:label="item[keyProp]"
:disabled="item[disabledProp]"
> {{item[labelProp]}}
</z-checkbox>
</z-checkbox-group>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import ZCheckbox from "@z-ui/checkbox";
import ZCheckboxGroup from "@z-ui/checkbox-group";
import {useCheck} from './useCheck'
export default defineComponent({
components: {
ZCheckbox,
ZCheckboxGroup,
},
props: {
data: {
type: Array,
default: () => [],
},
props:{
type: Object as PropType<Props>
}
},
setup(props) {
const { labelProp, keyProp, disabledProp } = useCheck(props);
return {
labelProp,
keyProp,
disabledProp
}
},
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
获取数据信息
import { computed } from "@vue/runtime-core";
export interface TransferPanelProps {
data: any[]
props: Props
}
export const useCheck = (props: TransferPanelProps) => {
const labelProp = computed(() => props.props.label);
const keyProp = computed(() => props.props.key);
const disabledProp = computed(() => props.props.disabled);
return {
labelProp,
keyProp,
disabledProp
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
3.获取当前选中的值
<z-checkbox v-model="allChecked" @change="handleAllCheckedChange"></z-checkbox>
1
const panelState = reactive({
checked: [], // 选中的值
allChecked: false, // 是否全选
});
const updateAllChecked = () => {
const checkableDataKeys = props.data.map(item => item[keyProp.value]);
panelState.allChecked = checkableDataKeys.length > 0 && checkableDataKeys.every(item => panelState.checked.includes(item))
}
watch(() => panelState.checked, (val) => {
updateAllChecked(); // 更新全选状态
emit('checked-change',val)
});
const handleAllCheckedChange = (value: Key[]) => { // 更新checked
panelState.checked = value ? props.data.map(item => item[keyProp.value]) : []
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
每次选中的时候,将选中的结果传递给父组件
<TransferPanel
:data="sourceData"
:props="props"
@checked-change="onSourceCheckedChange"
></TransferPanel>
<TransferPanel
:data="targetData"
:props="props"
@checked-change="onTargetCheckedChange"
></TransferPanel>
const checkedState = reactive({
leftChecked: [],
rightChecked: [],
});
const onSourceCheckedChange = (val) => {
checkedState.leftChecked = val;
};
const onTargetCheckedChange = (val) => {
checkedState.rightChecked = val;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
将左右选中的内容分别存储到对应的数组中
4.穿梭实现
const addToLeft = () => { // 减少modelValue中的值
const currentValue = props.modelValue.slice();
checkedState.rightChecked.forEach((item) => {
const index = currentValue.indexOf(item);
if (index > -1) {
currentValue.splice(index, 1);
}
});
emit("update:modelValue", currentValue);
};
const addToRight = () => {
let currentValue = props.modelValue.slice(); // 给modelValue添加值
const itemsToBeMoved = props.data // 在所有数据中晒出选中的
.filter((item) =>
checkedState.leftChecked.includes(item[propsKey.value])
)
.map((item) => item[propsKey.value]);
currentValue = currentValue.concat(itemsToBeMoved);
console.log(currentValue)
emit("update:modelValue", currentValue);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
四.修复Bug
1.穿梭后清空选中列表
watch(() => props.data, () => {
const checked = [];
panelState.checked = checked;
});
1
2
3
4
2
3
4
2.被禁用元素不支持穿梭
const checkableData = computed(() => {// 过滤禁用的数据
return props.data.filter(item => !item[disabledProp.value])
});
const updateAllChecked = () => { // 更新checkall
const checkableDataKeys = checkableData.value.map(item => item[keyProp.value]);
panelState.allChecked = checkableDataKeys.length > 0 && checkableDataKeys.every(item => panelState.checked.includes(item))
}
const handleAllCheckedChange = (value: Key[]) => { // 更新checked
panelState.checked = value ? checkableData.value.map(item => item[keyProp.value]) : []
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10