豆子碎片的故事

豆子碎片小程序很早就注册了,刚开始做的功能就是浏览 Go Web 框架 Beego 的教程。当时,正在自学 Beego,而 Beego 的网站时常打不开,就想做一个小程序,可以浏览 Beego 内容。当时看到了 towxml 组件,正好可以实现 Markdown 转 Wxml,于是就将 Beego 的 内容文档写成 Markdown 格式,做成了小程序。小程序的项目名叫 visit,就是浏览的意思。 当时小程序还是使用的云环境,云环境免费,并提供 dev 和 pro 两个环境。将 Beego 的 Markdown 文件存在云存储中。使用云函数调用数据。在小程序端使用 towxml 解析 Markdown 数据。当时发布小程序后,还是很受欢迎的。 中间歇了一段时间,没有更新文章,也没有维护小程序,小程序日浏览人数就下降了。云环境收费后,就彻底不管了。 后来,买了一台云服务器,为了练习自己所学的 Golang 语言,就将小程序重新拾起来。新做的项目内容是浏览唐诗,使用 Gin 框架进行后端开发,小程序端的布局和现在的豆子碎片布局差不多,只有一个首页,首页包含标题、一个搜索框、几个快捷按钮。项目很快完成,将小学和初中的唐诗、宋词、古诗经都录入了数据库,维护了很长一段时间。小程序上线后,每天也有几个人访问。在录入高中的唐诗内容时,就无法坚持下去了,高中的唐诗等内容都比较长,页面排版非常不美,所以高中的内容就没有录入。小程序在展示唐诗内容时,刚开始使用 Markdown 展示。后来想添加拼音,就将 Markdown 去掉,手撸 wxml,最后,拼音也添加了,但是费了很大劲,自己不擅长前端,美工。本还计划添加录音,但实在没有素材,真正做起来,费时费力,没有精力和动力就搁置了。 2024 年微信平台要求小程序都要备案。我有三个小程序,本想都放弃了,但后来,在工作中要用到音频格式转换,在小程序端实现操作起来是真的很方便,省时省力,不需要开电脑,也可以在任何时间操作。所以计划留下一两个吧。在备案的时候,注销了一个,剩下了两个,目前还在使用和维护,一个是豆子工具,用来制作工具,平时我也在用,一个是豆子碎片,记录技术经验和代码片段。在备案豆子碎片小程序时,小程序原名称叫找唐诗,提交备案后,微信备案客服人员说唐诗这两字不让用,让换个名称。在换名称的时候,我就在想这个小程序要不要放弃?不放弃做什么内容?已经有一个豆子工具小程序做工具了,没有必要再做一个工具类小程序吧。一直没有想好,等微信备案人员二次打电话时(真的很负责),才确定下来,做笔记类应用,记录日常开发经验和技巧以及代码片段,本想叫代码碎片,为了和豆子工具保持一致,小程序名称就叫豆子碎片,寓意像玩积木一样,学了本小程序内容,你也可以制作一款你想要的小程序。 豆子碎片项目已经开源,项目地址:https://github.com/littletow/visit 豆子碎片目前没有实名认证,如果你喜欢就收藏一下。豆子碎片面对的人群是有技术门槛的,后续看情况再进行实名认证。 豆子碎片的文章也已经在我公众号发布。有兴趣可以查看一下,一个程序员之小程序的成长与蜕变 想要查看豆子碎片,可以扫下面的小程序码:

2026-01-05 · 1 min · Eagle

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

前面介绍了如何学习豆子碎片小程序,今天本文将介绍包括核心功能实现、技术方案对比和业务逻辑落地等方面。豆子碎片小程序是一款小程序技术和经验等内容展示类的小程序,主要用于展示 Markdown 文件解析后的内容。 核心功能实现 豆子碎片小程序的核心功能主要包括以下几个方面: 数据下载与本地缓存:每次小程序启动时,从服务器下载最新的 Markdown 文件,并缓存到本地,以便后续使用。 Markdown 解析与渲染:使用 towxml 组件解析下载的 Markdown 文件,并渲染成 wxml 文件,以便在小程序中展示。 首页布局与搜索:首页采用上中下布局,上方介绍小程序的用途,中间是搜索框,下方是快捷按钮。当用户在搜索框输入内容或点击快捷按钮时,查询出相关的文章列表。 文章详情展示:点击文章列表中的文章,跳转到文章详情页,展示文章的详细内容。 技术方案对比 在实现豆子碎片小程序的过程中,我们对多种技术方案进行了对比,最终选择了以下技术方案: 数据下载与本地缓存 方案一:每次启动时都从服务器下载数据:这种方案实现简单,但会增加网络请求次数,影响小程序的启动速度和用户体验。 方案二:使用本地缓存:在小程序启动时,先检查本地缓存是否有最新数据,如果有则直接使用,否则从服务器下载数据并缓存到本地。最终我们选择了这种方案,以提高小程序的启动速度和用户体验。 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 文件中,我们实现了上中下布局和搜索框: ...

2026-01-05 · 3 min · Eagle

将数组按照时间重新降序排序

在写笔记列表页面时,发现从服务器获取的笔记列表包含最新更新时间 lastts,当我们想将最新更新的条目展示在最上端时,我们需要排序数组,将数组按照时间重新排序,排序规则为最新的时间条目放在最前面。 这样我们就可以在笔记列表中让用户看到最新更新的内容了。 我们需要将其封装为函数,方便调用。 函数代码如下: // 按照更新时间排序的函数 sortNotesByLastTs: function(notes) { return notes.slice().sort((a, b) => { // 将时间字符串转为时间戳比较 const timeA = new Date(a.lastts).getTime(); const timeB = new Date(b.lastts).getTime(); // 降序排列(最新的在前) return timeB - timeA; }); }, 当运行之后,发现时间戳格式和服务器返回格式不一致,我们需要重新调整函数,服务器返回格式 YYYY-MM-DD HH:mm:ss,本地函数使用的时间格式 ISO 8601 格式。 调整后的代码如下: // 按照更新时间排序的函数(适配"YYYY-MM-DD HH:mm:ss"格式) sortNotesByLastTs: function(notes) { return notes.slice().sort((a, b) => { // 解析自定义时间格式 const parseTime = (timeStr) => { if (!timeStr) return 0; // 替换空格为T,使其符合ISO格式 const isoFormat = timeStr.replace(' ', 'T'); return new Date(isoFormat).getTime() || 0; }; const timeA = parseTime(a.lastts); const timeB = parseTime(b.lastts); // 降序排列(最新的在前) return timeB - timeA; }); }, 推荐还是调整服务器端,让返回的时间格式转为 ISO 8601 格式。

2026-01-05 · 1 min · Eagle

解析Protobuf文件并转为JSON格式

我们知道 Protobuf 文件可以减少体积,方便存储和传输,但在获取文件后,我们还需要将其转换为 JSON 格式,以适配应用进行处理数据。 下面的代码是在微信小程序中使用,将 pb 文件转换为 JSON,然后再使用。 // 小程序根目录执行安装(需开启 npm 支持) // npm install protobufjs // 创建 proto 文件 /proto/item.proto /* syntax = "proto3"; message DataItem { string id = 1; string name = 2; string kw = 3; bool lock = 4; string category = 5; string label = 6; int32 grade = 7; } message DataArray { repeated DataItem items = 1; } */ // 在 app.js 中引入 const protobuf = require('protobufjs'); // 页面 JS 文件 Page({ data: { items: [] }, onLoad() { this.loadProtoData(); }, async loadProtoData() { try { // 1. 下载二进制文件 const { tempFilePath } = await this.downloadFile(); // 2. 读取文件内容 const arrayBuffer = await this.readFile(tempFilePath); // 3. 加载 Proto 定义 const root = await protobuf.load('/proto/item.proto'); const DataArray = root.lookupType('DataArray'); // 4. 解析数据 const decoded = DataArray.decode(new Uint8Array(arrayBuffer)); // 5. 转换格式 const result = decoded.items.map(item => ({ id: item.id || '', name: item.name || '', kw: item.kw || '', lock: typeof item.lock === 'boolean' ? item.lock : false, category: item.category || '', label: item.label || '', grade: item.grade || 1 })); this.setData({ items: result }); } catch (err) { console.error('解析失败:', err); } }, downloadFile() { return new Promise((resolve, reject) => { wx.downloadFile({ url: 'https://your-domain.com/data.bin', success: resolve, fail: reject }); }); }, readFile(tempFilePath) { return new Promise((resolve, reject) => { wx.getFileSystemManager().readFile({ filePath: tempFilePath, encoding: 'binary', success: res => resolve(res.data), fail: reject }); }); } }); 当文件非常大时,解析速度会有点慢啊,为了更进一步的提高速度,我们可以使用 WebAssembly,这里是一个 Wasm 示例: ...

2026-01-05 · 4 min · Eagle

删除JSON数组对象中的某个字段

我需要将 JSON 字符串中的一个对象属性删除,如果手动删除,又累又无聊。 一个 Python 脚本搞定。 import json def remove_filename_and_save(input_path, output_path): """ 从JSON数组中移除所有对象的filename属性并保存到新文件 Args: input_path: 输入JSON文件路径 output_path: 输出JSON文件路径 """ try: # 读取输入JSON文件 with open(input_path, 'r', encoding='utf-8') as f: data = json.load(f) # 检查是否是数组 if not isinstance(data, list): print("错误:输入JSON不是数组对象") return False # 处理每个对象 processed_data = [] for item in data: if isinstance(item, dict): # 创建新对象,排除filename属性 new_item = {k: v for k, v in item.items() if k != 'filename'} processed_data.append(new_item) else: processed_data.append(item) # 非字典元素保持不变 # 写入输出文件 with open(output_path, 'w', encoding='utf-8') as f: json.dump(processed_data, f, ensure_ascii=False, indent=2) print(f"成功处理并保存到 {output_path}") return True except FileNotFoundError: print(f"错误:输入文件 {input_path} 未找到") return False except json.JSONDecodeError: print(f"错误:输入文件 {input_path} 不是有效的JSON格式") return False except Exception as e: print(f"处理文件时出错: {e}") return False if __name__ == "__main__": # 文件路径配置 input_json = "/data/data.json" # 输入文件路径 output_json = "/data/data-temp.json" # 输出文件路径 # 执行处理 if remove_filename_and_save(input_json, output_json): print(f"操作成功完成,结果已保存到 {output_json}") else: print("操作失败,请检查错误信息") 可以基于该脚本进行扩展。

2026-01-05 · 1 min · Eagle

使用towxml组件在小程序中渲染Markdown内容

今天我们要讲到含金量非常高的技术了,将 Markdown 内容渲染为小程序页面,这是豆子碎片文章页面。 在微信小程序中,渲染 Markdown 内容可以使文章展示更加美观和统一。本文将通过一个完整的示例,演示如何从服务器下载 Markdown 内容,并使用 towxml 组件在小程序端渲染为页面。 目录 需求分析 页面布局 下载 Markdown 内容 使用 towxml 渲染 Markdown 代码与预览效果对比 1. 需求分析 在本次实战中,豆子碎片文章页面需要实现以下功能: 从服务器下载 Markdown 内容 使用 towxml 组件将 Markdown 内容渲染为页面 2. 页面布局 豆子碎片文章页面需要包含以下几个部分: 标题区域,用于展示文章标题 内容区域,用于展示渲染后的 Markdown 内容 示例代码 { "pages": ["pages/index/index", "pages/article/article"], "window": { "navigationBarBackgroundColor": "#ffffff", "navigationBarTextStyle": "black", "navigationBarTitleText": "豆子碎片文章" } } <view class="container"> <view class="article-title">{{title}}</view> <import src="/towxml/entry.wxml" /> <template is="entry" data="{{...articleContent}}" /> </view> .container { padding: 20px; } .article-title { font-size: 24px; font-weight: bold; margin-bottom: 20px; } 3. 下载 Markdown 内容 通过微信小程序的wx.request方法,从服务器下载 Markdown 内容。 ...

2026-01-05 · 2 min · Eagle

优化内容类小程序快速审核通过

我们已经知道如何上架,但是针对内容类小程序,我们还需要再特别注意一些内容。 引用真实案例!对于内容展示类的小程序,根据微信小程序的审核规则,个人主体的小程序不能涉及“资讯类”内容(如直接展示文章列表、新闻聚合等),否则会被要求以企业主体重新提交。我是技术类文章展示小程序,即使这样,也被拒多次,以下是针对这一问题的优化方案,既能规避审核风险,又能合理引导用户访问技术文章: 核心原则 内容不应牵涉政治、时事热点、色情暴力等信息,这个无需多说。 首页不直接展示文章内容列表,避免被判定为“资讯类”。这是资讯类默认布局,容易违反。 通过功能入口间接引导用户到二级页面(如搜索、分类、专题入口),在二级页面展示文章列表。 突出技术工具属性,而非内容聚合平台。一般指展示的内容没有相关性,不能突出主题。 内容应偏重于教程、经验性质,而非实时信息,不应具有时效性。比如相关技术最新消息很容易判定为资讯类。 在确定核心原则之后,以下几个推荐布局可以避免判定为资讯类布局。 方案 1:搜索 + 分类入口 + 专题推荐 <view class="container"> <!-- 搜索框(保持顶部) --> <view class="search-box"> <input placeholder="搜索技术文章" bindtap="navigateToSearch" /> </view> <!-- 快捷分类入口(工具按钮) --> <view class="category-buttons"> <view class="button" bindtap="navigateToCategory" data-category="前端">前端技术</view> <view class="button" bindtap="navigateToCategory" data-category="后端">后端架构</view> <view class="button" bindtap="navigateToCategory" data-category="数据库">数据库优化</view> </view> <!-- 专题推荐(以“技术专题”形式包装) --> <view class="special-topics"> <text class="section-title">精选技术专题</text> <view class="topic" bindtap="navigateToTopic" data-id="1"> <image src="/images/topic1.png" mode="aspectFill" /> <text class="topic-title">前端性能优化实战</text> </view> <view class="topic" bindtap="navigateToTopic" data-id="2"> <image src="/images/topic2.png" mode="aspectFill" /> <text class="topic-title">高并发系统设计指南</text> </view> </view> </view> 方案 2:搜索 + 问题解答入口 + 技术资源导航 <view class="container"> <!-- 搜索框 --> <view class="search-box"> <input placeholder="输入技术问题关键词" bindtap="navigateToSearch" /> </view> <!-- 技术问题快速入口(做成问答功能) --> <view class="qa-section"> <text class="section-title">常见技术问题</text> <view class="qa-item" bindtap="navigateToArticle" data-id="101"> <text>如何解决 Vue 3 响应式丢失问题?</text> </view> <view class="qa-item" bindtap="navigateToArticle" data-id="102"> <text>MySQL 索引优化最佳实践</text> </view> </view> <!-- 技术资源导航(强调工具属性) --> <view class="resource-nav"> <view class="nav-card" bindtap="navigateToPage" data-page="opensource"> <image src="/images/opensource.png" /> <text>开源项目推荐</text> </view> <view class="nav-card" bindtap="navigateToPage" data-page="tools"> <image src="/images/tools.png" /> <text>在线开发工具</text> </view> </view> </view> 关键审核规避策略 避免“文章列表”字眼: ...

2026-01-05 · 1 min · Eagle

在列表中搜索符合条件的内容

当我们创建笔记索引内容后,如何搜索是个关键的问题,目前的设计就是先搜索 name,看名称中是否包含关键字,然后查找 kws 数组,查看数组中是否有关键字,最后查看 tags 数组中是否有关键字?查到以后取出来,返回数组列表。 数据的结构如下: [{ "id":"id1", "name":"小星星", "kws":["爱","崇拜"], "tags":["想法","感情"], }] 如果使用 JS 来实现,那么代码如下: /** * 搜索笔记 * @param {Array} notes 笔记数组 * @param {String} keyword 搜索关键词 * @returns {Array} 匹配的笔记数组 */ function searchNotes(notes, keyword) { if (!keyword || typeof keyword !== 'string') { return []; // 如果关键词无效,返回空数组 } // 统一转换为小写以便不区分大小写搜索 const lowerKeyword = keyword.toLowerCase(); return notes.filter(note => { // 检查名称是否包含关键词 const nameMatch = note.name && note.name.toLowerCase().includes(lowerKeyword); // 检查kws数组是否包含关键词 const kwsMatch = Array.isArray(note.kws) && note.kws.some(kw => kw && kw.toLowerCase().includes(lowerKeyword)); // 检查tags数组是否包含关键词 const tagsMatch = Array.isArray(note.tags) && note.tags.some(tag => tag && tag.toLowerCase().includes(lowerKeyword)); // 如果任一条件匹配,则包含该笔记 return nameMatch || kwsMatch || tagsMatch; }); } 这里支持多维度搜索,同时搜索名称、关键词和标签三个字段,当搜索时,先将关键字转为小写,这样比较时不区分大小写。为了性能优化,使用 filter 和 some 方法,减少不必须要的循环。

2026-01-05 · 1 min · Eagle

在小程序文章页面动态添加广告

这是一项含金量非常高的技能,在页面中动态添加广告,可以增加广告曝光量和提高点击率。 小程序使用 Markdown 来编写文章内容,存储在服务器或者后端对象存储,小程序打开时,会从服务器拉取文章索引,然后展示文章列表页面给用户浏览。当用户点击列表上的某篇文章时,小程序下载该篇文章的 Markdown 内容,并在小程序端使用 towxml 组件进行解析渲染,该组件可以将 Markdown 内容渲染成 WXML 文件内容。 文章页面集成了流量主原生模板广告。起初是在页面顶端显示,直接在 article.wxml 页面顶部添加模板广告代码,就可以集成广告了,非常的简单。当前文章页面布局如下:从上到下广告组件、Markdown 文章内容、页面底部说明。但是当实际上线后,发现这样的广告曝光量虽然非常高,但是页面整体看起来非常的不舒服,不美观。然后将广告位置调整到文章底部,因为文章底部和页面底部之间有自定义内容,广告看起来还比较舒服。当前文章页面布局如下:从上到下 Markdown 文章内容、广告组件、页面底部说明。上线之后,发现实际的曝光量降低了很多,因为很多用户是不会浏览到页面底部的。所以广告也不会曝光。得想想办法。在写公众号时,发现公众号插入到文章中间的广告模式不错。我感觉这是一个不错的方案。可以这样试试。 现在就是如何将广告显示在文章中间。文章内容是使用 towxml 渲染的。比如,将广告显示在第一个段落之后。最初的想法,是查找文章页面的段落 p 元素,然后动态插入广告。根据微信官方文档内容,发现可以使用 wx.createSelectorQuery API,但当实际调试后,发现它无法穿透查询 towxml 生成的内容。然后使用 this.createSelectorQuery,这个是查询组件元素使用的,发现这个确实可以查询组件的元素,也确实查找到了 towxml 生成的内容的顶层元素,但是 towxml 生成的内容是多层嵌套,towxml 下面的组件是 decode,decode 然后再进行嵌套自定义组件。也就是说页面中存在着多个#shadow-root,它用来隔离元素和样式。感觉如果这样的布局使用这种方案行不通了。或者即使实现也非常的复杂,还是需要修改 towxml 组件。 当上面通过查询页面元素行不通后,就得想其它办法。虽然这个方案没有解决问题,但是学到了很多东西,以后在同一个#shadow-root 下的元素查找技能就学会了。本来一直不想动 towxml 库,现在的方案只有修改这个库了,来适配自己的需求。towxml 库的原理如下,解析原始的 Markdown 文件或者 Html 文件生成 AST,然后将 AST 转换成小程序 WXML 标签和结构。这里有两种方法:一种是在解析时处理,生成对应的广告组件;另一种是解析成对象后再插入数据。第一种方法实现后,就可以在 HTML 或 Markdown 文件中加入广告标签,然后 towxml 自动解析。虽然这一种功能看起来更强大,但是我的实际场景不是太适合,我的文章来源 Markdown 内容不一定是我编写,我的小程序只是内容展示平台。所以只能选择第二种方法,这个方法更灵活,可以在解析后的 AST 里插入广告数据,然后在 WXML 页面里根据条件进行渲染。这样用户提交的内容,我不进行修改就可以显示广告。想想还是不错的。 接下来就要考虑如何在 AST 插入数据,以及插入到哪个位置?如何查找段落位置?页面如何显示?我可以修改 towxml 库,以适配广告组件渲染,或者在自己的文章页面进行广告组件渲染。无论使用哪种方法,完整的数据流应该是这样的:Markdown 原始文件 -> Towxml 解析 -> 插入广告节点 -> 绑定广告数据 -> 渲染带广告的 WXML。剩下的内容就是想办法解决渲染广告组件这个问题了。 ...

2026-01-05 · 1 min · Eagle

在小程序页面查找元素

为了实现在页面中动态中插入广告,我们需要学会在小程序中查找元素的技能。例如,我要将广告显示在某某元素之后。 在微信小程序开发中,由于逻辑层(JavaScript)与视图层(WXML)分离的特性,开发者无法直接操作 DOM 元素。为了获取页面中某个元素的属性(如位置、尺寸等),小程序提供了 wx.createSelectorQuery API,通过创建节点查询对象来间接操作元素。 wx.createSelectorQuery 用于创建一个节点查询对象(SelectorQuery 实例),开发者可以通过该对象选择页面中的元素,并获取其布局信息(如宽高、位置等)。除了 wx.createSelectorQuery 外,还有一个 this.createSelectorQuery,他们之间的区别是一个用在页面中,一个用在组件组件中。 当我们创建了查询对象后,我们需要选择元素,选择元素有以下几种: select(selector):选择匹配选择器的第一个节点。 selectAll(selector):选择所有匹配的节点。 selectViewport():选择视口(可用于获取滚动容器的信息)。 当元素匹配到之后,我们可以获取元素属性,获取节点属性的方法有: boundingClientRect():获取节点的位置和尺寸。 scrollOffset():获取节点的滚动位置(适用于滚动容器)。 fields({…}):自定义需要获取的字段(如 id,dataset,rect 等)。 最后,一定要记得调用 exec(callback),这样结果才能查询出来。 下面我们举个小示例,可以方便地看到它如何使用?在这个示例中,我们将获取元素的尺寸和位置。场景如下:点击页面按钮,获取页面中某个 view 元素的宽度、高度和位置信息。 1,WXML 文件如下: <view class="target-box" id="target">我是目标元素</view> <button bindtap="getElementInfo">获取元素信息</button> 2,JS 文件如下: Page({ getElementInfo() { // 1. 创建查询实例 const query = wx.createSelectorQuery(); // 2. 选择元素并指定获取的属性 query.select('#target').boundingClientRect(); // 3. 执行查询 query.exec((res) => { // res 是包含查询结果的数组 const rect = res[0]; console.log('元素宽度:', rect.width); console.log('元素高度:', rect.height); console.log('元素位置:', rect.left, rect.top); }); } }); 如果代码没有错误的话,控制台将输出类似如下内容: ...

2026-01-05 · 1 min · Eagle