Files
Mdeical_Sur_Report/src/pages/Login.tsx

231 lines
9.7 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { User, Template, SystemSettings, FormField, DEFAULT_FORM_FIELDS } from '../types';
import { defaultReportContent } from '../utils/defaultContent';
import { storage } from '../utils/storage';
import { User as UserIcon, Lock } from 'lucide-react';
export default function Login() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const navigate = useNavigate();
useEffect(() => {
const initData = () => {
const existingUsers = storage.get<User[]>('users', []);
const hasAdmin = existingUsers.some((u) => u.username === 'admin' && u.password === '123456');
let savedTemplates = storage.get<Template[]>('templates', []);
if (savedTemplates.length === 0) {
const initialTemplate: Template = {
id: 'surgery',
name: '腹腔镜胆囊切除术报告',
desc: '标准手术记录模板',
content: defaultReportContent,
createdAt: new Date().toISOString(),
author: 'admin'
};
savedTemplates = [initialTemplate];
storage.set('templates', savedTemplates);
}
if (!hasAdmin) {
const allTplIds = savedTemplates.map(t => t.id);
const defaultUsers: User[] = [
{ username: 'admin', password: '123456', role: 'super', name: '超级管理员', status: 'active', createdAt: '2024-01-01', visibleTemplates: allTplIds, manageableTemplates: allTplIds },
{ username: 'manager', password: '123456', role: 'admin', name: '管理员', status: 'active', createdAt: '2024-01-01', department: '外科', visibleTemplates: allTplIds, manageableTemplates: allTplIds },
{ username: '0001', password: '123456', role: 'user', name: '张医生', status: 'active', createdAt: '2024-01-01', department: '外科', visibleTemplates: allTplIds, manageableTemplates: [] }
];
storage.set('users', defaultUsers);
console.log('Default users initialized');
}
const fieldsConfig = storage.get<FormField[]>('formFieldsConfig', []);
if (fieldsConfig.length === 0) {
storage.set('formFieldsConfig', DEFAULT_FORM_FIELDS);
}
const savedAssets = storage.get<{id: string; name: string; dataUrl: string}[]>('imageAssets', []);
if (savedAssets.length === 0) {
fetch('/logo_square.png')
.then(res => res.blob())
.then(blob => {
const reader = new FileReader();
reader.onloadend = () => {
const dataUrl = reader.result as string;
storage.set('imageAssets', [{ id: 'asset_logo', name: '医院Logo', dataUrl }]);
};
reader.readAsDataURL(blob);
})
.catch(() => {});
}
const settingsRaw = storage.get<SystemSettings>('systemSettings', {} as SystemSettings);
if (!settingsRaw.frameCount) {
const round1 = (n: number) => Math.round(n * 10) / 10;
const positions: number[] = [];
for (let i = 1; i <= 12; i++) {
positions.push(round1((100 / 13) * i));
}
const defaultSettings = {
frameCount: 12,
framePositions: positions,
apiEndpoint: '',
apiKey: '',
defaultTemplate: savedTemplates[0]?.id || '',
frameMode: 'uniform',
autoInsertFrames: true,
autoInsertDelay: 1,
autoInsertFrameIndices: [0, 1, 2, 3, 4, 5]
};
storage.set('systemSettings', defaultSettings);
}
};
initData();
}, []);
const handleLogin = (e: React.FormEvent) => {
e.preventDefault();
const u = username.trim();
const p = password.trim();
const users = storage.get<User[]>('users', []);
let user = users.find(user => user.username === u && user.password === p);
// Fallback for default accounts if localStorage is messed up
if (!user) {
const defaults = [
{ u: 'admin', p: '123456', r: 'super', n: '超级管理员' },
{ u: 'manager', p: '123456', r: 'admin', n: '管理员' },
{ u: '0001', p: '123456', r: 'user', n: '张医生' }
];
const d = defaults.find(item => item.u === u && item.p === p);
if (d) {
const allTemplates = storage.get<Template[]>('templates', []);
const allTplIds = allTemplates.map(t => t.id);
user = { username: d.u, password: d.p, role: d.r as any, name: d.n, status: 'active', createdAt: '2024-01-01', visibleTemplates: allTplIds, manageableTemplates: d.r === 'user' ? [] : allTplIds, department: d.r === 'super' ? '' : '外科' };
// Sync back to localStorage
const updatedUsers = [...users.filter(item => item.username !== u), user];
storage.set('users', updatedUsers);
}
}
if (user) {
if (user.status === 'inactive') {
setError('该账号已被禁用');
return;
}
storage.set('currentUser', user);
navigate('/dashboard');
} else {
setError('用户ID或密码错误');
console.log('Login failed for:', u);
}
};
const fillLogin = (u: string, p: string) => {
setUsername(u);
setPassword(p);
setTimeout(() => {
// Trigger the robust login logic manually
const users = storage.get<User[]>('users', []);
let user = users.find(user => user.username === u && user.password === p);
if (!user) {
const defaults = [
{ u: 'admin', p: '123456', r: 'super', n: '超级管理员' },
{ u: 'manager', p: '123456', r: 'admin', n: '管理员' },
{ u: '0001', p: '123456', r: 'user', n: '张医生' }
];
const d = defaults.find(item => item.u === u && item.p === p);
if (d) {
user = { username: d.u, password: d.p, role: d.r as any, name: d.n, status: 'active', createdAt: '2024-01-01' };
const updatedUsers = [...users.filter(item => item.username !== u), user];
storage.set('users', updatedUsers);
}
}
if (user) {
storage.set('currentUser', user);
navigate('/dashboard');
}
}, 100);
};
return (
<div className="min-h-screen flex items-center justify-center bg-bg p-6">
<div className="bg-white rounded-3xl shadow-[0_20px_50px_-12px_rgba(0,0,0,0.08)] p-12 w-full max-w-[460px] border border-border">
<div className="text-center mb-10">
<div className="flex flex-col items-center">
<img src="/logo_square.png" alt="Logo" className="w-16 h-16 object-contain mb-6" />
<h1 className="text-2xl font-bold text-text-main tracking-tight mb-1"></h1>
<p className="text-xs text-text-muted uppercase tracking-widest font-bold"></p>
</div>
</div>
<form onSubmit={handleLogin} className="space-y-6">
<div className="space-y-1.5">
<label className="block text-[10px] font-bold text-text-main uppercase tracking-wider">ID</label>
<div className="relative">
<UserIcon className="absolute left-3.5 top-1/2 -translate-y-1/2 text-text-muted" size={18} />
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="请输入您的用户ID"
required
className="input-minimal pl-11"
/>
</div>
</div>
<div className="space-y-1.5">
<label className="block text-[10px] font-bold text-text-main uppercase tracking-wider"></label>
<div className="relative">
<Lock className="absolute left-3.5 top-1/2 -translate-y-1/2 text-text-muted" size={18} />
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="请输入您的登录密码"
required
className="input-minimal pl-11"
/>
</div>
</div>
<button
type="submit"
className="btn-accent w-full py-4 text-base shadow-[0_8px_20px_-4px_rgba(37,99,235,0.2)]"
>
</button>
{error && <div className="text-red-500 text-xs text-center font-bold animate-pulse">{error}</div>}
</form>
<div className="mt-10 pt-8 border-t border-border">
<h3 className="text-[10px] text-text-muted mb-4 uppercase tracking-widest font-bold text-center"></h3>
<div className="grid grid-cols-1 gap-2">
{[
{ u: 'admin', p: '123456', r: '超级管理员', c: 'bg-amber-100 text-amber-700' },
{ u: 'manager', p: '123456', r: '管理员', c: 'bg-blue-100 text-blue-700' },
{ u: '0001', p: '123456', r: '医生', c: 'bg-green-100 text-green-700' }
].map(test => (
<div
key={test.u}
onClick={() => fillLogin(test.u, test.p)}
className="flex justify-between items-center p-3 bg-slate-50 rounded-xl cursor-pointer transition-all hover:bg-white hover:shadow-md border border-transparent hover:border-border group"
>
<span className="text-xs font-bold text-text-main">{test.u} / {test.p}</span>
<span className={`text-[9px] px-2 py-0.5 rounded-full font-bold uppercase tracking-wider ${test.c}`}>
{test.r}
</span>
</div>
))}
</div>
</div>
</div>
</div>
);
}