2026-04-18-19-08-43 - 六项UI优化:基础字段无下划线、field-value联动高亮、视频按钮整合、视频间距紧凑、签名空行、图片占位符自适应高度

This commit is contained in:
Administrator
2026-04-18 19:14:23 +08:00
parent 5f4ae1ff29
commit 4a7051b6db
6 changed files with 242 additions and 23 deletions

View File

@@ -54,6 +54,7 @@ export default function ReportEditor() {
const prevVideoCountRef = useRef(0);
const [activeTab, setActiveTab] = useState<'info' | 'video'>('info');
const [activeFieldKey, setActiveFieldKey] = useState<string | null>(null);
const [multiSelectOptions, setMultiSelectOptions] = useState<Record<string, string[]>>({
surgeon: ['张医生', '李医生', '王医生'],
assistant: ['赵医生', '钱医生', '孙医生'],
@@ -378,13 +379,14 @@ export default function ReportEditor() {
const targetEl = node as HTMLElement | null;
if (!targetEl) return;
// Handle click on field-value: switch to info tab and focus corresponding input
// Handle click on field-value: switch to info tab, highlight and focus corresponding input
const fieldValue = targetEl.closest('.field-value') as HTMLElement | null;
if (fieldValue) {
const bindKey = fieldValue.getAttribute('data-bind');
if (bindKey) {
setActiveTab('info');
stateRef.current = { ...stateRef.current, activeTab: 'info' };
setActiveFieldKey(bindKey);
setTimeout(() => {
const inputEl = document.getElementById(`input-${bindKey}`);
if (inputEl) {
@@ -489,11 +491,14 @@ export default function ReportEditor() {
const fillPlaceholderSrc = (placeholder: HTMLElement, src: string) => {
placeholder.innerHTML = `
<span class="delete-btn" contenteditable="false">×</span>
<img src="${src}" style="max-width:100%;max-height:100%;object-fit:contain;display:block;margin:0 auto;" draggable="false">
<img src="${src}" style="max-width:100%;height:auto;display:block;margin:0 auto;" draggable="false">
`;
placeholder.classList.add('has-image');
placeholder.style.border = 'none';
placeholder.style.background = 'transparent';
placeholder.style.height = 'auto';
placeholder.style.width = 'auto';
placeholder.style.lineHeight = 'normal';
if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
saveDraftToStorage();
};
@@ -664,6 +669,9 @@ export default function ReportEditor() {
emptyPlaceholder.classList.add('has-image');
emptyPlaceholder.style.border = 'none';
emptyPlaceholder.style.background = 'transparent';
emptyPlaceholder.style.height = 'auto';
emptyPlaceholder.style.width = 'auto';
emptyPlaceholder.style.lineHeight = 'normal';
contentRef.current = editorRef.current.innerHTML;
saveDraftToStorage();
}
@@ -690,11 +698,14 @@ 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%;max-height:100%;object-fit:contain;display:block;margin:0 auto;" draggable="false">
<img src="${frame.dataUrl}" style="max-width:100%;height:auto;display:block;margin:0 auto;" draggable="false">
`;
placeholder.classList.add('has-image');
placeholder.style.border = 'none';
placeholder.style.background = 'transparent';
placeholder.style.height = 'auto';
placeholder.style.width = 'auto';
placeholder.style.lineHeight = 'normal';
if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
saveDraftToStorage();
};
@@ -1453,7 +1464,7 @@ export default function ReportEditor() {
if (field.type === 'text' || field.type === 'date') {
const inputType = field.type === 'date' ? 'date' : 'text';
return (
<div key={field.key} id={`input-${field.key}`} className={field.category === '填空' && formFields.filter(f2 => f2.visibleInForm && f2.type === 'text' && f2.isSystemLocked).length > 1 && (field.key === 'patientName' || field.key === 'hospitalId') ? 'flex-1 space-y-1' : 'space-y-1'}>
<div key={field.key} id={`input-${field.key}`} className={`${field.category === '填空' && formFields.filter(f2 => f2.visibleInForm && f2.type === 'text' && f2.isSystemLocked).length > 1 && (field.key === 'patientName' || field.key === 'hospitalId') ? 'flex-1 space-y-1' : 'space-y-1'} p-2 -mx-2 rounded-xl transition-all duration-300 ${activeFieldKey === field.key ? 'bg-blue-50 ring-1 ring-accent shadow-sm' : ''}`}>
<label className="block text-xs font-bold text-text-main">
{field.label} {isRequired && <span className="text-red-500">*</span>}
</label>
@@ -1473,7 +1484,7 @@ export default function ReportEditor() {
const isOpen = openDropdown === field.key;
const opts = field.options || (field.key === 'anesthesiaType' ? anesthesiaOptions : []);
return (
<div key={field.key} id={`input-${field.key}`} className="space-y-1 select-dropdown-root relative">
<div key={field.key} id={`input-${field.key}`} className={`space-y-1 select-dropdown-root relative p-2 -mx-2 rounded-xl transition-all duration-300 ${activeFieldKey === field.key ? 'bg-blue-50 ring-1 ring-accent shadow-sm' : ''}`}>
<label className="block text-xs font-bold text-text-main">{field.label}</label>
<div
className="w-full px-3 py-2 border border-border rounded-lg bg-white flex items-center min-h-[42px] cursor-text"
@@ -1582,7 +1593,7 @@ export default function ReportEditor() {
const currentInputText = multiInputText[field.key] !== undefined ? multiInputText[field.key] : displayText;
return (
<div key={field.key} id={`input-${field.key}`} className="space-y-1 select-dropdown-root relative">
<div key={field.key} id={`input-${field.key}`} className={`space-y-1 select-dropdown-root relative p-2 -mx-2 rounded-xl transition-all duration-300 ${activeFieldKey === field.key ? 'bg-blue-50 ring-1 ring-accent shadow-sm' : ''}`}>
<label className="block text-xs font-bold text-text-main">{field.label}</label>
<div
className="w-full px-3 py-2 border border-border rounded-lg bg-white flex flex-wrap gap-1 items-center min-h-[42px] cursor-text"
@@ -1663,7 +1674,7 @@ export default function ReportEditor() {
const { h: h12, isPM } = from24h(h24val);
return (
<div key={field.key} id={`input-${field.key}`} className="space-y-1">
<div key={field.key} id={`input-${field.key}`} className={`space-y-1 p-2 -mx-2 rounded-xl transition-all duration-300 ${activeFieldKey === field.key ? 'bg-blue-50 ring-1 ring-accent shadow-sm' : ''}`}>
<label className="block text-xs font-bold text-text-main">{field.label}</label>
<div className="flex items-center gap-2">
<select
@@ -1777,7 +1788,7 @@ export default function ReportEditor() {
)}
{activeTab === 'video' && (
<div className="space-y-4">
<div className="space-y-2">
<input
ref={videoInputRef}
type="file"
@@ -1786,20 +1797,17 @@ export default function ReportEditor() {
className="hidden"
onChange={handleVideoUpload}
/>
<button
onClick={() => videoInputRef.current?.click()}
className="w-full flex items-center justify-center gap-2 p-3 border border-dashed border-border rounded-lg hover:border-accent hover:bg-slate-50 transition-all"
>
<Video size={18} />
<div className="text-left">
<p className="text-xs font-bold text-text-main"></p>
<p className="text-[10px] text-text-muted"> MP4, MOV </p>
</div>
</button>
{videos.length > 0 && (
<div className="space-y-4">
<div className="space-y-2">
<div className="flex gap-2 overflow-x-auto pb-2 no-scrollbar">
<button
onClick={() => videoInputRef.current?.click()}
className="shrink-0 w-24 h-[68px] flex flex-col items-center justify-center gap-1 border-2 border-dashed border-border rounded-xl hover:border-accent hover:bg-slate-50 transition-all text-text-muted hover:text-accent"
>
<Video size={18} />
<span className="text-[10px] font-bold"></span>
</button>
{videos.map((v, i) => (
<div
key={v.id}
@@ -1828,7 +1836,7 @@ export default function ReportEditor() {
</div>
{currentVideoIndex !== -1 && (
<div className="space-y-4">
<div className="space-y-2">
<div className="relative bg-slate-900 rounded-2xl overflow-hidden aspect-video shadow-lg">
<video
ref={videoRef}
@@ -1866,7 +1874,7 @@ export default function ReportEditor() {
</div>
</div>
<div className="flex justify-between items-center pt-2">
<div className="flex justify-between items-center pt-1 border-t border-border">
<span className="text-[10px] font-bold text-text-main uppercase tracking-wider"></span>
<button
onClick={captureFrame}

View File

@@ -145,11 +145,14 @@ export default function TemplateManage() {
const fillPlaceholderSrc = (placeholder: HTMLElement, src: string) => {
placeholder.innerHTML = `
<span class="delete-btn" contenteditable="false">×</span>
<img src="${src}" style="max-width:100%;max-height:100%;object-fit:contain;display:block;margin:0 auto;" draggable="false">
<img src="${src}" style="max-width:100%;height:auto;display:block;margin:0 auto;" draggable="false">
`;
placeholder.classList.add('has-image');
placeholder.style.border = 'none';
placeholder.style.background = 'transparent';
placeholder.style.height = 'auto';
placeholder.style.width = 'auto';
placeholder.style.lineHeight = 'normal';
saveTemplateContent();
};

View File

@@ -1,4 +1,8 @@
const smartField = (key: string) => `<span class="smart-field-wrapper" contenteditable="false" style="white-space:nowrap;position:relative;"><span class="field-value" data-bind="${key}" contenteditable="true" style="min-width:32px;padding:0 4px;margin:0 2px;border:1px solid #cbd5e1;border-radius:2px;display:inline-block;background:#f8fafc;color:#0f172a;line-height:1.2;font-size:inherit;vertical-align:text-bottom;box-sizing:border-box;min-height:1.2em;outline:none;"> </span><span class="delete-btn" contenteditable="false">×</span></span>&#8203;`;
const noUnderlineKeys = ['patientName', 'patientGender', 'patientAge', 'department', 'bedNumber', 'hospitalId'];
const smartField = (key: string) => {
const noUlClass = noUnderlineKeys.includes(key) ? ' no-underline' : '';
return `<span class="smart-field-wrapper" contenteditable="false" style="white-space:nowrap;position:relative;"><span class="field-value${noUlClass}" data-bind="${key}" contenteditable="true" style="min-width:32px;padding:0 4px;margin:0 2px;border:1px solid #cbd5e1;border-radius:2px;display:inline-block;background:#f8fafc;color:#0f172a;line-height:1.2;font-size:inherit;vertical-align:text-bottom;box-sizing:border-box;min-height:1.2em;outline:none;"> </span><span class="delete-btn" contenteditable="false">×</span></span>&#8203;`;
};
export const defaultReportContent = `
<div style="display: flex; justify-content: center; align-items: center; gap: 12px; margin-bottom: 4px;">
@@ -144,6 +148,8 @@ export const defaultReportContent = `
手术者签名:<span class="image-placeholder" data-placeholder="true" contenteditable="false" data-mode="manual" style="display:inline-block;text-align:center;width:200px;height:40px;line-height:40px;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;position:relative;"><span class="delete-btn" contenteditable="false">×</span><span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;display:inline-block;vertical-align:middle;line-height:normal;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">插入/点击放置图片</span></span>
</p>
<p style="margin: 0; padding: 0; line-height: 1.5;">&nbsp;</p>
<p style="text-align: right; font-family: SimSun; line-height: 1.5; margin: 0; padding: 0;">
${smartField('reportDate')}
</p>