index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. <template>
  2. <div class="fm-uplaod-container"
  3. :id="uploadId"
  4. >
  5. <draggable class="drag-img-list"
  6. v-model="fileList"
  7. v-bind="{group: uploadId, ghostClass: 'ghost', animation: 200}"
  8. :no-transition-on-drag="true"
  9. >
  10. <div
  11. :id="item.key"
  12. :style="{width: width+'px', height: height+'px'}"
  13. :class="{uploading: item.status=='uploading', 'is-success': item.status=='success', 'is-disabled': disabled}"
  14. class="upload-file" v-for="(item) in fileList" :key="item.key">
  15. <img :src="item.url" />
  16. <el-progress v-if="item.status=='uploading' && ui == 'element'" :width="miniWidth*0.9" class="upload-progress" type="circle" :percentage="item.percent"></el-progress>
  17. <a-progress v-if="item.status=='uploading' && ui == 'antd'" :width="miniWidth*0.9" class="upload-progress" type="circle" :percent="item.percent"></a-progress>
  18. <label class="item-status" v-if="item.status=='success'">
  19. <i class="el-icon-upload-success el-icon-check" v-if="ui=='element'"></i>
  20. <a-icon type="check" v-if="ui=='antd'" />
  21. </label>
  22. <div class="uplaod-action" :style="{height: miniWidth / 4 + 'px'}">
  23. <i class="iconfont icon-tupianyulan" :title="$t('fm.upload.preview')" @click="handlePreviewFile(item.key)" :style="{'font-size': miniWidth/8+'px'}"></i>
  24. <i v-if="isEdit && !disabled" class="iconfont icon-sync1" :title="$t('fm.upload.edit')" @click="handleEdit(item.key)" :style="{'font-size': miniWidth/8+'px'}"></i>
  25. <i v-if="isDelete && fileList.length > min && !disabled" class="iconfont icon-delete" :title="$t('fm.upload.delete')" @click="handleRemove(item.key)" :style="{'font-size': miniWidth/8+'px'}"></i>
  26. </div>
  27. </div>
  28. </draggable>
  29. <div
  30. :class="{'is-disabled': disabled, 'el-upload': ui == 'element', 'el-upload--picture-card': ui == 'element', 'ant-upload': ui == 'antd', 'ant-upload-select' : ui == 'antd', 'ant-upload-select-picture-card': ui == 'antd'}"
  31. v-show="(!isQiniu || (isQiniu && token)) && fileList.length < limit"
  32. :style="{width: width+'px', height: height+'px'}"
  33. @click="handleAdd"
  34. v-if="!readonly"
  35. >
  36. <a-icon type="plus" v-if="ui=='antd'" :style="{fontSize:miniWidth/4+'px',marginTop: (-miniWidth/8)+'px', marginLeft: (-miniWidth/8)+'px', position: 'absolute', left: '50%', top: '50%'}" />
  37. <i v-if="ui == 'element'" class="el-icon-plus" :style="{fontSize:miniWidth/4+'px',marginTop: (-miniWidth/8)+'px', marginLeft: (-miniWidth/8)+'px'}"></i>
  38. <input accept="image/*" v-if="multiple" multiple ref="uploadInput" @change="handleChange" type="file" :style="{width: 0, height: 0}" name="file" class=" upload-input">
  39. <input accept="image/*" v-else ref="uploadInput" @change="handleChange" type="file" :style="{width:0, height: 0}" name="file" class=" upload-input">
  40. </div>
  41. </div>
  42. </template>
  43. <script>
  44. import Viewer from 'viewerjs'
  45. import Draggable from 'vuedraggable'
  46. import { EventBus } from '../../util/event-bus.js'
  47. import * as qiniu from 'qiniu-js'
  48. require('viewerjs/dist/viewer.css')
  49. export default {
  50. components: {
  51. Draggable
  52. },
  53. props: {
  54. value: {
  55. type: Array,
  56. default: () => []
  57. },
  58. width: {
  59. type: Number,
  60. default: 100
  61. },
  62. height: {
  63. type: Number,
  64. default: 100
  65. },
  66. token: {
  67. type: String,
  68. default: ''
  69. },
  70. domain: {
  71. type: String,
  72. default: ''
  73. },
  74. multiple: {
  75. type: Boolean,
  76. default: false
  77. },
  78. limit: {
  79. type: Number,
  80. default: 9
  81. },
  82. isQiniu: {
  83. type: Boolean,
  84. default: false
  85. },
  86. isDelete: {
  87. type: Boolean,
  88. default: false
  89. },
  90. min: {
  91. type: Number,
  92. default: 0
  93. },
  94. meitu: {
  95. type: Boolean,
  96. default: false
  97. },
  98. isEdit: {
  99. type: Boolean,
  100. default: false
  101. },
  102. action: {
  103. type: String,
  104. default: ''
  105. },
  106. disabled: {
  107. type: Boolean,
  108. default: false
  109. },
  110. readonly: {
  111. type: Boolean,
  112. default: false
  113. },
  114. headers: {
  115. type: Array,
  116. default: () => []
  117. },
  118. ui: {
  119. type: String,
  120. default: 'element'
  121. }
  122. },
  123. data () {
  124. return {
  125. fileList: this.value.map(item => {
  126. return {
  127. ...item,
  128. key: item.key ? item.key : (new Date().getTime()) + '_' + Math.ceil(Math.random() * 99999),
  129. }
  130. }),
  131. viewer: null,
  132. uploadId: 'upload_' + new Date().getTime(),
  133. editIndex: -1,
  134. meituIndex: -1,
  135. }
  136. },
  137. computed: {
  138. miniWidth () {
  139. if (this.width > this.height) {
  140. return this.height
  141. } else {
  142. return this.width
  143. }
  144. }
  145. },
  146. methods: {
  147. handleChange () {
  148. console.log(this.$refs.uploadInput.files)
  149. const files = this.$refs.uploadInput.files
  150. for (let i = 0; i < files.length; i++) {
  151. const file = files[i]
  152. const reader = new FileReader()
  153. const key = (new Date().getTime()) + '_' + Math.ceil(Math.random() * 99999)
  154. reader.readAsDataURL(file)
  155. reader.onload = () => {
  156. if (this.editIndex >= 0) {
  157. this.$set(this.fileList, this.editIndex, {
  158. key,
  159. url: reader.result,
  160. percent: 0,
  161. status: 'uploading'
  162. })
  163. this.editIndex = -1
  164. } else {
  165. this.fileList.push({
  166. key,
  167. url: reader.result,
  168. percent: 0,
  169. status: 'uploading'
  170. })
  171. }
  172. this.$nextTick(() => {
  173. if (this.isQiniu) {
  174. this.uplaodAction2(reader.result, file, key)
  175. } else {
  176. this.uplaodAction(reader.result, file, key)
  177. }
  178. })
  179. }
  180. }
  181. this.$refs.uploadInput.value = []
  182. },
  183. uplaodAction (res, file, key) {
  184. let changeIndex = this.fileList.findIndex(item => item.key === key)
  185. console.log(this.fileList.findIndex(item => item.key === key))
  186. const xhr = new XMLHttpRequest()
  187. const url = this.action
  188. xhr.open('POST', url, true)
  189. // xhr.setRequestHeader('Content-Type', 'multipart/form-data')
  190. this.headers.map(item => {
  191. xhr.setRequestHeader(item.key, item.value)
  192. })
  193. let formData = new FormData()
  194. formData.append('file', file)
  195. formData.append('fname', file.name)
  196. formData.append('key', key)
  197. xhr.send(formData)
  198. xhr.onreadystatechange = () => {
  199. console.log(xhr)
  200. if (xhr.readyState === 4) {
  201. let resData = JSON.parse(xhr.response)
  202. if (resData && resData.url) {
  203. this.$set(this.fileList, this.fileList.findIndex(item => item.key === key), {
  204. ...this.fileList[this.fileList.findIndex(item => item.key === key)],
  205. url: resData.url,
  206. percent: 100,
  207. ...resData
  208. })
  209. setTimeout(() => {
  210. this.$set(this.fileList, this.fileList.findIndex(item => item.key === key), {
  211. ...this.fileList[this.fileList.findIndex(item => item.key === key)],
  212. status: 'success'
  213. })
  214. if (this.ui == 'element') {
  215. this.$emit('input', this.fileList)
  216. } else {
  217. EventBus.$emit('on-field-change', this.$attrs.id, this.fileList)
  218. }
  219. }, 200)
  220. } else {
  221. this.$set(this.fileList, this.fileList.findIndex(item => item.key === key), {
  222. ...this.fileList[this.fileList.findIndex(item => item.key === key)],
  223. status: 'error'
  224. })
  225. this.fileList.splice(this.fileList.findIndex(item => item.key === key), 1)
  226. }
  227. }
  228. }
  229. xhr.onprogress = (res) => {
  230. console.log('progress', res)
  231. if (res.total && res.loaded) {
  232. this.$set(this.fileList[this.fileList.findIndex(item => item.key === key)], 'percent', res.loaded/res.total*100)
  233. }
  234. }
  235. },
  236. uplaodAction2 (res, file, key) {
  237. const _this = this
  238. const observable = qiniu.upload(file, key, this.token, {
  239. fname: key,
  240. mimeType: []
  241. }, {
  242. useCdnDomain: true,
  243. // region: qiniu.region.z2
  244. })
  245. observable.subscribe({
  246. next (res) {
  247. _this.$set(_this.fileList[_this.fileList.findIndex(item => item.key === key)], 'percent', parseInt(res.total.percent))
  248. },
  249. error (err) {
  250. _this.$set(_this.fileList, _this.fileList.findIndex(item => item.key === key), {
  251. ..._this.fileList[_this.fileList.findIndex(item => item.key === key)],
  252. status: 'error'
  253. })
  254. _this.fileList.splice(_this.fileList.findIndex(item => item.key === key), 1)
  255. },
  256. complete (res) {
  257. _this.$set(_this.fileList, _this.fileList.findIndex(item => item.key === key), {
  258. ..._this.fileList[_this.fileList.findIndex(item => item.key === key)],
  259. url: _this.domain + res.key,
  260. percent: 100,
  261. ...res,
  262. })
  263. setTimeout(() => {
  264. _this.$set(_this.fileList, _this.fileList.findIndex(item => item.key === key), {
  265. ..._this.fileList[_this.fileList.findIndex(item => item.key === key)],
  266. status: 'success'
  267. })
  268. if (_this.ui == 'element') {
  269. _this.$emit('input', _this.fileList)
  270. } else {
  271. EventBus.$emit('on-field-change', _this.$attrs.id, _this.fileList)
  272. }
  273. }, 200)
  274. }
  275. })
  276. },
  277. handleRemove (key) {
  278. this.fileList.splice(this.fileList.findIndex(item => item.key === key), 1)
  279. this.$nextTick(() => {
  280. if (this.ui == 'element') {
  281. this.$emit('input', this.fileList)
  282. } else {
  283. EventBus.$emit('on-field-change', this.$attrs.id, this.fileList)
  284. }
  285. })
  286. },
  287. handleEdit (key) {
  288. this.editIndex = this.fileList.findIndex(item => item.key === key)
  289. this.$refs.uploadInput.click()
  290. },
  291. handleMeitu (key) {
  292. this.$emit('on-meitu', this.fileList.findIndex(item => item.key === key))
  293. },
  294. handleAdd () {
  295. if (!this.disabled) {
  296. this.editIndex = -1
  297. this.$refs.uploadInput.click()
  298. }
  299. },
  300. handlePreviewFile (key) {
  301. this.viewer && this.viewer.destroy()
  302. this.uploadId = 'upload_' + new Date().getTime()
  303. console.log(this.viewer)
  304. this.$nextTick(() => {
  305. this.viewer = new Viewer(document.getElementById(this.uploadId))
  306. this.viewer.view(this.fileList.findIndex(item => item.key === key))
  307. })
  308. }
  309. },
  310. watch: {
  311. value (val) {
  312. this.fileList = this.value.map(item => {
  313. return {
  314. ...item,
  315. key: item.key ? item.key : (new Date().getTime()) + '_' + Math.ceil(Math.random() * 99999),
  316. }
  317. })
  318. }
  319. }
  320. }
  321. </script>
  322. <style lang="scss">
  323. .fm-uplaod-container{
  324. .is-disabled{
  325. position: relative;
  326. &::after{
  327. position: absolute;
  328. top: 0;
  329. bottom: 0;
  330. left: 0;
  331. right: 0;
  332. // background: rgba(0,0,0,.1);
  333. content: '';
  334. display: block;
  335. cursor:not-allowed;
  336. }
  337. }
  338. .upload-file{
  339. margin: 0 10px 10px 0;
  340. display: inline-flex;
  341. justify-content: center;
  342. align-items: center;
  343. // background: #fff;
  344. overflow: hidden;
  345. background-color: #fff;
  346. border: 1px solid #c0ccda;
  347. border-radius: 6px;
  348. box-sizing: border-box;
  349. position: relative;
  350. vertical-align: top;
  351. &:hover{
  352. .uplaod-action{
  353. display: flex;
  354. }
  355. }
  356. .uplaod-action{
  357. position: absolute;
  358. // top: 0;
  359. // height: 30px;
  360. bottom: 0;
  361. left: 0;
  362. right: 0;
  363. background: rgba(0,0,0,0.6);
  364. display: none;
  365. justify-content: center;
  366. align-items: center;
  367. i{
  368. color: #fff;
  369. cursor: pointer;
  370. margin: 0 5px;
  371. }
  372. }
  373. &.is-success{
  374. .item-status{
  375. position: absolute;
  376. right: -15px;
  377. top: -6px;
  378. width: 40px;
  379. height: 24px;
  380. background: #00B53A;
  381. text-align: center;
  382. transform: rotate(45deg);
  383. box-shadow: 0 0 1pc 1px rgba(0,0,0,.2);
  384. &>i{
  385. font-size: 12px;
  386. margin-top: 11px;
  387. color: #fff;
  388. transform: rotate(-45deg);
  389. }
  390. }
  391. }
  392. &.uploading{
  393. &:before{
  394. display: block;
  395. content: '';
  396. position: absolute;
  397. top: 0;
  398. left: 0;
  399. right: 0;
  400. bottom: 0;
  401. background: rgba(0,0,0,0.3);
  402. }
  403. }
  404. .upload-progress{
  405. position: absolute;
  406. .el-progress__text{
  407. color: #fff;
  408. font-size: 16px !important;
  409. }
  410. }
  411. img{
  412. max-width: 100%;
  413. max-height: 100%;
  414. vertical-align: middle;
  415. }
  416. }
  417. .el-upload--picture-card{
  418. position: relative;
  419. overflow: hidden;
  420. .el-icon-plus{
  421. position: absolute;
  422. top: 50%;
  423. left: 50%;
  424. }
  425. }
  426. .upload-input{
  427. position: absolute;
  428. top: 0;
  429. left: 0;
  430. right: 0;
  431. bottom: 0;
  432. display: block;
  433. opacity: 0;
  434. cursor: pointer;
  435. }
  436. .drag-img-list{
  437. display: inline;
  438. .ghost{
  439. position: relative;
  440. &::after {
  441. width: 100%;
  442. height: 100%;
  443. display: block;
  444. content: '';
  445. background: #fbfdff;
  446. position: absolute;
  447. top: 0;
  448. bottom: 0;
  449. left: 0;
  450. right: 0;
  451. border: 1px dashed #3bb3c2;
  452. }
  453. }
  454. &>div{
  455. cursor: move;
  456. }
  457. }
  458. .ant-upload{
  459. display: inline-block;
  460. position: relative;
  461. }
  462. }
  463. .viewer-container{
  464. z-index: 9999 !important;
  465. }
  466. </style>