diff --git a/src/index.css b/src/index.css
index ea0e089..3e87168 100644
--- a/src/index.css
+++ b/src/index.css
@@ -156,6 +156,13 @@
.template-editor-mode .smart-field-wrapper:focus-within .delete-btn {
display: block;
}
+ .report-signature-img {
+ height: 2.4em;
+ width: auto;
+ vertical-align: middle;
+ display: inline-block;
+ margin: -0.3em 0;
+ }
}
@media print {
@@ -195,4 +202,11 @@
.print-content .smart-field-wrapper .delete-btn {
display: none !important;
}
+ .report-signature-img {
+ height: 2.4em !important;
+ width: auto !important;
+ vertical-align: middle !important;
+ display: inline-block !important;
+ margin: -0.3em 0 !important;
+ }
}
diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx
index 5db9d6c..e988411 100644
--- a/src/pages/ReportEditor.tsx
+++ b/src/pages/ReportEditor.tsx
@@ -940,6 +940,25 @@ export default function ReportEditor() {
const el = node as HTMLElement;
const fieldKey = el.getAttribute('data-bind')!;
+ if (fieldKey === 'surgeonSignature') {
+ const signatureData = currentUser?.signature;
+ if (signatureData) {
+ const imgHtml = `
`;
+ if (el.innerHTML !== imgHtml) {
+ el.innerHTML = imgHtml;
+ el.style.border = 'none';
+ el.style.backgroundColor = 'transparent';
+ }
+ } else {
+ if (el.innerText !== '【请上传电子签】') {
+ el.innerText = '【请上传电子签】';
+ el.style.border = '';
+ el.style.backgroundColor = '';
+ }
+ }
+ return;
+ }
+
let newValue = '';
if (fieldKey === 'startTime') {
newValue = `${reportData.startHour || ''}:${reportData.startMinute || ''}`;
diff --git a/src/pages/TemplateManage.tsx b/src/pages/TemplateManage.tsx
index 29eee79..9a3277e 100644
--- a/src/pages/TemplateManage.tsx
+++ b/src/pages/TemplateManage.tsx
@@ -254,6 +254,20 @@ export default function TemplateManage() {
editorRef.current?.focus();
};
+ const highlightField = (key: string, active: boolean) => {
+ if (!editorRef.current) return;
+ const el = editorRef.current.querySelector(`[data-bind="${key}"]`) as HTMLElement | null;
+ if (!el) return;
+ if (active) {
+ el.style.transition = 'all 0.2s';
+ el.style.boxShadow = '0 0 0 2px #3b82f6';
+ el.style.backgroundColor = '#e0f2fe';
+ } else {
+ el.style.boxShadow = '';
+ el.style.backgroundColor = '';
+ }
+ };
+
const toggleFieldVisible = (key: string) => {
const updated = formFields.map(f => f.key === key ? { ...f, visibleInForm: !f.visibleInForm } : f);
setFormFields(updated);
@@ -601,7 +615,7 @@ export default function TemplateManage() {
{fieldLibTab === 'insert' && (
- {['填空', '单选', '多选', '时间'].map(cat => {
+ {['填空', '单选', '多选', '时间', '图片'].map(cat => {
const catFields = formFields.filter(f => f.category === cat);
if (catFields.length === 0) return null;
return (
@@ -613,6 +627,8 @@ export default function TemplateManage() {
key={field.key}
type="button"
onClick={() => insertSmartField(field)}
+ onMouseEnter={() => highlightField(field.key, true)}
+ onMouseLeave={() => highlightField(field.key, false)}
className="px-2 py-1 text-[11px] bg-slate-100 hover:bg-slate-200 text-slate-700 rounded border border-slate-300 transition-colors"
title={`插入 ${field.label}`}
>
diff --git a/src/pages/UserManage.tsx b/src/pages/UserManage.tsx
index 09af005..6ef68ff 100644
--- a/src/pages/UserManage.tsx
+++ b/src/pages/UserManage.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useState, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import Sidebar from '../components/Sidebar';
-import { UserPlus, Edit, Trash2 } from 'lucide-react';
+import { UserPlus, Edit, Trash2, Upload, X } from 'lucide-react';
import { User, Template } from '../types';
import { storage } from '../utils/storage';
@@ -56,6 +56,50 @@ export default function UserManage() {
storage.set('users', updatedUsers);
};
+ const compressImage = (file: File, maxSize: number = 500): Promise => {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onload = (e) => {
+ const img = new Image();
+ img.src = e.target?.result as string;
+ img.onload = () => {
+ const canvas = document.createElement('canvas');
+ let { width, height } = img;
+ if (width > height && width > maxSize) {
+ height = Math.round((height * maxSize) / width);
+ width = maxSize;
+ } else if (height > maxSize) {
+ width = Math.round((width * maxSize) / height);
+ height = maxSize;
+ }
+ canvas.width = width;
+ canvas.height = height;
+ const ctx = canvas.getContext('2d');
+ if (ctx) {
+ ctx.fillStyle = '#FFFFFF';
+ ctx.fillRect(0, 0, width, height);
+ ctx.drawImage(img, 0, 0, width, height);
+ }
+ resolve(canvas.toDataURL('image/jpeg', 0.8));
+ };
+ img.onerror = reject;
+ };
+ reader.onerror = reject;
+ });
+ };
+
+ const handleSignatureUpload = async (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+ try {
+ const compressed = await compressImage(file);
+ setFormData(prev => ({ ...prev, signature: compressed }));
+ } catch {
+ alert('图片压缩失败,请重试');
+ }
+ };
+
const handleDelete = (username: string) => {
if (username === 'admin') {
alert('不能删除默认超级管理员');
@@ -226,7 +270,7 @@ export default function UserManage() {
updatedUsers = users.map(u => {
if (u.username === formData.username) {
- return { ...u, role: finalRole, department: finalDepartment, manageableTemplates, visibleTemplates: adminVisible, password: formData.password || u.password } as User;
+ return { ...u, role: finalRole, department: finalDepartment, manageableTemplates, visibleTemplates: adminVisible, password: formData.password || u.password, signature: formData.signature } as User;
}
if (u.role === 'user' && u.department === (oldUser.department || finalDepartment)) {
const currentVisible = Array.isArray(u.visibleTemplates) ? u.visibleTemplates : [];
@@ -568,6 +612,43 @@ export default function UserManage() {
)}
+
+
+ {formData.signature ? (
+
+

+
+
+
+
+
+ ) : (
+
+
+ 支持 JPG、PNG,自动压缩至 500px 以内
+
+ )}
+
+
{showManageableTemplates && (