feat:生产初版页面

This commit is contained in:
R524809
2026-03-26 17:33:04 +08:00
parent 3aaf340781
commit dc5e057cb3
63 changed files with 2940 additions and 543 deletions
+232
View File
@@ -0,0 +1,232 @@
/**
* Generates 81×81 RGBA PNG tab bar icons (no npm deps — zlib + raw PNG).
* Run: node tools/generate-tabbar-icons.js
*/
import { createWriteStream, mkdirSync } from "fs";
import { dirname, join } from "path";
import { fileURLToPath } from "url";
import { deflateSync } from "zlib";
const __dirname = dirname(fileURLToPath(import.meta.url));
const W = 81;
const H = 81;
const OUT_DIR = join(__dirname, "../miniprogram/assets/tabBar");
const PNG_SIG = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
/** PNG CRC-32 (polynomial 0xedb88320) */
function crc32(buf) {
let c = 0xffffffff;
for (let i = 0; i < buf.length; i++) {
c ^= buf[i];
for (let k = 0; k < 8; k++) {
c = (c >>> 1) ^ (0xedb88320 & -(c & 1));
}
}
return (c ^ 0xffffffff) >>> 0;
}
function chunk(type, data) {
const t = Buffer.from(type, "ascii");
const len = Buffer.allocUnsafe(4);
len.writeUInt32BE(data.length, 0);
const crc = Buffer.allocUnsafe(4);
crc.writeUInt32BE(crc32(Buffer.concat([t, data])), 0);
return Buffer.concat([len, t, data, crc]);
}
function encodePng(rgba) {
const rowLen = 1 + W * 4;
const raw = Buffer.allocUnsafe(H * rowLen);
let o = 0;
for (let y = 0; y < H; y++) {
raw[o++] = 0; // filter: None
const rowStart = y * W * 4; // top row first (y = 0)
rgba.copy(raw, o, rowStart, rowStart + W * 4);
o += W * 4;
}
const ihdr = Buffer.allocUnsafe(13);
ihdr.writeUInt32BE(W, 0);
ihdr.writeUInt32BE(H, 4);
ihdr[8] = 8; // bit depth
ihdr[9] = 6; // RGBA
ihdr[10] = 0;
ihdr[11] = 0;
ihdr[12] = 0;
const idat = deflateSync(raw, { level: 9 });
return Buffer.concat([PNG_SIG, chunk("IHDR", ihdr), chunk("IDAT", idat), chunk("IEND", Buffer.alloc(0))]);
}
function parseHexRgb(hex) {
const h = hex.replace("#", "");
return {
r: parseInt(h.slice(0, 2), 16),
g: parseInt(h.slice(2, 4), 16),
b: parseInt(h.slice(4, 6), 16),
};
}
function setPixel(rgba, x, y, r, g, b, a) {
if (x < 0 || x >= W || y < 0 || y >= H) return;
const i = (y * W + x) * 4;
rgba[i] = r;
rgba[i + 1] = g;
rgba[i + 2] = b;
rgba[i + 3] = a;
}
function fillTriangle(rgba, x0, y0, x1, y1, x2, y2, r, g, b, a) {
const ymin = Math.max(0, Math.min(y0, y1, y2));
const ymax = Math.min(H - 1, Math.max(y0, y1, y2));
for (let y = ymin; y <= ymax; y++) {
const xs = [];
const edge = (xa, ya, xb, yb) => {
if (ya === yb) {
if (y === ya) {
const lo = Math.min(xa, xb);
const hi = Math.max(xa, xb);
xs.push(lo, hi);
}
return;
}
if (y < Math.min(ya, yb) || y > Math.max(ya, yb)) return;
const t = (y - ya) / (yb - ya);
xs.push(Math.round(xa + t * (xb - xa)));
};
edge(x0, y0, x1, y1);
edge(x1, y1, x2, y2);
edge(x2, y2, x0, y0);
if (xs.length < 2) continue;
const xMin = Math.max(0, Math.min(...xs));
const xMax = Math.min(W - 1, Math.max(...xs));
for (let x = xMin; x <= xMax; x++) setPixel(rgba, x, y, r, g, b, a);
}
}
function fillRect(rgba, x0, y0, x1, y1, r, g, b, a) {
const xa = Math.max(0, Math.min(x0, x1));
const xb = Math.min(W - 1, Math.max(x0, x1));
const ya = Math.max(0, Math.min(y0, y1));
const yb = Math.min(H - 1, Math.max(y0, y1));
for (let y = ya; y <= yb; y++) {
for (let x = xa; x <= xb; x++) setPixel(rgba, x, y, r, g, b, a);
}
}
function fillCircle(rgba, cx, cy, rad, r, g, b, a) {
const r2 = rad * rad;
const x0 = Math.max(0, Math.floor(cx - rad - 1));
const x1 = Math.min(W - 1, Math.ceil(cx + rad + 1));
const y0 = Math.max(0, Math.floor(cy - rad - 1));
const y1 = Math.min(H - 1, Math.ceil(cy + rad + 1));
for (let y = y0; y <= y1; y++) {
for (let x = x0; x <= x1; x++) {
const dx = x - cx;
const dy = y - cy;
if (dx * dx + dy * dy <= r2) setPixel(rgba, x, y, r, g, b, a);
}
}
}
function fillEllipse(rgba, cx, cy, rx, ry, r, g, b, a, yMin = 0, yMax = H - 1) {
const x0 = Math.max(0, Math.floor(cx - rx - 1));
const x1 = Math.min(W - 1, Math.ceil(cx + rx + 1));
const ya = Math.max(0, yMin);
const yb = Math.min(H - 1, yMax);
const rx2 = rx * rx;
const ry2 = ry * ry;
for (let y = ya; y <= yb; y++) {
for (let x = x0; x <= x1; x++) {
const dx = x - cx;
const dy = y - cy;
if (dx * dx * ry2 + dy * dy * rx2 <= rx2 * ry2) setPixel(rgba, x, y, r, g, b, a);
}
}
}
function drawHome(rgba, rgb) {
const { r, g, b } = rgb;
const a = 255;
// Roof: triangle
fillTriangle(rgba, 40, 15, 20, 34, 60, 34, r, g, b, a);
// Body
fillRect(rgba, 26, 34, 54, 56, r, g, b, a);
}
function drawAge(rgba, rgb) {
const { r, g, b } = rgb;
const a = 255;
fillCircle(rgba, 40, 24, 10, r, g, b, a);
fillRect(rgba, 32, 35, 48, 59, r, g, b, a);
}
function drawHeart(rgba, rgb) {
const { r, g, b } = rgb;
const a = 255;
const cx = 40;
const cy = 38;
const s = 19;
for (let py = 0; py < H; py++) {
for (let px = 0; px < W; px++) {
const x = (px - cx) / s;
const y = -(py - cy) / s;
const x2 = x * x;
const y2 = y * y;
const t = x2 + y2 - 1;
if (t * t * t - x2 * y * y * y <= 0) setPixel(rgba, px, py, r, g, b, a);
}
}
}
function drawProfile(rgba, rgb) {
const { r, g, b } = rgb;
const a = 255;
fillCircle(rgba, 40, 25, 10, r, g, b, a);
// Wider shoulder ellipse (lower half reads as arc)
fillEllipse(rgba, 40, 52, 27, 13, r, g, b, a, 38, H - 1);
}
function writePng(path, png) {
mkdirSync(dirname(path), { recursive: true });
return new Promise((resolve, reject) => {
const ws = createWriteStream(path);
ws.on("error", reject);
ws.on("finish", resolve);
ws.end(png);
});
}
const COLOR_NORMAL = parseHexRgb("#999999");
const COLOR_ACTIVE = parseHexRgb("#333333");
const jobs = [
{ name: "icon-home.png", draw: drawHome, color: COLOR_NORMAL },
{ name: "icon-home-active.png", draw: drawHome, color: COLOR_ACTIVE },
{ name: "icon-age.png", draw: drawAge, color: COLOR_NORMAL },
{ name: "icon-age-active.png", draw: drawAge, color: COLOR_ACTIVE },
{ name: "icon-fav.png", draw: drawHeart, color: COLOR_NORMAL },
{ name: "icon-fav-active.png", draw: drawHeart, color: COLOR_ACTIVE },
{ name: "icon-profile.png", draw: drawProfile, color: COLOR_NORMAL },
{ name: "icon-profile-active.png", draw: drawProfile, color: COLOR_ACTIVE },
];
async function main() {
for (const { name, draw, color } of jobs) {
const rgba = Buffer.alloc(W * H * 4, 0);
draw(rgba, color);
const png = encodePng(rgba);
const out = join(OUT_DIR, name);
await writePng(out, png);
console.log("Wrote", out, `(${png.length} bytes)`);
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
});