网站建设用什么,建网站联系,上海手机网站建设价格,怎么用网站做转换服务器系列文章使用 abp cli 搭建项目给项目瘦身#xff0c;让它跑起来完善与美化#xff0c;Swagger登场数据访问和代码优先自定义仓储之增删改查统一规范API#xff0c;包装返回模型再说Swagger#xff0c;分组、描述、小绿锁接入GitHub#xff0c;用JWT保护你的API异常处理和… 系列文章使用 abp cli 搭建项目给项目瘦身让它跑起来完善与美化Swagger登场数据访问和代码优先自定义仓储之增删改查统一规范API包装返回模型再说Swagger分组、描述、小绿锁接入GitHub用JWT保护你的API异常处理和日志记录使用Redis缓存数据集成Hangfire实现定时任务处理用AutoMapper搞定对象映射定时任务最佳实战一定时任务最佳实战二定时任务最佳实战三博客接口实战篇一博客接口实战篇二博客接口实战篇三博客接口实战篇四博客接口实战篇五Blazor实战系列一Blazor实战系列二Blazor实战系列三Blazor实战系列四Blazor实战系列五Blazor实战系列六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/// 年份/// /summarypublic int Year { get; set; }/// summary/// Posts/// /summarypublic IEnumerablePostBriefForAdminDto Posts { get; set; }}
}//PostBriefForAdminDto.cs
namespace Meowv.Blog.BlazorApp.Response.Blog
{public class PostBriefForAdminDto : PostBriefDto{/// summary/// 主键/// /summarypublic 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 ServiceResultPagedListQueryPostForAdminDto 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 namepage/param
/// returns/returns
private async Task RenderPage(int? page)
{// 获取数据posts await Http.GetFromJsonAsyncServiceResultPagedListQueryPostForAdminDto($/blog/admin/posts?page{page}limit{Limit});// 计算总页码TotalPage (int)Math.Ceiling((posts.Result.Total / (double)Limit));
}
在初始化中判断page参数如果没有值给他设置一个默认值1。RenderPage(int? page)方法是调用API返回数据并计算出总页码值。最后在页面上进行数据绑定。AdminLayoutif (posts null){Loading /}else{div classpost-wrap archiveNavLink stylefloat:right href/admin/posth3????~~~ 新增文章 ~~~????/h3/NavLinkif (posts.Success posts.Result.Item.Any()){foreach (var item in posts.Result.Item){h3item.Year/h3foreach (var post in item.Posts){article classarchive-itemNavLink title❌删除 onclick(async () await DeleteAsync(post.Id))❌/NavLinkNavLink title????编辑 onclick(async () await Common.NavigateTo($/admin/post/{post.Id}))????/NavLinkNavLink target_blank classarchive-item-link href(/post post.Url)post.Title/NavLinkspan classarchive-item-datepost.CreationTime/span/article}}nav classpaginationfor (int i 1; i TotalPage; i){var _page i;if (page _page){span classpage-number current_page/span}else{a classpage-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 nameid/param
/// returns/returns
private async Task DeleteAsync(int id)
{// 弹窗确认bool confirmed await Common.InvokeAsyncbool(confirm, \n????????真的要干掉这篇该死的文章吗????????);if (confirmed){var response await Http.DeleteAsync($/blog/post?id{id});var result await response.Content.ReadFromJsonAsyncServiceResult();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 relstylesheet /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 FuncTask 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 ServiceResultIEnumerableQueryCategoryForAdminDto 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.GetFromJsonAsyncServiceResultPostForAdminDto($/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.GetFromJsonAsyncServiceResultIEnumerableQueryCategoryForAdminDto(/blog/admin/categories);// 渲染编辑器await Common.InvokeAsync(window.func.renderEditor);// 关闭加载isLoading !isLoading;
}
action是一个异步的委托在初始化中执行了ChangeOpenStatus方法这个方法等会说然后获取localStorage中token的值。通过参数Id是否有值来判断当前是新增文章还是更新文章如果有值就是更新文章这时候需要根据id去将文章的数据拿到赋值给PostForAdminDto对象展示在页面上如果没有可以添加几个默认值给PostForAdminDto对象。因为文章需要分类和标签的数据同时这里将分类的数据也查出来标签默认是List列表将其转换成字符串类型。但完成上面操作后调用JavaScript方法renderEditor渲染渲染编辑器最后关闭加载显示页面。现在来看看页面。AdminLayoutif (isLoading){Loading /}else{div classpost-boxdiv classpost-box-iteminput typetext placeholder标题 autocompleteoff bindinput.Title bind:eventoninput onclick(() { Open false; }) /input typetext placeholder作者 autocompleteoff bindinput.Author bind:eventoninput onclick(() { Open false; }) //divdiv classpost-box-iteminput typetext placeholderURL autocompleteoff bindinput.Url bind:eventoninput onclick(() { Open false; }) /input typetext placeholder时间 autocompleteoff bindinput.CreationTime bind:formatyyyy-MM-dd HH:mm:sss bind:eventoninput onclick(() { Open false; }) //divdiv ideditortextarea styledisplay:none;input.Markdown/textarea/divBox OnClickCallbackSubmitAsync OpenOpen ButtonText发布div classbox-itemb分类/bif (categories.Success categories.Result.Any()){foreach (var item in categories.Result){labelinput typeradio namecategory valueitem.Id onchange(() { input.CategoryId item.Id; }) checked(item.Id input.CategoryId) /item.CategoryName/label}}/divdiv classbox-item/divdiv classbox-itemb标签/binput typetext bindtags bind:eventoninput //div/Box/div}
/AdminLayout
添加了四个input框分别用来绑定标题、作者、URL、时间div ideditor/div中为编辑器所需。然后我这里还是把之前的弹窗组件搞出来了执行逻辑不介绍了在弹窗组件中自定义显示分类和标签的内容将获取到的分类和标签绑定到具体位置。每个分类都是一个radio标签并且对应一个点击事件点哪个就把当前分类的Id赋值给PostForAdminDto对象。所有的input框都使用bind和bind:event绑定数据和获取数据。Box弹窗组件这里自定义了按钮文字ButtonText发布。/// summary
/// 改变Open状态通知组件渲染
/// /summary
private async Task ChangeOpenStatus()
{Open true;var markdown await Common.InvokeAsyncstring(editor.getMarkdown);var html await Common.InvokeAsyncstring(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.ReadFromJsonAsyncServiceResult();if (result.Success){await Common.NavigateTo(/admin/posts);}
}
打开弹窗后执行回调事件之前还是要判断值是否为空为空的情况下还是给出alert提示此时将tags标签还是转换成List列表根据Id是否有值去执行新增数据或者更新数据最终成功后跳转到文章列表页。本篇到这里就结束了主要攻克了在Blazor中使用Markdown编辑器实现新增和更新文章这个系列差不多就快结束了预计还有2篇的样子感谢各位的支持。开源地址https://github.com/Meowv/Blog/tree/blog_tutorial