前端使用 Konva 实现可视化设计器(23)- 绘制曲线、属性面板

本章分享一下如何使用 Konva 绘制基础图形:曲线,以及属性面板的基本实现思路,希望大家继续关注和支持哈(多求 5 个 Stars 谢谢)!

请大家动动小手,给我一个免费的 Star 吧~

大家如果发现了 Bug,欢迎来提 Issue 哟~

github源码

gitee源码

示例地址

绘制曲线

先上效果!

在这里插入图片描述

这里其实取巧了哈,基本就是在绘制折线的基础上,给 Konva.Line 添加一个关键的属性 tension 即可,参照官方示例:

在这里插入图片描述

未来,在属性面板中,可以调节 tension 的值,基本可以实现绘制一些简单的曲线。

属性面板

早些时候,已经有小伙伴问,外部如何动态调整 Konva 内部各对象的一些特性,这里以页面的背景色、全局线条和填充颜色,及其素材各自的线条和填充颜色为例,分享一个基本可行实现思路是如何的。

这里以 svg 素材为例,可以调整 svg 素材的线条、填充颜色。

基本交互

在这里插入图片描述

UI

这里简单粗暴一些,使用 naive-ui 的组件组装一下就可以了:

<!-- src/App.vue --><n-tabs type="line" size="small" animated v-model:value="tabCurrent"><n-tab-pane name="page" tab="页面"><n-form ref="formRef" :model="pageSettingsModel" :rules="{}" label-placement="top" size="small"v-if="pageSettingsModel"><n-form-item label="背景色" path="background"><n-color-picker v-model:value="pageSettingsModelBackground" @update:show="(v: boolean) => {pageSettingsModel && !v && (pageSettingsModelBackground = pageSettingsModel.background)}" :actions="['clear', 'confirm']" show-preview@confirm="(v: string) => { pageSettingsModel && (pageSettingsModel.background = v) }"@clear="pageSettingsModel && (pageSettingsModel.background = Render.PageSettingsDefault.background)"></n-color-picker></n-form-item><n-form-item label="线条颜色" path="stroke"><n-color-picker v-model:value="pageSettingsModelStroke" @update:show="(v: boolean) => {pageSettingsModel && !v && (pageSettingsModelStroke = pageSettingsModel.stroke)}" :actions="['clear', 'confirm']" show-preview@confirm="(v: string) => { pageSettingsModel && (pageSettingsModel.stroke = v) }"@clear="pageSettingsModel && (pageSettingsModel.stroke = Render.AssetSettingsDefault.stroke)"></n-color-picker></n-form-item><n-form-item label="填充颜色" path="fill"><n-color-picker v-model:value="pageSettingsModelFill" @update:show="(v: boolean) => {pageSettingsModel && !v && (pageSettingsModelFill = pageSettingsModel.fill)}" :actions="['clear', 'confirm']" show-preview@confirm="(v: string) => { pageSettingsModel && (pageSettingsModel.fill = v) }"@clear="pageSettingsModel && (pageSettingsModel.fill = Render.AssetSettingsDefault.fill)"></n-color-picker></n-form-item></n-form></n-tab-pane><n-tab-pane name="asset" tab="素材" :disabled="assetCurrent === void 0"><n-form ref="formRef" :model="assetSettingsModel" :rules="{}" label-placement="top" size="small"v-if="assetSettingsModel"><n-form-item label="线条颜色" path="stroke" v-if="assetCurrent?.attrs.imageType === Types.ImageType.svg"><n-color-picker v-model:value="assetSettingsModelStorke" @update:show="(v: boolean) => {assetSettingsModel && !v && (assetSettingsModelStorke = assetSettingsModel.stroke)}" :actions="['clear', 'confirm']" show-preview@confirm="(v: string) => { assetSettingsModel && (assetSettingsModel.stroke = v) }"@clear="assetSettingsModel && (assetSettingsModel.stroke = '#000')"></n-color-picker></n-form-item><n-form-item label="填充颜色" path="fill" v-if="assetCurrent?.attrs.imageType === Types.ImageType.svg"><n-color-picker v-model:value="assetSettingsModelFill" @update:show="(v: boolean) => {assetSettingsModel && !v && (assetSettingsModelFill = assetSettingsModel.fill)}" :actions="['clear', 'confirm']" show-preview@confirm="(v: string) => { assetSettingsModel && (assetSettingsModel.fill = v) }"@clear="assetSettingsModel && (assetSettingsModel.fill = '#000')"></n-color-picker></n-form-item></n-form></n-tab-pane></n-tabs>

魔改一下组件样式:

/* src/App.vue */:deep(.n-tabs-nav-scroll-content) {box-shadow: 0 -1px 0 0 rgb(230, 230, 230) inset;border-bottom-color: rgb(230, 230, 230) !important;}:deep(.n-tabs-tab-pad) {width: 16px;}

组件和表单的控制:

// src/App.vue// 略function init() {if (boardElement.value && stageElement.value) {resizer.init(boardElement.value, {resize: async (x, y, width, height) => {if (render === null) {// 初始化渲染render = new Render(stageElement.value!, {width,height,//showBg: true,showRuler: true,showRefLine: true,attractResize: true,attractBg: true,showPreview: true,attractNode: true,})// 同步页面设置pageSettingsModel.value = render.getPageSettings()await nextTick()ready.value = true}render.resize(width, height)// 同步页面设置render.on('page-settings-change', (settings: Types.PageSettings) => {pageSettingsModelInnerChange.value = truepageSettingsModel.value = settings})render.on('selection-change', (nodes: Konva.Node[]) => {if (nodes.length === 0) {// 清空选择assetCurrent.value = undefinedassetSettingsModel.value = undefinedtabCurrent.value = 'page'} else if (nodes.length === 1) {// 单选assetCurrent.value = nodes[0]assetSettingsModel.value = render!.getAssetSettings(nodes[0])tabCurrent.value = 'asset'} else {// 多选assetCurrent.value = undefinedassetSettingsModel.value = undefinedtabCurrent.value = 'page'}})}})}
}// 略// 当前 tab
const tabCurrent = ref('page')// 页面设置
const pageSettingsModel: Ref<Types.PageSettings | undefined> = ref()
const pageSettingsModelInnerChange = ref(false)const pageSettingsModelBackground = ref('')
const pageSettingsModelStroke = ref('')
const pageSettingsModelFill = ref('')// 当前素材
const assetCurrent: Ref<Konva.Node | undefined> = ref()// 素材设置
const assetSettingsModel: Ref<Types.AssetSettings | undefined> = ref()
const assetSettingsModelInnerChange = ref(false)const assetSettingsModelStorke = ref('')
const assetSettingsModelFill = ref('')watch(() => pageSettingsModel.value, () => {if (pageSettingsModel.value) {pageSettingsModelBackground.value = pageSettingsModel.value.backgroundpageSettingsModelStroke.value = pageSettingsModel.value.strokepageSettingsModelFill.value = pageSettingsModel.value.fillif (ready.value && !pageSettingsModelInnerChange.value) {render?.setPageSettings(pageSettingsModel.value)}}pageSettingsModelInnerChange.value = false
}, {deep: true
})watch(() => assetSettingsModel.value, () => {if (assetSettingsModel.value && assetCurrent.value) {assetSettingsModelStorke.value = assetSettingsModel.value.strokeassetSettingsModelFill.value = assetSettingsModel.value.fillif (ready.value && !assetSettingsModelInnerChange.value) {render?.setAssetSettings(assetCurrent.value, assetSettingsModel.value)}}assetSettingsModelInnerChange.value = false
}, {deep: true
})

这里有几个小细节:

  • 颜色选择器 confirm 确认

没有直接用 v-model 绑定表单的颜色值,而定义了一些类似 xxxSettingsModelYyy 变量,原因是约束修改颜色必须通过 confirm 按钮才能使其颜色生效,需要一些变量作为缓存。

因此也多了一些初始化和同步赋值逻辑,看起来凌乱一些。

  • Tab自动切换

默认显示页面属性面板,选择单个素材(暂时只实现 svg 素材),切换至素材属性面板,清空选择则回到页面属性面板。

  • watch 逻辑锁

在监听 pageSettingsModel 的时候,需要判断 pageSettingsModelInnerChange 的状态,解决了因为 Render 的 上一步、下一步、导入 等操作,触发 page-settings-change 事件(自定义事件,后面细说),会改变 pageSettingsModel 的值,以此防止 重复的 setPageSettings(后面细说) 逻辑。

类型、事件定义

属性面板 与 Render 属性同步,主要靠的是自定义事件,原有的 selection-change 事件,可以解决判断当前应该处理页面属性还是素材属性;需要新增一个 page-settings-change 事件,获知因为 Render 的 上一步、下一步、导入 等操作,需要更新 pageSettingsModel 到值。

// src/Render/types.ts// 略export type RenderEvents = {['history-change']: { records: string[]; index: number }['selection-change']: Konva.Node[]['debug-change']: boolean['link-type-change']: LinkType['scale-change']: number['loading']: boolean['graph-type-change']: GraphType | undefined// 新增['page-settings-change']: PageSettings
}// 略/*** 页面设置*/
export interface PageSettings {background: stringstroke: stringfill: string
}/*** 素材设置*/
export interface AssetSettings {stroke: stringfill: string
}

属性默认值、获取属性值、设置属性值

这里是通过把页面属性、素材属性分别存放在 stage 和 素材group 的 attrs 中,pageSettings 和 assetSettings。

// src/Render/index.ts// 略// 页面设置 默认值static PageSettingsDefault: Types.PageSettings = {background: 'transparent',stroke: 'rgb(0,0,0)',fill: 'rgb(0,0,0)'}// 获取页面设置getPageSettings(): Types.PageSettings {return this.stage.attrs.pageSettings ?? { ...Render.PageSettingsDefault }}// 更新页面设置setPageSettings(settings: Types.PageSettings) {this.stage.setAttr('pageSettings', settings)// 更新背景this.updateBackground()// 更新历史this.updateHistory()// console.log(this.stage.attrs)}// 获取背景getBackground() {return this.draws[Draws.BgDraw.name].layer.findOne(`.${Draws.BgDraw.name}__background`) as Konva.Rect}// 更新背景updateBackground() {const background = this.getBackground()if (background) {background.fill(this.getPageSettings().background ?? 'transparent')}this.draws[Draws.BgDraw.name].draw()this.draws[Draws.PreviewDraw.name].draw()}// 素材设置 默认值static AssetSettingsDefault: Types.AssetSettings = {stroke: '',fill: ''}// 获取素材设置getAssetSettings(asset?: Konva.Node): Types.AssetSettings {const base = asset?.attrs.assetSettings ?? { ...Render.AssetSettingsDefault }return {// 特定...base,// 继承全局stroke: base.stroke || this.getPageSettings().stroke,fill: base.fill || this.getPageSettings().fill}}// 设置 svgXML 样式(部分)setSvgXMLSettings(xml: string, settings: Types.AssetSettings) {const reg = /<(circle|ellipse|line|path|polygon|rect|text|textPath|tref|tspan)[^>/]*\/?>/gconst shapes = xml.match(reg)const regStroke = / stroke="([^"]*)"/const regFill = / fill="([^"]*)"/for (const shape of shapes ?? []) {let result = shapeif (settings.stroke) {if (regStroke.test(shape)) {result = result.replace(regStroke, ` stroke="${settings.stroke}"`)} else {result = result.replace(/(<[^>/]*)(\/?>)/, `$1 stroke="${settings.stroke}" $2`)}}if (settings.fill) {if (regFill.test(shape)) {result = result.replace(regFill, ` fill="${settings.fill}"`)} else {result = result.replace(/(<[^>/]*)(\/?>)/, `$1 fill="${settings.fill}" $2`)}}xml = xml.replace(shape, result)}return xml}// 更新素材设置async setAssetSettings(asset: Konva.Node, settings: Types.AssetSettings) {asset.setAttr('assetSettings', settings)if (asset instanceof Konva.Group) {const node = asset.children[0] as Konva.Shapeif (node instanceof Konva.Image) {if (node.attrs.svgXML) {const n = await this.assetTool.loadSvgXML(this.setSvgXMLSettings(node.attrs.svgXML, settings))node.parent?.add(n)node.remove()node.destroy()n.zIndex(0)}}}this.draws[Draws.BgDraw.name].draw()this.draws[Draws.GraphDraw.name].draw()this.draws[Draws.LinkDraw.name].draw()this.draws[Draws.PreviewDraw.name].draw()}

这里素材的线条、填充默认值,是会继承页面的线条、填充值的,就是说,拖入的素材线条、填充值,会按当前页面的值初始化。

getBackground

这里获取的背景是一个放在网格线同 Layer 的 Rect,用于模拟页面背景的:

// src/Render/draws/BgDraw.ts// 略group.add(new Konva.Rect({name: `${this.constructor.name}__background`,x: this.render.toStageValue(-stageState.x + this.render.rulerSize),y: this.render.toStageValue(-stageState.y + this.render.rulerSize),width: this.render.toStageValue(stageState.width),height: this.render.toStageValue(stageState.height),listening: false,fill: this.render.getPageSettings().background}))// 略

这里说“模拟”的意思是,背景最后是在 导入、导出 的时候才真正的处理:

// 恢复async restore(json: string, silent = false) {try {// 略// 往 main layer 插入新节点this.render.layer.add(...nodes)// 同步页面设置this.render.stage.setAttr('pageSettings', stage.attrs.pageSettings)this.render.emit('page-settings-change', this.render.getPageSettings())// 更新背景this.render.updateBackground()// 略} catch (e) {console.error(e)} finally {// 略}}// 略// 获取元素图片getAssetImage(pixelRatio = 1, bgColor?: string) {// 略bg.setAttrs({x: -copy.x(),y: -copy.y(),width: copy.width(),height: copy.height(),fill: bgColor ?? this.render.getPageSettings().background})// 略}// 略// 获取Svgasync getSvg() {// 略// 获得 svglet rawSvg = c2s.getSerializedSvg()console.log(rawSvg)// 添加背景rawSvg = rawSvg.replace(/(<defs\/><g><rect fill=")([^"]+)(")/,`$1${this.render.getPageSettings().background}$3`)// 略}// 略}// 略/*** 获得元素(用于另存为元素)* @returns Konva.Stage*/getAsset() {const copy = this.getAssetView()// 添加背景const background = this.render.getBackground()background.width(copy.width())background.height(copy.height())copy.children[0].add(background)background.moveToBottom()// 略}

分别说说处理的思路:

  • 导出图片

在 toDataURL 之前在添加背景 Rect。

  • 导出 svg

这里的思路是,通过正则表达式替换 svg xml 内容,修改上面提到的 背景 Rect 对应的 svg xml rect 结构。

  • 导出素材 json

虽然这里也是添加背景 Rect,不同之处是,该层与其他素材同级,像似一个内部素材。

  • 导入 json

通过 stage 的 attrs 中 pageSettings 属性记录,通过事件 page-settings-change 恢复外部表单 model 的值。并同时更新 背景 Rect 的颜色。

setAssetSettings、setSvgXMLSettings

可以看到这里看起来明显有点复杂,由于素材 svg 最终是以 Konva.Image 的方式加载的,所以唯一可以影响显示的线条、填充颜色,只能在加载之前,通过替换 svg xml 实现。

替换 svg xml 分4步:
1、通过 attrs 取出 svgXml 的值;
2、通过正则表达式替换/插入线条、填充颜色值;
3、生成新的 Image 替换原来的 Image;
4、恢复新的 Image 的 zIndex(置顶);

替换 svg xml 思路比较简单粗暴,就是把可能的节点 circle|ellipse|line|path|polygon|rect|text|textPath|tref|tspan,识别提取出来,进行 stroke、fill 的替换/插入。

恢复加载 svg 素材的时候,也处理一遍:

// src/Render/tools/AssetTool.ts// 略// 加载 svgasync loadSvg(src: string) {const svgXML = await (await fetch(src)).text()return this.loadSvgXML(this.render.setSvgXMLSettings(svgXML, this.render.getAssetSettings()))}// 略

上面说到,拖入的 svg 素材,会基于 页面的线条、填充值,所以拖入的时候也要处理一下:

// src/Render/handlers/DragOutsideHandlers.ts// 略drop: (e: GlobalEventHandlersEventMap['drop']) => {// 略let group = null// 默认连接点let points: Types.AssetInfoPoint[] = []// 图片素材if (target instanceof Konva.Image) {group = new Konva.Group({id: nanoid(),width: target.width(),height: target.height(),name: 'asset',assetType: Types.AssetType.Image,draggable: true,imageType:type !== 'json'? type === Types.ImageType.svg? Types.ImageType.svg: type === Types.ImageType.gif? Types.ImageType.gif: Types.ImageType.other: undefined})this.render.setAssetSettings(group, this.render.getAssetSettings())// 略} else {// json 素材// 略}// 略})}}}// 略

说到这里,基本实现了页面属性、素材属性及其继承关系的实现(还有很多优化空间)啦!

Thanks watching~

More Stars please!勾勾手指~

源码

gitee源码

示例地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1547598.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

SQL常用数据过滤 - EXISTS运算符

SQL查询中的EXISTS运算符用于检查查询子句是否存在满足特定条件的记录&#xff0c;如果有一条或者多条记录存在&#xff0c;则返回True&#xff0c;否则返回False。 语法结构 SELECT column_name(s)FROM table_nameWHERE EXISTS(SELECT column_name FROM table_name WHERE co…

C++实现二叉树的创建删除,dfslfs,求叶子结点个数,求叶子结点个数,求树的高度

C实现二叉树的创建删除&#xff0c;dfs/lfs,求叶子结点个数&#xff0c;求树的高度 基本算法&#xff1a; 用链栈建立二叉树&#xff0c;通过递归实现深度优先的三种遍历&#xff0c;用队列实现广度优先层次遍历。借助递归思想求解叶子结点个数和树的深度。 tree.h定义基本的…

TMR技术的发展及其应用技术的介绍

目录 概述 1 TMR传感器介绍 1.1 原理介绍 1.2 技术演进历史 2 TMR技术的应用 2.1 电阻特性 2.2 技术比较 2.3 磁道特性 3 多维科技的芯片&#xff08;TMR1202&#xff09; 3.1 芯片介绍 3.2 特性 ​3.3 典型应用 参考文献 概述 本文主要介绍TMR技术的发展及其技术…

PyTorch构建卷积神经网络(CNN)训练模型:分步指南

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

基于卷积神经网络的体育运动项目分类识别系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 随着计算机视觉和深度学习技术的快速发展&#xff0c;利用先进的图像处理技术对体育运动进行智能分类与识别已成为研究热点。传统的运动分析方法通常依赖于人工观察和记录&#xff0c;耗时耗力且容…

Golang | Leetcode Golang题解之第440题字典序的第K小数字

题目&#xff1a; 题解&#xff1a; func getSteps(cur, n int) (steps int) {first, last : cur, curfor first < n {steps min(last, n) - first 1first * 10last last*10 9}return }func findKthNumber(n, k int) int {cur : 1k--for k > 0 {steps : getSteps(cu…

基于SSM+小程序的在线课堂微信管理系统(在线课堂1)(源码+sql脚本+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 &emsp1、管理员实现了首页、个人中心、用户管理、课程分类管理、课程信息管理、课程订阅管理、课程视频管理、公告栏管理、留言板管理、系统管理。 2、用户实现了首页、课程信息、公…

Windows系统下批量重命名文件的两种实现方法

我们如果有一批文件&#xff0c;想要大批的重命名文件。例如&#xff0c;将下面的这些图片重命名为boot_itc_00001.jpg、boot_itc_00002.jpg、……、boot_itc_01000.jpg。总不能一个一个改吧&#xff1f; 第一种方法&#xff08;也是最灵活的一种&#xff09;&#xff1a; 借助…

机器学习-KNN分类算法

1.1 KNN分类 KNN分类算法&#xff08;K-Nearest-Neighbors Classification&#xff09;&#xff0c;又叫K近邻算法。它是概念极其简单&#xff0c;而效果又很优秀的分类算法。1967年由Cover T和Hart P提出。 KNN分类算法的核心思想&#xff1a;如果一个样本在特征空间中的k个最…

IIs站点发布ERR_UNSAFE_PORT

换个端口&#xff0c;谢谢&#xff01; nice 浏览器对部分端口有特定的保护机制&#xff0c;如果你的应用使用了这些端口&#xff0c;浏览器在发送请求时会触发保护机制&#xff0c;拒绝发送请求&#xff0c;于是&#xff0c;你的服务器应用自然就收不到请求了。 1, // …

树莓派基础命令

目录 1.树莓派简介 2.树莓派使用命令 3.树莓派包管理 4.关于远程连接树莓派的思路&#xff1a; 5.总结 1.树莓派简介 树莓派&#xff08;Raspberry Pi&#xff09;是一款由英国非营利组织树莓派基金会开发的小型、低成本的单板计算机&#xff0c;最初设计目的是为了让学生…

好看的首页展示

代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style>/* RESET…

【BUG】静读天下|静读天下无法设置段间距解决方案

【BUG】静读天下&#xff5c;静读天下无法设置段间距解决方案 文章目录 【BUG】静读天下&#xff5c;静读天下无法设置段间距解决方案前言解决办法 凑质量分静读天下的特点与优势功能布局与使用技巧个人使用心得结语 前言 03-23 求助&#xff5c;关于排版的问题【静读天下吧】_…

数字孪生平台,助力制造设备迈入超感知与智控新时代!

痛点剖析 当前&#xff0c;制造业面临系统分散导致的数据孤岛问题&#xff0c;严重阻碍了有效监管与统计分析&#xff1b;同时&#xff0c;设备多样化且兼容性不足&#xff0c;增加了管理难度&#xff1b;台账记录方式混乱&#xff0c;工单审批流程繁琐且效率低下&#xff1b;…

Stable Diffusion零基础学习

Stable Diffusion学习笔记TOP11 _插件篇之ControlNet功能篇 ControlNet目前支持的10多种预处理器&#xff0c;根据数据检测种类可分为两种类型&#xff1a; 1、功能型&#xff1a;拥有着不同的能力 2、构图型&#xff1a;控制着SD扩散图形的构图规则 Shuffle洗牌/转换&#…

基于Ubuntu 20.04 LTS上部署MicroK8s(最小生产的 Kubernetes)

目录 文章目录 目录简介Kubernetes简介MicroK8s简介Ubuntu系统MicroK8s的优势安装环境基本要求执行安装命令加入群组(使用非 root 用户访问)开启 dashboard 仪表盘查看服务名称查看仪表盘开放的端口打开浏览器检查状态打开你想要的服务(使用附加组件)开始使用 microk8s访问 Kub…

【【通信协议之ICMP协议的FPGA实现】】

通信协议之ICMP协议的FPGA实现 整体的实现框图如下所示 arp_rx.v module arp_rx#(//开发板MAC地址 00-11-22-33-44-55parameter BOARD_MAC 48h00_11_22_33_44_55, //开发板IP地址 192.168.1.10 parameter BOARD_IP {8d192,8d168,8d1,8d10} )(input …

RFID手持机——物联网时代的核心工具

一、行业背景 在当今物联网技术高速发展的时代&#xff0c;RFID技术作为核心的数据采集与识别手段&#xff0c;在物流、仓储、资产管理等众多领域发挥着至关重要的作用。以物流行业为例&#xff0c;利用RFID技术能够对货物进行全程精准跟踪&#xff0c;从入库、存储、搬运到出…

Keepalived+Nginx 高可用集群(双主模式)

1.基础环境配置 [rootlb1 ~]# systemctl stop firewalld # 关闭防火墙 [rootlb1 ~]# sed -i s/^SELINUX.*/SELINUXdisabled/ /etc/sysconfig/selinux # 关闭selinux&#xff0c;重启生效 [rootlb1 ~]# setenforce 0          …

从Elasticsearch到RedisSearch:探索更快的搜索引擎解决方案

文章目录 RedisSearch 的关键功能与 ElasticSearch 对比性能对比产品对比 如何使用 Docker 安装 RedisSearch1. 获取 RedisSearch Docker 镜像2. 启动 RedisSearch 容器3. 验证安装 RedisSearch 使用示例1. 连接到 RedisSearch2. 创建索引3. 添加文档4. 执行搜索搜索所有包含 &…