透从零实现富文本编辑器#8-浏览器输入模式的非受控DOM行为j

揭椅臃叵IQueryable FromExpression(Expression>> expression)

看着好像很复杂的样子。其实不,咱们来拆解一下:

1、TResult 是类型参数(泛型的知识点没忘吧),这里其实指的就是实体类型,比如,你的爱狗 Dog。

2、这个方法返回 IQueryable 类型,说明允许你使用 LINQ 查询。

3、重点理解其参数——Expression 表达有个万能规律:可以把与 TDelegate 类型兼容的 lambda 表达式直接赋值给 Expression<> 变量。即这个 FromExpression 方法可以使用以下 lambda 表达式作为参数:

() => [返回 IQueryable]

这个委托的意思就是:它,单身狗(无参数)一枚,但可以生产 IQueryable 对象。

哦,说了一大堆,还没说这个方法到底有啥毛用。它的用处就是你可以指定一个表达式,让 EF 一开始就返回筛选过的查询。在 DbContext 的派生类中声明 DbSet 类型的带 get + set 的公共属性这种生成首个查询(根查询)是最常用的方案,这个相信大伙们都很熟了,EF Core 最基础操作,老周就不多介绍了。假如你要对查询的初始数据做筛选,那么,按照 DbSet 的方案,要先执行一下 SELECT **** FROM #$$#*& 语句,然后再执行 SELECT **** FROM &*^$ WHERE xxxxx,我还没操作数据呢就执行了两次 SELECT 语句了。所以说,如果你一开始并不打算提取所有数据,那么直接从一开始就执行 SELECT **** FROM xxxx WHERE yyyy 多好,何必多浪费一条 SQL 语句?

还有一种使用场景:你的数据不是从某个表 SELECT 出来的,而是从一个表值函数返回的,这种情况也要借助 FromExpression 方法。

不知道老周以上说明你是否明白?不明白没关系,咱们实战一下你就懂了。

咱们先定义的实体:

复制代码

///

/// 妖书实体

///

public class Book

{

///

/// 标识 + 主键

///

public Guid BookId { get; set; }

///

/// 书名

///

public string Title { get; set; } = string.Empty;

///

/// 简介

///

public string? Description { get; set; }

///

/// 作者

///

public string Author { get; set; } = string.Empty;

}

复制代码

然后,继承 DbContext 类,常规操作。

复制代码

public class MyDbContext : DbContext

{

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

{

optionsBuilder.UseSqlServer("Server=.\\TEST;Database=mydb;Integrated Security=True;Trust Server Certificate=True");

// 配置日志

//.LogTo(log => Debug.WriteLine(log));

}

protected override void OnModelCreating(ModelBuilder modelBuilder)

{

modelBuilder.Entity(t =>

{

t.ToTable("books", tb =>

{

tb.Property(x => x.BookId).HasColumnName("book_id");

tb.Property(z=>z.Title).HasColumnName("title");

tb.Property(k => k.Description).HasColumnName("desc");

tb.Property(f => f.Author).HasColumnName("author");

})

.HasKey(t => t.BookId);

});

}

// 以下行现在不需要了

//public DbSet Books { get; set; }

public IQueryable MyBooks

=> FromExpression(() => Set().Where(x => x.Author == "老周"));

}

复制代码

这里不用再定义 DbSet<> 类型的属性了,因为我们要对数据进行筛选,重点看 MyBooks 属性的实现:

public IQueryable MyBooks

=> FromExpression(() => Set().Where(x => x.Author == "老周"));

Set() 方法的调用会让 DbContext 自动在缓存字典中添加数据集合,然后一句 Where 做出筛选,上述代码的意思是只查询老周写的妖书,其他作者的不考虑。这时候 DbContext 不会发出 select * from xxx SQL 语句,所以你不用担心执行多余的 SQL。调用 FromExpression 方法后,会使初始查询直接生成 Select * from xxx where ...... 语句,只查询一次。

现在往 SQL Server 中新建 mydb 数据库,并创建 books 表。

复制代码

CREATE TABLE [dbo].[books] (

[book_id] UNIQUEIDENTIFIER DEFAULT (newid()) NOT NULL,

[title] NVARCHAR (35) NOT NULL,

[desc] NVARCHAR (100) NULL,

[author] NVARCHAR (20) NOT NULL,

PRIMARY KEY CLUSTERED ([book_id] ASC)

);

复制代码

顺便向表中插入些测试数据。

insert into books (title, author, [desc])

values (N'R语言从盗墓到考古', N'张法师', N'一套不正经的R语言退隐教程'),

(N'疯言疯语之HTML 6.0', N'老周', N'提前一个世纪出现的超文本协议'),

(N'程序员风水学', N'孙电鹰', N'先匪后将的他,曾有“东陵大盗”之称,在盗掘过程中他学会了用风水理论去Debug项目'),

(N'鸡形机器人编程入门', N'老周', N'未来,由于长期不种植作物,人类只能躺在病床上依靠吮吸预制营养液维持生命;后有人提出开发鸡形机器人,帮助人类进食')

现在,咱们试一下。

using var context = new MyDbContext();

foreach(Book bk in context.MyBooks)

{

Console.WriteLine($"{bk.Author,-10}{bk.Title}");

}

如果日志启用,那么,你会看到,DbContext 从初始化到 foreach 循环访问数据,只生成了一条 SQL 语句。

SELECT [b].[book_id], [b].[author], [b].[desc], [b].[title]

FROM [books] AS [b]

WHERE [b].[author] = N'老周'

下面来看看另一种应用情形——映射表值函数。

先在 SQL Server 中创建一个内联表值函数,名为 get_all_books,返回表中所有行。

CREATE FUNCTION [dbo].[get_all_books]()

RETURNS TABLE

RETURN select * from dbo.books;

回到 .NET 项目,咱们要映射一下函数。

A、先在 DbContext 的派生类中定义一个方法,用于映射到函数,不需要实现方法体,直接抛异常就行。

internal IQueryable GetAllBooksMap()

{

throw new NotSupportedException();

}

实际上,EF Core 并不会真正调用方法,只是通过生成表达式树 + 反射出方法名,然后再找到与方法名对应的数据库中的函数罢了。所以,方法不需要实现代码。

B、OnModelCreating 方法要改一下,映射列名的 HasColumnName 方法不能在 ToTable 方法中配置,否则表值函数返回的实体不能正确映射。

复制代码

modelBuilder.Entity(t =>

{

t.ToTable("books");

t.HasKey(t => t.BookId);

t.Property(x => x.BookId).HasColumnName("book_id");

t.Property(z => z.Title).HasColumnName("title");

t.Property(k => k.Description).HasColumnName("desc");

t.Property(f => f.Author).HasColumnName("author");

});

复制代码

也就是列名映射要在 Property 上配置,不能在 TableBuilder 上配置。

C、HasDbFunction 映射函数。

// 注意数据库中的函数名与类方法不同

modelBuilder.HasDbFunction(GetType().GetMethod(nameof(GetAllBooksMap), BindingFlags.NonPublic)!).HasName("get_all_books");

这里有个误区:很多大伙伴以为这样就完事了,然后就开始调用代码了。

using var context = new MyDbContext();

foreach(Book bk in context.GetAllBooksMap())

{

Console.WriteLine($"{bk.Author,-10}{bk.Title}");

}

你以为这样是对的,但运行后就是错的。上面不是说了吗?GetAllBooksMap 方法是没有实现的,你不能直接调用它!不能调用,不能调用,不能调用!!

我们还需要再给 DbContext 的派生类再定义一个方法,使用 FromExpression 方法让 GetAllBooksMap 转为表达式树。

public IQueryable GetAllBooks()

{

return this.FromExpression(() => GetAllBooksMap());

}

这么一来,GetAllBooksMap() 就成了表达式树,EF 不会真的调用它,只是获取相关信息,再翻译成 SQL。

然后这样用:

using var context = new MyDbContext();

foreach(Book bk in context.GetAllBooks())

{

Console.WriteLine($"{bk.Author,-10}{bk.Title}");

}

看,四条记录就读出来了。

image

可是,你也发现了,这TM太麻烦了,为了表值函数映射,我要封装两个方法成员。其实,这里可以把两个方法合成一个:

public IQueryable GetAllBooks()

{

return this.FromExpression(() => GetAllBooks());

}

由于是公共方法,OnModelCreating 中的 HasDbFunction 代码也可以精简一下。

modelBuilder.HasDbFunction(GetType().GetMethod(nameof(GetAllBooks))!).HasName("get_all_books");

这时候你又搞不懂了,What?GetAllBooks 方法怎么自己调用了自己?不不不,没有的事,你又忘了,FromExpression 只是转换为表达式树,并不会真的调用它。所以,这样合并后,其实代码是这样走的:

1、访问 context.GetAllBooks() ,这时候,GetAllBooks 方法确实被调用了,是你的代码调用的,不是EF调用;

2、GetAllBooks 方法被你调用后,FromExpression 方法被调用;

3、FromExpression 方法参数中,lambda 表达式虽然又引用了一次 GetAllBooks 方法,但这一次它不会被调用,EF Core 只是用来获取方法名。

现在明白了吗?

对,微软官方文档中的示例用的就是这种合并的方法,表面上看好像自己调用了自己,实则不会。

好了,今天就水到这里。

内容概要:本文主要介绍了一种基于Matlab实现的交叉小波和小波相干性分析方法,旨在帮助科研人员通过Matlab代码实现信号交叉小波和小波相干性(Matlab代码实现)的时频域联合分析。交叉小波可用于分析两个平稳信号之间的局部相关性,而小波相干性则进一步揭示它们在不同频率和时间尺度上的相干程度,适用于气象、海洋、生物医学、电力系统等多领域的时间序列数据分析。文中提供了完整的Matlab代码示例,并结合实际应用场景展示其操作流程与结果可视化方式。; 适合人群:具备一定信号处理基础和Matlab编程能力的研究生、科研人员及工程技术人员,尤其适合从事时间序列分析、多变量信号相关性研究的相关领域工作者。; 使用场景及目标:①分析两个时间序列在时频域内的局部相关性和相位关系;②识别信号间的周期性耦合特征,如气候因子关联、脑电/心电信号交互、电力负荷与气象因素的关系等;③通过小波相干图直观展示变量间的动态关联强度与滞后关系,支撑科学决策与机理探究; 阅读建议:建议读者结合Matlab环境实际运行所提供的代码,理解小波变换、交叉小波与小波相干性的数学原理,并尝试将方法迁移至自身研究领域的数据集上进行验证与优化,同时注意参数设置(如小波基函数、边缘效应处理)对结果的影响。
<think>我们正在使用Vue-Quill-Editor插件,并希望将HTML内容正确转换为富文本展示。 步骤: 1. 安装Vue-Quill-Editor 2. 在Vue组件中引入并使用 3. 配置编辑器以显示HTML内容(注意:Quill编辑器使用自己的Delta格式,所以我们需要将HTML转换为Delta格式,或者直接使用HTML内容但要注意XSS攻击) 4. 注意:Quill编辑器默认不直接支持HTML字符串的绑定,我们需要通过设置content的方式,并且使用v-model绑定可能会导致问题,因为v-model绑定的是Delta格式。 有两种常见方法: 方法一:使用v-model绑定Delta格式(推荐) - 我们需要将HTML字符串转换为Delta格式,可以使用第三方库如quill-delta-to-html和html-to-delta,或者使用Quill自带的转换功能(Quill内置的转换器在quilljs.com) - 但是,Vue-Quill-Editor的v-model绑定的是Delta对象,而不是HTML字符串。 方法二:使用受控模式,通过设置编辑器内容(使用refs设置内容) - 我们可以通过ref获取编辑器实例,然后使用setContents方法设置Delta内容,或者使用pasteHTML方法设置HTML内容(但注意:pasteHTML是异步的,且可能不保留所有格式) 考虑到安全性和格式的完整性,通常的做法是: 1. 在服务器端存储HTML字符串。 2. 在客户端需要展示时,将HTML字符串转换为Delta格式(使用html-to-delta转换器),然后通过v-model或setContents设置给编辑器。 3. 或者,如果不需要编辑,可以直接使用v-html展示在编辑区域(但这样就不是使用Quill编辑器了)。 但是,我们的需求是在编辑器中展示富文本(可编辑或只读),所以我们需要在编辑器中显示。 步骤详解: 安装: ``` npm install vue-quill-editor --save ``` 在组件中: ```vue <template> <div class="quill-editor"> <quill-editor v-model:content="content" contentType="html" :options="editorOption" /> </div> </template> <script> import { quillEditor } from &#39;vue-quill-editor&#39; import &#39;quill/dist/quill.core.css&#39; import &#39;quill/dist/quill.snow.css&#39; import &#39;quill/dist/quill.bubble.css&#39; export default { components: { quillEditor }, data() { return { content: &#39;<p>Hello world!</p>&#39;, // 这里可以直接是HTML字符串 editorOption: { modules: { toolbar: [ [&#39;bold&#39;, &#39;italic&#39;, &#39;underline&#39;, &#39;strike&#39;], // 其他工具栏配置 ] } } } } } </script> ``` 注意:在vue-quill-editor的较新版本中(>=5.0.0),支持了通过`v-model:content`绑定,并且可以指定`contentType`为&#39;html&#39;或&#39;delta&#39;。这样,我们就可以直接绑定HTML字符串。 但是,如果我们的HTML字符串包含复杂的样式或格式,需要确保Quill的配置支持这些格式。否则,不支持的标签会被过滤。 因此,我们需要配置Quill的formats和modules/toolbar来支持我们需要的格式。 另外,为了防止XSS攻击,Quill默认会对HTML内容进行过滤。如果我们需要完全信任的HTML内容(例如来自自己的服务器),可以自定义一个安全的HTML导入方法,或者使用白名单。 如果使用旧版本的vue-quill-editor(比如4.x),则可能不支持直接绑定HTML,那么我们需要手动转换: 在旧版本中,我们可以使用以下方法: 1. 使用`v-model`绑定一个Delta对象,然后使用`html-to-delta`将HTML转换为Delta。 2. 或者,使用`ref`和`setContents`方法设置内容。 但是,我们推荐使用新版本(5.0.0以上)并直接使用`v-model:content`绑定HTML。 如果无法升级,则考虑以下方法(旧版本): ```vue <template> <div class="quill-editor"> <quill-editor ref="myQuillEditor" v-model="content" :options="editorOption" /> </div> </template> <script> import { quillEditor } from &#39;vue-quill-editor&#39; import Quill from &#39;quill&#39; import &#39;quill/dist/quill.core.css&#39; import &#39;quill/dist/quill.snow.css&#39; import &#39;quill/dist/quill.bubble.css&#39; // 可能需要引入转换函数,这里我们可以使用Quill的clipboard来转换 export default { components: { quillEditor }, data() { return { content: { ops: [] }, // Delta格式 htmlContent: &#39;<p>Hello world!</p>&#39;, // 从服务器获取的HTML editorOption: { ... } } }, mounted() { // 将HTML字符串转换为Delta const delta = this.convertHtmlToDelta(this.htmlContent) this.content = delta }, methods: { convertHtmlToDelta(html) { const quill = this.$refs.myQuillEditor.quill const delta = quill.clipboard.convert(html) return delta } } } </script> ``` 但是,这种方法需要在mounted中设置,并且需要确保编辑器已经初始化。 总结:为了简便,建议升级到vue-quill-editor@5.x,然后直接使用contentType="html"和v-model:content绑定HTML字符串。 如果不想升级,也可以使用以下方法(在旧版本中绑定HTML): - 使用一个计算属性,将HTML转换为Delta,然后绑定到v-model(但注意,这样在编辑器内容变化时,需要反向转换,比较复杂) - 或者,使用一个watch来监听HTML内容的变化,然后通过ref获取编辑器实例并设置内容。 考虑到复杂性和维护性,升级到5.x是最简单的。 另外,注意在展示时,如果希望编辑器只读,可以设置`editorOption`中的`readOnly`为`true`。 因此,步骤: 1. 安装最新版vue-quill-editor(5.x) 2. 在组件中引入,并使用`v-model:content`绑定,设置`contentType="html"` 3. 确保传入的HTML字符串是安全的,并且Quill配置支持其中的格式。 关于格式支持:可以在Quill的配置中通过formats数组指定允许的格式,例如: ```js editorOption: { modules: { toolbar: [ ... ] // 工具栏按钮 }, formats: [&#39;bold&#39;, &#39;italic&#39;, &#39;underline&#39;, &#39;strike&#39;, ...] // 允许的格式 } ``` 如果不指定,默认支持一些基础格式。如果HTML中有不支持的格式,则会被过滤。 最后,关于XSS:虽然Quill会过滤不安全的标签,但如果你完全信任HTML内容,并且需要展示一些复杂的标签(比如iframe、script等),则需要额外配置。但一般情况下,不建议允许执行脚本,所以最好避免。 因此,在配置中,我们可以通过自定义whitelist来允许安全的标签和属性,或者使用Quill的sanitize模块进行配置。 具体安全配置可以参考Quill文档:https://quilljs.com/docs/modules/clipboard/#sanitize 示例:允许特定的标签和属性 ```js const options = { modules: { toolbar: [ ... ], clipboard: { // 自定义sanitize函数或使用默认 // 默认已经有一个sanitize函数,我们可以覆盖它 sanitize: { &#39;tag1&#39;: true, // 允许标签&#39;tag1&#39; &#39;tag2&#39;: [&#39;attr1&#39;, &#39;attr2&#39;], // 允许标签&#39;tag2&#39;及其属性&#39;attr1&#39;,&#39;attr2&#39; ... } } } } ``` 但是,由于sanitize配置比较复杂,且默认配置已经比较安全,通常我们不需要修改,除有特殊需求。 因此,最终实现: 1. 安装vue-quill-editor(5.x) 2. 在组件中引入,并配置为绑定HTML 3. 设置编辑器的选项(包括只读、工具栏等) 示例代码(使用5.x): ```vue <template> <div> <quill-editor v-model:content="htmlContent" contentType="html" :options="editorOptions" /> </div> </template> <script> import { quillEditor } from &#39;vue-quill-editor&#39; export default { components: { quillEditor }, data() { return { htmlContent: &#39;<p>Your HTML content here</p>&#39;, // 初始HTML内容 editorOptions: { modules: { toolbar: null // 设置为null则不显示工具栏,即只读模式;如果需要编辑,则配置工具栏数组 }, readOnly: true, // 如果只需要展示,设置为true theme: &#39;snow&#39; // 或 &#39;bubble&#39; } } } } </script> ``` 注意:如果内容较多,编辑器高度可能不够,可以通过CSS设置: ```css .ql-editor { height: auto; min-height: 200px; } ``` 这样,HTML内容就会在Quill编辑器中以富文本形式展示。 如果遇到转换问题(比如某些样式丢失),可能是因为Quill不支持这些样式,需要检查formats配置,或者考虑在转换前清理HTML(使用类似DOMPurify的库)并调整为Quill支持的格式。 另外,如果HTML中包含图片,需要确保图片的URL是有效的,并且Quill允许图片(默认支持)。 最后,如果使用只读模式,也可以考虑使用`quill-display`组件(一个只读的Quill展示组件),但vue-quill-editor本身在只读模式下已经可以满足需求。 因此,按照上述步骤,即可在Vue项目中配置Vue-Quill-Editor将HTML内容正确转换为富文本展示。</think>在 Vue 项目中配置 Vue-Quill-Editor 将 HTML 内容转换为富文本展示,需要以下步骤: ### 1. 安装依赖 ```bash npm install vue-quill-editor quill@1.3.7 --save ``` ### 2. 创建自定义组件 在 `components` 目录下创建 `RichTextEditor.vue`: ```vue <template> <div class="editor-container"> <!-- 编辑模式 --> <quill-editor v-if="editable" ref="editor" v-model:value="content" :options="editorOption" @blur="onEditorBlur" /> <!-- 只读模式 --> <div v-else class="ql-container ql-snow"> <div class="ql-editor" v-html="content"></div> </div> </div> </template> <script> import { quillEditor } from &#39;vue-quill-editor&#39; import &#39;quill/dist/quill.core.css&#39; import &#39;quill/dist/quill.snow.css&#39; export default { components: { quillEditor }, props: { value: String, // 接收HTML内容 editable: { // 控制编辑/只读模式 type: Boolean, default: true } }, data() { return { content: this.value, editorOption: { modules: { toolbar: [ [&#39;bold&#39;, &#39;italic&#39;, &#39;underline&#39;, &#39;strike&#39;], [&#39;blockquote&#39;, &#39;code-block&#39;], [{ &#39;list&#39;: &#39;ordered&#39;}, { &#39;list&#39;: &#39;bullet&#39; }], [{ &#39;indent&#39;: &#39;-1&#39;}, { &#39;indent&#39;: &#39;+1&#39; }], [{ &#39;header&#39;: [1, 2, 3, 4, 5, 6, false] }], [{ &#39;color&#39;: [] }, { &#39;background&#39;: [] }], [&#39;link&#39;, &#39;image&#39;], [&#39;clean&#39;] ] }, placeholder: &#39;请输入内容...&#39;, theme: &#39;snow&#39; } } }, watch: { value(newVal) { if (newVal !== this.content) { this.content = newVal } } }, methods: { onEditorBlur() { this.$emit(&#39;update:value&#39;, this.content) } } } </script> <style scoped> .editor-container { margin-bottom: 20px; } .ql-container { border: none !important; } .ql-editor { padding: 0 !important; } </style> ``` ### 3. 在父组件中使用 ```vue <template> <div> <rich-text-editor v-model:value="htmlContent" :editable="isEditable" /> <button @click="toggleMode"> {{ isEditable ? &#39;预览模式&#39; : &#39;编辑模式&#39; }} </button> </div> </template> <script> import RichTextEditor from &#39;./components/RichTextEditor.vue&#39; export default { components: { RichTextEditor }, data() { return { htmlContent: &#39;<p>Hello <strong>Vue-Quill-Editor</strong>!</p><ul><li>列表项1</li><li>列表项2</li></ul>&#39;, isEditable: true } }, methods: { toggleMode() { this.isEditable = !this.isEditable } } } </script> ``` ### 关键配置说明 1. **HTML 转换原理**: - Quill 使用自己的 Delta 格式存储内容 - 通过 `v-html` 指令直接渲染 HTML 字符串 - Quill 的 CSS 样式会自动应用到渲染内容 2. **模式切换**: - `editable` 属性控制编辑/只读模式 - 编辑模式使用 `<quill-editor>` 组件 - 只读模式使用原生 Quill 容器样式 3. **样式优化**: ```css .ql-container { border: none !important; /* 移除只读模式边框 */ } .ql-editor { padding: 0 !important; /* 移除内边距 */ } ``` 4. **数据同步**: - 使用 `v-model:value` 实现双向绑定 - 通过 `update:value` 事件更新父组件数据 - 在 blur 事件时触发更新保证实时性 ### 常见问题解决方案 1. **XSS 安全防护**: ```js // 安装DOMPurify npm install dompurify // 在组件中使用 import DOMPurify from &#39;dompurify&#39; // 清理HTML内容 this.content = DOMPurify.sanitize(this.value) ``` 2. **图片自适应**: ```css .ql-editor img { max-width: 100%; height: auto; } ``` 3. **自定义字体大小**: ```js // 在editorOption中添加 formats: [&#39;font&#39;, &#39;size&#39;], modules: { toolbar: [ [{&#39;size&#39;: [&#39;small&#39;, false, &#39;large&#39;, &#39;huge&#39;]}] ] } ``` ### 性能优化建议 1. 使用 `v-if` 替代 `v-show` 彻底销毁编辑器实例 2. 对大量内容使用虚拟滚动 3. 使用防抖处理内容更新: ```js import { debounce } from &#39;lodash&#39; methods: { onEditorChange: debounce(function() { this.$emit(&#39;update:value&#39;, this.content) }, 500) } ``` [^1]: Quill 官方文档中关于 Delta 格式的说明 [^2]: Vue-Quill-Editor GitHub 仓库中的使用示例
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值