Steps + Input.TextArea + InfiniteScroll 联调优化

复制代码
<Modal
                title="人才特征模型"
                open={modalOpen}
                centered
                onCancel={() => { setModalOpen(false); setEditingItemId(null); }}
                footer={null}
                width={800}
                maskClosable={false}
                classNames={{
                    header: styles.modalHeader,
                    content: styles.modal,
                }}
            >
                {modalInitialLoading ? (
                    <div className={styles.initialLoading}>加载中...</div>
                ) : (modelDesc.length === 0 ? (
                    <div className={styles.emptyTip}>暂无人才特征模型数据</div>
                ) : (
                    <div id="scrollContainer" style={{ height: 700, overflow: 'auto',padding: '0 10px', }}>
                        <InfiniteScroll
                            dataLength={modelDesc.length}
                            next={loadMore}
                            hasMore={hasMore}
                            loader={<div className={styles.loading}>加载中...</div>}
                            endMessage={<div style={{ textAlign: 'center', padding: 16 }}>已加载全部数据</div>}
                            scrollableTarget="scrollContainer"
                        >
                            <Steps
                                direction="vertical"
                                className={styles.steps}
                                progressDot
                                style={{ width: '100%' }}
                            >
                                {modelDesc.map((item, index) => (
                                    <Steps.Step
                                        key={index}
                                        status='finish'
                                        title={
                                            <Flex justify="space-between" align="center" className={styles.infoBar}>
                                                <div style={{ width: 500 }}>
                                                    <Flex gap={20} align="center">
                                                        <span className={styles.infoItem} style={{ width: '45%' }}>
                                                            <img
                                                                src="https://xingge-ai.oss-cn-shenzhen.aliyuncs.com/fusi/Users Group Two Rounded.png"
                                                                style={{ marginRight: 4, verticalAlign: 'middle', marginTop: -2 }}
                                                                alt="" width={18} height={18}
                                                            />
                                                            候选人:{item.applyName || 'null'}
                                                        </span>
                                                        <span className={styles.infoItem} style={{ width: '65%' }}>
                                                            <img
                                                                src="https://xingge-ai.oss-cn-shenzhen.aliyuncs.com/fusi/Clock Circle.png"
                                                                style={{ marginRight: 4, verticalAlign: 'middle', marginTop: -2 }}
                                                                alt="" width={18} height={18}
                                                            />
                                                            更新时间:{item.updateTime || 'null'}
                                                        </span>
                                                    </Flex>
                                                </div>
                                                <div style={{ width: '30%', textAlign: 'right' }}>
                                                    {editingItemId === item.id ? (
                                                        <>
                                                            <Button
                                                                size="small"
                                                                type="text"
                                                                style={{ color: '#0057FF', fontSize: 16, fontWeight: 500 }}
                                                                onClick={() => saveEdit(item)}
                                                                loading={modalLoading}
                                                            >
                                                                保存
                                                            </Button>
                                                            <Button
                                                                size="small"
                                                                type="text"
                                                                style={{ fontSize: 16, fontWeight: 500 }}
                                                                onClick={() => cancelEdit(item.id)}
                                                            >
                                                                取消
                                                            </Button>
                                                        </>
                                                    ) : (
                                                        <Button
                                                            icon={<EditOutlined style={{ color: '#0057FF' }} />}
                                                            size="small"
                                                            type="text"
                                                            style={{ color: '#0057FF', fontSize: 16, fontWeight: 500 }}
                                                            onClick={() => startEdit(item)}
                                                        >
                                                            编辑
                                                        </Button>
                                                    )}
                                                </div>
                                            </Flex>
                                        }
                                        description={
                                            <div className={styles.timelineContentWrapper}>
                                                {editingItemId === item.id ? (
                                                    <Input.TextArea
                                                        value={editContents[item.id] || ''}
                                                        onChange={(e) =>
                                                            setEditContents((prev) => ({
                                                                ...prev,
                                                                [item.id]: e.target.value,
                                                            }))
                                                        }
                                                        autoSize={{ minRows: 10, maxRows: 10 }}
                                                    />
                                                ) : (
                                                    <div
                                                        className={styles.viewModeContainer}
                                                        dangerouslySetInnerHTML={{ __html: md.render(item.modelContent || '')}}
                                                    />
                                                )}
                                            </div>
                                        }
                                    />
                                ))}
                            </Steps>
                        </InfiniteScroll>
                    </div>
                ))
                }
            </Modal >

主要优化点:

  • 利用 InfiniteScroll 实现滚动加载

  • modalInitialLoading 添加内容加载显示

    import InfiniteScroll from 'react-infinite-scroll-component';

    复制代码
      const [modalOpen, setModalOpen] = useState(false); // 模型弹窗
      const [modelDesc, setModelDesc] = useState([]); // 模型描述
      const [currentPage, setCurrentPage] = useState(1);
      const [pageSize, setPageSize] = useState(5);
      const [modalLoading, setModalLoading] = useState(false);
    
      const [editingItemId, setEditingItemId] = useState(null);
      const [editContents, setEditContents] = useState({});
      const [hasMore, setHasMore] = useState(true); // 是否还有更多数据
      const [modalInitialLoading, setModalInitialLoading] = useState(false);

    // 处理模型弹窗
    const handleModal = async (item) => {
    setModalOpen(true);
    setModalInitialLoading(true);
    setCurrentPage(1);
    setModelDesc([]);
    setHasMore(true);
    setJobId(item.id);
    try {
    const res = await getPersonalityModel({
    jobId: item.id,
    pageNum: 1,
    pageSize: pageSize,
    });

    复制代码
              if (res.code === 200) {
                  setModelDesc(res.rows || '');
                  setTotal(res.total || 0);
                  setHasMore((res.rows || []).length < (res.total || 0));
              } else {
                  message.warning('当前职位不存在人才特征模型');
                  setModalOpen(false);
              }
          } catch (error) {
              console.log(error);
          } finally {
              setModalInitialLoading(false);
          }
      }
    
      const loadMore = async () => {
          try {
              const nextPage = currentPage + 1;
              const res = await getPersonalityModel({
                  jobId: jobId,
                  pageNum: nextPage,
                  pageSize: pageSize,
              });
    
              if (res.code === 200) {
                  const newData = res.rows || [];
                  setModelDesc(prev => [...prev, ...newData]);
                  setCurrentPage(nextPage);
                  setHasMore([...modelDesc, ...newData].length < total);
              } else {
                  message.warning('没有更多数据了');
              }
          } catch (error) {
              console.log('加载更多失败:', error);
          }
      };
    
      const startEdit = (item) => {
          setEditingItemId(item.id);
          setEditContents((prev) => ({
              ...prev,
              [item.id]: item.modelContent,
          }));
      };
    
    
      const cancelEdit = (itemId) => {
          setEditingItemId(null);
          setEditContents((prev) => {
              const newState = { ...prev };
              delete newState[itemId];
              return newState;
          });
      };
    
      const saveEdit = async (item) => {
          setModalLoading(true);
          try {
              const res = await saveFeatureModel({
                  id: item.id,
                  modelContent: editContents[item.id] || ''
              });
              if (res.code === 200) {
                  message.success('保存成功');
              } else {
                  message.warning('保存失败');
              }
              const updatedData = modelDesc.map((modelItem) => {
                  if (modelItem.id === item.id) {
                      return { ...modelItem, modelContent: editContents[item.id] || '' };
                  }
                  return modelItem;
              });
              setModelDesc(updatedData);
              setEditingItemId(null);
          } catch (error) {
              console.log(error);
              message.error('保存失败');
          } finally {
              setModalLoading(false);
          }
      };

    .modal {
    background-image: url('https://xingge-ai.oss-cn-shenzhen.aliyuncs.com/fusi/image 28.png');
    background-size: 500px 350px;
    background-repeat: no-repeat;
    background-position: right top;
    height: 800px;
    }

    .initialLoading {
    text-align: center;
    padding: 45% 0;
    font-size: 16px;
    color: #666;
    }

    .listItem {
    padding: 0 !important;
    border: none !important;
    }

    .emptyTip {
    color: #333;
    font-size: 16px;
    font-weight: 500;
    text-align: center;
    margin-top: 45%;
    }

    .timelineItem {
    display: flex;
    margin-bottom: 30px;
    position: relative;
    }

    .steps {
    margin-right: 10px;

    复制代码
      :global(.ant-steps-item-title) {
          color: #000000 !important;
          padding: 0;
          width: 700px;
      }

    }

    .timelineContentWrapper {
    margin-top: 10px;
    flex: 1;
    }

    .infoBar {
    width: 100%;
    flex-wrap: nowrap;
    overflow: hidden;

    复制代码
      .infoItem {
          color: #767676;
          font-size: 16px;
          height: 30px;
          font-weight: 500;
      }

    }

    .viewModeContainer {
    min-height: 200px;
    padding: 6px 12px;
    border: 1px solid #d9d9d9;
    border-radius: 4px;
    word-break: break-all;
    }

    .loading {
    text-align: center;
    padding: 20px 0;
    }

相关推荐
皓月Code4 小时前
第四章、路由配置
前端·javascript·react.js·1024程序员节
Mr.Jessy5 小时前
JavaScript学习第六天:函数
开发语言·前端·javascript·学习·html·1024程序员节
code_YuJun5 小时前
管理系统——应用初始化 Loading 动画
前端
oak隔壁找我5 小时前
JavaScript 模块化演进历程:问题与解决方案。
前端·javascript·架构
Elieal6 小时前
AJAX 知识
前端·ajax·okhttp
sulikey6 小时前
Qt 入门简洁笔记:从框架概念到开发环境搭建
开发语言·前端·c++·qt·前端框架·visual studio·qt框架
烛阴7 小时前
循环背后的魔法:Lua 迭代器深度解析
前端·lua
元拓数智7 小时前
现代前端状态管理深度剖析:从单一数据源到分布式状态
前端·1024程序员节
mapbar_front7 小时前
Electron 应用自动更新方案:electron-updater 完整指南
前端·javascript·electron