2025-07-02 18:32:06 +08:00
|
|
|
|
<!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>官方频道:@apiapl_news</span></p>
|
|
|
|
|
|
<p style="color: #fff; font-size: 12px"><span>咨询客服:@apiapl</span></p>
|
|
|
|
|
|
<span></span>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="right" style="white-space: nowrap">
|
|
|
|
|
|
<p></p>
|
|
|
|
|
|
|
|
|
|
|
|
<p><span>技术服务:@apiapl_sdk</span></p>
|
|
|
|
|
|
<p><span>联系邮箱:info@apiapl.com</span></p>
|
|
|
|
|
|
<p class=""><span>联系电话:+18435173355 </span></p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="copyright">
|
|
|
|
|
|
<a href="https://beian.miit.gov.cn/" target="blank"
|
|
|
|
|
|
></a
|
|
|
|
|
|
>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|