前言
如果有一系列的页面布局很类似,为了节省时间,我们可以把这些类似的页面所通用的属性和方法抽离成一个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 = 1pageSize = 10tableData = []tableTotal = 0public 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, // 页面唯一keypublic 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 = configif (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.startthis.config.query.endTimeStr = tg.endif (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: trueshow: truelabel: "对比统计"icon: "icon-pie1"key: "pieAndBar"- selected: trueshow: falselabel: "趋势统计"icon: "icon-pie1"key: "lineTrend"barStyleConfig:grid: bottom: 30xAxis:axisLabel: width: 80modules: demoPage: leftPanelExpand: truebarStyleConfig:grid: bottom: 45xAxis: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.startTimeStrparams.endTimeStr = this.config.query.endTimeStr}this.apiService.getAccountListByPageApi(removeNullProperty(params)).then((res: resType) => {if (res.resultStat == "0") {this.tableData = res.data.listthis.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}
}