基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(八)

系列文章

  1. 使用 abp cli 搭建项目

  2. 给项目瘦身,让它跑起来

  3. 完善与美化,Swagger登场

  4. 数据访问和代码优先

  5. 自定义仓储之增删改查

  6. 统一规范API,包装返回模型

  7. 再说Swagger,分组、描述、小绿锁

  8. 接入GitHub,用JWT保护你的API

  9. 异常处理和日志记录

  10. 使用Redis缓存数据

  11. 集成Hangfire实现定时任务处理

  12. 用AutoMapper搞定对象映射

  13. 定时任务最佳实战(一)

  14. 定时任务最佳实战(二)

  15. 定时任务最佳实战(三)

  16. 博客接口实战篇(一)

  17. 博客接口实战篇(二)

  18. 博客接口实战篇(三)

  19. 博客接口实战篇(四)

  20. 博客接口实战篇(五)

  21. Blazor实战系列(一)

  22. Blazor实战系列(二)

  23. Blazor实战系列(三)

  24. Blazor实战系列(四)

  25. Blazor实战系列(五)

  26. Blazor实战系列(六)

  27. Blazor实战系列(七)


上一篇完成了标签模块和友情链接模块的所有功能,本篇来继续完成博客最后的模块,文章的管理。

文章列表&删除

先将分页查询的列表给整出来,这块和首页的分页列表是类似的,就是多了个Id字段。

添加两条路由规则。

@page "/admin/posts"
@page "/admin/posts/{page:int}"

新建返回数据默认QueryPostForAdminDto.cs

//QueryPostForAdminDto.cs
using System.Collections.Generic;namespace Meowv.Blog.BlazorApp.Response.Blog
{public class QueryPostForAdminDto{/// <summary>/// 年份/// </summary>public int Year { get; set; }/// <summary>/// Posts/// </summary>public IEnumerable<PostBriefForAdminDto> Posts { get; set; }}
}//PostBriefForAdminDto.cs
namespace Meowv.Blog.BlazorApp.Response.Blog
{public class PostBriefForAdminDto : PostBriefDto{/// <summary>/// 主键/// </summary>public int Id { get; set; }}
}

然后添加所需的参数:当前页码、限制条数、总页码、文章列表返回数据模型。

/// <summary>
/// 当前页码
/// </summary>
[Parameter]
public int? page { get; set; }/// <summary>
/// 限制条数
/// </summary>
private int Limit = 15;/// <summary>
/// 总页码
/// </summary>
private int TotalPage;/// <summary>
/// 文章列表数据
/// </summary>
private ServiceResult<PagedList<QueryPostForAdminDto>> posts;

然后在初始化函数OnInitializedAsync()中调用API获取文章数据.

/// <summary>
/// 初始化
/// </summary>
protected override async Task OnInitializedAsync()
{var token = await Common.GetStorageAsync("token");Http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");// 设置默认值page = page.HasValue ? page : 1;await RenderPage(page);
}/// <summary>
/// 点击页码重新渲染数据
/// </summary>
/// <param name="page"></param>
/// <returns></returns>
private async Task RenderPage(int? page)
{// 获取数据posts = await Http.GetFromJsonAsync<ServiceResult<PagedList<QueryPostForAdminDto>>>($"/blog/admin/posts?page={page}&limit={Limit}");// 计算总页码TotalPage = (int)Math.Ceiling((posts.Result.Total / (double)Limit));
}

在初始化中判断page参数,如果没有值给他设置一个默认值1。RenderPage(int? page)方法是调用API返回数据,并计算出总页码值。

最后在页面上进行数据绑定。

<AdminLayout>@if (posts == null){<Loading />}else{<div class="post-wrap archive"><NavLink style="float:right" href="/admin/post"><h3>????~~~ 新增文章 ~~~????</h3></NavLink>@if (posts.Success && posts.Result.Item.Any()){@foreach (var item in posts.Result.Item){<h3>@item.Year</h3>@foreach (var post in item.Posts){<article class="archive-item"><NavLink title="❌删除" @onclick="@(async () => await DeleteAsync(post.Id))">❌</NavLink><NavLink title="????编辑" @onclick="@(async () => await Common.NavigateTo($"/admin/post/{post.Id}"))">????</NavLink><NavLink target="_blank" class="archive-item-link" href="@("/post" + post.Url)">@post.Title</NavLink><span class="archive-item-date">@post.CreationTime</span></article>}}<nav class="pagination">@for (int i = 1; i <= TotalPage; i++){var _page = i;if (page == _page){<span class="page-number current">@_page</span>}else{<a class="page-number" @onclick="@(() => RenderPage(_page))" href="/admin/posts/@_page">@_page</a>}}</nav>}else{<ErrorTip />}</div>}
</AdminLayout>

HTML内容放在组件AdminLayout中,当 posts 没加载完数据的时候显示加载组件<Loading />

在页面上循环遍历文章数据和翻页页码,每篇文章标题前面添加两个按钮删除和编辑,同时单独加了一个新增文章的按钮。

删除文章调用DeleteAsync(int id)方法,需要传递参数,当前文章的id。

新增和编辑按钮都跳转到"/admin/post"页面,当编辑的时候将id也传过去即可,路由规则为:"/admin/post/{id}"。

删除文章``方法如下:

/// <summary>
/// 删除文章
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
private async Task DeleteAsync(int id)
{// 弹窗确认bool confirmed = await Common.InvokeAsync<bool>("confirm", "\n????????真的要干掉这篇该死的文章吗????????");if (confirmed){var response = await Http.DeleteAsync($"/blog/post?id={id}");var result = await response.Content.ReadFromJsonAsync<ServiceResult>();if (result.Success){await RenderPage(page);}}
}

删除之前进行二次确认,避免误删,当确认删除之后调用删除文章API,最后重新渲染数据即可。

新增&更新文章

完成了后台文章列表的查询和删除,现在整个博客模块功能就差新增和更新文章了,胜利就在前方,冲啊。

这块的开发工作耗费了我太多时间,因为想使用 markdown 来写文章,找了一圈下来没有一个合适的组件,所以退而求次只能选择现有的markdown编辑器来实现了。

我这里选择了开源的编辑器Editor.md,有需要的可以去 Github 自己下载,https://github.com/pandao/editor.md 。

将下载的资源包解压放在 wwwroot 文件夹下,默认是比较大的,而且还有很多示例文件,我已经将其精简了一番,可以去我 Github 下载使用。

先来看下最终的成品效果吧。

是不是感觉还可以,废话不多说,接下里告诉大家如何实现。

在 Admin 文件夹下添加post.razor组件,设置路由,并且引用一个样式文件,在页面中引用样式文件好像不太符合标准,不过无所谓了,这个后台就自己用,而且还就这一个页面用得到。

@page "/admin/post"
@page "/admin/post/{id:int}"<link href="./editor.md/css/editormd.css" rel="stylesheet" /><AdminLayout>...
</AdminLayout>

把具体HTML内容放在组件AdminLayout中。

因为新增和编辑放在同一个页面上,所以当id参数不为空的时候需要添加一个id参数,同时默认一进来就让页面显示加载中的组件,当页面和数据加载完成后在显示具体的内容,所以在指定一个布尔类型的是否加载参数isLoading

我们的编辑器主要依赖JavaScript实现的,所以这里不可避免要使用到JavaScript了。

app.js中添加几个全局函数。

switchEditorTheme: function () {editor.setTheme(localStorage.editorTheme || 'default');editor.setEditorTheme(localStorage.editorTheme === 'dark' ? 'pastel-on-dark' : 'default');editor.setPreviewTheme(localStorage.editorTheme || 'default');
},
renderEditor: async function () {await this._loadScript('./editor.md/lib/zepto.min.js').then(function () {func._loadScript('./editor.md/editormd.js').then(function () {editor = editormd("editor", {width: "100%",height: 700,path: './editor.md/lib/',codeFold: true,saveHTMLToTextarea: true,emoji: true,atLink: false,emailLink: false,theme: localStorage.editorTheme || 'default',editorTheme: localStorage.editorTheme === 'dark' ? 'pastel-on-dark' : 'default',previewTheme: localStorage.editorTheme || 'default',toolbarIcons: function () {return ["bold", "del", "italic", "quote", "ucwords", "uppercase", "lowercase", "h1", "h2", "h3", "h4", "h5", "h6", "list-ul", "list-ol", "hr", "link", "image", "code", "preformatted-text", "code-block", "table", "datetime", "html-entities", "emoji", "watch", "preview", "fullscreen", "clear", "||", "save"]},toolbarIconsClass: {save: "fa-check"},toolbarHandlers: {save: function () {func._shoowBox();}},onload: function () {this.addKeyMap({"Ctrl-S": function () {func._shoowBox();}});}});});});
},
_shoowBox: function () {DotNet.invokeMethodAsync('Meowv.Blog.BlazorApp', 'showbox');
},
_loadScript: async function (url) {let response = await fetch(url);var js = await response.text();eval(js);
}

renderEditor主要实现了动态加载JavaScript代码,将markdown编辑器渲染出来。这里不多说,都是Editor.md示例里面的代码。

为了兼容暗黑色主题,这里还加了一个切换编辑器主题的JavaScript方法,switchEditorTheme

_shoowBox就厉害了,这个方法是调用的.NET组件中的方法,前面我们用过了在Blazor中调用JavaScript,这里演示了JavaScript中调用Blazor中的组件方法。

现在将所需的几个参数都添加到代码中。

/// <summary>
/// 定义一个委托方法,用于组件实例方法调用
/// </summary>
private static Func<Task> action;/// <summary>
/// 默认隐藏Box
/// </summary>
private bool Open { get; set; } = false;/// <summary>
/// 修改时的文章Id
/// </summary>
[Parameter]
public int? Id { get; set; }/// <summary>
/// 格式化的标签
/// </summary>
private string tags { get; set; }/// <summary>
/// 默认显示加载中
/// </summary>
private bool isLoading = true;/// <summary>
/// 文章新增或者修改输入参数
/// </summary>
private PostForAdminDto input;/// <summary>
/// API返回的分类列表数据
/// </summary>
private ServiceResult<IEnumerable<QueryCategoryForAdminDto>> categories;

大家看看注释就知道参数是做什么的了。

现在我们在初始化函数中将所需的数据通过API获取到。

/// <summary>
/// 初始化
/// </summary>
/// <returns></returns>
protected override async Task OnInitializedAsync()
{action = ChangeOpenStatus;var token = await Common.GetStorageAsync("token");Http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");if (Id.HasValue){var post = await Http.GetFromJsonAsync<ServiceResult<PostForAdminDto>>($"/blog/admin/post?id={Id}");if (post.Success){var _post = post.Result;input = new PostForAdminDto{Title = _post.Title,Author = _post.Author,Url = _post.Url,Html = _post.Html,Markdown = _post.Markdown,CategoryId = _post.CategoryId,Tags = _post.Tags,CreationTime = _post.CreationTime};tags = string.Join(",", input.Tags);}}else{input = new PostForAdminDto(){Author = "阿星Plus",CreationTime = DateTime.Now};}categories = await Http.GetFromJsonAsync<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>>("/blog/admin/categories");// 渲染编辑器await Common.InvokeAsync("window.func.renderEditor");// 关闭加载isLoading = !isLoading;
}

action是一个异步的委托,在初始化中执行了ChangeOpenStatus方法,这个方法等会说,然后获取localStorage中token的值。

通过参数Id是否有值来判断当前是新增文章还是更新文章,如果有值就是更新文章,这时候需要根据id去将文章的数据拿到赋值给PostForAdminDto对象展示在页面上,如果没有可以添加几个默认值给PostForAdminDto对象。

因为文章需要分类和标签的数据,同时这里将分类的数据也查出来,标签默认是List列表,将其转换成字符串类型。

但完成上面操作后,调用JavaScript方法renderEditor渲染渲染编辑器,最后关闭加载,显示页面。

现在来看看页面。

<AdminLayout>@if (isLoading){<Loading />}else{<div class="post-box"><div class="post-box-item"><input type="text" placeholder="标题" autocomplete="off" @bind="@input.Title" @bind:event="oninput" @onclick="@(() => { Open = false; })" /><input type="text" placeholder="作者" autocomplete="off" @bind="@input.Author" @bind:event="oninput" @onclick="@(() => { Open = false; })" /></div><div class="post-box-item"><input type="text" placeholder="URL" autocomplete="off" @bind="@input.Url" @bind:event="oninput" @onclick="@(() => { Open = false; })" /><input type="text" placeholder="时间" autocomplete="off" @bind="@input.CreationTime" @bind:format="yyyy-MM-dd HH:mm:sss" @bind:event="oninput" @onclick="@(() => { Open = false; })" /></div><div id="editor"><textarea style="display:none;">@input.Markdown</textarea></div><Box OnClickCallback="@SubmitAsync" Open="@Open" ButtonText="发布"><div class="box-item"><b>分类:</b>@if (categories.Success && categories.Result.Any()){@foreach (var item in categories.Result){<label><input type="radio" name="category" value="@item.Id" @onchange="@(() => { input.CategoryId = item.Id; })" checked="@(item.Id == input.CategoryId)" />@item.CategoryName</label>}}</div><div class="box-item"></div><div class="box-item"><b>标签:</b><input type="text" @bind="@tags" @bind:event="oninput" /></div></Box></div>}
</AdminLayout>

添加了四个input框,分别用来绑定标题、作者、URL、时间,<div id="editor"></div>中为编辑器所需。

然后我这里还是把之前的弹窗组件搞出来了,执行逻辑不介绍了,在弹窗组件中自定义显示分类和标签的内容,将获取到的分类和标签绑定到具体位置。

每个分类都是一个radio标签,并且对应一个点击事件,点哪个就把当前分类的Id赋值给PostForAdminDto对象。

所有的input框都使用@bind@bind:event绑定数据和获取数据。

Box弹窗组件这里自定义了按钮文字,ButtonText="发布"

/// <summary>
/// 改变Open状态,通知组件渲染
/// </summary>
private async Task ChangeOpenStatus()
{Open = true;var markdown = await Common.InvokeAsync<string>("editor.getMarkdown");var html = await Common.InvokeAsync<string>("editor.getHTML");if (string.IsNullOrEmpty(input.Title) || string.IsNullOrEmpty(input.Url) ||string.IsNullOrEmpty(input.Author) || string.IsNullOrEmpty(markdown) ||string.IsNullOrEmpty(html)){await Alert();}input.Html = html;input.Markdown = markdown;StateHasChanged();
}/// <summary>
/// 暴漏给JS执行,弹窗确认框
/// </summary>
[JSInvokable("showbox")]
public static void ShowBox()
{action.Invoke();
}
/// <summary>
/// alert提示
/// </summary>
/// <returns></returns>
private async Task Alert()
{Open = false;await Common.InvokeAsync("alert", "\n????????好像漏了点什么吧????????");return;
}

现在可以来看看ChangeOpenStatus方法了,这个是改变当前弹窗状态的一个方法。为什么需要这个方法呢?

因为在Blazor中JavaScript想要调用组件内的方法,方法必须是静态的,那么只能通过这种方式去实现了,在静态方法是不能够直接改变弹窗的状态值的。

其实也可以不用这么麻烦,因为我在编辑器上自定义了一个按钮,为了好看一些所以只能曲折一点,嫌麻烦的可以直接在页面上搞个按钮执行保存数据逻辑也是一样的。

使用JSInvokableAttribute需要在_Imports.razor中添加命名空间@using Microsoft.JSInterop

ChangeOpenStatus中获取到文章内容:HTML和markdown,赋值给PostForAdminDto对象,要先进行判断页面上的几个参数是否有值,没值的话给出提示执行Alert()方法,最后使用StateHasChanged()通知组件其状态已更改。

Alert方法就是调用原生的JavaScriptalert方法,给出一个提示。

ShowBox就是暴漏给JavaScript的方法,使用DotNet.invokeMethodAsync('Meowv.Blog.BlazorApp', 'showbox');进行调用。

那么现在一切都正常进行的情况下,点击编辑器上自定义的保存按钮,页面上值不为空的情况下就会弹出我们的弹窗组件Box

最后在弹窗组件的回调方法中执行新增文章还是更新文章。

/// <summary>
/// 确认按钮点击事件
/// </summary>
/// <returns></returns>
private async Task SubmitAsync()
{if (string.IsNullOrEmpty(tags) || input.CategoryId == 0){await Alert();}input.Tags = tags.Split(",");var responseMessage = new HttpResponseMessage();if (Id.HasValue)responseMessage = await Http.PutAsJsonAsync($"/blog/post?id={Id}", input);elseresponseMessage = await Http.PostAsJsonAsync("/blog/post", input);var result = await responseMessage.Content.ReadFromJsonAsync<ServiceResult>();if (result.Success){await Common.NavigateTo("/admin/posts");}
}

打开弹窗后执行回调事件之前还是要判断值是否为空,为空的情况下还是给出alert提示,此时将tags标签还是转换成List列表,根据Id是否有值去执行新增数据或者更新数据,最终成功后跳转到文章列表页。

本篇到这里就结束了,主要攻克了在Blazor中使用Markdown编辑器实现新增和更新文章,这个系列差不多就快结束了,预计还有2篇的样子,感谢各位的支持。

开源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[数据结构-严蔚敏版]P95矩阵压缩-特殊矩阵的存储(对称矩阵,三角矩阵)

对称矩阵的存储&#xff1a; 代码如下: #include <iostream> using namespace std;int main() {int n;cin >> n;int *a;a new int[(n*(n 1)) / 2];for (int i 0; i < (n*(n 1)) / 2; i){cin >> a[i];}for (int i 1; i < n; i){for (int j 1; j…

微前端与项目实施方案研究

一、前言微前端(micro-frontends)是近几年在前端领域出现的一个新概念&#xff0c;主要内容是将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的小块&#xff0c;而在用户看来仍然是内聚的单个产品。微前端的理念源于微服务&#xff0c;是将庞大的整体拆成可控的小…

ASP.NET Core分布式项目实战(集成ASP.NETCore Identity)--学习笔记

任务24&#xff1a;集成ASP.NETCore Identity之前在 Index 页面写了一个 strong 标签&#xff0c;需要加个判断再显示&#xff0c;不然为空没有错误的时候也会显示if (!ViewContext.ModelState.IsValid) {<strong>Error""</strong><div asp-validatio…

Java中关于省略作用域报错问题分析

这个是很典型的作用域问题&#xff0c;if后如果省略那么if只作用于其后面的第一行代码 这时候如果这行代码只是个变量声明语句的话&#xff0c;这个变量是没有其他任何逻辑可以访问到的&#xff0c;因为作用域问题(如果有/&#xff0c;那么声明语句中声明的变量只在这个个内可用…

C#9就这么来了,.NET开发者该做点什么?

就在上周三10号&#xff0c;.NET5.0发布了第5个预览版&#xff0c;同时支持了C#9-preview&#xff01;是的&#xff0c;你没看错&#xff0c;虽然C# 8.0还未正式发布&#xff0c;但是通往C&#xff03;9的漫长道路却已经开始&#xff0c;这发展速度简直了&#xff01;C#语言的快…

Magicodes.IE在.NET Core中通过请求头导出多种格式文件

原文作者&#xff1a;HueiFeng前言在2.2里程碑中我们增加了一些新的功能,正如标题所写通过请求头进行导出我们不同格式的文件.下面我们来看一下如何使用.通过这种方式无论是对我们的数据多用途&#xff0c;还是说对我们的数据校验都做到了轻松易配。同时我们也将在本周发布2.3版…

使用DQL查询数据

文章目录DQL语言SELECT语法制定查询字段AS 子句作为别名DISTINCT关键字的使用使用表达式的列where条件语句逻辑操作符模糊查询 &#xff1a; 比较操作符连接查询JION自连接排序和分页子查询DQL语言 DQL( Data Query Language 数据查询语言 ) 查询数据库数据 , 如SELECT语句简…

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(九)

系列文章使用 abp cli 搭建项目给项目瘦身&#xff0c;让它跑起来完善与美化&#xff0c;Swagger登场数据访问和代码优先自定义仓储之增删改查统一规范API&#xff0c;包装返回模型再说Swagger&#xff0c;分组、描述、小绿锁接入GitHub&#xff0c;用JWT保护你的API异常处理和…

SilkierQuartz 1.0.21 发布, 是一个 Quartz.NET 的强大且简单的Web管理工具和承载组件...

SilkierQuartz 是一个新的合并了 Quartzmin 和 QuartzHostedService的组件!Quartz.NET 是一个完整的开源的任务规划系统&#xff0c;从小应用至大型企业级应用都可以适用.Quartzmin Quartzmin 是一个 Quartz.NET 的强大且简单的Web管理工具QuartzHostedService QuartzHostedSer…

用C#在STM32上写第一个Hello world

随着微软放弃.Net MF ,通过C#编写STM32 平台上的程序变得渺茫&#xff0c; 但是&#xff0c; 别着急&#xff0c; 目前至少有两个社区在做这件事情&#xff0c; 传承了微软的.Net MF , 一家是 nanoframework,另外一家比较封闭的是 GHI Electronics 地址是: https://github.com/…

如何找到Eclipse左侧项目栏

如何找到Eclipse左侧项目栏 window --> Show View --> other --> Java–> package Explorer

深入async/await知多少

.net的async/await功能相信对很多人来说并不陌生了&#xff0c;有人感觉这功能很好&#xff0c;但也有人说这功能不好容易产生一些莫名其妙的死锁&#xff1b;有人说这些异步功能也有人说这是同步功能。其实在使用async/await的有多少人真的了解它们呢&#xff1f;接下来详细地…

微软正式发布 gRPC-Web for .NET

今年一月份的时候&#xff0c;微软曾宣布对 gRPC-Web for .NET 的实验性支持微软实验性地对 .NET 支持 gRPC-Web&#xff0c;现在它已正式发布。gRPC 是谷歌开源的高性能、通用 RPC 框架&#xff0c;支持包括 .NET 在内的多种编程语言。它面向移动和基于 HTTP/2 标准设计&#…

Java多线程(review)

文章目录线程状态线程方法线程停止线程休眠——sleep网络延时模拟倒计时与打印当前系统时间线程礼让——yield线程强制执行——Join线程状态线程优先级守护线程不安全案例死锁Lock锁线程状态 新建状态: 使用 new 关键字和 Thread 类或其子类建立一个线程对象后&#xff0c;该线…

利用Azure Functions和k8s构建Serverless计算平台

题记&#xff1a;昨晚在一个技术社区直播分享了“利用Azure Functions和k8s构建Serverless计算平台”这一话题。整个分享分为4个部分&#xff1a;Serverless概念的介绍、Azure Functions的简单介绍、k8s和KEDA的介绍和最后的演示。ServerlessServerless其实包含了两种概念&…

基于 abp vNext 和 .NET Core 开发博客项目 - 终结篇之发布项目

系列文章使用 abp cli 搭建项目给项目瘦身&#xff0c;让它跑起来完善与美化&#xff0c;Swagger登场数据访问和代码优先自定义仓储之增删改查统一规范API&#xff0c;包装返回模型再说Swagger&#xff0c;分组、描述、小绿锁接入GitHub&#xff0c;用JWT保护你的API异常处理和…

对 JsonConvert 的认识太肤浅了,终于还是遇到了问题

一&#xff1a;背景1. 讲故事在开始本文之前&#xff0c;真的好想做个问卷调查&#xff0c;到底有多少人和我一样&#xff0c;对 JsonConvert 的认识只局限在 SerializeObject 和 DeserializeObject 这两个方法上(┬&#xff3f;┬), 这样我也好结伴同行&#xff0c;不再孤单落…

.Net微服务实战之DevOps篇

技术只是基础该系列的两篇文章《.Net微服务实战之技术选型篇》和《.Net微服务实战之技术架构分层篇》都是以技术角度出发描述微服务架构的实施。如果技术选型篇叙述的是工具&#xff0c;那么架构分层篇讲的就是技巧&#xff0c;而本篇要讨论的就是原则。一直以来我会给身边向我…

阿里云挑战赛

文章目录第一题题目&#xff1a;题解&#xff1a;第二题题目题解第三题题目题解第四题题目&#xff1a;题解第五题题目题解第六题题目题解第七题题目&#xff1a;题解第八题题目&#xff1a;题解;题解赛后出第一题 题目&#xff1a; 【单选】filter 方法意图过滤传入的订单列…