<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://altair288.github.io/Blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://altair288.github.io/Blog/" rel="alternate" type="text/html" /><updated>2025-11-20T00:43:40+00:00</updated><id>https://altair288.github.io/Blog/feed.xml</id><title type="html">Altair288’s Blog</title><subtitle>UwU</subtitle><entry><title type="html">微信小程序学习笔记-7</title><link href="https://altair288.github.io/Blog/wx-miniprogram7/" rel="alternate" type="text/html" title="微信小程序学习笔记-7" /><published>2025-10-15T00:00:00+00:00</published><updated>2025-10-15T00:00:00+00:00</updated><id>https://altair288.github.io/Blog/wx-miniprogram7</id><content type="html" xml:base="https://altair288.github.io/Blog/wx-miniprogram7/"><![CDATA[<h1 id="微信小程序登录与授权学习笔记">微信小程序「登录与授权」学习笔记</h1>

<blockquote>
  <p>目标：理解小程序登录整体流程，区分“登录”和“获取用户信息/授权”的关系，能在项目中实现一个可用的登录体系。</p>
</blockquote>

<hr />

<h2 id="一几个容易混淆的概念">一、几个容易混淆的概念</h2>

<ol>
  <li><strong>小程序登录（后端会话）</strong>
    <ul>
      <li>核心是：用 <code class="language-plaintext highlighter-rouge">wx.login</code> 拿到 <code class="language-plaintext highlighter-rouge">code</code> → 发给你自己的服务器 → 换取 <code class="language-plaintext highlighter-rouge">openid</code>、<code class="language-plaintext highlighter-rouge">session_key</code>，建立你自己的登录态（token）。</li>
      <li>这是“服务端鉴权”的基础，与 UI 授权弹窗没有直接关系。</li>
    </ul>
  </li>
  <li><strong>用户信息 / 授权（头像、昵称、手机号、地理位置等）</strong>
    <ul>
      <li>需要用户同意授权（按钮触发），后台才可以拿到用户信息。</li>
      <li>常见 API：<code class="language-plaintext highlighter-rouge">wx.getUserProfile</code>（旧规范）、手机号获取、地理位置授权等。</li>
    </ul>
  </li>
  <li><strong>微信侧登录 vs 业务侧登录</strong>
    <ul>
      <li>微信侧：微信已经登录（用户在用微信），小程序天然拥有用户在微信的身份（openid）。</li>
      <li>业务侧：你自己的应用系统需要知道“这是哪个用户”，并给他分配账号、权限、数据（由你自己的 token  / userId 维护）。</li>
    </ul>
  </li>
</ol>

<blockquote>
  <p>记忆：<strong>微信登录是“Who”，授权是“Can I read your X”</strong>（头像、手机号、位置等）。</p>
</blockquote>

<hr />

<h2 id="二小程序登录整体流程推荐做法">二、小程序登录整体流程（推荐做法）</h2>

<p>一个常见的“服务端登录”流程：</p>

<ol>
  <li>小程序调用 <code class="language-plaintext highlighter-rouge">wx.login()</code> 拿到 <code class="language-plaintext highlighter-rouge">code</code></li>
  <li>把 <code class="language-plaintext highlighter-rouge">code</code> 发送给你自己的服务器</li>
  <li>服务器调用微信的 <code class="language-plaintext highlighter-rouge">jscode2session</code> 接口，得到 <code class="language-plaintext highlighter-rouge">openid</code>、<code class="language-plaintext highlighter-rouge">session_key</code>（和可选 <code class="language-plaintext highlighter-rouge">unionid</code>）</li>
  <li>服务器根据 <code class="language-plaintext highlighter-rouge">openid</code> 在数据库中查找 / 创建用户，生成自己的 <code class="language-plaintext highlighter-rouge">token</code>（例如 JWT）</li>
  <li>服务器把 <code class="language-plaintext highlighter-rouge">token</code> 和业务用户信息返回给小程序</li>
  <li>小程序保存 <code class="language-plaintext highlighter-rouge">token</code> 到：
    <ul>
      <li>内存（<code class="language-plaintext highlighter-rouge">App.globalData</code>）</li>
      <li>本地缓存（<code class="language-plaintext highlighter-rouge">wx.setStorageSync</code>）用于下次自动登录</li>
    </ul>
  </li>
</ol>

<hr />

<h2 id="三前端wxlogin-基本使用">三、前端：<code class="language-plaintext highlighter-rouge">wx.login</code> 基本使用</h2>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// pages/login/login.js</span>
<span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">loading</span><span class="p">:</span> <span class="kc">false</span>
  <span class="p">},</span>

  <span class="nx">onLoginTap</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="na">loading</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span>

    <span class="nx">wx</span><span class="p">.</span><span class="nx">login</span><span class="p">({</span>
      <span class="na">success</span><span class="p">:</span> <span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">code</span><span class="p">)</span> <span class="p">{</span>
          <span class="c1">// 把 code 发给后台</span>
          <span class="k">this</span><span class="p">.</span><span class="nx">requestLogin</span><span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">code</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
          <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">登录失败！</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">res</span><span class="p">.</span><span class="nx">errMsg</span><span class="p">)</span>
          <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="na">loading</span><span class="p">:</span> <span class="kc">false</span> <span class="p">})</span>
        <span class="p">}</span>
      <span class="p">},</span>
      <span class="na">fail</span><span class="p">:</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">wx.login 调用失败</span><span class="dl">'</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="na">loading</span><span class="p">:</span> <span class="kc">false</span> <span class="p">})</span>
      <span class="p">}</span>
    <span class="p">})</span>
  <span class="p">},</span>

  <span class="nx">requestLogin</span><span class="p">(</span><span class="nx">code</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">request</span><span class="p">({</span>
      <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://your-domain.com/api/login</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">data</span><span class="p">:</span> <span class="p">{</span> <span class="nx">code</span> <span class="p">},</span>
      <span class="na">success</span><span class="p">:</span> <span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="c1">// 假设返回 { code: 0, data: { token, userInfo } }</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">code</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
          <span class="kd">const</span> <span class="p">{</span> <span class="nx">token</span><span class="p">,</span> <span class="nx">userInfo</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">res</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">data</span>
          <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">getApp</span><span class="p">()</span>

          <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">token</span> <span class="o">=</span> <span class="nx">token</span>
          <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">userInfo</span> <span class="o">=</span> <span class="nx">userInfo</span>

          <span class="nx">wx</span><span class="p">.</span><span class="nx">setStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">TOKEN</span><span class="dl">'</span><span class="p">,</span> <span class="nx">token</span><span class="p">)</span>
          <span class="nx">wx</span><span class="p">.</span><span class="nx">setStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">USER_INFO</span><span class="dl">'</span><span class="p">,</span> <span class="nx">userInfo</span><span class="p">)</span>

          <span class="c1">// 登录成功，跳转到首页或个人中心</span>
          <span class="nx">wx</span><span class="p">.</span><span class="nx">reLaunch</span><span class="p">({</span>
            <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/index/index</span><span class="dl">'</span>
          <span class="p">})</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
          <span class="nx">wx</span><span class="p">.</span><span class="nx">showToast</span><span class="p">({</span>
            <span class="na">title</span><span class="p">:</span> <span class="nx">res</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">msg</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">登录失败</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">icon</span><span class="p">:</span> <span class="dl">'</span><span class="s1">none</span><span class="dl">'</span>
          <span class="p">})</span>
        <span class="p">}</span>
      <span class="p">},</span>
      <span class="na">fail</span><span class="p">:</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">请求登录接口失败</span><span class="dl">'</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
      <span class="p">},</span>
      <span class="na">complete</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="na">loading</span><span class="p">:</span> <span class="kc">false</span> <span class="p">})</span>
      <span class="p">}</span>
    <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="四后端简要思路语言自定">四、后端（简要思路，语言自定）</h2>

<blockquote>
  <p>这里只记“思路”，实现看你用什么语言 / 框架。</p>
</blockquote>

<ol>
  <li>接收小程序传来的 <code class="language-plaintext highlighter-rouge">code</code></li>
  <li>
    <p>调用微信接口：</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://api.weixin.qq.com/sns/jscode2session?appid=APPID&amp;secret=SECRET&amp;js_code=JSCODE&amp;grant_type=authorization_code
</code></pre></div>    </div>

    <p>得到类似数据：</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"openid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"OPENID"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"session_key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SESSIONKEY"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"unionid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"UNIONID"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>根据 <code class="language-plaintext highlighter-rouge">openid</code> 在数据库中查找用户：
    <ul>
      <li>有 → 读取该用户</li>
      <li>无 → 创建新用户（可初始化一些信息）</li>
    </ul>
  </li>
  <li>生成业务 <code class="language-plaintext highlighter-rouge">token</code>（例如 JWT or 自定义 sessionId）</li>
  <li>将 <code class="language-plaintext highlighter-rouge">token + 用户基本信息</code> 返回给前端</li>
</ol>

<p>关键点：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">session_key</code> 是解密敏感数据（如手机号）的关键，不要直接暴露给前端。</li>
  <li>后端统一处理 token 的验证，前端每次请求接口都带上 token。</li>
</ul>

<hr />

<h2 id="五自动登录启动时恢复登录态">五、自动登录（启动时恢复登录态）</h2>

<h3 id="51-appjs-中恢复缓存">5.1 app.js 中恢复缓存</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// app.js</span>
<span class="nx">App</span><span class="p">({</span>
  <span class="nx">onLaunch</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="nx">wx</span><span class="p">.</span><span class="nx">getStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">TOKEN</span><span class="dl">'</span><span class="p">)</span>
    <span class="kd">const</span> <span class="nx">userInfo</span> <span class="o">=</span> <span class="nx">wx</span><span class="p">.</span><span class="nx">getStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">USER_INFO</span><span class="dl">'</span><span class="p">)</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">token</span> <span class="o">=</span> <span class="nx">token</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">userInfo</span> <span class="o">=</span> <span class="nx">userInfo</span>
    <span class="p">}</span>
  <span class="p">},</span>

  <span class="na">globalData</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">token</span><span class="p">:</span> <span class="dl">''</span><span class="p">,</span>
    <span class="na">userInfo</span><span class="p">:</span> <span class="kc">null</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<h3 id="52-页面中检查登录态">5.2 页面中检查登录态</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// pages/profile/profile.js</span>
<span class="nx">Page</span><span class="p">({</span>
  <span class="nx">onShow</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">getApp</span><span class="p">()</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// 未登录，引导去登录页</span>
      <span class="nx">wx</span><span class="p">.</span><span class="nx">navigateTo</span><span class="p">({</span>
        <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/login/login</span><span class="dl">'</span>
      <span class="p">})</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="c1">// 已登录，正常显示页面</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span>
        <span class="na">userInfo</span><span class="p">:</span> <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">userInfo</span>
      <span class="p">})</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="六用户信息授权头像昵称">六、用户信息授权（头像昵称）</h2>

<blockquote>
  <p>新政策下推荐按“必要即申请”的原则，不要随意弹授权框。</p>
</blockquote>

<h3 id="61-老的-button-open-typegetuserinfo-已不推荐">6.1 老的 <code class="language-plaintext highlighter-rouge">button open-type="getUserInfo"</code> 已不推荐</h3>

<p>现在更多是：使用 <code class="language-plaintext highlighter-rouge">wx.getUserProfile</code> 或相关接口（注意看最新官方文档）。</p>

<p>示意写法（逻辑思路）：</p>

<pre><code class="language-wxml">&lt;button type="primary" bindtap="onGetUserProfile"&gt;获取头像昵称&lt;/button&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="nx">onGetUserProfile</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">getUserProfile</span><span class="p">({</span>
      <span class="na">desc</span><span class="p">:</span> <span class="dl">'</span><span class="s1">用于完善会员资料</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">success</span><span class="p">:</span> <span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="c1">// res.userInfo 中有 avatarUrl, nickName 等</span>
        <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">getApp</span><span class="p">()</span>
        <span class="kd">const</span> <span class="nx">userInfo</span> <span class="o">=</span> <span class="nx">res</span><span class="p">.</span><span class="nx">userInfo</span>

        <span class="c1">// 更新全局和本地缓存</span>
        <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">userInfo</span> <span class="o">=</span> <span class="p">{</span>
          <span class="p">...</span><span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">userInfo</span><span class="p">,</span>
          <span class="p">...</span><span class="nx">userInfo</span>
        <span class="p">}</span>

        <span class="nx">wx</span><span class="p">.</span><span class="nx">setStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">USER_INFO</span><span class="dl">'</span><span class="p">,</span> <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">userInfo</span><span class="p">)</span>

        <span class="c1">// 可以把用户信息同步给自己的后台</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">updateUserProfileToServer</span><span class="p">(</span><span class="nx">userInfo</span><span class="p">)</span>
      <span class="p">},</span>
      <span class="na">fail</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">wx</span><span class="p">.</span><span class="nx">showToast</span><span class="p">({</span>
          <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">你拒绝了头像昵称授权</span><span class="dl">'</span><span class="p">,</span>
          <span class="na">icon</span><span class="p">:</span> <span class="dl">'</span><span class="s1">none</span><span class="dl">'</span>
        <span class="p">})</span>
      <span class="p">}</span>
    <span class="p">})</span>
  <span class="p">},</span>

  <span class="nx">updateUserProfileToServer</span><span class="p">(</span><span class="nx">userInfo</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 调后台更新资料</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<blockquote>
  <p>实际开发中需要根据最新隐私政策选用正确的接口，尊重用户授权。</p>
</blockquote>

<hr />

<h2 id="七获取手机号授权常见需求">七、获取手机号授权（常见需求）</h2>

<p><strong>必须</strong>使用微信提供的获取手机号能力，一般流程：</p>

<ol>
  <li>在页面放一个按钮，<code class="language-plaintext highlighter-rouge">open-type="getPhoneNumber"</code>（由用户点击触发）</li>
  <li>在回调中获取 <code class="language-plaintext highlighter-rouge">encryptedData</code> 和 <code class="language-plaintext highlighter-rouge">iv</code></li>
  <li>把这两个字段连同 <code class="language-plaintext highlighter-rouge">session_key</code> 或 <code class="language-plaintext highlighter-rouge">code</code> 发送给你的服务器</li>
  <li>服务器用微信提供的解密算法解密出真实手机号，并绑定到用户</li>
</ol>

<p>前端示例：</p>

<pre><code class="language-wxml">&lt;button
  type="primary"
  open-type="getPhoneNumber"
  bindgetphonenumber="onGetPhoneNumber"
&gt;
  获取手机号
&lt;/button&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="nx">onGetPhoneNumber</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">errMsg</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">getPhoneNumber:ok</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="p">{</span> <span class="nx">encryptedData</span><span class="p">,</span> <span class="nx">iv</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">detail</span>
      <span class="c1">// 建议后台使用 session_key 解密</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">sendPhoneToServer</span><span class="p">(</span><span class="nx">encryptedData</span><span class="p">,</span> <span class="nx">iv</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="nx">wx</span><span class="p">.</span><span class="nx">showToast</span><span class="p">({</span>
        <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">你拒绝了手机号授权</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">icon</span><span class="p">:</span> <span class="dl">'</span><span class="s1">none</span><span class="dl">'</span>
      <span class="p">})</span>
    <span class="p">}</span>
  <span class="p">},</span>

  <span class="nx">sendPhoneToServer</span><span class="p">(</span><span class="nx">encryptedData</span><span class="p">,</span> <span class="nx">iv</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">request</span><span class="p">({</span>
      <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://your-domain.com/api/bindPhone</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
        <span class="nx">encryptedData</span><span class="p">,</span>
        <span class="nx">iv</span>
      <span class="p">},</span>
      <span class="na">success</span><span class="p">:</span> <span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="c1">// 后台解密后返回手机号等</span>
      <span class="p">}</span>
    <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<blockquote>
  <p>解密逻辑必须在服务器侧完成，避免泄露 <code class="language-plaintext highlighter-rouge">session_key</code> 和解密逻辑。</p>
</blockquote>

<hr />

<h2 id="八登录态校验与过期处理">八、登录态校验与过期处理</h2>

<h3 id="81-前端请求时统一带上-token">8.1 前端请求时统一带上 token</h3>

<p>封装 request（简化示意）：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// utils/request.js</span>
<span class="kd">const</span> <span class="nx">BASE_URL</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">https://your-domain.com</span><span class="dl">'</span>

<span class="kd">function</span> <span class="nx">request</span><span class="p">(</span><span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">getApp</span><span class="p">()</span>
  <span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">token</span> <span class="o">||</span> <span class="nx">wx</span><span class="p">.</span><span class="nx">getStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">TOKEN</span><span class="dl">'</span><span class="p">)</span>

  <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">request</span><span class="p">({</span>
      <span class="na">url</span><span class="p">:</span> <span class="nx">BASE_URL</span> <span class="o">+</span> <span class="nx">options</span><span class="p">.</span><span class="nx">url</span><span class="p">,</span>
      <span class="na">method</span><span class="p">:</span> <span class="nx">options</span><span class="p">.</span><span class="nx">method</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">GET</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">data</span><span class="p">:</span> <span class="nx">options</span><span class="p">.</span><span class="nx">data</span> <span class="o">||</span> <span class="p">{},</span>
      <span class="na">header</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">Authorization</span><span class="p">:</span> <span class="nx">token</span> <span class="p">?</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span> <span class="p">:</span> <span class="dl">''</span><span class="p">,</span>
        <span class="p">...(</span><span class="nx">options</span><span class="p">.</span><span class="nx">header</span> <span class="o">||</span> <span class="p">{})</span>
      <span class="p">},</span>
      <span class="nx">success</span><span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 举例：后端约定 code = 401 表示未登录 / 登录过期</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">code</span> <span class="o">===</span> <span class="mi">401</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">handleNotLogin</span><span class="p">()</span>
          <span class="nx">reject</span><span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">data</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
          <span class="nx">resolve</span><span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">data</span><span class="p">)</span>
        <span class="p">}</span>
      <span class="p">},</span>
      <span class="nx">fail</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">reject</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
      <span class="p">}</span>
    <span class="p">})</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">handleNotLogin</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">getApp</span><span class="p">()</span>
  <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">token</span> <span class="o">=</span> <span class="dl">''</span>
  <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">userInfo</span> <span class="o">=</span> <span class="kc">null</span>
  <span class="nx">wx</span><span class="p">.</span><span class="nx">removeStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">TOKEN</span><span class="dl">'</span><span class="p">)</span>
  <span class="nx">wx</span><span class="p">.</span><span class="nx">removeStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">USER_INFO</span><span class="dl">'</span><span class="p">)</span>

  <span class="nx">wx</span><span class="p">.</span><span class="nx">navigateTo</span><span class="p">({</span>
    <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/login/login</span><span class="dl">'</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
  <span class="nx">request</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="82-后端统一校验-token">8.2 后端统一校验 token</h3>

<ul>
  <li>拦截所有需要登录的接口</li>
  <li>没有 token 或 token 失效 → 返回统一错误码（如 <code class="language-plaintext highlighter-rouge">401</code>）</li>
  <li>前端根据这个错误码做“清除本地登录信息 + 跳到登录页”</li>
</ul>

<hr />

<h2 id="九注销--退出登录">九、注销 / 退出登录</h2>

<p>前端逻辑一般：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// pages/profile/profile.js</span>
<span class="nx">Page</span><span class="p">({</span>
  <span class="nx">onLogout</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">showModal</span><span class="p">({</span>
      <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">提示</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">content</span><span class="p">:</span> <span class="dl">'</span><span class="s1">确定要退出登录吗？</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">success</span><span class="p">:</span> <span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">confirm</span><span class="p">)</span> <span class="p">{</span>
          <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">getApp</span><span class="p">()</span>
          <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">token</span> <span class="o">=</span> <span class="dl">''</span>
          <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">userInfo</span> <span class="o">=</span> <span class="kc">null</span>
          <span class="nx">wx</span><span class="p">.</span><span class="nx">removeStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">TOKEN</span><span class="dl">'</span><span class="p">)</span>
          <span class="nx">wx</span><span class="p">.</span><span class="nx">removeStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">USER_INFO</span><span class="dl">'</span><span class="p">)</span>

          <span class="c1">// 可选：通知服务器注销（清 token）</span>
          <span class="c1">// wx.request({ url: '/api/logout', method: 'POST' })</span>

          <span class="nx">wx</span><span class="p">.</span><span class="nx">reLaunch</span><span class="p">({</span>
            <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/index/index</span><span class="dl">'</span>
          <span class="p">})</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="十隐私--合规简要提示">十、隐私 &amp; 合规简要提示</h2>

<ul>
  <li>在用户第一次打开小程序时，建议展示隐私弹窗（参考微信最新规范）</li>
  <li>获取用户信息、手机号、位置等，必须说明用途，并由用户主动触发</li>
  <li>不要在未授权的情况下偷偷上报敏感信息</li>
  <li>对用户可识别的信息（如手机号）要在服务端做好安全存储和访问控制</li>
</ul>

<hr />]]></content><author><name></name></author><category term="miniprogram7" /><summary type="html"><![CDATA[微信小程序「登录与授权」学习笔记 目标：理解小程序登录整体流程，区分“登录”和“获取用户信息/授权”的关系，能在项目中实现一个可用的登录体系。 一、几个容易混淆的概念 小程序登录（后端会话） 核心是：用 wx.login 拿到 code → 发给你自己的服务器 → 换取 openid、session_key，建立你自己的登录态（token）。 这是“服务端鉴权”的基础，与 UI 授权弹窗没有直接关系。 用户信息 / 授权（头像、昵称、手机号、地理位置等） 需要用户同意授权（按钮触发），后台才可以拿到用户信息。 常见 API：wx.getUserProfile（旧规范）、手机号获取、地理位置授权等。 微信侧登录 vs 业务侧登录 微信侧：微信已经登录（用户在用微信），小程序天然拥有用户在微信的身份（openid）。 业务侧：你自己的应用系统需要知道“这是哪个用户”，并给他分配账号、权限、数据（由你自己的 token / userId 维护）。 记忆：微信登录是“Who”，授权是“Can I read your X”（头像、手机号、位置等）。 二、小程序登录整体流程（推荐做法） 一个常见的“服务端登录”流程： 小程序调用 wx.login() 拿到 code 把 code 发送给你自己的服务器 服务器调用微信的 jscode2session 接口，得到 openid、session_key（和可选 unionid） 服务器根据 openid 在数据库中查找 / 创建用户，生成自己的 token（例如 JWT） 服务器把 token 和业务用户信息返回给小程序 小程序保存 token 到： 内存（App.globalData） 本地缓存（wx.setStorageSync）用于下次自动登录 三、前端：wx.login 基本使用 // pages/login/login.js Page({ data: { loading: false }, onLoginTap() { this.setData({ loading: true }) wx.login({ success: (res) =&gt; { if (res.code) { // 把 code 发给后台 this.requestLogin(res.code) } else { console.error('登录失败！' + res.errMsg) this.setData({ loading: false }) } }, fail: (err) =&gt; { console.error('wx.login 调用失败', err) this.setData({ loading: false }) } }) }, requestLogin(code) { wx.request({ url: 'https://your-domain.com/api/login', method: 'POST', data: { code }, success: (res) =&gt; { // 假设返回 { code: 0, data: { token, userInfo } } if (res.data.code === 0) { const { token, userInfo } = res.data.data const app = getApp() app.globalData.token = token app.globalData.userInfo = userInfo wx.setStorageSync('TOKEN', token) wx.setStorageSync('USER_INFO', userInfo) // 登录成功，跳转到首页或个人中心 wx.reLaunch({ url: '/pages/index/index' }) } else { wx.showToast({ title: res.data.msg || '登录失败', icon: 'none' }) } }, fail: (err) =&gt; { console.error('请求登录接口失败', err) }, complete: () =&gt; { this.setData({ loading: false }) } }) } }) 四、后端（简要思路，语言自定） 这里只记“思路”，实现看你用什么语言 / 框架。 接收小程序传来的 code 调用微信接口： https://api.weixin.qq.com/sns/jscode2session?appid=APPID&amp;secret=SECRET&amp;js_code=JSCODE&amp;grant_type=authorization_code 得到类似数据： { "openid": "OPENID", "session_key": "SESSIONKEY", "unionid": "UNIONID" } 根据 openid 在数据库中查找用户： 有 → 读取该用户 无 → 创建新用户（可初始化一些信息） 生成业务 token（例如 JWT or 自定义 sessionId） 将 token + 用户基本信息 返回给前端 关键点： session_key 是解密敏感数据（如手机号）的关键，不要直接暴露给前端。 后端统一处理 token 的验证，前端每次请求接口都带上 token。 五、自动登录（启动时恢复登录态） 5.1 app.js 中恢复缓存 // app.js App({ onLaunch() { const token = wx.getStorageSync('TOKEN') const userInfo = wx.getStorageSync('USER_INFO') if (token) { this.globalData.token = token this.globalData.userInfo = userInfo } }, globalData: { token: '', userInfo: null } }) 5.2 页面中检查登录态 // pages/profile/profile.js Page({ onShow() { const app = getApp() if (!app.globalData.token) { // 未登录，引导去登录页 wx.navigateTo({ url: '/pages/login/login' }) } else { // 已登录，正常显示页面 this.setData({ userInfo: app.globalData.userInfo }) } } }) 六、用户信息授权（头像昵称） 新政策下推荐按“必要即申请”的原则，不要随意弹授权框。 6.1 老的 button open-type="getUserInfo" 已不推荐 现在更多是：使用 wx.getUserProfile 或相关接口（注意看最新官方文档）。 示意写法（逻辑思路）： &lt;button type="primary" bindtap="onGetUserProfile"&gt;获取头像昵称&lt;/button&gt; Page({ onGetUserProfile() { wx.getUserProfile({ desc: '用于完善会员资料', success: (res) =&gt; { // res.userInfo 中有 avatarUrl, nickName 等 const app = getApp() const userInfo = res.userInfo // 更新全局和本地缓存 app.globalData.userInfo = { ...app.globalData.userInfo, ...userInfo } wx.setStorageSync('USER_INFO', app.globalData.userInfo) // 可以把用户信息同步给自己的后台 this.updateUserProfileToServer(userInfo) }, fail: () =&gt; { wx.showToast({ title: '你拒绝了头像昵称授权', icon: 'none' }) } }) }, updateUserProfileToServer(userInfo) { // 调后台更新资料 } }) 实际开发中需要根据最新隐私政策选用正确的接口，尊重用户授权。 七、获取手机号授权（常见需求） 必须使用微信提供的获取手机号能力，一般流程： 在页面放一个按钮，open-type="getPhoneNumber"（由用户点击触发） 在回调中获取 encryptedData 和 iv 把这两个字段连同 session_key 或 code 发送给你的服务器 服务器用微信提供的解密算法解密出真实手机号，并绑定到用户 前端示例： &lt;button type="primary" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber" &gt; 获取手机号 &lt;/button&gt; Page({ onGetPhoneNumber(e) { if (e.detail.errMsg === 'getPhoneNumber:ok') { const { encryptedData, iv } = e.detail // 建议后台使用 session_key 解密 this.sendPhoneToServer(encryptedData, iv) } else { wx.showToast({ title: '你拒绝了手机号授权', icon: 'none' }) } }, sendPhoneToServer(encryptedData, iv) { wx.request({ url: 'https://your-domain.com/api/bindPhone', method: 'POST', data: { encryptedData, iv }, success: (res) =&gt; { // 后台解密后返回手机号等 } }) } }) 解密逻辑必须在服务器侧完成，避免泄露 session_key 和解密逻辑。 八、登录态校验与过期处理 8.1 前端请求时统一带上 token 封装 request（简化示意）： // utils/request.js const BASE_URL = 'https://your-domain.com' function request(options) { const app = getApp() const token = app.globalData.token || wx.getStorageSync('TOKEN') return new Promise((resolve, reject) =&gt; { wx.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { Authorization: token ? `Bearer ${token}` : '', ...(options.header || {}) }, success(res) { // 举例：后端约定 code = 401 表示未登录 / 登录过期 if (res.data.code === 401) { handleNotLogin() reject(res.data) } else { resolve(res.data) } }, fail(err) { reject(err) } }) }) } function handleNotLogin() { const app = getApp() app.globalData.token = '' app.globalData.userInfo = null wx.removeStorageSync('TOKEN') wx.removeStorageSync('USER_INFO') wx.navigateTo({ url: '/pages/login/login' }) } module.exports = { request } 8.2 后端统一校验 token 拦截所有需要登录的接口 没有 token 或 token 失效 → 返回统一错误码（如 401） 前端根据这个错误码做“清除本地登录信息 + 跳到登录页” 九、注销 / 退出登录 前端逻辑一般： // pages/profile/profile.js Page({ onLogout() { wx.showModal({ title: '提示', content: '确定要退出登录吗？', success: (res) =&gt; { if (res.confirm) { const app = getApp() app.globalData.token = '' app.globalData.userInfo = null wx.removeStorageSync('TOKEN') wx.removeStorageSync('USER_INFO') // 可选：通知服务器注销（清 token） // wx.request({ url: '/api/logout', method: 'POST' }) wx.reLaunch({ url: '/pages/index/index' }) } } }) } }) 十、隐私 &amp; 合规简要提示 在用户第一次打开小程序时，建议展示隐私弹窗（参考微信最新规范） 获取用户信息、手机号、位置等，必须说明用途，并由用户主动触发 不要在未授权的情况下偷偷上报敏感信息 对用户可识别的信息（如手机号）要在服务端做好安全存储和访问控制]]></summary></entry><entry><title type="html">微信小程序学习笔记-6</title><link href="https://altair288.github.io/Blog/wx-miniprogram6/" rel="alternate" type="text/html" title="微信小程序学习笔记-6" /><published>2025-10-14T00:00:00+00:00</published><updated>2025-10-14T00:00:00+00:00</updated><id>https://altair288.github.io/Blog/wx-miniprogram6</id><content type="html" xml:base="https://altair288.github.io/Blog/wx-miniprogram6/"><![CDATA[<h1 id="微信小程序模块化页面学习笔记">微信小程序「模块化页面」学习笔记</h1>

<blockquote>
  <p>目标：学会把“一个大页面”拆成多个可复用的模块（组件 / 公共函数 / 配置），提升代码可维护性与复用性。</p>
</blockquote>

<hr />

<h2 id="一为什么要做模块化页面">一、为什么要做“模块化页面”？</h2>

<p>常见痛点：</p>

<ul>
  <li>单个页面文件越来越长：<code class="language-plaintext highlighter-rouge">wxml</code>、<code class="language-plaintext highlighter-rouge">js</code> 逻辑堆在一起，难以维护</li>
  <li>多个页面重复写类似的 UI 区块：例如商品卡片、标题栏、操作按钮区</li>
  <li>相似的业务逻辑在多个页面拷贝粘贴：例如分页加载、列表刷新</li>
</ul>

<p>模块化的核心目标：</p>

<ol>
  <li><strong>UI 模块化</strong>：把重复使用的 UI 模块拆成组件</li>
  <li><strong>逻辑模块化</strong>：把通用逻辑封装成工具函数 / 行为（behavior）</li>
  <li><strong>配置模块化</strong>：把常量和接口配置抽离到独立文件</li>
</ol>

<hr />

<h2 id="二组件化把重复的-ui-区块抽成组件">二、组件化：把重复的 UI 区块抽成组件</h2>

<h3 id="21-什么时候考虑做组件">2.1 什么时候考虑做组件？</h3>

<ul>
  <li>多个页面出现相同结构的 UI：
    <ul>
      <li>商品卡片、用户信息卡片、空状态提示、标题区、底部操作栏</li>
    </ul>
  </li>
  <li>单个页面内部同一块 UI 重复多次</li>
  <li>某块 UI 逻辑较复杂（内部有状态和交互）</li>
</ul>

<h3 id="22-组件的基本结构回顾">2.2 组件的基本结构回顾</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>components/product-card/
  ├── product-card.wxml
  ├── product-card.wxss
  ├── product-card.js
  └── product-card.json
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">product-card.json</code>：</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"component"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">product-card.wxml</code>（示例）：</p>

<pre><code class="language-wxml">&lt;view class="product-card" bindtap="onTap"&gt;
  &lt;image class="cover" src="" mode="aspectFill" /&gt;
  &lt;view class="info"&gt;
    &lt;view class="title"&gt;&lt;/view&gt;
    &lt;view class="price"&gt;￥&lt;/view&gt;
  &lt;/view&gt;
&lt;/view&gt;
</code></pre>

<p><code class="language-plaintext highlighter-rouge">product-card.js</code>（接收数据 + 发事件）：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Component</span><span class="p">({</span>
  <span class="na">properties</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">item</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">type</span><span class="p">:</span> <span class="nb">Object</span><span class="p">,</span>
      <span class="na">value</span><span class="p">:</span> <span class="p">{}</span>
    <span class="p">}</span>
  <span class="p">},</span>

  <span class="na">methods</span><span class="p">:</span> <span class="p">{</span>
    <span class="nx">onTap</span><span class="p">()</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">triggerEvent</span><span class="p">(</span><span class="dl">'</span><span class="s1">tap</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">item</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">item</span> <span class="p">})</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<p>在页面中使用：</p>

<p>页面 json：</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"usingComponents"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"product-card"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/components/product-card/product-card"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>页面 wxml：</p>

<pre><code class="language-wxml">&lt;product-card
  wx:for=""
  wx:key="id"
  item=""
  bind:tap="onProductTap"
/&gt;
</code></pre>

<p>页面 js：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">list</span><span class="p">:</span> <span class="p">[]</span>
  <span class="p">},</span>

  <span class="nx">onProductTap</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">item</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">item</span>
    <span class="c1">// TODO: 跳转详情页等</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="三逻辑模块化抽离公共函数--请求">三、逻辑模块化：抽离公共函数 / 请求</h2>

<h3 id="31-utils-工具函数">3.1 Utils 工具函数</h3>

<p>项目结构示例：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>utils/
  ├── request.js   # 封装网络请求
  ├── format.js    # 封装格式化相关函数
  └── auth.js      # 登录、权限等
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">utils/request.js</code> 示例：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">BASE_URL</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">https://example.com/api</span><span class="dl">'</span>

<span class="kd">function</span> <span class="nx">request</span><span class="p">({</span> <span class="nx">url</span><span class="p">,</span> <span class="nx">method</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">GET</span><span class="dl">'</span><span class="p">,</span> <span class="nx">data</span> <span class="o">=</span> <span class="p">{}</span> <span class="p">})</span> <span class="p">{</span>
  <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">request</span><span class="p">({</span>
      <span class="na">url</span><span class="p">:</span> <span class="nx">BASE_URL</span> <span class="o">+</span> <span class="nx">url</span><span class="p">,</span>
      <span class="nx">method</span><span class="p">,</span>
      <span class="nx">data</span><span class="p">,</span>
      <span class="na">header</span><span class="p">:</span> <span class="p">{</span>
        <span class="c1">// 示例：统一加 token</span>
        <span class="na">Authorization</span><span class="p">:</span> <span class="nx">wx</span><span class="p">.</span><span class="nx">getStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">token</span><span class="dl">'</span><span class="p">)</span> <span class="o">||</span> <span class="dl">''</span>
      <span class="p">},</span>
      <span class="nx">success</span><span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">statusCode</span> <span class="o">===</span> <span class="mi">200</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">resolve</span><span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">data</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
          <span class="nx">reject</span><span class="p">(</span><span class="nx">res</span><span class="p">)</span>
        <span class="p">}</span>
      <span class="p">},</span>
      <span class="nx">fail</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">reject</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
      <span class="p">}</span>
    <span class="p">})</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
  <span class="nx">request</span>
<span class="p">}</span>
</code></pre></div></div>

<p>页面中使用：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">{</span> <span class="nx">request</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">../../utils/request</span><span class="dl">'</span><span class="p">)</span>

<span class="nx">Page</span><span class="p">({</span>
  <span class="k">async</span> <span class="nx">fetchList</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">request</span><span class="p">({</span> <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/product/list</span><span class="dl">'</span><span class="p">,</span> <span class="na">data</span><span class="p">:</span> <span class="p">{</span> <span class="na">page</span><span class="p">:</span> <span class="mi">1</span> <span class="p">}</span> <span class="p">})</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="na">list</span><span class="p">:</span> <span class="nx">res</span><span class="p">.</span><span class="nx">data</span> <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<blockquote>
  <p>这样每个页面就不用重复写 <code class="language-plaintext highlighter-rouge">wx.request</code>，页面逻辑更干净。</p>
</blockquote>

<h3 id="32-行为behavior抽离通用-page-逻辑进阶">3.2 行为（Behavior）：抽离通用 Page 逻辑（进阶）</h3>

<p>适合场景：</p>

<ul>
  <li>多个页面都有相似的数据结构和方法
    <ul>
      <li>如“列表页”：分页、下拉刷新、上拉加载更多</li>
    </ul>
  </li>
</ul>

<p>示例：封装一个列表行为 <code class="language-plaintext highlighter-rouge">behaviors/list.js</code></p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// behaviors/list.js</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">Behavior</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">list</span><span class="p">:</span> <span class="p">[],</span>
    <span class="na">page</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
    <span class="na">pageSize</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span>
    <span class="na">hasMore</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="na">loading</span><span class="p">:</span> <span class="kc">false</span>
  <span class="p">},</span>

  <span class="na">methods</span><span class="p">:</span> <span class="p">{</span>
    <span class="k">async</span> <span class="nx">loadMore</span><span class="p">()</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">hasMore</span> <span class="o">||</span> <span class="k">this</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">loading</span><span class="p">)</span> <span class="k">return</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="na">loading</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span>
      <span class="kd">const</span> <span class="nx">nextPage</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">page</span> <span class="o">+</span> <span class="mi">1</span>
      <span class="kd">const</span> <span class="nx">list</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">fetchPage</span><span class="p">(</span><span class="nx">nextPage</span><span class="p">)</span> <span class="c1">// 要求页面实现此方法</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span>
        <span class="na">page</span><span class="p">:</span> <span class="nx">nextPage</span><span class="p">,</span>
        <span class="na">list</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">list</span><span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="nx">list</span><span class="p">),</span>
        <span class="na">hasMore</span><span class="p">:</span> <span class="nx">list</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="k">this</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">pageSize</span><span class="p">,</span>
        <span class="na">loading</span><span class="p">:</span> <span class="kc">false</span>
      <span class="p">})</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<p>在页面中使用：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">listBehavior</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">../../behaviors/list</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">request</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">../../utils/request</span><span class="dl">'</span><span class="p">)</span>

<span class="nx">Page</span><span class="p">({</span>
  <span class="na">behaviors</span><span class="p">:</span> <span class="p">[</span><span class="nx">listBehavior</span><span class="p">],</span>

  <span class="k">async</span> <span class="nx">fetchPage</span><span class="p">(</span><span class="nx">page</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">request</span><span class="p">({</span> <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/product/list</span><span class="dl">'</span><span class="p">,</span> <span class="na">data</span><span class="p">:</span> <span class="p">{</span> <span class="nx">page</span> <span class="p">}</span> <span class="p">})</span>
    <span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">data</span>
  <span class="p">},</span>

  <span class="nx">onReachBottom</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">loadMore</span><span class="p">()</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<blockquote>
  <p>行为可以理解为“可混入的公共逻辑模块”，适合多个页面共用复杂逻辑。</p>
</blockquote>

<hr />

<h2 id="四配置模块化常量--接口统一管理">四、配置模块化：常量 / 接口统一管理</h2>

<h3 id="41-常量配置">4.1 常量配置</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>config/
  ├── api.js        # 接口路径
  └── consts.js     # 常量枚举
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">config/api.js</code>：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">API</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">PRODUCT_LIST</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/product/list</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">PRODUCT_DETAIL</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/product/detail</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">USER_INFO</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/user/info</span><span class="dl">'</span>
<span class="p">}</span>

<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">API</span>
</code></pre></div></div>

<p>使用：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">API</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">../../config/api</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">request</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">../../utils/request</span><span class="dl">'</span><span class="p">)</span>

<span class="nx">Page</span><span class="p">({</span>
  <span class="k">async</span> <span class="nx">fetchList</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">request</span><span class="p">({</span> <span class="na">url</span><span class="p">:</span> <span class="nx">API</span><span class="p">.</span><span class="nx">PRODUCT_LIST</span> <span class="p">})</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="na">list</span><span class="p">:</span> <span class="nx">res</span><span class="p">.</span><span class="nx">data</span> <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<blockquote>
  <p>这样改接口路径时，只需要改配置文件，不用到处查找字符串。</p>
</blockquote>

<hr />

<h2 id="五页面内部的区域模块化实践">五、页面内部的“区域模块化”实践</h2>

<p>即使不抽成组件，也可以在一个页面内部做逻辑分区，让结构更清晰。</p>

<h3 id="51-js-中按功能块组织代码">5.1 JS 中按功能块组织代码</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="c1">// UI 数据</span>
    <span class="na">loading</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
    <span class="na">activeTab</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>

    <span class="c1">// 业务数据</span>
    <span class="na">productList</span><span class="p">:</span> <span class="p">[],</span>
    <span class="na">cartList</span><span class="p">:</span> <span class="p">[]</span>
  <span class="p">},</span>

  <span class="c1">// ========== 生命周期 ==========</span>
  <span class="nx">onLoad</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">init</span><span class="p">()</span>
  <span class="p">},</span>

  <span class="c1">// ========== 初始化逻辑 ==========</span>
  <span class="k">async</span> <span class="nx">init</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="na">loading</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span>
    <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span><span class="k">this</span><span class="p">.</span><span class="nx">fetchProductList</span><span class="p">(),</span> <span class="k">this</span><span class="p">.</span><span class="nx">fetchCartList</span><span class="p">()])</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="na">loading</span><span class="p">:</span> <span class="kc">false</span> <span class="p">})</span>
  <span class="p">},</span>

  <span class="c1">// ========== 数据请求 ==========</span>
  <span class="k">async</span> <span class="nx">fetchProductList</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// ...</span>
  <span class="p">},</span>

  <span class="k">async</span> <span class="nx">fetchCartList</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// ...</span>
  <span class="p">},</span>

  <span class="c1">// ========== 事件处理 ==========</span>
  <span class="nx">onTabChange</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="na">activeTab</span><span class="p">:</span> <span class="nx">e</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">index</span> <span class="p">})</span>
  <span class="p">},</span>

  <span class="nx">onProductTap</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">item</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">item</span>
    <span class="c1">// ...</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<blockquote>
  <p>按“生命周期 / 初始化 / 请求 / 事件处理”等分段，让页面 JS 更可读。</p>
</blockquote>

<h3 id="52-wxml-中按区域分块注释">5.2 WXML 中按区域分块注释</h3>

<pre><code class="language-wxml">&lt;!-- 顶部 banner 区 --&gt;
&lt;swiper&gt;...&lt;/swiper&gt;

&lt;!-- 分类 tab 区 --&gt;
&lt;view class="tabs"&gt;...&lt;/view&gt;

&lt;!-- 商品列表区 --&gt;
&lt;view class="product-list"&gt;...&lt;/view&gt;

&lt;!-- 底部操作栏 --&gt;
&lt;view class="bottom-bar"&gt;...&lt;/view&gt;
</code></pre>

<blockquote>
  <p>即使暂时不抽组件，清晰的区域划分也是“模块化”的重要一步。</p>
</blockquote>

<hr />

<h2 id="六小项目实践示例把首页做成模块化">六、小项目实践示例：把“首页”做成模块化</h2>

<p>假设首页包括：</p>

<ul>
  <li>Banner 轮播图</li>
  <li>分类入口区</li>
  <li>推荐商品列表</li>
</ul>

<p>我们可以拆为：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">components/banner-swiper</code>：轮播图组件</li>
  <li><code class="language-plaintext highlighter-rouge">components/category-grid</code>：分类九宫格组件</li>
  <li><code class="language-plaintext highlighter-rouge">components/product-card</code>：商品卡片组件</li>
  <li><code class="language-plaintext highlighter-rouge">utils/request.js</code>：请求封装</li>
  <li><code class="language-plaintext highlighter-rouge">config/api.js</code>：接口地址</li>
</ul>

<p>首页 wxml：</p>

<pre><code class="language-wxml">&lt;view class="page-home"&gt;
  &lt;banner-swiper list="" bind:tap="onBannerTap" /&gt;

  &lt;category-grid list="" bind:tap="onCategoryTap" /&gt;

  &lt;view class="section-title"&gt;推荐商品&lt;/view&gt;
  &lt;product-card
    wx:for=""
    wx:key="id"
    item=""
    bind:tap="onProductTap"
  /&gt;
&lt;/view&gt;
</code></pre>

<p>首页 js：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">API</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">../../config/api</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">request</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">../../utils/request</span><span class="dl">'</span><span class="p">)</span>

<span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">bannerList</span><span class="p">:</span> <span class="p">[],</span>
    <span class="na">categoryList</span><span class="p">:</span> <span class="p">[],</span>
    <span class="na">productList</span><span class="p">:</span> <span class="p">[]</span>
  <span class="p">},</span>

  <span class="nx">onLoad</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">init</span><span class="p">()</span>
  <span class="p">},</span>

  <span class="k">async</span> <span class="nx">init</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="p">[</span><span class="nx">bannerRes</span><span class="p">,</span> <span class="nx">categoryRes</span><span class="p">,</span> <span class="nx">productRes</span><span class="p">]</span> <span class="o">=</span> <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span>
      <span class="nx">request</span><span class="p">({</span> <span class="na">url</span><span class="p">:</span> <span class="nx">API</span><span class="p">.</span><span class="nx">BANNER_LIST</span> <span class="p">}),</span>
      <span class="nx">request</span><span class="p">({</span> <span class="na">url</span><span class="p">:</span> <span class="nx">API</span><span class="p">.</span><span class="nx">CATEGORY_LIST</span> <span class="p">}),</span>
      <span class="nx">request</span><span class="p">({</span> <span class="na">url</span><span class="p">:</span> <span class="nx">API</span><span class="p">.</span><span class="nx">PRODUCT_RECOMMEND</span> <span class="p">})</span>
    <span class="p">])</span>

    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span>
      <span class="na">bannerList</span><span class="p">:</span> <span class="nx">bannerRes</span><span class="p">.</span><span class="nx">data</span><span class="p">,</span>
      <span class="na">categoryList</span><span class="p">:</span> <span class="nx">categoryRes</span><span class="p">.</span><span class="nx">data</span><span class="p">,</span>
      <span class="na">productList</span><span class="p">:</span> <span class="nx">productRes</span><span class="p">.</span><span class="nx">data</span>
    <span class="p">})</span>
  <span class="p">},</span>

  <span class="nx">onBannerTap</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">},</span>
  <span class="nx">onCategoryTap</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">},</span>
  <span class="nx">onProductTap</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<blockquote>
  <p>页面变得更像“拼装模块”，而非堆积代码。</p>
</blockquote>

<hr />

<h2 id="七常见问题与建议">七、常见问题与建议</h2>

<ol>
  <li><strong>什么时候抽组件？什么时候不抽？</strong>
    <ul>
      <li>明显重复出现、后续会复用 → 抽组件</li>
      <li>只在单一页面使用、逻辑简单 → 先不抽，后面多处用了再抽</li>
    </ul>
  </li>
  <li><strong>组件之间通信过于复杂</strong>
    <ul>
      <li>父 → 子：尽量用 <code class="language-plaintext highlighter-rouge">properties</code></li>
      <li>子 → 父：<code class="language-plaintext highlighter-rouge">triggerEvent</code></li>
      <li>复杂跨层级通信：考虑把状态上移到更高层或使用全局状态</li>
    </ul>
  </li>
  <li><strong>utils 里写了太多与页面高度耦合的逻辑</strong>
    <ul>
      <li>工具函数应该是“通用、无业务或弱业务”的</li>
      <li>具体页面逻辑仍然应在页面 / 组件内部</li>
    </ul>
  </li>
  <li><strong>文件过多导致结构混乱</strong>
    <ul>
      <li>使用清晰的目录：<code class="language-plaintext highlighter-rouge">components/</code>、<code class="language-plaintext highlighter-rouge">utils/</code>、<code class="language-plaintext highlighter-rouge">config/</code>、<code class="language-plaintext highlighter-rouge">behaviors/</code></li>
      <li>文件名做到“见名知意”，如 <code class="language-plaintext highlighter-rouge">user-card</code>、<code class="language-plaintext highlighter-rouge">list-behavior</code>、<code class="language-plaintext highlighter-rouge">api.js</code></li>
    </ul>
  </li>
</ol>

<hr />]]></content><author><name></name></author><category term="miniprogram6" /><summary type="html"><![CDATA[微信小程序「模块化页面」学习笔记 目标：学会把“一个大页面”拆成多个可复用的模块（组件 / 公共函数 / 配置），提升代码可维护性与复用性。 一、为什么要做“模块化页面”？ 常见痛点： 单个页面文件越来越长：wxml、js 逻辑堆在一起，难以维护 多个页面重复写类似的 UI 区块：例如商品卡片、标题栏、操作按钮区 相似的业务逻辑在多个页面拷贝粘贴：例如分页加载、列表刷新 模块化的核心目标： UI 模块化：把重复使用的 UI 模块拆成组件 逻辑模块化：把通用逻辑封装成工具函数 / 行为（behavior） 配置模块化：把常量和接口配置抽离到独立文件 二、组件化：把重复的 UI 区块抽成组件 2.1 什么时候考虑做组件？ 多个页面出现相同结构的 UI： 商品卡片、用户信息卡片、空状态提示、标题区、底部操作栏 单个页面内部同一块 UI 重复多次 某块 UI 逻辑较复杂（内部有状态和交互） 2.2 组件的基本结构回顾 components/product-card/ ├── product-card.wxml ├── product-card.wxss ├── product-card.js └── product-card.json product-card.json： { "component": true } product-card.wxml（示例）： &lt;view class="product-card" bindtap="onTap"&gt; &lt;image class="cover" src="" mode="aspectFill" /&gt; &lt;view class="info"&gt; &lt;view class="title"&gt;&lt;/view&gt; &lt;view class="price"&gt;￥&lt;/view&gt; &lt;/view&gt; &lt;/view&gt; product-card.js（接收数据 + 发事件）： Component({ properties: { item: { type: Object, value: {} } }, methods: { onTap() { this.triggerEvent('tap', { item: this.data.item }) } } }) 在页面中使用： 页面 json： { "usingComponents": { "product-card": "/components/product-card/product-card" } } 页面 wxml： &lt;product-card wx:for="" wx:key="id" item="" bind:tap="onProductTap" /&gt; 页面 js： Page({ data: { list: [] }, onProductTap(e) { const item = e.detail.item // TODO: 跳转详情页等 } }) 三、逻辑模块化：抽离公共函数 / 请求 3.1 Utils 工具函数 项目结构示例： utils/ ├── request.js # 封装网络请求 ├── format.js # 封装格式化相关函数 └── auth.js # 登录、权限等 utils/request.js 示例： const BASE_URL = 'https://example.com/api' function request({ url, method = 'GET', data = {} }) { return new Promise((resolve, reject) =&gt; { wx.request({ url: BASE_URL + url, method, data, header: { // 示例：统一加 token Authorization: wx.getStorageSync('token') || '' }, success(res) { if (res.statusCode === 200) { resolve(res.data) } else { reject(res) } }, fail(err) { reject(err) } }) }) } module.exports = { request } 页面中使用： const { request } = require('../../utils/request') Page({ async fetchList() { const res = await request({ url: '/product/list', data: { page: 1 } }) this.setData({ list: res.data }) } }) 这样每个页面就不用重复写 wx.request，页面逻辑更干净。 3.2 行为（Behavior）：抽离通用 Page 逻辑（进阶） 适合场景： 多个页面都有相似的数据结构和方法 如“列表页”：分页、下拉刷新、上拉加载更多 示例：封装一个列表行为 behaviors/list.js // behaviors/list.js module.exports = Behavior({ data: { list: [], page: 1, pageSize: 10, hasMore: true, loading: false }, methods: { async loadMore() { if (!this.data.hasMore || this.data.loading) return this.setData({ loading: true }) const nextPage = this.data.page + 1 const list = await this.fetchPage(nextPage) // 要求页面实现此方法 this.setData({ page: nextPage, list: this.data.list.concat(list), hasMore: list.length === this.data.pageSize, loading: false }) } } }) 在页面中使用： const listBehavior = require('../../behaviors/list') const { request } = require('../../utils/request') Page({ behaviors: [listBehavior], async fetchPage(page) { const res = await request({ url: '/product/list', data: { page } }) return res.data }, onReachBottom() { this.loadMore() } }) 行为可以理解为“可混入的公共逻辑模块”，适合多个页面共用复杂逻辑。 四、配置模块化：常量 / 接口统一管理 4.1 常量配置 config/ ├── api.js # 接口路径 └── consts.js # 常量枚举 config/api.js： const API = { PRODUCT_LIST: '/product/list', PRODUCT_DETAIL: '/product/detail', USER_INFO: '/user/info' } module.exports = API 使用： const API = require('../../config/api') const { request } = require('../../utils/request') Page({ async fetchList() { const res = await request({ url: API.PRODUCT_LIST }) this.setData({ list: res.data }) } }) 这样改接口路径时，只需要改配置文件，不用到处查找字符串。 五、页面内部的“区域模块化”实践 即使不抽成组件，也可以在一个页面内部做逻辑分区，让结构更清晰。 5.1 JS 中按功能块组织代码 Page({ data: { // UI 数据 loading: false, activeTab: 0, // 业务数据 productList: [], cartList: [] }, // ========== 生命周期 ========== onLoad() { this.init() }, // ========== 初始化逻辑 ========== async init() { this.setData({ loading: true }) await Promise.all([this.fetchProductList(), this.fetchCartList()]) this.setData({ loading: false }) }, // ========== 数据请求 ========== async fetchProductList() { // ... }, async fetchCartList() { // ... }, // ========== 事件处理 ========== onTabChange(e) { this.setData({ activeTab: e.detail.index }) }, onProductTap(e) { const item = e.currentTarget.dataset.item // ... } }) 按“生命周期 / 初始化 / 请求 / 事件处理”等分段，让页面 JS 更可读。 5.2 WXML 中按区域分块注释 &lt;!-- 顶部 banner 区 --&gt; &lt;swiper&gt;...&lt;/swiper&gt; &lt;!-- 分类 tab 区 --&gt; &lt;view class="tabs"&gt;...&lt;/view&gt; &lt;!-- 商品列表区 --&gt; &lt;view class="product-list"&gt;...&lt;/view&gt; &lt;!-- 底部操作栏 --&gt; &lt;view class="bottom-bar"&gt;...&lt;/view&gt; 即使暂时不抽组件，清晰的区域划分也是“模块化”的重要一步。 六、小项目实践示例：把“首页”做成模块化 假设首页包括： Banner 轮播图 分类入口区 推荐商品列表 我们可以拆为： components/banner-swiper：轮播图组件 components/category-grid：分类九宫格组件 components/product-card：商品卡片组件 utils/request.js：请求封装 config/api.js：接口地址 首页 wxml： &lt;view class="page-home"&gt; &lt;banner-swiper list="" bind:tap="onBannerTap" /&gt; &lt;category-grid list="" bind:tap="onCategoryTap" /&gt; &lt;view class="section-title"&gt;推荐商品&lt;/view&gt; &lt;product-card wx:for="" wx:key="id" item="" bind:tap="onProductTap" /&gt; &lt;/view&gt; 首页 js： const API = require('../../config/api') const { request } = require('../../utils/request') Page({ data: { bannerList: [], categoryList: [], productList: [] }, onLoad() { this.init() }, async init() { const [bannerRes, categoryRes, productRes] = await Promise.all([ request({ url: API.BANNER_LIST }), request({ url: API.CATEGORY_LIST }), request({ url: API.PRODUCT_RECOMMEND }) ]) this.setData({ bannerList: bannerRes.data, categoryList: categoryRes.data, productList: productRes.data }) }, onBannerTap(e) { /* ... */ }, onCategoryTap(e) { /* ... */ }, onProductTap(e) { /* ... */ } }) 页面变得更像“拼装模块”，而非堆积代码。 七、常见问题与建议 什么时候抽组件？什么时候不抽？ 明显重复出现、后续会复用 → 抽组件 只在单一页面使用、逻辑简单 → 先不抽，后面多处用了再抽 组件之间通信过于复杂 父 → 子：尽量用 properties 子 → 父：triggerEvent 复杂跨层级通信：考虑把状态上移到更高层或使用全局状态 utils 里写了太多与页面高度耦合的逻辑 工具函数应该是“通用、无业务或弱业务”的 具体页面逻辑仍然应在页面 / 组件内部 文件过多导致结构混乱 使用清晰的目录：components/、utils/、config/、behaviors/ 文件名做到“见名知意”，如 user-card、list-behavior、api.js]]></summary></entry><entry><title type="html">微信小程序学习笔记-5</title><link href="https://altair288.github.io/Blog/wx-miniprogram5/" rel="alternate" type="text/html" title="微信小程序学习笔记-5" /><published>2025-10-13T00:00:00+00:00</published><updated>2025-10-13T00:00:00+00:00</updated><id>https://altair288.github.io/Blog/wx-miniprogram5</id><content type="html" xml:base="https://altair288.github.io/Blog/wx-miniprogram5/"><![CDATA[<h1 id="微信小程序全局数据--本地缓存学习笔记">微信小程序「全局数据 &amp; 本地缓存」学习笔记</h1>

<blockquote>
  <p>目标：掌握在小程序中如何存放全局共享数据（<code class="language-plaintext highlighter-rouge">App.globalData</code>）以及如何使用本地缓存（<code class="language-plaintext highlighter-rouge">wx.setStorage</code> / <code class="language-plaintext highlighter-rouge">wx.getStorage</code> 等），并理解各自适用场景。</p>
</blockquote>

<hr />

<h2 id="一为什么需要全局数据和缓存">一、为什么需要全局数据和缓存？</h2>

<p>常见需求：</p>

<ul>
  <li>用户信息、多页面共享的配置（主题、语言、登录状态等）</li>
  <li>登录态、token、用户偏好（夜间模式、上次选择的城市）</li>
  <li>部分数据希望 <strong>进程内共享</strong>（只在本次使用内有效）</li>
  <li>部分数据希望 <strong>下次打开小程序仍然存在</strong>（例如登录信息、历史记录）</li>
</ul>

<p>对比：</p>

<ul>
  <li><strong>全局数据（<code class="language-plaintext highlighter-rouge">globalData</code>）</strong>：只在“这次打开小程序”的生命周期内存在，关闭后清空。</li>
  <li><strong>本地缓存（storage）</strong>：写进本地，关闭重开仍然存在，除非手动清除或被系统清理。</li>
</ul>

<hr />

<h2 id="二全局数据appglobaldata">二、全局数据：<code class="language-plaintext highlighter-rouge">App.globalData</code></h2>

<h3 id="21-定义全局数据">2.1 定义全局数据</h3>

<p><code class="language-plaintext highlighter-rouge">app.js</code>：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">App</span><span class="p">({</span>
  <span class="nx">onLaunch</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 小程序初始化时可以进行一些全局设置</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">App Launch</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">},</span>

  <span class="nx">onShow</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">App Show</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">},</span>

  <span class="na">globalData</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">userInfo</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>      <span class="c1">// 用户信息</span>
    <span class="na">token</span><span class="p">:</span> <span class="dl">''</span><span class="p">,</span>           <span class="c1">// 登录 token</span>
    <span class="na">theme</span><span class="p">:</span> <span class="dl">'</span><span class="s1">light</span><span class="dl">'</span><span class="p">,</span>      <span class="c1">// 主题：light/dark</span>
    <span class="na">language</span><span class="p">:</span> <span class="dl">'</span><span class="s1">zh-CN</span><span class="dl">'</span>    <span class="c1">// 当前语言</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">globalData</code>：一个普通对象，存放全局共享数据</li>
  <li>所有页面和组件都可以通过 <code class="language-plaintext highlighter-rouge">getApp()</code> 访问</li>
</ul>

<h3 id="22-在页面中读取--修改全局数据">2.2 在页面中读取 / 修改全局数据</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">userInfo</span><span class="p">:</span> <span class="kc">null</span>
  <span class="p">},</span>

  <span class="nx">onLoad</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">getApp</span><span class="p">()</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span>
      <span class="na">userInfo</span><span class="p">:</span> <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">userInfo</span>
    <span class="p">})</span>
  <span class="p">},</span>

  <span class="c1">// 更新全局主题</span>
  <span class="nx">switchTheme</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">getApp</span><span class="p">()</span>
    <span class="kd">const</span> <span class="nx">newTheme</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">theme</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">light</span><span class="dl">'</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">dark</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">light</span><span class="dl">'</span>
    <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">theme</span> <span class="o">=</span> <span class="nx">newTheme</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="na">theme</span><span class="p">:</span> <span class="nx">newTheme</span> <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<p>要点：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">getApp()</code> 返回当前小程序的 <strong>App 实例</strong></li>
  <li>不建议在 <code class="language-plaintext highlighter-rouge">App</code> 的 <code class="language-plaintext highlighter-rouge">onLaunch</code> 中立即调用 <code class="language-plaintext highlighter-rouge">getCurrentPages()</code>（这在文档中有说明）</li>
</ul>

<hr />

<h2 id="三全局数据的典型使用场景">三、全局数据的典型使用场景</h2>

<ol>
  <li><strong>用户登录信息（不必持久化的部分）</strong>
    <ul>
      <li>如当前登录用户基础信息</li>
      <li>token 通常会同时放入缓存，便于下次自动登录</li>
    </ul>
  </li>
  <li><strong>在多个页面需要共享的临时状态</strong>
    <ul>
      <li>当前选择的城市 / tab / 主题</li>
      <li>某些操作的临时结果（如一个复杂表单分步保存的数据）</li>
    </ul>
  </li>
  <li><strong>全局配置 &amp; 常量</strong>
    <ul>
      <li>API 基础地址（虽然更推荐单独封装 config 文件）</li>
      <li>业务层常量（如某些枚举）</li>
    </ul>
  </li>
</ol>

<blockquote>
  <p>注意：<strong>全局数据不是持久存储</strong>，小程序被完全关闭（进程被杀）后会消失。</p>
</blockquote>

<hr />

<h2 id="四本地缓存storage基础">四、本地缓存（Storage）基础</h2>

<p>微信提供了两套 API：</p>

<ul>
  <li><strong>同步接口</strong>：<code class="language-plaintext highlighter-rouge">wx.setStorageSync</code> / <code class="language-plaintext highlighter-rouge">wx.getStorageSync</code> / <code class="language-plaintext highlighter-rouge">wx.removeStorageSync</code> / <code class="language-plaintext highlighter-rouge">wx.clearStorageSync</code></li>
  <li><strong>异步接口</strong>：<code class="language-plaintext highlighter-rouge">wx.setStorage</code> / <code class="language-plaintext highlighter-rouge">wx.getStorage</code> / <code class="language-plaintext highlighter-rouge">wx.removeStorage</code> / <code class="language-plaintext highlighter-rouge">wx.clearStorage</code></li>
</ul>

<h3 id="41-同步接口简洁适合小量数据">4.1 同步接口（简洁，适合小量数据）</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 写入</span>
<span class="nx">wx</span><span class="p">.</span><span class="nx">setStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">token</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">abc123</span><span class="dl">'</span><span class="p">)</span>

<span class="c1">// 读取</span>
<span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="nx">wx</span><span class="p">.</span><span class="nx">getStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">token</span><span class="dl">'</span><span class="p">)</span>

<span class="c1">// 删除某项</span>
<span class="nx">wx</span><span class="p">.</span><span class="nx">removeStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">token</span><span class="dl">'</span><span class="p">)</span>

<span class="c1">// 清空所有缓存</span>
<span class="nx">wx</span><span class="p">.</span><span class="nx">clearStorageSync</span><span class="p">()</span>
</code></pre></div></div>

<p>特点：</p>

<ul>
  <li>调用后立即返回，逻辑简单</li>
  <li>在同步调用很多、数据较大时会阻塞 js 线程（注意性能）</li>
</ul>

<h3 id="42-异步接口建议在大多数业务中使用">4.2 异步接口（建议在大多数业务中使用）</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 写入</span>
<span class="nx">wx</span><span class="p">.</span><span class="nx">setStorage</span><span class="p">({</span>
  <span class="na">key</span><span class="p">:</span> <span class="dl">'</span><span class="s1">userInfo</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Tom</span><span class="dl">'</span><span class="p">,</span> <span class="na">age</span><span class="p">:</span> <span class="mi">18</span> <span class="p">},</span>
  <span class="nx">success</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">保存成功</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">},</span>
  <span class="nx">fail</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">保存失败</span><span class="dl">'</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">})</span>

<span class="c1">// 读取</span>
<span class="nx">wx</span><span class="p">.</span><span class="nx">getStorage</span><span class="p">({</span>
  <span class="na">key</span><span class="p">:</span> <span class="dl">'</span><span class="s1">userInfo</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">success</span><span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">读取到的数据：</span><span class="dl">'</span><span class="p">,</span> <span class="nx">res</span><span class="p">.</span><span class="nx">data</span><span class="p">)</span>
  <span class="p">},</span>
  <span class="nx">fail</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">没有这个 key 或读取失败</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">})</span>

<span class="c1">// 删除</span>
<span class="nx">wx</span><span class="p">.</span><span class="nx">removeStorage</span><span class="p">({</span>
  <span class="na">key</span><span class="p">:</span> <span class="dl">'</span><span class="s1">userInfo</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">success</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">删除成功</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">})</span>

<span class="c1">// 清空所有缓存</span>
<span class="nx">wx</span><span class="p">.</span><span class="nx">clearStorage</span><span class="p">()</span>
</code></pre></div></div>

<hr />

<h2 id="五缓存中可以存什么数据">五、缓存中可以存什么数据？</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">data</code> 支持：字符串、数字、布尔、对象、数组等（会被序列化）</li>
  <li>建议存储：
    <ul>
      <li>token、用户 ID、简单用户信息</li>
      <li>用户偏好设置（语言、主题、开关状态）</li>
      <li>非敏感的业务数据缓存（如最近浏览记录）</li>
    </ul>
  </li>
</ul>

<blockquote>
  <p>安全注意：不要在本地缓存中存 <strong>密码</strong> 这类敏感信息。</p>
</blockquote>

<hr />

<h2 id="六全局数据--缓存常见登录流程示例">六、全局数据 + 缓存：常见登录流程示例</h2>

<h3 id="61-登录成功后保存数据">6.1 登录成功后保存数据</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 假设在 login 页面</span>
<span class="nx">loginSuccessHandler</span><span class="p">(</span><span class="nx">loginResult</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">getApp</span><span class="p">()</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="nx">token</span><span class="p">,</span> <span class="nx">userInfo</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">loginResult</span>

  <span class="c1">// 1. 更新全局数据</span>
  <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">token</span> <span class="o">=</span> <span class="nx">token</span>
  <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">userInfo</span> <span class="o">=</span> <span class="nx">userInfo</span>

  <span class="c1">// 2. 写入缓存（方便下次打开自动登录）</span>
  <span class="nx">wx</span><span class="p">.</span><span class="nx">setStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">token</span><span class="dl">'</span><span class="p">,</span> <span class="nx">token</span><span class="p">)</span>
  <span class="nx">wx</span><span class="p">.</span><span class="nx">setStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">userInfo</span><span class="dl">'</span><span class="p">,</span> <span class="nx">userInfo</span><span class="p">)</span>

  <span class="c1">// 3. 跳转到首页</span>
  <span class="nx">wx</span><span class="p">.</span><span class="nx">reLaunch</span><span class="p">({</span>
    <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/index/index</span><span class="dl">'</span>
  <span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="62-小程序启动时尝试从缓存恢复登录态">6.2 小程序启动时尝试从缓存恢复登录态</h3>

<p><code class="language-plaintext highlighter-rouge">app.js</code>：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">App</span><span class="p">({</span>
  <span class="nx">onLaunch</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 从本地缓存中恢复用户信息</span>
    <span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="nx">wx</span><span class="p">.</span><span class="nx">getStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">token</span><span class="dl">'</span><span class="p">)</span>
    <span class="kd">const</span> <span class="nx">userInfo</span> <span class="o">=</span> <span class="nx">wx</span><span class="p">.</span><span class="nx">getStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">userInfo</span><span class="dl">'</span><span class="p">)</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">token</span> <span class="o">=</span> <span class="nx">token</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">userInfo</span> <span class="o">=</span> <span class="nx">userInfo</span>
    <span class="p">}</span>
  <span class="p">},</span>

  <span class="na">globalData</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">token</span><span class="p">:</span> <span class="dl">''</span><span class="p">,</span>
    <span class="na">userInfo</span><span class="p">:</span> <span class="kc">null</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<p>页面中使用时：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="nx">onShow</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">getApp</span><span class="p">()</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// 未登录，跳转到登录页</span>
      <span class="nx">wx</span><span class="p">.</span><span class="nx">navigateTo</span><span class="p">({</span>
        <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/login/login</span><span class="dl">'</span>
      <span class="p">})</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="七全局数据与缓存的对比与搭配使用">七、全局数据与缓存的对比与搭配使用</h2>

<table>
  <thead>
    <tr>
      <th>项目</th>
      <th>全局数据 (<code class="language-plaintext highlighter-rouge">globalData</code>)</th>
      <th>本地缓存 (<code class="language-plaintext highlighter-rouge">storage</code>)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>生命周期</td>
      <td>当前小程序运行期间</td>
      <td>持久存在（直到主动清除或被系统回收）</td>
    </tr>
    <tr>
      <td>访问方式</td>
      <td><code class="language-plaintext highlighter-rouge">getApp().globalData.xxx</code></td>
      <td><code class="language-plaintext highlighter-rouge">wx.getStorage*</code> / <code class="language-plaintext highlighter-rouge">wx.setStorage*</code></td>
    </tr>
    <tr>
      <td>典型用途</td>
      <td>运行时共享状态</td>
      <td>登录态、配置、偏好、历史等</td>
    </tr>
    <tr>
      <td>是否持久化</td>
      <td>否</td>
      <td>是</td>
    </tr>
    <tr>
      <td>应用关闭后是否保留</td>
      <td>否</td>
      <td>是</td>
    </tr>
    <tr>
      <td>是否影响性能</td>
      <td>一般较小</td>
      <td>大量/频繁读写可能有性能影响</td>
    </tr>
  </tbody>
</table>

<p>典型搭配：</p>

<ul>
  <li>登录成功后：
    <ul>
      <li><strong>全局数据</strong>：立即供各页面使用</li>
      <li><strong>缓存</strong>：用于下次启动时恢复状态</li>
    </ul>
  </li>
  <li>设置项（例如夜间模式）：
    <ul>
      <li>页面操作时：更新全局 + 缓存</li>
      <li>启动时：从缓存读出，写入全局</li>
    </ul>
  </li>
</ul>

<hr />

<h2 id="八封装一个-storage-工具模块推荐">八、封装一个 Storage 工具模块（推荐）</h2>

<h3 id="81-创建工具文件">8.1 创建工具文件</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="nx">name</span><span class="o">=</span><span class="nx">utils</span><span class="o">/</span><span class="nx">storage</span><span class="p">.</span><span class="nx">js</span>

<span class="kd">const</span> <span class="nx">TOKEN_KEY</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">TOKEN</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">USER_INFO_KEY</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">USER_INFO</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">SETTINGS_KEY</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">SETTINGS</span><span class="dl">'</span>

<span class="kd">const</span> <span class="nx">storage</span> <span class="o">=</span> <span class="p">{</span>
  <span class="c1">// token</span>
  <span class="nx">setToken</span><span class="p">(</span><span class="nx">token</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">setStorageSync</span><span class="p">(</span><span class="nx">TOKEN_KEY</span><span class="p">,</span> <span class="nx">token</span><span class="p">)</span>
  <span class="p">},</span>
  <span class="nx">getToken</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">wx</span><span class="p">.</span><span class="nx">getStorageSync</span><span class="p">(</span><span class="nx">TOKEN_KEY</span><span class="p">)</span> <span class="o">||</span> <span class="dl">''</span>
  <span class="p">},</span>
  <span class="nx">clearToken</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">removeStorageSync</span><span class="p">(</span><span class="nx">TOKEN_KEY</span><span class="p">)</span>
  <span class="p">},</span>

  <span class="c1">// 用户信息</span>
  <span class="nx">setUserInfo</span><span class="p">(</span><span class="nx">userInfo</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">setStorageSync</span><span class="p">(</span><span class="nx">USER_INFO_KEY</span><span class="p">,</span> <span class="nx">userInfo</span><span class="p">)</span>
  <span class="p">},</span>
  <span class="nx">getUserInfo</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">wx</span><span class="p">.</span><span class="nx">getStorageSync</span><span class="p">(</span><span class="nx">USER_INFO_KEY</span><span class="p">)</span> <span class="o">||</span> <span class="kc">null</span>
  <span class="p">},</span>
  <span class="nx">clearUserInfo</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">removeStorageSync</span><span class="p">(</span><span class="nx">USER_INFO_KEY</span><span class="p">)</span>
  <span class="p">},</span>

  <span class="c1">// 设置（比如主题、语言等）</span>
  <span class="nx">setSettings</span><span class="p">(</span><span class="nx">settings</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">setStorageSync</span><span class="p">(</span><span class="nx">SETTINGS_KEY</span><span class="p">,</span> <span class="nx">settings</span><span class="p">)</span>
  <span class="p">},</span>
  <span class="nx">getSettings</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">wx</span><span class="p">.</span><span class="nx">getStorageSync</span><span class="p">(</span><span class="nx">SETTINGS_KEY</span><span class="p">)</span> <span class="o">||</span> <span class="p">{}</span>
  <span class="p">},</span>

  <span class="nx">clearAll</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">clearStorageSync</span><span class="p">()</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">storage</span>
</code></pre></div></div>

<h3 id="82-在页面或-app-中使用">8.2 在页面或 app 中使用</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1">// app.js</span>
<span class="kd">const</span> <span class="nx">storage</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">./utils/storage</span><span class="dl">'</span><span class="p">)</span>

<span class="nx">App</span><span class="p">({</span>
  <span class="nx">onLaunch</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="nx">storage</span><span class="p">.</span><span class="nx">getToken</span><span class="p">()</span>
    <span class="kd">const</span> <span class="nx">userInfo</span> <span class="o">=</span> <span class="nx">storage</span><span class="p">.</span><span class="nx">getUserInfo</span><span class="p">()</span>
    <span class="kd">const</span> <span class="nx">settings</span> <span class="o">=</span> <span class="nx">storage</span><span class="p">.</span><span class="nx">getSettings</span><span class="p">()</span>

    <span class="k">this</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">token</span> <span class="o">=</span> <span class="nx">token</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">userInfo</span> <span class="o">=</span> <span class="nx">userInfo</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">settings</span> <span class="o">=</span> <span class="nx">settings</span>
  <span class="p">},</span>

  <span class="na">globalData</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">token</span><span class="p">:</span> <span class="dl">''</span><span class="p">,</span>
    <span class="na">userInfo</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
    <span class="na">settings</span><span class="p">:</span> <span class="p">{}</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<p>页面中:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kd">const</span> <span class="nx">storage</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">../../utils/storage</span><span class="dl">'</span><span class="p">)</span>

<span class="nx">Page</span><span class="p">({</span>
  <span class="nx">onLoad</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">settings</span> <span class="o">=</span> <span class="nx">storage</span><span class="p">.</span><span class="nx">getSettings</span><span class="p">()</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="nx">settings</span> <span class="p">})</span>
  <span class="p">},</span>

  <span class="nx">logout</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">getApp</span><span class="p">()</span>
    <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">token</span> <span class="o">=</span> <span class="dl">''</span>
    <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">userInfo</span> <span class="o">=</span> <span class="kc">null</span>
    <span class="nx">storage</span><span class="p">.</span><span class="nx">clearToken</span><span class="p">()</span>
    <span class="nx">storage</span><span class="p">.</span><span class="nx">clearUserInfo</span><span class="p">()</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<h2 id="九注意事项与常见坑">九、注意事项与常见坑</h2>

<ol>
  <li><strong>缓存读不到 / 报 key 不存在</strong>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">wx.getStorageSync('xxx')</code> 如果没有对应 key，返回 <code class="language-plaintext highlighter-rouge">""</code>（空字符串）或 <code class="language-plaintext highlighter-rouge">undefined</code>，要做好防御判断。</li>
      <li>异步的 <code class="language-plaintext highlighter-rouge">wx.getStorage</code> 在 <code class="language-plaintext highlighter-rouge">fail</code> 回调里处理“没有 key”的情况。</li>
    </ul>
  </li>
  <li><strong>频繁大数据读写</strong>
    <ul>
      <li>不要在频繁触发的事件（如滚动、滑动）里大量读写 storage，容易卡顿。</li>
      <li>可在内存（data / globalData）中维护，合适时机再写回缓存。</li>
    </ul>
  </li>
  <li><strong>不同小程序间不共享缓存</strong>
    <ul>
      <li>每个小程序都有自己独立的 storage 空间，不会互相干扰。</li>
    </ul>
  </li>
  <li><strong>key 命名冲突</strong>
    <ul>
      <li>建议封装 storage 工具模块，统一管理 key，避免手写字符串时写错或冲突。</li>
    </ul>
  </li>
  <li><strong>globalData 不是响应式</strong>
    <ul>
      <li>修改 <code class="language-plaintext highlighter-rouge">getApp().globalData.xxx = 1</code> 并不会自动更新页面显示，仍需配合 <code class="language-plaintext highlighter-rouge">this.setData()</code>。</li>
      <li>常见写法：页面读全局数据 + setData 到本页面 data 中。</li>
    </ul>
  </li>
</ol>

<hr />]]></content><author><name></name></author><category term="miniprogram5" /><summary type="html"><![CDATA[微信小程序「全局数据 &amp; 本地缓存」学习笔记 目标：掌握在小程序中如何存放全局共享数据（App.globalData）以及如何使用本地缓存（wx.setStorage / wx.getStorage 等），并理解各自适用场景。 一、为什么需要全局数据和缓存？ 常见需求： 用户信息、多页面共享的配置（主题、语言、登录状态等） 登录态、token、用户偏好（夜间模式、上次选择的城市） 部分数据希望 进程内共享（只在本次使用内有效） 部分数据希望 下次打开小程序仍然存在（例如登录信息、历史记录） 对比： 全局数据（globalData）：只在“这次打开小程序”的生命周期内存在，关闭后清空。 本地缓存（storage）：写进本地，关闭重开仍然存在，除非手动清除或被系统清理。 二、全局数据：App.globalData 2.1 定义全局数据 app.js： App({ onLaunch() { // 小程序初始化时可以进行一些全局设置 console.log('App Launch') }, onShow() { console.log('App Show') }, globalData: { userInfo: null, // 用户信息 token: '', // 登录 token theme: 'light', // 主题：light/dark language: 'zh-CN' // 当前语言 } }) globalData：一个普通对象，存放全局共享数据 所有页面和组件都可以通过 getApp() 访问 2.2 在页面中读取 / 修改全局数据 Page({ data: { userInfo: null }, onLoad() { const app = getApp() this.setData({ userInfo: app.globalData.userInfo }) }, // 更新全局主题 switchTheme() { const app = getApp() const newTheme = app.globalData.theme === 'light' ? 'dark' : 'light' app.globalData.theme = newTheme this.setData({ theme: newTheme }) } }) 要点： getApp() 返回当前小程序的 App 实例 不建议在 App 的 onLaunch 中立即调用 getCurrentPages()（这在文档中有说明） 三、全局数据的典型使用场景 用户登录信息（不必持久化的部分） 如当前登录用户基础信息 token 通常会同时放入缓存，便于下次自动登录 在多个页面需要共享的临时状态 当前选择的城市 / tab / 主题 某些操作的临时结果（如一个复杂表单分步保存的数据） 全局配置 &amp; 常量 API 基础地址（虽然更推荐单独封装 config 文件） 业务层常量（如某些枚举） 注意：全局数据不是持久存储，小程序被完全关闭（进程被杀）后会消失。 四、本地缓存（Storage）基础 微信提供了两套 API： 同步接口：wx.setStorageSync / wx.getStorageSync / wx.removeStorageSync / wx.clearStorageSync 异步接口：wx.setStorage / wx.getStorage / wx.removeStorage / wx.clearStorage 4.1 同步接口（简洁，适合小量数据） // 写入 wx.setStorageSync('token', 'abc123') // 读取 const token = wx.getStorageSync('token') // 删除某项 wx.removeStorageSync('token') // 清空所有缓存 wx.clearStorageSync() 特点： 调用后立即返回，逻辑简单 在同步调用很多、数据较大时会阻塞 js 线程（注意性能） 4.2 异步接口（建议在大多数业务中使用） // 写入 wx.setStorage({ key: 'userInfo', data: { name: 'Tom', age: 18 }, success() { console.log('保存成功') }, fail(err) { console.error('保存失败', err) } }) // 读取 wx.getStorage({ key: 'userInfo', success(res) { console.log('读取到的数据：', res.data) }, fail() { console.log('没有这个 key 或读取失败') } }) // 删除 wx.removeStorage({ key: 'userInfo', success() { console.log('删除成功') } }) // 清空所有缓存 wx.clearStorage() 五、缓存中可以存什么数据？ data 支持：字符串、数字、布尔、对象、数组等（会被序列化） 建议存储： token、用户 ID、简单用户信息 用户偏好设置（语言、主题、开关状态） 非敏感的业务数据缓存（如最近浏览记录） 安全注意：不要在本地缓存中存 密码 这类敏感信息。 六、全局数据 + 缓存：常见登录流程示例 6.1 登录成功后保存数据 // 假设在 login 页面 loginSuccessHandler(loginResult) { const app = getApp() const { token, userInfo } = loginResult // 1. 更新全局数据 app.globalData.token = token app.globalData.userInfo = userInfo // 2. 写入缓存（方便下次打开自动登录） wx.setStorageSync('token', token) wx.setStorageSync('userInfo', userInfo) // 3. 跳转到首页 wx.reLaunch({ url: '/pages/index/index' }) } 6.2 小程序启动时尝试从缓存恢复登录态 app.js： App({ onLaunch() { // 从本地缓存中恢复用户信息 const token = wx.getStorageSync('token') const userInfo = wx.getStorageSync('userInfo') if (token) { this.globalData.token = token this.globalData.userInfo = userInfo } }, globalData: { token: '', userInfo: null } }) 页面中使用时： Page({ onShow() { const app = getApp() if (!app.globalData.token) { // 未登录，跳转到登录页 wx.navigateTo({ url: '/pages/login/login' }) } } }) 七、全局数据与缓存的对比与搭配使用 项目 全局数据 (globalData) 本地缓存 (storage) 生命周期 当前小程序运行期间 持久存在（直到主动清除或被系统回收） 访问方式 getApp().globalData.xxx wx.getStorage* / wx.setStorage* 典型用途 运行时共享状态 登录态、配置、偏好、历史等 是否持久化 否 是 应用关闭后是否保留 否 是 是否影响性能 一般较小 大量/频繁读写可能有性能影响 典型搭配： 登录成功后： 全局数据：立即供各页面使用 缓存：用于下次启动时恢复状态 设置项（例如夜间模式）： 页面操作时：更新全局 + 缓存 启动时：从缓存读出，写入全局 八、封装一个 Storage 工具模块（推荐） 8.1 创建工具文件 name=utils/storage.js const TOKEN_KEY = 'TOKEN' const USER_INFO_KEY = 'USER_INFO' const SETTINGS_KEY = 'SETTINGS' const storage = { // token setToken(token) { wx.setStorageSync(TOKEN_KEY, token) }, getToken() { return wx.getStorageSync(TOKEN_KEY) || '' }, clearToken() { wx.removeStorageSync(TOKEN_KEY) }, // 用户信息 setUserInfo(userInfo) { wx.setStorageSync(USER_INFO_KEY, userInfo) }, getUserInfo() { return wx.getStorageSync(USER_INFO_KEY) || null }, clearUserInfo() { wx.removeStorageSync(USER_INFO_KEY) }, // 设置（比如主题、语言等） setSettings(settings) { wx.setStorageSync(SETTINGS_KEY, settings) }, getSettings() { return wx.getStorageSync(SETTINGS_KEY) || {} }, clearAll() { wx.clearStorageSync() } } module.exports = storage 8.2 在页面或 app 中使用 // app.js const storage = require('./utils/storage') App({ onLaunch() { const token = storage.getToken() const userInfo = storage.getUserInfo() const settings = storage.getSettings() this.globalData.token = token this.globalData.userInfo = userInfo this.globalData.settings = settings }, globalData: { token: '', userInfo: null, settings: {} } }) 页面中: const storage = require('../../utils/storage') Page({ onLoad() { const settings = storage.getSettings() this.setData({ settings }) }, logout() { const app = getApp() app.globalData.token = '' app.globalData.userInfo = null storage.clearToken() storage.clearUserInfo() } }) 九、注意事项与常见坑 缓存读不到 / 报 key 不存在 wx.getStorageSync('xxx') 如果没有对应 key，返回 ""（空字符串）或 undefined，要做好防御判断。 异步的 wx.getStorage 在 fail 回调里处理“没有 key”的情况。 频繁大数据读写 不要在频繁触发的事件（如滚动、滑动）里大量读写 storage，容易卡顿。 可在内存（data / globalData）中维护，合适时机再写回缓存。 不同小程序间不共享缓存 每个小程序都有自己独立的 storage 空间，不会互相干扰。 key 命名冲突 建议封装 storage 工具模块，统一管理 key，避免手写字符串时写错或冲突。 globalData 不是响应式 修改 getApp().globalData.xxx = 1 并不会自动更新页面显示，仍需配合 this.setData()。 常见写法：页面读全局数据 + setData 到本页面 data 中。]]></summary></entry><entry><title type="html">微信小程序学习笔记-4</title><link href="https://altair288.github.io/Blog/wx-miniprogram4/" rel="alternate" type="text/html" title="微信小程序学习笔记-4" /><published>2025-10-12T00:00:00+00:00</published><updated>2025-10-12T00:00:00+00:00</updated><id>https://altair288.github.io/Blog/wx-miniprogram4</id><content type="html" xml:base="https://altair288.github.io/Blog/wx-miniprogram4/"><![CDATA[<h1 id="微信小程序页面与路由学习笔记">微信小程序「页面与路由」学习笔记</h1>

<blockquote>
  <p>目标：理解小程序“页面”与“路由栈”的概念，熟练使用各种跳转方式（navigateTo / redirectTo / switchTab / reLaunch / navigateBack），并能在项目中设计合理的页面结构和跳转流程。</p>
</blockquote>

<hr />

<h2 id="一核心概念页面--路由栈">一、核心概念：页面 &amp; 路由栈</h2>

<h3 id="11-页面page">1.1 页面（page）</h3>

<ul>
  <li>每一个 <code class="language-plaintext highlighter-rouge">pages/xxx/xxx</code> 下的 <code class="language-plaintext highlighter-rouge">xxx.js + xxx.wxml + xxx.wxss + xxx.json</code> 就是一个页面</li>
  <li>在 <code class="language-plaintext highlighter-rouge">app.json</code> 的 <code class="language-plaintext highlighter-rouge">pages</code> 数组中注册：
    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"pages"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"pages/index/index"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"pages/logs/logs"</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li><code class="language-plaintext highlighter-rouge">pages</code> 数组的第一项，就是小程序的首页</li>
</ul>

<h3 id="12-路由栈页面栈">1.2 路由栈（页面栈）</h3>

<ul>
  <li>小程序维护一个页面栈（类似浏览器的历史记录）</li>
  <li>新开页面 → 入栈<br />
返回上一页 → 出栈</li>
  <li>栈顶页面 = 当前正在展示的页面</li>
</ul>

<p>可使用 <code class="language-plaintext highlighter-rouge">getCurrentPages()</code> 查看当前页面栈：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">pages</span> <span class="o">=</span> <span class="nx">getCurrentPages</span><span class="p">()</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">pages</span><span class="p">)</span>
</code></pre></div></div>

<blockquote>
  <p>注意：<code class="language-plaintext highlighter-rouge">getCurrentPages()</code> 只能在页面/组件逻辑中使用，不能在 app.js 的 <code class="language-plaintext highlighter-rouge">onLaunch</code> 里使用。</p>
</blockquote>

<hr />

<h2 id="二常用的-5-种页面跳转方式">二、常用的 5 种页面跳转方式</h2>

<h3 id="21-wxnavigateto--普通跳转入栈">2.1 <code class="language-plaintext highlighter-rouge">wx.navigateTo</code> —— 普通跳转（入栈）</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">wx</span><span class="p">.</span><span class="nx">navigateTo</span><span class="p">({</span>
  <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/detail/detail?id=123</span><span class="dl">'</span>
<span class="p">})</span>
</code></pre></div></div>

<p>特点：</p>

<ul>
  <li>打开非 <code class="language-plaintext highlighter-rouge">tabBar</code> 页面</li>
  <li><strong>保留当前页面</strong>，新页面压入栈顶</li>
  <li>支持传递参数（通过 url query）</li>
</ul>

<p>适用场景：</p>

<ul>
  <li>列表 → 详情</li>
  <li>首页 → 二级页面</li>
</ul>

<hr />

<h3 id="22-wxredirectto--替换当前页面">2.2 <code class="language-plaintext highlighter-rouge">wx.redirectTo</code> —— 替换当前页面</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">wx</span><span class="p">.</span><span class="nx">redirectTo</span><span class="p">({</span>
  <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/login/login</span><span class="dl">'</span>
<span class="p">})</span>
</code></pre></div></div>

<p>特点：</p>

<ul>
  <li>打开非 <code class="language-plaintext highlighter-rouge">tabBar</code> 页面</li>
  <li><strong>关闭当前页面</strong>，用新页面替换当前栈顶</li>
  <li>页面栈长度不增加</li>
</ul>

<p>适用场景：</p>

<ul>
  <li>不需要返回上一页的跳转（比如登录成功后不需要再回登录页）</li>
  <li>流程跳转中“不可返回”的节点</li>
</ul>

<hr />

<h3 id="23-wxswitchtab--切换-tabbar-页面">2.3 <code class="language-plaintext highlighter-rouge">wx.switchTab</code> —— 切换 tabBar 页面</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">wx</span><span class="p">.</span><span class="nx">switchTab</span><span class="p">({</span>
  <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/index/index</span><span class="dl">'</span>
<span class="p">})</span>
</code></pre></div></div>

<p>特点：</p>

<ul>
  <li>只能跳转到 <code class="language-plaintext highlighter-rouge">app.json</code> 中 <code class="language-plaintext highlighter-rouge">tabBar.list</code> 声明的页面</li>
  <li>切换后会 <strong>清空其它非 tabBar 页面的栈记录</strong></li>
  <li>不支持携带 query 参数（即 url 后面不能拼 <code class="language-plaintext highlighter-rouge">?a=1</code>）</li>
</ul>

<blockquote>
  <p>如需在切换 tab 时传参，一般方案是：</p>
  <ul>
    <li>使用全局变量 / 全局状态管理</li>
    <li>使用本地存储 <code class="language-plaintext highlighter-rouge">wx.setStorageSync</code> / <code class="language-plaintext highlighter-rouge">wx.setStorage</code></li>
  </ul>
</blockquote>

<hr />

<h3 id="24-wxrelaunch--关闭所有页面并打开新页面">2.4 <code class="language-plaintext highlighter-rouge">wx.reLaunch</code> —— 关闭所有页面并打开新页面</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">wx</span><span class="p">.</span><span class="nx">reLaunch</span><span class="p">({</span>
  <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/index/index</span><span class="dl">'</span>
<span class="p">})</span>
</code></pre></div></div>

<p>特点：</p>

<ul>
  <li>关闭所有页面（清空页面栈）</li>
  <li>打开指定的页面</li>
  <li>可以是 tabBar 页，也可以是普通页</li>
</ul>

<p>适用场景：</p>

<ul>
  <li>登录成功后，直接回到首页，避免用户通过返回按钮回到登录页</li>
  <li>从错误页面重进主流程</li>
</ul>

<hr />

<h3 id="25-wxnavigateback--返回上一页--多级返回">2.5 <code class="language-plaintext highlighter-rouge">wx.navigateBack</code> —— 返回上一页 / 多级返回</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 返回上一页</span>
<span class="nx">wx</span><span class="p">.</span><span class="nx">navigateBack</span><span class="p">({</span>
  <span class="na">delta</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">})</span>

<span class="c1">// 返回上两级</span>
<span class="nx">wx</span><span class="p">.</span><span class="nx">navigateBack</span><span class="p">({</span>
  <span class="na">delta</span><span class="p">:</span> <span class="mi">2</span>
<span class="p">})</span>
</code></pre></div></div>

<p>特点：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">delta</code> 默认是 1</li>
  <li>如果 <code class="language-plaintext highlighter-rouge">delta</code> 超过页面栈长度，会回到栈底页面</li>
</ul>

<hr />

<h2 id="三参数传递与接收">三、参数传递与接收</h2>

<h3 id="31-通过-url-传参">3.1 通过 URL 传参</h3>

<p><strong>跳转端：</strong></p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">wx</span><span class="p">.</span><span class="nx">navigateTo</span><span class="p">({</span>
  <span class="na">url</span><span class="p">:</span> <span class="s2">`/pages/detail/detail?id=</span><span class="p">${</span><span class="nx">item</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">&amp;name=</span><span class="p">${</span><span class="nx">item</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">`</span>
<span class="p">})</span>
</code></pre></div></div>

<p><strong>接收端（<code class="language-plaintext highlighter-rouge">pages/detail/detail.js</code>）：</strong></p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="nx">onLoad</span><span class="p">(</span><span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">options</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="nx">options</span><span class="p">.</span><span class="nx">name</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">onLoad(options)</code> 中即可拿到所有 query 参数（字符串类型）</li>
</ul>

<h3 id="32-参数类型处理">3.2 参数类型处理</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="nx">onLoad</span><span class="p">(</span><span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">id</span> <span class="o">=</span> <span class="nb">Number</span><span class="p">(</span><span class="nx">options</span><span class="p">.</span><span class="nx">id</span><span class="p">)</span>  <span class="c1">// 字符串转数字</span>
    <span class="kd">const</span> <span class="nx">isVip</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">isVip</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">true</span><span class="dl">'</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">id</span><span class="p">,</span> <span class="nx">isVip</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<blockquote>
  <p>要注意：路由参数一律为字符串，需要自己进行类型转换。</p>
</blockquote>

<hr />

<h2 id="四页面生命周期与路由关系">四、页面生命周期与路由关系</h2>

<h3 id="41-单个页面生命周期回顾">4.1 单个页面生命周期回顾</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="nx">onLoad</span><span class="p">(</span><span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 页面加载，获得路由参数</span>
  <span class="p">},</span>

  <span class="nx">onShow</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 页面显示（每次进入 / 回到页面都会触发）</span>
  <span class="p">},</span>

  <span class="nx">onReady</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 首次渲染完成</span>
  <span class="p">},</span>

  <span class="nx">onHide</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 页面隐藏：被 navigateTo 其他页面或切到后台</span>
  <span class="p">},</span>

  <span class="nx">onUnload</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 页面卸载：navigateBack / redirectTo / reLaunch 导致当前页面被销毁</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<h3 id="42-路由操作与生命周期触发对照">4.2 路由操作与生命周期触发对照</h3>

<table>
  <thead>
    <tr>
      <th>跳转方式</th>
      <th>当前页面生命周期</th>
      <th>目标页面生命周期</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">navigateTo</code></td>
      <td>当前页触发 <code class="language-plaintext highlighter-rouge">onHide</code></td>
      <td>目标页触发 <code class="language-plaintext highlighter-rouge">onLoad</code>→<code class="language-plaintext highlighter-rouge">onShow</code>→<code class="language-plaintext highlighter-rouge">onReady</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">redirectTo</code></td>
      <td>当前页触发 <code class="language-plaintext highlighter-rouge">onUnload</code></td>
      <td>目标页触发 <code class="language-plaintext highlighter-rouge">onLoad</code>→<code class="language-plaintext highlighter-rouge">onShow</code>→<code class="language-plaintext highlighter-rouge">onReady</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">switchTab</code></td>
      <td>当前页 <code class="language-plaintext highlighter-rouge">onHide</code> / <code class="language-plaintext highlighter-rouge">onUnload</code>（视栈情况）</td>
      <td>目标 tab 页 <code class="language-plaintext highlighter-rouge">onLoad</code>(首次) + <code class="language-plaintext highlighter-rouge">onShow</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">reLaunch</code></td>
      <td>所有页触发 <code class="language-plaintext highlighter-rouge">onUnload</code></td>
      <td>新页触发 <code class="language-plaintext highlighter-rouge">onLoad</code>→<code class="language-plaintext highlighter-rouge">onShow</code>→<code class="language-plaintext highlighter-rouge">onReady</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">navigateBack</code></td>
      <td>当前页触发 <code class="language-plaintext highlighter-rouge">onUnload</code></td>
      <td>上一页触发 <code class="language-plaintext highlighter-rouge">onShow</code></td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p>常见实战点：</p>
  <ul>
    <li>需要在“回到页面时刷新数据” → 放在 <code class="language-plaintext highlighter-rouge">onShow</code></li>
    <li>仅首次进入页面请求数据 → 放在 <code class="language-plaintext highlighter-rouge">onLoad</code></li>
  </ul>
</blockquote>

<hr />

<h2 id="五实践列表页--详情页--返回">五、实践：列表页 → 详情页 → 返回</h2>

<h3 id="51-列表页pageslistlistwxml">5.1 列表页（<code class="language-plaintext highlighter-rouge">pages/list/list.wxml</code>）</h3>

<pre><code class="language-wxml">&lt;view wx:for="" wx:key="id" class="item"
      data-id=""
      bindtap="goDetail"&gt;
  
&lt;/view&gt;
</code></pre>

<p><code class="language-plaintext highlighter-rouge">pages/list/list.js</code>：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">list</span><span class="p">:</span> <span class="p">[</span>
      <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">文章一</span><span class="dl">'</span> <span class="p">},</span>
      <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">文章二</span><span class="dl">'</span> <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">},</span>

  <span class="nx">goDetail</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">id</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">navigateTo</span><span class="p">({</span>
      <span class="na">url</span><span class="p">:</span> <span class="s2">`/pages/detail/detail?id=</span><span class="p">${</span><span class="nx">id</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<h3 id="52-详情页pagesdetaildetailjs">5.2 详情页（<code class="language-plaintext highlighter-rouge">pages/detail/detail.js</code>）</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">id</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
    <span class="na">detail</span><span class="p">:</span> <span class="kc">null</span>
  <span class="p">},</span>

  <span class="nx">onLoad</span><span class="p">(</span><span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">id</span> <span class="o">=</span> <span class="nb">Number</span><span class="p">(</span><span class="nx">options</span><span class="p">.</span><span class="nx">id</span><span class="p">)</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="nx">id</span> <span class="p">})</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">fetchDetail</span><span class="p">(</span><span class="nx">id</span><span class="p">)</span>
  <span class="p">},</span>

  <span class="nx">fetchDetail</span><span class="p">(</span><span class="nx">id</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 模拟请求</span>
    <span class="kd">const</span> <span class="nx">detail</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">id</span><span class="p">,</span>
      <span class="na">title</span><span class="p">:</span> <span class="s2">`文章 </span><span class="p">${</span><span class="nx">id</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
      <span class="na">content</span><span class="p">:</span> <span class="s2">`这是文章 </span><span class="p">${</span><span class="nx">id</span><span class="p">}</span><span class="s2"> 的内容`</span>
    <span class="p">}</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="nx">detail</span> <span class="p">})</span>
  <span class="p">},</span>

  <span class="nx">back</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">navigateBack</span><span class="p">()</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">pages/detail/detail.wxml</code>：</p>

<pre><code class="language-wxml">&lt;view&gt;详情：&lt;/view&gt;
&lt;view&gt;&lt;/view&gt;
&lt;button bindtap="back"&gt;返回列表&lt;/button&gt;
</code></pre>

<hr />

<h2 id="六路由跳转与-tabbar-配合示例">六、路由跳转与 tabBar 配合示例</h2>

<p>假设定义了 tabBar：</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"tabBar"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"list"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"pagePath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pages/index/index"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"首页"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"pagePath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pages/profile/profile"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"我的"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="61-非-tab-页面跳转到-tab-页面">6.1 非 tab 页面跳转到 tab 页面</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 比如在某个设置页点击“回首页”</span>
<span class="nx">wx</span><span class="p">.</span><span class="nx">switchTab</span><span class="p">({</span>
  <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/index/index</span><span class="dl">'</span>
<span class="p">})</span>
</code></pre></div></div>

<h3 id="62-登录成功后清空栈并回首页">6.2 登录成功后，清空栈并回首页</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 登录成功后的回调</span>
<span class="nx">wx</span><span class="p">.</span><span class="nx">reLaunch</span><span class="p">({</span>
  <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/index/index</span><span class="dl">'</span>
<span class="p">})</span>
</code></pre></div></div>

<blockquote>
  <p>小技巧：避免用户使用“返回”按钮回到登录页或其他流程页。</p>
</blockquote>

<hr />

<h2 id="七设计路由结构的实践经验">七、设计路由结构的实践经验</h2>

<ol>
  <li><strong>首页 + tabBar = 导航骨架</strong>
    <ul>
      <li>典型结构：<code class="language-plaintext highlighter-rouge">首页 / 分类 / 购物车 / 我的</code></li>
      <li>tabBar 页面尽量作为一级入口，不要做复杂流程</li>
    </ul>
  </li>
  <li><strong>深度页面使用 <code class="language-plaintext highlighter-rouge">navigateTo</code></strong>
    <ul>
      <li>列表 → 详情 → 子详情 → …</li>
      <li>用户可以逐级返回</li>
    </ul>
  </li>
  <li><strong>不应该返回的页面使用 <code class="language-plaintext highlighter-rouge">redirectTo</code> 或 <code class="language-plaintext highlighter-rouge">reLaunch</code></strong>
    <ul>
      <li>如：完成支付成功页 → <code class="language-plaintext highlighter-rouge">redirectTo</code> 到“订单详情页”</li>
      <li>引导页 / 新手教程完成后 → <code class="language-plaintext highlighter-rouge">reLaunch</code> 到首页</li>
    </ul>
  </li>
  <li><strong>参数设计</strong>
    <ul>
      <li>尽量使用简短、易理解的字段，如 <code class="language-plaintext highlighter-rouge">id</code>、<code class="language-plaintext highlighter-rouge">type</code>、<code class="language-plaintext highlighter-rouge">from</code></li>
      <li>复杂数据用 ID 传递，并在目标页重新请求详细数据</li>
    </ul>
  </li>
</ol>

<hr />

<h2 id="八常见问题与易踩坑">八、常见问题与易踩坑</h2>

<ol>
  <li><strong>在页面 B 调 <code class="language-plaintext highlighter-rouge">navigateBack</code>，却没有回到预期页面</strong>
    <ul>
      <li>可能：页面栈层级与预期不一致</li>
      <li>建议：在关键流程中打印 <code class="language-plaintext highlighter-rouge">getCurrentPages()</code> 检查栈结构</li>
    </ul>
  </li>
  <li><strong>跳转到 tabBar 页面失败</strong>
    <ul>
      <li>检查：目标页面是否已经配置在 <code class="language-plaintext highlighter-rouge">app.json</code> 的 <code class="language-plaintext highlighter-rouge">tabBar.list</code> 中</li>
      <li>使用方式：必须用 <code class="language-plaintext highlighter-rouge">wx.switchTab</code>，而不是 <code class="language-plaintext highlighter-rouge">navigateTo</code></li>
    </ul>
  </li>
  <li><strong>切换 tab 时想要传参</strong>
    <ul>
      <li>不能在 <code class="language-plaintext highlighter-rouge">url</code> 后加 query</li>
      <li>替代方法：
        <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 设置全局变量</span>
<span class="nx">getApp</span><span class="p">().</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">fromPage</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">xxx</span><span class="dl">'</span>

<span class="nx">wx</span><span class="p">.</span><span class="nx">switchTab</span><span class="p">({</span> <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/index/index</span><span class="dl">'</span> <span class="p">})</span>

<span class="c1">// 在 index 页 onShow 中读取</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">getApp</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">fromPage</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="nx">globalData</span><span class="p">.</span><span class="nx">fromPage</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li><strong><code class="language-plaintext highlighter-rouge">onLoad</code> 中通过 <code class="language-plaintext highlighter-rouge">getCurrentPages()</code> 获取不到当前页</strong>
    <ul>
      <li>文档说明：<code class="language-plaintext highlighter-rouge">getCurrentPages()</code> 中的页面实例与当前页面文件中的 <code class="language-plaintext highlighter-rouge">Page</code> 对象<strong>不是同一个引用</strong></li>
      <li>一般调试用即可，不要做复杂逻辑依赖</li>
    </ul>
  </li>
</ol>

<hr />]]></content><author><name></name></author><category term="miniprogram4" /><summary type="html"><![CDATA[微信小程序「页面与路由」学习笔记 目标：理解小程序“页面”与“路由栈”的概念，熟练使用各种跳转方式（navigateTo / redirectTo / switchTab / reLaunch / navigateBack），并能在项目中设计合理的页面结构和跳转流程。 一、核心概念：页面 &amp; 路由栈 1.1 页面（page） 每一个 pages/xxx/xxx 下的 xxx.js + xxx.wxml + xxx.wxss + xxx.json 就是一个页面 在 app.json 的 pages 数组中注册： { "pages": [ "pages/index/index", "pages/logs/logs" ] } pages 数组的第一项，就是小程序的首页 1.2 路由栈（页面栈） 小程序维护一个页面栈（类似浏览器的历史记录） 新开页面 → 入栈 返回上一页 → 出栈 栈顶页面 = 当前正在展示的页面 可使用 getCurrentPages() 查看当前页面栈： const pages = getCurrentPages() console.log(pages) 注意：getCurrentPages() 只能在页面/组件逻辑中使用，不能在 app.js 的 onLaunch 里使用。 二、常用的 5 种页面跳转方式 2.1 wx.navigateTo —— 普通跳转（入栈） wx.navigateTo({ url: '/pages/detail/detail?id=123' }) 特点： 打开非 tabBar 页面 保留当前页面，新页面压入栈顶 支持传递参数（通过 url query） 适用场景： 列表 → 详情 首页 → 二级页面 2.2 wx.redirectTo —— 替换当前页面 wx.redirectTo({ url: '/pages/login/login' }) 特点： 打开非 tabBar 页面 关闭当前页面，用新页面替换当前栈顶 页面栈长度不增加 适用场景： 不需要返回上一页的跳转（比如登录成功后不需要再回登录页） 流程跳转中“不可返回”的节点 2.3 wx.switchTab —— 切换 tabBar 页面 wx.switchTab({ url: '/pages/index/index' }) 特点： 只能跳转到 app.json 中 tabBar.list 声明的页面 切换后会 清空其它非 tabBar 页面的栈记录 不支持携带 query 参数（即 url 后面不能拼 ?a=1） 如需在切换 tab 时传参，一般方案是： 使用全局变量 / 全局状态管理 使用本地存储 wx.setStorageSync / wx.setStorage 2.4 wx.reLaunch —— 关闭所有页面并打开新页面 wx.reLaunch({ url: '/pages/index/index' }) 特点： 关闭所有页面（清空页面栈） 打开指定的页面 可以是 tabBar 页，也可以是普通页 适用场景： 登录成功后，直接回到首页，避免用户通过返回按钮回到登录页 从错误页面重进主流程 2.5 wx.navigateBack —— 返回上一页 / 多级返回 // 返回上一页 wx.navigateBack({ delta: 1 }) // 返回上两级 wx.navigateBack({ delta: 2 }) 特点： delta 默认是 1 如果 delta 超过页面栈长度，会回到栈底页面 三、参数传递与接收 3.1 通过 URL 传参 跳转端： wx.navigateTo({ url: `/pages/detail/detail?id=${item.id}&amp;name=${item.name}` }) 接收端（pages/detail/detail.js）： Page({ onLoad(options) { console.log(options.id, options.name) } }) onLoad(options) 中即可拿到所有 query 参数（字符串类型） 3.2 参数类型处理 Page({ onLoad(options) { const id = Number(options.id) // 字符串转数字 const isVip = options.isVip === 'true' console.log(id, isVip) } }) 要注意：路由参数一律为字符串，需要自己进行类型转换。 四、页面生命周期与路由关系 4.1 单个页面生命周期回顾 Page({ onLoad(options) { // 页面加载，获得路由参数 }, onShow() { // 页面显示（每次进入 / 回到页面都会触发） }, onReady() { // 首次渲染完成 }, onHide() { // 页面隐藏：被 navigateTo 其他页面或切到后台 }, onUnload() { // 页面卸载：navigateBack / redirectTo / reLaunch 导致当前页面被销毁 } }) 4.2 路由操作与生命周期触发对照 跳转方式 当前页面生命周期 目标页面生命周期 navigateTo 当前页触发 onHide 目标页触发 onLoad→onShow→onReady redirectTo 当前页触发 onUnload 目标页触发 onLoad→onShow→onReady switchTab 当前页 onHide / onUnload（视栈情况） 目标 tab 页 onLoad(首次) + onShow reLaunch 所有页触发 onUnload 新页触发 onLoad→onShow→onReady navigateBack 当前页触发 onUnload 上一页触发 onShow 常见实战点： 需要在“回到页面时刷新数据” → 放在 onShow 仅首次进入页面请求数据 → 放在 onLoad 五、实践：列表页 → 详情页 → 返回 5.1 列表页（pages/list/list.wxml） &lt;view wx:for="" wx:key="id" class="item" data-id="" bindtap="goDetail"&gt; &lt;/view&gt; pages/list/list.js： Page({ data: { list: [ { id: 1, title: '文章一' }, { id: 2, title: '文章二' } ] }, goDetail(e) { const id = e.currentTarget.dataset.id wx.navigateTo({ url: `/pages/detail/detail?id=${id}` }) } }) 5.2 详情页（pages/detail/detail.js） Page({ data: { id: null, detail: null }, onLoad(options) { const id = Number(options.id) this.setData({ id }) this.fetchDetail(id) }, fetchDetail(id) { // 模拟请求 const detail = { id, title: `文章 ${id}`, content: `这是文章 ${id} 的内容` } this.setData({ detail }) }, back() { wx.navigateBack() } }) pages/detail/detail.wxml： &lt;view&gt;详情：&lt;/view&gt; &lt;view&gt;&lt;/view&gt; &lt;button bindtap="back"&gt;返回列表&lt;/button&gt; 六、路由跳转与 tabBar 配合示例 假设定义了 tabBar： { "tabBar": { "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/profile/profile", "text": "我的" } ] } } 6.1 非 tab 页面跳转到 tab 页面 // 比如在某个设置页点击“回首页” wx.switchTab({ url: '/pages/index/index' }) 6.2 登录成功后，清空栈并回首页 // 登录成功后的回调 wx.reLaunch({ url: '/pages/index/index' }) 小技巧：避免用户使用“返回”按钮回到登录页或其他流程页。 七、设计路由结构的实践经验 首页 + tabBar = 导航骨架 典型结构：首页 / 分类 / 购物车 / 我的 tabBar 页面尽量作为一级入口，不要做复杂流程 深度页面使用 navigateTo 列表 → 详情 → 子详情 → … 用户可以逐级返回 不应该返回的页面使用 redirectTo 或 reLaunch 如：完成支付成功页 → redirectTo 到“订单详情页” 引导页 / 新手教程完成后 → reLaunch 到首页 参数设计 尽量使用简短、易理解的字段，如 id、type、from 复杂数据用 ID 传递，并在目标页重新请求详细数据 八、常见问题与易踩坑 在页面 B 调 navigateBack，却没有回到预期页面 可能：页面栈层级与预期不一致 建议：在关键流程中打印 getCurrentPages() 检查栈结构 跳转到 tabBar 页面失败 检查：目标页面是否已经配置在 app.json 的 tabBar.list 中 使用方式：必须用 wx.switchTab，而不是 navigateTo 切换 tab 时想要传参 不能在 url 后加 query 替代方法： // 设置全局变量 getApp().globalData.fromPage = 'xxx' wx.switchTab({ url: '/pages/index/index' }) // 在 index 页 onShow 中读取 const app = getApp() const fromPage = app.globalData.fromPage onLoad 中通过 getCurrentPages() 获取不到当前页 文档说明：getCurrentPages() 中的页面实例与当前页面文件中的 Page 对象不是同一个引用 一般调试用即可，不要做复杂逻辑依赖]]></summary></entry><entry><title type="html">微信小程序学习笔记-3</title><link href="https://altair288.github.io/Blog/wx-miniprogram3/" rel="alternate" type="text/html" title="微信小程序学习笔记-3" /><published>2025-10-11T00:00:00+00:00</published><updated>2025-10-11T00:00:00+00:00</updated><id>https://altair288.github.io/Blog/wx-miniprogram3</id><content type="html" xml:base="https://altair288.github.io/Blog/wx-miniprogram3/"><![CDATA[<h1 id="微信小程序数据绑定学习笔记">微信小程序数据绑定学习笔记</h1>

<blockquote>
  <p>目标：弄清楚“页面 data ↔ WXML 视图”之间是如何联动的，能熟练使用插值、属性绑定、事件更新等。</p>
</blockquote>

<hr />

<h2 id="一数据绑定的基本概念">一、数据绑定的基本概念</h2>

<ul>
  <li>小程序采用 <strong>数据驱动视图</strong>：
    <ul>
      <li>页面 <code class="language-plaintext highlighter-rouge">data</code> 中的字段 → 自动渲染到 <code class="language-plaintext highlighter-rouge">wxml</code></li>
      <li>通过 <code class="language-plaintext highlighter-rouge">this.setData()</code> 修改数据 → 视图自动更新</li>
    </ul>
  </li>
  <li>核心思想：<strong>不要直接操作 DOM，只操作数据</strong></li>
</ul>

<hr />

<h2 id="二最基础的插值绑定mustache-语法">二、最基础的插值绑定（Mustache 语法）</h2>

<h3 id="21-文本插值">2.1 文本插值</h3>

<pre><code class="language-wxml">&lt;view&gt;&lt;/view&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Hello 小程序</span><span class="dl">'</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<blockquote>
  <p>注意：`` 内只能写 <strong>表达式</strong>，不能写语句（如 <code class="language-plaintext highlighter-rouge">if</code>、<code class="language-plaintext highlighter-rouge">for</code>）。</p>
</blockquote>

<h3 id="22-支持的简单表达式例子">2.2 支持的简单表达式例子</h3>

<pre><code class="language-wxml">&lt;view&gt;8&lt;/view&gt;
&lt;view&gt;&lt;/view&gt;
&lt;view&gt;&lt;/view&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">count</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
    <span class="na">user</span><span class="p">:</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Tom</span><span class="dl">'</span> <span class="p">},</span>
    <span class="na">flag</span><span class="p">:</span> <span class="kc">true</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="三属性绑定标签属性里的数据绑定">三、属性绑定（标签属性里的数据绑定）</h2>

<p>除了内容可以使用 ``，<strong>标签属性</strong>也可以使用：</p>

<pre><code class="language-wxml">&lt;image src="" mode="" /&gt;
&lt;view class="box "&gt;内容&lt;/view&gt;
&lt;button disabled=""&gt;按钮&lt;/button&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">imgUrl</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://example.com/a.png</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">imgMode</span><span class="p">:</span> <span class="dl">'</span><span class="s1">aspectFit</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">isActive</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="na">isDisabled</span><span class="p">:</span> <span class="kc">false</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<p>常见场景：</p>

<ul>
  <li>控制样式类名：<code class="language-plaintext highlighter-rouge">class="xxx "</code></li>
  <li>控制组件属性：<code class="language-plaintext highlighter-rouge">disabled</code>、<code class="language-plaintext highlighter-rouge">autoplay</code>、<code class="language-plaintext highlighter-rouge">indicator-dots</code> 等</li>
  <li>控制图片 / 音频 / 视频资源地址</li>
</ul>

<hr />

<h2 id="四列表数据绑定wxfor">四、列表数据绑定（wx:for）</h2>

<h3 id="41-基本用法">4.1 基本用法</h3>

<pre><code class="language-wxml">&lt;view wx:for="" wx:key="id"&gt;
   - 
&lt;/view&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">list</span><span class="p">:</span> <span class="p">[</span>
      <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">苹果</span><span class="dl">'</span> <span class="p">},</span>
      <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">香蕉</span><span class="dl">'</span> <span class="p">},</span>
      <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">梨</span><span class="dl">'</span> <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">wx:for</code>：要遍历的数组（支持基本类型和对象）</li>
  <li>默认变量名：
    <ul>
      <li><code class="language-plaintext highlighter-rouge">item</code>：当前项</li>
      <li><code class="language-plaintext highlighter-rouge">index</code>：当前下标</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">wx:key</code>：<strong>必须写</strong>（官方强烈建议），用于提高性能和避免警告</li>
</ul>

<h3 id="42-自定义变量名">4.2 自定义变量名</h3>

<pre><code class="language-wxml">&lt;view
  wx:for=""
  wx:for-item="fruit"
  wx:for-index="i"
  wx:key="id"
&gt;
   - 
&lt;/view&gt;
</code></pre>

<hr />

<h2 id="五条件渲染wxif--wxelif--wxelse">五、条件渲染（wx:if / wx:elif / wx:else）</h2>

<pre><code class="language-wxml">&lt;view wx:if=""&gt;加载中...&lt;/view&gt;
&lt;view wx:elif=""&gt;加载成功&lt;/view&gt;
&lt;view wx:else&gt;加载失败&lt;/view&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">loading</span><span class="dl">'</span> <span class="c1">// loading / success / error</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<h3 id="51-wxif-与-hidden-的区别常考点">5.1 wx:if 与 hidden 的区别（常考点）</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">wx:if</code>：真正地 <strong>创建 / 销毁 DOM 节点</strong></li>
  <li><code class="language-plaintext highlighter-rouge">hidden</code>：仅仅是 <strong>控制显示或隐藏</strong>（节点还在）</li>
</ul>

<p>使用建议：</p>

<ul>
  <li>切换不频繁 → 用 <code class="language-plaintext highlighter-rouge">wx:if</code></li>
  <li>频繁显示 / 隐藏 → 用 <code class="language-plaintext highlighter-rouge">hidden=""</code></li>
</ul>

<hr />

<h2 id="六事件配合数据绑定交互更新视图">六、事件配合数据绑定：交互更新视图</h2>

<p>数据绑定只决定“显示什么”，要让用户操作改变数据，需要事件。</p>

<h3 id="61-点击按钮增加数字">6.1 点击按钮增加数字</h3>

<pre><code class="language-wxml">&lt;view&gt;当前计数：8&lt;/view&gt;
&lt;button bindtap="onAdd"&gt;+1&lt;/button&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">count</span><span class="p">:</span> <span class="mi">0</span>
  <span class="p">},</span>

  <span class="nx">onAdd</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span>
      <span class="na">count</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span>
    <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="七setdata-的用法与注意点">七、<code class="language-plaintext highlighter-rouge">setData</code> 的用法与注意点</h2>

<h3 id="71-基本用法">7.1 基本用法</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span>
  <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">更新后的文本</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">count</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span>
<span class="p">})</span>
</code></pre></div></div>

<ul>
  <li>调用 <code class="language-plaintext highlighter-rouge">setData</code> → 框架把变更同步到视图</li>
  <li><strong>只能更新 data 中已有的字段</strong>（动态新增字段要小心）</li>
</ul>

<h3 id="72-更新对象--数组的子属性使用点语法">7.2 更新对象 / 数组的子属性（使用“点语法”）</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">user</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Tom</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">age</span><span class="p">:</span> <span class="mi">18</span>
    <span class="p">},</span>
    <span class="na">list</span><span class="p">:</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
  <span class="p">},</span>

  <span class="nx">updateData</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span>
      <span class="dl">'</span><span class="s1">user.name</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Jerry</span><span class="dl">'</span><span class="p">,</span>
      <span class="dl">'</span><span class="s1">list[1]</span><span class="dl">'</span><span class="p">:</span> <span class="mi">20</span>
    <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<blockquote>
  <p>关键点：<code class="language-plaintext highlighter-rouge">'user.name'</code>、<code class="language-plaintext highlighter-rouge">'list[1]'</code> 要写成 <strong>字符串键名</strong>。</p>
</blockquote>

<h3 id="73-性能注意事项">7.3 性能注意事项</h3>

<ul>
  <li>尽量一次 <code class="language-plaintext highlighter-rouge">setData</code> 修改多个字段，而不是多次调用</li>
  <li>只传 <strong>必要字段</strong>，不要每次把整个大对象 / 大数组都传回去</li>
  <li>复杂页面中，频繁大数据 <code class="language-plaintext highlighter-rouge">setData</code> 会有卡顿问题</li>
</ul>

<hr />

<h2 id="八表单输入与双向效果">八、表单输入与双向“效果”</h2>

<p>小程序没有真正的“v-model”，但可以通过事件做到类似双向更新。</p>

<h3 id="81-input-输入同步到-data">8.1 input 输入同步到 data</h3>

<pre><code class="language-wxml">&lt;input
  value=""
  placeholder="请输入用户名"
  bindinput="onInputChange"
/&gt;

&lt;view&gt;你输入的是：&lt;/view&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">username</span><span class="p">:</span> <span class="dl">''</span>
  <span class="p">},</span>

  <span class="nx">onInputChange</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span>
      <span class="na">username</span><span class="p">:</span> <span class="nx">e</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">value</span>
    <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">e.detail.value</code>：当前输入框的值</li>
  <li><code class="language-plaintext highlighter-rouge">data.username</code> 更新后，视图显示跟着变</li>
</ul>

<h3 id="82-多个输入框的通用处理用-data-">8.2 多个输入框的通用处理（用 data-*）</h3>

<pre><code class="language-wxml">&lt;input
  data-field="username"
  value=""
  bindinput="onInputChange"
/&gt;
&lt;input
  data-field="email"
  value=""
  bindinput="onInputChange"
/&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">username</span><span class="p">:</span> <span class="dl">''</span><span class="p">,</span>
    <span class="na">email</span><span class="p">:</span> <span class="dl">''</span>
  <span class="p">},</span>

  <span class="nx">onInputChange</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">field</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">field</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span>
      <span class="p">[</span><span class="nx">field</span><span class="p">]:</span> <span class="nx">e</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">value</span>
    <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<blockquote>
  <p>这里用到 ES6 计算属性名：<code class="language-plaintext highlighter-rouge">[field]</code>。</p>
</blockquote>

<hr />

<h2 id="九复杂结构的绑定示例综合">九、复杂结构的绑定示例（综合）</h2>

<h3 id="91-数据结构">9.1 数据结构</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">banners</span><span class="p">:</span> <span class="p">[</span>
      <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="na">img</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://xx.com/b1.png</span><span class="dl">'</span><span class="p">,</span> <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">图一</span><span class="dl">'</span> <span class="p">},</span>
      <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="na">img</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://xx.com/b2.png</span><span class="dl">'</span><span class="p">,</span> <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">图二</span><span class="dl">'</span> <span class="p">}</span>
    <span class="p">],</span>
    <span class="na">products</span><span class="p">:</span> <span class="p">[</span>
      <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="mi">101</span><span class="p">,</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">商品 A</span><span class="dl">'</span><span class="p">,</span> <span class="na">price</span><span class="p">:</span> <span class="mi">99</span><span class="p">,</span> <span class="na">hot</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span>
      <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="mi">102</span><span class="p">,</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">商品 B</span><span class="dl">'</span><span class="p">,</span> <span class="na">price</span><span class="p">:</span> <span class="mi">199</span><span class="p">,</span> <span class="na">hot</span><span class="p">:</span> <span class="kc">false</span> <span class="p">}</span>
    <span class="p">],</span>
    <span class="na">currentBanner</span><span class="p">:</span> <span class="mi">0</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<h3 id="92-wxml-绑定展示">9.2 WXML 绑定展示</h3>

<pre><code class="language-wxml">&lt;!-- 轮播图 --&gt;
&lt;swiper
  indicator-dots="true"
  autoplay="true"
  circular="true"
  current=""
  bindchange="onBannerChange"
&gt;
  &lt;block wx:for="" wx:key="id"&gt;
    &lt;swiper-item&gt;
      &lt;image src="" mode="aspectFill" /&gt;
      &lt;view class="banner-title"&gt;&lt;/view&gt;
    &lt;/swiper-item&gt;
  &lt;/block&gt;
&lt;/swiper&gt;

&lt;!-- 商品列表 --&gt;
&lt;view
  class="product-item "
  wx:for=""
  wx:key="id"
&gt;
  &lt;view&gt;&lt;/view&gt;
  &lt;view&gt;￥&lt;/view&gt;
&lt;/view&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="c1">// 同上</span>
  <span class="p">},</span>

  <span class="nx">onBannerChange</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span>
      <span class="na">currentBanner</span><span class="p">:</span> <span class="nx">e</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">current</span>
    <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="十常见错误与排查">十、常见错误与排查</h2>

<ol>
  <li><strong>`` 不显示 / 显示为 <code class="language-plaintext highlighter-rouge">undefined</code></strong>
    <ul>
      <li>检查 <code class="language-plaintext highlighter-rouge">data</code> 里是否有这个字段</li>
      <li>检查拼写：字段名、层级、大小写</li>
    </ul>
  </li>
  <li><strong>更新数据了，但界面没变化</strong>
    <ul>
      <li>是否通过 <code class="language-plaintext highlighter-rouge">this.setData()</code> 更新？直接修改 <code class="language-plaintext highlighter-rouge">this.data.xxx = ...</code> 不会触发视图更新</li>
      <li><code class="language-plaintext highlighter-rouge">setData</code> 写法是否正确，尤其是更新子属性时的字符串键名</li>
    </ul>
  </li>
  <li><strong><code class="language-plaintext highlighter-rouge">wx:for</code> 报警告 / 渲染异常</strong>
    <ul>
      <li>是否漏写 <code class="language-plaintext highlighter-rouge">wx:key</code></li>
      <li><code class="language-plaintext highlighter-rouge">wx:key</code> 的值是否是数组项中独一无二的字段（推荐用 <code class="language-plaintext highlighter-rouge">id</code>）</li>
    </ul>
  </li>
  <li><strong>频繁 <code class="language-plaintext highlighter-rouge">setData</code> 导致卡顿</strong>
    <ul>
      <li>合并多次修改为一次 <code class="language-plaintext highlighter-rouge">setData</code></li>
      <li>精简要传递的数据，只更新真正变化的字段</li>
    </ul>
  </li>
</ol>

<hr />]]></content><author><name></name></author><category term="miniprogram3" /><summary type="html"><![CDATA[微信小程序数据绑定学习笔记 目标：弄清楚“页面 data ↔ WXML 视图”之间是如何联动的，能熟练使用插值、属性绑定、事件更新等。 一、数据绑定的基本概念 小程序采用 数据驱动视图： 页面 data 中的字段 → 自动渲染到 wxml 通过 this.setData() 修改数据 → 视图自动更新 核心思想：不要直接操作 DOM，只操作数据 二、最基础的插值绑定（Mustache 语法） 2.1 文本插值 &lt;view&gt;&lt;/view&gt; Page({ data: { message: 'Hello 小程序' } }) 注意：`` 内只能写 表达式，不能写语句（如 if、for）。 2.2 支持的简单表达式例子 &lt;view&gt;8&lt;/view&gt; &lt;view&gt;&lt;/view&gt; &lt;view&gt;&lt;/view&gt; Page({ data: { count: 1, user: { name: 'Tom' }, flag: true } }) 三、属性绑定（标签属性里的数据绑定） 除了内容可以使用 ``，标签属性也可以使用： &lt;image src="" mode="" /&gt; &lt;view class="box "&gt;内容&lt;/view&gt; &lt;button disabled=""&gt;按钮&lt;/button&gt; Page({ data: { imgUrl: 'https://example.com/a.png', imgMode: 'aspectFit', isActive: true, isDisabled: false } }) 常见场景： 控制样式类名：class="xxx " 控制组件属性：disabled、autoplay、indicator-dots 等 控制图片 / 音频 / 视频资源地址 四、列表数据绑定（wx:for） 4.1 基本用法 &lt;view wx:for="" wx:key="id"&gt; - &lt;/view&gt; Page({ data: { list: [ { id: 1, name: '苹果' }, { id: 2, name: '香蕉' }, { id: 3, name: '梨' } ] } }) wx:for：要遍历的数组（支持基本类型和对象） 默认变量名： item：当前项 index：当前下标 wx:key：必须写（官方强烈建议），用于提高性能和避免警告 4.2 自定义变量名 &lt;view wx:for="" wx:for-item="fruit" wx:for-index="i" wx:key="id" &gt; - &lt;/view&gt; 五、条件渲染（wx:if / wx:elif / wx:else） &lt;view wx:if=""&gt;加载中...&lt;/view&gt; &lt;view wx:elif=""&gt;加载成功&lt;/view&gt; &lt;view wx:else&gt;加载失败&lt;/view&gt; Page({ data: { status: 'loading' // loading / success / error } }) 5.1 wx:if 与 hidden 的区别（常考点） wx:if：真正地 创建 / 销毁 DOM 节点 hidden：仅仅是 控制显示或隐藏（节点还在） 使用建议： 切换不频繁 → 用 wx:if 频繁显示 / 隐藏 → 用 hidden="" 六、事件配合数据绑定：交互更新视图 数据绑定只决定“显示什么”，要让用户操作改变数据，需要事件。 6.1 点击按钮增加数字 &lt;view&gt;当前计数：8&lt;/view&gt; &lt;button bindtap="onAdd"&gt;+1&lt;/button&gt; Page({ data: { count: 0 }, onAdd() { this.setData({ count: this.data.count + 1 }) } }) 七、setData 的用法与注意点 7.1 基本用法 this.setData({ message: '更新后的文本', count: this.data.count + 1 }) 调用 setData → 框架把变更同步到视图 只能更新 data 中已有的字段（动态新增字段要小心） 7.2 更新对象 / 数组的子属性（使用“点语法”） Page({ data: { user: { name: 'Tom', age: 18 }, list: [1, 2, 3] }, updateData() { this.setData({ 'user.name': 'Jerry', 'list[1]': 20 }) } }) 关键点：'user.name'、'list[1]' 要写成 字符串键名。 7.3 性能注意事项 尽量一次 setData 修改多个字段，而不是多次调用 只传 必要字段，不要每次把整个大对象 / 大数组都传回去 复杂页面中，频繁大数据 setData 会有卡顿问题 八、表单输入与双向“效果” 小程序没有真正的“v-model”，但可以通过事件做到类似双向更新。 8.1 input 输入同步到 data &lt;input value="" placeholder="请输入用户名" bindinput="onInputChange" /&gt; &lt;view&gt;你输入的是：&lt;/view&gt; Page({ data: { username: '' }, onInputChange(e) { this.setData({ username: e.detail.value }) } }) e.detail.value：当前输入框的值 data.username 更新后，视图显示跟着变 8.2 多个输入框的通用处理（用 data-*） &lt;input data-field="username" value="" bindinput="onInputChange" /&gt; &lt;input data-field="email" value="" bindinput="onInputChange" /&gt; Page({ data: { username: '', email: '' }, onInputChange(e) { const field = e.currentTarget.dataset.field this.setData({ [field]: e.detail.value }) } }) 这里用到 ES6 计算属性名：[field]。 九、复杂结构的绑定示例（综合） 9.1 数据结构 Page({ data: { banners: [ { id: 1, img: 'https://xx.com/b1.png', title: '图一' }, { id: 2, img: 'https://xx.com/b2.png', title: '图二' } ], products: [ { id: 101, name: '商品 A', price: 99, hot: true }, { id: 102, name: '商品 B', price: 199, hot: false } ], currentBanner: 0 } }) 9.2 WXML 绑定展示 &lt;!-- 轮播图 --&gt; &lt;swiper indicator-dots="true" autoplay="true" circular="true" current="" bindchange="onBannerChange" &gt; &lt;block wx:for="" wx:key="id"&gt; &lt;swiper-item&gt; &lt;image src="" mode="aspectFill" /&gt; &lt;view class="banner-title"&gt;&lt;/view&gt; &lt;/swiper-item&gt; &lt;/block&gt; &lt;/swiper&gt; &lt;!-- 商品列表 --&gt; &lt;view class="product-item " wx:for="" wx:key="id" &gt; &lt;view&gt;&lt;/view&gt; &lt;view&gt;￥&lt;/view&gt; &lt;/view&gt; Page({ data: { // 同上 }, onBannerChange(e) { this.setData({ currentBanner: e.detail.current }) } }) 十、常见错误与排查 `` 不显示 / 显示为 undefined 检查 data 里是否有这个字段 检查拼写：字段名、层级、大小写 更新数据了，但界面没变化 是否通过 this.setData() 更新？直接修改 this.data.xxx = ... 不会触发视图更新 setData 写法是否正确，尤其是更新子属性时的字符串键名 wx:for 报警告 / 渲染异常 是否漏写 wx:key wx:key 的值是否是数组项中独一无二的字段（推荐用 id） 频繁 setData 导致卡顿 合并多次修改为一次 setData 精简要传递的数据，只更新真正变化的字段]]></summary></entry><entry><title type="html">微信小程序学习笔记-2</title><link href="https://altair288.github.io/Blog/wx-miniprogram2/" rel="alternate" type="text/html" title="微信小程序学习笔记-2" /><published>2025-10-10T00:00:00+00:00</published><updated>2025-10-10T00:00:00+00:00</updated><id>https://altair288.github.io/Blog/wx-miniprogram2</id><content type="html" xml:base="https://altair288.github.io/Blog/wx-miniprogram2/"><![CDATA[<h1 id="微信小程序轮播图学习笔记">微信小程序轮播图学习笔记</h1>

<blockquote>
  <p>目标：掌握在微信小程序中使用 <code class="language-plaintext highlighter-rouge">swiper</code> 组件实现基础轮播图，并能做常见配置和简单封装。</p>
</blockquote>

<hr />

<h2 id="一轮播图实现思路概览">一、轮播图实现思路概览</h2>

<p>在小程序中实现轮播图，主要依靠官方提供的组件：</p>

<ul>
  <li>容器组件：<code class="language-plaintext highlighter-rouge">&lt;swiper /&gt;</code></li>
  <li>子项组件：<code class="language-plaintext highlighter-rouge">&lt;swiper-item /&gt;</code></li>
</ul>

<p>核心步骤：</p>

<ol>
  <li>在 <code class="language-plaintext highlighter-rouge">wxml</code> 中使用 <code class="language-plaintext highlighter-rouge">swiper</code> + <code class="language-plaintext highlighter-rouge">swiper-item</code> 结构</li>
  <li>在 <code class="language-plaintext highlighter-rouge">js</code> 中准备轮播图数据（比如图片数组）</li>
  <li>在 <code class="language-plaintext highlighter-rouge">wxss</code> 中设置容器高度 / 宽度等样式</li>
  <li>可选：监听滑动事件、配置自动切换、指示点等</li>
</ol>

<hr />

<h2 id="二最简单的轮播图示例">二、最简单的轮播图示例</h2>

<h3 id="21-wxml-结构">2.1 WXML 结构</h3>

<pre><code class="language-wxml">&lt;!-- pages/banner/banner.wxml --&gt;
&lt;view class="banner-container"&gt;
  &lt;swiper
    class="banner-swiper"
    indicator-dots="true"
    autoplay="true"
    interval="3000"
    duration="500"
    circular="true"
  &gt;
    &lt;block wx:for="" wx:key="url"&gt;
      &lt;swiper-item&gt;
        &lt;image class="banner-img" src="" mode="aspectFill" /&gt;
      &lt;/swiper-item&gt;
    &lt;/block&gt;
  &lt;/swiper&gt;
&lt;/view&gt;
</code></pre>

<h3 id="22-js-数据">2.2 JS 数据</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// pages/banner/banner.js</span>
<span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">imgList</span><span class="p">:</span> <span class="p">[</span>
      <span class="p">{</span> <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://example.com/banner1.jpg</span><span class="dl">'</span> <span class="p">},</span>
      <span class="p">{</span> <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://example.com/banner2.jpg</span><span class="dl">'</span> <span class="p">},</span>
      <span class="p">{</span> <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://example.com/banner3.jpg</span><span class="dl">'</span> <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<h3 id="23-wxss-样式">2.3 WXSS 样式</h3>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">/* pages/banner/banner.wxss */</span>
<span class="nc">.banner-container</span> <span class="p">{</span>
  <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.banner-swiper</span> <span class="p">{</span>
  <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
  <span class="nl">height</span><span class="p">:</span> <span class="m">300</span><span class="n">rpx</span><span class="p">;</span> <span class="c">/* 轮播图高度可根据设计调整 */</span>
<span class="p">}</span>

<span class="nc">.banner-img</span> <span class="p">{</span>
  <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
  <span class="nl">height</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="三swiper-常用属性总结">三、swiper 常用属性总结</h2>

<pre><code class="language-wxml">&lt;swiper
  indicator-dots=""  &lt;!-- 是否显示面板指示点 --&gt;
  autoplay=""             &lt;!-- 是否自动切换 --&gt;
  interval=""             &lt;!-- 自动切换时间间隔，ms --&gt;
  duration=""             &lt;!-- 滑动动画时长，ms --&gt;
  circular=""             &lt;!-- 是否衔接滑动（循环） --&gt;
  vertical=""             &lt;!-- 是否纵向滑动 --&gt;
  current=""               &lt;!-- 当前所在滑块的 index --&gt;
  bindchange="onSwiperChange"         &lt;!-- 当前页改变事件 --&gt;
  bindanimationfinish="onAnimationFinish"
&gt;
&lt;/swiper&gt;
</code></pre>

<p>常用配置说明：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">indicator-dots</code>：轮播底部的小点点（true/false）</li>
  <li><code class="language-plaintext highlighter-rouge">autoplay</code>：是否自动播放</li>
  <li><code class="language-plaintext highlighter-rouge">interval</code>：自动切换时间，常用 3000（3 秒）</li>
  <li><code class="language-plaintext highlighter-rouge">duration</code>：每次滑动动画时间 300–500 比较常见</li>
  <li><code class="language-plaintext highlighter-rouge">circular</code>：打开后可以从最后一张滑到第一张</li>
  <li><code class="language-plaintext highlighter-rouge">vertical</code>：一般轮播图用横向，不勾选</li>
  <li><code class="language-plaintext highlighter-rouge">current</code>：可以用来“手动控制”当前显示第几张</li>
</ul>

<hr />

<h2 id="四监听滑动事件与当前索引">四、监听滑动事件与当前索引</h2>

<p>有时需要知道当前滑到了第几张，比如同步显示标题。</p>

<h3 id="41-监听-change-事件">4.1 监听 change 事件</h3>

<pre><code class="language-wxml">&lt;swiper
  indicator-dots="true"
  autoplay="true"
  circular="true"
  bindchange="onSwiperChange"
&gt;
  &lt;!-- ... --&gt;
&lt;/swiper&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">current</span><span class="p">:</span> <span class="mi">0</span>
  <span class="p">},</span>

  <span class="nx">onSwiperChange</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">current</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">current</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="nx">current</span> <span class="p">})</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">当前轮播索引：</span><span class="dl">'</span><span class="p">,</span> <span class="nx">current</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="五实现轮播图点击跳转">五、实现轮播图点击跳转</h2>

<p>常见需求：轮播图点击跳转到详情页或外部链接。</p>

<h3 id="51-给每个图片绑定点击事件">5.1 给每个图片绑定点击事件</h3>

<pre><code class="language-wxml">&lt;swiper
  indicator-dots="true"
  autoplay="true"
  circular="true"
&gt;
  &lt;block wx:for="" wx:key="id"&gt;
    &lt;swiper-item&gt;
      &lt;image
        class="banner-img"
        src=""
        mode="aspectFill"
        data-id=""
        bindtap="onBannerTap"
      /&gt;
    &lt;/swiper-item&gt;
  &lt;/block&gt;
&lt;/swiper&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">imgList</span><span class="p">:</span> <span class="p">[</span>
      <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://example.com/banner1.jpg</span><span class="dl">'</span><span class="p">,</span> <span class="na">target</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/detail/detail?id=1</span><span class="dl">'</span> <span class="p">},</span>
      <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://example.com/banner2.jpg</span><span class="dl">'</span><span class="p">,</span> <span class="na">target</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/detail/detail?id=2</span><span class="dl">'</span> <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">},</span>

  <span class="nx">onBannerTap</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">id</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">点击的 banner id:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">id</span><span class="p">)</span>
    <span class="c1">// 示例：根据 id 跳转</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">navigateTo</span><span class="p">({</span>
      <span class="na">url</span><span class="p">:</span> <span class="s2">`/pages/detail/detail?id=</span><span class="p">${</span><span class="nx">id</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="六从接口动态加载轮播图数据">六、从接口动态加载轮播图数据</h2>

<p>实际项目中，图片地址通常来自后台接口。</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">imgList</span><span class="p">:</span> <span class="p">[]</span>
  <span class="p">},</span>

  <span class="nx">onLoad</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">fetchBanner</span><span class="p">()</span>
  <span class="p">},</span>

  <span class="nx">fetchBanner</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">wx</span><span class="p">.</span><span class="nx">request</span><span class="p">({</span>
      <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://example.com/api/banner</span><span class="dl">'</span><span class="p">,</span> <span class="c1">// 示例地址</span>
      <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">GET</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">success</span><span class="p">:</span> <span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="c1">// 假设返回数据为 { code: 0, data: [ { url: '' }, ... ] }</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">code</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span>
            <span class="na">imgList</span><span class="p">:</span> <span class="nx">res</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">data</span>
          <span class="p">})</span>
        <span class="p">}</span>
      <span class="p">},</span>
      <span class="na">fail</span><span class="p">:</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">获取轮播图失败</span><span class="dl">'</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
      <span class="p">}</span>
    <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="七封装一个通用轮播图组件进阶">七、封装一个通用轮播图组件（进阶）</h2>

<h3 id="71-目录结构示例">7.1 目录结构示例</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>components/banner-swiper/
  ├── banner-swiper.wxml
  ├── banner-swiper.wxss
  ├── banner-swiper.js
  └── banner-swiper.json
</code></pre></div></div>

<h3 id="72-组件配置与逻辑">7.2 组件配置与逻辑</h3>

<p><code class="language-plaintext highlighter-rouge">banner-swiper.json</code></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"component"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">banner-swiper.wxml</code></p>

<pre><code class="language-wxml">&lt;view class="banner-container"&gt;
  &lt;swiper
    class="banner-swiper"
    indicator-dots=""
    autoplay=""
    interval=""
    duration=""
    circular=""
    bindchange="onSwiperChange"
  &gt;
    &lt;block wx:for="" wx:key="id"&gt;
      &lt;swiper-item&gt;
        &lt;image
          class="banner-img"
          src=""
          mode="aspectFill"
          data-item=""
          bindtap="onTap"
        /&gt;
      &lt;/swiper-item&gt;
    &lt;/block&gt;
  &lt;/swiper&gt;
&lt;/view&gt;
</code></pre>

<p><code class="language-plaintext highlighter-rouge">banner-swiper.js</code></p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Component</span><span class="p">({</span>
  <span class="na">properties</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">list</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">type</span><span class="p">:</span> <span class="nb">Array</span><span class="p">,</span>
      <span class="na">value</span><span class="p">:</span> <span class="p">[]</span>
    <span class="p">},</span>
    <span class="na">indicatorDots</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">type</span><span class="p">:</span> <span class="nb">Boolean</span><span class="p">,</span>
      <span class="na">value</span><span class="p">:</span> <span class="kc">true</span>
    <span class="p">},</span>
    <span class="na">autoplay</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">type</span><span class="p">:</span> <span class="nb">Boolean</span><span class="p">,</span>
      <span class="na">value</span><span class="p">:</span> <span class="kc">true</span>
    <span class="p">},</span>
    <span class="na">interval</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">type</span><span class="p">:</span> <span class="nb">Number</span><span class="p">,</span>
      <span class="na">value</span><span class="p">:</span> <span class="mi">3000</span>
    <span class="p">},</span>
    <span class="na">duration</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">type</span><span class="p">:</span> <span class="nb">Number</span><span class="p">,</span>
      <span class="na">value</span><span class="p">:</span> <span class="mi">500</span>
    <span class="p">},</span>
    <span class="na">circular</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">type</span><span class="p">:</span> <span class="nb">Boolean</span><span class="p">,</span>
      <span class="na">value</span><span class="p">:</span> <span class="kc">true</span>
    <span class="p">}</span>
  <span class="p">},</span>

  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">current</span><span class="p">:</span> <span class="mi">0</span>
  <span class="p">},</span>

  <span class="na">methods</span><span class="p">:</span> <span class="p">{</span>
    <span class="nx">onSwiperChange</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">current</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">current</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span> <span class="nx">current</span> <span class="p">})</span>
      <span class="c1">// 向外通知当前索引</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">triggerEvent</span><span class="p">(</span><span class="dl">'</span><span class="s1">change</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="nx">current</span> <span class="p">})</span>
    <span class="p">},</span>

    <span class="nx">onTap</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">item</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">item</span>
      <span class="c1">// 向外抛出点击事件和数据</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">triggerEvent</span><span class="p">(</span><span class="dl">'</span><span class="s1">tap</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="nx">item</span> <span class="p">})</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">banner-swiper.wxss</code></p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.banner-container</span> <span class="p">{</span>
  <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.banner-swiper</span> <span class="p">{</span>
  <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
  <span class="nl">height</span><span class="p">:</span> <span class="m">300</span><span class="n">rpx</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.banner-img</span> <span class="p">{</span>
  <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
  <span class="nl">height</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="73-在页面中使用组件">7.3 在页面中使用组件</h3>

<p>页面 json：</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"usingComponents"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"banner-swiper"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/components/banner-swiper/banner-swiper"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>页面 wxml：</p>

<pre><code class="language-wxml">&lt;banner-swiper
  list=""
  bind:tap="handleBannerTap"
  bind:change="handleBannerChange"
/&gt;
</code></pre>

<p>页面 js：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">imgList</span><span class="p">:</span> <span class="p">[</span>
      <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://example.com/banner1.jpg</span><span class="dl">'</span><span class="p">,</span> <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Banner 1</span><span class="dl">'</span> <span class="p">},</span>
      <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://example.com/banner2.jpg</span><span class="dl">'</span><span class="p">,</span> <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Banner 2</span><span class="dl">'</span> <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">},</span>

  <span class="nx">handleBannerTap</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">item</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">item</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">点击的轮播项：</span><span class="dl">'</span><span class="p">,</span> <span class="nx">item</span><span class="p">)</span>
    <span class="c1">// TODO: 自定义跳转逻辑</span>
  <span class="p">},</span>

  <span class="nx">handleBannerChange</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">current</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">current</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">轮播切换到：</span><span class="dl">'</span><span class="p">,</span> <span class="nx">current</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="八常见问题与排查思路">八、常见问题与排查思路</h2>

<ol>
  <li><strong>轮播图高度为 0 或不显示</strong>
    <ul>
      <li>检查 <code class="language-plaintext highlighter-rouge">swiper</code> / <code class="language-plaintext highlighter-rouge">image</code> 是否设置了 <code class="language-plaintext highlighter-rouge">height</code></li>
      <li>检查父级容器是否被 <code class="language-plaintext highlighter-rouge">overflow: hidden</code> 或其他样式影响</li>
    </ul>
  </li>
  <li><strong>图片拉伸变形</strong>
    <ul>
      <li>调整 <code class="language-plaintext highlighter-rouge">image</code> 的 <code class="language-plaintext highlighter-rouge">mode</code> 属性，例如 <code class="language-plaintext highlighter-rouge">aspectFill</code> / <code class="language-plaintext highlighter-rouge">widthFix</code></li>
    </ul>
  </li>
  <li><strong>图片闪烁 / 加载慢</strong>
    <ul>
      <li>可考虑图片压缩、CDN</li>
      <li>预加载：先显示占位图，加载完成再替换</li>
    </ul>
  </li>
  <li><strong>指示点位置不满足需求</strong>
    <ul>
      <li>小程序原生指示点支持有限，可以自己写一套指示器（在 <code class="language-plaintext highlighter-rouge">swiper</code> 外面根据 <code class="language-plaintext highlighter-rouge">current</code> 手写小圆点）</li>
    </ul>
  </li>
</ol>

<hr />]]></content><author><name></name></author><category term="miniprogram2" /><summary type="html"><![CDATA[微信小程序轮播图学习笔记 目标：掌握在微信小程序中使用 swiper 组件实现基础轮播图，并能做常见配置和简单封装。 一、轮播图实现思路概览 在小程序中实现轮播图，主要依靠官方提供的组件： 容器组件：&lt;swiper /&gt; 子项组件：&lt;swiper-item /&gt; 核心步骤： 在 wxml 中使用 swiper + swiper-item 结构 在 js 中准备轮播图数据（比如图片数组） 在 wxss 中设置容器高度 / 宽度等样式 可选：监听滑动事件、配置自动切换、指示点等 二、最简单的轮播图示例 2.1 WXML 结构 &lt;!-- pages/banner/banner.wxml --&gt; &lt;view class="banner-container"&gt; &lt;swiper class="banner-swiper" indicator-dots="true" autoplay="true" interval="3000" duration="500" circular="true" &gt; &lt;block wx:for="" wx:key="url"&gt; &lt;swiper-item&gt; &lt;image class="banner-img" src="" mode="aspectFill" /&gt; &lt;/swiper-item&gt; &lt;/block&gt; &lt;/swiper&gt; &lt;/view&gt; 2.2 JS 数据 // pages/banner/banner.js Page({ data: { imgList: [ { url: 'https://example.com/banner1.jpg' }, { url: 'https://example.com/banner2.jpg' }, { url: 'https://example.com/banner3.jpg' } ] } }) 2.3 WXSS 样式 /* pages/banner/banner.wxss */ .banner-container { width: 100%; } .banner-swiper { width: 100%; height: 300rpx; /* 轮播图高度可根据设计调整 */ } .banner-img { width: 100%; height: 100%; } 三、swiper 常用属性总结 &lt;swiper indicator-dots="" &lt;!-- 是否显示面板指示点 --&gt; autoplay="" &lt;!-- 是否自动切换 --&gt; interval="" &lt;!-- 自动切换时间间隔，ms --&gt; duration="" &lt;!-- 滑动动画时长，ms --&gt; circular="" &lt;!-- 是否衔接滑动（循环） --&gt; vertical="" &lt;!-- 是否纵向滑动 --&gt; current="" &lt;!-- 当前所在滑块的 index --&gt; bindchange="onSwiperChange" &lt;!-- 当前页改变事件 --&gt; bindanimationfinish="onAnimationFinish" &gt; &lt;/swiper&gt; 常用配置说明： indicator-dots：轮播底部的小点点（true/false） autoplay：是否自动播放 interval：自动切换时间，常用 3000（3 秒） duration：每次滑动动画时间 300–500 比较常见 circular：打开后可以从最后一张滑到第一张 vertical：一般轮播图用横向，不勾选 current：可以用来“手动控制”当前显示第几张 四、监听滑动事件与当前索引 有时需要知道当前滑到了第几张，比如同步显示标题。 4.1 监听 change 事件 &lt;swiper indicator-dots="true" autoplay="true" circular="true" bindchange="onSwiperChange" &gt; &lt;!-- ... --&gt; &lt;/swiper&gt; Page({ data: { current: 0 }, onSwiperChange(e) { const current = e.detail.current this.setData({ current }) console.log('当前轮播索引：', current) } }) 五、实现轮播图点击跳转 常见需求：轮播图点击跳转到详情页或外部链接。 5.1 给每个图片绑定点击事件 &lt;swiper indicator-dots="true" autoplay="true" circular="true" &gt; &lt;block wx:for="" wx:key="id"&gt; &lt;swiper-item&gt; &lt;image class="banner-img" src="" mode="aspectFill" data-id="" bindtap="onBannerTap" /&gt; &lt;/swiper-item&gt; &lt;/block&gt; &lt;/swiper&gt; Page({ data: { imgList: [ { id: 1, url: 'https://example.com/banner1.jpg', target: '/pages/detail/detail?id=1' }, { id: 2, url: 'https://example.com/banner2.jpg', target: '/pages/detail/detail?id=2' } ] }, onBannerTap(e) { const id = e.currentTarget.dataset.id console.log('点击的 banner id:', id) // 示例：根据 id 跳转 wx.navigateTo({ url: `/pages/detail/detail?id=${id}` }) } }) 六、从接口动态加载轮播图数据 实际项目中，图片地址通常来自后台接口。 Page({ data: { imgList: [] }, onLoad() { this.fetchBanner() }, fetchBanner() { wx.request({ url: 'https://example.com/api/banner', // 示例地址 method: 'GET', success: (res) =&gt; { // 假设返回数据为 { code: 0, data: [ { url: '' }, ... ] } if (res.data.code === 0) { this.setData({ imgList: res.data.data }) } }, fail: (err) =&gt; { console.error('获取轮播图失败', err) } }) } }) 七、封装一个通用轮播图组件（进阶） 7.1 目录结构示例 components/banner-swiper/ ├── banner-swiper.wxml ├── banner-swiper.wxss ├── banner-swiper.js └── banner-swiper.json 7.2 组件配置与逻辑 banner-swiper.json { "component": true } banner-swiper.wxml &lt;view class="banner-container"&gt; &lt;swiper class="banner-swiper" indicator-dots="" autoplay="" interval="" duration="" circular="" bindchange="onSwiperChange" &gt; &lt;block wx:for="" wx:key="id"&gt; &lt;swiper-item&gt; &lt;image class="banner-img" src="" mode="aspectFill" data-item="" bindtap="onTap" /&gt; &lt;/swiper-item&gt; &lt;/block&gt; &lt;/swiper&gt; &lt;/view&gt; banner-swiper.js Component({ properties: { list: { type: Array, value: [] }, indicatorDots: { type: Boolean, value: true }, autoplay: { type: Boolean, value: true }, interval: { type: Number, value: 3000 }, duration: { type: Number, value: 500 }, circular: { type: Boolean, value: true } }, data: { current: 0 }, methods: { onSwiperChange(e) { const current = e.detail.current this.setData({ current }) // 向外通知当前索引 this.triggerEvent('change', { current }) }, onTap(e) { const item = e.currentTarget.dataset.item // 向外抛出点击事件和数据 this.triggerEvent('tap', { item }) } } }) banner-swiper.wxss .banner-container { width: 100%; } .banner-swiper { width: 100%; height: 300rpx; } .banner-img { width: 100%; height: 100%; } 7.3 在页面中使用组件 页面 json： { "usingComponents": { "banner-swiper": "/components/banner-swiper/banner-swiper" } } 页面 wxml： &lt;banner-swiper list="" bind:tap="handleBannerTap" bind:change="handleBannerChange" /&gt; 页面 js： Page({ data: { imgList: [ { id: 1, url: 'https://example.com/banner1.jpg', title: 'Banner 1' }, { id: 2, url: 'https://example.com/banner2.jpg', title: 'Banner 2' } ] }, handleBannerTap(e) { const item = e.detail.item console.log('点击的轮播项：', item) // TODO: 自定义跳转逻辑 }, handleBannerChange(e) { const current = e.detail.current console.log('轮播切换到：', current) } }) 八、常见问题与排查思路 轮播图高度为 0 或不显示 检查 swiper / image 是否设置了 height 检查父级容器是否被 overflow: hidden 或其他样式影响 图片拉伸变形 调整 image 的 mode 属性，例如 aspectFill / widthFix 图片闪烁 / 加载慢 可考虑图片压缩、CDN 预加载：先显示占位图，加载完成再替换 指示点位置不满足需求 小程序原生指示点支持有限，可以自己写一套指示器（在 swiper 外面根据 current 手写小圆点）]]></summary></entry><entry><title type="html">微信小程序学习笔记-1</title><link href="https://altair288.github.io/Blog/wx-miniprogram1/" rel="alternate" type="text/html" title="微信小程序学习笔记-1" /><published>2025-10-09T00:00:00+00:00</published><updated>2025-10-09T00:00:00+00:00</updated><id>https://altair288.github.io/Blog/wx-miniprogram1</id><content type="html" xml:base="https://altair288.github.io/Blog/wx-miniprogram1/"><![CDATA[<h1 id="微信小程序学习笔记">微信小程序学习笔记</h1>

<blockquote>
  <p>适合入门和回顾使用的个人笔记草稿，可按需增删。</p>
</blockquote>

<hr />

<h2 id="一基础概念">一、基础概念</h2>

<h3 id="11-什么是微信小程序">1.1 什么是微信小程序</h3>

<ul>
  <li>运行在微信客户端里的“轻应用”</li>
  <li>无需下载安装，扫码或搜索即可使用</li>
  <li>以“页面 + 逻辑 + 样式 + 配置”的形式组织</li>
  <li>使用自有技术栈：<code class="language-plaintext highlighter-rouge">WXML + WXSS + JS + JSON</code></li>
</ul>

<h3 id="12-与传统-web--app-的区别简要">1.2 与传统 Web / App 的区别（简要）</h3>

<ul>
  <li>运行环境：基于微信的 WebView + JSCore，不等于完整浏览器</li>
  <li>能力：可以调用部分原生能力（相机、位置、扫一扫等）</li>
  <li>体积限制：代码包大小有限制（主包 + 分包）</li>
  <li>生命周期、路由管理方式不同</li>
</ul>

<hr />

<h2 id="二项目结构与文件类型">二、项目结构与文件类型</h2>

<h3 id="21-典型目录结构">2.1 典型目录结构</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>project-root/
  ├── app.js          # 全局 JS 逻辑
  ├── app.json        # 全局配置（页面路由、窗口样式等）
  ├── app.wxss        # 全局样式
  ├── project.config.json  # 微信开发者工具项目配置
  ├── sitemap.json    # 页面收录配置
  ├── pages/          # 各业务页面目录
  │   └── index/
  │       ├── index.wxml
  │       ├── index.wxss
  │       ├── index.js
  │       └── index.json
  ├── utils/          # 工具模块
  └── ...             # 组件、自定义 tabBar 等
</code></pre></div></div>

<h3 id="22-四大基础文件">2.2 四大基础文件</h3>

<ol>
  <li><strong>WXML</strong>
    <ul>
      <li>类似 HTML，用于描述页面结构</li>
      <li>支持数据绑定、条件渲染、列表渲染等</li>
    </ul>
  </li>
  <li><strong>WXSS</strong>
    <ul>
      <li>类似 CSS，增加了 <code class="language-plaintext highlighter-rouge">rpx</code> 自适应单位</li>
      <li>支持全局样式 + 局部样式</li>
    </ul>
  </li>
  <li><strong>JS</strong>
    <ul>
      <li>页面逻辑、事件处理、网络请求、数据处理</li>
      <li>使用微信提供的 API，如 <code class="language-plaintext highlighter-rouge">wx.request</code>、<code class="language-plaintext highlighter-rouge">wx.showToast</code> 等</li>
    </ul>
  </li>
  <li><strong>JSON</strong>
    <ul>
      <li>项目和页面配置</li>
      <li><code class="language-plaintext highlighter-rouge">app.json</code>：全局配置</li>
      <li>页面同名 json：页面个性化配置</li>
    </ul>
  </li>
</ol>

<hr />

<h2 id="三配置文件详解">三、配置文件详解</h2>

<h3 id="31-appjson全局配置">3.1 app.json（全局配置）</h3>

<p>常见字段：</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"pages"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"pages/index/index"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"pages/logs/logs"</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"window"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"navigationBarBackgroundColor"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#ffffff"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"navigationBarTitleText"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Demo"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"navigationBarTextStyle"</span><span class="p">:</span><span class="w"> </span><span class="s2">"black"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"backgroundTextStyle"</span><span class="p">:</span><span class="w"> </span><span class="s2">"light"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"tabBar"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"list"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"pagePath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pages/index/index"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"首页"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"iconPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"assets/tab/home.png"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"selectedIconPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"assets/tab/home-selected.png"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"sitemapLocation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sitemap.json"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"style"</span><span class="p">:</span><span class="w"> </span><span class="s2">"v2"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"usingComponents"</span><span class="p">:</span><span class="w"> </span><span class="p">{}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="32-页面配置如-pagesindexindexjson">3.2 页面配置（如 <code class="language-plaintext highlighter-rouge">pages/index/index.json</code>）</h3>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"navigationBarTitleText"</span><span class="p">:</span><span class="w"> </span><span class="s2">"首页"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"enablePullDownRefresh"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"usingComponents"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"my-card"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/components/my-card/my-card"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<hr />

<h2 id="四wxml-基础语法">四、WXML 基础语法</h2>

<h3 id="41-数据绑定">4.1 数据绑定</h3>

<pre><code class="language-wxml">&lt;view&gt;&lt;/view&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Hello 小程序</span><span class="dl">'</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<h3 id="42-条件渲染">4.2 条件渲染</h3>

<pre><code class="language-wxml">&lt;view wx:if=""&gt;已登录&lt;/view&gt;
&lt;view wx:else&gt;未登录&lt;/view&gt;
</code></pre>

<h3 id="43-列表渲染">4.3 列表渲染</h3>

<pre><code class="language-wxml">&lt;view wx:for="" wx:key="id"&gt;
   - 
&lt;/view&gt;
</code></pre>

<h3 id="44-事件绑定">4.4 事件绑定</h3>

<pre><code class="language-wxml">&lt;button bindtap="handleTap" data-id=""&gt;点击&lt;/button&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="nx">handleTap</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">id</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">点击 id：</span><span class="dl">'</span><span class="p">,</span> <span class="nx">id</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="五wxss-与自适应布局">五、WXSS 与自适应布局</h2>

<h3 id="51-rpx-单位">5.1 rpx 单位</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">rpx</code>：根据屏幕宽度自适应，设计稿一般按 750 宽</li>
  <li>公式：<code class="language-plaintext highlighter-rouge">750rpx == 屏幕宽度</code></li>
  <li>例：iPhone 375 宽 =&gt; <code class="language-plaintext highlighter-rouge">1rpx = 0.5px</code></li>
</ul>

<h3 id="52-常用写法示例">5.2 常用写法示例</h3>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.container</span> <span class="p">{</span>
  <span class="nl">padding</span><span class="p">:</span> <span class="m">20</span><span class="n">rpx</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.title</span> <span class="p">{</span>
  <span class="nl">font-size</span><span class="p">:</span> <span class="m">32</span><span class="n">rpx</span><span class="p">;</span>
  <span class="nl">font-weight</span><span class="p">:</span> <span class="nb">bold</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="六页面与生命周期">六、页面与生命周期</h2>

<h3 id="61-小程序整体生命周期appjs">6.1 小程序整体生命周期（app.js）</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">App</span><span class="p">({</span>
  <span class="nx">onLaunch</span><span class="p">(</span><span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 小程序初始化</span>
  <span class="p">},</span>
  <span class="nx">onShow</span><span class="p">(</span><span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 从后台进入前台</span>
  <span class="p">},</span>
  <span class="nx">onHide</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 进入后台</span>
  <span class="p">},</span>
  <span class="nx">onError</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<h3 id="62-页面生命周期pagejs">6.2 页面生命周期（page.js）</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{},</span>

  <span class="nx">onLoad</span><span class="p">(</span><span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 页面加载（只触发一次）</span>
  <span class="p">},</span>

  <span class="nx">onShow</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 页面显示</span>
  <span class="p">},</span>

  <span class="nx">onReady</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 页面初次渲染完成</span>
  <span class="p">},</span>

  <span class="nx">onHide</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 页面隐藏</span>
  <span class="p">},</span>

  <span class="nx">onUnload</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 页面卸载</span>
  <span class="p">},</span>

  <span class="nx">onPullDownRefresh</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 监听用户下拉动作</span>
  <span class="p">},</span>

  <span class="nx">onReachBottom</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 触底事件</span>
  <span class="p">},</span>

  <span class="nx">onShareAppMessage</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 用户点击右上角分享</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="七路由与页面跳转">七、路由与页面跳转</h2>

<h3 id="71-常用跳转-api">7.1 常用跳转 API</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">wx.navigateTo</code>：保留当前页面，跳转到非 tabBar 页面</li>
  <li><code class="language-plaintext highlighter-rouge">wx.redirectTo</code>：关闭当前页面，跳转</li>
  <li><code class="language-plaintext highlighter-rouge">wx.switchTab</code>：切换到 tabBar 页面</li>
  <li><code class="language-plaintext highlighter-rouge">wx.reLaunch</code>：关闭所有页面，打开到某个页面</li>
  <li><code class="language-plaintext highlighter-rouge">wx.navigateBack</code>：返回上一级或多级页面</li>
</ul>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">wx</span><span class="p">.</span><span class="nx">navigateTo</span><span class="p">({</span>
  <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/pages/detail/detail?id=123</span><span class="dl">'</span>
<span class="p">})</span>
</code></pre></div></div>

<p>接收参数：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="nx">onLoad</span><span class="p">(</span><span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">options</span><span class="p">.</span><span class="nx">id</span><span class="p">)</span> <span class="c1">// 123</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="八网络请求与本地存储">八、网络请求与本地存储</h2>

<h3 id="81-网络请求">8.1 网络请求</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">wx</span><span class="p">.</span><span class="nx">request</span><span class="p">({</span>
  <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://example.com/api/list</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">GET</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span> <span class="na">page</span><span class="p">:</span> <span class="mi">1</span> <span class="p">},</span>
  <span class="nx">success</span><span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">data</span><span class="p">)</span>
  <span class="p">},</span>
  <span class="nx">fail</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<h3 id="82-本地存储">8.2 本地存储</h3>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 同步</span>
<span class="nx">wx</span><span class="p">.</span><span class="nx">setStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">token</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">abc123</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="nx">wx</span><span class="p">.</span><span class="nx">getStorageSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">token</span><span class="dl">'</span><span class="p">)</span>

<span class="c1">// 异步</span>
<span class="nx">wx</span><span class="p">.</span><span class="nx">setStorage</span><span class="p">({</span>
  <span class="na">key</span><span class="p">:</span> <span class="dl">'</span><span class="s1">userInfo</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Tom</span><span class="dl">'</span> <span class="p">},</span>
  <span class="nx">success</span><span class="p">()</span> <span class="p">{}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="九组件与模块化">九、组件与模块化</h2>

<h3 id="91-自定义组件基本结构">9.1 自定义组件基本结构</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>components/my-card/
  ├── my-card.wxml
  ├── my-card.wxss
  ├── my-card.js
  └── my-card.json
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">my-card.json</code>：</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"component"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">my-card.js</code>：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Component</span><span class="p">({</span>
  <span class="na">properties</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">title</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">type</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
      <span class="na">value</span><span class="p">:</span> <span class="dl">''</span>
    <span class="p">}</span>
  <span class="p">},</span>
  <span class="na">data</span><span class="p">:</span> <span class="p">{},</span>
  <span class="na">methods</span><span class="p">:</span> <span class="p">{</span>
    <span class="nx">handleTap</span><span class="p">()</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">triggerEvent</span><span class="p">(</span><span class="dl">'</span><span class="s1">tapCard</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<p>使用组件（页面 json 与 wxml）：</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"usingComponents"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"my-card"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/components/my-card/my-card"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<pre><code class="language-wxml">&lt;my-card title="测试卡片" bind:tapCard="onTapCard" /&gt;
</code></pre>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Page</span><span class="p">({</span>
  <span class="nx">onTapCard</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">card tapped</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<hr />

<h2 id="十常用能力与-api-速记">十、常用能力与 API 速记</h2>

<ul>
  <li>UI 提示
    <ul>
      <li><code class="language-plaintext highlighter-rouge">wx.showToast</code>、<code class="language-plaintext highlighter-rouge">wx.showModal</code>、<code class="language-plaintext highlighter-rouge">wx.showLoading</code>、<code class="language-plaintext highlighter-rouge">wx.hideLoading</code></li>
    </ul>
  </li>
  <li>媒体相关
    <ul>
      <li><code class="language-plaintext highlighter-rouge">wx.chooseImage</code>、<code class="language-plaintext highlighter-rouge">wx.previewImage</code>、<code class="language-plaintext highlighter-rouge">wx.saveImageToPhotosAlbum</code></li>
    </ul>
  </li>
  <li>位置与地图
    <ul>
      <li><code class="language-plaintext highlighter-rouge">wx.getLocation</code>、<code class="language-plaintext highlighter-rouge">wx.openLocation</code>、<code class="language-plaintext highlighter-rouge">&lt;map /&gt;</code> 组件</li>
    </ul>
  </li>
  <li>授权与登录
    <ul>
      <li><code class="language-plaintext highlighter-rouge">wx.login</code>、<code class="language-plaintext highlighter-rouge">wx.getUserProfile</code>（注意隐私合规）</li>
    </ul>
  </li>
  <li>文件上传下载
    <ul>
      <li><code class="language-plaintext highlighter-rouge">wx.uploadFile</code>、<code class="language-plaintext highlighter-rouge">wx.downloadFile</code></li>
    </ul>
  </li>
</ul>

<hr />

<h2 id="十一调试与发布流程">十一、调试与发布流程</h2>

<ol>
  <li>安装并打开 <strong>微信开发者工具</strong></li>
  <li>使用 AppID 创建项目（个人练习可使用测试号）</li>
  <li>开发阶段：
    <ul>
      <li>使用“预览”在手机端测试</li>
      <li>使用“真机调试”排查问题</li>
    </ul>
  </li>
  <li>上传代码到微信后台</li>
  <li>在微信公众平台配置：
    <ul>
      <li>服务器域名</li>
      <li>业务域名</li>
      <li>体验版 / 审核 / 正式发布</li>
    </ul>
  </li>
</ol>]]></content><author><name></name></author><category term="miniprogram1" /><summary type="html"><![CDATA[微信小程序学习笔记 适合入门和回顾使用的个人笔记草稿，可按需增删。 一、基础概念 1.1 什么是微信小程序 运行在微信客户端里的“轻应用” 无需下载安装，扫码或搜索即可使用 以“页面 + 逻辑 + 样式 + 配置”的形式组织 使用自有技术栈：WXML + WXSS + JS + JSON 1.2 与传统 Web / App 的区别（简要） 运行环境：基于微信的 WebView + JSCore，不等于完整浏览器 能力：可以调用部分原生能力（相机、位置、扫一扫等） 体积限制：代码包大小有限制（主包 + 分包） 生命周期、路由管理方式不同 二、项目结构与文件类型 2.1 典型目录结构 project-root/ ├── app.js # 全局 JS 逻辑 ├── app.json # 全局配置（页面路由、窗口样式等） ├── app.wxss # 全局样式 ├── project.config.json # 微信开发者工具项目配置 ├── sitemap.json # 页面收录配置 ├── pages/ # 各业务页面目录 │ └── index/ │ ├── index.wxml │ ├── index.wxss │ ├── index.js │ └── index.json ├── utils/ # 工具模块 └── ... # 组件、自定义 tabBar 等 2.2 四大基础文件 WXML 类似 HTML，用于描述页面结构 支持数据绑定、条件渲染、列表渲染等 WXSS 类似 CSS，增加了 rpx 自适应单位 支持全局样式 + 局部样式 JS 页面逻辑、事件处理、网络请求、数据处理 使用微信提供的 API，如 wx.request、wx.showToast 等 JSON 项目和页面配置 app.json：全局配置 页面同名 json：页面个性化配置 三、配置文件详解 3.1 app.json（全局配置） 常见字段： { "pages": [ "pages/index/index", "pages/logs/logs" ], "window": { "navigationBarBackgroundColor": "#ffffff", "navigationBarTitleText": "Demo", "navigationBarTextStyle": "black", "backgroundTextStyle": "light" }, "tabBar": { "list": [ { "pagePath": "pages/index/index", "text": "首页", "iconPath": "assets/tab/home.png", "selectedIconPath": "assets/tab/home-selected.png" } ] }, "sitemapLocation": "sitemap.json", "style": "v2", "usingComponents": {} } 3.2 页面配置（如 pages/index/index.json） { "navigationBarTitleText": "首页", "enablePullDownRefresh": true, "usingComponents": { "my-card": "/components/my-card/my-card" } } 四、WXML 基础语法 4.1 数据绑定 &lt;view&gt;&lt;/view&gt; Page({ data: { message: 'Hello 小程序' } }) 4.2 条件渲染 &lt;view wx:if=""&gt;已登录&lt;/view&gt; &lt;view wx:else&gt;未登录&lt;/view&gt; 4.3 列表渲染 &lt;view wx:for="" wx:key="id"&gt; - &lt;/view&gt; 4.4 事件绑定 &lt;button bindtap="handleTap" data-id=""&gt;点击&lt;/button&gt; Page({ handleTap(e) { const id = e.currentTarget.dataset.id console.log('点击 id：', id) } }) 五、WXSS 与自适应布局 5.1 rpx 单位 rpx：根据屏幕宽度自适应，设计稿一般按 750 宽 公式：750rpx == 屏幕宽度 例：iPhone 375 宽 =&gt; 1rpx = 0.5px 5.2 常用写法示例 .container { padding: 20rpx; } .title { font-size: 32rpx; font-weight: bold; } 六、页面与生命周期 6.1 小程序整体生命周期（app.js） App({ onLaunch(options) { // 小程序初始化 }, onShow(options) { // 从后台进入前台 }, onHide() { // 进入后台 }, onError(err) { console.error(err) } }) 6.2 页面生命周期（page.js） Page({ data: {}, onLoad(options) { // 页面加载（只触发一次） }, onShow() { // 页面显示 }, onReady() { // 页面初次渲染完成 }, onHide() { // 页面隐藏 }, onUnload() { // 页面卸载 }, onPullDownRefresh() { // 监听用户下拉动作 }, onReachBottom() { // 触底事件 }, onShareAppMessage() { // 用户点击右上角分享 } }) 七、路由与页面跳转 7.1 常用跳转 API wx.navigateTo：保留当前页面，跳转到非 tabBar 页面 wx.redirectTo：关闭当前页面，跳转 wx.switchTab：切换到 tabBar 页面 wx.reLaunch：关闭所有页面，打开到某个页面 wx.navigateBack：返回上一级或多级页面 wx.navigateTo({ url: '/pages/detail/detail?id=123' }) 接收参数： Page({ onLoad(options) { console.log(options.id) // 123 } }) 八、网络请求与本地存储 8.1 网络请求 wx.request({ url: 'https://example.com/api/list', method: 'GET', data: { page: 1 }, success(res) { console.log(res.data) }, fail(err) { console.error(err) } }) 8.2 本地存储 // 同步 wx.setStorageSync('token', 'abc123') const token = wx.getStorageSync('token') // 异步 wx.setStorage({ key: 'userInfo', data: { name: 'Tom' }, success() {} }) 九、组件与模块化 9.1 自定义组件基本结构 components/my-card/ ├── my-card.wxml ├── my-card.wxss ├── my-card.js └── my-card.json my-card.json： { "component": true } my-card.js： Component({ properties: { title: { type: String, value: '' } }, data: {}, methods: { handleTap() { this.triggerEvent('tapCard') } } }) 使用组件（页面 json 与 wxml）： { "usingComponents": { "my-card": "/components/my-card/my-card" } } &lt;my-card title="测试卡片" bind:tapCard="onTapCard" /&gt; Page({ onTapCard() { console.log('card tapped') } }) 十、常用能力与 API 速记 UI 提示 wx.showToast、wx.showModal、wx.showLoading、wx.hideLoading 媒体相关 wx.chooseImage、wx.previewImage、wx.saveImageToPhotosAlbum 位置与地图 wx.getLocation、wx.openLocation、&lt;map /&gt; 组件 授权与登录 wx.login、wx.getUserProfile（注意隐私合规） 文件上传下载 wx.uploadFile、wx.downloadFile 十一、调试与发布流程 安装并打开 微信开发者工具 使用 AppID 创建项目（个人练习可使用测试号） 开发阶段： 使用“预览”在手机端测试 使用“真机调试”排查问题 上传代码到微信后台 在微信公众平台配置： 服务器域名 业务域名 体验版 / 审核 / 正式发布]]></summary></entry><entry><title type="html">金蝶云星空旗舰版-演示数据账套的创建</title><link href="https://altair288.github.io/Blog/kingdee/" rel="alternate" type="text/html" title="金蝶云星空旗舰版-演示数据账套的创建" /><published>2025-01-15T00:00:00+00:00</published><updated>2025-01-15T00:00:00+00:00</updated><id>https://altair288.github.io/Blog/kingdee</id><content type="html" xml:base="https://altair288.github.io/Blog/kingdee/"><![CDATA[<h1 id="创建数据库实例">创建数据库实例</h1>

<h2 id="1-下载演示中心数据">1. 下载演示中心数据</h2>

<p>访问地址：</p>

<p>https://vip.kingdee.com/article/489817422015440128?productLineId=40&amp;isKnowledge=2&amp;lang=zh-CN</p>

<p>下载符合私有云布署平台的演示中心数据。没特殊要求，下载最新日期的。</p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/14/z87dkv.jpg" alt="PixPin_2025-01-14_21-29-02" /></p>

<p>下载后的文件是个zip文件</p>

<h2 id="2-确定master节点">2. 确定master节点</h2>

<p>在安装时会要求指定哪个服务器为master。如下图：</p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/14/z9fvvb.png" alt="0100b7151bb42018477ab960a8cf716c48d3" /></p>

<h2 id="3-拷贝演示数据中心数据到master节点">3. 拷贝演示数据中心数据到master节点</h2>

<p>将下载的演示数据中心文件进行解压，将解压后的文件拷贝到私有云的布署集群的master节点中的/tmp文件夹下。</p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/14/z9ogy6.png" alt="010006a7d49a13274fa7b2ec680b0ca8f678" /></p>

<h2 id="4-登陆master节点">4. 登陆master节点</h2>

<p>ssh登陆到私有云的master节点。</p>

<h2 id="5-切换到用户">5. 切换到用户</h2>

<p>将当前用户切换到galaxyship</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>su – galaxyship
</code></pre></div></div>

<h2 id="6-进行文件授权">6. 进行文件授权</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#给kubernetes的配置文件授可读权限</span>
<span class="nb">sudo chmod</span> +r /etc/kubernetes/admin.conf
<span class="c">#给baseline_show.backup授权可读权限</span>
<span class="nb">sudo chmod</span> +r /tmp/baseline_show.backup
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/14/zau8yo.png" alt="01008478cd245c5945aabe45f550a44973cd" /></p>

<h2 id="7-将文件拷贝到容器内">7. 将文件拷贝到容器内</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl <span class="nb">cp</span> /tmp/baseline_show.backup  pg-system/kd-cosmic-1:/var/lib/postgresql/data
</code></pre></div></div>
<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/14/zaufor.png" alt="0100e6d80cd3965c43f489d2aa63a27a5183" /></p>

<h2 id="8-进入容器">8. 进入容器</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl <span class="nb">exec</span> <span class="nt">-it</span> <span class="nt">-n</span> pg-system kd-cosmic-1 <span class="nt">--</span> bash
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/14/zauj9w.png" alt="0100912ef5527ea4481fb016d18bda128189" /></p>

<h2 id="9-进入到数据库备份文件所在文件夹">9. 进入到数据库备份文件所在文件夹</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> /var/lib/postgresql/data
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/14/zbugse.png" alt="0100efc6a8dd4db5486788854f70aff0916f" /></p>

<h2 id="10-确认文件在容器内">10. 确认文件在容器内</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span>
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/14/zdk2wl.png" alt="01000f4cb17f687e4a18a726c0acc626ad32" /></p>

<h2 id="11-设置需要还原的新的库的实例名称">11. 设置需要还原的新的库的实例名称</h2>

<p>比如需要将演示中心的数据备份文件还原成名称叫xk_demo_all的实例。则执行以下命令。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">instance_db</span><span class="o">=</span>xk_demo_all
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/14/zbug8d.png" alt="01004fefff3e303a42b097c9aaa24064f745" /></p>

<h2 id="12-设置其它的默认变量">12. 设置其它的默认变量</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#在容器中设置相关变量</span>
<span class="c">#数据库备份文件的名称</span>
<span class="nv">dumpName</span><span class="o">=</span>baseline_show.backup
<span class="c">#将要生成的toc文件的名称   toc文件是还原过程中需要生成的一个文件</span>
<span class="nv">tocName</span><span class="o">=</span>baseline_show.toc
<span class="c">#还原的新的数据库拥有者  这个不需要修改，默认即可</span>
<span class="nv">adminUser</span><span class="o">=</span>cosmic
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/14/zbus2h.png" alt="010017167ff54f4e4e8f963a0cc3d07b7b0e" /></p>

<h2 id="13-创建数据库">13. 创建数据库</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#创建数据库</span>
psql   <span class="nt">-d</span> postgres <span class="nt">-c</span> <span class="s2">"create database </span><span class="k">${</span><span class="nv">instance_db</span><span class="k">}</span><span class="s2"> encoding utf8 owner </span><span class="k">${</span><span class="nv">adminUser</span><span class="k">}</span><span class="s2">"</span>
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/14/zepyaz.png" alt="0100ca58870049b04bc09b468bbf79253d29" /></p>

<h2 id="14-生成toc文件">14. 生成toc文件</h2>

<p>Toc文件是数据库还原过程中需要的一个文件，通过以下命令生成</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pg_restore  <span class="nt">-l</span>  <span class="k">${</span><span class="nv">dumpName</span><span class="k">}</span> <span class="o">&gt;</span> <span class="k">${</span><span class="nv">tocName</span><span class="k">}</span>
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/14/zeq1zp.png" alt="01001b1fe25e5be240fbb1952dd2afefbaca" /></p>

<h2 id="15-确认生成了toc文件">15. 确认生成了toc文件</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span>
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/14/zf1kg4.png" alt="010026555ccff4904c6e8fd0a9d3b0096579" /></p>

<h2 id="16-进行数据库还原">16. 进行数据库还原</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pg_restore    <span class="nt">-j</span> 8  <span class="nt">-d</span> <span class="k">${</span><span class="nv">instance_db</span><span class="k">}</span> <span class="nt">-c</span> <span class="nt">--if-exists</span> <span class="nt">-L</span>  <span class="k">${</span><span class="nv">tocName</span><span class="k">}</span> <span class="nt">--no-owner</span> <span class="nt">--role</span> <span class="k">${</span><span class="nv">adminUser</span><span class="k">}</span> <span class="nv">$dumpName</span> <span class="nt">-v</span> 1&gt;/dev/null
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/14/zfhk2w.png" alt="01005a49c1f430d747638ff4ae829db21cf3" /></p>]]></content><author><name></name></author><category term="Kingdee" /><summary type="html"><![CDATA[创建数据库实例 1. 下载演示中心数据 访问地址： https://vip.kingdee.com/article/489817422015440128?productLineId=40&amp;isKnowledge=2&amp;lang=zh-CN 下载符合私有云布署平台的演示中心数据。没特殊要求，下载最新日期的。 下载后的文件是个zip文件 2. 确定master节点 在安装时会要求指定哪个服务器为master。如下图： 3. 拷贝演示数据中心数据到master节点 将下载的演示数据中心文件进行解压，将解压后的文件拷贝到私有云的布署集群的master节点中的/tmp文件夹下。 4. 登陆master节点 ssh登陆到私有云的master节点。 5. 切换到用户 将当前用户切换到galaxyship sudo su – galaxyship 6. 进行文件授权 #给kubernetes的配置文件授可读权限 sudo chmod +r /etc/kubernetes/admin.conf #给baseline_show.backup授权可读权限 sudo chmod +r /tmp/baseline_show.backup 7. 将文件拷贝到容器内 kubectl cp /tmp/baseline_show.backup pg-system/kd-cosmic-1:/var/lib/postgresql/data 8. 进入容器 kubectl exec -it -n pg-system kd-cosmic-1 -- bash 9. 进入到数据库备份文件所在文件夹 cd /var/lib/postgresql/data 10. 确认文件在容器内 ls 11. 设置需要还原的新的库的实例名称 比如需要将演示中心的数据备份文件还原成名称叫xk_demo_all的实例。则执行以下命令。 instance_db=xk_demo_all 12. 设置其它的默认变量 #在容器中设置相关变量 #数据库备份文件的名称 dumpName=baseline_show.backup #将要生成的toc文件的名称 toc文件是还原过程中需要生成的一个文件 tocName=baseline_show.toc #还原的新的数据库拥有者 这个不需要修改，默认即可 adminUser=cosmic 13. 创建数据库 #创建数据库 psql -d postgres -c "create database ${instance_db} encoding utf8 owner ${adminUser}" 14. 生成toc文件 Toc文件是数据库还原过程中需要的一个文件，通过以下命令生成 pg_restore -l ${dumpName} &gt; ${tocName} 15. 确认生成了toc文件 ls 16. 进行数据库还原 pg_restore -j 8 -d ${instance_db} -c --if-exists -L ${tocName} --no-owner --role ${adminUser} $dumpName -v 1&gt;/dev/null]]></summary></entry><entry><title type="html">Ubuntu 22.04 基于Kubernets集群安装金蝶云星空-旗舰版</title><link href="https://altair288.github.io/Blog/Kingdee/" rel="alternate" type="text/html" title="Ubuntu 22.04 基于Kubernets集群安装金蝶云星空-旗舰版" /><published>2025-01-05T00:00:00+00:00</published><updated>2025-01-05T00:00:00+00:00</updated><id>https://altair288.github.io/Blog/Kingdee</id><content type="html" xml:base="https://altair288.github.io/Blog/Kingdee/"><![CDATA[<h1 id="安装要求">安装要求</h1>

<h2 id="1-账号要求">1 账号要求</h2>

<p>需要有金蝶云企业账号，并且是客户的管理员角色（只有管理员才能看到对应的企业名称）。
说明：金蝶云企业账号注册地址 企业账号中心-我的企业 (https://account.kdcloud.com/main)。
可参考 https://account.kdcloud.com/helpCenter?id=3515718155284762624 进行注册。</p>

<h2 id="2-网络要求">2 网络要求</h2>

<ol>
  <li>
    <p>环境可以连接互联网
 安装过程需要能够连接galaxyship.kingdee.com和galaxyship-registry.kingdee.com</p>
  </li>
  <li>
    <p>节点之间网络能通，以下端口不能占用：</p>
    <ul>
      <li>TCP 端口：2379、 2380、6443、10250、10257、10259</li>
      <li>UDP 端口：8472</li>
    </ul>
  </li>
</ol>

<h2 id="3-操作系统要求">3 操作系统要求</h2>

<ul>
  <li>
    <p>Ubuntu 18.04*，20.04，22.04</p>
  </li>
  <li>
    <p>CentOS  7.4<em>，7.5</em>，7.6<em>，7.7</em>，7.8<em>，7.9</em>，8.0<em>，8.1</em>，8.2<em>，8.3</em>，8.4*</p>
  </li>
  <li>
    <p>Rocky Linux 8.5，9.0<em>，9.1</em>，9.2，9.3，9.3</p>
  </li>
  <li>
    <p>RHEL  7.4<em>，7.5</em>，7.6<em>，7.7</em>，7.8<em>，7.9</em>，8.0<em>，8.1</em>，8.2<em>，8.3</em>，8.4<em>，8.5</em>，8.6，8.7<em>，8.8，8.9，8.10，9.0，9.1</em>，9.2，9.3</p>
  </li>
</ul>

<h2 id="4-服务器要求">4 服务器要求</h2>

<h3 id="41-配置要求">4.1 配置要求</h3>

<ol>
  <li>
    <p>单节点模式(非生产场景)</p>

    <p>CPU: x86-64 4核, 内存:16GB，SSD磁盘200GB</p>

    <p><strong>注意：当前集群日志、镜像、持久存储都默认在/var/目录下，所以要确保/var/有至少100GB及以上存储空间。</strong></p>
  </li>
  <li>
    <p>多节点模式(一个master，1或者N个worker节点)</p>

    <p>每个节点最低配置:</p>

    <p>CPU: x86-64 4核, 内存:16GB，SSD磁盘256GB（根据数据量的大小需要提前规划在些基础上增加）</p>
  </li>
</ol>

<h3 id="42-目录存储空间要求">4.2 目录存储空间要求</h3>

<p>当前集群日志、镜像、持久存储都默认在/var/目录下，所以要确保/var/有至少200GB及以上存储空间。</p>

<p>关键目录位置:</p>

<ul>
  <li>/var/lib/kubelet: &gt;30GB</li>
  <li>/var/lib/containerd: &gt; 50GB</li>
  <li>/var/openebs: PV持久存储, 存储数据库等，按需要配置</li>
</ul>

<h3 id="43-软件环境要求">4.3 软件环境要求</h3>

<ul>
  <li>如果服务器安装了docker ,  请先删除docker</li>
</ul>

<h3 id="44-主机名要求">4.4 主机名要求</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>运行: <span class="nb">cat</span> /etc/hostname 查看主机名称
</code></pre></div></div>

<ul>
  <li>主机hostname必须以字母开头且必须以字母数字结尾，主机hostname最多包含63个字符且只能包含小写字母、数字以及中划线-</li>
</ul>

<h3 id="45-浏览器要求">4.5 浏览器要求</h3>

<ul>
  <li>Google Chrome        如果使用有兼容性问题，请升级到最新版本。</li>
  <li>Microsoft Edge      如果使用有兼容性问题，请升级到最新版本。</li>
  <li>Safari             如果使用有兼容性问题，请升级到最新版本。</li>
  <li>不支持IE浏览器</li>
</ul>

<h1 id="环境准备步骤">环境准备步骤</h1>

<h2 id="0-安装必要工具及确认防火墙关闭">0 安装必要工具及确认防火墙关闭</h2>

<ol>
  <li>Ubuntu安装Openssh</li>
</ol>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>openssh-server
</code></pre></div></div>

<ol>
  <li>安装net-tools及curl</li>
</ol>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>net-tools curl
</code></pre></div></div>

<ol>
  <li>确认防火墙是否关闭</li>
</ol>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>ufw status
<span class="c"># Status: inactive 表示防火墙未启用</span>
<span class="c"># Status: active 表示防火墙已启用</span>
<span class="nb">sudo </span>ufw disable <span class="c">#关闭防火墙</span>
</code></pre></div></div>

<h2 id="1-添加galaxyship安装用户">1 添加galaxyship安装用户</h2>

<p>为了便于维护及进行权限的隔离，需要用专有的用户进行安装。如果有多个集群节点，需要在每个节点的机器上都创建此安装用户。</p>

<p>用户要求如下：</p>

<ol>
  <li>用户名为：galaxyship</li>
  <li>用户在root组内</li>
  <li>用户执行sudo 不需要输入密码。</li>
</ol>

<p>通过以下脚本进行用户的创建及授权：</p>

<p>#创建用户</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>useradd <span class="nt">-m</span> galaxyship
</code></pre></div></div>

<p>#添加到root用户组</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>usermod <span class="nt">-aG</span> root galaxyship
</code></pre></div></div>

<p>#进行sudo免密设置</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>bash <span class="nt">-c</span> <span class="s1">'echo -e "galaxyship ALL=(ALL) NOPASSWD: ALL" &gt;&gt; /etc/sudoers'</span>
</code></pre></div></div>

<p>#将新用户默认的shell改为bash 以避免切换用户的时候报错</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>chsh <span class="nt">-s</span> /bin/bash galaxyship
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/hgq2ai.png" alt="pic-1" /></p>

<h2 id="2-修改galaxyship用户的密码">2 修改galaxyship用户的密码</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>passwd galaxyship
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/hhg5dr.png" alt="pic-2" /></p>

<h2 id="3-进行ssh登陆设置">3 进行ssh登陆设置</h2>

<p>需要在哪台服务器（比如安装器所安装的服务器）免密连接到其它的服务器，就在哪台服务器（比如安装器所安装的服务器）上执行以下操作：</p>

<ol>
  <li>切换到安装用户</li>
</ol>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>su galaxyship
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/i7izgm.png" alt="pic-3" /></p>

<ol>
  <li>生成ssh密钥对</li>
</ol>

<p>执行以下命令，过程中续回车即可：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/i7jyow.png" alt="pic-4" /></p>

<ol>
  <li>将密钥对的公钥拷贝到需要免密登陆的服务器上</li>
</ol>

<p>可通过以下命令进行公钥的拷贝</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-copy-id galaxyship@<span class="k">${</span><span class="p">集群节点ip</span><span class="k">}</span>
</code></pre></div></div>

<p><strong>如果有多个集群节点，需要对每个节点都进行公钥的拷贝。</strong></p>

<p><strong>注意：如果执行脚本的机器也作为一个服务节点，需要执行</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-copy-id galaxyship@127.0.0.1
</code></pre></div></div>

<p>比如命令为：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-copy-id galaxyship@172.27.94.2
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/i7kb1i.png" alt="pic-5" /></p>

<h2 id="4-确认关闭swap分区">4 确认关闭swap分区</h2>

<p>执行以下命令，以检查是否是swap分区是否关闭。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>free
</code></pre></div></div>

<p>关闭了swap分区：</p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/i7kuml.png" alt="pic-6" /></p>

<p>未关闭swap分区：</p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/i8yuof.png" alt="pic-7" /></p>

<p>如果显示未关闭swap分区，可以用以下命令进行关闭：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>swapoff <span class="nt">-a</span>
</code></pre></div></div>

<h1 id="安装器下载">安装器下载</h1>

<p>安装kubernetes集群需要提前服务器准备，请查看上面安装要求</p>

<h2 id="1-登陆安装部署平台">1. 登陆【安装部署平台】</h2>

<ul>
  <li>先在金蝶云账号中心注册企业且完成企业认证</li>
  <li>使用该企业用户信息 登录安装部署平台https://galaxyship.kingdee.com</li>
  <li>该用户即可查看到企业名称</li>
</ul>

<h2 id="2-选择需要部署环境的企业">2. 选择需要部署环境的企业</h2>

<p>请在列头上选择需要部署环境的企业在选择企业名称时，分两种场景：</p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/ll2y51.png" alt="pic-8" /></p>

<ol>
  <li>
    <p>如果是安装客户正式环境（含沙箱环境、正式生产环境），则在选择企业名称时，一定要选择在【企业账号中心】上认证过的企业名称；</p>
  </li>
  <li>
    <p>如果是安装实施或机构本地测试类环境（非客户的），则在选择企业名称时，该企业名称在【企业账号中心】没有认证要求。</p>
  </li>
</ol>

<h2 id="3-选择要安装的目标环境">3. 选择要安装的目标环境</h2>
<ul>
  <li>选择安装环境后，点击下一步。安装环境根据实际需要进行选择。</li>
</ul>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/lmud0u.png" alt="pic-9" /></p>

<h2 id="4-下载集群安装器">4. 下载集群安装器</h2>

<ol>
  <li>安装kubernetes集群和Agent的安装器。</li>
</ol>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/lp5h7g.png" alt="pic-10" /></p>

<p>如果还没有安装kubernetes，则下载此安装器。</p>

<p>安装器下载后，是一个Linux shell脚本，名称为：installk8s.sh</p>

<h1 id="安装kubernetes集群">安装kubernetes集群</h1>

<p>如果还没有安装kubernetes，则在该步骤中利用“安装kubernetes集群和Agent的安装器”进行安装，即执行installk8s.sh。</p>

<h2 id="1-执行安装器">1. 执行安装器</h2>

<p>将脚本复制到目标linux服务器上，然后在命令行运行:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo chmod</span> +x ./installk8s.sh
bash ./installk8s.sh
</code></pre></div></div>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/lr9ezy.png" alt="pic-11" /></p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/lr9ds9.png" alt="pic-12" /></p>

<p>脚本执行的过程中，会下载kubernetes的相关文件，根据网速的情况，所需要的下载的时间有所区别。
脚本执行完成后，控制台会显示星空旗舰产品安装的访问页面地址。复制到浏览器中进行访问即可。
如果需要在多台机器上布署，<strong>也是选择在其中一台上执行此安装脚本即可</strong>。具体如下：</p>

<ul>
  <li>多节点安装示意图：</li>
</ul>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/ls706f.png" alt="pic-13" /></p>

<ul>
  <li>单节点安装示意图：</li>
</ul>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/ls76w5.png" alt="pic-14" /></p>

<h2 id="2-访问安装器页面">2. 访问安装器页面</h2>

<p>执行完installk8s.sh后，会启动一个web网站，通过访问此网站来进行星空旗舰产品安装。</p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/ltknbo.png" alt="pic-15" /></p>

<h2 id="3-填写安装用户">3. 填写安装用户</h2>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/ltm12r.png" alt="pic-16" /></p>

<h2 id="4-添加安装节点">4. 添加安装节点</h2>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/ltmdpe.png" alt="pic-17" /></p>

<p>根据安装需要，进行添加不同的节点，并进行相关的节点的信息的填写。</p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/ltmxwk.png" alt="pic-18" /></p>

<p>填写说明：</p>

<ol>
  <li>节点类型：kubernetes的master节点或worker节点；</li>
  <li>名称：节点的名称，系统自动生成，不可修改；</li>
  <li>SSH连接（host）：填写安装节点所在的机器ip；</li>
  <li>集群内网IP。
    <ul>
      <li>如果节点只有一个网卡，这里不需要填写。没有特别安装网卡，一般都只有一个网卡。</li>
      <li>当节点存在多个网卡时，需要在这里指定要用的网卡IP。在后续安装时，会用上这个IP。需要注意的是，不同节点所填写的IP需要在同一个网段中。</li>
    </ul>
  </li>
  <li>端口：节点上对外提供ssh服务的端口。
<strong>注意，暂时不支持多master节点高可用模式。也就是不能添加两个节点类型为master的节点。</strong></li>
</ol>

<h2 id="5-保存配置信息">5. 保存配置信息</h2>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/lwh8zu.png" alt="pic-19" /></p>

<h2 id="6-安装kubernetes集群和agent">6. 安装kubernetes集群和Agent</h2>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/lwhhzx.png" alt="pic-20" /></p>

<p><strong>注意，安装前请确认是否满足“软件环境要求”章节所提到的要求。</strong></p>

<h2 id="7-查看安装日志及进度">7. 查看安装日志及进度</h2>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/lwhxzc.png" alt="pic-21" /></p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/lwin1p.png" alt="pic-22" /></p>

<h1 id="安装星空旗舰产品">安装星空旗舰产品</h1>

<h2 id="1-确认agent安装状态">1. 确认agent安装状态</h2>

<p>回到部署平台: http://galaxyship.kingdee.com ，查看agent连接状态。如果是已连接，则可以点击下一步：</p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/lzljzv.png" alt="pic-23" /></p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/lzunb9.png" alt="pic-24" /></p>

<h2 id="2-进行星空旗舰产品配置">2. 进行星空旗舰产品配置</h2>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m19v2r.png" alt="pic-25" /></p>

<p>重点需要配置每个一组件（当前只支持高级模式，<strong>如果需要建议只修改标记处</strong>）</p>

<ul>
  <li>nginx端口配置</li>
</ul>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m3mjhp.png" alt="pic-26" /></p>

<ul>
  <li>MC配置服务</li>
</ul>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m3o08e.png" alt="pic-27" /></p>

<ul>
  <li>kd-cosmic-migration(数据初始化服务)</li>
</ul>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m3p4f0.png" alt="pic-28" /></p>

<ul>
  <li>配置默认的企业编码和企业名称以及管理员手机号</li>
</ul>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m4d19f.png" alt="pic-29" /></p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m4dczt.png" alt="pic-30" /></p>

<ul>
  <li>kd-cosmic-xk(星空旗舰服务)</li>
</ul>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m5asz3.png" alt="pic-31" /></p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m5ax4x.png" alt="pic-32" /></p>

<ul>
  <li>根据需要配置fileserver的nfs</li>
</ul>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m5k72c.png" alt="pic-33" /></p>

<ul>
  <li>postgresql数据库配置</li>
</ul>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m5t92v.png" alt="pic-34" /></p>

<ul>
  <li>勾选pg库对应的节点</li>
</ul>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m6i3wn.png" alt="pic-35" /></p>

<ul>
  <li>根据需要，配置数据库对外访问的服务端口。默认不对外提供数据库访问服务。</li>
</ul>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m6icpk.png" alt="pic-36" /></p>

<ul>
  <li>redis配置</li>
</ul>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m6i65l.png" alt="pic-37" /></p>

<h2 id="3-进行配置的保存">3. 进行配置的保存</h2>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m7c75b.png" alt="pic-38" /></p>

<h2 id="4-进行产品的安装">4. 进行产品的安装</h2>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m7cit3.png" alt="pic-39" /></p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m7ljcs.png" alt="pic-40" /></p>

<h2 id="5-安装资源事件日志查看">5. 安装资源、事件、日志查看</h2>

<p>如果安装过程中有失败，需要查询相关的事件、日志的信息，以确定失败的原因。</p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m89gdh.png" alt="pic-41" /></p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m89v8u.png" alt="pic-42" /></p>

<p><img src="https://share.altair288.eu.org/easyimage/i/2025/01/07/m8ve5m.png" alt="pic-43" /></p>

<h2 id="6-mc和erp的访问">6. mc和erp的访问</h2>

<p>直接通过ip的访问</p>

<ol>
  <li>
    <p>星空旗舰产品</p>

    <p>网址为：http:// $ {ip}:31101/</p>

    <p>账号为：administrator/Kdxk#admin001</p>
  </li>
  <li>
    <p>MC</p>

    <p>网址为：http:// ${ip}:31102/</p>

    <p>账号为：admin/Kdadmin001</p>
  </li>
</ol>

<p><strong>其中，${ip}为安装的服务器节点中的一个节点的ip。</strong></p>]]></content><author><name></name></author><category term="Kingdee" /><summary type="html"><![CDATA[安装要求 1 账号要求 需要有金蝶云企业账号，并且是客户的管理员角色（只有管理员才能看到对应的企业名称）。 说明：金蝶云企业账号注册地址 企业账号中心-我的企业 (https://account.kdcloud.com/main)。 可参考 https://account.kdcloud.com/helpCenter?id=3515718155284762624 进行注册。 2 网络要求 环境可以连接互联网 安装过程需要能够连接galaxyship.kingdee.com和galaxyship-registry.kingdee.com 节点之间网络能通，以下端口不能占用： TCP 端口：2379、 2380、6443、10250、10257、10259 UDP 端口：8472 3 操作系统要求 Ubuntu 18.04*，20.04，22.04 CentOS 7.4，7.5，7.6，7.7，7.8，7.9，8.0，8.1，8.2，8.3，8.4* Rocky Linux 8.5，9.0，9.1，9.2，9.3，9.3 RHEL 7.4，7.5，7.6，7.7，7.8，7.9，8.0，8.1，8.2，8.3，8.4，8.5，8.6，8.7，8.8，8.9，8.10，9.0，9.1，9.2，9.3 4 服务器要求 4.1 配置要求 单节点模式(非生产场景) CPU: x86-64 4核, 内存:16GB，SSD磁盘200GB 注意：当前集群日志、镜像、持久存储都默认在/var/目录下，所以要确保/var/有至少100GB及以上存储空间。 多节点模式(一个master，1或者N个worker节点) 每个节点最低配置: CPU: x86-64 4核, 内存:16GB，SSD磁盘256GB（根据数据量的大小需要提前规划在些基础上增加） 4.2 目录存储空间要求 当前集群日志、镜像、持久存储都默认在/var/目录下，所以要确保/var/有至少200GB及以上存储空间。 关键目录位置: /var/lib/kubelet: &gt;30GB /var/lib/containerd: &gt; 50GB /var/openebs: PV持久存储, 存储数据库等，按需要配置 4.3 软件环境要求 如果服务器安装了docker , 请先删除docker 4.4 主机名要求 运行: cat /etc/hostname 查看主机名称 主机hostname必须以字母开头且必须以字母数字结尾，主机hostname最多包含63个字符且只能包含小写字母、数字以及中划线- 4.5 浏览器要求 Google Chrome 如果使用有兼容性问题，请升级到最新版本。 Microsoft Edge 如果使用有兼容性问题，请升级到最新版本。 Safari 如果使用有兼容性问题，请升级到最新版本。 不支持IE浏览器 环境准备步骤 0 安装必要工具及确认防火墙关闭 Ubuntu安装Openssh sudo apt install openssh-server 安装net-tools及curl sudo apt install net-tools curl 确认防火墙是否关闭 sudo ufw status # Status: inactive 表示防火墙未启用 # Status: active 表示防火墙已启用 sudo ufw disable #关闭防火墙 1 添加galaxyship安装用户 为了便于维护及进行权限的隔离，需要用专有的用户进行安装。如果有多个集群节点，需要在每个节点的机器上都创建此安装用户。 用户要求如下： 用户名为：galaxyship 用户在root组内 用户执行sudo 不需要输入密码。 通过以下脚本进行用户的创建及授权： #创建用户 sudo useradd -m galaxyship #添加到root用户组 sudo usermod -aG root galaxyship #进行sudo免密设置 sudo bash -c 'echo -e "galaxyship ALL=(ALL) NOPASSWD: ALL" &gt;&gt; /etc/sudoers' #将新用户默认的shell改为bash 以避免切换用户的时候报错 sudo chsh -s /bin/bash galaxyship 2 修改galaxyship用户的密码 sudo passwd galaxyship 3 进行ssh登陆设置 需要在哪台服务器（比如安装器所安装的服务器）免密连接到其它的服务器，就在哪台服务器（比如安装器所安装的服务器）上执行以下操作： 切换到安装用户 sudo su galaxyship 生成ssh密钥对 执行以下命令，过程中续回车即可： ssh-keygen 将密钥对的公钥拷贝到需要免密登陆的服务器上 可通过以下命令进行公钥的拷贝 ssh-copy-id galaxyship@${集群节点ip} 如果有多个集群节点，需要对每个节点都进行公钥的拷贝。 注意：如果执行脚本的机器也作为一个服务节点，需要执行 ssh-copy-id galaxyship@127.0.0.1 比如命令为： ssh-copy-id galaxyship@172.27.94.2 4 确认关闭swap分区 执行以下命令，以检查是否是swap分区是否关闭。 free 关闭了swap分区： 未关闭swap分区： 如果显示未关闭swap分区，可以用以下命令进行关闭： sudo swapoff -a 安装器下载 安装kubernetes集群需要提前服务器准备，请查看上面安装要求 1. 登陆【安装部署平台】 先在金蝶云账号中心注册企业且完成企业认证 使用该企业用户信息 登录安装部署平台https://galaxyship.kingdee.com 该用户即可查看到企业名称 2. 选择需要部署环境的企业 请在列头上选择需要部署环境的企业在选择企业名称时，分两种场景： 如果是安装客户正式环境（含沙箱环境、正式生产环境），则在选择企业名称时，一定要选择在【企业账号中心】上认证过的企业名称； 如果是安装实施或机构本地测试类环境（非客户的），则在选择企业名称时，该企业名称在【企业账号中心】没有认证要求。 3. 选择要安装的目标环境 选择安装环境后，点击下一步。安装环境根据实际需要进行选择。 4. 下载集群安装器 安装kubernetes集群和Agent的安装器。 如果还没有安装kubernetes，则下载此安装器。 安装器下载后，是一个Linux shell脚本，名称为：installk8s.sh 安装kubernetes集群 如果还没有安装kubernetes，则在该步骤中利用“安装kubernetes集群和Agent的安装器”进行安装，即执行installk8s.sh。 1. 执行安装器 将脚本复制到目标linux服务器上，然后在命令行运行: sudo chmod +x ./installk8s.sh bash ./installk8s.sh 脚本执行的过程中，会下载kubernetes的相关文件，根据网速的情况，所需要的下载的时间有所区别。 脚本执行完成后，控制台会显示星空旗舰产品安装的访问页面地址。复制到浏览器中进行访问即可。 如果需要在多台机器上布署，也是选择在其中一台上执行此安装脚本即可。具体如下： 多节点安装示意图： 单节点安装示意图： 2. 访问安装器页面 执行完installk8s.sh后，会启动一个web网站，通过访问此网站来进行星空旗舰产品安装。 3. 填写安装用户 4. 添加安装节点 根据安装需要，进行添加不同的节点，并进行相关的节点的信息的填写。 填写说明： 节点类型：kubernetes的master节点或worker节点； 名称：节点的名称，系统自动生成，不可修改； SSH连接（host）：填写安装节点所在的机器ip； 集群内网IP。 如果节点只有一个网卡，这里不需要填写。没有特别安装网卡，一般都只有一个网卡。 当节点存在多个网卡时，需要在这里指定要用的网卡IP。在后续安装时，会用上这个IP。需要注意的是，不同节点所填写的IP需要在同一个网段中。 端口：节点上对外提供ssh服务的端口。 注意，暂时不支持多master节点高可用模式。也就是不能添加两个节点类型为master的节点。 5. 保存配置信息 6. 安装kubernetes集群和Agent 注意，安装前请确认是否满足“软件环境要求”章节所提到的要求。 7. 查看安装日志及进度 安装星空旗舰产品 1. 确认agent安装状态 回到部署平台: http://galaxyship.kingdee.com ，查看agent连接状态。如果是已连接，则可以点击下一步： 2. 进行星空旗舰产品配置 重点需要配置每个一组件（当前只支持高级模式，如果需要建议只修改标记处） nginx端口配置 MC配置服务 kd-cosmic-migration(数据初始化服务) 配置默认的企业编码和企业名称以及管理员手机号 kd-cosmic-xk(星空旗舰服务) 根据需要配置fileserver的nfs postgresql数据库配置 勾选pg库对应的节点 根据需要，配置数据库对外访问的服务端口。默认不对外提供数据库访问服务。 redis配置 3. 进行配置的保存 4. 进行产品的安装 5. 安装资源、事件、日志查看 如果安装过程中有失败，需要查询相关的事件、日志的信息，以确定失败的原因。 6. mc和erp的访问 直接通过ip的访问 星空旗舰产品 网址为：http:// $ {ip}:31101/ 账号为：administrator/Kdxk#admin001 MC 网址为：http:// ${ip}:31102/ 账号为：admin/Kdadmin001 其中，${ip}为安装的服务器节点中的一个节点的ip。]]></summary></entry><entry><title type="html">Ubuntu22.04 搭建Kubernetes 1.28版本集群</title><link href="https://altair288.github.io/Blog/Kubernetes/" rel="alternate" type="text/html" title="Ubuntu22.04 搭建Kubernetes 1.28版本集群" /><published>2024-12-06T00:00:00+00:00</published><updated>2024-12-06T00:00:00+00:00</updated><id>https://altair288.github.io/Blog/Kubernetes</id><content type="html" xml:base="https://altair288.github.io/Blog/Kubernetes/"><![CDATA[<h1 id="依赖安装">依赖安装</h1>

<p>准备工作需要在所有节点上进行。</p>

<ul>
  <li>
    <p>安装 ssh 服务</p>

    <p>1.安装 <code class="language-plaintext highlighter-rouge">openssh-server</code></p>
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nb">sudo </span>apt-get <span class="nb">install </span>openssh-server
</code></pre></div>    </div>

    <p>2.修改配置文件</p>
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  vim /etc/ssh/sshd_config
</code></pre></div>    </div>

    <p>找到配置项</p>
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  LoginGraceTime 120PermitRootLogin prohibit-passwordStrictModes <span class="nb">yes</span>
</code></pre></div>    </div>

    <p>把 <code class="language-plaintext highlighter-rouge">prohibit-password</code> 改为 <code class="language-plaintext highlighter-rouge">yes</code>，如下：</p>
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  LoginGraceTime 120PermitRootLogin yesStrictModes <span class="nb">yes</span>
</code></pre></div>    </div>
  </li>
  <li>重启机器，并设置 root 密码
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  rebootsudo passwd root
</code></pre></div>    </div>

    <p>设置主机名,保证每个节点名称都不相同</p>
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  hostnamectl set-hostname xxx
</code></pre></div>    </div>
  </li>
  <li>
    <p>同步节点时间</p>

    <p>1.配置时间与时区</p>
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  timedatectl set-timezone Asia/Shanghai
  timedatectl set-ntp no
  apt <span class="nb">install </span>ntp <span class="nt">-y</span>
  systemctl <span class="nb">enable </span>ntp
</code></pre></div>    </div>

    <p>2.安装 ntpdate</p>
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nb">sudo </span>apt-get <span class="nt">-y</span> <span class="nb">install </span>ntpdate
</code></pre></div>    </div>

    <p>3.配置 crontab，添加定时任务</p>
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  crontab <span class="nt">-e</span>

  0 <span class="k">*</span>/1 <span class="k">*</span> <span class="k">*</span> <span class="k">*</span> ntpdate time1.aliyun.com
</code></pre></div>    </div>
  </li>
  <li>关闭 Swap
  关闭 Linux 的 swap 分区，提升 Kubernetes 的性能。
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="c"># 确认 swap 是否启用</span>
  <span class="nb">sudo </span>swapon <span class="nt">--show</span>

  <span class="c"># 暂时关闭 swap</span>
  <span class="nb">sudo </span>swapoff <span class="nt">-a</span>

  <span class="c"># 永久关闭 swap</span>
  <span class="nb">sed</span> <span class="nt">-i</span> <span class="s1">'/swap/d'</span> /etc/fstab
</code></pre></div>    </div>
  </li>
</ul>

<blockquote>
  <p>为什么要关闭 swap 交换分区？
Swap 交换分区，如果机器内存不够，会使用 swap 分区，但是 swap 分区的性能较低，k8s 设计的时候为了能提升性能，默认是不允许使用交换分区的。Kubeadm 初始化的时候会检测 swap 是否关闭，如果没关闭，那就初始化失败。如果不想要关闭交换分区，安装k8s 的时候可以指定 –ignore-preflight-errors=Swap 来解决。</p>
</blockquote>

<h1 id="开始搭建">开始搭建</h1>

<h2 id="集群规划">集群规划</h2>

<p>每台上都安装 <strong>docker-ce</strong>、<strong>docker-ce-cli</strong>、<strong>containerd.io</strong>，使用 <strong>Containerd</strong> 作为容器运行时，和 <strong>kubelet</strong> 交互。</p>

<p>所有节点都安装 <strong>kubelet、kubeadm、kubectl</strong> 软件包，都启动 <strong>kubelet</strong>.<strong>service</strong> 服务。</p>

<h2 id="配置-docker-和-k8s-的-apt源">配置 docker 和 k8s 的 APT源</h2>

<p>k8s APT源新版配置方法</p>

<p>（比如需要安装 1.29 版本，则需要将如下配置中的 v1.28 替换成 v1.29）</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get update <span class="o">&amp;&amp;</span> apt-get <span class="nb">install</span> <span class="nt">-y</span> apt-transport-https
curl <span class="nt">-fsSL</span> https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.28/deb/Release.key |
    gpg <span class="nt">--dearmor</span> <span class="nt">-o</span> /etc/apt/keyrings/kubernetes-apt-keyring.gpg
<span class="nb">echo</span> <span class="s2">"deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.28/deb/ /"</span> |
    <span class="nb">tee</span> /etc/apt/sources.list.d/kubernetes.list
apt-get update
</code></pre></div></div>

<p>旧版 kubernetes 源只更新到 1.28 部分版本，不过 docker 源部分可以用</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>curl <span class="nt">-fsSL</span> https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | <span class="nb">sudo </span>apt-key add -
<span class="nb">sudo </span>add-apt-repository <span class="s2">"deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu </span><span class="si">$(</span>lsb_release <span class="nt">-cs</span><span class="si">)</span><span class="s2"> stable"</span>

<span class="nb">sudo </span>curl <span class="nt">-fsSL</span> https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | <span class="nb">sudo </span>apt-key add -
<span class="nb">sudo </span>add-apt-repository <span class="s2">"deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main"</span>

apt update
</code></pre></div></div>

<p>在 Ubuntu 系统中，可以通过以下两个位置查看源列表：</p>

<p><code class="language-plaintext highlighter-rouge">/etc/apt/sources.list</code> 文件：这是主要的源列表文件。您可以使用文本编辑器（如 <code class="language-plaintext highlighter-rouge">vi</code> 或 <code class="language-plaintext highlighter-rouge">nano</code>）以管理员权限打开该文件，查看其中列出的软件源。</p>

<p><code class="language-plaintext highlighter-rouge">/etc/apt/sources.list.d/</code> 目录：该目录包含额外的源列表文件。这些文件通常以 <code class="language-plaintext highlighter-rouge">.list</code> 扩展名结尾，并包含单独的软件源配置。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-cache madison kubelet <span class="c"># 命令来列出可用的 kubelet 软件包版本。检查是否存在版本号为 '1.28.8-00' 的软件包。</span>

/etc/apt/sources.list <span class="c"># apt软件系统源</span>
</code></pre></div></div>

<h2 id="安装dockercontainerd相关">安装docker、containerd相关</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt <span class="nb">install </span>docker-ce docker-ce-cli containerd.io
</code></pre></div></div>

<p>使用 <code class="language-plaintext highlighter-rouge">apt</code> 可以查看安装的 <code class="language-plaintext highlighter-rouge">docker</code> 三个软件及关联软件。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt list <span class="nt">--installed</span> | <span class="nb">grep</span> <span class="nt">-i</span> <span class="nt">-E</span> <span class="s1">'docker|containerd'</span>
</code></pre></div></div>

<h2 id="启动-docker-相关服务">启动 docker 相关服务</h2>

<p>ubuntu上的 dub 安装后，如果有服务，会被自动设置为开机自启动，且装完就会拉起，这里给出验证。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl list-unit-files | <span class="nb">grep</span> <span class="nt">-E</span> <span class="s1">'docker|containerd'</span>
<span class="c">###三个服务都应是running状态</span>

systemctl status containerd.service
systemctl status docker.service
systemctl status docker.socket
</code></pre></div></div>

<h2 id="配置containerd">配置containerd</h2>

<p>Cgroup 管理器，k8s默认是 systemd，需要将 Containerd 的 Cgroup 管理器也修改为 systemd（默认是 cgroupfs）。</p>

<p>配置 Containerd ，如果 /etc/containerd 目录不存在，就先创建它：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> /etc/containerd
</code></pre></div></div>

<p>生成默认配置：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>containerd config default <span class="o">&gt;</span> /etc/containerd/config.toml
</code></pre></div></div>

<p>配置 containerd 改使用 systemd</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sed</span> <span class="nt">-i</span> <span class="s1">'s/SystemdCgroup = false/SystemdCgroup = true/'</span> /etc/containerd/config.toml
</code></pre></div></div>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vim /etc/containerd/config.toml
约125行，[plugins.<span class="s2">"io.containerd.grpc.v1.cri"</span>.containerd.runtimes.runc.options]段落
默认：
SystemdCgroup <span class="o">=</span> <span class="nb">false
</span>改为：
SystemdCgroup <span class="o">=</span> <span class="nb">true

</span>约61行，[plugins.<span class="s2">"io.containerd.grpc.v1.cri"</span><span class="o">]</span>段落
默认：
sandbox_image <span class="o">=</span> <span class="s2">"registry.k8s.io/pause:3.6"</span>
改为：
sandbox_image <span class="o">=</span> <span class="s2">"registry.aliyuncs.com/google_containers/pause:3.9"</span>
</code></pre></div></div>

<p>配置后，重启 containerd 服务，并保证 containerd 状态正确</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl restart containerd.service
systemctl status containerd.service
</code></pre></div></div>

<h1 id="安装kubernetes">安装Kubernetes</h1>

<p>安装 Kubernetes 需要在所有节点上进行。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/apt/sources.list <span class="c"># apt软件系统源</span>
apt-cache madison kubelet <span class="c"># 命令来列出可用的 kubelet 软件包版本。检查是否存在版本号为 '1.28.8-00' 的软件包。</span>

ip route show <span class="c"># 查找以 "default via" 开头的行</span>

<span class="nb">sudo </span>apt-get purge kubelet kubeadm kubectl <span class="c"># 卸妆</span>
apt <span class="nb">install </span>docker-ce docker-ce-cli containerd.io
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
</code></pre></div></div>

<p>在 Ubuntu 系统中，可以通过以下两个位置查看源列表：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/apt/sources.list 文件：这是主要的源列表文件。您可以使用文本编辑器（如 vi 或 nano）以管理员权限打开该文件，查看其中列出的软件源。

/etc/apt/sources.list.d/ 目录：该目录包含额外的源列表文件。这些文件通常以 .list 扩展名结尾，并包含单独的软件源配置。

清空kubectl describe node命令输出的事件日志
kubectl delete events <span class="nt">--all</span> <span class="nt">--field-selector</span> involvedObject.kind<span class="o">=</span>Node,involvedObject.name<span class="o">=</span>&lt;节点名称&gt;
可以省略--all参数，只删除特定类型的事件
</code></pre></div></div>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt <span class="nb">install </span>kubelet kubeadm kubectl
<span class="c">###如果需要指定安装1.28.2这个版本，则可以这样：</span>
apt <span class="nb">install </span><span class="nv">kubelet</span><span class="o">=</span>1.28.2-00 <span class="nv">kubeadm</span><span class="o">=</span>1.28.2-00 <span class="nv">kubectl</span><span class="o">=</span>1.28.2-00
</code></pre></div></div>

<p>确认 kubelet 服务状态
同 docker 一样，kubelet 安装后，服务会自动配置为开机启动，且服务已经启动</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl <span class="nb">enable </span>kubelet.service <span class="c"># 配置kubelet为开机自启动</span>
systemctl status kubelet
</code></pre></div></div>

<p>这里没有启动成功是正常的，因为 kubelet 服务成功启动的先决条件，需要 kubelet 的配置文件，所在目录 /var/lib/kubelet 还没有建立。</p>

<p>可以用下面命令看日志，追踪到该临时问题。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>journalctl <span class="nt">-xeu</span> kubelet
</code></pre></div></div>

<p>kubelet的正常启动，要等到下一步，master 节点做 kubeadm 的初始化后，才会正常。</p>

<h2 id="版本锁定">版本锁定</h2>

<p>锁定这三个软件的版本，避免意外升级导致版本错误。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-mark hold kubeadm kubelet kubectl
</code></pre></div></div>

<h2 id="下载-kubernetes-组件镜像">下载 Kubernetes 组件镜像</h2>

<p>可以通过下面的命令看到 kubeadm 默认配置的 kubernetes 镜像，是外网的镜像</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubeadm config images list
</code></pre></div></div>

<p>使用阿里的 kubernetes 镜像源，下载镜像</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubeadm config images pull <span class="nt">--image-repository</span><span class="o">=</span>registry.aliyuncs.com/google_containers <span class="nt">--kubernetes-version</span><span class="o">=</span>v1.28.2 <span class="nt">--cri-socket</span> /run/containerd/containerd.sock
</code></pre></div></div>

<h1 id="kubernetes初始化">Kubernetes初始化</h1>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubeadm init <span class="se">\</span>
  <span class="nt">--apiserver-advertise-address</span><span class="o">=</span>&lt;自己本机的公网IP&gt; <span class="se">\</span>
  <span class="nt">--image-repository</span> registry.aliyuncs.com/google_containers <span class="se">\</span>
  <span class="nt">--kubernetes-version</span> v1.28.2 <span class="se">\</span>
  <span class="nt">--service-cidr</span><span class="o">=</span>10.96.0.0/12 <span class="se">\</span>
  <span class="nt">--pod-network-cidr</span><span class="o">=</span>10.244.0.0/16 <span class="se">\</span>
  <span class="nt">--ignore-preflight-errors</span><span class="o">=</span>all <span class="se">\</span>
  <span class="nt">--cri-socket</span> /run/containerd/containerd.sock
</code></pre></div></div>

<p>配置环境变量</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>To start using your cluster, you need to run the following as a regular user:

  <span class="nb">mkdir</span> <span class="nt">-p</span> <span class="nv">$HOME</span>/.kube
  <span class="nb">sudo cp</span> <span class="nt">-i</span> /etc/kubernetes/admin.conf <span class="nv">$HOME</span>/.kube/config
  <span class="nb">sudo chown</span> <span class="si">$(</span><span class="nb">id</span> <span class="nt">-u</span><span class="si">)</span>:<span class="si">$(</span><span class="nb">id</span> <span class="nt">-g</span><span class="si">)</span> <span class="nv">$HOME</span>/.kube/config

Alternatively, <span class="k">if </span>you are the root user, you can run:

  <span class="nb">export </span><span class="nv">KUBECONFIG</span><span class="o">=</span>/etc/kubernetes/admin.conf
</code></pre></div></div>

<h1 id="使用-calico-网络插件">使用 Calico 网络插件</h1>

<p>上述 master 节点初始化后，可以使用 kubectl get node 来检查 kubernetes 集群节点状态，当前 master 节点的状态为 NotReady，这是由于缺少网络插件，集群的内部网络还没有正常运作。</p>

<p>可以在 Calico 的网站（https://www.tigera.io/project-calico/）上找到它的安装方式，需要注意 Calico 版本支持适配的 kubernets 版本。</p>

<table>
  <thead>
    <tr>
      <th>Kubernetes 版本</th>
      <th>Calico 版本</th>
      <th>Calico YAML文件</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1.18、1.19、1.20 3.18</td>
      <td>https://projectcalico.docs.tigera.io/archive/v3.18/getting-started/kubernetes/requirements</td>
      <td>https://projectcalico.docs.tigera.io/archive/v3.18/manifests/calico.yaml</td>
    </tr>
    <tr>
      <td>1.19、1.20、1.21 3.19</td>
      <td>https://projectcalico.docs.tigera.io/archive/v3.19/getting-started/kubernetes/requirements</td>
      <td>https://projectcalico.docs.tigera.io/archive/v3.19/manifests/calico.yaml</td>
    </tr>
    <tr>
      <td>1.19、1.20、1.21 3.20</td>
      <td>https://projectcalico.docs.tigera.io/archive/v3.20/getting-started/kubernetes/requirements</td>
      <td>https://projectcalico.docs.tigera.io/archive/v3.20/manifests/calico.yaml</td>
    </tr>
    <tr>
      <td>1.20、1.21、1.22 3.21</td>
      <td>https://projectcalico.docs.tigera.io/archive/v3.21/getting-started/kubernetes/requirements</td>
      <td>https://projectcalico.docs.tigera.io/archive/v3.21/manifests/calico.yaml</td>
    </tr>
    <tr>
      <td>1.21、1.22、1.23 3.22</td>
      <td>https://projectcalico.docs.tigera.io/archive/v3.22/getting-started/kubernetes/requirements</td>
      <td>https://projectcalico.docs.tigera.io/archive/v3.22/manifests/calico.yaml</td>
    </tr>
    <tr>
      <td>1.21、1.22、1.23 3.23</td>
      <td>https://projectcalico.docs.tigera.io/archive/v3.23/getting-started/kubernetes/requirements</td>
      <td>https://projectcalico.docs.tigera.io/archive/v3.23/manifests/calico.yaml</td>
    </tr>
    <tr>
      <td>1.22、1.23、1.24 3.24</td>
      <td>https://projectcalico.docs.tigera.io/archive/v3.24/getting-started/kubernetes/requirements</td>
      <td>https://projectcalico.docs.tigera.io/archive/v3.24/manifests/calico.yaml</td>
    </tr>
  </tbody>
</table>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml <span class="nt">-O</span>
</code></pre></div></div>

<p>Calico 使用的镜像较大，如果安装超时，可以考虑在每个节点上预先使用 docker pull 拉取镜像：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 从calico.yaml文件中，找到需要下载的镜像源</span>
docker pull docker.io/calico/kube-controllers:v3.27.3
docker pull docker.io/calico/node:v3.27.3
docker pull docker.io/calico/pod2daemon-flexvol:v3.27.3
docker pull docker.io/calico/cni:v3.27.3
</code></pre></div></div>

<p>Calico 安装使用 kubectl apply即可：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl apply <span class="nt">-f</span> calico.yaml
</code></pre></div></div>

<h1 id="其他节点加入集群">其他节点加入集群</h1>

<p>查看节点列表，这时还只有主节点</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl get node
</code></pre></div></div>

<p>主节点在初始化结束后，已经创建了临时 token，但该临时 token 只有24小时有效期。</p>

<p>因此这里需要重新在节点创建永久有效的 token</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubeadm token create <span class="nt">--print-join-command</span> 
</code></pre></div></div>

<p>worker节点加入</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubeadm <span class="nb">join</span> &lt;master节点&gt;:6443 <span class="nt">--token</span> oyl72q.dth6p8kwi7fopsd6 <span class="se">\</span>
	<span class="nt">--discovery-token-ca-cert-hash</span> sha256:b31bb54c63a550d287c89ddd0094e27ca680a6c3386a8630a75445de3c4d6e43 <span class="se">\</span>
  <span class="nt">--cri-socket</span> /run/containerd/containerd.sock
</code></pre></div></div>

<p>如果遇到拉取镜像的问题，同样使用以上方式下载到本地即可。</p>

<h2 id="配置-console-节点">配置 Console 节点</h2>

<p>Console 节点的部署工作更加简单，它只需要安装一个 kubectl，然后复制“config”文件就行，你可以直接在 Master 节点上用“scp”远程拷贝，例如：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scp <span class="sb">`</span>which kubectl<span class="sb">`</span> niuben@192.168.56.2:~/
scp ~/.kube/config niuben@192.168.56.2:~/.kube
</code></pre></div></div>

<h2 id="卸载kubernetesdocker相关">卸载Kubernetes、docker相关</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get purge kubelet kubeadm kubectl <span class="c"># 卸载</span>
apt remove docker-ce docker-ce-cli containerd.io
</code></pre></div></div>]]></content><author><name></name></author><category term="Kubernets" /><summary type="html"><![CDATA[依赖安装 准备工作需要在所有节点上进行。 安装 ssh 服务 1.安装 openssh-server sudo apt-get install openssh-server 2.修改配置文件 vim /etc/ssh/sshd_config 找到配置项 LoginGraceTime 120PermitRootLogin prohibit-passwordStrictModes yes 把 prohibit-password 改为 yes，如下： LoginGraceTime 120PermitRootLogin yesStrictModes yes 重启机器，并设置 root 密码 rebootsudo passwd root 设置主机名,保证每个节点名称都不相同 hostnamectl set-hostname xxx 同步节点时间 1.配置时间与时区 timedatectl set-timezone Asia/Shanghai timedatectl set-ntp no apt install ntp -y systemctl enable ntp 2.安装 ntpdate sudo apt-get -y install ntpdate 3.配置 crontab，添加定时任务 crontab -e 0 */1 * * * ntpdate time1.aliyun.com 关闭 Swap 关闭 Linux 的 swap 分区，提升 Kubernetes 的性能。 # 确认 swap 是否启用 sudo swapon --show # 暂时关闭 swap sudo swapoff -a # 永久关闭 swap sed -i '/swap/d' /etc/fstab 为什么要关闭 swap 交换分区？ Swap 交换分区，如果机器内存不够，会使用 swap 分区，但是 swap 分区的性能较低，k8s 设计的时候为了能提升性能，默认是不允许使用交换分区的。Kubeadm 初始化的时候会检测 swap 是否关闭，如果没关闭，那就初始化失败。如果不想要关闭交换分区，安装k8s 的时候可以指定 –ignore-preflight-errors=Swap 来解决。 开始搭建 集群规划 每台上都安装 docker-ce、docker-ce-cli、containerd.io，使用 Containerd 作为容器运行时，和 kubelet 交互。 所有节点都安装 kubelet、kubeadm、kubectl 软件包，都启动 kubelet.service 服务。 配置 docker 和 k8s 的 APT源 k8s APT源新版配置方法 （比如需要安装 1.29 版本，则需要将如下配置中的 v1.28 替换成 v1.29） apt-get update &amp;&amp; apt-get install -y apt-transport-https curl -fsSL https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.28/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.28/deb/ /" | tee /etc/apt/sources.list.d/kubernetes.list apt-get update 旧版 kubernetes 源只更新到 1.28 部分版本，不过 docker 源部分可以用 sudo curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" sudo curl -fsSL https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add - sudo add-apt-repository "deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main" apt update 在 Ubuntu 系统中，可以通过以下两个位置查看源列表： /etc/apt/sources.list 文件：这是主要的源列表文件。您可以使用文本编辑器（如 vi 或 nano）以管理员权限打开该文件，查看其中列出的软件源。 /etc/apt/sources.list.d/ 目录：该目录包含额外的源列表文件。这些文件通常以 .list 扩展名结尾，并包含单独的软件源配置。 apt-cache madison kubelet # 命令来列出可用的 kubelet 软件包版本。检查是否存在版本号为 '1.28.8-00' 的软件包。 /etc/apt/sources.list # apt软件系统源 安装docker、containerd相关 apt install docker-ce docker-ce-cli containerd.io 使用 apt 可以查看安装的 docker 三个软件及关联软件。 apt list --installed | grep -i -E 'docker|containerd' 启动 docker 相关服务 ubuntu上的 dub 安装后，如果有服务，会被自动设置为开机自启动，且装完就会拉起，这里给出验证。 systemctl list-unit-files | grep -E 'docker|containerd' ###三个服务都应是running状态 systemctl status containerd.service systemctl status docker.service systemctl status docker.socket 配置containerd Cgroup 管理器，k8s默认是 systemd，需要将 Containerd 的 Cgroup 管理器也修改为 systemd（默认是 cgroupfs）。 配置 Containerd ，如果 /etc/containerd 目录不存在，就先创建它： mkdir /etc/containerd 生成默认配置： containerd config default &gt; /etc/containerd/config.toml 配置 containerd 改使用 systemd sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml vim /etc/containerd/config.toml 约125行，[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]段落 默认： SystemdCgroup = false 改为： SystemdCgroup = true 约61行，[plugins."io.containerd.grpc.v1.cri"]段落 默认： sandbox_image = "registry.k8s.io/pause:3.6" 改为： sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9" 配置后，重启 containerd 服务，并保证 containerd 状态正确 systemctl restart containerd.service systemctl status containerd.service 安装Kubernetes 安装 Kubernetes 需要在所有节点上进行。 /etc/apt/sources.list # apt软件系统源 apt-cache madison kubelet # 命令来列出可用的 kubelet 软件包版本。检查是否存在版本号为 '1.28.8-00' 的软件包。 ip route show # 查找以 "default via" 开头的行 sudo apt-get purge kubelet kubeadm kubectl # 卸妆 apt install docker-ce docker-ce-cli containerd.io deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main 在 Ubuntu 系统中，可以通过以下两个位置查看源列表： /etc/apt/sources.list 文件：这是主要的源列表文件。您可以使用文本编辑器（如 vi 或 nano）以管理员权限打开该文件，查看其中列出的软件源。 /etc/apt/sources.list.d/ 目录：该目录包含额外的源列表文件。这些文件通常以 .list 扩展名结尾，并包含单独的软件源配置。 清空kubectl describe node命令输出的事件日志 kubectl delete events --all --field-selector involvedObject.kind=Node,involvedObject.name=&lt;节点名称&gt; 可以省略--all参数，只删除特定类型的事件 apt install kubelet kubeadm kubectl ###如果需要指定安装1.28.2这个版本，则可以这样： apt install kubelet=1.28.2-00 kubeadm=1.28.2-00 kubectl=1.28.2-00 确认 kubelet 服务状态 同 docker 一样，kubelet 安装后，服务会自动配置为开机启动，且服务已经启动 systemctl enable kubelet.service # 配置kubelet为开机自启动 systemctl status kubelet 这里没有启动成功是正常的，因为 kubelet 服务成功启动的先决条件，需要 kubelet 的配置文件，所在目录 /var/lib/kubelet 还没有建立。 可以用下面命令看日志，追踪到该临时问题。 journalctl -xeu kubelet kubelet的正常启动，要等到下一步，master 节点做 kubeadm 的初始化后，才会正常。 版本锁定 锁定这三个软件的版本，避免意外升级导致版本错误。 sudo apt-mark hold kubeadm kubelet kubectl 下载 Kubernetes 组件镜像 可以通过下面的命令看到 kubeadm 默认配置的 kubernetes 镜像，是外网的镜像 kubeadm config images list 使用阿里的 kubernetes 镜像源，下载镜像 kubeadm config images pull --image-repository=registry.aliyuncs.com/google_containers --kubernetes-version=v1.28.2 --cri-socket /run/containerd/containerd.sock Kubernetes初始化 kubeadm init \ --apiserver-advertise-address=&lt;自己本机的公网IP&gt; \ --image-repository registry.aliyuncs.com/google_containers \ --kubernetes-version v1.28.2 \ --service-cidr=10.96.0.0/12 \ --pod-network-cidr=10.244.0.0/16 \ --ignore-preflight-errors=all \ --cri-socket /run/containerd/containerd.sock 配置环境变量 To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config Alternatively, if you are the root user, you can run: export KUBECONFIG=/etc/kubernetes/admin.conf 使用 Calico 网络插件 上述 master 节点初始化后，可以使用 kubectl get node 来检查 kubernetes 集群节点状态，当前 master 节点的状态为 NotReady，这是由于缺少网络插件，集群的内部网络还没有正常运作。 可以在 Calico 的网站（https://www.tigera.io/project-calico/）上找到它的安装方式，需要注意 Calico 版本支持适配的 kubernets 版本。 Kubernetes 版本 Calico 版本 Calico YAML文件 1.18、1.19、1.20 3.18 https://projectcalico.docs.tigera.io/archive/v3.18/getting-started/kubernetes/requirements https://projectcalico.docs.tigera.io/archive/v3.18/manifests/calico.yaml 1.19、1.20、1.21 3.19 https://projectcalico.docs.tigera.io/archive/v3.19/getting-started/kubernetes/requirements https://projectcalico.docs.tigera.io/archive/v3.19/manifests/calico.yaml 1.19、1.20、1.21 3.20 https://projectcalico.docs.tigera.io/archive/v3.20/getting-started/kubernetes/requirements https://projectcalico.docs.tigera.io/archive/v3.20/manifests/calico.yaml 1.20、1.21、1.22 3.21 https://projectcalico.docs.tigera.io/archive/v3.21/getting-started/kubernetes/requirements https://projectcalico.docs.tigera.io/archive/v3.21/manifests/calico.yaml 1.21、1.22、1.23 3.22 https://projectcalico.docs.tigera.io/archive/v3.22/getting-started/kubernetes/requirements https://projectcalico.docs.tigera.io/archive/v3.22/manifests/calico.yaml 1.21、1.22、1.23 3.23 https://projectcalico.docs.tigera.io/archive/v3.23/getting-started/kubernetes/requirements https://projectcalico.docs.tigera.io/archive/v3.23/manifests/calico.yaml 1.22、1.23、1.24 3.24 https://projectcalico.docs.tigera.io/archive/v3.24/getting-started/kubernetes/requirements https://projectcalico.docs.tigera.io/archive/v3.24/manifests/calico.yaml curl https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml -O Calico 使用的镜像较大，如果安装超时，可以考虑在每个节点上预先使用 docker pull 拉取镜像： # 从calico.yaml文件中，找到需要下载的镜像源 docker pull docker.io/calico/kube-controllers:v3.27.3 docker pull docker.io/calico/node:v3.27.3 docker pull docker.io/calico/pod2daemon-flexvol:v3.27.3 docker pull docker.io/calico/cni:v3.27.3 Calico 安装使用 kubectl apply即可： kubectl apply -f calico.yaml 其他节点加入集群 查看节点列表，这时还只有主节点 kubectl get node 主节点在初始化结束后，已经创建了临时 token，但该临时 token 只有24小时有效期。 因此这里需要重新在节点创建永久有效的 token kubeadm token create --print-join-command worker节点加入 kubeadm join &lt;master节点&gt;:6443 --token oyl72q.dth6p8kwi7fopsd6 \ --discovery-token-ca-cert-hash sha256:b31bb54c63a550d287c89ddd0094e27ca680a6c3386a8630a75445de3c4d6e43 \ --cri-socket /run/containerd/containerd.sock 如果遇到拉取镜像的问题，同样使用以上方式下载到本地即可。 配置 Console 节点 Console 节点的部署工作更加简单，它只需要安装一个 kubectl，然后复制“config”文件就行，你可以直接在 Master 节点上用“scp”远程拷贝，例如： scp `which kubectl` niuben@192.168.56.2:~/ scp ~/.kube/config niuben@192.168.56.2:~/.kube 卸载Kubernetes、docker相关 sudo apt-get purge kubelet kubeadm kubectl # 卸载 apt remove docker-ce docker-ce-cli containerd.io]]></summary></entry></feed>