之前用select 和option标签写了个自定义穿梭框,但发现问题多多
https://www.jianshu.com/p/949b50e2bf11
首先 option标签内是不支持增加标签的,基本上你只能使用文本,而且很多样式都不支持。比如业务上希望option内的内容能自动换行,然而碰到火狐浏览器,option标签内自动换行的样式就失效了。
这样的穿梭框不是一个完美的穿梭框。于是打算用div标签重写一个。
构思是保持select和option的框选,多选,全选功能,选择功能用鼠标事件实现。
<template>
<div class="checkbox-list">
<input
type="input"
style="height: 0; position: absolute; z-index: -1"
ref="inputRef"
@keyup="(ev) => onKeyPress(ev)"
/>
<div
:class="['checkbox-list__item', { 'item-checked': checkKeys.includes(item.value) }]"
v-for="(item, idx) in checkOptions"
:key="idx"
@mousedown="(ev) => onMouseDown(ev, item, idx)"
@mouseenter="(ev) => onMouseEnter(ev, item, idx)"
@mouseup="(ev) => onMouseUp(ev, item, idx)"
>
<slot name="dot" v-bind="item"></slot>
<SlotRender :fun="item.render" :data="item.label" />
</div>
</div>
</template>
<script setup lang="ts">
import { computed, watch, useSlots, ref } from 'vue';
import { useDragSelect } from './hooks/useDragSelect';
// import { useDebounceFn } from '@vueuse/core';
const emit = defineEmits(['change']);
const slots = useSlots();
const inputRef = ref();
const SlotRender = {
props: {
fun: Function || undefined,
data: Object || undefined,
},
setup(props2) {
return () => {
return props2.fun ? props2.fun(props2.data || {}) : '';
};
},
};
const props = defineProps({
items: {
type: Array,
default() {
return [];
},
},
dot: {
type: String,
},
});
const checkKeys = ref([]);
watch(
() => props.items,
() => {
checkKeys.value = [];
},
{
immediate: true,
},
);
const checkOptions = computed(() => {
let arr;
if (slots.render) {
arr = props.items.map((r) => {
const item = {
...r,
label: r,
render: slots.render,
};
return item;
});
} else {
arr = props.items;
}
return arr;
});
const { onMouseDown, onMouseEnter, onMouseUp } = useDragSelect({
checkOptions,
checkKeys,
emit,
inputRef,
});
const onKeyPress = (ev) => {
ev.preventDefault();
ev.stopPropagation();
if (ev.ctrlKey && ev.code === 'KeyA') {
checkKeys.value = checkOptions.value.map((r) => r.value);
emit('change', checkKeys.value);
}
};
const checkAll = (val) => {
checkKeys.value = val
? props.items.map((r) => {
return r.value;
})
: [];
};
const resetSelected = () => {
checkKeys.value = [];
emit('change', []);
};
defineExpose({
checkAll,
resetSelected,
});
</script>
<style lang="less" scoped>
.checkbox-list {
overflow: auto;
&__item {
width: 100%;
padding: 0 5px;
cursor: default;
&.item-checked {
background-color: rgb(0, 132, 255);
color: #fff;
}
}
}
</style>
请留意,列表内增加了一个不可见的input 元素。这是用于全选功能的一个辅助标签。因为当按ctrl + A 时,必须激活一个表单元素,div元素focus是没有效果了。表单元素激活后,键盘事件才生效。
与拖动选择相关的一些方法都封装在另一个文件useDragSelect里:
import _ from 'lodash';
// 拖拽选择实现
export function useDragSelect(options) {
const { emit, checkKeys, checkOptions, inputRef } = options;
let dragStart = false;
let dragStartIndex = -1,
dragEndIndex = -1,
isSetChecked = false,
preCheckKeys: any[] = [];
const onMouseDown = (ev, _item, index) => {
ev.preventDefault();
inputRef.value?.focus();
if (!ev.ctrlKey) {
checkKeys.value = [];
}
dragStartIndex = index;
dragStart = true;
preCheckKeys = [...checkKeys.value];
if (!checkKeys.value.includes(_item.value)) {
isSetChecked = true;
} else {
isSetChecked = false;
}
const currValue = checkOptions.value[index].value;
if (isSetChecked) {
checkKeys.value = _.uniq([...preCheckKeys, currValue]);
} else {
const newCheck: any[] = [];
preCheckKeys.forEach((key) => {
if (currValue !== key) {
newCheck.push(key);
}
});
checkKeys.value = _.uniq([...newCheck]);
}
};
const onMouseEnter = (ev, _item, index) => {
dragEndIndex = index;
ev.preventDefault();
if (dragStart) {
const start = Math.min(dragStartIndex, dragEndIndex);
const end = Math.max(dragStartIndex, dragEndIndex);
let i = start;
const currSelected: any[] = [];
while (i <= end) {
const _item = checkOptions.value[i];
currSelected.push(_item.value);
i++;
}
if (isSetChecked) {
checkKeys.value = _.uniq([...preCheckKeys, ...currSelected]);
} else {
const newCheck: any[] = [];
preCheckKeys.forEach((key) => {
if (!currSelected.includes(key)) {
newCheck.push(key);
}
});
checkKeys.value = _.uniq([...newCheck]);
}
}
emit('change', checkKeys.value);
};
const onMouseUp = (_ev, _item, index) => {
_ev.preventDefault();
_ev.stopPropagation();
dragStart = false;
dragEndIndex = index;
const currValue = checkOptions.value[index].value;
if (dragEndIndex === dragStartIndex) {
if (isSetChecked) {
checkKeys.value = _.uniq([...preCheckKeys, currValue]);
} else {
const newCheck: any[] = [];
preCheckKeys.forEach((key) => {
if (currValue !== key) {
newCheck.push(key);
}
});
checkKeys.value = _.uniq([...newCheck]);
}
}
emit('change', checkKeys.value);
};
return {
onMouseDown,
onMouseEnter,
onMouseUp,
};
}