【PPTist】查找替换、绘制文本框

news/2025/1/12 6:05:36/

一、查找、替换

查找替换的组件 src/views/Editor/SearchPanel.vue
回车的时候会执行查找,查找替换功能的相关方法和属性都在 src/hooks/useSearch.ts 中。

1、查找

查找的方法也比较的朴实无华,就是 for 循环所有的幻灯片中的所有元素,通过 .match() 方法匹配要查找的关键词,如果匹配上的话,就放到 searchResults 中,会保存元素的id、元素类型以及元素所在的幻灯片的id。然后对 searchResults 中的数据进行高亮。
在这里插入图片描述

高亮的方法是会给目标文本创建一个父级 marker
在这里插入图片描述

mark 上还增加了一个 data-index 表示顺序
通过 textNode.parentNode!.replaceChild(mark, textNode) 使用 mark 替换文本元素

2、下一个/上一个

src/hooks/useSearch.ts 中有一个属性 searchIndex 来标识当前查找第几个关键词。下一个/上一个 的时候,会修改这个属性,然后执行 turnTarget() 方法

const turnTarget = () => {if (searchIndex.value === -1) returnconst target = searchResults.value[searchIndex.value]if (target.slideId === currentSlide.value.id) setTimeout(setActiveMark, 0)else {const index = slides.value.findIndex(slide => slide.id === target.slideId)if (index !== -1) slidesStore.updateSlideIndex(index)}
}

这里有两种情况了,

  • 如果幻灯片没变化,执行 setActiveMark(),找对应的 mark,增加 active 类名
const setActiveMark = () => {const markNodes = document.querySelectorAll('mark[data-index]')for (const node of markNodes) {setTimeout(() => {const index = (node as HTMLElement).dataset.indexif (index !== undefined && +index === searchIndex.value) {node.classList.add('active')}else node.classList.remove('active')}, 0)}
}
  • 幻灯片变化,执行 slidesStore.updateSlideIndex(index) ,要跳转幻灯片。跳转完了之后呢,这里有一个监听幻灯片变化的方法,重新计算一下需要高亮的文本,设置 markactive
  • src/hooks/useSearch.ts
watch(slideIndex, () => {nextTick(() => {highlightCurrentSlide()setTimeout(setActiveMark, 0)})
})
3、替换

替换的时候,如果没有搜索结果的话,会直接执行 searchNext() 方法。
如果当前有搜索结果,就找当前处在激活状态中的 mark
先制造一个 fakeElement,通过 parentNode.replaceChild(document.createTextNode(replaceWord.value), mark) 方法替换节点,
然后还通过 slidesStore.updateElement({ id: target.elId, props }) 更新元素,使用 fakeElement 替换原来的元素。

4、替换全部

替换全部的时候其实跟替换差不多,但是循环 searchResults,遍历所有的文本节点,给所有的 mark 都创造一个 fakeElement,使用 slidesStore.updateElement({ id: target.elId, slideId: target.slideId, props }) 进行更新。
创建一个fakeElement 而不是直接更新 DOM ,可以防止后续错误导致流程失败结果已经更新了DOM的情况,以及减少页面重绘次数,另外可以统一将更新操作统一执行,代码可维护性更高。

二、绘制文本框

绘制文字范围、绘制形状范围使用的都是 mainStore.setCreatingElement() 方法。这个方法接收一个对象作为参数,就是要绘制的元素本身。如果是绘制文字的话,

// 绘制文字范围
const drawText = (vertical = false) => {mainStore.setCreatingElement({type: 'text',vertical,})
}

第二个参数表示是否是垂直方向,默认是横向。

src/views/Editor/Canvas/index.vue

<ElementCreateSelectionv-if="creatingElement"@created="data => insertElementFromCreateSelection(data)"/>

有了 creatingElement,就会创建 ElementCreateSelection 组件,同时会执行 insertElementFromCreateSelection()
方法。

  // 根据鼠标选区的位置大小插入元素
const insertElementFromCreateSelection = (selectionData: CreateElementSelectionData) => {if (!creatingElement.value) returnconst type = creatingElement.value.typeif (type === 'text') {const position = formatCreateSelection(selectionData)position && createTextElement(position, { vertical: creatingElement.value.vertical })}mainStore.setCreatingElement(null)
}

然而,这里有一个容易混淆的地方,@created 并不是Vue3内置的函数,而是这个自定义组件自己定义的监听函数🥹🥹🥹,我说呢,这个方法的执行实际不太对啊,这个方法是鼠标起来的时候才会执行,而不是创建这个组件的时候执行。
我们看一下 src/views/Editor/Canvas/ElementCreateSelection.vue 组件里面的执行流程

  • mousedown
    鼠标落下的时候,执行 createSelection(),记录此时的坐标到 start 中。此时会添加 document.onmousemove 监听函数

  • mousemove
    鼠标移动的时候,实时计算坐标,记录到 end

  • mouseup
    鼠标抬起时,清空 onmousemoveonmouseup
    通过 e.button 判断此时有没有按鼠标右键

    e.button = 0  // 鼠标左键
    e.button = 1  // 鼠标中键(滚轮)
    e.button = 2  // 鼠标右键
    e.button = 3  // 浏览器后退键
    e.button = 4  // 浏览器前进键
    

    如果按了就表示取消绘制,mainStore.setCreatingElement(null)creatingElement 清空。
    否则就触发 created 方法,将 startend 传进去,然后自定义组件就会监听到。那我就不明白了,明明是组件内部会触发这个方法,干嘛还要写成监听函数的形式,直接写成组件里面的方法不就行了吗?一般定义监听函数,都是给父组件来触发的👽👽👽
    哦,知道了,因为这个

    const { insertElementFromCreateSelection, formatCreateSelection } = useInsertFromCreateSelection(viewportRef)
    

    要将 viewportRef 传进去呢,这个模版元素只能从父组件中传过去。

  • @created
    监听到这个方法之后,就要执行 insertElementFromCreateSelection(data)data 是位置信息,就是上面说的 startend

  • createTextElement()
    根据位置信息创建文本元素。这个方法以前也见过了。
    src/hooks/useCreateElement.ts

      // 创建(插入)一个元素并将其设置为被选中元素const createElement = (element: PPTElement, callback?: () => void) => {// 添加元素到元素列表slidesStore.addElement(element)// 设置被选中元素列表mainStore.setActiveElementIdList([element.id])if (creatingElement.value) mainStore.setCreatingElement(null)setTimeout(() => {// 设置编辑器区域为聚焦状态mainStore.setEditorareaFocus(true)}, 0)if (callback) callback()// 添加历史快照addHistorySnapshot()}
    

http://www.ppmy.cn/news/1562420.html

相关文章

【20250109】Nature子刊:一种可改善帕金森患者冻结步态的柔性可穿戴机器人系统...

引言&#xff1a;冻结步态&#xff08;FoG&#xff09;是帕金森病中一种极具破坏性的步态障碍&#xff0c;会导致患者在行走时意外停顿。目前针对冻结步态的治疗效果有限且短暂&#xff0c;因此缺乏有效的治疗方法。该论文展示了一种使用机器人改善帕金森冻结步态的概念验证&am…

自然语言处理之jieba分词和TF-IDF分析

jieba分词和TF-IDF分析 目录 jieba分词和TF-IDF分析1 jieba1.1 简介1.2 终端下载1.3 基本语法 2 TF-IDF分析2.1 什么是语料库2.2 TF2.3 IDF2.4 TF-IDF2.5 函数导入2.6 方法 3 实际测试3.1 问题解析3.2 代码测试 1 jieba 1.1 简介 结巴分词&#xff08;Jieba&#xff09;是一个…

Web后端开发总结(day14)

Web后端开发总结 web后端开发现在基本上都是基于标准的三层架构进行开发的&#xff0c;在三层架构当中&#xff0c;Controller控制器 层负责接收请求响应数据&#xff0c;Service业务层负责具体的业务逻辑处理&#xff0c;Dao数据访问层也叫持久层&#xff0c; 就是用来处理数据…

MyBatis深入了解

目录 xml 映射文件中&#xff0c;除了常见的select、insert、update、delete 标签之外&#xff0c;还有哪些标签? Dao 接口的工作原理是什么?Dao 接口里的方法&#xff0c;参数不同时&#xff0c;方法能重载吗? MyBatis 是如何进行分页的?分页插件的原理是什么? 简述 …

sql正则表达

MySQL中的正则表达式使用REGEXP关键字来指定匹配模式。常见的正则表达式符号包括&#xff1a; .&#xff1a;匹配任意单个字符 ^&#xff1a;匹配字符串的开始位置 $&#xff1a;匹配字符串的结束位置 *&#xff1a;匹配前面的字符或字符集出现零次或多次 &#xff1a;匹配前面…

【漫话机器学习系列】044.热点对特性的影响(Effect Of One Hot On Feature Importance)

热点对特性的重要性影响&#xff08;Effect of One-Hot Encoding on Feature Importance&#xff09; 一热编码&#xff08;One-Hot Encoding&#xff09; 是处理类别型数据的常用方法&#xff0c;将每个类别特征转换为一组独立的二进制特征。这种方法在提高模型处理非数值数据…

云端 IPv4 VRRP+MSTP多备份组配置实验

SW3和SW4作为核心交换机&#xff0c;SW1和SW2分别有两条链路可做冗余链路连接到核心交换机上。 1、把SW3和SW4配置vrrp虚拟路由器冗余协议&#xff0c;把两台路由器虚拟成一台路由器 2、创建两个备份组&#xff0c;备份组一的虚拟ip做pc6的网关&#xff0c;备份组二的虚拟ip做…

C++ 位运算符 [学习笔记]

在C中&#xff0c;进位符<<和>>是位运算符&#xff0c;它们用于处理二进制数据&#xff0c;特别是在底层程序设计和嵌入式开发中非常重要。下面我们从基本概念、使用方法、应用场景等方面详细讲解。 1. 基本概念 1.1 << 左移运算符 作用&#xff1a;将二进…