5-1 React 实战之从零到一的组件库、插件开发(三)

第一篇:# 5-1 React 实战之从零到一的项目环境搭建(一)

接上文:# 5-1 React 实战之从零到一的项目开发(二)

4、组件库开发

采用 Rollup 打包,产物为 umd、esm 两种

  1. 新建组件库文件夹(进入packages/components内)
arduino 复制代码
mkdir react-x-components
  1. 初始化该项目(进入packages/component/react-x-components内)
bash 复制代码
cd react-x-components && pnpm init
  1. 创建两个组件文件
bash 复制代码
mkdir src && mkdir src/button && touch src/button/index.tsx && mkdir src/card && touch src/card/index.tsx
  1. src/button/index.tsx写入代码
javascript 复制代码
import React from "react";

type Props = {
	children: React.ReactNode;
	onClick?: () => void;
};

export default function Button({ children, onClick }: Props) {
	return (
		<button
			onClick={onClick}
			className=" w-16 h-8 mx-4 text-sm bg-blue-500 text-white flex justify-center items-center rounded-full hover:bg-blue-800 transition-all"
		>
			{children}
		</button>
	);
}
  1. src/card/index.tsx写入代码
typescript 复制代码
import React from "react";

type Props = {
	className?: string;
	children?: React.ReactNode;
};

export default function Card({ className, children }: Props) {
	return (
		<div
			className={` bg-white border border-gray-200 m-2 rounded-sm shadow-md ${className}`}
		>
			{children}
		</div>
	);
}

一、手写 Rollup 配置

  1. 安装对应依赖(packages/components/react-x-components下)
sql 复制代码
pnpm add rollup rollup-plugin-clear rollup-plugin-auto-add rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-alias rollup-plugin-peer-deps-external rollup-plugin-filesize rollup-plugin-postcss rollup-plugin-terser rollup-plugin-multi-input postcss typescript react @types/react -D
  1. 创建对应文件
bash 复制代码
mkdir scripts && touch scripts/rollup.config.js && touch tsconfig.json && touch scripts/tsconfig.esm.json && touch scripts/tsconfig.umd.json
  1. tsconfig.json写如下代码
json 复制代码
{
	"compilerOptions": {
		"target": "ESNext",
		"useDefineForClassFields": true,
		"lib": ["DOM", "DOM.Iterable", "ESNext"],
		"outDir": "./lib",
		"allowJs": false,
		"skipLibCheck": false,
		"esModuleInterop": false,
		"allowSyntheticDefaultImports": true,
		"strict": true,
		"forceConsistentCasingInFileNames": true,
		"module": "ESNext",
		"moduleResolution": "Node",
		"resolveJsonModule": true,
		"isolatedModules": true,
		"noEmit": false,
		"jsx": "react", // react18这里也可以改成react-jsx
		"baseUrl": "./",
		"paths": {
			"@/*": ["src/*"]
		},
		"noImplicitAny": false // 是否在表达式和声明上有隐含的any类型时报错
	},
	"include": ["./src/**/*"],
	"exclude": ["node_modules", "**/dist", "**/esm"],
	"ts-node": {
		"compilerOptions": {
			"module": "CommonJS"
		}
	}
}
  1. scripts/tsconfig.esm.json写如下代码
json 复制代码
{
	"extends": "../tsconfig.json",
	"ts-node": {
		"transplieOnly": true,
		"require": ["typescript-transform-paths/register"]
	},
	"compilerOptions": {
		"plugins": [
			{ "transform": "typescript-transform-paths" },
			{
				"transform": "typescript-transform-paths",
				"afterDeclarations": true
			}
		],
		"declaration": true,
		"jsx": "react",
		"jsxFactory": "React.createElement",
		"jsxFragmentFactory": "React.Fragment"
	},
	"include": ["../src"]
}
  1. scripts/tsconfig.umd.json写如下代码
json 复制代码
{
	"extends": "../tsconfig.json",
	"compilerOptions": {
		"jsx": "react",
		"jsxFactory": "React.createElement",
		"jsxFragmentFactory": "React.Fragment"
	}
}
  1. 改造package.json加上peerDependencies告知当前组件库所依赖的包(组件库可不提供)
perl 复制代码
{
  "name": "react-x-components",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@rollup/plugin-alias": "^5.1.0",
    "@rollup/plugin-commonjs": "^25.0.7",
    "@rollup/plugin-node-resolve": "^15.2.3",
    "@types/react": "^18.2.64",
    "postcss": "^8.4.35",
    "react": "^18.2.0",
    "rollup": "^4.12.1",
    "rollup-plugin-auto-add": "^0.0.6",
    "rollup-plugin-clear": "^2.0.7",
    "rollup-plugin-filesize": "^10.0.0",
    "rollup-plugin-multi-input": "^1.4.1",
    "rollup-plugin-peer-deps-external": "^2.2.4",
    "rollup-plugin-postcss": "^4.0.2",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-typescript2": "^0.36.0",
    "typescript": "^5.4.2"
  }
  "peerDependencies": { // ++++++
    "react": "^18.2.0", // ++++++
    "react-dom": "^18.2.0" // ++++++
  }
}
  1. 手写配置代码scripts/rollup.config.js
php 复制代码
const clear = require("rollup-plugin-clear");
const autoAdd = require("rollup-plugin-auto-add").default;
const multiInput = require("rollup-plugin-multi-input").default;
const typescript = require("rollup-plugin-typescript2");
const path = require("path");
const peerDepExternal = require("rollup-plugin-peer-deps-external");
const resolve = require("@rollup/plugin-node-resolve");
const commonjs = require("@rollup/plugin-commonjs");
const alias = require("@rollup/plugin-alias");
const postcss = require("rollup-plugin-postcss");
const { terser } = require("rollup-plugin-terser");
const filesize = require("rollup-plugin-filesize");

const pkg = require("../package.json");
module.exports = [
	// 打包成 esm 的配置项
	{
		input: "src/**/*",
		output: [
			{
				dir: "esm",
				format: "esm",
				sourceMap: false,
			},
		],

		// 打包时排除 peerDenpendencies 里面的依赖
		enternal: Object.keys(pkg.peerDenpendencies || {}),

		plugins: [
			// 自动清除生成代码
			clear({ target: "esm" }),

			// 自动注入代码
			autoAdd({
				// 匹配这种 src/myComponent/index.tsx
				include: [/src/(((?!/).)+?)/index.tsx/gi],
			}),

			// 多入口
			multiInput(),

			// 解析 ts
			typescript({
				path: path.resolve(__dirname, "./tsconfig.esm.json"),
			}),
			peerDepExternal(),
			resolve(), // 处理 node_modules
			commonjs(), // 处理 commonjs
			filesize(), // 处理包体积
			postcss({
				minimize: true,
				sourceMap: true,
				extensions: [".less", ".css"],
				use: ["less"],
			}),
			// 文件声明
			alias({
				entries: {
					"@": path.resolve(__dirname, "../src"),
				},
			}),
		],
	},

	// 打包成 umd 的配置项
	{
		input: "src/index.tsx",
		output: [
			{
				dir: "dist",
				format: "umd",
				exports: "named",
				name: pkg.name,
				sourceMap: true,
			},
		],

		// 打包时排除 peerDenpendencies 里面的依赖
		enternal: Object.keys(pkg.peerDenpendencies || {}),

		plugins: [
			// 自动清除生成代码
			clear({ target: "dist" }),

			// 自动注入代码
			autoAdd({
				// 匹配这种 src/myComponent/index.tsx
				include: [/src/(((?!/).)+?)/index.tsx/gi],
			}),

			// 多入口
			multiInput(),

			// 解析 ts
			typescript({
				path: path.resolve(__dirname, "./tsconfig.dist.json"),
			}),
			peerDepExternal(),
			resolve(), // 处理 node_modules
			commonjs(), // 处理 commonjs
			filesize(), // 处理包体积
			postcss({
				minimize: true,
				sourceMap: true,
				extensions: [".less", ".css"],
				use: ["less"],
			}),
			// 文件声明
			alias({
				entries: {
					"@": path.resolve(__dirname, "../src"),
				},
			}),
		],
	},
];
  1. 改造package.json加上打包命令
javascript 复制代码
{
  ....

  scripts: {
    "test": "echo "Error: no test specified" && exit 1",
    "build": "rollup --config ./scripts/rollup.config.js" // ++++++
  }
}
  1. 新建index.tsx
javascript 复制代码
touch src/index.tsx

// 写如下代码:
import Button from "./button";
import Card from "./card";

export { Button, Card };
  1. 运行pnpm build,进行打包

二、改造一些,给内部项目使用

  1. 改造package.json,看变更
  1. 内部项目安装它,进入apps/react-master内,运行安装命令
sql 复制代码
pnpm add @hzq/react-x-components
  1. 在项目中引入并使用
javascript 复制代码
import { Button } from "@hzq/react-x-components";

// ....

<Button>提问111</Button>

效果如下:

三、组件使用流程讲解

若需要实时使用组件,则可以如下处理:

1、改造组件项目的package.json,然后运行pnpm dev这样就是边开发边打包,能达到"实时"

javascript 复制代码
{
  .......

  "main": "src/index.tsx", // ++++++
  
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "build": "rollup --config ./scripts/rollup.config.js",
    "dev": "rollup --config ./scripts/rollup.config.js -w" // ++++++
  },

  ......
}

2、改造组件项目的package.json,这样直接使用组件源码,也能达到"实时"

json 复制代码
{
  .......

  "main": "src/index.tsx", // ++++++

  .......
}

5、插件封装

微内核架构

抽象不依赖实现

提供一个内核(core/engine),内核本身具有很强的扩展性,但内核不会因为有了一个扩展,就去修改内核自身

外部可通过插件(plugin)的形式往内核注入,然后内核去驱动插件的执行(plugins.run())

前端界常见的有:Webpack、Babel 等

核心伪代码:

typescript 复制代码
const events = {};

const typeEnum = ["create", "mount", "distory"];

class Core {
	context = {};

	defaultOpts = {
		beforeCreate() {
			console.log("beforeCreate");
		},
		created() {
			console.log("created");
		},

		beforeMount() {
			console.log("beforeMount");
		},
		mounted() {
			console.log("mounted");
		},

		beforeDistory() {
			console.log("beforeDistory");
		},
		distoryed() {
			console.log("distoryed");
		},
	};

	constructor(opts) {
		this.opts = { ...this.defaultOpts, ...opts };
	}

	addPlugin({ type, run }) {
		events[type] = events[type] || [];
		events[type].push(run);
	}

	pluginsRun(type) {
		events[type].forEach((fn) => fn(this.context));
	}

	start() {
		this.opts.beforeCreate(); // 模拟生命周期
		this.pluginsRun("create");
		this.opts.created(); // 模拟生命周期

		this.opts.beforeMount(); // 模拟生命周期
		this.pluginsRun("mount");
		this.opts.mounted(); // 模拟生命周期
	}

	end() {
		this.opts.beforeDistory(); // 模拟生命周期
		this.pluginsRun("distory");
		this.opts.distoryd(); // 模拟生命周期
	}
}

export default Core;

// 用户使用
const core = new Core({
	beforeCreate() {
		console.log("[ this is my beforeCreate] >");
	},
	mounted() {
		console.log("[ this is my mounted] >");
	},
	// ......
});

core.addPlugin({
	type: "create",
	run(context) {
		console.log("[ create run 1 ] >");
		context.xxxx = "xxxx";
	},
});

core.addPlugin({
	type: "create",
	run(context) {
		console.log("[ create run 2 ] >");
		console.log("[ create run 2 context ] >", context);
		context.yyyy = "yyyy";
	},
});

core.addPlugin({
	type: "mount",
	run(context) {
		console.log("[ mount context ] >", context);
	},
});

core.start();

Webpack:zipPlugin 插件封装(apps/react-master 下)

功能描述:将打包的东西压缩成一个包

  1. 安装前置依赖
csharp 复制代码
pnpm add jszip webpack-sources -D
  1. 新建对应文件
bash 复制代码
touch zipPlugin.js
  1. 编写代码
javascript 复制代码
const JSzip = require("jszip"); // 引入jszip

// RawSource 是其中一种 "源码"("sources") 类型,
const { RawSource } = require("webpack-sources");

// 自定义插件 官方文档:https://webpack.docschina.org/contribute/writing-a-plugin/#creating-a-plugin
class ZipPlugin {
	static defaultOptions = {
		outputFile: "dist.zip",
	};

	constructor(options) {
		this.options = { ...ZipPlugin.defaultOptions, ...options };
	}

	// 在插件函数的 prototype 上定义一个 `apply` 方法,以 compiler 为参数。
	apply(compiler) {
		const pluginName = ZipPlugin.name;

		compiler.hooks.emit.tapAsync(pluginName, (compilation, callback) => {
			const zip = new JSzip();

			// 遍历所有资源
			Object.keys(compilation.assets).forEach((filename) => {
				const source = compilation.assets[filename].source();

				zip.file(filename, source); // 添加文件到 zip
			});

			// generateAsync:生成 zip 文件
			zip.generateAsync({ type: "nodebuffer" }).then((content) => {
				// 向 compilation 添加新的资源,这样 webpack 就会自动生成并输出到 outputFile 目录
				compilation.emitAsset(this.options.outputFile, new RawSource(content));
				callback(); // 告诉 webpack 插件已经完成
			});
		});
	}
}

module.exports = { ZipPlugin };
  1. 更改react-master/scripts/webpack.prod.js(看变更)
  1. 运行pnpm build,生成的dist里面就会有个dist.zip,解压后就是整个dist

Babel:consolePlugin 插件封装(apps/react-master 下)

功能描述:在调试模式下,将 console.log() 丰富,支持打印具体位置:行数、列数

  1. 安装前置依赖
sql 复制代码
pnpm add @babel/generator -D 
  1. 新建对应文件
bash 复制代码
touch consolePlugin.js
  1. 编写代码
javascript 复制代码
const generator = require("@babel/generator").default;

// Babel 自定义插件官方文档:https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md#toc-writing-your-first-babel-plugin

function consolePlugin({ types }) {
	return {
		visitor: {
			CallExpression(path) {
				const name = generator(path.node.callee).code;

				if (["console.log", "console.info", "console.error"].includes(name)) {
					const { line, column } = path.node.loc.start;
					path.node.arguments.unshift(
						types.stringLiteral(`fliepath: ${line}:${column}`),
					);
				}
			},
		},
	};
}

module.exports = consolePlugin;
  1. 使用插件
json 复制代码
{
	"presets": [
		"@babel/preset-react", // 解析 react
		"@babel/preset-typescript" // 解析 typescript
	],
	"plugins": ["./consolePlugin.js"] // ++++++
}
  1. 重新启动项目pnpm start,写一个console.log(xx)并打印出来,可以看有行数、列数了

Postcss 插件:themePlugin(apps/react-master 下)

主要功能:实现网站主题色切换

基本功能

先展示基于tailwindcss实现的最朴实的颜色切换

  1. 更改tailwind.config.js
css 复制代码
/** @type {import('tailwindcss').Config} */
module.exports = {
	content: ["./src/**/*.{tsx,ts,jsx,js}"],
	theme: {
		extend: {},
		colors: { // +++ 下面的为新增的 +++
			// 将默认的颜色改为变量
			white: "var(--color-white)",
			black: "var(--color-black)",
			gray: {
				50: "var(--color-gray-50)",
				100: "var(--color-gray-100)",
				200: "var(--color-gray-200)",
				300: "var(--color-gray-300)",
				400: "var(--color-gray-400)",
				500: "var(--color-gray-500)",
				600: "var(--color-gray-600)",
				700: "var(--color-gray-700)",
				800: "var(--color-gray-800)",
				900: "var(--color-gray-900)",
				950: "var(--color-gray-950)",
			},
		},
	},
	plugins: [],
};
  1. 更改index.less
css 复制代码
// 全局的东西

// tailwind 配置
@tailwind base;
@tailwind components;
@tailwind utilities;

// 明亮模式
html {
	--color-white: #fff;
	--color-black: #000;
	--color-gray-50: #f9fafb;
	--color-gray-100: #f3f4f6;
	--color-gray-200: #e5e7eb;
	--color-gray-300: #d1d5db;
	--color-gray-400: #9ca3af;
	--color-gray-500: #6b7280;
	--color-gray-600: #4b5563;
	--color-gray-700: #374151;
	--color-gray-800: #1f2937;
	--color-gray-900: #111827;
	--color-gray-950: #030712;
}

// 深色模式
html[data-theme="dark"] {
	--color-white: #000;
	--color-black: #fff;
	--color-gray-950: #f9fafb;
	--color-gray-900: #f3f4f6;
	--color-gray-800: #e5e7eb;
	--color-gray-700: #d1d5db;
	--color-gray-600: #9ca3af;
	--color-gray-500: #6b7280;
	--color-gray-400: #4b5563;
	--color-gray-300: #374151;
	--color-gray-200: #1f2937;
	--color-gray-100: #111827;
	--color-gray-50: #030712;
}
  1. 更改react-master/src/components/navigation/index.tsx,增加一个主题切换操作,看 mr
  2. 切换之前
  1. 切换之后

基于插件

基本功能完成后,会发现我们的主题变量需要手动去维护,当主题多了就麻烦了,现在就可以写插件来处理

色卡:主题需要设计时提供一系列对应的颜色值,明亮、暗黑、xx 一套

在前端可以这样去维护:

  1. 新建色卡文件,就是之前index.less里面写 css 代码,改成 js 代码而已
css 复制代码
touch themeGroup.js

// 写如下代码
const themeGroup = {
	light: {
		"--color-white": "#fff",
		"--color-black": "#000",
		"--color-gray-50": "#f9fafb",
		"--color-gray-100": "#f3f4f6",
		"--color-gray-200": "#e5e7eb",
		"--color-gray-300": "#d1d5db",
		"--color-gray-400": "#9ca3af",
		"--color-gray-500": "#6b7280",
		"--color-gray-600": "#4b5563",
		"--color-gray-700": "#374151",
		"--color-gray-800": "#1f2937",
		"--color-gray-900": "#111827",
		"--color-gray-950": "#030712",
	},
	dark: {
		"--color-white": "#000",
		"--color-black": "#fff",
		"--color-gray-950": "#f9fafb",
		"--color-gray-900": "#f3f4f6",
		"--color-gray-800": "#e5e7eb",
		"--color-gray-700": "#d1d5db",
		"--color-gray-600": "#9ca3af",
		"--color-gray-500": "#6b7280",
		"--color-gray-400": "#4b5563",
		"--color-gray-300": "#374151",
		"--color-gray-200": "#1f2937",
		"--color-gray-100": "#111827",
		"--color-gray-50": "#030712",
	},
	green: {
		"--color-white": "#14532d",
		"--color-black": "#f0fdf4",
		"--color-gray-50": "#f0fdf4",
		"--color-gray-100": "#dcfce7",
		"--color-gray-200": "#bbf7d0",
		"--color-gray-300": "#86efac",
		"--color-gray-400": "#4ade80",
		"--color-gray-500": "#22c55e",
		"--color-gray-600": "#16a34a",
		"--color-gray-700": "#15803d",
		"--color-gray-800": "#166534",
		"--color-gray-900": "#14532d",
		"--color-gray-950": "#052e16",
	},
};

module.exports = { themeGroup, defaultTheme: "green" };
  1. 删除index.less里面的颜色配置
  2. 更改tailwind.config.js,将var改为hzqTheme,最后实现效果是将color:hzqTheme(--color-white)通过插件变为color:#fff,这样就完成了插件的功能
css 复制代码
/** @type {import('tailwindcss').Config} */
module.exports = {
	content: ["./src/**/*.{tsx,ts,jsx,js}"],
	theme: {
		extend: {},
		colors: {
			// 将默认的颜色改为变量
			white: "hzqTheme(--color-white)",
			black: "hzqTheme(--color-black)",
			gray: {
				50: "hzqTheme(--color-gray-50)",
				100: "hzqTheme(--color-gray-100)",
				200: "hzqTheme(--color-gray-200)",
				300: "hzqTheme(--color-gray-300)",
				400: "hzqTheme(--color-gray-400)",
				500: "hzqTheme(--color-gray-500)",
				600: "hzqTheme(--color-gray-600)",
				700: "hzqTheme(--color-gray-700)",
				800: "hzqTheme(--color-gray-800)",
				900: "hzqTheme(--color-gray-900)",
				950: "hzqTheme(--color-gray-950)",
			},
		},
	},
	plugins: [],
};
  1. 安装前置依赖:用于修改 css 代码的库
kotlin 复制代码
pnpm add postcss-nested@^6.0.1 postcss-nesting@^10.2.0
  1. 新建插件文件themePlugin.js
javascript 复制代码
touch themePlugin.js

// 写入代码:
// eslint-disable-next-line @typescript-eslint/no-var-requires
const postcss = require("postcss");

module.exports = postcss.plugin("postcss-theme", (options) => {
	const defalutOpts = {
		functionName: "hzqTheme",
		themeGroup: {},
		defaultTheme: "light",
		themeSelector: 'html[data-theme="$_$"]',
		nestingPlugin: null,
	};

	// 合并参数
	options = Object.assign({}, defalutOpts, options);

	const getColorByThemeGroup = (color, theme) => {
		return options.themeGroup[theme][color];
	};

	// 正则:获取 hzqTheme(--color-white) 括号中的值:--color-white
	const regColorValue = new RegExp(
		`\b${options.functionName}\(([^)]+)\)`,
		"g",
	);

	// 插件的入口函数
	return (style, result) => {
		const hasPlugin = (name) =>
			name.replace(/^postcss-/, "") === options.nestingPlugin ||
			result.processor.plugins.some((p) => p.postcssPlugin === name);

		// 获取 css 属性值,替换掉 hzqTheme(--color-gray-200)
		const getColorValue = (value, theme) => {
			return value.replace(regColorValue, (match, color) => {
				// match: hzqTheme(--color-gray-200)
				// color: --color-gray-200
				return getColorByThemeGroup(color, theme);
			});
		};

		style.walkDecls((decl) => {
			// decl 是每个 css 属性的对象,height: 10px 的 css ast { prop: "height", value: "10px" }

			// 每个 css 属性的具体值:height: 10px;
			const value = decl.value;

			if (!value || !regColorValue.test(value)) {
				// 如果没有匹配到,直接返回
				return;
			}

			// 说明有匹配到值

			try {
				let defaultTheme;

				Object.keys(options.themeGroup).forEach((key) => {
					// 处理各种模式
					const themeColor = getColorValue(value, key);
					const themeSelector = options.themeSelector.replace("$_$", key);
					let themeRule;
					// 使用 nest 插件,生成 dark 的规则:html[data-theme="dark"] {...}
					if (hasPlugin("postcss-nesting")) {
						themeRule = postcss.atRule({
							name: "nest",
							params: `${themeSelector} &`,
						});
					} else if (hasPlugin("postcss-nested")) {
						themeRule = postcss.rule({
							params: `${themeSelector} &`,
						});
					} else {
						throw new Error("请安装 postcss-nesting 或者 postcss-nested 插件");
					}
					const themeDecl = decl.clone({ value: themeColor });

					if (themeRule) {
						themeRule.append(themeDecl);
						decl.after(themeRule);
					}

					if (key === options.defaultTheme) {
						defaultTheme = themeDecl;
					}
				});

				// 处理为默认模式
				if (defaultTheme) decl.replaceWith(defaultTheme);
			} catch (error) {
				decl.warn(result, error);
			}
		});
	};
});
  1. 更改.postcssrc.js文件,加入对应插件
javascript 复制代码
const { themeGroup, defaultTheme } = require("./themeGroup"); // ++++++
module.exports = {
	plugins: [
		"autoprefixer", // 自动添加浏览器前缀
		"tailwindcss",
		"postcss-nested", // ++++++
		"postcss-nesting", // ++++++
		require("./themePlugin")({ themeGroup, defaultTheme }), // ++++++
	],
};
  1. 重启项目,默认为绿色,切换后为黑色,再切换为白色

打包之后,本地启动index.html也是一样的效果,这样就完成了主题切换的插件,本质是帮我们注入所有的主题 css 代码

相关推荐
编程零零七2 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
(⊙o⊙)~哦4 小时前
JavaScript substring() 方法
前端
无心使然云中漫步5 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者5 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_5 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋6 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120536 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢6 小时前
【Vue】VueRouter路由
前端·javascript·vue.js
随笔写8 小时前
vue使用关于speak-tss插件的详细介绍
前端·javascript·vue.js
史努比.8 小时前
redis群集三种模式:主从复制、哨兵、集群
前端·bootstrap·html