diff --git a/src/index.css b/src/index.css index a16fc6d..540702c 100644 --- a/src/index.css +++ b/src/index.css @@ -95,6 +95,31 @@ .manual-frame-badge { @apply absolute top-1 left-1 px-1.5 py-0.5 bg-yellow-400 text-yellow-900 text-[9px] font-bold rounded shadow-sm pointer-events-none; } + + /* Smart Field Bindable Controls */ + .smart-field-wrapper { + display: inline-flex; + align-items: center; + margin: 0 4px; + vertical-align: middle; + } + .smart-field-wrapper .field-label { + color: #64748b; + user-select: none; + } + .smart-field-wrapper .field-value { + min-width: 60px; + padding: 0 4px; + border: 1px solid #cbd5e1; + border-radius: 4px; + display: inline-block; + background: #fff; + color: #0f172a; + outline: none; + } + .smart-field-wrapper .field-value:empty::before { + content: '\200b'; + } } @media print { @@ -124,4 +149,11 @@ .print-content .image-placeholder:not(.has-image) { display: none !important; } + .print-content .smart-field-wrapper .field-value { + border: none !important; + border-bottom: 1px solid #000 !important; + border-radius: 0 !important; + background: transparent !important; + padding: 0 2px !important; + } } diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index 4012613..ec375ef 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -873,6 +873,48 @@ export default function ReportEditor() { if (status === 'completed') navigate('/report-manage'); }; + const handleEditorInput = (e: React.FormEvent) => { + if (editorRef.current) { + contentRef.current = editorRef.current.innerHTML; + } + updatePageHeight(); + saveDraftToStorage(); + + const target = e.target as HTMLElement; + if (target && target.hasAttribute('data-bind')) { + const fieldKey = target.getAttribute('data-bind')!; + const newValue = target.innerText; + + setReportData((prev) => { + const next = { ...prev, [fieldKey]: newValue }; + stateRef.current = { ...stateRef.current, reportData: next }; + return next; + }); + } + }; + + // Sync form state -> rich text field values + useEffect(() => { + if (!editorRef.current) return; + const bindNodes = editorRef.current.querySelectorAll('[data-bind]'); + bindNodes.forEach((node) => { + const el = node as HTMLElement; + const fieldKey = el.getAttribute('data-bind')!; + const rawValue = (reportData as any)[fieldKey]; + + let newValue = ''; + if (Array.isArray(rawValue)) { + newValue = rawValue.join(', '); + } else if (rawValue !== undefined && rawValue !== null) { + newValue = String(rawValue); + } + + if (el.innerText !== newValue) { + el.innerText = newValue; + } + }); + }, [reportData]); + if (!currentUser) return null; const hasVisibleTemplates = templates.length > 0; @@ -1006,7 +1048,7 @@ export default function ReportEditor() {
{ contentRef.current = editorRef.current?.innerHTML || ''; updatePageHeight(); saveDraftToStorage(); }} + onInput={handleEditorInput} onBlur={() => { contentRef.current = editorRef.current?.innerHTML || ''; saveDraftToStorage(); }} className="editor-content print-content" > diff --git a/src/pages/TemplateManage.tsx b/src/pages/TemplateManage.tsx index f36aa15..4299ad7 100644 --- a/src/pages/TemplateManage.tsx +++ b/src/pages/TemplateManage.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import Sidebar from '../components/Sidebar'; import { Plus, Edit, Trash2, Save, Printer, Undo, Redo, Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight, Table, Image as ImageIcon, Check } from 'lucide-react'; -import { User, Template } from '../types'; +import { User, Template, BINDABLE_FIELDS } from '../types'; import { defaultReportContent } from '../utils/defaultContent'; import { printDocument } from '../utils/print'; import { storage } from '../utils/storage'; @@ -156,6 +156,22 @@ export default function TemplateManage() { editorRef.current?.focus(); }; + const insertSmartField = (field: typeof BINDABLE_FIELDS[0]) => { + editorRef.current?.focus(); + const html = ` + + ${field.label}: + + +   + `; + document.execCommand('insertHTML', false, html); + editorRef.current?.focus(); + }; + const insertTable = () => { const rowsStr = prompt('请输入行数:', '2'); const colsStr = prompt('请输入列数:', '3'); @@ -396,58 +412,87 @@ export default function TemplateManage() {
-
- {/* Toolbar */} -
-
- - -
-
- -
-
- - - -
- execCmd('foreColor', e.target.value)} - className="w-9 h-9 p-1.5 bg-transparent border-none cursor-pointer rounded-lg hover:bg-white transition-colors" - title="文字颜色" - /> +
+ {/* Editor Main */} +
+ {/* Toolbar */} +
+
+ + +
+
+ +
+
+ + + +
+ execCmd('foreColor', e.target.value)} + className="w-9 h-9 p-1.5 bg-transparent border-none cursor-pointer rounded-lg hover:bg-white transition-colors" + title="文字颜色" + /> +
+
+
+ + + +
+
+