index.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. <template>
  2. <div class="panorama con">
  3. <div class="top">
  4. <crumbs :list="folderPath" @click-path="onClickPath" />
  5. </div>
  6. <div class="second-line">
  7. <div class="btn">
  8. <button @mouseover.stop="showList = true" @click="onUploadFile" class="ui-button submit">
  9. <span>{{ upload_material }}</span>
  10. <i class="iconfont icon-material_prompt hover-tips hover-tips-upload-icon">
  11. <div>
  12. <div class="remark">{{ pano_size }}</div>
  13. </div>
  14. </i>
  15. <upload ref="uploadFile" :failString="pano_fail" :limitFailStr="pano_limit" accept-type=".jpg"
  16. media-type="image" :limit="120" @file-change="onFileChange"></upload>
  17. </button>
  18. </div>
  19. <button
  20. class="ui-button submit"
  21. @click="isShowNewFolder = true"
  22. >
  23. {{$i18n.t(`gather.new_folder`)}}
  24. </button>
  25. <button class="ui-button cancel">{{$i18n.t(`gather.move_folder`)}}</button>
  26. <div class="filter">
  27. <div :class="{ active: isFilterFocus }" @focusin="onFilterFocus" @focusout="onFilterBlur">
  28. <i class="iconfont icon-works_search search"></i>
  29. <input type="text" v-model="searchKey" :placeholder="serch_material" />
  30. <i v-if="searchKey" @click="searchKey = ''" class="iconfont icontoast_red del"></i>
  31. </div>
  32. </div>
  33. </div>
  34. <div class="list">
  35. <tableList
  36. @selection-change="
  37. (data) => {
  38. selectedArr = data;
  39. }
  40. "
  41. @request-more-data="getMoreMaterialItem"
  42. :canRequestMoreData="hasMoreData && !isRequestingMoreData"
  43. :header="tabHeader"
  44. :showLine="true"
  45. :selection="true"
  46. :data="list"
  47. class="table-list"
  48. ref="table-list"
  49. >
  50. <!-- 插到tableList组件各个header插槽,并通过插槽的headerItem作用域拿到表头各项 -->
  51. <div slot-scope="{ headerItem }" slot="header">
  52. {{ headerItem.name && $i18n.t(`zh_key.${headerItem.name}`) }}
  53. </div>
  54. <!-- 内容各单元格 -->
  55. <div slot-scope="{ itemData, lineData, headerItem }" slot="tableItem" style="width: 100%">
  56. <!-- 操作型单元格 -->
  57. <div class="handle" v-if="headerItem.canclick">
  58. <i class="iconfont icon-material_operation_image hover-tips"
  59. @click="(showCover = true), (popupItem = lineData)">
  60. <div>
  61. <div class="remark">{{ edit_cover }}</div>
  62. </div>
  63. </i>
  64. <i class="iconfont icon-material_operation_editor hover-tips"
  65. @click="(showRename = true), (popupItem = lineData)">
  66. <div>
  67. <div class="remark">{{ rename }}</div>
  68. </div>
  69. </i>
  70. <i class="iconfont icon-material_operation_delete hover-tips-warn" @click="del(lineData)">
  71. <div>
  72. <div class="remark">{{ deltips }}</div>
  73. </div>
  74. </i>
  75. </div>
  76. <!-- 图片型单元格 -->
  77. <div
  78. v-else-if="headerItem.type == 'image' && lineData.type !== 'dir'"
  79. class="img"
  80. @click="previewImage(lineData)"
  81. >
  82. <img :src="itemData + (Number(lineData.fileSize) > 512 ? $imgsuffix : '')"
  83. alt="" />
  84. </div>
  85. <div
  86. v-else-if="headerItem.type == 'image' && lineData.type === 'dir'"
  87. class="img dir"
  88. >
  89. <img
  90. :src="require('@/assets/images/icons/folder-blue.png')"
  91. alt=""
  92. @click="onClickFolder(lineData)"
  93. />
  94. </div>
  95. <span
  96. v-else-if="headerItem.key == 'name' && lineData.type !== 'dir'"
  97. class="textItem"
  98. style="cursor: pointer;"
  99. @click="previewImage(lineData)"
  100. >
  101. {{ itemData || "-" }}
  102. </span>
  103. <span
  104. v-else-if="headerItem.key === 'name' && lineData.type === 'dir'"
  105. class="textItem dirName"
  106. @click="onClickFolder(lineData)"
  107. >
  108. {{ itemData || "-" }}
  109. </span>
  110. <!-- 文字型单元格 -->
  111. <span
  112. v-else
  113. class="textItem"
  114. >
  115. {{ itemData || "-" }}
  116. </span>
  117. </div>
  118. </tableList>
  119. <UploadTaskList class="upload-task-list" fileType="IMAGE" :taskList="uploadListForUI" :targetFolderId="currentFolderId" @cancel-task="onCancelTask">
  120. </UploadTaskList>
  121. <div class="total-number" v-if="list.length !== 0 || hasMoreData">{{ had_load }}</div>
  122. <div class="nodata" v-if="list.length == 0 && !hasMoreData && lastestUsedSearchKey">
  123. <img :src="$noresult" alt="" />
  124. <span>{{ no_serch_result }}</span>
  125. </div>
  126. <div class="nodata" v-if="list.length == 0 && !hasMoreData && !lastestUsedSearchKey">
  127. <img :src="config.empty" alt="" />
  128. <span>{{ no_material_result }}</span>
  129. <button @click="$refs.uploadFile.click()" class="upload-btn-in-table">{{ upload_material }}</button>
  130. </div>
  131. </div>
  132. <CreateFolder
  133. v-if="isShowNewFolder"
  134. :validate=validateNewFolderName
  135. @close="isShowNewFolder = false"
  136. @submit="onSubmitNewFolder"
  137. />
  138. <rename
  139. v-if="showRename"
  140. :item="popupItem"
  141. @rename="handleRename"
  142. @close="showRename = false"
  143. />
  144. <preview
  145. ref="image-previewer"
  146. :sceneCodeList="list.map(item => item.sceneCode)"
  147. :imageTitleList="list.map(item => item.name)"
  148. @click-delete="onClickDeleteInPreview"
  149. />
  150. <cover
  151. @panocover="handlePanoCover"
  152. :item="popupItem"
  153. v-if="showCover"
  154. @close="showCover = false"
  155. />
  156. </div>
  157. </template>
  158. <script>
  159. import config from "@/config";
  160. import tableList from "@/components/table/index.vue";
  161. import crumbs from "@/components/crumbs/index.vue";
  162. import { data } from "./pano";
  163. import rename from "../popup/rename";
  164. import preview from "../popup/panoImagePreviewer.vue";
  165. import cover from "../popup/cover";
  166. import Upload from "@/components/shared/uploads/UploadMultiple";
  167. import { getImgWH, changeByteUnit } from "@/utils/file";
  168. import UploadTaskList from "../components/uploadList1.1.0.vue";
  169. import { debounce } from "@/utils/other.js"
  170. import { mapState } from 'vuex';
  171. import { i18n } from "@/lang"
  172. import folderMixinFactory from "../folderMixinFactory.js";
  173. import {
  174. getMaterialList,
  175. uploadMaterial,
  176. editMaterial,
  177. delMaterial,
  178. uploadCover,
  179. checkMStatus,
  180. checkUserSize
  181. } from "@/api";
  182. const TYPE = "pano";
  183. const LONG_POLLING_INTERVAL = 5;
  184. const folderMixin = folderMixinFactory(TYPE)
  185. export default {
  186. mixins: [
  187. folderMixin,
  188. ],
  189. name: 'Pano',
  190. components: {
  191. tableList,
  192. crumbs,
  193. rename,
  194. cover,
  195. preview,
  196. Upload,
  197. UploadTaskList,
  198. },
  199. data() {
  200. return {
  201. upload_material: i18n.t("gather.upload_material"),
  202. serch_material: i18n.t("gather.serch_material"),
  203. no_serch_result: i18n.t("gather.no_serch_result"),
  204. no_material_result: i18n.t("gather.no_material_result"),
  205. pano_size: i18n.t("gather.pano_size"),
  206. pano_fail: i18n.t("gather.pano_fail"),
  207. pano_limit: i18n.t("gather.pano_limit"),
  208. edit_cover: i18n.t("gather.edit_cover"),
  209. rename: i18n.t("gather.rename"),
  210. deltips: i18n.t("gather.delete"),
  211. config,
  212. showRename: false,
  213. showCover: false,
  214. showList: false,
  215. popupItem: null,
  216. tabHeader: data,
  217. selectedArr: [],
  218. // 因为searchKey的变化经过debounce、异步请求的延时,才会反映到数据列表的变化上,所以是否显示、显示哪种无数据提示,也要等到数据列表变化后,根据数据列表是否为空,以及引发本次变化的那个searchKey瞬时值来决定。本变量就是用来保存那个瞬时值。
  219. lastestUsedSearchKey: '',
  220. isFilterFocus: false,
  221. searchKey: "",
  222. list: [],
  223. hasMoreData: true,
  224. isRequestingMoreData: false,
  225. };
  226. },
  227. computed: {
  228. ...mapState({
  229. uploadListForUI: 'uploadStatusListPano',
  230. }),
  231. needLongPolling() {
  232. return this.uploadListForUI.some((item) => {
  233. return item.status === 'LOADING' && item.ifKnowProgress === false
  234. })
  235. },
  236. had_load() {
  237. return i18n.t("gather.had_load", { msg: this.list.length })
  238. }
  239. },
  240. mounted() {
  241. console.log('mounted!!!!!');
  242. },
  243. watch: {
  244. needLongPolling: {
  245. handler: function (newVal) {
  246. if (!newVal) {
  247. console.log('dont need longpolling!!!!!!');
  248. this.clearinter();
  249. } else {
  250. this.clearinter();
  251. console.log('need longpolling!!!!!');
  252. this.interval = setInterval(() => {
  253. this._checkMStatus();
  254. }, LONG_POLLING_INTERVAL * 1000);
  255. }
  256. },
  257. immediate: true,
  258. },
  259. searchKey: {
  260. handler: function () {
  261. this.refreshListDebounced()
  262. },
  263. immediate: false,
  264. },
  265. },
  266. methods: {
  267. onUploadFile() {
  268. checkUserSize({}, (data) => {
  269. //判断已用是否大于3G
  270. if ((data.data / 1024 / 1024) > 3) {
  271. this.$alert({ content: i18n.t("tips_code.FAILURE_3024") });
  272. } else {
  273. this.$refs.uploadFile.click()
  274. }
  275. })
  276. },
  277. onFilterFocus() {
  278. this.isFilterFocus = true
  279. },
  280. onFilterBlur() {
  281. this.isFilterFocus = false
  282. },
  283. refreshListDebounced: debounce(function () {
  284. this.list = []
  285. this.isRequestingMoreData = false
  286. this.hasMoreData = true
  287. this.$refs['table-list'].requestMoreData()
  288. }, 700, true),
  289. clearinter() {
  290. this.interval && clearInterval(this.interval);
  291. this.interval = null;
  292. },
  293. handleRename(newName) {
  294. editMaterial(
  295. {
  296. id: this.popupItem.id,
  297. name: newName,
  298. },
  299. () => {
  300. this.$msg.success(i18n.t("gather.edit_success"));
  301. const index = this.list.findIndex((eachItem) => {
  302. return eachItem.id === this.popupItem.id
  303. })
  304. if (index >= 0) {
  305. this.list[index].name = newName
  306. } else {
  307. console.error('在素材列表里没找到要重命名的那一项!');
  308. }
  309. this.showRename = false;
  310. this.popupItem = null;
  311. }
  312. );
  313. },
  314. handlePanoCover(data) {
  315. if (data.indexOf("http") > -1) {
  316. this.showCover = false;
  317. this.popupItem = "";
  318. return;
  319. }
  320. uploadCover({ file: data, filename: "cover.jpg" }, (res) => {
  321. if (res.code == 0) {
  322. editMaterial(
  323. {
  324. id: this.popupItem.id,
  325. icon: res.data,
  326. },
  327. () => {
  328. this.$msg.success(i18n.t("gather.setting_success"));
  329. const index = this.list.findIndex((eachItem) => {
  330. return eachItem.id === this.popupItem.id
  331. })
  332. if (index >= 0) {
  333. this.isRequestingMoreData = true
  334. const lastestUsedSearchKey = this.searchKey
  335. getMaterialList(
  336. {
  337. pageNum: index + 1,
  338. pageSize: 1,
  339. type: TYPE,
  340. },
  341. (data) => {
  342. const newData = data.data.list.map((i) => {
  343. i.fileSize = changeByteUnit(Number(i.fileSize));
  344. return i;
  345. });
  346. this.list.splice(index, 1, newData[0])
  347. this.showCover = false;
  348. this.popupItem = "";
  349. this.isRequestingMoreData = false
  350. this.lastestUsedSearchKey = lastestUsedSearchKey
  351. },
  352. () => {
  353. this.isRequestingMoreData = false
  354. this.lastestUsedSearchKey = lastestUsedSearchKey
  355. this.showCover = false;
  356. this.popupItem = "";
  357. }
  358. )
  359. } else {
  360. console.error('在素材列表里没找到要编辑封面的那一项!');
  361. this.showCover = false;
  362. this.popupItem = "";
  363. }
  364. }
  365. );
  366. }
  367. });
  368. },
  369. _checkMStatus() {
  370. let needPollingTaskList = this.uploadListForUI.filter((item) => item.status === 'LOADING' && item.ifKnowProgress === false);
  371. if (needPollingTaskList.length > 0) {
  372. checkMStatus(
  373. {
  374. ids: needPollingTaskList.map((item) => item.backendId),
  375. islongpolling: true,
  376. },
  377. (res) => {
  378. // 1切图中,2失败,3成功
  379. res.data.forEach(eachRes => {
  380. if (eachRes.status === 2) {
  381. const index = this.uploadListForUI.findIndex(eachTask => eachTask.backendId === eachRes.id)
  382. index >= 0 && (this.uploadListForUI[index].status = 'FAIL')
  383. index >= 0 && (this.uploadListForUI[index].statusText = this.$msg.success(i18n.t("gather.material_cutting_fail")))
  384. } else if (eachRes.status === 3) {
  385. const index = this.uploadListForUI.findIndex(eachTask => eachTask.backendId === eachRes.id)
  386. index >= 0 && (this.uploadListForUI.splice(index, 1))
  387. index >= 0 && this.refreshListDebounced()
  388. }
  389. });
  390. }
  391. );
  392. }
  393. },
  394. del(item) {
  395. this.$confirm({
  396. title: i18n.t("gather.delete_material"),
  397. content: i18n.t("gather.comfirm_delete_material"),
  398. okText: i18n.t("gather.delete"),
  399. ok: () => {
  400. delMaterial(item.id, () => {
  401. this.$msg.success(i18n.t("gather.delete_success"));
  402. this.isRequestingMoreData = true
  403. const lastestUsedSearchKey = this.searchKey
  404. getMaterialList(
  405. {
  406. pageNum: this.list.length + 1,
  407. pageSize: 1,
  408. searchKey: this.searchKey,
  409. type: TYPE,
  410. },
  411. (data) => {
  412. const index = this.list.findIndex((eachItem) => {
  413. return eachItem.id === item.id
  414. })
  415. if (index >= 0) {
  416. this.list.splice(index, 1)
  417. const newData = data.data.list.map((i) => {
  418. i.fileSize = changeByteUnit(Number(i.fileSize));
  419. return i;
  420. });
  421. this.list = this.list.concat(newData)
  422. if (this.list.length === data.data.total) {
  423. this.hasMoreData = false
  424. }
  425. if (this.list.length === 0) {
  426. this.$refs['image-previewer'].onClickClose()
  427. }
  428. } else {
  429. console.error('在素材列表里没找到要删除的那一项!');
  430. }
  431. this.isRequestingMoreData = false
  432. this.lastestUsedSearchKey = lastestUsedSearchKey
  433. },
  434. () => {
  435. this.isRequestingMoreData = false
  436. this.lastestUsedSearchKey = lastestUsedSearchKey
  437. }
  438. )
  439. });
  440. },
  441. });
  442. },
  443. previewImage(targetItem) {
  444. const index = this.list.findIndex((eachItem) => {
  445. return eachItem.id === targetItem.id
  446. })
  447. this.$refs['image-previewer'].show(index)
  448. },
  449. onFileChange(e) {
  450. e.files.forEach(async (eachFile, i) => {
  451. if (
  452. eachFile.type.indexOf("jpeg") <= -1
  453. ) {
  454. setTimeout(() => {
  455. this.$msg({
  456. message: `“${eachFile.name}”${i18n.t("gather.pano_fail")}`,
  457. type: "warning",
  458. });
  459. }, i * 100);
  460. return;
  461. }
  462. if (eachFile.name.substring(0, eachFile.name.lastIndexOf(".")).length > 50) {
  463. setTimeout(() => {
  464. this.$msg({
  465. message: `“${eachFile.name}”${i18n.t("gather.too_long_word")}`,
  466. type: "warning",
  467. });
  468. }, i * 100);
  469. return;
  470. }
  471. let WHRate = null
  472. try {
  473. const { width, height } = await getImgWH(eachFile)
  474. WHRate = width / height
  475. } catch (e) {
  476. console.error('获取图像宽高失败:', e)
  477. setTimeout(() => {
  478. this.$msg({
  479. message: `“${eachFile.name}”${i18n.t("gather.pano_fail")}`,
  480. type: "warning",
  481. });
  482. }, i * 100);
  483. return
  484. }
  485. if (WHRate !== 2) {
  486. setTimeout(() => {
  487. this.$msg({
  488. message: `“${eachFile.name}”${i18n.t("gather.pano_fail")}`,
  489. type: "warning",
  490. });
  491. }, i * 100);
  492. return
  493. }
  494. let itemInUploadList = {
  495. title: eachFile.name,
  496. ifKnowProgress: true,
  497. progress: 0,
  498. status: 'LOADING',
  499. statusText: i18n.t("gather.uploading_material"),
  500. uid: `u_${this.$randomWord(true, 8, 8)}`,
  501. abortHandler: null,
  502. backendId: '',
  503. parentFolderId: this.currentFolderId,
  504. };
  505. itemInUploadList.abortHandler = uploadMaterial(
  506. {
  507. dirId: this.currentFolderId,
  508. file: eachFile,
  509. tempId: itemInUploadList.uid,
  510. type: TYPE,
  511. },
  512. (response) => { // 上传成功
  513. itemInUploadList.statusText = i18n.t("gather.cutting")
  514. itemInUploadList.ifKnowProgress = false
  515. itemInUploadList.backendId = response.data.id
  516. },
  517. (err) => {
  518. if (err.statusText === 'abort') { // 用户取消了上传任务。
  519. const index = this.uploadListForUI.findIndex((eachItem) => {
  520. return eachItem.uid === itemInUploadList.uid
  521. })
  522. this.uploadListForUI.splice(index, 1)
  523. } else {
  524. itemInUploadList.status = 'FAIL'
  525. itemInUploadList.statusText = i18n.t("gather.material_upload_fail")
  526. }
  527. },
  528. (progress) => {
  529. itemInUploadList.progress = progress
  530. }
  531. )
  532. this.uploadListForUI.push(itemInUploadList);
  533. })
  534. },
  535. onCancelTask(uid) {
  536. const index = this.uploadListForUI.findIndex((eachItem) => {
  537. return eachItem.uid === uid
  538. })
  539. if (this.uploadListForUI[index].status === 'LOADING') {
  540. this.uploadListForUI[index].abortHandler.abort()
  541. } else {
  542. this.uploadListForUI.splice(index, 1)
  543. }
  544. },
  545. getMoreMaterialItem(islongpolling = null) {
  546. this.isRequestingMoreData = true
  547. const lastestUsedSearchKey = this.searchKey
  548. getMaterialList(
  549. {
  550. dirId: this.currentFolderId,
  551. pageNum: Math.floor(this.list.length / config.PAGE_SIZE) + 1,
  552. pageSize: config.PAGE_SIZE,
  553. searchKey: this.searchKey,
  554. type: TYPE,
  555. islongpolling
  556. },
  557. (data) => {
  558. const newData = data.data.list.map((i) => {
  559. if (i.type !== 'dir') {
  560. i.fileSize = changeByteUnit(Number(i.fileSize));
  561. }
  562. return i;
  563. });
  564. this.list = this.list.concat(newData)
  565. if (this.list.length === data.data.total) {
  566. this.hasMoreData = false
  567. }
  568. this.isRequestingMoreData = false
  569. this.lastestUsedSearchKey = lastestUsedSearchKey
  570. },
  571. () => {
  572. this.isRequestingMoreData = false
  573. this.lastestUsedSearchKey = lastestUsedSearchKey
  574. }
  575. );
  576. },
  577. onClickDeleteInPreview(index) {
  578. this.del(this.list[index])
  579. },
  580. },
  581. };
  582. </script>
  583. <style lang="less" scoped>
  584. </style>
  585. <style lang="less" scoped>
  586. @import "../style.less";
  587. </style>