ImgUpload.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. <template>
  2. <div>
  3. <div v-if="type !== 'callback'" class="imgcomp-upload-list" v-for="item in uploadList">
  4. <template v-if="item.status === 'finished'">
  5. <div class="imgcomp-upload-img" v-bind:style="{ 'background-image': 'url(' + __thumb(item.thumb) + ')' }"></div>
  6. <div class="imgcomp-upload-list-cover">
  7. <Icon type="ios-eye-outline" @click.native="handleView(item)"></Icon>
  8. <Icon type="ios-trash-outline" @click.native="handleRemove(item)"></Icon>
  9. </div>
  10. </template>
  11. <template v-else>
  12. <Progress v-if="item.showProgress" :percent="item.percentage" hide-info></Progress>
  13. </template>
  14. </div>
  15. <div class="add-box" v-bind:class="{ 'callback-add-box': type === 'callback' }">
  16. <div class="add-box-icon">
  17. <Icon type="md-add" size="32"></Icon>
  18. </div>
  19. <div class="add-box-upload">
  20. <div class="add-box-item" @click="browsePicture">
  21. <span>{{$L('浏览')}}<em v-if="type === 'callback'">{{$L('图片')}}</em></span>
  22. </div>
  23. <div class="add-box-item">
  24. <Upload
  25. name="image"
  26. ref="upload"
  27. accept="image/*"
  28. :action="actionUrl"
  29. :data="uploadParams"
  30. :show-upload-list="false"
  31. :max-size="maxSize"
  32. :format="['jpg', 'jpeg', 'gif', 'png']"
  33. :default-file-list="defaultList"
  34. :on-progress="handleProgress"
  35. :on-success="handleSuccess"
  36. :on-error="handleError"
  37. :on-format-error="handleFormatError"
  38. :on-exceeded-size="handleMaxSize"
  39. :before-upload="handleBeforeUpload"
  40. :multiple=multiple>
  41. <span>{{$L('上传')}}<em v-if="type === 'callback'">{{$L('图片')}}</em></span>
  42. </Upload>
  43. </div>
  44. </div>
  45. </div>
  46. <Modal :title="$L('浏览图片空间的图片')" v-model="browseVisible" class="img-upload-modal" class-name="simple-modal" width="710" :styles="{top: '35px',paddingBottom: '35px'}">
  47. <div class="browse-load" v-if="isLoading">{{$L('加载中...')}}</div>
  48. <div class="browse-list" :class="httpType==='input'?'browse-list-disabled':''" ref="browselistbox">
  49. <div class="browse-item" v-for="item in browseList" @click="browseItem(item)">
  50. <Icon v-if="item.active" class="browse-icon" type="ios-checkmark-circle"></Icon>
  51. <div class="browse-img" v-bind:style="{ 'background-image': 'url(' + item.thumb + ')' }"></div>
  52. <div class="browse-title">{{item.title}}</div>
  53. </div>
  54. </div>
  55. <div slot="footer" class="img-upload-foot">
  56. <div v-if="type !== 'callback' && http && httpType===''" class="img-upload-foot-input" @click="httpType='input'">
  57. <Icon type="ios-image" size="22"/>
  58. <div class="img-upload-foot-httptitle">{{$L('自定义图片地址')}}</div>
  59. </div>
  60. <div v-if="type !== 'callback' && http && httpType==='input'" class="img-upload-foot-input">
  61. <Input v-model="httpValue" :placeholder="$L('以“http://”或“https://”开头')" @on-search="httpEnter" search :enter-button="$L('确定')">
  62. <span slot="prepend" @click="httpType=''" style="cursor:pointer">{{$L('自定义地址')}}: </span>
  63. </Input>
  64. </div>
  65. <Button v-if="httpType===''" @click="browseVisible=false">{{$L('关闭')}}</Button>
  66. <Button v-if="httpType===''" type="primary" @click="handleCallback(true)">{{$L('完成')}}</Button>
  67. </div>
  68. </Modal>
  69. <Modal :title="$L('查看图片')" v-model="visible" class="img-upload-modal" class-name="simple-modal" draggable>
  70. <div style="max-height:480px;overflow:auto;">
  71. <a :href="imgVisible" target="_blank"><img :src="imgVisible" v-if="visible" style="max-width:100%;max-height:900px;display:block;margin:0 auto"></a>
  72. </div>
  73. </Modal>
  74. </div>
  75. </template>
  76. <style lang="scss">
  77. .img-upload-modal {
  78. .ivu-modal-mask {
  79. z-index: 1001;
  80. }
  81. .ivu-modal-no-mask {
  82. background-color: rgba(55,55,55,.2);
  83. }
  84. .ivu-modal-wrap {
  85. z-index: 1001;
  86. }
  87. }
  88. .imgcomp-upload-list{
  89. display: inline-block;
  90. width: 60px;
  91. height: 60px;
  92. text-align: center;
  93. line-height: 60px;
  94. border: 1px solid transparent;
  95. border-radius: 4px;
  96. overflow: hidden;
  97. background: #fff;
  98. position: relative;
  99. box-shadow: 0 1px 1px rgba(0,0,0,.2);
  100. margin-right: 4px;
  101. vertical-align: top;
  102. .imgcomp-upload-img {
  103. position: absolute;
  104. top: 0;
  105. left: 0;
  106. width: 100%;
  107. height: 100%;
  108. background-position: center;
  109. background-size: cover;
  110. }
  111. .imgcomp-upload-list-cover{
  112. display: none;
  113. position: absolute;
  114. top: 0;
  115. bottom: 0;
  116. left: 0;
  117. right: 0;
  118. background: rgba(0,0,0,.6);
  119. }
  120. .imgcomp-upload-list-cover i{
  121. color: #fff;
  122. font-size: 24px;
  123. cursor: pointer;
  124. vertical-align: middle;
  125. margin: 0;
  126. transition: all .2s;
  127. }
  128. .imgcomp-upload-list-cover i:hover{
  129. font-size: 28px;
  130. }
  131. .ivu-progress-outer {
  132. background-color: rgba(0, 0, 0, 0.68);
  133. .ivu-progress-inner{
  134. width: 88%;
  135. }
  136. }
  137. }
  138. .imgcomp-upload-list:hover .imgcomp-upload-list-cover{
  139. display: block;
  140. }
  141. .img-upload-foot {
  142. display: flex;
  143. align-items: center;
  144. justify-content: flex-end;
  145. .img-upload-foot-input {
  146. flex: 1;
  147. text-align: left;
  148. display: flex;
  149. align-items: center;
  150. justify-content: flex-end;
  151. .img-upload-foot-httptitle {
  152. cursor: pointer;
  153. padding-left: 3px;
  154. margin-right: 22px;
  155. }
  156. }
  157. }
  158. .add-box {
  159. width: 60px;
  160. height: 60px;
  161. line-height: 60px;
  162. display: inline-block;
  163. background: #fff;
  164. border: 1px dashed #dddee1;
  165. border-radius: 4px;
  166. text-align: center;
  167. position: relative;
  168. overflow: hidden;
  169. vertical-align: top;
  170. .add-box-icon {
  171. i {
  172. vertical-align:middle;
  173. padding-bottom: 2px;
  174. }
  175. }
  176. .add-box-upload {
  177. display: none;
  178. position: absolute;
  179. top: 0;
  180. left: 0;
  181. height: 100%;
  182. width: 100%;
  183. color: #ffffff;
  184. padding-top: 9px;
  185. background: rgba(0, 0, 0, 0.6);
  186. .add-box-item {
  187. height: 22px;
  188. line-height: 22px;
  189. cursor: pointer;
  190. .ivu-upload-drag,.ivu-upload-drag:hover {
  191. background:transparent;
  192. border:0;
  193. border-radius:0;
  194. }
  195. span {
  196. transition: all .2s;
  197. font-size: 12px;
  198. }
  199. }
  200. .add-box-item:hover {
  201. span {
  202. font-size: 14px;
  203. }
  204. }
  205. }
  206. em {
  207. font-style: normal;
  208. }
  209. }
  210. .add-box:hover {
  211. border-color: rgba(0,0,0,.6);
  212. .add-box-upload {
  213. display: block;
  214. }
  215. }
  216. .callback-add-box {
  217. display: block;
  218. width: auto;
  219. height: 25px;
  220. line-height: 25px;
  221. border: 0;
  222. background: transparent;
  223. .add-box-icon {
  224. display: none;
  225. }
  226. .add-box-upload {
  227. display: block;
  228. width: auto;
  229. background: transparent;
  230. color: #333;
  231. padding: 0;
  232. > div {
  233. display: inline-block;
  234. padding-right: 10px;
  235. }
  236. }
  237. }
  238. .browse-load {
  239. margin: 20px;
  240. text-align: center;
  241. }
  242. .browse-list {
  243. max-height: 540px;
  244. overflow: auto;
  245. .browse-item {
  246. margin: 10px 15px;
  247. display: inline-block;
  248. text-align: center;
  249. cursor: pointer;
  250. position: relative;
  251. .browse-img {
  252. width: 64px;
  253. height: 64px;
  254. background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKoAAABxCAAAAABg5GeyAAACW0lEQVR4Ae3XVRLjMBAE0L3/rSwKM3OcSNPyLVYOLvM6UD0Bd03LVe9XH+RlhlRSSSWVVFJJJZVUUkkllVRSSSWVVFJJJZVUUkl9WyqppJJKKqmkkgpURP17xngOAR5NxW5wlJ9MaLQh83F4NHWmd/gZtdVBaOldfDB1bq5UpJFbFOC6LKnYrkRO209PAw+hIuzWB8Ep5es8HvYo4z4tE1X8UeRwlMM2D5Bzkc7kj6Bi3VTKDDwEeUcrMxrUvGDXTnHa6kK69SDN9sgq1clxKSbNHqqnYmdri81Q9QHf1JPt1Frncaib2XbiTKL2GkHaurnY9LOulMV0O7G6Kw+g9sw2ohhm62KezVJaaufjWC1TnOkr1exilJ7Ji0vxCCqO9V4UwV4PYr9+apouhGYLKfnahdpqegjmeoXOpXgANe70pKT6Zhu19qkY2nC0PZS527lQOyInqr8Uvc5jqfUb1X+PGh5IhW90S2quh3FQC2XRcF66TUkTXPcLKm5FtdR9RJq+2hWII7UpFtmsQLEyzsdJtkxxpr6gLotbUSlV9yeT0Trmzk2XPdUThLYarUbWOY9j04xXQ2u+pMZLYSumGmNUH3HbM9qOAwSHodN2Pks25F2j3aI7+IxqNsB+YLWb16ukSjiW4xNB0gMoMfApBS/XZQgi3p9/5RsiKNKZEOwYFVIF5VyTyD19sbyjIJiNJRZxpNbx2S8sGKvGZNHJBniBu9Wy5WxjGuQFqIAcBHiRGyt4ua5gSCWVVFJJJZVUUkkllVRSSSWVVFJJJZVUUkkllVRSSSWVVFI/AgO0SXIVYHeGAAAAAElFTkSuQmCC);
  255. background-position: center;
  256. background-repeat: no-repeat;
  257. background-size: cover;
  258. }
  259. .browse-title {
  260. display: block;
  261. width: 64px;
  262. margin-top: 5px;
  263. white-space: nowrap;
  264. overflow: hidden;
  265. text-overflow: ellipsis;
  266. }
  267. .browse-icon {
  268. position: absolute;
  269. top: 0;
  270. left: 0;
  271. width: 100%;
  272. height: 64px;
  273. font-size: 36px;
  274. padding-top: 15px;
  275. color: #ffffff;
  276. background-color: rgba(0, 0, 0, 0.5);
  277. }
  278. }
  279. }
  280. .browse-list-disabled {
  281. position: relative;
  282. }
  283. .browse-list-disabled:after {
  284. position: absolute;
  285. content: '';
  286. top: 0;
  287. left: 0;
  288. width: 100%;
  289. height: 100%;
  290. background-color: rgba(255, 255, 255, 0.9);
  291. z-index: 1;
  292. }
  293. </style>
  294. <script>
  295. export default {
  296. name: 'ImgUpload',
  297. props: {
  298. value: {
  299. },
  300. num: {
  301. },
  302. width: {
  303. },
  304. height: {
  305. },
  306. type: {
  307. },
  308. http: {
  309. type: Boolean,
  310. default: false
  311. },
  312. otherParams: {
  313. type: Object,
  314. default: () => {
  315. return {};
  316. }
  317. },
  318. uploadIng: {
  319. type: Number,
  320. default: 0
  321. }
  322. },
  323. data () {
  324. return {
  325. actionUrl: $A.apiUrl('system/imgupload'),
  326. params: {
  327. token: $A.getToken(),
  328. width: this.width,
  329. height: this.height
  330. },
  331. multiple: this.num > 1,
  332. visible: false,
  333. browseVisible: false,
  334. isLoading: false,
  335. browseList: [],
  336. browseListNext: [],
  337. imgVisible: '',
  338. defaultList: this.initItems(this.value),
  339. uploadList: [],
  340. maxNum: Math.min(Math.max($A.runNum(this.num), 1), 99),
  341. httpValue: '',
  342. httpType: '',
  343. maxSize: 204800
  344. }
  345. },
  346. mounted () {
  347. this.uploadList = this.$refs.upload.fileList;
  348. this.$emit('input', this.uploadList);
  349. //
  350. let browseBox = $A(this.$refs.browselistbox);
  351. browseBox.scroll(()=>{
  352. let nHight = browseBox[0].scrollHeight;
  353. let nTop = browseBox[0].scrollTop;
  354. let boxHight = browseBox.height();
  355. if(nTop + boxHight >= nHight) {
  356. //到底了
  357. if (this.browseListNext.length > 0) {
  358. let tmpNext = this.browseListNext;
  359. this.browseListNext = [];
  360. this.browsePictureFor(tmpNext);
  361. }
  362. }
  363. });
  364. },
  365. watch: {
  366. value (val) {
  367. if (typeof val === 'string') {
  368. this.$emit('input', this.initItems(val));
  369. return;
  370. }
  371. if (val === this.$refs.upload.fileList) {
  372. return;
  373. }
  374. this.$refs.upload.fileList = this.initItems(val);
  375. this.uploadList = this.$refs.upload.fileList;
  376. },
  377. browseVisible() {
  378. this.httpType = '';
  379. this.httpValue = '';
  380. }
  381. },
  382. computed: {
  383. uploadParams() {
  384. if (Object.keys(this.otherParams).length > 0) {
  385. return Object.assign(this.params, this.otherParams);
  386. } else {
  387. return this.params;
  388. }
  389. }
  390. },
  391. methods: {
  392. handleCallback(file) {
  393. if (this.type === 'callback') {
  394. if (file === true) {
  395. this.$emit('on-callback', this.uploadList);
  396. this.$refs.upload.fileList = [];
  397. this.uploadList = this.$refs.upload.fileList;
  398. }else if (typeof file === "object") {
  399. this.$emit('on-callback', [file]);
  400. }
  401. }
  402. this.browseVisible = false;
  403. },
  404. initItems(items) {
  405. //数据初始化
  406. if (typeof items === 'string') {
  407. items = [{'url': items}];
  408. }
  409. let lists = [];
  410. $A.each(items, (index, item)=>{
  411. if (typeof item === 'string') item = {'url': item};
  412. if (item.url) {
  413. item.active = true;
  414. item.status = 'finished';
  415. if (typeof item.path === 'undefined') item.path = item.url;
  416. if (typeof item.thumb === 'undefined') item.thumb = item.url;
  417. lists.push(item);
  418. }
  419. });
  420. return lists;
  421. },
  422. handleView (item) {
  423. //查看
  424. this.visible = true;
  425. this.imgVisible = item.url;
  426. },
  427. handleRemove (item) {
  428. //删除
  429. let fileList = this.$refs.upload.fileList;
  430. this.$refs.upload.fileList.splice(fileList.indexOf(item), 1);
  431. this.$emit('input', this.$refs.upload.fileList);
  432. },
  433. handleProgress() {
  434. //开始上传
  435. this.$emit('update:uploadIng', this.uploadIng + 1);
  436. },
  437. handleSuccess (res, file) {
  438. //上传完成
  439. this.$emit('update:uploadIng', this.uploadIng - 1);
  440. if (res.ret === 1) {
  441. file.url = res.data.url;
  442. file.path = res.data.path;
  443. file.thumb = res.data.thumb;
  444. this.handleCallback(file);
  445. }else{
  446. this.$Modal.warning({
  447. title: this.$L('上传失败'),
  448. content: this.$L('文件 % 上传失败 %', file.name, res.msg)
  449. });
  450. this.$refs.upload.fileList.pop();
  451. }
  452. this.$emit('input', this.$refs.upload.fileList);
  453. },
  454. handleError() {
  455. //上传错误
  456. this.$emit('update:uploadIng', this.uploadIng - 1);
  457. },
  458. handleFormatError (file) {
  459. //上传类型错误
  460. this.$Modal.warning({
  461. title: this.$L('文件格式不正确'),
  462. content: this.$L('文件 % 格式不正确,请上传 jpg、jpeg、gif、png 格式的图片。', file.name)
  463. });
  464. },
  465. handleMaxSize (file) {
  466. //上传大小错误
  467. this.$Modal.warning({
  468. title: this.$L('超出文件大小限制'),
  469. content: this.$L('文件 % 太大,不能超过%。', file.name, $A.bytesToSize(this.maxSize * 1024))
  470. });
  471. },
  472. handleBeforeUpload () {
  473. //上传前判断
  474. let check = this.uploadList.length < this.maxNum;
  475. if (!check && this.uploadList.length == 1) {
  476. this.handleRemove(this.uploadList[0]);
  477. check = this.uploadList.length < this.maxNum;
  478. }
  479. if (!check) {
  480. this.$Modal.warning({
  481. title: this.$L('温馨提示'),
  482. content: this.$L('最多只能上传 % 张图片。', this.maxNum)
  483. });
  484. }
  485. this.params = {
  486. token: $A.getToken(),
  487. width: this.width,
  488. height: this.height
  489. };
  490. return check;
  491. },
  492. handleClick() {
  493. //手动上传
  494. if (this.handleBeforeUpload()) {
  495. this.$refs.upload.handleClick()
  496. }
  497. },
  498. handleManual(file) {
  499. //手动传file
  500. if (this.handleBeforeUpload()) {
  501. this.$refs.upload.upload(file);
  502. }
  503. },
  504. browsePicture(path) {
  505. //获取图片空间
  506. this.browseVisible = true;
  507. this.browseList = [];
  508. this.browseListNext = [];
  509. this.isLoading = true;
  510. $A.apiAjax({
  511. url: 'system/imgview',
  512. data: { path: path?path:'' },
  513. beforeSend: true,
  514. complete: true,
  515. error: true,
  516. success: (res) => {
  517. this.isLoading = false;
  518. if (res.ret === 1) {
  519. let dirs = res.data['dirs'];
  520. for (let i = 0; i < dirs.length; i++) {
  521. this.browseList.push(dirs[i]);
  522. }
  523. this.browsePictureFor(res.data['files']);
  524. }else if (res.ret === -2) {
  525. this.browseVisible = false;
  526. this.$Modal.warning({ title: this.$L('温馨提示'), content: res.msg });
  527. }
  528. }
  529. });
  530. },
  531. browsePictureFor(files) {
  532. for (let o = 0; o < files.length; o++) {
  533. for (let j = 0; j < this.uploadList.length; j++) {
  534. if (this.uploadList[j]['url'] === files[o]['url']
  535. || this.uploadList[j]['url'] === files[o]['path']) {
  536. files[o]['active'] = true;
  537. break;
  538. }
  539. }
  540. if (o < 100) {
  541. this.browseList.push(files[o]);
  542. }else{
  543. this.browseListNext.push(files[o]);
  544. }
  545. }
  546. },
  547. browseItem(item) {
  548. //点击选择图片
  549. if (item.type === 'dir') {
  550. //目录
  551. this.browsePicture(item.path);
  552. }else if (item.type === 'file') {
  553. //文件
  554. if (item.active) {
  555. let fileList = this.$refs.upload.fileList;
  556. this.$refs.upload.fileList.splice(fileList.indexOf(item), 1);
  557. item.active = false;
  558. }else{
  559. if (this.maxNum === 1) {
  560. for (let i = 0; i < this.browseList.length; i++) {
  561. this.browseList[i].active = false;
  562. }
  563. this.$refs.upload.fileList = [];
  564. this.uploadList = this.$refs.upload.fileList;
  565. }
  566. let check = this.uploadList.length < this.maxNum;
  567. if (!check) {
  568. this.$Modal.warning({ title: this.$L('温馨提示'), content: this.$L('最多只能选择 % 张图片。', this.maxNum) });
  569. return;
  570. }
  571. item.active = true;
  572. item.status = 'finished';
  573. this.$refs.upload.fileList.push(item);
  574. this.uploadList = this.$refs.upload.fileList;
  575. }
  576. this.$emit('input', this.$refs.upload.fileList);
  577. }
  578. },
  579. __thumb(url) {
  580. if ($A.strExists(url, "?", false)) {
  581. return url + "&__thumb=true";
  582. }else{
  583. return url + "?__thumb=true";
  584. }
  585. },
  586. httpEnter() {
  587. this.$emit('input', this.initItems(this.httpValue));
  588. this.browseVisible = false;
  589. }
  590. }
  591. }
  592. </script>