Compare commits

...

4 Commits

@ -0,0 +1,372 @@
<!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>

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="userCommon::common-header"></head>
<body class="sb-nav-fixed">
<nav th:replace="userCommon::common-navbar"></nav>
<div id="layoutSidenav">
<div th:replace="userCommon::common-sidenav"></div>
<div id="layoutSidenav_content">
<main>
<div class="container-fluid px-4">
<h1 class="mt-4">预约信息</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item"><a th:href="@{/toUserMain}">主页</a></li>
</ol>
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-table me-1"></i>
预约信息表
</div>
<div class="card-body">
<table id="datatablesSimple">
<thead>
<tr>
<th>编号</th>
<th>预约号</th>
<th>名称</th>
<th>时间</th>
<th>地点</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="class:${classList}">
<td th:text="${class.cid}">编号</td>
<td th:text="${class.classId}">预约号</td>
<td th:text="${class.className}">名称</td>
<td th:text="${class.classBegin}">时间</td>
<td th:text="${class.coach}">地点</td>
<td>
<a th:href="@{/user/userBook(recordId=${class.classId},recordName=${class.className},recordBegin=${class.classBegin},Location=${class.coach})}"
style="text-decoration: none">
<input type="button" class="btn btn-sm btn-outline-primary" value="预约">
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div style="text-align:center;margin-top:20px;margin-bottom:20px">
</div>
</div>
</main>
<footer th:replace="userCommon::common-footer"></footer>
</div>
</div>
<div th:include="userCommon::common-scripts"></div>
<script>
function del() {
let msg = "确定要删除吗?";
if (confirm(msg) == true) {
return true;
} else {
return false;
}
}
</script>
</body>
</html>

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="userCommon::common-header"></head>
<body class="sb-nav-fixed">
<nav th:replace="userCommon::common-navbar"></nav>
<div id="layoutSidenav">
<div th:replace="userCommon::common-sidenav"></div>
<div id="layoutSidenav_content">
<main>
<div class="container-fluid px-4">
<h1 class="mt-4">预约记录</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item"><a th:href="@{/toUserMain}">主页</a></li>
<li class="breadcrumb-item"><a th:href="@{/user/toUserBook}">预约信息</a></li>
<li class="breadcrumb-item active">预约记录</li>
</ol>
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-table me-1"></i>
预约记录
</div>
<div class="card-body">
<table id="datatablesSimple">
<thead>
<tr>
<th>预约时间</th>
<th>预约号</th>
<th>器材</th>
<th>时间</th>
<th>地点</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="rec:${recordList}">
<td th:text="${rec.date}">编号</td>
<td th:text="${rec.recordId}">预约号</td>
<td th:text="${rec.recordName}">器材</td>
<td th:text="${rec.recordBegin}">时间</td>
<td th:text="${rec.Location}">地点</td>
<td>
<a th:href="@{/user/delUserRecord(recordId=${rec.recordId})}"
style="text-decoration: none">
<input type="button" class="btn btn-sm btn-outline-danger"
onclick="return del()" value="删除">
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</main>
<footer th:replace="userCommon::common-footer"></footer>
</div>
</div>
<div th:include="userCommon::common-scripts"></div>
<script>
function del() {
let msg = "确定要删除吗?";
if (confirm(msg) == true) {
return true;
} else {
return false;
}
}
</script>
</body>
</html>

@ -0,0 +1,191 @@
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<meta name="description" content="gym-management-system"/>
<meta name="author" content=" "/>
<title>健身房管理系统-注册</title>
<link href="/static/css/styles.css" th:href="@{css/styles.css}" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js"
crossorigin="anonymous"></script>
<script type="text/javascript">
var count = 0;
function checkSubmit(registrationForm) {
// 检查姓名
if (registrationForm.memberName.value == '') {
alert("请输入姓名");
registrationForm.memberName.focus();
count++;
return false;
}
// 检查密码
var password = registrationForm.memberPassword.value;
if (password == '') {
alert("请输入密码");
registrationForm.memberPassword.focus();
count++;
return false;
}
// 检查密码强度必须包含字母和数字且长度必须为8个字符
var passwordPattern = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8}$/;
if (!passwordPattern.test(password)) {
alert("密码必须包含一个字母和一个数字并且长度为8个字符");
registrationForm.memberPassword.focus();
count++;
return false;
}
// 检查确认密码
var confirmPassword = registrationForm.confirmPassword.value;
if (confirmPassword == '') {
alert("请输入确认密码");
registrationForm.confirmPassword.focus();
count++;
return false;
}
if (password !== confirmPassword) {
alert("两次输入的密码不一致");
registrationForm.confirmPassword.focus();
count++;
return false;
}
// 检查性别
if (registrationForm.memberGender.value == '') {
alert("请选择性别");
registrationForm.memberGender.focus();
count++;
return false;
}
// 检查年龄范围
var age = registrationForm.memberAge.value;
if (age == '' || age < 16 || age > 80) {
alert("年龄必须在16到80岁之间");
registrationForm.memberAge.focus();
count++;
return false;
}
// 检查身高是否为负数
var height = registrationForm.memberHeight.value;
if (height == '' || height <= 0) {
alert("请输入有效的身高");
registrationForm.memberHeight.focus();
count++;
return false;
}
// 检查体重是否为负数
var weight = registrationForm.memberWeight.value;
if (weight == '' || weight <= 0) {
alert("请输入有效的体重");
registrationForm.memberWeight.focus();
count++;
return false;
}
// 检查手机号码
var phonePattern = /^1[3-9]\d{9}$/;
var memberPhone = registrationForm.memberPhone.value;
if (memberPhone == '') {
alert("请输入电话号码");
registrationForm.memberPhone.focus();
count++;
return false;
} else if (!phonePattern.test(memberPhone)) {
alert("请输入有效的手机号码");
registrationForm.memberPhone.focus();
count++;
return false;
}
return true;
}
function check(registrationForm) {
var result = checkSubmit(registrationForm);
if (result) {
alert("注册成功,即将进入用户界面!");
return true;
}
return false;
}
</script>
</head>
<body class="bg-primary" th:style="'background-image: url(/img/background.jpeg);background-size: 100%, 100%'">
<div id="layoutAuthentication">
<div id="layoutAuthentication_content">
<main>
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-7">
<div class="card shadow-lg border-0 rounded-lg mt-5">
<div class="card-header"><h3 class="text-center font-weight-light my-4">注册</h3></div>
<div class="card-body">
<form th:action="@{/addUser}" method="post" name="addForm" onsubmit="return check(this)">
<!-- 姓名输入框 -->
<div class="form-floating mb-3">
<input class="form-control" id="inputName" name="memberName" type="text" placeholder="姓名" required />
<label for="inputName">姓名</label>
</div>
<!-- 密码输入框 -->
<div class="form-floating mb-3">
<input class="form-control" id="inputPassword" name="memberPassword" type="password" placeholder="密码" required />
<label for="inputPassword">8位密码(必须包含字母跟数字)</label>
</div>
<!-- 确认密码输入框 -->
<div class="form-floating mb-3">
<input class="form-control" id="inputConfirmPassword" name="confirmPassword" type="password" placeholder="确认密码" required />
<label for="inputConfirmPassword">确认密码</label>
</div>
<!-- 性别选择 -->
<div class="form-floating mb-3">
<select class="form-select" id="inputGender" name="memberGender" required>
<option value="">选择性别</option>
<option value="男"></option>
<option value="女"></option>
</select>
<label for="inputGender">性别</label>
</div>
<!-- 年龄输入框 -->
<div class="form-floating mb-3">
<input class="form-control" id="inputAge" name="memberAge" type="number" placeholder="年龄" required />
<label for="inputAge">年龄18-80</label>
</div>
<!-- 身高输入框 -->
<div class="form-floating mb-3">
<input class="form-control" id="inputHeight" name="memberHeight" type="number" placeholder="身高(cm)" required />
<label for="inputHeight">身高cm</label>
</div>
<!-- 体重输入框 -->
<div class="form-floating mb-3">
<input class="form-control" id="inputWeight" name="memberWeight" type="number" placeholder="体重(kg)" required />
<label for="inputWeight">体重kg</label>
</div>
<!-- 电话号码输入框 -->
<div class="form-floating mb-3">
<input class="form-control" id="inputPhone" name="memberPhone" type="tel" placeholder="电话号码" required />
<label for="inputPhone">电话号码</label>
</div>
<!-- 提交按钮 -->
<div class="d-flex align-items-center justify-content-between mt-4 mb-0">
<input type="submit" class="btn btn-success" value="注册">
<a class="btn btn-primary" th:href="@{/}">返回</a>
</div>
</form>
<div th:text="${msg}" style="margin-top: 20px;text-align: center;color: red"></div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
crossorigin="anonymous"></script>
<script src="/static/js/scripts.js" th:src="@{js/scripts.js}"></script>
</body>
</html>
Loading…
Cancel
Save