基于arco.designUI二次封装带分页、搜索查询请求接口的穿梭框

使用了arco.design组件库,发现里面的穿梭框组件没法进行分页搜索和分页查询接口,只能自己封装一个组件

html 父组件调用 getRequestUrl返回的数据格式有一定的要求

mokdata

{
    "requestId": "22",
    "code": "200",
    "data": {
        "content": [
            {
                "id": "1",
                "name": "name",
                "label": "中文名",
            },
            {
                "id": "2",
                "name": "name2",
                "label": "中文名2",
            },
        ],
        "totalElements": 4029,
        "totalPages": 202,
    },
    "success": true,
    "message": null,
    "devMsg": null,
    "hasPrivilege": true
}
import RemoteTransfer from '@/components/RemoteTransfer';

   <RemoteTransfer
         key={addDialogTitle}
         getMethod="post"
            getRequestUrl={()=>{
                return 'api/post';
            }}
            transferValue={remoteTargetKeys}
            defaultParams={{
            
                searchSubNode:true
            }}
            titleTexts={['可选列表','已选择列表']}
            transferProps={
                {
                    key: 'id',
                    search:['keyWord']
                }
            }
            targetFilterInput={
                {
                    key: 'id',
                    search:['modelName','modelLabel']
                }
            }

            column={transferColumns}
            pageSize={100}
            paginationConfig={
                {
                    pageNum:'offset',
                    pageSize:'limit'
                }
            }
            onChange={(value,to)=>remoteTransferonChange(value,to)}
            >
            </RemoteTransfer>

ts data

  const [addDialogTitle,setAddDialogTitle] =useState('新增');
  const [remoteTargetKeys, setRemoteTargetKeys] = useState([]);
  const transferColumns = [
    {
      title: '中文名',
      dataIndex: 'label',
      ellipsis:true,
      render:(col, record) => (
        <Tooltip content={record.label}>
           <span className='transfer-table-cell'>{record.label}</span>
        </Tooltip>
      )
    },
    {
      title: '英文名',
      dataIndex: 'name',
      ellipsis:true,
      render:(col, record) => (
        <Tooltip content={record.name}>
          <span className='transfer-table-cell'>{record.name}</span>
        </Tooltip>
      )
    }
    
  ];

函数

  const remoteTransferonChange=(value,to)=>{
    setRemoteTargetKeys(value)
  }

子组件

index.tsx

import React, {
  forwardRef,
  useContext,
  PropsWithChildren,
  useEffect,
  useState,
  useMemo,
  useRef,
  useCallback,
} from 'react';
import useMergeProps from '@arco-design/web-react/lib/_util/hooks/useMergeProps';
import cs from '@arco-design/web-react/lib/_util/classNames';
import axios from 'axios';
import { Button, Input, Table,Checkbox ,Message,Tooltip} from '@arco-design/web-react';
import { IconLeft, IconRight } from '@arco-design/web-react/icon';
import './style/index.less';
import useMergeValue from '@arco-design/web-react/lib/_util/hooks/useMergeValue';
import RemoteTransferList from './list';
import {
  RemoteTransferItem,
  RemoteTransferProcessDataRetrue,
  RemoteTransferProps,
  RemoteTransferType,
} from './interface';
require('static/guid.js');
const InputSearch = Input.Search;
// const transferProps: RemoteTransferProps = {
//     column: {},
//     processData: (raw) => raw,
//     props: { key: 'key', search: ['word'] },
//     pageSize: 20,
//   };

function RemoteTransfer(props: PropsWithChildren<RemoteTransferProps>) {
  const {
    className,
    style,
    requestUrl,
    getRequestUrl,
    processData,
    transferValue,
    pageSize,
    transferProps,
    column,
    titleTexts,
    platformCode,
    defaultParams,
    disabled,
    getMethod,
    // paginationConfig,
    targetFilterInput,
    
    // dataSource,
    onChange,
    ...rest
  } = props;
  const prefixCls="remote-transfer";
  const [targetValue, setTargetValue] = useMergeValue([], { value: transferValue });
  const [dataSource, setDataSource] = useState([]);
  const [columns, setColumns] = useState(column);
  const [platformCodeNew, setPlatformCodeNew] = useState(platformCode);
  const [loading, setLoading] = useState(false);
  
  const [transferChecked, setTransferChecked] = useState(false);
  const [selectchecked, setSelectchecked] = useState(false);
  const [sourcePagination, setSourcePagination] = useState({ current: 1, pageSize, total: 0 });
  const pageLoadedRef = useRef<number[]>([]);
  const filterInfo = useMemo(() => {
    type FilterInfo = {
      rowKey: keyof RemoteTransferItem;
      searchKeys: (keyof RemoteTransferItem)[];
    };

    const { key: rowKey, search } = transferProps;
    const searchKeys = [].concat(search);

    return { rowKey, searchKeys } as FilterInfo;
  }, [transferProps]);
  const targetFilterInfo = useMemo(() => {
    type targetFilterInfo = {
      rowKey: keyof RemoteTransferItem;
      searchKeys: (keyof RemoteTransferItem)[];
    };

    const { key: rowKey, search } = targetFilterInput;
    const searchKeys = [].concat(search);

    return { rowKey, searchKeys } as targetFilterInfo;
  }, [targetFilterInput]);

  type ListInfo = {
    dataSource: RemoteTransferItem[];
    selectedDisabledKeys: string[];
  };

  const sourceInfo = useMemo<ListInfo>(() => {
    const { current, pageSize } = sourcePagination;
    const currentLoadedIndex = pageLoadedRef.current.indexOf(current);

    return {
      dataSource: dataSource.slice(
        currentLoadedIndex * pageSize,
        (currentLoadedIndex + 1) * pageSize
      ),
      selectedDisabledKeys: targetValue.map((item) => item[filterInfo.rowKey]),
    };
  }, [dataSource, filterInfo.rowKey, sourcePagination, targetValue]);

  const [targetFilterText, setTargetFilterText] = useState('');
  const targetInfo = useMemo<ListInfo>(() => {
    let filterInfo_={};
    if(targetFilterInput){
        filterInfo_=targetFilterInfo;
    }else{
        filterInfo_=filterInfo;
    }
    return {
      dataSource:
        targetFilterText && filterInfo_.searchKeys.length
          ?  targetValue.filter(item => {
            return filterInfo_.searchKeys.some(key => item[key] && item[key].includes(targetFilterText));
          })
          : targetValue,
      selectedDisabledKeys: [],
    };
  }, [targetFilterText, targetValue, filterInfo.searchKeys]);

  type SelectedInfo = { source: string[]; target: string[] };
  const [selectedInfo, setSelectedInfo] = useState<SelectedInfo>({
    source: [],
    target: [],
  });

  type RemoteParams = { pageNum: number; [K: string]: any };
  const remoteParamsRef = useRef<RemoteParams>({ pageNum: 1 });

  const loadRemote = useCallback(
    async (reset = false,code) => {
      const url = getRequestUrl?.() || requestUrl || '';
      let obj={};
      filterInfo.searchKeys.forEach(key => {
        obj[key] = '';
      });
      let params={
        ...obj,
        ...defaultParams,
        ...remoteParamsRef.current,
        pageNum:remoteParamsRef.current.pageNum-1,
        pageSize,
      }
      
      if(code){
        params.platformCode=code;
      }
    //   if(paginationConfig){
    //    let pageNum = paginationConfig.pageNum;
    //    let pageSize_ = paginationConfig.pageSize;
    //     params[pageNum]=params.pageNum;
    //     params[pageSize_]=params.pageSize;
        //  params={params};
        // delete params.pageNum;
        // delete params.pageSize;
    //   }
      setLoading(true);
      const host=`${window.location.host}`;
        const protocol=`${window.location.protocol}`;
        let res={};
        if(getMethod && getMethod === 'post'){
             res = await axios({
                method:'post',
                url:`${protocol}//${host}/${url}` ,
                data: params ? params: {},
              }).then(res=> res.data).catch(err=>{ console.log(err); setLoading(false);});
        
        }else{
             res = await axios({
                method:'get',
                url:`${protocol}//${host}/${url}` ,
                params: params ? params: {},
              }).then(res=> res.data).catch(err=>{ console.log(err); setLoading(false);});
        }
   
    //   await axios.post(`/${APP_NAME}/_api/_/team/changeTeam`, { teamName:'asiainfo' });
      setLoading(false);
      let data=res?.data;
      if (processData) {
        data = processData(data);
      }
      
      if (data == null || !Array.isArray(data?.content) ) {
        console.log('远程穿梭框获取的数据格式不正确');
        if(res?.message){
            Message.warning(res?.message);
          }
        }
      if(data?.content.length>0){
        data.content.forEach((item,index)=>{
            if(item?.id){
                item.id=item.id;
            }else{
                item.id=index+'';
            }
          })
      }
      const { pageNum } = remoteParamsRef.current;
      const { content, totalElements } = data;

      setDataSource((source) => {
        if (reset) {
          pageLoadedRef.current = [pageNum];
          return content;
        }
        pageLoadedRef.current = pageLoadedRef.current.concat(pageNum).sort();
        const index = pageLoadedRef.current.indexOf(pageNum);
        source.splice(index * pageSize, 0, ...content);
        return source;
      });
      setSourcePagination({
        current: pageNum,
        pageSize,
        total: totalElements || content.length,
      });
    },
    [getRequestUrl, pageSize, processData, requestUrl]
  );
  
  useEffect(() => {
    if(platformCode){
        loadRemote(true,platformCode,);
    }else{
        loadRemote(true);
    }
    // loadRemote,platformCode
  }, []);
  useEffect(() => {
    if(column){
        setColumns(column);
    }
  }, [column]);
  useEffect(() => {
    if(props?.item?.field){
        if(props.thisEvent.formRef){
            let targetData_ = props.thisEvent.formRef.current.getFieldValue(props?.item?.field);
            if(Array.isArray(targetData_) && targetData_.length > 0 ){
                setTargetValue(targetData_);
            }else{
                let initialValue=props?.item?.initialValue;
                setTargetValue(Array.isArray(initialValue) && initialValue.length > 0 ? props?.item?.initialValue : []);
            }
        }
    }
  }, []);
  
  

  const moveTo = (to: RemoteTransferType) => {
    const selectedKeys = to === 'source' ? selectedInfo.target : selectedInfo.source;
    if (!selectedKeys.length) return;
    if (to === 'target') {
      const newValue = targetValue.concat(
        dataSource.filter((item) => selectedKeys.includes(item[filterInfo.rowKey]))
      );
      if(props?.options){
        props?.options?.onChange?.(newValue, to,props);
      }else{
        props?.onChange?.(newValue, to,props);
      }
      setTargetValue(newValue);
      setSelectedInfo({ ...selectedInfo, source: [] });
    } else {
      const newValue = targetValue.filter(
        (item) => !selectedKeys.includes(item[filterInfo.rowKey])
      );
      if(props?.options){
        props?.options?.onChange?.(newValue, to,props);
      }else{
        props?.onChange?.(newValue, to,props);
      }
      setTargetValue(newValue);
      setSelectedInfo({ ...selectedInfo, target: [] });
    }
  };
  const handleSelect = (to: RemoteTransferType, selectedKeys: (string | number)[]) => {
    const key: keyof SelectedInfo = to === 'source' ? 'source' : 'target';
    setSelectedInfo((info) => {
      return { ...info, [key]: selectedKeys };
    });
    if(to=='source'){
        setTransferChecked(!transferChecked)    
    }else if(to=='target'){
        setSelectchecked(!selectchecked)  
    }
   

  };

  const handleSourcePageChange = (current: number) => {
    if (pageLoadedRef.current.includes(current)) {
      setSourcePagination({ ...sourcePagination, current });
      return;
    }
    remoteParamsRef.current.pageNum = current;
    loadRemote(true,platformCode);
  };

  const handleSourceSearch = (value: string) => {
    remoteParamsRef.current = filterInfo.searchKeys.reduce(
      (params, key) => {
        params[key] = value;
        return params;
      },
      { pageNum: 1 }
    );

    loadRemote(true,platformCode);
  };

  const renderOperations = () => {

    const leftActive = selectedInfo.target.length > 0;
    const rightActive = selectedInfo.source.length > 0;
    const buttons: RemoteTransferType[] = ['target', 'source'];

    return (
      <div className={cs(`${prefixCls}-operations elx-remote-transfer-operate`)}>
        {buttons.map((to, index) => {
          let Icon;
          let _disabled;

          if (to === 'source') {
            Icon = IconLeft;
            _disabled = disabled || !leftActive;
          } else {
            Icon = IconRight;
            _disabled = disabled || !rightActive;
          }

          return (
            <Button
              key={index}
              className={to?'right':'left'}
              tabIndex={-1}
              aria-label={`move selected ${to === 'target' ? 'right' : 'left'}`}
              type="secondary"
              size="small"
              shape="round"
              disabled={_disabled}
              onClick={() => moveTo(to)}
              icon={<Icon />}
            />
          );
        })}
      </div>
    );
  };

  const renderList = (to: RemoteTransferType) => {
    const onSearch = to === 'target' ? setTargetFilterText : handleSourceSearch;
    
    const info = to === 'target' ? targetInfo : sourceInfo;
    const pagination =
      to === 'target'
        ? {}
        : {
            ...sourcePagination,
            onChange: handleSourcePageChange,
          };
    const rowSelection =
      to === 'target'
        ? {}
        : {
            checkboxProps(record: RemoteTransferItem) {
              return {
                disabled:
                  disabled || sourceInfo.selectedDisabledKeys.includes(record[filterInfo.rowKey]),
              };
            },
          };

    return (
      <RemoteTransferList
     
        {...info}
        prefixCls={prefixCls}
        className={cs(`${prefixCls}-view`, `${prefixCls}--view-${to}`)}
        disabled={disabled}
        rowKey={filterInfo.rowKey}
        selectchecked={selectchecked}
        selectedKeys={selectedInfo[to]}
        onSearch={onSearch}
        titleTexts={titleTexts}
        onSelect={(selectedKeys) => handleSelect(to, selectedKeys)}
        columns={columns.map((column, index) => {
            return {
                ...column,
                title: column?.label || column?.title,
                dataIndex: column?.name || column?.dataIndex,
                };
        })}
        pagination={{ showTotal: true, simple:true, pageSize, ...pagination }}
        rowSelection={rowSelection}
      />
    );
  };

  return (
    <div className={cs(prefixCls, className)} style={style} {...rest}>
      <div className={`elx-remote-transfer-left left-box--view-source`}>
        <div className='arco-transfer-view-header'>
            <Checkbox checked={selectedInfo?.source.length==0 ? false: transferChecked}>{Array.isArray(titleTexts)?titleTexts[0]:'可选'}</Checkbox>
            <div className='arco-transfer-view-header-unit'>{sourceInfo?.dataSource.length}/{selectedInfo?.source.length}</div>
        </div>
        <InputSearch
          className={`left-view-search`}
          placeholder="请输入搜索内容"
          searchButton
          loading={loading}
          onSearch={handleSourceSearch}
        />
        <Table
        loading={loading}
          className={`left-view-table`}
          rowKey={filterInfo.rowKey}
          rowSelection={{
            selectedRowKeys: selectedInfo.source,
            checkboxProps(record) {
              return {
                disabled:
                  disabled || sourceInfo.selectedDisabledKeys.includes(record[filterInfo.rowKey]),
              };
            },
            onChange: (selectedKeys) => {
              handleSelect('source', selectedKeys);
            
            },
          }}
          data={sourceInfo.dataSource}
          columns={columns.map((column, index) => {
            return {
                ...column,
                title: column?.label || column?.title,
                dataIndex: column?.name || column?.dataIndex,
                };
        })}
          pagePosition="bottomCenter"
          pagination={{
            ...sourcePagination,
            onChange: handleSourcePageChange,
            simple:true,
            showTotal: true,
          }}
          scroll={{ y: '100%' }}
        />
      </div>
      {renderOperations()}
      {renderList('target')}
    </div>
  );
}
RemoteTransfer.defaultProps = {
    requestUrl:'',
    // transferValue:[],//已选的key 
    pageSize:50,
    getMethod:'get',
    transferProps:{ key: 'key', search: ['word'] },
    targetFilterInput:{key: 'key', search: ['name']},
    column:[
        {
            "name": "label",
            "label": "中文名"
        },
        {
            "name": "name",
            "label": "英文名"
        }
    ],
    titleTexts:['可选','已选'],
    defaultParams:{},//默认传递的其他搜索值
    disabled:false,

}
export default RemoteTransfer;

list.tsx

import { Input, Table,Checkbox } from '@arco-design/web-react';
import cs from '@arco-design/web-react/lib/_util/classNames';

import React from 'react';
function RemoteTransferList(props) {
  const {
    prefixCls,
    className,
    disabled,
    onSearch,
    selectedKeys,
    onSelect,
    dataSource,
    columns,
    pagination,
    titleTexts,
    rowKey,
    selectchecked
  } = props;
  const InputSearch = Input.Search;
  
  return (
    <div className={className +' elx-remote-transfer-right'}>
         <div className='arco-transfer-view-header'>
            <Checkbox checked={selectedKeys.length==0 ? false : selectchecked}>{Array.isArray(titleTexts)?titleTexts[1]:'已选择'}</Checkbox>
            <div className='arco-transfer-view-header-unit'>{dataSource.length}/{selectedKeys.length}</div>
        </div>
      <InputSearch
        className={cs(`${prefixCls}-view-search`)}
        disabled={disabled}
        // allowClear={}
        placeholder="请输入搜索内容"
        onChange={onSearch}
      />
      <Table
        className={cs(`${prefixCls}-view-table`)}
        rowSelection={{
          selectedRowKeys: selectedKeys,
          onChange: onSelect,
        }}
        rowKey={rowKey}
        data={dataSource}
        columns={columns}
        pagePosition="bottomCenter"
        pagination={pagination}
        scroll={{ y: '100%' }}
      />
    </div>
  );
}

export default RemoteTransferList;

interface.ts

import { PaginationProps, TableColumnProps, TableRowSelectionProps } from '@arco-design/web-react';
import { CSSProperties } from 'react';

export type RemoteTransferType = 'target' | 'source';

export interface RemoteTransferItem {
  [K: string | number | symbol]: any;
}

/**
 * @title RemoteTransfer
 */
export interface RemoteTransferProps {
  style?: CSSProperties;
  className?: string;
  /**
   * @zh [左栏]数据请求地址(
   * @en The remote url
   */
  requestUrl?: string;
  /**
   * @zh [左栏]数据请求地址的回调方法
   * @en Get the remote url by function
   */
  getRequestUrl?: () => string;
  /**
   * @zh 处理成组件要求的数据方法
   * @en The function for process remote data
   */
  processData?: (raw: any) => RemoteTransferProcessDataRetrue;
  /**
   * @zh 值,`完全受控`
   * @en The value, is `Control`
   */
  transferValue?: RemoteTransferItem[];
  /**
   * @zh 所需展示的表格字段以及表头中文名对象(举例:`{ stepInst: '指令',stepInstLabel: '指令名称' }`)
   * @en The table columns `dataindex` & `title`, egs, `{ key1: 'Title 1', key2: 'Title 2' }`
   */
  column?: Record<string, string>;
  /**
   * @zh 页码设置
   * @en The page size
   */
  pageSize?: number;
  /**
   * @zh 配置选项,key: 每条数据的唯一值字段, search: 搜索框对应的字段
   * @en The config, key: the unique key of data, search: keys of filter data by search input
   */
  props?: { key: keyof RemoteTransferItem; search: string | string[] };
  /**
   * @zh 禁用穿梭框
   * @en Whether is disabled
   */
  disabled?: boolean;
  /**
   * @zh 选中项在两栏之间转移时的回调
   * @en Callback when the transfer between columns is complete
   */
  onChange?: (selected: RemoteTransferItem[], type: RemoteTransferType) => void;
}

/**
 * @title RemoteRequestParams
 *
 * @zh 远程请求的入参,请求方法 `get`
 * @en The remote request params, request method: `get`.
 */
export interface RemoteRequestParams {
  /**
   * @zh 页码
   * @en The page number
   */
  pageNum: number;
  /**
   * @zh 一页中含数据条数
   * @en The page size
   */
  pageSize: number;

  /**
   * @zh
   * @en The search keys ard define by `props.search`, value bu search input
   */
  [searchKey: string]: any;
}

/**
 * @title RemoteTransferProcessDataRetrue
 *
 * @zh `processData` 返回的数据格式
 * @en The type return of `processData`.
 */
export interface RemoteTransferProcessDataRetrue {
  /**
   * @zh 数据内容
   * @en Data Content
   */
  content: RemoteTransferItem[];
  /**
   * @zh 数据总数
   * @en Total of data
   */
  totalElements: number;
}

export interface RemoteTransferListProps {
  prefixCls: string;
  className: string;
  disabled: boolean;
  onSearch: (value: string) => void;
  selectedKeys: string[];
  onSelect: (selectedKeys: string[]) => void;
  dataSource: RemoteTransferItem[];
  columns: TableColumnProps[];
  pagination: PaginationProps;
  rowSelection: TableRowSelectionProps;
}

index.less

.remote-transfer{
    display: flex;
    min-width: 500px;
    width: 100%;
    height: 348px;
    .arco-transfer-view-header{
        height: 36px;
        line-height: 36px;
        font-weight: bold;
        background-color: var(--color-gray-2);
        margin-bottom: 10px;
    }
    .arco-table{
        height: calc(100% - 110px);
    }
    .arco-spin,
    .arco-spin-children,
    .arco-table-container,
    .arco-table-content-scroll,
    .arco-table-content-inner,
    .arco-table-body {
        height: calc(100%);
    }
    .arco-table-content-scroll{
        height: calc(100% - 5px);
        .arco-table-body{
            height: calc(100% - 40px);
        }
    }
    .arco-table-content-inner{
        height: 100%;
        padding: 0px !important;
    }
    .arco-table-container{
        padding: 0px !important;
    }
    .elx-remote-transfer-left{
        width: calc(50% - 25px);
        padding: 10px;
        box-shadow: 0px 0px 5px #d3d3d3;
        float: left;
    }
    .elx-remote-transfer-operate{
        width: 50px;
        height: 100%;
        float: left;
        text-align: center;
        position: relative;
        top:40%;
        .right{
            margin-bottom: 20px;
        }
    
    }
    .elx-remote-transfer-right{
        width: calc(50% - 25px);
        padding: 10px;
        box-shadow: 0px 0px 5px #d3d3d3;
        float: left;
    }
}
posted @ 2024-08-26 15:08  Empress&  阅读(289)  评论(0)    收藏  举报