Web 端本地缓存、首页与会员弹窗体验优化

新增 IndexedDB 与最近文件 L1 缓存、云保存/打开链路;首页支持下载与清空本地缓存;修复清空 storage 后语言初始化报错;优化 design2 背景加载与侧栏标签样式;完善会员激活/支付弹层样式;后端补充文件缩略图字段与 thumb 接口。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
24kycj
2026-06-11 11:05:44 +08:00
parent 877fd278c2
commit 4ce4486b26
33 changed files with 3190 additions and 1143 deletions
+217
View File
@@ -0,0 +1,217 @@
(function () {
'use strict';
var RECENT_KEY = 'soondesign_recent';
var MIGRATED_KEY = 'soondesign_recent_migrated';
var HISTORY_KEY = 'soondesign_history';
var MAX_ITEMS = 20;
function nowTimeStr() {
var d = new Date();
function pad(n) { return n < 10 ? '0' + n : String(n); }
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' +
pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
}
function readRaw() {
try {
var raw = localStorage.getItem(RECENT_KEY);
if (!raw) return { version: 1, items: [] };
var j = JSON.parse(raw);
if (!j || !Array.isArray(j.items)) return { version: 1, items: [] };
return j;
} catch (e) {
return { version: 1, items: [] };
}
}
function writeRaw(data) {
try {
localStorage.setItem(RECENT_KEY, JSON.stringify(data));
return true;
} catch (e) {
return false;
}
}
function soonRecentKindFromKey(key) {
if (!key) return 'local';
if (key.indexOf('soondesign_file:') === 0) return 'cloud';
if (key.indexOf('soondesign_template:') === 0) return 'template';
return 'local';
}
function parseFileId(key) {
var m = String(key).match(/^soondesign_file:(\d+)/);
return m ? parseInt(m[1], 10) : null;
}
function soonRecentList() {
return readRaw().items.slice();
}
function soonRecentRemove(key) {
if (!key) return;
var data = readRaw();
data.items = data.items.filter(function (it) { return it.key !== key; });
writeRaw(data);
}
function soonRecentUpsert(item) {
if (!item || !item.key) return;
var key = item.key;
var kind = item.kind || soonRecentKindFromKey(key);
var type = item.type != null ? item.type : 1;
var name = item.name || '';
if (!name && typeof window.soonDisplayFileName === 'function') {
name = window.soonDisplayFileName(key);
}
var entry = {
key: key,
name: name || 'design.soon',
type: type,
kind: kind,
fileId: item.fileId != null ? item.fileId : parseFileId(key),
thumbRef: item.thumbRef || key,
time: item.time || nowTimeStr()
};
var data = readRaw();
data.items = data.items.filter(function (it) { return it.key !== key; });
data.items.unshift(entry);
if (data.items.length > MAX_ITEMS) data.items = data.items.slice(0, MAX_ITEMS);
writeRaw(data);
}
function soonRecentUpsertFromOpen(fileKey, json) {
if (!fileKey) return;
var type = 1;
if (json) {
type = json.soonType ? json.soonType : (json.backBlackPic ? 2 : 1);
}
var name = '';
if (typeof window.soonDisplayFileName === 'function') name = window.soonDisplayFileName(fileKey);
var tplMeta = window._soonTemplateMeta;
if (tplMeta && tplMeta.name && fileKey.indexOf('soondesign_template:') === 0) {
name = tplMeta.name;
if (tplMeta.type) type = tplMeta.type;
}
var fileMeta = window._soonFileMeta;
if (fileMeta && fileMeta.name && fileKey.indexOf('soondesign_file:') === 0) {
name = fileMeta.name;
}
soonRecentUpsert({
key: fileKey,
name: name,
type: type,
kind: soonRecentKindFromKey(fileKey),
fileId: parseFileId(fileKey),
thumbRef: fileKey,
time: nowTimeStr(),
updated_at: (tplMeta && tplMeta.updated_at) ? tplMeta.updated_at : undefined
});
}
function soonRecentMigrateFromHistory() {
try {
if (localStorage.getItem(MIGRATED_KEY) === '1') return;
} catch (e) { return; }
var histRaw;
try {
histRaw = localStorage.getItem(HISTORY_KEY);
} catch (e) { return; }
if (!histRaw) {
try { localStorage.setItem(MIGRATED_KEY, '1'); } catch (e2) { /* ignore */ }
return;
}
var hist;
try {
hist = JSON.parse(histRaw);
} catch (e) {
try { localStorage.setItem(MIGRATED_KEY, '1'); } catch (e2) { /* ignore */ }
return;
}
var list = (hist && Array.isArray(hist.history)) ? hist.history : [];
list.forEach(function (h) {
if (!h || !h.path) return;
soonRecentUpsert({
key: h.path,
name: typeof window.soonDisplayFileName === 'function' ? window.soonDisplayFileName(h.path) : h.path,
type: h.type || 1,
kind: soonRecentKindFromKey(h.path),
fileId: parseFileId(h.path),
thumbRef: h.path,
time: h.time || nowTimeStr()
});
});
try { localStorage.setItem(MIGRATED_KEY, '1'); } catch (e) { /* ignore */ }
}
function soonRecentEnrichFromCloud(items, cloudItems) {
if (!items || !items.length || !cloudItems || !cloudItems.length) return items;
var byId = {};
cloudItems.forEach(function (c) {
if (c && c.id != null) byId[c.id] = c;
});
return items.map(function (it) {
if (!it.fileId || !byId[it.fileId]) return it;
var c = byId[it.fileId];
var copy = Object.assign({}, it);
if (c.name) copy.name = c.name;
var curKey = it.filePath || it.key;
if (c.version != null && typeof window.soonMakeFileKey === 'function') {
var newKey = window.soonMakeFileKey(c.id, c.version);
if (newKey !== curKey) {
soonRecentRemove(curKey);
copy.filePath = newKey;
copy.key = newKey;
copy.thumbRef = newKey;
soonRecentUpsert({
key: newKey,
name: copy.name,
type: copy.type,
kind: soonRecentKindFromKey(newKey),
fileId: c.id,
thumbRef: newKey
});
}
}
return copy;
});
}
function soonRecentOnCloudSave(res, prevKey, type) {
if (!res || !res.fileKey) return;
var dropKeys = {};
if (prevKey && prevKey !== res.fileKey) dropKeys[prevKey] = 1;
if (res.fileId != null) {
readRaw().items.forEach(function (it) {
if (it.fileId === res.fileId && it.key !== res.fileKey) dropKeys[it.key] = 1;
});
}
Object.keys(dropKeys).forEach(function (k) {
soonRecentRemove(k);
if (typeof window.soonLocalRemove === 'function') window.soonLocalRemove(k);
});
var name = res.name || '';
if (!name && typeof window.soonDisplayFileName === 'function') {
name = window.soonDisplayFileName(res.fileKey);
}
soonRecentUpsert({
key: res.fileKey,
name: name || 'design.soon',
type: type != null ? type : 1,
kind: soonRecentKindFromKey(res.fileKey),
fileId: res.fileId,
thumbRef: res.fileKey
});
}
window.soonRecentList = soonRecentList;
window.soonRecentRemove = soonRecentRemove;
window.soonRecentUpsert = soonRecentUpsert;
window.soonRecentUpsertFromOpen = soonRecentUpsertFromOpen;
window.soonRecentMigrateFromHistory = soonRecentMigrateFromHistory;
window.soonRecentKindFromKey = soonRecentKindFromKey;
window.soonRecentEnrichFromCloud = soonRecentEnrichFromCloud;
window.soonRecentOnCloudSave = soonRecentOnCloudSave;
})();