网页端:平台桥与部署修复(宝塔/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:
+45
-26
@@ -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
@@ -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
@@ -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
File diff suppressed because one or more lines are too long
+44
-26
@@ -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
File diff suppressed because it is too large
Load Diff
+189
-109
File diff suppressed because one or more lines are too long
+63
-49
@@ -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
@@ -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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
||||
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); });
|
||||
}
|
||||
});
|
||||
@@ -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(); }
|
||||
}
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 平台抽象层 - Electron 实现
|
||||
* 仅在存在 require 且可加载 electron 时使用;封装 IPC、dialog、fs、remote。
|
||||
*/
|
||||
(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;
|
||||
})();
|
||||
@@ -0,0 +1,341 @@
|
||||
/**
|
||||
* 平台抽象层 - 网页实现
|
||||
* 使用 LocalStorage、File Picker、Blob 下载、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 = '';
|
||||
})();
|
||||
Reference in New Issue
Block a user