ant design pro 如何实现动态菜单带上 icon 的

news2024/9/21 14:44:49

在这里插入图片描述

  • ant design pro 如何去保存颜色
  • ant design pro v6 如何做好角色管理
  • ant design 的 tree 如何作为角色中的权限选择之一
  • ant design 的 tree 如何作为角色中的权限选择之二
  • ant design pro access.ts 是如何控制多角色的权限的
  • ant design pro 中用户的表单如何控制多个角色

如上图所示,这里的菜单是从后端动态得到的

在这里插入图片描述
我们看下后端菜单的响应数据:

{
    "success": true,
    "data": [
        {
            "_id": "66b6cd18b9ad87dfa985f190",
            "name": "认证管理",
            "path": "/auth",
            "permission": {
                "_id": "66b9ad528554e602536acc84",
                "name": "授权管理菜单",
                "path": "/auth",
                "action": "GET",
                "permissionGroup": "66b9ad348554e602536acc67",
                "createdAt": "2024-08-12T06:36:02.754Z",
                "updatedAt": "2024-08-12T06:36:02.754Z",
                "__v": 0
            },
            "createdAt": "2024-08-10T02:14:48.819Z",
            "updatedAt": "2024-08-12T10:30:50.749Z",
            "__v": 0,
            "icon": "SecurityScanOutlined",
            "children": [
                {
                    "_id": "66b6cdbbb9ad87dfa985f1f9",
                    "name": "用户管理",
                    "path": "/auth/users",
                    "parent": {
                        "_id": "66b6cd18b9ad87dfa985f190",
                        "name": "认证管理",
                        "path": "/auth",
                        "permission": "66b9ad528554e602536acc84",
                        "createdAt": "2024-08-10T02:14:48.819Z",
                        "updatedAt": "2024-08-12T10:30:50.749Z",
                        "__v": 0,
                        "icon": "SecurityScanOutlined"
                    },
                    "permission": {
                        "_id": "66b6d352b9ad87dfa985f3f0",
                        "name": "查看用户",
                        "path": "/users",
                        "action": "GET",
                        "permissionGroup": "66b6d2c9b9ad87dfa985f34f",
                        "createdAt": "2024-08-10T02:41:22.895Z",
                        "updatedAt": "2024-08-10T08:03:22.477Z",
                        "__v": 0
                    },
                    "createdAt": "2024-08-10T02:17:31.227Z",
                    "updatedAt": "2024-08-12T10:23:32.641Z",
                    "__v": 0,
                    "icon": "HeartOutlined",
                    "children": []
                },
                {
                    "_id": "66b6cdcfb9ad87dfa985f210",
                    "name": "角色管理",
                    "path": "/auth/roles",
                    "parent": {
                        "_id": "66b6cd18b9ad87dfa985f190",
                        "name": "认证管理",
                        "path": "/auth",
                        "permission": "66b9ad528554e602536acc84",
                        "createdAt": "2024-08-10T02:14:48.819Z",
                        "updatedAt": "2024-08-12T10:30:50.749Z",
                        "__v": 0,
                        "icon": "SecurityScanOutlined"
                    },
                    "permission": {
                        "_id": "66b6d40db9ad87dfa985f475",
                        "name": "查看角色",
                        "path": "/roles",
                        "action": "GET",
                        "permissionGroup": "66b6d2e9b9ad87dfa985f377",
                        "createdAt": "2024-08-10T02:44:29.797Z",
                        "updatedAt": "2024-08-10T08:03:18.669Z",
                        "__v": 0
                    },
                    "createdAt": "2024-08-10T02:17:51.754Z",
                    "updatedAt": "2024-08-12T10:11:47.776Z",
                    "__v": 0,
                    "icon": "MenuFoldOutlined",
                    "children": []
                },
                {
                    "_id": "66b6cde2b9ad87dfa985f229",
                    "name": "菜单管理",
                    "path": "/auth/menus",
                    "parent": {
                        "_id": "66b6cd18b9ad87dfa985f190",
                        "name": "认证管理",
                        "path": "/auth",
                        "permission": "66b9ad528554e602536acc84",
                        "createdAt": "2024-08-10T02:14:48.819Z",
                        "updatedAt": "2024-08-12T10:30:50.749Z",
                        "__v": 0,
                        "icon": "SecurityScanOutlined"
                    },
                    "permission": {
                        "_id": "66b6d48bb9ad87dfa985f4e7",
                        "name": "查看菜单",
                        "path": "/menus",
                        "action": "GET",
                        "permissionGroup": "66b6d2ddb9ad87dfa985f362",
                        "createdAt": "2024-08-10T02:46:35.896Z",
                        "updatedAt": "2024-08-10T08:03:13.698Z",
                        "__v": 0
                    },
                    "createdAt": "2024-08-10T02:18:10.776Z",
                    "updatedAt": "2024-08-12T10:30:56.693Z",
                    "__v": 0,
                    "icon": "MenuFoldOutlined",
                    "children": []
                },
                {
                    "_id": "66b6cdfcb9ad87dfa985f244",
                    "name": "权限管理",
                    "path": "/auth/permissions",
                    "parent": {
                        "_id": "66b6cd18b9ad87dfa985f190",
                        "name": "认证管理",
                        "path": "/auth",
                        "permission": "66b9ad528554e602536acc84",
                        "createdAt": "2024-08-10T02:14:48.819Z",
                        "updatedAt": "2024-08-12T10:30:50.749Z",
                        "__v": 0,
                        "icon": "SecurityScanOutlined"
                    },
                    "permission": {
                        "_id": "66b1c55141364c27c464f858",
                        "name": "查看权限",
                        "path": "/permissions",
                        "action": "GET",
                        "permissionGroup": "66b1b00bb5d937a0aef34034",
                        "createdAt": "2024-08-06T06:40:17.991Z",
                        "updatedAt": "2024-08-10T08:03:27.245Z",
                        "__v": 0
                    },
                    "createdAt": "2024-08-10T02:18:36.390Z",
                    "updatedAt": "2024-08-10T02:18:36.390Z",
                    "__v": 0,
                    "children": []
                },
                {
                    "_id": "66b6ce29b9ad87dfa985f261",
                    "name": "权限组管理",
                    "path": "/auth/permission-groups",
                    "parent": {
                        "_id": "66b6cd18b9ad87dfa985f190",
                        "name": "认证管理",
                        "path": "/auth",
                        "permission": "66b9ad528554e602536acc84",
                        "createdAt": "2024-08-10T02:14:48.819Z",
                        "updatedAt": "2024-08-12T10:30:50.749Z",
                        "__v": 0,
                        "icon": "SecurityScanOutlined"
                    },
                    "permission": {
                        "_id": "66b6d52cb9ad87dfa985f546",
                        "name": "查看权限组",
                        "path": "/permission-groups",
                        "action": "GET",
                        "permissionGroup": "66b6d314b9ad87dfa985f3a7",
                        "createdAt": "2024-08-10T02:49:16.624Z",
                        "updatedAt": "2024-08-10T08:03:09.517Z",
                        "__v": 0
                    },
                    "createdAt": "2024-08-10T02:19:21.424Z",
                    "updatedAt": "2024-08-12T06:37:05.295Z",
                    "__v": 0,
                    "children": []
                },
                {
                    "_id": "66b6ce41b9ad87dfa985f280",
                    "name": "数据权限管理",
                    "path": "/auth/data-permissions",
                    "parent": {
                        "_id": "66b6cd18b9ad87dfa985f190",
                        "name": "认证管理",
                        "path": "/auth",
                        "permission": "66b9ad528554e602536acc84",
                        "createdAt": "2024-08-10T02:14:48.819Z",
                        "updatedAt": "2024-08-12T10:30:50.749Z",
                        "__v": 0,
                        "icon": "SecurityScanOutlined"
                    },
                    "permission": {
                        "_id": "66b6d586b9ad87dfa985f592",
                        "name": "查看数据权限",
                        "path": "/data-permissions",
                        "action": "GET",
                        "permissionGroup": "66b6d2fdb9ad87dfa985f38e",
                        "createdAt": "2024-08-10T02:50:46.780Z",
                        "updatedAt": "2024-08-10T08:03:04.925Z",
                        "__v": 0
                    },
                    "createdAt": "2024-08-10T02:19:45.054Z",
                    "updatedAt": "2024-08-12T08:23:19.027Z",
                    "__v": 0,
                    "children": []
                }
            ]
        },
        {
            "_id": "66b6ce76b9ad87dfa985f2a1",
            "name": "材料类目",
            "path": "/material-categories",
            "permission": {
                "_id": "66b6d7d0b9ad87dfa985f782",
                "name": "查看材料类目",
                "path": "/material-categories",
                "action": "GET",
                "permissionGroup": "66adec30d647a4fde5546b1c",
                "createdAt": "2024-08-10T03:00:32.932Z",
                "updatedAt": "2024-08-10T08:02:59.634Z",
                "__v": 0
            },
            "createdAt": "2024-08-10T02:20:38.550Z",
            "updatedAt": "2024-08-12T09:58:32.426Z",
            "__v": 0,
            "icon": "UsergroupAddOutlined",
            "children": []
        }
    ]
}

看到这里的数据结构了吗:

主要是 name children 这些比较重要,name 是显示出来的,children 是层级结构

当然还有 icon 之类的 还有 path ,也是要的,毕竟菜单要有路径的。

那后端如何实现呢:

// @desc Get permission menus
// @route GET /api/menus/fetch
// @access Private
const fetchMenus = handleAsync(async (req: RequestCustom, res: Response) => {
  const query = buildQuery(req.query);

  const menus = await Menu.find(query)
    .populate('parent')
    .populate('permission');

  const menusWithChildren = await Promise.all(
    menus.map(async (menu) => {
      const menuWithChildren = menu.toObject();
      menuWithChildren.children = await getChildren(menu._id);
      return menuWithChildren;
    }),
  );

  res.json({
    success: true,
    data: checkMenu(menusWithChildren, req.user),
  });
});

只要你的后端能返回出这样的数据就行。

那前端呢:

app.tsx

menu: {
      // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
      params: {
        userId: initialState?.currentUser?._id,
      },
      request: async () => {
        // initialState.currentUser 中包含了所有用户信息
        const { data, success } = await fetchMenuData();
        console.log('data', data);
        if (success) {
          console.log('loopMenuItem(data)', loopMenuItem(data));
          return loopMenuItem(data);
        } else {
          return [];
        }
      },
    },

这里还有 icon 的使用:

const iconEnum: { [key: string]: ReactElement<any, any> } = {
  UsergroupAddOutlined: <UsergroupAddOutlined />,
  SmileOutlined: <SmileOutlined />,
  HeartOutlined: <HeartOutlined />,
  GlobalOutlined: <GlobalOutlined />,
  MenuFoldOutlined: <MenuFoldOutlined />,
  TeamOutlined: <TeamOutlined />,
  DatabaseOutlined: <DatabaseOutlined />,
  GatewayOutlined: <GatewayOutlined />,
  SecurityScanOutlined: <SecurityScanOutlined />,
};

console.log('iconEnum', iconEnum);

const loopMenuItem = (menus: MenuDataItem[]): MenuDataItem[] =>
  menus.map(({ icon, children, ...item }) => {
    return {
      ...item,
      icon: icon && iconEnum[icon as string],
      children: children && loopMenuItem(children),
    };
  });

完整代码是这样的:

import { Footer, SelectLang, AvatarDropdown, AvatarName } from '@/components';
import {
  DatabaseOutlined,
  GatewayOutlined,
  GlobalOutlined,
  HeartOutlined,
  LinkOutlined,
  MenuFoldOutlined,
  SecurityScanOutlined,
  SmileOutlined,
  TeamOutlined,
  UsergroupAddOutlined,
} from '@ant-design/icons';
import type { Settings as LayoutSettings, MenuDataItem } from '@ant-design/pro-components';
import { SettingDrawer } from '@ant-design/pro-components';
import type { RunTimeLayoutConfig } from '@umijs/max';
import { history, Link } from '@umijs/max';
import defaultSettings from '../config/defaultSettings';
import { errorConfig } from './requestErrorConfig';
import { fetchMenuData, currentUser as queryCurrentUser } from '@/services/ant-design-pro/api';
import React, { ReactElement } from 'react';
const isDev = process.env.NODE_ENV === 'development';
const loginPath = '/user/login';

const iconEnum: { [key: string]: ReactElement<any, any> } = {
  UsergroupAddOutlined: <UsergroupAddOutlined />,
  SmileOutlined: <SmileOutlined />,
  HeartOutlined: <HeartOutlined />,
  GlobalOutlined: <GlobalOutlined />,
  MenuFoldOutlined: <MenuFoldOutlined />,
  TeamOutlined: <TeamOutlined />,
  DatabaseOutlined: <DatabaseOutlined />,
  GatewayOutlined: <GatewayOutlined />,
  SecurityScanOutlined: <SecurityScanOutlined />,
};

console.log('iconEnum', iconEnum);

const loopMenuItem = (menus: MenuDataItem[]): MenuDataItem[] =>
  menus.map(({ icon, children, ...item }) => {
    return {
      ...item,
      icon: icon && iconEnum[icon as string],
      children: children && loopMenuItem(children),
    };
  });

/**
 * @see  https://umijs.org/zh-CN/plugins/plugin-initial-state
 * */
export async function getInitialState(): Promise<{
  settings?: Partial<LayoutSettings>;
  currentUser?: API.CurrentUser;
  loading?: boolean;
  fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
}> {
  const fetchUserInfo = async () => {
    try {
      const response = await queryCurrentUser({
        skipErrorHandler: true,
      });
      return response.data;
    } catch (error) {
      history.push(loginPath);
    }
    return undefined;
  };
  // 如果不是登录页面,执行
  const { location } = history;
  if (location.pathname !== loginPath) {
    const currentUser = await fetchUserInfo();
    return {
      fetchUserInfo,
      currentUser,
      settings: defaultSettings as Partial<LayoutSettings>,
    };
  }
  return {
    fetchUserInfo,
    settings: defaultSettings as Partial<LayoutSettings>,
  };
}

// ProLayout 支持的api https://procomponents.ant.design/components/layout
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
  return {
    actionsRender: () => [<SelectLang key="SelectLang" />],
    avatarProps: {
      src: initialState?.currentUser?.avatar,
      title: <AvatarName />,
      render: (_, avatarChildren) => {
        return <AvatarDropdown>{avatarChildren}</AvatarDropdown>;
      },
    },
    menu: {
      // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
      params: {
        userId: initialState?.currentUser?._id,
      },
      request: async () => {
        // initialState.currentUser 中包含了所有用户信息
        const { data, success } = await fetchMenuData();
        console.log('data', data);
        if (success) {
          console.log('loopMenuItem(data)', loopMenuItem(data));
          return loopMenuItem(data);
        } else {
          return [];
        }
      },
    },
    waterMarkProps: {
      content: '',
    },
    footerRender: () => <Footer />,
    onPageChange: () => {
      const { location } = history;
      // 如果没有登录,重定向到 login
      if (!initialState?.currentUser && location.pathname !== loginPath) {
        history.push(loginPath);
      }
    },
    links: isDev
      ? [
          <Link key="openapi" to="/umi/plugin/openapi" target="_blank">
            <LinkOutlined />
            <span>OpenAPI 文档</span>
          </Link>,
        ]
      : [],
    menuHeaderRender: undefined,
    // 自定义 403 页面
    // unAccessible: <div>unAccessible</div>,
    // 增加一个 loading 的状态
    childrenRender: (children) => {
      // if (initialState?.loading) return <PageLoading />;
      return (
        <>
          {children}
          {isDev && (
            <SettingDrawer
              disableUrlParams
              enableDarkTheme
              settings={initialState?.settings}
              onSettingChange={(settings) => {
                setInitialState((preInitialState) => ({
                  ...preInitialState,
                  settings,
                }));
              }}
            />
          )}
        </>
      );
    },
    ...initialState?.settings,
  };
};

/**
 * @name request 配置,可以配置错误处理
 * 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
 * @doc https://umijs.org/docs/max/request#配置
 */
export const request = {
  baseURL: `${process.env.UMI_APP_API_URL}/api`,
  ...errorConfig,
};

export async function fetchMenuData() {
  return request<menuResponse>('/menus/fetch', {
    method: 'GET',
  });
}

这样就可以弄好动态菜单的:

我们拥有 12 年建站编程经验

  1. 虚拟产品交易平台定制开发
  2. WordPress 外贸电商独立站建站

我的网站

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2070095.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

免费无损音乐、音效素材,马住着6个网站

如果你正在寻找免费的无损音乐和音效素材&#xff0c;这里有6个网站可以帮助你找到高质量的资源。无论是制作视频、播客还是其他创意项目&#xff0c;这些平台提供了丰富的选项&#xff0c;满足你的各种需求。 1、菜鸟图库 音效素材下载_mp3音效大全 - 菜鸟图库 菜鸟图库音…

Datawhale X 李宏毅苹果书 AI夏令营 学习笔记(三)

批量归一化(Batch Normalization&#xff0c;BN) 如果说自适应学习率是让训练适应loss&#xff0c;那归一化就是让loss适应训练。 我们抛掉使用自适应学习率的想法&#xff0c;重新看下面的图。可以看到w1固定时&#xff0c;w2的梯度是比较大的。w2固定时&#xff0c;w1的梯度…

GitHub开源的PDF管理工具Stirling-pdf

Stirling pdf 手动搭建docker搭建 官网&#xff1a;https://github.com/Stirling-Tools/Stirling-PDF 手动搭建 Ubuntu2404环境 安装所需软件包 apt install -y git automake autoconf libtool libleptonica-dev pkg-config zlib1g-dev make g openjdk-21-jdk python3…

【R语言】基于nls函数的非线性拟合

非线性拟合 1.写在前面2.实现代码 1.写在前面 以下代码记录了立地指数的计算过程&#xff0c;包括了优势树筛选、误差清理、非线性拟合以及结果成图。 优势树木确定以及数据清理过程&#xff1a; 相关导向函数&#xff1a; 2.实现代码 ##*******************************…

web测试之功能测试常用的方法有哪几种?有什么要点要注意?

1、前言 功能测试就是对产品的各功能进行验证&#xff0c;根据功能测试用例&#xff0c;逐项测试&#xff0c;检查产品是否达到用户要求的功能。 2、常用的测试方法如下&#xff1a; 1、页面链接检查&#xff1a; 每一个链接是否都有对应的页面&#xff0c;并且页面之间切换…

在Excel中“直接引用”字符串地址

indirect是Excel唯一可以拥有直接解析字符串引用地址参数能力的函数&#xff0c;是绝无仅有的宝贝疙瘩。 (笔记模板由python脚本于2024年08月21日 12:45:49创建&#xff0c;本篇笔记适合喜欢用Excel处理数据的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;ht…

Navicat中怎么查看数据库密码

一、版本问题 场景&#xff1a;在配置数据库连接后&#xff0c;忘记了数据库的密码&#xff0c;想要找回来。 其实有些版本&#xff08;好像是低版本才有&#xff0c;具体哪个版本就没去研究了&#xff09;在配置连接页面&#xff0c;是有个选项勾选是否显示密码的&#xff0…

Datawhale AI 夏令营(第五期) 李宏毅苹果书 Task 1 《深度学习详解(入门)》- 1.1 通过案例了解机器学习

预测本频道观看人数&#xff08;上&#xff09; - 机器学习基本概念简介_哔哩哔哩_bilibili 1 隐藏任务&#xff1a;找出本篇中形如回归&#xff08;regression&#xff09;加粗字体的术语&#xff0c;并用自己的话进行解释&#xff0c;列成表格 术语解释机器学习&#xff08;…

改VS2008 MFC项目 C语言1改字体,2颜色,3界面禁用项 CCM4202S量产SP下载工具 天津国芯

效果 1改字体 用progresss上画文字&#xff0c;并改字体及大小 要修改 DrawText 函数绘制文本的字体大小&#xff0c;你需要在绘制之前设置设备上下文的字体。这里是一个完整的示例&#xff0c;展示了如何在使用 DrawText 函数之前设置字体大小。 假设你已经有一个 HDC 设备…

85.游戏改造-修改UI分辨率,面向对象方式

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;易道云信息技术研究院 上一个内容&#xff1a;84.游戏改造-窗口化下的分辨率 首先剑侠情缘这个游戏它按f9是可以隐藏ui界面的&#xf…

stm32-USB-1

1. USB简介 USB&#xff0c; 英文全称&#xff1a;Universal Serial Bus&#xff0c;即通用串行总线 USB提供适合各种应用的传输协议&#xff0c;而且协议标准向下兼容 优缺点 2. USB2.0拓扑结构 USB是一种主从结构的系统&#xff0c;数据交换只能发生在主从设备之间&#…

Jenkins配置SSH凭据

在jenkins中&#xff0c;绕不开的便是操作远程的SSH服务器&#xff0c;如向远程服务器传送文件、在远程服务器上执行脚本或者命令等&#xff0c;而这一切的前提&#xff0c;则需要配置访问远程服务器的凭据&#xff0c;常用的方式包括远程服务器的账号和密码以及密匙对等&#…

编译 ARM 平台 Qt5.12.9 源码-思维导图-学习笔记-基于正点原子阿尔法开发板

编译 ARM 平台 Qt5.12.9 源码 概述 库的后缀名 Windows平台&#xff1a;编译出的Qt库文件后缀为.dll Linux平台&#xff1a;编译出的Qt库文件后缀为.so 这些库被称为动态库&#xff0c;意味着它们在运行时被加载到应用程序中&#xff0c;而不是在编译时静态链接 库的作用 …

Linux--数据链路层(macarp)

目录 1.认识以太网 2.以太网帧格式 3.模拟一次局域网通信&#xff08;交换机&#xff09; 4.认识 MAC 地址 对比理解 MAC 地址和 IP 地址 5.认识MTU MTU 对 IP 协议的影响 MTU 对 UDP 协议的影响 MTU 对于 TCP 协议的影响 6.ARP协议 ARP 协议的作用及原理 ARP 数据报的…

【精选】基于移动端的个人博客系统的设计与实现(源码+定制+辅导)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

Java面试题--JVM大厂篇之JVM 大厂面试题及答案解析(2)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到我的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而我的博客&…

关于“数据完全版本记录”的系统设计

时间&#xff1a;2024年08月24日 作者&#xff1a;小蒋聊技术 邮箱&#xff1a;wei_wei10163.com 微信&#xff1a;wei_wei10 音频&#xff1a;https://xima.tv/1_Gtthca?_sonic0 希望大家帮个忙&#xff01;如果大家有工作机会&#xff0c;希望帮小蒋内推一下&#xff0c…

[运算放大器系列]四、PT100和热电偶采集电路分析

[运算放大器系列]三、PT100和热电偶采集电路分析 1. 前言2. 电路原理图3. 热电偶电路4. 三线热电阻电路 1. 前言 淘宝偶然发现一款可以支持热电阻和热电偶多种传感器的温度变送器 , 从图上看重要的芯片丝印都磨掉了。 2. 电路原理图 在其他网站上搜到两篇关于该设备的帖子 …

理解 HarmonyOS 中的网格布局:综合指南

网格布局是创建响应式和结构化用户界面的强大工具。通过将界面划分为由行和列组成的单元格&#xff0c;网格可以精确控制组件的分布和对齐。这使得它们成为各种应用程序&#xff08;例如图库、日历和计算器&#xff09;的理想选择。 在 HarmonyOS 中&#xff0c;ArkUI 提供了用…