网页端:平台桥与部署修复(宝塔/Nginx)

- lib/platform:网页 Electron 统一 bridge/web/electron,子目录 API 基路径、返回首页跳站点根、路径末尾斜杠兼容
- index/design*.web.html:网页入口,web.js 加缓存参数避免旧脚本缓存
- api:PHP 读写 soon;文档说明目录权限与 Nginx
- lib/design*、lib/index:与 platformBridge 对接及网页侧逻辑
- 公共资源 JsBarcode/jr-qrcode;文档与 package/README/.gitignore 等同步更新

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
24kycj
2026-05-09 18:57:53 +08:00
parent ee6ef462ca
commit 2e6578247e
27 changed files with 6606 additions and 3542 deletions
+45 -26
View File
@@ -1,6 +1,6 @@
// design1.js - 主入口文件
// 导入公共模块(不依赖jQuery的部分)
require('./common/fabric-ext.js');
// 导入公共模块(不依赖jQuery的部分);网页端由页面 script 标签提前加载
if (typeof require !== 'undefined') { try { require('./common/fabric-ext.js'); } catch (e) {} }
// 过滤 Canvas2D willReadFrequently 警告(不影响功能,只是性能提示)
if (typeof console !== 'undefined' && console.warn) {
@@ -15,20 +15,24 @@ if (typeof console !== 'undefined' && console.warn) {
};
}
// Electron相关导入
const remote = require('@electron/remote');
var path = require('path');
const exePath = remote.app.getPath('userData');
const { ipcRenderer } = require('electron');
const { dialog } = require('@electron/remote');
var jrQrcode = require('jr-qrcode');
var JsBarcode = require('jsbarcode');
const { clipboard } = require('electron');
const dpi = 600;
// 使用平台桥(桌面端由 design1.html 先加载 lib/platform/electron.js 注入)
var remote = typeof window !== 'undefined' ? window.remote : null;
var path = typeof window !== 'undefined' ? window.path : null;
var exePath = typeof window !== 'undefined' ? window.exePath : '';
var ipcRenderer = typeof window !== 'undefined' ? window.ipcRenderer : null;
var dialog = typeof window !== 'undefined' ? window.dialog : null;
var clipboard = typeof window !== 'undefined' ? window.clipboard : null;
var jrQrcode, JsBarcode;
if (typeof require !== 'undefined') {
try { jrQrcode = require('jr-qrcode'); } catch (e) {}
try { JsBarcode = require('jsbarcode'); } catch (e) {}
}
var dpi = 600;
// 发送IPC消息
ipcRenderer.send('get-sys-fonts');
ipcRenderer.send('get-scale-rate');
if (ipcRenderer) {
ipcRenderer.send('get-sys-fonts');
ipcRenderer.send('get-scale-rate');
}
// 使用layui
layui.use(['layer', 'slider', 'form', 'colorpicker'], function () {
@@ -41,7 +45,7 @@ const dpi = 600;
if (typeof global !== 'undefined') {
global.s_lan = s_lan;
}
ipcRenderer.send('get-sys-language');
if (ipcRenderer) ipcRenderer.send('get-sys-language');
var $ = layui.$;
var layer = layui.layer;
@@ -90,7 +94,7 @@ const dpi = 600;
var colorpicker = layui.colorpicker;
// 历史记录文件路径(供 output.js 使用)
var fullPath = path.join(exePath, 'data.json');
var fullPath = (path && exePath) ? path.join(exePath, 'data.json') : (typeof window !== 'undefined' ? window.fullPath : '');
// 确保 fullPath 在全局作用域中可用(用于 .jsc 文件加载)
if (typeof window !== 'undefined') {
window.fullPath = fullPath;
@@ -489,25 +493,40 @@ const dpi = 600;
var ctx2 = window.ctx2;
let is_bgi_add = window.is_bgi_add;
// 加载design1模块
const fs = require('fs');
const appPath = (() => { try { return remote.app.getAppPath(); } catch (e) { return __dirname || process.cwd(); } })();
// 加载 design1 模块:网页端在回调内动态加载(保证 $ 已存在),桌面端用 require+eval
var fs = typeof window !== 'undefined' ? window.fs : null;
var appPath = (remote && remote.app && remote.app.getAppPath) ? remote.app.getAppPath() : (typeof __dirname !== 'undefined' ? __dirname : '.');
if (typeof require === 'undefined') {
// 网页端:按顺序动态插入 script,确保 core/ui 执行时 window.$ 已存在
function loadScript(src, next) {
var s = document.createElement('script');
s.src = src;
s.onload = s.onerror = function () { if (typeof next === 'function') next(); };
document.body.appendChild(s);
}
loadScript('./lib/design1/output.js', function () {
loadScript('./lib/design1/core.js', function () {
loadScript('./lib/design1/ui.js', function () {});
});
});
return;
}
const loadModule = (moduleName) => {
const jsPath = path.join(appPath, 'lib', 'design1', `${moduleName}.js`);
// 加载 .js 源文件
if (!path || !fs) return;
const jsPath = path.join(appPath, 'lib', 'design1', moduleName + '.js');
if (fs.existsSync(jsPath)) {
try {
eval(fs.readFileSync(jsPath, 'utf8'));
} catch (e) {
alert(`加载 ${moduleName} 失败!\n\n错误: ${e.message}`);
alert('加载 ' + moduleName + ' 失败!\n\n错误: ' + e.message);
}
} else {
alert(`文件不存在!\n\n请检查:${jsPath}`);
alert('文件不存在!\n\n请检查:' + jsPath);
}
};
loadModule('output'); // 先加载 output,因为 core 中的 addBackground() 需要调用 output 中的 open()
loadModule('output');
loadModule('core');
loadModule('ui');
});
+47 -21
View File
@@ -81,25 +81,31 @@ canvas2.preserveObjectStacking = true;
var front_list = [];
ipcRenderer.on('font-list', (event, data) => {
const newFontList = [];
data.map(item => {
function applyFontList(data) {
var newFontList = [];
(data || []).map(function(item) {
if (item.indexOf('"') === 0) {
newFontList.push(item.replace(/^"|"$/g, ''));
} else {
newFontList.push(item);
}
})
});
front_list = newFontList;
for (let item of front_list) {
for (var i = 0; i < front_list.length; i++) {
var item = front_list[i];
$("#text_font_family").append("<option value='" + item + "'>" + item + "</option>");
$("#circle_text_font_family").append("<option value='" + item + "'>" + item + "</option>");
$("#out_text_font_family").append("<option value='" + item + "'>" + item + "</option>");
$("#barcode_font_family").append("<option value='" + item + "'>" + item + "</option>");
$("#count_font_family").append("<option value='" + item + "'>" + item + "</option>");
}
});
ipcRenderer.on('sys-lan', (event, data) => {
}
if (typeof ipcRenderer !== 'undefined' && ipcRenderer) {
ipcRenderer.on('font-list', function(event, data) { applyFontList(data); });
} else if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.getSystemFonts) {
window.platformBridge.getSystemFonts().then(applyFontList);
}
function applySysLan(data) {
let lang = localStorage.getItem("lang");
if (lang) {
s_lan = lang;
@@ -246,23 +252,33 @@ ipcRenderer.on('sys-lan', (event, data) => {
localStorage.setItem("lang", "en");
break;
}
});
ipcRenderer.on('close', (event, message) => {
layer.confirm('<font style="color:black">' + language_str("whetherSave") + '</font>', {//是否保存当前文件?
btn: [language_str("save"), language_str("noSave"), language_str("cancel")] //['保存', '不保存', '取消']
, btn3: function (index, layero) {
}
}
if (typeof ipcRenderer !== 'undefined' && ipcRenderer) {
ipcRenderer.on('sys-lan', function(event, data) { applySysLan(data); });
} else if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.getLocale) {
applySysLan(window.platformBridge.getLocale());
}
function onCloseConfirm() {
layer.confirm('<font style="color:black">' + language_str("whetherSave") + '</font>', {
btn: [language_str("save"), language_str("noSave"), language_str("cancel")]
, btn3: function (index, layero) {}
}, function (index, layero) {
if (typeof window.output === 'function') {
window.output(() => ipcRenderer.send('run-close'));
window.output(function() {
if (ipcRenderer) ipcRenderer.send('run-close');
else if (window.platformBridge && window.platformBridge.runClose) window.platformBridge.runClose();
});
}
}, function (index) {
ipcRenderer.send('run-close');
if (ipcRenderer) ipcRenderer.send('run-close');
else if (window.platformBridge && window.platformBridge.runClose) window.platformBridge.runClose();
});
})
}
if (typeof ipcRenderer !== 'undefined' && ipcRenderer) {
ipcRenderer.on('close', function(event, message) { onCloseConfirm(); });
} else if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.onClose) {
window.platformBridge.onClose(onCloseConfirm);
}
addBackground();//添加背景 白色卡片
initAligningGuidelines(canvas1);
initAligningGuidelines(canvas2);
@@ -402,9 +418,19 @@ function addBackground() {
recordObjs2.push(JSON.stringify(objs2));
recordJson2.push(canvas2.toJSON(TO_JSON_PROPERTIES));
let file = GetFile().get('file')
let file = GetFile().get('file');
if (!file && typeof sessionStorage !== 'undefined') {
try {
file = sessionStorage.getItem('soondesign_open_file');
if (file) {
sessionStorage.removeItem('soondesign_open_file');
sessionStorage.removeItem('soondesign_open_type');
}
} catch (e) {}
}
if (file && file != 'empty') {
window.openFile(file)
var p = window.openFile(file)
if (p && p.then) p.then(function() {}, function() {})
}
});
});
+279 -266
View File
@@ -14,27 +14,41 @@ var openAs = {
}
};
// 导出文件
// 导出文件(兼容桌面 fs 与网页 bridge 下载)
async function saveImageAsPNG(buffer) {
let fs = require('fs');
// 弹出保存对话框
const { canceled, filePath } = await dialog.showSaveDialog({
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null);
if (!dialogApi) return;
var pathJoin = (path && path.join) ? path.join.bind(path) : function() { return [].slice.call(arguments).join('/').replace(/\/+/g, '/'); };
var defaultPath = exePath ? pathJoin(exePath, 'output.png') : 'output.png';
const result = await dialogApi.showSaveDialog({
title: '保存图片',
defaultPath: path.join(exePath, 'output.png'), // 默认路径
filters: [{ name: 'PNG Images', extensions: ['png'] }] // 文件过滤器
defaultPath: defaultPath,
filters: [{ name: 'PNG Images', extensions: ['png'] }]
});
if (canceled) {
var canceled = result.canceled, filePath = result.filePath || result.fileHandle;
if (canceled || !filePath) return;
if (window.platformBridge && window.platformBridge.writeFile) {
var blob = buffer instanceof Uint8Array ? new Blob([buffer]) : (buffer && buffer.buffer ? new Blob([buffer]) : new Blob([buffer]));
var name = (typeof filePath === 'string' ? filePath.split(/[/\\]/).pop() : 'output.png') || 'output.png';
if (!/\.png$/i.test(name)) name = name + '.png';
if (result.fileHandle && result.fileHandle.createWritable) {
result.fileHandle.createWritable().then(function(w) { w.write(blob); return w.close(); }).then(function() { layer.msg(language_str("saveSucc") + name); });
} else {
var a = document.createElement('a');
a.download = name;
a.href = URL.createObjectURL(blob);
a.click();
URL.revokeObjectURL(a.href);
layer.msg(language_str("saveSucc") + name);
}
return;
}
// 将Buffer写入PNG文件到用户选择的路径
fs.writeFile(filePath, buffer, (err) => {
if (err) {
} else {
layer.msg(language_str("saveSucc") + filePath);//'保存成功至'
}
});
var fs = typeof require !== 'undefined' ? require('fs') : null;
if (fs) {
fs.writeFile(filePath, buffer, (err) => {
if (!err) layer.msg(language_str("saveSucc") + filePath);
});
}
}
// 将 display_func 附加到 window 对象,确保全局可访问
@@ -368,11 +382,22 @@ window.display_func = function display_func(img1, img2, img3) {
w = ((document.body.clientHeight * 0.8 - 150) / 0.62548 + 40) / 0.94
}
let fs = require('fs');
const Buffer = require('buffer').Buffer;
const buffer = Buffer.from(url3.replace(/^data:image\/\w+;base64,/, ""), 'base64');
const printPath = path.join(exePath, 'print.png');
fs.writeFileSync(printPath, buffer);
var printPath, printBlobUrl, buffer;
if (typeof require !== 'undefined' && require('fs')) {
var fs = require('fs');
var Buffer = require('buffer').Buffer;
var buf = Buffer.from(url3.replace(/^data:image\/\w+;base64,/, ""), 'base64');
printPath = path.join(exePath, 'print.png');
fs.writeFileSync(printPath, buf);
buffer = buf;
} else {
var bin = atob(url3.replace(/^data:image\/\w+;base64,/, ""));
var arr = new Uint8Array(bin.length);
for (var i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
buffer = arr;
printBlobUrl = URL.createObjectURL(new Blob([arr], { type: 'image/png' }));
printPath = printBlobUrl;
}
layer.open({
type: 1,
@@ -392,14 +417,18 @@ window.display_func = function display_func(img1, img2, img3) {
</div>`,
btn: [language_str("output"), "打印"],//'导出'
btn1: function (index, layero) {
// 只处理导出(保存PNG
saveImageAsPNG(buffer)
saveImageAsPNG(buffer);
},
btn2: function () {
// 只处理打印
printJS({ printable: printPath, type: 'image', style: 'img { width: 100%; height: auto; }' })
if (window.platformBridge && window.platformBridge.printPdf) {
var blob = buffer instanceof Uint8Array ? new Blob([buffer], { type: 'image/png' }) : new Blob([buffer], { type: 'image/png' });
window.platformBridge.printPdf(blob);
} else if (typeof printJS === 'function') {
printJS({ printable: printPath, type: 'image', style: 'img { width: 100%; height: auto; }' });
}
},
end: function () {
if (printBlobUrl) try { URL.revokeObjectURL(printBlobUrl); } catch (e) {}
// 预览窗口关闭后,恢复所有对象的 selectable 和 evented 状态
canvas1.getObjects().forEach((obj, index) => {
if (!obj.isGuideLine && objStates1[index]) {
@@ -1047,157 +1076,111 @@ window.regenerateQrCodesAndBarcodes = function regenerateQrCodesAndBarcodes(canv
// open() 函数在 output.js 中定义,因为 output.js 先于 core.js 加载
// 使用 window.openFile 避免与浏览器原生的 window.open 冲突
window.openFile = function open(file) {
try {
let fs = require('fs');
if (!fs.existsSync(file)) {
return;
}
var fsData = fs.readFileSync(file);
openAs.name = file;
let j;
try {
j = JSON.parse(fsData.toString());
} catch (e) {
return;
}
if (!background_image) {
return;
}
if (!j.f || !j.f.objects || !Array.isArray(j.f.objects) || j.f.objects.length === 0) {
return;
}
window.openFile = function open(file, jAlready) {
if (!file) return;
function doOpenWithJson(j) {
if (!j || !background_image) return;
if (!j.f || !j.f.objects || !Array.isArray(j.f.objects) || j.f.objects.length === 0) return;
if (!j.b || !j.b.objects || !Array.isArray(j.b.objects) || j.b.objects.length === 0) {
j.b = {
version: '4.6.0',
objects: [],
hoverCursor: 'move'
};
j.b = { version: '4.6.0', objects: [], hoverCursor: 'move' };
}
let left = background_image.left;
let top = background_image.top;
let new_left = j.f.objects[0].left;
let new_top = j.f.objects[0].top;
for (let o of j.f.objects) {
o.left = o.left - new_left + left;
o.top = o.top - new_top + top;
openAs.name = file;
var left = background_image.left;
var top = background_image.top;
var new_left = j.f.objects[0].left;
var new_top = j.f.objects[0].top;
for (var i = 0; i < j.f.objects.length; i++) {
j.f.objects[i].left = j.f.objects[i].left - new_left + left;
j.f.objects[i].top = j.f.objects[i].top - new_top + top;
}
if (j.f.objects[0] && !j.f.objects[0].src) {
j.f.objects[0].src = './public/images/bg_front_1.png'
}
if (j.b.objects && j.b.objects.length > 0 && j.b.objects[0] && !j.b.objects[0].src) {
j.b.objects[0].src = './public/images/bg_back.png'
}
const fo = j.fo || [];
const bo = j.bo || [];
const placeholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
fo.forEach((objMeta, idx) => {
if ((objMeta.type === 8 || objMeta.type === 9) && j.f.objects[idx + 1] && !j.f.objects[idx + 1].src) {
j.f.objects[idx + 1].src = placeholder;
} else if (objMeta.type === 2 && j.f.objects[idx + 1] && !j.f.objects[idx + 1].src) {
// 合成图片类型,恢复默认图片
j.f.objects[idx + 1].src = './public/images/outpic_extra.png';
}
if (j.f.objects[0] && !j.f.objects[0].src) j.f.objects[0].src = './public/images/bg_front_1.png';
if (j.b.objects && j.b.objects[0] && !j.b.objects[0].src) j.b.objects[0].src = './public/images/bg_back.png';
var fo = j.fo || [], bo = j.bo || [];
var placeholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
fo.forEach(function(objMeta, idx) {
if ((objMeta.type === 8 || objMeta.type === 9) && j.f.objects[idx + 1] && !j.f.objects[idx + 1].src) j.f.objects[idx + 1].src = placeholder;
else if (objMeta.type === 2 && j.f.objects[idx + 1] && !j.f.objects[idx + 1].src) j.f.objects[idx + 1].src = './public/images/outpic_extra.png';
});
bo.forEach((objMeta, idx) => {
if ((objMeta.type === 8 || objMeta.type === 9) && j.b.objects && j.b.objects[idx + 1] && !j.b.objects[idx + 1].src) {
j.b.objects[idx + 1].src = placeholder;
} else if (objMeta.type === 2 && j.b.objects && j.b.objects[idx + 1] && !j.b.objects[idx + 1].src) {
// 合成图片类型,恢复默认图片
j.b.objects[idx + 1].src = './public/images/outpic_extra.png';
}
bo.forEach(function(objMeta, idx) {
if ((objMeta.type === 8 || objMeta.type === 9) && j.b.objects && j.b.objects[idx + 1] && !j.b.objects[idx + 1].src) j.b.objects[idx + 1].src = placeholder;
else if (objMeta.type === 2 && j.b.objects && j.b.objects[idx + 1] && !j.b.objects[idx + 1].src) j.b.objects[idx + 1].src = './public/images/outpic_extra.png';
});
canvas1.loadFromJSON(j.f, function () {
// 加载后解锁所有对象(背景除外)
if (typeof window.unlockAllObjects === 'function') {
window.unlockAllObjects(canvas1);
}
if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
window.regenerateQrCodesAndBarcodes(canvas1, fo);
}
// 加载后更新列表和图标显示
if (typeof window.updateList === 'function') {
window.updateList();
}
canvas1.loadFromJSON(j.f, function() {
if (typeof window.unlockAllObjects === 'function') window.unlockAllObjects(canvas1);
if (typeof window.regenerateQrCodesAndBarcodes === 'function') window.regenerateQrCodesAndBarcodes(canvas1, fo);
if (typeof window.updateList === 'function') window.updateList();
canvas1.renderAll();
});
canvas2.loadFromJSON(j.b, function () {
// 加载后解锁所有对象(背景除外)
if (typeof window.unlockAllObjects === 'function') {
window.unlockAllObjects(canvas2);
}
if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
window.regenerateQrCodesAndBarcodes(canvas2, bo);
}
// 加载后更新列表和图标显示
if (typeof window.updateList === 'function') {
window.updateList();
}
canvas2.loadFromJSON(j.b, function() {
if (typeof window.unlockAllObjects === 'function') window.unlockAllObjects(canvas2);
if (typeof window.regenerateQrCodesAndBarcodes === 'function') window.regenerateQrCodesAndBarcodes(canvas2, bo);
if (typeof window.updateList === 'function') window.updateList();
canvas2.renderAll();
});
let objects1 = canvas1.getObjects();
let objects2 = canvas2.getObjects();
if (!objects1 || objects1.length === 0) {
return;
}
background_image1 = objects1[0];
background_image2 = objects2 && objects2.length > 0 ? objects2[0] : null;
objs2 = j.bo || [];
objs1 = j.fo || [];
step1.val = 0;
step2.val = 0;
recordJson1 = [];
recordJson2 = [];
recordObjs1 = [];
recordObjs2 = [];
if ($("#front_side").hasClass("ui-button-active")) {
objs = objs1;
recordObjs = recordObjs1;
recordJson = recordJson1;
step = step1;
background_image = background_image1;
} else if ($("#back_side").hasClass("ui-button-active")) {
objs = objs2;
recordObjs = recordObjs2;
recordJson = recordJson2;
background_image = background_image2 || background_image1;
step = step2;
}
canvas1.renderAll();
canvas2.renderAll();
// 调用 updateList,现在它已附加到 window 对象
if (typeof window.updateList === 'function') {
window.updateList();
} else {
setTimeout(() => {
if (typeof window.updateList === 'function') {
window.updateList();
var objects1 = canvas1.getObjects(), objects2 = canvas2.getObjects();
if (objects1 && objects1.length > 0) {
background_image1 = objects1[0];
background_image2 = objects2 && objects2.length > 0 ? objects2[0] : null;
objs2 = j.bo || [];
objs1 = j.fo || [];
step1.val = 0;
step2.val = 0;
recordJson1 = [];
recordJson2 = [];
recordObjs1 = [];
recordObjs2 = [];
if ($("#front_side").hasClass("ui-button-active")) {
objs = objs1;
recordObjs = recordObjs1;
recordJson = recordJson1;
step = step1;
background_image = background_image1;
} else if ($("#back_side").hasClass("ui-button-active")) {
objs = objs2;
recordObjs = recordObjs2;
recordJson = recordJson2;
background_image = background_image2 || background_image1;
step = step2;
}
}, 100);
}
setTimeout(() => {
recordObjs1.push(JSON.stringify(objs1))
const props = window.TO_JSON_PROPERTIES || ["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline", "evented"];
let j1 = canvas1.toJSON(props);
j1.objects[0].hoverCursor = "default";
recordJson1.push(j1);
recordObjs2.push(JSON.stringify(objs2))
let j2 = canvas2.toJSON(props);
recordJson2.push(j2);
}, 0);
} catch (e) {
// 静默处理错误
canvas1.renderAll();
canvas2.renderAll();
if (typeof window.updateList === 'function') window.updateList();
else setTimeout(function() { if (typeof window.updateList === 'function') window.updateList(); }, 100);
setTimeout(function() {
recordObjs1.push(JSON.stringify(objs1));
var props = window.TO_JSON_PROPERTIES || ["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline", "evented"];
var j1 = canvas1.toJSON(props);
if (j1.objects && j1.objects[0]) j1.objects[0].hoverCursor = "default";
recordJson1.push(j1);
recordObjs2.push(JSON.stringify(objs2));
var j2 = canvas2.toJSON(props);
recordJson2.push(j2);
}, 0);
}
});
}
}
if (arguments.length >= 2 && jAlready) {
doOpenWithJson(jAlready);
openAs.name = file;
return;
}
if (window.platformBridge && window.platformBridge.readJsonFile) {
return window.platformBridge.readJsonFile(file).then(function(j) {
if (j) {
doOpenWithJson(j);
openAs.name = file;
}
}).catch(function() {});
}
try {
var fs = typeof require !== 'undefined' ? require('fs') : null;
if (!fs || file.indexOf('soondesign_session:') === 0) return;
if (!fs.existsSync(file)) return;
var fsData = fs.readFileSync(file);
var j;
try { j = JSON.parse(fsData.toString()); } catch (e) { return; }
doOpenWithJson(j);
} catch (e) {}
};
// 清理不需要的 src 字段(保留自定义图片的 src)
// 将 cleanupSrcFields 附加到 window 对象,确保在 ui.js 中的 saveAs 函数也能访问
@@ -1266,34 +1249,37 @@ function saveAs(op1, callback) {
let o = { f: j, b: {}, fo: objs1, bo: objs2 };
let con_o = Object.assign(o, op1);
dialog.showSaveDialog({
title: language_str("saveFile"),//'保存文件'
filters: [
{ name: 'Soon File Type', extensions: ['soon'] },
],
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null);
if (!dialogApi) return;
dialogApi.showSaveDialog({
title: language_str("saveFile"),
filters: [{ name: 'Soon File Type', extensions: ['soon'] }],
defaultPath: openAs.name || undefined
}).then(result => {
if (result.filePath == "") { return; }
if (result.filePath == null || result.filePath == undefined) { return; }
// 检查文件扩展名,确保以.soon结尾
const path = require('path');
let filePath = result.filePath;
const ext = path.extname(filePath).toLowerCase();
if (ext !== '.soon') {
filePath = filePath + '.soon';
}).then(async function(result) {
var fp = result.filePath || (result.fileHandle && result.fileHandle.name);
if (!fp && !result.fileHandle) return;
var pathExt = (typeof window !== 'undefined' && window.path && window.path.extname) ? window.path.extname(fp) : (fp.indexOf('.') >= 0 ? fp.slice(fp.lastIndexOf('.')) : '');
if (pathExt !== '.soon') fp = (fp || 'design').replace(/\.soon$/i, '') + '.soon';
var content = JSON.stringify(con_o);
con_o.soonType = 1;
if (window.platformBridge && window.platformBridge.writeFile) {
await window.platformBridge.writeFile(result.fileHandle || fp, content);
openAs.name = 'soondesign_session:' + fp;
webSessionStore(content, fp);
layer.msg(language_str("saveSucc") + fp);
saveHistory();
if (callback && typeof callback === 'function') callback();
return;
}
result.filePath = filePath;
let fs = require('fs')
con_o.soonType = 1
fs.writeFileSync(result.filePath, JSON.stringify(con_o), 'utf8')
openAs.name = result.filePath;
layer.msg(language_str("saveSucc") + openAs.name);//'保存成功至'
saveHistory();
if (callback && typeof callback === 'function') {
callback();
var fs = typeof require !== 'undefined' ? require('fs') : null;
if (fs) {
fs.writeFileSync(result.filePath || fp, content, 'utf8');
openAs.name = result.filePath || fp;
layer.msg(language_str("saveSucc") + openAs.name);
saveHistory();
if (callback && typeof callback === 'function') callback();
}
}).catch(err => {
})
}).catch(function(err) {});
}
function save(op1, callback) {
@@ -1330,95 +1316,122 @@ function save(op1, callback) {
let o = { f: j, b: {}, fo: objs1, bo: objs2 };
let con_o = Object.assign(o, op1);
if (openAs.name != "") {
//打开的文件
let fs = require('fs')
con_o.soonType = 1
fs.writeFileSync(openAs.name, JSON.stringify(con_o), 'utf8')
layer.msg(language_str("saveSucc") + openAs.name);//'保存成功至'
saveHistory();
if (callback && typeof callback === 'function') {
callback();
con_o.soonType = 1;
var content = JSON.stringify(con_o);
if (openAs.name.indexOf('soondesign_session:') === 0) {
webSessionStore(content, openAs.name);
// 服务器环境:同时保存到服务器
var fileName = openAs.name.replace(/^soondesign_session:/, '');
if (window.platformBridge && window.platformBridge.writeFile && fileName) {
window.platformBridge.writeFile(fileName, content).catch(function() {});
}
layer.msg(language_str("saveSucc") + fileName);
saveHistory();
if (callback && typeof callback === 'function') callback();
return;
}
if (window.platformBridge && window.platformBridge.writeFile) {
webSessionStore(content, openAs.name);
window.platformBridge.writeFile(openAs.name, content).then(function() {
layer.msg(language_str("saveSucc") + openAs.name);
saveHistory();
if (callback && typeof callback === 'function') callback();
});
return;
}
var fs = typeof require !== 'undefined' ? require('fs') : null;
if (fs) {
fs.writeFileSync(openAs.name, content, 'utf8');
layer.msg(language_str("saveSucc") + openAs.name);
saveHistory();
if (callback && typeof callback === 'function') callback();
}
return;
}
dialog.showSaveDialog({
title: language_str("saveFile"),//'保存文件'
filters: [
{ name: 'Soon File Type', extensions: ['soon'] },
],
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null);
if (!dialogApi) return;
dialogApi.showSaveDialog({
title: language_str("saveFile"),
filters: [{ name: 'Soon File Type', extensions: ['soon'] }],
defaultPath: openAs.name || undefined
}).then(result => {
if (result.filePath == "") { return; }
if (result.filePath == null || result.filePath == undefined) { return; }
// 检查文件扩展名,确保以.soon结尾
const path = require('path');
let filePath = result.filePath;
const ext = path.extname(filePath).toLowerCase();
if (ext !== '.soon') {
filePath = filePath + '.soon';
}).then(async function(result) {
var fp = result.filePath || (result.fileHandle && result.fileHandle.name);
if (!fp && !result.fileHandle) return;
var pathExt = (typeof window !== 'undefined' && window.path && window.path.extname) ? window.path.extname(fp) : (fp.indexOf('.') >= 0 ? fp.slice(fp.lastIndexOf('.')) : '');
if (pathExt !== '.soon') fp = (fp || 'design').replace(/\.soon$/i, '') + '.soon';
var content = JSON.stringify(con_o);
con_o.soonType = 1;
if (window.platformBridge && window.platformBridge.writeFile) {
await window.platformBridge.writeFile(result.fileHandle || fp, content);
openAs.name = 'soondesign_session:' + fp;
webSessionStore(content, fp);
layer.msg(language_str("saveSucc") + fp);
saveHistory();
if (callback && typeof callback === 'function') callback();
return;
}
result.filePath = filePath;
let fs = require('fs')
con_o.soonType = 1
fs.writeFileSync(result.filePath, JSON.stringify(con_o), 'utf8')
openAs.name = result.filePath;
layer.msg(language_str("saveSucc") + openAs.name);//'保存成功至'
saveHistory();
if (callback && typeof callback === 'function') {
callback();
var fs = typeof require !== 'undefined' ? require('fs') : null;
if (fs) {
fs.writeFileSync(result.filePath || fp, content, 'utf8');
openAs.name = result.filePath || fp;
layer.msg(language_str("saveSucc") + openAs.name);
saveHistory();
if (callback && typeof callback === 'function') callback();
}
}).catch(err => {
})
}).catch(function(err) {});
}
// 网页端:保存前把当前内容写入 sessionStorage 与 localStorage,供首页历史读取(localStorage 跨标签/重开可用)
function webSessionStore(content, fileName) {
if (!window.platformBridge || typeof sessionStorage === 'undefined') return;
var name = fileName || openAs.name || 'design.soon';
var key = (name && name.indexOf('soondesign_session:') === 0) ? name : ('soondesign_session:' + name);
try {
sessionStorage.setItem(key, content);
if (typeof localStorage !== 'undefined') localStorage.setItem(key, content);
} catch (e) {}
}
// 将 saveHistory 附加到 window 对象,确保在 ui.js 中也能访问
window.saveHistory = function saveHistory() {
let fs = require('fs');
let j = { history: [] }; // 默认结构必须是数组
try {
// 1. 读取并解析现有文件
if (fs.existsSync(fullPath)) {
let content = fs.readFileSync(fullPath).toString();
// 防止空文件报错
if (content.trim()) {
j = JSON.parse(content);
}
function doWrite(j) {
var currentPath = openAs.name;
var pathForHistory = currentPath;
if (window.platformBridge && (currentPath.indexOf('soondesign_session:') !== 0)) {
pathForHistory = 'soondesign_session:' + (currentPath || 'design.soon');
}
var newList = (j.history || []).filter(function(item) { return item.path !== pathForHistory && item.path !== currentPath; });
newList.unshift({ time: getDate(), path: pathForHistory, type: 1 });
if (newList.length > 20) newList = newList.slice(0, 20);
j.history = newList;
if (window.platformBridge && window.platformBridge.writeHistory) {
window.platformBridge.writeHistory(j);
return;
}
var fs = typeof require !== 'undefined' ? require('fs') : null;
if (fs && fullPath) {
try { fs.writeFileSync(fullPath, JSON.stringify(j), 'utf8'); } catch (e) {}
}
} catch (e) {
// 出错时使用默认空数组,不中断流程
j = { history: [] };
}
// 确保 j.history 是数组 (防御性编程)
if (!Array.isArray(j.history)) {
j.history = [];
if (window.platformBridge && window.platformBridge.readHistory) {
window.platformBridge.readHistory().then(function(j) {
if (!j || !Array.isArray(j.history)) j = { history: [] };
doWrite(j);
}).catch(function() { doWrite({ history: [] }); });
return;
}
// 2. 【核心优化】先过滤掉当前文件(如果已存在),然后加到最前面
// 这样可以实现“最近使用的文件排在第一位”的效果
let currentPath = openAs.name;
let newList = j.history.filter(item => item.path !== currentPath);
// 3. 插入到头部 (Unshift)
newList.unshift({
time: getDate(),
path: currentPath,
// type: 1 // 如果需要记录文件类型也可以加在这里
});
// 4. (可选) 限制历史记录数量,比如只保留最近20条,防止文件无限膨胀
if (newList.length > 20) {
newList = newList.slice(0, 20);
}
j.history = newList;
// 5. 统一写入
try {
fs.writeFileSync(fullPath, JSON.stringify(j), 'utf8');
} catch (e) {
var fs = typeof require !== 'undefined' ? require('fs') : null;
var j = { history: [] };
if (fs && fullPath) {
try {
if (fs.existsSync(fullPath)) {
var content = fs.readFileSync(fullPath).toString();
if (content.trim()) j = JSON.parse(content);
}
} catch (e) { j = { history: [] }; }
}
if (!Array.isArray(j.history)) j.history = [];
doWrite(j);
};
// 向后兼容
function saveHistory() {
+72 -54
View File
File diff suppressed because one or more lines are too long
+44 -26
View File
@@ -1,6 +1,6 @@
// design2.js - 主入口文件
// 导入公共模块(不依赖jQuery的部分)
require('./common/fabric-ext.js');
// 导入公共模块(不依赖jQuery的部分);网页端由页面 script 标签提前加载
if (typeof require !== 'undefined') { try { require('./common/fabric-ext.js'); } catch (e) {} }
// 过滤 Canvas2D willReadFrequently 警告(不影响功能,只是性能提示)
if (typeof console !== 'undefined' && console.warn) {
@@ -15,25 +15,29 @@ if (typeof console !== 'undefined' && console.warn) {
};
}
// Electron相关导入
const remote = require('@electron/remote');
var path = require('path');
const exePath = remote.app.getPath('userData');
const { ipcRenderer } = require('electron');
const { dialog } = require('@electron/remote');
var jrQrcode = require('jr-qrcode');
var JsBarcode = require('jsbarcode');
const { clipboard } = require('electron');
// 使用平台桥(桌面端由 design2.html 先加载 lib/platform/electron.js 注入)
var remote = typeof window !== 'undefined' ? window.remote : null;
var path = typeof window !== 'undefined' ? window.path : null;
var exePath = typeof window !== 'undefined' ? window.exePath : '';
var ipcRenderer = typeof window !== 'undefined' ? window.ipcRenderer : null;
var dialog = typeof window !== 'undefined' ? window.dialog : null;
var clipboard = typeof window !== 'undefined' ? window.clipboard : null;
var jrQrcode, JsBarcode;
if (typeof require !== 'undefined') {
try { jrQrcode = require('jr-qrcode'); } catch (e) {}
try { JsBarcode = require('jsbarcode'); } catch (e) {}
}
// 发送IPC消息
ipcRenderer.send('get-sys-fonts');
ipcRenderer.send('get-scale-rate');
if (ipcRenderer) {
ipcRenderer.send('get-sys-fonts');
ipcRenderer.send('get-scale-rate');
}
// 使用layui
layui.use(['layer', 'slider', 'form', 'colorpicker'], function () {
let myDate = new Date();
let s_lan = "";
ipcRenderer.send('get-sys-language');
if (ipcRenderer) ipcRenderer.send('get-sys-language');
var $ = layui.$;
var layer = layui.layer;
@@ -81,7 +85,7 @@ ipcRenderer.send('get-scale-rate');
var colorpicker = layui.colorpicker;
// 历史记录文件路径(供 output.js 使用)
var fullPath = path.join(exePath, 'data.json');
var fullPath = (path && exePath) ? path.join(exePath, 'data.json') : (typeof window !== 'undefined' ? window.fullPath : '');
// 确保 fullPath 在全局作用域中可用(用于 .jsc 文件加载)
if (typeof window !== 'undefined') {
window.fullPath = fullPath;
@@ -482,25 +486,39 @@ ipcRenderer.send('get-scale-rate');
var ctx2 = window.ctx2;
let is_bgi_add = window.is_bgi_add;
// 加载design2模块
const fs = require('fs');
const appPath = (() => { try { return remote.app.getAppPath(); } catch(e) { return __dirname || process.cwd(); } })();
// 加载 design2 模块:网页端在回调内动态加载(保证 $ 已存在),桌面端用 require+eval
var fs = typeof window !== 'undefined' ? window.fs : null;
var appPath = (remote && remote.app && remote.app.getAppPath) ? remote.app.getAppPath() : (typeof __dirname !== 'undefined' ? __dirname : '.');
if (typeof require === 'undefined') {
function loadScript(src, next) {
var s = document.createElement('script');
s.src = src;
s.onload = s.onerror = function () { if (typeof next === 'function') next(); };
document.body.appendChild(s);
}
loadScript('./lib/design2/output.js', function () {
loadScript('./lib/design2/core.js', function () {
loadScript('./lib/design2/ui.js', function () {});
});
});
return;
}
const loadModule = (moduleName) => {
const jsPath = path.join(appPath, 'lib', 'design2', `${moduleName}.js`);
// 加载 .js 源文件
if (!path || !fs) return;
const jsPath = path.join(appPath, 'lib', 'design2', moduleName + '.js');
if (fs.existsSync(jsPath)) {
try {
eval(fs.readFileSync(jsPath, 'utf8'));
} catch (e) {
alert(`加载 ${moduleName} 失败!\n\n错误: ${e.message}`);
alert('加载 ' + moduleName + ' 失败!\n\n错误: ' + e.message);
}
} else {
alert(`文件不存在!\n\n请检查:${jsPath}`);
alert('文件不存在!\n\n请检查:' + jsPath);
}
};
loadModule('output'); // 先加载 output,因为 core 中的 addBackground() 需要调用 output 中的 open()
loadModule('output');
loadModule('core');
loadModule('ui');
});
+2571 -2534
View File
File diff suppressed because it is too large Load Diff
+189 -109
View File
File diff suppressed because one or more lines are too long
+63 -49
View File
@@ -1283,32 +1283,34 @@ function saveAs(op1, callback) {
let o = { f: j, b: b, fo: objs1, bo: objs2 }
let con_o = Object.assign(o, op1)
dialog
.showSaveDialog({
title: language_str('saveFile'), //'保存文件'
filters: [{ name: 'Soon File Type', extensions: ['soon'] }]
})
.then((result) => {
if (result.filePath == '') {
return
con_o.soonType = 2
var content = JSON.stringify(con_o)
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null);
if (!dialogApi) return;
dialogApi.showSaveDialog({ title: language_str('saveFile'), filters: [{ name: 'Soon File Type', extensions: ['soon'] }] })
.then(function(result) {
var fp = result.filePath || (result.fileHandle && result.fileHandle.name);
if (!fp && !result.fileHandle) return;
if (typeof fp === 'string' && fp.substring(fp.length - 5).indexOf('.') === -1) fp += '.soon';
if (window.platformBridge && window.platformBridge.writeFile) {
window.platformBridge.writeFile(result.fileHandle || fp, content).then(function() {
openAs.name = fp;
layer.msg(language_str('saveSucc') + openAs.name);
if (typeof window.saveHistory === 'function') window.saveHistory();
if (callback && typeof callback === 'function') callback();
});
return;
}
if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) {
result.filePath += '.soon'
}
let fs = require('fs')
con_o.soonType = 2
fs.writeFileSync(result.filePath, JSON.stringify(con_o), 'utf8')
openAs.name = result.filePath
layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至'
if (typeof window.saveHistory === 'function') {
window.saveHistory();
}
if (callback && typeof callback === 'function') {
callback()
var fs = typeof require !== 'undefined' ? require('fs') : null;
if (fs) {
fs.writeFileSync(fp, content, 'utf8');
openAs.name = fp;
layer.msg(language_str('saveSucc') + openAs.name);
if (typeof window.saveHistory === 'function') window.saveHistory();
if (callback && typeof callback === 'function') callback();
}
})
.catch((err) => {
})
.catch(function(err) {});
}
// save 函数已在 output.js 中定义
// 复制
@@ -1354,49 +1356,60 @@ $('#open').click(function () {
}
)
function OpenDialog() {
dialog
.showOpenDialog({
title: '请选择文件',
buttonLabel: language_str('comf'),
filters: [{ name: 'Soon File Type', extensions: ['soon'] }]
})
.then((result) => {
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showOpenDialog ? { showOpenDialog: function(opts) { return window.platformBridge.showOpenDialog(opts); } } : null);
if (!dialogApi) return;
dialogApi.showOpenDialog({ title: '请选择文件', buttonLabel: language_str('comf'), filters: [{ name: 'Soon File Type', extensions: ['soon'] }] })
.then(function(result) {
if (result.canceled) return;
let filePath = result.filePaths[0];
let fs = require('fs');
let fsData = fs.readFileSync(filePath);
let j = JSON.parse(fsData.toString());
let type = j.soonType ? j.soonType : j.backBlackPic ? 2 : 1;
if (type == 1) {
ipcRenderer.send('open-design-page', filePath, type);
var filePath = result.filePaths && result.filePaths[0];
var file = result.files && result.files[0];
if (file && window.platformBridge && window.platformBridge.readJsonFile) {
window.platformBridge.readJsonFile(file).then(function(j) {
if (!j) return;
var type = j.soonType ? j.soonType : (j.backBlackPic ? 2 : 1);
if (type == 1 && window.platformBridge.openDesignPage) {
window.platformBridge.openDesignPage(file.name, 1);
return;
}
if (typeof window.openFile === 'function') window.openFile(file.name, j);
});
return;
}
// 调用 output.js 中的 openFile 函数
if (typeof window.openFile === 'function') {
window.openFile(filePath);
if (!filePath) return;
var fs = typeof require !== 'undefined' ? require('fs') : null;
if (!fs) return;
var fsData = fs.readFileSync(filePath);
var j = JSON.parse(fsData.toString());
var type = j.soonType ? j.soonType : (j.backBlackPic ? 2 : 1);
if (type == 1) {
if (window.platformBridge && window.platformBridge.openDesignPage) window.platformBridge.openDesignPage(filePath, type);
else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-design-page', filePath, type);
return;
}
if (typeof window.openFile === 'function') window.openFile(filePath);
})
.catch((err) => {
})
.catch(function(err) {});
}
})
function goFirstPage() {
if (window.platformBridge && window.platformBridge.openFirstPage) window.platformBridge.openFirstPage();
else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-first-page');
}
$('#new').click(function () {
if (step.val == 0) {
ipcRenderer.send('open-first-page');
goFirstPage();
return;
}
layer.confirm(
'<font style="color:black">' + language_str('whetherSave') + '</font>',
function (index) {
if (typeof window.output === 'function') {
window.output(() => ipcRenderer.send('open-first-page'));
window.output(goFirstPage);
}
layer.close(index);
},
function (index) {
ipcRenderer.send('open-first-page');
goFirstPage();
}
)
})
@@ -2718,8 +2731,9 @@ $("#about").click(async function () {
'</div>'
});
})
$("#help").click(() => {
ipcRenderer.send('open-help-file');
$("#help").click(function() {
if (window.platformBridge && window.platformBridge.openHelp) window.platformBridge.openHelp();
else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-help-file');
});
// ===========================================================
+302 -268
View File
@@ -1,269 +1,303 @@
const { ipcRenderer } = require('electron');
const { dialog } = require('@electron/remote');
// 辅助函数:获取文件名 (保留扩展名)
function get_filename(filePath) {
if (!filePath) return "";
return filePath.split(/[/\\]/).pop();
}
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" },
"vers": { zh: "版本:", ozh: "版本:", en: "Version:" },
"copyright": { zh: "版权所有 © 2023", ozh: "版權所有 © 2023", en: "Copyright © 2023" },
"about": { zh: "关于 SoonDesign", ozh: "關於 SoonDesign", en: "About SoonDesign" },
"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?" }
}
return t[str][s_lan] || t[str]['zh'];
}
layui.use(['layer', 'form', 'jquery'], function () {
var $ = layui.$;
var layer = layui.layer
, form = layui.form;
if (window.sysAPI && window.sysAPI.getAppVersion) {
window.sysAPI.getAppVersion().then(ver => {
$("#app-version").text("v" + ver);
}).catch(e => console.log(e));
}
ipcRenderer.send('get-sys-language');
ipcRenderer.on('close', (event, message) => {
ipcRenderer.send('run-close');
})
function langua_ge(lan = 'zh') {
$("[language='m']").each(function (i) {
$(this).html($(this).attr(lan));
})
$("[language='t']").each(function (i) {
$(this).attr("title", $(this).attr(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();
});
ipcRenderer.on('sys-lan', (event, data) => {
let lang = localStorage.getItem("lang");
if (lang) {
s_lan = lang;
} else {
if (data.startsWith('zh')) {
s_lan = data === 'zh-TW' ? 'ozh' : 'zh';
} else {
s_lan = 'en';
}
}
langua_ge(s_lan);
$("#language_select").val(s_lan);
localStorage.setItem("lang", s_lan);
loadHistory();
});
// ==========================================
// 加载列表
// ==========================================
async function loadHistory() {
try {
const j = await window.sysAPI.readHistory();
let h = "";
let needUpdate = false;
for (let item of j.history) {
let src = "";
let fileExists = true;
let imgStyle = "";
let realType = item.type;
const soonData = await window.sysAPI.readJsonFile(item.path);
if (soonData) {
src = soonData.frontDisplayPic;
let fileType = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1);
if (item.type != fileType) {
item.type = fileType;
realType = fileType;
needUpdate = true;
}
} else {
fileExists = false;
if (realType && (realType == 2 || realType == "2")) {
src = "./public/images/bg_2.png";
} else {
src = "./public/images/bg_1.png";
}
imgStyle = "opacity: 0.6; filter: grayscale(100%);";
}
let displayName = get_filename(item.path);
let cardTitle = item.path;
let cardStyle = "";
if (!fileExists) {
displayName += ` <span style='color:#ff5722;font-size:12px;'>${language_str("lost")}</span>`;
cardTitle = language_str("clickToDelete");
cardStyle = "border: 1px dashed #ff5722;";
}
h += `<div class="card" data="${item.path}" title="${cardTitle}" style="${cardStyle}">
<div class="rect ${realType == 2 ? 'rect1' : ''}">
<img src="${src}" style="width:190px; ${imgStyle}">
<div class="tip">${displayName}</div>
</div>
</div>`
}
$(".card-list").html(h);
if (needUpdate) {
await window.sysAPI.writeHistory({ history: j.history });
}
} catch (e) {
console.error("加载历史记录出错", e);
}
}
// 按钮事件
$("#openfile").click(function () { OpenDialog(); });
$("#new1").click(function () {
// 这里如果你也想改成和保存一样的弹窗逻辑,可以在这里加
ipcRenderer.send('open-design-page', "", 1);
});
$("#new2").click(function () { ipcRenderer.send('open-design-page', "", 2); });
$("#about").click(async function () {
let version = "3.0.0";
try {
if (window.sysAPI && window.sysAPI.getAppVersion) {
const v = await window.sysAPI.getAppVersion();
version = "v" + v;
}
} catch (e) {}
layer.open({
type: 1
, title: language_str("about")
, area: '450px;'
, id: 'LAY_layuipro'
, moveType: 1
, content: '<div style="padding-top:20px;padding-bottom:30px;background-color: #32363A; color: #fff; font-weight: 300;text-align:center;">' +
'<div style="width: 120px;margin: 0 auto;"><img style="width: 120px;" src="./public/images/IconA.png"></div>' +
'<div style="font-size:24px;">Soon Design</div>' +
'<div style="font-size:14px;">' + language_str("vers") + ' ' + version + '</div>' +
'<div style="font-size:14px;">' + language_str("copyright") + '</div>' +
'</div>'
});
});
// ==========================================
// 点击事件 (修改后:完全匹配“保存”弹窗样式)
// ==========================================
$(".card-list").on("click", '.card', async function () {
let filePath = $(this).attr("data");
// 再次检查文件是否存在
const soonData = await window.sysAPI.readJsonFile(filePath);
if (soonData) {
// 1. 文件存在 -> 正常打开
let type = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1);
const currentData = await window.sysAPI.readHistory();
let newHistory = currentData.history.filter(item => item.path !== filePath);
newHistory.unshift({ path: filePath, time: Date.now(), type: type });
await window.sysAPI.writeHistory({ history: newHistory });
ipcRenderer.send('open-design-page', filePath, type);
} else {
// 2. 文件不存在 -> 警告确认
// 【关键修改】
// - 纯文字,左对齐 (text-align: left)
// - 适当的 padding
// - 去除所有图标
let contentHtml = `
<div style="padding: 20px 20px; text-align: left; color: #333; font-size: 14px;">
${language_str("delContent")}
</div>
`;
// 使用 layer.open
layer.open({
type: 1,
title: language_str("delTitle"), // 标题:警告
content: contentHtml,
btn: [language_str("comfirm"), language_str("cancel")],
btnAlign: 'r', // 【关键修改】按钮右对齐
area: ['300px', 'auto'], // 【关键修改】宽度设为 300px,标准小窗口
resize: false,
shadeClose: true,
yes: async function(index){
const currentData = await window.sysAPI.readHistory();
const newHistory = currentData.history.filter(item => item.path !== filePath);
if (newHistory.length !== currentData.history.length) {
await window.sysAPI.writeHistory({ history: newHistory });
layer.msg(language_str("deleted"), { icon: 1, time: 1000 });
loadHistory();
}
layer.close(index);
}
});
}
});
function OpenDialog() {
dialog.showOpenDialog({
title: language_str("selectFile"),
buttonLabel: language_str("comfirm"),
filters: [
{ name: language_str("SDFile"), extensions: ['soon'] },
]
}).then(async (result) => {
if (result.canceled || !result.filePaths[0]) return;
let filePath = result.filePaths[0];
const soonData = await window.sysAPI.readJsonFile(filePath);
if (!soonData) {
layer.msg(language_str("noFile"));
return;
}
let type = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1);
const currentData = await window.sysAPI.readHistory();
let newHistory = currentData.history.filter(item => item.path !== filePath);
newHistory.unshift({ path: filePath, time: Date.now(), type: type });
await window.sysAPI.writeHistory({ history: newHistory });
ipcRenderer.send('open-design-page', filePath, type);
loadHistory();
}).catch(err => {
console.log(err)
})
}
// 使用平台桥(桌面端由 lib/platform/electron.js 注入 window.ipcRenderer / window.dialog / window.sysAPI
var ipcRenderer = typeof window !== 'undefined' ? window.ipcRenderer : null;
var dialog = typeof window !== 'undefined' ? window.dialog : null;
// 辅助函数:获取文件名 (保留扩展名)
function get_filename(filePath) {
if (!filePath) return "";
return filePath.split(/[/\\]/).pop();
}
// 辅助函数:HTML 属性转义,避免 path 中的 "&<> 破坏属性导致点击传参错误
function escapeAttr(s) {
if (s == null) return "";
return String(s).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
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" },
"vers": { zh: "版本:", ozh: "版本:", en: "Version:" },
"copyright": { zh: "版权所有 © 2023", ozh: "版權所有 © 2023", en: "Copyright © 2023" },
"about": { zh: "关于 SoonDesign", ozh: "關於 SoonDesign", en: "About SoonDesign" },
"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?" }
}
return t[str][s_lan] || t[str]['zh'];
}
layui.use(['layer', 'form', 'jquery'], function () {
var $ = layui.$;
var layer = layui.layer
, form = layui.form;
if (window.sysAPI && window.sysAPI.getAppVersion) {
window.sysAPI.getAppVersion().then(ver => {
$("#app-version").text("v" + ver);
}).catch(e => console.log(e));
}
if (ipcRenderer) {
ipcRenderer.send('get-sys-language');
ipcRenderer.on('close', (event, message) => {
ipcRenderer.send('run-close');
});
}
function langua_ge(lan = 'zh') {
$("[language='m']").each(function (i) {
$(this).html($(this).attr(lan));
})
$("[language='t']").each(function (i) {
$(this).attr("title", $(this).attr(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();
});
if (ipcRenderer && ipcRenderer.on) {
ipcRenderer.on('sys-lan', (event, data) => {
let lang = localStorage.getItem("lang");
if (lang) {
s_lan = lang;
} else {
if (data && data.startsWith('zh')) {
s_lan = data === 'zh-TW' ? 'ozh' : 'zh';
} else {
s_lan = 'en';
}
}
langua_ge(s_lan);
$("#language_select").val(s_lan);
localStorage.setItem("lang", s_lan);
loadHistory();
});
}
// ==========================================
// 加载列表
// ==========================================
async function loadHistory() {
try {
if (!window.sysAPI || typeof window.sysAPI.readHistory !== 'function') {
$(".card-list").html("");
return;
}
const j = await window.sysAPI.readHistory();
if (!j || !Array.isArray(j.history)) {
$(".card-list").html("");
return;
}
let h = "";
let needUpdate = false;
for (let item of j.history) {
let src = "";
let fileExists = true;
let imgStyle = "";
let realType = item.type;
const soonData = await window.sysAPI.readJsonFile(item.path);
if (soonData) {
src = soonData.frontDisplayPic;
let fileType = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1);
if (item.type != fileType) {
item.type = fileType;
realType = fileType;
needUpdate = true;
}
} else {
fileExists = false;
if (realType && (realType == 2 || realType == "2")) {
src = "./public/images/bg_2.png";
} else {
src = "./public/images/bg_1.png";
}
imgStyle = "opacity: 0.6; filter: grayscale(100%);";
}
// 网页端历史 path 为 soondesign_session:文件名.soon,展示时去掉前缀
const sessionPrefix = 'soondesign_session:';
let displayName = (item.path && item.path.indexOf(sessionPrefix) === 0)
? item.path.substring(sessionPrefix.length)
: get_filename(item.path);
let cardTitle = (item.path && item.path.indexOf(sessionPrefix) === 0)
? displayName
: item.path;
let cardStyle = "";
if (!fileExists) {
displayName += ` <span style='color:#ff5722;font-size:12px;'>${language_str("lost")}</span>`;
cardTitle = language_str("clickToDelete");
cardStyle = "border: 1px dashed #ff5722;";
}
h += `<div class="card" data-file="${escapeAttr(item.path)}" title="${escapeAttr(cardTitle)}" style="${cardStyle}">
<div class="rect ${realType == 2 ? 'rect1' : ''}">
<img src="${src}" style="width:190px; ${imgStyle}">
<div class="tip">${displayName}</div>
</div>
</div>`
}
$(".card-list").html(h);
if (needUpdate) {
await window.sysAPI.writeHistory({ history: j.history });
}
} catch (e) {
console.error("加载历史记录出错", e);
}
}
loadHistory();
// 按钮事件
$("#openfile").click(function () { OpenDialog(); });
$("#new1").click(function () {
if (window.platformBridge && window.platformBridge.openDesignPage) window.platformBridge.openDesignPage("", 1);
else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-design-page', "", 1);
});
$("#new2").click(function () {
if (window.platformBridge && window.platformBridge.openDesignPage) window.platformBridge.openDesignPage("", 2);
else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-design-page', "", 2);
});
$("#about").click(async function () {
let version = "3.0.0";
try {
if (window.sysAPI && window.sysAPI.getAppVersion) {
const v = await window.sysAPI.getAppVersion();
version = "v" + v;
}
} catch (e) {}
layer.open({
type: 1
, title: language_str("about")
, area: '450px;'
, id: 'LAY_layuipro'
, moveType: 1
, content: '<div style="padding-top:20px;padding-bottom:30px;background-color: #32363A; color: #fff; font-weight: 300;text-align:center;">' +
'<div style="width: 120px;margin: 0 auto;"><img style="width: 120px;" src="./public/images/IconA.png"></div>' +
'<div style="font-size:24px;">Soon Design</div>' +
'<div style="font-size:14px;">' + language_str("vers") + ' ' + version + '</div>' +
'<div style="font-size:14px;">' + language_str("copyright") + '</div>' +
'</div>'
});
});
// ==========================================
// 点击事件 (修改后:完全匹配“保存”弹窗样式)
// ==========================================
$(".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) {
// 1. 文件存在 -> 正常打开
let type = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1);
const currentData = await window.sysAPI.readHistory();
let newHistory = currentData.history.filter(item => item.path !== filePath);
newHistory.unshift({ path: filePath, time: Date.now(), type: type });
await window.sysAPI.writeHistory({ history: newHistory });
if (window.platformBridge && window.platformBridge.openDesignPage) window.platformBridge.openDesignPage(filePath, type);
else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-design-page', filePath, type);
} else {
// 2. 文件不存在 -> 警告确认
// 【关键修改】
// - 纯文字,左对齐 (text-align: left)
// - 适当的 padding
// - 去除所有图标
let contentHtml = `
<div style="padding: 20px 20px; text-align: left; color: #333; font-size: 14px;">
${language_str("delContent")}
</div>
`;
// 使用 layer.open
layer.open({
type: 1,
title: language_str("delTitle"), // 标题:警告
content: contentHtml,
btn: [language_str("comfirm"), language_str("cancel")],
btnAlign: 'r', // 【关键修改】按钮右对齐
area: ['300px', 'auto'], // 【关键修改】宽度设为 300px,标准小窗口
resize: false,
shadeClose: true,
yes: async function(index){
const currentData = await window.sysAPI.readHistory();
const newHistory = currentData.history.filter(item => item.path !== filePath);
if (newHistory.length !== currentData.history.length) {
await window.sysAPI.writeHistory({ history: newHistory });
layer.msg(language_str("deleted"), { icon: 1, time: 1000 });
loadHistory();
}
layer.close(index);
}
});
}
});
function OpenDialog() {
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (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 filePath = result.filePaths && result.filePaths[0];
var file = (result.files && result.files[0]) || result.file;
var soonData;
if (file && file.text) {
filePath = file.name || 'design.soon';
soonData = await window.sysAPI.readJsonFile(file);
// 网页端:将内容存到 sessionStorage,设计页通过 sessionKey 读取
if (soonData && typeof sessionStorage !== 'undefined' && window.platformBridge) {
try {
sessionStorage.setItem('soondesign_session:' + filePath, JSON.stringify(soonData));
filePath = 'soondesign_session:' + filePath;
} catch (e) {}
}
} else if (filePath) {
soonData = await window.sysAPI.readJsonFile(filePath);
}
if (!soonData) {
if (!filePath && !file) return;
layer.msg(language_str("noFile"));
return;
}
var type = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1);
var currentData = await window.sysAPI.readHistory();
var newHistory = currentData.history.filter(function(item) { return item.path !== filePath; });
newHistory.unshift({ path: filePath, time: Date.now(), type: type });
await window.sysAPI.writeHistory({ history: newHistory });
if (window.platformBridge && window.platformBridge.openDesignPage) window.platformBridge.openDesignPage(filePath, type);
else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-design-page', filePath, type);
loadHistory();
}).catch(function(err) { console.log(err); });
}
});
+44
View File
@@ -0,0 +1,44 @@
/**
* 平台抽象层 - 统一接口定义
* 桌面端由 lib/platform/electron.js 实现网页端由 lib/platform/web.js 实现
* 业务代码通过 window.platformBridge 或兼容的 window.sysAPI / window.dialog 调用
*/
(function () {
'use strict';
// 若已存在(由 electron 或 web 注入),则不覆盖
if (typeof window.platformBridge !== 'undefined') {
return;
}
// 默认空实现,避免未注入时报错(仅作占位,实际运行前必须注入 electron 或 web 实现)
function notImplemented() {
console.warn('[platform] bridge not injected');
return Promise.reject(new Error('Platform bridge not available'));
}
window.platformBridge = {
readHistory: notImplemented,
writeHistory: notImplemented,
readJsonFile: notImplemented,
showOpenDialog: notImplemented,
showSaveDialog: notImplemented,
writeFile: notImplemented,
readFile: notImplemented,
getAppVersion: function () { return Promise.resolve('0.0.0'); },
getLocale: function () { return 'zh'; },
getUserDataPath: function () { return ''; },
getSystemFonts: function () { return Promise.resolve([]); },
openDesignPage: function () {},
openFirstPage: function () {},
onClose: function () {},
runClose: function () {},
openHelp: function () {},
printPdf: notImplemented,
getScaleRate: function () { return typeof window !== 'undefined' ? (window.devicePixelRatio || 1) : 1; },
clipboard: {
readText: function () { return Promise.resolve(''); },
writeText: function () { return Promise.resolve(); }
}
};
})();
+145
View File
@@ -0,0 +1,145 @@
/**
* 平台抽象层 - Electron 实现
* 仅在存在 require 且可加载 electron 时使用封装 IPCdialogfsremote
*/
(function () {
'use strict';
if (typeof require === 'undefined') {
return;
}
var ipcRenderer, dialog, path, fs, remote, clipboard;
try {
ipcRenderer = require('electron').ipcRenderer;
dialog = require('@electron/remote').dialog;
path = require('path');
fs = require('fs');
remote = require('@electron/remote');
clipboard = require('electron').clipboard;
} catch (e) {
console.warn('[platform] Electron modules not available', e);
return;
}
var exePath = remote.app.getPath('userData');
var historyFilePath = path.join(exePath, 'data.json');
var bridge = {
readHistory: function () {
return ipcRenderer.invoke('history:read');
},
writeHistory: function (data) {
return ipcRenderer.invoke('history:write', data);
},
readJsonFile: function (filePath) {
if (!filePath) return Promise.resolve(null);
return ipcRenderer.invoke('file:read-json', filePath);
},
showOpenDialog: function (options) {
return dialog.showOpenDialog(options).then(function (result) {
return { canceled: result.canceled, filePaths: result.filePaths };
});
},
showSaveDialog: function (options) {
return dialog.showSaveDialog(options);
},
writeFile: function (filePath, content) {
return new Promise(function (resolve, reject) {
var buf = Buffer.isBuffer(content) ? content : (typeof content === 'string' ? Buffer.from(content, 'utf8') : Buffer.from(content));
fs.writeFile(filePath, buf, 'utf8', function (err) {
if (err) reject(err);
else resolve();
});
});
},
writeFileSync: function (filePath, content) {
fs.writeFileSync(filePath, content, 'utf8');
},
readFile: function (filePath) {
return new Promise(function (resolve, reject) {
fs.readFile(filePath, function (err, data) {
if (err) reject(err);
else resolve(data);
});
});
},
readFileSync: function (filePath) {
return fs.readFileSync(filePath);
},
existsSync: function (p) {
return fs.existsSync(p);
},
getAppVersion: function () {
return ipcRenderer.invoke('app:get-version');
},
getLocale: function () {
return remote.app.getLocale();
},
getUserDataPath: function () {
return exePath;
},
getSystemFonts: function () {
return new Promise(function (resolve, reject) {
ipcRenderer.once('font-list', function (ev, fonts) {
resolve(fonts || []);
});
ipcRenderer.send('get-sys-fonts');
setTimeout(function () {
resolve([]);
}, 10000);
});
},
openDesignPage: function (file, type) {
ipcRenderer.send('open-design-page', file || 'empty', type || '1');
},
openFirstPage: function () {
ipcRenderer.send('open-first-page');
},
onClose: function (callback) {
ipcRenderer.on('close', callback);
},
runClose: function () {
ipcRenderer.send('run-close');
},
openHelp: function () {
ipcRenderer.send('open-help-file');
},
printPdf: function (urlOrBlob) {
ipcRenderer.send('print-pdf', urlOrBlob);
},
getScaleRate: function () {
return typeof window !== 'undefined' && window.devicePixelRatio ? window.devicePixelRatio : 1;
},
clipboard: {
readText: function () {
return Promise.resolve(clipboard.readText());
},
writeText: function (text) {
clipboard.writeText(text);
return Promise.resolve();
}
}
};
window.platformBridge = bridge;
// 兼容现有命名
window.sysAPI = {
readHistory: bridge.readHistory,
writeHistory: bridge.writeHistory,
readJsonFile: bridge.readJsonFile,
getAppVersion: bridge.getAppVersion
};
window.dialog = {
showOpenDialog: bridge.showOpenDialog,
showSaveDialog: bridge.showSaveDialog
};
window.path = path;
window.fs = fs;
window.remote = remote;
window.ipcRenderer = ipcRenderer;
window.clipboard = clipboard;
window.exePath = exePath;
window.fullPath = historyFilePath;
})();
+341
View File
@@ -0,0 +1,341 @@
/**
* 平台抽象层 - 网页实现
* 使用 LocalStorageFile PickerBlob 下载navigator Web API
*/
(function () {
'use strict';
var HISTORY_KEY = 'soondesign_history';
var VERSION = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '3.2.101';
/** 站点目录前缀(支持部署在子目录 / 宝塔子路径):去掉入口 HTML 文件名 */
function getAppBasePathname() {
var p = (typeof window !== 'undefined' && window.location && window.location.pathname) ? window.location.pathname : '/';
// Nginx/宝塔可能把页面写成 /design2.web.html/ ,末尾斜杠会导致匹配不到 .html,误判站点根为「目录 URL」→ 回首页 404
while (p.length > 1 && p.endsWith('/')) {
p = p.slice(0, -1);
}
if (/\.web\.html$/i.test(p) || /\.html$/i.test(p)) {
p = p.replace(/[^/]+$/, '');
}
if (!p.endsWith('/')) p += '/';
return p;
}
// API 相对站点根目录(与 api/README 中「相对网站根目录的 /api/」一致)
var SERVER_API_BASE = (typeof window !== 'undefined' && window.location)
? (window.location.origin + getAppBasePathname() + 'api/')
: '/api/';
function navigateToPage(pathWithQuery) {
try {
location.href = new URL(pathWithQuery, window.location.href).href;
} catch (e) {
location.href = pathWithQuery;
}
}
function getLocale() {
var lang = typeof navigator !== 'undefined' ? (navigator.language || navigator.browserLanguage || '') : '';
if (lang.indexOf('zh') === 0) return lang.indexOf('TW') >= 0 ? 'ozh' : 'zh';
return 'en';
}
function getSystemFonts() {
var list = [
'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Georgia',
'Impact', 'Microsoft YaHei', 'SimHei', 'SimSun', 'KaiTi', 'FangSong',
'Times New Roman', 'Trebuchet MS', 'Verdana', 'PingFang SC', 'Hiragino Sans GB'
];
if (typeof document !== 'undefined' && document.fonts && document.fonts.forEach) {
var set = {};
document.fonts.forEach(function (f) {
var name = (f.family || '').replace(/^["']|["']$/g, '');
if (name) set[name] = 1;
});
list = Object.keys(set).length ? Object.keys(set).sort() : list;
}
return Promise.resolve(list);
}
var bridge = {
readHistory: function () {
try {
var raw = localStorage.getItem(HISTORY_KEY);
return Promise.resolve(raw ? JSON.parse(raw) : { history: [] });
} catch (e) {
return Promise.resolve({ history: [] });
}
},
writeHistory: function (data) {
try {
localStorage.setItem(HISTORY_KEY, JSON.stringify(data));
return Promise.resolve({ success: true });
} catch (e) {
return Promise.resolve({ success: false, error: e.message });
}
},
readJsonFile: function (pathOrHandle) {
if (!pathOrHandle) return Promise.resolve(null);
if (typeof pathOrHandle === 'object' && pathOrHandle.text) {
return pathOrHandle.text().then(function (t) {
try { return JSON.parse(t); } catch (e) { return null; }
});
}
var key = typeof pathOrHandle === 'string' ? pathOrHandle : '';
if (key.indexOf('soondesign_session:') === 0) {
try {
var j = sessionStorage.getItem(key);
if (!j && typeof localStorage !== 'undefined') j = localStorage.getItem(key);
return Promise.resolve(j ? JSON.parse(j) : null);
} catch (e) { return Promise.resolve(null); }
}
// 服务器文件:从服务器读取
if (key && key.indexOf('.soon') > 0 && typeof fetch !== 'undefined') {
var isServerEnv = typeof window !== 'undefined' && window.location &&
(window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1' && window.location.protocol !== 'file:');
if (isServerEnv) {
return fetch(SERVER_API_BASE + 'read_file.php?fileName=' + encodeURIComponent(key))
.then(function(response) {
if (!response.ok) return null;
return response.json();
})
.catch(function() { return null; });
}
}
return Promise.resolve(null);
},
showOpenDialog: function (options) {
return new Promise(function (resolve) {
var input = document.createElement('input');
input.type = 'file';
// 根据 options.filters 动态设置 accept
if (options && options.filters && Array.isArray(options.filters) && options.filters.length > 0) {
var acceptList = [];
options.filters.forEach(function(filter) {
if (filter.extensions && Array.isArray(filter.extensions)) {
filter.extensions.forEach(function(ext) {
// 移除点号(如果有),然后添加点号前缀
var cleanExt = ext.replace(/^\./, '');
// 图片类型使用 image/* MIME 类型,其他使用扩展名
if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg'].indexOf(cleanExt.toLowerCase()) >= 0) {
var mimeType = 'image/' + (cleanExt.toLowerCase() === 'jpg' ? 'jpeg' : cleanExt.toLowerCase());
if (acceptList.indexOf(mimeType) < 0) acceptList.push(mimeType);
} else {
var extWithDot = '.' + cleanExt;
if (acceptList.indexOf(extWithDot) < 0) acceptList.push(extWithDot);
}
});
}
});
input.accept = acceptList.length > 0 ? acceptList.join(',') : '';
} else {
// 默认:.soon 文件
input.accept = '.soon,application/json';
}
input.style.display = 'none';
input.onchange = function () {
var f = input.files && input.files[0];
document.body.removeChild(input);
if (!f) resolve({ canceled: true });
else resolve({ canceled: false, filePaths: [], files: [f], file: f });
};
document.body.appendChild(input);
input.click();
});
},
showSaveDialog: function (options) {
var raw = (options && options.defaultPath) ? options.defaultPath : '';
var defaultName = raw
? (raw.indexOf('soondesign_session:') === 0 ? raw.substring('soondesign_session:'.length) : raw.split(/[/\\]/).pop())
: 'design.soon';
return new Promise(function (resolve) {
if (typeof window.showSaveFilePicker !== 'function') {
var name = typeof prompt === 'function' ? prompt('保存为文件名(如 xxx.soon', defaultName) : defaultName;
if (name === null) {
resolve({ canceled: true });
return;
}
var fp = (name && String(name).trim()) ? String(name).trim() : defaultName;
if (fp.indexOf('.soon') === -1 || fp.slice(-5).toLowerCase() !== '.soon') {
fp = (fp.replace(/\.soon$/i, '') || 'design') + '.soon';
}
resolve({ canceled: false, filePath: fp, useDownload: true });
return;
}
window.showSaveFilePicker({
suggestedName: defaultName,
types: [{ description: 'SoonDesign', accept: { 'application/json': ['.soon'] } }]
}).then(function (handle) {
var filePath = (handle && handle.name) ? handle.name : defaultName;
resolve({ canceled: false, filePath: filePath, fileHandle: handle });
}).catch(function () {
resolve({ canceled: true });
});
});
},
writeFile: function (pathOrHandle, content) {
var isBlob = content instanceof Blob;
var str = typeof content === 'string' ? content : (isBlob ? null : (content && content.toString ? content.toString() : ''));
// File System Access API(浏览器原生文件保存)
if (pathOrHandle && pathOrHandle.createWritable) {
return pathOrHandle.createWritable().then(function (w) {
if (isBlob) w.write(content);
else w.write(str);
return w.close();
});
}
// 服务器环境:上传到服务器
var name = typeof pathOrHandle === 'string' ? pathOrHandle : (isBlob ? 'output.pdf' : 'design.soon');
var isServerEnv = typeof window !== 'undefined' && window.location &&
(window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1' && window.location.protocol !== 'file:');
if (isServerEnv && !isBlob && typeof fetch !== 'undefined') {
// 上传到服务器
var formData = new FormData();
formData.append('fileName', name);
formData.append('fileContent', str);
return fetch(SERVER_API_BASE + 'save_file.php', {
method: 'POST',
body: formData
}).then(function(response) {
return response.json();
}).then(function(result) {
if (result.success) {
return Promise.resolve();
} else {
throw new Error(result.error || '保存失败');
}
}).catch(function(err) {
// 如果服务器保存失败,降级为下载
console.warn('服务器保存失败,降级为下载:', err);
var a = document.createElement('a');
a.download = name;
a.href = 'data:application/json;charset=utf-8,' + encodeURIComponent(str);
a.click();
return Promise.resolve();
});
}
// 本地环境或 Blob:触发下载
var a = document.createElement('a');
a.download = name;
if (isBlob) a.href = URL.createObjectURL(content);
else a.href = 'data:application/json;charset=utf-8,' + encodeURIComponent(str);
a.click();
if (isBlob && a.href) URL.revokeObjectURL(a.href);
return Promise.resolve();
},
readFile: function (pathOrHandle) {
if (pathOrHandle && pathOrHandle.getFile) {
return pathOrHandle.getFile().then(function (f) {
return new Promise(function (res, rej) {
var r = new FileReader();
r.onload = function () { res(r.result); };
r.onerror = rej;
r.readAsArrayBuffer(f);
});
});
}
if (pathOrHandle && pathOrHandle.arrayBuffer) {
return pathOrHandle.arrayBuffer();
}
return Promise.reject(new Error('No file'));
},
getAppVersion: function () { return Promise.resolve(VERSION); },
getLocale: getLocale,
getUserDataPath: function () { return ''; },
getSystemFonts: getSystemFonts,
openDesignPage: function (file, type) {
var t = type || 1;
if (file && typeof sessionStorage !== 'undefined') {
try {
sessionStorage.setItem('soondesign_open_file', file);
sessionStorage.setItem('soondesign_open_type', String(t));
} catch (e) {}
}
var fileParam = file ? encodeURIComponent(file) : '';
navigateToPage('design' + t + '.web.html?file=' + fileParam + '&type=' + t);
},
openFirstPage: function () {
var loc = typeof window !== 'undefined' ? window.location : null;
if (!loc) return;
// 显式拼站点根(与 getAppBasePathname 一致),避免「路径末尾斜杠 / URL API」与 navigateToPage('.') 在个别环境下的解析差异
loc.href = loc.origin + getAppBasePathname();
},
onClose: function (callback) {
if (typeof window !== 'undefined') {
window.addEventListener('beforeunload', callback);
}
},
runClose: function () {
if (typeof window !== 'undefined' && window.close) window.close();
},
openHelp: function () {
try {
window.open(new URL('help/User Manual.pdf', window.location.href).href, '_blank');
} catch (e) {
window.open('help/User Manual.pdf', '_blank');
}
},
printPdf: function (urlOrBlob) {
var url = urlOrBlob;
if (urlOrBlob && typeof urlOrBlob === 'object' && !(urlOrBlob instanceof String)) {
url = URL.createObjectURL(urlOrBlob);
}
var w = window.open(url, '_blank');
if (w) w.onload = function () { w.print(); };
},
getScaleRate: function () {
return typeof window !== 'undefined' && window.devicePixelRatio ? window.devicePixelRatio : 1;
},
clipboard: {
readText: function () {
return navigator.clipboard && navigator.clipboard.readText ? navigator.clipboard.readText() : Promise.resolve('');
},
writeText: function (text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text);
}
return Promise.resolve();
}
}
};
window.platformBridge = bridge;
var pathStub = {
join: function () { return [].slice.call(arguments).join('/').replace(/\/+/g, '/'); }
};
window.sysAPI = {
readHistory: bridge.readHistory,
writeHistory: bridge.writeHistory,
readJsonFile: bridge.readJsonFile,
getAppVersion: bridge.getAppVersion
};
window.dialog = {
showOpenDialog: bridge.showOpenDialog,
showSaveDialog: bridge.showSaveDialog
};
window.path = pathStub;
window.fs = null;
window.remote = null;
var loc = getLocale();
window.ipcRenderer = {
send: function (ch, a1, a2) {
if (ch === 'get-sys-language') { window._sysLanPending = true; setTimeout(function () { if (window._sysLanCb && window._sysLanPending) { window._sysLanCb(null, loc); window._sysLanPending = false; } }, 0); }
if (ch === 'open-first-page' && bridge.openFirstPage) { bridge.openFirstPage(); }
if (ch === 'open-design-page' && bridge.openDesignPage && (a1 !== undefined || a2 !== undefined)) { bridge.openDesignPage(a1 || '', a2 || 1); }
if (ch === 'open-help-file' && bridge.openHelp) { bridge.openHelp(); }
if (ch === 'run-close' && bridge.runClose) { bridge.runClose(); }
},
on: function (ch, cb) {
if (ch === 'sys-lan') { window._sysLanCb = cb; if (window._sysLanPending) setTimeout(function () { if (window._sysLanCb) { window._sysLanCb(null, loc); window._sysLanPending = false; } }, 0); }
if (ch === 'close') window._closeCb = cb;
}
};
window.clipboard = bridge.clipboard;
window.exePath = '';
window.fullPath = '';
})();