My_upload.vue 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. <template>
  2. <view v-if="custom">
  3. <up-upload @after-read="afterRead" :deletable="!disabled" @delete="deletePic"
  4. :max-count="disabled ? fileList.length : maxCount" :accept="accept" multiple @clickPreview="clickPreview">
  5. <slot />
  6. </up-upload>
  7. </view>
  8. <up-upload v-else :fileList="fileList" @after-read="afterRead" :deletable="!disabled" @delete="deletePic"
  9. :max-count="disabled ? fileList.length : maxCount" :accept="accept" multiple @clickPreview="clickPreview" />
  10. </template>
  11. <script setup>
  12. import { ref, reactive, defineProps, defineEmits, getCurrentInstance, onUnmounted } from 'vue'
  13. const emit = defineEmits(['uploadCallback', 'startUploading'])
  14. const props = defineProps({
  15. accept: {
  16. type: String,
  17. default: "image"
  18. },
  19. maxCount: {
  20. type: [String, Number],
  21. default: 99
  22. },
  23. uploadCallback: {
  24. type: Function
  25. },
  26. startUploading: {
  27. type: Function
  28. },
  29. fileList: {
  30. type: Array,
  31. default: reactive([])
  32. },
  33. usetype: {
  34. type: String,
  35. default: 'default'
  36. },
  37. ownertable: {
  38. type: String,
  39. default: 'temporary'
  40. },
  41. ownerid: {
  42. type: [String, Number],
  43. default: 1
  44. },
  45. disabled: {
  46. type: Boolean,
  47. default: false
  48. },
  49. custom: {
  50. type: Boolean,
  51. default: false
  52. }
  53. })
  54. const { $Http } = getCurrentInstance().proxy;
  55. const deleteList = reactive([]); // 用于存储待删除的文件列表
  56. const clickPreview = (e) => {
  57. uni.previewImage({
  58. urls: props.fileList.map(v => v.url),
  59. current: e.url,
  60. loop: true,
  61. })
  62. }
  63. // 文件读取后处理
  64. const afterRead = ({ file }) => {
  65. emit('startUploading', file);
  66. file.forEach(v => {
  67. // #ifdef H5
  68. getArrayBuffer(v).then(data => {
  69. data.data.url = v.url;
  70. console.log("data.data", data.data)
  71. handleUploadFile(requestType(v), data.data)
  72. props.fileList.push({
  73. ...v,
  74. status: 'uploading',
  75. message: '上传中',
  76. });
  77. })
  78. // #endif
  79. // #ifndef H5
  80. uni.getFileSystemManager().readFile({
  81. filePath: v.url,
  82. success: data => {
  83. data.data.url = v.url;
  84. handleUploadFile(requestType(v), data.data)
  85. props.fileList.push({
  86. ...v,
  87. status: 'uploading',
  88. message: '上传中',
  89. });
  90. },
  91. fail: console.error
  92. })
  93. // #endif
  94. })
  95. }
  96. // 获取文件类型信息
  97. const requestType = (file) => {
  98. let ext = ''
  99. // #ifdef H5
  100. ext = file.name.substring(file.name.lastIndexOf(".") + 1)
  101. // #endif
  102. // #ifndef H5
  103. ext = file.type?.split("/")[1] ||
  104. file.url.substring(file.url.lastIndexOf(".") + 1) ||
  105. file.name.substring(file.name.lastIndexOf(".") + 1)
  106. // #endif
  107. return {
  108. id: '10019701',
  109. "content": {
  110. "filename": `${Date.now() + file.size}.${ext}`,
  111. "filetype": ext,
  112. "parentid": uni.getStorageSync('WsiteP').appfolderid
  113. }
  114. }
  115. }
  116. // 处理文件上传
  117. const handleUploadFile = (file, data) => {
  118. $Http.basic(file).then(res => {
  119. console.log("上传文件成功", res)
  120. if (res.msg == "成功") {
  121. uploadFile(res.data, data)
  122. } else {
  123. uni.showToast({
  124. title: `${file.content.filename}上传失败`,
  125. icon: "none"
  126. })
  127. }
  128. })
  129. }
  130. // 获取ArrayBuffer (H5专用)
  131. const getArrayBuffer = (file) => {
  132. return new Promise((resolve, reject) => {
  133. const xhr = new XMLHttpRequest()
  134. xhr.open('GET', file.url, true)
  135. xhr.responseType = 'blob'
  136. xhr.onload = function () {
  137. if (this.status === 200) {
  138. const myBlob = this.response
  139. const files = new File(
  140. [myBlob],
  141. file.name,
  142. { type: file.type },
  143. )
  144. const reader = new FileReader()
  145. reader.readAsArrayBuffer(files)
  146. reader.onload = () => resolve({
  147. data: reader.result
  148. })
  149. reader.onerror = error => reject(error)
  150. } else {
  151. reject(`文件加载失败: ${this.status}`)
  152. }
  153. }
  154. xhr.onerror = () => reject('网络请求失败')
  155. xhr.send()
  156. })
  157. }
  158. // 上传文件到服务器
  159. const uploadFile = (res, data) => {
  160. uni.request({
  161. url: res.uploadurl,
  162. method: "PUT",
  163. data,
  164. header: { 'Content-Type': 'application/octet-stream' },
  165. success: () => {
  166. $Http.basic({
  167. id: 10019901,
  168. "content": { "serialfilename": res.serialfilename }
  169. }).then(s => {
  170. console.log("文件上传反馈", s)
  171. handleFileLink([{
  172. attachmentid: s.data.attachmentids[0],
  173. url: data.url
  174. }], "temporary", 1, props.usetype)
  175. }).catch(console.error)
  176. },
  177. fail: console.error
  178. })
  179. }
  180. function handleFileLink(list, ownertable = "temporary", ownerid = 1, usetype = 'default', resolve = () => { }) {
  181. if (list.length == 0) return resolve(true);
  182. let content = {
  183. ownertable,
  184. ownerid,
  185. usetype,
  186. attachmentids: list.map(v => v.attachmentid),
  187. siteid: uni.getStorageSync("WuserMsg").siteid
  188. }
  189. $Http.basic({
  190. "classname": "system.attachment.Attachment",
  191. "method": "createFileLink",
  192. content
  193. }).then(res => {
  194. console.log('跟进记录绑定附件', res)
  195. resolve(res.code == '1')
  196. if (res.code != '1') return uni.showToast({
  197. title: res.msg,
  198. icon: "none"
  199. })
  200. list.forEach(v => {
  201. const file = props.fileList.find(s => v.url === s.url || v.url === s.thumb);
  202. if (file) {
  203. delete file.status;
  204. delete file.message;
  205. Object.assign(file, res.data.find(s => s.attachmentid === v.attachmentid));
  206. }
  207. });
  208. emit('uploadCallback', { fileList: props.fileList, attachmentids: content.attachmentids })
  209. })
  210. }
  211. // 保存所有的附件绑定到表上,有在上传的文件不能保存
  212. const isUploading = (showToast = true) => {
  213. let res = props.fileList.some(file => file.status === 'uploading');
  214. if (res && showToast) uni.showToast({
  215. title: '文件正在上传中,请稍后再试',
  216. icon: 'none'
  217. });
  218. return res
  219. }
  220. // 保存接口 接受数据调用handleFileLink
  221. const saveFileLinks = (ownertable, ownerid, usetype = 'default') => {
  222. // 如果有待删除的文件,先删除
  223. deleteList.length && deleteFile(deleteList);
  224. return new Promise((resolve, reject) => {
  225. const list = props.fileList;
  226. console.log("list", list)
  227. if (list.length) {
  228. return handleFileLink(list, ownertable, ownerid, usetype, resolve);
  229. } else {
  230. resolve(true)
  231. }
  232. })
  233. }
  234. function deletePic({ file, index, name }) {
  235. uni.showModal({
  236. cancelText: '取消',
  237. confirmText: '删除',
  238. content: '是否确定删除该文件?',
  239. title: '提示',
  240. success: ({ confirm }) => {
  241. if (confirm) {
  242. console.log("删除文件", file);
  243. if (file.ownertable == 'temporary') {
  244. // 临时文件直接删除
  245. deleteFile([file]).then(res => {
  246. if (res) {
  247. props.fileList.splice(index, 1);
  248. emit('uploadCallback', { fileList: props.fileList });
  249. }
  250. })
  251. } else {
  252. deleteList.push(file)
  253. props.fileList.splice(index, 1);
  254. emit('uploadCallback', { fileList: props.fileList })
  255. }
  256. }
  257. },
  258. })
  259. }
  260. // 直接删除文件
  261. const deleteFile = (arr) => {
  262. return new Promise((resolve, reject) => {
  263. let list = arr.filter(file => file.linksid);
  264. if (list.length) {
  265. $Http.basic({
  266. "classname": "system.attachment.Attachment",
  267. "method": "deleteFileLink",
  268. "content": {
  269. linksids: list.map(v => v.linksid),
  270. }
  271. }).then(res => {
  272. console.log("删除文件", res);
  273. resolve(res.code == 1)
  274. if (res.code != 1) uni.showToast({
  275. title: res.msg,
  276. icon: "none"
  277. })
  278. })
  279. } else {
  280. resolve(true)
  281. }
  282. })
  283. }
  284. // 清空临时文件 ownertable == 'temporary'
  285. const clearTemporaryFiles = (arr = props.fileList) => {
  286. let list = arr.filter(file => file.ownertable == 'temporary' && file.linksid);
  287. if (list.length) $Http.basic({
  288. "classname": "system.attachment.Attachment",
  289. "method": "deleteFileLink",
  290. "content": {
  291. linksids: list.map(v => v.linksid),
  292. }
  293. }).then(res => {
  294. console.log("清空临时文件", res);
  295. })
  296. }
  297. // 在页面销毁的时候 自动清空所有的临时文件
  298. onUnmounted(() => {
  299. clearTemporaryFiles();
  300. })
  301. defineExpose({ isUploading, handleFileLink, saveFileLinks, deleteFile })
  302. </script>