天天看點

基于.NetCore開發部落格項目 StarBlog - (8) 分類層級結構展示

系列文章

  • 基于.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',
    }
];
           

官網的預設效果

基于.NetCore開發部落格項目 StarBlog - (8) 分類層級結構展示

不過經過我的測試,官網這個例子在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

    樣式,存在下劃線不美觀
  • ...

這幾個問題留着後面優化吧~ 暫時先折騰到這裡…

基于.NetCore開發部落格項目 StarBlog - (8) 分類層級結構展示

部落格項目的開發已經基本完成,項目代碼完全開源,有興趣的朋友可以點個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前後端開發等,歡迎一起探讨技術,分享學習實踐經驗。

繼續閱讀