前端公共组件库优化

背景

前段时间入职了新公司后,做一些内部前端基建的工作,其中一个工作就是优化现有的frontend-common公共组件库。之前的组件库一直是以源码依赖的形式存在,即各个项目通过git submodule的方式将该仓库引入到各个项目中,作为一个目录,然后打包的时候将frontend-common的源码以及项目本身的代码一起打包到产物中。公共组件的运行依赖于宿主,要求引入frontend-common的项目(宿主)本身要安装依赖的包,否则无法运行,例如公共组件依赖element这个库,所以引入公共组件的项目也要求要安装element才可以运行。

分析

当前这种使用方式以及实际的落地方式上存在一些问题,这里简单罗列下

  • 分支管理不规范(每个引用frontend-common的子项目都单独维护了一个分支,没有合入到主分支,导致各自的差异越来越大)
  • 代码风格不统一(不同的开发的编辑器配置不一样,导致大家提交上来的代码五花八门)
  • 组件没有文档和预览(写公共组件的开发实现之后就没有花更多时间在文档和预览上,导致其他开发要使用组件的时候有上手成本,而且不方便熟悉这些公共组件的功能和使用)
  • 没有提交规范(因历史原因,不少提交的commit message上面都是随便写,没有什么规范,也不方便根据commit信息判断改动的内容)
  • 无法保证改动不影响之前的一些功能(即无法保证能向下兼容,改动需要更多靠人工的方式来验证功能是否不影响之类的)
  • 使用submodule的方式引入的方式不是很优雅(个人偏向于用npm包的方式,或者用monorepo的方式)

优化思路

根据上面存在的一些问题我们有针对性的做出一些调整和策略

  • 分支管理规范,先让团队成员把各自的分支合并,如果只是单独自己项目用的组件,就迁移到自己项目的代码仓库中维护,不写在公共组件中。后续都从主分支拉新的分支进行开发,本地调试可以用自己的分支拉取代码调试,开发完之后合并到测试分支,线上环境和预发布环境必须用指定的分支来拉取公共组件库的代码。
  • 用eslint + prettier + husky + lint-stage来保证代码风格统一
  • 接入storybook,用于做组件预览和文档的功能
  • 增加commitlint commitizen等工具,用于命令式生成commit,保证commit信息的规范
  • 增加单元测试,新增一个组件要写单元测试,后续修改之后要保证之前的单元测试都运行通过才可以合并代码
  • 因为内部基建的原因,暂时还没有搭建内部的npm源,monorepo的方式改动也比较大,暂时不做调整

改造步骤

仓库初始化npm

因为原先是作为当成一个组件来使用,所以frontend-common这个代码仓库里面是没有package.json node_module等配置,我们为了接入的规范肯定要增加包来处理这些,所以第一步要初始化npm

npm init

直接按提示输入即可,这里就不再赘述

增加代码规范的包

eslint + prettier + lint-stage + husky + 对应的eslint包
根据自己项目的实际情况增加对应的包,比如笔者这个仓库是用vue2的,就用vue相关的eslint包
这里笔者列一下自己安装的包和创建的配置文件

新增包和命令

在package.json中新增对应的包和命令、配置

"scripts": {..."lint-staged": "lint-staged","prepare": " husky install",
},
"lint-staged": {"*.{js,ts,vue,jsx,tsx}": ["eslint --cache --fix"]
},
"devDependencies": {..."@commitlint/cli": "^17.6.5","@commitlint/config-conventional": "^17.6.5","eslint": "^7.32.0","eslint-config-prettier": "^8.3.0","eslint-plugin-prettier": "^4.0.0","eslint-plugin-vue": "^8.0.3","husky": "^8.0.0","lint-staged": "^13.2.2","prettier": "^2.4.1",
}
新增配置文件
  • .eslintrc.js (eslint的配置文件)
  • .eslintignore(eslint的忽略配置文件)
  • .prettier.js (prettier的配置文件)
  • commitlint.config.js (commitlint的配置文件)

commitlint.config.js

module.exports = {extends: ["@commitlint/config-conventional"],
};

eslintrc.js

// .eslintrc.js
module.exports = {root: true,// 指定代码的运行环境env: {browser: true,node: true,es6: true},plugins: ['vue', 'prettier'],extends: [// 继承 vue 的标准特性'plugin:vue/essential','eslint:recommended',// 避免与 prettier 冲突'plugin:prettier/recommended'],parserOptions: {// 定义ESLint的解析器parser: '@babel/eslint-parser',sourceType: 'module'}
};
处理husky

配置完成之后记得npm i,这时候会添加.husky目录,给这个目录添加文件
commit-msg

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"npx commitlint -e $GIT_PARAMS

pre-commit

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"npm run lint-staged

参考目录如下:
image.png

运行

这样配置完后续正常commit就会触发eslint和commitlint,保证提交的代码和commit的规范。如果报错要修复完问题才可以正常提交,而且代码都会进行格式化,保证每个人提交的风格都一致。其他的不展开赘述。

接入storybook

初始化storybook

在原先的项目中执行命令初始化storybook的相关配置和依赖

npx -p @storybook/cli sb init --type vue

选择webpack5和安装依赖

image.png

自动运行storybook

image.png

打开浏览器,我们可以看到storybook的界面

image.png

来走读一下创建出来的storybook demo文件,我们以Button.stories.js这个文件为例

import MyButton from './Button.vue';// More on how to set up stories at: https://storybook.js.org/docs/vue/writing-stories/introduction
export default {title: 'Example/Button',component: MyButton,tags: ['autodocs'],render: (args, { argTypes }) => ({props: Object.keys(argTypes),components: { MyButton },template: '<my-button @onClick="onClick" v-bind="$props" />',}),argTypes: {backgroundColor: { control: 'color' },size: {control: { type: 'select' },options: ['small', 'medium', 'large'],},},
};// More on writing stories with args: https://storybook.js.org/docs/vue/writing-stories/args
export const Primary = {args: {primary: true,label: 'Button',},
};export const Secondary = {args: {label: 'Button',},
};export const Large = {args: {size: 'large',label: 'Button',},
};export const Small = {args: {size: 'small',label: 'Button',},
};

走读下default这个配置

  • title: 该故事在 Storybook 应用的侧边栏中的名称。路径式的名称表示故事的层级结构。在这个例子中,“Example” 是一个文件夹,“Button” 是这个文件夹下的一个故事。

  • component: 这是你想要展示的组件,Storybook 将使用它来自动生成文档页(如果你启用了这个功能)。

  • tags: 这是一个标签数组,你可以添加任何你喜欢的标签来帮助你组织和查找你的故事。

  • render: 这是一个函数,返回一个 Vue 组件的配置对象,用于定义如何渲染故事。在这个例子中,所有的 args 和 argTypes 都被传递给 MyButton 组件,你可以在 Storybook 的 UI 中调整它们的值。

  • argTypes: 这个对象定义了每个 arg 的控件和其他配置。在这个例子中,backgroundColor 的控件是一个颜色选择器,size 的控件是一个下拉列表,选项包括 ‘small’、‘medium’ 和 ‘large’。

    • control: 用于指定参数控件的类型,例如:‘color’、‘select’、‘range’ 等。
    • options: 用于指定 ‘select’ 类型控件的选项。
新建story

新建一个story,用于编写我们自己的组件的story,如下,这个是我们新创建的stories文件,我们引入自己的vue2组件

image.png

先照猫画虎写一个配置

import CommonNoFound from "../../../components/commonPage/FcommonNoFound/index.vue";export default {title: "components/FcommonNoFound",component: CommonNoFound,tags: ["autodocs"],
};export const NoFound = {};

看下效果

image.png

image.png

这是因为我们的组件里面用了vue-i18n,用$t然后storybook识别不到,这里我们就需要解决这个vue-i18n的问题

解决vue-i18n

我们需要在.storybook/preview.js中设置vue-i18n相关的配置 看下原先的文件

/** @type { import('@storybook/vue').Preview } */
const preview = {parameters: {actions: { argTypesRegex: "^on[A-Z].*" },controls: {matchers: {color: /(background|color)$/i,date: /Date$/,},},},
};export default preview;

我们在这个文件的基础上增加vue-i18n的配置 要预先安装好vue vue-i18n,然后同i18n初始化一致实例化i18n实例然后设置到storybook中 看下代码

import Vue from "vue";
import VueI18n from "vue-i18n";
import en from "../lang/en_us";
import zh from "../lang/zh_cn";Vue.use(VueI18n);const i18n = new VueI18n({locale: "en",messages: {zh: {language: "简体中文",...zh,},en: {language: "English",...en,},},
});/** @type { import('@storybook/vue').Preview } */
const preview = {parameters: {actions: { argTypesRegex: "^on[A-Z].*" },controls: {matchers: {color: /(background|color)$/i,date: /Date$/,},},},decorators: [(Story) => ({components: { Story },template: '<story v-bind="$props" />',i18n,}),],
};export default preview;

在这个例子中,decorators 数组中的函数接收一个 Story 参数,这个参数表示当前的故事组件。然后,我们创建了一个模板,这个模板包含一个 story 组件,并且使用 v-bind 来绑定故事的属性。最后,我们在 components 对象中指定了 Story 组件。

这样,你的故事组件就会接收到 i18n 实例,并且会正确地被渲染。

解决环境变量问题

vue代码里面会有环境变量,但是在storybook的环境中这个环境变量是没有的,所以我们需要手动设置这个环境变量,保证我们的代码可以正常运行 这时候我们需要一个包,我们安装dotEnv这个包

npm i dotenv --save-dev

然后我们新建一个.env的文件,在这个文件中我们设置我们需要的环境变量,例如我的这个

VUE_APP_WEB_ENV=dev

然后就是在我们的storybook的mainjs或者preview.js配置中引入dotEnv的配置即可

require("dotenv").config();
// 其他配置
解决请求代理问题

我们在常见的vue项目中,本地开发会经常用proxy的配置来解决跨域问题,转发接口,当我们的组件中依赖了接口的话,这时候我们可以同样模拟一下这个proxy的过程 我们需要安装proxy的包

npm install http-proxy-middleware --save-dev

然后我们在.storybook目录下新建一个middleware.js,然后同我们的webpack配置一样补充我们需要的proxy,这里补充下笔者接手的这个项目的配置

const { createProxyMiddleware } = require("http-proxy-middleware");module.exports = function expressMiddleware(router) {let env = process.env.VUE_APP_WEB_ENV || "dev";const comonApi = require(`../config/api/${env}.js`);if (comonApi && Object.keys(comonApi).length) {Object.keys(comonApi).forEach((e) => {const apiBase = comonApi[e].apiBase;const apiRoot = comonApi[e].apiRoot;if (apiBase && apiRoot) {router.use(apiBase,createProxyMiddleware({target: apiRoot,changeOrigin: true,pathRewrite: {[`^${apiBase}`]: "",},}));}});}
};
引入组件库

组件会依赖一些UI库的组件,比如笔者用到的element ui,在storybook中需要引入这些element的组件,这里我们在.storybook/preview.js中引入element,参考如下

import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";Vue.use(ElementUI);
解决样式问题

引入组件会有一些样式,所以我们也需要处理下引入的css,类似webpack一样增加对应的loader,我们安装对应的loader

npm install --save-dev sass-loader style-loader css-loader

然后在.storybook/main.js文件中补充对应的webpack配置

const config = {webpackFinal: async (config, { configType }) => {// 处理 SCSS 文件config.module.rules.push({test: /\.scss$/,use: ["style-loader", "css-loader", "sass-loader"],});return config;},
}

接入commitizen

组件库之前的各种commit都是五花八门,这里为了规范commit信息,然后方便后面生成changelog,我们这里需要一个命令式的commit提交工具,笔者选择了用commitizen,先安装好这个包

npm install --save-dev commitizen cz-conventional-changelog 

然后在我们的package.json中增加对应的script或者配置

"scripts": {"commit": "git-cz",...
},
"config": {"commitizen": {"path": "cz-conventional-changelog"}
},

配合我们上面的husky,我们可以在.husky目录下新增prepare-commit-msg文件,然后补充下面的命令,在我们git的生命周期中触发commitzen

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"exec < /dev/tty && npx cz --hook || true

因为我们都是用commitzen生成的commit信息,上面原先.husky/commit-msg可以考虑移除掉了,笔者只保留了两个

image.png

我们正常运行git add git commit就会触发下面这个,然后根据实际情况填写内容

image.png 全部填写完成之后就会生成对应的commit记录 image.png

生成changelog(可忽略)

下面的自动升级版本的命令会自动生成changelog,实际接入中可以不用看这一部分 changelog就是根据我们的commit生成变更的日志,尝试效果的话我们需要引入新的包

npm install --save-dev conventional-changelog-cli

在package.json中增加一个生成changelog的脚本,通过这个命令我们可以手动生成changelog

{"scripts": {"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"}
}

版本升级

命令升级

我们需要在准备发版的时候,更新package.json中的版本号,生成changelog文件,提交更改和创建标签,这里我们需要用到第三方的工具包,这里用了standard-version

npm install --save-dev standard-version

增加脚本

"scripts": {"release": "standard-version"
}

当我们准备发新版本的时候,就跑一下这个命令npm run release,这时候就会帮我们自动增加一个commit做上面说的事情,比如这样的commit

image.png

因为standard-version这个包内置了生成changelog的包,所以我们不需要额外引入上面部分提到的conventional-changelog-cli

跳过检测

因为我们通过上面命令会自动提交一个commit,但是我们的commit会触发我们的eslint、commintlint等,就像上面截图的那种命令式的commit界面,我们其实不需要再次手动输入,只需要release自己生成即可,所以这里我们要做的是跳过prepare-commit-msg这个阶段,思路是增加一个环境变量,然后跳过这个命令

.husky/prepare-commit-msg调整为

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"if [ "$HUSKY_SKIP_HOOKS" = "1" ]; thenecho "Skipping prepare-commit-msg hook"exit 0
fiexec < /dev/tty && npx cz --hook || true

release命令设置参数

"release": "HUSKY_SKIP_HOOKS=1 standard-version"
兼容多平台

上面的设置参数在mac下是可以的,但是在windows下不行,为了兼容命令,这里我们需要增加cross-env
安装这个包

npm i cross-env --save-dev

更新release命令

"release": "cross-env HUSKY_SKIP_HOOKS=1 standard-version"

接入单元测试

单元测试的作用

组件库会被多个项目引用,每个项目的情况不一样,可能需要根据本身项目的需求对组件进行修改或者增加一些改动,原则上改动都是要向下兼容的,每次组件库更新理论上引用的项目都要跟着更新,验证下改动是否没问题,但是考虑到每次都要让各个项目来验证这种成本比较高,所以引入单元测试,组件的创建人在写完组件之后,顺便根据自己的场景补充好单元测试。下一个修改的人如果要修改这个组件,修改完成之后,需要保证原先的单元测试都跑通过才可以,另外需要补充单元测试。

编写单元测试

我们在编写好vue组件之后,如果要对当前这个组件编写单元测试,可以在组件当前的目录(初定是和组件放在同一个目录下)创建对应的一个 xx.spec.js文件,然后在文件中编写对应的单元测试,可以参考项目中已有的单元测试文件,如下。

import { shallowMount } from "@vue/test-utils";
import CommonNoFound from "./index";
import { i18n, localVue } from "../../../jest.setup";describe("FcommonNoFound.vue", () => {it("can find list text", () => {const wrapper = shallowMount(CommonNoFound, { localVue, i18n });expect(wrapper.text()).toContain("Lost...");});it("can find pageNotFound text", () => {const wrapper = shallowMount(CommonNoFound, { localVue, i18n });expect(wrapper.text()).toContain("页面已飞到太空外");});
});
运行与调试单元测试

我们在package.json中增加一个命令,用于运行单元测试

{"scripts": {"test": "jest"}
}

运行单个单测文件,可以单独验证单测文件是否运行通过,可以在命令后面补充对应的单测文件路径

npm run test components/commonPage/FcommonNoFound/commonNoFound.spec.js

image.png

运行结果,可以看到哪些通过哪些不通过,如果不通过会有报错信息,根据报错信息调整单测

全量运行,结果展示同上

npm run test

image.png

单元测试卡点

有了单元测试之后,我们需要在每次提交合并的时候保证所有的单元测试都跑通过,否则就不给合并代码,相当于对每次合码都做一次卡点,减少一些改动无法向下兼容,导致引用组件的项目出现问题。

  • 可以考虑使用自动化测试在每次PR或者MR的时候做运行所有的单元测试,检查测试覆盖率之类的,可以参考笔者之前的这篇文章
  • 如果无法做自动化测试的话,可以考虑每次PR或者MR的时候要求提交人补充本地运行所有单元测试的结果,这里就可以通过配置一些MR或者PR提交的模板,要求代码提交人按这种格式来提交,补充好单元测试的截图之类的

合并代码策略

指定分支合并到对应的分支,例如合并到release或者master分支,这时候会有预置的模板,按照模板补充说明然后提交PR进行审核
以下是笔者的搞的一个合码的模板,要求提交人按这种格式去填写

image.png

组件预览部署

在上面的步骤中我们已经接入了storybook,可以在本地预览,如果我们要单独把storybook单独部署一个到一个站点,其他开发可以直接打开去看

增加构建命令

在package.json中增加命令,构建出storybook的产物

"scripts": {"build-dev": "storybook build -o dist",
}
项目部署

配合运维,绑定好分支,然后当指定分支有merge或者Push的时候,触发构建,这个根据自己团队的情况去部署即可。
笔者部署完的大概样子如下:

image.png

总结

当前这版优化对现有的组件库做了一次大的调整,本身不涉及具体组件的改动,只是规范和优化整个流程,方便前端开发接入和使用等,但是还存在不少的优化空间,比如以submodule接入的方式,笔者觉得不是很好,还是偏向于用npm包的方式,但是由于内部还没有搞自建的npm源,加上不少项目都已经在用submodule的方式了,所以暂时不做这种处理。在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【K8S 云原生】K8S的对外服务—ingress

目录 一、K8S的Service 1、Service的作用 2、Service类型&#xff1a; 二、ingress 1、ingress的组成&#xff1a; 2、ingress资源的定义项&#xff1a; 3、ingress暴露服务端的方式 3.1、DeploymentLoadBalancer模式&#xff1a; 1、工作流程图&#xff1a; 3.2、Dae…

PDF转PowerPoint - Java实现方法

通过编程实现PDF转PPT的功能&#xff0c;可以自动化转换过程&#xff0c;减少手动操作的工作量&#xff0c;并根据需要进行批量转换。将PDF文件转换为PPT文档后&#xff0c;可以利用PPT的丰富功能和动画效果&#xff0c;达到更好的演示效果。 在Java中&#xff0c;我们可以使用…

PyTorch损失函数(二)

损失函数 5、nn.L1Loss nn.L1Loss是一个用于计算输入和目标之间差异的损失函数&#xff0c;它计算输入和目标之间的绝对值差异。 主要参数&#xff1a; reduction&#xff1a;计算模式&#xff0c;可以是none、sum或mean。 none&#xff1a;逐个元素计算损失&#xff0c;返…

清晰光谱空间:全自动可调波长系统的高光谱成像优势

高光谱成像技术 高光谱成像技术是一种捕获和分析宽波长信息的技术&#xff0c;能够对材料和特征进行详细的光谱分析和识别。高光谱成像技术的实现通过高光谱相机&#xff0c;其工作原理是使用多个光学传感器或光学滤波器分离不同波长的光&#xff0c;并捕获每个波段的图像&…

Node+Express编写接口---服务端

开始&#xff1a; 个人兴趣爱好&#xff0c;欢迎大家多多指教&#xff01;(点击直达源码!) node_vue_admin: 第一个以node为后端,vue为前端的后台管理项目https://gitee.com/ah-ah-bao/node_vue_admin.git 第一步:安装 安装Node.js 点击直达Node.js (nodejs.org) 安装Navic…

点赞模块设计 - Redis缓存 + 定时写入数据库实现高性能点赞功能

点赞模块设计 - Redis缓存 定时写入数据库实现高性能点赞功能 源码地址&#xff1a;github.com/cachecats/c… 点赞是作为整个系统的一个小模块&#xff0c;代码在 user-service 用户服务下。 本文基于 SpringCloud, 用户发起点赞、取消点赞后先存入 Redis 中&#xff0c;再…

WordPress回收站自动清空时间?如何关闭回收站或设置自动清理天数?

我们在WordPress后台的文章、页面、评论等页面都可以看到有回收站&#xff0c;意思就是我们不能直接删除某篇文章、页面、评论&#xff0c;而是需要现将它们移至回收站&#xff0c;然后再到回收站永久删除&#xff0c;或等回收站自动清理。 如上图所示&#xff0c;WordPress 6.…

javacv和opencv对图文视频编辑-裸眼3D图片制作

通过斗鸡眼&#xff0c;将左右两张相似的图片叠加到一起看&#xff0c;就会有3D效果。 3D图片&#xff0c;3D眼镜&#xff0c;3D视频等原理类似&#xff0c;都是通过两眼视觉差引起脑补产生3D效果。 图片&#xff1a; 图片来源&#xff1a; 一些我拍摄的真*裸眼3D照片 - 哔哩…

Java21 + SpringBoot3集成Spring Data JPA

Java21 SpringBoot3集成Spring Data JPA 文章目录 Java21 SpringBoot3集成Spring Data JPA前言相关技术简介ORM&#xff08;Object-Relational Mapping&#xff0c;对象-关系映射&#xff09;JPA&#xff08;Java Persistence API&#xff0c;Java持久层API&#xff09;Hiber…

新版K8s:v1.28拉取Harbor仓库镜像以及本地镜像(docker弃用改用containerd,纯纯踩坑)

目录 一、项目概述二、环境三、项目样式Harborkuboard运行样式 四、核心点Harbor安装config.toml文件修改(containerd)ctr、nerdctl相关命令kuboard工作负载 五、总结 一、项目概述 使用Kuboard作为k8s集群的管理平台&#xff0c;Harbor作为镜像仓库&#xff0c;拉取Harbor镜像…

【kafka】记录用-----------1

主题&#xff08;topic&#xff09;&#xff1a;消息的第一次分类 根据人为的划分条件将消息分成不同的主题 主题的划分是人为的根据不同的任务情景去划分 比如&#xff0c;我们有两个主题&#xff0c;一个是"订单"&#xff0c;另一个是"库存"。每个主题代…

24号资源——程序:电力系统程序集合已提供下载资源

24号资源&#xff1a;程序集合包含17个程序&#xff08;经典电力系统经济调度程序&#xff1b;2解决带储&#xff1b;3智能微电网PSO优化算法&#xff1b;微电网调度等等见资源描述&#xff09;资源-CSDN文库https://download.csdn.net/download/LIANG674027206/88752141&#…

ruoyi-cloud—若依微服务打包部署

1. 前端端口修改 2. 后端端口修改 &#xff08;1&#xff09;修改ruoyi-gateway服务中的bootstrap.yml的port端口 &#xff08;2&#xff09;修改ruoyi-ui中的vue.confing.js的target中的端口 3. 后端部署 (1) 在本地电脑上代码界面上打包后端 在ruoyi项目的bin目录下执行pa…

迭代器模式介绍

目录 一、迭代器模式介绍 1.1 迭代器模式定义 1.2 迭代器模式原理 1.2.1 迭代器模式类图 1.2.2 模式角色说明 1.2.3 示例代码 二、迭代模式的应用 2.1 需求说明 2.2 需求实现 2.2.1 抽象迭代类 2.2.2 抽象集合类 2.2.3 主题类 2.2.4 具体迭代类 2.2.5 具体集合类 …

【动态规划】【数学】【C++算法】18赛车

作者推荐 视频算法专题 本文涉及知识点 动态规划 数学 LeetCode818赛车 你的赛车可以从位置 0 开始&#xff0c;并且速度为 1 &#xff0c;在一条无限长的数轴上行驶。赛车也可以向负方向行驶。赛车可以按照由加速指令 ‘A’ 和倒车指令 ‘R’ 组成的指令序列自动行驶。 当…

软件工程应用题汇总

绘制数据流图(L0/L1/L2) DFD/L0&#xff08;基本系统模型&#xff09; 只包含源点终点和一个处理(XXX系统) DFD/L1&#xff08;功能级数据流图&#xff09;在L0基础上进一步划分处理(XXX系统) 个人理解 DFD/L2&#xff08;在L1基础上进一步分解后的数据流图&#xff09; 数据…

flex布局(3)

九、骰子 *{margin:0;padding: 0;box-sizing: border-box; } .flex{display: flex;flex-flow: row wrap;justify-content: space-between;align-items: center;align-content: space-between;padding:20px; } .touzi{width: 120px;height: 120px;background-color: aliceblue;…

canvas绘制美队盾牌

查看专栏目录 canvas示例教程100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

陶瓷碗口缺口检测-图像分割

图像分割 由于对碗口进行缺口检测&#xff0c;因此只需要碗口的边界信息。得到陶瓷碗区域填充后的图像&#xff0c;对图像进行边缘检测。这是属于图像分割中的内容&#xff0c;在图像的边缘中&#xff0c;可以利用导数算子对数字图像求差分&#xff0c;将边缘提取出来。 本案…

React 基于Ant Degisn 实现table表格列表拖拽排序

效果图&#xff1a; 代码&#xff1a; myRow.js import { MenuOutlined } from ant-design/icons; import { DndContext } from dnd-kit/core; import { restrictToVerticalAxis } from dnd-kit/modifiers; import {arrayMove,SortableContext,useSortable,verticalListSorti…