chenlei 7 mesi fa
parent
commit
9873a7dd71

+ 3 - 2
.vscode/settings.json

@@ -1,4 +1,5 @@
 {
   "editor.defaultFormatter": "esbenp.prettier-vscode",
-  "editor.formatOnSave": true
-}
+  "editor.formatOnSave": true,
+  "typescript.tsdk": "node_modules\\typescript\\lib"
+}

+ 1 - 1
package.json

@@ -6,7 +6,7 @@
     "@ant-design/icons": "^5.1.4",
     "@ant-design/pro-components": "^2.8.1",
     "@babel/core": "^7.16.0",
-    "@dage/pc-components": "^1.3.3",
+    "@dage/pc-components": "^1.3.4",
     "@dage/service": "^1.0.3",
     "@dage/utils": "^1.0.2",
     "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",

File diff suppressed because it is too large
+ 8529 - 6998
pnpm-lock.yaml


+ 39 - 0
src/api/assessment.ts

@@ -0,0 +1,39 @@
+import {
+  ASS_INDEX_TYPE,
+  AssIndexTreeItemType,
+  IAssIndexDetail,
+  IFileTemplateFormParams,
+  IFileTemplateFormResponse,
+} from "@/types";
+import { requestByGet, requestByPost } from "@dage/service";
+
+/**
+ * 获取指标设置列表树
+ * @param {ASS_INDEX_TYPE} type (ASS_INDEX_TYPE.FIXED)
+ */
+export const getAssIndexTreeApi = (type = ASS_INDEX_TYPE.FIXED) => {
+  return requestByGet<AssIndexTreeItemType[]>(`/api/cms/norm/getTree/${type}`);
+};
+
+export const getAssIndexDetailApi = (id: number) => {
+  return requestByGet<IAssIndexDetail>(`/api/cms/norm/detail/${id}`);
+};
+
+export const createAssIndexApi = (params: any) => {
+  return requestByPost("/api/cms/norm/save", params);
+};
+
+export const saveAssEntityApi = (params: IFileTemplateFormParams) => {
+  return requestByPost<IFileTemplateFormResponse>(
+    "/api/cms/norm/file/saveEntity",
+    params
+  );
+};
+
+export const saveAssIndexApi = (params: any) => {
+  return requestByPost("/api/cms/norm/save", params);
+};
+
+export const deleteAssIndexApi = (ids: string | number) => {
+  return requestByGet(`/api/cms/norm/removes/${ids}`);
+};

+ 1 - 0
src/api/index.ts

@@ -15,3 +15,4 @@ export const updatePwd = (data: UpdatePwdRequest) => {
 
 export * from "./log";
 export * from "./user";
+export * from "./assessment";

+ 48 - 38
src/components/FileTemplateModal/index.tsx

@@ -7,28 +7,22 @@ import {
   ModalProps,
   Radio,
 } from "antd";
-import { FC, useEffect, useMemo, useState } from "react";
+import { FC, useEffect, useMemo } from "react";
 import style from "./index.module.scss";
 import {
-  DageFileResponseType,
   DageUpload,
   DageUploadConsumer,
   DageUploadProvider,
   DageUploadType,
 } from "@dage/pc-components";
-import { uniqueId } from "lodash";
 import { FILE_TYPE_ENUM } from "./constants";
-
-export interface FileTemplateForm {
-  id: string;
-  name: string;
-  required: number;
-  file?: DageFileResponseType[];
-  types?: string[];
-}
+import { IFileTemplateForm, IFileTemplateFormParams } from "@/types";
+import { saveAssEntityApi } from "@/api";
+import { getBaseURL } from "@dage/service";
 
 export interface FileTemplateModalProps extends ModalProps {
-  item?: FileTemplateForm | null;
+  item?: IFileTemplateFormParams | null;
+  module?: "norm" | "assess";
   onCancel?: () => void;
   onOk?: (val: any, isEdit?: boolean) => void;
 }
@@ -36,18 +30,14 @@ export interface FileTemplateModalProps extends ModalProps {
 export const FileTemplateModal: FC<FileTemplateModalProps> = ({
   item,
   open,
+  module,
   onOk,
   onCancel,
   ...rest
 }) => {
-  const [form] = Form.useForm<FileTemplateForm>();
-  const [fields, setFields] = useState<any[]>([
-    {
-      name: "required",
-      value: 0,
-    },
-  ]);
-  const typesVal = Form.useWatch("types", form);
+  const [form] = Form.useForm<IFileTemplateForm>();
+  const typesVal = Form.useWatch("suffix", form);
+  const baseUrl = getBaseURL();
 
   const checkAll = useMemo(
     () => FILE_TYPE_ENUM.length === typesVal?.length,
@@ -69,20 +59,24 @@ export const FileTemplateModal: FC<FileTemplateModalProps> = ({
     form.submit();
   };
 
-  const handleSubmit = async (values: FileTemplateForm) => {
-    onOk?.(
-      {
-        // @ts-ignore
-        id: item?.id || uniqueId("template"),
-        ...values,
-      },
-      Boolean(item)
-    );
+  const handleSubmit = async (values: IFileTemplateForm) => {
+    const { file, suffix, ...rest } = values;
+
+    const data = await saveAssEntityApi({
+      ...rest,
+      level: 1,
+      module: module as "norm" | "assess",
+      fileName: file?.[0].response?.fileName,
+      filePath: file?.[0].response?.filePath,
+      suffix: suffix?.join(","),
+    });
+
+    onOk?.(data, Boolean(item));
     handleCancel();
   };
 
   const onCheckAllChange: CheckboxProps["onChange"] = (e) => {
-    form.setFieldValue("types", e.target.checked ? [...FILE_TYPE_ENUM] : []);
+    form.setFieldValue("suffix", e.target.checked ? [...FILE_TYPE_ENUM] : []);
   };
 
   useEffect(() => {
@@ -91,7 +85,23 @@ export const FileTemplateModal: FC<FileTemplateModalProps> = ({
       return;
     }
 
-    form.setFieldsValue(item);
+    const { suffix, fileName, filePath, ...rest } = item;
+    form.setFieldsValue({
+      ...rest,
+      file: fileName
+        ? [
+            {
+              name: fileName,
+              url: baseUrl + process.env.REACT_APP_IMG_PUBLIC + filePath,
+              response: {
+                fileName,
+                filePath,
+              },
+            },
+          ]
+        : undefined,
+      suffix: suffix?.split(","),
+    });
   }, [item, form]);
 
   return (
@@ -113,12 +123,8 @@ export const FileTemplateModal: FC<FileTemplateModalProps> = ({
             {...rest}
           >
             <Form
-              fields={fields}
               labelCol={{ span: 4, offset: 1 }}
               form={form}
-              onFieldsChange={(_, newFields) => {
-                setFields(newFields);
-              }}
               onFinish={handleSubmit}
             >
               <Form.Item
@@ -133,7 +139,11 @@ export const FileTemplateModal: FC<FileTemplateModalProps> = ({
                   maxLength={20}
                 />
               </Form.Item>
-              <Form.Item label="填报指标时必须上传" name="required">
+              <Form.Item
+                label="填报指标时必须上传"
+                name="isUpload"
+                initialValue={0}
+              >
                 <Radio.Group
                   size="large"
                   options={[
@@ -152,7 +162,7 @@ export const FileTemplateModal: FC<FileTemplateModalProps> = ({
               </Form.Item>
               <Form.Item label="资料模板" name="file">
                 <DageUpload
-                  action="/api/cms/goods/upload"
+                  action="/api/cms/norm/file/upload"
                   dType={DageUploadType.DOC}
                   maxCount={1}
                   tips="最多1个附件"
@@ -167,7 +177,7 @@ export const FileTemplateModal: FC<FileTemplateModalProps> = ({
                   全部
                 </Checkbox>
 
-                <Form.Item name="types" noStyle>
+                <Form.Item name="suffix" noStyle>
                   <Checkbox.Group options={FILE_TYPE_ENUM} />
                 </Form.Item>
               </Form.Item>

+ 20 - 10
src/components/FileTemplateTable/index.tsx

@@ -2,24 +2,33 @@ import { FC, useState } from "react";
 import { PlusOutlined } from "@ant-design/icons";
 import { Button, Table } from "antd";
 import { DageTableActions } from "@dage/pc-components";
-import { FileTemplateForm, FileTemplateModal } from "../FileTemplateModal";
+import { IFileTemplateFormResponse } from "@/types";
+import { FileTemplateModal } from "../FileTemplateModal";
 import style from "./index.module.scss";
 
 export interface FileTemplateTableProps {
-  value?: FileTemplateForm[];
+  /**
+   * 模块名称
+   * norm:指标 | assess:考核
+   * @default 'assess'
+   */
+  module?: "norm" | "assess";
+  value?: IFileTemplateFormResponse[];
   tips?: string;
-  onChange?: (val: FileTemplateForm[]) => void;
+  onChange?: (val: IFileTemplateFormResponse[]) => void;
 }
 
 export const FileTemplateTable: FC<FileTemplateTableProps> = ({
   tips,
   value,
+  module = "assess",
   onChange,
 }) => {
-  const [checkedItem, setCheckedItem] = useState<FileTemplateForm | null>(null);
+  const [checkedItem, setCheckedItem] =
+    useState<IFileTemplateFormResponse | null>(null);
   const [templateVisible, setTemplateVisible] = useState(false);
 
-  const handleTemplate = (val: FileTemplateForm, isEdit?: boolean) => {
+  const handleTemplate = (val: IFileTemplateFormResponse, isEdit?: boolean) => {
     const temp = value ? [...value] : [];
 
     if (isEdit) {
@@ -74,22 +83,22 @@ export const FileTemplateTable: FC<FileTemplateTableProps> = ({
             title: "必须上传",
             key: "age",
             align: "center",
-            render: (item: FileTemplateForm) => {
-              return item.required ? "是" : "否";
+            render: (item: IFileTemplateFormResponse) => {
+              return item.isUpload ? "是" : "否";
             },
           },
           {
             title: "模板",
             key: "address",
             align: "center",
-            render: (item: FileTemplateForm) => {
-              return item.file ? item.file[0].name : "/";
+            render: (item: IFileTemplateFormResponse) => {
+              return item.fileName || "/";
             },
           },
           {
             title: "操作",
             align: "center",
-            render: (item: FileTemplateForm, record, index) => {
+            render: (item: IFileTemplateFormResponse, record, index) => {
               return (
                 <DageTableActions
                   onEdit={() => {
@@ -107,6 +116,7 @@ export const FileTemplateTable: FC<FileTemplateTableProps> = ({
       <FileTemplateModal
         item={checkedItem}
         open={templateVisible}
+        module={module}
         onOk={handleTemplate}
         onCancel={handleCloseModal}
       />

+ 195 - 58
src/pages/Assessment/Index/CreateOrEdit/index.tsx

@@ -1,57 +1,167 @@
-import { FC, useState } from "react";
+import { FC, useEffect, useMemo, useState } from "react";
 import classNames from "classnames";
-import { Col, Form, Input, InputNumber, Radio, Row, Select } from "antd";
-import { useNavigate } from "react-router-dom";
+import {
+  Col,
+  Form,
+  Input,
+  InputNumber,
+  Radio,
+  Row,
+  Select,
+  TreeSelect,
+} from "antd";
+import { useLocation, useNavigate, useParams } from "react-router-dom";
 import { FileTemplateTable, FormPageFooter, PageContainer } from "@/components";
 import style from "./index.module.scss";
+import {
+  AssIndexTreeItemType,
+  ASS_INDEX_TYPE,
+  IAssIndexDetail,
+  YES_OR_NO,
+} from "@/types";
+import {
+  getAssIndexDetailApi,
+  getAssIndexTreeApi,
+  saveAssIndexApi,
+} from "@/api";
+import { CONNECT_WITH_SYMBOL_OPTIONS, SYMBOL_OPTIONS } from "../constants";
+import { isNull } from "lodash";
+import { DageLoading } from "@dage/pc-components";
 
 const { TextArea } = Input;
 const CONFIRM_OPTIONS = [
   {
     label: "是",
-    value: 1,
+    value: YES_OR_NO.YES,
   },
   {
     label: "否",
-    value: 0,
+    value: YES_OR_NO.NO,
   },
 ];
 
 const CreateOrEditIndex: FC = () => {
+  const location = useLocation();
   const navigate = useNavigate();
+  const query = new URLSearchParams(location.search);
+  const params = useParams();
   const [form] = Form.useForm();
-  const [fields, setFields] = useState<any[]>([
-    {
-      name: "dfd",
-      value: 1,
-    },
-    {
-      name: "gjqz",
-      value: 1,
-    },
-  ]);
+  const [loading, setLoading] = useState(false);
+  const [values, setValues] = useState<any>({
+    isPoint: YES_OR_NO.YES,
+    isWarn: YES_OR_NO.YES,
+  });
+  const [treeData, setTreeData] = useState<AssIndexTreeItemType[]>([]);
+  const isEdit = useMemo(
+    () => location.pathname.indexOf("edit") > -1,
+    [location]
+  );
+  const [detail, setDetail] = useState<null | IAssIndexDetail>(null);
 
   const handleSubmit = async () => {
     if (!(await form.validateFields())) return;
 
-    console.log(form.getFieldsValue());
+    const { materialIds, api, isAdd, score, warnData, ...rest } =
+      form.getFieldsValue();
+
+    if (materialIds)
+      rest.materialIds = materialIds.map((i: any) => i.id).join(",");
+    if (rest.isPoint === YES_OR_NO.YES) {
+      // 打分点
+      rest.jsonPoint = {
+        api,
+        isAdd,
+        score,
+      };
+    }
+    if (rest.isWarn === YES_OR_NO.YES) {
+      // 告警阈值
+      rest.jsonWarn = JSON.stringify(warnData);
+    }
+
+    await saveAssIndexApi({
+      ...rest,
+      level: 1,
+      type: params.type,
+      id: detail?.id,
+    });
+    navigate(-1);
+  };
+
+  const getAssIndexTree = async () => {
+    const data = await getAssIndexTreeApi(params.type as ASS_INDEX_TYPE);
+    setTreeData(data);
+  };
+
+  const getDetail = async () => {
+    try {
+      setLoading(true);
+      const data = await getAssIndexDetailApi(Number(params.id));
+      const pointData =
+        data.isPoint === YES_OR_NO.YES ? JSON.parse(data.jsonPoint) : null;
+      const warnData =
+        data.isWarn === YES_OR_NO.YES ? JSON.parse(data.jsonWarn) : null;
+      setDetail(data);
+      form.setFieldsValue({
+        parentId: data.parentId,
+        name: data.name,
+        remark: data.remark,
+        num: data.num,
+        isPoint: data.isPoint,
+        isWarn: data.isWarn,
+        score: pointData?.score,
+        api: pointData?.api,
+        isAdd: pointData?.isAdd,
+        warnData,
+        materialIds: data.materials,
+        sort: data.sort,
+      });
+      setValues({
+        isPoint: data.isPoint,
+        isWarn: data.isWarn,
+      });
+    } finally {
+      setLoading(false);
+    }
   };
 
+  useEffect(() => {
+    getAssIndexTree();
+
+    const parentId = query.get("parentId");
+    if (!isNull(parentId)) {
+      form.setFieldValue("parentId", parentId);
+    }
+
+    if (isEdit) getDetail();
+  }, []);
+
   return (
     <PageContainer title="新增指标">
       <Form
-        fields={fields}
         labelCol={{ span: 4 }}
         form={form}
-        onFieldsChange={(_, newFields) => {
-          setFields(newFields);
-        }}
         onFinish={handleSubmit}
+        onValuesChange={(v, allValue) => {
+          setValues(allValue);
+        }}
       >
-        <Form.Item label="父级指标">
-          <Select className="mw650" placeholder="请选择" options={[]} />
+        <Form.Item label="父级指标" name="parentId">
+          <TreeSelect
+            disabled={isEdit}
+            allowClear
+            className="mw650"
+            placeholder="请选择"
+            treeData={treeData}
+            fieldNames={{ label: "name", value: "id" }}
+          />
         </Form.Item>
-        <Form.Item label="指标名称" required>
+        <Form.Item
+          label="指标名称"
+          required
+          name="name"
+          rules={[{ required: true, message: "请输入指标名称" }]}
+        >
           <Input
             className="mw650"
             placeholder="请输入内容,最多20字"
@@ -59,7 +169,7 @@ const CreateOrEditIndex: FC = () => {
             maxLength={20}
           />
         </Form.Item>
-        <Form.Item label="指标编号">
+        <Form.Item label="指标编号" name="num">
           <Input
             className="mw650"
             placeholder="请输入内容,最多20字"
@@ -67,7 +177,7 @@ const CreateOrEditIndex: FC = () => {
             maxLength={20}
           />
         </Form.Item>
-        <Form.Item label="指标说明">
+        <Form.Item label="指标说明" name="reamrk">
           <TextArea
             className="mw650"
             showCount
@@ -77,7 +187,7 @@ const CreateOrEditIndex: FC = () => {
           />
         </Form.Item>
 
-        <Form.Item label="打分点" name="dfd">
+        <Form.Item label="打分点" name="isPoint" initialValue={values.isPoint}>
           <Radio.Group
             options={CONFIRM_OPTIONS}
             optionType="button"
@@ -86,19 +196,31 @@ const CreateOrEditIndex: FC = () => {
         </Form.Item>
         <Form.Item label=" " colon={false} style={{ marginTop: -14 }}>
           <div className={classNames("mw650", style.panel)}>
-            {fields[0].value ? (
+            {Boolean(values.isPoint) ? (
               <>
-                <Form.Item required label="指标分值" labelCol={{ span: 4 }}>
+                <Form.Item
+                  required
+                  label="指标分值"
+                  labelCol={{ span: 4 }}
+                  name="score"
+                  rules={[{ required: true, message: "请输入指标分值" }]}
+                >
                   <InputNumber
                     precision={0}
                     placeholder="请输入正整数"
                     className="w160"
                   />
                 </Form.Item>
-                <Form.Item label="数据API" labelCol={{ span: 4 }}>
+                <Form.Item label="数据API" labelCol={{ span: 4 }} name="api">
                   <Input placeholder="请输入地址" style={{ width: 427 }} />
                 </Form.Item>
                 <Form.Item label="加分项" labelCol={{ span: 4 }} colon={false}>
+                  <Form.Item noStyle name="isAdd">
+                    <Radio.Group>
+                      <Radio value={1}>是</Radio>
+                      <Radio value={0}>否</Radio>
+                    </Radio.Group>
+                  </Form.Item>
                   <span className={style.tips}>
                     注:设为加分项后,该指标的分值不会参与父级指标的计算
                   </span>
@@ -114,14 +236,14 @@ const CreateOrEditIndex: FC = () => {
           </div>
         </Form.Item>
 
-        <Form.Item label="告警阈值" name="gjqz">
+        <Form.Item label="告警阈值" name="isWarn" initialValue={values.isWarn}>
           <Radio.Group
             options={CONFIRM_OPTIONS}
             optionType="button"
             buttonStyle="solid"
           />
         </Form.Item>
-        {fields[1].value ? (
+        {Boolean(values.isWarn) ? (
           <Form.Item label=" " colon={false} style={{ marginTop: -14 }}>
             <div
               className={classNames("mw650", style.panel)}
@@ -129,44 +251,57 @@ const CreateOrEditIndex: FC = () => {
             >
               <Row gutter={10}>
                 <Col span={8}>
-                  <Select placeholder="请选择" options={[]} />
+                  <Form.Item
+                    noStyle
+                    name={["warnData", "symbol"]}
+                    rules={[{ required: true, message: "" }]}
+                  >
+                    <Select placeholder="请选择" options={SYMBOL_OPTIONS} />
+                  </Form.Item>
                 </Col>
                 <Col span={12}>
-                  <InputNumber
-                    precision={0}
-                    placeholder="请输入正整数"
-                    className="w100"
-                  />
+                  <Form.Item
+                    noStyle
+                    name={["warnData", "num"]}
+                    rules={[{ required: true, message: "" }]}
+                  >
+                    <InputNumber
+                      precision={0}
+                      placeholder="请输入正整数"
+                      className="w100"
+                    />
+                  </Form.Item>
                 </Col>
                 <Col span={4}>
                   <span className={style.required}>(必填)</span>
                 </Col>
               </Row>
 
-              <Radio.Group
-                options={[
-                  {
-                    label: "与",
-                    value: 0,
-                  },
-                  {
-                    label: "否",
-                    value: 1,
-                  },
-                ]}
-                style={{ margin: "3px 0" }}
-              />
+              <Form.Item noStyle name={["warnData", "and"]}>
+                <Radio.Group
+                  options={CONNECT_WITH_SYMBOL_OPTIONS}
+                  style={{ margin: "3px 0" }}
+                />
+              </Form.Item>
 
               <Row gutter={10}>
                 <Col span={8}>
-                  <Select placeholder="请选择" options={[]} />
+                  <Form.Item noStyle name={["warnData", "symbol2"]}>
+                    <Select
+                      allowClear
+                      placeholder="请选择"
+                      options={SYMBOL_OPTIONS}
+                    />
+                  </Form.Item>
                 </Col>
                 <Col span={12}>
-                  <InputNumber
-                    precision={0}
-                    placeholder="请输入正整数"
-                    className="w100"
-                  />
+                  <Form.Item noStyle name={["warnData", "num2"]}>
+                    <InputNumber
+                      precision={0}
+                      placeholder="请输入正整数"
+                      className="w100"
+                    />
+                  </Form.Item>
                 </Col>
                 <Col span={4}></Col>
               </Row>
@@ -176,11 +311,11 @@ const CreateOrEditIndex: FC = () => {
           ""
         )}
 
-        <Form.Item label="需上传资料">
+        <Form.Item label="需上传资料" name="materialIds">
           <FileTemplateTable />
         </Form.Item>
 
-        <Form.Item required label="排序值">
+        <Form.Item required label="排序值" name="sort" initialValue={999}>
           <InputNumber
             precision={0}
             min={1}
@@ -192,6 +327,8 @@ const CreateOrEditIndex: FC = () => {
 
         <FormPageFooter onSubmit={handleSubmit} onCancel={() => navigate(-1)} />
       </Form>
+
+      {loading && <DageLoading />}
     </PageContainer>
   );
 };

+ 89 - 45
src/pages/Assessment/Index/components/Container/index.tsx

@@ -1,69 +1,112 @@
-import { FC } from "react";
+import { FC, useEffect, useMemo, useState } from "react";
 import { Table } from "antd";
 import style from "../../index.module.scss";
+import { DageLoading } from "@dage/pc-components";
+import { getAssIndexDetailApi } from "@/api";
+import { IAssIndexDetail, YES_OR_NO } from "@/types";
+import { CONNECT_WITH_SYMBOL_OPTIONS, SYMBOL_OPTIONS } from "../../constants";
 
-const dataSource = [
-  {
-    key: "1",
-    name: "胡彦斌",
-    age: 32,
-    address: "西湖区湖底公园1号",
-  },
-  {
-    key: "2",
-    name: "胡彦祖",
-    age: 42,
-    address: "西湖区湖底公园1号",
-  },
-];
+export interface ContainerProps {
+  currentId: number | null;
+}
+
+export const Container: FC<ContainerProps> = ({ currentId }) => {
+  const [loading, setLoading] = useState(false);
+  const [detail, setDetail] = useState<IAssIndexDetail | null>(null);
+  // 打分点
+  const isPoint = detail?.isPoint === YES_OR_NO.YES;
+  const point = useMemo(
+    () => (detail && isPoint ? JSON.parse(detail.jsonPoint) : null),
+    [detail]
+  );
+  // 告警阈值
+  const isWarn = detail?.isWarn === YES_OR_NO.YES;
+  const warnStr = useMemo(() => {
+    if (!isWarn) return;
+
+    const data = JSON.parse(detail.jsonWarn);
+    const symbol = SYMBOL_OPTIONS.find((i) => i.value === data.symbol);
+    let str = `${symbol?.label}${data.num}`;
+
+    const connect = CONNECT_WITH_SYMBOL_OPTIONS.find(
+      (i) => i.value === data.and
+    );
+    const symbol2 = SYMBOL_OPTIONS.find((i) => i.value === data.symbol2);
+    if (connect && symbol2 && data.num2) {
+      str += ` ${connect.label} ${symbol2.label}${data.num2}`;
+    }
+    return str;
+  }, [detail]);
+
+  const getDetail = async () => {
+    if (!currentId) return;
+
+    try {
+      setLoading(true);
+      const data = await getAssIndexDetailApi(currentId);
+      setDetail(data);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    getDetail();
+  }, [currentId]);
 
-export const Container: FC = () => {
   return (
     <div className={style.container}>
       <h4 className={style.containerTitle}>指标详情</h4>
 
       <div className={style.tableItem}>
         <p className={style.tableItemLabel}>指标名称</p>
-        <div className={style.tableItemInner}>0-0-0-0</div>
+        <div className={style.tableItemInner}>{detail?.name}</div>
       </div>
       <div className={style.tableItem}>
         <p className={style.tableItemLabel}>指标编号</p>
-        <div className={style.tableItemInner}>0-0-0-0</div>
+        <div className={style.tableItemInner}>{detail?.num}</div>
       </div>
       <div className={style.tableItem}>
         <p className={style.tableItemLabel}>指标说明</p>
-        <div className={style.tableItemInner}>
-          善良和谦虚是永远不应令人厌恶的两种品德。 —— 斯蒂文生
-        </div>
+        <div className={style.tableItemInner}>{detail?.remark}</div>
       </div>
       <div className={style.tableItem}>
         <p className={style.tableItemLabel}>是否为打分点</p>
-        <div className={style.tableItemInner}>是</div>
-      </div>
-      <div className={style.tableItem}>
-        <p className={style.tableItemLabel}>数据API</p>
-        <div className={style.tableItemInner}>数据API</div>
-      </div>
-      <div className={style.tableItem}>
-        <p className={style.tableItemLabel}>是否为加分项</p>
-        <div className={style.tableItemInner}>是</div>
+        <div className={style.tableItemInner}>{!isPoint ? "否" : "是"}</div>
       </div>
+      {isPoint && point && (
+        <>
+          <div className={style.tableItem}>
+            <p className={style.tableItemLabel}>数据API</p>
+            <div className={style.tableItemInner}>{point.api || "--"}</div>
+          </div>
+          <div className={style.tableItem}>
+            <p className={style.tableItemLabel}>是否为加分项</p>
+            <div className={style.tableItemInner}>
+              {point.isAdd === 1 ? "是" : "否"}
+            </div>
+          </div>
+          <div className={style.tableItem}>
+            <p className={style.tableItemLabel}>分值</p>
+            <div className={style.tableItemInner}>{point.score}</div>
+          </div>
+        </>
+      )}
       <div className={style.tableItem}>
         <p className={style.tableItemLabel}>告警阈值</p>
-        <div className={style.tableItemInner}>
-          {">"}50 或 {"<"}20
-        </div>
-      </div>
-      <div className={style.tableItem}>
-        <p className={style.tableItemLabel}>分值</p>
-        <div className={style.tableItemInner}>100</div>
+        {!isWarn ? (
+          "未开启"
+        ) : (
+          <div className={style.tableItemInner}>{warnStr}</div>
+        )}
       </div>
       <div className={style.tableItem}>
         <p className={style.tableItemLabel}>需上传资料</p>
         <div className={style.tableItemInner}>
           <Table
             className="cus-table"
-            dataSource={dataSource}
+            rowKey="id"
+            dataSource={detail?.materials}
             columns={[
               {
                 title: "资料名称",
@@ -73,14 +116,13 @@ export const Container: FC = () => {
               },
               {
                 title: "必须上传",
-                dataIndex: "age",
-                key: "age",
                 align: "center",
+                render: (e) => (e.isUpload === YES_OR_NO.YES ? "是" : "否"),
               },
               {
                 title: "模板",
-                dataIndex: "address",
-                key: "address",
+                dataIndex: "fileName",
+                key: "fileName",
                 align: "center",
               },
             ]}
@@ -89,16 +131,18 @@ export const Container: FC = () => {
       </div>
       <div className={style.tableItem}>
         <p className={style.tableItemLabel}>排序值</p>
-        <div className={style.tableItemInner}>88</div>
+        <div className={style.tableItemInner}>{detail?.sort}</div>
       </div>
       <div className={style.tableItem}>
         <p className={style.tableItemLabel}>编辑时间</p>
-        <div className={style.tableItemInner}>2020-02-02 12:12</div>
+        <div className={style.tableItemInner}>{detail?.updateTime}</div>
       </div>
       <div className={style.tableItem}>
         <p className={style.tableItemLabel}>编辑人</p>
-        <div className={style.tableItemInner}>张三</div>
+        <div className={style.tableItemInner}>{detail?.creatorName}</div>
       </div>
+
+      {loading && <DageLoading />}
     </div>
   );
 };

+ 75 - 48
src/pages/Assessment/Index/components/Sidebar/index.tsx

@@ -1,62 +1,72 @@
-import { FC } from "react";
-import { Button, TreeDataNode } from "antd";
-import { DataNode } from "antd/es/tree";
+import { FC, useEffect, useRef, useState } from "react";
+import { Button } from "antd";
 import { useNavigate } from "react-router-dom";
-import { DageTreeActions } from "@dage/pc-components";
+import {
+  DageLoading,
+  DageTreeActionData,
+  DageTreeActions,
+} from "@dage/pc-components";
 import { PlusOutlined } from "@ant-design/icons";
+import { deleteAssIndexApi, getAssIndexTreeApi } from "@/api";
+import { AssIndexTreeItemType, ASS_INDEX_TYPE } from "@/types";
 import style from "../../index.module.scss";
+import { isNumber } from "lodash";
 
 export interface SidebarProps {
   /**
    * 勾选模式
    */
   checkable?: boolean;
+  type: ASS_INDEX_TYPE;
+  currentId: null | number;
+  setCurrentId: (id: number) => void;
 }
 
-const treeData: TreeDataNode[] = [
-  {
-    title: "parent 1",
-    key: "0-0",
-    children: [
-      {
-        title: "leaf",
-        key: "0-0-0",
-      },
-      {
-        title: "leaf",
-        key: "0-0-1",
-        children: [
-          {
-            title: "leaf leaf",
-            key: "0-1-1",
-          },
-        ],
-      },
-    ],
-  },
-];
+export const Sidebar: FC<SidebarProps> = ({
+  checkable,
+  type,
+  currentId,
+  setCurrentId,
+}) => {
+  const navigate = useNavigate();
+  const [loading, setLoading] = useState(false);
+  const [treeData, setTreeData] = useState<AssIndexTreeItemType[]>([]);
+  const [checkedKeys, setCheckedKeys] = useState<number[]>([]);
+  const lastCheckedItem = useRef<any>(null);
 
-let key = 0;
+  const getAssIndexTree = async () => {
+    try {
+      setLoading(true);
+      const data = await getAssIndexTreeApi(type);
+      if (data.length) {
+        data[0].disabled = true;
+        setCurrentId(data[0].id);
+        setCheckedKeys([data[0].id]);
+        lastCheckedItem.current = data[0];
+      }
+      setTreeData(data);
+    } finally {
+      setLoading(false);
+    }
+  };
 
-export const Sidebar: FC<SidebarProps> = ({ checkable }) => {
-  const navigate = useNavigate();
+  const handleAddNode = (item: DageTreeActionData) => {
+    navigate(`/assessment/index/create/${type}?parentId=${item.id}`);
+  };
 
-  const handleAddNode = async (item: DataNode) => {
-    return new Promise((res) => {
-      setTimeout(() => {
-        if (!item.children) {
-          item.children = [
-            {
-              title: "leaf",
-              key: "0-0-0" + key++,
-            },
-          ];
-        }
-        res(true);
-      }, 1000);
-    });
+  const handleEditNode = (item: DageTreeActionData) => {
+    navigate(`/assessment/index/edit/${type}/${item.id}`);
   };
 
+  const handleDeleteNode = async (item: DageTreeActionData) => {
+    await deleteAssIndexApi(item.id);
+    getAssIndexTree();
+  };
+
+  useEffect(() => {
+    getAssIndexTree();
+  }, [type]);
+
   return (
     <div className={style.sidebar}>
       <div className={style.sidebarHeader}>
@@ -66,17 +76,34 @@ export const Sidebar: FC<SidebarProps> = ({ checkable }) => {
           <Button
             type="primary"
             icon={<PlusOutlined />}
-            onClick={() => navigate("/assessment/index/create")}
+            onClick={() => navigate("/assessment/index/create/" + type)}
           >
             新增指标
           </Button>
         )}
       </div>
 
-      <DageTreeActions
-        treeData={treeData}
-        onAdd={checkable ? undefined : handleAddNode}
-      />
+      {currentId && (
+        <DageTreeActions
+          defaultExpandAll
+          treeData={treeData}
+          selectedKeys={checkedKeys}
+          onAdd={checkable ? undefined : handleAddNode}
+          onEdit={handleEditNode}
+          onDelete={handleDeleteNode}
+          onSelect={(keys, { selectedNodes }) => {
+            const checkedId = keys[keys.length - 1] as number;
+
+            lastCheckedItem.current.disabled = false;
+            selectedNodes[0].disabled = true;
+            setCurrentId(checkedId);
+            setCheckedKeys([checkedId]);
+            lastCheckedItem.current = selectedNodes[0];
+          }}
+        />
+      )}
+
+      {loading && <DageLoading />}
     </div>
   );
 };

+ 35 - 0
src/pages/Assessment/Index/constants.ts

@@ -0,0 +1,35 @@
+/**
+ * 告警阈值-运算符
+ */
+export const SYMBOL_OPTIONS = [
+  {
+    label: ">",
+    value: 0,
+  },
+  {
+    label: "≥",
+    value: 1,
+  },
+  {
+    label: "<",
+    value: 2,
+  },
+  {
+    label: "≤",
+    value: 3,
+  },
+];
+
+/**
+ * 告警阈值-关联参数
+ */
+export const CONNECT_WITH_SYMBOL_OPTIONS = [
+  {
+    label: "与",
+    value: 0,
+  },
+  {
+    label: "或",
+    value: 1,
+  },
+];

+ 9 - 0
src/pages/Assessment/Index/index.module.scss

@@ -20,6 +20,7 @@
 }
 
 .sidebar {
+  position: relative;
   flex-shrink: 0;
   width: 338px;
   min-height: 100%;
@@ -29,10 +30,17 @@
   :global {
     .ant-tree {
       background: none;
+
+      .ant-tree-treenode-disabled .ant-tree-node-content-wrapper {
+        cursor: initial;
+      }
     }
     .ant-tree-treenode {
       padding: 0 15px;
     }
+    .ant-tree-title {
+      color: black !important;
+    }
   }
 }
 
@@ -52,6 +60,7 @@
 }
 
 .container {
+  position: relative;
   padding-right: 40px;
 }
 .containerTitle {

+ 23 - 4
src/pages/Assessment/Index/index.tsx

@@ -1,28 +1,47 @@
+import { useState } from "react";
 import { Button } from "antd";
 import { PageContainer } from "@/components";
 import { Sidebar } from "./components/Sidebar";
 import { Container } from "./components/Container";
 import style from "./index.module.scss";
+import { ASS_INDEX_TYPE } from "@/types";
 
 const IndexPage = () => {
+  const [type, setType] = useState(ASS_INDEX_TYPE.FIXED);
+  const [currentId, setCurrentId] = useState<number | null>(null);
+
   return (
     <PageContainer
       title="指标管理"
       headerSlot={
         <div className={style.pageTools}>
-          <Button type="primary" size="large">
+          <Button
+            type="primary"
+            ghost={type !== ASS_INDEX_TYPE.FIXED}
+            size="large"
+            onClick={() => setType(ASS_INDEX_TYPE.FIXED)}
+          >
             定级评估指标
           </Button>
-          <Button type="primary" ghost size="large">
+          <Button
+            type="primary"
+            ghost={type !== ASS_INDEX_TYPE.OPERATION}
+            size="large"
+            onClick={() => setType(ASS_INDEX_TYPE.OPERATION)}
+          >
             运行评估指标
           </Button>
         </div>
       }
     >
       <div className={style.page}>
-        <Sidebar />
+        <Sidebar
+          type={type}
+          currentId={currentId}
+          setCurrentId={setCurrentId}
+        />
 
-        <Container />
+        <Container currentId={currentId} />
       </div>
     </PageContainer>
   );

+ 1 - 1
src/pages/Management/Index/CreateOrEdit/index.tsx

@@ -58,7 +58,7 @@ const CreateOrEditManagementIndex: FC = () => {
         </Form.Item>
 
         <Form.Item label="需上传资料" name="file">
-          <FileTemplateTable tips="注:该资料用于此次整体考核,与具体指标无关" />
+          {/* <FileTemplateTable tips="注:该资料用于此次整体考核,与具体指标无关" /> */}
         </Form.Item>
 
         <Form.Item label="附加项">

+ 1 - 1
src/pages/Performance/Report/index.tsx

@@ -20,7 +20,7 @@ const ManagementReportPage = () => {
       }
     >
       <div className={style.page}>
-        <Sidebar checkable />
+        {/* <Sidebar checkable /> */}
 
         <Container />
       </div>

+ 9 - 1
src/router/index.tsx

@@ -20,12 +20,20 @@ export const DEFAULT_MENU: DageRouteItem[] = [
         children: [
           {
             hide: true,
-            path: "/assessment/index/create",
+            path: "/assessment/index/create/:type",
             title: "新增指标",
             Component: React.lazy(
               () => import("../pages/Assessment/Index/CreateOrEdit")
             ),
           },
+          {
+            hide: true,
+            path: "/assessment/index/edit/:type/:id",
+            title: "编辑指标",
+            Component: React.lazy(
+              () => import("../pages/Assessment/Index/CreateOrEdit")
+            ),
+          },
         ],
       },
       {

+ 45 - 0
src/types/assessment.ts

@@ -0,0 +1,45 @@
+import { YES_OR_NO } from ".";
+
+export enum ASS_INDEX_TYPE {
+  /**
+   * 固定指标
+   */
+  FIXED = "fixed",
+  /**
+   * 运营指标
+   */
+  OPERATION = "operation",
+}
+
+export type AssIndexTreeItemType = {
+  id: number;
+  name: string;
+  parentId: number;
+  level: number;
+  children: AssIndexTreeItemType[];
+  disabled?: boolean;
+};
+
+export interface IAssIndexDetail {
+  id: number;
+  parentId: number;
+  name: string;
+  /** 指标编号 */
+  num: string;
+  remark: string;
+  /** 打分点 */
+  isPoint: YES_OR_NO;
+  jsonPoint: string;
+  /** 告警阈值 */
+  isWarn: YES_OR_NO;
+  jsonWarn: string;
+  materials: {
+    name: string;
+    id: number;
+    fileName: string;
+    isUpload: YES_OR_NO;
+  }[];
+  sort: number;
+  updateTime: string;
+  creatorName: string;
+}

+ 27 - 0
src/types/index.ts

@@ -1,3 +1,5 @@
+import { DageFileResponseType } from "@dage/pc-components";
+
 export interface LoginRequest {
   userName: string;
   passWord: string | string[];
@@ -26,5 +28,30 @@ export enum ResponseStatusCode {
   TOKEN_INVALID2 = 5002,
 }
 
+export interface IFileTemplateForm {
+  name: string;
+  isUpload: number;
+  file?: DageFileResponseType[];
+  suffix?: string[];
+}
+export interface IFileTemplateFormParams
+  extends Omit<IFileTemplateForm, "file" | "suffix"> {
+  fileName?: string;
+  filePath?: string;
+  suffix?: string;
+  level: number;
+  module: "norm" | "assess";
+}
+export interface IFileTemplateFormResponse
+  extends Required<IFileTemplateFormParams> {
+  id: number;
+}
+
+export enum YES_OR_NO {
+  YES = 1,
+  NO = 0,
+}
+
 export * from "./log";
 export * from "./user";
+export * from "./assessment";