我用微信小程序做了一个工具箱:从 0 到上线的完整记录

0 阅读11分钟

我用微信小程序做了一个工具箱:从 0 到上线的完整记录

没有用任何框架,纯原生 + npm 包,9 个实用工具,一套墨蓝仪表盘 UI。本文完整记录从需求分析到发布的全过程,附带核心代码。文末有体验入口,欢迎扫码试用。


为什么做这个小程序?

事情很简单——我做了 15 年前端开发,日常工作中总有一些小需求反复出现:

  • 客户发来一段 JSON,需要快速格式化看看结构对不对
  • 要把一个链接转成二维码,发到群里让别人扫
  • 前端收到后端的时间戳,要看看对应什么时间
  • 吃饭 AA 分账,算每人该出多少钱

这些需求不需要装 App,但每次都要打开某个网页、等加载、甚至有时候网络不好还打不开。

我就想:能不能在微信里一键解决? 不用下载、不用注册、用完即走——这正是微信小程序的天然优势。

于是就有了这个「瑞宸工具箱」。目前已经上线,包含 9 个工具,分 3 大类:

图像工具: 图片压缩、二维码生成

开发工具: 时间戳转换、JSON 格式化、Base64 编解码、MD5/SHA 哈希

其他工具: AA 分账、日期计算、单位换算


一、技术选型:为什么坚持用原生?

市面上做小程序的方案很多:Uni-app、Taro、Remax……但我最终选了纯原生微信小程序开发

原因很实际:

  1. 工具类小程序没有复杂状态管理——不需要 Vuex/Redux 那套东西
  2. 每个工具页独立运行——页面之间几乎没有数据共享需求
  3. 性能优先——原生 Canvas 2D API 做二维码渲染,没有中间层损耗
  4. 包体积控制——没有框架运行时开销,整个小程序打包后非常轻量

项目结构也很干净:

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.js
Page({
  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) {
  const name = e.currentTarget.dataset.name
  const routeMap = {
    '图片压缩': '/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.0Canvas 2.0 的大改。旧版 wx.createCanvasContext 已经废弃,新版要用 Canvas 2D 原生 API。

我用的 weapp-qrcode 库,但它的内部实现还是基于旧版 Canvas API。怎么兼容?

方案:拦截绘制指令,用新 API 重绘。

const QR = require('../../utils/weapp-qrcode.js')

Page({
  onGenerate() {
    const that = this
    const query = wx.createSelectorQuery()
    query.select('#qrCanvas').fields({ node: true }).exec((res) => {
      if (!res || !res[0] || !res[0].node) {
        wx.showToast({ title: 'Canvas 未就绪', icon: 'none' })
        return
      }

      const canvas = res[0].node
      const ctx = canvas.getContext('2d')

      // 第一步:用假 ctx 收集所有绘制指令
      const commands = []
      let currentColor = that.data.backgroundColor
      const fakeCtx = {
        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 (const cmd of commands) {
        if (cmd.type === 'img') {
          // 处理 Logo 图片
          const img = 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)
        }
      }
    })
  }
})

这个指令拦截 + 重绘的方案是核心亮点:

  1. 不修改第三方库源码
  2. 兼容新旧 Canvas API
  3. 可以任意扩展(比如加圆角、渐变、阴影)

配色预设: 我做了 6 套配色方案,用户可以一键切换:

const COLOR_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) {
  const text = this.data.inputText.trim()
  let obj = null
  let usedFallback = false

  // 第一层:标准 JSON.parse
  try {
    obj = JSON.parse(text)
  } catch (e) {
    // 第二层:JS 对象语法转 JSON 后再解析
    try {
      const jsonText = 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
        }
      }
    }
  }

  const output = beautify ? JSON.stringify(obj, null, 2) : JSON.stringify(obj)
  // 渲染输出...
}

四层降级解析策略: 标准 JSON → JS 转换 → 自动包裹 → JS 转换 + 包裹。能处理绝大多数"不规范 JSON"的情况。

_jsToJson 转换逻辑:

_jsToJson(text) {
  let s = 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')
  return s
}

附加功能:JSON 路径查询

格式化后可以直接查询某个字段的值:

onQuery() {
  const path = this.data.queryPath.trim()
  // 将 categories[0].tools[0].name 转为 categories.0.tools.0.name
  const normalizedPath = path.replace(/\[(\d+)\]/g, '.$1')
  const keys = normalizedPath.split('.')
  let current = this.data.parsedObj

  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    if (Array.isArray(current)) {
      current = current[parseInt(key)]
    } else if (typeof current === 'object') {
      current = current[key]
    }
  }

  const result = typeof current === 'object'
    ? JSON.stringify(current, null, 2)
    : String(current)
  this.setData({ queryResult: result })
}

输入 data.categories[0].tools[0].name 直接拿到 图片压缩


工具 3:AA 分账 —— 最接地气的工具

这个工具使用场景很简单:朋友聚餐,多人分摊费用。

核心逻辑:

  • 输入总金额、人数 → 自动计算每人金额
  • 支持有人先付了部分金额 → 算出谁该补给谁

这个工具虽然逻辑简单,但是拉新利器——每次聚餐大家都会用到,用完还会分享给同桌的人。


五、架构设计亮点

1. 数据驱动

简历页面的内容全部抽到 utils/resume-data.js

module.exports = {
  name: '周老师',
  title: '前端开发工程师',
  city: '重庆',
  // ... 所有简历内容
}

改内容只改这一个文件,不动页面代码。 这也是我在企业级项目中的惯用做法——数据和视图分离。

2. 包体积控制

整个项目只用了一个 npm 依赖(weapp-qrcode),没有引入任何 UI 框架或工具库。原因:

  • 工具类小程序不需要复杂组件库
  • 每个工具的 UI 结构差异很大,强行抽象反而增加耦合
  • 原生 API 已经够用(wx.setClipboardDatawx.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 表达式解析

计划加的功能:

  • 分享功能——每个工具页加一键转发按钮
  • 最近使用记录——快速回到上次用的工具
  • 搜索工具——工具多了以后按名字搜索

九、总结

做这个小程序的体会:

  1. 微信小程序做工具类应用天然合适——不用安装、不用注册、即用即走
  2. 原生开发在简单场景下是最佳选择——不需要框架的过度抽象
  3. CSS 变量是设计系统的基石——所有颜色走变量,改一处全局生效
  4. 容错设计决定用户体验——JSON 格式化能处理不规范输入,比严格报错好用 10 倍

扫码体验

用微信搜索「瑞宸工具箱」或扫描下方二维码,即可使用:

下载.png

包含 9 个实用工具: 二维码生成、JSON 格式化、Base64 编解码、时间戳转换、MD5/SHA 哈希、图片压缩、AA 分账、日期计算、单位换算。

全部本地计算,不上传任何数据。


作者: 周老师,15 年前端开发经验,现任职于新能源行业。接项目开发合作(微信小程序 / Vue 管理后台 / H5 / 数据可视化),欢迎扫码联系。