|
@@ -19,7 +19,8 @@ import type { CreateDisabledPersonRequest, UpdateDisabledPersonRequest } from '.
|
|
|
import { DISABILITY_TYPES, getDisabilityTypeLabel } from '@d8d/allin-enums';
|
|
import { DISABILITY_TYPES, getDisabilityTypeLabel } from '@d8d/allin-enums';
|
|
|
import { DISABILITY_LEVELS, getDisabilityLevelLabel } from '@d8d/allin-enums';
|
|
import { DISABILITY_LEVELS, getDisabilityLevelLabel } from '@d8d/allin-enums';
|
|
|
import { AreaSelect } from '@d8d/area-management-ui/components';
|
|
import { AreaSelect } from '@d8d/area-management-ui/components';
|
|
|
-import { FileSelector } from '@d8d/file-management-ui/components';
|
|
|
|
|
|
|
+import PhotoUploadField, { type PhotoItem } from './PhotoUploadField';
|
|
|
|
|
+import PhotoPreview from './PhotoPreview';
|
|
|
|
|
|
|
|
interface DisabilityPersonSearchParams {
|
|
interface DisabilityPersonSearchParams {
|
|
|
page: number;
|
|
page: number;
|
|
@@ -35,6 +36,8 @@ const DisabilityPersonManagement: React.FC = () => {
|
|
|
const [personToDelete, setPersonToDelete] = useState<number | null>(null);
|
|
const [personToDelete, setPersonToDelete] = useState<number | null>(null);
|
|
|
const [viewDialogOpen, setViewDialogOpen] = useState(false);
|
|
const [viewDialogOpen, setViewDialogOpen] = useState(false);
|
|
|
const [personToView, setPersonToView] = useState<number | null>(null);
|
|
const [personToView, setPersonToView] = useState<number | null>(null);
|
|
|
|
|
+ const [createPhotos, setCreatePhotos] = useState<PhotoItem[]>([]);
|
|
|
|
|
+ const [updatePhotos, setUpdatePhotos] = useState<PhotoItem[]>([]);
|
|
|
|
|
|
|
|
// 表单实例 - 创建表单
|
|
// 表单实例 - 创建表单
|
|
|
const createForm = useForm<CreateDisabledPersonRequest>({
|
|
const createForm = useForm<CreateDisabledPersonRequest>({
|
|
@@ -85,12 +88,12 @@ const DisabilityPersonManagement: React.FC = () => {
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // 查看详情查询
|
|
|
|
|
|
|
+ // 查看详情查询 - 使用聚合API获取包含照片的完整数据
|
|
|
const { data: viewData } = useQuery({
|
|
const { data: viewData } = useQuery({
|
|
|
- queryKey: ['disabled-person-detail', personToView],
|
|
|
|
|
|
|
+ queryKey: ['disabled-person-aggregated-detail', personToView],
|
|
|
queryFn: async () => {
|
|
queryFn: async () => {
|
|
|
if (!personToView) return null;
|
|
if (!personToView) return null;
|
|
|
- const res = await disabilityClientManager.get().getDisabledPerson[':id'].$get({
|
|
|
|
|
|
|
+ const res = await disabilityClientManager.get().getAggregatedDisabledPerson[':id'].$get({
|
|
|
param: { id: personToView }
|
|
param: { id: personToView }
|
|
|
});
|
|
});
|
|
|
if (res.status !== 200) throw new Error('获取残疾人详情失败');
|
|
if (res.status !== 200) throw new Error('获取残疾人详情失败');
|
|
@@ -102,7 +105,22 @@ const DisabilityPersonManagement: React.FC = () => {
|
|
|
// 创建残疾人
|
|
// 创建残疾人
|
|
|
const createMutation = useMutation({
|
|
const createMutation = useMutation({
|
|
|
mutationFn: async (data: CreateDisabledPersonRequest) => {
|
|
mutationFn: async (data: CreateDisabledPersonRequest) => {
|
|
|
- const res = await disabilityClientManager.get().createDisabledPerson.$post({ json: data });
|
|
|
|
|
|
|
+ // 准备聚合数据,包含照片
|
|
|
|
|
+ const aggregatedData = {
|
|
|
|
|
+ personInfo: data,
|
|
|
|
|
+ photos: createPhotos
|
|
|
|
|
+ .filter(photo => photo.fileId !== null)
|
|
|
|
|
+ .map(photo => ({
|
|
|
|
|
+ photoType: photo.photoType,
|
|
|
|
|
+ fileId: photo.fileId!,
|
|
|
|
|
+ canDownload: photo.canDownload
|
|
|
|
|
+ }))
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 使用聚合API创建残疾人信息
|
|
|
|
|
+ const res = await disabilityClientManager.get().createAggregatedDisabledPerson.$post({
|
|
|
|
|
+ json: aggregatedData
|
|
|
|
|
+ });
|
|
|
if (res.status !== 200) throw new Error('创建残疾人失败');
|
|
if (res.status !== 200) throw new Error('创建残疾人失败');
|
|
|
return await res.json();
|
|
return await res.json();
|
|
|
},
|
|
},
|
|
@@ -110,6 +128,7 @@ const DisabilityPersonManagement: React.FC = () => {
|
|
|
toast.success('残疾人创建成功');
|
|
toast.success('残疾人创建成功');
|
|
|
setIsModalOpen(false);
|
|
setIsModalOpen(false);
|
|
|
createForm.reset();
|
|
createForm.reset();
|
|
|
|
|
+ setCreatePhotos([]); // 重置照片状态
|
|
|
refetch();
|
|
refetch();
|
|
|
},
|
|
},
|
|
|
onError: (error) => {
|
|
onError: (error) => {
|
|
@@ -120,8 +139,48 @@ const DisabilityPersonManagement: React.FC = () => {
|
|
|
// 更新残疾人
|
|
// 更新残疾人
|
|
|
const updateMutation = useMutation({
|
|
const updateMutation = useMutation({
|
|
|
mutationFn: async (data: UpdateDisabledPersonRequest) => {
|
|
mutationFn: async (data: UpdateDisabledPersonRequest) => {
|
|
|
- const res = await disabilityClientManager.get().updateDisabledPerson.$post({
|
|
|
|
|
- json: data
|
|
|
|
|
|
|
+ // 准备聚合数据,包含照片
|
|
|
|
|
+ // 注意:我们需要从表单中获取完整数据,因为data只包含更新的字段
|
|
|
|
|
+ const formData = updateForm.getValues();
|
|
|
|
|
+
|
|
|
|
|
+ // 为必填字段提供默认值,确保类型安全
|
|
|
|
|
+ const personInfo = {
|
|
|
|
|
+ name: formData.name || '',
|
|
|
|
|
+ gender: formData.gender || '男',
|
|
|
|
|
+ idCard: formData.idCard || '',
|
|
|
|
|
+ disabilityId: formData.disabilityId || '',
|
|
|
|
|
+ disabilityType: formData.disabilityType || '',
|
|
|
|
|
+ disabilityLevel: formData.disabilityLevel || '',
|
|
|
|
|
+ idAddress: formData.idAddress || '',
|
|
|
|
|
+ phone: formData.phone || '',
|
|
|
|
|
+ province: formData.province || '',
|
|
|
|
|
+ city: formData.city || '',
|
|
|
|
|
+ // 可选字段
|
|
|
|
|
+ district: formData.district,
|
|
|
|
|
+ detailedAddress: formData.detailedAddress,
|
|
|
|
|
+ nation: formData.nation,
|
|
|
|
|
+ isMarried: formData.isMarried,
|
|
|
|
|
+ canDirectContact: formData.canDirectContact,
|
|
|
|
|
+ isInBlackList: formData.isInBlackList,
|
|
|
|
|
+ jobStatus: formData.jobStatus,
|
|
|
|
|
+ id: data.id // 确保包含ID
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const aggregatedData = {
|
|
|
|
|
+ personInfo,
|
|
|
|
|
+ photos: updatePhotos
|
|
|
|
|
+ .filter(photo => photo.fileId !== null)
|
|
|
|
|
+ .map(photo => ({
|
|
|
|
|
+ photoType: photo.photoType,
|
|
|
|
|
+ fileId: photo.fileId!,
|
|
|
|
|
+ canDownload: photo.canDownload
|
|
|
|
|
+ }))
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 使用聚合API更新残疾人信息
|
|
|
|
|
+ const res = await disabilityClientManager.get().updateAggregatedDisabledPerson[':id']['$put']({
|
|
|
|
|
+ param: { id: data.id! },
|
|
|
|
|
+ json: aggregatedData
|
|
|
});
|
|
});
|
|
|
if (res.status !== 200) throw new Error('更新残疾人失败');
|
|
if (res.status !== 200) throw new Error('更新残疾人失败');
|
|
|
return await res.json();
|
|
return await res.json();
|
|
@@ -129,6 +188,7 @@ const DisabilityPersonManagement: React.FC = () => {
|
|
|
onSuccess: () => {
|
|
onSuccess: () => {
|
|
|
toast.success('残疾人更新成功');
|
|
toast.success('残疾人更新成功');
|
|
|
setIsModalOpen(false);
|
|
setIsModalOpen(false);
|
|
|
|
|
+ setUpdatePhotos([]); // 重置照片状态
|
|
|
refetch();
|
|
refetch();
|
|
|
},
|
|
},
|
|
|
onError: (error) => {
|
|
onError: (error) => {
|
|
@@ -165,6 +225,7 @@ const DisabilityPersonManagement: React.FC = () => {
|
|
|
const handleCreateOpen = () => {
|
|
const handleCreateOpen = () => {
|
|
|
setIsCreateForm(true);
|
|
setIsCreateForm(true);
|
|
|
createForm.reset();
|
|
createForm.reset();
|
|
|
|
|
+ setCreatePhotos([]); // 重置创建照片状态
|
|
|
setIsModalOpen(true);
|
|
setIsModalOpen(true);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -172,6 +233,32 @@ const DisabilityPersonManagement: React.FC = () => {
|
|
|
const handleEditOpen = (person: any) => {
|
|
const handleEditOpen = (person: any) => {
|
|
|
setIsCreateForm(false);
|
|
setIsCreateForm(false);
|
|
|
updateForm.reset(person);
|
|
updateForm.reset(person);
|
|
|
|
|
+ setUpdatePhotos([]); // 先重置,然后加载已有照片
|
|
|
|
|
+
|
|
|
|
|
+ // 加载聚合数据获取照片信息
|
|
|
|
|
+ if (person.id) {
|
|
|
|
|
+ disabilityClientManager.get().getAggregatedDisabledPerson[':id']['$get']({
|
|
|
|
|
+ param: { id: person.id }
|
|
|
|
|
+ }).then(async (res) => {
|
|
|
|
|
+ if (res.status === 200) {
|
|
|
|
|
+ const aggregatedData = await res.json();
|
|
|
|
|
+ if (aggregatedData && aggregatedData.photos) {
|
|
|
|
|
+ // 转换照片数据格式
|
|
|
|
|
+ const photos: PhotoItem[] = aggregatedData.photos.map((photo: any) => ({
|
|
|
|
|
+ photoType: photo.photoType,
|
|
|
|
|
+ fileId: photo.fileId,
|
|
|
|
|
+ canDownload: photo.canDownload,
|
|
|
|
|
+ tempId: `existing-${photo.id || Date.now()}`
|
|
|
|
|
+ }));
|
|
|
|
|
+ setUpdatePhotos(photos);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(error => {
|
|
|
|
|
+ console.error('加载照片数据失败:', error);
|
|
|
|
|
+ toast.error('加载照片数据失败');
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
setIsModalOpen(true);
|
|
setIsModalOpen(true);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -548,21 +635,13 @@ const DisabilityPersonManagement: React.FC = () => {
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div>
|
|
|
|
|
- <FormLabel>照片上传</FormLabel>
|
|
|
|
|
- <div className="mt-2">
|
|
|
|
|
- <FileSelector
|
|
|
|
|
- value={null}
|
|
|
|
|
- onChange={() => {
|
|
|
|
|
- // 这里需要处理文件ID的存储
|
|
|
|
|
- // 由于后端Schema没有photoFileId字段,暂时注释
|
|
|
|
|
- // createForm.setValue('photoFileId', fileId as number);
|
|
|
|
|
- }}
|
|
|
|
|
- accept="image/*"
|
|
|
|
|
- filterType="image"
|
|
|
|
|
- placeholder="选择或上传照片"
|
|
|
|
|
- />
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div className="col-span-full">
|
|
|
|
|
+ <PhotoUploadField
|
|
|
|
|
+ value={createPhotos}
|
|
|
|
|
+ onChange={setCreatePhotos}
|
|
|
|
|
+ photoTypes={['身份证照片', '残疾证照片', '个人照片', '其他照片']}
|
|
|
|
|
+ maxPhotos={5}
|
|
|
|
|
+ />
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</form>
|
|
</form>
|
|
@@ -781,6 +860,16 @@ const DisabilityPersonManagement: React.FC = () => {
|
|
|
)}
|
|
)}
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 照片上传 */}
|
|
|
|
|
+ <div className="col-span-full">
|
|
|
|
|
+ <PhotoUploadField
|
|
|
|
|
+ value={updatePhotos}
|
|
|
|
|
+ onChange={setUpdatePhotos}
|
|
|
|
|
+ photoTypes={['身份证照片', '残疾证照片', '个人照片', '其他照片']}
|
|
|
|
|
+ maxPhotos={5}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</form>
|
|
</form>
|
|
@@ -817,65 +906,84 @@ const DisabilityPersonManagement: React.FC = () => {
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
|
<div>
|
|
<div>
|
|
|
<label className="text-sm font-medium">姓名</label>
|
|
<label className="text-sm font-medium">姓名</label>
|
|
|
- <p className="text-sm text-muted-foreground">{viewData.name}</p>
|
|
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">{viewData.personInfo.name}</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div>
|
|
<div>
|
|
|
<label className="text-sm font-medium">性别</label>
|
|
<label className="text-sm font-medium">性别</label>
|
|
|
- <p className="text-sm text-muted-foreground">{viewData.gender}</p>
|
|
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">{viewData.personInfo.gender}</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div>
|
|
<div>
|
|
|
<label className="text-sm font-medium">身份证号</label>
|
|
<label className="text-sm font-medium">身份证号</label>
|
|
|
- <p className="text-sm text-muted-foreground">{viewData.idCard}</p>
|
|
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">{viewData.personInfo.idCard}</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div>
|
|
<div>
|
|
|
<label className="text-sm font-medium">残疾证号</label>
|
|
<label className="text-sm font-medium">残疾证号</label>
|
|
|
- <p className="text-sm text-muted-foreground">{viewData.disabilityId}</p>
|
|
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">{viewData.personInfo.disabilityId}</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div>
|
|
<div>
|
|
|
<label className="text-sm font-medium">残疾类型</label>
|
|
<label className="text-sm font-medium">残疾类型</label>
|
|
|
- <p className="text-sm text-muted-foreground">{viewData.disabilityType}</p>
|
|
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">{viewData.personInfo.disabilityType}</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div>
|
|
<div>
|
|
|
<label className="text-sm font-medium">残疾等级</label>
|
|
<label className="text-sm font-medium">残疾等级</label>
|
|
|
- <p className="text-sm text-muted-foreground">{viewData.disabilityLevel}</p>
|
|
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">{viewData.personInfo.disabilityLevel}</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div>
|
|
<div>
|
|
|
<label className="text-sm font-medium">联系电话</label>
|
|
<label className="text-sm font-medium">联系电话</label>
|
|
|
- <p className="text-sm text-muted-foreground">{viewData.phone}</p>
|
|
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">{viewData.personInfo.phone}</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div>
|
|
<div>
|
|
|
<label className="text-sm font-medium">身份证地址</label>
|
|
<label className="text-sm font-medium">身份证地址</label>
|
|
|
- <p className="text-sm text-muted-foreground">{viewData.idAddress}</p>
|
|
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">{viewData.personInfo.idAddress}</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div className="col-span-1 md:col-span-2">
|
|
<div className="col-span-1 md:col-span-2">
|
|
|
<label className="text-sm font-medium">居住地址</label>
|
|
<label className="text-sm font-medium">居住地址</label>
|
|
|
<p className="text-sm text-muted-foreground">
|
|
<p className="text-sm text-muted-foreground">
|
|
|
- {viewData.province} {viewData.city} {viewData.district} {viewData.detailedAddress}
|
|
|
|
|
|
|
+ {viewData.personInfo.province} {viewData.personInfo.city} {viewData.personInfo.district} {viewData.personInfo.detailedAddress}
|
|
|
</p>
|
|
</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div>
|
|
<div>
|
|
|
<label className="text-sm font-medium">民族</label>
|
|
<label className="text-sm font-medium">民族</label>
|
|
|
- <p className="text-sm text-muted-foreground">{viewData.nation || '未填写'}</p>
|
|
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">{viewData.personInfo.nation || '未填写'}</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div>
|
|
<div>
|
|
|
<label className="text-sm font-medium">婚姻状况</label>
|
|
<label className="text-sm font-medium">婚姻状况</label>
|
|
|
<p className="text-sm text-muted-foreground">
|
|
<p className="text-sm text-muted-foreground">
|
|
|
- {viewData.isMarried === 1 ? '已婚' : viewData.isMarried === 0 ? '未婚' : '未知'}
|
|
|
|
|
|
|
+ {viewData.personInfo.isMarried === 1 ? '已婚' : viewData.personInfo.isMarried === 0 ? '未婚' : '未知'}
|
|
|
</p>
|
|
</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div>
|
|
<div>
|
|
|
<label className="text-sm font-medium">创建时间</label>
|
|
<label className="text-sm font-medium">创建时间</label>
|
|
|
<p className="text-sm text-muted-foreground">
|
|
<p className="text-sm text-muted-foreground">
|
|
|
- {format(new Date(viewData.createTime), 'yyyy-MM-dd HH:mm:ss')}
|
|
|
|
|
|
|
+ {format(new Date(viewData.personInfo.createTime), 'yyyy-MM-dd HH:mm:ss')}
|
|
|
</p>
|
|
</p>
|
|
|
</div>
|
|
</div>
|
|
|
<div>
|
|
<div>
|
|
|
<label className="text-sm font-medium">更新时间</label>
|
|
<label className="text-sm font-medium">更新时间</label>
|
|
|
<p className="text-sm text-muted-foreground">
|
|
<p className="text-sm text-muted-foreground">
|
|
|
- {format(new Date(viewData.updateTime), 'yyyy-MM-dd HH:mm:ss')}
|
|
|
|
|
|
|
+ {format(new Date(viewData.personInfo.updateTime), 'yyyy-MM-dd HH:mm:ss')}
|
|
|
</p>
|
|
</p>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 照片预览 */}
|
|
|
|
|
+ {viewData.photos && viewData.photos.length > 0 && (
|
|
|
|
|
+ <div className="mt-6 pt-6 border-t">
|
|
|
|
|
+ <PhotoPreview
|
|
|
|
|
+ photos={viewData.photos.map((photo: any) => ({
|
|
|
|
|
+ id: photo.id,
|
|
|
|
|
+ photoType: photo.photoType,
|
|
|
|
|
+ fileId: photo.fileId,
|
|
|
|
|
+ fileUrl: photo.fileUrl,
|
|
|
|
|
+ fileName: `照片-${photo.photoType}`,
|
|
|
|
|
+ canDownload: photo.canDownload,
|
|
|
|
|
+ uploadTime: photo.uploadTime
|
|
|
|
|
+ }))}
|
|
|
|
|
+ title="照片"
|
|
|
|
|
+ showDownloadButton={true}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
|
|
|