更新优化
This commit is contained in:
+178
-69
@@ -1,4 +1,5 @@
|
||||
function display_func(img1, img2, img3) {
|
||||
// 将 display_func 附加到 window 对象,确保全局可访问
|
||||
window.display_func = function display_func(img1, img2, img3) {
|
||||
$('#base_control').hide()
|
||||
$('#line_control').hide()
|
||||
$('#pic_control').hide()
|
||||
@@ -15,6 +16,27 @@ function display_func(img1, img2, img3) {
|
||||
$('#component_type').text(language_str('bg')) //"背景"
|
||||
canvas1.discardActiveObject().renderAll()
|
||||
canvas2.discardActiveObject().renderAll()
|
||||
|
||||
// 保存所有对象的 selectable 和 evented 状态,确保预览后能恢复
|
||||
const objStates1 = [];
|
||||
const objStates2 = [];
|
||||
canvas1.getObjects().forEach((obj, index) => {
|
||||
if (!obj.isGuideLine) {
|
||||
objStates1[index] = {
|
||||
selectable: obj.selectable,
|
||||
evented: obj.evented
|
||||
};
|
||||
}
|
||||
});
|
||||
canvas2.getObjects().forEach((obj, index) => {
|
||||
if (!obj.isGuideLine) {
|
||||
objStates2[index] = {
|
||||
selectable: obj.selectable,
|
||||
evented: obj.evented
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 过滤掉辅助线
|
||||
let _objs1 = canvas1.getObjects().filter(obj => !obj.isGuideLine)
|
||||
let g1 = []
|
||||
@@ -30,7 +52,7 @@ function display_func(img1, img2, img3) {
|
||||
let list1 = _objs1.filter(obj => !obj.isGuideLine)
|
||||
for (let item of list1) {
|
||||
let idx = getIndex(item, canvas1)
|
||||
|
||||
|
||||
// 安全检查:确保 objs1[idx - 1] 存在且有 type 属性
|
||||
if (idx != 0 && (!objs1[idx - 1] || typeof objs1[idx - 1].type === 'undefined')) {
|
||||
continue
|
||||
@@ -114,10 +136,9 @@ function display_func(img1, img2, img3) {
|
||||
})
|
||||
|
||||
// g3.push(img1)
|
||||
// 预览图缩小尺寸以减小文件大小,使用 multiplier 来缩放整个 Group,保持所有内容完整
|
||||
let url3 = new fabric.Group(g3).toDataURL({
|
||||
format: 'png',
|
||||
multiplier: 0.6, // 缩小到原来的 60%,保持宽高比和所有内容
|
||||
height: 648,
|
||||
width: 1012,
|
||||
top: all_top / zoom,
|
||||
left: all_left / zoom
|
||||
})
|
||||
@@ -142,7 +163,7 @@ function display_func(img1, img2, img3) {
|
||||
let filteredObjs2 = _objs2.filter(obj => !obj.isGuideLine)
|
||||
for (let item of filteredObjs2) {
|
||||
let idx = getIndex(item, canvas2)
|
||||
|
||||
|
||||
// 安全检查:确保 objs2[idx - 1] 存在且有 type 属性
|
||||
if (idx != 0 && (!objs2[idx - 1] || typeof objs2[idx - 1].type === 'undefined')) {
|
||||
continue
|
||||
@@ -225,10 +246,9 @@ function display_func(img1, img2, img3) {
|
||||
width: 1012
|
||||
})
|
||||
// g4.push(img2)
|
||||
// 预览图缩小尺寸以减小文件大小,使用 multiplier 来缩放整个 Group,保持所有内容完整
|
||||
let url4 = new fabric.Group(g4).toDataURL({
|
||||
format: 'png',
|
||||
multiplier: 0.6, // 缩小到原来的 60%,保持宽高比和所有内容
|
||||
height: 648,
|
||||
width: 1012,
|
||||
top: all_top / zoom,
|
||||
left: all_left / zoom
|
||||
})
|
||||
@@ -323,23 +343,53 @@ function display_func(img1, img2, img3) {
|
||||
</div>`,
|
||||
btn: btns, //'导出'
|
||||
btn1: function (index, layero) {
|
||||
savePdf(Buffer.from(pdfBuffer))
|
||||
// 只处理导出(保存PDF)
|
||||
if (typeof window.savePdf === 'function') {
|
||||
window.savePdf(Buffer.from(pdfBuffer));
|
||||
} else {
|
||||
console.error('savePdf 函数未定义!');
|
||||
}
|
||||
},
|
||||
btn2: function () {
|
||||
// 根据 btns[1] 的值决定打印哪一面
|
||||
if (btns[1] === '打印正面') {
|
||||
// 打印正面
|
||||
printJS({ printable: printPath3, type: 'image', style: 'img { width: 100%; height: auto; }' })
|
||||
} else {
|
||||
} else if (btns[1] === '打印背面') {
|
||||
// 打印背面(只有背面时)
|
||||
printJS({ printable: printPath4, type: 'image', style: 'img { width: 100%; height: auto; }' })
|
||||
}
|
||||
},
|
||||
btn3: function () {
|
||||
//打印背面
|
||||
// 打印背面(两面都有时,btn3 对应 btns[2] = '打印背面')
|
||||
printJS({ printable: printPath4, type: 'image', style: 'img { width: 100%; height: auto; }' })
|
||||
},
|
||||
end: function() {
|
||||
// 预览窗口关闭后,恢复所有对象的 selectable 和 evented 状态
|
||||
canvas1.getObjects().forEach((obj, index) => {
|
||||
if (!obj.isGuideLine && objStates1[index]) {
|
||||
obj.set({
|
||||
selectable: objStates1[index].selectable,
|
||||
evented: objStates1[index].evented
|
||||
});
|
||||
}
|
||||
});
|
||||
canvas2.getObjects().forEach((obj, index) => {
|
||||
if (!obj.isGuideLine && objStates2[index]) {
|
||||
obj.set({
|
||||
selectable: objStates2[index].selectable,
|
||||
evented: objStates2[index].evented
|
||||
});
|
||||
}
|
||||
});
|
||||
canvas1.renderAll();
|
||||
canvas2.renderAll();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function output(callback = null, _save = save) {
|
||||
// 将 output 附加到 window 对象,确保全局可访问
|
||||
window.output = function output(callback = null, _save = save) {
|
||||
// 类型改变
|
||||
fabric.Image.fromURL('./public/images/op_2.png', function (i1) {
|
||||
i1.left = background_image.left
|
||||
@@ -380,7 +430,7 @@ function output(callback = null, _save = save) {
|
||||
// _objs1 已经在上面过滤过辅助线了,直接使用
|
||||
for (let item of _objs1) {
|
||||
let idx = getIndex(item, canvas1)
|
||||
|
||||
|
||||
// 安全检查:确保 objs1[idx - 1] 存在且有 type 属性
|
||||
if (idx != 0 && (!objs1[idx - 1] || typeof objs1[idx - 1].type === 'undefined')) {
|
||||
continue
|
||||
@@ -464,10 +514,9 @@ function output(callback = null, _save = save) {
|
||||
left: left / zoom
|
||||
})
|
||||
g3.push(img1)
|
||||
// 预览图缩小尺寸以减小文件大小,使用 multiplier 来缩放整个 Group,保持所有内容完整
|
||||
let url3 = new fabric.Group(g3).toDataURL({
|
||||
format: 'png',
|
||||
multiplier: 0.6, // 缩小到原来的 60%,保持宽高比和所有内容
|
||||
height: 648,
|
||||
width: 1012,
|
||||
top: all_top / zoom,
|
||||
left: all_left / zoom
|
||||
})
|
||||
@@ -491,7 +540,7 @@ function output(callback = null, _save = save) {
|
||||
// _objs2 已经在上面过滤过辅助线了,直接使用
|
||||
for (let item of _objs2) {
|
||||
let idx = getIndex(item, canvas2)
|
||||
|
||||
|
||||
// 安全检查:确保 objs2[idx - 1] 存在且有 type 属性
|
||||
if (idx != 0 && (!objs2[idx - 1] || typeof objs2[idx - 1].type === 'undefined')) {
|
||||
continue
|
||||
@@ -571,10 +620,9 @@ function output(callback = null, _save = save) {
|
||||
width: 1012
|
||||
})
|
||||
g4.push(img2)
|
||||
// 预览图缩小尺寸以减小文件大小,使用 multiplier 来缩放整个 Group,保持所有内容完整
|
||||
let url4 = new fabric.Group(g4).toDataURL({
|
||||
format: 'png',
|
||||
multiplier: 0.6, // 缩小到原来的 60%,保持宽高比和所有内容
|
||||
height: 648,
|
||||
width: 1012,
|
||||
top: all_top / zoom,
|
||||
left: all_left / zoom
|
||||
})
|
||||
@@ -615,7 +663,7 @@ function output(callback = null, _save = save) {
|
||||
// _objs1 已经在上面过滤过辅助线了,直接使用
|
||||
for (let item of _objs1) {
|
||||
let idx = getIndex(item, canvas1)
|
||||
|
||||
|
||||
// 安全检查:确保 objs1[idx - 1] 存在且有 type 属性
|
||||
if (idx != 0 && (!objs1[idx - 1] || typeof objs1[idx - 1].type === 'undefined')) {
|
||||
continue
|
||||
@@ -758,7 +806,7 @@ function output(callback = null, _save = save) {
|
||||
// _objs2 已经在上面过滤过辅助线了,直接使用
|
||||
for (let item of _objs2) {
|
||||
let idx = getIndex(item, canvas2)
|
||||
|
||||
|
||||
// 安全检查:确保 objs2[idx - 1] 存在且有 type 属性
|
||||
if (idx != 0 && (!objs2[idx - 1] || typeof objs2[idx - 1].type === 'undefined')) {
|
||||
continue
|
||||
@@ -902,43 +950,44 @@ function output(callback = null, _save = save) {
|
||||
}
|
||||
|
||||
// 重新生成二维码和条形码(加载文件后调用)
|
||||
function regenerateQrCodesAndBarcodes(canvas, objsArray) {
|
||||
// 将 regenerateQrCodesAndBarcodes 附加到 window 对象,确保全局可访问
|
||||
window.regenerateQrCodesAndBarcodes = function regenerateQrCodesAndBarcodes(canvas, objsArray) {
|
||||
if (!canvas || !objsArray || !Array.isArray(objsArray)) return
|
||||
|
||||
|
||||
const objects = canvas.getObjects()
|
||||
|
||||
|
||||
// 遍历对象(跳过背景图片,索引 0)
|
||||
for (let i = 1; i < objects.length; i++) {
|
||||
const obj = objects[i]
|
||||
if (!obj || obj.type !== 'image') continue
|
||||
|
||||
|
||||
const objIndex = i - 1
|
||||
if (objIndex >= objsArray.length) continue
|
||||
|
||||
|
||||
const objMeta = objsArray[objIndex]
|
||||
if (!objMeta || (objMeta.type !== 8 && objMeta.type !== 9)) continue
|
||||
|
||||
|
||||
// type == 8: 二维码 - 复用 ui.js 中的生成逻辑
|
||||
if (objMeta.type === 8) {
|
||||
const qr = jrQrcode.getQrBase64(objMeta.val || 'https://www.cardsoon.com', {
|
||||
padding: 0,
|
||||
foreground: objMeta.color || '#000000'
|
||||
const qr = jrQrcode.getQrBase64(objMeta.val || 'https://www.cardsoon.com', {
|
||||
padding: 0,
|
||||
foreground: objMeta.color || '#000000'
|
||||
})
|
||||
obj.setSrc(qr, function(img) {
|
||||
obj.setSrc(qr, function (img) {
|
||||
canvas.renderAll()
|
||||
})
|
||||
}
|
||||
// type == 9: 条形码 - 复用 ui.js 中的生成逻辑
|
||||
else if (objMeta.type === 9) {
|
||||
JsBarcode("#barcode", objMeta.val || '123456789', {
|
||||
margin: 0,
|
||||
lineColor: objMeta.color || '#000000',
|
||||
fontSize: objMeta.fontSize || 18,
|
||||
font: objMeta.font || 'Arial'
|
||||
JsBarcode("#barcode", objMeta.val || '123456789', {
|
||||
margin: 0,
|
||||
lineColor: objMeta.color || '#000000',
|
||||
fontSize: objMeta.fontSize || 18,
|
||||
font: objMeta.font || 'Arial'
|
||||
})
|
||||
const barcode = document.getElementById('barcode')
|
||||
const bar = barcode.toDataURL("image/png")
|
||||
obj.setSrc(bar, function(img) {
|
||||
obj.setSrc(bar, function (img) {
|
||||
canvas.renderAll()
|
||||
})
|
||||
}
|
||||
@@ -946,7 +995,8 @@ function regenerateQrCodesAndBarcodes(canvas, objsArray) {
|
||||
}
|
||||
|
||||
// 清理不需要的 src 字段(保留自定义图片的 src)
|
||||
function cleanupSrcFields(jsonObjects, objsArray) {
|
||||
// 将 cleanupSrcFields 附加到 window 对象,确保在 ui.js 中的 saveAs 函数也能访问
|
||||
window.cleanupSrcFields = function cleanupSrcFields(jsonObjects, objsArray) {
|
||||
if (!jsonObjects || !Array.isArray(jsonObjects)) return;
|
||||
if (!objsArray || !Array.isArray(objsArray)) return;
|
||||
|
||||
@@ -972,6 +1022,10 @@ function cleanupSrcFields(jsonObjects, objsArray) {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// 向后兼容
|
||||
function cleanupSrcFields(jsonObjects, objsArray) {
|
||||
return window.cleanupSrcFields(jsonObjects, objsArray);
|
||||
}
|
||||
|
||||
function saveAs(op1, callback) {
|
||||
@@ -990,7 +1044,7 @@ function saveAs(op1, callback) {
|
||||
canvas2.remove(obj)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
let j = canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline'])
|
||||
let b = canvas2.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline'])
|
||||
|
||||
@@ -1009,18 +1063,27 @@ function saveAs(op1, callback) {
|
||||
dialog
|
||||
.showSaveDialog({
|
||||
title: language_str('saveFile'), //'保存文件'
|
||||
filters: [{ name: 'Soon File Type', extensions: ['soon'] }]
|
||||
filters: [{ name: 'Soon File Type', extensions: ['soon'] }],
|
||||
defaultPath: openAs.name || undefined
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.filePath == '') {
|
||||
return
|
||||
}
|
||||
if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) {
|
||||
result.filePath += '.soon'
|
||||
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'
|
||||
}
|
||||
result.filePath = filePath
|
||||
let fs = require('fs')
|
||||
con_o.soonType = 2
|
||||
fs.writeFileSync(result.filePath, JSON.stringify(con_o))
|
||||
fs.writeFileSync(result.filePath, JSON.stringify(con_o), 'utf8')
|
||||
openAs.name = result.filePath
|
||||
layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至'
|
||||
saveHistory()
|
||||
@@ -1048,7 +1111,7 @@ function save(op1, callback) {
|
||||
canvas2.remove(obj)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
let j = canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline'])
|
||||
let b = canvas2.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline'])
|
||||
|
||||
@@ -1068,7 +1131,7 @@ function save(op1, callback) {
|
||||
//打开的文件
|
||||
let fs = require('fs')
|
||||
con_o.soonType = 2
|
||||
fs.writeFileSync(openAs.name, JSON.stringify(con_o))
|
||||
fs.writeFileSync(openAs.name, JSON.stringify(con_o), 'utf8')
|
||||
layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至'
|
||||
saveHistory()
|
||||
if (callback && typeof callback === 'function') {
|
||||
@@ -1079,18 +1142,27 @@ function save(op1, callback) {
|
||||
dialog
|
||||
.showSaveDialog({
|
||||
title: language_str('saveFile'), //'保存文件'
|
||||
filters: [{ name: 'Soon File Type', extensions: ['soon'] }]
|
||||
filters: [{ name: 'Soon File Type', extensions: ['soon'] }],
|
||||
defaultPath: openAs.name || undefined
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.filePath == '') {
|
||||
return
|
||||
}
|
||||
if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) {
|
||||
result.filePath += '.soon'
|
||||
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'
|
||||
}
|
||||
result.filePath = filePath
|
||||
let fs = require('fs')
|
||||
con_o.soonType = 2
|
||||
fs.writeFileSync(result.filePath, JSON.stringify(con_o))
|
||||
fs.writeFileSync(result.filePath, JSON.stringify(con_o), 'utf8')
|
||||
openAs.name = result.filePath
|
||||
layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至'
|
||||
saveHistory()
|
||||
@@ -1102,7 +1174,8 @@ function save(op1, callback) {
|
||||
})
|
||||
}
|
||||
|
||||
function saveHistory() {
|
||||
// 将 saveHistory 附加到 window 对象,确保在 ui.js 中也能访问
|
||||
window.saveHistory = function saveHistory() {
|
||||
let fs = require('fs')
|
||||
let j = {}
|
||||
try {
|
||||
@@ -1111,19 +1184,24 @@ function saveHistory() {
|
||||
for (let item of j.history) {
|
||||
if (item.path == openAs.name) {
|
||||
item.time = getDate()
|
||||
fs.writeFileSync(fullPath, JSON.stringify(j))
|
||||
fs.writeFileSync(fullPath, JSON.stringify(j), 'utf8')
|
||||
return
|
||||
}
|
||||
}
|
||||
j.history.push({ time: getDate(), path: openAs.name })
|
||||
fs.writeFileSync(fullPath, JSON.stringify(j))
|
||||
fs.writeFileSync(fullPath, JSON.stringify(j), 'utf8')
|
||||
} catch (e) {
|
||||
let j = { history: { time: getDate(), path: openAs.name } }
|
||||
fs.writeFileSync(fullPath, JSON.stringify(j))
|
||||
fs.writeFileSync(fullPath, JSON.stringify(j), 'utf8')
|
||||
}
|
||||
};
|
||||
// 向后兼容
|
||||
function saveHistory() {
|
||||
return window.saveHistory();
|
||||
}
|
||||
|
||||
function open(file) {
|
||||
// 使用 window.openFile 避免与浏览器原生的 window.open 冲突
|
||||
window.openFile = function open(file) {
|
||||
let fs = require('fs')
|
||||
var fsData = fs.readFileSync(file)
|
||||
openAs.name = file
|
||||
@@ -1147,34 +1225,38 @@ function open(file) {
|
||||
if (j.b.objects[0] && !j.b.objects[0].src) {
|
||||
j.b.objects[0].src = './public/images/bg_back_2.png'
|
||||
}
|
||||
|
||||
|
||||
// 为二维码和条形码对象创建临时的 src(Fabric.js 需要 src 来创建 Image 对象)
|
||||
// 加载完成后会重新生成,所以这里只需要一个占位符
|
||||
const fo = j.fo || []
|
||||
const bo = j.bo || []
|
||||
const placeholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='
|
||||
|
||||
|
||||
// 为正面二维码和条形码创建临时 src
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 为背面二维码和条形码创建临时 src
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 加载 JSON,并在回调中重新生成二维码和条形码
|
||||
canvas1.loadFromJSON(j.f, function() {
|
||||
regenerateQrCodesAndBarcodes(canvas1, fo)
|
||||
canvas1.loadFromJSON(j.f, function () {
|
||||
if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
|
||||
window.regenerateQrCodesAndBarcodes(canvas1, fo);
|
||||
}
|
||||
canvas1.renderAll()
|
||||
})
|
||||
canvas2.loadFromJSON(j.b, function() {
|
||||
regenerateQrCodesAndBarcodes(canvas2, bo)
|
||||
canvas2.loadFromJSON(j.b, function () {
|
||||
if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
|
||||
window.regenerateQrCodesAndBarcodes(canvas2, bo);
|
||||
}
|
||||
canvas2.renderAll()
|
||||
})
|
||||
background_image1 = canvas1.getObjects()[0]
|
||||
@@ -1203,10 +1285,22 @@ function open(file) {
|
||||
}
|
||||
canvas1.renderAll()
|
||||
canvas2.renderAll()
|
||||
updateList() /*
|
||||
修复打开新文件后无法撤销的bug
|
||||
2022-07-16
|
||||
*/
|
||||
// 调用 updateList,现在它已附加到 window 对象
|
||||
if (typeof window.updateList === 'function') {
|
||||
window.updateList(); /*
|
||||
修复打开新文件后无法撤销的bug
|
||||
2022-07-16
|
||||
*/
|
||||
} else {
|
||||
// 延迟检查,确保 core.js 已加载
|
||||
setTimeout(() => {
|
||||
if (typeof window.updateList === 'function') {
|
||||
window.updateList();
|
||||
} else {
|
||||
console.error('[open函数] ❌ updateList 函数未定义!');
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
setTimeout(() => {
|
||||
recordObjs1.push(JSON.stringify(objs1))
|
||||
let j1 = canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline'])
|
||||
@@ -1257,7 +1351,22 @@ function clearAll() {
|
||||
openAs.name = ''
|
||||
canvas1.renderAll()
|
||||
canvas2.renderAll()
|
||||
updateList()
|
||||
// 调用 updateList,现在它已附加到 window 对象
|
||||
if (typeof window.updateList === 'function') {
|
||||
window.updateList();
|
||||
} else {
|
||||
// 延迟检查,确保 core.js 已加载
|
||||
setTimeout(() => {
|
||||
if (typeof window.updateList === 'function') {
|
||||
window.updateList();
|
||||
} else {
|
||||
console.error('[clearAll] ❌ updateList 函数未定义!');
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
console.log()
|
||||
}
|
||||
|
||||
// 将 clearAll 附加到 window 对象,确保全局可访问
|
||||
window.clearAll = clearAll;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user