materialSelector.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  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,image/gif'"
  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. <!-- 视频已带suffix -->
  301. <template v-slot:materialIcon="slotProps">
  302. <img
  303. v-if="slotProps.materialInfo[slotProps.tableItemStructure.key]"
  304. :src="slotProps.materialInfo[slotProps.tableItemStructure.key]"
  305. alt=""
  306. />
  307. <img v-else src="@/assets/img/list_placeholder.png" alt="" />
  308. </template>
  309. </MaterialList>
  310. <MaterialList
  311. v-if="selectableType.includes('3D')"
  312. v-show="currentMaterialType === '3D'"
  313. :isDarkTheme="isDarkTheme"
  314. :currentMaterialType="currentMaterialType"
  315. :materialType="'3D'"
  316. :tableHeaderKeyList="['thumb', 'sceneName']"
  317. :isMultiSelection="isMultiSelection"
  318. :select="select"
  319. :searchKey="searchKey"
  320. @need-clear-filter="
  321. () => {
  322. searchKey = '';
  323. }
  324. "
  325. >
  326. <template v-slot:materialIcon="slotProps">
  327. <img
  328. v-if="slotProps.materialInfo[slotProps.tableItemStructure.key]"
  329. :src="
  330. slotProps.materialInfo[slotProps.tableItemStructure.key] +
  331. $imgsuffix
  332. "
  333. alt=""
  334. />
  335. <img v-else src="@/assets/img/list_placeholder.png" alt="" />
  336. </template>
  337. </MaterialList>
  338. <div class="btns">
  339. <button
  340. class="ui-button"
  341. :class="isDarkTheme ? 'deepcancel' : 'cancel'"
  342. @click="$emit('cancle')"
  343. >
  344. {{ $i18n.t("gather.cancel") }}
  345. </button>
  346. <button
  347. class="ui-button submit"
  348. :class="{ disable: !select.length }"
  349. @click="onClickComfirm"
  350. >
  351. {{ $i18n.t("gather.comfirm"), }}
  352. </button>
  353. </div>
  354. </div>
  355. </template>
  356. <script>
  357. import { mapMutations } from "vuex";
  358. import MaterialList from "./materialListInMaterialSelector.vue";
  359. import AudioIconCanPlay from "@/components/audio/indexForEditor.vue";
  360. import { getImgWH, changeByteUnit } from "@/utils/file";
  361. import { debounce } from "@/utils/other.js";
  362. import { checkMStatus } from "@/api";
  363. export default {
  364. components: {
  365. MaterialList,
  366. AudioIconCanPlay,
  367. },
  368. props: {
  369. isDarkTheme: {
  370. type: Boolean,
  371. default: true,
  372. },
  373. title: {
  374. default: "",
  375. type: String,
  376. },
  377. selectableType: {
  378. type: Array,
  379. default: function () {
  380. return ["image", "pano", "audio", "video", "3D"];
  381. },
  382. },
  383. initialMaterialType: {
  384. type: String,
  385. default: "image",
  386. },
  387. isMultiSelection: {
  388. type: Boolean,
  389. default: false,
  390. },
  391. },
  392. data() {
  393. return {
  394. select: [],
  395. isSearchKeyInputActive: false,
  396. searchKey: "", // 搜索关键词
  397. latestUsedSearchKey: "",
  398. currentMaterialType: this.initialMaterialType,
  399. };
  400. },
  401. watch: {},
  402. methods: {
  403. ...mapMutations(["clearUploadStatusLists"]),
  404. async checkPanoFileInput(eachFile, i) {
  405. let WHRate = null;
  406. try {
  407. const { width, height } = await getImgWH(eachFile);
  408. WHRate = width / height;
  409. } catch (e) {
  410. console.error("获取图像宽高失败:", e);
  411. setTimeout(() => {
  412. this.$msg({
  413. message: `“${eachFile.name}”${this.$i18n.t(`gather.pano_fail`)}`,
  414. type: "warning",
  415. });
  416. }, i * 100);
  417. return false;
  418. }
  419. if (WHRate !== 2) {
  420. console.log("宽高比不对!");
  421. setTimeout(() => {
  422. this.$msg({
  423. message: `“${eachFile.name}”${this.$i18n.t(`gather.pano_fail`)}`,
  424. type: "warning",
  425. });
  426. }, i * 100);
  427. return false;
  428. } else {
  429. console.log(WHRate);
  430. return true;
  431. }
  432. },
  433. panoUploadLongPolling(uploadStatusList) {
  434. let needPollingTaskList = uploadStatusList.filter(
  435. (item) => item.status === "LOADING" && item.ifKnowProgress === false
  436. );
  437. if (needPollingTaskList.length > 0) {
  438. checkMStatus(
  439. {
  440. ids: needPollingTaskList.map((item) => item.backendId),
  441. islongpolling: true,
  442. },
  443. (res) => {
  444. // 1切图中,2失败,3成功
  445. res.data.forEach((eachRes) => {
  446. if (eachRes.status === 2) {
  447. const index = uploadStatusList.findIndex(
  448. (eachTask) => eachTask.backendId === eachRes.id
  449. );
  450. if (index >= 0) {
  451. const targetItem = uploadStatusList[index];
  452. targetItem.status = "FAIL";
  453. targetItem.statusText = this.$i18n.t(
  454. `gather.material_cutting_fail`
  455. );
  456. targetItem.ifKnowProgress = true;
  457. }
  458. } else if (eachRes.status === 3) {
  459. const index = uploadStatusList.findIndex(
  460. (eachTask) => eachTask.backendId === eachRes.id
  461. );
  462. if (index >= 0) {
  463. const targetItem = uploadStatusList[index];
  464. targetItem.status = "SUCCESS";
  465. if (eachRes.fileSize) {
  466. eachRes.fileSize = changeByteUnit(Number(eachRes.fileSize));
  467. } else {
  468. eachRes.fileSize = "";
  469. }
  470. targetItem.successInfo = eachRes;
  471. }
  472. }
  473. });
  474. }
  475. );
  476. }
  477. },
  478. videoMaterialItemCustomProcess(item) {
  479. item.icon =
  480. process.env.VUE_APP_ORIGIN == "aws"
  481. ? item.icon
  482. : item.ossPath + this.$videoImg;
  483. },
  484. onClickComfirm: debounce(function () {
  485. this.$emit("submit", this.select);
  486. }, 250),
  487. },
  488. mounted() {
  489. this.clearUploadStatusLists();
  490. },
  491. };
  492. </script>
  493. <style lang="less" scoped>
  494. .table-select {
  495. position: absolute;
  496. z-index: 3;
  497. left: 50%;
  498. top: 50%;
  499. transform: translateX(-50%) translateY(-50%);
  500. width: 600px;
  501. height: 730px;
  502. background: #1a1b1d;
  503. border-radius: 4px;
  504. border: 1px solid #404040;
  505. padding: 26px;
  506. .title {
  507. font-size: 18px;
  508. color: rgba(255, 255, 255, 0.6);
  509. }
  510. .close-btn {
  511. display: inline-block;
  512. position: absolute;
  513. top: 26px;
  514. right: 20px;
  515. font-size: 12px;
  516. color: #969799;
  517. cursor: pointer;
  518. padding: 6px;
  519. }
  520. .material-tab {
  521. margin-top: 35px;
  522. > .material-tab-item {
  523. display: inline-block;
  524. margin-right: 20px;
  525. position: relative;
  526. cursor: pointer;
  527. > .text {
  528. font-size: 14px;
  529. font-family: MicrosoftYaHei;
  530. color: rgba(255, 255, 255, 0.6);
  531. &.active {
  532. color: #fff;
  533. }
  534. }
  535. > .bottom-line {
  536. position: absolute;
  537. left: 50%;
  538. transform: translateX(-50%);
  539. bottom: -4px;
  540. width: 16px;
  541. height: 2px;
  542. background: #0076f6;
  543. border-radius: 1px;
  544. }
  545. }
  546. }
  547. .filter {
  548. margin-top: 28px;
  549. width: 100%;
  550. height: 36px;
  551. background: #252526;
  552. border-radius: 2px;
  553. border: 1px solid #404040;
  554. position: relative;
  555. &.active {
  556. border: 1px solid @color;
  557. }
  558. > input {
  559. box-sizing: border-box;
  560. width: calc(100% - 42px);
  561. height: 100%;
  562. border: none;
  563. padding-left: 16px;
  564. background: transparent;
  565. color: #fff;
  566. outline: none;
  567. }
  568. > input::placeholder,
  569. textarea::placeholder {
  570. font-size: 14px;
  571. color: #505050 !important;
  572. }
  573. > .search-icon {
  574. position: absolute;
  575. top: 50%;
  576. transform: translateY(-50%);
  577. right: 18px;
  578. color: #404040;
  579. font-size: 20px;
  580. }
  581. > .clear-icon {
  582. position: absolute;
  583. top: 50%;
  584. transform: translateY(-50%);
  585. right: 18px;
  586. color: #fff;
  587. font-size: 20px;
  588. cursor: pointer;
  589. }
  590. }
  591. .btns {
  592. position: absolute;
  593. right: 26px;
  594. bottom: 26px;
  595. button:first-child {
  596. margin-right: 16px;
  597. }
  598. }
  599. }
  600. .table-select.bright {
  601. border: 1px solid #ebedf0;
  602. background: #fff;
  603. .title {
  604. color: #323233;
  605. }
  606. .close-btn {
  607. }
  608. .material-tab {
  609. > .material-tab-item {
  610. > .text {
  611. color: #969799;
  612. &.active {
  613. color: #323233;
  614. }
  615. }
  616. > .bottom-line {
  617. }
  618. }
  619. }
  620. .filter {
  621. background: #f7f8fa;
  622. border: 1px solid #ebedf0;
  623. > input {
  624. color: #323233;
  625. }
  626. > input::placeholder,
  627. textarea::placeholder {
  628. font-size: 14px;
  629. color: #c8c9cc !important;
  630. }
  631. > .search-icon {
  632. color: #c8c9cc;
  633. }
  634. > .clear-icon {
  635. color: #c8c9cc;
  636. }
  637. }
  638. .btns {
  639. }
  640. }
  641. </style>