Browse Source

feat: 合同回款增加行编辑功能。

lanjianrong 4 years ago
parent
commit
92a9bb3ae5

+ 7 - 0
.vscode/.settings.json

@@ -9,3 +9,10 @@
 //   "scss.validate": false,
 //   "typescript.validate.enable": false
 // }
+{
+  "editor.codeActionsOnSave": {
+    "source.fixAll.eslint": true,
+    "source.fixAll.stylelint": true,
+    "source.organizeImports": true
+  }
+}

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

@@ -407,3 +407,7 @@
     border-radius: 50%;
   }
 }
+
+.pi-pointer {
+  cursor: pointer;
+}

+ 0 - 234
src/pages/Contract/Content/Income/GCsheet.tsx

@@ -1,234 +0,0 @@
-// import { tenderStore } from '@/store/mobx'
-// import consts from '@/utils/consts'
-// import GC from '@grapecity/spread-sheets'
-// import { SpreadSheets, Worksheet } from '@grapecity/spread-sheets-react'
-// import { Button, message, Radio, Table, Tabs } from 'antd'
-// import Modal from 'antd/lib/modal/Modal'
-// import { RadioChangeEvent } from 'antd/lib/radio'
-// import React, { useEffect, useState } from 'react'
-// import { useActivate } from 'react-activation'
-// import { apiContractIncome, apiSetTemplate } from './components/Modal/api'
-// import Detail from './components/Tabs/Detail'
-// import File from './components/Tabs/File'
-// import Receivable from './components/Tabs/Receivable'
-// import styles from './index.module.scss'
-// GC.Spread.Common.CultureManager.culture("zh-cn")
-// // GC.Spread.Sheets.LicenseKey = secret
-// interface iGCsheetPorps {
-//   modalHandler: (type: string) => void
-// }
-// interface iTemplateState {
-//   attribution: string
-//   children: iTemplateState[] | undefined
-//   depth: number
-//   id: number
-//   isEnd: boolean
-//   leaf: boolean
-//   name: string
-//   parentId: number
-//   serial: string
-// }
-// interface iShowTemplateState {
-//   isShow: boolean
-//   template: string
-//   loading: boolean
-// }
-// const GCsheet: React.FC<iGCsheetPorps> = ({ modalHandler }) => {
-//   const [ sectionTemplate, setSectionTemplate ] = useState<iShowTemplateState>({
-//     isShow: false,
-//     template: '',
-//     loading: false
-//   })
-//   const [ tempalte, setTempalte ] = useState<{template1: iTemplateState, template2: iTemplateState}>({
-//     template1: {
-//       attribution: '',
-//       children: undefined,
-//       depth: 0,
-//       id: 0,
-//       isEnd: false,
-//       leaf: false,
-//       name: '',
-//       parentId: 0,
-//       serial: ''
-//     },
-//     template2: {
-//       attribution: '',
-//       children: undefined,
-//       depth: 0,
-//       id: 0,
-//       isEnd: false,
-//       leaf: false,
-//       name: '',
-//       parentId: 0,
-//       serial: ''
-//     }
-//   })
-//   const { TabPane } = Tabs
-//   const initSpread = (spread: GC.Spread.Sheets.Workbook) => {
-//     spread.suspendPaint()
-//     spread.resumePaint()
-//   }
-//   useEffect(() => {
-//     initHandler()
-//   }, [])
-//   const initHandler = async () => {
-//     const data  = await apiContractIncome(tenderStore.bidsectionId)
-//     if (data.code === consts.RET_CODE.SUCCESS) {
-//       if (data.isTemplate && data.isTemplate === 1) {
-//         setSectionTemplate({
-//           ...sectionTemplate,
-//           isShow: true
-//         })
-//         setTempalte({
-//           ...tempalte,
-//           template1: data.sectionTemplate1,
-//           template2: data.sectionTemplate2
-//         })
-//       } else {
-//         setProjectTree(data.data)
-//       }
-//     }
-//   }
-//   const columns = [
-//     {
-//       title: '项目节',
-//       dataIndex: 'serial',
-//       width: '30%',
-//       // eslint-disable-next-line react/display-name
-//       render: (text: string, row: iTemplateState) => {
-//         const { attribution = '', serial = '' } = row
-//         return <span>{`${attribution}${serial}`}</span>
-//       }
-//     },
-//     {
-//       title: '名称',
-//       dataIndex: 'name',
-//       width: '70%'
-//     }
-//   ]
-//   useActivate(() =>{
-//     initHandler()
-//   })
-
-//   // modal 确认 - 回调
-//   const handleModalConfirm = async () => {
-//     setSectionTemplate({
-//       ...sectionTemplate,
-//       loading: true
-//     })
-//     if (!sectionTemplate.template) return message.error('请选择项目节模板!')
-//     const { code = -1 } = await apiSetTemplate(sectionTemplate.template, tenderStore.bidsectionId)
-//     if (code === consts.RET_CODE.SUCCESS) {
-//       await initHandler()
-//     }
-//     setSectionTemplate({
-//       ...sectionTemplate,
-//       loading: false,
-//       isShow: false
-//     })
-//   }
-//   // radio 切换回调
-//   const handleRadioEvent = (e: RadioChangeEvent) => {
-//     if (e.target.checked) {
-//       setSectionTemplate({
-//         ...sectionTemplate,
-//         template: e.target.value
-//       })
-//     }
-
-//   }
-//   return sectionTemplate.isShow ?
-//   <Modal
-//     visible={sectionTemplate.isShow}
-//     maskClosable={false}
-//     title="选择合同项目节模板"
-//     okText="确定"
-//     confirmLoading={sectionTemplate.loading}
-//     cancelText="关闭"
-//     closable={false}
-//     keyboard={false}
-//     onOk={() => handleModalConfirm()}
-//     width='70vw'
-//   >
-//     <div className={styles.modalWarnText}>默认项目节无法修改,可自行增加维护子节点</div>
-//     <div className={styles.modalTemplateContent}>
-//       <div className={styles.leftTemplate}>
-//         <div className="pi-pd-20">
-//           <Radio value="1" checked={sectionTemplate.template === '1'} onChange={(e: RadioChangeEvent) => handleRadioEvent(e)}><span className="pi-gray-color">项目节模板1</span></Radio>
-//         </div>
-//         <div className={styles.projectTable}>
-//           {
-//             tempalte.template1?.children &&  tempalte.template1?.children.length?
-//             <Table
-//             dataSource={tempalte.template1?.children}
-//             columns={columns}
-//             pagination={false}
-//             bordered
-//             scroll={{ y: '300px' }}
-//             rowKey={record => record.id}
-//             defaultExpandAllRows={true}
-//             >
-//           </Table> : ''
-//           }
-//         </div>
-
-//       </div>
-//       <div className={styles.rightTemplate}>
-//         <div className="pi-pd-20 pi-gray-color">
-//           <Radio value="2" checked={sectionTemplate.template === '2'} onChange={(e: RadioChangeEvent) => handleRadioEvent(e)}><span className="pi-gray-color">项目节模板2</span></Radio>
-//         </div>
-//         <div className={styles.projectTable}>
-//           {
-//             tempalte.template2?.children &&  tempalte.template2?.children.length? <Table
-//             dataSource={tempalte.template2?.children}
-//             columns={columns}
-//             bordered
-//             pagination={false}
-//             scroll={{ y: '300px' }}
-//             rowKey={record => record.id}
-//             defaultExpandAllRows={true}
-//             >
-//           </Table> : ''
-//           }
-
-//         </div>
-//       </div>
-//     </div>
-//   </Modal>
-//   :
-//   <div className={styles.spreadContent}>
-//     <div className={styles.spreadSheets}>
-//       <SpreadSheets
-//         workbookInitialized={(spread: GC.Spread.Sheets.Workbook)=> initSpread(spread)}
-//         showHorizontalScrollbar={false}
-//         showVerticalScrollbar={false}
-//         tabStripVisible={false}
-//         >
-//         <Worksheet rowHeaderVisible={false}>
-//         </Worksheet>
-//     </SpreadSheets>
-//     </div>
-//     <div className={styles.extraControl}>
-//       <Tabs
-//         type="card"
-//         size="small"
-//         defaultActiveKey="1"
-//         tabBarExtraContent={{ right: <div className="pi-mg-right-5"><Button type="primary" size="small" danger className="pi-mg-right-3" onClick={() => modalHandler('close')}>关闭合同</Button><Button type="primary" size="small" onClick={() => modalHandler('edit')}>编辑合同</Button></div> }}>
-//         <TabPane key="1" tab="合同详情">
-//           <Detail></Detail>
-//         </TabPane>
-//         <TabPane key="2" tab="合同回款">
-//           <Receivable></Receivable>
-//         </TabPane>
-//         <TabPane key="3" tab="合同文件">
-//           <File></File>
-//         </TabPane>
-//       </Tabs>
-//     </div>
-//   </div>
-// }
-
-// // function formatSheetData(tree: iTemplateState[]): {
-
-// // }
-// export default GCsheet

+ 1 - 1
src/pages/Contract/Content/Income/components/Modal/api.ts

@@ -1,7 +1,7 @@
 import request from '@/utils/common/request'
 
 export async function apiContractIncome(bidsectionId: string) {
-  const { data } = await request.get('/api/contract/income', { bidsectionId })
+  const { data } = await request.get('/api/contract/income/section/all', { bidsectionId })
   return data
 }
 

+ 17 - 7
src/pages/Contract/Content/Income/components/TableContent/index.tsx

@@ -7,7 +7,7 @@ import Modal from 'antd/lib/modal/Modal'
 import { RadioChangeEvent } from 'antd/lib/radio'
 import { ColumnsType } from 'antd/lib/table'
 import { observer } from 'mobx-react'
-import React, { useEffect, useRef, useState } from 'react'
+import React, { KeyboardEvent, useEffect, useRef, useState } from 'react'
 import { useActivate } from 'react-activation'
 import { apiResfulContractTree } from '../../api'
 import { apiContractIncome, apiSetTemplate } from '../Modal/api'
@@ -97,13 +97,21 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
     name?: string
   }
 
-  const newLabelHandler = async (payload: iLabelHandlerProps) => {
-    payload.name = inputEl.current?.state.value
-    const { code = -1 } = await apiResfulContractTree('add', payload)
-    if (code === consts.RET_CODE.SUCCESS) {
+  const newLabelHandler = async (type: string, payload: iLabelHandlerProps) => {
+    let RET_CODE: number = -1
+    if (type === 'name') {
+      payload.name = inputEl.current?.state.value
+      const { code = -1 } = await apiResfulContractTree('add', payload)
+      RET_CODE = code
+    }
+    if (RET_CODE === consts.RET_CODE.SUCCESS) {
       contractStore.resetTree(tenderStore.bidsectionId)
     }
   }
+  // const codeEditHandler = (event: KeyboardEvent) => {
+    // event.persist()
+    // console.log(event.currentTarget.getAttribute('value'))
+  // }
   const inputEl = useRef<Input>(null)
   const modalColumns: ColumnsType<iTemplateState> = [
     {
@@ -127,14 +135,16 @@ const GCsheet: React.FC<iTableContentPorps> = ({ modalHandler, row, setRow }) =>
     {
       title: '编号',
       dataIndex: 'code',
-      width: '10%'
+      width: '15%',
+      // eslint-disable-next-line react/display-name
+      render: (text: string, row: iIncomeTree) => <span>{row.code}</span>
     },
     {
       title: '项目名称',
       dataIndex: 'name',
       render: (text:any, record: iIncomeTree) => {
         if (record.isEdit) {
-          return <Input size="small" type="text" ref={inputEl} onPressEnter={() => newLabelHandler({ id: record.parentId, bidsectionId: record.bidsectionId })} onBlur={() => newLabelHandler({ id: record.parentId, bidsectionId: record.bidsectionId })}></Input>
+          return <Input size="small" type="text" ref={inputEl} onPressEnter={() => newLabelHandler('name', { id: record.parentId, bidsectionId: record.bidsectionId })} onBlur={() => newLabelHandler('name', { id: record.parentId, bidsectionId: record.bidsectionId })}></Input>
         } else {
           return <span>{text}</span>
         }

+ 159 - 26
src/pages/Contract/Content/Income/components/Tabs/Receivable/index.tsx

@@ -1,8 +1,22 @@
+import DatePicker from '@/components/DatePicker'
+import { dayjsFomrat } from '@/utils/util'
 import { DisconnectOutlined } from '@ant-design/icons'
-import { Popconfirm, Table } from 'antd'
+import { Form, Input, Popconfirm, Table } from 'antd'
+import locale from 'antd/es/date-picker/locale/zh_CN'
 import Modal from 'antd/lib/modal/Modal'
-import { ColumnsType } from 'antd/lib/table'
 import React, { useState } from 'react'
+const source = [
+  {
+    id: '123',
+    receDate: new Date(),
+    receAmount: 199999,
+    receWay: '支票',
+    receDesigner: '张三',
+    createTime: new Date(),
+    remark: '备注内容',
+    attacment: 3
+  }
+]
 interface iReceivableProps {
   id: string
   receDate: Date
@@ -13,74 +27,193 @@ interface iReceivableProps {
   remark: string
   attacment: number
 }
+interface EditableCellProps extends React.HTMLAttributes<HTMLElement> {
+  editing: boolean;
+  dataIndex: string;
+  title: any;
+  cellType: 'DatePicker' | 'text';
+  record: iReceivableProps;
+  index: number;
+  children: React.ReactNode;
+}
+
+const EditableCell: React.FC<EditableCellProps> = ({
+  editing,
+  dataIndex,
+  title,
+  cellType,
+  children,
+  record,
+  index,
+  ...restProps
+}) => {
+  console.log(dataIndex, record)
+
+  const cellNode = cellType === 'text' ? <Input value={dataIndex && record[dataIndex]} size="small" allowClear/> : <DatePicker  value={dataIndex && record[dataIndex]} size="small" allowClear locale={locale} />
+
+  return (
+    <td {...restProps}>
+      {editing ? (
+        <Form.Item name={dataIndex} style={{ margin: 0 }} rules={[ { required: true, message: `请输入${title}!` } ]}>
+          {cellNode}
+        </Form.Item>
+      ) : (
+        children
+      )}
+    </td>
+  )
+}
+
 const Receivable:React.FC<{}> = () => {
+  const [ form ] = Form.useForm()
+  const [ data, setData ] = useState(source)
   const [ visible, setVisible ] = useState(false)
+  const [ editingKey, setEditingKey ] = useState<string>('')
   const delConfirm = (key: string) => {
     console.log(key)
-
   }
-  const data = [
-    {
-      id: '123',
-      receDate: new Date(),
-      receAmount: 199999,
-      receWay: '支票',
-      receDesigner: '张三',
-      createTime: new Date(),
-      remark: '备注内容',
-      attacment: 3
+
+  const save = async (key: React.Key) => {
+    try {
+      const row = (await form.validateFields()) as iReceivableProps
+
+      const newData = [ ...data ]
+      const index = newData.findIndex(item => key === item.id)
+      if (index > -1) {
+        const item = newData[index]
+        newData.splice(index, 1, {
+          ...item,
+          ...row
+        })
+        setData(newData)
+        setEditingKey('')
+      } else {
+        newData.push(row)
+        setData(newData)
+        setEditingKey('')
+      }
+    } catch (errInfo) {
+      console.log('Validate Failed:', errInfo)
     }
-  ]
-  const columns: ColumnsType<iReceivableProps> = [
+  }
+
+  const isEditing = (record: iReceivableProps) => record.id === editingKey
+  const columns = [
     {
       dataIndex: 'sort',
+      width: '5%',
       // eslint-disable-next-line react/display-name
-      render: (text, record, index) => {
+      render: (_: any, record: any, index: number) => {
         return <span>{index + 1}</span>
       }
     },
     {
       title: '回款日期',
-      dataIndex: 'receDate'
+      dataIndex: 'receDate',
+      editable: true,
+      width: '12%',
+      // eslint-disable-next-line react/display-name
+      render: (text: string) => <span>{dayjsFomrat(text, 'YYYY-MM-DD')}</span>
     },
     {
       title: '回款金额',
-      dataIndex: 'receAmount'
+      dataIndex: 'receAmount',
+      editable: true,
+      width: '12%',
+      // eslint-disable-next-line react/display-name
+      render: (text: string) => <span className="pi-text-right pi-width-100P">{text}</span>
     },
     {
       title: '回款方式',
       dataIndex: 'receWay',
-      align: 'left'
+      editable: true,
+      width: '12%'
     },
     {
       title: '创建人',
-      dataIndex: 'receDesigner'
+      dataIndex: 'receDesigner',
+      editable: true,
+      width: '12%'
     },
     {
       title: '创建时间',
-      dataIndex: 'createTime'
+      dataIndex: 'createTime',
+      editable: true,
+      width: '12%',
+      // eslint-disable-next-line react/display-name
+      render: (text: any) => <span>{dayjsFomrat(text, 'YYYY-MM-DD')}</span>
     },
     {
       title: '备注',
-      dataIndex: 'remark'
+      dataIndex: 'remark',
+      editable: true,
+      width: '12%'
     },
     {
       title: '附件',
       dataIndex: 'attacment',
       // eslint-disable-next-line react/display-name
-      render: (text: number) => <span><DisconnectOutlined />{text}</span>
+      render: (text: number) => <span className="pi-pointer" onClick={() => setVisible(true)}><DisconnectOutlined /> {text}</span>
     },
     {
       title: '操作',
       dataIndex: 'opreate',
+      width: '10%',
       // eslint-disable-next-line react/display-name
-      render: (text: any, record: iReceivableProps) => <div><a href="javascript:;" className="pi-link-blue">编辑</a><Popconfirm title="确认删除?" cancelText="取消" okText="确认" onConfirm={() => delConfirm(record.id)}><a href="javascript:;" className="pi-link-red pi-mg-left-5">删除</a></Popconfirm></div>
+      render: (text: any, record: iReceivableProps) => {
+        const editable = isEditing(record)
+        return (
+        <div>
+          {
+            editable ?
+            (<><span className="pi-link-blue pi-mg-right-5" onClick={() => save(record.id)}>保存</span><span className="pi-link-blue" onClick={() => setEditingKey('')}>取消</span></>)
+            :
+            <span className="pi-link-blue" onClick={() => setEditingKey(record.id)}>编辑</span>
+          }
+          <Popconfirm title="确认删除?" cancelText="取消" okText="确认" onConfirm={() => delConfirm(record.id)}>
+            <span className="pi-link-red pi-mg-left-5">删除</span>
+          </Popconfirm>
+        </div>
+        )
+      }
     }
   ]
+
+  const cancel = () => {
+    setEditingKey('')
+  }
+
+
+  const mergedColumns = columns.map(col => {
+    if (!col.editable) {
+      return col
+    }
+    return {
+      ...col,
+      onCell: (record: iReceivableProps) => ({
+        record,
+        cellType: col.dataIndex === 'createTime' || col.dataIndex === 'receDate' ? 'DatePicker' : 'text',
+        dataIndex: col.dataIndex,
+        title: col.title,
+        editing: isEditing(record)
+      })
+    }
+  })
+
   return (
     <>
-      <Table size="small" dataSource={data} columns={columns} bordered pagination={false} rowKey={record => record.id}></Table>
-      <Modal visible={visible} okText="确认" cancelText="关闭" title="附件"></Modal>
+      <Form form={form} component={false}>
+        <Table
+          components={{ body: { cell: EditableCell } }}
+          dataSource={data}
+          columns={mergedColumns}
+          bordered
+          rowClassName="editable-row"
+          pagination={{ onChange: cancel }}
+          rowKey={record => record.id}>
+        </Table>
+      </Form>
+      <Modal visible={visible} okText="确认" cancelText="关闭" title="附件" onCancel={() => setVisible(false)}></Modal>
     </>
   )
 }

File diff suppressed because it is too large
+ 0 - 2
src/utils/sheets.ts