feat: 初始化
This commit is contained in:
411
pages/components/baseTree/baseTree.vue
Normal file
411
pages/components/baseTree/baseTree.vue
Normal file
@ -0,0 +1,411 @@
|
||||
<template>
|
||||
<List
|
||||
class="he-tree"
|
||||
:id="nodeKey"
|
||||
ref="virtualizationList"
|
||||
:items="visibleStats"
|
||||
:enabled="virtualization"
|
||||
:class="{'he-tree-rtl': rtl}"
|
||||
>
|
||||
<template #prepend>
|
||||
<slot name="prepend" :tree="self"></slot>
|
||||
</template>
|
||||
<template #default="{ item: stat, index }">
|
||||
<TreeNode :vt-index="index" :class="[
|
||||
stat.class,
|
||||
{
|
||||
'drag-placeholder-wrapper': stat.data === placeholderData,
|
||||
'dragging-node': stat === dragNode,
|
||||
},
|
||||
]" :style="stat.style" :stat="stat" :rtl="rtl" :btt="btt" :indent="indent" :table="table" :treeLine="treeLine"
|
||||
:treeLineOffset="treeLineOffset" :processor="processor" @click="$emit('click:node', stat)"
|
||||
@open="$emit('open:node', $event)" @close="$emit('close:node', $event)" @check="$emit('check:node', $event)">
|
||||
<template #default="{ indentStyle }">
|
||||
<template v-if="stat.data === placeholderData">
|
||||
<view v-if="!table" class="drag-placeholder he-tree-drag-placeholder">
|
||||
<slot name="placeholder" :tree="self"></slot>
|
||||
</view>
|
||||
<td v-else :style="indentStyle" :colspan="placeholderColspan">
|
||||
<view class="drag-placeholder he-tree-drag-placeholder">
|
||||
<slot name="placeholder" :tree="self"></slot>
|
||||
</view>
|
||||
</td>
|
||||
</template>
|
||||
<slot v-else :node="stat.data" :stat="stat" :indentStyle="indentStyle" :tree="self">{{ stat.data[textKey] }}
|
||||
</slot>
|
||||
</template>
|
||||
</TreeNode>
|
||||
</template>
|
||||
<template #append>
|
||||
<slot name="append" :tree="self"></slot>
|
||||
</template>
|
||||
</List>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// 如果遇到滚动不流畅的情况,不用处理,因为Dev tool造成的。
|
||||
// If the scrolling is not smooth, do not deal with it, because it is caused by the Dev tool.
|
||||
import { PropType, defineComponent, reactive } from "./vue";
|
||||
import * as hp from "./helper-js";
|
||||
import List from "./list";
|
||||
import TreeNode from "./treeNode.vue";
|
||||
import { vueMakeTreeProcessor, Stat, TreeProcessor } from "./TreeProcessorVue";
|
||||
|
||||
export { walkTreeData } from "./helper-js";
|
||||
|
||||
const cpt = defineComponent({
|
||||
components: { List, TreeNode },
|
||||
props: {
|
||||
// for vue3
|
||||
modelValue: { required: true, type: Array as PropType<any[]> },
|
||||
updateBehavior: {
|
||||
type: String as PropType<"modify" | "new" | "disabled">,
|
||||
default: "modify",
|
||||
},
|
||||
processor: {
|
||||
type: Object as PropType<TreeProcessor>,
|
||||
default: () =>
|
||||
vueMakeTreeProcessor([], {
|
||||
noInitialization: true,
|
||||
}),
|
||||
},
|
||||
childrenKey: { type: String, default: "children" },
|
||||
/**
|
||||
* for default slot. 用于默认插槽
|
||||
*/
|
||||
textKey: { type: String, default: "text" },
|
||||
/**
|
||||
* node indent. 节点缩进
|
||||
*/
|
||||
indent: { type: Number, default: 20 },
|
||||
/**
|
||||
* Enable virtual list. 启用虚拟列表
|
||||
*/
|
||||
virtualization: { type: Boolean, default: false },
|
||||
/**
|
||||
* Render count for virtual list at start. 虚拟列表初始渲染数量.
|
||||
*/
|
||||
virtualizationPrerenderCount: { type: Number, default: 20 },
|
||||
/**
|
||||
* Open all nodes by default. 默认打开所有节点.
|
||||
*/
|
||||
defaultOpen: { type: Boolean, default: true },
|
||||
statHandler: { type: Function as PropType<(stat: Stat<any>) => Stat<any>> },
|
||||
/**
|
||||
* From right to left. 由右向左显示.
|
||||
*/
|
||||
rtl: { type: Boolean, default: false },
|
||||
/**
|
||||
* From bottom to top. 由下向上显示
|
||||
*/
|
||||
btt: { type: Boolean, default: false },
|
||||
/**
|
||||
* Display as table
|
||||
*/
|
||||
table: { type: Boolean, default: false },
|
||||
watermark: { type: Boolean, default: false },
|
||||
nodeKey: {
|
||||
type: [String, Function] as PropType<
|
||||
"index" | ((stat: Stat<any>, index: number) => any)
|
||||
>,
|
||||
default: 'index',
|
||||
},
|
||||
treeLine: { type: Boolean, default: false },
|
||||
treeLineOffset: { type: Number, default: 8 },
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
"click:node",
|
||||
"open:node",
|
||||
"close:node",
|
||||
"check:node",
|
||||
"beforeDragStart",
|
||||
"before-drag-start",
|
||||
"after-drop",
|
||||
"change",
|
||||
"enter",
|
||||
"leave",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
treeID: hp.randString(),
|
||||
stats: [],
|
||||
statsFlat: [],
|
||||
dragNode: null,
|
||||
dragOvering: false,
|
||||
placeholderData: {},
|
||||
placeholderColspan: 1,
|
||||
batchUpdateWaiting: false,
|
||||
self: this,
|
||||
_ignoreValueChangeOnce: false,
|
||||
} as {
|
||||
treeID: any,
|
||||
stats: Exclude<TreeProcessor["stats"], null>;
|
||||
statsFlat: Exclude<TreeProcessor["statsFlat"], null>;
|
||||
dragNode: Stat<any> | null;
|
||||
dragOvering: boolean;
|
||||
placeholderData: {};
|
||||
placeholderColspan: number;
|
||||
batchUpdateWaiting: boolean;
|
||||
self: any;
|
||||
_ignoreValueChangeOnce: boolean;
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
valueComputed() {
|
||||
return (this.modelValue) || [];
|
||||
},
|
||||
visibleStats() {
|
||||
const { statsFlat, isVisible } = this;
|
||||
let items = statsFlat;
|
||||
if (this.btt) {
|
||||
items = items.slice();
|
||||
items.reverse();
|
||||
}
|
||||
return items.filter((stat) => isVisible(stat));
|
||||
},
|
||||
rootChildren() {
|
||||
return this.stats
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
_emitValue(value: any[]) {
|
||||
// @ts-ignore
|
||||
this.$emit("update:modelValue", value);
|
||||
},
|
||||
/**
|
||||
* private method
|
||||
* @param value
|
||||
*/
|
||||
_updateValue(value: any[]) {
|
||||
if (this.updateBehavior === "disabled") {
|
||||
return false;
|
||||
}
|
||||
// if value changed, ignore change once
|
||||
if (value !== this.valueComputed) {
|
||||
this._ignoreValueChangeOnce = true;
|
||||
}
|
||||
this._emitValue(value);
|
||||
return true;
|
||||
},
|
||||
getStat: reactiveFirstArg(
|
||||
processorMethodProxy("getStat")
|
||||
) as TreeProcessor["getStat"],
|
||||
has: reactiveFirstArg(processorMethodProxy("has")) as TreeProcessor["has"],
|
||||
updateCheck: processorMethodProxy(
|
||||
"updateCheck"
|
||||
) as TreeProcessor["updateCheck"],
|
||||
getChecked: processorMethodProxy(
|
||||
"getChecked"
|
||||
) as TreeProcessor["getChecked"],
|
||||
getUnchecked: processorMethodProxy(
|
||||
"getUnchecked"
|
||||
) as TreeProcessor["getUnchecked"],
|
||||
openAll: processorMethodProxy("openAll") as TreeProcessor["openAll"],
|
||||
closeAll: processorMethodProxy("closeAll") as TreeProcessor["closeAll"],
|
||||
openNodeAndParents: processorMethodProxy("openNodeAndParents") as TreeProcessor["openNodeAndParents"],
|
||||
isVisible: processorMethodProxy("isVisible") as TreeProcessor["isVisible"],
|
||||
move: processorMethodProxyWithBatchUpdate("move") as TreeProcessor["move"],
|
||||
add: reactiveFirstArg(
|
||||
processorMethodProxyWithBatchUpdate("add")
|
||||
) as TreeProcessor["add"],
|
||||
addMulti(
|
||||
dataArr: any[],
|
||||
parent?: Stat<any> | null,
|
||||
startIndex?: number | null
|
||||
) {
|
||||
this.batchUpdate(() => {
|
||||
let index = startIndex;
|
||||
for (const data of dataArr) {
|
||||
this.add(data, parent, index);
|
||||
if (index != null) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
remove: processorMethodProxy("remove") as TreeProcessor["remove"],
|
||||
removeMulti(dataArr: any[]) {
|
||||
let cloned = [...dataArr];
|
||||
this.batchUpdate(() => {
|
||||
for (const data of cloned) {
|
||||
this.remove(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
iterateParent: processorMethodProxy(
|
||||
"iterateParent"
|
||||
) as TreeProcessor["iterateParent"],
|
||||
getSiblings: processorMethodProxy(
|
||||
"getSiblings"
|
||||
) as TreeProcessor["getSiblings"],
|
||||
getData: processorMethodProxy("getData") as hp.ReplaceReturnType<
|
||||
TreeProcessor["getData"],
|
||||
any[]
|
||||
>,
|
||||
getRootEl() {
|
||||
// @ts-ignore
|
||||
return this.$refs.vtlist.listElRef as HTMLElement;
|
||||
},
|
||||
batchUpdate(task: () => any | Promise<any>) {
|
||||
const r = this.ignoreUpdate(task);
|
||||
if (!this.batchUpdateWaiting) {
|
||||
this._updateValue(
|
||||
this.updateBehavior === "new" ? this.getData() : this.valueComputed
|
||||
);
|
||||
}
|
||||
return r;
|
||||
},
|
||||
ignoreUpdate(task: () => any | Promise<any>) {
|
||||
const old = this.batchUpdateWaiting;
|
||||
this.batchUpdateWaiting = true;
|
||||
const r = task();
|
||||
this.batchUpdateWaiting = old;
|
||||
return r;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
processor: {
|
||||
immediate: true,
|
||||
handler(processor: typeof this.processor) {
|
||||
if (processor) {
|
||||
// hook
|
||||
const getNodeDataChildren = (nodeData: any): any[] => {
|
||||
if (!nodeData) {
|
||||
return this.valueComputed;
|
||||
} else {
|
||||
const { childrenKey } = this;
|
||||
if (!nodeData[childrenKey]) {
|
||||
nodeData[childrenKey] = [];
|
||||
}
|
||||
return nodeData[childrenKey];
|
||||
}
|
||||
};
|
||||
processor["_statHandler2"] = this.statHandler
|
||||
? (stat) => {
|
||||
if (stat.data === this.placeholderData) {
|
||||
return stat;
|
||||
}
|
||||
return this.statHandler!(stat);
|
||||
}
|
||||
: null;
|
||||
processor.afterSetStat = (stat, parent, index) => {
|
||||
const { childrenKey, updateBehavior } = this;
|
||||
let value = this.valueComputed;
|
||||
if (updateBehavior === "new") {
|
||||
if (this.batchUpdateWaiting) {
|
||||
return;
|
||||
}
|
||||
value = this.getData();
|
||||
} else if (updateBehavior === "modify") {
|
||||
const siblings = getNodeDataChildren(parent?.data);
|
||||
if (siblings.includes(stat.data)) {
|
||||
// when call add -> add child -> _setPositionm ignore because the child already in parent.children
|
||||
} else {
|
||||
siblings.splice(index, 0, stat.data);
|
||||
}
|
||||
} else if (updateBehavior === "disabled") {
|
||||
}
|
||||
if (this.batchUpdateWaiting) {
|
||||
return;
|
||||
}
|
||||
this._updateValue(value);
|
||||
};
|
||||
processor.afterRemoveStat = (stat) => {
|
||||
const { childrenKey, updateBehavior } = this;
|
||||
let value = this.valueComputed;
|
||||
if (updateBehavior === "new") {
|
||||
if (this.batchUpdateWaiting) {
|
||||
return;
|
||||
}
|
||||
value = this.getData();
|
||||
} else if (updateBehavior === "modify") {
|
||||
const siblings = getNodeDataChildren(stat.parent?.data);
|
||||
hp.arrayRemove(siblings, stat.data);
|
||||
} else if (updateBehavior === "disabled") {
|
||||
}
|
||||
if (this.batchUpdateWaiting) {
|
||||
return;
|
||||
}
|
||||
this._updateValue(value);
|
||||
};
|
||||
}
|
||||
if (!processor.initialized) {
|
||||
processor.data = this.valueComputed;
|
||||
Object.assign(
|
||||
processor,
|
||||
hp.objectOnly(this, ["childrenKey", "defaultOpen"])
|
||||
);
|
||||
processor.init();
|
||||
processor.updateCheck();
|
||||
}
|
||||
this.stats = processor.stats!;
|
||||
this.statsFlat = processor.statsFlat!;
|
||||
if (processor.data !== this.valueComputed) {
|
||||
this._updateValue(processor.data);
|
||||
}
|
||||
},
|
||||
},
|
||||
valueComputed: {
|
||||
handler(value) {
|
||||
// isDragging triggered in Vue2 because its array is not same with Vue3
|
||||
const isDragging = this.dragOvering || this.dragNode
|
||||
if (isDragging || this._ignoreValueChangeOnce) {
|
||||
this._ignoreValueChangeOnce = false;
|
||||
} else {
|
||||
const { processor } = this;
|
||||
processor.data = value;
|
||||
processor.init();
|
||||
this.stats = processor.stats!;
|
||||
this.statsFlat = processor.statsFlat!;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
created() { },
|
||||
mounted() {
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
export default cpt;
|
||||
export type BaseTreeType = InstanceType<typeof cpt>;
|
||||
|
||||
function processorMethodProxy(name: string) {
|
||||
return function (...args) {
|
||||
// @ts-ignore
|
||||
return this.processor[name](...args);
|
||||
};
|
||||
}
|
||||
function processorMethodProxyWithBatchUpdate(name: string) {
|
||||
return function (...args) {
|
||||
// @ts-ignore
|
||||
return this.batchUpdate(() => {
|
||||
// @ts-ignore
|
||||
return this.processor[name](...args);
|
||||
});
|
||||
};
|
||||
}
|
||||
function reactiveFirstArg(func: any) {
|
||||
return function (arg1, ...args) {
|
||||
if (arg1) {
|
||||
arg1 = reactive(arg1);
|
||||
}
|
||||
// @ts-ignore
|
||||
return func.call(this, arg1, ...args);
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.he-tree--rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.he-tree-drag-placeholder {
|
||||
background: #ddf2f9;
|
||||
border: 1px dashed #00d9ff;
|
||||
height: 22px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user