index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. <template>
  2. <div class="ibps-image">
  3. <!--展示-->
  4. <ul class="ibps-p-0">
  5. <vue-draggable
  6. v-if="fileList && fileList.length"
  7. v-model="fileList"
  8. v-bind="draggableOptions"
  9. class="list-group"
  10. @start="isDragging = true"
  11. @end="()=>{isDragging= false}"
  12. >
  13. <li
  14. v-for="(item, index) in fileList"
  15. :key="index"
  16. class="image-reader-item"
  17. :style="{
  18. 'backgroundImage': 'url(' + getImageUrl(item.id) + ')',
  19. 'backgroundPosition': 'center center',
  20. 'backgroundRepeat': 'no-repeat',
  21. 'backgroundSize': 'cover',
  22. 'width': `${$utils.isEmpty(width) ? 100 : width}px`,
  23. 'height': `${$utils.isEmpty(height) ? 100 : height}px`
  24. }"
  25. @click="clickHandler(item, index)"
  26. @mouseover="showTips(item, index)"
  27. @mouseout="hideTips(item, index)"
  28. >
  29. <el-tag v-if="!disabled" class="image-reader-item-tag" @click.stop="onDeleteImage(index)">
  30. <i class="el-icon-delete" />
  31. </el-tag>
  32. <el-tag v-if="!disabled" class="image-reader-item-tag" @click.stop="onDownloadImage(index)">
  33. <i class="el-icon-download" />
  34. </el-tag>
  35. <el-tag v-if="!disabled" class="image-reader-item-tag draggable" @click.stop="onDownloadImage(index)">
  36. <i class="el-icon-rank" />
  37. </el-tag>
  38. <div :class="{ 'image-tip-visible': item.uid !== uid, 'image-tip': item.uid === uid }" v-text="tip" />
  39. </li>
  40. </vue-draggable>
  41. </ul>
  42. <template v-if="!disabled && (isBuilder || fileList.length < limit || !limit)">
  43. <!--ibps 附件上传方式-->
  44. <el-upload
  45. v-if="uploadType === 'attachment'"
  46. :style="uploadStyle"
  47. :action="action"
  48. :disabled="true"
  49. :file-list="fileList"
  50. list-type="picture-card"
  51. @click.native="clickAttachmentUpload"
  52. >
  53. <i class="el-icon-plus" />
  54. </el-upload>
  55. <!--默认附件上传方式-->
  56. <el-upload
  57. v-else
  58. :style="uploadStyle"
  59. action="action"
  60. list-type="picture-card"
  61. :http-request="httpRequest"
  62. :file-list="fileList"
  63. :multiple="multiple"
  64. :accept="fileAccept"
  65. :limit="flieLimit"
  66. :disabled="disabled"
  67. :on-exceed="handlePicAmount"
  68. :before-upload="beforeUpload"
  69. >
  70. <i class="el-icon-plus" />
  71. </el-upload>
  72. </template>
  73. <!--图片预览-->
  74. <ibps-image-viewer
  75. v-if="showViewer"
  76. :z-index="zIndex"
  77. :initial-index="initialIndex"
  78. :on-close="closeViewer"
  79. :url-list="previewSrcList"
  80. />
  81. <ibps-uploader-selector-dialog
  82. :visible="uploaderSelectorVisible"
  83. :value="selectorValue"
  84. :multiple="multiple"
  85. :accept="accept"
  86. :file-size="size"
  87. @close="visible => uploaderSelectorVisible = visible"
  88. @action-event="handleUpload"
  89. />
  90. </div>
  91. </template>
  92. <script>
  93. import { uploadFile, previewFile, get, transfer } from '@/api/platform/file/attachment'
  94. import { imageAccept } from '@/business/platform/file/constants/fileTypes'
  95. import { downloadFile } from '@/business/platform/file/utils'
  96. import { valueEquals } from '@/plugins/element-ui/src/utils/util'
  97. import emitter from '@/plugins/element-ui/src/mixins/emitter'
  98. import { remoteRequest, remoteTransRequest } from '@/utils/remote'
  99. import PopupManager from '@/utils/popup'
  100. import IbpsUploaderSelectorDialog from '@/business/platform/file/uploader'
  101. import IbpsImageViewer from '@/components/ibps-file-viewer/image'
  102. import { TRANSFER_DATA } from '@/constant'
  103. import VueDraggable from 'vuedraggable'
  104. export default {
  105. name: 'ibps-image',
  106. components: {
  107. IbpsUploaderSelectorDialog,
  108. IbpsImageViewer,
  109. VueDraggable
  110. },
  111. mixins: [emitter],
  112. props: {
  113. value: {
  114. type: [String, Number, Array, Object]
  115. },
  116. size: [Number, String], // 限制大小
  117. accept: String, // 限制图片类型
  118. tip: String, // 提示消息
  119. limit: [Number, String], // 上传数量
  120. // quality: Number, // 压缩质量
  121. width: { // 宽
  122. type: String,
  123. default: '150'
  124. },
  125. height: { // 高
  126. type: String,
  127. default: '150'
  128. },
  129. disabled: { // 是否禁用
  130. type: Boolean,
  131. default: false
  132. },
  133. isBuilder: { // 是否表单设计
  134. type: Boolean,
  135. default: false
  136. },
  137. multiple: { // 是否支持选择多张
  138. type: Boolean,
  139. default: true
  140. },
  141. uploadType: { // 上传方式 ( default:直接打开上传,attachment:ibps上传附件打开上传 )
  142. type: String,
  143. default: 'attachment'
  144. },
  145. store: {
  146. type: String,
  147. default: 'json',
  148. validator: function (value) {
  149. return ['id', 'json', 'array', 'arrayId', 'bind'].indexOf(value) !== -1
  150. }
  151. },
  152. labelKey: { // 展示的值
  153. type: String,
  154. default: 'fileName'
  155. },
  156. valueKey: { // 存储唯一值
  157. type: String,
  158. default: 'id'
  159. }
  160. },
  161. inject: {
  162. elForm: {
  163. default: ''
  164. },
  165. elFormItem: {
  166. default: ''
  167. }
  168. },
  169. data () {
  170. return {
  171. action: 'https://www.bpmhome.cn/post',
  172. fileList: [],
  173. realFileList: [],
  174. selectorValue: this.multiple ? [] : {},
  175. cacheData: {}, // 缓存数据
  176. initialIndex: 0,
  177. showViewer: false,
  178. zIndex: 2000,
  179. uploaderSelectorVisible: false,
  180. isActive: false,
  181. uid: '',
  182. isDragging: false,
  183. draggableOptions: {
  184. handle: '.draggable',
  185. ghostClass: 'sortable-ghost',
  186. distance: 1,
  187. disabled: false,
  188. animation: 200,
  189. axis: 'y'
  190. }
  191. }
  192. },
  193. computed: {
  194. flieLimit () {
  195. return this.$utils.isNotEmpty(this.limit) ? parseInt(this.limit) : null
  196. },
  197. fileAccept () {
  198. if (this.$utils.isEmpty(this.accept) || this.accept === '*') {
  199. return 'image/*'
  200. }
  201. return this.converImageAccept(this.accept)
  202. },
  203. previewSrcList () {
  204. const list = []
  205. this.fileList.forEach(file => {
  206. list.push(previewFile(file.id))
  207. })
  208. return list
  209. },
  210. uploadStyle () {
  211. const { width, height } = this
  212. return {
  213. 'width': `${this.$utils.isEmpty(width) ? 100 : width}px`,
  214. 'height': `${this.$utils.isEmpty(height) ? 100 : height}px`,
  215. 'lineHeight': `${this.$utils.isEmpty(height) ? 100 : height}px`,
  216. 'display': 'inline'
  217. }
  218. }
  219. },
  220. watch: {
  221. value (val, oldVal) {
  222. this.setValue()
  223. if (!valueEquals(val, oldVal)) {
  224. this.dispatch('ElFormItem', 'el.form.change', val)
  225. }
  226. },
  227. fileList: {
  228. handler (val, oldVal) {
  229. console.log(val)
  230. this.$emit('input', this.getValue())
  231. },
  232. deep: true
  233. }
  234. },
  235. mounted () {
  236. this.setValue()
  237. },
  238. methods: {
  239. /**
  240. * zxh 修复zindex 不是最高的被遮住
  241. */
  242. fixZIndex () {
  243. return PopupManager.getZIndex()
  244. },
  245. converImageAccept (accept) {
  246. const accepts = accept.split(',')
  247. const fileAccept = []
  248. accepts.forEach((item) => {
  249. let type = item
  250. if (item.substr(0, 1) === '.') {
  251. type = item.substr(1)
  252. }
  253. fileAccept.push('.' + type)
  254. })
  255. return fileAccept.join(',')
  256. },
  257. converFileAccept (accept) {
  258. const accepts = accept.split(',')
  259. const fileAccept = []
  260. accepts.forEach((item) => {
  261. let type = item
  262. if (item.substr(0, 1) === '.') {
  263. type = item.substr(1)
  264. }
  265. fileAccept.push(imageAccept[type])
  266. })
  267. return fileAccept
  268. },
  269. setValue () {
  270. if (this.$utils.isEmpty(this.value)) {
  271. this.fileList = []
  272. return
  273. }
  274. // TODO: id展示问题
  275. this.fileList = this.getArrayValue(this.value)
  276. this.initRealFileList()
  277. },
  278. initRealFileList () {
  279. this.realFileList = []
  280. this.fileList.forEach(v => {
  281. const id = v[this.valueKey]
  282. if (this.cacheData[id]) {
  283. this.setRealFileList(id)
  284. } else {
  285. this.getDataInfo(id)
  286. }
  287. })
  288. },
  289. setCacheData (v) {
  290. this.cacheData[v[this.valueKey]] = v
  291. },
  292. setRealFileList (v) {
  293. this.realFileList.push(this.cacheData[v])
  294. },
  295. /**
  296. * 通过ID获取数据
  297. */
  298. getDataInfo (id) {
  299. if (TRANSFER_DATA === 'transfer') {
  300. this.getTransferData(id)
  301. } else {
  302. this.getRemoteData(id)
  303. }
  304. },
  305. getTransferData (id) {
  306. remoteTransRequest('attachment', id).then(idset => {
  307. const ids = Array.from(idset)
  308. remoteRequest('attachmentIds', ids, () => {
  309. return this.getRemoteTransFunc(ids)
  310. }).then(response => {
  311. const responseData = response.data
  312. const data = responseData[id]
  313. this.setRemoteData(data)
  314. }).catch(() => {
  315. })
  316. })
  317. },
  318. getRemoteTransFunc (ids) {
  319. return new Promise((resolve, reject) => {
  320. transfer({
  321. 'ids': ids
  322. }).then(response => {
  323. resolve(response)
  324. }).catch((error) => {
  325. reject(error)
  326. })
  327. })
  328. },
  329. getRemoteData (id) {
  330. remoteRequest('attachment' + this.valueKey, id, () => {
  331. return this.getRemoteByIdFunc(id)
  332. }).then(response => {
  333. const data = response.data
  334. this.setRemoteData(data)
  335. }).catch(() => {
  336. })
  337. },
  338. getRemoteByIdFunc (id) {
  339. return new Promise((resolve, reject) => {
  340. get({
  341. attachmentId: id
  342. }).then(response => {
  343. resolve(response)
  344. }).catch(() => {
  345. })
  346. })
  347. },
  348. setRemoteData (data) {
  349. if (this.$utils.isNotEmpty(data)) {
  350. this.cacheData[data[this.valueKey]] = data
  351. this.setSelectorValue(data[this.valueKey])
  352. }
  353. },
  354. /**
  355. * 获得数组数据
  356. */
  357. getArrayValue (value) {
  358. if (this.$utils.isEmpty(value)) {
  359. return []
  360. }
  361. if (this.store === 'json') { // json
  362. try {
  363. return this.$utils.parseData(value)
  364. } catch (error) {
  365. console.error(error)
  366. return []
  367. }
  368. } else if (this.store === 'id') { // id
  369. return this.$utils.isString(value) ? value.split(this.storeSeparator) : []
  370. } else { // array
  371. return value.map((d) => {
  372. return d[this.valueKey]
  373. })
  374. }
  375. },
  376. getStoreValue (value) {
  377. const res = []
  378. if (this.store === 'json') { // json
  379. if (this.$utils.isEmpty(value)) {
  380. return ''
  381. }
  382. value.forEach(v => {
  383. const o = {}
  384. o[this.valueKey] = v[this.valueKey]
  385. o[this.labelKey] = v[this.labelKey]
  386. res.push(o)
  387. })
  388. return JSON.stringify(res)
  389. } else if (this.store === 'id') { // id
  390. if (this.$utils.isEmpty(value)) {
  391. return ''
  392. }
  393. value.forEach(v => {
  394. res.push(v[this.valueKey])
  395. })
  396. return res.join(this.storeSeparator)
  397. } else { // 数组 array
  398. return value || []
  399. }
  400. },
  401. getValue () {
  402. return this.getStoreValue(this.fileList)
  403. },
  404. showTips (item, index) {
  405. this.uid = item.uid
  406. // item.isActive = true
  407. },
  408. hideTips (item, index) {
  409. this.uid = ''
  410. },
  411. /**
  412. * 文件上传
  413. */
  414. httpRequest (options) {
  415. return uploadFile(options.file, {}).then((response) => {
  416. const data = response.data
  417. this.setCacheData(data)
  418. this.fileList.push(data)
  419. })
  420. },
  421. /**
  422. * 上传方式为ibps附件上传时
  423. */
  424. clickAttachmentUpload () {
  425. this.uploaderSelectorVisible = true
  426. },
  427. handleSuccess (response, file, fileList) {
  428. if (!this.disabled) {
  429. this.fileList = fileList.map(item => {
  430. item.isActive = false
  431. return item
  432. })
  433. }
  434. },
  435. // handleChange(file, fileList) {
  436. // this.fileList = fileList
  437. // },
  438. // handleRemove(file, fileList) {
  439. // this.fileList = fileList
  440. // },
  441. // 预览
  442. clickHandler (file, index) {
  443. this.zIndex = this.fixZIndex()
  444. this.initialIndex = index
  445. this.showViewer = true
  446. },
  447. closeViewer () {
  448. this.showViewer = false
  449. },
  450. /**
  451. * 删除图片
  452. */
  453. onDeleteImage (index) {
  454. // if (this.uploadType === 'default') {
  455. // TODD:删除同时删除数据库的
  456. // } else {
  457. this.fileList.splice(index, 1)
  458. // }
  459. },
  460. // 下载
  461. onDownloadImage (index) {
  462. this.setRealFileList(this.fileList[index][this.valueKey])
  463. this.$nextTick(() => {
  464. downloadFile(this.realFileList[index])
  465. })
  466. },
  467. // 图片上传数量限制
  468. handlePicAmount (files, fileList) {
  469. if (this.multiple && this.limit) {
  470. this.$message.closeAll()
  471. this.$message({
  472. message: `图片上传上限${this.limit}张`,
  473. type: 'warning'
  474. })
  475. }
  476. },
  477. // 格式、大小限制
  478. beforeUpload (file) {
  479. let isType = true
  480. if (this.$utils.isNotEmpty(this.accept) && this.accept !== '*') {
  481. const acceptType = this.converFileAccept(this.accept)
  482. isType = this.accept ? acceptType.includes(file.type) : true
  483. if (!isType) {
  484. this.$message.closeAll()
  485. this.$message.error(`上传图片的格式必须为【${this.accept}】`)
  486. return false
  487. }
  488. }
  489. const isLimitSize = this.size ? (file.size / 1024 / 1024 < this.size) : true
  490. if (!isLimitSize) {
  491. this.$message.closeAll()
  492. this.$message.error(`上传图片的大小不能超过 ${this.size}M!`)
  493. return false
  494. }
  495. return isLimitSize && isType
  496. },
  497. handleUpload (buttonKey, data) {
  498. const limit = parseInt(this.limit)
  499. if (this.$utils.isNotEmpty(this.limit) && data.length > limit) {
  500. this.$message.closeAll()
  501. this.$message.error(`图片上传上限${this.limit}张`)
  502. return
  503. }
  504. if (this.$utils.isNotEmpty(this.fileList) && (this.fileList.length >= limit || this.fileList.length + data.length > limit)) {
  505. this.$message.closeAll()
  506. this.$message.error(`图片数量上限为${this.limit}张`)
  507. return
  508. }
  509. data.forEach(d => {
  510. this.setCacheData(d)
  511. this.fileList.push(d)
  512. })
  513. this.uploaderSelectorVisible = false
  514. },
  515. getImageUrl (id) {
  516. return previewFile(id)
  517. }
  518. }
  519. }
  520. </script>
  521. <style lang="scss">
  522. .ibps-image {
  523. font-size: 28px;
  524. .el-upload-list--picture-card {
  525. display: none;
  526. }
  527. .el-upload--picture-card {
  528. width: inherit;
  529. height: inherit;
  530. line-height: inherit;
  531. }
  532. .image-reader-item {
  533. position: relative;
  534. float: left;
  535. width: 23.5%;
  536. margin-bottom: 2%;
  537. margin-right: 2%;
  538. background: #FFF;
  539. box-shadow: 0 5px 20px rgba(197, 202, 213, .25);
  540. box-sizing: border-box;
  541. list-style: none;
  542. border-radius: 4px;
  543. background-size: cover;
  544. overflow: hidden;
  545. .image-reader-item-tag {
  546. background: #111A34;
  547. color: #fff;
  548. float: right;
  549. border-radius: 0;
  550. padding: 0 10px;
  551. border: 0;
  552. cursor: pointer;
  553. }
  554. .draggable {
  555. cursor: move;
  556. }
  557. .image-tip-visible {
  558. display: none
  559. }
  560. .image-tip {
  561. position: absolute;
  562. bottom: 0;
  563. color: #fff;
  564. background: #111A34;
  565. font-size: 12px;
  566. width: 100%;
  567. }
  568. }
  569. }
  570. </style>