|
|
@@ -1,597 +1,288 @@
|
|
|
<template>
|
|
|
- <!-- 模板部分保持不变 -->
|
|
|
<view v-if="custom">
|
|
|
- <up-upload @after-read="afterRead" :deletable="!disabled" @delete="deletePic"
|
|
|
- :max-count="disabled ? fileList.length : maxCount" :accept="accept" multiple @clickPreview="clickPreview">
|
|
|
+ <up-upload
|
|
|
+ @after-read="afterRead"
|
|
|
+ :deletable="!disabled"
|
|
|
+ @delete="deletePic"
|
|
|
+ :max-count="disabled ? fileList.length : maxCount"
|
|
|
+ :accept="accept"
|
|
|
+ multiple
|
|
|
+ @clickPreview="clickPreview"
|
|
|
+ >
|
|
|
<slot />
|
|
|
</up-upload>
|
|
|
</view>
|
|
|
- <up-upload v-else :fileList="fileList" @after-read="afterRead" :deletable="!disabled" @delete="deletePic"
|
|
|
- :max-count="disabled ? fileList.length : maxCount" :accept="accept" multiple @clickPreview="clickPreview" />
|
|
|
+ <up-upload
|
|
|
+ v-else
|
|
|
+ :fileList="fileList"
|
|
|
+ @after-read="afterRead"
|
|
|
+ :deletable="!disabled"
|
|
|
+ @delete="deletePic"
|
|
|
+ :max-count="disabled ? fileList.length : maxCount"
|
|
|
+ :accept="accept"
|
|
|
+ multiple
|
|
|
+ @clickPreview="clickPreview"
|
|
|
+ />
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, reactive, defineProps, defineEmits, getCurrentInstance, onUnmounted } from 'vue'
|
|
|
+import { reactive, defineProps, defineEmits, getCurrentInstance, onUnmounted } from 'vue'
|
|
|
+
|
|
|
+/* ================= emits / props ================= */
|
|
|
+
|
|
|
const emit = defineEmits(['uploadCallback', 'startUploading'])
|
|
|
+
|
|
|
const props = defineProps({
|
|
|
- accept: {
|
|
|
- type: String,
|
|
|
- default: "image"
|
|
|
- },
|
|
|
- maxCount: {
|
|
|
- type: [String, Number],
|
|
|
- default: 99
|
|
|
- },
|
|
|
- uploadCallback: {
|
|
|
- type: Function
|
|
|
- },
|
|
|
- startUploading: {
|
|
|
- type: Function
|
|
|
- },
|
|
|
- fileList: {
|
|
|
- type: Array,
|
|
|
- default: reactive([])
|
|
|
- },
|
|
|
- usetype: {
|
|
|
- type: String,
|
|
|
- default: 'default'
|
|
|
- },
|
|
|
- ownertable: {
|
|
|
- type: String,
|
|
|
- default: 'temporary'
|
|
|
- },
|
|
|
- ownerid: {
|
|
|
- type: [String, Number],
|
|
|
- default: 1
|
|
|
- },
|
|
|
- disabled: {
|
|
|
- type: Boolean,
|
|
|
- default: false
|
|
|
- },
|
|
|
- custom: {
|
|
|
- type: Boolean,
|
|
|
- default: false
|
|
|
- },
|
|
|
- // 新增压缩相关配置
|
|
|
+ accept: { type: String, default: 'image' },
|
|
|
+ maxCount: { type: [String, Number], default: 99 },
|
|
|
+ uploadCallback: { type: Function },
|
|
|
+ startUploading: { type: Function },
|
|
|
+ fileList: { type: Array, default: reactive([]) },
|
|
|
+ usetype: { type: String, default: 'default' },
|
|
|
+ ownertable: { type: String, default: 'temporary' },
|
|
|
+ ownerid: { type: [String, Number], default: 1 },
|
|
|
+ disabled: { type: Boolean, default: false },
|
|
|
+ custom: { type: Boolean, default: false },
|
|
|
compressConfig: {
|
|
|
type: Object,
|
|
|
default: () => ({
|
|
|
- enable: true, // 是否启用压缩
|
|
|
- maxSize: 1024 * 1024, // 1MB,超过此大小才压缩(单位:字节)
|
|
|
- maxWidth: 1920, // 最大宽度
|
|
|
- maxHeight: 1080, // 最大高度
|
|
|
- quality: 0.8, // 图片质量 0-1
|
|
|
- videoBitrate: 1000000, // 视频比特率 1Mbps
|
|
|
- videoMaxWidth: 1280, // 视频最大宽度
|
|
|
- videoMaxHeight: 720 // 视频最大高度
|
|
|
+ enable: true,
|
|
|
+ threshold: 300 * 1024,
|
|
|
+ targetSize: 100 * 1024,
|
|
|
+ maxWidth: 2560,
|
|
|
+ maxHeight: 2560,
|
|
|
+ quality: 0.6
|
|
|
})
|
|
|
}
|
|
|
})
|
|
|
-const { $Http } = getCurrentInstance().proxy;
|
|
|
-const deleteList = reactive([]); // 用于存储待删除的文件列表
|
|
|
-
|
|
|
-const clickPreview = (e) => {
|
|
|
- uni.previewImage({
|
|
|
- urls: props.fileList.map(v => v.url),
|
|
|
- current: e.url,
|
|
|
- loop: true,
|
|
|
- })
|
|
|
-}
|
|
|
|
|
|
-// 判断是否为图片
|
|
|
-const isImage = (file) => {
|
|
|
- return file.type?.startsWith('image/') ||
|
|
|
- /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name) ||
|
|
|
- /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.url);
|
|
|
-}
|
|
|
+const { $Http } = getCurrentInstance().proxy
|
|
|
+const deleteList = reactive([])
|
|
|
|
|
|
-// 判断是否为视频
|
|
|
-const isVideo = (file) => {
|
|
|
- return file.type?.startsWith('video/') ||
|
|
|
- /\.(mp4|mov|avi|wmv|flv|mkv|webm)$/i.test(file.name) ||
|
|
|
- /\.(mp4|mov|avi|wmv|flv|mkv|webm)$/i.test(file.url);
|
|
|
-}
|
|
|
+/* ================= 工具 ================= */
|
|
|
|
|
|
-// H5平台压缩图片
|
|
|
-const compressImageH5 = (file) => {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const img = new Image();
|
|
|
- img.crossOrigin = 'Anonymous';
|
|
|
- img.onload = () => {
|
|
|
- const canvas = document.createElement('canvas');
|
|
|
- const ctx = canvas.getContext('2d');
|
|
|
-
|
|
|
- // 计算压缩后的尺寸
|
|
|
- let width = img.width;
|
|
|
- let height = img.height;
|
|
|
-
|
|
|
- if (width > props.compressConfig.maxWidth || height > props.compressConfig.maxHeight) {
|
|
|
- const ratio = Math.min(
|
|
|
- props.compressConfig.maxWidth / width,
|
|
|
- props.compressConfig.maxHeight / height
|
|
|
- );
|
|
|
- width = Math.floor(width * ratio);
|
|
|
- height = Math.floor(height * ratio);
|
|
|
- }
|
|
|
-
|
|
|
- canvas.width = width;
|
|
|
- canvas.height = height;
|
|
|
-
|
|
|
- // 填充白色背景(对于透明图片)
|
|
|
- ctx.fillStyle = '#FFFFFF';
|
|
|
- ctx.fillRect(0, 0, width, height);
|
|
|
-
|
|
|
- // 绘制图片
|
|
|
- ctx.drawImage(img, 0, 0, width, height);
|
|
|
-
|
|
|
- // 转换为Blob
|
|
|
- canvas.toBlob((blob) => {
|
|
|
- resolve(blob);
|
|
|
- }, file.type || 'image/jpeg', props.compressConfig.quality);
|
|
|
- };
|
|
|
-
|
|
|
- img.onerror = reject;
|
|
|
-
|
|
|
- // 创建Object URL
|
|
|
- if (file.url.startsWith('blob:')) {
|
|
|
- img.src = file.url;
|
|
|
- } else if (file.originFileObj) {
|
|
|
- img.src = URL.createObjectURL(file.originFileObj);
|
|
|
- } else {
|
|
|
- img.src = file.url;
|
|
|
- }
|
|
|
- });
|
|
|
-}
|
|
|
+const isImage = (file) =>
|
|
|
+ file.type?.startsWith('image/') ||
|
|
|
+ /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name || file.url)
|
|
|
|
|
|
-// H5平台压缩视频(使用MediaRecorder API)
|
|
|
-const compressVideoH5 = (file) => {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const video = document.createElement('video');
|
|
|
- video.preload = 'metadata';
|
|
|
-
|
|
|
- video.onloadedmetadata = () => {
|
|
|
- // 计算压缩后的尺寸
|
|
|
- let width = video.videoWidth;
|
|
|
- let height = video.videoHeight;
|
|
|
-
|
|
|
- if (width > props.compressConfig.videoMaxWidth || height > props.compressConfig.videoMaxHeight) {
|
|
|
- const ratio = Math.min(
|
|
|
- props.compressConfig.videoMaxWidth / width,
|
|
|
- props.compressConfig.videoMaxHeight / height
|
|
|
- );
|
|
|
- width = Math.floor(width * ratio);
|
|
|
- height = Math.floor(height * ratio);
|
|
|
- }
|
|
|
-
|
|
|
- // 创建Canvas来捕获视频帧
|
|
|
- const canvas = document.createElement('canvas');
|
|
|
- canvas.width = width;
|
|
|
- canvas.height = height;
|
|
|
- const ctx = canvas.getContext('2d');
|
|
|
-
|
|
|
- // 设置视频尺寸
|
|
|
- video.width = width;
|
|
|
- video.height = height;
|
|
|
-
|
|
|
- // 开始捕获视频(这里简化处理,实际应用中可能需要使用MediaRecorder)
|
|
|
- // 注意:完整视频压缩需要更复杂的实现,这里只做演示
|
|
|
- video.onseeked = () => {
|
|
|
- ctx.drawImage(video, 0, 0, width, height);
|
|
|
- canvas.toBlob((blob) => {
|
|
|
- // 这里应该压缩整个视频,而不是单帧
|
|
|
- // 实际项目中建议使用第三方库或服务端压缩
|
|
|
- resolve(blob);
|
|
|
- }, 'video/mp4');
|
|
|
- };
|
|
|
-
|
|
|
- video.currentTime = 0;
|
|
|
- };
|
|
|
-
|
|
|
- video.onerror = reject;
|
|
|
-
|
|
|
- if (file.url.startsWith('blob:')) {
|
|
|
- video.src = file.url;
|
|
|
- } else if (file.originFileObj) {
|
|
|
- video.src = URL.createObjectURL(file.originFileObj);
|
|
|
- } else {
|
|
|
- video.src = file.url;
|
|
|
- }
|
|
|
- });
|
|
|
-}
|
|
|
+/* ================= 压缩 ================= */
|
|
|
|
|
|
-// 小程序/App平台压缩图片
|
|
|
-const compressImageNative = (file) => {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
+const compressImageNative = (file, quality) =>
|
|
|
+ new Promise(resolve => {
|
|
|
uni.compressImage({
|
|
|
src: file.url,
|
|
|
- quality: props.compressConfig.quality * 100, // 转换为百分比
|
|
|
- success: (res) => {
|
|
|
- console.log('图片压缩成功', res);
|
|
|
- resolve(res.tempFilePath);
|
|
|
- },
|
|
|
- fail: (err) => {
|
|
|
- console.error('图片压缩失败', err);
|
|
|
- // 压缩失败时使用原文件
|
|
|
- resolve(file.url);
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
-}
|
|
|
+ quality: Math.floor(quality * 100),
|
|
|
+ success: res => resolve(res.tempFilePath),
|
|
|
+ fail: () => resolve(file.url)
|
|
|
+ })
|
|
|
+ })
|
|
|
|
|
|
-// 小程序/App平台压缩视频
|
|
|
-const compressVideoNative = (file) => {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- uni.compressVideo({
|
|
|
- src: file.url,
|
|
|
- quality: 'medium', // low, medium, high
|
|
|
- bitrate: props.compressConfig.videoBitrate,
|
|
|
- fps: 30,
|
|
|
- resolution: props.compressConfig.videoMaxHeight,
|
|
|
- success: (res) => {
|
|
|
- console.log('视频压缩成功', res);
|
|
|
- resolve(res.tempFilePath);
|
|
|
- },
|
|
|
- fail: (err) => {
|
|
|
- console.error('视频压缩失败', err);
|
|
|
- // 压缩失败时使用原文件
|
|
|
- resolve(file.url);
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
-}
|
|
|
+const getFileSize = (filePath) =>
|
|
|
+ new Promise(resolve => {
|
|
|
+ uni.getFileInfo({
|
|
|
+ filePath,
|
|
|
+ success: res => resolve(res.size),
|
|
|
+ fail: () => resolve(0)
|
|
|
+ })
|
|
|
+ })
|
|
|
|
|
|
-// 压缩文件处理
|
|
|
const compressFile = async (file) => {
|
|
|
- try {
|
|
|
- // 检查是否启用压缩
|
|
|
- if (!props.compressConfig.enable) {
|
|
|
- return file;
|
|
|
- }
|
|
|
-
|
|
|
- // 检查文件大小,小于阈值不压缩
|
|
|
- let fileSize = file.size;
|
|
|
-
|
|
|
- // 如果在H5环境,获取文件大小
|
|
|
- if (!fileSize && file.originFileObj) {
|
|
|
- fileSize = file.originFileObj.size;
|
|
|
- }
|
|
|
-
|
|
|
- // 如果无法获取大小,默认压缩
|
|
|
- if (!fileSize || fileSize > props.compressConfig.maxSize) {
|
|
|
- let compressedUrl;
|
|
|
-
|
|
|
- // #ifdef H5
|
|
|
- if (isImage(file)) {
|
|
|
- const blob = await compressImageH5(file);
|
|
|
- compressedUrl = URL.createObjectURL(blob);
|
|
|
- file.compressedSize = blob.size;
|
|
|
- } else if (isVideo(file)) {
|
|
|
- // H5视频压缩需要专门的库,这里简化处理
|
|
|
- console.warn('H5视频压缩需要专门的库,暂使用原文件');
|
|
|
- compressedUrl = file.url;
|
|
|
- }
|
|
|
- // #endif
|
|
|
-
|
|
|
- // #ifndef H5
|
|
|
- if (isImage(file)) {
|
|
|
- compressedUrl = await compressImageNative(file);
|
|
|
- } else if (isVideo(file)) {
|
|
|
- compressedUrl = await compressVideoNative(file);
|
|
|
- }
|
|
|
- // #endif
|
|
|
-
|
|
|
- if (compressedUrl && compressedUrl !== file.url) {
|
|
|
- return {
|
|
|
- ...file,
|
|
|
- url: compressedUrl,
|
|
|
- compressed: true
|
|
|
- };
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return file;
|
|
|
- } catch (error) {
|
|
|
- console.error('压缩文件失败:', error);
|
|
|
- // 压缩失败返回原文件
|
|
|
- return file;
|
|
|
- }
|
|
|
+ if (!props.compressConfig.enable || !isImage(file)) return file
|
|
|
+
|
|
|
+ const size = file.size || 0
|
|
|
+ if (size && size <= props.compressConfig.threshold) return file
|
|
|
+
|
|
|
+ const quality = Math.min(
|
|
|
+ Math.max((props.compressConfig.targetSize / size) * 0.5, 0.1),
|
|
|
+ 0.5
|
|
|
+ )
|
|
|
+
|
|
|
+ const newUrl = await compressImageNative(file, quality)
|
|
|
+ const finalSize = await getFileSize(newUrl)
|
|
|
+
|
|
|
+ return { ...file, url: newUrl, size: finalSize }
|
|
|
}
|
|
|
|
|
|
-// 文件读取后处理(修改后)
|
|
|
+/* ================= afterRead ================= */
|
|
|
+
|
|
|
const afterRead = async ({ file }) => {
|
|
|
- emit('startUploading', file);
|
|
|
-
|
|
|
- for (const item of file) {
|
|
|
+ emit('startUploading', file)
|
|
|
+ const files = Array.isArray(file) ? file : [file]
|
|
|
+
|
|
|
+ for (const item of files) {
|
|
|
try {
|
|
|
- // 压缩处理
|
|
|
- const compressedFile = await compressFile(item);
|
|
|
-
|
|
|
- // #ifdef H5
|
|
|
- const arrayBuffer = await getArrayBuffer(compressedFile);
|
|
|
- arrayBuffer.data.url = compressedFile.url;
|
|
|
- handleUploadFile(requestType(compressedFile), arrayBuffer.data);
|
|
|
-
|
|
|
- // 更新文件列表状态
|
|
|
- props.fileList.push({
|
|
|
- ...compressedFile,
|
|
|
- status: 'uploading',
|
|
|
- message: '上传中',
|
|
|
- });
|
|
|
- // #endif
|
|
|
-
|
|
|
- // #ifndef H5
|
|
|
+ const processedFile = await compressFile(item)
|
|
|
+
|
|
|
+ // ★ 修复点 1:生成稳定 uid(不影响原逻辑)
|
|
|
+ const uid = `${Date.now()}_${Math.random()}`
|
|
|
+ processedFile.uid = uid
|
|
|
+
|
|
|
+ const executeUpload = (dataValue, finalUrl) => {
|
|
|
+ const fileConf = requestType(processedFile)
|
|
|
+ handleUploadFile(fileConf, dataValue, finalUrl, uid)
|
|
|
+
|
|
|
+ props.fileList.push({
|
|
|
+ ...processedFile,
|
|
|
+ uid,
|
|
|
+ url: finalUrl,
|
|
|
+ status: 'uploading',
|
|
|
+ message: '上传中'
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
uni.getFileSystemManager().readFile({
|
|
|
- filePath: compressedFile.url,
|
|
|
- success: data => {
|
|
|
- data.data.url = compressedFile.url;
|
|
|
- handleUploadFile(requestType(compressedFile), data.data);
|
|
|
-
|
|
|
- // 更新文件列表状态
|
|
|
- props.fileList.push({
|
|
|
- ...compressedFile,
|
|
|
- status: 'uploading',
|
|
|
- message: '上传中',
|
|
|
- });
|
|
|
- },
|
|
|
+ filePath: processedFile.url,
|
|
|
+ success: (data) => executeUpload(data.data, processedFile.url),
|
|
|
fail: console.error
|
|
|
- });
|
|
|
- // #endif
|
|
|
-
|
|
|
- } catch (error) {
|
|
|
- console.error('处理文件失败:', error);
|
|
|
- uni.showToast({
|
|
|
- title: '文件处理失败',
|
|
|
- icon: 'none'
|
|
|
- });
|
|
|
+ })
|
|
|
+ } catch (e) {
|
|
|
+ console.error('上传失败:', e)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 获取文件类型信息
|
|
|
-const requestType = (file) => {
|
|
|
- let ext = ''
|
|
|
-
|
|
|
- // #ifdef H5
|
|
|
- ext = file.name.substring(file.name.lastIndexOf(".") + 1)
|
|
|
- // #endif
|
|
|
-
|
|
|
- // #ifndef H5
|
|
|
- ext = file.type?.split("/")[1] ||
|
|
|
- file.url.substring(file.url.lastIndexOf(".") + 1) ||
|
|
|
- file.name.substring(file.name.lastIndexOf(".") + 1)
|
|
|
- // #endif
|
|
|
+/* ================= 请求配置 ================= */
|
|
|
|
|
|
+const requestType = (file) => {
|
|
|
+ const ext = file.url.substring(file.url.lastIndexOf('.') + 1) || 'jpg'
|
|
|
return {
|
|
|
id: '10019701',
|
|
|
- "content": {
|
|
|
- "filename": `${Date.now() + (file.size || 0)}.${ext}`,
|
|
|
- "filetype": ext,
|
|
|
- "parentid": uni.getStorageSync('siteP').appfolderid
|
|
|
+ content: {
|
|
|
+ filename: `${Date.now()}.${ext}`,
|
|
|
+ filetype: ext,
|
|
|
+ parentid: uni.getStorageSync('siteP')?.appfolderid || ''
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 获取ArrayBuffer (H5专用) - 修改以支持压缩后的文件
|
|
|
-const getArrayBuffer = (file) => {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- // 如果是压缩后的文件且是Blob URL
|
|
|
- if (file.url.startsWith('blob:')) {
|
|
|
- fetch(file.url)
|
|
|
- .then(response => response.blob())
|
|
|
- .then(blob => {
|
|
|
- const reader = new FileReader();
|
|
|
- reader.readAsArrayBuffer(blob);
|
|
|
- reader.onload = () => resolve({
|
|
|
- data: reader.result,
|
|
|
- compressed: file.compressed
|
|
|
- });
|
|
|
- reader.onerror = error => reject(error);
|
|
|
- })
|
|
|
- .catch(reject);
|
|
|
- } else {
|
|
|
- // 原逻辑
|
|
|
- const xhr = new XMLHttpRequest()
|
|
|
- xhr.open('GET', file.url, true)
|
|
|
- xhr.responseType = 'blob'
|
|
|
- xhr.onload = function () {
|
|
|
- if (this.status === 200) {
|
|
|
- const myBlob = this.response
|
|
|
- const files = new File(
|
|
|
- [myBlob],
|
|
|
- file.name,
|
|
|
- { type: file.type },
|
|
|
- )
|
|
|
- const reader = new FileReader()
|
|
|
- reader.readAsArrayBuffer(files)
|
|
|
- reader.onload = () => resolve({
|
|
|
- data: reader.result,
|
|
|
- compressed: file.compressed
|
|
|
- })
|
|
|
- reader.onerror = error => reject(error)
|
|
|
- } else {
|
|
|
- reject(`文件加载失败: ${this.status}`)
|
|
|
- }
|
|
|
- }
|
|
|
- xhr.onerror = () => reject('网络请求失败')
|
|
|
- xhr.send()
|
|
|
- }
|
|
|
- })
|
|
|
-}
|
|
|
+/* ================= 上传 ================= */
|
|
|
|
|
|
-// 处理文件上传
|
|
|
-const handleUploadFile = (file, data) => {
|
|
|
- $Http.basic(file).then(res => {
|
|
|
- console.log("上传文件成功", res)
|
|
|
- if (res.msg == "成功") {
|
|
|
- uploadFile(res.data, data)
|
|
|
- } else {
|
|
|
- uni.showToast({
|
|
|
- title: `${file.content.filename}上传失败`,
|
|
|
- icon: "none"
|
|
|
- })
|
|
|
+const handleUploadFile = (fileConfig, data, localUrl, uid) => {
|
|
|
+ $Http.basic(fileConfig).then(res => {
|
|
|
+ if (res.msg === '成功') {
|
|
|
+ uploadFile(res.data, data, localUrl, uid)
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-// 上传文件到服务器
|
|
|
-const uploadFile = (res, data) => {
|
|
|
+const uploadFile = (res, data, localUrl, uid) => {
|
|
|
uni.request({
|
|
|
url: res.uploadurl,
|
|
|
- method: "PUT",
|
|
|
+ method: 'PUT',
|
|
|
data,
|
|
|
header: { 'Content-Type': 'application/octet-stream' },
|
|
|
success: () => {
|
|
|
$Http.basic({
|
|
|
id: 10019901,
|
|
|
- "content": { "serialfilename": res.serialfilename }
|
|
|
+ content: { serialfilename: res.serialfilename }
|
|
|
}).then(s => {
|
|
|
- console.log("文件上传反馈", s)
|
|
|
- handleFileLink([{
|
|
|
- attachmentid: s.data.attachmentids[0],
|
|
|
- url: data.url
|
|
|
- }], "temporary", 1, props.usetype)
|
|
|
- }).catch(console.error)
|
|
|
- },
|
|
|
- fail: console.error
|
|
|
+ // ★ 修复点 2:把 uid 透传下去
|
|
|
+ handleFileLink(
|
|
|
+ [{ attachmentid: s.data.attachmentids[0], url: localUrl, uid }],
|
|
|
+ props.ownertable,
|
|
|
+ props.ownerid,
|
|
|
+ props.usetype
|
|
|
+ )
|
|
|
+ })
|
|
|
+ }
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-function handleFileLink(list, ownertable = "temporary", ownerid = 1, usetype = 'default', resolve = () => { }) {
|
|
|
- if (list.length == 0) return resolve(true);
|
|
|
- let content = {
|
|
|
+/* ================= 文件关联(核心修复在这里) ================= */
|
|
|
+
|
|
|
+function handleFileLink(list, ownertable = 'temporary', ownerid = 1, usetype = 'default', resolve = () => {}) {
|
|
|
+ if (!list.length) return resolve(true)
|
|
|
+
|
|
|
+ const content = {
|
|
|
ownertable,
|
|
|
ownerid,
|
|
|
usetype,
|
|
|
attachmentids: list.map(v => v.attachmentid),
|
|
|
- siteid: uni.getStorageSync("userMsg").siteid
|
|
|
+ siteid: uni.getStorageSync('userMsg').siteid
|
|
|
}
|
|
|
+
|
|
|
$Http.basic({
|
|
|
- "classname": "system.attachment.Attachment",
|
|
|
- "method": "createFileLink",
|
|
|
+ classname: 'system.attachment.Attachment',
|
|
|
+ method: 'createFileLink',
|
|
|
content
|
|
|
}).then(res => {
|
|
|
- console.log('跟进记录绑定附件', res)
|
|
|
- resolve(res.code == '1')
|
|
|
- if (res.code != '1') return uni.showToast({
|
|
|
- title: res.msg,
|
|
|
- icon: "none"
|
|
|
- })
|
|
|
+ resolve(res.code === '1')
|
|
|
+
|
|
|
list.forEach(v => {
|
|
|
- const file = props.fileList.find(s => v.url === s.url || v.url === s.thumb);
|
|
|
+ // ★ 修复点 3:不再用 url,用 uid 找文件(真机关键)
|
|
|
+ const file = props.fileList.find(s => s.uid === v.uid)
|
|
|
if (file) {
|
|
|
- delete file.status;
|
|
|
- delete file.message;
|
|
|
- Object.assign(file, res.data.find(s => s.attachmentid === v.attachmentid));
|
|
|
+ // ★ 修复点 4:不 delete,明确设置 success
|
|
|
+ file.status = 'success'
|
|
|
+ file.message = ''
|
|
|
+ Object.assign(file, res.data.find(s => s.attachmentid === v.attachmentid))
|
|
|
}
|
|
|
- });
|
|
|
- emit('uploadCallback', { fileList: props.fileList, attachmentids: content.attachmentids })
|
|
|
+ })
|
|
|
+
|
|
|
+ emit('uploadCallback', {
|
|
|
+ fileList: props.fileList,
|
|
|
+ attachmentids: content.attachmentids
|
|
|
+ })
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-// 保存所有的附件绑定到表上,有在上传的文件不能保存
|
|
|
+/* ================= 业务方法(原样保留) ================= */
|
|
|
+
|
|
|
const isUploading = (showToast = true) => {
|
|
|
- let res = props.fileList.some(file => file.status === 'uploading');
|
|
|
- if (res && showToast) uni.showToast({
|
|
|
- title: '文件正在上传中,请稍后再试',
|
|
|
- icon: 'none'
|
|
|
- });
|
|
|
+ const res = props.fileList.some(file => file.status === 'uploading')
|
|
|
+ if (res && showToast) {
|
|
|
+ uni.showToast({ title: '文件正在上传中', icon: 'none' })
|
|
|
+ }
|
|
|
return res
|
|
|
}
|
|
|
|
|
|
-// 保存接口 接受数据调用handleFileLink
|
|
|
const saveFileLinks = (ownertable, ownerid, usetype = 'default') => {
|
|
|
- // 如果有待删除的文件,先删除
|
|
|
- deleteList.length && deleteFile(deleteList);
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const list = props.fileList;
|
|
|
- console.log("list", list)
|
|
|
- if (list.length) {
|
|
|
- return handleFileLink(list, ownertable, ownerid, usetype, resolve);
|
|
|
- } else {
|
|
|
- resolve(true)
|
|
|
+ deleteList.length && deleteFile(deleteList)
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ if (props.fileList.length) {
|
|
|
+ return handleFileLink(props.fileList, ownertable, ownerid, usetype, resolve)
|
|
|
}
|
|
|
+ resolve(true)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-function deletePic({ file, index, name }) {
|
|
|
+function deletePic({ file, index }) {
|
|
|
uni.showModal({
|
|
|
- cancelText: '取消',
|
|
|
- confirmText: '删除',
|
|
|
- content: '是否确定删除该文件?',
|
|
|
title: '提示',
|
|
|
+ content: '确定删除?',
|
|
|
success: ({ confirm }) => {
|
|
|
- if (confirm) {
|
|
|
- console.log("删除文件", file);
|
|
|
- if (file.ownertable == 'temporary') {
|
|
|
- // 临时文件直接删除
|
|
|
- deleteFile([file]).then(res => {
|
|
|
- if (res) {
|
|
|
- props.fileList.splice(index, 1);
|
|
|
- emit('uploadCallback', { fileList: props.fileList });
|
|
|
- }
|
|
|
- })
|
|
|
- } else {
|
|
|
- deleteList.push(file)
|
|
|
- props.fileList.splice(index, 1);
|
|
|
+ if (!confirm) return
|
|
|
+ if (file.ownertable === 'temporary') {
|
|
|
+ deleteFile([file]).then(() => {
|
|
|
+ props.fileList.splice(index, 1)
|
|
|
emit('uploadCallback', { fileList: props.fileList })
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-// 直接删除文件
|
|
|
-const deleteFile = (arr) => {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- let list = arr.filter(file => file.linksid);
|
|
|
- if (list.length) {
|
|
|
- $Http.basic({
|
|
|
- "classname": "system.attachment.Attachment",
|
|
|
- "method": "deleteFileLink",
|
|
|
- "content": {
|
|
|
- linksids: list.map(v => v.linksid),
|
|
|
- }
|
|
|
- }).then(res => {
|
|
|
- console.log("删除文件", res);
|
|
|
- resolve(res.code == 1)
|
|
|
- if (res.code != 1) uni.showToast({
|
|
|
- title: res.msg,
|
|
|
- icon: "none"
|
|
|
})
|
|
|
- })
|
|
|
- } else {
|
|
|
- resolve(true)
|
|
|
+ } else {
|
|
|
+ deleteList.push(file)
|
|
|
+ props.fileList.splice(index, 1)
|
|
|
+ emit('uploadCallback', { fileList: props.fileList })
|
|
|
+ }
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-// 清空临时文件 ownertable == 'temporary'
|
|
|
-const clearTemporaryFiles = (arr = props.fileList) => {
|
|
|
- let list = arr.filter(file => file.ownertable == 'temporary' && file.linksid);
|
|
|
- if (list.length) $Http.basic({
|
|
|
- "classname": "system.attachment.Attachment",
|
|
|
- "method": "deleteFileLink",
|
|
|
- "content": {
|
|
|
- linksids: list.map(v => v.linksid),
|
|
|
- }
|
|
|
- }).then(res => {
|
|
|
- console.log("清空临时文件", res);
|
|
|
+const deleteFile = (arr) =>
|
|
|
+ new Promise(resolve => {
|
|
|
+ const list = arr.filter(file => file.linksid)
|
|
|
+ if (!list.length) return resolve(true)
|
|
|
+ $Http.basic({
|
|
|
+ classname: 'system.attachment.Attachment',
|
|
|
+ method: 'deleteFileLink',
|
|
|
+ content: { linksids: list.map(v => v.linksid) }
|
|
|
+ }).then(res => resolve(res.code === 1))
|
|
|
})
|
|
|
-}
|
|
|
|
|
|
-// 在页面销毁的时候 自动清空所有的临时文件
|
|
|
-onUnmounted(() => {
|
|
|
- // 清理压缩产生的临时URL(H5)
|
|
|
- // #ifdef H5
|
|
|
- props.fileList.forEach(file => {
|
|
|
- if (file.compressed && file.url.startsWith('blob:')) {
|
|
|
- URL.revokeObjectURL(file.url);
|
|
|
- }
|
|
|
- });
|
|
|
- // #endif
|
|
|
-
|
|
|
- clearTemporaryFiles();
|
|
|
-})
|
|
|
+onUnmounted(() => {})
|
|
|
|
|
|
-defineExpose({ isUploading, handleFileLink, saveFileLinks, deleteFile })
|
|
|
-</script>
|
|
|
+defineExpose({
|
|
|
+ isUploading,
|
|
|
+ handleFileLink,
|
|
|
+ saveFileLinks,
|
|
|
+ deleteFile
|
|
|
+})
|
|
|
+</script>
|