没有用任何框架,纯原生 + npm 包,9 个实用工具,一套墨蓝仪表盘 UI。本文完整记录从需求分析到发布的全过程,附带核心代码。文末有体验入口,欢迎扫码试用。
为什么做这个小程序?
事情很简单——我做了 15 年前端开发,日常工作中总有一些小需求反复出现:
- 客户发来一段 JSON,需要快速格式化看看结构对不对
- 要把一个链接转成二维码,发到群里让别人扫
- 前端收到后端的时间戳,要看看对应什么时间
- 吃饭 AA 分账,算每人该出多少钱
这些需求不需要装 App,但每次都要打开某个网页、等加载、甚至有时候网络不好还打不开。
我就想:能不能在微信里一键解决?不用下载、不用注册、用完即走——这正是微信小程序的天然优势。
目前已经上线,包含 9 个工具,分 3 大类:
图像工具:图片压缩、二维码生成
开发工具:时间戳转换、JSON 格式化、Base64 编解码、MD5/SHA 哈希
其他工具:AA 分账、日期计算、单位换算
一、技术选型:为什么坚持用原生?
市面上做小程序的方案很多:Uni-app、Taro、Remax……但我最终选了纯原生微信小程序开发。
原因很实际:
- 工具类小程序没有复杂状态管理——不需要 Vuex/Redux 那套东西
- 每个工具页独立运行——页面之间几乎没有数据共享需求
- 性能优先——原生 Canvas 2D API 做二维码渲染,没有中间层损耗
- 包体积控制——没有框架运行时开销,整个小程序打包后非常轻量
项目结构也很干净:
workjob/ ├── app.js # 最小入口 ├── app.json # 页面注册 + TabBar 配置 ├── app.wxss # CSS 变量设计令牌中心 ├── pages/ │ ├── index/ # 首页(工具箱导航) │ ├── resume/ # 关于我(个人主页) │ ├── compress/ # 图片压缩 │ ├── qrcode/ # 二维码生成 │ ├── timestamp/ # 时间戳转换 │ ├── jsonfmt/ # JSON 格式化 │ ├── base64/ # Base64 编解码 │ ├── hash/ # MD5/SHA 哈希 │ ├── split/ # AA 分账 │ ├── datecalc/ # 日期计算 │ └── unitconvert/ # 单位换算 └── utils/ ├── resume-data.js # 简历数据源(数据驱动) └── weapp-qrcode.js # 二维码生成库一个原则:每个工具一个页面目录,结构统一,维护成本极低。
二、UI 设计:墨蓝仪表盘风格
大多数工具箱小程序都长一个样——白底、圆角卡片、蓝色按钮。我想做点不一样的。
我的日常工作是在能源行业做前端,做的是充电站运营平台、物联网数据大屏。那种墨蓝底色 + 电光橙高亮的仪表盘界面看多了,觉得很酷——为什么工具箱不能是这个风格?
CSS 变量设计令牌
所有颜色集中在app.wxss里,用 CSS 变量管理:
page{--ink:#1B2A4A;/* 墨蓝:标题、导航栏 */--ink-2:#24365E;/* 墨蓝浅档 */--paper:#F4F6F9;/* 冷调纸白:页面底色 */--card:#FFFFFF;/* 内容卡片 */--volt:#FF5A1F;/* 电光橙:唯一强调色 */--volt-2:#FFB100;/* 琥珀:渐变起点 */--mist:#8A94A6;/* 灰蓝:次要文字 */--line:#E3E8EF;/* 分隔线 */--font-num:"DIN Alternate","Bahnschrift",monospace;--font-body:-apple-system,"PingFang SC",sans-serif;}这样做的好处:
- 改一处,全局生效——要调色调只改变量
- 暗色模式好扩展——后面加
@media (prefers-color-scheme: dark)覆盖变量就行 - 团队协作友好——看到
--volt就知道是强调色,不用猜#FF5A1F是什么
字体分工
数字用 DIN Alternate(工业仪表盘感),中文用苹方/思源。在 CSS 里做了强制分工:
.din{font-family:var(--font-num);}需要显示数字的地方套一个.din类,自动切到数字字体。
三、首页设计:数据驱动的导航
首页是用户进来的第一屏。我的想法是:分类清晰、一触即达、支持后续扩展。
数据结构
工具列表不走硬编码,而是用一个数组结构来渲染:
// pages/index/index.jsPage({data:{categories:[{title:'图像工具',tools:[{name:'图片压缩',icon:'/images/tit1_ico1.png'},{name:'二维码生成',icon:'/images/tit1_ico2.png'}]},{title:'开发工具',tools:[{name:'时间戳转换',icon:'/images/tit2_ico4.png'},{name:'JSON 格式化',icon:'/images/tit2_ico5.png'},{name:'Base64 编解码',icon:'/images/tit2_ico6.png'},{name:'MD5/SHA 哈希',icon:'/images/tit2_ico7.png'}]},{title:'其他工具',tools:[{name:'AA分账',icon:'/images/tit2_ico1.png'},{name:'日期计算',icon:'/images/tit2_ico2.png'},{name:'单位换算',icon:'/images/tit2_ico3.png'}]}]}})新增工具只需要在categories数组里加一项,不需要改布局模板。
跳转逻辑
点击工具后,根据name判断跳转到哪个页面:
onToolTap(e){constname=e.currentTarget.dataset.nameconstrouteMap={'图片压缩':'/pages/compress/index','二维码生成':'/pages/qrcode/index','时间戳转换':'/pages/timestamp/index','JSON 格式化':'/pages/jsonfmt/index','Base64 编解码':'/pages/base64/index','MD5/SHA 哈希':'/pages/hash/index','AA分账':'/pages/split/index','日期计算':'/pages/datecalc/index','单位换算':'/pages/unitconvert/index',}if(routeMap[name]){wx.navigateTo({url:routeMap[name]})}else{wx.showToast({title:'敬请期待',icon:'none'})}}用路由映射表替代一长串if-else,新增工具时只要在routeMap里加一行。
💡 实际代码中我最初用的是if-else,后来重构成了映射表。这也是一个很实用的优化——当你发现条件分支超过 5 个,就该考虑用对象映射替代了。*
底部 TabBar
两个 Tab:首页(工具箱)+关于我(个人主页)。
{"tabBar":{"color":"#8A94A6","selectedColor":"#FF5A1F","backgroundColor":"#1B2A4A","list":[{"pagePath":"pages/index/index","text":"首页"},{"pagePath":"pages/resume/resume","text":"关于我"}]}}TabBar 底色用墨蓝,选中色用电光橙,跟整体风格统一。
四、核心工具开发实录
挑几个有代表性的工具,讲讲实现细节和踩坑。
工具 1:二维码生成 —— Canvas 2D 的坑
这是我最满意的一个工具。支持自定义颜色、Logo、一键保存。
技术难点:微信小程序的 Canvas 经历过从Canvas 1.0到Canvas 2.0的大改。旧版wx.createCanvasContext已经废弃,新版要用Canvas 2D原生 API。
我用的weapp-qrcode库,但它的内部实现还是基于旧版 Canvas API。怎么兼容?
方案:拦截绘制指令,用新 API 重绘。
constQR=require('../../utils/weapp-qrcode.js')Page({onGenerate(){constthat=thisconstquery=wx.createSelectorQuery()query.select('#qrCanvas').fields({node:true}).exec((res)=>{if(!res||!res[0]||!res[0].node){wx.showToast({title:'Canvas 未就绪',icon:'none'})return}constcanvas=res[0].nodeconstctx=canvas.getContext('2d')// 第一步:用假 ctx 收集所有绘制指令constcommands=[]letcurrentColor=that.data.backgroundColorconstfakeCtx={setFillStyle(c){currentColor=c},fillRect(x,y,w,h){commands.push({x,y,w,h,color:currentColor})},drawImage(src,dx,dy,dw,dh){commands.push({type:'img',src,dx,dy,dw,dh})},draw(_,cb){if(cb)cb()}}// 调用库生成 QR 矩阵(但不真正绘制)QR({ctx:fakeCtx,width:size,height:size,text:that.data.text.trim(),correctLevel:0,foreground:that.data.foregroundColor,background:that.data.backgroundColor,image:that.data.hasLogo?that.data.logoPath:''})// 第二步:用 Canvas 2D API 真正重绘canvas.width=size canvas.height=size ctx.fillStyle=that.data.backgroundColor ctx.fillRect(0,0,size,size)for(constcmdofcommands){if(cmd.type==='img'){// 处理 Logo 图片constimg=canvas.createImage()img.onload=()=>{ctx.drawImage(img,cmd.dx,cmd.dy,cmd.dw,cmd.dh)that._exportQR(canvas,size)}img.src=cmd.src}else{ctx.fillStyle=cmd.color ctx.fillRect(cmd.x,cmd.y,cmd.w,cmd.h)}}})}})这个指令拦截 + 重绘的方案是核心亮点:
- 不修改第三方库源码
- 兼容新旧 Canvas API
- 可以任意扩展(比如加圆角、渐变、阴影)
配色预设:我做了 6 套配色方案,用户可以一键切换:
constCOLOR_PRESETS=[{fg:'#000000',bg:'#FFFFFF',label:'经典'},{fg:'#1B2A4A',bg:'#F4F6F9',label:'墨蓝'},{fg:'#FF5A1F',bg:'#FFFFFF',label:'电光橙'},{fg:'#000000',bg:'#FFB100',label:'琥珀'},{fg:'#3DDC84',bg:'#000000',label:'赛博'},{fg:'#6366F1',bg:'#FFFFFF',label:'紫罗兰'}]生成的二维码可以直接保存到手机相册,方便转发。
工具 2:JSON 格式化 —— 容错的艺术
开发者最常用到的工具。除了常规的美化和压缩,我加了一个关键特性:JS 对象语法兼容。
痛点:开发中经常遇到这种情况:后端或同事发来的 JSON 是 JS 对象格式——单引号、无引号属性名、尾逗号,直接JSON.parse会报错。
// 这不是合法 JSON,但开发者经常遇到{name:'test',tags:['a','b',],}解决方案:四层降级解析
_format(beautify){consttext=this.data.inputText.trim()letobj=nullletusedFallback=false// 第一层:标准 JSON.parsetry{obj=JSON.parse(text)}catch(e){// 第二层:JS 对象语法转 JSON 后再解析try{constjsonText=this._jsToJson(text)obj=JSON.parse(jsonText)usedFallback=true}catch(e2){// 第三层:尝试用 {} 包裹后解析try{obj=JSON.parse('{'+text+'}')usedFallback=true}catch(e3){// 第四层:{} 包裹 + JS 转换try{obj=JSON.parse(this._jsToJson('{'+text+'}'))usedFallback=true}catch(e4){this.setData({errorMsg:this._formatError(e.message,text)})return}}}}constoutput=beautify?JSON.stringify(obj,null,2):JSON.stringify(obj)// 渲染输出...}四层降级解析策略:标准 JSON → JS 转换 → 自动包裹 → JS 转换 + 包裹能处理绝大多数“不规范 JSON”的情况况。
_jsToJson转换逻辑:
_jsToJson(text){lets=text// 移除注释s=s.replace(/\/\/.*$/gm,'')s=s.replace(/\/\*[\s\S]*?\*\//g,'')// 单引号转双引号s=s.replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g,'"$1"')// 无引号属性名加双引号s=s.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g,'$1"$2":')// 移除尾逗号s=s.replace(/,\s*([}\]])/g,'$1')// 移除 undefined / function(JSON 不支持)s=s.replace(/:\s*undefined\b/g,': null')s=s.replace(/:\s*function\s*\([^)]*\)\s*\{[^}]*\}/g,': null')returns}附加功能:JSON 路径查询
格式化后可以直接查询某个字段的值:
onQuery(){constpath=this.data.queryPath.trim()// 将 categories[0].tools[0].name 转为 categories.0.tools.0.nameconstnormalizedPath=path.replace(/\[(\d+)\]/g,'.$1')constkeys=normalizedPath.split('.')letcurrent=this.data.parsedObjfor(leti=0;i<keys.length;i++){constkey=keys[i]if(Array.isArray(current)){current=current[parseInt(key)]}elseif(typeofcurrent==='object'){current=current[key]}}constresult=typeofcurrent==='object'?JSON.stringify(current,null,2):String(current)this.setData({queryResult:result})}输入data.categories[0].tools[0].name直接拿到图片压缩。
工具 3:AA 分账 —— 最接地气的工具
这个工具使用场景很简单:朋友聚餐,多人分摊费用。但就是这样一个看似不起眼的小工具,却成了整个工具箱里使用频率最高、口碑传播最快的一个。
为什么需要它?
聚餐结账时最尴尬的瞬间,莫过于服务员拿着账单过来,一桌人开始掏出手机各自计算。有人点了贵的菜、有人只喝了饮料、有人提前垫付了整桌费用——怎么算才公平?用计算器一个个加,既慢又容易出错;用 Excel 又太小题大做。AA 分账工具就是为这个场景设计的。
核心逻辑
- 输入总金额、人数 → 自动计算每人金额
- 支持有人先付了部分金额 → 算出谁该补给谁
进阶场景:差额结算
除了最简单的"总金额 ÷ 人数",我还加了一个更实用的场景——有人垫付。比如小王先付了 500 元,最后账单是 450 元,那么小王应该拿回 50 元;如果账单是 600 元,其他人就要补给小王 150 元。工具会自动算出每个人该付多少、该补给谁,避免"谁先付谁吃亏"的尴尬。
交互设计
- 金额输入:支持小数点后两位,自动过滤非法字符
- 人数调整:用步进器加减,最少 2 人、最多 20 人
- 结果展示:每人金额大字居中显示,下方列出"谁该补给谁"的明细
- 一键复制:把结算结果复制成文本,直接发到群里,省去手动打字的麻烦
为什么它是"拉新利器"?
这个工具虽然逻辑简单,但是拉新利器——每次聚餐大家都会用到,用完还会分享给同桌的人。很多人第一次用是朋友转发的链接,用完觉得方便,就顺手收藏了整个工具箱,进而发现里面还有 JSON 格式化、二维码生成这些开发工具。一个接地气的小工具,成了整个工具箱的流量入口。
五、架构设计亮点
1. 数据驱动
简历页面的内容全部抽到utils/resume-data.js:
module.exports={name:'瑞宸老师',title:'前端开发工程师',city:'重庆',// ... 所有简历内容}改内容只改这一个文件,不动页面代码。这也是我在企业级项目中的惯用做法——数据和视图分离。
2. 包体积控制
整个项目只用了一个 npm 依赖(weapp-qrcode),没有引入任何 UI 框架或工具库。原因:
- 工具类小程序不需要复杂组件库
- 每个工具的 UI 结构差异很大,强行抽象反而增加耦合
- 原生 API 已经够用(
wx.setClipboardData、wx.navigateTo等)
3. 安全考虑
- 所有计算都在本地完成——没有网络请求,不上传用户数据
- 二维码生成纯 Canvas 渲染——内容不经过任何服务器
- MD5/SHA 哈希本地计算——敏感信息不会泄露
这对于开发者工具来说是加分项——你不需要担心代码片段被传到某个服务器上。
六、踩坑记录
坑 1:Canvas 2D API 兼容
如前面所说,wx.createCanvasContext已废弃,必须用canvas.node.getContext('2d')。但很多第三方库还在用旧 API。我用的指令拦截方案完美解决了这个问题。
坑 2:rpx 适配
微信小程序用rpx做响应式单位(750rpx = 屏幕宽度)。但在 iPhone 上env(safe-area-inset-bottom)的处理要注意——底部操作栏必须保留安全距离:
.fixed-bar{padding-bottom:env(safe-area-inset-bottom);}坑 3:小程序包大小限制
单个包不能超过 2MB。虽然我的项目很小,但如果后续加更多工具,需要考虑分包加载(subpackages)。目前 9 个工具 + 图片资源还在 1MB 以内,暂时不需要。
七、从 0 到上线的完整流程
1. 需求分析(1 天)
列出最常用的工具,按优先级排序。我选了上面 9 个作为第一版。
2. 设计 UI(2 天)
- 确定墨蓝 + 电光橙的配色方案
- CSS 变量设计令牌
- 首页导航布局
3. 开发工具(5 天)
每个工具 0.5-1 天,从简单到复杂:
时间戳转换(最简单)→ Base64 → JSON 格式化 → 二维码生成 → 图片压缩
4. 测试与优化(2 天)
- 微信开发者工具模拟器测试
- 真机预览(不同型号手机)
- 包体积检查
5. 提交审核(1-3 天)
小程序提交审核,微信团队审核通过后发布。
八、后续规划
计划加入的工具:
- 颜色选择器 / 色值转换(HEX ↔ RGB ↔ HSL)
- 正则表达式测试
- 图片转 Base64
- Cron 表达式解析
计划加的功能:
- 分享功能——每个工具页加一键转发按钮
- 最近使用记录——快速回到上次用的工具
- 搜索工具——工具多了以后按名字搜索
九、总结
做这个小程序的体会:
- 微信小程序做工具类应用天然合适——不用安装、不用注册、即用即走
- 原生开发在简单场景下是最佳选择——不需要框架的过度抽象
- CSS 变量是设计系统的基石——所有颜色走变量,改一处全局生效
- 容错设计决定用户体验——JSON 格式化能处理不规范输入,比严格报错好用 10 倍
全部本地计算,不上传任何数据。