在上一节《移除节点》基础上继续迭代JuanTree
的功能,我们将实现节点重命名的功能。
实现效果:
可以对现有节点进行编辑,点回车或失去焦点完成编辑,如果输入为空,会恢复为原来的值。同时支持对新增的节点自动启用编辑功能,以方便用户操作。
关于节点名称重复检查
注意,这里对于节点编辑只做了非空限制,节点名称重复前端不处理哦,对于节点的变更在提交后台时可以再进行统一的校验或者交给后台校验,目前的实现是这样的。因为考虑到在懒加载的情况下启用节点的编辑功能,检查标签名是否重复需要调用远程接口判断,因此我们干脆取消这一独立的检查功能,简化开发的难度,后续可以再完善噢~
用法示例
只需要调用插槽返回对象的startEdit()
方法即可。
具体实现
属性和方法
新增一个表示节点编辑中的属性:
// 扩展的扁平化节点
export interface IFlatTreeNode extends ITreeNode {
...
editing?: boolean // 节点处于编辑中
}
新增触发节点编辑的用户API操作方法:
// 用户可进行节点操作的接口,该接口是提供给用户的。
export interface ITreeNodeOperation {
...
startEdit: () => void // 触发节点编辑的方法
}
tsx组件完善
渲染树节点名称的地方调整下:
{/* 非编辑状态下渲染标签名 */}
{!node.editing ? (
node[labelName as 'label']
) : (
/* 编辑状态下渲染一个输入框 */
<input
class='box-border h-[21px] border border-solid'
type='text'
v-model={editLabel.value}
ref={input}
onBlur={() => {
inputBlur(node as never)
}}
onKeydown={($event) => {
inputKeydown($event, node as never)
}}
/>
)}
这里会对输入框绑定一个响应式的变量:
const editLabel = ref('') // 输入框中编辑的节点标签名
相关的事件处理:
// 结束节点标签名编辑的函数
const finishEdit = (node: IFlatTreeNode) => {
// 如果输入不为空,则绑定节点的名称
// 同时更新关联的原始节点的name,
if (editLabel.value !== '') {
const name = labelName as 'label'
node[name] = node.originalNode[name] = editLabel.value
}
// 结束编辑
node.editing = false
}
// 当输入框失去焦点时触发结束编辑
const inputBlur = (node: IFlatTreeNode) => {
finishEdit(node)
}
// 当在编辑输入框时按下回车键也结束编辑
const inputKeydown = (evt: KeyboardEvent, node: IFlatTreeNode) => {
if (evt.key === 'Enter') finishEdit(node)
}
提供给用户的API实现:
// 编辑节点名称的输入框元素的引用
const input = ref<HTMLInputElement>()
// 返回节点操作方法的函数
const nodeOperation = (node: IFlatTreeNode): ITreeNodeOperation => {
// 注意,这里不应该直接给用户提供node,而是要包成对外公开的ITreeNodeOperation,遵循迪米特法则!!
return {
// 给用户操作的启用节点编辑方法
startEdit() {
// 绑定节点的name值到输入框
editLabel.value = node[labelName as 'label']
// 启用编辑
node.editing = true
// 在input完成渲染后自动获得焦点
nextTick(() => {
input.value!.focus()
})
},
...
}
}
支持新增时启用编辑
将原先的启用编辑的逻辑提取为一个内部的函数:
const _startEdit = (node: IFlatTreeNode) => {
// 绑定节点的name值到输入框
editLabel.value = node[labelName as 'label']
// 启用编辑
node.editing = true
// 在input完成渲染后自动获得焦点
nextTick(() => {
input.value!.focus()
})
}
在以下地方进行调用:
// 返回节点操作方法的函数
const nodeOperation = (node: IFlatTreeNode): ITreeNodeOperation => {
return {
// 给用户操作的启用节点编辑方法
startEdit() {
_startEdit(node)
},
append(newNode: ILeafNode) {
// 返回新的扁平化节点
const newFlatNode = node.append(newNode, originalFlatData)
_startEdit(newFlatNode)
},
...
}
}
const exposeProps: ExposeProps = {
appendTop(newNode: ILeafNode) {
// 内部方法调用
const newFlatNode = appendTop(newNode, originalFlatData, data, optionProps)
_startEdit(newFlatNode)
}
}
这里调整下append
和appendTop
方法的返回值,原来是void
,现在改为IFlatTreeNode
。