My_upload.vue 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. <template>
  2. <view v-if="custom">
  3. <up-upload
  4. @after-read="afterRead"
  5. :deletable="!disabled"
  6. @delete="deletePic"
  7. :max-count="disabled ? fileList.length : maxCount"
  8. :accept="accept"
  9. multiple
  10. @clickPreview="clickPreview"
  11. >
  12. <slot />
  13. </up-upload>
  14. </view>
  15. <up-upload
  16. v-else
  17. :fileList="fileList"
  18. @after-read="afterRead"
  19. :deletable="!disabled"
  20. @delete="deletePic"
  21. :max-count="disabled ? fileList.length : maxCount"
  22. :accept="accept"
  23. multiple
  24. @clickPreview="clickPreview"
  25. />
  26. </template>
  27. <script setup>
  28. import { reactive, defineProps, defineEmits, getCurrentInstance, onUnmounted } from 'vue'
  29. /* ================= emits / props ================= */
  30. const emit = defineEmits(['uploadCallback', 'startUploading'])
  31. const props = defineProps({
  32. accept: { type: String, default: 'image' },
  33. maxCount: { type: [String, Number], default: 99 },
  34. uploadCallback: { type: Function },
  35. startUploading: { type: Function },
  36. fileList: { type: Array, default: reactive([]) },
  37. usetype: { type: String, default: 'default' },
  38. ownertable: { type: String, default: 'temporary' },
  39. ownerid: { type: [String, Number], default: 1 },
  40. disabled: { type: Boolean, default: false },
  41. custom: { type: Boolean, default: false },
  42. compressConfig: {
  43. type: Object,
  44. default: () => ({
  45. enable: true,
  46. threshold: 300 * 1024,
  47. targetSize: 100 * 1024,
  48. maxWidth: 2560,
  49. maxHeight: 2560,
  50. quality: 0.6
  51. })
  52. }
  53. })
  54. const { $Http } = getCurrentInstance().proxy
  55. const deleteList = reactive([])
  56. /* ================= 工具 ================= */
  57. const isImage = (file) =>
  58. file.type?.startsWith('image/') ||
  59. /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name || file.url)
  60. /* ================= 压缩 ================= */
  61. const compressImageNative = (file, quality) =>
  62. new Promise(resolve => {
  63. uni.compressImage({
  64. src: file.url,
  65. quality: Math.floor(quality * 100),
  66. success: res => resolve(res.tempFilePath),
  67. fail: () => resolve(file.url)
  68. })
  69. })
  70. const getFileSize = (filePath) =>
  71. new Promise(resolve => {
  72. uni.getFileInfo({
  73. filePath,
  74. success: res => resolve(res.size),
  75. fail: () => resolve(0)
  76. })
  77. })
  78. const compressFile = async (file) => {
  79. if (!props.compressConfig.enable || !isImage(file)) return file
  80. const size = file.size || 0
  81. if (size && size <= props.compressConfig.threshold) return file
  82. const quality = Math.min(
  83. Math.max((props.compressConfig.targetSize / size) * 0.5, 0.1),
  84. 0.5
  85. )
  86. const newUrl = await compressImageNative(file, quality)
  87. const finalSize = await getFileSize(newUrl)
  88. return { ...file, url: newUrl, size: finalSize }
  89. }
  90. /* ================= afterRead ================= */
  91. const afterRead = async ({ file }) => {
  92. emit('startUploading', file)
  93. const files = Array.isArray(file) ? file : [file]
  94. for (const item of files) {
  95. try {
  96. const processedFile = await compressFile(item)
  97. // ★ 修复点 1:生成稳定 uid(不影响原逻辑)
  98. const uid = `${Date.now()}_${Math.random()}`
  99. processedFile.uid = uid
  100. const executeUpload = (dataValue, finalUrl) => {
  101. const fileConf = requestType(processedFile)
  102. handleUploadFile(fileConf, dataValue, finalUrl, uid)
  103. props.fileList.push({
  104. ...processedFile,
  105. uid,
  106. url: finalUrl,
  107. status: 'uploading',
  108. message: '上传中'
  109. })
  110. }
  111. uni.getFileSystemManager().readFile({
  112. filePath: processedFile.url,
  113. success: (data) => executeUpload(data.data, processedFile.url),
  114. fail: console.error
  115. })
  116. } catch (e) {
  117. console.error('上传失败:', e)
  118. }
  119. }
  120. }
  121. /* ================= 请求配置 ================= */
  122. const requestType = (file) => {
  123. const ext = file.url.substring(file.url.lastIndexOf('.') + 1) || 'jpg'
  124. return {
  125. id: '10019701',
  126. content: {
  127. filename: `${Date.now()}.${ext}`,
  128. filetype: ext,
  129. parentid: uni.getStorageSync('siteP')?.appfolderid || ''
  130. }
  131. }
  132. }
  133. /* ================= 上传 ================= */
  134. const handleUploadFile = (fileConfig, data, localUrl, uid) => {
  135. $Http.basic(fileConfig).then(res => {
  136. if (res.msg === '成功') {
  137. uploadFile(res.data, data, localUrl, uid)
  138. }
  139. })
  140. }
  141. const uploadFile = (res, data, localUrl, uid) => {
  142. uni.request({
  143. url: res.uploadurl,
  144. method: 'PUT',
  145. data,
  146. header: { 'Content-Type': 'application/octet-stream' },
  147. success: () => {
  148. $Http.basic({
  149. id: 10019901,
  150. content: { serialfilename: res.serialfilename }
  151. }).then(s => {
  152. // ★ 修复点 2:把 uid 透传下去
  153. handleFileLink(
  154. [{ attachmentid: s.data.attachmentids[0], url: localUrl, uid }],
  155. props.ownertable,
  156. props.ownerid,
  157. props.usetype
  158. )
  159. })
  160. }
  161. })
  162. }
  163. /* ================= 文件关联(核心修复在这里) ================= */
  164. function handleFileLink(list, ownertable = 'temporary', ownerid = 1, usetype = 'default', resolve = () => {}) {
  165. if (!list.length) return resolve(true)
  166. const content = {
  167. ownertable,
  168. ownerid,
  169. usetype,
  170. attachmentids: list.map(v => v.attachmentid),
  171. siteid: uni.getStorageSync('userMsg').siteid
  172. }
  173. $Http.basic({
  174. classname: 'system.attachment.Attachment',
  175. method: 'createFileLink',
  176. content
  177. }).then(res => {
  178. resolve(res.code === '1')
  179. list.forEach(v => {
  180. // ★ 修复点 3:不再用 url,用 uid 找文件(真机关键)
  181. const file = props.fileList.find(s => s.uid === v.uid)
  182. if (file) {
  183. // ★ 修复点 4:不 delete,明确设置 success
  184. file.status = 'success'
  185. file.message = ''
  186. Object.assign(file, res.data.find(s => s.attachmentid === v.attachmentid))
  187. }
  188. })
  189. emit('uploadCallback', {
  190. fileList: props.fileList,
  191. attachmentids: content.attachmentids
  192. })
  193. })
  194. }
  195. /* ================= 业务方法(原样保留) ================= */
  196. const isUploading = (showToast = true) => {
  197. const res = props.fileList.some(file => file.status === 'uploading')
  198. if (res && showToast) {
  199. uni.showToast({ title: '文件正在上传中', icon: 'none' })
  200. }
  201. return res
  202. }
  203. const saveFileLinks = (ownertable, ownerid, usetype = 'default') => {
  204. deleteList.length && deleteFile(deleteList)
  205. return new Promise((resolve) => {
  206. if (props.fileList.length) {
  207. return handleFileLink(props.fileList, ownertable, ownerid, usetype, resolve)
  208. }
  209. resolve(true)
  210. })
  211. }
  212. function deletePic({ file, index }) {
  213. uni.showModal({
  214. title: '提示',
  215. content: '确定删除?',
  216. success: ({ confirm }) => {
  217. if (!confirm) return
  218. if (file.ownertable === 'temporary') {
  219. deleteFile([file]).then(() => {
  220. props.fileList.splice(index, 1)
  221. emit('uploadCallback', { fileList: props.fileList })
  222. })
  223. } else {
  224. deleteList.push(file)
  225. props.fileList.splice(index, 1)
  226. emit('uploadCallback', { fileList: props.fileList })
  227. }
  228. }
  229. })
  230. }
  231. const deleteFile = (arr) =>
  232. new Promise(resolve => {
  233. const list = arr.filter(file => file.linksid)
  234. if (!list.length) return resolve(true)
  235. $Http.basic({
  236. classname: 'system.attachment.Attachment',
  237. method: 'deleteFileLink',
  238. content: { linksids: list.map(v => v.linksid) }
  239. }).then(res => resolve(res.code === 1))
  240. })
  241. onUnmounted(() => {})
  242. defineExpose({
  243. isUploading,
  244. handleFileLink,
  245. saveFileLinks,
  246. deleteFile
  247. })
  248. </script>