Przeglądaj źródła

feat: 新增质量巡检页面。

lanjianrong 4 lat temu
rodzic
commit
58e20cd272

+ 12 - 2
src/pages/Contract/List/index.tsx

@@ -7,11 +7,12 @@ import { CaretDownOutlined } from '@ant-design/icons'
 import { Button, Dropdown, Menu, message, Table } from 'antd'
 import { ColumnsType } from 'antd/lib/table'
 import React, { useEffect, useState } from 'react'
-import { Link } from 'react-router-dom'
+import { useAliveController } from 'react-activation'
 import { apiContractList } from './api'
 import styles from './index.module.scss'
 import './index.scss'
 const List: React.FC<{}> = () => {
+  const { clear } = useAliveController()
   useEffect(() => {
     getTree()
   }, [])
@@ -42,6 +43,14 @@ const List: React.FC<{}> = () => {
       setTree(data)
     }
   }
+
+  const linkHandler = (id: string, name: string) => {
+    tenderStore.saveBidsectionId(id)
+    tenderStore.saveName(name)
+    props.history.push('/console/safe/content/summary')
+    // 清除所有的缓存页面
+    clear()
+  }
   const columns:ColumnsType<ContractTree> = [
     {
       title: '名称',
@@ -53,7 +62,8 @@ const List: React.FC<{}> = () => {
           return <div style={{ verticalAlign: "baseline" }}><SvgIcon type={record.hasFolder ? "xxh-folder-open" : "xxh-folder"} /><span className="pi-mg-left-2">{text}</span></div>
         } else {
         return <div><span style={{ color: '#6c757d', marginRight: '.5rem' }}>{record.isEnd ? '└' : '├'}</span>
-        <Link to={{ pathname: '/console/contract/content/summary', state: { id: record.bidsectionId } }}>{text}</Link>
+        {/* <Link to={{ pathname: '/console/contract/content/summary', state: { id: record.bidsectionId } }}>{text}</Link> */}
+        <span className="pi-link-blue" onClick={() => linkHandler(record.bidsectionId, record.name)}>{text}</span>
         </div>
         }
       }

+ 22 - 0
src/pages/Quality/Content/Info/Detail/api.ts

@@ -0,0 +1,22 @@
+import { iFile } from "@/types/file"
+import request from "@/utils/common/request"
+
+/**
+ * 获取安全巡检详情
+ * @param id - 安全巡检id
+ */
+export async function apiGetSafeDetail(id: string) {
+  const { data } = await request.get('/api/safe/detail', { id })
+  return data
+}
+
+/**
+ *
+ * @param fileList - 附件数组
+ * @param dataType - 类型
+ * @param dataId - 数据id
+ */
+export async function apiSaveFileInfo(fileList: iFile[], dataType: number, dataId: string) {
+  const { data } = await request.post('/api/file', { fileList, dataType, dataId })
+  return data
+}

+ 48 - 0
src/pages/Quality/Content/Info/Detail/index.module.scss

@@ -0,0 +1,48 @@
+.detailContainer {
+  width: 100%;
+  margin: 0 auto;
+  .content {
+    flex: 0 0 66.666667%;
+    max-width: 66.666667%;
+    margin: 0 auto;
+    .header {
+      margin: 1rem 0;
+      font-size: 1.5rem;
+      font-weight: 500;
+      line-height: 1.2;
+      text-align: center;
+    }
+    .table {
+      width: 100%;
+      margin-bottom: 1rem;
+      color: #212529;
+      border-collapse: collapse;
+      border: 1px solid #dee2e6;
+      & > tbody td,
+      & > thead td {
+        padding: 0.3rem;
+        vertical-align: top;
+        border: 1px solid #dee2e6;
+      }
+      & > tbody th,
+      & > thead th {
+        padding: 0.3rem;
+        font-weight: normal;
+        color: #000000;
+        vertical-align: top;
+        background: #e9ecef;
+        border: 1px solid #dee2e6;
+      }
+
+      & > thead th {
+        vertical-align: bottom;
+        border-bottom: 2px solid #dee2e6;
+        border-bottom-width: 2px;
+      }
+
+      & > tbody th {
+        width: 150px;
+      }
+    }
+  }
+}

+ 173 - 0
src/pages/Quality/Content/Info/Detail/index.tsx

@@ -0,0 +1,173 @@
+import { ZhSubmitButton, ZhUploadButton } from '@/components/Button'
+import DatePicker from '@/components/DatePicker'
+import { apiGetFileList } from '@/components/FileModal/api'
+import Header from '@/components/Header'
+import Slot from '@/components/Header/slot'
+import OssUploadModal from '@/components/OssUpload'
+import SvgIcon from '@/components/SvgIcon'
+import { userStore } from '@/store/mobx'
+import { iFile } from '@/types/file'
+import consts from '@/utils/consts'
+import { dayjsFomrat } from '@/utils/util'
+import { Button, Input, Pagination, Tooltip } from 'antd'
+import locale from 'antd/es/date-picker/locale/zh_CN'
+import dayjs from 'dayjs'
+import React, { useEffect, useState } from 'react'
+import { RouteComponentProps } from 'react-router'
+import { apiGetSafeDetail, apiSaveFileInfo } from './api'
+import styles from './index.module.scss'
+const { TextArea } = Input
+interface iDetailState {
+  auditName: string
+  auditors: any[]
+  bidsectionId: string
+  code: string
+  createTime: string | undefined
+  demand: string
+  file: iFileState
+  id: string
+  inspection: string
+  inspectionDetail: string
+  position: string
+  status: number
+}
+interface iFileState {
+  fileList: any[]
+  total: number
+}
+const Detail:React.FC<RouteComponentProps> = (props) => {
+  const [ visible, setVisible ] = useState<boolean>(false)
+  const [ detail, setDetail ] = useState<iDetailState>({
+    auditName: "",
+    auditors: [],
+    bidsectionId: "",
+    code: "",
+    createTime: "",
+    demand: "",
+    file: { fileList: [], total: 0 },
+    id: "",
+    inspection: "",
+    inspectionDetail: "",
+    position: "",
+    status: 0
+  })
+
+  useEffect(() => {
+    initData()
+  }, [])
+  const initData = async() => {
+    const { saveId = "" } = props.location.state as any
+    const { code = -1, data = {} } = await apiGetSafeDetail(saveId)
+    if (code === consts.RET_CODE.SUCCESS) {
+      setDetail({ ...detail, ...data })
+    }
+  }
+  const onCreate = async (fileList: iFile[]) => {
+    const { code = -1 } = await apiSaveFileInfo(fileList, consts.DATA_TYPE.SAFE, detail.id)
+    if (code === consts.RET_CODE.SUCCESS) {
+      const newFiles = detail.file.fileList.concat(fileList.map(file => {
+        return { ...file, accountName: userStore.userInfo.name }
+      }))
+      setDetail({ ...detail, file: { ...detail.file, fileList: newFiles } })
+      setVisible(false)
+    }
+  }
+  const onShow = (show: boolean) => setVisible(show)
+  const fileListChange = async (pageNo: number = 1, pageSize: number = 10) => {
+    const { code = -1, data } = await apiGetFileList(consts.DATA_TYPE.SAFE,detail.id, pageNo, pageSize)
+    if (code === consts.RET_CODE.SUCCESS) {
+      setDetail({ ...detail, file: { ...detail.file, fileList: data } })
+    }
+  }
+  return (
+    <div className="wrap-contaniner">
+      <Header title="安全巡检">
+        <Slot position="right">
+          <div>
+            <ZhSubmitButton size="small">提交审批</ZhSubmitButton>
+          </div>
+        </Slot>
+      </Header>
+      <div className={styles.detailContainer}>
+        <div className={styles.content}>
+          <h4 className={styles.header}>{detail.code}</h4>
+          <table className="pi-table pi-bordered">
+            <thead>
+              <tr>
+                <th colSpan={2} className="pi-text-center">安全巡检单</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr><th style={{ width: "150px" }}>检查项目</th><td><TextArea value={detail.inspection}></TextArea></td></tr>
+              <tr><th style={{ width: "150px" }}>现场检查情况</th><td><TextArea value={detail.inspectionDetail}></TextArea></td></tr>
+              <tr><th style={{ width: "150px" }}>处理要求及措施</th><td><TextArea value={detail.demand}></TextArea></td></tr>
+              <tr>
+                <th style={{ width: "150px" }}>检查日期</th>
+                <td><DatePicker size="small" locale={locale} allowClear={false} value={dayjs(detail.createTime)} onChange={(value) => setDetail({ ...detail, createTime: value?.format() })}></DatePicker></td>
+              </tr>
+              <tr><th style={{ width: "150px" }}>质检员</th><td>{detail.auditName}</td></tr>
+            </tbody>
+
+          </table>
+          <table className="pi-table pi-bordered mt-3">
+            <thead>
+              <tr>
+                <th></th>
+                <th className="pi-text-center">附件</th>
+                <th className="pi-text-center">上传者</th>
+                <th className="pi-text-center" style={{ width: 200 }}>上传时间</th>
+                <th className="pi-text-center">操作</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr><td colSpan={5}><ZhUploadButton size="small" icon={<SvgIcon type="xxh-cloud-upload"/>} onClick={() => setVisible(true)}>上传附件</ZhUploadButton></td></tr>
+
+              {
+                detail.file.fileList?.map((file, idx) => {
+                  return (
+                    <tr key={idx}>
+                      <td className="pi-width-70">{idx+1}</td>
+                      <td style={{ width: 383, maxWidth: 383, overflow: 'hidden', whiteSpace: 'nowrap',textOverflow:'ellipsis' }}>
+                        <a href={consts.OSS_PATH.REVIEW + file.filepath} target="_blank" rel="noopener noreferrer">{file.filename}</a>
+                      </td>
+                      <td className="pi-text-center">{file.accountName}</td><td className="pi-text-center">{dayjsFomrat(file.createTime)}</td>
+                      <td className="pi-text-center pi-width-90">
+                        <Tooltip title="移除">
+                          <Button size="small" type="text" icon={<SvgIcon type="xxh-times-circle1"/>} style={{ color: "#df3f45" }}></Button>
+                        </Tooltip>
+                      </td>
+                    </tr>
+                  )
+                })
+              }
+              <tr><td colSpan={5} className="pi-text-right">
+              {
+                detail.file.total ?
+                <Pagination
+                defaultCurrent={1}
+                size="small"
+                pageSize={consts.PAGE_SIZE}
+                hideOnSinglePage={true}
+                total={detail.file.total}
+                onChange={(page, pageSize) => fileListChange(page, pageSize)}
+                >
+              </Pagination>
+              : ''
+              }
+              </td></tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+      <OssUploadModal
+        visible={visible}
+        onCancel={() => setVisible(false)}
+        onCreate={onCreate}
+        onShow={onShow}
+        >
+      </OssUploadModal>
+    </div>
+  )
+}
+
+export default Detail

+ 17 - 0
src/pages/Quality/Content/Info/Summary/index.scss

@@ -0,0 +1,17 @@
+.card {
+  height: 300px;
+  padding: 0.5rem;
+  background-color: #ffffff;
+  background-clip: border-box;
+  border: 1px solid rgba(0, 0, 0, 0.125);
+  border-radius: 0.25rem;
+  .card-title {
+    margin-bottom: 0.75rem;
+    font-size: 1rem;
+    font-weight: 500;
+    line-height: 1.2;
+  }
+}
+.card.h400 {
+  height: 400px;
+}

+ 46 - 0
src/pages/Quality/Content/Info/Summary/index.tsx

@@ -0,0 +1,46 @@
+import Header from '@/components/Header'
+import React from 'react'
+import { RouteProps } from 'react-router'
+import './index.scss'
+
+
+const Summary:React.FC<RouteProps> = (props) => {
+  // console.log(props.location?.state)
+
+  // useActivate(() => {
+  //   BidHander()
+  // })
+  // useEffect(() => {
+  //   BidHander()
+  // }, [])
+  // const BidHander = () => {
+  //   if (Object.keys(props.location?.state as object).length) {
+  //     // console.log(props.location?.state)
+
+  //     const { id = '', name = '' } = props.location?.state as {id: string;name: string}
+  //     id && (tenderStore.saveBidsectionId(id))
+  //     name && (tenderStore.saveName(name))
+  //   }
+  // }
+  return (
+    <div className="wrap-contaniner">
+      <Header title="巡检概况"></Header>
+      <div className="wrap-content m-3 pi-flex-column pi-justify-start">
+        <div className="pi-justify-start">
+          <div className="pi-flex-twice card pi-flex-column">
+            <header className="card-title">整改中 (23) </header>
+            <div>
+              <p>检查路面清洗,路面污染严重</p>
+              <p>检查路面清洗,路面污染严重</p>
+              <p>检查路面清洗,路面污染严重</p>
+              <p>检查路面清洗,路面污染严重</p>
+            </div>
+          </div>
+          <div className="pi-flex-treble pi-mg-left-30 card"></div>
+        </div>
+        <div className="card h-400 mt-3"></div>
+      </div>
+    </div>
+  )
+}
+export default Summary

+ 18 - 0
src/pages/Quality/Content/Info/index.tsx

@@ -0,0 +1,18 @@
+import Guards from '@/components/Navigation'
+import { NavigationGuardsProps } from '@/types/router'
+import React from 'react'
+import { Switch } from 'react-router-dom'
+const Content:React.FC<NavigationGuardsProps> = props => {
+  const { routeConfig, match, location } = props
+  return (
+    <>
+      <div className="panel-content">
+        <Switch>
+              <Guards routeConfig={routeConfig} match={match} location={location}></Guards>
+        </Switch>
+      </div>
+    </>
+  )
+}
+
+export default Content

+ 53 - 0
src/pages/Quality/Content/List/api.ts

@@ -0,0 +1,53 @@
+import { iRulePayload } from "@/types/rule"
+import { iCreateSafe } from "@/types/safe"
+import request from "@/utils/common/request"
+
+/**
+ * 获取当前标段的安全巡检列表
+ * @param bidsectionId 标段id
+ */
+export async function apiSafeList(bidsectionId: string, pageNo: number, pageSize: number) {
+  const { data } = await request.get('/api/safe', { bidsectionId, pageNo, pageSize })
+  return data
+}
+
+/**
+ * 创建新的安全巡检记录
+ * @param payload 载荷
+ */
+export async function apiCreateSafe(payload: iCreateSafe) {
+  const { data } = await request.post('/api/safe', payload)
+  return data
+}
+
+/**
+ * 获取当前的规则
+ * @param bidsectionId 标段id
+ */
+export async function apiGetRule(bidsectionId: string) {
+  const { data } = await request.get('/api/rule', { bidsectionId })
+  return data
+}
+
+/**
+ * 提交编号规则设置
+ * @param payload 载荷
+ */
+export async function apiSaveRule(payload: iRulePayload) {
+  const newValue = payload.rule.reduce((prev, curr) => {
+    prev[curr.type] = curr.value
+    return prev
+  }, {})
+  const { data } = await request.post('/api/rule', { ...payload, rule: JSON.stringify(newValue) })
+  return data
+}
+
+/**
+ *
+ * @param bidsectionId 标段id
+ * @param type 规则类型
+ */
+export async function apiAutoCode(bidsectionId: string, type: string) {
+  const { data } = await request.post("/api/rule/auto", { bidsectionId, type })
+  return data
+}

+ 39 - 0
src/pages/Quality/Content/List/index.module.scss

@@ -0,0 +1,39 @@
+.SafeModalForm {
+  position: relative;
+  top: 0;
+  left: 0;
+  :global(.ant-input-group-addon) {
+    padding: 0;
+    &:hover {
+      color: #ffffff;
+      background-color: #6c757d;
+      border-color: #6c757d;
+    }
+    &:not(:disabled) {
+      cursor: pointer;
+    }
+  }
+
+  .ruleContaniner {
+    margin: 0.5rem 0;
+
+    .ruleText {
+      margin-bottom: 0.5rem;
+    }
+  }
+
+  .position {
+    position: absolute;
+    top: 0;
+    right: 0;
+    z-index: 100;
+  }
+  .warningFooter {
+    padding: 0.5rem;
+    color: #856404;
+    background-color: #fff3cd;
+    border: 1px solid transparent;
+    border-color: #ffeeba;
+    border-radius: 0.25rem;
+  }
+}

+ 212 - 0
src/pages/Quality/Content/List/index.tsx

@@ -0,0 +1,212 @@
+import FileModal from '@/components/FileModal'
+import Header from '@/components/Header'
+import Slot from '@/components/Header/slot'
+import RuleModal from '@/components/RuleModal'
+import SvgIcon from '@/components/SvgIcon'
+import { tenderStore } from '@/store/mobx'
+import { iCreateSafe } from '@/types/safe'
+import { safeStatus } from '@/utils/common/constStatus'
+import consts from '@/utils/consts'
+import { dayjsFomrat } from '@/utils/util'
+import { SettingOutlined } from '@ant-design/icons'
+import { Button, message, Table } from 'antd'
+import { ColumnsType } from 'antd/lib/table'
+import React, { useEffect, useState } from 'react'
+import { Link } from 'react-router-dom'
+import { apiCreateSafe, apiSafeList, apiSaveRule } from './api'
+import AddModel from './modal'
+interface iSafeList {
+  id: string;
+  code: string;
+  createTime: string;
+  position: string;
+  inspection: string;
+  inspectionDetail: string;
+  demand: string;
+  status: number;
+  auditName: string;
+  fileCounts: number;
+}
+interface iModal {
+  visible: boolean
+  loading: boolean
+}
+
+interface iFileModal {
+  visible: boolean
+  dataType: number
+  dataId: string
+}
+const SafeList:React.FC<{}> =() => {
+  const [ ruleModal, setRuleModal ] = useState<iModal>({
+    visible: false,
+    loading: false
+  })
+  const [ addModal, setAddModal ] = useState<iModal>({
+    visible: false,
+    loading: false
+  })
+  const [ fileModal, setFileModal ] = useState<iFileModal>({
+    visible: false,
+    dataType: consts.DATA_TYPE.SAFE,
+    dataId: ''
+  })
+  useEffect(() => {
+    initData()
+  }, [])
+  const columns:ColumnsType<iSafeList> = [
+    {
+      title: '序号',
+      // eslint-disable-next-line react/display-name
+      render: (_: string, record: any, i: number) => {
+        return <span>{i + 1}</span>
+      }
+    },
+    {
+      title: '编号',
+      dataIndex: 'code',
+      // eslint-disable-next-line react/display-name
+      render: (text: string, record) => {
+        return <Link to={{ pathname: "/console/safe/content/detail/info", state: { saveId: record.id } }}>{text}</Link>
+      }
+    },
+    {
+      title: '检查项目',
+      dataIndex: 'inspection'
+    },
+    {
+      title: '现场检查情况',
+      dataIndex: 'inspection_detail'
+    },
+    {
+      title: '处理要求及措施',
+      dataIndex: 'demand'
+    },
+    {
+      title: '检查日期',
+      dataIndex: 'create_time',
+      // eslint-disable-next-line react/display-name
+      render: (text: string) => {
+        return <span>{dayjsFomrat(text, 'YYYY-MM-DD')}</span>
+      }
+    },
+    {
+      title: '检查人',
+      dataIndex: 'auditName'
+    },
+    {
+      title: '附件',
+      dataIndex: 'fileCounts',
+      // eslint-disable-next-line react/display-name
+      render: (text: string, record) => {
+      return <span className="pi-pointer" onClick={() => setFileModal({ ...fileModal, dataId: record.id, visible: true })}><SvgIcon type="xxh-paperclip1"></SvgIcon> {text}</span>
+      }
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      // eslint-disable-next-line react/display-name
+      render: (statu: number) => {
+      return <span className={safeStatus[statu].className}>{safeStatus[statu].text}</span>
+      }
+    }
+  ]
+  const [ list, setList ] = useState<iSafeList[]>([
+    {
+      id: '',
+      code: '',
+      createTime: '',
+      position: '',
+      inspection: '',
+      inspectionDetail: '',
+      demand: '',
+      status: 0,
+      auditName: '',
+      fileCounts: 0
+    }
+  ])
+  const [ total, setTotal ] = useState<number>(0)
+  const initData = (pageNo: number = 1, pageSize: number = consts.PAGE_SIZE) => {
+    apiSafeList(tenderStore.bid, pageNo, pageSize).then(({ code = -1, data = [], total = 0 }) => {
+      if (code === consts.RET_CODE.SUCCESS) {
+        setList(data)
+        setTotal(total)
+      }
+    }).catch(err => {
+      console.log(err)
+    })
+  }
+
+  const onRuleCreate = async (ruleValue: any) => {
+    setRuleModal({ ...ruleModal, loading: true })
+    const { code = -1 } = await apiSaveRule({ bidsectionId: tenderStore.bid, type: 'safe_rule', rule: ruleValue })
+    if (code === consts.RET_CODE.SUCCESS) {
+      message.success("规则更改成功!")
+      initData()
+    }
+    setRuleModal({ ...ruleModal, loading: false, visible: false })
+  }
+  const onAddCreate = async (payload: iCreateSafe) => {
+    setAddModal({ ...addModal, loading: true })
+    const createTime = dayjsFomrat(payload.createTime)
+    const { code = -1 } = await apiCreateSafe({ ...payload, createTime })
+    if (code === consts.RET_CODE.SUCCESS) {
+      initData()
+    }
+    setAddModal({ ...addModal, loading: false, visible: false })
+  }
+
+  return (
+    <div className="wrap-contaniner">
+      <Header title="巡检概况">
+        <Slot position="right">
+          {
+            !list.length ?
+            <Button type="ghost" size="small" icon={<SettingOutlined />} className="pi-mg-right-3" style={{ color: '#007bff' }} onClick={() => setRuleModal({ ...ruleModal, visible: true })}>设置</Button>
+            : ""
+          }
+
+          <Button type="primary" size="small" onClick={() => setAddModal({ ...addModal, visible: true })}>新建巡检</Button>
+        </Slot>
+      </Header>
+      <Table
+        dataSource={list}
+        columns={columns}
+        pagination={{
+          hideOnSinglePage: true,
+          size: "small",
+          pageSize: consts.PAGE_SIZE,
+          onChange: (page, pageSize) => initData(page, pageSize),
+          total
+        }}
+        rowKey={record => record.id}
+        bordered
+        >
+      </Table>
+      <RuleModal
+        type={consts.RULE.SAFE}
+        title="安全巡检编号设置"
+        visible={ruleModal.visible}
+        onCreate={onRuleCreate}
+        loading={ruleModal.loading}
+        onCancel={() => setRuleModal({ ...ruleModal, visible: false })}
+        >
+      </RuleModal>
+      <AddModel
+        visible={addModal.visible}
+        onCreate={onAddCreate}
+        loading={addModal.loading}
+        onCancel={() => setAddModal({ ...addModal, visible: false })}
+        >
+      </AddModel>
+      <FileModal
+        visible={fileModal.visible}
+        dataType={fileModal.dataType}
+        dataId={fileModal.dataId}
+        onCancel={() => setFileModal({ ...fileModal, visible: false })}
+      ></FileModal>
+    </div>
+  )
+}
+
+export default SafeList

+ 95 - 0
src/pages/Quality/Content/List/modal.tsx

@@ -0,0 +1,95 @@
+import DatePicker from '@/components/DatePicker'
+import { tenderStore } from '@/store/mobx'
+import consts from '@/utils/consts'
+import { Form, Input, Modal } from 'antd'
+import locale from 'antd/es/date-picker/locale/zh_CN'
+import React, { useEffect } from 'react'
+import { apiAutoCode } from './api'
+import styles from './index.module.scss'
+interface iSafeCreateFormProps {
+  visible: boolean;
+  loading: boolean;
+  onCreate: (values: any) => void;
+  onCancel: () => void;
+}
+
+const SafeCreateForm: React.FC<iSafeCreateFormProps> = ({
+  visible,
+  loading,
+  onCreate,
+  onCancel
+}) => {
+  const [ form ] = Form.useForm()
+  const autoCodeHandler = async () => {
+    const { code = -1, data = "" } = await apiAutoCode(tenderStore.bidsectionId, 'safeRule')
+    if (code === consts.RET_CODE.SUCCESS) {
+      if (data) {
+        const ruleArr: string[] = []
+        const code = JSON.parse(data)
+        for (const key in code) {
+          if (Object.prototype.hasOwnProperty.call(code, key)) {
+            const element = code[key]
+            if (element) {
+              ruleArr.push(element)
+            }
+          }
+        }
+        form.setFieldsValue({ code: ruleArr.join("-") })
+      }
+    }
+  }
+  useEffect(() => {
+    if (visible) {
+      form.setFieldsValue({ bidsectionId: tenderStore.bidsectionId })
+    }
+
+  }, [ visible ])
+  return (
+    <Modal
+      getContainer={false}
+      visible={visible}
+      title="新建安全巡检"
+      okText="确认添加"
+      cancelText="取消"
+      onCancel={onCancel}
+      confirmLoading={loading}
+      okButtonProps={{ size: 'small' }}
+      cancelButtonProps={{ size: 'small' }}
+      onOk={() => {
+        form
+          .validateFields()
+          .then((values) => {
+            form.resetFields()
+            onCreate(values)
+          })
+          .catch(info => {
+            console.log('Validate Failed:', info)
+          })
+      }}
+    >
+      <Form form={form} layout="vertical" size="middle" className={styles.SafeModalForm}>
+        <Form.Item name="bidsectionId" hidden>
+          <Input />
+        </Form.Item>
+        <span className={[ styles.position, "pi-link-blue" ].join(" ")}>部位设置</span>
+        <Form.Item name="position" label="部位" rules={[ { required: true, message: '请选择' } ]}>
+          {/* <span className={[ styles.position, "pi-link-blue" ].join(" ")}>部位设置</span> */}
+          <Input />
+        </Form.Item>
+        <Form.Item name="code" label="安全编号" rules={[ { required: true, message: '请输入/生成安全编号' } ]}>
+          <Input addonAfter={<span className="pi-pd-lr-11"onClick={() => autoCodeHandler()}>自动编号</span>}/>
+        </Form.Item>
+        <Form.Item name="inspection" label="检查项" rules={[ { required: true, message: '请填写检查项' } ]}>
+          <Input placeholder="请填写巡检项"/>
+        </Form.Item>
+        <Form.Item name="createTime" label="日期" rules={[ { required: true, message: '请选择日期' } ]}>
+          <DatePicker locale={locale} allowClear style={{ width: '100%' }}></DatePicker>
+        </Form.Item>
+        <div className={styles.warningFooter}>添加后再补充完善其余信息</div>
+      </Form>
+    </Modal>
+  )
+}
+
+
+export default SafeCreateForm

+ 20 - 0
src/pages/Quality/Content/index.tsx

@@ -0,0 +1,20 @@
+import LeftSide from '@/components/LeftSide'
+import Guards from '@/components/Navigation'
+import { NavigationGuardsProps } from '@/types/router'
+import React from 'react'
+import { Switch } from 'react-router-dom'
+const Content:React.FC<NavigationGuardsProps> = props => {
+  const { routeConfig, match, location } = props
+  return (
+    <>
+      <LeftSide childRoutes={routeConfig} location={location}></LeftSide>
+      <div className="panel-content">
+        <Switch>
+              <Guards routeConfig={routeConfig} match={match} location={location}></Guards>
+        </Switch>
+      </div>
+    </>
+  )
+}
+
+export default Content

+ 6 - 0
src/pages/Quality/List/api.ts

@@ -0,0 +1,6 @@
+import request from "@/utils/common/request"
+
+export async function apiContractList() {
+  const { data } = await request.get('/api/contract/folder')
+  return data
+}

+ 35 - 0
src/pages/Quality/List/index.module.scss

@@ -0,0 +1,35 @@
+.tableContent {
+  margin: 0;
+  font-size: 12px;
+
+  .treeBtn {
+    padding: 1px 0.6rem;
+    color: #007bff;
+    border: 1px solid #007bff;
+    border-radius: 0.2rem;
+    &:hover {
+      color: #ffffff;
+      background-color: #007bff;
+    }
+  }
+
+  // :global(.ant-table-thead .ant-table-cell) {
+  //   padding: 0.4rem;
+  //   text-align: center !important;
+  //   background-color: #e9ecef;
+  //   border-right: 1px solid #dee2e6;
+  //   border-bottom: 2px solid #dee2e6;
+  // }
+  // :global(.ant-table-tbody .ant-table-cell) {
+  //   padding: 0.4rem;
+  // }
+  // :global(.ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > thead > tr > th) {
+  //   border-right: 1px solid #dee2e6;
+  // }
+}
+
+.textBtn {
+  &:hover {
+    color: #007bff;
+  }
+}

+ 3 - 0
src/pages/Quality/List/index.scss

@@ -0,0 +1,3 @@
+.list-content {
+  width: calc(100vw - 55px);
+}

+ 144 - 0
src/pages/Quality/List/index.tsx

@@ -0,0 +1,144 @@
+import Header from '@/components/Header'
+import Slot from '@/components/Header/slot'
+import SvgIcon from '@/components/SvgIcon'
+import { tenderStore } from '@/store/mobx'
+import { ContractTree } from '@/types/contract'
+import consts from '@/utils/consts'
+import { CaretDownOutlined } from '@ant-design/icons'
+import { Button, Dropdown, Menu, message, Table } from 'antd'
+import { ColumnsType } from 'antd/lib/table'
+import React, { useEffect, useState } from 'react'
+import { useAliveController } from 'react-activation'
+import { RouteComponentProps, withRouter } from 'react-router-dom'
+import { apiContractList } from './api'
+import styles from './index.module.scss'
+import './index.scss'
+const List: React.FC<RouteComponentProps> = (props) => {
+  const { clear } = useAliveController()
+  useEffect(() => {
+    getTree()
+  }, [])
+  const [ tree, setTree ] = useState({
+    bidsectionId: '',
+    children: [],
+    childsTotal: 0,
+    contracts: 0,
+    contractsIncome: '',
+    contractsIncomeProgress: '',
+    contractsPaid: '',
+    contractsPay: '',
+    contractsPayProgress: '',
+    contractsReturned: '',
+    csrf: '',
+    hasFolder: false,
+    id: '',
+    isBid: false,
+    isEnd: false,
+    isfolder: 0,
+    name: '',
+    parentId: '',
+    projectId: ''
+  })
+  const getTree = async () => {
+    const { data, code = -1 } = await apiContractList()
+    if (code === consts.RET_CODE.SUCCESS) {
+      setTree(data)
+    }
+  }
+
+  const linkHandler = (id: string, name: string) => {
+    tenderStore.saveBidsectionId(id)
+    tenderStore.saveName(name)
+    props.history.push('/console/safe/content/summary')
+    // 清除所有的缓存页面
+    // console.log(getCachingNodes())
+
+    clear()
+  }
+  const columns:ColumnsType<ContractTree> = [
+    {
+      title: '名称',
+      dataIndex: 'name',
+      key: 'name',
+      width: '25%',
+      render: (text: string, record: ContractTree) => {
+        if (record.isfolder) {
+          return <div style={{ verticalAlign: "baseline" }}><SvgIcon type={record.hasFolder ? "xxh-folder-open" : "xxh-folder"} /><span className="pi-mg-left-2">{text}</span></div>
+        } else {
+        return <div><span style={{ color: '#6c757d', marginRight: '.5rem' }}>{record.isEnd ? '└' : '├'}</span>
+          {/* <Link to={{ pathname: '/console/safe/content/summary', search: `?bid=${record.id}` }}>{text}</Link> */}
+          <span className="pi-link-blue" onClick={() => linkHandler(record.bidsectionId, record.name)}>{text}</span>
+        </div>
+        }
+      }
+    },
+    {
+      title: '合同总数',
+      dataIndex: 'contracts',
+      key: 'contracts',
+      width: '12%',
+      align: 'right'
+    },
+    {
+      title: '收入合同金额',
+      dataIndex: 'contractsIncome',
+      key: 'contractsIncome',
+      width: '18%',
+      align: 'right'
+    },
+    {
+      title: '回款进度',
+      dataIndex: 'contractsIncomeProgress',
+      key: 'contractsIncomeProgress',
+      width: '12%',
+      align: 'center'
+    },
+    {
+      title: '支出合同金额',
+      dataIndex: 'contractsPay',
+      key: 'contractsPay',
+      width: '18%',
+      align: 'right'
+    },
+    {
+      title: '支付进度',
+      dataIndex: 'contractsPayProgress',
+      key: 'contractsPayProgress',
+      width: '12%',
+      align: 'center'
+    }
+  ]
+  const handleMenuClick = ({ key }: any) => {
+    message.info(`Click on item ${key}`)
+  }
+  const menu = (
+    <Menu onClick={handleMenuClick}>
+      <Menu.Item key="1">展开所有</Menu.Item>
+      <Menu.Item key="2">收起所有</Menu.Item>
+    </Menu>
+  )
+
+  return (
+    <div className="list-content">
+      <Header>
+        <Slot>
+          <Dropdown overlay={menu}>
+            <Button type="text" size="small" className={styles.textBtn}>展开/收起<CaretDownOutlined /></Button>
+          </Dropdown>
+        </Slot>
+      </Header>
+      <div className={styles.tableContent}>
+        <Table<ContractTree>
+          columns={columns}
+          dataSource={tree.children}
+          pagination={false}
+          rowKey={record => record.id}
+          indentSize={20}
+          bordered
+        >
+        </Table>
+      </div>
+    </div>
+  )
+}
+export default withRouter(List)

+ 15 - 10
src/pages/Quality/index.tsx

@@ -1,11 +1,16 @@
-import React, { Component } from 'react'
-export default class index extends Component {
-  render() {
-    return (
-      <div className="pi-flex-row">
-        {/* <Meun></Meun> */}
-        <h1>质量巡检</h1>
-      </div>
-    )
-  }
+import Guards from '@/components/Navigation'
+import { NavigationGuardsProps } from '@/types/router'
+import React from 'react'
+import { Switch } from 'react-router-dom'
+const Contract:React.FC<NavigationGuardsProps> = props => {
+  const { routeConfig, match, location } = props
+  return (
+    <>
+      <Switch>
+            <Guards routeConfig={routeConfig} match={match} location={location}></Guards>
+        </Switch>
+    </>
+  )
 }
+
+export default Contract

+ 0 - 2
src/pages/Safe/List/index.tsx

@@ -51,8 +51,6 @@ const List: React.FC<RouteComponentProps> = (props) => {
     tenderStore.saveName(name)
     props.history.push('/console/safe/content/summary')
     // 清除所有的缓存页面
-    // console.log(getCachingNodes())
-
     clear()
   }
   const columns:ColumnsType<ContractTree> = [

+ 54 - 3
src/router/routes.ts

@@ -97,7 +97,10 @@ export const routeConfig: RouteModel[] = [
             path: 'list',
             component: AsyncModuleLoader(() => import('@/pages/Safe/List')),
             defaultChildRoute: true,
-            auth: [ 'USER', 'ADMIN' ]
+            auth: [ 'USER', 'ADMIN' ],
+            meta: {
+              noCache: true
+            }
           },
           {
             path: 'content',
@@ -135,7 +138,6 @@ export const routeConfig: RouteModel[] = [
                   }
                 ]
               }
-
             ]
           }
         ]
@@ -149,7 +151,56 @@ export const routeConfig: RouteModel[] = [
           title: '质量巡检',
           isTop: true,
           sort: 4
-        }
+        },
+        childRoutes:[
+          {
+            path: 'list',
+            component: AsyncModuleLoader(() => import('@/pages/Quality/List')),
+            defaultChildRoute: true,
+            auth: [ 'USER', 'ADMIN' ],
+            meta: {
+              noCache: true
+            }
+          },
+          {
+            path: 'content',
+            component: AsyncModuleLoader(() => import('@/pages/Quality/Content')),
+            auth: [ 'USER', 'ADMIN' ],
+            childRoutes: [
+              {
+                path: 'summary',
+                component: AsyncModuleLoader(() => import('@/pages/Quality/Content/Info/Summary')),
+                auth: [ 'USER', 'ADMIN' ],
+                defaultChildRoute: true,
+                meta: {
+                  icon: 'xxh-chart-pie',
+                  title: '巡检概况'
+                }
+              },
+              {
+                path: "detail",
+                component: AsyncModuleLoader(() => import('@/pages/Quality/Content/Info')),
+                auth: [ 'USER', 'ADMIN' ],
+                meta: {
+                  title: '安全巡检'
+                },
+                childRoutes: [
+                  {
+                    path: 'list',
+                    component: AsyncModuleLoader(() => import('@/pages/Quality/Content/List')),
+                    auth: [ 'USER', 'ADMIN' ],
+                    defaultChildRoute: true
+                  },
+                  {
+                    path: 'info',
+                    component: AsyncModuleLoader(() => import('@/pages/Quality/Content/Info/Detail')),
+                    auth: [ 'USER', 'ADMIN' ]
+                  }
+                ]
+              }
+            ]
+          }
+        ]
       },
       {
         path: 'management',