Explorar o código

test: 测试中新增添加客户实例

lanjianrong %!s(int64=5) %!d(string=hai) anos
pai
achega
72332baf51

+ 22 - 7
src/components/Modal/src/components/addCustomerModal/index.jsx

@@ -1,18 +1,29 @@
-import React, { useState, useRef } from 'react'
-import { Form, Row, Col, Button } from 'antd'
+import React, { useState, useRef, useEffect } from 'react'
+import { Form, Row, Col, Button, message } from 'antd'
 import { validCascaderRule } from '@/components/LazyCascader'
-import { customerTypeOptions } from '@/consts/customer'
 import LazyCascader from '@/components/LazyCascader'
 import { ModalForm, ProFormText, ProFormCheckbox } from '@ant-design/pro-form'
 import { PlusOutlined } from '@ant-design/icons'
+import consts from '@/consts'
+import { queryNatures } from '@/services/contact'
 
-const AddCustomerModal = ({ clientId = '331233', ...restProps }) => {
+const AddCustomerModal = ({ clientId, onConfirm }) => {
   const formRef = useRef()
   const [modalVisible, setModalVisible] = useState(false)
+  const [natures, setNatures] = useState([])
   const layout = {
     layout: 'horizontal',
     labelCol: { flex: '100px' }
   }
+  useEffect(() => {
+    const getNatures = async () => {
+      const { code = -1, data = [] } = await queryNatures()
+      if (code === consts.RET_CODE.SUCCESS) {
+        setNatures(data)
+      }
+    }
+    !natures.length && getNatures()
+  }, [])
   const [showContactItem, setShowContactItem] = useState(false)
   return (
     <ModalForm
@@ -43,7 +54,11 @@ const AddCustomerModal = ({ clientId = '331233', ...restProps }) => {
       }
       onFinish={async values => {
         // await waitTime(2000);
-        console.log(values)
+        const { code = -1, msg = '请求失败' } = await onConfirm(values)
+        if (code === consts.RET_CODE.SUCCESS) {
+          return true
+        }
+        return message.error(msg)
       }}>
       <ProFormText
         name="companyName"
@@ -59,8 +74,8 @@ const AddCustomerModal = ({ clientId = '331233', ...restProps }) => {
         rules={[{ validator: validCascaderRule }]}>
         <LazyCascader />
       </Form.Item>
-      <ProFormCheckbox.Group name="nature" label="客户性质" options={customerTypeOptions} />
-      <ProFormText name="address" label="客户地址" placeholder="" />
+      <ProFormCheckbox.Group name="natureIds" label="客户性质" options={natures} />
+      <ProFormText name="address" label="客户地址" placeholder="" rules={[{ required: true }]} />
       <Row>
         <Col span={12}>
           <ProFormText name="fax" label="客户传真" placeholder="" />

+ 44 - 25
src/components/Modal/src/components/customerModal/index.jsx

@@ -3,43 +3,43 @@ import React, { useState, useEffect } from 'react'
 import styles from './index.less'
 import { SearchOutlined, CloseOutlined, PlusOutlined } from '@ant-design/icons'
 import { useDebounceFn } from 'ahooks'
-import { connect } from 'dva'
+import { useDispatch } from 'dva'
 import AddCustomerModal from '../addCustomerModal'
-import { createCustomer } from '@/services/customer'
+import { createCustomer, queryCustomers } from '@/services/customer'
 import consts from '@/basic/consts'
 import { formatValues } from '@/components/LazyCascader'
+import { apiChangeCustomer } from '@/services/contact'
 
-const SearchModal = ({ data, total, dispatch }) => {
-  const [form] = Form.useForm()
-  const initData = payload => {
-    dispatch({
-      type: 'customer/fetch',
-      payload
-    })
-  }
-  useEffect(() => {
-    if (!data.length) {
-      initData({ page: 1, size: 10 })
-    }
-  }, [])
+const SearchModal = ({ clientId, onSelect }) => {
+  const dispatch = useDispatch()
   const [state, setState] = useState({
+    customer: [],
     searchVal: '',
     page: 1,
-    size: 10
+    size: 10,
+    total: 0
   })
+  const initData = async params => {
+    const { data = { customer: [], total: 0 }, code = -1 } = await queryCustomers(params)
+    if (code === consts.RET_CODE.SUCCESS) {
+      setState({ ...state, ...data })
+    }
+  }
 
-  const [visible, setVisible] = useState(false)
+  useEffect(() => {
+    initData({ page: 1, size: 10 })
+  }, [])
   const onChange = e => {
     const { value = '' } = e.target || e.currentTarget
     initData({ search: value, page: state.page, size: state.size })
   }
 
+  const { run: handleInputChange } = useDebounceFn(onChange)
   const closeModal = () => {
     dispatch({
       type: 'single/changeModal'
     })
   }
-  const { run } = useDebounceFn(onChange)
 
   const addConfirm = async values => {
     const payload = formatValues(values)
@@ -49,30 +49,49 @@ const SearchModal = ({ data, total, dispatch }) => {
       initData()
     }
   }
+
+  // 联系人关联客户
+  const connectCustomer = async (customerId, companyName) => {
+    const { code = -1 } = await apiChangeCustomer({ id: clientId, customerId })
+    if (code === consts.RET_CODE.SUCCESS) {
+      onSelect(companyName)
+      closeModal()
+    }
+  }
   return (
     <div className={['ant-modal-content', styles.modalContainer].join(' ')}>
       <div className={styles.modalHeader}>
         <Input
           prefix={<SearchOutlined />}
-          onChange={run}
           className={styles.inputSearch}
           placeholder="输入客户名称搜索"
+          onChange={handleInputChange}
         />
         <span className={styles.closeIcon}>
           <CloseOutlined onClick={closeModal} />
         </span>
       </div>
       <div className={styles.modalContent}>
-        <span>11</span>
+        {state.customer.map(item => (
+          <div key={item.id} className={styles.card}>
+            <div className="zh-flex-sub">{item.companyName}</div>
+            <div className="zh-flex-sub zh-justify-between">
+              <span>{item.local}</span>
+              <Button
+                type="primary"
+                size="small"
+                onClick={() => connectCustomer(item.id, item.companyName)}>
+                关联
+              </Button>
+            </div>
+          </div>
+        ))}
       </div>
       <div className={styles.modalFooter}>
-        <AddCustomerModal />
+        <AddCustomerModal onConfirm={addConfirm} />
       </div>
     </div>
   )
 }
 
-export default connect(({ customer }) => ({
-  data: customer.customer,
-  total: customer.total
-}))(SearchModal)
+export default SearchModal

+ 18 - 0
src/components/Modal/src/components/customerModal/index.less

@@ -13,8 +13,26 @@
     }
   }
   .modalContent {
+    max-height: 63vh;
+    padding: 1.5rem;
+    overflow-y: auto;
     border-top: 1px solid #dee2e6;
     border-bottom: 1px solid #dee2e6;
+    .card {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 1rem;
+      word-wrap: break-word;
+      background-color: #ffffff;
+      background-clip: border-box;
+      border: 1px solid rgba(0, 0, 0, 0.08);
+      border-radius: 4px;
+      box-shadow: 0 0 13px 0 rgb(74 53 107 / 8%) !important;
+      &:not(:last-of-type) {
+        margin-bottom: 1rem;
+      }
+    }
   }
 
   .modalFooter {

+ 6 - 5
src/components/PopContent/Contacts/ContanctForm.jsx

@@ -12,17 +12,18 @@ const ContanctForm = props => {
       span: 24
     },
     {
-      dataIndex: 'companyName',
-      label: '客户名称',
-      span: 24
-    },
-    {
       dataIndex: 'district',
       label: '联系人地区',
       editCellType: 'cascader',
       span: 24
     },
     {
+      dataIndex: 'companyName',
+      label: '客户名称',
+      editCellType: '',
+      span: 24
+    },
+    {
       dataIndex: 'gender',
       label: '性别',
       editCellType: 'select',

+ 2 - 2
src/components/SvgIcon/index.jsx

@@ -1,6 +1,6 @@
 import { createFromIconfontCN } from '@ant-design/icons'
 import setting from '../../../config/defaultSettings'
-
+import '@icon-park/react/styles/index.less'
 import Icon from '@icon-park/react/es/all'
 import React from 'react'
 
@@ -9,4 +9,4 @@ const IconFont = createFromIconfontCN({
 })
 export default IconFont
 
-export const IconPark = props => <Icon size="16" {...props} />
+export const IconPark = props => <Icon {...props} />

+ 1 - 1
src/layouts/BasicLayout.jsx

@@ -43,7 +43,7 @@ const menuDataRender = menuList =>
       children: item.children ? menuDataRender(item.children) : undefined
     }
     if (item.icon) {
-      localItem.icon = <IconPark type={item.icon} className="anticon anticon-form" />
+      localItem.icon = <IconPark type={item.icon} size="16" className="anticon anticon-form" />
     }
     return Authorized.check(item.authority, localItem, null)
   })

+ 31 - 31
src/models/customer.js

@@ -1,32 +1,32 @@
-import consts from '@/consts'
-import { queryCustomers } from '@/services/customer'
+// import consts from '@/consts'
+// import { queryCustomers } from '@/services/customer'
 
-export default {
-  namespace: 'customer',
-  state: {
-    customer: [], // 客户列表
-    total: 0
-  },
-  effects: {
-    *fetch({ payload }, { call, put }) {
-      const response = yield call(queryCustomers, payload)
-      if (response && response.code === consts.RET_CODE.SUCCESS) {
-        yield put({
-          type: 'changeState',
-          payload: {
-            data: response.data
-          }
-        })
-      }
-    }
-  },
-  reducers: {
-    // 通用reducer
-    changeState(state, action) {
-      return {
-        ...state,
-        ...action.payload
-      }
-    }
-  }
-}
+// export default {
+//   namespace: 'customer',
+//   state: {
+//     customer: [], // 客户列表
+//     total: 0
+//   },
+//   effects: {
+//     *fetch({ payload }, { call, put }) {
+//       const response = yield call(queryCustomers, payload)
+//       if (response && response.code === consts.RET_CODE.SUCCESS) {
+//         yield put({
+//           type: 'changeState',
+//           payload: {
+//             data: response.data
+//           }
+//         })
+//       }
+//     }
+//   },
+//   reducers: {
+//     // 通用reducer
+//     changeState(state, action) {
+//       return {
+//         ...state,
+//         ...action.payload
+//       }
+//     }
+//   }
+// }

+ 55 - 0
src/pages/Customer/Contact/components/ChangeCompany/index.jsx

@@ -0,0 +1,55 @@
+import { useModal } from '@/components/Modal'
+import { IconPark } from '@/components/SvgIcon'
+import { Card, Tooltip } from 'antd'
+import React, { useState } from 'react'
+import styles from './index.less'
+import SearchModal from '@/components/Modal/src/components/customerModal'
+
+const ChangeCompanyInput = props => {
+  const { toggleModal, setModalProps } = useModal()
+  const { value = '' } = props
+  const [companyName, setCompanyName] = useState(value)
+  const changeCompany = name => {
+    setCompanyName(name)
+  }
+  const handleConnectContract = () => {
+    setModalProps({
+      width: '60vw',
+      modalRender: node => <SearchModal node={node} onSelect={changeCompany} />,
+      onCancel: () => toggleModal()
+    })
+    toggleModal()
+  }
+  const notValueRender = (
+    <div onClick={handleConnectContract} className={styles.notCompanyContent}>
+      {/* style={{ transform: 'rotate(-90deg)' }} */}
+      <IconPark type="link-one" size="14" />
+      <span className="zh-mg-left-5">关联客户</span>
+    </div>
+  )
+
+  const normalValueRender = (
+    <Card size="small">
+      <div className="zh-flex-row zh-justify-between">
+        <div className={styles.nameContent}>{companyName}</div>
+        <div>
+          <Tooltip placement="left" title="移除客户">
+            <span className={styles.unlinkIcon}>
+              <IconPark type="link-interrupt" />
+            </span>
+          </Tooltip>
+          <Tooltip placement="left" title="移除客户" className="zh-mg-left-5">
+            <span className={styles.switchIcon}>
+              <IconPark type="switch" />
+            </span>
+          </Tooltip>
+        </div>
+      </div>
+    </Card>
+  )
+  return (
+    <div className={styles.companyInput}>{companyName ? normalValueRender : notValueRender}</div>
+  )
+}
+
+export default ChangeCompanyInput

+ 60 - 0
src/pages/Customer/Contact/components/ChangeCompany/index.less

@@ -0,0 +1,60 @@
+.companyInput {
+  padding: 8px 12px 6px;
+  color: @primary-color;
+  background: #f7f7f7;
+  border: 1px solid #f7f7f7;
+  &:hover {
+    background: #f2f2f2;
+    border-color: #f2f2f2;
+  }
+  :global(.ant-card-small > .ant-card-body) {
+    padding: 0.5rem;
+  }
+  .notCompanyContent {
+    cursor: pointer;
+    &:hover {
+      color: #967bbd;
+    }
+  }
+  .nameContent {
+    color: @primary-color;
+    cursor: pointer;
+    &:hover {
+      color: #967bbd;
+    }
+  }
+  .unlinkIcon {
+    padding: 0 3px;
+    color: #fd3995;
+    line-height: 1.15rem;
+    border: 1px solid #fd3995;
+    border-radius: 0.25rem;
+    cursor: pointer;
+    &:hover {
+      color: #ffffff;
+      background-color: #fd3995;
+      border-color: #fd3995;
+      transition: all 0.2s ease-in-out;
+    }
+    // &:active {
+    //   box-shadow: 0 2px 5px rgb(0 0 0 / 15%) inset;
+    // }
+  }
+  .switchIcon {
+    padding: 0 3px;
+    color: #886ab5;
+    line-height: 1.15rem;
+    border: 1px solid #886ab5;
+    border-radius: 0.25rem;
+    cursor: pointer;
+    &:hover {
+      color: #ffffff;
+      background-color: #886ab5;
+      border-color: #886ab5;
+      transition: all 0.2s ease-in-out;
+    }
+    // &:active {
+    //   box-shadow: 0 2px 5px rgb(0 0 0 / 15%) inset;
+    // }
+  }
+}

+ 3 - 15
src/pages/Customer/Test/index.jsx

@@ -4,6 +4,7 @@ import EditableForm from '@/components/EditableForm'
 import { useModal } from '@/components/Modal'
 import { connect } from 'dva'
 import SearchModal from '@/components/Modal/src/components/customerModal'
+import ChangeCompanyInput from '../Contact/components/ChangeCompany'
 
 const Test = () => {
   const { toggleModal, setModalProps, dispatch } = useModal()
@@ -68,31 +69,18 @@ const Test = () => {
   ]
   const onClick = () => {
     setModalProps({
-      // title: (
-      //   <Input
-      //     prefix={<IconPark type="search" />}
-      //     placeholder="输入客户名称搜索"
-      //     style={{ width: '90%' }}
-      //   />
-      // ),
       modalRender: node => <SearchModal node={node} />,
       onCancel: () => toggleModal()
     })
     toggleModal()
-    // console.log(iRef)
-    // iRef.current.input.focus()
   }
-  // const modalSearchChange = e => {
-  //   dispatch({
-  //     type: 'single/setModalSearchVal',
-  //     payload
-  //   })
-  // }
+
   return (
     <Card bordered={false}>
       <EditableForm dataSource={record} columns={columns} />
       <Button onClick={onClick}>click</Button>
       <Cascader ref={iRef} options={options} />
+      <ChangeCompanyInput />
     </Card>
   )
 }

+ 21 - 2
src/services/contact.js

@@ -28,7 +28,7 @@ export async function apiContactDetail(id) {
  * @param {*} payload 载荷
  */
 export async function apiAddClient(payload) {
-  const data = await request.post('/api/client/add',{ data:{ ...payload } })
+  const data = await request.post('/api/client/add', { data: { ...payload } })
   return data
 }
 
@@ -37,6 +37,25 @@ export async function apiAddClient(payload) {
  * @param {*} payload 载荷
  */
 export async function apiAddService(payload) {
-  const data = await request.post('/api/servicelog/add',{ data:{ ...payload } })
+  const data = await request.post('/api/servicelog/add', { data: { ...payload } })
+  return data
+}
+
+/**
+ * 联系人更换客户
+ * @param {*} payload
+ * @returns
+ */
+export async function apiChangeCustomer(payload) {
+  const data = await request.post('/api/client/change/customer', { data: { ...payload } })
+  return data
+}
+
+/**
+ * 获取客户性质列表
+ * @returns
+ */
+export async function queryNatures() {
+  const data = await request('/api/customer/nature')
   return data
 }