song-ui-u
Version:
vue3 + js的PC前端组件库
311 lines (275 loc) • 10.7 kB
JavaScript
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