فهرست منبع

feat: 审批详情页审批组件ok

lanjianrong 4 سال پیش
والد
کامیت
74f726b2b3

+ 1 - 0
package.json

@@ -184,6 +184,7 @@
     "mobx": "^5.15.4",
     "mobx-react": "^6.1.7",
     "nprogress": "^0.2.0",
+    "rc-queue-anim": "^1.8.5",
     "rc-tween-one": "^2.7.3",
     "react": "^17.0.1",
     "react-activation": "^0.7.0",

+ 17 - 0
src/assets/css/common.scss

@@ -15,6 +15,14 @@
   background-color: $pi-red;
 }
 
+.pi-bg-yellow {
+  background-color: $pi-yellow;
+}
+
+.pi-bg-green {
+  background-color: $pi-green;
+}
+
 .pi-gray {
   color: $pi-gray;
 }
@@ -26,6 +34,15 @@
 .pi-blue {
   color: $pi-blue;
 }
+
+.pi-green {
+  color: $pi-green;
+}
+
+.pi-yellow {
+  color: $pi-yellow;
+}
+
 .pi-link-blue {
   color: $pi-blue;
   &:hover {

+ 30 - 0
src/components/AuditContent/api.ts

@@ -0,0 +1,30 @@
+import { iAccountGroup, iAccountGroupItem, iUserInfo } from "@/types/setting"
+import request from "@/utils/common/request"
+
+/**
+ * 获取用户分组列表
+ */
+export async function apiGetGroup(name: string = "") {
+  const { data } = await request.get("/api/projectAccount/group", { name })
+  const { roleDate = {}, accountData = [] } = data
+  const newData: iAccountGroup = {}
+  for (const key in roleDate) {
+    if (Object.prototype.hasOwnProperty.call(roleDate, key)) {
+      const element = roleDate[key]
+      newData[key] = { value: element, children: [] }
+    }
+  }
+  accountData.forEach((account: iUserInfo) => {
+    if (newData[account.accountGroup]) {
+      newData[account.accountGroup].children.push(account)
+    }
+  })
+  const toArray: iAccountGroupItem[] = []
+  for (const key in newData) {
+    if (Object.prototype.hasOwnProperty.call(newData, key)) {
+      const item = newData[key]
+      item && (toArray.push(item))
+    }
+  }
+  return { code: data.code, data: toArray }
+}

+ 250 - 0
src/components/AuditContent/index.scss

@@ -0,0 +1,250 @@
+.audit-card {
+  display: flex;
+  flex-direction: column;
+  margin: 0.25rem 0 1rem 0;
+  background-color: #ffffff;
+  background-clip: border-box;
+  border: 1px solid rgba(0, 0, 0, 0.125);
+  border-radius: 0.25rem;
+
+  .card-header {
+    padding: 0.75rem 1.25rem;
+    margin-bottom: 0;
+    background-color: rgba(0, 0, 0, 0.03);
+    border-bottom: 1px solid rgba(0, 0, 0, 0.125);
+  }
+  .card-content {
+    display: flex;
+    flex-direction: column;
+    padding: 0;
+    & > li {
+      &:first-of-type {
+        border-top: 0;
+      }
+      &:last-of-type {
+        border-bottom: 0;
+      }
+
+      display: flex;
+      justify-content: space-between;
+      padding: 0.75rem 1.25rem;
+      margin-bottom: -1px;
+      background-color: #ffffff;
+      border-top: 1px solid rgba(0, 0, 0, 0.125);
+      border-bottom: 1px solid rgba(0, 0, 0, 0.125);
+      .role {
+        margin-left: 0.5rem;
+      }
+    }
+  }
+}
+
+.item-border-bottom {
+  border-bottom: 1px solid #dee2e6;
+}
+
+.popover-card {
+  width: 220px;
+  padding: 0.5rem 0;
+  margin: 0.125rem 0 0;
+  ::-webkit-scrollbar {
+    width: 6px;
+    height: 6px;
+  }
+
+  ::-webkit-scrollbar-track {
+    width: 6px;
+    background: rgba(#101f1c, 0.1);
+    -webkit-border-radius: 2em;
+    -moz-border-radius: 2em;
+    border-radius: 2em;
+  }
+
+  ::-webkit-scrollbar-thumb {
+    min-height: 28px;
+    background-color: rgba(#101f1c, 0.5);
+    background-clip: padding-box;
+    -webkit-border-radius: 2em;
+    -moz-border-radius: 2em;
+    border-radius: 2em;
+  }
+
+  ::-webkit-scrollbar-thumb:hover {
+    background-color: rgba(#101f1c, 1);
+  }
+  .ant-popover-title {
+    padding: 0.5rem;
+    margin-bottom: 0.5rem;
+  }
+  .ant-popover-inner-content {
+    height: 285px;
+    padding: 0 0;
+    overflow-x: hidden;
+    overflow-y: auto;
+  }
+  .group-item-label-name {
+    display: flex;
+    justify-content: flex-start;
+    padding-left: 5px;
+    font-weight: 700;
+    background-color: #f2f2f2;
+  }
+  .group-item-content {
+    padding: 0.5rem;
+    cursor: pointer;
+    &:hover {
+      background-color: #f2f2f2;
+    }
+  }
+}
+
+.ant-table-row-expand-icon {
+  position: relative;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  display: -webkit-inline-box;
+  display: -ms-inline-flexbox;
+  display: inline-flex;
+  float: left;
+  width: 17px;
+  height: 17px;
+  padding: 0;
+  line-height: 17px;
+  color: #1890ff;
+  color: inherit;
+  text-decoration: none;
+  cursor: pointer;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  background: #ffffff;
+  border: 1px solid #f0f0f0;
+  border-radius: 2px;
+  outline: none;
+  -webkit-transition: color 0.3s;
+  -webkit-transition: all 0.3s;
+  transition: color 0.3s;
+  transition: all 0.3s;
+}
+.ant-table-row-expand-icon:focus,
+.ant-table-row-expand-icon:hover {
+  color: #40a9ff;
+}
+.ant-table-row-expand-icon:active {
+  color: #096dd9;
+}
+.ant-table-row-expand-icon:focus,
+.ant-table-row-expand-icon:hover,
+.ant-table-row-expand-icon:active {
+  border-color: currentColor;
+}
+.ant-table-row-expand-icon::before,
+.ant-table-row-expand-icon::after {
+  position: absolute;
+  content: '';
+  background: currentColor;
+  -webkit-transition: -webkit-transform 0.3s ease-out;
+  transition: -webkit-transform 0.3s ease-out;
+  transition: transform 0.3s ease-out;
+  transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out;
+}
+.ant-table-row-expand-icon::before {
+  top: 7px;
+  right: 3px;
+  left: 3px;
+  height: 1px;
+}
+.ant-table-row-expand-icon::after {
+  top: 3px;
+  bottom: 3px;
+  left: 7px;
+  width: 1px;
+  -webkit-transform: rotate(90deg);
+  transform: rotate(90deg);
+}
+.ant-table-row-expand-icon-collapsed::before {
+  -webkit-transform: rotate(-180deg);
+  transform: rotate(-180deg);
+}
+.ant-table-row-expand-icon-collapsed::after {
+  -webkit-transform: rotate(0deg);
+  transform: rotate(0deg);
+}
+
+.text-muted {
+  color: #6c757d;
+}
+.timeline-list {
+  padding-left: 0;
+  margin-top: 0.5rem;
+  list-style: none;
+  .timeline-list-item {
+    position: relative;
+    padding-bottom: 0.5rem;
+    .timeline-item-date {
+      position: absolute;
+      left: 0;
+      width: 50px;
+      color: #cccccc;
+      text-align: center;
+      & > :not(:first-child) {
+        color: #333333;
+      }
+    }
+    .timeline-item-tail {
+      position: absolute;
+      top: 15px;
+      left: 55px;
+      height: calc(100% - 10px);
+      border-left: 1px solid #dddddd;
+    }
+    .timeline-item-icon {
+      position: absolute;
+      left: 45px;
+      width: 20px;
+      height: 20px;
+      color: #f8f9fa;
+      border-radius: 50%;
+    }
+    .timeline-item-content {
+      position: relative;
+      margin: 0 0 0 70px;
+      word-break: break-word;
+    }
+    .card-container {
+      display: flex;
+      flex-direction: column;
+      border: 1px solid rgba(0, 0, 0, 0.125);
+      border-radius: 0.25rem;
+      .card-content {
+        padding: 1rem;
+        .label {
+          margin-bottom: 0.25rem;
+          & > :first-child {
+            margin-bottom: 0.5rem;
+            font-size: 1.25rem;
+            font-weight: 500;
+            line-height: 1.2;
+          }
+        }
+      }
+      .textarea {
+        padding: 1rem;
+        border-top: 1px solid #dee2e6;
+      }
+    }
+  }
+}
+
+.audit-container {
+  border: 1px solid #dee2e6;
+  .header {
+    padding: 0.3rem;
+    font-weight: normal;
+    color: #000000;
+    background: #e9ecef;
+    border-bottom: 2px solid #dee2e6;
+    border-bottom-width: 2px;
+  }
+}

+ 481 - 0
src/components/AuditContent/index.tsx

@@ -0,0 +1,481 @@
+import { iAuditHistoryState, iAuditor, iLatestAuditorState } from '@/types/safe'
+import { iAccountGroupItem, iUserInfo } from '@/types/setting'
+import { getUserGroup } from '@/utils/common/user'
+import { formatDate } from '@/utils/util'
+import { Input, Popover } from 'antd'
+import QueueAnim from 'rc-queue-anim'
+import React, { ChangeEvent, useEffect, useState } from 'react'
+import { ExpandButton, ZhButton } from '../Button'
+import SvgIcon from '../SvgIcon'
+import './index.scss'
+
+interface iGroupItem extends iAccountGroupItem {
+  onSelect: (item: iUserInfo) => void
+}
+const GroupItem: React.FC<iGroupItem> = props => {
+  const { onSelect } = props
+  const [ visible, setVisible ] = useState<boolean>(true)
+
+  const changeVisible = () => {
+    setVisible(!visible)
+  }
+  return props.children.length ? (
+    <div>
+      <div className="group-item-label-name pi-align-center">
+        <ExpandButton expanded={visible} onExpand={() => setVisible(!visible)}></ExpandButton>
+        <span onClick={changeVisible} className="pi-mg-left-5">
+          {props.value}
+        </span>
+      </div>
+      <QueueAnim>
+        {visible
+          ? props.children.map((account: iUserInfo) => {
+              return (
+                <div key={account.id} className={[ 'group-item-content', 'pi-justify-between', 'item-border-bottom' ].join(' ')} onClick={() => onSelect(account)}>
+                  <div className="pi-flex-column">
+                    <span className="pi-blue">{account.name}</span>
+                    <span className="pi-gray">{account.position}</span>
+                  </div>
+                  <span>{account.mobile}</span>
+                </div>
+              )
+            })
+          : null}
+      </QueueAnim>
+    </div>
+  ) : null
+}
+
+interface iAuditContentProps {
+  onSelect: (type: string, item: iUserInfo) => void
+  onDelete: (id: string) => void
+  latest: iLatestAuditorState
+  auditors: iAuditor[]
+  auditHistory: iAuditHistoryState[][]
+  status: number
+}
+
+const Index: React.FC<iAuditContentProps> = props => {
+  console.log(typeof props.auditHistory)
+
+  const { onSelect, auditors, onDelete, status, auditHistory } = props
+  const [ visible, setVisible ] = useState({
+    check: false,
+    reCheck: false
+  })
+
+  const showPopover = (type: string) => {
+    setVisible({ ...visible, [type]: true })
+  }
+  const [ searchValue, setSearchValue ] = useState<string>('')
+  const [ groups, setGroups ] = useState<Array<iAccountGroupItem>>([])
+  useEffect(() => {
+    if (visible.check || visible.reCheck) {
+      initGroupList()
+    }
+  }, [ visible.check, visible.reCheck ])
+  const initGroupList = async (serach?: string) => {
+    const data = await getUserGroup(serach)
+    setGroups(data)
+  }
+  const handleVisibleChange = (type: string, isShow: boolean) => {
+    setVisible({ ...visible, [type]: isShow })
+  }
+
+  const search = (value: string) => {
+    if (value != searchValue) {
+      setSearchValue(value)
+      initGroupList(value)
+    }
+  }
+
+  const change = (e: ChangeEvent) => {
+    e.persist()
+    const target = e.target as HTMLTextAreaElement
+    if (!target.value) {
+      initGroupList()
+    }
+  }
+
+  const itemSelectHandler = (type: string, item: iUserInfo) => {
+    onSelect(type, item)
+    setVisible({ ...visible, [type]: false })
+  }
+
+  const renderAuditorSelectItem = (type = 'check') => {
+    return (
+      <ul className="card-content">
+        {type === 'check'
+          ? auditors
+              .filter(item => item.progress === '0')
+              .map((item, idx) => {
+                return (
+                  <li key={item.audit_id}>
+                    <div className="pi-flex-column">
+                      <p>
+                        <span>{idx + 1}</span> {item.name}
+                        <small className="text-muted pi-mg-left-5">{item.position}</small>
+                      </p>
+                      <small className="text-muted">{item.company}</small>
+                    </div>
+                    <span className="pi-red pi-pointer" onClick={() => onDelete(item.audit_id)}>
+                      移除
+                    </span>
+                  </li>
+                )
+              })
+          : auditors
+              .filter(item => item.progress === '2')
+              .map((item, idx) => {
+                return (
+                  <li key={item.audit_id}>
+                    <div className="pi-flex-column">
+                      <p>
+                        <span>{idx + 1}</span> {item.name}
+                        <small className="text-muted pi-mg-left-5">{item.position}</small>
+                      </p>
+                      <small className="text-muted">{item.company}</small>
+                    </div>
+                    <span className="pi-red pi-pointer" onClick={() => onDelete(item.audit_id)}>
+                      移除
+                    </span>
+                  </li>
+                )
+              })}
+      </ul>
+    )
+  }
+
+  const renderStatusIcon = (status: number, isStart: boolean) => {
+    let bgColor: string = ''
+    let iconType: string = ''
+    switch (status) {
+      case 0:
+        bgColor = 'pi-bg-gray'
+        iconType = ''
+        break
+      case 1:
+        bgColor = 'pi-bg-yellow'
+        iconType = 'xxh-ellipsis-h1'
+        break
+      case 2:
+        bgColor = 'pi-bg-green'
+        iconType = 'xxh-check'
+        break
+      case 3:
+        bgColor = 'pi-bg-yellow'
+        iconType = 'xxh-level-up'
+        break
+      case 4:
+        bgColor = 'pi-bg-red'
+        iconType = 'xxh-minus'
+        break
+      default:
+        break
+    }
+    if (isStart) {
+      bgColor = 'pi-bg-green'
+      iconType = 'xxh-caret-down1'
+    }
+    return <div className={[ 'timeline-item-icon', 'pi-justify-center', 'pi-align-center', bgColor ].join(' ')}>{iconType ? <SvgIcon type={iconType}></SvgIcon> : null}</div>
+  }
+
+  const renderStatusEle = (status: number, progress: string) => {
+    let text = ''
+    let textClass = 'pi-color-green'
+    if (progress === "0") {
+      switch (status) {
+        case 1:
+          text = '审批中'
+          textClass = 'pi-yellow'
+          break
+        case 2:
+          text = '审批通过'
+          textClass = 'pi-green'
+          break
+        case 3:
+          text = '审批退回'
+          textClass = 'pi-yellow'
+          break
+        case 4:
+          text = '审批关闭'
+          textClass = 'pi-red'
+          break
+        default:
+          break
+      }
+    } else if (progress === '1') {
+      switch (status) {
+        case 1:
+          text = '整改中'
+          textClass = 'pi-yellow'
+          break
+        case 2:
+          text = '整改完成'
+          textClass = 'pi-green'
+          break
+        case 3:
+          text = '审批退回'
+          textClass = 'pi-yellow'
+          break
+        default:
+          break
+      }
+    } else if (progress === '2') {
+      switch (status) {
+        case 1:
+          text = '复查中'
+          textClass = 'pi-yellow'
+          break
+        case 2:
+          text = '复查完成'
+          textClass = 'pi-green'
+          break
+        case 3:
+          text = '审批退回'
+          textClass = 'pi-yellow'
+          break
+        default:
+          break
+      }
+    }
+    return { text, textClass }
+  }
+  return (
+    <table className="pi-table pi-bordered mt-3">
+      <thead>
+        <tr>
+          <th colSpan={2} className="pi-text-center">
+            审批流程
+          </th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          {!status ? (
+            <>
+              <td width="30%">
+                <table className="table table-bordered">
+                  <tbody>
+                    {auditors.map((item, idx) => {
+                      if (!item.progress) {
+                        return (
+                          <tr key={item.audit_id}>
+                            <td className="pi-text-center">检查人</td>
+                            <td>
+                              <SvgIcon type="xxh-play-circle"></SvgIcon> {item.name} <small className="text-muted">{item.position}</small>
+                            </td>
+                          </tr>
+                        )
+                      } else if (item.progress === '0') {
+                        return auditors[idx - 1].progress === '0' ? (
+                          <tr key={item.audit_id}>
+                            <td>
+                              <SvgIcon type={item.status === 0 ? 'xxh-stop-circle' : 'xxh-chevron-circle-down'}></SvgIcon> {item.name}{' '}
+                              <small className="text-muted">{item.position}</small>
+                            </td>
+                          </tr>
+                        ) : (
+                          <tr key={item.audit_id}>
+                            <td rowSpan={2} className="pi-text-center">
+                              审批
+                            </td>
+                            <td>
+                              <SvgIcon type={item.status === 0 ? 'xxh-stop-circle' : 'xxh-chevron-circle-down'}></SvgIcon> {item.name}{' '}
+                              <small className="text-muted">{item.position}</small>
+                            </td>
+                          </tr>
+                        )
+                      } else if (item.progress === '1' || !auditors.find(item => item.progress === '1')) {
+                        return !auditors.find(item => item.progress === '1') ? (
+                          <tr key={item.audit_id}>
+                            <td className="pi-text-center">整改</td>
+                            <td>
+                              <SvgIcon type={item.status === 0 ? 'xxh-stop-circle' : 'xxh-chevron-circle-down'}></SvgIcon> 由 {auditors[idx - 1].name} 指派
+                            </td>
+                          </tr>
+                        ) : (
+                          <tr key={item.audit_id}>
+                            <td className="pi-text-center">整改</td>
+                            <td>
+                              <SvgIcon type={item.status === 0 ? 'xxh-stop-circle' : 'xxh-chevron-circle-down'}></SvgIcon> {item.name}{' '}
+                              <small className="text-muted">{item.position}</small>
+                            </td>
+                          </tr>
+                        )
+                      } else {
+                        return (
+                          <tr key={item.audit_id}>
+                            <td className="pi-text-center">复查</td>
+                            <td>
+                              <SvgIcon type="xxh-stop-circle"></SvgIcon> {item.name} <small className="text-muted">{item.position}</small>
+                            </td>
+                          </tr>
+                        )
+                      }
+                    })}
+                  </tbody>
+                </table>
+              </td>
+              <td width="70%">
+                <div className="pi-justify-end">
+                  <Popover
+                    title={<Input.Search size="small" placeholder="姓名/手机 检索" onSearch={search} onChange={e => change(e)}></Input.Search>}
+                    content={groups.map(item => (
+                      <GroupItem {...item} key={item.value} onSelect={(item: iUserInfo) => itemSelectHandler('check', item)}></GroupItem>
+                    ))}
+                    overlayClassName="popover-card"
+                    trigger="click"
+                    visible={visible.check}
+                    onVisibleChange={visible => handleVisibleChange('check', visible)}
+                    placement="bottomRight">
+                    <ZhButton size="small" onClick={() => showPopover('check')}>
+                      添加审批流程
+                    </ZhButton>
+                  </Popover>
+                </div>
+                <div className="audit-card">
+                  <div className="card-header">审批流程</div>
+                  {renderAuditorSelectItem()}
+                </div>
+                <div className="audit-card">
+                  <div className="card-header">整改流程</div>
+                  <ul className="card-content">
+                    <li>
+                      <span>整改人由最后一位审批人指派</span>
+                    </li>
+                  </ul>
+                </div>
+                <div className="pi-justify-end">
+                  <Popover
+                    title={<Input.Search size="small" placeholder="姓名/手机 检索" onSearch={search} onChange={e => change(e)}></Input.Search>}
+                    content={groups.map(item => (
+                      <GroupItem {...item} key={item.value} onSelect={(item: iUserInfo) => itemSelectHandler('reCheck', item)}></GroupItem>
+                    ))}
+                    overlayClassName="popover-card"
+                    trigger="click"
+                    visible={visible.reCheck}
+                    onVisibleChange={visible => handleVisibleChange('reCheck', visible)}
+                    placement="bottomRight">
+                    <ZhButton size="small" onClick={() => showPopover('reCheck')}>
+                      添加审批流程
+                    </ZhButton>
+                  </Popover>
+                </div>
+                <div className="audit-card">
+                  <div className="card-header">复查流程</div>
+                  {renderAuditorSelectItem('reCheck')}
+                </div>
+              </td>
+            </>
+          ) : (
+            <td>
+              <div className="pi-flex-row">
+                <div className="pi-flex-sub pi-mg-right-15 pi-mg-top-5 pi-mg-left-5">
+                  <table className="table table-bordered pi-width-100P">
+                    <tbody>
+                      {auditors.map((item, idx) => {
+                        if (!item.progress) {
+                          return (
+                            <tr key={item.audit_id}>
+                              <td className="pi-text-center">检查人</td>
+                              <td>
+                                <SvgIcon type="xxh-play-circle"></SvgIcon>
+                                <span className="pi-mg-left-3">{item.name}</span>
+                                <small className="text-muted pi-mg-left-3">{item.position}</small>
+                              </td>
+                            </tr>
+                          )
+                        } else if (item.progress === '0') {
+                          return auditors[idx - 1].progress === '0' ? (
+                            <tr key={item.audit_id}>
+                              <td>
+                                <SvgIcon type={item.status === 0 ? 'xxh-stop-circle' : 'xxh-chevron-circle-down'}></SvgIcon>
+                                <span className="pi-mg-left-3">{item.name}</span>
+                                <small className="text-muted pi-mg-left-3">{item.position}</small>
+                              </td>
+                            </tr>
+                          ) : (
+                            <tr key={item.audit_id}>
+                              <td rowSpan={2} className="pi-text-center">
+                                审批
+                              </td>
+                              <td>
+                                <SvgIcon type={item.status === 0 ? 'xxh-stop-circle' : 'xxh-chevron-circle-down'}></SvgIcon>
+                                <span className="pi-mg-left-3">{item.name}</span>
+                                <small className="text-muted pi-mg-left-3">{item.position}</small>
+                              </td>
+                            </tr>
+                          )
+                        } else if (item.progress === '1' || !auditors.find(item => item.progress === '1')) {
+                          return !auditors.find(item => item.progress === '1') ? (
+                            <tr key={item.audit_id}>
+                              <td className="pi-text-center">整改</td>
+                              <td>
+                                <SvgIcon type={item.status === 0 ? 'xxh-stop-circle' : 'xxh-chevron-circle-down'}></SvgIcon>
+                                <span>由 {auditors[idx - 1].name} 指派</span>
+                              </td>
+                            </tr>
+                          ) : (
+                            <tr key={item.audit_id}>
+                              <td className="pi-text-center">整改</td>
+                              <td>
+                                <SvgIcon type={item.status === 0 ? 'xxh-stop-circle' : 'xxh-chevron-circle-down'}></SvgIcon>
+                                <span className="pi-mg-left-3">{item.name}</span>
+                                <small className="text-muted pi-mg-left-3">{item.position}</small>
+                              </td>
+                            </tr>
+                          )
+                        } else {
+                          return (
+                            <tr key={item.audit_id}>
+                              <td className="pi-text-center">复查</td>
+                              <td>
+                                <SvgIcon type="xxh-stop-circle"></SvgIcon>
+                                <span className="pi-mg-left-3">{item.name}</span>
+                                <small className="text-muted pi-mg-left-3">{item.position}</small>
+                              </td>
+                            </tr>
+                          )
+                        }
+                      })}
+                    </tbody>
+                  </table>
+                </div>
+                <div className="pi-flex-treble pi-mg-left-15">
+                  {auditHistory.map((item, idx) => {
+                    return (
+                      <ul className="timeline-list" key={idx}>
+                        {item.map((auditor, idx) => (
+                          <li className="timeline-list-item" key={auditor.id}>
+                            <div className="timeline-item-date pi-flex-column" dangerouslySetInnerHTML={{ __html: formatDate(auditor.create_time) }}></div>
+                            <div className={item.length - 1 === idx ? '' : 'timeline-item-tail'}></div>
+                            {renderStatusIcon(auditor.status, idx === 0 ? true : false)}
+                            <div className="timeline-item-content">
+                              <div className="card-container">
+                                <div className="card-content">
+                                  <div className="pi-justify-between label">
+                                    <span>{auditor.name}</span>
+                                    <span className={renderStatusEle(auditor.status, auditor.progress).textClass}>{idx === 0 ? '上报审批' : renderStatusEle(auditor.status, auditor.progress).text}</span>
+                                  </div>
+                                  <div className="text-muted">{auditor.position}</div>
+                                </div>
+                                {auditor.opinion ? <div className="textarea"><span>{auditor.opinion}</span></div> : null}
+                              </div>
+                            </div>
+                          </li>
+                        ))}
+                      </ul>
+                    )
+                  })}
+                </div>
+              </div>
+            </td>
+          )}
+        </tr>
+      </tbody>
+    </table>
+  )
+}
+
+export default Index

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

@@ -105,3 +105,28 @@
     border-color: #f8f9fa;
   }
 }
+
+.auditBackButton {
+  :global(.ant-btn) {
+    color: #212529;
+    background-color: #ffc107;
+    border-color: #ffc107;
+  }
+  :global(.ant-btn:hover) {
+    background-color: #d39e00;
+    border-color: #c69500;
+  }
+  :global(.ant-btn:focus) {
+    background-color: #d39e00;
+    border-color: #c69500;
+  }
+  :global(.ant-btn:active) {
+    background-color: #d39e00;
+    border-color: #c69500;
+  }
+
+  :global(.ant-btn:not(:hover)) {
+    background-color: #ffc107;
+    border-color: #ffc107;
+  }
+}

+ 17 - 1
src/components/Button/index.tsx

@@ -1,4 +1,5 @@
 import { Button } from 'antd'
+// import 'antd/es/table/style/index'
 import { ButtonProps } from 'antd/lib/button/button'
 import React from 'react'
 import styles from './index.module.scss'
@@ -17,10 +18,25 @@ const ZhSubmitButton:React.FC<ButtonProps> = props => {
 const ZhUploadButton:React.FC<ButtonProps> = props => {
   return <div className={styles.uploadButton}><Button {...props} type="text"></Button></div>
 }
+
+const ZhAuditBackButton:React.FC<ButtonProps> = props => {
+  return <div className={styles.auditBackButton}><Button {...props}></Button></div>
+}
+interface iExpandProps {
+  onExpand: () => void
+  expanded: boolean
+}
+const ExpandButton:React.FC<iExpandProps> = props => {
+  const { onExpand, expanded = true } = props
+  const className = expanded ? "ant-table-row-expand-icon-expanded" : "ant-table-row-expand-icon-collapsed"
+  return <button type="button" className={[ "ant-table-row-expand-icon", className ].join(" ")} onClick={onExpand}></button>
+}
 export {
   ZhButton,
   ZhCloseButton,
   ZhSubmitButton,
-  ZhUploadButton
+  ZhUploadButton,
+  ExpandButton,
+  ZhAuditBackButton
 }
 

+ 1 - 1
src/components/SvgIcon/index.tsx

@@ -1,5 +1,5 @@
 import { createFromIconfontCN } from '@ant-design/icons'
 const IconFont = createFromIconfontCN({
-  scriptUrl: '//at.alicdn.com/t/font_2224180_82mpoja7619.js'
+  scriptUrl: '//at.alicdn.com/t/font_2224180_hl1573b0nw6.js'
 })
 export default IconFont

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

@@ -17,7 +17,7 @@ interface iModalStatus {
 export default function Info() {
   const initUserState = {
     account: '',
-    accountGroup: undefined,
+    accountGroup: 0,
     company: '',
     csrf: '',
     enable: 0,

+ 9 - 0
src/pages/Safe/Content/Info/Detail/api.ts

@@ -1,3 +1,4 @@
+import { iAuditHistoryState } from "@/types/safe"
 import request from "@/utils/common/request"
 
 /**
@@ -6,5 +7,13 @@ import request from "@/utils/common/request"
  */
 export async function apiGetSafeDetail(id: string) {
   const { data } = await request.get('/api/safe/detail', { id })
+  const auditHistory: iAuditHistoryState[][] = []
+  for (const key in data?.data?.auditHistory) {
+    if (Object.prototype.hasOwnProperty.call(data?.data?.auditHistory, key)) {
+      const history = data?.data?.auditHistory[key]
+      auditHistory.push(history)
+    }
+  }
+  data.data.auditHistory = auditHistory
   return data
 }

+ 194 - 167
src/pages/Safe/Content/Info/Detail/index.tsx

@@ -1,4 +1,5 @@
-import { ZhSubmitButton, ZhUploadButton } from '@/components/Button'
+import AuditContent from '@/components/AuditContent'
+import { ZhAuditBackButton, ZhCloseButton, ZhSubmitButton, ZhUploadButton } from '@/components/Button'
 import DatePicker from '@/components/DatePicker'
 import Header from '@/components/Header'
 import Slot from '@/components/Header/slot'
@@ -6,7 +7,8 @@ import OssUploadModal from '@/components/OssUpload'
 import SvgIcon from '@/components/SvgIcon'
 import { userStore } from '@/store/mobx'
 import { iFile } from '@/types/file'
-import { iAuditHistory, iLatestAuditorState, iRectifiedState } from '@/types/safe'
+import { iAuditHistoryState, iAuditor, iLatestAuditorState, iRectifiedState } from '@/types/safe'
+import { iUserInfo } from '@/types/setting'
 import { apiDelFile, apiGetFileList, apiSaveFileInfo } from '@/utils/common/api'
 import consts from '@/utils/consts'
 import { dayjsFormat } from '@/utils/util'
@@ -20,7 +22,7 @@ import styles from './index.module.scss'
 const { TextArea } = Input
 interface iDetailState {
   auditName: string
-  auditors: any[]
+  auditors: iAuditor[]
   bidsectionId: string
   code: string
   createTime: string | undefined
@@ -32,12 +34,12 @@ interface iDetailState {
   position: string
   status: number
   checkOrder: iModifiedOrder
-  auditHistory: iAuditHistory
+  auditHistory: iAuditHistoryState[][]
   rectifiedInfo: iRectifiedState[]
   latestAuditor: iLatestAuditorState
 }
 
-interface iModifiedOrder{
+interface iModifiedOrder {
   name: string
   end_time: string | undefined
   opinion: string
@@ -47,43 +49,41 @@ interface iFileState {
   fileList: any[]
   total: number
 }
-const Detail:React.FC<RouteComponentProps> = (props) => {
-  const { saveId = "" } = props.location.state as any
+const Detail: React.FC<RouteComponentProps> = props => {
+  const { saveId = '' } = props.location.state as any
   const [ visible, setVisible ] = useState<boolean>(false)
   const [ detail, setDetail ] = useState<iDetailState>({
-    auditName: "",
+    auditName: '',
     auditors: [],
-    bidsectionId: "",
-    code: "",
-    createTime: "",
-    demand: "",
+    bidsectionId: '',
+    code: '',
+    createTime: '',
+    demand: '',
     file: { fileList: [], total: 0 },
-    id: "",
-    inspection: "",
-    inspectionDetail: "",
-    position: "",
+    id: '',
+    inspection: '',
+    inspectionDetail: '',
+    position: '',
     status: 0,
     checkOrder: {
-      name: "",
+      name: '',
       status: 0,
-      opinion: "",
-      end_time: ""
-    },
-    auditHistory: {
-      "1": []
+      opinion: '',
+      end_time: ''
     },
+    auditHistory: [],
     rectifiedInfo: [],
     latestAuditor: {
-      audit_id: "",
+      audit_id: '',
       status: 0,
-      progress: ""
+      progress: ''
     }
   })
 
   useEffect(() => {
     initData()
   }, [ saveId ])
-  const initData = async() => {
+  const initData = async () => {
     const { code = -1, data = {}, checkOrder = {} } = await apiGetSafeDetail(saveId)
     if (code === consts.RET_CODE.SUCCESS) {
       setDetail({ ...detail, ...data, checkOrder })
@@ -92,35 +92,80 @@ const Detail:React.FC<RouteComponentProps> = (props) => {
   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 }
-      }))
+      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)
+    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 } })
     }
   }
 
-  const delFile = async(id: string) => {
+  const delFile = async (id: string) => {
     const { code = -1 } = await apiDelFile(id)
     if (code === consts.RET_CODE.SUCCESS) {
       const newFiles = detail.file.fileList.filter(file => file.id !== id)
       setDetail({ ...detail, file: { ...detail.file, fileList: newFiles } })
     }
   }
+
+  const addAuditor = (type: string, user: iUserInfo) => {
+    if (type === "check") {
+      const newAuditors = detail.auditors
+      const len = detail.auditors.filter((item: iAuditor) => item.progress === "0").length
+      newAuditors.push({ audit_id: user.id, audit_order: len + 1,position: user.position, progress: "0", name: user.name,accountGroup: user.accountGroup, company: user.company, status: 0 })
+      setDetail({ ...detail, auditors: newAuditors })
+    } else {
+      const newAuditors = detail.auditors
+      const len = detail.auditors.filter((item: iAuditor) => item.progress === "2").length
+      newAuditors.push({ audit_id: user.id, audit_order: len + 1,position: user.position, progress: "2",  name: user.name, accountGroup: user.accountGroup, company: user.company, status: 0 })
+      setDetail({ ...detail, auditors: newAuditors })
+    }
+  }
+
+  const delAuditor = (id: string) => {
+    const newAuditors = detail.auditors.filter(item => item.audit_id !== id)
+    setDetail({ ...detail, auditors: newAuditors })
+  }
+
+  const renderHeaderBtn = (status: number) => {
+    if (!status) {
+      return (
+        <div className="pi-flex-row pi-align-center">
+          <ZhCloseButton size="small">删除巡检</ZhCloseButton>
+          <ZhSubmitButton size="small" className="pi-mg-left-5">提交审批</ZhSubmitButton>
+        </div>
+      )
+    } else if(status === 1) {
+      return (
+        <div className="pi-flex-row pi-align-center">
+          <Button type="primary" danger size="small">关闭</Button>
+          <ZhAuditBackButton size="small" className="pi-mg-left-5">审批退回</ZhAuditBackButton>
+          <ZhSubmitButton size="small" className="pi-mg-left-5">审批通过</ZhSubmitButton>
+        </div>
+      )
+    } else {
+      return (
+        <div className="pi-flex-row pi-align-center">
+          <ZhAuditBackButton size="small">审批退回</ZhAuditBackButton>
+          <ZhSubmitButton size="small" className="pi-mg-left-5">整改</ZhSubmitButton>
+        </div>
+      )
+    }
+  }
   return (
     <div className="wrap-contaniner">
       <Header title="安全巡检">
         <Slot position="right">
-          <div>
-            <ZhSubmitButton size="small">提交审批</ZhSubmitButton>
-          </div>
+          {renderHeaderBtn(detail.status)}
         </Slot>
       </Header>
       <div className={styles.detailContainer}>
@@ -129,169 +174,151 @@ const Detail:React.FC<RouteComponentProps> = (props) => {
           <table className="pi-table pi-bordered">
             <thead>
               <tr>
-                <th colSpan={2} className="pi-text-center">安全巡检单</th>
+                <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>
+                <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>
-              <tr><th style={{ width: "150px" }}>质检员</th><td>{detail.auditName}</td></tr>
             </tbody>
-
           </table>
-          <table className="pi-table pi-bordered">
+          {
+            detail.rectifiedInfo.length ?
+            <table className="pi-table pi-bordered">
             <thead>
               <tr>
-                <th colSpan={2} className="pi-text-center">整改单</th>
+                <th colSpan={2} className="pi-text-center">
+                  整改单
+                </th>
               </tr>
             </thead>
             <tbody>
-              <tr><th style={{ width: "150px" }}>整改情况</th><td><TextArea value={detail.checkOrder.opinion}></TextArea></td></tr>
-              <tr><th style={{ width: "150px" }}>整改日期</th><td><DatePicker size="small" locale={locale} allowClear={false} value={dayjs(detail.checkOrder.end_time)} onChange={(value) => setDetail({ ...detail, checkOrder: { ...detail.checkOrder, end_time: value?.format() } })}></DatePicker></td></tr>
-              <tr><th style={{ width: "150px" }}>整改人</th><td>王五</td></tr>
+              <tr>
+                <th style={{ width: '150px' }}>整改情况</th>
+                <td>
+                  <TextArea value={detail.checkOrder.opinion}></TextArea>
+                </td>
+              </tr>
+              <tr>
+                <th style={{ width: '150px' }}>整改日期</th>
+                <td>
+                  <DatePicker
+                    size="small"
+                    locale={locale}
+                    allowClear={false}
+                    value={dayjs(detail.checkOrder.end_time)}
+                    onChange={value => setDetail({ ...detail, checkOrder: { ...detail.checkOrder, end_time: value?.format() } })}></DatePicker>
+                </td>
+              </tr>
+              <tr>
+                <th style={{ width: '150px' }}>整改人</th>
+                <td>王五</td>
+              </tr>
             </tbody>
           </table>
+          : null
+          }
           <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" 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>
+              <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">{dayjsFormat(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" }} onClick={() => delFile(file.id)}></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>
+              {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">{dayjsFormat(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' }} onClick={() => delFile(file.id)}></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>
-          <table className="table table-bordered mt-3">
-                  <thead>
-                    <tr>
-                      <th colSpan={2} className="text-center">审批流程</th>
-                    </tr>
-                  </thead>
-                  <tbody>
-                    <tr>
-                      <td width="30%">
-                        <table className="table table-bordered">
-                              <tbody><tr><td className="text-center">检查人</td><td><i className="fas fa fa-play-circle fa-rotate-90"></i> 布尔 <small className="text-muted">职称</small></td></tr>
-                              <tr><td rowSpan={2} className="text-center">审批</td><td><i className="fas fa-chevron-circle-down"></i> 张三  <small className="text-muted">职称</small></td></tr>
-                              <tr><td><i className="fas fa-chevron-circle-down"></i> 李四  <small className="text-muted">职称</small></td></tr>
-                              <tr><td className="text-center">整改</td><td><i className="fas fa-chevron-circle-down"></i> 由 张三 指派</td></tr>
-                              <tr><td className="text-center">复查</td><td><i className="fas fa fa-stop-circle"></i> 李四 <small className="text-muted">职称</small></td></tr>
-                            </tbody></table>
-                      </td>
-                      <td width="70%">
-                        <div className="dropdown text-right">
-                          <button className="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                            添加审批流程
-                          </button>
-                        </div>
-                        <div className="card mt-1 mb-3">
-                          <div className="card-header">
-                            审批流程
-                          </div>
-                          <ul className="list-group list-group-flush">
-                            <li className="list-group-item d-flex">
-                              <div>
-                                <p className="m-0 ">1 张三  <small className="text-muted">监理</small></p>
-                                <p className="m-0 ml-2 d-inline"><small className="text-muted">XXX单位</small></p>
-                              </div>
-                              <a href="" className="text-danger ml-auto">移除</a>
-                            </li>
-                            <li className="list-group-item d-flex">
-                              <div>
-                                <p className="m-0 ">2 李四  <small className="text-muted">监理</small></p>
-                                <p className="m-0 ml-2 d-inline"><small className="text-muted">XXX单位</small></p>
-                              </div>
-                              <a href="" className="text-danger ml-auto">移除</a>
-                            </li>
-                          </ul>
-                        </div>
-                        <div className="card mt-1 mb-3">
-                          <div className="card-header">
-                            整改流程
-                          </div>
-                          <ul className="list-group list-group-flush">
-                            <li className="list-group-item d-flex">
-                              <div>
-                                整改人由最后一位审批人指派
-                              </div>
-                            </li>
-                          </ul>
-                        </div>
-                        <div className="dropdown text-right">
-                          <button className="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                            添加复查流程
-                          </button>
-                        </div>
-                        <div className="card mt-1">
-                          <div className="card-header">
-                            复查流程
-                          </div>
-                          <ul className="list-group list-group-flush">
-                            <li className="list-group-item d-flex">
-                              <div>
-                                <p className="m-0 ">1 李四  <small className="text-muted">监理</small></p>
-                                <p className="m-0 ml-2 d-inline"><small className="text-muted">XXX单位</small></p>
-                              </div>
-                              <a href="" className="text-danger ml-auto">移除</a>
-                            </li>
-                          </ul>
-                        </div>
-                      </td>
-                    </tr>
-                  </tbody>
-                </table>
+          <AuditContent
+            auditors={detail.auditors}
+            onSelect={addAuditor}
+            onDelete={delAuditor}
+            latest={detail.latestAuditor}
+            auditHistory={detail.auditHistory}
+            status={detail.status}
+            ></AuditContent>
         </div>
       </div>
-      <OssUploadModal
-        visible={visible}
-        onCancel={() => setVisible(false)}
-        onCreate={onCreate}
-        onShow={onShow}
-        >
-      </OssUploadModal>
+      <OssUploadModal visible={visible} onCancel={() => setVisible(false)} onCreate={onCreate} onShow={onShow}></OssUploadModal>
     </div>
   )
 }

+ 20 - 2
src/store/mobx/user/index.ts

@@ -1,7 +1,8 @@
 import { apiLogout } from '@/components/Menu/api'
 import { apiLogin } from '@/pages/Login/api'
 import { iFromValues } from '@/types/login'
-import { iUserInfo } from '@/types/setting'
+import { iGroup, iUserInfo } from '@/types/setting'
+import { apiGetGroupList } from '@/utils/common/api'
 import { delUserInfo, getUserInfo, saveUserInfo } from '@/utils/common/user'
 import consts from '@/utils/consts'
 import history from '@/utils/history'
@@ -10,7 +11,7 @@ import { action, computed, observable } from 'mobx'
 class UserState {
   initUserState = {
     account: '',
-    accountGroup: undefined,
+    accountGroup: 0,
     company: '',
     csrf: '',
     enable: 0,
@@ -30,6 +31,8 @@ class UserState {
 
   @observable showLeftSide: boolean = true
 
+  @observable groupList:iGroup[] = []
+
   @computed get isLogin() {
     return !!this.userInfo.id
   }
@@ -67,6 +70,21 @@ class UserState {
       this.userInfo = user
     }
   }
+
+  @action getGroupList() {
+    apiGetGroupList().then(({ code = -1, data = [] }) => {
+      if (code === consts.RET_CODE.SUCCESS) {
+        const newData = []
+        for (const key in data) {
+          if (Object.prototype.hasOwnProperty.call(data, key)) {
+            const value: string = data[key]
+            newData.push({ key: parseInt(key), value })
+          }
+        }
+        this.groupList = newData
+      }
+    })
+  }
 }
 
 export default new UserState()

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

@@ -14,6 +14,7 @@ export interface iAuditHistoryState {
   end_time: string;
   opinion: string;
   status: number;
+  progress: string
 }
 
 export interface iAuditHistory {
@@ -32,3 +33,18 @@ export interface iLatestAuditorState {
   status: number;
   progress: string;
 }
+
+export interface iAuditor {
+  audit_id: string
+  name: string
+  position: string
+  progress: string
+  audit_order: number
+  accountGroup: number
+  company: string
+  status: number
+}
+
+export interface iAuditors {
+  (key: string): iAuditor[]
+}

+ 15 - 1
src/types/setting.d.ts

@@ -14,7 +14,7 @@ export interface iAccountEdit extends accountCreatePayload {
 
 export interface iUserInfo {
   account: string;
-  accountGroup: number | undefined;
+  accountGroup: number;
   company: string;
   csrf: string;
   enable: number;
@@ -39,3 +39,17 @@ export interface iAccountEnable {
   id: string
   enable: number
 }
+
+export interface iAccountGroup {
+  [key: string]: iAccountGroupItem
+}
+
+interface iAccountGroupItem {
+  value: string
+  children: iUserInfo[]
+}
+
+export interface iGroup {
+  key: number
+  value: string
+}

+ 14 - 0
src/utils/common/api.ts

@@ -36,3 +36,17 @@ export async function getSignature() {
   const { data } = await request.get('/api/oss/signature')
   return data
 }
+
+// 获取账号分组信息
+export async function apiGetGroupList() {
+  const { data } = await request.get("/api/projectAccount/group")
+  return data
+}
+
+// 获取账号列表(检索)
+export async function apiGetAccountWithSearch(name?: string) {
+  const { data } = await request.get("/api/projectAccount/search", { name })
+  return data
+
+}
+

+ 10 - 1
src/utils/common/constStatus.ts

@@ -44,10 +44,19 @@ const safeStatus = {
     text: '关闭'
   }
 }
+const auditConsts = {
+  uncheck: 0, // 未提交
+  checking: 1, //审批中
+  checked: 2, // 待整改
+  checkNo: 3, // 待复查
+  finish: 4, //完成
+  close: 5 // 关闭
+}
 const contractTreeBaseId: string = 'xCi4xUL6uur0h7fVI--NeA'
 export {
   contractConsts,
   contractTreeBaseId,
-  safeStatus
+  safeStatus,
+  auditConsts
 }
 

+ 26 - 1
src/utils/common/user.ts

@@ -1,5 +1,8 @@
-import { iUserInfo } from '@/types/setting'
+import { userStore } from '@/store/mobx'
+import { iAccountGroupItem, iGroup, iUserInfo } from '@/types/setting'
 import { storage } from '@/utils/util'
+import consts from '../consts'
+import { apiGetAccountWithSearch } from './api'
 const USER_INFO = 'user_info' // 用户个人信息
 /**
  * 保存用户信息到本地存储中
@@ -31,3 +34,25 @@ export const logout = () => {
   delUserInfo()
 }
 
+// 获取用户分组
+export const getUserGroupName = (key: number): iGroup | undefined =>{
+  return userStore.groupList.find((item: iGroup) => item.key === key)
+}
+
+// 根据group对用户列表进行分组
+export const getUserGroup = async (name?: string) => {
+  if (!userStore.groupList.length) {
+    userStore.getGroupList()
+  }
+  const { code = -1, data = [] } = await apiGetAccountWithSearch(name)
+  const accountGroup: iAccountGroupItem[] = []
+  if (code === consts.RET_CODE.SUCCESS) {
+    for (const group of userStore.groupList) {
+      const groupItem = { value: group.value, children: [] }
+      const items = data.filter((item: iUserInfo) => item.accountGroup === group.key)
+      groupItem.children = items
+      accountGroup.push(groupItem)
+    }
+  }
+  return accountGroup
+}

+ 28 - 2
src/utils/util.ts

@@ -100,7 +100,32 @@ const generatePsw = (len: number): string => {
   }
   return password
 }
-
+const formatDate = (d: string) => {
+  if (!d) return ''
+  const date = new Date(d)
+  const year = date.getFullYear()
+  let mon: number | string = date.getMonth() + 1
+  let day: number | string = date.getDate()
+  let hour: number | string = date.getHours()
+  let minute: number | string = date.getMinutes()
+  let scond: number | string = date.getSeconds()
+  if (mon < 10) {
+      mon = '0' + mon.toString()
+  }
+  if (day < 10) {
+      day = '0' + day.toString()
+  }
+  if (hour < 10) {
+      hour = '0' + hour.toString()
+  }
+  if (minute < 10) {
+      minute = '0' + minute.toString()
+  }
+  if (scond < 10) {
+      scond = '0' + scond.toString()
+  }
+  return `<span>${year}</span><span>${mon}-${day}</span><span>${hour}:${minute}:${scond}</span>`
+}
 export {
   getCookie,
   storage,
@@ -108,6 +133,7 @@ export {
   debounce,
   combinationPath,
   dayjsFormat,
-  generatePsw
+  generatePsw,
+  formatDate
 }