甘南州城乡建设局网站,建筑企业信息查询平台,怎么做点播网站,网站限定域名这是一个稍微复杂的功能了#xff0c;因为 element-tiptap 中没有查找替换功能#xff0c;需要从零开始开发。但是#xff0c;在万能的github上有一个开源的库#xff0c;我们可以借用一下 tiptap-search-and-replace 不过这个库是没有UI的#xff0c;只有一个扩展的方法。…这是一个稍微复杂的功能了因为 element-tiptap 中没有查找替换功能需要从零开始开发。但是在万能的github上有一个开源的库我们可以借用一下 tiptap-search-and-replace 不过这个库是没有UI的只有一个扩展的方法。但是这个关键的方法只要有了剩下的就简单多了 searchAndReplace.ts 我的项目的目录名文件名都已经从首字母大写改成了全部小写的写法不影响大家阅读哦 我们首先把这个源码放到 scr/extensions 目录下面 然后UI我们可以参考在线WPS的UI 先分析一下需求。我发现开发之前的需求分析真的很重要可能这就是所谓的“慢即是快”看似有些繁琐并且浪费时间但是可以在开发的时候少走很多弯路可以让自己开发的思路更加的清晰效率也会更高。
新建一个扩展需要一个下拉框组件点击按钮出现查找和替换两个菜单项点击查找、替换菜单项的时候都需要弹出弹出框组件并且把状态传递给弹出框组件需要一个弹出框组件有两种状态表示当前是查找还是替换弹出框组件分为两个tab栏查找可以找上一个、下一个替换可以找上一个、下一个、全部替换、替换当前查找可以用快捷键⌘F唤醒替换可以用快捷键⌘H唤醒 更多选项里面的功能就先不做了
1、新建一个扩展
① 新建一个扩展src/extensions/search-replace.ts然后把上面的文件的源码放进去当然我们后续还需要对它进行稍微的改造这里先不管 ② 在 src/extensions/index.ts 文件中模仿其他扩展也导出这个扩展
export { default as SearchAndReplace } from ./search-replace;③ 在 src/components/editor.vue 文件中的扩展列表 extensions 增加一项根据官网的提示需要增加 configure 配置项
import {SearchAndReplace
} from ../extensions;
SearchAndReplace.configure({searchResultClass: search-result, // class to give to found items. default search-resultcaseSensitive: false, // no need to explaindisableRegex: false, // also no need to explain
}),2、下拉框组件
① 创建下拉框组件 src/components/menu-commands/search-replace/search-replace.dropdown.vue ps我这里的文件命名自己有根据项目需要修改过大家自行修改哈 代码说明
下拉框菜单有两个查找、替换点击查找或者替换的时候会将对应的标识字符串传递到回调函数中以此标识当前的操作类型一个弹出框组件当点击查找或替换的时候都会被激活并且接受表示操作类型的参数
templateel-dropdown placementbottom triggerclick commandhandleCommand popper-classmy-dropdown:popper-options{ modifiers: [{ name: computeStyles, options: { adaptive: false } }] }divcommand-button :enable-tooltipenableTooltip :tooltipt(editor.extensions.searchAndReplace.tooltip)iconsearch :button-iconbuttonIcon //divtemplate #dropdownel-dropdown-menu classel-tiptap-dropdown-menuel-dropdown-item commandsearchspan查找/span/el-dropdown-itemel-dropdown-item commandreplacespan替换/span/el-dropdown-item/el-dropdown-menu/template/el-dropdownsearch-replace-popup v-ifshowPopup :modepopupMode :editoreditor closeshowPopup false /
/templatescript langts
import { defineComponent, inject, ref } from vue;
import { Editor } from tiptap/vue-3;
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from element-plus;
import CommandButton from ../command.button.vue;
import searchReplacePopup from ./search-replace.popup.vue;export default defineComponent({name: searchAndReplaceDropdown,components: {ElDropdown,ElDropdownMenu,ElDropdownItem,CommandButton,searchReplacePopup,},props: {editor: {type: Object as () Editor,required: true,},buttonIcon: {default: ,type: String}},setup(props) {const t inject(t) as (key: string) string;const enableTooltip inject(enableTooltip, true);const showPopup ref(false);const popupMode refsearch | replace(search);const handleCommand (command: string) {popupMode.value command as search | replace;showPopup.value true;};return {t,enableTooltip,showPopup,popupMode,handleCommand};},
});
/scriptstyle scoped
.dropdown-title {font-size: 14px;font-weight: 500;margin: 5px;
}.el-tiptap-dropdown-menu__item {margin-left: 5px;
}
/style② 这里的图标 search 需要我们自己添加 src/icons/search.svg
svg width16 height16 viewBox0 0 16 16 fillnone stroke-width1.5g idgroup-0 stroke#333333 fill#333333path dM11.2426 11.2426L14.5 14.5M13 7C13 10.3137 10.3137 13 7 13C3.68629 13 1 10.3137 1 7C1 3.68629 3.68629 1 7 1C10.3137 1 13 3.68629 13 7Z stroke-linecapround stroke-linejoinmiter fillnone vector-effectnon-scaling-stroke/path/g/svg③ 扩展文件修改应用下拉框组件 主要是 addOptions 方法
import searchAndReplaceDropdown from /components/menu-commands/search-replace/search-replace.dropdown.vue;
addOptions() {return {// 保留父扩展的所有选项...this.parent?.(),button({ editor, t }: { editor: Editor; t: (...args: any[]) string }) {return {component: searchAndReplaceDropdown,componentProps: {editor,},};},};
},3、弹出框组件
src/components/menu-commands/search-replace/search-replace.popup.vue ① 创建弹出框组件 组件说明
UI 参考WPS编辑器的效果查找、替换、上一个、下一个、替换全部 这些功能已经被我们的扩展文件添加到了 editor.commands 上所以直接通过命令调用即可输入框中文字改变的时候就需要执行查找点击右上角的叉号关闭弹出框的时候要去除所有的选中状态
templatediv v-ifdrawerVisible classsearch-replace-container :stylecontainerStylespan classsearch-replace-close typetext clickhandleClose×/spanel-tabs v-modelactiveTabel-tab-pane label查找 namesearchdiv classsearch-replace-title查找/divel-input v-modelsearchTerminputonSearchTermChangesizedefault:placeholdert(editor.extensions.searchAndReplace.searchPlaceholder)/el-inputdiv classsearch-replace-actionsel-button sizedefault clickfindPrevious上一个/el-buttonel-button sizedefault clickfindNext下一个/el-button/div/el-tab-paneel-tab-pane label替换 namereplacediv classsearch-replace-title查找/divel-input v-modelsearchTerminputonSearchTermChangesizedefault:placeholdert(editor.extensions.searchAndReplace.searchPlaceholder)/el-inputdiv classsearch-replace-title替换为/divel-input v-modelreplaceTermsizedefault:placeholdert(editor.extensions.searchAndReplace.replacePlaceholder)/el-inputdiv classsearch-replace-actionsel-button sizedefault clickfindPrevious上一个/el-buttonel-button sizedefault clickfindNext下一个/el-buttonel-button sizedefault clickreplaceCurrent替换/el-buttonel-button sizedefault clickreplaceAll替换全部/el-button/div/el-tab-pane/el-tabs/div
/templatescript langts
import { defineComponent, ref, watch, inject, computed } from vue;
import { Editor } from tiptap/vue-3;
import { ElTabs, ElTabPane, ElInput, ElButton } from element-plus;export default defineComponent({name: searchAndReplacePopup,components: {ElTabs,ElTabPane,ElInput,ElButton,},props: {mode: {type: String as () search | replace,required: true,},editor: {type: Object as () Editor,required: true,},},emits: [close],setup(props, { emit }) {const t inject(t) as (key: string) string;const drawerVisible ref(true);const activeTab ref(props.mode);const searchTerm ref();const replaceTerm ref();watch(() props.mode, (newMode) {activeTab.value newMode;});const handleClose () {emit(close);searchTerm.value ;replaceTerm.value ;props.editor.commands.setSearchTerm();props.editor.commands.resetIndex();};const findNext () {props.editor.commands.setSearchTerm(searchTerm.value);props.editor.commands.nextSearchResult();};const findPrevious () {props.editor.commands.setSearchTerm(searchTerm.value);props.editor.commands.previousSearchResult();};const replaceCurrent () {props.editor.commands.setReplaceTerm(replaceTerm.value);props.editor.commands.replace();};const replaceAll () {props.editor.commands.setReplaceTerm(replaceTerm.value);props.editor.commands.replaceAll();};const onSearchTermChange () {props.editor.commands.setSearchTerm(searchTerm.value);};// 动态计算容器宽度const containerStyle computed(() ({width: activeTab.value search ? 358px : 478px,}));return {t,drawerVisible,activeTab,searchTerm,replaceTerm,handleClose,findNext,findPrevious,replaceCurrent,replaceAll,onSearchTermChange,containerStyle,};},
});
/scriptstyle langscss
import ../../../styles/variables.scss;
.search-replace-container {position: absolute;top: 10%;right: 10%;width: 30%;background-color: white;border: 1px solid #ccc;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);padding: 16px;z-index: 1000;border-radius: 5px;transition: width 0.3s ease; /* 添加过渡效果 */
}.search-replace-container .el-tabs__item {font-size: 16px;
}
.search-replace-container .el-tabs__nav-wrap::after {background-color: transparent;
}
.search-replace-close {cursor: pointer;font-size: 18px;color: #60646c;font-weight: 600;position: absolute;width: 24px;height: 24px;display: flex;align-items: center;justify-content: center;border-radius: 4px;top: 22px;right: 18px;z-index: 3;
}
.search-replace-close:hover {background-color: #f0f0f0;
}.search-replace-title {font-weight: 600;font-size: 14px;line-height: 22px;color: hsla(0, 0%, 5%, .9);margin-top: 8px;margin-bottom: 3px;
}.search-result {background: $lighter-primary-color;
}.search-result.search-result-current {background: $tiptap-search-result-current-color;
}.search-replace-actions {margin-top: 16px;display: flex;justify-content: flex-end;
}
/style
② 背景颜色定义 如上图所示查找的时候会查找到很多结果所有的结果都会被添加类名 search-result不过当前选中的结果还会被添加 search-result-current 类名。 上面的代码中我们给这两个类添加了背景颜色背景颜色是通过 import ../../../styles/variables.scss; 引入进来的在这个文件中还需要增加一个定义 src/styles/variables.scss
$tiptap-search-result-current-color:rgb(193, 243, 181);这样就可以实现如下效果
4、替换方法改造
这里有一个小问题就是替换功能总是会替换第一个查找结果但是期望结果应该是替换我们选中的结果 此时选中的是第三个 Content点击替换的时候还是会替换第一个 Content 这其实是源码中的 replace 方法的问题我们看一下这个方法的定义
const replace (replaceTerm: string,results: Range[],{ state, dispatch }: { state: EditorState; dispatch: Dispatch },
) {const firstResult results[0];if (!firstResult) return;const { from, to } results[0];if (dispatch) dispatch(state.tr.insertText(replaceTerm, from, to));
};可以看到确实是进行的是结果列表中第一个元素的替换 这个方法的调用在 addCommands 里
replace:() ({ editor, state, dispatch }) {const { replaceTerm, results } editor.storage.searchAndReplace;console.log(editor.storage.searchAndReplace);console.log(results);replace(replaceTerm, results, { state, dispatch });return false;},控制台输出一下 editor.storage.searchAndReplace 我们可以发现有一个属性可以标识当前选中的是哪一个结果 索引为 2也就是第三个。 那么我们可以改造一下 replace 函数接收一个索引的参数来指定替换哪一个
// 替换当前搜索结果
const replace (replaceTerm: string,results: Range[],resultIndex: number,{ state, dispatch }: { state: EditorState; dispatch: Dispatch },
) {if (resultIndex 0 || resultIndex results.length) return;const { from, to } results[resultIndex];if (dispatch) dispatch(state.tr.insertText(replaceTerm, from, to));
};这样就可以实现 替换当前选中的结果 了
5、快捷键
① src/extensions/search-replace.ts 增加快捷键 如果当前鼠标选中的区域有文本的话就需要获取到选中区域的文本传递给 setSearchTerm 命令
addKeyboardShortcuts() {return {Mod-f: () {const { state } this.editor;const { from, to } state.selection;const selectedText state.doc.textBetween(from, to);this.editor.commands.setSearchTerm(selectedText);this.editor.emit(openSearchReplacePopup, search);return true;},Mod-h: () {const { state } this.editor;const { from, to } state.selection;const selectedText state.doc.textBetween(from, to);this.editor.commands.setSearchTerm(selectedText);this.editor.commands.setReplaceTerm();this.editor.emit(openSearchReplacePopup, replace);return true;},};
},② src/components/menu-commands/search-replace/search-replace.dropdown.vue 我们知道弹出框显示与否是通过这个组件里面的 showPopup 属性控制的快捷键按下的时候会使用 emit 触发 openSearchReplacePopup 事件。那么在下拉框组件中我们需要监听openSearchReplacePopup 事件在回调函数中将showPopup 属性设置为 true
onMounted(() {props.editor.on(openSearchReplacePopup, (mode: search | replace) {popupMode.value mode;showPopup.value true;});
});③ src/components/menu-commands/search-replace/search-replace.popup.vue 在这个组件中searchTerm 我们之前是初始化为的但是现在这个值需要从编辑器的属性中获取
const searchTerm ref(props.editor.storage.searchAndReplace.searchTerm);另外还有一个小细节如图我们选中了第三个 Content并且按下了快捷键 此时会发生什么会发现当前的查找结果是第一个 Content 这不是我想要的。 应该是咱们鼠标选中的内容作为当前查找结果。 那么此时我们就需要在查找结果数组中找到和我们鼠标选中区域的位置一样的查找结果并且选中这个查找结果
const selection props.editor.state.selection;
const results props.editor.storage.searchAndReplace.results;
if(results.length 0) {// 找到 results 中from 和 selection.from 一样的结果然后设置为当前结果for(let i 0; i results.length; i) {if(results[i].from ! selection.from) {// 向后找props.editor.commands.nextSearchResult();}else{break;}}
}然后你会发现现在选中第三个 Content 按下快捷键是这种效果
其实不是bug了是因为当前我们用鼠标选中了第三个 Content并且鼠标选中的背景色跟我们设置的选中结果的背景色一样就这个我以为是bug看了好久好久。。。。。。 不如改个颜色直观一些吧 src/styles/variables.scss
$tiptap-search-result-color:#FDFF00;
$tiptap-search-result-current-color:#F79632;src/components/menu-commands/search-replace/search-replace.popup.vue
.search-result {display: inline-block;background: $tiptap-search-result-color;
}.search-result.search-result-current {background: $tiptap-search-result-current-color;
}随便点击一下页面 长呼一口气这个还真是有点复杂。