系列文章
- 基于.NetCore開發部落格項目 StarBlog - (1) 為什麼需要自己寫一個部落格?
- 基于.NetCore開發部落格項目 StarBlog - (2) 環境準備和建立項目
- 基于.NetCore開發部落格項目 StarBlog - (3) 模型設計
- 基于.NetCore開發部落格項目 StarBlog - (4) markdown部落格批量導入
- 基于.NetCore開發部落格項目 StarBlog - (5) 開始搭建Web項目
- 基于.NetCore開發部落格項目 StarBlog - (6) 頁面開發之部落格文章清單
- 基于.NetCore開發部落格項目 StarBlog - (7) 頁面開發之文章詳情頁面
- 基于.NetCore開發部落格項目 StarBlog - (8) 分類層級結構展示
- 基于.NetCore開發部落格項目 StarBlog - (9) 圖檔批量導入
- 基于.NetCore開發部落格項目 StarBlog - (10) 圖檔瀑布流
- 基于.NetCore開發部落格項目 StarBlog - (11) 實作通路統計
- 基于.NetCore開發部落格項目 StarBlog - (12) Razor頁面動态編譯
- 基于.NetCore開發部落格項目 StarBlog - (13) 加入友情連結功能
- 基于.NetCore開發部落格項目 StarBlog - (14) 實作主題切換功能
- 基于.NetCore開發部落格項目 StarBlog - (15) 生成随機尺寸圖檔
- ...
前言
前面 (6) 頁面開發之部落格文章清單 介紹了文章清單的開發,頁面中左側是分類清單,右側是該分類下的文章,這個布局乍看還是不錯的,不過考慮到本項目支援多級分類,但分類清單隻會機械式的把所有分類都顯示出來,無法展現分類的層級結構且占用了很大的頁面縱向空間,是以本文将對分類清單進行改造,使之能夠展現多級分類、節省頁面空間。
關于樹形結構元件,我找了一圈,适配bootstrap(基于jQuery)的元件很難找,大都是很老的,隻找到了bootstrap-treeview這個稍微好用一點的,看了下GitHub項目首頁,同樣是好久沒更新了,它适配的甚至是3.x版本的bootstrap,現在都已經2022年了,bootstrap都更新到5.x版本了,然而沒找到更好的,湊合用吧~ (實在不行還能把它代碼clone下來魔改)
安裝
這個元件是比較老的
依賴bower,如果沒有bower的話需要先安裝
npm install -g bower
然後在
StarBlog.Web
目錄下執行以下指令安裝依賴
npm install bootstrap-treeview
因為我們的靜态資源都在
wwwroot
下,是以npm安裝的前端資源還需要通過gulp工具自動複制到
wwwroot
裡,這一點在前面的文章中有介紹過,忘記的同學可以看一下前面這篇:基于.NetCore開發部落格項目 StarBlog - (5) 開始搭建Web項目
編輯
gulpfile.js
檔案,在
const libs
配置中增加一行
//使用 npm 下載下傳的前端元件包
const libs = [
// ...
{name: "bootstrap-treeview", dist: "./node_modules/bootstrap-treeview/dist/**/*.*"},
];
然後執行gulp任務即可
gulp move
完成之後可以看到
wwwroot/lib
下已經多了一個
bootstrap-treeview
目錄了
接下來我們就可以在頁面中引用
用法
正式開始前,先來了解一下這個元件的用法
引入依賴
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap-treeview/dist/bootstrap-treeview.min.js"></script>
在網頁裡放一個容器
<div id="categories">
根據官方例子,使用js激活元件
const instance = $('#categories').treeview({
data: collections,
});
collections
格式如下
const collections = [
{
text: 'Parent 1',
href: '#parent1',
nodes: [
{
text: 'Child 1',
href: '#child1',
nodes: [
{
text: 'Grandchild 1',
href: '#grandchild1',
},
{
text: 'Grandchild 2',
href: '#grandchild2',
}
]
},
{
text: 'Child 2',
href: '#child2',
}
]
},
{
text: 'Parent 2',
href: '#parent2',
},
{
text: 'Parent 3',
href: '#parent3',
},
{
text: 'Parent 4',
href: '#parent4',
},
{
text: 'Parent 5',
href: '#parent5',
}
];
官網的預設效果
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLyIDOzczN4kDNtgjNyYDN2AzMyQjM1AjMyAjMtIDN5YjN48CX1AjMyAjMvwlM0kjN2gzLcd2bsJ2Lc12bj5ycn9Gbi52YuIjMwIzZtl2Lc9CX6MHc0RHaiojIsJye.png)
不過經過我的測試,官網這個例子在bootstrap5下是有些問題的,預設的圖示都顯示不出來。需要我們自定義一下,加上圖示配置就行,用到的圖示是我們之前的安裝的FontAwesome Icons
const instance = $('#categories').treeview({
data: collections,
collapseIcon: "fa fa-caret-down",
expandIcon: "fa fa-caret-right",
emptyIcon: 'fa fa-circle-o',
});
處理分類資料
為了友善使用這個元件,我們需要在後端把分類層級包裝成這個元件需要的形式。
首先定義一個節點類
public class CategoryNode {
public string text { get; set; } = "";
public string href { get; set; } = "";
public List<CategoryNode>? nodes { get; set; }
}
然後在
Services/CategoryyService.cs
裡新增一個方法,用來生成分類的樹結構,為了代碼編寫友善,我直接用遞歸來實作。
public List<CategoryNode>? GetNodes(int parentId = 0) {
var categories = _cRepo.Select
.Where(a => a.ParentId == parentId).ToList();
if (categories.Count == 0) return null;
return categories.Select(category => new CategoryNode {
text = category.Name,
nodes = GetNodes(category.Id)
}).ToList();
}
這樣輸出來的資料就是這樣
[
{
"text": "Android開發",
"href": "",
"nodes": null
},
{
"text": "AspNetCore",
"href": "",
"nodes": [
{
"text": "Asp-Net-Core學習筆記",
"href": "",
"nodes": null
},
{
"text": "Asp-Net-Core開發筆記",
"href": "",
"nodes": null
}
]
}
]
哦差點忘了還得給每個節點加上
href
參數
寫死是不可能寫死的,
ControllerBase
執行個體預設帶有一個
IUrlHelper
類型的
Url
屬性,可以用其
Link()
方法實作位址路由解析。
不過我們這個方法是寫在Service裡,并沒有
ControllerBase
執行個體,這時隻能用依賴注入的方式,不過我在Stack Overflow上看到一個說法是,AspNetCore3.x之後,用
LinkGenerator
更好。
上代碼,先注冊服務
builder.Services.AddHttpContextAccessor();
然後依賴注入
private readonly IHttpContextAccessor _accessor;
private readonly LinkGenerator _generator;
public CategoryService(IHttpContextAccessor accessor, LinkGenerator generator) {
_accessor = accessor;
_generator = generator;
}
修改上面那個
GetNodes
方法,在
CategoryNode
初始化器裡加上
href = _generator.GetUriByAction(
_accessor.HttpContext!,
nameof(BlogController.List),
"Blog",
new {categoryId = category.Id}
)
具體代碼可以看GitHub:https://github.com/Deali-Axy/StarBlog/blob/master/StarBlog.Web/Services/CategoryService.cs
生成的連結形式是這樣的:
{
"text": "Android開發",
"href": "http://localhost:5038/Blog/List?categoryId=2",
"nodes": null
}
前端渲染
資料準備好了,這時遇到一個問題,資料是要放到js中處理的,那我要用
fetch
之類的異步請求來擷取分類資料再顯示樹形分類嗎?這樣的好處是寫起來比較直覺,然而我們項目的部落格網站是後端渲染,現在部落格清單頁面混入了異步請求,會導緻割裂感,右邊部分的文章清單服務端渲染出來在浏覽器上展示了,左側的分類還要異步去請求。
斟酌了一下,我決定這個分類也使用後端渲染,雖然有點反直覺,但根據
bootstrap-treeview
元件的文檔,它可以使用json方式渲染分類,那我隻需要在後端把分類資料序列化成json格式,然後在view中渲染到js代碼中就行。
開始吧~
編輯
StarBlog.Web/ViewModels/BlogListViewModel.cs
檔案,添加倆字段
public List<CategoryNode> CategoryNodes { get; set; }
// 将上面的分類層級資料轉換成Json字元串
public string CategoryNodesJson => JsonSerializer.Serialize(
CategoryNodes,
new JsonSerializerOptions {Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping}
);
然後修改一下Controller,
StarBlog.Web/Controllers/BlogController.cs
,先依賴注入
CategoryService
然後修改
List
方法
public IActionResult List(int categoryId = 0, int page = 1, int pageSize = 5) {
var categories = _categoryRepo.Where(a => a.Visible)
.IncludeMany(a => a.Posts).ToList();
categories.Insert(0, new Category {Id = 0, Name = "All", Posts = _postRepo.Select.ToList()});
return View(new BlogListViewModel {
CurrentCategory = categoryId == 0 ? categories[0] : categories.First(a => a.Id == categoryId),
CurrentCategoryId = categoryId,
Categories = categories,
// 增加這一行
CategoryNodes = _categoryService.GetNodes(),
Posts = _postService.GetPagedList(new PostQueryParameters {
CategoryId = categoryId,
Page = page,
PageSize = pageSize,
OnlyPublished = true
})
});
}
最後一步,修改View,
StarBlog.Web/Views/Blog/List.cshtml
,在底部加入js引用和一些js代碼,treeview元件的配置我已經封裝成
initTreeView
方法,可以直接使用。
@section bottom {
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap-treeview/dist/bootstrap-treeview.min.js"></script>
<script src="~/js/blog-list.js"></script>
<script>
const categories = '@Html.Raw(Model.CategoryNodesJson)'
initTreeView(categories);
</script>
}
View的關鍵代碼就這幾行,完整代碼可見:https://github.com/Deali-Axy/StarBlog/blob/master/StarBlog.Web/Views/Blog/List.cshtml
最終效果
完成之後的最終效果如下,算是支援了分類層級了,不過仍然不完美,存在幾個問題:
- 不能高亮顯示目前所選分類
- 沒有實作分類文章數量顯示(原來的版本是有的)
- 無法自定義
樣式,存在下劃線不美觀list-group-item
- ...
這幾個問題留着後面優化吧~ 暫時先折騰到這裡…
部落格項目的開發已經基本完成,項目代碼完全開源,有興趣的朋友可以點個star~
- 部落格後端+前台項目位址:https://github.com/Deali-Axy/StarBlog
- 管理背景前端項目位址:https://github.com/Deali-Axy/StarBlog-Admin
參考資料
- Bower官網:https://bower.io/
- bootstrap-treeview項目首頁:https://github.com/jonmiles/bootstrap-treeview
- https://onelib.biz/blog/a/602b8b65906abf3c8f946fd7
- https://stackoverflow.com/questions/37322076/injection-of-iurlhelper-in-asp-net-core
微信公衆号:「程式設計實驗室」
專注于網際網路熱門新技術探索與團隊靈活開發實踐,包括架構設計、機器學習與資料分析算法、移動端開發、Linux、Web前後端開發等,歡迎一起探讨技術,分享學習實踐經驗。