<template>
<view class="container">
<!-- 操作按钮区 -->
<view class="card">
<button class="btn primary" @click="chooseFileNative">
📱 使用原生选择器
</button>
<button class="btn outline" @click="triggerFileInput">
🖼️ 选择文件(浏览器方式)
</button>
<!-- 隐藏的 input -->
<input
type="file"
ref="fileInputRef"
:accept="acceptTypes"
multiple
@change="handleFiles"
style="display: none"
/>
</view>
<!-- 已选文件列表 -->
<view class="card">
<text class="section-title">📤 已选文件</text>
<view class="file-list" v-if="files.length > 0">
<view class="file-item" v-for="(file, index) in files" :key="index">
<text>
<strong>📄 名称:</strong> {{ file.name }}<br />
<strong>📏 大小:</strong> {{ formatSize(file.size) }}<br />
<strong>🏷️ 类型:</strong> {{ file.type || '未知' }}
</text>
<!-- 图片预览 -->
<image
v-if="file.type?.startsWith('image/')"
:src="file.url"
mode="aspectFit"
class="preview-img"
/>
<!-- 视频提示 -->
<text v-else-if="file.type?.startsWith('video/')">
🎥 视频文件,可上传查看
</text>
<!-- PDF 预览链接 -->
<a
v-else-if="getFileExt(file.name) === 'pdf'"
:href="file.url"
target="_blank"
class="link"
>
🔗 查看 PDF
</a>
</view>
</view>
<view v-else class="empty-tip">
<text>暂无文件,请先选择</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
// 环境变量
const env = import.meta.env.MODE
const baseUrl = ref(import.meta.env.VITE_DOMAIN_URL)
// 文件输入引用
const fileInputRef = ref(null)
// 可接受的文件类型(可根据需要调整)
const acceptTypes = 'image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
// 存储已选文件
const files = ref([])
// 记录上传状态
const uploading = reactive({})
// 格式化文件大小
const formatSize = (bytes) => {
if (!bytes) return '0 KB'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
// 方法1:调用原生 JS Bridge
const chooseFileNative = () => {
if (window.NativeBridge && window.NativeBridge.chooseFile) {
window.NativeBridge.chooseFile()
} else {
alert('原生功能不可用,将使用浏览器方式')
triggerFileInput()
}
}
// 安全获取文件扩展名
const getFileExt = (filename) => {
if (!filename) return ''
const match = filename.trim().toLowerCase().match(/\.([a-z0-9]+)$/)
return match ? match[1] : ''
}
// 方法2:触发文件选择(动态创建 input,确保 click 可用)
const triggerFileInput = () => {
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
input.multiple = true
input.style.display = 'none'
document.body.appendChild(input)
input.onchange = (event) => {
handleFiles(event)
document.body.removeChild(input)
}
input.click() // 这里一定会成功调用原生 click
}
// 处理 input 选择的文件
const handleFiles = async (event) => {
console.log("🚀 ~ handleFiles ~ event:", event)
const selectedFiles = event.target.files
if (!selectedFiles) return
for (let file of Array.from(selectedFiles)) {
const url = URL.createObjectURL(file)
console.log("🚀 ~ handleFiles ~ url:", url)
const base64 = await readFileAsBase64(file) // 🔥 添加 base64
console.log("🚀 ~ handleFiles ~ base64:", base64)
files.value.push({
name: file.name,
size: file.size,
type: file.type,
url, // 预览用
base64 // 新增:base64 流
})
}
}
// 工具函数:将 Blob/File 转为 base64
const readFileAsBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => {
// result 就是 base64 数据 URL: data:image/png;base64,...
resolve(reader.result)
}
reader.onerror = reject
reader.readAsDataURL(file) // 开始读取为 base64
})
}
// 接收原生返回的文件(JS Bridge 回调)
function onNativeFileSelected(filePath, fileName, fileSize, mimeType) {
console.log("🚀 ~ onNativeFileSelected1 ~ filePath:", filePath)
console.log("🚀 ~ onNativeFileSelected2 ~ fileName:", fileName)
console.log("🚀 ~ onNativeFileSelected3 ~ fileSize:", fileSize)
console.log("🚀 ~ onNativeFileSelected4 ~ mimeType:", mimeType)
try {
// 添加防御判断
if (!fileName || typeof fileName !== 'string') {
fileName = 'unknown_file'
}
if (!fileSize || typeof fileSize !== 'number') {
fileSize = 0
}
if (!mimeType) mimeType = 'application/octet-stream'
const fakeFile = new File([], fileName, { type: mimeType })
const blobUrl = URL.createObjectURL(fakeFile)
files.value.push({
name: fileName,
size: fileSize,
type: mimeType,
nativePath: filePath,
url: blobUrl
})
} catch (error) {
console.log("🚀 ~ onNativeFileSelected ~ error:", error)
}
}
// 暴露给原生调用(必须挂载到 window)
onMounted(() => {
window.onNativeFileSelected = onNativeFileSelected
console.log('[H5] 页面加载完成,等待文件选择...')
})
</script>
<style scoped lang="scss">
.container {
padding: 20px;
background: #f8f9fa;
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.btn {
display: block;
width: 100%;
padding: 15px;
margin: 10px 0;
border: none;
border-radius: 8px;
font-size: 16px;
text-align: center;
cursor: pointer;
&.primary {
background: #1a73e8;
color: white;
}
&.outline {
background: #e8f0fe;
color: #1a73e8;
}
}
.section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 15px;
display: block;
}
.file-list {
display: flex;
flex-direction: column;
gap: 15px;
}
.file-item {
padding: 15px;
background: #f1f3f5;
border-radius: 8px;
font-size: 14px;
position: relative;
}
.preview-img {
max-width: 100%;
max-height: 200px;
margin-top: 10px;
border-radius: 6px;
}
.link {
color: #1a73e8;
text-decoration: underline;
margin-top: 8px;
display: inline-block;
}
.empty-tip {
color: #666;
font-style: italic;
text-align: center;
padding: 20px 0;
}
</style>
修改当前组件,让它和饿了么Ui的上传组件的功能相同,并且因为当前组件是小程序跳转进入的页面,希望在选择文件后,点击返回按钮可以把所有选中的文件信息携带回小程序
最新发布