Jelajahi Sumber

fix: 地区级联组件支持单组件以及form表单自定义组件调用

lanjianrong 5 tahun lalu
induk
melakukan
e13ebea0ed

+ 3 - 0
package.json

@@ -51,6 +51,7 @@
     "@ant-design/pro-layout": "^6.5.15",
     "@ant-design/pro-table": "^2.9.16",
     "@umijs/route-utils": "^1.0.33",
+    "ahooks": "^2.10.0",
     "antd": "^4.8.0",
     "classnames": "^2.2.6",
     "dayjs": "^1.10.4",
@@ -92,6 +93,8 @@
     "detect-installer": "^1.0.1",
     "enzyme": "^3.11.0",
     "eslint": "^7.18.0",
+    "eslint-plugin-jsx": "^0.1.0",
+    "eslint-plugin-typescript": "^0.14.0",
     "express": "^4.17.1",
     "gh-pages": "^3.0.0",
     "jsdom-global": "^3.0.2",

+ 29 - 23
src/components/EditableForm/editableFormItem.jsx

@@ -2,12 +2,12 @@ import consts from '@/basic/consts'
 import { editFormApi } from '@/services/common'
 import { Input, Row, Col, Select } from 'antd'
 import React, { useState, useEffect, useRef } from 'react'
+import LazyCascader from '../LazyCascader'
 import './index.less'
 
 const EditableFormItem = ({ label, dataIndex, record, dataId, span, editCellType, requestUrl }) => {
-
   const [editing, setEditing] = useState(false)
-  const inputRef = useRef(null)
+  const iRef = useRef(null)
   const [val, setVal] = useState('')
   const { Option } = Select
   useEffect(() => {
@@ -16,7 +16,7 @@ const EditableFormItem = ({ label, dataIndex, record, dataId, span, editCellType
   useEffect(() => {
     // 处于编辑状态自动聚焦
     if (editing) {
-      inputRef.current.focus()
+      iRef.current.focus()
     }
     // 当val为空时,需要设置默认值
     if (!val) {
@@ -29,19 +29,27 @@ const EditableFormItem = ({ label, dataIndex, record, dataId, span, editCellType
     setVal(record)
   }, [dataId])
 
-
   const toggleEdit = () => {
     setEditing(!editing)
   }
 
   const handleConfirm = async value => {
-    const { code = -1 } = await editFormApi(requestUrl, { [dataIndex]: value, id: dataId })
+    const params = { id: dataId }
+    if (editCellType === 'cascader') {
+      const [province = '', city = '', area = ''] = value
+      params.province = province
+      params.city = city
+      params.area = area
+    } else {
+      params[dataIndex] = value
+    }
+    const { code = -1 } = await editFormApi(requestUrl, { ...params })
     if (code === consts.RET_CODE.SUCCESS) {
       toggleEdit()
     }
   }
   const save = async e => {
-    const { value = '' } = e.currentTarget
+    const { value } = e.currentTarget
     if (value === record || !value) {
       return
     }
@@ -50,36 +58,34 @@ const EditableFormItem = ({ label, dataIndex, record, dataId, span, editCellType
     toggleEdit()
   }
 
-  // function handleSelectChange(value) {
-  //   console.log(`selected ${value}`)
-  // }
-  // const handleOnChange = e => {
-  //   setVal(value)
-  //   const { value = '' } = e.currentTarget
-  // }
-
   let cell = null
 
   if (editing) {
     const options = (
       <>
-        <Option key="男" vlaue="男">男</Option>
-        <Option key="女" vlaue="女">女</Option>
+        <Option key="男" vlaue="男">
+          男
+        </Option>
+        <Option key="女" vlaue="女">
+          女
+        </Option>
       </>
     )
     switch (editCellType) {
       case 'cascader':
-        // cell = ()
+        cell = (
+          <LazyCascader
+            defaultValue={val}
+            onChange={value => save({ currentTarget: { value } })}></LazyCascader>
+        )
         break
       case 'select':
         cell = (
           <Select
             defaultValue={val}
-            ref={inputRef}
             // onBlur={save}
-            onChange={(value) => save({ currentTarget: { value } })}
-            className="zh-width-100P"
-          >
+            onChange={value => save({ currentTarget: { value } })}
+            className="zh-width-100P">
             {options}
           </Select>
         )
@@ -87,11 +93,11 @@ const EditableFormItem = ({ label, dataIndex, record, dataId, span, editCellType
       default:
         cell = (
           <Input
-            ref={inputRef}
+            ref={iRef}
             onPressEnter={save}
             onBlur={save}
             defaultValue={val}
-          // onChange={handleOnChange}
+            // onChange={handleOnChange}
           />
         )
         break

+ 51 - 0
src/components/LazyCascader/LazyCascader.jsx

@@ -0,0 +1,51 @@
+import { Cascader } from 'antd'
+import React, { useEffect } from 'react'
+import { connect } from 'dva'
+
+const LazyCascader = props => {
+  const { options, dispatch, onChange, ...resetProps } = props
+  useEffect(() => {
+    if (!options.length) {
+      dispatch({
+        type: 'district/fetch'
+      })
+    }
+  }, [])
+
+  const triggerChange = value => {
+    if (onChange) {
+      onChange(value)
+    }
+  }
+  const handleOnChange = value => {
+    triggerChange(value)
+  }
+
+  const loadData = selectedOptions => {
+    const targetOption = selectedOptions[selectedOptions.length - 1]
+    targetOption.loading = true
+
+    // load options lazily
+    dispatch({
+      type: 'district/fetch',
+      payload: { parentId: targetOption.id, level: targetOption.level }
+    })
+  }
+
+  return (
+    <Cascader
+      {...resetProps}
+      placeholder="省/市/区"
+      options={options}
+      onChange={handleOnChange}
+      loadData={loadData}
+      // allowClear={false}
+      fieldNames={{ label: 'name', value: 'id' }}
+      changeOnSelect
+    />
+  )
+}
+
+export default connect(({ district }) => ({
+  options: district.data
+}))(LazyCascader)

+ 4 - 56
src/components/LazyCascader/index.jsx

@@ -1,57 +1,5 @@
-import { Cascader } from 'antd'
-import React, { useEffect } from 'react'
-import { connect } from 'dva'
+import LazyCascader from './LazyCascader'
+import { validCascaderRule, formatValues } from './util'
 
-const LazyCascader = props => {
-  const { options, dispatch, onChange, ...resetProps } = props
-  useEffect(() => {
-    if (!options.length) {
-      dispatch({
-        type: 'district/fetch'
-      })
-    }
-  }, [])
-
-  const triggerChange = value => {
-    if (onChange) {
-      onChange(value)
-    }
-  }
-  const handleOnChange = value => {
-    triggerChange(value)
-  }
-
-  const loadData = selectedOptions => {
-    const targetOption = selectedOptions[selectedOptions.length - 1]
-    targetOption.loading = true
-
-    // load options lazily
-    dispatch({
-      type: 'district/fetch',
-      payload: { parentId: targetOption.id, level: targetOption.level }
-    })
-  }
-
-  return (
-    <Cascader
-      {...resetProps}
-      options={options}
-      onChange={handleOnChange}
-      loadData={loadData}
-      // allowClear={false}
-      fieldNames={{ label: 'name', value: 'id' }}
-      changeOnSelect
-      defaultValue={['省', '市', '区']}
-    />
-  )
-}
-
-export const validCascaderRule = (_, value) => {
-  if (!value || !value.length) {
-    return Promise.reject(new Error('请选择地区'))
-  }
-  return Promise.resolve(value)
-}
-export default connect(({ district }) => ({
-  options: district.data
-}))(LazyCascader)
+export default LazyCascader
+export { validCascaderRule, formatValues }

+ 17 - 0
src/components/LazyCascader/util.js

@@ -0,0 +1,17 @@
+export const validCascaderRule = (_, value) => {
+  if (!value || !value.length) {
+    return Promise.reject(new Error('请选择地区'))
+  }
+  return Promise.resolve(value)
+}
+
+/**
+ * 格式化value中的district,返回form提交所需的value
+ * @param {object} value
+ * @returns {object}
+ */
+export function formatValues(value) {
+  const { district = [], ...resetVal } = value
+  const [province = '', city = '', area = ''] = district
+  return { province, city, area, ...resetVal }
+}

+ 2 - 1
src/components/PopContent/Contacts/ContanctForm.jsx

@@ -19,6 +19,7 @@ const ContanctForm = props => {
     {
       dataIndex: 'district',
       label: '联系人地区',
+      editCellType: 'cascader',
       span: 24
     },
     {
@@ -120,7 +121,7 @@ const ContanctForm = props => {
           {client.staffName} 创建于{client.createTime}
         </Col>
       </Row>
-      <EditableForm dataSource={client} columns={columns} tartgetUrl="/api/client/update"/>
+      <EditableForm dataSource={client} columns={columns} tartgetUrl="/api/client/update" />
     </div>
   )
 }

+ 11 - 6
src/hooks/useAutoTable.js

@@ -1,4 +1,5 @@
 import { useState, useEffect } from "react"
+import { useDebounceFn } from 'ahooks'
 
 /**
  *
@@ -7,21 +8,25 @@ import { useState, useEffect } from "react"
  * @param {number} needSubtractWidth 被裁去的宽度
  */
 const useAutoTable = (collapsed, needSubtractHeight, needSubtractWidth) => {
-  // console.log(collapsed)
 
 
   const [scroll, setScroll] = useState({ x: document.body.clientWidth - needSubtractWidth, y: document.body.clientHeight - needSubtractHeight })
+  // const { run, cancel } = useDebounceFn(handleScroll, { wait: 5000})
+  const { run, cancel } = useDebounceFn(() => setScroll({ ...scroll, x: document.body.clientWidth - needSubtractWidth, y: document.body.clientHeight - needSubtractHeight }))
+  useEffect(() => {
+    run()
+    return () => cancel()
 
+  }, [collapsed])
   useEffect(() => {
-    const handleScroll = () => setScroll({ ...scroll, x: document.body.clientWidth - needSubtractWidth, y: document.body.clientHeight - needSubtractHeight })
-    window.addEventListener('resize', handleScroll)
+    window.addEventListener('resize', run)
     return () => {
-      window.removeEventListener('resize', handleScroll)
+      window.removeEventListener('resize', cancel)
     }
-  }, [collapsed])
+  }, [])
 
   // 实际返回的宽度要大一点让table出现滚动条,反正折叠菜单栏卡顿
-  return [scroll.x, scroll.y]
+  return [scroll.x + 50, scroll.y]
 }
 
 export default useAutoTable

+ 21 - 9
src/pages/Customer/Contact/components/AddContact/index.jsx

@@ -2,6 +2,7 @@ import React, { useEffect } from 'react'
 import { Form, Input, Modal, Row, Col, Select } from 'antd'
 import LazyCascader, { validCascaderRule } from '@/components/LazyCascader'
 import { useForm } from 'antd/lib/form/Form'
+import { formatValues } from '@/components/LazyCascader'
 
 const Addcontact = props => {
   const [form] = useForm()
@@ -13,7 +14,12 @@ const Addcontact = props => {
   function handleChange(value) {
     console.log(`selected ${value}`)
   }
-  // useEffect(() => {}, [visible])
+
+  useEffect(() => {
+    if (visible) {
+      form.resetFields()
+    }
+  }, [])
   return (
     <div>
       <Modal
@@ -24,8 +30,8 @@ const Addcontact = props => {
         confirmLoading={loading}
         onOk={() => {
           form.validateFields().then(values => {
-            form.resetFields()
-            onConfirm(values)
+            const params = formatValues(values)
+            onConfirm(params)
           })
         }}>
         <Form {...layout} form={form} name="basic">
@@ -34,7 +40,7 @@ const Addcontact = props => {
               <Form.Item
                 label="姓名"
                 name="clientName"
-                rules={[{ required: true, message: '姓名' }]}>
+                rules={[{ required: true, message: '请输入姓名' }]}>
                 <Input />
               </Form.Item>
             </Col>
@@ -52,7 +58,10 @@ const Addcontact = props => {
             </Col>
             <Col span={12} />
             <Col span={12}>
-              <Form.Item label="性别" name="gender" rules={[{ message: '性别' }]}>
+              <Form.Item
+                label="性别"
+                name="gender"
+                rules={[{ required: true, message: '请选择性别' }]}>
                 <Select onChange={handleChange}>
                   <Option key="男" value="男">
@@ -64,7 +73,7 @@ const Addcontact = props => {
               </Form.Item>
             </Col>
             <Col span={12}>
-              <Form.Item label="昵称" name="niceName" rules={[{ message: '昵称' }]}>
+              <Form.Item label="昵称" name="niceName">
                 <Input />
               </Form.Item>
             </Col>
@@ -72,17 +81,20 @@ const Addcontact = props => {
               <Form.Item
                 label="手机"
                 name="telephone"
-                rules={[{ required: true, message: '手机' }]}>
+                rules={[{ required: true, message: '请输入手机号码' }]}>
                 <Input />
               </Form.Item>
             </Col>
             <Col span={12}>
-              <Form.Item label="QQ" name="qq" rules={[{ required: true, message: 'QQ' }]}>
+              <Form.Item label="QQ" name="qq" rules={[{ required: true, message: '请输入QQ号码' }]}>
                 <Input />
               </Form.Item>
             </Col>
             <Col span={12}>
-              <Form.Item label="电话" name="phone" rules={[{ required: true, message: '电话' }]}>
+              <Form.Item
+                label="电话"
+                name="phone"
+                rules={[{ required: true, message: '请输入电话/座机号' }]}>
                 <Input />
               </Form.Item>
             </Col>

+ 11 - 7
src/pages/Customer/Contact/index.jsx

@@ -4,7 +4,7 @@ import ProTable from '@ant-design/pro-table'
 // import { PageHeaderWrapper } from '@ant-design/pro-layout';
 import { Button, message, Tag } from 'antd'
 import { connect } from 'dva'
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useState, useRef } from 'react'
 import SvgIcon from '@/components/SvgIcon'
 import useAutoTable from '@/hooks/useAutoTable'
 import consts from '@/consts'
@@ -17,6 +17,7 @@ const Contact = props => {
   const needSubtractHeight = 48 + 48 + 48 + 64 + 24 + 32 // 需要被裁掉的高度
   const needSubtractWidth = (collapsed ? 208 : 48) + 48 + 48 + 48 // 需要被裁掉的高度
 
+  const tRef = useRef()
   const [x, y] = useAutoTable(collapsed, needSubtractHeight, needSubtractWidth)
 
   const [state, setState] = useState({
@@ -43,6 +44,9 @@ const Contact = props => {
     } = await queryContact(payload)
     if (code === consts.RET_CODE.SUCCESS) {
       setState({ ...state, data: client, journal: log, servicelist: serviceLog, total })
+      if (addClient.visible) {
+        setAddClient({ ...addClient, loading: false, visible: false })
+      }
     }
   }
 
@@ -50,12 +54,11 @@ const Contact = props => {
     setAddClient({ ...addClient, loading: true })
     const { code = -1 } = await apiAddClient(values)
     if (code === consts.RET_CODE.SUCCESS) {
-      message.success('新增成功')
-      setState({ ...state, data: values })
+      await initData()
+      return message.success('新增成功')
     }
     // 请求完了
-    setAddClient({ ...addClient, loading: false, visible: false })
-    await initData()
+    setAddClient({ ...addClient, loading: false })
   }
 
   // 展示drawer
@@ -347,8 +350,9 @@ const Contact = props => {
     initData({ search: value })
   }
 
-  const onDistrictChange = payload => {
-    initData({ ...payload })
+  const onDistrictChange = value => {
+    const [province = '', city = '', area = ''] = value
+    initData({ province, city, area })
   }
   return (
     // <PageHeaderWrapper></PageHeaderWrapper>

+ 5 - 3
src/pages/user/login/index.jsx

@@ -29,7 +29,7 @@ const LoginMessage = ({ content }) => (
 const Login = props => {
   const { userLogin = {}, submitting } = props
   const { status } = userLogin
-  const [ type, setType ] = useState('account')
+  const [type, setType] = useState('account')
   const intl = useIntl()
 
   const handleSubmit = values => {
@@ -44,7 +44,9 @@ const Login = props => {
     <div className={styles.main}>
       <ProForm
         initialValues={{
-          autoLogin: true
+          autoLogin: true,
+          username: '蔡频',
+          password: '123456'
         }}
         submitter={{
           render: (_, dom) => dom.pop(),
@@ -76,7 +78,7 @@ const Login = props => {
           /> */}
         </Tabs>
 
-        {status !== consts.RET_CODE.SUCCESS &&  !submitting && (
+        {status !== consts.RET_CODE.SUCCESS && !submitting && (
           <LoginMessage
             content={intl.formatMessage({
               id: 'pages.login.accountLogin.errorMessage',