邃芒慧语、照片说话(PC) https://photo.metavatar.cc/
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 

3420 wiersze
99 KiB

  1. <template>
  2. <div class="page flex justify-around">
  3. <!-- 左侧栏 -->
  4. <div class="left ">
  5. <!-- 顶部 -->
  6. <!-- 选中AiFace -->
  7. <div class="left-top" v-show="showUploadBtn == 1" >
  8. <el-row class="justify-between">
  9. <!-- title框 v-show="!submitImgBtn"-->
  10. <div
  11. v-show="showUploadBtn == 1"
  12. class="flex justify-between"
  13. style="width: 300px"
  14. >
  15. <el-input
  16. maxlength="15"
  17. v-model="title"
  18. :placeholder="$t('createVideo.typeTitle')"
  19. />
  20. <el-icon class="icon-edit"><i-ep-edit /></el-icon>
  21. <!-- 编辑字幕框 -->
  22. </div>
  23. <div v-if="imgUrl&&SubtitlesSwitch" class="editSubtitlesBox ">
  24. <!-- 字体 -->
  25. <div class="item">{{$t('createVideo.fontFamily')}}:&nbsp;<el-select v-model="SubtitlesFontFamily" style="width: 80px;" placeholder=" " size="small">
  26. <el-option
  27. v-for="item in fontFamilyList"
  28. :key="item.value"
  29. :label="item.label"
  30. :value="item.value"
  31. />
  32. </el-select></div>
  33. <!-- Y轴 -->
  34. <div v-show="false" class="item">{{$t('createVideo.YAxis')}}:&nbsp;<el-input
  35. v-model="SubtitlesTop"
  36. width="30"
  37. /></div>
  38. <!-- X轴 -->
  39. <div v-show="false" class="item" style="">{{$t('createVideo.XAxis')}}:&nbsp;<el-input
  40. v-model="SubtitlesLeft"
  41. width="30"
  42. /></div>
  43. <!-- 单双行 -->
  44. <div class="item" >{{$t('createVideo.rows')}}:&nbsp; <el-switch
  45. v-model="SubtitlesRow"
  46. inline-prompt
  47. style="--el-switch-on-color: #000; --el-switch-off-color: #13ce66;"
  48. :active-text="$t('createVideo.rows2')"
  49. :inactive-text="$t('createVideo.rows1')"
  50. /></div>
  51. <!-- 字号大小 -->
  52. <div class="item">{{$t('createVideo.fontSize')}}:&nbsp;<el-input
  53. v-model="SubtitlesSize"
  54. width="30"
  55. /></div>
  56. <!-- 字幕盒子宽度 -->
  57. <div v-if="false" class="item">{{$t('createVideo.width')}}:&nbsp;<el-input
  58. v-model="SubtitlesWidth"
  59. width="30"
  60. /></div>
  61. <!-- 字色 -->
  62. <div class="item">
  63. <el-tooltip
  64. class="box-item"
  65. effect="dark"
  66. :content="$t('createVideo.FontColor')"
  67. placement="bottom"
  68. >
  69. <img style=" max-width: 15px;
  70. max-height: 15px;" src="../../assets/img/font.png" alt="">
  71. </el-tooltip>
  72. :&nbsp;
  73. <div @click="handleSubtitlesColorShow" class="myColorBox" :style="{backgroundColor: SubtitlesColor}">
  74. <div v-show="SubtitlesColorShow" class="selectColor">
  75. <div v-for="item in colorList" :style="{backgroundColor: item}" class="selectColor-item" @click="SubtitlesColor=item"></div>
  76. </div>
  77. </div>
  78. </div>
  79. <!-- 背景色 -->
  80. <div class="item"> <el-tooltip
  81. class="box-item"
  82. effect="dark"
  83. :content="$t('createVideo.backgroundColor')"
  84. placement="bottom"
  85. >
  86. <img style=" max-width: 25px;
  87. max-height: 25px;" src="../../assets/img/bgc.png" alt="">
  88. </el-tooltip>:&nbsp;
  89. <div @click="handleSubtitlesBgcShow" class="myColorBox" :style="{backgroundColor: SubtitlesBgc}">
  90. <div v-show="SubtitlesBgcShow" class="selectColor">
  91. <div v-for="item in colorList" :style="{backgroundColor: item}" class="selectColor-item" @click="SubtitlesBgc=item"></div>
  92. </div>
  93. </div>
  94. </div>
  95. </div>
  96. </el-row>
  97. <!-- 中间展示大图 -->
  98. <div ref="canvasImgBox" class="bigImg inmiddle">
  99. <img id="canvasImg" :src="imgUrl" alt="" class="" />
  100. <div id="SubtitlesBox" ref="SubtitlesBox" @mousedown="handleDrag" :style="SubtitlesStyle" v-if="SubtitlesSwitch" class="SubtitlesBox">
  101. <div class="SubtitlesTip">{{$t('createVideo.SubtitlesTip')}}</div>
  102. <div v-show="SubtitlesRow" class="SubtitlesTip">{{$t('createVideo.SubtitlesTip')}}</div>
  103. <div class="stretchBox box1"></div>
  104. <div class="stretchBox box2"></div>
  105. <div class="stretchBox box3"></div>
  106. <div class="stretchBox box4" @mousedown="startResize2"
  107. ></div>
  108. <div class="stretchBox box5" @mousedown="startResize"
  109. ></div>
  110. <div class="stretchBox box6"></div>
  111. <div class="stretchBox box7"></div>
  112. <div class="stretchBox box8"></div>
  113. <div v-show="false" class="stretchBox box9" @mousedown="startRotation" @mousemove="rotateBox" @mouseup="stopRotation"></div>
  114. </div>
  115. </div>
  116. <!-- 字幕开关 -->
  117. <div v-if="imgUrl" class="SubtitlesSwitch">
  118. <p>{{ $t('createVideo.subtitles') }}</p>
  119. <el-switch v-model="SubtitlesSwitch" style="--el-switch-on-color: #000"/>
  120. </div>
  121. </div>
  122. <!-- 选中需要上传的图片前 -->
  123. <div class="left-top" v-show="showUploadBtn == 2">
  124. <!-- 中间展示大图 -->
  125. <div class="noImg">
  126. <my-loading
  127. style="margin-top: 150px"
  128. :isLoading="compressionImgLoad"
  129. ></my-loading>
  130. <div v-if="!compressionImgLoad" class="trueImg inmiddle">
  131. <div class="imgBox">
  132. <img src="../../assets/img/trueImg.png" alt="" />
  133. </div>
  134. <div class="icon">✔</div>
  135. </div>
  136. <div v-if="!compressionImgLoad" class="upload inmiddle">
  137. <p>
  138. <!-- <el-icon><i-ep-FolderAdd /></el-icon> -->
  139. <img src="../../assets/img/uploadImg.png" alt="" />
  140. </p>
  141. <span>{{ $t("createVideo.uploadImgTip1") }}</span>
  142. <span>{{ $t("createVideo.uploadImgTip2") }}</span>
  143. <input accept="image/*" type="file" @change="lookImg" />
  144. </div>
  145. <div v-if="!compressionImgLoad" class="falseImg inmiddle">
  146. <div>
  147. <div class="imgBox">
  148. <img src="../../assets/img/false1.png" alt="" />
  149. </div>
  150. <div class="icon">×</div>
  151. </div>
  152. <div>
  153. <div class="imgBox">
  154. <img src="../../assets/img/false2.png" alt="" />
  155. </div>
  156. <div class="icon">×</div>
  157. </div>
  158. <div>
  159. <div class="imgBox">
  160. <img src="../../assets/img/false3.png" alt="" />
  161. </div>
  162. <div class="icon">×</div>
  163. </div>
  164. </div>
  165. </div>
  166. </div>
  167. <!-- loading -->
  168. <div class="left-top" v-show="showUploadBtn == 3">
  169. <!-- 中间展示大图 -->
  170. <div class="noImg">
  171. <my-loading
  172. style="margin-top: 150px"
  173. :isLoading="compressionImgLoad"
  174. :tips="imgUploadTips"
  175. ></my-loading>
  176. </div>
  177. </div>
  178. <!-- 选中需要上传的图片后 -->
  179. <div class="left-top" v-show="showUploadBtn == 4">
  180. <el-row style="display: flex; justify-content: flex-end">
  181. <!-- 上传按钮 废弃了-->
  182. <!-- <div v-if="false" class="btnBox" v-show="submitImgBtn"> -->
  183. <div v-if="false" class="btnBox">
  184. <button class="uploadBtn">
  185. {{ $t("createVideo.reselect") }}
  186. <input accept="image/*" type="file" @change="lookImg" />
  187. </button>
  188. <button class="uploadBtn" @click="changeFile">
  189. {{ $t("createVideo.sureUpload") }}
  190. </button>
  191. </div>
  192. </el-row>
  193. <!-- 中间展示大图 -->
  194. <div class="bigImg inmiddle">
  195. <img :src="imgUrl" alt="" class="inmiddle" />
  196. </div>
  197. </div>
  198. <!-- AI生成图片展示区 -->
  199. <div class="left-top" v-show="showUploadBtn == 5">
  200. <div class="editAiPicBox">
  201. <div class="edit">
  202. <div class="TagBox"></div>
  203. </div>
  204. <div class="AIpicBox">
  205. <div class="picBox">
  206. <img src="../../assets/img/trueImg.png" alt="" />
  207. </div>
  208. </div>
  209. </div>
  210. </div>
  211. <!-- 底部 -->
  212. <div class="left-bottom">
  213. <!-- tab栏 -->
  214. <div class="tab flex">
  215. <div
  216. class="pointer"
  217. :class="showFace == 0 ? 'tabAvtive' : ''"
  218. @click="chooSystemImg"
  219. >
  220. {{ $t("createVideo.availableMould") }}
  221. </div>
  222. <div
  223. class="pointer"
  224. :class="showFace == 1 ? 'tabAvtive' : ''"
  225. @click="chooseMyImg"
  226. >
  227. {{ $t("createVideo.personalWarehouse") }}
  228. </div>
  229. <div
  230. class="pointer aiPicTab"
  231. :class="showFace == 2 ? 'tabAvtive' : ''"
  232. @click="chooseAiPic"
  233. >
  234. <div class="subscript subscript1">
  235. <!-- <span>AI &nbsp;</span> -->
  236. <img src="../../assets/img/aitoface.png" alt="" />
  237. </div>
  238. {{ $t("createVideo.Aipicture") }}
  239. </div>
  240. </div>
  241. <!-- 系统相册 -->
  242. <div
  243. class="faceList"
  244. v-if="showFace == 0"
  245. v-infinite-scroll="loadMoreAiFaceSystem"
  246. :infinite-scroll-disabled="overLoadSystem"
  247. infinite-scroll-distance="10"
  248. >
  249. <!-- 上传图片组件 -->
  250. <div v-if="showFace == 1" class="faceItem" @click="clickAddBtn">
  251. <span class="uploadImg">+ </span>
  252. </div>
  253. <my-loading :isLoading="feceItemLoading"></my-loading>
  254. <!-- face列表 -->
  255. <div
  256. :class="faceItemActive == item.id ? 'faceItemActive' : ''"
  257. class="faceItem pointer"
  258. @click="setImgUrl(item.material, item.id)"
  259. v-for="item in AiFaceList"
  260. :key="item.id"
  261. >
  262. <div class="imgBox">
  263. <img :src="item.material" alt="" />
  264. </div>
  265. s
  266. </div>
  267. </div>
  268. <!-- 个人相册 -->
  269. <div
  270. class="faceList"
  271. v-if="showFace == 1"
  272. v-infinite-scroll="loadMoreAiFaceUser"
  273. :infinite-scroll-disabled="overLoadUser"
  274. infinite-scroll-distance="10"
  275. >
  276. <!-- 上传图片组件 -->
  277. <div v-if="showFace == 1" class="faceItem" @click="clickAddBtn">
  278. <span class="uploadImg">+ </span>
  279. </div>
  280. <my-loading :isLoading="feceItemLoading2"></my-loading>
  281. <!-- face列表 -->
  282. <div
  283. :class="faceItemActive == item.id ? 'faceItemActive' : ''"
  284. class="faceItem pointer"
  285. @click="setImgUrl(item.material, item.id)"
  286. v-for="item in userAiFaceList"
  287. :key="item.id"
  288. >
  289. <div class="imgBox">
  290. <img :src="item.material" alt="" />
  291. </div>
  292. <div
  293. v-if="faceItemActive == item.id"
  294. @click="deleteFaceImg(item.id)"
  295. class="deleteIcon"
  296. >
  297. ✖︎
  298. </div>
  299. </div>
  300. </div>
  301. <!-- AI生成图片 -->
  302. <div
  303. class="Aipicture"
  304. v-if="showFace == 2"
  305. >
  306. <div class="title">
  307. <el-icon><i-ep-Lock /></el-icon>
  308. {{ $t("createVideo.comingSoon") }}
  309. </div>
  310. </div>
  311. </div>
  312. </div>
  313. <!-- 右侧栏 -->
  314. <div class="right scrolly">
  315. <div class="subscript">
  316. <!-- <span>GPT</span> -->
  317. <img src="../../assets/img/gpttotext.png" alt="" />
  318. </div>
  319. <el-tabs @tab-click="clickTab4" type="border-card" class="tabs">
  320. <!-- ----------------------------- 1 -->
  321. <el-tab-pane :label="$t('createVideo.typeScript')">
  322. <div class="card1">
  323. <div class="text">
  324. <!-- 文本域 -->
  325. <el-input
  326. maxlength="500"
  327. id="textInputId"
  328. v-model="textareaContent"
  329. :rows="12"
  330. type="textarea"
  331. :placeholder="$t('createVideo.aiText')"
  332. />
  333. <div class="sundriesBox">
  334. <span v-if="false" class="pointer" @click="getPreviewAudio">
  335. <el-tooltip
  336. class="box-item"
  337. effect="dark"
  338. :content="$t('createVideo.tooltip1')"
  339. placement="top"
  340. >
  341. 🔊
  342. </el-tooltip></span
  343. >
  344. <my-Loading-Icon
  345. class="previewAudioLoadingBox"
  346. v-if="false"
  347. ></my-Loading-Icon>
  348. <span class="pointer" @click="insertIcon">
  349. <el-tooltip
  350. class="box-item"
  351. effect="dark"
  352. :content="$t('createVideo.tooltip2')"
  353. placement="bottom"
  354. >
  355. 🕗
  356. </el-tooltip></span
  357. >
  358. <i
  359. >{{ $t("createVideo.entered") }}
  360. {{ textareaContent.length }} / 500</i
  361. >
  362. </div>
  363. </div>
  364. <p>{{ $t("createVideo.languages") }}</p>
  365. <!-- 语言 -->
  366. <el-select
  367. @change="chooseType"
  368. v-model="language"
  369. :placeholder="$t('createVideo.select')"
  370. style="width: 100%"
  371. >
  372. <!-- :label="lanChange == 'zh-cn' ? item.chineseName : item.name" -->
  373. <el-option
  374. v-for="item in languageOptions"
  375. :key="item.value"
  376. :label="lanChange == 'zh-cn' ? item.chineseName : item.name"
  377. :value="item.id"
  378. >
  379. <img
  380. :src="item.img"
  381. alt=""
  382. style="
  383. width: 20px;
  384. margin-right: 10px;
  385. vertical-align: text-top;
  386. "
  387. />
  388. <span style="line-height: 20px">{{
  389. lanChange == "zh-cn" ? item.chineseName : item.name
  390. }}</span>
  391. </el-option>
  392. </el-select>
  393. <p>{{ $t("createVideo.voices") }}</p>
  394. <!-- 声音 -->
  395. <el-select
  396. @change="chooseStyle"
  397. :disabled="language ? false : true"
  398. v-model="sound"
  399. :placeholder="$t('createVideo.select')"
  400. style="width: 100%"
  401. >
  402. <el-option
  403. v-for="item in soundOptions"
  404. :key="item.value"
  405. :label="
  406. (lanChange == 'zh-cn'
  407. ? item.displayName +
  408. `&nbsp; (${item.sex == 1 ? '男' : '女'})`
  409. : item.localName +
  410. `&nbsp; (${item.sex == 1 ? 'male' : 'female'})`) +
  411. returnAge(item.ageType)
  412. "
  413. :value="item.id"
  414. />
  415. </el-select>
  416. <p>{{ $t("createVideo.styles") }}</p>
  417. <!-- 风格 -->
  418. <el-select
  419. :disabled="soundStyleOptions ? false : true"
  420. v-model="soundStyle"
  421. :placeholder="$t('createVideo.select')"
  422. style="width: 100%"
  423. >
  424. <el-option
  425. v-for="item in soundStyleOptions"
  426. :key="item.name"
  427. :label="lanChange == 'zh-cn' ? item.name : item.engName"
  428. :value="item.name"
  429. >
  430. <div style="padding: 0 20px">
  431. {{ lanChange == "zh-cn" ? item.name : item.engName }}
  432. <span
  433. style="float: right; cursor: pointer"
  434. @click.stop="ListenVoices(item.url)"
  435. >🔊</span
  436. >
  437. </div>
  438. <!-- <span class="pointer" @click="getPreviewAudio">
  439. <el-tooltip
  440. v-if="false"
  441. class="box-item"
  442. effect="dark"
  443. :content="$t('createVideo.tooltip1')"
  444. placement="top"
  445. >
  446. </el-tooltip></span > -->
  447. </el-option>
  448. </el-select>
  449. <!-- 保存视频 -->
  450. <el-button
  451. @click="saveOrUpdate(1, 'saveVideo')"
  452. color="#000"
  453. type="primary"
  454. >
  455. <my-loading :isLoading="createLoading"></my-loading>
  456. <span v-if="!createLoading">{{
  457. $t("createVideo.saveVideo")
  458. }}</span></el-button
  459. >
  460. <!-- 生成视频 -->
  461. <el-button
  462. style="margin-left: 0"
  463. @click="saveOrUpdate(1)"
  464. color="#000"
  465. type="primary"
  466. >
  467. <my-loading :isLoading="createLoading"></my-loading>
  468. <span v-if="!createLoading">{{
  469. $t("createVideo.generateVideo")
  470. }}</span></el-button
  471. >
  472. </div>
  473. </el-tab-pane>
  474. <!-- ------------------------ 2 -->
  475. <el-tab-pane :label="$t('createVideo.uploadAudio')">
  476. <div class="card2">
  477. <!-- 上传音频 -->
  478. <p>
  479. <el-icon class="icon-Headset"><i-ep-Headset /></el-icon>
  480. {{ $t("createVideo.uploadAudio") }}
  481. <div class="overtimeTips" style="float: right">
  482. <img src="../../assets/img/tips.png" alt="" />
  483. {{
  484. lanChange == "zh-cn"
  485. ? `免费用户限制为60秒!`
  486. : `Free user limit 60 seconds!`
  487. }}
  488. </div>
  489. </p>
  490. <div
  491. class="uploadVideo"
  492. :class="
  493. audioUrl && showAudioWhich == 1 ? 'uploadAudioAvtion2' : ''
  494. "
  495. >
  496. <!-- <el-icon><i-ep-FolderAdd /></el-icon> -->
  497. <!-- <div>
  498. </div> -->
  499. <img src="../../assets/img/uploadImg.png" alt="" />
  500. <!-- fileInput.value.files[0] e.target.value = null -->
  501. <input
  502. accept="audio/*"
  503. type="file"
  504. ref="fileInput"
  505. @click="handleFileClick"
  506. @change="handleFileChange"
  507. />
  508. <div class="AudioBox" v-if="audioUrl && showAudioWhich == 1">
  509. <audio controls :src="audioUrl" id="uploadAudio"></audio>
  510. </div>
  511. </div>
  512. <!-- 录制MP3上传语音 -->
  513. <p>
  514. <el-icon class="icon-Mic"><i-ep-Mic /></el-icon>
  515. {{ $t("createVideo.recordedAudio") }}
  516. <div class="overtimeTips" style="float: right">
  517. <img src="../../assets/img/tips.png" alt="" />
  518. {{
  519. lanChange == "zh-cn"
  520. ? `免费用户限制为60秒!`
  521. : `Free user limit 60 seconds!`
  522. }}
  523. </div>
  524. </p>
  525. <div
  526. class="uploadAudio"
  527. :class="
  528. audioUrl && showAudioWhich == 2 ? 'uploadAudioAvtion' : ''
  529. "
  530. >
  531. <div class="btnBox">
  532. <button
  533. class="pointer"
  534. :disabled="!isRecording"
  535. @click="startRecording"
  536. >
  537. {{ $t("createVideo.startRecorded") }}
  538. <div v-if="isRecording" class="pointer">
  539. <el-icon><i-ep-VideoPlay /></el-icon>
  540. </div>
  541. <div v-if="!isRecording" class="pointer">
  542. <el-icon><i-ep-VideoPause /></el-icon>
  543. </div>
  544. </button>
  545. {{ formatTime(duration) }}
  546. <button
  547. class="pointer"
  548. @click="stopRecording"
  549. :disabled="isRecording"
  550. >
  551. {{ $t("createVideo.stopRecorded") }}
  552. <div class="pointer">
  553. <el-icon><i-ep-SwitchButton /></el-icon>
  554. </div>
  555. </button>
  556. </div>
  557. <div class="AudioBox" v-if="audioUrl && showAudioWhich == 2">
  558. <audio controls :src="audioUrl" id="uploadAudio"></audio>
  559. </div>
  560. </div>
  561. <!-- <div class="AudioBox" v-if="audioUrl">
  562. <audio controls :src="audioUrl" id="uploadAudio"></audio>
  563. </div> -->
  564. <!-- 保存视频 -->
  565. <el-button
  566. @click="saveOrUpdate(2, 'saveVideo')"
  567. color="#000"
  568. type="primary"
  569. >
  570. <my-loading :isLoading="createLoading"></my-loading>
  571. <span v-if="!createLoading">{{
  572. $t("createVideo.saveVideo")
  573. }}</span></el-button
  574. >
  575. <!-- 生成视频 -->
  576. <el-button
  577. style="margin-left: 0"
  578. @click="saveOrUpdate(2)"
  579. color="#000"
  580. type="primary"
  581. >
  582. <my-loading :isLoading="createLoading"></my-loading>
  583. <span v-if="!createLoading">{{
  584. $t("createVideo.generateVideo")
  585. }}</span>
  586. </el-button>
  587. </div>
  588. </el-tab-pane>
  589. <!-- ----------------------- 3 -->
  590. <el-tab-pane :label="$t('createVideo.music')">
  591. <div class="card3">
  592. <!-- 选择音乐 -->
  593. <p>
  594. <el-icon class="icon-Headset"><i-ep-Headset /></el-icon>
  595. {{ $t("createVideo.chooseMusic") }}
  596. </p>
  597. <!-- tab栏 -->
  598. <div class="musicTab">
  599. <div
  600. class="musicTabItem"
  601. :class="musicTabActive == 1 ? 'musicTabItemActive' : ''"
  602. @click="musicTabActiveChange(1)"
  603. >
  604. {{ $t("createVideo.hotMusic") }}
  605. </div>
  606. <div
  607. class="musicTabItem"
  608. :class="musicTabActive == 2 ? 'musicTabItemActive' : ''"
  609. @click="musicTabActiveChange(2)"
  610. >
  611. {{ $t("createVideo.personalWarehouse") }}
  612. </div>
  613. </div>
  614. <!-- 上传音乐 -->
  615. <div class="overtimeTips" v-if="musicTabActive == 2" style="margin-top: 5px;">
  616. <img src="../../assets/img/tips.png" alt="" />
  617. {{
  618. lanChange == "zh-cn"
  619. ? `免费用户限制为60秒!`
  620. : `Free user limit 60 seconds!`
  621. }}
  622. </div>
  623. <div v-if="musicTabActive == 2" class="uploadVideo">
  624. <!-- <el-icon><i-ep-FolderAdd /></el-icon> -->
  625. <div class="imgInCenter">
  626. <img src="../../assets/img/uploadImg.png" alt="" />
  627. </div>
  628. <input
  629. accept="audio/*"
  630. type="file"
  631. ref="fileMusicInput"
  632. @click="handleFileMusicClick"
  633. @change="uploadMusic"
  634. />
  635. </div>
  636. <!-- 系统音乐列表展示 -->
  637. <div v-if="musicTabActive == 1" class="musicList">
  638. <div
  639. @click="chooseMusicUrl(item)"
  640. class="musicItem"
  641. :class="musicItemAction == item.id ? 'musicItemAvtive' : ''"
  642. v-for="item in musicListSystem"
  643. :key="item.id"
  644. >
  645. <span class="musicNmae"> 🎵{{ item.name }}</span>
  646. <span class="musicTime"> {{ handleTime(item.time) }}</span>
  647. </div>
  648. </div>
  649. <!-- 个人音乐列表展示 -->
  650. <div v-if="musicTabActive == 2" class="musicList">
  651. <div
  652. @click="chooseMusicUrl(item)"
  653. class="musicItem"
  654. :class="musicItemAction == item.id ? 'musicItemAvtive' : ''"
  655. v-for="item in musicListPerson"
  656. :key="item.id"
  657. >
  658. <span class="musicNmae"> 🎵{{ item.name }}</span>
  659. <span class="musicTime"> {{ handleTime(item.time) }}</span>
  660. <span
  661. v-if="musicItemAction == item.id"
  662. class="delMusic"
  663. @click="deleteMusic(item.id)"
  664. ><el-icon><i-ep-Delete /></el-icon>
  665. </span>
  666. </div>
  667. </div>
  668. <!-- 系统分页 -->
  669. <div
  670. v-if="musicTabActive == 1 && musicListTotalSystem"
  671. class="pagination"
  672. >
  673. <el-pagination
  674. v-model:current-page="musicListPageNumSystem"
  675. v-model:page-size="musicListPageSize"
  676. :small="false"
  677. :disabled="false"
  678. :background="true"
  679. layout="total, prev, pager, next, jumper"
  680. :total="musicListTotalSystem"
  681. @current-change="musicListPageNumSystemChange"
  682. />
  683. </div>
  684. <!-- 个人分页 -->
  685. <div
  686. v-if="musicTabActive == 2 && musicListTotalPerson"
  687. class="pagination"
  688. >
  689. <el-pagination
  690. v-model:current-page="musicListPageNumPerson"
  691. v-model:page-size="musicListPageSize"
  692. :small="false"
  693. :disabled="false"
  694. :background="true"
  695. layout="total, prev, pager, next, jumper"
  696. :total="musicListTotalPerson"
  697. @current-change="musicListPageNumPersonChange"
  698. />
  699. </div>
  700. <div class="AudioBox" v-if="audioMusicUrl">
  701. <audio controls :src="audioMusicUrl" id="uploadAudio"></audio>
  702. </div>
  703. <!-- 保存视频 -->
  704. <el-button
  705. @click="saveOrUpdate(3, 'saveVideo')"
  706. color="#000"
  707. type="primary"
  708. >
  709. <my-loading :isLoading="createLoading"></my-loading>
  710. <span v-if="!createLoading">{{
  711. $t("createVideo.saveVideo")
  712. }}</span></el-button
  713. >
  714. <!-- 生成视频 -->
  715. <el-button
  716. style="margin-left: 0"
  717. @click="saveOrUpdate(3)"
  718. color="#000"
  719. type="primary"
  720. >
  721. <my-loading :isLoading="createLoading"></my-loading>
  722. <span v-if="!createLoading">{{
  723. $t("createVideo.generateVideo")
  724. }}</span>
  725. </el-button>
  726. </div>
  727. </el-tab-pane>
  728. <!-- ----------------------- 4 -->
  729. <el-tab-pane :label="$t('createVideo.aiScript')">
  730. <div class="card4">
  731. <!-- 语言 -->
  732. <div class="text">
  733. <!-- 文本域 -->
  734. <el-input
  735. maxlength="200"
  736. id="textInputId"
  737. v-model="problemContent"
  738. :rows="5"
  739. type="textarea"
  740. :placeholder="$t('createVideo.inputRequirement')"
  741. />
  742. </div>
  743. <el-button
  744. type="primary"
  745. :disabled="sendLoading"
  746. @click="sendRequirement"
  747. color="#000"
  748. >
  749. <my-Loading-Icon v-if="sendLoading"></my-Loading-Icon>
  750. <span v-if="!sendLoading">
  751. {{ $t("createVideo.GeneratedText") }}
  752. </span>
  753. </el-button>
  754. <p>{{ $t("createVideo.SmartText") }}</p>
  755. <!-- 语言 -->
  756. <div class="text" style="cursor: text">
  757. <!-- 文本域 -->
  758. <el-input
  759. maxlength="500"
  760. id="textInputId"
  761. v-model="answerContent"
  762. :rows="15"
  763. type="textarea"
  764. disabled=""
  765. />
  766. <i
  767. >{{ $t("createVideo.entered") }} {{ answerContent.length }} /
  768. 500</i
  769. >
  770. </div>
  771. <!-- <div class="card4Btn" @click="copyAnswerContent">
  772. {{ $t("createVideo.copy") }}
  773. </div> -->
  774. <el-button color="#000" @click="copyAnswerContent">
  775. {{ $t("createVideo.copy") }}
  776. </el-button>
  777. </div>
  778. </el-tab-pane>
  779. <!-- -------------------------脚标 -->
  780. </el-tabs>
  781. </div>
  782. <!-- 试听的mp3 -->
  783. <!-- style="opacity: 0; width: 0" -->
  784. <div>
  785. <audio muted autoplay :src="previewAudioUrl" id="audio"></audio>
  786. </div>
  787. </div>
  788. </template>
  789. <script setup>
  790. //#region 导入
  791. import { getCurrentInstance } from "vue";
  792. import {
  793. personPhotoList,
  794. personPhotoUserListList,
  795. awsImgUpload,
  796. delImgApi,
  797. baiduPhotoApi,
  798. checkFaceApi,
  799. videoUpload,
  800. saveImgApi,
  801. uploadProgressApi,
  802. saveAudioApi,
  803. voiceTotalApi,
  804. chooseTypeApi,
  805. saveOrUpdateApi,
  806. createVideoApi,
  807. previewAudioApi,
  808. findByIdApi,
  809. musicListSystemApi,
  810. musicListPersonApi,
  811. saveMusicApi,
  812. delMusicApi,
  813. } from "@/apis/createVideo";
  814. import { useI18n } from "vue-i18n";
  815. import Recorder from "js-audio-recorder"; //mp3插件
  816. import imageCompression from "browser-image-compression"; //压缩图片插件
  817. import { useRoute, useRouter } from "vue-router";
  818. import { fabric } from "fabric";
  819. //#endregion ---------------------------------
  820. const route = useRoute();
  821. const router = useRouter();
  822. const { locale } = useI18n();
  823. const lanChange = ref(locale);
  824. const AccessToken = localStorage.getItem("AccessToken"); // 判断有没有登录
  825. //#region 头像区的逻辑
  826. // 获取AI头像
  827. const AiFaceList = ref([]); //系统相册列表
  828. const userAiFaceList = ref([]); //个人相册列表
  829. const feceItemLoading = ref(false);
  830. const feceItemLoading2 = ref(false);
  831. const pageSize = ref(30); //加载页数
  832. function getAiFaceList(pageNum, pageSize, sendType) {
  833. feceItemLoading.value = true;
  834. personPhotoList(pageNum, pageSize, sendType)
  835. .then((res) => {
  836. AiFaceList.value = res.data.list;
  837. feceItemLoading.value = false;
  838. })
  839. .catch((err) => {
  840. feceItemLoading.value = false;
  841. console.log(err, "err");
  842. });
  843. }
  844. function getAiFaceUserList(pageNum, pageSize, sendType) {
  845. feceItemLoading2.value = true;
  846. personPhotoUserListList(pageNum, pageSize, sendType)
  847. .then((res) => {
  848. userAiFaceList.value = res.data.list;
  849. feceItemLoading2.value = false;
  850. })
  851. .catch((err) => {
  852. feceItemLoading2.value = false;
  853. console.log(err, "err");
  854. });
  855. }
  856. const pageNumSystem = ref(1);
  857. const overLoadSystem = ref(false); //禁用系统相册加载更多
  858. const pageNumUser = ref(1);
  859. const overLoadUser = ref(false); //禁用个人相册加载更多
  860. //系统相册触底加载更多
  861. function loadMoreAiFaceSystem() {
  862. pageNumSystem.value += 1;
  863. personPhotoList(pageNumSystem.value, pageSize.value, 1)
  864. .then((res) => {
  865. AiFaceList.value.push(...res.data.list);
  866. if (res.data.list.length == 0) {
  867. overLoadSystem.value = true;
  868. pageNumSystem.value = pageNumSystem.value - 1;
  869. }
  870. })
  871. .catch((err) => {
  872. console.log(err, "err");
  873. });
  874. }
  875. //个人相册触底加载更多
  876. function loadMoreAiFaceUser() {
  877. console.log(12333212311);
  878. pageNumUser.value += 1;
  879. personPhotoUserListList(pageNumUser.value, pageSize.value, 3)
  880. .then((res) => {
  881. if (res.data.list.length == 0) {
  882. overLoadUser.value = true;
  883. pageNumUser.value = pageNumUser.value - 1;
  884. }
  885. userAiFaceList.value.push(...res.data.list);
  886. })
  887. .catch((err) => {
  888. console.log(err, "err");
  889. });
  890. }
  891. // 选择显示的face列表
  892. const showFace = ref("0");
  893. function ChangeShowFaceList(index) {
  894. showFace.value = index;
  895. }
  896. const faceItemActive = ref(); //高亮处理和当前选择图片的ID
  897. // 点击aifece
  898. function setImgUrl(url, id) {
  899. faceItemActive.value = id; //高亮
  900. imgUrl.value = url; //回显地址
  901. showUploadBtn.value = 1; //
  902. // submitImgBtn.value = false;
  903. }
  904. // 删除个人相册照片
  905. async function deleteFaceImg(id) {
  906. const msg =
  907. lanChange.value == "zh-cn"
  908. ? `确定要删除这张图片吗? `
  909. : `Are you sure to delete this image ? `;
  910. const notice = lanChange.value == "zh-cn" ? "提示" : "Notice";
  911. const confirm = lanChange.value == "zh-cn" ? "确定" : "Confirm";
  912. const cancel = lanChange.value == "zh-cn" ? "取消" : "Cancel";
  913. ElMessageBox.confirm(msg, notice, {
  914. confirmButtonText: confirm,
  915. cancelButtonText: cancel,
  916. }).then(async () => {
  917. try {
  918. const res = await delImgApi(id);
  919. imgUrl.value = null;
  920. faceItemActive.value = null;
  921. userAiFaceList.value = [];
  922. getAiFaceUserList(1, pageSize.value, 3);
  923. pageNumUser.value = 1;
  924. overLoadUser.value = false;
  925. } catch (error) {
  926. ElMessage.error(
  927. lanChange.value == "zh-cn" ? `删除失败!` : `Deletion failure`
  928. );
  929. }
  930. });
  931. }
  932. //#endregion ------------------------------
  933. //#region 预览图片
  934. const imgUploadTips = ref(null); // 图片检测loading的文字提示
  935. const compressionImgLoad = ref(false);
  936. async function lookImg(e) {
  937. if (e.target.files[0]) {
  938. faceItemActive.value = null;
  939. imgUrl.value = null;
  940. imgData.value = null;
  941. showUploadBtn.value = 3;
  942. // submitImgBtn.value = false;
  943. compressionImgLoad.value = true;
  944. // 压缩配置
  945. const options = {
  946. maxSizeMB: 4, // 最大压缩大小为 4MB
  947. useWebWorker: true, // 使用 Web Worker 进行压缩,提高性能
  948. };
  949. imgUploadTips.value = "compress";
  950. const compressedFile = await imageCompression(e.target.files[0], options);
  951. imgUploadTips.value = "sensitiveness";
  952. try {
  953. const res = await baiduPhotoApi(compressedFile);
  954. if (res.code != 200) {
  955. throw new Error();
  956. }
  957. // 图片人脸信息检测通过时,继续检测敏感信息
  958. imgUploadTips.value = "chackFace";
  959. const res2 = await checkFaceApi(compressedFile);
  960. if (res2.code != 200) {
  961. throw new Error();
  962. }
  963. imgUrl.value = URL.createObjectURL(compressedFile);
  964. imgData.value = compressedFile;
  965. showUploadBtn.value = 4;
  966. // submitImgBtn.value = true;
  967. compressionImgLoad.value = false;
  968. await changeFile();
  969. } catch (error) {
  970. console.log(error, "上传图片的error");
  971. ElMessage.error(error.message);
  972. showFace.value = 1;
  973. showUploadBtn.value = 2;
  974. compressionImgLoad.value = false;
  975. imgUploadTips.value = null;
  976. }
  977. } else {
  978. return;
  979. }
  980. }
  981. // blob转base64
  982. function getImageBase64(blob) {
  983. return new Promise((resolve, reject) => {
  984. const reader = new FileReader();
  985. reader.readAsDataURL(blob);
  986. reader.onload = () => {
  987. const base64 = reader.result;
  988. resolve(base64);
  989. };
  990. reader.onerror = (error) => reject(error);
  991. });
  992. }
  993. //#endregion ------------------
  994. // 标题
  995. const title =
  996. lanChange.value == "zh-cn" ? ref("未定义标题") : ref("Undefined Title");
  997. //#region 上传图片
  998. const imgUrl = ref(""); //图片回显路径
  999. const imgData = ref(""); // 上传图片文件
  1000. const showUploadBtn = ref(1); //上传图片+号的显示隐藏
  1001. // const submitImgBtn = ref(false); //右上角俩按钮显示与隐藏
  1002. // 点击系统仓库tab
  1003. function chooSystemImg(params) {
  1004. showFace.value = 0;
  1005. showUploadBtn.value = 1;
  1006. }
  1007. // 上传图片
  1008. async function changeFile() {
  1009. imgUploadTips.value = "upLoadPictures";
  1010. compressionImgLoad.value = true;
  1011. showUploadBtn.value = 3;
  1012. // imgUrl.value = URL.createObjectURL(imgData.value);
  1013. let formData = new FormData();
  1014. const timestamp = Date.now();
  1015. formData.append("file", imgData.value, timestamp + ".png");
  1016. try {
  1017. // 上传图片
  1018. const res = await awsImgUpload(formData);
  1019. if (res.code != 200) {
  1020. throw new Error();
  1021. }
  1022. // 保存图片
  1023. const res2 = await saveImgApi({ material: res.data.url });
  1024. if (res2.code != 200) {
  1025. throw new Error();
  1026. } else {
  1027. ElMessage.success(
  1028. lanChange.value == "zh-cn" ? `上传成功` : `Upload successfully`
  1029. );
  1030. }
  1031. // 重新获取列表
  1032. compressionImgLoad.value = false;
  1033. showUploadBtn.value = 4;
  1034. userAiFaceList.value = [];
  1035. getAiFaceUserList(1, pageSize.value, 3);
  1036. pageNumUser.value = 1;
  1037. overLoadUser.value = false;
  1038. faceItemActive.value = res2.data.id;
  1039. imgUrl.value = res2.data.material;
  1040. // submitImgBtn.value = false;
  1041. showUploadBtn.value = 1;
  1042. imgUploadTips.value = null;
  1043. } catch (error) {
  1044. ElMessage.error(lanChange.value == "zh-cn" ? `上传失败` : `Upload failure`);
  1045. imgUrl.value = null;
  1046. showFace.value = 1;
  1047. showUploadBtn.value = 2;
  1048. compressionImgLoad.value = false;
  1049. imgUploadTips.value = null;
  1050. }
  1051. }
  1052. // 点击个人仓库tab
  1053. function chooseMyImg() {
  1054. if (!AccessToken) {
  1055. router.push({ path: "/login" });
  1056. return;
  1057. }
  1058. showFace.value = 1;
  1059. showUploadBtn.value = 2;
  1060. }
  1061. // 点击AI头像区的+号
  1062. function clickAddBtn() {
  1063. // 如果imgUploadTips有值,则
  1064. if (imgUploadTips.value) {
  1065. showUploadBtn.value = 3;
  1066. submitImgBtn.value = false;
  1067. compressionImgLoad.value = true;
  1068. return;
  1069. }
  1070. showUploadBtn.value = 2;
  1071. faceItemActive.value = null;
  1072. }
  1073. //#endregion ---------------------------
  1074. //#region AI生成图片
  1075. function chooseAiPic() {
  1076. if (!AccessToken) {
  1077. router.push({ path: "/login" });
  1078. return;
  1079. }
  1080. showFace.value = 2;
  1081. // showUploadBtn.value = 5;
  1082. }
  1083. //#endregion --------------------------
  1084. //#region card1的逻辑
  1085. const textareaContent = ref("");
  1086. function insertIcon() {
  1087. const textarea = document.getElementById("textInputId");
  1088. const icon = "🕗"; // 这里使用了一个星星作为示例图标,你可以替换为你自己的图标
  1089. // 获取光标位置
  1090. const startPos = textarea.selectionStart;
  1091. const endPos = textarea.selectionEnd;
  1092. console.log(startPos);
  1093. console.log(endPos);
  1094. // 在光标位置插入图标
  1095. textareaContent.value =
  1096. textareaContent.value.substring(0, startPos) +
  1097. icon +
  1098. textareaContent.value.substring(endPos, textareaContent.value.length);
  1099. // 设置光标位置为插入图标后的位置
  1100. const newCursorPos = startPos + icon.length;
  1101. textarea.setSelectionRange(newCursorPos, newCursorPos);
  1102. textarea.focus();
  1103. }
  1104. const language = ref(""); //选择的语言
  1105. const sound = ref(""); // 选择的声音
  1106. const soundStyle = ref(""); //选择的风格
  1107. // 语言下拉框内容
  1108. const languageOptions = ref([]);
  1109. async function voiceTotal() {
  1110. try {
  1111. const res = await voiceTotalApi();
  1112. languageOptions.value = res.data;
  1113. } catch (error) {
  1114. ElMessage.error(
  1115. lanChange.value == "zh-cn"
  1116. ? `获取语言列表失败,请稍后重试`
  1117. : `Failed to get the language list, please try again later`
  1118. );
  1119. }
  1120. }
  1121. // 声音下拉框内容
  1122. const soundOptions = ref([]);
  1123. async function chooseType() {
  1124. const res = await chooseTypeApi(language.value);
  1125. soundOptions.value = res.data;
  1126. sound.value = null;
  1127. }
  1128. // 风格下拉框内容
  1129. const soundStyleOptions = ref(null);
  1130. async function chooseStyle() {
  1131. const item = soundOptions.value.find((element) => element.id == sound.value);
  1132. soundStyle.value = null;
  1133. soundStyleOptions.value = item.style;
  1134. }
  1135. // 根据ageType返回年龄
  1136. function returnAge(ageType) {
  1137. if (!ageType) {
  1138. return "";
  1139. } else if (ageType == 1) {
  1140. if (lanChange.value == "zh-cn") {
  1141. return " 儿童";
  1142. } else {
  1143. return "Child";
  1144. }
  1145. } else if (ageType == 2) {
  1146. if (lanChange.value == "zh-cn") {
  1147. return " 少年";
  1148. } else {
  1149. return "Teen";
  1150. }
  1151. } else if (ageType == 3) {
  1152. if (lanChange.value == "zh-cn") {
  1153. return " 年轻";
  1154. } else {
  1155. return "YoungAdult";
  1156. }
  1157. } else if (ageType == 4) {
  1158. if (lanChange.value == "zh-cn") {
  1159. return " 中年";
  1160. } else {
  1161. return "OlderAdult";
  1162. }
  1163. } else if (ageType == 5) {
  1164. if (lanChange.value == "zh-cn") {
  1165. return " 老年";
  1166. } else {
  1167. return "Senior";
  1168. }
  1169. }
  1170. }
  1171. // 试听
  1172. const previewAudioUrl = ref(null);
  1173. const previewAudioLoading = ref(false);
  1174. async function getPreviewAudio() {
  1175. previewAudioLoading.value = true;
  1176. if (!textareaContent.value) {
  1177. ElMessage.error(
  1178. lanChange.value == "zh-cn"
  1179. ? `您还没有输入文案`
  1180. : `You have not entered the copy yet`
  1181. );
  1182. previewAudioLoading.value = false;
  1183. return;
  1184. }
  1185. if (!sound.value) {
  1186. ElMessage.error(
  1187. lanChange.value == "zh-cn"
  1188. ? `您还没有选择声音`
  1189. : `You have not selected the sound`
  1190. );
  1191. previewAudioLoading.value = false;
  1192. return;
  1193. }
  1194. try {
  1195. const res = await previewAudioApi(
  1196. textareaContent.value,
  1197. sound.value,
  1198. soundStyle.value
  1199. );
  1200. if (!res.data.data) {
  1201. ElMessage.error(
  1202. lanChange.value == "zh-cn"
  1203. ? `暂时无法试听,请稍后重试`
  1204. : `Unable to listen, please try again later`
  1205. );
  1206. previewAudioLoading.value = false;
  1207. return;
  1208. }
  1209. previewAudioUrl.value = res.data.data;
  1210. let audioA = document.getElementById("audio");
  1211. audioA.addEventListener("loadedmetadata", async () => {
  1212. audioA.muted = false; // 将 muted 属性设置为 false
  1213. await audioA.play();
  1214. previewAudioLoading.value = false;
  1215. console.log(audioA.muted, "播放了");
  1216. });
  1217. } catch (error) {
  1218. ElMessage.error(
  1219. lanChange.value == "zh-cn"
  1220. ? `暂时无法试听,请稍后重试`
  1221. : `Unable to listen, please try again later`
  1222. );
  1223. previewAudioLoading.value = false;
  1224. return;
  1225. }
  1226. }
  1227. // 新的试听
  1228. function ListenVoices(url) {
  1229. previewAudioUrl.value = url;
  1230. // previewAudioUrl.value =
  1231. // "/suimang/static/tts/2023-07-04/16884538755531874.wav";
  1232. let audioA = document.getElementById("audio");
  1233. audioA.addEventListener("loadedmetadata", async () => {
  1234. audioA.muted = false; // 将 muted 属性设置为 false
  1235. await audioA.play();
  1236. previewAudioLoading.value = false;
  1237. });
  1238. }
  1239. //#endregion
  1240. //#region card2的逻辑
  1241. const showAudioWhich = ref(0); // 哪个音乐控件显示
  1242. //#endregion ----------------------------
  1243. //#region 录制MP3上传语音
  1244. const parameter = {
  1245. sampleBits: 16, // 采样位数,支持 8 或 16,默认是16
  1246. sampleRate: 8000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000
  1247. numChannels: 1, // 声道,支持 1 或 2, 默认是1
  1248. };
  1249. const recorder = new Recorder(parameter);
  1250. const isRecording = ref(true);
  1251. const audioUrl = ref(null); // audio控件的url
  1252. const audioData = ref("");
  1253. const videoId = ref(null); //上传mp3的videoId,创建视频会用到
  1254. const duration = ref(""); //实时显示录音时长
  1255. const getduration = ref(null);
  1256. const startRecording = async () => {
  1257. if (showAudioWhich.value != 2) {
  1258. audioUrl.value = null;
  1259. audioData.value = null;
  1260. showAudioWhich.value = 2;
  1261. }
  1262. try {
  1263. await recorder.start();
  1264. duration.value = 0;
  1265. console.log("Recording started");
  1266. isRecording.value = false;
  1267. getduration.value = setInterval(() => {
  1268. if (isRecording.value == true) {
  1269. clearInterval(getduration.value);
  1270. return;
  1271. }
  1272. if (duration.value >= 60) {
  1273. ElMessage.error(
  1274. lanChange.value == "zh-cn"
  1275. ? `免费用户录音时长限制为60秒!`
  1276. : `Free user recording duration is limited to 60 seconds!`
  1277. );
  1278. clearInterval(getduration.value);
  1279. stopRecording();
  1280. return;
  1281. }
  1282. duration.value = Math.floor(recorder.duration);
  1283. // console.log(duration.value);
  1284. }, 500);
  1285. onUnmounted(() => {
  1286. clearInterval(duration.value);
  1287. });
  1288. } catch (error) {
  1289. console.error(error);
  1290. }
  1291. };
  1292. const stopRecording = async () => {
  1293. try {
  1294. const recording = await recorder.stop();
  1295. audioUrl.value = URL.createObjectURL(recorder.getWAVBlob());
  1296. audioData.value = recorder.getWAVBlob();
  1297. isRecording.value = true;
  1298. // console.log(document.getElementById("uploadAudio").duration);
  1299. } catch (error) {
  1300. console.error(error);
  1301. }
  1302. };
  1303. const playRecording = () => {
  1304. if (audioUrl.value) {
  1305. const audio = new Audio(audioUrl.value);
  1306. audio.play();
  1307. }
  1308. };
  1309. // 上传音频
  1310. async function uploadAudio(isSaveVideo) {
  1311. createLoading.value = true;
  1312. let formData = new FormData();
  1313. const timestamp = Date.now();
  1314. formData.append("file", audioData.value, timestamp + ".mp3");
  1315. try {
  1316. const res = await videoUpload(formData);
  1317. videoId.value = res.data.videoId;
  1318. if (res) {
  1319. // ElMessage.success("上传成功!");
  1320. } else {
  1321. ElMessage.error(
  1322. lanChange.value == "zh-cn" ? `上传音频失败!` : `Failed to upload audio!`
  1323. );
  1324. return;
  1325. }
  1326. // 查询进度
  1327. intervalId = setInterval(() => {
  1328. uploadProgress(res.data.videoId, isSaveVideo);
  1329. }, 1000);
  1330. // ElMessage.success("上传成功!");
  1331. } catch (error) {
  1332. ElMessage.error(error);
  1333. }
  1334. }
  1335. //#endregion ------------------------------------
  1336. //#region 上传音频功能
  1337. const fileInput = ref(null);
  1338. let intervalId = null; // 用于存储 setInterval 的返回值
  1339. const uploadAudioTime = ref(null); // 音频时长
  1340. // 点击上传input
  1341. function handleFileClick(e) {
  1342. stopRecording();
  1343. duration.value = 0;
  1344. showAudioWhich.value = 0;
  1345. audioUrl.value = null;
  1346. audioData.value = null;
  1347. e.target.value = null;
  1348. }
  1349. // 上传input触发change
  1350. const handleFileChange = async () => {
  1351. await stopRecording();
  1352. showAudioWhich.value = 1;
  1353. const file = fileInput.value.files[0];
  1354. if (file) {
  1355. const videoUrl = URL.createObjectURL(file);
  1356. const audio2 = new Audio();
  1357. audio2.src = videoUrl;
  1358. // 进行大小时长限制
  1359. await countAudioTime(audio2);
  1360. // 限制60秒
  1361. if (uploadAudioTime.value > 60) {
  1362. ElMessage.error(
  1363. lanChange.value == "zh-cn"
  1364. ? `上传音频大小或者长度超出限制,请重新上传`
  1365. : `The size or length of the uploaded audio exceeds the limit. Please upload it again`
  1366. );
  1367. audioUrl.value = null;
  1368. audioData.value = null;
  1369. return;
  1370. }
  1371. // 限制5mb
  1372. if (file.size / 1048576 > 5) {
  1373. ElMessage.error(
  1374. lanChange.value == "zh-cn"
  1375. ? `上传音频大小或者长度超出限制,请重新上传`
  1376. : `The size or length of the uploaded audio exceeds the limit. Please upload it again`
  1377. );
  1378. audioUrl.value = null;
  1379. audioData.value = null;
  1380. return;
  1381. }
  1382. // 成功了
  1383. audioUrl.value = videoUrl;
  1384. audioData.value = file;
  1385. }
  1386. };
  1387. // 计算音频的时长
  1388. const countAudioTime = async (audio) => {
  1389. while (isNaN(audio.duration) || audio.duration === Infinity) {
  1390. // 延迟一会 不然网页都卡死
  1391. await new Promise((resolve) => setTimeout(resolve, 200));
  1392. // 设置随机播放时间,模拟调进度条
  1393. audio.currentTime = 10000000 * Math.random();
  1394. }
  1395. // console.log("音频的总时长:", Math.floor(audio.duration));
  1396. uploadAudioTime.value = Math.floor(audio.duration);
  1397. };
  1398. // 获取视频上传进度
  1399. const afterSaveAudioId = ref(null);
  1400. async function uploadProgress(videoId, isSaveVideo) {
  1401. const res = await uploadProgressApi(videoId);
  1402. if (res.data == "complete") {
  1403. clearInterval(intervalId);
  1404. try {
  1405. const res2 = await saveAudioApi({
  1406. videoId: videoId,
  1407. sendType: showAudioWhich.value,
  1408. });
  1409. afterSaveAudioId.value = res2.data.id; // 得到id
  1410. } catch (error) {
  1411. ElMessage.error(error);
  1412. }
  1413. // 定义参数
  1414. const saveOrUpdateData = {
  1415. id: id.value, //从草稿或者重新编辑回到创建视频时有id
  1416. title: title.value,
  1417. personPhotoId: faceItemActive.value, //任务照片id,
  1418. voiceFrom: tabFrom.value, //:声音来源 1:来自选择模板,2:来自用户上传
  1419. paperwork: textareaContent.value, //文案
  1420. speakTypeStr: soundStyle.value, //声音风格
  1421. voiceMouldId: sound.value, //选择声音id voiceFrom=1 时必传
  1422. voiceMaterialId: afterSaveAudioId.value, //上传音频且保存音频后得到的id voiceFrom=2 时必传
  1423. // subtitleEnabled:SubtitlesSwitch.value?1:0 , //字幕开关
  1424. // subtitleParams:{
  1425. // Fontname:SubtitlesFontFamily.value,
  1426. // Fontsize:SubtitlesSize.value,
  1427. // Boxwidth:SubtitlesWidth.value,
  1428. // // Boxheight:Number(SubtitlesSize.value+10),
  1429. // Boxheight:SubtitlesBox.value.style.height,
  1430. // SecondaryColour:SubtitlesColorBGRA.value,
  1431. // Marginl:SubtitlesLeft.value,
  1432. // }
  1433. };
  1434. try {
  1435. const res3 = await saveOrUpdateApi(saveOrUpdateData); // 保存数据
  1436. // 这个if判断是不是仅仅保存视频
  1437. if (isSaveVideo == "saveVideo") {
  1438. ElMessage.success(
  1439. lanChange.value == "zh-cn" ? `已保存为草稿` : `Saved as manuscript`
  1440. );
  1441. createLoading.value = false;
  1442. router.push("/myCreating");
  1443. return;
  1444. }
  1445. ElMessage.success(
  1446. lanChange.value == "zh-cn"
  1447. ? `预计生成的视频时长约为${formatTime(uploadAudioTime.value)}`
  1448. : `The video generated is expected to be approximately${formatTime(
  1449. uploadAudioTime.value
  1450. )} in length`
  1451. );
  1452. const res4 = await createVideoApi({ id: res3.data.id }); // 生成视频
  1453. if (res4.message == "success") {
  1454. ElMessage.success(
  1455. lanChange.value == "zh-cn"
  1456. ? `视频正在生成中,请耐心等待`
  1457. : `The video is being generated, please wait patiently`
  1458. );
  1459. }
  1460. createLoading.value = false;
  1461. router.push("/myCreating");
  1462. } catch (error) {
  1463. ElMessage.error(error);
  1464. createLoading.value = false;
  1465. }
  1466. }
  1467. }
  1468. //#endregion --------------------------------------
  1469. //#region 生成视频
  1470. // tab栏切换
  1471. const tabFrom = ref(1);
  1472. const createLoading = ref(false);
  1473. // 上传音频后,得到videoId
  1474. // 保存数据
  1475. async function saveOrUpdate(index, isSaveVideo) {
  1476. if (createLoading.value == true) {
  1477. ElMessage.error(lanChange.value == "zh-cn" ? `正在生成` : `Be generating`);
  1478. return;
  1479. }
  1480. tabFrom.value = index; // 判断是哪一个tab栏内的生成视频按钮
  1481. const saveOrUpdateData = {
  1482. id: id.value, //从草稿或者重新编辑回到创建视频时有id
  1483. title: title.value,
  1484. personPhotoId: faceItemActive.value, //任务照片id,
  1485. voiceFrom: tabFrom.value, //:声音来源 1:来自选择模板,2:来自用户上传
  1486. paperwork: textareaContent.value, //文案
  1487. speakTypeStr: soundStyle.value, //声音风格
  1488. voiceMouldId: sound.value, //选择声音id voiceFrom=1 时必传
  1489. voiceMaterialId: afterSaveAudioId.value, //上传音频且保存音频后得到的id voiceFrom=2 时必传
  1490. musicId: musicItemAction.value, // 选中音乐的id,voiceFrom=3 时必传
  1491. };
  1492. if (index == 1) {
  1493. createLoading.value = true;
  1494. if (!saveOrUpdateData.personPhotoId) {
  1495. ElMessage.error(
  1496. lanChange.value == "zh-cn" ? `请选择图片` : `Please select a picture`
  1497. );
  1498. createLoading.value = false;
  1499. return;
  1500. }
  1501. if (!saveOrUpdateData.paperwork) {
  1502. ElMessage.error(
  1503. lanChange.value == "zh-cn" ? `请输入文案` : `Please enter copy`
  1504. );
  1505. createLoading.value = false;
  1506. return;
  1507. }
  1508. if (!saveOrUpdateData.voiceMouldId) {
  1509. ElMessage.error(
  1510. lanChange.value == "zh-cn" ? `请选择声音` : `Please select sound`
  1511. );
  1512. createLoading.value = false;
  1513. return;
  1514. }
  1515. try {
  1516. // 获取预估时长
  1517. const res4 = await previewAudioApi(
  1518. textareaContent.value,
  1519. sound.value,
  1520. soundStyle.value
  1521. );
  1522. if (res4.data.code != 200) {
  1523. ElMessage.error(
  1524. lanChange.value == "zh-cn"
  1525. ? `视频时长预估失败,无法生成视频,您可以换个声音试试`
  1526. : `Video duration estimation failed, can not generate video, you can try to change the sound`
  1527. );
  1528. createLoading.value = false;
  1529. return;
  1530. }
  1531. if (res4.data.data > 60) {
  1532. ElMessage.error(
  1533. lanChange.value == "zh-cn"
  1534. ? `预计生成的视频时长约为${formatTime(
  1535. res4.data.data
  1536. )},免费用户生成视频时长为60秒,无法生成视频`
  1537. : `The video generated is expected to be about ${formatTime(
  1538. res4.data.data
  1539. )} long, and the free user-generated video is 60 seconds long and cannot be generated`
  1540. );
  1541. createLoading.value = false;
  1542. return;
  1543. }
  1544. ElMessage.success(
  1545. lanChange.value == "zh-cn"
  1546. ? `预计生成的视频时长约为${formatTime(res4.data.data)}`
  1547. : `The video generated is expected to be approximately${formatTime(
  1548. res4.data.data
  1549. )} in length`
  1550. );
  1551. const res2 = await saveOrUpdateApi(saveOrUpdateData); // 保存数据
  1552. if (res2.code == 1002) {
  1553. }
  1554. // 这个if判断是不是仅仅保存视频
  1555. if (isSaveVideo == "saveVideo") {
  1556. ElMessage.success(
  1557. lanChange.value == "zh-cn" ? `已保存为草稿` : `Saved as manuscript`
  1558. );
  1559. createLoading.value = false;
  1560. router.push("/myCreating");
  1561. return;
  1562. }
  1563. // 3秒后调用生成视频
  1564. const changeHasNewCreate = setTimeout(async () => {
  1565. const res3 = await createVideoApi({ id: res2.data.id }); // 生成视频
  1566. if (res3.message == "success") {
  1567. ElMessage.success(
  1568. lanChange.value == "zh-cn"
  1569. ? `视频正在生成中,请耐心等待`
  1570. : `The video is being generated, please wait patiently`
  1571. );
  1572. }
  1573. createLoading.value = false;
  1574. router.push("/myCreating");
  1575. }, 3000);
  1576. } catch (error) {
  1577. // ElMessage.error($t(error.message));
  1578. ElMessage.error(error);
  1579. createLoading.value = false;
  1580. }
  1581. }
  1582. if (index == 2) {
  1583. if (!saveOrUpdateData.personPhotoId) {
  1584. ElMessage.error(
  1585. lanChange.value == "zh-cn" ? `请选择图片` : `Please select a picture`
  1586. );
  1587. createLoading.value = false;
  1588. return;
  1589. }
  1590. if (!audioData.value && !afterSaveAudioId.value) {
  1591. console.log(audioData.value);
  1592. console.log(afterSaveAudioId.value);
  1593. ElMessage.error(
  1594. lanChange.value == "zh-cn"
  1595. ? `请选择音频文件`
  1596. : `Please select an audio file`
  1597. );
  1598. createLoading.value = false;
  1599. return;
  1600. }
  1601. try {
  1602. const res1 = await uploadAudio(isSaveVideo); // 上传音频
  1603. } catch (error) {
  1604. // ElMessage.error(error);
  1605. ElMessage.error(
  1606. lanChange.value == "zh-cn"
  1607. ? `生成失败,请重新选择音频`
  1608. : `Generation failed, please re-select the audio`
  1609. );
  1610. createLoading.value = false;
  1611. }
  1612. }
  1613. if (index == 3) {
  1614. createLoading.value = true;
  1615. try {
  1616. if (!saveOrUpdateData.personPhotoId) {
  1617. ElMessage.error(
  1618. lanChange.value == "zh-cn" ? `请选择图片` : `Please select a picture`
  1619. );
  1620. createLoading.value = false;
  1621. return;
  1622. }
  1623. if (!saveOrUpdateData.musicId) {
  1624. ElMessage.error(
  1625. lanChange.value == "zh-cn"
  1626. ? `请选择音乐文件`
  1627. : `Please select an music file`
  1628. );
  1629. createLoading.value = false;
  1630. return;
  1631. }
  1632. const res1 = await saveOrUpdateApi(saveOrUpdateData); // 保存数据
  1633. // 这个if判断是不是仅仅保存视频
  1634. if (isSaveVideo == "saveVideo") {
  1635. ElMessage.success(
  1636. lanChange.value == "zh-cn" ? `已保存为草稿` : `Saved as manuscript`
  1637. );
  1638. createLoading.value = false;
  1639. router.push("/myCreating");
  1640. return;
  1641. }
  1642. console.log("ceshi");
  1643. ElMessage.success(
  1644. lanChange.value == "zh-cn"
  1645. ? `预计生成的视频时长约为${formatTime(estimatedDuration.value)}`
  1646. : `The video generated is expected to be approximately${formatTime(
  1647. estimatedDuration.value
  1648. )} in length`
  1649. );
  1650. const res2 = await createVideoApi({ id: res1.data.id }); // 生成视频
  1651. if (res2.message == "success") {
  1652. ElMessage.success(
  1653. lanChange.value == "zh-cn"
  1654. ? `视频正在生成中,请耐心等待`
  1655. : `The video is being generated, please wait patiently`
  1656. );
  1657. }
  1658. createLoading.value = false;
  1659. router.push("/myCreating");
  1660. } catch (error) {
  1661. if (error) {
  1662. ElMessage.error(error);
  1663. } else {
  1664. ElMessage.error(
  1665. lanChange.value == "zh-cn"
  1666. ? `生成失败,请稍后重试`
  1667. : `Failed to generate, please try again later`
  1668. );
  1669. }
  1670. createLoading.value = false;
  1671. }
  1672. }
  1673. }
  1674. // 预估时长时把秒转为分
  1675. function formatTime(seconds) {
  1676. const minutes = Math.floor(Math.floor(seconds) / 60);
  1677. const remainingSeconds = Math.floor(seconds) % 60;
  1678. const formattedMinutes = String(minutes).padStart(2, "0");
  1679. const formattedSeconds = String(remainingSeconds).padStart(2, "0");
  1680. return `${formattedMinutes}:${formattedSeconds}`;
  1681. }
  1682. //#endregion ----------------------------
  1683. //#region 根据作品id查询作品信息并回显
  1684. const id = ref(null); //作品的id
  1685. async function findById(findId) {
  1686. const res = await findByIdApi(findId);
  1687. id.value = res.data.id;
  1688. title.value = res.data.title; //标题回显
  1689. showAudioWhich.value = res.data.voiceMaterial
  1690. ? res.data.voiceMaterial.sendType
  1691. : null;
  1692. faceItemActive.value = res.data.personPhotoId; //aiface高亮和图片id
  1693. tabFrom.value = res.data.voiceFrom; //输入文案还是上传mp3模式
  1694. textareaContent.value = res.data.paperwork; //文案内容
  1695. afterSaveAudioId.value = res.data.voiceMaterialId; //保存音频后得到的id
  1696. imgUrl.value = res.data.personPhotoUrl; //图片回显
  1697. audioUrl.value = res.data.voiceMaterialUrl; // mp3回显
  1698. musicItemAction.value = res.data.musicId; //音乐id回显
  1699. language.value = res.data.voiceInfo ? res.data.voiceInfo.languageId : null;
  1700. if (language.value) {
  1701. await chooseType(language.value);
  1702. }
  1703. sound.value = res.data.voiceMouldId; //声音id
  1704. chooseStyle();
  1705. soundStyle.value = res.data.speakTypeStr; //选择风格
  1706. }
  1707. //#endregion ----------------------------
  1708. //#region card3
  1709. const musicListSystem = ref([]); //展示系统音乐列表
  1710. const musicListPerson = ref([]); //展示个人音乐列表
  1711. const audioMusicUrl = ref(null); //选中音乐Url回显
  1712. const musicItemAction = ref(null); //选中高亮
  1713. const musicListPageNumSystem = ref(1); //系统音乐页数
  1714. const musicListTotalSystem = ref(null); //系统音乐总条数
  1715. const musicListPageNumPerson = ref(1); //个人音乐页数
  1716. const musicListTotalPerson = ref(null); //个人音乐总条数
  1717. const musicListPageSize = ref(8); //
  1718. const musicTabActive = ref(1); //热门歌曲和个人仓库高亮
  1719. const musicData = ref(null); //上传歌曲的数据
  1720. const fileMusicInput = ref(null); // 上传组件的dom
  1721. const estimatedDuration = ref(""); // card3的生成视频预估时长
  1722. // tab切换
  1723. function musicTabActiveChange(index) {
  1724. if (index == 2) {
  1725. if (!AccessToken) {
  1726. router.push({ path: "/login" });
  1727. return;
  1728. }
  1729. }
  1730. musicTabActive.value = index;
  1731. }
  1732. // 获取系统音乐列表
  1733. async function getmusicListSystem(pageNum, pageSize) {
  1734. try {
  1735. const res = await musicListSystemApi(pageNum, pageSize);
  1736. musicListSystem.value = res.data.list;
  1737. musicListTotalSystem.value = Number(res.data.total);
  1738. } catch (error) {
  1739. console.log(error);
  1740. }
  1741. }
  1742. // 获取个人音乐列表
  1743. async function getmusicListPerson(pageNum, pageSize) {
  1744. try {
  1745. const res = await musicListPersonApi(pageNum, pageSize);
  1746. musicListPerson.value = res.data.list;
  1747. musicListTotalPerson.value = Number(res.data.total);
  1748. } catch (error) {
  1749. console.log(error);
  1750. }
  1751. }
  1752. // 系统页数发生改变
  1753. const musicListPageNumSystemChange = (val) => {
  1754. musicListPageNumSystem.value = val;
  1755. getmusicListSystem(musicListPageNumSystem.value, musicListPageSize.value);
  1756. };
  1757. // 个人页数发生改变
  1758. const musicListPageNumPersonChange = (val) => {
  1759. musicListPageNumPerson.value = val;
  1760. getmusicListPerson(musicListPageNumPerson.value, musicListPageSize.value);
  1761. };
  1762. // 点击选择音乐
  1763. function chooseMusicUrl(item) {
  1764. audioMusicUrl.value = item.url;
  1765. musicItemAction.value = item.id;
  1766. estimatedDuration.value = item.time;
  1767. }
  1768. // 转化秒为分秒
  1769. function handleTime(time) {
  1770. let m = Math.floor(time / 60).toFixed(0);
  1771. let s = (time % 60).toFixed(0);
  1772. if (s < 10) {
  1773. s = "0" + s;
  1774. }
  1775. return m + ":" + s;
  1776. }
  1777. // 点击上传音乐
  1778. // 点击上传input
  1779. function handleFileMusicClick(e) {
  1780. musicData.value = null;
  1781. e.target.value = null;
  1782. }
  1783. // 选择文件上传音乐
  1784. async function uploadMusic() {
  1785. const file = fileMusicInput.value.files[0];
  1786. if (file) {
  1787. musicData.value = file;
  1788. let formData = new FormData();
  1789. formData.append("file", musicData.value, file.name + ".mp3");
  1790. const videoUrl = URL.createObjectURL(file);
  1791. const audio2 = new Audio();
  1792. audio2.src = videoUrl;
  1793. // 进行大小时长限制
  1794. await countAudioTime(audio2);
  1795. // 限制60秒
  1796. if (uploadAudioTime.value > 60) {
  1797. ElMessage.error(
  1798. lanChange.value == "zh-cn"
  1799. ? `上传音频大小或者长度超出限制,请重新上传`
  1800. : `The size or length of the uploaded audio exceeds the limit. Please upload it again`
  1801. );
  1802. return;
  1803. }
  1804. // 限制3mb
  1805. if (file.size / 1048576 > 5) {
  1806. ElMessage.error(
  1807. lanChange.value == "zh-cn"
  1808. ? `上传音频大小或者长度超出限制,请重新上传`
  1809. : `The size or length of the uploaded audio exceeds the limit. Please upload it again`
  1810. );
  1811. return;
  1812. }
  1813. try {
  1814. const res = await videoUpload(formData);
  1815. if (res) {
  1816. // ElMessage.success("上传成功!");
  1817. } else {
  1818. ElMessage.error(
  1819. lanChange.value == "zh-cn"
  1820. ? `上传音频失败!`
  1821. : `Failed to upload audio!`
  1822. );
  1823. return;
  1824. }
  1825. // 查询进度
  1826. intervalId = setInterval(async () => {
  1827. const res2 = await uploadProgressApi(res.data.videoId);
  1828. if (res2.data == "complete") {
  1829. clearInterval(intervalId);
  1830. try {
  1831. const res3 = await saveMusicApi({ voiceId: res.data.videoId });
  1832. afterSaveAudioId.value = res2.data.id; // 得到id
  1833. musicListPageNumPerson.value = 1;
  1834. const res4 = await musicListPersonApi(
  1835. musicListPageNumPerson.value,
  1836. musicListPageSize.value
  1837. );
  1838. musicListPerson.value = res4.data.list;
  1839. musicListTotalPerson.value = Number(res4.data.total);
  1840. ElMessage.success(
  1841. lanChange.value == "zh-cn" ? `上传成功!` : `Upload successfully`
  1842. );
  1843. } catch (error) {
  1844. ElMessage.error(error);
  1845. }
  1846. }
  1847. }, 1000);
  1848. } catch (error) {
  1849. ElMessage.error(
  1850. lanChange.value == "zh-cn" ? `上传失败!` : `Upload failure`
  1851. );
  1852. }
  1853. }
  1854. }
  1855. // 删除音乐
  1856. async function deleteMusic(id) {
  1857. const msg =
  1858. lanChange.value == "zh-cn"
  1859. ? `确定要删除这首音乐吗? `
  1860. : `Are you sure to delete this music ? `;
  1861. const notice = lanChange.value == "zh-cn" ? "提示" : "Notice";
  1862. const confirm = lanChange.value == "zh-cn" ? "确定" : "Confirm";
  1863. const cancel = lanChange.value == "zh-cn" ? "取消" : "Cancel";
  1864. ElMessageBox.confirm(msg, notice, {
  1865. confirmButtonText: confirm,
  1866. cancelButtonText: cancel,
  1867. }).then(async () => {
  1868. try {
  1869. const res = await delMusicApi(id);
  1870. getmusicListPerson(musicListPageNumPerson.value, musicListPageSize.value);
  1871. ElMessage.success(
  1872. lanChange.value == "zh-cn" ? `删除成功!` : `Successfully deleted `
  1873. );
  1874. } catch (error) {
  1875. ElMessage.error(
  1876. lanChange.value == "zh-cn" ? `删除失败!` : `Deletion failure `
  1877. );
  1878. }
  1879. });
  1880. }
  1881. //#endregion ----------------------------
  1882. //#region card4的逻辑
  1883. const problemContent = ref(null);
  1884. const answerContent = ref("");
  1885. let mySocket = null;
  1886. const sendLoading = ref(false);
  1887. // 没有登录时跳转登录页
  1888. function clickTab4(e) {
  1889. if (e.props.label == "AI生成文本" || e.props.label == "AI Script") {
  1890. if (!AccessToken) {
  1891. router.push({ path: "/login" });
  1892. return;
  1893. }
  1894. }
  1895. }
  1896. // 创建websocket
  1897. function createWS() {
  1898. mySocket = new WebSocket("wss://gptsocket.malls.iformall.com:8010");
  1899. mySocket.onopen = () => {
  1900. console.log("WebSocket 连接成功1");
  1901. sendRequirement();
  1902. };
  1903. mySocket.onmessage = (event) => {
  1904. // console.log("接收到 WebSocket 消息2:", event.data);
  1905. if (!event.data) {
  1906. return;
  1907. }
  1908. if (event.data == "___talk_end___") {
  1909. sendLoading.value = false;
  1910. mySocket.close();
  1911. return;
  1912. }
  1913. answerContent.value = answerContent.value + event.data;
  1914. };
  1915. mySocket.onclose = () => {
  1916. console.log(
  1917. "WebSocket 连接关闭3,关闭代码:",
  1918. event.code,
  1919. "关闭原因:",
  1920. event.reason
  1921. );
  1922. console.log(mySocket.readyState, "链接状态3");
  1923. };
  1924. mySocket.onerror = (error) => {
  1925. ElMessage.error(
  1926. lanChange.value == "zh-cn"
  1927. ? `GPT服务器连接失败,请稍后重试!`
  1928. : `GPT Server connection failed, please try again later`
  1929. );
  1930. sendLoading.value = false;
  1931. mySocket.close();
  1932. console.error("WebSocket 错误4:", error);
  1933. };
  1934. }
  1935. // 点击发送需求
  1936. async function sendRequirement() {
  1937. // loading
  1938. sendLoading.value = true;
  1939. // 无需求时
  1940. if (!problemContent.value) {
  1941. sendLoading.value = false;
  1942. mySocket.close();
  1943. return;
  1944. }
  1945. const sendMessage = [
  1946. {
  1947. role: "user",
  1948. content: problemContent.value,
  1949. },
  1950. ];
  1951. // 如果处于链接状态
  1952. if (mySocket.readyState != WebSocket.OPEN) {
  1953. createWS();
  1954. } else {
  1955. answerContent.value = "";
  1956. mySocket.send(JSON.stringify(sendMessage));
  1957. }
  1958. }
  1959. // 复制到文案
  1960. function copyAnswerContent() {
  1961. textareaContent.value = answerContent.value;
  1962. document.getElementById("tab-0").click();
  1963. }
  1964. //#endregion ----------------------------
  1965. //#region 字幕部分
  1966. const canvasImgBox = ref(null); // 图片外盒子dom
  1967. const SubtitlesBox = ref(null); // 字幕盒子dom
  1968. const SubtitlesSwitch = ref(false); // 字幕开关
  1969. const SubtitlesWidth = ref(250); // 字幕宽度
  1970. const SubtitlesFontFamily = ref(""); //字幕字体系列
  1971. const SubtitlesTop = ref(400); // 字幕Y轴
  1972. const SubtitlesLeft = ref(100); // 字幕X轴
  1973. const SubtitlesRow = ref(false); // 字幕单双行
  1974. const SubtitlesSize = ref(18); // 字幕字号
  1975. const isRotating = ref(false); //控制是否旋转
  1976. const rotationStartAngle = ref(0); //旋转中间件
  1977. const SubtitlesAngle = ref(20); //旋转度数
  1978. const lastMouseAngle = ref(0);
  1979. const initialRotation = ref(0);
  1980. const SubtitlesColor = ref('rgba(255, 255, 255, 1)'); // 字幕字色
  1981. const SubtitlesColorBGRA = ref(rgbaToBgra(SubtitlesColor.value)); // 字幕字色
  1982. const SubtitlesBgc = ref('rgba(0, 0, 0, 1)'); // 字幕背景色
  1983. const SubtitlesBgcBGRA = ref(rgbaToBgra(SubtitlesBgc.value)); // 字幕背景色
  1984. // 调色板
  1985. const colorList = [
  1986. 'rgba(255, 0, 0, 1)', // 红色
  1987. 'rgba(0, 255, 0, 1)', // 绿色
  1988. 'rgba(0, 0, 255, 1)', // 蓝色
  1989. 'rgba(255, 255, 0, 1)', // 黄色
  1990. 'rgba(0, 0, 0, 1)', // 黑色
  1991. 'rgba(0, 255, 255, 1)', // 青色
  1992. 'rgba(128, 0, 0, 1)', // 深红色
  1993. 'rgba(0, 128, 0, 1)', // 深绿色
  1994. 'rgba(0, 0, 128, 1)', // 深蓝色
  1995. 'rgba(255, 255, 255, 1)', // 白色
  1996. 'rgba(128, 0, 128, 1)', // 深品红色
  1997. 'rgba(0, 128, 128, 1)', // 深青色
  1998. 'rgba(255, 128, 0, 1)', // 橙色
  1999. 'rgba(255, 0, 128, 1)', // 粉红色
  2000. 'rgba(0, 255, 128, 1)', // 中绿色
  2001. 'rgba(128, 255, 0, 1)' // 中黄色
  2002. ];
  2003. const fontFamilyList = [
  2004. {label:"黑体",value:"SimHei"},
  2005. {label:"新细明体",value:"PMingLiU"}, {label:"楷体",value:"KaiTi"}, { label: 'Montserrat', value: 'Montserrat' },
  2006. { label: 'Open Sans', value: 'Open Sans' },
  2007. { label: 'Roboto', value: 'Roboto' },
  2008. { label: 'Lato', value: 'Lato' }
  2009. ];
  2010. const SubtitlesColorShow = ref(false); // 字色调色板开关
  2011. const SubtitlesBgcShow = ref(false); // 背景调色板开关
  2012. const handleSubtitlesColorShow = (e) => {
  2013. e.stopPropagation(); // 阻止事件冒泡
  2014. SubtitlesColorShow.value = true;
  2015. SubtitlesBgcShow.value = false;
  2016. };
  2017. const handleSubtitlesBgcShow = (e) => {
  2018. e.stopPropagation(); // 阻止事件冒泡
  2019. SubtitlesBgcShow.value = true;
  2020. SubtitlesColorShow.value = false;
  2021. };
  2022. const handleClickOutside = () => {
  2023. if(SubtitlesColorShow.value==true) {
  2024. }
  2025. SubtitlesColorShow.value = false;
  2026. if(SubtitlesBgcShow.value==true) {
  2027. }
  2028. SubtitlesBgcShow.value = false;
  2029. };
  2030. onMounted(() => {
  2031. document.addEventListener('click',handleClickOutside);
  2032. });
  2033. function rgbaToBgra(rgba) {
  2034. const regex = /rgba\((\d+),\s*(\d+),\s*(\d+),\s*(\d+(\.\d+)?)\)/;
  2035. const match = regex.exec(rgba);
  2036. if (match) {
  2037. const red = parseInt(match[1]);
  2038. const green = parseInt(match[2]);
  2039. const blue = parseInt(match[3]);
  2040. const alpha = parseFloat(match[4]);
  2041. const b = blue;
  2042. const g = green;
  2043. const r = red;
  2044. const a = Math.round(alpha * 255);
  2045. const bgra = `bgra(${b}, ${g}, ${r}, ${a})`;
  2046. return bgra;
  2047. }
  2048. // Return the original value if no match is found
  2049. return rgba;
  2050. }
  2051. watch(
  2052. () => SubtitlesColor,
  2053. (newValue, oldValue) => {
  2054. SubtitlesColorBGRA.value = ref(rgbaToBgra(SubtitlesColor.value)); // 字幕字色
  2055. },
  2056. { deep: true }
  2057. );
  2058. watch(
  2059. () => SubtitlesBgc,
  2060. (newValue, oldValue) => {
  2061. SubtitlesBgcBGRA.value = ref(rgbaToBgra(SubtitlesBgc.value)); // 字幕背景色
  2062. },
  2063. { deep: true }
  2064. );
  2065. // 计算属性来计算盒子位置
  2066. const SubtitlesStyle = computed(() => {
  2067. return {
  2068. width: SubtitlesWidth.value + 'px',
  2069. top: SubtitlesTop.value + 'px',
  2070. left: SubtitlesLeft.value + 'px',
  2071. fontSize: SubtitlesSize.value + 'px',
  2072. fontFamily: SubtitlesFontFamily.value,
  2073. color: SubtitlesColor.value,
  2074. backgroundColor: SubtitlesBgc.value,
  2075. };
  2076. });
  2077. // 拖动盒子
  2078. const handleDrag = (event) => {
  2079. if (isResizing.value) {
  2080. return;
  2081. }
  2082. event.preventDefault();
  2083. const boxElement = canvasImgBox.value;
  2084. const imageElement = SubtitlesBox.value;
  2085. const offsetX = event.clientX - imageElement.offsetLeft;
  2086. const offsetY = event.clientY - imageElement.offsetTop;
  2087. const onMouseMove = (event) => {
  2088. const newX = event.clientX - offsetX;
  2089. const newY = event.clientY - offsetY;
  2090. SubtitlesLeft.value = newX;
  2091. SubtitlesTop.value = newY;
  2092. };
  2093. const onMouseUp = () => {
  2094. document.removeEventListener('mousemove', onMouseMove);
  2095. document.removeEventListener('mouseup', onMouseUp);
  2096. };
  2097. document.addEventListener('mousemove', onMouseMove);
  2098. document.addEventListener('mouseup', onMouseUp);
  2099. };
  2100. // 改变盒子宽度
  2101. const isResizing = ref(false);
  2102. const startX = ref(0);
  2103. const startResize = (event) => {
  2104. isResizing.value = true;
  2105. startX.value = event.clientX;
  2106. document.addEventListener('mousemove', resizeBox);
  2107. document.addEventListener('mouseup', stopResize);
  2108. };
  2109. const stopResize = () => {
  2110. isResizing.value = false;
  2111. document.removeEventListener('mousemove', resizeBox);
  2112. document.removeEventListener('mouseup', stopResize);
  2113. };
  2114. const resizeBox = (event) => {
  2115. if (isResizing.value) {
  2116. const deltaX = event.clientX - startX.value;
  2117. SubtitlesWidth.value += deltaX;
  2118. startX.value = event.clientX;
  2119. }
  2120. };
  2121. const startResize2 = (event) => {
  2122. isResizing.value = true;
  2123. startX.value = event.clientX;
  2124. document.addEventListener('mousemove', resizeBox2);
  2125. document.addEventListener('mouseup', stopResize2);
  2126. };
  2127. const stopResize2 = () => {
  2128. isResizing.value = false;
  2129. document.removeEventListener('mousemove', resizeBox2);
  2130. document.removeEventListener('mouseup', stopResize2);
  2131. };
  2132. const resizeBox2 = (event) => {
  2133. if (isResizing.value) {
  2134. const deltaX = startX.value - event.clientX;
  2135. const newLeft = SubtitlesLeft.value - deltaX;
  2136. SubtitlesWidth.value += deltaX;
  2137. SubtitlesLeft.value = newLeft;
  2138. startX.value = event.clientX;
  2139. }
  2140. };
  2141. //旋转盒子 暂时弃用
  2142. const startRotation = (event) => {
  2143. isRotating.value = true;
  2144. isResizing.value = true;
  2145. // document.removeEventListener('mousedown', handleDrag);
  2146. document.addEventListener('mousemove', rotateBox);
  2147. document.addEventListener('mouseup', stopRotation);
  2148. const box9CenterX = event.clientX;
  2149. const box9CenterY = event.clientY;
  2150. const SubtitlesBoxCenterX = SubtitlesBox.value.offsetLeft + SubtitlesBox.value.offsetWidth / 2;
  2151. const SubtitlesBoxCenterY = SubtitlesBox.value.offsetTop + SubtitlesBox.value.offsetHeight / 2;
  2152. rotationStartAngle.value = Math.atan2(box9CenterY - SubtitlesBoxCenterY, box9CenterX - SubtitlesBoxCenterX);
  2153. };
  2154. const rotateBox = (event) => {
  2155. if (isRotating.value) {
  2156. const SubtitlesBoxCenterX = SubtitlesBox.value.offsetLeft + SubtitlesBox.value.offsetWidth / 2;
  2157. const SubtitlesBoxCenterY = SubtitlesBox.value.offsetTop + SubtitlesBox.value.offsetHeight / 2;
  2158. const mouseAngle = Math.atan2(event.clientY - SubtitlesBoxCenterY, event.clientX - SubtitlesBoxCenterX);
  2159. SubtitlesAngle.value = (mouseAngle - rotationStartAngle.value);
  2160. SubtitlesBox.value.style.transform = `rotate(${SubtitlesAngle.value}rad)`;
  2161. }
  2162. };
  2163. const stopRotation = () => {
  2164. isRotating.value = false;
  2165. isResizing.value = false;
  2166. document.removeEventListener('mousemove', rotateBox);
  2167. document.removeEventListener('mouseup', stopRotation);
  2168. };
  2169. onBeforeUnmount(() => {
  2170. document.removeEventListener('mousemove', resizeBox);
  2171. document.removeEventListener('mouseup', stopResize);
  2172. document.removeEventListener('mousemove', resizeBox2);
  2173. document.removeEventListener('mouseup', stopResize2);
  2174. document.removeEventListener('mousemove', rotateBox);
  2175. document.removeEventListener('mouseup', stopRotation);
  2176. });
  2177. // 监听字幕显示
  2178. watch(
  2179. () => SubtitlesSwitch,
  2180. async (newValue, oldValue) => {
  2181. },
  2182. { deep: true }
  2183. );
  2184. // 监听图片变化
  2185. watch(
  2186. () => imgUrl.value,
  2187. async (newValue, oldValue) => {
  2188. await nextTick()
  2189. // console.log(getComputedStyle(document.getElementById("canvasImg"),null));
  2190. },
  2191. { deep: true }
  2192. );
  2193. //#endregion ----------------------------
  2194. //#region
  2195. //#endregion ----------------------------
  2196. //#region $t在script中的使用
  2197. const {
  2198. appContext: {
  2199. config: { globalProperties },
  2200. },
  2201. } = getCurrentInstance();
  2202. const string = "createVideo.typeScript";
  2203. const t = globalProperties.$t(string);
  2204. //#endregion ----------------------------
  2205. //#region 页面初始化
  2206. onMounted(async () => {
  2207. getAiFaceList(1, pageSize.value, 1);
  2208. if (AccessToken) {
  2209. getAiFaceUserList(1, pageSize.value, 3);
  2210. await getmusicListPerson(
  2211. musicListPageNumPerson.value,
  2212. musicListPageSize.value
  2213. );
  2214. }
  2215. await voiceTotal();
  2216. if (route.query.id) {
  2217. await findById(route.query.id);
  2218. }
  2219. await getmusicListSystem(
  2220. musicListPageNumSystem.value,
  2221. musicListPageSize.value
  2222. );
  2223. musicListSystem.value.forEach((item) => {
  2224. if (item.id == musicItemAction.value) {
  2225. audioMusicUrl.value = item.url;
  2226. }
  2227. });
  2228. musicListPerson.value.forEach((item) => {
  2229. if (item.id == musicItemAction.value) {
  2230. audioMusicUrl.value = item.url;
  2231. }
  2232. });
  2233. createWS(); //创建websocket
  2234. });
  2235. //#endregion ----------------------------
  2236. //#region 页面销毁
  2237. onUnmounted(() => {
  2238. if (mySocket) {
  2239. mySocket.close();
  2240. }
  2241. });
  2242. //#endregion ----------------------
  2243. </script>
  2244. <style lang="scss" scoped>
  2245. .page {
  2246. // height: 1000px;
  2247. height: calc(100vh - 80px);
  2248. // padding: 20px 0 0 40px;
  2249. background-color: #fff;
  2250. border-radius: 25px 0 0;
  2251. }
  2252. .left {
  2253. width: 68%;
  2254. // height: 860px;
  2255. // height: 1000px;
  2256. // max-height: 100%;
  2257. padding: 20px;
  2258. overflow: hidden;
  2259. // overflow-y: scroll;
  2260. height: 100%;
  2261. :deep(.el-input__wrapper) {
  2262. border: none;
  2263. border-bottom: 2px solid #000;
  2264. border-radius: 0;
  2265. box-shadow: none;
  2266. }
  2267. .el-icon.icon-edit {
  2268. width: 35px;
  2269. height: 35px;
  2270. font-size: 35px;
  2271. color: #8c939d;
  2272. text-align: center;
  2273. }
  2274. &-top {
  2275. position: relative;
  2276. width: 100%;
  2277. // height: 65%;
  2278. // height: 580px;
  2279. height: 67%;
  2280. // height: calc(70vh - 80px );
  2281. margin-bottom: 20px;
  2282. overflow: hidden;
  2283. .editSubtitlesBox{
  2284. display: flex;
  2285. justify-content: space-evenly;
  2286. // padding-right: 80px;
  2287. padding:0 10px;
  2288. margin-left: 10px;
  2289. margin-right: 80px;
  2290. border-radius: 20px;
  2291. flex: 1;
  2292. line-height: 35px;
  2293. background-color: #f1f2f6;
  2294. font-size: 16px;
  2295. .item {
  2296. display: flex;
  2297. align-items: center;
  2298. margin-left: 10px;
  2299. img {
  2300. cursor: pointer;
  2301. }
  2302. // 调色盘
  2303. .myColorBox {
  2304. position: relative;
  2305. width: 25px;
  2306. height: 25px;
  2307. cursor: pointer;
  2308. .selectColor {
  2309. position: absolute;
  2310. top: 150%;
  2311. left: 50%;
  2312. transform: translateX(-50%);
  2313. display: flex;
  2314. justify-content: flex-start;
  2315. flex-wrap: wrap;
  2316. width: 142px;
  2317. background-color: #ffffff;
  2318. z-index: 99;
  2319. box-shadow: rgba(0, 0, 0, 0.3) 0px 0px 5px 5px;
  2320. &::before {
  2321. content: "";
  2322. display: block;
  2323. position: absolute;
  2324. top: -10%;
  2325. left: 50%;
  2326. transform: translateX(-50%);
  2327. width: 0;
  2328. height: 0;
  2329. border-left: 15px solid transparent;
  2330. border-right: 15px solid transparent;
  2331. border-bottom: 15px solid white;
  2332. }
  2333. &-item {
  2334. width: 25px;
  2335. height: 25px;
  2336. margin: 5px 5px;
  2337. border: #000 solid 2px;
  2338. border-radius: 5px;
  2339. cursor: pointer;
  2340. }
  2341. }
  2342. }
  2343. .el-input {
  2344. width: 60px;
  2345. height: 25px;
  2346. }
  2347. .el-input__wrapper {
  2348. height: 25px;
  2349. }
  2350. }
  2351. :deep(.el-input__wrapper) {
  2352. border: none;
  2353. border: 2px solid #000;
  2354. border-radius: 5px;
  2355. box-shadow: none;
  2356. }
  2357. }
  2358. .uploadBtn {
  2359. position: relative;
  2360. padding: 10px 15px;
  2361. margin-left: 10px;
  2362. border-radius: 5px;
  2363. transition: all 0.3s;
  2364. background-color: #000;
  2365. color: #fff;
  2366. &:hover {
  2367. background-color: #15d1b8;
  2368. }
  2369. }
  2370. .bigImg {
  2371. display: flex;
  2372. align-items: center;
  2373. justify-content: space-between;
  2374. width: 50%;
  2375. height: 88%;
  2376. margin-top: 20px;
  2377. .SubtitlesBox {
  2378. position: absolute;
  2379. user-select: none;
  2380. // width: 200px;
  2381. padding: 5px 5px;
  2382. text-align: center;
  2383. // border: 2px solid rgba(0, 0, 0, 0);
  2384. border: 2px solid #00ff00;
  2385. background-color: #fff;
  2386. cursor: move;
  2387. &:hover {
  2388. // border: 2px solid #00ff00;
  2389. .stretchBox {
  2390. // opacity: 1;
  2391. }
  2392. }
  2393. .SubtitlesTip {
  2394. white-space: nowrap;
  2395. overflow: hidden;
  2396. text-overflow: ellipsis;
  2397. }
  2398. .stretchBox {
  2399. opacity: 1;
  2400. position: absolute;
  2401. width: 10px;
  2402. height: 10px;
  2403. background-color: #fff;
  2404. }
  2405. .box1 {
  2406. top: -5px;
  2407. left: -5px;
  2408. cursor: nwse-resize;
  2409. display: none;
  2410. }
  2411. .box2 {
  2412. display: none;
  2413. top: -5px;
  2414. left: 50%;
  2415. transform: translateX(-50%);
  2416. cursor: ns-resize;
  2417. }
  2418. .box3 {
  2419. display: none;
  2420. top: -5px;
  2421. left:100%;
  2422. transform: translateX(-5px);
  2423. cursor: nesw-resize;
  2424. }
  2425. .box4 {
  2426. top: 50%;
  2427. left: -5px;
  2428. transform: translateY(-5px);
  2429. cursor: ew-resize;
  2430. }
  2431. .box5 {
  2432. top: 50%;
  2433. left:100%;
  2434. transform: translate(-5px,-5px);
  2435. cursor: ew-resize;
  2436. }
  2437. .box6 {
  2438. display: none;
  2439. top: 100%;
  2440. left: -5px;
  2441. transform: translateY(-5px);
  2442. cursor: nesw-resize;
  2443. }
  2444. .box7 {
  2445. display: none;
  2446. top: 100%;
  2447. left: 50%;
  2448. transform: translate(-5px,-5px);
  2449. cursor: ns-resize;
  2450. }
  2451. .box8 {
  2452. display: none;
  2453. top: 100%;
  2454. left: 100%;
  2455. transform: translate(-5px,-5px);
  2456. cursor: nwse-resize;
  2457. }
  2458. .box9 {
  2459. top: 150%;
  2460. left: 50%;
  2461. transform: translate(-5px,50%);
  2462. cursor: pointer;
  2463. }
  2464. }
  2465. img {
  2466. position: absolute;
  2467. left: 0;
  2468. top: 0;
  2469. max-width: 100%;
  2470. max-height: 100%;
  2471. border-radius: 15px;
  2472. z-index: -1;
  2473. }
  2474. }
  2475. .noImg {
  2476. position: relative;
  2477. width: 100%;
  2478. height: 100%;
  2479. border: 1px solid #ccc;
  2480. border-radius: 30px;
  2481. .imgBox {
  2482. position: relative;
  2483. width: 100%;
  2484. height: 100%;
  2485. border-radius: 50%;
  2486. overflow: hidden;
  2487. border: 1px solid #ccc;
  2488. img {
  2489. position: absolute;
  2490. top: 50%;
  2491. left: 50%;
  2492. transform: translate(-50%, -50%);
  2493. max-width: 120%;
  2494. }
  2495. }
  2496. .trueImg {
  2497. position: relative;
  2498. top: 4%;
  2499. width: 100px;
  2500. height: 100px;
  2501. border-radius: 50%;
  2502. .icon {
  2503. position: absolute;
  2504. top: 0%;
  2505. right: -5%;
  2506. border: 2px solid #00ff95;
  2507. color: #00ff95;
  2508. }
  2509. }
  2510. .el-upload {
  2511. position: absolute;
  2512. top: 0;
  2513. left: 0;
  2514. width: 100%;
  2515. height: 100%;
  2516. opacity: 0;
  2517. }
  2518. .upload {
  2519. top: 30%;
  2520. width: 400px;
  2521. height: 200px;
  2522. padding-top: 10px;
  2523. font-size: 50px;
  2524. line-height: 100px;
  2525. text-align: center;
  2526. background-color: rgba(204, 204, 204, 0.1);
  2527. border-radius: 30px;
  2528. box-shadow: rgba(108, 120, 238, 0.3) 0px 0px 5px 5px;
  2529. p {
  2530. position: absolute;
  2531. top: 50%;
  2532. left: 50%;
  2533. transform: translate(-50%, -50%);
  2534. z-index: -1;
  2535. opacity: 0.5;
  2536. .el-icon {
  2537. width: 200px;
  2538. height: 200px;
  2539. font-size: 150px;
  2540. }
  2541. img {
  2542. max-width: 60%;
  2543. max-height: 60%;
  2544. }
  2545. }
  2546. span {
  2547. display: block;
  2548. font-size: 16px;
  2549. font-weight: 900;
  2550. line-height: 16px;
  2551. text-align: center;
  2552. margin-bottom: 130px;
  2553. }
  2554. }
  2555. .falseImg {
  2556. top: 75%;
  2557. // background-color: #795562;
  2558. display: flex;
  2559. justify-content: space-evenly;
  2560. width: 700px;
  2561. height: 100px;
  2562. > div {
  2563. position: relative;
  2564. width: 100px;
  2565. height: 100px;
  2566. background-color: #ccc;
  2567. border-radius: 50%;
  2568. .icon {
  2569. position: absolute;
  2570. top: 0%;
  2571. right: -5%;
  2572. border: 2px solid red;
  2573. color: red;
  2574. }
  2575. }
  2576. }
  2577. }
  2578. // 公共类名
  2579. input {
  2580. position: absolute;
  2581. top: 0;
  2582. left: 0;
  2583. width: 100%;
  2584. height: 100%;
  2585. opacity: 0;
  2586. cursor: pointer;
  2587. }
  2588. .icon {
  2589. position: absolute;
  2590. right: 2px;
  2591. width: 30px;
  2592. height: 30px;
  2593. font-size: 20px;
  2594. font-weight: 700;
  2595. line-height: 26px;
  2596. text-align: center;
  2597. border: 2px solid #000;
  2598. border-radius: 50%;
  2599. }
  2600. .editAiPicBox {
  2601. position: relative;
  2602. display: flex;
  2603. width: 100%;
  2604. height: 100%;
  2605. border: 1px solid #ccc;
  2606. background-color: #f1f2f6;
  2607. border-radius: 30px;
  2608. overflow: hidden;
  2609. .edit {
  2610. width: 50%;
  2611. height: 100%;
  2612. background-color: #fff;
  2613. .TagBox {
  2614. width: 100%;
  2615. height: 100%;
  2616. border-radius: 0 0 30px 0;
  2617. background-color: #f1f2f6;
  2618. }
  2619. }
  2620. .AIpicBox {
  2621. position: relative;
  2622. width: 50%;
  2623. height: 100%;
  2624. border-radius: 30px 0 0 0;
  2625. background-color: #fff;
  2626. .picBox {
  2627. position: relative;
  2628. width: 100%;
  2629. height: 100%;
  2630. border-radius: 30px 0 0 0;
  2631. background-color: #fff;
  2632. img {
  2633. position: absolute;
  2634. top: 50%;
  2635. left: 50%;
  2636. transform: translate(-50%, -50%);
  2637. max-width: 100%;
  2638. max-height: 100%;
  2639. border-radius: 30px;
  2640. }
  2641. }
  2642. }
  2643. }
  2644. .SubtitlesSwitch {
  2645. position: absolute;
  2646. right: 0;
  2647. top: 0px;
  2648. padding: 10px;
  2649. border-radius: 20px;
  2650. background-color: #f1f2f6;
  2651. text-align: center;
  2652. }
  2653. }
  2654. &-bottom {
  2655. width: 100%;
  2656. height: 29%;
  2657. // height: calc(30vh - 80px );
  2658. // margin-top: 10px;
  2659. .tab {
  2660. padding: 10px;
  2661. > div {
  2662. margin-right: 20px;
  2663. padding: 10px 15px;
  2664. transition: all 0.3s;
  2665. border-radius: 5px;
  2666. &:hover {
  2667. background-color: #000;
  2668. color: #fff;
  2669. }
  2670. }
  2671. .tabAvtive {
  2672. background-color: #000;
  2673. color: #fff;
  2674. }
  2675. }
  2676. .ai {
  2677. width: 100%;
  2678. }
  2679. .faceList {
  2680. padding: 8px;
  2681. display: flex;
  2682. flex-wrap: wrap;
  2683. justify-content: flex-start;
  2684. width: 100%;
  2685. // height: 220px;
  2686. height: 90%;
  2687. overflow-y: scroll;
  2688. .faceItem {
  2689. position: relative;
  2690. margin-right: 30px;
  2691. margin-bottom: 35px;
  2692. width: 100px;
  2693. height: 100px;
  2694. // padding: 0 25px;
  2695. line-height: 100px;
  2696. text-align: center;
  2697. border-radius: 50%;
  2698. background-color: #fbf8f8;
  2699. // border: 2px solid #fff;
  2700. box-shadow: rgba(0, 0, 0, 0.3) 0px 0px 5px 5px;
  2701. // overflow: hidden;
  2702. transition: all 0.3s;
  2703. .imgBox {
  2704. cursor: pointer;
  2705. position: absolute;
  2706. top: 0;
  2707. left: 0;
  2708. right: 0;
  2709. bottom: 0;
  2710. z-index: 3;
  2711. width: 100px;
  2712. height: 100px;
  2713. border-radius: 50%;
  2714. background-color: #fbf8f8;
  2715. overflow: hidden;
  2716. img {
  2717. position: absolute;
  2718. top: 50%;
  2719. left: 50%;
  2720. transform: translate(-50%, -50%);
  2721. width: 100%;
  2722. }
  2723. }
  2724. .deleteIcon {
  2725. position: absolute;
  2726. top: 0;
  2727. right: -5px;
  2728. z-index: 999;
  2729. width: 25px;
  2730. height: 25px;
  2731. border: 2px solid #6d76ef;
  2732. border-radius: 50%;
  2733. color: #6d76ef;
  2734. font-size: 20px;
  2735. line-height: 23px;
  2736. cursor: pointer;
  2737. }
  2738. .uploadImg {
  2739. line-height: 100px;
  2740. font-size: 50px;
  2741. font-weight: 700;
  2742. text-align: center;
  2743. }
  2744. }
  2745. .faceItemActive {
  2746. position: relative;
  2747. transition: all 0.3s;
  2748. &::before {
  2749. content: "";
  2750. position: absolute;
  2751. // top: -3px;
  2752. // right: -7px;
  2753. // left: -3px;
  2754. // bottom: -7px;
  2755. top: -3px;
  2756. right: -3px;
  2757. left: -3px;
  2758. bottom: -3px;
  2759. z-index: 2;
  2760. border-radius: 50%;
  2761. background: linear-gradient(
  2762. 225deg,
  2763. rgb(81 217 202 / 100%),
  2764. rgb(114 100 245 / 100%)
  2765. );
  2766. }
  2767. }
  2768. }
  2769. .Aipicture {
  2770. width: 100%;
  2771. height: 180px;
  2772. .title {
  2773. font-size: 30px;
  2774. text-align: center;
  2775. line-height: 80px;
  2776. background-image: -webkit-linear-gradient(
  2777. left,
  2778. #4b61dc,
  2779. #15d1b8 25%,
  2780. #4b61dc 50%,
  2781. #15d1b8 75%,
  2782. #4b61dc
  2783. ) !important;
  2784. -webkit-text-fill-color: transparent !important;
  2785. -webkit-background-clip: text !important;
  2786. background-clip: text !important;
  2787. -webkit-background-size: 200% 100% !important;
  2788. background-size: 200% 100% !important;
  2789. .el-icon {
  2790. vertical-align: text-top;
  2791. font-size: 32px;
  2792. }
  2793. }
  2794. }
  2795. .aiPicTab {
  2796. position: relative;
  2797. z-index: 2;
  2798. .subscript {
  2799. position: absolute;
  2800. top: -20px;
  2801. right: -20px;
  2802. width: 40px;
  2803. height: 40px;
  2804. z-index: 3;
  2805. background-color: #fff;
  2806. img {
  2807. position: absolute;
  2808. top: 0;
  2809. left: 0px;
  2810. width: 100%;
  2811. height: 100%;
  2812. z-index: 3;
  2813. }
  2814. span {
  2815. position: absolute;
  2816. line-height: 45px;
  2817. z-index: 4;
  2818. padding-left: 0px;
  2819. padding-bottom: 5px;
  2820. font-size: 16px;
  2821. color: #17ceb9;
  2822. font-weight: 900;
  2823. transform: rotate(45deg);
  2824. }
  2825. }
  2826. .subscript1 {
  2827. position: absolute;
  2828. top: -20px;
  2829. right: -20px;
  2830. width: 40px;
  2831. height: 40px;
  2832. z-index: 3;
  2833. transform: rotate(45deg);
  2834. img {
  2835. position: absolute;
  2836. top: 0;
  2837. left: 0px;
  2838. width: 100%;
  2839. height: 100%;
  2840. z-index: 3;
  2841. }
  2842. span {
  2843. position: absolute;
  2844. line-height: 45px;
  2845. z-index: 4;
  2846. padding-left: 6px;
  2847. padding-bottom: 5px;
  2848. font-size: 16px;
  2849. color: #17ceb9;
  2850. font-weight: 900;
  2851. transform: rotate(45deg);
  2852. }
  2853. }
  2854. }
  2855. }
  2856. }
  2857. .right {
  2858. width: 32%;
  2859. // height: 860px;
  2860. // height: 1000px;
  2861. // max-height: 100%;
  2862. padding: 30px 30px 20px 30px;
  2863. background-color: #f1f2f6;
  2864. border-radius: 30px 0 0 0;
  2865. position: relative;
  2866. z-index: 2;
  2867. overflow: hidden;
  2868. overflow-y: scroll;
  2869. height: 100%;
  2870. .subscript {
  2871. position: absolute;
  2872. top: 30px;
  2873. right: 35px;
  2874. width: 40px;
  2875. height: 40px;
  2876. z-index: 3;
  2877. transform: rotate(45deg);
  2878. img {
  2879. position: absolute;
  2880. top: 0;
  2881. left: 0px;
  2882. width: 100%;
  2883. height: 100%;
  2884. z-index: 3;
  2885. }
  2886. span {
  2887. position: absolute;
  2888. line-height: 45px;
  2889. z-index: 4;
  2890. padding-left: 0px;
  2891. padding-bottom: 5px;
  2892. font-size: 16px;
  2893. color: #17ceb9;
  2894. font-weight: 900;
  2895. transform: rotate(45deg);
  2896. }
  2897. }
  2898. :deep(.el-tabs--border-card) {
  2899. font-size: 20px;
  2900. border: none;
  2901. }
  2902. .tabs {
  2903. min-height: 100%;
  2904. padding: 20px 20px 0px 20px;
  2905. border-radius: 20px;
  2906. position: relative;
  2907. .card1 {
  2908. .text {
  2909. padding: 10px;
  2910. border: 1px solid #c0c4cc;
  2911. .el-button {
  2912. float: right;
  2913. margin-top: 10px;
  2914. }
  2915. :deep(.el-textarea__inner) {
  2916. resize: none;
  2917. box-shadow: none;
  2918. }
  2919. }
  2920. p {
  2921. margin: 7px 0;
  2922. }
  2923. > .el-button {
  2924. width: 100%;
  2925. height: 50px;
  2926. margin-top: 10px;
  2927. }
  2928. .sundriesBox {
  2929. width: 100%;
  2930. height: 30px;
  2931. line-height: 30px;
  2932. overflow: hidden;
  2933. span {
  2934. display: inline-block;
  2935. font-size: 20px;
  2936. height: 30px;
  2937. width: 30px;
  2938. text-align: center;
  2939. width: 40px;
  2940. }
  2941. i {
  2942. font-size: 14px;
  2943. float: right;
  2944. }
  2945. .previewAudioLoadingBox {
  2946. display: inline-block;
  2947. margin-top: -20px;
  2948. width: 20px;
  2949. height: 33px;
  2950. transform: scale(1);
  2951. transition: all 0.3s;
  2952. }
  2953. }
  2954. :deep(.el-input .is-focus) {
  2955. // box-shadow: none !important;
  2956. // box-shadow: 0 0 0 1px #000 !important;
  2957. }
  2958. }
  2959. .card2 {
  2960. .overtimeTips {
  2961. height: 0;
  2962. opacity: 0;
  2963. font-size: 16px;
  2964. // width: 60%;
  2965. // display: inline-block;
  2966. padding: 5px;
  2967. margin-bottom: 10px;
  2968. // background-color: #ccc;
  2969. border-radius: 5px;
  2970. line-height: 20px;
  2971. color: red;
  2972. text-align: center;
  2973. img {
  2974. width: 16px;
  2975. height: 16px;
  2976. vertical-align: text-top;
  2977. margin-right: 5px;
  2978. }
  2979. border: none;
  2980. box-shadow: none;
  2981. &:hover {
  2982. transform: translateY(0);
  2983. }
  2984. }
  2985. padding: 10px;
  2986. .el-icon {
  2987. flex: 1;
  2988. // color: red;
  2989. vertical-align: text-top;
  2990. transition: all 0.3s;
  2991. }
  2992. > div {
  2993. position: relative;
  2994. margin-top: 10px;
  2995. width: 100%;
  2996. border: #000 1px solid;
  2997. border-radius: 10px;
  2998. overflow: hidden;
  2999. transition: all 0.3s;
  3000. box-shadow: #00000026 2px 2px 2px 2px;
  3001. &:hover {
  3002. transform: translateY(-10px);
  3003. }
  3004. }
  3005. // 录制mp3部分
  3006. .uploadAudio {
  3007. padding: 20px;
  3008. // margin-top: 30px;
  3009. margin-bottom: 20px;
  3010. // height: 70px;
  3011. .btnBox {
  3012. display: flex;
  3013. justify-content: space-between;
  3014. // justify-content: flex-end;
  3015. margin-bottom: 10px;
  3016. }
  3017. audio {
  3018. height: 50px;
  3019. width: 100%;
  3020. }
  3021. button {
  3022. border-radius: 10px;
  3023. // height: 30px;
  3024. line-height: 22px;
  3025. padding: 5px 10px;
  3026. background-color: #fff;
  3027. transition: all 0.3s;
  3028. cursor: pointer;
  3029. &:hover {
  3030. background-color: #000;
  3031. color: #fff;
  3032. }
  3033. .el-icon {
  3034. font-size: 20px;
  3035. }
  3036. }
  3037. }
  3038. .uploadAudioAvtion {
  3039. height: 170px;
  3040. }
  3041. .uploadAudioAvtion2 {
  3042. // height: 220px !important;
  3043. }
  3044. // 上传音频部分
  3045. .uploadVideo {
  3046. display: flex;
  3047. justify-content: space-between;
  3048. align-items: center;
  3049. text-align: center;
  3050. margin-bottom: 20px;
  3051. height: 100px;
  3052. font-size: 50px;
  3053. font-weight: 700;
  3054. line-height: 100px;
  3055. transition: all 0.3s;
  3056. img {
  3057. margin: auto;
  3058. max-width: 80%;
  3059. max-height: 80%;
  3060. }
  3061. input {
  3062. position: absolute;
  3063. top: 0;
  3064. left: 0;
  3065. width: 100%;
  3066. height: 100px;
  3067. opacity: 0;
  3068. cursor: pointer;
  3069. }
  3070. }
  3071. .AudioBox {
  3072. display: flex;
  3073. justify-content: space-around;
  3074. padding: 10px;
  3075. transition: all 0.3s;
  3076. }
  3077. > .el-button {
  3078. width: 100%;
  3079. height: 50px;
  3080. margin-top: 10px;
  3081. // background-color: #000;
  3082. }
  3083. }
  3084. .card3 {
  3085. width: 100%;
  3086. padding: 10px;
  3087. .overtimeTips {
  3088. opacity: 0;
  3089. height: 0;
  3090. font-size: 16px;
  3091. // width: 60%;
  3092. // display: inline-block;
  3093. padding: 5px;
  3094. margin-bottom: 10px;
  3095. // background-color: #ccc;
  3096. border-radius: 5px;
  3097. line-height: 20px;
  3098. color: red;
  3099. text-align: center;
  3100. img {
  3101. width: 16px;
  3102. height: 16px;
  3103. vertical-align: text-top;
  3104. margin-right: 5px;
  3105. }
  3106. border: none;
  3107. box-shadow: none;
  3108. }
  3109. .el-icon {
  3110. vertical-align: text-top;
  3111. }
  3112. .musicTab {
  3113. display: flex;
  3114. width: 100%;
  3115. height: 50px;
  3116. line-height: 50px;
  3117. text-align: center;
  3118. .musicTabItem {
  3119. background-color: #f5f7fa;
  3120. width: 50%;
  3121. cursor: pointer;
  3122. }
  3123. .musicTabItemActive {
  3124. background-color: #000;
  3125. color: #fff;
  3126. }
  3127. }
  3128. .uploadVideo {
  3129. position: relative;
  3130. margin-top: 10px;
  3131. width: 100%;
  3132. border: #000 1px solid;
  3133. border-radius: 10px;
  3134. overflow: hidden;
  3135. transition: all 0.3s;
  3136. box-shadow: #00000026 2px 2px 2px 2px;
  3137. text-align: center;
  3138. margin-bottom: 20px;
  3139. height: 100px;
  3140. font-size: 50px;
  3141. font-weight: 700;
  3142. line-height: 100px;
  3143. input {
  3144. position: absolute;
  3145. top: 0;
  3146. left: 0;
  3147. width: 100%;
  3148. height: 100%;
  3149. opacity: 0;
  3150. cursor: pointer;
  3151. }
  3152. }
  3153. .musicList {
  3154. width: 100%;
  3155. display: flex;
  3156. flex-wrap: wrap;
  3157. justify-content: space-between;
  3158. .musicItem {
  3159. width: 100%;
  3160. height: 30px;
  3161. position: relative;
  3162. margin-top: 10px;
  3163. padding: 0 20px;
  3164. border: #000 1px solid;
  3165. border-radius: 10px;
  3166. overflow: hidden;
  3167. transition: all 0.3s;
  3168. box-shadow: #00000026 2px 2px 2px 2px;
  3169. transition: all 0.3s;
  3170. cursor: pointer;
  3171. .musicNmae {
  3172. display: inline-block;
  3173. width: 260px;
  3174. margin-right: 20px;
  3175. overflow: hidden;
  3176. text-overflow: ellipsis;
  3177. white-space: nowrap;
  3178. }
  3179. .musicTime {
  3180. display: inline-block;
  3181. max-width: 300px;
  3182. overflow: hidden;
  3183. text-overflow: ellipsis;
  3184. white-space: nowrap;
  3185. }
  3186. .delMusic {
  3187. color: #fff;
  3188. padding-left: 15px;
  3189. padding-top: -15px;
  3190. line-height: 30px;
  3191. vertical-align: top;
  3192. }
  3193. }
  3194. .musicItemAvtive {
  3195. background-color: #000;
  3196. color: #fff;
  3197. }
  3198. }
  3199. .pagination {
  3200. // position: fixed;
  3201. // bottom: 20px;
  3202. // left: 50%;
  3203. // transform: translateX(-50%);
  3204. margin-top: 10px;
  3205. background-color: #ffffff;
  3206. padding: 10px 20px;
  3207. border-radius: 8px;
  3208. transition: all 0.3s;
  3209. cursor: pointer;
  3210. &:hover {
  3211. background-color: #b8b8b873;
  3212. }
  3213. }
  3214. .AudioBox {
  3215. display: flex;
  3216. justify-content: space-around;
  3217. width: 100%;
  3218. padding: 20px;
  3219. transition: all 0.3s;
  3220. audio {
  3221. width: 100%;
  3222. }
  3223. }
  3224. > .el-button {
  3225. width: 100%;
  3226. height: 50px;
  3227. margin-top: 10px;
  3228. }
  3229. :deep(.el-input__wrapper) {
  3230. border: none;
  3231. border-bottom: 2px solid #000;
  3232. border-radius: 0;
  3233. box-shadow: none;
  3234. }
  3235. .el-icon.icon-edit {
  3236. width: 30px;
  3237. height: 30px;
  3238. font-size: 30px;
  3239. color: #8c939d;
  3240. text-align: center;
  3241. cursor: pointer;
  3242. transition: all 0.3s;
  3243. &:hover {
  3244. color: #292929;
  3245. }
  3246. }
  3247. }
  3248. .card4 {
  3249. .text {
  3250. padding: 10 px;
  3251. border: 1px solid #c0c4cc;
  3252. .el-button {
  3253. float: right;
  3254. margin-top: 10px;
  3255. }
  3256. :deep(.el-textarea__inner) {
  3257. cursor: text;
  3258. }
  3259. :deep(.el-textarea__inner) {
  3260. resize: none;
  3261. box-shadow: none;
  3262. }
  3263. i {
  3264. font-size: 14px;
  3265. float: right;
  3266. }
  3267. }
  3268. p {
  3269. margin: 7px 0;
  3270. }
  3271. .card4Btn {
  3272. margin-top: 10px;
  3273. border-radius: 5px;
  3274. height: 50px;
  3275. width: 100%;
  3276. color: #fff;
  3277. background-color: #000;
  3278. text-align: center;
  3279. line-height: 50px;
  3280. cursor: pointer;
  3281. }
  3282. .el-button {
  3283. width: 100%;
  3284. height: 50px;
  3285. margin-top: 10px;
  3286. }
  3287. }
  3288. :deep(.el-tabs__item.is-active) {
  3289. color: #909393;
  3290. background-color: #000;
  3291. }
  3292. :deep(.el-tabs__nav) {
  3293. width: 100%;
  3294. }
  3295. :deep(.el-tabs__item) {
  3296. width: 26%;
  3297. }
  3298. :deep(.el-tabs__item:hover) {
  3299. color: #909393;
  3300. }
  3301. :deep(.el-tabs__content) {
  3302. height: calc(100% - 60px);
  3303. padding: 0;
  3304. }
  3305. }
  3306. }
  3307. </style>