import { useState, useCallback, useRef, useMemo } from 'react'; import { Table, Button, Space, Modal, Form, Input, Select, Badge, Popconfirm, message, Row, Col, } from 'antd'; import { PlusOutlined, SearchOutlined, EditOutlined, DeleteOutlined, } from '@ant-design/icons'; import { doctorApi, type Doctor, type CreateDoctorReq, type UpdateDoctorReq } from '../../api/health/doctors'; import { useDictionary } from '../../hooks/useDictionary'; import { AuthButton } from '../../components/AuthButton'; import { PageContainer } from '../../components/PageContainer'; import { EntityName } from '../../components/EntityName'; import { formatDateTime } from '../../utils/format'; import { usePaginatedData } from '../../hooks/usePaginatedData'; /** 科室选项 — 可后续改为从字典接口获取 */ const DEPARTMENT_FALLBACK = [ { value: '全科', label: '全科' }, { value: '内科', label: '内科' }, { value: '外科', label: '外科' }, { value: '儿科', label: '儿科' }, { value: '妇产科', label: '妇产科' }, { value: '骨科', label: '骨科' }, { value: '眼科', label: '眼科' }, { value: '口腔科', label: '口腔科' }, { value: '皮肤科', label: '皮肤科' }, { value: '中医科', label: '中医科' }, { value: '体检中心', label: '体检中心' }, ]; const TITLE_FALLBACK = [ { value: '住院医师', label: '住院医师' }, { value: '主治医师', label: '主治医师' }, { value: '副主任医师', label: '副主任医师' }, { value: '主任医师', label: '主任医师' }, { value: '护士', label: '护士' }, { value: '护师', label: '护师' }, { value: '主管护师', label: '主管护师' }, { value: '副主任护师', label: '副主任护师' }, { value: '主任护师', label: '主任护师' }, ]; const STATUS_OPTIONS = [ { value: 'online', label: '在线' }, { value: 'offline', label: '离线' }, { value: 'busy', label: '忙碌' }, ]; const ONLINE_STATUS_MAP: Record = { online: { status: 'success', text: '在线' }, offline: { status: 'default', text: '离线' }, busy: { status: 'processing', text: '忙碌' }, }; /** 筛选器类型 */ interface DoctorFilters { search: string; department: string | undefined; title: string | undefined; status: string | undefined; } export default function DoctorList() { const { options: DEPARTMENT_OPTIONS } = useDictionary('health_department', DEPARTMENT_FALLBACK); const { options: TITLE_OPTIONS } = useDictionary('health_title', TITLE_FALLBACK); const [modalOpen, setModalOpen] = useState(false); const [editing, setEditing] = useState(null); const [form] = Form.useForm(); // ---- 数据获取 ---- const fetcher = useCallback( async (page: number, pageSize: number, filters: DoctorFilters) => { return doctorApi.list({ page, page_size: pageSize, search: filters.search || undefined, department: filters.department || undefined, title: filters.title || undefined, }); }, [], ); const { data, total, page, loading, filters, setFilters, refresh, } = usePaginatedData(fetcher, { pageSize: 20, defaultFilters: { search: '', department: undefined, title: undefined, status: undefined }, }); // ---- 搜索防抖 ---- const debounceRef = useRef | null>(null); const handleSearchChange = useCallback((val: string) => { setFilters((prev) => ({ ...prev, search: val })); if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => refresh(1), 300); }, [setFilters, refresh]); const handleFilterChange = useCallback( (key: keyof DoctorFilters, value: string | undefined) => { setFilters((prev) => ({ ...prev, [key]: value })); refresh(1); }, [setFilters, refresh], ); const resetFilters = useCallback(() => { setFilters({ search: '', department: undefined, title: undefined, status: undefined }); refresh(1); }, [setFilters, refresh]); // ---- 新建 / 编辑 ---- const openCreate = () => { setEditing(null); form.resetFields(); setModalOpen(true); }; const openEdit = (record: Doctor) => { setEditing(record); form.setFieldsValue({ name: record.name, department: record.department, title: record.title, specialty: record.specialty, license_number: record.license_number, bio: record.bio, }); setModalOpen(true); }; const handleSubmit = async (values: { name: string; department?: string; title?: string; specialty?: string; license_number?: string; bio?: string; }) => { try { if (editing) { const req: UpdateDoctorReq & { version: number } = { name: values.name, department: values.department, title: values.title, specialty: values.specialty, license_number: values.license_number, bio: values.bio, version: editing.version, }; await doctorApi.update(editing.id, req); message.success('更新成功'); } else { const req: CreateDoctorReq = { name: values.name, department: values.department, title: values.title, specialty: values.specialty, license_number: values.license_number, bio: values.bio, }; await doctorApi.create(req); message.success('创建成功'); } setModalOpen(false); form.resetFields(); refresh(); } catch { message.error(editing ? '更新失败' : '创建失败'); } }; // ---- 删除 ---- const handleDelete = async (id: string) => { try { await doctorApi.delete(id); message.success('删除成功'); refresh(); } catch { message.error('删除失败'); } }; // ---- 列定义 ---- const columns = useMemo(() => [ { title: '姓名', dataIndex: 'name', key: 'name', width: 120, fixed: 'left' as const, }, { title: '科室', dataIndex: 'department', key: 'department', width: 120, render: (val: string) => val || '-', }, { title: '职称', dataIndex: 'title', key: 'title', width: 120, render: (val: string) => val || '-', }, { title: '专长', dataIndex: 'specialty', key: 'specialty', width: 200, ellipsis: true, render: (val: string) => val || '-', }, { title: '执业编号', dataIndex: 'license_number', key: 'license_number', width: 150, render: (val: string) => val || '-', }, { title: '关联用户', dataIndex: 'user_id', key: 'user_id', width: 120, render: (_: unknown, record: Doctor) => record.user_id ? ( ) : ( '-' ), }, { title: '在线状态', dataIndex: 'online_status', key: 'online_status', width: 100, render: (val: string) => { const cfg = ONLINE_STATUS_MAP[val] || { status: 'default' as const, text: val }; return ; }, }, { title: '创建时间', dataIndex: 'created_at', key: 'created_at', width: 180, render: (val: string) => formatDateTime(val), }, { title: '操作', key: 'action', width: 140, fixed: 'right' as const, render: (_: unknown, record: Doctor) => ( handleDelete(record.id)} okText="确定" cancelText="取消" > ), }, ], [openEdit, handleDelete]); return ( } value={filters.search} onChange={(e) => handleSearchChange(e.target.value)} allowClear style={{ width: 220 }} /> handleFilterChange('title', val)} options={TITLE_OPTIONS} allowClear style={{ width: 160 }} /> ); }