u-upload.vue 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925
  1. <template>
  2. <view class="u-upload" :style="[addStyle(customStyle)]">
  3. <view class="u-upload__wrap" >
  4. <template v-if="previewImage">
  5. <view
  6. class="u-upload__wrap__preview"
  7. v-for="(item, index) in lists"
  8. :key="index"
  9. >
  10. <image
  11. v-if="item.isImage || (item.type && item.type === 'image')"
  12. :src="item.thumb || item.url"
  13. :mode="imageMode"
  14. class="u-upload__wrap__preview__image"
  15. @tap="onClickPreview(item, index)"
  16. :style="[{
  17. width: addUnit(width),
  18. height: addUnit(height)
  19. }]"
  20. />
  21. <view class="u-upload__wrap__preview__video"
  22. :style="{
  23. width: addUnit(width),
  24. height: addUnit(height)
  25. }"
  26. v-else-if="(item.isVideo || (item.type && item.type === 'video')) && getVideoThumb">
  27. <image
  28. v-if="item.thumb"
  29. :src="item.thumb"
  30. :mode="imageMode"
  31. class="u-upload__wrap__preview__image"
  32. @tap="onClickPreview(item, index)"
  33. :style="[{
  34. width: addUnit(width),
  35. height: addUnit(height)
  36. }]"
  37. />
  38. <u-icon
  39. v-else
  40. color="#80CBF9"
  41. size="26"
  42. :name="item.isVideo || (item.type && item.type === 'video') ? 'movie' : 'file-text'"
  43. ></u-icon>
  44. <view v-if="item.status === 'success'"
  45. class="u-upload__wrap__play"
  46. @tap="onClickPreview(item, index)">
  47. <slot name="playIcon"></slot>
  48. <up-icon v-if="!$slots['playIcon']"
  49. class="u-upload__wrap__play__icon"
  50. name="play-right" size="22px"></up-icon>
  51. </view>
  52. </view>
  53. <view
  54. v-else
  55. class="u-upload__wrap__preview__other"
  56. @tap="onClickPreview(item, index)"
  57. :style="[{
  58. width: addUnit(width),
  59. height: addUnit(height)
  60. }]"
  61. >
  62. <u-icon
  63. color="#80CBF9"
  64. size="26"
  65. :name="item.isVideo || (item.type && item.type === 'video') ? 'movie' : 'folder'"
  66. ></u-icon>
  67. <text class="u-upload__wrap__preview__other__text">
  68. {{item.isVideo || (item.type && item.type === 'video') ? item.name || '视频' : item.name || '文件'}}
  69. </text>
  70. </view>
  71. <view
  72. class="u-upload__status"
  73. v-if="item.status === 'uploading' || item.status === 'failed'"
  74. >
  75. <view class="u-upload__status__icon">
  76. <u-icon
  77. v-if="item.status === 'failed'"
  78. name="close-circle"
  79. color="#ffffff"
  80. size="25"
  81. />
  82. <u-loading-icon
  83. size="22"
  84. mode="circle"
  85. v-else
  86. />
  87. </view>
  88. <text
  89. v-if="item.message"
  90. class="u-upload__status__message"
  91. >{{ item.message }}</text>
  92. <up-gap class="u-upload__progress" height="3px"
  93. :style="{width: item.progress + '%'}"></up-gap>
  94. </view>
  95. <view
  96. class="u-upload__deletable"
  97. v-if="item.status !== 'uploading' && (deletable || item.deletable)"
  98. @tap.stop="deleteItem(index)"
  99. >
  100. <view class="u-upload__deletable__icon">
  101. <u-icon
  102. name="close"
  103. color="#ffffff"
  104. size="10"
  105. ></u-icon>
  106. </view>
  107. </view>
  108. <slot name="success">
  109. <view
  110. class="u-upload__success"
  111. v-if="item.status === 'success'"
  112. >
  113. <!-- #ifdef APP-NVUE -->
  114. <image
  115. :src="successIcon"
  116. class="u-upload__success__icon"
  117. ></image>
  118. <!-- #endif -->
  119. <!-- #ifndef APP-NVUE -->
  120. <view class="u-upload__success__icon">
  121. <u-icon
  122. name="checkmark"
  123. color="#ffffff"
  124. size="12"
  125. ></u-icon>
  126. </view>
  127. <!-- #endif -->
  128. </view>
  129. </slot>
  130. </view>
  131. </template>
  132. <canvas id="myCanvas" type="2d"
  133. style="width: 100px; height: 150px;display: none;"></canvas>
  134. <template v-if="isInCount">
  135. <view
  136. v-if="$slots.trigger"
  137. @tap="chooseFile"
  138. >
  139. <slot name="trigger" />
  140. </view>
  141. <view
  142. v-else-if="!$slots.trigger && ($slots.default || $slots.$default)"
  143. @tap="chooseFile"
  144. >
  145. <slot />
  146. </view>
  147. <view
  148. v-else
  149. class="u-upload__button"
  150. :hover-class="!disabled ? 'u-upload__button--hover' : ''"
  151. hover-stay-time="150"
  152. @tap="chooseFile"
  153. :class="[disabled && 'u-upload__button--disabled']"
  154. :style="[{
  155. width: addUnit(width),
  156. height: addUnit(height)
  157. }]"
  158. >
  159. <u-icon
  160. :name="uploadIcon"
  161. size="26"
  162. :color="uploadIconColor"
  163. ></u-icon>
  164. <text
  165. v-if="uploadText"
  166. class="u-upload__button__text"
  167. >{{ uploadText }}</text>
  168. </view>
  169. </template>
  170. </view>
  171. <up-popup
  172. mode="center"
  173. v-model:show="popupShow">
  174. <video id="myVideo" v-if="popupShow"
  175. :src="currentItemIndex >= 0 ? lists[currentItemIndex].url : ''"
  176. @error="videoErrorCallback" show-center-play-btn
  177. :object-fit='videoPreviewObjectFit' show-fullscreen-btn='true'
  178. enable-play-gesture controls
  179. :autoplay="true" auto-pause-if-open-native
  180. @loadedmetadata="loadedVideoMetadata"
  181. :initial-time='0.1'>
  182. </video>
  183. </up-popup>
  184. </view>
  185. </template>
  186. <script>
  187. import {
  188. chooseFile
  189. } from './utils';
  190. import { mixinUpload } from './mixin';
  191. import { props } from './props';
  192. import { mpMixin } from '../../libs/mixin/mpMixin';
  193. import { mixin } from '../../libs/mixin/mixin';
  194. import { addStyle, addUnit, toast } from '../../libs/function/index';
  195. import test from '../../libs/function/test';
  196. /**
  197. * upload 上传
  198. * @description 该组件用于上传图片场景
  199. * @tutorial https://uview-plus.jiangruyi.com/components/upload.html
  200. * @property {String} accept 接受的文件类型, 可选值为all media image file video (默认 'image' )
  201. * @property {String | Array} capture 图片或视频拾取模式,当accept为image类型时设置capture可选额外camera可以直接调起摄像头(默认 ['album', 'camera'] )
  202. * @property {Array} extension 选择文件的后缀名,暂只支持.zip、.png等,不支持application/msword等值
  203. * @property {Boolean} compressed 当accept为video时生效,是否压缩视频,默认为true(默认 true )
  204. * @property {String} camera 当accept为video时生效,可选值为back或front(默认 'back' )
  205. * @property {Number} maxDuration 当accept为video时生效,拍摄视频最长拍摄时间,单位秒(默认 60 )
  206. * @property {String} uploadIcon 上传区域的图标,只能内置图标(默认 'camera-fill' )
  207. * @property {String} uploadIconColor 上传区域的图标的字体颜色,只能内置图标(默认 #D3D4D6 )
  208. * @property {Boolean} useBeforeRead 是否开启文件读取前事件(默认 false )
  209. * @property {Boolean} previewFullImage 是否显示组件自带的图片预览功能(默认 true )
  210. * @property {String | Number} maxCount 最大上传数量(默认 52 )
  211. * @property {Boolean} disabled 是否启用(默认 false )
  212. * @property {String} imageMode 预览上传的图片时的裁剪模式,和image组件mode属性一致(默认 'aspectFill' )
  213. * @property {String} name 标识符,可以在回调函数的第二项参数中获取
  214. * @property {Array} sizeType 所选的图片的尺寸, 可选值为original compressed(默认 ['original', 'compressed'] )
  215. * @property {Boolean} multiple 是否开启图片多选,部分安卓机型不支持 (默认 false )
  216. * @property {Boolean} deletable 是否展示删除按钮(默认 true )
  217. * @property {String | Number} maxSize 文件大小限制,单位为byte (默认 Number.MAX_VALUE )
  218. * @property {Array} fileList 显示已上传的文件列表
  219. * @property {String} uploadText 上传区域的提示文字
  220. * @property {String | Number} width 内部预览图片区域和选择图片按钮的区域宽度(默认 80 )
  221. * @property {String | Number} height 内部预览图片区域和选择图片按钮的区域高度(默认 80 )
  222. * @property {Object} customStyle 组件的样式,对象形式
  223. * @event {Function} afterRead 读取后的处理函数
  224. * @event {Function} beforeRead 读取前的处理函数
  225. * @event {Function} oversize 文件超出大小限制
  226. * @event {Function} clickPreview 点击预览图片
  227. * @event {Function} delete 删除图片
  228. * @example <u-upload :action="action" :fileList="fileList" ></u-upload>
  229. */
  230. export default {
  231. name: "u-upload",
  232. mixins: [mpMixin, mixin, mixinUpload, props],
  233. data() {
  234. return {
  235. // #ifdef APP-NVUE
  236. successIcon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAKKADAAQAAAABAAAAKAAAAAB65masAAACP0lEQVRYCc3YXygsURwH8K/dpcWyG3LF5u/6/+dKVylSypuUl6uUPMifKMWL8oKEB1EUT1KeUPdR3uTNUsSLxb2udG/cbvInNuvf2rVnazZ/ZndmZ87snjM1Z+Z3zpzfp9+Z5mEAhlvjRtZgCKs+gnPAOcAkkMOR4jEHfItjDvgRxxSQD8cM0BuOCaAvXNCBQrigAsXgggYUiwsK0B9cwIH+4gIKlIILGFAqLiBAOTjFgXJxigJp4BQD0sIpAqSJow6kjSNAFTnRaHJwLenD6Mud52VQAcrBfTd2oyq+HtGaGGWAcnAVcXWoM3bCZrdi+ncPfaAcXE5UKVpdW/vitGPqqAtn98d0gXJwX7Qp6MmegUYVhvmTIezdmHlxJCjpHRTCFerLkRRu4k0aqdajN3sWOo0BK//msHa+xDuPC/oNFMKRhTtM4xjIX0SCNpXL4+7VIaHuyiWEp2L7ahWLf8fejfPdqPmC3mJicORZUp1CQzm+GiphvljGk+PBvWRbxii+xVTj5M6CiZ/tsDufvaXyxEUDxeLIyvu3m0iOyEFWVAkydcVYdyFrE9tQk9iMq6f/GNlvwt3LjQfh60LUrw9/cFyyMJUW/XkLSNMV4Mi6C5ML+ui4x5ClAX9sB9w0wV6wglJwJCv5fOxcr6EstgbGiEw4XcfUry4cWrcEUW8n+ARKxXEJHhw2WG43UKSvwI/TSZgvl7kh0b3XLZaLEy0QmMgLZAVH7J+ALOE+AVnDvQOyiPMAWcW5gSzjCPAV+78S5WE0GrQAAAAASUVORK5CYII=',
  237. // #endif
  238. lists: [],
  239. isInCount: true,
  240. popupShow: false,
  241. currentItemIndex: -1
  242. }
  243. },
  244. watch: {
  245. // 监听文件列表的变化,重新整理内部数据
  246. fileList: {
  247. handler() {
  248. this.formatFileList()
  249. },
  250. immediate: true,
  251. deep: true,
  252. },
  253. deletable(newVal) {
  254. this.formatFileList()
  255. },
  256. maxCount(newVal) {
  257. this.formatFileList()
  258. },
  259. accept(newVal) {
  260. this.formatFileList()
  261. },
  262. popupShow(newVal) {
  263. if (!newVal) {
  264. this.currentItemIndex = -1;
  265. }
  266. }
  267. },
  268. // #ifdef VUE3
  269. emits: ['error', 'beforeRead', 'oversize', 'afterRead', 'delete', 'clickPreview', 'update:fileList', 'afterAutoUpload'],
  270. // #endif
  271. methods: {
  272. addUnit,
  273. addStyle,
  274. videoErrorCallback() {},
  275. loadedVideoMetadata(e) {
  276. if (this.currentItemIndex < 0) {
  277. return;
  278. }
  279. if (this.autoUploadDriver != 'local') {
  280. return;
  281. }
  282. if (!this.getVideoThumb) {
  283. return;
  284. }
  285. // 截取第一帧作为封面,oss等云存储场景直接使用拼接参数。
  286. let w = this.lists[this.currentItemIndex].width;
  287. let h = this.lists[this.currentItemIndex].height;
  288. const dpr = uni.getSystemInfoSync().pixelRatio;
  289. uni.createSelectorQuery().select('#myVideo').context(res => {
  290. console.log('select video', res)
  291. const myVideo = res.context
  292. uni.createSelectorQuery()
  293. .select('#myCanvas')
  294. .fields({ node: true, size: true })
  295. .exec(([res]) => {
  296. console.log('select canvas', res)
  297. const ctx1 = res[0].node.getContext('2d')
  298. res[0].node.width = w * dpr
  299. res[0].node.height = h * dpr
  300. // Draw the first frame and export it as an image
  301. // myVideo.onPlay(() => {
  302. setTimeout(() => {
  303. captureFirstFrame()
  304. }, 500)
  305. // })
  306. const captureFirstFrame = () => {
  307. ctx1.drawImage(myVideo, 0, 0, w * dpr, h * dpr)
  308. wx.canvasToTempFilePath({
  309. canvas: res[0].node,
  310. success: (result) => {
  311. console.log('First frame image path:', result
  312. .tempFilePath)
  313. // Now you can use the image path (result.tempFilePath)
  314. this.fileList['currentItemIndex'].thumb = result.tempFilePath
  315. },
  316. fail: (err) => {
  317. console.error('Failed to export image:', err)
  318. }
  319. })
  320. }
  321. // Capture the first frame
  322. setInterval(() => {
  323. ctx1.drawImage(myVideo, 0, 0, w * dpr, h * dpr);
  324. }, 1000 / 24)
  325. }).exec()
  326. }).exec()
  327. },
  328. formatFileList() {
  329. const {
  330. fileList = [], maxCount
  331. } = this;
  332. const lists = fileList.map((item) => {
  333. const name = item.name || item.url || item.thumb
  334. return Object.assign(Object.assign({}, item), {
  335. // 如果item.url为本地选择的blob文件的话,无法判断其为video还是image,此处优先通过accept做判断处理
  336. isImage: item.name ? test.image(item.name) : (this.accept === 'image' || test.image(name)),
  337. isVideo: item.name ? test.video(item.name) : (this.accept === 'video' || test.video(name)),
  338. deletable: typeof item.deletable === 'boolean' ? item.deletable : this.deletable,
  339. })
  340. });
  341. this.lists = lists
  342. this.isInCount = lists.length < maxCount
  343. },
  344. chooseFile(params) {
  345. const {
  346. maxCount,
  347. multiple,
  348. lists,
  349. disabled
  350. } = this;
  351. if (disabled) return Promise.reject();
  352. const chooseParams = Object.assign({
  353. accept: this.accept,
  354. extension: this.extension,
  355. multiple: this.multiple,
  356. capture: this.capture,
  357. compressed: this.compressed,
  358. maxDuration: this.maxDuration,
  359. sizeType: this.sizeType,
  360. camera: this.camera,
  361. }, {
  362. maxCount: maxCount - lists.length,
  363. ...params
  364. })
  365. return chooseFile(chooseParams)
  366. .then((res) => {
  367. const result = chooseParams.multiple ? res : res[0]
  368. this.onBeforeRead(result);
  369. return result
  370. })
  371. .catch((error) => {
  372. this.$emit('error', error);
  373. });
  374. },
  375. // 文件读取之前
  376. onBeforeRead(file) {
  377. const {
  378. beforeRead,
  379. useBeforeRead,
  380. } = this;
  381. let res = file
  382. // beforeRead是否为一个方法
  383. if (test.func(beforeRead)) {
  384. // 如果用户定义了此方法,则去执行此方法,并传入读取的文件回调
  385. res = beforeRead(file, this.getDetail());
  386. }
  387. if (useBeforeRead) {
  388. res = new Promise((resolve, reject) => {
  389. this.$emit(
  390. 'beforeRead',
  391. Object.assign(Object.assign({
  392. file
  393. }, this.getDetail()), {
  394. callback: (ok) => {
  395. ok ? resolve() : reject();
  396. },
  397. })
  398. );
  399. });
  400. }
  401. if (test.promise(res)) {
  402. res.then((data) => this.onAfterRead(data || file));
  403. } else {
  404. this.onAfterRead(res || file);
  405. }
  406. },
  407. getDetail(index) {
  408. return {
  409. name: this.name,
  410. index: index == null ? this.fileList.length : index,
  411. };
  412. },
  413. async onAfterRead(file) {
  414. const {
  415. maxSize,
  416. afterRead
  417. } = this;
  418. const oversize = Array.isArray(file) ?
  419. file.some((item) => item.size > maxSize) :
  420. file.size > maxSize;
  421. if (oversize) {
  422. uni.showToast({
  423. title: '超过大小限制'
  424. })
  425. this.$emit('oversize', Object.assign({
  426. file
  427. }, this.getDetail()));
  428. return;
  429. }
  430. let len = this.fileList.length;
  431. if (this.autoUpload) {
  432. // 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
  433. let lists = [].concat(file);
  434. let fileListLen = this.fileList.length;
  435. lists.map((item) => {
  436. this.fileList.push({
  437. ...item,
  438. status: 'uploading',
  439. message: '上传中',
  440. progress: 0
  441. });
  442. });
  443. let that = this;
  444. this.$emit('update:fileList', this.fileList);
  445. for (let i = 0; i < lists.length; i++) {
  446. let j = i;
  447. let result = '';
  448. switch(this.autoUploadDriver) {
  449. case 'cos': // 腾讯云
  450. break;
  451. case 'kodo': // 七牛云
  452. break;
  453. case 'oss':
  454. case 'upload_oss':
  455. // 阿里云前端直传
  456. // 获取签名
  457. console.log()
  458. let formData = {};
  459. let ret = await uni.request({
  460. url: this.autoUploadAuthUrl,
  461. method: 'get',
  462. header: this.autoUploadHeader,
  463. data: {
  464. filename: lists[j].name
  465. }
  466. });
  467. // console.log(ret);
  468. let res0 = ret.data;
  469. if (res0.code == 200) {
  470. // 路径 + 文件名 + 扩展名
  471. // 不传递filename就要拼接key
  472. // res0.data.params.key = res0.data.params.dir + res0.data.params.uniqidName + fileExt;
  473. formData = res0.data.params;
  474. } else {
  475. uni.showToast({
  476. title: res0.msg,
  477. duration: 1500
  478. });
  479. return;
  480. }
  481. var uploadTask = uni.uploadFile({
  482. url: res0.data.params.host,
  483. filePath: lists[j].url,
  484. name: 'file',
  485. // fileType: 'video', // 仅支付宝小程序,且必填。
  486. // header: header,
  487. formData: formData,
  488. success: (uploadFileRes) => {
  489. let thumb = '';
  490. let afterPromise = '';
  491. if (that.customAfterAutoUpload) {
  492. afterPromise = new Promise((resolve, reject) => {
  493. that.$emit(
  494. 'afterAutoUpload',
  495. Object.assign(res0, {
  496. callback: (r) => {
  497. r.url ? resolve(r) : reject();
  498. },
  499. })
  500. );
  501. });
  502. }
  503. if (test.promise(afterPromise)) {
  504. afterPromise.then((data) => that.succcessUpload(len + j, data.url, data.thumb));
  505. } else {
  506. result = res0.data.params.host + '/' + res0.data.params.key;
  507. if (that.accept === 'video' || test.video(result)) {
  508. thumb = result + '?x-oss-process=video/snapshot,t_10000,m_fast';
  509. }
  510. that.succcessUpload(len + j, result, thumb);
  511. }
  512. }
  513. });
  514. uploadTask.onProgressUpdate((res) => {
  515. that.updateUpload(len + j, {
  516. progress: res.progress
  517. });
  518. // console.log('上传进度' + res.progress);
  519. // console.log('已经上传的数据长度' + res.totalBytesSent);
  520. // console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend);
  521. });
  522. break;
  523. case 'local':
  524. default:
  525. // 服务器本机上传
  526. var uploadTask = uni.uploadFile({
  527. url: this.autoUploadApi,
  528. filePath: lists[j].url,
  529. name: 'file',
  530. // fileType: 'video', // 仅支付宝小程序,且必填。
  531. header: this.autoUploadHeader,
  532. success: (uploadFileRes) => {
  533. let res0 = uploadFileRes.data;
  534. let afterPromise = '';
  535. if (that.customAfterAutoUpload) {
  536. afterPromise = new Promise((resolve, reject) => {
  537. that.$emit(
  538. 'afterAutoUpload',
  539. Object.assign(res0, {
  540. callback: (r) => {
  541. r.url ? resolve(r) : reject();
  542. }
  543. })
  544. );
  545. });
  546. }
  547. if (test.promise(afterPromise)) {
  548. afterPromise.then((data) => that.succcessUpload(len + j, data.url));
  549. } else {
  550. if (res0.code != 200) {
  551. uni.showToast({
  552. title: res0.msg
  553. });
  554. } else {
  555. result = res0.data.url;
  556. that.succcessUpload(len + j, result);
  557. }
  558. }
  559. }
  560. });
  561. uploadTask.onProgressUpdate((res) => {
  562. that.updateUpload(len + j, {
  563. progress: res.progress
  564. });
  565. // console.log('上传进度' + res.progress);
  566. // console.log('已经上传的数据长度' + res.totalBytesSent);
  567. // console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend);
  568. });
  569. break;
  570. }
  571. }
  572. } else {
  573. if (typeof afterRead === 'function') {
  574. afterRead(file, this.getDetail());
  575. }
  576. this.$emit('afterRead', Object.assign({
  577. file
  578. }, this.getDetail()));
  579. }
  580. },
  581. updateUpload(index, param) {
  582. let item = this.fileList[index];
  583. this.fileList.splice(index, 1, {
  584. ...item,
  585. // 注意这里不判断会出现succcessUpload先执行又被覆盖的问题
  586. status: param.progress == 100 ? 'success' : 'uploading',
  587. message: '',
  588. progress: param.progress
  589. });
  590. this.$emit('update:fileList', this.fileList);
  591. },
  592. succcessUpload(index, url, thumb = '') {
  593. let item = this.fileList[index];
  594. this.fileList.splice(index, 1, {
  595. ...item,
  596. status: 'success',
  597. message: '',
  598. url: url,
  599. progress: 100,
  600. thumb: thumb
  601. });
  602. this.$emit('update:fileList', this.fileList);
  603. },
  604. deleteItem(index) {
  605. if (this.autoDelete) {
  606. this.fileList.splice(index, 1);
  607. this.$emit('update:fileList', this.fileList);
  608. } else {
  609. this.$emit(
  610. 'delete',
  611. Object.assign(Object.assign({}, this.getDetail(index)), {
  612. file: this.fileList[index],
  613. })
  614. );
  615. }
  616. },
  617. // 预览图片
  618. onPreviewImage(previewItem, index) {
  619. if (!previewItem.isImage || !this.previewFullImage) return
  620. let current = 0;
  621. const urls = [];
  622. let imageIndex = 0;
  623. for (var i = 0; i < this.lists.length; i++) {
  624. const item = this.lists[i];
  625. if (item.isImage || (item.type && item.type === 'image')) {
  626. urls.push(item.url || item.thumb);
  627. if (i === index) {
  628. current = imageIndex;
  629. }
  630. imageIndex += 1;
  631. }
  632. }
  633. if (urls.length < 1) {
  634. return;
  635. }
  636. uni.previewImage({
  637. urls: urls,
  638. current: current,
  639. fail() {
  640. toast('预览图片失败')
  641. },
  642. });
  643. },
  644. onPreviewVideo(previewItem, index) {
  645. if (!this.previewFullImage) return;
  646. let current = 0;
  647. const sources = [];
  648. let videoIndex = 0;
  649. for (var i = 0; i < this.lists.length; i++) {
  650. const item = this.lists[i];
  651. if (item.isVideo || (item.type && item.type === 'video')) {
  652. sources.push(Object.assign(Object.assign({}, item), {
  653. type: 'video'
  654. }));
  655. if (i === index) {
  656. current = videoIndex;
  657. }
  658. videoIndex += 1;
  659. }
  660. }
  661. if (sources.length < 1) {
  662. return;
  663. }
  664. // #ifndef MP-WEIXIN
  665. this.popupShow = true;
  666. this.currentItemIndex = index;
  667. console.log(this.lists[this.currentItemIndex])
  668. // #endif
  669. // #ifdef MP-WEIXIN
  670. wx.previewMedia({
  671. sources: sources,
  672. current: current,
  673. fail() {
  674. toast('预览视频失败')
  675. },
  676. });
  677. // #endif
  678. },
  679. onClickPreview(item, index) {
  680. if (this.previewFullImage) {
  681. switch (item.type) {
  682. case 'image':
  683. this.onPreviewImage(item, index);
  684. break;
  685. case 'video':
  686. this.onPreviewVideo(item, index);
  687. break;
  688. default:
  689. break;
  690. }
  691. }
  692. this.$emit(
  693. 'clickPreview',
  694. Object.assign(Object.assign({}, item), this.getDetail(index))
  695. );
  696. }
  697. }
  698. }
  699. </script>
  700. <style lang="scss" scoped>
  701. $u-upload-preview-border-radius: 2px !default;
  702. $u-upload-preview-margin: 0 8px 8px 0 !default;
  703. $u-upload-image-width:80px !default;
  704. $u-upload-image-height:$u-upload-image-width;
  705. $u-upload-other-bgColor: rgb(242, 242, 242) !default;
  706. $u-upload-other-flex:1 !default;
  707. $u-upload-text-font-size:11px !default;
  708. $u-upload-text-color:$u-tips-color !default;
  709. $u-upload-text-margin-top:2px !default;
  710. $u-upload-deletable-right:0 !default;
  711. $u-upload-deletable-top:0 !default;
  712. $u-upload-deletable-bgColor:rgb(55, 55, 55) !default;
  713. $u-upload-deletable-height:14px !default;
  714. $u-upload-deletable-width:$u-upload-deletable-height;
  715. $u-upload-deletable-boder-bottom-left-radius:100px !default;
  716. $u-upload-deletable-zIndex:3 !default;
  717. $u-upload-success-bottom:0 !default;
  718. $u-upload-success-right:0 !default;
  719. $u-upload-success-border-style:solid !default;
  720. $u-upload-success-border-top-color:transparent !default;
  721. $u-upload-success-border-left-color:transparent !default;
  722. $u-upload-success-border-bottom-color: $u-success !default;
  723. $u-upload-success-border-right-color:$u-upload-success-border-bottom-color;
  724. $u-upload-success-border-width:9px !default;
  725. $u-upload-icon-top:0px !default;
  726. $u-upload-icon-right:0px !default;
  727. $u-upload-icon-h5-top:1px !default;
  728. $u-upload-icon-h5-right:0 !default;
  729. $u-upload-icon-width:16px !default;
  730. $u-upload-icon-height:$u-upload-icon-width;
  731. $u-upload-success-icon-bottom:-10px !default;
  732. $u-upload-success-icon-right:-10px !default;
  733. $u-upload-status-right:0 !default;
  734. $u-upload-status-left:0 !default;
  735. $u-upload-status-bottom:0 !default;
  736. $u-upload-status-top:0 !default;
  737. $u-upload-status-bgColor:rgba(0, 0, 0, 0.5) !default;
  738. $u-upload-status-icon-Zindex:1 !default;
  739. $u-upload-message-font-size:12px !default;
  740. $u-upload-message-color:#FFFFFF !default;
  741. $u-upload-message-margin-top:5px !default;
  742. $u-upload-button-width:80px !default;
  743. $u-upload-button-height:$u-upload-button-width;
  744. $u-upload-button-bgColor:rgb(244, 245, 247) !default;
  745. $u-upload-button-border-radius:2px !default;
  746. $u-upload-botton-margin: 0 8px 8px 0 !default;
  747. $u-upload-text-font-size:11px !default;
  748. $u-upload-text-color:$u-tips-color !default;
  749. $u-upload-text-margin-top: 2px !default;
  750. $u-upload-hover-bgColor:rgb(230, 231, 233) !default;
  751. $u-upload-disabled-opacity:.5 !default;
  752. .u-upload {
  753. @include flex(column);
  754. flex: 1;
  755. &__wrap {
  756. @include flex;
  757. flex-wrap: wrap;
  758. flex: 1;
  759. &__preview {
  760. border-radius: $u-upload-preview-border-radius;
  761. margin: $u-upload-preview-margin;
  762. position: relative;
  763. overflow: hidden;
  764. @include flex;
  765. &__image {
  766. width: $u-upload-image-width;
  767. height: $u-upload-image-height;
  768. }
  769. &__video,
  770. &__other {
  771. width: $u-upload-image-width;
  772. height: $u-upload-image-height;
  773. background-color: $u-upload-other-bgColor;
  774. flex: $u-upload-other-flex;
  775. @include flex(column);
  776. justify-content: center;
  777. align-items: center;
  778. &__text {
  779. font-size: $u-upload-text-font-size;
  780. color: $u-upload-text-color;
  781. margin-top: $u-upload-text-margin-top;
  782. }
  783. }
  784. }
  785. }
  786. &__wrap__play {
  787. position: absolute;
  788. top: 0px;
  789. left: 0px;
  790. bottom: 0px;
  791. right: 0px;
  792. display: flex;
  793. justify-content: center;
  794. align-items: center;
  795. &__icon {
  796. background: #fff;
  797. border-radius: 100px;
  798. opacity: 0.8;
  799. };
  800. }
  801. &__deletable {
  802. position: absolute;
  803. top: $u-upload-deletable-top;
  804. right: $u-upload-deletable-right;
  805. background-color: $u-upload-deletable-bgColor;
  806. height: $u-upload-deletable-height;
  807. width: $u-upload-deletable-width;
  808. @include flex;
  809. border-bottom-left-radius: $u-upload-deletable-boder-bottom-left-radius;
  810. align-items: center;
  811. justify-content: center;
  812. z-index: $u-upload-deletable-zIndex;
  813. &__icon {
  814. position: absolute;
  815. transform: scale(0.7);
  816. top: $u-upload-icon-top;
  817. right: $u-upload-icon-right;
  818. /* #ifdef H5 */
  819. top: $u-upload-icon-h5-top;
  820. right: $u-upload-icon-h5-right;
  821. /* #endif */
  822. }
  823. }
  824. &__success {
  825. position: absolute;
  826. bottom: $u-upload-success-bottom;
  827. right: $u-upload-success-right;
  828. @include flex;
  829. // 由于weex(nvue)为阿里巴巴的KPI(部门业绩考核)的laji产物,不支持css绘制三角形
  830. // 所以在nvue下使用图片,非nvue下使用css实现
  831. /* #ifndef APP-NVUE */
  832. border-style: $u-upload-success-border-style;
  833. border-top-color: $u-upload-success-border-top-color;
  834. border-left-color: $u-upload-success-border-left-color;
  835. border-bottom-color: $u-upload-success-border-bottom-color;
  836. border-right-color: $u-upload-success-border-right-color;
  837. border-width: $u-upload-success-border-width;
  838. align-items: center;
  839. justify-content: center;
  840. /* #endif */
  841. &__icon {
  842. /* #ifndef APP-NVUE */
  843. position: absolute;
  844. transform: scale(0.7);
  845. bottom: $u-upload-success-icon-bottom;
  846. right: $u-upload-success-icon-right;
  847. /* #endif */
  848. /* #ifdef APP-NVUE */
  849. width: $u-upload-icon-width;
  850. height: $u-upload-icon-height;
  851. /* #endif */
  852. }
  853. }
  854. &__progress {
  855. background-color: $u-primary !important;
  856. position: absolute;
  857. bottom: 0;
  858. left: 0;
  859. }
  860. &__status {
  861. position: absolute;
  862. top: $u-upload-status-top;
  863. bottom: $u-upload-status-bottom;
  864. left: $u-upload-status-left;
  865. right: $u-upload-status-right;
  866. background-color: $u-upload-status-bgColor;
  867. @include flex(column);
  868. align-items: center;
  869. justify-content: center;
  870. &__icon {
  871. position: relative;
  872. z-index: $u-upload-status-icon-Zindex;
  873. }
  874. &__message {
  875. font-size: $u-upload-message-font-size;
  876. color: $u-upload-message-color;
  877. margin-top: $u-upload-message-margin-top;
  878. }
  879. }
  880. &__button {
  881. @include flex(column);
  882. align-items: center;
  883. justify-content: center;
  884. width: $u-upload-button-width;
  885. height: $u-upload-button-height;
  886. background-color: $u-upload-button-bgColor;
  887. border-radius: $u-upload-button-border-radius;
  888. margin: $u-upload-botton-margin;
  889. /* #ifndef APP-NVUE */
  890. box-sizing: border-box;
  891. /* #endif */
  892. &__text {
  893. font-size: $u-upload-text-font-size;
  894. color: $u-upload-text-color;
  895. margin-top: $u-upload-text-margin-top;
  896. }
  897. &--hover {
  898. background-color: $u-upload-hover-bgColor;
  899. }
  900. &--disabled {
  901. opacity: $u-upload-disabled-opacity;
  902. }
  903. }
  904. }
  905. </style>