这13个前端库,帮我在工作中赢得了不少摸鱼时间

前言

平时开发的过程中,常常会使用到一些第三方库来提高开发效率,我总结了自己工作这么久以来经常用到的 13 个库,希望对大家有帮助~

antd

image.png

全称应该是Ant Design,这是一个 React 的组件库,旨在提供一套常用的组件库解决方案,用 React 的同学可能对他不会陌生,就像用 Vue 的同学不会对 Element UI 陌生一样。

虽然现在也出现了更多新颖的组件库,但是无论是在公司还是自己做一些东西,只要是中后台的产品,我们都会使用它。

image.png

它提供了 75 个组件,包括常用的按钮、下拉、输入框、菜单、分页、时间选择器、级联选择器、对话框、进度条等等,常用的不常用的组件基本都有,我觉得基本上能涵盖 90% 的常规开发场景。

支持国际化以及自定义主题色,可以让我们轻松拓展。

image.png

除了拥有不俗的样式以及交互效果之外,还向外输出了一套设计理念,包括 Icon 、颜色、字体大小、阴影等,也是我们值得借鉴和学习的地方。

axios

image.png

这可以说是我每天都用到的库,相信各位同学对它也不会陌生。这是一个基于 Promisehttp 请求库,也就是我们常说的调接口,就是用它来调用。

它支持浏览器端和 node 端,除此之外,它支持我们自定义请求和响应的拦截器,这可以让我们在发送请求前或收到响应后执行额外的操作。比如,可以在请求发送前添加认证信息,或在收到响应后处理数据。

它提供了取消请求的功能,我们可以在请求发送后取消请求,避免不必要的网络请求或资源浪费。

以下是我自己常用的一种封装 axios 的手法:

let _BASE_URL = "/api";
import { message } from "antd";
import _axios from "axios";

const axiosInstance = _axios.create({
  withCredentials: true, // 是否允许带cookie这些
});
axiosInstance.interceptors.request.use((request) => {
  request.headers['Authorization'] = getAuthorization();
  return request;
});
axiosInstance.interceptors.response.use(
  (response: any) => {
    const data = response.data;
    if (data.code === 401) {
      const loginUrl = `${location.protocol}//${
        location.host
      }/login?redirect_url=${encodeURIComponent(location.href)}`;
      location.replace(loginUrl);
    } else if (data.code !== 200) {
      message.error(data.msg);
      return Promise.reject(data.msg);
    }
    return response.data;
  },
  (error) => {
    // 超出 2xx 范围的状态码都会触发该函数。对响应错误时调用。
    console.error("请求错误: ", error);
    message.error(error?.message);
    return Promise.reject(error);
  }
);

export const BASE_URL = _BASE_URL;
export const axios = axiosInstance;

封装一个 axiosInstance ,然后再新建一个请求模块文件,比如说我们对用户相关的接口都可以放在 user 模块中:

import { BASE_URL, axios } from ".";
export const register = (params: {
  account: string;
  password: string;
  code: string;
}) => {
  return axios.post(`${BASE_URL}/user/register/${params.code}`, {
    account: params.account,
    password: params.password,
  });
};

export const login = (params: { account: string; password: string }) => {
  return axios.post(`${BASE_URL}/user/login`, params);
};

然后再在代码里面如下使用 user 模块中的接口:

  try {
    setLoading(true);
    await login({ account: fields.account, password: fields.password });
    message.success("登录成功");
  } finally {
    setLoading(false);
  }

dayjs

image.png

dayjs是一个十分强大的日期处理库,它只有 2KB ,十分轻巧。API设计支持链式调用,非常灵活,而且兼容所有的浏览器。

以下是 dayjs 中使用的时候常见的 10 个例子,仅供参考

import dayjs from "dayjs";

// 1. 获取当前日期
const currentDate = dayjs();
console.log(currentDate.format('YYYY-MM-DD')); // 输出当前日期,如:2024-04-30

// 2. 获取当前时间
const currentTime = dayjs();
console.log(currentTime.format('HH:mm:ss')); // 输出当前时间,如:14:30:00

// 3. 获取指定日期
const specifiedDate = dayjs('2023-01-15');
console.log(specifiedDate.format('YYYY-MM-DD')); // 输出指定日期,如:2023-01-15

// 4. 格式化日期
const formattedDate = dayjs('2024-04-30');
console.log(formattedDate.format('dddd, MMMM D, YYYY')); // 输出格式化后的日期,如:Saturday, April 30, 2024

// 5. 添加/减去时间
const addedTime = dayjs().add(7, 'days');
console.log(addedTime.format('YYYY-MM-DD')); // 输出添加 7 天后的日期,如:2024-05-07

// 6. 计算两个日期之间的差值
const date1 = dayjs('2024-01-01');
const date2 = dayjs('2024-02-01');
const diffInDays = date2.diff(date1, 'days');
console.log(diffInDays); // 输出两个日期之间的天数差,如:31

// 7. 检查日期是否在某个范围内
const targetDate = dayjs('2024-04-30');
const startDate = dayjs('2024-04-01');
const endDate = dayjs('2024-05-01');
const isWithinRange = targetDate.isBetween(startDate, endDate);
console.log(isWithinRange); // 输出 true,因为目标日期在范围内

// 8. 获取一周中的第几天
const dayOfWeek = dayjs().day();
console.log(dayOfWeek); // 输出今天是一周中的第几天,0 表示星期日,1 表示星期一,依此类推

// 9. 获取月份的天数
const daysInMonth = dayjs('2024-02-01').daysInMonth();
console.log(daysInMonth); // 输出该月份的天数,如:29

// 10. 获取两个日期之间的所有日期
const startDate = dayjs('2024-04-01');
const endDate = dayjs('2024-04-05');
const allDates = [];
let currentDate = startDate;
while (currentDate.isBefore(endDate) || currentDate.isSame(endDate, 'day')) {
    allDates.push(currentDate.format('YYYY-MM-DD'));
    currentDate = currentDate.add(1, 'day');
}
console.log(allDates); // 输出包含所有日期的数组,如:['2024-04-01', '2024-04-02', '2024-04-03', '2024-04-04', '2024-04-05']

lodash

lodash 是一个 js 工具库,提供了许多工具函数,用于简化常见的编程需求。

以下是我对它特点的一些理解:

  1. 集合处理:提供了许多用于处理数组和对象的方法,如排序、过滤、查找、分组等,使得对集合进行操作变得非常方便。
  2. 函数工具:包含了许多函数工具,如节流、防抖、柯里化等。
  3. 类型检查:提供了一系列用于类型检查的函数,如 isStringisArrayisObject 等。
  4. 深拷贝: 提供了强大的深拷贝函数,兼容了函数、日期、正则对象等。
  5. 字符串处理:提供了许多用于字符串处理的方法,如截取、拼接、格式化等。
  6. 数学运算:提供了一些常见的数学运算函数,如求和、求平均数、最大值、最小值等。

下面举几个我常用的lodash函数:

深拷贝

import { cloneDeep } from "lodash"

const originalObject = { name: '名字', age: 30, hobbies: ['唱', '跳'] };
const clonedObject = cloneDeep(originalObject);
console.log(clonedObject); // 输出一个深拷贝后的对象

数组去重

import { uniq } from "lodash"

const numbers = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4];
const uniqueNumbers = uniq(numbers);
console.log(uniqueNumbers); // 输出 [1, 2, 3, 4]

函数防抖

import { debounce } from "lodash"

const debouncedFunction = debounce(() => {
    console.log('防抖函数');
}, 1000);

debouncedFunction();

对象深度比较

import { isEqual } from "lodash"

const object1 = { a: { b: 2 } };
const object2 = { a: { b: 2 } };
const res = isEqual(object1, object2);
console.log(res); // 输出 true,因为两个对象的内容相同

xss

xss是一个用于处理 HTML 和防止 XSS 攻击的库,一般提交一些富文本内容的时候我们都会用它过滤一下。

它通过过滤和转义,可以去除输入的 HTML 字符串中的不安全的标签;同时支持白名单配置,用于指定允许出现的标签和属性;而且它是一个轻量级的库,体积十分小巧,引入也不会对项目造成什么影响;并且它在浏览器端跟 Node 端都能使用。

下面是一些使用的例子:

import xss from "xss";
const userInput = '<script>alert("恶意代码");</script><p>这是用户输入的文本内容</p>'; 
const output = xss(userInput);
console.log(output); // &lt;script&gt;alert("恶意代码");&lt;/script&gt;<p>这是用户输入的文本内容</p>

再举一个白名单的使用例子,下面是没设置白名单的代码

import xss from "xss";

const userInput = '<script>alert("恶意代码");</script><p>这是用户输入的文本内容</p><a href="javascript:alert(\'XSS攻击\')">恶意链接</a>';

const output = xss(userInput);

console.log(output); // &lt;script&gt;alert("恶意代码");&lt;/script&gt;<p>这是用户输入的文本内容</p><a href>恶意链接</a>

这里是设置了白名单的代码:

import xss from "xss";

const userInput = '<script>alert("恶意代码");</script><p>这是用户输入的文本内容</p><a href="http://example.com">正常链接</a><a href="javascript:alert(\'XSS攻击\')">恶意链接</a>';
const options = {
  whiteList: {
    a: ['href']
  },
  onTagAttr: (tag, name, value, isWhiteAttr) => {
    if (tag === 'a' && name === 'href' && !/^https?:\/\//.test(value)) {
      return `${name}="#"`;
    }
  }
};

const output = xss(userInput, options);
console.log(output); //&lt;script&gt;alert("恶意代码");&lt;/script&gt;&lt;p&gt;这是用户输入的文本内容&lt;/p&gt;<a href="http://example.com">正常链接</a><a href="#">恶意链接</a>

更详尽的用法请参考他的文档

classnames

classnames是用来处理 css 类名组合的一个库,提供了一种方便的方式来动态添加或者移除类名,实现样式的灵活控制。

比如说我们的一个按钮,它有按下跟 hover 两种状态,对应不同的样式。对于没有用 classnames 来说,可能会这么写

let btnClass = 'btn';
if (isPressed) btnClass += ' btn-pressed';
else if (isHovered) btnClass += ' btn-over';


return (
    <button className={btnClass}>
        按钮
    </button>
);

这种写法都算好的,更常见的是在 className 中用三元表达式+模版字符串来拼接,更是惨不忍睹。用上 classnames 之后,常常可以这么写:

const btnClass = classnames({
    btn: true,
    'btn-pressed': isPressed,
    'btn-over': !isPressed && isHovered,
});
return (
    <button className={btnClass}>
        按钮
    </button>
);

copy-text-to-clipboard

copy-text-to-clipboard是一个轻量级的复制文本到剪贴板的库,仅仅只有 0.2KB ,提供了简单的 API ,非常易用,每周也是有着 37W+ 的下载量。

它兼容了不同的浏览器,没有任何外部依赖,在一些浏览器中,复制文本到剪贴板需要用户授权,该库提供了相应的错误处理机制。

import copy from 'copy-text-to-clipboard';

button.addEventListener('click', () => {
	copy('复制一些东西');
});

uuid

uuid 用于生成符合 RFC 4122 标准的 UUID(通用唯一标识符)UUID 是一种全局唯一的标识符,在分布式系统中的是唯一的。

uuid 库支持多种 UUID 版本,包括 版本 1版本 3版本 4版本 5 ,每种版本都有不同的生成算法和格式。它提供了简洁的 API ,使得生成 UUID 变得非常简单和方便。

同时,它在生成 UUID 时具有很高的性能,可以快速生成大量的 UUID

它也是支持多端的,包括浏览器端跟 Node.js ,不仅如此,它还支持多种 UUID 的表示格式,包括标准的 UUID 字符串、二进制格式、数组格式等。

import { v4 as uuidv4 } from 'uuid';
uuidv4(); //  d702159f-384c-4d09-bf16-89f42e27ca30

Quill

image.png

我相信做过中后台产品的同学一定会遇到的一个场景,就是有一个编辑框需要用到富文本编辑框,但是呢,他又不需要跟那些在线文档功能那么齐全强大,这个时候我们一般就是去找一些开源的富文本组件来解决。

这个时候我一般都会选择quill,它提供了丰富的编辑功能,包括文本样式(粗体、斜体、下划线等)、列表、链接、图像插入、表格、代码块等。

而且提供了丰富的事件和钩子,我们可以监听编辑器的各种操作事件,实现自定义的业务逻辑和交互效果。

以及它支持插件和扩展,可以通过第三方插件或自定义扩展来增强编辑器的功能和特性,满足更复杂的编辑需求

使用起来也很简单,如果你仅仅是需要一个能用的富文本框,并不需要定制什么东西,那下面的示例就已经满足

image.png

crypto-js

crypto-js提供了多种加密算法和常用的加密功能。包括对称加密算法(如 AESDES )、哈希算法(如 MD5SHA-1SHA-256 )等。

除了提供多种加密算法之外,它的 API 设计也十分简单易用,以及对于各个平台的兼容性也十分好。

它的性能也很好,在加解密的过程中,可以快速处理大量的数据,不会对性能造成明显影响。

举一些例子,比如常用的MD5哈希算法:

import CryptoJS from "crypto-js";
const data = 'Hello, world!';
const md5Hash = CryptoJS.MD5(data);
console.log(md5Hash.toString());

对称加密:

import CryptoJS from "crypto-js";

const key = CryptoJS.enc.Utf8.parse("1234567890123456");
const iv = CryptoJS.enc.Utf8.parse("1234567890123456");

const plaintext = "Hello, world!";

const ciphertext = CryptoJS.AES.encrypt(plaintext, key, {
  iv: iv,
  mode: CryptoJS.mode.CBC,
  padding: CryptoJS.pad.Pkcs7,
});

console.log("加密后的数据:", ciphertext.toString()); //yWUReRP6G/jD2zWIXJMtTw==

const decrypted = CryptoJS.AES.decrypt(ciphertext, key, {
  iv: iv,
  mode: CryptoJS.mode.CBC,
  padding: CryptoJS.pad.Pkcs7,
});

console.log("解密后的数据:", decrypted.toString(CryptoJS.enc.Utf8)); //Hello, world!

viewerjs

图片预览需求想必很多同学都遇到过,这里介绍一个功能强大的图片预览库——viewerjs。

该库支持缩放、拖动、旋转等交互功能,可以通过鼠标或触摸屏进行操作,自由调整图片的大小和位置;而且支持缩略图导航功能,可以通过缩略图预览图片,快速导航到指定的图片。

它还提供了丰富的定制选项,可以根据需要自定义显示效果、交互行为和样式样式,满足不同项目的需求。

image.png

image.png

localforage

localstorage 相信大家都知道,但是它只有 5M 的存储空间,如果想在浏览器本地存储更多的东西,那就应该使用 indexDB 或者 WebSQL

localforage就是一个封装浏览器存储引擎的库,它的设计目的就是为了让开发者能够以简易的API来使用浏览器的本地存储来存储数据。

它可以自动地选择合适的存储引擎( IndexedDBWebSQLlocalStorage )来进行数据存储。

它支持自动数据类型转换,可以将对象、数组等复杂数据结构转换成适合存储的格式,并在取出数据时自动转换回原始格式。

它支持异步操作,所有的存储操作都是非阻塞的,可以避免因为数据存取操作而阻塞 UI 线程,支持回调函数的方式和 Promise 的方式来处理异步。

读操作:

try {
    const value = await localforage.getItem('key');
    console.log(value);
} catch (err) {
    console.log(err);
}

写操作:

try {
    const value = await localforage.setItem('key', [1, 2, 3]);
} catch (err) {
    console.log(err);
}

vconsole

我们平时在开发的时候一般都是 F12 打开开发者工具来调试,但是当我们的 Web 项目跑在真机移动端中,是没有这个开发者工具的。那应该怎么调试呢?

这个时候就可以用到vconsole这个库,它可以在手机浏览器中实时查看日志、错误信息、网络请求等调试信息,帮助我们快速定位和解决问题。

image.png

在引入的时候,我有一个小技巧可以分享,最好是使用 cdn的 方式引入,这样你就很容易根据一些特征去做动态导入,这种包没必要打包进主包里面。

比如说:

  const loadVConsole = () => {
    const url = "https://unpkg.com/vconsole@latest/dist/vconsole.min.js";
    if (!location.href.includes("debug")) {
      return;
    }
    const script = document.createElement("script");
    script.src = url;
    script.onload = () => {
      new window.VConsole();
    };
    document.body.appendChild(script);
  };

当然,这个 cdn 包的资源最好你自己维护一份。

最后

以上就是我开发工作过程中经常会用到的一些库,你平时会用到那些库呢?评论区一起交流一下吧!

如果你觉得有意思的话,点点关注点点赞吧~

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

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

相关文章

Android Studio 中gradle的bin和all区别

1.在android studio中设置安装gradle时&#xff0c;真各种版本看到眼花缭乱&#xff0c;还有疑惑gradle-*.*-all.zip与gradle-*.*-bin.zip的区别是什么。下面解压如下: bin&#xff1a; all: 其实&#xff0c;用bin就可以了&#xff0c;all文件就是多了docs(文档)和src(源码)两…

本周日晚8点预约宣讲会 | 深入了解项目,开启你的开源之旅!

引言 社区的亲爱的同学们&#xff01;为了帮助大家在这个夏天更好的参加“开源之夏”的活动&#xff0c;我们联合2位资深开源项目导师&#xff0c;给大家策划了这次“开源之夏”宣讲会。 这不仅是一个了解如何参与开源项目的机会&#xff0c;更是一个直接与项目导师面对面交流…

华火硬核专利库丨登创新科技之巅,探创新未至之境

十年的艰苦卓越&#xff0c;“灶”就了华火科技之巅&#xff1b;电生明火的应用&#xff0c;不仅是一次颠覆性的创新&#xff0c;更是对未来厨房的无尽遐想与探索。在当今日新月异的科技时代&#xff0c;创新已成为推动社会进步的重要动力。 华火烹饪科技&#xff0c;以其深厚的…

Unity 直线间隔放置物体

直线间隔放置物体 0. 新建一个空物体&#xff0c;挂上脚本ZYF_QuickPlaceObj 设置 间隔距离 和 预制体在Scene中拖动即可按间隔距离实例化物体物体的朝向始终朝向统一方向&#xff0c;并且可以在Scene中拖拽更改 传送门

Object类——toString方法和equals方法

前言&#xff1a; 在java中&#xff0c;所有类都是有继承关系存在的&#xff0c;都默认继承Object类。当一个类继承了其他父类&#xff0c;它并不会直接继承Object类&#xff0c;但是它的父类若是没有其他继承关系也会默认继承Object类&#xff0c;子类也可以继续调用Object类…

深度学习——图像分类(CNN)—测试模型

测试模型 1.导入必要的库2.加载测试数据集3.假设CSV文件中的图像文件名是完整的路径4.随机选择一张图片进行展示5.加载图像6.使用模型进行预测7.设置模型的预测结果8.计算准确率9.指定test文件夹路径10.读取名为image_path的图片11.加载图像12.检查图像是否为空 训练的模型是上…

Easy IP + DNAT(服务器NAT转换)

第一章 Easy IP 1.1 一般家庭和企业使用的地址转换方式 直接使用出接口的地址做转换Easy IP适用于小规模居于网中的主机访问Internet的场景如&#xff1a;家庭、小型网吧、小型办公室中&#xff0c;这些地方内部主机不多&#xff0c;出接口可以通过拨号方式获取一个临时公网I…

做抖音小店不懂这四个“重点”!那就别怪你的店铺,做不长久!

我相信大家做抖音小店&#xff0c;都去抖音刷过知识点&#xff0c;也去浏览器学习过技巧 但在这里&#xff0c;我给大家泼盆冷水 方法再多&#xff01;这四点不搞明白&#xff0c;那你的店铺出几天单&#xff0c;也就再也做不起来了 哪四点&#xff1f;请认真的看下去&#…

面试官:讲讲为什么SpringBoot的 jar 可以直接运行?

Spring Boot 是一个用于简化 Spring 应用程序开发的框架&#xff0c;它通过约定优于配置和大量的自动化配置&#xff0c;使得开发者可以更轻松地创建和部署 Spring 应用程序。一个特别引人注目的特性是 Spring Boot 应用可以打包成一个可执行的 JAR 文件&#xff0c;并且可以直…

新计划,不断变更!做自己,接受不美好!猪肝移植——早读(逆天打工人爬取热门微信文章解读)

时间不等人 引言Python 代码第一篇 做自己&#xff0c;没有很好也没关系第二篇结尾 引言 新计划&#xff1a; 早上一次性发几个视频不现实 所以更改一下 待后面有比较稳定的框架再优化 每天早上更新 早到8点 晚到10点 你刚刚好上班或者上课 然后偷瞄的看两眼 学习一下 补充知…

吴恩达2022机器学习专项课程C2W2:2.22 多类 softmax softmax与神经网络 softmax的代码改良 多标签分类

目录 多分类问题1.什么是多分类问题2.多分类问题案例3.二分类与多分类的区别 Softmax1. 什么是Softmax2.逻辑回归预测的计算过程3. Softmax预测的计算过程4.Softmax 回归与逻辑回归的关系5. Softmax的损失函数 softmax与神经网络1.设置Softmax层2.Softmax层的计算3.softmax激活…

Yolov5——训练目标检测模型详解(含完整源码)

项目的克隆 打开yolov5官网&#xff08;GitHub - ultralytics/yolov5 at v5.0&#xff09;&#xff0c;下载yolov5的项目&#xff1a; 环境的安装&#xff08;免额外安装CUDA和cudnn&#xff09; 打开anaconda的终端&#xff0c;创建新的名为yolov5的环境&#xff08;python选…

源码编译安装LAMP(安装apeche mysql php 论坛 网站 巨详细版)

目录 一.LAMP架构相关概述 1.各组件作用 Linux&#xff08;平台&#xff09; Apache&#xff08;前台&#xff09; MySQL&#xff08;后台&#xff09; PHP/Perl/Python&#xff08;中间连接&#xff09; 总结 二.编译安装Apache httpd服务 1.关闭防火墙&#xff0c;将…

DMPO -- Stressmarq

货号&#xff1a;SIH-324 名称&#xff1a;DMPO 规格&#xff1a;25mg、125mg 产品描述&#xff1a; 自由基和其他高活性氧的形成与许多疾病状态的发病机制有关。识别这些物种的能力至关重要&#xff0c;而自旋诱捕已经完成了这一目标。DMPO&#xff08;5&#xff0c;5-二甲…

分析训练全球 2k+ 水文站数据,中科院团队发布 ED-DLSTM,实现无监测数据地区洪水预测

随着全球气候变化&#xff0c;洪水灾害正变得愈发频繁。联合国减少灾害风险办公室与比利时鲁汶大学灾害流行问题研究中心联合发布的报告指出&#xff1a;过去 20 年间&#xff0c;全球洪水灾害数量从 1,389 起上升到 3,254 起&#xff0c;增加了超两倍&#xff0c;占到灾害总数…

第一届 长城杯 总决赛wp

第一届 长城杯 总决赛 - Ahisec 第一阶段 Zip_guessinteger 第一层bkcrack攻击部分明文&#xff0c;注意偏移 ​ ┌──(root㉿Ten)-[~/tools/Misc/bkcrack] └─# ./bkcrack -C zip_guessinteger.zip -c breakthroughentry.txtflag.txt.zip -p 1.txt -o 30 bkcrack 1.6.1 …

一个给新手进阶的IAT加密壳

前言 这篇文章中介绍了IAT加壳与解壳的全过程&#xff0c;并用Ollydbg进行逆向分析&#xff0c;说明这个壳的鸡肋的之处&#xff0c;最后给出了核心源代码。 必备基础 必须很熟悉PE结构&#xff0c;特别是导入表的双桥结构。 IAT(Import Address Table)&#xff0c;导入地址…

Shiro+Jwt+Redis

如何整合ShiroJwtRedis&#xff0c;以及为什么要这么做 我个人认为 ①为什么用shiro&#xff1a;“ShiroJwtRedis”模式和“单纯的shiro”模式相比&#xff0c;主要用的是shiro里面的登录认证和权限控制功能 ②为什么用jwt&#xff1a;“ShiroJwt”模式和“ShiroCookie”模式相…

怎么搭建微信留言板功能

在信息爆炸的时代&#xff0c;微信已经成为了我们日常生活中不可或缺的一部分。它不仅仅是一个简单的聊天工具&#xff0c;更是一个充满无限可能的营销平台。今天&#xff0c;我要向大家介绍的是如何在你的微信平台上搭建一个独具特色的留言板功能&#xff0c;让用户能够自由发…