uniapp 实现身份证上传选择文件上传相册选择拍摄

张开发
2026/4/21 4:11:18 15 分钟阅读

分享文章

uniapp 实现身份证上传选择文件上传相册选择拍摄
使用技术 uniapp vue3 应用场景微信小程序上传图片 使用组件uview-plus组件库实现思路 移除组件自带的upload的上传事件加插槽事件选择三种类型实现效果如下htmltemplate view classbox_centent view classuni-padding-wrap uni-common-mt/view view class template v-foritem in array :keyitem.id view classcustoms-declaration-box v-ifshouldShowComponent(item.key) view classcustoms-declaration-title {{ item.name }} text classrequired-label必填/text nbsp; uni-icons custom-prefixiconfont v-ifitem.demoitem.keyid clickpreviewDemoImage(item.demo) typeicon-hetongmoban size20 / !-- image v-ifitem.demoitem.key!id clickpreviewDemoImage(item.demo) src/static/icons/word.png stylewidth:22px;height:22px mode/image -- uni-icons custom-prefixiconfont v-ifitem.demoitem.key!id clickpreviewDemoImage(item.demo) typeicon-hetongmoban size20 / !-- uni-icons custom-prefixiconfont typeicon-moban size20 / -- /view !-- 通用上传组件 -- view stylemargin:auto;width: 92%; class view classuniheight/view up-upload classcustom-upload v-ifitem.key ! id :fileListfileList[item.key] delete(event) deletePic(event, item.key) multiple :maxCountitem.keyother?5:3 :disabledtrue :show-choose-btnfalse acceptimage/*,.pdf,.doc,.docx,.xls,.xlsx !-- 触发按钮不变 -- template #trigger view classupload-trigger clickshowUploadOptions(item.key) image src/static/carlm.png stylewidth:30px;height:30px mode/image view stylefont-size:14px;{{ fileList[item.key]?.length || 0 }}/{{item.keyother?5:3}}/view /view /template !-- 自定义文件列表项根据文件类型显示图片或图标 -- template #file{ file } view classfile-item !-- 图片文件直接显示图片 -- image v-ifisImage(file.url) :srcfile.url classfile-preview modewidthFix /image !-- 非图片文件显示file.png图标 -- !-- image v-else src/static/icons/file.png classfile-icon /image -- !-- 文件信息名称、状态等 -- view classfile-info view classfile-name{{ getFileName(file.url) }}/view view classfile-status v-iffile.status uploading上传中.../view view classfile-status error v-iffile.status fail上传失败/view /view /view /template /up-upload /view !-- 身份证信息部分 -- view classcustoms-declaration-box v-ifitem.key id view stylemargin:auto;width: 92%; class view classuniheight/view !-- 上传人像页 -- view class stylemargin:auto;width: 92%;height: 160px;background-color: #FAFBFF;border-radius: 4px;border:1px solid #DFE5F0;display: flex;align-items: center;justify-content: center;position: relative; !-- 显示已上传的人像页图片 -- image v-iffileList[id] fileList[id][0] :srcfileList[id][0].status success ? fileList[id][0].url : /static/wImage1x.png stylewidth:100%;height: 160px; mode/image image v-else src/static/wImage1x.png stylewidth:80%;height: 145px; mode /image !-- 上传按钮 -- view class styleposition: absolute;right:26% view class v-if!fileList[id] || !fileList[id][0] || fileList[id][0].status ! success up-upload :fileListfileList[id] || [] afterRead(event) handleIdUpload(event, id) delete(event) deletePic(event, id) :maxCount1 template #trigger view class v-if!fileList[id] || !fileList[id][0] || fileList[id][0].status ! success stylewidth: 35px;height: 35px;border-radius: 35px;background-color: #4C7CEE;display:flex;align-items:center;justify-content: center;cursor: pointer; image src/static/md-photo_camera 11x.png stylewidth: 23px;height:23px mode/image /view /template /up-upload /view view class v-else up-upload afterRead(event) handleIdUpload(event, id) delete(event) deletePic(event, id) :maxCount1 template #trigger view class stylewidth: 35px;height: 35px;border-radius: 35px;background-color: #8fc480;display:flex;align-items:center;justify-content: center;cursor: pointer; image src/static/md-photo_camera 11x.png stylewidth: 23px;height:23px mode/image /view /template /up-upload /view view class :style{ color: fileList[id] fileList[id][0] fileList[id][0].status success ? #666666 : #666666, marginLeft: -29px } {{ fileList[id] fileList[id][0] fileList[id][0].status success ? 点击替换人像頁 : 点击上传人像頁 }} /view /view /view view classuniheight/view view classuniheight/view !-- 上传国徽页 -- view class stylemargin:auto;width: 92%;height: 160px;background-color: #FAFBFF;border-radius: 4px;border:1px solid #DFE5F0;display: flex;align-items: center;justify-content: center;position: relative; !-- 显示已上传的国徽页图片 -- image v-iffileList[idB] fileList[idB][0] :srcfileList[idB][0].status success ? fileList[idB][0].url : /static/wImage2x.png stylewidth:100%;height: 160px;position:relative mode/image image v-else src/static/wImage2x.png stylewidth:80%;height: 145px;position:relative mode/image !-- 上传按钮 -- view class styleposition: absolute;right:26% !-- 未上传或上传失败状态 -- view class v-if!fileList[idB] || !fileList[idB][0] || fileList[idB][0].status ! success up-upload :fileListfileList[idB] || [] afterRead(event) handleIdUpload(event, idB) delete(event) deletePic(event, idB) :maxCount1 template #trigger view class stylewidth: 35px;height: 35px;border-radius: 35px;background-color: #4C7CEE;display:flex;align-items:center;justify-content: center;cursor: pointer; image src/static/md-photo_camera 11x.png stylewidth: 23px;height:23px mode/image /view /template /up-upload /view !-- 上传成功状态 -- view class v-else up-upload afterRead(event) handleIdUpload(event, idB) delete(event) deletePic(event, idB) :maxCount1 template #trigger view class stylewidth: 35px;height: 35px;border-radius: 35px;background-color: #8fc480;display:flex;align-items:center;justify-content: center;cursor: pointer; image src/static/md-photo_camera 11x.png stylewidth: 23px;height:23px mode/image /view /template /up-upload /view !-- 提示文本 -- view class :style{ color: fileList[idB] fileList[idB][0] fileList[idB][0].status success ? #666666 : #666666, marginLeft: -29px } {{ fileList[idB] fileList[idB][0] fileList[idB][0].status success ? 点击替换国徽页 : 点击上传国徽页 }} /view /view /view /view view classuni-padding-wrap uni-common-mt/view view classuni-padding-wrap uni-common-mt/view !-- view class stylemargin:auto;width: 86%;height: 30px; 确认身份信息 /view view class stylemargin:auto;width: 86%;color: #BABABA; 系统将根据您的上传照片自动识别填充 /view view styleheight: 15px;/view view class stylemargin:auto;width: 80%;height: 30px;display: flex;align-items: center;justify-content: space-between; view class 真实姓名 /view view class 李天霸 /view /view view styleheight: 15px;/view view class stylemargin:auto;width: 80%;height: 30px;display: flex;align-items: center;justify-content: space-between; view class 证件号码 /view view class stylecolor: #BABABA; 41027749921099847 /view /view view styleheight: 15px;/view -- /view /view view classuni-padding-wrap uni-common-mt/view /template /view view classuni-padding-wrap uni-common-mt v-showsuipupload/view !-- 3 -- !-- view classcustoms-declaration-box v-ifclasshow view classcustoms-declaration-title 其他 /view view stylemargin:auto;width: 92%; class view classuniheight/view up-upload classcustom-upload :fileListfileList_other afterReadafterRead_other deletedeletePic_other multiple :maxCount5 template #trigger view classupload-trigger image src/static/carlm.png stylewidth:30px;height:30px mode /image view class stylefont-size: 14px; {{fileList_other.length}}/5 /view /view /template /up-upload /view /view -- view classuni-padding-wrap uni-common-mt/view view classuni-padding-wrap uni-common-mt/view view classuni-padding-wrap uni-common-mt/view view class stylewidth: 100%;height: 80px;background-color: white;position: fixed;bottom: 0px;display: flex;align-items: center;justify-content: center;z-index: 99; !-- up-bottom确定/up-bottom -- up-button type customStyleborder-radius:20px;width:90%;background-color:#E44A6C;color:white clickshore :textchillr?關閉:確認/up-button /view /view /templatejsscript setup import { ref, watch } from vue; import { onShow, onLoad } from dcloudio/uni-app; const chillr ref() onLoad((options) { chillr.value options.colse }) // 3. 匹配图标确保与扩展名对应 // 统一使用 file.png 作为非图片文件的图标 const getFileIcon (url) { // 调用 isImage 判断是否为图片非图片 → 返回 pdf.png return isImage(url) ? : /static/icons/pdf.png; }; const previewFile (url) { console.log(预览文件 URL:, url); // 关键调试打印 URL if (!url) { uni.showToast({ title: 文件路径无效, icon: none }); return; } // ...原有代码... }; // 显示上传选项弹窗 // 显示上传选项弹窗新增微信聊天文件选项 const showUploadOptions (key) { uni.showActionSheet({ itemList: [拍摄, 从相册选择, 選擇文件], // 新增选项 success: (res) { switch (res.tapIndex) { case 0: // 拍摄 chooseImage(camera, key); break; case 1: // 相册选择 chooseImage(album, key); break; case 2: // 从微信聊天选择 chooseWechatMessageFile(key); break; } }, fail: (err) { console.log(取消选择, err); } }); }; // 从微信聊天记录好友/群聊选择文件 // 从微信聊天记录好友/群聊选择文件 - 修正API调用 const chooseWechatMessageFile (key) { uni.chooseMessageFile({ count: 3 - (fileList.value[key]?.length || 0), type: file, success: (res) { // 关键修复微信聊天文件的路径字段是 path而非 tempFilePath res.tempFiles.forEach(file { const tempFilePath file.path; // 正确字段名是 path if (tempFilePath) { // 增加校验确保路径存在 handleFileUpload(tempFilePath, key); } else { console.error(微信文件路径获取失败, file); uni.showToast({ title: 文件路径错误, icon: none }); } }); }, fail: (err) { /* ... */ } }); }; // 1. 处理拍摄/相册选择图片 const chooseImage (sourceType, key) { uni.chooseImage({ count: 3 - (fileList.value[key]?.length || 0), // 最多选择3张减去已上传数量 sizeType: [original, compressed], sourceType: [sourceType], // camera 或 album success: (res) { // 选择成功后调用上传 res.tempFilePaths.forEach(tempPath { handleFileUpload(tempPath, key); }); } }); }; // 3. 统一文件上传逻辑 const handleFileUpload (tempFilePath, key) { // 1. 先添加到文件列表标记为上传中 if (!fileList.value[key]) { fileList.value[key] []; } const fileIndex fileList.value[key].length; fileList.value[key].push({ url: tempFilePath, status: uploading, message: , id: }); // 2. 调用上传接口 uploadFile(tempFilePath) .then(result { // 上传成功更新状态 fileList.value[key][fileIndex] { ...fileList.value[key][fileIndex], url: result.url, status: success, id: result.id }; console.log(PDF文件上传成功后端返回URL, result.url); // 关键检查此URL是否带.pdf扩展名 // 同步到全局存储 allUploadedUrls.value.push({ url: result.url, key }); uni.setStorageSync(allUploadedUrls, allUploadedUrls.value); }) .catch(error { // 上传失败更新状态 fileList.value[key][fileIndex].status fail; fileList.value[key][fileIndex].message error.message; }); }; // 4. 辅助方法判断是否为图片 // 4. 辅助方法判断是否为图片 const isImage (url) { if (!url) return false; // 路径为空时视为非图片 const imgExts [jpg, jpeg, png, gif, bmp, webp, svg, jfif]; // 覆盖常见图片格式 const ext url.split(.).pop().toLowerCase().split(?)[0]; // 去除URL参数影响如xxx.png?123 return imgExts.includes(ext); }; // 辅助方法提取文件名保持不变确保正确获取扩展名 const getFileName (url) { if (!url) return 未知文件; const urlWithoutParams url.split(?)[0]; // 去除参数 const fileName urlWithoutParams.split(/).pop(); // 提取最后一段作为文件名 return fileName || 未知文件; }; const shore () { // 可添加点击确定后的逻辑 // 检查是否有未上传的文件 let allUploaded true; // 检查 array 对应的上传项 array.value.forEach(item { if (item.key id (!fileList.value[item.key] || fileList.value[item.key].length 0)) { allUploaded false; } }); if (!allUploaded) { uni.showToast({ title: 所有项都是必填项, icon: none }); return; } else { // 其他 let filelasrt_other [] fileList_other.value.forEach(item { filelasrt_other.push(item.url) }) // 初始化文件数组 const fileGroups { id: [], contract: [], bill: [], boxing: [], form: [], other:[] }; // 处理已上传的文件 try { const allUploadedUrls uni.getStorageSync(allUploadedUrls) || []; console.log(allUploadedUrls, 刪除后保存); if (Array.isArray(allUploadedUrls)) { allUploadedUrls.forEach(item { if (item.key item.url) { // 根据key将url分类到对应的数组 switch (item.key) { case contract: fileGroups.contract.push(item.url); break; case bill: fileGroups.bill.push(item.url); break; case id: case idB: // 身份证正反面都归类到id fileGroups.id.push(item.url); break; case boxing: fileGroups.boxing.push(item.url); break; case form: fileGroups.form.push(item.url); break; case other: fileGroups.other.push(item.url); break; default: console.warn(未知文件类型:, item.key); } } }); } } catch (error) { console.error(处理已上传文件时出错:, error); } // 构建最终信息对象 const info { ...fileGroups, text: 已上傳 }; let declareclass uni.getStorageSync(declare) || []; // 确保获取到数组默认空数组 // 提取 declareclass 中所有必填的 key这些 key 必须在 filteredInfo 中存在 const requiredKeys declareclass.map(item item.key); // 过滤 info 对象仅保留存在于 declareclass 中的 key const filteredInfo Object.keys(info).reduce((acc, key) { if (requiredKeys.includes(key)) { acc[key] info[key]; } return acc; }, {}); filteredInfo.text 已上傳; console.log(filteredInfo, 輸出); // 移除值为空数组的字段 for (let key in filteredInfo) { if (Array.isArray(filteredInfo[key]) filteredInfo[key].length 0) { delete filteredInfo[key]; } } // 新增检查身份证图片数量是否为2 if (requiredKeys.includes(id) (!filteredInfo.id || filteredInfo.id.length ! 2)) { uni.showToast({ title: 请上传身份证正反面照片, icon: none }); return; } // 核心检查确保 declareclass 中的所有 key 都在 filteredInfo 中存在 const allRequiredExist requiredKeys.every(key filteredInfo.hasOwnProperty(key)); if (!allRequiredExist) { // 存在未上传的必填项 uni.showToast({ title: 所有项都是必填项1, icon: none }); return; // 阻止后续操作 } // 所有必填项都存在继续执行 console.log(filteredInfo, so); uni.setStorageSync(info_url, filteredInfo); uni.setStorageSync(info_content_url, filteredInfo); uni.setStorageSync(clsledir, 1); uni.navigateBack(1); uni.setStorageSync(info_urclassl, 1); } }; const previewDemoImage (imageUrl) { // 校验图片URL是否有效 if (!imageUrl) { uni.showToast({ title: 图片地址无效, icon: none }); return; } // 调用微信原生预览接口 uni.previewImage({ urls: [imageUrl], // 预览的图片URL数组单张图片也需放在数组中 current: 0, // 默认显示第几张这里只有一张为0 success: () { console.log(图片预览成功); }, fail: (err) { console.error(图片预览失败, err); uni.showToast({ title: 预览失败请检查图片地址, icon: none }); } }); }; let array ref([]); const towupload ref(false); const onwupload ref(false); const suipupload ref(false); // 处理身份证上传的通用函数 const handleIdUpload (event, key) { const files Array.isArray(event.file) ? event.file : [event.file]; files.forEach((file) { // 确保数组存在 if (!fileList.value[key]) { fileList.value[key] []; } // 清空现有数组替换模式 if (key id fileList.value[key].length 0) { fileList.value[key] []; } if (key idB fileList.value[key].length 0) { fileList.value[key] []; } // 添加新的上传项 fileList.value[key].push({ url: file.url, status: uploading, message: , id: }); uploadFile(file.url) .then((result) { const index fileList.value[key].findIndex((item) item.url file.url); if (index ! -1) { // 创建新对象替换旧对象确保响应式更新 const newItem { ...fileList.value[key][index], status: success, url: result.url, id: result.id }; fileList.value[key].splice(index, 1, newItem); // 更新 allUploadedUrls const urlIndex allUploadedUrls.value.findIndex( (item) item.key key ); if (urlIndex ! -1) { allUploadedUrls.value.splice(urlIndex, 1, { url: result.url, key: key, type: key id ? id : idB, fileName: file.name }); } else { allUploadedUrls.value.push({ url: result.url, key: key, type: key id ? id : idB, fileName: file.name }); } uni.setStorageSync(allUploadedUrls, allUploadedUrls.value); // 身份证上传成功提示日志 const successMsg key id ? 身份证人像页上传成功 : 身份证国徽页上传成功; uni.showToast({ title: successMsg, icon: none }); console.log(successMsg, 文件ID:, result.id); } }) .catch((error) { const index fileList.value[key].findIndex((item) item.url file.url); if (index ! -1) { fileList.value[key][index].status fail; fileList.value[key][index].message error.message; } // 身份证上传失败提示日志 const failMsg key id ? 身份证人像页上传失败: ${error.message} : 身份证国徽页上传失败: ${error.message}; uni.showToast({ title: failMsg, icon: none }); console.error(failMsg, 错误详情:, error); }); }); }; // const array ref([]); const fileList ref({}); const fileList_other ref([]); const allUploadedUrls ref([]); // 用于存储所有上传成功的 URL // 删除图片 const deletePic (event, key) { const deletedUrl fileList.value[key][event.index].url; console.log(deletedUrl); fileList.value[key].splice(event.index, 1); console.log(After delete:, fileList.value[key]); console.log(allUploadedUrls.value); // 从 allUploadedUrls 中移除删除的 URL 及其对应的 key allUploadedUrls.value allUploadedUrls.value.filter(item !(item.url deletedUrl item.key key)); uni.setStorageSync(allUploadedUrls, allUploadedUrls.value); }; // 删除其他图片 const deletePic_other (event) { fileList_other.value.splice(event.index, 1); }; // 上传文件方法 const uploadFile (filePath) { return new Promise((resolve, reject) { const baseUrl uni.getStorageSync(http); const token uni.getStorageSync(token); const fullUrl ${baseUrl}/api/public/uploadImage; // 基础参数校验 if (!baseUrl || !token) { reject(new Error(配置错误服务器地址或Token缺失)); return; } uni.uploadFile({ url: fullUrl, filePath: filePath, name: file, formData: { user: test }, header: { Authorization: Bearer ${uni.getStorageSync(token)} // 修正 Token 格式 }, success: (res) { console.log(上传接口响应状态, res); // 1. 处理 500 服务器错误 if (res.statusCode 500) { // 记录错误详情方便排查 console.error(服务器内部错误响应内容, res.data); reject(new Error(服务器繁忙请稍后再试)); // 友好提示用户 return; } // 2. 处理非 200/500 的其他状态码如 404 接口不存在、403 权限不足 if (res.statusCode ! 200) { reject(new Error(上传失败状态码${res.statusCode})); return; } // 3. 处理 200 状态码HTTP请求成功需解析业务逻辑 try { const data JSON.parse(res.data); if (data.code 0) { // 业务成功返回文件信息 resolve({ url: data.data.url, id: data.data.id }); } else { // 业务失败如文件类型不支持、大小超限后端明确返回错误信息 reject(new Error(data.message || 上传失败请检查文件是否符合要求)); } } catch (error) { // 200状态码但响应格式错误如后端返回HTML而非JSON console.error(响应格式错误原始数据, res.data); reject(new Error(服务器返回数据格式错误)); } }, fail: (err) { uni.showToast({ title: 上傳失敗, icon: none }); // 客户端调用失败如网络问题、文件不存在 console.error(上传调用失败, err.errMsg); reject(new Error(上传失败${err.errMsg})); } }); }); }; // 处理文件上传后的逻辑 const afterRead (event, key) { console.log(Before upload:, fileList.value[key]); console.log(key); const files Array.isArray(event.file) ? event.file : [event.file]; files.forEach((file) { if (!fileList.value[key]) { fileList.value[key] []; } fileList.value[key].push({ url: file.url, status: uploading, message: , id: }); uploadFile(file.url) .then((result) { const index fileList.value[key].findIndex((item) item.url file.url); if (index ! -1) { fileList.value[key][index] { ...fileList.value[key][index], status: success, url: result.url, id: result.id }; allUploadedUrls.value.push({ url: result.url, key: key }); uni.setStorageSync(allUploadedUrls, allUploadedUrls.value); // 新增通用文件上传成功提示 const item array.value.find(i i.key key); const fileName item?.name || 文件; uni.showToast({ title: ${fileName}上传成功, icon: none }); console.log(${fileName}上传成功, result.url); } console.log(After upload success:, fileList.value[key]); }) .catch((error) { const index fileList.value[key].findIndex((item) item.url file.url); if (index ! -1) { fileList.value[key][index].status fail; fileList.value[key][index].message error.message; } // 新增通用文件上传失败提示 const item array.value.find(i i.key key); const fileName item?.name || 文件; uni.showToast({ title: ${fileName}上传失败: ${error.message}, icon: none }); console.error(${fileName}上传失败, error); }); }); }; // 根据 key 判断是否显示组件 const shouldShowComponent (key) { // 这里可以添加更多逻辑目前默认都显示 return true; }; function transformData(obj) { const result []; // 遍历对象的每个属性 Object.keys(obj).forEach(key { // 处理特殊情况id 字段需要拆分为 id 和 idB if (key id Array.isArray(obj[key]) obj[key].length 0) { // 添加 id 字段 result.push({ key: id, url: obj[key][0] }); // 添加 idB 字段如果存在第二个 URL if (obj[key].length 1) { result.push({ key: idB, url: obj[key][1] }); } } // 处理其他字段 else if (Array.isArray(obj[key]) obj[key].length 0) { // 每个 URL 创建一个对象 obj[key].forEach(url { result.push({ key: key, url: url }); }); } }); return result; } // 新增标记位记录是否已初始化 const isInitialized ref(false); const classhow ref(true) onShow(() { if (!isInitialized.value) { // 首次进入初始化并读取存储 initializeFileList(); isInitialized.value true; } else { // 页面回流跳过初始化避免清空已上传内容 console.log(页面重新显示复用之前的文件状态); } }); // 抽离初始化逻辑为独立函数 function initializeFileList() { // 1. 初始化 fileList仅首次 fileList.value { id: [], idB: [], contract: [], bill: [], boxing: [], form: [] }; // 2. 初始化 other 文件列表 fileList_other.value []; const uni_order uni.getStorageSync(Details); if (uni_order) { classhow.value false; } // 3. 读取存储并回显 const declareArr uni.getStorageSync(declare); array.value declareArr || []; const clasasppr uni.getStorageSync(info_url); if (clasasppr) { handleOtherFiles(clasasppr); // 抽离的回显逻辑 } const storedUrls uni.getStorageSync(allUploadedUrls) || []; restoreUploadedUrls(storedUrls); // 抽离的回显逻辑 } // 抽离回显 other 类型文件 function handleOtherFiles(clasasppr) { if (clasasppr.other Array.isArray(clasasppr.other)) { clasasppr.other.forEach(url { if (!fileList_other.value.some(item item.url url)) { fileList_other.value.push({ url, status: success, message: }); } }); } } // 抽离回显已上传的图片 function restoreUploadedUrls(storedUrls) { allUploadedUrls.value []; storedUrls.forEach((item) { if (!fileList.value[item.key]) { fileList.value[item.key] []; } fileList.value[item.key].push({ url: item.url, status: success, id: item.id || , message: }); allUploadedUrls.value.push({ url: item.url, key: item.key }); }); uni.setStorageSync(classupdate, allUploadedUrls.value); } /script

更多文章