@@ -20,6 +20,7 @@ | |||||
"axios": "^1.3.4", | "axios": "^1.3.4", | ||||
"echarts": "^5.2.2", | "echarts": "^5.2.2", | ||||
"element-plus": "^2.3.1", | "element-plus": "^2.3.1", | ||||
"js-audio-recorder": "^1.0.7", | |||||
"nprogress": "^0.2.0", | "nprogress": "^0.2.0", | ||||
"path-browserify": "^1.0.1", | "path-browserify": "^1.0.1", | ||||
"path-to-regexp": "^6.2.0", | "path-to-regexp": "^6.2.0", | ||||
@@ -0,0 +1,22 @@ | |||||
import request from "@/utils/request.js"; | |||||
/** | |||||
* @description 上传图片 | |||||
* @params ticket | |||||
* @returns data | |||||
*/ | |||||
export function awsImgUpload(formData) { | |||||
return request({ | |||||
url: `/api/upload/awsImgUpload`, | |||||
method: 'post', | |||||
data: formData | |||||
}) | |||||
} | |||||
export function awsMp3Upload(formData) { | |||||
return request({ | |||||
url: `/api/upload/awsImgUpload`, | |||||
method: 'post', | |||||
data: formData | |||||
}) | |||||
} |
@@ -40,7 +40,8 @@ export default { | |||||
languages: "Languages", | languages: "Languages", | ||||
voices: "Voices", | voices: "Voices", | ||||
styles: "Styles", | styles: "Styles", | ||||
reload: "Reload", | |||||
reselect: "reselect", | |||||
sureUpload:'Confirm upload', | |||||
generateVideo: "Generate Video", | generateVideo: "Generate Video", | ||||
}, | }, | ||||
@@ -32,7 +32,7 @@ export default { | |||||
createVideo: { | createVideo: { | ||||
title: "创作视频", | title: "创作视频", | ||||
typeScript: "输入文案", | typeScript: "输入文案", | ||||
uploadAudio: "上传视频", | |||||
uploadAudio: "上传音频", | |||||
aiScript: "AI生成文本", | aiScript: "AI生成文本", | ||||
typeTitle: "输入标题", | typeTitle: "输入标题", | ||||
confirm: "确定", | confirm: "确定", | ||||
@@ -40,7 +40,8 @@ export default { | |||||
languages: "选择语言", | languages: "选择语言", | ||||
voices: "选择声音", | voices: "选择声音", | ||||
styles: "选择风格", | styles: "选择风格", | ||||
reload: "重新上传", | |||||
reselect: "重新选择", | |||||
sureUpload:'确认上传', | |||||
generateVideo: "生成视频", | generateVideo: "生成视频", | ||||
}, | }, | ||||
@@ -55,6 +55,7 @@ const logo = ref(new URL(`../../../assets/logo.png`, import.meta.url).href); | |||||
// https://cn.vuejs.org/guide/built-ins/transition.html#the-transition-component | // https://cn.vuejs.org/guide/built-ins/transition.html#the-transition-component | ||||
.left-top-box { | .left-top-box { | ||||
height: 80px; | |||||
background-color: #000; | background-color: #000; | ||||
} | } | ||||
@@ -76,7 +76,7 @@ function routerTo(url: RouteLocationRaw) { | |||||
<style lang="scss" scoped> | <style lang="scss" scoped> | ||||
.myRouter { | .myRouter { | ||||
position: relative; | position: relative; | ||||
height: calc(100% - 50px); | |||||
height: calc(100% - 80px); | |||||
padding: 200px 20px 0; | padding: 200px 20px 0; | ||||
color: #fff; | color: #fff; | ||||
@@ -18,7 +18,7 @@ | |||||
<!--主页面--> | <!--主页面--> | ||||
<app-main /> | <app-main /> | ||||
<!-- 设置面板 -->- | |||||
<!-- 设置面板 --> | |||||
<RightPanel v-if="showSettings"> | <RightPanel v-if="showSettings"> | ||||
<settings /> | <settings /> | ||||
</RightPanel> | </RightPanel> | ||||
@@ -21,6 +21,7 @@ declare module '@vue/runtime-core' { | |||||
ElIcon: typeof import('element-plus/es')['ElIcon'] | ElIcon: typeof import('element-plus/es')['ElIcon'] | ||||
ElInput: typeof import('element-plus/es')['ElInput'] | ElInput: typeof import('element-plus/es')['ElInput'] | ||||
ElOption: typeof import('element-plus/es')['ElOption'] | ElOption: typeof import('element-plus/es')['ElOption'] | ||||
ElProgress: typeof import('element-plus/es')['ElProgress'] | |||||
ElRow: typeof import('element-plus/es')['ElRow'] | ElRow: typeof import('element-plus/es')['ElRow'] | ||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] | ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] | ||||
ElSelect: typeof import('element-plus/es')['ElSelect'] | ElSelect: typeof import('element-plus/es')['ElSelect'] | ||||
@@ -28,6 +29,7 @@ declare module '@vue/runtime-core' { | |||||
ElTabPane: typeof import('element-plus/es')['ElTabPane'] | ElTabPane: typeof import('element-plus/es')['ElTabPane'] | ||||
ElTabs: typeof import('element-plus/es')['ElTabs'] | ElTabs: typeof import('element-plus/es')['ElTabs'] | ||||
ElTooltip: typeof import('element-plus/es')['ElTooltip'] | ElTooltip: typeof import('element-plus/es')['ElTooltip'] | ||||
ElUpload: typeof import('element-plus/es')['ElUpload'] | |||||
GithubCorner: typeof import('./../components/GithubCorner/index.vue')['default'] | GithubCorner: typeof import('./../components/GithubCorner/index.vue')['default'] | ||||
Hamburger: typeof import('./../components/Hamburger/index.vue')['default'] | Hamburger: typeof import('./../components/Hamburger/index.vue')['default'] | ||||
IconSelect: typeof import('./../components/IconSelect/index.vue')['default'] | IconSelect: typeof import('./../components/IconSelect/index.vue')['default'] | ||||
@@ -0,0 +1,510 @@ | |||||
<template> | |||||
<div class="page flex justify-around"> | |||||
<!-- 左侧栏 --> | |||||
<div class="left"> | |||||
<!-- 顶部 --> | |||||
<!-- 有图片时 --> | |||||
<div class="left-top" v-show="imgUrl"> | |||||
<el-row class="justify-between"> | |||||
<!-- title框 --> | |||||
<div class="flex justify-between" style="width: 300px"> | |||||
<el-input | |||||
v-model="title" | |||||
:placeholder="$t('createVideo.typeTitle')" | |||||
/> | |||||
<el-icon class="icon-edit"><i-ep-edit /></el-icon> | |||||
</div> | |||||
<!-- 上传按钮 --> | |||||
<button class="uploadBtn"> | |||||
{{ $t("createVideo.reload") }} | |||||
<input type="file" @change="changeFile" /> | |||||
</button> | |||||
</el-row> | |||||
<!-- 中间展示大图 --> | |||||
<div class="bigImg inmiddle"> | |||||
<img :src="imgUrl" alt="" class="inmiddle" /> | |||||
</div> | |||||
</div> | |||||
<!-- 没有图片时 --> | |||||
<div class="left-top" v-show="!imgUrl"> | |||||
<!-- 中间展示大图 --> | |||||
<div class="noImg"> | |||||
<div class="trueImg inmiddle"> | |||||
<div class="icon">✔</div> | |||||
</div> | |||||
<div class="upload inmiddle"> | |||||
+ | |||||
<input type="file" @change="changeFile" /> | |||||
</div> | |||||
<div class="falseImg inmiddle"> | |||||
<div><div class="icon">×</div></div> | |||||
<div><div class="icon">×</div></div> | |||||
<div><div class="icon">×</div></div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<!-- 底部 --> | |||||
<div class="left-bottom"> | |||||
<div class="ai"> | |||||
<div class="aiAvatar"></div> | |||||
<div style="margin-left: 20px">Generate By Ai</div> | |||||
</div> | |||||
<div class="faceList scrolly"> | |||||
<div class="faceItem" v-for="item in 7" :key="item"> | |||||
<div class="img"></div> | |||||
<!-- <img src="" alt="" /> --> | |||||
<p>{{ item }}name</p> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<!-- 右侧栏 --> | |||||
<div class="right"> | |||||
<el-tabs type="border-card" class="tabs"> | |||||
<!-- ----------------------------- 1 --> | |||||
<el-tab-pane :label="$t('createVideo.typeScript')"> | |||||
<div class="card1">111111111111111111</div> | |||||
</el-tab-pane> | |||||
<!-- ------------------------ 2 --> | |||||
<el-tab-pane :label="$t('createVideo.uploadAudio')"> | |||||
<div class="card2"> | |||||
<!-- 录制MP3上传语音 --> | |||||
<div class="uploadAudio"> | |||||
<button @click="startRecording">开始录音</button> | |||||
<button @click="stopRecording">停止录音</button> | |||||
<button @click="playRecording">播放录音</button> | |||||
<audio v-if="audioUrl" controls :src="audioUrl"></audio> | |||||
<button v-if="audioUrl" @click="uploadAudio">上传音频</button> | |||||
</div> | |||||
<!-- 上传视频 --> | |||||
<div class="uploadVideo"> | |||||
<input type="file" ref="fileInput" @change="handleFileChange" /> | |||||
<video | |||||
v-if="videoSrc" | |||||
:src="videoSrc" | |||||
ref="videoPlayer" | |||||
controls | |||||
></video> | |||||
</div> | |||||
</div> | |||||
</el-tab-pane> | |||||
<!-- ----------------------- 3 --> | |||||
<el-tab-pane :label="$t('createVideo.aiScript')"> | |||||
<div class="card3"> | |||||
<div class="text"> | |||||
<el-input | |||||
v-model="title" | |||||
:placeholder="$t('createVideo.typeTitle')" | |||||
/> | |||||
<el-button type="primary"> | |||||
{{ $t("createVideo.confirm") }} | |||||
</el-button> | |||||
<el-input | |||||
v-model="title" | |||||
:rows="15" | |||||
type="textarea" | |||||
:placeholder="$t('createVideo.aiText')" | |||||
/> | |||||
<el-icon :size="20"> | |||||
<Edit /> | |||||
</el-icon> | |||||
</div> | |||||
<p>{{ $t("createVideo.languages") }}</p> | |||||
<el-select | |||||
v-model="language" | |||||
placeholder="Select" | |||||
style="width: 100%" | |||||
> | |||||
<el-option | |||||
v-for="item in languageOptions" | |||||
:key="item.value" | |||||
:label="item.label" | |||||
:value="item.value" | |||||
/> | |||||
</el-select> | |||||
<p>{{ $t("createVideo.voices") }}</p> | |||||
<el-select | |||||
v-model="language" | |||||
placeholder="Select" | |||||
style="width: 100%" | |||||
> | |||||
<el-option | |||||
v-for="item in languageOptions" | |||||
:key="item.value" | |||||
:label="item.label" | |||||
:value="item.value" | |||||
/> | |||||
</el-select> | |||||
<p>{{ $t("createVideo.styles") }}</p> | |||||
<el-select | |||||
v-model="language" | |||||
placeholder="Select" | |||||
style="width: 100%" | |||||
> | |||||
<el-option | |||||
v-for="item in languageOptions" | |||||
:key="item.value" | |||||
:label="item.label" | |||||
:value="item.value" | |||||
/> | |||||
</el-select> | |||||
<el-button type="primary">{{ | |||||
$t("createVideo.generateVideo") | |||||
}}</el-button> | |||||
</div> | |||||
</el-tab-pane> | |||||
</el-tabs> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script setup> | |||||
import { ref } from "vue"; | |||||
import { getCurrentInstance } from "vue"; | |||||
import Recorder from "js-audio-recorder"; | |||||
// 标题 | |||||
let title = ref(""); | |||||
// 上传图片 | |||||
let imgUrl = ref(""); | |||||
function changeFile(e) { | |||||
imgUrl.value = URL.createObjectURL(e.target.files[0]); | |||||
} | |||||
// $t在script中的使用 | |||||
const { | |||||
appContext: { | |||||
config: { globalProperties }, | |||||
}, | |||||
} = getCurrentInstance(); | |||||
const string = "createVideo.typeScript"; | |||||
const t = globalProperties.$t(string); | |||||
// 录制MP3上传语音 | |||||
const parameter = { | |||||
sampleBits: 16, // 采样位数,支持 8 或 16,默认是16 | |||||
sampleRate: 8000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000 | |||||
numChannels: 1, // 声道,支持 1 或 2, 默认是1 | |||||
}; | |||||
const recorder = new Recorder(parameter); | |||||
const isRecording = ref(false); | |||||
const audioUrl = ref(null); | |||||
const startRecording = async () => { | |||||
if (!isRecording.value) { | |||||
try { | |||||
await recorder.start(); | |||||
console.log("Recording started"); | |||||
isRecording.value = true; | |||||
} catch (error) { | |||||
console.error(error); | |||||
} | |||||
} | |||||
}; | |||||
const stopRecording = async () => { | |||||
if (isRecording.value) { | |||||
try { | |||||
const recording = await recorder.stop(); | |||||
audioUrl.value = URL.createObjectURL(recorder.getWAVBlob()); | |||||
isRecording.value = false; | |||||
} catch (error) { | |||||
console.error(error); | |||||
} | |||||
} | |||||
}; | |||||
const playRecording = () => { | |||||
if (audioUrl.value) { | |||||
const audio = new Audio(audioUrl.value); | |||||
audio.play(); | |||||
} | |||||
}; | |||||
const uploadAudio = () => {}; | |||||
// 上传视频功能 | |||||
const fileInput = ref(null); | |||||
const videoSrc = ref(null); | |||||
const handleFileChange = () => { | |||||
const file = fileInput.value.files[0]; | |||||
if (file) { | |||||
const videoUrl = URL.createObjectURL(file); | |||||
videoSrc.value = videoUrl; | |||||
} | |||||
}; | |||||
// card3的 | |||||
let language = ref(""); | |||||
let languageOptions = reactive([ | |||||
{ | |||||
value: "Option1", | |||||
label: "Option1", | |||||
}, | |||||
{ | |||||
value: "Option2", | |||||
label: "Option2", | |||||
}, | |||||
]); | |||||
</script> | |||||
<style lang="scss" scoped> | |||||
.page { | |||||
// height: calc(100vh - 84px); | |||||
height: 887px; | |||||
padding: 20px 0 0 40px; | |||||
background-color: #fff; | |||||
border-radius: 25px 0 0; | |||||
} | |||||
.left { | |||||
width: 70%; | |||||
height: 100%; | |||||
:deep(.el-input__wrapper) { | |||||
border: none; | |||||
border-bottom: 2px solid #000; | |||||
border-radius: 0; | |||||
box-shadow: none; | |||||
} | |||||
.el-icon.icon-edit { | |||||
width: 35px; | |||||
height: 35px; | |||||
font-size: 35px; | |||||
color: #8c939d; | |||||
text-align: center; | |||||
} | |||||
&-top { | |||||
position: relative; | |||||
width: 100%; | |||||
height: 65%; | |||||
.uploadBtn { | |||||
position: relative; | |||||
} | |||||
.bigImg { | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: space-between; | |||||
width: 80%; | |||||
height: 88%; | |||||
margin-top: 20px; | |||||
img { | |||||
max-width: 100%; | |||||
max-height: 100%; | |||||
} | |||||
} | |||||
.noImg { | |||||
position: relative; | |||||
width: 100%; | |||||
height: 100%; | |||||
border: 1px solid #000; | |||||
.trueImg { | |||||
top: 60px; | |||||
width: 100px; | |||||
height: 100px; | |||||
background-color: green; | |||||
border-radius: 50%; | |||||
} | |||||
.upload { | |||||
top: 200px; | |||||
width: 100px; | |||||
height: 100px; | |||||
font-size: 50px; | |||||
line-height: 100px; | |||||
text-align: center; | |||||
background-color: pink; | |||||
border-radius: 50%; | |||||
} | |||||
.falseImg { | |||||
top: 350px; | |||||
// background-color: #795562; | |||||
display: flex; | |||||
justify-content: space-evenly; | |||||
width: 500px; | |||||
height: 100px; | |||||
> div { | |||||
position: relative; | |||||
width: 100px; | |||||
height: 100px; | |||||
background-color: #ccc; | |||||
border-radius: 50%; | |||||
} | |||||
} | |||||
} | |||||
// 公共类名 | |||||
input { | |||||
position: absolute; | |||||
top: 0; | |||||
left: 0; | |||||
width: 100%; | |||||
height: 100%; | |||||
opacity: 0; | |||||
} | |||||
.icon { | |||||
position: absolute; | |||||
right: 2px; | |||||
width: 30px; | |||||
height: 30px; | |||||
font-size: 20px; | |||||
font-weight: 700; | |||||
line-height: 26px; | |||||
text-align: center; | |||||
border: 2px solid #000; | |||||
border-radius: 50%; | |||||
} | |||||
} | |||||
&-bottom { | |||||
width: 100%; | |||||
height: 32%; | |||||
margin-top: 10px; | |||||
.ai { | |||||
width: 100%; | |||||
.aiAvatar { | |||||
width: 100px; | |||||
height: 100px; | |||||
margin-left: 25px; | |||||
background-color: pink; | |||||
border-radius: 50%; | |||||
} | |||||
} | |||||
.faceList { | |||||
display: flex; | |||||
flex-wrap: wrap; | |||||
justify-content: flex-start; | |||||
width: 100%; | |||||
height: 130px; | |||||
.faceItem { | |||||
width: 150px; | |||||
height: 150px; | |||||
padding: 0 25px; | |||||
line-height: 30px; | |||||
text-align: center; | |||||
p { | |||||
height: 30px; | |||||
} | |||||
.img { | |||||
width: 100px; | |||||
height: 100px; | |||||
background-color: green; | |||||
border-radius: 50%; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
.right { | |||||
width: 25%; | |||||
height: 100%; | |||||
background-color: green; | |||||
:deep(.el-tabs--border-card) { | |||||
font-size: 20px; | |||||
border: none; | |||||
} | |||||
.tabs { | |||||
height: 100%; | |||||
.card2 { | |||||
padding: 10px; | |||||
> div { | |||||
position: relative; | |||||
margin-top: 10px; | |||||
width: 100%; | |||||
height: 100px; | |||||
border-radius: 10px; | |||||
background-color: red; | |||||
overflow: hidden; | |||||
transition: all 0.3s; | |||||
&:hover { | |||||
transform: translateY(-10px); | |||||
box-shadow: #00000026 2px 2px 2px 2px; | |||||
} | |||||
} | |||||
.uploadAudio { | |||||
margin-bottom: 20px; | |||||
} | |||||
.uploadVideo { | |||||
input { | |||||
position: absolute; | |||||
width: 100%; | |||||
height: 100%; | |||||
opacity: 0; | |||||
} | |||||
video { | |||||
width: 100%; | |||||
height: 100%; | |||||
} | |||||
} | |||||
} | |||||
.card3 { | |||||
.text { | |||||
padding: 10px; | |||||
border: 1px solid #000; | |||||
.el-button { | |||||
float: right; | |||||
margin-top: 10px; | |||||
} | |||||
:deep(.el-textarea__inner) { | |||||
resize: none; | |||||
box-shadow: none; | |||||
} | |||||
} | |||||
p { | |||||
margin: 10px 0; | |||||
} | |||||
> .el-button { | |||||
width: 100%; | |||||
height: 50px; | |||||
margin-top: 10px; | |||||
} | |||||
} | |||||
:deep(.el-tabs__item.is-active) { | |||||
color: #909393; | |||||
background-color: #000; | |||||
} | |||||
:deep(.el-tabs__nav) { | |||||
width: 100%; | |||||
} | |||||
:deep(.el-tabs__item) { | |||||
width: 34%; | |||||
} | |||||
:deep(.el-tabs__item:hover) { | |||||
color: #909393; | |||||
} | |||||
:deep(.el-tabs__content) { | |||||
height: calc(100% - 60px); | |||||
padding: 0; | |||||
} | |||||
} | |||||
} | |||||
</style> |
@@ -15,10 +15,15 @@ | |||||
<el-icon class="icon-edit"><i-ep-edit /></el-icon> | <el-icon class="icon-edit"><i-ep-edit /></el-icon> | ||||
</div> | </div> | ||||
<!-- 上传按钮 --> | <!-- 上传按钮 --> | ||||
<button class="uploadBtn"> | |||||
{{ $t("createVideo.reload") }} | |||||
<input type="file" @change="changeFile" /> | |||||
</button> | |||||
<div class="btnBox"> | |||||
<button class="uploadBtn"> | |||||
{{ $t("createVideo.reselect") }} | |||||
<input type="file" @change="lookImg" /> | |||||
</button> | |||||
<button class="uploadBtn" @click="changeFile"> | |||||
{{ $t("createVideo.sureUpload") }} | |||||
</button> | |||||
</div> | |||||
</el-row> | </el-row> | ||||
<!-- 中间展示大图 --> | <!-- 中间展示大图 --> | ||||
<div class="bigImg inmiddle"> | <div class="bigImg inmiddle"> | ||||
@@ -34,7 +39,17 @@ | |||||
</div> | </div> | ||||
<div class="upload inmiddle"> | <div class="upload inmiddle"> | ||||
+ | + | ||||
<input type="file" @change="changeFile" /> | |||||
<input type="file" @change="lookImg" /> | |||||
<!-- <el-upload | |||||
class="el-upload" | |||||
v-model="fileList" | |||||
multiple | |||||
:on-preview="handlePreview" | |||||
:on-remove="handleRemove" | |||||
:before-remove="beforeRemove" | |||||
:limit="3" | |||||
:on-exceed="handleExceed" | |||||
/> --> | |||||
</div> | </div> | ||||
<div class="falseImg inmiddle"> | <div class="falseImg inmiddle"> | ||||
<div><div class="icon">×</div></div> | <div><div class="icon">×</div></div> | ||||
@@ -51,10 +66,15 @@ | |||||
<div style="margin-left: 20px">Generate By Ai</div> | <div style="margin-left: 20px">Generate By Ai</div> | ||||
</div> | </div> | ||||
<div class="faceList scrolly"> | <div class="faceList scrolly"> | ||||
<div class="faceItem" v-for="item in 7" :key="item"> | |||||
<div class="img"></div> | |||||
<!-- <img src="" alt="" /> --> | |||||
<p>{{ item }}name</p> | |||||
<div | |||||
class="faceItem pointer" | |||||
@click="setImgUrl(item.material)" | |||||
v-for="item in AiFace" | |||||
:key="item.id" | |||||
> | |||||
<!-- <div class="img"></div> --> | |||||
<img :src="item.material" alt="" /> | |||||
<p>{{ item.title }}</p> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -64,19 +84,117 @@ | |||||
<el-tabs type="border-card" class="tabs"> | <el-tabs type="border-card" class="tabs"> | ||||
<!-- ----------------------------- 1 --> | <!-- ----------------------------- 1 --> | ||||
<el-tab-pane :label="$t('createVideo.typeScript')"> | <el-tab-pane :label="$t('createVideo.typeScript')"> | ||||
<div class="card1">111111111111111111</div> | |||||
<div class="card1"> | |||||
<div class="text"> | |||||
<el-input | |||||
v-model="title" | |||||
:placeholder="$t('createVideo.typeTitle')" | |||||
/> | |||||
<el-button type="primary"> | |||||
{{ $t("createVideo.confirm") }} | |||||
</el-button> | |||||
<el-input | |||||
v-model="title" | |||||
:rows="15" | |||||
type="textarea" | |||||
:placeholder="$t('createVideo.aiText')" | |||||
/> | |||||
<el-icon :size="20"> | |||||
<Edit /> | |||||
</el-icon> | |||||
</div> | |||||
<p>{{ $t("createVideo.languages") }}</p> | |||||
<el-select | |||||
v-model="language" | |||||
placeholder="Select" | |||||
style="width: 100%" | |||||
> | |||||
<el-option | |||||
v-for="item in languageOptions" | |||||
:key="item.value" | |||||
:label="item.label" | |||||
:value="item.value" | |||||
/> | |||||
</el-select> | |||||
<p>{{ $t("createVideo.voices") }}</p> | |||||
<el-select | |||||
v-model="language" | |||||
placeholder="Select" | |||||
style="width: 100%" | |||||
> | |||||
<el-option | |||||
v-for="item in languageOptions" | |||||
:key="item.value" | |||||
:label="item.label" | |||||
:value="item.value" | |||||
/> | |||||
</el-select> | |||||
<p>{{ $t("createVideo.styles") }}</p> | |||||
<el-select | |||||
v-model="language" | |||||
placeholder="Select" | |||||
style="width: 100%" | |||||
> | |||||
<el-option | |||||
v-for="item in languageOptions" | |||||
:key="item.value" | |||||
:label="item.label" | |||||
:value="item.value" | |||||
/> | |||||
</el-select> | |||||
<el-button type="primary">{{ | |||||
$t("createVideo.generateVideo") | |||||
}}</el-button> | |||||
</div> | |||||
</el-tab-pane> | </el-tab-pane> | ||||
<!-- ------------------------ 2 --> | <!-- ------------------------ 2 --> | ||||
<el-tab-pane :label="$t('createVideo.uploadAudio')"> | <el-tab-pane :label="$t('createVideo.uploadAudio')"> | ||||
<div class="card2"> | <div class="card2"> | ||||
<div class="uploadVideo"> | |||||
<!-- 录制MP3上传语音 --> | |||||
<div | |||||
class="uploadAudio" | |||||
:class="audioUrl ? 'uploadAudioAvtion' : ''" | |||||
> | |||||
<div class="btnBox"> | |||||
<button | |||||
class="pointer" | |||||
:disabled="!isRecording" | |||||
@click="startRecording" | |||||
> | |||||
开始录音 | |||||
</button> | |||||
<button | |||||
class="pointer" | |||||
@click="stopRecording" | |||||
:disabled="isRecording" | |||||
> | |||||
停止录音 | |||||
</button> | |||||
</div> | |||||
<!-- <button @click="playRecording">播放录音</button> --> | |||||
<audio v-if="audioUrl" controls :src="audioUrl"></audio> | |||||
<button v-if="audioUrl" @click="uploadAudio" class="pointer"> | |||||
上传音频 | |||||
</button> | |||||
</div> | |||||
<!-- 上传视频 --> | |||||
<div | |||||
class="uploadVideo" | |||||
style="text-align: center; line-height: 70px" | |||||
> | |||||
上传视频 | |||||
<input type="file" ref="fileInput" @change="handleFileChange" /> | <input type="file" ref="fileInput" @change="handleFileChange" /> | ||||
<video ref="videoPlayer" controls></video> | |||||
<video | |||||
v-if="videoSrc" | |||||
:src="videoSrc" | |||||
ref="videoPlayer" | |||||
controls | |||||
></video> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</el-tab-pane> | </el-tab-pane> | ||||
<!-- ----------------------- 3 --> | <!-- ----------------------- 3 --> | ||||
<el-tab-pane :label="$t('createVideo.aiScript')"> | |||||
<el-tab-pane v-if="false" :label="$t('createVideo.aiScript')"> | |||||
<div class="card3"> | <div class="card3"> | ||||
<div class="text"> | <div class="text"> | ||||
<el-input | <el-input | ||||
@@ -147,27 +265,115 @@ | |||||
<script setup> | <script setup> | ||||
import { ref } from "vue"; | import { ref } from "vue"; | ||||
import { getCurrentInstance } from "vue"; | import { getCurrentInstance } from "vue"; | ||||
import Recorder from "js-audio-recorder"; | |||||
import axios from "axios"; | |||||
import { awsImgUpload, awsMp3Upload } from "@/apis/createVideo"; | |||||
import { ElMessage } from "element-plus"; | |||||
// 获取AI头像 | |||||
const AiFace = ref([]); | |||||
function getAiFace() { | |||||
axios({ | |||||
url: "https://photo.metavatar.cc/C//api/personPhoto/list?pageNum=1&pageSize=1", | |||||
method: "get", | |||||
}).then((res) => { | |||||
AiFace.value = res.data.data.list; | |||||
console.log(res.data.data.list); | |||||
}); | |||||
} | |||||
getAiFace(); | |||||
// 标题 | // 标题 | ||||
let title = ref(""); | let title = ref(""); | ||||
// 上传图片 | // 上传图片 | ||||
let imgUrl = ref(""); | let imgUrl = ref(""); | ||||
function changeFile(e) { | |||||
const imgData = ref(""); | |||||
// 预览图片 | |||||
function lookImg(e) { | |||||
imgUrl.value = URL.createObjectURL(e.target.files[0]); | imgUrl.value = URL.createObjectURL(e.target.files[0]); | ||||
imgData.value = e.target.files[0]; | |||||
} | |||||
// 上传图片 | |||||
async function changeFile() { | |||||
imgUrl.value = URL.createObjectURL(imgData.value); | |||||
let formData = new FormData(); | |||||
const timestamp = Date.now(); | |||||
formData.append("file", imgData.value, timestamp + ".png"); | |||||
try { | |||||
const res = await awsImgUpload(formData); | |||||
ElMessage.success("上传成功!"); | |||||
} catch (error) { | |||||
ElMessage.error(error); | |||||
} | |||||
} | } | ||||
// $t在script中的使用 | |||||
// 点击aifece | |||||
function setImgUrl(url) { | |||||
imgUrl.value = url; | |||||
} | |||||
const { | |||||
appContext: { | |||||
config: { globalProperties }, | |||||
}, | |||||
} = getCurrentInstance(); | |||||
// 录制MP3上传语音 | |||||
const parameter = { | |||||
sampleBits: 16, // 采样位数,支持 8 或 16,默认是16 | |||||
sampleRate: 8000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000 | |||||
numChannels: 1, // 声道,支持 1 或 2, 默认是1 | |||||
}; | |||||
const recorder = new Recorder(parameter); | |||||
const isRecording = ref(true); | |||||
const audioUrl = ref(null); | |||||
const audioData = ref(""); | |||||
const startRecording = async () => { | |||||
try { | |||||
await recorder.start(); | |||||
console.log("Recording started"); | |||||
isRecording.value = false; | |||||
} catch (error) { | |||||
console.error(error); | |||||
} | |||||
}; | |||||
const stopRecording = async () => { | |||||
try { | |||||
const recording = await recorder.stop(); | |||||
audioUrl.value = URL.createObjectURL(recorder.getWAVBlob()); | |||||
audioData.value = recorder.getWAVBlob(); | |||||
isRecording.value = true; | |||||
} catch (error) { | |||||
console.error(error); | |||||
} | |||||
}; | |||||
const string = "createVideo.typeScript"; | |||||
const t = globalProperties.$t(string); | |||||
const playRecording = () => { | |||||
if (audioUrl.value) { | |||||
const audio = new Audio(audioUrl.value); | |||||
audio.play(); | |||||
} | |||||
}; | |||||
async function uploadAudio(params) { | |||||
let formData = new FormData(); | |||||
const timestamp = Date.now(); | |||||
formData.append("file", audioData.value, timestamp + ".mp3"); | |||||
try { | |||||
const res = await awsMp3Upload(formData); | |||||
ElMessage.success("上传成功!"); | |||||
} catch (error) { | |||||
ElMessage.error(error); | |||||
} | |||||
} | |||||
// 上传视频功能 | |||||
const fileInput = ref(null); | |||||
const videoSrc = ref(null); | |||||
const handleFileChange = () => { | |||||
const file = fileInput.value.files[0]; | |||||
if (file) { | |||||
const videoUrl = URL.createObjectURL(file); | |||||
videoSrc.value = videoUrl; | |||||
} | |||||
}; | |||||
// card3的 | // card3的 | ||||
let language = reactive(""); | |||||
let language = ref(""); | |||||
let languageOptions = reactive([ | let languageOptions = reactive([ | ||||
{ | { | ||||
@@ -179,6 +385,16 @@ let languageOptions = reactive([ | |||||
label: "Option2", | label: "Option2", | ||||
}, | }, | ||||
]); | ]); | ||||
// $t在script中的使用 | |||||
const { | |||||
appContext: { | |||||
config: { globalProperties }, | |||||
}, | |||||
} = getCurrentInstance(); | |||||
const string = "createVideo.typeScript"; | |||||
const t = globalProperties.$t(string); | |||||
</script> | </script> | ||||
<style lang="scss" scoped> | <style lang="scss" scoped> | ||||
@@ -216,6 +432,14 @@ let languageOptions = reactive([ | |||||
.uploadBtn { | .uploadBtn { | ||||
position: relative; | position: relative; | ||||
padding: 5px 10px; | |||||
margin-left: 10px; | |||||
border-radius: 5px; | |||||
transition: all 0.3s; | |||||
&:hover { | |||||
background-color: #000; | |||||
color: #fff; | |||||
} | |||||
} | } | ||||
.bigImg { | .bigImg { | ||||
@@ -245,7 +469,14 @@ let languageOptions = reactive([ | |||||
background-color: green; | background-color: green; | ||||
border-radius: 50%; | border-radius: 50%; | ||||
} | } | ||||
.el-upload { | |||||
position: absolute; | |||||
top: 0; | |||||
left: 0; | |||||
width: 100%; | |||||
height: 100%; | |||||
opacity: 0; | |||||
} | |||||
.upload { | .upload { | ||||
top: 200px; | top: 200px; | ||||
width: 100px; | width: 100px; | ||||
@@ -333,11 +564,13 @@ let languageOptions = reactive([ | |||||
p { | p { | ||||
height: 30px; | height: 30px; | ||||
} | } | ||||
img { | |||||
width: 100%; | |||||
} | |||||
.img { | .img { | ||||
width: 100px; | width: 100px; | ||||
height: 100px; | height: 100px; | ||||
background-color: green; | |||||
// background-color: green; | |||||
border-radius: 50%; | border-radius: 50%; | ||||
} | } | ||||
} | } | ||||
@@ -357,21 +590,90 @@ let languageOptions = reactive([ | |||||
.tabs { | .tabs { | ||||
height: 100%; | height: 100%; | ||||
.card1 { | |||||
.text { | |||||
padding: 10px; | |||||
border: 1px solid #000; | |||||
.el-button { | |||||
float: right; | |||||
margin-top: 10px; | |||||
} | |||||
:deep(.el-textarea__inner) { | |||||
resize: none; | |||||
box-shadow: none; | |||||
} | |||||
} | |||||
p { | |||||
margin: 10px 0; | |||||
} | |||||
> .el-button { | |||||
width: 100%; | |||||
height: 50px; | |||||
margin-top: 10px; | |||||
} | |||||
} | |||||
.card2 { | .card2 { | ||||
padding: 10px; | padding: 10px; | ||||
.uploadVideo { | |||||
> div { | |||||
position: relative; | position: relative; | ||||
margin-top: 10px; | margin-top: 10px; | ||||
width: 100%; | width: 100%; | ||||
height: 100px; | |||||
border: #000 1px solid; | |||||
border-radius: 10px; | border-radius: 10px; | ||||
background-color: red; | |||||
overflow: hidden; | |||||
transition: all 0.3s; | |||||
box-shadow: #00000026 2px 2px 2px 2px; | |||||
&:hover { | |||||
transform: translateY(-10px); | |||||
} | |||||
} | |||||
.uploadAudio { | |||||
padding: 20px; | |||||
margin-bottom: 20px; | |||||
height: 70px; | |||||
.btnBox { | |||||
display: flex; | |||||
justify-content: space-between; | |||||
margin-bottom: 10px; | |||||
} | |||||
audio { | |||||
height: 50px; | |||||
width: 100%; | |||||
} | |||||
button { | |||||
border-radius: 10px; | |||||
height: 30px; | |||||
line-height: 22px; | |||||
padding: 5px 10px; | |||||
background-color: #fff; | |||||
transition: all 0.3s; | |||||
&:hover { | |||||
background-color: #000; | |||||
color: #fff; | |||||
} | |||||
} | |||||
} | |||||
.uploadAudioAvtion { | |||||
height: 170px; | |||||
} | |||||
.uploadVideo { | |||||
height: 100px; | |||||
input { | input { | ||||
position: absolute; | position: absolute; | ||||
width: 100%; | width: 100%; | ||||
height: 100%; | height: 100%; | ||||
opacity: 0; | opacity: 0; | ||||
} | } | ||||
video { | |||||
width: 100%; | |||||
height: 100%; | |||||
} | |||||
} | } | ||||
} | } | ||||
.card3 { | .card3 { | ||||
@@ -96,5 +96,8 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => { | |||||
symbolId: "icon-[dir]-[name]", | symbolId: "icon-[dir]-[name]", | ||||
}), | }), | ||||
], | ], | ||||
define: { | |||||
global:{} | |||||
} | |||||
}; | }; | ||||
}); | }); |
@@ -3722,6 +3722,11 @@ jiti@^1.16.0, jiti@^1.18.2: | |||||
resolved "https://registry.npmmirror.com/jiti/-/jiti-1.18.2.tgz" | resolved "https://registry.npmmirror.com/jiti/-/jiti-1.18.2.tgz" | ||||
integrity sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg== | integrity sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg== | ||||
js-audio-recorder@^1.0.7: | |||||
version "1.0.7" | |||||
resolved "https://registry.yarnpkg.com/js-audio-recorder/-/js-audio-recorder-1.0.7.tgz#01501cb9eb143d57994b42bd5ae97974fda18908" | |||||
integrity sha512-JiDODCElVHGrFyjGYwYyNi7zCbKk9va9C77w+zCPMmi4C6ix7zsX2h3ddHugmo4dOTOTCym9++b/wVW9nC0IaA== | |||||
js-base64@^2.1.9: | js-base64@^2.1.9: | ||||
version "2.6.4" | version "2.6.4" | ||||
resolved "https://registry.npmmirror.com/js-base64/-/js-base64-2.6.4.tgz" | resolved "https://registry.npmmirror.com/js-base64/-/js-base64-2.6.4.tgz" | ||||