Angular BaseView抽离页面公用属性

前言

如果有一系列的页面布局很类似,为了节省时间,我们可以把这些类似的页面所通用的属性和方法抽离成一个BaseView,让其它页面继承该基础页面,同时将一些经常改变的属性和差异的属性写到配置文件里。例如树容器初始时是否展开、某些图表是否显示等都可以写到配置文件里面。本文将带你实现该功能,抽离出BaseView页面组件,鉴于json文件无法写注释的情况,配置文件采取yml的格式

页面设计

组件抽离

BaseViewComponent

import { Injectable, OnDestroy, OnInit } from "@angular/core";

import { ConfigService } from "@app/core/config/config.service";
import { Subject } from "rxjs";
import { takeUntil } from "rxjs/operators";
import { deepMergeKey } from "../utils";
import { HandlebarCalendarModelType } from "./baseView.type";
import { TimeRange } from "topdsm-lib/core/util"

import dayjs from 'dayjs';
var customParseFormat = require('dayjs/plugin/advancedFormat')


@Injectable()
export class BaseViewComponent implements OnInit, OnDestroy {

    /** 页面通用属性 */
    pageNum = 1
    pageSize = 10
    tableData = []
    tableTotal = 0


    public unsubscribe$ = new Subject<void>();
    /** 原始config */
    public originalConfig: Record<string, any> = null

    /** 页面config */
    public config: Record<string, any> = {
        leftPanelExpand: true,
        handlebarCalendarModel: [],
        query: {}
    }
    constructor(
        public viewKey: string, // 页面唯一key
        public configService: ConfigService // 用来读取json配置文件
    ) {
        console.log("BaseViewComponent constructor");

    }
    ngOnInit(): void {
        console.log();

        this.configService.change.pipe(takeUntil(this.unsubscribe$)).subscribe(async (config) => {
            console.log(config);
            if (config) {
                this.originalConfig = config
                if (this['configServiceReaderBefore']) {
                    await this['configServiceReaderBefore'](config)
                }
                this.handleConfig()
                if (this['configServiceReaderAfter']) {
                    await this['configServiceReaderAfter'](config)
                }
            }
        });
    }
    ngOnDestroy(): void {
        const { unsubscribe$ } = this;
        unsubscribe$.next();
        unsubscribe$.complete();
    }

    handleConfig() {
        deepMergeKey(this.config, true, this.originalConfig.global, this.originalConfig?.modules?.[this.viewKey])
        this.handleCalendarTime()
        this.handleBarBtn()
        console.log(this.config);
    }

    /**
    * handlebar 日历组件初始值处理,
    * 获取开始时间和结束时间,该逻辑可以根据自己的业务场景自定义
    */
    handleCalendarTime() {
        let tg = {
            start: "",
            end: ""
        }
        switch (this.config.handlebarCalendarModelType) {
            case HandlebarCalendarModelType.CUSTOM:
                if (this.config.handlebarCalendarModel.length === 1) {
                    tg.start = this.config.handlebarCalendarModel[0]
                    tg.end = dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss").substring(0, 10) + " 23:59:59"
                } else if (this.config.handlebarCalendarModel.length === 2) {
                    tg.start = this.config.handlebarCalendarModel[0]
                    tg.end = this.config.handlebarCalendarModel[1]
                }
                break;
            case HandlebarCalendarModelType.LASTYEAY: // 上一年
                tg = TimeRange.getLastYearRange()
                break;
            case HandlebarCalendarModelType.LASTQUATER: // 上一季度
                tg = TimeRange.getLastQuarterRange()
                break;
            case HandlebarCalendarModelType.LASTMONTH: // 上一月
                tg = TimeRange.getLastMonthRange()
                break;

            case HandlebarCalendarModelType.LAST7DAY: // 近7天
                tg = {
                    start: TimeRange.getLast7dayRange().start,
                    end: dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss").substring(0, 10) + " 23:59:59"
                }
                break;
            case HandlebarCalendarModelType.MONTH: // 本月
                tg = TimeRange.getMonthRange()
                break;

            case HandlebarCalendarModelType.QUATER: // 本年度
                tg = TimeRange.getQuarterRange()
                break;

            case HandlebarCalendarModelType.YEAR: // 本年度
                tg = TimeRange.getYearRange()
                break;
            default:
                break;
        }

        if (tg.start !== "" && tg.end !== "") {
            tg.start = tg.start.substring(0, 10) + " 00:00:00"
            tg.end = tg.end.substring(0, 10) + " 23:59:59"
        }
        this.config.query.startTimeStr = tg.start
        this.config.query.endTimeStr = tg.end

        if (tg.start !== "" && tg.end !== "") {
            this.config.handlebarCalendarModel = [
                dayjs(this.config.query.startTimeStr, "YYYY-MM-DD HH:mm:ss").toDate(),
                dayjs(this.config.query.endTimeStr, "YYYY-MM-DD HH:mm:ss").toDate(),
            ]
        } else {
            this.config.handlebarCalendarModel = []
        }
    }

    handleBarBtn() {
        let btnSelected = {

        }
        this.config.handlebarRightBtn = this.config.handlebarRightBtn.filter(item => item.show)
        
        this.config.handlebarRightBtn.forEach(item => {
            btnSelected[item.key] = item.selected
        })
        this.config.handlebarRightBtnSelected = btnSelected
    }
}

时间段枚举

export enum HandlebarCalendarModelType {
    /** 自定义 */
    CUSTOM = "0",
    /** 上一年 */
    LASTYEAY = "1",
    /** 上一季度 */
    LASTQUATER = "2",
    /** 上一月 */
    LASTMONTH = "3",
    /** 上一周 */
    LASTWEEK = "4",
    /** 本周 */
    WEEK = "5",
    /** 本月 */
    MONTH = "6",
    /** 本季度 */
    QUATER = "7",
    /** 本年度 */
    YEAR = "8",
    /** 近7天 */
    LAST7DAY = "9"
}

属性合并的函数

/**
 * Deep merge object.
 *
 * 深度合并对象
 *
 * @param original 原始对象
 * @param arrayProcessMethod 数组处理方式
 *  - `true` 表示替换新值,不管新值为哪种类型
 *  - `false` 表示会合并整个数组(将旧数据与新数据合并成新数组)
 * @param objects 要合并的对象
 */
 export function deepMergeKey(original: unknown, arrayProcessMethod: boolean, ...objects: NzSafeAny[]): NzSafeAny {
  if (Array.isArray(original) || typeof original !== 'object') return original;

  const isObject = (v: unknown): boolean => typeof v === 'object';

  const merge = (target: NzSafeAny, obj: NzSafeAny): NzSafeAny => {
    Object.keys(obj)
      .filter(key => key !== '__proto__' && Object.prototype.hasOwnProperty.call(obj, key))
      .forEach(key => {
        const fromValue = obj[key];
        const toValue = target[key];
        if (Array.isArray(toValue)) {
          target[key] = arrayProcessMethod ? fromValue : [...toValue, ...fromValue];
        } else if (typeof fromValue === 'function') {
          target[key] = fromValue;
        } else if (fromValue != null && isObject(fromValue) && toValue != null && isObject(toValue)) {
          target[key] = merge(toValue, fromValue);
        } else {
          target[key] = deepCopy(fromValue);
        }
      });
    return target;
  };

  objects.filter(v => v != null && isObject(v)).forEach(v => merge(original, v));

  return original;
}

ConfigService

读取yml配置文件

import { Injectable } from "@angular/core";
import { environment } from "@env/environment";
import { BehaviorSubject, Observable } from "rxjs";
import yaml from "js-yaml"
import axios from "axios"
@Injectable({
    providedIn: 'root'
})
export class ConfigService {
    private change$ = new BehaviorSubject(null);
    constructor() {
        this.getGlobalConfig()
    }

    get change(): Observable<any> {
        return this.change$.asObservable();
    }

    getGlobalConfig() {
        return new Promise((resolve, reject) => {
            let url = "/assets/config.yml"
            if(environment.production){
                url = environment.assetBaseUrl + "/assets/config.yml"
            }
            axios.get(url).then(res => {
                const config = yaml.load(res.data)
                this.change$.next(config);             
            }).catch(err => {
                reject(err)
            })
        })
    }
}

config.yml

global: 
  handlebarCalendarModelType: "0"
  handlebarCalendarModel: 
     - "2023-01-01 00:00:00"
  leftPanelWidth: "200px" # 左侧树容器宽度
  leftPanelExpand: true   # 左侧容器初始是否展开 true: 展开  false: 收起
  handlebarRight: true # 是否展示 handlebar右侧的操作按钮
  handlebarRightBtn: # hanlebar右侧操作按钮 控制图标统计区域的显示与否
    - selected: true      # 是否选中
      show: true          # 是否显示
      label: "总量统计"
      icon: "icon-proxy"
      key: "cardNumStatis" # 每个按钮都应该有唯一的key
    - selected: true
      show: true
      label: "对比统计"
      icon: "icon-pie1"
      key: "pieAndBar"
    - selected: true
      show: false
      label: "趋势统计"
      icon: "icon-pie1"
      key: "lineTrend"

  barStyleConfig:
    grid: 
      bottom: 30
    xAxis:
      axisLabel: 
        width: 80

modules: 
  demoPage: 
    leftPanelExpand: true
    barStyleConfig:
      grid: 
        bottom: 45
      xAxis:
        axisLabel:
          overflow: "truncate" # 截断
          rotate: 330  # 旋转度数

demo

demo-component.ts

import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { ActivatedRoute, NavigationEnd, Router, RouterEvent } from "@angular/router";
import { BaseViewComponent } from "@app/core/config/base-view.component";
import { ConfigService } from "@app/core/config/config.service";
import { DEMOPAGE } from "@app/core/config/view-key";
import { removeNullProperty } from "@app/core/utils";
import { AccountAssetService } from "@app/services/data-asset-management/account-asset/account-asset.service";
import { format } from "date-fns";

@Component({
  selector: 'app-demo',
  templateUrl: './demo.component.html',
  styleUrls: ['./demo.component.less']
})
export class DemoComponent extends BaseViewComponent {

  q = {
   
  }

  constructor(
    private router: Router,
    public activatedRoute: ActivatedRoute,
    public configService: ConfigService,
    private apiService: AccountAssetService,
  ) {
    super(DEMOPAGE, configService)
    console.log("DemoComponent constructor");
  }

  ngOnInit(): void {
    console.log("DemoComponent ngOnInit");

    super.ngOnInit()
  }
  ngOnDestroy(): void {
    console.log("DemoComponent ngOnDestroy");
    super.ngOnDestroy()
  }

  configServiceReaderAfter(config) {
    console.log("configServiceReaderAfter...");
    return new Promise(async (resolve, reject) => {
      this.refsh()
      resolve(null)
    })
  }

  async refsh() {
    //await this.getAccountTypeTreeL1()
    if (this.config.handlebarRight) {
      this.getPieChart1Data()
      this.getPieChart2Data()
      this.getBarChartData()
      this.getAccountCard()
    }

    this.getData()
  }

  getAccountCard() {
  
  }

  getPieChart1Data() {
   
  }

  getPieChart2Data() {
   
  }

  getBarChartData() {
  
  }

  getData() {

    let params: { [key: string]: any } = {
      ...this.q,
      pageNum: this.pageNum,
      pageSize: this.pageSize,
    }

    if (this.config.handlebarRight) {
      params.startTimeStr = this.config.query.startTimeStr
      params.endTimeStr = this.config.query.endTimeStr
    }
    this.apiService.getAccountListByPageApi(removeNullProperty(params)).then((res: resType) => {
      if (res.resultStat == "0") {
        this.tableData = res.data.list
        this.tableTotal = res.data.total
      }
    })
  }

   /** handlebar 操作栏 */
   handleChange(e: any) {
    console.log(e);
    if(e.type === "button"){
      this.config.handlebarRightBtnSelected[e.data.key] = e.data.selected
    }else if(e.type === "calendar"){
      if(e.data.length === 2){
          this.config.query = {
            startTimeStr: format(e.data[0], 'yyyy-MM-dd HH:mm:ss').substring(0,10)+ " 00:00:00",
            endTimeStr: format(e.data[1], 'yyyy-MM-dd HH:mm:ss').substring(0,10)+ " 23:59:59",
          }
      }else{
        this.config.query = {
          startTimeStr: "",
          endTimeStr: ""
        }
      }
      this.refsh()
    }

  }

}

合并后的配置对象config

{
    "leftPanelExpand": true,
    "handlebarCalendarModel": [
        "2022-12-31T16:00:00.000Z",
        "2024-02-04T15:59:59.000Z"
    ],
    "query": {
        "startTimeStr": "2023-01-01 00:00:00",
        "endTimeStr": "2024-02-04 23:59:59"
    },
    "handlebarCalendarModelType": "0",
    "leftPanelWidth": "200px",
    "handlebarRight": true,
    "handlebarRightBtn": [
        {
            "selected": true,
            "show": true,
            "label": "总量统计",
            "icon": "icon-proxy",
            "key": "cardNumStatis"
        },
        {
            "selected": true,
            "show": true,
            "label": "对比统计",
            "icon": "icon-pie1",
            "key": "pieAndBar"
        }
    ],
    "barStyleConfig": {
        "grid": {
            "bottom": 45
        },
        "xAxis": {
            "axisLabel": {
                "width": 80,
                "overflow": "truncate",
                "rotate": 330
            }
        }
    },
    "handlebarRightBtnSelected": {
        "cardNumStatis": true,
        "pieAndBar": true
    }
}
相关推荐
开心工作室_kaic35 分钟前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā36 分钟前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年2 小时前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder2 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_882727572 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架
SoaringHeart2 小时前
Flutter进阶:基于 MLKit 的 OCR 文字识别
前端·flutter
会发光的猪。3 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
天下代码客3 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js
猫爪笔记3 小时前
前端:HTML (学习笔记)【1】
前端·笔记·学习·html
前端李易安3 小时前
Webpack 热更新(HMR)详解:原理与实现
前端·webpack·node.js