天天看點

[開源]OSharpNS 步步為營系列 - 5. 添加前端Angular子產品

什麼是OSharp

OSharpNS全稱OSharp Framework with .NetStandard2.0,是一個基于

.NetStandard2.0

開發的一個

.NetCore

快速開發架構。這個架構使用最新穩定版的

.NetCore SDK

(目前是.NET Core 2.2),對 AspNetCore 的配置、依賴注入、日志、緩存、實體架構、Mvc(WebApi)、身份認證、權限授權等子產品進行更高一級的自動化封裝,并規範了一套業務實作的代碼結構與操作流程,使 .Net Core 架構更易于應用到實際項目開發中。

  • 開源位址:https://github.com/i66soft/osharp
  • 官方示例:https://www.osharp.org
  • 文檔中心:https://docs.osharp.org
  • VS 插件:https://marketplace.visualstudio.com/items?itemName=LiuliuSoft.osharp

感謝大家關注

首先特别感謝大家對OSharp快速開發架構的關注,這個系列每一篇都收到了比較多園友的關注,也在部落格園首頁開啟了 刷屏模式

[開源]OSharpNS 步步為營系列 - 5. 添加前端Angular子產品

同時示範網站的使用者注冊數量也在持續上漲

[開源]OSharpNS 步步為營系列 - 5. 添加前端Angular子產品

項目 Star 數也增長了幾百,歡迎沒點 Star 的也來關注下 OSharp快速開發架構

[開源]OSharpNS 步步為營系列 - 5. 添加前端Angular子產品
[開源]OSharpNS 步步為營系列 - 5. 添加前端Angular子產品

再次感謝

概述

前後端分離的系統中,前端和後端隻有必要的資料通信互動,前端相當于一個完整的用戶端應用程式,需要包含如下幾個方面:

  • 各個子產品的布局組合
  • 各個頁面的路由連接配接
  • 業務功能的資料展現和操作流程展現
  • 操作界面的菜單/按鈕權限控制

OSharp的Angular前端是基于 NG-ALAIN 架構的,這個架構基于阿裡的 NG-ZORRO 封裝了很多友善實用的元件,讓我們很友善的實作自己需要的前端界面布局。

前端業務子產品代碼布局

在Angular應用程式中,存在着子產品

module

的組織形式,一個後端的子產品正好可以對應着前端的一個

module

部落格子產品涉及的代碼檔案布局如下:

src                                          源代碼檔案夾
└─app                                        APP檔案夾
   └─routes                                  路由檔案夾
       └─blogs                               部落格子產品檔案夾
           ├─blogs.module.ts                 部落格子產品檔案
           ├─blogs.routing.ts                部落格子產品路由檔案
           ├─blog                            部落格元件檔案夾
           │   ├─blog.component.html         部落格元件模闆檔案
           │   └─blog.component.ts           部落格元件檔案
           └─post                            文章元件檔案夾
               ├─post.component.html         文章元件模闆檔案
               └─post.component.ts           文章元件檔案           

業務元件

元件

Component

是Angular應用程式的最小組織單元,是完成資料展現和業務操作的基本場所。

一個元件通常包含

元件類

元件模闆

兩個部分,如需要,還可包含

元件樣式

STComponentBase

為友善實作各個資料實體的通用管理清單,OSharp定義了一個通用清單元件基類

STComponentBase

,基于這個基類,隻需要傳入幾個關鍵的配置資訊,即可很友善的實作一個背景管理的實體清單資訊。

STComponentBase

主要特點如下:

  • 使用了 NG-ALAIN 的 STComponent 實作資料表格
  • 使用 SFComponent + NzModalComponent 實作資料的

    添加/編輯

    操作
  • 封裝了一個通用的進階查詢元件

    AdSearchComponent

    ,可以很友善實作資料的多條件/條件組無級嵌套資料查詢功能
  • 對清單元件進行統一的界面布局,使各清單風格一緻
  • 提供了對清單資料的

    讀取/添加/編輯/删除

    的預設實作
  • 極易擴充其他表格功能

STComponentBase 代碼實作如下:

export abstract class STComponentBase {
  moduleName: string;

  // URL
  readUrl: string;
  createUrl: string;
  updateUrl: string;
  deleteUrl: string;

  // 表格屬性
  columns: STColumn[];
  request: PageRequest;
  req: STReq;
  res: STRes;
  page: STPage;
  @ViewChild('st') st: STComponent;

  // 編輯屬性

  schema: SFSchema;
  ui: SFUISchema;
  editRow: STData;
  editTitle = '編輯';
  @ViewChild('modal') editModal: NzModalComponent;

  osharp: OsharpService;
  alain: AlainService;
  selecteds: STData[] = [];

  public get http(): _HttpClient {
    return this.osharp.http;
  }

  constructor(injector: Injector) {
    this.osharp = injector.get(OsharpService);
    this.alain = injector.get(AlainService);
  }

  protected InitBase() {
    this.readUrl = `api/admin/${this.moduleName}/read`;
    this.createUrl = `api/admin/${this.moduleName}/create`;
    this.updateUrl = `api/admin/${this.moduleName}/update`;
    this.deleteUrl = `api/admin/${this.moduleName}/delete`;

    this.request = new PageRequest();
    this.columns = this.GetSTColumns();
    this.req = this.GetSTReq(this.request);
    this.res = this.GetSTRes();
    this.page = this.GetSTPage();

    this.schema = this.GetSFSchema();
    this.ui = this.GetSFUISchema();
  }

  // #region 表格

  /**
   * 重寫以擷取表格的列設定Columns
   */
  protected abstract GetSTColumns(): OsharpSTColumn[];

  protected GetSTReq(request: PageRequest): STReq {
    let req: STReq = {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: request,
      allInBody: true,
      process: opt => this.RequestProcess(opt),
    };
    return req;
  }

  protected GetSTRes(): STRes {
    let res: STRes = {
      reName: { list: 'Rows', total: 'Total' },
      process: data => this.ResponseDataProcess(data),
    };
    return res;
  }

  protected GetSTPage(): STPage {
    let page: STPage = {
      showSize: true,
      showQuickJumper: true,
      toTop: true,
      toTopOffset: 0,
    };
    return page;
  }

  protected RequestProcess(opt: STRequestOptions): STRequestOptions {
    if (opt.body.PageCondition) {
      let page: PageCondition = opt.body.PageCondition;
      page.PageIndex = opt.body.pi;
      page.PageSize = opt.body.ps;
      if (opt.body.sort) {
        page.SortConditions = [];
        let sorts = opt.body.sort.split('-');
        for (const item of sorts) {
          let sort = new SortCondition();
          let num = item.lastIndexOf('.');
          let field = item.substr(0, num);
          field = this.ReplaceFieldName(field);
          sort.SortField = field;
          sort.ListSortDirection =
            item.substr(num + 1) === 'ascend'
              ? ListSortDirection.Ascending
              : ListSortDirection.Descending;
          page.SortConditions.push(sort);
        }
      } else {
        page.SortConditions = [];
      }
    }
    return opt;
  }

  protected ResponseDataProcess(data: STData[]): STData[] {
    return data;
  }

  protected ReplaceFieldName(field: string): string {
    return field;
  }

  search(request: PageRequest) {
    if (!request) {
      return;
    }
    this.req.body = request;
    this.st.reload();
  }

  change(value: STChange) {
    if (value.type === 'checkbox') {
      this.selecteds = value.checkbox;
    } else if (value.type === 'radio') {
      this.selecteds = [value.radio];
    }
  }

  error(value: STError) {
    console.log(value);
  }

  // #endregion

  // #region 編輯

  /**
   * 預設由列配置 `STColumn[]` 來生成SFSchema,不需要可以重寫定義自己的SFSchema
   */
  protected GetSFSchema(): SFSchema {
    let schema: SFSchema = { properties: this.ColumnsToSchemas(this.columns) };
    return schema;
  }

  protected ColumnsToSchemas(
    columns: OsharpSTColumn[],
  ): { [key: string]: SFSchema } {
    let properties: { [key: string]: SFSchema } = {};
    for (const column of columns) {
      if (!column.index || !column.editable || column.buttons) {
        continue;
      }
      let schema: SFSchema = this.alain.ToSFSchema(column);
      properties[column.index as string] = schema;
    }
    return properties;
  }

  protected GetSFUISchema(): SFUISchema {
    let ui: SFUISchema = {};
    return ui;
  }

  protected toEnum(items: { id: number; text: string }[]): SFSchemaEnumType[] {
    return items.map(item => {
      let e: SFSchemaEnumType = { value: item.id, label: item.text };
      return e;
    });
  }

  create() {
    if (!this.editModal) return;
    this.schema = this.GetSFSchema();
    this.ui = this.GetSFUISchema();
    this.editRow = {};
    this.editTitle = '新增';
    this.editModal.open();
  }

  edit(row: STData) {
    if (!row || !this.editModal) {
      return;
    }
    this.schema = this.GetSFSchema();
    this.ui = this.GetSFUISchema();
    this.editRow = row;
    this.editTitle = '編輯';
    this.editModal.open();
  }

  close() {
    if (!this.editModal) return;
    console.log(this.editModal);
    this.editModal.destroy();
  }

  save(value: STData) {
    let url = value.Id ? this.updateUrl : this.createUrl;
    this.http.post<AjaxResult>(url, [value]).subscribe(result => {
      this.osharp.ajaxResult(result, () => {
        this.st.reload();
        this.editModal.destroy();
      });
    });
  }

  delete(value: STData) {
    if (!value) {
      return;
    }
    this.http.post<AjaxResult>(this.deleteUrl, [value.Id]).subscribe(result => {
      this.osharp.ajaxResult(result, () => {
        this.st.reload();
      });
    });
  }

  // #endregion
}           

STComponentBase

基類的使用很簡單,隻需重寫關鍵的

GetSTColumns

方法傳入實體的列選項,即可完成一個管理清單的資料讀取,查詢,更新,删除等操作。

部落格子產品的元件實作

部落格-Blog

  • 部落格元件

    blog.component.ts

import { Component, OnInit, Injector } from '@angular/core';
import { SFUISchema } from '@delon/form';
import { OsharpSTColumn } from '@shared/osharp/services/ng-alain.types';
import { STComponentBase, } from '@shared/osharp/components/st-component-base';
import { STData } from '@delon/abc';
import { AjaxResult } from '@shared/osharp/osharp.model';

@Component({
  selector: 'app-blog',
  templateUrl: './blog.component.html',
  styles: []
})
export class BlogComponent extends STComponentBase implements OnInit {

  constructor(injector: Injector) {
    super(injector);
    this.moduleName = 'blog';
  }

  ngOnInit() {
    super.InitBase();
    this.createUrl = `api/admin/${this.moduleName}/apply`;
  }

  protected GetSTColumns(): OsharpSTColumn[] {
    let columns: OsharpSTColumn[] = [
      {
        title: '操作', fixed: 'left', width: 65, buttons: [{
          text: '操作', children: [
            { text: '稽核', icon: 'flag', acl: 'Root.Admin.Blogs.Blog.Verify', iif: row => !row.IsEnabled, click: row => this.verify(row) },
            { text: '編輯', icon: 'edit', acl: 'Root.Admin.Blogs.Blog.Update', iif: row => row.Updatable, click: row => this.edit(row) },
          ]
        }]
      },
      { title: '編号', index: 'Id', sort: true, readOnly: true, editable: true, filterable: true, ftype: 'number' },
      { title: '部落格位址', index: 'Url', sort: true, editable: true, filterable: true, ftype: 'string' },
      { title: '顯示名稱', index: 'Display', sort: true, editable: true, filterable: true, ftype: 'string' },
      { title: '已開通', index: 'IsEnabled', sort: true, filterable: true, type: 'yn' },
      { title: '作者編号', index: 'UserId', type: 'number' },
      { title: '建立時間', index: 'CreatedTime', sort: true, filterable: true, type: 'date' },
    ];
    return columns;
  }

  protected GetSFUISchema(): SFUISchema {
    let ui: SFUISchema = {
      '*': { spanLabelFixed: 100, grid: { span: 12 } },
      $Url: { grid: { span: 24 } },
      $Display: { grid: { span: 24 } },
    };
    return ui;
  }

  create() {
    if (!this.editModal) {
      return;
    }
    this.schema = this.GetSFSchema();
    this.ui = this.GetSFUISchema();
    this.editRow = {};
    this.editTitle = "申請部落格";
    this.editModal.open();
  }

  save(value: STData) {
    // 申請部落格
    if (!value.Id) {
      this.http.post<AjaxResult>(this.createUrl, value).subscribe(result => {
        this.osharp.ajaxResult(result, () => {
          this.st.reload();
          this.editModal.destroy();
        });
      });
      return;
    }
    // 稽核部落格
    if (value.Reason) {
      let url = 'api/admin/blog/verify';
      this.http.post<AjaxResult>(url, value).subscribe(result => {
        this.osharp.ajaxResult(result, () => {
          this.st.reload();
          this.editModal.destroy();
        });
      });
      return;
    }
    super.save(value);
  }

  verify(value: STData) {
    if (!value || !this.editModal) return;
    this.schema = {
      properties: {
        Id: { title: '編号', type: 'number', readOnly: true, default: value.Id },
        Name: { title: '部落格名', type: 'string', readOnly: true, default: value.Display },
        IsEnabled: { title: '是否開通', type: 'boolean' },
        Reason: { title: '稽核理由', type: 'string' }
      },
      required: ['Reason']
    };
    this.ui = {
      '*': { spanLabelFixed: 100, grid: { span: 12 } },
      $Id: { widget: 'text' },
      $Name: { widget: 'text', grid: { span: 24 } },
      $Reason: { widget: 'textarea', grid: { span: 24 } }
    };
    this.editRow = value;
    this.editTitle = "稽核部落格";
    this.editModal.open();
  }
}           
  • 部落格元件模闆

    blog.component.html

<nz-card>
  <div>
    <button nz-button (click)="st.reload()"><i nz-icon nzType="reload" nzTheme="outline"></i>重新整理</button>
    <button nz-button (click)="create()" acl="Root.Admin.Blogs.Blog.Apply" *ngIf="data.length == 0"><i nz-icon type="plus-circle" theme="outline"></i>申請</button>
    <app-ad-search [request]="request" [columns]="columns" (submited)="search($event)"></app-ad-search>
  </div>
  <st #st [data]="readUrl" [columns]="columns" [req]="req" [res]="res" [(pi)]="request.PageCondition.PageIndex" [(ps)]="request.PageCondition.PageSize" [page]="page" size="small" [scroll]="{x:'800px'}" multiSort
    (change)="change($event)" (error)="error($event)"></st>
</nz-card>

<nz-modal #modal [nzVisible]="false" [nzTitle]="editTitle" [nzClosable]="false" [nzFooter]="null">
  <sf #sf mode="edit" [schema]="schema" [ui]="ui" [formData]="editRow" button="none">
    <div class="modal-footer">
      <button nz-button type="button" (click)="close()">關閉</button>
      <button nz-button type="submit" [nzType]="'primary'" (click)="save(sf.value)" [disabled]="!sf.valid" [nzLoading]="http.loading" [acl]="'Root.Admin.Blogs.Blog.Update'">儲存</button>
    </div>
  </sf>
</nz-modal>           

文章-Post

  • 文章元件

    post.component.ts

import { Component, OnInit, Injector } from '@angular/core';
import { SFUISchema } from '@delon/form';
import { OsharpSTColumn } from '@shared/osharp/services/ng-alain.types';
import { STComponentBase, } from '@shared/osharp/components/st-component-base';

@Component({
  selector: 'app-post',
  templateUrl: './post.component.html',
  styles: []
})
export class PostComponent extends STComponentBase implements OnInit {

  constructor(injector: Injector) {
    super(injector);
    this.moduleName = 'post';
  }

  ngOnInit() {
    super.InitBase();
  }

  protected GetSTColumns(): OsharpSTColumn[] {
    let columns: OsharpSTColumn[] = [
      {
        title: '操作', fixed: 'left', width: 65, buttons: [{
          text: '操作', children: [
            { text: '編輯', icon: 'edit', acl: 'Root.Admin.Blogs.Post.Update', iif: row => row.Updatable, click: row => this.edit(row) },
            { text: '删除', icon: 'delete', type: 'del', acl: 'Root.Admin.Blogs.Post.Delete', iif: row => row.Deletable, click: row => this.delete(row) },
          ]
        }]
      },
      { title: '編号', index: 'Id', sort: true, readOnly: true, editable: true, filterable: true, ftype: 'number' },
      { title: '文章标題', index: 'Title', sort: true, editable: true, filterable: true, ftype: 'string' },
      { title: '文章内容', index: 'Content', sort: true, editable: true, filterable: true, ftype: 'string' },
      { title: '部落格編号', index: 'BlogId', readOnly: true, sort: true, filterable: true, type: 'number' },
      { title: '作者編号', index: 'UserId', readOnly: true, sort: true, filterable: true, type: 'number' },
      { title: '建立時間', index: 'CreatedTime', sort: true, filterable: true, type: 'date' },
    ];
    return columns;
  }

  protected GetSFUISchema(): SFUISchema {
    let ui: SFUISchema = {
      '*': { spanLabelFixed: 100, grid: { span: 12 } },
      $Title: { grid: { span: 24 } },
      $Content: { widget: 'textarea', grid: { span: 24 } }
    };
    return ui;
  }
}           
  • 文章元件模闆

    post.component.html

<nz-card>
  <div>
    <button nz-button (click)="st.reload()"><i nz-icon nzType="reload" nzTheme="outline"></i>重新整理</button>
    <button nz-button (click)="create()" acl="Root.Admin.Blogs.Post.Create"><i nz-icon type="plus-circle" theme="outline"></i>新增</button>
    <app-ad-search [request]="request" [columns]="columns" (submited)="search($event)"></app-ad-search>
  </div>
  <st #st [data]="readUrl" [columns]="columns" [req]="req" [res]="res" [(pi)]="request.PageCondition.PageIndex" [(ps)]="request.PageCondition.PageSize" [page]="page" size="small"
    [scroll]="{x:'900px'}" multiSort (change)="change($event)" (error)="error($event)"></st>
</nz-card>

<nz-modal #modal [nzVisible]="false" [nzTitle]="editTitle" [nzClosable]="false" [nzFooter]="null">
  <sf #sf mode="edit" [schema]="schema" [ui]="ui" [formData]="editRow" button="none">
    <div class="modal-footer">
      <button nz-button type="button" (click)="close()">關閉</button>
      <button nz-button type="submit" [nzType]="'primary'" (click)="save(sf.value)" [disabled]="!sf.valid" [nzLoading]="http.loading" [acl]="'Root.Admin.Blogs.Post.Update'">儲存</button>
    </div>
  </sf>
</nz-modal>           

子產品路由 blogs.routing.ts

前端路由負責前端頁面的連接配接導航,一個子產品中的路由很簡單,隻要将元件導航起來即可。

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ACLGuard } from '@delon/acl';
import { BlogComponent } from './blog/blog.component';
import { PostComponent } from './post/post.component';

const routes: Routes = [
  { path: 'blog', component: BlogComponent, canActivate: [ACLGuard], data: { title: '部落格管理', reuse: true, guard: 'Root.Admin.Blogs.Blog.Read' } },
  { path: 'post', component: PostComponent, canActivate: [ACLGuard], data: { title: '文章管理', reuse: true, guard: 'Root.Admin.Blogs.Post.Read' } },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class BlogsRoutingModule { }
           

此外,還需要在根路由配置 routes.routing.ts 上注冊目前子產品的路由,并使用延遲加載特性

{ path: 'blogs', loadChildren: './blogs/blogs.module#BlogsModule', canActivateChild: [ACLGuard], data: { guard: 'Root.Admin.Blogs' } },           

子產品入口 blogs.module.ts

子產品入口聲明一個Angular子產品,負責引入其他的公開子產品,并聲明自己的元件/服務

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared';
import { BlogsRoutingModule } from './blogs.routing';
import { BlogComponent } from './blog/blog.component';
import { PostComponent } from './post/post.component';

@NgModule({
  imports: [
    CommonModule,
    SharedModule,
    BlogsRoutingModule
  ],
  declarations: [
    BlogComponent,
    PostComponent,
  ]
})
export class BlogsModule { }           

菜單資料

菜單資料指的是背景管理界面左側導航菜單,在 assets/osharp/app-data.json 檔案中進行配置。

{
  "app": {
    "name": "OSharp Framework",
    "description": "一個開源的基于 .NETCORE 的快速開發架構"
  },
  "menu": [{
    "text": "導航菜單",
    "i18n": "menu.nav",
    "group": true,
    "hideInBreadcrumb": true,
    "children": [{
      "text": "首頁",
      "i18n": "menu.nav.home",
      "icon": "anticon-dashboard",
      "link": "/dashboard",
      "acl": "Root.Admin.Dashboard"
    }]
  }, {
    "text": "業務子產品",
    "i18n": "menu.nav.business",
    "group": true,
    "hideInBreadcrumb": true,
    "children": [{
      "text": "部落格子產品",
      "group": "true",
      "icon": "anticon-border",
      "acl": "Root.Admin.Blogs",
      "children": [{
        "text": "部落格管理",
        "link": "/blogs/blog",
        "acl": "Root.Admin.Blogs.Blog"
      }, {
        "text": "文章管理",
        "link": "/blogs/post",
        "acl": "Root.Admin.Blogs.Post"
      }]
    }]
  }, {
    "text": "權限子產品",
    // ...
  }]
}               

前端權限控制

OSharp的Angular前端項目的權限控制,是基于 NG-ALAIN 的 ACL 功能來實作的。ACL 全稱叫通路控制清單(Access Control List),是一種非常簡單的基于角色權限控制方式。

前端權限控制流程

  • 代碼實作時,基于ACL功能,給需要權限控制的節點配置需要的功能點字元串。配置原則為:執行目前功能主要需要涉及後端的哪個功能點,就在ACL設定哪個功能點的字元串
  • 使用者登入時,緩存使用者的所有可用功能點集合
  • 前端頁面初始化或重新整理時(前端路由跳轉是無重新整理的,隻有主動F5或浏覽器重新整理時,才會重新整理),從後端擷取目前使用者的可用功能點集合
  • 将功能點集合緩存到 ACLService 中,作為ACL權限判斷的資料源,然後一切權限判斷的事就交給ACL了
  • ACL 根據 資料源中是否包含設定的ACL功能點 來決定是否顯示/隐藏菜單項或按鈕,進而達到前端權限控制的目的

NG-ALAIN 的 ACL 子產品的權限控制判斷依賴可為 角色 或 功能點,預設的設定中,角色資料類型是字元串,功能點資料類型是數值。OSharp的功能點是形如

Root.Admin.Blogs.Post

的字元串形式,要應用上 ACL,需要進行如下配置:

src/app/delon.module.ts 檔案的 fnDelonACLConfig() 函數中進行配置

export function fnDelonACLConfig(): DelonACLConfig {
  return {
    guard_url: '/exception/403',
    preCan: (roleOrAbility: ACLCanType) => {
      function isAbility(val: string) {
        return val && val.startsWith('Root.');
      }

      // 單個字元串,可能是角色也可能是功能點
      if (typeof roleOrAbility === 'string') {
        return isAbility(roleOrAbility) ? { ability: [roleOrAbility] } : { role: [roleOrAbility] };
      }
      // 字元串集合,每項可能是角色或是功能點,逐個處理每項
      if (Array.isArray(roleOrAbility) && roleOrAbility.length > 0 && typeof roleOrAbility[0] === 'string') {
        let abilities: string[] = [], roles: string[] = [];
        let type: ACLType = {};
        (roleOrAbility as string[]).forEach((val: string) => {
          if (isAbility(val)) abilities.push(val);
          else roles.push(val);
        });
        type.role = roles.length > 0 ? roles : null;
        type.ability = abilities.length > 0 ? abilities : null;
        return type;
      }
      return roleOrAbility;
    }
  } as DelonACLConfig;
}           

元件權限控制

元件中的權限控制

元件中的權限通常是按鈕權限,例如:

  • 清單行操作按鈕:

    通過

    acl

    控制功能權限,

    iif

    控制資料權限,共同決定一個按鈕是否可用。
{ text: '編輯', icon: 'edit', {==acl: 'Root.Admin.Blogs.Post.Update'==}, {==iif: row => row.Updatable==}, click: row => this.edit(row) },           

元件模闆的權限控制

元件模闆中各個 html 元素,都可以進行權限控制:

  • 按鈕權限:
<button nz-button (click)="create()" {==acl="Root.Admin.Blogs.Post.Create"==}><i nz-icon type="plus-circle" theme="outline"></i>新增</button>           

路由權限控制

路由的權限控制,通過 守衛路由 來實作,如果目前使用者沒有權限通路指定的路由連結,将會被攔截,未登入的使用者将跳轉到登入頁,已登入的使用者将跳轉到 403 頁面。

配置路由權限控制很簡單,需要使用守衛路由

[ACLGuard]

,然後在路由的

data

中配置

guard

為需要的功能點字元串:

{ path: 'blog', component: BlogComponent, {==canActivate: [ACLGuard]==}, data: { title: '部落格管理', reuse: true, {==guard: 'Root.Admin.Blogs.Blog.Read'==} } },           

菜單權限控制

菜單資料上也可以配置ACL權限控制,沒權限的菜單不會顯示

{
  "text": "部落格子產品",
  "group": "true",
  "icon": "anticon-border",
  "acl": "Root.Admin.Blogs",
  "children": [{
    "text": "部落格管理",
    "link": "/blogs/blog",
    "acl": "Root.Admin.Blogs.Blog"
  }, {
    "text": "文章管理",
    "link": "/blogs/post",
    "acl": "Root.Admin.Blogs.Post"
  }]
}           

權限控制效果示範

部落格資訊

根據部落格子產品需求分析的設定,部落格管理者 和 部落客 兩個角色對 部落格 的權限分别如下:

-- 部落格管理者 部落客
檢視
申請
稽核
修改

部落客-部落格

部落客隻能檢視自己的部落格資料,能申請部落格,不能稽核部落格,申請成功之後,申請按鈕隐藏。

[開源]OSharpNS 步步為營系列 - 5. 添加前端Angular子產品

部落格管理者-部落格

部落格管理者不能申請部落格,可以稽核新增的部落格,部落格稽核通過之後不能再次稽核。

[開源]OSharpNS 步步為營系列 - 5. 添加前端Angular子產品

文章資訊

根據部落格子產品需求分析的設定,部落格管理者 和 部落客 兩個角色對 文章 的權限分别如下:

-- 部落格管理者 部落客
檢視
新增
修改
删除

部落客-文章

部落客能新增文章,隻能檢視、更新、删除自己的文章

[開源]OSharpNS 步步為營系列 - 5. 添加前端Angular子產品

部落格管理者-文章

部落格管理者不能新增文章,能檢視、更新、删除所有文章

[開源]OSharpNS 步步為營系列 - 5. 添加前端Angular子產品

步步為營教程總結

本系列教程為OSharp入門初級教程,通過一個 部落格子產品 執行個體來示範了使用OSharp架構進行業務開發所涉及到的項目分層,代碼布局組織,業務代碼實作規範,以及業務實作過程中常用的架構基礎設施。讓開發人員對使用OSharp架構進行項目開發的過程、使用難度等方面有一個初步的認識。

這隻是一個簡單的業務示範,限于篇幅,不可能對架構的技術細節進行很詳細的講解,後邊,我們将會分Pack子產品來對每個子產品的設計思路,技術細節進行詳細的解說。

繼續閱讀