需求预览
需求,做一个类似CMD命令提示工具
1、有命令提示; 2、有层级关系 ; 3、以 / 开头,空格提示下一步;
分析
(一)用户输入进行调用方法实现命令提示;Ele-Plus的Autocomplete组件方法无法以Tree的方式进行补齐.
---> 不以 / 开头
---> 仅 /
---> /a 显示包含 /a 所有
---> /add和提示的一致,则不提示
---> /add空格,提示下一步;
(二)创建Tree结构
S--> root
A---> /a
B-----> 120
C-------> 111
C-------> 222
A---> /b
B-----> 123
C-------> 111
B-----> 741
C-------> 111
(三)递归匹配树 分两个方法,来调用是否存在空格进行匹配
matchPathWithFullPath()
handlePathWithSpace()
(**)核心是构建树结构,通过递归去遍历匹配树的节点和子节点
代码
1.1 定义clas类,基于OOP
class InputNode {
private children: { [key: string]: InputNode } = {} // 存储子节点的字典
private value: string
constructor(value: string) {
this.value = value
}
// 添加子节点
addChild(key: string, child: InputNode) {
this.children[key] = child
}
// 获取子节点
getChild(key: string): InputNode | undefined {
return this.children[key]
}
// 获取当前节点的值
getValue(): string {
return this.value
}
// 获取所有子节点
getChildren(): { [key: string]: InputNode } {
return this.children
}
}
1.2 解析路径并构建树结构
function initCmd(path: string, root: InputNode) {
const parts = path.split(' ').filter(Boolean) // 按空格分割路径并过滤空字符串
let currentNode = root
// 遍历路径部分,逐层构建树
for (const part of parts) {
if (!currentNode.getChild(part)) {
const newNode = new InputNode(part)
currentNode.addChild(part, newNode)
}
currentNode = currentNode.getChild(part)! // 移动到下一个节点
}
}
1.3 递归检查路径与树中节点的匹配关系,并返回包含路径的完整路径
function matchPathWithFullPath(path: string, node: InputNode, currentPath: string = ''): string[] {
const pathParts = path.split(' ').filter(Boolean) // 拆分路径
let results: string[] = []
// 当前路径部分
const currentPart = pathParts[0]
const remainingPath = pathParts.slice(1).join(' ') // 剩余部分路径
// 遍历当前节点的子节点,进行递归匹配
for (const key in node.getChildren()) {
const childNode = node.getChild(key)
if (childNode && childNode.getValue().includes(currentPart)) {
// 如果匹配到当前节点的值包含路径部分,则继续递归
const newPath = currentPath + childNode.getValue()
if (remainingPath) {
const childMatches = matchPathWithFullPath(remainingPath, childNode, newPath + ' ')
results = results.concat(childMatches) // 合并匹配结果
} else {
// 如果路径已经完全匹配到当前节点,返回该节点的完整路径
results.push(newPath)
}
}
}
return results
}
1.4 处理路径输入,如果输入路径以空格结尾,提示下一层子节点
function handlePathWithSpace(path: string, root: InputNode): string[] {
const trimmedPath = path.trim() // 去掉多余的空格
const results: string[] = []
// 查找路径的最后一个部分
const pathParts = trimmedPath.split(' ').filter(Boolean)
let currentNode: InputNode | undefined = root
// 找到路径中的最后一个节点
for (const part of pathParts) {
currentNode = currentNode?.getChild(part)
}
if (currentNode) {
// 如果节点存在,获取它的所有子节点,并返回它们的完整路径
for (const key in currentNode.getChildren()) {
const childNode = currentNode.getChild(key)
if (childNode) {
results.push(trimmedPath + ' ' + childNode.getValue())
}
}
}
return results
}
1.5 前端部分
Template
<template>
<div class="user-input-container">
<el-input
type="text"
class="user-input"
v-model="cmdCode"
@keydown="handleKeyDown"
placeholder="请以 / 开头"
/>
<ul v-if="tempResult.length" class="suggestions-list">
<li
v-for="(item, index) in tempResult"
:key="index"
class="suggestion-item"
:class="{ 'is-selected': index === activeIndex }"
@click="selectItem(item)"
>
{{ item }}
</li>
</ul>
</div>
</template>
Script setup lang-ts
// 创建根节点
let root = new InputNode('root')
// 使用命令初始化树结构
initCmd('/add1 105 330', root)
initCmd('/add1 105 430', root)
initCmd('/acc2 233 530', root)
initCmd('/dcc2 133 930', root)
const cmdCode = ref('')
const info = ref<string[]>([])
const activeIndex = ref(0) // 新增一个响应式变量用于跟踪当前选中的索引
let tempResult = ref<string[]>([])
监听输入框,可以自行加一个防抖
watch(
() => cmdCode.value,
(newValue) => {
if (!newValue) {
info.value = [] // 清空info数组
tempResult.value = [] // 清空tempResult数组
return
}
if (!newValue.startsWith('/')) {
info.value = [] // 清空info数组
tempResult.value = [] // 清空tempResult数组
failInfo('请以 / 开头')
return
}
const parts = newValue.split(' ')
switch (parts.length) {
case 1:
// 不带空格的输入
tempResult.value = []
info.value = matchPathWithFullPath(newValue, root)
if (info.value.length) {
info.value.filter((one) => {
if (one != newValue) {
tempResult.value.push(one)
}
})
}
break
case 2:
tempResult.value = []
// 带一个空格,空格后面没有值或有值
if (parts[1] === '') {
info.value = handlePathWithSpace(newValue, root)
} else {
info.value = matchPathWithFullPath(newValue, root)
}
if (info.value.length) {
info.value.forEach((one) => {
if (one !== newValue) {
let temp = one.split(' ')
tempResult.value.push(temp[1])
}
})
}
break
case 3:
tempResult.value = []
// 带两个空格,第二个空格后面没有值或有值
if (parts[2] === '') {
info.value = handlePathWithSpace(newValue, root)
} else {
info.value = matchPathWithFullPath(newValue, root)
}
if (info.value.length) {
info.value.forEach((one) => {
if (one !== newValue) {
let temp = one.split(' ')
if (temp.length > 2) {
// 确保有足够的分割部分
tempResult.value.push(temp[2])
}
}
})
}
break
default:
console.log('Invalid input or more complex path structure')
}
activeIndex.value = 0 // 每次输入时重置活动索引
}
)
// 处理键盘按下事件
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'ArrowDown') {
navigateDown()
event.preventDefault() // 阻止默认行为
} else if (event.key === 'ArrowUp') {
navigateUp()
event.preventDefault()
} else if (event.key === 'Enter') {
selectItem()
event.preventDefault()
} else if (event.key === 'Tab') {
selectItem()
event.preventDefault()
}
}
// 导航到下一项
const navigateDown = () => {
if (info.value.length && activeIndex.value < info.value.length - 1) {
activeIndex.value++
}
}
// 导航到上一项
const navigateUp = () => {
if (activeIndex.value > -1) {
activeIndex.value--
}
}
// 选择当前项
const selectItem = (value?: string) => {
if (value) {
if (value == '/add1') {
cmdCode.value = value
} else {
cmdCode.value += value
}
} else {
if (info.value.length && activeIndex.value !== -1) {
cmdCode.value = info.value[activeIndex.value]
// info.value = [] // 选择后清空下拉列表
}
}
}
</script>
style
.suggestions-list {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
z-index: 1000;
max-height: 200px;
overflow-y: auto;
}
.suggestion-item {
margin-left: 0;
padding: 10px;
cursor: pointer;
transition: background-color 0.3s ease;
margin-left: -40px;
}
.suggestion-item:hover,
.suggestion-item:focus {
background-color: #f0f0f0;
}
/* 为选中的列表项添加特殊样式 */
.suggestion-item.is-selected {
background-color: #e0e0e0;
}
这个方法可能不是最优,可以作为参考,不想造的工具告诉我,我来帮你造,一起加油新时代的打工人!⛽