瀏覽代碼

feat: 完善数据权限自定义穿梭框

lanjianrong 5 年之前
父節點
當前提交
482abe33c5

+ 5 - 5
config/routes.js

@@ -97,12 +97,12 @@ export default [
             component: './Product/Lock/LockStore',
             access: 'authRouteFilter',
             validatePerm: true
+          },
+          {
+            path: '/product/lock/lockcount',
+            name: 'lockcount',
+            component: './Product/Lock/LockCount'
           }
-          // {
-          //   path: '/product/lock/lockcount',
-          //   name: 'lockcount',
-          //   component: './Product/Lock/LockCount'
-          // }
         ]
       },
       {

+ 1 - 1
package.json

@@ -56,7 +56,7 @@
     "@icon-park/react": "^1.3.3",
     "@umijs/route-utils": "^1.0.33",
     "ahooks": "^2.10.0",
-    "antd": "4.16.6",
+    "antd": "4.16.12",
     "classnames": "^2.2.6",
     "crypto-js": "^4.0.0",
     "dayjs": "^1.10.4",

+ 2 - 2
src/components/LazyCascader/util.js

@@ -23,7 +23,7 @@ export function formatValues(value) {
 /** 对级联数据进行auth历史性存储 */
 export function serializeRecord(name = '', id = '', value = []) {
   if (!name || !id) return
-  const hty = getAuthCache(CASCADER_HISTORY_KEY) || []
+  let hty = getAuthCache(CASCADER_HISTORY_KEY) || []
   // 数组为空
   if (!hty?.length) {
     hty.push({ key: id, name, value })
@@ -37,7 +37,7 @@ export function serializeRecord(name = '', id = '', value = []) {
   }
   // 存在历史节点,删除历史节点
   if (id && i !== -1) {
-    hty.splice(i + 1, 0)
+    hty = hty.filter((_, k) => k !== i)
   }
   if (hty?.length < 5) {
     hty.unshift({ key: id, name, value })

+ 105 - 14
src/pages/Customer/Company/components/CustomPerm.jsx

@@ -1,12 +1,14 @@
+/* eslint-disable no-underscore-dangle */
 /* eslint-disable consistent-return */
 import { Button, Transfer, Tree } from 'antd'
-import React, { useState, useEffect } from 'react'
+import React, { useMemo, useState, useEffect } from 'react'
 import styles from './ConnectCompany/index.less'
-import { CloseOutlined } from '@ant-design/icons'
+import { CloseOutlined, DeleteOutlined } from '@ant-design/icons'
 import consts from '@/basic/consts'
 import { useDispatch } from 'umi'
 import { queryDepartmentStaffList } from '@/services/department'
 import { useWindowSizeFn } from '@/hooks/event/useWindomSizeFn'
+import { isEmpty, isNull, isNullOrUnDef } from '@/utils/is'
 
 const isChecked = (selectedKeys, eventKey) => selectedKeys.includes(eventKey)
 
@@ -18,6 +20,7 @@ const itemIsChecked = onItemSelectAll => (selectedKeys, checkedKeys, checked, no
       .filter(({ isDepartment }) => !isDepartment)
       .filter(({ key }) => !isChecked(checkedKeys, key))
       .map(({ key }) => key)
+
     onItemSelectAll(keys, checked)
   }
 }
@@ -40,8 +43,10 @@ const generateTree = (treeNodes = [], checkedKeys = []) =>
 const CustomPerm = () => {
   const [state, setState] = useState({
     dataSource: [],
+    leftTargetId: null,
     treeKeys: [],
     targetKeys: [],
+    rightTargetId: null,
     transferDataSource: [],
     height: 0
   })
@@ -91,7 +96,20 @@ const CustomPerm = () => {
   }
 
   const handleOnSearch = (direction, value) => {
-    console.log(direction, value)
+    const targetNode = state.transferDataSource.find(item => item.title.indexOf(value) > -1)
+    if (direction === 'left') {
+      if (!isEmpty(value) && !isNullOrUnDef(targetNode)) {
+        setState({ ...state, leftTargetId: targetNode.key })
+        return
+      }
+      isEmpty(value) && setState({ ...state, leftTargetId: null })
+    } else {
+      if (!isEmpty(value) && !isNullOrUnDef(targetNode)) {
+        setState({ ...state, rightTargetId: targetNode.key })
+        return
+      }
+      isEmpty(value) && setState({ ...state, rightTargetId: null })
+    }
   }
 
   const onChange = (keys, direction, moveKeys) => {
@@ -114,9 +132,14 @@ const CustomPerm = () => {
     } else {
       setState({ ...state, targetKeys: [...state.targetKeys, ...keys] })
     }
+    if (direction === 'right') {
+      const newTargetKeys = moveKeys.filter(item => !state.targetKeys.includes(item))
+      setState({ ...state, targetKeys: [...state.targetKeys, ...newTargetKeys] })
+    }
   }
 
-  const handleUnCheckItem = (checkedKeys, key) => {
+  const handleUnCheckItem = (checkedKeys, key, options = {}) => {
+    // options-用于处理右侧自定义删除逻辑中,防止两个setState合并从而导致更新失败
     const node = state.transferDataSource.find(item => item.key === key)
     if (node?.isDepartment) {
       const filterKeys = state.transferDataSource
@@ -124,6 +147,7 @@ const CustomPerm = () => {
         .map(item => item.key)
       setState({
         ...state,
+        ...options,
         treeKeys: state.treeKeys.filter(item => ![key, ...filterKeys].includes(item))
       })
     } else {
@@ -132,16 +156,48 @@ const CustomPerm = () => {
         // 说明 父节点已经被选中
         setState({
           ...state,
+          ...options,
           treeKeys: state.treeKeys.filter(item => item !== pNode.key && item !== key)
         })
       } else {
         setState({
           ...state,
+          ...options,
           treeKeys: state.treeKeys.filter(item => item !== key)
         })
       }
     }
   }
+  const checkedKeys = useMemo(() => {
+    const targetId = state.leftTargetId
+    if (isNull(targetId)) {
+      return [...state.treeKeys]
+    }
+    const node = state.transferDataSource.find(item => item.key === targetId)
+    if (!isNullOrUnDef(node)) {
+      // 搜索的节点存在
+      if (node.isDepartment) {
+        // 节点是树节点
+        if (state.treeKeys.findIndex(item => item === targetId) !== -1) {
+          // treeKeys中有这个树节点
+          return [
+            ...state.transferDataSource
+              .filter(item => item.parentId === targetId)
+              .map(item => item.key),
+            node.key
+          ]
+        }
+        // 过滤出当前树节点的子元素中所有处于target状态的叶子结点
+        return state.transferDataSource
+          .filter(item => item.parentId === targetId)
+          .filter(item => state.treeKeys.includes(item.key))
+          .map(item => item.key)
+      }
+      // 直接返回当前节点
+      return [targetId]
+    }
+    return []
+  }, [state.leftTargetId, state.treeKeys.length, state.targetKeys.length])
   return (
     <div className={['ant-modal-content', styles.modalContainer].join(' ')}>
       <div className={styles.modalHeader}>
@@ -157,13 +213,12 @@ const CustomPerm = () => {
           className="h-full"
           render={item => item.title}
           onSearch={handleOnSearch}
+          dataSource={state.transferDataSource}
           targetKeys={state.targetKeys}
           onChange={onChange}
-          dataSource={state.transferDataSource}
           showSelectAll={false}>
           {({ direction, onItemSelectAll, onItemSelect }) => {
             if (direction === 'left') {
-              const checkedKeys = [...state.targetKeys, ...state.treeKeys]
               return state.dataSource?.length ? (
                 <Tree
                   blockNode
@@ -171,7 +226,12 @@ const CustomPerm = () => {
                   defaultExpandAll
                   height={state.height}
                   checkedKeys={checkedKeys}
-                  treeData={generateTree(state.dataSource, state.targetKeys)}
+                  treeData={generateTree(
+                    state.leftTargetId
+                      ? state.transferDataSource.filter(item => item.key === state.leftTargetId)
+                      : state.dataSource,
+                    state.targetKeys
+                  )}
                   onCheck={(_, { checked, checkedNodes, node: { isDepartment, key } }) => {
                     if (!checked) {
                       handleUnCheckItem(checkedKeys, key)
@@ -194,15 +254,16 @@ const CustomPerm = () => {
                       onItemSelect(key, !isChecked(checkedKeys, key))
                     }
                   }}
-                  onSelect={(_, { selected: checked, node: { isDepartment, key, parentId } }) => {
+                  onSelect={(_, { node: { isDepartment, key, parentId } }) => {
+                    const checked = !state.treeKeys.includes(key)
                     if (!checked) {
                       handleUnCheckItem(checkedKeys, key)
                     } else {
                       const treeKeys = [...state.treeKeys]
                       if (!isDepartment) {
-                        const samePidArr = state.transferDataSource.filter(
-                          item => item.key === parentId
-                        )
+                        const samePidArr = state.transferDataSource
+                          .filter(item => item.parentId === parentId)
+                          .map(item => item.key)
                         const count = checkedKeys.reduce((prev, curr) => {
                           if (samePidArr.includes(curr)) {
                             return prev + 1
@@ -212,6 +273,13 @@ const CustomPerm = () => {
                         if (samePidArr.length - count === 1) {
                           treeKeys.push(parentId)
                         }
+                        treeKeys.push(key)
+                      } else {
+                        const samePidArr = state.transferDataSource
+                          .filter(item => item.parentId === key)
+                          .filter(item => !checkedKeys.includes(item.key))
+                          .map(item => item.key)
+                        treeKeys.push(...samePidArr, key)
                       }
                       setState({
                         ...state,
@@ -220,9 +288,7 @@ const CustomPerm = () => {
                     }
                     isDepartment &&
                       itemIsChecked(onItemSelectAll)(
-                        state.transferDataSource.filter(
-                          item => item.key === key || item.parentId === key
-                        ),
+                        state.transferDataSource.filter(item => item.parentId === key),
                         checkedKeys,
                         checked,
                         state.transferDataSource
@@ -234,6 +300,31 @@ const CustomPerm = () => {
                 />
               ) : null
             }
+
+            if (direction === 'right') {
+              return (
+                <ul className="ant-transfer-list-content ant-transfer-list-content-show-remove">
+                  {(state.rightTargetId
+                    ? state.targetKeys.filter(item => item === state.rightTargetId)
+                    : state.targetKeys
+                  ).map(item => (
+                    <li className="ant-transfer-list-content-item" key={item}>
+                      <span className="ant-transfer-list-content-item-text">
+                        {state.transferDataSource.find(i => i.key === item)?.title}
+                      </span>
+                      <span
+                        className="text-hex-d9d9d9 hover:text-hex-886ab5 transition-colors duration-300"
+                        onClick={() => {
+                          const currKeys = state.targetKeys.filter(i => i !== item)
+                          handleUnCheckItem(state.treeKeys, item, { targetKeys: currKeys })
+                        }}>
+                        <DeleteOutlined />
+                      </span>
+                    </li>
+                  ))}
+                </ul>
+              )
+            }
           }}
         </Transfer>
       </div>

+ 10 - 13
src/pages/workbench/Dashboard/components/LeaderBoard.jsx

@@ -25,7 +25,6 @@ const LeaderBoard = ({ loading = false, dataList = [] }) => {
       {
         title: '排名',
         dataIndex: 'rank',
-        valueType: 'indexBorder',
         align: 'center',
         width: '30%',
         render: (_, record) =>
@@ -112,18 +111,16 @@ const LeaderBoard = ({ loading = false, dataList = [] }) => {
         </div>
       }>
       <div className="text-center border border-x-0 p-2">{opMap[activeOp]}排行榜</div>
-      <div className="">
-        <ProTable
-          border={true}
-          rowKey={record => record.rank}
-          columns={columns}
-          dataSource={dataList}
-          search={false}
-          size="small"
-          toolBarRender={false}
-          pagination={false}
-        />
-      </div>
+      <ProTable
+        border={true}
+        rowKey={record => record.rank}
+        columns={columns}
+        dataSource={dataList}
+        search={false}
+        size="small"
+        toolBarRender={false}
+        pagination={false}
+      />
     </Card>
   )
 }

+ 30 - 2
src/utils/is.ts

@@ -16,10 +16,38 @@ export function isNull(val: unknown): val is null {
   return val === null
 }
 
-export function isNullOrUnDef(val: unknown): val is null | undefined {
-  return isUnDef(val) || isNull(val)
+export function isObject(val: any): val is Record<any, any> {
+  return val !== null && is(val, 'Object')
 }
 
 export function isBoolean(val: unknown): val is boolean {
   return is(val, 'Boolean')
 }
+
+export function isString(val: unknown): val is string {
+  return is(val, 'String')
+}
+
+export function isArray(val: any): val is Array<any> {
+  return val && Array.isArray(val)
+}
+
+export function isNullOrUnDef(val: unknown): val is null | undefined {
+  return isUnDef(val) || isNull(val)
+}
+
+export function isEmpty<T = unknown>(val: T): val is T {
+  if (isArray(val) || isString(val)) {
+    return val.length === 0
+  }
+
+  if (val instanceof Map || val instanceof Set) {
+    return val.size === 0
+  }
+
+  if (isObject(val)) {
+    return Object.keys(val).length === 0
+  }
+
+  return false
+}