Explorar o código

feat: 增加最近使用功能

lanjianrong %!s(int64=5) %!d(string=hai) anos
pai
achega
91f90c820c

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 20 - 0
src/components/EditableForm/src/components/ImagePreview.jsx


+ 1 - 0
src/components/EditableForm/src/editableFormItem.jsx

@@ -155,6 +155,7 @@ const EditableFormItem = ({
             className="w-full"
             onChange={(value, local) => save({ currentTarget: { value, local } })}
             popupVisible={true}
+            showFooter={true}
             onBlur={save}
           />
         )

+ 104 - 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, useCallback } 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 [state, setState] = useState({
+    visible: popupVisible,
+    historyRecord: showFooter ? getAuthCache(CASCADER_HISTORY_KEY) || [] : []
+  })
+
+  const [cValue, setValue] = useState(defaultValue)
   useEffect(() => {
     if (!options.length) {
       dispatch({
@@ -14,13 +34,27 @@ const CLazyCascader = props => {
   }, [])
 
   const triggerChange = (value, local = '') => {
+    setValue(value)
     if (onChange) {
       onChange(value, local)
     }
   }
+
   const handleOnChange = (value, selectedOptions) => {
+    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) {
+        setState({ ...state, historyRecord: htr })
+      }
+    }
     const local = selectedOptions.reduce((prev, curr) => {
-      return `${prev}${prev ? ',' : ''}${curr.name}`
+      return `${prev}${prev ? ' / ' : ''}${curr.name}`
     }, '')
     triggerChange(value, local)
   }
@@ -36,17 +70,82 @@ const CLazyCascader = props => {
     })
   }
 
+  const handleTagClick = (values, selectedOptions) => {
+    // 加载可能dva没有缓存的地区
+    dispatch({
+      type: 'district/fetchWithValues',
+      payload: values
+    })
+    // 触发级联的onChange回调
+    handleOnChange(values, selectedOptions)
+    // 手动将pop浮层隐藏
+    setState({ ...state, visible: false })
+  }
+
+  // useLayoutEffect(() => {
+  //   document.querySelector('.Cascader-Body')?.addEventListener(
+  //     'DOMSubtreeModified',
+  //     () => {
+  //       const width = document.getElementById('casaContent')?.clientWidth
+  //       console.log('width', width)
+  //       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 = useCallback(
+    menu => (
+      <>
+        <div id="casaContent">{menu}</div>
+        {showFooter ? (
+          <>
+            <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">
+                {state.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}
+      </>
+    ),
+    [state.historyRecord]
+  )
+
   return (
-    <div className={styles.cascader}>
+    <div className={[styles.cascader, 'Cascader-Body'].join(' ')}>
       <Cascader
+        value={cValue}
         getPopupContainer={triggerNode => triggerNode.parentNode}
+        popupVisible={state.visible}
         placeholder="省/市/区"
         options={options}
         ref={refinstance}
+        dropdownRender={dropdownRender}
         onChange={handleOnChange}
         loadData={loadData}
         fieldNames={{ label: 'name', value: 'id' }}
         changeOnSelect={changeOnSelect}
+        onPopupVisibleChange={e => setState({ ...state, visible: e })}
         {...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 (hty?.length < 5) {
+    // 存在历史节点,删除并在最前面插入重复的新的
+    if (id && i !== -1) {
+      hty.splice(i + 1, 0)
+    }
+    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/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}
             />,

+ 44 - 2
src/pages/Customer/Test/index.jsx

@@ -1,9 +1,51 @@
-import React from 'react'
+import { Button, Cascader } from 'antd'
+import React, { useState } from 'react'
 
 export default function Index() {
+  const [state, setState] = useState(['jiangsu', 'nanjing', 'zhonghuamen'])
+  const options = [
+    {
+      value: 'zhejiang',
+      label: 'Zhejiang',
+      children: [
+        {
+          value: 'hangzhou',
+          label: 'Hangzhou',
+          children: [
+            {
+              value: 'xihu',
+              label: 'West Lake'
+            }
+          ]
+        }
+      ]
+    },
+    {
+      value: 'jiangsu',
+      label: 'Jiangsu',
+      children: [
+        {
+          value: 'nanjing',
+          label: 'Nanjing',
+          children: [
+            {
+              value: 'zhonghuamen',
+              label: 'Zhong Hua Men'
+            }
+          ]
+        }
+      ]
+    }
+  ]
   return (
     <div>
-      <iconpark-icon name="address-book" />
+      <Cascader options={options} value={state} onChange={e => setState(e)} />
+      <Button
+        onClick={() => {
+          setState(['zhejiang', 'hangzhou', 'xihu'])
+        }}>
+        1111
+      </Button>
     </div>
   )
 }

+ 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