upload-file.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. <template>
  2. <view class="upload_box">
  3. <view class="upload_list">
  4. <view v-for="(item, index) in fileList" :key="index" class="flex_box upload_item">
  5. <view @click="$openFile(item.url)" class="flex_box upload_left">
  6. <u-image
  7. width="60rpx"
  8. height="60rpx"
  9. :showError="false"
  10. :showLoading="false"
  11. :src="`${$IMG_URL}/static/format/${fileTypes.indexOf(item.type) === '-1' ? 'file' : item.type}.png`"
  12. ></u-image>
  13. <view class="upload_content">
  14. <view class="upload_name">{{ item.name }}</view>
  15. <view v-if="item.size" class="upload_text">{{ item.size }}</view>
  16. </view>
  17. </view>
  18. <u-image
  19. v-if="!isDetail"
  20. @click="deletePic(index)"
  21. width="48rpx"
  22. height="48rpx"
  23. :showError="false"
  24. :showLoading="false"
  25. :src="`${$IMG_URL}/static/home/icon_cancel@2x.png`"
  26. ></u-image>
  27. <view class="service_check">
  28. <u-icon color="#FD910C" size="24rpx" :name="item.status === 'success' ? 'checkmark-circle-fill' : 'close-circle-fill'"></u-icon>
  29. </view>
  30. </view>
  31. </view>
  32. <!-- #ifdef APP -->
  33. <view class="upload_btn_box" v-if="(!isDetail || limit === fileList.length) && fileShow">
  34. <template v-if="authList['WRITE_EXTERNAL_STORAGE'] && authList['CAMERA']">
  35. <lsj-upload
  36. ref="lsjUpload"
  37. :wxFileType="wxFileType"
  38. :extension="extension"
  39. :childId="childId"
  40. :width="width"
  41. :height="height"
  42. :option="option"
  43. :size="fileSize"
  44. :count="limit"
  45. :multiple="multiple"
  46. :formats="fileType"
  47. :accept="fileAccept"
  48. :debug="debug"
  49. :instantly="instantly"
  50. @uploadEnd="onuploadEnd"
  51. @progress="onprogre"
  52. @change="change"
  53. >
  54. <view class="upload_btn">
  55. <u-image :width="width" :height="height" :showError="false" :showLoading="false" :src="`${$IMG_URL}/static/contract/icon_increase2-2.png`"></u-image>
  56. </view>
  57. </lsj-upload>
  58. </template>
  59. <view v-else-if="!authList['WRITE_EXTERNAL_STORAGE']" class="upload_btn_auth" @tap.stop="openAuth('WRITE_EXTERNAL_STORAGE')">
  60. <view class="upload_btn">
  61. <u-image :width="width" :height="height" :showError="false" :showLoading="false" :src="`${$IMG_URL}/static/contract/icon_increase2-2.png`"></u-image>
  62. </view>
  63. </view>
  64. <view v-else class="upload_btn_auth" @tap.stop="openAuth('CAMERA')">
  65. <view class="upload_btn">
  66. <u-image :width="width" :height="height" :showError="false" :showLoading="false" :src="`${$IMG_URL}/static/contract/icon_increase2-2.png`"></u-image>
  67. </view>
  68. </view>
  69. </view>
  70. <yk-authpup ref="authpup" type="top" @changeAuth="changeAuth" :permissionID="permissionID"></yk-authpup>
  71. <!-- #endif -->
  72. <!-- #ifndef APP -->
  73. <view class="upload_btn_box" v-if="(!isDetail || limit === fileList.length) && fileShow">
  74. <lsj-upload
  75. ref="lsjUpload"
  76. :wxFileType="wxFileType"
  77. :extension="extension"
  78. :childId="childId"
  79. :width="width"
  80. :height="height"
  81. :option="option"
  82. :size="fileSize"
  83. :count="limit"
  84. :multiple="multiple"
  85. :formats="fileType"
  86. :accept="fileAccept"
  87. :debug="debug"
  88. :instantly="instantly"
  89. @uploadEnd="onuploadEnd"
  90. @progress="onprogre"
  91. @change="change"
  92. >
  93. <view class="upload_btn">
  94. <u-image :width="width" :height="height" :showError="false" :showLoading="false" :src="`${$IMG_URL}/static/contract/icon_increase2-2.png`"></u-image>
  95. </view>
  96. </lsj-upload>
  97. </view>
  98. <!-- #endif -->
  99. </view>
  100. </template>
  101. <script>
  102. import { mapGetters } from 'vuex'
  103. import ykAuthpup from '@/components/yk-authpup/yk-authpup'
  104. import { getFileName } from '@/common/request/apis/index'
  105. export default {
  106. name: 'UploadFile',
  107. components: {
  108. ykAuthpup
  109. },
  110. props: {
  111. // 值
  112. value: [String, Object, Array],
  113. // 宽度
  114. width: {
  115. type: String,
  116. default: '104rpx'
  117. },
  118. // 高度
  119. height: {
  120. type: String,
  121. default: '104rpx'
  122. },
  123. // 数量限制
  124. limit: {
  125. type: Number,
  126. default: 9
  127. },
  128. // 大小限制(MB)
  129. fileSize: {
  130. type: Number,
  131. default: 200
  132. },
  133. // 文件类型, 例如'png, jpg, jpeg']
  134. fileType: {
  135. type: String,
  136. default: ''
  137. },
  138. // 文件类型, 例如'audio/*, video/*, image/*']
  139. fileAccept: {
  140. type: String,
  141. default: '*'
  142. },
  143. // 是否文件覆盖图片
  144. isOne: {
  145. type: Boolean,
  146. default: false
  147. },
  148. // 强制刷新
  149. fileShow: {
  150. type: Boolean,
  151. default: true
  152. },
  153. // 是否多选
  154. multiple: {
  155. type: Boolean,
  156. default: true
  157. },
  158. // 是否显示提示
  159. isShowTip: {
  160. type: Boolean,
  161. default: true
  162. },
  163. // 是否详情
  164. isDetail: {
  165. type: Boolean,
  166. default: false
  167. },
  168. //id
  169. childId: {
  170. type: String,
  171. default: 'lsjUpload'
  172. },
  173. // 微信选择文件类型
  174. //all=从所有文件选择,
  175. //video=只能选择视频文件,
  176. //image=只能选择图片文件,
  177. //file=可以选择除了图片和视频之外的其它的文件
  178. wxFileType: { type: String, default: 'all' },
  179. extension: { type: Array, default: () => ['*'] }
  180. },
  181. data() {
  182. return {
  183. fileTypes: ['bmp', 'doc', 'docx', 'gif', 'jpeg', 'jpg', 'mp3', 'mp4', 'pdf', 'png', 'ppt', 'pptx', 'rar', 'wav', 'webm', 'webp', 'xls', 'xlsx', 'zip'],
  184. // 上传接口参数
  185. option: {
  186. // 上传服务器地址,需要替换为你的接口地址
  187. url: this.$UPLOAD_URL, // 该地址非真实路径,需替换为你项目自己的接口地址
  188. // 上传附件的key
  189. name: 'file',
  190. // 根据你接口需求自定义请求头,默认不要写content-type,让浏览器自适配
  191. header: {
  192. token: `${this.$store.getters.token}`
  193. },
  194. // 根据你接口需求自定义body参数
  195. formData: {
  196. filename: ''
  197. }
  198. },
  199. // 选择文件后是否立即自动上传,true=选择后立即上传
  200. instantly: true,
  201. // 文件回显列表
  202. files: new Map(),
  203. // 微信小程序Map对象for循环不显示,所以转成普通数组,不要问为什么,我也不知道
  204. wxFiles: [],
  205. // 是否打印日志
  206. debug: false,
  207. // 文件显示列表
  208. fileList: [],
  209. // 权限判断
  210. permissionID: ''
  211. }
  212. },
  213. watch: {
  214. value: {
  215. async handler(val) {
  216. if (val) {
  217. // if (this.fileList.length > 0) return
  218. // 首先将值转为数组
  219. const list = Array.isArray(val) ? val : this.value.split(',')
  220. const files = []
  221. // 然后将数组转为对象数组
  222. for (let i = 0; i < list.length; i++) {
  223. let item = list[i]
  224. if (typeof item === 'string') {
  225. let name = item + ''
  226. let size = ''
  227. if (name && name.lastIndexOf('/') > -1) {
  228. const result = await getFileName({
  229. file_name: name,
  230. type: 1
  231. })
  232. name = result && result.code === 1 ? result.data.filename : name
  233. size = result && result.code === 1 ? this.$filterSize(result.data.filesize) : size
  234. }
  235. item = {
  236. name: name,
  237. url: this.getUrl(item),
  238. fullurl: item,
  239. size: size,
  240. status: 'success',
  241. message: '',
  242. type: item.slice(item.lastIndexOf('.') + 1).toLowerCase()
  243. }
  244. }
  245. files.push(item)
  246. }
  247. this.fileList = files
  248. } else {
  249. this.fileList = []
  250. return []
  251. }
  252. },
  253. deep: true,
  254. immediate: true
  255. }
  256. },
  257. computed: {
  258. // 是否显示提示
  259. showTip() {
  260. return this.isShowTip && (this.fileType || this.fileSize)
  261. },
  262. ...mapGetters('auth', ['authList', 'onceIn'])
  263. },
  264. mounted() {
  265. // #ifdef APP
  266. // if (this.onceIn) {
  267. // this.$store.commit('auth/edit', {data: false, index: 'onceIn'})
  268. // this.openAuth('WRITE_EXTERNAL_STORAGE')
  269. // }
  270. // #endif
  271. },
  272. methods: {
  273. //打开自定义权限目的弹框
  274. openAuth(permissionID) {
  275. this.permissionID = permissionID //这个是对应的权限 ACCESS_FINE_LOCATION 位置权限 / WRITE_EXTERNAL_STORAGE 存储空间/照片权限 / CAMERA相机权限 / CALL_PHONE 拨打电话
  276. setTimeout(() => {
  277. this.$refs['authpup'].open()
  278. }, 500)
  279. },
  280. //用户授权权限后的回调
  281. changeAuth() {
  282. //这里是权限通过后执行自己的代码逻辑
  283. console.log('权限已授权,可执行自己的代码逻辑了')
  284. this.authList[this.permissionID] = true
  285. const list = { ...this.authList }
  286. this.$store.commit('auth/edit', { data: list, index: 'authList' })
  287. if (!this.authList['CAMERA']) {
  288. this.openAuth('CAMERA')
  289. }
  290. },
  291. // 手动触发
  292. actionClick() {
  293. // 强制更新视图
  294. this.$forceUpdate()
  295. console.log(this.extension)
  296. this.$nextTick(() => {
  297. this.$refs.lsjUpload.onClick()
  298. })
  299. },
  300. // 刷新组件
  301. refresh() {
  302. this.$refs.lsjUpload.hide()
  303. this.$nextTick(() => {
  304. setTimeout(() => {
  305. this.$refs.lsjUpload.show()
  306. }, 200)
  307. })
  308. },
  309. /**
  310. * 某文件上传结束回调(成功失败都回调)
  311. * @param {Object} item 当前上传完成的文件
  312. */
  313. onuploadEnd(item) {
  314. if (item) {
  315. if (this.isOne) {
  316. this.files.clear()
  317. this.wxFiles = []
  318. this.fileList = []
  319. }
  320. // 更新当前窗口状态变化的文件
  321. this.files.set(item.name, item)
  322. this.wxFiles = [...this.files.values()]
  323. // 强制更新视图
  324. this.$forceUpdate()
  325. const isHas = this.fileList.find((file) => file.name === item.name)
  326. if (item.responseText && !isHas) {
  327. const result = JSON.parse(item.responseText)
  328. if (result.code === 1) {
  329. this.fileList.push({
  330. status: 'success',
  331. name: item.name,
  332. size: this.$filterSize(item.size),
  333. message: '',
  334. url: result.data.fullurl,
  335. fullurl: result.data.url,
  336. type: result.data.url.slice(result.data.url.lastIndexOf('.') + 1).toLowerCase()
  337. })
  338. } else {
  339. uni.showModal({
  340. content: `${item.name}上传失败,请重新上传`,
  341. showCancel: false
  342. })
  343. }
  344. }
  345. }
  346. let isAll = [...this.files.values()].find((item) => item.type !== 'success')
  347. if (!isAll) {
  348. this.setUrl()
  349. }
  350. },
  351. /**
  352. * 上传进度回调
  353. * 如果网页上md文档没有渲染出事件名称onprogre,请复制代码的小伙伴自行添加上哈,没有哪个事件是只(item)的
  354. * @param {Object} item 当前正在上传的文件
  355. */
  356. onprogre(item) {
  357. // 更新当前状态变化的文件
  358. this.files.set(item.name, item)
  359. // 微信小程序Map对象for循环不显示,所以转成普通数组,不要问为什么,我也不知道
  360. this.wxFiles = [...this.files.values()]
  361. // 强制更新视图
  362. this.$forceUpdate()
  363. },
  364. /**
  365. * 文件选择回调
  366. * @param {Object} files 已选择的所有文件Map集合
  367. */
  368. change(files) {
  369. const list = [...files.values()]
  370. if (list.length === 0) return
  371. uni.showLoading({
  372. title: '上传中',
  373. mask: true
  374. })
  375. // 更新选择的文件
  376. this.files = files
  377. // 强制更新视图
  378. this.$forceUpdate()
  379. this.wxFiles = [...list]
  380. },
  381. /**
  382. * 移除某个文件
  383. * @param {Object} name 带后缀名的文件名称
  384. */
  385. deletePic(index) {
  386. // name=指定文件名,不传name默认移除所有文件
  387. this.$refs.lsjUpload.clear(this.fileList[index].name)
  388. this.fileList.splice(index, 1)
  389. this.setUrl()
  390. },
  391. // 设置图片
  392. setUrl() {
  393. let that = this
  394. let urls = []
  395. let list = []
  396. for (let i = 0; i < that.fileList.length; i++) {
  397. if (that.fileList[i].status === 'success') {
  398. urls.push(that.fileList[i].fullurl)
  399. list.push({
  400. url: that.fileList[i].fullurl,
  401. fullurl: that.fileList[i].url,
  402. index: i,
  403. name: that.fileList[i].name
  404. })
  405. }
  406. }
  407. that.$emit('input', urls.join(','))
  408. that.$emit('change', list)
  409. uni.hideLoading()
  410. },
  411. // 设置域名
  412. getUrl(url) {
  413. if (url.indexOf('http') === -1) url = this.$UPLOAD_BASE + url
  414. return url
  415. }
  416. }
  417. }
  418. </script>
  419. <style lang="scss" scoped>
  420. .upload_box {
  421. position: relative;
  422. .upload_btn_box {
  423. position: relative;
  424. z-index: 99;
  425. .upload_btn_auth {
  426. width: 96rpx;
  427. height: 96rpx;
  428. }
  429. .upload_btn {
  430. width: 96rpx;
  431. height: 96rpx;
  432. position: relative;
  433. }
  434. }
  435. .upload_list {
  436. .upload_item {
  437. background: rgba(134, 158, 180, 0.1);
  438. border-radius: 8rpx;
  439. padding: 8rpx 24rpx 8rpx 8rpx;
  440. margin-bottom: 24rpx;
  441. position: relative;
  442. .upload_left {
  443. width: calc(100% - 48rpx);
  444. padding-left: 16rpx;
  445. }
  446. .upload_content {
  447. width: calc(100% - 96rpx);
  448. padding: 0 16rpx;
  449. .upload_name {
  450. width: 100%;
  451. word-break: break-all;
  452. font-size: 28rpx;
  453. color: #333333;
  454. line-height: 40rpx;
  455. margin-bottom: 8rpx;
  456. }
  457. .upload_text {
  458. font-size: 24rpx;
  459. color: #999999;
  460. line-height: 34rpx;
  461. }
  462. }
  463. .service_check {
  464. position: absolute;
  465. top: 0;
  466. right: 0;
  467. z-index: 10;
  468. text-align: center;
  469. padding: 6rpx;
  470. }
  471. }
  472. }
  473. }
  474. </style>