<template>
<div class="coach-page">
<!-- 搜索表单 -->
<div class="search-section">
<el-form
:model="form"
label-width="80px"
@submit.native.prevent="findCoachAll"
>
<el-form-item label="教练姓名">
<el-input
v-model="form.nickName"
placeholder="输入教练姓名"
clearable
prefix-icon="el-icon-user"
style="width: 180px; margin-right: 20px"
/>
<span class="label">预约时段</span>
<el-date-picker
v-model="startEndTime"
type="datetimerange"
range-separator="→"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="yyyy-MM-dd HH:mm"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 360px; margin-right: 20px"
@change="onTimeRangeChange"
/>
<el-button
type="primary"
icon="el-icon-search"
@click="findCoachAll"
round
>
搜索教练
</el-button>
</el-form-item>
</el-form>
<!-- 加载状态提示 -->
<div v-if="loading" class="loading-tip">
<i class="el-icon-loading"></i> 正在加载教练信息...
</div>
</div>
<!-- 教练列表 -->
<ul v-if="!loading" class="coach-list">
<li
v-for="v in coachList"
:key="v.id"
class="coach-card"
@click="booking(v.nickName, v.id)"
>
<img :src="v.pic || defaultAvatar" alt="教练头像" class="coach-img" />
<h4 class="coach-name">{{ v.nickName }}</h4>
<p class="coach-phone">
<i class="el-icon-phone-outline"></i> {{ v.phoneCode || "未公开" }}
</p>
<p class="coach-intro" v-html="formatIntroduce(v.introduce)"></p>
<el-tag size="mini" type="success" effect="light" class="tag-online">
可预约
</el-tag>
</li>
<!-- 空数据提示 -->
<li v-if="coachList.length === 0" class="empty-tip">
<i class="el-icon-document-delete"></i>
<span>暂无符合条件的教练</span>
</li>
</ul>
<!-- 分页 -->
<div v-if="total > 0 && !loading" class="pagination-container">
<el-pagination
background
layout="prev, pager, next, total, jumper"
:total="total"
:page-size="pageSize"
:current-page="pageNum"
@current-change="handlePageChange"
prev-text="上一页"
next-text="下一页"
></el-pagination>
</div>
<!-- 预约确认弹窗 -->
<el-dialog
title="确认预约"
:visible.sync="centerDialogVisible"
width="35%"
center
custom-class="booking-dialog"
append-to-body
>
<div class="dialog-content">
<i class="el-icon-warning-outline dialog-icon"></i>
<p>确定要预约</p>
<strong>{{ bookingContent }}</strong>
<p>教练吗?请确保时间段已选好。</p>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="centerDialogVisible = false" size="medium"
>取 消</el-button
>
<el-button type="primary" @click="bookingCoach" size="medium"
>确 定 预 约</el-button
>
</span>
</el-dialog>
</div>
</template>
<script>
import { findCoachAll } from "@/api/user";
import { saveUserCoach } from "@/api/user";
export default {
data() {
return {
pageSize: 8,
pageNum: 1,
total: 0,
loading: false,
bookingContent: "",
startEndTime: [],
centerDialogVisible: false,
coachList: [],
form: {
startTime: "",
endTime: "",
nickName: "",
resname: "",
page: 1,
size: 8,
},
userCoach: {
userId: "",
coachId: "",
startTime: "",
endTime: "",
},
defaultAvatar: "", // 默认头像
};
},
created() {
this.findCoachAll();
},
methods: {
formatIntroduce(text) {
if (!text) return "<span style='color:#999'>暂无个人介绍</span>";
return text
.replace(/\n/g, "<br/>")
.replace(/(^\s+|\s+$)/g, "") // 去首尾空格
.replace(/<br\/>\s*<br\/>/g, "<br/><br/>"); // 合并多余换行
},
onTimeRangeChange(val) {
if (val) {
this.form.startTime = val[0];
this.form.endTime = val[1];
this.userCoach.startTime = val[0];
this.userCoach.endTime = val[1];
} else {
this.form.startTime = "";
this.form.endTime = "";
this.userCoach.startTime = "";
this.userCoach.endTime = "";
}
},
booking(nickName, id) {
this.bookingContent = nickName;
this.userCoach.coachId = id;
this.centerDialogVisible = true;
},
async bookingCoach() {
const userId = localStorage.getItem("userId");
if (!userId) {
this.$message.error("用户未登录!");
this.centerDialogVisible = false;
return;
}
this.userCoach.userId = userId;
if (!this.userCoach.startTime || !this.userCoach.endTime) {
this.$message.warning("请先选择有效的预约时间段!");
return;
}
try {
const resp = await saveUserCoach(this.userCoach);
if (resp.data.code === 200) {
this.$message.success({
message: resp.data.message || "预约成功!",
duration: 2000,
});
} else {
this.$message.error(resp.data.message || "预约失败,请重试");
}
} catch (err) {
console.error("预约请求失败:", err);
this.$message.error("网络异常,请稍后重试");
} finally {
this.centerDialogVisible = false;
this.userCoach.coachId = ""; // 清空选择
}
},
async findCoachAll() {
this.loading = true;
this.form.page = this.pageNum;
this.form.size = this.pageSize;
try {
const resp = await findCoachAll(this.form);
const data = resp.data?.data?.courses;
if (data && Array.isArray(data.list)) {
this.coachList = data.list;
this.total = data.total;
} else {
this.coachList = [];
this.total = 0;
this.$message.info("暂无相关教练信息");
}
} catch (err) {
console.error("获取教练列表失败:", err);
this.$message.error("网络错误,无法加载教练数据");
this.coachList = [];
this.total = 0;
} finally {
this.loading = false;
}
},
handlePageChange(page) {
this.pageNum = page;
this.findCoachAll();
},
},
};
</script>
<style scoped>
.coach-page {
padding: 20px;
background-color: #f7f9fc;
min-height: 100vh;
font-family: "Helvetica Neue", Arial, sans-serif;
}
/* 搜索区域 */
.search-section {
background: #ffffff;
padding: 24px;
border-radius: 16px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
margin-bottom: 28px;
border: 1px solid #e0e3eb;
}
.label {
font-size: 14px;
color: #5a5a5a;
margin-right: 8px;
}
.search-section .el-form-item {
margin-bottom: 0;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.search-section .el-input,
.search-section .el-date-picker {
transition: all 0.3s ease;
}
.search-section .el-input:focus-within,
.search-section .el-date-editor:focus {
transform: scale(1.02);
}
/* 加载提示 */
.loading-tip {
text-align: center;
color: #666;
font-size: 14px;
margin-top: 16px;
padding: 12px;
background: #f0f4ff;
border-radius: 8px;
border-left: 4px solid #409eff;
}
/* 教练列表网格 */
.coach-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 24px;
list-style: none;
padding: 0;
margin: 0;
}
.coach-card {
position: relative;
background: #fff;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.07);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
cursor: pointer;
text-align: center;
padding-bottom: 16px;
border: 1px solid #edf0f5;
}
.coach-card:hover {
transform: translateY(-8px);
box-shadow: 0 16px 30px rgba(0, 0, 0, 0.15);
border-color: #cdd4df;
}
.coach-img {
width: 100%;
height: 160px;
object-fit: cover;
border-bottom: 1px solid #eee;
background-color: #f8f8f8;
}
.coach-name {
margin: 14px 0 6px;
font-size: 17px;
font-weight: 600;
color: #2c3e50;
}
.coach-phone {
margin: 6px 0;
color: #4a90e2;
font-size: 14px;
}
.coach-intro {
margin: 8px 12px 0;
color: #666;
font-size: 13px;
line-height: 1.6;
min-height: 42px;
max-height: 60px;
overflow: hidden;
text-overflow: ellipsis;
}
.tag-online {
position: absolute;
top: 12px;
right: 12px;
font-size: 12px;
padding: 4px 8px;
}
.empty-tip {
grid-column: 1 / -1;
text-align: center;
color: #999;
font-size: 15px;
padding: 60px 0;
}
.empty-tip i {
font-size: 36px;
margin-bottom: 10px;
color: #ccc;
}
/* 分页容器 */
.pagination-container {
margin: 40px auto 20px;
text-align: center;
}
::v-deep .el-pagination .btn-prev,
::v-deep .el-pagination .btn-next,
::v-deep .el-pagination .number {
border-radius: 8px !important;
margin: 0 4px;
}
::v-deep .el-pagination .number.current {
background-color: #409eff !important;
color: #fff;
}
/* 弹窗样式 */
::v-deep .booking-dialog {
border-radius: 16px;
overflow: hidden;
}
::v-deep .booking-dialog .el-dialog__header {
background-color: #fef6f6;
border-bottom: 1px dashed #fbc0c0;
}
::v-deep .booking-dialog .el-dialog__title {
color: #d32f2f;
font-weight: 600;
letter-spacing: 0.5px;
}
.dialog-content {
text-align: center;
font-size: 16px;
color: #333;
line-height: 1.8;
}
.dialog-icon {
font-size: 30px;
color: #faad14;
margin-bottom: 10px;
display: block;
}
.dialog-content strong {
color: #e74c3c;
font-size: 18px;
}
</style>
再漂亮点