Explorar o código

feat: 项目设置-标段管理 树结构功能完成

lanjianrong %!s(int64=4) %!d(string=hai) anos
pai
achega
d8cb326347

+ 40 - 20
src/pages/Management/Tender/api.ts

@@ -1,3 +1,4 @@
+import { iModalTypeProps } from '@/types/tender'
 import request from '@/utils/common/request'
 
 /**
@@ -9,27 +10,46 @@ export async function apiTree() {
 }
 
 /**
- * 新建文件夹
+ * 树结构-resful api
+ * @param {iModalTypeProps} type - 类型
+ * @param {object} payload - 载荷
  */
-interface iNewFolder {
-  id: string
-  depth: number
-  name: string
-}
-export async function apiNewFolder(payload: iNewFolder) {
-  const { data } = await request.post('/api/tree/create', payload)
-  return data
-}
+export async function apiResfulTree(type: iModalTypeProps, payload: object) {
+  let url:string = '', method: string = ''
 
-/**
- * 新建标段
- */
-interface iNewTender {
-  folderId: string
-  depth: number
-  name: string
-}
-export async function apiNewTender(payload: iNewTender) {
-  const { data } = await request.post('/api/bidsection/create', payload)
+  switch (type) {
+    case 'root':
+      url = '/api/tree/create'
+      method = 'post'
+      break
+    case 'folder':
+      url = '/api/tree/create'
+      method = 'post'
+      break
+
+    case 'tender':
+      url = '/api/bidsection/create'
+      method = 'post'
+      break
+    case 'rename':
+      url = '/api/tree/rename'
+      method = 'post'
+      break
+    case 'move':
+      url = '/api/tree/move'
+      method = 'post'
+      break
+    case 'delTender':
+      url = '/api/tree'
+      method = 'del'
+      break
+    case 'delFolder':
+      url = '/api/tree'
+      method = 'del'
+      break
+    default:
+      break
+  }
+  const { data } = await request[method](url, payload)
   return data
 }

+ 118 - 48
src/pages/Management/Tender/components/ModalForm.tsx

@@ -1,45 +1,95 @@
-import { Form, Input, message, Modal } from 'antd'
-import React, { useEffect } from 'react'
+import { iModalProps, TenderTree } from '@/types/tender'
+import { Form, Input, message, Modal, TreeSelect } from 'antd'
+import React, { useEffect, useState } from 'react'
 
 interface Option {
   value: string | number;
-  label?: React.ReactNode;
-  disabled?: boolean;
+  title: React.ReactNode;
+  disabled: boolean;
   children?: Option[];
 }
-interface iModalObj {
-  visible: boolean
-  type: 'folder' | 'tender' | 'root'
-  confirmLoading: boolean
-  pid: string
-}
+
 interface iModalFormProps {
-  modalObj:iModalObj
+  treeObj: TenderTree
+  modalObj: iModalProps
   onCreate: (values, type ) => void
   onCancel: () => void
 }
 
 const ModalForm: React.FC<iModalFormProps> = ({
+  treeObj,
   onCreate,
   onCancel,
-  modalObj: { visible, confirmLoading, type, pid }
+  modalObj: { visible, confirmLoading, type, id, name, isFolder }
 }) => {
   const [ form ] = Form.useForm()
   const modalObj = {
-    title: type === 'tender' ? '添加新标段' : '新建文件夹',
-    nameTitle:  type === 'tender' ? '标段名称' : '文件夹名称',
-    namePlaceholder:  type === 'tender' ? '输入标段名称' : '输入文件夹名称'
+    tender: {
+      title: '添加新标段',
+      nameTitle: '标段名称',
+      namePlaceholder: '输入标段名称',
+      okText: '确认添加'
+    },
+    folder: {
+      title: '新建文件夹',
+      nameTitle: '文件夹名称',
+      namePlaceholder: '输入文件夹名称',
+      okText: '确认添加'
+    },
+    root: {
+      title: '新建文件夹',
+      nameTitle: '文件夹名称',
+      namePlaceholder: '输入文件夹名称',
+      okText: '确认添加'
+    },
+    rename: {
+      title: '重命名',
+      nameTitle: '名称',
+      namePlaceholder: '输入新的名称',
+      okText: '确认修改'
+    },
+    delFolder: {
+      title: '删除文件夹',
+      okText: '确认删除'
+    },
+    delTender: {
+      title: '删除标段',
+      okText: '确认删除'
+    },
+    move: {
+      title: '移动',
+      nameTitle: '移动至',
+      namePlaceholder: '选择文件夹父级目录',
+      okText: '确认修改'
+    }
   }
+
+  const [ cascader, setCascader ] = useState<Option[]>([])
+  const [ showNameInput, setShowNameInput ] = useState(true)
+
   useEffect(() => {
-    form.resetFields()
-  }, [ modalObj ])
+    form.setFieldsValue({ [type === 'tender' ? 'folderId' : 'id']: id })
+    if (type === 'move') {
+      console.log('iiiid', id)
+
+      setCascader(mapTree([ treeObj ], id, isFolder))
+    }
+    if (type === 'move' || type.indexOf('del') !== -1) {
+      setShowNameInput(false)
+    } else {
+      setShowNameInput(true)
+      showNameInput && form.setFieldsValue({ depth: type === 'root' ? -1 : 0 })
+    }
+  }, [ type, id ])
   return (
     <Modal
       getContainer={false}
       visible={visible}
       confirmLoading={confirmLoading}
-      title={modalObj.title}
-      okText="确认添加"
+      title={modalObj[type].title}
+      okText={modalObj[type].okText}
+      okButtonProps={{ size: 'small', danger: type.indexOf('del') !== -1 ? true : false }}
+      cancelButtonProps={{ size: "small" }}
       cancelText="取消"
       onCancel={onCancel}
       closable={false}
@@ -52,40 +102,60 @@ const ModalForm: React.FC<iModalFormProps> = ({
         })
     }}>
       <Form form={form} layout="vertical">
-        <Form.Item name={type === 'tender' ? 'folderId' : 'id'} initialValue={pid} hidden>
-          <Input></Input>
-            {/* <TreeSelect treeData={cascaderArrOption} placeholder={modalObj.seletePlaceholder} disabled={type === 'root'} allowClear ></TreeSelect> */}
-        </Form.Item>
-        <Form.Item  name="name" label={modalObj.nameTitle} rules={[ { required: true, message: '请输入文件夹名称!' },{
-          validator: async (_, names) => {
-            if (names && names.length > 30) {
-              return Promise.reject(new Error('名称超过30个字,请缩减名称。'))
+        <Form.Item name={type === 'tender' ? 'folderId' : 'id'} hidden ><Input></Input></Form.Item>
+        {
+          showNameInput ? <Form.Item  name="name" label={modalObj[type].nameTitle} rules={[ { required: true, message: '请输入文件夹名称!' },{
+            validator: async (_, names) => {
+              if (names && names.length > 30) {
+                return Promise.reject(new Error('名称超过30个字,请缩减名称。'))
+              }
             }
-          }
-        } ]}>
-          <Input allowClear size="small" autoComplete="off"></Input>
-        </Form.Item>
-        <Form.Item name="depth" initialValue={type === 'root' ? -1 : 0} hidden>
-          <Input></Input>
-        </Form.Item>
+          } ]}>
+            <Input allowClear size="small" autoComplete="off"></Input>
+          </Form.Item> : ''
+        }
+        {
+          showNameInput ? <Form.Item name="depth" hidden><Input></Input></Form.Item> : ''
+        }
+        {
+          type === 'move' ? <Form.Item name="targetFolderId"><TreeSelect treeData={cascader} placeholder={modalObj[type].namePlaceholder} allowClear ></TreeSelect></Form.Item> : ''
+        }
+        {
+          type.indexOf('del') !== -1 ? <div><p>确认删除「{name}」?</p><p>删除后,数据无法恢复,请谨慎操作。</p></div> : ''
+        }
       </Form>
     </Modal>
   )
 }
 
-// function mapTree(treeObj:TenderTree, type: 'folder' | 'tender' | 'root') {
-//   if (type === 'root') {
-//     return [ { value: treeObj.id, title: treeObj.name } ]
-//   }
-//   return treeObj.children?.map((tree:TenderTree) => {
-//     const newTree = { value: tree.id, title: tree.name }
-//     if ((type === 'folder' && !tree.isBid) || (type === 'tender' && tree.leaf)) {
-//       if (tree.children?.length) {
-//         newTree.children = mapTree(tree.children, type)
-//       }
-//       return newTree
-//     }
-//   }).filter(item => item) // filter的作用是过滤掉map最终可能产生的underfined的item(map的数组有多少个item就会返回多少个item,没有返回就是underfined)
-// }
+function mapTree(treeArr:TenderTree[], id: string, isFolder: boolean) {
+  console.log(treeArr)
+  console.log('id', id)
+  console.log('isFolder', isFolder)
+
+  const arr =  treeArr.map((tree:TenderTree) => {
+    const newTree = { value: tree.id, title: tree.name, disabled: tree.id === id }
+    // 要移动的是文件夹
+    if (isFolder && tree.isBid) {
+      // 该目录下已有标段不可移动文件夹至此目录
+      newTree.disabled = true
+      console.log(newTree)
+
+      return newTree
+    }
+    if (tree.isfolder || tree.hasFolder) {
+      if (tree.childsTotal > 0) {
+        if (tree.children.findIndex(item => item.id === id) !== -1) {
+          newTree.disabled = true
+        }
+        newTree.children = mapTree(tree.children, tree.id, Boolean(tree.isfolder))
+      }
+      return newTree
+    }
+  }).filter(item => item) // filter过滤滤掉map可能产生的underfined item
+  console.log('arr', arr)
+
+  return arr
+}
 
 export default ModalForm

+ 18 - 24
src/pages/Management/Tender/index.tsx

@@ -8,24 +8,25 @@ import { Button, Dropdown, Menu, Table, Tooltip } from 'antd'
 import { ColumnsType } from 'antd/lib/table'
 import React, { useEffect, useState } from 'react'
 import { Link } from 'react-router-dom'
-import { apiNewFolder, apiNewTender, apiTree } from './api'
+import { apiResfulTree, apiTree } from './api'
 import ModalForm from './components/ModalForm'
 import styles from './index.module.scss'
 import './index.scss'
 const Tender: React.FC<{}> = () =>{
   const [ tree, setTree ] = useState<TenderTree>({})
   const [ modal, setModal ] = useState<iModalProps>({
-    type: '',
+    type: 'folder',
     visible: false,
     confirmLoading: false,
     pid: ''
   })
-  const addBtnClick = (type: 'folder' | 'tender' | 'root', pid: string) => {
+  const treeBtnClick = (payload) => {
+    console.log(payload)
+
     setModal({
       ...modal,
-      type,
       visible: true,
-      pid
+      ...payload
     })
   }
   const columns: ColumnsType<TenderTree>  = [
@@ -64,8 +65,8 @@ const Tender: React.FC<{}> = () =>{
         return <Dropdown overlay={() => {
           return (
             <Menu>
-              <Menu.Item key="0"><div className="menu-item"><SvgIcon iconClass="edit" fontSize="12"></SvgIcon><span>重命名</span></div></Menu.Item>
-              <Menu.Item key="1"><div className="menu-item"><SvgIcon iconClass="exchange-alt" fontSize="12"></SvgIcon><span>移动</span></div></Menu.Item>
+              <Menu.Item key="0"><div className="menu-item"  onClick={() => treeBtnClick({ type: 'rename', id: record.id })}><SvgIcon iconClass="edit" fontSize="12"></SvgIcon><span>重命名</span></div></Menu.Item>
+              <Menu.Item key="1"><div className="menu-item" onClick={() => treeBtnClick({ type: 'move', id: record.id, isFolder: Boolean(record.isfolder) })}><SvgIcon iconClass="exchange-alt" fontSize="12"></SvgIcon><span>移动</span></div></Menu.Item>
               {
                 record.children?.length ?
                   <Menu.Item key="2">
@@ -76,14 +77,14 @@ const Tender: React.FC<{}> = () =>{
                     </Tooltip>
                   </Menu.Item>
                 :
-                <Menu.Item key="2"><div className="menu-item"><SvgIcon iconClass="times" fontSize="12"></SvgIcon><span>删除</span></div></Menu.Item>
+                <Menu.Item key="2"><div className="menu-item" onClick={() => treeBtnClick({ type : record.isfolder ? 'delFolder' : 'delTender', id: record.id, name: record.name })}><SvgIcon iconClass="times" fontSize="12"></SvgIcon><span>删除</span></div></Menu.Item>
               }
               {
                 record.isfolder ? <Menu.Divider /> : ''
               }
               {
-                record.isfolder ? record.leaf ?
-                <Menu.Item key="3"><div className= "menu-item" onClick={() => addBtnClick('tender', record.id)}><SvgIcon iconClass="plus" fontSize="12"></SvgIcon><span>新建标段</span></div></Menu.Item>
+                record.isfolder ? (!record.hasFolder) ?
+                <Menu.Item key="3"><div className= "menu-item" onClick={() => treeBtnClick({ type: 'tender', id: record.id })}><SvgIcon iconClass="plus" fontSize="12"></SvgIcon><span>新建标段</span></div></Menu.Item>
                 :
                 <Menu.Item key="3">
                   <Tooltip placement="left" title="标段无法与文件夹同层">
@@ -93,7 +94,7 @@ const Tender: React.FC<{}> = () =>{
               }
               {
                 record.isfolder ? !record.isBid ?
-                <Menu.Item key="4"><div className="menu-item" onClick={() => addBtnClick('folder', record.id)}><SvgIcon iconClass="folder" fontSize="12"></SvgIcon><span>新建子文件夹</span></div></Menu.Item>
+                <Menu.Item key="4"><div className="menu-item" onClick={() => treeBtnClick({ type : 'folder', id: record.id })}><SvgIcon iconClass="folder" fontSize="12"></SvgIcon><span>新建子文件夹</span></div></Menu.Item>
                 :
                 <Menu.Item key="4">
                   <Tooltip placement="left" title="文件夹无法与标段同层">
@@ -114,28 +115,21 @@ const Tender: React.FC<{}> = () =>{
       }
     }
   ]
-  const onCreate = async (values: iTenderFormValue, type: 'folder' | 'tender' | 'root') => {
+  const onCreate = async (values: iTenderFormValue, type: string) => {
     console.log('Received values of form: ', values, type)
     setModal({
       ...modal,
       confirmLoading: true
     })
-    if (type === 'tender') {
-      const { code = -1 } = await apiNewTender(values)
-      if (code === consts.RET_CODE.SUCCESS) {
-        getTree()
-      }
-    } else {
-      const { code = -1 } = await apiNewFolder(values)
-      if (code === consts.RET_CODE.SUCCESS) {
-        getTree()
-      }
+    const { code = -1 } = await apiResfulTree(type, values)
+    if (code === consts.RET_CODE.SUCCESS) {
+      getTree()
     }
     setModal({
       ...modal,
       confirmLoading: false,
       visible: false,
-      pid: ''
+      id: ''
     })
 
   }
@@ -152,7 +146,7 @@ const Tender: React.FC<{}> = () =>{
     <div className="content-wrap">
       <Header title="标段管理">
         <Slot position="right">
-          <Button type="primary" size="small" icon={<FolderAddFilled />} onClick={() => addBtnClick('root', tree.id)}>新建文件夹</Button>
+          <Button type="primary" size="small" icon={<FolderAddFilled />} onClick={() => treeBtnClick({ type: 'root', id: tree.id })}>新建文件夹</Button>
         </Slot>
       </Header>
       <div className="pi-flex-column">

+ 21 - 18
src/types/tender.d.ts

@@ -1,34 +1,37 @@
 export type TenderTree = {
+  ancounts: number;
+  attribution: string;
+  bidsectionId: string;
+  children: any[];
+  childsTotal: number;
+  createTime: string;
+  csrf: string;
+  depth: number;
+  hasFolder: boolean;
   id: string;
+  isBid: boolean;
+  isEnd: boolean;
+  isfolder: number;
+  moveId: string;
   name: string;
-  projectId: string;
-  tenderId: number;
   parentId: string;
-  depth: number;
+  projectId: string;
   serial: string;
-  attribution: string;
-  isfolder: number;
-  createTime: string;
-  isBid: boolean;
   updateTime: string;
-  moveId: string;
-  ancounts: number;
-  csrf: string;
-  leaf: boolean;
-  isEnd: boolean;
-  childsTotal: number;
   children: TenderTree[] | null;
 }
-
 export interface iTenderFormValue {
   [propName: 'folderId' | 'id'] : string
   name: string
   depth: number
 }
-
-export interface iModalProps {
-  type: string
+export interface iModalTypeProps {
+  type: 'root' | 'folder' | 'tender' | 'rename' | 'move' | 'delTender' | 'delFolder'
+}
+export interface iModalProps extends iModalTypeProps {
   visible: boolean
   confirmLoading: boolean
-  pid: string
+  id: string
+  name?: string
+  isFolder?: boolean
 }

+ 2 - 1
src/utils/common/request.ts

@@ -53,6 +53,7 @@ service.interceptors.response.use(
   (response: AxiosResponse) => {
     removePending(response.config)
     const data: ResponseData = response.data
+
     // 对Code不等于Success进行message提示
     if (data.code !== CONSTS.RET_CODE.SUCCESS) {
       message.error(data.msg)
@@ -106,6 +107,6 @@ export default {
     return service.post(url, data )
   },
   del(url: string, data?: any) {
-    return service.delete(url, data)
+    return service.delete(url, { params: data })
   }
 }