可信源码风险提示:保存或下载前请阅读源码和权限声明。
const GRID = 8;
const PALETTE = [
{hex:'#FFFFFF', name:'白'}, {hex:'#000000', name:'黑'},
{hex:'#FF0000', name:'红'}, {hex:'#00FF00', name:'绿'},
{hex:'#0000FF', name:'蓝'}, {hex:'#FFFF00', name:'黄'},
{hex:'#FF00FF', name:'品'}, {hex:'#00FFFF', name:'青'},
{hex:'#FFA500', name:'橙'}, {hex:'#800080', name:'紫'},
{hex:'#FFC0CB', name:'粉'}, {hex:'#A52A2A', name:'棕'},
{hex:'#808080', name:'灰'}, {hex:'#FFD700', name:'金'},
{hex:'#C0C0C0', name:'银'}, {hex:'#000080', name:'深蓝'}
];
const SYNC_URL = 'https://ZLhr66cc.html2.link';
const pixels = ui.signal(Array(GRID * GRID).fill(0));
const picked = ui.signal(1);
const currentView = ui.signal('canvas');
const historyList = ui.signal([]);
let autoSaveTimer;
function encodeArt() {
const p = pixels.get();
let h = '';
for (let i = 0; i < p.length; i++) h += p[i].toString(16).toUpperCase();
return `JSLAB;ART;${GRID};${GRID};${h}`;
}
function decodeArt(s) {
if (!s || !s.includes('JSLAB;ART;')) return null;
const p = s.split(';');
if (p.length < 5) return null;
const w = parseInt(p[2], 10), h = parseInt(p[3], 10);
const hex = p[4];
if (w !== GRID || h !== GRID || !hex) return null;
const arr = [];
const len = Math.min(hex.length, GRID * GRID);
for (let i = 0; i < len; i++) arr.push(parseInt(hex[i], 16) || 0);
while (arr.length < GRID * GRID) arr.push(0);
return arr;
}
function paint(idx) {
const p = pixels.get(), c = picked.get();
if (p[idx] === c) return;
const n = [...p]; n[idx] = c;
pixels.set(n);
clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(() => {
script.data.set('autoSave', encodeArt()).catch(() => {});
}, 500);
}
async function saveToHistory() {
const list = await script.data.get('history', []);
const now = Date.now();
const d = new Date(now);
const name = `${d.getMonth()+1}/${d.getDate()} ${d.getHours()}:${d.getMinutes().toString().padStart(2,'0')}`;
list.unshift({ id: String(now), name, data: encodeArt(), time: now });
if (list.length > 20) list.length = 20;
await script.data.set('history', list);
historyList.set(list);
script.toast('已保存到历史');
}
async function loadHistory() {
const list = await script.data.get('history', []);
historyList.set(list);
}
function loadFromHistory(item) {
const d = decodeArt(item.data);
if (d) pixels.set(d);
currentView.set('canvas');
ui.setTitle('像素画板');
ui.refresh();
}
async function deleteHistoryItem(id) {
const list = await script.data.get('history', []);
const filtered = list.filter(x => x.id !== id);
await script.data.set('history', filtered);
historyList.set(filtered);
}
async function clearAll() {
const r = await dialog.confirm({
title: '确认清空',
message: '确定要清空画布吗?',
confirmText: '清空',
cancelText: '取消'
});
if (r.action !== 'confirm') return;
pixels.set(Array(GRID * GRID).fill(0));
script.data.set('autoSave', encodeArt()).catch(() => {});
}
function renderMiniGrid(data) {
const p = decodeArt(data) || Array(GRID * GRID).fill(0);
const rows = [];
for (let y = 0; y < GRID; y++) {
const cells = [];
for (let x = 0; x < GRID; x++) {
const i = y * GRID + x;
cells.push(ui.text(' ', {
width: 10, height: 10,
background: PALETTE[p[i]].hex,
color: PALETTE[p[i]].hex,
lines: 1
}));
}
rows.push(ui.row(cells, { gap: 1 }));
}
return rows;
}
function renderCanvas() {
const p = pixels.get(), cur = picked.get();
const rows = [];
for (let y = 0; y < GRID; y++) {
const cells = [];
for (let x = 0; x < GRID; x++) {
const i = y * GRID + x, ci = p[i];
cells.push(ui.button(' ', () => paint(i), {
id: `px${i}`,
background: PALETTE[ci].hex,
color: PALETTE[ci].hex,
height: 48,
flex: 1
}));
}
rows.push(ui.row(cells, { gap: 2 }));
}
const palettes = [];
for (let r = 0; r < 2; r++) {
const cells = [];
for (let c = 0; c < 8; c++) {
const i = r * 8 + c, col = PALETTE[i], sel = i === cur;
cells.push(ui.button(sel ? '●' : ' ', () => picked.set(i), {
id: `cl${i}`,
background: col.hex,
color: sel ? '#00FF00' : col.hex,
height: 48,
flex: 1
}));
}
palettes.push(ui.row(cells, { gap: 2 }));
}
return [
ui.text('点击方格作画', { align: 'center', size: 16, lines: 1, color: '#aaa' }),
...rows,
ui.spacer(4),
ui.text('颜色', { size: 14, lines: 1 }),
...palettes,
ui.spacer(6),
ui.buttonRow([
{ text: '历史', onPress: () => { loadHistory(); currentView.set('history'); ui.setTitle('历史记录'); ui.refresh(); }, id: 'btnHist' },
{ text: '放大', onPress: () => { currentView.set('zoom'); ui.setTitle('全屏预览'); ui.refresh(); }, id: 'btnZoom' },
{ text: '保存', onPress: () => saveToHistory(), id: 'btnSave' }
], { gap: 4 }),
ui.spacer(4),
ui.buttonRow([
{ text: '同步', onPress: () => { currentView.set('syncArt'); ui.setTitle('同步到手机'); ui.refresh(); }, id: 'btnSyncArt' },
{ text: '网站码', onPress: () => { currentView.set('syncWeb'); ui.setTitle('同步器网站'); ui.refresh(); }, id: 'btnSyncWeb' },
{ text: '清空', onPress: () => clearAll(), id: 'btnClear', tone: 'danger' }
], { gap: 4 })
];
}
function renderHistory() {
const list = historyList.get();
const items = [];
items.push(ui.text(`共 ${list.length} 幅作品`, { align: 'center', size: 14, lines: 1, color: '#aaa' }));
if (!list.length) {
items.push(ui.spacer(16));
items.push(ui.text('暂无历史记录', { align: 'center', size: 16 }));
}
for (const item of list) {
const mini = renderMiniGrid(item.data);
items.push(ui.row([
ui.column(mini, { gap: 1, width: 92 }),
ui.column([
ui.text(item.name, { size: 14, lines: 1 }),
ui.row([
ui.button('加载', () => loadFromHistory(item), { id: `ld${item.id}`, height: 38, flex: 1, tone: 'primary' }),
ui.button('删', () => deleteHistoryItem(item.id), { id: `dl${item.id}`, height: 38, flex: 1, tone: 'danger' })
], { gap: 3 })
], { gap: 2, padding: 2, flex: 1 })
], { gap: 6, align: 'center' }));
items.push(ui.divider({ color: '#333', height: 1 }));
}
items.push(ui.spacer(6));
items.push(ui.button('返回画板', () => { currentView.set('canvas'); ui.setTitle('像素画板'); ui.refresh(); }, { id: 'backCanvas', tone: 'neutral' }));
return items;
}
function renderZoom() {
const p = pixels.get();
const rows = [];
const sz = 36;
for (let y = 0; y < GRID; y++) {
const cells = [];
for (let x = 0; x < GRID; x++) {
const i = y * GRID + x;
cells.push(ui.text(' ', {
width: sz, height: sz,
background: PALETTE[p[i]].hex,
color: PALETTE[p[i]].hex,
lines: 1
}));
}
rows.push(ui.row(cells, { gap: 2, justify: 'center' }));
}
return [
ui.spacer(8),
...rows,
ui.spacer(16),
ui.button('退出全屏', () => { currentView.set('canvas'); ui.setTitle('像素画板'); ui.refresh(); }, { id: 'exitZoom', tone: 'neutral' })
];
}
function renderSyncWeb() {
return [
ui.text('手机扫描打开同步网页', { align: 'center', size: 16, lines: 1 }),
ui.spacer(10),
ui.qrcode(SYNC_URL, { size: 240 }),
ui.spacer(10),
ui.button('返回画板', () => { currentView.set('canvas'); ui.setTitle('像素画板'); ui.refresh(); }, { id: 'backWeb', tone: 'neutral' })
];
}
function renderSyncArt() {
const url = `${SYNC_URL}#${encodeArt()}`;
return [
ui.text('手机扫描直接查看画作', { align: 'center', size: 16, lines: 1 }),
ui.spacer(10),
ui.qrcode(url, { size: 240 }),
ui.spacer(10),
ui.text('扫描后自动显示作品', { align: 'center', size: 13, color: '#aaa', lines: 1 }),
ui.spacer(6),
ui.button('返回画板', () => { currentView.set('canvas'); ui.setTitle('像素画板'); ui.refresh(); }, { id: 'backArt', tone: 'neutral' })
];
}
function mainView() {
switch (currentView.get()) {
case 'canvas': return renderCanvas();
case 'history': return renderHistory();
case 'zoom': return renderZoom();
case 'syncWeb': return renderSyncWeb();
case 'syncArt': return renderSyncArt();
default: return renderCanvas();
}
}
async function main() {
const saved = await script.data.get('autoSave', '');
const d = decodeArt(saved);
if (d) pixels.set(d);
ui.setTitle('像素画板');
ui.render(mainView);
}
return main();