保存
This commit is contained in:
@@ -1,8 +1,7 @@
|
|||||||
export class BaseConfig {
|
export class BaseConfig {
|
||||||
// protected static servesUrl: string = "http://192.168.0.142:5298";
|
// protected static servesUrl: string = "http://192.168.0.142:5300";
|
||||||
// protected static imgUrl: string = "http://192.168.0.142:5298";
|
// protected static imgUrl: string = "http://192.168.0.142:5300";
|
||||||
// protected static mediaUrl: string = "http://192.168.0.142:5298/";
|
// protected static mediaUrl: string = "http://192.168.0.142:5300/";
|
||||||
|
|
||||||
|
|
||||||
protected static servesUrl: string = "https://swimming.api.xypays.cn";
|
protected static servesUrl: string = "https://swimming.api.xypays.cn";
|
||||||
protected static imgUrl: string = "https://swimming.api.xypays.cn";
|
protected static imgUrl: string = "https://swimming.api.xypays.cn";
|
||||||
|
|||||||
@@ -76,8 +76,9 @@
|
|||||||
{
|
{
|
||||||
"path": "project",
|
"path": "project",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "包干"
|
"navigationBarTitleText": "包干",
|
||||||
|
"pageOrientation": "auto",
|
||||||
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -89,7 +90,9 @@
|
|||||||
{
|
{
|
||||||
"path": "hunyang",
|
"path": "hunyang",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "混氧"
|
"navigationBarTitleText": "混氧",
|
||||||
|
"pageOrientation": "auto",
|
||||||
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -97,12 +100,18 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "创建项目"
|
"navigationBarTitleText": "创建项目"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "cs",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "测试",
|
||||||
|
"pageOrientation": "auto"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
"root": "pages/dataAnalyze",
|
"root": "pages/dataAnalyze",
|
||||||
"pages": [
|
"pages": [{
|
||||||
{
|
|
||||||
"path": "baoganAnalyze",
|
"path": "baoganAnalyze",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "包干数据"
|
"navigationBarTitleText": "包干数据"
|
||||||
|
|||||||
@@ -11,7 +11,31 @@
|
|||||||
<u-icon name="arrow-right" size="16" color="#bbb"></u-icon>
|
<u-icon name="arrow-right" size="16" color="#bbb"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<up-picker v-model:show="showProject" keyName="name" valueName="planId" @confirm="selectProcetFunc" :columns="projectOptions"></up-picker>
|
<!-- 项目选择弹窗 -->
|
||||||
|
<up-popup :show="showProject" mode="center" round="32" :safeAreaInsetBottom="false"
|
||||||
|
@close="showProject = false">
|
||||||
|
<view class="project-popup-content">
|
||||||
|
<view class="modal-header">
|
||||||
|
<text class="modal-title">选择项目</text>
|
||||||
|
<view class="modal-close" @click="showProject = false">
|
||||||
|
<u-icon name="close" size="20" color="#999"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="popup-project-list">
|
||||||
|
<view class="project-list-inner">
|
||||||
|
<view v-for="(project, index) in projectOptions[0]" :key="project.planId" class="project-item"
|
||||||
|
@click="selectProcetFunc(project)">
|
||||||
|
<view class="item-info">
|
||||||
|
<text class="item-name">{{ project.name }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="!projectOptions[0] || projectOptions[0].length === 0" class="empty-project">
|
||||||
|
<text class="empty-text">暂无项目</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</up-popup>
|
||||||
|
|
||||||
<!-- 日期选择卡片 -->
|
<!-- 日期选择卡片 -->
|
||||||
<view class="filter-card " v-if="selectProcet">
|
<view class="filter-card " v-if="selectProcet">
|
||||||
@@ -46,9 +70,6 @@
|
|||||||
<view v-for="student in projectStudents" :key="student.studentId"
|
<view v-for="student in projectStudents" :key="student.studentId"
|
||||||
:class="['student-item', { selected: selectedStudentIds.includes(student.studentId) }]"
|
:class="['student-item', { selected: selectedStudentIds.includes(student.studentId) }]"
|
||||||
@click="toggleStudent(student.studentId)">
|
@click="toggleStudent(student.studentId)">
|
||||||
<view class="student-avatar">
|
|
||||||
<text class="avatar-text">{{ student.name.charAt(0) }}</text>
|
|
||||||
</view>
|
|
||||||
<text class="student-name">{{ student.name }}</text>
|
<text class="student-name">{{ student.name }}</text>
|
||||||
<view class="check-icon" v-if="selectedStudentIds.includes(student.studentId)">
|
<view class="check-icon" v-if="selectedStudentIds.includes(student.studentId)">
|
||||||
<u-icon name="checkmark" size="14" color="#fff"></u-icon>
|
<u-icon name="checkmark" size="14" color="#fff"></u-icon>
|
||||||
@@ -58,7 +79,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 图表区域 -->
|
<!-- 图表区域 -->
|
||||||
<view class="chart-section" v-if="selectedStudentIds.length > 0">
|
<view class="chart-section" v-if="selectedStudentIds.length > 0 && !showCalendar">
|
||||||
<!-- 统计概览 -->
|
<!-- 统计概览 -->
|
||||||
<view class="stats-card">
|
<view class="stats-card">
|
||||||
<view class="stat-item">
|
<view class="stat-item">
|
||||||
@@ -87,7 +108,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 日历弹窗 -->
|
<!-- 日历弹窗 -->
|
||||||
<up-calendar :show="showCalendar" mode="date" minDate="1776240407000" @confirm="calendarConfirm" @close="calendarClose"></up-calendar>
|
<up-calendar :show="showCalendar" mode="date" minDate="1776240407000" zIndex="1000" @confirm="calendarConfirm" @close="calendarClose"></up-calendar>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -96,6 +117,7 @@
|
|||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { PlanService } from '@/Service/swimming/PlanService'
|
import { PlanService } from '@/Service/swimming/PlanService'
|
||||||
|
import { studentService } from '@/Service/swimming/studentService'
|
||||||
|
|
||||||
const MAX_SELECTED_STUDENTS = 3
|
const MAX_SELECTED_STUDENTS = 3
|
||||||
const chartColors = ['#52c41a', '#1890ff', '#faad14']
|
const chartColors = ['#52c41a', '#1890ff', '#faad14']
|
||||||
@@ -191,9 +213,10 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectProcetFunc = (e: any) => {
|
const selectProcetFunc = (project: any) => {
|
||||||
selectProcet.value = e.value[0].name
|
selectProcet.value = project.name
|
||||||
selectId.value = e.value[0].planId
|
selectId.value = project.planId
|
||||||
|
showProject.value = false
|
||||||
selectedStudentIds.value = []
|
selectedStudentIds.value = []
|
||||||
begin.value = ''
|
begin.value = ''
|
||||||
end.value = ''
|
end.value = ''
|
||||||
@@ -218,13 +241,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadProjectStudents = (projectId: string) => {
|
const loadProjectStudents = (projectId: string) => {
|
||||||
PlanService.GetPlanInfo(projectId).then(res => {
|
studentService.GetStudentList().then(res => {
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
projectStudents.value = res.data.plan.users
|
projectStudents.value = res.data
|
||||||
} else {
|
} else {
|
||||||
Service.Msg(res.msg)
|
Service.Msg(res.msg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleStudent = (studentId: string) => {
|
const toggleStudent = (studentId: string) => {
|
||||||
@@ -262,6 +286,7 @@
|
|||||||
res.data.list.map((item: any) => {
|
res.data.list.map((item: any) => {
|
||||||
lineChartData.value.categories.push(item.dayTime)
|
lineChartData.value.categories.push(item.dayTime)
|
||||||
item.data.map((content: any, index: any) => {
|
item.data.map((content: any, index: any) => {
|
||||||
|
|
||||||
xData.value[index].name = content.studentName
|
xData.value[index].name = content.studentName
|
||||||
xData.value[index].data.push(content.time)
|
xData.value[index].data.push(content.time)
|
||||||
})
|
})
|
||||||
@@ -469,6 +494,8 @@
|
|||||||
/* 图表区域 */
|
/* 图表区域 */
|
||||||
.chart-section {
|
.chart-section {
|
||||||
margin-top: 4rpx;
|
margin-top: 4rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-card {
|
.stats-card {
|
||||||
@@ -549,4 +576,117 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 项目选择弹窗 */
|
||||||
|
.project-popup-content {
|
||||||
|
width: 680rpx;
|
||||||
|
max-height: 70vh;
|
||||||
|
background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
|
||||||
|
border-radius: 32rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 0 24rpx 80rpx rgba(0, 0, 0, 0.25), 0 0 0 1rpx rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-project-list {
|
||||||
|
max-height: 55vh;
|
||||||
|
padding: 24rpx 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
background: linear-gradient(135deg, #fff 0%, #fafbfc 100%);
|
||||||
|
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;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
|
||||||
|
&::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-project {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 60rpx 0;
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="baogan-container">
|
<view class="timing-container">
|
||||||
<!-- 顶部筛选卡片 -->
|
<!-- 顶部筛选卡片 -->
|
||||||
<view class="filter-card">
|
<view class="filter-card">
|
||||||
<view class="card-title">
|
<view class="card-title">
|
||||||
@@ -7,15 +7,16 @@
|
|||||||
<text>筛选条件</text>
|
<text>筛选条件</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="filter-item" @click="showProject = true">
|
<view class="filter-item" @click="showProjectPopup = true">
|
||||||
<text class="filter-label">项目</text>
|
<text class="filter-label">项目</text>
|
||||||
<view class="filter-value">
|
<view class="filter-value">
|
||||||
<text class="value-text" :class="{ placeholder: !selectProcet }">{{ selectProcet || '请选择项目' }}</text>
|
<text class="value-text"
|
||||||
|
:class="{ placeholder: !selectProcet }">{{ selectProcet || '请选择项目' }}</text>
|
||||||
<u-icon name="arrow-right" size="14" color="#bbb"></u-icon>
|
<u-icon name="arrow-right" size="14" color="#bbb"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="selectProcet" class="filter-item" @click="openCalendar">
|
<view class="filter-item" @click="openCalendar">
|
||||||
<text class="filter-label">日期</text>
|
<text class="filter-label">日期</text>
|
||||||
<view class="filter-value">
|
<view class="filter-value">
|
||||||
<text class="value-text" :class="{ placeholder: !begin }">
|
<text class="value-text" :class="{ placeholder: !begin }">
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="selectProcet && begin" class="filter-item" @click="showStudentPicker = true">
|
<view class="filter-item" @click="showStudentPicker = true">
|
||||||
<text class="filter-label">学生</text>
|
<text class="filter-label">学生</text>
|
||||||
<view class="filter-value">
|
<view class="filter-value">
|
||||||
<text class="value-text" :class="{ placeholder: selectedStudentIndexes.length === 0 }">
|
<text class="value-text" :class="{ placeholder: selectedStudentIndexes.length === 0 }">
|
||||||
@@ -36,40 +37,106 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<up-picker v-model:show="showProject" keyName="name" valueName="planId" @confirm="selectProcetFunc" :columns="projectOptions"></up-picker>
|
<!-- 项目选择弹窗 -->
|
||||||
|
<up-popup :show="showProjectPopup" mode="center" round="32" :safeAreaInsetBottom="false"
|
||||||
|
@close="showProjectPopup = false">
|
||||||
|
<view class="project-popup-content">
|
||||||
|
<view class="modal-header">
|
||||||
|
<text class="modal-title">选择项目</text>
|
||||||
|
<view class="modal-close" @click="showProjectPopup = false">
|
||||||
|
<u-icon name="close" size="20" color="#999"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="popup-project-list">
|
||||||
|
<view class="project-list-inner">
|
||||||
|
<view v-for="(project, index) in projectOptions[0]" :key="project.planId" class="project-item"
|
||||||
|
@click="selectProject(project)">
|
||||||
|
<view class="item-info">
|
||||||
|
<text class="item-name">{{ project.name }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="!projectOptions[0] || projectOptions[0].length === 0" class="empty-project">
|
||||||
|
<text class="empty-text">暂无项目</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</up-popup>
|
||||||
|
|
||||||
<!-- 数据表格 -->
|
<!-- 数据表格 -->
|
||||||
<view v-if="tableData.length > 0" class="data-card">
|
<view class="data-card">
|
||||||
<view class="card-title">
|
<view class="card-title">
|
||||||
<u-icon name="list" size="18" color="#1890ff"></u-icon>
|
<u-icon name="list" size="18" color="#1890ff"></u-icon>
|
||||||
<text>训练记录</text>
|
<text>训练记录</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="table-wrap">
|
<view v-for="(item,index) in tableData" :key="index">
|
||||||
<next-table :show-header="true" :columns="columns" :stripe="true" @cellClick="cellClick" :fit="false" :show-summary="false" :data="tableData" :show-paging="true" :pageIndex="page" @pageChange="pageChange" :pageTotal="pageTotal"></next-table>
|
<view class="day-title" @click="toggleDayCollapse(index)">
|
||||||
|
<text>{{ item.dayTime }} · {{ item.planName }} </text>
|
||||||
|
<u-icon name="arrow-down" size="16" color="#666"
|
||||||
|
:class="{ 'arrow-rotate': collapsedDays[index] }"></u-icon>
|
||||||
|
</view>
|
||||||
|
<view class="table-wrap" v-if="collapsedDays[index]">
|
||||||
|
<next-table :show-header="true" @cellClick="cellClick" :columns="columns" :stripe="true"
|
||||||
|
:fit="false" :show-summary="false" :data="item.data"></next-table>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<view v-else-if="selectProcet && begin && selectedStudentIndexes.length > 0" class="empty-card">
|
<view v-if=" tableData.length == 0" class="empty-card">
|
||||||
<u-icon name="info-circle" size="64" color="#d9d9d9"></u-icon>
|
<u-icon name="info-circle" size="64" color="#d9d9d9"></u-icon>
|
||||||
<text class="empty-text">暂无训练记录</text>
|
<text class="empty-text"> {{ isLoading?'暂无训练记录':'加载中' }} </text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 提示状态 -->
|
|
||||||
<view v-else-if="selectProcet" class="empty-card">
|
|
||||||
<u-icon name="search" size="64" color="#d9d9d9"></u-icon>
|
|
||||||
<text class="empty-text">请选择筛选条件查看数据</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 日历弹窗 -->
|
<!-- 日历弹窗 -->
|
||||||
<up-calendar :show="showCalendar" mode="date" minDate="1776240407000" @confirm="calendarConfirm" @close="calendarClose"></up-calendar>
|
<up-calendar :show="showCalendar" mode="date" minDate="1776240407000" @confirm="calendarConfirm"
|
||||||
|
@close="calendarClose"></up-calendar>
|
||||||
|
|
||||||
<!-- 圈数达标时间展示弹窗 -->
|
<!-- 学生选择弹窗 -->
|
||||||
<up-popup :show="showLapsModal" @close="closeLapsModal" mode="center" round="16" bgColor="#fff">
|
<view class="modal-overlay" v-if="showStudentPicker" @click="closeStudentPicker"></view>
|
||||||
|
<view class="student-picker-modal" v-if="showStudentPicker">
|
||||||
|
<view class="picker-header">
|
||||||
|
<view class="header-left">
|
||||||
|
<text class="header-title">选择学生</text>
|
||||||
|
<text class="student-count">已选({{ selectedStudentIndexes.length }}/{{ studentList.length }})</text>
|
||||||
|
</view>
|
||||||
|
<view class="header-actions">
|
||||||
|
<view class="select-all-btn" @click="toggleSelectAll">
|
||||||
|
<view class="checkbox-icon" :class="{ checked: allSelected }">
|
||||||
|
<u-icon v-if="allSelected" name="checkmark" size="14" color="#fff"></u-icon>
|
||||||
|
</view>
|
||||||
|
<text class="select-all-text">{{ allSelected ? '取消全选' : '全选' }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="action-btn primary" @click="confirmStudentSelection">确定</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<scroll-view class="student-list" scroll-y>
|
||||||
|
<view class="student-grid">
|
||||||
|
<view v-for="(student, index) in studentList" :key="student.studentId" class="student-item"
|
||||||
|
:class="{ checked: selectedStudentIndexes.includes(index) }"
|
||||||
|
@click="toggleStudentSelection(index)">
|
||||||
|
<view class="student-checkbox" :class="{ checked: selectedStudentIndexes.includes(index) }">
|
||||||
|
<text v-if="selectedStudentIndexes.includes(index)" class="checkbox-index">
|
||||||
|
{{ selectedStudentIndexes.indexOf(index) + 1 }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="student-info">
|
||||||
|
<view class="student-name">{{ student.name }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 记录详情弹窗 -->
|
||||||
|
<up-popup :show="showRecordPopup" @close="closeRecordPopup" mode="center" round="16" bgColor="#fff">
|
||||||
<view class="laps-popup-content">
|
<view class="laps-popup-content">
|
||||||
<view class="laps-popup-header">
|
<view class="laps-popup-header">
|
||||||
<text class="laps-popup-title">圈数达标详情</text>
|
<text
|
||||||
<u-icon name="close" size="22" color="#999" @click="closeLapsModal"></u-icon>
|
class="laps-popup-title">{{ row.studentName + ' · '+row.planName + ' · ' +row.subsection }}</text>
|
||||||
|
<u-icon name="close" size="22" color="#999" @click="closeRecordPopup"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
<scroll-view class="laps-popup-body" scroll-y>
|
<scroll-view class="laps-popup-body" scroll-y>
|
||||||
<view class="laps-table">
|
<view class="laps-table">
|
||||||
@@ -77,12 +144,8 @@
|
|||||||
<text class="laps-th">圈数</text>
|
<text class="laps-th">圈数</text>
|
||||||
<text class="laps-th">达标时间</text>
|
<text class="laps-th">达标时间</text>
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view v-for="(item, index) in rowData.split('|')" :key="index" class="laps-table-row"
|
||||||
v-for="(item, index) in rowData.split('|')"
|
:class="{ 'laps-row-odd': index % 2 === 1 }">
|
||||||
:key="index"
|
|
||||||
class="laps-table-row"
|
|
||||||
:class="{ 'laps-row-odd': index % 2 === 1 }"
|
|
||||||
>
|
|
||||||
<text class="laps-td">第 {{ index+1 }} 圈</text>
|
<text class="laps-td">第 {{ index+1 }} 圈</text>
|
||||||
<text class="laps-td">{{ item }}</text>
|
<text class="laps-td">{{ item }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -93,31 +156,6 @@
|
|||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
</up-popup>
|
</up-popup>
|
||||||
|
|
||||||
<!-- 学生选择弹窗 -->
|
|
||||||
<view class="modal-overlay" v-if="showStudentPicker" @click="closeStudentPicker"></view>
|
|
||||||
<view class="student-picker-modal" v-if="showStudentPicker">
|
|
||||||
<view class="picker-header">
|
|
||||||
<text class="header-title">选择学生</text>
|
|
||||||
<view class="header-actions">
|
|
||||||
<text class="action-btn" @click="selectAllStudents">全选</text>
|
|
||||||
<text class="action-btn primary" @click="confirmStudentSelection">确定</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<scroll-view class="student-list" scroll-y>
|
|
||||||
<view v-for="(student, index) in studentList" :key="student.studentId" class="student-item" :class="{ selected: selectedStudentIndexes.includes(index) }" @click="toggleStudentSelection(index)">
|
|
||||||
<view class="item-checkbox">
|
|
||||||
<view class="checkbox-inner" :class="{ checked: selectedStudentIndexes.includes(index) }">
|
|
||||||
<u-icon v-if="selectedStudentIndexes.includes(index)" name="checkmark" size="12" color="#fff"></u-icon>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="item-avatar">
|
|
||||||
<text class="avatar-text">{{ student.name.charAt(0) }}</text>
|
|
||||||
</view>
|
|
||||||
<text class="item-name">{{ student.name }}</text>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -126,36 +164,29 @@
|
|||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { PlanService } from '@/Service/swimming/PlanService'
|
import { PlanService } from '@/Service/swimming/PlanService'
|
||||||
|
import { studentService } from '@/Service/swimming/studentService'
|
||||||
interface Project {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Student {
|
interface Student {
|
||||||
id: string
|
studentId : string
|
||||||
name: string
|
name : string
|
||||||
gender: string
|
gender : string
|
||||||
age: number
|
age : number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TableDataItem {
|
interface TableDataItem {
|
||||||
date: string
|
date : string
|
||||||
name: string
|
name : string
|
||||||
projectName: string
|
projectName : string
|
||||||
plan: string
|
plan : string
|
||||||
completion: number
|
completion : number
|
||||||
detail: string
|
detail : string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StudentRecord {
|
// 数据加载
|
||||||
student: Student
|
let isLoading = ref(false)
|
||||||
date: string
|
|
||||||
segments: TableDataItem[]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 项目选择
|
// 项目选择
|
||||||
let showProject = ref(false)
|
let showProjectPopup = ref(false)
|
||||||
let selectProcet = ref('')
|
let selectProcet = ref('')
|
||||||
let selectId = ref('')
|
let selectId = ref('')
|
||||||
|
|
||||||
@@ -169,40 +200,30 @@
|
|||||||
let pageTotal = ref(10)
|
let pageTotal = ref(10)
|
||||||
|
|
||||||
const columns = ref([
|
const columns = ref([
|
||||||
{ label: '训练日期', name: 'addTime' },
|
|
||||||
{ label: '姓名', name: 'studentName' },
|
{ label: '姓名', name: 'studentName' },
|
||||||
{ label: '项目名称', name: 'name' },
|
{ label: '训练数据', name: 'subsection' },
|
||||||
{ label: '项目类型', name: 'planType' },
|
|
||||||
{ label: '完成比例', name: 'completion' },
|
{ label: '完成比例', name: 'completion' },
|
||||||
{ label: '详细数据', name: 'detail' }
|
{ label: '详细数据', name: 'detail' }
|
||||||
])
|
])
|
||||||
|
|
||||||
let rowData=ref('')
|
|
||||||
|
|
||||||
const tableData = ref<TableDataItem[]>([])
|
let row = ref<any>({})
|
||||||
|
let rowData = ref('')
|
||||||
|
|
||||||
|
|
||||||
|
const tableData = ref<any[]>([])
|
||||||
const projectOptions = ref<Array<any>>([[]])
|
const projectOptions = ref<Array<any>>([[]])
|
||||||
const studentList = ref<Student[]>([])
|
const studentList = ref<Student[]>([])
|
||||||
const selectedStudentIndexes = ref<number[]>([])
|
const selectedStudentIndexes = ref<number[]>([])
|
||||||
const selectedStudentIds = ref<string[]>([])
|
const selectedStudentIds = ref<string[]>([])
|
||||||
const showStudentPicker = ref(false)
|
const showStudentPicker = ref(false)
|
||||||
|
|
||||||
// 圈数达标时间弹窗
|
// 日期折叠状态
|
||||||
const showLapsModal = ref(false)
|
const collapsedDays = ref<boolean[]>([])
|
||||||
interface LapsItem {
|
|
||||||
lap: number
|
|
||||||
time: string
|
|
||||||
}
|
|
||||||
const lapsData = ref<LapsItem[]>([])
|
|
||||||
|
|
||||||
// 打开圈数达标时间弹窗
|
// 切换日期折叠状态
|
||||||
const openLapsModal = (data: LapsItem[]) => {
|
const toggleDayCollapse = (index : number) => {
|
||||||
lapsData.value = data
|
collapsedDays.value[index] = !collapsedDays.value[index]
|
||||||
showLapsModal.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭圈数达标时间弹窗
|
|
||||||
const closeLapsModal = () => {
|
|
||||||
showLapsModal.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedStudentDisplay = computed(() => {
|
const selectedStudentDisplay = computed(() => {
|
||||||
@@ -219,12 +240,18 @@
|
|||||||
return `${begin.value} 至 ${end.value}`
|
return `${begin.value} 至 ${end.value}`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
loadData()
|
|
||||||
getProjectData()
|
getProjectData()
|
||||||
|
getRecordList()
|
||||||
|
getStudentList()
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadData = () => {}
|
onShow(() => { })
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const getProjectData = () => {
|
const getProjectData = () => {
|
||||||
PlanService.GetPlanListNoPage('计时项目').then(res => {
|
PlanService.GetPlanListNoPage('计时项目').then(res => {
|
||||||
@@ -236,21 +263,34 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const cellClick = (rows: any) => {
|
const getStudentList = () => {
|
||||||
rowData.value = rows.detail
|
studentService.GetStudentList().then(res => {
|
||||||
console.log(rowData.value);
|
if (res.code == 0) {
|
||||||
showLapsModal.value=true
|
studentList.value = res.data
|
||||||
|
} else {
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectProcetFunc = (e: any) => {
|
|
||||||
selectProcet.value = e.value[0].name
|
const cellClick = (rows : any) => {
|
||||||
selectId.value = e.value[0].planId
|
|
||||||
|
row.value = rows
|
||||||
|
rowData.value = rows.detail
|
||||||
|
showRecordPopup.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectProject = (project : any) => {
|
||||||
|
selectProcet.value = project.name
|
||||||
|
selectId.value = project.planId
|
||||||
|
showProjectPopup.value = false
|
||||||
begin.value = ''
|
begin.value = ''
|
||||||
end.value = ''
|
end.value = ''
|
||||||
selectedStudentIndexes.value = []
|
selectedStudentIndexes.value = []
|
||||||
studentList.value = []
|
selectedStudentIds.value = []
|
||||||
tableData.value = []
|
tableData.value = []
|
||||||
getProjectDetail()
|
getRecord()
|
||||||
}
|
}
|
||||||
|
|
||||||
const getProjectDetail = () => {
|
const getProjectDetail = () => {
|
||||||
@@ -267,21 +307,30 @@
|
|||||||
showCalendar.value = true
|
showCalendar.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const calendarConfirm = (e: any) => {
|
const calendarConfirm = (e : any) => {
|
||||||
begin.value = e[0]
|
begin.value = e[0]
|
||||||
end.value = e[e.length - 1]
|
end.value = e[e.length - 1]
|
||||||
showCalendar.value = false
|
showCalendar.value = false
|
||||||
|
getRecord()
|
||||||
}
|
}
|
||||||
|
|
||||||
const calendarClose = () => {
|
const calendarClose = () => {
|
||||||
showCalendar.value = false
|
showCalendar.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectAllStudents = () => {
|
const allSelected = computed(() => {
|
||||||
selectedStudentIndexes.value = studentList.value.map((_, index) => index)
|
return studentList.value.length > 0 && studentList.value.every((_, index) => selectedStudentIndexes.value.includes(index))
|
||||||
|
})
|
||||||
|
|
||||||
|
const toggleSelectAll = () => {
|
||||||
|
if (allSelected.value) {
|
||||||
|
selectedStudentIndexes.value = []
|
||||||
|
} else {
|
||||||
|
selectedStudentIndexes.value = studentList.value.map((_, index) => index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleStudentSelection = (index: number) => {
|
const toggleStudentSelection = (index : number) => {
|
||||||
const idx = selectedStudentIndexes.value.indexOf(index)
|
const idx = selectedStudentIndexes.value.indexOf(index)
|
||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
selectedStudentIndexes.value.splice(idx, 1)
|
selectedStudentIndexes.value.splice(idx, 1)
|
||||||
@@ -302,6 +351,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getRecordList = () => {
|
const getRecordList = () => {
|
||||||
|
Service.Msg('数据加载中...')
|
||||||
let studentIdList = selectedStudentIndexes.value.map(index => {
|
let studentIdList = selectedStudentIndexes.value.map(index => {
|
||||||
return studentList.value[index].studentId
|
return studentList.value[index].studentId
|
||||||
})
|
})
|
||||||
@@ -310,21 +360,24 @@
|
|||||||
planId: selectId.value,
|
planId: selectId.value,
|
||||||
studentId: JSON.stringify(studentIdList),
|
studentId: JSON.stringify(studentIdList),
|
||||||
sTime: begin.value,
|
sTime: begin.value,
|
||||||
eTime: end.value,
|
eTime: end.value
|
||||||
page: page.value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PlanService.GetBaoganLog(data).then(res => {
|
PlanService.GetBaoganLog(data).then(res => {
|
||||||
|
isLoading.value = true
|
||||||
|
Service.LoadClose()
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
pageTotal.value = res.data.pageTotal
|
pageTotal.value = res.data.pageTotal
|
||||||
tableData.value = res.data.list
|
tableData.value = res.data.list
|
||||||
|
// 初始化折叠状态,默认全部展开
|
||||||
|
collapsedDays.value = new Array(res.data.list.length).fill(false)
|
||||||
} else {
|
} else {
|
||||||
Service.Msg(res.msg)
|
Service.Msg(res.msg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageChange = (e: any) => {
|
const pageChange = (e : any) => {
|
||||||
page.value = e
|
page.value = e
|
||||||
getRecordList()
|
getRecordList()
|
||||||
}
|
}
|
||||||
@@ -332,16 +385,44 @@
|
|||||||
const closeStudentPicker = () => {
|
const closeStudentPicker = () => {
|
||||||
showStudentPicker.value = false
|
showStudentPicker.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录详情弹窗
|
||||||
|
const showRecordPopup = ref(false)
|
||||||
|
|
||||||
|
interface RecordItem {
|
||||||
|
name : string
|
||||||
|
time : string
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordList = ref<RecordItem[]>([])
|
||||||
|
|
||||||
|
// 打开记录详情弹窗
|
||||||
|
const openRecordPopup = (data : RecordItem[]) => {
|
||||||
|
recordList.value = data
|
||||||
|
showRecordPopup.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭记录详情弹窗
|
||||||
|
const closeRecordPopup = () => {
|
||||||
|
showRecordPopup.value = false
|
||||||
|
}
|
||||||
|
// 简化时间格式化(分:秒.毫秒)
|
||||||
|
const formatSimpleTime = (seconds : number) : string => {
|
||||||
|
const mins = Math.floor(seconds / 60)
|
||||||
|
const secs = Math.floor(seconds % 60)
|
||||||
|
const millis = Math.floor((seconds % 1) * 100)
|
||||||
|
return `${mins.toString().padStart(2, '0')}′${secs.toString().padStart(2, '0')}″${millis.toString().padStart(2, '0')}`
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
page {
|
page {
|
||||||
background-color: #eef1f6;
|
background-color: #e8ecf3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.baogan-container {
|
.timing-container {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 20rpx;
|
padding: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 通用卡片标题 */
|
/* 通用卡片标题 */
|
||||||
@@ -362,8 +443,8 @@
|
|||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
padding: 24rpx;
|
padding: 24rpx;
|
||||||
margin-bottom: 20rpx;
|
margin-bottom: 24rpx;
|
||||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-item {
|
.filter-item {
|
||||||
@@ -418,13 +499,35 @@
|
|||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
padding: 24rpx;
|
padding: 24rpx;
|
||||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 0 20rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
padding: 16rpx 0 8rpx;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-rotate {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrap {
|
.table-wrap {
|
||||||
border-radius: 12rpx;
|
border-radius: 12rpx;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1rpx solid #f0f0f0;
|
border: 1rpx solid #f0f0f0;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 空状态 */
|
/* 空状态 */
|
||||||
@@ -433,10 +536,10 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 100rpx 40rpx;
|
padding: 120rpx 40rpx;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||||
|
|
||||||
.empty-text {
|
.empty-text {
|
||||||
margin-top: 24rpx;
|
margin-top: 24rpx;
|
||||||
@@ -445,6 +548,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 弹窗遮罩 */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.55);
|
||||||
|
z-index: 998;
|
||||||
|
}
|
||||||
|
|
||||||
/* 学生选择弹窗 */
|
/* 学生选择弹窗 */
|
||||||
.student-picker-modal {
|
.student-picker-modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -470,6 +584,18 @@
|
|||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
border-radius: 24rpx 24rpx 0 0;
|
border-radius: 24rpx 24rpx 0 0;
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
|
||||||
|
.student-count {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #999;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.header-title {
|
.header-title {
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -482,6 +608,36 @@
|
|||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select-all-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-icon {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-all-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #666;
|
color: #666;
|
||||||
@@ -507,69 +663,69 @@
|
|||||||
padding: 16rpx 24rpx 40rpx;
|
padding: 16rpx 24rpx 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.student-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.student-item {
|
.student-item {
|
||||||
|
width: calc((100% - 24rpx) / 3);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20rpx;
|
gap: 8rpx;
|
||||||
padding: 22rpx 20rpx;
|
padding: 16rpx 8rpx;
|
||||||
border-radius: 12rpx;
|
background-color: #f5f5f5;
|
||||||
margin-bottom: 12rpx;
|
border-radius: 16rpx;
|
||||||
background: #f8f9fa;
|
border: 2rpx solid transparent;
|
||||||
transition: background 0.2s;
|
transition: all 0.25s ease;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background: #eeeeee;
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.checked {
|
||||||
background: #fff3e0;
|
background-color: #e6f7ff;
|
||||||
border: 1rpx solid #ffcc80;
|
border-color: #1890ff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-checkbox {
|
.student-checkbox {
|
||||||
flex-shrink: 0;
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
.checkbox-inner {
|
|
||||||
width: 36rpx;
|
|
||||||
height: 36rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
border: 2rpx solid #bfbfbf;
|
|
||||||
background: #fff;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.2s;
|
|
||||||
|
|
||||||
&.checked {
|
|
||||||
border-color: #faad14;
|
|
||||||
background: #faad14;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-avatar {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 64rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #1890ff;
|
border: 2rpx solid #d9d9d9;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
.avatar-text {
|
&.checked {
|
||||||
font-size: 28rpx;
|
border-color: #1890ff;
|
||||||
|
background-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-index {
|
||||||
|
font-size: 22rpx;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-name {
|
.student-info {
|
||||||
flex: 1;
|
width: 100%;
|
||||||
font-size: 30rpx;
|
|
||||||
color: #333;
|
.student-name {
|
||||||
font-weight: 500;
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 表格深度样式 */
|
/* 表格深度样式 */
|
||||||
@@ -601,10 +757,250 @@
|
|||||||
background-color: #fff !important;
|
background-color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 项目选择弹窗 */
|
||||||
|
.project-popup-content {
|
||||||
|
width: 680rpx;
|
||||||
|
max-height: 70vh;
|
||||||
|
background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
|
||||||
|
border-radius: 32rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 0 24rpx 80rpx rgba(0, 0, 0, 0.25), 0 0 0 1rpx rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-project-list {
|
||||||
|
max-height: 55vh;
|
||||||
|
padding: 24rpx 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
background: linear-gradient(135deg, #fff 0%, #fafbfc 100%);
|
||||||
|
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;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
|
||||||
|
&::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.icon-bg {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 50%, #36cfc9 100%);
|
||||||
|
border-radius: 14rpx;
|
||||||
|
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: 6rpx;
|
||||||
|
right: 6rpx;
|
||||||
|
width: 10rpx;
|
||||||
|
height: 10rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分段详情弹窗 */
|
||||||
|
.segment-popup-content {
|
||||||
|
width: 620rpx;
|
||||||
|
max-height: 70vh;
|
||||||
|
padding: 32rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.popup-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
|
||||||
|
.popup-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-table {
|
||||||
|
background-color: #fafafa;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.popup-table-header {
|
||||||
|
display: flex;
|
||||||
|
background: linear-gradient(135deg, #faad14 0%, #ffc53d 100%);
|
||||||
|
padding: 24rpx 0;
|
||||||
|
|
||||||
|
.popup-th {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-scroll {
|
||||||
|
max-height: 400rpx;
|
||||||
|
|
||||||
|
.popup-table-row {
|
||||||
|
display: flex;
|
||||||
|
padding: 24rpx 0;
|
||||||
|
border-bottom: 1rpx solid #eee;
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-td {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-empty {
|
||||||
|
padding: 60rpx 0;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.popup-empty-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes slideUp {
|
@keyframes slideUp {
|
||||||
from {
|
from {
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
@@ -612,7 +1008,7 @@
|
|||||||
|
|
||||||
/* 圈数达标弹窗 */
|
/* 圈数达标弹窗 */
|
||||||
.laps-popup-content {
|
.laps-popup-content {
|
||||||
width: 600rpx;
|
width: 700rpx;
|
||||||
max-height: 70vh;
|
max-height: 70vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -629,7 +1025,11 @@
|
|||||||
|
|
||||||
.laps-popup-title {
|
.laps-popup-title {
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
font-weight: 600;
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<u-icon name="arrow-right" size="16" color="#bbb"></u-icon>
|
<u-icon name="arrow-right" size="16" color="#bbb"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<up-picker v-model:show="showProject" keyName="name" valueName="planId" @confirm="selectProcetFunc" :columns="projectOptions"></up-picker>
|
|
||||||
|
|
||||||
<!-- 排名列表区域 -->
|
<!-- 排名列表区域 -->
|
||||||
<view class="ranking-section" v-if="selectProcet && gradeList.length > 0">
|
<view class="ranking-section" v-if="selectProcet && gradeList.length > 0">
|
||||||
@@ -53,6 +53,33 @@
|
|||||||
<u-icon name="file-text" size="64" color="#d9d9d9"></u-icon>
|
<u-icon name="file-text" size="64" color="#d9d9d9"></u-icon>
|
||||||
<text class="empty-text">暂无排名数据</text>
|
<text class="empty-text">暂无排名数据</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 项目选择弹窗 -->
|
||||||
|
<up-popup :show="showProject" mode="center" round="32" :safeAreaInsetBottom="false"
|
||||||
|
@close="showProject = false">
|
||||||
|
<view class="project-popup-content">
|
||||||
|
<view class="modal-header">
|
||||||
|
<text class="modal-title">选择项目</text>
|
||||||
|
<view class="modal-close" @click="showProject = false">
|
||||||
|
<u-icon name="close" size="20" color="#999"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="popup-project-list">
|
||||||
|
<view class="project-list-inner">
|
||||||
|
<view v-for="(project, index) in projectOptions[0]" :key="project.planId" class="project-item"
|
||||||
|
@click="selectProcetFunc(project)">
|
||||||
|
<view class="item-info">
|
||||||
|
<text class="item-name">{{ project.name }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="!projectOptions[0] || projectOptions[0].length === 0" class="empty-project">
|
||||||
|
<text class="empty-text">暂无项目</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</up-popup>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -100,8 +127,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectProcetFunc = (e: any) => {
|
const selectProcetFunc = (e: any) => {
|
||||||
selectProcet.value = e.value[0].name
|
|
||||||
selectId.value = e.value[0].planId
|
selectProcet.value = e.name
|
||||||
|
selectId.value = e.planId
|
||||||
loadProjectGrades(selectId.value)
|
loadProjectGrades(selectId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +137,7 @@
|
|||||||
PlanService.GetRankData(projectId).then(res => {
|
PlanService.GetRankData(projectId).then(res => {
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
gradeList.value = res.data.list
|
gradeList.value = res.data.list
|
||||||
|
showProject.value=false
|
||||||
} else {
|
} else {
|
||||||
Service.Msg(res.msg)
|
Service.Msg(res.msg)
|
||||||
}
|
}
|
||||||
@@ -339,4 +368,141 @@
|
|||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 项目选择弹窗 */
|
||||||
|
.project-popup-content {
|
||||||
|
width: 680rpx;
|
||||||
|
max-height: 70vh;
|
||||||
|
background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
|
||||||
|
border-radius: 32rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 0 24rpx 80rpx rgba(0, 0, 0, 0.25), 0 0 0 1rpx rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-project-list {
|
||||||
|
max-height: 55vh;
|
||||||
|
padding: 24rpx 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
background: linear-gradient(135deg, #fff 0%, #fafbfc 100%);
|
||||||
|
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;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
|
||||||
|
&::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.icon-bg {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 50%, #36cfc9 100%);
|
||||||
|
border-radius: 14rpx;
|
||||||
|
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: 6rpx;
|
||||||
|
right: 6rpx;
|
||||||
|
width: 10rpx;
|
||||||
|
height: 10rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
|||||||
<text>筛选条件</text>
|
<text>筛选条件</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="filter-item" @click="showProject = true">
|
<view class="filter-item" @click="showProjectPopup = true">
|
||||||
<text class="filter-label">项目</text>
|
<text class="filter-label">项目</text>
|
||||||
<view class="filter-value">
|
<view class="filter-value">
|
||||||
<text class="value-text" :class="{ placeholder: !selectProcet }">{{ selectProcet || '请选择项目' }}</text>
|
<text class="value-text" :class="{ placeholder: !selectProcet }">{{ selectProcet || '请选择项目' }}</text>
|
||||||
@@ -36,7 +36,30 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<up-picker v-model:show="showProject" keyName="name" valueName="planId" @confirm="selectProcetFunc" :columns="projectOptions"></up-picker>
|
<!-- 项目选择弹窗 -->
|
||||||
|
<up-popup :show="showProjectPopup" mode="center" round="32" :safeAreaInsetBottom="false" @close="showProjectPopup = false">
|
||||||
|
<view class="project-popup-content">
|
||||||
|
<view class="modal-header">
|
||||||
|
<text class="modal-title">选择项目</text>
|
||||||
|
<view class="modal-close" @click="showProjectPopup = false">
|
||||||
|
<u-icon name="close" size="20" color="#999"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="popup-project-list">
|
||||||
|
<view class="project-list-inner">
|
||||||
|
<view v-for="(project, index) in projectOptions[0]" :key="project.planId" class="project-item" @click="selectProject(project)">
|
||||||
|
|
||||||
|
<view class="item-info">
|
||||||
|
<text class="item-name">{{ project.name }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="!projectOptions[0] || projectOptions[0].length === 0" class="empty-project">
|
||||||
|
<text class="empty-text">暂无项目</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</up-popup>
|
||||||
|
|
||||||
<!-- 数据表格 -->
|
<!-- 数据表格 -->
|
||||||
<view class="data-card">
|
<view class="data-card">
|
||||||
@@ -46,7 +69,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view v-for="(item,index) in tableData" :key="index">
|
<view v-for="(item,index) in tableData" :key="index">
|
||||||
<view class="day-title" @click="toggleDayCollapse(index)">
|
<view class="day-title" @click="toggleDayCollapse(index)">
|
||||||
<text>{{ item.dayTime }}</text>
|
<text>{{ item.dayTime }} · {{ item.planName }} </text>
|
||||||
<u-icon
|
<u-icon
|
||||||
name="arrow-down"
|
name="arrow-down"
|
||||||
size="16"
|
size="16"
|
||||||
@@ -54,7 +77,7 @@
|
|||||||
:class="{ 'arrow-rotate': collapsedDays[index] }"
|
:class="{ 'arrow-rotate': collapsedDays[index] }"
|
||||||
></u-icon>
|
></u-icon>
|
||||||
</view>
|
</view>
|
||||||
<view class="table-wrap" v-if="collapsedDays[index]">
|
<view class="table-wrap" v-if="collapsedDays[index]">
|
||||||
<next-table :show-header="true" @cellClick="cellClick" :columns="columns" :stripe="true" :fit="false" :show-summary="false" :data="item.data" ></next-table>
|
<next-table :show-header="true" @cellClick="cellClick" :columns="columns" :stripe="true" :fit="false" :show-summary="false" :data="item.data" ></next-table>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -66,11 +89,7 @@
|
|||||||
<text class="empty-text"> {{ isLoading?'暂无训练记录':'加载中' }} </text>
|
<text class="empty-text"> {{ isLoading?'暂无训练记录':'加载中' }} </text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 提示状态 -->
|
|
||||||
<!-- <view v-else-if="selectProcet" class="empty-card">
|
|
||||||
<u-icon name="search" size="64" color="#d9d9d9"></u-icon>
|
|
||||||
<text class="empty-text">请选择筛选条件查看数据</text>
|
|
||||||
</view> -->
|
|
||||||
|
|
||||||
<!-- 日历弹窗 -->
|
<!-- 日历弹窗 -->
|
||||||
<up-calendar :show="showCalendar" mode="date" minDate="1776240407000" @confirm="calendarConfirm" @close="calendarClose"></up-calendar>
|
<up-calendar :show="showCalendar" mode="date" minDate="1776240407000" @confirm="calendarConfirm" @close="calendarClose"></up-calendar>
|
||||||
@@ -79,23 +98,32 @@
|
|||||||
<view class="modal-overlay" v-if="showStudentPicker" @click="closeStudentPicker"></view>
|
<view class="modal-overlay" v-if="showStudentPicker" @click="closeStudentPicker"></view>
|
||||||
<view class="student-picker-modal" v-if="showStudentPicker">
|
<view class="student-picker-modal" v-if="showStudentPicker">
|
||||||
<view class="picker-header">
|
<view class="picker-header">
|
||||||
<text class="header-title">选择学生</text>
|
<view class="header-left">
|
||||||
|
<text class="header-title">选择学生</text>
|
||||||
|
<text class="student-count">已选({{ selectedStudentIndexes.length }}/{{ studentList.length }})</text>
|
||||||
|
</view>
|
||||||
<view class="header-actions">
|
<view class="header-actions">
|
||||||
<text class="action-btn" @click="selectAllStudents">全选</text>
|
<view class="select-all-btn" @click="toggleSelectAll">
|
||||||
|
<view class="checkbox-icon" :class="{ checked: allSelected }">
|
||||||
|
<u-icon v-if="allSelected" name="checkmark" size="14" color="#fff"></u-icon>
|
||||||
|
</view>
|
||||||
|
<text class="select-all-text">{{ allSelected ? '取消全选' : '全选' }}</text>
|
||||||
|
</view>
|
||||||
<text class="action-btn primary" @click="confirmStudentSelection">确定</text>
|
<text class="action-btn primary" @click="confirmStudentSelection">确定</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<scroll-view class="student-list" scroll-y>
|
<scroll-view class="student-list" scroll-y>
|
||||||
<view v-for="(student, index) in studentList" :key="student.studentId" class="student-item" :class="{ selected: selectedStudentIndexes.includes(index) }" @click="toggleStudentSelection(index)">
|
<view class="student-grid">
|
||||||
<view class="item-checkbox">
|
<view v-for="(student, index) in studentList" :key="student.studentId" class="student-item" :class="{ checked: selectedStudentIndexes.includes(index) }" @click="toggleStudentSelection(index)">
|
||||||
<view class="checkbox-inner" :class="{ checked: selectedStudentIndexes.includes(index) }">
|
<view class="student-checkbox" :class="{ checked: selectedStudentIndexes.includes(index) }">
|
||||||
<u-icon v-if="selectedStudentIndexes.includes(index)" name="checkmark" size="12" color="#fff"></u-icon>
|
<text v-if="selectedStudentIndexes.includes(index)" class="checkbox-index">
|
||||||
|
{{ selectedStudentIndexes.indexOf(index) + 1 }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="student-info">
|
||||||
|
<view class="student-name">{{ student.name }}</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="item-avatar">
|
|
||||||
<text class="avatar-text">{{ student.name.charAt(0) }}</text>
|
|
||||||
</view>
|
|
||||||
<text class="item-name">{{ student.name }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
@@ -104,7 +132,7 @@
|
|||||||
<up-popup :show="showRecordPopup" @close="closeRecordPopup" mode="center" round="16" bgColor="#fff">
|
<up-popup :show="showRecordPopup" @close="closeRecordPopup" mode="center" round="16" bgColor="#fff">
|
||||||
<view class="record-popup-content">
|
<view class="record-popup-content">
|
||||||
<view class="record-popup-header">
|
<view class="record-popup-header">
|
||||||
<text class="record-popup-title">记录详情</text>
|
<text class="record-popup-title">{{ rowData.studentName +'·' + rowData.planName}}</text>
|
||||||
<u-icon name="close" size="22" color="#999" @click="closeRecordPopup"></u-icon>
|
<u-icon name="close" size="22" color="#999" @click="closeRecordPopup"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
<scroll-view class="record-popup-body" scroll-y>
|
<scroll-view class="record-popup-body" scroll-y>
|
||||||
@@ -114,7 +142,7 @@
|
|||||||
<text class="record-th">时间</text>
|
<text class="record-th">时间</text>
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
v-for="(item, index) in rowData.split('|')"
|
v-for="(item, index) in rowData.time.split('|')"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="record-table-row"
|
class="record-table-row"
|
||||||
:class="{ 'record-row-odd': index % 2 === 1 }"
|
:class="{ 'record-row-odd': index % 2 === 1 }"
|
||||||
@@ -122,7 +150,7 @@
|
|||||||
<text class="record-td">记录{{ index+1 }}</text>
|
<text class="record-td">记录{{ index+1 }}</text>
|
||||||
<text class="record-td">{{ item }}</text>
|
<text class="record-td">{{ item }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="rowData.split('|').length === 0" class="record-empty">
|
<view v-if="rowData.time.split('|').length === 0" class="record-empty">
|
||||||
<text class="record-empty-text">暂无数据</text>
|
<text class="record-empty-text">暂无数据</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -159,7 +187,7 @@
|
|||||||
let isLoading=ref(false)
|
let isLoading=ref(false)
|
||||||
|
|
||||||
// 项目选择
|
// 项目选择
|
||||||
let showProject = ref(false)
|
let showProjectPopup = ref(false)
|
||||||
let selectProcet = ref('')
|
let selectProcet = ref('')
|
||||||
let selectId = ref('')
|
let selectId = ref('')
|
||||||
|
|
||||||
@@ -174,12 +202,14 @@
|
|||||||
|
|
||||||
const columns = ref([
|
const columns = ref([
|
||||||
{ label: '姓名', name: 'studentName' },
|
{ label: '姓名', name: 'studentName' },
|
||||||
{ label: '项目名称', name: 'planName' },
|
|
||||||
{ label: '最快速度', name: 'quicklyTime' },
|
{ label: '最快速度', name: 'quicklyTime' },
|
||||||
{ label: '详细数据', name: 'time', width: '200' }
|
{ label: '详细数据', name: 'time', width: '200' }
|
||||||
])
|
])
|
||||||
|
|
||||||
let rowData=ref('')
|
let rowData=ref<any>({
|
||||||
|
studentName:'',
|
||||||
|
time:''
|
||||||
|
})
|
||||||
|
|
||||||
const tableData = ref<any[]>([])
|
const tableData = ref<any[]>([])
|
||||||
const projectOptions = ref<Array<any>>([[]])
|
const projectOptions = ref<Array<any>>([[]])
|
||||||
@@ -218,6 +248,12 @@
|
|||||||
|
|
||||||
onShow(() => {})
|
onShow(() => {})
|
||||||
|
|
||||||
|
|
||||||
|
const cs=(e)=>{
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const getProjectData = () => {
|
const getProjectData = () => {
|
||||||
PlanService.GetPlanListNoPage('计时项目').then(res => {
|
PlanService.GetPlanListNoPage('计时项目').then(res => {
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
@@ -240,17 +276,18 @@
|
|||||||
|
|
||||||
|
|
||||||
const cellClick=(e:any)=>{
|
const cellClick=(e:any)=>{
|
||||||
rowData.value=e.time
|
rowData.value=e
|
||||||
console.log(rowData.value);
|
|
||||||
showRecordPopup.value=true
|
showRecordPopup.value=true
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectProcetFunc = (e: any) => {
|
const selectProject = (project: any) => {
|
||||||
selectProcet.value = e.value[0].name
|
selectProcet.value = project.name
|
||||||
selectId.value = e.value[0].planId
|
selectId.value = project.planId
|
||||||
|
showProjectPopup.value = false
|
||||||
begin.value = ''
|
begin.value = ''
|
||||||
end.value = ''
|
end.value = ''
|
||||||
selectedStudentIndexes.value = []
|
selectedStudentIndexes.value = []
|
||||||
|
selectedStudentIds.value = []
|
||||||
// studentList.value = []
|
// studentList.value = []
|
||||||
tableData.value = []
|
tableData.value = []
|
||||||
// getProjectDetail()
|
// getProjectDetail()
|
||||||
@@ -282,8 +319,16 @@
|
|||||||
showCalendar.value = false
|
showCalendar.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectAllStudents = () => {
|
const allSelected = computed(() => {
|
||||||
selectedStudentIndexes.value = studentList.value.map((_, index) => index)
|
return studentList.value.length > 0 && studentList.value.every((_, index) => selectedStudentIndexes.value.includes(index))
|
||||||
|
})
|
||||||
|
|
||||||
|
const toggleSelectAll = () => {
|
||||||
|
if (allSelected.value) {
|
||||||
|
selectedStudentIndexes.value = []
|
||||||
|
} else {
|
||||||
|
selectedStudentIndexes.value = studentList.value.map((_, index) => index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleStudentSelection = (index: number) => {
|
const toggleStudentSelection = (index: number) => {
|
||||||
@@ -533,6 +578,18 @@
|
|||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
border-radius: 24rpx 24rpx 0 0;
|
border-radius: 24rpx 24rpx 0 0;
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
|
||||||
|
.student-count {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #999;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.header-title {
|
.header-title {
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -545,6 +602,36 @@
|
|||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select-all-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-icon {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-all-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #666;
|
color: #666;
|
||||||
@@ -570,69 +657,69 @@
|
|||||||
padding: 16rpx 24rpx 40rpx;
|
padding: 16rpx 24rpx 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.student-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.student-item {
|
.student-item {
|
||||||
|
width: calc((100% - 24rpx) / 3);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20rpx;
|
gap: 8rpx;
|
||||||
padding: 22rpx 20rpx;
|
padding: 16rpx 8rpx;
|
||||||
border-radius: 12rpx;
|
background-color: #f5f5f5;
|
||||||
margin-bottom: 12rpx;
|
border-radius: 16rpx;
|
||||||
background: #f8f9fa;
|
border: 2rpx solid transparent;
|
||||||
transition: background 0.2s;
|
transition: all 0.25s ease;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background: #eeeeee;
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.checked {
|
||||||
background: #fff3e0;
|
background-color: #e6f7ff;
|
||||||
border: 1rpx solid #ffcc80;
|
border-color: #1890ff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-checkbox {
|
.student-checkbox {
|
||||||
flex-shrink: 0;
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
.checkbox-inner {
|
|
||||||
width: 36rpx;
|
|
||||||
height: 36rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
border: 2rpx solid #bfbfbf;
|
|
||||||
background: #fff;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.2s;
|
|
||||||
|
|
||||||
&.checked {
|
|
||||||
border-color: #faad14;
|
|
||||||
background: #faad14;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-avatar {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 64rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #1890ff;
|
border: 2rpx solid #d9d9d9;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
.avatar-text {
|
&.checked {
|
||||||
font-size: 28rpx;
|
border-color: #1890ff;
|
||||||
|
background-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-index {
|
||||||
|
font-size: 22rpx;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-name {
|
.student-info {
|
||||||
flex: 1;
|
width: 100%;
|
||||||
font-size: 30rpx;
|
|
||||||
color: #333;
|
.student-name {
|
||||||
font-weight: 500;
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 表格深度样式 */
|
/* 表格深度样式 */
|
||||||
@@ -664,6 +751,168 @@
|
|||||||
background-color: #fff !important;
|
background-color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 项目选择弹窗 */
|
||||||
|
.project-popup-content {
|
||||||
|
width: 680rpx;
|
||||||
|
max-height: 70vh;
|
||||||
|
background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
|
||||||
|
border-radius: 32rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 0 24rpx 80rpx rgba(0, 0, 0, 0.25), 0 0 0 1rpx rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-project-list {
|
||||||
|
max-height: 55vh;
|
||||||
|
padding: 24rpx 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
background: linear-gradient(135deg, #fff 0%, #fafbfc 100%);
|
||||||
|
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;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
|
||||||
|
&::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.icon-bg {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 50%, #36cfc9 100%);
|
||||||
|
border-radius: 14rpx;
|
||||||
|
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: 6rpx;
|
||||||
|
right: 6rpx;
|
||||||
|
width: 10rpx;
|
||||||
|
height: 10rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes slideUp {
|
@keyframes slideUp {
|
||||||
from {
|
from {
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 包干项目模块 -->
|
<!-- 包干项目模块 -->
|
||||||
<view @click="Service.GoPage('/pages/userFunc/project')" class="create-card package-card">
|
<view @click=" goBaogan()" class="create-card package-card">
|
||||||
<view class="card-icon">
|
<view class="card-icon">
|
||||||
<view class="icon-circle package-icon">
|
<view class="icon-circle package-icon">
|
||||||
<u-icon name="grid" size="40" color="#fff"></u-icon>
|
<u-icon name="grid" size="40" color="#fff"></u-icon>
|
||||||
@@ -74,8 +74,12 @@
|
|||||||
<view class="card-arrow">
|
<view class="card-arrow">
|
||||||
<u-icon name="arrow-right" size="20" color="#1890ff"></u-icon>
|
<u-icon name="arrow-right" size="20" color="#1890ff"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<!-- <view @click="Service.GoPage('/pages/userFunc/cs')" class="">
|
||||||
|
cs
|
||||||
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 计时项目列表弹窗 -->
|
<!-- 计时项目列表弹窗 -->
|
||||||
@@ -98,11 +102,11 @@
|
|||||||
<view class="project-list">
|
<view class="project-list">
|
||||||
<text class="list-title" v-if="projects.length > 0">项目列表</text>
|
<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">
|
<scroll-view v-if="projects.length > 0" scroll-y="true" @scrolltolower='getList()' class="list-container">
|
||||||
<view v-for="project in projects" :key="project.planId" class="project-item"
|
<view v-for="(project,index) in projects" :key="project.planId" class="project-item"
|
||||||
@click="handleProjectClick(project)">
|
@click="handleProjectClick(project)">
|
||||||
<view class="item-icon">
|
<view class="item-icon">
|
||||||
<view class="icon-bg">
|
<view class="icon-bg">
|
||||||
<text class="icon-text">{{ project.name.charAt(0) }}</text>
|
<text class="icon-text">{{ index+1 }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="item-info">
|
<view class="item-info">
|
||||||
@@ -128,6 +132,7 @@
|
|||||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||||
import { loginService } from '@/Service/swimming/loginService'
|
import { loginService } from '@/Service/swimming/loginService'
|
||||||
import { PlanService } from '@/Service/swimming/PlanService'
|
import { PlanService } from '@/Service/swimming/PlanService'
|
||||||
|
import { userService } from '@/Service/swimming/userService'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -143,9 +148,14 @@
|
|||||||
// 弹窗状态
|
// 弹窗状态
|
||||||
const showTimingModal = ref(false)
|
const showTimingModal = ref(false)
|
||||||
|
|
||||||
|
|
||||||
|
let isUse=ref(false)
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
if (!Service.GetUserToken()) {
|
if (!Service.GetUserToken()) {
|
||||||
login()
|
login()
|
||||||
|
}else{
|
||||||
|
isUseFunc()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -167,6 +177,7 @@
|
|||||||
).then(content => {
|
).then(content => {
|
||||||
if (content.code == 0) {
|
if (content.code == 0) {
|
||||||
Service.SetUserToken(content.data.token)
|
Service.SetUserToken(content.data.token)
|
||||||
|
isUseFunc()
|
||||||
} else {
|
} else {
|
||||||
Service.Msg(content.msg)
|
Service.Msg(content.msg)
|
||||||
}
|
}
|
||||||
@@ -180,6 +191,18 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const isUseFunc=()=>{
|
||||||
|
userService.IsCheakUserVerify().then(res=>{
|
||||||
|
if(res.code==0){
|
||||||
|
isUse.value=res.data.isCheak
|
||||||
|
}else{
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 获取项目列表数据
|
// 获取项目列表数据
|
||||||
const getData = () => {
|
const getData = () => {
|
||||||
projects.value = []
|
projects.value = []
|
||||||
@@ -206,11 +229,23 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const goBaogan=()=>{
|
||||||
|
if(!isUse.value){
|
||||||
|
Service.Msg('用户暂无权限,请等待审核!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Service.GoPage('/pages/userFunc/project')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 显示计时项目列表弹窗
|
// 显示计时项目列表弹窗
|
||||||
const showTimingProjectModal = (index : any) => {
|
const showTimingProjectModal = (index : any) => {
|
||||||
uni.hideTabBar()
|
if(!isUse.value){
|
||||||
|
Service.Msg('用户暂无权限,请等待审核!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.hideTabBar()
|
||||||
showTimingModal.value = true
|
showTimingModal.value = true
|
||||||
currentIndex.value = index
|
currentIndex.value = index
|
||||||
getData()
|
getData()
|
||||||
@@ -531,6 +566,7 @@
|
|||||||
backdrop-filter: blur(10rpx);
|
backdrop-filter: blur(10rpx);
|
||||||
z-index: 998;
|
z-index: 998;
|
||||||
animation: fadeIn 0.3s ease;
|
animation: fadeIn 0.3s ease;
|
||||||
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header {
|
.modal-header {
|
||||||
@@ -578,7 +614,7 @@
|
|||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-width: 680rpx;
|
max-width: 680rpx;
|
||||||
max-height: 80vh;
|
height: 80vh;
|
||||||
background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
|
background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
|
||||||
border-radius: 32rpx;
|
border-radius: 32rpx;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
@@ -659,7 +695,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
max-height: 240rpx;
|
max-height: 800rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-item {
|
.project-item {
|
||||||
|
|||||||
@@ -23,14 +23,14 @@
|
|||||||
<view class="stats-section">
|
<view class="stats-section">
|
||||||
<view class="stats-card">
|
<view class="stats-card">
|
||||||
<view class="stat-item">
|
<view class="stat-item">
|
||||||
<view @click="Service.GoPage('/pages/userFunc/projectList')" class="stat-info">
|
<view @click="goPage('/pages/userFunc/projectList')" class="stat-info">
|
||||||
<text class="stat-value">{{ planCount }}</text>
|
<text class="stat-value">{{ planCount }}</text>
|
||||||
<text class="stat-label">我的项目</text>
|
<text class="stat-label">我的项目</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-divider"></view>
|
<view class="stat-divider"></view>
|
||||||
<view class="stat-item">
|
<view class="stat-item">
|
||||||
<view @click="Service.GoPage('/pages/userFunc/student')" class="stat-info">
|
<view @click="goPage('/pages/userFunc/student')" class="stat-info">
|
||||||
<text class="stat-value">{{ studentCount }}</text>
|
<text class="stat-value">{{ studentCount }}</text>
|
||||||
<text class="stat-label">学员数</text>
|
<text class="stat-label">学员数</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
<view class="menu-section">
|
<view class="menu-section">
|
||||||
<view class="menu-card">
|
<view class="menu-card">
|
||||||
<!-- 项目管理菜单项 -->
|
<!-- 项目管理菜单项 -->
|
||||||
<view class="menu-item" @click="Service.GoPage('/pages/userFunc/projectList')">
|
<view class="menu-item" @click="goPage('/pages/userFunc/projectList')">
|
||||||
<view class="menu-icon-bg project-icon-bg">
|
<view class="menu-icon-bg project-icon-bg">
|
||||||
<u-icon name="list" size="28" color="#fff"></u-icon>
|
<u-icon name="list" size="28" color="#fff"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<!-- 学员管理菜单项 -->
|
<!-- 学员管理菜单项 -->
|
||||||
<view class="menu-item" @click="Service.GoPage('/pages/userFunc/student')">
|
<view class="menu-item" @click="goPage('/pages/userFunc/student')">
|
||||||
<view class="menu-icon-bg academy-icon-bg">
|
<view class="menu-icon-bg academy-icon-bg">
|
||||||
<u-icon name="grid" size="28" color="#fff"></u-icon>
|
<u-icon name="grid" size="28" color="#fff"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
<view class="menu-divider"></view>
|
<view class="menu-divider"></view>
|
||||||
|
|
||||||
<!-- 数据分析菜单项 -->
|
<!-- 数据分析菜单项 -->
|
||||||
<view class="menu-item" @click="Service.GoPage('/pages/userFunc/analyze')">
|
<view class="menu-item" @click="goPage('/pages/userFunc/analyze')">
|
||||||
<view class="menu-icon-bg analysis-icon-bg">
|
<view class="menu-icon-bg analysis-icon-bg">
|
||||||
<u-icon name="order" size="28" color="#fff"></u-icon>
|
<u-icon name="order" size="28" color="#fff"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
@@ -104,6 +104,12 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部联系信息 -->
|
||||||
|
<view class="contact-footer" @click="callContact">
|
||||||
|
<text class="contact-label">客服联系人:张教练</text>
|
||||||
|
<text class="contact-phone">138-0000-0000</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -116,7 +122,7 @@
|
|||||||
let planCount=ref(0)
|
let planCount=ref(0)
|
||||||
let studentCount=ref(0)
|
let studentCount=ref(0)
|
||||||
let userInfo = ref<any>({})
|
let userInfo = ref<any>({})
|
||||||
|
let isUse=ref(false)
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
|
|
||||||
@@ -124,8 +130,19 @@
|
|||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
loadUserInfo()
|
loadUserInfo()
|
||||||
|
isUseFunc()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isUseFunc=()=>{
|
||||||
|
userService.IsCheakUserVerify().then(res=>{
|
||||||
|
if(res.code==0){
|
||||||
|
isUse.value=res.data.isCheak
|
||||||
|
}else{
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const loadUserInfo = () => {
|
const loadUserInfo = () => {
|
||||||
userService.GetUserInfo().then((content:any) => {
|
userService.GetUserInfo().then((content:any) => {
|
||||||
if (content.code == 0) {
|
if (content.code == 0) {
|
||||||
@@ -138,26 +155,21 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const goPage=(path:any)=>{
|
||||||
/**
|
if(!isUse.value){
|
||||||
* 跳转到训练计划页面
|
Service.Msg('用户暂无权限,请等待审核!')
|
||||||
* 点击"训练计划"菜单项时触发
|
return
|
||||||
* 功能说明:
|
}
|
||||||
* - 跳转到训练计划列表页面
|
Service.GoPage(path)
|
||||||
* - 用户可以查看、添加、编辑训练计划
|
|
||||||
*/
|
|
||||||
const goToTrainingPlans = () => {
|
|
||||||
Service.GoPage('/pages/userFunc/plan')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跳转到设置
|
const callContact = () => {
|
||||||
const goToSettings = () => {
|
uni.makePhoneCall({
|
||||||
Service.Msg('系统设置功能开发中')
|
phoneNumber: '13800000000',
|
||||||
}
|
fail: () => {
|
||||||
|
Service.Msg('拨打电话失败')
|
||||||
// 跳转到关于
|
}
|
||||||
const goToAbout = () => {
|
})
|
||||||
Service.Msg('关于我们功能开发中')
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -169,7 +181,7 @@
|
|||||||
.user-container {
|
.user-container {
|
||||||
padding: 20rpx;
|
padding: 20rpx;
|
||||||
padding-top: 30rpx;
|
padding-top: 30rpx;
|
||||||
padding-bottom: 40rpx;
|
padding-bottom: 140rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 用户信息卡片 */
|
/* 用户信息卡片 */
|
||||||
@@ -551,4 +563,30 @@
|
|||||||
margin: 0 32rpx;
|
margin: 0 32rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 底部固定联系信息 */
|
||||||
|
.contact-footer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
border-top: 1rpx solid #eee;
|
||||||
|
padding: 16rpx 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6rpx;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.contact-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-phone {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -378,10 +378,12 @@
|
|||||||
console.log('保存的数据:', data)
|
console.log('保存的数据:', data)
|
||||||
PlanService.AddPlan(data).then(res => {
|
PlanService.AddPlan(data).then(res => {
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
Service.Msg('添加成功!')
|
Service.Msg(planId.value?'修改成功!':'添加成功!')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
Service.GoPageBack()
|
Service.GoPageDelse('/pages/userFunc/hunyang?id=' + res.data.planId)
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Service.Msg(res.msg)
|
Service.Msg(res.msg)
|
||||||
}
|
}
|
||||||
|
|||||||
3872
src/pages/userFunc/cs.vue
Normal file
3872
src/pages/userFunc/cs.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,51 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<up-navbar v-if="!isLandscape" title="混氧项目" placeholder='true' :autoBack="true">
|
||||||
|
</up-navbar>
|
||||||
<view class="hunyang-container">
|
<view class="hunyang-container">
|
||||||
<!-- 项目信息头部 -->
|
<!-- 项目信息头部 -->
|
||||||
<view class="card-section header-section" style="position: relative;">
|
<view class="card-section header-section">
|
||||||
<view class="header-content">
|
<view class="header-content">
|
||||||
<view class="project-name">{{ name }}</view>
|
<view class="project-name">{{ name }}</view>
|
||||||
<view class="total-duration">
|
<view class="header-action" @click="Service.GoPage('/pages/userFunc/addHunyang?id='+planId)">
|
||||||
<text class="duration-label">计划总时长:</text>
|
<up-icon name="setting" size="22"></up-icon>
|
||||||
<text class="duration-value">{{ formatTotalDuration(totalDuration) }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="total-progress">
|
|
||||||
<text class="progress-text">
|
|
||||||
总进度: {{ totalCompletedGroupPlans }}/{{ totalGroupPlans }} 计划
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view style="position: absolute; top: 20rpx; right: 30rpx;">
|
|
||||||
<up-icon @click="Service.GoPage('/pages/userFunc/addHunyang?id='+planId)" name="setting" size="22"></up-icon>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 分组训练区域 -->
|
<!-- 分组训练区域 -->
|
||||||
<view class="card-section groups-section" v-if="groupStates.length > 0">
|
<view class="card-section groups-section" v-if="groupStates.length > 0">
|
||||||
<view class="section-title">训练分组 (同时进行)</view>
|
|
||||||
|
|
||||||
<view class="groups-list">
|
<view class="groups-list">
|
||||||
<view
|
<view v-for="(groupState, groupIndex) in groupStates" :key="groupIndex" class="group-card" :class="{
|
||||||
v-for="(groupState, groupIndex) in groupStates"
|
|
||||||
:key="groupIndex"
|
|
||||||
class="group-card"
|
|
||||||
:class="{
|
|
||||||
resting: groupState.isResting && !groupState.isCompleted,
|
resting: groupState.isResting && !groupState.isCompleted,
|
||||||
completed: groupState.isCompleted
|
completed: groupState.isCompleted
|
||||||
}"
|
}">
|
||||||
>
|
|
||||||
<!-- 分组头部 -->
|
<!-- 分组头部 -->
|
||||||
<view class="group-header">
|
<view class="group-header">
|
||||||
<view class="group-title-area">
|
<view class="group-title-area">
|
||||||
<text class="group-name">{{ groupState.groupName }}</text>
|
<text class="group-name">{{ groupState.groupName }}</text>
|
||||||
<text class="group-plan-index">
|
|
||||||
计划 {{ groupState.currentPlanIndex }}/{{ groupState.plans.length }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<view class="status-tag" :class="{ completed: groupState.isCompleted }">
|
|
||||||
<text v-if="groupState.isCompleted">已完成</text>
|
|
||||||
<text v-else-if="groupState.isResting">休息中</text>
|
|
||||||
<text v-else>训练中</text>
|
|
||||||
</view>
|
</view>
|
||||||
|
<text class="group-plan-index">
|
||||||
|
计划总时长: {{ groupState.allTime }}
|
||||||
|
<!-- {{ groupState.currentPlanIndex }}/{{ groupState.plans.length }} -->
|
||||||
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 当前计划内容 -->
|
<!-- 当前计划内容 -->
|
||||||
@@ -54,14 +36,10 @@
|
|||||||
<view class="plan-name">{{ groupState.currentPlan?.name }}</view>
|
<view class="plan-name">{{ groupState.currentPlan?.name }}</view>
|
||||||
|
|
||||||
<!-- 圈数指示器 -->
|
<!-- 圈数指示器 -->
|
||||||
<view class="laps-indicator">
|
<!-- <view class="laps-indicator">
|
||||||
<view
|
<view v-for="lap in groupState.currentPlan?.circle" :key="lap" class="lap-dot"
|
||||||
v-for="lap in groupState.currentPlan?.circle"
|
:class="{ completed: lap <= groupState.completedLaps }"></view>
|
||||||
:key="lap"
|
</view> -->
|
||||||
class="lap-dot"
|
|
||||||
:class="{ completed: lap <= groupState.completedLaps }"
|
|
||||||
></view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 倒计时 -->
|
<!-- 倒计时 -->
|
||||||
<view class="countdown-box" :class="{ resting: groupState.isResting }">
|
<view class="countdown-box" :class="{ resting: groupState.isResting }">
|
||||||
@@ -71,30 +49,46 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 阶段提示 -->
|
<!-- 阶段提示 -->
|
||||||
<view class="phase-hint" :class="{ resting: groupState.isResting }">
|
<!-- <view class="phase-hint" :class="{ resting: groupState.isResting }">
|
||||||
<text v-if="groupState.isResting" class="phase-text">
|
<text v-if="groupState.isResting && groupState.isPlanCompleted" class="phase-text">
|
||||||
|
计划完成 - 休息中
|
||||||
|
</text>
|
||||||
|
<text v-else-if="groupState.isResting" class="phase-text">
|
||||||
休息中 - 第 {{ groupState.completedLaps + 1 }} 圈准备
|
休息中 - 第 {{ groupState.completedLaps + 1 }} 圈准备
|
||||||
</text>
|
</text>
|
||||||
<text v-else class="phase-text">
|
<text v-else class="phase-text">
|
||||||
训练中 - 第 {{ groupState.completedLaps + 1 }} 圈
|
训练中 - 第 {{ groupState.completedLaps + 1 }} 圈
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view> -->
|
||||||
|
|
||||||
<!-- 计划详情 -->
|
<!-- 计划详情 -->
|
||||||
<view class="plan-details">
|
<view class="plan-details">
|
||||||
<view class="detail-box">
|
<text class="detail-badge">
|
||||||
<text class="detail-label">目标时间</text>
|
M:{{ formatCountdown(groupState.currentPlan?.target) }}
|
||||||
<text class="detail-value">{{ groupState.currentPlan?.target }}秒</text>
|
</text>
|
||||||
|
<text class="detail-badge">
|
||||||
|
X:{{ groupState.currentPlan?.rest }}
|
||||||
|
</text>
|
||||||
|
<text class="detail-badge">
|
||||||
|
Q:{{ groupState.currentPlan?.circle }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 分组控制按钮 -->
|
||||||
|
<view class="group-controls">
|
||||||
|
<view v-if="!groupState.isRunning && !groupState.isCompleted"
|
||||||
|
class="group-control-btn group-start-btn" @click="startGroup(groupState)">
|
||||||
|
<u-icon name="play-circle" size="24" color="#fff"></u-icon>
|
||||||
|
<text>开始</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="detail-box">
|
<view v-else-if="groupState.isRunning" class="group-control-btn group-pause-btn"
|
||||||
<text class="detail-label">休息时间</text>
|
@click="pauseGroup(groupState)">
|
||||||
<text class="detail-value">{{ groupState.currentPlan?.rest }}秒</text>
|
<u-icon name="pause-circle" size="24" color="#fff"></u-icon>
|
||||||
|
<text>暂停</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="detail-box">
|
<view class="group-control-btn group-reset-btn" @click="resetGroup(groupIndex)">
|
||||||
<text class="detail-label">圈数</text>
|
<u-icon name="reload" size="24" color="#fff"></u-icon>
|
||||||
<text class="detail-value">
|
<text>重置</text>
|
||||||
{{ groupState.completedLaps }}/{{ groupState.currentPlan?.circle }}
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -108,14 +102,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 全部完成 -->
|
|
||||||
<!-- <view class="card-section all-completed" v-if="isAllCompleted">
|
|
||||||
<view class="completed-center">
|
|
||||||
<u-icon name="checkmark-circle-fill" size="80" color="#4caf50"></u-icon>
|
|
||||||
<text class="completed-title">训练完成</text>
|
|
||||||
<text class="completed-desc">所有分组计划已执行完毕</text>
|
|
||||||
</view>
|
|
||||||
</view> -->
|
|
||||||
|
|
||||||
<!-- 底部按钮 -->
|
<!-- 底部按钮 -->
|
||||||
<view class="bottom-controls">
|
<view class="bottom-controls">
|
||||||
@@ -145,27 +132,30 @@
|
|||||||
|
|
||||||
// ==================== 类型 ====================
|
// ==================== 类型 ====================
|
||||||
interface PlanItem {
|
interface PlanItem {
|
||||||
name: string
|
name : string
|
||||||
target: number
|
target : number
|
||||||
rest: number
|
rest : number
|
||||||
circle: number
|
circle : number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GroupData {
|
interface GroupData {
|
||||||
groupName: string
|
groupName : string
|
||||||
group: PlanItem[]
|
allTime : string
|
||||||
|
group : PlanItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GroupState {
|
interface GroupState {
|
||||||
groupName: string
|
groupName : string
|
||||||
plans: PlanItem[]
|
plans : PlanItem[]
|
||||||
currentPlanIndex: number
|
currentPlanIndex : number
|
||||||
currentPlan: PlanItem | null
|
currentPlan : PlanItem | null
|
||||||
countdown: number
|
countdown : number
|
||||||
restCountdown: number
|
restCountdown : number
|
||||||
completedLaps: number
|
completedLaps : number
|
||||||
isResting: boolean
|
isResting : boolean
|
||||||
isCompleted: boolean
|
isCompleted : boolean
|
||||||
|
isPlanCompleted : boolean // 当前计划是否已完成(最后一圈休息后切换计划)
|
||||||
|
isRunning : boolean // 当前分组是否正在运行
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 数据 ====================
|
// ==================== 数据 ====================
|
||||||
@@ -174,6 +164,8 @@
|
|||||||
const groupStates = ref<GroupState[]>([])
|
const groupStates = ref<GroupState[]>([])
|
||||||
const isRunning = ref(false)
|
const isRunning = ref(false)
|
||||||
const timer = ref<number | null>(null)
|
const timer = ref<number | null>(null)
|
||||||
|
const planTotalDuration = ref(0) // 接口返回的计划总时长
|
||||||
|
const isLandscape = ref(false) // 是否为横屏
|
||||||
|
|
||||||
// ==================== 计算属性 ====================
|
// ==================== 计算属性 ====================
|
||||||
const totalGroupPlans = computed(() => {
|
const totalGroupPlans = computed(() => {
|
||||||
@@ -200,8 +192,10 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
// ==================== 生命周期 ====================
|
// ==================== 生命周期 ====================
|
||||||
onLoad((data: any) => {
|
onLoad((data : any) => {
|
||||||
planId.value = data.id
|
planId.value = data.id
|
||||||
|
checkOrientation()
|
||||||
|
uni.onWindowResize(checkOrientation)
|
||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
@@ -210,14 +204,20 @@
|
|||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
stopTimer()
|
stopTimer()
|
||||||
|
uni.offWindowResize(checkOrientation)
|
||||||
})
|
})
|
||||||
|
|
||||||
// ==================== 方法 ====================
|
// ==================== 方法 ====================
|
||||||
|
const checkOrientation = () => {
|
||||||
|
const info = uni.getSystemInfoSync()
|
||||||
|
isLandscape.value = info.windowWidth > info.windowHeight
|
||||||
|
}
|
||||||
|
|
||||||
const getPlanInfo = () => {
|
const getPlanInfo = () => {
|
||||||
PlanService.GetPlanInfo(planId.value).then(res => {
|
PlanService.GetPlanInfo(planId.value).then(res => {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
name.value = res.data.plan.name
|
name.value = res.data.plan.name
|
||||||
const data: GroupData[] = JSON.parse(res.data.plan.project)
|
const data : GroupData[] = JSON.parse(res.data.plan.project)
|
||||||
initGroups(data)
|
initGroups(data)
|
||||||
} else {
|
} else {
|
||||||
Service.Msg(res.msg)
|
Service.Msg(res.msg)
|
||||||
@@ -227,9 +227,10 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const initGroups = (data: GroupData[]) => {
|
const initGroups = (data : GroupData[]) => {
|
||||||
groupStates.value = data.map(g => ({
|
groupStates.value = data.map(g => ({
|
||||||
groupName: g.groupName,
|
groupName: g.groupName,
|
||||||
|
allTime: g.allTime,
|
||||||
plans: g.group,
|
plans: g.group,
|
||||||
currentPlanIndex: 0,
|
currentPlanIndex: 0,
|
||||||
currentPlan: g.group[0] || null,
|
currentPlan: g.group[0] || null,
|
||||||
@@ -237,10 +238,134 @@
|
|||||||
restCountdown: 0,
|
restCountdown: 0,
|
||||||
completedLaps: 0,
|
completedLaps: 0,
|
||||||
isResting: false,
|
isResting: false,
|
||||||
isCompleted: g.group.length === 0
|
isCompleted: g.group.length === 0,
|
||||||
|
isPlanCompleted: false,
|
||||||
|
isRunning: false
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 分组级控制 ====================
|
||||||
|
const startGroup = (gs : GroupState) => {
|
||||||
|
if (gs.isCompleted) {
|
||||||
|
Service.Msg('该分组已完成')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化未开始的分组
|
||||||
|
if (!gs.isCompleted && gs.countdown === 0 && !gs.isResting) {
|
||||||
|
gs.countdown = gs.currentPlan?.target || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
gs.isRunning = true
|
||||||
|
|
||||||
|
// 如果全局计时器未运行,启动它
|
||||||
|
if (!timer.value) {
|
||||||
|
startGlobalTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pauseGroup = (gs : GroupState) => {
|
||||||
|
gs.isRunning = false
|
||||||
|
|
||||||
|
// 检查是否还有运行的分组,如果没有则停止全局计时器
|
||||||
|
const hasRunning = groupStates.value.some(g => g.isRunning)
|
||||||
|
if (!hasRunning) {
|
||||||
|
stopTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetGroup = (groupIndex : number) => {
|
||||||
|
const gs = groupStates.value[groupIndex]
|
||||||
|
if (!gs) return
|
||||||
|
|
||||||
|
// 停止当前分组运行
|
||||||
|
gs.isRunning = false
|
||||||
|
|
||||||
|
// 重置分组状态
|
||||||
|
gs.currentPlanIndex = 0
|
||||||
|
gs.currentPlan = gs.plans[0] || null
|
||||||
|
gs.countdown = 0
|
||||||
|
gs.restCountdown = 0
|
||||||
|
gs.completedLaps = 0
|
||||||
|
gs.isResting = false
|
||||||
|
gs.isCompleted = gs.plans.length === 0
|
||||||
|
gs.isPlanCompleted = false
|
||||||
|
|
||||||
|
// 检查是否还有运行的分组,如果没有则停止全局计时器
|
||||||
|
const hasRunning = groupStates.value.some(g => g.isRunning)
|
||||||
|
if (!hasRunning) {
|
||||||
|
stopTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 全局计时器 ====================
|
||||||
|
const startGlobalTimer = () => {
|
||||||
|
isRunning.value = true
|
||||||
|
|
||||||
|
timer.value = setInterval(() => {
|
||||||
|
let hasRunningGroup = false
|
||||||
|
let allCompleted = true
|
||||||
|
|
||||||
|
groupStates.value.forEach(gs => {
|
||||||
|
if (gs.isCompleted) return
|
||||||
|
allCompleted = false
|
||||||
|
|
||||||
|
if (!gs.isRunning) return
|
||||||
|
hasRunningGroup = true
|
||||||
|
|
||||||
|
if (gs.isResting) {
|
||||||
|
// 休息倒计时
|
||||||
|
gs.restCountdown -= 0.01
|
||||||
|
if (gs.restCountdown <= 0) {
|
||||||
|
gs.isResting = false
|
||||||
|
gs.restCountdown = 0
|
||||||
|
|
||||||
|
if (gs.isPlanCompleted) {
|
||||||
|
// 当前计划所有圈已完成,切换到下一个计划
|
||||||
|
gs.isPlanCompleted = false
|
||||||
|
gs.currentPlanIndex++
|
||||||
|
if (gs.currentPlanIndex >= gs.plans.length) {
|
||||||
|
// 分组全部完成
|
||||||
|
gs.isCompleted = true
|
||||||
|
gs.currentPlan = null
|
||||||
|
gs.isRunning = false
|
||||||
|
} else {
|
||||||
|
// 开始下一个计划
|
||||||
|
gs.currentPlan = gs.plans[gs.currentPlanIndex]
|
||||||
|
gs.countdown = gs.currentPlan.target
|
||||||
|
gs.completedLaps = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 继续当前计划的下一圈
|
||||||
|
gs.countdown = gs.currentPlan?.target || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 训练倒计时
|
||||||
|
gs.countdown -= 0.01
|
||||||
|
if (gs.countdown <= 0) {
|
||||||
|
gs.completedLaps++
|
||||||
|
|
||||||
|
if (gs.completedLaps >= (gs.currentPlan?.circle || 0)) {
|
||||||
|
// 当前计划最后一圈完成,进入休息(休息后切换计划)
|
||||||
|
gs.isPlanCompleted = true
|
||||||
|
gs.isResting = true
|
||||||
|
gs.restCountdown = gs.currentPlan?.rest || 0
|
||||||
|
} else {
|
||||||
|
// 进入休息
|
||||||
|
gs.isResting = true
|
||||||
|
gs.restCountdown = gs.currentPlan?.rest || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (allCompleted || !hasRunningGroup) {
|
||||||
|
stopTimer()
|
||||||
|
}
|
||||||
|
}, 10)
|
||||||
|
}
|
||||||
|
|
||||||
const startTraining = () => {
|
const startTraining = () => {
|
||||||
if (groupStates.value.length === 0) {
|
if (groupStates.value.length === 0) {
|
||||||
Service.Msg('暂无训练计划')
|
Service.Msg('暂无训练计划')
|
||||||
@@ -251,66 +376,18 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isRunning.value = true
|
// 启动所有未完成的组
|
||||||
|
|
||||||
// 初始化未开始的分组
|
|
||||||
groupStates.value.forEach(gs => {
|
groupStates.value.forEach(gs => {
|
||||||
if (!gs.isCompleted && gs.countdown === 0 && !gs.isResting) {
|
if (!gs.isCompleted) {
|
||||||
gs.countdown = gs.currentPlan?.target || 0
|
startGroup(gs)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 启动计时器
|
|
||||||
timer.value = setInterval(() => {
|
|
||||||
let allDone = true
|
|
||||||
|
|
||||||
groupStates.value.forEach(gs => {
|
|
||||||
if (gs.isCompleted) return
|
|
||||||
allDone = false
|
|
||||||
|
|
||||||
if (gs.isResting) {
|
|
||||||
// 休息倒计时
|
|
||||||
gs.restCountdown -= 0.01
|
|
||||||
if (gs.restCountdown <= 0) {
|
|
||||||
gs.isResting = false
|
|
||||||
gs.restCountdown = 0
|
|
||||||
gs.countdown = gs.currentPlan?.target || 0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 训练倒计时
|
|
||||||
gs.countdown -= 0.01
|
|
||||||
if (gs.countdown <= 0) {
|
|
||||||
gs.completedLaps++
|
|
||||||
|
|
||||||
if (gs.completedLaps >= (gs.currentPlan?.circle || 0)) {
|
|
||||||
// 当前计划完成,切换到下一个
|
|
||||||
gs.currentPlanIndex++
|
|
||||||
if (gs.currentPlanIndex >= gs.plans.length) {
|
|
||||||
// 分组全部完成
|
|
||||||
gs.isCompleted = true
|
|
||||||
gs.currentPlan = null
|
|
||||||
} else {
|
|
||||||
// 开始下一个计划
|
|
||||||
gs.currentPlan = gs.plans[gs.currentPlanIndex]
|
|
||||||
gs.countdown = gs.currentPlan.target
|
|
||||||
gs.completedLaps = 0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 进入休息
|
|
||||||
gs.isResting = true
|
|
||||||
gs.restCountdown = gs.currentPlan?.rest || 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (allDone) {
|
|
||||||
stopTimer()
|
|
||||||
}
|
|
||||||
}, 10)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pauseTraining = () => {
|
const pauseTraining = () => {
|
||||||
|
groupStates.value.forEach(gs => {
|
||||||
|
gs.isRunning = false
|
||||||
|
})
|
||||||
stopTimer()
|
stopTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,19 +403,20 @@
|
|||||||
stopTimer()
|
stopTimer()
|
||||||
const data = groupStates.value.map(gs => ({
|
const data = groupStates.value.map(gs => ({
|
||||||
groupName: gs.groupName,
|
groupName: gs.groupName,
|
||||||
|
allTime: gs.allTime,
|
||||||
group: gs.plans
|
group: gs.plans
|
||||||
}))
|
}))
|
||||||
initGroups(data)
|
initGroups(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatCountdown = (seconds: number): string => {
|
const formatCountdown = (seconds : number) : string => {
|
||||||
const s = Math.max(0, Math.floor(seconds))
|
const s = Math.max(0, Math.floor(seconds))
|
||||||
const m = Math.floor(s / 60)
|
const m = Math.floor(s / 60)
|
||||||
const sec = s % 60
|
const sec = s % 60
|
||||||
return `${m.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`
|
return `${m.toString().padStart(2, '0')}′${sec.toString().padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatTotalDuration = (seconds: number): string => {
|
const formatTotalDuration = (seconds : number) : string => {
|
||||||
if (seconds === 0) return '0分0秒'
|
if (seconds === 0) return '0分0秒'
|
||||||
const m = Math.floor(seconds / 60)
|
const m = Math.floor(seconds / 60)
|
||||||
const s = Math.floor(seconds % 60)
|
const s = Math.floor(seconds % 60)
|
||||||
@@ -363,6 +441,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header-section {
|
.header-section {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.header-content {
|
.header-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -371,11 +451,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.project-name {
|
.project-name {
|
||||||
font-size: 36rpx;
|
font-size: 28rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-action {
|
||||||
|
position: absolute;
|
||||||
|
top: 24rpx;
|
||||||
|
right: 24rpx;
|
||||||
|
padding: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.total-duration {
|
.total-duration {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -464,7 +551,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.group-name {
|
.group-name {
|
||||||
font-size: 30rpx;
|
font-size: 22rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
@@ -493,10 +580,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.plan-name {
|
.plan-name {
|
||||||
font-size: 34rpx;
|
font-size: 28rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 16rpx;
|
margin-bottom: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 圈数指示器 */
|
/* 圈数指示器 */
|
||||||
@@ -550,40 +637,82 @@
|
|||||||
font-size: 72rpx;
|
font-size: 72rpx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-family: 'Helvetica Neue', monospace;
|
font-family: 'Helvetica Neue', monospace;
|
||||||
color: #4a90e2;
|
color: #000;
|
||||||
letter-spacing: 2rpx;
|
letter-spacing: 2rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.resting .countdown-number {
|
&.resting .countdown-number {
|
||||||
color: #ff9800;
|
color: red;
|
||||||
animation: flash 0.5s ease-in-out infinite;
|
animation: flash 0.5s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-details {
|
.plan-details {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: center;
|
||||||
margin-top: 20rpx;
|
|
||||||
padding-top: 16rpx;
|
|
||||||
border-top: 1rpx dashed #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-box {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6rpx;
|
gap: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-label {
|
.detail-badge {
|
||||||
font-size: 22rpx;
|
display: inline-flex;
|
||||||
color: #999;
|
align-items: center;
|
||||||
|
gap: 4rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
background: #f5f7fa;
|
||||||
|
padding: 8rpx 4rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-value {
|
.detail-badge-tag {
|
||||||
font-size: 28rpx;
|
width: 32rpx;
|
||||||
font-weight: 600;
|
height: 32rpx;
|
||||||
color: #333;
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分组控制按钮 */
|
||||||
|
.group-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20rpx;
|
||||||
|
|
||||||
|
padding-top: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-control-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
padding: 12rpx 24rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #fff;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-start-btn {
|
||||||
|
background: linear-gradient(135deg, #52c41a 0%, #389e0d 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-pause-btn {
|
||||||
|
background: linear-gradient(135deg, #faad14 0%, #d48806 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-reset-btn {
|
||||||
|
background: linear-gradient(135deg, #ff4d4f 0%, #d9363e 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-completed {
|
.plan-completed {
|
||||||
@@ -675,7 +804,168 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes flash {
|
@keyframes flash {
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: 0.6; }
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 横屏适配 */
|
||||||
|
@media (orientation: landscape) {
|
||||||
|
.hunyang-container {
|
||||||
|
padding: 16rpx;
|
||||||
|
padding-bottom: 140rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-section {
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-section {
|
||||||
|
.header-content {
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-action {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
font-size: 34rpx;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 26rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groups-list {
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-card {
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 40%;
|
||||||
|
max-width: 50%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-header {
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-name {
|
||||||
|
font-size: 22rpx;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-plan-index {
|
||||||
|
font-size: 22rpx;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.laps-indicator {
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
gap: 10rpx;
|
||||||
|
|
||||||
|
.lap-dot {
|
||||||
|
width: 14rpx;
|
||||||
|
height: 14rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-box {
|
||||||
|
margin: 10rpx 0;
|
||||||
|
|
||||||
|
.countdown-number {
|
||||||
|
font-size: 60rpx;
|
||||||
|
letter-spacing: 4rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-hint {
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
padding: 6rpx 16rpx;
|
||||||
|
|
||||||
|
.phase-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-details {
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-badge {
|
||||||
|
font-size: 24rpx;
|
||||||
|
padding: 6rpx 14rpx;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-controls {
|
||||||
|
padding-top: 10rpx;
|
||||||
|
gap: 16rpx;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-control-btn {
|
||||||
|
font-size: 24rpx;
|
||||||
|
padding: 10rpx 28rpx;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
gap: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-completed {
|
||||||
|
padding: 20rpx;
|
||||||
|
gap: 10rpx;
|
||||||
|
|
||||||
|
.completed-label {
|
||||||
|
font-size: 26rpx;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-controls {
|
||||||
|
padding: 10rpx 32rpx;
|
||||||
|
|
||||||
|
.controls-inner {
|
||||||
|
max-width: 100%;
|
||||||
|
gap: 24rpx;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn {
|
||||||
|
padding: 7rpx 32rpx;
|
||||||
|
flex: none;
|
||||||
|
min-width: 140rpx;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-text {
|
||||||
|
font-size: 18rpx;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,40 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="segmentation-container">
|
<view class="segmentation-container">
|
||||||
<!-- 配置区域 -->
|
|
||||||
<view class="config-section">
|
|
||||||
<view class="config-header">
|
|
||||||
<text class="config-title">分段设置</text>
|
|
||||||
|
|
||||||
<u-icon @click="Service.GoPage('/pages/userFunc/setCourse?id='+planId+'&type=2')" name="setting"
|
|
||||||
size="24" color="#1890ff"></u-icon>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
|
|
||||||
<view class="config-info">
|
|
||||||
<view class="info-text">总距离: {{ totalDistance }}米 ({{ segmentCount }}段 × {{ segmentDistance }}米) {{ startMode==0?'一起出发':'间隔出发' }} {{ startMode==0?'':' · '+intervalTime+'s' }} </view>
|
|
||||||
<view class="info-text" style="margin-top: 14rpx;" >启动模式: {{ startMode==0?'一起出发':'间隔出发' }} {{ startMode==0?'':' · '+intervalTime+'s' }} </view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 总计时器区域 -->
|
<!-- 总计时器区域 -->
|
||||||
<view class="total-time-section">
|
<view class="total-time-section" style="position: relative;">
|
||||||
<view class="timer-bar">
|
<view class="timer-bar">
|
||||||
<view class="timer-time">{{ formatTime(currentTime) }}</view>
|
<view class="timer-time">{{ formatTime(currentTime) }}</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="config-info">
|
||||||
|
<view class="info-text">总距离: {{ totalDistance }}米 ({{ segmentCount }}段 × {{ segmentDistance }}米)
|
||||||
|
{{ startMode==0?'一起出发':'间隔出发' }} {{ startMode==0?'':' · '+intervalTime+'s' }} </view>
|
||||||
|
</view>
|
||||||
|
<view class="" style="position: absolute; top: 20rpx; right: 20rpx; ">
|
||||||
|
<u-icon @click="Service.GoPage('/pages/userFunc/setCourse?id='+planId+'&type=2')" name="setting"
|
||||||
|
size="24" color="#1890ff"></u-icon>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 学生列表 -->
|
<!-- 学生列表 -->
|
||||||
<view class="students-section" style="margin-top: 20rpx;" >
|
<view class="students-section" style="margin-top: 20rpx;">
|
||||||
<view class="section-header">
|
<!-- <view class="section-header">
|
||||||
<text class="section-title">学生列表</text>
|
<text class="section-title">学生列表</text>
|
||||||
<text class="student-count">{{ students.length }}位学生</text>
|
<text class="student-count">{{ students.length }}位学生</text>
|
||||||
</view>
|
</view> -->
|
||||||
|
|
||||||
<view class="students-list">
|
<view class="students-list">
|
||||||
<view v-for="(student, index) in students" :key="student.id" class="student-item"
|
<view v-for="(student, index) in students" :key="student.id" class="student-item"
|
||||||
:class="{ 'not-started': !student.hasStarted }">
|
:class="{ 'not-started': !student.hasStarted }">
|
||||||
<view class="student-header">
|
<view class="student-header">
|
||||||
<view class="student-number" :class="{ 'started': student.hasStarted }">{{ student.number }}
|
<view class="student-number" @click="showStudentRecords(student)"
|
||||||
|
:class="{ 'started': student.hasStarted }">{{ student.number }}
|
||||||
</view>
|
</view>
|
||||||
<view class="student-info">
|
<view class="student-info">
|
||||||
<text class="student-name">{{ student.name }}</text>
|
<text class="student-name">{{ student.name }}</text>
|
||||||
@@ -51,17 +46,14 @@
|
|||||||
<view class="student-buttons">
|
<view class="student-buttons">
|
||||||
<button class="record-btn" :disabled="!student.hasStarted"
|
<button class="record-btn" :disabled="!student.hasStarted"
|
||||||
@click="recordStudentSegment(student)">
|
@click="recordStudentSegment(student)">
|
||||||
|
|
||||||
<text class="btn-text">记录</text>
|
<text class="btn-text">记录</text>
|
||||||
</button>
|
</button>
|
||||||
<button class="view-btn" @click="showStudentRecords(student)">
|
<view @click="resetStudent(student)" class="">
|
||||||
|
<u-icon name="reload" :bold="true" size="24" color="#1890ff"></u-icon>
|
||||||
<text class="btn-text">详情</text>
|
</view>
|
||||||
</button>
|
<!-- <button class="reset-btn" @click="resetStudent(student)">
|
||||||
<button class="reset-btn" @click="resetStudent(student)">
|
|
||||||
|
|
||||||
<text class="btn-text">重置</text>
|
<text class="btn-text">重置</text>
|
||||||
</button>
|
</button> -->
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -206,12 +198,12 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化时间显示
|
// 格式化时间显示(分:秒.毫秒)
|
||||||
const formatTime = (seconds : number) : string => {
|
const formatTime = (seconds : number) : string => {
|
||||||
const hours = Math.floor(seconds / 3600)
|
const mins = Math.floor(seconds / 60)
|
||||||
const mins = Math.floor((seconds % 3600) / 60)
|
|
||||||
const secs = Math.floor(seconds % 60)
|
const secs = Math.floor(seconds % 60)
|
||||||
return `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
const millis = Math.floor((seconds % 1) * 100)
|
||||||
|
return `${mins.toString().padStart(2, '0')}′${secs.toString().padStart(2, '0')}″${millis.toString().padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化持续时间
|
// 格式化持续时间
|
||||||
@@ -261,11 +253,12 @@
|
|||||||
return student.segments[lastIndex].time - student.segments[lastIndex - 1].time
|
return student.segments[lastIndex].time - student.segments[lastIndex - 1].time
|
||||||
}
|
}
|
||||||
|
|
||||||
// 简化时间格式化(分:秒)
|
// 简化时间格式化(分:秒.毫秒)
|
||||||
const formatSimpleTime = (seconds : number) : string => {
|
const formatSimpleTime = (seconds : number) : string => {
|
||||||
const mins = Math.floor(seconds / 60)
|
const mins = Math.floor(seconds / 60)
|
||||||
const secs = Math.floor(seconds % 60)
|
const secs = Math.floor(seconds % 60)
|
||||||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
const millis = Math.floor((seconds % 1) * 100)
|
||||||
|
return `${mins.toString().padStart(2, '0')}′${secs.toString().padStart(2, '0')}″${millis.toString().padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始计时
|
// 开始计时
|
||||||
@@ -278,7 +271,7 @@
|
|||||||
timerInterval = setInterval(() => {
|
timerInterval = setInterval(() => {
|
||||||
const elapsed = (Date.now() - startTime) / 1000
|
const elapsed = (Date.now() - startTime) / 1000
|
||||||
currentTime.value = elapsed
|
currentTime.value = elapsed
|
||||||
}, 10)
|
}, 1)
|
||||||
|
|
||||||
// 间隔出发模式
|
// 间隔出发模式
|
||||||
if (startMode.value === 1) {
|
if (startMode.value === 1) {
|
||||||
@@ -333,7 +326,6 @@
|
|||||||
|
|
||||||
// 记录学生分段
|
// 记录学生分段
|
||||||
const recordStudentSegment = (student : any) => {
|
const recordStudentSegment = (student : any) => {
|
||||||
console.log(22222);
|
|
||||||
if (!isRunning.value) {
|
if (!isRunning.value) {
|
||||||
Service.Msg('请先开始计时')
|
Service.Msg('请先开始计时')
|
||||||
return
|
return
|
||||||
@@ -357,7 +349,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
student.segments.push({
|
student.segments.push({
|
||||||
time: recordTime
|
time: Number(recordTime).toFixed(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
Service.Msg(`${student.name} 第${student.segments.length}段已记录`)
|
Service.Msg(`${student.name} 第${student.segments.length}段已记录`)
|
||||||
@@ -399,13 +391,6 @@
|
|||||||
Service.Msg('暂无数据可保存!')
|
Service.Msg('暂无数据可保存!')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSave) {
|
|
||||||
Service.Msg('存在学生未完成!')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let data = [
|
let data = [
|
||||||
{
|
{
|
||||||
studentId: "",
|
studentId: "",
|
||||||
@@ -429,18 +414,29 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
PlanService.AddPlanLog(planId.value, '分段项目', '', JSON.stringify(data), '').then(res => {
|
uni.showModal({
|
||||||
if (res.code == 0) {
|
title: '提示', // 对话框标题
|
||||||
Service.Msg('提交成功!')
|
content: '请确定提交数据!', // 显示的内容
|
||||||
setTimeout(()=>{
|
showCancel: true, // 是否显示取消按钮
|
||||||
Service.GoPageBack()
|
cancelText: '取消', // 取消按钮的文字
|
||||||
},1000)
|
confirmText: '确定', // 确认按钮的文字
|
||||||
} else {
|
success: function (res) {
|
||||||
Service.Msg(res.msg)
|
if (res.confirm) {
|
||||||
|
PlanService.AddPlanLog(planId.value, '分段项目', '', JSON.stringify(data), '').then(res => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
Service.Msg('提交成功!')
|
||||||
|
} else {
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (res.cancel) {
|
||||||
|
console.log('用户点击取消');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示学生记录弹窗
|
// 显示学生记录弹窗
|
||||||
@@ -474,41 +470,7 @@
|
|||||||
padding-bottom: 180rpx;
|
padding-bottom: 180rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 配置区域 */
|
|
||||||
.config-section {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
padding: 30rpx;
|
|
||||||
margin-bottom: 30rpx;
|
|
||||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
.config-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
|
|
||||||
.config-title {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.config-info {
|
|
||||||
background-color: #e6f7ff;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
padding: 16rpx 20rpx;
|
|
||||||
|
|
||||||
.info-text {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #1890ff;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 总计时器区域 */
|
/* 总计时器区域 */
|
||||||
.total-time-section {
|
.total-time-section {
|
||||||
@@ -516,6 +478,20 @@
|
|||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
padding: 30rpx;
|
padding: 30rpx;
|
||||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.config-info {
|
||||||
|
margin-top: 10rpx;
|
||||||
|
background-color: #e6f7ff;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.info-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #1890ff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.timer-bar {
|
.timer-bar {
|
||||||
@@ -599,6 +575,7 @@
|
|||||||
gap: 12rpx;
|
gap: 12rpx;
|
||||||
|
|
||||||
.student-name {
|
.student-name {
|
||||||
|
width: 120rpx;
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
color: #333;
|
||||||
@@ -617,7 +594,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.total-time-text {
|
.total-time-text {
|
||||||
font-size: 24rpx;
|
font-size: 30rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
font-family: 'DIN Alternate', monospace;
|
font-family: 'DIN Alternate', monospace;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -652,7 +629,7 @@
|
|||||||
.view-btn,
|
.view-btn,
|
||||||
.reset-btn {
|
.reset-btn {
|
||||||
height: 56rpx;
|
height: 56rpx;
|
||||||
padding: 0 20rpx;
|
padding: 0 35rpx;
|
||||||
border-radius: 8rpx;
|
border-radius: 8rpx;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
|
|||||||
@@ -121,6 +121,9 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="student-info">
|
<view class="student-info">
|
||||||
<view class="student-name">{{ student.name }}</view>
|
<view class="student-name">{{ student.name }}</view>
|
||||||
|
<view v-if="planId && type==1 " class="student-time" >
|
||||||
|
pb:{{ student.quicklyTime}}
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -766,6 +769,14 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
.student-time{
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #888;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.student-meta {
|
.student-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -19,17 +19,19 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else class="students-list">
|
<view v-else class="students-list">
|
||||||
<view v-for="(student, index) in students" :key="student.id" class="student-card">
|
<view v-for="(student, index) in students" :key="student.id" class="student-card" >
|
||||||
<view class="student-info">
|
<view class="student-info">
|
||||||
<view class="student-avatar">
|
<view class="student-avatar">
|
||||||
<text class="avatar-text">{{ student.name.charAt(0) }}</text>
|
<text class="avatar-text">{{ student.name.charAt(0) }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="student-details">
|
<view class="student-details">
|
||||||
<view class="detail-row">
|
<view class="detail-row" style="justify-content: space-between;">
|
||||||
<text class="student-name">{{ student.name }}</text>
|
<view style="display: flex; align-items: center; gap: 8rpx;">
|
||||||
<view class="student-gender" :class="student.sex === '男' ? 'male' : 'female'">
|
<text class="student-name">{{ student.name }}</text>
|
||||||
<u-icon :name="student.sex === '男' ? 'man' : 'woman'" size="14"></u-icon>
|
<view class="student-gender" :class="student.sex === '男' ? 'male' : 'female'">
|
||||||
{{ student.sex }}
|
<u-icon :name="student.sex === '男' ? 'man' : 'woman'" size="14"></u-icon>
|
||||||
|
{{ student.sex }}
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="detail-row">
|
<view class="detail-row">
|
||||||
@@ -48,18 +50,22 @@
|
|||||||
<text class="detail-label">住址:</text>
|
<text class="detail-label">住址:</text>
|
||||||
<text class="detail-value address-text">{{ student.address }}</text>
|
<text class="detail-value address-text">{{ student.address }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="detail-row">
|
||||||
|
<text class="detail-label">状态:</text>
|
||||||
|
<up-switch v-model="student.active" size="16" @change="confirmDelete(student)" ></up-switch>
|
||||||
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="student-actions">
|
<view class="student-actions">
|
||||||
<view class="action-btn edit-btn" @click="openEditModal(student)">
|
<view class="action-btn edit-btn" @click="openEditModal(student)">
|
||||||
<u-icon name="edit-pen" size="18" color="#1890ff"></u-icon>
|
<u-icon name="edit-pen" size="18" color="#1890ff"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="" style="width: 100%; height: 100rpx;" >
|
<view class="" style="width: 100%; height: 100rpx;">
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
<!-- 底部添加按钮 -->
|
<!-- 底部添加按钮 -->
|
||||||
@@ -87,15 +93,19 @@
|
|||||||
<view class="form-group">
|
<view class="form-group">
|
||||||
<text class="form-label">性别</text>
|
<text class="form-label">性别</text>
|
||||||
<view class="gender-selector">
|
<view class="gender-selector">
|
||||||
<view class="gender-option" :class="{ active: formData.gender === '男' }" @click="formData.gender = '男'">
|
<view class="gender-option" :class="{ active: formData.gender === '男' }"
|
||||||
|
@click="formData.gender = '男'">
|
||||||
<view class="gender-icon-box">
|
<view class="gender-icon-box">
|
||||||
<u-icon name="man" size="20" :color="formData.gender === '男' ? '#fff' : '#1890ff'"></u-icon>
|
<u-icon name="man" size="20"
|
||||||
|
:color="formData.gender === '男' ? '#fff' : '#1890ff'"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
<text>男</text>
|
<text>男</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="gender-option" :class="{ active: formData.gender === '女' }" @click="formData.gender = '女'">
|
<view class="gender-option" :class="{ active: formData.gender === '女' }"
|
||||||
|
@click="formData.gender = '女'">
|
||||||
<view class="gender-icon-box">
|
<view class="gender-icon-box">
|
||||||
<u-icon name="woman" size="20" :color="formData.gender === '女' ? '#fff' : '#fa8c16'"></u-icon>
|
<u-icon name="woman" size="20"
|
||||||
|
:color="formData.gender === '女' ? '#fff' : '#fa8c16'"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
<text>女</text>
|
<text>女</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -196,12 +206,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 打开编辑弹窗
|
// 打开编辑弹窗
|
||||||
const openEditModal = (student :any) => {
|
const openEditModal = (student : any) => {
|
||||||
editingStudent.value = student
|
editingStudent.value = student
|
||||||
formData.value = {
|
formData.value = {
|
||||||
id: student.studentId,
|
id: student.studentId,
|
||||||
name: student.name,
|
name: student.name,
|
||||||
gender: student.sex?student.sex: '男',
|
gender: student.sex ? student.sex : '男',
|
||||||
birthDate: student.birthday,
|
birthDate: student.birthday,
|
||||||
school: student.school,
|
school: student.school,
|
||||||
address: student.address
|
address: student.address
|
||||||
@@ -241,7 +251,11 @@
|
|||||||
status.value = 'loading'
|
status.value = 'loading'
|
||||||
studentService.GetStudentListPage(page.value.toString()).then(res => {
|
studentService.GetStudentListPage(page.value.toString()).then(res => {
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
students.value = [...students.value, ...res.data]
|
const list = res.data.map((item : any) => ({
|
||||||
|
...item,
|
||||||
|
active:item.status==1?true:false
|
||||||
|
}))
|
||||||
|
students.value = [...students.value, ...list]
|
||||||
status.value = res.data.length == 10 ? 'loadmore' : 'nomore'
|
status.value = res.data.length == 10 ? 'loadmore' : 'nomore'
|
||||||
page.value++
|
page.value++
|
||||||
} else {
|
} else {
|
||||||
@@ -264,19 +278,19 @@
|
|||||||
|
|
||||||
// 构造接口请求数据
|
// 构造接口请求数据
|
||||||
const requestData = {
|
const requestData = {
|
||||||
studentId:formData.value.id?formData.value.id:'' ,
|
studentId: formData.value.id ? formData.value.id : '',
|
||||||
name: formData.value.name,
|
name: formData.value.name,
|
||||||
sex: formData.value.gender,
|
sex: formData.value.gender,
|
||||||
birthday: formData.value.birthDate,
|
birthday: formData.value.birthDate,
|
||||||
school: formData.value.school?formData.value.school:'' ,
|
school: formData.value.school ? formData.value.school : '',
|
||||||
address: formData.value.address ?formData.value.address:''
|
address: formData.value.address ? formData.value.address : ''
|
||||||
}
|
}
|
||||||
console.log(requestData);
|
console.log(requestData);
|
||||||
|
|
||||||
// 调用添加学员接口
|
// 调用添加学员接口
|
||||||
studentService.Add(requestData).then((content) => {
|
studentService.Add(requestData).then((content) => {
|
||||||
if (content.code == 0) {
|
if (content.code == 0) {
|
||||||
Service.Msg(formData.value.id?'修改成功!':'添加成功!')
|
Service.Msg(formData.value.id ? '修改成功!' : '添加成功!')
|
||||||
getData()
|
getData()
|
||||||
} else {
|
} else {
|
||||||
Service.Msg(content.msg)
|
Service.Msg(content.msg)
|
||||||
@@ -287,17 +301,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 确认删除
|
// 确认删除
|
||||||
const confirmDelete = (student : Student) => {
|
const confirmDelete = (student : any) => {
|
||||||
uni.showModal({
|
|
||||||
title: '确认删除',
|
deleteStudent(student.studentId)
|
||||||
content: `确定要删除学员「${student.name}」吗?`,
|
|
||||||
confirmColor: '#ff4d4f',
|
|
||||||
success: (res) => {
|
|
||||||
if (res.confirm) {
|
|
||||||
deleteStudent(student.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除学员
|
// 删除学员
|
||||||
@@ -305,7 +311,7 @@
|
|||||||
studentService.Delete(id).then(res => {
|
studentService.Delete(id).then(res => {
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
students.value = students.value.filter(s => s.id !== id)
|
students.value = students.value.filter(s => s.id !== id)
|
||||||
Service.Msg('删除成功', 'success')
|
Service.Msg('修改成功!')
|
||||||
} else {
|
} else {
|
||||||
Service.Msg(res.msg)
|
Service.Msg(res.msg)
|
||||||
}
|
}
|
||||||
@@ -332,6 +338,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 36rpx;
|
font-size: 36rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -501,10 +508,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.delete-btn {
|
&.delete-btn {
|
||||||
background-color: #fff1f0;
|
background-color: #f3f3f3;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: #ffccc7;
|
background-color: #f3f3f3;
|
||||||
transform: scale(0.92);
|
transform: scale(0.92);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,17 +11,13 @@
|
|||||||
</view>
|
</view>
|
||||||
<!-- 计划信息 -->
|
<!-- 计划信息 -->
|
||||||
<view class="plan-info" v-if="planId">
|
<view class="plan-info" v-if="planId">
|
||||||
<text class="plan-title">项目名称: {{ planName }}</text>
|
<text class="plan-title">{{ planName }} · {{ stopwatchMode=='interval'?'间隔触发':'一起出发' }} ·
|
||||||
|
共{{ groupedAthletes.length }}组{{ athletes.length }}位选手</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 选手列表 -->
|
<!-- 选手列表 -->
|
||||||
<view class="athletes-section">
|
<view class="athletes-section">
|
||||||
<view class="section-header">
|
|
||||||
<text class="section-title">{{ stopwatchMode=='interval'?'间隔触发':'一起出发' }}·共{{ groupedAthletes.length }}组</text>
|
|
||||||
<text class="athlete-count">{{ athletes.length }}位选手</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="athletes-list">
|
<view class="athletes-list">
|
||||||
<view v-for="group in groupedAthletes" :key="group.groupNumber" class="group-wrapper">
|
<view v-for="group in groupedAthletes" :key="group.groupNumber" class="group-wrapper">
|
||||||
<view class="group-header">
|
<view class="group-header">
|
||||||
@@ -42,9 +38,9 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="time-wrapper">
|
<view class="time-wrapper">
|
||||||
<text
|
<text
|
||||||
class="athlete-time">{{ !athlete.time? ' 00:00:00 ':formatTime(athlete.time) }}</text>
|
class="athlete-time">{{ !athlete.time? ' 00′00″00 ':formatTime(athlete.time) }}</text>
|
||||||
<text class="best-time">最快:
|
<text class="best-time">PB:
|
||||||
{{ athlete.bestTime !== null ? athlete.quicklyTime: '无' }}</text>
|
{{ athlete.studentQuicklyTime !== null ? athlete.studentQuicklyTime: '无' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="" style="display: flex;align-items: center; gap: 10rpx;">
|
<view class="" style="display: flex;align-items: center; gap: 10rpx;">
|
||||||
|
|
||||||
@@ -60,12 +56,13 @@
|
|||||||
<view class="" style="width: 100%; height: 100rpx;"></view>
|
<view class="" style="width: 100%; height: 100rpx;"></view>
|
||||||
|
|
||||||
<!-- 右下角悬浮提交按钮 -->
|
<!-- 右下角悬浮提交按钮 -->
|
||||||
<view class="fab-submit" @click="submitData">
|
<!-- <view class="fab-submit" @click="submitData">
|
||||||
<text class="fab-text">提交</text>
|
<text class="fab-text">提交</text>
|
||||||
</view>
|
</view> -->
|
||||||
|
|
||||||
<!-- 记录提示弹窗 -->
|
<!-- 记录提示弹窗 -->
|
||||||
<u-popup :show="showRecord" mode="center" :round="20" :closeable="true" :safeAreaInsetBottom='false' @close="showRecord=false" closeOnClickOverlay>
|
<u-popup :show="showRecord" mode="center" :round="20" :closeable="true" :safeAreaInsetBottom='false'
|
||||||
|
@close="showRecord=false" closeOnClickOverlay>
|
||||||
<view class="record-notice-modal">
|
<view class="record-notice-modal">
|
||||||
<view class="notice-header">
|
<view class="notice-header">
|
||||||
<text class="notice-title">恭喜以下学生打破记录</text>
|
<text class="notice-title">恭喜以下学生打破记录</text>
|
||||||
@@ -80,36 +77,39 @@
|
|||||||
<view v-for="(item,index) in record" class="table-row">
|
<view v-for="(item,index) in record" class="table-row">
|
||||||
<text class="row-cell index-cell">{{ index+1 }}</text>
|
<text class="row-cell index-cell">{{ index+1 }}</text>
|
||||||
<text class="row-cell name-cell">{{ item.name }}</text>
|
<text class="row-cell name-cell">{{ item.name }}</text>
|
||||||
<text class="row-cell time-cell">{{ item.quicklyTime }}</text>
|
<text class="row-cell time-cell">{{ item.studentQuicklyTime }}</text>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</u-popup>
|
</u-popup>
|
||||||
|
|
||||||
|
<!-- 可拖动悬浮球 -->
|
||||||
|
<view class="float-ball" @click="ballClick(isRunning)" :style="ballStyle" @touchstart="touchStart" @touchmove="touchMove"
|
||||||
|
@touchend="touchEnd">
|
||||||
|
<view class="ball-shine"></view>
|
||||||
|
<!-- @click="startTimer" @click="recordNextAthlete" -->
|
||||||
|
<view v-if="!isRunning" class="ball-text">开始</view>
|
||||||
|
<view v-if="isRunning" class="ball-text">记录</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 底部控制按钮 -->
|
<!-- 底部控制按钮 -->
|
||||||
<view class="control-buttons">
|
<view class="control-buttons">
|
||||||
<view class="button-group" :class="{ 'record-group': isRunning }">
|
<button class="ctrl-btn btn-submit" @click="submitData">
|
||||||
<button v-if="!isRunning" class="start-btn" @click="startTimer">
|
<u-icon name="checkmark-circle" size="24" color="#fff"></u-icon>
|
||||||
<u-icon name="play-right" size="24" color="#fff"></u-icon>
|
<text class="btn-text">提交</text>
|
||||||
<text class="btn-text">开始</text>
|
</button>
|
||||||
</button>
|
<button class="ctrl-btn btn-data" @click="Service.GoPage('/pages/dataAnalyze/timingAnalze')">
|
||||||
|
<u-icon name="list-dot" size="24" color="#fff"></u-icon>
|
||||||
<button v-if="isRunning" class="record-btn" @click="recordNextAthlete">
|
<text class="btn-text">数据</text>
|
||||||
<u-icon name="checkmark-circle" size="24" color="#fff"></u-icon>
|
</button>
|
||||||
<text class="btn-text">记录</text>
|
<button v-if="!isRunning" class="ctrl-btn btn-reset" @click="resetAll">
|
||||||
</button>
|
<u-icon name="reload" size="24" color="#fff"></u-icon>
|
||||||
</view>
|
<text class="btn-text">重置</text>
|
||||||
<view class="button-group" :class="{ 'pause-group': isRunning }">
|
</button>
|
||||||
<button v-if="!isRunning" class="reset-all-btn" @click="resetAll">
|
<button v-if="isRunning" class="ctrl-btn btn-pause" @click="stopTimer">
|
||||||
<u-icon name="reload" size="24" color="#fff"></u-icon>
|
<text class="btn-text">暂停</text>
|
||||||
<text class="btn-text">重置</text>
|
</button>
|
||||||
</button>
|
|
||||||
<button v-if="isRunning" class="reset-all-btn" @click="stopTimer">
|
|
||||||
<text class="btn-text" style="font-size: 28rpx;">暂停</text>
|
|
||||||
</button>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
||||||
@@ -154,7 +154,8 @@
|
|||||||
const groupSize = ref('0')
|
const groupSize = ref('0')
|
||||||
|
|
||||||
|
|
||||||
|
// 创建播放器
|
||||||
|
const video = uni.createInnerAudioContext()
|
||||||
|
|
||||||
// 计时器状态
|
// 计时器状态
|
||||||
const isRunning = ref(false)
|
const isRunning = ref(false)
|
||||||
@@ -169,11 +170,46 @@
|
|||||||
const stopwatchMode = ref<'interval' | 'together'>('interval')
|
const stopwatchMode = ref<'interval' | 'together'>('interval')
|
||||||
// 间隔时间(秒)
|
// 间隔时间(秒)
|
||||||
const intervalTime = ref('0')
|
const intervalTime = ref('0')
|
||||||
let record=ref<Array<any>>([])
|
let record = ref<Array<any>>([])
|
||||||
let showRecord=ref(false)
|
let showRecord = ref(false)
|
||||||
|
|
||||||
|
// 已提示过出发的组号集合(间隔模式用)
|
||||||
|
const notifiedGroupNumbers = ref<Set<number>>(new Set())
|
||||||
|
|
||||||
|
// 可拖动悬浮球
|
||||||
|
const FLOAT_BALL_KEY = 'swiming_float_ball_position'
|
||||||
|
const ballPosition = ref({ x: 20, y: 300 })
|
||||||
|
const startPosition = ref({ x: 0, y: 0 })
|
||||||
|
const touchStartPoint = ref({ x: 0, y: 0 })
|
||||||
|
const ballSize = ref({ w: 50, h: 50 })
|
||||||
|
|
||||||
|
const ballStyle = computed(() => {
|
||||||
|
return `left:${ballPosition.value.x}px;top:${ballPosition.value.y}px;`
|
||||||
|
})
|
||||||
|
|
||||||
onLoad((data : any) => {
|
onLoad((data : any) => {
|
||||||
planId.value = data.id
|
planId.value = data.id
|
||||||
|
|
||||||
|
const sys = uni.getSystemInfoSync()
|
||||||
|
ballSize.value.w = sys.windowWidth * 50 / 375
|
||||||
|
ballSize.value.h = ballSize.value.w
|
||||||
|
ballPosition.value = {
|
||||||
|
x: 20,
|
||||||
|
y: sys.windowHeight - ballSize.value.h - 200
|
||||||
|
}
|
||||||
|
|
||||||
|
const saved = uni.getStorageSync(FLOAT_BALL_KEY)
|
||||||
|
if (saved) {
|
||||||
|
try {
|
||||||
|
const pos = JSON.parse(saved)
|
||||||
|
if (typeof pos.x === 'number' && typeof pos.y === 'number') {
|
||||||
|
ballPosition.value = {
|
||||||
|
x: Math.max(0, Math.min(pos.x, sys.windowWidth - ballSize.value.w)),
|
||||||
|
y: Math.max(0, Math.min(pos.y, sys.windowHeight - ballSize.value.h))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) { }
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
@@ -196,6 +232,21 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 悬浮球事件
|
||||||
|
const ballClick=( data:any)=>{
|
||||||
|
if(data){
|
||||||
|
recordNextAthlete()
|
||||||
|
}else{
|
||||||
|
startTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 播报
|
||||||
|
const speak=()=>{
|
||||||
|
video.src='/static/icon/video.mp3'
|
||||||
|
video.play()
|
||||||
|
}
|
||||||
|
|
||||||
// 计算分组
|
// 计算分组
|
||||||
const groupedAthletes = computed<Group[]>(() => {
|
const groupedAthletes = computed<Group[]>(() => {
|
||||||
const size = parseInt(groupSize.value) || 2
|
const size = parseInt(groupSize.value) || 2
|
||||||
@@ -243,7 +294,7 @@
|
|||||||
const mins = Math.floor(seconds / 60)
|
const mins = Math.floor(seconds / 60)
|
||||||
const secs = Math.floor(seconds % 60)
|
const secs = Math.floor(seconds % 60)
|
||||||
const ms = Math.floor((seconds % 1) * 100)
|
const ms = Math.floor((seconds % 1) * 100)
|
||||||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}.${ms.toString().padStart(2, '0')}`
|
return `${mins.toString().padStart(2, '0')}′${secs.toString().padStart(2, '0')}″${ms.toString().padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始计时
|
// 开始计时
|
||||||
@@ -251,12 +302,26 @@
|
|||||||
if (isRunning.value) return
|
if (isRunning.value) return
|
||||||
|
|
||||||
isRunning.value = true
|
isRunning.value = true
|
||||||
|
notifiedGroupNumbers.value.clear()
|
||||||
startTime = Date.now() - currentTime.value * 1000
|
startTime = Date.now() - currentTime.value * 1000
|
||||||
|
|
||||||
timerInterval = setInterval(() => {
|
timerInterval = setInterval(() => {
|
||||||
const elapsed = (Date.now() - startTime) / 1000
|
const elapsed = (Date.now() - startTime) / 1000
|
||||||
currentTime.value = elapsed
|
currentTime.value = elapsed
|
||||||
|
|
||||||
|
// 间隔模式下,检查是否有新组到达出发时间
|
||||||
|
if (stopwatchMode.value === 'interval') {
|
||||||
|
const interval = parseFloat(intervalTime.value) || 10
|
||||||
|
groupedAthletes.value.forEach(group => {
|
||||||
|
const groupOffset = (group.groupNumber - 1) * interval
|
||||||
|
if (elapsed >= groupOffset && !notifiedGroupNumbers.value.has(group.groupNumber)) {
|
||||||
|
notifiedGroupNumbers.value.add(group.groupNumber)
|
||||||
|
Service.Msg(`第${group.groupNumber}组出发了`)
|
||||||
|
speak()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 更新未完成选手的时间(考虑偏移量)
|
// 更新未完成选手的时间(考虑偏移量)
|
||||||
athletes.value.forEach(athlete => {
|
athletes.value.forEach(athlete => {
|
||||||
if (!athlete.finished) {
|
if (!athlete.finished) {
|
||||||
@@ -264,7 +329,7 @@
|
|||||||
athlete.time = Math.max(0, elapsed - offset)
|
athlete.time = Math.max(0, elapsed - offset)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, 16)
|
}, 30)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 停止计时
|
// 停止计时
|
||||||
@@ -375,6 +440,7 @@
|
|||||||
stopTimer()
|
stopTimer()
|
||||||
currentTime.value = 0
|
currentTime.value = 0
|
||||||
currentAthleteIndex.value = 0
|
currentAthleteIndex.value = 0
|
||||||
|
notifiedGroupNumbers.value.clear()
|
||||||
|
|
||||||
athletes.value.forEach(athlete => {
|
athletes.value.forEach(athlete => {
|
||||||
athlete.time = 0
|
athlete.time = 0
|
||||||
@@ -388,7 +454,7 @@
|
|||||||
const mins = Math.floor(seconds / 60)
|
const mins = Math.floor(seconds / 60)
|
||||||
const secs = Math.floor(seconds % 60)
|
const secs = Math.floor(seconds % 60)
|
||||||
const ms = Math.floor((seconds % 1) * 1000)
|
const ms = Math.floor((seconds % 1) * 1000)
|
||||||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}`
|
return `${mins.toString().padStart(2, '0')}′${secs.toString().padStart(2, '0')}″${ms.toString().padStart(3, '0')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交数据
|
// 提交数据
|
||||||
@@ -400,10 +466,6 @@
|
|||||||
Service.Msg('暂无数据可提交')
|
Service.Msg('暂无数据可提交')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if(!allFinished){
|
|
||||||
// Service.Msg('还用学生未完成!')
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
stopTimer()
|
stopTimer()
|
||||||
let data = [{
|
let data = [{
|
||||||
planName: "",
|
planName: "",
|
||||||
@@ -417,35 +479,74 @@
|
|||||||
planName: planName.value,
|
planName: planName.value,
|
||||||
studentId: item.studentId,
|
studentId: item.studentId,
|
||||||
studentName: item.name,
|
studentName: item.name,
|
||||||
time: formatMinuteTime(item.time)
|
time: item.time
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示', // 对话框标题
|
||||||
|
content: '请确定提交数据!', // 显示的内容
|
||||||
|
showCancel: true, // 是否显示取消按钮
|
||||||
|
cancelText: '取消', // 取消按钮的文字
|
||||||
|
confirmText: '确定', // 确认按钮的文字
|
||||||
|
success: function (res) {
|
||||||
|
if (res.confirm) {
|
||||||
|
PlanService.AddPlanLog(planId.value, '计时项目', JSON.stringify(data), '', '', '').then(res => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
Service.Msg('提交成功!')
|
||||||
|
stopTimer()
|
||||||
|
currentTime.value = 0
|
||||||
|
currentAthleteIndex.value = 0
|
||||||
|
|
||||||
PlanService.AddPlanLog(planId.value,'计时项目',JSON.stringify(data),'','','').then(res=>{
|
athletes.value.forEach(athlete => {
|
||||||
if(res.code==0){
|
athlete.time = 0
|
||||||
Service.Msg('提交成功!')
|
athlete.finished = false
|
||||||
stopTimer()
|
athlete.startOffset = undefined
|
||||||
currentTime.value = 0
|
})
|
||||||
currentAthleteIndex.value = 0
|
|
||||||
|
|
||||||
athletes.value.forEach(athlete => {
|
if (res.data.record.length > 0) {
|
||||||
athlete.time = 0
|
record.value = res.data.record
|
||||||
athlete.finished = false
|
showRecord.value = true
|
||||||
athlete.startOffset = undefined
|
}
|
||||||
})
|
} else {
|
||||||
|
Service.Msg(res.msg)
|
||||||
if(res.data.record.length>0){
|
}
|
||||||
record.value=res.data.record
|
})
|
||||||
showRecord.value=true
|
} else if (res.cancel) {
|
||||||
|
console.log('用户点击取消');
|
||||||
}
|
}
|
||||||
}else{
|
|
||||||
Service.Msg(res.msg)
|
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 悬浮球拖动事件
|
||||||
|
const touchStart = (e : any) => {
|
||||||
|
const touch = e.touches[0]
|
||||||
|
touchStartPoint.value = { x: touch.clientX, y: touch.clientY }
|
||||||
|
startPosition.value = { ...ballPosition.value }
|
||||||
|
}
|
||||||
|
|
||||||
|
const touchMove = (e : any) => {
|
||||||
|
const touch = e.touches[0]
|
||||||
|
const deltaX = touch.clientX - touchStartPoint.value.x
|
||||||
|
const deltaY = touch.clientY - touchStartPoint.value.y
|
||||||
|
ballPosition.value = {
|
||||||
|
x: startPosition.value.x + deltaX,
|
||||||
|
y: startPosition.value.y + deltaY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const touchEnd = () => {
|
||||||
|
const sys = uni.getSystemInfoSync()
|
||||||
|
ballPosition.value = {
|
||||||
|
x: Math.max(0, Math.min(ballPosition.value.x, sys.windowWidth - ballSize.value.w)),
|
||||||
|
y: Math.max(0, Math.min(ballPosition.value.y, sys.windowHeight - ballSize.value.h))
|
||||||
|
}
|
||||||
|
uni.setStorageSync(FLOAT_BALL_KEY, JSON.stringify(ballPosition.value))
|
||||||
|
}
|
||||||
|
|
||||||
// 页面卸载时清理计时器
|
// 页面卸载时清理计时器
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (timerInterval) {
|
if (timerInterval) {
|
||||||
@@ -479,6 +580,10 @@
|
|||||||
font-size: 80rpx;
|
font-size: 80rpx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #ff0000;
|
color: #ff0000;
|
||||||
|
font-family: 'DIN Alternate', monospace;
|
||||||
|
min-width: 400rpx;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-info {
|
.plan-info {
|
||||||
@@ -534,7 +639,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 24rpx 28rpx;
|
padding: 10rpx 28rpx;
|
||||||
background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%);
|
background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%);
|
||||||
border-left: 6rpx solid #1890ff;
|
border-left: 6rpx solid #1890ff;
|
||||||
|
|
||||||
@@ -712,143 +817,111 @@
|
|||||||
/* 底部控制按钮 */
|
/* 底部控制按钮 */
|
||||||
.control-buttons {
|
.control-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 24rpx;
|
gap: 20rpx;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, #ffffff 100%);
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, #ffffff 100%);
|
||||||
padding: 32rpx 40rpx;
|
padding: 24rpx 32rpx;
|
||||||
// padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||||
border-top: 1rpx solid rgba(0, 0, 0, 0.06);
|
border-top: 1rpx solid rgba(0, 0, 0, 0.06);
|
||||||
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.08);
|
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
.button-group {
|
.ctrl-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
height: 92rpx;
|
||||||
justify-content: center;
|
border-radius: 24rpx;
|
||||||
}
|
|
||||||
|
|
||||||
.record-group {
|
|
||||||
flex: 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pause-group {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-btn,
|
|
||||||
.start-btn,
|
|
||||||
.pause-btn,
|
|
||||||
.reset-all-btn,
|
|
||||||
.record-btn,
|
|
||||||
.submit-btn {
|
|
||||||
flex: 1;
|
|
||||||
height: 100rpx;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 32rpx;
|
font-size: 30rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 12rpx;
|
gap: 10rpx;
|
||||||
position: relative;
|
transition: all 0.25s ease;
|
||||||
overflow: hidden;
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.12);
|
||||||
transition: all 0.3s ease;
|
|
||||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.15);
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 50%;
|
|
||||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.3) 0%, transparent 100%);
|
|
||||||
border-radius: 20rpx 20rpx 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 2rpx;
|
|
||||||
background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.5) 50%, transparent 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.u-icon) {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.start-btn {
|
|
||||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
|
||||||
color: #ffffff;
|
|
||||||
box-shadow: 0 6rpx 20rpx rgba(82, 196, 26, 0.4);
|
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.96);
|
transform: scale(0.96);
|
||||||
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pause-btn {
|
.btn-submit {
|
||||||
background: linear-gradient(135deg, #fa541c 0%, #ff7a45 100%);
|
background: linear-gradient(135deg, #faad14 0%, #ffc53d 100%);
|
||||||
color: #ffffff;
|
color: #fff;
|
||||||
box-shadow: 0 6rpx 20rpx rgba(250, 84, 28, 0.4);
|
box-shadow: 0 6rpx 18rpx rgba(250, 173, 20, 0.35);
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.96);
|
|
||||||
box-shadow: 0 2rpx 8rpx rgba(250, 84, 28, 0.3);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.reset-all-btn {
|
.btn-data {
|
||||||
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||||
color: #ffffff;
|
color: #fff;
|
||||||
box-shadow: 0 6rpx 20rpx rgba(24, 144, 255, 0.4);
|
box-shadow: 0 6rpx 18rpx rgba(24, 144, 255, 0.35);
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.96);
|
|
||||||
box-shadow: 0 2rpx 8rpx rgba(24, 144, 255, 0.3);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-btn {
|
.btn-reset {
|
||||||
background: linear-gradient(135deg, #722ed1 0%, #9254de 100%);
|
|
||||||
color: #ffffff;
|
|
||||||
box-shadow: 0 6rpx 20rpx rgba(114, 46, 209, 0.4);
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.96);
|
|
||||||
box-shadow: 0 2rpx 8rpx rgba(114, 46, 209, 0.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-btn {
|
|
||||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||||
color: #ffffff;
|
color: #fff;
|
||||||
box-shadow: 0 6rpx 20rpx rgba(82, 196, 26, 0.4);
|
box-shadow: 0 6rpx 18rpx rgba(82, 196, 26, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
&:active {
|
.btn-pause {
|
||||||
transform: scale(0.96);
|
background: linear-gradient(135deg, #fa541c 0%, #ff7a45 100%);
|
||||||
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
|
color: #fff;
|
||||||
}
|
box-shadow: 0 6rpx 18rpx rgba(250, 84, 28, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-text {
|
.btn-text {
|
||||||
font-size: 32rpx;
|
font-size: 30rpx;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 2rpx;
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可拖动悬浮球 */
|
||||||
|
.float-ball {
|
||||||
|
position: fixed;
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow:
|
||||||
|
0 8rpx 24rpx rgba(82, 196, 26, 0.45),
|
||||||
|
0 16rpx 40rpx rgba(0, 0, 0, 0.15),
|
||||||
|
inset 0 4rpx 10rpx rgba(255, 255, 255, 0.35);
|
||||||
|
z-index: 999;
|
||||||
|
touch-action: none;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ball-shine {
|
||||||
|
position: absolute;
|
||||||
|
top: 20rpx;
|
||||||
|
left: 24rpx;
|
||||||
|
width: 36rpx;
|
||||||
|
height: 18rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(2rpx);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ball-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.15);
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
src/static/icon/video.mp3
Normal file
BIN
src/static/icon/video.mp3
Normal file
Binary file not shown.
Reference in New Issue
Block a user