Compare commits
2 Commits
e46805deca
...
4c898306e0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c898306e0 | ||
|
|
1983c15fb9 |
@@ -23,18 +23,19 @@ html, body {
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 12px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
min-width: 72px;
|
||||
height: 40px;
|
||||
padding: 0 12px;
|
||||
font-size: 15px;
|
||||
min-width: 68px;
|
||||
height: 36px;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
@@ -62,6 +63,46 @@ html, body {
|
||||
background: #3a7bc8;
|
||||
}
|
||||
|
||||
/* 工具栏垂直分隔线 */
|
||||
.toolbar-divider-v {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background: #e0e0e0;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
/* 工具栏图标按钮 */
|
||||
.toolbar-btn-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 14px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
color: #555;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.toolbar-btn-icon:hover {
|
||||
background: #f5f5f5;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
.toolbar-btn-icon:active {
|
||||
background: #e8e8e8;
|
||||
}
|
||||
|
||||
.toolbar-btn-icon:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 文本输入框删除按钮 */
|
||||
.text-input .text-delete-btn {
|
||||
position: absolute;
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
<button class="btn" id="addImageBtn">背景图</button>
|
||||
<button class="btn" id="addOverlayBtn">层叠图</button>
|
||||
<button class="btn" id="addTextBtn">文本</button>
|
||||
<div class="toolbar-divider-v"></div>
|
||||
<button class="toolbar-btn-icon" id="undoBtn" title="撤销 (Ctrl+Z)">↩</button>
|
||||
<button class="toolbar-btn-icon" id="redoBtn" title="重做 (Ctrl+Y)">↪</button>
|
||||
<div class="toolbar-divider-v"></div>
|
||||
<button class="btn btn-primary" id="saveBtn">保存/预览</button>
|
||||
</div>
|
||||
|
||||
|
||||
414
js/script.js
414
js/script.js
@@ -60,6 +60,15 @@
|
||||
let maxOffsetX = 0;
|
||||
let isInteractingWithBgImage = false;
|
||||
|
||||
// 撤销重做相关变量
|
||||
const undoStack = [];
|
||||
const redoStack = [];
|
||||
const MAX_HISTORY = 50;
|
||||
let isUndoing = false;
|
||||
let saveStateTimer = null;
|
||||
let lastSavedState = null;
|
||||
const SAVE_DELAY = 300; // 防抖延迟 300ms
|
||||
|
||||
// 初始化
|
||||
init();
|
||||
|
||||
@@ -100,6 +109,23 @@
|
||||
// 保存按钮
|
||||
saveBtn.addEventListener('click', saveImage);
|
||||
|
||||
// 撤销重做按钮
|
||||
const undoBtn = document.getElementById('undoBtn');
|
||||
const redoBtn = document.getElementById('redoBtn');
|
||||
|
||||
undoBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
undo();
|
||||
});
|
||||
redoBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
redo();
|
||||
});
|
||||
|
||||
updateUndoRedoButtons();
|
||||
|
||||
// 初始化输入框
|
||||
initInput(input1);
|
||||
initInput(input2);
|
||||
@@ -127,6 +153,9 @@
|
||||
opacityValue.textContent = e.target.value + '%';
|
||||
}
|
||||
});
|
||||
opacitySlider.addEventListener('change', () => {
|
||||
saveState();
|
||||
});
|
||||
|
||||
// 旋转滑块
|
||||
rotateSlider.addEventListener('input', (e) => {
|
||||
@@ -150,6 +179,9 @@
|
||||
rotateValue.textContent = rotateDeg + '°';
|
||||
}
|
||||
});
|
||||
rotateSlider.addEventListener('change', () => {
|
||||
saveState();
|
||||
});
|
||||
|
||||
// 水平镜像按钮
|
||||
mirrorHBtn.addEventListener('click', () => {
|
||||
@@ -171,6 +203,9 @@
|
||||
}
|
||||
|
||||
img.style.transform = newTransform;
|
||||
|
||||
// 保存状态
|
||||
saveState();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -181,6 +216,9 @@
|
||||
bgScaleValue.textContent = e.target.value + '%';
|
||||
updateBgImage();
|
||||
});
|
||||
bgScaleSlider.addEventListener('change', () => {
|
||||
saveState();
|
||||
});
|
||||
|
||||
// 背景图透明度滑块
|
||||
bgOpacitySlider.addEventListener('input', (e) => {
|
||||
@@ -188,6 +226,9 @@
|
||||
bgOpacityValue.textContent = e.target.value + '%';
|
||||
bgImage.style.opacity = bgImageOpacity;
|
||||
});
|
||||
bgOpacitySlider.addEventListener('change', () => {
|
||||
saveState();
|
||||
});
|
||||
|
||||
// 点击空白区域取消选中
|
||||
document.addEventListener('click', (e) => {
|
||||
@@ -273,9 +314,288 @@
|
||||
// 加载默认背景图
|
||||
loadDefaultBackground();
|
||||
|
||||
// 保存初始状态
|
||||
setTimeout(() => {
|
||||
saveState();
|
||||
}, 500);
|
||||
|
||||
}
|
||||
|
||||
// ==================== 撤销重做功能 ====================
|
||||
|
||||
// 保存当前状态(带防抖)
|
||||
function saveState(immediate = false) {
|
||||
if (isUndoing) return;
|
||||
|
||||
// 清除之前的定时器
|
||||
if (saveStateTimer) {
|
||||
clearTimeout(saveStateTimer);
|
||||
saveStateTimer = null;
|
||||
}
|
||||
|
||||
const doSave = () => {
|
||||
const state = captureState();
|
||||
|
||||
// 如果新状态与上一个状态相同,不保存
|
||||
if (lastSavedState && statesEqual(state, lastSavedState)) {
|
||||
return;
|
||||
}
|
||||
|
||||
undoStack.push(state);
|
||||
lastSavedState = JSON.parse(JSON.stringify(state));
|
||||
|
||||
// 限制历史记录数量
|
||||
if (undoStack.length > MAX_HISTORY) {
|
||||
undoStack.shift();
|
||||
}
|
||||
|
||||
// 清空重做栈
|
||||
redoStack.length = 0;
|
||||
|
||||
updateUndoRedoButtons();
|
||||
};
|
||||
|
||||
if (immediate) {
|
||||
doSave();
|
||||
} else {
|
||||
saveStateTimer = setTimeout(doSave, SAVE_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
// 捕获当前状态
|
||||
function captureState() {
|
||||
return {
|
||||
// 背景图状态
|
||||
bgImageSrc: bgImage.src,
|
||||
bgImageScale: bgImageScale,
|
||||
bgImageOpacity: bgImageOpacity,
|
||||
imageOffsetX: imageOffsetX,
|
||||
|
||||
// 层叠图状态
|
||||
overlays: Array.from(overlayLayer.querySelectorAll('.overlay-image')).map(el => {
|
||||
const img = el.querySelector('img');
|
||||
return {
|
||||
src: img ? img.src : '',
|
||||
left: el.style.left,
|
||||
top: el.style.top,
|
||||
width: el.style.width,
|
||||
height: el.style.height,
|
||||
opacity: el.style.opacity,
|
||||
transform: el.style.transform,
|
||||
imgTransform: img ? img.style.transform : ''
|
||||
};
|
||||
}),
|
||||
|
||||
// 文本输入框状态
|
||||
textInputs: Array.from(document.querySelectorAll('.preview-input-layer .text-input')).map(el => ({
|
||||
id: el.id,
|
||||
content: el.innerText.replace('×', '').trim(),
|
||||
left: el.style.left,
|
||||
top: el.style.top,
|
||||
color: el.style.color,
|
||||
fontFamily: el.style.fontFamily,
|
||||
fontSize: el.style.fontSize,
|
||||
fontWeight: el.style.fontWeight,
|
||||
fontStyle: el.style.fontStyle,
|
||||
isLocked: el.dataset.locked === 'true'
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
// 比较两个状态是否相同
|
||||
function statesEqual(state1, state2) {
|
||||
return JSON.stringify(state1) === JSON.stringify(state2);
|
||||
}
|
||||
|
||||
// 撤销
|
||||
function undo() {
|
||||
if (undoStack.length <= 1) return;
|
||||
|
||||
isUndoing = true;
|
||||
|
||||
// 将当前状态移到重做栈
|
||||
const currentState = undoStack.pop();
|
||||
redoStack.push(currentState);
|
||||
|
||||
// 恢复上一个状态
|
||||
const prevState = undoStack[undoStack.length - 1];
|
||||
restoreState(prevState);
|
||||
|
||||
updateUndoRedoButtons();
|
||||
|
||||
isUndoing = false;
|
||||
}
|
||||
|
||||
// 重做
|
||||
function redo() {
|
||||
if (redoStack.length === 0) return;
|
||||
|
||||
isUndoing = true;
|
||||
|
||||
// 从重做栈恢复状态
|
||||
const state = redoStack.pop();
|
||||
undoStack.push(state);
|
||||
|
||||
restoreState(state);
|
||||
|
||||
updateUndoRedoButtons();
|
||||
|
||||
isUndoing = false;
|
||||
}
|
||||
|
||||
// 恢复状态
|
||||
function restoreState(state) {
|
||||
// 恢复背景图
|
||||
if (state.bgImageSrc) {
|
||||
if (state.bgImageSrc !== bgImage.src) {
|
||||
// 创建新图片对象确保加载完成
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
currentImage = img;
|
||||
bgImage.src = state.bgImageSrc;
|
||||
bgImageScale = state.bgImageScale;
|
||||
bgImageOpacity = state.bgImageOpacity;
|
||||
imageOffsetX = state.imageOffsetX;
|
||||
|
||||
bgScaleSlider.value = Math.round(bgImageScale * 100);
|
||||
bgScaleValue.textContent = Math.round(bgImageScale * 100) + '%';
|
||||
bgOpacitySlider.value = Math.round(bgImageOpacity * 100);
|
||||
bgOpacityValue.textContent = Math.round(bgImageOpacity * 100) + '%';
|
||||
|
||||
updateBgImage();
|
||||
|
||||
// 更新UI状态
|
||||
imageContainer.classList.remove('empty');
|
||||
emptyTip.classList.add('hidden');
|
||||
};
|
||||
img.src = state.bgImageSrc;
|
||||
} else {
|
||||
// 相同的图片,直接更新参数
|
||||
bgImageScale = state.bgImageScale;
|
||||
bgImageOpacity = state.bgImageOpacity;
|
||||
imageOffsetX = state.imageOffsetX;
|
||||
|
||||
bgScaleSlider.value = Math.round(bgImageScale * 100);
|
||||
bgScaleValue.textContent = Math.round(bgImageScale * 100) + '%';
|
||||
bgOpacitySlider.value = Math.round(bgImageOpacity * 100);
|
||||
bgOpacityValue.textContent = Math.round(bgImageOpacity * 100) + '%';
|
||||
|
||||
updateBgImage();
|
||||
}
|
||||
} else {
|
||||
// 没有背景图,清空
|
||||
bgImage.src = '';
|
||||
currentImage = null;
|
||||
imageContainer.classList.add('empty');
|
||||
emptyTip.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 恢复层叠图
|
||||
overlayLayer.innerHTML = '';
|
||||
state.overlays.forEach(overlayData => {
|
||||
// 创建新图片对象
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
createOverlayElementFromData(img, overlayData);
|
||||
};
|
||||
img.src = overlayData.src;
|
||||
});
|
||||
|
||||
// 恢复文本输入框
|
||||
const inputLayer = document.getElementById('previewInputLayer');
|
||||
|
||||
// 先隐藏所有预设输入框
|
||||
[input1, input2, input3].forEach(inputEl => {
|
||||
if (inputEl) {
|
||||
inputEl.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// 清除动态添加的输入框(保留预设的三个)
|
||||
const allInputs = inputLayer.querySelectorAll('.text-input');
|
||||
allInputs.forEach(input => {
|
||||
if (input.id !== 'input1' && input.id !== 'input2' && input.id !== 'input3') {
|
||||
input.remove();
|
||||
}
|
||||
});
|
||||
|
||||
state.textInputs.forEach(inputData => {
|
||||
let input = document.getElementById(inputData.id);
|
||||
if (!input) {
|
||||
// 创建新的输入框
|
||||
input = document.createElement('div');
|
||||
input.id = inputData.id;
|
||||
input.className = 'text-input';
|
||||
input.contentEditable = false;
|
||||
input.dataset.placeholder = '请输入';
|
||||
inputLayer.appendChild(input);
|
||||
initInput(input);
|
||||
}
|
||||
|
||||
// 显示输入框
|
||||
input.classList.remove('hidden');
|
||||
|
||||
// 清除现有内容(保留删除按钮)
|
||||
const deleteBtn = input.querySelector('.text-delete-btn');
|
||||
input.innerHTML = '';
|
||||
|
||||
// 设置文本内容
|
||||
input.appendChild(document.createTextNode(inputData.content));
|
||||
|
||||
// 重新添加删除按钮
|
||||
if (deleteBtn) {
|
||||
input.appendChild(deleteBtn);
|
||||
} else {
|
||||
const newDeleteBtn = document.createElement('button');
|
||||
newDeleteBtn.className = 'text-delete-btn';
|
||||
newDeleteBtn.innerHTML = '×';
|
||||
newDeleteBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
input.remove();
|
||||
saveState();
|
||||
});
|
||||
input.appendChild(newDeleteBtn);
|
||||
}
|
||||
|
||||
// 应用样式
|
||||
input.style.left = inputData.left;
|
||||
input.style.top = inputData.top;
|
||||
input.style.color = inputData.color || '#000000';
|
||||
input.style.fontFamily = inputData.fontFamily || '';
|
||||
input.style.fontSize = inputData.fontSize || '';
|
||||
input.style.fontWeight = inputData.fontWeight || 'bold';
|
||||
input.style.fontStyle = inputData.fontStyle || 'normal';
|
||||
input.dataset.locked = inputData.isLocked ? 'true' : '';
|
||||
|
||||
// 更新锁定样式
|
||||
if (inputData.isLocked) {
|
||||
input.classList.add('locked');
|
||||
} else {
|
||||
input.classList.remove('locked');
|
||||
}
|
||||
|
||||
// 检查是否需要显示 placeholder
|
||||
if (!inputData.content.trim()) {
|
||||
input.classList.add('show-placeholder');
|
||||
} else {
|
||||
input.classList.remove('show-placeholder');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 更新撤销重做按钮状态
|
||||
function updateUndoRedoButtons() {
|
||||
const undoBtn = document.getElementById('undoBtn');
|
||||
const redoBtn = document.getElementById('redoBtn');
|
||||
|
||||
if (undoBtn) {
|
||||
undoBtn.disabled = undoStack.length <= 1;
|
||||
}
|
||||
if (redoBtn) {
|
||||
redoBtn.disabled = redoStack.length === 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 加载默认背景图
|
||||
function loadDefaultBackground() {
|
||||
const defaultBgUrl = './images/default-bg.jpg';
|
||||
@@ -334,6 +654,9 @@
|
||||
|
||||
// 显示预设的文本输入框
|
||||
showPresetTextInputs();
|
||||
|
||||
// 保存状态(立即)
|
||||
saveState(true);
|
||||
};
|
||||
img.src = e.target.result;
|
||||
};
|
||||
@@ -381,6 +704,9 @@
|
||||
initInput(textEl);
|
||||
|
||||
selectInput(textEl);
|
||||
|
||||
// 保存状态(立即)
|
||||
saveState(true);
|
||||
}
|
||||
|
||||
// 加载层叠图
|
||||
@@ -436,6 +762,8 @@
|
||||
deleteBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
overlayEl.remove();
|
||||
// 保存状态(立即)
|
||||
saveState(true);
|
||||
});
|
||||
overlayEl.appendChild(deleteBtn);
|
||||
|
||||
@@ -452,6 +780,49 @@
|
||||
|
||||
// 选中新添加的层叠图
|
||||
selectOverlay(overlayEl);
|
||||
|
||||
// 保存状态(立即)
|
||||
saveState(true);
|
||||
}
|
||||
|
||||
// 从数据创建层叠图元素(用于撤销恢复)
|
||||
function createOverlayElementFromData(img, overlayData) {
|
||||
// 创建层叠图元素
|
||||
const overlayEl = document.createElement('div');
|
||||
overlayEl.className = 'overlay-image';
|
||||
overlayEl.dataset.scale = 1;
|
||||
|
||||
const imgEl = document.createElement('img');
|
||||
imgEl.src = img.src;
|
||||
imgEl.style.width = '100%';
|
||||
imgEl.style.height = '100%';
|
||||
imgEl.style.objectFit = 'contain';
|
||||
imgEl.style.transform = overlayData.imgTransform || '';
|
||||
overlayEl.appendChild(imgEl);
|
||||
|
||||
// 删除按钮
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.className = 'delete-btn';
|
||||
deleteBtn.innerHTML = '×';
|
||||
deleteBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
overlayEl.remove();
|
||||
saveState(true);
|
||||
});
|
||||
overlayEl.appendChild(deleteBtn);
|
||||
|
||||
// 恢复位置和尺寸
|
||||
overlayEl.style.left = overlayData.left;
|
||||
overlayEl.style.top = overlayData.top;
|
||||
overlayEl.style.width = overlayData.width;
|
||||
overlayEl.style.height = overlayData.height;
|
||||
overlayEl.style.opacity = overlayData.opacity || '1';
|
||||
overlayEl.style.transform = overlayData.transform || '';
|
||||
|
||||
// 初始化拖拽和缩放
|
||||
initOverlayImage(overlayEl);
|
||||
|
||||
overlayLayer.appendChild(overlayEl);
|
||||
}
|
||||
|
||||
// 初始化层叠图交互
|
||||
@@ -534,6 +905,9 @@
|
||||
document.removeEventListener('mouseup', handleDragEnd);
|
||||
document.removeEventListener('touchmove', handleDragMove);
|
||||
document.removeEventListener('touchend', handleDragEnd);
|
||||
|
||||
// 保存状态
|
||||
saveState();
|
||||
}
|
||||
|
||||
// 缩放控制(四角)
|
||||
@@ -633,6 +1007,9 @@
|
||||
document.removeEventListener('mouseup', endResize);
|
||||
document.removeEventListener('touchmove', handleResize);
|
||||
document.removeEventListener('touchend', endResize);
|
||||
|
||||
// 保存状态
|
||||
saveState();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -935,6 +1312,10 @@
|
||||
}
|
||||
|
||||
function handleImageEnd() {
|
||||
if (isDraggingImage) {
|
||||
// 保存状态
|
||||
saveState();
|
||||
}
|
||||
isDraggingImage = false;
|
||||
isInteractingWithBgImage = false;
|
||||
}
|
||||
@@ -1098,6 +1479,9 @@
|
||||
isDragging = false;
|
||||
element.classList.remove('dragging');
|
||||
hideGuides();
|
||||
|
||||
// 保存状态
|
||||
saveState();
|
||||
}
|
||||
|
||||
// 阻止事件冒泡
|
||||
@@ -1240,6 +1624,9 @@
|
||||
editingInput = null;
|
||||
// 隐藏编辑器工具栏
|
||||
editorToolbar.classList.remove('active');
|
||||
|
||||
// 保存状态
|
||||
saveState();
|
||||
}
|
||||
|
||||
// 更新工具栏状态
|
||||
@@ -1735,6 +2122,7 @@
|
||||
}
|
||||
boldBtn.classList.toggle('active');
|
||||
}
|
||||
saveState();
|
||||
});
|
||||
|
||||
// 斜体按钮
|
||||
@@ -1753,6 +2141,7 @@
|
||||
}
|
||||
italicBtn.classList.toggle('active');
|
||||
}
|
||||
saveState();
|
||||
});
|
||||
|
||||
// 锁定按钮
|
||||
@@ -1767,6 +2156,7 @@
|
||||
targetInput.classList.add('locked');
|
||||
}
|
||||
lockBtn.classList.toggle('active', !isLocked);
|
||||
saveState();
|
||||
});
|
||||
|
||||
// 颜色选择器
|
||||
@@ -1780,12 +2170,16 @@
|
||||
targetInput.style.color = colorPicker.value;
|
||||
}
|
||||
});
|
||||
colorPicker.addEventListener('change', () => {
|
||||
saveState();
|
||||
});
|
||||
|
||||
// 字体选择器
|
||||
fontPicker.addEventListener('change', () => {
|
||||
const targetInput = editingInput || selectedInput;
|
||||
if (!targetInput) return;
|
||||
targetInput.style.fontFamily = fontPicker.value;
|
||||
saveState();
|
||||
});
|
||||
|
||||
// 字号选择器 - 密码锁式滚轮
|
||||
@@ -1814,6 +2208,7 @@
|
||||
if (targetInput) {
|
||||
const currentSize = parseInt(window.getComputedStyle(targetInput).fontSize) || fontSizeValue;
|
||||
updateFontSize(currentSize + 1, true);
|
||||
saveState();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1822,6 +2217,7 @@
|
||||
if (targetInput) {
|
||||
const currentSize = parseInt(window.getComputedStyle(targetInput).fontSize) || fontSizeValue;
|
||||
updateFontSize(currentSize - 1, true);
|
||||
saveState();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1832,6 +2228,7 @@
|
||||
const currentSize = parseInt(window.getComputedStyle(targetInput).fontSize) || fontSizeValue;
|
||||
const delta = e.deltaY > 0 ? -1 : 1;
|
||||
updateFontSize(currentSize + delta, true);
|
||||
saveState();
|
||||
}
|
||||
}, { passive: false });
|
||||
}
|
||||
@@ -1839,6 +2236,23 @@
|
||||
// 初始化键盘微调功能(方向键 1px 移动)
|
||||
function initKeyboardNudge() {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
// 撤销重做快捷键
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
|
||||
e.preventDefault();
|
||||
if (e.shiftKey) {
|
||||
redo();
|
||||
} else {
|
||||
undo();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'y') {
|
||||
e.preventDefault();
|
||||
redo();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果正在编辑文本,不处理方向键
|
||||
if (editingInput) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user