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
    }
}
相关推荐
格子软件13 小时前
2026年GEO优化系统源码级状态机与多模型调度拆解
java·前端·vue.js·人工智能·vue·geo
HUMHSX14 小时前
Vue 项目启动全流程解析:从入口文件到全局指令注册与页面渲染
前端·javascript·vue.js
有颜有货14 小时前
PMC生产排产的4种算法,一次讲清
java·服务器·前端
小虎牙00714 小时前
Android kotlin图片库Coil源码详解
android·前端
随风一样自由14 小时前
【前端领域】前端开发核心应用场景与落地实践
前端·前端框架
an3174214 小时前
弹窗数据流设计的两种高阶架构实践
前端·vue.js·架构
谢尔登15 小时前
【React】 状态管理方案
前端·react.js·前端框架
用户21366100357215 小时前
Vue商品详情与放大镜组件
前端·javascript
半个落月15 小时前
从Tapas小Demo理清localStorage、事件与this
前端·javascript
李明卫杭州15 小时前
Vue2 中 v-model 处理不同数据结构的技巧
前端·javascript·vue.js