组件样式
组件代码
import { Button, Input, message, Modal, Space, Spin, Upload } from 'antd'
import { useState } from 'react'
import { UploadOutlined, LoadingOutlined } from '@ant-design/icons'
// 定义批量导入模态框的属性接口
interface BatchModalProps {
visible: boolean // 模态框是否可见
handleCancel: () => void // 关闭模态框的回调
handleOk: (data: LooseObject) => void // 确认导入成功的回调
title: string // 模态框标题
templateUrl: string // 模板下载链接
maxSize?: number // 最大支持的条目数,默认为500
onConfirmImport: (file: File) => Promise<LooseObject> // 确认导入的逻辑
}
/**
* 批量导入模态框组件
* @param props - 组件属性
* @returns React 元素
*/
const BatchModal = (props: BatchModalProps) => {
const {
visible,
handleCancel,
handleOk,
title,
templateUrl,
onConfirmImport,
maxSize = 500
} = props
// 定义模态框内部状态
const [formLoading, setFormLoading] = useState(false) // 表单加载状态
const [uploadFile, setUploadFile] = useState<File | null>(null) // 上传的文件
const [errorUrl, setErrorUrl] = useState('') // 错误数据下载链接
const [loadStatus, setLoadStatus] = useState(1) // 加载状态:1-确认导入,2-正在导入
const [loadError, setLoadError] = useState<number | string>('0') // 导入结果:0-全部成功,1-全部失败,2-部分成功
/**
* 确认导入文件
*/
const onUploadFile = async () => {
try {
setLoadStatus(2) // 设置为正在导入状态
const data = await onConfirmImport(uploadFile!) // 调用外界传入的确认导入逻辑
if (data) {
// 根据导入结果显示不同的消息
if (data?.successUrl && !data?.errorUrl) {
message.success('上传成功')
handleCancel() // 关闭模态框
}
if (!data?.successUrl && data?.errorUrl) {
message.error('上传失败')
}
if (data?.successUrl && data?.errorUrl) {
message.error('上传部分成功')
}
// 设置导入结果和错误数据下载链接
setLoadError(
data?.successUrl && !data?.errorUrl
? '0'
: !data?.successUrl && data?.errorUrl
? '1'
: '2'
)
setErrorUrl(data.errorUrl)
// 调用父组件的确认回调
handleOk(data)
}
setLoadStatus(1) // 恢复为确认导入状态
} catch (error) {
// 捕获异常并恢复为确认导入状态
console.error('导入失败', error)
setLoadStatus(1)
}
}
/**
* 关闭模态框
*/
const onCancel = () => {
handleCancel() // 调用父组件的关闭回调
}
return (
<>
<Modal
open={visible} // 模态框是否可见
width={700} // 模态框宽度
title={title} // 模态框标题
footer={null} // 不显示默认的底部按钮
onCancel={onCancel} // 关闭模态框的回调
destroyOnClose // 关闭时销毁模态框内容
>
{loadStatus === 2 ? (
// 正在导入状态
<div style={{ textAlign: 'center' }}>
<LoadingOutlined style={{ fontSize: 32, marginBottom: 16 }} />{' '}
<br />
导入中,请稍等!
</div>
) : (
// 确认导入状态
<Spin spinning={formLoading}>
<Space align="center" style={{ marginLeft: 64 }}>
<span style={{ color: '#ff6062' }}>*</span>选择文件
<Input
value={uploadFile?.name} // 显示上传文件的名称
readOnly // 只读输入框
style={{ width: 240 }}
/>
<Upload
accept=".xls,.xlsx" // 限制上传文件类型
showUploadList={false} // 不显示上传列表
beforeUpload={(file) => {
// 在上传前处理文件
setLoadError('0') // 重置导入结果
setUploadFile(file) // 设置上传的文件
setErrorUrl('') // 重置错误数据下载链接
return false // 阻止默认上传行为
}}>
<Button icon={<UploadOutlined />}>浏览</Button>
</Upload>
<Button
type="primary" // 主按钮样式
disabled={!uploadFile} // 如果没有上传文件,禁用按钮
onClick={onUploadFile} // 点击按钮时确认导入
>
确认导入
</Button>
</Space>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
margin: '15px 85px 15px 130px',
boxSizing: 'border-box'
}}>
<div
style={{
display: 'inline-block',
textAlign: 'center',
color: 'red'
}}>
<a download="导入模板.xlsx" href={templateUrl}>
点击下载导入模板
</a>
最大支持{maxSize}条!
</div>
</div>
{loadError === '1' || loadError === '2' ? (
// 如果导入失败或部分成功,显示错误信息和下载链接
<div style={{ textAlign: 'center', marginTop: 30 }}>
<p style={{ color: 'red' }}>
{loadError === '2'
? '部分导入失败信息'
: loadError === '1'
? '全部导入失败'
: ''}
</p>
<a href={errorUrl} rel="noreferrer" download="">
下载导入异常数据
</a>
</div>
) : null}
</Spin>
)}
</Modal>
</>
)
}
export default BatchModal
文档说明
import type { Meta, StoryObj } from '@storybook/react'
import BatchModal from '../../components/BatchModal'
// 定义组件元数据
const meta = {
title: '文件上传/BatchModal', // 组件标题
component: BatchModal, // 组件
argTypes: {
visible: {
control: { type: 'boolean' }, // visible 属性的类型为布尔值
description: '模态框是否可见' // visible 属性的描述
},
handleCancel: {
action: 'handleCancel', // handleCancel 属性的事件处理
description: '关闭模态框的回调' // handleCancel 属性的描述
},
handleOk: {
action: 'handleOk', // handleOk 属性的事件处理
description: '确认导入成功的回调' // handleOk 属性的描述
},
title: {
control: { type: 'text' }, // title 属性的类型为文本
description: '模态框标题' // title 属性的描述
},
templateUrl: {
control: { type: 'text' }, // templateUrl 属性的类型为文本
description: '模板下载链接' // templateUrl 属性的描述
},
maxSize: {
control: { type: 'number' }, // maxSize 属性的类型为数字
description: '最大支持的条目数' // maxSize 属性的描述
},
onConfirmImport: {
description: '确认导入的逻辑' // onConfirmImport 属性的描述
}
}
} satisfies Meta<typeof BatchModal>
export default meta
type Story = StoryObj<typeof meta>
// 基本用法示例
export const BasicUsage: Story = {
args: {
visible: true,
title: '批量导入',
templateUrl: 'https://example.com/success.xlsx',
maxSize: 500,
onConfirmImport: async (file) => {
// 模拟确认导入逻辑
return new Promise((resolve) => {
setTimeout(() => {
resolve({
successUrl: 'https://example.com/success.xlsx',
errorUrl: 'https://example.com/error.xlsx'
})
}, 1000)
})
}
}
}
// 自定义配置示例
export const CustomConfiguration: Story = {
args: {
visible: true,
title: '自定义批量导入',
templateUrl: 'https://example.com/success.xlsx',
maxSize: 1000,
onConfirmImport: async (file) => {
// 模拟确认导入逻辑
return new Promise((resolve) => {
setTimeout(() => {
resolve({
successUrl: 'https://example.com/success.xlsx',
errorUrl: 'https://example.com/error.xlsx'
})
}, 1000)
})
}
}
}
// 导入状态示例
export const ImportStatus: Story = {
args: {
visible: true,
title: '导入状态显示',
templateUrl: 'https://example.com/success.xlsx',
maxSize: 500,
onConfirmImport: async (file) => {
// 模拟确认导入逻辑
return new Promise((resolve) => {
setTimeout(() => {
resolve({
successUrl: 'https://example.com/success.xlsx'
// errorUrl: 'https://example.com/error.xlsx'
})
}, 1000)
})
}
}
}