You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
manage/timer.html

373 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>健身房管理系统-用户</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body, html {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-family: Arial, sans-serif;
background-color: #f5f5f5;
}
.container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #fff;
transform-origin: center;
padding: 20px;
transform: scale(1);
}
.countdown-text {
font-size: 16px;
color: #666;
margin-bottom: 10px;
}
.countdown-time {
font-size: 32px;
font-weight: bold;
margin: 20px 0;
}
.button-group {
display: flex;
gap: 20px;
}
.button {
width: 60px;
height: 60px;
border-radius: 50%;
border: 2px solid #000;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 24px;
color: #000;
background-color: transparent;
}
/* 去掉点击或聚焦时的默认蓝色高亮 */
.button:focus {
outline: none;
}
.button:active {
background-color: transparent;
}
/* 样式调整针对input按钮 */
input[type="button"] {
width: 80px;
height: 60px;
font-size: 24px;
color: #000;
background-color: transparent;
border: 2px solid #000;
border-radius: 5px;
cursor: pointer;
}
input[type="button"]:hover {
background-color: #f0f0f0;
}
.center-bold {
text-align: center; /* 文本居中 */
font-size: 1.2em; /* 增大字体大小 */
font-weight: bold; /* 加粗字体 */
}
/* 模态弹窗样式优化 */
.modal {
display: none; /* 默认隐藏 */
position: fixed; /* 固定位置 */
z-index: 1000; /* 在最上层 */
left: 0;
top: 0;
width: 100%; /* 全宽 */
height: 100%; /* 全高 */
overflow: auto; /* 允许滚动 */
background-color: rgba(0, 0, 0, 0.6); /* 更深的半透明背景 */
}
.modal-content {
background-color: #ffffff;
margin: 15% auto; /* 上下15%自动居中 */
padding: 20px;
border-radius: 8px; /* 圆角边框 */
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); /* 阴影效果 */
width: 80%; /* 宽度 */
max-width: 600px; /* 最大宽度 */
transition: all 0.3s ease; /* 平滑过渡效果 */
}
.alert-title {
font-size: 28px;
color: #d9534f;
text-align: center;
margin-bottom: 20px;
}
.modal-body {
font-size: 18px; /* 增大字体大小 */
line-height: 1.6; /* 设置行高,使文本行间距更舒适 */
}
.modal-body span {
display: block; /* 使每个span元素单独成行 */
margin-bottom: 10px; /* 添加底部外边距,增加行间距 */
}
.btn {
background-color: #d9534f;
color: white;
padding: 14px 20px;
border: none;
border-radius: 4px; /* 圆角按钮 */
cursor: pointer;
width: 100%;
font-size: 16px;
}
.btn:hover {
background-color: #c9302c; /* 悬停时颜色加深 */
}
body, html {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-family: Arial, sans-serif;
background-color: #f5f5f5;
background-image: url('/static/img/background.jpeg');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
</style>
</head>
<body>
<div class="record-list" style="position: absolute; top: 0; left: 0; width: 100%; font-weight: bold; z-index: 10; display: flex; flex-direction: column; padding: 10px;">
<div class="record-item" th:each="rel:${TargetRecord}" style="display: flex; flex-direction: column; margin-bottom: 10px; background-color: #fff; padding: 5px;">
<span class="center-bold">预期锻炼目标:</span>
<span class="center-bold">项目:<a class="record-cell" th:text="${rel.getTpName()}"></a></span>
<span class="center-bold">器材:<a class="record-cell" th:text="${rel.getTepName()}"></a></span>
<span class="center-bold">地点:<a class="record-cell" th:text="${rel.getTrLocation()}"></a></span>
<span class="center-bold">时长:<a class="record-cell" th:text="${rel.getTTime()}"></a></span>
<!-- <span class="center-bold">时间:<a class="record-cell" th:text="${rel.getTDate()}"></a></span>-->
</div>
</div>
<div class="container">
<div class="countdown-text">距离锻炼开始已经过</div>
<div class="countdown-time" id="countdown">00小时00分00秒</div>
<div class="button-group" th:each="rel:${TargetRecord}">
<input type="button" value="开始" id="playButton">
<a th:attr="data-rpName=${rel.getTpName()}, data-repName=${rel.getTepName()}, data-rrLocation=${rel.getTrLocation()}" id="endLink">
<input type="button" value="结束" id="endButton" th:action="@{/user/addTargetRecord}">
</a>
</div>
</div>
<!-- 模态弹窗内容 -->
<div id="myModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal()">&times;</span>
<div class="alert-title">警报!</div>
<div class="modal-body" th:each="rel:${TargetRecord}">
<span>器材:<a th:text="${rel.getTepName()}"></a></span>
<span>地点:<a th:text="${rel.getTrLocation()}"></a></span>
</div>
<button class="btn" onclick="closeModal()">确认收到</button>
</div>
</div>
<script>
let countdownTime = 0; // 从0秒开始
let countdownInterval = null;
let isCounting = false; // 用于跟踪倒计时是否在进行中
let pausedTime = null; // 用于存储暂停时的时间
// 将秒数转换为"XX小时XX分XX秒"格式
function formatTime(seconds) {
const hrs = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
return `${String(hrs).padStart(2, '0')}小时${String(mins).padStart(2, '0')}${String(secs).padStart(2, '0')}`;
}
// 更新倒计时显示
function updateCountdown() {
document.getElementById("countdown").textContent = formatTime(countdownTime);
}
// 启动或暂停计时
function toggleCountdown() {
const playButton = document.getElementById("playButton");
if (isCounting) {
// 暂停时记录当前时间
pausedTime = countdownTime; // 保存当前时间
clearInterval(countdownInterval); // 停止计时器
playButton.value = "开始"; // 切换回播放按钮
isCounting = false;
// 关闭语音识别
if (recognition) {
recognition.stop();
}
} else {
countdownInterval = setInterval(() => {
countdownTime++; // 每秒增加1
updateCountdown();
}, 1000);
playButton.value = "暂停"; // 切换为暂停按钮
isCounting = true;
// 开启语音识别
startVoiceRecognition();
}
}
// 结束计时并跳转页面
function endCountdown() {
if (confirm("确定要结束计时吗?")) {
clearInterval(countdownInterval); // 停止计时器
var endTime = countdownTime; // 获取结束时的时长
countdownTime = 0; // 重置计时器
updateCountdown(); // 更新显示为0
isCounting = false;
document.getElementById("playButton").value = "开始"; // 重置播放按钮
// 获取结束时的时长,并转换为格式化的时间字符串
var formattedEndTime = formatTime(endTime);
// 构建完整的URL并设置<a>标签的href属性
var endLink = document.getElementById("endLink");
var rpName = endLink.getAttribute("data-rpName");
var repName = endLink.getAttribute("data-repName");
var rrLocation = endLink.getAttribute("data-rrLocation");
var url = `/user/addRealRecord?rpName=${encodeURIComponent(rpName)}&repName=${encodeURIComponent(repName)}&rrLocation=${encodeURIComponent(rrLocation)}&rTime=${formattedEndTime}`;
endLink.href = url;
}
}
// 显示模态弹窗的函数
function showModal() {
var modal = document.getElementById("myModal");
modal.style.display = "block";
}
// 关闭模态弹窗的函数
function closeModal() {
var modal = document.getElementById("myModal");
modal.style.display = "none";
}
// 检查时间并显示弹窗的逻辑
function checkTimeAndAlert() {
if (countdownTime >= 10) { // 当时间达到10秒时
showModal(); // 显示弹窗
clearInterval(checkTimeAndAlertInterval); // 停止检查时间的间隔
}
}
// 设置检查时间的间隔
let checkTimeAndAlertInterval = setInterval(checkTimeAndAlert, 1000);
// 绑定结束按钮点击事件
document.getElementById("endButton").addEventListener("click", endCountdown);
// 绑定播放/暂停按钮点击事件
document.getElementById("playButton").addEventListener("click", toggleCountdown);
// 页面加载时初始化计时
updateCountdown();
let recognition;
function startVoiceRecognition() {
if (!('SpeechRecognition' in window) && !('webkitSpeechRecognition' in window)) {
alert("浏览器不支持语音识别功能,请使用支持该功能的浏览器!");
return;
}
// 在这里使用 Google Cloud Speech-to-Text API
const audioStream = new MediaStream();
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const analyser = audioContext.createAnalyser();
// 使用 Web Audio API 获取麦克风音频流
navigator.mediaDevices.getUserMedia({ audio: true })
.then(function(stream) {
const mediaStreamSource = audioContext.createMediaStreamSource(stream);
mediaStreamSource.connect(analyser);
// 创建 Google Cloud API 需要的语音识别请求体
const request = {
config: {
encoding: 'LINEAR16',
sampleRateHertz: 16000,
languageCode: 'zh-CN',
},
audio: {
content: "" // 将要转换的音频数据
}
};
// 请求音频流,发送到 Google Cloud Speech-to-Text API
const reader = new FileReader();
reader.onloadend = function() {
request.audio.content = reader.result.split(',')[1]; // Base64 编码的音频数据
fetch('https://speech.googleapis.com/v1p1beta1/speech:recognize?key=YOUR_GOOGLE_API_KEY', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(request),
})
.then(response => response.json())
.then(data => {
if (data.results && data.results.length > 0) {
const transcript = data.results[0].alternatives[0].transcript;
console.log('识别结果:', transcript);
// 判断是否包含“救命”
if (transcript.includes("救命")) {
alert("紧急情况!请立即寻求帮助!");
}
}
})
.catch(error => console.error('语音识别失败:', error));
};
// 从音频流中获取数据
const audioChunks = [];
const mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = event => audioChunks.push(event.data);
mediaRecorder.onstop = () => reader.readAsDataURL(new Blob(audioChunks));
mediaRecorder.start();
recognition = mediaRecorder;
})
.catch(error => {
console.error("无法获取麦克风权限:", error);
});
}
startVoiceRecognition();
</script>
</body>
</html>