Hexo 神复现洛谷 Md 插件!
_ruyingsuixing_ · · 科技·工程
前言
:::info[Tip:]{open} 可以在我的博客观看以获得更好的阅读体验哦! :::
OIer 都知道,洛谷中有个 Markdown 插件,非常好用,效果如下:
:::info[这是一个提示] 我是提示内容。 :::
:::::warning[我是父容器]
我是一些文字。
::::success[我是子容器 1]{open} 我是内容 1。 ::::
我是一些文字。
::::success[我是子容器 2]{open} :::error[我是子容器 3]{open} 我是内容 2。 ::: ::::
我是一些文字。
:::::
:::epigraph[——otto] 大家好啊,我是说的道理。 :::
:::align{left} 居左内容。 :::
:::align{center} 居中内容。 :::
:::align{right} 居右内容。 :::
源码:
:::info[这是一个提示]
我是提示内容。
:::
:::::warning[我是父容器]
我是一些文字。
::::success[我是子容器 1]{open}
我是内容 1。
::::
我是一些文字。
::::success[我是子容器 2]{open}
:::error[我是子容器 3]{open}
我是内容 2。
:::
::::
我是一些文字。
:::::
:::epigraph[——otto]
大家好啊,我是说的道理。
:::
:::align{left}
居左内容。
:::
:::align{center}
居中内容。
:::
:::align{right}
居右内容。
:::
还有更多……
尽管安知鱼主题自带很多标签外挂,但仍然无法满足复制洛谷文章到博客编写习惯性的需求,于是,我全网首发了在 Hexo 中使用洛谷 Md 插件教程。
:::info[闲话]
由于不良习惯,我的所有 Markdown 文章编写均遵循洛谷的 专栏全站推荐规范、洛谷主题库题解规范和如何用 Markdown 和 LaTeX 写一篇排版整齐的题解?。
:::
方法
:::align{center} 知其然,知其所以然 :::
用 Javasript 自定义 Hexo 的 markdown 解析,用正则匹配把标识等进行匹配,转换为 Html,最后由 Css 完成样式。
实现
:::warning{open}
本教程以 anzhiyu 主题为例,其他主题亦可,视情况更改 Css 文件目录和引用方式,参见你的博客主题的文档以后可能会更新下这篇文章,搞个多主题支持。
:::
首先,在博客中新建 Css (~/themes/···(主题名称)/source/css/custom_marked.css)粘贴以下代码并引入(Anzhiyu 主题可以在 config.yml 中的 Inject.head 配置项中引入)。
:::success[代码]
.align-left {
text-align: left !important;
}
.align-center {
text-align: center !important;
}
.align-right {
text-align: right !important;
}
.epigraph {
width: 40%;
margin-left: 60%;
}
.epigraph > :last-child {
margin-top: 0;
border-top: 1px solid #888;
text-align: right;
}
summary {
list-style: none;
-webkit-user-select: none;
user-select: none;
outline: 0;
margin: 0;
position: relative;
padding-left: 24px;
font-weight: 700;
list-style: none;
display: list-item;
min-height: 1em;
}
.foldable {
padding: 0.5em 1em;
margin: 1em 0 1em 0.2em;
border-left-width: 5px;
border-left-style: solid;
overflow: hidden;
}
.foldable.info {
border-left-color: rgb(52, 152, 219);
}
.foldable.warning {
border-left-color: rgb(255, 193, 22);
}
.foldable.success {
border-left-color: rgb(82, 196, 26);
}
.foldable.error {
border-left-color: rgb(231, 76, 60);
}
.foldable.bug {
border-left-color: rgb(231, 76, 60);
}
.foldable.flask {
border-left-color: rgb(155, 89, 182);
}
details.info > summary {
color: rgb(52, 152, 219);
}
details.warning > summary {
color: rgb(255, 193, 22);
}
details.success > summary {
color: rgb(82, 196, 26);
}
details.error > summary {
color: rgb(231, 76, 60);
}
details.bug > summary {
color: rgb(231, 76, 60);
}
details.flask > summary {
color: rgb(155, 89, 182);
}
details.info > summary::before {
-webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"%20viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>');
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"%20viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>');
}
details.success > summary::before {
-webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"%20viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"/></svg>');
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"%20viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"/></svg>');
}
details.warning > summary::before {
-webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"%20viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>');
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"%20viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>');
}
details.error > summary::before {
-webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"%20viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"/></svg>');
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"%20viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"/></svg>');
}
details.bug > summary::before {
-webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons:CC BY 4.0,Fonts:SIL OFL 1.1,Code:MIT License) Copyright 2024 Fonticons,Inc. --><path d="M256 0c53 0 96 43 96 96l0 3.6c0 15.7-12.7 28.4-28.4 28.4l-135.1 0c-15.7 0-28.4-12.7-28.4-28.4l0-3.6c0-53 43-96 96-96zM41.4 105.4c12.5-12.5 32.8-12.5 45.3 0l64 64c.7 .7 1.3 1.4 1.9 2.1c14.2-7.3 30.4-11.4 47.5-11.4l112 0c17.1 0 33.2 4.1 47.5 11.4c.6-.7 1.2-1.4 1.9-2.1l64-64c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3l-64 64c-.7 .7-1.4 1.3-2.1 1.9c6.2 12 10.1 25.3 11.1 39.5l64.3 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-64 0c0 24.6-5.5 47.8-15.4 68.6c2.2 1.3 4.2 2.9 6 4.8l64 64c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0l-63.1-63.1c-24.5 21.8-55.8 36.2-90.3 39.6L272 240c0-8.8-7.2-16-16-16s-16 7.2-16 16l0 239.2c-34.5-3.4-65.8-17.8-90.3-39.6L86.6 502.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l64-64c1.9-1.9 3.9-3.4 6-4.8C101.5 367.8 96 344.6 96 320l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l64.3 0c1.1-14.1 5-27.5 11.1-39.5c-.7-.6-1.4-1.2-2.1-1.9l-64-64c-12.5-12.5-12.5-32.8 0-45.3z"/></svg>');
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons:CC BY 4.0,Fonts:SIL OFL 1.1,Code:MIT License) Copyright 2024 Fonticons,Inc. --><path d="M256 0c53 0 96 43 96 96l0 3.6c0 15.7-12.7 28.4-28.4 28.4l-135.1 0c-15.7 0-28.4-12.7-28.4-28.4l0-3.6c0-53 43-96 96-96zM41.4 105.4c12.5-12.5 32.8-12.5 45.3 0l64 64c.7 .7 1.3 1.4 1.9 2.1c14.2-7.3 30.4-11.4 47.5-11.4l112 0c17.1 0 33.2 4.1 47.5 11.4c.6-.7 1.2-1.4 1.9-2.1l64-64c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3l-64 64c-.7 .7-1.4 1.3-2.1 1.9c6.2 12 10.1 25.3 11.1 39.5l64.3 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-64 0c0 24.6-5.5 47.8-15.4 68.6c2.2 1.3 4.2 2.9 6 4.8l64 64c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0l-63.1-63.1c-24.5 21.8-55.8 36.2-90.3 39.6L272 240c0-8.8-7.2-16-16-16s-16 7.2-16 16l0 239.2c-34.5-3.4-65.8-17.8-90.3-39.6L86.6 502.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l64-64c1.9-1.9 3.9-3.4 6-4.8C101.5 367.8 96 344.6 96 320l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l64.3 0c1.1-14.1 5-27.5 11.1-39.5c-.7-.6-1.4-1.2-2.1-1.9l-64-64c-12.5-12.5-12.5-32.8 0-45.3z"/></svg>');
}
details.flask > summary::before {
-webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons:CC BY 4.0,Fonts:SIL OFL 1.1,Code:MIT License) Copyright 2024 Fonticons,Inc. --><path d="M288 0L160 0 128 0C110.3 0 96 14.3 96 32s14.3 32 32 32l0 132.8c0 11.8-3.3 23.5-9.5 33.5L10.3 406.2C3.6 417.2 0 429.7 0 442.6C0 480.9 31.1 512 69.4 512l309.2 0c38.3 0 69.4-31.1 69.4-69.4c0-12.8-3.6-25.4-10.3-36.4L329.5 230.4c-6.2-10.1-9.5-21.7-9.5-33.5L320 64c17.7 0 32-14.3 32-32s-14.3-32-32-32L288 0zM192 196.8L192 64l64 0 0 132.8c0 23.7 6.6 46.9 19 67.1L309.5 320l-171 0L173 263.9c12.4-20.2 19-43.4 19-67.1z"/></svg>');
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons:CC BY 4.0,Fonts:SIL OFL 1.1,Code:MIT License) Copyright 2024 Fonticons,Inc. --><path d="M288 0L160 0 128 0C110.3 0 96 14.3 96 32s14.3 32 32 32l0 132.8c0 11.8-3.3 23.5-9.5 33.5L10.3 406.2C3.6 417.2 0 429.7 0 442.6C0 480.9 31.1 512 69.4 512l309.2 0c38.3 0 69.4-31.1 69.4-69.4c0-12.8-3.6-25.4-10.3-36.4L329.5 230.4c-6.2-10.1-9.5-21.7-9.5-33.5L320 64c17.7 0 32-14.3 32-32s-14.3-32-32-32L288 0zM192 196.8L192 64l64 0 0 132.8c0 23.7 6.6 46.9 19 67.1L309.5 320l-171 0L173 263.9c12.4-20.2 19-43.4 19-67.1z"/></svg>');
}
summary::before {
content: "";
background-color: currentcolor;
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 16px;
height: 16px;
margin-right: 8px;
}
summary:after {
content: "";
background-color: currentcolor;
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20256%20512%22%3E%3Cpath%20d%3D%22M246.6%20278.6c12.5-12.5%2012.5-32.8%200-45.3l-128-128c-9.2-9.2-22.9-11.9-34.9-6.9s-19.8%2016.6-19.8%2029.6l0%20256c0%2012.9%207.8%2024.6%2019.8%2029.6s25.7%202.2%2034.9-6.9l128-128z%22%2F%3E%3C%2Fsvg%3E");;
mask-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20256%20512%22%3E%3Cpath%20d%3D%22M246.6%20278.6c12.5-12.5%2012.5-32.8%200-45.3l-128-128c-9.2-9.2-22.9-11.9-34.9-6.9s-19.8%2016.6-19.8%2029.6l0%20256c0%2012.9%207.8%2024.6%2019.8%2029.6s25.7%202.2%2034.9-6.9l128-128z%22%2F%3E%3C%2Fsvg%3E");
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 16px;
height: 16px;
transition: transform 0.2s ease;
}
details[open] > summary::after {
transform: translateY(-50%) rotate(90deg);
}
:::
然后直接在 ~/themes/···(主题名称)/scripts 中新建 custom_marked.js(也可以是别的名字),粘贴以下代码即可,不用引入。
:::success[代码]
// 允许使用的折叠容器类型集合
const allowedFoldableTypes = new Set(['info', 'warning', 'success', 'error', 'bug', 'flask']);
/**
* 使用 Hexo 同步渲染接口将文本渲染为 Markdown 格式
* @param {string} content 待渲染的文本内容
* @returns {string} 渲染后的 HTML 内容
*/
function renderMarkdown(content) {
return hexo.render.renderSync({ text: content.trim(), engine: 'markdown' });
}
/**
* 根据块类型生成对应的自定义 HTML 结构
* @param {string} blockType 块类型
* @param {string} title 块标题
* @param {string} opt 块配置选项
* @param {string} body 块内部内容
* @returns {string|null} 生成的 HTML 字符串,未知类型返回 null
*/
function renderCustomBlock(blockType, title, opt, body) {
const type = blockType.toLowerCase(); // 统一转换为小写,兼容大小写写法
const content = renderMarkdown(body.trim()); // 渲染块内的 Markdown 内容
// 文本对齐块:生成带对齐类名的 div 容器
if (type === 'align') {
const dir = title ? title.trim() : 'left'; // 未指定方向时默认左对齐
return `\n<div class="align-${dir}">\n${content}\n</div>\n`;
}
// 题记/引语块:生成带作者信息的引语容器
if (type === 'epigraph') {
const author = title ? `<p class="epigraph-author">${title}</p>` : ''; // 标题作为作者信息
return `\n<div class="epigraph">\n${content}${author}\n</div>\n`;
}
// 可折叠容器:生成 details/summary 结构
if (allowedFoldableTypes.has(type)) {
const openAttr = opt && opt.includes('open') ? ' open' : ''; // 包含 open 选项则默认展开
const summary = title ? title.trim() : '详情'; // 未指定标题时默认显示"详情"
return `\n<details class="foldable ${type}"${openAttr}>\n <summary>${summary}</summary>\n <div>\n${content}\n </div>\n</details>\n`;
}
return null; // 未知类型返回 null,保留原始标记
}
/**
* 解析 Markdown 内容中的自定义块语法,构建 AST 并渲染为最终 HTML
* @param {string} content 原始 Markdown 内容
* @returns {string} 处理后的 HTML 内容
*/
function parseCustomBlocks(content) {
const lines = content.split(/\r?\n/); // 按行分割内容,兼容 Windows/Unix 换行符
const root = { type: 'root', children: [] }; // 抽象语法树根节点
const stack = [root]; // 栈结构,用于处理嵌套的块层级
let codeFence = null; // 代码块标记,记录当前是否处于代码块内及围栏字符
// 自定义块开始标记正则:匹配 :::type[title]{opt} 格式
const openRule = /^\s*(:{3,})([a-zA-Z]\w*)(?:\s*\[([^\]]*)\])?(?:\s*\{([^}]*)\})?\s*$/;
// 自定义块结束标记正则:匹配 ::: 格式
const closeRule = /^\s*(:{3,})\s*$/;
for (const line of lines) {
// ---------- 代码块处理逻辑 ----------
const fenceMatch = line.match(/^\s*(`{3,}|~{3,})/);
if (fenceMatch) {
const fence = fenceMatch[1];
if (!codeFence) {
codeFence = fence; // 进入代码块,记录围栏字符
} else if (codeFence === fence) {
codeFence = null; // 退出代码块,清空标记
}
// 代码块标记行直接作为文本保留
stack[stack.length - 1].children.push({ type: 'text', value: line });
continue;
}
// 当前处于代码块内时,所有行均作为纯文本处理,不解析自定义块
if (codeFence) {
stack[stack.length - 1].children.push({ type: 'text', value: line });
continue;
}
// ---------- 自定义块开始标记处理 ----------
const openMatch = line.match(openRule);
if (openMatch) {
const level = openMatch[1].length; // 冒号数量代表块的嵌套层级
const blockType = openMatch[2].toLowerCase(); // 块类型
const title = openMatch[3] || ''; // 块标题(方括号内内容)
const opt = openMatch[4] || ''; // 块配置选项(大括号内内容)
// 创建块节点并加入语法树
const node = { type: 'block', level, blockType, title, opt, children: [] };
stack[stack.length - 1].children.push(node);
stack.push(node); // 压入栈顶,成为当前作用域
continue;
}
// ---------- 自定义块结束标记处理 ----------
const closeMatch = line.match(closeRule);
if (closeMatch) {
const level = closeMatch[1].length;
// 结束标记层级与栈顶块层级一致时,弹出栈顶,闭合当前块
if (stack.length > 1 && stack[stack.length - 1].level === level) {
stack.pop();
continue;
}
}
// ---------- 普通文本行处理 ----------
stack[stack.length - 1].children.push({ type: 'text', value: line });
}
return renderNode(root); // 递归渲染语法树,生成最终 HTML
}
/**
* 递归渲染语法树节点,生成最终 HTML 字符串
* @param {object} node 语法树节点
* @returns {string} 渲染后的文本内容
*/
function renderNode(node) {
// 根节点:遍历渲染所有子节点并拼接
if (node.type === 'root') {
return node.children.map(renderNode).join('\n');
}
// 文本节点:直接返回原始文本
if (node.type === 'text') {
return node.value;
}
// 块节点:先递归渲染内部内容,再包裹对应 HTML 结构
if (node.type === 'block') {
const inner = node.children.map(renderNode).join('\n');
const customHtml = renderCustomBlock(node.blockType, node.title, node.opt, inner);
// 已知类型返回自定义 HTML,未知类型保留原始标记
if (customHtml !== null) {
return customHtml;
}
// 未知块类型:还原原始的自定义标记语法
const start = `${':'.repeat(node.level)}${node.blockType}${node.title ? `[${node.title}]` : ''}${node.opt ? `{${node.opt}}` : ''}`;
const end = ':'.repeat(node.level);
return `${start}\n${inner}\n${end}`;
}
return '';
}
// 注册 Hexo 前置渲染过滤器,在 Markdown 渲染前处理自定义块
hexo.extend.filter.register('before_post_render', data => {
if (typeof data.content !== 'string') return data; // 非字符串内容跳过处理
// 输出调试日志,标记当前处理的文章
hexo.log.debug('custom_marked before_post_render', data.source || data.path || data.title || 'unknown');
data.content = parseCustomBlocks(data.content); // 执行自定义块解析与渲染
return data;
});
:::
AI 声明:代码注释使用智谱清言 GLM-5.2 大模型进行修改和润色。
:::info[为什么不用引入?]{open}
与前面引入的 Css 不同的是,任意主题的目录 ~/themes/···(主题名称)/scripts 功能都一样,Hexo 解析时都会运行此目录下的所有 Javascript 文件。
:::
:::error[可能的错误]
一不小心放到了 ~/themes/anzhiyu/source/js/custom_marked.js 里。唐
:::
完结
hexo new "plugin-test"
hexo cl&&hexo g&&hexo s
赶紧在新文章里尝试并体验一下吧!是不是很好用?如果有什么建议,可以在评论区告诉我。