2026-04-18-00-02-08 - 拖拽关键帧样式修复、占位符分类隔离与Modal弹窗改造、表格插入Modal化
This commit is contained in:
@@ -66,6 +66,12 @@ export default function ReportEditor() {
|
||||
const [imagePickerOpen, setImagePickerOpen] = useState(false);
|
||||
const [imagePickerTarget, setImagePickerTarget] = useState<HTMLElement | null>(null);
|
||||
const [imageAssets, setImageAssets] = useState<{id: string; name: string; dataUrl: string}[]>([]);
|
||||
const [placeholderModal, setPlaceholderModal] = useState({
|
||||
isOpen: false, width: '200', height: '200', mode: 'frame' as 'frame' | 'manual'
|
||||
});
|
||||
const [tableModal, setTableModal] = useState({
|
||||
isOpen: false, rows: '2', cols: '3'
|
||||
});
|
||||
|
||||
const editorRef = useRef<HTMLDivElement>(null);
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
@@ -479,75 +485,13 @@ export default function ReportEditor() {
|
||||
};
|
||||
|
||||
const insertTable = () => {
|
||||
const rowsStr = prompt('请输入行数:', '2');
|
||||
const colsStr = prompt('请输入列数:', '3');
|
||||
if (rowsStr && colsStr) {
|
||||
const rows = parseInt(rowsStr);
|
||||
const cols = parseInt(colsStr);
|
||||
if (isNaN(rows) || isNaN(cols)) return;
|
||||
|
||||
let table = '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; table-layout: fixed;">';
|
||||
for (let i = 0; i < rows; i++) {
|
||||
table += '<tr>';
|
||||
for (let j = 0; j < cols; j++) {
|
||||
table += '<td style="padding: 8px; border: 1px solid #e2e8f0; vertical-align: top;">单元格</td>';
|
||||
}
|
||||
table += '</tr>';
|
||||
}
|
||||
table += '</table><p></p>';
|
||||
execCmd('insertHTML', table);
|
||||
}
|
||||
editorRef.current?.focus();
|
||||
setTableModal({ isOpen: true, rows: '2', cols: '3' });
|
||||
};
|
||||
|
||||
const insertImage = () => {
|
||||
editorRef.current?.focus();
|
||||
|
||||
const sel = window.getSelection();
|
||||
let node: Node | null = sel?.anchorNode ?? null;
|
||||
let inTable = false;
|
||||
while (node) {
|
||||
if ((node as Element).nodeName === 'TD' || (node as Element).nodeName === 'TH') {
|
||||
inTable = true;
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
|
||||
let width = 200;
|
||||
let height = 200;
|
||||
if (!inTable) {
|
||||
while (true) {
|
||||
const input = prompt('请输入占位符的最大宽度和高度(px),用*号分隔(如: 100*50)。留空则默认宽高为 200*200。(提示: 正文一行文字高度约为 20 像素左右)', '');
|
||||
if (input === null) return;
|
||||
const trimmed = input.trim();
|
||||
if (trimmed === '') break;
|
||||
const parts = trimmed.split('*').map(s => s.trim());
|
||||
if (parts.length === 2 && /^\d+$/.test(parts[0]) && /^\d+$/.test(parts[1])) {
|
||||
width = parseInt(parts[0]) || 0;
|
||||
height = parseInt(parts[1]) || 0;
|
||||
break;
|
||||
}
|
||||
alert('格式错误,请确保使用 * 分隔两个数字,例如 100*50');
|
||||
}
|
||||
}
|
||||
|
||||
const hintText = '插入/点击放置图片';
|
||||
const id = 'ph_' + Date.now();
|
||||
|
||||
let html: string;
|
||||
if (inTable) {
|
||||
const styleStr = 'display:flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;cursor:pointer;width:100%;height:100%;max-width:200px;max-height:200px;min-height:60px;margin:0 auto;';
|
||||
html = `<div id="${id}" class="image-placeholder" data-placeholder="true" contenteditable="false" style="${styleStr}"><span class="delete-btn" contenteditable="false">×</span><span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;">${hintText}</span></div>`;
|
||||
} else {
|
||||
let styleStr = 'display:inline-flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;';
|
||||
if (width > 0) styleStr += `width:${width}px;`;
|
||||
if (height > 0) styleStr += `height:${height}px;`;
|
||||
const showShortText = width > 0 && width < 80;
|
||||
const text = showShortText ? '插入图片' : hintText;
|
||||
html = `<span id="${id}" class="image-placeholder" data-placeholder="true" contenteditable="false" style="${styleStr}"><span class="delete-btn" contenteditable="false">×</span><span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">${text}</span></span>​`;
|
||||
}
|
||||
|
||||
execCmd('insertHTML', html);
|
||||
setPlaceholderModal({ isOpen: true, width: '200', height: '200', mode: 'frame' });
|
||||
};
|
||||
|
||||
const handleVideoUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -676,7 +620,7 @@ export default function ReportEditor() {
|
||||
|
||||
setTimeout(() => {
|
||||
if (!editorRef.current) return;
|
||||
const emptyPlaceholder = editorRef.current.querySelector('.image-placeholder:not(.has-image)') as HTMLElement | null;
|
||||
const emptyPlaceholder = editorRef.current.querySelector('.image-placeholder:not(.has-image):not([data-mode="manual"])') as HTMLElement | null;
|
||||
if (emptyPlaceholder) {
|
||||
emptyPlaceholder.innerHTML = `
|
||||
<span class="delete-btn" contenteditable="false">×</span>
|
||||
@@ -709,15 +653,21 @@ export default function ReportEditor() {
|
||||
const fillPlaceholder = (placeholder: HTMLElement, frame: CapturedFrame) => {
|
||||
placeholder.innerHTML = `
|
||||
<span class="delete-btn" contenteditable="false">×</span>
|
||||
<img src="${frame.dataUrl}" style="max-width: 100%; height: auto; display: block; margin: 0 auto;" draggable="false">
|
||||
<img src="${frame.dataUrl}" style="max-width:100%;max-height:100%;object-fit:contain;display:block;margin:0 auto;" draggable="false">
|
||||
`;
|
||||
placeholder.classList.add('has-image');
|
||||
placeholder.style.border = 'none';
|
||||
placeholder.style.background = 'transparent';
|
||||
if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
|
||||
saveDraftToStorage();
|
||||
};
|
||||
|
||||
const handleDrop = (e: React.DragEvent, placeholder: HTMLElement) => {
|
||||
e.preventDefault();
|
||||
if (placeholder.getAttribute('data-mode') === 'manual') {
|
||||
alert('此处为静态图片占位符,仅支持点击插入(如Logo/签名),不支持拖入关键帧');
|
||||
return;
|
||||
}
|
||||
const frameId = e.dataTransfer.getData('frameId');
|
||||
const frame = capturedFrames.find(f => f.id.toString() === frameId);
|
||||
if (frame) {
|
||||
@@ -730,7 +680,7 @@ export default function ReportEditor() {
|
||||
alert('编辑器未准备好');
|
||||
return;
|
||||
}
|
||||
const emptyPlaceholder = editorRef.current.querySelector('.image-placeholder:not(.has-image)') as HTMLElement | null;
|
||||
const emptyPlaceholder = editorRef.current.querySelector('.image-placeholder:not(.has-image):not([data-mode="manual"])') as HTMLElement | null;
|
||||
if (!emptyPlaceholder) {
|
||||
alert('没有可插入图片的空位');
|
||||
return;
|
||||
@@ -1895,6 +1845,108 @@ export default function ReportEditor() {
|
||||
</div>
|
||||
<canvas ref={canvasRef} className="hidden" />
|
||||
|
||||
{placeholderModal.isOpen && (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-2xl p-6 w-full max-w-[360px] shadow-2xl border border-border">
|
||||
<h3 className="text-lg font-bold text-text-main mb-4">插入图片占位符</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs mb-1">宽度(px)</label>
|
||||
<input type="number" value={placeholderModal.width} onChange={e => setPlaceholderModal({...placeholderModal, width: e.target.value})} className="w-full px-2 py-1 text-xs border border-border rounded" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs mb-1">高度(px)</label>
|
||||
<input type="number" value={placeholderModal.height} onChange={e => setPlaceholderModal({...placeholderModal, height: e.target.value})} className="w-full px-2 py-1 text-xs border border-border rounded" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs mb-1">占位符类型</label>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => setPlaceholderModal({...placeholderModal, mode: 'frame'})} className={`flex-1 py-1.5 text-xs rounded border ${placeholderModal.mode === 'frame' ? 'bg-accent text-white border-accent' : 'bg-white text-slate-600 border-border'}`}>手术影像占位<br/><span className="text-[10px] opacity-80">(支持自动/拖拽插入)</span></button>
|
||||
<button onClick={() => setPlaceholderModal({...placeholderModal, mode: 'manual'})} className={`flex-1 py-1.5 text-xs rounded border ${placeholderModal.mode === 'manual' ? 'bg-accent text-white border-accent' : 'bg-white text-slate-600 border-border'}`}>静态图片占位<br/><span className="text-[10px] opacity-80">(仅支持点击插入)</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 flex justify-end gap-2">
|
||||
<button onClick={() => setPlaceholderModal({...placeholderModal, isOpen: false})} className="px-4 py-2 bg-slate-100 text-slate-600 rounded text-sm">取消</button>
|
||||
<button onClick={() => {
|
||||
const sel = window.getSelection();
|
||||
let node: Node | null = sel?.anchorNode ?? null;
|
||||
let inTable = false;
|
||||
while (node) {
|
||||
if ((node as Element).nodeName === 'TD' || (node as Element).nodeName === 'TH') {
|
||||
inTable = true;
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
const w = parseInt(placeholderModal.width) || 200;
|
||||
const h = parseInt(placeholderModal.height) || 200;
|
||||
const modeAttr = placeholderModal.mode === 'manual' ? ' data-mode="manual"' : '';
|
||||
const hintText = '插入/点击放置图片';
|
||||
const id = 'ph_' + Date.now();
|
||||
let html: string;
|
||||
if (inTable) {
|
||||
const styleStr = 'display:flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;cursor:pointer;width:100%;height:100%;max-width:200px;max-height:200px;min-height:60px;margin:0 auto;';
|
||||
html = `<div id="${id}" class="image-placeholder" data-placeholder="true" contenteditable="false"${modeAttr} style="${styleStr}"><span class="delete-btn" contenteditable="false">×</span><span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;">${hintText}</span></div>`;
|
||||
} else {
|
||||
let styleStr = 'display:inline-flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;';
|
||||
styleStr += `width:${w}px;height:${h}px;`;
|
||||
const showShortText = w > 0 && w < 80;
|
||||
const text = showShortText ? '插图' : hintText;
|
||||
html = `<span id="${id}" class="image-placeholder" data-placeholder="true" contenteditable="false"${modeAttr} style="${styleStr}"><span class="delete-btn" contenteditable="false">×</span><span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">${text}</span></span>​`;
|
||||
}
|
||||
execCmd('insertHTML', html);
|
||||
setPlaceholderModal({...placeholderModal, isOpen: false});
|
||||
}} className="px-4 py-2 bg-accent text-white rounded text-sm">确认插入</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tableModal.isOpen && (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-2xl p-6 w-full max-w-[360px] shadow-2xl border border-border">
|
||||
<h3 className="text-lg font-bold text-text-main mb-4">插入表格</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs mb-1">行数</label>
|
||||
<input type="number" min="1" value={tableModal.rows} onChange={e => setTableModal({...tableModal, rows: e.target.value})} className="w-full px-2 py-1 text-xs border border-border rounded" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs mb-1">列数</label>
|
||||
<input type="number" min="1" value={tableModal.cols} onChange={e => setTableModal({...tableModal, cols: e.target.value})} className="w-full px-2 py-1 text-xs border border-border rounded" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 flex justify-end gap-2">
|
||||
<button onClick={() => setTableModal({...tableModal, isOpen: false})} className="px-4 py-2 bg-slate-100 text-slate-600 rounded text-sm">取消</button>
|
||||
<button onClick={() => {
|
||||
const rows = parseInt(tableModal.rows);
|
||||
const cols = parseInt(tableModal.cols);
|
||||
if (isNaN(rows) || isNaN(cols) || rows < 1 || cols < 1) {
|
||||
setTableModal({...tableModal, isOpen: false});
|
||||
return;
|
||||
}
|
||||
let table = '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; table-layout: fixed;">';
|
||||
for (let i = 0; i < rows; i++) {
|
||||
table += '<tr>';
|
||||
for (let j = 0; j < cols; j++) {
|
||||
table += '<td style="padding: 8px; border: 1px solid #e2e8f0; vertical-align: top;">单元格</td>';
|
||||
}
|
||||
table += '</tr>';
|
||||
}
|
||||
table += '</table><p></p>';
|
||||
execCmd('insertHTML', table);
|
||||
setTableModal({...tableModal, isOpen: false});
|
||||
}} className="px-4 py-2 bg-accent text-white rounded text-sm">确认插入</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{imagePickerOpen && imagePickerTarget && (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-2xl p-6 w-full max-w-[360px] shadow-2xl border border-border">
|
||||
|
||||
@@ -40,6 +40,12 @@ export default function TemplateManage() {
|
||||
const [customTimeFormats, setCustomTimeFormats] = useState<string[]>([]);
|
||||
const [formatDropdownOpen, setFormatDropdownOpen] = useState(false);
|
||||
const [newFormatDropdownOpen, setNewFormatDropdownOpen] = useState(false);
|
||||
const [placeholderModal, setPlaceholderModal] = useState({
|
||||
isOpen: false, width: '200', height: '200', mode: 'frame' as 'frame' | 'manual'
|
||||
});
|
||||
const [tableModal, setTableModal] = useState({
|
||||
isOpen: false, rows: '2', cols: '3'
|
||||
});
|
||||
const [imageAssets, setImageAssets] = useState<{ id: string; name: string; dataUrl: string }[]>([]);
|
||||
|
||||
const updatePageHeight = () => {
|
||||
@@ -493,78 +499,17 @@ export default function TemplateManage() {
|
||||
};
|
||||
|
||||
const insertTable = () => {
|
||||
const rowsStr = prompt('请输入行数:', '2');
|
||||
const colsStr = prompt('请输入列数:', '3');
|
||||
if (rowsStr && colsStr) {
|
||||
const rows = parseInt(rowsStr);
|
||||
const cols = parseInt(colsStr);
|
||||
if (isNaN(rows) || isNaN(cols)) return;
|
||||
|
||||
let table = '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; table-layout: fixed;">';
|
||||
for (let i = 0; i < rows; i++) {
|
||||
table += '<tr>';
|
||||
for (let j = 0; j < cols; j++) {
|
||||
table += '<td style="padding: 8px; border: 1px solid #e2e8f0; vertical-align: top;">单元格</td>';
|
||||
}
|
||||
table += '</tr>';
|
||||
}
|
||||
table += '</table><p></p>';
|
||||
pushHistory();
|
||||
execCmd('insertHTML', table);
|
||||
}
|
||||
editorRef.current?.focus();
|
||||
restoreSelection();
|
||||
pushHistory();
|
||||
setTableModal({ isOpen: true, rows: '2', cols: '3' });
|
||||
};
|
||||
|
||||
const insertImage = () => {
|
||||
editorRef.current?.focus();
|
||||
restoreSelection();
|
||||
|
||||
const sel = window.getSelection();
|
||||
let node: Node | null = sel?.anchorNode ?? null;
|
||||
let inTable = false;
|
||||
while (node) {
|
||||
if ((node as Element).nodeName === 'TD' || (node as Element).nodeName === 'TH') {
|
||||
inTable = true;
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
|
||||
let width = 200;
|
||||
let height = 200;
|
||||
if (!inTable) {
|
||||
while (true) {
|
||||
const input = prompt('请输入占位符的最大宽度和高度(px),用 * 分隔(如: 100*50)。留空则默认宽高为 200*200。(提示: 正文一行文字高度约为 20 像素左右)', '');
|
||||
if (input === null) return;
|
||||
const trimmed = input.trim();
|
||||
if (trimmed === '') break;
|
||||
const parts = trimmed.split('*').map(s => s.trim());
|
||||
if (parts.length === 2 && /^\d+$/.test(parts[0]) && /^\d+$/.test(parts[1])) {
|
||||
width = parseInt(parts[0]) || 0;
|
||||
height = parseInt(parts[1]) || 0;
|
||||
break;
|
||||
}
|
||||
alert('格式错误,请确保使用 * 分隔两个数字,例如 100*50');
|
||||
}
|
||||
}
|
||||
|
||||
const hintText = '插入/点击放置图片';
|
||||
const id = 'ph_' + Date.now();
|
||||
|
||||
let html: string;
|
||||
if (inTable) {
|
||||
const styleStr = 'display:flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;cursor:pointer;width:100%;height:100%;max-width:200px;max-height:200px;min-height:60px;margin:0 auto;';
|
||||
html = `<div id="${id}" class="image-placeholder" data-placeholder="true" contenteditable="false" style="${styleStr}"><span class="delete-btn" contenteditable="false">×</span><span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;">${hintText}</span></div>`;
|
||||
} else {
|
||||
let styleStr = 'display:inline-flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;';
|
||||
if (width > 0) styleStr += `width:${width}px;`;
|
||||
if (height > 0) styleStr += `height:${height}px;`;
|
||||
const showShortText = width > 0 && width < 80;
|
||||
const text = showShortText ? '插图' : hintText;
|
||||
html = `<span id="${id}" class="image-placeholder" data-placeholder="true" contenteditable="false" style="${styleStr}"><span class="delete-btn" contenteditable="false">×</span><span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">${text}</span></span>​`;
|
||||
}
|
||||
|
||||
pushHistory();
|
||||
execCmd('insertHTML', html);
|
||||
setPlaceholderModal({ isOpen: true, width: '200', height: '200', mode: 'frame' });
|
||||
};
|
||||
|
||||
const saveCurrentTemplate = () => {
|
||||
@@ -1269,6 +1214,108 @@ export default function TemplateManage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{placeholderModal.isOpen && (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-2xl p-6 w-full max-w-[360px] shadow-2xl border border-border">
|
||||
<h3 className="text-lg font-bold text-text-main mb-4">插入图片占位符</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs mb-1">宽度(px)</label>
|
||||
<input type="number" value={placeholderModal.width} onChange={e => setPlaceholderModal({...placeholderModal, width: e.target.value})} className="w-full px-2 py-1 text-xs border border-border rounded" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs mb-1">高度(px)</label>
|
||||
<input type="number" value={placeholderModal.height} onChange={e => setPlaceholderModal({...placeholderModal, height: e.target.value})} className="w-full px-2 py-1 text-xs border border-border rounded" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs mb-1">占位符类型</label>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => setPlaceholderModal({...placeholderModal, mode: 'frame'})} className={`flex-1 py-1.5 text-xs rounded border ${placeholderModal.mode === 'frame' ? 'bg-accent text-white border-accent' : 'bg-white text-slate-600 border-border'}`}>手术影像占位<br/><span className="text-[10px] opacity-80">(支持自动/拖拽插入)</span></button>
|
||||
<button onClick={() => setPlaceholderModal({...placeholderModal, mode: 'manual'})} className={`flex-1 py-1.5 text-xs rounded border ${placeholderModal.mode === 'manual' ? 'bg-accent text-white border-accent' : 'bg-white text-slate-600 border-border'}`}>静态图片占位<br/><span className="text-[10px] opacity-80">(仅支持点击插入)</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 flex justify-end gap-2">
|
||||
<button onClick={() => setPlaceholderModal({...placeholderModal, isOpen: false})} className="px-4 py-2 bg-slate-100 text-slate-600 rounded text-sm">取消</button>
|
||||
<button onClick={() => {
|
||||
const sel = window.getSelection();
|
||||
let node: Node | null = sel?.anchorNode ?? null;
|
||||
let inTable = false;
|
||||
while (node) {
|
||||
if ((node as Element).nodeName === 'TD' || (node as Element).nodeName === 'TH') {
|
||||
inTable = true;
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
const w = parseInt(placeholderModal.width) || 200;
|
||||
const h = parseInt(placeholderModal.height) || 200;
|
||||
const modeAttr = placeholderModal.mode === 'manual' ? ' data-mode="manual"' : '';
|
||||
const hintText = '插入/点击放置图片';
|
||||
const id = 'ph_' + Date.now();
|
||||
let html: string;
|
||||
if (inTable) {
|
||||
const styleStr = 'display:flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;cursor:pointer;width:100%;height:100%;max-width:200px;max-height:200px;min-height:60px;margin:0 auto;';
|
||||
html = `<div id="${id}" class="image-placeholder" data-placeholder="true" contenteditable="false"${modeAttr} style="${styleStr}"><span class="delete-btn" contenteditable="false">×</span><span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;">${hintText}</span></div>`;
|
||||
} else {
|
||||
let styleStr = 'display:inline-flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;';
|
||||
styleStr += `width:${w}px;height:${h}px;`;
|
||||
const showShortText = w > 0 && w < 80;
|
||||
const text = showShortText ? '插图' : hintText;
|
||||
html = `<span id="${id}" class="image-placeholder" data-placeholder="true" contenteditable="false"${modeAttr} style="${styleStr}"><span class="delete-btn" contenteditable="false">×</span><span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">${text}</span></span>​`;
|
||||
}
|
||||
execCmd('insertHTML', html);
|
||||
setPlaceholderModal({...placeholderModal, isOpen: false});
|
||||
}} className="px-4 py-2 bg-accent text-white rounded text-sm">确认插入</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tableModal.isOpen && (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-2xl p-6 w-full max-w-[360px] shadow-2xl border border-border">
|
||||
<h3 className="text-lg font-bold text-text-main mb-4">插入表格</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs mb-1">行数</label>
|
||||
<input type="number" min="1" value={tableModal.rows} onChange={e => setTableModal({...tableModal, rows: e.target.value})} className="w-full px-2 py-1 text-xs border border-border rounded" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs mb-1">列数</label>
|
||||
<input type="number" min="1" value={tableModal.cols} onChange={e => setTableModal({...tableModal, cols: e.target.value})} className="w-full px-2 py-1 text-xs border border-border rounded" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 flex justify-end gap-2">
|
||||
<button onClick={() => setTableModal({...tableModal, isOpen: false})} className="px-4 py-2 bg-slate-100 text-slate-600 rounded text-sm">取消</button>
|
||||
<button onClick={() => {
|
||||
const rows = parseInt(tableModal.rows);
|
||||
const cols = parseInt(tableModal.cols);
|
||||
if (isNaN(rows) || isNaN(cols) || rows < 1 || cols < 1) {
|
||||
setTableModal({...tableModal, isOpen: false});
|
||||
return;
|
||||
}
|
||||
let table = '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; table-layout: fixed;">';
|
||||
for (let i = 0; i < rows; i++) {
|
||||
table += '<tr>';
|
||||
for (let j = 0; j < cols; j++) {
|
||||
table += '<td style="padding: 8px; border: 1px solid #e2e8f0; vertical-align: top;">单元格</td>';
|
||||
}
|
||||
table += '</tr>';
|
||||
}
|
||||
table += '</table><p></p>';
|
||||
execCmd('insertHTML', table);
|
||||
setTableModal({...tableModal, isOpen: false});
|
||||
}} className="px-4 py-2 bg-accent text-white rounded text-sm">确认插入</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{imagePickerOpen && imagePickerTarget && (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-2xl p-6 w-full max-w-[360px] shadow-2xl border border-border">
|
||||
|
||||
Reference in New Issue
Block a user