为了帮助女票“减肥”,我写了个小程序

18 阅读1分钟

一、写在前面

女朋友嘴馋想吃东西,但又总喊着要减肥。作为男票,怎么能袖手旁观?

于是我写了个小程序——《就决定是你了皮卡秋》

表面看,这就是个平平无奇的随机转盘。但!只要我在分享前长按那个“分享”按钮,就能偷偷设置概率。嘿嘿

然后分享给她,她就可以心安理得地转、心安理得地吃。😉

你看,完美解决问题!

二、技术实现

前端代码基本交给 AI 生成,我只负责“改改改”。

1. 模板页

基础逻辑 + 基础列表,略过不表。

2. 转盘页(核心)

转盘使用 Canvas 手绘,关键点如下:

  • 绘制逻辑:扇形从 -90°(顶部)起画,每块从中心到边缘线性渐变,柔和好看。

  • 文字排布:沿扇形中弧线放置,自动适配字号,超长截断。

  • 旋转约束:最少转 4 圈,指针固定顶部,最终通过 (prev + toCenter) % 360 落在指定奖品区间。

  • 概率控制:长按分享按钮生成带权重参数的分享链接,分享页自动解析并应用权重。

绘制代码节选如下(AI 主力,我微调):

_drawWheel(ctx, size) {      const list = this._optionList;      if (!list || list.length < 2) return;      const n = list.length;      const center = size / 2;      const R = size / 2 - 8;      const rInner = size * 0.14;      const degStep = (2 * Math.PI) / n;      // 浅色背景圆(整圆)      ctx.beginPath();      ctx.arc(center, center, R, 0, 2 * Math.PI);      ctx.fillStyle = '#FCF8F5';      ctx.fill();      // 扇形:柔和渐变(从中心到外缘)      for (let i = 0; i < n; i++) {        const startAngle = -Math.PI / 2 + i * degStep;        const endAngle = startAngle + degStep;        const midAngle = (startAngle + endAngle) / 2;        const g = getSectorGradient(i);        const x0 = center + Math.cos(midAngle) * rInner;        const y0 = center + Math.sin(midAngle) * rInner;        const x1 = center + Math.cos(midAngle) * R;        const y1 = center + Math.sin(midAngle) * R;        const gradient = ctx.createLinearGradient(x0, y0, x1, y1);        gradient.addColorStop(0, g.start);        gradient.addColorStop(1, g.end);        ctx.beginPath();        ctx.moveTo(center, center);        ctx.arc(center, center, R, startAngle, endAngle);        ctx.closePath();        ctx.fillStyle = gradient;        ctx.fill();        ctx.strokeStyle = 'rgba(255,255,255,0.5)';        ctx.lineWidth = 1;        ctx.stroke();      }      // 文字(扇形中点外侧,沿弧排布)      const textR = rInner + (R - rInner) * 0.6;      const fontSize = Math.max(12, Math.floor(size / 22));      ctx.font = `${fontSize}px sans-serif`;      ctx.fillStyle = '#333';      ctx.textAlign = 'center';      ctx.textBaseline = 'middle';      for (let i = 0; i < n; i++) {        const midAngle = -Math.PI / 2 + (i + 0.5) * degStep;        const tx = center + Math.cos(midAngle) * textR;        const ty = center + Math.sin(midAngle) * textR;        ctx.save();        ctx.translate(tx, ty);        ctx.rotate(midAngle + Math.PI / 2);        const text = String(list[i]).slice(0, 6);        ctx.fillText(text, 0, 0);        ctx.restore();      }      // 中心浅色圆(与中心按钮视觉衔接)      ctx.beginPath();      ctx.arc(center, center, rInner, 0, 2 * Math.PI);      ctx.fillStyle = '#FFF8F5';      ctx.fill();    },

权重随机函数,核心逻辑就这几行:

 /** 按权重随机一个下标,weights 和为 1;无效时等权 */    _weightedIndex(weights, n) {      if (!Array.isArray(weights) || weights.length !== n) return Math.floor(Math.random() * n);      const sum = weights.reduce((a, b) => a + Number(b), 0);      if (!Number.isFinite(sum) || sum <= 0) return Math.floor(Math.random() * n);      const r = Math.random();      let acc = 0;      for (let i = 0; i < n; i++) {        acc += Number(weights[i]) / sum;        if (r < acc) return i;      }      return n - 1;    },

3. 历史记录

纯前端存储,数据存到 localStorage,做了条数限制,旧数据自动清理。无后端,省成本。

4. 分享功能 —— 最骚的一步

本着能省则省、绝不写后端的原则,我把整个转盘的“概率配置”塞进了分享链接里。

技术限制

  • 纯英文字符 URL 建议 ≤1500 字

  • 中英文混合建议 ≤800 字

而我们转盘才几个奖品?完全装得下!
于是——URL 即数据,分享即配置

女票点开链接,转盘就已经被“动过手脚”了。她转得开心,我看得满足。

三、关于 AI 写代码的一些碎碎念

这项目基本是“AI 生成 + 人工修图”的模式。过程中产生了一些真实想法:

1. 恐惧是真的

看着 AI 刷刷刷生成我可能要写半小时的逻辑,说没有危机感是假的。

2. 但 AI 还离不开人

它经常写出“看起来对、跑起来错”的代码。边界条件漏了、样式歪了、交互不对了——这些还得人来修。

前端开发者的掌控力,依然是产品的底线。

3. 提示词即生产力

同样的需求,不同的人写提示词,产出质量天差地别。
AI 很聪明,但也非常依赖“怎么问”。这事儿和写需求文档一样,是个需要练的技能。

4. 不是“被替代”,是“会者越强”

与其焦虑 AI 会不会取代我,不如学着怎么用好它。
把它当成一个无限耐心的初级工程师,你指哪儿它打哪儿,但方向得你把。

四、诸君共勉

工具越强,越考验使用工具的人。
愿我们都能成为驾驭浪潮的人,而不是被浪潮推着走的人。

🌊 共勉。

五、小程序地址