网页端:平台桥与部署修复(宝塔/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
+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