805 lines
22 KiB
HTML
805 lines
22 KiB
HTML
<!DOCTYPE HTML>
|
||
<html>
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||
<link rel="shortcut icon" type="image/png" href="assets/icon.png">
|
||
<title>家庭管理语音AI页面</title>
|
||
</head>
|
||
|
||
<body>
|
||
<!-- 讯飞 -->
|
||
<script src="./js/voice.js"></script>
|
||
<script src="./js/crypto-js.min.js"></script>
|
||
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
|
||
<script src="js/w_md5.js"></script>
|
||
|
||
<!-- 构建界面 -->
|
||
<div class="main">
|
||
<div class="content">
|
||
<!-- 加载遮罩 -->
|
||
<div class="dialog" id="dialog" style="display: none;">
|
||
<!-- <div class="overlay" id="overlay"></div> -->
|
||
<div class="modal" id="modal">
|
||
<div class="ld"><em></em></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 视频 -->
|
||
<div class="video-wrap">
|
||
<video id="myVideo" muted loop autoplay playsinline>
|
||
<source src="https://img.agrimedia.cn/bmsc/%E9%A3%9E%E4%B9%A620240918-175041.mp4" type="video/mp4">
|
||
</video>
|
||
</div>
|
||
<audio
|
||
id="myAudio"
|
||
autoplay
|
||
controls
|
||
style="width: 100% height: 10px">
|
||
</audio>
|
||
|
||
<div id="AiButton">
|
||
<!-- 录制 -->
|
||
<img class="startRec" src="https://img.agrimedia.cn/bmsc/apps/start-tuya.png">
|
||
<!-- 录制中 -->
|
||
<img class="runRec" src="https://img.agrimedia.cn/bmsc/apps/runnig-tuya.png">
|
||
<!-- 停止 -->
|
||
<img class="endRec" src="https://img.agrimedia.cn/bmsc/apps/end-tuya.png">
|
||
</div>
|
||
|
||
<!-- 讯飞测试 -->
|
||
<div style="opacity: 1">
|
||
<div class="voice-box">
|
||
<input class="voice-input" type="search" name="voice" id="voice-txt" style="pointer-events: none"/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
var Items = ['血糖', '睡眠', '血氧', '血压', '尿酸', '梅拖', '心率', '体温', '心电图', '身体成份', '运动', '血脂'];
|
||
var Question = '';
|
||
|
||
function getURLParameter(name) {
|
||
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
||
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
||
results = regex.exec(location.search);
|
||
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
||
}
|
||
|
||
// SEE事件解析
|
||
function extractStopEvent(sseString) {
|
||
// 使用正则表达式和字符串的 split 方法来分割事件
|
||
// 假设每个事件由空行分隔(\n\n),但请注意这取决于实际的换行符
|
||
const events = sseString.split(/\r?\n\r?\n/);
|
||
let stopEvent = null;
|
||
|
||
// 遍历每个事件
|
||
events.forEach(event => {
|
||
if (!event.trim()) {
|
||
// 忽略空字符串
|
||
return;
|
||
}
|
||
// 假设 data 字段是最后一个字段,并且可能跨越多行
|
||
let dataLines = [];
|
||
let isDataLine = false;
|
||
|
||
// 分解当前事件为行
|
||
const lines = event.split(/\r?\n/);
|
||
|
||
// 遍历每一行来找到 data 字段
|
||
lines.forEach(line => {
|
||
if (line.trim().startsWith('data:')) {
|
||
isDataLine = true;
|
||
dataLines.push(line.trim().slice(5)); // 移除 'data:' 前缀
|
||
} else if (isDataLine && !line.trim()) {
|
||
// 如果在 data 字段后遇到了空行,则停止收集
|
||
isDataLine = false;
|
||
} else if (isDataLine) {
|
||
// 继续收集 data 字段的行(跨越多行的情况)
|
||
dataLines.push(line.trim());
|
||
}
|
||
});
|
||
|
||
const dataJson = dataLines.join('');
|
||
try {
|
||
if (dataJson) {
|
||
const eventData = JSON.parse(dataJson);
|
||
if (eventData.output.finish_reason === "stop") {
|
||
stopEvent = eventData;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('解析 data 字段为 JSON 时出错:', error);
|
||
}
|
||
});
|
||
|
||
// 如果没有找到 finish_reason 为 "stop" 的事件,则返回 null 或其他默认值
|
||
return stopEvent;
|
||
}
|
||
|
||
// 筛选关键词
|
||
function containsKeywordRegex(text) {
|
||
var index = Items.findIndex(item => text.includes(item));
|
||
|
||
if (index !== -1) {
|
||
return index
|
||
} else {
|
||
return 99999
|
||
}
|
||
}
|
||
|
||
// 语音所需时间
|
||
function calculateSpeakingTime(text) {
|
||
// 假设平均每分钟说230个单词
|
||
const wordsPerMinute = 230;
|
||
// 计算文字的长度
|
||
const wordCount = text.trim().length;
|
||
// 计算所需时间(以分钟为单位)
|
||
const speakingTime = wordCount / wordsPerMinute;
|
||
// 转换为秒数
|
||
const speakingTimeInSeconds = speakingTime * 60;
|
||
return speakingTimeInSeconds;
|
||
}
|
||
</script>
|
||
|
||
<!-- 讯飞语音识别 -->
|
||
<script>
|
||
window.onload = function () {
|
||
var demoData = {
|
||
bloodGlucose: "血糖",
|
||
SleepDatas: "睡眠",
|
||
bloodOxygen: "血氧",
|
||
bloodPressure: '血压',
|
||
bloodLiquid: "血脂",
|
||
meiTuo: '梅脱',
|
||
pulseReat: '心率',
|
||
bodyTemperature: '体温',
|
||
ECGData: '心电图',
|
||
bodyData: '身体成份',
|
||
stepIndex: '运动'
|
||
};
|
||
|
||
var videoElement = document.getElementById('myVideo');
|
||
var startTime = 5; // 开始时间(以秒为单位)
|
||
var endTime = 10; // 结束时间(以秒为单位)
|
||
var timeUpdateListener; // 保存timeupdate事件的监听器
|
||
|
||
// 指定段落
|
||
function playVideoSegment(startTime, endTime) {
|
||
videoElement.currentTime = startTime;
|
||
videoElement.play();
|
||
|
||
// 添加timeupdate事件监听器
|
||
timeUpdateListener = function() {
|
||
if (videoElement.currentTime >= endTime) {
|
||
videoElement.currentTime = startTime;
|
||
}
|
||
};
|
||
videoElement.addEventListener('timeupdate', timeUpdateListener);
|
||
}
|
||
|
||
// 重新播放
|
||
function replayVideoSegment(startTime, endTime) {
|
||
stopVideo();
|
||
playVideoSegment(startTime, endTime);
|
||
}
|
||
|
||
// 停止播放
|
||
function stopVideo() {
|
||
videoElement.pause();
|
||
videoElement.currentTime = 0;
|
||
|
||
// 移除timeupdate事件监听器
|
||
if (timeUpdateListener) {
|
||
videoElement.removeEventListener('timeupdate', timeUpdateListener);
|
||
timeUpdateListener = null;
|
||
}
|
||
}
|
||
|
||
// 当前AI视频循环
|
||
replayVideoSegment(0, 60);
|
||
|
||
// 获取 audio 元素的引用
|
||
var audioElement = document.getElementById('myAudio');
|
||
audioElement.muted = true; // 先静音
|
||
|
||
// 获取页面元素
|
||
var element = document.getElementById("elementId");
|
||
|
||
// 获取遮罩和弹窗元素
|
||
var dialog = document.getElementById('dialog');
|
||
|
||
// 点击事件
|
||
var startRec = document.getElementsByClassName('startRec')[0];
|
||
var runRec = document.getElementsByClassName('runRec')[0];
|
||
var endRec = document.getElementsByClassName('endRec')[0];
|
||
|
||
var token = null;
|
||
let times = null;
|
||
|
||
// 获取微软token
|
||
fetch("https://eastasia.api.cognitive.microsoft.com/sts/v1.0/issueToken", {
|
||
method: 'POST',
|
||
headers: {
|
||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737'
|
||
},
|
||
}).then(async(response) => {
|
||
token = await response.text();
|
||
}).catch(e => {});
|
||
|
||
|
||
// 文本输入框
|
||
const voiceTxt = document.querySelector('#voice-txt');
|
||
|
||
// 防止多次请求
|
||
var isCallbackExecuted = false;
|
||
|
||
|
||
/*
|
||
* 给予数据文字标识
|
||
*/
|
||
let exampleData = JSON.parse(getURLParameter('data'));
|
||
for (let i = 0; i < exampleData.length; i++) {
|
||
exampleData[i].name = demoData[exampleData[i].type]
|
||
}
|
||
|
||
/*
|
||
* 实例化迅飞语音听写(流式版)WebAPI
|
||
*/
|
||
const voice = new Voice({
|
||
// 服务接口认证信息
|
||
appId: '5f4ffdeb',
|
||
apiSecret: 'OGIwM2RlMjBkOTI5Mzk5YTJlMzUwODI5',
|
||
apiKey: '0b17a761b6b7174b789f639119d7e29a',
|
||
onWillStatusChange: function (oldStatus, newStatus) {},
|
||
|
||
onTextChange: function (text) {
|
||
// 监听识别结果的变化
|
||
console.log(text, '监听')
|
||
voiceTxt.value = text;
|
||
|
||
// 3秒钟内没有说话,就自动关闭
|
||
if (text) {
|
||
clearTimeout(times);
|
||
if (!isCallbackExecuted) {
|
||
times = setTimeout(() => {
|
||
this.stop();
|
||
// voice.stop();
|
||
|
||
const params = { msg: text }
|
||
|
||
/*
|
||
* 拿到匹配的文字下标
|
||
*/
|
||
let QSindex = containsKeywordRegex(params.msg);
|
||
console.log(QSindex)
|
||
if (QSindex == 0) {
|
||
const obj = exampleData.filter(item => item.type == "bloodGlucose");
|
||
if (obj[0].data_msg) {
|
||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血糖为${obj[0].data_msg}毫摩尔/升`
|
||
} else {
|
||
alert ('当前数据为空');
|
||
return
|
||
}
|
||
}
|
||
if (QSindex == 1) {
|
||
const obj = exampleData.filter(item => item.type == "SleepDatas");
|
||
if (obj[0].data_msg) {
|
||
Question = `请模仿全科医生的口吻与我对话,我最近睡眠时长为${obj[0].data_msg[0].sleepTotalTime}分钟`
|
||
} else {
|
||
alert ('当前数据为空');
|
||
return
|
||
}
|
||
}
|
||
if (QSindex == 2) {
|
||
const obj = exampleData.filter(item => item.type == "bloodOxygen");
|
||
if (obj[0].data_msg) {
|
||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血氧为${obj[0].data_msg}毫摩尔/升`
|
||
} else {
|
||
alert ('当前数据为空');
|
||
return
|
||
}
|
||
}
|
||
if (QSindex == 3) {
|
||
const obj = exampleData.filter(item => item.type == "bloodPressure");
|
||
if (obj[0].data_msg) {
|
||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血压为${obj[0].data_msg.bloodPressureLow}/${obj[0].data_msg.bloodPressureHigh}毫摩尔/升`
|
||
} else {
|
||
alert ('当前数据为空');
|
||
return
|
||
}
|
||
}
|
||
if (QSindex == 4 || QSindex == 11) {
|
||
const obj = exampleData.filter(item => item.type == "bloodLiquid");
|
||
if (obj[0].data_msg.cholesterol) {
|
||
Question = `请模仿全科医生的口吻与我对话,我最近测量的血脂状况为,
|
||
尿酸为${obj[0].data_msg.uricAcidVal/10},
|
||
总胆固醇为${obj[0].data_msg.cholesterol/100},
|
||
甘油三酯为${obj[0].data_msg.cholesterol/100},
|
||
高密度脂蛋白为${obj[0].data_msg.cholesterol/100},
|
||
低密度脂蛋白为${obj[0].data_msg.cholesterol/100}, `
|
||
} else {
|
||
alert ('当前数据为空');
|
||
return
|
||
}
|
||
}
|
||
if (QSindex == 6) {
|
||
const obj = exampleData.filter(item => item.type == "pulseReat");
|
||
if (obj[0].data_msg) {
|
||
Question = `请模仿全科医生的口吻与我对话,我最近测量的心率为${obj[0].data_msg[0]}, `
|
||
} else {
|
||
alert ('当前数据为空');
|
||
return
|
||
}
|
||
}
|
||
if (QSindex == 7) {
|
||
const obj = exampleData.filter(item => item.type == "bodyTemperature");
|
||
if (obj[0].data_msg) {
|
||
Question = `请模仿全科医生的口吻与我对话,我最近测量的体温为${obj[0].data_msg}摄氏度`
|
||
} else {
|
||
alert ('当前数据为空');
|
||
return
|
||
}
|
||
}
|
||
if (QSindex == 8) {
|
||
const obj = exampleData.filter(item => item.type == "ECGData");
|
||
if (obj[0].data_msg) {
|
||
Question = `请模仿全科医生的口吻与我对话,我最近心电图测量结果为${obj[0].data_msg.heartRate}次/分`
|
||
} else {
|
||
alert ('当前数据为空');
|
||
return
|
||
}
|
||
}
|
||
if (QSindex == 9) {
|
||
const obj = exampleData.filter(item => item.type == "bodyData");
|
||
if (obj[0].data_msg.BMI) {
|
||
Question = `请模仿全科医生的口吻与我对话,我最近身体成分结果为${obj[0].data_msg.BMI}`
|
||
} else {
|
||
alert ('当前数据为空');
|
||
return
|
||
}
|
||
}
|
||
|
||
if (QSindex == 10) {
|
||
const obj = exampleData.filter(item => item.type == "stepIndex");
|
||
if (obj[0].data_msg) {
|
||
Question = `请模仿全科医生的口吻与我对话,我最近测量的运动为${obj[0].data_msg.step}步数,
|
||
${obj[0].data_msg.calorie/10}千卡,
|
||
${obj[0].data_msg.distance/1000}公里`
|
||
} else {
|
||
alert ('当前数据为空');
|
||
return
|
||
}
|
||
}
|
||
|
||
if (QSindex == 99999) {
|
||
const xhr = new XMLHttpRequest();
|
||
xhr.open('POST', 'http://sc2.agrimedia.cn:8787/api/user/ask', true);
|
||
|
||
var data = JSON.stringify({
|
||
"messages": [
|
||
{"role": "system", "content": params.msg},
|
||
{"role": "user", "content": params.msg}
|
||
]
|
||
})
|
||
|
||
xhr.onreadystatechange = function() {
|
||
if (xhr.readyState === 4) {
|
||
const chunk = xhr.responseText;
|
||
const str = extractStopEvent(chunk);
|
||
|
||
/*
|
||
* 微软接口识别
|
||
*/
|
||
fetch("https://eastasia.tts.speech.microsoft.com/cognitiveservices/v1", {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': 'Bearer ' + token,
|
||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737',
|
||
'Content-Type': 'application/ssml+xml',
|
||
'X-Microsoft-OutputFormat': 'audio-24khz-48kbitrate-mono-mp3'
|
||
},
|
||
responseType: 'arraybuffer',
|
||
body: `<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
|
||
<voice name="zh-CN-XiaoxiaoNeural">
|
||
<mstts:express-as style="Default" >
|
||
<prosody rate="0%" pitch="0%">
|
||
${str.output.text}
|
||
</prosody>
|
||
</mstts:express-as>
|
||
</voice>
|
||
</speak> `,
|
||
}).then(async(response) => {
|
||
const content_bytes = await response.arrayBuffer();
|
||
const blob = new Blob([content_bytes], { type: 'audio/mp3' });
|
||
const blobUrl = URL.createObjectURL(blob);
|
||
|
||
// 设置音频源
|
||
audioElement.src = blobUrl;
|
||
|
||
// 播放音频
|
||
audioElement.play();
|
||
|
||
// 循环视频
|
||
replayVideoSegment(60, 120);
|
||
|
||
// 计算所需时间
|
||
const speakingTime = calculateSpeakingTime(content.data.choices[0].text);
|
||
|
||
// 开始倒计时
|
||
var totalTime = speakingTime;
|
||
var countdown = setInterval(function() {
|
||
// 更新剩余时间
|
||
totalTime --;
|
||
if (totalTime <= 0) {
|
||
// 停止倒计时
|
||
clearInterval(countdown);
|
||
replayVideoSegment(0, 60);
|
||
}
|
||
}, 1000);
|
||
|
||
hideModal()
|
||
}).catch(e => {
|
||
hideModal();
|
||
});
|
||
|
||
startRec.style.display = 'block';
|
||
runRec.style.display = 'none';
|
||
// endRec.style.opacity = 0;
|
||
}
|
||
};
|
||
xhr.send(data);
|
||
isCallbackExecuted = true;
|
||
return
|
||
} else {
|
||
/*
|
||
* 调用接口 传递关键信息 文字转语音
|
||
*/
|
||
const xhr = new XMLHttpRequest();
|
||
xhr.open('POST', 'http://sc2.agrimedia.cn:8787/api/user/ask', true);
|
||
|
||
|
||
/*
|
||
* 关键字转换
|
||
*/
|
||
var data = JSON.stringify({
|
||
"messages": [
|
||
{"role": "system", "content": Question},
|
||
{"role": "user", "content": `请问我${Items[QSindex]}正常吗`}
|
||
]
|
||
})
|
||
|
||
xhr.onreadystatechange = function() {
|
||
if (xhr.readyState === 4) {
|
||
const chunk = xhr.responseText;
|
||
const str = extractStopEvent(chunk);
|
||
|
||
/*
|
||
* 微软接口识别
|
||
*/
|
||
fetch("https://eastasia.tts.speech.microsoft.com/cognitiveservices/v1", {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': 'Bearer ' + token,
|
||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737',
|
||
'Content-Type': 'application/ssml+xml',
|
||
'X-Microsoft-OutputFormat': 'audio-24khz-48kbitrate-mono-mp3'
|
||
},
|
||
responseType: 'arraybuffer',
|
||
body: `<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
|
||
<voice name="zh-CN-XiaoxiaoNeural">
|
||
<mstts:express-as style="Default" >
|
||
<prosody rate="0%" pitch="0%">
|
||
${str.output.text}
|
||
</prosody>
|
||
</mstts:express-as>
|
||
</voice>
|
||
</speak> `,
|
||
}).then(async(response) => {
|
||
const content_bytes = await response.arrayBuffer();
|
||
const blob = new Blob([content_bytes], { type: 'audio/mp3' });
|
||
const blobUrl = URL.createObjectURL(blob);
|
||
|
||
// 设置音频源
|
||
audioElement.src = blobUrl;
|
||
|
||
// 播放音频
|
||
audioElement.play();
|
||
|
||
// 循环视频
|
||
replayVideoSegment(60, 120);
|
||
|
||
// 计算所需时间
|
||
const speakingTime = calculateSpeakingTime(content.data.choices[0].text);
|
||
|
||
// 开始倒计时
|
||
var totalTime = speakingTime;
|
||
var countdown = setInterval(function() {
|
||
// 更新剩余时间
|
||
totalTime --;
|
||
if (totalTime <= 0) {
|
||
// 停止倒计时
|
||
clearInterval(countdown);
|
||
replayVideoSegment(0, 60);
|
||
}
|
||
}, 1000);
|
||
|
||
hideModal()
|
||
}).catch(e => {
|
||
hideModal();
|
||
});
|
||
|
||
startRec.style.display = 'block';
|
||
runRec.style.display = 'none';
|
||
// endRec.style.opacity = 0;
|
||
}
|
||
};
|
||
xhr.send(data);
|
||
isCallbackExecuted = true;
|
||
return
|
||
}
|
||
}, 3000);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
|
||
// 开始识别
|
||
startRec.addEventListener("click", function() {
|
||
/**开始识别**/
|
||
voiceTxt.value = '';
|
||
voice.start();
|
||
isCallbackExecuted = false;
|
||
|
||
// 先静音即可处理解决(提前做交互)
|
||
audioElement.muted = false;
|
||
audioElement.pause();
|
||
audioElement.currentTime = 0;
|
||
|
||
startRec.style.display = 'none';
|
||
runRec.style.display = 'block';
|
||
// endRec.style.opacity = 0;
|
||
showModal()
|
||
});
|
||
|
||
|
||
// 关闭识别
|
||
endRec.addEventListener("click", function() {
|
||
/**关闭识别**/
|
||
voiceTxt.value = '';
|
||
voice.stop();
|
||
// 音频
|
||
audioElement.pause();
|
||
audioElement.currentTime = 0;
|
||
// 视频
|
||
replayVideoSegment(0, 60);
|
||
|
||
isCallbackExecuted = false;
|
||
|
||
startRec.style.display = 'block';
|
||
runRec.style.display = 'none';
|
||
// endRec.style.opacity = 0;
|
||
hideModal()
|
||
});
|
||
|
||
|
||
// 显示弹窗和遮罩
|
||
function showModal() {
|
||
// overlay.style.display = 'block';
|
||
modal.style.display = 'block';
|
||
dialog.style.display = 'block';
|
||
}
|
||
|
||
// 隐藏弹窗和遮罩
|
||
function hideModal() {
|
||
// overlay.style.display = 'none';
|
||
modal.style.display = 'none';
|
||
dialog.style.display = 'none';
|
||
}
|
||
};
|
||
</script>
|
||
|
||
|
||
<style>
|
||
body {
|
||
margin: 0;
|
||
}
|
||
.content {
|
||
width: 100%;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
background-image: url('https://img.agrimedia.cn/bmsc/index/ai-persion-bg-tuya.jpeg');
|
||
background-size: 100% 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.video-wrap {
|
||
width: 80%;
|
||
top: 10%;
|
||
height: auto;
|
||
background-image: url('https://img.agrimedia.cn/bmsc/index/video-bg.png');
|
||
background-size: 100% 100%;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
box-sizing: border-box;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
#myVideo {
|
||
width: 100%;
|
||
height: 100% !important;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
#myAudio {
|
||
position: absolute;
|
||
top: 0px;
|
||
left: 0px;
|
||
opacity: 0;
|
||
}
|
||
|
||
#AiButton {
|
||
width: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-around;
|
||
text-align: center;
|
||
margin-top: 30px;
|
||
}
|
||
#AiButton > img {
|
||
width: 120px;
|
||
height: 30px;
|
||
}
|
||
|
||
/* 遮罩样式 */
|
||
.overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: rgba(0, 0, 0, 0.3); /* 半透明黑色背景 */
|
||
display: none; /* 初始隐藏 */
|
||
z-index: 9999; /* 设置为最高层级 */
|
||
}
|
||
|
||
/* 弹窗样式 */
|
||
.modal {
|
||
position: fixed;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%); /* 居中对齐 */
|
||
padding: 20px;
|
||
border-radius: 5px;
|
||
text-align: center;
|
||
z-index: 99999;
|
||
}
|
||
|
||
.ld {
|
||
background: #000;
|
||
transform: rotate(45deg);
|
||
transform-origin: center;
|
||
margin-top: calc(var(--dis) / -1.41421); /* Math.sqrt(2) */
|
||
|
||
--dis: 20px; /*圆尺寸和偏移量*/
|
||
--dur: 2s; /*动画时长*/
|
||
}
|
||
|
||
.ld em {
|
||
position: relative;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.ld::before {
|
||
background-color: #38597A;
|
||
}
|
||
|
||
.ld::after {
|
||
background-color: #3FEBFF;
|
||
}
|
||
|
||
.ld em::before {
|
||
background-color: #87B2DD;
|
||
}
|
||
|
||
.ld em::after {
|
||
background-color: #58D5FF;
|
||
}
|
||
|
||
.ld::before,
|
||
.ld::after,
|
||
.ld em::before,
|
||
.ld em::after {
|
||
content: "";
|
||
position: absolute;
|
||
width: var(--dis);
|
||
height: var(--dis);
|
||
border-radius: var(--dis);
|
||
left: 0;
|
||
top: 0;
|
||
animation-duration: var(--dur);
|
||
animation-iteration-count: infinite;
|
||
animation-fill-mode: both;
|
||
--dir: 1;
|
||
--tr: calc(var(--dir) * var(--dis));
|
||
--ttr: calc(-1 * var(--tr));
|
||
--tx: translateX(var(--tr));
|
||
--ttx: translateX(var(--ttr));
|
||
--ty: translateY(var(--tr));
|
||
--tty: translateY(var(--ttr));
|
||
}
|
||
.ld::before,
|
||
.ld::after {
|
||
--dir: -1;
|
||
}
|
||
|
||
.ld::before,
|
||
.ld em::before {
|
||
transform: var(--ttx);
|
||
animation-name: tx;
|
||
}
|
||
.ld::after,
|
||
.ld em::after {
|
||
transform: var(--tty);
|
||
animation-name: ty;
|
||
}
|
||
|
||
@keyframes tx {
|
||
50% {
|
||
transform: var(--tx);
|
||
}
|
||
}
|
||
|
||
@keyframes ty {
|
||
50% {
|
||
transform: var(--ty);
|
||
}
|
||
}
|
||
|
||
.rule {
|
||
width: 1px;
|
||
background: #aaa;
|
||
position: fixed;
|
||
left: 50%;
|
||
top: 0;
|
||
bottom: 0;
|
||
}
|
||
.rule::after {
|
||
content: "";
|
||
height: 1px;
|
||
background: #aaa;
|
||
position: fixed;
|
||
top: 50%;
|
||
left: 0;
|
||
right: 0;
|
||
}
|
||
.runRec {
|
||
display: none;
|
||
}
|
||
|
||
.voice-input {
|
||
margin: 0 auto;
|
||
width: 100%;
|
||
height: 50px;
|
||
background-color: none;
|
||
line-height: 50px;
|
||
border: 0;/*清除自带的2px的边框*/
|
||
padding: 0;/*清除自带的padding间距*/
|
||
outline: none;/*清除input点击之后的黑色边框*/
|
||
text-align: center;
|
||
background-color: transparent;
|
||
color: #fff;
|
||
font-size: 26px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
</style>
|
||
|
||
</body>
|
||
</html> |