UNPKG

song-ui-u

Version:

vue3 + js的PC前端组件库

311 lines (275 loc) 10.7 kB
import { computed, ref, openBlock, createElementBlock, normalizeClass, createElementVNode, normalizeStyle, Fragment, renderList, withModifiers, createBlock, withCtx, createVNode, createCommentVNode, createTextVNode, toDisplayString } from 'vue'; import { useNamespace } from '../../../hook/use-namespace/index.mjs'; import { PlusSquare, MinusSquare } from 'song-ui-pro-icon'; import '../../../hook/use-zindex/index.mjs'; import '../../button/index.mjs'; import '../../buttonGroup/index.mjs'; import { XIcon } from '../../icon/index.mjs'; import '../../input/index.mjs'; import '../../textarea/index.mjs'; import '../../row/index.mjs'; import '../../col/index.mjs'; import '../../container/index.mjs'; import { XCheckbox } from '../../checkbox/index.mjs'; import '../../switch/index.mjs'; import '../../form/index.mjs'; import '../../message/index.mjs'; import '../../mask/src/mask.mjs'; import '../../modal/index.mjs'; import '../../messageBox/index.mjs'; import '../../drawer/index.mjs'; import '../../badge/index.mjs'; import '../../space/index.mjs'; import '../../image/index.mjs'; import { XRadio } from '../../radio/index.mjs'; import '../../divider/index.mjs'; import '../../chat/index.mjs'; import '../../progress/index.mjs'; import '../../upload/index.mjs'; import '../index.mjs'; import '../../table/index.mjs'; import '../../tabs/index.mjs'; import '../../menu/index.mjs'; import '../../steps/index.mjs'; import '../../header/index.mjs'; import '../../breadcrumble/index.mjs'; import '../../datePicker/index.mjs'; import '../../tooltip/index.mjs'; import '../../popover/index.mjs'; import '../../timePicker/index.mjs'; import '../../select/index.mjs'; import '../../collapse/index.mjs'; import '../../card/index.mjs'; import '../../timeline/index.mjs'; import '../../tag/index.mjs'; import '../../result/index.mjs'; import '../../sender/index.mjs'; import _export_sfc from '../../../_virtual/_plugin-vue_export-helper.mjs'; const itemHeight = 30; const visibleCount = 10; // 扁平化树结构 const _sfc_main = /*#__PURE__*/Object.assign({ name: "x-vtree", }, { __name: 'index', props: { data: { type: Array, required: true, default: () => [], }, selectionMode: { type: String, default: "single", validator: (value) => ["single", "multiple"].includes(value), }, // label label: { type: String, default: "label", }, // value // value: { // type: String, // default: "id", // }, }, emits: ["change", "expand", "collapse"], setup(__props, { expose: __expose, emit: __emit }) { __expose(); const ns = useNamespace("vtree"); const props = __props; const emits = __emit; const islabel = computed(() => props.label); // const isvalue = computed(() => props.value); const container = ref(null); const scrollTop = ref(0); const flatTree = computed(() => { const result = []; function traverse(node, depth = 0) { if (!node) return; // 确保节点有 expanded 属性 if (node.expanded === undefined) { node.expanded = false; } node.depth = depth; result.push(node); if (node.children && node.expanded) { node.children.forEach((child) => traverse(child, depth + 1)); } } props.data.forEach((node) => traverse(node)); return result; }); const totalHeight = computed(() => flatTree.value.length * itemHeight); const visibleNodes = computed(() => { // 根据滚动位置计算起始索引: // scrollTop.value 是当前滚动的像素值 // itemHeight 是每个节点的高度(30px) // 除法结果向下取整,得到应该从第几个节点开始显示 const start = Math.floor(scrollTop.value / itemHeight); // 计算结束索引: // start + visibleCount 是理论上需要显示的节点数量 // 使用 Math.min 确保不会超出实际节点总数 const end = Math.min(start + visibleCount, flatTree.value.length); // 从扁平化的树结构中截取需要显示的部分 return flatTree.value.slice(start, end); }); const getNodeStyle = (index) => ({ position: "absolute", top: `${scrollTop.value + index * itemHeight}px`, }); const handleScroll = () => { if (container.value) { scrollTop.value = container.value.scrollTop; } }; const selectedNodes = ref([]); // 对于多选 const selectedNode = ref(null); // 对于单选 const toggleNode = (node) => { if (props.selectionMode === "single") { selectedNode.value = node.id; // 选中 emits("change", selectedNode.value); } else if (props.selectionMode === "multiple") { const index = selectedNodes.value.indexOf(node.id); if (index > -1) { selectedNodes.value.splice(index, 1); // 取消选中 if (node.children && node.expanded) { deselectChildren(node.children); // 取消选中子节点 } } else { selectedNodes.value.push(node.id); // 选中 if (node.children && node.expanded) { selectChildren(node.children); // 选中子节点 } } emits("change", selectedNodes.value); } }; const selectChildren = (children) => { children.forEach((child) => { if (!selectedNodes.value.includes(child.id)) { selectedNodes.value.push(child.id); } if (child.children && child.expanded) { selectChildren(child.children); } }); }; const deselectChildren = (children) => { children.forEach((child) => { const index = selectedNodes.value.indexOf(child.id); if (index > -1) { selectedNodes.value.splice(index, 1); } if (child.children && child.expanded) { deselectChildren(child.children); } }); }; const isSelected = (node) => { if (props.selectionMode === "single") { return selectedNode.value === node.id; } else if (props.selectionMode === "multiple") { return selectedNodes.value.includes(node.id); } return false; }; const toggleExpand = (node) => { if (!node.children) return; if (node.expanded) { emits("collapse", node); } else { emits("expand", node); } node.expanded = !node.expanded; }; const __returned__ = { ns, props, emits, islabel, container, scrollTop, itemHeight, visibleCount, flatTree, totalHeight, visibleNodes, getNodeStyle, handleScroll, selectedNodes, selectedNode, toggleNode, selectChildren, deselectChildren, isSelected, toggleExpand, ref, computed, get useNamespace() { return useNamespace }, get XIcon() { return XIcon }, get XCheckbox() { return XCheckbox }, get XRadio() { return XRadio }, get PlusSquare() { return PlusSquare }, get MinusSquare() { return MinusSquare } }; Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true }); return __returned__ } }); const _hoisted_1 = ["onClick"]; const _hoisted_2 = ["onClick"]; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { return (openBlock(), createElementBlock("div", { class: normalizeClass($setup.ns.b()), onScroll: $setup.handleScroll, ref: "container" }, [ createElementVNode("div", { style: normalizeStyle({ height: $setup.totalHeight + 'px' }), class: normalizeClass($setup.ns.e('spacer')) }, null, 6 /* CLASS, STYLE */), (openBlock(true), createElementBlock(Fragment, null, renderList($setup.visibleNodes, (node, index) => { return (openBlock(), createElementBlock("div", { key: node.id, style: normalizeStyle($setup.getNodeStyle(index)), class: normalizeClass([$setup.ns.e('node'), $setup.ns.is('selected', $setup.isSelected(node))]), onClick: withModifiers($event => ($setup.toggleNode(node)), ["stop"]) }, [ createElementVNode("span", { class: normalizeClass($setup.ns.e('node-inner')), style: normalizeStyle({ paddingLeft: `${node.depth * 20}px` }) }, [ (node.children) ? (openBlock(), createElementBlock("span", { key: 0, class: normalizeClass($setup.ns.e('expand-icon')), onClick: withModifiers($event => ($setup.toggleExpand(node)), ["stop"]) }, [ (node.expanded) ? (openBlock(), createBlock($setup["XIcon"], { key: 0, color: "#abb1bf" }, { default: withCtx(() => [ createVNode($setup["MinusSquare"]) ]), _: 1 /* STABLE */ })) : (openBlock(), createBlock($setup["XIcon"], { key: 1, color: "#abb1bf" }, { default: withCtx(() => [ createVNode($setup["PlusSquare"]) ]), _: 1 /* STABLE */ })) ], 10 /* CLASS, PROPS */, _hoisted_2)) : createCommentVNode("v-if", true), ($setup.props.selectionMode === 'multiple') ? (openBlock(), createBlock($setup["XCheckbox"], { key: 1, size: "small", checked: $setup.isSelected(node), onClick: withModifiers($event => ($setup.toggleNode(node)), ["stop"]) }, { default: withCtx(() => [ createTextVNode(toDisplayString(node[$setup.islabel]), 1 /* TEXT */) ]), _: 2 /* DYNAMIC */ }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["checked", "onClick"])) : (openBlock(), createBlock($setup["XRadio"], { key: 2, type: "radio", size: "small", name: $setup.props.selectionMode, value: node.id, checked: $setup.isSelected(node), onClick: withModifiers($event => ($setup.toggleNode(node)), ["stop"]) }, { default: withCtx(() => [ createTextVNode(toDisplayString(node[$setup.islabel]), 1 /* TEXT */) ]), _: 2 /* DYNAMIC */ }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["name", "value", "checked", "onClick"])) ], 6 /* CLASS, STYLE */) ], 14 /* CLASS, STYLE, PROPS */, _hoisted_1)) }), 128 /* KEYED_FRAGMENT */)) ], 34 /* CLASS, NEED_HYDRATION */)) } var vtree = /*#__PURE__*/_export_sfc(_sfc_main, [['render',_sfc_render],['__file',"E:\\code\\my-code\\song-ui-ultra\\packages\\components\\vTree\\src\\index.vue"]]); export { vtree as default }; //# sourceMappingURL=index.vue.mjs.map