@ithinkdt/naive
Version:
iThinkDT Naive UI
246 lines (228 loc) • 8.83 kB
JSX
import { defineComponent, reactive, watch } from 'vue'
import { NIcon, NButton, NTree, NScrollbar } from 'ithinkdt-ui'
import { walkTree } from '@ithinkdt/common'
import { nanoid } from 'nanoid'
import { IPlus, IPlus2, IClose } from './assets'
export const DtTreeInput = defineComponent({
name: 'DtTreeInput',
props: {
value: {
type: Array,
default: () => [],
},
onCreate: {
type: Function,
default: () => ({}),
},
readonly: {
type: Boolean,
default: false,
},
renderItem: {
type: Function,
required: true,
},
creatable: {
type: Function,
default: () => true,
},
removable: {
type: Function,
default: () => true,
},
},
emits: {
'update-value': () => true,
'update:value': () => true,
},
setup(props, { emit }) {
const keyField = '__id'
const emitValue = (v) => {
emit('update:value', v)
emit('update-value', v)
}
const onCreate = () => {
const obj = props.onCreate()
Object.defineProperty(obj, keyField, {
value: nanoid(),
enumerable: false,
configurable: false,
writable: true,
})
return obj
}
const renderSuffix = ({ option }) => {
return (
<div
onClick={(e) => {
e.stopImmediatePropagation()
}}
style="display: flex; gap: 6px; align-items: center; margin: 0 8px"
>
<NButton
key="create"
type="primary"
text
style="font-size: 22px"
onClick={() => {
walkTree(props.value, (it, i, p) => {
if (it[keyField] === option[keyField]) {
if (p) {
p.children.splice(i + 1, 0, onCreate())
emitValue([...(props.value ?? [])])
} else {
const v = [...props.value]
v.splice(i + 1, 0, onCreate())
emitValue(v)
}
return false
}
})
}}
>
<NIcon>
<IPlus />
</NIcon>
</NButton>
<NButton
key="create-child"
type="primary"
text
style="font-size: 20px"
disabled={!props.creatable(option)}
onClick={() => {
option.children ||= []
option.children.push(onCreate())
emitValue([...(props.value ?? [])])
}}
>
<NIcon>
<IPlus2 />
</NIcon>
</NButton>
<NButton
key="delete"
type="error"
text
style="font-size: 20px"
disabled={!props.removable(option)}
onClick={() => {
walkTree(props.value, (it, i, p) => {
if (it[keyField] === option[keyField]) {
if (p) {
p.children.splice(i, 1)
if (p.children.length === 0) {
delete p.children
}
emitValue([...(props.value ?? [])])
} else {
const v = [...props.value]
v.splice(i, 1)
emitValue(v)
}
return false
}
})
}}
>
<NIcon>
<IClose />
</NIcon>
</NButton>
</div>
)
}
watch(
() => props.value,
(v) => {
walkTree(v, (it) => {
if (it && !it[keyField]) {
Object.defineProperty(it, keyField, {
value: nanoid(),
enumerable: false,
configurable: false,
writable: true,
})
}
})
},
{ immediate: true, deep: false },
)
const findSiblingsAndIndex = (node, nodes) => {
if (!nodes) return []
for (let i = 0; i < nodes.length; ++i) {
const siblingNode = nodes[i]
if (siblingNode.key === node.key) return [nodes, i]
const [siblings, index] = findSiblingsAndIndex(node, siblingNode.children)
if (siblings && index !== undefined) return [siblings, index]
}
return []
}
const handleDrop = ({ node, dragNode, dropPosition }) => {
const [dragNodeSiblings, dragNodeIndex] = findSiblingsAndIndex(dragNode, props.value)
if (dragNodeSiblings === undefined || dragNodeIndex === undefined) return
dragNodeSiblings.splice(dragNodeIndex, 1)
switch (dropPosition) {
case 'inside': {
if (node.children) {
node.children.unshift(dragNode)
} else {
node.children = [dragNode]
}
break
}
case 'before': {
const [nodeSiblings, nodeIndex] = findSiblingsAndIndex(node, props.value)
if (nodeSiblings === undefined || nodeIndex === undefined) return
nodeSiblings.splice(nodeIndex, 0, dragNode)
break
}
case 'after': {
const [nodeSiblings, nodeIndex] = findSiblingsAndIndex(node, props.value)
if (nodeSiblings === undefined || nodeIndex === undefined) return
nodeSiblings.splice(nodeIndex + 1, 0, dragNode)
break
}
// No default
}
emitValue([...props.value])
}
return () => {
const tree = props.value?.length ? (
<NTree
data={props.value}
keyField={keyField}
draggable={!props.readonly}
blockLine
renderLabel={({ option }) => props.renderItem({ value: option, readonly: props.readonly })}
renderSuffix={props.readonly ? undefined : renderSuffix}
defaultExpandAll
onDrop={handleDrop}
></NTree>
) : undefined
if (props.readonly) return tree
return (
<div style="display: flex; flex-direction: column">
<NButton
dashed
type="primary"
onClick={() => {
emitValue(reactive([...(props.value || []), onCreate()]))
}}
style="width: 160px; margin: 0 0 16px 30px"
>
{{
icon: () => (
<NIcon size={16}>
<IPlus />
</NIcon>
),
default: () => '添加',
}}
</NButton>
<NScrollbar style="flex: 1 1 0%">{tree}</NScrollbar>
</div>
)
}
},
})