index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. <template>
  2. <div class="more-img-uploade">
  3. <draggable
  4. :list="previewList"
  5. tag="ul"
  6. class="img-list"
  7. v-bind="dragOptions"
  8. draggable=".item"
  9. @update="updateDrag"
  10. >
  11. <li v-for="(item, index) in previewList" :key="index" class="item">
  12. <div class="el-upload-list el-upload-list--picture-card flex">
  13. <auth-img v-if="!isPublic" :auth-src="item.thumbUrl" image-width="80px" image-height="80px"></auth-img>
  14. <el-image v-if="isPublic" :src="item.thumbUrl" fit="contain" style="width: 80px;height: 80px;"></el-image>
  15. <label class="el-upload-list__item-status-label"><i class="el-icon-upload-success el-icon-check"></i></label>
  16. <span class="el-upload-list__item-actions">
  17. <span class="el-upload-list__item-preview"><i class="el-icon-zoom-in" @click="handlePreview(item)"></i></span>
  18. <span class="el-upload-list__item-delete"><i class="el-icon-delete" @click="handleDelete(item)"></i></span>
  19. </span>
  20. </div>
  21. </li>
  22. <el-upload
  23. :action="fileSaveUrl"
  24. list-type="picture-card"
  25. :data="reqData"
  26. :on-success="fileUploadSuccess"
  27. :before-upload="handleBeforeUpload"
  28. :limit="limit"
  29. :on-error="handleUploadError"
  30. :on-exceed="handleExceed"
  31. name="file"
  32. multiple
  33. :on-remove="handleRemove"
  34. :show-file-list="false"
  35. :headers="headers"
  36. :http-request="reqUploadImage"
  37. :file-list="previewList"
  38. :on-preview="handlePictureCardPreview"
  39. :auto-upload="true"
  40. :class="{hide: this.fileList.length >= this.limit}">
  41. <i class="el-icon-plus"></i>
  42. </el-upload>
  43. </draggable>
  44. <el-dialog :visible.sync="dialogVisible" title="预览" width="800" append-to-body>
  45. <div style="display: block; max-width: 100%; margin: 0 auto;text-align: center;max-height: 435px;overflow: auto;">
  46. <auth-img v-if="!isPublic && dialogVisible" :auth-src="dialogImageUrl" image-width="435px" ></auth-img>
  47. <img v-if="isPublic" :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
  48. </div>
  49. </el-dialog>
  50. </div>
  51. </template>
  52. <script>
  53. import draggable from "vuedraggable";
  54. import { getToken, getSign } from "@/utils/auth";
  55. import { randomStr20 } from "@/utils/util";
  56. import {
  57. privateFileSaveUrl,
  58. publicFileSaveUrl,
  59. publicFileSaveAPI,
  60. privateFileSaveAPI,
  61. publicFileGetUrl,
  62. privateFileGetUrl,
  63. } from "@/api/common";
  64. import AuthImg from "@/components/AuthImg/index.vue";
  65. import UploadImg from "@/components/UploadImg/index.vue";
  66. export default {
  67. components: {
  68. draggable,
  69. AuthImg,
  70. UploadImg
  71. },
  72. props: {
  73. // 已经上传的文件url列表
  74. value: [String, Object, Array],
  75. // 图片数量限制
  76. limit: {
  77. type: Number,
  78. default: 5,
  79. },
  80. // 大小限制(MB)
  81. fileSize: {
  82. type: Number,
  83. default: 5,
  84. },
  85. // 文件类型, 例如['png', 'jpg', 'jpeg']
  86. fileType: {
  87. type: Array,
  88. default: () => ["png", "jpg", "jpeg"],
  89. },
  90. // 是否显示提示
  91. isShowTip: {
  92. type: Boolean,
  93. default: true
  94. },
  95. // 是否显示提示
  96. isPublic: {
  97. type: Boolean,
  98. default: true
  99. },
  100. // 半高显示
  101. low: {
  102. type: Boolean,
  103. default: false
  104. }
  105. },
  106. data() {
  107. return {
  108. dialogImageUrl: "",
  109. dialogVisible: false,
  110. hideUpload: false,
  111. baseUrl: process.env.VUE_APP_BASE_API,
  112. uploadImgUrl: this.isPublic ? publicFileSaveUrl : privateFileSaveUrl, // 上传的图片服务器地址
  113. fileSaveUrl: '',
  114. headers: {
  115. Authorization: "Bearer " + getToken(),
  116. },
  117. fileList: [],
  118. previewList: [],
  119. reqData: {}
  120. };
  121. },
  122. data() {
  123. return {
  124. dialogImageUrl: "",
  125. dialogVisible: false,
  126. hideUpload: false,
  127. baseUrl: process.env.VUE_APP_BASE_API,
  128. uploadImgUrl: this.isPublic ? publicFileSaveUrl : privateFileSaveUrl, // 上传的图片服务器地址
  129. fileSaveUrl: '',
  130. headers: {
  131. Authorization: "Bearer " + getToken(),
  132. },
  133. fileList: [],
  134. previewList: [],
  135. reqData: {}
  136. };
  137. },
  138. watch: {
  139. value(val) {
  140. if (val) {
  141. this.initHandle()
  142. }
  143. }
  144. },
  145. computed: {
  146. // 拖拽属性
  147. dragOptions() {
  148. return {
  149. animation: 200, // 动画时间
  150. disabled: false, // false可拖拽,true不可拖拽
  151. group: "description",
  152. ghostClass: "ghost",
  153. };
  154. },
  155. },
  156. methods: {
  157. // 拖拽完成
  158. updateDrag(e){
  159. this.fileList = this.previewList.map(item => {
  160. return {
  161. fileName: item.name
  162. }
  163. })
  164. this.$emit("input", this.fileList);
  165. this.$emit('change')
  166. },
  167. initHandle() {
  168. if (this.value) {
  169. // 首先将值转为数组
  170. const list = Array.isArray(this.value) ? this.value : this.value.split(',')
  171. // 然后将数组转为对象数组
  172. this.previewList = []
  173. this.fileList = list.map(item => {
  174. var pitem = {
  175. name: item.fileName,
  176. url: (this.isPublic ? publicFileGetUrl : (this.baseUrl + privateFileGetUrl)) + item.fileName,
  177. thumbUrl: (this.isPublic ? publicFileGetUrl : (this.baseUrl + privateFileGetUrl)) + item.fileName + "?imageView2/2/w/375"
  178. };
  179. this.previewList.push(pitem)
  180. return item;
  181. });
  182. } else {
  183. this.previewList = []
  184. this.fileList = []
  185. }
  186. },
  187. handlePreview(item){
  188. this.dialogVisible = true;
  189. this.dialogImageUrl = item.url
  190. },
  191. handleDelete(item){
  192. const findex = this.fileList.map(f => f.fileName).indexOf(item.name);
  193. this.fileList.splice(findex, 1);
  194. this.previewList.splice(findex, 1);
  195. this.$emit("input", this.fileList);
  196. this.$emit('change')
  197. },
  198. // 删除图片
  199. handleRemove(file, fileList) {
  200. const findex = this.fileList.map(f => f.name).indexOf(file.name);
  201. this.fileList.splice(findex, 1);
  202. this.previewList.splice(findex, 1);
  203. this.$emit("input", this.fileList);
  204. this.$emit('change')
  205. },
  206. /**
  207. * 上传图片
  208. */
  209. fileUploadSuccess(response, file, fileList) {
  210. this.fileList.push({
  211. fileName: response.fileName,
  212. fileType: response.fileType
  213. });
  214. this.previewList.push({
  215. name: response.fileName,
  216. url: (this.isPublic ? publicFileGetUrl : (this.baseUrl + privateFileGetUrl)) + response.fileName,
  217. fileType: response.fileType
  218. });
  219. this.$emit("input", this.fileList);
  220. this.$emit('change')
  221. this.loading.close();
  222. },
  223. // 上传前loading加载
  224. handleBeforeUpload(file) {
  225. let isImg = false;
  226. if (this.fileType.length) {
  227. let fileExtension = "";
  228. if (file.name.lastIndexOf(".") > -1) {
  229. fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
  230. }
  231. isImg = this.fileType.some(type => {
  232. if (file.type.indexOf(type) > -1) return true;
  233. if (fileExtension && fileExtension.indexOf(type) > -1) return true;
  234. return false;
  235. });
  236. } else {
  237. isImg = file.type.indexOf("image") > -1;
  238. }
  239. if (!isImg) {
  240. this.$message.error(
  241. `文件格式不正确, 请上传${this.fileType.join("/")}图片格式文件!`
  242. );
  243. return false;
  244. }
  245. if (this.fileSize) {
  246. const isLt = file.size / 1024 / 1024 < this.fileSize;
  247. if (!isLt) {
  248. this.$message.error(`上传图片大小不能超过 ${this.fileSize} MB!`);
  249. return false;
  250. }
  251. }
  252. this.getHttpHeader()
  253. this.loading = this.$loading({
  254. lock: true,
  255. text: "上传中",
  256. background: "rgba(0, 0, 0, 0.7)",
  257. });
  258. },
  259. // 自定义文件上传的实现
  260. reqUploadImage(param) {
  261. var data = this.reqData || {}
  262. var params = {
  263. file: param.file,
  264. ...data
  265. }
  266. const request = this.isPublic ? publicFileSaveAPI : privateFileSaveAPI
  267. request(params, this.headers).then(response => {
  268. var res = {
  269. fileName: response.data.fileName,
  270. fileType: response.data.fileType
  271. }
  272. this.fileList.push({
  273. fileName: response.data.fileName,
  274. fileType: response.data.fileType
  275. });
  276. this.previewList.push({
  277. name: response.data.fileName,
  278. url: (this.isPublic ? publicFileGetUrl : (this.baseUrl + privateFileGetUrl)) + response.data.fileName,
  279. fileType: response.fileType
  280. });
  281. this.$emit("input", this.fileList);
  282. this.$emit('change')
  283. this.loading.close();
  284. // 但是我们上传成功了图片, fileList 里面的值却没有改变,还好有on-change指令可以使用
  285. }).catch(response => {
  286. param.onError()
  287. })
  288. },
  289. // 请求头
  290. getHttpHeader() {
  291. let timestamp = parseInt(new Date().getTime()),
  292. nonce = randomStr20();
  293. var sign = getSign(this.reqData || {}, timestamp)
  294. let url = this.uploadImgUrl + '?sign=' + sign + '&nonce=' + nonce;
  295. this.fileSaveUrl = url
  296. var headers = {
  297. "Authorization": "Bearer " + getToken(),
  298. "x-zz-timestamp": timestamp
  299. }
  300. this.headers = headers
  301. },
  302. // 文件个数超出
  303. handleExceed() {
  304. this.$message.error(`上传文件数量不能超过 ${this.limit} 个!`);
  305. },
  306. // 上传失败
  307. handleUploadError() {
  308. // this.$message({
  309. // type: "error",
  310. // message: "上传失败",
  311. // });
  312. this.loading.close();
  313. },
  314. // 预览
  315. handlePictureCardPreview(file) {
  316. this.dialogImageUrl = file.url;
  317. this.dialogVisible = true;
  318. },
  319. // 对象转成指定字符串分隔
  320. listToString(list, separator) {
  321. let strs = "";
  322. separator = separator || ",";
  323. for (let i in list) {
  324. strs += list[i].url.replace(this.baseUrl, "") + separator;
  325. }
  326. return strs != '' ? strs.substr(0, strs.length - 1) : '';
  327. }
  328. }
  329. };
  330. </script>
  331. <style lang="scss" scoped>
  332. .flex{
  333. display: flex;
  334. align-items: center;
  335. justify-content: center;
  336. }
  337. // .el-upload--picture-card 控制加号部分
  338. ::v-deep.hide .el-upload--picture-card {
  339. display: none;
  340. }
  341. ::v-deep .el-upload--picture-card{
  342. display: flex;
  343. align-items: center;
  344. justify-content: center;
  345. width: 80px;
  346. height: 80px;
  347. line-height: 80px;
  348. }
  349. </style>
  350. <style scoped>
  351. .img-list {
  352. width: 100%;
  353. display: flex;
  354. flex-wrap: wrap;
  355. padding: 0;
  356. }
  357. .img-list li,
  358. .img-li {
  359. display: flex;
  360. align-items: center;
  361. justify-content: center;
  362. width: 80px;
  363. height: 80px;
  364. margin: 0 10px 10px;
  365. border: 1px dashed #d9d9d9;
  366. border-radius: 6px;
  367. cursor: pointer;
  368. position: relative;
  369. overflow: hidden;
  370. }
  371. .img-li {
  372. float: left;
  373. }
  374. .img-list li img {
  375. width: 100%;
  376. height: 100%;
  377. object-fit: contain;
  378. background: #000;
  379. }
  380. .upload-class {
  381. font-size: 28px;
  382. color: #8c939d;
  383. text-align: center;
  384. height: 120px;
  385. width: 180px;
  386. line-height: 120px;
  387. box-sizing: border-box;
  388. }
  389. </style>