系列文章

  1. 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目
  2. 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来
  3. 基于 abp vNext 和 .NET Core 开发博客项目 - 完善与美化,Swagger登场
  4. 基于 abp vNext 和 .NET Core 开发博客项目 - 数据访问和代码优先
  5. 基于 abp vNext 和 .NET Core 开发博客项目 - 自定义仓储之增删改查
  6. 基于 abp vNext 和 .NET Core 开发博客项目 - 统一规范API,包装返回模型
  7. 基于 abp vNext 和 .NET Core 开发博客项目 - 再说Swagger,分组、描述、小绿锁
  8. 基于 abp vNext 和 .NET Core 开发博客项目 - 接入GitHub,用JWT保护你的API
  9. 基于 abp vNext 和 .NET Core 开发博客项目 - 异常处理和日志记录
  10. 基于 abp vNext 和 .NET Core 开发博客项目 - 使用Redis缓存数据
  11. 基于 abp vNext 和 .NET Core 开发博客项目 - 集成Hangfire实现定时任务处理
  12. 基于 abp vNext 和 .NET Core 开发博客项目 - 用AutoMapper搞定对象映射
  13. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(一)
  14. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(二)
  15. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(三)
  16. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(一)
  17. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(二)
  18. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(三)
  19. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(四)
  20. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(五)
  21. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(一)
  22. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(二)
  23. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(三)
  24. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(四)
  25. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(五)

上一篇完成了博客文章详情页面的数据展示和基于JWT方式的简单身份验证,本篇继续推进,完成后台分类管理的所有增删改查等功能。

分类管理

在 Admin 文件夹下新建Razor组件,Categories.razor,设置路由,@page "/admin/categories"。将具体的展示内容放在组件AdminLayout中。

  1. @page "/admin/categories"
  2. <AdminLayout>
  3. <Loading />
  4. </AdminLayout>

在这里我会将所有分类展示出来,新增、更新、删除都会放在一个页面上去完成。

先将列表查出来,添加API的返回参数,private ServiceResult<IEnumerable<QueryCategoryForAdminDto>> categories;,然后再初始化中去获取数据。

  1. //QueryCategoryForAdminDto.cs
  2. namespace Meowv.Blog.BlazorApp.Response.Blog
  3. {
  4. public class QueryCategoryForAdminDto : QueryCategoryDto
  5. {
  6. /// <summary>
  7. /// 主键
  8. /// </summary>
  9. public int Id { get; set; }
  10. }
  11. }
  1. /// <summary>
  2. /// API返回的分类列表数据
  3. /// </summary>
  4. private ServiceResult<IEnumerable<QueryCategoryForAdminDto>> categories;
  5. /// <summary>
  6. /// 初始化
  7. /// </summary>
  8. /// <returns></returns>
  9. protected override async Task OnInitializedAsync()
  10. {
  11. var token = await Common.GetStorageAsync("token");
  12. Http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
  13. categories = await FetchData();
  14. }
  15. /// <summary>
  16. /// 获取数据
  17. /// </summary>
  18. /// <returns></returns>
  19. private async Task<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>> FetchData()
  20. {
  21. return await Http.GetFromJsonAsync<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>>("/blog/admin/categories");
  22. }

初始化的时候,需要将我们存在localStorage中的token读取出来,因为我们后台的API都需要添加 Authorization Header 请求头才能成功返回数据。

在Blazor添加请求头也是比较方便的,直接Http.DefaultRequestHeaders.Add(...)即可,要注意的是 token值前面需要加 Bearer ,跟了一个空格不可以省略。

获取数据单独提成了一个方法FetchData(),因为会频繁用到,现在在页面上将数据绑定进行展示。

  1. @if (categories == null)
  2. {
  3. <Loading />
  4. }
  5. else
  6. {
  7. <div class="post-wrap categories">
  8. <h2 class="post-title">-&nbsp;Categories&nbsp;-</h2>
  9. @if (categories.Success && categories.Result.Any())
  10. {
  11. <div class="categories-card">
  12. @foreach (var item in categories.Result)
  13. {
  14. <div class="card-item">
  15. <div class="categories">
  16. <NavLink title="删除" @onclick="@(async () => await DeleteAsync(item.Id))"></NavLink>
  17. <NavLink title="编辑" @onclick="@(() => ShowBox(item))"></NavLink>
  18. <NavLink target="_blank" href="@($"/category/{item.DisplayName}")">
  19. <h3>@item.CategoryName</h3>
  20. <small>(@item.Count)</small>
  21. </NavLink>
  22. </div>
  23. </div>
  24. }
  25. <div class="card-item">
  26. <div class="categories">
  27. <NavLink><h3 @onclick="@(() => ShowBox())">~~~ 新增分类 ~~~</h3></NavLink>
  28. </div>
  29. </div>
  30. </div>
  31. }
  32. else
  33. {
  34. <ErrorTip />
  35. }
  36. </div>
  37. }

同样的当categories还没成功获取到数据的时候,我们直接在展示 <Loading />组件。然后就是循环列表数据在foreach中进行绑定数据。

在每条数据最前面,加了删除和编辑两个按钮,删除的时候调用DeleteAsync方法,将当前分类的Id传给他即可。新增和编辑的时候调用ShowBox方法,他接受一个参数,当前循环到的分类对象item,即QueryCategoryForAdminDto

同时这里考虑到复用性,我写了一个弹窗组件,Box.Razor,放在Shared文件夹下面,可以先看一下标题为弹窗组件的内容再回来继续往下看。

删除分类

接下来看看删除方法。

  1. /// <summary>
  2. /// 删除分类
  3. /// </summary>
  4. /// <param name="id"></param>
  5. /// <returns></returns>
  6. private async Task DeleteAsync(int id)
  7. {
  8. // 弹窗确认
  9. bool confirmed = await Common.InvokeAsync<bool>("confirm", "\n真的要干掉这个该死的分类吗");
  10. if (confirmed)
  11. {
  12. var response = await Http.DeleteAsync($"/blog/category?id={id}");
  13. var result = await response.Content.ReadFromJsonAsync<ServiceResult>();
  14. if (result.Success)
  15. {
  16. categories = await FetchData();
  17. }
  18. }
  19. }

删除之前搞个原生的confirm进行提示,避免手残误删。因为API那边使用的是HttpDelete,所有我们调用API时候要用Http.DeleteAsync,返回的是HttpResponseMessage对象,需要我们手动处理接收返回数据,将其转换为ServiceResult对象,如果判断删除成功后重新调用FetchData()刷新分类数据。

新增/更新分类

新增和更新数据选择使用弹窗的方式来进行(弹窗组件在下方),首先是需要一个参数判断弹窗是否打开,因为是将新增和更新放在一起,所以如何判断是新增还是更新呢?这里使用Id来进行判断,当编辑的时候肯定会有Id参数。新增的时候是没有参数传递的。

当我们打开弹窗后里面需要展示两个input框,用来供输入要保存的数据,同样是添加两个变量。

添加所需的这几个参数。

  1. /// <summary>
  2. /// 默认隐藏Box
  3. /// </summary>
  4. private bool Open { get; set; } = false;
  5. /// <summary>
  6. /// 新增或者更新时候的分类字段值
  7. /// </summary>
  8. private string categoryName, displayName;
  9. /// <summary>
  10. /// 更新分类的Id值
  11. /// </summary>
  12. private int id;

现在可以将Box组件添加到页面上。

  1. <div class="post-wrap categories">
  2. ...
  3. </div>
  4. <Box OnClickCallback="@SubmitAsync" Open="@Open">
  5. <div class="box-item">
  6. <b>DisplayName:</b><input type="text" @bind="@displayName" @bind:event="oninput" />
  7. </div>
  8. <div class="box-item">
  9. <b>CategoryName:</b><input type="text" @bind="@categoryName" @bind:event="oninput" />
  10. </div>
  11. </Box>

确定按钮回调事件执行SubmitAsync()方法,打开状态参数为上面添加的Open,按钮文字ButtonText为默认值不填。

添加了两个input,将两个分类字段分别绑定上去,使用@bind@bind:event。前者等价于设置其value值,后者等价于一个change事件当值改变后会重新赋给绑定的字段参数。

现在可以来看看点击了新增或者编辑按钮的方法ShowBox(...),接收一个参数QueryCategoryForAdminDto让其默认值为null。

  1. /// <summary>
  2. /// 显示box,绑定字段
  3. /// </summary>
  4. /// <param name="dto"></param>
  5. private void ShowBox(QueryCategoryForAdminDto dto = null)
  6. {
  7. Open = true;
  8. id = 0;
  9. // 新增
  10. if (dto == null)
  11. {
  12. displayName = null;
  13. categoryName = null;
  14. }
  15. else // 更新
  16. {
  17. id = dto.Id;
  18. displayName = dto.DisplayName;
  19. categoryName = dto.CategoryName;
  20. }
  21. }

执行ShowBox()方法,将弹窗打开,设置Open = true;和初始化id的值id = 0;

通过参数是否null进行判断是新增还是更新,这样打开弹窗就搞定了,剩下的就交给弹窗来处理了。

因为新增和更新API需要还对应的输入参数EditCategoryInput,去添加它不要忘了。

那么现在就只差按钮回调事件SubmitAsync()了,主要是给输入参数进行赋值调用API,执行新增或者更新即可。

  1. /// <summary>
  2. /// 确认按钮点击事件
  3. /// </summary>
  4. /// <returns></returns>
  5. private async Task SubmitAsync()
  6. {
  7. var input = new EditCategoryInput()
  8. {
  9. DisplayName = displayName.Trim(),
  10. CategoryName = categoryName.Trim()
  11. };
  12. if (string.IsNullOrEmpty(input.DisplayName) || string.IsNullOrEmpty(input.CategoryName))
  13. {
  14. return;
  15. }
  16. var responseMessage = new HttpResponseMessage();
  17. if (id > 0)
  18. responseMessage = await Http.PutAsJsonAsync($"/blog/category?id={id}", input);
  19. else
  20. responseMessage = await Http.PostAsJsonAsync("/blog/category", input);
  21. var result = await responseMessage.Content.ReadFromJsonAsync<ServiceResult>();
  22. if (result.Success)
  23. {
  24. categories = await FetchData();
  25. Open = false;
  26. }
  27. }

当参数为空时,直接return什么都不执行。通过当前Id判断是新增还是更新操作,调用不同的方法PutAsJsonAsyncPostAsJsonAsync去请求API,同样返回到是HttpResponseMessage对象,最后如果操作成功,重新请求一个数据,刷新分类列表,将弹窗关闭掉。

分类管理页面的全部代码如下:

点击查看代码
  1. @page "/admin/categories"
  2. <AdminLayout>
  3. @if (categories == null)
  4. {
  5. <Loading />
  6. }
  7. else
  8. {
  9. <div class="post-wrap categories">
  10. <h2 class="post-title">-&nbsp;Categories&nbsp;-</h2>
  11. @if (categories.Success && categories.Result.Any())
  12. {
  13. <div class="categories-card">
  14. @foreach (var item in categories.Result)
  15. {
  16. <div class="card-item">
  17. <div class="categories">
  18. <NavLink title="删除" @onclick="@(async () => await DeleteAsync(item.Id))"></NavLink>
  19. <NavLink title="编辑" @onclick="@(() => ShowBox(item))"></NavLink>
  20. <NavLink target="_blank" href="@($"/category/{item.DisplayName}")">
  21. <h3>@item.CategoryName</h3>
  22. <small>(@item.Count)</small>
  23. </NavLink>
  24. </div>
  25. </div>
  26. }
  27. <div class="card-item">
  28. <div class="categories">
  29. <NavLink><h3 @onclick="@(() => ShowBox())">~~~ 新增分类 ~~~</h3></NavLink>
  30. </div>
  31. </div>
  32. </div>
  33. }
  34. else
  35. {
  36. <ErrorTip />
  37. }
  38. </div>
  39. <Box OnClickCallback="@SubmitAsync" Open="@Open">
  40. <div class="box-item">
  41. <b>DisplayName:</b><input type="text" @bind="@displayName" @bind:event="oninput" />
  42. </div>
  43. <div class="box-item">
  44. <b>CategoryName:</b><input type="text" @bind="@categoryName" @bind:event="oninput" />
  45. </div>
  46. </Box>
  47. }
  48. </AdminLayout>
  49. @code {
  50. /// <summary>
  51. /// 默认隐藏Box
  52. /// </summary>
  53. private bool Open { get; set; } = false;
  54. /// <summary>
  55. /// 新增或者更新时候的分类字段值
  56. /// </summary>
  57. private string categoryName, displayName;
  58. /// <summary>
  59. /// 更新分类的Id值
  60. /// </summary>
  61. private int id;
  62. /// <summary>
  63. /// API返回的分类列表数据
  64. /// </summary>
  65. private ServiceResult<IEnumerable<QueryCategoryForAdminDto>> categories;
  66. /// <summary>
  67. /// 初始化
  68. /// </summary>
  69. /// <returns></returns>
  70. protected override async Task OnInitializedAsync()
  71. {
  72. var token = await Common.GetStorageAsync("token");
  73. Http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
  74. categories = await FetchData();
  75. }
  76. /// <summary>
  77. /// 获取数据
  78. /// </summary>
  79. /// <returns></returns>
  80. private async Task<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>> FetchData()
  81. {
  82. return await Http.GetFromJsonAsync<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>>("/blog/admin/categories");
  83. }
  84. /// <summary>
  85. /// 删除分类
  86. /// </summary>
  87. /// <param name="id"></param>
  88. /// <returns></returns>
  89. private async Task DeleteAsync(int id)
  90. {
  91. Open = false;
  92. // 弹窗确认
  93. bool confirmed = await Common.InvokeAsync<bool>("confirm", "\n真的要干掉这个该死的分类吗");
  94. if (confirmed)
  95. {
  96. var response = await Http.DeleteAsync($"/blog/category?id={id}");
  97. var result = await response.Content.ReadFromJsonAsync<ServiceResult>();
  98. if (result.Success)
  99. {
  100. categories = await FetchData();
  101. }
  102. }
  103. }
  104. /// <summary>
  105. /// 显示box,绑定字段
  106. /// </summary>
  107. /// <param name="dto"></param>
  108. private void ShowBox(QueryCategoryForAdminDto dto = null)
  109. {
  110. Open = true;
  111. id = 0;
  112. // 新增
  113. if (dto == null)
  114. {
  115. displayName = null;
  116. categoryName = null;
  117. }
  118. else // 更新
  119. {
  120. id = dto.Id;
  121. displayName = dto.DisplayName;
  122. categoryName = dto.CategoryName;
  123. }
  124. }
  125. /// <summary>
  126. /// 确认按钮点击事件
  127. /// </summary>
  128. /// <returns></returns>
  129. private async Task SubmitAsync()
  130. {
  131. var input = new EditCategoryInput()
  132. {
  133. DisplayName = displayName.Trim(),
  134. CategoryName = categoryName.Trim()
  135. };
  136. if (string.IsNullOrEmpty(input.DisplayName) || string.IsNullOrEmpty(input.CategoryName))
  137. {
  138. return;
  139. }
  140. var responseMessage = new HttpResponseMessage();
  141. if (id > 0)
  142. responseMessage = await Http.PutAsJsonAsync($"/blog/category?id={id}", input);
  143. else
  144. responseMessage = await Http.PostAsJsonAsync("/blog/category", input);
  145. var result = await responseMessage.Content.ReadFromJsonAsync<ServiceResult>();
  146. if (result.Success)
  147. {
  148. categories = await FetchData();
  149. Open = false;
  150. }
  151. }
  152. }

弹窗组件

考虑到新增和更新数据的时候需要弹窗,这里就简单演示一下写一个小组件。

在 Shared 文件夹下新建一个Box.razor

在开始之前分析一下弹窗组件所需的元素,弹窗肯定有一个确认和取消按钮,右上角需要有一个关闭按钮,关闭按钮和取消按钮一个意思。他还需要一个打开或者关闭的状态,判断是否打开弹窗,还有就是弹窗内需要自定义展示内容。

确定按钮的文字可以自定义,所以差不多就需要3个参数,组件内容RenderFragment ChildContent,是否打开弹窗bool Open默认隐藏,按钮文字string ButtonText默认值给"确定"。然后最重要的是确定按钮需要一个回调事件,EventCallback<MouseEventArgs> OnClickCallback 用于执行不同的事件。

  1. /// <summary>
  2. /// 组件内容
  3. /// </summary>
  4. [Parameter]
  5. public RenderFragment ChildContent { get; set; }
  6. /// <summary>
  7. /// 是否隐藏
  8. /// </summary>
  9. [Parameter]
  10. public bool Open { get; set; } = true;
  11. /// <summary>
  12. /// 按钮文字
  13. /// </summary>
  14. [Parameter]
  15. public string ButtonText { get; set; } = "确定";
  16. /// <summary>
  17. /// 确认按钮点击事件回调
  18. /// </summary>
  19. [Parameter]
  20. public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
  21. /// <summary>
  22. /// 关闭Box
  23. /// </summary>
  24. private void Close() => Open = false;

右上角关闭和取消按钮直接在内部进行处理,执行Close()方法,将参数Open值设置为false即可。

对应的html如下。

  1. @if (Open)
  2. {
  3. <div class="shadow"></div>
  4. <div class="box">
  5. <div class="close" @onclick="Close"></div>
  6. <div class="box-content">
  7. @ChildContent
  8. <div class="box-item box-item-btn">
  9. <button class="box-btn" @onclick="OnClickCallback">@ButtonText</button>
  10. <button class="box-btn btn-primary" @onclick="Close">取消</button>
  11. </div>
  12. </div>
  13. </div>
  14. }

关于样式

下面是弹窗组件所需的样式代码,大家需要的自取,也可以直接去GitHub实时获取最新的样式文件。

  1. .box {
  2. width: 600px;
  3. height: 300px;
  4. border-radius: 5px;
  5. background-color: #fff;
  6. position: fixed;
  7. top: 50%;
  8. left: 50%;
  9. margin-top: -150px;
  10. margin-left: -300px;
  11. z-index: 997;
  12. }
  13. .close {
  14. position: absolute;
  15. right: 3px;
  16. top: 2px;
  17. cursor: pointer;
  18. }
  19. .shadow {
  20. width: 100%;
  21. height: 100%;
  22. position: fixed;
  23. left: 0;
  24. top: 0;
  25. z-index: 996;
  26. background-color: #000;
  27. opacity: 0.3;
  28. }
  29. .box-content {
  30. width: 90%;
  31. margin: 20px auto;
  32. }
  33. .box-item {
  34. margin-top: 10px;
  35. height: 30px;
  36. }
  37. .box-item b {
  38. width: 130px;
  39. display: inline-block;
  40. }
  41. .box-item input[type=text] {
  42. padding-left: 5px;
  43. width: 300px;
  44. height: 30px;
  45. }
  46. .box-item label {
  47. width: 100px;
  48. white-space: nowrap;
  49. }
  50. .box-item input[type=radio] {
  51. width: auto;
  52. height: auto;
  53. visibility: initial;
  54. display: initial;
  55. margin-right: 2px;
  56. }
  57. .box-item button {
  58. height: 30px;
  59. width: 100px;
  60. }
  61. .box-item-btn {
  62. position: absolute;
  63. right: 20px;
  64. bottom: 20px;
  65. }
  66. .box-btn {
  67. display: inline-block;
  68. height: 30px;
  69. line-height: 30px;
  70. padding: 0 18px;
  71. background-color: #5A9600;
  72. color: #fff;
  73. white-space: nowrap;
  74. text-align: center;
  75. font-size: 14px;
  76. border: none;
  77. border-radius: 2px;
  78. cursor: pointer;
  79. }
  80. button:focus {
  81. outline: 0;
  82. }
  83. .box-btn:hover {
  84. opacity: .8;
  85. filter: alpha(opacity=80);
  86. color: #fff;
  87. }
  88. .btn-primary {
  89. border: 1px solid #C9C9C9;
  90. background-color: #fff;
  91. color: #555;
  92. }
  93. .btn-primary:hover {
  94. border-color: #5A9600;
  95. color: #333;
  96. }
  97. .post-box {
  98. width: 98%;
  99. margin: 27px auto 0;
  100. }
  101. .post-box-item {
  102. width: 100%;
  103. height: 30px;
  104. margin-bottom: 5px;
  105. }
  106. .post-box-item input {
  107. width: 49.5%;
  108. height: 30px;
  109. padding-left: 5px;
  110. border: 1px solid #ddd;
  111. }
  112. .post-box-item input:nth-child(1) {
  113. float: left;
  114. margin-right: 1px;
  115. }
  116. .post-box-item input:nth-child(2) {
  117. float: right;
  118. margin-left: 1px;
  119. }
  120. .post-box .box-item b {
  121. width: auto;
  122. }
  123. .post-box .box-item input[type=text] {
  124. width: 90%;
  125. }

好了,分类模块的功能都完成了,标签和友情链接的管理界面还会远吗?这两个模块的做法和分类是一样的,有兴趣的可以自己动手完成,今天到这吧,未完待续...

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

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(六)的更多相关文章

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

随机推荐

  1. PAT-1080 Graduate Admission (结构体排序)

    1080. Graduate Admission It is said that in 2013, there were about 100 graduate schools ready to pro ...

  2. 【万字图文-原创】 | 学会Java中的线程池,这一篇也许就够了!

    碎碎念 关于JDK源码相关的文章这已经是第四篇了,原创不易,粉丝从几十人到昨天的666人,真的很感谢之前帮我转发文章的一些朋友们. 从16年开始写技术文章,到现在博客园已经发表了222篇文章,大多数都 ...

  3. 【图机器学习】cs224w Lecture 11 & 12 - 网络传播

    目录 Decision Based Model of Diffusion Large Cascades Extending the Model Probabilistic Spreading Mode ...

  4. 消息队列RabbitMQ的安装配置与PHP中的使用

    一.RabbitMQ安装 windows安装 下载地址: https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.3/ra ...

  5. web.xml——Error:cvc-complex-type.2.4.a: Invalid content was found starting with element

    配置web.xml文件时报错 错误:cvc-complex-type.2.4.a: Invalid content was found starting with element 详细报错信息:cvc ...

  6. web自动化之浏览器的窗口切换

    from selenium import webdriver from selenium.webdriver.support.wait import WebDriverWait from seleni ...

  7. Tortoise svn 基础知识

    1 不跟踪文件.文件夹 1.1  文件.文件夹已经被svn跟踪 将本地文件.文件夹删除(windows删除文件的删除,快捷键是shift+delete),然后执行svn  update 将服务器同步到 ...

  8. centos安装以及网络配置

    Linux安装 1.Linux安装完成后 第一个问题就是网络不通的问题 ,问题图片如下: 解决办法: 三种网络模式: 桥接模式:虚拟机和宿主机是兄弟关系,统一由宿主机连接的路由器分发ip NAT模式: ...

  9. ngnix随笔三

    1.location模块 在server中也可以嵌套location 例 server{ listen 80; server_name www.a.com; root   /data/vhosts/; ...

  10. 前端基础进阶(十三):透彻掌握Promise的使用,读这篇就够了

    Promise的重要性我认为我没有必要多讲,概括起来说就是必须得掌握,而且还要掌握透彻.这篇文章的开头,主要跟大家分析一下,为什么会有Promise出现. 在实际的使用当中,有非常多的应用场景我们不能 ...