feat(files): 新增前端上传与订单证据图片
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import { useState } from 'react'
|
||||
import { App, Button, Image, Input, Space, Typography, Upload } from 'antd'
|
||||
import { DeleteOutlined, LinkOutlined, UploadOutlined } from '@ant-design/icons'
|
||||
|
||||
import { isImageFileURL, uploadFile } from '@/shared/api/files'
|
||||
|
||||
interface EvidenceUploadProps {
|
||||
scene: string
|
||||
value?: string[]
|
||||
onChange?: (value: string[]) => void
|
||||
max?: number
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export function EvidenceUpload({
|
||||
scene,
|
||||
value = [],
|
||||
onChange,
|
||||
max = 9,
|
||||
disabled = false,
|
||||
}: EvidenceUploadProps) {
|
||||
const { message } = App.useApp()
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [link, setLink] = useState('')
|
||||
|
||||
const add = (url: string) => {
|
||||
const trimmed = url.trim()
|
||||
if (!trimmed || value.includes(trimmed)) return
|
||||
onChange?.([...value, trimmed])
|
||||
}
|
||||
|
||||
const remove = (url: string) => {
|
||||
onChange?.(value.filter(item => item !== url))
|
||||
}
|
||||
|
||||
const handleFile = async (file: File) => {
|
||||
if (value.length >= max) {
|
||||
message.warning(`最多上传 ${max} 张图片`)
|
||||
return
|
||||
}
|
||||
if (!file.type.startsWith('image/')) {
|
||||
message.error('证据图片仅支持 JPG、PNG、WebP 格式')
|
||||
return
|
||||
}
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
message.error('单张图片不能超过 10MB')
|
||||
return
|
||||
}
|
||||
setUploading(true)
|
||||
try {
|
||||
const uploaded = await uploadFile(file, scene)
|
||||
add(uploaded.url)
|
||||
message.success('图片已上传')
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '图片上传失败')
|
||||
} finally {
|
||||
setUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const addLink = () => {
|
||||
const trimmed = link.trim()
|
||||
if (!trimmed) return
|
||||
if (!/^https?:\/\//i.test(trimmed)) {
|
||||
message.warning('请填写以 http:// 或 https:// 开头的链接')
|
||||
return
|
||||
}
|
||||
add(trimmed)
|
||||
setLink('')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="evidence-upload">
|
||||
<Space wrap size={10} className="evidence-upload__grid">
|
||||
{value.map(url => (
|
||||
<div className="evidence-upload__item" key={url}>
|
||||
{isImageFileURL(url) ? (
|
||||
<Image src={url} alt="证据图片" width={92} height={92} className="evidence-upload__image" />
|
||||
) : (
|
||||
<a href={url} target="_blank" rel="noreferrer" className="evidence-upload__link-card">
|
||||
<LinkOutlined />
|
||||
<span>外部链接</span>
|
||||
</a>
|
||||
)}
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
className="evidence-upload__remove"
|
||||
aria-label="删除证据"
|
||||
onClick={() => remove(url)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Upload
|
||||
accept="image/jpeg,image/png,image/webp"
|
||||
multiple
|
||||
showUploadList={false}
|
||||
disabled={disabled || uploading}
|
||||
beforeUpload={file => {
|
||||
void handleFile(file)
|
||||
return false
|
||||
}}
|
||||
>
|
||||
<button type="button" className="evidence-upload__add" disabled={disabled || uploading}>
|
||||
<UploadOutlined />
|
||||
<span>{uploading ? '上传中…' : '上传图片'}</span>
|
||||
</button>
|
||||
</Upload>
|
||||
</Space>
|
||||
|
||||
<Space.Compact style={{ width: '100%', marginTop: 10 }}>
|
||||
<Input
|
||||
value={link}
|
||||
placeholder="也可以粘贴外部证据链接"
|
||||
prefix={<LinkOutlined />}
|
||||
disabled={disabled}
|
||||
onChange={event => setLink(event.target.value)}
|
||||
onPressEnter={addLink}
|
||||
/>
|
||||
<Button onClick={addLink} disabled={disabled || !link.trim()}>
|
||||
添加
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
<Typography.Text type="secondary" className="evidence-upload__hint">
|
||||
最多 {max} 条,支持图片或外部链接。
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user