function get_filename(filePath) { if (!filePath) return ""; return filePath.split(/[/\\]/).pop(); } function escapeAttr(s) { if (s == null) return ""; return String(s).replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); } var SOON_EMPTY_IMG = '../assets/images/web/empty-state.png'; function soonEmptyBlock(title, hint, extraHtml) { var html = '
' + '' + '

' + escapeAttr(title) + '

'; if (hint) { html += '

' + escapeAttr(hint) + '

'; } if (extraHtml) html += extraHtml; html += '
'; return html; } let s_lan = "zh"; function language_str(str) { let t = { "noFile": { zh: "当前文件不存在!", ozh: "This file does not exist!", en: "File not found!" }, "saveTime": { zh: "保存时间:", ozh: "保存時間:", en: "Saved on:" }, "selectFile": { zh: "请选择文件", ozh: "請選擇文件", en: "Please select a file" }, "comfirm": { zh: "确认", ozh: "確認", en: "Confirm" }, "cancel": { zh: "取消", ozh: "取消", en: "Cancel" }, "SDFile": { zh: "SoonDesign模板文件", ozh: "SoonDesign模闆文件", en: "SoonDesign Template File" }, "lost": { zh: "(文件已丢失)", ozh: "(文件已丟失)", en: "(File Lost)" }, "clickToDelete": { zh: "文件已丢失,点击删除此记录", ozh: "文件已丟失,點擊刪除此記錄", en: "File lost, click to remove" }, "deleted": { zh: "已删除", ozh: "已刪除", en: "Deleted" }, "whetherSave": { zh: "是否保存当前文件?", ozh: "是否保存當前文件?", en: "Save current file?" }, "delTitle": { zh: "警告", ozh: "警告", en: "Warning" }, "delContent": { zh: "文件已丢失,确认是否删除?", ozh: "文件已丟失,確認是否刪除?", en: "File lost, confirm delete?" }, "deleteFileConfirm": { zh: "确认删除该文件?删除后无法恢复。", ozh: "確認刪除該文件?刪除後無法恢復。", en: "Delete this file? This cannot be undone." }, "deleteBtn": { zh: "删除", ozh: "刪除", en: "Delete" } }; return t[str][s_lan] || t[str]['zh']; } function openDesign(type, filePath) { if (window.platformBridge && window.platformBridge.openDesignPage) { window.platformBridge.openDesignPage(filePath || '', type); } } layui.use(['layer', 'form', 'jquery'], function () { var $ = layui.$; var layer = layui.layer; layer.config({ skin: 'soon-layer' }); if (window.sysAPI && window.sysAPI.getAppVersion) { window.sysAPI.getAppVersion().then(function (ver) { $("#app-version").text("v" + ver); }).catch(function () {}); } function langua_ge(lan) { lan = lan || 'zh'; $("[language='m']").each(function () { $(this).html($(this).attr(lan)); }); $("[language='t']").each(function () { $(this).attr("title", $(this).attr(lan)); }); } var lang = localStorage.getItem("lang"); if (lang) { s_lan = lang; langua_ge(s_lan); $("#language_select").val(s_lan); } else if (window.platformBridge && window.platformBridge.getLocale) { window.platformBridge.getLocale().then(function (loc) { if (loc && loc.indexOf('zh') === 0) { s_lan = loc.indexOf('TW') >= 0 ? 'ozh' : 'zh'; } else { s_lan = 'en'; } langua_ge(s_lan); $("#language_select").val(s_lan); localStorage.setItem("lang", s_lan); }); } $('#language_select').on('change', function () { langua_ge($(this).find('option:selected').val()); s_lan = $(this).find('option:selected').val(); localStorage.setItem("lang", s_lan); loadHistory(); }); function hasCloudAuth() { if (typeof window.soonGetAccessToken === 'function') return !!window.soonGetAccessToken(); try { return !!localStorage.getItem('soon_access'); } catch (e) { return false; } } function makeFileKey(id, version) { return typeof window.soonMakeFileKey === 'function' ? window.soonMakeFileKey(id, version) : ('soondesign_file:' + id + ':v' + version); } var fileListState = { page: 1, size: 12, total: 0, items: [] }; var templateListState = { page: 1, size: 8, total: 0, items: [] }; function displayNameFromPath(path) { if (!path) return 'design.soon'; var sessionPrefix = 'soondesign_session:'; if (path.indexOf(sessionPrefix) === 0) return path.substring(sessionPrefix.length); if (path.indexOf('soondesign_file:') === 0) { var meta = window._soonFileMeta; if (meta && meta.name) return meta.name; return path.replace(/^soondesign_file:(\d+).*/, '文件 #$1'); } return get_filename(path) || 'design.soon'; } function parseItemTime(value) { if (!value) return 0; var d = new Date(String(value).replace(' ', 'T')); return isNaN(d.getTime()) ? 0 : d.getTime(); } function mergeRecentItems(localHistory, cloudItems) { var items = []; var cloudById = {}; var seenCloudIds = {}; (cloudItems || []).forEach(function (c) { if (c && c.id != null) cloudById[c.id] = c; }); (localHistory || []).forEach(function (h) { if (!h || !h.path) return; var cloudMatch = String(h.path).match(/^soondesign_file:(\d+)/); if (cloudMatch) { var id = parseInt(cloudMatch[1], 10); var cloud = cloudById[id]; if (cloud) { seenCloudIds[id] = true; items.push({ kind: 'cloud', filePath: makeFileKey(cloud.id, cloud.version), fileId: cloud.id, name: cloud.name || ('文件 #' + cloud.id), type: h.type, sortTime: parseItemTime(cloud.updated_at || cloud.created_at || h.time), }); return; } } items.push({ kind: 'local', filePath: h.path, name: displayNameFromPath(h.path), type: h.type, sortTime: parseItemTime(h.time), }); }); (cloudItems || []).forEach(function (c) { if (!c || c.id == null || seenCloudIds[c.id]) return; items.push({ kind: 'cloud', filePath: makeFileKey(c.id, c.version), fileId: c.id, name: c.name || ('文件 #' + c.id), type: null, sortTime: parseItemTime(c.updated_at || c.created_at), }); }); items.sort(function (a, b) { return (b.sortTime || 0) - (a.sortTime || 0); }); return items; } async function fetchLocalHistory() { if (!window.sysAPI || typeof window.sysAPI.readHistory !== 'function') return []; try { var j = await window.sysAPI.readHistory(); return (j && Array.isArray(j.history)) ? j.history : []; } catch (e) { return []; } } async function fetchCloudItems() { if (!hasCloudAuth() || !window.platformBridge || !window.platformBridge.listCloudFiles) return []; try { var data = await window.platformBridge.listCloudFiles({ page: 1, size: 50 }); return (data && data.items) ? data.items : []; } catch (e) { return []; } } function removeLocalHistoryPath(path) { if (!path || !window.sysAPI || typeof window.sysAPI.readHistory !== 'function') { return Promise.resolve(); } return window.sysAPI.readHistory().then(function (j) { var list = (j && Array.isArray(j.history)) ? j.history : []; var next = list.filter(function (item) { return item.path !== path; }); if (next.length === list.length) return null; return window.sysAPI.writeHistory({ history: next }); }).catch(function () { return null; }); } function renderFilePager() { var el = document.getElementById('filePager'); if (!el) return; var page = fileListState.page; var size = fileListState.size; var total = fileListState.total; var pages = Math.max(1, Math.ceil(total / size)); if (total === 0) { el.innerHTML = ''; return; } el.innerHTML = '
' + '' + '第 ' + page + ' / ' + pages + ' 页,共 ' + total + ' 个' + '
'; var prev = document.getElementById('filePrev'); var next = document.getElementById('fileNext'); if (prev && !prev.disabled) { prev.onclick = function () { fileListState.page = Math.max(1, fileListState.page - 1); loadHistory(); }; } if (next && !next.disabled) { next.onclick = function () { fileListState.page = Math.min(pages, fileListState.page + 1); loadHistory(); }; } } async function loadHistory() { try { var recentCountEl = document.getElementById('recentCount'); var localHistory = await fetchLocalHistory(); var cloudItems = await fetchCloudItems(); var merged = mergeRecentItems(localHistory, cloudItems); fileListState.items = merged; fileListState.total = merged.length; var pages = Math.max(1, Math.ceil(fileListState.total / fileListState.size)); if (fileListState.page > pages) fileListState.page = pages; if (recentCountEl) recentCountEl.textContent = String(fileListState.total); if (!merged.length) { $(".card-list").html(soonEmptyBlock('暂无文件', '点击「打开文件」导入,或新建模板开始设计')); renderFilePager(); return; } var start = (fileListState.page - 1) * fileListState.size; var slice = merged.slice(start, start + fileListState.size); let h = ""; for (let item of slice) { let filePath = item.filePath; let src = ""; let fileExists = true; let imgStyle = ""; let realType = item.type || 1; const soonData = await window.sysAPI.readJsonFile(filePath); if (soonData) { realType = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1); var fallbackThumb = (realType == 2 || realType == "2") ? soonAsset('bg_2.png') : soonAsset('bg_1.png'); src = typeof soonSafeImageUrl === 'function' ? soonSafeImageUrl(soonData.frontDisplayPic, fallbackThumb) : (soonData.frontDisplayPic || fallbackThumb); if (!src) src = fallbackThumb; } else { fileExists = false; src = soonAsset((realType == 2 || realType == "2") ? 'bg_2.png' : 'bg_1.png'); imgStyle = "opacity: 0.6; filter: grayscale(100%);"; } let displayName = item.name || displayNameFromPath(filePath); let cardTitle = displayName; var cardClass = 'card'; if (!fileExists) { cardTitle = language_str("clickToDelete"); cardClass += ' soon-card--lost'; } var lostBadge = !fileExists ? ' ' + language_str("lost") + '' : ''; var actions = ''; if (item.kind === 'cloud' && item.fileId) { actions = '' + ''; } else { actions = ''; } h += `
${actions}
${escapeAttr(displayName)}${lostBadge}
`; } $(".card-list").html(h); renderFilePager(); } catch (e) { console.error("加载文件列表出错", e); $(".card-list").html(soonEmptyBlock('加载失败', '请刷新页面重试', '')); var retry = document.getElementById('fileListRetry'); if (retry) retry.onclick = function () { loadHistory(); }; if (document.getElementById('filePager')) document.getElementById('filePager').innerHTML = ''; } } window.soonReloadRecentFiles = loadHistory; function renderTemplatesError(message) { var grid = document.getElementById('templatesGrid'); var countEl = document.getElementById('templateCount'); if (!grid) return; templateListState.items = []; templateListState.total = 0; templateListState.page = 1; grid.innerHTML = soonEmptyBlock(message, null, ''); if (countEl) countEl.textContent = '!'; renderTemplatePager(); var btn = document.getElementById('templatesRetry'); if (btn) btn.onclick = loadTemplates; } function templateFallbackThumb(type) { var t = Number(type) || 1; return soonAsset(t === 2 ? 'bg_2.png' : 'bg_1.png'); } function resolveTemplateItem(m, base) { var type = Number(m.type) || 1; var id = m.id; return { id: id, name: m.name || m.title || '模板', type: type, thumbSrc: id ? (base + '/templates/' + id + '/thumb') : templateFallbackThumb(type), file_url: id ? (base + '/templates/' + id + '/file') : (m.file_url || '') }; } function openTemplateItem(m) { var type = Number(m.type) || 1; function openWithJson(j) { if (!j) { openDesign(type, ''); return; } var key = 'soondesign_session:tpl-' + Date.now(); try { sessionStorage.setItem(key, JSON.stringify(j)); openDesign(type, key); } catch (e) { openDesign(type, ''); } } var fileUrl = m.file_url || ''; if (!fileUrl) { openDesign(type, ''); return; } fetch(fileUrl).then(function (r) { if (!r.ok) throw new Error('fetch'); return r.json(); }).then(openWithJson).catch(function () { openDesign(type, ''); }); } function templateCardHtml(m, itemIndex) { var type = Number(m.type) || 1; var fallback = templateFallbackThumb(type); var thumbSrc = m.thumbSrc || fallback; var esc = typeof soonEscapeHtml === 'function' ? soonEscapeHtml : escapeAttr; var name = esc(m.name || m.title || '模板'); var rectClass = 'rect' + (type == 2 ? ' rect1' : ''); return '
' + '
' + '' + '
' + name + '
' + '
'; } function bindTemplateCards(grid) { if (!grid) return; grid.querySelectorAll('.card[data-index]').forEach(function (card) { card.onclick = function () { var idx = Number(card.dataset.index); var m = templateListState.items[idx]; if (m) openTemplateItem(m); }; }); } function renderTemplatePager() { var prevNav = document.getElementById('templatePrev'); var nextNav = document.getElementById('templateNext'); var page = templateListState.page; var size = templateListState.size; var total = templateListState.total; var pages = Math.max(1, Math.ceil(total / size)); if (prevNav) prevNav.disabled = total === 0 || page <= 1; if (nextNav) nextNav.disabled = total === 0 || page >= pages; } function goTemplatePage(page) { var pages = Math.max(1, Math.ceil(templateListState.total / templateListState.size)); templateListState.page = Math.max(1, Math.min(pages, page)); renderTemplatesPage(); } function renderTemplatesPage() { var grid = document.getElementById('templatesGrid'); var countEl = document.getElementById('templateCount'); if (!grid) return; var items = templateListState.items || []; templateListState.total = items.length; if (countEl) countEl.textContent = String(items.length); if (!items.length) { grid.innerHTML = soonEmptyBlock('暂无模板'); renderTemplatePager(); return; } var start = (templateListState.page - 1) * templateListState.size; var slice = items.slice(start, start + templateListState.size); grid.innerHTML = slice.map(function (m, i) { return templateCardHtml(m, start + i); }).join(''); bindTemplateCards(grid); renderTemplatePager(); } async function loadTemplates() { var grid = document.getElementById('templatesGrid'); var countEl = document.getElementById('templateCount'); if (!grid) return; var base = (window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base) || ''; if (!base) { templateListState.items = []; templateListState.total = 0; templateListState.page = 1; grid.innerHTML = soonEmptyBlock('暂不可用', '请稍后重试'); if (countEl) countEl.textContent = '0'; renderTemplatePager(); return; } try { var r = await fetch(base + '/templates'); var j = await r.json(); if (!j.ok) { renderTemplatesError(j.message || '模板加载失败'); return; } var items = (j.data && j.data.items) ? j.data.items : []; templateListState.items = items.map(function (it) { return resolveTemplateItem(it, base); }); templateListState.total = templateListState.items.length; templateListState.page = 1; renderTemplatesPage(); } catch (e) { renderTemplatesError('网络错误,请稍后重试'); } } loadHistory(); loadTemplates(); var templatePrevNav = document.getElementById('templatePrev'); var templateNextNav = document.getElementById('templateNext'); if (templatePrevNav) { templatePrevNav.onclick = function () { if (!templatePrevNav.disabled) goTemplatePage(templateListState.page - 1); }; } if (templateNextNav) { templateNextNav.onclick = function () { if (!templateNextNav.disabled) goTemplatePage(templateListState.page + 1); }; } $("#openfile").click(function () { OpenDialog(); }); $("#new1").click(function () { openDesign(1, ''); }); $("#new2").click(function () { openDesign(2, ''); }); function confirmDeleteCloudFile(fileId, message, onDone) { if (!fileId) return; layer.open({ type: 1, skin: 'soon-layer', title: language_str("delTitle"), content: '
' + message + '
', btn: [language_str("comfirm"), language_str("cancel")], btnAlign: 'r', area: ['320px', 'auto'], resize: false, shadeClose: true, yes: async function (index) { if (window.platformBridge && window.platformBridge.deleteCloudFile) { try { await window.platformBridge.deleteCloudFile(fileId); } catch (e) { /* ignore */ } } layer.msg(language_str("deleted"), { icon: 1, time: 1000 }); if (typeof onDone === 'function') onDone(); layer.close(index); } }); } $(".card-list").on("click", '.soon-file-card__download', function (e) { e.stopPropagation(); var card = $(this).closest('.card'); var id = parseInt(card.attr('data-id'), 10); var name = card.attr('data-name') || 'design.soon'; if (id && window.platformBridge && window.platformBridge.downloadCloudFile) { window.platformBridge.downloadCloudFile(id, name).catch(function () {}); } }); $(".card-list").on("click", '.soon-file-card__delete', function (e) { e.stopPropagation(); var card = $(this).closest('.card'); var kind = card.attr('data-kind') || 'cloud'; var fileId = parseInt(card.attr('data-id'), 10); var filePath = card.attr('data-file') || ''; if (kind === 'local' || !fileId) { layer.open({ type: 1, skin: 'soon-layer', title: language_str("delTitle"), content: '
' + language_str('deleteFileConfirm') + '
', btn: [language_str("comfirm"), language_str("cancel")], btnAlign: 'r', area: ['320px', 'auto'], resize: false, shadeClose: true, yes: function (index) { removeLocalHistoryPath(filePath).then(function () { if (filePath.indexOf('soondesign_session:') === 0) { try { sessionStorage.removeItem(filePath); localStorage.removeItem(filePath); } catch (err) { /* ignore */ } } layer.msg(language_str("deleted"), { icon: 1, time: 1000 }); loadHistory(); layer.close(index); }); } }); return; } confirmDeleteCloudFile(fileId, language_str('deleteFileConfirm'), loadHistory); }); $(".card-list").on("click", '.card', async function () { let filePath = $(this).attr("data-file") || $(this).attr("data"); const soonData = await window.sysAPI.readJsonFile(filePath); if (soonData) { let type = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1); if (typeof window.soonBindFileMeta === 'function') { window.soonBindFileMeta(filePath, { name: $(this).attr('data-name') || '' }); } openDesign(type, filePath); } else { var kind = $(this).attr('data-kind') || 'cloud'; var fileId = parseInt($(this).attr('data-id'), 10); if (kind === 'local' || !fileId) { removeLocalHistoryPath(filePath).then(function () { if (filePath.indexOf('soondesign_session:') === 0) { try { sessionStorage.removeItem(filePath); localStorage.removeItem(filePath); } catch (err) { /* ignore */ } } loadHistory(); }); return; } confirmDeleteCloudFile(fileId, language_str("delContent"), loadHistory); } }); function OpenDialog() { var dialogApi = window.platformBridge && window.platformBridge.showOpenDialog ? { showOpenDialog: function (opts) { return window.platformBridge.showOpenDialog(opts); } } : null; if (!dialogApi) return; dialogApi.showOpenDialog({ title: language_str("selectFile"), buttonLabel: language_str("comfirm"), filters: [{ name: language_str("SDFile"), extensions: ['soon'] }] }).then(async function (result) { if (result.canceled) return; var file = (result.files && result.files[0]) || result.file; if (!file || !file.text) { layer.msg(language_str("noFile")); return; } var fileName = file.name || 'design.soon'; var soonData; try { soonData = JSON.parse(await file.text()); } catch (e) { layer.msg(language_str("noFile")); return; } var tok = typeof window.soonGetAccessToken === 'function' ? window.soonGetAccessToken() : (localStorage.getItem('soon_access') || ''); if (!tok && typeof window.soonOpenSoonJsonLocally === 'function') { window.soonOpenSoonJsonLocally(soonData, fileName); return; } if (!window.platformBridge || !window.platformBridge.importSoonFile) return; try { var res = await window.platformBridge.importSoonFile(fileName, soonData); if (!res || !res.fileKey) return; var type = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1); openDesign(type, res.fileKey); loadHistory(); } catch (err) { /* errors shown by bridge */ } }).catch(function (err) { console.log(err); }); } });