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
    }
}
相关推荐
GISer_Jing5 分钟前
前端面试常考题目详解
前端·javascript
Boilermaker19921 小时前
【Java EE】SpringIoC
前端·数据库·spring
中微子1 小时前
JavaScript 防抖与节流:从原理到实践的完整指南
前端·javascript
天天向上10241 小时前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
芬兰y2 小时前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁2 小时前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry2 小时前
Fetch 笔记
前端·javascript
拾光拾趣录2 小时前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟2 小时前
vue3,你看setup设计详解,也是个人才
前端
Lefan2 小时前
一文了解什么是Dart
前端·flutter·dart