<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>个人笔记</title><description>云小逸</description><link>https://fuwari.vercel.app/</link><language>zh_CN</language><item><title>后端开发过程关闭 com.chrome.devtools.json 请求方法</title><link>https://fuwari.vercel.app/posts/2025%E5%B9%B4/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91%E8%BF%87%E7%A8%8B%E5%85%B3%E9%97%AD-comchromedevtoolsjson-%E8%AF%B7%E6%B1%82%E6%96%B9%E6%B3%95/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2025%E5%B9%B4/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91%E8%BF%87%E7%A8%8B%E5%85%B3%E9%97%AD-comchromedevtoolsjson-%E8%AF%B7%E6%B1%82%E6%96%B9%E6%B3%95/</guid><pubDate>Wed, 17 Dec 2025 20:56:19 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;如果你在开发服务器过程中遇到这样的一个请求：&quot;/.well-known/appspecific/com.chrome.devtools.json&quot;，或许你会很疑惑，自己的代码中并没有写这样的一个请求，其实它是 Chrome DevTools 发起的一个资源请求，核心目的是为了实现 “自动工作区文件夹” 功能 —— 让 DevTools 能自动识别本地开发服务器的项目结构，目的是跳过手动配置直接编辑并保存文件到本地源码。&lt;/p&gt;
&lt;p&gt;但是作为开发者，开发环境后台输出意料之外的请求总是让人恼火，尤其对于不了解该功能的开发者，容易误以为是自己的路由配置或代码出现 bug。&lt;/p&gt;
&lt;h2&gt;解决方法&lt;/h2&gt;
&lt;p&gt;针对这个请求，我们可以根据需求选择 “禁用” 或 “利用” 两种策略&lt;/p&gt;
&lt;h3&gt;禁用方法&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;打开 Chrome 浏览器，在地址栏输入 &lt;code&gt;chrome://flags/#devtools-project-settings&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;在打开的页面中，找到 &quot;DevTools Project Settings&quot; 选项并设置为 &quot;Disabled&quot;;&lt;/li&gt;
&lt;li&gt;重启浏览器，后续将不再发起该请求。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;深入了解 com.chrome.devtools.json&lt;/h2&gt;
&lt;p&gt;当你在工作本地项目时打开 Chrome DevTools，它会自动向 URL &lt;code&gt;/.well-known/appspecific/com.chrome.devtools.json&lt;/code&gt; 发送请求以检查特定的 JSON 文件。此文件包含有关你的项目文件夹结构的信息，使 DevTools 能够将你的本地文件映射到浏览器中加载的资源。&lt;/p&gt;
&lt;h3&gt;触发条件&lt;/h3&gt;
&lt;p&gt;其实这个请求并非所有场景下都会出现，它的触发有严格限制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;仅当项目运行在&lt;a href=&quot;https://localhost/&quot;&gt;localhost&lt;/a&gt;（本地开发服务器）时触发；&lt;/li&gt;
&lt;li&gt;仅在开发模式下，且 Chrome DevTools 处于打开状态；&lt;/li&gt;
&lt;li&gt;仅 Chromium 内核浏览器（Chrome、Edge、Brave 等）会发起该请求，Firefox 等其他浏览器无此行为。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;功能背景&lt;/h3&gt;
&lt;p&gt;该请求是 Chrome DevTools 在 M-135 版本（2025 年发布）新增的 “Automatic Workspace Folders”的功能，在这个功能出现之前，Chrome 的 Workspace 功能虽能实现 “DevTools 编辑→本地文件同步”，但存在两大问题：一是需要手动配置文件映射，操作繁琐且不直观；二是多项目切换时需重复设置，易用性极差。而 “Automatic Workspace Folders” 功能通过自动识别项目结构，完美解决了这两个痛点 &lt;a href=&quot;https://developer.chrome.google.cn/docs/devtools/automatic-workspaces?hl=zh-cn&quot;&gt;Chrome 开发者工具中的自动 Workspace 连接&lt;/a&gt;。&lt;/p&gt;
&lt;h3&gt;文件：com.chrome.devtools.json 的规范&lt;/h3&gt;
&lt;p&gt;DevTools 之所以发起请求，是为了获取一个特定的 JSON 文件，该文件需满足以下要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;路径位置：必须放在项目根目录的 &lt;code&gt;/.well-known/appspecific/&lt;/code&gt; 文件夹下；&lt;/li&gt;
&lt;li&gt;核心结构：包含 &lt;code&gt;workspace&lt;/code&gt; 对象，且必须有两个属性：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;root&lt;/code&gt;：项目文件夹的绝对路径（如 &lt;code&gt;/Users/foobar/Projects/my-awesome-web-project&lt;/code&gt;）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uuid&lt;/code&gt;：项目唯一标识，推荐使用 v4 版本 UUID（可通过 &lt;code&gt;npx uuid v4&lt;/code&gt; 生成）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例文件内容如下：&lt;/p&gt;
&lt;p&gt;json&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;workspace&quot;: {
    &quot;root&quot;: &quot;/Users/foobar/Projects/my-awesome-web-project&quot;,
    &quot;uuid&quot;: &quot;53b029bb-c989-4dca-969b-835fecec3717&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果服务器无法返回该文件，就会出现 404 错误 —— 这也是大多数开发者遇到的核心问题，因为 Remix、Next.js、SvelteKit 等主流框架默认都没有配置该路由。&lt;/p&gt;
&lt;h3&gt;充分利用 com.chrome.devtools.json&lt;/h3&gt;
&lt;p&gt;如果想充分利用 Chrome 的这个实用功能，只需让服务器正确返回 JSON 文件即可。不同框架有不同的实现方式，以下是主流场景的配置指南：&lt;/p&gt;
&lt;h4&gt;通用手动配置（适用于所有框架）&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;在项目根目录创建文件夹：&lt;code&gt;/.well-known/appspecific/&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;在该文件夹下创建 &lt;code&gt;com.chrome.devtools.json&lt;/code&gt; 文件，按规范填写 &lt;code&gt;root&lt;/code&gt; 和 &lt;code&gt;uuid&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;确保服务器能正常访问该静态文件（如使用 &lt;code&gt;npx serve&lt;/code&gt; 启动简单服务器）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;框架专属配置（更高效）&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Vite 项目（React、Svelte 等）&lt;/strong&gt;：使用 &lt;code&gt;vite-plugin-devtools-json&lt;/code&gt; 插件自动生成文件，无需手动配置：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;安装插件：&lt;code&gt;npm install --save-dev vite-plugin-devtools-json&lt;/code&gt;；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 &lt;code&gt;vite.config.js&lt;/code&gt; 中引入：&lt;/p&gt;
&lt;p&gt;javascript&lt;/p&gt;
&lt;p&gt;运行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { defineConfig } from &apos;vite&apos;;
import devtoolsJson from &apos;vite-plugin-devtools-json&apos;;

export default defineConfig({
  plugins: [devtoolsJson()],
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Remix 项目&lt;/strong&gt;：创建路由文件 &lt;code&gt;app/routes/[.]well-known.appspecific.[com.chrome.devtools.json].tsx&lt;/code&gt;，添加 loader 函数：&lt;/p&gt;
&lt;p&gt;typescript&lt;/p&gt;
&lt;p&gt;运行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { json } from &apos;@remix-run/node&apos;;

export const loader = async () =&amp;gt; {
  return json({
    workspace: {
      root: process.cwd(),
      uuid: &apos;6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b&apos;,
    },
  });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Next.js 项目&lt;/strong&gt;：在 &lt;code&gt;app/.well-known/appspecific/com.chrome.devtools.json/&lt;/code&gt; 目录下创建 &lt;code&gt;route.js&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;javascript&lt;/p&gt;
&lt;p&gt;运行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export async function GET() {
  return Response.json({
    workspace: {
      root: process.cwd(),
      uuid: &apos;6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b&apos;,
    },
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：启用 “自动工作区文件夹” 功能，实现 DevTools 实时编辑本地文件，提升调试效率；&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：需要少量配置，新增文件会带来轻微的维护成本。&lt;/p&gt;
&lt;h2&gt;如何使用 “Automatic Workspace Folders” 功能&lt;/h2&gt;
&lt;p&gt;配置完成后，即可享受该功能带来的高效调试体验，步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;启动本地开发服务器（如 &lt;code&gt;npm run dev&lt;/code&gt;），确保项目运行在&lt;a href=&quot;https://localhost/&quot;&gt;localhost&lt;/a&gt;；&lt;/li&gt;
&lt;li&gt;打开 Chrome DevTools（快捷键 &lt;code&gt;Ctrl+Shift+I&lt;/code&gt; 或 &lt;code&gt;Cmd+Option+I&lt;/code&gt;）；&lt;/li&gt;
&lt;li&gt;切换到 Sources 标签，找到 Workspace 面板；&lt;/li&gt;
&lt;li&gt;点击项目文件夹旁的 “Connect”，并允许 DevTools 访问本地文件；&lt;/li&gt;
&lt;li&gt;此后在 DevTools 中编辑 HTML、CSS、JS 文件，更改会自动保存到本地源码，文件旁会显示绿色圆点表示映射成功。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;常见问题与最佳实践&lt;/h2&gt;
&lt;h3&gt;1. troubleshooting 常见问题&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;禁用功能后请求仍存在：清除浏览器缓存或重新启动 Chrome；&lt;/li&gt;
&lt;li&gt;Brave 浏览器也出现该请求：Brave 基于 Chromium 内核，需在 &lt;code&gt;brave://flags/#devtools-project-settings&lt;/code&gt; 中禁用；&lt;/li&gt;
&lt;li&gt;UUID 无效报错：使用 &lt;code&gt;npx uuid v4&lt;/code&gt; 生成标准 v4 UUID，避免手动编写；&lt;/li&gt;
&lt;li&gt;配置后文件无法访问：检查服务器是否正确配置静态文件访问，或路由是否匹配。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 最佳实践建议&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;团队协作：如果选择支持该功能，建议在项目 README 中添加配置说明，确保团队成员统一环境；&lt;/li&gt;
&lt;li&gt;优先使用插件：Vite 项目推荐直接使用 &lt;code&gt;vite-plugin-devtools-json&lt;/code&gt;，避免手动维护 JSON 文件；&lt;/li&gt;
&lt;li&gt;生产环境无需关注：该请求仅在&lt;a href=&quot;https://localhost/&quot;&gt;localhost&lt;/a&gt;开发环境触发，不会影响线上应用；&lt;/li&gt;
&lt;li&gt;避免误屏蔽：服务器端屏蔽时，确保仅针对该特定路径，不要影响 Let’s Encrypt 等其他依赖 &lt;code&gt;.well-known&lt;/code&gt; 路径的服务。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;这个看似“烦人”的 &lt;code&gt;/.well-known/appspecific/com.chrome.devtools.json&lt;/code&gt; 请求，本质是 Chrome 为提升调试体验而推出的实用功能。因此无需被它的 404 错误困扰，根据自身需求，要么直接禁用省心省力，要么配置启用享受高效调试。&lt;/p&gt;
&lt;p&gt;对于追求开发效率的开发者来说，只需简单几步就能解锁 “DevTools 实时编辑本地文件” 的强大能力，可以让调试流程更顺畅。&lt;/p&gt;
</content:encoded></item><item><title>Enquirer 现代化的交互式命令行工具库</title><link>https://fuwari.vercel.app/posts/2025%E5%B9%B4/enquirer-%E7%8E%B0%E4%BB%A3%E5%8C%96%E7%9A%84%E4%BA%A4%E4%BA%92%E5%BC%8F%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%B7%A5%E5%85%B7%E5%BA%93/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2025%E5%B9%B4/enquirer-%E7%8E%B0%E4%BB%A3%E5%8C%96%E7%9A%84%E4%BA%A4%E4%BA%92%E5%BC%8F%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%B7%A5%E5%85%B7%E5%BA%93/</guid><pubDate>Mon, 30 Jun 2025 14:08:54 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在现代前端开发中，命令行工具 (CLI) 已经成为开发者日常工作中不可或缺的一部分。而如何让CLI工具更加友好、直观，提供良好的用户体验？交互式命令行界面是关键。这里将介绍如何使用&lt;code&gt;Enquirer&lt;/code&gt; 来构建交互式命令行。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Enquirer&lt;/code&gt; 是一个现代化、直观且用户友好的命令行提示工具库。它提供了简洁的 API 、现代化的界面以及更强大的交互功能。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/enquirer/enquirer&quot;&gt;Github&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;h3&gt;安装&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm install enquirer 
# or
yarn yarn add enquirer
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;使用&lt;/h3&gt;
&lt;h4&gt;1.基本选择提示&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;const { Select } = require(&quot;enquirer&quot;);

const prompt = new Select({
  name: &quot;color&quot;,
  message: &quot;选择你喜欢的颜色&quot;,
  choices: [&quot;红色&quot;, &quot;黄色&quot;, &quot;蓝色&quot;],
});

prompt
  .run()
  .then((answer) =&amp;gt; console.log(&quot;你选择了:&quot;, answer))
  .catch(console.error);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.多选提示&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;const { MultiSelect } = require(&apos;enquirer&apos;);

const prompt = new MultiSelect({
  name: &apos;foods&apos;,
  message: &apos;选择你喜欢的食物(按空格选择)&apos;,
  choices: [
    { name: &apos;pizza&apos;, value: &apos;披萨&apos; },
    { name: &apos;burger&apos;, value: &apos;汉堡&apos; },
    { name: &apos;sushi&apos;, value: &apos;寿司&apos; },
    { name: &apos;noodles&apos;, value: &apos;面条&apos; }
  ]
});

prompt.run()
  .then(answers =&amp;gt; console.log(&apos;你选择了:&apos;, answers))
  .catch(console.error);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.输入框提示&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;const { Input } = require(&apos;enquirer&apos;);

const prompt = new Input({
  name: &apos;username&apos;,
  message: &apos;请输入你的用户名&apos;
});

prompt.run()
  .then(answer =&amp;gt; console.log(&apos;用户名:&apos;, answer))
  .catch(console.error);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4.表单输入&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;const { Form } = require(&apos;enquirer&apos;);

const prompt = new Form({
  name: &apos;user&apos;,
  message: &apos;请填写用户信息&apos;,
  choices: [
    { name: &apos;firstName&apos;, message: &apos;名&apos; },
    { name: &apos;lastName&apos;, message: &apos;姓&apos; },
    { name: &apos;email&apos;, message: &apos;邮箱&apos; }
  ]
});

prompt.run()
  .then(value =&amp;gt; console.log(&apos;用户信息:&apos;, value))
  .catch(console.error);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5.自动补全&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;const { AutoComplete } = require(&apos;enquirer&apos;);

const prompt = new AutoComplete({
  name: &apos;flavor&apos;,
  message: &apos;选择你喜欢的编程语言(输入可过滤)&apos;,
  limit: 10,
  initial: 2,
  choices: [
    &apos;JavaScript&apos;,
    &apos;TypeScript&apos;,
    &apos;Python&apos;,
    &apos;Java&apos;,
    &apos;C++&apos;,
    &apos;C#&apos;,
    &apos;Go&apos;,
    &apos;Ruby&apos;,
    &apos;Swift&apos;,
    &apos;Kotlin&apos;
  ]
});

prompt.run()
  .then(answer =&amp;gt; console.log(&apos;你选择了:&apos;, answer))
  .catch(console.error);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;6.自定义验证&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;const { Input } = require(&apos;enquirer&apos;);

const prompt = new Input({
  name: &apos;age&apos;,
  message: &apos;请输入你的年龄&apos;,
  validate(value) {
    if (isNaN(value)) return &apos;请输入数字&apos;;
    if (value &amp;lt; 18) return &apos;必须年满18岁&apos;;
    return true;
  }
});

prompt.run()
  .then(answer =&amp;gt; console.log(&apos;年龄:&apos;, answer))
  .catch(console.error);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;构建一个 Cli 项目&lt;/h2&gt;
&lt;p&gt;在这里我们使用 &lt;code&gt;prompt&lt;/code&gt; 函数构建：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const { prompt } = require(&quot;enquirer&quot;);

async function initProject() {
  const answers = await prompt([
    {
      type: &quot;select&quot;,
      name: &quot;type&quot;,
      message: &quot;选择框架&quot;,
      choices: [&quot;Vue&quot;, &quot;React&quot;],
    },
    {
      type: &quot;input&quot;,
      name: &quot;name&quot;,
      message: &quot;输入项目名称&quot;,
      validate: (value) =&amp;gt; (value.trim() ? true : &quot;项目名称不能为空&quot;),
    },
    {
      type: &quot;confirm&quot;,
      name: &quot;typescript&quot;,
      message: &quot;确定要安装吗?&quot;,
    },
    {
      type: &quot;multiselect&quot;,
      name: &quot;features&quot;,
      message: &quot;选择额外功能(按空格选择)&quot;,
      choices: [
        { name: &quot;eslint&quot;, message: &quot;ESLint代码检查&quot; },
        { name: &quot;prettier&quot;, message: &quot;Prettier代码格式化&quot; },
        { name: &quot;tests&quot;, message: &quot;单元测试&quot; },
        { name: &quot;ci&quot;, message: &quot;CI配置&quot; },
      ],
    },
  ]);
  console.log(&quot;\n配置汇总:&quot;);
  console.log(&quot;项目类型:&quot;, answers.type);
  console.log(&quot;项目名称:&quot;, answers.name);
  console.log(&quot;使用TypeScript:&quot;, answers.typescript ? &quot;是&quot; : &quot;否&quot;);
  console.log(&quot;额外功能:&quot;, answers.features.join(&quot;, &quot;));
}

initProject().catch(console.error);
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>告别手动计算！PixiJS Layout v3 带来 CSS 级布局能力</title><link>https://fuwari.vercel.app/posts/2025%E5%B9%B4/%E5%91%8A%E5%88%AB%E6%89%8B%E5%8A%A8%E8%AE%A1%E7%AE%97pixijs-layout-v3-%E5%B8%A6%E6%9D%A5-css-%E7%BA%A7%E5%B8%83%E5%B1%80%E8%83%BD%E5%8A%9B/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2025%E5%B9%B4/%E5%91%8A%E5%88%AB%E6%89%8B%E5%8A%A8%E8%AE%A1%E7%AE%97pixijs-layout-v3-%E5%B8%A6%E6%9D%A5-css-%E7%BA%A7%E5%B8%83%E5%B1%80%E8%83%BD%E5%8A%9B/</guid><pubDate>Mon, 26 May 2025 21:02:35 GMT</pubDate><content:encoded>&lt;p&gt;2025年4月29日，PixiJS官方正式发布 &lt;a href=&quot;https://layout.pixijs.io/&quot;&gt;​​PixiJS Layout v3​​&lt;/a&gt;，这是一个从零开始彻底重构的库版本。新版本基于 &lt;a href=&quot;https://layout.pixijs.io/&quot;&gt;​​Yoga&lt;/a&gt;​ ​布局引擎，为 &lt;a href=&quot;https://pixijs.com/&quot;&gt;PixiJS&lt;/a&gt; 项目提供了一种强大且符合网页标准的布局管理方式。&lt;/p&gt;
&lt;p&gt;这不仅仅是一次重写，v3 版本代表着​&lt;strong&gt;​重大飞跃​&lt;/strong&gt;​：它将真正的​&lt;strong&gt;​弹性盒子布局（flexbox）​&lt;/strong&gt;​理念引入PixiJS生态系统，让习惯 CSS 布局的网页开发者能够无缝过渡。无论您是在开发游戏UI、动态画布应用还是完整交互体验，现在都可以使用熟悉的布局模式——如&lt;code&gt;flex-grow&lt;/code&gt;、&lt;code&gt;justify-content&lt;/code&gt;、&lt;code&gt;align-items&lt;/code&gt;等属性。&lt;/p&gt;
&lt;p&gt;此外，PixiJS Layout v3 与 &lt;a href=&quot;https://react.pixijs.io/&quot;&gt;PixiJS React&lt;/a&gt; 深度集成，让 React 开发者能够通过声明式语法轻松构建和管理 PixiJS 界面。无论您使用原生 PixiJS 还是结合 React，布局现在都变得更简单、直观且强大。&lt;/p&gt;
&lt;h2&gt;为网页开发者彻底重构&lt;/h2&gt;
&lt;p&gt;v3是​ &lt;strong&gt;​完全重写​&lt;/strong&gt; ​的版本，底层采用 ​&lt;strong&gt;​Yoga​&lt;/strong&gt;​ 引擎，首次为 PixiJS 带来可预测的网页标准布局行为。您可以用网页开发的思维方式处理布局——弹性容器、自动换行、对齐和间距——同时保持 PixiJS 的性能优势。&lt;/p&gt;
&lt;h3&gt;为什么这很重要&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;strong&gt;​网页开发者可以使用熟悉的弹性盒子概念​&lt;/strong&gt;​，无需改变开发习惯&lt;/li&gt;
&lt;li&gt;​&lt;strong&gt;​零布局学习曲线​&lt;/strong&gt;​：如果您了解 CSS Flexbox，就已经掌握 PixiJS 场景布局&lt;/li&gt;
&lt;li&gt;​&lt;strong&gt;​React开发者获得更强能力​&lt;/strong&gt;​：v3完美兼容PixiJS React，支持直接在 JSX 中声明布局&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;核心特性&lt;/h2&gt;
&lt;h3&gt;Yoga驱动的布局引擎&lt;/h3&gt;
&lt;p&gt;采用 &lt;a href=&quot;https://reactnative.dev/&quot;&gt;React Native&lt;/a&gt; 同款的 Yoga 引擎，为 PixiJS 带来经过验证的弹性盒子布局系统。完整支持 &lt;code&gt;justifyContent&lt;/code&gt;、&lt;code&gt;alignItems&lt;/code&gt;、&lt;code&gt;flexDirection&lt;/code&gt;和&lt;code&gt;gap&lt;/code&gt;等常见属性。&lt;/p&gt;
&lt;h3&gt;可选式设计&lt;/h3&gt;
&lt;p&gt;PixiJS Layout v3 采用​&lt;strong&gt;​按需启用​&lt;/strong&gt;​设计。您可以仅为需要布局的对象（如 &lt;code&gt;Containers&lt;/code&gt;、&lt;code&gt;Sprites&lt;/code&gt;、&lt;code&gt;Graphics&lt;/code&gt;、&lt;code&gt;Text&lt;/code&gt; 或自定义对象）激活功能，无需重构整个项目。&lt;/p&gt;
&lt;p&gt;这种灵活性让您可以在现有项目中逐步引入布局功能，保持代码整洁高效。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const sprite = new Sprite({ texture, layout: true });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或直接在创建对象时定义布局：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const container = new Container({
    layout: {
        width: 500,
        height: 300,
        justifyContent: &apos;center&apos;,
        alignContent: &apos;center&apos;,
        flexWrap: &apos;wrap&apos;,
    },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;React深度整合&lt;/h3&gt;
&lt;p&gt;v3包含与 &lt;a href=&quot;https://react.pixijs.io/&quot;&gt;PixiJS React&lt;/a&gt; 的完整集成，支持直观的JSX语法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;layoutContainer layout={{ flexDirection: &apos;row&apos;, gap: 10 }}&amp;gt;
    &amp;lt;layoutSprite texture={texture} layout={{ width: 100, height: 100 }} /&amp;gt;
    &amp;lt;layoutSprite texture={texture} layout={{ width: 100, height: 100 }} /&amp;gt;
&amp;lt;/layoutContainer&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这让 React 开发者能够像构建网页UI一样声明式地组合布局，同时享受 PixiJS 的渲染性能。&lt;/p&gt;
&lt;h3&gt;网页风格特性&lt;/h3&gt;
&lt;p&gt;新增多项网页风格功能增强布局能力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;strong&gt;​&lt;code&gt;objectFit&lt;/code&gt;​&lt;/strong&gt;​：控制内容在容器内的缩放方式（&lt;code&gt;fill&lt;/code&gt;、&lt;code&gt;contain&lt;/code&gt;、&lt;code&gt;cover&lt;/code&gt;、&lt;code&gt;none&lt;/code&gt;、&lt;code&gt;scale-down&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;​&lt;strong&gt;​&lt;code&gt;objectPosition&lt;/code&gt;​&lt;/strong&gt;​：微调内容在布局边界内的对齐&lt;/li&gt;
&lt;li&gt;​&lt;strong&gt;​溢出滚动​&lt;/strong&gt;​：为任何容器启用 &lt;code&gt;overflow: scroll&lt;/code&gt; 效果&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;文档全面升级&lt;/h3&gt;
&lt;p&gt;重写后的&lt;a href=&quot;https://layout.pixijs.io/&quot;&gt;文档&lt;/a&gt;​现在包含：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;清晰的上手指南和最佳实践&lt;/li&gt;
&lt;li&gt;覆盖常见用例的详细示例&lt;/li&gt;
&lt;li&gt;改进的布局行为解释和 PixiJS 集成说明&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;新版文档让入门比以往更简单。&lt;/p&gt;
&lt;h2&gt;快速开始&lt;/h2&gt;
&lt;p&gt;由于采用可选式架构，您可以在任何 PixiJS 项目中逐步应用布局功能。查看&lt;a href=&quot;https://layout.pixijs.io/docs/guides/guide/quick-start&quot;&gt;入门指南&lt;/a&gt;获取详细教程，以下是简要步骤：&lt;/p&gt;
&lt;h3&gt;安装配置&lt;/h3&gt;
&lt;p&gt;安装PixiJS Layout v3：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm add @pixi/layout
# 或
yarn add @pixi/layout
# 或
npm install @pixi/layout
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在应用中早期导入库：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import &apos;@pixi/layout&apos;;

// ... 初始化应用
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在需要时启用布局：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const container = new Container({
    layout: {
        width: 500,
        height: 300,
        justifyContent: &apos;center&apos;,
        alignContent: &apos;center&apos;,
        flexWrap: &apos;wrap&apos;,
    },
});

const sprite = new Sprite({ texture, layout: true });

container.addChild(sprite);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;​&lt;strong&gt;​PixiJS Layout v3 将网页标准弹性盒子布局引入 2D 图形领域。​&lt;/strong&gt;​&lt;/p&gt;
&lt;p&gt;网页开发者现在可以使用熟悉的工具构建 PixiJS 项目，React 开发者则能像开发网页应用一样组合高性能的 canvas 界面。&lt;/p&gt;
&lt;p&gt;原文地址：&lt;a href=&quot;https://pixijs.com/blog/layout-v3&quot;&gt;https://pixijs.com/blog/layout-v3&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Windows Terminal 文本出现纯色背景问题解决方法</title><link>https://fuwari.vercel.app/posts/2025%E5%B9%B4/windows-terminal-%E6%96%87%E6%9C%AC%E5%87%BA%E7%8E%B0%E7%BA%AF%E8%89%B2%E8%83%8C%E6%99%AF%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2025%E5%B9%B4/windows-terminal-%E6%96%87%E6%9C%AC%E5%87%BA%E7%8E%B0%E7%BA%AF%E8%89%B2%E8%83%8C%E6%99%AF%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</guid><pubDate>Thu, 03 Apr 2025 20:47:25 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;不知道是最近的哪一次自动更新，当 Windows Terminal 背景透明度不为100%时，PowerShell 的文字会出现黑色的纯色背景，排查了终端美化工具和配色设置均没发现问题，最后发现该Bug跟PowerShell中的 PSReadline 模块相关。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../assets/Pasted%20image%2020250403204908.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;PSReadline&lt;/h2&gt;
&lt;p&gt;PSReadline 是一个用于增强 PowerShell 控制台体验的模块，主要提供命令行编辑、历史记录搜索、自动完成以及命令行高亮显示等功能。它提供了类似 Unix Shell 的命令行增强功能。&lt;/p&gt;
&lt;p&gt;但是 Windows Terminal 最近更新的版本 1.21.10351.0 以及之后的版本估计添加了新的特性，导致老版本的 PSReadline 不兼容。&lt;/p&gt;
&lt;h2&gt;解决方法&lt;/h2&gt;
&lt;p&gt;解决方法也很简单，就是把 PSReadline 更新到最新版即可。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;查看当前 PSReadline 版本&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-Module -ListAvailable PSReadline
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果显示版本为 2.0.0 那么就需要更新了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;安装或更新（需要管理员身份运行）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Install-Module -Name PSReadLine -Scope AllUsers -AllowClobber -Force
#or
Update-Module -Name PSReadline
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再次重启 Windows Terminal 就可以看到纯色文字背景的问题解决了。&lt;/p&gt;
&lt;p&gt;参考：&lt;a href=&quot;https://github.com/microsoft/terminal/issues/18624&quot;&gt;Black text background when using PowerShell 5&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>深入认识工厂模式</title><link>https://fuwari.vercel.app/posts/2025%E5%B9%B4/%E6%B7%B1%E5%85%A5%E8%AE%A4%E8%AF%86%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2025%E5%B9%B4/%E6%B7%B1%E5%85%A5%E8%AE%A4%E8%AF%86%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/</guid><pubDate>Fri, 28 Mar 2025 21:05:15 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在平时开发过程中，如果遇到需要创建多个配置相似的对象，我们普遍想到的都是使用构造函数来解决，这种方式简单直观，有着清晰的继承体系，特别适合&lt;strong&gt;面向对象编程&lt;/strong&gt;，但是不可避免的必须使用 &lt;code&gt;new&lt;/code&gt; 关键字，否则 &lt;code&gt;this&lt;/code&gt; 指向全局对象（如 &lt;code&gt;window&lt;/code&gt;），导致隐蔽的 Bug。而 &lt;code&gt;new&lt;/code&gt; 和 &lt;code&gt;this&lt;/code&gt; 的关键字依赖又导致原型链的复杂性增加，难以实现私有变量。&lt;/p&gt;
&lt;p&gt;这在我们需要灵活创建多个配置相似对象同时实现真正的封装和私有状态还要避免类继承的局限性时，使用构造函数已经很难满足我们的需求，试想一下当你在封装组件时遇到响应式丢失、类继承导致的重构困难时，就不可避免的走向对&lt;/p&gt;
&lt;h2&gt;工厂模式&lt;/h2&gt;
&lt;p&gt;也因此​&lt;strong&gt;工厂模式（Factory Pattern）​&lt;/strong&gt;这种&lt;strong&gt;创建型&lt;/strong&gt;的设计模式出现了，它的核心思想是 ​&lt;strong&gt;将对象的创建逻辑封装在一个函数或类中&lt;/strong&gt;，而不是直接通过 &lt;code&gt;new&lt;/code&gt; 或字面量手动创建。通过这种方式，可以实现 ​&lt;strong&gt;对象创建的集中管理、解耦代码&lt;/strong&gt;，并支持 ​&lt;strong&gt;动态生成不同类型的对象&lt;/strong&gt;，这些特性完美契合&lt;strong&gt;函数式编程&lt;/strong&gt;的理念，即&lt;strong&gt;无副作用&lt;/strong&gt;、&lt;strong&gt;纯函数&lt;/strong&gt; 和 ​&lt;strong&gt;不可变性&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;实现&lt;/h3&gt;
&lt;p&gt;而为了实现工厂模式，我们​需要&lt;strong&gt;封装创建过程&lt;/strong&gt;，也就是通过一个函数，根据输入参数返回不同对象，可以发现工厂模式它就像流水线一样批量生产对象，不同于传统的类或构造函数，它通过普通函数&lt;strong&gt;返回新对象&lt;/strong&gt;的方式实现对象创建。&lt;/p&gt;
&lt;p&gt;也因此工厂模式本质是通过一个普通函数，在&lt;strong&gt;内部构造对象并返回&lt;/strong&gt;，无需 &lt;code&gt;new&lt;/code&gt; 关键字调用。&lt;/p&gt;
&lt;p&gt;既然提到了函数，那么在工厂模式中函数又分为两种，即&lt;strong&gt;工厂函数&lt;/strong&gt;和&lt;strong&gt;函数工厂&lt;/strong&gt;，虽然它们的名字非常相似，但是​侧重点不同，可以理解为 ​&lt;strong&gt;同一设计模式的不同应用场景&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;工厂函数（Factory Function）&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;工厂函数&lt;/strong&gt;是一种用于创建并返回对象的函数，它的核心思想是 ​&lt;strong&gt;封装对象的创建过程&lt;/strong&gt;，提供一种更灵活、可复用的对象生成方式，与传统的构造函数相比，工厂函数不依赖 &lt;code&gt;new&lt;/code&gt; 关键字，而是直接通过函数调用来生成对象。&lt;/p&gt;
&lt;p&gt;在 JavaScript 中，​工厂函数并不是一种官方的语言特性，而是一种被广泛使用的 ​&lt;strong&gt;设计模式&lt;/strong&gt;​。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工厂函数：返回一个对象&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function createUser(name) {
  return {
    name,
    greet() {
      console.log(`Hi, I&apos;m ${name}!`);
    },
  };
}

const user = createUser(&quot;Alice&quot;);
user.greet(); // &quot;Hi, I&apos;m Alice!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;函数工厂（Function Factory）&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;函数工厂&lt;/strong&gt;是一种通过函数生成并返回其他函数的设计模式，它通过闭包和参数化机制，动态创建具有特定行为的函数。在 JavaScript 中，函数工厂常用于 &lt;strong&gt;逻辑复用&lt;/strong&gt;、&lt;strong&gt;动态配置&lt;/strong&gt; 和 &lt;strong&gt;上下文隔离&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;函数工厂：返回一个函数&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function createMultiplier(factor) {
  return function (x) {
    return x * factor;
  };
}

const double = createMultiplier(2);
console.log(double(5)); // 10
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;对比&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;术语&lt;/th&gt;
&lt;th&gt;目标&lt;/th&gt;
&lt;th&gt;返回值类型&lt;/th&gt;
&lt;th&gt;用途场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;​&lt;strong&gt;工厂函数&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;​&lt;strong&gt;创建对象&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;对象（Object）&lt;/td&gt;
&lt;td&gt;生成结构相似的对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;​&lt;strong&gt;函数工厂&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;​&lt;strong&gt;创建函数&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;函数（Function）&lt;/td&gt;
&lt;td&gt;动态生成不同行为的函数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;两者都是 ​&lt;strong&gt;工厂模式&lt;/strong&gt; 的具体实现，而使用哪种则取决于具体的场景，也因此接下来我会将这两种函数都统称为工厂函数。&lt;/p&gt;
&lt;h3&gt;与传统方式的对比&lt;/h3&gt;
&lt;h4&gt;1. 构造函数方式&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function User(name) {
  this.name = name
}

User.prototype.login = function() {
  console.log(`${this.name} logged in`)
}

const user = new User(&apos;Charlie&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. 类语法&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;class User {
  constructor(name) {
    this.name = name
  }

  login() {
    console.log(`${this.name} logged in`)
  }
}

const user = new User(&apos;David&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. 工厂函数方法&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function createUser(name) {
  return {
    name,  // 等价于 name: name
    login() {  // 直接定义方法
      console.log(`${this.name} logged in`)
    }
  }
}

// 使用方式
const user = createUser(&apos;Charlie&apos;)  // 不需要new关键字
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;工厂函数更适合 ​&lt;strong&gt;快速创建灵活对象、实现私有状态、避免 &lt;code&gt;new&lt;/code&gt; 依赖&lt;/strong&gt; 的场景。三者并非对立，而是互补的工具，应根据具体场景需求选择。&lt;/p&gt;
&lt;h2&gt;创建工厂函数的四个步骤&lt;/h2&gt;
&lt;h3&gt;1. 基础对象创建&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function createCircle(radius) {
  return {
    radius,
    draw() {
      console.log(&apos;Drawing circle&apos;)
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 添加私有成员&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function createBankAccount(balance) {
  // 真正私有的变量
  let _balance = balance 

  return {
    deposit(amount) {
      _balance += amount
    },
    getBalance() {
      return _balance
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 实现方法共享&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const userMethods = {
  login() {
    console.log(`${this.name} logged in`)
  },
  logout() {
    console.log(`${this.name} logged out`)
  }
}

function createUser(name) {
  return {
    name,
    ...userMethods
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 实现组合继承&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function createAdminUser(name) {
  const user = createUser(name)
  
  return {
    ...user,
    deleteUser(id) {
      console.log(`Deleting user ${id}`)
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;高级应用技巧&lt;/h2&gt;
&lt;h3&gt;动态创建组件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const defineFormComponent = &amp;lt;T extends object&amp;gt;(factory: (ctx: T) =&amp;gt; JSX.Element) =&amp;gt; {
  return defineComponent({
    inheritAttrs: false,
    setup(props, { attrs }) {
      const context = reactive({ ...attrs }) as T
      onBeforeUnmount(() =&amp;gt; {
        if (&apos;inputRef&apos; in context) {
          (context as any).inputRef = null
        }
      })
      return () =&amp;gt; factory(context)
    }
  })
}

// 表单配置
const formState = reactive({
  username: &apos;&apos;,
  id: &apos;&apos;
})

const editModalFormItems = Object.freeze([
  {
    label: &quot;username&quot;,
    name: &quot;username&quot;,
    component: defineFormComponent((ctx) =&amp;gt; 
      &amp;lt;a-input 
        v-model:value={ctx.formState.username}
        ref={(el) =&amp;gt; ctx.inputRef = el}
      /&amp;gt;
    ),
    extraProps: {
      formState,
      inputRef: null as HTMLInputElement | null
    }
  },
  {
    label: &quot;id&quot;,
    name: &quot;id&quot;,
    component: defineFormComponent((ctx) =&amp;gt; 
      &amp;lt;a-input 
        v-model:value={ctx.formState.id}
        ref={(el) =&amp;gt; ctx.inputRef = el}
      /&amp;gt;
    ),
    extraProps: {
      formState,
      inputRef: null as HTMLInputElement | null
    }
  }
] as const)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;函数工厂 = 普通函数 + 对象字面量 + 闭包&lt;/strong&gt;，它以函数式的方式实现对象创建，是现代 JavaScript 中践行&lt;strong&gt;组合优于继承&lt;/strong&gt;理念的典型实践。是构建可维护的代码结构的不二之选。&lt;/p&gt;
</content:encoded></item><item><title>文件同步工具推荐</title><link>https://fuwari.vercel.app/posts/2025%E5%B9%B4/%E6%96%87%E4%BB%B6%E5%90%8C%E6%AD%A5%E5%B7%A5%E5%85%B7%E6%8E%A8%E8%8D%90/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2025%E5%B9%B4/%E6%96%87%E4%BB%B6%E5%90%8C%E6%AD%A5%E5%B7%A5%E5%85%B7%E6%8E%A8%E8%8D%90/</guid><pubDate>Tue, 25 Feb 2025 20:33:43 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;无论是个人学习办公还是团队合作，备份重要资料、跨设备共享文件都是保障效率的核心环节，也因此拥有一套完善的文件存储传输体系，可以让你在遇到诸如数据迁移、备份等复杂场景中，实现信息的高效流转与管理。&lt;/p&gt;
&lt;p&gt;为此我结合自己实际的使用体验，推荐以下四款文件同步、传输工具，帮助大家构建适合自己文件存储传输体系。&lt;/p&gt;
&lt;h2&gt;FolderSync&lt;/h2&gt;
&lt;p&gt;推荐度：★★★★☆（4/5）​​&lt;/p&gt;
&lt;p&gt;FolderSync 是一款专为 ​Android 系统​设计的文件同步工具，支持将手机中的文件自动同步到云端存储（如坚果云、Dropbox 等）或本地设备。它具备实时同步、定时同步、手动同步等多种模式，并支持文件过滤、忽略隐藏文件、保持文件结构等高级功能。用户可自定义同步规则，如仅在 Wi-Fi 环境下同步，或设置同步频率（每分钟到每月）。&lt;/p&gt;
&lt;p&gt;注意：FolderSync 仅支持 Android 系统，免费版足够满足日常使用需求，高级版本主要是免除广告。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://foldersync.io/&quot;&gt;官方网站&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;支持协议
&lt;ul&gt;
&lt;li&gt;FTP&lt;/li&gt;
&lt;li&gt;SFTP&lt;/li&gt;
&lt;li&gt;WebDAV&lt;/li&gt;
&lt;li&gt;SMB&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;支持平台
&lt;ul&gt;
&lt;li&gt;Android
​&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;FreeFileSync&lt;/h2&gt;
&lt;p&gt;推荐度：★★★★★（5/5）​​&lt;/p&gt;
&lt;p&gt;FreeFileSync 是一款开源免费​的文件同步与备份工具，支持按文件内容、修改时间、大小等多种方式对比差异，仅传输必要数据以实现高效同步，是桌面端文件同步的标杆工具。&lt;/p&gt;
&lt;p&gt;主要使用场景：备份照片、文档到移动硬盘或 NAS。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://freefilesync.org/&quot;&gt;官方网站&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开源地址： &lt;a href=&quot;https://freefilesync.org/download.php&quot;&gt;开源代码下载&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;支持协议
&lt;ul&gt;
&lt;li&gt;FTP&lt;/li&gt;
&lt;li&gt;SFTP&lt;/li&gt;
&lt;li&gt;SMB&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;支持平台
&lt;ul&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;MacOS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;WinSCP&lt;/h2&gt;
&lt;p&gt;推荐度：★★★★★（5/5）​​&lt;/p&gt;
&lt;p&gt;WinSCP 是一款 免费开源​的图形化文件传输工具，专为 Windows 系统设计，支持通过 ​SFTP、SCP、FTP、WebDAV​ 等协议在本地计算机与远程服务器之间安全传输文件。其核心功能包括文件上传/下载、目录同步、远程文件编辑（内置文本编辑器）、脚本自动化等。适用于网站开发、服务器运维、数据备份等场景，尤其适合需要加密传输的敏感数据操作。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://winscp.net/eng/index.php&quot;&gt;官方网站&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开源地址： &lt;a href=&quot;https://github.com/winscp/winscp&quot;&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;支持协议
&lt;ul&gt;
&lt;li&gt;FTP&lt;/li&gt;
&lt;li&gt;SFTP&lt;/li&gt;
&lt;li&gt;WebDAV&lt;/li&gt;
&lt;li&gt;SCP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;支持平台
&lt;ul&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SyncThing&lt;/h2&gt;
&lt;p&gt;推荐度：★★★★☆（4/5）​​&lt;/p&gt;
&lt;p&gt;Syncthing 是一款开源、去中心化的文件同步工具，基于点对点（P2P）技术实现设备间的实时文件同步。适合小范围跨设备同步文件，替代网盘实现私有云。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://syncthing.net/&quot;&gt;官方网站&lt;/a&gt;
​&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开源地址： &lt;a href=&quot;https://github.com/syncthing/syncthing&quot;&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;支持协议
&lt;ul&gt;
&lt;li&gt;P2P&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;支持平台
&lt;ul&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;macOS&lt;/li&gt;
&lt;li&gt;Android&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>使用 CSS3DRenderer 制作3D穿梭效果</title><link>https://fuwari.vercel.app/posts/2025%E5%B9%B4/%E4%BD%BF%E7%94%A8-css3drenderer-%E5%88%B6%E4%BD%9C3d%E7%A9%BF%E6%A2%AD%E6%95%88%E6%9E%9C/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2025%E5%B9%B4/%E4%BD%BF%E7%94%A8-css3drenderer-%E5%88%B6%E4%BD%9C3d%E7%A9%BF%E6%A2%AD%E6%95%88%E6%9E%9C/</guid><pubDate>Mon, 27 Jan 2025 22:51:39 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;当你想要在浏览器中实现元素由远到近的动画效果的时候，你可以使用 &lt;a href=&quot;https://threejs.org/&quot;&gt;Three.js&lt;/a&gt; 的 &lt;a href=&quot;https://threejs.org/docs/examples/zh/renderers/CSS3DRenderer.html&quot;&gt;CSS3DRenderer&lt;/a&gt; 来模拟 3D 变换并将其渲染到 HTML DOM 元素上。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;从官方文档的描述中我们可以得知 &lt;code&gt;CSS3DRenderer&lt;/code&gt; 的本质是通过CSS3的 &lt;a href=&quot;https://www.w3schools.com/cssref/css3_pr_transform.asp&quot;&gt;transform&lt;/a&gt; 属性， 将层级的3D变换应用到DOM元素上，这样就可以直接在 HTML DOM 元素上做出 3D 的效果。&lt;/p&gt;
&lt;h3&gt;NPM 安装&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm install three
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;导入项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import * as THREE from &quot;three&quot;;
import { CSS3DRenderer, CSS3DObject } from &quot;three/examples/jsm/renderers/CSS3DRenderer.js&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3D穿梭效果示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import * as THREE from &quot;three&quot;;
import { CSS3DRenderer, CSS3DObject } from &quot;three/examples/jsm/renderers/CSS3DRenderer.js&quot;;

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
const renderer = new CSS3DRenderer();
const objects: CSS3DObject[] = [];

initScene();
animate();

function initScene() {
  let sceneContainer = document.getElementById(&quot;sceneContainer&quot;);
  if (!sceneContainer) return;
  scene.clear();
  camera.position.set(0, 0, 3000);

  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.domElement.style.position = &quot;absolute&quot;;
  renderer.domElement.style.top = &quot;0&quot;;
  sceneContainer.appendChild(renderer.domElement);

  for (let i = 0; i &amp;lt; 50; i++) {
    createItem(i);
  }
}

function createItem(index: number) {
  const element = document.createElement(&quot;img&quot;);
  element.style.borderRadius = &quot;50%&quot;;
  element.style.width = &quot;30px&quot;;
  element.style.height = &quot;30px&quot;;
  element.style.background = &quot;gray&quot;;
  element.id = `item-${index}`;

  const cssObject = new CSS3DObject(element);
  cssObject.position.set(Math.random() * 200 - 100, Math.random() * 200 - 100, Math.random() * -2000 - 500);

  scene.add(cssObject);
  objects.push(cssObject);
}

function animate() {
  objects.forEach((obj) =&amp;gt; {
    obj.position.z += 10;

    const distance = Math.abs(camera.position.z - obj.position.z);
    const scale = Math.max(0.1, 1 - distance / 5000);
    obj.element.style.width = `${30 * scale}px`;
    obj.element.style.height = `${30 * scale}px`;
    obj.element.style.opacity = `${Math.max(0, 1 - distance / 3000)}`;

    if (obj.position.z &amp;gt; 3000) {
      obj.element.style.opacity = &quot;0&quot;;
      obj.position.set(Math.random() * 200 - 100, Math.random() * 200 - 100, Math.random() * -2000 - 500);
    }
  });
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>在浏览器中实时获取声音频率</title><link>https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E5%9C%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E4%B8%AD%E5%AE%9E%E6%97%B6%E8%8E%B7%E5%8F%96%E5%A3%B0%E9%9F%B3%E9%A2%91%E7%8E%87/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E5%9C%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E4%B8%AD%E5%AE%9E%E6%97%B6%E8%8E%B7%E5%8F%96%E5%A3%B0%E9%9F%B3%E9%A2%91%E7%8E%87/</guid><pubDate>Tue, 31 Dec 2024 20:21:31 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;当你在给乐器调音时、又或者在小提琴、二胡这样的无品格乐器上演奏但无法确定音准时，你可以借助电脑或者移动设备的麦克风采集当前演奏乐器的声音，并通过算法实时分析声音的基音音高以及响度，这可以更好地帮助我们把握乐器的音准。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;h3&gt;在浏览器获取麦克风权限&lt;/h3&gt;
&lt;p&gt;在这里我们使用 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Learn_web_development/Extensions/Client-side_APIs/Introduction&quot;&gt;Web Api&lt;/a&gt; 中的 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia&quot;&gt;getUserMedia&lt;/a&gt; 方法来获取设备麦克风。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async function getMicroPhoneDevice() {
  if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
    console.log(&quot;浏览器不支持音频输入&quot;);
    return;
  }

  try {
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
    const audioContext = new window.AudioContext();
    const mediaStreamSource = audioContext.createMediaStreamSource(stream);

    const processor = audioContext.createScriptProcessor(4096, 1, 1);
    mediaStreamSource.connect(processor);
    processor.connect(audioContext.destination);
    processor.onaudioprocess = (e) =&amp;gt; {
      let inputBuffer = e.inputBuffer.getChannelData(0); // 读取第一个声道的数据
      console.log(inputBuffer);
    };
  } catch (error) {
    console.error(&quot;音频解析失败，请检查麦克风或重新尝试！&quot;, error);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的 &lt;code&gt;inputBuffer&lt;/code&gt; 就是麦克风实时获取的音频数据，格式为 Buffer 数组。&lt;/p&gt;
&lt;h3&gt;处理获取的音频数据&lt;/h3&gt;
&lt;p&gt;现在我们得到了原始的音频数据，如果想要知道这段音频的频率，我们还需要使用自相关算法得出音频的基音以及周期。&lt;/p&gt;
&lt;h4&gt;1.首先使用 RMS 计算信号强度&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;let rms = 0;
let SIZE = inputBuffer.length;
for (let i = 0; i &amp;lt; SIZE; i++) {
  let val = inputBuffer[i];
  rms += val * val;
}
rms = Math.sqrt(rms / SIZE);
if (rms &amp;lt; 0.01) {
  console.log(&quot;信号强度不足&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.筛除噪声&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;let startIndex = 0,
  endIndex = SIZE - 1,
  threshold = 0.2;

for (let i = 0; i &amp;lt; SIZE / 2; i++) {
  if (Math.abs(inputBuffer[i]) &amp;lt; threshold) {
    startIndex = i;
    break;
  }
}
for (let i = 1; i &amp;lt; SIZE / 2; i++) {
  if (Math.abs(inputBuffer[SIZE - i]) &amp;lt; threshold) {
    endIndex = SIZE - i;
    break;
  }
}

inputBuffer = inputBuffer.slice(startIndex, endIndex);
SIZE = inputBuffer.length;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.自相关计算（Autocorrelation Function, ACF）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;let autocorrelation = new Array(SIZE).fill(0);

for (let i = 0; i &amp;lt; SIZE; i++) {
  for (let j = 0; j &amp;lt; SIZE - i; j++) {
    autocorrelation[i] += inputBuffer[j] * inputBuffer[j + i];
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4.峰值检测&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;let peakIndex = 0;
while (autocorrelation[peakIndex] &amp;gt; autocorrelation[peakIndex + 1]) peakIndex++;

let maxCorrelationValue = -1,
  maxCorrelationPosition = -1;
for (let i = peakIndex; i &amp;lt; SIZE; i++) {
  if (autocorrelation[i] &amp;gt; maxCorrelationValue) {
    maxCorrelationValue = autocorrelation[i];
    maxCorrelationPosition = i;
  }
}

let period = maxCorrelationPosition;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5.二次插值提高精度&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;let prevValue = autocorrelation[period - 1],
  currentValue = autocorrelation[period],
  nextValue = autocorrelation[period + 1];
let quadraticA = (prevValue + nextValue - 2 * currentValue) / 2;
let quadraticB = (nextValue - prevValue) / 2;
if (quadraticA) period = period - quadraticB / (2 * quadraticA);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;6.频率计算&lt;/h4&gt;
&lt;p&gt;频率计算的公式为：f = 采样率 ​ / 周期&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let sampleRate = audioContext.sampleRate;
let frequency = sampleRate / period;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;完整代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;async function getMicroPhoneDevice() {
  if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
    console.log(&quot;浏览器不支持音频输入&quot;);
    return;
  }

  try {
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
    const audioContext = new window.AudioContext();
    const mediaStreamSource = audioContext.createMediaStreamSource(stream);

    const processor = audioContext.createScriptProcessor(4096, 1, 1);
    mediaStreamSource.connect(processor);
    processor.connect(audioContext.destination);
    processor.onaudioprocess = (e) =&amp;gt; {
      let inputBuffer = e.inputBuffer.getChannelData(0); // 读取第一个声道的数据
      console.log(`音频频率为：${getFrequency(inputBuffer, audioContext.sampleRate)}`);
    };
  } catch (error) {
    console.error(&quot;音频解析失败，请检查麦克风或重新尝试！&quot;, error);
  }
}

function getFrequency(inputBuffer, sampleRate) {
  let rms = 0;
  let SIZE = inputBuffer.length;
  for (let i = 0; i &amp;lt; SIZE; i++) {
    let val = inputBuffer[i];
    rms += val * val;
  }
  rms = Math.sqrt(rms / SIZE);
  if (rms &amp;lt; 0.01) {
    console.log(&quot;信号强度不足&quot;);
  }
  let startIndex = 0,
    endIndex = SIZE - 1,
    threshold = 0.2;

  for (let i = 0; i &amp;lt; SIZE / 2; i++) {
    if (Math.abs(inputBuffer[i]) &amp;lt; threshold) {
      startIndex = i;
      break;
    }
  }
  for (let i = 1; i &amp;lt; SIZE / 2; i++) {
    if (Math.abs(inputBuffer[SIZE - i]) &amp;lt; threshold) {
      endIndex = SIZE - i;
      break;
    }
  }

  inputBuffer = inputBuffer.slice(startIndex, endIndex);
  SIZE = inputBuffer.length;

  let autocorrelation = new Array(SIZE).fill(0);

  for (let i = 0; i &amp;lt; SIZE; i++) {
    for (let j = 0; j &amp;lt; SIZE - i; j++) {
      autocorrelation[i] += inputBuffer[j] * inputBuffer[j + i];
    }
  }

  let peakIndex = 0;
  while (autocorrelation[peakIndex] &amp;gt; autocorrelation[peakIndex + 1]) peakIndex++;

  let maxCorrelationValue = -1,
    maxCorrelationPosition = -1;
  for (let i = peakIndex; i &amp;lt; SIZE; i++) {
    if (autocorrelation[i] &amp;gt; maxCorrelationValue) {
      maxCorrelationValue = autocorrelation[i];
      maxCorrelationPosition = i;
    }
  }

  let period = maxCorrelationPosition;
  let prevValue = autocorrelation[period - 1],
    currentValue = autocorrelation[period],
    nextValue = autocorrelation[period + 1];
  let quadraticA = (prevValue + nextValue - 2 * currentValue) / 2;
  let quadraticB = (nextValue - prevValue) / 2;
  if (quadraticA) period = period - quadraticB / (2 * quadraticA);
  if (period === 0) return null;
  let frequency = sampleRate / period;
  return frequency;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>批量把doc格式文档转换为docx文档</title><link>https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E6%89%B9%E9%87%8F%E6%8A%8Adoc%E6%A0%BC%E5%BC%8F%E6%96%87%E6%A1%A3%E8%BD%AC%E6%8D%A2%E4%B8%BAdocx%E6%96%87%E6%A1%A3/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E6%89%B9%E9%87%8F%E6%8A%8Adoc%E6%A0%BC%E5%BC%8F%E6%96%87%E6%A1%A3%E8%BD%AC%E6%8D%A2%E4%B8%BAdocx%E6%96%87%E6%A1%A3/</guid><pubDate>Wed, 20 Nov 2024 21:50:33 GMT</pubDate><content:encoded>&lt;h2&gt;引言&lt;/h2&gt;
&lt;p&gt;在使用 &lt;a href=&quot;https://github.com/mwilliamson/mammoth.js&quot;&gt;mammoth.js&lt;/a&gt;  进行文档读取的时候，如果目标文件不是 docx 格式的文档则无法进行解析，因此我们需要使用 &lt;a href=&quot;https://www.microsoft.com/zh-cn/microsoft-365/microsoft-office&quot;&gt;Microsoft Office&lt;/a&gt; 或者 &lt;a href=&quot;https://www.wps.com/&quot;&gt;WPS&lt;/a&gt; 将文件重新导出进行格式转换，但是如果需要进行格式转换的文件很多的话，这样做难免效率过低，所以我们可以考虑在 Microsoft Office 中使用 VBA(&lt;a href=&quot;https://learn.microsoft.com/zh-cn/office/vba/api/overview&quot;&gt;Visual Basic for Applications&lt;/a&gt;) 来进行脚本处理。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;首先第一步我们要创建一个 &lt;code&gt;.docm&lt;/code&gt; 格式的文档并打开&lt;/p&gt;
&lt;h3&gt;编写脚本&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;打开文件后我们使用快捷键 &lt;code&gt;alt+F11&lt;/code&gt; 打开 VBA 窗口（如果宏被禁用的话先启用宏）&lt;/li&gt;
&lt;li&gt;在菜单栏找到&lt;strong&gt;插入(Insert)&lt;/strong&gt; 然后点击&lt;strong&gt;模块(Module)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;在新插入的模块内填写以下代码&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;Sub TranslateDocIntoDocx()
    Dim objWordApp As New Word.Application
    Dim objDoc As Word.Document
    Dim strFolder As String
    Dim strFile As String
    Dim strOutputFolder As String
    Dim count As Long
    Dim fso As Object

    &apos; 初始化计数器
    count = 0

    &apos; 获取当前文档的路径
    strFolder = ThisDocument.Path &amp;amp; &quot;\&quot;
    strOutputFolder = strFolder &amp;amp; &quot;output\&quot;

    Set fso = CreateObject(&quot;Scripting.FileSystemObject&quot;)
    &apos; 检查 output 文件夹是否存在，如果不存在则创建
    If Not fso.FolderExists(strOutputFolder) Then
        fso.CreateFolder strOutputFolder
    End If

    &apos; 遍历 .doc 文件并转换为 .docx
    strFile = Dir(strFolder &amp;amp; &quot;*.doc&quot;)
    Do While strFile &amp;lt;&amp;gt; &quot;&quot;
        With objWordApp
            Set objDoc = .Documents.Open(strFolder &amp;amp; strFile, AddToRecentFiles:=False, ReadOnly:=True, Visible:=False)
            objDoc.SaveAs2 strOutputFolder &amp;amp; fso.GetBaseName(strFile) &amp;amp; &quot;.docx&quot;, FileFormat:=wdFormatXMLDocument
            objDoc.Close
        End With

        count = count + 1

        &apos; 获取下一个文件
        strFile = Dir()
    Loop

    &apos; 清理对象
    Set objDoc = Nothing
    Set objWordApp = Nothing
    Set fso = Nothing

    &apos; 显示转换结果
    MsgBox &quot;执行完成，共转换了: &quot; &amp;amp; count &amp;amp; &quot; 个文件&quot;
End Sub
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;运行脚本&lt;/h3&gt;
&lt;p&gt;在我们创建的 &lt;code&gt;.docm&lt;/code&gt; 格式的文档的根目录放一些 &lt;code&gt;.doc&lt;/code&gt; 格式的文件后在 VBA 窗口运行写好模块&lt;/p&gt;
&lt;p&gt;如果成功执行，模块脚本执行结束后会弹窗提示&lt;code&gt;执行完成，共转换了: n 个文件&lt;/code&gt;，转换好的文件会放在根目录新创建的 &lt;code&gt;output&lt;/code&gt; 文件夹中&lt;/p&gt;
</content:encoded></item><item><title>自建 Anki 同步服务器</title><link>https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E8%87%AA%E5%BB%BA-anki-%E5%90%8C%E6%AD%A5%E6%9C%8D%E5%8A%A1%E5%99%A8/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E8%87%AA%E5%BB%BA-anki-%E5%90%8C%E6%AD%A5%E6%9C%8D%E5%8A%A1%E5%99%A8/</guid><pubDate>Thu, 10 Oct 2024 23:49:23 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在使用 Anki 官方的同步服务器时，时常遇到连接超时以及同步速度过慢的情况，好在从官方文档 &lt;a href=&quot;https://docs.ankiweb.net/sync-server.html&quot;&gt;sync-server&lt;/a&gt; 中可知，从 2.1.57+ 版本开始的桌面版 Anki 已经内置了同步服务器，只需要简单的几行命令就可以创建一个私人同步服务。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;h3&gt;下载桌面版 Anki&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ankitects/anki/releases&quot;&gt;Github 仓库&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;考虑到市面上大部分服务器都基于 Linux 发行版所以该教程也只介绍如何在 Linux 系统中进行部署&lt;/p&gt;
&lt;p&gt;首先检查自己的 Linux 系统版本以及CPU架构&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uname -a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据终端输出系统版本及架构的选择最适合自己的安装包&lt;/p&gt;
&lt;p&gt;在这里我们选 &lt;code&gt;linux-qt5.tar.zst&lt;/code&gt; 为后缀的压缩包来安装，可以看到文件格式是 tar.zst，这是 Facebook推出的一种无损压缩格式，全称 Zstandard (ZSTD) ，压缩率更大速度更快，但是需要安装 &lt;a href=&quot;https://github.com/facebook/zstd&quot;&gt;ZSTD&lt;/a&gt;
才能解压。&lt;/p&gt;
&lt;p&gt;考虑到该教程主要介绍如何搭建专属于自己的anki服务器，所以不展开讲述 ZSTD 安装相关步骤。&lt;/p&gt;
&lt;p&gt;先下载 &lt;code&gt;anki-2.1.57-linux-qt5.tar.zst&lt;/code&gt; 或者以上版本的压缩包&lt;/p&gt;
&lt;p&gt;我们就先在 windows 系统的电脑上先解压  tar.zst 格式的资源，再用tar进行打包上传到 Linux 服务器来简化这一步&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tar -zcvf  anki-2.1.57-linux-qt5.tar.gz anki-2.1.57-linux-qt5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将重新压缩好的资源上传到 Linux 服务器并选择合适的目录进行解压，解压完成后进入项目根目录&lt;/p&gt;
&lt;h4&gt;运行同步服务器&lt;/h4&gt;
&lt;p&gt;输入以下指令测试 Anki 服务器是否成功启动&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SYNC_USER1=YourUsername:YourPassword ./anki-2.1.57-linux-qt5/anki --syncserver
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果弹出以下内容则证明服务器启动成功&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INFO listening addr=0.0.0.0:8080
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;待打开对应端口的防火墙后测试服务器是否能正常连接&lt;/p&gt;
&lt;p&gt;启动 Anki 客户端 —— 在客户端打开设置 —— 网络 —— 输入你的服务器ip+端口&lt;/p&gt;
&lt;p&gt;保存后尝试使用 &lt;code&gt;SYNC_USER1&lt;/code&gt; 里面的账号与密码进行登录&lt;/p&gt;
&lt;p&gt;如果登录成功则私人同步服务器运行成功&lt;/p&gt;
&lt;h3&gt;使用守护进程持久运行&lt;/h3&gt;
&lt;p&gt;考虑到同步服务器需要在服务器后台保持常驻，所以我们使用PM2进行进程守护：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//ecosyste.config.js
module.exports = {
  apps: [{
    name: &quot;anki-server&quot;,
    script: &quot;./anki-2.1.57-linux-qt5/anki --syncserver&quot;,
    env: {
      SYNC_PORT: 8080,
      SYNC_USER1: &quot;YourUsername:YourPassword&quot;,
    },
    out_file: &quot;./logs/anki-server.log&quot;,
  }]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将 &lt;code&gt;ecosyste.config.js&lt;/code&gt; 文件放到 anki 服务器文件根目录，并输入以下指令启动&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pm2 start ecosyste.config.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样我们的 Anki 私人同步服务器就部署完成了。&lt;/p&gt;
&lt;h3&gt;使用域名作为地址&lt;/h3&gt;
&lt;p&gt;如果你想要用域名作为地址你可以使用 nginx 进行代理转发&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    server {
        listen 443 ssl;
        server_name yourdomain.com;
        root html;
        index index.html index.htm;
        location / {
            proxy_pass http://127.0.0.1:8080/;
        }
    }
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Starship 美化终端</title><link>https://fuwari.vercel.app/posts/2024%E5%B9%B4/starship-%E7%BE%8E%E5%8C%96%E7%BB%88%E7%AB%AF/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2024%E5%B9%B4/starship-%E7%BE%8E%E5%8C%96%E7%BB%88%E7%AB%AF/</guid><pubDate>Mon, 23 Sep 2024 14:59:07 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;随着 Windows 11 的逐渐稳定，微软也将 &lt;a href=&quot;https://apps.microsoft.com/detail/9n0dx20hk701?rtc=1&amp;amp;hl=zh-cn&amp;amp;gl=cn&quot;&gt;Windows Terminal&lt;/a&gt; 收录到新版本的Windows 操作系统中，使其作为 Windows 的默认终端界面。&lt;/p&gt;
&lt;p&gt;虽然 Windows Terminal 已经做得很好了，但是相比于主流 Linux 发行版和 MAC 的终端界面，Windows Terminal 可自定义的样式还是太少了还缺少对命令行输出的美化，因此我选择使用 Starship 来补齐这块短板，定制一套简洁、美观且高效的终端。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;h4&gt;下载并安装 Starship&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://starship.rs/zh-CN/&quot;&gt;官方网站&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/starship/starship/releases/latest&quot;&gt;下载地址&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;配置环境&lt;/h4&gt;
&lt;p&gt;下载并安装 Starship 后将以下内容添加到 PowerShell 配置文件的末尾（通过运行 &lt;code&gt;$PROFILE&lt;/code&gt; 来获取配置文件的路径）&lt;/p&gt;
&lt;p&gt;默认返回的文件路径&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\Users\用户名\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将以下内容粘贴进 &lt;code&gt;Microsoft.PowerShell_profile.ps1&lt;/code&gt; 文件内&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Invoke-Expression (&amp;amp;starship init powershell)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时再重新启动 PowerShell 可能会出现&lt;/p&gt;
&lt;p&gt;&lt;code&gt;powershell. : 无法加载文件 C:\Users\user\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1，因为在此系统上禁止运行脚本。 &lt;/code&gt;&lt;/p&gt;
&lt;p&gt;不用担心，我们通过管理员权限运行 PowerShell，并输入以下内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; set-ExecutionPolicy RemoteSigned
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果想要改回默认则输入以下内容即可&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set-executionpolicy restricted
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再次开启 PowerShell 就可以看到美化后的界面了&lt;/p&gt;
&lt;h4&gt;配置文件路径&lt;/h4&gt;
&lt;p&gt;默认的配置文件路径在 &lt;code&gt;C:\Users\用户名\.config\starship.toml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;您也可以使用 &lt;code&gt;STARSHIP_CONFIG&lt;/code&gt; 环境变量更改默认配置文件的位置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export STARSHIP_CONFIG=~/example/non/default/path/starship.toml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 PowerShell (Windows) 中，在 &lt;code&gt;$PROFILE&lt;/code&gt; 中添加下面的代码行能达到同样的效果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ENV:STARSHIP_CONFIG = &quot;$HOME\example\non\default\path\starship.toml&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;配置分享&lt;/h4&gt;
&lt;p&gt;在这里附上我的 Starship 配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/starship.toml

format = &quot;&quot;&quot;
$time \
$username\
$directory \
$character\
&quot;&quot;&quot;

add_newline = false

[battery]
full_symbol = &quot;🔋&quot;
charging_symbol = &quot;🔌&quot;
discharging_symbol = &quot;⚡&quot;

[[battery.display]]
threshold = 30
style = &quot;bold red&quot;

[character]
error_symbol = &quot;[❌](bold red)&quot;
#success_symbol = &quot;[⚡](bold green)&quot; 
#success_symbol = &quot;[✨](bold green)&quot; 
success_symbol = &quot;[💖](bold green)&quot; 

[cmd_duration]
min_time = 0  # Show command duration over 10,000 milliseconds (=10 sec)
format = &quot; took [$duration]($style)&quot;

[directory]
truncation_length = 5
format = &quot;[$path]($style)[$lock_symbol]($lock_style)&quot;

[git_branch]
format = &quot; [$symbol$branch]($style) &quot;
symbol = &quot;🍣 &quot;
style = &quot;bold yellow&quot;

[git_commit]
commit_hash_length = 8
style = &quot;bold white&quot;

[git_state]
format = &apos;[\($state( $progress_current of $progress_total)\)]($style) &apos;

[git_status]
conflicted = &quot;⚔️ &quot;
ahead = &quot;🏎️ 💨 ×${count}&quot;
behind = &quot;🐢 ×${count}&quot;
diverged = &quot;🔱 🏎️ 💨 ×${ahead_count} 🐢 ×${behind_count}&quot;
untracked = &quot;🛤️  ×${count}&quot;
stashed = &quot;📦 &quot;
modified = &quot;📝 ×${count}&quot;
staged = &quot;🗃️  ×${count}&quot;
renamed = &quot;📛 ×${count}&quot;
deleted = &quot;🗑️  ×${count}&quot;
style = &quot;bright-white&quot;
format = &quot;$all_status$ahead_behind&quot;

[hostname]
ssh_only = false
format = &quot;&amp;lt;[$hostname]($style)&amp;gt;&quot;
trim_at = &quot;-&quot;
style = &quot;bold dimmed white&quot;
disabled = true

[julia]
format = &quot;[$symbol$version]($style) &quot;
symbol = &quot;ஃ &quot;
style = &quot;bold green&quot;

[memory_usage]
format = &quot;$symbol[${ram}( | ${swap})]($style) &quot;
threshold = 70
style = &quot;bold dimmed white&quot;
disabled = false


[username]
style_user = &quot;green&quot;
show_always = true
format = &quot;[$user]($style) &quot;

[time]
disabled = false
time_format = &quot;%R&quot; # Hour:Minute Format
format = &apos;[$time]($style)&apos;
style = &quot;bold white&quot;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>多邻国666天连胜纪念</title><link>https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E5%A4%9A%E9%82%BB%E5%9B%BD666%E5%A4%A9%E8%BF%9E%E8%83%9C%E7%BA%AA%E5%BF%B5/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E5%A4%9A%E9%82%BB%E5%9B%BD666%E5%A4%A9%E8%BF%9E%E8%83%9C%E7%BA%AA%E5%BF%B5/</guid><pubDate>Thu, 15 Aug 2024 21:24:59 GMT</pubDate><content:encoded>&lt;h2&gt;初次体验多邻国&lt;/h2&gt;
&lt;p&gt;还记得第一次用多邻国是20年5月，当时是以随便玩玩的心态下载的，没想到在随后的每天要是不学能收到好几条来自多邻国的短信提醒我该学英语了（&lt;s&gt;多儿：不学是吧，看我短信炸死你&lt;/s&gt;），也就这样每天晚上被迫上线练习，但也慢慢也形成了每天打卡学习的习惯。&lt;/p&gt;
&lt;p&gt;随着学习的深入，我给自己每天的目标也从学两个单元涨到了每天学四个单元，不得不说连续的打卡让我明显感受到自己英语水平的提升，看着一个又一个单元学到满杯自己也感觉充满成就。&lt;/p&gt;
&lt;h3&gt;失去动力&lt;/h3&gt;
&lt;p&gt;终于在历经五百多天后，我把多邻国所有的单元都学成了金杯，段位也到了最高点的钻石，但是等那一刻的喜悦结束后，我发现我同时也失去了奋斗目标，全成就全课程达成的我就仿佛游戏已经玩到通关，也因此放弃了继续打卡多邻国。&lt;/p&gt;
&lt;h2&gt;再次回归&lt;/h2&gt;
&lt;p&gt;在达成全成就的一年后我再次打开了这款软件，没想到我以前全通关的账号现在只有课程总量3分之1的进度，还多了一个叫Super的会员专享，本着好习惯不能丢的想法，我又重新开始每天打开多邻国。&lt;/p&gt;
&lt;h3&gt;新变化&lt;/h3&gt;
&lt;p&gt;时隔一年能明显感受到多邻国的变化，最明显的就是激励机制。最开始的自律是通过短信加通知界面的轮流轰炸来提醒你，现在是增加了每个课程之间的奖励宝箱，其次就是在早晨和下午做题会送双倍经验宝箱。&lt;/p&gt;
&lt;p&gt;这些激励机制无疑增加了学习的动力，但也让经验迅速贬值，还记得我最开始使用多邻国做完一个单元全对的情况下才只有15经验，一天做4个单元最多也才60经验，新版本后配合双倍经验做单词配对，哪怕是第一关就可以给到40经验，随着后面关卡的递增，经验最高可以给到120。只是使用了一个月新版多邻国，我就获得了五千多经验，要知道以前我花了五百多天学完所有课程也才勉勉强强到2万经验。&lt;/p&gt;
&lt;h2&gt;现状&lt;/h2&gt;
&lt;p&gt;不知不觉也已经使用新版本的多邻国666天了，相比最开始使用多邻国，我能感受到现在早已没了最开始的那种激情，每天上线只是为了把任务做完，有时候为了经验榜的排名重复刷很多遍几乎没有提升的单词配对。&lt;/p&gt;
&lt;p&gt;我也知道这和我最开始的学习背道而驰，似乎我只是在勉强维持一种表面的坚持，害怕一旦停止打卡，这个好不容易养成的习惯就会彻底消失。但正因为如此，我反而变得越来越依赖这种机械化的操作，逐渐失去了对学习本身的热情。&lt;/p&gt;
&lt;p&gt;我知道这不是理想的状态。或许，我需要找到新的方式，重新点燃内心对学习的热情，或者至少，找到一种平衡，在有限的精力中，既能维持习惯，又能感受到学习的真正意义。&lt;/p&gt;
</content:encoded></item><item><title>Web Speech API 网页语音 API</title><link>https://fuwari.vercel.app/posts/2024%E5%B9%B4/web-speech-api-%E7%BD%91%E9%A1%B5%E8%AF%AD%E9%9F%B3-api/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2024%E5%B9%B4/web-speech-api-%E7%BD%91%E9%A1%B5%E8%AF%AD%E9%9F%B3-api/</guid><pubDate>Wed, 03 Jul 2024 22:16:49 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;Web Speech API（网页语音 API）是一组用于实现语音识别（Speech Recognition）和语音合成（Speech Synthesis）功能的浏览器 API。它允许开发者在网页上利用语音交互，使用户能够通过语音输入和语音输出与网页进行交互。&lt;/p&gt;
&lt;p&gt;Web Speech API 提供了以下两个主要的功能模块：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;语音合成（Speech Synthesis）：允许将文本转换为语音输出。通过使用 &lt;code&gt;SpeechSynthesis&lt;/code&gt; 对象，网页可以将文本转换为语音，并播放出来。这个功能模块依赖于底层系统的语音合成引擎。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;语音识别（Speech Recognition）：允许将用户的语音输入转换为文本。通过使用 &lt;code&gt;SpeechRecognition&lt;/code&gt; 对象，网页可以监听用户的语音输入，并将其转换为文本，以便进行语音命令、语音搜索、语音填写表单等应用。这个功能模块依赖于底层系统的语音识别引擎。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/SpeechSynthesis&quot;&gt;技术文档&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;h3&gt;语音合成&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;function speak(text) {
  let textContent = new SpeechSynthesisUtterance(text);
  speechSynthesis.speak(textContent);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;属性&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;voice&lt;/code&gt; 设置将用于说出语音的&lt;strong&gt;声音&lt;/strong&gt;，默认 &lt;code&gt;getVoices()&lt;/code&gt; 方法获取当前设备支持的语音选项数组的第一个。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rate&lt;/code&gt; 设置将用于说出语音的&lt;strong&gt;速度&lt;/strong&gt;，默认 1，范围 0-2。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pitch&lt;/code&gt; 设置将用于说出语音的&lt;strong&gt;音调&lt;/strong&gt;，默认 1，范围 0-2。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;volume&lt;/code&gt; 设置将用于说出语音的&lt;strong&gt;音量&lt;/strong&gt;，默认1，范围 0-1。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;完整示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
    &amp;lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot; /&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&amp;gt;
    &amp;lt;title&amp;gt;Document&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;button onclick=&quot;speak(&apos;hello world&apos;)&quot;&amp;gt;Speak&amp;lt;/button&amp;gt;
    &amp;lt;select id=&quot;voiceSelect&quot;&amp;gt;&amp;lt;/select&amp;gt;
  &amp;lt;/body&amp;gt;

  &amp;lt;script&amp;gt;
    let utterance = new SpeechSynthesisUtterance(&quot;hello&quot;);
    let voices = [];
    let select = document.getElementById(&quot;voiceSelect&quot;);
    speechSynthesis.onvoiceschanged = function (event) {
      voices = speechSynthesis.getVoices();
      createVoiceOption(voices);
      
    };
    select.onchange = function () {
      utterance.voice = voices.find(
        (voice) =&amp;gt; voice.name === select.selectedOptions[0].getAttribute(&quot;data-name&quot;)
      );
      utterance.lang = select.selectedOptions[0].getAttribute(&quot;data-lang&quot;);
    };
    function createVoiceOption(voices) {
      for (let i = 0; i &amp;lt; voices.length; i++) {
        const option = document.createElement(&quot;option&quot;);
        option.textContent = `${voices[i].name} (${voices[i].lang})`;
        if (voices[i].default) {
          option.textContent += &quot; — DEFAULT&quot;;
        }
        option.setAttribute(&quot;data-lang&quot;, voices[i].lang);
        option.setAttribute(&quot;data-name&quot;, voices[i].name);
        document.getElementById(&quot;voiceSelect&quot;).appendChild(option);
      }
    }
    function speak(text) {
      utterance.text=text;
      speechSynthesis.speak(utterance);
    }
  &amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;语音识别&lt;/h3&gt;
&lt;p&gt;完整示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;语音识别示例&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;button id=&quot;startButton&quot;&amp;gt;开始识别&amp;lt;/button&amp;gt;
  &amp;lt;div id=&quot;result&quot;&amp;gt;&amp;lt;/div&amp;gt;

  &amp;lt;script&amp;gt;
    const startButton = document.getElementById(&apos;startButton&apos;);
    const resultDiv = document.getElementById(&apos;result&apos;);
    let recognition;

    // 创建 SpeechRecognition 对象
    if (&apos;webkitSpeechRecognition&apos; in window) {
      recognition = new webkitSpeechRecognition();
    } else if (&apos;SpeechRecognition&apos; in window) {
      recognition = new SpeechRecognition();
    } else {
      console.error(&apos;浏览器不支持语音识别功能&apos;);
    }

    // 配置识别参数
    recognition.continuous = true;
    recognition.lang = &apos;zh-CN&apos;; // 设置识别语言，默认为浏览器语言

    // 识别结果回调
    recognition.onresult = function(event) {
      const transcript = event.results[event.results.length - 1][0].transcript;
      resultDiv.textContent = transcript;
      console.log(transcript)
    };

    // 开始识别
    startButton.addEventListener(&apos;click&apos;, function() {
      recognition.start();
    });
  &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Nodemailer 从 Node.js 发送电子邮件</title><link>https://fuwari.vercel.app/posts/2024%E5%B9%B4/nodemailer-%E4%BB%8E-nodejs-%E5%8F%91%E9%80%81%E7%94%B5%E5%AD%90%E9%82%AE%E4%BB%B6/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2024%E5%B9%B4/nodemailer-%E4%BB%8E-nodejs-%E5%8F%91%E9%80%81%E7%94%B5%E5%AD%90%E9%82%AE%E4%BB%B6/</guid><pubDate>Sun, 02 Jun 2024 15:22:10 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;Nodemailer 是一个流行的 Node.js 库，用于发送电子邮件。它提供了一个简单而强大的方式来通过各种电子邮件服务和协议（如SMTP、IMAP、POP3）发送和接收电子邮件。Nodemailer 使您能够在 Node.js 应用程序中轻松集成电子邮件功能，无论是用于发送验证邮件、通知邮件、订阅服务还是其他用途。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nodemailer/nodemailer&quot;&gt;Github&lt;/a&gt; &lt;a href=&quot;https://nodemailer.com/&quot;&gt;官方网站&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;h3&gt;安装&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm install nodemailer
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;简单示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const nodemailer = require(&apos;nodemailer&apos;);

// 创建一个SMTP传输器对象
const transporter = nodemailer.createTransport({
  host: &apos;smtpdm.aliyun.com&apos;,
  port: 25, // 默认SMTP端口 smtpdm.aliyun.com
  // 如果服务器需要身份验证，请提供用户名和密码
  //  auth: {
  //    user: &apos;sender@server.com&apos;,
  //    pass: &apos;XXXXXX&apos;,
  //  },
});

// 邮件选项
const mailOptions = {
  from: &apos;&quot;NickName&quot; &amp;lt;sender@server.com&amp;gt;&apos;,
  to: &quot;receiver@sender.com&quot;,
  subject: &apos;Test&apos;, //标题
  text: &apos;This is a test message&apos;, //文本内容
  html: &quot;&amp;lt;b&amp;gt;Hello world?&amp;lt;/b&amp;gt;&quot;, // html body
  attachments: [  //附件
    {
        filename: &apos;text1.txt&apos;,
        content: &apos;hello world!&apos;,
        path: &apos;./package.json&apos;
    }
],
};

// 发送邮件
transporter.sendMail(mailOptions, (error, info) =&amp;gt; {
  if (error) {
    console.log(&apos;Error sending email:&apos;, error);
  } else {
    console.log(&apos;Email sent:&apos;, info.response);
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要注意如果邮件选项里有 &lt;code&gt;html&lt;/code&gt; 选项的内容那么 &lt;code&gt;text&lt;/code&gt; 选项的内容会被覆盖，所以要二者选其一。&lt;/p&gt;
</content:encoded></item><item><title>Mri 解析命令行参数</title><link>https://fuwari.vercel.app/posts/2024%E5%B9%B4/mri-%E8%A7%A3%E6%9E%90%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%8F%82%E6%95%B0/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2024%E5%B9%B4/mri-%E8%A7%A3%E6%9E%90%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%8F%82%E6%95%B0/</guid><pubDate>Tue, 07 May 2024 22:39:07 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;这是一个快速轻量级的替代品，用于替代 [minimist](https://github.com/substack/minimist 和 &lt;a href=&quot;https://github.com/yargs/yargs-parser&quot;&gt;&lt;code&gt;yargs-parser&lt;/code&gt;&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;之所以存在这个工具，是因为大多数项目通常不需要 minimist 和 yargs-parser 提供的大多数功能。不过，mri 与它们相似，可能对你也可以作为“替代方案”来使用！&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lukeed/mri&quot;&gt;Github&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install mri
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简单示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import mri from &quot;mri&quot;;

const argsOptions: mri.Options = {
  boolean: [&quot;h&quot;, &quot;help&quot;, &quot;v&quot;, &quot;version&quot;],
  string: [&quot;host&quot;, &quot;name&quot;, &quot;cookieAge&quot;, &quot;sessionAge&quot;,&quot;port&quot;],
  alias: {
    v: &quot;version&quot;,
    h: [&quot;help&quot;],
  },
  default: {
  },
};

const args = mri(process.argv.slice(2), argsOptions);

export default args;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;node app.js --port=3001
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>游戏服务器数据传输优化</title><link>https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E6%B8%B8%E6%88%8F%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93%E4%BC%98%E5%8C%96/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E6%B8%B8%E6%88%8F%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93%E4%BC%98%E5%8C%96/</guid><pubDate>Sat, 20 Apr 2024 12:35:05 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;在一些需要频繁跟服务器进行数据交换的场景，比如游戏服务器。假设一个玩家每秒要向服务器发送30次数据包，如果玩家站着不动，那么这一秒发送的30个数据包都是完全相同的数据，这样造成了大量的性能和宽带的浪费，针对这种情况，服务器会使用数据缓存技术来减少服务器和客户端之间的数据传输量。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;实现的方法主要就是通过：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在客户端缓存玩家的状态，当状态不变时，停止向服务器发送数据包，从而避免发送重复的数据。&lt;/li&gt;
&lt;li&gt;在服务器端，通过缓存一些常用数据，每次给玩家发送新的数据包的时候对比这些数据是否发生改变，避免每次向玩家发送重复的数据，以减少发送数据包的大小，从而减少宽带占用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但同时还要考虑到玩家丢包的情况，比如某位玩家在移动的时候发生了丢包，过了一秒后他的网络恢复正常，但是因为该名玩家少接收了30个包，实际获得的数据并非完整的数据，此时他的人物动画仍然是&quot;跑步&quot;的状态，但是他本人早已停止了移动，就出现了玩家没有移动，但是人物的动画却一直在原地&quot;跑步&quot;。&lt;/p&gt;
&lt;p&gt;为了消除这样的影响，推荐每隔5秒就给所有玩家发送一次游戏完整的数据包，这样可以保证网络传输宽带最大化的利用的同时不造成过多的性能浪费。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;diff 算法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在这里我们使用 &lt;code&gt;jsondiffpatch&lt;/code&gt; 库来进行数据比对，&lt;code&gt;jsondiffpatch&lt;/code&gt; 库是一个将两个 json 文档、文本、数组等进行对比，并生成diff、patch信息的 javascript 库，该库支持多种formatter格式输出。&lt;/p&gt;
&lt;p&gt;安装：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install jsondiffpatch -S
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const jsondiffpatch = require(&quot;jsondiffpatch&quot;);
let original = [
  {
    id: &quot;1&quot;,
    status: &quot;idle&quot;,
    x: 0,
    y: 0,
  },
  {
    id: &quot;2&quot;,
    status: &quot;idle&quot;,
    x: 100,
    y: 100,
  },
];

let modified = [
  {
    id: &quot;1&quot;,
    status: &quot;run&quot;,
    x: 999,
    y: 0,
  },
  {
    id: &quot;2&quot;,
    status: &quot;idle&quot;,
    x: 100,
    y: 100,
  },
];

function diffForArray(original, modified) {
  return modified.map((el) =&amp;gt; {
    const data = original.find((o) =&amp;gt; o.id === el.id);
    if (data) {
      const diff = jsondiffpatch.diff(data, el);
      if (diff) {
        return {
          id: el.id,
          ...jsondiffpatch.patch({}, diff),
        };
      } else {
        return {
          id: el.id,
        };
      }
    } else {
      return el;
    }
  });
}

console.log(diffForArray(original, modified)); //[{ id: &quot;1&quot;, status: &quot;run&quot;, x: 999 }, { id: &quot;2&quot; }];
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>蓝叠adb连接并配置网络代理</title><link>https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E8%93%9D%E5%8F%A0adb%E8%BF%9E%E6%8E%A5%E5%B9%B6%E9%85%8D%E7%BD%AE%E7%BD%91%E7%BB%9C%E4%BB%A3%E7%90%86/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E8%93%9D%E5%8F%A0adb%E8%BF%9E%E6%8E%A5%E5%B9%B6%E9%85%8D%E7%BD%AE%E7%BD%91%E7%BB%9C%E4%BB%A3%E7%90%86/</guid><pubDate>Sat, 02 Mar 2024 23:56:50 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;有时候要抓取手机 APP 内的数据包，但是手机配置过于麻烦，所以选择使用安卓模拟器来进行调试。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://dl.google.com/android/repository/platform-tools-latest-windows.zip&quot;&gt;ADB 下载&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.bluestacks.com/&quot;&gt;蓝叠官网下载&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://support.bluestacks.com/hc/zh-tw/articles/4402611273485-BlueStacks-5-%E9%9B%A2%E7%B7%9A%E5%AE%89%E8%A3%9D%E7%A8%8B%E5%BC%8F&quot;&gt;蓝叠离线版下载&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;配置Adb环境&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;下载并解压ADB工具包到任意位置。&lt;/li&gt;
&lt;li&gt;将ADB工具包的路径添加到系统环境变量 Path 中，方便在任意位置使用ADB命令。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;启用蓝叠 ABD&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;打开蓝叠模拟器，进入设置。&lt;/li&gt;
&lt;li&gt;在 设置——高级 中启用ADB开关，记下显示的地址（如127.0.0.1:55555）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://support.bluestacks.com/hc/zh-tw/articles/360058929011-BlueStacks-5%E4%B8%AD%E7%9A%84%E6%87%89%E7%94%A8%E4%BA%8C%E9%80%B2%E5%88%B6%E6%8E%A5%E5%8F%A3-ABI-%E6%98%AF%E4%BB%80%E9%BA%BC?locale=zh-tw&quot;&gt;蓝叠开启 Android 调试&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;保存好后重启模拟器。&lt;/p&gt;
&lt;h3&gt;连接ADB到蓝叠&lt;/h3&gt;
&lt;p&gt;启动模拟器后，在命令提示符（cmd）中输入以下命令连接ADB：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;adb connect localhost:[port]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;替换 &lt;code&gt;[port]&lt;/code&gt; 为前面记下的端口号。&lt;/p&gt;
&lt;p&gt;连接成功后，使用以下命令查看连接状态：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;adb devices
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用以下命令进入设备的ADB环境：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;adb -s localhost:[port] shell
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;配置网络代理&lt;/h2&gt;
&lt;p&gt;连接成功后，可以通过ADB命令配置网络代理。注意，错误配置可能导致无法联网。&lt;/p&gt;
&lt;h3&gt;修改为指定代理&lt;/h3&gt;
&lt;p&gt;使用以下命令配置代理&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;settings put global http_proxy [ip]:[port]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;恢复默认代理&lt;/h3&gt;
&lt;p&gt;如果需要恢复默认代理，使用以下命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;settings put global http_proxy :0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;确认代理IP地址&lt;/h3&gt;
&lt;p&gt;代理的IP地址应为本机IP。可以通过以下命令查看本机IP&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ipconfig /all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;找到对应的IPv4地址，即为本机IP&lt;/p&gt;
&lt;h3&gt;测试网络连接&lt;/h3&gt;
&lt;p&gt;在模拟器内置浏览器中打开任意网页，确认网络连接是否正常。如有问题，可尝试切换不同的代理IP地址。&lt;/p&gt;
&lt;h2&gt;退出ADB&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;exit
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>编译带 CUDA 模块的 OpenCV</title><link>https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E7%BC%96%E8%AF%91%E5%B8%A6-cuda-%E6%A8%A1%E5%9D%97%E7%9A%84-opencv/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E7%BC%96%E8%AF%91%E5%B8%A6-cuda-%E6%A8%A1%E5%9D%97%E7%9A%84-opencv/</guid><pubDate>Mon, 19 Feb 2024 18:16:25 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;众所周知，OpenCV 自带预编译的安装包，可是这个预编译的 OpenCV 不能很好地调用 GPU 进行运算，这导致了运行速度总是不尽人意。如果想要 OpenCV 更好地调用 GPU ，这需要在编译 OpenCV 的过程中添加对 CUDA 的支持。&lt;/p&gt;
&lt;p&gt;因为CUDA 模块在 opencv_contrib 包中，所以在编译时还需要附带上与 opencv 版本一致的 opencv_contrib 源代码。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;h3&gt;需要的工具&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;CMake&lt;/li&gt;
&lt;li&gt;Visual Studio&lt;/li&gt;
&lt;li&gt;CUDA&lt;/li&gt;
&lt;li&gt;CUDA Toolkit&lt;/li&gt;
&lt;li&gt;OpenCV 源代码&lt;/li&gt;
&lt;li&gt;opencv_contrib 源代码&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;具体步骤&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先需要把 OpenCV 和 opencv_contrib 的源代码解压在一个不包含中文和空格路径的文件夹内。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;打开 CMake 把解压后 OpenCV 源代码的路径填在 &lt;code&gt;Where is the source code&lt;/code&gt; 选项中。在 &lt;code&gt;Where to build the binaries&lt;/code&gt; 选项中填入要生成二进制文件的路径，这里推荐在解压源代码的根目录创建一个 &quot;build&quot; 文件夹来作为生成目录。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;接着点击 &lt;code&gt;Configure&lt;/code&gt; 按钮，这时候会弹出一个选项框，这里选择你当前安装的Visual Studio 的版本，然后选择x64，选择完后点击&lt;code&gt;Finish&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;接着CMake就会开始第一次源码的配置，这个过程可能会比较慢，这是因为有一些文件需要从外部服务器下载，而这些文件国内的网络可能无法访问，你可以自行下载对应的文件并且在源代码根目录的 &lt;code&gt;.cache&lt;/code&gt; 文件夹内自行替换。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果下方日志打印出 &lt;code&gt;Configuring done&lt;/code&gt; ，则代表第一次配置完成，接着我们在 CMake 的配置框中勾选：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BUILD_CUDA_STUBS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OPENCV_DNN_CUDA&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WITH_CUDA&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BUILD_opencv_world&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这四个选项，接着在 &lt;code&gt;OPENCV_EXTRA_MODULES_PATH&lt;/code&gt; 中填入 opencv_contrib 源代码根目录文件夹 &lt;code&gt;modules&lt;/code&gt; 的路径 。如果不需要的话，python 和 java 的绑定也可以取消勾选&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;再次点击 &lt;code&gt;Configure&lt;/code&gt; 按钮开始第二次配置，等配置完成后，在配置中找到 &lt;code&gt;CUDA_ARCH_BIN&lt;/code&gt; 的选项在里面填入你当前电脑显卡的计算系数。&lt;/p&gt;
&lt;p&gt;可以在这里找到对应显卡的算力：&lt;a href=&quot;https://developer.nvidia.com/cuda-gpus&quot;&gt;Nvidia显卡算力表&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意一定只选择你当前显卡的算力，不要填写多个，否则等到了编译阶段，它会把每个架构都编译一遍。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;等上述步骤都做完后就可以点击 &lt;code&gt;Generate&lt;/code&gt; 按钮生成可以编译的文件，等生成完成后就可以点击 &lt;code&gt;Open Project&lt;/code&gt; 按钮进入 Visual Studio 开始最终的编译阶段了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 Visual Studio 中，先将上方的 Debug 下拉框选为 Release ，接着在右侧资源管理器中找到 CMakeTargets 文件夹中的 ALL_BUILD，对其右键点击生成。等漫长的生成结束后在对着 INSTALL 右键选择“仅用于项目”——“仅生成INSTALL”。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;等待编译结束后，编译好的文件就在 &lt;code&gt;build&lt;/code&gt; 文件夹的 &lt;code&gt;install&lt;/code&gt; 文件夹中。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>制作 NPM 模板脚手架</title><link>https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E5%88%B6%E4%BD%9C-npm-%E6%A8%A1%E6%9D%BF%E8%84%9A%E6%89%8B%E6%9E%B6/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2024%E5%B9%B4/%E5%88%B6%E4%BD%9C-npm-%E6%A8%A1%E6%9D%BF%E8%84%9A%E6%89%8B%E6%9E%B6/</guid><pubDate>Fri, 12 Jan 2024 18:06:02 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;在平时进行新项目开发的时候，重新搭建项目目录费时又费力，而且这些内容还和旧项目的工作内容有很高的耦合度，为了提高开发效率，创建一个完整且高度可配置的脚手架会是一个明智的选择。&lt;/p&gt;
&lt;p&gt;通过预定义的项目模板，其中包含了一系列已经配置好的文件、目录结构和依赖项，可以帮助我们快速启动新项目，省去手动搭建项目的繁琐步骤，同时保持项目结构的一致性以及代码规范。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;在这里参考了 vite 和 nuxt 的脚手架的实现原理。&lt;/p&gt;
&lt;p&gt;package.json 配置参考&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;template-framework&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;This is a create application framework.&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;bin&quot;: {
    &quot;create-template&quot;: &quot;index.js&quot;,
   },
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;MIT&quot;,
  &quot;dependencies&quot;: {
    &quot;fs-extra&quot;: &quot;^11.1.1&quot;,
    &quot;minimist&quot;: &quot;^1.2.8&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;制作模板生成脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//index.js
#!/usr/bin/env node
const path = require(&apos;path&apos;)
const fs = require(&apos;fs-extra&apos;)
const argv = require(&apos;minimist&apos;)(process.argv.slice(2))

async function init() {
  const targetDir = argv._[0] || &apos;.&apos;
  const cwd = process.cwd()
  const root = path.join(cwd, targetDir)
  const renameFiles = {
    _gitignore: &apos;.gitignore&apos;,
  }
  console.log(`Scaffolding project in ${root}...`)

  await fs.ensureDir(root)
  const existing = await fs.readdir(root)
  if (existing.length) {
    console.error(`Error: target directory is not empty.`)
    process.exit(1)
  }

  const templateDir = path.join(
    __dirname,
    `template-${argv.t || argv.template || &apos;default&apos;}`
  )
  const write = async (file, content) =&amp;gt; {
    const targetPath = renameFiles[file]
      ? path.join(root, renameFiles[file])
      : path.join(root, file)
    if (content) {
      await fs.writeFile(targetPath, content)
    } else {
      await fs.copy(path.join(templateDir, file), targetPath)
    }
  }

  const files = await fs.readdir(templateDir)
  for (const file of files.filter((f) =&amp;gt; f !== &apos;package.json&apos;)) {
    await write(file)
  }

  const pkg = require(path.join(templateDir, `package.json`))
  pkg.name = path.basename(root)
  await write(&apos;package.json&apos;, JSON.stringify(pkg, null, 2))

  console.log(`\nDone. Now run:\n`)
  if (root !== cwd) {
    console.log(`  cd ${path.relative(cwd, root)}`)
  }
  console.log(`  npm install (or \`yarn\`)`)
  console.log(`  npm run dev (or \`yarn dev\`)`)
  console.log()
}

init().catch((e) =&amp;gt; {
  console.error(e)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;模板文件夹命名格式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;template-default&lt;/code&gt; 默认模板&lt;/li&gt;
&lt;li&gt;&lt;code&gt;template-popular&lt;/code&gt; 这样的命名在后续安装时只需添加 &lt;code&gt;--template popular&lt;/code&gt; 这样的命令选项即可指定模板风格。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;测试&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;node index.js abc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果成功创建 &lt;code&gt;abc&lt;/code&gt; 文件夹则脚手架成功搭建。&lt;/p&gt;
&lt;p&gt;如果你想要指定模板你可以这样&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;node index.js abc --template &amp;lt;NewTemplate&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;NewTemplate&amp;gt;&lt;/code&gt; 是指定的模板，这需要你创建相对于的文件夹。&lt;/p&gt;
&lt;p&gt;测试没有问题后可以选择上传到 npm 中方便以后搭建&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm publish
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上传到npm后可以选择使用 &lt;code&gt;npx&lt;/code&gt; 进行部署也可以选择全局安装后调用 &lt;code&gt;create-template&lt;/code&gt; 指令。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx template-framework abc
#or
npm template-framework -g
create-template
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;推荐使用 &lt;code&gt;npx&lt;/code&gt; 方法来进行模板的安装，因为这样可以保证安装的是最新的模板并且无需担心全局命令冲突的问题。&lt;/p&gt;
</content:encoded></item><item><title>文件系统路由</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E8%B7%AF%E7%94%B1/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E8%B7%AF%E7%94%B1/</guid><pubDate>Tue, 12 Dec 2023 19:15:55 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;&quot;文件系统路由&quot; 通常是指将文件系统中的文件和文件夹映射到 Web 应用程序的不同 URL 路径，以便通过 Web 服务器来访问这些文件和文件夹。这是一种常见的用于构建静态网站或单页应用程序（SPA）的技术。&lt;/p&gt;
&lt;h2&gt;简单的文件路由算法实现&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import express, { Router } from &quot;express&quot;;
import fs from &quot;fs&quot;;
import path from &quot;path&quot;;
import  chalk from &quot;chalk&quot;
// 创建路由对象
const router: Router = express.Router();

// 用于存储已存在的路由路径
const existingPaths = new Set();

function loadRoutes(routeFolder: string, parentPath = &quot;&quot;) {
  const files = fs.readdirSync(routeFolder);
  files.forEach(async (file) =&amp;gt; {
    const filePath = path.join(routeFolder, file);
    const isDirectory = fs.statSync(filePath).isDirectory();
    if (isDirectory) {
      // 如果是文件夹，递归加载子文件夹中的路由文件
      let subfolder = path.join(parentPath, file);
      subfolder = subfolder.startsWith(&quot;/&quot;) ? subfolder : `/${subfolder}`;
      loadRoutes(filePath, subfolder);
    } else if ((file.endsWith(&quot;.js&quot;) || file.endsWith(&quot;.ts&quot;)) &amp;amp;&amp;amp; !file.endsWith(&quot;.d.ts&quot;)) {
      // 如果是路由文件，导入并注册路由
      const routePath = path.parse(file).name === &apos;index&apos; ? `${parentPath}` : `${parentPath}/${path.parse(file).name}`;
      // 检查是否存在重复路由路径
      if (existingPaths.has(routePath)) {
        console.warn(chalk.yellow(&quot;[Warning]&quot;),`重复的路由路径: ${routePath} (${filePath})`);
        // 可以采取适当的措施来处理重复路径，如抛出异常或其他处理
      } else {
        existingPaths.add(routePath);
        const route = await import(filePath);
        router.use(routePath, route.router);
      }
    }
  });
}

// 调用函数开始递归加载路由文件
const routesFolderPath = path.join(__dirname, &quot;routes&quot;);
loadRoutes(routesFolderPath);

router.use(apiLimiter);

export default router;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Tar 打包</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/tar-%E6%89%93%E5%8C%85/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/tar-%E6%89%93%E5%8C%85/</guid><pubDate>Tue, 21 Nov 2023 22:06:44 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;tar&lt;/code&gt; 是一个在 Unix 和类 Unix 操作系统上广泛使用的命令行工具，用于创建和管理归档文件（通常称为 &quot;tarball&quot;）。&quot;tar&quot; 实际上是 &quot;tape archive&quot; 的缩写，最初设计用于将多个文件和目录组合成单个归档文件，并且可以选择性地使用其他压缩程序（例如 gzip 或 bzip2）来对这些文件进行压缩。&lt;code&gt;tar&lt;/code&gt; 命令通常用于在文件传输、备份和分发文件时创建和解压这些归档。&lt;/p&gt;
&lt;p&gt;当然 Windows 系统也可以使用tar命令。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;tar&lt;/code&gt; 命令的基本语法如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tar [选项] [文件或目录]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一些常用的 &lt;code&gt;tar&lt;/code&gt; 选项包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-c&lt;/code&gt;：创建新的 tar 归档。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-x&lt;/code&gt;：解压 tar 归档。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-f&lt;/code&gt;：指定归档文件的名称。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-v&lt;/code&gt;：显示详细的操作信息，通常用于查看正在执行的操作。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-z&lt;/code&gt;：在 tar 操作中使用 gzip 进行压缩。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-j&lt;/code&gt;：在 tar 操作中使用 bzip2 进行压缩。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-t&lt;/code&gt;：列出 tar 归档中的内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;压缩&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;tar -zcvf  file.tar.gz filename
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;解压&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;tar -zxvf file.tar -C /home/dir
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>定时任务 node-schedule</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1-node-schedule/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1-node-schedule/</guid><pubDate>Sun, 12 Nov 2023 16:12:32 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;node-schedule&lt;/code&gt; 是一个用于 Node.js 的 JavaScript 库，它允许您在特定的时间点或按照预定的时间表执行任务。它的主要用途是在 Node.js 应用程序中执行定时任务，例如定时发送电子邮件、生成报告、清理临时文件等。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/node-schedule/node-schedule&quot;&gt;Github&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;安装：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install node-schedule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简单示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const schedule = require(&apos;node-schedule&apos;);

// 创建一个定时任务，每天的下午 2 点执行
const dailyJob = schedule.scheduleJob(&apos;0 14 * * *&apos;, function() {
    console.log(&apos;定时任务执行了！&apos;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上述示例中，我们首先导入了 &lt;code&gt;node-schedule&lt;/code&gt; 模块，然后使用 &lt;code&gt;scheduleJob&lt;/code&gt; 方法创建了一个定时任务。参数 &lt;code&gt;&apos;0 14 * * *&apos;&lt;/code&gt; 是一个 Cron 表达式，表示每天的下午 2 点执行任务。当任务执行时，会输出一条消息。&lt;/p&gt;
&lt;p&gt;除了基本的 cron 表达式之外，&lt;code&gt;node-schedule&lt;/code&gt; 还支持其他更灵活的调度方式，您可以根据需要选择适合您应用程序的方法。&lt;/p&gt;
&lt;p&gt;总的来说，&lt;code&gt;node-schedule&lt;/code&gt; 是一个方便的工具，用于在 Node.js 应用程序中执行定时任务。它可以帮助你自动化重复的工作，提高应用程序的效率和可靠性。&lt;/p&gt;
</content:encoded></item><item><title>键盘按键监听</title><link>https://fuwari.vercel.app/posts/2020%E5%B9%B4/%E9%94%AE%E7%9B%98%E6%8C%89%E9%94%AE%E7%9B%91%E5%90%AC/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2020%E5%B9%B4/%E9%94%AE%E7%9B%98%E6%8C%89%E9%94%AE%E7%9B%91%E5%90%AC/</guid><pubDate>Sun, 12 Nov 2023 08:06:00 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;如果你想让你的页面监听你的键盘事件，你可以使用 &lt;code&gt;window.addEventListener&lt;/code&gt; 函数来全局监听你的键盘事件，如果你想要单独监听某个 dom 的按键事件，你可以使用 &lt;code&gt;document.getElementByID(&apos;yourDomId&apos;).addEventListener&lt;/code&gt; 来添加监听&lt;/p&gt;
&lt;h2&gt;示例&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let left = {
  key: [&quot;a&quot;, &quot;A&quot;],
  isDown: false,
  isUp: true,
  pressEvent: function (e, obj) {
    if (this.key.includes(e.key)) {
      this.isDown = true;
      this.isUp = false;
    }
  },
  releaseEvent: function (e, obj) {
    if (this.key.includes(e.key)) {
      this.isDown = false;
      this.isUp = true;
    }
  },
};

let keyboard = [left];

window.addEventListener(&quot;keydown&quot;, function (event) {
  keyboard.forEach((item) =&amp;gt; {
    item.pressEvent(event, userInput);
  });
});

window.addEventListener(&quot;keyup&quot;, function (event) {
  keyboard.forEach((item) =&amp;gt; {
    item.releaseEvent(event, userInput);
  });
});
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>时间库 Day.js</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E6%97%B6%E9%97%B4%E5%BA%93-dayjs/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E6%97%B6%E9%97%B4%E5%BA%93-dayjs/</guid><pubDate>Sun, 12 Nov 2023 08:06:00 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;Day.js 是一个极简主义的 JavaScript 库，它使用大部分与 Moment.js 兼容的 API 为现代浏览器解析、验证、操作和显示日期和时间。如果你使用 Moment.js，你已经知道如何使用 Day.js。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://day.js.org&quot;&gt;官方网站&lt;/a&gt; &lt;a href=&quot;https://github.com/iamkun/dayjs&quot;&gt;Github&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;特性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;🕒 熟悉的 Moment.js API 和模式&lt;/li&gt;
&lt;li&gt;💪 不可变&lt;/li&gt;
&lt;li&gt;🔥 链式操作&lt;/li&gt;
&lt;li&gt;🌐 国际化支持&lt;/li&gt;
&lt;li&gt;📦 2kb 迷你库&lt;/li&gt;
&lt;li&gt;👫 支持所有浏览器&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install dayjs --save
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;浏览器使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;
  dayjs().format()
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Node.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//es5
const dayjs = require(&quot;dayjs&quot;);
//es6
import dayjs from &apos;dayjs&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;当前时间&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;let now = dayjs();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;格式化日期&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;dayjs().format() 
// 默认返回的是 ISO8601 格式字符串 &apos;2020-04-02T08:02:17-05:00&apos;

dayjs(&apos;2019-01-25&apos;).format(&apos;[YYYYescape] YYYY-MM-DDTHH:mm:ssZ[Z]&apos;) 
// &apos;YYYYescape 2019-01-25T00:00:00-02:00Z&apos;

dayjs(&apos;2019-01-25&apos;).format(&apos;DD/MM/YYYY&apos;) // &apos;25/01/2019&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;a href=&quot;https://day.js.org/docs/zh-CN/display/format#%E6%94%AF%E6%8C%81%E7%9A%84%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%8D%A0%E4%BD%8D%E7%AC%A6%E5%88%97%E8%A1%A8&quot;&gt;&lt;/a&gt;支持的格式化占位符列表&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;占位符&lt;/th&gt;
&lt;th&gt;输出&lt;/th&gt;
&lt;th&gt;详情&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;YY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;两位数的年份&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;YYYY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2018&lt;/td&gt;
&lt;td&gt;四位数的年份&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;M&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1-12&lt;/td&gt;
&lt;td&gt;月份，从 1 开始&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;01-12&lt;/td&gt;
&lt;td&gt;月份，两位数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MMM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jan-Dec&lt;/td&gt;
&lt;td&gt;缩写的月份名称&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MMMM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;January-December&lt;/td&gt;
&lt;td&gt;完整的月份名称&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;D&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1-31&lt;/td&gt;
&lt;td&gt;月份里的一天&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;01-31&lt;/td&gt;
&lt;td&gt;月份里的一天，两位数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0-6&lt;/td&gt;
&lt;td&gt;一周中的一天，星期天是 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Su-Sa&lt;/td&gt;
&lt;td&gt;最简写的星期几&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ddd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sun-Sat&lt;/td&gt;
&lt;td&gt;简写的星期几&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dddd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sunday-Saturday&lt;/td&gt;
&lt;td&gt;星期几&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;H&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0-23&lt;/td&gt;
&lt;td&gt;小时&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;00-23&lt;/td&gt;
&lt;td&gt;小时，两位数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1-12&lt;/td&gt;
&lt;td&gt;小时, 12 小时制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;01-12&lt;/td&gt;
&lt;td&gt;小时, 12 小时制, 两位数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;m&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0-59&lt;/td&gt;
&lt;td&gt;分钟&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;00-59&lt;/td&gt;
&lt;td&gt;分钟，两位数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0-59&lt;/td&gt;
&lt;td&gt;秒&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ss&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;00-59&lt;/td&gt;
&lt;td&gt;秒 两位数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SSS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;000-999&lt;/td&gt;
&lt;td&gt;毫秒 三位数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;+05:00&lt;/td&gt;
&lt;td&gt;UTC 的偏移量，±HH:mm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ZZ&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;+0500&lt;/td&gt;
&lt;td&gt;UTC 的偏移量，±HHmm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;A&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AM PM&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;am pm&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;其他格式 ( 依赖 &lt;a href=&quot;https://day.js.org/docs/zh-CN/plugin/advanced-format&quot;&gt;&lt;code&gt;AdvancedFormat&lt;/code&gt;&lt;/a&gt; 插件&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>常用的正则表达式</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E5%B8%B8%E7%94%A8%E7%9A%84%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E5%B8%B8%E7%94%A8%E7%9A%84%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/</guid><pubDate>Tue, 17 Oct 2023 13:41:26 GMT</pubDate><content:encoded>&lt;h2&gt;开始&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;匹配任意字符：&lt;code&gt;([\s\S]*?)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配手机号码：&lt;code&gt;/^1[3456789]\d{9}$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配邮箱地址：&lt;code&gt;/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配身份证号码（18位）：&lt;code&gt;/(^\d{17}[\dXx]$)/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配日期（YYYY-MM-DD）：&lt;code&gt;/^\d{4}-\d{2}-\d{2}$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配URL：&lt;code&gt;/^https?:\/\/([\w-]+\.)+[\w-]+(\/[\w-.\/?%&amp;amp;=]*)?$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配邮政编码：&lt;code&gt;/^[1-9]\d{5}$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配IP地址：&lt;code&gt;/^((25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配整数：&lt;code&gt;/^-?\d+$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配浮点数：&lt;code&gt;/^-?\d+(\.\d+)?$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配字母和数字：&lt;code&gt;/^[a-zA-Z0-9]+$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配英文字母：&lt;code&gt;/^[a-zA-Z]+$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配大写字母：&lt;code&gt;/^[A-Z]+$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配小写字母：&lt;code&gt;/^[a-z]+$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配中文字符：&lt;code&gt;/[\u4e00-\u9fa5]/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配只包含空白字符的字符串：&lt;code&gt;/^\s*$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配十六进制颜色值：&lt;code&gt;/^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配邮箱用户名部分：&lt;code&gt;/^[\w.-]+$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配英文单词：&lt;code&gt;/^[A-Za-z]+$/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;匹配HTML标签（非贪婪模式）：&lt;code&gt;/&amp;lt;.+?&amp;gt;/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>正则表达式 捕获组</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F-%E6%8D%95%E8%8E%B7%E7%BB%84/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F-%E6%8D%95%E8%8E%B7%E7%BB%84/</guid><pubDate>Mon, 16 Oct 2023 12:39:59 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;捕获组是正则表达式中用括号 &lt;code&gt;()&lt;/code&gt; 包裹的部分，它允许你对匹配的文本进行分组。每一对括号都表示一个捕获组，它可以包含一个或多个字符，也可以包含其他正则表达式元字符。捕获组有以下几个主要用途：&lt;/p&gt;
&lt;h2&gt;特性&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;分组：&lt;/strong&gt; 捕获组允许你将正则表达式的一部分组合在一起，并将其视为一个整体。这样，你可以在后续的模式中引用这个分组。&lt;/p&gt;
&lt;p&gt;例如，&lt;code&gt;(abc)&lt;/code&gt; 表示一个捕获组，匹配字符串中的 &quot;abc&quot;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;引用：&lt;/strong&gt; 你可以在正则表达式或替换字符串中引用捕获组的内容。在大多数正则表达式引擎中，你可以使用 &lt;code&gt;\1&lt;/code&gt;、&lt;code&gt;\2&lt;/code&gt;、&lt;code&gt;\3&lt;/code&gt; 等来引用第一个、第二个、第三个捕获组的内容。&lt;/p&gt;
&lt;p&gt;例如，如果使用 &lt;code&gt;(abc)(\d)&lt;/code&gt; 匹配 &quot;abc123&quot;，可以在替换字符串中使用 &lt;code&gt;$1&lt;/code&gt; 来引用第一个捕获组（&quot;abc&quot;），使用 &lt;code&gt;$2&lt;/code&gt; 来引用第二个捕获组（&quot;123&quot;）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;捕获匹配的部分：&lt;/strong&gt; 当正则表达式匹配一个字符串时，捕获组允许你提取匹配的特定部分。&lt;/p&gt;
&lt;p&gt;例如，对于正则表达式 &lt;code&gt;(\d{3})-(\d{4})&lt;/code&gt;，如果匹配了字符串 &quot;123-4567&quot;，则第一个捕获组包含 &quot;123&quot;，第二个捕获组包含 &quot;4567&quot;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在 Notepad 中，你可以在查找和替换中使用捕获组。在替换字符串中，你可以使用 &lt;code&gt;$1&lt;/code&gt;、&lt;code&gt;$2&lt;/code&gt; 等来引用捕获组。在查找中，你可以使用捕获组来定义模式。&lt;/p&gt;
&lt;h2&gt;简单示例&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;abc def ght
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正则表达式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(abc)(.*?)(ght)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;替换&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\1 ??? \3
或者
$1 ??? $3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;abc ??? ght
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>跨域资源共享 CORS</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E8%B7%A8%E5%9F%9F%E8%B5%84%E6%BA%90%E5%85%B1%E4%BA%AB-cors/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E8%B7%A8%E5%9F%9F%E8%B5%84%E6%BA%90%E5%85%B1%E4%BA%AB-cors/</guid><pubDate>Sun, 15 Oct 2023 12:32:44 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;通过配置合适的响应头，可以明确指定允许的来源域、请求方法和头部信息。&lt;/p&gt;
&lt;h2&gt;Node.js 配置&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;app.all(&apos;*&apos;, function(req, res, next) {
  res.header(&quot;Access-Control-Allow-Origin&quot;, &quot;*&quot;);
  res.header(&quot;Access-Control-Allow-Headers&quot;, &quot;X-Requested-With&quot;);
  res.header(&quot;Access-Control-Allow-Methods&quot;,&quot;PUT,POST,GET,DELETE,OPTIONS&quot;);
  res.header(&quot;X-Powered-By&quot;,&apos; 3.2.1&apos;)
  res.header(&quot;Content-Type&quot;, &quot;application/json;charset=utf-8&quot;);
  res.header(&quot;Access-Control-Allow-Credentials&quot;, &quot;true&quot;);
  next();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Nginx 配置&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 80;
    server_name localhost; #你的域名
    root html;
    index index.html index.htm;

    add_header Access-Control-Allow-Origin &quot;*&quot;;
    add_header Access-Control-Allow-Credentials &quot;true&quot;;
    add_header Access-Control-Allow-Methods &quot;PUT,POST,GET,DELETE,OPTIONS&quot;;
    add_header Access-Control-Allow-Headers &quot;token,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,XRequested-With&quot;;

    location / {
        proxy_pass http://localhost:3000; #你的代理服务
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>SSL 证书自签</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/ssl-%E8%AF%81%E4%B9%A6%E8%87%AA%E7%AD%BE/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/ssl-%E8%AF%81%E4%B9%A6%E8%87%AA%E7%AD%BE/</guid><pubDate>Tue, 19 Sep 2023 18:38:49 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;SSL证书自签（Self-Signed SSL Certificate）是指由网站或个人自行创建和签名的SSL证书，而不是由可信任的第三方证书颁发机构（Certificate Authority）签发的证书。&lt;/p&gt;
&lt;p&gt;自签证书在某些情况下可能有用，例如在本地开发环境中进行测试和调试，或在私有网络中建立加密通信。自签证书可以提供加密的传输，并验证服务器的身份，但由于它们不受第三方机构的信任，因此在公共网络环境中使用可能会出现浏览器警告问题。&lt;/p&gt;
&lt;p&gt;自签证书的创建和签署可以使用不同的工具和命令，例如OpenSSL。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.openssl.org/&quot;&gt;OpenSSL 官网&lt;/a&gt; &lt;a href=&quot;https://github.com/openssl/openssl&quot;&gt;OpenSSL Github&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://slproweb.com/products/Win32OpenSSL.html&quot;&gt;预编译 OpenSSL 下载&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;一般步骤&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;生成私钥：使用工具生成一个私钥文件，这个私钥将用于后续步骤中的证书签名。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;生成证书签名请求（CSR）：使用私钥生成一个CSR文件，其中包含了要在证书中包含的信息，例如域名、组织信息等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;自签证书签署：使用私钥和CSR文件，使用工具生成一个自签证书，该证书包含了与CSR文件中提供的信息相对应的数字签名。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;要实现自签名的 HTTPS 证书，你可以按照以下步骤进行操作：&lt;/p&gt;
&lt;p&gt;生成私钥（private key）： 首先，你需要生成一个私钥文件。使用 OpenSSL 工具可以执行此操作。在命令行中运行以下命令来生成私钥文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl genrsa -out private.key 2048`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这将生成一个名为 &lt;code&gt;private.key&lt;/code&gt; 的私钥文件，其中 2048 是私钥的长度（可以根据需要进行调整）。&lt;/p&gt;
&lt;p&gt;生成证书签名请求（Certificate Signing Request，CSR）： 接下来，你需要生成一个证书签名请求文件，该文件将用于创建自签名证书。运行以下命令来生成 CSR 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl req -new -key private.key -out csr.csr`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在生成 CSR 文件时，你将被要求提供一些相关信息，例如组织名称、组织单位、通用名称（域名）等。请根据你的情况提供正确的信息。&lt;/p&gt;
&lt;p&gt;生成自签名证书： 使用以下命令生成自签名证书：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl x509 -req -in csr.csr -signkey private.key -out certificate.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这将使用私钥和 CSR 文件生成一个自签名证书文件 &lt;code&gt;certificate.crt&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;配置服务器： 将生成的私钥文件 &lt;code&gt;private.key&lt;/code&gt; 和自签名证书文件 &lt;code&gt;certificate.crt&lt;/code&gt; 配置到你的服务器上。具体配置方法取决于你使用的服务器软件（如 Apache、Nginx 等）。&lt;/p&gt;
&lt;p&gt;例如，在使用 Nginx 服务器时，可以在配置文件中指定私钥和证书的路径，类似于：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 443 ssl;
    server_name your_domain.com;
    ssl_certificate /path/to/certificate.crt;
    ssl_certificate_key /path/to/private.key;
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据你的服务器配置和需求，进行适当的调整。&lt;/p&gt;
&lt;p&gt;请注意，自签名证书在浏览器中可能会显示安全警告，因为它们不是由受信任的第三方机构签发。在实际生产环境中，为了获得受信任的证书，建议从受信任的证书颁发机构（CA）获取证书。&lt;/p&gt;
&lt;p&gt;自签名证书通常适用于开发、测试环境或个人使用，而不适用于公共或生产环境。如果你需要在公共环境中使用 HTTPS，请考虑获得由受信任的证书颁发机构签发的证书。&lt;/p&gt;
&lt;p&gt;请确保在生成自签名证书时，妥善保管私钥文件，以防止私钥泄露导致安全问题。&lt;/p&gt;
</content:encoded></item><item><title>WebRTC 库 PeerJS</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/webrtc-%E5%BA%93-peerjs/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/webrtc-%E5%BA%93-peerjs/</guid><pubDate>Mon, 28 Aug 2023 12:17:12 GMT</pubDate><content:encoded>&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install peerjs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简单示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Peer } from &quot;peerjs&quot;;

const peer = new Peer(&quot;pick-an-id&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;连接&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const conn = peer.connect(&quot;another-peers-id&quot;);
conn.on(&quot;open&quot;, () =&amp;gt; {
	conn.send(&quot;hi!&quot;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;收到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;peer.on(&quot;connection&quot;, (conn) =&amp;gt; {
	conn.on(&quot;data&quot;, (data) =&amp;gt; {
		// Will print &apos;hi!&apos;
		console.log(data);
	});
	conn.on(&quot;open&quot;, () =&amp;gt; {
		conn.send(&quot;hello!&quot;);
	});
});
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Base64 编码的字符串转换为 Blob 对象方法</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/base64-%E7%BC%96%E7%A0%81%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E6%8D%A2%E4%B8%BA-blob-%E5%AF%B9%E8%B1%A1%E6%96%B9%E6%B3%95/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/base64-%E7%BC%96%E7%A0%81%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E6%8D%A2%E4%B8%BA-blob-%E5%AF%B9%E8%B1%A1%E6%96%B9%E6%B3%95/</guid><pubDate>Wed, 16 Aug 2023 19:32:20 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;通过将 Base64 编码的文件数据进行转化，可以更好地进行数据的传输以及节省流量。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;const blob = function (data: string, mime: string) {
  data = data.split(&apos;,&apos;)[1];
  data = window.atob(data);
  let ia = new Uint8Array(data.length);
  for (var i = 0; i &amp;lt; data.length; i++) {
    ia[i] = data.charCodeAt(i);
  };
  return new Blob([ia], {
    type: mime
  });
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>TypeScript 项目文件命名及代码规范</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/typescript-%E9%A1%B9%E7%9B%AE%E6%96%87%E4%BB%B6%E5%91%BD%E5%90%8D%E5%8F%8A%E4%BB%A3%E7%A0%81%E8%A7%84%E8%8C%83/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/typescript-%E9%A1%B9%E7%9B%AE%E6%96%87%E4%BB%B6%E5%91%BD%E5%90%8D%E5%8F%8A%E4%BB%A3%E7%A0%81%E8%A7%84%E8%8C%83/</guid><pubDate>Tue, 11 Jul 2023 20:16:50 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;当开发 TypeScript 项目时，良好的文件命名和代码规范是非常重要的。它们不仅可以提高代码的可读性和可维护性，还能帮助团队成员更好地理解和协作开发。通过遵循一致的命名约定和编码规范，可以减少代码错误、改善代码的一致性，并为项目的可扩展性和可维护性奠定基础。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;h3&gt;文件命名：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;TypeScript 声明文件的扩展名为 &lt;code&gt;.d.ts&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;入口文件通常命名为 &lt;code&gt;index.ts&lt;/code&gt;，用于导出模块或启动应用程序。&lt;/li&gt;
&lt;li&gt;模块文件应根据其功能或目的进行命名，例如 &lt;code&gt;utils.ts&lt;/code&gt;、&lt;code&gt;apiClient.ts&lt;/code&gt; 等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;代码规范：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用驼峰命名法（camelCase）来命名变量、函数和方法。例如：&lt;code&gt;myVariable&lt;/code&gt;、&lt;code&gt;calculateTotal&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;类名使用帕斯卡命名法（PascalCase）。例如：&lt;code&gt;MyClass&lt;/code&gt;、&lt;code&gt;UserService&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;使用清晰、具有描述性的名称，使代码易于理解。&lt;/li&gt;
&lt;li&gt;遵循一致的缩进和代码格式。通常使用 2 或 4 个空格进行缩进，并在代码块之间添加适当的空行和垂直间距。&lt;/li&gt;
&lt;li&gt;使用强类型声明，并尽可能使用 TypeScript 的类型系统来提供静态类型检查。&lt;/li&gt;
&lt;li&gt;在必要时添加注释来解释代码的用途、实现细节或注意事项。&lt;/li&gt;
&lt;li&gt;遵循 SOLID 原则和其他软件工程最佳实践，如单一职责原则、依赖倒置原则等。&lt;/li&gt;
&lt;li&gt;分离关注点，将代码分成逻辑模块、函数和类，以提高代码的可维护性和可测试性。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>TypeScript 类型标注</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/typescript-%E7%B1%BB%E5%9E%8B%E6%A0%87%E6%B3%A8/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/typescript-%E7%B1%BB%E5%9E%8B%E6%A0%87%E6%B3%A8/</guid><pubDate>Sun, 09 Jul 2023 12:25:31 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;在现代软件开发中，静态类型检查成为了一项重要的工具，可以帮助开发人员在编码阶段捕获潜在的错误，提高代码的可靠性和可维护性。TypeScript 作为一种强类型的编程语言，为 JavaScript 代码提供了类型标注的能力，使开发人员能够明确指定变量、函数和对象的类型。&lt;/p&gt;
&lt;p&gt;无论您是已经熟悉 JavaScript 并希望进一步提高代码质量，还是想要学习 TypeScript 并掌握其类型系统的知识，通过掌握类型标注的技巧，使我们能够编写更可靠、可维护和可扩展的代码。&lt;/p&gt;
&lt;h2&gt;优势&lt;/h2&gt;
&lt;p&gt;通过使用类型标注，您可以获得以下好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;错误检测：TypeScript可以在编码阶段捕获潜在的类型错误，帮助您避免在运行时出现意外的错误。&lt;/li&gt;
&lt;li&gt;代码提示：类型标注提供了更准确的代码提示和自动补全功能，提高了开发效率和代码可读性。&lt;/li&gt;
&lt;li&gt;文档化代码：通过类型标注，您可以清楚地了解代码中每个变量、函数和对象的用途和预期行为，使代码更易于理解和维护。&lt;/li&gt;
&lt;li&gt;重构支持：在重构代码时，类型标注可以帮助您更准确地修改和调整代码，减少引入错误的风险。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;h3&gt;函数的标注&lt;/h3&gt;
&lt;p&gt;使用别名&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface AddFunction {
  (a: number, b: number): number;
}
//or
type AddFunction = (a: number, b: number) =&amp;gt; number;

const add: AddFunction = (a, b) =&amp;gt; {
  return a + b;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不使用别名&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const add = (a: number, b: number): number =&amp;gt; {
  return a + b;
};
//or
function add(a: number, b: number): number {
  return a + b;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;变量的标注&lt;/h3&gt;
&lt;p&gt;使用别名&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface Person {
  name: string;
  age: number;
  hobbies: string[];
  address: {
    street: string;
    number: number;
  };
}
//or
type Person = {
    name: string;
    age: number;
    hobbies: string[];
    address: {
      street: string;
      number: number;
    };
  };
  
const person: Person = {
  name: &quot;John&quot;,
  age: 42,
  hobbies: [&quot;Sports&quot;, &quot;Cooking&quot;],
  address: {
    street: &quot;Main Street&quot;,
    number: 123,
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不使用别名&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const person: {
  name: string;
  age: number;
  hobbies: string[];
  address: {
    street: string;
    number: number;
  };
} = {
  name: &quot;John&quot;,
  age: 42,
  hobbies: [&quot;Sports&quot;, &quot;Cooking&quot;],
  address: {
    street: &quot;Main Street&quot;,
    number: 123,
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;组合类型&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;interface Person {
  name: string;
  age: number;
}

type Employee = {
  company: string;
  position: string;
};

type EmployeePerson = Person &amp;amp; Employee;
//or
interface EmployeePerson extends Person, Employee {}

const employee: EmployeePerson = {
  name: &quot;John&quot;,
  age: 30,
  company: &quot;ABC Inc&quot;,
  position: &quot;Manager&quot;,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;泛型&lt;/h3&gt;
&lt;p&gt;泛型类型参数用于定义在接口、类或函数中使用的类型，可以在尖括号 &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt; 中指定。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface Box&amp;lt;T&amp;gt; {
    value: T;
}
  
const box: Box&amp;lt;number&amp;gt; = { value: 42 };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上述示例中，我们定义了一个名为 &lt;code&gt;Box&lt;/code&gt; 的接口，它有一个泛型类型参数 &lt;code&gt;T&lt;/code&gt;。通过将 &lt;code&gt;T&lt;/code&gt; 指定为 &lt;code&gt;number&lt;/code&gt;，我们创建了一个 &lt;code&gt;Box&amp;lt;number&amp;gt;&lt;/code&gt; 类型的变量 &lt;code&gt;box&lt;/code&gt;，它的 &lt;code&gt;value&lt;/code&gt; 属性的类型为 &lt;code&gt;number&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;多个泛型&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;interface Pair&amp;lt;T, U&amp;gt; {
  first: T;
  second: U;
}

const pair: Pair&amp;lt;number, string&amp;gt; = {
  first: 1,
  second: &quot;2&quot;,
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>打字动画 Typed.js</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E6%89%93%E5%AD%97%E5%8A%A8%E7%94%BB-typedjs/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E6%89%93%E5%AD%97%E5%8A%A8%E7%94%BB-typedjs/</guid><pubDate>Sun, 18 Jun 2023 18:06:10 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;Typed.js 是一个 JavaScript 库，用于在网页上创建打字动画效果。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mattboldt.com/demos/typed-js/&quot;&gt;官方网站&lt;/a&gt; &lt;a href=&quot;https://github.com/mattboldt/typed.js&quot;&gt;Github&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;h4&gt;CDN&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script src=&quot;https://unpkg.com/typed.js@2.0.16/dist/typed.umd.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;NPM&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;npm install typed.js
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;General ESM Usage&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import Typed from &apos;typed.js&apos;;

const typed = new Typed(&apos;#element&apos;, {
  strings: [&apos;&amp;lt;i&amp;gt;First&amp;lt;/i&amp;gt; sentence.&apos;, &apos;&amp;amp;amp; a second sentence.&apos;],
  typeSpeed: 50,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;HTML&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdnjs.cloudflare.com/ajax/libs/typicons/2.0.9/typicons.min.css&quot;&amp;gt;
  &amp;lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/typed.js/2.0.11/typed.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;&amp;lt;span id=&quot;typed&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/h1&amp;gt;

  &amp;lt;script&amp;gt;
    // 初始化 Typed.js
    var typed = new Typed(&apos;#typed&apos;, {
      strings: [&apos;Hello, World!&apos;, &apos;Welcome to Typed.js&apos;],
      typeSpeed: 50,
      backSpeed: 30,
      loop: true
    });
  &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;常用属性&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;strings&lt;/code&gt;：一个字符串数组，包含要展示的文本内容。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;typeSpeed&lt;/code&gt;：打字速度，表示每个字符输入的延迟时间（以毫秒为单位）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;startDelay&lt;/code&gt;：动画开始之前的延迟时间（以毫秒为单位）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backSpeed&lt;/code&gt;：删除速度，表示每个字符删除的延迟时间（以毫秒为单位）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backDelay&lt;/code&gt;：每次删除完成后的等待时间（以毫秒为单位）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loop&lt;/code&gt;：一个布尔值，指示动画是否应该循环播放。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loopCount&lt;/code&gt;：限制循环播放的次数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;showCursor&lt;/code&gt;：一个布尔值，指示是否显示光标。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cursorChar&lt;/code&gt;：光标的字符。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cursorSpeed&lt;/code&gt;：光标闪烁的速度（以毫秒为单位）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;smartBackspace&lt;/code&gt;：一个布尔值，指示是否启用智能删除，根据回退速度自动调整删除时间。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shuffle&lt;/code&gt;：一个布尔值，指示是否在打字之前随机打乱字符串数组。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;contentType&lt;/code&gt;：指定输入的内容类型，可以是 &lt;code&gt;&apos;html&apos;&lt;/code&gt;、&lt;code&gt;&apos;text&apos;&lt;/code&gt; 或 &lt;code&gt;&apos;null&apos;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onComplete&lt;/code&gt;：动画完成时的回调函数。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Pixi.js 开发者工具</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/pixijs-%E5%BC%80%E5%8F%91%E8%80%85%E5%B7%A5%E5%85%B7/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/pixijs-%E5%BC%80%E5%8F%91%E8%80%85%E5%B7%A5%E5%85%B7/</guid><pubDate>Sun, 21 May 2023 13:38:11 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;用于调试使用&lt;a href=&quot;http://pixijs.com/&quot;&gt;PixiJS&lt;/a&gt;编写的游戏和应用程序的浏览器扩展。&lt;/p&gt;
&lt;h2&gt;特征&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;显示场景图&lt;/li&gt;
&lt;li&gt;查看和编辑属性&lt;/li&gt;
&lt;li&gt;在大纲中双击以 console.log 节点&lt;/li&gt;
&lt;li&gt;在视口中勾勒出活动节点的轮廓。&lt;/li&gt;
&lt;li&gt;活动节点在开发者控制台中可用，如下所示&lt;code&gt;$pixi&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在视口中右键单击（或按住 Alt 单击）以激活节点&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;p&gt;从以下位置安装 PixiJS Devtools：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/pixi-inspector/aamddddknhcagpehecnhphigffljadon&quot;&gt;Chrome 网上应用店&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/pixijs-devtools/&quot;&gt;Firefox 附加组件&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;用法&lt;/h2&gt;
&lt;h3&gt;PixiJS&lt;/h3&gt;
&lt;p&gt;在您的代码_中找到&lt;code&gt;PIXI.Application&lt;/code&gt;创建实例的位置，它看起来像这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Application } from &quot;pixi.js&quot;;

const app = new Application(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过添加以下行公开&lt;code&gt;app&lt;/code&gt;给&lt;strong&gt;PixiJS Devtools ：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;globalThis.__PIXI_APP__ = app;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者取决于您的 TypeScript 和 ESLint 配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(globalThis as any).__PIXI_APP__ = app; // eslint-disable-line
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;自定义设置？&lt;/h2&gt;
&lt;p&gt;如果你不使用&lt;code&gt;PIXI.Application&lt;/code&gt;或&lt;code&gt;Phaser.Game&lt;/code&gt;？您可以手动指定根节点：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;globalThis.__PIXI_STAGE__ = yourContainer;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并启用突出显示和选择视口中的节点添加：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;globalThis.__PIXI_RENDERER__ = yourRenderer;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>服务架构的选择</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E7%9A%84%E9%80%89%E6%8B%A9/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E7%9A%84%E9%80%89%E6%8B%A9/</guid><pubDate>Sun, 23 Apr 2023 22:06:10 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;随着前端框架迅速发展，前后端分离和单页应用已成为开发行业的大趋势。它们带来了更好的开发效率、更灵活的架构设计以及更好的用户体验。&lt;/p&gt;
&lt;p&gt;但同样的，单页面应用也有着一些不容忽视的缺点，如何通过优化不同使用场景架构来解决这些问题，也是当代开发者需要面临的一个挑战。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;在这个以流量为王的时代，单页面应用因为页面是由 JavaScript 实时生成的，这对于只默认爬取网页源代码，不执行JS逻辑的爬虫来说，意味着如果你的内容是通过JS加载，则爬虫爬取不到。&lt;/p&gt;
&lt;p&gt;虽然谷歌更新了爬虫策略，对单页面网站的爬取进行了专门的优化。但是并非所有的搜索引擎都能很好地进行单页面网站的爬取，加上国内的搜索引擎生态，作为国内最大的搜索引擎的百度仍然不能很好地支持单页面的爬取，这使得国内开发者在进行单页面应用的开发时，对于 SEO 的优化始终是一个绕不过去的课题。&lt;/p&gt;
&lt;p&gt;那肯定有人说：“那我写需要搜索引擎爬取的网站时候使用多页面进行开发，不需要搜索引擎收录的再用单页面不就行了吗? ”&lt;/p&gt;
&lt;p&gt;虽然确实可以这样做，但是之所以单页面应用成为前端开发的主流，正是因为 Vue 和 Angular 这样的前端框架提供了丰富的工具和组件，简化了前端开发的复杂性，加上其强大的渲染能力、数据绑定和组件化等特性，使得开发人员可以更高效地构建现代化的用户界面。&lt;/p&gt;
&lt;p&gt;无论是对比开发效率、性能开销还是项目规模，单页面应用相较于多页面应用都有着无可比拟的优势。也正是如此，为了迎合国内搜索引擎的生态而放弃使用前沿的技术，颇有点因噎废食的感觉。但同样，作为开发者的我们很难改变国内搜索引擎对单页面网站的态度。&lt;/p&gt;
&lt;p&gt;那么既然无法改变环境就学会适应环境，让单页面应用也支持服务端渲染也是一个不错的选择。通过在服务器中实时运行单页面应用，在用户访问时将渲染好的单页面再发送给用户，这样保证了搜索引擎也能很好地识别网站内容，也保留了单页面开发的优势。虽然缺点可能就是加大了服务器负载，但也不是太大的问题。&lt;/p&gt;
&lt;p&gt;同时我们还要知道，任何事都不是绝对的，并不是用了服务器渲染就不能用客户端渲染了，这二者可以相结合，通过在每级路由进行灵活的配置，实现混合渲染。这样指定哪些地址使用客户端渲染，哪些地址使用服务器渲染，可以最大化发挥服务器和客户端的性能，甚至解决单页面首屏加载慢的问题。&lt;/p&gt;
&lt;p&gt;不同的应用场景可以选择不同的服务架构的搭配，像是搭建博客网站，为了更好地支持 SEO ，往往需要在服务器中进行页面的渲染。但是如果是一个后台管理系统，那么使用单页面来进行交互，会是一个非常优质的选择。&lt;/p&gt;
&lt;h3&gt;客户端渲染&lt;/h3&gt;
&lt;p&gt;常见使用场景：后台管理系统、游戏。&lt;/p&gt;
&lt;p&gt;使用需求：不需要 SEO、节省服务器性能、更快的响应速度。&lt;/p&gt;
&lt;p&gt;劣势：不能很好地被搜索引擎收录。&lt;/p&gt;
&lt;h3&gt;服务端渲染&lt;/h3&gt;
&lt;p&gt;常见使用场景：博客网站、媒体网站。&lt;/p&gt;
&lt;p&gt;使用需求：需要 SEO。&lt;/p&gt;
&lt;p&gt;劣势：增加了服务器性能的开销、新增功能需要重新打包发布。&lt;/p&gt;
&lt;h3&gt;混合渲染&lt;/h3&gt;
&lt;p&gt;常见使用场景：游戏网站、购物网站、工具类网站。&lt;/p&gt;
&lt;p&gt;使用需求：需要 SEO、新增功能可以不重新打包发布。&lt;/p&gt;
&lt;p&gt;劣势：增加了项目复杂度。&lt;/p&gt;
</content:encoded></item><item><title>在Node.JS中使用OpenCV库</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E5%9C%A8nodejs%E4%B8%AD%E4%BD%BF%E7%94%A8opencv%E5%BA%93/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E5%9C%A8nodejs%E4%B8%AD%E4%BD%BF%E7%94%A8opencv%E5%BA%93/</guid><pubDate>Mon, 20 Mar 2023 18:32:57 GMT</pubDate><content:encoded>&lt;p&gt;安装opencv4nodejs库，在此之前你要先确定你的电脑里已经安装过OpenCV库，如果没有就先去安装OpenCV库。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install @u4/opencv4nodejs -S
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;方法一：使用&quot;build-opencv&quot;来构建&lt;/p&gt;
&lt;p&gt;在package.json里添加以下内容，里面的路径填你安装OpenCV的位置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
  &quot;build&quot;: &quot;build-opencv --incDir E:\\opencv\\build\\include --libDir E:\\opencv\\build\\x64\\vc16\\lib --binDir E:\\opencv\\build\\x64\\vc16\\bin --nobuild rebuild&quot;
  },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保存后在命令行输入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;等待build-opencv完成构建后再打开package.json，添加以下内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &quot;opencv4nodejs&quot;: {
    &quot;disableAutoBuild&quot;: &quot;1&quot;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保存后即可使用&lt;/p&gt;
&lt;p&gt;方法二：使用&quot;npm link&quot;来连接&lt;/p&gt;
&lt;p&gt;在package.json里添加以下内容，里面的路径填写你安装OpenCV的位置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &quot;opencv4nodejs&quot;: {
    &quot;disableAutoBuild&quot;: &quot;1&quot;,
    &quot;OPENCV_INCLUDE_DIR&quot;: &quot;E:\\opencv\\build\\include&quot;,
    &quot;OPENCV_LIB_DIR&quot;: &quot;E:\\opencv\\build\\x64\\vc16\\lib&quot;,
    &quot;OPENCV_BIN_DIR&quot;: &quot;E:\\opencv\\build\\x64\\vc16\\bin&quot;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保存后在命令行输入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm link
build-opencv --nobuild rebuild
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;等待指令结束就可以愉快地在Node.JS里运行OpenCV了&lt;/p&gt;
</content:encoded></item><item><title>解构赋值语法</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E8%A7%A3%E6%9E%84%E8%B5%8B%E5%80%BC%E8%AF%AD%E6%B3%95/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E8%A7%A3%E6%9E%84%E8%B5%8B%E5%80%BC%E8%AF%AD%E6%B3%95/</guid><pubDate>Fri, 03 Mar 2023 19:14:43 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;解构赋值语法（destructuring assignment）是 ECMAScript 6（ES6）引入的新特性，允许我们从数组或对象中提取值并将其赋值给变量。它可以简化代码，并提供了一种方便的方式来访问和使用复杂数据结构的值。&lt;/p&gt;
&lt;p&gt;解构赋值的语法有两种形式：数组解构和对象解构。&lt;/p&gt;
&lt;h2&gt;示例&lt;/h2&gt;
&lt;h3&gt;数组解构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const [a, b, c] = [1, 2, 3];
console.log(a); // 输出 1
console.log(b); // 输出 2
console.log(c); // 输出 3
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;对象解构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const { name, age } = { name: &apos;John&apos;, age: 30 };
console.log(name); // 输出 &apos;John&apos;
console.log(age); // 输出 30
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在数组解构中，我们使用方括号 &lt;code&gt;[]&lt;/code&gt; 将要提取的值括起来，并将其赋值给相应的变量。在对象解构中，我们使用花括号 &lt;code&gt;{}&lt;/code&gt; 将要提取的属性括起来，并将其赋值给相应的变量。&lt;/p&gt;
&lt;p&gt;解构赋值语法可以用于函数参数、对象属性的提取、嵌套结构的解构等场景，使得代码更简洁、可读性更高，并且能够轻松地从复杂的数据结构中提取所需的值。&lt;/p&gt;
&lt;h2&gt;解构对象重新命名&lt;/h2&gt;
&lt;p&gt;在解构赋值语法中，可以使用冒号 &lt;code&gt;:&lt;/code&gt; 来为解构出的变量重新命名。&lt;/p&gt;
&lt;p&gt;在数组解构中，可以使用重新命名的方式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const [a, b, c] = [1, 2, 3];
const [x, y, z] = [4, 5, 6];
console.log(a); // 输出 1
console.log(x); // 输出 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在对象解构中，可以使用冒号来重新命名&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const { name: firstName, age: userAge } = { name: &apos;John&apos;, age: 30 };
console.log(firstName); // 输出 &apos;John&apos;
console.log(userAge); // 输出 30
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上述示例中，我们使用冒号 &lt;code&gt;:&lt;/code&gt; 将原本的变量名和新的变量名进行分隔，左边是原始的变量名，右边是重新命名后的变量名。&lt;/p&gt;
&lt;p&gt;通过这种方式，我们可以灵活地为解构出的变量指定新的名称，以便更好地符合代码的语义和需求。&lt;/p&gt;
</content:encoded></item><item><title>RobotJS截取屏幕screen.capture踩坑</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/robotjs%E6%88%AA%E5%8F%96%E5%B1%8F%E5%B9%95screencapture%E8%B8%A9%E5%9D%91/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/robotjs%E6%88%AA%E5%8F%96%E5%B1%8F%E5%B9%95screencapture%E8%B8%A9%E5%9D%91/</guid><pubDate>Mon, 20 Feb 2023 14:32:41 GMT</pubDate><content:encoded>&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;调用 robot.screen.capture() 或 robot.screen.capture(0,0,1920,1080)，返回的Bitmap对象是色彩格式是BGR色彩,这导致了如果未经处理就直接生成图像，色彩会产生错误，&lt;/p&gt;
&lt;h2&gt;处理方法&lt;/h2&gt;
&lt;p&gt;只需将BGR色彩转换成RGB色彩即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const robot = require(&apos;robotjs&apos;);
const jimp = require(&quot;jimp&quot;);

const swapRedAndBlueChannel = bmp =&amp;gt; {
    for (let i = 0; i &amp;lt; (bmp.width * bmp.height) * 4; i += 4) { // swap red and blue channel
        [bmp.image[i], bmp.image[i + 2]] = [bmp.image[i + 2], bmp.image[i]]; // red channel;
    }
};

const screenshot = robot.screen.capture();
swapRedAndBlueChannel(screenshot);
const screenJimp = new jimp({ data: screenshot.image, width: screenshot.width, height: screenshot.height });

screenJimp.write(&apos;screenshot.png&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你有使用OpenCV，则可以使用“COLOR_BGR2RGB”函数直接转换&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const cv = require(&apos;@u4/opencv4nodejs&apos;);
const robot = require(&apos;robotjs&apos;);
const jimp = require(&quot;jimp&quot;);


const screenshot = robot.screen.capture();
const screenJimp = new jimp({ data: screenshot.image, width: screenshot.width, height: screenshot.height });

screenJimp.getBuffer(jimp.MIME_PNG, function (err, buffer) {
    if (err) {
        return;
    }
    const screen = cv.imdecode(Buffer.from(buffer)).cvtColor(cv.COLOR_BGR2RGB);
    console.log(screen)
});
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>NPM 管理包</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/npm-%E7%AE%A1%E7%90%86%E5%8C%85/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/npm-%E7%AE%A1%E7%90%86%E5%8C%85/</guid><pubDate>Sun, 05 Feb 2023 10:12:08 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;当今的软件开发领域充满了各种强大的工具和库，而在这个丰富的生态系统中，NPM（Node Package Manager）无疑是最受欢迎和广泛使用的代码包管理工具之一。无论是前端开发、后端开发还是构建工具，NPM都扮演着至关重要的角色。&lt;/p&gt;
&lt;p&gt;在过去，开发人员往往需要手动下载和管理各种依赖项，而且随着项目规模的增长，这种方式变得非常繁琐且容易出错。NPM的出现极大地简化了这个过程，使开发人员能够轻松地安装、更新和管理依赖项。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/&quot;&gt;官方网站&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;登录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm login
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上传包&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm publish
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;删除包&lt;/p&gt;
&lt;p&gt;前提你是包的作者，并且包上传的时间不超过 24 小时。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm unpublish &amp;lt;package-name&amp;gt;[@&amp;lt;version&amp;gt;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你不加版本删除包则会删除所有版本，这是一个危险的操作，并且会提示不能直接删除。&lt;/p&gt;
&lt;p&gt;如果你想要真的想要删除整个项目，可以在命令中加入 &lt;code&gt;--force&lt;/code&gt; 参数。但是，需要注意的是，这将删除整个项目，包括项目中的所有包和文件，这是一个危险操作，请确保你了解操作的后果。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm unpublish &amp;lt;package-name&amp;gt;[@&amp;lt;version&amp;gt;] --force
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：默认情况下，npm 允许在发布后的 24 小时内删除包。如果包已过期或发布时间超过 24 小时，则无法直接删除。在这种情况下，你可以通过与 npm 支持团队联系，请求他们删除该包。&lt;/p&gt;
</content:encoded></item><item><title>NPM 安装生产环境依赖</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/npm-%E5%AE%89%E8%A3%85%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%BE%9D%E8%B5%96/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/npm-%E5%AE%89%E8%A3%85%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%BE%9D%E8%B5%96/</guid><pubDate>Sat, 04 Feb 2023 13:42:23 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;默认情况下，npm install 将安装列为依赖项的所有模块 package.json。&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;--production&lt;/code&gt; 标志（或将 NODE_ENV 环境变量设置为 production）时，npm 将不会安装中列出的模块 devDependencies。&lt;/p&gt;
&lt;p&gt;注意：将 --production 依赖项添加到项目时，该标志没有特殊含义。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm install --production
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>NPM 包安装策略</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/npm-%E5%8C%85%E5%AE%89%E8%A3%85%E7%AD%96%E7%95%A5/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/npm-%E5%8C%85%E5%AE%89%E8%A3%85%E7%AD%96%E7%95%A5/</guid><pubDate>Sat, 04 Feb 2023 10:12:08 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;在 npm 中，&quot;安装策略&quot;（Installation Strategy）是指用于解析和管理包的依赖项的方法和规则。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;h3&gt;install-strategy&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;默认值：&quot;hoisted&quot;&lt;/p&gt;
&lt;p&gt;Default: &quot;hoisted&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;类型：&quot;hoisted&quot;、&quot;nested&quot;、&quot;shallow&quot; 或 &quot;linked&quot;&lt;/p&gt;
&lt;p&gt;Type: &quot;hoisted&quot;, &quot;nested&quot;, &quot;shallow&quot;, or &quot;linked&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;设置在 node_modules 中安装包的策略。提升（默认）：在顶层安装非复制，并在目录结构中根据需要复制。嵌套：（以前的 --legacy-bundling）安装到位，无需吊装。浅层（以前的 --global-style）只在顶层安装直接的 deps。链接：（实验性）安装在 node_modules/.store 中，链接到位，未提升。&lt;/p&gt;
&lt;p&gt;Sets the strategy for installing packages in node_modules. hoisted (default): Install non-duplicated in top-level, and duplicated as necessary within directory structure. nested: (formerly --legacy-bundling) install in place, no hoisting. shallow (formerly --global-style) only install direct deps at top-level. linked: (experimental) install in node_modules/.store, link in place, unhoisted.&lt;/p&gt;
&lt;p&gt;简单示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install --install-strategy=shallow &amp;lt;package-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过这个指令 npm 使用浅层安装策略，类似于以前的 &lt;code&gt;global-style&lt;/code&gt; 选项的行为。它可以将指定包以及它的所有依赖放到同一个文件夹内，而不是平铺在 node_modules 中。&lt;/p&gt;
</content:encoded></item><item><title>Node.js 批量下载文件</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/nodejs-%E6%89%B9%E9%87%8F%E4%B8%8B%E8%BD%BD%E6%96%87%E4%BB%B6/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/nodejs-%E6%89%B9%E9%87%8F%E4%B8%8B%E8%BD%BD%E6%96%87%E4%BB%B6/</guid><pubDate>Fri, 20 Jan 2023 14:18:21 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;在平时如果遇到需要批量下载连续的文件，我们可以写一段脚本，让其自动下载。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;const fs = require(&quot;fs&quot;);
const request = require(&quot;request&quot;);

const syncDownload = (url, path) =&amp;gt; {
  return new Promise((resolve) =&amp;gt; {
    let writeStream = fs.createWriteStream(path);
    let readStream = request(url);
    readStream.pipe(writeStream);
    readStream.on(&quot;end&quot;, function (err) {
      if (err) {
        console.log(err);
        resolve(&quot;err&quot;);
        return;
      }
      resolve(&quot;ok&quot;);
    });
  });
};
const errs = [];
//需要爬取的数据
const data = [
  {
    url: &quot;&quot;,
    filename: &quot;&quot;,
  },
];
async function downloadFile() {
  for (let i = 0, length = data.length; i &amp;lt; length; i++) {
    let result = await syncDownload(data[i].url, `./save/${data[i].filename}`);
    if (result === &quot;ok&quot;) {
      console.log(`第 ${i + 1} 个文件下载成功`);
    } else {
      console.log(`第 ${i + 1} 个文件下载失败！！！`);
      errs.push(data[i]);
    }
  }
  console.log(errs);
  console.log(`共有 ${errs.length} 个文件下载失败`);
}
downloadFile();
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>数组遍历</title><link>https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E6%95%B0%E7%BB%84%E9%81%8D%E5%8E%86/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2023%E5%B9%B4/%E6%95%B0%E7%BB%84%E9%81%8D%E5%8E%86/</guid><pubDate>Thu, 12 Jan 2023 18:56:50 GMT</pubDate><content:encoded>&lt;h2&gt;forEach&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;forEach()&lt;/code&gt;方法用于遍历数组中的每个元素，对每个元素执行指定的操作。与 &lt;code&gt;map()&lt;/code&gt; 不同的是，&lt;code&gt;forEach()&lt;/code&gt;不会返回一个新的数组，而是直接对原始数组进行操作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const numbers = [1, 2, 3, 4, 5];

numbers.forEach(function(num) {
  console.log(num);
});

// Output:
// 1
// 2
// 3
// 4
// 5
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;map&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;map()&lt;/code&gt;方法是用于对数组中的每个元素进行转换，返回一个新的数组，该数组包含了每个元素被转换后的结果。具体来说，&lt;code&gt;map()&lt;/code&gt;方法会遍历原始数组中的每个元素，将每个元素传递给回调函数进行处理，并将处理后的结果添加到新的数组中。原始数组不会被改变。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map(function (num) {
  return num * 2;
});
console.log(doubledNumbers); // [2, 4, 6, 8, 10]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;filter&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;filter()&lt;/code&gt;方法是用于从数组中筛选出满足特定条件的元素，返回一个新的数组，该数组包含了符合条件的元素。具体来说，&lt;code&gt;filter()&lt;/code&gt;方法会遍历原始数组中的每个元素，将每个元素传递给回调函数进行处理，如果回调函数返回 &lt;code&gt;true&lt;/code&gt;，则该元素会被添加到新的数组中。原始数组不会被改变。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(function (num) {
  return num % 2 === 0;
});
console.log(evenNumbers); // [2, 4]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;find&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;find()&lt;/code&gt;方法会返回数组中第一个满足给定测试函数的元素的值，否则返回 undefined&lt;/p&gt;
&lt;p&gt;&lt;code&gt;find()&lt;/code&gt;方法接受一个回调函数作为参数，该回调函数接受三个参数：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;element&lt;/code&gt;：当前遍历到的数组元素。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;index&lt;/code&gt;（可选）：当前元素的索引。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;array&lt;/code&gt;（可选）：正在操作的数组。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;回调函数应该返回一个布尔值。如果返回 &lt;code&gt;true&lt;/code&gt;，则 &lt;code&gt;Array.prototype.find()&lt;/code&gt; 方法会返回当前遍历到的元素，如果没有找到满足条件的元素，则返回 &lt;code&gt;undefined&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const array = [1, 2, 3, 4, 5];
const found = array.find(element =&amp;gt; element === 3);
console.log(found); // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;for...of&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;for...of&lt;/code&gt; 是 JavaScript 中一种用于遍历可迭代对象的语法结构。它提供了一种更简洁、直观的方式来遍历数组、字符串、集合等可迭代对象的元素。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 遍历数组
const myArray = [1, 2, 3];
for (const element of myArray) {
  console.log(element);
}

// 遍历字符串
const myString = &apos;Hello&apos;;
for (const char of myString) {
  console.log(char);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;for...of&lt;/code&gt; 循环本身不提供直接的索引（index）访问。但可以通过结合使用数组的 &lt;code&gt;entries&lt;/code&gt; 方法和解构赋值来获取索引和元素。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const myArray = [&apos;a&apos;, &apos;b&apos;, &apos;c&apos;];

for (const [index, element] of myArray.entries()) {
  console.log(`Index: ${index}, Element: ${element}`);
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>页面滚动元素动画 Aos.js</title><link>https://fuwari.vercel.app/posts/2022%E5%B9%B4/%E9%A1%B5%E9%9D%A2%E6%BB%9A%E5%8A%A8%E5%85%83%E7%B4%A0%E5%8A%A8%E7%94%BB-aosjs/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2022%E5%B9%B4/%E9%A1%B5%E9%9D%A2%E6%BB%9A%E5%8A%A8%E5%85%83%E7%B4%A0%E5%8A%A8%E7%94%BB-aosjs/</guid><pubDate>Tue, 15 Nov 2022 10:52:01 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;滚动时为页面上的元素设置动画的小型库。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/michalsnik/aos&quot;&gt;Github&lt;/a&gt; &lt;a href=&quot;http://michalsnik.github.io/aos&quot;&gt;示例&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;h3&gt;🌟 示例&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://codepen.io/michalsnik/pen/WxNdvq&quot;&gt;不同的内置动画&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://codepen.io/michalsnik/pen/jrOYVO&quot;&gt;使用锚点设置&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://codepen.io/michalsnik/pen/EyxoNm&quot;&gt;使用锚点放置和不同的缓动&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://codepen.io/michalsnik/pen/WxvNvE&quot;&gt;使用简单的自定义动画&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;👉 为了更好地理解这实际上是如何工作的，我鼓励您查看 &lt;a href=&quot;https://css-tricks.com/aos-css-driven-scroll-animation-library/&quot;&gt;关于 CSS-tricks 的帖子&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;⚙ 安装&lt;/h2&gt;
&lt;h3&gt;基本的&lt;/h3&gt;
&lt;p&gt;在中添加样式&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://unpkg.com/aos@next/dist/aos.css&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在关闭标签之前添加脚本&lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt;，并初始化 AOS：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;script src=&quot;https://unpkg.com/aos@next/dist/aos.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;script&amp;gt;
    AOS.init();
  &amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;使用包管理器&lt;/h3&gt;
&lt;p&gt;安装 &lt;code&gt;aos&lt;/code&gt; 包：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn add aos@next
# or
npm install --save aos@next
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;导入脚本、样式并初始化 AOS：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import AOS from &apos;aos&apos;;
import &apos;aos/dist/aos.css&apos;; // You can also use &amp;lt;link&amp;gt; for styles
// ..
AOS.init();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了使其正常工作，您必须确保您的构建过程已配置样式加载器，并将其全部正确捆绑。但是，如果您使用的是&lt;a href=&quot;https://parceljs.org/&quot;&gt;Parcel&lt;/a&gt;，它将按照提供的方式开箱即用。&lt;/p&gt;
&lt;h2&gt;🤔 如何使用它？&lt;/h2&gt;
&lt;h3&gt;1. 初始化AOS：&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;AOS.init();

// You can also pass an optional settings object
// below listed default settings
AOS.init({
  // Global settings:
  disable: false, // accepts following values: &apos;phone&apos;, &apos;tablet&apos;, &apos;mobile&apos;, boolean, expression or function
  startEvent: &apos;DOMContentLoaded&apos;, // name of the event dispatched on the document, that AOS should initialize on
  initClassName: &apos;aos-init&apos;, // class applied after initialization
  animatedClassName: &apos;aos-animate&apos;, // class applied on animation
  useClassNames: false, // if true, will add content of `data-aos` as classes on scroll
  disableMutationObserver: false, // disables automatic mutations&apos; detections (advanced)
  debounceDelay: 50, // the delay on debounce used while resizing window (advanced)
  throttleDelay: 99, // the delay on throttle used while scrolling the page (advanced)
  

  // Settings that can be overridden on per-element basis, by `data-aos-*` attributes:
  offset: 120, // offset (in px) from the original trigger point
  delay: 0, // values from 0 to 3000, with step 50ms
  duration: 400, // values from 0 to 3000, with step 50ms
  easing: &apos;ease&apos;, // default easing for AOS animations
  once: false, // whether animation should happen only once - while scrolling down
  mirror: false, // whether elements should animate out while scrolling past them
  anchorPlacement: &apos;top-bottom&apos;, // defines which position of the element regarding to window should trigger the animation

});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 使用属性设置动画&lt;code&gt;data-aos&lt;/code&gt;：&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;div data-aos=&quot;fade-in&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并使用&lt;code&gt;data-aos-*&lt;/code&gt;属性调整行为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;div
    data-aos=&quot;fade-up&quot;
    data-aos-offset=&quot;200&quot;
    data-aos-delay=&quot;50&quot;
    data-aos-duration=&quot;1000&quot;
    data-aos-easing=&quot;ease-in-out&quot;
    data-aos-mirror=&quot;true&quot;
    data-aos-once=&quot;false&quot;
    data-aos-anchor-placement=&quot;top-center&quot;
  &amp;gt;
  &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/michalsnik/aos#animations&quot;&gt;查看所有动画、缓动和锚点放置的完整列表&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;锚&lt;/h4&gt;
&lt;p&gt;还有一个设置只能在每个元素的基础上使用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;data-aos-anchor&lt;/code&gt;- 其偏移量将用于触发动画而不是实际动画的元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div data-aos=&quot;fade-up&quot; data-aos-anchor=&quot;.other-element&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过这种方式，您可以在一个元素上触发动画，同时滚动到另一个元素 - 这对于为固定元素设置动画很有用。&lt;/p&gt;
&lt;h2&gt;应用程序接口&lt;/h2&gt;
&lt;p&gt;AOS 对象作为全局变量公开，目前有以下三种方法可用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;init&lt;/code&gt;- 初始化 AOS&lt;/li&gt;
&lt;li&gt;&lt;code&gt;refresh&lt;/code&gt;- 重新计算元素的所有偏移量和位置（在调整窗口大小时调用）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;refreshHard&lt;/code&gt;- 使用 AOS 元素和触发器重新初始化数组&lt;code&gt;refresh&lt;/code&gt;（调用与元素相关的 DOM 更改&lt;code&gt;aos&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;执行示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AOS.refresh();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认情况下，AOS 正在监视 DOM 更改，如果有任何异步加载的新元素，或者当某些内容从 DOM 中删除时，它会&lt;code&gt;refreshHard&lt;/code&gt;自动调用。IE等不支持的浏览器&lt;code&gt;MutationObserver&lt;/code&gt;可能需要&lt;code&gt;AOS.refreshHard()&lt;/code&gt;自己调用。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;refresh&lt;/code&gt;方法在调整窗口大小等时被调用，因为它不需要使用 AOS 元素构建新商店并且应该尽可能轻。&lt;/p&gt;
&lt;h2&gt;JS事件&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;aos:in&lt;/code&gt; AOS 在 document: 和 &lt;code&gt;aos:out&lt;/code&gt; 任何元素动画进出时调度两个事件，以便您可以在 JS 中做额外的事情：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;document.addEventListener(&apos;aos:in&apos;, ({ detail }) =&amp;gt; {
  console.log(&apos;animated in&apos;, detail);
});

document.addEventListener(&apos;aos:out&apos;, ({ detail }) =&amp;gt; {
  console.log(&apos;animated out&apos;, detail);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;您还可以通过设置属性告诉 AOS 在特定元素上触发自定义事件&lt;code&gt;data-aos-id&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div data-aos=&quot;fade-in&quot; data-aos-id=&quot;super-duper&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后您将能够收听两个自定义事件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;aos:in:super-duper&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aos:out:super-duper&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;秘诀：&lt;/h2&gt;
&lt;h4&gt;添加自定义动画：&lt;/h4&gt;
&lt;p&gt;有时内置动画还不够。假设您需要一个盒子来根据分辨率设置不同的动画。您可以这样做：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[data-aos=&quot;new-animation&quot;] {
  opacity: 0;
  transition-property: transform, opacity;

  &amp;amp;.aos-animate {
    opacity: 1;
  }

  @media screen and (min-width: 768px) {
    transform: translateX(100px);

    &amp;amp;.aos-animate {
      transform: translateX(0);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在 HTML 中使用它：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div data-aos=&quot;new-animation&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该元素只会在移动设备上动画不透明度，但从 768px 宽度开始，它也会从右向左滑动。&lt;/p&gt;
&lt;h4&gt;添加自定义缓动：&lt;/h4&gt;
&lt;p&gt;与动画类似，您可以添加自定义缓动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[data-aos] {
  body[data-aos-easing=&quot;new-easing&quot;] &amp;amp;,
  &amp;amp;[data-aos][data-aos-easing=&quot;new-easing&quot;] {
    transition-timing-function: cubic-bezier(.250, .250, .750, .750);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;自定义默认动画距离&lt;/h4&gt;
&lt;p&gt;内置动画的默认距离是 100px。不过，只要您使用的是 SCSS，就可以覆盖它：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$aos-distance: 200px; // It has to be above import
@import &apos;node_modules/aos/src/sass/aos.scss&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是，您必须配置构建过程以允许它预先导入样式 &lt;code&gt;node_modules&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;集成外部CSS动画库（如Animate.css）：&lt;/h4&gt;
&lt;p&gt;用于&lt;code&gt;animatedClassName&lt;/code&gt;更改 AOS 的默认行为，以应用放置在滚动中的类名&lt;code&gt;data-aos&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div data-aos=&quot;fadeInUp&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;AOS.init({
  useClassNames: true,
  initClassName: false,
  animatedClassName: &apos;animated&apos;,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的元素将得到两个类：&lt;code&gt;animated&lt;/code&gt;和&lt;code&gt;fadeInUp&lt;/code&gt;。使用以上三个设置的不同组合，您应该能够集成任何外部 CSS 动画库。&lt;/p&gt;
&lt;p&gt;然而，外部库并不太关心实际动画之前的动画状态。因此，如果您希望这些元素在滚动之前不可见，您可能需要添加类似的样式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[data-aos] {
  visibility: hidden;
}
[data-aos].animated {
  visibility: visible;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;注意事项：&lt;/h2&gt;
&lt;h4&gt;设置：&lt;code&gt;duration&lt;/code&gt;,&lt;code&gt;delay&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;持续时间和延迟接受从 50 到 3000 的值，步长为 50 毫秒，这是因为它们是由 css 处理的，并且为了不使 css 比它已经存在的时间更长，我只实现了一个子集。我相信这些应该涵盖大多数情况。&lt;/p&gt;
&lt;p&gt;如果没有，您可以编写简单的 CSS 来添加另一个持续时间，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  body[data-aos-duration=&apos;4000&apos;] [data-aos],
  [data-aos][data-aos][data-aos-duration=&apos;4000&apos;] {
    transition-duration: 4000ms;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此代码将添加 4000 毫秒持续时间，供您在 AOS 元素上设置，或在初始化 AOS 脚本时设置为全局持续时间。请注意 double &lt;code&gt;[data-aos][data-aos]&lt;/code&gt;- 这不是一个错误，它是一个技巧，使个人设置比全局设置更重要，而不需要在那里写丑陋的“！重要”:)&lt;/p&gt;
&lt;p&gt;用法示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;div data-aos=&quot;fade-in&quot; data-aos-duration=&quot;4000&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Nginx 负载均衡</title><link>https://fuwari.vercel.app/posts/2022%E5%B9%B4/nginx-%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2022%E5%B9%B4/nginx-%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1/</guid><pubDate>Mon, 07 Nov 2022 15:37:24 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;在 Nginx 中，负载均衡可以通过使用其内置的负载均衡模块来实现。Nginx 提供了多种负载均衡策略，如轮询（Round Robin）、IP 哈希（IP Hash）、最少连接（Least Connections）等。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;下面是一种基本的配置示例，演示了如何使用 Nginx 实现负载均衡：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http {
    upstream backend {
        server backend1.example.com;
        server backend2.example.com;
        server backend3.example.com;
    }

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述配置示例中，&lt;code&gt;upstream&lt;/code&gt; 块定义了后端服务器的列表，可以通过添加多个 &lt;code&gt;server&lt;/code&gt; 来指定不同的后端服务器。然后，在 &lt;code&gt;location&lt;/code&gt; 块中，使用 &lt;code&gt;proxy_pass&lt;/code&gt; 指令将请求代理到后端服务器，实现负载均衡。&lt;code&gt;proxy_set_header&lt;/code&gt; 指令用于设置一些 HTTP 请求头信息，以便将请求正确地传递给后端服务器。&lt;/p&gt;
&lt;p&gt;此外，你还可以根据需求选择适合的负载均衡策略。例如，如果要基于客户端的 IP 地址进行负载均衡，可以使用 IP 哈希策略：&lt;/p&gt;
&lt;p&gt;nginxCopy code&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;upstream backend {
    ip_hash;
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
}```

上述配置中的 `ip_hash` 指令将根据客户端的 IP 地址将请求发送到相同的后端服务器，从而实现会话保持。

这只是负载均衡的一个简单示例，Nginx 还提供了更多高级的负载均衡配置选项，如权重调整、健康检查、负载均衡器的备份服务器等。你可以根据自己的需求进一步探索和配置。&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>纯静态服务器配置</title><link>https://fuwari.vercel.app/posts/2022%E5%B9%B4/%E7%BA%AF%E9%9D%99%E6%80%81%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%85%8D%E7%BD%AE/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2022%E5%B9%B4/%E7%BA%AF%E9%9D%99%E6%80%81%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%85%8D%E7%BD%AE/</guid><pubDate>Sat, 11 Jun 2022 18:40:09 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;仅提供静态内容（如HTML、CSS、JavaScript、图像等），不支持动态内容或服务器端脚本的服务器。&lt;/p&gt;
&lt;h3&gt;优点：&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;高性能：不需要处理动态内容生成和数据库查询等复杂操作，具有卓越的性能和响应速度。&lt;/li&gt;
&lt;li&gt;简单：服务器的配置和部署通常相对简单，不需要配置数据库连接或处理服务器端脚本语言的环境。使得更易于维护和管理。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;缺点：&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;功能有限：纯静态服务器无法提供动态内容或实时数据处理。只能呈现预先生成的静态文件，无法根据用户请求或其他外部条件进行个性化响应。&lt;/li&gt;
&lt;li&gt;缺乏交互：无法实现复杂的用户操作，如用户登录、表单提交等。&lt;/li&gt;
&lt;li&gt;更新和维护困难：对于频繁更新的内容，例如新闻文章、博客等，纯静态服务器可能需要手动更新静态文件并重新部署才能使更改生效。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;使用场景&lt;/h3&gt;
&lt;p&gt;游戏宣传页、下载站、个人简历、CDN服务。&lt;/p&gt;
&lt;h2&gt;如何实现&lt;/h2&gt;
&lt;p&gt;常见的做法是使用 &lt;strong&gt;Web 服务器&lt;/strong&gt;来进行静态服务器的部署，通过转发特定端口的静态资源服务来进行。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;p&gt;使用 Express&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import express from &quot;express&quot;;

const app = express(); // 创建 express 应用
app.use(express.static(&quot;public&quot;)); // 配置静态文件目录，用于提供静态资源

const port = 3000;

// 监听指定端口
app.listen(port, () =&amp;gt; {
  console.log(`服务在 ${port} 端口运行`);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 Nginx&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http {
    include mime.types;
    default_type application/octet-stream;
    sendfile on;
    keepalive_timeout 65;

    gzip on;

    server {
        listen 80;
        server_name localhost; #你的域名
        root html;
        index index.html index.htm;
        location / {
            proxy_pass http://localhost:3000; #你的代理服务
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>使用js来执行“置换”：给定N和k，列出所有置换</title><link>https://fuwari.vercel.app/posts/2022%E5%B9%B4/%E4%BD%BF%E7%94%A8js%E6%9D%A5%E6%89%A7%E8%A1%8C%E7%BD%AE%E6%8D%A2%E7%BB%99%E5%AE%9An%E5%92%8Ck%E5%88%97%E5%87%BA%E6%89%80%E6%9C%89%E7%BD%AE%E6%8D%A2/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2022%E5%B9%B4/%E4%BD%BF%E7%94%A8js%E6%9D%A5%E6%89%A7%E8%A1%8C%E7%BD%AE%E6%8D%A2%E7%BB%99%E5%AE%9An%E5%92%8Ck%E5%88%97%E5%87%BA%E6%89%80%E6%9C%89%E7%BD%AE%E6%8D%A2/</guid><pubDate>Fri, 18 Mar 2022 11:41:21 GMT</pubDate><content:encoded>&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;使用js来执行“置换”：给定N和k，列出所有置换&lt;br /&gt;
-例如，N=4，k=3:123124132134142143213214，。。。432（其中有24个）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var replacement = (N, k) =&amp;gt; {
    let result = [];
    if (N &amp;lt; 0) {
        console.log(&apos;N值不能为负数&apos;)
    } else if (k &amp;lt; 1) {
        console.log(&apos;k值不能为0和负数&apos;)
    } else if (k &amp;gt; N) {
        console.log(&apos;k值不能大于置换长度&apos;)
    } else {
        //将N值转化为数组
        let data = []
        for (let i = 1; i &amp;lt;= N; i++) {
            data.push(i)
        }
        //构建多叉树树干
        getBranch(data).map((p) =&amp;gt; {
            //开始遍历
            dataToTree(getBranch(p.data), 0, [p.index]);
        })
    }
    //获取当前分支下可置换数组
    function getBranch(arr) {
        let data = []
        for (let i = 0; i &amp;lt; arr.length; i++) {
            let data_copy = JSON.parse(JSON.stringify(arr));
            let index = data_copy.splice(i, 1)
            data.push({
                index: index[0],
                data: data_copy
            })
        }
        return data;
    }
    //多叉树
    function dataToTree(d, t, e) {
        //d为数据,t为次数,e为遍历继承的数据
        //多叉树构建深度
        if (t &amp;lt; k - 1) {
            d.map((p) =&amp;gt; {
                if (p.data.length &amp;gt;= 1) { //如果还有分支就再次遍历
                    let e2 = JSON.parse(JSON.stringify(e));
                    let t2 = t + 1;
                    e2.push(p.index)
                    dataToTree(getBranch(p.data), t2, e2)
                } else { //没有了就将结果放入result数组
                    let e2 = JSON.parse(JSON.stringify(e));
                    e2.push(p.index)
                    result.push(e2);
                }
            });
        } else {
            result.push(e);
        }
    }
    return result;
}
//示例
var replaceResult = replacement(4, 3)
console.log(replaceResult)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>二维码生成 QRCode.js</title><link>https://fuwari.vercel.app/posts/2021%E5%B9%B4/%E4%BA%8C%E7%BB%B4%E7%A0%81%E7%94%9F%E6%88%90-qrcodejs/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2021%E5%B9%B4/%E4%BA%8C%E7%BB%B4%E7%A0%81%E7%94%9F%E6%88%90-qrcodejs/</guid><pubDate>Thu, 18 Nov 2021 17:58:16 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;如果你需要在浏览器中生成二维码，你可以尝试使用 QRCode.js 。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/davidshimjs/qrcodejs&quot;&gt;Github&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div id=&quot;qrcode&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
new QRCode(document.getElementById(&quot;qrcode&quot;), &quot;http://jindo.dev.naver.com/collie&quot;);
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者添加一些选项&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div id=&quot;qrcode&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
var qrcode = new QRCode(document.getElementById(&quot;qrcode&quot;), {
	text: &quot;http://jindo.dev.naver.com/collie&quot;,
	width: 128,
	height: 128,
	colorDark : &quot;#000000&quot;,
	colorLight : &quot;#ffffff&quot;,
	correctLevel : QRCode.CorrectLevel.H
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你可以使用一些方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;qrcode.clear(); // 清除二维码.
qrcode.makeCode(&quot;http://naver.com&quot;); // 重新生成其他二维码.
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>倒计时功能</title><link>https://fuwari.vercel.app/posts/2021%E5%B9%B4/%E5%80%92%E8%AE%A1%E6%97%B6%E5%8A%9F%E8%83%BD/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2021%E5%B9%B4/%E5%80%92%E8%AE%A1%E6%97%B6%E5%8A%9F%E8%83%BD/</guid><pubDate>Sun, 15 Aug 2021 14:55:23 GMT</pubDate><content:encoded>&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;原生写法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const laborDayTime = new Date(&quot;5 1,2023&quot;).getTime();
setInterval(() =&amp;gt; {
  let nowDayTime = new Date().getTime();
  let remainingTime = getRemainingTime(laborDayTime - nowDayTime);
  console.log(`距离距离五一还有:${remainingTime}`);
}, 1000);

function getRemainingTime(time) {
  let day = getDateTimeDay(time);
  let hours = getDateTimeHour(time);
  let minutes = getDateTimeMinute(time);
  let seconds = getDateTimeSecond(time);
  return `${day}天${hours}小时${minutes}分${seconds}秒`;
}
function getDateTimeSecond(time) {
  return Math.floor(time / 1000) % 60;
}
function getDateTimeMinute(time) {
  return Math.floor((time / (60 * 1000)) % 60);
}
function getDateTimeHour(time) {
  return Math.floor((time / (60 * 60 * 1000)) % 24);
}
function getDateTimeDay(time) {
  return Math.floor(time / (24 * 60 * 60 * 1000));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用moment.js写法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var moment = require(&apos;./js/moment.js&apos;);
const laborDayTime =moment(&quot;2023-05-01 00:00:00.000&quot;)
setInterval(function () {
let remainingTime = moment.duration(laborDayTime.diff(moment()))
console.log(`距离距离五一还有:${remainingTime._data.days}天${remainingTime._data.hours}小时${remainingTime._data.minutes}分${remainingTime._data.seconds}秒`);
}, 1000);
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>深拷贝和浅拷贝</title><link>https://fuwari.vercel.app/posts/2021%E5%B9%B4/%E6%B7%B1%E6%8B%B7%E8%B4%9D%E5%92%8C%E6%B5%85%E6%8B%B7%E8%B4%9D/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2021%E5%B9%B4/%E6%B7%B1%E6%8B%B7%E8%B4%9D%E5%92%8C%E6%B5%85%E6%8B%B7%E8%B4%9D/</guid><pubDate>Thu, 24 Jun 2021 21:50:05 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;在 JavaScript 中，深拷贝和浅拷贝是用来复制对象或数组的两种不同方式&lt;/p&gt;
&lt;h2&gt;浅拷贝：&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;创建一个新的对象或数组，但只复制了原始对象或数组的引用。新对象与原对象共享相同的内存地址，因此对新对象的修改也会影响到原对象。&lt;/li&gt;
&lt;li&gt;浅拷贝只复制了对象或数组的第一层结构，对于嵌套的对象或数组，仍然是共享引用关系。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;深拷贝：&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;创建一个全新的对象或数组，复制了原始对象或数组的所有嵌套属性和值，而不仅仅是引用。&lt;/li&gt;
&lt;li&gt;深拷贝会递归遍历原对象或数组的所有层级，将每个属性或元素复制到新对象或数组中。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>动画插件 Anime.js</title><link>https://fuwari.vercel.app/posts/2021%E5%B9%B4/%E5%8A%A8%E7%94%BB%E6%8F%92%E4%BB%B6-animejs/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2021%E5%B9%B4/%E5%8A%A8%E7%94%BB%E6%8F%92%E4%BB%B6-animejs/</guid><pubDate>Tue, 02 Mar 2021 12:56:00 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;Anime.js 是一个强大且灵活的 JavaScript 动画库，用于在网页上创建各种类型的动画效果。能够创建平滑、流畅和复杂的动画。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://animejs.com/&quot;&gt;官方网站&lt;/a&gt; &lt;a href=&quot;https://animejs.com/documentation/#cssSelector&quot;&gt;官方文档&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;简单示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;div id=&quot;box&quot;&amp;gt;&amp;lt;/div&amp;gt;

  &amp;lt;script&amp;gt;
    // 创建动画
    anime({
      targets: &apos;#box&apos;,
      translateX: &apos;250px&apos;,
      duration: 1000,
      easing: &apos;easeInOutQuad&apos;
    });
  &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>对象遍历 For of</title><link>https://fuwari.vercel.app/posts/2021%E5%B9%B4/%E5%AF%B9%E8%B1%A1%E9%81%8D%E5%8E%86-for-of/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2021%E5%B9%B4/%E5%AF%B9%E8%B1%A1%E9%81%8D%E5%8E%86-for-of/</guid><pubDate>Sat, 06 Feb 2021 13:17:15 GMT</pubDate><content:encoded>&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;for...of&lt;/code&gt; 循环在每次迭代中自动获取迭代对象的下一个值，不需要手动管理索引。它也可以用于遍历具有迭代器（iterator）的自定义对象。&lt;/p&gt;
&lt;p&gt;需要注意的是，与传统的 for...in 循环不同，for...of 不遍历对象的属性，而是遍历对象实例的值。如果你需要遍历对象的属性，应该使用 for...in 循环。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const myObject = { a: 1, b: 2, c: 3 };
for (const key in myObject) {
  console.log(key, myObject[key]);
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>koa2中使用Cookie和Session</title><link>https://fuwari.vercel.app/posts/2020%E5%B9%B4/koa2%E4%B8%AD%E4%BD%BF%E7%94%A8cookie%E5%92%8Csession/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2020%E5%B9%B4/koa2%E4%B8%AD%E4%BD%BF%E7%94%A8cookie%E5%92%8Csession/</guid><pubDate>Tue, 01 Dec 2020 20:07:49 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;在koa2中cookie可以在原生组件中直接调用，但是session必须要安装对应中间件&quot;koa-session&quot;&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm install koa-session -S
//或者
yarn add koa-session
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;在服务端内我们可以这样写&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const Koa = require(&apos;koa&apos;);
const app = new Koa();
const session = require(&apos;koa-session&apos;);
const port = process.env.PORT || 3000

app.keys = [&apos;shinelikearainbow&apos;];
//session配置
const CONFIG = {
    key: &apos;koa:session&apos;,  
    maxAge: 1000*60*3, 
};

app.listen(port, () =&amp;gt; {
    console.log(`服务开始在${port}端口运行`);
});
app.use(session(CONFIG, app));
app.use( async ( ctx ) =&amp;gt; {
//添加cookie
ctx.cookies.set(
        &apos;name&apos;,
        &apos;tom&apos;,
        {
            maxAge: 10 * 60 * 1000, // cookie有效时长
            httpOnly: false,  // 是否只用于http请求中获取
            overwrite: false,  // 是否允许重写
            signed: true    //是否添加签名
        }
    )

//添加session
ctx.session.name=&quot;tom&quot;;
ctx.body = &quot;hello world&quot;;
})
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Mongoose中update返回的字段对应意思</title><link>https://fuwari.vercel.app/posts/2020%E5%B9%B4/mongoose%E4%B8%ADupdate%E8%BF%94%E5%9B%9E%E7%9A%84%E5%AD%97%E6%AE%B5%E5%AF%B9%E5%BA%94%E6%84%8F%E6%80%9D/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2020%E5%B9%B4/mongoose%E4%B8%ADupdate%E8%BF%94%E5%9B%9E%E7%9A%84%E5%AD%97%E6%AE%B5%E5%AF%B9%E5%BA%94%E6%84%8F%E6%80%9D/</guid><pubDate>Fri, 20 Nov 2020 06:33:19 GMT</pubDate><content:encoded>&lt;p&gt;n:1
找到需要修改的数据一条&lt;/p&gt;
&lt;p&gt;nModified:1
成功修改对应数据对应字段1次（如果一段数据有多条值被更新这里仍然只显示1，这里只记录被找到需要修改数据的数据的值是否发生变化，如果只是找到而里面的数据没有被
更新或者修改后仍然重复那么这里不会+1而是0,不过ok仍然为1因为虽然数据没有被改变但是更新已经完成）&lt;/p&gt;
&lt;p&gt;ok:1
更新成功（ok的结果只有0和1相当于布尔值，0代表修改失败1反之，所以无论修改多少条字段这里只会返回0或1）&lt;/p&gt;
</content:encoded></item><item><title>Express 动态路由</title><link>https://fuwari.vercel.app/posts/2020%E5%B9%B4/express-%E5%8A%A8%E6%80%81%E8%B7%AF%E7%94%B1/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2020%E5%B9%B4/express-%E5%8A%A8%E6%80%81%E8%B7%AF%E7%94%B1/</guid><pubDate>Fri, 10 Jul 2020 20:48:40 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;在 Express 中，动态路由是一种用于处理具有可变部分的 URL 的路由技术。通常，URL 中的某些部分需要根据请求的具体内容进行变化，例如在一个博客应用中，文章的 URL 可能包含文章的唯一标识符。动态路由允许你在 Express 应用中捕获这些可变部分，并根据它们执行相应的操作。&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;Express 使用冒号 &lt;code&gt;:&lt;/code&gt; 来表示动态路由参数。例如，如果你有一个博客应用，想要处理文章页面的请求，你可以创建一个动态路由来捕获文章的标识符，如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const express = require(&apos;express&apos;);
const app = express();

app.get(&apos;/article/:id&apos;, (req, res) =&amp;gt; {
  const articleId = req.params.id;
  // 根据文章标识符（id）执行相应的操作
  res.send(`查看文章 ${articleId}`);
});

app.listen(3000, () =&amp;gt; {
  console.log(&apos;服务器正在监听端口 3000&apos;);
});
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Express 下载接口</title><link>https://fuwari.vercel.app/posts/2020%E5%B9%B4/express-%E4%B8%8B%E8%BD%BD%E6%8E%A5%E5%8F%A3/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2020%E5%B9%B4/express-%E4%B8%8B%E8%BD%BD%E6%8E%A5%E5%8F%A3/</guid><pubDate>Fri, 10 Jul 2020 12:19:32 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;Express 中的 &lt;code&gt;res.download()&lt;/code&gt; 是一个用于处理文件下载的方法。它允许你将文件发送给客户端浏览器以供下载。&lt;/p&gt;
&lt;p&gt;常见参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;res.download(path [, filename] [, options] [, callback])
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;path&lt;/code&gt;：必需参数，表示要下载的文件的路径，可以是相对路径或绝对路径。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filename&lt;/code&gt;：可选参数，表示客户端下载时显示的文件名。如果不提供此参数，将使用&lt;code&gt;path&lt;/code&gt;中的文件名。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;options&lt;/code&gt;：可选参数，一个包含下载选项的对象，例如&lt;code&gt;headers&lt;/code&gt;，用于设置响应头。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;callback&lt;/code&gt;：可选参数，一个回调函数，用于处理下载完成后的回调。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;简单示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const express = require(&apos;express&apos;);
const app = express();

app.get(&apos;/download&apos;, (req, res) =&amp;gt; {
  const filePath = &apos;path/to/your/file.pdf&apos;; // 替换为你要提供下载的文件的路径
  const fileName = &apos;your-file.pdf&apos;; // 替换为客户端下载时显示的文件名

  res.download(filePath, fileName, (err) =&amp;gt; {
    if (err) {
      // 处理错误，例如文件不存在或其他问题
      res.status(404).send(&apos;文件未找到&apos;);
    }
  });
});
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>如何将之前编辑的文章HTML源代码导入到TinyMCE编辑器中</title><link>https://fuwari.vercel.app/posts/2019%E5%B9%B4/%E5%A6%82%E4%BD%95%E5%B0%86%E4%B9%8B%E5%89%8D%E7%BC%96%E8%BE%91%E7%9A%84%E6%96%87%E7%AB%A0html%E6%BA%90%E4%BB%A3%E7%A0%81%E5%AF%BC%E5%85%A5%E5%88%B0tinymce%E7%BC%96%E8%BE%91%E5%99%A8%E4%B8%AD/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2019%E5%B9%B4/%E5%A6%82%E4%BD%95%E5%B0%86%E4%B9%8B%E5%89%8D%E7%BC%96%E8%BE%91%E7%9A%84%E6%96%87%E7%AB%A0html%E6%BA%90%E4%BB%A3%E7%A0%81%E5%AF%BC%E5%85%A5%E5%88%B0tinymce%E7%BC%96%E8%BE%91%E5%99%A8%E4%B8%AD/</guid><pubDate>Wed, 11 Dec 2019 22:23:40 GMT</pubDate><content:encoded>&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;如果你想用 TinyMCE 来修改你之前写的文章那么你需要将源代码放到 TinyMCE 中，如果服务器把 HTML 源码发给我们可是我们应该怎样调用？&lt;/p&gt;
&lt;p&gt;方法为使用 &lt;code&gt;tinymce.activeEditor.setContent()&lt;/code&gt; 这个函数&lt;/p&gt;
&lt;h3&gt;具体用法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt; tinymce.activeEditor.setContent()   //设置TinyMCE编辑器里的内容源代码
 
 tinymce.activeEditor.getContent()   //获取TinyMCE编辑器里的内容源代码
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可是我们发现直接放到HTML文件里执行无法获取到这个函数，这是因为TinyMCE这个时候还没有初始化完成，也就是说我们还无法调取这个函数，必须要等到TinyMCE彻底初始化完成后才能调用，所以我们这里有两种写法获取初始化完成的消息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//第一种：
//使用setup
var data = &quot;&amp;lt;p&amp;gt;这是一个P标签&amp;lt;/p&amp;gt;&amp;lt;h1&amp;gt;这是一个H1标签&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;这是一个斜体字&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&quot;;
tinymce.init({
            selector: &apos;#mytextarea&apos;,
            setup: function (editor) {
                editor.on(&apos;init&apos;, function (e) {
                    console.log(&apos;初始化完成&apos;);
                    tinymce.activeEditor.setContent(data);
                });
            }
        })
 
//________________________________________________________________________________________
 
//第二种
//使用.then回调函数
var data = &quot;&amp;lt;p&amp;gt;这是一个P标签&amp;lt;/p&amp;gt;&amp;lt;h1&amp;gt;这是一个H1标签&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;这是一个斜体字&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&quot;;
tinymce.init({
            selector: &apos;#mytextarea&apos;,
        }).then(resolve =&amp;gt; {
           tinymce.activeEditor.setContent(data);
        });
 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;../assets/Pasted%20image%2020240720182211.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们可以看到刚启动编辑器就有内容了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../assets/Pasted%20image%2020240720182221.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;HTML源码也是整齐的&lt;/p&gt;
&lt;p&gt;（不要吐槽中文和工具数量，这些是我后面设置的，没有放进代码片里）&lt;/p&gt;
</content:encoded></item><item><title>TinyMCE上传图片后端处理写法</title><link>https://fuwari.vercel.app/posts/2019%E5%B9%B4/tinymce%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87%E5%90%8E%E7%AB%AF%E5%A4%84%E7%90%86%E5%86%99%E6%B3%95/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2019%E5%B9%B4/tinymce%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87%E5%90%8E%E7%AB%AF%E5%A4%84%E7%90%86%E5%86%99%E6%B3%95/</guid><pubDate>Tue, 10 Dec 2019 01:26:02 GMT</pubDate><content:encoded>&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;写法有两种，一种是先把图片转换成base64后再本地解析显示，这样可以直接将图片储存在HTML文件中，如果图片过多可能会让HTML文件过大，或者你可以将已经转码成base64的图片上传到服务器后服务器再转码成原jpg或png格式。&lt;/p&gt;
&lt;p&gt;另一种是方法是通过POST把图片传输给服务器后，服务器再返回图片地址我们再调用&lt;/p&gt;
&lt;h3&gt;第一种方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;utf-8&quot; /&amp;gt;
    &amp;lt;title&amp;gt;TinyMCE图片上传测试&amp;lt;/title&amp;gt;
    &amp;lt;script src=&quot;tinymce.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
        tinymce.init({
            selector: &apos;#mytextarea&apos;,
            language: &apos;zh_CN&apos;,
            plugins: &quot;image&quot;,
            toolbar: &quot;image&quot;,
            images_dataimg_filter: function (img) {
                //添加图片渲染到编辑器中
                return img.hasAttribute(&apos;internal-blob&apos;);
            },
            paste_data_images: true,
            images_upload_handler: function (blobInfo, success, failure) {
                //将图片转码为base64格式
                success(&quot;data:&quot; + blobInfo.blob().type + &quot;;base64,&quot; + blobInfo.base64());
            }
        });
    &amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;textarea id=&quot;mytextarea&quot;&amp;gt;&amp;lt;/textarea&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样所有的图片都会以base64的方式存储在HTML文件中，如果需要上传到服务器中则可以在服务端这样写&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//NodeJS服务端：
app.post(&apos;/upload&apos;, function (req, res) {
    //接收前台POST过来的base64
    var imgData = req.body.imgData;
    var base64Data = imgData.replace(/^data:image\/\w+;base64,/, &quot;&quot;);     //过滤data:URL
    var dataBuffer = new Buffer(base64Data, &apos;base64&apos;);       
    fs.writeFile(&quot;image.png&quot;, dataBuffer, function (err) {
        if (err) {
            res.send(err);
        } else {
            res.send(&quot;保存成功！&quot;);
        }
    });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;第二种方法 ，先上传再调用&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;utf-8&quot; /&amp;gt;
    &amp;lt;title&amp;gt;TinyMCE图片上传测试&amp;lt;/title&amp;gt;
    &amp;lt;script src=&quot;tinymce.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
        tinymce.init({
            selector: &apos;#mytextarea&apos;,
            language: &apos;zh_CN&apos;,
            plugins: &quot;image&quot;,
            toolbar: &quot;image&quot;,
            images_upload_base_path: &apos;dir&apos;, //图片存放目录
            images_upload_url: &apos;/upload&apos;,   //上传地址
        });
    &amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;textarea id=&quot;mytextarea&quot;&amp;gt;&amp;lt;/textarea&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后端图片处理&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var express = require(&quot;express&quot;);
var app = express();
var server = require(&quot;http&quot;).createServer(app);
var fs = require(&apos;fs&apos;);
var path = require(&apos;path&apos;);
var formidable = require(&apos;formidable&apos;);
 
app.use(express.static(&quot;public&quot;));
var port = process.env.PORT || 3000;
server.listen(port, function () { console.log(&quot;connection&quot;) });
 
app.post(&apos;/upload&apos;, function (req, res, next) {
    var form = new formidable.IncomingForm();
    form.uploadDir = &quot;./public/dir&quot;;
    form.maxFieldsSize = 4 * 1024 * 1024;  //用户头像大小限制为最大4M    
    form.keepExtensions = true;        //使用文件的原扩展名  
    form.parse(req, function (err, fields, file) {
        res.send({
            location: path.basename(file.file.path) //返回图片地址
        });
    });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样每次上传新图片的时候服务器的dir文件夹就会收到一张新的图片&lt;/p&gt;
</content:encoded></item><item><title>Switch 语法</title><link>https://fuwari.vercel.app/posts/2019%E5%B9%B4/switch-%E8%AF%AD%E6%B3%95/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2019%E5%B9%B4/switch-%E8%AF%AD%E6%B3%95/</guid><pubDate>Sun, 10 Nov 2019 11:53:28 GMT</pubDate><content:encoded>&lt;h2&gt;开始&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;function checkQuestionType(type) {
    switch (type) {
      case &quot;fill&quot;:
        return &quot;填空题&quot;;
      case &quot;single&quot;:
        return &quot;单选题&quot;;
      case &quot;multi&quot;:
        return &quot;多选题&quot;;
      case &quot;judge&quot;:
        return &quot;判断题&quot;;
      case &quot;essay&quot;:
        return &quot;问答题&quot;;
      default:
        return &quot;未知题型&quot;;
    }
  }
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>CreateJS0.8版本制作的工程使用1.0版本框架可能会出现的问题及新属性的调用方法</title><link>https://fuwari.vercel.app/posts/2019%E5%B9%B4/createjs08%E7%89%88%E6%9C%AC%E5%88%B6%E4%BD%9C%E7%9A%84%E5%B7%A5%E7%A8%8B%E4%BD%BF%E7%94%A810%E7%89%88%E6%9C%AC%E6%A1%86%E6%9E%B6%E5%8F%AF%E8%83%BD%E4%BC%9A%E5%87%BA%E7%8E%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E6%96%B0%E5%B1%9E%E6%80%A7%E7%9A%84%E8%B0%83%E7%94%A8%E6%96%B9%E6%B3%95/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2019%E5%B9%B4/createjs08%E7%89%88%E6%9C%AC%E5%88%B6%E4%BD%9C%E7%9A%84%E5%B7%A5%E7%A8%8B%E4%BD%BF%E7%94%A810%E7%89%88%E6%9C%AC%E6%A1%86%E6%9E%B6%E5%8F%AF%E8%83%BD%E4%BC%9A%E5%87%BA%E7%8E%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E6%96%B0%E5%B1%9E%E6%80%A7%E7%9A%84%E8%B0%83%E7%94%A8%E6%96%B9%E6%B3%95/</guid><pubDate>Sat, 28 Sep 2019 06:12:14 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;1.0版本的CreateJS弃用了很多的早期的获取调用属性的函数及方法，这让使用早期CreateJS制作的文件在升级新版CreateJS框架后会出现许多的属性无法调用的情况，好在虽然CreateJS1.0虽然弃用了这些方法但是在新版本框架中仍然可以调用，预计会在下一次更新中会彻底删除弃用部分，而且在新版本框架中如果你继续使用旧方法调用，那么在你的开发者控制台会有醒目的提示告诉你CreateJS已经弃用你所调用的部分属性及方法，详细内容需要参考官方API，在官网的API我们可以清楚的看到弃用的属性有哪些。&lt;/p&gt;
&lt;p&gt;刚开始觉得弃用这些代码带来了许多不便，因为它与我之前的工程不兼容，后来当我把所有的弃用的属性方法修改成新版本的新调用代码后发现，控制属性变得比以前更简洁了，而且帧数也有提高，所以建议还没有尝试更新后的框架的小伙伴们可以试试。&lt;/p&gt;
&lt;h2&gt;被弃用属性&lt;/h2&gt;
&lt;p&gt;给大家几个经常使用但是已经被弃用并替代的属性供大家参考:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//1.0版本已弃用：
Container.getNumChildren();
//1.0更新后的新方法：
Container.numChildren;

//1.0版本已弃用：
DisplayObject.getStage();
//1.0更新后的新方法：
DisplayObject.stage;

//1.0版本已弃用：
Ticker.getFPS();
//1.0更新后的新方法：
Ticker.framerate;
Container.framerate = 30; //单独设置元件动画的帧数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中关于Ticker.getFPS()的更新是非常赞的，更新后可以单独设定单个元件的帧数，再也不用担心因为更新了舞台帧数后其他元件也跟着舞台帧数的增加或减少进行加速或减速运动了。也不用为了让动画加速或减速再重新写一遍动画脚本，这个更新可以让制作游戏内debuff的效果更加真实比如被减速后人物移动速度不仅减慢了并且动画速度也跟着一起减慢，超赞。&lt;/p&gt;
&lt;p&gt;更多详细的更新可以浏览官网的文档：&lt;a href=&quot;https://www.createjs.com/docs/easeljs/modules/EaselJS.html&quot;&gt;https://www.createjs.com/docs/easeljs/modules/EaselJS.html&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>通过NodeJS使用cookie和session</title><link>https://fuwari.vercel.app/posts/2019%E5%B9%B4/%E9%80%9A%E8%BF%87nodejs%E4%BD%BF%E7%94%A8cookie%E5%92%8Csession/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2019%E5%B9%B4/%E9%80%9A%E8%BF%87nodejs%E4%BD%BF%E7%94%A8cookie%E5%92%8Csession/</guid><pubDate>Thu, 28 Mar 2019 12:41:51 GMT</pubDate><content:encoded>&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;如果想要通过 NodeJS 使用 cookie，那么你需要安装&lt;code&gt;cookie-parser&lt;/code&gt; 依赖,如果是 session 那么需要安装&lt;code&gt;cookie-session&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm install cookie-parser
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;在服务端引用&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;var cookieParser = require(&apos;cookie-parser&apos;);    //cookie
var cookieSession = require(&apos;cookie-session&apos;);  //session
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;调用&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;//cookie方法
app.use(cookieParser());
 
app.get(&apos;/cookie&apos;, function (req,res) {
    res.cookie(&quot;me&quot;, &quot;come on!&quot;);
    res.send(req.cookies);
    console.log(req.cookies);
    res.end();
})
 
 
//session方法
 
app.use(cookieSession({
         //session的秘钥，防止session劫持。 这个秘钥会被循环使用，秘钥越长，数量越多，破解难度越高。
     keys: [&apos;aaa&apos;, &apos;bbb&apos;, &apos;ccc&apos;],
         //session过期时间，不易太长。php默认20分钟
     maxAge: 60 * 60,
         //可以改变浏览器cookie的名字
     name: &apos;session&apos;
 }));
 
app.use(&apos;/session&apos;, function (req, res) {
    req.session.user = &quot;mitte&quot;;
    console.log(req.session);
    res.send(req.cookies);
    res.end();
})
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>CreateJS中调用动画里一个让人头疼的问题</title><link>https://fuwari.vercel.app/posts/2019%E5%B9%B4/createjs%E4%B8%AD%E8%B0%83%E7%94%A8%E5%8A%A8%E7%94%BB%E9%87%8C%E4%B8%80%E4%B8%AA%E8%AE%A9%E4%BA%BA%E5%A4%B4%E7%96%BC%E7%9A%84%E9%97%AE%E9%A2%98/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2019%E5%B9%B4/createjs%E4%B8%AD%E8%B0%83%E7%94%A8%E5%8A%A8%E7%94%BB%E9%87%8C%E4%B8%80%E4%B8%AA%E8%AE%A9%E4%BA%BA%E5%A4%B4%E7%96%BC%E7%9A%84%E9%97%AE%E9%A2%98/</guid><pubDate>Tue, 26 Mar 2019 23:20:51 GMT</pubDate><content:encoded>&lt;h2&gt;经历&lt;/h2&gt;
&lt;p&gt;有时候你可能发现在你使用函数生成元件布置到舞台后，元件的动画错位了，并且只会运行第一帧或第二帧，无论你如何修改函数，他仍然无济于事，后来当你将布置动画函数延迟一秒执行后就可以正常运行，错误原理估计是代码为同步执行，但结果为异步，生成在前动画布置在后，可是生成函数在执行后会在后台部署生成元件的数据，可是在生成指令后面就是布置动画的指令，动画指令发出后因为这时候元件生成动画代码还没彻底部署完成，于是就出现了没有找到相关动画资源，也就出现了舞台上的元件动画加载失败，如果延迟一秒执行动画，这时间完全足够全部动画的部署，这时候再调用动画就不会出现加载问题，当然这样写肯定不行，因为为了等待几毫秒的时间差而让组件延后生成1秒是非常浪费加载时间的一种行为，最好的做法应该是加上callback调用只用组件彻底加载完成后才调用动画生成的函数。&lt;/p&gt;
&lt;p&gt;这可真是一个令人抓狂的BUG，这究竟是要怪代码运行太快还是元件生成太慢。&lt;/p&gt;
&lt;p&gt;这也反映了CreateJS里的PreloadJS的重要性，一定要等元件彻底部署完成后再调用动画，不然真的就是写码不规范，改码泪两行了。&lt;/p&gt;
</content:encoded></item><item><title>使用NodeJS将数据保存成JSON文件</title><link>https://fuwari.vercel.app/posts/2019%E5%B9%B4/%E4%BD%BF%E7%94%A8nodejs%E5%B0%86%E6%95%B0%E6%8D%AE%E4%BF%9D%E5%AD%98%E6%88%90json%E6%96%87%E4%BB%B6/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2019%E5%B9%B4/%E4%BD%BF%E7%94%A8nodejs%E5%B0%86%E6%95%B0%E6%8D%AE%E4%BF%9D%E5%AD%98%E6%88%90json%E6%96%87%E4%BB%B6/</guid><pubDate>Mon, 07 Jan 2019 12:49:58 GMT</pubDate><content:encoded>&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;在平时我需要把后端一些object变量保存至本地，可以这样做：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var express = require(&quot;express&quot;);
var app = express();
var server = require(&quot;http&quot;).createServer(app);
var fs = require(&apos;fs&apos;);
app.use(express.static(&quot;public&quot;));
var port = process.env.PORT || 3000;
 
server.listen(port, function () { console.log(&quot;connected&quot;) });
 
var obj1 = {
    time: mytime.toLocaleString(),
    user: &quot;薄荷派&quot;,
    objName: &quot;obj1&quot;
};
//写入文件，会完全替换之前JSON文件中的内容
function writeData(value) {
       var str = JSON.stringify(value, &quot;&quot;, &quot;\t&quot;);
    fs.writeFile(&apos;./json.json&apos;, str, function (err) {
        if (err) {
            console.error(err);
        }
        console.log(&apos;写入成功!&apos;);
    })
}
//读取文件然后在原有文件内容的基础上添加内容，如果key名重复则覆盖
function addData(value) {
    fs.readFile(&apos;./json.json&apos;, &quot;utf-8&quot;, function (err, data) {
        if (err) {
            console.log(err);
        }
        var person = JSON.parse(data);
        person[obj1.objName] = value;   
        var str = JSON.stringify(person, &quot;&quot;, &quot;\t&quot;);
        fs.writeFile(&apos;./json.json&apos;, str, function (err) {
            if (err) {
                console.error(err);
            }
            console.log(&apos;新增成功!&apos;);
        })
    })
}
 
//运行函数,可以查看不同函数对数据的改动
//writeData(obj1);
//addData(obj1);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过运行上面函数可以分别在原有JSON文件中写入、覆盖数据&lt;/p&gt;
&lt;p&gt;下面的函数可以帮你更好的操作数据&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Object.assign(obj1, obj2);//合并两个obj的内容，重复的会被第一个obj替换,最后obj1的内容为obj1+obj2的内容
Object.keys(person).length;//查看当前obj里同级所有的key的总数，结果为数字
JSON.stringify(value);//将obj输出成json格式
JSON.stringify(value, &quot;&quot;, &quot;\t&quot;);//将obj输出成json格式同时自动格式化
JSON.parse(value);//将json格式的内容转化为obj
obj.arrayObj.push(value);//将传来的对象push进数组对象中(前两个写对应的对象)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>使用CreateJs生成图形</title><link>https://fuwari.vercel.app/posts/2019%E5%B9%B4/%E4%BD%BF%E7%94%A8createjs%E7%94%9F%E6%88%90%E5%9B%BE%E5%BD%A2/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2019%E5%B9%B4/%E4%BD%BF%E7%94%A8createjs%E7%94%9F%E6%88%90%E5%9B%BE%E5%BD%A2/</guid><pubDate>Tue, 01 Jan 2019 06:11:28 GMT</pubDate><content:encoded>&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;通过CreateJs中的Shape指令可以手动生成很多基本图形。&lt;/p&gt;
&lt;h3&gt;矩形&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;var Rect1 = new createjs.Shape();
Rect1.graphics.beginFill(&quot;red&quot;).drawRect(0, 0, 100, 100);
Rect1.x = Rect1.y = 50;
stage.addChild(Rect1);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;圆形&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;var circle1 = new createjs.Shape();
circle1.graphics.beginFill(&quot;blue&quot;).drawCircle(0, 0, 100);
circle1.x = circle1.y = 300;
stage.addChild(circle1);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;多边形&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;var polystar = new createjs.Shape();
polystar.graphics.beginFill(&quot;yellow&quot;).drawPolyStar(0, 0, 150, 3, 0, 90);
polystar.x = polystar.y = 500;
stage.addChild(polystar);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;效果如下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../assets/Pasted%20image%2020240720180546.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;多边形可以通过改变draw中的属性实现许多有趣的图形&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var polystar1 = new createjs.Shape();
polystar1.graphics.beginFill(&quot;yellow&quot;).drawPolyStar(0, 0, 30, 3, 5, 30);
polystar1.x = polystar1.y = 500;
stage.addChild(polystar1);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由此可以看出多边形的draw属性分别为（x轴间距，y轴间距，像素，边数，拉伸，角度）六个属性。&lt;/p&gt;
</content:encoded></item><item><title>使用CreatJS制作鼠标追踪效果</title><link>https://fuwari.vercel.app/posts/2018%E5%B9%B4/%E4%BD%BF%E7%94%A8creatjs%E5%88%B6%E4%BD%9C%E9%BC%A0%E6%A0%87%E8%BF%BD%E8%B8%AA%E6%95%88%E6%9E%9C/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2018%E5%B9%B4/%E4%BD%BF%E7%94%A8creatjs%E5%88%B6%E4%BD%9C%E9%BC%A0%E6%A0%87%E8%BF%BD%E8%B8%AA%E6%95%88%E6%9E%9C/</guid><pubDate>Sat, 15 Dec 2018 05:47:04 GMT</pubDate><content:encoded>&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;使用的是Animate CC + CreatJS进行制作的所以有些步骤和直接使用CreatJS有所不同，不过主要内容完全一致。&lt;/p&gt;
&lt;h4&gt;第一种效果：跟随效果&lt;/h4&gt;
&lt;p&gt;JS部分(js文件名：&quot;MyFileCopy&quot;)：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//导入舞台
exportRoot = new lib.MyFile();
	stage = new lib.Stage(canvas);	
 
//导入预先在An中制作好的素材：
var Shape = new lib.Shape();
    Shape.x = 750;
    Shape.y = 856.90;
    exportRoot.addChild(Shape);
 
//为了保证就算鼠标离开画布跟踪物仍然可以追踪鼠标我们要在Stage中加入这行代码：
stage.mouseMoveOutside = true;
 
//鼠标跟踪事件：
 stage.on(&quot;stagemousemove&quot;, function (evt) {      
         Shape.x = evt.stageX;
         Shape.y = evt.stageY;
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML部分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;head runat=&quot;server&quot;&amp;gt;
&amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot;/&amp;gt;
    &amp;lt;title&amp;gt;鼠标追踪事件&amp;lt;/title&amp;gt;
    &amp;lt;script src=&quot;https://code.createjs.com/createjs-2015.11.26.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;MyFile.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;MyFileCopy.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body οnlοad=&quot;init();&quot;&amp;gt;
 &amp;lt;div id=&quot;animation_container&quot; style=&quot;background-color:rgba(153, 153, 204, 1.00); width:1500px; height:900px;margin: 0 auto;&quot;&amp;gt;
		&amp;lt;canvas id=&quot;canvas&quot; width=&quot;1500&quot; height=&quot;900&quot; style=&quot;position: absolute; display: block; background-color:rgba(153, 153, 204, 1.00);&quot;&amp;gt;&amp;lt;/canvas&amp;gt;
		&amp;lt;div id=&quot;dom_overlay_container&quot; style=&quot;pointer-events:none; overflow:hidden; width:1500px; height:900px; position: absolute; left: 0px; top: 0px; display: block;&quot;&amp;gt;
		&amp;lt;/div&amp;gt;
	&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;第二种效果：拖拽效果&lt;/h4&gt;
&lt;p&gt;JS部分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//导入舞台
exportRoot = new lib.MyFile();
	stage = new lib.Stage(canvas);	
 
//导入预先在An中制作好的素材：
var Shape = new lib.Shape();
    Shape.x = 750;
    Shape.y = 856.90;
    exportRoot.addChild(Shape);
 
//为了保证就算鼠标离开画布跟踪物仍然可以追踪鼠标我们要在Stage中加入这行代码：
stage.mouseMoveOutside = true;
 
//鼠标跟踪事件：
Shape.on(&quot;pressmove&quot;, function (evt) {
        evt.currentTarget.x = evt.stageX;
        evt.currentTarget.y = evt.stageY;
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML部分与效果一相同。&lt;/p&gt;
&lt;p&gt;PS：JS部分代码没有截全，因为JS代码是由Animate自动生成的，我们只需要在自动生成的文件里找到生成舞台的字段，直接在下面写入追踪效果的代码即可&lt;/p&gt;
</content:encoded></item><item><title>如何在Canvas中平铺图像</title><link>https://fuwari.vercel.app/posts/2018%E5%B9%B4/%E5%A6%82%E4%BD%95%E5%9C%A8canvas%E4%B8%AD%E5%B9%B3%E9%93%BA%E5%9B%BE%E5%83%8F/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/2018%E5%B9%B4/%E5%A6%82%E4%BD%95%E5%9C%A8canvas%E4%B8%AD%E5%B9%B3%E9%93%BA%E5%9B%BE%E5%83%8F/</guid><pubDate>Wed, 28 Nov 2018 01:02:45 GMT</pubDate><content:encoded>&lt;p&gt;如果我们要在Canvas中只用个方块做出这样的效果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../assets/Pasted%20image%2020240720175634.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;开始&lt;/h2&gt;
&lt;p&gt;首先我们先创建一个画布，大小自定&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;canvas id=&quot;canvas&quot; height=&quot;2000&quot; width=&quot;2000&quot;style=back&quot;background-color:white;border:3px solid red;&quot;&amp;gt;&amp;lt;/canvas&amp;gt;
&amp;lt;body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我这里的长宽为2000*2000，背景颜色为白色，边框是3px的红色直线。&lt;/p&gt;
&lt;p&gt;接着我们制作一个用来平铺的图像，我制作的图像是一个110&lt;em&gt;110的粉红色的方形，其中外边框10px为透明区域，方形实际可见面积为100&lt;/em&gt;100。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../assets/Pasted%20image%2020240720175833.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;画布做好后我们开始写JS的部分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  function draw() {
    var canvas = document.getElementById(&quot;canvas&quot;);
    if (canvas == null) return false;
    var tile = canvas.getContext(&quot;2d&quot;);
    var img = new Image();
    img.src = &quot;YourImage.png&quot;;
    img.onload = function () {
      var pattern = tile.createPattern(img, &quot;repeat&quot;);
      tile.fillStyle = pattern;
      tile.fillRect(0, 0, 2000, 2000);
    };
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写好JS部分后我们要把加载放到 &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; 中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;body onload=&quot;draw(&apos;canvas&apos;)&quot;&amp;gt;
  &amp;lt;canvas id=&quot;canvas&quot; height=&quot;2000&quot; width=&quot;2000&quot;style=back&quot;background-color:white;border:3px solid red;&quot;&amp;gt;&amp;lt;/canvas&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;全部完成后我们生成制作出的网页看看效果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../assets/Pasted%20image%2020240720180122.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content:encoded></item></channel></rss>