1171 lines
43 KiB
HTML
1171 lines
43 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||
<meta name="baidu-site-verification" content="codeva-bhaFvYlfgd" />
|
||
<meta
|
||
name="keywords"
|
||
content="deepl、deepl翻译、百度翻译、谷歌翻译、腾讯翻译君"
|
||
/>
|
||
<meta
|
||
name="description"
|
||
content="翻译之家提供即时免费的中文、英语、日语、韩语、法语、德语、俄语、西班牙语、葡萄牙语、越南语、印尼语、意大利语、荷兰语、泰语全文翻译等服务。"
|
||
/>
|
||
<meta name="baidu-site-verification" content="codeva-mXz3BqI9VN" />
|
||
<link rel="stylesheet" href="static/css/public.css" />
|
||
<link rel="stylesheet" href="static/css/layui.css" />
|
||
<script src="static/js/jquery-1.11.0.min.js"></script>
|
||
<script src="static/js/axios.min.js"></script>
|
||
<script src="static/js/countUp.min.js"></script>
|
||
<script src="static/js/vue.js"></script>
|
||
<script src="static/js/lodash.min.js"></script>
|
||
<script src="static/js/layui.all.js"></script>
|
||
<script src="static/js/common.js"></script>
|
||
<title>翻译之家</title>
|
||
<script>
|
||
var _hmt = _hmt || [];
|
||
(function () {
|
||
var hm = document.createElement("script");
|
||
hm.src = "https://hm.baidu.com/hm.js?c4e0dd6add63dd71fa52870120ca22cf";
|
||
var s = document.getElementsByTagName("script")[0];
|
||
s.parentNode.insertBefore(hm, s);
|
||
})();
|
||
</script>
|
||
</head>
|
||
<body>
|
||
<div class="public-header normal" id="main">
|
||
<div class="container clearfix">
|
||
<div class="left">
|
||
<img class="logo default" src="static/picture/logo.png" alt="" /><img
|
||
class="logo1 default"
|
||
src="static/picture/logo1.png"
|
||
alt=""
|
||
/><img class="logo light" src="static/picture/logow.png" alt="" /><img
|
||
class="logo1 light"
|
||
src="static/picture/logo1w.png"
|
||
alt=""
|
||
/>
|
||
<div class="nav">
|
||
<a class="" href="index.html"><span>首页</span></a
|
||
><a class="" href="onlineTranslation.html"><span>文本翻译</span></a
|
||
><a class="active" href=""><span>语音翻译</span></a
|
||
><a class="" href="api.html"><span>API文档</span></a
|
||
><a class="" href="commonProblems.html"><span>常见问题</span></a>
|
||
</div>
|
||
</div>
|
||
<div v-if="userInfo!=undefined&&userInfo.userId>0" class="right">
|
||
<a href="userInfo.html" class="console">控制台</a>
|
||
<div class="user-info">
|
||
<div class="phone">{{userInfo.name}}</div>
|
||
<i></i>
|
||
<div class="log-out" @click="logOut">退出</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="right">
|
||
<a class="unlogin" href="login.html">登录</a>
|
||
</div>
|
||
</div>
|
||
<div class="bg"></div>
|
||
</div>
|
||
<script type="text/javascript">
|
||
var vm = new Vue({
|
||
el: "#main",
|
||
data() {
|
||
return {
|
||
userInfo: {},
|
||
token: "",
|
||
};
|
||
},
|
||
created() {},
|
||
mounted() {
|
||
let token = localStorage.getItem("token");
|
||
if (token) {
|
||
this.token = token;
|
||
this.getUserInfo(token);
|
||
} else {
|
||
// location.href = "login.html";
|
||
}
|
||
},
|
||
methods: {
|
||
logOut() {
|
||
axios
|
||
.post("/logout", {
|
||
headers: {
|
||
Authorization: `Bearer ${this.token}`,
|
||
},
|
||
})
|
||
.then((response) => {
|
||
localStorage.removeItem("token");
|
||
location.reload();
|
||
});
|
||
},
|
||
|
||
getUserInfo(token) {
|
||
let that = this;
|
||
axios
|
||
.get("/getinfo", {
|
||
headers: {
|
||
Authorization: `Bearer ${this.token}`,
|
||
},
|
||
})
|
||
.then((response) => {
|
||
if (response.data.code === 200) {
|
||
this.userInfo = response.data.data;
|
||
console.log("userinfo", this.userInfo);
|
||
} else {
|
||
localStorage.removeItem("token");
|
||
location.href = "login.html";
|
||
}
|
||
});
|
||
},
|
||
},
|
||
});
|
||
</script>
|
||
<div class="page-translation" id="app">
|
||
<div class="w1200">
|
||
<div class="translation-engine">
|
||
<div
|
||
v-for="item in engineType"
|
||
:key="item.text"
|
||
class="voiceitem"
|
||
:class="{ on: engine.text == item.text }"
|
||
@click="onSelectEngine(item)"
|
||
>
|
||
<img
|
||
:src="engine.text == item.text ? item.icon :item.icon1 "
|
||
alt=""
|
||
/><span class="engine-name">{{ item.text }}</span>
|
||
</div>
|
||
</div>
|
||
<div
|
||
class="translation-row"
|
||
style="
|
||
padding-top: 40px;
|
||
padding-left: 62px;
|
||
border: 1px solid #fff;
|
||
padding-bottom: 40px;
|
||
display: flex;
|
||
"
|
||
>
|
||
<div class="left-side">
|
||
<div class="title">
|
||
{{ engine.text === '语音识别' ? '语音转文字,我们帮您一键识别!' :
|
||
engine.text === '语音合成' ? '录入文字,我们帮你翻译并朗读!' :
|
||
'说出中文,我们帮你语音翻译!' }}
|
||
</div>
|
||
<div class="subtitle">
|
||
{{ engine.text === '语音识别' ?
|
||
'识别结果(单次识别最长支持1分钟)' : engine.text === '语音合成' ?
|
||
'文字录入(单次合成最长支持100字符)' :
|
||
'识别结果(单次识别最长支持1分钟)' }}
|
||
</div>
|
||
<textarea
|
||
class="transcription-box"
|
||
:placeholder="engine.text === '语音识别' ? '这里实时显示语音识别结果' :
|
||
engine.text === '语音合成' ? '请输入要合成语音的文字内容' :
|
||
'这里实时显示翻译结果'"
|
||
disabled
|
||
></textarea>
|
||
<div class="action-button">
|
||
<div class="sound-wave-container" v-show="soundShow">
|
||
<!-- First sound wave -->
|
||
<div class="sound-wave">
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
</div>
|
||
<!-- Second sound wave -->
|
||
<div class="sound-wave">
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
</div>
|
||
<!-- Third sound wave -->
|
||
<div class="sound-wave">
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
</div>
|
||
<!-- Fourth sound wave -->
|
||
<div class="sound-wave">
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
</div>
|
||
<!-- Fifth sound wave -->
|
||
<div class="sound-wave">
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
<div class="sound-bar"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="synthesis-options">
|
||
<div class="synthesis-option" @click="start" v-show="!soundShow">
|
||
<img
|
||
src="static/picture/icon1.png"
|
||
alt="合成图标"
|
||
class="option-icon"
|
||
/><span
|
||
>{{ engine.text === '语音识别' ? '开始识别' : engine.text ===
|
||
'语音合成' ? '开始合成' : '开始翻译' }}</span
|
||
>
|
||
</div>
|
||
<div
|
||
class="synthesis-option"
|
||
@click="stop"
|
||
v-show="soundShow"
|
||
style="background-image: url('static/image/stopbj.png')"
|
||
>
|
||
<img
|
||
src="static/picture/icon2.png"
|
||
alt="合成图标"
|
||
class="option-icon"
|
||
/><span
|
||
>{{ engine.text === '语音识别' ? '结束识别' : engine.text ===
|
||
'语音合成' ? '结束合成' : '结束翻译' }}</span
|
||
>
|
||
</div>
|
||
</div>
|
||
<div class="balance-warning" v-show="errmsg!=''">
|
||
<div class="balance-warnings">
|
||
<img
|
||
src="static/picture/warning-icon.png"
|
||
alt="警告"
|
||
class="warning-icon"
|
||
/><span v-show="errmsg == '余额'">
|
||
余额不足,请前往控制台充值</span
|
||
><span v-show="errmsg == '登录'"> 请先登录</span>
|
||
</div>
|
||
</div>
|
||
<div style="height: 30px"></div>
|
||
</div>
|
||
<div class="right-side">
|
||
<div class="voice-input-title">
|
||
{{ engine.text === '语音识别' ? '语音录入' : engine.text ===
|
||
'语音合成' ? '语音配置' : '语音配置' }}
|
||
</div>
|
||
<div
|
||
class="language-row"
|
||
v-show="engine.text === '语音识别'"
|
||
style="margin-top: 48px"
|
||
>
|
||
<div class="original-language-text">原始语种:</div>
|
||
<div class="language-selector-box">
|
||
<div
|
||
class="translation-language"
|
||
style="padding-left: 0; border-bottom: none; width: 100%"
|
||
>
|
||
<div
|
||
class="source language-selector"
|
||
@click="onShowLanguage('sourceLanguage')"
|
||
style="
|
||
width: 100%;
|
||
margin-right: 0;
|
||
justify-content: space-between;
|
||
"
|
||
>
|
||
{{ sourceLanguage.name }}
|
||
<div
|
||
class="icon select-icons"
|
||
:class="{ rotate: languageShow }"
|
||
></div>
|
||
</div>
|
||
<div
|
||
class="language-row dropdown-menu"
|
||
:class="{ show: languageShow }"
|
||
:style="{ left: '-10px', top: '50px', width: '280px' }"
|
||
>
|
||
<div class="search">
|
||
<i class="icon"></i
|
||
><input
|
||
type="text"
|
||
class="search-input"
|
||
v-model="serachKeywords"
|
||
placeholder="搜索你想要的"
|
||
/>
|
||
</div>
|
||
<div class="row">
|
||
<div
|
||
class="cols"
|
||
v-for="(k, v) in languageCurrent"
|
||
:key="v"
|
||
:value="v"
|
||
@click="onSelectLanguage(v)"
|
||
:class="{ on: sourceLanguage.key == v}"
|
||
style="height: 34px; overflow-y: hidden"
|
||
>
|
||
<div class="cols"><span>{{ k }}</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div
|
||
v-show="engine.text === '语音翻译' || engine.text === '语音合成' "
|
||
>
|
||
<div class="language-row" style="margin-top: 30px">
|
||
<div class="target-language-text">翻译类型:</div>
|
||
<div class="language-selector-box">
|
||
<div
|
||
class="translation-language"
|
||
style="padding-left: 0; border-bottom: none; width: 100%"
|
||
>
|
||
<div
|
||
class="target language-selector"
|
||
@click="onShowLanguage('transTypeValue')"
|
||
style="
|
||
width: 100%;
|
||
margin-right: 0;
|
||
justify-content: space-between;
|
||
font-size: 16px;
|
||
"
|
||
>
|
||
{{ transTypeValue.value }}
|
||
<div
|
||
class="icon select-icons"
|
||
:class="{ rotate: transShow }"
|
||
></div>
|
||
</div>
|
||
<transtype-dropdown
|
||
class="dropdown-menu"
|
||
:show="transShow"
|
||
:left="languageRowLeft"
|
||
:top="languageRowTop"
|
||
width="280px"
|
||
:current-key="transTypeValue.key"
|
||
:language-list="transType"
|
||
@select="onSelectTransType"
|
||
></transtype-dropdown>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div
|
||
class="language-row"
|
||
v-show="engine.text === '语音翻译' && transTypeValue.key == '1' "
|
||
style="margin-top: 30px"
|
||
>
|
||
<div class="original-language-text">原始语种:</div>
|
||
<div class="language-selector-box">
|
||
<div
|
||
class="translation-language"
|
||
style="padding-left: 0; border-bottom: none; width: 100%"
|
||
>
|
||
<div
|
||
class="source"
|
||
@click="onShowLanguage('sourceLanguage')"
|
||
style="
|
||
width: 100%;
|
||
margin-right: 0;
|
||
justify-content: space-between;
|
||
font-size: 16px;
|
||
"
|
||
>
|
||
{{ sourceLanguage.name }}
|
||
<div
|
||
class="icon select-icons"
|
||
:class="{ rotate: languageShow }"
|
||
></div>
|
||
</div>
|
||
<language-dropdown
|
||
class="dropdown-menu"
|
||
:show="languageShow"
|
||
:left="languageRowLeft"
|
||
:top="languageRowTop"
|
||
width="280px"
|
||
:current-key="sourceLanguage.key"
|
||
:language-list="languageCurrent"
|
||
@select="onSelectLanguage"
|
||
></language-dropdown>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div
|
||
class="language-row"
|
||
style="margin-top: 30px"
|
||
v-show="engine.text === '语音翻译' && transTypeValue.key == '1' "
|
||
>
|
||
<div class="target-language-text">目标语种:</div>
|
||
<div class="language-selector-box">
|
||
<div
|
||
class="translation-language"
|
||
style="padding-left: 0; border-bottom: none; width: 100%"
|
||
>
|
||
<div
|
||
class="target"
|
||
@click="onShowLanguage('targetLanguage')"
|
||
style="
|
||
width: 100%;
|
||
margin-right: 0;
|
||
justify-content: space-between;
|
||
font-size: 16px;
|
||
"
|
||
>
|
||
{{ targetLanguage.name }}
|
||
<div
|
||
class="icon select-icons"
|
||
:class="{ rotate: languageShowTarget }"
|
||
></div>
|
||
</div>
|
||
<language-dropdown
|
||
class="dropdown-menu"
|
||
:show="languageShowTarget"
|
||
:left="languageRowLeft"
|
||
:top="languageRowTop"
|
||
width="280px"
|
||
:current-key="targetLanguage.key"
|
||
:language-list="languageCurrent"
|
||
@select="onSelectLanguage"
|
||
></language-dropdown>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div
|
||
class="language-row"
|
||
style="margin-top: 30px"
|
||
v-show="engine.text === '语音翻译' && transTypeValue.key == '2' "
|
||
>
|
||
<div class="language-selector-box">
|
||
<div
|
||
class="translation-language"
|
||
style="padding-left: 0; border-bottom: none; width: 100%"
|
||
>
|
||
<div
|
||
class="source"
|
||
@click="onShowLanguage('sourceLanguage')"
|
||
style="
|
||
width: 100%;
|
||
margin-right: 0;
|
||
justify-content: space-between;
|
||
font-size: 16px;
|
||
"
|
||
>
|
||
<div
|
||
style="
|
||
max-width: 143px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
"
|
||
>
|
||
{{ sourceLanguage.name }}
|
||
</div>
|
||
<div
|
||
class="icon select-icons"
|
||
:class="{ rotate: languageShow }"
|
||
></div>
|
||
</div>
|
||
<language-dropdown
|
||
class="dropdown-menu"
|
||
:show="languageShow"
|
||
:left="languageRowLeft"
|
||
:top="languageRowTop"
|
||
:current-key="sourceLanguage.key"
|
||
:language-list="languageCurrent"
|
||
width="200px"
|
||
@select="onSelectLanguage"
|
||
></language-dropdown>
|
||
</div>
|
||
</div>
|
||
<div
|
||
style="
|
||
cursor: pointer;
|
||
background: url(static/image/p14.png) no-repeat center;
|
||
background-size: 19px;
|
||
width: 40px;
|
||
height: 19px;
|
||
"
|
||
@click="onSwitch"
|
||
></div>
|
||
<!-- 目标 -->
|
||
<div class="language-selector-box">
|
||
<div
|
||
class="translation-language"
|
||
style="padding-left: 0; border-bottom: none; width: 100%"
|
||
>
|
||
<div
|
||
class="target"
|
||
@click="onShowLanguage('targetLanguage')"
|
||
style="
|
||
width: 100%;
|
||
margin-right: 0;
|
||
justify-content: space-between;
|
||
font-size: 16px;
|
||
"
|
||
>
|
||
<div
|
||
style="
|
||
max-width: 143px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
"
|
||
>
|
||
{{ targetLanguage.name }}
|
||
</div>
|
||
<div
|
||
class="icon select-icons"
|
||
:class="{ rotate: languageShowTarget }"
|
||
></div>
|
||
</div>
|
||
<language-dropdown
|
||
class="dropdown-menu"
|
||
:show="languageShowTarget"
|
||
:left="languageRowLeft"
|
||
:top="languageRowTop"
|
||
width="200px"
|
||
:current-key="targetLanguage.key"
|
||
:language-list="languageCurrent"
|
||
@select="onSelectLanguage"
|
||
></language-dropdown>
|
||
</div>
|
||
</div>
|
||
<!-- 目标 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script type="text/x-template" id="transtype-dropdown">
|
||
<div class="language-row" :class="{ show: show }" :style="{ left: '-10px', top: '50px', width: '280px' }" style="height: auto;" ><!-- <div class="search"><i class="icon"></i><input
|
||
type="text"
|
||
class="search-input"
|
||
v-model="search"
|
||
placeholder="搜索你想要的"
|
||
/></div> --><div class="row cols" v-for="item in filteredOptions"
|
||
:key="item.key" @click="select(item)"
|
||
:class="{ on: currentKey === item.key }"><div
|
||
class="cols"
|
||
><span>{{ item.value }}</span></div></div></div>
|
||
</script>
|
||
<script>
|
||
Vue.component("transtype-dropdown", {
|
||
name: "transtype-dropdown",
|
||
template: "#transtype-dropdown",
|
||
props: {
|
||
show: Boolean,
|
||
left: String,
|
||
top: String,
|
||
width: {
|
||
type: String,
|
||
default: "280px",
|
||
},
|
||
currentKey: String,
|
||
languageList: {
|
||
type: Array,
|
||
default: () => [],
|
||
},
|
||
},
|
||
data() {
|
||
return {
|
||
search: "",
|
||
};
|
||
},
|
||
computed: {
|
||
filteredOptions() {
|
||
if (!this.search) return this.languageList;
|
||
return this.languageList.filter((item) =>
|
||
item.value.includes(this.search)
|
||
);
|
||
},
|
||
},
|
||
methods: {
|
||
select(key) {
|
||
this.$emit("update:modelValue", key);
|
||
this.$emit("select", key);
|
||
},
|
||
},
|
||
});
|
||
</script>
|
||
<script type="text/x-template" id="language-dropdown">
|
||
<div class="language-row" :class="{ show: show }" :style="{ left, top, width }" v-if="show"><div class="search"><i class="icon"></i><input
|
||
type="text"
|
||
class="search-input"
|
||
v-model="search"
|
||
placeholder="搜索你想要的"
|
||
/></div><div class="row"><div class="cols" v-for="(k, v) in filteredLanguages"
|
||
:key="v" @click="select(v)"
|
||
:class="{ on: currentKey === v }"
|
||
style="height: 34px; overflow-y: hidden;"><div class="cols"><span>{{ k }}</span></div></div></div></div>
|
||
</script>
|
||
<script>
|
||
Vue.component("language-dropdown", {
|
||
template: "#language-dropdown",
|
||
props: {
|
||
show: Boolean,
|
||
left: String,
|
||
top: String,
|
||
width: {
|
||
type: String,
|
||
default: "280px",
|
||
},
|
||
currentKey: String,
|
||
languageList: Object,
|
||
},
|
||
data() {
|
||
return {
|
||
search: "",
|
||
};
|
||
},
|
||
computed: {
|
||
filteredLanguages() {
|
||
if (!this.search) return this.languageList;
|
||
const result = {};
|
||
for (const key in this.languageList) {
|
||
if (this.languageList[key].includes(this.search)) {
|
||
result[key] = this.languageList[key];
|
||
}
|
||
}
|
||
return result;
|
||
},
|
||
},
|
||
methods: {
|
||
select(key) {
|
||
this.$emit("select", key);
|
||
},
|
||
},
|
||
});
|
||
</script>
|
||
<script type="text/javascript">
|
||
const CancelToken = axios.CancelToken;
|
||
let source = CancelToken.source();
|
||
new Vue({
|
||
el: "#app",
|
||
data() {
|
||
return {
|
||
soundShow: false,
|
||
engine: {},
|
||
transType: [
|
||
{ key: "1", value: "指定翻译" },
|
||
{ key: "2", value: "双向翻译" },
|
||
],
|
||
engineType: [
|
||
{
|
||
key: "baidu",
|
||
value: 0,
|
||
icon: "/static/translate/img/identify.png",
|
||
icon1: "/static/translate/img/identify1.png",
|
||
text: "语音识别",
|
||
},
|
||
// {
|
||
// key: 'baidu',
|
||
// value: 1,
|
||
// icon: '/static/translate/img/synthetic.png',
|
||
// icon1: '/static/translate/img/synthetic1.png',
|
||
// text:'语音合成',
|
||
// },
|
||
{
|
||
key: "baidu",
|
||
value: 2,
|
||
icon: "/static/translate/img/translation.png",
|
||
icon1: "/static/translate/img/translation1.png",
|
||
text: "语音翻译",
|
||
},
|
||
],
|
||
sourceLanguage: {
|
||
key: "zh-cn",
|
||
name: "中文(普通话,简体)",
|
||
},
|
||
targetLanguage: {
|
||
key: "en-US",
|
||
name: "英语(美国)",
|
||
},
|
||
transTypeValue: {
|
||
key: "1",
|
||
value: "指定翻译",
|
||
},
|
||
sourceText: "",
|
||
targetText: "",
|
||
instance: {},
|
||
serachKeywords: "",
|
||
languageShow: false,
|
||
languageShowTarget: false,
|
||
transShow: false,
|
||
languageRowLeft: "-10px",
|
||
languageRowTop: "50px",
|
||
languageTotalData: {},
|
||
isLoading: false,
|
||
selectedGender: "male",
|
||
speedValue: 5,
|
||
pitchValue: 5,
|
||
mediaRecorder: null,
|
||
audioContext: null,
|
||
processor: null,
|
||
source: null,
|
||
socket: null,
|
||
preSocket: null,
|
||
autoCloseTimer: null,
|
||
stream: null,
|
||
errmsg: "",
|
||
token: "",
|
||
sampleRate: "16000",
|
||
};
|
||
},
|
||
async created() {
|
||
this.engine = this.engineType[0];
|
||
this.getLanguage();
|
||
this.getuserinfo();
|
||
layui.use("layer", function () {
|
||
window.layer = layui.layer;
|
||
});
|
||
const tempCtx = new (window.AudioContext ||
|
||
window.webkitAudioContext)();
|
||
const sampleRate = tempCtx.sampleRate;
|
||
this.sampleRate = sampleRate;
|
||
await tempCtx.close();
|
||
},
|
||
beforeUnmount() {
|
||
this.stop();
|
||
},
|
||
mounted() {
|
||
document.addEventListener("click", this.handleOutsideClick);
|
||
},
|
||
beforeDestroy() {
|
||
document.removeEventListener("click", this.handleOutsideClick);
|
||
},
|
||
watch: {},
|
||
computed: {
|
||
languageCurrent() {
|
||
const current = this.languageTotalData;
|
||
const copyCurrent = {};
|
||
if (this.serachKeywords) {
|
||
Object.keys(current).forEach((key) => {
|
||
// console.log('current[key]', current[key])
|
||
if (current[key].indexOf(this.serachKeywords) > -1) {
|
||
copyCurrent[key] = current[key];
|
||
}
|
||
});
|
||
return copyCurrent;
|
||
}
|
||
return this.languageTotalData;
|
||
},
|
||
},
|
||
methods: {
|
||
handleOutsideClick(e) {
|
||
const dropdownButtons = document.querySelectorAll(
|
||
".translation-language"
|
||
);
|
||
const dropdownMenus = document.querySelectorAll(".dropdown-menu");
|
||
|
||
let clickedInside = false;
|
||
|
||
dropdownButtons.forEach((button) => {
|
||
if (button.contains(e.target)) {
|
||
clickedInside = true;
|
||
}
|
||
});
|
||
|
||
dropdownMenus.forEach((menu) => {
|
||
if (menu.contains(e.target)) {
|
||
clickedInside = true;
|
||
}
|
||
});
|
||
|
||
if (!clickedInside) {
|
||
this.languageShow = false;
|
||
this.languageShowTarget = false;
|
||
this.transShow = false;
|
||
}
|
||
},
|
||
getuserinfo() {
|
||
this.errmsg = "";
|
||
axios.get("/api/voice/getAmount").then((res) => {
|
||
if (res.data.code == 1) {
|
||
let result = res.data.data;
|
||
this.token = result.token;
|
||
|
||
if (
|
||
Number(result.total_amount) - Number(result.use_amount) <
|
||
0
|
||
) {
|
||
this.errmsg = "余额";
|
||
}
|
||
} else {
|
||
this.errmsg = "登录";
|
||
layer.msg(res.data.info);
|
||
}
|
||
});
|
||
},
|
||
convertFloat32ToInt16(buffer) {
|
||
let l = buffer.length;
|
||
const buf = new Int16Array(l);
|
||
while (l--) {
|
||
buf[l] = Math.min(1, buffer[l]) * 0x7fff;
|
||
}
|
||
return buf.buffer;
|
||
},
|
||
|
||
onSocketOpen() {
|
||
console.log("WebSocket已连接");
|
||
this.soundShow = true;
|
||
this.autoCloseTimer = setTimeout(() => {
|
||
this.stop();
|
||
}, 60000);
|
||
},
|
||
async start() {
|
||
if (this.errmsg != "") {
|
||
return;
|
||
}
|
||
try {
|
||
document.querySelector(".transcription-box").value = "";
|
||
|
||
this.audioContext = new (window.AudioContext ||
|
||
window.webkitAudioContext)({ sampleRate: this.sampleRate });
|
||
const sampleRate = this.audioContext.sampleRate;
|
||
this.sampleRate = sampleRate;
|
||
let url = "";
|
||
if (this.engine.text === "语音识别") {
|
||
url = `wss://wss.trans-home.com/speech/ws?token=${this.token}&language=${this.sourceLanguage.key}&sampleRate=${sampleRate}`;
|
||
} else {
|
||
url =
|
||
this.transTypeValue.key == "1"
|
||
? `wss://wss.trans-home.com/speech/ws?token=${this.token}&language=${this.sourceLanguage.key}&transLanguage=${this.targetLanguage.key}&transType=1&sampleRate=${sampleRate}`
|
||
: `wss://wss.trans-home.com/speech/ws?token=${this.token}&language=${this.sourceLanguage.key},${this.targetLanguage.key}&transType=2&sampleRate=${sampleRate}`;
|
||
}
|
||
|
||
this.socket = new WebSocket(url);
|
||
this.socket.onopen = () => {
|
||
this.onSocketOpen();
|
||
};
|
||
|
||
window.finalResult = "";
|
||
this.socket.onmessage = (event) => {
|
||
try {
|
||
if (event.data == "recharge") {
|
||
this.stop();
|
||
return;
|
||
}
|
||
|
||
const msg = JSON.parse(event.data);
|
||
console.log("收到消息:", msg);
|
||
|
||
const outputBox =
|
||
document.querySelector(".transcription-box");
|
||
if (!outputBox) return;
|
||
|
||
let tempResult = "";
|
||
|
||
if (this.engine.text === "语音识别") {
|
||
if (msg.status == "recognizing") {
|
||
tempResult = msg.text;
|
||
outputBox.value =
|
||
finalResult +
|
||
(finalResult && tempResult ? " " : "") +
|
||
tempResult;
|
||
} else if (msg.status === "recognized") {
|
||
finalResult =
|
||
finalResult + (finalResult ? " " : "") + msg.text; // 修正这里
|
||
tempResult = "";
|
||
outputBox.value = finalResult;
|
||
}
|
||
} else {
|
||
if (msg.status == "recognizing") {
|
||
tempResult =
|
||
this.transTypeValue.key == "1"
|
||
? Object.values(msg.target)[0] || ""
|
||
: this.getTranslatedText(msg) || "";
|
||
outputBox.value =
|
||
finalResult +
|
||
(finalResult && tempResult ? " " : "") +
|
||
tempResult;
|
||
} else if (msg.status === "recognized") {
|
||
let val =
|
||
this.transTypeValue.key == "1"
|
||
? Object.values(msg.target)[0] || ""
|
||
: this.getTranslatedText(msg) || "";
|
||
finalResult =
|
||
finalResult + (finalResult ? " " : "") + val; // 修正这里
|
||
tempResult = "";
|
||
outputBox.value = finalResult;
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error("消息处理错误:", err);
|
||
}
|
||
};
|
||
|
||
this.socket.onerror = (error) => {
|
||
console.error("WebSocket错误:", error);
|
||
this.stop();
|
||
};
|
||
|
||
this.socket.onclose = () => {
|
||
console.log("WebSocket已关闭");
|
||
this.cleanupResources();
|
||
};
|
||
|
||
// 初始化音频处理
|
||
this.stream = this.stream
|
||
? this.stream
|
||
: await navigator.mediaDevices.getUserMedia({ audio: true });
|
||
this.source = this.audioContext.createMediaStreamSource(
|
||
this.stream
|
||
);
|
||
this.processor = this.audioContext.createScriptProcessor(
|
||
4096,
|
||
1,
|
||
1
|
||
);
|
||
|
||
// 安全发送数据函数
|
||
const safeSend = (data) => {
|
||
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||
try {
|
||
this.socket.send(data);
|
||
} catch (err) {
|
||
console.error("发送数据失败:", err);
|
||
this.stop(); // 出错时自动停止
|
||
}
|
||
}
|
||
};
|
||
|
||
this.processor.onaudioprocess = (e) => {
|
||
if (!this.socket || this.socket.readyState !== WebSocket.OPEN)
|
||
return;
|
||
|
||
const input = e.inputBuffer.getChannelData(0);
|
||
const pcmData = this.convertFloat32ToInt16(input);
|
||
safeSend(pcmData);
|
||
};
|
||
|
||
this.source.connect(this.processor);
|
||
this.processor.connect(this.audioContext.destination);
|
||
} catch (err) {
|
||
layer.msg("初始化错误");
|
||
console.error("初始化错误:", err);
|
||
this.stop();
|
||
}
|
||
},
|
||
|
||
getTranslatedText(data) {
|
||
const mainLang = data.language.split("-")[0];
|
||
const entries = Object.entries(data.target);
|
||
const nonMainLangEntry = entries.find(
|
||
([lang]) => !lang.startsWith(mainLang)
|
||
);
|
||
return nonMainLangEntry
|
||
? nonMainLangEntry[1]
|
||
: entries[0]
|
||
? entries[0][1]
|
||
: null;
|
||
},
|
||
|
||
stop() {
|
||
console.log("停止");
|
||
window.finalResult = "";
|
||
// 关闭WebSocket
|
||
if (this.socket) {
|
||
if (
|
||
this.socket.readyState === WebSocket.OPEN ||
|
||
this.socket.readyState === WebSocket.CONNECTING
|
||
) {
|
||
this.socket.close();
|
||
}
|
||
|
||
this.socket = null;
|
||
}
|
||
|
||
// 清理音频资源
|
||
this.cleanupResources();
|
||
|
||
if (this.autoCloseTimer) {
|
||
clearTimeout(this.autoCloseTimer);
|
||
this.autoCloseTimer = null;
|
||
}
|
||
|
||
this.soundShow = false;
|
||
|
||
//过一秒调用
|
||
setTimeout(() => {
|
||
this.getuserinfo();
|
||
}, 1000);
|
||
},
|
||
|
||
// 清理音频资源
|
||
cleanupResources() {
|
||
if (this.processor) {
|
||
this.processor.disconnect();
|
||
this.processor = null;
|
||
}
|
||
|
||
if (this.source) {
|
||
this.source.disconnect();
|
||
this.source = null;
|
||
}
|
||
|
||
if (this.audioContext) {
|
||
this.audioContext.close().catch((e) => {
|
||
console.error("关闭AudioContext失败:", e);
|
||
});
|
||
this.audioContext = null;
|
||
}
|
||
},
|
||
copy() {
|
||
navigator.clipboard
|
||
.writeText(this.targetText)
|
||
.then(() => {
|
||
window.alert("复制成功");
|
||
})
|
||
.catch((err) => {
|
||
window.alert("复制失败");
|
||
});
|
||
},
|
||
onShowLanguage(key) {
|
||
if (!this.soundShow) {
|
||
if (key === "sourceLanguage") {
|
||
this.languageShow = !this.languageShow;
|
||
this.languageShowTarget = false;
|
||
this.transShow = false;
|
||
} else if (key === "targetLanguage") {
|
||
this.languageShowTarget = !this.languageShowTarget;
|
||
this.languageShow = false;
|
||
this.transShow = false;
|
||
} else if (key === "transTypeValue") {
|
||
this.transShow = !this.transShow;
|
||
this.languageShow = false;
|
||
this.languageShowTarget = false;
|
||
}
|
||
} else {
|
||
layer.msg("目前识别中,不可修改!");
|
||
}
|
||
this.currentKey = key;
|
||
},
|
||
onSelectEngine(e) {
|
||
if (this.soundShow) {
|
||
layer.msg("目前识别中,不可修改!");
|
||
return;
|
||
}
|
||
this.engine = e;
|
||
(this.sourceLanguage = {
|
||
key: "zh-cn",
|
||
name: "中文(普通话,简体)",
|
||
}),
|
||
(this.targetLanguage = {
|
||
key: "en-US",
|
||
name: "英语(美国)",
|
||
});
|
||
},
|
||
getLanguage() {
|
||
axios.get("/api/voice/getLanguage").then((res) => {
|
||
if (res.data.code == 1) {
|
||
this.languageTotalData = res.data.data;
|
||
console.log("res", this.languageTotalData);
|
||
}
|
||
});
|
||
},
|
||
onSelectTransType(e) {
|
||
if (this.soundShow) {
|
||
layer.msg("目前识别中,不可修改!");
|
||
return;
|
||
}
|
||
this.transTypeValue = e;
|
||
this.transShow = false;
|
||
},
|
||
onSelectLanguage(e) {
|
||
console.log(this.currentKey);
|
||
if (this.soundShow) {
|
||
layer.msg("目前识别中,不可修改!");
|
||
return;
|
||
}
|
||
this[this.currentKey] = {
|
||
key: e,
|
||
name: this.languageCurrent[e],
|
||
};
|
||
this.languageShow = false;
|
||
this.languageShowTarget = false;
|
||
console.log(this[this.currentKey]);
|
||
},
|
||
onSwitch() {
|
||
if (this.soundShow) {
|
||
layer.msg("目前识别中,不可修改!");
|
||
return;
|
||
}
|
||
const copySource = JSON.parse(JSON.stringify(this.sourceLanguage));
|
||
const copyTarget = JSON.parse(JSON.stringify(this.targetLanguage));
|
||
this.targetLanguage = copySource;
|
||
this.sourceLanguage = copyTarget;
|
||
},
|
||
onInput(e) {
|
||
if (this.sourceText) {
|
||
// _.debounce(this.translate, 500)
|
||
// this.translate()
|
||
} else {
|
||
this.targetText = "";
|
||
}
|
||
},
|
||
},
|
||
});
|
||
</script>
|
||
<div class="public-footer">
|
||
<div class="w1200">
|
||
<div class="left"><img src="static/picture/logo.png" alt="" /></div>
|
||
<div
|
||
style="
|
||
flex-direction: column;
|
||
line-height: 23px;
|
||
padding-left: 100px;
|
||
color: #fff;
|
||
font-size: 12px;
|
||
"
|
||
>
|
||
<p style="color: #fff; font-size: 12px"><span>关于我们:</span></p>
|
||
<p style="color: #fff; font-size: 12px">
|
||
<span></span>
|
||
</p>
|
||
</div>
|
||
<div class="right" style="white-space: nowrap">
|
||
<p></p>
|
||
<p><span>联系我们:</span></p>
|
||
<p><span>地址:</span></p>
|
||
<p><span></span></p>
|
||
<p class=""><span>电话: </span></p>
|
||
</div>
|
||
</div>
|
||
<div class="copyright">
|
||
<a href="https://beian.miit.gov.cn/" target="blank"
|
||
></a
|
||
>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|