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

纯列表版效果展示:

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

 

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

 

 ③配置后

 

 

可编辑表格组件文档:

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

 

纯列表组件代码:

表单引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 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) => {
           // 表单值改变时的处理逻辑
          }}
        />

  

组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
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;

  

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

 

完整版需求效果展示:

 

 

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

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

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

 

具体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
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 @   芝麻小仙女  阅读(2254)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示