<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>吖远zzyの博客</title>
  <icon>https://www.hzv5.cn/icon.png</icon>
  <subtitle>一个不知名的编程爱好者</subtitle>
  <link href="https://www.hzv5.cn/atom.xml" rel="self"/>
  
  <link href="https://www.hzv5.cn/"/>
  <updated>2026-07-08T19:23:36.329Z</updated>
  <id>https://www.hzv5.cn/</id>
  
  <author>
    <name>吖远zzy</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>记录一下豆包视频去水印的研究过程，以及fallback_api加密数据qAAB开头数据的解密。</title>
    <link href="https://www.hzv5.cn/2026/07/06/doubao-watermark-research/"/>
    <id>https://www.hzv5.cn/2026/07/06/doubao-watermark-research/</id>
    <published>2026-07-06T13:10:32.000Z</published>
    <updated>2026-07-08T19:23:36.329Z</updated>
    
    <content type="html"><![CDATA[<h2 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h2><p>朋友丢过来一个链接，问我能不能写个接口，输入豆包的分享链接就返回无水印的视频直链。<br>需求很明确：部署在 PHP 环境里，访问 <code>?url=分享链接</code> 就吐 JSON 结果。<br>听起来像是抖音去水印那一套，心想应该不难。</p><h2 id="第一阶段：抄作业"><a href="#第一阶段：抄作业" class="headerlink" title="第一阶段：抄作业"></a>第一阶段：抄作业</h2><p>先去 GitHub 上找了一圈，发现有个浏览器js插件，正好是类似效果的。直接拉到本地看源码，核心流程大概是这样：</p><ol><li>请求 thread 页面的 HTML</li><li>从 HTML 里提取 <code>fallback_api</code> 这个接口地址</li><li>把这个地址里的参数改成 <code>logo_type=unwatermarked, channel=no, codec_type=8</code></li><li>请求修改后的接口，返回的是一段加密数据</li><li>用 AES-128-CBC 解密这段数据，得到真正的视频直链</li></ol><p>加密这块研究了半天。</p><p><img src="/img/decode_qAAB.png" alt="qAAB开头数据解密流程"></p><p>数据体前面 4 个字节是 qAAB 标记头（<code>0xA8 0x00 0x01 0x00</code>），跳过这 4 字节后是密文。</p><p>解密用的 key 和 iv 是从 URL 参数里的 <code>key_seed</code> 派生出来的：先 SHA-512 哈希一次，再拼接一个固定盐值做第二次 SHA-512，前 16 字节是 key，后 16 字节是 iv。</p><p>盐值是个写死的十六进制字符串。</p><p>第一版写好测试，发现 thread 类型的链接完美工作，拿到了无水印直链。</p><span id="more"></span><h2 id="第二阶段：碰壁"><a href="#第二阶段：碰壁" class="headerlink" title="第二阶段：碰壁"></a>第二阶段：碰壁</h2><p>正当我以为搞定的时候，朋友又丢来一种新链接：<code>/video-sharing?share_id=xxx&amp;video_id=xxx</code>。和 thread 完全不同，这种链接是豆包内生成 AI 视频后分享出来的。</p><p>试了一下，页面完全不包含 <code>fallback_api</code>。不管怎么请求都是空的。<br>对比了一下两种页面的 HTML 大小：thread 页面大概 70KB 而且包含了完整的 SSR 数据（服务端渲染），video-sharing 页面直接飙到 140KB，全是 SPA 壳子，真正的数据得靠 JS 动态加载。</p><h3 id="尝试过的路子"><a href="#尝试过的路子" class="headerlink" title="尝试过的路子"></a>尝试过的路子</h3><p><strong>chain/single 接口</strong>：给 share_id 请求 <code>/aweme/v2/chain/single</code>，返回的是一个 HTML 登录页。这条路不通。</p><p><strong>get_play_info 接口</strong>：<code>POST /samantha/media/get_play_info</code>，这个能通，返回了视频的分辨率、时长、封面这些元数据。但 <code>fallback_api</code> 字段是空的。</p><p><strong>用 share_id 当 thread ID</strong>：试试请求 <code>/thread/&#123;share_id&#125;</code>，确实返回了 70KB 的 HTML，里面也有 <code>_ROUTER_DATA</code>，但 <code>fallback_api</code> 依然不存在。而且 SSR 数据里显示的是”会话过期，请重新登录”。</p><p><strong>get_video_share_info</strong>：POST 请求 <code>/creativity/share/get_video_share_info</code>，这个是后来发现的。能返回视频的 prompt（生成提示词）、用户信息、以及一个带水印的播放链接。</p><h3 id="浏览器扩展插件的启示"><a href="#浏览器扩展插件的启示" class="headerlink" title="浏览器扩展插件的启示"></a>浏览器扩展插件的启示</h3><p>为了搞清楚别人到底是怎么做的，又去下了一个浏览器扩展的完整源码分析。</p><p>它用了 Chrome 的 debugger API，在用户打开豆包聊天页面的时候，拦截 <code>doubao.com/im/chain/single</code> 这个接口的响应。这个接口返回的数据里确实包含了 <code>fallback_api</code>。</p><p>但问题在于：</p><ul><li>这个接口需要 protobuf 编码的请求体</li><li>需要用户登录后的 Cookie</li><li>而且请求格式是豆包聊天服务的私有协议</li></ul><p>换言之，在服务端 PHP 里根本无法复刻。这条路是浏览器专属的，必须用户已经登录了豆包才行。</p><h2 id="第三阶段：认命和取舍"><a href="#第三阶段：认命和取舍" class="headerlink" title="第三阶段：认命和取舍"></a>第三阶段：认命和取舍</h2><p>经过一轮轮的测试，基本把豆包视频的解析边界摸清楚了：</p><table><thead><tr><th>链接类型</th><th>SSR 数据</th><th>fallback_api</th><th>无水印</th></tr></thead><tbody><tr><td>/thread/xxx（公开分享）</td><td>有</td><td>有</td><td>可以</td></tr><tr><td>/thread/xxx（需登录）</td><td>有，但缺 fallback</td><td>无</td><td>不行</td></tr><tr><td>/video-sharing（公开分享）</td><td>无</td><td>无</td><td>不行</td></tr><tr><td>/video-sharing（登录态）</td><td>N/A</td><td>有（需拦截）</td><td>仅浏览器可行</td></tr></tbody></table><p>最终的结论是：<strong>thread 类型的公开分享链接可以做到无水印直链解析；video-sharing 类型在不登录的情况下，只能拿到带水印的视频链接和元数据（或许也能拿到无水印的链接，只是我还没研究透）。</strong></p><h2 id="最终的-PHP-实现"><a href="#最终的-PHP-实现" class="headerlink" title="最终的 PHP 实现"></a>最终的 PHP 实现</h2><p>整合下来，处理流程分成两条线：</p><p><strong>thread 链接</strong>：</p><ol><li>GET 请求页面 HTML</li><li>正则提取 video_id 和 fallback_api URL</li><li>修改 fallback_api 参数（去水印标识）</li><li>请求修改后的接口获取加密数据</li><li>提取 key_seed，派生 AES key/iv，解密得到无水印直链</li></ol><p><strong>video-sharing 链接</strong>：</p><ol><li>POST 请求 <code>get_video_share_info</code> 获取 share 元数据（prompt、用户名）</li><li>POST 请求 <code>get_play_info</code> 获取视频详情（分辨率、时长、封面）</li><li>返回带水印的直链 + 元数据，附加说明告知需要登录才能去水印</li></ol><h2 id="收尾"><a href="#收尾" class="headerlink" title="收尾"></a>收尾</h2><p>整个研究过程大概花了一个晚上，大部分时间花在试各种 API 端点上。倒是搞清楚了一件事：豆包对视频分享的权限控制分得很细。thread 是对话分享方式，数据都打在页面里；video-sharing 是直接分享视频，把敏感数据藏到了后面。</p><p>如果之后有兴趣搞更完整的方案，但现在暂时不折腾了。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;起因&quot;&gt;&lt;a href=&quot;#起因&quot; class=&quot;headerlink&quot; title=&quot;起因&quot;&gt;&lt;/a&gt;起因&lt;/h2&gt;&lt;p&gt;朋友丢过来一个链接，问我能不能写个接口，输入豆包的分享链接就返回无水印的视频直链。&lt;br&gt;需求很明确：部署在 PHP 环境里，访问 &lt;code&gt;?url=分享链接&lt;/code&gt; 就吐 JSON 结果。&lt;br&gt;听起来像是抖音去水印那一套，心想应该不难。&lt;/p&gt;
&lt;h2 id=&quot;第一阶段：抄作业&quot;&gt;&lt;a href=&quot;#第一阶段：抄作业&quot; class=&quot;headerlink&quot; title=&quot;第一阶段：抄作业&quot;&gt;&lt;/a&gt;第一阶段：抄作业&lt;/h2&gt;&lt;p&gt;先去 GitHub 上找了一圈，发现有个浏览器js插件，正好是类似效果的。直接拉到本地看源码，核心流程大概是这样：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;请求 thread 页面的 HTML&lt;/li&gt;
&lt;li&gt;从 HTML 里提取 &lt;code&gt;fallback_api&lt;/code&gt; 这个接口地址&lt;/li&gt;
&lt;li&gt;把这个地址里的参数改成 &lt;code&gt;logo_type=unwatermarked, channel=no, codec_type=8&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;请求修改后的接口，返回的是一段加密数据&lt;/li&gt;
&lt;li&gt;用 AES-128-CBC 解密这段数据，得到真正的视频直链&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;加密这块研究了半天。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/decode_qAAB.png&quot; alt=&quot;qAAB开头数据解密流程&quot;&gt;&lt;/p&gt;
&lt;p&gt;数据体前面 4 个字节是 qAAB 标记头（&lt;code&gt;0xA8 0x00 0x01 0x00&lt;/code&gt;），跳过这 4 字节后是密文。&lt;/p&gt;
&lt;p&gt;解密用的 key 和 iv 是从 URL 参数里的 &lt;code&gt;key_seed&lt;/code&gt; 派生出来的：先 SHA-512 哈希一次，再拼接一个固定盐值做第二次 SHA-512，前 16 字节是 key，后 16 字节是 iv。&lt;/p&gt;
&lt;p&gt;盐值是个写死的十六进制字符串。&lt;/p&gt;
&lt;p&gt;第一版写好测试，发现 thread 类型的链接完美工作，拿到了无水印直链。&lt;/p&gt;</summary>
    
    
    
    <category term="去水印" scheme="https://www.hzv5.cn/categories/%E5%8E%BB%E6%B0%B4%E5%8D%B0/"/>
    
    <category term="api接口" scheme="https://www.hzv5.cn/categories/%E5%8E%BB%E6%B0%B4%E5%8D%B0/api%E6%8E%A5%E5%8F%A3/"/>
    
    <category term="收集控" scheme="https://www.hzv5.cn/categories/%E5%8E%BB%E6%B0%B4%E5%8D%B0/api%E6%8E%A5%E5%8F%A3/collections/"/>
    
    
    <category term="doubao" scheme="https://www.hzv5.cn/tags/doubao/"/>
    
    <category term="豆包AI" scheme="https://www.hzv5.cn/tags/%E8%B1%86%E5%8C%85AI/"/>
    
    <category term="视频去水印" scheme="https://www.hzv5.cn/tags/%E8%A7%86%E9%A2%91%E5%8E%BB%E6%B0%B4%E5%8D%B0/"/>
    
    <category term="豆包" scheme="https://www.hzv5.cn/tags/%E8%B1%86%E5%8C%85/"/>
    
    <category term="去水印" scheme="https://www.hzv5.cn/tags/%E5%8E%BB%E6%B0%B4%E5%8D%B0/"/>
    
    <category term="数据解密" scheme="https://www.hzv5.cn/tags/%E6%95%B0%E6%8D%AE%E8%A7%A3%E5%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>解决华为开发者工具 DevEco Studio 登录跳转 localhost:10101 端口被阻止问题</title>
    <link href="https://www.hzv5.cn/2026/06/14/DevEco-Studio-err/"/>
    <id>https://www.hzv5.cn/2026/06/14/DevEco-Studio-err/</id>
    <published>2026-06-14T16:00:00.000Z</published>
    <updated>2026-07-08T19:23:36.329Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>今天下午想捣鼓一下华为的鸿蒙应用，写一个我的“好事录”应用，结果卡在了第一步——DevEco Studio死活登录不上去，太尴尬了- -!!。</p><h2 id="遇到的坑"><a href="#遇到的坑" class="headerlink" title="遇到的坑"></a>遇到的坑</h2><p>点登录按钮，开发工具自动打开浏览器，输入华为账号密码登录账号后，自动跳转到华为账号授权页，一切正常。我点击同意授权，然后浏览器跳转到：</p><p><code>http://localhost:10101/callback</code></p><p>接着就看到一行大字：<strong>“无法访问此网站，localhost的响应时间过长”</strong>。</p><p><img src="/img/HWerr.png" alt="DevEco Studio 登录授权时的报错信息"></p><p>然后，开始检查网络，检查代理，搜索引擎上搜索有没有网友有相关的问题。</p><p>结果，方法好像都不太管用，就连官方文档中的常见问题中也没有，心态有点崩。</p><h2 id="尝试的过程"><a href="#尝试的过程" class="headerlink" title="尝试的过程"></a>尝试的过程</h2><h3 id="第一次尝试：关代理"><a href="#第一次尝试：关代理" class="headerlink" title="第一次尝试：关代理"></a>第一次尝试：关代理</h3><span id="more"></span><p>我平时会开着Clash，想着是不是代理搞的鬼。就去 <code>File &gt; Settings &gt; HTTP Proxy</code> 看了一眼，果然配了代理，但是并未启用。</p><p>先把代理改成 <code>No proxy</code>，重启IDE，再试一次——还是不行。</p><h3 id="第二次尝试：找端口"><a href="#第二次尝试：找端口" class="headerlink" title="第二次尝试：找端口"></a>第二次尝试：找端口</h3><p>上网搜了一下，发现10101这个端口是DevEco Studio用来接收登录回调的。可能是被别的程序占用了。</p><p>打开命令行（Win+R输入cmd），敲：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">netstat -ano | findstr :10101</span><br></pre></td></tr></table></figure><p>果然返回了一行，最后有个进程ID（比如3809）。说明端口被占用了。</p><p>直接干掉它：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">taskkill /PID 12345 /F</span><br></pre></td></tr></table></figure><p>结果被干掉的是 DevEco Studio IDE 自己，哈哈哈哈哈我差点绷不住了。重启IDE，再登录——还是不行。</p><h3 id="第三次尝试：防火墙"><a href="#第三次尝试：防火墙" class="headerlink" title="第三次尝试：防火墙"></a>第三次尝试：防火墙</h3><p>想着会不会是防火墙把端口拦了。去Windows Defender防火墙那里，新建了一个入站规则：</p><ul><li>端口：10101</li><li>协议：TCP</li><li>动作：允许连接</li></ul><p>重启，再试——<strong>终于成了</strong>。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>如果你也遇到这个问题，按这个顺序排查：</p><ol><li><strong>检查代理</strong>：<code>Settings &gt; HTTP Proxy</code>，没代理就选No proxy</li><li><strong>检查端口占用</strong>：<code>netstat -ano | findstr :10101</code>，有结果就杀掉</li><li><strong>检查防火墙</strong>：新建入站规则，放行10101端口</li></ol><p>哪个方法管用了，记得记下来，下次遇到能省不少时间。</p><p>么么哒~~</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;今天下午想捣鼓一下华为的鸿蒙应用，写一个我的“好事录”应用，结果卡在了第一步——DevEco Studio死活登录不上去，太尴尬了- -!!。&lt;/p&gt;
&lt;h2 id=&quot;遇到的坑&quot;&gt;&lt;a href=&quot;#遇到的坑&quot; class=&quot;headerlink&quot; title=&quot;遇到的坑&quot;&gt;&lt;/a&gt;遇到的坑&lt;/h2&gt;&lt;p&gt;点登录按钮，开发工具自动打开浏览器，输入华为账号密码登录账号后，自动跳转到华为账号授权页，一切正常。我点击同意授权，然后浏览器跳转到：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;http://localhost:10101/callback&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;接着就看到一行大字：&lt;strong&gt;“无法访问此网站，localhost的响应时间过长”&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/HWerr.png&quot; alt=&quot;DevEco Studio 登录授权时的报错信息&quot;&gt;&lt;/p&gt;
&lt;p&gt;然后，开始检查网络，检查代理，搜索引擎上搜索有没有网友有相关的问题。&lt;/p&gt;
&lt;p&gt;结果，方法好像都不太管用，就连官方文档中的常见问题中也没有，心态有点崩。&lt;/p&gt;
&lt;h2 id=&quot;尝试的过程&quot;&gt;&lt;a href=&quot;#尝试的过程&quot; class=&quot;headerlink&quot; title=&quot;尝试的过程&quot;&gt;&lt;/a&gt;尝试的过程&lt;/h2&gt;&lt;h3 id=&quot;第一次尝试：关代理&quot;&gt;&lt;a href=&quot;#第一次尝试：关代理&quot; class=&quot;headerlink&quot; title=&quot;第一次尝试：关代理&quot;&gt;&lt;/a&gt;第一次尝试：关代理&lt;/h3&gt;</summary>
    
    
    
    <category term="开发工具" scheme="https://www.hzv5.cn/categories/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="HarmonyOS" scheme="https://www.hzv5.cn/tags/HarmonyOS/"/>
    
    <category term="DevEco Studio" scheme="https://www.hzv5.cn/tags/DevEco-Studio/"/>
    
    <category term="踩坑记录" scheme="https://www.hzv5.cn/tags/%E8%B8%A9%E5%9D%91%E8%AE%B0%E5%BD%95/"/>
    
  </entry>
  
  <entry>
    <title>微信小程序个人资质审核血泪史：踩坑，从反复被拒到神奇过审</title>
    <link href="https://www.hzv5.cn/2026/06/13/xcx-Problems/"/>
    <id>https://www.hzv5.cn/2026/06/13/xcx-Problems/</id>
    <published>2026-06-13T11:30:00.000Z</published>
    <updated>2026-07-08T19:23:36.331Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近搞了个微信小程序，折腾了一个多星期才过审。过程中踩了不少坑，尤其是个人资质这块，简直让人想砸电脑。今天就和大家聊聊我这段经历，希望能给同样在做小程序的个人开发者一点帮助。</p><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">小程序涉及用户自行生成内容（文字、图片、音／) 的记录、分享，属社交﹣笔记范畴，为个人主体小程序未开放类目。</span><br></pre></td></tr></table></figure><p><img src="/img/xcxP/xcx_p02.jpg" alt="审核第二次打回"></p><h2 id="理想很丰满，现实很骨感"><a href="#理想很丰满，现实很骨感" class="headerlink" title="理想很丰满，现实很骨感"></a>理想很丰满，现实很骨感</h2><p>我的小程序功能其实挺简单的，就是一个节日头像制作分享类工具。用户可以自己写点文字、拍张照片、使用小程序预制的贴纸装饰后保存头像图片，在小程序里记录下来，也可以分享给朋友看看或者这只为自己的社交头像。我觉得这就是个很普通的工具，没啥复杂的地方。</p><p>提交审核的时候我还挺自信的，觉得这种应用肯定能过。结果第二天收到审核反馈，直接傻眼了。</p><p>审核意见写的是：“你的小程序涉及用户自行生成内容（文字、图片、音/视频）的记录、分享，属社交-笔记范畴，为个人主体小程序未开放类目，建议申请企业主体小程序。”</p><span id="more"></span><p>还有一个内容是让接入官方的审核机制，提醒用户的内容敏感违规之类的（这个比较简单对接微信开放平台的api就行了。）</p><p><img src="/img/xcxP/xcx_p01.jpg" alt="审核第一次打回全文"></p><p>我当时就懵了。我还专门查了个人主体的类目限制，想着这玩意儿不就是个工具吗？怎么就成社交了。</p><p>后来我才知道，个人主体的小程序根本做不了社交类功能。微信明确规定，涉及社交、医疗、金融这些类目的，基本不对个人主体开放。尤其是有用户生成内容（也就是UGC）的，审核极其严格。关键是，个人主体你连那个类目都选不了。我这个小程序，用户能自己生成内容，还能分享给别人，确实踩到了红线。</p><p>第一次提交，卒。</p><h2 id="被拒后的挣扎"><a href="#被拒后的挣扎" class="headerlink" title="被拒后的挣扎"></a>被拒后的挣扎</h2><p>收到拒绝后我还不死心，想着把分享功能删掉，单纯让用户自己记录，应该就没问题了吧？毕竟没有分享出去，就不算社交了对吧？</p><p>于是我把分享的代码全删了，整个小程序变成了一个纯本地的记录工具，用户写的东西、拍的照片都只保存在自己手机里。重新提交审核的时候我还挺有信心，觉得这下肯定能过了。</p><p>结果第二天一看，又被拒了。</p><p>这次的理由还是那套说辞，说小程序涉及用户生成内容的记录，属于社交-笔记类，个人主体未开放。</p><p>我真的是服了。用户写的文字、拍的照片，这些内容确实是用户自己生成的没错，但记录在自己手机里，不分享不交流，这也能算社交？这跟手机里的备忘录有什么区别？</p><p>我去网上搜了一圈，发现我不是一个人。好多个人开发者都遇到过这种问题。有人做了个图片编辑小程序，没有历史作品墙，不能分享编辑结果，结果审核还是被判社交-笔记。还有人做了个感恩日记类的记录工具，审核也说涉及社交-笔记，但开发者急了——说小程序里哪个功能能让别人看到？根本看不到啊。最气人的是，有人前面好几个版本都过了，就改了一点东西再提审就被拒了。还有开发者在社区发帖吐槽，说多次审核，全符合工具类目规范，内容仅本地存储，审核员却机械套模板驳回，从没实际复核过代码。</p><p>第二次提交，卒。</p><h2 id="快放弃的时候，奇迹发生了"><a href="#快放弃的时候，奇迹发生了" class="headerlink" title="快放弃的时候，奇迹发生了"></a>快放弃的时候，奇迹发生了</h2><p>说实话，第二次被拒后我真的很沮丧。本来都想放弃了，准备后面如果有机会再注册个公司搞企业主体或者说挂靠别人的企业算了。但后来想想，做个个人小玩意儿还得去开公司，也太折腾了吧？我就想它完全在本地使用，用户在手机上的所有操作都不会离开这台设备。</p><p>我又翻了下微信的审核规则，发现个人主体其实可以做“工具”类目，包括笔记工具、图片处理工具这些。问题在于审核系统看到有用户输入和生成内容的地方，就会自动打上“社交”标签，不管这些内容到底有没有对外公开。</p><p>我干脆一不做二不休，把之前所有和“云端”沾边的代码都删了，全面大改造。</p><p>总共干了这几件事：</p><ol><li><strong>删除了所有远程数据库相关的代码</strong>，原本存储笔记内容的云数据库表直接作废。</li><li><strong>把所有数据的增删改查全都换成了wx.setStorageSync和wx.getStorageSync</strong>，这样一来数据只存在用户自己的手机里，小程序根本没有外传数据的途径[reference:10]。</li><li><strong>把分享到朋友圈、分享给好友的API都去掉了</strong>，审核的时候再也不怕被查了。</li><li>提交审核的时候，在“备注”栏特意写了一句：“本程序为纯本地工具，所有数据仅保存在用户手机本地存储中，无任何内容上传、分享或社交功能。”</li></ol><p>提交之后我就不抱太大希望了，想着大不了再被拒就去买服务器、注册企业主体。</p><p>然后神奇的事情发生了。就过了一天，站内信提示审核通过，可以发布了。我当时看到消息还以为看错了，仔细看了一遍，确实是【审核通过】。</p><p>那一刻，我真的想说一句脏话表达心情。</p><h2 id="总结：个人开发者怎么避坑"><a href="#总结：个人开发者怎么避坑" class="headerlink" title="总结：个人开发者怎么避坑"></a>总结：个人开发者怎么避坑</h2><p>经过这次折腾，我总结了几点经验，给同样是个人开发者的朋友们参考：</p><ol><li><p><strong>先看类目再开发，别等做完才发现做不了</strong>。个人主体能做的类目非常有限，工具、教育、生活服务部分、出行、部分教育内容这些是企业专属之外能选的。开发前一定先去微信官方的开放服务类目列表里查清楚，确认你想做的类目个人主体能不能搞。</p></li><li><p><strong>UGC功能是重灾区</strong>。但凡涉及用户生成内容的，比如笔记、评论、帖子，审核都会特别严格。如果非要搞，尽量做成纯本地的，没有上传、没有分享，让审核人员挑不出刺来。不过说实话，纯本地数据也随时可能被抽检出问题，想稳妥还是得往工具类上靠。</p></li><li><p><strong>被动挨打不如主动出击</strong>。提交审核之前，用微信开发者工具里内置的“审核模拟”功能提前走一遍，能发现大部分问题提前改好。</p></li><li><p><strong>提交备注要写清楚</strong>。在提交审核的时候，务必要在“备注”栏里写清楚你的功能边界。像我写的“纯本地、无分享”就很有用，至少给审核人员留了个参考。审核人员也联系不到你，主动说明情况能少很多麻烦。</p></li><li><p><strong>做好多次提交的准备</strong>。小程序审核时间一般是1到7个工作日，个人小程序一年有三次加急审核的机会。一次不过很正常，改完再交就行，心态放平最重要。有时候一次审核过了也别高兴太早，后面更新版本指不定又给你打回来，说变就变。</p></li><li><p><strong>调整心态</strong>。虽然有点玄学，但个人开发者本来就是夹缝中求生存。我这次能过，可能确实有运气的成分在里面。但换个角度想，审核机制确实也有边界模糊的地方[reference:18]。不管怎么说，希望还在审核路上挣扎的朋友们都能尽快通过！</p></li></ol><p>希望这篇文章经历对大家有所帮助，就这样，么么哒~</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;最近搞了个微信小程序，折腾了一个多星期才过审。过程中踩了不少坑，尤其是个人资质这块，简直让人想砸电脑。今天就和大家聊聊我这段经历，希望能给同样在做小程序的个人开发者一点帮助。&lt;/p&gt;
&lt;h2 id=&quot;问题&quot;&gt;&lt;a href=&quot;#问题&quot; class=&quot;headerlink&quot; title=&quot;问题&quot;&gt;&lt;/a&gt;问题&lt;/h2&gt;&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;小程序涉及用户自行生成内容（文字、图片、音／) 的记录、分享，属社交﹣笔记范畴，为个人主体小程序未开放类目。&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;img src=&quot;/img/xcxP/xcx_p02.jpg&quot; alt=&quot;审核第二次打回&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;理想很丰满，现实很骨感&quot;&gt;&lt;a href=&quot;#理想很丰满，现实很骨感&quot; class=&quot;headerlink&quot; title=&quot;理想很丰满，现实很骨感&quot;&gt;&lt;/a&gt;理想很丰满，现实很骨感&lt;/h2&gt;&lt;p&gt;我的小程序功能其实挺简单的，就是一个节日头像制作分享类工具。用户可以自己写点文字、拍张照片、使用小程序预制的贴纸装饰后保存头像图片，在小程序里记录下来，也可以分享给朋友看看或者这只为自己的社交头像。我觉得这就是个很普通的工具，没啥复杂的地方。&lt;/p&gt;
&lt;p&gt;提交审核的时候我还挺自信的，觉得这种应用肯定能过。结果第二天收到审核反馈，直接傻眼了。&lt;/p&gt;
&lt;p&gt;审核意见写的是：“你的小程序涉及用户自行生成内容（文字、图片、音/视频）的记录、分享，属社交-笔记范畴，为个人主体小程序未开放类目，建议申请企业主体小程序。”&lt;/p&gt;</summary>
    
    
    
    <category term="小程序开发" scheme="https://www.hzv5.cn/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="微信小程序" scheme="https://www.hzv5.cn/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
    <category term="审核踩坑" scheme="https://www.hzv5.cn/tags/%E5%AE%A1%E6%A0%B8%E8%B8%A9%E5%9D%91/"/>
    
    <category term="个人开发者" scheme="https://www.hzv5.cn/tags/%E4%B8%AA%E4%BA%BA%E5%BC%80%E5%8F%91%E8%80%85/"/>
    
  </entry>
  
  <entry>
    <title>如何实现公众号自动回复或做一个短视频无水印解析机器人？</title>
    <link href="https://www.hzv5.cn/2026/06/05/WeChat-official-account-ai/"/>
    <id>https://www.hzv5.cn/2026/06/05/WeChat-official-account-ai/</id>
    <published>2026-06-05T16:00:00.000Z</published>
    <updated>2026-07-08T19:23:36.329Z</updated>
    
    <content type="html"><![CDATA[<h2 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h2><p>前几天一个朋友问我，能不能让公众号自动回复用户发的抖音链接，把视频或者图集解析出来。我看了看微信后台的自动回复，只能匹配固定的关键词，搞不定这种动态链接api接口的方式。想了想，干脆自己动手写一个。</p><p>考虑到成本，我不想买服务器。Vercel 的免费额度对于这种小场景足够用了，而且域名不用备案，只要自己有域名解析过去就行。整个过程折腾了差不多两天，踩了不少坑，记录下来当个笔记。</p><h2 id="整体思路"><a href="#整体思路" class="headerlink" title="整体思路"></a>整体思路</h2><p>微信公众号收到用户消息后，会向配置的服务器地址推送一条 XML 格式的请求。我们要做的就是：</p><ol><li>验证消息确实来自微信（验证签名）</li><li>解析用户消息内容，提取其中的抖音分享链接</li><li>调用一个现成的解析接口，拿到视频或图片信息</li><li>按照微信要求的 XML 格式回复给用户</li></ol><p>听起来不复杂，但细节很多。</p><span id="more"></span><h2 id="第一步：准备-Vercel-项目"><a href="#第一步：准备-Vercel-项目" class="headerlink" title="第一步：准备 Vercel 项目"></a>第一步：准备 Vercel 项目</h2><p>注册 Vercel 并登录，用 GitHub 账号授权。新建一个项目，关联一个仓库。我直接在项目根目录下建了 <code>api</code> 文件夹，里面放一个 <code>wx.js</code> 文件。</p><p>Vercel 的约定：<code>api/xxx.js</code> 会自动映射成 <code>/api/xxx</code> 路由。后面微信后台填的 URL 就是 <code>https://你的域名/api/wx</code>。</p><h2 id="第二步：写处理代码"><a href="#第二步：写处理代码" class="headerlink" title="第二步：写处理代码"></a>第二步：写处理代码</h2><p>我从开源项目 <code>aiwechat-vercel</code> 得到启发，但根据自己的需求大幅修改了。核心逻辑是：</p><ul><li>接收微信 POST 来的 XML</li><li>提取 <code>Content</code> 字段</li><li>用正则匹配抖音链接（<code>v.douyin.com</code> 或 <code>iesdouyin.com</code>）</li><li>请求 <code>http://api.hzv5.cn/dysp.php?url=...</code> 解析</li><li>把解析结果拼成文本，包装成 XML 回复</li></ul><p>踩的第一个坑：Vercel 的 <code>req.body</code> 默认是 Buffer，不是字符串，需要手动转。而且微信的 XML 里用了 CDATA，解析时要注意换行和空格。我一开始用正则死活匹配不到内容，后来写了一个简单的 <code>extractTag</code> 函数，同时支持普通标签和 CDATA。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">function extractTag(xml, tag) &#123;</span><br><span class="line">  const cdataRegex = new RegExp(`&lt;$&#123;tag&#125;&gt;&lt;\\!\\[CDATA\\[([\\s\\S]*?)\\]\\]&gt;&lt;/$&#123;tag&#125;&gt;`);</span><br><span class="line">  let match = xml.match(cdataRegex);</span><br><span class="line">  if (match) return match[1];</span><br><span class="line">  const textRegex = new RegExp(`&lt;$&#123;tag&#125;&gt;([\\s\\S]*?)&lt;/$&#123;tag&#125;&gt;`);</span><br><span class="line">  match = xml.match(textRegex);</span><br><span class="line">  return match ? match[1].trim() : &#x27;&#x27;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="第三步：配置环境变量"><a href="#第三步：配置环境变量" class="headerlink" title="第三步：配置环境变量"></a>第三步：配置环境变量</h2><p>微信验证需要一个 Token，自己随便设一个字符串。在 Vercel 项目里，进入 Settings -&gt; Environment Variables，添加 WX_TOKEN，值就是你自己定的那个。</p><p>注意：不要被那个“Create Pre-production Environment”误导了，那东西不是用来添加环境变量的。我一开始也在那上面浪费了不少时间。</p><p>添加完变量后必须 Redeploy，否则不生效。</p><h2 id="第四步：配置微信公众号"><a href="#第四步：配置微信公众号" class="headerlink" title="第四步：配置微信公众号"></a>第四步：配置微信公众号</h2><p>登录公众平台，进入“设置与开发” -&gt; “基本配置” -&gt; “服务器配置”。</p><p>· URL: https://你的域名/api/wx<br>· Token: 和上面 WX_TOKEN 保持一致<br>· 消息加解密方式: 选“明文模式”（省事）</p><p>提交后如果 Token 匹配且代码没有问题，就会验证通过。</p><h2 id="第五步：各种坑"><a href="#第五步：各种坑" class="headerlink" title="第五步：各种坑"></a>第五步：各种坑</h2><h4 id="坑一：收不到消息，日志显示“非文本消息”"><a href="#坑一：收不到消息，日志显示“非文本消息”" class="headerlink" title="坑一：收不到消息，日志显示“非文本消息”"></a>坑一：收不到消息，日志显示“非文本消息”</h4><p>我发送了好几条 test，Vercel 日志里却显示“非文本消息，忽略”。排查了很久，发现是 extractTag 没有拿到 MsgType，导致代码认为不是文本消息。</p><p>后来在代码里加了很多 console.log，把原始 XML 打印出来，才发现微信发的 XML 里 Content 标签前后有换行，正则没考虑到。改了正则就好了。</p><h4 id="坑二：解析出来的链接太长，多图回复失败"><a href="#坑二：解析出来的链接太长，多图回复失败" class="headerlink" title="坑二：解析出来的链接太长，多图回复失败"></a>坑二：解析出来的链接太长，多图回复失败</h4><p>抖音图文集的图片链接非常长，一个链接就有两百多个字符。如果图集有七八张图，加上作者、标题、点赞数，总长度很容易超过微信的限制（大概是 2048 字节）。结果是用户那边收不到任何回复，或者看到乱七八糟的“查看图片.0”之类的东西。</p><p>解决方案：生成回复前计算字节长度，如果超过限制就动态减少图片数量，直到满足要求，并在末尾提示“仅显示前 N 张，共 M 张”。同时把标题截断到 15 个字符，作者、标题、点赞合并成一行，图片显示为“图1”“图2”这种简短的链接文字，点击即可跳转。这样既节省篇幅又不影响使用。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">buildFullReply</span>(<span class="params">data</span>) &#123;</span><br><span class="line">  <span class="comment">// 构建头部</span></span><br><span class="line">  <span class="keyword">const</span> header = <span class="string">`<span class="subst">$&#123;author&#125;</span> | <span class="subst">$&#123;shortenTitle(title)&#125;</span> | ❤️<span class="subst">$&#123;like&#125;</span>`</span>;</span><br><span class="line">  <span class="comment">// 尝试加入所有图片，超长则逐步减少</span></span><br><span class="line">  <span class="keyword">while</span> (currentUrls.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> testText = lines.<span class="title function_">concat</span>(currentUrls.<span class="title function_">map</span>(<span class="function">(<span class="params">url, idx</span>) =&gt;</span> <span class="string">`&lt;a href=&quot;<span class="subst">$&#123;url&#125;</span>&quot;&gt;图<span class="subst">$&#123;idx+<span class="number">1</span>&#125;</span>&lt;/a&gt;`</span>)).<span class="title function_">join</span>(<span class="string">&#x27;\n&#x27;</span>);</span><br><span class="line">    <span class="keyword">if</span> (<span class="title class_">Buffer</span>.<span class="title function_">byteLength</span>(testText, <span class="string">&#x27;utf8&#x27;</span>) &lt;= <span class="variable constant_">MAX_BYTES</span>) &#123;</span><br><span class="line">      replyText = testText;</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    currentUrls.<span class="title function_">pop</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 如果图片被截断，追加提示</span></span><br><span class="line">  <span class="keyword">if</span> (currentUrls.<span class="property">length</span> &lt; totalNum) &#123;</span><br><span class="line">    replyText += <span class="string">`\n(仅显示前<span class="subst">$&#123;currentUrls.length&#125;</span>张，共<span class="subst">$&#123;totalNum&#125;</span>张)`</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> replyText;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="坑三：非抖音消息也会回复提示"><a href="#坑三：非抖音消息也会回复提示" class="headerlink" title="坑三：非抖音消息也会回复提示"></a>坑三：非抖音消息也会回复提示</h4><p>一开始的逻辑是：如果用户发的消息不包含抖音链接，就回复“请发送抖音分享链接”。后来觉得这样挺烦的，用户随便说句话都要被怼一下。于是改成：没有抖音链接就直接返回 success，不回复任何内容。静默忽略，体验好多了。</p><h4 id="坑四：自定义菜单不见了"><a href="#坑四：自定义菜单不见了" class="headerlink" title="坑四：自定义菜单不见了"></a>坑四：自定义菜单不见了</h4><p>启用服务器配置之后，公众号后台的自定义菜单和自动回复会被自动停用。这是微信的设计：开发者模式和后端配置互斥。</p><p>本来想通过 API 重新创建菜单，但发现个人未认证的订阅号根本没有调用菜单接口的权限。调用就返回 48001 错误。</p><p>折腾了一圈发现没戏。最后我选择不搞菜单了，反正核心的解析功能还能用。如果实在想要菜单，要么暂停服务器配置（但解析功能就没了），要么去微信认证（个人号认证门槛不低，而且花钱）。我选择了接受现实。</p><h4 id="坑五：创建菜单接口访问失败，报找不到-node-fetch"><a href="#坑五：创建菜单接口访问失败，报找不到-node-fetch" class="headerlink" title="坑五：创建菜单接口访问失败，报找不到 node-fetch"></a>坑五：创建菜单接口访问失败，报找不到 node-fetch</h4><p>后来想单独写一个创建菜单的接口放在 Vercel，访问时 500 错误，日志显示缺少 node-fetch 模块。其实 Vercel 的 Node.js 运行时版本是 18+，已经原生支持 fetch，根本不需要这个依赖。删掉 require(‘node-fetch’) 就好了。</p><p>再后来调通之后又遇到 IP 白名单问题，加了白名单又遇到 48001…… 最终还是因为权限问题放弃。个人号就是个人号，认了。</p><h2 id="最终稳定版代码"><a href="#最终稳定版代码" class="headerlink" title="最终稳定版代码"></a>最终稳定版代码</h2><p>我把最终能用的 api/wx.js 完整贴出来。包含了智能长度控制、无抖音链接不回复、支持视频和图文集解析。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> crypto = <span class="built_in">require</span>(<span class="string">&#x27;crypto&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">TOKEN</span> = process.<span class="property">env</span>.<span class="property">WX_TOKEN</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">API_URL</span> = <span class="string">&#x27;http://api.hzv5.cn/dysp.php&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">MAX_BYTES</span> = <span class="number">2000</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getRawBodyFromReq</span>(<span class="params">req</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> data = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">    req.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="function"><span class="params">chunk</span> =&gt;</span> &#123; data += chunk; &#125;);</span><br><span class="line">    req.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, <span class="function">() =&gt;</span> &#123; <span class="title function_">resolve</span>(data); &#125;);</span><br><span class="line">    req.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, reject);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">checkSignature</span>(<span class="params">signature, timestamp, nonce</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> arr = [<span class="variable constant_">TOKEN</span>, timestamp, nonce].<span class="title function_">sort</span>();</span><br><span class="line">  <span class="keyword">const</span> sha1 = crypto.<span class="title function_">createHash</span>(<span class="string">&#x27;sha1&#x27;</span>).<span class="title function_">update</span>(arr.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>)).<span class="title function_">digest</span>(<span class="string">&#x27;hex&#x27;</span>);</span><br><span class="line">  <span class="keyword">return</span> sha1 === signature;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">extractTag</span>(<span class="params">xml, tag</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> cdataRegex = <span class="keyword">new</span> <span class="title class_">RegExp</span>(<span class="string">`&lt;<span class="subst">$&#123;tag&#125;</span>&gt;&lt;\\!\\[CDATA\\[([\\s\\S]*?)\\]\\]&gt;&lt;/<span class="subst">$&#123;tag&#125;</span>&gt;`</span>);</span><br><span class="line">  <span class="keyword">let</span> match = xml.<span class="title function_">match</span>(cdataRegex);</span><br><span class="line">  <span class="keyword">if</span> (match) <span class="keyword">return</span> match[<span class="number">1</span>];</span><br><span class="line">  <span class="keyword">const</span> textRegex = <span class="keyword">new</span> <span class="title class_">RegExp</span>(<span class="string">`&lt;<span class="subst">$&#123;tag&#125;</span>&gt;([\\s\\S]*?)&lt;/<span class="subst">$&#123;tag&#125;</span>&gt;`</span>);</span><br><span class="line">  match = xml.<span class="title function_">match</span>(textRegex);</span><br><span class="line">  <span class="keyword">return</span> match ? match[<span class="number">1</span>].<span class="title function_">trim</span>() : <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">extractDouyinLink</span>(<span class="params">text</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!text) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">  <span class="keyword">const</span> regex = <span class="regexp">/https?:\/\/(v\.douyin\.com|iesdouyin\.com)\/[a-zA-Z0-9_-]+\/?/</span>;</span><br><span class="line">  <span class="keyword">const</span> match = text.<span class="title function_">match</span>(regex);</span><br><span class="line">  <span class="keyword">return</span> match ? match[<span class="number">0</span>] : <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">parseDouyin</span>(<span class="params">shareUrl</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> url = <span class="string">`<span class="subst">$&#123;API_URL&#125;</span>?url=<span class="subst">$&#123;<span class="built_in">encodeURIComponent</span>(shareUrl)&#125;</span>`</span>;</span><br><span class="line">  <span class="keyword">const</span> res = <span class="keyword">await</span> <span class="title function_">fetch</span>(url, &#123; <span class="attr">headers</span>: &#123; <span class="string">&#x27;User-Agent&#x27;</span>: <span class="string">&#x27;Mozilla/5.0&#x27;</span> &#125; &#125;);</span><br><span class="line">  <span class="keyword">if</span> (!res.<span class="property">ok</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`HTTP <span class="subst">$&#123;res.status&#125;</span>`</span>);</span><br><span class="line">  <span class="keyword">const</span> data = <span class="keyword">await</span> res.<span class="title function_">json</span>();</span><br><span class="line">  <span class="keyword">if</span> (data.<span class="property">code</span> !== <span class="number">200</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(data.<span class="property">msg</span> || <span class="string">&#x27;解析失败&#x27;</span>);</span><br><span class="line">  <span class="keyword">return</span> data.<span class="property">data</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">extractImageUrls</span>(<span class="params">urlField</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!urlField) <span class="keyword">return</span> [];</span><br><span class="line">  <span class="keyword">if</span> (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(urlField)) <span class="keyword">return</span> urlField;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> urlField === <span class="string">&#x27;object&#x27;</span>) <span class="keyword">return</span> <span class="title class_">Object</span>.<span class="title function_">values</span>(urlField);</span><br><span class="line">  <span class="keyword">return</span> [urlField];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">shortenTitle</span>(<span class="params">title, maxLen = <span class="number">15</span></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!title) <span class="keyword">return</span> <span class="string">&#x27;无标题&#x27;</span>;</span><br><span class="line">  <span class="keyword">if</span> (title.<span class="property">length</span> &lt;= maxLen) <span class="keyword">return</span> title;</span><br><span class="line">  <span class="keyword">return</span> title.<span class="title function_">substring</span>(<span class="number">0</span>, maxLen) + <span class="string">&#x27;…&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">buildFullReply</span>(<span class="params">data</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> type = data.<span class="property">type</span>;</span><br><span class="line">  <span class="keyword">const</span> author = data.<span class="property">author</span> || <span class="string">&#x27;未知&#x27;</span>;</span><br><span class="line">  <span class="keyword">const</span> title = <span class="title function_">shortenTitle</span>(data.<span class="property">title</span>);</span><br><span class="line">  <span class="keyword">const</span> like = data.<span class="property">like</span> || <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">const</span> header = <span class="string">`<span class="subst">$&#123;author&#125;</span> | <span class="subst">$&#123;title&#125;</span> | ❤️<span class="subst">$&#123;like&#125;</span>`</span>;</span><br><span class="line">  <span class="keyword">const</span> lines = [header];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (type === <span class="string">&#x27;视频&#x27;</span>) &#123;</span><br><span class="line">    lines.<span class="title function_">push</span>(<span class="string">`&lt;a href=&quot;<span class="subst">$&#123;data.url&#125;</span>&quot;&gt;▶ 观看视频&lt;/a&gt;`</span>);</span><br><span class="line">    <span class="keyword">return</span> lines.<span class="title function_">join</span>(<span class="string">&#x27;\n&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (type !== <span class="string">&#x27;图文&#x27;</span>) &#123;</span><br><span class="line">    lines.<span class="title function_">push</span>(<span class="string">`未知类型：<span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(data).substring(<span class="number">0</span>, <span class="number">100</span>)&#125;</span>`</span>);</span><br><span class="line">    <span class="keyword">return</span> lines.<span class="title function_">join</span>(<span class="string">&#x27;\n&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> allUrls = <span class="title function_">extractImageUrls</span>(data.<span class="property">url</span>);</span><br><span class="line">  <span class="keyword">const</span> totalNum = data.<span class="property">num</span> || allUrls.<span class="property">length</span>;</span><br><span class="line">  lines.<span class="title function_">push</span>(<span class="string">`📷 共<span class="subst">$&#123;totalNum&#125;</span>张图`</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> currentUrls = [...allUrls];</span><br><span class="line">  <span class="keyword">let</span> replyText = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span> (currentUrls.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> testLines = [...lines];</span><br><span class="line">    currentUrls.<span class="title function_">forEach</span>(<span class="function">(<span class="params">url, idx</span>) =&gt;</span> &#123;</span><br><span class="line">      testLines.<span class="title function_">push</span>(<span class="string">`&lt;a href=&quot;<span class="subst">$&#123;url&#125;</span>&quot;&gt;图<span class="subst">$&#123;idx+<span class="number">1</span>&#125;</span>&lt;/a&gt;`</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">const</span> testText = testLines.<span class="title function_">join</span>(<span class="string">&#x27;\n&#x27;</span>);</span><br><span class="line">    <span class="keyword">const</span> byteLength = <span class="title class_">Buffer</span>.<span class="title function_">byteLength</span>(testText, <span class="string">&#x27;utf8&#x27;</span>);</span><br><span class="line">    <span class="keyword">if</span> (byteLength &lt;= <span class="variable constant_">MAX_BYTES</span>) &#123;</span><br><span class="line">      replyText = testText;</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (currentUrls.<span class="property">length</span> === <span class="number">1</span>) &#123;</span><br><span class="line">        replyText = testText;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      currentUrls.<span class="title function_">pop</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (replyText &amp;&amp; currentUrls.<span class="property">length</span> &lt; allUrls.<span class="property">length</span>) &#123;</span><br><span class="line">    replyText += <span class="string">`\n(仅显示前<span class="subst">$&#123;currentUrls.length&#125;</span>张，共<span class="subst">$&#123;totalNum&#125;</span>张)`</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> replyText || (lines.<span class="title function_">join</span>(<span class="string">&#x27;\n&#x27;</span>) + <span class="string">&#x27;\n(无法生成回复)&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">buildReply</span>(<span class="params">toUser, fromUser, content</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> timestamp = <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Date</span>.<span class="title function_">now</span>() / <span class="number">1000</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`&lt;xml&gt;</span></span><br><span class="line"><span class="string">  &lt;ToUserName&gt;&lt;![CDATA[<span class="subst">$&#123;toUser&#125;</span>]]&gt;&lt;/ToUserName&gt;</span></span><br><span class="line"><span class="string">  &lt;FromUserName&gt;&lt;![CDATA[<span class="subst">$&#123;fromUser&#125;</span>]]&gt;&lt;/FromUserName&gt;</span></span><br><span class="line"><span class="string">  &lt;CreateTime&gt;<span class="subst">$&#123;timestamp&#125;</span>&lt;/CreateTime&gt;</span></span><br><span class="line"><span class="string">  &lt;MsgType&gt;&lt;![CDATA[text]]&gt;&lt;/MsgType&gt;</span></span><br><span class="line"><span class="string">  &lt;Content&gt;&lt;![CDATA[<span class="subst">$&#123;content&#125;</span>]]&gt;&lt;/Content&gt;</span></span><br><span class="line"><span class="string">&lt;/xml&gt;`</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="title function_">async</span> (req, res) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (req.<span class="property">method</span> === <span class="string">&#x27;GET&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; signature, timestamp, nonce, echostr &#125; = req.<span class="property">query</span>;</span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_">checkSignature</span>(signature, timestamp, nonce)) &#123;</span><br><span class="line">      <span class="keyword">return</span> res.<span class="title function_">status</span>(<span class="number">200</span>).<span class="title function_">send</span>(echostr);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res.<span class="title function_">status</span>(<span class="number">401</span>).<span class="title function_">send</span>(<span class="string">&#x27;Invalid signature&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (req.<span class="property">method</span> === <span class="string">&#x27;POST&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> rawXml = <span class="keyword">await</span> <span class="title function_">getRawBodyFromReq</span>(req);</span><br><span class="line">      <span class="keyword">const</span> fromUser = <span class="title function_">extractTag</span>(rawXml, <span class="string">&#x27;FromUserName&#x27;</span>);</span><br><span class="line">      <span class="keyword">const</span> toUser = <span class="title function_">extractTag</span>(rawXml, <span class="string">&#x27;ToUserName&#x27;</span>);</span><br><span class="line">      <span class="keyword">const</span> content = <span class="title function_">extractTag</span>(rawXml, <span class="string">&#x27;Content&#x27;</span>);</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (!content) <span class="keyword">return</span> res.<span class="title function_">status</span>(<span class="number">200</span>).<span class="title function_">send</span>(<span class="string">&#x27;success&#x27;</span>);</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> douyinUrl = <span class="title function_">extractDouyinLink</span>(content);</span><br><span class="line">      <span class="keyword">if</span> (!douyinUrl) <span class="keyword">return</span> res.<span class="title function_">status</span>(<span class="number">200</span>).<span class="title function_">send</span>(<span class="string">&#x27;success&#x27;</span>);</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> parsed = <span class="keyword">await</span> <span class="title function_">parseDouyin</span>(douyinUrl);</span><br><span class="line">      <span class="keyword">const</span> replyText = <span class="title function_">buildFullReply</span>(parsed);</span><br><span class="line">      <span class="keyword">const</span> replyXml = <span class="title function_">buildReply</span>(fromUser, toUser, replyText);</span><br><span class="line">      res.<span class="title function_">setHeader</span>(<span class="string">&#x27;Content-Type&#x27;</span>, <span class="string">&#x27;application/xml&#x27;</span>);</span><br><span class="line">      <span class="keyword">return</span> res.<span class="title function_">status</span>(<span class="number">200</span>).<span class="title function_">send</span>(replyXml);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">error</span>(err);</span><br><span class="line">      <span class="keyword">return</span> res.<span class="title function_">status</span>(<span class="number">200</span>).<span class="title function_">send</span>(<span class="string">&#x27;success&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  res.<span class="title function_">status</span>(<span class="number">405</span>).<span class="title function_">send</span>(<span class="string">&#x27;Method Not Allowed&#x27;</span>);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>用 Vercel 搭公众号机器人，优点是免费、不用折腾服务器、域名免备案。缺点是 Vercel 函数执行时间只有 10 秒，但解析抖音通常一两秒就够了。</p><p>另外，个人订阅号的权限限制是个硬伤。如果你只想做一个简单的消息自动回复，那没问题；但如果你想要自定义菜单、客服消息、网页授权等高级功能，最好老老实实搞个认证的服务号，或者用测试号体验。</p><p>这次折腾下来，最深的体会是：别小看微信的 XML 解析，也别高估个人号的权限。先把最简单的跑通，再一点点加功能，遇到问题耐心看日志，总能解决的。</p><p>希望这篇文章能帮到也想折腾公众号机器人的朋友。如果你也遇到类似的坑，欢迎交流。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;起因&quot;&gt;&lt;a href=&quot;#起因&quot; class=&quot;headerlink&quot; title=&quot;起因&quot;&gt;&lt;/a&gt;起因&lt;/h2&gt;&lt;p&gt;前几天一个朋友问我，能不能让公众号自动回复用户发的抖音链接，把视频或者图集解析出来。我看了看微信后台的自动回复，只能匹配固定的关键词，搞不定这种动态链接api接口的方式。想了想，干脆自己动手写一个。&lt;/p&gt;
&lt;p&gt;考虑到成本，我不想买服务器。Vercel 的免费额度对于这种小场景足够用了，而且域名不用备案，只要自己有域名解析过去就行。整个过程折腾了差不多两天，踩了不少坑，记录下来当个笔记。&lt;/p&gt;
&lt;h2 id=&quot;整体思路&quot;&gt;&lt;a href=&quot;#整体思路&quot; class=&quot;headerlink&quot; title=&quot;整体思路&quot;&gt;&lt;/a&gt;整体思路&lt;/h2&gt;&lt;p&gt;微信公众号收到用户消息后，会向配置的服务器地址推送一条 XML 格式的请求。我们要做的就是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;验证消息确实来自微信（验证签名）&lt;/li&gt;
&lt;li&gt;解析用户消息内容，提取其中的抖音分享链接&lt;/li&gt;
&lt;li&gt;调用一个现成的解析接口，拿到视频或图片信息&lt;/li&gt;
&lt;li&gt;按照微信要求的 XML 格式回复给用户&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;听起来不复杂，但细节很多。&lt;/p&gt;</summary>
    
    
    
    <category term="项目实战" scheme="https://www.hzv5.cn/categories/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/"/>
    
    
    <category term="微信公众号" scheme="https://www.hzv5.cn/tags/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7/"/>
    
    <category term="Vercel" scheme="https://www.hzv5.cn/tags/Vercel/"/>
    
    <category term="Serverless" scheme="https://www.hzv5.cn/tags/Serverless/"/>
    
    <category term="抖音解析" scheme="https://www.hzv5.cn/tags/%E6%8A%96%E9%9F%B3%E8%A7%A3%E6%9E%90/"/>
    
  </entry>
  
  <entry>
    <title>以后在手机上用 Termux 和 GitHub Actions 更新 Hexo 博客</title>
    <link href="https://www.hzv5.cn/2026/06/01/GitHub_Actions/"/>
    <id>https://www.hzv5.cn/2026/06/01/GitHub_Actions/</id>
    <published>2026-06-01T14:30:00.000Z</published>
    <updated>2026-07-08T19:23:36.329Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>如果你像我一样，经常在手机上写文章，却苦于无法在手机端运行 <code>hexo generate</code> 和 <code>hexo deploy</code>（耗电且容易出错），那么这篇教程正是为你准备的。我们将利用 <strong>Termux</strong> 在手机上完成 Git 操作，再借助 <strong>GitHub Actions</strong> 在云端自动构建和部署博客——你只需要专注写作，剩下的交给云端。</p><p><strong>最终效果</strong>：在手机上写完文章 → <code>git push</code> → 等待 2~5 分钟 → 博客自动更新。全程无需在手机运行 Hexo 或 Node.js。</p><span id="more"></span><h2 id="整体思路"><a href="#整体思路" class="headerlink" title="整体思路"></a>整体思路</h2><p>采用双仓库隔离方案：</p><ul><li><strong>私有源码仓库</strong>（例如 <code>hexo-blog-source</code>）：存放 Hexo 的源文件（<code>source/</code>、<code>themes/</code>、<code>_config.yml</code> 等）。</li><li><strong>公开页面仓库</strong>（例如 <code>yourname.github.io</code>）：存放 Hexo 生成的静态网站文件，用于 GitHub Pages 展示。</li></ul><p>自动化流程：当你向<strong>源码仓库</strong>推送代码时，GitHub Actions 会自动拉取、安装依赖、生成静态文件，并将 <code>public/</code> 文件夹推送到<strong>页面仓库</strong>，最后通过 GitHub Pages 发布。</p><h2 id="一、手机端环境搭建（Termux）"><a href="#一、手机端环境搭建（Termux）" class="headerlink" title="一、手机端环境搭建（Termux）"></a>一、手机端环境搭建（Termux）</h2><h3 id="1-安装-Termux-并更新基础包"><a href="#1-安装-Termux-并更新基础包" class="headerlink" title="1. 安装 Termux 并更新基础包"></a>1. 安装 Termux 并更新基础包</h3><p>从 F-Droid 或 Google Play 下载 Termux，打开后执行：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pkg update &amp;&amp; pkg upgrade -y</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>建议更换国内软件源：termux-change-repo，选择清华或中科大源，速度更快。</p><h3 id="2-安装-Git、OpenSSH-和-Node-js"><a href="#2-安装-Git、OpenSSH-和-Node-js" class="headerlink" title="2. 安装 Git、OpenSSH 和 Node.js"></a>2. 安装 Git、OpenSSH 和 Node.js</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pkg install git openssh nodejs -y</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="3-配置-Git-用户信息（可选但推荐）"><a href="#3-配置-Git-用户信息（可选但推荐）" class="headerlink" title="3. 配置 Git 用户信息（可选但推荐）"></a>3. 配置 Git 用户信息（可选但推荐）</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git config --global user.name &quot;你的GitHub用户名&quot;</span><br><span class="line">git config --global user.email &quot;你的GitHub邮箱&quot;</span><br></pre></td></tr></table></figure><h1 id="二、GitHub-端准备工作"><a href="#二、GitHub-端准备工作" class="headerlink" title="二、GitHub 端准备工作"></a>二、GitHub 端准备工作</h1><h3 id="1-创建两个仓库"><a href="#1-创建两个仓库" class="headerlink" title="1. 创建两个仓库"></a>1. 创建两个仓库</h3><p>页面仓库（公开）：名称必须为 &lt;你的GitHub用户名&gt;.github.io，例如 myusername.github.io。初始化时不要添加 README 或 .gitignore，保持完全空白。</p><p>源码仓库（私有）：名称随意，如 <code>hexo-blog-source</code>。</p><h3 id="2-生成-Personal-Access-Token（Classic）"><a href="#2-生成-Personal-Access-Token（Classic）" class="headerlink" title="2. 生成 Personal Access Token（Classic）"></a>2. 生成 Personal Access Token（Classic）</h3><p>这一步是让 GitHub Actions 有权将构建好的文件写入你的页面仓库。</p><p>登录 <code>GitHub</code> → <code>Settings</code> → <code>Developer settings</code> → <code>Personal access tokens</code> → <code>Tokens (classic)</code> → <code>Generate new token (classic)</code>。</p><p>设置：</p><p>Note：填写 <code>HEXO_PAGES_TOKEN</code></p><p>Expiration：选择 <code>No expiration</code></p><p>Scopes：勾选 <code>repo</code>（全选即可）</p><p>点击生成，立即复制保存生成的 token（只显示一次）。</p><h3 id="3-将-Token-添加为仓库-Secret"><a href="#3-将-Token-添加为仓库-Secret" class="headerlink" title="3. 将 Token 添加为仓库 Secret"></a>3. 将 Token 添加为仓库 Secret</h3><p>进入你的源码仓库（例如 hexo-blog-source）→ <code>Settings</code> → <code>Secrets and variables</code> → <code>Actions</code> → <code>New repository secret</code>。</p><p>Name 填写 <code>PAGES_TOKEN</code>，Secret 粘贴刚才的 token，点击 Add secret。</p><h1 id="三、编写-GitHub-Actions-工作流"><a href="#三、编写-GitHub-Actions-工作流" class="headerlink" title="三、编写 GitHub Actions 工作流"></a>三、编写 GitHub Actions 工作流</h1><h3 id="1-在源码仓库中创建-github-workflows-deploy-yml"><a href="#1-在源码仓库中创建-github-workflows-deploy-yml" class="headerlink" title="1. 在源码仓库中创建 .github/workflows/deploy.yml"></a>1. 在源码仓库中创建 <code>.github/workflows/deploy.yml</code></h3><p>在你本地的 Hexo 博客根目录下创建该文件，内容如下（需要替换仓库名）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">yaml</span><br><span class="line">name: Build and Deploy Hexo</span><br><span class="line"></span><br><span class="line">on:</span><br><span class="line">  push:</span><br><span class="line">    branches:</span><br><span class="line">      - main  # 监听 main 分支</span><br><span class="line"></span><br><span class="line">jobs:</span><br><span class="line">  build-and-deploy:</span><br><span class="line">    runs-on: ubuntu-latest</span><br><span class="line">    steps:</span><br><span class="line">      - name: Checkout source code</span><br><span class="line">        uses: actions/checkout@v4</span><br><span class="line"></span><br><span class="line">      - name: Setup Node.js</span><br><span class="line">        uses: actions/setup-node@v4</span><br><span class="line">        with:</span><br><span class="line">          node-version: &#x27;20&#x27;</span><br><span class="line"></span><br><span class="line">      - name: Install hexo-cli</span><br><span class="line">        run: npm install -g hexo-cli</span><br><span class="line"></span><br><span class="line">      - name: Install dependencies</span><br><span class="line">        run: npm install</span><br><span class="line"></span><br><span class="line">      - name: Generate static files</span><br><span class="line">        run: hexo clean &amp;&amp; hexo generate</span><br><span class="line"></span><br><span class="line">      - name: Deploy to GitHub Pages</span><br><span class="line">        uses: peaceiris/actions-gh-pages@v3</span><br><span class="line">        with:</span><br><span class="line">          personal_token: $&#123;&#123; secrets.PAGES_TOKEN &#125;&#125;</span><br><span class="line">          external_repository: 你的GitHub用户名/你的页面仓库名   # 例如 myusername/myusername.github.io</span><br><span class="line">          publish_branch: main          # 页面仓库的分支，通常是 main</span><br><span class="line">          publish_dir: ./public</span><br><span class="line">          commit_message: $&#123;&#123; github.event.head_commit.message &#125;&#125;</span><br><span class="line">注意：external_repository 请替换为你真实的页面仓库，格式为 用户名/仓库名。</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="2-配置-gitignore"><a href="#2-配置-gitignore" class="headerlink" title="2. 配置 .gitignore"></a>2. 配置 .gitignore</h3><p>确保你的 Hexo 根目录下的 <code>.gitignore</code> 包含以下内容（避免将无关文件上传到源码仓库）：</p><p><code>gitignore</code><br><code>node_modules/</code><br><code>public/</code><br><code>.deploy_git/</code><br><code>db.json</code><br><code>*.log</code><br><code>.DS_Store</code></p><h1 id="四、本地-Hexo-项目初始化与推送"><a href="#四、本地-Hexo-项目初始化与推送" class="headerlink" title="四、本地 Hexo 项目初始化与推送"></a>四、本地 Hexo 项目初始化与推送</h1><h3 id="1-确认主题中没有-git-子文件夹"><a href="#1-确认主题中没有-git-子文件夹" class="headerlink" title="1. 确认主题中没有 .git 子文件夹"></a>1. 确认主题中没有 <code>.git</code> 子文件夹</h3><p>进入 themes/你的主题名/ 目录，检查是否存在 <code>.git</code> 文件夹（隐藏）。如果有，<strong>必须删除</strong>，否则 GitHub 会把主题当作子模块，导致线上博客空白。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">rm -rf themes/你的主题名/.git</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="2-将本地项目关联到源码仓库"><a href="#2-将本地项目关联到源码仓库" class="headerlink" title="2. 将本地项目关联到源码仓库"></a>2. 将本地项目关联到源码仓库</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">cd 你的Hexo博客根目录</span><br><span class="line">git init</span><br><span class="line">git remote add origin https://github.com/你的GitHub用户名/源码仓库名.git</span><br><span class="line">git branch -M main</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="3-添加并推送所有源文件"><a href="#3-添加并推送所有源文件" class="headerlink" title="3. 添加并推送所有源文件"></a>3. 添加并推送所有源文件</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git add .</span><br><span class="line">git commit -m &quot;首次提交 Hexo 源码&quot;</span><br><span class="line">git push -u origin main</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>如果你的网络环境需要代理，请先配置 Git 代理或改用 SSH 方式（见文末“常见问题”）。</p><h1 id="五、验证自动化部署"><a href="#五、验证自动化部署" class="headerlink" title="五、验证自动化部署"></a>五、验证自动化部署</h1><p>访问你的源码仓库页面，点击 <code>Actions</code> 标签页，应该能看到一个正在运行的工作流（黄色圆点）。</p><p>等待约 2~5 分钟，工作流变为绿色 ✅。</p><p>访问你的页面仓库对应的 GitHub Pages 地址：https://你的用户名.github.io，如果博客正常显示，恭喜你成功了！</p><h1 id="六、日常写作与更新流程"><a href="#六、日常写作与更新流程" class="headerlink" title="六、日常写作与更新流程"></a>六、日常写作与更新流程</h1><p>以后你只需要在手机上（或任何电脑）做以下几步：</p><p>拉取最新源码（如果多设备协作）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git pull</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>新建或修改文章：在 <code>source/_posts/</code> 目录下编辑 <code>.md</code> 文件，可用 Termux 中的 nano、vim 或更友好的 Spck Editor。</p><p>提交并推送：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git add .</span><br><span class="line">git commit -m &quot;更新文章：xxxx&quot;</span><br><span class="line">git push</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>等待 GitHub Actions 自动构建部署，几分钟后线上博客即更新。</p><h1 id="七、常见问题及解决"><a href="#七、常见问题及解决" class="headerlink" title="七、常见问题及解决"></a>七、常见问题及解决</h1><h3 id="Q1-Git-push-时报错-Failed-to-connect-to-127-0-0-1-port-xxx"><a href="#Q1-Git-push-时报错-Failed-to-connect-to-127-0-0-1-port-xxx" class="headerlink" title="Q1: Git push 时报错 Failed to connect to 127.0.0.1 port xxx"></a>Q1: Git push 时报错 <code>Failed to connect to 127.0.0.1 port xxx</code></h3><p>原因：你的 VPN 或代理软件开了，但 Git 代理端口不一致。<br>解决：推荐改用 SSH 方式 推送，彻底绕开 HTTP 代理。</p><p>生成 SSH 密钥：<code>ssh-keygen -t ed25519 -C &quot;你的邮箱&quot;</code></p><p>将公钥（<code>~/.ssh/id_ed25519.pub</code>）添加到 <code>GitHub</code> → <code>Settings</code> → <code>SSH and GPG keys</code></p><p>修改远程仓库地址：<code>git remote set-url origin git@github.com:用户名/源码仓库名.git</code></p><h3 id="Q2-Actions-运行失败，报错-Docker-build-failed"><a href="#Q2-Actions-运行失败，报错-Docker-build-failed" class="headerlink" title="Q2: Actions 运行失败，报错 Docker build failed"></a>Q2: Actions 运行失败，报错 <code>Docker build failed</code></h3><p>原因：某些第三方 action 年久失修。<br>解决：使用推荐的 <code>peaceiris/actions-gh-pages</code>，如本文提供的 YAML 所示。</p><h3 id="Q3-部署后博客空白，或者没有主题样式"><a href="#Q3-部署后博客空白，或者没有主题样式" class="headerlink" title="Q3: 部署后博客空白，或者没有主题样式"></a>Q3: 部署后博客空白，或者没有主题样式</h3><p>原因：主题文件夹内残留 <code>.git</code> 子文件夹。<br>解决：<strong>删除</strong> themes/你的主题/.git，然后重新 <code>git push</code>。</p><h3 id="Q4-推送时提示-src-refspec-main-does-not-match-any"><a href="#Q4-推送时提示-src-refspec-main-does-not-match-any" class="headerlink" title="Q4: 推送时提示 src refspec main does not match any"></a>Q4: 推送时提示 <code>src refspec main does not match any</code></h3><p>原因：本地没有任何 commit。<br>解决：先执行 <code>git add .</code> → <code>git commit -m &quot;xxx&quot;</code>，再 <code>push</code>。</p><h3 id="Q5-页面仓库需要设置分支吗？"><a href="#Q5-页面仓库需要设置分支吗？" class="headerlink" title="Q5: 页面仓库需要设置分支吗？"></a>Q5: 页面仓库需要设置分支吗？</h3><p>在页面仓库的 <code>Settings</code> → <code>Pages</code> → <code>Build and deployment</code> 中，选择 <code>Deploy from a branch</code>，并选择 <code>main</code> 分支（与工作流中的 <code>publish_branch</code> 一致）。</p><h1 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h1><p>通过 Termux + GitHub Actions 的双仓库方案，我们成功实现了手机端零环境依赖的博客更新流程。</p><p>你现在可以：随时随地用手机写文章</p><p>利用 GitHub 的免费云服务器完成构建</p><p>避免手机耗电、性能不足等问题</p><p>希望这篇教程对你有所帮助！如果遇到任何问题，欢迎留言交流。</p><p><strong>Happy blogging! 🚀</strong></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;如果你像我一样，经常在手机上写文章，却苦于无法在手机端运行 &lt;code&gt;hexo generate&lt;/code&gt; 和 &lt;code&gt;hexo deploy&lt;/code&gt;（耗电且容易出错），那么这篇教程正是为你准备的。我们将利用 &lt;strong&gt;Termux&lt;/strong&gt; 在手机上完成 Git 操作，再借助 &lt;strong&gt;GitHub Actions&lt;/strong&gt; 在云端自动构建和部署博客——你只需要专注写作，剩下的交给云端。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最终效果&lt;/strong&gt;：在手机上写完文章 → &lt;code&gt;git push&lt;/code&gt; → 等待 2~5 分钟 → 博客自动更新。全程无需在手机运行 Hexo 或 Node.js。&lt;/p&gt;</summary>
    
    
    
    <category term="教程" scheme="https://www.hzv5.cn/categories/tutorials/"/>
    
    
    <category term="Hexo" scheme="https://www.hzv5.cn/tags/Hexo/"/>
    
    <category term="GitHub Actions" scheme="https://www.hzv5.cn/tags/GitHub-Actions/"/>
    
    <category term="Termux" scheme="https://www.hzv5.cn/tags/Termux/"/>
    
    <category term="自动化" scheme="https://www.hzv5.cn/tags/%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>必备！这款万能小程序能给抖音/豆包视频图集去除水印、还有有趣的测反应、玩成语模拟工具。</title>
    <link href="https://www.hzv5.cn/2026/03/05/xcx-tuijian/"/>
    <id>https://www.hzv5.cn/2026/03/05/xcx-tuijian/</id>
    <published>2026-03-05T13:00:00.000Z</published>
    <updated>2026-07-08T19:23:36.331Z</updated>
    
    <content type="html"><![CDATA[<h2 id="缘起：一个截图背后的“瑞士军刀”"><a href="#缘起：一个截图背后的“瑞士军刀”" class="headerlink" title="缘起：一个截图背后的“瑞士军刀”"></a>缘起：一个截图背后的“瑞士军刀”</h2><p>前两天在群里看到朋友分享了一张小程序截图，下面却赫然列着<strong>抖音视频图集去水印</strong>、<strong>豆包图片去水印</strong>、<strong>真实体感温度</strong>、<strong>反应速度测试</strong>等一长串功能。</p><p>我第一反应是：这该不会是P图吧？一个小程序怎么可能同时塞下这么多实用的工具？</p><p>直到我自己搜进去试用了一圈，才确信——这真是一个被名字耽误的万能工具箱。今天就把这款 <strong>「豆包去水印+趣味游戏」</strong> 小程序（搜索“游戏活动汇总”也能找到）安利给所有人。</p><h2 id="功能一：抖音-快手-图集一键去水印（积分永久有效）"><a href="#功能一：抖音-快手-图集一键去水印（积分永久有效）" class="headerlink" title="功能一：抖音/快手/图集一键去水印（积分永久有效）"></a>功能一：抖音/快手/图集一键去水印（积分永久有效）</h2><p>这是小程序里最硬核的功能。无论你是自媒体从业者，还是单纯想保存喜欢的视频到相册，最烦的就是平台水印。</p><p>进入“去水印”页面，界面异常简洁：</p><ul><li><strong>支持抖音、快手、小红书等主流平台</strong>的视频/图文去水印</li><li><strong>特别优化了抖音图集</strong>（就是那种多张图片合成的视频）也能一键去水印</li><li><strong>解析速度快</strong>：粘贴链接后点击“开始解析”，几秒内就能拿到高清无水印视频</li></ul><p>更让人惊喜的是<strong>积分系统</strong>。截图里显示当前积分高达 <strong>987,005</strong> —— 这可不是摆设。每次解析会消耗少量积分，而积分的获取方式非常良心：</p><ul><li>新用户注册即送大量积分</li><li>每日签到、观看激励视频都可免费领取</li><li><strong>分享给好友双方都能获得积分</strong>（你看到的高额积分，很可能是早期用户福利或者测试余额，正常使用完全够用）</li></ul><blockquote><p>对比市面上一堆按次收费或者强制看广告的解析工具，这个小程序把去水印做成了一项长期福利。</p></blockquote><span id="more"></span><p><img src="https://www.hzv5.cn/img/xcx.jpg" alt="抖音视频去水印 豆包视频去水印"></p><p><strong>操作演示</strong>：</p><ol><li>在抖音/快手找到喜欢的视频 → 点击分享 → 复制链接</li><li>打开小程序 → 粘贴链接 → 点击“开始解析”</li><li>等待2秒 → 保存无水印视频到相册</li></ol><h2 id="功能二：趣味游戏合集（摸鱼神器）"><a href="#功能二：趣味游戏合集（摸鱼神器）" class="headerlink" title="功能二：趣味游戏合集（摸鱼神器）"></a>功能二：趣味游戏合集（摸鱼神器）</h2><p>工作间隙需要放松一下？这个“游戏活动汇总”板块里藏着好几款让人上瘾的小游戏：</p><h3 id="1-数字记忆挑战"><a href="#1-数字记忆挑战" class="headerlink" title="1. 数字记忆挑战"></a>1. 数字记忆挑战</h3><p>考验短期记忆力。屏幕会快速闪现一组数字，你需要按顺序复现。从3位数开始，逐步升级到10位数以上 —— 玩几局下来，明显感觉自己的数字记忆力提升了。</p><h3 id="2-反应速度测试"><a href="#2-反应速度测试" class="headerlink" title="2. 反应速度测试"></a>2. 反应速度测试</h3><p>经典的“变脸点击”测试。屏幕红色时等待，变绿瞬间点击。小程序会精确记录你的反应时（毫秒级）。我测了三次，最快一次是287ms，评论区有位大神晒出了174ms的记录。</p><h3 id="3-龙成成语接龙"><a href="#3-龙成成语接龙" class="headerlink" title="3. 龙成成语接龙"></a>3. 龙成成语接龙</h3><p>成语接龙的变种玩法。给出一个成语，你需要接上最后一个字的新成语。内置词库丰富，接不上时可以提示。很适合碎片时间学成语，也适合和朋友PK。</p><h3 id="4-色盲测试"><a href="#4-色盲测试" class="headerlink" title="4. 色盲测试"></a>4. 色盲测试</h3><p>石原氏色盲测试图（那些彩色圆点组成的数字）。如果你看不出某些图案，可能需要留意一下自己的辨色能力。这个小测试可以作为简单的健康自查，尤其适合长时间面对屏幕的上班族。</p><h2 id="功能三：真实体感温度-amp-日常小工具"><a href="#功能三：真实体感温度-amp-日常小工具" class="headerlink" title="功能三：真实体感温度 &amp; 日常小工具"></a>功能三：真实体感温度 &amp; 日常小工具</h2><p>天气预报里的“温度”和体感温度往往相差很大。这个功能根据你<strong>授权的地理位置</strong>，结合当地湿度、风速，给出一个更贴近人体真实感受的温度值。</p><p>比如夏天32度，湿度80%，体感温度可能显示39度 —— 这时候你就知道涂防晒、多喝水了。</p><p>另外截图里还出现了<strong>暖暖时装秀</strong>（猜测是换装小游戏或穿搭推荐）和<strong>豆包图片去水印</strong>（图片去水印专用入口），进一步拓宽了工具箱的边界。</p><h2 id="到底怎么找到它？"><a href="#到底怎么找到它？" class="headerlink" title="到底怎么找到它？"></a>到底怎么找到它？</h2><p>由于小程序名称有点特殊，我直接给出两个关键词，在微信首页下拉搜索即可：</p><ul><li><strong>关键词一</strong>：<code>逗宝去水印</code> </li><li><strong>关键词二</strong>：<code>时装秀逗宝去水印</code></li><li><strong>其他方式</strong>：直接扫下方的小程序码进入<br><img src="https://www.hzv5.cn/img/xcx_.jpg" alt="抖音逗宝视频去水印小程序"></li></ul><p>或者让朋友从聊天窗口分享给你，点一下就能直接打开。</p><h2 id="适合哪些人？"><a href="#适合哪些人？" class="headerlink" title="适合哪些人？"></a>适合哪些人？</h2><ul><li><strong>自媒体运营/短视频剪辑</strong>：每天需要大量下载素材，去水印功能节省大量时间</li><li><strong>摸鱼党/学生党</strong>：课间或午休玩两局记忆挑战、反应测试，放松大脑</li><li><strong>爱好分享美图/视频的普通人</strong>：看到喜欢的抖音图集，一键存图不发糊</li><li><strong>喜欢探索实用工具的数码爱好者</strong>：这种集成式工具箱，总有你意想不到的功能</li></ul><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>说实话，我一开始对这类“全家桶”小程序是抱有偏见的——总觉得功能多就意味着每个都做不精。但实际体验下来，<strong>去水印的成功率和速度不输专门的小程序，小游戏的趣味性也足够</strong>。</p><p>何况它完全免费（积分够用到天荒地老），没有乱七八糟的订阅套路。如果你也觉得原生保存视频带水印很烦，或者平时想找个简单的小游戏打发时间，不妨去微信搜一下它。</p><p>说不定哪天你用它的体感温度查了一下，发现体感比预报高5度，果断带上了伞——这就是一个好工具存在的意义。</p><blockquote><p>📌 小提醒：去水印功能仅限个人收藏或合理使用，请勿用于商业盗搬，尊重原创作者版权。</p></blockquote><p><strong>你平时都用哪些小程序工具？欢迎在评论区互相安利～</strong></p>]]></content>
    
    
    <summary type="html">一款集成视频去水印、体感温度查询、记忆力挑战、成语接龙等超多功能的宝藏小程序，用完再也舍不得卸载。</summary>
    
    
    
    <category term="小程序推荐" scheme="https://www.hzv5.cn/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E6%8E%A8%E8%8D%90/"/>
    
    
    <category term="微信小程序" scheme="https://www.hzv5.cn/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
    <category term="去水印工具" scheme="https://www.hzv5.cn/tags/%E5%8E%BB%E6%B0%B4%E5%8D%B0%E5%B7%A5%E5%85%B7/"/>
    
    <category term="趣味游戏" scheme="https://www.hzv5.cn/tags/%E8%B6%A3%E5%91%B3%E6%B8%B8%E6%88%8F/"/>
    
    <category term="效率工具" scheme="https://www.hzv5.cn/tags/%E6%95%88%E7%8E%87%E5%B7%A5%E5%85%B7/"/>
    
  </entry>
  
  <entry>
    <title>自制的一些免费API接口第二弹（获取QQ昵称、头像、抖音/豆包视频去水印等...）</title>
    <link href="https://www.hzv5.cn/2026/03/02/my-apis-2/"/>
    <id>https://www.hzv5.cn/2026/03/02/my-apis-2/</id>
    <published>2026-03-02T04:00:00.000Z</published>
    <updated>2026-07-08T19:23:36.329Z</updated>
    
    <content type="html"><![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>为了让 Hexo 这个静态博客变得更有“灵气”，我动手搭建了自己的 API 服务 —— <strong><a href="https://api.hzv5.cn">api.hzv5.cn</a></strong>。它现在已经集成超过 27 种实用的免费接口[reference:1]，从媒体解析到 AI 工具，让我的博客能实时调用各种动态数据。上一次收集整理和自制的api接口获得很多人的喜爱，那么，今天就把这个自制的“工具箱”第二弹分享出来，并手把手教大家如何把它接入到 Hexo 主题中，或许运用到自己的一些项目中。</p><h2 id="API接口：概览与分类"><a href="#API接口：概览与分类" class="headerlink" title="API接口：概览与分类"></a>API接口：概览与分类</h2><p><code>api.hzv5.cn</code> 是一个完全免费、无需注册的接口站，所有接口都可以直接在浏览器或代码中调用。目前提供的接口主要分为以下几类：</p><h3 id="1-智能工具-AI-amp-Utility"><a href="#1-智能工具-AI-amp-Utility" class="headerlink" title="1. 智能工具 (AI &amp; Utility)"></a>1. 智能工具 (AI &amp; Utility)</h3><ul><li><strong>艺名生成器</strong> (<code>https://api.hzv5.cn/artname.php</code>): 根据你的姓名或文本，智能生成 1-5 个中文或英文艺名并附带解释。</li><li><strong>答案之书</strong> (<code>https://api.hzv5.cn/answer.php</code>): 获取随机经典语录，用于解惑或娱乐。</li><li><strong>体感温度计算</strong> (<code>https://api.hzv5.cn/feel.php</code>): 基于温湿度等数据，计算体感温度并提供生活建议。</li><li><strong>随机评论生成</strong> (<code>https://api.hzv5.cn/random_comment.php</code>): 获取预设的随机评论内容，可以当作网站的填充语。</li><li><strong>敏感词检测与过滤</strong> (<code>https://api.hzv5.cn/mg.php</code>): 支持多种检测模式，可对文本进行脱敏、替换或分类统计。</li></ul><span id="more"></span><h3 id="2-主流平台解析"><a href="#2-主流平台解析" class="headerlink" title="2. 主流平台解析"></a>2. 主流平台解析</h3><ul><li><strong>抖音解析</strong> (<code>https://api.hzv5.cn/dyzb.php</code>):<ul><li><strong>视频/图文</strong>: 输入抖音链接，解析视频、图集或实况内容，提取无水印素材与作者信息。</li><li><strong>主页作品</strong>: 获取指定用户主页的所有作品列表。</li><li><strong>直播</strong>: 查询直播间信息与实时流地址。</li></ul></li><li><strong>豆包解析</strong> (<code>https://api.hzv5.cn/doubao.php</code>):<ul><li><strong>视频去水印</strong>: 解析豆包视频的分享链接，提取无水印地址。</li><li><strong>图片去水印</strong>: 解析分享链接，获取对话中的无水印图片。</li></ul></li><li><strong>蓝奏云直链解析</strong> (<code>https://api.hzv5.cn/lanzou.php</code>): 将蓝奏云分享链接转为文件直链。</li></ul><h3 id="3-数据查询服务"><a href="#3-数据查询服务" class="headerlink" title="3. 数据查询服务"></a>3. 数据查询服务</h3><ul><li><strong>IP 归属地查询</strong> (<code>https://api.hzv5.cn/ip_lookup.php</code>): 查询 IPv4 地址的地理位置[reference:13]。</li><li><strong>百科查询</strong> (<code>https://api.hzv5.cn/bk.php</code>): 获取词条的简要摘要（纯文本）。</li><li><strong>历史上的今天</strong> (<code>https://api.hzv5.cn/history.php</code>): 获取指定日期发生的大事记。</li><li><strong>快递查询</strong> (<code>https://api.hzv5.cn/kd_protect.php</code>): 根据单号查询物流信息。</li><li><strong>QQ 信息获取</strong> (<code>https://api.hzv5.cn/qqnote.php</code>): 查询 QQ 号的昵称与头像。</li><li><strong>实时金价</strong> (<code>https://api.hzv5.cn/gold_price.php</code>): 获取国际现货黄金实时价格。</li><li><strong>天气查询</strong> (<code>https://api.hzv5.cn/weather.php</code>): 根据城市获取实时天气。</li></ul><h3 id="4-实用小工具"><a href="#4-实用小工具" class="headerlink" title="4. 实用小工具"></a>4. 实用小工具</h3><ul><li><strong>UA 设备识别</strong> (<code>https://api.hzv5.cn/ua.php</code>): 解析 UA 字符串，返回设备、OS 和浏览器信息。</li><li><strong>成语接龙</strong> (<code>https://api.hzv5.cn/idiom_jielong.php</code>): 自定义模式和长度进行成语接龙[reference:21]。</li><li><strong>文本加密</strong> (<code>https://api.hzv5.cn/textencryption.php</code>): 使用专属密钥加密文本。</li><li><strong>随机数据</strong>：颜色(<code>https://api.hzv5.cn/ys.php</code>)[reference:23]、密码(<code>/randomcode.php</code>)。</li><li><strong>二维码</strong>：生成(<code>https://api.hzv5.cn/qr.php</code>)与解析(<code>/qr.php</code>)。</li><li><strong>折扣凑单计算</strong> (<code>https://api.hzv5.cn/zhekou.php</code>): 根据满减规则计算最优凑单方案。</li><li><strong>Zepp 步数刷步</strong> (<code>https://api.hzv5.cn/zepp.php</code>): 通过 Web 端 API 提交步数。</li></ul><h2 id="在-Hexo-主题中集成"><a href="#在-Hexo-主题中集成" class="headerlink" title="在 Hexo 主题中集成"></a>在 Hexo 主题中集成</h2><p>嵌入方法很简单，只要找到主题里合适的 <code>.ejs</code> 或 <code>.pug</code> 模板文件，添加 <code>fetch</code> 代码即可。</p><h3 id="实用场景示例"><a href="#实用场景示例" class="headerlink" title="实用场景示例"></a>实用场景示例</h3><h4 id="1-在侧边栏显示“答案之书”"><a href="#1-在侧边栏显示“答案之书”" class="headerlink" title="1. 在侧边栏显示“答案之书”"></a>1. 在侧边栏显示“答案之书”</h4><p>在 <code>layout/_partial/sidebar.ejs</code> 中添加：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;side-sentence&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span> <span class="attr">id</span>=<span class="string">&quot;api-text&quot;</span>&gt;</span>加载中…<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">fetch</span>(<span class="string">&#x27;https://api.hzv5.cn/answer.php?format=json&#x27;</span>)</span></span><br><span class="line"><span class="language-javascript">        .<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =&gt;</span> res.<span class="title function_">json</span>())</span></span><br><span class="line"><span class="language-javascript">        .<span class="title function_">then</span>(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;api-text&#x27;</span>).<span class="property">innerText</span> = data.<span class="property">data</span>;</span></span><br><span class="line"><span class="language-javascript">        &#125;)</span></span><br><span class="line"><span class="language-javascript">        .<span class="title function_">catch</span>(<span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;api-text&#x27;</span>).<span class="property">innerText</span> = <span class="string">&#x27;顺其自然&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript">        &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="2-在页脚显示一句随机的搞笑评论"><a href="#2-在页脚显示一句随机的搞笑评论" class="headerlink" title="2. 在页脚显示一句随机的搞笑评论"></a>2. 在页脚显示一句随机的搞笑评论</h4><p>在 layout/_partial/footer.ejs 中添加：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;random-comment&quot;</span> <span class="attr">style</span>=<span class="string">&quot;margin-top: 10px; font-size: 12px; text-align: center;&quot;</span>&gt;</span>✨ 加载中...<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">fetch</span>(<span class="string">&#x27;https://api.hzv5.cn/random_comment.php?format=json&amp;count=1&#x27;</span>)</span></span><br><span class="line"><span class="language-javascript">        .<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =&gt;</span> res.<span class="title function_">json</span>())</span></span><br><span class="line"><span class="language-javascript">        .<span class="title function_">then</span>(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">if</span>(data.<span class="property">code</span> === <span class="number">0</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;random-comment&#x27;</span>).<span class="property">innerHTML</span> = <span class="string">`💬 <span class="subst">$&#123;data.data&#125;</span>`</span>;</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript">        &#125;)</span></span><br><span class="line"><span class="language-javascript">        .<span class="title function_">catch</span>(<span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;random-comment&#x27;</span>).<span class="property">innerHTML</span> = <span class="string">&#x27;💬 代码如诗，博客如画&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript">        &#125;);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="3-创建“今日运势”等创意模块"><a href="#3-创建“今日运势”等创意模块" class="headerlink" title="3. 创建“今日运势”等创意模块"></a>3. 创建“今日运势”等创意模块</h4><p>用户访问时通过 answer.php 或 artname.php 获取随机内容，增加互动趣味。</p><h3 id="使用须知"><a href="#使用须知" class="headerlink" title="使用须知"></a>使用须知</h3><p>免费服务并不意味着可以无节制使用，所有调用都是公开透明的，请合理调用、请勿滥用。建议在请求时设置超时处理，并做好请求失败的容错，以免影响页面整体加载。</p><h3 id="功能扩展"><a href="#功能扩展" class="headerlink" title="功能扩展"></a>功能扩展</h3><p>除了增强 Hexo 博客，这些接口还能用于：</p><p>· 微信机器人：快速接入视频解析等功能。<br>· 自动化脚本：定期获取金价或天气数据。<br>· 移动应用：将 api.hzv5.cn 作为小工具的后端。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>未来我会根据大家的需求，继续增加更多有趣、实用的接口。</p><p>如果在使用中遇到任何问题，或有新的接口需求，欢迎随时交流。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;引言&quot;&gt;&lt;a href=&quot;#引言&quot; class=&quot;headerlink&quot; title=&quot;引言&quot;&gt;&lt;/a&gt;引言&lt;/h2&gt;&lt;p&gt;为了让 Hexo 这个静态博客变得更有“灵气”，我动手搭建了自己的 API 服务 —— &lt;strong&gt;&lt;a href=&quot;https://api.hzv5.cn&quot;&gt;api.hzv5.cn&lt;/a&gt;&lt;/strong&gt;。它现在已经集成超过 27 种实用的免费接口[reference:1]，从媒体解析到 AI 工具，让我的博客能实时调用各种动态数据。上一次收集整理和自制的api接口获得很多人的喜爱，那么，今天就把这个自制的“工具箱”第二弹分享出来，并手把手教大家如何把它接入到 Hexo 主题中，或许运用到自己的一些项目中。&lt;/p&gt;
&lt;h2 id=&quot;API接口：概览与分类&quot;&gt;&lt;a href=&quot;#API接口：概览与分类&quot; class=&quot;headerlink&quot; title=&quot;API接口：概览与分类&quot;&gt;&lt;/a&gt;API接口：概览与分类&lt;/h2&gt;&lt;p&gt;&lt;code&gt;api.hzv5.cn&lt;/code&gt; 是一个完全免费、无需注册的接口站，所有接口都可以直接在浏览器或代码中调用。目前提供的接口主要分为以下几类：&lt;/p&gt;
&lt;h3 id=&quot;1-智能工具-AI-amp-Utility&quot;&gt;&lt;a href=&quot;#1-智能工具-AI-amp-Utility&quot; class=&quot;headerlink&quot; title=&quot;1. 智能工具 (AI &amp;amp; Utility)&quot;&gt;&lt;/a&gt;1. 智能工具 (AI &amp;amp; Utility)&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;艺名生成器&lt;/strong&gt; (&lt;code&gt;https://api.hzv5.cn/artname.php&lt;/code&gt;): 根据你的姓名或文本，智能生成 1-5 个中文或英文艺名并附带解释。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;答案之书&lt;/strong&gt; (&lt;code&gt;https://api.hzv5.cn/answer.php&lt;/code&gt;): 获取随机经典语录，用于解惑或娱乐。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;体感温度计算&lt;/strong&gt; (&lt;code&gt;https://api.hzv5.cn/feel.php&lt;/code&gt;): 基于温湿度等数据，计算体感温度并提供生活建议。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;随机评论生成&lt;/strong&gt; (&lt;code&gt;https://api.hzv5.cn/random_comment.php&lt;/code&gt;): 获取预设的随机评论内容，可以当作网站的填充语。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;敏感词检测与过滤&lt;/strong&gt; (&lt;code&gt;https://api.hzv5.cn/mg.php&lt;/code&gt;): 支持多种检测模式，可对文本进行脱敏、替换或分类统计。&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="去水印" scheme="https://www.hzv5.cn/categories/%E5%8E%BB%E6%B0%B4%E5%8D%B0/"/>
    
    <category term="api接口" scheme="https://www.hzv5.cn/categories/%E5%8E%BB%E6%B0%B4%E5%8D%B0/api%E6%8E%A5%E5%8F%A3/"/>
    
    <category term="收集控" scheme="https://www.hzv5.cn/categories/%E5%8E%BB%E6%B0%B4%E5%8D%B0/api%E6%8E%A5%E5%8F%A3/collections/"/>
    
    
    <category term="Hexo" scheme="https://www.hzv5.cn/tags/Hexo/"/>
    
    <category term="去水印" scheme="https://www.hzv5.cn/tags/%E5%8E%BB%E6%B0%B4%E5%8D%B0/"/>
    
    <category term="免费API" scheme="https://www.hzv5.cn/tags/%E5%85%8D%E8%B4%B9API/"/>
    
    <category term="接口收集" scheme="https://www.hzv5.cn/tags/%E6%8E%A5%E5%8F%A3%E6%94%B6%E9%9B%86/"/>
    
    <category term="教程" scheme="https://www.hzv5.cn/tags/%E6%95%99%E7%A8%8B/"/>
    
    <category term="自建服务" scheme="https://www.hzv5.cn/tags/%E8%87%AA%E5%BB%BA%E6%9C%8D%E5%8A%A1/"/>
    
  </entry>
  
  <entry>
    <title>告别原生导航栏：微信小程序自定义导航栏完美适配方案</title>
    <link href="https://www.hzv5.cn/2025/11/25/xcx-tabbar/"/>
    <id>https://www.hzv5.cn/2025/11/25/xcx-tabbar/</id>
    <published>2025-11-25T07:30:00.000Z</published>
    <updated>2026-07-08T19:23:36.331Z</updated>
    
    <content type="html"><![CDATA[<h1 id="为什么需要自定义导航栏？"><a href="#为什么需要自定义导航栏？" class="headerlink" title="为什么需要自定义导航栏？"></a>为什么需要自定义导航栏？</h1><p>原生小程序导航栏虽然开箱即用，但在实际开发中常常遇到以下痛点：</p><ul><li><strong>设计风格受限</strong>：无法自由定制背景、字体颜色、高度，难以融入品牌设计</li><li><strong>扩展能力弱</strong>：无法在导航栏区域增加搜索框、自定义按钮、动态效果等</li><li><strong>无法实现沉浸式</strong>：标题栏与状态栏分离感强，缺少整体感</li><li><strong>屏幕适配难</strong>：不同机型（刘海屏、挖孔屏、动态岛）的高度表现不一致</li></ul><p>因此，<strong>自定义导航栏</strong>成为许多商业项目和小程序框架的标配。本文将手把手教你实现一套适配所有主流机型、易扩展、性能优良的自定义导航栏组件。</p><h2 id="基础配置：开启自定义模式"><a href="#基础配置：开启自定义模式" class="headerlink" title="基础配置：开启自定义模式"></a>基础配置：开启自定义模式</h2><p>在 <code>app.json</code> 中将 <code>navigationStyle</code> 设置为 <code>custom</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">  &quot;window&quot;: &#123;</span><br><span class="line">    &quot;navigationStyle&quot;: &quot;custom&quot;,</span><br><span class="line">    &quot;navigationBarTextStyle&quot;: &quot;black&quot;  // 仅对页面内文本颜色有影响，导航栏已隐藏</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>💡 小贴士：也可以单独为某个页面配置，在页面目录的 .json 文件中设置相同字段即可。</p><p>配置完成后，小程序所有页面将不再渲染原生导航栏，状态栏区域完全暴露，需要我们自己实现导航UI和内容区域的避让。</p><span id="more"></span><h2 id="核心技术：动态获取设备信息"><a href="#核心技术：动态获取设备信息" class="headerlink" title="核心技术：动态获取设备信息"></a>核心技术：动态获取设备信息</h2><p>实现完美适配的关键在于准确获取两个高度：</p><ol><li>状态栏高度 (statusBarHeight)</li><li>胶囊按钮位置信息 (menuButtonBoundingClientRect)</li></ol><p>获取关键数据</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在 app.js 或页面/组件的 onLoad/attached 中执行</span></span><br><span class="line"><span class="keyword">const</span> systemInfo = wx.<span class="title function_">getSystemInfoSync</span>();</span><br><span class="line"><span class="keyword">const</span> menuButtonInfo = wx.<span class="title function_">getMenuButtonBoundingClientRect</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> &#123; statusBarHeight &#125; = systemInfo;</span><br><span class="line"><span class="keyword">const</span> &#123; top, height &#125; = menuButtonInfo;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 计算导航栏内容区域高度（通常等于 胶囊距顶间距*2 + 胶囊高度 ）</span></span><br><span class="line"><span class="comment">// 间距 = 胶囊顶部 - 状态栏高度</span></span><br><span class="line"><span class="keyword">const</span> gap = top - statusBarHeight;</span><br><span class="line"><span class="keyword">const</span> navContentHeight = gap * <span class="number">2</span> + height;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 导航栏总高度 = 状态栏高度 + 内容高度</span></span><br><span class="line"><span class="keyword">const</span> navTotalHeight = statusBarHeight + navContentHeight;</span><br></pre></td></tr></table></figure><p>⚠️ 注意：getMenuButtonBoundingClientRect 必须在页面渲染完成后调用才能获取正确值，建议放在 onReady 或组件的 ready 生命周期中。</p><h2 id="封装导航栏组件-NavBar"><a href="#封装导航栏组件-NavBar" class="headerlink" title="封装导航栏组件 (NavBar)"></a>封装导航栏组件 (NavBar)</h2><p>为了提高复用性，我们将导航栏封装成自定义组件。</p><h4 id="1-组件代码结构"><a href="#1-组件代码结构" class="headerlink" title="1. 组件代码结构"></a>1. 组件代码结构</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">components/nav-bar/</span><br><span class="line">├── index.js</span><br><span class="line">├── index.json</span><br><span class="line">├── index.wxml</span><br><span class="line">└── index.wxss</span><br></pre></td></tr></table></figure><h4 id="2-index-js-核心逻辑"><a href="#2-index-js-核心逻辑" class="headerlink" title="2. index.js 核心逻辑"></a>2. index.js 核心逻辑</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Component</span>(&#123;</span><br><span class="line">  <span class="attr">properties</span>: &#123;</span><br><span class="line">    <span class="attr">title</span>: &#123;            <span class="comment">// 页面标题</span></span><br><span class="line">      <span class="attr">type</span>: <span class="title class_">String</span>,</span><br><span class="line">      <span class="attr">value</span>: <span class="string">&#x27;&#x27;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">showBack</span>: &#123;         <span class="comment">// 是否显示返回按钮（根据页面栈自动判断时该属性可省略）</span></span><br><span class="line">      <span class="attr">type</span>: <span class="title class_">Boolean</span>,</span><br><span class="line">      <span class="attr">value</span>: <span class="literal">true</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">backgroundColor</span>: &#123;  <span class="comment">// 导航栏背景色</span></span><br><span class="line">      <span class="attr">type</span>: <span class="title class_">String</span>,</span><br><span class="line">      <span class="attr">value</span>: <span class="string">&#x27;#ffffff&#x27;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">titleColor</span>: &#123;       <span class="comment">// 标题颜色</span></span><br><span class="line">      <span class="attr">type</span>: <span class="title class_">String</span>,</span><br><span class="line">      <span class="attr">value</span>: <span class="string">&#x27;#000000&#x27;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">backIconColor</span>: &#123;    <span class="comment">// 返回图标颜色</span></span><br><span class="line">      <span class="attr">type</span>: <span class="title class_">String</span>,</span><br><span class="line">      <span class="attr">value</span>: <span class="string">&#x27;#000000&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="attr">data</span>: &#123;</span><br><span class="line">    <span class="attr">statusBarHeight</span>: <span class="number">20</span>,     <span class="comment">// 状态栏高度（默认值，防止未获取前布局抖动）</span></span><br><span class="line">    <span class="attr">navContentHeight</span>: <span class="number">44</span>,    <span class="comment">// 导航内容高度（默认值，同原生导航高度）</span></span><br><span class="line">    <span class="attr">navTotalHeight</span>: <span class="number">64</span>,      <span class="comment">// 总高度</span></span><br><span class="line">    <span class="attr">menuButtonWidth</span>: <span class="number">87</span>      <span class="comment">// 胶囊宽度，用于右边留白避让</span></span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="attr">lifetimes</span>: &#123;</span><br><span class="line">    <span class="title function_">attached</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">initNavInfo</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="attr">pageLifetimes</span>: &#123;</span><br><span class="line">    <span class="comment">// 如果组件放在页面中且页面可能被隐藏再显示，可监听show重新获取（某些机型旋转屏幕后可能需要）</span></span><br><span class="line">    <span class="title function_">show</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">initNavInfo</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="attr">methods</span>: &#123;</span><br><span class="line">    <span class="title function_">initNavInfo</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> systemInfo = wx.<span class="title function_">getSystemInfoSync</span>();</span><br><span class="line">        <span class="keyword">const</span> menuBtn = wx.<span class="title function_">getMenuButtonBoundingClientRect</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">const</span> &#123; statusBarHeight &#125; = systemInfo;</span><br><span class="line">        <span class="keyword">const</span> &#123; top, height, width &#125; = menuBtn;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 计算导航栏内容区域高度（保证胶囊垂直居中）</span></span><br><span class="line">        <span class="keyword">const</span> gap = top - statusBarHeight;</span><br><span class="line">        <span class="keyword">const</span> navContentHeight = gap * <span class="number">2</span> + height;</span><br><span class="line">        <span class="keyword">const</span> navTotalHeight = statusBarHeight + navContentHeight;</span><br><span class="line">        </span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">setData</span>(&#123;</span><br><span class="line">          statusBarHeight,</span><br><span class="line">          navContentHeight,</span><br><span class="line">          navTotalHeight,</span><br><span class="line">          <span class="attr">menuButtonWidth</span>: width</span><br><span class="line">        &#125;);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 通过事件将总高度传递给父页面，方便页面内容区域设置padding-top（非必须，组件内部已有占位view）</span></span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">triggerEvent</span>(<span class="string">&#x27;heightChange&#x27;</span>, &#123; navTotalHeight &#125;);</span><br><span class="line">      &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;获取导航信息失败&#x27;</span>, e);</span><br><span class="line">        <span class="comment">// 降级方案：使用默认值</span></span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">setData</span>(&#123;</span><br><span class="line">          <span class="attr">statusBarHeight</span>: <span class="number">20</span>,</span><br><span class="line">          <span class="attr">navContentHeight</span>: <span class="number">44</span>,</span><br><span class="line">          <span class="attr">navTotalHeight</span>: <span class="number">64</span></span><br><span class="line">        &#125;);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回按钮点击事件</span></span><br><span class="line">    <span class="title function_">handleBack</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> pages = <span class="title function_">getCurrentPages</span>();</span><br><span class="line">      <span class="keyword">if</span> (pages.<span class="property">length</span> === <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="comment">// 根页面时返回首页或提示</span></span><br><span class="line">        wx.<span class="title function_">switchTab</span>(&#123;</span><br><span class="line">          <span class="attr">url</span>: <span class="string">&#x27;/pages/index/index&#x27;</span>  <span class="comment">// 根据实际首页路径修改</span></span><br><span class="line">        &#125;);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        wx.<span class="title function_">navigateBack</span>(&#123;</span><br><span class="line">          <span class="attr">delta</span>: <span class="number">1</span>,</span><br><span class="line">          <span class="attr">fail</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">            wx.<span class="title function_">switchTab</span>(&#123; <span class="attr">url</span>: <span class="string">&#x27;/pages/index/index&#x27;</span> &#125;);</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 预留右侧插槽点击事件</span></span><br><span class="line">    <span class="title function_">handleRightTap</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">triggerEvent</span>(<span class="string">&#x27;rightTap&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h4 id="3-index-wxml-结构"><a href="#3-index-wxml-结构" class="headerlink" title="3. index.wxml 结构"></a>3. index.wxml 结构</h4><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 占位view（防止内容被固定导航栏遮挡） --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">view</span> <span class="attr">style</span>=<span class="string">&quot;height: &#123;&#123;navTotalHeight&#125;&#125;px;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">view</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 固定定位的导航栏 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;nav-bar-fixed&quot;</span> <span class="attr">style</span>=<span class="string">&quot;height: &#123;&#123;navTotalHeight&#125;&#125;px; background-color: &#123;&#123;backgroundColor&#125;&#125;; padding-top: &#123;&#123;statusBarHeight&#125;&#125;px; box-sizing: border-box;&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;nav-content&quot;</span> <span class="attr">style</span>=<span class="string">&quot;height: &#123;&#123;navContentHeight&#125;&#125;px;&quot;</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 左侧返回区域 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;nav-left&quot;</span> <span class="attr">bindtap</span>=<span class="string">&quot;handleBack&quot;</span> <span class="attr">wx:if</span>=<span class="string">&quot;&#123;&#123;showBack&#125;&#125;&quot;</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">icon</span> <span class="attr">type</span>=<span class="string">&quot;default&quot;</span> <span class="attr">class</span>=<span class="string">&quot;back-icon&quot;</span> <span class="attr">style</span>=<span class="string">&quot;color: &#123;&#123;backIconColor&#125;&#125;;&quot;</span> <span class="attr">size</span>=<span class="string">&quot;22&quot;</span> /&gt;</span></span><br><span class="line">      <span class="comment">&lt;!-- 或者使用自定义图片 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">view</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;nav-left&quot;</span> <span class="attr">wx:else</span>&gt;</span><span class="tag">&lt;/<span class="name">view</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 标题区域 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;nav-title&quot;</span> <span class="attr">style</span>=<span class="string">&quot;color: &#123;&#123;titleColor&#125;&#125;;&quot;</span>&gt;</span>&#123;&#123;title&#125;&#125;<span class="tag">&lt;/<span class="name">view</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 右侧自定义插槽（可用于放置更多按钮、分享等） --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;nav-right&quot;</span> <span class="attr">bindtap</span>=<span class="string">&quot;handleRightTap&quot;</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">slot</span> <span class="attr">name</span>=<span class="string">&quot;right&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">slot</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">view</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">view</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">view</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="4-index-wxss-样式"><a href="#4-index-wxss-样式" class="headerlink" title="4. index.wxss 样式"></a>4. index.wxss 样式</h4><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.nav-bar-fixed</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: fixed;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">right</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">z-index</span>: <span class="number">999</span>;</span><br><span class="line">  <span class="attribute">background-color</span>: <span class="number">#ffffff</span>;</span><br><span class="line">  <span class="comment">/* 可选毛玻璃效果 */</span></span><br><span class="line">  <span class="comment">/* backdrop-filter: blur(10px); */</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.nav-content</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: flex;</span><br><span class="line">  <span class="attribute">align-items</span>: center;</span><br><span class="line">  <span class="attribute">justify-content</span>: space-between;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">  <span class="attribute">box-sizing</span>: border-box;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">0</span> <span class="number">32</span>rpx;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.nav-left</span>, <span class="selector-class">.nav-right</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">80</span>rpx;</span><br><span class="line">  <span class="attribute">display</span>: flex;</span><br><span class="line">  <span class="attribute">align-items</span>: center;</span><br><span class="line">  <span class="attribute">justify-content</span>: center;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.back-icon</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">44</span>rpx;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">44</span>rpx;</span><br><span class="line">  <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">&quot;data:image/svg+xml,%3Csvg xmlns=&#x27;http://www.w3.org/2000/svg&#x27; viewBox=&#x27;0 0 24 24&#x27; fill=&#x27;none&#x27; stroke=&#x27;currentColor&#x27; stroke-width=&#x27;2&#x27; stroke-linecap=&#x27;round&#x27; stroke-linejoin=&#x27;round&#x27;%3E%3Cpath d=&#x27;M15 18l-6-6 6-6&#x27;/%3E%3C/svg%3E&quot;</span>);</span><br><span class="line">  <span class="attribute">background-size</span>: contain;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.nav-title</span> &#123;</span><br><span class="line">  <span class="attribute">flex</span>: <span class="number">1</span>;</span><br><span class="line">  <span class="attribute">text-align</span>: center;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">34</span>rpx;</span><br><span class="line">  <span class="attribute">font-weight</span>: <span class="number">500</span>;</span><br><span class="line">  <span class="attribute">overflow</span>: hidden;</span><br><span class="line">  <span class="attribute">text-overflow</span>: ellipsis;</span><br><span class="line">  <span class="attribute">white-space</span>: nowrap;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">0</span> <span class="number">20</span>rpx;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意：为了方便演示，这里用SVG DataURI 作为返回箭头。实际项目中推荐使用本地图片或iconfont字体。</p><h4 id="5-注册组件"><a href="#5-注册组件" class="headerlink" title="5. 注册组件"></a>5. 注册组件</h4><p>在 app.json 或页面配置中注册全局组件：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;usingComponents&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;nav-bar&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/components/nav-bar/index&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="页面中使用导航栏"><a href="#页面中使用导航栏" class="headerlink" title="页面中使用导航栏"></a>页面中使用导航栏</h2><h4 id="1-页面的-WXML"><a href="#1-页面的-WXML" class="headerlink" title="1. 页面的 WXML"></a>1. 页面的 WXML</h4><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 引入导航栏组件 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">nav-bar</span> <span class="attr">title</span>=<span class="string">&quot;个人中心&quot;</span> <span class="attr">showBack</span>=<span class="string">&quot;&#123;&#123;true&#125;&#125;&quot;</span> <span class="attr">bind:heightChange</span>=<span class="string">&quot;onNavHeightChange&quot;</span> /&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 页面主要内容，组件内部已做占位，正常布局即可 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;page-content&quot;</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- 你的页面内容 --&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;card&quot;</span>&gt;</span>内容卡片<span class="tag">&lt;/<span class="name">view</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">view</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="2-页面的-JS-（可选）"><a href="#2-页面的-JS-（可选）" class="headerlink" title="2. 页面的 JS （可选）"></a>2. 页面的 JS （可选）</h4><p>如果需要对页面内容额外调整，可以监听高度变化事件：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Page</span>(&#123;</span><br><span class="line">  <span class="attr">data</span>: &#123;</span><br><span class="line">    <span class="attr">navBarHeight</span>: <span class="number">0</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">onNavHeightChange</span>(<span class="params">e</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setData</span>(&#123; <span class="attr">navBarHeight</span>: e.<span class="property">detail</span>.<span class="property">navTotalHeight</span> &#125;);</span><br><span class="line">    <span class="comment">// 如果页面顶部有特殊的吸顶元素，可能需要用到这个高度</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h1 id="避坑指南与性能优化"><a href="#避坑指南与性能优化" class="headerlink" title="避坑指南与性能优化"></a>避坑指南与性能优化</h1><h2 id="✅-必须避开的坑"><a href="#✅-必须避开的坑" class="headerlink" title="✅ 必须避开的坑"></a>✅ 必须避开的坑</h2><p>问题现象 原因分析 解决方案<br>部分安卓机型胶囊信息获取为0 getMenuButtonBoundingClientRect 在 onLoad 中调用过早 延迟调用或放在 onReady / 组件的 ready 生命周期<br>小程序闪动/布局错位 未设置默认高度，初始化时无数据导致视图变化 先在data中预设常见机型的默认高度（如 64/44）<br>页面内容被导航栏遮挡 忘记留出占位区域 使用组件内占位view，或页面容器设置padding-top为导航总高<br>右侧胶囊会遮挡自定义返回按钮 没有给导航栏右侧预留足够的空白区域 按照胶囊宽度，在nav-right区域设置margin-right动态值（一般胶囊宽87px，可用系统信息计算后给padding） 改进方案：组件样式保证右上角无交互元素即可，默认胶囊在右侧，自定义导航栏覆盖胶囊区域不会影响点击。但为了不遮挡标题文字，标题区应留有padding-right<br>横屏/折叠屏适配异常 屏幕旋转后导航高度未重新计算 监听 wx.onWindowResize 重新调用获取方法（需要页面级别处理，组件内部无法直接监听，建议页面监听后更新组件数据）</p><h2 id="🚀-性能优化建议"><a href="#🚀-性能优化建议" class="headerlink" title="🚀 性能优化建议"></a>🚀 性能优化建议</h2><p>· 避免重复获取：在 app.js 启动时获取一次设备信息并挂载到全局，组件优先使用全局缓存。<br>· 减少setData频率：初始化一次性设置完整数据，不要在多个生命周期重复设置。<br>· 动画独立：如果需要滚动渐变效果（导航栏透明变不透明），最好单独建立一个透明导航层，避免频繁setData。</p><p>扩展进阶：动态背景与滚动渐变</p><p>有时候我们需要导航栏随着页面滚动而改变背景透明度（例如从透明逐渐变为白色）。这时需要：</p><ul><li><ol><li>页面监听 onPageScroll 事件</li></ol></li><li><ol><li>将滚动偏移量传递给导航栏组件</li></ol></li><li><ol><li>组件内部动态计算背景色的rgba值</li></ol></li></ul><p>由于篇幅有限，这个高级用法留作课后作业，感兴趣的读者可以尝试实现。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>自定义导航栏让小程序的设计和交互更加自由，而通过 wx.getMenuButtonBoundingClientRect 和动态高度计算，我们可以做到一套代码完美适配所有机型。</p><p>本文提供的组件已在实际项目中经过多轮测试，兼容以下场景：</p><p>· iOS（刘海屏、动态岛、非全面屏）<br>· Android（挖孔屏、水滴屏、全面屏）<br>· 折叠屏（展开/折叠状态需单独适配resize事件）<br>· iPad / 平板（状态栏高度变化）</p><p>只需复制代码稍作样式调整，即可集成到你的小程序中。</p>]]></content>
    
    
    <summary type="html">一套让微信小程序导航栏适配所有机型的完整方案，包含动态计算、组件封装和避坑指南</summary>
    
    
    
    <category term="技术分享" scheme="https://www.hzv5.cn/categories/tech/"/>
    
    <category term="小程序开发" scheme="https://www.hzv5.cn/categories/tech/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="小程序开发" scheme="https://www.hzv5.cn/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="微信小程序" scheme="https://www.hzv5.cn/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
    <category term="自定义导航栏" scheme="https://www.hzv5.cn/tags/%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AF%BC%E8%88%AA%E6%A0%8F/"/>
    
    <category term="移动端适配" scheme="https://www.hzv5.cn/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E9%80%82%E9%85%8D/"/>
    
  </entry>
  
  <entry>
    <title>在安卓手机上运行OpenClaw？当然可以，用Termux折腾OpenClaw安装QQ机器人的踩坑记录...</title>
    <link href="https://www.hzv5.cn/2025/06/27/OpenClaw-for-Android/"/>
    <id>https://www.hzv5.cn/2025/06/27/OpenClaw-for-Android/</id>
    <published>2025-06-27T16:00:00.000Z</published>
    <updated>2026-07-08T19:23:36.329Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近想在手机上跑一个 QQ 机器人，选了个看起来挺灵活的框架 OpenClaw。本以为按文档几分钟就能搞定，结果断断续续折腾了一晚上。这里记一下过程，给同样在 Termux 上折腾的人省点时间。</p><p>为什么选 OpenClaw</p><p>主要看中它支持多平台（QQ、微信、Telegram 等），还能接各种大模型。我想把智谱的免费模型 glm-4.7-flash 接上 QQ，手机上跑着省电。</p><h3 id="第一个坑：插件找不到"><a href="#第一个坑：插件找不到" class="headerlink" title="第一个坑：插件找不到"></a>第一个坑：插件找不到</h3><p>装完 OpenClaw 后，配置文件里加了 openclaw-qqbot，结果启动报错：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">plugins.allow: plugin not found: openclaw-qqbot</span><br></pre></td></tr></table></figure><p>官方的安装命令跑了一遍，又说找不到 openclaw/dist/plugins-sdk/index_js/core。搜了一下，大概是版本不匹配——OpenClaw 核心和 QQ 插件各说各话。</p><h3 id="第二个坑：Node-js-编译失败"><a href="#第二个坑：Node-js-编译失败" class="headerlink" title="第二个坑：Node.js 编译失败"></a>第二个坑：Node.js 编译失败</h3><p>升级 Node.js 到 v24 后，npm install -g openclaw@latest 直接挂掉，卡在 tree-sitter-bash 的编译上：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gyp: Undefined variable android_ndk_path in binding.gyp</span><br></pre></td></tr></table></figure><p>这是 Termux 环境的老毛病了——Android 的 C 库（Bionic）和很多 npm 原生模块不对付。折腾半天无解，决定换路子。</p><h3 id="终极方案：Ubuntu-容器"><a href="#终极方案：Ubuntu-容器" class="headerlink" title="终极方案：Ubuntu 容器"></a>终极方案：Ubuntu 容器</h3><p>Termux 里有个 proot-distro 可以跑完整 Linux 发行版。既然原生环境不行，就在容器里装。</p><span id="more"></span><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pkg install proot-distro</span><br><span class="line">proot-distro install ubuntu</span><br><span class="line">proot-distro login ubuntu</span><br></pre></td></tr></table></figure><p>进到 Ubuntu 容器里，先装 Node.js 22（LTS 比较稳）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">apt update &amp;&amp; apt install -y curl</span><br><span class="line">curl -fsSL https://deb.nodesource.com/setup_22.x | bash -</span><br><span class="line">apt install -y nodejs</span><br></pre></td></tr></table></figure><p>然后装 OpenClaw：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g openclaw@latest</span><br></pre></td></tr></table></figure><p>这次编译顺利通过。终于有了 openclaw 命令。</p><h2 id="配置向导：模型和-QQ-频道"><a href="#配置向导：模型和-QQ-频道" class="headerlink" title="配置向导：模型和 QQ 频道"></a>配置向导：模型和 QQ 频道</h2><p>运行 openclaw，按照提示选择：</p><p>· QuickStart（推荐）<br>· 认证提供方：Skip for now<br>· 默认模式：手动输入，填 zhipu/glm-4.7-flash<br>· 频道：QQ Bot (Official API)<br>· 搜索和技能：都跳过</p><p>注意这里模型名中间是点号 glm-4.7-flash，不是连字符。后来因为这个连字符又折腾了半天。</p><h2 id="新问题：Gateway-不工作"><a href="#新问题：Gateway-不工作" class="headerlink" title="新问题：Gateway 不工作"></a>新问题：Gateway 不工作</h2><p>配完启动，出现一个叫 Crestodian 的界面，提示 Gateway: not reachable。查了一下，原来直接运行 openclaw 启动的是一个轻量级客户端，必须先把 Gateway 服务跑起来。</p><p>手动开两个终端（或者在 tmux 里分屏）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 终端1</span></span><br><span class="line">openclaw gateway --port 18791</span><br><span class="line"></span><br><span class="line"><span class="comment"># 终端2</span></span><br><span class="line">openclaw</span><br></pre></td></tr></table></figure><p>这次代理连上了 Gateway，QQ 也显示 WebSocket 已连接。我在手机 QQ 上给机器人发消息，日志里能收到事件，但机器人不回复。</p><h2 id="核心问题：智谱-API-Key-没配"><a href="#核心问题：智谱-API-Key-没配" class="headerlink" title="核心问题：智谱 API Key 没配"></a>核心问题：智谱 API Key 没配</h2><p>模型虽然选对了，但没给 API Key，OpenClaw 不知道去哪调用。</p><p>修改配置文件 ~/.openclaw/openclaw.json，在 models 里加上 apiKey：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;models&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;zhipu/glm-4.7-flash&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;apiKey&quot;</span><span class="punctuation">:</span> <span class="string">&quot;你的智谱API密钥&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>智谱的 API Key 去 open.bigmodel.cn 注册就有免费额度。</p><p>改完重启 Gateway 和代理，再发消息就能正常回复了。</p><h2 id="一键启动的小脚本"><a href="#一键启动的小脚本" class="headerlink" title="一键启动的小脚本"></a>一键启动的小脚本</h2><p>每次手动开两个 tmux 窗格有点烦，写了个小脚本 start_openclaw.sh：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line">proot-distro login ubuntu -- tmux kill-session -t openclaw 2&gt;/dev/null</span><br><span class="line">proot-distro login ubuntu -- tmux new-session -d -s openclaw <span class="string">&quot;openclaw gateway --port 18791&quot;</span></span><br><span class="line"><span class="built_in">sleep</span> 2</span><br><span class="line">proot-distro login ubuntu -- tmux split-window -t openclaw -v <span class="string">&quot;openclaw&quot;</span></span><br><span class="line">proot-distro login ubuntu -- tmux attach -t openclaw</span><br></pre></td></tr></table></figure><p>之后要启动就 ./start_openclaw.sh，日志直接看到两个窗格。脱离 tmux 用 Ctrl+B D，完全退出就 proot-distro login ubuntu – tmux kill-session -t openclaw。</p><h2 id="几个容易忽略的点"><a href="#几个容易忽略的点" class="headerlink" title="几个容易忽略的点"></a>几个容易忽略的点</h2><ol><li>模型名：glm-4.7-flash 中间是点号，不是连字符。配错会报 Invalid input。</li><li>端口一致：Gateway 启动时用了 –port 18791，配置文件里的 gateway.port 也要改成一样，否则代理连不上。</li><li>QQ 凭证：appId 和 clientSecret 填在 channels.qqbot 里，去 QQ 开放平台申请机器人。</li><li>不要用 openclaw gateway restart：Android 上不支持 systemd，直接 pkill -f openclaw 再启动就行。</li></ol><h2 id="最后的效果"><a href="#最后的效果" class="headerlink" title="最后的效果"></a>最后的效果</h2><p>现在手机 Termux 里跑着 Ubuntu 容器，容器里 tmux 挂着 OpenClaw。QQ 上给机器人发消息，它能调用智谱模型回复，速度还行，省电模式也能撑一整天。</p><p>如果你也遇到类似的编译问题或 Gateway 连不上，可以考虑直接上容器方案，省心。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;最近想在手机上跑一个 QQ 机器人，选了个看起来挺灵活的框架 OpenClaw。本以为按文档几分钟就能搞定，结果断断续续折腾了一晚上。这里记一下过程，给同样在 Termux 上折腾的人省点时间。&lt;/p&gt;
&lt;p&gt;为什么选 OpenClaw&lt;/p&gt;
&lt;p&gt;主要看中它支持多平台（QQ、微信、Telegram 等），还能接各种大模型。我想把智谱的免费模型 glm-4.7-flash 接上 QQ，手机上跑着省电。&lt;/p&gt;
&lt;h3 id=&quot;第一个坑：插件找不到&quot;&gt;&lt;a href=&quot;#第一个坑：插件找不到&quot; class=&quot;headerlink&quot; title=&quot;第一个坑：插件找不到&quot;&gt;&lt;/a&gt;第一个坑：插件找不到&lt;/h3&gt;&lt;p&gt;装完 OpenClaw 后，配置文件里加了 openclaw-qqbot，结果启动报错：&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;plugins.allow: plugin not found: openclaw-qqbot&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;官方的安装命令跑了一遍，又说找不到 openclaw/dist/plugins-sdk/index_js/core。搜了一下，大概是版本不匹配——OpenClaw 核心和 QQ 插件各说各话。&lt;/p&gt;
&lt;h3 id=&quot;第二个坑：Node-js-编译失败&quot;&gt;&lt;a href=&quot;#第二个坑：Node-js-编译失败&quot; class=&quot;headerlink&quot; title=&quot;第二个坑：Node.js 编译失败&quot;&gt;&lt;/a&gt;第二个坑：Node.js 编译失败&lt;/h3&gt;&lt;p&gt;升级 Node.js 到 v24 后，npm install -g openclaw@latest 直接挂掉，卡在 tree-sitter-bash 的编译上：&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;gyp: Undefined variable android_ndk_path in binding.gyp&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;这是 Termux 环境的老毛病了——Android 的 C 库（Bionic）和很多 npm 原生模块不对付。折腾半天无解，决定换路子。&lt;/p&gt;
&lt;h3 id=&quot;终极方案：Ubuntu-容器&quot;&gt;&lt;a href=&quot;#终极方案：Ubuntu-容器&quot; class=&quot;headerlink&quot; title=&quot;终极方案：Ubuntu 容器&quot;&gt;&lt;/a&gt;终极方案：Ubuntu 容器&lt;/h3&gt;&lt;p&gt;Termux 里有个 proot-distro 可以跑完整 Linux 发行版。既然原生环境不行，就在容器里装。&lt;/p&gt;</summary>
    
    
    
    <category term="技术" scheme="https://www.hzv5.cn/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
    <category term="Termux" scheme="https://www.hzv5.cn/tags/Termux/"/>
    
    <category term="OpenClaw" scheme="https://www.hzv5.cn/tags/OpenClaw/"/>
    
    <category term="QQ机器人" scheme="https://www.hzv5.cn/tags/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/"/>
    
    <category term="智谱AI" scheme="https://www.hzv5.cn/tags/%E6%99%BA%E8%B0%B1AI/"/>
    
  </entry>
  
  <entry>
    <title>个人开发工具之抖音直播录制工具：一款功能强大的Android直播录制应用</title>
    <link href="https://www.hzv5.cn/2025/01/18/douyin-blog-post/"/>
    <id>https://www.hzv5.cn/2025/01/18/douyin-blog-post/</id>
    <published>2025-01-18T14:11:22.000Z</published>
    <updated>2025-01-18T14:11:22.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在移动互联网时代，直播已经成为一种重要的内容形式。有时我们会希望将喜欢的直播内容保存下来，以便日后回顾或学习。今天为大家介绍一款博主设计并基于uni-app开发的抖音直播录制工具，它不仅支持普通的录制功能，可以进行后台不需要看着直播画面进行录制抖音直播画面，还具备自动检测、后台保活等进阶特性。</p><p><img src="/img/douyin/01.jpg" alt="配图_应用主界面截图"></p><span id="more"></span><h2 id="核心功能"><a href="#核心功能" class="headerlink" title="核心功能"></a>核心功能</h2><h3 id="1-多样化的链接支持"><a href="#1-多样化的链接支持" class="headerlink" title="1. 多样化的链接支持"></a>1. 多样化的链接支持</h3><p>这款工具支持多种形式的直播链接输入：</p><ul><li>标准直播间链接</li><li>短链接</li><li>分享文本（从抖音APP复制的分享内容）</li></ul><h3 id="2-智能解析与录制"><a href="#2-智能解析与录制" class="headerlink" title="2. 智能解析与录制"></a>2. 智能解析与录制</h3><p>输入链接后，工具会自动解析并显示直播信息：</p><ul><li>主播昵称</li><li>直播间标题</li><li>实时在线人数</li><li>直播源地址</li></ul><p><img src="/img/douyin/02.png" alt="配图_解析界面"></p><p>解析完成后，只需点击”开始录制”即可开始保存直播内容。录制过程中可以实时查看：</p><ul><li>已录制时长</li><li>文件大小</li><li>录制状态</li></ul><p><img src="/img/douyin/03.png" alt="配图_录制界面"></p><h3 id="3-自动检测功能"><a href="#3-自动检测功能" class="headerlink" title="3. 自动检测功能"></a>3. 自动检测功能</h3><p>最实用的功能之一是自动检测系统：</p><ol><li>输入直播链接后开启自动检测</li><li>系统会定期检查直播状态</li><li>检测到开播自动开始录制</li><li>直播结束自动停止录制</li></ol><p>这个功能特别适合：</p><ul><li>不定时开播的主播</li><li>需要完整录制的长直播</li><li>多场直播的批量录制</li></ul><h3 id="4-后台保活机制"><a href="#4-后台保活机制" class="headerlink" title="4. 后台保活机制"></a>4. 后台保活机制</h3><p>为了确保录制的稳定性，工具实现了完整的后台保活机制：</p><ul><li>悬浮窗权限</li><li>电池优化白名单</li><li>前台服务保活</li></ul><h3 id="5-历史记录管理"><a href="#5-历史记录管理" class="headerlink" title="5. 历史记录管理"></a>5. 历史记录管理</h3><p>工具提供了两种历史记录功能：</p><ol><li><p>录制文件历史</p><ul><li>支持视频预览</li><li>文件重命名</li><li>快速删除</li></ul></li><li><p>输入链接历史</p><ul><li>保存最近使用的链接</li><li>记录主播昵称</li><li>一键重复录制</li></ul></li></ol><p><img src="/img/douyin/04.png" alt="配图_历史记录界面"></p><h2 id="使用技巧"><a href="#使用技巧" class="headerlink" title="使用技巧"></a>使用技巧</h2><h3 id="基础录制流程"><a href="#基础录制流程" class="headerlink" title="基础录制流程"></a>基础录制流程</h3><ol><li>从抖音APP复制直播链接</li><li>粘贴到输入框</li><li>点击”获取直播源”</li><li>确认信息无误后点击”开始录制”</li><li>录制完成后点击”结束录制”</li></ol><h3 id="自动检测使用方法"><a href="#自动检测使用方法" class="headerlink" title="自动检测使用方法"></a>自动检测使用方法</h3><ol><li>输入直播链接</li><li>打开自动检测开关</li><li>保持应用在后台运行</li><li>系统会自动处理录制过程</li></ol><h3 id="提升录制稳定性"><a href="#提升录制稳定性" class="headerlink" title="提升录制稳定性"></a>提升录制稳定性</h3><ol><li>首次使用时授予所有必要权限</li><li>开启悬浮窗和忽略电池优化</li><li>将应用加入系统后台白名单</li><li>确保设备有足够的存储空间</li><li>保持网络连接稳定</li></ol><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ol><li><p><strong>权限说明</strong></p><ul><li>存储权限：用于保存录制文件</li><li>悬浮窗权限：提高后台存活率</li><li>电池优化：防止系统休眠影响录制</li></ul></li><li><p><strong>资源消耗</strong></p><ul><li>建议在WiFi环境下使用</li><li>录制时会持续占用网络带宽</li><li>注意监控存储空间使用情况</li></ul></li><li><p><strong>使用限制</strong></p><ul><li>仅支持Android系统</li><li>需要Android 7.0及以上版本</li><li>部分机型可能需要特殊设置</li></ul></li></ol><h2 id="常见问题解答"><a href="#常见问题解答" class="headerlink" title="常见问题解答"></a>常见问题解答</h2><ol><li><p>Q: 为什么录制会自动停止？<br>A: 可能的原因有：</p><ul><li>直播已结束</li><li>网络连接不稳定</li><li>存储空间不足</li><li>后台进程被清理</li></ul></li><li><p>Q: 如何处理录制失败？<br>A: 建议按以下步骤排查：</p><ul><li>检查网络连接</li><li>验证存储权限</li><li>确认保活设置</li><li>清理设备存储空间</li></ul></li><li><p>Q: 自动检测不工作？<br>A: 请确认：</p><ul><li>是否授予了必要权限</li><li>链接格式是否正确</li><li>后台运行是否正常</li><li>检测间隔是否合理</li></ul></li></ol><h2 id="未来展望"><a href="#未来展望" class="headerlink" title="未来展望"></a>未来展望</h2><p>未来计划添加的功能：</p><ol><li>支持更多直播平台</li><li>添加录制视频剪辑功能</li><li>优化文件管理系统</li><li>提供更多自定义选项</li><li>增加云端备份功能</li></ol><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>这款抖音直播录制工具虽然功能强大，但使用简单直观。无论是普通用户还是专业用户，都能快速上手使用。希望这篇文章能帮助大家更好地使用这款工具。</p><h2 id="免责声明"><a href="#免责声明" class="headerlink" title="免责声明"></a>免责声明</h2><p>本工具仅供学习交流使用，请勿用于任何商业用途。使用本工具时，请遵守相关法律法规，尊重直播作者的权益。对于使用本工具所产生的任何问题，本项目概不负责。 </p>]]></content>
    
    
    <summary type="html">可以进行后台录制抖音直播画面的uniapp安卓端应用，再不需要打开直播间画面的情况下实现录制直播间的直播内容，无弹幕无礼物特效的纯净录屏。</summary>
    
    
    
    <category term="应用开发" scheme="https://www.hzv5.cn/categories/%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91/"/>
    
    <category term="工具分享" scheme="https://www.hzv5.cn/categories/%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91/%E5%B7%A5%E5%85%B7%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="Android" scheme="https://www.hzv5.cn/tags/Android/"/>
    
    <category term="直播录制" scheme="https://www.hzv5.cn/tags/%E7%9B%B4%E6%92%AD%E5%BD%95%E5%88%B6/"/>
    
    <category term="uni-app" scheme="https://www.hzv5.cn/tags/uni-app/"/>
    
    <category term="实用工具" scheme="https://www.hzv5.cn/tags/%E5%AE%9E%E7%94%A8%E5%B7%A5%E5%85%B7/"/>
    
  </entry>
  
  <entry>
    <title>UniApp中Canvas绘图的易错点与踩坑指南</title>
    <link href="https://www.hzv5.cn/2025/01/10/uniapp-canvas-error/"/>
    <id>https://www.hzv5.cn/2025/01/10/uniapp-canvas-error/</id>
    <published>2025-01-10T13:12:28.000Z</published>
    <updated>2025-01-10T13:12:28.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>在UniApp中使用Canvas绘图时，常常会遇到一些容易忽略的问题和陷阱。本文将总结这些易错点，并提供解决方案，帮助你避免踩坑，提升开发效率。</p></blockquote><span id="more"></span><hr><h3 id="1-易错点与踩坑指南"><a href="#1-易错点与踩坑指南" class="headerlink" title="1. 易错点与踩坑指南"></a><strong>1. 易错点与踩坑指南</strong></h3><h4 id="1-1-Canvas上下文未正确获取"><a href="#1-1-Canvas上下文未正确获取" class="headerlink" title="1.1 Canvas上下文未正确获取"></a><strong>1.1 Canvas上下文未正确获取</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>调用<code>uni.createCanvasContext</code>后，绘图操作未生效。</li></ul></li><li><strong>原因</strong>：<ul><li>未正确获取Canvas上下文，或未调用<code>ctx.draw()</code>方法。</li></ul></li><li><strong>解决方案</strong>：<ol><li>确保正确获取Canvas上下文：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ctx = uni.<span class="title function_">createCanvasContext</span>(<span class="string">&#x27;myCanvas&#x27;</span>);</span><br></pre></td></tr></table></figure></li><li>在绘图操作后调用<code>ctx.draw()</code>：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ctx.<span class="title function_">fillRect</span>(<span class="number">10</span>, <span class="number">10</span>, <span class="number">100</span>, <span class="number">100</span>);</span><br><span class="line">ctx.<span class="title function_">draw</span>(); <span class="comment">// 必须调用draw方法才会生效</span></span><br></pre></td></tr></table></figure></li></ol></li></ul><h4 id="1-2-Canvas尺寸设置不当"><a href="#1-2-Canvas尺寸设置不当" class="headerlink" title="1.2 Canvas尺寸设置不当"></a><strong>1.2 Canvas尺寸设置不当</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>Canvas绘图内容显示不全或完全不可见。</li></ul></li><li><strong>原因</strong>：<ul><li>Canvas的宽度或高度设置不正确，或者未适配屏幕分辨率。</li></ul></li><li><strong>解决方案</strong>：<ol><li>在<code>&lt;canvas&gt;</code>标签中设置正确的宽度和高度：<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">canvas</span> <span class="attr">canvas-id</span>=<span class="string">&quot;myCanvas&quot;</span> <span class="attr">style</span>=<span class="string">&quot;width: 300px; height: 300px;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">canvas</span>&gt;</span></span><br></pre></td></tr></table></figure></li><li>使用<code>uni.getSystemInfoSync</code>获取屏幕宽度并动态设置Canvas尺寸：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> systemInfo = uni.<span class="title function_">getSystemInfoSync</span>();</span><br><span class="line"><span class="keyword">const</span> canvasWidth = systemInfo.<span class="property">windowWidth</span>;</span><br><span class="line"><span class="keyword">const</span> canvasHeight = systemInfo.<span class="property">windowHeight</span>;</span><br></pre></td></tr></table></figure></li></ol></li></ul><h4 id="1-3-Canvas层级问题"><a href="#1-3-Canvas层级问题" class="headerlink" title="1.3 Canvas层级问题"></a><strong>1.3 Canvas层级问题</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>Canvas被其他组件遮挡，导致绘图内容不可见。</li></ul></li><li><strong>原因</strong>：<ul><li>Canvas的层级（z-index）较低，或被其他组件覆盖。</li></ul></li><li><strong>解决方案</strong>：<ol><li>使用<code>position: fixed</code>或<code>position: absolute</code>提升Canvas层级：<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">canvas</span> <span class="attr">canvas-id</span>=<span class="string">&quot;myCanvas&quot;</span> <span class="attr">style</span>=<span class="string">&quot;position: fixed; top: 0; left: 0;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">canvas</span>&gt;</span></span><br></pre></td></tr></table></figure></li><li>避免在Canvas上方放置其他组件。</li></ol></li></ul><h4 id="1-4-Canvas渲染时机问题"><a href="#1-4-Canvas渲染时机问题" class="headerlink" title="1.4 Canvas渲染时机问题"></a><strong>1.4 Canvas渲染时机问题</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>在页面加载时绘图，但内容未显示。</li></ul></li><li><strong>原因</strong>：<ul><li>Canvas渲染时机过早，页面尚未完全加载。</li></ul></li><li><strong>解决方案</strong>：<ol><li>在<code>onReady</code>生命周期中执行绘图操作：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="title function_">onReady</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> ctx = uni.<span class="title function_">createCanvasContext</span>(<span class="string">&#x27;myCanvas&#x27;</span>);</span><br><span class="line">    ctx.<span class="title function_">fillRect</span>(<span class="number">10</span>, <span class="number">10</span>, <span class="number">100</span>, <span class="number">100</span>);</span><br><span class="line">    ctx.<span class="title function_">draw</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li><li>使用<code>setTimeout</code>延迟绘图操作：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> ctx = uni.<span class="title function_">createCanvasContext</span>(<span class="string">&#x27;myCanvas&#x27;</span>);</span><br><span class="line">  ctx.<span class="title function_">fillRect</span>(<span class="number">10</span>, <span class="number">10</span>, <span class="number">100</span>, <span class="number">100</span>);</span><br><span class="line">  ctx.<span class="title function_">draw</span>();</span><br><span class="line">&#125;, <span class="number">500</span>);</span><br></pre></td></tr></table></figure></li></ol></li></ul><h4 id="1-5-Canvas绘图性能问题"><a href="#1-5-Canvas绘图性能问题" class="headerlink" title="1.5 Canvas绘图性能问题"></a><strong>1.5 Canvas绘图性能问题</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>绘图操作卡顿或延迟。</li></ul></li><li><strong>原因</strong>：<ul><li>绘图操作过于频繁，或Canvas尺寸过大。</li></ul></li><li><strong>解决方案</strong>：<ol><li>减少不必要的绘图操作。</li><li>使用<code>ctx.draw(true)</code>开启离屏渲染，提升性能。</li></ol></li></ul><h4 id="1-6-Canvas绘图内容模糊"><a href="#1-6-Canvas绘图内容模糊" class="headerlink" title="1.6 Canvas绘图内容模糊"></a><strong>1.6 Canvas绘图内容模糊</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>绘图内容显示模糊。</li></ul></li><li><strong>原因</strong>：<ul><li>Canvas的CSS尺寸与画布分辨率不匹配。</li></ul></li><li><strong>解决方案</strong>：<ol><li>使用<code>uni.getSystemInfoSync</code>获取设备像素比（<code>pixelRatio</code>），并设置Canvas的实际分辨率：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> systemInfo = uni.<span class="title function_">getSystemInfoSync</span>();</span><br><span class="line"><span class="keyword">const</span> pixelRatio = systemInfo.<span class="property">pixelRatio</span>;</span><br><span class="line"><span class="keyword">const</span> canvasWidth = <span class="number">300</span> * pixelRatio;</span><br><span class="line"><span class="keyword">const</span> canvasHeight = <span class="number">300</span> * pixelRatio;</span><br></pre></td></tr></table></figure></li><li>在CSS中设置Canvas的显示尺寸为逻辑像素：<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">canvas</span> <span class="attr">canvas-id</span>=<span class="string">&quot;myCanvas&quot;</span> <span class="attr">style</span>=<span class="string">&quot;width: 300px; height: 300px;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">canvas</span>&gt;</span></span><br></pre></td></tr></table></figure></li></ol></li></ul><hr><h3 id="2-示例代码"><a href="#2-示例代码" class="headerlink" title="2. 示例代码"></a><strong>2. 示例代码</strong></h3><p>以下是一个完整的Canvas绘图示例，涵盖了上述问题的解决方案：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">view</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">canvas</span> <span class="attr">canvas-id</span>=<span class="string">&quot;myCanvas&quot;</span> <span class="attr">style</span>=<span class="string">&quot;width: 300px; height: 300px;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">canvas</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">view</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">onReady</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="title function_">drawCanvas</span>();</span></span><br><span class="line"><span class="language-javascript">  &#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="attr">methods</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">drawCanvas</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> systemInfo = uni.<span class="title function_">getSystemInfoSync</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> pixelRatio = systemInfo.<span class="property">pixelRatio</span>;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> canvasWidth = <span class="number">300</span> * pixelRatio;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> canvasHeight = <span class="number">300</span> * pixelRatio;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> ctx = uni.<span class="title function_">createCanvasContext</span>(<span class="string">&#x27;myCanvas&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript">      ctx.<span class="title function_">fillRect</span>(<span class="number">10</span>, <span class="number">10</span>, <span class="number">100</span>, <span class="number">100</span>);</span></span><br><span class="line"><span class="language-javascript">      ctx.<span class="title function_">draw</span>();</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">canvas</span> &#123;</span></span><br><span class="line"><span class="language-css">  <span class="attribute">position</span>: fixed;</span></span><br><span class="line"><span class="language-css">  <span class="attribute">top</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">  <span class="attribute">left</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br></pre></td></tr></table></figure><hr><h3 id="3-总结"><a href="#3-总结" class="headerlink" title="3. 总结"></a><strong>3. 总结</strong></h3><p>在UniApp中使用Canvas绘图时，常见的易错点包括上下文未正确获取、Canvas尺寸问题、层级问题和渲染时机问题。通过本文提供的解决方案，你可以快速定位并解决这些问题，确保Canvas绘图内容正常显示。</p><blockquote><p>希望这篇博客对你有帮助！如果有其他问题，欢迎随时交流！ 🚀</p></blockquote>]]></content>
    
    
    <summary type="html">深入探讨uni-app组件Canvas开发的最佳实践,包括基础组件封装、组件通信、绘图不显示的常见问题与解决方案等内容,帮助开发者构建高质量的UI组件库。</summary>
    
    
    
    <category term="前端开发" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    <category term="uni-app" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/uni-app/"/>
    
    <category term="canvas" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/uni-app/canvas/"/>
    
    
    <category term="uni-app" scheme="https://www.hzv5.cn/tags/uni-app/"/>
    
    <category term="前端开发" scheme="https://www.hzv5.cn/tags/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    <category term="组件开发" scheme="https://www.hzv5.cn/tags/%E7%BB%84%E4%BB%B6%E5%BC%80%E5%8F%91/"/>
    
    <category term="UI组件" scheme="https://www.hzv5.cn/tags/UI%E7%BB%84%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title>QQ频道机器人与UniApp开发：常见踩坑点与解决方案</title>
    <link href="https://www.hzv5.cn/2025/01/07/uniapp-qq-channel-bot/"/>
    <id>https://www.hzv5.cn/2025/01/07/uniapp-qq-channel-bot/</id>
    <published>2025-01-07T03:41:22.000Z</published>
    <updated>2025-01-07T03:41:22.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>在开发QQ频道机器人和UniApp应用的过程中，可能会遇到一些常见的错误和坑。本文将总结这些容易出错的地方，并提供解决方案，帮助你更顺利地完成开发。</p></blockquote><span id="more"></span><hr><h3 id="1-QQ频道机器人开发中的常见问题"><a href="#1-QQ频道机器人开发中的常见问题" class="headerlink" title="1. QQ频道机器人开发中的常见问题"></a><strong>1. QQ频道机器人开发中的常见问题</strong></h3><h4 id="1-1-权限问题"><a href="#1-1-权限问题" class="headerlink" title="1.1 权限问题"></a><strong>1.1 权限问题</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>调用API时返回“权限不足”或“未授权”错误。</li></ul></li><li><strong>原因</strong>：<ul><li>未正确申请API权限，或未在请求头中携带正确的<code>Authorization</code>。</li></ul></li><li><strong>解决方案</strong>：<ol><li>在QQ开放平台确认已申请相关API权限。</li><li>确保请求头中包含正确的<code>Authorization</code>，格式为：<code>Bot &#123;BotToken&#125;</code>。</li></ol></li></ul><h4 id="1-2-消息发送失败"><a href="#1-2-消息发送失败" class="headerlink" title="1.2 消息发送失败"></a><strong>1.2 消息发送失败</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>发送消息时返回“频道不存在”或“消息发送失败”。</li></ul></li><li><strong>原因</strong>：<ul><li>频道ID错误，或机器人未加入目标频道。</li></ul></li><li><strong>解决方案</strong>：<ol><li>确认频道ID是否正确。</li><li>确保机器人已加入目标频道。</li></ol></li></ul><h4 id="1-3-Webhook配置问题"><a href="#1-3-Webhook配置问题" class="headerlink" title="1.3 Webhook配置问题"></a><strong>1.3 Webhook配置问题</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>Webhook无法接收消息，或消息格式解析失败。</li></ul></li><li><strong>原因</strong>：<ul><li>Webhook地址未正确配置，或未处理消息的加密和签名。</li></ul></li><li><strong>解决方案</strong>：<ol><li>在QQ开放平台正确配置Webhook地址。</li><li>参考官方文档处理消息的加密和签名验证。</li></ol></li></ul><hr><h3 id="2-UniApp开发中的常见问题"><a href="#2-UniApp开发中的常见问题" class="headerlink" title="2. UniApp开发中的常见问题"></a><strong>2. UniApp开发中的常见问题</strong></h3><h4 id="2-1-跨域问题"><a href="#2-1-跨域问题" class="headerlink" title="2.1 跨域问题"></a><strong>2.1 跨域问题</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>在H5端调用API时，出现跨域错误。</li></ul></li><li><strong>原因</strong>：<ul><li>后端服务未配置CORS（跨域资源共享）。</li></ul></li><li><strong>解决方案</strong>：<ol><li>在后端服务中添加CORS支持：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="title function_">express</span>();</span><br><span class="line">app.<span class="title function_">use</span>(<span class="function">(<span class="params">req, res, next</span>) =&gt;</span> &#123;</span><br><span class="line">  res.<span class="title function_">header</span>(<span class="string">&#x27;Access-Control-Allow-Origin&#x27;</span>, <span class="string">&#x27;*&#x27;</span>);</span><br><span class="line">  res.<span class="title function_">header</span>(<span class="string">&#x27;Access-Control-Allow-Headers&#x27;</span>, <span class="string">&#x27;Content-Type&#x27;</span>);</span><br><span class="line">  <span class="title function_">next</span>();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></li><li>在UniApp中使用代理解决跨域问题：<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// manifest.json</span></span><br><span class="line"><span class="attr">&quot;h5&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;devServer&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;proxy&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;/api&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;target&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://your-server.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;changeOrigin&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li></ol></li></ul><h4 id="2-2-样式兼容性问题"><a href="#2-2-样式兼容性问题" class="headerlink" title="2.2 样式兼容性问题"></a><strong>2.2 样式兼容性问题</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>在不同平台上样式显示不一致。</li></ul></li><li><strong>原因</strong>：<ul><li>不同平台对CSS的支持存在差异。</li></ul></li><li><strong>解决方案</strong>：<ol><li>使用UniApp提供的条件编译功能，针对不同平台编写样式：<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* #ifdef H5 */</span></span><br><span class="line"><span class="selector-class">.container</span> &#123; <span class="attribute">padding</span>: <span class="number">10px</span>; &#125;</span><br><span class="line"><span class="comment">/* #endif */</span></span><br><span class="line"><span class="comment">/* #ifdef MP-WEIXIN */</span></span><br><span class="line"><span class="selector-class">.container</span> &#123; <span class="attribute">padding</span>: <span class="number">20px</span>; &#125;</span><br><span class="line"><span class="comment">/* #endif */</span></span><br></pre></td></tr></table></figure></li><li>使用Flexbox或Grid布局，避免使用平台特定的样式。</li></ol></li></ul><h4 id="2-3-真机调试问题"><a href="#2-3-真机调试问题" class="headerlink" title="2.3 真机调试问题"></a><strong>2.3 真机调试问题</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>在真机上运行时，功能异常或无法调试。</li></ul></li><li><strong>原因</strong>：<ul><li>真机环境与开发环境存在差异。</li></ul></li><li><strong>解决方案</strong>：<ol><li>使用HBuilderX的真机调试功能，实时查看日志和错误信息。</li><li>确保真机与开发机在同一局域网下，并正确配置网络权限。</li></ol></li></ul><hr><h3 id="3-结合开发中的常见问题"><a href="#3-结合开发中的常见问题" class="headerlink" title="3. 结合开发中的常见问题"></a><strong>3. 结合开发中的常见问题</strong></h3><h4 id="3-1-数据格式不一致"><a href="#3-1-数据格式不一致" class="headerlink" title="3.1 数据格式不一致"></a><strong>3.1 数据格式不一致</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>QQ频道API返回的数据格式与UniApp前端预期不一致。</li></ul></li><li><strong>原因</strong>：<ul><li>未对API返回的数据进行格式化处理。</li></ul></li><li><strong>解决方案</strong>：<ol><li>在后端服务中对API返回的数据进行格式化：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">formatMessage</span> = (<span class="params">message</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">id</span>: message.<span class="property">id</span>,</span><br><span class="line">    <span class="attr">content</span>: message.<span class="property">content</span>,</span><br><span class="line">    <span class="attr">timestamp</span>: <span class="keyword">new</span> <span class="title class_">Date</span>(message.<span class="property">timestamp</span>).<span class="title function_">toLocaleString</span>()</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li><li>在UniApp前端使用<code>computed</code>属性对数据进行处理：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">computed</span>: &#123;</span><br><span class="line">  <span class="title function_">formattedMessages</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">messages</span>.<span class="title function_">map</span>(<span class="function"><span class="params">msg</span> =&gt;</span> (&#123;</span><br><span class="line">      ...msg,</span><br><span class="line">      <span class="attr">timestamp</span>: <span class="keyword">new</span> <span class="title class_">Date</span>(msg.<span class="property">timestamp</span>).<span class="title function_">toLocaleString</span>()</span><br><span class="line">    &#125;));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol></li></ul><h4 id="3-2-网络请求超时"><a href="#3-2-网络请求超时" class="headerlink" title="3.2 网络请求超时"></a><strong>3.2 网络请求超时</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>网络请求超时，导致功能异常。</li></ul></li><li><strong>原因</strong>：<ul><li>网络环境不稳定，或请求未设置超时时间。</li></ul></li><li><strong>解决方案</strong>：<ol><li>在UniApp中设置请求超时时间：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">uni.<span class="title function_">request</span>(&#123;</span><br><span class="line">  <span class="attr">url</span>: <span class="string">&#x27;https://your-server.com/api&#x27;</span>,</span><br><span class="line">  <span class="attr">timeout</span>: <span class="number">5000</span>, <span class="comment">// 设置超时时间为5秒</span></span><br><span class="line">  <span class="attr">success</span>: <span class="function">(<span class="params">res</span>) =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(res.<span class="property">data</span>)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></li><li>在后端服务中优化接口性能，减少响应时间。</li></ol></li></ul><hr><h3 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a><strong>4. 总结</strong></h3><p>在开发QQ频道机器人和UniApp应用时，权限问题、跨域问题、样式兼容性和网络请求是常见的踩坑点。通过本文提供的解决方案，你可以有效避免这些问题，提升开发效率和项目质量。</p><blockquote><p>如果你在开发过程中遇到其他问题，欢迎在评论区留言讨论！ 😊</p></blockquote>]]></content>
    
    
    <summary type="html">深入探讨uni-app中开发QQ机器人的最佳实践,包括基础封装、通信、跨端适配等常见问题内容,帮助开发者构建高质量的QQ频道机器人。</summary>
    
    
    
    <category term="前端开发" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    <category term="uni-app" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/uni-app/"/>
    
    <category term="QQ机器人" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/uni-app/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/"/>
    
    <category term="QQ频道" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/uni-app/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/QQ%E9%A2%91%E9%81%93/"/>
    
    
    <category term="QQ机器人" scheme="https://www.hzv5.cn/tags/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/"/>
    
    <category term="uni-app" scheme="https://www.hzv5.cn/tags/uni-app/"/>
    
    <category term="前端开发" scheme="https://www.hzv5.cn/tags/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    <category term="QQ频道" scheme="https://www.hzv5.cn/tags/QQ%E9%A2%91%E9%81%93/"/>
    
  </entry>
  
  <entry>
    <title>UniApp中Canvas绘图不显示的常见问题与解决方案</title>
    <link href="https://www.hzv5.cn/2025/01/02/uniapp-canvas-not-show/"/>
    <id>https://www.hzv5.cn/2025/01/02/uniapp-canvas-not-show/</id>
    <published>2025-01-02T07:22:36.000Z</published>
    <updated>2025-01-02T07:22:36.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>在UniApp中使用Canvas绘图时，可能会遇到绘图内容不显示的问题。本文将总结这些常见问题的原因，并提供解决方案，帮助你快速定位和解决问题。</p></blockquote><span id="more"></span><hr><h3 id="1-Canvas绘图不显示的常见原因"><a href="#1-Canvas绘图不显示的常见原因" class="headerlink" title="1. Canvas绘图不显示的常见原因"></a><strong>1. Canvas绘图不显示的常见原因</strong></h3><h4 id="1-1-Canvas上下文未正确获取"><a href="#1-1-Canvas上下文未正确获取" class="headerlink" title="1.1 Canvas上下文未正确获取"></a><strong>1.1 Canvas上下文未正确获取</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>调用<code>uni.createCanvasContext</code>后，绘图操作未生效。</li></ul></li><li><strong>原因</strong>：<ul><li>未正确获取Canvas上下文，或未调用<code>ctx.draw()</code>方法。</li></ul></li><li><strong>解决方案</strong>：<ol><li>确保正确获取Canvas上下文：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ctx = uni.<span class="title function_">createCanvasContext</span>(<span class="string">&#x27;myCanvas&#x27;</span>);</span><br></pre></td></tr></table></figure></li><li>在绘图操作后调用<code>ctx.draw()</code>：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ctx.<span class="title function_">fillRect</span>(<span class="number">10</span>, <span class="number">10</span>, <span class="number">100</span>, <span class="number">100</span>);</span><br><span class="line">ctx.<span class="title function_">draw</span>(); <span class="comment">// 必须调用draw方法才会生效</span></span><br></pre></td></tr></table></figure></li></ol></li></ul><h4 id="1-2-Canvas尺寸问题"><a href="#1-2-Canvas尺寸问题" class="headerlink" title="1.2 Canvas尺寸问题"></a><strong>1.2 Canvas尺寸问题</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>Canvas绘图内容显示不全或完全不可见。</li></ul></li><li><strong>原因</strong>：<ul><li>Canvas的宽度或高度设置不正确，或者未适配屏幕分辨率。</li></ul></li><li><strong>解决方案</strong>：<ol><li>在<code>&lt;canvas&gt;</code>标签中设置正确的宽度和高度：<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">canvas</span> <span class="attr">canvas-id</span>=<span class="string">&quot;myCanvas&quot;</span> <span class="attr">style</span>=<span class="string">&quot;width: 300px; height: 300px;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">canvas</span>&gt;</span></span><br></pre></td></tr></table></figure></li><li>使用<code>uni.getSystemInfoSync</code>获取屏幕宽度并动态设置Canvas尺寸：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> systemInfo = uni.<span class="title function_">getSystemInfoSync</span>();</span><br><span class="line"><span class="keyword">const</span> canvasWidth = systemInfo.<span class="property">windowWidth</span>;</span><br><span class="line"><span class="keyword">const</span> canvasHeight = systemInfo.<span class="property">windowHeight</span>;</span><br></pre></td></tr></table></figure></li></ol></li></ul><h4 id="1-3-Canvas层级问题"><a href="#1-3-Canvas层级问题" class="headerlink" title="1.3 Canvas层级问题"></a><strong>1.3 Canvas层级问题</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>Canvas被其他组件遮挡，导致绘图内容不可见。</li></ul></li><li><strong>原因</strong>：<ul><li>Canvas的层级（z-index）较低，或被其他组件覆盖。</li></ul></li><li><strong>解决方案</strong>：<ol><li>使用<code>position: fixed</code>或<code>position: absolute</code>提升Canvas层级：<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">canvas</span> <span class="attr">canvas-id</span>=<span class="string">&quot;myCanvas&quot;</span> <span class="attr">style</span>=<span class="string">&quot;position: fixed; top: 0; left: 0;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">canvas</span>&gt;</span></span><br></pre></td></tr></table></figure></li><li>避免在Canvas上方放置其他组件。</li></ol></li></ul><h4 id="1-4-Canvas渲染时机问题"><a href="#1-4-Canvas渲染时机问题" class="headerlink" title="1.4 Canvas渲染时机问题"></a><strong>1.4 Canvas渲染时机问题</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>在页面加载时绘图，但内容未显示。</li></ul></li><li><strong>原因</strong>：<ul><li>Canvas渲染时机过早，页面尚未完全加载。</li></ul></li><li><strong>解决方案</strong>：<ol><li>在<code>onReady</code>生命周期中执行绘图操作：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="title function_">onReady</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> ctx = uni.<span class="title function_">createCanvasContext</span>(<span class="string">&#x27;myCanvas&#x27;</span>);</span><br><span class="line">    ctx.<span class="title function_">fillRect</span>(<span class="number">10</span>, <span class="number">10</span>, <span class="number">100</span>, <span class="number">100</span>);</span><br><span class="line">    ctx.<span class="title function_">draw</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li><li>使用<code>setTimeout</code>延迟绘图操作：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> ctx = uni.<span class="title function_">createCanvasContext</span>(<span class="string">&#x27;myCanvas&#x27;</span>);</span><br><span class="line">  ctx.<span class="title function_">fillRect</span>(<span class="number">10</span>, <span class="number">10</span>, <span class="number">100</span>, <span class="number">100</span>);</span><br><span class="line">  ctx.<span class="title function_">draw</span>();</span><br><span class="line">&#125;, <span class="number">500</span>);</span><br></pre></td></tr></table></figure></li></ol></li></ul><hr><h3 id="2-其他常见问题"><a href="#2-其他常见问题" class="headerlink" title="2. 其他常见问题"></a><strong>2. 其他常见问题</strong></h3><h4 id="2-1-Canvas绘图性能问题"><a href="#2-1-Canvas绘图性能问题" class="headerlink" title="2.1 Canvas绘图性能问题"></a><strong>2.1 Canvas绘图性能问题</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>绘图操作卡顿或延迟。</li></ul></li><li><strong>原因</strong>：<ul><li>绘图操作过于频繁，或Canvas尺寸过大。</li></ul></li><li><strong>解决方案</strong>：<ol><li>减少不必要的绘图操作。</li><li>使用<code>ctx.draw(true)</code>开启离屏渲染，提升性能。</li></ol></li></ul><h4 id="2-2-Canvas绘图内容模糊"><a href="#2-2-Canvas绘图内容模糊" class="headerlink" title="2.2 Canvas绘图内容模糊"></a><strong>2.2 Canvas绘图内容模糊</strong></h4><ul><li><strong>问题描述</strong>：<ul><li>绘图内容显示模糊。</li></ul></li><li><strong>原因</strong>：<ul><li>Canvas的CSS尺寸与画布分辨率不匹配。</li></ul></li><li><strong>解决方案</strong>：<ol><li>使用<code>uni.getSystemInfoSync</code>获取设备像素比（<code>pixelRatio</code>），并设置Canvas的实际分辨率：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> systemInfo = uni.<span class="title function_">getSystemInfoSync</span>();</span><br><span class="line"><span class="keyword">const</span> pixelRatio = systemInfo.<span class="property">pixelRatio</span>;</span><br><span class="line"><span class="keyword">const</span> canvasWidth = <span class="number">300</span> * pixelRatio;</span><br><span class="line"><span class="keyword">const</span> canvasHeight = <span class="number">300</span> * pixelRatio;</span><br></pre></td></tr></table></figure></li><li>在CSS中设置Canvas的显示尺寸为逻辑像素：<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">canvas</span> <span class="attr">canvas-id</span>=<span class="string">&quot;myCanvas&quot;</span> <span class="attr">style</span>=<span class="string">&quot;width: 300px; height: 300px;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">canvas</span>&gt;</span></span><br></pre></td></tr></table></figure></li></ol></li></ul><hr><h3 id="3-示例代码"><a href="#3-示例代码" class="headerlink" title="3. 示例代码"></a><strong>3. 示例代码</strong></h3><p>以下是一个完整的Canvas绘图示例，涵盖了上述问题的解决方案：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">view</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">canvas</span> <span class="attr">canvas-id</span>=<span class="string">&quot;myCanvas&quot;</span> <span class="attr">style</span>=<span class="string">&quot;width: 300px; height: 300px;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">canvas</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">view</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">onReady</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="title function_">drawCanvas</span>();</span></span><br><span class="line"><span class="language-javascript">  &#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="attr">methods</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">drawCanvas</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> systemInfo = uni.<span class="title function_">getSystemInfoSync</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> pixelRatio = systemInfo.<span class="property">pixelRatio</span>;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> canvasWidth = <span class="number">300</span> * pixelRatio;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> canvasHeight = <span class="number">300</span> * pixelRatio;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> ctx = uni.<span class="title function_">createCanvasContext</span>(<span class="string">&#x27;myCanvas&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript">      ctx.<span class="title function_">fillRect</span>(<span class="number">10</span>, <span class="number">10</span>, <span class="number">100</span>, <span class="number">100</span>);</span></span><br><span class="line"><span class="language-javascript">      ctx.<span class="title function_">draw</span>();</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">canvas</span> &#123;</span></span><br><span class="line"><span class="language-css">  <span class="attribute">position</span>: fixed;</span></span><br><span class="line"><span class="language-css">  <span class="attribute">top</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">  <span class="attribute">left</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br></pre></td></tr></table></figure><hr><h3 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a><strong>4. 总结</strong></h3><p>在UniApp中使用Canvas绘图时，常见的问题包括上下文未正确获取、Canvas尺寸问题、层级问题和渲染时机问题。通过本文提供的解决方案，你可以快速定位并解决这些问题，确保Canvas绘图内容正常显示。</p><blockquote><p>希望这篇博客对你有帮助！如果有其他问题，欢迎随时交流！ 🚀</p></blockquote>]]></content>
    
    
    <summary type="html">深入探讨uni-app开发中canvas组件使用的最佳实践,包括基础组件封装、组件通信、绘图不显示的常见问题与解决方案等内容,帮助开发者构建高质量的UI组件库。</summary>
    
    
    
    <category term="前端开发" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    <category term="uni-app" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/uni-app/"/>
    
    <category term="canvas" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/uni-app/canvas/"/>
    
    
    <category term="uni-app" scheme="https://www.hzv5.cn/tags/uni-app/"/>
    
    <category term="前端开发" scheme="https://www.hzv5.cn/tags/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    <category term="组件开发" scheme="https://www.hzv5.cn/tags/%E7%BB%84%E4%BB%B6%E5%BC%80%E5%8F%91/"/>
    
    <category term="UI组件" scheme="https://www.hzv5.cn/tags/UI%E7%BB%84%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title>Hexo博客实现随机文章功能的完整教程</title>
    <link href="https://www.hzv5.cn/2025/01/01/hexo-random-post/"/>
    <id>https://www.hzv5.cn/2025/01/01/hexo-random-post/</id>
    <published>2025-01-01T14:55:12.000Z</published>
    <updated>2025-01-01T14:55:12.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>想要给博客增加一些趣味性？让读者可以随机浏览你的文章是个不错的选择。本文将详细介绍如何在Hexo博客中实现随机文章功能，就跟本博客的侧栏导航中的【<a href="/random" title="随机获取一篇博文 - 吖远zzy">手气</a>】菜单一样的效果。</p></blockquote><span id="more"></span><h2 id="1-功能介绍"><a href="#1-功能介绍" class="headerlink" title="1. 功能介绍"></a>1. 功能介绍</h2><p>随机文章功能可以让访问者随机浏览博客中的任意一篇文章,这不仅能增加博客的趣味性,还能提高文章的曝光率,让一些早期的优质文章也有机会被读者发现。</p><h2 id="2-实现步骤"><a href="#2-实现步骤" class="headerlink" title="2. 实现步骤"></a>2. 实现步骤</h2><h3 id="2-1-创建模板文件"><a href="#2-1-创建模板文件" class="headerlink" title="2.1 创建模板文件"></a>2.1 创建模板文件</h3><p>首先在主题的<code>layout</code>目录下创建<code>random.ejs</code>文件:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">&lt;%</span><br><span class="line">// 获取所有文章</span><br><span class="line">const posts = site.posts.data;</span><br><span class="line">// 随机选择一篇文章</span><br><span class="line">const randomPost = posts[Math.floor(Math.random() * posts.length)];</span><br><span class="line">%&gt;</span><br><span class="line"></span><br><span class="line">&lt;script&gt;</span><br><span class="line">// 立即重定向到随机文章</span><br><span class="line">window.location.href = &#x27;&lt;%- url_for(randomPost.path) %&gt;&#x27;;</span><br><span class="line">&lt;/script&gt;</span><br><span class="line"></span><br><span class="line">&lt;noscript&gt;</span><br><span class="line">  &lt;div style=&quot;text-align: center; padding: 20px;&quot;&gt;</span><br><span class="line">    &lt;p&gt;请点击下方链接访问随机文章：&lt;/p&gt;</span><br><span class="line">    &lt;a href=&quot;&lt;%- url_for(randomPost.path) %&gt;&quot;&gt;&lt;%- randomPost.title %&gt;&lt;/a&gt;</span><br><span class="line">  &lt;/div&gt;</span><br><span class="line">&lt;/noscript&gt;</span><br></pre></td></tr></table></figure><h3 id="2-2-创建页面文件"><a href="#2-2-创建页面文件" class="headerlink" title="2.2 创建页面文件"></a>2.2 创建页面文件</h3><p>在<code>source</code>目录下创建<code>random</code>文件夹,并在其中创建<code>index.md</code>:</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">title: 随机文章</span><br><span class="line">date: 2023-12-31 12:00:00</span><br><span class="line">type: random</span><br><span class="line">layout: random</span><br><span class="line"><span class="section">comments: false</span></span><br><span class="line"><span class="section">---</span></span><br></pre></td></tr></table></figure><h3 id="2-3-添加菜单项"><a href="#2-3-添加菜单项" class="headerlink" title="2.3 添加菜单项"></a>2.3 添加菜单项</h3><p>在主题的<code>_config.yml</code>中添加菜单项:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">menu:</span></span><br><span class="line">  <span class="attr">random:</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">/random</span></span><br><span class="line">    <span class="attr">text:</span> <span class="string">手气</span></span><br><span class="line">    <span class="attr">icon:</span> <span class="string">fas</span> <span class="string">fa-random</span></span><br></pre></td></tr></table></figure><h2 id="3-功能说明"><a href="#3-功能说明" class="headerlink" title="3. 功能说明"></a>3. 功能说明</h2><h3 id="3-1-工作原理"><a href="#3-1-工作原理" class="headerlink" title="3.1 工作原理"></a>3.1 工作原理</h3><ol><li>当访问者点击”随机文章”菜单时,会访问<code>/random</code>页面</li><li>模板会从所有文章中随机选择一篇</li><li>通过JavaScript自动跳转到选中的文章</li><li>如果浏览器禁用了JavaScript,则显示一个可点击的链接</li></ol><h3 id="3-2-优化建议"><a href="#3-2-优化建议" class="headerlink" title="3.2 优化建议"></a>3.2 优化建议</h3><ol><li><strong>性能优化</strong>: 如果博客文章很多,可以考虑缓存文章列表</li><li><strong>用户体验</strong>: 可以添加加载动画</li><li><strong>功能扩展</strong>: 可以按分类或标签随机</li></ol><h2 id="4-常见问题"><a href="#4-常见问题" class="headerlink" title="4. 常见问题"></a>4. 常见问题</h2><h3 id="4-1-页面闪烁"><a href="#4-1-页面闪烁" class="headerlink" title="4.1 页面闪烁"></a>4.1 页面闪烁</h3><p>如果出现页面闪烁,可以添加loading效果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&lt;style&gt;</span><br><span class="line">.loading &#123;</span><br><span class="line">  text-align: center;</span><br><span class="line">  padding: 20px;</span><br><span class="line">&#125;</span><br><span class="line">.loading:after &#123;</span><br><span class="line">  content: &#x27;正在随机选择文章...&#x27;;</span><br><span class="line">  color: #666;</span><br><span class="line">&#125;</span><br><span class="line">&lt;/style&gt;</span><br><span class="line">&lt;div class=&quot;loading&quot;&gt;&lt;/div&gt;</span><br></pre></td></tr></table></figure><h3 id="4-2-重复访问"><a href="#4-2-重复访问" class="headerlink" title="4.2 重复访问"></a>4.2 重复访问</h3><p>如果想避免连续访问同一篇文章,可以使用sessionStorage记录历史:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取历史记录</span></span><br><span class="line"><span class="keyword">const</span> history = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(<span class="variable language_">sessionStorage</span>.<span class="title function_">getItem</span>(<span class="string">&#x27;randomHistory&#x27;</span>) || <span class="string">&#x27;[]&#x27;</span>);</span><br><span class="line"><span class="comment">// 过滤已访问的文章</span></span><br><span class="line"><span class="keyword">const</span> availablePosts = posts.<span class="title function_">filter</span>(<span class="function"><span class="params">post</span> =&gt;</span> !history.<span class="title function_">includes</span>(post.<span class="property">path</span>));</span><br><span class="line"><span class="comment">// 如果所有文章都访问过,清空历史</span></span><br><span class="line"><span class="keyword">if</span> (availablePosts.<span class="property">length</span> === <span class="number">0</span>) &#123;</span><br><span class="line">  <span class="variable language_">sessionStorage</span>.<span class="title function_">removeItem</span>(<span class="string">&#x27;randomHistory&#x27;</span>);</span><br><span class="line">  availablePosts = posts;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 随机选择文章</span></span><br><span class="line"><span class="keyword">const</span> randomPost = availablePosts[<span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * availablePosts.<span class="property">length</span>)];</span><br><span class="line"><span class="comment">// 记录到历史</span></span><br><span class="line">history.<span class="title function_">push</span>(randomPost.<span class="property">path</span>);</span><br><span class="line"><span class="variable language_">sessionStorage</span>.<span class="title function_">setItem</span>(<span class="string">&#x27;randomHistory&#x27;</span>, <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(history));</span><br></pre></td></tr></table></figure><h2 id="5-总结"><a href="#5-总结" class="headerlink" title="5. 总结"></a>5. 总结</h2><p>随机文章功能虽然实现简单,但能大大提升博客的趣味性和用户体验。你还可以基于这个基础功能,添加更多个性化的扩展,比如按照文章热度随机、按照阅读时间随机等。</p><hr><blockquote><p>如果觉得文章对你有帮助,欢迎点赞、评论、分享,你的支持是我继续创作的动力！ </p></blockquote>]]></content>
    
    
    <summary type="html">详细介绍如何在Hexo博客中实现随机文章功能,包括创建模板文件、配置路由、添加菜单项等完整步骤,帮助你的博客增添趣味性。</summary>
    
    
    
    <category term="Web开发" scheme="https://www.hzv5.cn/categories/Web%E5%BC%80%E5%8F%91/"/>
    
    <category term="Hexo" scheme="https://www.hzv5.cn/categories/Web%E5%BC%80%E5%8F%91/Hexo/"/>
    
    
    <category term="Hexo" scheme="https://www.hzv5.cn/tags/Hexo/"/>
    
    <category term="博客优化" scheme="https://www.hzv5.cn/tags/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96/"/>
    
    <category term="JavaScript" scheme="https://www.hzv5.cn/tags/JavaScript/"/>
    
    <category term="前端开发" scheme="https://www.hzv5.cn/tags/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>QQ频道机器人Android客户端使用指南</title>
    <link href="https://www.hzv5.cn/2024/12/18/qqch-bot-readme/"/>
    <id>https://www.hzv5.cn/2024/12/18/qqch-bot-readme/</id>
    <published>2024-12-18T18:58:36.000Z</published>
    <updated>2026-07-08T19:23:36.329Z</updated>
    
    <content type="html"><![CDATA[<h1 id="QQ频道机器人-Android客户端"><a href="#QQ频道机器人-Android客户端" class="headerlink" title="QQ频道机器人 Android客户端"></a>QQ频道机器人 Android客户端</h1><p>一个用于管理和操作QQ频道机器人的Android客户端应用，基于uniapp开发，提供了直观的用户界面和丰富的功能特性。</p><h2 id="客户端界面预览"><a href="#客户端界面预览" class="headerlink" title="客户端界面预览"></a>客户端界面预览</h2><p><img src="/img/qqBot/login_chat.png" alt="登录频道聊天查看和发送消息"></p><p><img src="/img/qqBot/plugin_setting_log.png" alt="插件功能运行日志和设置"></p><h2 id="功能特性"><a href="#功能特性" class="headerlink" title="功能特性"></a>功能特性</h2><h3 id="1-账号管理"><a href="#1-账号管理" class="headerlink" title="1. 账号管理"></a>1. 账号管理</h3><ul><li>App ID和Token登录</li><li>记住登录状态</li><li>自动登录选项</li><li>沙盒/正式环境切换</li><li>多账号管理</li><li>账号安全保护</li></ul><h3 id="2-频道管理"><a href="#2-频道管理" class="headerlink" title="2. 频道管理"></a>2. 频道管理</h3><ul><li>频道列表显示<ul><li>分组展示</li><li>在线人数显示</li><li>频道类型图标</li><li>频道状态标识</li></ul></li><li>未读消息统计<ul><li>普通消息计数</li><li>@消息特别标记</li><li>按频道分类统计</li></ul></li><li>频道权限管理<ul><li>自动识别管理员权限</li><li>权限值实时计算</li><li>权限检查功能</li></ul></li></ul><h3 id="3-消息功能"><a href="#3-消息功能" class="headerlink" title="3. 消息功能"></a>3. 消息功能</h3><ul><li><p>实时消息接收</p><span id="more"></span><ul><li>WebSocket长连接</li><li>断线自动重连</li><li>心跳保活机制</li></ul></li><li><p>消息发送功能</p><ul><li>文本消息支持</li><li>消息审核处理</li><li>审核状态显示</li></ul></li><li><p>消息管理</p><ul><li>消息撤回</li><li>历史记录查看</li><li>系统消息显示</li><li>在线状态更新</li></ul></li><li><p>消息展示</p><ul><li>时间戳显示</li><li>用户身份标识</li><li>消息状态图标</li><li>长按菜单操作</li></ul></li></ul><h3 id="4-插件系统"><a href="#4-插件系统" class="headerlink" title="4. 插件系统"></a>4. 插件系统</h3><ul><li>插件管理<ul><li>启用/禁用控制</li><li>本地存储支持</li><li>配置项管理</li></ul></li><li>插件功能<ul><li>命令处理</li><li>数据持久化</li><li>日志记录</li><li>API调用</li></ul></li><li>内置插件<ul><li>每日签到</li><li>百度百科</li></ul></li><li>开发支持<ul><li>完整API文档</li><li>示例代码</li><li>开发指南</li></ul></li></ul><h3 id="5-通知系统"><a href="#5-通知系统" class="headerlink" title="5. 通知系统"></a>5. 通知系统</h3><ul><li>消息通知<ul><li>新消息提醒</li><li>@消息特殊提示</li><li>消息合并显示</li><li>通知延迟处理</li></ul></li><li>通知管理<ul><li>是否开启通知</li><li>是否震动提醒</li><li>是否系统通知</li><li>免打扰设置</li></ul></li><li>通知优化<ul><li>消息去重</li><li>批量处理</li><li>智能延迟</li><li>性能优化</li></ul></li></ul><h2 id="系统要求"><a href="#系统要求" class="headerlink" title="系统要求"></a>系统要求</h2><h3 id="基本要求"><a href="#基本要求" class="headerlink" title="基本要求"></a>基本要求</h3><ul><li>Android 5.0+</li><li>网络连接</li><li>存储空间 &gt;= 50MB</li><li>通知权限(可选)</li><li>后台运行权限(推荐)</li></ul><h3 id="权限说明"><a href="#权限说明" class="headerlink" title="权限说明"></a>权限说明</h3><ol><li><p>必需权限</p><ul><li>网络访问</li><li>存储读写</li><li>后台运行</li></ul></li><li><p>可选权限</p><ul><li>通知显示</li><li>开机自启</li><li>震动控制</li></ul></li></ol><h2 id="安装说明"><a href="#安装说明" class="headerlink" title="安装说明"></a>安装说明</h2><ol><li><p>下载安装</p><ul><li>从Release页面下载最新APK</li><li>允许安装未知来源应用</li><li>按提示完成安装</li></ul></li><li><p>首次配置</p><ul><li>授予必要权限</li><li>登录机器人账号</li><li>配置基本设置</li></ul></li></ol><h2 id="使用指南"><a href="#使用指南" class="headerlink" title="使用指南"></a>使用指南</h2><h3 id="1-登录配置"><a href="#1-登录配置" class="headerlink" title="1. 登录配置"></a>1. 登录配置</h3><h4 id="1-1-获取凭据"><a href="#1-1-获取凭据" class="headerlink" title="1.1 获取凭据"></a>1.1 获取凭据</h4><ol><li>访问<a href="https://q.qq.com">QQ机器人管理平台</a></li><li>创建/选择机器人</li><li>获取App ID和Token</li><li>确认机器人状态正常</li></ol><h4 id="1-2-登录设置"><a href="#1-2-登录设置" class="headerlink" title="1.2 登录设置"></a>1.2 登录设置</h4><ol><li>输入App ID和Token</li><li>选择环境(沙盒/正式)</li><li>设置自动登录(可选)</li><li>配置安全选项</li></ol><h3 id="2-频道操作"><a href="#2-频道操作" class="headerlink" title="2. 频道操作"></a>2. 频道操作</h3><h4 id="2-1-查看频道"><a href="#2-1-查看频道" class="headerlink" title="2.1 查看频道"></a>2.1 查看频道</h4><ol><li><p>打开频道列表</p><ul><li>点击顶部频道信息展开列表</li><li>查看分组和子频道</li><li>观察在线人数和状态</li></ul></li><li><p>频道分类</p><ul><li>文字频道(#)</li><li>语音频道(🔊)</li><li>直播频道(📺)</li><li>公告频道(📢)</li><li>应用频道(💬)</li><li>论坛频道(🎮)</li></ul></li><li><p>未读消息</p><ul><li>红点显示未读数</li><li>橙色标记@消息</li><li>点击清除未读</li></ul></li></ol><h4 id="2-2-消息管理"><a href="#2-2-消息管理" class="headerlink" title="2.2 消息管理"></a>2.2 消息管理</h4><ol><li><p>发送消息</p><ul><li>在输入框输入内容</li><li>点击发送按钮</li><li>等待发送状态</li><li>查看审核结果</li></ul></li><li><p>消息操作</p><ul><li>长按消息显示菜单</li><li>选择撤回等操作</li><li>查看消息详情</li></ul></li><li><p>消息状态</p><ul><li>正常消息</li><li>审核中(灰色)</li><li>审核拒绝(红色)</li><li>已撤回(灰色斜体)</li></ul></li></ol><h3 id="3-插件使用"><a href="#3-插件使用" class="headerlink" title="3. 插件使用"></a>3. 插件使用</h3><h4 id="3-1-插件管理"><a href="#3-1-插件管理" class="headerlink" title="3.1 插件管理"></a>3.1 插件管理</h4><ol><li><p>内置插件</p><ul><li>每日签到(/签到)</li><li>百度百科(/百科)</li></ul></li><li><p>插件控制</p><ul><li>启用/禁用插件</li><li>查看插件信息</li><li>管理插件数据</li></ul></li></ol><h4 id="3-2-插件命令"><a href="#3-2-插件命令" class="headerlink" title="3.2 插件命令"></a>3.2 插件命令</h4><ol><li><p>命令格式</p><ul><li>以/开头</li><li>严格匹配命令名</li><li>空格分隔参数</li></ul></li><li><p>使用示例</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/签到              # 执行签到</span><br><span class="line">/百科 太阳         # 查询百科</span><br></pre></td></tr></table></figure></li></ol><h3 id="4-通知设置"><a href="#4-通知设置" class="headerlink" title="4. 通知设置"></a>4. 通知设置</h3><h4 id="4-1-通知选项"><a href="#4-1-通知选项" class="headerlink" title="4.1 通知选项"></a>4.1 通知选项</h4><ol><li><p>基本设置</p><ul><li>启用通知</li><li>震动提醒</li><li>系统通知</li></ul></li><li><p>通知规则</p><ul><li>新消息延迟2秒</li><li>多条消息合并</li><li>@消息立即提醒</li><li>非当前频道通知</li></ul></li></ol><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="1-连接问题"><a href="#1-连接问题" class="headerlink" title="1. 连接问题"></a>1. 连接问题</h3><ul><li>问题: 无法连接或频繁断开</li><li>解决: <ol><li>检查网络连接</li><li>确认Token正确</li><li>查看机器人状态</li><li>等待自动重连</li></ol></li></ul><h3 id="2-消息问题"><a href="#2-消息问题" class="headerlink" title="2. 消息问题"></a>2. 消息问题</h3><ul><li>问题: 消息发送失败</li><li>解决:<ol><li>检查频道权限</li><li>确认消息格式</li><li>等待审核通过</li><li>查看错误提示</li></ol></li></ul><h3 id="3-插件问题"><a href="#3-插件问题" class="headerlink" title="3. 插件问题"></a>3. 插件问题</h3><ul><li>问题: 插件无响应</li><li>解决:<ol><li>检查插件状态</li><li>确认命令格式</li><li>查看错误日志</li><li>重启应用</li></ol></li></ul><h3 id="4-通知问题"><a href="#4-通知问题" class="headerlink" title="4. 通知问题"></a>4. 通知问题</h3><ul><li>问题: 收不到通知</li><li>解决:<ol><li>检查通知权限</li><li>确认通知设置</li><li>关闭省电模式</li><li>允许后台运行</li></ol></li></ul><h2 id="更新日志"><a href="#更新日志" class="headerlink" title="更新日志"></a>更新日志</h2><h3 id="v1-1-0-2024-01-20"><a href="#v1-1-0-2024-01-20" class="headerlink" title="v1.1.0 (2024-01-20)"></a>v1.1.0 (2024-01-20)</h3><ul><li>优化WebSocket连接</li><li>改进消息处理逻辑</li><li>完善插件系统</li><li>优化通知机制</li><li>修复已知问题</li></ul><h3 id="v1-0-0-2023-12-25"><a href="#v1-0-0-2023-12-25" class="headerlink" title="v1.0.0 (2023-12-25)"></a>v1.0.0 (2023-12-25)</h3><ul><li>首次发布</li><li>基础功能实现</li><li>插件系统支持</li><li>通知系统集成</li></ul><h2 id="更多待补充"><a href="#更多待补充" class="headerlink" title="更多待补充"></a>更多待补充</h2><p>么么哒~~</p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;QQ频道机器人-Android客户端&quot;&gt;&lt;a href=&quot;#QQ频道机器人-Android客户端&quot; class=&quot;headerlink&quot; title=&quot;QQ频道机器人 Android客户端&quot;&gt;&lt;/a&gt;QQ频道机器人 Android客户端&lt;/h1&gt;&lt;p&gt;一个用于管理和操作QQ频道机器人的Android客户端应用，基于uniapp开发，提供了直观的用户界面和丰富的功能特性。&lt;/p&gt;
&lt;h2 id=&quot;客户端界面预览&quot;&gt;&lt;a href=&quot;#客户端界面预览&quot; class=&quot;headerlink&quot; title=&quot;客户端界面预览&quot;&gt;&lt;/a&gt;客户端界面预览&lt;/h2&gt;&lt;p&gt;&lt;img src=&quot;/img/qqBot/login_chat.png&quot; alt=&quot;登录频道聊天查看和发送消息&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/qqBot/plugin_setting_log.png&quot; alt=&quot;插件功能运行日志和设置&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;功能特性&quot;&gt;&lt;a href=&quot;#功能特性&quot; class=&quot;headerlink&quot; title=&quot;功能特性&quot;&gt;&lt;/a&gt;功能特性&lt;/h2&gt;&lt;h3 id=&quot;1-账号管理&quot;&gt;&lt;a href=&quot;#1-账号管理&quot; class=&quot;headerlink&quot; title=&quot;1. 账号管理&quot;&gt;&lt;/a&gt;1. 账号管理&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;App ID和Token登录&lt;/li&gt;
&lt;li&gt;记住登录状态&lt;/li&gt;
&lt;li&gt;自动登录选项&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;h3 id=&quot;2-频道管理&quot;&gt;&lt;a href=&quot;#2-频道管理&quot; class=&quot;headerlink&quot; title=&quot;2. 频道管理&quot;&gt;&lt;/a&gt;2. 频道管理&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;频道列表显示&lt;ul&gt;
&lt;li&gt;分组展示&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;/li&gt;
&lt;li&gt;未读消息统计&lt;ul&gt;
&lt;li&gt;普通消息计数&lt;/li&gt;
&lt;li&gt;@消息特别标记&lt;/li&gt;
&lt;li&gt;按频道分类统计&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;频道权限管理&lt;ul&gt;
&lt;li&gt;自动识别管理员权限&lt;/li&gt;
&lt;li&gt;权限值实时计算&lt;/li&gt;
&lt;li&gt;权限检查功能&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-消息功能&quot;&gt;&lt;a href=&quot;#3-消息功能&quot; class=&quot;headerlink&quot; title=&quot;3. 消息功能&quot;&gt;&lt;/a&gt;3. 消息功能&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;实时消息接收&lt;/p&gt;</summary>
    
    
    
    <category term="知识点" scheme="https://www.hzv5.cn/categories/%E7%9F%A5%E8%AF%86%E7%82%B9/"/>
    
    
    <category term="笔记" scheme="https://www.hzv5.cn/tags/%E7%AC%94%E8%AE%B0/"/>
    
    <category term="QQ机器人" scheme="https://www.hzv5.cn/tags/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/"/>
    
    <category term="QQ" scheme="https://www.hzv5.cn/tags/QQ/"/>
    
    <category term="QQ频道" scheme="https://www.hzv5.cn/tags/QQ%E9%A2%91%E9%81%93/"/>
    
    <category term="使用文档" scheme="https://www.hzv5.cn/tags/%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3/"/>
    
  </entry>
  
  <entry>
    <title>安卓版QQ频道机器人APP客户端插件开发指南</title>
    <link href="https://www.hzv5.cn/2024/12/18/qqch-bot-plugin/"/>
    <id>https://www.hzv5.cn/2024/12/18/qqch-bot-plugin/</id>
    <published>2024-12-18T17:50:56.000Z</published>
    <updated>2026-07-08T19:23:36.329Z</updated>
    
    <content type="html"><![CDATA[<p>本文档将指导您如何为QQ频道机器人客户端开发插件。</p><h2 id="插件结构"><a href="#插件结构" class="headerlink" title="插件结构"></a>插件结构</h2><p>每个插件都是一个标准的 JavaScript 文件，需要导出一个包含以下字段的对象:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 插件对象</span></span><br><span class="line"><span class="keyword">const</span> plugin = &#123;</span><br><span class="line">  <span class="comment">// 基本信息(必需)</span></span><br><span class="line">  <span class="attr">id</span>: <span class="string">&#x27;plugin-id&#x27;</span>,          <span class="comment">// 插件ID,只能包含小写字母、数字、下划线和横线</span></span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;插件名称&#x27;</span>,         <span class="comment">// 插件显示名称</span></span><br><span class="line">  <span class="attr">description</span>: <span class="string">&#x27;插件描述&#x27;</span>,  <span class="comment">// 插件功能描述</span></span><br><span class="line">  <span class="attr">command</span>: <span class="string">&#x27;/命令&#x27;</span>,         <span class="comment">// 触发命令,必须以/开头</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 基本信息(可选)</span></span><br><span class="line">  <span class="attr">version</span>: <span class="string">&#x27;1.0.0&#x27;</span>,        <span class="comment">// 插件版本号</span></span><br><span class="line">  <span class="attr">author</span>: <span class="string">&#x27;作者名称&#x27;</span>,      <span class="comment">// 插件作者</span></span><br><span class="line">  <span class="attr">enabled</span>: <span class="literal">true</span>,           <span class="comment">// 插件是否启用</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 配置项(可选)</span></span><br><span class="line">  <span class="attr">config</span>: &#123;</span><br><span class="line">    <span class="comment">// 插件的配置项</span></span><br><span class="line">    <span class="attr">minPoints</span>: <span class="number">10</span>,         <span class="comment">// 示例:最小积分</span></span><br><span class="line">    <span class="attr">maxPoints</span>: <span class="number">100</span>,        <span class="comment">// 示例:最大积分</span></span><br><span class="line">  &#125;,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 数据存储(可选)</span></span><br><span class="line">  <span class="attr">storage</span>: &#123;</span><br><span class="line">    <span class="comment">// 插件的数据存储</span></span><br><span class="line">    <span class="attr">records</span>: <span class="keyword">new</span> <span class="title class_">Map</span>()     <span class="comment">// 示例:记录存储</span></span><br><span class="line">  &#125;,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 消息处理函数(必需)</span></span><br><span class="line">  <span class="attr">handler</span>: <span class="title function_">async</span> (message, channel, context) =&gt; &#123;</span><br><span class="line">    <span class="comment">// 返回值:</span></span><br><span class="line">    <span class="comment">// - 字符串: 要发送的回复消息</span></span><br><span class="line">    <span class="comment">// - null/undefined: 不发送回复</span></span><br><span class="line">    <span class="comment">// - Promise&lt;string&gt;: 异步返回回复消息</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;回复消息&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 导出插件</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = plugin</span><br></pre></td></tr></table></figure><h2 id="API-说明"><a href="#API-说明" class="headerlink" title="API 说明"></a>API 说明</h2><h3 id="1-消息对象-message"><a href="#1-消息对象-message" class="headerlink" title="1. 消息对象(message)"></a>1. 消息对象(message)</h3>  <span id="more"></span><p>消息对象包含了消息的完整信息：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">id</span>: <span class="string">&quot;消息ID&quot;</span>,</span><br><span class="line">  <span class="attr">channel_id</span>: <span class="string">&quot;频道ID&quot;</span>, </span><br><span class="line">  <span class="attr">guild_id</span>: <span class="string">&quot;服务器ID&quot;</span>,</span><br><span class="line">  <span class="attr">content</span>: <span class="string">&quot;消息内容&quot;</span>,</span><br><span class="line">  <span class="attr">cleanContent</span>: <span class="string">&quot;去除命令前缀的消息内容&quot;</span>, <span class="comment">// 新增:清理后的内容</span></span><br><span class="line">  <span class="attr">timestamp</span>: <span class="string">&quot;2023-01-01T00:00:00.000Z&quot;</span>,</span><br><span class="line">  <span class="attr">status</span>: <span class="string">&quot;normal&quot;</span>, <span class="comment">// 消息状态: normal-正常, auditing-审核中, rejected-审核拒绝</span></span><br><span class="line">  <span class="attr">author</span>: &#123;</span><br><span class="line">    <span class="attr">id</span>: <span class="string">&quot;用户ID&quot;</span>,</span><br><span class="line">    <span class="attr">username</span>: <span class="string">&quot;用户名&quot;</span>,</span><br><span class="line">    <span class="attr">bot</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">avatar</span>: <span class="string">&quot;头像URL&quot;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">member</span>: &#123;</span><br><span class="line">    <span class="attr">roles</span>: [<span class="string">&quot;角色ID1&quot;</span>, <span class="string">&quot;角色ID2&quot;</span>],</span><br><span class="line">    <span class="attr">joined_at</span>: <span class="string">&quot;加入时间&quot;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mentions</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">id</span>: <span class="string">&quot;被@用户ID&quot;</span>,</span><br><span class="line">      <span class="attr">username</span>: <span class="string">&quot;被@用户名&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">mention_everyone</span>: <span class="literal">false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-频道对象-channel"><a href="#2-频道对象-channel" class="headerlink" title="2. 频道对象(channel)"></a>2. 频道对象(channel)</h3><p>频道对象包含了频道的基本信息：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">id</span>: <span class="string">&quot;频道ID&quot;</span>,</span><br><span class="line">  <span class="attr">guild_id</span>: <span class="string">&quot;服务器ID&quot;</span>,</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;频道名称&quot;</span>,</span><br><span class="line">  <span class="attr">type</span>: <span class="number">0</span>, <span class="comment">// 频道类型</span></span><br><span class="line">  <span class="attr">position</span>: <span class="number">1</span>, <span class="comment">// 显示位置</span></span><br><span class="line">  <span class="attr">parent_id</span>: <span class="string">&quot;父频道ID&quot;</span>,</span><br><span class="line">  <span class="attr">owner_id</span>: <span class="string">&quot;创建者ID&quot;</span>,</span><br><span class="line">  <span class="attr">permissions</span>: <span class="string">&quot;权限值&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-上下文对象-context"><a href="#3-上下文对象-context" class="headerlink" title="3. 上下文对象(context)"></a>3. 上下文对象(context)</h3><p>上下文对象提供了插件运行时的环境和工具：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="comment">// 存储API - 持久化存储插件数据</span></span><br><span class="line">  <span class="attr">storage</span>: &#123;</span><br><span class="line">    <span class="attr">get</span>: <span class="function">(<span class="params">key</span>) =&gt;</span> value, <span class="comment">// 获取存储的值</span></span><br><span class="line">    <span class="attr">set</span>: <span class="function">(<span class="params">key, value</span>) =&gt;</span> <span class="keyword">void</span>, <span class="comment">// 设置存储的值</span></span><br><span class="line">    <span class="attr">delete</span>: <span class="function">(<span class="params">key</span>) =&gt;</span> <span class="keyword">void</span>, <span class="comment">// 删除存储的值</span></span><br><span class="line">    <span class="attr">clear</span>: <span class="function">() =&gt;</span> <span class="keyword">void</span>, <span class="comment">// 清空存储</span></span><br><span class="line">    <span class="attr">has</span>: <span class="function">(<span class="params">key</span>) =&gt;</span> boolean <span class="comment">// 检查键是否存在</span></span><br><span class="line">  &#125;,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 配置API - 管理插件配置</span></span><br><span class="line">  <span class="attr">config</span>: &#123;</span><br><span class="line">    <span class="attr">get</span>: <span class="function">(<span class="params">key</span>) =&gt;</span> value, <span class="comment">// 获取配置项</span></span><br><span class="line">    <span class="attr">set</span>: <span class="function">(<span class="params">key, value</span>) =&gt;</span> <span class="keyword">void</span>, <span class="comment">// 设置配置项</span></span><br><span class="line">  &#125;,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 日志API - 记录插件运行日志</span></span><br><span class="line">  <span class="attr">log</span>: &#123;</span><br><span class="line">    <span class="attr">info</span>: <span class="function">(<span class="params">message, details</span>) =&gt;</span> <span class="keyword">void</span>, <span class="comment">// 记录信息日志</span></span><br><span class="line">    <span class="attr">success</span>: <span class="function">(<span class="params">message, details</span>) =&gt;</span> <span class="keyword">void</span>, <span class="comment">// 记录成功日志</span></span><br><span class="line">    <span class="attr">warning</span>: <span class="function">(<span class="params">message, details</span>) =&gt;</span> <span class="keyword">void</span>, <span class="comment">// 记录警告日志</span></span><br><span class="line">    <span class="attr">error</span>: <span class="function">(<span class="params">message, details</span>) =&gt;</span> <span class="keyword">void</span> <span class="comment">// 记录错误日志</span></span><br><span class="line">  &#125;,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 工具API</span></span><br><span class="line">  <span class="attr">api</span>: &#123;</span><br><span class="line">    <span class="comment">// 发送消息</span></span><br><span class="line">    <span class="attr">sendMessage</span>: <span class="title function_">async</span> (channelId, content, options) =&gt; &#123;</span><br><span class="line">      <span class="comment">// channelId: 频道ID</span></span><br><span class="line">      <span class="comment">// content: 消息内容</span></span><br><span class="line">      <span class="comment">// options: 可选参数</span></span><br><span class="line">      <span class="comment">//   - msg_id: 回复的消息ID</span></span><br><span class="line">      <span class="comment">//   - event_id: 事件ID</span></span><br><span class="line">      <span class="comment">// 返回:</span></span><br><span class="line">      <span class="comment">// - 成功: messageObject</span></span><br><span class="line">      <span class="comment">// - 审核: &#123; code: 304023, data: &#123; message_audit: &#123; audit_id: &quot;xxx&quot; &#125; &#125; &#125;</span></span><br><span class="line">      <span class="keyword">return</span> response</span><br><span class="line">    &#125;,</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 删除消息</span></span><br><span class="line">    <span class="attr">deleteMessage</span>: <span class="title function_">async</span> (channelId, messageId) =&gt; &#123;</span><br><span class="line">      <span class="comment">// channelId: 频道ID</span></span><br><span class="line">      <span class="comment">// messageId: 消息ID</span></span><br><span class="line">      <span class="keyword">return</span> boolean</span><br><span class="line">    &#125;,</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取成员信息</span></span><br><span class="line">    <span class="attr">getMember</span>: <span class="title function_">async</span> (guildId, userId) =&gt; &#123;</span><br><span class="line">      <span class="comment">// guildId: 服务器ID</span></span><br><span class="line">      <span class="comment">// userId: 用户ID</span></span><br><span class="line">      <span class="keyword">return</span> memberObject</span><br><span class="line">    &#125;,</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取服务器角色列表</span></span><br><span class="line">    <span class="attr">getGuildRoles</span>: <span class="title function_">async</span> (guildId) =&gt; &#123;</span><br><span class="line">      <span class="comment">// guildId: 服务器ID</span></span><br><span class="line">      <span class="keyword">return</span> rolesArray</span><br><span class="line">    &#125;,</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 检查权限</span></span><br><span class="line">    <span class="attr">hasPermission</span>: <span class="function">(<span class="params">permissions, permission</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// permissions: 权限值</span></span><br><span class="line">      <span class="comment">// permission: 要检查的权限</span></span><br><span class="line">      <span class="keyword">return</span> boolean</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="插件功能界面预览"><a href="#插件功能界面预览" class="headerlink" title="插件功能界面预览"></a>插件功能界面预览</h2><p><img src="/img/qqBot/log.jpg" alt="插件功能界面"></p><h2 id="消息处理流程"><a href="#消息处理流程" class="headerlink" title="消息处理流程"></a>消息处理流程</h2><ol><li><p>消息接收</p><ul><li>系统接收到新消息时，会检查消息内容是否以配置的命令前缀开始</li><li>解析出命令和参数</li><li>查找所有匹配的已启用插件</li></ul></li><li><p>插件执行</p><ul><li>对每个匹配的插件：<ul><li>创建独立的插件上下文对象</li><li>调用插件的handler函数</li><li>等待处理结果</li></ul></li><li>多个插件可以使用相同的命令</li><li>插件按照加载顺序依次执行</li><li>某个插件执行失败不会影响其他插件</li></ul></li><li><p>响应处理</p><ul><li>每个插件的handler如果返回字符串，系统会自动发送响应消息</li><li>每个响应都是独立的，会分别发送到频道</li><li>发送的消息可能进入审核状态(code: 304023)</li><li>审核通过或拒绝会触发相应的事件</li></ul></li></ol><h2 id="权限处理"><a href="#权限处理" class="headerlink" title="权限处理"></a>权限处理</h2><p>系统会自动计算机器人的权限：</p><ol><li><p>管理员权限</p><ul><li>频道主</li><li>管理员</li><li>超级管理员</li><li>子频道管理员<br>拥有以上角色时自动获得所有权限(1099511627775)</li></ul></li><li><p>普通权限</p><ul><li>根据角色的permissions值按位计算</li><li>多个角色的权限值进行按位或运算</li><li>可以使用api.hasPermission检查特定权限</li></ul></li></ol><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><ol><li><p>错误处理</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">handler</span>: <span class="title function_">async</span> (message, channel, context) =&gt; &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// 处理逻辑</span></span><br><span class="line">    context.<span class="property">log</span>.<span class="title function_">info</span>(<span class="string">&#x27;处理成功&#x27;</span>, &#123; <span class="attr">messageId</span>: message.<span class="property">id</span> &#125;)</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;成功消息&#x27;</span></span><br><span class="line">  &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">    context.<span class="property">log</span>.<span class="title function_">error</span>(<span class="string">&#x27;处理失败&#x27;</span>, &#123; error, <span class="attr">messageId</span>: message.<span class="property">id</span> &#125;)</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;处理失败，请稍后重试&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>数据持久化</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用storage API保存数据</span></span><br><span class="line"><span class="keyword">const</span> records = context.<span class="property">storage</span>.<span class="title function_">get</span>(<span class="string">&#x27;records&#x27;</span>) || &#123;&#125;</span><br><span class="line">records[userId] = &#123; <span class="comment">/* 数据 */</span> &#125;</span><br><span class="line">context.<span class="property">storage</span>.<span class="title function_">set</span>(<span class="string">&#x27;records&#x27;</span>, records)</span><br></pre></td></tr></table></figure></li><li><p>消息审核处理</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> response = <span class="keyword">await</span> context.<span class="property">api</span>.<span class="title function_">sendMessage</span>(channelId, content)</span><br><span class="line"><span class="keyword">if</span> (response.<span class="property">code</span> === <span class="number">304023</span>) &#123;</span><br><span class="line">  context.<span class="property">log</span>.<span class="title function_">info</span>(<span class="string">&#x27;消息进入审核&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">auditId</span>: response.<span class="property">data</span>.<span class="property">message_audit</span>.<span class="property">audit_id</span></span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>权限检查</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!context.<span class="property">api</span>.<span class="title function_">hasPermission</span>(channel.<span class="property">permissions</span>, <span class="string">&#x27;MANAGE_MESSAGES&#x27;</span>)) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&#x27;权限不足，需要管理消息的权限&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><p>么么扎~~</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文档将指导您如何为QQ频道机器人客户端开发插件。&lt;/p&gt;
&lt;h2 id=&quot;插件结构&quot;&gt;&lt;a href=&quot;#插件结构&quot; class=&quot;headerlink&quot; title=&quot;插件结构&quot;&gt;&lt;/a&gt;插件结构&lt;/h2&gt;&lt;p&gt;每个插件都是一个标准的 JavaScript 文件，需要导出一个包含以下字段的对象:&lt;/p&gt;
&lt;figure class=&quot;highlight javascript&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// 插件对象&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; plugin = &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;// 基本信息(必需)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;id&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;#x27;plugin-id&amp;#x27;&lt;/span&gt;,          &lt;span class=&quot;comment&quot;&gt;// 插件ID,只能包含小写字母、数字、下划线和横线&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;name&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;#x27;插件名称&amp;#x27;&lt;/span&gt;,         &lt;span class=&quot;comment&quot;&gt;// 插件显示名称&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;description&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;#x27;插件描述&amp;#x27;&lt;/span&gt;,  &lt;span class=&quot;comment&quot;&gt;// 插件功能描述&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;command&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;#x27;/命令&amp;#x27;&lt;/span&gt;,         &lt;span class=&quot;comment&quot;&gt;// 触发命令,必须以/开头&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;// 基本信息(可选)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;version&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;#x27;1.0.0&amp;#x27;&lt;/span&gt;,        &lt;span class=&quot;comment&quot;&gt;// 插件版本号&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;author&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;#x27;作者名称&amp;#x27;&lt;/span&gt;,      &lt;span class=&quot;comment&quot;&gt;// 插件作者&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;enabled&lt;/span&gt;: &lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;,           &lt;span class=&quot;comment&quot;&gt;// 插件是否启用&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;// 配置项(可选)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;config&lt;/span&gt;: &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 插件的配置项&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;attr&quot;&gt;minPoints&lt;/span&gt;: &lt;span class=&quot;number&quot;&gt;10&lt;/span&gt;,         &lt;span class=&quot;comment&quot;&gt;// 示例:最小积分&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;attr&quot;&gt;maxPoints&lt;/span&gt;: &lt;span class=&quot;number&quot;&gt;100&lt;/span&gt;,        &lt;span class=&quot;comment&quot;&gt;// 示例:最大积分&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;// 数据存储(可选)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;storage&lt;/span&gt;: &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 插件的数据存储&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;attr&quot;&gt;records&lt;/span&gt;: &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Map&lt;/span&gt;()     &lt;span class=&quot;comment&quot;&gt;// 示例:记录存储&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;// 消息处理函数(必需)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;handler&lt;/span&gt;: &lt;span class=&quot;title function_&quot;&gt;async&lt;/span&gt; (message, channel, context) =&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 返回值:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// - 字符串: 要发送的回复消息&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// - null/undefined: 不发送回复&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// - Promise&amp;lt;string&amp;gt;: 异步返回回复消息&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;回复消息&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// 导出插件&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;variable language_&quot;&gt;module&lt;/span&gt;.&lt;span class=&quot;property&quot;&gt;exports&lt;/span&gt; = plugin&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;API-说明&quot;&gt;&lt;a href=&quot;#API-说明&quot; class=&quot;headerlink&quot; title=&quot;API 说明&quot;&gt;&lt;/a&gt;API 说明&lt;/h2&gt;&lt;h3 id=&quot;1-消息对象-message&quot;&gt;&lt;a href=&quot;#1-消息对象-message&quot; class=&quot;headerlink&quot; title=&quot;1. 消息对象(message)&quot;&gt;&lt;/a&gt;1. 消息对象(message)&lt;/h3&gt;</summary>
    
    
    
    <category term="知识点" scheme="https://www.hzv5.cn/categories/%E7%9F%A5%E8%AF%86%E7%82%B9/"/>
    
    
    <category term="笔记" scheme="https://www.hzv5.cn/tags/%E7%AC%94%E8%AE%B0/"/>
    
    <category term="QQ机器人" scheme="https://www.hzv5.cn/tags/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/"/>
    
    <category term="QQ" scheme="https://www.hzv5.cn/tags/QQ/"/>
    
    <category term="QQ频道" scheme="https://www.hzv5.cn/tags/QQ%E9%A2%91%E9%81%93/"/>
    
    <category term="开发文档" scheme="https://www.hzv5.cn/tags/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/"/>
    
  </entry>
  
  <entry>
    <title>2024年AI编程助手深度评测：哪款最适合你？</title>
    <link href="https://www.hzv5.cn/2024/10/15/ai-coding-assistants/"/>
    <id>https://www.hzv5.cn/2024/10/15/ai-coding-assistants/</id>
    <published>2024-10-15T06:30:00.000Z</published>
    <updated>2024-10-15T06:30:00.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>随着AI技术的快速发展，各种AI编程助手层出不穷。本文将深入对比主流AI编程助手的优缺点，帮助你找到最适合自己的AI编程伙伴。</p></blockquote><span id="more"></span><h2 id="1-Cursor-专注高效的AI编程助手"><a href="#1-Cursor-专注高效的AI编程助手" class="headerlink" title="1. Cursor - 专注高效的AI编程助手"></a>1. Cursor - 专注高效的AI编程助手</h2><h3 id="1-1-主要特点"><a href="#1-1-主要特点" class="headerlink" title="1.1 主要特点"></a>1.1 主要特点</h3><ul><li>基于先进的AI模型</li><li>完整的IDE功能</li><li>强大的代码补全和生成能力</li><li>支持多种编程语言</li><li>实时代码分析和建议</li></ul><h3 id="1-2-优点"><a href="#1-2-优点" class="headerlink" title="1.2 优点"></a>1.2 优点</h3><ol><li><p><strong>代码理解能力强</strong></p><ul><li>能准确理解项目上下文</li><li>提供精准的代码建议</li><li>支持复杂代码重构</li></ul></li><li><p><strong>交互体验优秀</strong></p><ul><li>界面简洁直观</li><li>响应速度快</li><li>支持自然语言交互</li></ul></li><li><p><strong>功能完整性</strong></p><ul><li>集成Git版本控制</li><li>支持代码调试</li><li>内置终端功能</li></ul></li></ol><h3 id="1-3-缺点"><a href="#1-3-缺点" class="headerlink" title="1.3 缺点"></a>1.3 缺点</h3><ol><li><p><strong>资源占用较高</strong></p><ul><li>运行需要较好的硬件配置</li><li>启动速度较慢</li></ul></li><li><p><strong>收费较高</strong></p><ul><li>完整功能需要付费订阅</li><li>价格对个人开发者略高</li></ul></li></ol><h2 id="2-CodeGeeX-国产优秀AI编程助手"><a href="#2-CodeGeeX-国产优秀AI编程助手" class="headerlink" title="2. CodeGeeX - 国产优秀AI编程助手"></a>2. CodeGeeX - 国产优秀AI编程助手</h2><h3 id="2-1-主要特点"><a href="#2-1-主要特点" class="headerlink" title="2.1 主要特点"></a>2.1 主要特点</h3><ul><li>支持中文交互</li><li>离线运行能力</li><li>跨平台支持</li><li>多种编辑器插件</li></ul><h3 id="2-2-优点"><a href="#2-2-优点" class="headerlink" title="2.2 优点"></a>2.2 优点</h3><ol><li><p><strong>本地化优势</strong></p><ul><li>完善的中文支持</li><li>符合国内开发习惯</li><li>响应速度快</li></ul></li><li><p><strong>性价比高</strong></p><ul><li>基础功能免费</li><li>企业版价格合理</li><li>教育优惠政策</li></ul></li><li><p><strong>安全性好</strong></p><ul><li>支持私有部署</li><li>代码不出境</li><li>数据安全有保障</li></ul></li></ol><h3 id="2-3-缺点"><a href="#2-3-缺点" class="headerlink" title="2.3 缺点"></a>2.3 缺点</h3><ol><li><p><strong>功能完整度</strong></p><ul><li>部分高级功能不如国外产品</li><li>IDE集成度有待提高</li></ul></li><li><p><strong>模型能力</strong></p><ul><li>在某些专业领域理解不够深入</li><li>代码生成质量略低于顶级产品</li></ul></li></ol><h2 id="3-GitHub-Copilot-最受欢迎的AI助手"><a href="#3-GitHub-Copilot-最受欢迎的AI助手" class="headerlink" title="3. GitHub Copilot - 最受欢迎的AI助手"></a>3. GitHub Copilot - 最受欢迎的AI助手</h2><h3 id="3-1-主要特点"><a href="#3-1-主要特点" class="headerlink" title="3.1 主要特点"></a>3.1 主要特点</h3><ul><li>微软和GitHub联合开发</li><li>基于OpenAI技术</li><li>支持主流编辑器</li><li>强大的代码生成能力</li></ul><h3 id="3-2-优点"><a href="#3-2-优点" class="headerlink" title="3.2 优点"></a>3.2 优点</h3><ol><li><p><strong>代码质量高</strong></p><ul><li>基于海量GitHub代码训练</li><li>生成代码可读性好</li><li>注释详细准确</li></ul></li><li><p><strong>集成度高</strong></p><ul><li>支持VS Code等主流IDE</li><li>操作便捷</li><li>学习成本低</li></ul></li><li><p><strong>更新频繁</strong></p><ul><li>持续改进和优化</li><li>Bug修复及时</li><li>新功能不断</li></ul></li></ol><h3 id="3-3-缺点"><a href="#3-3-缺点" class="headerlink" title="3.3 缺点"></a>3.3 缺点</h3><ol><li><p><strong>价格较贵</strong></p><ul><li>月付费用较高</li><li>没有永久授权选项</li></ul></li><li><p><strong>网络依赖</strong></p><ul><li>需要稳定的网络连接</li><li>无法离线使用</li></ul></li></ol><h2 id="4-国内其他AI编程助手"><a href="#4-国内其他AI编程助手" class="headerlink" title="4. 国内其他AI编程助手"></a>4. 国内其他AI编程助手</h2><h3 id="4-1-MarsCode-抖音官方AI编程助手"><a href="#4-1-MarsCode-抖音官方AI编程助手" class="headerlink" title="4.1 MarsCode - 抖音官方AI编程助手"></a>4.1 MarsCode - 抖音官方AI编程助手</h3><p><strong>主要特点：</strong></p><ul><li>字节跳动官方出品</li><li>基于先进的CodeFuse大模型</li><li>支持VSCode、JetBrains等主流IDE</li><li>提供Web版在线使用</li><li>完全免费使用</li></ul><p><strong>优点：</strong></p><ol><li><p><strong>强大的代码生成能力</strong></p><ul><li>基于海量字节跳动内部代码训练</li><li>支持40+种主流编程语言</li><li>代码质量和可用性较高</li><li>支持完整函数和类的生成</li></ul></li><li><p><strong>智能交互体验</strong></p><ul><li>支持自然语言对话</li><li>提供代码解释和优化建议</li><li>实时代码补全</li><li>多轮对话上下文理解</li></ul></li><li><p><strong>便捷的使用方式</strong></p><ul><li>完全免费，无需付费</li><li>支持Web端直接使用</li><li>IDE插件集成便捷</li><li>快速的响应速度</li></ul></li><li><p><strong>中文支持优秀</strong></p><ul><li>完善的中文交互</li><li>准确的中文需求理解</li><li>详细的中文注释生成</li><li>符合国内开发习惯</li></ul></li></ol><p><strong>缺点：</strong></p><ol><li><p><strong>功能限制</strong></p><ul><li>部分高级功能尚未开放</li><li>代码分析深度有限</li><li>重构功能相对基础</li></ul></li><li><p><strong>生态体系</strong></p><ul><li>插件数量较少</li><li>社区资源不够丰富</li><li>文档和教程较少</li></ul></li><li><p><strong>使用限制</strong></p><ul><li>需要抖音账号登录</li><li>可能有API调用限制</li><li>暂不支持私有部署</li></ul></li><li><p><strong>稳定性待提升</strong></p><ul><li>服务偶有波动</li><li>代码生成质量不够稳定</li><li>复杂场景支持有限</li></ul></li></ol><h3 id="4-2-百度智能编程助手"><a href="#4-2-百度智能编程助手" class="headerlink" title="4.2 百度智能编程助手"></a>4.2 百度智能编程助手</h3><p><strong>优点：</strong></p><ul><li>中文支持好</li><li>与百度生态集成</li><li>免费额度大</li></ul><p><strong>缺点：</strong></p><ul><li>功能相对简单</li><li>仅支持部分语言</li><li>社区活跃度低</li></ul><h3 id="4-3-阿里云代码智能助手"><a href="#4-3-阿里云代码智能助手" class="headerlink" title="4.3 阿里云代码智能助手"></a>4.3 阿里云代码智能助手</h3><p><strong>优点：</strong></p><ul><li>阿里云生态集成</li><li>企业级支持</li><li>安全性高</li></ul><p><strong>缺点：</strong></p><ul><li>需要阿里云账号</li><li>收费较高</li><li>通用性不足</li></ul><h2 id="5-选择建议"><a href="#5-选择建议" class="headerlink" title="5. 选择建议"></a>5. 选择建议</h2><h3 id="5-1-不同场景的选择建议"><a href="#5-1-不同场景的选择建议" class="headerlink" title="5.1 不同场景的选择建议"></a>5.1 不同场景的选择建议</h3><ol><li><p><strong>个人开发者</strong></p><ul><li>预算充足：Cursor或GitHub Copilot</li><li>预算有限：CodeGeeX</li><li>中文环境：CodeGeeX或百度智能编程助手</li></ul></li><li><p><strong>企业用户</strong></p><ul><li>注重安全：CodeGeeX企业版</li><li>团队协作：GitHub Copilot商业版</li><li>云生态：阿里云代码智能助手</li></ul></li><li><p><strong>学生用户</strong></p><ul><li>GitHub Copilot（有教育优惠）</li><li>CodeGeeX（免费版够用）</li></ul></li></ol><h3 id="5-2-选择考虑因素"><a href="#5-2-选择考虑因素" class="headerlink" title="5.2 选择考虑因素"></a>5.2 选择考虑因素</h3><ol><li><p><strong>预算</strong></p><ul><li>免费使用需求</li><li>性价比考虑</li><li>长期订阅成本</li></ul></li><li><p><strong>使用场景</strong></p><ul><li>个人/团队使用</li><li>项目规模</li><li>编程语言支持</li></ul></li><li><p><strong>特殊需求</strong></p><ul><li>中文支持</li><li>离线使用</li><li>安全合规</li></ul></li></ol><h2 id="6-使用技巧分享"><a href="#6-使用技巧分享" class="headerlink" title="6. 使用技巧分享"></a>6. 使用技巧分享</h2><h3 id="6-1-提高AI助手效率的技巧"><a href="#6-1-提高AI助手效率的技巧" class="headerlink" title="6.1 提高AI助手效率的技巧"></a>6.1 提高AI助手效率的技巧</h3><ol><li><p><strong>编写清晰的提示</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 好的提示示例</span></span><br><span class="line"><span class="comment">// 实现一个基于Promise的延迟函数，支持取消操作</span></span><br></pre></td></tr></table></figure></li><li><p><strong>善用上下文</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 先说明需求背景</span></span><br><span class="line"><span class="comment">// 再描述具体功能</span></span><br><span class="line"><span class="comment">// 最后指出关键点</span></span><br></pre></td></tr></table></figure></li><li><p><strong>结合文档使用</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 查看官方文档</span></span><br><span class="line"><span class="comment">// 参考示例代码</span></span><br><span class="line"><span class="comment">// 理解实现原理</span></span><br></pre></td></tr></table></figure></li></ol><h3 id="6-2-避免常见误区"><a href="#6-2-避免常见误区" class="headerlink" title="6.2 避免常见误区"></a>6.2 避免常见误区</h3><ol><li><p><strong>过度依赖</strong></p><ul><li>不要完全依赖AI生成代码</li><li>保持代码审查习惯</li><li>理解代码逻辑</li></ul></li><li><p><strong>忽视性能</strong></p><ul><li>注意代码质量</li><li>考虑性能影响</li><li>测试生成代码</li></ul></li></ol><h2 id="7-未来展望"><a href="#7-未来展望" class="headerlink" title="7. 未来展望"></a>7. 未来展望</h2><ol><li><p><strong>技术趋势</strong></p><ul><li>模型能力持续提升</li><li>多模态交互增强</li><li>个性化推荐加强</li></ul></li><li><p><strong>行业影响</strong></p><ul><li>提高开发效率</li><li>降低入门门槛</li><li>改变开发模式</li></ul></li><li><p><strong>注意事项</strong></p><ul><li>保持学习能力</li><li>提高代码素养</li><li>关注安全合规</li></ul></li></ol><h2 id="8-总结"><a href="#8-总结" class="headerlink" title="8. 总结"></a>8. 总结</h2><ol><li>选择合适的AI编程助手很重要</li><li>根据实际需求和场景选择</li><li>合理使用提高开发效率</li><li>避免过度依赖</li><li>持续关注新技术发展</li></ol><h2 id="5-产品对比分析"><a href="#5-产品对比分析" class="headerlink" title="5. 产品对比分析"></a>5. 产品对比分析</h2><h3 id="5-1-功能对比"><a href="#5-1-功能对比" class="headerlink" title="5.1 功能对比"></a>5.1 功能对比</h3><table><thead><tr><th>功能特性</th><th>Cursor</th><th>GitHub Copilot</th><th>CodeGeeX</th><th>MarsCode</th></tr></thead><tbody><tr><td>代码补全</td><td>⭐⭐⭐⭐⭐</td><td>⭐⭐⭐⭐⭐</td><td>⭐⭐⭐⭐</td><td>⭐⭐⭐⭐</td></tr><tr><td>代码生成</td><td>⭐⭐⭐⭐⭐</td><td>⭐⭐⭐⭐⭐</td><td>⭐⭐⭐⭐</td><td>⭐⭐⭐⭐</td></tr><tr><td>中文支持</td><td>⭐⭐⭐</td><td>⭐⭐</td><td>⭐⭐⭐⭐⭐</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td>IDE集成</td><td>⭐⭐⭐⭐⭐</td><td>⭐⭐⭐⭐⭐</td><td>⭐⭐⭐⭐</td><td>⭐⭐⭐</td></tr><tr><td>响应速度</td><td>⭐⭐⭐⭐</td><td>⭐⭐⭐⭐⭐</td><td>⭐⭐⭐⭐</td><td>⭐⭐⭐⭐</td></tr><tr><td>代码质量</td><td>⭐⭐⭐⭐⭐</td><td>⭐⭐⭐⭐⭐</td><td>⭐⭐⭐⭐</td><td>⭐⭐⭐⭐</td></tr></tbody></table><h3 id="5-2-价格对比"><a href="#5-2-价格对比" class="headerlink" title="5.2 价格对比"></a>5.2 价格对比</h3><table><thead><tr><th>产品</th><th>免费版</th><th>个人版</th><th>团队版</th><th>企业版</th></tr></thead><tbody><tr><td>Cursor</td><td>基础功能</td><td>$20/月</td><td>$40/人/月</td><td>联系商务</td></tr><tr><td>GitHub Copilot</td><td>无</td><td>$10/月</td><td>$19/人/月</td><td>联系商务</td></tr><tr><td>CodeGeeX</td><td>完整功能</td><td>免费</td><td>¥299/人/年</td><td>联系商务</td></tr><tr><td>MarsCode</td><td>完整功能</td><td>免费</td><td>免费</td><td>免费</td></tr></tbody></table><h3 id="5-3-适用场景对比"><a href="#5-3-适用场景对比" class="headerlink" title="5.3 适用场景对比"></a>5.3 适用场景对比</h3><ol><li><p><strong>个人开发者</strong></p><ul><li>预算充足：GitHub Copilot &gt; Cursor</li><li>预算有限：MarsCode &gt; CodeGeeX</li><li>中文环境：MarsCode = CodeGeeX &gt; 其他</li></ul></li><li><p><strong>团队使用</strong></p><ul><li>大型企业：GitHub Copilot &gt; Cursor &gt; CodeGeeX</li><li>中小企业：CodeGeeX &gt; MarsCode &gt; 其他</li><li>创业团队：MarsCode &gt; CodeGeeX &gt; 其他</li></ul></li><li><p><strong>特定需求</strong></p><ul><li>离线使用：CodeGeeX &gt; 其他</li><li>私有部署：CodeGeeX &gt; GitHub Copilot</li><li>中文支持：MarsCode = CodeGeeX &gt; 其他</li></ul></li></ol><hr><blockquote><p>如果觉得文章对你有帮助,欢迎点赞、评论、分享,你的支持是我继续创作的动力！ </p></blockquote>]]></content>
    
    
    <summary type="html">深度评测2024年主流AI编程助手，包括Cursor、CodeGeeX等国内外知名工具，从多个维度对比分析各自优缺点，帮助开发者选择最适合的AI编程助手。</summary>
    
    
    
    <category term="技术评测" scheme="https://www.hzv5.cn/categories/%E6%8A%80%E6%9C%AF%E8%AF%84%E6%B5%8B/"/>
    
    <category term="AI工具" scheme="https://www.hzv5.cn/categories/%E6%8A%80%E6%9C%AF%E8%AF%84%E6%B5%8B/AI%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="AI编程" scheme="https://www.hzv5.cn/tags/AI%E7%BC%96%E7%A8%8B/"/>
    
    <category term="开发工具" scheme="https://www.hzv5.cn/tags/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"/>
    
    <category term="Cursor" scheme="https://www.hzv5.cn/tags/Cursor/"/>
    
    <category term="CodeGeeX" scheme="https://www.hzv5.cn/tags/CodeGeeX/"/>
    
    <category term="编程助手" scheme="https://www.hzv5.cn/tags/%E7%BC%96%E7%A8%8B%E5%8A%A9%E6%89%8B/"/>
    
  </entry>
  
  <entry>
    <title>uni-app地图定位踩坑记：地图功能和定位的那些坑</title>
    <link href="https://www.hzv5.cn/2024/06/25/uniapp-map-location/"/>
    <id>https://www.hzv5.cn/2024/06/25/uniapp-map-location/</id>
    <published>2024-06-25T03:40:00.000Z</published>
    <updated>2024-06-25T03:40:00.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>在uni-app开发中，地图和定位功能经常会遇到各种问题。本文总结了一些常见的坑和解决方案，帮助大家更好地实现地图相关功能。</p></blockquote><span id="more"></span><h2 id="1-定位权限问题"><a href="#1-定位权限问题" class="headerlink" title="1. 定位权限问题"></a>1. 定位权限问题</h2><h3 id="1-1-获取定位权限"><a href="#1-1-获取定位权限" class="headerlink" title="1.1 获取定位权限"></a>1.1 获取定位权限</h3><p><strong>问题描述：</strong><br>用户拒绝定位权限后，无法正常使用定位功能。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 检查定位权限</span></span><br><span class="line"><span class="keyword">async</span> <span class="title function_">checkLocationAuth</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> res = <span class="keyword">await</span> uni.<span class="title function_">getSetting</span>()</span><br><span class="line">    <span class="keyword">if</span> (!res.<span class="property">authSetting</span>[<span class="string">&#x27;scope.userLocation&#x27;</span>]) &#123;</span><br><span class="line">      <span class="comment">// 未授权，引导用户开启</span></span><br><span class="line">      <span class="keyword">await</span> uni.<span class="title function_">showModal</span>(&#123;</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;提示&#x27;</span>,</span><br><span class="line">        <span class="attr">content</span>: <span class="string">&#x27;需要获取您的位置信息，是否授权？&#x27;</span>,</span><br><span class="line">        <span class="attr">success</span>: <span class="title function_">async</span> (res) =&gt; &#123;</span><br><span class="line">          <span class="keyword">if</span> (res.<span class="property">confirm</span>) &#123;</span><br><span class="line">            <span class="keyword">await</span> uni.<span class="title function_">openSetting</span>()</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res.<span class="property">authSetting</span>[<span class="string">&#x27;scope.userLocation&#x27;</span>]</span><br><span class="line">  &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;获取权限失败：&#x27;</span>, e)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-2-定位失败处理"><a href="#1-2-定位失败处理" class="headerlink" title="1.2 定位失败处理"></a>1.2 定位失败处理</h3><p><strong>问题描述：</strong><br>定位失败时没有合适的提示和处理。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取位置信息</span></span><br><span class="line"><span class="keyword">async</span> <span class="title function_">getLocation</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> auth = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">checkLocationAuth</span>()</span><br><span class="line">    <span class="keyword">if</span> (!auth) &#123;</span><br><span class="line">      uni.<span class="title function_">showToast</span>(&#123;</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;请先开启定位权限&#x27;</span>,</span><br><span class="line">        <span class="attr">icon</span>: <span class="string">&#x27;none&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">const</span> location = <span class="keyword">await</span> uni.<span class="title function_">getLocation</span>(&#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">&#x27;gcj02&#x27;</span>, <span class="comment">// gcj02火星坐标系</span></span><br><span class="line">      <span class="attr">isHighAccuracy</span>: <span class="literal">true</span>, <span class="comment">// 开启高精度定位</span></span><br><span class="line">      <span class="attr">highAccuracyExpireTime</span>: <span class="number">3000</span> <span class="comment">// 超时时间</span></span><br><span class="line">    &#125;)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> location</span><br><span class="line">  &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">    <span class="keyword">let</span> message = <span class="string">&#x27;定位失败&#x27;</span></span><br><span class="line">    <span class="keyword">switch</span> (e.<span class="property">errMsg</span>) &#123;</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&#x27;getLocation:fail system permission denied&#x27;</span>:</span><br><span class="line">        message = <span class="string">&#x27;系统定位权限已关闭&#x27;</span></span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">      <span class="keyword">case</span> <span class="string">&#x27;getLocation:fail timeout&#x27;</span>:</span><br><span class="line">        message = <span class="string">&#x27;定位超时，请重试&#x27;</span></span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">      <span class="attr">default</span>:</span><br><span class="line">        message = <span class="string">&#x27;定位失败，请检查网络&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    uni.<span class="title function_">showToast</span>(&#123;</span><br><span class="line">      <span class="attr">title</span>: message,</span><br><span class="line">      <span class="attr">icon</span>: <span class="string">&#x27;none&#x27;</span></span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="2-地图显示问题"><a href="#2-地图显示问题" class="headerlink" title="2. 地图显示问题"></a>2. 地图显示问题</h2><h3 id="2-1-地图加载失败"><a href="#2-1-地图加载失败" class="headerlink" title="2.1 地图加载失败"></a>2.1 地图加载失败</h3><p><strong>问题描述：</strong><br>地图组件加载失败或显示空白。</p><p><strong>解决方案：</strong></p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 地图组件使用 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;map-container&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">map</span></span></span><br><span class="line"><span class="tag">      <span class="attr">id</span>=<span class="string">&quot;map&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">:style</span>=<span class="string">&quot;mapStyle&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">:latitude</span>=<span class="string">&quot;latitude&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">:longitude</span>=<span class="string">&quot;longitude&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">:markers</span>=<span class="string">&quot;markers&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">:scale</span>=<span class="string">&quot;scale&quot;</span></span></span><br><span class="line"><span class="tag">      @<span class="attr">regionchange</span>=<span class="string">&quot;onRegionChange&quot;</span></span></span><br><span class="line"><span class="tag">      @<span class="attr">markertap</span>=<span class="string">&quot;onMarkerTap&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">:show-location</span>=<span class="string">&quot;true&quot;</span></span></span><br><span class="line"><span class="tag">    &gt;</span><span class="tag">&lt;/<span class="name">map</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">view</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">data</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">mapStyle</span>: <span class="string">&#x27;width: 100%; height: 300px;&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">latitude</span>: <span class="number">0</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">longitude</span>: <span class="number">0</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">markers</span>: [],</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">scale</span>: <span class="number">16</span></span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">async</span> <span class="title function_">onReady</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 等待地图组件创建完成</span></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">this</span>.<span class="property">mapContext</span> = uni.<span class="title function_">createMapContext</span>(<span class="string">&#x27;map&#x27;</span>, <span class="variable language_">this</span>)</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">initMap</span>()</span></span><br><span class="line"><span class="language-javascript">  &#125;,</span></span><br><span class="line"><span class="language-javascript">  <span class="attr">methods</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">async</span> <span class="title function_">initMap</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">try</span> &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> location = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">getLocation</span>()</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">if</span> (location) &#123;</span></span><br><span class="line"><span class="language-javascript">          <span class="variable language_">this</span>.<span class="property">latitude</span> = location.<span class="property">latitude</span></span></span><br><span class="line"><span class="language-javascript">          <span class="variable language_">this</span>.<span class="property">longitude</span> = location.<span class="property">longitude</span></span></span><br><span class="line"><span class="language-javascript">          <span class="comment">// 添加当前位置标记</span></span></span><br><span class="line"><span class="language-javascript">          <span class="variable language_">this</span>.<span class="title function_">addMarker</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="attr">id</span>: <span class="number">0</span>,</span></span><br><span class="line"><span class="language-javascript">            <span class="attr">latitude</span>: location.<span class="property">latitude</span>,</span></span><br><span class="line"><span class="language-javascript">            <span class="attr">longitude</span>: location.<span class="property">longitude</span>,</span></span><br><span class="line"><span class="language-javascript">            <span class="attr">title</span>: <span class="string">&#x27;当前位置&#x27;</span></span></span><br><span class="line"><span class="language-javascript">          &#125;)</span></span><br><span class="line"><span class="language-javascript">        &#125;</span></span><br><span class="line"><span class="language-javascript">      &#125; <span class="keyword">catch</span> (e) &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;初始化地图失败：&#x27;</span>, e)</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="2-2-标记点显示问题"><a href="#2-2-标记点显示问题" class="headerlink" title="2.2 标记点显示问题"></a>2.2 标记点显示问题</h3><p><strong>问题描述：</strong><br>自定义标记点样式不生效或显示异常。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 添加自定义标记</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">addMarker</span>(<span class="params">point</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> marker = &#123;</span><br><span class="line">    <span class="attr">id</span>: point.<span class="property">id</span>,</span><br><span class="line">    <span class="attr">latitude</span>: point.<span class="property">latitude</span>,</span><br><span class="line">    <span class="attr">longitude</span>: point.<span class="property">longitude</span>,</span><br><span class="line">    <span class="attr">title</span>: point.<span class="property">title</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="number">32</span>,</span><br><span class="line">    <span class="attr">height</span>: <span class="number">32</span>,</span><br><span class="line">    <span class="attr">callout</span>: &#123;</span><br><span class="line">      <span class="attr">content</span>: point.<span class="property">title</span>,</span><br><span class="line">      <span class="attr">color</span>: <span class="string">&#x27;#ffffff&#x27;</span>,</span><br><span class="line">      <span class="attr">fontSize</span>: <span class="number">14</span>,</span><br><span class="line">      <span class="attr">borderRadius</span>: <span class="number">4</span>,</span><br><span class="line">      <span class="attr">bgColor</span>: <span class="string">&#x27;#1989fa&#x27;</span>,</span><br><span class="line">      <span class="attr">padding</span>: <span class="number">8</span>,</span><br><span class="line">      <span class="attr">display</span>: <span class="string">&#x27;ALWAYS&#x27;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 自定义图标</span></span><br><span class="line">    <span class="attr">iconPath</span>: point.<span class="property">iconPath</span> || <span class="string">&#x27;/static/images/marker.png&#x27;</span>,</span><br><span class="line">    <span class="comment">// 自定义气泡</span></span><br><span class="line">    <span class="attr">customCallout</span>: &#123;</span><br><span class="line">      <span class="attr">display</span>: <span class="string">&#x27;ALWAYS&#x27;</span>,</span><br><span class="line">      <span class="attr">anchorY</span>: <span class="number">0</span>,</span><br><span class="line">      <span class="attr">anchorX</span>: <span class="number">0</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">markers</span>.<span class="title function_">push</span>(marker)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="3-路线规划问题"><a href="#3-路线规划问题" class="headerlink" title="3. 路线规划问题"></a>3. 路线规划问题</h2><h3 id="3-1-路线绘制错误"><a href="#3-1-路线绘制错误" class="headerlink" title="3.1 路线绘制错误"></a>3.1 路线绘制错误</h3><p><strong>问题描述：</strong><br>路线规划时绘制的路线不准确或无法显示。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 规划路线</span></span><br><span class="line"><span class="keyword">async</span> <span class="title function_">planRoute</span>(<span class="params">start, end</span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// 清除旧路线</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">polyline</span> = []</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取路线规划</span></span><br><span class="line">    <span class="keyword">const</span> route = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">getRoutePoints</span>(start, end)</span><br><span class="line">    <span class="keyword">if</span> (!route) <span class="keyword">return</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 绘制路线</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">polyline</span> = [&#123;</span><br><span class="line">      <span class="attr">points</span>: route,</span><br><span class="line">      <span class="attr">color</span>: <span class="string">&#x27;#1989fa&#x27;</span>,</span><br><span class="line">      <span class="attr">width</span>: <span class="number">4</span>,</span><br><span class="line">      <span class="attr">arrowLine</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">borderColor</span>: <span class="string">&#x27;#ffffff&#x27;</span>,</span><br><span class="line">      <span class="attr">borderWidth</span>: <span class="number">2</span></span><br><span class="line">    &#125;]</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 调整视野以显示整条路线</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">mapContext</span>.<span class="title function_">includePoints</span>(&#123;</span><br><span class="line">      <span class="attr">points</span>: [start, end],</span><br><span class="line">      <span class="attr">padding</span>: [<span class="number">50</span>, <span class="number">50</span>, <span class="number">50</span>, <span class="number">50</span>]</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;规划路线失败：&#x27;</span>, e)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-导航问题"><a href="#3-2-导航问题" class="headerlink" title="3.2 导航问题"></a>3.2 导航问题</h3><p><strong>问题描述：</strong><br>调用导航功能时出现兼容性问题。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 打开导航</span></span><br><span class="line"><span class="keyword">async</span> <span class="title function_">openNavigation</span>(<span class="params">destination</span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// 检查平台</span></span><br><span class="line">    <span class="comment">// #ifdef APP-PLUS</span></span><br><span class="line">    <span class="comment">// APP端使用系统导航</span></span><br><span class="line">    plus.<span class="property">runtime</span>.<span class="title function_">openURL</span>(</span><br><span class="line">      <span class="string">`androidamap://navi?sourceApplication=appname&amp;lat=<span class="subst">$&#123;destination.latitude&#125;</span>&amp;lon=<span class="subst">$&#123;destination.longitude&#125;</span>&amp;dev=0&amp;style=2`</span></span><br><span class="line">    )</span><br><span class="line">    <span class="comment">// #endif</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// #ifdef H5</span></span><br><span class="line">    <span class="comment">// H5端使用Web导航</span></span><br><span class="line">    <span class="variable language_">window</span>.<span class="property">location</span>.<span class="property">href</span> = <span class="string">`https://uri.amap.com/navigation?to=<span class="subst">$&#123;destination.longitude&#125;</span>,<span class="subst">$&#123;destination.latitude&#125;</span>&amp;mode=car&amp;policy=1`</span></span><br><span class="line">    <span class="comment">// #endif</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// #ifdef MP-WEIXIN</span></span><br><span class="line">    <span class="comment">// 小程序端使用微信导航</span></span><br><span class="line">    uni.<span class="title function_">openLocation</span>(&#123;</span><br><span class="line">      <span class="attr">latitude</span>: destination.<span class="property">latitude</span>,</span><br><span class="line">      <span class="attr">longitude</span>: destination.<span class="property">longitude</span>,</span><br><span class="line">      <span class="attr">name</span>: destination.<span class="property">name</span>,</span><br><span class="line">      <span class="attr">address</span>: destination.<span class="property">address</span></span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="comment">// #endif</span></span><br><span class="line">  &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">    uni.<span class="title function_">showToast</span>(&#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;打开导航失败&#x27;</span>,</span><br><span class="line">      <span class="attr">icon</span>: <span class="string">&#x27;none&#x27;</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="4-性能优化问题"><a href="#4-性能优化问题" class="headerlink" title="4. 性能优化问题"></a>4. 性能优化问题</h2><h3 id="4-1-标记点过多"><a href="#4-1-标记点过多" class="headerlink" title="4.1 标记点过多"></a>4.1 标记点过多</h3><p><strong>问题描述：</strong><br>地图上标记点太多导致卡顿。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 聚合标记点</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">clusterMarkers</span>(<span class="params">markers, distance = <span class="number">20</span></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> clusters = []</span><br><span class="line">  <span class="keyword">const</span> used = <span class="keyword">new</span> <span class="title class_">Set</span>()</span><br><span class="line">  </span><br><span class="line">  markers.<span class="title function_">forEach</span>(<span class="function">(<span class="params">marker, index</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (used.<span class="title function_">has</span>(index)) <span class="keyword">return</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">const</span> cluster = &#123;</span><br><span class="line">      <span class="attr">center</span>: &#123;</span><br><span class="line">        <span class="attr">latitude</span>: marker.<span class="property">latitude</span>,</span><br><span class="line">        <span class="attr">longitude</span>: marker.<span class="property">longitude</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">points</span>: [marker]</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 查找附近的点</span></span><br><span class="line">    markers.<span class="title function_">forEach</span>(<span class="function">(<span class="params">m, i</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (i === index || used.<span class="title function_">has</span>(i)) <span class="keyword">return</span></span><br><span class="line">      </span><br><span class="line">      <span class="keyword">const</span> d = <span class="variable language_">this</span>.<span class="title function_">calculateDistance</span>(</span><br><span class="line">        marker.<span class="property">latitude</span>,</span><br><span class="line">        marker.<span class="property">longitude</span>,</span><br><span class="line">        m.<span class="property">latitude</span>,</span><br><span class="line">        m.<span class="property">longitude</span></span><br><span class="line">      )</span><br><span class="line">      </span><br><span class="line">      <span class="keyword">if</span> (d &lt;= distance) &#123;</span><br><span class="line">        cluster.<span class="property">points</span>.<span class="title function_">push</span>(m)</span><br><span class="line">        used.<span class="title function_">add</span>(i)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">    </span><br><span class="line">    clusters.<span class="title function_">push</span>(cluster)</span><br><span class="line">  &#125;)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> clusters</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-频繁更新"><a href="#4-2-频繁更新" class="headerlink" title="4.2 频繁更新"></a>4.2 频繁更新</h3><p><strong>问题描述：</strong><br>频繁更新标记点位置导致性能问题。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用节流更新位置</span></span><br><span class="line"><span class="keyword">const</span> throttleUpdate = <span class="title function_">throttle</span>(<span class="keyword">function</span>(<span class="params">markers</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">markers</span> = markers</span><br><span class="line">&#125;, <span class="number">200</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 更新标记位置</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">updateMarkers</span>(<span class="params">newMarkers</span>) &#123;</span><br><span class="line">  <span class="title function_">throttleUpdate</span>(newMarkers)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 节流函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">throttle</span>(<span class="params">fn, delay</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> timer = <span class="literal">null</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">function</span>(<span class="params">...args</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (timer) <span class="keyword">return</span></span><br><span class="line">    timer = <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      fn.<span class="title function_">apply</span>(<span class="variable language_">this</span>, args)</span><br><span class="line">      timer = <span class="literal">null</span></span><br><span class="line">    &#125;, delay)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="5-实用功能分享"><a href="#5-实用功能分享" class="headerlink" title="5. 实用功能分享"></a>5. 实用功能分享</h2><h3 id="5-1-计算距离"><a href="#5-1-计算距离" class="headerlink" title="5.1 计算距离"></a>5.1 计算距离</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 计算两点距离</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">calculateDistance</span>(<span class="params">lat1, lng1, lat2, lng2</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> R = <span class="number">6371</span> <span class="comment">// 地球半径，单位km</span></span><br><span class="line">  <span class="keyword">const</span> dLat = (lat2 - lat1) * <span class="title class_">Math</span>.<span class="property">PI</span> / <span class="number">180</span></span><br><span class="line">  <span class="keyword">const</span> dLng = (lng2 - lng1) * <span class="title class_">Math</span>.<span class="property">PI</span> / <span class="number">180</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">const</span> a = <span class="title class_">Math</span>.<span class="title function_">sin</span>(dLat/<span class="number">2</span>) * <span class="title class_">Math</span>.<span class="title function_">sin</span>(dLat/<span class="number">2</span>) +</span><br><span class="line">    <span class="title class_">Math</span>.<span class="title function_">cos</span>(lat1 * <span class="title class_">Math</span>.<span class="property">PI</span> / <span class="number">180</span>) * <span class="title class_">Math</span>.<span class="title function_">cos</span>(lat2 * <span class="title class_">Math</span>.<span class="property">PI</span> / <span class="number">180</span>) *</span><br><span class="line">    <span class="title class_">Math</span>.<span class="title function_">sin</span>(dLng/<span class="number">2</span>) * <span class="title class_">Math</span>.<span class="title function_">sin</span>(dLng/<span class="number">2</span>)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">const</span> c = <span class="number">2</span> * <span class="title class_">Math</span>.<span class="title function_">atan2</span>(<span class="title class_">Math</span>.<span class="title function_">sqrt</span>(a), <span class="title class_">Math</span>.<span class="title function_">sqrt</span>(<span class="number">1</span>-a))</span><br><span class="line">  <span class="keyword">return</span> R * c</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-地址解析"><a href="#5-2-地址解析" class="headerlink" title="5.2 地址解析"></a>5.2 地址解析</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 地址转坐标</span></span><br><span class="line"><span class="keyword">async</span> <span class="title function_">geocode</span>(<span class="params">address</span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> res = <span class="keyword">await</span> uni.<span class="title function_">request</span>(&#123;</span><br><span class="line">      <span class="attr">url</span>: <span class="string">&#x27;geocode-api-url&#x27;</span>,</span><br><span class="line">      <span class="attr">data</span>: &#123;</span><br><span class="line">        <span class="attr">address</span>: address,</span><br><span class="line">        <span class="attr">key</span>: <span class="string">&#x27;your-api-key&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (res.<span class="property">data</span>.<span class="property">status</span> === <span class="string">&#x27;1&#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">latitude</span>: res.<span class="property">data</span>.<span class="property">result</span>.<span class="property">location</span>.<span class="property">lat</span>,</span><br><span class="line">        <span class="attr">longitude</span>: res.<span class="property">data</span>.<span class="property">result</span>.<span class="property">location</span>.<span class="property">lng</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line">  &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;地址解析失败：&#x27;</span>, e)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="6-最佳实践建议"><a href="#6-最佳实践建议" class="headerlink" title="6. 最佳实践建议"></a>6. 最佳实践建议</h2><ol><li>注意权限处理</li><li>做好错误提示</li><li>优化性能表现</li><li>处理兼容问题</li><li>注意用户体验</li></ol><h2 id="7-总结"><a href="#7-总结" class="headerlink" title="7. 总结"></a>7. 总结</h2><ol><li>权限问题要提前处理</li><li>地图显示要做好兼容</li><li>路线规划要考虑性能</li><li>导航功能要适配平台</li><li>注意优化用户体验</li></ol><hr><blockquote><p>如果觉得文章对你有帮助,欢迎点赞、评论、分享,你的支持是我继续创作的动力！ </p></blockquote>]]></content>
    
    
    <summary type="html">详细介绍uni-app中地图功能和定位服务的常见问题及解决方案，包括权限处理、地图标记、路线规划等实用功能，适合新手阅读。</summary>
    
    
    
    <category term="前端开发" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    <category term="uni-app" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/uni-app/"/>
    
    
    <category term="uni-app" scheme="https://www.hzv5.cn/tags/uni-app/"/>
    
    <category term="前端开发" scheme="https://www.hzv5.cn/tags/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    <category term="地图功能" scheme="https://www.hzv5.cn/tags/%E5%9C%B0%E5%9B%BE%E5%8A%9F%E8%83%BD/"/>
    
    <category term="定位服务" scheme="https://www.hzv5.cn/tags/%E5%AE%9A%E4%BD%8D%E6%9C%8D%E5%8A%A1/"/>
    
  </entry>
  
  <entry>
    <title>uni-app文件上传踩坑记：图片处理和上传全攻略</title>
    <link href="https://www.hzv5.cn/2024/06/01/uniapp-file-upload/"/>
    <id>https://www.hzv5.cn/2024/06/01/uniapp-file-upload/</id>
    <published>2024-06-01T07:20:00.000Z</published>
    <updated>2024-06-01T07:20:00.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>在uni-app开发中，文件上传和图片处理是很常见的需求，但也经常会遇到各种问题。本文总结了一些常见问题和解决方案，希望能帮助大家更好地处理文件上传相关的需求。</p></blockquote><span id="more"></span><h2 id="1-图片选择问题"><a href="#1-图片选择问题" class="headerlink" title="1. 图片选择问题"></a>1. 图片选择问题</h2><h3 id="1-1-图片选择数量限制"><a href="#1-1-图片选择数量限制" class="headerlink" title="1.1 图片选择数量限制"></a>1.1 图片选择数量限制</h3><p><strong>问题描述：</strong><br>选择图片时没有限制数量，导致用户可能选择太多图片。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 限制选择数量和大小</span></span><br><span class="line"><span class="keyword">async</span> <span class="title function_">chooseImages</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> res = <span class="keyword">await</span> uni.<span class="title function_">chooseImage</span>(&#123;</span><br><span class="line">      <span class="attr">count</span>: <span class="number">9</span>, <span class="comment">// 最多选择9张</span></span><br><span class="line">      <span class="attr">sizeType</span>: [<span class="string">&#x27;original&#x27;</span>, <span class="string">&#x27;compressed&#x27;</span>], <span class="comment">// 原图和压缩图都可以</span></span><br><span class="line">      <span class="attr">sourceType</span>: [<span class="string">&#x27;album&#x27;</span>, <span class="string">&#x27;camera&#x27;</span>] <span class="comment">// 相册和相机都可以</span></span><br><span class="line">    &#125;)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 检查文件大小</span></span><br><span class="line">    <span class="keyword">const</span> maxSize = <span class="number">5</span> * <span class="number">1024</span> * <span class="number">1024</span> <span class="comment">// 5MB</span></span><br><span class="line">    <span class="keyword">const</span> overSizeFiles = res.<span class="property">tempFiles</span>.<span class="title function_">filter</span>(<span class="function"><span class="params">file</span> =&gt;</span> file.<span class="property">size</span> &gt; maxSize)</span><br><span class="line">    <span class="keyword">if</span> (overSizeFiles.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">      uni.<span class="title function_">showToast</span>(&#123;</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;图片大小不能超过5MB&#x27;</span>,</span><br><span class="line">        <span class="attr">icon</span>: <span class="string">&#x27;none&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">handleUpload</span>(res.<span class="property">tempFilePaths</span>)</span><br><span class="line">  &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;选择图片失败：&#x27;</span>, e)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-2-图片格式限制"><a href="#1-2-图片格式限制" class="headerlink" title="1.2 图片格式限制"></a>1.2 图片格式限制</h3><p><strong>问题描述：</strong><br>用户上传了不支持的图片格式。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 检查图片格式</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">checkImageType</span>(<span class="params">filePath</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> allowTypes = [<span class="string">&#x27;jpg&#x27;</span>, <span class="string">&#x27;jpeg&#x27;</span>, <span class="string">&#x27;png&#x27;</span>, <span class="string">&#x27;gif&#x27;</span>]</span><br><span class="line">  <span class="keyword">const</span> extension = filePath.<span class="title function_">split</span>(<span class="string">&#x27;.&#x27;</span>).<span class="title function_">pop</span>().<span class="title function_">toLowerCase</span>()</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (!allowTypes.<span class="title function_">includes</span>(extension)) &#123;</span><br><span class="line">    uni.<span class="title function_">showToast</span>(&#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;只支持jpg、png、gif格式&#x27;</span>,</span><br><span class="line">      <span class="attr">icon</span>: <span class="string">&#x27;none&#x27;</span></span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="2-图片压缩问题"><a href="#2-图片压缩问题" class="headerlink" title="2. 图片压缩问题"></a>2. 图片压缩问题</h2><h3 id="2-1-上传前压缩"><a href="#2-1-上传前压缩" class="headerlink" title="2.1 上传前压缩"></a>2.1 上传前压缩</h3><p><strong>问题描述：</strong><br>图片太大导致上传慢，浪费流量。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 压缩图片</span></span><br><span class="line"><span class="keyword">async</span> <span class="title function_">compressImage</span>(<span class="params">tempFilePath</span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> res = <span class="keyword">await</span> uni.<span class="title function_">compressImage</span>(&#123;</span><br><span class="line">      <span class="attr">src</span>: tempFilePath,</span><br><span class="line">      <span class="attr">quality</span>: <span class="number">80</span> <span class="comment">// 压缩质量0-100</span></span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">return</span> res.<span class="property">tempFilePath</span></span><br><span class="line">  &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;压缩失败：&#x27;</span>, e)</span><br><span class="line">    <span class="keyword">return</span> tempFilePath <span class="comment">// 压缩失败返回原图</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用示例</span></span><br><span class="line"><span class="keyword">async</span> <span class="title function_">handleUpload</span>(<span class="params">tempFilePaths</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> compressedPaths = []</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> path <span class="keyword">of</span> tempFilePaths) &#123;</span><br><span class="line">    <span class="keyword">const</span> compressed = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">compressImage</span>(path)</span><br><span class="line">    compressedPaths.<span class="title function_">push</span>(compressed)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="title function_">uploadFiles</span>(compressedPaths)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-2-大图预览优化"><a href="#2-2-大图预览优化" class="headerlink" title="2.2 大图预览优化"></a>2.2 大图预览优化</h3><p><strong>问题描述：</strong><br>直接预览原图占用内存大，可能导致卡顿。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 预览时使用缩略图，点击查看原图</span></span><br><span class="line"><span class="keyword">const</span> imageList = &#123;</span><br><span class="line">  <span class="attr">original</span>: [], <span class="comment">// 原图</span></span><br><span class="line">  <span class="attr">thumbnail</span>: [] <span class="comment">// 缩略图</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 预览图片</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">previewImage</span>(<span class="params">index</span>) &#123;</span><br><span class="line">  uni.<span class="title function_">previewImage</span>(&#123;</span><br><span class="line">    <span class="attr">urls</span>: imageList.<span class="property">original</span>,</span><br><span class="line">    <span class="attr">current</span>: index,</span><br><span class="line">    <span class="comment">// 显示加载提示</span></span><br><span class="line">    <span class="attr">showmenu</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">success</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;预览成功&#x27;</span>)</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">fail</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      uni.<span class="title function_">showToast</span>(&#123;</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;预览失败&#x27;</span>,</span><br><span class="line">        <span class="attr">icon</span>: <span class="string">&#x27;none&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="3-上传进度问题"><a href="#3-上传进度问题" class="headerlink" title="3. 上传进度问题"></a>3. 上传进度问题</h2><h3 id="3-1-进度显示不准"><a href="#3-1-进度显示不准" class="headerlink" title="3.1 进度显示不准"></a>3.1 进度显示不准</h3><p><strong>问题描述：</strong><br>上传进度显示不准确，影响用户体验。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 更精确的进度显示</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">uploadFile</span>(<span class="params">filePath</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> uploadTask = uni.<span class="title function_">uploadFile</span>(&#123;</span><br><span class="line">      <span class="attr">url</span>: <span class="string">&#x27;your-upload-url&#x27;</span>,</span><br><span class="line">      <span class="attr">filePath</span>: filePath,</span><br><span class="line">      <span class="attr">name</span>: <span class="string">&#x27;file&#x27;</span>,</span><br><span class="line">      <span class="attr">success</span>: <span class="function"><span class="params">res</span> =&gt;</span> <span class="title function_">resolve</span>(res),</span><br><span class="line">      <span class="attr">fail</span>: <span class="function"><span class="params">err</span> =&gt;</span> <span class="title function_">reject</span>(err)</span><br><span class="line">    &#125;)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 监听上传进度</span></span><br><span class="line">    uploadTask.<span class="title function_">onProgressUpdate</span>(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> progress = res.<span class="property">progress</span></span><br><span class="line">      <span class="keyword">const</span> speed = (res.<span class="property">totalBytesSent</span> / <span class="number">1024</span> / <span class="number">1024</span>).<span class="title function_">toFixed</span>(<span class="number">2</span>)</span><br><span class="line">      </span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">uploadInfo</span> = &#123;</span><br><span class="line">        <span class="attr">progress</span>: progress,</span><br><span class="line">        <span class="attr">speed</span>: speed + <span class="string">&#x27;MB/s&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">      </span><br><span class="line">      <span class="comment">// 更新UI显示</span></span><br><span class="line">      <span class="variable language_">this</span>.$set(<span class="variable language_">this</span>.<span class="property">uploadProgress</span>, filePath, progress)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-批量上传进度"><a href="#3-2-批量上传进度" class="headerlink" title="3.2 批量上传进度"></a>3.2 批量上传进度</h3><p><strong>问题描述：</strong><br>多文件上传时无法显示总体进度。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 批量上传进度管理</span></span><br><span class="line"><span class="keyword">async</span> <span class="title function_">uploadFiles</span>(<span class="params">files</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> total = files.<span class="property">length</span></span><br><span class="line">  <span class="keyword">let</span> completed = <span class="number">0</span></span><br><span class="line">  </span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">totalProgress</span> = <span class="number">0</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">const</span> uploads = files.<span class="title function_">map</span>(<span class="title function_">async</span> (file, index) =&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">uploadFile</span>(file)</span><br><span class="line">      completed++</span><br><span class="line">      <span class="comment">// 更新总进度</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">totalProgress</span> = <span class="title class_">Math</span>.<span class="title function_">floor</span>((completed / total) * <span class="number">100</span>)</span><br><span class="line">    &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`文件<span class="subst">$&#123;index + <span class="number">1</span>&#125;</span>上传失败：`</span>, e)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>(uploads)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="4-上传错误处理"><a href="#4-上传错误处理" class="headerlink" title="4. 上传错误处理"></a>4. 上传错误处理</h2><h3 id="4-1-断网重传"><a href="#4-1-断网重传" class="headerlink" title="4.1 断网重传"></a>4.1 断网重传</h3><p><strong>问题描述：</strong><br>上传过程中断网导致上传失败。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 添加重试机制</span></span><br><span class="line"><span class="keyword">async</span> <span class="title function_">uploadWithRetry</span>(<span class="params">file, maxRetries = <span class="number">3</span></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> retries = <span class="number">0</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">while</span> (retries &lt; maxRetries) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">uploadFile</span>(file)</span><br><span class="line">    &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">      retries++</span><br><span class="line">      <span class="keyword">if</span> (retries === maxRetries) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;上传失败，请检查网络后重试&#x27;</span>)</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 等待后重试</span></span><br><span class="line">      <span class="keyword">await</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> <span class="built_in">setTimeout</span>(resolve, <span class="number">1000</span>))</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-错误提示优化"><a href="#4-2-错误提示优化" class="headerlink" title="4.2 错误提示优化"></a>4.2 错误提示优化</h3><p><strong>问题描述：</strong><br>上传失败时提示不够友好。</p><p><strong>解决方案：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 优化错误提示</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">handleUploadError</span>(<span class="params">error</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> message = <span class="string">&#x27;上传失败&#x27;</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 根据错误类型显示不同提示</span></span><br><span class="line">  <span class="keyword">switch</span> (error.<span class="property">code</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;NETWORK_ERROR&#x27;</span>:</span><br><span class="line">      message = <span class="string">&#x27;网络异常，请检查网络设置&#x27;</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;SIZE_EXCEED&#x27;</span>:</span><br><span class="line">      message = <span class="string">&#x27;文件大小超出限制&#x27;</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;TYPE_ERROR&#x27;</span>:</span><br><span class="line">      message = <span class="string">&#x27;不支持的文件类型&#x27;</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      message = error.<span class="property">message</span> || <span class="string">&#x27;上传失败，请重试&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  uni.<span class="title function_">showToast</span>(&#123;</span><br><span class="line">    <span class="attr">title</span>: message,</span><br><span class="line">    <span class="attr">icon</span>: <span class="string">&#x27;none&#x27;</span>,</span><br><span class="line">    <span class="attr">duration</span>: <span class="number">2000</span></span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="5-实用技巧分享"><a href="#5-实用技巧分享" class="headerlink" title="5. 实用技巧分享"></a>5. 实用技巧分享</h2><h3 id="5-1-自动重命名"><a href="#5-1-自动重命名" class="headerlink" title="5.1 自动重命名"></a>5.1 自动重命名</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 生成文件名</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">generateFileName</span>(<span class="params">file</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> date = <span class="keyword">new</span> <span class="title class_">Date</span>()</span><br><span class="line">  <span class="keyword">const</span> random = <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">10000</span>)</span><br><span class="line">  <span class="keyword">const</span> extension = file.<span class="title function_">split</span>(<span class="string">&#x27;.&#x27;</span>).<span class="title function_">pop</span>()</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;date.getTime()&#125;</span>_<span class="subst">$&#123;random&#125;</span>.<span class="subst">$&#123;extension&#125;</span>`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-秒传优化"><a href="#5-2-秒传优化" class="headerlink" title="5.2 秒传优化"></a>5.2 秒传优化</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 检查文件是否已上传</span></span><br><span class="line"><span class="keyword">async</span> <span class="title function_">checkFileExists</span>(<span class="params">file</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> md5 = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">calculateMD5</span>(file)</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> res = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">checkMD5</span>(md5)</span><br><span class="line">    <span class="keyword">if</span> (res.<span class="property">exists</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> res.<span class="property">url</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="6-最佳实践建议"><a href="#6-最佳实践建议" class="headerlink" title="6. 最佳实践建议"></a>6. 最佳实践建议</h2><ol><li>上传前做好校验</li><li>合理使用压缩</li><li>添加重试机制</li><li>优化用户体验</li><li>做好错误处理</li></ol><h2 id="7-总结"><a href="#7-总结" class="headerlink" title="7. 总结"></a>7. 总结</h2><ol><li>注意文件大小限制</li><li>处理好进度显示</li><li>优化上传体验</li><li>做好容错处理</li><li>保持代码可维护</li></ol><hr><blockquote><p>如果觉得文章对你有帮助,欢迎点赞、评论、分享,你的支持是我继续创作的动力！ </p></blockquote>]]></content>
    
    
    <summary type="html">详细介绍uni-app中文件上传和图片处理的各种问题及解决方案，包括图片压缩、批量上传、上传进度等实用技巧，适合新手阅读。</summary>
    
    
    
    <category term="前端开发" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    <category term="uni-app" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/uni-app/"/>
    
    
    <category term="uni-app" scheme="https://www.hzv5.cn/tags/uni-app/"/>
    
    <category term="前端开发" scheme="https://www.hzv5.cn/tags/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    <category term="文件上传" scheme="https://www.hzv5.cn/tags/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0/"/>
    
    <category term="图片处理" scheme="https://www.hzv5.cn/tags/%E5%9B%BE%E7%89%87%E5%A4%84%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>uni-app表单验证踩坑记：这些坑我替你踩过了</title>
    <link href="https://www.hzv5.cn/2024/05/10/uniapp-form-validation/"/>
    <id>https://www.hzv5.cn/2024/05/10/uniapp-form-validation/</id>
    <published>2024-05-10T02:30:00.000Z</published>
    <updated>2024-05-10T02:30:00.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>做过表单的同学都知道，表单验证看似简单，但总有一些意想不到的问题。本文整理了一些实用的表单验证经验，希望能帮你少走弯路。</p></blockquote><span id="more"></span><h2 id="1-常见验证踩坑"><a href="#1-常见验证踩坑" class="headerlink" title="1. 常见验证踩坑"></a>1. 常见验证踩坑</h2><h3 id="1-1-手机号验证不全面"><a href="#1-1-手机号验证不全面" class="headerlink" title="1.1 手机号验证不全面"></a>1.1 手机号验证不全面</h3><p><strong>问题描述：</strong><br>很多人只用一个简单的11位数字正则来验证手机号，但实际上这样并不严谨。</p><p><strong>解决方案：</strong></p><ol><li>更严谨的手机号验证：<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 更完整的手机号验证规则</span></span><br><span class="line"><span class="keyword">const</span> phoneReg = <span class="regexp">/^1[3-9]\d&#123;9&#125;$/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用方法</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">validatePhone</span>(<span class="params">phone</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!phoneReg.<span class="title function_">test</span>(phone)) &#123;</span><br><span class="line">    uni.<span class="title function_">showToast</span>(&#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;请输入正确的手机号&#x27;</span>,</span><br><span class="line">      <span class="attr">icon</span>: <span class="string">&#x27;none&#x27;</span></span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h3 id="1-2-密码强度验证不够"><a href="#1-2-密码强度验证不够" class="headerlink" title="1.2 密码强度验证不够"></a>1.2 密码强度验证不够</h3><p><strong>问题描述：</strong><br>简单的密码验证容易导致用户设置过于简单的密码。</p><p><strong>解决方案：</strong></p><ol><li>添加密码强度检查：<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 密码必须包含数字、字母、特殊字符</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">checkPasswordStrength</span>(<span class="params">password</span>) &#123;</span><br><span class="line">  <span class="comment">// 至少8位，包含数字和字母</span></span><br><span class="line">  <span class="keyword">const</span> baseReg = <span class="regexp">/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]&#123;8,&#125;$/</span></span><br><span class="line">  <span class="comment">// 增加特殊字符要求</span></span><br><span class="line">  <span class="keyword">const</span> strongReg = <span class="regexp">/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&amp;])[A-Za-z\d@$!%*#?&amp;]&#123;8,&#125;$/</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (!baseReg.<span class="title function_">test</span>(password)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">valid</span>: <span class="literal">false</span>,</span><br><span class="line">      <span class="attr">message</span>: <span class="string">&#x27;密码至少8位，必须包含数字和字母&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (!strongReg.<span class="title function_">test</span>(password)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">valid</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">level</span>: <span class="string">&#x27;normal&#x27;</span>,</span><br><span class="line">      <span class="attr">message</span>: <span class="string">&#x27;建议增加特殊字符提高安全性&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">valid</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">level</span>: <span class="string">&#x27;strong&#x27;</span>,</span><br><span class="line">    <span class="attr">message</span>: <span class="string">&#x27;密码强度很好&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h2 id="2-实时验证问题"><a href="#2-实时验证问题" class="headerlink" title="2. 实时验证问题"></a>2. 实时验证问题</h2><h3 id="2-1-验证频率过高"><a href="#2-1-验证频率过高" class="headerlink" title="2.1 验证频率过高"></a>2.1 验证频率过高</h3><p><strong>问题描述：</strong><br>输入时实时验证，导致频繁提示，影响用户体验。</p><p><strong>解决方案：</strong></p><ol><li>使用防抖处理：<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> timer = <span class="literal">null</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">debounceValidate</span>(<span class="params">value, rule</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (timer) <span class="built_in">clearTimeout</span>(timer)</span><br><span class="line">  timer = <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 执行验证</span></span><br><span class="line">    <span class="keyword">const</span> result = rule.<span class="title function_">test</span>(value)</span><br><span class="line">    <span class="keyword">if</span> (!result) &#123;</span><br><span class="line">      uni.<span class="title function_">showToast</span>(&#123;</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;格式不正确&#x27;</span>,</span><br><span class="line">        <span class="attr">icon</span>: <span class="string">&#x27;none&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, <span class="number">500</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h3 id="2-2-失焦验证时机"><a href="#2-2-失焦验证时机" class="headerlink" title="2.2 失焦验证时机"></a>2.2 失焦验证时机</h3><p><strong>问题描述：</strong><br>在输入框失焦时才验证，但有时用户直接提交表单。</p><p><strong>解决方案：</strong></p><ol><li>结合多个验证时机：<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">input</span> </span></span><br><span class="line"><span class="tag">  <span class="attr">v-model</span>=<span class="string">&quot;form.phone&quot;</span></span></span><br><span class="line"><span class="tag">  @<span class="attr">input</span>=<span class="string">&quot;onPhoneInput&quot;</span></span></span><br><span class="line"><span class="tag">  @<span class="attr">blur</span>=<span class="string">&quot;validatePhone&quot;</span></span></span><br><span class="line"><span class="tag">  <span class="attr">placeholder</span>=<span class="string">&quot;请输入手机号&quot;</span></span></span><br><span class="line"><span class="tag">/&gt;</span></span><br></pre></td></tr></table></figure></li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="attr">methods</span>: &#123;</span><br><span class="line">    <span class="comment">// 输入时简单验证</span></span><br><span class="line">    <span class="title function_">onPhoneInput</span>(<span class="params">e</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> value = e.<span class="property">target</span>.<span class="property">value</span></span><br><span class="line">      <span class="comment">// 只允许输入数字</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">form</span>.<span class="property">phone</span> = value.<span class="title function_">replace</span>(<span class="regexp">/\D/g</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">    &#125;,</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 失焦时完整验证</span></span><br><span class="line">    <span class="title function_">validatePhone</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">form</span>.<span class="property">phone</span>) <span class="keyword">return</span></span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="title function_">checkPhone</span>(<span class="variable language_">this</span>.<span class="property">form</span>.<span class="property">phone</span>)</span><br><span class="line">    &#125;,</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 提交时再次验证</span></span><br><span class="line">    <span class="title function_">submitForm</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="title function_">validatePhone</span>()) <span class="keyword">return</span></span><br><span class="line">      <span class="comment">// 继续提交流程</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="3-特殊场景验证"><a href="#3-特殊场景验证" class="headerlink" title="3. 特殊场景验证"></a>3. 特殊场景验证</h2><h3 id="3-1-身份证号验证"><a href="#3-1-身份证号验证" class="headerlink" title="3.1 身份证号验证"></a>3.1 身份证号验证</h3><p><strong>问题描述：</strong><br>简单的18位验证无法识别假身份证号。</p><p><strong>解决方案：</strong></p><ol><li>完整的身份证验证：<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">validateIdCard</span>(<span class="params">idCard</span>) &#123;</span><br><span class="line">  <span class="comment">// 基本格式验证</span></span><br><span class="line">  <span class="keyword">const</span> reg = <span class="regexp">/(^\d&#123;15&#125;$)|(^\d&#123;18&#125;$)|(^\d&#123;17&#125;(\d|X|x)$)/</span></span><br><span class="line">  <span class="keyword">if</span> (!reg.<span class="title function_">test</span>(idCard)) <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 验证出生日期</span></span><br><span class="line">  <span class="keyword">const</span> year = idCard.<span class="title function_">substr</span>(<span class="number">6</span>, <span class="number">4</span>)</span><br><span class="line">  <span class="keyword">const</span> month = idCard.<span class="title function_">substr</span>(<span class="number">10</span>, <span class="number">2</span>)</span><br><span class="line">  <span class="keyword">const</span> day = idCard.<span class="title function_">substr</span>(<span class="number">12</span>, <span class="number">2</span>)</span><br><span class="line">  <span class="keyword">const</span> date = <span class="keyword">new</span> <span class="title class_">Date</span>(year + <span class="string">&#x27;-&#x27;</span> + month + <span class="string">&#x27;-&#x27;</span> + day)</span><br><span class="line">  <span class="keyword">if</span> (date &gt; <span class="keyword">new</span> <span class="title class_">Date</span>()) <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 验证校验码（略）</span></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h3 id="3-2-金额输入验证"><a href="#3-2-金额输入验证" class="headerlink" title="3.2 金额输入验证"></a>3.2 金额输入验证</h3><p><strong>问题描述：</strong><br>金额输入需要限制小数位数和输入格式。</p><p><strong>解决方案：</strong></p><ol><li>金额输入处理：<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 限制只能输入数字和小数点，最多两位小数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">validateAmount</span>(<span class="params">value</span>) &#123;</span><br><span class="line">  <span class="comment">// 先去除非数字和小数点</span></span><br><span class="line">  value = value.<span class="title function_">replace</span>(<span class="regexp">/[^\d.]/g</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">  <span class="comment">// 只保留第一个小数点</span></span><br><span class="line">  value = value.<span class="title function_">replace</span>(<span class="regexp">/\.&#123;2,&#125;/g</span>, <span class="string">&#x27;.&#x27;</span>)</span><br><span class="line">  <span class="comment">// 保留两位小数</span></span><br><span class="line">  value = value.<span class="title function_">replace</span>(<span class="regexp">/^(\d+)\.(\d\d).*$/</span>, <span class="string">&#x27;$1.$2&#x27;</span>)</span><br><span class="line">  <span class="keyword">return</span> value</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用示例</span></span><br><span class="line"><span class="title function_">onAmountInput</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">form</span>.<span class="property">amount</span> = <span class="title function_">validateAmount</span>(e.<span class="property">target</span>.<span class="property">value</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h2 id="4-表单提交验证"><a href="#4-表单提交验证" class="headerlink" title="4. 表单提交验证"></a>4. 表单提交验证</h2><h3 id="4-1-重复提交问题"><a href="#4-1-重复提交问题" class="headerlink" title="4.1 重复提交问题"></a>4.1 重复提交问题</h3><p><strong>问题描述：</strong><br>用户快速点击提交按钮，导致表单重复提交。</p><p><strong>解决方案：</strong></p><ol><li>添加提交锁：<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="title function_">data</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">submitting</span>: <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">methods</span>: &#123;</span><br><span class="line">    <span class="keyword">async</span> <span class="title function_">submitForm</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">submitting</span>) <span class="keyword">return</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">submitting</span> = <span class="literal">true</span></span><br><span class="line">      </span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">submit</span>()</span><br><span class="line">        uni.<span class="title function_">showToast</span>(&#123;</span><br><span class="line">          <span class="attr">title</span>: <span class="string">&#x27;提交成功&#x27;</span></span><br><span class="line">        &#125;)</span><br><span class="line">      &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        uni.<span class="title function_">showToast</span>(&#123;</span><br><span class="line">          <span class="attr">title</span>: <span class="string">&#x27;提交失败&#x27;</span>,</span><br><span class="line">          <span class="attr">icon</span>: <span class="string">&#x27;none&#x27;</span></span><br><span class="line">        &#125;)</span><br><span class="line">      &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">submitting</span> = <span class="literal">false</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h3 id="4-2-必填项检查"><a href="#4-2-必填项检查" class="headerlink" title="4.2 必填项检查"></a>4.2 必填项检查</h3><p><strong>问题描述：</strong><br>提交时才发现有必填项未填写，体验不好。</p><p><strong>解决方案：</strong></p><ol><li>实时显示必填状态：<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">view</span> <span class="attr">class</span>=<span class="string">&quot;form-item&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">text</span> <span class="attr">class</span>=<span class="string">&quot;label&quot;</span>&gt;</span></span><br><span class="line">    手机号</span><br><span class="line">    <span class="tag">&lt;<span class="name">text</span> <span class="attr">class</span>=<span class="string">&quot;required&quot;</span>&gt;</span>*<span class="tag">&lt;/<span class="name">text</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">text</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">input</span> </span></span><br><span class="line"><span class="tag">    <span class="attr">v-model</span>=<span class="string">&quot;form.phone&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">:class</span>=<span class="string">&quot;&#123;&#x27;is-error&#x27;: showError &amp;&amp; !form.phone&#125;&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">placeholder</span>=<span class="string">&quot;请输入手机号&quot;</span></span></span><br><span class="line"><span class="tag">  /&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">text</span> <span class="attr">v-if</span>=<span class="string">&quot;showError &amp;&amp; !form.phone&quot;</span> <span class="attr">class</span>=<span class="string">&quot;error-tip&quot;</span>&gt;</span></span><br><span class="line">    手机号不能为空</span><br><span class="line">  <span class="tag">&lt;/<span class="name">text</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">view</span>&gt;</span></span><br></pre></td></tr></table></figure></li></ol><h2 id="5-常用验证规则"><a href="#5-常用验证规则" class="headerlink" title="5. 常用验证规则"></a>5. 常用验证规则</h2><h3 id="5-1-常用正则表达式"><a href="#5-1-常用正则表达式" class="headerlink" title="5.1 常用正则表达式"></a>5.1 常用正则表达式</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> rules = &#123;</span><br><span class="line">  <span class="comment">// 手机号</span></span><br><span class="line">  <span class="attr">phone</span>: <span class="regexp">/^1[3-9]\d&#123;9&#125;$/</span>,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 邮箱</span></span><br><span class="line">  <span class="attr">email</span>: <span class="regexp">/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]&#123;2,&#125;$/</span>,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 车牌号</span></span><br><span class="line">  <span class="attr">carNo</span>: <span class="regexp">/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]&#123;1&#125;[A-Z]&#123;1&#125;[A-Z0-9]&#123;4&#125;[A-Z0-9挂学警港澳]&#123;1&#125;$/</span>,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 银行卡号</span></span><br><span class="line">  <span class="attr">bankCard</span>: <span class="regexp">/^([1-9]&#123;1&#125;)(\d&#123;15&#125;|\d&#123;18&#125;)$/</span>,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 密码强度</span></span><br><span class="line">  <span class="attr">password</span>: <span class="regexp">/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&amp;])[A-Za-z\d@$!%*#?&amp;]&#123;8,&#125;$/</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-自定义验证函数"><a href="#5-2-自定义验证函数" class="headerlink" title="5.2 自定义验证函数"></a>5.2 自定义验证函数</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> validators = &#123;</span><br><span class="line">  <span class="comment">// 年龄验证</span></span><br><span class="line">  <span class="title function_">age</span>(<span class="params">value</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> age = <span class="built_in">parseInt</span>(value)</span><br><span class="line">    <span class="keyword">return</span> age &gt;= <span class="number">0</span> &amp;&amp; age &lt;= <span class="number">120</span></span><br><span class="line">  &#125;,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 日期验证</span></span><br><span class="line">  <span class="title function_">date</span>(<span class="params">value</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> !<span class="built_in">isNaN</span>(<span class="keyword">new</span> <span class="title class_">Date</span>(value).<span class="title function_">getTime</span>())</span><br><span class="line">  &#125;,</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 金额验证</span></span><br><span class="line">  <span class="title function_">amount</span>(<span class="params">value</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="regexp">/^\d+(\.\d&#123;1,2&#125;)?$/</span>.<span class="title function_">test</span>(value)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="6-最佳实践建议"><a href="#6-最佳实践建议" class="headerlink" title="6. 最佳实践建议"></a>6. 最佳实践建议</h2><ol><li>合理使用验证时机</li><li>注意验证提示的友好性</li><li>考虑特殊情况处理</li><li>做好防重复提交</li><li>保持代码可维护性</li></ol><h2 id="7-总结"><a href="#7-总结" class="headerlink" title="7. 总结"></a>7. 总结</h2><ol><li>验证规则要严谨但不过度</li><li>注意用户体验</li><li>考虑各种异常情况</li><li>做好性能优化</li><li>保持代码整洁</li></ol><hr><blockquote><p>如果觉得文章对你有帮助,欢迎点赞、评论、分享,你的支持是我继续创作的动力！ </p></blockquote>]]></content>
    
    
    <summary type="html">总结uni-app表单验证中的常见问题和解决方案，包括手机号验证、密码验证、自定义验证等实用技巧，通俗易懂的语言讲解开发经验。</summary>
    
    
    
    <category term="前端开发" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    <category term="uni-app" scheme="https://www.hzv5.cn/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/uni-app/"/>
    
    
    <category term="uni-app" scheme="https://www.hzv5.cn/tags/uni-app/"/>
    
    <category term="前端开发" scheme="https://www.hzv5.cn/tags/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    <category term="表单验证" scheme="https://www.hzv5.cn/tags/%E8%A1%A8%E5%8D%95%E9%AA%8C%E8%AF%81/"/>
    
    <category term="实战经验" scheme="https://www.hzv5.cn/tags/%E5%AE%9E%E6%88%98%E7%BB%8F%E9%AA%8C/"/>
    
  </entry>
  
</feed>
