Bläddra i källkod

Merge branch 'master' of http://192.168.1.41:3000/outaozhen/cldV2react

outaozhen 5 år sedan
förälder
incheckning
e20a6f9787

+ 8 - 8
config/config.js

@@ -9,7 +9,7 @@ import defaultSettings from './defaultSettings'
 import proxy from './proxy'
 import routes from './routes'
 import windicss from 'windicss-webpack-plugin/dist/index'
-const { REACT_APP_ENV } = process.env
+const { NODE_ENV, REACT_APP_ENV } = process.env
 export default defineConfig({
   hash: true,
   antd: {},
@@ -38,12 +38,12 @@ export default defineConfig({
   targets: {
     ie: 11
   },
-  terserOptions: {
-    compress: {
-      drop_console: REACT_APP_ENV === 'prod' ? true : false,
-      drop_debugger: REACT_APP_ENV === 'prod' ? true : false
-    }
-  },
+  // terserOptions: {
+  //   compress: {
+  //     drop_console: NODE_ENV === 'production' ? true : false,
+  //     drop_debugger: NODE_ENV === 'production' ? true : false
+  //   }
+  // },
   // umi routes: https://umijs.org/docs/routing
   routes,
   // Theme for antd: https://ant.design/docs/react/customize-theme-cn
@@ -78,7 +78,7 @@ export default defineConfig({
     config.plugin('windicss').use(windicss)
     config.plugin('antd-dayjs-webpack-plugin').use(AntdDayjsWebpackPlugin)
 
-    if (REACT_APP_ENV === 'prod') {
+    if (NODE_ENV === 'production') {
       config.merge({
         optimization: {
           minimize: true,

+ 6 - 6
config/routes.js

@@ -60,13 +60,13 @@ export default [
         icon: 'icon-usd-circle',
         component: './Customer/Business',
         access: 'authRouteFilter'
-      },
-      {
-        path: '/customer/test',
-        name: 'test',
-        icon: 'icon-usd-circle',
-        component: './Customer/Test'
       }
+      // {
+      //   path: '/customer/test',
+      //   name: 'test',
+      //   icon: 'icon-usd-circle',
+      //   component: './Customer/Test'
+      // }
     ]
   },
   {

+ 15 - 4
src/components/EditableForm/src/components/ChangeDatePicker.jsx

@@ -3,22 +3,33 @@ import { DatePicker } from 'antd'
 import dayjs, { isDayjs } from 'dayjs'
 
 const ChangeDatePicker = props => {
-  const { defaultValue, refinstance, onSave } = props
+  const { defaultValue, refinstance, onSave, defaultOpen = false, ...restProps } = props
   const [val, setVal] = useState(defaultValue)
+  const [open, setOpen] = useState(defaultOpen)
   const handleOnChange = d => {
+    if (!d) return
     const date = d.format('YYYY-MM-DD')
     setVal(date)
+    onSave({ currentTarget: { value: date } })
+  }
+
+  const handleOnOpenChange = e => {
+    setOpen(e)
+    if (!e && val === defaultValue) {
+      onSave({ currentTarget: { value: null } })
+    }
   }
   return (
     <DatePicker
-      defaultOpen={true}
+      open={open}
+      value={isDayjs(val) ? val : dayjs(val)}
       style={{ width: '100%' }}
       placeholder="请选择日期"
       ref={refinstance}
-      allowClear
-      defaultValue={isDayjs(defaultValue) ? defaultValue : dayjs(defaultValue)}
       onBlur={() => onSave({ currentTarget: { value: val } })}
       onChange={handleOnChange}
+      onOpenChange={handleOnOpenChange}
+      {...restProps}
     />
   )
 }

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 20 - 0
src/components/EditableForm/src/components/ImagePreview.jsx


+ 11 - 2
src/components/EditableForm/src/editableFormItem.jsx

@@ -9,6 +9,7 @@ import ChangeSelect from './components/ChangeSelect'
 import ChangeTreeSelect from './components/ChangeTreeSelect'
 import ChangeDatePicker from './components/ChangeDatePicker'
 import './index.less'
+import { isNull } from '@/utils/is'
 
 const extraValArr = ['cascader', 'select', 'treeSelect'] // 需要设置ExtraVal的类型
 const EditableFormItem = ({
@@ -131,7 +132,7 @@ const EditableFormItem = ({
   }
   const save = async e => {
     const { value, local = '' } = e.currentTarget
-    if (value === record || !value) {
+    if (value === record || isNull(value)) {
       toggleEdit()
       return
     }
@@ -155,6 +156,7 @@ const EditableFormItem = ({
             className="w-full"
             onChange={(value, local) => save({ currentTarget: { value, local } })}
             popupVisible={true}
+            showFooter={true}
             onBlur={save}
           />
         )
@@ -179,7 +181,14 @@ const EditableFormItem = ({
         )
         break
       case 'datePicker':
-        cell = <ChangeDatePicker onSave={save} defaultValue={state.val} />
+        cell = (
+          <ChangeDatePicker
+            defaultOpen={true}
+            onSave={save}
+            defaultValue={state.val}
+            allowClear={false}
+          />
+        )
         break
       default:
         cell = (

+ 114 - 5
src/components/LazyCascader/LazyCascader.jsx

@@ -1,10 +1,30 @@
-import { Cascader } from 'antd'
-import React, { forwardRef, useEffect } from 'react'
+import { getAuthCache } from '@/utils/auth'
+import { CASCADER_HISTORY_KEY } from '@/utils/cache/cacheEnum'
+import { Cascader, Divider, Tag } from 'antd'
+import React, { forwardRef, useEffect, useState, useLayoutEffect } from 'react'
 import { connect } from 'umi'
 import styles from './index.less'
+import { serializeRecord } from './util'
 
 const CLazyCascader = props => {
-  const { refinstance, options, dispatch, onChange, changeOnSelect = false, ...resetProps } = props
+  const {
+    refinstance,
+    options,
+    dispatch,
+    onChange,
+    changeOnSelect = false,
+    popupVisible = false,
+    showFooter = false,
+    defaultValue,
+    onBlur,
+    ...resetProps
+  } = props
+  const [visible, setVisible] = useState(popupVisible)
+
+  const [historyRecord, setHistoryRecord] = useState(
+    showFooter ? getAuthCache(CASCADER_HISTORY_KEY) || [] : []
+  )
+  const [cValue, setValue] = useState(defaultValue)
   useEffect(() => {
     if (!options.length) {
       dispatch({
@@ -18,9 +38,23 @@ const CLazyCascader = props => {
       onChange(value, local)
     }
   }
+
   const handleOnChange = (value, selectedOptions) => {
+    setValue(value)
+    const len = value?.length
+    if (len === 3) {
+      const { name, id } = selectedOptions[2]
+      const htr = serializeRecord(
+        name,
+        id,
+        selectedOptions.map(item => ({ id: item.id, name: item.name }))
+      )
+      if (htr?.length) {
+        setHistoryRecord(htr)
+      }
+    }
     const local = selectedOptions.reduce((prev, curr) => {
-      return `${prev}${prev ? ',' : ''}${curr.name}`
+      return `${prev}${prev ? ' / ' : ''}${curr.name}`
     }, '')
     triggerChange(value, local)
   }
@@ -36,17 +70,92 @@ const CLazyCascader = props => {
     })
   }
 
+  const handleTagClick = (values, selectedOptions) => {
+    // 加载可能dva没有缓存的地区
+    dispatch({
+      type: 'district/fetchWithValues',
+      payload: values
+    })
+    // 触发级联的onChange回调
+    handleOnChange(values, selectedOptions)
+    // 手动将pop浮层隐藏
+    setVisible(false)
+  }
+
+  useLayoutEffect(() => {
+    document.querySelector('.Cascader-Body')?.addEventListener(
+      'DOMSubtreeModified',
+      () => {
+        let width = 0
+        const menus = document.getElementsByClassName('ant-cascader-menu')
+        // eslint-disable-next-line no-plusplus
+        for (let i = 0; i < menus.length; i++) {
+          const element = menus[i]
+          if (element.clientWidth) {
+            width += element.clientWidth
+          }
+        }
+        const extraEl = document.getElementById('h-address')
+        if (extraEl) {
+          extraEl.style.width = `${width === 0 ? 156 : width}px`
+        }
+      },
+      false
+    )
+    return () => {
+      document.querySelector('.Cascader-Body')?.removeEventListener('DOMSubtreeModified', () => {})
+    }
+  }, [])
+  const dropdownRender = menu => (
+    <>
+      <div id="casaContent">{menu}</div>
+      {showFooter && historyRecord.length ? (
+        <>
+          <Divider style={{ margin: 0 }} />
+          <div id="h-address" className="p-3" style={{ width: 'auto' }}>
+            <div className="font-semibold">最近使用</div>
+            <div className="inline-flex flex-wrap">
+              {historyRecord?.map((item, idx) => (
+                <div
+                  key={item.key + idx}
+                  className="mt-1 cursor-pointer"
+                  onClick={() =>
+                    handleTagClick(
+                      item.value.map(v => v.id),
+                      item.value
+                    )
+                  }>
+                  <Tag>{item.name}</Tag>
+                </div>
+              ))}
+            </div>
+          </div>
+        </>
+      ) : null}
+    </>
+  )
+
+  const handleOnPopupVisibleChange = e => {
+    setVisible(e)
+    if (!e && cValue === defaultValue) {
+      triggerChange(null)
+    }
+  }
   return (
-    <div className={styles.cascader}>
+    <div className={[styles.cascader, 'Cascader-Body'].join(' ')}>
       <Cascader
+        value={cValue}
         getPopupContainer={triggerNode => triggerNode.parentNode}
+        popupVisible={visible}
         placeholder="省/市/区"
         options={options}
         ref={refinstance}
+        dropdownRender={dropdownRender}
         onChange={handleOnChange}
         loadData={loadData}
         fieldNames={{ label: 'name', value: 'id' }}
         changeOnSelect={changeOnSelect}
+        onPopupVisibleChange={handleOnPopupVisibleChange}
         {...resetProps}
       />
     </div>

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

@@ -1,3 +1,7 @@
+/* eslint-disable consistent-return */
+import { getAuthCache, setAuthCache } from '@/utils/auth'
+import { CASCADER_HISTORY_KEY } from '@/utils/cache/cacheEnum'
+
 export const validCascaderRule = (_, value) => {
   if (!value || !value.length) {
     return Promise.reject(new Error('请选择地区'))
@@ -15,3 +19,33 @@ export function formatValues(value) {
   const [province = '', city = '', area = ''] = district
   return { province, city, area, ...resetVal }
 }
+
+/** 对级联数据进行auth历史性存储 */
+export function serializeRecord(name = '', id = '', value = []) {
+  if (!name || !id) return
+  const hty = getAuthCache(CASCADER_HISTORY_KEY) || []
+  // 数组为空
+  if (!hty?.length) {
+    hty.push({ key: id, name, value })
+    setAuthCache(CASCADER_HISTORY_KEY, hty)
+    return hty
+  }
+  const i = hty.findIndex(item => item.key === id)
+  // 第一个即同值,无需重复操作
+  if (id && i === 0) {
+    return
+  }
+  // 存在历史节点,删除历史节点
+  if (id && i !== -1) {
+    hty.splice(i + 1, 0)
+  }
+  if (hty?.length < 5) {
+    hty.unshift({ key: id, name, value })
+  } else {
+    hty.pop()
+    hty.unshift({ key: id, name, value })
+  }
+  setAuthCache(CASCADER_HISTORY_KEY, hty)
+  // 返回最新的数据
+  return hty
+}

+ 1 - 0
src/pages/Customer/Client/index.jsx

@@ -573,6 +573,7 @@ const Client = props => {
               onChange={onDistrictChange}
               changeOnSelect={true}
               bordered={false}
+              showFooter={true}
               className="hover:bg-[rgba(0,0,0,0.1)]"
             />,
             <AddContact onConfirm={handleAddClient} key="addContactBtn" />

+ 5 - 4
src/pages/Customer/Client/model.js

@@ -30,10 +30,11 @@ export default {
           type: 'changeState',
           payload: {
             key: tagType === TagTypeEnum.PERSONTAG ? 'personTagColorMap' : 'teamTagColorMap',
-            data: response.data.reduce((curr, prev, idx) => {
-              const item = { ...curr, [prev.id]: LabelStatusColorMap[tagType][idx] }
-              return item
-            }, {})
+            data:
+              response.data?.reduce((curr, prev, idx) => {
+                const item = { ...curr, [prev.id]: LabelStatusColorMap[tagType][idx] }
+                return item
+              }, {}) || {}
           }
         })
       }

+ 1 - 0
src/pages/Customer/Company/index.jsx

@@ -475,6 +475,7 @@ const Company = props => {
               onChange={onDistrictChange}
               changeOnSelect={true}
               key="lazyCascader"
+              showFooter={true}
               className="hover:bg-[rgba(0,0,0,0.1)]"
               bordered={false}
             />,

+ 5 - 4
src/pages/Customer/Company/model.js

@@ -31,10 +31,11 @@ export default {
           type: 'changeState',
           payload: {
             key: tagType === TagTypeEnum.PERSONTAG ? 'personTagColorMap' : 'teamTagColorMap',
-            data: response.data.reduce((curr, prev, idx) => {
-              const item = { ...curr, [prev.id]: LabelStatusColorMap[tagType][idx] }
-              return item
-            }, {})
+            data:
+              response.data?.reduce((curr, prev, idx) => {
+                const item = { ...curr, [prev.id]: LabelStatusColorMap[tagType][idx] }
+                return item
+              }, {}) || {}
           }
         })
       }

+ 9 - 8
src/pages/Customer/Test/index.jsx

@@ -1,9 +1,10 @@
-import React from 'react'
+// import React from 'react'
 
-export default function Index() {
-  return (
-    <div>
-      <iconpark-icon name="address-book" />
-    </div>
-  )
-}
+// export default function Index() {
+
+//   return (
+//     <div>
+
+//     </div>
+//   )
+// }

+ 1 - 1
src/pages/Hr/Employee/components/EmployeeDetail/Form.jsx

@@ -24,7 +24,7 @@ const EmployeeForm = props => {
     {
       dataIndex: 'departmentId',
       label: '办事处/部门',
-      span: 12,
+      span: 24,
       editCellType: 'treeSelect'
     },
     {

+ 3 - 3
src/utils/cache/cacheEnum.ts

@@ -2,15 +2,15 @@
 // token key
 export const TOKEN_KEY = 'TOKEN__'
 
-// role role key
-export const ROLES_KEY = 'ROLES__KEY__'
-
 // base global local key
 export const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY__'
 
 // base global session key
 export const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY__'
 
+// base global casader components history value
+export const CASCADER_HISTORY_KEY = 'CASCADER_HISTORY_KEY__'
+
 export enum CacheTypeEnum {
   SESSION,
   LOCAL

+ 7 - 2
src/utils/cache/persistent.ts

@@ -3,13 +3,18 @@
 import { Memory } from './memory'
 
 import { createLocalStorage, createSessionStorage } from '@/utils/cache'
-import { TOKEN_KEY, ROLES_KEY, APP_LOCAL_CACHE_KEY, APP_SESSION_CACHE_KEY } from './cacheEnum'
+import {
+  TOKEN_KEY,
+  APP_LOCAL_CACHE_KEY,
+  APP_SESSION_CACHE_KEY,
+  CASCADER_HISTORY_KEY
+} from './cacheEnum'
 import { DEFAULT_CACHE_TIME } from '@/settings/encryptionSetting'
 import { pick, omit } from 'lodash-es'
 
 interface BasicStore {
   [TOKEN_KEY]: string | null | undefined
-  [ROLES_KEY]: string[]
+  [CASCADER_HISTORY_KEY]: { key: string; value: string[]; label: string }[]
 }
 
 type LocalStore = BasicStore