【从零基础开发物联网AI智控系统】优雅草星云鸿蒙端适配详解:集成deveco studio,扩展告警中心与Canvas折线图绘制指南

【05】20250416优雅草星云物联网AI智控系统从0开发鸿蒙端适配-deveco studio-增加告警中心相关卡片页面WarningCardWidget相关-增加Canvas 绘制折线图-Canvas 绘制柱状图-首页-优雅草卓伊凡

项目背景

本项目渊源已久,优雅草2025年发布,PC端已经发布,将在4月完成成品发布,目前由于考虑到鸿蒙端用户使用,毕竟新一代纯血鸿蒙harmonyos next已经不再支持安卓android,因此优雅草团队必须考虑把鸿蒙端也开发上,以下实战过程完整记录了整个开发过程,优雅草卓伊凡审核代码,记录并更新,再次感谢优雅草团队所有同事们的努力,鸿蒙端为了加速鸿蒙生态已经开源地址供查看,关于优雅草星云物联网AI智控系统可以关注优雅草官网,在本文梳理的过程中发现在后面的页面中,只需要直接写出代码并且附上注释,卓伊凡相信大家应该看得懂,针对比较复杂的部分会单独做独立解释,每一个页面都可以对应查看,基本都会截图,如果没有截图的那就是真的没截图,另外注意由于文章篇幅关系不会把所有页面都一一写,写一些重要页面发布在文档中,每次的改进,更多内容参考大家直接观看git并且下载。

项目开源代码地址

优雅草星云智控鸿蒙端harmonyos: 优雅草星云智控AI智能设备监控系统-移动端鸿蒙端-成都星云智控科技有限公司是成都市一颗优雅草科技有限公司的子公司

鸿蒙端运行环境

deveco 5.0.4版本

实战过程

很多天没来帮忙注释文档以及 发布 过程了, 拆卸每个页面,由于实在工作太忙

Fast-forward
entry/src/main/ets/pages/Index.ets | 2 +-
entry/src/main/ets/pages/LoginView.ets | 58 ++++++-
entry/src/main/ets/pages/Welcome.ets | 10 +-
entry/src/main/module.json5 | 5 +
lib/CommonConstLibrary/Index.ets | 3 +
…/src/main/ets/common/common.ets | 15 ++
…/src/main/ets/enum/ApiErrorCode.ets | 15 ++
…/src/main/ets/enum/SeverityLevel.ets | 9 ++
…/src/main/ets/model/ApiModel.ets | 45 ++++++
…/main/ets/components/common/LoadingWidget.ets | 24 +++
…/ets/components/common/StatisticsWidget.ets | 18 ++-
…/main/ets/components/home/TopologyMapWidget.ets | 118 ++++++++++++++
…/ets/components/settings/SettingInputWidget.ets | 7 +-
…/components/settings/SettingSelectWidget.ets | 3 +-
…/ets/components/settings/SettingSettingItem.ets | 2 +-
…/components/warning/WarningMessageWidget.ets | 27 +++-
…/ets/components/warning/WarningSearchWidget.ets | 12 +-
…/src/main/ets/view/HomeView.ets | 21 ++-
…/src/main/ets/view/WarningCenterView.ets | 121 ++++++++———-
…/main/resources/base/media/ic_topology_map.png | Bin 0 -> 3079030 bytes
lib/UtilsLibrary/Index.ets | 5 +-
…/src/main/ets/lazyForEach/ApiDataSource.ets | 7 +
lib/UtilsLibrary/src/main/ets/utils/Request.ets | 170 +++++++++++++++++++++
23 files changed, 607 insertions(+), 90 deletions(-)
create mode 100644 lib/CommonConstLibrary/src/main/ets/enum/ApiErrorCode.ets
create mode 100644 lib/CommonConstLibrary/src/main/ets/enum/SeverityLevel.ets
create mode 100644 lib/CommonConstLibrary/src/main/ets/model/ApiModel.ets
create mode 100644 lib/ComponentLibrary/src/main/ets/components/common/LoadingWidget.ets
create mode 100644 lib/ComponentLibrary/src/main/ets/components/home/TopologyMapWidget.ets
create mode 100644 lib/ComponentLibrary/src/main/resources/base/media/ic_topology_map.png
create mode 100644 lib/UtilsLibrary/src/main/ets/lazyForEach/ApiDataSource.ets
create mode 100644 lib/UtilsLibrary/src/main/ets/utils/Request.ets

今天拉了一下发现代码已经开始 写到 对接接口部分的了。

lib/ComponentLibrary/src/main/ets/components/common/BarChartNewWidget.ets

/*
 * Copyright (c) 2025.成都市一颗优雅草科技有限公司
 * author:卓伊凡-优雅草技术总监
 * project:优雅草星云物联网智控AI系统
 */

import { BarChartDateModel, commonColor, CommonConst } from "@wcmzllx/common-const-library";
import { getRandomIntInclusive } from "@wcmzllx/utils-library";
import { vp2 } from "../../common/NewVp";

// 定义图表的上边距
const MARGIN_TOP = 20;
// 定义图表的下边距
const MARGIN_BOTTOM = 20;
// 定义图表的右边距
const MARGIN_RIGHT = 30;
// 定义柱子之间的间距
const BAR_GAP = 45; // 柱子间距
// 定义字体大小
const FONT_SIZE = 10; // 修改字体大小
// 定义柱子的颜色
const BAR_COLOR = "#16CA46";

// 定义一个结构体用于绘制柱状图
@ComponentV2
export struct BarChartNewWidget {
    // 定义柱状图的数据源
    @Param data: BarChartDateModel[] = [
        { xAxis: '设备1', value: getRandomIntInclusive(0, 100) },
        { xAxis: '设备2', value: getRandomIntInclusive(0, 100) },
        { xAxis: '设备3', value: getRandomIntInclusive(0, 100) },
        { xAxis: '设备4', value: getRandomIntInclusive(0, 100) },
        { xAxis: '设备5', value: getRandomIntInclusive(0, 100) },
    ];

    // 初始化渲染上下文设置
    private settings: RenderingContextSettings = new RenderingContextSettings(true)
    // 创建用于绘制网格的2D渲染上下文
    private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
    // 创建用于绘制柱子的2D渲染上下文
    private context1: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
    // 定义图表的宽度
    @Local chartWidth: number = 0;
    // 定义图表的高度
    @Local chartHeight: number = 0;
    // 转换字体大小为像素值
    private FONT_SIZE: number = vp2px(vp2.vp2(FONT_SIZE));
    // 定义Y轴单位
    @Param yUnit: string = ""
    // 定义图表的左边距
    private MARGIN_LEFT = 30;

    // 监听数据变化,当数据长度变化时调用以下方法
    @Monitor("data.length")
    onDataChange() {
        // 调用绘制网格的方法
        this.onDrawGrid();
        // 调用绘制柱子的方法
        this.onDrawBars();
    }

    // 在组件即将出现时调用,根据Y轴单位调整左边距
    aboutToAppear(): void {
        for (let i = 0; i < this.yUnit.length; i++) {
            this.MARGIN_LEFT += 5;
        }
    }

    // 构建组件布局
    build() {
        Column() {
            Stack() {
                // 创建用于绘制网格的Canvas
                Canvas(this.context)
                    .onReady(() => this.onDrawGrid())
                    .width(CommonConst.GLOBAL_FULL_SCREEN)
                    .height(CommonConst.GLOBAL_FULL_SCREEN)

                // 创建用于绘制柱子的Canvas
                Canvas(this.context1)
                    .onReady(() => this.onDrawBars())
                    .width(CommonConst.GLOBAL_FULL_SCREEN)
                    .height(CommonConst.GLOBAL_FULL_SCREEN)
            }
            // 当组件大小变化时,更新图表宽度和高度
            .onSizeChange((_, newValue) => {
                this.chartWidth = newValue.width as number;
                this.chartHeight = newValue.height as number;
            })
        }
    }

    // 绘制网格的方法
    onDrawGrid() {
        if (!this.chartWidth || !this.chartHeight) return;

        // 清除画布
        this.context.clearRect(0, 0, this.chartWidth, this.chartHeight);

        // Y轴计算
        const maxValue = Math.max(...this.data.map(d => d.value));
        const maxYValue = Math.ceil(maxValue / 10) * 10 || 10;

        // 样式设置
        this.context.strokeStyle = 'rgba(255, 255, 255, 0.1)'// 'rgba(235, 235, 235, 0.6)';
        this.context.lineWidth = 1;
        this.context.fillStyle = '#FF90ACE9';
        this.context.font = `${this.FONT_SIZE}px sans-serif`; // 修改字体大小

        const plotHeight = this.chartHeight - MARGIN_TOP - MARGIN_BOTTOM;

        // 绘制Y轴网格
        const ySteps = 4;
        for (let i = 0; i <= ySteps; i++) {
            const y = MARGIN_TOP + plotHeight * (1 - i/ySteps);
            this.context.beginPath();
            this.context.moveTo(this.MARGIN_LEFT, y);
            this.context.lineTo(this.chartWidth - MARGIN_RIGHT, y);
            this.context.stroke();

            // Y轴标签
            this.context.textAlign = 'right';
            this.context.textBaseline = 'middle';
            this.context.fillText(
                `${Math.round(maxYValue * (i/ySteps))}` + this.yUnit,
                this.MARGIN_LEFT - 10,
                y
            );
        }

        // 绘制X轴标签
        this.context.fillStyle = '#FF90ACE9';
        this.context.textAlign = 'center';
        this.context.textBaseline = 'top';
        const xAxisY = this.chartHeight - MARGIN_BOTTOM + 6;

        this.data.forEach((item, index) => {
            const barWidth = this.getBarWidth();
            const x = this.MARGIN_LEFT + index * (barWidth + BAR_GAP) + barWidth/2;

            // 处理最后一个标签溢出
            const textWidth = this.context.measureText(item.xAxis).width;
            const maxX = this.chartWidth - MARGIN_RIGHT - textWidth/2;
            const finalX = x > maxX && index === this.data.length-1 ? maxX : x;

            this.context.fillText(item.xAxis, finalX, xAxisY);
        });
    }

    // 绘制柱子的方法
    onDrawBars() {
        if (!this.chartWidth || !this.chartHeight) return;

        // 清除画布
        this.context1.clearRect(0, 0, this.chartWidth, this.chartHeight);

        // 计算最大值和Y轴的最大值
        const maxValue = Math.max(...this.data.map(d => d.value));
        const maxYValue = Math.ceil(maxValue / 10) * 10 || 10;
        const plotHeight = this.chartHeight - MARGIN_TOP - MARGIN_BOTTOM;

        // 遍历数据源,绘制柱子
        this.data.forEach((item, index) => {
            const barWidth = this.getBarWidth();
            const x = this.MARGIN_LEFT + index * (barWidth + BAR_GAP);
            const height = (item.value / maxYValue) * plotHeight;

            // 绘制柱子
            this.context1.fillStyle = BAR_COLOR;
            this.context1.fillRect(
                x,
                MARGIN_TOP + plotHeight - height,
                barWidth,
                height
            );
        });
    }

    // 计算柱子宽度的方法
    private getBarWidth(): number {
        const totalBars = this.data.length;
        const availableWidth = this.chartWidth - this.MARGIN_LEFT - MARGIN_RIGHT;
        return (availableWidth - (totalBars - 1) * BAR_GAP) / totalBars;
    }
}

lib/ComponentLibrary/src/main/ets/view/HomeView.ets 首页

/*
 * Copyright (c) 2025.成都市一颗优雅草科技有限公司
 * author:卓伊凡-优雅草技术总监
 * project:优雅草星云物联网智控AI系统
 */

import { BarChartDateModel, commonColor, CommonConst } from "@wcmzllx/common-const-library"
import { uiWindows } from "../common/UIWindows"
import { vp2 } from "../common/NewVp"
import { BarChartWidget } from "../components/common/BarChartWidget"
import { HomeCardWidget } from "../components/home/HomeCardWidget"
import { getRandomIntInclusive, navStackUtils } from '@wcmzllx/utils-library';
import { BarChartNewWidget } from "../components/common/BarChartNewWidget"
import { LineChartWidget } from "../components/common/LineChartWidget"
import { TopologyMapWidget } from "../components/home/TopologyMapWidget"

// 定义文本样式的方法,使文本样式统一且易于管理
@Extend(Text)
function textStyles(size: number, color: ResourceColor, weight: FontWeight, lineHeight = 11) {
    .fontSize(vp2.vp2(size))
    .fontColor(color)
    .fontWeight(weight)
    .lineHeight(vp2.vp2(lineHeight))
}

// 首页视图组件,包含多个子组件和数据展示
@Preview
@ComponentV2
export struct HomeView {
    // 导航栈,用于页面跳转
    private indexNavStack: NavPathStack = navStackUtils.getIndexStack();
    // 本地数据,用于展示在首页的不同卡片中
    @Local data: string[][] = [
        ["CPU TOP5", "%"],
        ["风扇状态", "Rpm"],
        ["内存 TOP5", "%"],
        ["接口量 TOP5", "%"],
        ["风扇状态", "℃"],
        ["丢包率 TOP5", ""],
        ["光功率", "mw"]
    ]
    // 柱状图数据模型数组,用于展示告警趋势
    @Local lineData: BarChartDateModel[] = [
        { xAxis: '9月12日', value: getRandomIntInclusive(0, 100) },
        { xAxis: '9月13日', value: getRandomIntInclusive(0, 100) },
        { xAxis: '9月14日', value: getRandomIntInclusive(0, 100) },
        { xAxis: '9月15日', value: getRandomIntInclusive(0, 100) },
        { xAxis: '9月16日', value: getRandomIntInclusive(0, 100) },
    ];

    // 构建首页视图
    build() {
        Column() {
            // 首页的标题与其它都不同,单独写
            this.titleBuilder()

            Column() {
                Scroll() {
                    Column({ space: vp2.vp2(15) }) {
                        Column({ space: 15 }) {

                            Row({ space: vp2.vp2(19.5) }) {

                                Stack() {
                                    Image($r("app.media.ic_btn_unselect"))
                                        .width(CommonConst.GLOBAL_FULL_SCREEN)
                                        .height(CommonConst.GLOBAL_FULL_SCREEN)
                                    Text("拓扑图")
                                        .textStyles(14, commonColor.WHITE_COLOR, FontWeight.Normal, 11)
                                }
                                .padding(vp2.vp2(6))
                                .width(vp2.vp2(75))
                                .height(vp2.vp2(38))
                                .onClick(() => {
                                    this.indexNavStack.pushPathByName("NetworkTopologyView", undefined)
                                })

                                Stack() {
                                    Image($r("app.media.ic_btn_unselect"))
                                        .width(CommonConst.GLOBAL_FULL_SCREEN)
                                        .height(CommonConst.GLOBAL_FULL_SCREEN)
                                    Text("机房图")
                                        .textStyles(14, commonColor.WHITE_COLOR, FontWeight.Bold, 11)
                                }
                                .padding(vp2.vp2(6))
                                .width(vp2.vp2(75))
                                .height(vp2.vp2(38))
                            }
                            .height(vp2.vp2(38))

                            Row() {
                                // Image($r("app.media.ic_home"))
                                //     .width(CommonConst.GLOBAL_FULL_SCREEN)
                                //     .height(CommonConst.GLOBAL_FULL_SCREEN)
                                //     .onClick(()=>{
                                //         this.indexNavStack.pushPathByName("NetworkTopologyView", undefined)
                                //     })
                                //     .autoResize(true)
                                //     .syncLoad(true)
                                TopologyMapWidget()
                            }
                            .width(CommonConst.GLOBAL_FULL_SCREEN)
                            .layoutWeight(1)
                        }
                        .margin({ top: vp2.vp2(12) })
                        .padding(vp2.vp2(10))
                        .width(CommonConst.GLOBAL_FULL_SCREEN)
                        .height(vp2.vp2(290))
                        .borderRadius(vp2.vp2(5))
                        .backgroundColor("#FF102042")

                        HomeCardWidget({
                            title: "设备类别",
                            contentView: this.contentBuilder,
                            buttonContent: "查看详情"
                        })

                        HomeCardWidget({
                            title: "平均响应",
                            contentView: this.contentBuilder,
                            buttonContent: "查看详情"
                        })

                        HomeCardWidget({
                            title: "告警趋势",
                            contentView: this.lineContentBuilder
                        })

                        HomeCardWidget({
                            title: "核心设备情况",
                            contentView: this.meterContentBuilder
                        })

                        ForEach(this.data, (item: string[]) => {
                            HomeCardWidget({
                                title: item[0],
                                contentView: () => {
                                    this.barContentBuilder(item[1])
                                }
                            })
                        })
                    }
                    .width(CommonConst.GLOBAL_FULL_SCREEN)
                    .padding({
                        left: vp2.vp2(15),
                        right: vp2.vp2(15),
                        bottom: vp2.vp2(uiWindows.getNavigationHeight())
                    })
                }
            }
            .layoutWeight(1)
        }

        .backgroundColor("#FF000B11")
    }

    // 展示核心设备情况的内容构建器
    @LocalBuilder
    meterContentBuilder() {
        Row() {
            Column() {
                Image($r("app.media.ic_meter"))
                    .width(vp2.vp2(118))
                    .aspectRatio(1)

                Text("入口流量")
                    .textStyles(13, "#FFADC0E6", FontWeight.Regular, 8)
            }

            Blank()

            Column() {
                Image($r("app.media.ic_meter"))
                    .width(vp2.vp2(118))
                    .aspectRatio(1)
                Text("出口流量")
                    .textStyles(13, "#FFADC0E6", FontWeight.Regular, 8)
            }
        }
        .padding({ left: vp2.vp2(51.5), right: vp2.vp2(51.5) })
        .width(CommonConst.GLOBAL_FULL_SCREEN)
        .height(vp2.vp2(160))
    }

    // 展示告警趋势的内容构建器
    @LocalBuilder
    lineContentBuilder() {
        Row() {
            LineChartWidget({
                data: this.lineData,
            })
        }
        .padding({ bottom: vp2.vp2(6) })
    }

    // 通用内容构建器,用于展示设备类别和平均响应
    @Builder
    contentBuilder() {
        BarChartNewWidget()
            .width(CommonConst.GLOBAL_FULL_SCREEN)
            .height(vp2.vp2(160))
    }

    // 柱状图内容构建器,根据传入的单位展示不同的数据
    @Builder
    barContentBuilder(yUnit: string) {
        BarChartNewWidget({ yUnit: yUnit })
            .width(CommonConst.GLOBAL_FULL_SCREEN)
            .height(vp2.vp2(160))
    }

    // 标题构建器,用于展示首页的标题和品牌信息
    @Builder
    titleBuilder() {
        Column() {
            Blank().height(vp2.vp2(59))

            Stack({ alignContent: Alignment.End }) {
                Image($r("app.media.ic_home_top"))
                    .width(CommonConst.GLOBAL_FULL_SCREEN)
                    .height(vp2.vp2(60.5))
                    .offset({
                        y: vp2.vp2(10)
                    })
                Row() {
                    Row() {
                        Image($r("app.media.ic_brand_icon"))
                            .width(vp2.vp2(70))
                            .height(vp2.vp2(35))

                        Text("优雅草星云物联网AI智控系统").fontSize(vp2.vp2(17)).fontColor(commonColor.WHITE_COLOR)
                    }
                    .width(vp2.vp2(262))
                    .height(vp2.vp2(35))
                    .offset({ x: vp2.vp2(-15) })
                }
                .width(CommonConst.GLOBAL_FULL_SCREEN)
                .justifyContent(FlexAlign.Center)
            }
            .width(CommonConst.GLOBAL_FULL_SCREEN)
            .height(vp2.vp2(44))
        }
        //.backgroundColor("#FF091429")
        .width(CommonConst.GLOBAL_FULL_SCREEN)
    }
}

lib/ComponentLibrary/src/main/ets/components/warning/WarningChartCardWidget.ets 告警中心 卡片页

/*
 * Copyright (c) 2025.成都市一颗优雅草科技有限公司
 * author:卓伊凡-优雅草技术总监
 * project:优雅草星云物联网智控AI系统
 */

import { CommonConst, commonColor } from "@wcmzllx/common-const-library";
import { vp2 } from "../../common/NewVp";
import { BarChartDateModel } from "@wcmzllx/common-const-library"
import { LineChartWidget } from "../common/LineChartWidget";

// 定义文本样式扩展方法
@Extend(Text)
function textStyles(size: number, color: ResourceColor, weight: FontWeight, lineHeight = 11) {
    .fontSize(vp2.vp2(size))
    .fontColor(color)
    .fontWeight(weight)
    .textAlign(TextAlign.Start)
    // .width(CommonConst.GLOBAL_FULL_SCREEN)
    .lineHeight(lineHeight)
}

// 定义选择框样式扩展方法
@Extend(Select)
function selectOption(text: string) {
    .value(text)
    .font({ size: 14, weight: FontWeight.Regular })
    .fontColor(commonColor.FONT_9999_COLOR)
    .backgroundColor(Color.Transparent)
    .padding(0)
    //.padding(vp2.vp2(10))
    .margin(0)
    .borderRadius(0)
}

// 定义警告图表卡片组件
@ComponentV2
export struct WarningChartCardWidget {
    @Param title: string = "风扇运行状态"
    private cardHeight: number = 263.5
    @Param data: BarChartDateModel[] = [
        { xAxis: '23:00:11', value: 10 },
        { xAxis: '23:00:11', value: 40 },
        { xAxis: '23:00:11', value: 30 },
        { xAxis: '23:00:11', value: 40 },
    ];
    @Param isNoSelect: boolean = false;
    @Param selectData: SelectOption[] = [
        { value: '最近一小时' },
        { value: '最近三小时' },
        { value: '最近六小时' },
        { value: '最近十小时' }]

    // 构建警告图表卡片界面
    build() {
        Column() {
            Column({ space: vp2.vp2(15) }) {
                Row({ space: vp2.vp2(5) }) {
                    Text(this.title)
                        .textStyles(15, commonColor.FONT_3333_COLOR, FontWeight.Medium)

                    Blank()
                    if (!this.isNoSelect){
                        this.selectBuilder()
                    }
                }
                .height(vp2.vp2(24))
                .width(CommonConst.GLOBAL_FULL_SCREEN)

                Row() {
                    LineChartWidget()
                }
                // .borderWidth(vp2.vp2(0.5))
                // .borderColor("#E6EBF0")
                .width(CommonConst.GLOBAL_FULL_SCREEN)
                .layoutWeight(1)
            }
            .height(vp2.vp2(this.cardHeight))
            .clip(true)
            .borderRadius(5)
            .backgroundColor(commonColor.WHITE_COLOR)
            .width(CommonConst.GLOBAL_FULL_SCREEN)
            .padding({
                left: vp2.vp2(10),
                right: vp2.vp2(10),
                top: vp2.vp2(12.5),
                bottom: vp2.vp2(10)
            })
        }
        .padding({ left: vp2.vp2(15), right: vp2.vp2(15) })
    }

    // 构建选择框界面
    @Builder
    selectBuilder() {
        Stack() {
            Row() {
                Select(this.selectData)
                    .selectOption(this.selectData[0].value as string)
                    .onSelect((index: number, text?: string | undefined) => {

                    })
                    .height(CommonConst.GLOBAL_FULL_SCREEN)
                    .width("200%")
            }
            .height(CommonConst.GLOBAL_FULL_SCREEN)
            .width(CommonConst.GLOBAL_FULL_SCREEN)
            .zIndex(1)

            Row() {
                Image($r("app.media.ic_input_select"))
                    .height(vp2.vp2(16))
                    .aspectRatio(1)
            }
            .justifyContent(FlexAlign.End)
            .padding(vp2.vp2(10))
            .hitTestBehavior(HitTestMode.Transparent)
            .zIndex(2)
            .height(CommonConst.GLOBAL_FULL_SCREEN)
            .width(CommonConst.GLOBAL_FULL_SCREEN)
        }
        .clip(true)
        .width(vp2.vp2(120))
        .height(vp2.vp2(40))
        .borderRadius(5)
        .borderWidth(vp2.vp2(1))
        .borderColor("#FFE6EBF0")
    }
}

lib/CommonConstLibrary/src/main/ets/model/DateModel.ets 日期数据模型定义 下方还有格外几个页面

/*
 * Copyright (c) 2025.成都市一颗优雅草科技有限公司
 * author:卓伊凡-优雅草技术总监
 * project:优雅草星云物联网智控AI系统
 */

/**
 * 定义日期模型接口,用于规范日期信息的结构和格式
 */
export interface DateModel{
    year: number;          // 年份
    month: number;         // 月份
    day: number;           // 日期
    weekday: number;       // 星期几,用数字表示(例如:0表示星期日)
    week: string;          // 星期的字符串表示(例如:"Monday")
    hour: number;          // 小时
    minute: number;        // 分钟
    second: number;        // 秒
    millisecond: number;   // 毫秒
    hhmm: string;          // 小时和分钟的字符串表示,格式为HH:mm
    formatDate: string;    // 格式化的日期字符串,具体的格式可能根据用途有所不同

    timestamp: number;     // 时间戳,通常是从1970年1月1日(UTC)以来的毫秒数
}

这里发现我们已经开始定义数据模型,因此是CommonConstLibrary目录下,这里是扩展知识

1. 数据模型定义

  • 业务实体建模model 目录里的文件很可能用于定义应用中的数据模型。比如,在一个电商应用中,可能会有定义商品信息的 ProductModel.ts(假设使用 ArkTS 语言)文件,在该文件中通过类或者接口来描述商品的数据结构,像商品的 ID、名称、价格、描述、图片路径等属性。
  • // ProductModel.ts
    export interface Product {
        productId: string;
        productName: string;
        price: number;
        description: string;
        imageUrl: string;
    }
  • 数据交互结构:除了业务实体,还可能定义与服务器交互时使用的数据结构模型。例如,定义服务器响应数据格式或者请求参数格式的模型。如果服务器返回用户信息,可能有 UserResponseModel.ts 文件来定义响应数据结构。
  • // UserResponseModel.ts
    export interface UserResponse {
        code: number;
        message: string;
        data: {
            userId: string;
            username: string;
            email: string;
        }
    }

    2. 常量与枚举定义

  • 通用常量:如果 CommonConstLibrary 是用于存放通用的常量库,model 目录下可能存放与数据模型相关的常量。比如,定义用户角色类型的常量。
  • // UserRoleConstants.ts
    export const USER_ROLE_ADMIN = 'admin';
    export const USER_ROLE_USER = 'user';
  • 枚举类型:用于定义一些固定取值集合的数据类型。例如,在一个任务管理应用中,定义任务状态的枚举。
  • // TaskStatusEnum.ts
    export enum TaskStatus {
        Pending = 'pending',
        InProgress = 'in_progress',
        Completed = 'completed'
    }

    3. 数据处理与转换逻辑

  • 模型转换函数:可能包含将一种数据格式转换为另一种数据格式的函数。比如,将服务器返回的原始数据转换为符合应用内数据模型的格式。假设服务器返回的商品数据中有一些额外的字段不需要在应用内使用,就可以在 model 目录下的文件中定义转换函数。
  • // ProductDataConverter.ts
    import { Product } from './ProductModel';
    
    export function convertServerProductToAppProduct(serverProduct: any): Product {
        return {
            productId: serverProduct.product_id,
            productName: serverProduct.product_name,
            price: serverProduct.price,
            description: serverProduct.description,
            imageUrl: serverProduct.image_url
        };
    }

    4. 数据验证逻辑

  • 模型验证:为了确保数据的正确性和完整性,model 目录下可能包含验证数据是否符合模型定义的逻辑。例如,验证一个用户注册数据是否符合 UserModel 的要求,检查用户名长度、邮箱格式等。
  • // UserValidator.ts
    import { User } from './UserModel';
    import { validateEmail } from '../utils/ValidatorUtils';
    
    export function validateUser(user: User): boolean {
        if (user.username.length < 3) {
            return false;
        }
        if (!validateEmail(user.email)) {
            return false;
        }
        return true;
    }

    lib/CommonConstLibrary/src/main/ets/model/BarChartDateModel.ets 图表数据模型

    /*
     * Copyright (c) 2025.成都市一颗优雅草科技有限公司
     * author:卓伊凡-优雅草技术总监
     * project:优雅草星云物联网智控AI系统
     */
    
    /**
     * 定义柱状图数据模型接口
     * 该接口用于描述柱状图中每个数据项的信息,包括横轴标签、数值、可选标题和颜色
     */
    export interface BarChartDateModel {
        // 横轴标签,用于标识数据项的位置
        xAxis: string;
        // 数据项的数值,决定柱状图的高度
        value: number;
        // 可选字段:数据项的标题,用于提供额外的描述信息
        title?: string;
        // 可选字段:数据项的颜色,用于个性化展示
        color?: ResourceColor;
    }

    lib/CommonConstLibrary/src/main/ets/model/ApiModel.ets 数据模型api

    /*
     * Copyright (c) 2025.成都市一颗优雅草科技有限公司
     * author:卓伊凡-优雅草技术总监
     * project:优雅草星云物联网智控AI系统
     */
    
    import { PersistenceV2, Type } from "@kit.ArkUI";
    import { SeverityLevel } from "../enum/SeverityLevel";
    
    // 定义通用的返回结构,使用泛型 T 来表示 data 的具体类型
    @ObservedV2
    export class BaseModel {
        @Trace code: number = 0;
        //@Trace data?: T;
        @Trace message: string = "";
        success: boolean = false;
    }
    
    // 定义登录数据的接口
    @ObservedV2
    export class LoginDataModel {
        @Trace RealName: string = "";
        @Trace ForceExpiresTimeUnix: number = 0;
        @Trace Token: string = "";
    }
    
    // 定义登录返回的接口,继承 BaseModel 并指定 data 的具体类型为 LoginDataModel
    @ObservedV2
    export class LoginModel extends BaseModel {
        @Type(LoginDataModel)
        @Trace data: LoginDataModel = new LoginDataModel();
    }
    
    // 定义告警数据的接口
    @ObservedV2
    export class WarningDataModel {
        eventId: string = "";
        name: string = "";
        severity: SeverityLevel = SeverityLevel.UNCLASSIFIED;
        clock: string = "";
        @Trace status: boolean = false;
        hostName: string = "";
        ip: string = "";
        dns: string = "";
    }
    
    // 定义告警返回的接口,继承 BaseModel 并指定 data 的具体类型为 WarningDataModel
    @ObservedV2
    export class  WarningModel extends BaseModel {
        @Trace data: Array<WarningDataModel> = [];
    }

    lib/CommonConstLibrary/src/main/ets/model/InputDataModel.ets 输入数据模型

    /*
     * Copyright (c) 2025.成都市一颗优雅草科技有限公司
     * author:卓伊凡-优雅草技术总监
     * project:优雅草星云物联网智控AI系统
     */
    
    @ObservedV2
    export class InputDataModel {
        @Trace input: string;
        @Trace err: boolean = false;
        @Trace isEdit: boolean = false;
        placeholder: string;
        type: number;
    
        constructor(input: string="", type: number=1, placeholder: string="") {
            this.input = input;
            this.type = type;
            this.placeholder = placeholder;
        }
    }

    lib/CommonConstLibrary/src/main/ets/model/SettingsItemModel.ets 设置数据模型

    /*
     * Copyright (c) 2025.成都市一颗优雅草科技有限公司
     * author:卓伊凡-优雅草技术总监
     * project:优雅草星云物联网智控AI系统
     */
    
    /**
     * 定义设置项的模型接口
     * 用于描述不同类型的设置项,包括开关、选择框和输入框
     */
    export interface SettingsItemModel {
        // 设置项的类型,可以是开关、选择框或输入框
        type: "switch" | "select" | "input";
        // 设置项的标题
        title: string;
        // 选择框的选项数据,仅当类型为select时需要
        data?: SelectOption[];
        // 是否为必填项,用于校验
        isMust?: boolean;
        // 开关的状态,仅当类型为switch时需要
        isOn?: boolean;
        // 开关变化时的回调函数,仅当类型为switch时需要
        onSwitchChange?: (isOn: boolean) => void;
        // 输入框文本变化时的回调函数,仅当类型为input时需要
        onInputChange?: (value: string) => void;
        // 选择框选项变化时的回调函数,仅当类型为select时需要
        onSelectChange?: (value: string) => void;
    }
    
    /**
     * 定义设置数组的模型类
     * 用于组织多个设置项,这些设置项通常具有相同的主题或标题
     */
    export class SettingsArrayModel {
        // 设置数组的标题
        title: string = "";
        // 设置数组的设置项列表
        item: SettingsItemModel[] = [];
    
        /**
         * 构造函数
         * @param item 设置项列表
         * @param title 设置数组的标题,默认为空字符串
         */
        constructor(item: SettingsItemModel[], title: string = "") {
            this.item = item;
            this.title = title;
        }
    }

    lib/ComponentLibrary/src/main/ets/components/warning/WarningDeviceInfoWidget.ets 告警设备详细信息

    /*
     * Copyright (c) 2025.成都市一颗优雅草科技有限公司
     * author:卓伊凡-优雅草技术总监
     * project:优雅草星云物联网智控AI系统
     */
    
    import { commonColor, CommonConst } from "@wcmzllx/common-const-library"
    import { vp2 } from "../../common/NewVp"
    
    /**
     * 扩展Text组件的样式配置函数
     * 用于统一设置文本的字体大小、颜色、权重、对齐方式、宽度和行高
     *
     * @param size 文本字体大小
     * @param color 文本颜色
     * @param weight 字体权重
     * @param lineHeight 行高,默认为11
     */
    @Extend(Text)
    function textStyles(size: number, color: ResourceColor, weight: FontWeight, lineHeight = 11) {
        .fontSize(vp2.vp2(size))
        .fontColor(color)
        .fontWeight(weight)
        .textAlign(TextAlign.Start)
        .width(CommonConst.GLOBAL_FULL_SCREEN)
        .lineHeight(lineHeight)
    }
    
    /**
     * 设备信息警告组件
     * 显示设备的CPU、内存利用率,响应时间和端口使用率等信息
     */
    @ComponentV2
    export struct WarningDeviceInfoWidget {
        build() {
            // 布局主体结构,包含两列设备信息
            Row(){
                Column({ space: vp2.vp2(15) }) {
                    // 第一列:CPU和内存利用率
                    Row({ space: vp2.vp2(6.5) }) {
                        this.deviceInfoBuilder($r("app.media.ic_cpu"), "设备平均CPU利用率", "0%", "rgba(0, 141, 240, 0.06)")
                        this.deviceInfoBuilder($r("app.media.ic_memory"), "设备平均内存利用率", "0%", "rgba(240, 164, 0, 0.06)")
                    }
                    .width(CommonConst.GLOBAL_FULL_SCREEN)
    
                    // 第二列:响应时间和端口使用率
                    Row({ space: vp2.vp2(6.5) }) {
                        this.deviceInfoBuilder($r("app.media.ic_cpu"), "响应时间", "0%", "rgba(22, 202, 70, 0.06)")
                        this.deviceInfoBuilder($r("app.media.ic_com_port"), "端口使用率", "0%", "rgba(17, 195, 207, 0.06)")
                    }
                    .width(CommonConst.GLOBAL_FULL_SCREEN)
                }
                .borderRadius(5)
                .backgroundColor(commonColor.WHITE_COLOR)
                .padding({top: vp2.vp2(10.5), bottom: vp2.vp2(10.5), left: vp2.vp2(11), right: vp2.vp2(11)})
            }
            .padding({left: vp2.vp2(15), right: vp2.vp2(15)})
        }
    
        /**
         * 构建单个设备信息项
         *
         * @param icon 设备信息项的图标
         * @param title 设备信息项的标题
         * @param content 设备信息项的内容
         * @param color 背景颜色
         */
        @Builder
        deviceInfoBuilder(icon: ResourceStr, title: string, content: string, color: ResourceColor) {
            Row({ space: vp2.vp2(5) }) {
                // 设备信息项的图标
                Stack() {
                    Image(icon)
                        .width(vp2.vp2(19.35))
                        .aspectRatio(1)
                }
                .backgroundColor(color)
                .width(vp2.vp2(40))
                .aspectRatio(1)
                .borderRadius(vp2.vp2(2.58))
    
                // 设备信息项的标题和内容
                Column({ space: vp2.vp2(3.5) }) {
                    Text(content)
                        .textStyles(17, commonColor.BLACK_COLOR, FontWeight.Regular, 19.92)
                    Text(title)
                        .textStyles(13, commonColor.FONT_9999_COLOR, FontWeight.Regular, 15.23)
                }
            }
            .layoutWeight(1)
        }
    }

    lib/ComponentLibrary/src/main/ets/components/warning/WarningSearchWidget.ets 告警设备检索

    lib/UtilsLibrary/src/main/ets/utils/DateUtils.ets 数据计入

    以上内容就是以下几个页面,并且这几个页面都还未包含完整的内容:

    首页 ,其实由于卓伊凡今天才来更新,但是今天其实已经有数据对接了

    首页图表的部分

    告警中心的部分

    /*
     * Copyright (c) 2025.成都市一颗优雅草科技有限公司
     * author:卓伊凡-优雅草技术总监
     * project:优雅草星云物联网智控AI系统
     */
    
    // 导入常用的色彩和常量库
    import { commonColor, CommonConst } from "@wcmzllx/common-const-library"
    // 导入自定义的视口转换函数
    import { vp2 } from "../../common/NewVp"
    // 导入设置输入框组件
    import { SettingInputWidget } from "../settings/SettingInputWidget"
    // 导入设置选择框组件
    import { SettingSelectWidget } from "../settings/SettingSelectWidget"
    
    /**
     * 自定义文本样式函数
     * @param size 字体大小
     * @param color 字体颜色
     * @param weight 字体粗细
     * 该函数用于统一设置文本的样式,包括字体大小、颜色和粗细等
     */
    @Extend(Text)
    function textStyles(size: number, color: ResourceColor, weight: FontWeight) {
        .fontSize(vp2.vp2(size))
        .fontColor(color)
        .fontWeight(weight)
        .lineHeight(vp2.vp2(11))
    }
    
    /**
     * 告警搜索组件
     * 该组件用于用户输入告警相关信息进行搜索,包含告警内容、告警状态等筛选条件
     */
    @ComponentV2
    export struct WarningSearchWidget {
        // 点击事件回调函数
        @Event onClickEvent: (event: ClickEvent) => void
        // 告警内容本地变量
        @Local warningContent: string = ""
        // 告警状态本地变量
        @Local warningStatus: string = ""
    
        build() {
            // 垂直布局,用于排列各个设置项和按钮
            Column({ space: vp2.vp2(15) }) {
                // 告警内容输入框
                SettingInputWidget({title: "告警内容", isMust: false, onContentChange: this.warningContent!!})
                // 告警状态选择框
                SettingSelectWidget({title: "告警状态", isMust: false, onSelectedChange: (index, res)=>{
                    this.warningStatus = res
                }})
                // 重复的告警状态选择框,可能需要移除
                SettingSelectWidget({title: "告警状态", isMust: false})
                // 告警级别选择框
                SettingSelectWidget({title: "告警级别", isMust: false})
                // 静默状态选择框
                SettingSelectWidget({title: "静默状态", isMust: false})
    
                // 水平布局,用于排列重置和搜索按钮
                Row({ space: vp2.vp2(15) }) {
    
                    // 重置按钮,点击后清空告警内容和状态
                    Button("重置", { type: ButtonType.Normal })
                        .borderRadius(5)
                        .fontColor(commonColor.FONT_3333_COLOR)
                        .backgroundColor("#1A333333")
                        .height(vp2.vp2(40))
                        .fontSize(vp2.vp2(14))
                        .layoutWeight(1)
                        .onClick(()=>{
                            this.warningContent = ""
                            this.warningStatus = ""
                        })
    
                    // 搜索按钮,点击后触发搜索事件
                    Button("搜索", { type: ButtonType.Normal })
                        .borderRadius(5)
                        .backgroundColor(commonColor.BRAND_COLOR)
                        .height(vp2.vp2(40))
                        .fontSize(vp2.vp2(14))
                        .layoutWeight(1)
                        .onClick(this.onClickEvent)
    
                }
            }
            // 设置组件的样式和布局
            .padding(vp2.vp2(15))
            .width(CommonConst.GLOBAL_FULL_SCREEN)
            .height(vp2.vp2(345))
            .shadow({
                radius: vp2.vp2(13),
                color: commonColor.E6EBF0_COLOR,
                offsetY: vp2.vp2(6)
            })
        }
    }

    更多代码 请查看我们的gitee

    作者:卓伊凡

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【从零基础开发物联网AI智控系统】优雅草星云鸿蒙端适配详解:集成deveco studio,扩展告警中心与Canvas折线图绘制指南

    发表回复