react+antd pro实现【列表可实时行内编辑】的弹窗表单组件

纯列表版效果展示:

① 初始无值,展示为唤醒按钮+文案外链

 

②点击按钮唤醒弹窗(简易版示意图)

 

 ③配置后

 

 

可编辑表格组件文档:

https://procomponents.ant.design/components/editable-table

 

纯列表组件代码:

表单引用:

// antd-pro的高级表单组件
import SchemaForm from '@/components/SchemaForm';
import AssetsTable from './AssetsTable';

---------------------------------------

<SchemaForm<API.AssetsProps>
          formRef={formRef}
          dataSource={dataSource.map((item: any) => {
            if (item.id === 'event_asset') {
              return {
                ...item,
                required: '请配置事件资产',
                fieldProps: { ...item.fieldProps, fetchListApi },
                renderFormItem: (schema: any) => <AssetsTable {...schema.fieldProps} />,
                formItemProps: {
                  rules: [
                    {
                      required: true,
                      validator(rule: any, value: any[]) {
                        if (value && value.length) {
                          if (value.length !== accounts?.length) {
                            return Promise.reject(new Error('请为全部账户配置资产事件'));
                          }
                          if (value.find((itemDeep: any) => !itemDeep.deep_bid_type)) {
                            return Promise.reject(new Error('请为全部账户配置深度出价方式'));
                          }
                          return Promise.resolve();
                        }
                        return Promise.resolve();
                      },
                    },
                  ],
                },
              };
            }
            return item;
          })}
          submitter={false}
          autoFocusFirstInput={false}
          onValuesChange={(changedFields: any, allFields: any) => {
           // 表单值改变时的处理逻辑
          }}
        />

  

组件代码:

import { useEffect, useState } from 'react';
import { Modal, Button, Input, message } from 'antd';
import { isEmpty } from 'lodash';
import type { ProColumns } from '@ant-design/pro-table';
import { EditableProTable } from '@ant-design/pro-table';
import { fetchAssetsList } from './services';

const { Search } = Input;

type AssetsProps = {
    value?: any[];
    onChange?: (value?: any[]) => void;
    /** 列表接口地址 */
    fetchListApi?: string;
};

/** 弹窗 */
const AssetsTable: React.FC<AssetsProps> = (props) => {
    const { value, onChange, fetchListApi } = props;
    const [data, setData] = useState<API.AssetsProps[]>(value || []); // 存储选中的数据
    const [visible, setVisible] = useState<boolean>(false);
    const [dataSource, setDataSource] = useState<API.AssetsProps[]>([]); // 列表数据
    const [tableColumns, setTableColumns] = useState<ProColumns[]>([]);
    const [tableParams, setTableParams] = useState<any>();
    const [tableLoading, setTableLoading] = useState<boolean>(false);
    const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
    const [keys, setKeys] = useState<(string | number)[]>([]);

    const handleOk = () => {
        if (onChange) { onChange(data); setVisible(false);}
    };

    const fetchList = async () => {
        setTableLoading(true);
        if (fetchListApi) {
            // 根据资产类型+投放账号调用头条已有的资产作为选择值
            try {
                const res: any = await fetchAssetsList(fetchListApi, tableParams);
                if (res.result) {
                    setTableColumns([]);
                    const { tableHeaderList = [], tableList = [] } = res.result;
                    // 当有存储值时,覆盖掉接口返回的对应数据
                    if (data && data.length) {
                        const initObj = data.find((item: any) => item.account_id === id);
                        const newList = tableList.map((item: any) => {
                            if (initObj && initObj.id && item.id === initObj.id) {
                                return initObj;
                            }
                            return item;
                        });
                        setDataSource(newList);
                        setEditableRowKeys(newList?.map((item: any) => item.id));
                    } else {
                        setDataSource(tableList);
                        setEditableRowKeys(tableList?.map((item: any) => item.id));
                    }
                    // 深度优化目标下拉单选
                    setTableColumns([
                        ...tableHeaderList.map((item: any) => {
                            if (item.dataIndex === 'deep_external_action') {
                                return {
                                    ...item,
                                    width: 200,
                                    valueType: 'select',
                                    valueEnum: (row: any) => {
                                        const optionList = row?.deep_goals
                                            ?.filter((itemF: any) => itemF.optimization_name)
                                            .map((itemO: any) => {
                                                return {
                                                    label: itemO.optimization_name,
                                                    value: itemO.deep_external_action,
                                                };
                                            });
                                        const valueEnumObj = {};
                                        optionList?.forEach((items: any) => {
                                            valueEnumObj[items.value] = { text: items.label };
                                        });
                                        return valueEnumObj;
                                    },
                                };
                            }
                            // 深度出价方式需要动态获取
                            if (item.dataIndex === 'deep_bid_type') {
                                return {
                                    ...item,
                                    width: 200,
                                    valueType: 'select',
                                    valueEnum: (row: any) => {
                                        let deepList: any[] = [];
                                        const hasChoose = row?.deep_goals?.find(
                                            (el: any) => el.deep_external_action === row.deep_external_action,
                                        );
                                        if (hasChoose) {
                                            deepList =
                                                row?.deep_goals.find(
                                                    (el: any) => el.deep_external_action === row.deep_external_action,
                                                )?.deep_bid_type || [];
                                        } else {
                                            deepList = row?.default_deep_bid_type || [];
                                        }
                                        const optionList = deepList?.map((itemO: any) => {
                                            return {
                                                label: itemO.name,
                                                value: itemO.id,
                                            };
                                        });
                                        const valueEnumObj = {};
                                        optionList?.forEach((items: any) => {
                                            valueEnumObj[items.value] = { text: items.label };
                                        });
                                        return valueEnumObj;
                                    },
                                };
                            }
                            return { ...item, editable: false };
                        }),
                    ]);
                }
                setTableLoading(false);
            } catch (error) {
                //
            }
        }
    };

    // 关键词搜索
    useEffect(() => {
        if (!isEmpty(tableParams)) fetchList();
    }, [tableParams]);

    useEffect(() => {
        setData(dataSource || []);
    }, [dataSource]);

    useEffect(() => {
        if (visible) {
            fetchList();
            // 更新data
            if (value && Array.isArray(value) && value.length) {
                setData(value);
            }
        }
    }, [visible]);

    useEffect(() => {
        // 更新data
        if (value && Array.isArray(value) && value.length) {
            setData(value);
        }
    }, [value]);

    return (
        <>
            <div>
                <Button onClick={() => setVisible(true)}>
                    {data && data.length ? '已' : ''}选择事件资产{data && data.length ? ',点击配置' : ''}
                </Button>
            </div>
            <Modal width={1500} visible={visible} onCancel={() => setVisible(false)} onOk={handleOk}>
                <div>
                    <Search
                        addonBefore="资产名称"
                        placeholder="请输入关键词搜索"
                        allowClear
                        onSearch={async (valueSearch: string) =>
                            setTableParams({ ...tableParams, asset_name: valueSearch })
                        }
                        style={{ width: 300, marginBottom: '8px' }}
                    />
                    <EditableProTable
                        rowKey="id"
                        loading={tableLoading}
                        bordered
                        request={async () => ({
                            data: dataSource,
                            total: dataSource.length,
                            success: true,
                        })}
                        tableAlertRender={false}
                        value={dataSource}
                        onChange={(valueNew: any[]) => {
                            const newTableData = valueNew.map((item: any, index: number) => {
                                // 深度优化目标的值改变时,清空本条数据的深度出价方式的值
                                if (item.id === dataSource[index].id) {
                                    if (item.deep_external_action !== dataSource[index].deep_external_action) {
                                        return { ...item, deep_bid_type: undefined };
                                    }
                                    return item;
                                }
                                return item;
                            });
                            setDataSource(newTableData);
                        }}
                        controlled
                        recordCreatorProps={false}
                        editable={{
                            type: 'multiple',
                            editableKeys,
                            actionRender: undefined,
                            onChange: setEditableRowKeys,
                        }}
                        scroll={{ y: 320, x: 'max-content' }}
                        columns={tableColumns}
                        tableRender={undefined}
                        pagination={false}
                        rowSelection={{
                            type: 'radio',
                            selectedRowKeys: keys,
                            onChange: (selectedRowKeys: any, selectedRows: any) => {
                                // 存储选中数据逻辑
                            },
                        }}
                    />
                    {dataSource && dataSource.length ? <p>共 {dataSource.length} 条</p> : null}
                </div>
            </Modal>
        </>
    );
};

export default AssetsTable;

  

--------------------------------------以上代码可满足按钮唤醒弹窗并对列表数据进行行内修改的需求-------------------------------------

 

完整版需求效果展示:

 

 

其实就是多了左侧账号栏,要求给左侧每个账号进行配置,右侧展示可选列表,单项选择,可配置内容,其中选中项必须配置出价方式;

点击确定时校验是否满足要求,否则弹出提示;

重新点开弹窗时也要保留上次选择与配置的内容。

 

具体代码:

import { useEffect, useState, useRef, useContext } from 'react';
import { Modal, Button, Input, message } from 'antd';
import { isEmpty } from 'lodash';
import ScrollList from '@/components/ScrollList';
import type { ProColumns } from '@ant-design/pro-table';
import { EditableProTable } from '@ant-design/pro-table';
import { fetchAssetsList } from './services';
import SmartCreationContext from '../../context';
import styles from './Assets.less';

const { Search } = Input;

type AssetsProps = {
  value?: any[];
  onChange?: (value?: any[]) => void;
  /** 事件资产列表接口地址 */
  fetchListApi?: string;
};

/** 事件资产弹窗 */
const AssetsTable: React.FC<AssetsProps> = (props) => {
  const { value, onChange, fetchListApi } = props;
  const { dependentData, values } = useContext(SmartCreationContext);
  const { accounts = [] } = dependentData || {};
  const { create_rules } = values || {};
  const adsRef = useRef<any>();
  const [data, setData] = useState<API.AssetsProps[]>(value || []); // 存储选中的数据
  const [visible, setVisible] = useState<boolean>(false);
  const [dataSource, setDataSource] = useState<API.AssetsProps[]>([]); // 列表数据
  const [tableColumns, setTableColumns] = useState<ProColumns[]>([]);
  const [tableParams, setTableParams] = useState<any>();
  const [tableLoading, setTableLoading] = useState<boolean>(false);
  const [targetAccount, setTargetAccount] = useState<any>('');
  const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
  const [keys, setKeys] = useState<(string | number)[]>([]);

  const handleOk = () => {
    if (onChange) {
      if (data.length === accounts.length) {
        if (data?.find((itemDeep: any) => !itemDeep.deep_bid_type)) {
          message.error('请为全部账户配置深度出价方式');
          return;
        }
        onChange(data);
        setVisible(false);
      } else {
        message.error('请为全部账户配置资产事件');
      }
    }
  };

  const fetchList = async (id: string) => {
    setTargetAccount(id);
    setTableLoading(true);
    const trace_events = JSON.parse(sessionStorage.getItem('ASSETS_FORM') || '');
    if (fetchListApi) {
      // 根据账号调用已有的资产作为选择值
      try {
        const res: any = await fetchAssetsList(fetchListApi, {
          account_id: id, // 账号
          ...tableParams,
        });
        if (res.result) {
          setTableColumns([]);
          const { tableHeaderList = [], tableList = [] } = res.result;
          const dataTable = tableList.map((item: any) => {
            return { ...item, account_id: id };
          });
          // 当有存储值时,覆盖掉接口返回的对应数据
          if (data && data.length) {
            const initObj = data.find((item: any) => item.account_id === id);
            const newList = dataTable.map((item: any) => {
              if (initObj && initObj.id && item.id === initObj.id) {
                return initObj;
              }
              return item;
            });
            setDataSource(newList);
            setEditableRowKeys(newList?.map((item: any) => item.id));
          } else {
            setDataSource(dataTable);
            setEditableRowKeys(dataTable?.map((item: any) => item.id));
          }
          // 深度优化目标下拉单选
          setTableColumns([
            ...tableHeaderList.map((item: any) => {
              if (item.dataIndex === 'deep_external_action') {
                return {
                  ...item,
                  width: 200,
                  valueType: 'select',
                  valueEnum: (row: any) => {
                    const optionList = row?.deep_goals
                      ?.filter((itemF: any) => itemF.optimization_name)
                      .map((itemO: any) => {
                        return {
                          label: itemO.optimization_name,
                          value: itemO.deep_external_action,
                        };
                      });
                    const valueEnumObj = {};
                    optionList?.forEach((items: any) => {
                      valueEnumObj[items.value] = { text: items.label };
                    });
                    return valueEnumObj;
                  },
                };
              }
              // 深度出价方式需要动态获取
              if (item.dataIndex === 'deep_bid_type') {
                return {
                  ...item,
                  width: 200,
                  valueType: 'select',
                  valueEnum: (row: any) => {
                    let deepList: any[] = [];
                    const hasChoose = row?.deep_goals?.find(
                      (el: any) => el.deep_external_action === row.deep_external_action,
                    );
                    if (hasChoose) {
                      deepList =
                        row?.deep_goals.find(
                          (el: any) => el.deep_external_action === row.deep_external_action,
                        )?.deep_bid_type || [];
                    } else {
                      deepList = row?.default_deep_bid_type || [];
                    }
                    const optionList = deepList?.map((itemO: any) => {
                      return {
                        label: itemO.name,
                        value: itemO.id,
                      };
                    });
                    const valueEnumObj = {};
                    optionList?.forEach((items: any) => {
                      valueEnumObj[items.value] = { text: items.label };
                    });
                    return valueEnumObj;
                  },
                };
              }
               return {
                  ...item,
                  width: 200,
                  editable: false,
                };
            }),
          ]);
        }
        setTableLoading(false);
      } catch (error) {
        //
      }
    }
  };

  // 关键词搜索
  useEffect(() => {
    if (!isEmpty(tableParams)) fetchList(targetAccount);
  }, [tableParams]);

  useEffect(() => {
    if (dataSource.length) {
      const newList = data?.map((item: any) => {
        if (item.account_id === targetAccount) {
          const newObj = dataSource.find((itemList: any) => itemList.id === item.id);
          return newObj;
        }
        return item;
      });
      setData(newList);
    } else {
      setData([]);
    }
  }, [dataSource]);

  // 当前选中key
  useEffect(() => {
    if (targetAccount) {
      if (data && data.length) {
        const idList = data
          .filter((item) => item.account_id === targetAccount)
          .map((item: API.AssetsProps) => {
            return item.id;
          });
        setKeys(idList);
      } else {
        setKeys([]);
      }
    }
  }, [targetAccount, data]);

  // 打开弹窗时,默认左侧广告投放列表选中第一项,并进行列表请求
  useEffect(() => {
    if (accounts && accounts[0]) {
      adsRef?.current?.selectItem(accounts[0]);
      setTargetAccount(accounts[0].id);
      if (visible) {
        fetchList(accounts[0].id || '');
        // 更新data
        if (value && Array.isArray(value) && value.length) {
          setData(value);
        }
      }
    }
  }, [visible]);

  useEffect(() => {
    // 更新data
    if (value && Array.isArray(value) && value.length) {
      setData(value);
    }
  }, [value]);

  return (
    <>
      <div>
        <Button
          onClick={() => {
            const trace_events = JSON.parse(sessionStorage.getItem('ASSETS_FORM') || '');
            if (!trace_events?.app_type) {
              message.error('请选择资产类型');
              return;
            }
            setVisible(true);
          }}
        >
          {data && data.length ? '已' : ''}选择事件资产{data && data.length ? ',点击配置' : ''}
        </Button>
        {data && data.length ? null : (
          <span style={{ marginLeft: '8px' }}>
            找不到适用的资产,去
            <a
              href="/"
              key="createAssets"
              target="_blank"
              rel="noreferrer"
            >
              创建资产
            </a>
            !
          </span>
        )}
      </div>
      <Modal width={1500} visible={visible} onCancel={() => setVisible(false)} onOk={handleOk}>
        <div className={styles['existring-ads-list']}>
          <div style={{ width: '16%', marginRight: '16px' }}>
            <ScrollList cRef={adsRef} list={accounts} title="账号名称" onChange={fetchList} />
          </div>
          <div style={{ width: '84%' }}>
            <Search
              addonBefore="资产名称"
              placeholder="请输入关键词搜索"
              allowClear
              onSearch={async (valueSearch: string) =>
                setTableParams({ ...tableParams, asset_name: valueSearch })
              }
              style={{ width: 300, marginBottom: '8px' }}
            />
            <EditableProTable
              rowKey="id"
              loading={tableLoading}
              bordered
              request={async () => ({
                data: dataSource,
                total: dataSource.length,
                success: true,
              })}
              tableAlertRender={false}
              className={styles['exist-table-edit']}
              value={dataSource}
              onChange={(valueNew: any[]) => {
                const newTableData = valueNew.map((item: any, index: number) => {
                  // 深度优化目标的值改变时,清空本条数据的深度出价方式的值
                  if (item.id === dataSource[index].id) {
                    if (item.deep_external_action !== dataSource[index].deep_external_action) {
                      return { ...item, deep_bid_type: undefined };
                    }
                    return item;
                  }
                  return item;
                });
                setDataSource(newTableData);
              }}
              controlled
              recordCreatorProps={false}
              editable={{
                type: 'multiple',
                editableKeys,
                actionRender: undefined,
                onChange: setEditableRowKeys,
              }}
              scroll={{ y: 320, x: 'max-content' }}
              columns={tableColumns}
              tableRender={undefined}
              pagination={false}
              rowSelection={{
                type: 'radio',
                selectedRowKeys: keys,
                onChange: (selectedRowKeys: any, selectedRows: any) => {
                  if (data && data.length) {
                    // hasAccount = 当前账号是否存在
                    const hasAccount = data.filter((item) => item.account_id === targetAccount);
                    if (hasAccount && hasAccount.length) {
                      const newList = data.map((item: API.AssetsProps) => {
                        if (item.account_id === targetAccount) {
                          return selectedRows[0];
                        }
                        return item;
                      });
                      setData(newList);
                    } else {
                      setData([...data, ...selectedRows]);
                    }
                  } else {
                    setData(selectedRows);
                  }
                },
              }}
            />
            {dataSource && dataSource.length ? <p>共 {dataSource.length} 条</p> : null}
          </div>
        </div>
      </Modal>
    </>
  );
};

export default AssetsTable;

  

posted @ 2022-08-22 15:58  芝麻小仙女  阅读(2088)  评论(0编辑  收藏  举报