npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kne/button-group

v0.1.24

Published

实现了一个按钮组,loading按钮,确认按钮和加载按钮.

Downloads

308

Readme

button-group

描述

实现了一个按钮组,loading按钮,确认按钮和加载按钮.

安装

npm i --save @kne/button-group

概述

@kne/button-group 是一个 React 按钮组件库,提供了一系列功能丰富的按钮组件,用于简化常见的按钮交互场景。该库专注于提供自适应布局、加载状态管理、确认操作和数据请求等功能,使开发者能够快速实现各种复杂的按钮交互需求。

ButtonGroup

自适应按钮组组件,能够根据容器宽度自动调整显示的按钮数量。当容器宽度不足以显示所有按钮时,会自动将多余的按钮放入下拉菜单中,确保界面布局美观且功能完整。

主要特性:

  • 自动适应容器宽度
  • 支持紧凑模式(Space.Compact)
  • 支持链接样式(适用于表格操作列)
  • 可指定显示的按钮数量
  • 支持禁用、隐藏状态
  • 支持自定义按钮渲染
  • 支持禁用按钮的工具提示

适用场景:

  • 操作栏、工具栏
  • 表格操作列
  • 面板标题栏
  • 任何需要自适应按钮布局的场景

LoadingButton

封装了加载状态的按钮组件,简化了异步操作的处理。通过内置的状态管理,在异步操作执行期间自动显示加载状态,提升用户体验。同时提供了 useLoading hook,方便在其他组件中复用加载状态管理逻辑。

主要特性:

  • 自动管理加载状态
  • 支持自定义加载文案
  • 自动处理错误情况
  • 支持 loading 属性手动控制
  • 提供的 useLoading Hook 可在其他组件使用

适用场景:

  • 表单提交按钮
  • 数据导出按钮
  • 任何需要异步操作的按钮
  • 非按钮组件的异步状态管理

ConfirmButton

带有确认功能的按钮组件,支持弹窗确认(Popconfirm)和模态框确认(Modal)两种模式。适用于需要用户二次确认的操作,如删除、提交等重要操作。同时提供了 ConfirmLink 和 ConfirmText 变体,以及 withConfirm 高阶组件,满足不同场景的确认需求。

主要特性:

  • 支持 Popconfirm 和 Modal 两种确认模式
  • 支持危险操作样式(红色按钮)
  • 可自定义确认和取消按钮文案
  • 提供 ConfirmLink 和 ConfirmText 变体
  • 确认时自动显示加载状态

适用场景:

  • 删除操作
  • 数据提交
  • 审核操作
  • 表格行操作(使用 ConfirmLink/ConfirmText)
  • 任何需要用户二次确认的操作

FetchButton

集成了数据请求功能的按钮组件,基于 @kne/react-fetch 库实现。可以直接处理 API 请求,并在请求过程中自动管理加载状态,简化了数据交互的实现。

主要特性:

  • 自动管理请求加载状态
  • 支持请求成功/失败回调
  • 支持参数传递
  • 支持请求拦截(beforeFetch)
  • 基于 @kne/react-fetch 实现

适用场景:

  • 数据刷新按钮
  • 文件导出按钮
  • API 请求按钮
  • 任何按钮触发的数据请求场景

ButtonFooter

页面底部按钮区域组件,可以自动计算高度并设置 CSS 变量,方便页面布局和样式调整。在小屏幕下,会自动将内容渲染到 body,确保按钮始终可见。

主要特性:

  • 自动计算高度并设置 CSS 变量
  • 响应式设计,小屏幕下固定到底部
  • 支持多种布局方式(居中、左右分布等)
  • 适用于表单页面的底部操作区

适用场景:

  • 表单页面底部操作按钮
  • 详情页面底部操作按钮
  • 对话框底部按钮
  • 任何需要固定在底部的操作按钮区域

组件关系

ButtonGroup (自适应布局)
  ├── LoadingButton (加载状态)
  ├── ConfirmButton (确认功能)
  │   ├── ConfirmLink (链接变体)
  │   └── ConfirmText (文本变体)
  └── FetchButton (数据请求)
      └── 基于 LoadingButton

ButtonFooter (底部固定区域)

设计理念

该组件库的设计理念是通过封装常见的按钮交互模式,提供开箱即用的解决方案,同时保持足够的灵活性和可扩展性:

  1. 关注点分离:每个组件专注于解决特定的问题,如 ButtonGroup 专注于布局,LoadingButton 专注于状态管理。

  2. 组合优于继承:通过组合不同的功能组件,可以实现复杂的交互需求,如 FetchButton 就是 LoadingButton 与数据请求功能的组合。

  3. 声明式 API:提供简洁明了的 API,使开发者能够以声明式的方式描述 UI 和交互行为。

  4. 渐进式增强:基础组件可以独立使用,也可以与其他组件组合使用,实现更复杂的功能。

  5. 用户体验优先:注重细节,如自适应布局、加载状态反馈等,提升最终用户的使用体验。

特性总结

  • 自适应布局:根据容器宽度自动调整按钮显示方式
  • 加载状态管理:简化异步操作的加载状态处理
  • 操作确认:提供多种确认模式,增强用户操作安全性
  • 数据请求集成:简化按钮与后端 API 的交互
  • 国际化支持:内置中英文语言包
  • 高度可定制:组件提供丰富的配置选项
  • 响应式设计:适配不同屏幕尺寸
  • TypeScript 友好:完整的类型定义

示例

示例代码

  • ButtonGroup 基础用法
  • ButtonGroup 能够根据容器宽度自动调整显示的按钮数量,并将多余的按钮放入下拉菜单中。适用于操作栏、工具栏等场景。
  • _ButtonGroup(@kne/current-lib_button-group)[import * as _ButtonGroup from "@kne/button-group"],antd(antd)
const { default: ButtonGroup } = _ButtonGroup;
const { Flex, Button, Space, Typography } = antd;
const { useState } = React;
const { Text } = Typography;

// 基础用法 - 自动适应容器宽度
const BasicExample = () => {
  const [width, setWidth] = useState(300);
  return (
    <Flex gap={16} vertical>
      <Text type="secondary">调整容器宽度查看自适应效果</Text>
      <Flex gap={8}>
        <div style={{ width: &#96;${width}px&#96;, padding: '12px', background: '#f5f5f5', borderRadius: '8px' }}>
          <ButtonGroup
            list={[
              { type: 'primary', children: '新建' },
              { type: 'default', children: '编辑' },
              { type: 'default', children: '导出' },
              { type: 'default', children: '打印' },
              { children: '更多操作1', isDelete: false, message: '确定执行吗?' },
              { children: '更多操作2', message: '确定执行吗?' },
              { children: '删除', isDelete: true }
            ]}
          />
        </div>
      </Flex>
      <Space>
        <Button onClick={() => setWidth(w => Math.max(200, w - 50))}>减少宽度</Button>
        <Button onClick={() => setWidth(w => Math.min(600, w + 50))}>增加宽度</Button>
      </Space>
    </Flex>
  );
};

// 紧凑模式
const CompactExample = () => {
  return (
    <Flex gap={16} vertical>
      <Text type="secondary">紧凑模式(适用于工具栏)</Text>
      <ButtonGroup compact list={[{ type: 'primary', children: '保存' }, { children: '撤销' }, { children: '重做' }, { children: '删除', isDelete: true }]} />
    </Flex>
  );
};

// 链接样式 - 更多按钮
const LinkStyleExample = () => {
  const [width, setWidth] = useState(200);
  return (
    <Flex gap={16} vertical>
      <Text type="secondary">链接样式(适用于表格操作栏)</Text>
      <div style={{ width: &#96;${width}px&#96; }}>
        <ButtonGroup
          moreType="link"
          list={[
            { children: '查看', type: 'link' },
            { children: '编辑', type: 'link' },
            { children: '删除', type: 'link', isDelete: true, message: '确定删除吗?' },
            { children: '审核', type: 'link' },
            { children: '驳回', type: 'link' }
          ]}
        />
      </div>
      <Space>
        <Button onClick={() => setWidth(w => Math.max(150, w - 30))}>-</Button>
        <Button onClick={() => setWidth(w => Math.min(400, w + 30))}>+</Button>
      </Space>
    </Flex>
  );
};

// 指定显示数量
const FixedLengthExample = () => {
  const [showLength, setShowLength] = useState(2);
  return (
    <Flex gap={16} vertical>
      <Text type="secondary">指定显示按钮数量(showLength)</Text>
      <Space>
        <Button type={showLength === 1 ? 'primary' : 'default'} onClick={() => setShowLength(1)}>
          显示1个
        </Button>
        <Button type={showLength === 2 ? 'primary' : 'default'} onClick={() => setShowLength(2)}>
          显示2个
        </Button>
        <Button type={showLength === 3 ? 'primary' : 'default'} onClick={() => setShowLength(3)}>
          显示3个
        </Button>
      </Space>
      <ButtonGroup showLength={showLength} list={[{ type: 'primary', children: '主要操作' }, { children: '次要操作1' }, { children: '次要操作2' }, { children: '次要操作3' }, { children: '次要操作4' }]} />
    </Flex>
  );
};

// 自定义渲染函数
const CustomRenderExample = () => {
  const CustomButton = props => (
    <Button {...props} style={{ borderRadius: '4px' }}>
      {props.children}
    </Button>
  );

  return (
    <Flex gap={16} vertical>
      <Text type="secondary">自定义渲染(支持函数式配置)</Text>
      <div style={{ padding: '12px', background: '#f5f5f5', borderRadius: '8px', width: '280px' }}>
        <ButtonGroup
          moreType="link"
          list={[
            props => (
              <CustomButton {...props} type="primary">
                自定义按钮
              </CustomButton>
            ),
            props => <CustomButton {...props}>按钮2</CustomButton>,
            props => <CustomButton {...props}>按钮3</CustomButton>,
            props => <CustomButton {...props}>按钮4</CustomButton>
          ]}
        />
      </div>
    </Flex>
  );
};

// 禁用状态与隐藏
const StateExample = () => {
  const [disabled, setDisabled] = useState(true);
  const [hidden, setHidden] = useState(true);

  return (
    <Flex gap={16} vertical>
      <Text type="secondary">禁用与隐藏状态</Text>
      <Space>
        <Button onClick={() => setDisabled(!disabled)}>{disabled ? '启用' : '禁用'}操作3</Button>
        <Button onClick={() => setHidden(!hidden)}>{hidden ? '显示' : '隐藏'}操作4</Button>
      </Space>
      <ButtonGroup list={[{ type: 'primary', children: '操作1' }, { children: '操作2' }, { children: '操作3', disabled }, { children: '操作4', hidden }, { children: '操作5', message: '确定吗?' }]} />
    </Flex>
  );
};

// 工具提示
const TooltipExample = () => {
  return (
    <Flex gap={16} vertical>
      <Text type="secondary">禁用按钮提示(tooltipProps)</Text>
      <ButtonGroup
        list={[
          { type: 'primary', children: '可用操作' },
          {
            children: '已禁用操作',
            disabled: true,
            tooltipProps: {
              title: '此操作暂时不可用,请先完成前置步骤',
              placement: 'bottom'
            }
          },
          {
            children: '需要权限',
            disabled: true,
            tooltipProps: {
              title: '您没有执行此操作的权限',
              placement: 'bottom'
            }
          }
        ]}
      />
    </Flex>
  );
};

const BaseExample = () => {
  return (
    <Space direction="vertical" size="large">
      <Typography.Title level={3}>ButtonGroup 自适应按钮组</Typography.Title>
      <Typography.Paragraph>ButtonGroup 是一个自适应按钮组组件,能够根据容器宽度自动调整显示的按钮数量, 多余的按钮会放入下拉菜单中。适用于操作栏、工具栏、表格操作列等场景。</Typography.Paragraph>

      <Flex vertical gap={32}>
        <div>
          <Typography.Title level={4}>基础用法</Typography.Title>
          <BasicExample />
        </div>

        <div>
          <Typography.Title level={4}>紧凑模式</Typography.Title>
          <CompactExample />
        </div>

        <div>
          <Typography.Title level={4}>链接样式</Typography.Title>
          <LinkStyleExample />
        </div>

        <div>
          <Typography.Title level={4}>指定显示数量</Typography.Title>
          <FixedLengthExample />
        </div>

        <div>
          <Typography.Title level={4}>自定义渲染</Typography.Title>
          <CustomRenderExample />
        </div>

        <div>
          <Typography.Title level={4}>禁用与隐藏</Typography.Title>
          <StateExample />
        </div>

        <div>
          <Typography.Title level={4}>禁用提示</Typography.Title>
          <TooltipExample />
        </div>
      </Flex>
    </Space>
  );
};

render(<BaseExample />);
  • LoadingButton 加载按钮
  • LoadingButton 封装了加载状态,简化异步操作的处理。点击按钮时自动显示加载状态,避免重复提交。
  • _ButtonGroup(@kne/current-lib_button-group)[import * as _ButtonGroup from "@kne/button-group"],antd(antd)
const { LoadingButton, useLoading } = _ButtonGroup;
const { Space, Button, Typography, message, Card, Alert, Flex } = antd;
const { useState } = React;

// 基础用法 - 自动加载状态
const BasicExample = () => {
  const handleClick = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        message.success('操作成功!');
        resolve();
      }, 1500);
    });
  };

  return (
    <Space direction="vertical">
      <Typography.Text type="secondary">点击按钮,自动管理加载状态</Typography.Text>
      <Space wrap>
        <LoadingButton type="primary" onClick={handleClick}>
          保存数据
        </LoadingButton>
        <LoadingButton onClick={handleClick}>提交审核</LoadingButton>
        <LoadingButton danger onClick={handleClick}>删除</LoadingButton>
      </Space>
    </Space>
  );
};

// 自定义加载文案
const CustomTextExample = () => {
  const handleClick = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        message.success('上传完成');
        resolve();
      }, 2000);
    });
  };

  return (
    <Space direction="vertical">
      <Typography.Text type="secondary">使用函数自定义加载时的文案</Typography.Text>
      <Space>
        <LoadingButton onClick={handleClick}>
          {(isLoading) => (isLoading ? '正在上传...' : '上传文件')}
        </LoadingButton>
        <LoadingButton onClick={handleClick} type="primary">
          {(isLoading) => (isLoading ? '提交中...' : '提交订单')}
        </LoadingButton>
      </Space>
    </Space>
  );
};

// 错误处理
const ErrorExample = () => {
  const [shouldFail, setShouldFail] = useState(false);

  const handleClick = () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (shouldFail) {
          message.error('操作失败,请重试');
          reject(new Error('操作失败'));
        } else {
          message.success('操作成功');
          resolve();
        }
      }, 1000);
    });
  };

  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      <Typography.Text type="secondary">
        演示错误处理:加载状态会自动解除
      </Typography.Text>
      <Space>
        <Button onClick={() => setShouldFail(!shouldFail)}>
          {shouldFail ? '切换为成功' : '切换为失败'}
        </Button>
      </Space>
      <Space>
        <LoadingButton onClick={handleClick}>
          {shouldFail ? '会失败的操作' : '会成功的操作'}
        </LoadingButton>
      </Space>
      {shouldFail && <Alert message="当前设置为失败模式" type="warning" />}
    </Space>
  );
};

// 手动控制加载状态
const ManualExample = () => {
  const [loading, setLoading] = useState(false);

  const handleClick = () => {
    setLoading(true);
    setTimeout(() => {
      setLoading(false);
      message.success('手动控制加载完成');
    }, 2000);
  };

  return (
    <Space direction="vertical">
      <Typography.Text type="secondary">通过 loading 属性手动控制加载状态</Typography.Text>
      <LoadingButton loading={loading} onClick={handleClick}>
        手动控制加载
      </LoadingButton>
    </Space>
  );
};

// useLoading Hook 示例
const UseLoadingExample = () => {
  const { isLoading, callback } = useLoading(() => {
    return new Promise((resolve) => {
      setTimeout(() => {
        message.success('Hook 模式操作完成');
        resolve();
      }, 1500);
    });
  });

  return (
    <Card title="useLoading Hook" style={{ width: 400 }}>
      <Space direction="vertical" style={{ width: '100%' }}>
        <Typography.Text type="secondary">
          在非按钮组件中使用 useLoading 管理异步状态
        </Typography.Text>
        <Space>
          <Button onClick={callback} loading={isLoading}>
            使用 Hook
          </Button>
          <Button onClick={() => {}}>
            独立按钮(不受影响)
          </Button>
        </Space>
        {isLoading && (
          <Alert message="当前状态:加载中" type="info" showIcon />
        )}
      </Space>
    </Card>
  );
};

// 不同按钮类型
const ButtonTypesExample = () => {
  const handleClick = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        message.success('完成');
        resolve();
      }, 1000);
    });
  };

  return (
    <Space direction="vertical">
      <Typography.Text type="secondary">支持所有 Ant Design Button 类型</Typography.Text>
      <Space wrap>
        <LoadingButton type="primary" onClick={handleClick}>Primary</LoadingButton>
        <LoadingButton type="default" onClick={handleClick}>Default</LoadingButton>
        <LoadingButton type="dashed" onClick={handleClick}>Dashed</LoadingButton>
        <LoadingButton type="link" onClick={handleClick}>Link</LoadingButton>
        <LoadingButton type="text" onClick={handleClick}>Text</LoadingButton>
      </Space>
      <Space wrap>
        <LoadingButton type="primary" ghost onClick={handleClick}>Primary Ghost</LoadingButton>
        <LoadingButton type="default" ghost onClick={handleClick}>Default Ghost</LoadingButton>
      </Space>
    </Space>
  );
};

// 图标按钮
const IconExample = () => {
  const handleClick = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        message.success('操作完成');
        resolve();
      }, 1200);
    });
  };

  return (
    <Space direction="vertical">
      <Typography.Text type="secondary">支持图标</Typography.Text>
      <Space>
        <LoadingButton type="primary" icon={<span>⬆️</span>} onClick={handleClick}>
          上传
        </LoadingButton>
        <LoadingButton icon={<span>⬇️</span>} onClick={handleClick}>
          下载
        </LoadingButton>
        <LoadingButton danger icon={<span>🗑️</span>} onClick={handleClick}>
          删除
        </LoadingButton>
      </Space>
    </Space>
  );
};

// 实际应用场景 - 表单提交
const FormSubmitExample = () => {
  const handleSubmit = async () => {
    // 模拟表单验证
    await new Promise(resolve => setTimeout(resolve, 500));
    // 模拟 API 请求
    await new Promise((resolve) => {
      setTimeout(() => {
        message.success('表单提交成功!');
        resolve();
      }, 1500);
    });
  };

  return (
    <Card title="表单提交场景" style={{ width: 400 }}>
      <Space direction="vertical" style={{ width: '100%' }}>
        <Typography.Text type="secondary">
          点击提交按钮,自动防止重复提交
        </Typography.Text>
        <Space>
          <LoadingButton type="primary" onClick={handleSubmit}>
            提交表单
          </LoadingButton>
          <Button onClick={() => message.info('已取消')}>取消</Button>
        </Space>
      </Space>
    </Card>
  );
};

const BaseExample = () => {
  return (
    <Space direction="vertical" size="large">
      <Typography.Title level={3}>LoadingButton 加载按钮</Typography.Title>
      <Typography.Paragraph>
        LoadingButton 封装了加载状态,简化异步操作的处理。点击按钮时自动显示加载状态,
        避免重复提交,同时提供 useLoading Hook 供其他组件使用。
      </Typography.Paragraph>

      <Flex vertical gap={32}>
        <div>
          <Typography.Title level={4}>基础用法</Typography.Title>
          <BasicExample />
        </div>

        <div>
          <Typography.Title level={4}>自定义加载文案</Typography.Title>
          <CustomTextExample />
        </div>

        <div>
          <Typography.Title level={4}>错误处理</Typography.Title>
          <ErrorExample />
        </div>

        <div>
          <Typography.Title level={4}>手动控制加载状态</Typography.Title>
          <ManualExample />
        </div>

        <div>
          <Typography.Title level={4}>useLoading Hook</Typography.Title>
          <UseLoadingExample />
        </div>

        <div>
          <Typography.Title level={4}>不同按钮类型</Typography.Title>
          <ButtonTypesExample />
        </div>

        <div>
          <Typography.Title level={4}>图标按钮</Typography.Title>
          <IconExample />
        </div>

        <div>
          <Typography.Title level={4}>实际应用场景</Typography.Title>
          <FormSubmitExample />
        </div>
      </Flex>
    </Space>
  );
};

render(<BaseExample />);
  • ConfirmButton 确认按钮
  • ConfirmButton 带有确认功能,支持弹窗确认和模态框确认两种模式。适用于删除、提交等重要操作。
  • _ButtonGroup(@kne/current-lib_button-group)[import * as _ButtonGroup from "@kne/button-group"],antd(antd)
const { ConfirmButton, ConfirmLink, ConfirmText } = _ButtonGroup;
const { Flex, Space, Typography, message, Card, List, Tag, Button } = antd;
const { useState } = React;

// 基础用法 - Popconfirm 模式
const BasicExample = () => {
  const handleDelete = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        message.success('删除成功');
        resolve();
      }, 500);
    });
  };

  return (
    <Space direction="vertical">
      <Typography.Text type="secondary">
        Popconfirm 模式(气泡确认框),适用于快速确认
      </Typography.Text>
      <Space>
        <ConfirmButton message="确定要删除吗?" onClick={handleDelete}>
          删除
        </ConfirmButton>
        <ConfirmButton message="确定要提交吗?" onClick={handleDelete}>
          提交
        </ConfirmButton>
      </Space>
    </Space>
  );
};

// Modal 模式
const ModalExample = () => {
  const handleSubmit = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        message.success('提交成功');
        resolve();
      }, 500);
    });
  };

  return (
    <Space direction="vertical">
      <Typography.Text type="secondary">
        Modal 模式(模态框),适用于重要操作或长内容提示
      </Typography.Text>
      <Space>
        <ConfirmButton
          isModal
          message="此操作将永久删除该数据,删除后无法恢复。确定要继续吗?"
          onClick={handleSubmit}
        >
          删除数据
        </ConfirmButton>
        <ConfirmButton
          isModal
          title="提交确认"
          message="提交后数据将进入审核流程,确认要提交吗?"
          onClick={handleSubmit}
        >
          提交审核
        </ConfirmButton>
      </Space>
    </Space>
  );
};

// 危险操作样式
const DangerExample = () => {
  const handleDelete = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        message.success('已删除');
        resolve();
      }, 500);
    });
  };

  return (
    <Space direction="vertical">
      <Typography.Text type="secondary">
        使用 isDelete 标识危险操作(红色按钮)
      </Typography.Text>
      <Space>
        <ConfirmButton
          danger
          message="确定删除吗?"
          onClick={handleDelete}
        >
          普通按钮
        </ConfirmButton>
        <ConfirmButton
          isDelete
          message="确定删除吗?"
          onClick={handleDelete}
        >
          删除按钮
        </ConfirmButton>
        <ConfirmButton
          isDelete
          isModal
          message="此操作无法撤销,确定要继续吗?"
          onClick={handleDelete}
        >
          删除(Modal)
        </ConfirmButton>
      </Space>
    </Space>
  );
};

// 自定义确认文案
const CustomTextExample = () => {
  const handleAction = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        message.success('操作完成');
        resolve();
      }, 500);
    });
  };

  return (
    <Space direction="vertical">
      <Typography.Text type="secondary">自定义确认和取消按钮文案</Typography.Text>
      <Space>
        <ConfirmButton
          message="确定要执行此操作吗?"
          okText="确认执行"
          cancelText="暂不执行"
          onClick={handleAction}
        >
          自定义文案
        </ConfirmButton>
        <ConfirmButton
          isModal
          title="操作确认"
          message="请确认是否继续执行此操作"
          okText="是,继续"
          cancelText="否,取消"
          onClick={handleAction}
        >
          Modal 自定义文案
        </ConfirmButton>
      </Space>
    </Space>
  );
};

// ConfirmLink 和 ConfirmText
const LinkAndTextExample = () => {
  const handleAction = () => {
    message.success('操作成功');
    return Promise.resolve();
  };

  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      <Typography.Text type="secondary">
        ConfirmLink 和 ConfirmText 变体,适用于表格行操作等场景
      </Typography.Text>
      <List
        bordered
        style={{ width: 400 }}
        dataSource={[
          { id: 1, name: '数据项 A', status: '已提交' },
          { id: 2, name: '数据项 B', status: '草稿' },
          { id: 3, name: '数据项 C', status: '已审核' }
        ]}
        renderItem={(item) => (
          <List.Item
            actions={[
              <ConfirmLink key="edit" message="确定编辑吗?" onClick={handleAction}>
                编辑
              </ConfirmLink>,
              <ConfirmLink key="delete" isDelete message="确定删除吗?" onClick={handleAction}>
                删除
              </ConfirmLink>
            ]}
          >
            <List.Item.Meta
              title={item.name}
              description={<Tag color={item.status === '已提交' ? 'blue' : 'default'}>{item.status}</Tag>}
            />
          </List.Item>
        )}
      />
      <Space>
        <ConfirmText onClick={handleAction}>纯文本确认</ConfirmText>
        <Typography.Text type="secondary">|</Typography.Text>
        <ConfirmText isDelete onClick={handleAction}>删除</ConfirmText>
      </Space>
    </Space>
  );
};

// 禁用状态
const DisabledExample = () => {
  const [disabled, setDisabled] = useState(true);
  const handleDelete = () => {
    message.success('已删除');
    return Promise.resolve();
  };

  return (
    <Space direction="vertical">
      <Typography.Text type="secondary">禁用状态下不会触发确认</Typography.Text>
      <Space>
        <Button onClick={() => setDisabled(!disabled)}>
          {disabled ? '启用' : '禁用'}
        </Button>
      </Space>
      <Space>
        <ConfirmButton disabled={disabled} message="确定删除吗?" onClick={handleDelete}>
          删除
        </ConfirmButton>
        <ConfirmLink disabled={disabled} message="确定删除吗?" onClick={handleDelete}>
          删除
        </ConfirmLink>
      </Space>
    </Space>
  );
};

// 实际应用场景 - 表格操作
const TableActionExample = () => {
  const handleEdit = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        message.success('进入编辑模式');
        resolve();
      }, 300);
    });
  };

  const handleDelete = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        message.success('已删除');
        resolve();
      }, 300);
    });
  };

  const handleAudit = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        message.success('审核通过');
        resolve();
      }, 300);
    });
  };

  return (
    <Card title="实际应用:表格操作栏" style={{ width: 500 }}>
      <Space direction="vertical" style={{ width: '100%' }}>
        <Typography.Text type="secondary">
          模拟表格中的操作按钮,包含不同的确认方式
        </Typography.Text>
        <List
          bordered
          dataSource={[
            { id: 1, name: '产品 A', price: '¥99.00' },
            { id: 2, name: '产品 B', price: '¥199.00' }
          ]}
          renderItem={(item) => (
            <List.Item
              actions={[
                <ConfirmButton key="edit" type="link" message={&#96;确定编辑 ${item.name} 吗?&#96;} onClick={handleEdit}>
                  编辑
                </ConfirmButton>,
                <ConfirmButton key="audit" type="link" message={&#96;确定通过 ${item.name} 的审核吗?&#96;} onClick={handleAudit}>
                  审核
                </ConfirmButton>,
                <ConfirmButton
                  key="delete"
                  type="link"
                  danger
                  message={&#96;确定删除 ${item.name} 吗?此操作无法撤销。&#96;}
                  onClick={handleDelete}
                >
                  删除
                </ConfirmButton>
              ]}
            >
              <List.Item.Meta
                title={item.name}
                description={item.price}
              />
            </List.Item>
          )}
        />
      </Space>
    </Card>
  );
};

// 批量操作
const BatchExample = () => {
  const [selectedCount, setSelectedCount] = useState(0);
  const handleBatchDelete = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        message.success(&#96;已删除 ${selectedCount} 条数据&#96;);
        setSelectedCount(0);
        resolve();
      }, 500);
    });
  };

  return (
    <Card title="批量操作场景" style={{ width: 450 }}>
      <Space direction="vertical" style={{ width: '100%' }}>
        <Space>
          <Typography.Text type="secondary">已选择:</Typography.Text>
          <Typography.Text strong>{selectedCount} 项</Typography.Text>
          <Button size="small" onClick={() => setSelectedCount(Math.floor(Math.random() * 10))}>
            随机选择
          </Button>
        </Space>
        <Space>
          <ConfirmButton
            type="primary"
            disabled={selectedCount === 0}
            isModal
            title="批量删除确认"
            message={&#96;确定要删除选中的 ${selectedCount} 条数据吗?此操作无法撤销。&#96;}
            onClick={handleBatchDelete}
          >
            批量删除
          </ConfirmButton>
          <ConfirmButton
            disabled={selectedCount === 0}
            message={&#96;确定导出选中的 ${selectedCount} 条数据吗?&#96;}
            onClick={() => {
              message.success(&#96;正在导出 ${selectedCount} 条数据&#96;);
              return Promise.resolve();
            }}
          >
            导出
          </ConfirmButton>
        </Space>
      </Space>
    </Card>
  );
};

const BaseExample = () => {
  return (
    <Space direction="vertical" size="large">
      <Typography.Title level={3}>ConfirmButton 确认按钮</Typography.Title>
      <Typography.Paragraph>
        ConfirmButton 提供确认功能,支持 Popconfirm(气泡确认框)和 Modal(模态框)两种模式。
        还提供 ConfirmLink 和 ConfirmText 变体,以及 withConfirm 高阶组件。
      </Typography.Paragraph>

      <Flex vertical gap={32}>
        <div>
          <Typography.Title level={4}>Popconfirm 模式</Typography.Title>
          <BasicExample />
        </div>

        <div>
          <Typography.Title level={4}>Modal 模式</Typography.Title>
          <ModalExample />
        </div>

        <div>
          <Typography.Title level={4}>危险操作样式</Typography.Title>
          <DangerExample />
        </div>

        <div>
          <Typography.Title level={4}>自定义文案</Typography.Title>
          <CustomTextExample />
        </div>

        <div>
          <Typography.Title level={4}>Link 和 Text 变体</Typography.Title>
          <LinkAndTextExample />
        </div>

        <div>
          <Typography.Title level={4}>禁用状态</Typography.Title>
          <DisabledExample />
        </div>

        <div>
          <Typography.Title level={4}>实际应用场景</Typography.Title>
          <TableActionExample />
        </div>

        <div>
          <Typography.Title level={4}>批量操作</Typography.Title>
          <BatchExample />
        </div>
      </Flex>
    </Space>
  );
};

render(<BaseExample />);
  • FetchButton 请求按钮
  • FetchButton 集成了数据请求功能,自动管理加载状态和请求流程。适用于按钮触发 API 请求的场景。
  • _ButtonGroup(@kne/current-lib_button-group)[import * as _ButtonGroup from "@kne/button-group"],(@kne/current-lib_button-group/dist/index.css),antd(antd)
const { FetchButton } = _ButtonGroup;
const { Space, Typography, message, Card, Alert, Form, Input, Select, Button, Flex } = antd;
const { useState } = React;

// 基础用法
const BasicExample = () => {
  const handleSuccess = ({ data }) => {
    message.success(&#96;获取数据成功: ${data}&#96;);
  };

  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      <Typography.Text type="secondary">
        点击按钮触发 API 请求,自动管理加载状态
      </Typography.Text>
      <FetchButton
        type="primary"
        api={{
          loader: async () => {
            return new Promise((resolve) => {
              setTimeout(() => {
                resolve({ data: '用户信息数据' });
              }, 1500);
            });
          }
        }}
        onClick={handleSuccess}
      >
        获取用户信息
      </FetchButton>
    </Space>
  );
};

// 带参数请求
const WithParamsExample = () => {
  const [userId, setUserId] = useState('1');

  const handleSuccess = ({ data }) => {
    message.success(&#96;获取成功: ${JSON.stringify(data)}&#96;);
  };

  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      <Typography.Text type="secondary">传递参数到 API 请求</Typography.Text>
      <Space>
        <Select
          value={userId}
          onChange={setUserId}
          style={{ width: 120 }}
          options={[
            { value: '1', label: '用户1' },
            { value: '2', label: '用户2' },
            { value: '3', label: '用户3' }
          ]}
        />
        <FetchButton
          params={{ userId }}
          api={{
            loader: async ({ params }) => {
              return new Promise((resolve) => {
                setTimeout(() => {
                  resolve({ data: { userId: params.userId, name: &#96;用户${params.userId}&#96;, role: '管理员' } });
                }, 1000);
              });
            }
          }}
          onClick={handleSuccess}
        >
          获取用户详情
        </FetchButton>
      </Space>
    </Space>
  );
};

// 成功和失败回调
const CallbackExample = () => {
  const [status, setStatus] = useState('');
  const [shouldFail, setShouldFail] = useState(false);

  const handleSuccess = ({ data }) => {
    setStatus('success');
    message.success('数据加载成功');
    console.log('成功数据:', data);
  };

  const handleError = (error) => {
    setStatus('error');
    message.error('数据加载失败');
    console.error('错误信息:', error);
  };

  return (
    <Card title="成功与失败回调" style={{ width: 450 }}>
      <Space direction="vertical" style={{ width: '100%' }}>
        <Space>
          <Button onClick={() => setShouldFail(!shouldFail)} size="small">
            {shouldFail ? '切换为成功' : '切换为失败'}
          </Button>
        </Space>
        <FetchButton
          type="primary"
          api={{
            loader: async () => {
              return new Promise((resolve, reject) => {
                setTimeout(() => {
                  if (shouldFail) {
                    reject(new Error('模拟的请求失败'));
                  } else {
                    resolve({ data: { message: '请求成功', timestamp: Date.now() } });
                  }
                }, 1000);
              });
            }
          }}
          onSuccess={handleSuccess}
          onError={handleError}
        >
          {shouldFail ? '失败请求' : '成功请求'}
        </FetchButton>
        {status === 'success' && <Alert message="上次请求:成功" type="success" />}
        {status === 'error' && <Alert message="上次请求:失败" type="error" />}
      </Space>
    </Card>
  );
};

// 导出文件场景
const ExportExample = () => {
  const handleExport = ({ data }) => {
    message.success(&#96;导出成功: ${data.url}&#96;);
  };

  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      <Typography.Text type="secondary">模拟文件导出场景</Typography.Text>
      <Space>
        <FetchButton
          api={{
            loader: async () => {
              return new Promise((resolve) => {
                setTimeout(() => {
                  resolve({ data: { url: '/download/report.xlsx', size: '2.5MB' } });
                }, 2000);
              });
            }
          }}
          onClick={handleExport}
        >
          导出报表
        </FetchButton>
        <FetchButton
          type="primary"
          api={{
            loader: async () => {
              return new Promise((resolve) => {
                setTimeout(() => {
                  resolve({ data: { url: '/download/data.csv', size: '1.2MB' } });
                }, 1500);
              });
            }
          }}
          onClick={handleExport}
        >
          导出 CSV
        </FetchButton>
      </Space>
    </Space>
  );
};

// 表单提交场景
const FormSubmitExample = () => {
  const [form] = Form.useForm();
  const [loading, setLoading] = useState(false);
  const [submittedData, setSubmittedData] = useState(null);

  const handleSubmit = ({ data }) => {
    setSubmittedData(data);
    message.success('表单提交成功');
  };

  const onFinish = async () => {
    try {
      const values = await form.validateFields();
      setLoading(true);
      // 使用 FetchButton 内部处理,这里只是演示
      await new Promise(resolve => setTimeout(resolve, 1000));
      setLoading(false);
      message.success('验证通过');
    } catch (error) {
      message.error('请检查表单内容');
    }
  };

  return (
    <Card title="表单提交场景" style={{ width: 450 }}>
      <Space direction="vertical" style={{ width: '100%' }}>
        <Form
          form={form}
          layout="vertical"
        >
          <Form.Item
            name="username"
            label="用户名"
            rules={[{ required: true, message: '请输入用户名' }]}
          >
            <Input placeholder="请输入用户名" />
          </Form.Item>
          <Form.Item
            name="email"
            label="邮箱"
            rules={[{ required: true, message: '请输入邮箱' }]}
          >
            <Input placeholder="请输入邮箱" />
          </Form.Item>
        </Form>
        <Space>
          <FetchButton
            type="primary"
            api={{
              loader: async ({ params }) => {
                return new Promise((resolve) => {
                  setTimeout(() => {
                    resolve({ data: { id: 123, ...params, createTime: new Date().toISOString() } });
                  }, 1500);
                });
              }
            }}
            beforeFetch={() => {
              const values = form.getFieldsValue();
              if (!values.username || !values.email) {
                message.error('请填写完整信息');
                return false;
              }
              return true;
            }}
            onClick={handleSubmit}
          >
            提交表单
          </FetchButton>
          <Button onClick={() => form.resetFields()}>重置</Button>
        </Space>
        {submittedData && (
          <Alert
            message="提交成功"
            description={JSON.stringify(submittedData, null, 2)}
            type="success"
          />
        )}
      </Space>
    </Card>
  );
};

// 刷新数据场景
const RefreshExample = () => {
  const [data, setData] = useState(null);
  const [lastRefresh, setLastRefresh] = useState(null);

  const handleRefresh = ({ data: newData }) => {
    console.log(newData);
    setData(newData);
    setLastRefresh(new Date().toLocaleTimeString());
    message.success('数据已更新');
  };

  return (
    <Card title="刷新数据场景" style={{ width: 450 }}>
      <Space direction="vertical" style={{ width: '100%' }}>
        <Space>
          <Typography.Text type="secondary">上次刷新:</Typography.Text>
          <Typography.Text>{lastRefresh || '从未刷新'}</Typography.Text>
        </Space>
        <FetchButton
          api={{
            loader: async () => {
              return new Promise((resolve) => {
                setTimeout(() => {
                  resolve({
                    users: [
                      { id: 1, name: '用户A', status: '在线' },
                      { id: 2, name: '用户B', status: '离线' },
                      { id: 3, name: '用户C', status: '在线' }
                    ],
                    total: 3
                  });
                }, 1000);
              });
            }
          }}
          onClick={handleRefresh}
        >
          刷新数据
        </FetchButton>
        {data && (
          <Alert
            message={&#96;当前数据:${data.users?.length} 个用户在线&#96;}
            type="info"
          />
        )}
      </Space>
    </Card>
  );
};

// beforeFetch 拦截
const BeforeFetchExample = () => {
  const [allowed, setAllowed] = useState(true);

  const handleFetch = ({ data }) => {
    message.success('请求通过');
  };

  const beforeFetch = () => {
    if (!allowed) {
      message.warning('请求被 beforeFetch 拦截');
      return false;
    }
    return true;
  };

  return (
    <Space direction="vertical">
      <Typography.Text type="secondary">beforeFetch 可以拦截请求</Typography.Text>
      <Space>
        <Button onClick={() => setAllowed(!allowed)} size="small">
          {allowed ? '拦截请求' : '允许请求'}
        </Button>
      </Space>
      <FetchButton
        api={{
          loader: async () => {
            return new Promise((resolve) => {
              setTimeout(() => {
                resolve({ data: '请求成功' });
              }, 800);
            });
          }
        }}
        beforeFetch={beforeFetch}
        onClick={handleFetch}
      >
        {allowed ? '发送请求' : '请求已拦截'}
      </FetchButton>
    </Space>
  );
};

const BaseExample = () => {
  return (
    <Space direction="vertical" size="large">
      <Typography.Title level={3}>FetchButton 请求按钮</Typography.Title>
      <Typography.Paragraph>
        FetchButton 集成了数据请求功能,基于 @kne/react-fetch 库实现。
        自动管理加载状态,支持成功/失败回调、参数传递、请求拦截等功能。
      </Typography.Paragraph>

      <Flex vertical gap={32}>
        <div>
          <Typography.Title level={4}>基础用法</Typography.Title>
          <BasicExample />
        </div>

        <div>
          <Typography.Title level={4}>带参数请求</Typography.Title>
          <WithParamsExample />
        </div>

        <div>
          <Typography.Title level={4}>成功与失败回调</Typography.Title>
          <CallbackExample />
        </div>

        <div>
          <Typography.Title level={4}>文件导出场景</Typography.Title>
          <ExportExample />
        </div>

        <div>
          <Typography.Title level={4}>表单提交场景</Typography.Title>
          <FormSubmitExample />
        </div>

        <div>
          <Typography.Title level={4}>刷新数据场景</Typography.Title>
          <RefreshExample />
        </div>

        <div>
          <Typography.Title level={4}>请求拦截</Typography.Title>
          <BeforeFetchExample />
        </div>
      </Flex>
    </Space>
  );
};

render(<BaseExample />);
  • ButtonFooter 底部按钮区
  • ButtonFooter 是页面底部按钮区域组件,在小屏幕下自动将内容渲染到 body,方便表单页面的操作按钮布局。
  • _ButtonGroup(@kne/current-lib_button-group)[import * as _ButtonGroup from "@kne/button-group"],(@kne/current-lib_button-group/dist/index.css),antd(antd)
const { ButtonFooter } = _ButtonGroup;
const { Flex, Button, Space, Typography, Card, Form, Input } = antd;
const { useState } = React;

// 基础用法
const BasicExample = () => {
  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      <Typography.Text type="secondary">
        ButtonFooter 固定在页面底部,在小屏幕(≤768px)下自动渲染到 body
      </Typography.Text>
      <Card
        title="页面内容区域"
        style={{ width: 400, minHeight: 200 }}
      >
        <Typography.Paragraph>
          这是页面的主要内容区域。ButtonFooter 会自动计算高度并设置 CSS 变量,
          方便页面布局调整。
        </Typography.Paragraph>
        <Typography.Paragraph>
          在移动端,按钮会自动固定在屏幕底部,确保操作按钮始终可见。
        </Typography.Paragraph>
      </Card>
      <ButtonFooter>
        <Flex justify="flex-end" gap={8} style={{ padding: '16px 24px', background: '#fff', borderTop: '1px solid #f0f0f0' }}>
          <Button>取消</Button>
          <Button type="primary">保存</Button>
        </Flex>
      </ButtonFooter>
    </Space>
  );
};

// 表单提交场景
const FormExample = () => {
  const [form] = Form.useForm();
  const [loading, setLoading] = useState(false);

  const handleSave = () => {
    setLoading(true);
    setTimeout(() => {
      setLoading(false);
      message.success('保存成功');
    }, 1000);
  };

  const handleSubmit = () => {
    setLoading(true);
    setTimeout(() => {
      setLoading(false);
      message.success('提交成功');
    }, 1000);
  };

  return (
    <Card title="表单底部操作" style={{ width: 500 }}>
      <Space direction="vertical" style={{ width: '100%' }}>
        <Typography.Text type="secondary">
          适用于表单页面的底部操作按钮
        </Typography.Text>
        <Form form={form} layout="vertical">
          <Form.Item name="name" label="名称">
            <Input placeholder="请输入名称" />
          </Form.Item>
          <Form.Item name="desc" label="描述">
            <Input.TextArea placeholder="请输入描述" rows={4} />
          </Form.Item>
        </Form>
        <ButtonFooter>
          <Flex justify="flex-end" gap={8} style={{ padding: '16px 0', borderTop: '1px solid #f0f0f0' }}>
            <Button onClick={() => form.resetFields()}>重置</Button>
            <Button onClick={handleSave}>保存草稿</Button>
            <Button type="primary" loading={loading} onClick={handleSubmit}>
              提交
            </Button>
          </Flex>
        </ButtonFooter>
      </Space>
    </Card>
  );
};

// 居中对齐
const CenterExample = () => {
  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      <Typography.Text type="secondary">按钮居中对齐</Typography.Text>
      <Card title="对话框" style={{ width: 400 }}>
        <Typography.Paragraph>
          这是对话框的内容区域,底部按钮居中对齐。
        </Typography.Paragraph>
      </Card>
      <ButtonFooter>
        <Flex justify="center" gap={8} style={{ padding: '16px 24px', background: '#fff', borderTop: '1px solid #f0f0f0' }}>
          <Button>关闭</Button>
          <Button type="primary">确认</Button>
        </Flex>
      </ButtonFooter>
    </Space>
  );
};

// 多按钮布局
const MultipleButtonsExample = () => {
  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      <Typography.Text type="secondary">多个操作按钮</Typography.Text>
      <Card title="详情页面" style={{ width: 450 }}>
        <Typography.Paragraph>
          页面详情内容区域...
        </Typography.Paragraph>
        <Typography.Paragraph>
          支持多个按钮布局,包括主要操作、次要操作等。
        </Typography.Paragraph>
      </Card>
      <ButtonFooter>
        <Flex justify="space-between" align="middle" style={{ padding: '12px 24px', background: '#fff', borderTop: '1px solid #f0f0f0' }}>
          <Space>
            <Button type="text" danger>删除</Button>
            <Button type="text">导出</Button>
          </Space>
          <Space>
            <Button>编辑</Button>
            <Button type="primary">提交审核</Button>
          </Space>
        </Flex>
      </ButtonFooter>
    </Space>
  );
};

// 紧凑样式
const CompactExample = () => {
  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      <Typography.Text type="secondary">紧凑样式</Typography.Text>
      <Card title="设置页面" style={{ width: 400 }}>
        <Typography.Paragraph>
          系统设置内容区域...
        </Typography.Paragraph>
      </Card>
      <ButtonFooter>
        <Flex justify="flex-end" gap={8} style={{ padding: '8px 0', borderTop: '1px solid #f0f0f0' }}>
          <Button size="small">取消</Button>
          <Button size="small" type="primary">保存设置</Button>
        </Flex>
      </ButtonFooter>
    </Space>
  );
};

// 禁用状态
const DisabledExample = () => {
  const [disabled, setDisabled] = useState(true);

  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      <Typography.Text type="secondary">按钮禁用状态</Typography.Text>
      <Space>
        <Button onClick={() => setDisabled(!disabled)} size="small">
          {disabled ? '启用按钮' : '禁用按钮'}
        </Button>
      </Space>
      <Card title="详情页" style={{ width: 400 }}>
        <Typography.Paragraph>
          内容区域...
        </Typography.Paragraph>
      </Card>
      <ButtonFooter>
        <Flex justify="flex-end" gap={8} style={{ padding: '16px 0', borderTop: '1px solid #f0f0f0' }}>
          <Button disabled={disabled}>编辑</Button>
          <Button type="primary" disabled={disabled}>
            提交
          </Button>
        </Flex>
      </ButtonFooter>
    </Space>
  );
};

// 步骤条场景
const StepsExample = () => {
  const [currentStep, setCurrentStep] = useState(0);
  const totalSteps = 3;

  const nextStep = () => {
    if (currentStep < totalSteps - 1) {
      setCurrentStep(currentStep + 1);
    }
  };

  const prevStep = () => {
    if (currentStep > 0) {
      setCurrentStep(currentStep - 1);
    }
  };

  const stepContent = [
    '第一步:填写基本信息',
    '第二步:上传相关文件',
    '第三步:确认提交信息'
  ];

  return (
    <Card title="步骤操作" style={{ width: 450 }}>
      <Space direction="vertical" style={{ width: '100%' }}>
        <Typography.Text type="secondary">
          当前步骤:{currentStep + 1} / {totalSteps}
        </Typography.Text>
        <div style={{ padding: '24px', background: '#f5f5f5', borderRadius: '8px', minHeight: '100px' }}>
          <Typography.Text>{stepContent[currentStep]}</Typography.Text>
        </div>
        <ButtonFooter>
          <Flex justify="space-between" style={{ padding: '16px 0', borderTop: '1px solid #f0f0f0' }}>
            <Button disabled={currentStep === 0} onClick={prevStep}>
              上一步
            </Button>
            <Button
              type="primary"
              onClick={currentStep === totalSteps - 1 ? () => message.success('提交成功') : nextStep}
            >
              {currentStep === totalSteps - 1 ? '提交' : '下一步'}
            </Button>
          </Flex>
        </ButtonFooter>
      </Space>
    </Card>
  );
};

// 实际应用场景 - 完整页面
const FullPageExample = () => {
  const [form] = Form.useForm();

  const handleSubmit = () => {
    message.success('提交成功');
  };

  const handleSave = () => {
    message.success('已保存草稿');
  };

  return (
    <Card title="完整页面示例" style={{ width: 500 }}>
      <Space direction="vertical" style={{ width: '100%' }}>
        <Typography.Text type="secondary">
          模拟一个完整的表单页面,包含标题、内容区和底部操作按钮
        </Typography.Text>
        <div style={{ minHeight: '200px', padding: '20px', background: '#fafafa', borderRadius: '8px' }}>
          <Typography.Title level={5}>用户信息编辑</Typography.Title>
          <Form form={form} layout="vertical">
            <Form.Item name="username" label="用户名">
              <Input placeholder="请输入用户名" />
            </Form.Item>
            <Form.Item name="email" label="邮箱">
              <Input placeholder="请输入邮箱" />
            </Form.Item>
          </Form>
        </div>
        <ButtonFooter>
          <Flex justify="flex-end" gap={12} style={{ padding: '16px 24px', background: '#fff', borderTop: '1px solid #f0f0f0' }}>
            <Button onClick={() => form.resetFields()}>重置</Button>
            <Button onClick={handleSave}>保存草稿</Button>
            <Button type="primary" onClick={handleSubmit}>提交</Button>
          </Flex>
        </ButtonFooter>
      </Space>
    </Card>
  );
};

const BaseExample = () => {
  return (
    <Space direction="vertical" size="large">
      <Typography.Title level={3}>ButtonFooter 底部按钮区</Typography.Title>
      <Typography.Paragraph>
        ButtonFooter 是页面底部按钮区域组件,可以自动计算高度并设置 CSS 变量。
        在小屏幕(≤768px)下,会将内容渲染到 body,确保按钮始终可见。
        适用于表单页面、详情页面、对话框等场景。
      </Typography.Paragraph>

      <Flex vertical gap={32}>
        <div>
          <Typography.Title level={4}>基础用法</Typography.Title>
          <BasicExample />
        </div>

        <div>
          <Typography.Title level={4}>表单提交场景</Typography.Title>
          <FormExample />
        </div>

        <div>
          <Typography.Title level={4}>居中对齐</Typography.Title>
          <CenterExample />
        </div>

        <div>
          <Typography.Title level={4}>多按钮布局</Typography.Title>
          <MultipleButtonsExample />
        </div>

        <div>
          <Typography.Title level={4}>紧凑样式</Typography.Title>
          <CompactExample />
        </div>

        <div>
          <Typography.Title level={4}>禁用状态</Typography.Title>
          <DisabledExample />
        </div>

        <div>
          <Typography.Title level={4}>步骤操作</Typography.Title>
          <StepsExample />
        </div>

        <div>
          <Typography.Title level={4}>完整页面示例</Typography.Title>
          <FullPageExample />
        </div>
      </Flex>
    </Space>
  );
};

render(<BaseExample />);

API

自适应按钮组组件,能够根据容器宽度自动调整显示的按钮数量,并将多余的按钮放入下拉菜单中。

属性

| 属性名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | list | Array<object | function> | [] | 按钮列表,可以是配置对象或渲染函数 | | compact | boolean | false | 是否使用紧凑模式(Space.Compact) | | showLength | number | - | 指定显示的按钮数量,不指定则自动计算 | | more | ReactNode | - | 自定义"更多"按钮 | | moreType | 'default' | 'link' | 'default' | 更多按钮类型 | | getPopupContainer | function | - | 下拉菜单渲染父节点 | | trigger | string | - | 下拉菜单触发方式 | | itemClassName | string | - | 按钮项的自定义类名 | | ...SpaceProps | - | - | Space 组件的其他属性(size、split、align、style等) |

list 配置项

当 list 项为对象时,支持以下属性:

| 属性名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | children | ReactNode | - | 按钮内容 | | type | string | - | 按钮类型(primary、default、dashed、link、text) | | disabled | boolean | false | 是否禁用 | | hidden | boolean | false | 是否隐藏 | | confirm | boolean | false | 是否需要确认 | | message | string | ReactNode | - | 确认提示内容(设置后会自动使用 ConfirmButton) | | isDelete | boolean | false | 是否为删除操作(红色按钮) | | isModal | boolean | false | 是否使用模态框确认(在下拉菜单中自动启用) | | buttonComponent | ReactComponent | - | 自定义按钮组件 | | tooltipProps | object | - | Tooltip 组件属性(禁用时显示提示) | | ...ButtonProps | - | - | Button 组件的其他属性 |

当 list 项为函数时,函数签名为:

(props: { key: number; className: string }, context: { isDropdown: boolean }) => ReactNode

LoadingButton

封装了加载状态的按钮组件,简化异步操作的处理。

属性

| 属性名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | onClick | function | Promise | - | 点击按钮时的回调函数,可以返回 Promise | | loading | boolean | false | 是否显示加载状态 | | disabled | boolean | false | 是否禁用按钮 | | children | ReactNode | function | - | 按钮内容,可以是函数接收 loading 状态 | | ...ButtonProps | - | - | Button 组件的其他属性 |

useLoading Hook

用于管理异步操作加载状态的 Hook。

参数

| 参数名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | callback | function | - | 异步回调函数 |

返回值

| 名称 | 类型 | 说明 | |------|------|------| | isLoading | boolean | 当前加载状态 | | setIsLoading | function | 设置加载状态的函数 | | callback | function | 包装后的回调函数 |


ConfirmButton

带有确认功能的按钮组件,支持弹窗确认和模态框确认两种模式。

属性

| 属性名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | children | ReactNode | - | 按钮内容 | | onClick | function | Promise | - | 确认后的回调函数,可以返回 Promise | | title | string | ReactNode | - | 确认框标题 | | message | string | ReactNode | - | 确认框内容 | | okText | string | - | 确认按钮文字(默认根据 isDelete 动态显示) | | cancelText | string | - | 取消按钮文字 | | isModal | boolean | false | 是否使用模态框确认(默认为 Popconfirm) | | isDelete | boolean | true | 是否为删除操作(红色按钮、确认按钮) | | showCancel | boolean | true | 是否显示取消按钮 | | placement | string | - | Popconfirm 的位置 | | getContainer | function | - | 确认框渲染容器 | | renderModal | function | - | 自定义 Modal 渲染函数 | | onCancel | function | - | 取消按钮的回调 | | ...ButtonProps | - | - | Button 组件的其他属性 |

ConfirmLink

ConfirmButton 的链接样式变体。

ConfirmText

ConfirmButton 的纯文本样式变体。

withConfirm

高阶组件,用于为任意组件添加确认功能。

⚠️ 该 API 已标记为废弃,后续版本可能删除,建议不要使用。


FetchButton

集成了数据请求功能的按钮组件,基于 @kne/react-fetch 库实现。

属性

| 属性名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | api | object | function | - | 请求 API 配置 | | params | object | - | 请求参数 | | onSuccess | function | - | 请求成功回调,参数为 { data } | | onError | function | - | 请求失败回调 | | beforeFetch | function | - | 请求前处理函数,返回 false 可阻止请求 | | afterFetch | function | - | 请求后处理函数 | | fetchOptions | object | - | 传递给 fetch 函数的选项 | | onClick | function | - | 请求成功后的回调(与 onSuccess 相同) | | ...LoadingButtonProps | - | - | LoadingButton 组件的其他属性 |

api 配置

api 可以是对象或函数:

// 对象形式
api: {
  loader: async ({ params }) => {
    return { data: 'response data' };
  }
}

// 函数形式
api: async ({ params }) => {
  return { data: 'response data' };
}

ButtonFooter

页面底部按钮区域组件,可以自动计算高度并设置 CSS 变量。

属性

| 属性名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | children | ReactNode | - | 按钮区域内容 | | className | string | - | 容器的自定义类名 | | innerClassName | string | - | 内部容器的自定义类名 | | target | HTMLElement | document.body | 移动端渲染的目标容器 |

特性

  • 在小屏幕(≤768px)下,会将内容使用 Portal 渲染到 body
  • 自动计算高度并设置 CSS 变量
  • 适用于固定在页面底部的操作按钮区域