示例代码
最小可运行骨架:启动幂等、存储兼容模板、锁定处理、导出与选图
示例代码
最小可运行骨架,覆盖本规范的全部必选项。可直接复制改造。
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<title>我的插件</title>
<style>
:root{--bg:#F5F1E8;--card:#FFF;--text:#3E3A33;--text2:#9B9484;--accent:#8B6F4E;--border:#E5DFD3}
@media(prefers-color-scheme:dark){:root{--bg:#191714;--card:#232019;--text:#EDE7DA;--text2:#8F887A;--border:#37322A}}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:system-ui,-apple-system,'PingFang SC',sans-serif;background:var(--bg);color:var(--text);min-height:100vh}
.header{padding:12px 16px;padding-top:max(12px,env(safe-area-inset-top));border-bottom:1px solid var(--border)}
</style>
</head>
<body>
<div class="header">我的插件</div>
<div id="app"></div>
<script src="app.js"></script>
</body>
</html>
app.js
'use strict';
const $ = id => document.getElementById(id);
/* ===== 存储兼容模板(必须完整复制,详见开发指南) ===== */
function unwrapResp(r) {
return (r && typeof r === 'object' && !Array.isArray(r) && ('value' in r)) ? r.value : r;
}
function parseStored(raw) {
if (raw === null || raw === undefined || raw === '') return null;
if (typeof raw === 'object') {
if (raw && !Array.isArray(raw) && ('value' in raw)) raw = raw.value;
if (raw === null || raw === undefined || raw === '') return null;
if (typeof raw !== 'object') return parseStored(raw);
return raw;
}
try { return JSON.parse(raw); } catch (e) { return null; }
}
/* ===== 启动(幂等:whenToolbox 与 toolboxReady 双触发安全) ===== */
function whenToolbox(timeoutMs) {
return new Promise(function (resolve) {
if (window.toolbox) return resolve(window.toolbox);
var done = false;
function ok() { if (done) return; done = true; resolve(window.toolbox || null); }
try { window.addEventListener('yzhToolboxReady', ok, { once: true }); } catch (e) {}
var waited = 0;
var timer = setInterval(function () {
waited += 100;
if (window.toolbox || waited >= (timeoutMs || 3000)) { clearInterval(timer); ok(); }
}, 100);
});
}
function loadAll() {
if (!window.toolbox || !window.toolbox.storage) {
document.getElementById('app').textContent = '请在宇宙核 App 内使用本插件';
return;
}
loadAllAsync();
}
async function loadAllAsync() {
try {
const data = parseStored(unwrapResp(await window.toolbox.storage.get('my_data')));
render(Array.isArray(data) ? data : []);
} catch (e) {
// 任何启动异常都必须转成用户可见的错误,不允许静默卡死
renderError(e);
}
}
function showLocked(msg) { document.getElementById('app').textContent = msg; }
/* ===== 渲染与保存 ===== */
function render(items) {
document.getElementById('app').textContent = items.length ? items.join('、') : '暂无内容';
}
async function saveData(items) {
const res = await window.toolbox.storage.set('my_data', JSON.stringify(items));
if (res && res.error) { renderError(res); return false; } // 错误必须可见
return true;
}
function renderError(res) {
const app = document.getElementById('app');
app.textContent = res && res.locked
? '数据已加密,请解锁安全中心后重试'
: '存储失败:' + (res && res.error || '未知错误');
}
/* ===== 事件绑定只做一次;启动双触发 ===== */
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('addBtn')?.addEventListener('click', async () => {
const items = parseStored(unwrapResp(await window.toolbox.storage.get('my_data'))) || [];
items.push('条目 ' + new Date().toLocaleTimeString());
if (await saveData(items)) render(items);
});
});
whenToolbox(3000).then(function () { loadAll(); });
window.addEventListener('toolboxReady', loadAll); // 存储水合完成后再读一次(幂等)
选图(image.pick,App 内唯一有效方式)
function pickImage(cb) { // cb(dataUrl)
if (window.__yzhPreview) { // 浏览器预览:降级 input[type=file]
const inp = document.createElement('input');
inp.type = 'file'; inp.accept = 'image/*';
inp.onchange = () => { const f = inp.files[0]; if (f) readFile(f, cb); };
inp.click();
return;
}
window.toolbox.image.pick('gallery').then(r => {
if (!r || !r.data) return; // 用户取消
cb('data:' + r.mimeType + ';base64,' + r.data);
}).catch(e => alert('选图失败:' + (e.message || e)));
}
function readFile(file, cb) {
const reader = new FileReader();
reader.onload = () => cb(reader.result);
reader.readAsDataURL(file);
}
导出(exportFile:写盘 + 自动弹系统分享)
async function doExport() {
const res = await window.toolbox.file.exportFile(
'export_' + Date.now() + '.txt',
btoa(unescape(encodeURIComponent('导出内容')))); // UTF-8 文本转 base64
if (res && res.error) { alert('导出失败:' + res.error); return; }
alert('已导出,存储位置:' + (res && res.path || ''));
}
锁定处理(密码保护开启 + 安全中心锁定时)
storage 调用会返回 {error:'locked', locked:true}——显示锁定页 + 重试按钮,
重试 = 重新执行 loadAllAsync(解锁后即恢复):
function renderError(res) {
if (res && res.locked) {
document.getElementById('app').innerHTML =
'<p>数据已加密,请先解锁安全中心。</p>' +
'<button onclick="loadAllAsync()">重试</button>';
return;
}
/* ...其他错误... */
}