前端公共组件库优化

背景

前段时间入职了新公司后,做一些内部前端基建的工作,其中一个工作就是优化现有的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" ]; then
    echo "Skipping prepare-commit-msg hook"
    exit 0
fi
    exec < /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.mfbz.cn/a/331320.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Win32 字符串表达式计算

简单表达式计算实用类 支持的运算如表所示 运算符号释义例子加法1024512-减法1024-512*乘法1024*1024/除法1024/128^平方1024^2%取模(求余数)10%3(优先级左括号(1024512)*8)优先级右括号(1024512)*8 表达式示例: 表达式有效性备注2(2-7)*2*(8-2)/2有效1024^3有效1024的3次方…

头像空白问题

当用户没有设置头像时&#xff0c;我们可以使用用户名第一个字来当头像 主要涉及一个截取&#xff0c;截取字符串第一个字 变量名.charAt(0) 如果变量名为null或者undefine 那么就会报错 使用可选链操作符 &#xff1f; 当前面的值为nul或undefine时&#xff0c;就不会执行…

CSS||选择器

目录 作用 分类 基础选择器 标签选择器 ​编辑类选择器 id选择器 通配符选择器 作用 选择器&#xff08;选择符&#xff09;就是根据不同需求把不同的标签选出来这就是选择器的作用。 简单来说&#xff0c;就是选择标签用的。 选择器的使用一共分为两步&#xff1a; 1.…

代码随想录算法训练营第23天 | 669. 修剪二叉搜索树 + 108.将有序数组转换为二叉搜索树 + 538.把二叉搜索树转换为累加树

今日任务 669. 修剪二叉搜索树 108.将有序数组转换为二叉搜索树 538.把二叉搜索树转换为累加树 总结篇 669. 修剪二叉搜索树 - Medium 题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 给你二叉搜索树的根节点 root &#xf…

代码随想录算法训练营29期|day 22 任务以及具体安排

235. 二叉搜索树的最近公共祖先 class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if(root null) return null;//向左遍历if(root.val > p.val && root.val > q.val){TreeNode left lowestCommonAncestor(roo…

MySQL表的基本插入查询操作详解

博学而笃志&#xff0c;切问而近思 文章目录 插入插入更新 替换查询全列查询指定列查询查询字段为表达式查询结果指定别名查询结果去重 WHERE 条件基本比较逻辑运算符使用LIKE进行模糊匹配使用IN进行多个值匹配 排序筛选分页结果更新数据删除数据截断表聚合函数COUNTSUMAVGMAXM…

C语言——atoi函数解析

目录 前言 atoi函数的介绍 atoi函数的使用 atoi函数的模拟实现 前言 对于atoi函数大家可能会有些陌生&#xff0c;不过当你选择并阅读到这里时&#xff0c;请往下阅读&#xff0c;我相信你能对atoi函数熟悉该函数的头文件为<stdlib.h> 或 <cstdlib> atoi函数的…

被遗忘在角落的RPA,成了提升AI Agent执行能力的天选神器

LLM&#xff08;Large Language Models&#xff09;刚爆发之时&#xff0c;很多人认为RPA要完了&#xff0c;自然语言交互API操作足以干掉任何UI自动化工具。 然而&#xff0c;大语言模型应用发展到AI Agent这一步&#xff0c;大家才发现API并不是万能的。Agent平台雨后春笋一…

【开源项目】经典开源项目实景三维数字孪生泰山

飞渡科技数字孪生文旅运营中心&#xff0c;基于文旅单位的运营管理、服务质量以及游客需求&#xff0c;通过数字孪生、AR/VR、大数据分析等技术&#xff0c;为景区打造虚实融合、超沉浸体验的专属虚拟数字场景&#xff0c;实现文旅领域的数据可视化、产业数字化以及智能化管理。…

Django Web开发(day4)——数据模型使用与填充网站数据(对数据库的基本操作)

本博客将会涉及: Django 数据模型的使用视频数据的导入admin 后台的使用 1、Django 数据模型的使用 在上一篇中完成了网站的数据模型的创建,在数据模型创建之后,Django 会为我们的数据模型创建一套数据库抽象的 API 接口,以供我们进行检索数据、创建数据、更新和修改数据…

vim 编辑器如何同时注释多行以及将多行进行空格

当然可以&#xff0c;以下是我对您的文字进行润色后的版本&#xff1a; 一、场景 YAML文件对空格的要求非常严格&#xff0c;因此在修改YAML时&#xff0c;我们可能需要批量添加空格。 二、操作步骤 请注意&#xff1a;您的所有操作都将以第一行为基准。也就是说&#xff0…

滚动菜单ListView

activity_main.xml <include layout"layout/title"/> 引用上章自定义标题栏 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app&qu…

BigeMap在Unity3d中的应用,助力数字孪生

1. 首先需要用到3个软件&#xff0c;unity&#xff0c;gis office 和 bigemap离线服务器 Unity下载地址:点击前往下载页面(Unity需要 Unity 2021.3.2f1之后的版本) Gis office下载地址:点击前往下载页面 Bigemap离线服务器 下载地址: 点击前往下载页面 Unity用于数字孪生项…

计算机系统基础知识揭秘:硬件、处理器和校验码

计算机系统基础知识揭秘&#xff1a;硬件、处理器和校验码 一、计算机系统基础知识的重要性二、计算机系统硬件2.1、内存和存储设备2.2、输入输出设备 三、中央处理器&#xff08;CPU&#xff09;3.1、运算器3.2、控制器3.3、寄存器组3.4、多核CPU 四、数据表示4.1、原码、反码…

前端项目配置 Dockerfile 打包后镜像部署无法访问

Dockerfile 配置如下&#xff1a; FROM node:lts-alpineWORKDIR /app COPY . . RUN npm install RUN npm run buildEXPOSE 3001CMD ["npm", "run", "preview"]构建镜像 docker build -t vite-clarity-project .启动镜像容器 docker run -p 30…

《C++入门篇》——弥补C不足

文章目录 前言一.命名空间二.缺省参数三.函数重载四.引用4.1引用做参数4.2引用做返回值 五.内联函数六.小语法6.1auto6.2范围for6.3空指针 前言 C是业内一门久负盛名的计算机语言&#xff0c;从C语言发展起来的它&#xff0c;不仅支持C语言的语法&#xff0c;还新添加了面向对…

MySQL之视图索引

学生表&#xff1a;Student (Sno, Sname, Ssex , Sage, Sdept) 学号&#xff0c;姓名&#xff0c;性别&#xff0c;年龄&#xff0c;所在系 Sno为主键 课程表&#xff1a;Course (Cno, Cname,) 课程号&#xff0c;课程名 Cno为主键 学生选课表&#xff1a;SC (Sno, Cno, Score)…

华为设备NAT的配置

实现内网外网地址转换 静态转换 AR1&#xff1a; sys int g0/0/0 ip add 192.168.10.254 24 int g0/0/1 ip add 22.33.44.55 24 //静态转换 nat static global 22.33.44.56 inside 192.168.10.1 动态转换 最多有两台主机同时访问外网 AR1&#xff1a; sys int g0/0/0 ip add…

C语言之【函数】篇章以及例题分析

文章目录 前言一、函数是什么&#xff1f;二、C语言中函数的分类1、库函数2、自定义函数 三、函数的参数1、实际参数&#xff08;实参&#xff09;2、形式参数&#xff08;形参&#xff09; 四、函数的调用1、传值调用2、传址调用3、专项练习3.1 素数判断3.2 闰年判断3.3 二分查…

【OpenCV学习笔记17】- 平滑图像

这是对于 OpenCV 官方文档中 图像处理 的学习笔记。学习笔记中会记录官方给出的例子&#xff0c;也会给出自己根据官方的例子完成的更改代码&#xff0c;同样彩蛋的实现也会结合多个知识点一起实现一些小功能&#xff0c;来帮助我们对学会的知识点进行结合应用。 如果有喜欢我笔…