【手把手带你搓组件库】从零开始实现Element Plus

news2024/11/15 8:41:45

从零开始实现Element Plus

  • 前言
  • 亮点
  • 项目搭建
    • 1、创建项目
      • 初始化
      • monorepo
      • 创建 .gitignore
      • 目录结构
      • 安装基础依赖
      • 配置文件
      • 创建各个分包入口
        • utils
        • components
        • core
        • play
        • theme
    • 2、创建VitePress文档
    • 3、部署到Github Actions
        • 生成 GH_TOKEN
        • GitHub Page 演示
    • 4、总结

前言

在本文中,将手把手带你从零开始实现一个类似于Element Plus 的组件库。Element Plus 是一个非常流行的Vue UI 组件库,我们将尝试实现一些常见的组件,如基础组件、反馈组件、表单组件等。让我们开始吧!

亮点

  • Vite+Vitest+Vitepress 工具链 (项目构建+测试+项目文档)
  • monorepo 分包管理
  • GitHub actions 实现 CI/CD 自动化部署
  • 大模型辅助:使用大模型辅助完成需求分析,设计思路,快速实现组件,提升开发效率
  • 发布开箱即用的npm包

项目搭建

1、创建项目

初始化

mkdir Wannaer-element
cd Wannaer-element
git init
pnpm init

在这里插入图片描述

monorepo

monorepo ,那就先创建一个 pnpm-workspace.yaml 文件。

mkdir packages
echo -e 'packages:\n  - "packages/*"' > pnpm-workspace.yaml
// 在Windows系统中,echo命令默认不支持像在Linux系统中那样使用"-e"参数来表示换行符
// 创建完成后,手动操作换行
echo 'packages:\n  - "packages/*"' > pnpm-workspace.yaml

// 如果出现 pnpm: null byte is not allowed in input (1:4) 可能是有隐藏字符问题
packages:
  - "packages/*"

在这里插入图片描述

创建 .gitignore

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
coverage
dist
dist-ssr
*.local

/cyperss/videos/
/cypress/srceenshots/

.vitepress/dist
.vitepress/cache

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

目录结构

为了目录扁平,就只创建 packages 这么一个 pnpm 工作区,下面大概说一下这个项目计划的分包结构

- components # 组件目录
- core # npm 包入口
- docs # 文档目录
- hooks # 组合式API hooks 目录
- play # 组件开发实验室
- theme # 主题目录
- utils # 工具函数目录
// 创建这些目录
cd packages
// 分别初始化这些目录 在 packages 目录下创建 init.shell 内容如下
for i in components core docs hooks theme utils; do
  mkdir $i
  cd $i
  pnpm init
  cd ..
done
// 执行后删除 init.shell

在这里插入图片描述
这波 play 目录先留着,我们用 vite 来创建一个 vue 开发项目

pnpm create vite play --template vue-ts

创建完成后分别到 各个分包目录中修改 package.json 中的 name,防止重名

- core # npm 包入口
	"name": "Wannaer-element",
- components # 组件目录
	"name": "@Wannaer-element/components",
- docs # 文档目录
	"name": "@Wannaer-element/docs",
- hooks # 组合式API hooks 目录
	"name": "@Wannaer-element/hooks",
- play # 组件开发实验室
	"name": "@Wannaer-element/play",
- theme # 主题目录
	"name": "@Wannaer-element/theme",
- utils # 工具函数目录
	"name": "@Wannaer-element/utils",
- 根目录
	“name”: "@Wannaer-element/workspace"

安装基础依赖

在根目录 安装
-Dw表示在package.json文件中配置的scripts中运行特定的脚本命令,xxx为脚本命令的名称。
-w表示在指定的工作区目录中运行特定的脚本命令,xxx为脚本命令的名称。

// 开发依赖
pnpm add -Dw typescript@^5.2.2 vite@^5.1.4 vitest@^1.4.0 vue-tsc@^1.8.27 postcss-color-mix@^1.1.0 postcss-each@^1.1.0 postcss-each-variables@^0.3.0 postcss-for@^2.1.1 postcss-nested@^6.0.1 @types/node@^20.11.20 @types/lodash-es@^4.17.12 @vitejs/plugin-vue@^5.0.4 @vitejs/plugin-vue-jsx@^3.1.0 @vue/tsconfig@^0.5.1

// 非开发依赖
pnpm add -w lodash-es@^4.17.21 vue@^3.4.19

在 根目录 package.json 中添加如下内容 添加一下子包的依赖

{
  "dependencies": {
    "Wannaer-element": "workspace:*",
    "@Wannaer-element/hooks": "workspace:*",
    "@Wannaer-element/utils": "workspace:*",
    "@Wannaer-element/theme": "workspace:*"
  }
}
  • components
pnpm add -D @vue/test-utils@^2.4.5 @vitest/coverage-v8@^1.4.0 jsdom@^24.0.0 --filter @Wannaer-element/components
pnpm add @popperjs/core@^2.11.8 async-validator@^4.2.5 --filter @Wannaer-element/components
  • core
// 在 core/package.json 中添加如下内容
{
  "dependencies": {
    "@Wannaer-element/components": "workspace:*"
  }
}
  • docs
pnpm add -D vitepress@1.0.0-rc.44 --filter @Wannaer-element/docs
  • play
    将 play/package.json 中冗余部分删除, 并且删除掉tsconfig.jsontsconfig.node.json
{
  "name": "@Wannaer-element/play",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc && vite build",
    "preview": "vite preview"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.0.4"
  }
}

配置文件

在根目录创建一些必要额配置文件,比如刚才删除play中的ts配置,我们在根目录配置

  • tsconfig.json
{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "jsxImportSource": "vue",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["packages/**/*.ts", "packages/**/*.tsx", "packages/**/*.vue"]
}
  • tsconfig.node.json
{
  "extends": "@tsconfig/node18/tsconfig.json",
  "include": ["packages/**/**.config.ts"],
  "compilerOptions": {
    "composite": true,
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "types": ["node"]
  }
}
  • postcss.config.cjs
/* eslint-env node */
module.exports = {
  plugins: [
    require("postcss-nested"),
    require("postcss-each-variables"),
    require("postcss-each")({
      plugins: {
        beforeEach: [require("postcss-for"), require("postcss-color-mix")],
      },
    }),
  ],
};

配置完成后,重新安装一下依赖 pnpm install 执行之前更新的部分操作

创建各个分包入口

utils

在utils文件夹 新建一个文件 install.ts 用于 vue plugin 安装的一系列操作

import type { App, Plugin } from "vue";
import { each } from "lodash-es";

type SFCWithInstall<T> = T & Plugin;

export function makeInstaller(components: Plugin[]) {
  const install = (app: App) =>
    each(components, (c) => {
      app.use(c);
    });

  return install;
}

export const withInstall = <T>(component: T) => {
  (component as SFCWithInstall<T>).install = (app: App) => {
    const name = (component as any)?.name || "UnnamedComponent";
    app.component(name, component as SFCWithInstall<T>);
  };
  return component as SFCWithInstall<T>;
};

创建一个utils入口 index.ts 文件 用于导出utils所有方法

export * from "./install";

在这里插入图片描述

components

创建 index.ts 以及第一个基础组件 Button 组件目录

// index.ts
export * from './Button'
//  Button 目录 Button.vue
<template>
  <button style="color: red">this is a button</button>
</template>

<script setup lang="ts">
defineOptions({
  name: "WButton",
});
</script>

<style scoped></style>
// Button 目录 index.ts
import Button from "./Button.vue";
import { withInstall } from "@Wannaer-element/utils";

export const WButton = withInstall(Button);

在这里插入图片描述

core

创建 index.ts 、components.ts

// components.ts

import { ErButton } from "@toy-element/components";
import type { Plugin } from "vue";

export default [ErButton] as Plugin[];
import { makeInstaller } from "@toy-element/utils";
import components from "./components";

const installer = makeInstaller(components);

export * from "@toy-element/components";
export default installer;

在这里插入图片描述

play

在main.ts 中 引入了我们刚刚写好的"Wannaer-element"的自定义元素库,并在App.vue中使用。
通过createApp(App).use(WElement).mount(“#app”)这行代码,将"Wannaer-element"库应用到了Vue实例中,并挂载到了id为"app"的DOM元素上。
在这里插入图片描述
在根目录的package.json中配置

  "scripts": {
    "dev": "pnpm --filter @Wannaer-element/play dev",
    "test": "echo \"Error: no test specified\" && exit 1"
  }

它定义了一个名为"dev"的脚本命令。在这个命令中,使用了pnpm工具,并通过"–filter @Wannaer-element/play"参数指定了要过滤的包,然后执行"dev"命令。这段代码的作用是在开发过程中使用pnpm工具来过滤特定的包并执行相应的开发命令。

配置完成后运行 pnpm dev 可以查看到我们刚刚封装好的 Button 虽然很简陋 接下来我们进行样式的修改,让他变得更加美观
在这里插入图片描述

theme

创建 index.css 、reset.css 在 theme/index.css 中导入 reset.css

/** index.css */
@import "./reset.css";
/** reset.css */
body {
  font-family: var(--wan-font-family);
  font-weight: 400;
  font-size: var(--wan-font-size-base);
  line-height: calc(var(--wan-font-size-base) * 1.2);
  color: var(--wan-text-color-primary);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: transparent;
}

a {
  color: var(--wan-color-primary);
  text-decoration: none;

  &:hovwan,
  &:focus {
    color: var(--wan-color-primary-light-3);
  }

  &:active {
    color: var(--wan-color-primary-dark-2);
  }
}

h1,
h2,
h3,
h4,
h5,
h6 {
  color: var(--wan-text-color-regular);
  font-weight: inhwanit;

  &:first-child {
    margin-top: 0;
  }

  &:last-child {
    margin-bottom: 0;
  }
}

h1 {
  font-size: calc(var(--wan-font-size-base) + 6px);
}

h2 {
  font-size: calc(var(--wan-font-size-base) + 4px);
}

h3 {
  font-size: calc(var(--wan-font-size-base) + 2px);
}

h4,
h5,
h6,
p {
  font-size: inhwanit;
}

p {
  line-height: 1.8;

  &:first-child {
    margin-top: 0;
  }

  &:last-child {
    margin-bottom: 0;
  }
}

sup,
sub {
  font-size: calc(var(--wan-font-size-base) - 1px);
}

small {
  font-size: calc(var(--wan-font-size-base) - 2px);
}

hr {
  margin-top: 20px;
  margin-bottom: 20px;
  bordwan: 0;
  bordwan-top: 1px solid var(--wan-bordwan-color-lightwan);
}

最后改 package.json 中 入口为 index.css 在 core/index.ts 中导出我们的 theme
在这里插入图片描述

2、创建VitePress文档

可以直接参考官方文档

npx vitepress init

在这里插入图片描述

// 运行查看效果
pnpm docs:dev

在这里插入图片描述
我们改一下package.json指令 配置后统一可以从根目录运行

// docs目录 package.json
  "scripts": {
    "dev": "vitepress dev",
    "build": "vitepress build",
    "preview": "vitepress preview"
  },
// 根目录 package.js
  "scripts": {
    "dev": "pnpm --filter @Wannaer-element/play dev",
    "docs:dev": "pnpm --filter @Wannaer-element/docs dev",
    "docs:build": "pnpm --filter @Wannaer-element/docs build",
    "docs:preview": "pnpm --filter @Wannaer-element/docs preview",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

接下来我们需要将 VitePress文档部署到 GitHub Actions
所以需要配置一下 docs目录下vitepress => config.mts 添加一个 base: “/wan-element”,解决部署后样式丢失问题

import { defineConfig } from "vitepress";

// https://vitepress.dev/reference/site-config
export default defineConfig({
  title: "Wan-Element",
  description: "高仿 ElementPlus 组件库",
  base: "/wan-element",
  themeConfig: {
    // https://vitepress.dev/reference/default-theme-config
    nav: [
      { text: "Home", link: "/" },
      { text: "Examples", link: "/markdown-examples" },
    ],

    sidebar: [
      {
        text: "Examples",
        items: [
          { text: "Markdown Examples", link: "/markdown-examples" },
          { text: "Runtime API Examples", link: "/api-examples" },
        ],
      },
    ],

    socialLinks: [
      { icon: "github", link: "https://github.com/vuejs/vitepress" },
    ],
  },
});

3、部署到Github Actions

创建一个 .github/workflows/deploy.yml 文件,内容如下

name: deploy

on:
  push:
    branches:
      - master

jobs:
  test:
    name: Run Lint and Test
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repo
        uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v3

      - name: Install pnpm 
        run: npm install -g pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Run tests
        run: npm run test

  build:
    name: Build docs
    runs-on: ubuntu-latest
    needs: test

    steps:
      - name: Checkout repo
        uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v3

      - name: Install pnpm
        run: npm install -g pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build docs
        run: npm run docs:build

      - name: Upload docs
        uses: actions/upload-artifact@v3
        with:
          name: docs
          path: ./packages/docs/.vitepress/dist

  deploy:
    name: Deploy to GitHub Pages
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Download docs
        uses: actions/download-artifact@v3
        with:
          name: docs

      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GH_TOKEN }}
          publish_dir: .

secrets.GH_TOKEN 需要到Github 上面去生成

接下来去 github 创建一个仓库
在这里插入图片描述
复制仓库地址

https://github.com/Manba0/wan-element.git
git remote add origin https://github.com/Manba0/wan-element.git

git add .

git commit -m ":data: first commit"
生成 GH_TOKEN

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

最后将刚刚提交的代码 push到Github仓库

git push origin master

如果 push 出现一下报错
fatal: unable to access ‘https://github.com/XXXX/XXXX.git/’: Failed to connect to github.com port 443 after 21067 ms: Couldn’t connect to server

有可能你的gitbub之前设置过代理,只需分别执行如下代码即可:

git config --global --unset http.proxy
git config --global --unset https.proxy

提交成功后 发现 Settings 中Page 没有找到访问的链接,我们查看 Actions 发现 Run tests 没有通过, 因为我们根目录下 package.json 中的 test 指令 "test": "echo \"Error: no test specified\" && exit 1",修改成 "test": "echo 'todo'"重新提交
在这里插入图片描述
在这里插入图片描述

这样就是成功了 我们直接去看Settings中的page https://manba0.github.io/wan-element/
在这里插入图片描述
在这里插入图片描述

GitHub Page 演示

在这里插入图片描述

4、总结

到此我们就已经全流程跑通了 接下来就是完善组件内容了。

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

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

相关文章

vim操作手册

vim分为插入模式、命令模式、底行模式。 插入模式&#xff1a;编辑模式 命令模式&#xff1a;允许使用者通过命令&#xff0c;来进行文本的编辑控制 底行模式&#xff1a;用来进行让vim进行包括但不限于shell进行交互 w&#xff1a;保存 wq&am…

北邮22级信通院DSP:用C++程序实现给定参数下四种滤波器的Butterworth模拟滤波器设计:给定上下截频和衰减系数求H(p)和H(s)

北邮22信通一枚~ 跟随课程进度更新北邮信通院DSP的笔记、代码和文章&#xff0c;欢迎关注~ 获取更多文章&#xff0c;请访问专栏&#xff1a; 北邮22级信通院DSP_青山入墨雨如画的博客-CSDN博客 目录 一、 核心算法 1.1判断滤波器类型 1.2 带通滤波器BP 1.3带阻滤波器B…

十二、shell编程之awk

12.1 什么是awk 虽然sed编辑器是非常方便自动修改文本文件的工具&#xff0c;但其也有自身的限制。通常你需要一个用来处理文件中的数据的更高级工具&#xff0c;它能提供一个类编程环境来修改和重新组织文件中的数据。这正是awk能够做到的。 awk程序是Unix中的原始awk程序的…

P4097 【模板】李超线段树 / [HEOI2013] Segment 题解

题意 有一个平面直角坐标系&#xff0c;总共 n n n 个操作&#xff0c;每个操作有两种&#xff1a; 给定正整数 x 0 , y 0 , x 1 , y 1 x_0,y_0,x_1,y_1 x0​,y0​,x1​,y1​ 表示一条线段的两个端点。你需要在平面上加入这一条线段&#xff0c;第 i i i 条被插入的线段的标…

【面试干货】完全平方数

【面试干货】完全平方数 1、实现思想2、代码实现 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 一个整数&#xff0c;它加上 100 后是一个完全平方数&#xff0c;再加上 168 又是一个完全平方数&#xff0c;请问该数是多少&#xff1f; 1、…

设计模式 17 组合模式 Composite Pattern

设计模式 17 组合模式 Composite Pattern 1.定义 组合模式&#xff08;Composite Pattern&#xff09;&#xff0c;又叫部分整体模式&#xff0c;是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象&#xff0c;用来表示部分以及整体层次。这种类型的设…

wps使用(解决毕业论文)

目录自动生成 页码自动生成 一部分使用I II III IV 格式&#xff0c;一部分使用1&#xff0c;2&#xff0c;3&#xff0c;4 格式 先设置全部文章为I II III IV 格式&#xff0c;然后再需要的地方再设置1&#xff0c;2&#xff0c;3&#xff0c;4 格式 一键设置中文、英文、数…

特斯拉FSD的「端到端」到底能不能成?

引言 近年来&#xff0c;特斯拉的全自动驾驶&#xff08;Full Self-Driving&#xff0c;FSD&#xff09;技术备受关注&#xff0c;尤其是其「端到端」的AI软件框架更是引发了广泛讨论。端到端技术到底是一条正确的路径吗&#xff1f;它能否真正实现完全自动驾驶&#xff1f;本…

Java面试八股之什么是锁消除和锁粗化

什么是锁消除和锁粗化 锁消除&#xff08;Lock Elimination&#xff09;&#xff1a; 锁消除是Java虚拟机&#xff08;JVM&#xff09;进行的一种高级优化策略&#xff0c;旨在消除那些没有必要存在的同步操作&#xff0c;以减少不必要的性能开销。这一优化发生在即时编译器&a…

Docker拉取镜像报错:x509: certificate has expired or is not yet v..

太久没有使用docker进行镜像拉取&#xff0c;今天使用docker-compose拉取mongo发现报错&#xff08;如下图&#xff09;&#xff1a; 报错信息翻译&#xff1a;证书已过期或尚未有效。 解决办法&#xff1a; 1.一般都是证书问题或者系统时间问题导致&#xff0c;可以先执行 da…

Nginx-狂神说

Nginx概述 公司产品出现瓶颈&#xff1f; 我们公司项目刚刚上线的时候&#xff0c;并发量小&#xff0c;用户使用的少&#xff0c;所以在低并发的情况下&#xff0c;一个jar包启动应用就够了&#xff0c;然后内部tomcat返回内容给用户。 但是慢慢的&#xff0c;使用我们平台…

微信小程序- 实现横向滑动列表

1. 微信小程序-实现横向滑动列表 微信小程序如何隐藏scroll-view滚动条    1.1. photoScroll.wxml <view class"hs-body"><scroll-view class"hs-layout" scroll-x"true" scroll-left"{{x}}" scroll-with-animation&quo…

设计循环队列(C语言)怎会如此简单!!!

目录 题目题目分析 解答结构体初始化判空判满插入删除去队头数据取队尾数据队列的销毁 题目 链接: 题目 设计你的循环队列实现。 循环队列是一种线性数据结构&#xff0c;其操作表现基于 FIFO&#xff08;先进先出&#xff09;原则并且队尾被连接在队首之后以形成一个循环。它…

AI Agent: Agent框架+7个实例

何谓Agent Agent 作为一种新兴的人工智能技术&#xff0c;正在受到越来越多的关注。要说清楚什么是 Agent&#xff0c;先得看看人工智能的本质是什么。 人工智能这个名称来自它试图通过计算机程序或机器来模拟、扩展和增强人类智能的 一些方面。在这个定义中&#xff0c;“人…

【QGIS入门实战精品教程】10.6:QGIS制作酒店分布热力图

相关阅读: ArcGIS实验教程——实验四十二:ArcGIS密度分析(核密度、点密度、线密度) 【ArcGIS微课1000例】0086:基于七普人口数据的人口密度分析与制图 ArcGIS实验教程——实验二十四:人口密度制图 文章目录 一、加载酒店分布数据二、热力分析一、加载酒店分布数据 订阅专…

LeetCode刷题之HOT100之合并二叉树

2024/5/26 晴。是的&#xff0c;等下我要去长乐沙滩赶海哈哈&#xff0c;因为这几天数字峰会&#xff0c;地铁公交又免费啦。ok&#xff0c;今天做的是HOT100里面最后一道easy题目啦&#xff0c;明天就是要跨越一个难度啦&#xff01;做题吧 1、题目描述 2、逻辑分析 题目要求…

Python脚本必加代码:99%的程序员都忽视了这个细节!

文章目录 一、初识 if __name__ __main__二、__name__ 和 __main__ 是什么&#xff1f;三、实战讲解四、实际应用场景测试代码提高代码可重用性避免不必要的执行 五、深入理解和更多用法使用 argparse 解析命令行参数使用 unittest 进行单元测试使用 multiprocessing 创建子进…

任推邦:实力强劲的APP推广拉新平台,号称不扣量

任推邦简介 任推邦是国内数一数二的项目分发平台&#xff0c;也是一个不扣量的项目APP推广拉新平台&#xff0c;隶属于聚名科技集团股份有限公司。聚名科技成立时间在2012年&#xff0c;是安徽省老牌互联网企业&#xff0c;历经11年的飞速发展&#xff0c;聚名科技成功布局打造…

Adobe Bridge BR v14.0.3 安装教程 (多媒体文件组织管理工具)

Adobe系列软件安装目录 一、Adobe Photoshop PS 25.6.0 安装教程 (最流行的图像设计软件) 二、Adobe Media Encoder ME v24.3.0 安装教程 (视频和音频编码渲染工具) 三、Adobe Premiere Pro v24.3.0 安装教程 (领先的视频编辑软件) 四、Adobe After Effects AE v24.3.0 安装…

探秘NumPy的奥秘:元素级操作与广播机制

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、NumPy基础与元素级操作 元素级操作的引入 元素级操作详解 广播机制初探 二、NumPy矩…