Files
swimmingUni/src/pages/index/index.vue
2026-07-10 08:41:45 +08:00

1060 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="home-container">
<!-- 空状态 -->
<view class="empty-state-container">
<!-- 标题区域 -->
<view class="title-section">
<!-- <text class="main-title">暂无项目</text>
<text class="sub-title">您还没有创建任何训练项目开始您的第一次游泳训练记录吧</text> -->
</view>
<!-- 创建项目模块列表 -->
<view class="create-modules">
<!-- 计时项目模块 -->
<view @click="showTimingProjectModal(1)" class="create-card timing-card">
<view class="card-icon">
<view class="icon-circle timing-icon">
<u-icon name="clock" size="40" color="#fff"></u-icon>
</view>
</view>
<view class="card-info">
<text class="card-title">计时项目</text>
</view>
<view class="card-arrow">
<u-icon name="arrow-right" size="20" color="#1890ff"></u-icon>
</view>
</view>
<!-- 包干项目模块 -->
<view @click=" goBaogan()" class="create-card package-card">
<view class="card-icon">
<view class="icon-circle package-icon">
<u-icon name="grid" size="40" color="#fff"></u-icon>
</view>
</view>
<view class="card-info">
<text class="card-title">包干项目</text>
</view>
<view class="card-arrow">
<u-icon name="arrow-right" size="20" color="#1890ff"></u-icon>
</view>
</view>
<!-- 分段项目模块 -->
<view @click="showTimingProjectModal(2)" class="create-card segment-card">
<view class="card-icon">
<view class="icon-circle segment-icon">
<u-icon name="list" size="40" color="#fff"></u-icon>
</view>
</view>
<view class="card-info">
<text class="card-title">分段项目</text>
</view>
<view class="card-arrow">
<u-icon name="arrow-right" size="20" color="#1890ff"></u-icon>
</view>
</view>
<!-- 混氧项目模块 -->
<view @click="showTimingProjectModal(3)" class="create-card hunyang-card">
<view class="card-icon">
<view class="icon-circle hunyang-icon">
<u-icon name="grid" size="40" color="#fff"></u-icon>
</view>
</view>
<view class="card-info">
<text class="card-title">混氧项目</text>
</view>
<view class="card-arrow">
<u-icon name="arrow-right" size="20" color="#1890ff"></u-icon>
</view>
</view>
</view>
<!-- <view @click="Service.GoPage('/pages/userFunc/cs')" class="">
cs
</view> -->
</view>
<!-- 计时项目列表弹窗 -->
<view v-if="showTimingModal" class="modal-overlay" @click="closeTimingProjectModal"></view>
<view v-if="showTimingModal" class="project-list-modal">
<view class="modal-header">
<text class="modal-title"> {{ currentIndex==1?"计时项目":(currentIndex==2?"分段项目":"混氧项目") }} </text>
<view class="modal-close" @click="closeTimingProjectModal">
<u-icon name="close" size="20" color="#999"></u-icon>
</view>
</view>
<view class="modal-content">
<view class="add-btn" @click="createTimingProject">
<u-icon name="plus" size="18" color="#1890ff"></u-icon>
<text class="add-btn-text">新增项目</text>
</view>
<!-- 项目列表 -->
<view class="project-list">
<text class="list-title" v-if="projects.length > 0">项目列表</text>
<scroll-view v-if="projects.length > 0" scroll-y="true" @scrolltolower='getList()'
class="list-container">
<view v-for="(project,index) in projects" :key="project.planId" class="project-item"
@click="handleProjectClick(project)">
<view class="item-icon">
<view class="icon-bg">
<text class="icon-text">{{ index+1 }}</text>
</view>
</view>
<view class="item-info">
<text class="item-name">{{ project.name }}</text>
<text v-if="currentIndex!==3" class="item-count">{{ project.users.length }}</text>
</view>
</view>
</scroll-view>
<view v-else class="empty-project">
<text class="empty-text">暂无项目</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { Service } from '@/Service/Service'
import { onLoad, onShow } from '@dcloudio/uni-app'
import { loginService } from '@/Service/swimming/loginService'
import { PlanService } from '@/Service/swimming/PlanService'
import { userService } from '@/Service/swimming/userService'
let currentIndex = ref(0)
// 分页相关
let page = ref(1)
let status = ref('loadmore')
// 模拟项目数据
const projects = ref<Array<any>>([])
// 弹窗状态
const showTimingModal = ref(false)
let isUse = ref(false)
onLoad(() => {
if (!Service.GetUserToken()) {
login()
} else {
isUseFunc()
}
})
onShow(() => {
showTimingModal.value = false
uni.showTabBar()
})
const login = () => {
uni.getProvider({
service: 'oauth',
success: function (res : any) {
uni.login({
onlyAuthorize: true,
provider: res.provider,
success: function (loginRes) {
loginService.Login(
loginRes.code,
).then(content => {
if (content.code == 0) {
Service.SetUserToken(content.data.token)
isUseFunc()
} else {
Service.Msg(content.msg)
}
})
},
fail: function (error) {
}
});
}
});
}
const isUseFunc = () => {
userService.IsCheakUserVerify().then(res => {
if (res.code == 0) {
isUse.value = res.data.isCheak
} else {
Service.Msg(res.msg)
}
})
}
// 获取项目列表数据
const getData = () => {
projects.value = []
page.value = 1
status.value = 'loadmore'
getList()
}
// 获取项目列表
const getList = () => {
if (status.value == 'loading' || status.value == 'nomore') {
return
}
status.value = 'loading'
PlanService.GetPlanList(currentIndex.value == 1 ? '计时项目' : (currentIndex.value == 2 ? '分段项目' : '混氧项目'), page.value.toString()).then(res => {
if (res.code == 0) {
projects.value = [...projects.value, ...res.data]
status.value = res.data.length == 10 ? 'loadmore' : 'nomore'
page.value++
} else {
Service.Msg(res.msg)
}
})
}
const goBaogan = () => {
if (!isUse.value) {
Service.Msg('用户暂无权限,请等待审核!')
return
}
Service.GoPage('/pages/userFunc/project')
}
// 显示计时项目列表弹窗
const showTimingProjectModal = (index : any) => {
if (!isUse.value) {
Service.Msg('用户暂无权限,请等待审核!')
return
}
uni.hideTabBar()
showTimingModal.value = true
currentIndex.value = index
getData()
}
// 关闭计时项目列表弹窗
const closeTimingProjectModal = () => {
showTimingModal.value = false
uni.showTabBar()
}
// 创建计时项目
const createTimingProject = () => {
if (currentIndex.value == 3) {
Service.GoPage('/pages/userFunc/addHunyang')
} else {
Service.GoPage('/pages/userFunc/setCourse?type=' + currentIndex.value)
}
}
// 处理项目点击事件
const handleProjectClick = (project : any) => {
if (currentIndex.value === 1) {
showTimingModal.value = false
Service.GoPage('/pages/userFunc/swiming?id=' + project.planId)
}
else if (currentIndex.value === 2) {
showTimingModal.value = false
Service.GoPage('/pages/userFunc/segmentation?id=' + project.planId)
}
else {
showTimingModal.value = false
Service.GoPage('/pages/userFunc/hunyang?id=' + project.planId)
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #f5f5f5;
}
.home-container {
padding: 20rpx;
}
/* 项目列表区域 */
.projects-section {
padding: 20rpx 0;
}
/* 计时器卡片 */
.timer-card {
background-color: #fff;
border-radius: 24rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
.timer-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
.timer-title {
font-size: 32rpx;
font-weight: 700;
color: #333;
}
.task-count {
font-size: 24rpx;
color: #999;
font-weight: 500;
}
}
.timer-content {
display: flex;
align-items: center;
gap: 30rpx;
&:active {
transform: scale(0.98);
}
}
.timer-circle {
position: relative;
}
.circle-progress {
width: 120rpx;
height: 120rpx;
position: relative;
}
.progress-ring {
width: 100%;
height: 100%;
border-radius: 50%;
background: conic-gradient(#1890ff 0deg, #40a9ff 360deg, #1890ff 100%);
display: flex;
align-items: center;
justify-content: center;
animation: progress 1.5s ease-in-out infinite;
}
.ring-inner {
width: 85%;
height: 85%;
background: radial-gradient(circle, #fff 30%, transparent 70%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.15);
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
}
}
}
@keyframes progress {
0% {
background: #40a9ff;
}
100% {
background: #1890ff;
}
}
.timer-info {
flex: 1;
.timer-name {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
display: block;
}
.timer-time {
font-size: 24rpx;
color: #666;
}
}
/* 空状态 */
.empty-state-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 20rpx;
}
/* 标题区域 */
.title-section {
text-align: center;
margin-bottom: 60rpx;
.main-title {
font-size: 42rpx;
font-weight: 700;
color: #333;
display: block;
margin-bottom: 16rpx;
}
.sub-title {
font-size: 26rpx;
color: #999;
line-height: 1.6;
}
}
/* 创建项目模块列表 */
.create-modules {
width: 100%;
max-width: 600rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
}
/* 创建项目卡片 */
.create-card {
background-color: #fff;
border-radius: 24rpx;
padding: 32rpx 28rpx;
display: flex;
align-items: center;
gap: 30rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
position: relative;
overflow: hidden;
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
}
.card-icon {
flex-shrink: 0;
margin-right: 24rpx;
}
.icon-circle {
width: 96rpx;
height: 96rpx;
border-radius: 22rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.12);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
&:active {
transform: scale(0.9);
}
/* 计时图标 - 蓝色渐变 */
&.timing-icon {
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 50%, #096dd9 100%);
}
/* 包干图标 - 橙色渐变 */
&.package-icon {
background: linear-gradient(135deg, #faad14 0%, #ffc53d 50%, #d48806 100%);
}
/* 分段图标 - 绿色渐变 */
&.segment-icon {
background: linear-gradient(135deg, #52c41a 0%, #73d13d 50%, #389e0d 100%);
}
/* 混氧图标 - 紫色渐变 */
&.hunyang-icon {
background: linear-gradient(135deg, #722ed1 0%, #9254de 50%, #531dab 100%);
}
}
&::after {
content: '';
position: absolute;
top: 6rpx;
right: 6rpx;
width: 18rpx;
height: 18rpx;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
}
.card-info {
flex: 1;
min-width: 0;
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.card-desc {
font-size: 24rpx;
color: #999;
line-height: 1.4;
}
.card-arrow {
flex-shrink: 0;
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background: linear-gradient(135deg, rgba(24, 144, 255, 0.1) 0%, rgba(24, 144, 255, 0.05) 100%);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&:active {
transform: scale(1.1);
}
}
/* 弹窗通用通用样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.4) 100%);
backdrop-filter: blur(10rpx);
z-index: 998;
animation: fadeIn 0.3s ease;
height: 100vh;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 36rpx 32rpx 28rpx;
background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
.modal-title {
font-size: 36rpx;
font-weight: 700;
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.modal-close {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background: linear-gradient(135deg, #f5f5f5 0%, #f0f0f0 100%);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
&:active {
background: linear-gradient(135deg, #e8e8e8 0%, #e0e0e0 100%);
transform: scale(0.92);
}
}
}
/* 计时项目列表弹窗 */
.project-list-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-width: 680rpx;
height: 80vh;
background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
border-radius: 32rpx;
z-index: 999;
animation: slideUp 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: 0 24rpx 80rpx rgba(0, 0, 0, 0.25), 0 0 0 1rpx rgba(0, 0, 0, 0.04);
overflow: hidden;
display: flex;
flex-direction: column;
}
.modal-content {
flex: 1;
padding: 24rpx 28rpx;
overflow-y: auto;
}
/* 新增按钮 */
.add-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
width: 100%;
height: 96rpx;
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 50%, #36cfc9 100%);
background-size: 200% 200%;
border-radius: 20rpx;
margin-bottom: 36rpx;
box-shadow: 0 8rpx 24rpx rgba(24, 144, 255, 0.35), 0 0 0 1rpx rgba(255, 255, 255, 0.5) inset;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
animation: shimmer 2s infinite;
}
&:active {
transform: scale(0.97);
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.25);
}
.add-btn-text {
font-size: 32rpx;
color: #fff;
font-weight: 600;
letter-spacing: 2rpx;
position: relative;
z-index: 1;
}
}
/* 项目列表 */
.project-list {
margin-bottom: 20rpx;
.list-title {
font-size: 30rpx;
font-weight: 700;
color: #333;
display: block;
margin-bottom: 24rpx;
padding-left: 12rpx;
border-left: 4rpx solid #1890ff;
background: linear-gradient(90deg, rgba(24, 144, 255, 0.05) 0%, transparent 100%);
padding: 8rpx 12rpx;
border-radius: 0 8rpx 8rpx 0;
}
.list-container {
display: flex;
flex-direction: column;
gap: 16rpx;
max-height: 800rpx;
}
.project-item {
display: flex;
align-items: center;
gap: 20rpx;
padding: 28rpx 24rpx;
background: linear-gradient(135deg, #fff 0%, #fafbfc 100%);
// border-radius: 20rpx;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06), 0 0 0 1rpx rgba(0, 0, 0, 0.04) inset;
position: relative;
overflow: hidden;
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4rpx;
height: 100%;
background: linear-gradient(180deg, #1890ff 0%, #40a9ff 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.item-icon {
flex-shrink: 0;
.icon-bg {
width: 72rpx;
height: 72rpx;
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 50%, #36cfc9 100%);
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.25);
position: relative;
&::before {
content: '';
position: absolute;
top: 8rpx;
right: 8rpx;
width: 12rpx;
height: 12rpx;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
}
.icon-text {
font-size: 34rpx;
color: #fff;
font-weight: 700;
letter-spacing: 2rpx;
}
}
}
.item-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 8rpx;
.item-name {
font-size: 30rpx;
font-weight: 600;
color: #333;
letter-spacing: 1rpx;
}
.item-count {
font-size: 24rpx;
color: #999;
font-weight: 500;
}
}
.item-arrow {
flex-shrink: 0;
width: 52rpx;
height: 52rpx;
border-radius: 50%;
background: linear-gradient(135deg, rgba(24, 144, 255, 0.1) 0%, rgba(64, 169, 255, 0.05) 100%);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
}
.empty-project {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 20rpx;
gap: 16rpx;
.empty-text {
font-size: 28rpx;
color: #999;
font-weight: 500;
}
&::before {
content: '';
width: 120rpx;
height: 120rpx;
background: linear-gradient(135deg, rgba(24, 144, 255, 0.08) 0%, rgba(64, 169, 255, 0.03) 100%);
border-radius: 28rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8rpx;
}
}
}
/* 项目详情弹窗 */
.project-detail-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-width: 680rpx;
max-height: 80vh;
background-color: #fff;
border-radius: 28rpx;
z-index: 999;
animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.2);
overflow: hidden;
display: flex;
flex-direction: column;
}
.detail-section {
padding: 24rpx 0;
margin-bottom: 24rpx;
.detail-title {
font-size: 28rpx;
font-weight: 600;
color: #666;
margin-bottom: 16rpx;
display: block;
padding-left: 8rpx;
border-left: 3rpx solid #1890ff;
}
.detail-item {
display: flex;
align-items: center;
margin-bottom: 16rpx;
.detail-label {
font-size: 26rpx;
color: #666;
min-width: 100rpx;
}
.detail-value {
font-size: 28rpx;
color: #333;
font-weight: 500;
flex: 1;
}
}
}
.detail-count {
font-size: 24rpx;
color: #999;
margin-bottom: 16rpx;
display: block;
}
.student-scroll {
max-height: 400rpx;
overflow-y: auto;
.student-item {
display: flex;
align-items: center;
gap: 16rpx;
padding: 16rpx 20rpx;
background-color: #f5f5f5;
border-radius: 16rpx;
margin-bottom: 12rpx;
.student-index {
width: 48rpx;
height: 48rpx;
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
color: #fff;
border-radius: 50%;
font-size: 22rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.student-avatar {
width: 56rpx;
height: 56rpx;
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
border-radius: 14rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
.avatar-text {
color: #fff;
font-size: 26rpx;
font-weight: 700;
}
}
.student-info {
flex: 1;
min-width: 0;
gap: 8rpx;
}
.student-name {
font-size: 26rpx;
font-weight: 600;
color: #333;
}
.student-meta {
display: flex;
align-items: center;
gap: 12rpx;
.gender-badge {
font-size: 22rpx;
padding: 4rpx 10rpx;
border-radius: 8rpx;
font-weight: 500;
&.male {
color: #1890ff;
background-color: #e6f7ff;
}
&.female {
color: #fa8c16;
background-color: #fff7e6;
}
}
.age-text {
font-size: 22rpx;
color: #999;
}
}
}
}
/* 动画 */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translate(-50%, -45%);
}
to {
opacity: 1;
transform: translate(-50%, -50%);
}
}
/* 包干项目选项样式 */
.package-options {
margin-bottom: 36rpx;
}
.form-group {
margin-bottom: 32rpx;
}
.form-label {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 16rpx;
}
.radio-group {
display: flex;
gap: 20rpx;
}
.radio-item {
flex: 1;
display: flex;
align-items: center;
gap: 12rpx;
padding: 24rpx 20rpx;
background: #f5f5f5;
border-radius: 16rpx;
border: 2rpx solid transparent;
transition: all 0.3s ease;
}
.radio-item.active {
background: linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%);
border-color: #1890ff;
}
.radio-item:active {
transform: scale(0.98);
}
.radio-circle {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
border: 3rpx solid #d9d9d9;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.radio-item.active .radio-circle {
border-color: #1890ff;
}
.radio-dot {
width: 18rpx;
height: 18rpx;
border-radius: 50%;
background: #1890ff;
}
.radio-label {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
</style>