-
Notifications
You must be signed in to change notification settings - Fork 273
feat: 增加浏览器中粘贴上传的功能,并增加相应的演示功能 #3151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Conversation
Walkthrough本次变更主要为 Uploader 组件新增粘贴上传功能及其相关演示、文档更新和内部文件处理优化。新增的测试用例验证了粘贴上传行为,Demo 展示中引入了新组件 Demo15 并增加对多语言翻译的支持,同时文档中添加了 enablePasteUpload 属性说明。内部上传逻辑也调整为更好地处理图片预览及文件读取流程。 Changes
Suggested reviewers
Poem
Tip ⚡💬 Agentic Chat (Pro Plan, General Availability)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
b2c0079
to
b741c74
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
src/packages/uploader/demos/h5/demo15.tsx (1)
4-10
: 示例代码实现良好,但可考虑添加提示说明组件实现简洁明了,清晰地展示了粘贴上传功能。建议考虑添加一些提示文本,告知用户可以粘贴图片进行上传,这样能提高用户体验。
const Demo15 = () => { return ( <> - <Uploader enablePasteUpload /> + <Uploader enablePasteUpload uploadLabel="点击上传或粘贴图片" /> </> ) }src/packages/uploader/doc.md (1)
140-146
: 文档更新清晰,建议增加使用说明新增的粘贴上传功能文档说明简洁清晰。建议可以在演示说明中补充一些使用提示,例如如何使用粘贴功能(Ctrl+V 或右键粘贴)以及支持的图片格式等信息,这将有助于用户更好地理解和使用此功能。
src/packages/uploader/__tests__/uploader.spec.tsx (1)
290-331
: 测试用例实现全面且规范测试用例结构良好,采用了 arrange-act-assert 模式,清晰地验证了粘贴上传功能。模拟的 ClipboardEvent 和文件创建方式正确,期望值检查全面。
唯一可以改进的地方是考虑测试不同类型的文件(如非图片文件)或边缘情况的处理。
src/packages/uploader/uploader.tsx (1)
390-453
: 粘贴上传功能实现。粘贴上传功能实现得很完整,但有几点可以优化:
- 建议使用可选链操作符来避免潜在的空值引用
- 可以考虑添加对粘贴事件的错误处理
- if (clipboardData.items && clipboardData.items.length) { + if (clipboardData?.items && clipboardData.items.length) {- } else if (clipboardData.files && clipboardData.files.length) { + } else if (clipboardData?.files && clipboardData.files.length) {🧰 Tools
🪛 Biome (1.9.4)
[error] 399-399: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 409-409: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
src/packages/uploader/uploader.taro.tsx (1)
431-446
: 优化图片预览处理。添加了对图片文件的预览处理,使用
FileReader
读取图片数据并设置URL和路径,这是一个很好的功能增强。不过,建议添加错误处理逻辑,以防读取文件失败。const reader = new FileReader() + reader.onerror = (error) => { + console.error('读取文件失败:', error) + executeUpload(fileItem, index) + results.push(fileItem) + } reader.onload = (event) => { fileItem.url = event.target?.result as string fileItem.path = event.target?.result as string
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/packages/uploader/__tests__/uploader.spec.tsx
(1 hunks)src/packages/uploader/demo.tsx
(5 hunks)src/packages/uploader/demos/h5/demo15.tsx
(1 hunks)src/packages/uploader/doc.md
(2 hunks)src/packages/uploader/uploader.taro.tsx
(2 hunks)src/packages/uploader/uploader.tsx
(6 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
src/packages/uploader/demos/h5/demo15.tsx (2)
src/packages/uploader/uploader.taro.tsx (1)
Uploader
(580-580)src/packages/uploader/uploader.tsx (1)
Uploader
(535-535)
🪛 Biome (1.9.4)
src/packages/uploader/uploader.tsx
[error] 399-399: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 409-409: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (9)
src/packages/uploader/doc.md (1)
204-204
: 属性表格说明完整属性表中的
enablePasteUpload
说明准确,类型和默认值描述清晰。src/packages/uploader/demo.tsx (3)
18-18
: 导入新组件的方式正确新增 Demo15 组件的导入语句位置恰当,保持了与其他 Demo 组件相同的导入风格。
36-36
: 多语言支持实现完整为新功能添加了中文简体、中文繁体和英文三种语言的翻译,保持了应用的国际化一致性。
Also applies to: 52-52, 69-69
104-105
: 界面集成方式符合现有模式新功能的 UI 展示方式与现有其他功能一致,代码结构清晰。
src/packages/uploader/uploader.tsx (4)
74-74
: 接口添加新属性,增强组件功能。通过添加可选的
enablePasteUpload
属性,使组件支持从浏览器粘贴上传的功能,增强了用户体验。
104-104
: 默认值配置合理。将
enablePasteUpload
的默认值设置为false
是合理的,这样可以保持向后兼容性,用户需要显式开启该功能才能使用。
314-314
: 文件读取逻辑优化。修改后的代码确保了在读取文件后正确更新文件列表,这是一个很好的修复。
455-467
: 事件监听器的生命周期管理。事件监听器的添加和移除逻辑实现得很好,确保了在组件卸载时正确清理事件监听器,并且仅在
enablePasteUpload
为true
时才添加事件监听器。src/packages/uploader/uploader.taro.tsx (1)
421-421
: 改进对原始文件的处理。使用空值合并运算符(
??
)来处理可能为空的originalFileObj
是一个很好的改进,确保了在各种情况下都能正确地将文件添加到formData
中。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/packages/uploader/uploader.tsx (2)
390-453
: 粘贴上传功能实现完善,但可优化代码健壮性
handlePaste
函数实现了粘贴上传的核心功能,逻辑完整且考虑了各种边界情况。但建议使用可选链操作符来提高代码健壮性。建议对剪贴板数据的检查使用可选链操作符:
- if (clipboardData?.items && clipboardData.items.length) { + if (clipboardData?.items?.length) {以及:
- } else if (clipboardData?.files && clipboardData.files.length) { + } else if (clipboardData?.files?.length) {🧰 Tools
🪛 Biome (1.9.4)
[error] 399-399: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 409-409: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
390-450
: 考虑重构以减少代码重复
handlePaste
函数中的文件处理逻辑与fileChange
函数中的逻辑有一定重复,特别是在处理beforeUpload
和文件过滤方面。可以考虑提取一个共同的函数来处理这些相似逻辑。可以考虑创建一个共用函数来处理文件上传逻辑:
const processFiles = (files: File[]) => { if (beforeUpload) { beforeUpload(files).then((f: Array<File> | boolean) => { if (typeof f === 'boolean') return const _files = filterFiles(new Array<File>().slice.call(f)) if (_files.length) { readFile(_files) onChange?.([ ...fileList, ..._files.map((file) => ({ name: file.name, type: file.type, status: 'ready', })), ]) } }) } else { const _files = filterFiles(new Array<File>().slice.call(files)) if (_files.length) { readFile(_files) onChange?.([ ...fileList, ..._files.map((file) => ({ name: file.name, type: file.type, status: 'ready', })), ]) } } }然后在
handlePaste
和fileChange
中调用这个函数。🧰 Tools
🪛 Biome (1.9.4)
[error] 399-399: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 409-409: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/packages/uploader/demos/h5/demo15.tsx
(1 hunks)src/packages/uploader/doc.md
(2 hunks)src/packages/uploader/uploader.tsx
(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/packages/uploader/demos/h5/demo15.tsx
- src/packages/uploader/doc.md
🧰 Additional context used
🪛 Biome (1.9.4)
src/packages/uploader/uploader.tsx
[error] 399-399: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 409-409: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (7)
src/packages/uploader/uploader.tsx (7)
74-74
: UploaderProps 接口新增的属性合理明确新增的
enablePasteUpload
可选布尔属性符合接口设计规范,并且作为可选参数确保了向后兼容性,这对于现有代码不会造成破坏性影响。
104-104
: 默认属性设置符合预期将
enablePasteUpload
默认值设置为false
是合理的,确保了现有使用组件的代码不会受到影响,用户需要显式启用该功能才能使用粘贴上传。
314-314
: 图片预览处理逻辑优化修改后的代码确保在读取到图片URL后立即更新文件列表,这样可以在图片文件完成读取后立即显示预览,改善了用户体验。
455-467
: 事件监听器管理逻辑正确
useEffect
钩子正确地根据enablePasteUpload
属性添加和移除粘贴事件监听器,确保只有在功能启用时才会响应粘贴事件。依赖数组包含了所有必要的依赖项,避免了闭包陷阱问题。
8-8
: React 钩子使用得当引入
useCallback
并使用它来优化handlePaste
函数是一个很好的做法,避免了不必要的重新渲染。依赖数组包含了所有相关的变量,确保回调函数在依赖项变化时能够正确更新。Also applies to: 390-453
154-154
: 属性解构完整将
enablePasteUpload
添加到解构的属性列表中,确保了新属性能够被组件正确访问和使用。
399-416
: 粘贴事件处理全面代码全面处理了两种可能的剪贴板数据格式(
items
和files
),并正确过滤只接受图片类型文件,这符合最佳实践。🧰 Tools
🪛 Biome (1.9.4)
[error] 399-399: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 409-409: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
修改uploader.taro.tsx文件是为了解决什么问题
是应该修改 uploader.tsx 对吗🥹?我以为 uploader.taro.tsx 是源文件,会被适配到不同的端。 |
这个功能看起来主要是针对H5端的。.tsx对应的是nutui-react纯H5的源码。taro.tsx对应的是nutui-react-taro,需要验证新增功能是否在多端可行 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/packages/uploader/uploader.taro.tsx (1)
527-567
: 粘贴事件处理函数实现粘贴事件处理函数实现得当,但有几处可以优化:
- 在获取clipboardData后需要增加对浏览器兼容性的考虑
- 使用可选链可以简化代码并提高可读性
建议优化以下部分:
} } else if (clipboardData?.files && clipboardData.files.length) { for (let i = 0; i < clipboardData.files.length; i++) { - const file = clipboardData.files[i] + const file = clipboardData?.files[i] if (file.type.startsWith('image/')) { files.push(file) } } }🧰 Tools
🪛 Biome (1.9.4)
[error] 544-546: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/packages/uploader/demo.taro.tsx
(5 hunks)src/packages/uploader/demos/taro/demo15.tsx
(1 hunks)src/packages/uploader/uploader.taro.tsx
(7 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/packages/uploader/demos/taro/demo15.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
src/packages/uploader/uploader.taro.tsx
[error] 544-546: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 566-569: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (7)
src/packages/uploader/demo.taro.tsx (3)
21-21
: 组件导入新增正确正确地导入了新的Demo15组件,用于展示粘贴上传功能。
39-39
: 翻译键添加完整为三种语言(简体中文、繁体中文和英文)添加了"enablePasteUpload"的翻译键,命名一致且表达准确。
Also applies to: 55-55, 73-73
109-110
: Demo展示部分添加正确正确添加了新功能的标题和Demo15组件,遵循了现有的模式和结构。
src/packages/uploader/uploader.taro.tsx (4)
8-8
: 新增useCallback导入正确引入了useCallback钩子,用于优化后续的粘贴事件处理函数。
123-123
: 属性定义和默认值设置完整成功添加了enablePasteUpload属性到UploaderProps接口,设置了默认值为false,并在组件参数中正确解构。这种方式符合组件库的最佳实践。
Also applies to: 157-157, 208-208
425-425
: 优化FormData附加文件的处理使用空值合并操作符(??)确保即使originalFileObj为空也能将有效文件对象附加到FormData中,提高了代码的健壮性。
569-581
: 生命周期管理正确正确地使用了useEffect来管理粘贴事件监听器的添加和移除,并正确地依赖了相关状态,避免了内存泄漏。当enablePasteUpload为true时添加监听器,为false或组件卸载时移除监听器。
if (preview && file.type?.includes('image')) { | ||
const reader = new FileReader() | ||
reader.onload = (event) => { | ||
fileItem.url = event.target?.result as string | ||
fileItem.path = event.target?.result as string | ||
fileItem.name = (file as unknown as Blob).name | ||
executeUpload(fileItem, index) | ||
results.push(fileItem) | ||
setFileList([...fileList, ...results]) | ||
} | ||
|
||
reader.readAsDataURL(file.originalFileObj ?? (file as unknown as Blob)) | ||
} else { | ||
executeUpload(fileItem, index) | ||
results.push(fileItem) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
文件读取逻辑优化建议
图片文件读取逻辑进行了异步处理,但当前实现存在以下问题:
- 代码结构导致可能重复添加文件到fileList
- 条件分支下的状态更新逻辑不一致
建议修改为以下实现,确保状态更新的一致性:
if (preview && file.type?.includes('image')) {
const reader = new FileReader()
reader.onload = (event) => {
fileItem.url = event.target?.result as string
fileItem.path = event.target?.result as string
fileItem.name = (file as unknown as Blob).name
executeUpload(fileItem, index)
results.push(fileItem)
- setFileList([...fileList, ...results])
}
reader.readAsDataURL(file.originalFileObj ?? (file as unknown as Blob))
} else {
executeUpload(fileItem, index)
results.push(fileItem)
}
})
+ // 确保只在所有文件处理完成后一次性更新fileList
setFileList([...fileList, ...results])
Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里 if 下面的 setFileList 不能删除,因为它是异步触发的。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Jeff-Tian 您是对的,这里的 setFileList
不能删除。由于 FileReader.onload
是异步回调函数,它会在文件读取完成后才执行。如果只在外部调用 setFileList
,那么异步读取的图片文件可能还没有被添加到 results
数组中,导致状态更新不完整。
保留这个异步回调中的 setFileList
可以确保用户上传图片后立即看到预览效果,而不必等待所有文件处理完成。
✏️ Learnings added
Learnt from: Jeff-Tian
PR: jdf2e/nutui-react#3151
File: src/packages/uploader/uploader.taro.tsx:435-450
Timestamp: 2025-04-19T14:22:49.831Z
Learning: 在 Uploader 组件中处理图片文件时,FileReader.onload 回调内的 setFileList 调用不能删除,因为它是异步触发的,确保图片读取完成后及时更新状态显示预览。
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
7d3983b
to
6365529
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
src/packages/uploader/__tests__/uploader.spec.tsx
(1 hunks)src/packages/uploader/demo.taro.tsx
(5 hunks)src/packages/uploader/demo.tsx
(5 hunks)src/packages/uploader/demos/h5/demo15.tsx
(1 hunks)src/packages/uploader/demos/taro/demo15.tsx
(1 hunks)src/packages/uploader/doc.md
(2 hunks)src/packages/uploader/uploader.taro.tsx
(7 hunks)src/packages/uploader/uploader.tsx
(6 hunks)
✅ Files skipped from review due to trivial changes (2)
- src/packages/uploader/demo.tsx
- src/packages/uploader/demo.taro.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
- src/packages/uploader/doc.md
- src/packages/uploader/demos/h5/demo15.tsx
- src/packages/uploader/demos/taro/demo15.tsx
- src/packages/uploader/tests/uploader.spec.tsx
- src/packages/uploader/uploader.taro.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
src/packages/uploader/uploader.tsx
[error] 399-399: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 409-409: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (4)
src/packages/uploader/uploader.tsx (4)
74-74
: 新增的粘贴上传功能属性配置良好enablePasteUpload属性的添加遵循了良好的TypeScript实践,默认值设置为false可确保向后兼容性。这是一个很好的opt-in功能设计。
Also applies to: 104-104
314-314
: 修复了文件预览状态更新取消注释这一行代码确保了在文件读取后正确更新文件列表状态,修复了文件预览功能。
455-467
: 事件监听器管理实现正确useEffect钩子正确管理了粘贴事件监听器的注册和销毁,依赖项数组也包含了所有必要的变量。
8-8
: 添加useCallback导入正确导入了useCallback钩子,用于优化handlePaste函数的性能。
]) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
验证粘贴上传功能与剪贴板API兼容性
请确保在不同浏览器中测试此功能,因为剪贴板API在不同浏览器中可能有细微差别。
🌐 Web query:
浏览器对Clipboard API的支持情况及兼容性
💡 Result:
Clipboard API 兼容性与主流浏览器支持情况
Clipboard API(即 navigator.clipboard
)是用于在 Web 页面中与操作系统剪贴板进行交互的现代 JavaScript 接口。该 API 支持异步读写操作,是对旧有 document.execCommand()
的替代,具备更好的安全性和功能拓展性(如支持读写图片等多种数据类型)[2][5][6][8]。
主流浏览器支持状况
浏览器 | 支持情况 | 备注 |
---|---|---|
Chrome | 43+ ✅ | 需 HTTPS 环境,支持异步读写 |
Edge | 12+ ✅ | 需 HTTPS,权限和体验类似 Chrome |
Firefox | 41+ ✅ | 需 HTTPS,写权限自动授予,读权限用户需确认 |
Safari | 10+ ✅ | 支持基本文本操作 |
Opera | 29+ ✅ | 与 Chromium 类似 |
Internet Explorer | ❌ | 不支持,需用旧 API 兼容 |
说明:
- 需 HTTPS 环境,即只有在安全域(https 或 localhost)下才可用[2][5][6]
- 写入剪贴板一般无需额外权限,读取内容则需用户同意(涉及安全提示)[2][5]
- 所有操作均为异步,返回 Promise[5][6]
兼容性补充说明
- 低版本浏览器及IE支持:老旧浏览器(如 IE)不支持 Clipboard API。可回退到
document.execCommand('copy')
(已废弃)、或者更早的window.clipboardData
(IE 专有)[3][10]。 - 移动端兼容性:现代移动浏览器(iOS/Android 上的 Chrome、Safari、Edge 等)也大多支持 Clipboard API,但部分内嵌浏览器(如微信内置浏览器)可能禁用此功能[9]。
- 第三方库兼容方案:如 clipboard.js 通过自动切换不同实现方式,从而支持旧版浏览器和移动设备[7]。
推荐开发兼容性实践
- 检查
navigator.clipboard
是否存在,优先使用 Clipboard API。 - 不支持时,回退到
document.execCommand('copy')
。 - 如仍不支持,可以给出提示或降级处理。
- 建议所有剪贴板操作仅在用户触发的点击等交互事件中执行,满足用户安全需求[10]。
示例判断与回退代码
function copyTextToClipboard(text) {
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(
() => console.log('Copy succeeded'),
err => console.error('Copy failed', err)
);
} else if (document.execCommand) {
const input = document.createElement('textarea');
input.value = text;
document.body.appendChild(input);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
} else {
alert('当前浏览器不支持复制功能!');
}
}
[3][10]
总结
- 大多数现代浏览器(PC和主流移动端)已原生支持 Clipboard API,但需 HTTPS 和权限授权[2][5][6]。
- 低版本浏览器和 IE 不支持,需要回退旧 API。
- 可利用第三方库 clipboard.js 做更全面的兼容处理[7]。
- 建议开发时采用多层兼容方案,提升用户体验和普适性。
如需具体浏览器版本覆盖详情,可以通过 “Can I Use” 网站实时查询最新的兼容信息[6]。
Citations:
- 1: https://blog.csdn.net/qq_24956515/article/details/141992424
- 2: https://www.ruanyifeng.com/blog/2021/01/clipboard-api.html
- 3: https://blog.csdn.net/LETEfly/article/details/135650522
- 4: https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/WebExtensions/API/clipboard
- 5: https://juejin.cn/post/7158370002674909192
- 6: https://tehub.com/a/cwgdXYjS7t
- 7: https://docs.pingcode.com/baike/2343246
- 8: https://juejin.cn/post/6910421635971416078
- 9: https://zz-zzzz.github.io/share/copy.html
- 10: https://my.oschina.net/emacs_8699792/blog/17038352
确保粘贴上传功能兼容主流浏览器 Clipboard API
请在 src/packages/uploader/uploader.tsx
(约第450行)粘贴上传逻辑中加入对剪贴板 API 的运行时检测和回退处理,确保在不支持 navigator.clipboard
的环境下仍能正常复制/粘贴:
– 在用户触发事件中,优先使用
if (navigator.clipboard) {
await navigator.clipboard.readText();
// …处理粘贴内容
}
– 对不支持的旧浏览器或 IE,回退到
const textarea = document.createElement('textarea');
// 设置值、加入 DOM、选中、execCommand('paste')(或 'copy')并移除
– 如有必要,可引入第三方库(如 clipboard.js)统一兼容方案。
– 确保所有剪贴板读写均在 HTTPS 环境及安全用户交互中执行。
const handlePaste = useCallback( | ||
(event: ClipboardEvent) => { | ||
if (!enablePasteUpload || disabled) return | ||
|
||
const clipboardData = event.clipboardData | ||
if (!clipboardData) return | ||
|
||
const files: File[] = [] | ||
|
||
if (clipboardData?.items && clipboardData.items.length) { | ||
for (let i = 0; i < clipboardData.items.length; i++) { | ||
const item = clipboardData.items[i] | ||
if (item.kind === 'file' && item.type.startsWith('image/')) { | ||
const file = item.getAsFile() | ||
if (file) { | ||
files.push(file) | ||
} | ||
} | ||
} | ||
} else if (clipboardData?.files && clipboardData.files.length) { | ||
for (let i = 0; i < clipboardData.files.length; i++) { | ||
const file = clipboardData.files[i] | ||
if (file.type.startsWith('image/')) { | ||
files.push(file) | ||
} | ||
} | ||
} | ||
|
||
if (files.length) { | ||
if (beforeUpload) { | ||
beforeUpload(files).then((f: Array<File> | boolean) => { | ||
if (typeof f === 'boolean') return | ||
|
||
const _files = filterFiles(new Array<File>().slice.call(f)) | ||
if (_files.length) { | ||
readFile(_files) | ||
onChange?.([ | ||
...fileList, | ||
...files.map((file) => ({ | ||
name: file.name, | ||
type: file.type, | ||
status: 'ready', | ||
})), | ||
]) | ||
} | ||
}) | ||
} else { | ||
const _files = filterFiles(new Array<File>().slice.call(files)) | ||
if (_files.length) { | ||
readFile(_files) | ||
onChange?.([ | ||
...fileList, | ||
..._files.map((file) => ({ | ||
name: file.name, | ||
type: file.type, | ||
status: 'ready', | ||
})), | ||
]) | ||
} | ||
} | ||
} | ||
}, | ||
[enablePasteUpload, disabled, beforeUpload, filterFiles, readFile, onChange] | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
handlePaste函数实现良好,建议优化可选链使用
粘贴上传功能的实现逻辑清晰,但可以使用可选链操作符来简化代码并提高安全性。
建议对clipboardData的访问使用可选链操作符:
- if (clipboardData?.items && clipboardData.items.length) {
+ if (clipboardData?.items?.length) {
- } else if (clipboardData?.files && clipboardData.files.length) {
+ } else if (clipboardData?.files?.length) {
另外,在beforeUpload回调中的if (typeof f === 'boolean') return
这一行,建议添加更明确的处理逻辑,以便更好地理解当返回布尔值时的行为。
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const handlePaste = useCallback( | |
(event: ClipboardEvent) => { | |
if (!enablePasteUpload || disabled) return | |
const clipboardData = event.clipboardData | |
if (!clipboardData) return | |
const files: File[] = [] | |
if (clipboardData?.items && clipboardData.items.length) { | |
for (let i = 0; i < clipboardData.items.length; i++) { | |
const item = clipboardData.items[i] | |
if (item.kind === 'file' && item.type.startsWith('image/')) { | |
const file = item.getAsFile() | |
if (file) { | |
files.push(file) | |
} | |
} | |
} | |
} else if (clipboardData?.files && clipboardData.files.length) { | |
for (let i = 0; i < clipboardData.files.length; i++) { | |
const file = clipboardData.files[i] | |
if (file.type.startsWith('image/')) { | |
files.push(file) | |
} | |
} | |
} | |
if (files.length) { | |
if (beforeUpload) { | |
beforeUpload(files).then((f: Array<File> | boolean) => { | |
if (typeof f === 'boolean') return | |
const _files = filterFiles(new Array<File>().slice.call(f)) | |
if (_files.length) { | |
readFile(_files) | |
onChange?.([ | |
...fileList, | |
...files.map((file) => ({ | |
name: file.name, | |
type: file.type, | |
status: 'ready', | |
})), | |
]) | |
} | |
}) | |
} else { | |
const _files = filterFiles(new Array<File>().slice.call(files)) | |
if (_files.length) { | |
readFile(_files) | |
onChange?.([ | |
...fileList, | |
..._files.map((file) => ({ | |
name: file.name, | |
type: file.type, | |
status: 'ready', | |
})), | |
]) | |
} | |
} | |
} | |
}, | |
[enablePasteUpload, disabled, beforeUpload, filterFiles, readFile, onChange] | |
) | |
const handlePaste = useCallback( | |
(event: ClipboardEvent) => { | |
if (!enablePasteUpload || disabled) return | |
const clipboardData = event.clipboardData | |
if (!clipboardData) return | |
const files: File[] = [] | |
if (clipboardData?.items?.length) { | |
for (let i = 0; i < clipboardData.items.length; i++) { | |
const item = clipboardData.items[i] | |
if (item.kind === 'file' && item.type.startsWith('image/')) { | |
const file = item.getAsFile() | |
if (file) { | |
files.push(file) | |
} | |
} | |
} | |
} else if (clipboardData?.files?.length) { | |
for (let i = 0; i < clipboardData.files.length; i++) { | |
const file = clipboardData.files[i] | |
if (file.type.startsWith('image/')) { | |
files.push(file) | |
} | |
} | |
} | |
if (files.length) { | |
if (beforeUpload) { | |
beforeUpload(files).then((f: Array<File> | boolean) => { | |
if (typeof f === 'boolean') return | |
const _files = filterFiles(new Array<File>().slice.call(f)) | |
if (_files.length) { | |
readFile(_files) | |
onChange?.([ | |
...fileList, | |
...files.map((file) => ({ | |
name: file.name, | |
type: file.type, | |
status: 'ready', | |
})), | |
]) | |
} | |
}) | |
} else { | |
const _files = filterFiles(new Array<File>().slice.call(files)) | |
if (_files.length) { | |
readFile(_files) | |
onChange?.([ | |
...fileList, | |
..._files.map((file) => ({ | |
name: file.name, | |
type: file.type, | |
status: 'ready', | |
})), | |
]) | |
} | |
} | |
} | |
}, | |
[enablePasteUpload, disabled, beforeUpload, filterFiles, readFile, onChange] | |
) |
🧰 Tools
🪛 Biome (1.9.4)
[error] 399-399: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 409-409: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
@Alex-huxiyang 感谢说明! 我看了一下似乎只有浏览器支持读取剪贴板中的图片。不过,我希望加到 .taro.tsx 中,因为我目前使用的 taro 版本的 nut-ui。我在文档中增加了说明, 增加了 taro 版本的 h5 粘贴上传示例,并附上了录屏演示。 |
🤔 这个变动的性质是?
🔗 相关 Issue
💡 需求背景和解决方案
个人非常喜欢这个组件库,并用在了自己的小程序端和h5上(因为是多端,所以使用的 taro 版本)。在浏览器中上传时,经常需要一个粘贴上传的功能,之前基于 2.7.7 的版本通过 patch-package 的方式已经在自己的站点上实现了,但是一旦升级就需要重新 patch,所以提个 PR,希望能够将此功能合并到上游,望采纳,不胜感激!
可能需要做一些代码修改,求大佬指点!
<Uploader enablePasteUpload />
pnpm dev 演示 Web 版本:
Screen.Recording.2025-04-04.at.11.55.35.mov
pnpm dev:taro:h5 演示 taro 版本的 h5 端:
pnpm.dev_taro_h5.mp4
☑️ 请求合并前的自查清单
Summary by CodeRabbit
Summary by CodeRabbit
新功能
文档
测试