chat-pc/src/components/x-naive-ui/x-n-data-table/index.vue

216 lines
4.6 KiB
Vue

<script setup>
import {
h,
useSlots,
computed,
shallowRef,
onMounted,
onUnmounted,
nextTick,
markRaw,
watch,
} from "vue";
import Sortable from "sortablejs";
import { debounce } from "lodash-es";
import { NDataTable } from "naive-ui";
// Props 定义
const props = defineProps({
columns: {
type: Array,
default: () => [],
required: true,
},
data: {
type: Array,
default: () => [],
required: true,
},
align: {
type: String,
default: "center",
validator: (value) => ["left", "center", "right"].includes(value),
},
});
/**
* 拖拽功能相关逻辑
*/
const useDraggable = (props, emit) => {
const dragConfig = {
handleId: "drag-handle",
handleStyle: { cursor: "move", padding: "0 4px" },
};
const dragColumn = computed(() =>
props.columns?.find((col) => col.type === "drag")
);
const sortable = shallowRef();
const nDataTableRef = shallowRef();
const initSortable = () => {
if (!dragColumn.value) return;
const tbody = nDataTableRef.value?.$el?.querySelector?.(
".n-data-table-tbody"
);
if (!tbody) return;
sortable.value = markRaw(
new Sortable(tbody, {
animation: 150,
handle: dragColumn.value.handle ? `.${dragConfig.handleId}` : undefined,
onEnd: ({ oldIndex, newIndex }) => {
if (oldIndex === newIndex) return;
const newData = [...props.data];
const [removed] = newData.splice(oldIndex, 1);
newData.splice(newIndex, 0, removed);
emit("update:data", newData);
dragColumn.value?.onDragEnd?.({
oldIndex,
newIndex,
data: newData,
row: removed,
});
},
})
);
};
const debouncedInitSortable = debounce(initSortable, 200);
onMounted(() => {
nextTick(initSortable);
});
onUnmounted(() => {
sortable.value?.destroy();
});
watch(
() => props.data,
() => nextTick(debouncedInitSortable)
);
return {
dragConfig,
dragColumn,
nDataTableRef,
};
};
/**
* 列渲染相关逻辑
*/
const useColumns = (props, slots, dragConfig) => {
// 创建标题
const createTitle = (column, slotKey) => {
const titleSlotKey = `${slotKey}_title`;
if (column.titleRender) {
return column.titleRender;
}
if (slots[titleSlotKey]) {
return () => slots[titleSlotKey]({ column });
}
return column.title;
};
// 创建展开渲染器
const createExpandRenderer = () => {
if (!slots["templateExpand"]) return null;
return (row, index) => h(slots["templateExpand"], { row, index });
};
// 创建拖拽列渲染器
const createDragColumnRenderer = (column, slotKey) => {
return (row, index) =>
h(
"div",
{
class: [dragConfig.handleId, "drag-handle-wrapper"],
style: dragConfig.handleStyle,
onClick: column.handle ? (e) => e.stopPropagation() : undefined,
},
slots[slotKey] ? slots[slotKey]({ row, index, column }) : "⋮⋮"
);
};
// 创建普通列渲染器
const createDefaultColumnRenderer = (column, slotKey) => {
if (slots[slotKey]) {
return (row, index) => slots[slotKey]({ row, index, column });
}
if (column.render) {
return column.render;
}
return (row) => row[column.key];
};
// 创建列配置
const createColumnRender = (column) => {
const slotKey = column.key;
const baseColumn = {
...column,
align: props.align,
title: createTitle(column, slotKey),
renderExpand: createExpandRenderer(),
};
if (column.type === "drag") {
return {
...baseColumn,
render: createDragColumnRenderer(column, slotKey),
};
}
return {
...baseColumn,
render: createDefaultColumnRenderer(column, slotKey),
};
};
return computed(() => props.columns?.map(createColumnRender) || []);
};
// 事件定义
const emit = defineEmits(["update:data"]);
const slots = useSlots();
// 组合功能
const { dragConfig, dragColumn, nDataTableRef } = useDraggable(props, emit);
const computedColumns = useColumns(props, slots, dragConfig);
</script>
<template>
<n-data-table
ref="nDataTableRef"
:class="[dragColumn?.handle ? 'handle-only-drag' : 'full-row-drag']"
remote
v-bind="{ ...$attrs, ...$props, columns: computedColumns }"
/>
</template>
<style scoped>
.drag-handle-wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.handle-only-drag tbody tr {
cursor: default;
}
.full-row-drag tbody tr {
cursor: move;
}
</style>