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;
    }

相关推荐
A尘埃27 分钟前
大模型应用python+Java后端+Vue前端的整合
java·前端·python
遥遥晚风点点1 小时前
Spark导出数据文件到HDFS
前端·javascript·ajax
克里斯蒂亚L1 小时前
开发一个计时器组件
前端·浏览器
克里斯蒂亚诺更新2 小时前
微信小程序 点击某个marker改变其大小
开发语言·前端·javascript
天才奇男子2 小时前
从零开始搭建Linux Web服务器
linux·服务器·前端
长空任鸟飞_阿康2 小时前
AI 多模态全栈应用项目描述
前端·vue.js·人工智能·node.js·语音识别
Mintopia2 小时前
🌐 实时协同 AIGC:多人在线 Web 创作的技术架构设计
前端·人工智能·trae
Mintopia2 小时前
🔥 “Solo Coding”的近期热度解析(截至 2025 年末)
前端·人工智能·trae
顾安r3 小时前
11.14 脚本网页游戏 猜黑红
前端·javascript·游戏·flask·html
码码哈哈0.03 小时前
Vue 3 + Vite 集成 Spring Boot 完整部署指南 - 前后端一体化打包方案
前端·vue.js·spring boot