豆子碎片小程序开发实践:从核心功能到技术实现

前面介绍了如何学习豆子碎片小程序,今天本文将介绍包括核心功能实现、技术方案对比和业务逻辑落地等方面。豆子碎片小程序是一款小程序技术和经验等内容展示类的小程序,主要用于展示 Markdown 文件解析后的内容。

核心功能实现

豆子碎片小程序的核心功能主要包括以下几个方面:

  1. 数据下载与本地缓存:每次小程序启动时,从服务器下载最新的 Markdown 文件,并缓存到本地,以便后续使用。
  2. Markdown 解析与渲染:使用 towxml 组件解析下载的 Markdown 文件,并渲染成 wxml 文件,以便在小程序中展示。
  3. 首页布局与搜索:首页采用上中下布局,上方介绍小程序的用途,中间是搜索框,下方是快捷按钮。当用户在搜索框输入内容或点击快捷按钮时,查询出相关的文章列表。
  4. 文章详情展示:点击文章列表中的文章,跳转到文章详情页,展示文章的详细内容。

技术方案对比

在实现豆子碎片小程序的过程中,我们对多种技术方案进行了对比,最终选择了以下技术方案:

数据下载与本地缓存

  • 方案一:每次启动时都从服务器下载数据:这种方案实现简单,但会增加网络请求次数,影响小程序的启动速度和用户体验。
  • 方案二:使用本地缓存:在小程序启动时,先检查本地缓存是否有最新数据,如果有则直接使用,否则从服务器下载数据并缓存到本地。最终我们选择了这种方案,以提高小程序的启动速度和用户体验。

Markdown 解析与渲染

  • 方案一:自定义解析器:自行开发 Markdown 解析器,解析 Markdown 文件并生成 wxml 文件。这种方案实现复杂,维护成本高。
  • 方案二:使用现有组件:我们对比了 wxParse 组件和 towxml 组件,最终选择使用开源的 towxml 组件进行 Markdown 解析和渲染,它组件成熟,易于集成和使用。最终我们选择了这种方案,以简化开发流程并提高开发效率。

首页布局与搜索

  • 方案一:手动实现布局和搜索功能:自行编写代码实现首页布局和搜索功能,灵活性高,但开发成本较高。
  • 方案二:使用微信小程序提供的组件:使用微信小程序提供的 UI 组件,如<view><input><button>等,实现首页布局和搜索功能,并使用 weui 样式。最终我们选择了这种方案,以简化开发工作并提高开发效率。

业务逻辑落地

下方只展示了代码片段,详情请查看 visit 项目。

数据下载与本地缓存

在小程序的 app.js 文件中,我们实现了数据下载和本地缓存的逻辑:

// 登入
  async login() {
    const that = this;
    const tzms = utils.getTodayZeroMsTime(); // 获取今日零时毫秒时间戳
    // 检查设备信息?
    that.logDevInfo();
    // 检查Git服务是否正常?
    const isAlive = await that.chkServerAlive();
    console.log('url,', that.globalData.url, isAlive);
    // 检查版本更新?
    that.uptVer(tzms);
    // 加载用户信息
    that.onLogin();
  },

  // 小程序每次启动都会调用
  onLaunch: function () {
    const startTime = Date.now();
    this.globalData.startTime = startTime;
    this.login();
    const loginTime = Date.now();
    const launchTime = loginTime - startTime;
    console.log(`app onLaunch: ${launchTime} ms`);
  },

首页布局与搜索

在首页的 index.wxml 文件中,我们实现了上中下布局和搜索框:

<view class="page">
  <view class="page__hd">
    <view class="page__title" bind:tap="bindShowContact"> Coder <text class="clickable">加油</text> <image class="contact-image" src="/images/email.png"></image>
    </view>
    <view class="page__desc">小程序开发全流程指南,碎片化学习也能掌握组件、调试、上线全链路技能。它是学习小程序开发的好工具,值得收藏!</view>
  </view>
  <view class="page__bd page__bd_spacing">
    <!-- 搜索框-->
    <view class="weui-search-bar {{inputShowed ? 'weui-search-bar_focusing' : ''}}" id="searchBar">
      <form class="weui-search-bar__form">
        <view class="weui-search-bar__box">
          <i class="weui-icon-search"></i>
          <input type="text" confirm-type="search" class="weui-search-bar__input" placeholder="请输入您要查找的内容" value="{{inputVal}}" focus="{{inputShowed}}" bindinput="inputTyping" bindconfirm="bindSearch" />
        </view>
        <label class="weui-search-bar__label" bindtap="showInput">
          <i class="weui-icon-search"></i>
          <span class="weui-search-bar__text">搜索</span>
        </label>
      </form>
      <view class="weui-search-bar__cancel-btn" bindtap="hideInput">取消</view>
    </view>

    <!-- 推荐搜索内容 -->
    <view aria-role="listbox" id="searchResult" class="weui-cells searchbar-result" wx:if="{{kwList.length > 0}}">
      <block wx:for="{{kwList}}" wx:key="*this">
        <view role="option" class="weui-cell weui-cell_active weui-cell_access" bindtap="selectKeyword" data-kw="{{item.kw}}">
          <view class="weui-cell__bd weui-cell_primary">
            <view>{{item.kw}}</view>
          </view>
        </view>
      </block>
    </view>

    <!-- 查询快捷按钮,用于快速查找文章-->
    <!-- 第一排快捷按钮-->
    <view class="weui-flex">
      <view class="weui-flex__item">
        <view class="placeholder" id="DevEnv" bindtap="bindBtn" hover-class="placeholder-hover">开发环境</view>
      </view>
      <view class="weui-flex__item">
        <view class="placeholder" id="CompDev" bindtap="bindBtn" hover-class="placeholder-hover">组件开发</view>
      </view>
      <view class="weui-flex__item">
        <view class="placeholder" id="APIInte" bindtap="bindBtn" hover-class="placeholder-hover">接口集成</view>
      </view>
    </view>
    <!-- 第二排快捷按钮-->
    <view class="weui-flex">
      <view class="weui-flex__item">
        <view class="placeholder" id="ProjCase" bindtap="bindBtn" hover-class="placeholder-hover">项目实战</view>
      </view>
      <view class="weui-flex__item">
        <view class="placeholder" id="DebugTest" bindtap="bindBtn" hover-class="placeholder-hover">调试测试</view>
      </view>
      <view class="weui-flex__item">
        <view class="placeholder" id="AuditDeploy" bindtap="bindBtn" hover-class="placeholder-hover">审核上线</view>
      </view>
    </view>
    <!-- 第三排快捷按钮-->
    <view class="weui-flex">
      <view class="weui-flex__item">
        <view class="placeholder" id="OperPromo" bindtap="bindBtn" hover-class="placeholder-hover">运营推广</view>
      </view>
      <view class="weui-flex__item">
        <view class="placeholder" id="MaintOpti" bindtap="bindBtn" hover-class="placeholder-hover">维护优化</view>
      </view>
      <view class="weui-flex__item">
        <view class="placeholder" id="MyProject" bindtap="bindBtn" hover-class="placeholder-hover">我的项目</view>
      </view>
    </view>
    <block wx:if="{{isShowContact}}">
      <!--联系卡片-->
      <view class="view-contact">
        <view class="card">
          <view class="left-content">
            <button class="icon-button" open-type="feedback">
              <image src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEQAAABECAYAAAA4E5OyAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAOESURBVHhe7ZrbSxZBFMD7D8uuUET1YFQPET300t20C2ZlCaIRqElWWGZERZYE2l1Fw8hSs4jKCgyp6KU4zWHO4VsP+nk+nZ2drfnBoHvmzNnZn3vD2SUQmUEUIohCBFGIIAoRRCGCKEQQhQj+HSFfp+mXxVFcyM5GgKUHs2/3BmlCs9A1ALC93uadvQXw7gt1LIziQuTEsmr9ozQhQdv9Qs6OBvtzWx3A2EdKKJ35hexuoo3AaDRnA8vofWFjLKi8FuDlexsrkXwKSZ4ZHQ8pSHDfxhqA5xMU1JM/IUkZ2LacARj9QJ0E56w7BjAwRkEd+RKSlIGXCW+XnwIYEZcI9606DPBkhILzkx8hUgbD8U0nAIbfUpDgvmUVAM9eU7A4+RAylwyG+9dXAwy9oSDBfTVXKFCc8IXMJ4PhvLXmvtEv7hs8XkHYQrQyGM5fbe4bT1/NjOFjWkG4QkqVwfC4FZUA1e2FGnO93AnCFLJQGUxyfIk1whOyWBlIssb1xxTUEZYQ1zIWUCMcIQHIQMIQEogMJHshAclAshXiSwbGlfWzE+LzzOAcBdkI8SkD4TwF/oX4loFwrgK/QrKQgXC+An9CspKB8BgFfoRkKQPhcQrSF5K1DITHKkhXSAgyEB6vID0hochAuIaCdISEJAPhOgrcCznZsfgDcSkD4VoK3Ar58cuumPEEWu5SRwm4loFwPQVuhQyO2zFJKXiAWtKQgXBNBW6FtPfYMU1dAHtaChNp76WEIqQlA+G6CtwKOXLJjuH11H3n7TaunBX732aaMhCurcCdkKnvdh11jWm//1DQcKDV1ikzUm73UTBB2jIQrq/AnRBcGML8yjYKJKi4YPvKDgF0D1HQ4EMGwvtQ4E5Is7lvYH7nIwoIqi7a/uVGCh68LxkI70eBOyH8ndfEJAVm4ehlm4OrajzJtGUgvC8FboRMfrO5W+soIEBJVx8A7E08ebD5kIHw/hS4EXKn3+bW36CAAb/VaDYvZnzmcNt82n7d2NpNiR7gfStwI4QvBfyL13YCbDhemMTKKoD95kmD94y+UYDpnzTIIzwXBW6E4IsX7xQbnhUNNwF6hs3lNEVJGcLzUuBGyGdzD6m9Zl++xj9RMCC8CwmdKEQQhQiiEEEUIohCBFGIIAoROBeS9+ZMyK5zhWJ5byhGQXEh/yFRiCAKEUQhgihEEIUIopAZAPwF2AJS9v7V7qoAAAAASUVORK5CYII=" class="icon"></image>
            </button>
          </view>
          <view class="right-content">
            <button class="contact-button" bindtap="showContactDialog">联系我</button>
            <text class="support-text">点击左侧按钮反馈,或联系我获取邮箱发邮件</text>
          </view>
        </view>
      </view>
    </block>
  </view>
  <!--<view class="page__ft"></view>-->
</view>

在页面的 index.js 文件中,我们实现了搜索功能:

 search(keyword) {
    if (utils.isEmpty(keyword)) {
      wx.showToast({
        title: '内容不能为空',
      })
      return
    }
    const kw = keyword.toLowerCase();
    this.setData({
      kwList: [],
      keyword: kw,
      op: 1,
    })
    this.searchArt(1, kw)
  },

文章详情展示

Markdown 解析与渲染

在页面的 article.js 文件中,我们使用 towxml 组件解析 Markdown 数据并渲染:

// 加载文章资源,现在从Git获取,下载Markdown文件,然后解析文件。
  getArt(category, artId, label) {
    const that = this;
    // 修改此处可以切换Git地址
    let fileUrl = app.globalData.url + category + '/' + artId;
    utils.downloadFile(
      fileUrl,
      20000, // 超时时间20秒
      (tmpfile) => {
        // console.log('Download successful, file saved at:', tmpfile);
        // 可以在这里对下载的文件进行进一步处理
        // 下载成功后,会存储为临时文件,需要使用微信API读取文件内容。
        const fs = wx.getFileSystemManager()
        fs.readFile({
          filePath: tmpfile,
          encoding: 'utf8',
          success(res) {
            // console.log(res.data)
            if (label == 'md') {
              let obj = app.towxml(res.data, 'markdown', {
                theme: 'light',
                events: {
                  tap: (e) => {
                    console.log('tap', e);
                  }
                }
              });
              // 将文件内容赋值给towxml组件,它会自动进行解析渲染。然后将加载动画关闭。
              that.setData({
                article: obj,
                isLoading: false,
              });
            } else if (label == 'html') {
              let obj = app.towxml(res.data, 'html', {
                theme: 'light',
                events: {
                  tap: (e) => {
                    console.log('tap', e);
                  }
                }
              });
              // 将文件内容赋值给towxml组件,它会自动进行解析渲染。然后将加载动画关闭。
              that.setData({
                article: obj,
                isLoading: false,
              });
            }
          },
        })
      },
      (err) => {
        console.error('Download failed:', err.message);
        log.error('file url,', fileUrl, 'error message,', err.message);
        const title = '下载Markdown文件时错误';
        const content = "文件地址:" + fileUrl + ",错误信息:" + JSON.stringify(err.message);
        app.rptErrInfo(title, content);
      },
    );
  },

在页面的 article.wxml 文件中,我们渲染解析后的内容:

<view class="page">
        <view class="page__bd">
                <!--loading 加载动画-->
                <view class="loading" wx:if="{{isLoading}}">
                        <image class="loading__icon" src="../../images/loading.svg"></image>
                </view>
                <!--使用towxml-->
                <towxml nodes="{{article}}" />
                <view class="weui-footer">
                        <view class="weui-footer__text">--- 这是底线了 ---</view>
                </view>
        </view>
</view>

总结

通过以上介绍,我们详细讲解了豆子碎片小程序的核心功能实现、技术方案对比和业务逻辑落地的过程。希望本文能为您在开发微信小程序时提供一些参考和帮助。如果在开发过程中遇到问题,可以查阅微信官方文档或在开发者社区寻求帮助。