materialSelector.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. <template>
  2. <div class="table-select" :class="isDarkTheme ? '' : 'bright'">
  3. <span class="title">{{ title }}</span>
  4. <div class="close-btn">
  5. <i class="iconfont icon-close" @click="$emit('cancle')"></i>
  6. </div>
  7. <div class="material-tab">
  8. <a
  9. v-if="selectableType.includes('image')"
  10. class="material-tab-item"
  11. @click.prevent="currentMaterialType = 'image'"
  12. >
  13. <span :class="{ active: currentMaterialType === 'image' }" class="text">
  14. {{ $i18n.t(`gather.image`) }}
  15. </span>
  16. <div v-if="currentMaterialType === 'image'" class="bottom-line"></div>
  17. </a>
  18. <a
  19. v-if="selectableType.includes('pano')"
  20. class="material-tab-item"
  21. @click.prevent="currentMaterialType = 'pano'"
  22. >
  23. <span :class="{ active: currentMaterialType === 'pano' }" class="text">
  24. {{ $i18n.t(`gather.panorama`) }}
  25. </span>
  26. <div v-if="currentMaterialType === 'pano'" class="bottom-line"></div>
  27. </a>
  28. <a
  29. v-if="selectableType.includes('audio')"
  30. class="material-tab-item"
  31. @click.prevent="currentMaterialType = 'audio'"
  32. >
  33. <span :class="{ active: currentMaterialType === 'audio' }" class="text">
  34. {{ $i18n.t(`gather.audio`) }}
  35. </span>
  36. <div v-if="currentMaterialType === 'audio'" class="bottom-line"></div>
  37. </a>
  38. <a
  39. v-if="selectableType.includes('video')"
  40. class="material-tab-item"
  41. @click.prevent="currentMaterialType = 'video'"
  42. >
  43. <span :class="{ active: currentMaterialType === 'video' }" class="text">
  44. {{ $i18n.t(`gather.video`) }}
  45. </span>
  46. <div v-if="currentMaterialType === 'video'" class="bottom-line"></div>
  47. </a>
  48. <a
  49. v-if="selectableType.includes('3D')"
  50. class="material-tab-item"
  51. @click.prevent="currentMaterialType = '3D'"
  52. >
  53. <span :class="{ active: currentMaterialType === '3D' }" class="text">
  54. {{ $i18n.t(`gather.scene`) }}
  55. </span>
  56. <div v-if="currentMaterialType === '3D'" class="bottom-line"></div>
  57. </a>
  58. </div>
  59. <div class="filter" :class="{ active: isSearchKeyInputActive }">
  60. <input
  61. type="text"
  62. :placeholder="$i18n.t('gather.keywords')"
  63. v-model="searchKey"
  64. @focus="isSearchKeyInputActive = true"
  65. @blur="isSearchKeyInputActive = false"
  66. />
  67. <i v-if="!searchKey" class="iconfont icon-editor_search search-icon" />
  68. <i
  69. v-if="searchKey"
  70. @click="searchKey = ''"
  71. class="iconfont icon-toast_red clear-icon"
  72. ></i>
  73. </div>
  74. <MaterialList
  75. v-if="selectableType.includes('pano')"
  76. v-show="currentMaterialType === 'pano'"
  77. :isDarkTheme="isDarkTheme"
  78. :currentMaterialType="currentMaterialType"
  79. :materialType="'pano'"
  80. :tableHeaderKeyList="['icon', 'name']"
  81. :isMultiSelection="isMultiSelection"
  82. :select="select"
  83. :searchKey="searchKey"
  84. :canUpload="true"
  85. :fileInputCustomCheck="checkPanoFileInput"
  86. :fileUploadLongPollingCb="panoUploadLongPolling"
  87. :fileUploadLongPollingStatusText="$i18n.t(`gather.cutting`)"
  88. :fileInputBtnTip="$i18n.t(`gather.pano_size`)"
  89. :fileInputFailString="$i18n.t(`gather.pano_fail`)"
  90. :fileInputLimitFailStr="$i18n.t(`gather.pano_limit`)"
  91. :fileInputAcceptType="'image/png,image/jpeg,image/jpg'"
  92. :fileInputMediaType="'image'"
  93. :fileInputLimit="120"
  94. @need-clear-filter="
  95. () => {
  96. searchKey = '';
  97. }
  98. "
  99. >
  100. <template v-slot:materialUploadSuccessIcon="slotProps">
  101. <img
  102. :src="
  103. slotProps.uploadInfo.successInfo[slotProps.tableItemStructure.key] +
  104. $imgsuffix
  105. "
  106. alt=""
  107. />
  108. </template>
  109. <template v-slot:materialUploadingIcon>
  110. <img
  111. src="@/assets/images/icons/upload-file-type-icon-image@2x.png"
  112. style="object-fit: contain"
  113. alt=""
  114. />
  115. </template>
  116. <template v-slot:materialUploadFailIcon>
  117. <img
  118. src="@/assets/images/icons/upload-file-type-icon-image@2x.png"
  119. style="object-fit: contain"
  120. |
  121. alt=""
  122. />
  123. </template>
  124. <template v-slot:materialIcon="slotProps">
  125. <img
  126. v-if="slotProps.materialInfo[slotProps.tableItemStructure.key]"
  127. :src="
  128. slotProps.materialInfo[slotProps.tableItemStructure.key] +
  129. $imgsuffix
  130. "
  131. alt=""
  132. />
  133. <img v-else src="@/assets/img/list_placeholder.png" alt="" />
  134. </template>
  135. </MaterialList>
  136. <MaterialList
  137. v-if="selectableType.includes('image')"
  138. v-show="currentMaterialType === 'image'"
  139. :isDarkTheme="isDarkTheme"
  140. :currentMaterialType="currentMaterialType"
  141. :materialType="'image'"
  142. :tableHeaderKeyList="['icon', 'name']"
  143. :isMultiSelection="isMultiSelection"
  144. :select="select"
  145. :searchKey="searchKey"
  146. :canUpload="true"
  147. :fileInputBtnTip="$i18n.t(`gather.img_size`)"
  148. :fileInputFailString="$i18n.t(`gather.img_fail`)"
  149. :fileInputLimitFailStr="$i18n.t(`gather.img_limit`)"
  150. :fileInputAcceptType="'image/png,image/jpeg,image/jpg'"
  151. :fileInputMediaType="'image'"
  152. :fileInputLimit="10"
  153. @need-clear-filter="
  154. () => {
  155. searchKey = '';
  156. }
  157. "
  158. >
  159. <template v-slot:materialUploadSuccessIcon="slotProps">
  160. <img
  161. :src="
  162. slotProps.uploadInfo.successInfo[slotProps.tableItemStructure.key] +
  163. $imgsuffix
  164. "
  165. alt=""
  166. />
  167. </template>
  168. <template v-slot:materialUploadingIcon>
  169. <img
  170. src="@/assets/images/icons/upload-file-type-icon-image@2x.png"
  171. style="object-fit: contain"
  172. alt=""
  173. />
  174. </template>
  175. <template v-slot:materialUploadFailIcon>
  176. <img
  177. src="@/assets/images/icons/upload-file-type-icon-image@2x.png"
  178. style="object-fit: contain"
  179. alt=""
  180. />
  181. </template>
  182. <template v-slot:materialIcon="slotProps">
  183. <img
  184. v-if="slotProps.materialInfo[slotProps.tableItemStructure.key]"
  185. :src="
  186. slotProps.materialInfo[slotProps.tableItemStructure.key] +
  187. $imgsuffix
  188. "
  189. alt=""
  190. />
  191. <img v-else src="@/assets/img/list_placeholder.png" alt="" />
  192. </template>
  193. </MaterialList>
  194. <MaterialList
  195. v-if="selectableType.includes('audio')"
  196. v-show="currentMaterialType === 'audio'"
  197. :isDarkTheme="isDarkTheme"
  198. :currentMaterialType="currentMaterialType"
  199. :materialType="'audio'"
  200. :tableHeaderKeyList="['ossPath', 'name']"
  201. :isMultiSelection="isMultiSelection"
  202. :select="select"
  203. :searchKey="searchKey"
  204. :canUpload="true"
  205. :fileInputBtnTip="$i18n.t(`gather.audio_size`)"
  206. :fileInputFailString="$i18n.t(`gather.audio_fail`)"
  207. :fileInputLimitFailStr="$i18n.t(`gather.audio_limit`)"
  208. :fileInputAcceptType="'audio/mp3'"
  209. :fileInputMediaType="'audio'"
  210. :fileInputLimit="20"
  211. @need-clear-filter="
  212. () => {
  213. searchKey = '';
  214. }
  215. "
  216. >
  217. <template v-slot:materialUploadSuccessIcon="slotProps">
  218. <AudioIconCanPlay
  219. class="audio-player"
  220. :vKey="slotProps.uploadInfo.successInfo.id"
  221. :idleft="`_${$randomWord(true, 8, 8)}`"
  222. :idright="`_${$randomWord(true, 8, 8)}`"
  223. :myAudioUrl="slotProps.uploadInfo.successInfo.ossPath"
  224. />
  225. </template>
  226. <template v-slot:materialUploadingIcon>
  227. <img
  228. :src="
  229. require('@/assets/images/icons/upload-file-type-icon-audio@2x.png')
  230. "
  231. style="object-fit: contain"
  232. alt=""
  233. />
  234. </template>
  235. <template v-slot:materialUploadFailIcon>
  236. <img
  237. :src="
  238. require('@/assets/images/icons/upload-file-type-icon-audio@2x.png')
  239. "
  240. style="object-fit: contain"
  241. alt=""
  242. />
  243. </template>
  244. <template v-slot:materialIcon="slotProps">
  245. <AudioIconCanPlay
  246. class="audio-player"
  247. :vKey="slotProps.materialInfo.id"
  248. :idleft="`_${$randomWord(true, 8, 8)}`"
  249. :idright="`_${$randomWord(true, 8, 8)}`"
  250. :myAudioUrl="slotProps.materialInfo.ossPath"
  251. />
  252. </template>
  253. </MaterialList>
  254. <MaterialList
  255. v-if="selectableType.includes('video')"
  256. v-show="currentMaterialType === 'video'"
  257. :isDarkTheme="isDarkTheme"
  258. :currentMaterialType="currentMaterialType"
  259. :materialType="'video'"
  260. :materialItemCustomProcess="videoMaterialItemCustomProcess"
  261. :tableHeaderKeyList="['icon', 'name']"
  262. :isMultiSelection="isMultiSelection"
  263. :select="select"
  264. :searchKey="searchKey"
  265. :canUpload="true"
  266. :fileInputBtnTip="$i18n.t(`gather.video_size`)"
  267. :fileInputFailString="$i18n.t(`gather.video_fail`)"
  268. :fileInputLimitFailStr="$i18n.t(`gather.video_limit`)"
  269. :fileInputAcceptType="'video/mp4'"
  270. :fileInputMediaType="'video'"
  271. :fileInputLimit="200"
  272. @need-clear-filter="
  273. () => {
  274. searchKey = '';
  275. }
  276. "
  277. >
  278. <template v-slot:materialUploadSuccessIcon="slotProps">
  279. <img
  280. :src="
  281. slotProps.uploadInfo.successInfo[slotProps.tableItemStructure.key]
  282. "
  283. alt=""
  284. />
  285. </template>
  286. <template v-slot:materialUploadingIcon>
  287. <img
  288. src="@/assets/images/icons/upload-file-type-icon-video@2x.png"
  289. style="object-fit: contain"
  290. alt=""
  291. />
  292. </template>
  293. <template v-slot:materialUploadFailIcon>
  294. <img
  295. src="@/assets/images/icons/upload-file-type-icon-video@2x.png"
  296. style="object-fit: contain"
  297. alt=""
  298. />
  299. </template>
  300. <template v-slot:materialIcon="slotProps">
  301. <img
  302. v-if="slotProps.materialInfo[slotProps.tableItemStructure.key]"
  303. :src="
  304. slotProps.materialInfo[slotProps.tableItemStructure.key] +
  305. $imgsuffix
  306. "
  307. alt=""
  308. />
  309. <img v-else src="@/assets/img/list_placeholder.png" alt="" />
  310. </template>
  311. </MaterialList>
  312. <MaterialList
  313. v-if="selectableType.includes('3D')"
  314. v-show="currentMaterialType === '3D'"
  315. :isDarkTheme="isDarkTheme"
  316. :currentMaterialType="currentMaterialType"
  317. :materialType="'3D'"
  318. :tableHeaderKeyList="['thumb', 'sceneName']"
  319. :isMultiSelection="isMultiSelection"
  320. :select="select"
  321. :searchKey="searchKey"
  322. @need-clear-filter="
  323. () => {
  324. searchKey = '';
  325. }
  326. "
  327. >
  328. <template v-slot:materialIcon="slotProps">
  329. <img
  330. v-if="slotProps.materialInfo[slotProps.tableItemStructure.key]"
  331. :src="
  332. slotProps.materialInfo[slotProps.tableItemStructure.key] +
  333. $imgsuffix
  334. "
  335. alt=""
  336. />
  337. <img v-else src="@/assets/img/list_placeholder.png" alt="" />
  338. </template>
  339. </MaterialList>
  340. <div class="btns">
  341. <button
  342. class="ui-button"
  343. :class="isDarkTheme ? 'deepcancel' : 'cancel'"
  344. @click="$emit('cancle')"
  345. >
  346. {{ $i18n.t("gather.cancel") }}
  347. </button>
  348. <button
  349. class="ui-button submit"
  350. :class="{ disable: !select.length }"
  351. @click="onClickComfirm"
  352. >
  353. {{ $i18n.t("gather.comfirm"), }}
  354. </button>
  355. </div>
  356. </div>
  357. </template>
  358. <script>
  359. import { mapMutations } from "vuex";
  360. import MaterialList from "./materialListInMaterialSelector.vue";
  361. import AudioIconCanPlay from "@/components/audio/indexForEditor.vue";
  362. import { getImgWH, changeByteUnit } from "@/utils/file";
  363. import { debounce } from "@/utils/other.js";
  364. import { checkMStatus } from "@/api";
  365. export default {
  366. components: {
  367. MaterialList,
  368. AudioIconCanPlay,
  369. },
  370. props: {
  371. isDarkTheme: {
  372. type: Boolean,
  373. default: true,
  374. },
  375. title: {
  376. default: "",
  377. type: String,
  378. },
  379. selectableType: {
  380. type: Array,
  381. default: function () {
  382. return ["image", "pano", "audio", "video", "3D"];
  383. },
  384. },
  385. initialMaterialType: {
  386. type: String,
  387. default: "image",
  388. },
  389. isMultiSelection: {
  390. type: Boolean,
  391. default: false,
  392. },
  393. },
  394. data() {
  395. return {
  396. select: [],
  397. isSearchKeyInputActive: false,
  398. searchKey: "", // 搜索关键词
  399. latestUsedSearchKey: "",
  400. currentMaterialType: this.initialMaterialType,
  401. };
  402. },
  403. watch: {},
  404. methods: {
  405. ...mapMutations(["clearUploadStatusLists"]),
  406. async checkPanoFileInput(eachFile, i) {
  407. let WHRate = null;
  408. try {
  409. const { width, height } = await getImgWH(eachFile);
  410. WHRate = width / height;
  411. } catch (e) {
  412. console.error("获取图像宽高失败:", e);
  413. setTimeout(() => {
  414. this.$msg({
  415. message: `“${eachFile.name}”${this.$i18n.t(`gather.pano_fail`)}`,
  416. type: "warning",
  417. });
  418. }, i * 100);
  419. return false;
  420. }
  421. if (WHRate !== 2) {
  422. console.log("宽高比不对!");
  423. setTimeout(() => {
  424. this.$msg({
  425. message: `“${eachFile.name}”${this.$i18n.t(`gather.pano_fail`)}`,
  426. type: "warning",
  427. });
  428. }, i * 100);
  429. return false;
  430. } else {
  431. console.log(WHRate);
  432. return true;
  433. }
  434. },
  435. panoUploadLongPolling(uploadStatusList) {
  436. let needPollingTaskList = uploadStatusList.filter(
  437. (item) => item.status === "LOADING" && item.ifKnowProgress === false
  438. );
  439. if (needPollingTaskList.length > 0) {
  440. checkMStatus(
  441. {
  442. ids: needPollingTaskList.map((item) => item.backendId),
  443. islongpolling: true,
  444. },
  445. (res) => {
  446. // 1切图中,2失败,3成功
  447. res.data.forEach((eachRes) => {
  448. if (eachRes.status === 2) {
  449. const index = uploadStatusList.findIndex(
  450. (eachTask) => eachTask.backendId === eachRes.id
  451. );
  452. if (index >= 0) {
  453. const targetItem = uploadStatusList[index];
  454. targetItem.status = "FAIL";
  455. targetItem.statusText = this.$i18n.t(
  456. `gather.material_cutting_fail`
  457. );
  458. targetItem.ifKnowProgress = true;
  459. }
  460. } else if (eachRes.status === 3) {
  461. const index = uploadStatusList.findIndex(
  462. (eachTask) => eachTask.backendId === eachRes.id
  463. );
  464. if (index >= 0) {
  465. const targetItem = uploadStatusList[index];
  466. targetItem.status = "SUCCESS";
  467. if (eachRes.fileSize) {
  468. eachRes.fileSize = changeByteUnit(Number(eachRes.fileSize));
  469. } else {
  470. eachRes.fileSize = "";
  471. }
  472. targetItem.successInfo = eachRes;
  473. }
  474. }
  475. });
  476. }
  477. );
  478. }
  479. },
  480. videoMaterialItemCustomProcess(item) {
  481. item.icon =
  482. process.env.VUE_APP_ORIGIN == "aws"
  483. ? item.icon
  484. : item.ossPath + this.$videoImg;
  485. },
  486. onClickComfirm: debounce(function () {
  487. this.$emit("submit", this.select);
  488. }, 250),
  489. },
  490. mounted() {
  491. this.clearUploadStatusLists();
  492. },
  493. };
  494. </script>
  495. <style lang="less" scoped>
  496. .table-select {
  497. position: absolute;
  498. z-index: 3;
  499. left: 50%;
  500. top: 50%;
  501. transform: translateX(-50%) translateY(-50%);
  502. width: 600px;
  503. height: 730px;
  504. background: #1a1b1d;
  505. border-radius: 4px;
  506. border: 1px solid #404040;
  507. padding: 26px;
  508. .title {
  509. font-size: 18px;
  510. color: rgba(255, 255, 255, 0.6);
  511. }
  512. .close-btn {
  513. display: inline-block;
  514. position: absolute;
  515. top: 26px;
  516. right: 20px;
  517. font-size: 12px;
  518. color: #969799;
  519. cursor: pointer;
  520. padding: 6px;
  521. }
  522. .material-tab {
  523. margin-top: 35px;
  524. > .material-tab-item {
  525. display: inline-block;
  526. margin-right: 20px;
  527. position: relative;
  528. cursor: pointer;
  529. > .text {
  530. font-size: 14px;
  531. font-family: MicrosoftYaHei;
  532. color: rgba(255, 255, 255, 0.6);
  533. &.active {
  534. color: #fff;
  535. }
  536. }
  537. > .bottom-line {
  538. position: absolute;
  539. left: 50%;
  540. transform: translateX(-50%);
  541. bottom: -4px;
  542. width: 16px;
  543. height: 2px;
  544. background: #0076f6;
  545. border-radius: 1px;
  546. }
  547. }
  548. }
  549. .filter {
  550. margin-top: 28px;
  551. width: 100%;
  552. height: 36px;
  553. background: #252526;
  554. border-radius: 2px;
  555. border: 1px solid #404040;
  556. position: relative;
  557. &.active {
  558. border: 1px solid @color;
  559. }
  560. > input {
  561. box-sizing: border-box;
  562. width: calc(100% - 42px);
  563. height: 100%;
  564. border: none;
  565. padding-left: 16px;
  566. background: transparent;
  567. color: #fff;
  568. outline: none;
  569. }
  570. > input::placeholder,
  571. textarea::placeholder {
  572. font-size: 14px;
  573. color: #505050 !important;
  574. }
  575. > .search-icon {
  576. position: absolute;
  577. top: 50%;
  578. transform: translateY(-50%);
  579. right: 18px;
  580. color: #404040;
  581. font-size: 20px;
  582. }
  583. > .clear-icon {
  584. position: absolute;
  585. top: 50%;
  586. transform: translateY(-50%);
  587. right: 18px;
  588. color: #fff;
  589. font-size: 20px;
  590. cursor: pointer;
  591. }
  592. }
  593. .btns {
  594. position: absolute;
  595. right: 26px;
  596. bottom: 26px;
  597. button:first-child {
  598. margin-right: 16px;
  599. }
  600. }
  601. }
  602. .table-select.bright {
  603. border: 1px solid #ebedf0;
  604. background: #fff;
  605. .title {
  606. color: #323233;
  607. }
  608. .close-btn {
  609. }
  610. .material-tab {
  611. > .material-tab-item {
  612. > .text {
  613. color: #969799;
  614. &.active {
  615. color: #323233;
  616. }
  617. }
  618. > .bottom-line {
  619. }
  620. }
  621. }
  622. .filter {
  623. background: #f7f8fa;
  624. border: 1px solid #ebedf0;
  625. > input {
  626. color: #323233;
  627. }
  628. > input::placeholder,
  629. textarea::placeholder {
  630. font-size: 14px;
  631. color: #c8c9cc !important;
  632. }
  633. > .search-icon {
  634. color: #c8c9cc;
  635. }
  636. > .clear-icon {
  637. color: #c8c9cc;
  638. }
  639. }
  640. .btns {
  641. }
  642. }
  643. </style>