2026-04-18-19-08-43 - 六项UI优化:基础字段无下划线、field-value联动高亮、视频按钮整合、视频间距紧凑、签名空行、图片占位符自适应高度
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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>​`;
|
||||
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>​`;
|
||||
};
|
||||
|
||||
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;"> </p>
|
||||
|
||||
<p style="text-align: right; font-family: SimSun; line-height: 1.5; margin: 0; padding: 0;">
|
||||
${smartField('reportDate')}
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user