feat: 添加教练详情页面及相关数据模型

This commit is contained in:
richarjiang
2026-04-13 17:08:17 +08:00
parent 1f45c3dc3f
commit f78cdcc9d1
4 changed files with 753 additions and 62 deletions

View File

@@ -0,0 +1,442 @@
<template>
<view class="teacher-detail-page">
<CustomNavBar title="教练详情" show-back />
<scroll-view class="detail-scroll" scroll-y :style="{ height: scrollHeight }">
<view class="nav-spacer" :style="{ height: navBarHeight }" />
<view class="hero-section">
<image class="hero-image" :src="teacher.cover" mode="aspectFill" />
<view class="hero-overlay" />
<view class="hero-content">
<view class="hero-badges">
<text v-for="item in teacher.badges" :key="item" class="hero-badge">{{ item }}</text>
</view>
<text class="hero-name">{{ teacher.name }}</text>
<text class="hero-title">{{ teacher.title }}</text>
</view>
</view>
<view class="content-wrap">
<view class="summary-card">
<view class="summary-head">
<view>
<text class="summary-eyebrow">Private Pilates Coach</text>
<text class="summary-title">为你定制更细腻的训练节奏</text>
</view>
</view>
<view class="specialty-row">
<text v-for="item in teacher.specialties" :key="item" class="specialty-pill">{{ item }}</text>
</view>
<text class="summary-intro">{{ teacher.intro }}</text>
<view class="stats-grid">
<view v-for="item in teacher.stats" :key="item.label" class="stat-item">
<text class="stat-value">{{ item.value }}</text>
<text class="stat-label">{{ item.label }}</text>
</view>
</view>
</view>
<view class="detail-card">
<text class="card-title">教练介绍</text>
<text class="card-text">
Iris 注重基础控制与身体感知课程会围绕呼吸核心稳定动作路径和发力节奏展开帮助学员在安全前提下持续进步
</text>
<text class="card-text">
无论你希望改善久坐体态建立训练基础还是进一步雕塑身体线条都可以通过私教课程获得更清晰的动作反馈与进阶方案
</text>
</view>
<view class="detail-card">
<text class="card-title">认证背景</text>
<view v-for="item in teacher.certifications" :key="item" class="list-row">
<view class="list-dot" />
<text class="list-text">{{ item }}</text>
</view>
</view>
<view class="detail-card">
<text class="card-title">授课重点</text>
<view class="focus-grid">
<view v-for="item in teacher.teachingFocus" :key="item.title" class="focus-item">
<text class="focus-title">{{ item.title }}</text>
<text class="focus-desc">{{ item.desc }}</text>
</view>
</view>
</view>
<view class="detail-card cta-card">
<text class="card-title">适合这样的你</text>
<text class="card-text">久坐肩颈紧张想改善圆肩骨盆前倾训练没感觉或想通过更稳定的核心训练提升身体线条与控制力</text>
<view class="cta-inline" @tap="goToBooking">
<text class="cta-inline-text">去约 Iris 的课程</text>
<text class="cta-inline-arrow"></text>
</view>
</view>
<view class="bottom-space" />
</view>
</scroll-view>
<view class="bottom-bar">
<view class="bottom-note">
<text class="bottom-note-title">1 1 私教课</text>
<text class="bottom-note-sub">60 分钟 · 进入约课页选择时段</text>
</view>
<view class="booking-btn" @tap="goToBooking">
<text class="booking-btn-text">立即预约</text>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import CustomNavBar from '../../components/CustomNavBar.vue'
import { irisProfile } from '../../utils/teacher'
import { getSystemLayout } from '../../utils/system'
const teacher = irisProfile
const navBarHeight = ref('64px')
const scrollHeight = ref('500px')
onLoad(() => {
updateLayout()
})
function updateLayout() {
const { navBarHeight: navBarPx } = getSystemLayout()
const { windowHeight } = uni.getWindowInfo()
navBarHeight.value = `${navBarPx}px`
scrollHeight.value = `${windowHeight}px`
}
function goToBooking() {
uni.switchTab({ url: '/pages/booking/index' })
}
</script>
<style lang="scss" scoped>
.teacher-detail-page {
height: 100vh;
background: #f7f2ee;
box-sizing: border-box;
}
.nav-spacer {
width: 100%;
flex-shrink: 0;
}
.hero-section {
position: relative;
height: 580rpx;
overflow: hidden;
}
.hero-image {
width: 100%;
height: 100%;
}
.hero-overlay {
position: absolute;
inset: 0;
background:
linear-gradient(180deg, rgba(33, 24, 22, 0.08) 0%, rgba(33, 24, 22, 0.08) 34%, rgba(33, 24, 22, 0.64) 100%),
radial-gradient(circle at top right, rgba(255, 228, 208, 0.26), transparent 28%);
}
.hero-content {
position: absolute;
left: 32rpx;
right: 32rpx;
bottom: 92rpx;
z-index: 1;
}
.hero-badges {
display: flex;
gap: 10rpx;
margin-bottom: 18rpx;
}
.hero-badge {
height: 42rpx;
padding: 0 16rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.18);
border: 1rpx solid rgba(255, 255, 255, 0.24);
color: #fff8f3;
font-size: 20rpx;
backdrop-filter: blur(10rpx);
}
.hero-name {
display: block;
font-size: 56rpx;
font-weight: 700;
color: #ffffff;
line-height: 1.05;
margin-bottom: 10rpx;
}
.hero-title {
font-size: 26rpx;
color: rgba(255, 247, 241, 0.88);
}
.detail-scroll {
width: 100%;
}
.content-wrap {
position: relative;
margin-top: -8rpx;
padding: 0 24rpx;
}
.summary-card,
.detail-card {
background: rgba(255, 255, 255, 0.92);
border-radius: 28rpx;
box-shadow: 0 16rpx 36rpx rgba(120, 91, 79, 0.08);
backdrop-filter: blur(12rpx);
}
.summary-card {
padding: 28rpx;
margin-bottom: 18rpx;
}
.summary-head {
margin-bottom: 18rpx;
}
.summary-eyebrow {
display: block;
font-size: 20rpx;
color: #c09a89;
letter-spacing: 3rpx;
text-transform: uppercase;
margin-bottom: 8rpx;
}
.summary-title {
font-size: 34rpx;
line-height: 1.35;
font-weight: 700;
color: #2f2622;
}
.specialty-row {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
margin-bottom: 18rpx;
}
.specialty-pill {
padding: 10rpx 18rpx;
border-radius: 999rpx;
background: rgba(92, 126, 151, 0.12);
color: #56778a;
font-size: 22rpx;
font-weight: 600;
}
.summary-intro,
.card-text,
.list-text {
font-size: 25rpx;
line-height: 1.78;
color: #5f534c;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14rpx;
margin-top: 24rpx;
}
.stat-item {
padding: 20rpx 16rpx;
border-radius: 20rpx;
background: linear-gradient(180deg, #fff8f4 0%, #f7f1ec 100%);
text-align: center;
}
.stat-value {
display: block;
font-size: 24rpx;
line-height: 1.45;
font-weight: 700;
color: #3f3029;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 20rpx;
color: #9c8579;
}
.detail-card {
padding: 28rpx;
margin-bottom: 18rpx;
}
.card-title {
display: block;
font-size: 30rpx;
font-weight: 700;
color: #342925;
margin-bottom: 18rpx;
}
.card-text + .card-text {
margin-top: 16rpx;
}
.list-row {
display: flex;
align-items: flex-start;
gap: 14rpx;
}
.list-row + .list-row {
margin-top: 18rpx;
}
.list-dot {
width: 14rpx;
height: 14rpx;
border-radius: 50%;
margin-top: 14rpx;
flex-shrink: 0;
background: linear-gradient(135deg, #ff8b69 0%, #ff593f 100%);
}
.focus-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14rpx;
}
.focus-item {
min-height: 164rpx;
border-radius: 22rpx;
padding: 22rpx 18rpx;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
background: linear-gradient(135deg, #fff8f5 0%, #f6eee9 100%);
}
.focus-title {
font-size: 24rpx;
font-weight: 600;
color: #5c4a42;
}
.focus-desc {
margin-top: 10rpx;
font-size: 21rpx;
line-height: 1.65;
color: #8a7569;
}
.cta-card {
background: linear-gradient(135deg, #fff6f1 0%, #fffdfc 100%);
}
.cta-inline {
margin-top: 22rpx;
height: 88rpx;
border-radius: 999rpx;
padding: 0 28rpx;
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(135deg, #ff7f5e 0%, #ff543c 100%);
box-shadow: 0 16rpx 28rpx rgba(255, 95, 73, 0.22);
}
.cta-inline-text,
.cta-inline-arrow {
color: #ffffff;
font-weight: 700;
}
.cta-inline-text {
font-size: 28rpx;
}
.cta-inline-arrow {
font-size: 38rpx;
line-height: 1;
}
.bottom-space {
height: 160rpx;
}
.bottom-bar {
position: fixed;
left: 24rpx;
right: 24rpx;
bottom: 24rpx;
z-index: 20;
display: flex;
align-items: center;
gap: 18rpx;
padding: 18rpx;
border-radius: 28rpx;
background: rgba(35, 29, 27, 0.9);
backdrop-filter: blur(18rpx);
box-shadow: 0 20rpx 48rpx rgba(36, 28, 26, 0.18);
}
.bottom-note {
flex: 1;
min-width: 0;
}
.bottom-note-title {
display: block;
font-size: 26rpx;
font-weight: 700;
color: #fffaf6;
margin-bottom: 6rpx;
}
.bottom-note-sub {
font-size: 20rpx;
color: rgba(255, 244, 236, 0.72);
}
.booking-btn {
width: 224rpx;
height: 88rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #ff815e 0%, #ff5136 100%);
}
.booking-btn-text {
font-size: 28rpx;
font-weight: 700;
color: #ffffff;
letter-spacing: 1rpx;
}
</style>