chenlei 4 meses atrás
pai
commit
46927868ec

+ 105 - 5
src/api/management.ts

@@ -17,6 +17,9 @@ import {
   YES_OR_NO,
   IManageNormItem,
   MaterialType,
+  DEPT_STATUS_ENUM,
+  REVIEW_MATERIAL_TYPE,
+  ARCHIVE_TYPE,
 } from "@/types";
 import { requestByGet, requestByPost, requestPagination } from "@dage/service";
 
@@ -104,10 +107,10 @@ export const getManageFormListApi = (params: IManageFormListParams) => {
 // 部门考核单概况
 export const getManageFormDetailApi = (
   id: number | string,
-  accessId: number | string
+  assessId: number | string
 ) => {
   return requestByGet<IManageFormDetail>(
-    `/api/cms/fill/detail/${id}/${accessId}`
+    `/api/cms/fill/detail/${id}/${assessId}`
   );
 };
 
@@ -190,7 +193,7 @@ export const getManageNormListApi = (params: {
 
 // 附件管理-列表
 export const getManageFileListAPi = (params: any) => {
-  return requestByPost("/api/cms/assessFile/pageList", params);
+  return requestByPost("/api/cms/annex/page", params);
 };
 
 // 自评得分
@@ -202,9 +205,13 @@ export const saveSelfScoreApi = (
 };
 
 // 查看指标&考核单资料列表
-export const getFileListApi = (moduleId: number, type: "norm" | "access") => {
+export const getFileListApi = (
+  assessId: number,
+  type: "norm" | "assess",
+  moduleId: number
+) => {
   return requestByGet<MaterialType[]>(
-    `/api/cms/fill/norm/getFile/${moduleId}/${type}`
+    `/api/cms/fill/norm/getFile/${moduleId}/${type}/${assessId}`
   );
 };
 
@@ -212,3 +219,96 @@ export const getFileListApi = (moduleId: number, type: "norm" | "access") => {
 export const deleteFileApi = (ids: string | number) => {
   return requestByGet(`/api/cms/fill/file/removes/${ids}`);
 };
+
+// 考核单提交
+export const submitAssessmentApi = (deptId?: string) => {
+  return requestByGet(`/api/cms/fill/save/${deptId}`);
+};
+
+// 考核单审核
+export const examineAssessmentApi = (
+  id: number | string,
+  status: DEPT_STATUS_ENUM
+) => {
+  return requestByPost(`/api/cms/fill/audit/${id}/${status}`);
+};
+
+// 附加项-评定意见
+export const additionalEvaOpinionApi = (params: {
+  assessId: number;
+  deptId: number;
+  opinionJson: string;
+  type: "add" | "sub";
+}) => {
+  return requestByPost("/api/cms/review/addItem/opinion", params);
+};
+
+// 考核评定-评定意见
+export const assessmentEvaOpinionApi = (params: {
+  deptId: number;
+  opinion: string;
+}) => {
+  return requestByPost("/api/cms/review/dept/opinion", params);
+};
+
+// 考核评定-设置资料是否合格
+export const changeMaterialConditionApi = (params: {
+  condition: REVIEW_MATERIAL_TYPE;
+  deptId: number;
+  materialId: number;
+}) => {
+  return requestByPost("/api/cms/review/material/condition", params);
+};
+
+// 考核评定-资料-评定意见
+export const materialEvaOpinionApi = (params: {
+  deptId: number;
+  materialId: number;
+  opinion: string;
+}) => {
+  return requestByPost("/api/cms/review/material/opinion", params);
+};
+
+// 考核评定-指标-评定
+export const normEvaOpinionApi = (params: {
+  deptId: number;
+  deptNormId: number;
+  mats: {
+    condition: REVIEW_MATERIAL_TYPE;
+    materialId: number;
+    opinion: string;
+  }[];
+  opinionScore?: number;
+}) => {
+  return requestByPost("/api/cms/review/norm/opinion", params);
+};
+
+// 考核评定-资料列表
+export const getReviewNormListApi = (params: {
+  deptId: string | number;
+  searchKey?: string;
+  uploadStatus?: YES_OR_NO;
+}) => {
+  return requestByPost<IManageNormItem[]>(
+    "/api/cms/review/norm/getList",
+    params
+  );
+};
+
+// 考核评定-提交
+export const saveReviewApi = (assessId: number) => {
+  return requestByGet(`/api/cms/review/save/${assessId}`);
+};
+
+// 考核评定-退回
+export const refundReviewApi = (assessId: number, deptId: number) => {
+  return requestByGet(`/api/cms/review/dept/return/${deptId}/${assessId}`);
+};
+
+// 附件归档
+export const changeArchiveApi = (
+  condition: ARCHIVE_TYPE,
+  materialId: number
+) => {
+  return requestByGet(`/api/cms/annex/archive/${materialId}/${condition}`);
+};

+ 5 - 0
src/api/performance.ts

@@ -0,0 +1,5 @@
+import { requestByGet } from "@dage/service";
+
+export const getPerformanceDetailApi = () => {
+  return requestByGet(``);
+};

+ 18 - 1
src/constants.ts

@@ -1,4 +1,10 @@
-import { ASS_INDEX_TYPE, DEPT_STATUS_ENUM, PUBLISH_ENUM } from "./types";
+import {
+  ARCHIVE_TYPE,
+  ASS_INDEX_TYPE,
+  DEPT_STATUS_ENUM,
+  PUBLISH_ENUM,
+  REVIEW_MATERIAL_TYPE,
+} from "./types";
 
 export const PUBLISH_STATUS_MAP = {
   [PUBLISH_ENUM.PENDING]: {
@@ -129,3 +135,14 @@ export const DEPT_STATUS_OPTIONS = [
     value: DEPT_STATUS_ENUM.SUCCESS,
   },
 ];
+
+export const REVIEW_MATERIAL_STATUS_MAP = {
+  [REVIEW_MATERIAL_TYPE.PENDING]: "待审核",
+  [REVIEW_MATERIAL_TYPE.PASS]: "合格",
+  [REVIEW_MATERIAL_TYPE.FAIL]: "不合格",
+};
+
+export const ARCHIVE_TYPE_MAP = {
+  [ARCHIVE_TYPE.ARCHIVED]: "已归档",
+  [ARCHIVE_TYPE.UNARCHIVED]: "未归档",
+};

+ 15 - 3
src/pages/Assessment/Index/components/Container/index.tsx

@@ -1,5 +1,5 @@
 import { FC, useEffect, useMemo, useState } from "react";
-import { Table } from "antd";
+import { Empty, Table } from "antd";
 import style from "../../index.module.scss";
 import { DageLoading } from "@dage/pc-components";
 import { getAssIndexDetailApi } from "@/api";
@@ -13,6 +13,7 @@ export interface ContainerProps {
 
 export const Container: FC<ContainerProps> = ({ currentId, type }) => {
   const isFixed = type === ASS_INDEX_TYPE.FIXED;
+  const [noData, setNoData] = useState(false);
   const [loading, setLoading] = useState(false);
   const [detail, setDetail] = useState<IAssIndexDetail | null>(null);
   // 打分点
@@ -56,10 +57,21 @@ export const Container: FC<ContainerProps> = ({ currentId, type }) => {
   };
 
   useEffect(() => {
-    getDetail();
+    if (!currentId) {
+      setNoData(true);
+    } else {
+      getDetail();
+      setNoData(false);
+    }
   }, [currentId]);
 
-  return (
+  return noData ? (
+    <Empty
+      className={style.container}
+      image={Empty.PRESENTED_IMAGE_SIMPLE}
+      style={{ flex: 1 }}
+    />
+  ) : (
     <div className={style.container}>
       <h4 className={style.containerTitle}>指标详情</h4>
 

+ 6 - 2
src/pages/Assessment/Index/components/Sidebar/index.tsx

@@ -10,7 +10,6 @@ import { PlusOutlined } from "@ant-design/icons";
 import { deleteAssIndexApi, getAssIndexTreeApi } from "@/api";
 import { AssIndexTreeItemType, ASS_INDEX_TYPE, YES_OR_NO } from "@/types";
 import style from "../../index.module.scss";
-import { isNumber } from "lodash";
 
 export interface SidebarProps {
   /**
@@ -19,7 +18,7 @@ export interface SidebarProps {
   checkable?: boolean;
   type: ASS_INDEX_TYPE;
   currentId: null | number;
-  setCurrentId: (id: number) => void;
+  setCurrentId: (id: number | null) => void;
 }
 
 export const Sidebar: FC<SidebarProps> = ({
@@ -60,6 +59,11 @@ export const Sidebar: FC<SidebarProps> = ({
         lastCheckedItem.current = data[0];
 
         setTreeData(transformTreeData(data));
+      } else {
+        setCurrentId(null);
+        setCheckedKeys([]);
+        setTreeData([]);
+        lastCheckedItem.current = null;
       }
     } finally {
       setLoading(false);

+ 25 - 10
src/pages/AssessmentDetail/components/EvaluationFormModal/index.tsx

@@ -1,24 +1,27 @@
-import { FC } from "react";
-import { Form, Input, InputNumber, Modal, ModalProps } from "antd";
+import { FC, useEffect, useState } from "react";
+import { Form, Input, Modal, ModalProps } from "antd";
 import style from "@/components/AddIndexModal/index.module.scss";
 
-export interface EvaluationModalProps extends Omit<ModalProps, "onOk"> {
+export interface EvaluationFormModalProps extends Omit<ModalProps, "onOk"> {
+  initContent?: string;
   onCancel?: () => void;
   onOk?: (val: any) => void;
 }
 
 const { TextArea } = Input;
 
-export const EvaluationModal: FC<EvaluationModalProps> = ({
+export const EvaluationFormModal: FC<EvaluationFormModalProps> = ({
+  initContent,
   open,
   onOk,
   onCancel,
   ...rest
 }) => {
   const [form] = Form.useForm<any>();
+  const [loading, setLoading] = useState(false);
 
   const handleCancel = () => {
-    form.resetFields();
+    form.setFieldValue("opinion", initContent);
     onCancel?.();
   };
 
@@ -27,10 +30,19 @@ export const EvaluationModal: FC<EvaluationModalProps> = ({
   };
 
   const handleSubmit = async (values: any) => {
-    onOk?.(values);
-    handleCancel();
+    try {
+      setLoading(true);
+      await onOk?.(values);
+      handleCancel();
+    } finally {
+      setLoading(false);
+    }
   };
 
+  useEffect(() => {
+    form.setFieldValue("opinion", initContent);
+  }, [initContent]);
+
   return (
     <Modal
       className={style.modal}
@@ -40,8 +52,10 @@ export const EvaluationModal: FC<EvaluationModalProps> = ({
       open={open}
       width={640}
       okButtonProps={{
-        disabled: false,
+        disabled: loading,
       }}
+      forceRender
+      maskClosable={false}
       onOk={handleConfirm}
       onCancel={handleCancel}
       {...rest}
@@ -51,11 +65,12 @@ export const EvaluationModal: FC<EvaluationModalProps> = ({
         form={form}
         onFinish={handleSubmit}
       >
-        <Form.Item name="">
+        <Form.Item name="opinion">
           <TextArea
-            rows={6}
+            rows={20}
             maxLength={2000}
             placeholder="请输入内容,不超过2000字"
+            showCount
           />
         </Form.Item>
       </Form>

+ 109 - 52
src/pages/AssessmentDetail/components/IndexAssessment/index.tsx

@@ -1,9 +1,10 @@
 import { FC, useEffect, useMemo, useState } from "react";
 import { Button, Form, Input, Select, Table } from "antd";
-import { useNavigate, useParams } from "react-router-dom";
-import { getManageNormListApi } from "@/api";
+import { useParams } from "react-router-dom";
+import { getManageNormListApi, getReviewNormListApi } from "@/api";
 import { debounce } from "lodash";
 import {
+  DEPT_STATUS_ENUM,
   IManageFormDetail,
   IManageIndexDetail,
   IManageNormItem,
@@ -12,6 +13,7 @@ import {
 import { SelfReportScoreModal } from "../SelfReportScoreModal";
 import { ColumnsType } from "antd/es/table";
 import { IndexDetailModal } from "../IndexDetailModal";
+import { IndexDetailFormModal } from "../IndexDetailModal/form";
 
 export interface IndexAssessmentProps {
   detail: IManageIndexDetail | IManageFormDetail | null;
@@ -24,28 +26,16 @@ const DEFAULT_PARAMS = {
   uploadStatus: undefined,
 };
 
-const CONFIRM_OPTIONS = [
-  {
-    label: "全部",
-    value: "",
-  },
-  {
-    label: "已完成",
-    value: YES_OR_NO.YES,
-  },
-  {
-    label: "未完成",
-    value: YES_OR_NO.NO,
-  },
-];
-
 export const IndexAssessment: FC<IndexAssessmentProps> = ({
   detail,
   isReportDetail,
   isEvalutionDetail,
 }) => {
-  const navigate = useNavigate();
-  const routeParams = useParams();
+  const routeParams = useParams(); // 待填报
+  const canUpload = [
+    DEPT_STATUS_ENUM.PENDING_SUBMIT,
+    DEPT_STATUS_ENUM.RETURN,
+  ].includes((detail as IManageFormDetail)?.deptStatus);
   const [loading, setLoading] = useState(false);
   const [params, setParams] = useState<any>({
     ...DEFAULT_PARAMS,
@@ -54,6 +44,7 @@ export const IndexAssessment: FC<IndexAssessmentProps> = ({
   const [checkedItem, setCheckedItem] = useState<null | IManageNormItem>(null);
   const [selfReportVisible, setSelfReportVisible] = useState(false);
   const [indexDetailVisible, setIndexDetailVisible] = useState(false);
+  const [indexDetailFormVisible, setIndexDetailFormVisible] = useState(false);
   const columns = useMemo(() => {
     const stack: ColumnsType<IManageNormItem> = [
       {
@@ -120,31 +111,16 @@ export const IndexAssessment: FC<IndexAssessmentProps> = ({
         dataIndex: "deptName",
       },
       {
-        title: "评定得分",
-        align: "center",
-        minWidth: 120,
-        render: (val) => val.score || <p className="empty-text">(空)</p>,
-      },
-      {
-        title: "评定人",
-        align: "center",
-        minWidth: 100,
-        render: (val) => <p className="empty-text">(空)</p>,
-      },
-      {
-        title: "评定意见",
-        align: "center",
-        minWidth: 120,
-        render: (val) => <p className="empty-text">(空)</p>,
-      },
-      {
         title: "操作",
         align: "center",
         hidden: !isEvalutionDetail,
-        render: () => (
+        render: (val) => (
           <Button
             type="link"
-            onClick={() => navigate("/management/evaluation/detail/submit")}
+            onClick={() => {
+              setCheckedItem(val);
+              setIndexDetailFormVisible(true);
+            }}
           >
             评定
           </Button>
@@ -164,7 +140,7 @@ export const IndexAssessment: FC<IndexAssessmentProps> = ({
           align: "center",
           minWidth: 120,
           render: (val) =>
-            !val.selfScore ? (
+            !val.selfScore && canUpload ? (
               <Button
                 type="text"
                 color="primary"
@@ -177,7 +153,7 @@ export const IndexAssessment: FC<IndexAssessmentProps> = ({
                 (空)
               </Button>
             ) : (
-              val.selfScore
+              val.selfScore || <p className="empty-text">(空)</p>
             ),
         },
         {
@@ -189,13 +165,52 @@ export const IndexAssessment: FC<IndexAssessmentProps> = ({
       );
     }
 
+    if (isEvalutionDetail) {
+      stack.splice(
+        stack.length - 2,
+        0,
+        {
+          title: "评定得分",
+          align: "center",
+          minWidth: 120,
+          render: (val) =>
+            val.opinionScore || <p className="empty-text">(空)</p>,
+        },
+        {
+          title: "评定人",
+          align: "center",
+          minWidth: 100,
+          render: (val) =>
+            val.opinionName || <p className="empty-text">(空)</p>,
+        },
+        {
+          title: "评定意见",
+          align: "center",
+          minWidth: 120,
+          render: (val) => (
+            <Button
+              type="link"
+              onClick={() => {
+                setCheckedItem(val);
+                setIndexDetailVisible(true);
+              }}
+            >
+              查看
+            </Button>
+          ),
+        }
+      );
+    }
+
     return stack;
   }, [detail]);
 
   const getList = async () => {
     try {
       setLoading(true);
-      const data = await getManageNormListApi({
+      const data = await (isEvalutionDetail
+        ? getReviewNormListApi
+        : getManageNormListApi)({
         deptId: routeParams.id as string,
         ...params,
       });
@@ -233,7 +248,16 @@ export const IndexAssessment: FC<IndexAssessmentProps> = ({
             <Form.Item noStyle name="uploadStatus">
               <Select
                 allowClear
-                options={CONFIRM_OPTIONS}
+                options={[
+                  {
+                    label: "未完成",
+                    value: 1,
+                  },
+                  {
+                    label: "已完成",
+                    value: 2,
+                  },
+                ]}
                 placeholder="请选择"
               />
             </Form.Item>
@@ -264,16 +288,49 @@ export const IndexAssessment: FC<IndexAssessmentProps> = ({
         />
       </div>
 
-      <IndexDetailModal
-        open={indexDetailVisible}
-        id={checkedItem?.normId || 0}
-        title={checkedItem?.name || ""}
-        onCancel={() => {
-          setCheckedItem(null);
-          setIndexDetailVisible(false);
-        }}
-      />
+      {detail && (
+        <>
+          <IndexDetailModal
+            readonly={!canUpload}
+            open={indexDetailVisible}
+            id={checkedItem?.normId || 0}
+            moduleId={detail.id}
+            restUploadParams={{
+              deptId: detail.id,
+              assessId: (detail as IManageFormDetail).accessId,
+            }}
+            title={checkedItem?.name || ""}
+            onCancel={() => {
+              setCheckedItem(null);
+              setIndexDetailVisible(false);
+            }}
+          />
+
+          <IndexDetailFormModal
+            open={indexDetailFormVisible}
+            id={checkedItem?.normId || 0}
+            deptNormId={checkedItem?.id || 0}
+            initScore={
+              checkedItem?.opinionScore
+                ? Number(checkedItem?.opinionScore)
+                : undefined
+            }
+            moduleId={detail.id}
+            title={checkedItem?.name || ""}
+            showScoreInput={checkedItem?.isPoint === YES_OR_NO.YES}
+            onOk={() => {
+              getList();
+              setIndexDetailFormVisible(false);
+            }}
+            onCancel={() => {
+              setCheckedItem(null);
+              setIndexDetailFormVisible(false);
+            }}
+          />
+        </>
+      )}
 
+      {/* 自评弹窗 */}
       <SelfReportScoreModal
         open={selfReportVisible}
         item={checkedItem}

+ 336 - 0
src/pages/AssessmentDetail/components/IndexDetailModal/form.tsx

@@ -0,0 +1,336 @@
+import { FC, Key, useEffect, useRef, useState } from "react";
+import {
+  Button,
+  Empty,
+  Input,
+  InputNumber,
+  Modal,
+  ModalProps,
+  Radio,
+  Tag,
+} from "antd";
+import { MaterialType, REVIEW_MATERIAL_TYPE, YES_OR_NO } from "@/types";
+import { getBaseURL } from "@dage/service";
+import { getFileListApi, normEvaOpinionApi } from "@/api";
+import { downloadFile } from "@/utils";
+import { DageLoading, DageTableActions } from "@dage/pc-components";
+import {
+  EditableProTable,
+  ProForm,
+  ProFormInstance,
+} from "@ant-design/pro-components";
+import style from "./index.module.scss";
+import { REVIEW_MATERIAL_STATUS_MAP } from "@/constants";
+
+export interface IndexDetailFormModalProps extends Omit<ModalProps, "onOk"> {
+  id: number;
+  deptNormId: number;
+  title: string;
+  moduleId: number;
+  /** 是否显示评定得分输入 */
+  showScoreInput?: boolean;
+  initScore?: number;
+  onCancel?: () => void;
+  onOk?: () => void;
+}
+
+const { TextArea } = Input;
+
+export const IndexDetailFormModal: FC<IndexDetailFormModalProps> = ({
+  open,
+  id,
+  deptNormId,
+  title,
+  moduleId,
+  showScoreInput,
+  initScore,
+  onOk,
+  onCancel,
+  ...rest
+}) => {
+  const baseUrl = getBaseURL();
+  const formRef = useRef<ProFormInstance<any>>();
+  const [loading, setLoading] = useState(false);
+  const [btnLoading, setBtnLoading] = useState(false);
+  const [list, setList] = useState<MaterialType[]>([]);
+  const [editableKeys, setEditableKeys] = useState<Key[]>([]);
+
+  const handleCancel = () => {
+    onCancel?.();
+  };
+
+  const getList = async () => {
+    try {
+      setLoading(true);
+      const data = await getFileListApi(moduleId, "norm", id);
+      setList(data.filter((i) => i.module === "norm"));
+
+      const temp: Record<number, MaterialType[]> = {};
+      data
+        .filter((i) => i.module === "fill-norm")
+        .forEach((item) => {
+          if (!item.parentId) return;
+          if (Array.isArray(temp[item.parentId]))
+            temp[item.parentId].push(item);
+          else temp[item.parentId] = [item];
+        });
+
+      const k: number[] = [];
+      const keys = Object.keys(temp).map((key) => Number(key));
+      keys.forEach((id) => {
+        formRef.current?.setFieldValue(id, temp[id]);
+        k.push(...temp[id].map((i) => i.id));
+      });
+      setEditableKeys(k);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleSubmit = async () => {
+    if (!(await formRef.current?.validateFields())) return;
+
+    const res = formRef.current?.getFieldsValue();
+    const keys = Object.keys(res).filter((key) => !isNaN(Number(key)));
+    const mats: any[] = [];
+
+    try {
+      setBtnLoading(true);
+      keys.forEach((id) => {
+        const list = res[id];
+        list?.forEach((item: any) => {
+          mats.push({
+            materialId: item.id,
+            condition: item.status,
+            opinion: item.opinion,
+          });
+        });
+      });
+      await normEvaOpinionApi({
+        opinionScore: res.score,
+        mats,
+        deptNormId,
+        deptId: moduleId,
+      });
+      onOk?.();
+    } finally {
+      setBtnLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    if (open) {
+      getList();
+      formRef.current?.setFieldValue("score", initScore);
+    }
+  }, [open]);
+
+  return (
+    <Modal
+      className={style.modal}
+      title={title}
+      open={open}
+      width={1000}
+      footer={!list.length ? null : undefined}
+      maskClosable={false}
+      okText="提交"
+      okButtonProps={{
+        disabled: btnLoading,
+      }}
+      onOk={handleSubmit}
+      onCancel={handleCancel}
+      {...rest}
+    >
+      {loading && <DageLoading />}
+
+      {!list.length && <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+
+      <ProForm
+        formRef={formRef}
+        layout="horizontal"
+        submitter={false}
+        style={{ marginTop: 30 }}
+      >
+        {showScoreInput && Boolean(list.length) && (
+          <ProForm.Item
+            name="score"
+            label="评定得分"
+            required
+            rules={[{ required: true, message: "此项为必填项" }]}
+          >
+            <InputNumber
+              min={0}
+              max={50}
+              precision={1}
+              className="w450"
+              placeholder="请输入0~50的数字,支持小数点后一位"
+            />
+          </ProForm.Item>
+        )}
+
+        {list.map((item) => {
+          const isUpload = item.isUpload === YES_OR_NO.YES;
+
+          return (
+            <div key={item.id} className={style.fileUpload}>
+              <div className={style.fileUploadHeader}>
+                <div className={style.fileUploadHeaderInfo}>
+                  <Tag color={isUpload ? "red" : ""}>
+                    {isUpload ? "必填" : "选填"}
+                  </Tag>
+                  <h3>
+                    {item.name} | {item.suffix || "*"}
+                  </h3>
+                </div>
+                <Button
+                  type="primary"
+                  ghost
+                  onClick={() =>
+                    downloadFile(
+                      baseUrl +
+                        process.env.REACT_APP_IMG_PUBLIC +
+                        item.filePath,
+                      item.fileName
+                    )
+                  }
+                >
+                  下载模板
+                </Button>
+              </div>
+
+              <EditableProTable
+                rowKey="id"
+                name={item.id}
+                className="cus-table"
+                columns={[
+                  {
+                    title: "附件名称",
+                    dataIndex: "fileName",
+                    align: "center",
+                    editable: false,
+                  },
+                  {
+                    title: "责任部门",
+                    dataIndex: "deptName",
+                    align: "center",
+                    editable: false,
+                  },
+                  {
+                    title: "上传用户",
+                    dataIndex: "creatorName",
+                    align: "center",
+                    editable: false,
+                  },
+                  {
+                    title: "上传时间",
+                    dataIndex: "updateTime",
+                    align: "center",
+                    editable: false,
+                  },
+                  {
+                    title: "评定结果",
+                    align: "center",
+                    dataIndex: "status",
+                    formItemProps: () => {
+                      return {
+                        rules: [
+                          {
+                            validator(rule, value) {
+                              if (value === 0) {
+                                return Promise.reject("此项为必选项");
+                              } else {
+                                return Promise.resolve();
+                              }
+                            },
+                          },
+                        ],
+                      };
+                    },
+                    renderFormItem: () => (
+                      <Radio.Group
+                        options={[
+                          {
+                            value: REVIEW_MATERIAL_TYPE.PASS,
+                            label:
+                              REVIEW_MATERIAL_STATUS_MAP[
+                                REVIEW_MATERIAL_TYPE.PASS
+                              ],
+                          },
+                          {
+                            value: REVIEW_MATERIAL_TYPE.FAIL,
+                            label:
+                              REVIEW_MATERIAL_STATUS_MAP[
+                                REVIEW_MATERIAL_TYPE.FAIL
+                              ],
+                          },
+                        ]}
+                      />
+                    ),
+                  },
+                  {
+                    title: "评定意见",
+                    width: 300,
+                    dataIndex: "opinion",
+                    align: "center",
+                    formItemProps: () => {
+                      return {
+                        rules: [{ required: true, message: "此项为必填项" }],
+                      };
+                    },
+                    renderFormItem: () => (
+                      <TextArea
+                        showCount
+                        rows={6}
+                        maxLength={500}
+                        style={{ marginBottom: 20 }}
+                        placeholder="请输入内容,最多500字"
+                      />
+                    ),
+                  },
+                  {
+                    title: "操作",
+                    align: "center",
+                    editable: false,
+                    render: (val) => (
+                      <DageTableActions
+                        renderBefore={
+                          <Button
+                            size="small"
+                            type="link"
+                            onClick={() =>
+                              downloadFile(
+                                baseUrl +
+                                  process.env.REACT_APP_IMG_PUBLIC +
+                                  item.filePath,
+                                item.fileName
+                              )
+                            }
+                          >
+                            下载
+                          </Button>
+                        }
+                        showEdit={false}
+                        showDelete={false}
+                      />
+                    ),
+                  },
+                ]}
+                recordCreatorProps={{
+                  style: {
+                    display: "none",
+                  },
+                  // @ts-ignore
+                  record: () => ({}),
+                }}
+                editable={{
+                  type: "multiple",
+                  editableKeys,
+                }}
+              />
+            </div>
+          );
+        })}
+      </ProForm>
+    </Modal>
+  );
+};

+ 4 - 0
src/pages/AssessmentDetail/components/IndexDetailModal/index.module.scss

@@ -8,6 +8,10 @@
   flex: 1;
   display: flex;
   align-items: center;
+
+  h3 {
+    word-break: break-all;
+  }
 }
 div.uploadBtn {
   flex: unset;

+ 57 - 24
src/pages/AssessmentDetail/components/IndexDetailModal/index.tsx

@@ -1,6 +1,6 @@
 import { FC, useEffect, useState } from "react";
 import { Button, Empty, Modal, ModalProps, Table, Tag } from "antd";
-import { MaterialType, YES_OR_NO } from "@/types";
+import { MaterialType, REVIEW_MATERIAL_TYPE, YES_OR_NO } from "@/types";
 import { getBaseURL } from "@dage/service";
 import { deleteFileApi, getFileListApi, saveManageFileApi } from "@/api";
 import { downloadFile, beforeUpload } from "@/utils";
@@ -15,10 +15,15 @@ import {
 } from "@dage/pc-components";
 import classNames from "classnames";
 import style from "./index.module.scss";
+import { REVIEW_MATERIAL_STATUS_MAP } from "@/constants";
 
 export interface IndexDetailModalProps extends Omit<ModalProps, "onOk"> {
   id: number;
+  /** 指标所属模块的id */
+  moduleId: number;
   title: string;
+  readonly?: boolean;
+  restUploadParams?: Record<string, unknown>;
   onCancel?: () => void;
   onOk?: (val: any) => void;
 }
@@ -26,7 +31,10 @@ export interface IndexDetailModalProps extends Omit<ModalProps, "onOk"> {
 export const IndexDetailModal: FC<IndexDetailModalProps> = ({
   open,
   id,
+  moduleId,
   title,
+  readonly,
+  restUploadParams,
   onOk,
   onCancel,
   ...rest
@@ -64,6 +72,7 @@ export const IndexDetailModal: FC<IndexDetailModalProps> = ({
         parentId: item.id,
         module: "fill-norm",
         moduleId: id,
+        ...restUploadParams,
       });
       // @ts-ignore
       file.uploaded = true;
@@ -75,7 +84,7 @@ export const IndexDetailModal: FC<IndexDetailModalProps> = ({
   const getList = async () => {
     try {
       setLoading(true);
-      const data = await getFileListApi(id, "norm");
+      const data = await getFileListApi(moduleId, "norm", id);
       setList(data.filter((i) => i.module === "norm"));
 
       const temp: typeof uploadedFileMap = {};
@@ -142,26 +151,28 @@ export const IndexDetailModal: FC<IndexDetailModalProps> = ({
               >
                 下载模板
               </Button>
-              <DageUploadProvider>
-                <DageUploadConsumer>
-                  {(res) => (
-                    <DageUpload
-                      className={classNames(style.uploadBtn, "no-list")}
-                      dType={DageUploadType.DOC}
-                      action="/api/cms/fill/file/upload"
-                      // @ts-ignore
-                      accept={item.suffix}
-                      // @ts-ignore
-                      beforeUpload={beforeUpload.bind(undefined, item.suffix)}
-                      onChange={saveManageFile.bind(undefined, item)}
-                    >
-                      <Button type="primary" ghost loading={res?.uploading}>
-                        上传附件
-                      </Button>
-                    </DageUpload>
-                  )}
-                </DageUploadConsumer>
-              </DageUploadProvider>
+              {!readonly && (
+                <DageUploadProvider>
+                  <DageUploadConsumer>
+                    {(res) => (
+                      <DageUpload
+                        className={classNames(style.uploadBtn, "no-list")}
+                        dType={DageUploadType.DOC}
+                        action="/api/cms/fill/file/upload"
+                        // @ts-ignore
+                        accept={item.suffix}
+                        // @ts-ignore
+                        beforeUpload={beforeUpload.bind(undefined, item.suffix)}
+                        onChange={saveManageFile.bind(undefined, item)}
+                      >
+                        <Button type="primary" ghost loading={res?.uploading}>
+                          上传附件
+                        </Button>
+                      </DageUpload>
+                    )}
+                  </DageUploadConsumer>
+                </DageUploadProvider>
+              )}
             </div>
 
             <Table
@@ -171,37 +182,58 @@ export const IndexDetailModal: FC<IndexDetailModalProps> = ({
               dataSource={uploadedFileMap[item.id]}
               columns={[
                 {
+                  width: 200,
                   title: "附件名称",
                   dataIndex: "fileName",
                   align: "center",
                 },
                 {
                   title: "责任部门",
+                  width: 120,
                   dataIndex: "deptName",
                   align: "center",
                 },
                 {
+                  width: 120,
                   title: "上传用户",
                   dataIndex: "creatorName",
                   align: "center",
                 },
                 {
+                  width: 120,
                   title: "上传时间",
                   dataIndex: "updateTime",
                   align: "center",
                 },
                 {
                   title: "评定结果",
-                  dataIndex: "creatorName",
+                  width: 120,
                   align: "center",
+                  render: (val: MaterialType) => {
+                    return (
+                      <Tag
+                        color={
+                          val.status === REVIEW_MATERIAL_TYPE.PENDING
+                            ? "default"
+                            : val.status === REVIEW_MATERIAL_TYPE.PASS
+                            ? "success"
+                            : "error"
+                        }
+                      >
+                        {REVIEW_MATERIAL_STATUS_MAP[val.status]}
+                      </Tag>
+                    );
+                  },
                 },
                 {
+                  width: 120,
                   title: "评定意见",
-                  dataIndex: "updateTime",
+                  dataIndex: "opinion",
                   align: "center",
                 },
                 {
                   title: "操作",
+                  width: 140,
                   align: "center",
                   render: (val) => (
                     <DageTableActions
@@ -222,6 +254,7 @@ export const IndexDetailModal: FC<IndexDetailModalProps> = ({
                         </Button>
                       }
                       showEdit={false}
+                      showDelete={!readonly}
                       onDelete={handleDeleteFile.bind(undefined, val.id)}
                     />
                   ),

+ 352 - 181
src/pages/AssessmentDetail/components/OverallAssessment/index.tsx

@@ -1,5 +1,5 @@
-import { Button, Form, Input, Table, Tag } from "antd";
-import { FC, useEffect, useMemo, useState } from "react";
+import { Button, Form, Radio, Table, Tag } from "antd";
+import { FC, useEffect, useMemo, useRef, useState } from "react";
 import classNames from "classnames";
 import {
   DageFileResponseType,
@@ -12,37 +12,68 @@ import {
 import { SubEvaluationModal } from "../SubEvaluationModal";
 import style from "./index.module.scss";
 import {
+  DEPT_STATUS_ENUM,
   IManageFormDetail,
   IManageIndexDetail,
   MaterialType,
-  PUBLISH_ENUM,
+  REVIEW_MATERIAL_TYPE,
   YES_OR_NO,
 } from "@/types";
 import { downloadFile, beforeUpload } from "@/utils";
 import { getBaseURL } from "@dage/service";
-import { deleteFileApi, getFileListApi, saveManageFileApi } from "@/api";
+import {
+  additionalEvaOpinionApi,
+  assessmentEvaOpinionApi,
+  changeMaterialConditionApi,
+  deleteFileApi,
+  getFileListApi,
+  materialEvaOpinionApi,
+  saveManageFileApi,
+} from "@/api";
+import { EvaluationFormModal } from "../EvaluationFormModal";
+import { ColumnsType } from "antd/es/table";
+import { useSelector } from "react-redux";
+import { RootState } from "@/store";
+import { REVIEW_MATERIAL_STATUS_MAP } from "@/constants";
 
 export interface OverallAssessmentProps {
   detail: IManageIndexDetail | IManageFormDetail | null;
   isReportDetail?: boolean;
   isEvalutionDetail?: boolean;
+  isIndexDetail?: boolean;
+  refreshDetail(): void;
 }
 
-const { TextArea } = Input;
-
 export const OverallAssessment: FC<OverallAssessmentProps> = ({
   detail,
+  isIndexDetail,
   isReportDetail,
   isEvalutionDetail,
+  refreshDetail,
 }) => {
+  const { userInfo } = useSelector<RootState, RootState["base"]>(
+    (state) => state.base
+  );
   const baseUrl = getBaseURL();
-  // 待填报
-  const toBeFilled =
-    (detail as IManageFormDetail)?.publishStatus === PUBLISH_ENUM.PUBLISHED;
-  // 已评定
-  const evaluated =
-    (detail as IManageFormDetail)?.publishStatus === PUBLISH_ENUM.EVALUATED;
-  const [modalVisible, setModelVisible] = useState(false);
+  // 是否能够上传附件
+  const canUpload = [
+    DEPT_STATUS_ENUM.PENDING_SUBMIT,
+    DEPT_STATUS_ENUM.RETURN,
+  ].includes((detail as IManageFormDetail)?.deptStatus);
+  // 是否退回状态
+  const isRefund =
+    (detail as IManageFormDetail)?.deptStatus === DEPT_STATUS_ENUM.RETURN;
+  // 当前评定的类型
+  const curEvaluationData = useRef<{
+    type: "material" | "assessment";
+    id: number;
+  } | null>(null);
+  const [initEvaluationContent, setInitEvaluationContent] = useState("");
+  const [subEvaluationVisible, setSubEvaluationVisible] = useState(false);
+  const [evaluationVisible, setEvaluationVisible] = useState(false);
+  // 选中的附加项
+  const checkedAddItem = useRef<any>(null);
+  const checkedAddIndex = useRef(0);
   // 加分项
   const addList = useMemo(() => {
     if (!detail || !detail.jsonAdd) return [];
@@ -58,13 +89,169 @@ export const OverallAssessment: FC<OverallAssessmentProps> = ({
   const [uploadedFileMap, setUploadedFileMap] = useState<
     Record<number, MaterialType[]>
   >({});
+  const fileColumns = useMemo(() => {
+    const stack: ColumnsType<MaterialType> = [
+      {
+        title: "附件名称",
+        dataIndex: "fileName",
+        align: "center",
+      },
+      {
+        title: "责任部门",
+        dataIndex: "deptName",
+        align: "center",
+      },
+      {
+        title: "上传用户",
+        dataIndex: "creatorName",
+        align: "center",
+      },
+      {
+        title: "操作",
+        align: "center",
+        render: (val) => (
+          <DageTableActions
+            renderBefore={
+              <Button
+                size="small"
+                type="link"
+                onClick={() => {
+                  downloadFile(
+                    baseUrl + process.env.REACT_APP_IMG_PUBLIC + val.filePath,
+                    val.fileName
+                  );
+                }}
+              >
+                下载
+              </Button>
+            }
+            showDelete={isReportDetail && canUpload}
+            showEdit={false}
+            onDelete={handleDeleteFile.bind(undefined, val.id)}
+          />
+        ),
+      },
+    ];
+
+    if (isEvalutionDetail || (isReportDetail && isRefund)) {
+      stack.splice(
+        2,
+        0,
+        {
+          title: "评定结果",
+          align: "center",
+          render: (item: MaterialType) => {
+            if (
+              item.status === REVIEW_MATERIAL_TYPE.PENDING &&
+              !isEvalutionDetail
+            )
+              return "";
+
+            return isRefund ? (
+              <Tag
+                color={
+                  item.status === REVIEW_MATERIAL_TYPE.PASS
+                    ? "success"
+                    : "error"
+                }
+              >
+                {REVIEW_MATERIAL_STATUS_MAP[item.status]}
+              </Tag>
+            ) : (
+              <Radio.Group
+                defaultValue={item.status}
+                options={[
+                  {
+                    label: "合格",
+                    value: REVIEW_MATERIAL_TYPE.PASS,
+                  },
+                  {
+                    label: "不合格",
+                    value: REVIEW_MATERIAL_TYPE.FAIL,
+                  },
+                ]}
+                onChange={(v) => {
+                  changeMaterialConditionApi({
+                    condition: v.target.value,
+                    deptId: detail!.id,
+                    materialId: item.id,
+                  });
+                }}
+              />
+            );
+          },
+        },
+        {
+          title: "评定意见",
+          align: "center",
+          render: (val) =>
+            isRefund ? (
+              val.opinion
+            ) : (
+              <Button
+                type="link"
+                onClick={() => {
+                  curEvaluationData.current = {
+                    type: "material",
+                    id: val.id,
+                  };
+                  setInitEvaluationContent(val.opinion);
+                  setEvaluationVisible(true);
+                }}
+              >
+                填写
+              </Button>
+            ),
+        }
+      );
+    } else {
+      stack.splice(2, 0, {
+        title: "编辑时间",
+        dataIndex: "updateTime",
+        align: "center",
+      });
+    }
+
+    return stack;
+  }, [detail]);
+  const additionalColumns: ColumnsType<any> = [
+    {
+      title: "评定得分",
+      dataIndex: "score",
+      align: "center",
+    },
+    {
+      title: "评定人",
+      dataIndex: "assessor",
+      align: "center",
+    },
+    {
+      title: "评定意见",
+      dataIndex: "comment",
+      align: "center",
+    },
+    {
+      title: "操作",
+      align: "center",
+      hidden: !isEvalutionDetail,
+      render: (item, record, index) => (
+        <Button
+          type="link"
+          onClick={openSubEvaluationModal.bind(undefined, index, item)}
+        >
+          评定
+        </Button>
+      ),
+    },
+  ];
 
   const getList = async () => {
     try {
       setLoading(true);
       const data = await getFileListApi(
-        (detail as IManageFormDetail).accessId,
-        "access"
+        isIndexDetail ? detail!.id : (detail as IManageFormDetail).accessId,
+        "assess",
+        detail!.id
       );
       const temp: typeof uploadedFileMap = {};
       data.forEach((item) => {
@@ -99,6 +286,8 @@ export const OverallAssessment: FC<OverallAssessmentProps> = ({
         parentId: item.id,
         module: "fill-assess",
         moduleId: (detail as IManageFormDetail).accessId,
+        deptId: detail?.id,
+        assessId: (detail as IManageFormDetail).accessId,
       });
       // @ts-ignore
       file.uploaded = true;
@@ -112,6 +301,46 @@ export const OverallAssessment: FC<OverallAssessmentProps> = ({
     getList();
   };
 
+  const handleSubmitEvaluation = async (val: any) => {
+    if (!curEvaluationData.current) return;
+
+    if (curEvaluationData.current.type === "assessment") {
+      await assessmentEvaOpinionApi({
+        deptId: curEvaluationData.current.id,
+        ...val,
+      });
+    } else {
+      await materialEvaOpinionApi({
+        deptId: detail!.id,
+        materialId: curEvaluationData.current.id,
+        ...val,
+      });
+    }
+    refreshDetail();
+  };
+
+  const openSubEvaluationModal = (index: number, item: any) => {
+    checkedAddIndex.current = index;
+    checkedAddItem.current = item;
+    setSubEvaluationVisible(true);
+  };
+
+  const handleAdditionalEvalution = async (vals: any) => {
+    const temp = [...addList];
+    temp.splice(checkedAddIndex.current, 1, {
+      ...checkedAddItem.current,
+      ...vals,
+      assessor: userInfo?.user.realName,
+    });
+    await additionalEvaOpinionApi({
+      assessId: (detail as IManageFormDetail).accessId,
+      deptId: detail!.id,
+      opinionJson: JSON.stringify(temp),
+      type: checkedAddItem.current.id.indexOf("bonus") > -1 ? "add" : "sub",
+    });
+    refreshDetail();
+  };
+
   useEffect(() => {
     if (!detail) return;
     getList();
@@ -136,7 +365,8 @@ export const OverallAssessment: FC<OverallAssessmentProps> = ({
             <Table
               pagination={false}
               className="cus-table"
-              rowKey="id"
+              dataSource={detail?.plan}
+              rowKey="deptId"
               columns={[
                 {
                   title: "部门名称",
@@ -144,9 +374,20 @@ export const OverallAssessment: FC<OverallAssessmentProps> = ({
                   align: "center",
                 },
                 {
+                  title: "分配指标数量",
+                  dataIndex: "pcsNorm",
+                  align: "center",
+                },
+                {
+                  title: "完成填报数量",
+                  dataIndex: "pcsFill",
+                  align: "center",
+                },
+                {
                   title: "部门考核状态",
-                  dataIndex: "type",
                   align: "center",
+                  // @ts-ignore
+                  render: (val) => REVIEW_MATERIAL_STATUS_MAP[val.status],
                 },
               ]}
             />
@@ -154,11 +395,19 @@ export const OverallAssessment: FC<OverallAssessmentProps> = ({
 
           <Form.Item label="评定意见">
             {isEvalutionDetail ? (
-              <TextArea
-                rows={6}
-                placeholder="请输入文字,不超过1000字"
-                maxLength={1000}
-              />
+              <Button
+                type="primary"
+                onClick={() => {
+                  curEvaluationData.current = {
+                    type: "assessment",
+                    id: detail!.id,
+                  };
+                  setInitEvaluationContent(detail!.opinion);
+                  setEvaluationVisible(true);
+                }}
+              >
+                填写
+              </Button>
             ) : (
               <p>
                 1、
@@ -176,120 +425,78 @@ export const OverallAssessment: FC<OverallAssessmentProps> = ({
       )}
 
       <Form.Item label="资料上传">
-        {(detail?.materials || []).map((item) => {
-          const isUpload = item.isUpload === YES_OR_NO.YES;
+        {(detail?.materials?.filter((i) => i.module === "assess") || []).map(
+          (item) => {
+            const isUpload = item.isUpload === YES_OR_NO.YES;
 
-          return (
-            <div key={item.id} className={style.fileUpload}>
-              <div className={style.fileUploadHeader}>
-                <div>
-                  <Tag color={isUpload ? "red" : ""}>
-                    {isUpload ? "必填" : "选填"}
-                  </Tag>
-                  <h3>
-                    {item.name} | {item.suffix || "*"}
-                  </h3>
+            return (
+              <div key={item.id} className={style.fileUpload}>
+                <div className={style.fileUploadHeader}>
+                  <div>
+                    <Tag color={isUpload ? "red" : ""}>
+                      {isUpload ? "必填" : "选填"}
+                    </Tag>
+                    <h3>
+                      {item.name} | {item.suffix || "*"}
+                    </h3>
+                  </div>
+                  <Button
+                    type="primary"
+                    ghost
+                    onClick={() =>
+                      downloadFile(
+                        baseUrl +
+                          process.env.REACT_APP_IMG_PUBLIC +
+                          item.filePath,
+                        item.fileName
+                      )
+                    }
+                  >
+                    下载模板
+                  </Button>
+                  {canUpload && !isEvalutionDetail && (
+                    <DageUploadProvider>
+                      <DageUploadConsumer>
+                        {(res) => (
+                          <DageUpload
+                            className={classNames(style.uploadBtn, "no-list")}
+                            dType={DageUploadType.DOC}
+                            action="/api/cms/fill/file/upload"
+                            // @ts-ignore
+                            accept={item.suffix}
+                            // @ts-ignore
+                            beforeUpload={beforeUpload.bind(
+                              undefined,
+                              item.suffix
+                            )}
+                            onChange={saveManageFile.bind(undefined, item)}
+                          >
+                            <Button
+                              type="primary"
+                              ghost
+                              loading={res?.uploading}
+                            >
+                              上传附件
+                            </Button>
+                          </DageUpload>
+                        )}
+                      </DageUploadConsumer>
+                    </DageUploadProvider>
+                  )}
                 </div>
-                <Button
-                  type="primary"
-                  ghost
-                  onClick={() =>
-                    downloadFile(
-                      baseUrl +
-                        process.env.REACT_APP_IMG_PUBLIC +
-                        item.filePath,
-                      item.fileName
-                    )
-                  }
-                >
-                  下载模板
-                </Button>
-                {toBeFilled && !isEvalutionDetail && (
-                  <DageUploadProvider>
-                    <DageUploadConsumer>
-                      {(res) => (
-                        <DageUpload
-                          className={classNames(style.uploadBtn, "no-list")}
-                          dType={DageUploadType.DOC}
-                          action="/api/cms/fill/file/upload"
-                          // @ts-ignore
-                          accept={item.suffix}
-                          // @ts-ignore
-                          beforeUpload={beforeUpload.bind(
-                            undefined,
-                            item.suffix
-                          )}
-                          onChange={saveManageFile.bind(undefined, item)}
-                        >
-                          <Button type="primary" ghost loading={res?.uploading}>
-                            上传附件
-                          </Button>
-                        </DageUpload>
-                      )}
-                    </DageUploadConsumer>
-                  </DageUploadProvider>
-                )}
-              </div>
 
-              <Table
-                rowKey="id"
-                loading={loading}
-                className="cus-table"
-                pagination={false}
-                dataSource={uploadedFileMap[item.id]}
-                columns={[
-                  {
-                    title: "附件名称",
-                    dataIndex: "fileName",
-                    align: "center",
-                  },
-                  {
-                    title: "责任部门",
-                    dataIndex: "deptName",
-                    align: "center",
-                  },
-                  {
-                    title: "上传用户",
-                    dataIndex: "creatorName",
-                    align: "center",
-                  },
-                  {
-                    title: "编辑时间",
-                    dataIndex: "updateTime",
-                    align: "center",
-                  },
-                  {
-                    title: "操作",
-                    align: "center",
-                    render: (val) => (
-                      <DageTableActions
-                        renderBefore={
-                          <Button
-                            size="small"
-                            type="link"
-                            onClick={() => {
-                              downloadFile(
-                                baseUrl +
-                                  process.env.REACT_APP_IMG_PUBLIC +
-                                  val.filePath,
-                                val.fileName
-                              );
-                            }}
-                          >
-                            下载
-                          </Button>
-                        }
-                        showDelete={isReportDetail}
-                        showEdit={false}
-                        onDelete={handleDeleteFile.bind(undefined, val.id)}
-                      />
-                    ),
-                  },
-                ]}
-              />
-            </div>
-          );
-        })}
+                <Table
+                  rowKey="id"
+                  loading={loading}
+                  className="cus-table"
+                  pagination={false}
+                  dataSource={uploadedFileMap[item.id]}
+                  columns={fileColumns}
+                />
+              </div>
+            );
+          }
+        )}
       </Form.Item>
 
       {!isReportDetail && (
@@ -310,27 +517,7 @@ export const OverallAssessment: FC<OverallAssessmentProps> = ({
                 dataIndex: "num",
                 align: "center",
               },
-              {
-                title: "评定得分",
-                dataIndex: "user",
-                align: "center",
-              },
-              {
-                title: "评定人",
-                dataIndex: "create_time",
-                align: "center",
-              },
-              {
-                title: "评定意见",
-                dataIndex: "create_time",
-                align: "center",
-              },
-              {
-                title: "操作",
-                align: "center",
-                hidden: !isEvalutionDetail,
-                render: () => <Button type="link">评定</Button>,
-              },
+              ...additionalColumns,
             ]}
           />
 
@@ -351,39 +538,23 @@ export const OverallAssessment: FC<OverallAssessmentProps> = ({
                 dataIndex: "num",
                 align: "center",
               },
-              {
-                title: "评定得分",
-                dataIndex: "user",
-                align: "center",
-              },
-              {
-                title: "评定人",
-                dataIndex: "create_time",
-                align: "center",
-              },
-              {
-                title: "评定意见",
-                dataIndex: "create_time",
-                align: "center",
-              },
-              {
-                title: "操作",
-                align: "center",
-                hidden: !isEvalutionDetail,
-                render: () => (
-                  <Button type="link" onClick={() => setModelVisible(true)}>
-                    评定
-                  </Button>
-                ),
-              },
+              ...additionalColumns,
             ]}
           />
         </Form.Item>
       )}
 
       <SubEvaluationModal
-        open={modalVisible}
-        onCancel={() => setModelVisible(false)}
+        open={subEvaluationVisible}
+        onOk={handleAdditionalEvalution}
+        onCancel={() => setSubEvaluationVisible(false)}
+      />
+
+      <EvaluationFormModal
+        initContent={initEvaluationContent}
+        open={evaluationVisible}
+        onOk={handleSubmitEvaluation}
+        onCancel={() => setEvaluationVisible(false)}
       />
     </Form>
   );

+ 16 - 7
src/pages/AssessmentDetail/components/SubEvaluationModal/index.tsx

@@ -1,4 +1,4 @@
-import { FC } from "react";
+import { FC, useState } from "react";
 import { Form, Input, InputNumber, Modal, ModalProps } from "antd";
 import style from "@/components/AddIndexModal/index.module.scss";
 
@@ -16,6 +16,7 @@ export const SubEvaluationModal: FC<SubEvaluationModalProps> = ({
   ...rest
 }) => {
   const [form] = Form.useForm<any>();
+  const [loading, setLoading] = useState(false);
 
   const handleCancel = () => {
     form.resetFields();
@@ -27,8 +28,13 @@ export const SubEvaluationModal: FC<SubEvaluationModalProps> = ({
   };
 
   const handleSubmit = async (values: any) => {
-    onOk?.(values);
-    handleCancel();
+    try {
+      setLoading(true);
+      await onOk?.(values);
+      handleCancel();
+    } finally {
+      setLoading(false);
+    }
   };
 
   return (
@@ -40,8 +46,10 @@ export const SubEvaluationModal: FC<SubEvaluationModalProps> = ({
       open={open}
       width={640}
       okButtonProps={{
-        disabled: false,
+        disabled: loading,
       }}
+      forceRender
+      maskClosable={false}
       onOk={handleConfirm}
       onCancel={handleCancel}
       {...rest}
@@ -53,8 +61,8 @@ export const SubEvaluationModal: FC<SubEvaluationModalProps> = ({
       >
         <Form.Item
           label="评定得分"
-          name="checkedKeys"
           required
+          name="score"
           rules={[{ required: true, message: "请输入 " }]}
         >
           <InputNumber
@@ -65,10 +73,11 @@ export const SubEvaluationModal: FC<SubEvaluationModalProps> = ({
             placeholder="请输入0~50的数字,支持小数点后一位"
           />
         </Form.Item>
-        <Form.Item label="评定意见">
+        <Form.Item label="评定意见" name="comment">
           <TextArea
-            rows={4}
+            rows={6}
             maxLength={200}
+            showCount
             placeholder="请输入内容,不超过200字"
           />
         </Form.Item>

+ 95 - 7
src/pages/AssessmentDetail/index.tsx

@@ -8,9 +8,13 @@ import { useHashQuery } from "@/hooks";
 import { DEPT_STATUS_MAP, PUBLISH_STATUS_MAP } from "@/constants";
 import { useNavigate, useParams } from "react-router-dom";
 import {
+  examineAssessmentApi,
   getManageEvaluationDetailApi,
   getManageFormDetailApi,
   getManageIndexDetailApi,
+  refundReviewApi,
+  saveReviewApi,
+  submitAssessmentApi,
 } from "@/api";
 import {
   DEPT_STATUS_ENUM,
@@ -20,8 +24,10 @@ import {
 } from "@/types";
 import { DageLoading } from "@dage/pc-components";
 import { isNumber } from "lodash";
+import useApp from "antd/es/app/useApp";
 
 const AssessmentDetailPage: FC = () => {
+  const app = useApp();
   // 判断是否为考核填报进入
   const isReportDetail = useRef(window.location.hash.indexOf("form") > -1);
   // 判断是否为考核评定进入
@@ -32,6 +38,10 @@ const AssessmentDetailPage: FC = () => {
   const isIndexDetail = useRef(window.location.hash.indexOf("index") > -1);
   const [tab, setTab] = useHashQuery("tab", "1");
   const [loading, setLoading] = useState(false);
+  const [submitLoading, setSubmitLoading] = useState(false);
+  const [statusLoading, setStatusLoading] = useState(false);
+  const [reviewSubmitLoading, setReviewSubmitLoading] = useState(false);
+  const [reviewRefundLoading, setReviewRefundLoading] = useState(false);
   const [detail, setDetail] = useState<
     IManageIndexDetail | IManageFormDetail | null
   >(null);
@@ -68,6 +78,50 @@ const AssessmentDetailPage: FC = () => {
     }
   };
 
+  const handleSubmit = async () => {
+    try {
+      setSubmitLoading(true);
+      await submitAssessmentApi(params.id);
+      app.message.success("操作成功");
+      navigate(-1);
+    } finally {
+      setSubmitLoading(false);
+    }
+  };
+
+  const handleStatus = async (status: DEPT_STATUS_ENUM) => {
+    try {
+      setStatusLoading(true);
+      await examineAssessmentApi(params.id as string, status);
+      app.message.success("操作成功");
+      navigate(-1);
+    } finally {
+      setStatusLoading(false);
+    }
+  };
+
+  const handleReviewSubmit = async () => {
+    try {
+      setReviewSubmitLoading(true);
+      await saveReviewApi((detail as IManageFormDetail).accessId);
+      app.message.success("操作成功");
+      navigate(-1);
+    } finally {
+      setReviewSubmitLoading(false);
+    }
+  };
+
+  const handleReviewRefund = async () => {
+    try {
+      setReviewRefundLoading(true);
+      await refundReviewApi((detail as IManageFormDetail).accessId, detail!.id);
+      app.message.success("操作成功");
+      navigate(-1);
+    } finally {
+      setReviewRefundLoading(false);
+    }
+  };
+
   useEffect(() => {
     getDetail();
   }, []);
@@ -151,8 +205,10 @@ const AssessmentDetailPage: FC = () => {
               children: (
                 <OverallAssessment
                   detail={detail}
+                  isIndexDetail={isIndexDetail.current}
                   isReportDetail={isReportDetail.current}
                   isEvalutionDetail={isEvalutionDetail.current}
+                  refreshDetail={getDetail}
                 />
               ),
             },
@@ -179,7 +235,12 @@ const AssessmentDetailPage: FC = () => {
           DEPT_STATUS_ENUM.EXAMINE_REJECT,
         ].includes((detail as IManageFormDetail)?.deptStatus) && (
           <FormPageFooter>
-            <Button size="large" type="primary">
+            <Button
+              size="large"
+              type="primary"
+              loading={submitLoading}
+              onClick={handleSubmit}
+            >
               提交
             </Button>
             <Button size="large" onClick={() => navigate(-1)}>
@@ -193,10 +254,26 @@ const AssessmentDetailPage: FC = () => {
         (detail as IManageFormDetail)?.deptStatus ===
           DEPT_STATUS_ENUM.PENDING_EXAMINE && (
           <FormPageFooter>
-            <Button size="large" type="primary">
+            <Button
+              disabled={statusLoading}
+              size="large"
+              type="primary"
+              onClick={handleStatus.bind(
+                undefined,
+                DEPT_STATUS_ENUM.EXAMINE_SUCCESS
+              )}
+            >
               审核通过
             </Button>
-            <Button size="large" color="danger">
+            <Button
+              disabled={statusLoading}
+              size="large"
+              color="danger"
+              onClick={handleStatus.bind(
+                undefined,
+                DEPT_STATUS_ENUM.EXAMINE_REJECT
+              )}
+            >
               审核驳回
             </Button>
             <Button size="large" onClick={() => navigate(-1)}>
@@ -207,13 +284,24 @@ const AssessmentDetailPage: FC = () => {
 
       {isEvalutionDetail.current && (
         <FormPageFooter>
-          <Button size="large" type="primary">
+          <Button
+            size="large"
+            type="primary"
+            loading={reviewSubmitLoading}
+            onClick={handleReviewSubmit}
+          >
             完成评定
           </Button>
-          <Button size="large" type="primary" ghost>
+          {/* <Button size="large" type="primary" ghost>
             保存
-          </Button>
-          <Button size="large" danger style={{ width: 160 }}>
+          </Button> */}
+          <Button
+            size="large"
+            danger
+            style={{ width: 160 }}
+            loading={reviewRefundLoading}
+            onClick={handleReviewRefund}
+          >
             退回不合格的资料
           </Button>
           <Button size="large" onClick={() => navigate(-1)}>

+ 1 - 0
src/pages/Login/index.tsx

@@ -103,6 +103,7 @@ export default function Login() {
               }
               placeholder="请输入验证码"
               variant="filled"
+              autoComplete="off"
             />
           </Form.Item>
 

+ 63 - 24
src/pages/Management/Files/index.tsx

@@ -4,15 +4,19 @@ import { Button, Form, Input, Select, Table } from "antd";
 import { PageContainer } from "@/components";
 import style from "./index.module.scss";
 import { debounce } from "lodash";
-import { getManageFileListAPi } from "@/api";
+import { changeArchiveApi, getManageFileListAPi } from "@/api";
+import { ARCHIVE_TYPE_MAP } from "@/constants";
+import { ARCHIVE_TYPE } from "@/types";
 
 const DEFAULT_PARAMS = {
   pageNum: 1,
   pageSize: 20,
   searchKey: "",
+  archive: "",
 };
 
 const ManagementReportPage = () => {
+  const [form] = Form.useForm();
   const [params, setParams] = useState({
     ...DEFAULT_PARAMS,
   });
@@ -46,6 +50,16 @@ const ManagementReportPage = () => {
     }
   }, [params]);
 
+  const handleArchive = async (item: any) => {
+    await changeArchiveApi(
+      item.archive === ARCHIVE_TYPE.ARCHIVED
+        ? ARCHIVE_TYPE.UNARCHIVED
+        : ARCHIVE_TYPE.ARCHIVED,
+      item.id
+    );
+    getList();
+  };
+
   useEffect(() => {
     getList();
   }, []);
@@ -53,13 +67,37 @@ const ManagementReportPage = () => {
   return (
     <PageContainer title="附件管理">
       <div className={style.filter}>
-        <Form layout="inline" className="inline-form">
-          <Form.Item label="搜索">
-            <Input placeholder="请输入考核名称" className="w160" />
+        <Form
+          form={form}
+          layout="inline"
+          className="inline-form"
+          onValuesChange={debounceSearch}
+        >
+          <Form.Item label="搜索" name="searchKey">
+            <Input
+              placeholder="请输入资料名称/考核名称./指标名称/责任部门名称"
+              className="w450"
+              allowClear
+            />
           </Form.Item>
           <Form.Item label="归档状态">
             <div className="w160">
-              <Select />
+              <Form.Item noStyle name="archive">
+                <Select
+                  allowClear
+                  options={[
+                    {
+                      label: "未归档",
+                      value: 0,
+                    },
+                    {
+                      label: "已归档",
+                      value: 1,
+                    },
+                  ]}
+                  placeholder="请选择"
+                />
+              </Form.Item>
             </div>
           </Form.Item>
           <Form.Item>
@@ -81,70 +119,71 @@ const ManagementReportPage = () => {
             {
               title: "资料名称",
               dataIndex: "name",
-              key: "name",
               align: "center",
               minWidth: 100,
             },
             {
               title: "关联考核",
-              dataIndex: "a",
-              key: "a",
+              dataIndex: "assessName",
               align: "center",
               minWidth: 100,
             },
             {
               title: "考核周期",
-              dataIndex: "description",
-              key: "description",
               align: "center",
               minWidth: 100,
+              render: (item) => {
+                return `${item.dateStart}-${item.dateEnd}`;
+              },
             },
             {
               title: "关联指标",
-              dataIndex: "b",
-              key: "b",
+              dataIndex: "normName",
               align: "center",
               minWidth: 100,
             },
             {
               title: "附件名称",
-              dataIndex: "b",
-              key: "b",
+              dataIndex: "fileName",
               align: "center",
               minWidth: 100,
             },
             {
               title: "责任部门",
-              dataIndex: "b",
-              key: "b",
+              dataIndex: "deptName",
               align: "center",
               minWidth: 100,
             },
             {
               title: "上传用户",
-              dataIndex: "b",
-              key: "b",
+              dataIndex: "creatorName",
               align: "center",
               minWidth: 100,
             },
             {
               title: "归档状态",
-              dataIndex: "b",
-              key: "b",
               align: "center",
               minWidth: 100,
+              // @ts-ignore
+              render: (item) => ARCHIVE_TYPE_MAP[item.archive],
             },
             {
               title: "操作",
-              key: "h",
               align: "center",
               fixed: "right",
-              render: (item: any) => {
+              render: (item) => {
+                const isArchived = item.archive === ARCHIVE_TYPE.ARCHIVED;
+
                 return (
                   <>
                     <Button type="link">下载</Button>
-                    <Button type="text" size="small">
-                      归档
+                    <Button
+                      type="link"
+                      danger={isArchived}
+                      size="small"
+                      onClick={handleArchive.bind(undefined, item)}
+                    >
+                      {!isArchived ? "归档" : "取消归档"}
                     </Button>
                   </>
                 );

+ 6 - 1
src/pages/Management/Index/components/AllocationOfIndexModal/index.tsx

@@ -39,6 +39,7 @@ export const AllocationOfIndexModal: FC<AllocationOfIndexModalProps> = ({
     []
   );
   const [loading, setLoading] = useState(false);
+  /** 仅查看尚未分配指标 */
   const assign = Form.useWatch("assign", form);
   const [indexList, setIndexList] = useState<IAssIndexDetail[]>([]);
   const [allocationOfIndexDataVisible, setAllocationOfIndexDataVisible] =
@@ -82,8 +83,12 @@ export const AllocationOfIndexModal: FC<AllocationOfIndexModalProps> = ({
         assign
       );
       if (!assign) {
+        const keys = getDefaultCheckedKeys(data);
         setTreeData(data);
-        setCheckedKeys(getDefaultCheckedKeys(data));
+        setCheckedKeys(keys);
+        form.setFieldsValue({
+          checkedKeys: keys,
+        });
       } else {
         setTreeData(getUnAllocationTree(data));
       }

+ 0 - 8
src/router/index.tsx

@@ -169,14 +169,6 @@ export const DEFAULT_MENU: DageRouteItem[] = [
               () => import("../pages/AssessmentDetail/IndexDetail")
             ),
           },
-          {
-            hide: true,
-            path: "/management/evaluation/detail/submit",
-            title: "考核指标详情",
-            Component: React.lazy(
-              () => import("../pages/AssessmentDetail/IndexDetail")
-            ),
-          },
         ],
       },
       {

+ 3 - 0
src/types/index.ts

@@ -1,4 +1,5 @@
 import { DageFileResponseType } from "@dage/pc-components";
+import { REVIEW_MATERIAL_TYPE } from "./management";
 
 /** norm:指标 | assess:考核 | fill:填报 */
 export type ModuleType = "norm" | "assess" | "fill-norm" | "fill-assess";
@@ -69,6 +70,8 @@ export type MaterialType = {
   module: ModuleType;
   moduleId: number;
   parentId: number | null;
+  opinion: string;
+  status: REVIEW_MATERIAL_TYPE;
 };
 
 export * from "./log";

+ 26 - 0
src/types/management.ts

@@ -17,6 +17,16 @@ export enum PUBLISH_ENUM {
   EVALUATED = 3,
 }
 
+/**
+ * 考核进度
+ */
+export type PlanType = {
+  name: string;
+  pcsNorm: number;
+  pcsFill: number;
+  status: REVIEW_MATERIAL_TYPE;
+};
+
 export interface IManageIndexDetail {
   id: number;
   name: string;
@@ -32,6 +42,8 @@ export interface IManageIndexDetail {
   jsonAdd: string;
   jsonSub: string;
   materials: MaterialType[] | null;
+  plan: PlanType[];
+  opinion: string;
 }
 
 export interface IManageAssessmentIndex {
@@ -149,6 +161,8 @@ export interface ISaveManageFileParams {
   name: string;
   suffix: string;
   parentId?: number;
+  deptId?: number;
+  assessId?: number;
 }
 
 export interface IManageNormItem {
@@ -160,9 +174,21 @@ export interface IManageNormItem {
   selfScore: number | null;
   isPoint: YES_OR_NO;
   deptName: string;
+  opinionScore: string;
 }
 
 export enum WARNING_TYPE {
   PUBLISH = 0,
   STOP = 1,
 }
+
+export enum REVIEW_MATERIAL_TYPE {
+  PENDING = 0,
+  PASS = 1,
+  FAIL = 2,
+}
+
+export enum ARCHIVE_TYPE {
+  ARCHIVED = 1,
+  UNARCHIVED = 0,
+}