浏览代码

feat: 安全巡检列表及弹窗组件

lanjianrong 4 年之前
父节点
当前提交
fdd3fa73d3

+ 52 - 0
src/components/Button/index.module.scss

@@ -0,0 +1,52 @@
+.primaryButton {
+  :global(.ant-btn) {
+    color: #007bff;
+    border-color: #007bff;
+  }
+  :global(.ant-btn:hover) {
+    color: #ffffff;
+    background-color: #007bff;
+    border-color: #0062d9;
+  }
+  :global(.ant-btn:focus) {
+    color: #ffffff;
+    background-color: #007bff;
+    border-color: #0062d9;
+  }
+  :global(.ant-btn:active) {
+    color: #ffffff;
+    background-color: #007bff;
+    border-color: #0062d9;
+  }
+
+  :global(.ant-btn:not(:hover)) {
+    color: #007bff;
+    background-color: #ffffff;
+    border-color: #007bff;
+  }
+}
+
+.closeButton {
+  :global(.ant-btn) {
+    color: #ffffff;
+    background-color: #6c757d;
+    border-color: #6c757d;
+  }
+  :global(.ant-btn:hover) {
+    background-color: #5a6268;
+    border-color: #545b62;
+  }
+  :global(.ant-btn:focus) {
+    background-color: #5a6268;
+    border-color: #545b62;
+  }
+  :global(.ant-btn:active) {
+    background-color: #5a6268;
+    border-color: #545b62;
+  }
+
+  :global(.ant-btn:not(:hover)) {
+    background-color: #6c757d;
+    border-color: #6c757d;
+  }
+}

+ 16 - 0
src/components/Button/index.tsx

@@ -0,0 +1,16 @@
+import { Button } from 'antd'
+import { BaseButtonProps } from 'antd/lib/button/button'
+import React from 'react'
+import styles from './index.module.scss'
+const ZhButton:React.FC<BaseButtonProps> = props => {
+  return <div className={styles.primaryButton}><Button {...props}></Button></div>
+}
+
+const ZhCloseButton:React.FC<BaseButtonProps> = props => {
+  return <div className={styles.closeButton}><Button {...props}></Button></div>
+}
+
+export {
+  ZhButton,
+  ZhCloseButton
+}

+ 1 - 1
src/components/DatePicker.tsx

@@ -1,7 +1,7 @@
 import generatePicker from 'antd/es/date-picker/generatePicker'
 import 'antd/es/date-picker/style/index'
 import { Dayjs } from 'dayjs'
-import 'dayjs/locale/zh-cn'
+// import 'dayjs/locale/zh-cn'
 import dayjsGenerateConfig from 'rc-picker/lib/generate/dayjs'
 
 const DatePicker = generatePicker<Dayjs>(dayjsGenerateConfig)

+ 51 - 1
src/components/LeftSide/index.scss

@@ -3,7 +3,7 @@
   left: 55px;
   display: flex;
   flex-direction: column;
-  justify-content: flex-start;
+  justify-content: space-between;
   width: 120px;
   height: 100vh;
   background: #fbfcfd;
@@ -22,3 +22,53 @@
     }
   }
 }
+
+.scale-out-hor-left {
+  -webkit-animation: scale-out-hor-left 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53) both;
+  animation: scale-out-hor-left 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53) both;
+}
+
+/* ----------------------------------------------
+ * Generated by Animista on 2020-11-25 14:13:57
+ * Licensed under FreeBSD License.
+ * See http://animista.net/license for more info.
+ * w: http://animista.net, t: @cssanimista
+ * ---------------------------------------------- */
+
+/**
+ * ----------------------------------------
+ * animation scale-out-hor-left
+ * ----------------------------------------
+ */
+@-webkit-keyframes scale-out-hor-left {
+  0% {
+    opacity: 1;
+    -webkit-transform: scaleX(1);
+    transform: scaleX(1);
+    -webkit-transform-origin: 0 0;
+    transform-origin: 0 0;
+  }
+  100% {
+    opacity: 1;
+    -webkit-transform: scaleX(0);
+    transform: scaleX(0);
+    -webkit-transform-origin: 0 0;
+    transform-origin: 0 0;
+  }
+}
+
+@keyframes scale-out-hor-left {
+  0% {
+    -webkit-transform: scaleX(1);
+    transform: scaleX(1);
+    -webkit-transform-origin: 0 0;
+    transform-origin: 0 0;
+  }
+  100% {
+    display: none;
+    -webkit-transform: scaleX(0);
+    transform: scaleX(0);
+    -webkit-transform-origin: 0 0;
+    transform-origin: 0 0;
+  }
+}

+ 18 - 8
src/components/LeftSide/index.tsx

@@ -1,5 +1,7 @@
-import { tenderStore } from '@/store/mobx'
+import SvgIcon from '@/components/SvgIcon'
+import { tenderStore, userStore } from '@/store/mobx'
 import { iNavSide } from '@/types/router'
+import { observer } from 'mobx-react'
 import React from 'react'
 import { Link } from 'react-router-dom'
 import "./index.scss"
@@ -10,15 +12,23 @@ const leftSide:React.FC<iNavSide> = ({ childRoutes, location }) => {
   }
   const pathname = location.pathname
   return (
-    <div className="panel-sidebar">
-      <div className="pi-pd-10 pi-mg-bottom-10 sidebar-title">项目设置</div>
-      <div className="pi-flex-column pi-justify-start">
-        { childRoutes.map((item, idx) =>
-          item.meta && <Link key={idx} to={{ pathname: item.path, state: { id: location.state?.id } }} className={pathname.indexOf(item.path) !== -1 ? 'nav-link active' : 'nav-link'}>{item.meta.title}</Link>
-        )}
+    <div className={userStore.showLeftSide ? "panel-sidebar" : "scale-out-hor-left"}>
+      <div>
+        <div className="pi-pd-10 pi-mg-bottom-10 sidebar-title">项目设置</div>
+        <div className="pi-flex-column pi-justify-start">
+          { childRoutes.map((item, idx) =>
+            item.meta && <Link key={idx} to={{ pathname: item.path, state: { id: location.state?.id } }} className={pathname.indexOf(item.path) !== -1 ? 'nav-link active' : 'nav-link'}>{item.meta.icon ? <SvgIcon type={item.meta.icon}></SvgIcon> : ''} {item.meta.title}</Link>
+          )}
+        </div>
       </div>
+      <div></div>
+      {/* <div className="pi-text-center pi-pd-10 pi-pointer" onClick={() => userStore.toggleLeftSide()}>
+        <Tooltip title="折叠侧栏">
+          <SvgIcon type="xxh-sign-out" style={{ color: '#007bff', fontSize: 24 }}></SvgIcon>
+        </Tooltip>
+      </div> */}
     </div>
   )
 }
 
-export default leftSide
+export default observer(leftSide)

+ 7 - 7
src/components/Menu/index.scss

@@ -1,7 +1,7 @@
 .main-nav {
   z-index: 99;
-  height: 100%;
   width: 55px;
+  height: 100%;
   background-color: #33425b;
   .logo {
     width: 55px;
@@ -13,19 +13,19 @@
     justify-content: space-between;
     height: calc(100% - 55px);
     .nav-top {
-      padding: 0;
-      margin: 0;
       display: flex;
       flex-direction: column;
-      justify-content: center;
       align-items: center;
+      justify-content: center;
+      padding: 0;
+      margin: 0;
     }
     a {
-      width: 100%;
       display: flex;
       flex-direction: column;
-      justify-content: center;
       align-items: center;
+      justify-content: center;
+      width: 100%;
       padding: 12px 0;
       text-align: center;
       &::first-child {
@@ -41,8 +41,8 @@
         }
       }
       &.active {
-        background-color: #192948;
         color: #7786ab;
+        background-color: #192948;
       }
     }
   }

+ 17 - 0
src/layout/NavSide/index.module.scss

@@ -12,4 +12,21 @@
   :global(.ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > thead > tr > th) {
     border-right: 1px solid #dee2e6;
   }
+  :global(.ant-modal-body) {
+    padding: 1rem;
+  }
+  :global(.ant-form-item) {
+    margin-bottom: 0.5rem;
+  }
+  :global(.ant-form-vertical .ant-form-item-label) {
+    padding: 0 0 0.3rem;
+  }
+  :global(.ant-modal-header) {
+    padding: 0.5rem 1rem;
+  }
+  :global(.ant-modal-close-x) {
+    width: 39px;
+    height: 39px;
+    line-height: 39px;
+  }
 }

+ 15 - 0
src/layout/RightContent/index.tsx

@@ -0,0 +1,15 @@
+import { userStore } from '@/store/mobx'
+import { observer } from 'mobx-react'
+import React, { PropsWithChildren } from 'react'
+
+const RightContent: React.FC<PropsWithChildren<{}>> = ({ children }) => {
+  return (
+    <>
+      <div className={userStore.showLeftSide ? "content-wrap" : "content-wrap hide"}>
+        {children}
+      </div>
+    </>
+  )
+}
+
+export default observer(RightContent)

+ 1 - 0
src/pages/Contract/Content/Income/components/Modal/index.tsx

@@ -31,6 +31,7 @@ const ContractModal: React.FC<iModalCommonProps> = ({ modalObj: { type, visible,
   }
   return (
     <Modal
+      getContainer={false}
       visible={visible}
       title={modalObj[type]?.title}
       onCancel={onCancel}

+ 1 - 1
src/pages/Contract/Content/Income/index.tsx

@@ -76,7 +76,7 @@ export default function Income() {
     }
   }
   return (
-    <div className="content-wrap">
+    <div className="wrap-contaniner">
       <Header title="维护项目节:">
         <Slot position="left">
           <div className="pi-flex-row">

+ 1 - 1
src/pages/Management/Info/index.tsx

@@ -5,7 +5,7 @@ import React from 'react'
 
 export default function Info() {
   return (
-    <div className="content-wrap">
+    <div className="wrap-contaniner">
       <Header title="项目信息">
         <Slot position="right">
           <Button type="primary" size="small">保存修改</Button>

+ 1 - 1
src/pages/Management/Setting/index.tsx

@@ -150,7 +150,7 @@ export default function Info() {
     }
   ]
   return (
-    <div className="content-wrap">
+    <div className="wrap-contaniner">
       <Header>
         <Slot position="right">
           <Button type="primary" size="small" onClick={() => {

+ 1 - 1
src/pages/Management/Tender/index.tsx

@@ -164,7 +164,7 @@ const Tender: React.FC<{}> = () =>{
     }
   }
   return (
-    <div className="content-wrap">
+    <div className="wrap-contaniner">
       <Header title="标段管理">
         <Slot position="right">
           <Button type="primary" size="small" icon={<FolderAddFilled />} onClick={() => treeBtnClick({ type: 'root', id: tree.id })}>新建文件夹</Button>

+ 9 - 0
src/pages/Safe/Content/Detail/index.tsx

@@ -0,0 +1,9 @@
+import React from 'react'
+
+export default function index() {
+  return (
+    <div>
+      巡检审批详情页
+    </div>
+  )
+}

+ 21 - 0
src/pages/Safe/Content/List/api.ts

@@ -0,0 +1,21 @@
+import { iCreateSafe } from "@/types/safe"
+import request from "@/utils/common/request"
+
+/**
+ * 获取当前标段的安全巡检列表
+ * @param bidsectionId 标段id
+ */
+export async function apiSafeList(bidsectionId: string) {
+  const { data } = await request.get('/api/safe', { bidsectionId })
+  return data
+}
+
+/**
+ * 创建新的安全巡检记录
+ * @param payload 载荷
+ */
+export async function apiCreateSafe(payload: iCreateSafe) {
+  const { data } = await request.post('/api/safe', payload)
+  return data
+}
+

+ 21 - 0
src/pages/Safe/Content/List/index.module.scss

@@ -0,0 +1,21 @@
+.SafeModalForm {
+  :global(.ant-input-group-addon) {
+    &:hover {
+      color: #ffffff;
+      background-color: #6c757d;
+      border-color: #6c757d;
+    }
+    &:not(:disabled) {
+      cursor: pointer;
+    }
+  }
+
+  .warningFooter {
+    padding: 0.5rem;
+    color: #856404;
+    background-color: #fff3cd;
+    border: 1px solid transparent;
+    border-color: #ffeeba;
+    border-radius: 0.25rem;
+  }
+}

+ 163 - 0
src/pages/Safe/Content/List/index.tsx

@@ -0,0 +1,163 @@
+import Header from '@/components/Header'
+import Slot from '@/components/Header/slot'
+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, Table } from 'antd'
+import { ColumnsType } from 'antd/lib/table'
+import React, { useEffect, useState } from 'react'
+import { useActivate } from 'react-activation'
+import { Link } from 'react-router-dom'
+import { apiCreateSafe, apiSafeList } from './api'
+import Modal 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 {
+  type: string
+  visible: boolean
+  loading: boolean
+}
+
+const SafeList:React.FC<{}> =() => {
+  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) => {
+        return <Link to={{ pathname: "/console/safe/content/detail", state: { id: tenderStore.bidsectionId } }}>{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) => {
+      return <span><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
+    }
+  ])
+  useEffect(() => {
+    initData()
+  }, [])
+  useActivate(() => {
+    initData()
+  })
+  const initData = async () => {
+    const { code = -1, data = [] } = await apiSafeList(tenderStore.bidsectionId)
+    if (code === consts.RET_CODE.SUCCESS) {
+      setList(data)
+    }
+  }
+  const [ modal, setModal ] = useState<iModal>({
+    type: '',
+    visible: false,
+    loading: false
+  })
+  const onCreate = async (payload: iCreateSafe) => {
+    setModal({ ...modal, loading: true })
+    const createTime = dayjsFomrat(payload.createTime)
+
+    const { code = -1 } = await apiCreateSafe({ ...payload, createTime })
+    if (code === consts.RET_CODE.SUCCESS) {
+      initData()
+    }
+    setModal({ ...modal, loading: false, visible: false })
+  }
+  return (
+    <div className="wrap-contaniner">
+      <Header title="巡检概况">
+        <Slot position="right">
+          <Button type="ghost" size="small" icon={<SettingOutlined />} className="pi-mg-right-3" style={{ color: '#007bff' }} onClick={() => setModal({ ...modal, visible: true,type: 'setting' })}>设置</Button>
+          <Button type="primary" size="small" onClick={() => setModal({ ...modal, visible: true,type: 'add' })}>新建巡检</Button>
+        </Slot>
+      </Header>
+      <Table
+        dataSource={list}
+        columns={columns}
+        pagination={false}
+        rowKey={record => record.id}
+        bordered
+        >
+      </Table>
+      <Modal
+        visible={modal.visible}
+        type={modal.type}
+        onCreate={onCreate}
+        loading={modal.loading}
+        onCancel={() => setModal({ ...modal, visible: false })}
+        >
+      </Modal>
+    </div>
+  )
+}
+
+export default SafeList

+ 123 - 0
src/pages/Safe/Content/List/modal.tsx

@@ -0,0 +1,123 @@
+import { ZhButton } from '@/components/Button'
+import DatePicker from '@/components/DatePicker'
+import { tenderStore } from '@/store/mobx'
+import { Form, Input, InputNumber, Modal, Select, Tabs } from 'antd'
+import locale from 'antd/es/date-picker/locale/zh_CN'
+import React, { useEffect, useState } from 'react'
+import styles from './index.module.scss'
+const { TabPane } = Tabs
+const { Option } = Select
+interface iSafeCreateFormProps {
+  visible: boolean;
+  type: string;
+  loading: boolean;
+  onCreate: (values: any) => void;
+  onCancel: () => void;
+}
+
+const SafeCreateForm: React.FC<iSafeCreateFormProps> = ({
+  visible,
+  loading,
+  type,
+  onCreate,
+  onCancel
+}) => {
+  const [ form ] = Form.useForm()
+  const [ ruleType, setRuleType ] = useState<string>('3')
+
+  useEffect(() => {
+    form.setFieldsValue({ bidsectionId: tenderStore.bidsectionId })
+  }, [ tenderStore.bidsectionId ])
+  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}>
+        {
+          type === 'add' ?
+          <>
+            <Form.Item name="bidsectionId" hidden>
+              <Input />
+            </Form.Item>
+            <Form.Item
+              name="position"
+              label="部位"
+              rules={[ { required: true, message: '请选择' } ]}
+            >
+              <Input />
+            </Form.Item>
+            <Form.Item name="code" label="安全编号" rules={[ { required: true, message: '请输入/生成安全编号' } ]}>
+              <Input addonAfter={<span>自动编号</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>
+          </>
+          :
+          <>
+            <Tabs defaultActiveKey="1" type="card" size="small">
+              <TabPane tab="编号规则设置" key="1">
+                <Form.Item label="添加新组建规则" name="ruleType" initialValue="3">
+                  <Select onChange={(value: string) => setRuleType(value)}>
+                    <Option value="0">标段名</Option>
+                    <Option value="1">文本</Option>
+                    <Option value="2">当前年月</Option>
+                    <Option value="3">自动编号</Option>
+                  </Select>
+                </Form.Item>
+                {
+                  ruleType === '3' ?
+                    <>
+                      <Form.Item label="自动编号位数" name="digits" initialValue="3">
+                        <InputNumber size="small" style={{ width: '100%' }}></InputNumber>
+                      </Form.Item>
+                      <Form.Item label="起始编号" name="initCode" initialValue="001">
+                        <Input></Input>
+                      </Form.Item>
+                    </>
+                  : ''
+                }
+                {
+                  ruleType === '1' ?
+                  <>
+                    <Form.Item label="规则文本" name="ruleText">
+                      <Input></Input>
+                    </Form.Item>
+                  </>
+                  : ''
+                }
+                <ZhButton size="small">添加组件</ZhButton>
+              </TabPane>
+              <TabPane tab="部位设置" key="2"></TabPane>
+            </Tabs>
+          </>
+        }
+      </Form>
+    </Modal>
+  )
+}
+
+export default SafeCreateForm

+ 17 - 0
src/pages/Safe/Content/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;
+}

+ 47 - 0
src/pages/Safe/Content/Summary/index.tsx

@@ -0,0 +1,47 @@
+import Header from '@/components/Header'
+import { tenderStore } from '@/store/mobx'
+import React, { useEffect } from 'react'
+import { useActivate } from 'react-activation'
+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 = "" } = props.location?.state as {id: string}
+      id && (tenderStore.saveBidsectionId(id))
+    }
+  }
+  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

+ 20 - 0
src/pages/Safe/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/Safe/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/Safe/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/Safe/List/index.scss

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

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

@@ -0,0 +1,130 @@
+import Header from '@/components/Header'
+import Slot from '@/components/Header/slot'
+import SvgIcon from '@/components/SvgIcon'
+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 { Link } from 'react-router-dom'
+import { apiContractList } from './api'
+import styles from './index.module.scss'
+import './index.scss'
+const List: React.FC<{}> = () => {
+  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 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', state: { id: record.bidsectionId } }}>{text}</Link>
+        </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 List

+ 15 - 11
src/pages/Safe/index.tsx

@@ -1,12 +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

+ 40 - 2
src/router/routes.ts

@@ -91,14 +91,52 @@ export const routeConfig: RouteModel[] = [
           title: '安全巡检',
           isTop: true,
           sort: 3
-        }
+        },
+        childRoutes:[
+          {
+            path: 'list',
+            component: AsyncModuleLoader(() => import('@/pages/Safe/List')),
+            defaultChildRoute: true,
+            auth: [ 'USER', 'ADMIN' ]
+          },
+          {
+            path: 'content',
+            component: AsyncModuleLoader(() => import('@/pages/Safe/Content')),
+            auth: [ 'USER', 'ADMIN' ],
+            childRoutes: [
+              {
+                path: 'summary',
+                component: AsyncModuleLoader(() => import('@/pages/Safe/Content/Summary')),
+                auth: [ 'USER', 'ADMIN' ],
+                defaultChildRoute: true,
+                meta: {
+                  icon: 'xxh-chart-pie',
+                  title: '巡检概况'
+                }
+              },
+              {
+                path: 'list',
+                component: AsyncModuleLoader(() => import('@/pages/Safe/Content/List')),
+                auth: [ 'USER', 'ADMIN' ],
+                meta: {
+                  title: '安全巡检'
+                }
+              },
+              {
+                path: 'detail',
+                component: AsyncModuleLoader(() => import('@/pages/Safe/Content/Detail')),
+                auth: [ 'USER', 'ADMIN' ]
+              }
+            ]
+          }
+        ]
       },
       {
         path: 'quality',
         component: AsyncModuleLoader(() => import('@/pages/Quality')),
         auth: [ 'USER', 'ADMIN' ],
         meta: {
-          icon: 'xxh-users-cog',
+          icon: 'xxh-toolbox',
           title: '质量巡检',
           isTop: true,
           sort: 4

+ 3 - 3
src/store/mobx/frame/index.ts

@@ -52,9 +52,9 @@ class RouterState {
         this.defaultRouteMapping = defaultRouteMapping
     }
 
-    @observable routeNameMapping: Map<string, string> = new Map();
-    @observable defaultRouteMapping: Map<string, string> = new Map();
-    @observable handledRouteConfig: RouteModel | RouteModel[] = [];
+    @observable routeNameMapping: Map<string, string> = new Map()
+    @observable defaultRouteMapping: Map<string, string> = new Map()
+    @observable handledRouteConfig: RouteModel | RouteModel[] = []
     @observable currentRoutePath!: string;
 
     @action setRouteNameMapping(key: string, value: string) {

+ 6 - 0
src/store/mobx/user/index.ts

@@ -28,6 +28,8 @@ class UserState {
 
   @observable permission: [] = []
 
+  @observable showLeftSide: boolean = true
+
   @computed get isLogin() {
     return !!this.userInfo.id
   }
@@ -45,6 +47,10 @@ class UserState {
     })
   }
 
+  @action toggleLeftSide() {
+    this.showLeftSide = !this.showLeftSide
+  }
+
   @action logout() {
     apiLogout().then(({ code = -1 }) => {
       if (code === consts.RET_CODE.SUCCESS) {

+ 7 - 0
src/types/safe.d.ts

@@ -0,0 +1,7 @@
+export interface iCreateSafe {
+  bidsectionId: string;
+  code: string;
+  createTime: string;
+  inspection: string
+  position: string
+}

+ 30 - 2
src/utils/common/constStatus.ts

@@ -1,4 +1,4 @@
-const contractConsts: object = {
+const contractConsts = {
   0: {
     className: 'pi-circle-yellow',
     text: '履行中'
@@ -13,8 +13,36 @@ const contractConsts: object = {
   }
 }
 
+const safeStatus = {
+  0: {
+    className: '',
+    text: '未提交'
+  },
+  1: {
+    className: 'pi-circle-yellow',
+    text: '审批中'
+  },
+  2: {
+    className: 'pi-circle-yellow',
+    text: '待整改'
+  },
+  3: {
+    className: 'pi-circle-yellow',
+    text: '待复查'
+  },
+  4: {
+    className: 'pi-circle-green',
+    text: '完成'
+  },
+  5: {
+    className: 'pi-circle-red',
+    text: '关闭'
+  }
+}
 const contractTreeBaseId: string = 'xCi4xUL6uur0h7fVI--NeA'
 export {
   contractConsts,
-  contractTreeBaseId
+  contractTreeBaseId,
+  safeStatus
 }
+

+ 2 - 2
src/utils/consts.ts

@@ -6,9 +6,9 @@ export default {
     PROD: ''
   },
   TOKEN_API: '', // 获取token
-  TOKEN_INVALID_CODE: [ 2, 3 ], // 接口返回码如果是2/3 则表明token过期或无效 需要自动刷新token
+  TOKEN_INVALID_CODE: [ 1 ], // 接口返回码如果是1 则表明token过期或无效 需要重新登录
   TOKEN_WHITE_LIST: [ '/api/login' ], // 不需要设置token的白名单
-  RET_CODE: { SUCCESS: 0, FAIL: 1, TOKEN_UNDEFINED: 19, TOKEN_EXPIRED: 2 }, // 接口返回状态码
+  RET_CODE: { SUCCESS: 0, FAIL: 1, TOKEN_UNDEFINED: 19, TOKEN_EXPIRED: 1 }, // 接口返回状态码
   RETRY: { COUNT: 3, DELAY: 1000 } // 请求重试次数/间隙
 
 }