Kaynağa Gözat

feat(qualification): 资质编辑回显用code并映射practiceSubject;重复点击详情强制刷新抽屉;构建产物

code4eat 1 hafta önce
ebeveyn
işleme
4defebc72e
33 değiştirilmiş dosya ile 4008 ekleme ve 267 silme
  1. 33 1
      .umirc.ts
  2. 15 14
      src/app.tsx
  3. 5 1
      src/components/KCIMLeftList/index.tsx
  4. 368 0
      src/pages/certificateMana/components/AddCertificateModal.tsx
  5. 490 0
      src/pages/certificateMana/index.tsx
  6. 325 0
      src/pages/certificateMana/service.ts
  7. 134 0
      src/pages/certificateMana/style.less
  8. 65 0
      src/pages/dataFilling/fillingMana/authHisttory/index.tsx
  9. 35 0
      src/pages/dataFilling/fillingMana/authHisttory/style.less
  10. 82 0
      src/pages/dataFilling/fillingMana/generateTableData.tsx
  11. 657 0
      src/pages/dataFilling/fillingMana/index.tsx
  12. 101 0
      src/pages/dataFilling/fillingMana/service.ts
  13. 557 0
      src/pages/dataFilling/fillingMana/style.less
  14. 1 2
      src/pages/personalCenter/myApplication/index.tsx
  15. 1 4
      src/pages/personalCenter/myApplication/service.ts
  16. 1 0
      src/pages/personalCenter/myApplication/style.less
  17. 108 38
      src/pages/personalCenter/myQualifications/index.tsx
  18. 8 0
      src/pages/personalCenter/myQualifications/service.ts
  19. 62 4
      src/pages/personalCenter/myQualifications/style.less
  20. 552 88
      src/pages/qualificationMana/doctorQualifiAuth/index.tsx
  21. 57 0
      src/pages/qualificationMana/doctorQualifiAuth/service.ts
  22. 69 2
      src/pages/qualificationMana/doctorQualifiAuth/style.less
  23. 2 2
      src/pages/qualificationMana/qualifiDicMana/index.tsx
  24. 3 2
      src/pages/qualificationMana/qualificationApproval/index.tsx
  25. 44 4
      src/pages/qualificationMana/qualificationApproval/style.less
  26. 112 34
      src/pages/qualificationMana/qualificationAuth/index.tsx
  27. 8 0
      src/pages/qualificationMana/qualificationAuth/service.ts
  28. 3 2
      src/pages/qualificationMana/qualificationBatchAuth/DoctorSelection/index.tsx
  29. 69 63
      src/pages/static/index.tsx
  30. 14 5
      src/pages/static/service.ts
  31. 27 1
      src/utils/tooljs.ts
  32. BIN
      static/riqijiaobiao_gray.png
  33. BIN
      static/riqijiaobiao_red.png

+ 33 - 1
.umirc.ts

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-08-02 11:28:57
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-17 10:25:50
+ * @LastEditTime: 2025-02-28 10:35:48
  * @FilePath: /MediResourceManaSys/.umirc.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -24,6 +24,13 @@ export default defineConfig({
       prefixCls: 'mrms-ant',
     },
   },
+  locale: {
+    default: 'zh-CN',
+    antd: true,
+    title: false,
+    baseNavigator: true,
+    baseSeparator: '-',
+  },
   // alias:{
   //   'ace-builds': path.resolve(__dirname, './node_modules/ace-builds/src-noconflict'),
   // },
@@ -144,6 +151,31 @@ export default defineConfig({
         },
       ]
     },
+    {
+      path:'/certificateMana',
+      name:'证书管理',
+      routes:[
+        {
+          name:'证书管理',
+          path: '/certificateMana',
+          component: './certificateMana/index',
+        },
+      ]
+    },
+    {
+      path:'/indicator',
+      name:'指标一览',
+      routes:[
+      {}
+      ]
+    },
+    {
+      path:'/dataFilling',
+      name:'数据填报',
+      routes:[
+        {}
+      ]
+    },
     { path: '*', component: '@/pages/404' },
     
  

+ 15 - 14
src/app.tsx

@@ -18,7 +18,7 @@ import { JSXElementConstructor, Key, ReactElement, ReactFragment, ReactPortal, u
 
 
 import './utils/zhongtaiC'
-import { RuntimeAntdConfig } from '@umijs/max';
+
 import { useLocation } from '@umijs/max';
 import ResizableContainer from './components/ResizableContainer/index';
 import { IRoute } from '@umijs/max';
@@ -266,7 +266,6 @@ export const request: RequestConfig = {
                 //console.log({code,'response.data':response.data});
                 return response.data
               } else {
-
                 notification.error({
                   top: 72,
                   message: '提示',
@@ -358,7 +357,7 @@ export function patchClientRoutes({ routes }: { routes: any }) {
         element: <Static />,
       }));
 
-      const lanhuPagePaths = [...new Array(5).keys()].map((a, index) => ({
+      const lanhuPagePaths = [...new Array(10).keys()].map((a, index) => ({
         path: `${treeData.path == '/' ? '' : treeData.path}/static/${index}`,
         exact: true,
         element: <Static />,
@@ -381,6 +380,7 @@ export function patchClientRoutes({ routes }: { routes: any }) {
 
   }
 
+
   treeLoop(routes[0]);
 
 }
@@ -486,17 +486,17 @@ export const layout = ({ initialState, setInitialState }: { initialState: any, s
               if (!item.children || item.children.length === 0) {
                 let queryParams = [];
 
-                if (newItem.softUrl) {
-                  queryParams.push(`softUrl=${newItem.softUrl}`);
-                }
+                // if (newItem.softUrl) {
+                //   queryParams.push(`softUrl=${newItem.softUrl}`);
+                // }
 
-                if (newItem.youshuUrl) {
-                  queryParams.push(`youshuUrl=${newItem.youshuUrl}`);
-                }
+                // if (newItem.youshuUrl) {
+                //   queryParams.push(`youshuUrl=${newItem.youshuUrl}`);
+                // }
 
-                if (queryParams.length > 0) {
-                  newItem.path = `${newItem.path}?${queryParams.join('&')}`;
-                }
+                // if (queryParams.length > 0) {
+                //   newItem.path = `${newItem.path}?${queryParams.join('&')}`;
+                // }
 
                 return newItem; // 返回新的叶子节点
               }
@@ -511,8 +511,9 @@ export const layout = ({ initialState, setInitialState }: { initialState: any, s
               return menuArray.map(processMenuItem);
             }
 
-            const _menu = processMenu(data);
-
+            const _menu = processMenu([
+              ...data
+            ]);
             setInitialState((t: any) => ({ ...t, spacicalPageParamsType: _menu, memuData: data, userData: JSON.parse(userData as string) }));
 
 

+ 5 - 1
src/components/KCIMLeftList/index.tsx

@@ -68,6 +68,8 @@ const findFirstLeafNodeWithParents: any = (node: any, children: string) => {
   return findNode(node, []);
 };
 
+let searchKeyword:string|undefined = undefined;
+
 export const KCIMLeftList = (props: KCIMLeftListProps) => {
   const {
     searchKey,
@@ -90,6 +92,7 @@ export const KCIMLeftList = (props: KCIMLeftListProps) => {
   const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
   const [autoExpandParent, setAutoExpandParent] = useState(true);
   const [defaultSelectedKeys, set_defaultSelectedKeys] = useState<Key[]>([]);
+ 
 
   const onSelect: TreeProps['onSelect'] = (selectedKeys, info) => {
     const { node } = info;
@@ -126,7 +129,7 @@ export const KCIMLeftList = (props: KCIMLeftListProps) => {
   };
 
   useEffect(() => {
-    onChange && onChange(currentSelected);
+    onChange && onChange({...currentSelected,searchKeyword});
   }, [currentSelected]);
 
   useEffect(() => {
@@ -158,6 +161,7 @@ export const KCIMLeftList = (props: KCIMLeftListProps) => {
           autoComplete="off"
           suffix={<IconFont style={{ color: '#99A6BF' }} type="iconsousuo" />}
           onChange={(e) => {
+            searchKeyword = e.target.value;
             if (listType === 'list') {
               const result = dataSource.filter((item) =>
                 item[`${searchKey}`].includes(e.target.value),

+ 368 - 0
src/pages/certificateMana/components/AddCertificateModal.tsx

@@ -0,0 +1,368 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2025-01-07 14:30:00
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-08-14 15:54:33
+ * @FilePath: /KC-MiddlePlatform/src/pages/certificateMana/components/AddCertificateModal.tsx
+ * @Description: 新增证书弹窗组件
+ */
+
+import React, { useEffect, useState } from 'react';
+import { Modal, Form, Input, DatePicker, Upload, Button, Select, message } from 'antd';
+import { UploadOutlined } from '@ant-design/icons';
+import { getDoctorListForSelect, addCertificate, editCertificate } from '../service';
+import { debounce } from 'lodash';
+import { useModel } from '@umijs/max';
+import moment from 'moment';
+
+const { TextArea } = Input;
+const { RangePicker } = DatePicker;
+
+interface AddCertificateModalProps {
+    visible: boolean;
+    onCancel: () => void;
+    onSuccess: () => void;
+    currentDeptCode?: string; // 当前选中的科室代码
+    editData?: any; // 编辑数据
+}
+
+interface DoctorOption {
+    id: string;
+    name: string;
+    account: string;
+}
+
+const AddCertificateModal: React.FC<AddCertificateModalProps> = ({
+    visible,
+    onCancel,
+    onSuccess,
+    currentDeptCode,
+    editData
+}) => {
+    const { initialState } = useModel('@@initialState');
+    const [form] = Form.useForm();
+    const [loading, setLoading] = useState(false);
+    const [doctorOptions, setDoctorOptions] = useState<DoctorOption[]>([]);
+    const [doctorLoading, setDoctorLoading] = useState(false);
+    const [fileList, setFileList] = useState<any[]>([]);
+
+    // 加载医生列表
+    const loadDoctors = async (searchValue: string = '') => {
+        if (!currentDeptCode) {
+            setDoctorOptions([]);
+            return;
+        }
+
+        setDoctorLoading(true);
+        try {
+            const params = {
+                deptCode: currentDeptCode,
+                userName: searchValue, // 修正:使用userName作为搜索关键字
+                current: 1,
+                pageSize: 50 // 限制返回数量
+            };
+
+            const response = await getDoctorListForSelect(params);
+            
+            const doctors = response?.list || response?.data || [];
+            
+            // 转换为下拉选项格式
+            const options: DoctorOption[] = doctors.map((doctor: any) => ({
+                id: doctor.userInfo?.id || doctor.id || doctor.userId,
+                name: doctor.userInfo?.name || doctor.name || doctor.doctorName,
+                account: doctor.userInfo?.account || doctor.account || doctor.doctorCode || doctor.workNo
+            }));
+
+            setDoctorOptions(options);
+        } catch (error) {
+            console.error('获取医生列表失败:', error);
+            message.error('获取医生列表失败');
+        } finally {
+            setDoctorLoading(false);
+        }
+    };
+
+    // 防抖搜索医生,增加延迟减少请求频率
+    const searchDoctors = debounce(loadDoctors, 500);
+
+    // 处理医生搜索
+    const handleDoctorSearch = (value: string) => {
+        searchDoctors(value);
+    };
+
+    // 当弹窗打开时自动加载医生列表
+    useEffect(() => {
+        if (visible && currentDeptCode) {
+            loadDoctors(); // 初始加载,不传搜索关键词
+        } else if (!visible) {
+            // 弹窗关闭时清空选项和表单
+            setDoctorOptions([]);
+            form.resetFields();
+        }
+    }, [visible, currentDeptCode]);
+
+    // 当编辑数据变化时,设置表单初始值
+    useEffect(() => {
+        if (visible && editData) {
+            const initialValues = getInitialValues();
+            form.setFieldsValue(initialValues);
+            
+            // 设置文件列表
+            const files = [{
+                uid: editData.id,
+                name: editData.fileName,
+                url: editData.url,
+                status: 'done'
+            }];
+            setFileList(files);
+            
+            // 为医生选择设置对应的选项(如果编辑数据中包含医生信息)
+            if (editData.empNameAndCode) {
+                // 从 empNameAndCode 解析医生姓名和工号
+                // 格式可能是 "姓名(工号)" 或其他格式
+                const match = editData.empNameAndCode.match(/^(.+)\((.+)\)$/);
+                if (match) {
+                    const [, name, account] = match;
+                    setDoctorOptions([{
+                        id: editData.userId,
+                        name: name.trim(),
+                        account: account.trim()
+                    }]);
+                }
+            }
+        } else if (!visible) {
+            // 弹窗关闭时清空文件列表
+            setFileList([]);
+        }
+    }, [visible, editData]);
+
+    // 处理表单提交
+    const handleSubmit = async () => {
+        try {
+            const values = await form.validateFields();
+            setLoading(true);
+
+            // 检查文件上传
+            const { doctorId, validityPeriod, attachmentDescription } = values;
+            if (!fileList || fileList.length === 0) {
+                message.error('请上传证书附件');
+                return;
+            }
+
+            // 处理日期范围 - 按照个人中心的格式处理
+            // validityPeriod是moment对象数组,需要转换为字符串数组
+            let dateRange = undefined;
+            if (validityPeriod && validityPeriod.length === 2) {
+                dateRange = [
+                    validityPeriod[0].format('YYYY-MM-DD'),
+                    validityPeriod[1].format('YYYY-MM-DD')
+                ];
+            }
+            
+            // 按照个人中心的数据格式组织参数
+            let submitData;
+            const file = fileList[0];
+            
+            if (file.response) {
+                // 新上传的文件,有response
+                const response = file.response;
+                
+                // 检查上传是否成功
+                if (response.code && response.code !== 0 && response.code !== 200) {
+                    message.error(response.msg || '文件上传失败!');
+                    return;
+                }
+                
+                if (!response.data || !response.data.downUrl) {
+                    message.error('文件上传返回数据异常,缺少下载地址!');
+                    return;
+                }
+                
+                const { downUrl } = response.data;
+                const { name } = file;
+                submitData = {
+                    id: editData ? editData.id : null, // 编辑时使用原有id,新增时为null
+                    userId: doctorId,
+                    fileName: name,
+                    url: downUrl,
+                    description: attachmentDescription || '',
+                    activeDate: dateRange ? dateRange[0] : undefined,
+                    expireDate: dateRange ? dateRange[1] : undefined,
+                };
+            } else {
+                // 已有文件(编辑时的情况)
+                const { name, url } = file;
+                submitData = {
+                    id: editData ? editData.id : null, // 编辑时使用原有id,新增时为null
+                    userId: doctorId,
+                    fileName: name,
+                    url: url,
+                    description: attachmentDescription || '',
+                    activeDate: dateRange ? dateRange[0] : undefined,
+                    expireDate: dateRange ? dateRange[1] : undefined,
+                };
+            }
+
+            // 根据是否是编辑模式调用不同的接口
+            const resp = editData 
+                ? await editCertificate(submitData)
+                : await addCertificate(submitData);
+            
+            if (resp) {
+                message.success(editData ? '修改证书成功!' : '新增证书成功!');
+                form.resetFields();
+                setDoctorOptions([]);
+                onSuccess();
+            } else {
+                message.error('保存失败:接口返回空响应');
+            }
+        } catch (error) {
+            console.error(editData ? '修改证书失败:' : '新增证书失败:', error);
+            message.error(editData ? '修改证书失败,请重试' : '新增证书失败,请重试');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    // 处理取消
+    const handleCancel = () => {
+        form.resetFields();
+        setDoctorOptions([]);
+        setFileList([]);
+        onCancel();
+    };
+
+    // 文件上传属性(参考个人中心的上传配置)
+    const uploadProps = {
+        name: 'file',
+        action: '/gateway/centerSys/api/upload',
+        maxCount: 1,
+        fileList: fileList,
+        headers: {
+            token: initialState?.userData?.token as string
+        },
+        onChange: (info: any) => {
+            const { status, response } = info.file;
+            // 更新文件列表状态
+            setFileList(info.fileList);
+            
+            if (status === 'done') {
+                // 如果后端返回 code==500,表示上传失败,msg 为错误提示
+                if (response && response.code === 500) {
+                    message.error(response.msg || `${info.file.name} 文件上传失败。`);
+                } else {
+                    // 上传成功提示
+                    message.success(`${info.file.name} 文件上传成功。`);
+                }
+            } else if (status === 'error') {
+                // 上传失败提示
+                message.error(`${info.file.name} 文件上传失败。`);
+            }
+        }
+    };
+
+    // 构造表单初始值
+    const getInitialValues = () => {
+        if (!editData) return {};
+        
+        // 构造日期范围
+        let validityPeriod = undefined;
+        if (editData.activeDate && editData.expireDate) {
+            validityPeriod = [
+                moment(editData.activeDate),
+                moment(editData.expireDate)
+            ];
+        }
+        
+        return {
+            doctorId: editData.userId,
+            validityPeriod,
+            attachmentDescription: editData.description
+        };
+    };
+
+    return (
+        <Modal
+            title={editData ? "修改" : "新增"}
+            open={visible}
+            onCancel={handleCancel}
+            footer={[
+                <Button key="cancel" onClick={handleCancel}>
+                    取消
+                </Button>,
+                <Button key="submit" type="primary" loading={loading} onClick={handleSubmit}>
+                    {editData ? "保存" : "上传"}
+                </Button>
+            ]}
+            width={352}
+            destroyOnClose
+        >
+            <Form
+                form={form}
+                layout="vertical"
+                requiredMark={false}
+                initialValues={getInitialValues()}
+            >
+                <Form.Item
+                    label={<span><span style={{ color: 'red' }}>*</span>医生:</span>}
+                    name="doctorId"
+                    rules={[{ required: true, message: '请选择医生' }]}
+                >
+                    <Select
+                        showSearch
+                        placeholder="请选择"
+                        notFoundContent={doctorLoading ? '搜索中...' : '暂无数据'}
+                        loading={doctorLoading}
+                        filterOption={false}
+                        onSearch={handleDoctorSearch}
+                        onDropdownVisibleChange={(open) => {
+                            // 当下拉框打开且还没有数据时,也尝试加载一次
+                            if (open && doctorOptions.length === 0 && currentDeptCode) {
+                                loadDoctors();
+                            }
+                        }}
+                        onClear={() => setDoctorOptions([])}
+                        allowClear
+                    >
+                        {doctorOptions.map(doctor => (
+                            <Select.Option key={doctor.id} value={doctor.id}>
+                                {doctor.name}({doctor.account})
+                            </Select.Option>
+                        ))}
+                    </Select>
+                </Form.Item>
+
+                <Form.Item
+                    label={<span><span style={{ color: 'red' }}>*</span>上传证书附件:</span>}
+                >
+                    <Upload {...uploadProps}>
+                        <Button icon={<UploadOutlined />}>点击上传</Button>
+                    </Upload>
+                </Form.Item>
+
+                <Form.Item
+                    label="有效期限:"
+                    name="validityPeriod"
+                >
+                    <RangePicker
+                        style={{ width: '100%' }}
+                        placeholder={['开始日期', '结束日期']}
+                        format="YYYY-MM-DD"
+                    />
+                </Form.Item>
+
+                <Form.Item
+                    label="附件说明:"
+                    name="attachmentDescription"
+                >
+                    <TextArea
+                        placeholder="请输入"
+                        rows={4}
+                    />
+                </Form.Item>
+            </Form>
+        </Modal>
+    );
+};
+
+export default AddCertificateModal;

+ 490 - 0
src/pages/certificateMana/index.tsx

@@ -0,0 +1,490 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2025-08-06 10:12:30
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-08-14 17:59:20
+ * @FilePath: /MediResourceManaSys/src/pages/certificateMana/index.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2024-12-25 16:00:00
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-08-06 10:28:55
+ * @FilePath: /KC-MiddlePlatform/src/pages/certificateMana/index.tsx
+ * @Description: 证书管理页面
+ */
+
+import KCIMPagecontainer from '@/components/KCIMPageContainer';
+import { KCIMTable } from '@/components/KCIMTable';
+import { KCIMLeftList } from '@/components/KCIMLeftList';
+import { createFromIconfontCN } from '@ant-design/icons';
+import { ActionType, ProFormDatePicker, ProFormText } from '@ant-design/pro-components';
+import { ProColumns } from '@ant-design/pro-table';
+import { Button, Input, message, Modal, Popconfirm, Space } from 'antd';
+import { Fragment, Key, useEffect, useRef, useState } from 'react';
+import locale from 'antd/es/date-picker/locale/zh_CN';
+import { getCertificateList, getDepartmentList, deleteCertificate, getDoctorAttachmentList } from './service';
+import AddCertificateModal from './components/AddCertificateModal';
+import './style.less';
+
+const IconFont = createFromIconfontCN({
+    scriptUrl: '',
+});
+
+export default function CertificateMana() {
+    const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>();
+    const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
+    const [certificateSearchKeywords, set_certificateSearchKeywords] = useState<string>('');
+    const [departmentData, set_departmentData] = useState<any[]>([]);
+    const [currentSelectedDepartment, set_currentSelectedDepartment] = useState<any | undefined>(undefined);
+    const [selectedRowKeys, set_selectedRowKeys] = useState<Key[]>([]);
+    const [selectedRows, set_selectedRows] = useState<any[]>([]);
+    const [addModalVisible, set_addModalVisible] = useState<boolean>(false);
+    const [editModalVisible, set_editModalVisible] = useState<boolean>(false);
+    const [currentEditRecord, set_currentEditRecord] = useState<any>(undefined);
+
+    const tableRef = useRef<ActionType>();
+
+    // 表格列定义
+    const columns: ProColumns[] = [
+        {
+            title: '证书',
+            dataIndex: 'fileName',
+            width: 150,
+            ellipsis: true,
+        },
+        {
+            title: '医生',
+            dataIndex: 'empNameAndCode',
+            width: 160,
+            ellipsis: true,
+        },
+        {
+            title: '有效期限',
+            dataIndex: 'validityPeriod',
+            width: 280,
+            ellipsis: true,
+            renderText: (text, record) => {
+                // 检查是否有有效的日期数据
+                if (record.activeDate && record.expireDate) {
+                    return `${record.activeDate} 至 ${record.expireDate}`;
+                }
+                return '暂未设置';
+            },
+        },
+        {
+            title: '附件说明',
+            dataIndex: 'description',
+            ellipsis: true,
+        },
+        {
+            title: '操作',
+            key: 'option',
+            width: 150,
+            fixed: 'right',
+            valueType: 'option',
+            render: (_: any, record: any) => {
+                return [
+                    <a key="download" onClick={() => handleDownload(record)}>
+                        下载
+                    </a>,
+                    <a key="edit" onClick={() => handleEdit(record)}>
+                        修改
+                    </a>,
+                    <Popconfirm
+                        key="delete"
+                        title="确定要删除这条证书记录吗?"
+                        onConfirm={() => handleDelete(record)}
+                        okText="确定"
+                        cancelText="取消"
+                    >
+                        <a>删除</a>
+                    </Popconfirm>
+                ];
+            },
+        },
+    ];
+
+    // 获取表格数据(对接后端真实接口 listAllDoctorAttachments)
+    const getTableData = async (params: any) => {
+        const { departmentCode, doctorName, fileName, expirationDate, current = 1, pageSize = 10 } = params || {};
+
+        // 将选择的日期作为 expireDate 传入
+        let expireDate: string | undefined = undefined;
+        if (expirationDate) {
+            // 兼容 moment/dayjs 字符串
+            expireDate = typeof expirationDate === 'string' ? expirationDate : (expirationDate?.format ? expirationDate.format('YYYY-MM-DD') : expirationDate?.toString());
+        }
+
+        const query = {
+            doctorName: tableDataSearchKeywords || doctorName,
+            attachmentName: certificateSearchKeywords || fileName,
+            deptCode: (currentSelectedDepartment?.code || departmentCode),
+            expireDate,
+            current, // 使用ProTable传递的current参数
+            pageSize, // 使用ProTable传递的pageSize参数
+        } as any;
+
+        const resp = await getDoctorAttachmentList(query);
+
+        // 根据实际接口返回结构处理
+        if (Array.isArray(resp)) {
+            return {
+                data: resp,
+                success: true,
+                total: resp.length,
+            };
+        }
+
+        // 处理 { status, msg, data: { list, totalCount, ... } } 结构
+        const data = resp?.data || resp;
+        const list = data?.list || [];
+        const total = data?.totalCount || data?.total || 0;
+
+        return {
+            data: list,
+            success: true,
+            total,
+        };
+    };
+
+    // 获取科室列表(对齐医生授权管理返回结构)
+    const getDepartmentListData = async () => {
+        const resp = await getDepartmentList();
+        // 后端直接返回树数组;兼容历史 { success, data } 包装
+        const data = Array.isArray(resp) ? resp : (resp?.data || resp?.list || []);
+        if (Array.isArray(data)) {
+            set_departmentData(data);
+        } else {
+            set_departmentData([]);
+        }
+    };
+
+    // 处理下载
+    const handleDownload = async (record: any) => {
+        try {
+            if (!record.url) {
+                message.error('文件下载地址不存在!');
+                return;
+            }
+            
+            // 使用fetch获取文件数据,强制下载
+            const response = await fetch(record.url);
+            if (!response.ok) {
+                throw new Error('文件下载失败');
+            }
+            
+            const blob = await response.blob();
+            const url = window.URL.createObjectURL(blob);
+            
+            // 创建下载链接
+            const link = document.createElement('a');
+            link.href = url;
+            link.download = record.fileName || '证书文件';
+            link.style.display = 'none';
+            document.body.appendChild(link);
+            link.click();
+            document.body.removeChild(link);
+            
+            // 清理对象URL
+            window.URL.revokeObjectURL(url);
+            
+            message.success('下载成功!');
+        } catch (error) {
+            console.error('下载失败:', error);
+            message.error('下载失败!');
+        }
+    };
+
+    // 处理编辑
+    const handleEdit = (record: any) => {
+        set_currentEditRecord(record);
+        set_editModalVisible(true);
+    };
+
+    // 处理删除
+    const handleDelete = async (record: any) => {
+        try {
+            await deleteCertificate(record.id);
+            message.success('删除成功!');
+            tableRef.current?.reload();
+        } catch (error) {
+            message.error('删除失败!');
+        }
+    };
+
+    // 处理新增
+    const handleAdd = () => {
+        if (!currentSelectedDepartment?.code) {
+            message.warning('请先选择科室');
+            return;
+        }
+        set_addModalVisible(true);
+    };
+
+    // 处理新增成功
+    const handleAddSuccess = () => {
+        set_addModalVisible(false);
+        tableRef.current?.reload();
+        message.success('新增证书成功!');
+    };
+
+    // 处理新增取消
+    const handleAddCancel = () => {
+        set_addModalVisible(false);
+    };
+
+    // 处理编辑成功
+    const handleEditSuccess = () => {
+        set_editModalVisible(false);
+        set_currentEditRecord(undefined);
+        tableRef.current?.reload();
+        message.success('修改证书成功!');
+    };
+
+    // 处理编辑取消
+    const handleEditCancel = () => {
+        set_editModalVisible(false);
+        set_currentEditRecord(undefined);
+    };
+
+    // 处理批量删除
+    const handleBatchDelete = () => {
+        if (selectedRows.length === 0) {
+            message.warning('请先选择要删除的证书');
+            return;
+        }
+        
+        const modal = Modal.confirm({
+            title: '确认批量删除',
+            content: `确定要删除选中的 ${selectedRows.length} 个证书吗?此操作不可恢复。`,
+            okText: '确定删除',
+            cancelText: '取消',
+            onOk: async () => {
+                try {
+                    // 并发删除所有选中的证书
+                    const deletePromises = selectedRows.map(record => 
+                        deleteCertificate(record.id)
+                    );
+                    
+                    await Promise.all(deletePromises);
+                    
+                    message.success(`成功删除 ${selectedRows.length} 个证书!`);
+                    
+                    // 清空选择并刷新表格
+                    set_selectedRowKeys([]);
+                    set_selectedRows([]);
+                    tableRef.current?.reload();
+                } catch (error) {
+                    console.error('批量删除失败:', error);
+                    message.error('批量删除失败,请重试');
+                }
+            }
+        });
+    };
+
+    // 取消批量选择
+    const handleCancelBatchSelect = () => {
+        set_selectedRowKeys([]);
+        set_selectedRows([]);
+    };
+
+    // 处理表格选择变化
+    const handleTableSelectChange = (selectedRowKeys: Key[], selectedRows: any[], info: any) => {
+        set_selectedRowKeys(selectedRowKeys);
+        set_selectedRows(selectedRows);
+    };
+
+    // 处理医生姓名搜索
+    const handleSearch = () => {
+        set_tableDataFilterParams({
+            ...tableDataFilterParams,
+            doctorName: tableDataSearchKeywords,
+        });
+        // 搜索时重置到第一页
+        tableRef.current?.reload();
+    };
+
+    // 处理证书名称搜索
+    const handleCertificateSearch = () => {
+        set_tableDataFilterParams({
+            ...tableDataFilterParams,
+            fileName: certificateSearchKeywords,
+        });
+        // 搜索时重置到第一页
+        tableRef.current?.reload();
+    };
+
+    // 处理科室选择变化
+    const handleDepartmentChange = (selectedItem: any) => {
+        set_currentSelectedDepartment(selectedItem);
+        set_tableDataFilterParams({
+            ...tableDataFilterParams,
+            departmentCode: selectedItem?.code,
+        });
+    };
+
+    useEffect(() => {
+        getDepartmentListData();
+    }, []);
+
+    useEffect(() => {
+        if (currentSelectedDepartment) {
+            set_tableDataFilterParams({
+                ...tableDataFilterParams,
+                departmentCode: currentSelectedDepartment.code,
+            });
+        }
+    }, [currentSelectedDepartment]);
+
+    return (
+        <KCIMPagecontainer className="CertificateMana" title={false} >
+            <div className="pageContent">
+                <div className="left">
+                    <KCIMLeftList
+                        searchKey="name"
+                        dataSource={departmentData}
+                        fieldNames={{ title: 'name', key: 'code' }}
+                        defaultSelectType="node"
+                        listType="tree"
+                        onChange={handleDepartmentChange}
+                        placeholder="请输入科室名称"
+                    />
+                </div>
+                <div className="right">
+                    <div className="toolBar">
+                        <div className="filter">
+                            <div className="filterItem">
+                                <span className="label">医生姓名:</span>
+                                <Input
+                                    placeholder="请输入医生姓名"
+                                    style={{ width: 200, marginRight: 16 }}
+                                    allowClear
+                                    autoComplete="off"
+                                    suffix={
+                                        <IconFont
+                                            style={{ color: '#99A6BF' }}
+                                            type="iconsousuo"
+                                            onClick={handleSearch}
+                                        />
+                                    }
+                                    onChange={(e) => {
+                                        set_tableDataSearchKeywords(e.target.value);
+                                        if (e.target.value.length === 0) {
+                                            set_tableDataSearchKeywords('');
+                                            set_tableDataFilterParams({
+                                                ...tableDataFilterParams,
+                                                doctorName: undefined,
+                                            });
+                                        }
+                                    }}
+                                    onPressEnter={handleSearch}
+                                />
+                            </div>
+                            <div className="filterItem">
+                                <span className="label">证书名称:</span>
+                                <Input
+                                    placeholder="请输入证书名称"
+                                    style={{ width: 200, marginRight: 16 }}
+                                    allowClear
+                                    autoComplete="off"
+                                    suffix={
+                                        <IconFont
+                                            style={{ color: '#99A6BF' }}
+                                            type="iconsousuo"
+                                            onClick={handleCertificateSearch}
+                                        />
+                                    }
+                                    onChange={(e) => {
+                                        set_certificateSearchKeywords(e.target.value);
+                                        if (e.target.value.length === 0) {
+                                            set_certificateSearchKeywords('');
+                                            set_tableDataFilterParams({
+                                                ...tableDataFilterParams,
+                                                fileName: undefined,
+                                            });
+                                        }
+                                    }}
+                                    onPressEnter={handleCertificateSearch}
+                                />
+                            </div>
+                            <div className="filterItem">
+                                <span className="label">到期时间:</span>
+                                <ProFormDatePicker
+                                    noStyle
+                                    name="expirationDate"
+                                    style={{ width: 200, marginRight: 16 }}
+                                    placeholder="请选择日期"
+                                    fieldProps={{
+                                        locale: locale,
+                                        format: 'YYYY-MM-DD',
+                                        onChange: (date) => {
+                                            set_tableDataFilterParams({
+                                                ...tableDataFilterParams,
+                                                expirationDate: date,
+                                            });
+                                        },
+                                    }}
+                                />
+                            </div>
+                        </div>
+                        <div className="btnGroup">
+                            <a className="add" onClick={handleAdd}>
+                                新增
+                            </a>
+                        </div>
+                    </div>
+                    <div style={{ marginTop: 16 }}>
+                        <KCIMTable
+                            scroll={{ y: `calc(100vh - 230px)` }}
+                            actionRef={tableRef}
+                            columns={columns as ProColumns[]}
+                            rowKey="id"
+                            params={tableDataFilterParams}
+                            request={(params) => getTableData(params)}
+                            tableAlertRender={false}
+                            rowSelection={{
+                                selectedRowKeys,
+                                onChange: handleTableSelectChange,
+                            }}
+                            pagination={{
+                                showSizeChanger: true,
+                                showQuickJumper: false,
+                                showTotal: (total) => `共计${total}条记录`,
+                            }}
+                        />
+                    </div>
+                </div>
+            </div>
+            
+            {/* 新增证书弹窗 */}
+            <AddCertificateModal
+                visible={addModalVisible}
+                onCancel={handleAddCancel}
+                onSuccess={handleAddSuccess}
+                currentDeptCode={currentSelectedDepartment?.code}
+            />
+            
+            {/* 编辑证书弹窗 */}
+            <AddCertificateModal
+                visible={editModalVisible}
+                onCancel={handleEditCancel}
+                onSuccess={handleEditSuccess}
+                currentDeptCode={currentSelectedDepartment?.code}
+                editData={currentEditRecord}
+            />
+            
+            {/* 批量操作底部条 */}
+            {selectedRows.length > 0 && (
+                <div className='bottom'>
+                    <div className='tags'>已选择证书<span>{selectedRows.length}</span>项</div>
+                    <div className='btn-groups'>
+                        <div className='cancelBtn' onClick={handleCancelBatchSelect}>取消选择</div>
+                        <div className='batchBtn danger' onClick={handleBatchDelete}>批量删除</div>
+                    </div>
+                </div>
+            )}
+        </KCIMPagecontainer>
+    );
+} 

+ 325 - 0
src/pages/certificateMana/service.ts

@@ -0,0 +1,325 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2024-12-25 16:00:00
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-08-06 10:20:51
+ * @FilePath: /KC-MiddlePlatform/src/pages/certificateMana/service.ts
+ * @Description: 证书管理服务接口
+ */
+
+import { request } from '@umijs/max';
+
+// 模拟科室数据
+const departmentData = [
+    {
+        name: '全部',
+        code: 'all',
+        children: []
+    },
+    {
+        name: '神经内科病区',
+        code: 'neurology',
+        children: []
+    },
+    {
+        name: '心内科病区',
+        code: 'cardiology',
+        children: [
+            {
+                name: '心内科一病区',
+                code: 'cardiology_1',
+                children: []
+            },
+            {
+                name: '心内科二病区',
+                code: 'cardiology_2',
+                children: []
+            },
+            {
+                name: '心内科三病区',
+                code: 'cardiology_3',
+                children: []
+            },
+            {
+                name: '心内科四病区',
+                code: 'cardiology_4',
+                children: []
+            }
+        ]
+    },
+    {
+        name: '呼吸内科病区',
+        code: 'respiratory',
+        children: []
+    },
+    {
+        name: '肾内科病区',
+        code: 'nephrology',
+        children: []
+    },
+    {
+        name: '血液科病区',
+        code: 'hematology',
+        children: []
+    },
+    {
+        name: '内分泌科病区',
+        code: 'endocrinology',
+        children: []
+    },
+    {
+        name: '放射科病区',
+        code: 'radiology',
+        children: []
+    },
+    {
+        name: '临床医生',
+        code: 'clinical_doctor',
+        children: []
+    },
+    {
+        name: '医技辅助医生',
+        code: 'auxiliary_doctor',
+        children: []
+    }
+];
+
+// 模拟证书数据
+const certificateData = [
+    {
+        id: '1',
+        certificateType: '本科毕业证',
+        doctorName: '杨崇文',
+        doctorId: '000029',
+        startDate: '2023-11-01',
+        endDate: '2024-11-01',
+        attachmentDescription: '这里是大学本科学位证的说明',
+        departmentCode: 'cardiology_2',
+        departmentName: '心内科二病区'
+    },
+    {
+        id: '2',
+        certificateType: '本科毕业证',
+        doctorName: '杨崇文',
+        doctorId: '000029',
+        startDate: '2023-11-01',
+        endDate: '2024-11-01',
+        attachmentDescription: '这里是大学本科学位证的说明',
+        departmentCode: 'cardiology_2',
+        departmentName: '心内科二病区'
+    },
+    {
+        id: '3',
+        certificateType: '本科毕业证',
+        doctorName: '杨崇文',
+        doctorId: '000029',
+        startDate: '2023-11-01',
+        endDate: '2024-11-01',
+        attachmentDescription: '这里是大学本科学位证的说明',
+        departmentCode: 'cardiology_2',
+        departmentName: '心内科二病区'
+    },
+    {
+        id: '4',
+        certificateType: '本科毕业证',
+        doctorName: '杨崇文',
+        doctorId: '000029',
+        startDate: '2023-11-01',
+        endDate: '2024-11-01',
+        attachmentDescription: '这里是大学本科学位证的说明',
+        departmentCode: 'cardiology_2',
+        departmentName: '心内科二病区'
+    },
+    {
+        id: '5',
+        certificateType: '本科毕业证',
+        doctorName: '杨崇文',
+        doctorId: '000029',
+        startDate: '2023-11-01',
+        endDate: '2024-11-01',
+        attachmentDescription: '这里是大学本科学位证的说明',
+        departmentCode: 'cardiology_2',
+        departmentName: '心内科二病区'
+    },
+    {
+        id: '6',
+        certificateType: '本科毕业证',
+        doctorName: '杨崇文',
+        doctorId: '000029',
+        startDate: '2023-11-01',
+        endDate: '2024-11-01',
+        attachmentDescription: '这里是大学本科学位证的说明',
+        departmentCode: 'cardiology_2',
+        departmentName: '心内科二病区'
+    },
+    {
+        id: '7',
+        certificateType: '本科毕业证',
+        doctorName: '杨崇文',
+        doctorId: '000029',
+        startDate: '2023-11-01',
+        endDate: '2024-11-01',
+        attachmentDescription: '这里是大学本科学位证的说明',
+        departmentCode: 'cardiology_2',
+        departmentName: '心内科二病区'
+    },
+    {
+        id: '8',
+        certificateType: '本科毕业证',
+        doctorName: '杨崇文',
+        doctorId: '000029',
+        startDate: '2023-11-01',
+        endDate: '2024-11-01',
+        attachmentDescription: '这里是大学本科学位证的说明',
+        departmentCode: 'cardiology_2',
+        departmentName: '心内科二病区'
+    },
+    {
+        id: '9',
+        certificateType: '本科毕业证',
+        doctorName: '杨崇文',
+        doctorId: '000029',
+        startDate: '2023-11-01',
+        endDate: '2024-11-01',
+        attachmentDescription: '这里是大学本科学位证的说明',
+        departmentCode: 'cardiology_2',
+        departmentName: '心内科二病区'
+    },
+    {
+        id: '10',
+        certificateType: '本科毕业证',
+        doctorName: '杨崇文',
+        doctorId: '000029',
+        startDate: '2023-11-01',
+        endDate: '2024-11-01',
+        attachmentDescription: '这里是大学本科学位证的说明',
+        departmentCode: 'cardiology_2',
+        departmentName: '心内科二病区'
+    }
+];
+
+// 获取证书列表
+export async function getCertificateList(params: any) {
+    // 模拟API调用延迟
+    await new Promise(resolve => setTimeout(resolve, 500));
+    
+    const { current = 1, pageSize = 10, departmentCode, doctorName, expirationDate } = params;
+    
+    let filteredData = [...certificateData];
+    
+    // 根据科室筛选
+    if (departmentCode && departmentCode !== 'all') {
+        filteredData = filteredData.filter(item => item.departmentCode === departmentCode);
+    }
+    
+    // 根据医生姓名筛选
+    if (doctorName) {
+        filteredData = filteredData.filter(item => 
+            item.doctorName.includes(doctorName) || item.doctorId.includes(doctorName)
+        );
+    }
+    
+    // 根据到期时间筛选
+    if (expirationDate && expirationDate.length === 2) {
+        const [startDate, endDate] = expirationDate;
+        filteredData = filteredData.filter(item => {
+            const itemEndDate = new Date(item.endDate);
+            const filterStartDate = new Date(startDate);
+            const filterEndDate = new Date(endDate);
+            return itemEndDate >= filterStartDate && itemEndDate <= filterEndDate;
+        });
+    }
+    
+    const startIndex = (current - 1) * pageSize;
+    const endIndex = startIndex + pageSize;
+    const list = filteredData.slice(startIndex, endIndex);
+    
+    return {
+        success: true,
+        list,
+        total: filteredData.length,
+        current,
+        pageSize
+    };
+}
+
+// 获取科室列表(改为与医生授权管理一致的后端接口)
+export async function getDepartmentList(params?: any) {
+    return request<any>('/medical/qualificationManage/listAllDepartment', {
+        method: 'GET',
+        params: { ...params }
+    });
+}
+
+// 获取医生附件(证书)列表
+export async function getDoctorAttachmentList(params?: any) {
+    return request<any>('/medical/qualificationManage/listAllDoctorAttachments', {
+        method: 'GET',
+        params: { ...params }
+    });
+}
+
+// 删除证书(复用个人中心的删除附件接口)
+export async function deleteCertificate(id: number) {
+    return request<any>('/medical/qualificationManage/deleteDoctorAttachment', {
+        method: 'POST',
+        params: { id }
+    });
+}
+
+// 下载证书
+export async function downloadCertificate(id: string) {
+    // 模拟API调用延迟
+    await new Promise(resolve => setTimeout(resolve, 1000));
+    
+    // 模拟下载文件
+    const blob = new Blob(['模拟的证书文件内容'], { type: 'application/pdf' });
+    const url = window.URL.createObjectURL(blob);
+    const link = document.createElement('a');
+    link.href = url;
+    link.download = `certificate_${id}.pdf`;
+    document.body.appendChild(link);
+    link.click();
+    document.body.removeChild(link);
+    window.URL.revokeObjectURL(url);
+    
+    return {
+        success: true,
+        message: '下载成功'
+    };
+}
+
+// 新增证书(复用个人中心的保存附件接口)
+export async function addCertificate(data: any) {
+    return request<any>('/medical/qualificationManage/saveDoctorAttachment', {
+        method: 'POST',
+        data
+    });
+}
+
+// 编辑证书(复用个人中心的编辑附件接口)
+export async function editCertificate(data: any) {
+    return request<any>('/medical/qualificationManage/editDoctorAttachment', {
+        method: 'POST',
+        data
+    });
+}
+
+// 更新证书
+export async function updateCertificate(data: any) {
+    // 模拟API调用延迟
+    await new Promise(resolve => setTimeout(resolve, 500));
+    
+    return {
+        success: true,
+        message: '更新成功'
+    };
+}
+
+// 获取医生下拉列表(复用医生授权管理接口)
+export async function getDoctorListForSelect(params?: any) {
+    return request<any>('/medical/qualificationManage/listByDoctor', {
+        method: 'GET',
+        params: { ...params }
+    });
+} 

+ 134 - 0
src/pages/certificateMana/style.less

@@ -0,0 +1,134 @@
+.CertificateMana {
+  border-radius: 4px;
+
+  .pageContent {
+    display: flex;
+    width: 100%;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: flex-start;
+
+    .left {
+      border-radius: 4px;
+      width: 220px;
+      height: calc(100vh - 80px);
+      overflow: hidden;
+      margin-right: 16px;
+      padding-top: 8px;
+      background: #FFFFFF;
+    }
+
+    .right {
+      border-radius: 4px;
+      background: #FFFFFF;
+      width: calc(100% - 236px);
+      padding: 16px;
+
+      .toolBar {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        align-items: center;
+
+        .filter {
+          display: flex;
+          flex-direction: row;
+          justify-content: flex-start;
+          align-items: center;
+
+          .filterItem {
+            display: flex;
+            flex-direction: row;
+            justify-content: center;
+            align-items: center;
+
+            .label {
+              font-size: 14px;
+              color: #17181A;
+              margin-right: 8px;
+            }
+          }
+        }
+
+        .btnGroup {
+          .add {
+            cursor: pointer;
+            display: inline-block;
+            font-size: 14px;
+            font-weight: 400;
+            color: #FFFFFF;
+            line-height: 24px;
+            padding: 0 14px;
+            background: #3377FF;
+            border-radius: 4px;
+          }
+        }
+      }
+    }
+  }
+
+  // 批量操作底部条样式
+  .bottom {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: center;
+    padding: 0 16px;
+    position: absolute;
+    left: 0;
+    z-index: 99;
+    bottom: 0;
+    width: 100%;
+    height: 48px;
+    background: #FFFFFF;
+    box-shadow: 0px -8px 16px 0px rgba(64, 85, 128, 0.1);
+
+    .tags {
+      font-weight: 400;
+      font-size: 14px;
+      color: #17181A;
+
+      span {
+        color: #3376FE;
+      }
+    }
+
+    .btn-groups {
+      display: flex;
+      flex-direction: row;
+      justify-content: center;
+      align-items: center;
+      
+      .cancelBtn {
+        text-align: center;
+        width: 80px;
+        height: 24px;
+        cursor: pointer;
+        line-height: 24px;
+        border-radius: 4px;
+        font-weight: 400;
+        font-size: 14px;
+        color: #17181A;
+        margin-right: 8px;
+        border: 1px solid #dae2f2;
+      }
+      
+      .batchBtn {
+        text-align: center;
+        width: 80px;
+        height: 24px;
+        cursor: pointer;
+        line-height: 24px;
+        background: #3377FF;
+        border-radius: 4px;
+        font-weight: 400;
+        font-size: 14px;
+        color: #FFFFFF;
+        
+        &.danger {
+          background: #FF4D4F;
+        }
+      }
+    }
+  }
+} 

+ 65 - 0
src/pages/dataFilling/fillingMana/authHisttory/index.tsx

@@ -0,0 +1,65 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2024-12-06 14:42:57
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2024-12-10 16:58:39
+ * @FilePath: /MediResourceManaSys/src/pages/qualificationMana/qualificationAuth/authHisttory/index.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AEi m z z
+ */
+
+
+import { createFromIconfontCN } from '@ant-design/icons';
+import React, { Children } from 'react'
+import { Timeline } from 'antd'
+import './style.less'
+import { authTimeType } from '@/constant';
+
+const IconFont = createFromIconfontCN({
+    scriptUrl: '',
+});
+
+
+export default function AuthHisContent({ hisList }: { hisList: any[] }) {
+
+    const getPeriodStr = (code:number)=>{
+        const result = authTimeType.filter((a)=>a.value == code);
+        if(result.length>0){
+            return result[0].label
+        }
+        return undefined
+    }
+
+    const dotNode = (isActive=false)=>{
+        return <div style={{display:'flex',justifyContent:'center',alignItems:'center',width:16,height:16,border:'2px solid #E7EBF2',borderRadius:'50%'}}>
+            {isActive&&<div style={{width:8,height:8,background:'#363F4D',borderRadius:'50%'}}></div>}
+        </div>
+    }
+    return (
+        <div className='AuthHisContent'>
+            <div className='AuthHisContent-title'>
+                <IconFont type={'icon-qingliangtishi'} style={{ paddingRight:12 }} />调整历史
+            </div>
+            <div className='AuthHisContent-content'>
+                <Timeline>
+                    {
+                        hisList.map((a,index)=>{
+                             return (
+                                <Timeline.Item key={index} dot={dotNode(index == 0?true:false)} >
+                                      <div style={{fontWeight:'bold',fontSize:14,color:'#17181A',height:14,lineHeight:'14px',marginBottom:8}}>{`${a.adjustUserName} ${a.adjustTime}`}</div>
+                                      <div style={{height:14,fontSize:12,color:'#176DE6',lineHeight:'14px',marginBottom:8,paddingLeft:6}}>
+                                        <img style={{width:43,height:14,marginRight:4}} src={require('../../../../../static/tiaozhenghou.png')} />{getPeriodStr(a.qualificationPeriodAfter)} {`(${a.beginDateAfter}至${a.endDateAfter})`}
+                                      </div>
+                                      <div style={{height:14,fontSize:12,color:'#525866',lineHeight:'14px',marginBottom:8,paddingLeft:6}}>
+                                        <img style={{width:43,height:14,marginRight:4}} src={require('../../../../../static/tiaozhengqian.png')} />
+                                        {getPeriodStr(a.qualificationPeriodBefore)} {`(${a.beginDateBefore}至${a.endDateBefore})`}
+                                      </div>
+                                      {(a.memo&&a.memo.length>0)&&<div style={{padding:'5px 8px',fontSize:12,color:'#525866',background:'#F7F9FC',borderRadius:4}}>{a.memo}</div>}
+                                </Timeline.Item>
+                             )
+                        })
+                    }
+                </Timeline>
+            </div>
+        </div>
+    )
+}

+ 35 - 0
src/pages/dataFilling/fillingMana/authHisttory/style.less

@@ -0,0 +1,35 @@
+
+
+.AuthHisContent {
+    width: 400px;
+   
+    .AuthHisContent-title {
+        font-weight: 500;
+        font-size: 16px;
+        height: 16px;
+        color: #17181A;
+        line-height: 16px;
+    }
+    .AuthHisContent-content {
+        max-height: 400px;
+        overflow: scroll;
+        overflow-x: hidden;
+        padding-top: 24px;
+
+        .mrms-ant-timeline-item {
+            &.mrms-ant-timeline-item-last {
+                padding-bottom: 0;
+            }
+        }
+        .mrms-ant-timeline-item-tail {
+            left:8px;
+        }
+        .mrms-ant-timeline-item-head-custom {
+            top:0.5px;
+            left:9px;
+        }
+        .mrms-ant-timeline-item-content {
+            margin: 0 0 0 35px;
+        }
+    }
+}

+ 82 - 0
src/pages/dataFilling/fillingMana/generateTableData.tsx

@@ -0,0 +1,82 @@
+
+import { createFromIconfontCN } from "@ant-design/icons";
+import { ProColumns } from "@ant-design/pro-components";
+
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2025-02-14 17:50:16
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-02-17 17:59:04
+ * @FilePath: /MediResourceManaSys/src/pages/dataFilling/fillingMana/generateTableData.ts
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+
+import { Drawer, Input, message, Popconfirm, Popover, Switch, Tooltip } from 'antd';
+import AuthHisContent from "./authHisttory";
+
+
+type HistoryItem = {
+  value: number;
+  createTime: string;
+};
+
+type ItemDic = {
+  code: string;
+  name: string;
+  type: number;
+  unit: string;
+  status: number;
+  periodType: number;
+  description: string;
+  dimensionType: number;
+};
+
+type ItemData = {
+  code: string;
+  value: any;
+  history?: HistoryItem[];
+  itemDic?: ItemDic;
+};
+
+type ItemTitle = {
+  code: string;
+  name: string;
+};
+
+const IconFont = createFromIconfontCN({
+  scriptUrl: '',
+});
+
+export default function generateTableData(itemTitles: ItemTitle[], itemDatas: ItemData[]) {
+  const columns: ProColumns[] = itemTitles.map((title, index) => ({
+    title: title.name,
+    dataIndex: title.code,
+    key: title.code,
+    width: 120,
+    render: (text: any, record: any) => {
+      const { history=[] } = record;
+      const content = <AuthHisContent hisList={[...history]} />
+      return <>{text}{true && (
+        <Popover className='qualificationAuth-popover' overlayInnerStyle={{ borderRadius: 4 }} content={content} title={false} >
+          <IconFont type={'icon-qingliangtishi'} style={{ marginLeft: 10 }} />
+        </Popover>
+      )}</>; // 返回 text 对应的数据
+    },
+  }));
+  
+  const dataSource = itemDatas.map((data, index) => {
+    const rowData: any = {
+      id: `${data.code}_mockid`,
+      [data.code]: data.value,
+    };
+
+    // 如果存在 itemDic,添加到行数据中
+    if (data.itemDic) {
+      rowData[data.code + '_itemDic'] = data.itemDic;
+    }
+
+    return rowData;
+  });
+
+  return { columns, dataSource };
+}

+ 657 - 0
src/pages/dataFilling/fillingMana/index.tsx

@@ -0,0 +1,657 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2023-03-03 11:30:33
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-02-17 17:30:51
+ * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+
+
+
+import KCIMPagecontainer from '@/components/KCIMPageContainer';
+import { KCIMTable } from '@/components/KCIMTable';
+
+import { createFromIconfontCN, DownOutlined, InboxOutlined } from '@ant-design/icons';
+import { ActionType, ProFormDependency, ProFormInstance, ProFormText, ProFormSelect } from '@ant-design/pro-components';
+import { ModalForm, ProFormCascader, ProFormCheckbox, ProFormDigit, ProFormRadio, ProFormSwitch, ProFormTextArea } from '@ant-design/pro-form'
+import { ProColumns } from '@ant-design/pro-table';
+import { Drawer, Dropdown, Input, MenuProps, message, Popconfirm, Popover, Switch, Tooltip } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+
+import 'moment/locale/zh-cn';
+
+
+import { addData, delData, editData, generateTableDataReq, getCurrentHospAlldeps, getTableList, uploadFilesReq, withdrawReq } from './service';
+
+import './style.less';
+
+
+import QualifiDicMana from '@/pages/qualificationMana/qualifiDicMana';
+import Dragger from 'antd/es/upload/Dragger';
+import { useModel } from '@umijs/max';
+import { getDicDataBySysId } from '@/services/getDic';
+import { KcimCenterSysId } from '@/constant';
+import React from 'react';
+import generateTableData from './generateTableData';
+
+
+
+const IconFont = createFromIconfontCN({
+    scriptUrl: '',
+});
+
+//填报周期
+export const DATAFILL_PERIODTYPE = [
+    { label: '月度', value: 1 },
+    { label: '季度', value: 2 },
+    { label: '年度', value: 3 },
+    { label: '变动时', value: 4 },
+];
+//填报主体类型
+export const DATAFILL_MAINTYPE = [
+    { label: '职能科室填报 ', value: 1 },
+    { label: '科室填报', value: 2 },
+];
+
+//填报维度
+export const DATAFILL_DIMENSIONTYPE = [
+    { label: '按全院  ', value: 1 },
+    { label: '按科室', value: 2 },
+];
+
+
+type Data = {
+    [key: number]: string;
+};
+
+type LabelValue = {
+    label: string;
+    value: number;
+};
+
+function transformToLabelValue(data: Data): LabelValue[] {
+
+    const result: LabelValue[] = [];
+    for (const key in data) {
+        if (data.hasOwnProperty(key)) {
+            result.push({
+                label: data[key],
+                value: parseInt(key)
+            });
+        }
+    }
+    return result;
+}
+
+
+
+export const searchTree = (tree: any[], searchTerm: string) => {
+    const searchTermLower = searchTerm.toLowerCase();
+
+    function searchNode(node: any) {
+        const matchedChildren: any[] = [];
+
+        if (node.children && node.children.length > 0) {
+            node.children.forEach((child: any) => {
+                const matchedChild = searchNode(child);
+                if (matchedChild) {
+                    matchedChildren.push(matchedChild);
+                }
+            });
+        }
+
+        if (node.responsibilityName.toLowerCase().includes(searchTermLower) || (node.responsibilityCode && node.responsibilityCode.toLowerCase().includes(searchTermLower))) {
+            return {
+                ...node,
+                children: matchedChildren.length > 0 ? matchedChildren : node.children
+            };
+        } else if (matchedChildren.length > 0) {
+            return {
+                ...node,
+                children: matchedChildren
+            };
+        }
+
+        return null;
+    }
+
+    return tree.map(rootNode => searchNode(rootNode)).filter(node => node !== null);
+}
+
+
+let detailTableColumns: ProColumns[] = [];
+
+
+
+
+export default function FillingMana() {
+    const { initialState, setInitialState } = useModel('@@initialState');
+    const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>();
+    const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
+    const tableRef = useRef<ActionType>();
+    const formRef = useRef<ProFormInstance>();
+    const [tableDataSource, set_tableDataSource] = useState<any[]>([]);
+    const [tableColumns, set_tableColumns] = useState<ProColumns[]>([]);
+    const [currentEditRow, set_currentEditRow] = useState<any>(undefined)
+    const [actionType, set_actionType] = useState<'NORMAL' | 'DETAIL' | undefined>('NORMAL');
+    const [currentEditTableRow, set_currentEditTableRow] = useState<any>(undefined);
+
+    const tableColumns_default: ProColumns[] = [
+        {
+            title: '操作',
+            key: 'option',
+            fixed: 'right',
+            width: 120,
+            valueType: 'option',
+            render: (_: any, record: any) => {
+                return <a key={'edit'} onClick={() => { set_currentEditTableRow(record) }}>编辑</a>
+            },
+        }
+    ];
+
+    const columns: ProColumns[] = [
+        {
+            title: '填报项目',
+            ellipsis: true,
+            dataIndex: 'name'
+        },
+        {
+            title: '填报周期',
+            ellipsis: true,
+            dataIndex: 'periodType',
+            renderText(code, record, index, action) {
+                const item = DATAFILL_PERIODTYPE.find(item => item.value === code);
+                return item ? item.label : '';
+            }
+        },
+        {
+            title: '填报维度',
+            ellipsis: true,
+            dataIndex: 'dimensionType',
+            renderText(code, record, index, action) {
+                const item = DATAFILL_DIMENSIONTYPE.find(item => item.value === code);
+                return item ? item.label : '';
+            }
+        },
+        {
+            title: '填报主体类型',
+            ellipsis: true,
+            dataIndex: 'type',
+            renderText(code, record, index, action) {
+                const item = DATAFILL_MAINTYPE.find(item => item.value === code);
+                return item ? item.label : '';
+            }
+        },
+        {
+            title: '填报主体',
+            ellipsis: true,
+            dataIndex: 'typeName',
+        },
+        {
+            title: '填报单位',
+            ellipsis: true,
+            dataIndex: 'unit',
+        },
+        {
+            title: '填报说明',
+            ellipsis: true,
+            dataIndex: 'description',
+        },
+        {
+            title: '启用',
+            width: 60,
+            ellipsis: true,
+            dataIndex: 'status',
+            renderText(num, record) {
+                return <Switch size='small' checked={num} onChange={(bool) => updateTable({...record,status:bool?1:0,}, 'EDIT')} />
+            },
+        },
+        {
+            title: '操作',
+            key: 'option',
+            fixed: 'right',
+            width: 120,
+            valueType: 'option',
+            render: (_: any, record: any) => {
+                const { applyStatus } = record;
+
+                return [
+                    <a key={'detail'} onClick={() => { set_currentEditRow(record) }}>详情</a>,
+                    <UpDataActBtn record={record} type={'EDIT'} />,
+                    <Popconfirm
+                        title="是否确认删除?"
+                        key="del"
+                        onConfirm={() => delTableData(record)}
+                    >
+                        <a>删除</a>
+                    </Popconfirm>
+
+                ]
+
+            },
+        },
+
+    ];
+
+
+    const getGenerateTableData = async () => {
+        //   const resp = await generateTableDataReq({periodType:1,dataYear:'2025',});
+        //   if(resp){
+
+        //   }
+
+        const itemTitles = [
+            { code: "fillItemName", name: "项目名称" },
+            { code: "dimensionName", name: "维度名称" },
+            { code: "1", name: "数值1" },
+            { code: "2", name: "数值2" },
+            { code: "3", name: "数值3" },
+            { code: "4", name: "数值4" },
+            { code: "5", name: "数值5" },
+            { code: "6", name: "数值6" },
+            { code: "7", name: "数值7" },
+            { code: "8", name: "数值8" },
+            { code: "9", name: "数值9" },
+            { code: "10", name: "数值10" },
+            { code: "11", name: "数值11" },
+            { code: "12", name: "数值12" },
+        ];
+
+        const itemDatas = [
+            {
+                code: "fillItemName",
+                value: "test",
+                itemDic: {
+                    id: 1,
+                    hospId: "48",
+                    code: "1",
+                    name: "test",
+                    type: 1,
+                    typeName: "职能部门",
+                    dimensionType: 1,
+                    dimensionTypeName: "全院",
+                    periodType: 1,
+                    periodTypeName: "月度",
+                    unit: "test",
+                    status: 1,
+                    description: "1",
+                    delFlag: 0,
+                },
+            },
+            { code: "dimensionName", value: "全院" },
+            { code: "1", value: "20.0000" },
+            { code: "2", value: undefined },
+            { code: "3", value: undefined },
+        ];
+
+
+        const { columns, dataSource } = generateTableData(itemTitles, itemDatas);
+        detailTableColumns = columns;
+
+        set_tableColumns(columns);
+        set_tableDataSource(dataSource);
+
+    }
+    const getTableData = async (params: any) => {
+        const resp = await getTableList({ ...params });
+        if (resp) {
+            return {
+                data: resp.list,
+                success: true,
+                total: resp.totalCount,
+                pageSize: resp.pageSize,
+                totalPage: resp.totalPage,
+            }
+        }
+        return []
+    }
+
+
+    const delTableData = async (record: any) => {
+        const resp = await delData(record.code);
+        if (resp) {
+            message.success('操作成功!');
+            tableRef.current?.reload();
+            // message.success('操作成功!');
+        }
+    }
+
+
+    const updateTable = async (data: any, type: 'ADD' | 'EDIT') => {
+        try {
+            const result = {
+                name: data.name,
+                type: data.type,
+                dimensionType: data.dimensionType,
+                periodType: data.periodType,
+                unit: data.unit,
+                status:type == 'ADD'?null:data.status,
+                description: data.description,
+                fillDeptList: data.fillDeptList ? data.fillDeptList.map((a: any) => ({ departmentCode: a.value, departmentName: a.label })) : [],
+                relateDeptList: data.relateDeptList ? data.relateDeptList.map((a: any) => ({ departmentCode: a.value, departmentName: a.label })) : []
+            }
+
+            if (type == 'ADD') {
+                const resp = await addData(result);
+                if (resp) {
+                    message.success('操作成功!');
+                    tableRef.current?.reload();
+                    return true
+                }
+            }
+            if (type == 'EDIT') {
+                const resp = await editData({ code: data.code, ...result });
+                if (resp) {
+                    message.success('操作成功!');
+                    tableRef.current?.reload();
+
+                }
+                return true
+
+            }
+
+        } catch (error) {
+            console.log({ 'updateTable error': error })
+        }
+
+
+        return false
+
+    }
+
+    const tableDataSearchHandle = (paramName: string) => {
+
+        set_tableDataFilterParams({
+            ...tableDataFilterParams,
+            current: 1,
+            [`${paramName}`]: tableDataSearchKeywords
+        })
+
+    }
+
+
+    const UpDataActBtn = ({ record, type }: { record: any, type: 'EDIT' | 'ADD' }) => {
+        const ref = React.createRef<{ save: any; }>();
+        const [maintype, set_maintype] = useState(undefined);
+        const [dimensionType, set_dimensionType] = useState(undefined);
+
+        useEffect(() => {
+            if (type !== undefined) {
+                formRef.current?.setFieldValue('fillDeptList', []); // 清空填报主体
+            }
+        }, [maintype]);
+
+        useEffect(() => {
+            if (dimensionType !== undefined) {
+                formRef.current?.setFieldValue('relateDeptList', []); // 清空填相关科室
+            }
+        }, [dimensionType]);
+
+        return (
+            <ModalForm
+                title={`${type == 'EDIT' ? '编辑' : '新增'}`}
+                width={352}
+                formRef={formRef}
+                initialValues={type == 'EDIT' ? {
+                    ...record,
+                    fillDeptList: record?.fillDeptList?.map((a: any) => ({ label: a.departmentName, value: Number(a.departmentCode) })) || [],
+                    relateDeptList: record?.relateDeptList?.map((a: any) => ({ label: a.departmentName, value: Number(a.departmentCode) })) || []
+                } : { periodType: 1, type: 1, dimensionType: 1 }}
+                trigger={
+                    type == 'EDIT' ? <a key="edit" >编辑</a> : <a className='add'>新增</a>
+                }
+                onFinish={(val) => {
+                    return updateTable(type == 'EDIT' ? { ...record, ...val } : { ...val }, type);
+                }}
+                modalProps={{ destroyOnClose: true }}
+                colProps={{ span: 24 }}
+                grid
+            >
+
+                {
+                    (
+                        <>
+                            <ProFormText
+                                name="name"
+                                label="填报项目:"
+                                placeholder="请输入"
+                                // disabled={type == 'EDIT'}
+                                rules={[{ required: true, message: '填报项目不能为空!' }]}
+                            />
+
+                            <ProFormRadio.Group
+                                label="填报周期:"
+                                name="periodType"
+                                disabled={type == 'EDIT'&&record.limitFlag}
+                                options={DATAFILL_PERIODTYPE}
+                                rules={[{ required: true }]}
+                            />
+                            <ProFormRadio.Group
+                                label="填报主体类型:"
+                                name="type"
+                                disabled={type == 'EDIT'&&record.limitFlag}
+                                options={DATAFILL_MAINTYPE}
+                                rules={[{ required: true }]}
+                                fieldProps={{
+                                    onChange(e) {
+                                        set_maintype(e.target.value)
+                                    },
+                                }}
+                            />
+                            <ProFormDependency name={['type']}>
+                                {({ type }) => {
+                                    return (
+                                        <ProFormSelect
+                                            label="填报主体:"
+                                            name="fillDeptList"
+                                            disabled={type == 'EDIT'&&record.limitFlag}
+                                            fieldProps={
+                                                type === 1
+                                                    ? {
+                                                        mode: 'tags',
+                                                        maxTagCount: 'responsive',
+                                                        labelInValue: true,
+                                                    }
+                                                    : { labelInValue: true }
+                                            }
+                                            rules={[{ required: true, message: '填报主体不能为空!' }]}
+                                            request={async () => {
+                                                const depLists = await getCurrentHospAlldeps();
+                                                if (depLists) {
+                                                    return transformToLabelValue(depLists);
+                                                }
+                                                return [];
+                                            }}
+                                        />
+                                    );
+                                }}
+                            </ProFormDependency>
+                            <ProFormRadio.Group
+                                label="填报维度:"
+                                name="dimensionType"
+                                disabled={type == 'EDIT'&&record.limitFlag}
+                                options={DATAFILL_DIMENSIONTYPE}
+                                rules={[{ required: true }]}
+                            />
+                            <ProFormDependency name={['dimensionType']}>
+                                {
+                                    ({ dimensionType }) => {
+                                        return dimensionType == 1 && <ProFormSelect
+                                            label='相关科室:'
+                                            disabled={type == 'EDIT'&&record.limitFlag}
+                                            name={'relateDeptList'}
+                                            fieldProps={{
+                                                mode: 'tags',
+                                                maxTagCount: 'responsive',
+                                                labelInValue: true
+                                            }}
+                                            rules={[{ required: true, message: '相关科室不能为空!' }]}
+                                            request={async () => {
+                                                const depLists = await getCurrentHospAlldeps();
+                                                if (depLists) {
+                                                    return transformToLabelValue(depLists);
+                                                }
+                                                return [];
+                                            }}
+                                        />
+                                    }
+                                }
+                            </ProFormDependency>
+                            <ProFormText
+                                name="unit"
+                                label="填报单位:"
+                                placeholder="请输入"
+                                rules={[{ required: true, message: '填报单位不能为空!' }]}
+                            />
+                            <ProFormTextArea
+                                name="description"
+                                label="填报说明:"
+                                placeholder="请输入"
+                                rules={[{ required: false }]}
+                            />
+
+                        </>
+                    )
+                }
+
+            </ModalForm>
+        )
+    }
+
+    useEffect(() => {
+        // if (currentEditTableRow) {
+        //     const fetchTableData = async () => {
+        //         const { columns, dataSource } = await getGenerateTableData();
+        //         set_tableColumns(columns);
+        //         set_tableDataSource(dataSource);
+        //     };
+
+        //     fetchTableData();
+        // }
+    }, [currentEditTableRow]);
+
+    useEffect(() => {
+        if (currentEditRow) {
+            set_actionType('DETAIL');
+            getGenerateTableData();
+        } else {
+            set_actionType('NORMAL');
+        }
+    }, [currentEditRow])
+
+    return (
+        <KCIMPagecontainer className='FillingMana' title={false}>
+            {
+                actionType == 'DETAIL' && (
+                    <div className='FillingMana-detail'>
+                        <div className='FillingMana-detail-header'>
+                            <div className='FillingMana-detail-header-title'>
+                                <div className='backBtn' onClick={() => set_currentEditRow(undefined)}><IconFont style={{ fontSize: 15 }} type={'iconfanhui'} /></div><span>{currentEditRow?.name}</span>
+                            </div>
+                            <div className='FillingMana-detail-header-title-sub'>
+                                <span>填报周期:{currentEditRow&&`${(DATAFILL_PERIODTYPE.find(item => item.value === currentEditRow.periodType))?(DATAFILL_PERIODTYPE.find(item => item.value === currentEditRow.periodType))?.label:''}`}</span>
+                                <span>填报维度:{currentEditRow&&`${(DATAFILL_DIMENSIONTYPE.find(item => item.value === currentEditRow.dimensionType))?(DATAFILL_DIMENSIONTYPE.find(item => item.value === currentEditRow.periodType))?.label:''}`}</span>
+                                <span>填报单位:{currentEditRow?.unit}</span>
+                            </div>
+                        </div>
+                        <div className='FillingMana-detail-content'>
+                            <div className='toolBar'>
+                                <div className='filter'>
+                                    <div className='filterItem'>
+                                        <span className='label' style={{ whiteSpace: 'nowrap' }}> 检索:</span>
+                                        <Input placeholder={'填报科室'} allowClear autoComplete='off'
+                                            suffix={
+                                                <IconFont type="iconsousuo" style={{ color: '#99A6BF' }} onClick={() => tableDataSearchHandle('queryCondition')} />
+                                            }
+                                            onChange={(e) => {
+                                                set_tableDataSearchKeywords(e.target.value);
+                                                if (e.target.value.length == 0) {
+                                                    set_tableDataFilterParams({
+                                                        ...tableDataFilterParams,
+                                                        current: 1,
+                                                        queryCondition: ''
+                                                    });
+                                                }
+                                            }}
+                                            onPressEnter={(e) => {
+                                                set_tableDataFilterParams({
+                                                    ...tableDataFilterParams,
+                                                    current: 1,
+                                                    queryCondition: (e.target as HTMLInputElement).value
+                                                });
+                                            }}
+
+                                        />
+                                    </div>
+                                </div>
+                                <div className='btnGroup'>
+
+                                </div>
+                            </div>
+                            <KCIMTable
+                                columns={[...tableColumns, ...tableColumns_default]}
+                                scroll={{ y: 'calc(100vh - 232px)' }} rowKey='id'
+                                dataSource={tableDataSource}
+                                editable={{
+                                    editableKeys: currentEditTableRow ? [currentEditTableRow.id] : [],
+                                    actionRender: (row, config, defaultDom) => [
+                                        defaultDom.save,
+                                        defaultDom.cancel,
+                                    ],
+                                    onSave(key, record, originRow, newLineConfig) {
+                                        return Promise.resolve(true);
+                                    },
+                                    onCancel: (key, record, originRow, newLineConfig) => {
+                                        set_currentEditTableRow(undefined);
+                                        return Promise.resolve(true)
+                                    },
+                                }}
+                            />
+                        </div>
+                    </div>
+                )
+            }
+            {
+                actionType != 'DETAIL' && (
+                    <div style={{ padding: 16, background: '#fff', borderRadius: 4 }}>
+                        <div className='toolBar'>
+                            <div className='filter'>
+                                <div className='filterItem'>
+                                    <span className='label' style={{ whiteSpace: 'nowrap' }}> 检索:</span>
+                                    <Input placeholder={'填报项目'} allowClear autoComplete='off'
+                                        suffix={
+                                            <IconFont type="iconsousuo" style={{ color: '#99A6BF' }} onClick={() => tableDataSearchHandle('queryCondition')} />
+                                        }
+                                        onChange={(e) => {
+                                            set_tableDataSearchKeywords(e.target.value);
+                                            if (e.target.value.length == 0) {
+                                                set_tableDataFilterParams({
+                                                    ...tableDataFilterParams,
+                                                    current: 1,
+                                                    queryCondition: ''
+                                                });
+                                            }
+                                        }}
+                                        onPressEnter={(e) => {
+                                            set_tableDataFilterParams({
+                                                ...tableDataFilterParams,
+                                                current: 1,
+                                                queryCondition: (e.target as HTMLInputElement).value
+                                            });
+                                        }}
+
+                                    />
+                                </div>
+                            </div>
+                            <div className='btnGroup'>
+                                <UpDataActBtn record={undefined} type={'ADD'} />
+                            </div>
+                        </div>
+                        <KCIMTable columns={columns as ProColumns[]} scroll={{ y: 'calc(100vh - 232px)' }} actionRef={tableRef} rowKey='id' params={tableDataFilterParams} request={(params) => getTableData(params)} />
+                    </div>
+                )
+            }
+        </KCIMPagecontainer>
+    )
+}

+ 101 - 0
src/pages/dataFilling/fillingMana/service.ts

@@ -0,0 +1,101 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2023-03-03 16:31:27
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-02-17 16:18:07
+ * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/service.ts
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+
+
+
+import { request } from 'umi';
+
+//获取table列表数据
+
+export const getTableList = (params?:any) => {
+  return request('/centerSys/datafill/getPageItemList', {
+    method: 'GET',
+    params:{...params}
+  });
+};
+
+//获取当前院区所有科室
+export const getCurrentHospAlldeps = () => {
+  return request('/centerSys/sysdepartment/getHospDepartment', {
+    method: 'GET',
+  });
+};
+
+//获取动态表格数据
+export const generateTableDataReq = (params:{periodType:number,dataYear:string,name?:string}) => {
+  return request('/centerSys/datafill/getMyItemDetail', {
+    method: 'GET',
+    params:{...params}
+  });
+};
+
+//新增
+export type AddTableDataType = {
+
+}
+
+export const addData = (data:AddTableDataType) => {
+  return request('/centerSys/datafill/addItem', {
+    method: 'POST',
+    data
+  });
+};
+
+
+
+//编辑表格数据
+
+export type TableRowEditType = {
+  id:number;
+}&AddTableDataType
+
+export const editData = (data:any) => {
+  return request(`/centerSys/datafill/updateItem`, {
+    method: 'POST',
+    data
+  });
+};
+
+//删除表格操作
+export const delData = (code:string) => {
+  return request('/centerSys/datafill/deleteItem', {
+    method: 'POST',
+    params:{code}
+  });
+};
+
+//撤回操作
+export const withdrawReq = (id:number) => {
+  return request('/medical/qualificationApply/withdraw', {
+    method: 'POST',
+    params:{id}
+  });
+};
+
+//批量上传文件
+export const uploadFilesReq = (formData:any) => {
+  return request('/centerSys/api/upload', {
+    method: 'POST',
+    data:formData
+  });
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 557 - 0
src/pages/dataFilling/fillingMana/style.less

@@ -0,0 +1,557 @@
+.FillingMana-applyDrawer {
+  .applyDrawer-topBar {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+
+    .applyDrawer-topBar-title {
+      font-weight: 500;
+      font-size: 16px;
+      color: #17181A;
+
+      .closeIcon {
+        position: relative;
+        top: 2px;
+        display: inline-flex;
+        justify-content: center;
+        align-items: center;
+        width: 24px;
+        height: 24px;
+        cursor: pointer;
+        margin-right: 8px;
+        border-radius: 4px;
+
+        &:hover {
+          background-color: #E6EAF2;
+        }
+      }
+    }
+
+    .btnGroup {
+      &>span {
+        display: inline-block;
+        text-align: center;
+        cursor: pointer;
+        width: 56px;
+        height: 24px;
+        line-height: 21px;
+        margin-right: 8px;
+        font-weight: 400;
+        font-size: 14px;
+        color: #17181A;
+        background: #FAFCFF;
+        border-radius: 4px;
+        border: 1px solid #DAE2F2;
+
+        &.commit {
+          width: auto;
+          min-width: 56px;
+          color: #FFFFFF;
+          background: #3377FF;
+        }
+
+        &:last-child {
+          margin-right: 0;
+        }
+      }
+    }
+  }
+
+  .content {
+    display: flex;
+    width: 100%;
+    height: calc(100vh - 75px);
+    overflow-y: scroll;
+
+
+    .editContent-header {
+      width: 100%;
+      background: #FFFFFF;
+      border-radius: 4px;
+      padding: 16px;
+
+
+      .editContent-header-title {
+        display: flex;
+        width: 100%;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        font-weight: bold;
+        font-size: 20px;
+        height: 20px;
+        line-height: 20px;
+        color: #17181A;
+        margin-bottom: 12px;
+      }
+
+      .editContent-header-title-sub {
+        font-weight: 400;
+        font-size: 14px;
+        height: 16px;
+        color: #525866;
+        line-height: 16px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+
+      .editContent-header-title-authStandard {
+        position: relative;
+        background: #F7F9FC;
+        border-radius: 4px;
+        min-height: 56px;
+        padding: 6px 12px;
+        padding-right: 64px;
+
+        font-weight: 400;
+        font-size: 14px;
+        color: #17181A;
+        line-height: 18px;
+        margin-top: 12px;
+        border: 1px solid #DAE4F2;
+
+        &::after {
+          position: absolute;
+          content: '';
+          top: 8px;
+          right: 12px;
+          display: inline-block;
+          width: 40px;
+          height: 40px;
+          background: url('../../../../static/shouquanyiju.png');
+          background-size: contain;
+          background-repeat: no-repeat;
+        }
+      }
+
+      .editContent-header-title-detail {
+        min-height: 32px;
+        background: #F7F9FC;
+        border-radius: 4px;
+        max-height: 72px;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        font-weight: 400;
+        font-size: 14px;
+        color: #17181A;
+        line-height: 18px;
+        padding: 6px 12px;
+        margin-top: 10px;
+        margin-bottom: 12px;
+      }
+
+      .appendix {
+        font-weight: 400;
+        font-size: 14px;
+        color: #525866;
+      }
+
+    }
+
+    .apply-info {
+      width: 100%;
+      background: #FFFFFF;
+      border-radius: 4px;
+      padding: 16px;
+      margin-top: 16px;
+
+
+      .apply-info-title {
+        font-weight: 500;
+        font-size: 16px;
+        height: 16px;
+        color: #17181A;
+        line-height: 16px;
+        margin-bottom: 16px;
+      }
+
+      .apply-info-detail {
+        .apply-info-detail-value {
+          display: flex;
+          flex-direction: row;
+          justify-content: flex-start;
+          align-items: center;
+
+          &>span {
+            color: #525866;
+          }
+
+          &>div {
+            border-radius: 4px;
+            max-height: 72px;
+            overflow: hidden;
+            white-space: nowrap;
+            text-overflow: ellipsis;
+            font-weight: 400;
+            font-size: 14px;
+            color: #17181A;
+            line-height: 18px;
+          }
+        }
+
+      }
+
+      .appendix {
+        margin-top: 12px;
+        font-weight: 400;
+        font-size: 14px;
+        color: #525866;
+      }
+    }
+
+    .deptOpinion {
+      width: 100%;
+      background: #FFFFFF;
+      border-radius: 4px;
+      padding: 16px;
+      margin-top: 16px;
+
+      .deptOpinion-title {
+        font-weight: 500;
+        font-size: 16px;
+        height: 16px;
+        line-height: 16px;
+        color: #17181A;
+        margin-bottom: 10px;
+      }
+
+      .deptOpinion-detail {
+        font-weight: 400;
+        font-size: 14px;
+        color: #17181A;
+        line-height: 18px;
+      }
+
+    }
+
+    .managerOpinion {
+      width: 100%;
+      background: #FFFFFF;
+      border-radius: 4px;
+      padding: 16px;
+      margin-top: 16px;
+
+      .managerOpinion-title {
+        font-weight: 500;
+        font-size: 16px;
+        height: 16px;
+        line-height: 16px;
+        color: #17181A;
+        margin-bottom: 10px;
+      }
+
+      .managerOpinion-info {
+        font-weight: 400;
+        font-size: 14px;
+        height: 14px;
+        line-height: 14px;
+        color: #525866;
+        margin-bottom: 12px;
+
+        &>span {
+          font-weight: 400;
+          font-size: 14px;
+          color: #17181A;
+          height: 14px;
+          line-height: 14px;
+        }
+      }
+
+      .managerOpinion-detail {
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: flex-start;
+        font-weight: 400;
+        font-size: 14px;
+        line-height: 14px;
+        color: #525866;
+
+        &>i {
+          display: inline-block;
+          width: 70px;
+          font-style: normal;
+        }
+
+        &>span {
+          display: inline-block;
+          color: #17181A;
+          line-height: 18px;
+          width: calc(100% - 70px);
+        }
+      }
+
+
+    }
+
+    .masterOpinion {
+      width: 100%;
+      background: #FFFFFF;
+      border-radius: 4px;
+      padding: 16px;
+      margin-top: 16px;
+
+      .masterOpinion-title {
+        font-weight: 500;
+        font-size: 16px;
+        height: 16px;
+        line-height: 16px;
+        color: #17181A;
+        margin-bottom: 10px;
+      }
+
+      .masterOpinion-detail {
+        font-weight: 400;
+        font-size: 14px;
+        color: #17181A;
+        line-height: 18px;
+      }
+    }
+
+    .detail-noAuthRecord {
+      display: flex;
+      width: 100%;
+      height: 390px;
+      margin-top: 16px;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      padding: 16px;
+      background: #FFFFFF;
+      border-radius: 4px;
+
+      &>img {
+        width: 160px;
+        height: 140px;
+      }
+
+      .detail-noAuthRecord-title {
+        font-weight: 500;
+        font-size: 24px;
+        height: 24px;
+        color: #17181A;
+        line-height: 24px;
+        margin-top: 24px;
+        margin-bottom: 16px;
+      }
+
+      .detail-noAuthRecord-title-sub {
+        font-weight: 400;
+        font-size: 14px;
+        height: 14px;
+        color: #7A8599;
+        line-height: 14px;
+      }
+
+      .backBtn {
+        width: 56px;
+        height: 24px;
+        text-align: center;
+        margin-top: 24px;
+        background: #3377FF;
+        border-radius: 4px;
+        font-weight: 400;
+        font-size: 14px;
+        color: #FFFFFF;
+        line-height: 24px;
+        cursor: pointer;
+      }
+    }
+
+    // background: #FFFFFF;
+    .applyDrawer-left {
+      width: 760px;
+      height: 100%;
+      padding: 16px;
+      background: #FFFFFF;
+      border-radius: 4px;
+      margin-right: 16px;
+
+      .applyDrawer-left-title {
+        font-weight: 500;
+        font-size: 16px;
+        height: 16px;
+        line-height: 16px;
+        color: #17181A;
+        margin-bottom: 16px;
+      }
+    }
+
+    .applyDrawer-right {
+      width: 350px;
+      height: 100%;
+      padding: 16px;
+      border-radius: 4px;
+      background: #FFFFFF;
+
+      .applyDrawer-right-title {
+        font-weight: 500;
+        font-size: 16px;
+        height: 16px;
+        line-height: 16px;
+        color: #17181A;
+        margin-bottom: 16px;
+      }
+
+      .applyDrawer-right-textarealable {
+        font-weight: 400;
+        font-size: 14px;
+        height: 14px;
+        line-height: 14px;
+        color: #17181A;
+        margin-bottom: 8px;
+      }
+    }
+  }
+}
+
+.FillingMana {
+  padding: 16px;
+  // background: #FFFFFF;
+  // border-radius: 4px;
+
+  .toolBar {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+
+    .filter {
+      display: flex;
+      flex-direction: row;
+      justify-content: flex-start;
+      align-items: center;
+
+      .filterItem {
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+        align-items: center;
+      }
+    }
+
+    .btnGroup {
+
+      .import,
+      .export {
+        cursor: pointer;
+        display: inline-block;
+        font-size: 14px;
+        font-weight: 400;
+        color: #17181A;
+        line-height: 24px;
+        padding: 0 14px;
+        margin-right: 8px;
+        background: #FAFCFF;
+        border-radius: 4px;
+        border: 1px solid #DAE2F2;
+      }
+
+      .add {
+        cursor: pointer;
+        display: inline-block;
+        font-size: 14px;
+        font-weight: 400;
+        color: #FFFFFF;
+        line-height: 24px;
+        padding: 0 14px;
+        background: #3377FF;
+        border-radius: 4px;
+      }
+    }
+
+  }
+
+  .FillingMana-detail {
+    .FillingMana-detail-header {
+      height: 80px;
+      padding: 16px;
+      background: #FFFFFF;
+      border-radius: 4px;
+      margin-bottom: 16px;
+
+      .FillingMana-detail-header-title {
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        margin-bottom: 10px;
+
+        .backBtn {
+          cursor: pointer;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 24px;
+          height: 24px;
+          background: #FAFCFF;
+          border-radius: 4px;
+          margin-right: 12px;
+          border: 1px solid #DAE2F2;
+        }
+
+        &>span {
+          font-weight: bold;
+          font-size: 20px;
+          color: #17181A;
+          height: 20px;
+          line-height: 20px;
+        }
+      }
+
+      .FillingMana-detail-header-title-sub {
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+        padding-left: 36px;
+
+        &>span {
+          display: inline-block;
+          height: 14px;
+          line-height: 14px;
+          font-weight: 400;
+          font-size: 14px;
+          color: #525866;
+          margin-right: 24px;
+        }
+      }
+    }
+
+    .FillingMana-detail-content {
+      padding: 16px;
+      background: #FFFFFF;
+      border-radius: 4px;
+
+      .toolBar {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 16px;
+
+        .filter {
+          display: flex;
+          flex-direction: row;
+          justify-content: flex-start;
+          align-items: center;
+
+          .filterItem {
+            display: flex;
+            flex-direction: row;
+            justify-content: center;
+            align-items: center;
+          }
+        }
+
+        .btnGroup {}
+
+      }
+    }
+  }
+}

+ 1 - 2
src/pages/personalCenter/myApplication/index.tsx

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-03-03 11:30:33
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-31 10:28:20
+ * @LastEditTime: 2025-08-07 17:22:43
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -548,7 +548,6 @@ export default function MyApplication() {
                                     }
                                 }}
                                 onPressEnter={(e) => {
-
                                     set_tableDataFilterParams({
                                         ...tableDataFilterParams,
                                         current: 1,

+ 1 - 4
src/pages/personalCenter/myApplication/service.ts

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-03-03 16:31:27
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-11-28 14:56:40
+ * @LastEditTime: 2025-02-14 16:10:45
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/service.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -13,9 +13,6 @@ import { request } from 'umi';
 
 //获取table列表数据
 
-
-
-
 export const getTableList = (params?:any) => {
   return request('/medical/qualificationApply/list', {
     method: 'GET',

+ 1 - 0
src/pages/personalCenter/myApplication/style.less

@@ -367,6 +367,7 @@
       padding: 16px;
       background: #FFFFFF;
       border-radius: 4px;
+      margin-right: 16px;
 
       .applyDrawer-left-title {
         font-weight: 500;

+ 108 - 38
src/pages/personalCenter/myQualifications/index.tsx

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-03-03 11:30:33
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-31 13:58:04
+ * @LastEditTime: 2025-08-07 17:23:04
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -24,7 +24,7 @@ import { Fragment, Key, useEffect, useRef, useState } from 'react';
 
 
 
-import { addData, delData, editData, getMyInfoReq, getTableDataReq } from './service';
+import { addData, delData, editData, getMyInfoReq, getTableDataReq, getQualificationHistoryReq } from './service';
 
 import './style.less';
 
@@ -40,6 +40,17 @@ import { calculateDelayedDate } from '@/utils/tooljs';
 import { debounce } from 'lodash';
 import { useModel } from '@umijs/max';
 
+// 判断附件是否在半年内到期
+const isExpiringWithinHalfYear = (expireDate: string) => {
+    if (!expireDate) return false;
+    
+    const expireMoment = moment(expireDate);
+    const now = moment();
+    const sixMonthsLater = moment().add(6, 'months');
+    
+    // 如果过期日期在现在和6个月后之间,且还未过期
+    return expireMoment.isBetween(now, sixMonthsLater, 'day', '[]') && expireMoment.isAfter(now);
+};
 
 
 const IconFont = createFromIconfontCN({
@@ -58,6 +69,10 @@ export default function MyQualifications() {
 
     const [currentSelectedTableRows, set_currentSelectedTableRows] = useState<any[]>([]);
 
+    // 添加授权历史记录状态
+    const [qualificationHistory, set_qualificationHistory] = useState<any[]>([]);
+    const [historyLoading, set_historyLoading] = useState<boolean>(false);
+
 
     const tableRef = useRef<ActionType>();
     const [modalFormVisible, set_modalFormVisible] = useState(false);
@@ -158,19 +173,54 @@ export default function MyQualifications() {
         }
     }
 
+    // 获取资质授权历史记录
+    const getQualificationHistory = async (qualificationCode: string) => {
+        set_historyLoading(true);
+        try {
+            const resp = await getQualificationHistoryReq(qualificationCode);
+            if (resp) {
+                set_qualificationHistory(resp);
+            } else {
+                set_qualificationHistory([]);
+            }
+        } catch (error) {
+            console.error('获取授权历史失败:', error);
+            set_qualificationHistory([]);
+        } finally {
+            set_historyLoading(false);
+        }
+    }
+
 
     const updateTable = async (formVal: any, type: 'ADD' | 'EDIT') => {
+        console.log({formVal});
         try {
-            const { files, memo, id } = formVal;
+            const { files, memo, id,dateRange=undefined } = formVal;
             let result = {};
+            // 处理上传文件的 response 结构和错误
             if (files[0].response) {
-                const { name, response: { data: { downUrl } } } = files[0];
+                const response = files[0].response;
+                // 判断接口返回是否成功(code为0或200,根据实际后端约定)
+                if (response.code && response.code !== 0 && response.code !== 200) {
+                    // 上传失败,提示后端返回的错误信息
+                    message.error(response.msg || '文件上传失败!');
+                    return false;
+                }
+                // 判断 data/downUrl 是否存在
+                if (!response.data || !response.data.downUrl) {
+                    message.error('文件上传返回数据异常,缺少下载地址!');
+                    return false;
+                }
+                const { name } = files[0];
+                const { downUrl } = response.data;
                 result = {
                     id: type == 'EDIT' ? id : null,
                     userId: userInfo.id,
                     fileName: name,
                     url: downUrl,
-                    description: memo
+                    description: memo,
+                    activeDate:dateRange?dateRange[0]:undefined,
+                    expireDate:dateRange?dateRange[1]:undefined,
                 };
             } else {
                 const { name ,url} = files[0];
@@ -179,7 +229,9 @@ export default function MyQualifications() {
                     userId: userInfo.id,
                     fileName: name,
                     url: url,
-                    description: memo
+                    description: memo,
+                    activeDate:dateRange?dateRange[0]:undefined,
+                    expireDate:dateRange?dateRange[1]:undefined,
                 };
             }
 
@@ -258,6 +310,10 @@ export default function MyQualifications() {
     useEffect(() => {
         if (drawerVisible) {
             getOperationLevelList();
+            // 获取授权历史记录
+            if (currentEditRow?.qualification?.code) {
+                getQualificationHistory(currentEditRow.qualification.code);
+            }
         }
     }, [drawerVisible]);
     
@@ -279,7 +335,8 @@ export default function MyQualifications() {
                 open={modalFormVisible}
                 initialValues={currentEditAttachment ? {
                     files: [{ uid: currentEditAttachment.id, name: currentEditAttachment.fileName, url: currentEditAttachment.url, status: 'done' }],
-                    memo: currentEditAttachment.description
+                    memo: currentEditAttachment.description,
+                    dateRange:(currentEditAttachment.activeDate&&currentEditAttachment.expireDate)?[moment(currentEditAttachment.activeDate).format('YYYY-MM-DD'),moment(currentEditAttachment.expireDate).format('YYYY-MM-DD')]:[]
                 } : {}}
 
                 onFinish={(val) => {
@@ -297,6 +354,7 @@ export default function MyQualifications() {
             >
 
                 <ProFormUploadDragger
+                    rules={[{ required:true, message: '文件不能为空!' }]}
                     name="files" // 表单字段名
                     action={'/gateway/centerSys/api/upload'}
                     max={1} // 最大上传文件数
@@ -309,25 +367,31 @@ export default function MyQualifications() {
                         headers: {
                             token: initialState?.userData.token as string
                         },
-
-                        // onChange(info) {
-                        //     const { status } = info.file;
-                        //     if (status !== 'uploading') {
-                        //         console.log(info.file, info.fileList);
-                        //     }
-                        //     if (status === 'done') {
-
-                        //         message.success(`${info.file.name} 文件上传成功。`);
-
-                        //     } else if (status === 'error') {
-                        //         message.error(`${info.file.name} 文件上传失败。`);
-                        //     }
-                        // },
+                        // 上传状态变更时的回调,处理成功和失败的提示
+                        onChange(info) {
+                            const { status, response } = info.file;
+                            if (status === 'done') {
+                                // 如果后端返回 code==500,表示上传失败,msg 为错误提示
+                                if (response && response.code === 500) {
+                                    message.error(response.msg || `${info.file.name} 文件上传失败。`);
+                                } else {
+                                    // 上传成功提示
+                                    // message.success(`${info.file.name} 文件上传成功。`);
+                                }
+                            } else if (status === 'error') {
+                                // 上传失败提示
+                                message.error(`${info.file.name} 文件上传失败。`);
+                            }
+                        },
                         // onDrop(e) {
                         //     console.log('拖拽上传的文件:', e.dataTransfer.files);
                         // },
                     }}
                 />
+                <ProFormDateRangePicker rules={[{ required:false, message: '有效期限不能为空!' }]}
+                 label='有效期限:' name={'dateRange'} fieldProps={{
+                    // defaultValue: [moment().startOf('days'), momentq().endOf('days')]
+                }} />
                 <ProFormTextArea name={'memo'} fieldProps={{
                     style: { height: 85 }
                 }} label='附件说明:' />
@@ -378,14 +442,16 @@ export default function MyQualifications() {
                     <div className='authHistory'>
                         <div className='authHistory-title'>授权记录</div>
                         <div className='historyListWrapper'>
-                            {
-                                currentEditRow?.applyList?.map((a: any, index: number) => {
+                            {historyLoading ? (
+                                <div style={{ textAlign: 'center', padding: '20px' }}>加载中...</div>
+                            ) : (
+                                qualificationHistory.map((a: any, index: number) => {
                                     return <div className='historyList' key={index}>
                                         <img src={require(`../../../../static/${index == 0 ? 'gou_black' : 'gou_white'}.png`)} alt="" />
                                         <div className='historyList-detail'>
                                             {(authTimeType.filter((b) => b.value == a.qualificationPeriod)).length > 0 ? (authTimeType.filter((b) => b.value == a.qualificationPeriod))[0].label : ''}
                                             |{`${a.beginDate}至${a.endDate}`}
-                                            {a.applyAdjust.length > 0 && (
+                                            {a.applyAdjust && a.applyAdjust.length > 0 && (
                                                 <Popover className='qualificationAuth-popover' overlayInnerStyle={{borderRadius:4}} content={<AuthHisContent hisList={[...a.applyAdjust]} />} title={false} >
                                                     <IconFont type={'icon-qingliangtishi'} style={{ marginLeft: 10, cursor: 'pointer' }} />
                                                 </Popover>
@@ -394,8 +460,7 @@ export default function MyQualifications() {
                                         <div className={`status ${a.currentStatus == '授权中'?'authing':'expired'}`}>{a.currentStatus}</div>
                                     </div>
                                 })
-                            }
-
+                            )}
                         </div>
                     </div>
 
@@ -473,7 +538,7 @@ export default function MyQualifications() {
                         )}
                     </div>
                     <div style={{ marginTop: 16 }}>
-                        <KCIMTable scroll={{ y: `calc(100vh - 230px)` }}
+                        <KCIMTable scroll={{ y: `calc(100vh - 264px)` }}
                             actionRef={tableRef} columns={columns as ProColumns[]} rowKey='id'
                             params={tableDataFilterParams}
                             request={(params) => getTableData(params)}
@@ -486,10 +551,10 @@ export default function MyQualifications() {
                 </div>
                 <div className='right'>
                     <div className='avatar-info'>
-                        <img src={require('../../../../static/avatar.png')} alt="" />
+                        <img src={userInfo?.avatarUrl?userInfo?.avatarUrl:require('../../../../static/avatar.png')} alt="" />
                         <div className='name'>
                             <div className='name-text'>{userInfo?.name}{userInfo?.gender&&<img src={require(`../../../../static/${userInfo?.gender == '男' ? 'male' : 'female'}.png`)} alt="" />}</div>
-                            <div className='id'>{userInfo?.id}</div>
+                            <div className='id'>{userInfo?.account}</div>
                         </div>
                     </div>
                     <div className='jobInfo'>
@@ -507,6 +572,7 @@ export default function MyQualifications() {
                         </div>
                     </div>
                     <div className='qualifi-info-title'><span>资质信息</span></div>
+            
                     <div className='qualifi-info-detail'>
                         <div className='qualifi-info-list'>资格证号:<span>{userInfo?.qualificationCertificateNo??'-'}</span></div>
                         <div className='qualifi-info-list'>执业类别:<span>{userInfo?.practiceCate??'-'}</span></div>
@@ -517,16 +583,20 @@ export default function MyQualifications() {
                     <div className='attachment-title'><span>相关附件</span><a onClick={() => set_modalFormVisible(true)}>上传</a></div>
                     <div className='attachment-detail'>
                         {
-                            userInfo?.attachments.map((a: any, index: number) => (
-                                <div className='attachment-detail-list' key={index}>
-                                    <img src={require('../../../../static/fileIcon.png')} alt="" />
-                                    <div className='attachment-detail-list-info'>
-                                        <div className='attachment-list-name'>{a.fileName}</div>
-                                        <div className='attachment-list-subText'>{a.description}</div>
+                            userInfo?.attachments.map((a: any, index: number) => {
+                                const isExpiring = isExpiringWithinHalfYear(a.expireDate);
+                                return (
+                                    <div className='attachment-detail-list' key={index}>
+                                        {(a.activeDate&&a.expireDate)&&<div className={`dateTag ${a.validFlag ? '' : 'valid'} ${isExpiring ? 'expiring' : ''}`}><span>{`${a.activeDate}~${a.expireDate}`}</span></div>}
+                                        <img src={require('../../../../static/fileIcon.png')} alt="" />
+                                        <div className='attachment-detail-list-info'>
+                                            <div className='attachment-list-name'>{a.fileName}</div>
+                                            <div className='attachment-list-subText'>{a.description?a.description:'暂无附件说明'}</div>
+                                        </div>
+                                        <Dropdown trigger={['hover']} menu={{ items: drapDownItems(a) }}><span className='more'>...</span></Dropdown>
                                     </div>
-                                    <Dropdown trigger={['hover']} menu={{ items: drapDownItems(a) }}><span className='more'>...</span></Dropdown>
-                                </div>
-                            ))
+                                );
+                            })
                         }
                     </div>
                 </div>

+ 8 - 0
src/pages/personalCenter/myQualifications/service.ts

@@ -60,6 +60,14 @@ export const delData = (id:number) => {
   });
 };
 
+//获取资质授权历史记录
+export const getQualificationHistoryReq = (qualificationCode: string) => {
+  return request<any>('/medical/qualificationManage/getMyQualificationHistory', {
+    method: 'GET',
+    params: { qualificationCode }
+  });
+};
+
 
 
 

+ 62 - 4
src/pages/personalCenter/myQualifications/style.less

@@ -28,7 +28,7 @@
 
       .closeIcon {
         position: relative;
-        top:2px;
+        top: 2px;
         display: inline-flex;
         justify-content: center;
         align-items: center;
@@ -37,6 +37,7 @@
         cursor: pointer;
         margin-right: 8px;
         border-radius: 4px;
+
         &:hover {
           background-color: #E6EAF2;
         }
@@ -168,22 +169,24 @@
 
           .status {
             position: absolute;
-            top:8px;
+            top: 8px;
             right: 16px;
             font-weight: 500;
             font-size: 14px;
             color: #17181A;
+
             &::before {
               position: relative;
               content: '';
               display: inline-block;
               width: 8px;
               height: 8px;
-              top:-1px;
+              top: -1px;
               border-radius: 50%;
               margin-right: 4px;
               background: #FF4D6A;
             }
+
             &.authing {
               &::before {
                 background: #FFB54D;
@@ -479,6 +482,10 @@
       }
 
       .attachment-detail {
+        height: calc(100vh - 472px);
+        overflow-y: scroll;
+        overflow-x: hidden;
+
         .attachment-detail-list {
           position: relative;
           display: flex;
@@ -492,6 +499,46 @@
           padding: 12px;
           margin-bottom: 8px;
 
+          .dateTag {
+            position: absolute;
+            width: 152px;
+            height: 18px;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            top: 0;
+            right: 0;
+            // border-left: 1px solid #fff;
+            // border-bottom: 1px solid #fff;
+            border-radius: 0px 4px 0px 4px;
+            background: url('../../../../static/riqijiaobiao_gray.png');
+            background-size: cover;
+            span {
+              position: relative;
+              top:-1px;
+              display: inline-block;
+              font-size: 12px;
+              color: #6B8299;
+              white-space: nowrap;
+              transform: scale(0.9);
+            }
+
+            &.valid {
+              background: url('../../../../static/riqijiaobiao_red.png') !important;
+              background-size: cover;
+              span {
+                color: #FF5975;
+              }
+            }
+
+            &.expiring {
+              background: linear-gradient(90deg, hsl(29, 89%, 93%) 100%,#fff5eb 0%) !important;
+              span {
+                color: #FF8000;
+              }
+            }
+          }
+
           &>img {
             width: 20px;
             height: 20px;
@@ -500,27 +547,35 @@
 
           .attachment-detail-list-info {
             .attachment-list-name {
+              width: 170px;
               font-weight: 500;
               font-size: 14px;
               height: 14px;
               color: #17181A;
               line-height: 14px;
               margin-bottom: 4px;
+              overflow: hidden;
+              white-space: nowrap;
+              text-overflow: ellipsis;
             }
 
             .attachment-list-subText {
+              width: 295px;
               font-weight: 400;
               font-size: 12px;
               height: 12px;
               color: #7A8599;
               line-height: 12px;
+              overflow: hidden;
+              white-space: nowrap;
+              text-overflow: ellipsis;
             }
           }
 
           .more {
             position: absolute;
             display: inline-block;
-            top: 16px;
+            top: 23px;
             right: 19px;
             width: 16px;
             height: 16px;
@@ -529,10 +584,13 @@
             color: #17181A;
             transform: rotate(270deg);
           }
+          
 
           &:last-child {
             margin-bottom: 0px;
           }
+
+          
         }
       }
     }

+ 552 - 88
src/pages/qualificationMana/doctorQualifiAuth/index.tsx

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-03-03 11:30:33
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-31 14:25:56
+ * @LastEditTime: 2025-09-23 14:54:02
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -14,17 +14,17 @@
 
 import KCIMPagecontainer from '@/components/KCIMPageContainer';
 import { KCIMTable } from '@/components/KCIMTable';
-import { createFromIconfontCN } from '@ant-design/icons';
+import { createFromIconfontCN, MoreOutlined } from '@ant-design/icons';
 
 import { ActionType, arrayMoveImmutable, ProFormText, ProFormTextArea, useRefFunction } from '@ant-design/pro-components';
-import ProForm, { ModalForm, ProFormCascader, ProFormCheckbox, ProFormDateRangePicker, ProFormDependency, ProFormDigit, ProFormRadio, ProFormSelect, ProFormSwitch } from '@ant-design/pro-form'
+import ProForm, { ModalForm, ProFormCascader, ProFormCheckbox, ProFormDateRangePicker, ProFormDependency, ProFormDigit, ProFormRadio, ProFormSelect, ProFormSwitch, ProFormUploadDragger } from '@ant-design/pro-form'
 import { ProColumns } from '@ant-design/pro-table';
-import { Drawer, Input, message, Popconfirm, Popover, Switch, Tooltip } from 'antd';
-import { Fragment, Key, useEffect, useRef, useState } from 'react';
+import { Drawer, Input, message, Popconfirm, Popover, Switch, Tooltip, Dropdown, Modal } from 'antd';
+import { Fragment, Key, useEffect, useRef, useState, useMemo } from 'react';
 import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
 
 
-import { editData, getDepartmentDataReq, getTableDataReq, reAuthData } from './service';
+import { editData, getDepartmentDataReq, getTableDataReq, reAuthData, getApplyListByDoctorReq, getDoctorInfoReq, getDictDataByType, updateDoctorQualInfo } from './service';
 
 import './style.less';
 import { KCIMLeftList } from '@/components/KCIMLeftList';
@@ -37,7 +37,8 @@ import 'moment/locale/zh-cn';
 import moment from 'moment';
 import AuthHisContent from './authHisttory';
 import { buildTree } from '@/utils/format';
-import { useAccess } from '@umijs/max';
+import { useAccess, useModel } from '@umijs/max';
+import { addData as addUserAttachment, editData as editUserAttachment, delData as deleteUserAttachment } from '@/pages/personalCenter/myQualifications/service';
 
 
 
@@ -54,6 +55,7 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
     defaultSelectedKeys?: Key[]
 }) {
 
+    const { initialState } = useModel('@@initialState');
     const [tableDataFilterParams, set_tableDataFilterParams] = useState<any | undefined>();
     const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
     const [dataSource, set_dataSource] = useState<any[]>([]);
@@ -69,12 +71,28 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
     const tableRef = useRef<ActionType>();
     const drawerTableRef = useRef<ActionType>();
     const [drawerTableData, set_drawerTableData] = useState<any[]>([]);
+    const [drawerUserInfo, set_drawerUserInfo] = useState<any | undefined>();
+    // 进入编辑态时的备份,用于取消还原
+    const [drawerUserInfoBackup, set_drawerUserInfoBackup] = useState<any | undefined>();
     const [ifeditQualifiInfo, set_ifeditQualifiInfo] = useState(false);
+    // 字典数据
+    const [dictPracticeCate, set_dictPracticeCate] = useState<any[]>([]);
+    const [dictPracticeSubject, set_dictPracticeSubject] = useState<any[]>([]);
+    const [dictDoctorLevel, set_dictDoctorLevel] = useState<any[]>([]);
 
     const access = useAccess();
     const tabs = access.whatCanIDoInThisPage(location.pathname.replace('/MediResourceManaSys', ''));
     const a = tabs.reduce((prev: any, cur: any) => `${prev},${cur.code}`, '');
 
+    // 工具:判断附件是否在半年内到期
+    const isExpiringWithinHalfYear = (expireDate: string) => {
+        if (!expireDate) return false;
+        const expireMoment = moment(expireDate);
+        const now = moment();
+        const sixMonthsLater = moment().add(6, 'months');
+        return expireMoment.isBetween(now, sixMonthsLater, 'day', '[]') && expireMoment.isAfter(now);
+    };
+
     const columns: ProColumns[] = [
         {
             title: '医生姓名',
@@ -90,8 +108,8 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
             width: 100,
             ellipsis: true,
             renderText(text, record, index, action) {
-                const { userInfo: { id } } = record;
-                return id
+                const { userInfo: { account } } = record;
+                return account
             },
         },
         {
@@ -103,10 +121,30 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
                 return deptName
             },
         },
+        // {
+        //     title: '资质权限',
+        //     ellipsis: true,
+        //     dataIndex: 'qualificationList'
+
+        // },
+        {
+            title: '职称',
+            ellipsis: true,
+            dataIndex: 'title',
+            renderText(text, record, index, action) {
+                const { userInfo: { title } } = record;
+                return title
+            },
+
+        },
         {
-            title: '资质权限',
+            title: '职务',
             ellipsis: true,
-            dataIndex: 'qualificationList'
+            dataIndex: 'jobTitle',
+            renderText(text, record, index, action) {
+                const { userInfo: { jobTitle } } = record;
+                return jobTitle
+            },
 
         },
         {
@@ -118,8 +156,13 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
             render: (_: any, record: any) => {
                 return [
                     <a key={'detailBtn'} onClick={() => {
+                        const isSame = currentEditRow?.userInfo?.id === record?.userInfo?.id;
                         set_currentEditRow(record);
                         set_drawerVisible(true);
+                        // 重复点击同一条时主动刷新数据
+                        if (isSame && record?.userInfo?.id) {
+                            loadDrawerData(record.userInfo.id, record);
+                        }
                     }}>详情</a>
                 ]
             },
@@ -138,14 +181,17 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
         {
             title: '资质名称',
             renderText(text, record, index, action) {
-                const { qualificationInfo: { name,standard } } = record;
-                return <>{name}{(standard&&standard.length>0)&&<Tooltip title={`授权依据:${standard}`}><IconFont style={{marginLeft:10,fontSize:16,position:'relative',top:1}} type={'iconchakan'} /></Tooltip>}</>;
+                const { qualificationInfo: { name, standard } } = record;
+                return <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center' }}>
+                    <span title={name} style={{ display: 'inline-block', maxWidth: 124, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>{name}</span>
+                    {(standard && standard.length > 0) && <Tooltip title={`授权依据:${standard}`}><IconFont style={{ marginLeft: 10, fontSize: 16, position: 'relative', top: 1 }} type={'iconchakan'} /></Tooltip>}
+                </div>;
             },
         },
         {
             title: '授权期限',
             ellipsis: true,
-            width:80,
+            width: 80,
             dataIndex: 'qualificationPeriod',
             renderText(text, record, index, action) {
                 const result = authTimeType.filter((a) => a.value == text);
@@ -155,17 +201,18 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
             },
 
         },
+
         {
             title: '授权时间',
             width: 260,
             renderText(text, record, index, action) {
-                const { beginDate, endDate, applyAdjust } = record;
+                const { beginDate, endDate, applyAdjust = [] } = record;
                 const content = <AuthHisContent hisList={[...applyAdjust]} />
 
                 return <>{`${beginDate}至${endDate}`}
                     {applyAdjust.length > 0 && (
-                        <Popover className='qualificationAuth-popover' overlayInnerStyle={{borderRadius:4}} content={content} title={false} >
-                            <IconFont type={'icon-qingliangtishi'} style={{ marginLeft: 10}} />
+                        <Popover className='qualificationAuth-popover' overlayInnerStyle={{ borderRadius: 4 }} content={content} title={false} >
+                            <IconFont type={'icon-qingliangtishi'} style={{ marginLeft: 10 }} />
                         </Popover>
                     )}</>;
             },
@@ -204,29 +251,29 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
         },
         {
             title: '当前状态',
-            width:80,
+            width: 80,
             ellipsis: true,
             dataIndex: 'currentStatus',
             renderText(text, record, index, action) {
-                    if (text == '授权中') {
-                        return <><span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: '50%', marginRight: 4, background: '#FFB54D' }}></span>{text}</>
-                    }
-                    if (text == '已过期') {
-                        return <><span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: '50%', marginRight: 4, background: '#FF4D6A' }}></span>{text}</>
-                    }
+                if (text == '授权中') {
+                    return <><span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: '50%', marginRight: 4, background: '#FFB54D' }}></span>{text}</>
+                }
+                if (text == '已过期') {
+                    return <><span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: '50%', marginRight: 4, background: '#FF4D6A' }}></span>{text}</>
+                }
             },
         },
         {
             title: '操作',
             key: 'option',
             width: 100,
-            hideInTable:a.indexOf('medical-adjust') == -1,
+            hideInTable: a.indexOf('medical-adjust') == -1,
             fixed: 'right',
             valueType: 'option',
             render: (_: any, record: any) => {
-                const { currentStatus,allowReAuthorize } = record;
+                const { currentStatus, allowReAuthorize } = record;
                 return [
-                    currentStatus == '授权中' ? <UpDataActBtn key={'act'} record={record} type='EDIT' /> :allowReAuthorize == 1&&<UpDataActBtn key={'act'} record={record} type='AUTH' />,
+                    currentStatus == '授权中' ? <UpDataActBtn key={'act'} record={record} type='EDIT' /> : allowReAuthorize == 1 && <UpDataActBtn key={'act'} record={record} type='AUTH' />,
                 ]
             },
         },
@@ -235,17 +282,36 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
 
 
     const getTableData = async (params: any) => {
-        const { qualificationTypeCode } = params;
+        // 非子组件模式下,deptCode 必传;缺失时不发起请求
+        if (!isChildComponent && !params?.deptCode) {
+            return {
+                data: [],
+                success: true,
+                total: 0,
+                current: params?.current || 1,
+                pageSize: params?.pageSize || 10,
+            }
+        }
         const resp = await getTableDataReq(params);
         if (resp) {
-            const data = (resp.list).map((a: any, index: number) => ({ id: index, ...a }));
+            const list = Array.isArray(resp.list) ? resp.list : [];
+            const data = list.map((a: any, index: number) => ({ id: index, ...a }));
             set_currentPageTableData(data);
             return {
                 data,
-                success: true
+                success: true,
+                total: resp.totalCount || resp.total || 0,
+                current: resp.current || params?.current || 1,
+                pageSize: resp.pageSize || params?.pageSize || 10,
             }
         } else {
-            return []
+            return {
+                data: [],
+                success: false,
+                total: 0,
+                current: params?.current || 1,
+                pageSize: params?.pageSize || 10,
+            }
         }
     }
 
@@ -253,7 +319,6 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
     const updateTable = async (formVal: any, type: 'EDIT' | 'AUTH') => {
         try {
             if (type == 'EDIT') {
-
                 const resp = await editData({
                     qualificationApplyId: formVal.id,
                     qualificationPeriod: Number(formVal.qualificationPeriod),
@@ -286,13 +351,11 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
             console.log({ error });
             return false;
         }
-
-
-
-
     }
 
     const formRef = useRef();
+    // 资质信息编辑表单的引用
+    const qualifiInfoFormRef = useRef<any>();
     const UpDataActBtn = ({ record, type }: { record: any, type: 'EDIT' | 'AUTH' }) => {
         const ref = React.createRef<{ save: any; }>();
         return (
@@ -324,6 +387,317 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
         )
     }
 
+    // 进入资质信息编辑态
+    const handleEnterEditQualInfo = () => {
+        // 进入编辑态时备份当前数据,取消时用于回滚
+        set_drawerUserInfoBackup(drawerUserInfo ? JSON.parse(JSON.stringify(drawerUserInfo)) : (currentEditRow?.userInfo ? JSON.parse(JSON.stringify(currentEditRow?.userInfo)) : undefined));
+        set_ifeditQualifiInfo(true);
+        // 拉取字典
+        fetchDictionaries();
+    }
+
+    // 取消资质信息编辑
+    const handleCancelEditQualInfo = () => {
+        // 取消编辑:还原备份
+        if (drawerUserInfoBackup) {
+            set_drawerUserInfo(drawerUserInfoBackup);
+            if (currentEditRow) {
+                set_currentEditRow({ ...currentEditRow, userInfo: drawerUserInfoBackup });
+            }
+        }
+        set_ifeditQualifiInfo(false);
+    }
+
+    // 保存资质信息(当前仅前端合并更新展示,后续可对接后端接口)
+    const handleSaveQualInfo = async () => {
+        try {
+            const values = await (qualifiInfoFormRef?.current?.validateFields?.() || Promise.resolve({}));
+            // 组包并提交接口
+            const payload = {
+                id: (drawerUserInfo || currentEditRow?.userInfo)?.id,
+                qualificationCertificateNo: values.qualificationCertificateNo,
+                practiceCate: values.practiceCate,
+                practiceCertificateNo: values.practiceCertificateNo,
+                // 与后端字段保持一致:practiceSubject
+                practiceSubject: values.major,
+                doctorLevel: values.doctorLevel,
+            };
+            const resp = await updateDoctorQualInfo(payload);
+            if (resp) {
+                const prevInfo = (drawerUserInfo || currentEditRow?.userInfo || {});
+                // 根据 code 反查中文名,保证展示正确
+                const getLabelByCode = (list: any[], code: any) => {
+                    if (!Array.isArray(list)) return undefined;
+                    const found = list.find((i: any) => String(i.value) === String(code));
+                    return found?.label;
+                };
+                const newInfo = {
+                    ...prevInfo,
+                    qualificationCertificateNo: values.qualificationCertificateNo,
+                    practiceCertificateNo: values.practiceCertificateNo,
+                    // 同步维护 code 与中文名
+                    practiceCateCode: values.practiceCate,
+                    majorCode: values.major,
+                    doctorLevelCode: values.doctorLevel,
+                    practiceCate: getLabelByCode(dictPracticeCate, values.practiceCate) ?? prevInfo.practiceCate,
+                    major: getLabelByCode(dictPracticeSubject, values.major) ?? prevInfo.major,
+                    doctorLevel: getLabelByCode(dictDoctorLevel, values.doctorLevel) ?? prevInfo.doctorLevel,
+                };
+                set_drawerUserInfo(newInfo);
+                if (currentEditRow) {
+                    set_currentEditRow({ ...currentEditRow, userInfo: newInfo });
+                }
+                message.success('已保存');
+                set_drawerUserInfoBackup(undefined);
+                set_ifeditQualifiInfo(false);
+            }
+        } catch (e) {
+            // 校验失败时不做处理,保持在编辑态
+        }
+    }
+
+    // 拉取字典并缓存
+    const fetchDictionaries = async () => {
+        try {
+            const [cate, subject, level] = await Promise.all([
+                getDictDataByType('JOB_TYPE'),
+                getDictDataByType('PROFESSIONAL_TYPE'),
+                getDictDataByType('PHYSICIAN_TYPE'),
+            ]);
+            const mapToOptions = (arr: any[]) => (Array.isArray(arr) ? arr.map((i: any) => ({ label: i.name, value: i.code })) : []);
+            set_dictPracticeCate(mapToOptions(cate));
+            set_dictPracticeSubject(mapToOptions(subject));
+            set_dictDoctorLevel(mapToOptions(level));
+        } catch (e) {}
+    }
+
+    // 新增附件卡片(触发弹窗表单)
+    const AddAttachmentCard = () => {
+        return (
+            <ModalForm
+                title={'新增附件'}
+                width={520}
+                initialValues={{ validFlag: true }}
+                trigger={
+                    <div
+                        style={{
+                            width: '100%',
+                            height: '100%',
+                            display: 'flex',
+                            alignItems: 'center',
+                            justifyContent: 'center',
+                            color: '#4E5969',
+                            cursor: 'pointer',
+                            padding: '0 12px',
+                            border: 'none',
+                            background: 'transparent',
+                        }}
+                    >
+                        <span style={{ fontSize: 16, marginRight: 8 }}>+</span>
+                        <span>上传文件</span>
+                    </div>
+                }
+                onFinish={async (val) => {
+                    // 走与个人中心一致的新增接口逻辑
+                    try {
+                        const { files, description, dateRange, validFlag } = val;
+                        if (!files || files.length === 0) {
+                            message.error('请上传附件文件');
+                            return false;
+                        }
+                        let payload: any = {};
+                        if (files[0].response) {
+                            const resp = files[0].response;
+                            if (resp && resp.code === 500) {
+                                message.error(resp.msg || '文件上传失败');
+                                return false;
+                            }
+                            const downUrl = resp?.data?.downUrl;
+                            if (!downUrl) {
+                                message.error('上传返回缺少下载地址');
+                                return false;
+                            }
+                            payload = {
+                                userId: (drawerUserInfo || currentEditRow?.userInfo)?.id,
+                                fileName: files[0].name,
+                                url: downUrl,
+                                description: description,
+                                activeDate: dateRange ? dateRange[0] : undefined,
+                                expireDate: dateRange ? dateRange[1] : undefined,
+                                validFlag: validFlag ? 1 : 0,
+                            };
+                        } else {
+                            payload = {
+                                userId: (drawerUserInfo || currentEditRow?.userInfo)?.id,
+                                fileName: files[0].name,
+                                url: files[0].url,
+                                description: description,
+                                activeDate: dateRange ? dateRange[0] : undefined,
+                                expireDate: dateRange ? dateRange[1] : undefined,
+                                validFlag: validFlag ? 1 : 0,
+                            };
+                        }
+
+                        const resp = await addUserAttachment(payload);
+                        if (resp) {
+                            const prev = (drawerUserInfo || currentEditRow?.userInfo || {});
+                            const list = Array.isArray(prev.attachments) ? prev.attachments : [];
+                            const newInfo = { ...prev, attachments: [...list, { ...payload }] };
+                            set_drawerUserInfo(newInfo);
+                            if (currentEditRow) {
+                                set_currentEditRow({ ...currentEditRow, userInfo: newInfo });
+                            }
+                            message.success('已添加');
+                            return true;
+                        }
+                        return false;
+                    } catch (e) {
+                        return false;
+                    }
+                }}
+                modalProps={{ destroyOnClose: true }}
+                colProps={{ span: 24 }}
+                grid
+            >
+                <ProFormUploadDragger
+                    rules={[{ required:true, message: '文件不能为空!' }]}
+                    name='files'
+                    action={'/gateway/centerSys/api/upload'}
+                    max={1}
+                    description={false}
+                    title={<span style={{ fontSize: 14, color: '#17181A' }}>点击或将文件拖拽到这里上传</span>}
+                    fieldProps={{
+                        name: 'file',
+                        height: 140,
+                        multiple: false,
+                        headers: {
+                            token: initialState?.userData?.token as string
+                        },
+                    }}
+                />
+                <ProFormDateRangePicker name='dateRange' label='有效期:' />
+                <ProFormSwitch name='validFlag' label='当前有效:' />
+                <ProFormTextArea name='description' label='说明:' />
+            </ModalForm>
+        )
+    }
+
+    // 删除附件
+    const handleRemoveAttachment = async (removeIndex: number) => {
+        try {
+            const prev = (drawerUserInfo || currentEditRow?.userInfo || {});
+            const list = Array.isArray(prev.attachments) ? prev.attachments : [];
+            const target = list[removeIndex];
+            if (target?.id) {
+                await deleteUserAttachment(target.id);
+            }
+            const newList = list.filter((_: any, idx: number) => idx !== removeIndex);
+            const newInfo = { ...prev, attachments: newList };
+            set_drawerUserInfo(newInfo);
+            if (currentEditRow) {
+                set_currentEditRow({ ...currentEditRow, userInfo: newInfo });
+            }
+            message.success('已删除');
+        } catch (e) {
+            message.error('删除失败');
+        }
+    }
+
+    // 编辑附件按钮(弹窗表单)
+    const EditAttachmentBtn = ({ index, item }: { index: number; item: any; }) => {
+        return (
+            <ModalForm
+                title={'编辑附件'}
+                width={520}
+                initialValues={{
+                    files: [{ uid: item?.id, name: item?.fileName, url: item?.url, status: 'done' }],
+                    description: item?.description,
+                    dateRange: (item?.activeDate && item?.expireDate) ? [moment(item.activeDate).format('YYYY-MM-DD'), moment(item.expireDate).format('YYYY-MM-DD')] : [],
+                    validFlag: item?.validFlag === 1,
+                }}
+                trigger={<a>编辑</a>}
+                onFinish={async (val) => {
+                    try {
+                        const { files, description, dateRange, validFlag } = val;
+                        let payload: any = { id: item?.id };
+                        if (files && files[0] && files[0].response) {
+                            const resp = files[0].response;
+                            if (resp && resp.code === 500) {
+                                message.error(resp.msg || '文件上传失败');
+                                return false;
+                            }
+                            const downUrl = resp?.data?.downUrl;
+                            if (!downUrl) {
+                                message.error('上传返回缺少下载地址');
+                                return false;
+                            }
+                            payload = {
+                                ...payload,
+                                userId: (drawerUserInfo || currentEditRow?.userInfo)?.id,
+                                fileName: files[0].name,
+                                url: downUrl,
+                            };
+                        } else if (files && files[0]) {
+                            payload = {
+                                ...payload,
+                                userId: (drawerUserInfo || currentEditRow?.userInfo)?.id,
+                                fileName: files[0].name,
+                                url: files[0].url,
+                            };
+                        }
+                        payload = {
+                            ...payload,
+                            description: description,
+                            activeDate: dateRange ? dateRange[0] : undefined,
+                            expireDate: dateRange ? dateRange[1] : undefined,
+                            validFlag: validFlag ? 1 : 0,
+                        };
+
+                        const resp = await editUserAttachment(payload);
+                        if (resp) {
+                            const prev = (drawerUserInfo || currentEditRow?.userInfo || {});
+                            const list = Array.isArray(prev.attachments) ? [...prev.attachments] : [];
+                            list[index] = { ...item, ...payload };
+                            const newInfo = { ...prev, attachments: list };
+                            set_drawerUserInfo(newInfo);
+                            if (currentEditRow) {
+                                set_currentEditRow({ ...currentEditRow, userInfo: newInfo });
+                            }
+                            message.success('已更新');
+                            return true;
+                        }
+                        return false;
+                    } catch (e) {
+                        return false;
+                    }
+                }}
+                modalProps={{ destroyOnClose: true }}
+                colProps={{ span: 24 }}
+                grid
+            >
+                <ProFormUploadDragger
+                    rules={[{ required:true, message: '文件不能为空!' }]}
+                    name='files'
+                    action={'/gateway/centerSys/api/upload'}
+                    max={1}
+                    description={false}
+                    title={<span style={{ fontSize: 14, color: '#17181A' }}>点击或将文件拖拽到这里上传</span>}
+                    fieldProps={{
+                        name: 'file',
+                        height: 140,
+                        multiple: false,
+                        headers: {
+                            token: initialState?.userData?.token as string
+                        },
+                    }}
+                />
+                <ProFormDateRangePicker name='dateRange' label='有效期:' />
+                <ProFormSwitch name='validFlag' label='当前有效:' />
+                <ProFormTextArea name='description' label='说明:' />
+            </ModalForm>
+        )
+    }
+
     const tableDataSearchHandle = (key: string) => {
         set_tableDataFilterParams({ ...tableDataFilterParams, current: 1, [key]: tableDataSearchKeywords })
     }
@@ -389,18 +763,36 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
         }
     }, [qualifiCode]);
 
+    // 提炼为方法,便于重复点击同一行时强制刷新
+    async function loadDrawerData(userId?: any, record?: any) {
+        try {
+            const id = userId ?? record?.userInfo?.id ?? currentEditRow?.userInfo?.id;
+            if (!id) return;
+            const applyResp = await getApplyListByDoctorReq({ userId: id });
+            set_drawerTableData(Array.isArray(applyResp?.list) ? applyResp.list : (Array.isArray(applyResp) ? applyResp : []));
+            const doctorResp = await getDoctorInfoReq({ userId: id });
+            set_drawerUserInfo(doctorResp || {});
+        } catch (e) {
+            console.log('fetch drawer data error', e);
+            if (record?.applyList) {
+                set_drawerTableData([...record.applyList]);
+            } else if (currentEditRow?.applyList) {
+                set_drawerTableData([...currentEditRow.applyList]);
+            }
+            set_drawerUserInfo(record?.userInfo || currentEditRow?.userInfo || {});
+        }
+    };
+
     useEffect(() => {
         if (currentEditRow) {
-            set_drawerTableData([
-                ...currentEditRow.applyList,
-            ]);
+            loadDrawerData();
         }
     }, [currentEditRow]);
 
     useEffect(() => {
         if (currentPageTableData && drawerVisible) {
             const needitem = currentPageTableData.filter((a) => a.userInfo.id == currentEditRow.userInfo.id);
-        
+
             if (needitem.length > 0) {
                 set_currentEditRow(needitem[0]);
             }
@@ -412,6 +804,16 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
         getDepartmentsHandle()
     }, [])
 
+    // 仅向表格透传业务筛选参数,去除 current/pageSize,避免覆盖 ProTable 内部分页参数
+    const sanitizedTableParams = useMemo(() => {
+        const p = { ...(tableDataFilterParams || {}) } as any;
+        if (p && typeof p === 'object') {
+            delete p.current;
+            delete p.pageSize;
+        }
+        return p;
+    }, [tableDataFilterParams]);
+
     return (
         <KCIMPagecontainer className='QualificationAuth' style={!isChildComponent ? {} : { border: 'none' }} title={false}>
             <Drawer destroyOnClose={true} className='doctorQualifiAuth-authDetailDrawer' width={1100}
@@ -419,7 +821,7 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
             >
                 <div className='authDetailDrawer-topBar'>
                     <div className='authDetailDrawer-topBar-title'>
-                        <div className='closeIcon' onClick={() => set_drawerVisible(false)}><IconFont  type={'iconquxiao'} /></div>{'授权详情'}
+                        <div className='closeIcon' onClick={() => set_drawerVisible(false)}><IconFont type={'iconquxiao'} /></div>{'授权详情'}
                     </div>
                     {/* <div className='btnGroup'>
                         <span onClick={() => set_drawerVisible(false)}>取消</span>
@@ -429,70 +831,131 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
                 </div>
                 <div className='authDetailDrawer-content'>
                     <div className='authDetailDrawer-info'>
-                        <div className='avator'><img src={(currentEditRow?.userInfo?.avatarUrl)?currentEditRow?.userInfo?.avatarUrl:require('../../../../static/avatar.png')} alt="avator" /></div>
-                        <div className='authDetailDrawer-info-detail'>
-                            <div className='authDetailDrawer-info-title'>
-                                {currentEditRow?.userInfo?.name}
-                                <img style={{ width: 16, height: 16, marginLeft: 8 }} src={require(`../../../../static/${currentEditRow?.userInfo?.gender == '男' ? 'male' : 'female'}.png`)} alt="" />
-                            </div>
-                            <div className='authDetailDrawer-info-title-sub'>
-                                工号:{currentEditRow?.userInfo?.id}<span style={{ padding: '0 8px' }}></span>
-                                科室:{currentEditRow?.userInfo?.deptName}<span style={{ padding: '0 8px' }}></span>
-                                职称:{currentEditRow?.userInfo?.jobTitle}<span style={{ padding: '0 8px' }}></span>
-                                职务:{currentEditRow?.userInfo?.title}
-                            </div>
-                        </div>
-
+                        {(() => {
+                            const currentUserInfo = drawerUserInfo || currentEditRow?.userInfo; return (
+                                <>
+                                    <div className='avator'><img src={(currentUserInfo?.avatarUrl) ? currentUserInfo?.avatarUrl : require('../../../../static/avatar.png')} alt="avator" /></div>
+                                    <div className='authDetailDrawer-info-detail'>
+                                        <div className='authDetailDrawer-info-title'>
+                                            {currentUserInfo?.name}
+                                            <img style={{ width: 16, height: 16, marginLeft: 8 }} src={require(`../../../../static/${currentUserInfo?.gender == '男' ? 'male' : 'female'}.png`)} alt="" />
+                                        </div>
+                                        <div className='authDetailDrawer-info-title-sub'>
+                                            工号:{currentUserInfo?.account}<span style={{ padding: '0 8px' }}></span>
+                                            科室:{currentUserInfo?.deptName}<span style={{ padding: '0 8px' }}></span>
+                                            职称:{currentUserInfo?.jobTitle}<span style={{ padding: '0 8px' }}></span>
+                                            职务:{currentUserInfo?.title}
+                                        </div>
+                                    </div>
+                                </>
+                            )
+                        })()}
                     </div>
                     <div className='authDetailDrawer-qualication-info'>
                         <div className='authDetailDrawer-qualication-info-title'>资质信息
-                            {/* <span>
-                                {!ifeditQualifiInfo && <a onClick={() => set_ifeditQualifiInfo(true)}>编辑</a>}
-                                {ifeditQualifiInfo && <><a onClick={() => set_ifeditQualifiInfo(true)} style={{ paddingRight: 8 }}>取消</a><a onClick={() => set_ifeditQualifiInfo(true)}>保存</a></>}
-                            </span> */}
+                            <span>
+                                {!ifeditQualifiInfo && <a onClick={handleEnterEditQualInfo}>编辑</a>}
+                                {ifeditQualifiInfo && <><a onClick={handleCancelEditQualInfo} style={{ paddingRight: 8 }}>取消</a><a onClick={handleSaveQualInfo}>保存</a></>}
+                            </span>
                         </div>
                         {
                             !ifeditQualifiInfo && (<>
                                 <div className='authDetailDrawer-qualication-info-tag-wrapper'>
-                                    <div className='qualication-info-tag'>资质证号:<span>{currentEditRow?.userInfo?.qualificationCertificateNo}</span></div>
-                                    <div className='qualication-info-tag'>执业类别:<span>{currentEditRow?.userInfo?.practiceCate}</span></div>
-                                    <div className='qualication-info-tag'>执业证号:<span>{currentEditRow?.userInfo?.practiceCertificateNo}</span></div>
-                                    <div className='qualication-info-tag'>执业专业:<span>{currentEditRow?.userInfo?.major}</span></div>
-                                    <div className='qualication-info-tag'>医师级别:<span>{currentEditRow?.userInfo?.doctorLevel}</span></div>
+                                    {(() => {
+                                        const currentUserInfo = drawerUserInfo || currentEditRow?.userInfo; return (<>
+                                            <div className='qualication-info-tag'>资质证号:<span>{currentUserInfo?.qualificationCertificateNo}</span></div>
+                                            <div className='qualication-info-tag'>执业类别:<span>{currentUserInfo?.practiceCate}</span></div>
+                                            <div className='qualication-info-tag'>执业证号:<span>{currentUserInfo?.practiceCertificateNo}</span></div>
+                                            <div className='qualication-info-tag'>执业专业:<span>{currentUserInfo?.major}</span></div>
+                                            <div className='qualication-info-tag'>医师级别:<span>{currentUserInfo?.doctorLevel}</span></div>
+                                        </>)
+                                    })()}
 
                                 </div>
                             </>)
                         }
                         {
                             ifeditQualifiInfo && (<>
-                                <ProForm layout='vertical' submitter={false} grid>
-                                    <ProFormText colProps={{ md: 12, xl: 8 }} label='资格证号:' />
-                                    <ProFormText colProps={{ md: 12, xl: 8 }} label='执业类别:' />
-                                    <ProFormText colProps={{ md: 12, xl: 8 }} label='执业证号:' />
-                                    <ProFormText colProps={{ md: 12, xl: 8 }} label='执业专业:' />
-                                    <ProFormText colProps={{ md: 12, xl: 8 }} label='医师级别:' />
+                                <ProForm
+                                    layout='vertical'
+                                    submitter={false}
+                                    grid
+                                    formRef={qualifiInfoFormRef}
+                                    initialValues={(() => {
+                                        // 使用抽屉内获取到的医生信息作为初始值
+                                        const currentUserInfo = drawerUserInfo || currentEditRow?.userInfo || {};
+                                        return {
+                                            qualificationCertificateNo: currentUserInfo?.qualificationCertificateNo,
+                                            practiceCate: currentUserInfo?.practiceCateCode ?? currentUserInfo?.practiceCate,
+                                            practiceCertificateNo: currentUserInfo?.practiceCertificateNo,
+                                            major: currentUserInfo?.majorCode ?? currentUserInfo?.major,
+                                            doctorLevel: currentUserInfo?.doctorLevelCode ?? currentUserInfo?.doctorLevel,
+                                        };
+                                    })()}
+                                >
+                                    <ProFormText colProps={{ md: 12, xl: 8 }} label='资格证号:' name='qualificationCertificateNo' placeholder='请输入' />
+                                    <ProFormSelect colProps={{ md: 12, xl: 8 }} label='执业类别:' name='practiceCate' placeholder='请选择' options={dictPracticeCate} />
+                                    <ProFormText colProps={{ md: 12, xl: 8 }} label='执业证号:' name='practiceCertificateNo' placeholder='请输入' />
+                                    <ProFormSelect colProps={{ md: 12, xl: 8 }} label='执业专业:' name='major' placeholder='请选择' options={dictPracticeSubject} />
+                                    <ProFormSelect colProps={{ md: 12, xl: 8 }} label='医师级别:' name='doctorLevel' placeholder='请选择' options={dictDoctorLevel} />
                                 </ProForm>
                             </>)
                         }
 
 
                         <div className='authDetailDrawer-qualication-info-certs'>
-                            {
-                                currentEditRow?.userInfo?.attachments?.map((item: any, index: number) => {
-                                    return (
-                                        <div key={index} className='qualificationInfo-fileItem'>
-                                            <img src={require('../../../../static/fileIcon.png')} alt="" />
-                                            <div className='qualificationInfo-fileItem-detail'>
-                                                <div className='qualificationInfo-fileItem-detail-name'>{item.fileName}</div>
-                                                <div className='qualificationInfo-fileItem-detail-sub'>{item.description}</div>
+                            {(() => {
+                                const currentUserInfo = drawerUserInfo || currentEditRow?.userInfo; return (
+                                    <>
+                                        {currentUserInfo?.attachments?.map((item: any, index: number) => {
+                                            return (
+                                            <div key={index} className='qualificationInfo-fileItem' style={{ position: 'relative' }}>
+                                                {(item.activeDate && item.expireDate) && (() => {
+                                                    const isExpiring = isExpiringWithinHalfYear(item.expireDate);
+                                                    return <div className={`dateTag ${item.validFlag ? '' : 'valid'} ${isExpiring ? 'expiring' : ''}`}><span>{`${item.activeDate}~${item.expireDate}`}</span></div>
+                                                })()}
+                                                <img src={require('../../../../static/fileIcon.png')} alt="" />
+                                                <div className='qualificationInfo-fileItem-detail'>
+                                                    <div className='qualificationInfo-fileItem-detail-name'>{item.fileName}</div>
+                                                    <div className='qualificationInfo-fileItem-detail-sub'>{item.description}</div>
+                                                </div>
+                                                {!ifeditQualifiInfo && (
+                                                    <Tooltip title='下载'>
+                                                        <a className='downloadBtn' href={item.url} target='_blank' ><IconFont style={{ color: '#17181A' }} type={'iconxiazai'} /></a>
+                                                    </Tooltip>
+                                                )}
+                                                {ifeditQualifiInfo && (
+                                                    <Dropdown
+                                                        trigger={['hover']}
+                                                        menu={{
+                                                            items: [
+                                                                {
+                                                                    key: 'edit',
+                                                                    label: <EditAttachmentBtn index={index} item={item} />,
+                                                                },
+                                                                {
+                                                                    key: 'delete',
+                                                                    label: <Popconfirm title='确认删除该附件?' onConfirm={() => handleRemoveAttachment(index)}><a>删除</a></Popconfirm>,
+                                                                },
+                                                            ],
+                                                        }}
+                                                    >
+                                                        <a className='attachment-more'>
+                                                            <MoreOutlined />
+                                                        </a>
+                                                    </Dropdown>
+                                                )}
                                             </div>
-                                            <Tooltip title='下载'>
-                                                <a className='downloadBtn' href={item.url} target='_blank' ><IconFont style={{color:'#17181A'}} type={'iconxiazai'} /></a>
-                                            </Tooltip>
-                                        </div>
-                                    )
-                                })
-                            }
+                                            )
+                                        })}
+                                        {ifeditQualifiInfo && (
+                                            <div key={'add-card'} className='qualificationInfo-fileItem addBtn'>
+                                                <AddAttachmentCard />
+                                            </div>
+                                        )}
+                                    </>
+                                )
+                            })()}
                         </div>
                     </div>
                     <div className='authDetailDrawer-content-table'>
@@ -534,7 +997,7 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
                                         set_tableDataSearchKeywords(e.target.value);
                                         if (e.target.value.length == 0) {
                                             set_tableDataSearchKeywords('');
-                                            set_tableDataFilterParams({...tableDataFilterParams,userName:undefined})
+                                            set_tableDataFilterParams({ ...tableDataFilterParams, userName: undefined })
                                         }
                                     }}
 
@@ -552,9 +1015,10 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
                         )} */}
                     </div>
                     <div style={{ marginTop: 16 }}>
-                        {currentSelectedType && <KCIMTable scroll={{ y: `calc(100vh - 230px)` }}
+                        {currentSelectedType && <KCIMTable
+                            scroll={{ y: `calc(100vh - 230px)` }}
                             actionRef={tableRef} columns={columns as ProColumns[]} rowKey='id'
-                            params={tableDataFilterParams}
+                            params={sanitizedTableParams}
                             request={(params) => getTableData(params)}
                             // dataSource={showList}
                             tableAlertRender={false}

+ 57 - 0
src/pages/qualificationMana/doctorQualifiAuth/service.ts

@@ -54,6 +54,63 @@ export const reAuthData = (data:EditTableDataType) => {
 };
 
 
+// 根据医生获取授权申请列表(抽屉-授权详情表格)
+export const getApplyListByDoctorReq = (params?: any) => {
+  // 说明:后端接口期望入参包含 userId
+  return request<any>('/medical/qualificationManage/listQualificationApplyByDoctor', {
+    method: 'GET',
+    params: { ...params },
+  });
+};
+
+// 获取医生基础信息与附件(抽屉-资质信息附件)
+export const getDoctorInfoReq = (params?: any) => {
+  // 说明:后端接口期望入参包含 userId
+  return request<any>('/medical/qualificationManage/getDoctorInfo', {
+    method: 'GET',
+    params: { ...params },
+  });
+};
+
+// ---- 字典获取(一次拉全量 + 内存缓存) ----
+let __ALL_DICT_CACHE__: any[] | null = null;
+let __ALL_DICT_PROMISE__: Promise<any[] | null> | null = null;
+
+export const getAllDictData = async (): Promise<any[]> => {
+  if (__ALL_DICT_CACHE__) return __ALL_DICT_CACHE__;
+  if (__ALL_DICT_PROMISE__) return (await __ALL_DICT_PROMISE__) || [];
+  __ALL_DICT_PROMISE__ = request<any>('/centerSys/sysdictdata/getDictData', { method: 'GET' })
+    .then((resp) => {
+      const list = Array.isArray(resp?.data) ? resp.data : (Array.isArray(resp) ? resp : []);
+      __ALL_DICT_CACHE__ = list || [];
+      __ALL_DICT_PROMISE__ = null;
+      return __ALL_DICT_CACHE__;
+    })
+    .catch((e) => {
+      __ALL_DICT_PROMISE__ = null;
+      throw e;
+    });
+
+  return (await __ALL_DICT_PROMISE__) || [];
+};
+
+// 基于全量缓存筛选具体字典
+export const getDictDataByType = async (dictType: string) => {
+  const list = await getAllDictData();
+  const target = (list || []).find((item: any) => item.code === dictType);
+  return target?.dataVoList || [];
+};
+
+// 编辑医生资质基础信息
+// 字段:id, qualificationCertificateNo, practiceCate, practiceCertificateNo, practiceSubject, doctorLevel
+export const updateDoctorQualInfo = (data: any) => {
+  return request<any>('/centerSys/user/editUser', {
+    method: 'POST',
+    data,
+  });
+};
+
+
 
 
 

+ 69 - 2
src/pages/qualificationMana/doctorQualifiAuth/style.less

@@ -151,6 +151,61 @@
         border-radius: 4px;
         padding: 0 12px;
 
+        &.addBtn {
+          background: transparent;
+          border: 1px dashed #D9D9D9;
+          justify-content: center;
+          padding: 0 12px;
+        }
+
+        &.addBtn {
+          background: transparent;
+          border: 1px dashed #D9D9D9;
+          justify-content: center;
+          color: #4E5969;
+          cursor: pointer;
+        }
+
+        .dateTag {
+          position: absolute;
+          width: 152px;
+          height: 18px;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          top: 0;
+          right: 0;
+          // border-left: 1px solid #fff;
+          // border-bottom: 1px solid #fff;
+          border-radius: 0px 4px 0px 12px;
+          background: url('../../../../static/riqijiaobiao_gray.png');
+          background-size: cover;
+          span {
+            position: relative;
+            top:-1px;
+            display: inline-block;
+            font-size: 12px;
+            color: #6B8299;
+            white-space: nowrap;
+            transform: scale(0.9);
+          }
+
+          &.valid {
+            background: url('../../../../static/riqijiaobiao_red.png');
+            background-size: cover;
+            span {
+              color: #FF5975;
+            }
+          }
+
+          &.expiring {
+            background: linear-gradient(90deg, #FFF5EB 100%, #fff5eb 0%) !important;
+            span {
+              color: #FF8000;
+            }
+          }
+        }
+
         &>img {
           width: 20px;
           height: 20px;
@@ -165,8 +220,8 @@
           width: 16px;
           height: 16px;
           padding: 2px;
-          top: 16px;
-          right: 12px;
+          top: 25px;
+          right: 8px;
 
           &:hover {
             color: #3376FE;
@@ -177,12 +232,16 @@
           margin-left: 8px;
 
           .qualificationInfo-fileItem-detail-name {
+            width: 170px;
             font-weight: 500;
             font-size: 14px;
             height: 14px;
             color: #17181A;
             line-height: 14px;
             margin-bottom: 4px;
+            overflow: hidden;
+            white-space: nowrap;
+            text-overflow: ellipsis;
           }
 
           .qualificationInfo-fileItem-detail-sub {
@@ -202,6 +261,14 @@
           margin-right: 0;
         }
 
+        .attachment-more {
+          position: absolute;
+          right: 8px;
+          top: 22px;
+          color: #4E5969;
+          &:hover { color: #3376FE; }
+        }
+
       }
     }
   }

+ 2 - 2
src/pages/qualificationMana/qualifiDicMana/index.tsx

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-03-03 11:30:33
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-24 14:56:28
+ * @LastEditTime: 2025-01-06 10:19:41
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -454,7 +454,7 @@ export default function QualifiDicMana({ isChildComponent = false,qualifiCode,on
     useEffect(() => {
         if (currentSelectedType&&!isChildComponent) {                                           
             // getTableData({ qualificationTypeCode: currentSelectedType.code });
-            set_tableDataFilterParams({ ...tableDataFilterParams,current:1, qualificationTypeCode: currentSelectedType.code })
+            set_tableDataFilterParams({ ...tableDataFilterParams,current:1, qualificationTypeCode: currentSelectedType.code,qualificationTypeNameFilter:currentSelectedType.searchKeyword })
         }
     }, [currentSelectedType]);
 

+ 3 - 2
src/pages/qualificationMana/qualificationApproval/index.tsx

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-03-03 11:30:33
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-26 17:28:50
+ * @LastEditTime: 2025-01-02 14:01:34
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -418,10 +418,11 @@ export default function QualificationApproval() {
                                 currentEditRow?.userAttachment?.map((item: any, index: number) => {
                                     return (
                                         <div key={index} className='qualificationInfo-fileItem'>
+                                            {(item.activeDate&&item.expireDate)&&<div className={item.validFlag?'dateTag':'dateTag valid'}><span>{`${item.activeDate}~${item.expireDate}`}</span></div>}
                                             <img src={require('../../../../static/fileIcon.png')} alt="" />
                                             <div className='qualificationInfo-fileItem-detail'>
                                                 <div className='qualificationInfo-fileItem-detail-name'>{item.fileName}</div>
-                                                <div className='qualificationInfo-fileItem-detail-sub'>{item.description}</div>
+                                                <div className='qualificationInfo-fileItem-detail-sub'>{item.description?item.description:'暂无附件说明'}</div>
                                             </div>
                                             <Tooltip title='下载'>
                                                 <div className='downloadBtn' ><IconFont type={'iconxiazai'} /></div>

+ 44 - 4
src/pages/qualificationMana/qualificationApproval/style.less

@@ -24,8 +24,9 @@
         border-radius: 4px;
         margin-right: 8px;
         cursor: pointer;
+
         &:hover {
-            background-color: #E6EAF2;
+          background-color: #E6EAF2;
         }
       }
     }
@@ -193,6 +194,41 @@
           border-radius: 4px;
           padding: 0 12px;
 
+          .dateTag {
+            position: absolute;
+            width: 152px;
+            height: 18px;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            top: 0;
+            right: 0;
+            // border-left: 1px solid #fff;
+            // border-bottom: 1px solid #fff;
+            border-radius: 0px 4px 0px 12px;
+            background: url('../../../../static/riqijiaobiao_gray.png');
+            background-size: cover;
+
+            span {
+              position: relative;
+              top: -1px;
+              display: inline-block;
+              font-size: 12px;
+              color: #6B8299;
+              white-space: nowrap;
+              transform: scale(0.9);
+            }
+
+            &.valid {
+              background: url('../../../../static/riqijiaobiao_red.png');
+              background-size: cover;
+
+              span {
+                color: #FF5975;
+              }
+            }
+          }
+
           &>img {
             width: 20px;
             height: 20px;
@@ -207,7 +243,7 @@
             width: 16px;
             height: 16px;
             padding: 2px;
-            top: 16px;
+            top: 23px;
             right: 12px;
 
             &:hover {
@@ -219,16 +255,20 @@
             margin-left: 8px;
 
             .qualificationInfo-fileItem-detail-name {
+              width: 170px;
               font-weight: 500;
               font-size: 14px;
               height: 14px;
               color: #17181A;
               line-height: 14px;
               margin-bottom: 4px;
+              overflow: hidden;
+              white-space: nowrap;
+              text-overflow: ellipsis;
             }
 
             .qualificationInfo-fileItem-detail-sub {
-              width: 200;
+              width: 295;
               font-weight: 400;
               font-size: 12px;
               height: 12px;
@@ -267,7 +307,7 @@
         color: #17181A;
         margin-bottom: 16px;
 
-       
+
       }
 
       .editContent-header-title-sub {

+ 112 - 34
src/pages/qualificationMana/qualificationAuth/index.tsx

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-03-03 11:30:33
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-25 15:46:44
+ * @LastEditTime: 2025-08-08 14:20:50
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -24,7 +24,7 @@ import { Fragment, Key, useEffect, useRef, useState } from 'react';
 import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
 
 
-import { editData, getTableDataReq, reAuthData } from './service';
+import { editData, getTableDataReq, reAuthData, getQualificationApplyList } from './service';
 
 import './style.less';
 import { KCIMLeftList } from '@/components/KCIMLeftList';
@@ -63,6 +63,7 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
     const [drawerVisible, set_drawerVisible] = useState(false);
     const [currentEditRow, set_currentEditRow] = useState<any>(undefined);
     const [currentPageTableData,set_currentPageTableData] = useState<any[]>([]);
+    const [drawerTableLoading, set_drawerTableLoading] = useState(false);
 
     const access = useAccess();
     const tabs = access.whatCanIDoInThisPage(location.pathname.replace('/MediResourceManaSys', ''));
@@ -98,12 +99,6 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
                 return qualificationTypeName
             },
         },
-        {
-            title: '医生',
-            ellipsis: true,
-            dataIndex: 'doctorList',
-
-        },
         {
             title: '特殊类别',
             ellipsis: true,
@@ -166,8 +161,8 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
             title: '工号',
             ellipsis: true,
             renderText(text, record, index, action) {
-                const { userInfo: { id } } = record;
-                return id;
+                const { userInfo: { account } } = record;
+                return account;
             },
         },
         {
@@ -210,10 +205,10 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
             ellipsis: true,
             renderText(text, record, index, action) {
                 const { beginDate, endDate, applyAdjust } = record;
-                const content = <AuthHisContent hisList={[...applyAdjust]}  />
+                const content = <AuthHisContent hisList={applyAdjust ? [...applyAdjust] : []} />
                     
                 return <>{`${beginDate}至${endDate}`}
-                    {applyAdjust.length > 0 && (
+                    {applyAdjust && applyAdjust.length > 0 && (
                         <Popover className='qualificationAuth-popover' overlayInnerStyle={{borderRadius:4}} content={content} title={false} >
                             <IconFont type={'icon-qingliangtishi'} style={{marginLeft:10,cursor:'pointer' }} />
                         </Popover>
@@ -257,46 +252,99 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
         if (!qualificationTypeCode) {
 
         }
+        console.log('params',params);
         const resp = await getTableDataReq(params);
         if (resp) {
             const data = (resp.list).map((a: any, index: number) => ({ id: index, ...a }));
             set_currentPageTableData(data);
             return {
                 data,
-                success: true
+                success: true,
+                total: resp.totalCount || 0,
+                current: resp.current || 1,
+                pageSize: resp.pageSize || 10
             }
         } else {
-            return []
+            return {
+                data: [],
+                success: false,
+                total: 0,
+                current: 1,
+                pageSize: 10
+            }
         }
     }
 
 
     const updateTable = async (formVal: any, type: 'EDIT' | 'AUTH') => {
-        console.log({formVal});
         try {
             if (type == 'EDIT') {
 
                 const resp = await editData({
-                    qualificationApplyId: formVal.id,
+                    qualificationApplyId: formVal.id, // 新接口返回的数据中有id字段
                     qualificationPeriod: Number(formVal.qualificationPeriod),
                     beginDate: formVal.timerange[0],
                     endDate: formVal.timerange[1],
                     memo: formVal.memo
                 });
                 if (resp) {
-                    tableRef.current?.reload();
+                    // 重新获取抽屉表格数据
+                    if (currentEditRow?.qualification?.code) {
+                        set_drawerTableLoading(true);
+                        const applyListResp = await getQualificationApplyList(currentEditRow.qualification.code);
+                        
+                        // 检查不同的数据结构可能性
+                        let dataList = [];
+                        if (applyListResp && applyListResp.list) {
+                            dataList = applyListResp.list;
+                        } else if (applyListResp && Array.isArray(applyListResp)) {
+                            dataList = applyListResp;
+                        } else if (applyListResp && applyListResp.data) {
+                            dataList = applyListResp.data;
+                        }
+                        
+                        set_drawerTableData(dataList);
+                        
+                        // 强制刷新表格
+                        if (drawerTableRef.current) {
+                            drawerTableRef.current.reload();
+                        }
+                        set_drawerTableLoading(false);
+                    }
                     message.success('操作成功!');
                 }
             } else {
                 const resp = await reAuthData({
-                    qualificationApplyId: formVal.id,
+                    qualificationApplyId: formVal.id, // 新接口返回的数据中有id字段
                     qualificationPeriod: Number(formVal.qualificationPeriod),
                     beginDate: formVal.timerange[0],
                     endDate: formVal.timerange[1],
                     memo: formVal.memo
                 });
                 if (resp) {
-                    tableRef.current?.reload();
+                    // 重新获取抽屉表格数据
+                    if (currentEditRow?.qualification?.code) {
+                        set_drawerTableLoading(true);
+                        const applyListResp = await getQualificationApplyList(currentEditRow.qualification.code);
+                        
+                        // 检查不同的数据结构可能性
+                        let dataList = [];
+                        if (applyListResp && applyListResp.list) {
+                            dataList = applyListResp.list;
+                        } else if (applyListResp && Array.isArray(applyListResp)) {
+                            dataList = applyListResp;
+                        } else if (applyListResp && applyListResp.data) {
+                            dataList = applyListResp.data;
+                        }
+                        
+                        set_drawerTableData(dataList);
+                        
+                        // 强制刷新表格
+                        if (drawerTableRef.current) {
+                            drawerTableRef.current.reload();
+                        }
+                        set_drawerTableLoading(false);
+                    }
                     message.success('操作成功!');
                 }
             }
@@ -308,10 +356,6 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
             console.log({ error });
             return false;
         }
-
-
-
-
     }
 
     const formRef = useRef();
@@ -323,9 +367,9 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
                 width={352}
                 formRef={formRef}
                 initialValues={{
-                    // memo: record.applyMemo,
-                    qualificationPeriod:1,
-                    timerange:[moment().startOf("day"), moment().startOf("day")]
+                    qualificationPeriod: record.qualificationPeriod || 1,
+                    timerange: record.beginDate && record.endDate ? [moment(record.beginDate), moment(record.endDate)] : [moment().startOf("day"), moment().startOf("day")],
+                    memo: record.applyMemo || ''
                 }}
                 trigger={
                     type == 'EDIT' ? <a key="edit" >调整</a> : <a className='auth'>重新授权</a>
@@ -347,7 +391,8 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
     }
 
     const tableDataSearchHandle = (key: string) => {
-        set_tableDataFilterParams({ ...tableDataFilterParams, current: 1, [key]: tableDataSearchKeywords })
+        // 搜索时需要重置到第1页
+        set_tableDataFilterParams({ ...tableDataFilterParams, [key]: tableDataSearchKeywords, _searchTrigger: Date.now() })
     }
 
     const getQualClassRequest = async () => {
@@ -400,22 +445,51 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
     useEffect(() => {
         if (currentSelectedType && !isChildComponent) {
             // getTableData({ qualificationTypeCode: currentSelectedType.code });
-            set_tableDataFilterParams({ ...tableDataFilterParams, current: 1, qualificationTypeCode: currentSelectedType.code })
+            // 分类变化时需要重置到第1页
+            set_tableDataFilterParams({ ...tableDataFilterParams, qualificationTypeCode: currentSelectedType.code, _categoryTrigger: Date.now() })
         }
     }, [currentSelectedType]);
 
     useEffect(() => {
         if (isChildComponent && qualifiCode) {
             set_currentSelectedType(true);
-            set_tableDataFilterParams({ ...tableDataFilterParams, current: 1, qualificationTypeCode: qualifiCode })
+            set_tableDataFilterParams({ ...tableDataFilterParams, qualificationTypeCode: qualifiCode })
         }
     }, [qualifiCode]);
 
     useEffect(() => {
         if (currentEditRow) {
-            set_drawerTableData([
-                ...currentEditRow.applyList,
-            ]);
+            // 通过新接口获取授权申请列表
+            const fetchApplyList = async () => {
+                try {
+                    set_drawerTableLoading(true);
+                    const qualificationCode = currentEditRow.qualification?.code;
+                    if (qualificationCode) {
+                        const resp = await getQualificationApplyList(qualificationCode);
+                        
+                        // 检查不同的数据结构可能性
+                        let dataList = [];
+                        if (resp && resp.list) {
+                            dataList = resp.list;
+                        } else if (resp && Array.isArray(resp)) {
+                            dataList = resp;
+                        } else if (resp && resp.data) {
+                            dataList = resp.data;
+                        }
+                        
+                        set_drawerTableData(dataList);
+                    } else {
+                        set_drawerTableData([]);
+                    }
+                } catch (error) {
+                    console.error('获取授权申请列表失败:', error);
+                    set_drawerTableData([]);
+                } finally {
+                    set_drawerTableLoading(false);
+                }
+            };
+            
+            fetchApplyList();
         }
     }, [currentEditRow]);
     
@@ -487,11 +561,13 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
                     <div className='authDetailDrawer-content-table'>
                         <div className='authDetailDrawer-content-table-title'>授权信息</div>
                         <KCIMTable
+                            key={`drawer-table-${drawerTableData.length}`}
                             actionRef={drawerTableRef} columns={drawerTablecolumns as ProColumns[]} rowKey='id'
                             // request={(params) => getTableData(params)}
                             dataSource={drawerTableData}
                             tableAlertRender={false}
                             scroll={{ y: 545 }}
+                            loading={drawerTableLoading}
                         />
                     </div>
 
@@ -526,7 +602,8 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
                                     }}
                                     fieldProps={{
                                         onChange(value, option) {
-                                            set_tableDataFilterParams({ ...tableDataFilterParams, current: 1, operationLevelCode: value })
+                                            // 筛选条件变化时需要重置到第1页
+                                            set_tableDataFilterParams({ ...tableDataFilterParams, operationLevelCode: value, _levelTrigger: Date.now() })
                                         },
                                     }}
                                 />
@@ -560,11 +637,12 @@ export default function QualificationAuth({ isChildComponent = false, qualifiCod
                                     options={['医疗技术', '手术']}
                                     fieldProps={{
                                         onChange(checkedValue) {
+                                            // 筛选条件变化时需要重置到第1页
                                             set_tableDataFilterParams({
                                                 ...tableDataFilterParams,
-                                                current: 1,
                                                 techFlag: checkedValue.findIndex((a) => a == '医疗技术') != -1 ? 1 : 0,
                                                 operationFlag: checkedValue.findIndex((a) => a == '手术') != -1 ? 1 : 0,
+                                                _checkboxTrigger: Date.now()
                                                 // enableFlag: checkedValue.findIndex((a) => a == '已停用') != -1 ? 0 : 1,
                                             })
                                         },

+ 8 - 0
src/pages/qualificationMana/qualificationAuth/service.ts

@@ -46,6 +46,14 @@ export const reAuthData = (data:EditTableDataType) => {
   });
 };
 
+// 获取资质申请列表
+export const getQualificationApplyList = (qualificationCode: string) => {
+  return request<any>('/medical/qualificationManage/listQualificationApply', {
+    method: 'GET',
+    params: { qualificationCode }
+  });
+};
+
 
 
 

+ 3 - 2
src/pages/qualificationMana/qualificationBatchAuth/DoctorSelection/index.tsx

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2024-12-11 11:21:13
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-13 10:01:58
+ * @LastEditTime: 2025-08-04 18:05:34
  * @FilePath: /MediResourceManaSys/src/pages/qualificationMana/qualificationBatchAuth/DoctorSelection/index.tsx
  * @Description: 从外部传入医生数据,并对外提供选中项的完整数据
  */
@@ -12,6 +12,7 @@ import './style.less';
 import { ProFormCheckbox, ProFormText } from '@ant-design/pro-components';
 
 interface Doctor {
+    account: string;
     name: string;
     id: string;
     deptName: string;
@@ -94,7 +95,7 @@ const DoctorSelection: React.FC<DoctorSelectionProps> = ({ doctorsData = [], onS
                         />
                         <div className="doctor-selection-info">
                             <div className="doctor-selection-name">{d.name}</div>
-                            <div className="doctor-selection-subinfo">{d.id} | {d.deptName}</div>
+                            <div className="doctor-selection-subinfo">{d.account} | {d.deptName}</div>
                         </div>
                         <ProFormCheckbox  
                             className="doctor-selection-checkbox" 

+ 69 - 63
src/pages/static/index.tsx

@@ -1,96 +1,102 @@
 /*
- * @Author: code4eat awesomedema@gmail.com
- * @Date: 2023-03-03 11:30:33
+ * @Author: your name
+ * @Date: 2022-03-03 18:04:40
+ * @LastEditTime: 2025-02-28 11:35:07
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-09-13 12:13:10
- * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
- * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/reports/index.tsx
  */
 
-import { createFromIconfontCN } from '@ant-design/icons';
+import { useEffect, useState } from 'react';
 
 import { Skeleton } from 'antd';
-import { useEffect, useRef, useState } from 'react';
-import { useLocation } from 'umi';
 
+import { useModel } from '@umijs/max';
+import { getUrlParams } from '@/utils/tooljs';
+import { getPlatFormParams } from './service';
 import KCIMPagecontainer from '@/components/KCIMPageContainer';
 
 
-
-const IconFont = createFromIconfontCN({
-  scriptUrl: '',
-});
-
-
-function getQueryParams() {
-  const queryString = window.location.search; // 获取 location.search 的值
-
-  // 使用正则表达式匹配 softUrl 的完整值
-  const softUrlMatch = queryString.match(/softUrl=([^&]+(?:&[^=]+=true)*)/);  // 改进后的正则表达式
-
-  let softUrl = ''; // 用于存储 softUrl 的值
-
-  if (softUrlMatch) {
-    // 获取匹配到的第一个捕获组(即 softUrl 的值),并进行解码
-    softUrl = decodeURIComponent(softUrlMatch[1]);
+const findPage = (tree:any[], pathname:string):any => {
+  for (let node of tree) {
+    // 检查当前节点的路径是否匹配
+    if (`/MediResourceManaSys${node.path}` === pathname) {
+      return node; // 找到匹配的节点
+    }
+    // 如果当前节点有子节点,递归查找
+    if (node.children && node.children.length > 0) {
+      const found = findPage(node.children, pathname);
+      if (found) return found;
+    }
   }
+  return null; // 如果没有找到匹配的节点
+};
 
-  // console.log({ softUrl, queryString, softUrlMatch }); // 打印完整的 softUrl
-
-  return { softUrl }; // 返回带有完整 softUrl 的对象
-}
-
-
-
-
-export default function StaticPage() {
-
+export default () => {
+  const { initialState } = useModel('@@initialState');
   const [specialPageUrl, setspecialPageUrl] = useState<string | undefined>(undefined);
   const [loading, setloading] = useState(false);
-  const { softUrl } = getQueryParams();
-  const location = useLocation();
-  const [scale,set_scale] = useState(0.5);
-  const [translate,set_translate] = useState({x:0,y:0});
+  const { pathname } = location;
 
+  
+  
 
   const onLoadhandle = () => {
     setloading(false);
   };
 
-  const adjustIframeScale = ()=>{
-    const desiredWidth = 1200
-    const windowWidth = window.innerWidth - 232;
-    let scale = windowWidth / desiredWidth;
-    // scale = Math.min(scale, 1); // 限制最大缩放比例为1
-    const  translateX = (windowWidth - desiredWidth * scale) / 2 / scale;
-    const translateY = 0; // 如果需要,也可以计算垂直平移
-    set_translate({x:translateX,y:translateY});
-    set_scale(scale);
+  const getHost = async ()=>{
+       const resp = await getPlatFormParams('1547394914533380096','1788836132977512448');
+       if(resp){
+          return resp
+       }
   }
 
+  const loadPage = ()=>{
+    
+    if (initialState && initialState.spacicalPageParamsType && initialState.userData) {
+      const {
+        spacicalPageParamsType,
+        userData: { youshuToken },
+      } = initialState;
+
+  
+      
+      
+      const spacicalPage = findPage(spacicalPageParamsType, pathname);
+      // console.log({pathname,spacicalPageParamsType,spacicalPage});
+  
+      if (spacicalPage) {
+        //当前页面属于有数数据展示页面
+        const url = `${spacicalPage.softUrl}`;
+
+        const {noMenu=undefined,noTopbar=undefined} = getUrlParams(`http://localhost:8000${url}`);
+        if(noMenu == 'true'&&noTopbar == 'true'){
+          getHost().then((val)=>{
+                setspecialPageUrl(`${val.value}${url}&userData=${localStorage.getItem('userData')}`);
+          })
+        }else{
+          setspecialPageUrl(url);
+        }
+        setloading(false); 
+      }
+    }
+  }
 
-  useEffect(() => {
-    setloading(true);
-    setspecialPageUrl(softUrl ? softUrl : '');
-    console.log({softUrl});
-  }, [location]);
 
   useEffect(() => {
-    adjustIframeScale();
-    window.addEventListener('resize', adjustIframeScale);
-    return ()=>{
-      window.removeEventListener('resize', adjustIframeScale);
-    }
-  }, [])
+    setloading(true); 
+    loadPage();
+  }, [pathname,initialState]);
 
   return (
-    <KCIMPagecontainer className="StaticPage" title={false}>
+    <KCIMPagecontainer style={{padding:0}} title={false}>
       <Skeleton loading={loading} paragraph={{ rows: 50 }} active />
       <iframe
         onLoad={() => onLoadhandle()}
-        style={{ width: '100%', height: 'calc(100vh - 80px)', border: 'none',overflow:'hidden'}}
+        style={{ width: '100%', height: '100vh', border: 'none' }}
         src={specialPageUrl}
-      ></iframe>
+      />
     </KCIMPagecontainer>
   );
-}
+};

+ 14 - 5
src/pages/static/service.js → src/pages/static/service.ts

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-03-03 16:31:27
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2023-10-20 11:15:06
+ * @LastEditTime: 2025-02-28 10:21:16
  * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/service.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -14,7 +14,7 @@ import axios from 'axios';
 
 //获取table列表数据
 
-export const getData = (params) => {
+export const getData = (params: any) => {
   return request('/costAccount/computeImport/getPatientItemList', {
     method: 'GET',
     params:{...params}
@@ -22,7 +22,7 @@ export const getData = (params) => {
 };
 
 //获取总数
-export const getTotalNumReq = (computeDate) => {
+export const getTotalNumReq = (computeDate: any) => {
   return request('/costAccount/computeImport/getComputeTotal', {
     method: 'GET',
     params:{computeDate}
@@ -30,7 +30,7 @@ export const getTotalNumReq = (computeDate) => {
 };
 
 //导入数据
-export const importDataPost = (data) => {
+export const importDataPost = (data: any) => {
   return request('/costAccount/computeImport/importPatientItem', {
     method: 'POST',
     data
@@ -46,7 +46,7 @@ export const downloadTemplateReq = () => {
   let path = '/gateway/costAccount/computeImport/exportPatientItem';
 
   const userData = localStorage.getItem('userData');
-  const { token = '' } = JSON.parse(userData);
+  const { token = '' } = JSON.parse(userData as string);
 
   axios({
       method: 'get',
@@ -76,6 +76,15 @@ export const downloadTemplateReq = () => {
 
 }
 
+//获取中台指定参数
+export const getPlatFormParams = (systemId:string,code: string) => {
+
+  return request('/centerSys/parameter/getParameterByCode', {
+      method: 'GET',
+      params: { systemId,parameterCode:code }
+  })
+}
+
 
 
 

+ 27 - 1
src/utils/tooljs.ts

@@ -2,7 +2,7 @@
  * @Author: code4eat awesomedema@gmail.com
  * @Date: 2023-02-20 14:31:06
  * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2024-12-16 15:54:17
+ * @LastEditTime: 2025-02-28 10:19:30
  * @FilePath: /BudgetManaSystem/src/utils/tooljs.js
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -377,6 +377,32 @@ export function calculateDelayedDate(dateStr: string, days: number): string {
 
 
 
+export const getUrlParams = (url: string): { [key: string]: string | string[] } => {
+  // 创建一个URL对象(假设URL是有效的)
+  const urlObj = new URL(url);
+  // 获取所有查询字符串
+  const queryParams = new URLSearchParams(urlObj.search);
+  const params: { [key: string]: string | string[] } = {};  // 定义具有索引签名的对象
+
+  // 遍历查询字符串并将它们添加到params对象中
+  for (const [key, value] of queryParams) {
+      // 检查对象中是否已经存在这个键
+      if (params.hasOwnProperty(key)) {
+          // 如果这个键已经有值,将它转换为数组(如果尚未转换的话)
+          if (!Array.isArray(params[key])) {
+              params[key] = [params[key] as string];  // 作为 string 断言是安全的
+          }
+          // 添加新的值到数组
+          (params[key] as string[]).push(value);
+      } else {
+          // 如果这个键还没有值,直接添加
+          params[key] = value;
+      }
+  }
+
+  return params;
+}
+
 
 
 

BIN
static/riqijiaobiao_gray.png


BIN
static/riqijiaobiao_red.png