提交数据
This commit is contained in:
@@ -77,7 +77,8 @@
|
|||||||
{
|
{
|
||||||
"path": "project",
|
"path": "project",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "包干"
|
"navigationBarTitleText": "包干",
|
||||||
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
<text class="section-title">选择项目</text>
|
<text class="section-title">选择项目</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 项目选择器 -->
|
<!-- 项目选择器 -->
|
||||||
<picker mode="selector" :range="projectOptions" range-key="name" :value="selectedProjectIndex" @change="handleProjectChange">
|
<picker mode="selector" :range="projectOptions" range-key="name" :value="selectedProjectIndex"
|
||||||
|
@change="handleProjectChange">
|
||||||
<view class="picker-wrapper">
|
<view class="picker-wrapper">
|
||||||
<text class="picker-text">{{ selectedProjectName }}</text>
|
<text class="picker-text">{{ selectedProjectName }}</text>
|
||||||
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
|
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
|
||||||
@@ -31,7 +32,9 @@
|
|||||||
</view>
|
</view>
|
||||||
<!-- 学生列表 -->
|
<!-- 学生列表 -->
|
||||||
<view class="student-list">
|
<view class="student-list">
|
||||||
<view v-for="student in projectStudents" :key="student.id" :class="['student-item', { 'selected': selectedStudentIds.includes(student.id) }]" @click="toggleStudent(student.id)">
|
<view v-for="student in projectStudents" :key="student.id"
|
||||||
|
:class="['student-item', { 'selected': selectedStudentIds.includes(student.id) }]"
|
||||||
|
@click="toggleStudent(student.id)">
|
||||||
<!-- 学生头像(使用首字母作为头像) -->
|
<!-- 学生头像(使用首字母作为头像) -->
|
||||||
<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>
|
||||||
@@ -75,9 +78,11 @@
|
|||||||
<text class="chart-desc">历史训练成绩变化曲线</text>
|
<text class="chart-desc">历史训练成绩变化曲线</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 折线图容器 -->
|
<!-- 折线图容器 -->
|
||||||
<view class="chart-box">
|
<scroll-view class="chart-scroll" scroll-x :show-scrollbar="false">
|
||||||
<qiun-data-charts type="line" :opts="lineOpts" :chartData="lineChartData" :ontouch="true" />
|
<view class="chart-box" :style="{ width: chartWidth + 'rpx' }">
|
||||||
</view>
|
<qiun-data-charts type="line" :opts="lineOpts" :chartData="lineChartData" :ontouch="true" />
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 图例说明 -->
|
<!-- 图例说明 -->
|
||||||
@@ -142,8 +147,8 @@
|
|||||||
* 定义学生的基本信息
|
* 定义学生的基本信息
|
||||||
*/
|
*/
|
||||||
interface Student {
|
interface Student {
|
||||||
id: string
|
id : string
|
||||||
name: string
|
name : string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -151,12 +156,12 @@
|
|||||||
* 定义学生某次训练的详细记录
|
* 定义学生某次训练的详细记录
|
||||||
*/
|
*/
|
||||||
interface StudentTrainingRecord {
|
interface StudentTrainingRecord {
|
||||||
studentId: string
|
studentId : string
|
||||||
studentName: string
|
studentName : string
|
||||||
time: number
|
time : number
|
||||||
recordDate: string
|
recordDate : string
|
||||||
recordFullDate: string
|
recordFullDate : string
|
||||||
round: number
|
round : number
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 响应式数据 - 学生相关 ====================
|
// ==================== 响应式数据 - 学生相关 ====================
|
||||||
@@ -176,6 +181,14 @@
|
|||||||
|
|
||||||
// ==================== 响应式数据 - 图表相关 ====================
|
// ==================== 响应式数据 - 图表相关 ====================
|
||||||
|
|
||||||
|
// 计算图表宽度(根据数据点数量动态调整)
|
||||||
|
const chartWidth = computed(() => {
|
||||||
|
const categoryCount = lineChartData.value.categories.length
|
||||||
|
if (categoryCount <= 6) return 700
|
||||||
|
// 每个数据点分配 100rpx,最小 700rpx
|
||||||
|
return Math.max(700, categoryCount * 100)
|
||||||
|
})
|
||||||
|
|
||||||
// 折线图配置选项
|
// 折线图配置选项
|
||||||
const lineOpts = ref({
|
const lineOpts = ref({
|
||||||
color: chartColors,
|
color: chartColors,
|
||||||
@@ -187,13 +200,14 @@
|
|||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
disableGrid: true,
|
disableGrid: true,
|
||||||
itemCount: 6,
|
rotateLabel: true,
|
||||||
rotateLabel: true
|
scrollShow: true,
|
||||||
|
scrollAlign: 'left'
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
data: [{
|
data: [{
|
||||||
min: 0,
|
min: 0,
|
||||||
format: (val: number) => val.toFixed(1) + 's'
|
format: (val : number) => val.toFixed(1) + 's'
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
extra: {
|
extra: {
|
||||||
@@ -226,7 +240,7 @@
|
|||||||
// ==================== 模拟数据 ====================
|
// ==================== 模拟数据 ====================
|
||||||
|
|
||||||
// 模拟的学生数据(按项目分组)
|
// 模拟的学生数据(按项目分组)
|
||||||
const mockProjectStudents: Record<string, Student[]> = {
|
const mockProjectStudents : Record<string, Student[]> = {
|
||||||
'1': [
|
'1': [
|
||||||
{ id: 's1', name: '张小明' },
|
{ id: 's1', name: '张小明' },
|
||||||
{ id: 's2', name: '李小红' },
|
{ id: 's2', name: '李小红' },
|
||||||
@@ -252,7 +266,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 模拟的训练记录数据(按项目分组)
|
// 模拟的训练记录数据(按项目分组)
|
||||||
const mockTrainingRecords: Record<string, StudentTrainingRecord[]> = {
|
const mockTrainingRecords : Record<string, StudentTrainingRecord[]> = {
|
||||||
'1': [
|
'1': [
|
||||||
{ studentId: 's1', studentName: '张小明', time: 28.5, recordDate: '03-12', recordFullDate: '2026-03-12', round: 1 },
|
{ studentId: 's1', studentName: '张小明', time: 28.5, recordDate: '03-12', recordFullDate: '2026-03-12', round: 1 },
|
||||||
{ studentId: 's1', studentName: '张小明', time: 27.2, recordDate: '03-14', recordFullDate: '2026-03-14', round: 1 },
|
{ studentId: 's1', studentName: '张小明', time: 27.2, recordDate: '03-14', recordFullDate: '2026-03-14', round: 1 },
|
||||||
@@ -319,7 +333,7 @@
|
|||||||
// TODO: 调用后端 API 获取项目列表
|
// TODO: 调用后端 API 获取项目列表
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleProjectChange = (e: any) => {
|
const handleProjectChange = (e : any) => {
|
||||||
const index = e.detail.value
|
const index = e.detail.value
|
||||||
selectedProjectIndex.value = index
|
selectedProjectIndex.value = index
|
||||||
const selectedProject = projectList.value[index]
|
const selectedProject = projectList.value[index]
|
||||||
@@ -330,7 +344,7 @@
|
|||||||
selectedStudentIds.value = []
|
selectedStudentIds.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadProjectStudents = (projectId: string) => {
|
const loadProjectStudents = (projectId : string) => {
|
||||||
if (mockProjectStudents[projectId]) {
|
if (mockProjectStudents[projectId]) {
|
||||||
projectStudents.value = mockProjectStudents[projectId]
|
projectStudents.value = mockProjectStudents[projectId]
|
||||||
} else {
|
} else {
|
||||||
@@ -338,7 +352,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleStudent = (studentId: string) => {
|
const toggleStudent = (studentId : string) => {
|
||||||
const index = selectedStudentIds.value.indexOf(studentId)
|
const index = selectedStudentIds.value.indexOf(studentId)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
selectedStudentIds.value.splice(index, 1)
|
selectedStudentIds.value.splice(index, 1)
|
||||||
@@ -351,7 +365,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStudentBestTime = (studentId: string): number => {
|
const getStudentBestTime = (studentId : string) : number => {
|
||||||
const records = mockTrainingRecords[selectedProjectId.value] || []
|
const records = mockTrainingRecords[selectedProjectId.value] || []
|
||||||
const studentRecords = records.filter(r => r.studentId === studentId)
|
const studentRecords = records.filter(r => r.studentId === studentId)
|
||||||
if (studentRecords.length === 0) return 0
|
if (studentRecords.length === 0) return 0
|
||||||
@@ -378,7 +392,7 @@
|
|||||||
return `${parts[1]}-${parts[2]}`
|
return `${parts[1]}-${parts[2]}`
|
||||||
})
|
})
|
||||||
|
|
||||||
const seriesData: any[] = []
|
const seriesData : any[] = []
|
||||||
|
|
||||||
selectedStudentIds.value.forEach((studentId, index) => {
|
selectedStudentIds.value.forEach((studentId, index) => {
|
||||||
const studentRecords = projectRecords.filter(r => r.studentId === studentId)
|
const studentRecords = projectRecords.filter(r => r.studentId === studentId)
|
||||||
@@ -430,249 +444,255 @@
|
|||||||
.subtitle {
|
.subtitle {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.section-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 30rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-count {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #52c41a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-select-section {
|
|
||||||
background-color: #fff;
|
|
||||||
margin: 0 20rpx 20rpx;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
padding: 28rpx;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
.picker-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 20rpx 24rpx;
|
|
||||||
background-color: #f8f8f8;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
border: 1rpx solid #e8e8e8;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
border-color: #52c41a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.picker-text {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.student-select-section {
|
|
||||||
background-color: #fff;
|
|
||||||
margin: 0 20rpx 20rpx;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
padding: 28rpx;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
.section-header {
|
.section-header {
|
||||||
margin-bottom: 24rpx;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-count {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.student-list {
|
.project-select-section {
|
||||||
display: grid;
|
background-color: #fff;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
margin: 0 20rpx 20rpx;
|
||||||
gap: 16rpx;
|
border-radius: 16rpx;
|
||||||
|
padding: 28rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
.student-item {
|
.picker-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20rpx 10rpx;
|
justify-content: space-between;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
background-color: #f8f8f8;
|
background-color: #f8f8f8;
|
||||||
border-radius: 12rpx;
|
border-radius: 12rpx;
|
||||||
border: 2rpx solid transparent;
|
border: 1rpx solid #e8e8e8;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.95);
|
background-color: #f0f0f0;
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background-color: #f0f9eb;
|
|
||||||
border-color: #52c41a;
|
border-color: #52c41a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.student-avatar {
|
.picker-text {
|
||||||
width: 60rpx;
|
font-size: 28rpx;
|
||||||
height: 60rpx;
|
|
||||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
|
|
||||||
|
|
||||||
.avatar-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.student-name {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #333;
|
color: #333;
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-icon {
|
|
||||||
position: absolute;
|
|
||||||
top: 8rpx;
|
|
||||||
right: 8rpx;
|
|
||||||
width: 28rpx;
|
|
||||||
height: 28rpx;
|
|
||||||
background-color: #52c41a;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
.student-select-section {
|
||||||
|
|
||||||
.chart-section {
|
|
||||||
margin: 0 20rpx;
|
|
||||||
|
|
||||||
.stats-card {
|
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
margin: 0 20rpx 20rpx;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
padding: 24rpx;
|
padding: 28rpx;
|
||||||
margin-bottom: 20rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
.stat-item {
|
.section-header {
|
||||||
text-align: center;
|
margin-bottom: 24rpx;
|
||||||
|
|
||||||
.stat-label {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-value {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #52c41a;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-divider {
|
.student-list {
|
||||||
width: 1rpx;
|
display: grid;
|
||||||
height: 50rpx;
|
grid-template-columns: repeat(4, 1fr);
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-card {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
padding: 24rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
.chart-header {
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
padding-left: 12rpx;
|
|
||||||
border-left: 6rpx solid #52c41a;
|
|
||||||
|
|
||||||
.chart-title {
|
|
||||||
font-size: 30rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-desc {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-box {
|
|
||||||
width: 100%;
|
|
||||||
height: 500rpx;
|
|
||||||
background-color: #fafafa;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend-section {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
padding: 24rpx;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
.legend-title {
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
|
|
||||||
.legend-item {
|
.student-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12rpx;
|
padding: 20rpx 10rpx;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
border: 2rpx solid transparent;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.legend-color {
|
&:active {
|
||||||
width: 24rpx;
|
transform: scale(0.95);
|
||||||
height: 24rpx;
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: #f0f9eb;
|
||||||
|
border-color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-avatar {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
flex-shrink: 0;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
|
||||||
|
|
||||||
|
.avatar-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-name {
|
.student-name {
|
||||||
font-size: 26rpx;
|
font-size: 24rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
font-weight: 600;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-desc {
|
.check-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 8rpx;
|
||||||
|
right: 8rpx;
|
||||||
|
width: 28rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
background-color: #52c41a;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.chart-section {
|
||||||
|
margin: 0 20rpx;
|
||||||
|
|
||||||
|
.stats-card {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-divider {
|
||||||
|
width: 1rpx;
|
||||||
|
height: 50rpx;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.chart-header {
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
padding-left: 12rpx;
|
||||||
|
border-left: 6rpx solid #52c41a;
|
||||||
|
|
||||||
|
.chart-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-desc {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart-scroll {
|
||||||
|
width: 100%;
|
||||||
|
height: 500rpx;
|
||||||
|
background-color: #fafafa;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.chart-box {
|
||||||
|
height: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-section {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.legend-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16rpx;
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
|
||||||
|
.legend-color {
|
||||||
|
width: 24rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-name {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
</style>
|
||||||
</style>
|
|
||||||
@@ -8,78 +8,81 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 选择器区域 -->
|
<!-- 项目选择区域 -->
|
||||||
<view class="selector-section">
|
<view class="select-section">
|
||||||
<view class="selector-item" @click="showProjectPicker = true">
|
<view class="select-label">
|
||||||
<text class="selector-label">项目名称</text>
|
<text class="label-text">选择项目</text>
|
||||||
<view class="selector-value">
|
|
||||||
<text class="value-text">{{ selectedProject }}</text>
|
|
||||||
<u-icon name="arrow-down" size="24" color="#999"></u-icon>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="selector-item" @click="showStudentPicker = true">
|
<picker mode="selector" :range="projectOptions" range-key="name" :value="selectedProjectIndex"
|
||||||
<text class="selector-label">选择学生</text>
|
@change="handleProjectChange">
|
||||||
<view class="selector-value">
|
<view class="picker-wrapper">
|
||||||
<text class="value-text">{{ selectedStudent }}</text>
|
<text class="picker-text">{{ selectedProjectName }}</text>
|
||||||
<u-icon name="arrow-down" size="24" color="#999"></u-icon>
|
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</picker>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 项目选择器 -->
|
|
||||||
<u-picker
|
|
||||||
:show="showProjectPicker"
|
|
||||||
:columns="[projectList]"
|
|
||||||
@confirm="handleProjectConfirm"
|
|
||||||
@cancel="showProjectPicker = false"
|
|
||||||
keyName="label"></u-picker>
|
|
||||||
|
|
||||||
<!-- 学生选择器 -->
|
|
||||||
<u-picker
|
|
||||||
:show="showStudentPicker"
|
|
||||||
:columns="[studentList]"
|
|
||||||
@confirm="handleStudentConfirm"
|
|
||||||
@cancel="showStudentPicker = false"
|
|
||||||
keyName="label"
|
|
||||||
multiple
|
|
||||||
:defaultIndex="defaultStudentIndex"></u-picker>
|
|
||||||
|
|
||||||
<!-- 日历组件 -->
|
<!-- 日历组件 -->
|
||||||
<view class="calendar-wrapper">
|
<view class="calendar-wrapper" v-if="selectedProjectId">
|
||||||
<uni-calendar
|
<uni-calendar :insert="true" :range='true' :lunar="false" :show-month="true" :selected="selectedDates"
|
||||||
:insert="true"
|
@monthSwitch="handleMonthSwitch" @change="handleDateChange">
|
||||||
:lunar="false"
|
|
||||||
:show-month="true"
|
|
||||||
:selected="selectedDates"
|
|
||||||
@monthSwitch="handleMonthSwitch"
|
|
||||||
@change="handleDateChange">
|
|
||||||
</uni-calendar>
|
</uni-calendar>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 选中日期数据统计 -->
|
<!-- 学生选择区域 -->
|
||||||
<view class="date-summary" v-if="selectedDate">
|
<view class="select-section" v-if="selectedProjectId && selectedDate">
|
||||||
<view class="summary-item">
|
<view class="select-label">
|
||||||
<text class="summary-label">选中日期</text>
|
<text class="label-text">选择学生</text>
|
||||||
<text class="summary-value">{{ selectedDate }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="summary-divider"></view>
|
<view class="picker-wrapper" @click="showStudentPicker = true">
|
||||||
<view class="summary-item">
|
<text class="picker-text">{{ selectedStudentDisplay }}</text>
|
||||||
<text class="summary-label">训练人数</text>
|
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
|
||||||
<text class="summary-value">{{ tableData.length }}人</text>
|
|
||||||
</view>
|
|
||||||
<view class="summary-divider"></view>
|
|
||||||
<view class="summary-item">
|
|
||||||
<text class="summary-label">完成率平均</text>
|
|
||||||
<text class="summary-value">{{ averageCompletion }}%</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 学生多选选择器 -->
|
||||||
|
<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.id" 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="14"
|
||||||
|
color="#fff"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="item-avatar">
|
||||||
|
<view class="avatar-circle"
|
||||||
|
:class="{ 'male': student.gender === '男', 'female': student.gender === '女' }">
|
||||||
|
<text class="avatar-text">{{ student.name.charAt(0) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="item-info">
|
||||||
|
<text class="item-name">{{ student.name }}</text>
|
||||||
|
<view class="item-meta">
|
||||||
|
<text class="gender-badge"
|
||||||
|
:class="{ 'male': student.gender === '男', 'female': student.gender === '女' }">
|
||||||
|
{{ student.gender }}
|
||||||
|
</text>
|
||||||
|
<text class="age-text">{{ student.age }}岁</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 数据表格 -->
|
<!-- 数据表格 -->
|
||||||
<view class="table-section" v-if="selectedDate">
|
<view class="table-section" v-if="selectedDate && selectedStudentIndexes.length > 0">
|
||||||
<sl-table
|
<sl-table :columns="columns" :tableData="tableData" @cell-click="handleCellClick">
|
||||||
:columns="columns"
|
|
||||||
:tableData="tableData"
|
|
||||||
@cell-click="handleCellClick">
|
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<view class="empty-container">
|
<view class="empty-container">
|
||||||
<view class="empty-icon">
|
<view class="empty-icon">
|
||||||
@@ -91,7 +94,7 @@
|
|||||||
</sl-table>
|
</sl-table>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -100,15 +103,43 @@
|
|||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
// 当前年月
|
interface Project {
|
||||||
|
id : string
|
||||||
|
name : string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Student {
|
||||||
|
id : string
|
||||||
|
name : string
|
||||||
|
gender : string
|
||||||
|
age : number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TableDataItem {
|
||||||
|
date : string
|
||||||
|
name : string
|
||||||
|
projectName : string
|
||||||
|
plan : string
|
||||||
|
completion : number
|
||||||
|
detail : string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StudentRecord {
|
||||||
|
student : Student
|
||||||
|
date : string
|
||||||
|
segments : TableDataItem[]
|
||||||
|
}
|
||||||
|
|
||||||
const currentYear = ref(new Date().getFullYear())
|
const currentYear = ref(new Date().getFullYear())
|
||||||
const currentMonth = ref(new Date().getMonth() + 1)
|
const currentMonth = ref(new Date().getMonth() + 1)
|
||||||
|
|
||||||
// 选中的日期
|
|
||||||
const selectedDate = ref('')
|
const selectedDate = ref('')
|
||||||
|
|
||||||
// 表格列定义
|
|
||||||
const columns = ref([
|
const columns = ref([
|
||||||
|
{
|
||||||
|
label: '日期',
|
||||||
|
prop: 'date',
|
||||||
|
width: '100px'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '姓名',
|
label: '姓名',
|
||||||
prop: 'name',
|
prop: 'name',
|
||||||
@@ -136,211 +167,218 @@
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
// 表格数据
|
|
||||||
interface TableDataItem {
|
|
||||||
name: string
|
|
||||||
projectName: string
|
|
||||||
plan: string
|
|
||||||
completion: number
|
|
||||||
detail: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableData = ref<TableDataItem[]>([])
|
const tableData = ref<TableDataItem[]>([])
|
||||||
|
|
||||||
// 项目列表
|
const projectList = ref<Project[]>([
|
||||||
const projectList = ref([
|
{ id: '1', name: '100米自由泳' },
|
||||||
{ label: '全部项目', value: 'all' },
|
{ id: '2', name: '200米自由泳' },
|
||||||
{ label: '100米自由泳', value: '100m-free' },
|
{ id: '3', name: '400米自由泳' },
|
||||||
{ label: '200米自由泳', value: '200m-free' },
|
{ id: '4', name: '100米蛙泳' }
|
||||||
{ label: '400米自由泳', value: '400m-free' },
|
|
||||||
{ label: '100米蛙泳', value: '100m-breast' }
|
|
||||||
])
|
])
|
||||||
|
|
||||||
// 学生列表
|
const projectOptions = ref(projectList.value)
|
||||||
const studentList = ref([
|
|
||||||
{ label: '全部学生', value: 'all' },
|
|
||||||
{ label: '张小明', value: 'zhang-xiaoming' },
|
|
||||||
{ label: '李小红', value: 'li-xiaohong' },
|
|
||||||
{ label: '王小明', value: 'wang-xiaoming' },
|
|
||||||
{ label: '赵小芳', value: 'zhao-xiaofang' },
|
|
||||||
{ label: '陈小刚', value: 'chen-xiaogang' }
|
|
||||||
])
|
|
||||||
|
|
||||||
// 选中的项目和学生
|
const selectedProjectIndex = ref(-1)
|
||||||
const selectedProject = ref('全部项目')
|
const selectedProjectId = ref('')
|
||||||
const selectedStudent = ref('全部学生')
|
|
||||||
const selectedProjectValue = ref('all')
|
|
||||||
const selectedStudentValues = ref<string[]>([])
|
|
||||||
|
|
||||||
// 选择器显示状态
|
const selectedProjectName = computed(() => {
|
||||||
const showProjectPicker = ref(false)
|
if (selectedProjectIndex.value === -1) {
|
||||||
const showStudentPicker = ref(false)
|
return '请选择项目'
|
||||||
const defaultStudentIndex = ref<number[]>([0])
|
}
|
||||||
|
return projectList.value[selectedProjectIndex.value]?.name || '请选择项目'
|
||||||
// 日历打点数据
|
|
||||||
// const selectedDates = ref([
|
|
||||||
// { date: '2026-03-15', info: '训练' },
|
|
||||||
// { date: '2026-03-18', info: '训练' },
|
|
||||||
// { date: '2026-03-20', info: '训练' },
|
|
||||||
// { date: '2026-03-22', info: '训练' },
|
|
||||||
// { date: '2026-03-25', info: '训练' }
|
|
||||||
// ])
|
|
||||||
|
|
||||||
// 平均完成率
|
|
||||||
const averageCompletion = computed(() => {
|
|
||||||
if (tableData.value.length === 0) return 0
|
|
||||||
const total = tableData.value.reduce((sum, item) => sum + item.completion, 0)
|
|
||||||
return Math.round(total / tableData.value.length)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 完整模拟数据
|
|
||||||
const mockData: Record<string, TableDataItem[]> = {
|
|
||||||
'2026-03-28': [
|
|
||||||
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 95, detail: '1900/2000' },
|
|
||||||
{ name: '张小明', projectName: '200米自由泳', plan: '1500米', completion: 88, detail: '1320/1500' },
|
|
||||||
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 92, detail: '1840/2000' },
|
|
||||||
{ name: '李小红', projectName: '400米自由泳', plan: '1200米', completion: 85, detail: '1020/1200' },
|
|
||||||
{ name: '王小明', projectName: '100米自由泳', plan: '2000米', completion: 90, detail: '1800/2000' },
|
|
||||||
{ name: '赵小芳', projectName: '100米蛙泳', plan: '1800米', completion: 88, detail: '1584/1800' },
|
|
||||||
{ name: '陈小刚', projectName: '200米自由泳', plan: '1500米', completion: 95, detail: '1425/1500' }
|
|
||||||
],
|
|
||||||
'2026-03-29': [
|
|
||||||
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 90, detail: '1800/2000' },
|
|
||||||
{ name: '张小明', projectName: '100米蛙泳', plan: '1800米', completion: 85, detail: '1530/1800' },
|
|
||||||
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 95, detail: '1900/2000' },
|
|
||||||
{ name: '李小红', projectName: '200米自由泳', plan: '1500米', completion: 92, detail: '1380/1500' },
|
|
||||||
{ name: '王小明', projectName: '200米自由泳', plan: '1500米', completion: 98, detail: '1470/1500' },
|
|
||||||
{ name: '赵小芳', projectName: '100米自由泳', plan: '2000米', completion: 82, detail: '1640/2000' },
|
|
||||||
{ name: '陈小刚', projectName: '100米自由泳', plan: '2000米', completion: 88, detail: '1760/2000' }
|
|
||||||
],
|
|
||||||
'2026-03-30': [
|
|
||||||
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 92, detail: '1840/2000' },
|
|
||||||
{ name: '张小明', projectName: '400米自由泳', plan: '1200米', completion: 90, detail: '1080/1200' },
|
|
||||||
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 88, detail: '1760/2000' },
|
|
||||||
{ name: '李小红', projectName: '100米蛙泳', plan: '1800米', completion: 95, detail: '1710/1800' },
|
|
||||||
{ name: '王小明', projectName: '100米自由泳', plan: '2000米', completion: 95, detail: '1900/2000' },
|
|
||||||
{ name: '赵小芳', projectName: '200米自由泳', plan: '1500米', completion: 85, detail: '1275/1500' },
|
|
||||||
{ name: '陈小刚', projectName: '400米自由泳', plan: '1200米', completion: 92, detail: '1104/1200' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 日历打点数据
|
|
||||||
const selectedDates = ref([
|
const selectedDates = ref([
|
||||||
{ date: '2026-03-28', info: '训练' },
|
{ date: '2026-03-28', info: '训练' },
|
||||||
{ date: '2026-03-29', info: '训练' },
|
{ date: '2026-03-29', info: '训练' },
|
||||||
{ date: '2026-03-30', info: '训练' }
|
{ date: '2026-03-30', info: '训练' }
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const studentList = ref<Student[]>([])
|
||||||
|
|
||||||
|
const selectedStudentIndexes = ref<number[]>([])
|
||||||
|
const selectedStudentIds = ref<string[]>([])
|
||||||
|
const showStudentPicker = ref(false)
|
||||||
|
|
||||||
|
const selectedStudentDisplay = computed(() => {
|
||||||
|
if (selectedStudentIndexes.value.length === 0) {
|
||||||
|
return '请选择学生'
|
||||||
|
} else {
|
||||||
|
const names = selectedStudentIndexes.value.map(index => studentList.value[index].name)
|
||||||
|
return names.length > 2 ? `${names.slice(0, 2).join(', ')}等${names.length}人` : names.join(', ')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const averageCompletion = computed(() => {
|
||||||
|
if (tableData.value.length === 0) return 0
|
||||||
|
const total = tableData.value.reduce((sum, item) => sum + item.completion, 0)
|
||||||
|
return Math.round(total / tableData.value.length)
|
||||||
|
})
|
||||||
|
|
||||||
|
const mockData : Record<string, Record<string, TableDataItem[]>> = {
|
||||||
|
'1': {
|
||||||
|
'2026-03-28': [
|
||||||
|
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 95, detail: '1900/2000' },
|
||||||
|
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 92, detail: '1840/2000' },
|
||||||
|
{ name: '王小明', projectName: '100米自由泳', plan: '2000米', completion: 90, detail: '1800/2000' }
|
||||||
|
],
|
||||||
|
'2026-03-29': [
|
||||||
|
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 90, detail: '1800/2000' },
|
||||||
|
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 95, detail: '1900/2000' },
|
||||||
|
{ name: '王小明', projectName: '100米自由泳', plan: '2000米', completion: 88, detail: '1760/2000' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'2': {
|
||||||
|
'2026-03-28': [
|
||||||
|
{ name: '张小明', projectName: '200米自由泳', plan: '1500米', completion: 88, detail: '1320/1500' },
|
||||||
|
{ name: '李小红', projectName: '200米自由泳', plan: '1500米', completion: 92, detail: '1380/1500' },
|
||||||
|
{ name: '王小明', projectName: '200米自由泳', plan: '1500米', completion: 85, detail: '1275/1500' }
|
||||||
|
],
|
||||||
|
'2026-03-29': [
|
||||||
|
{ name: '张小明', projectName: '200米自由泳', plan: '1500米', completion: 90, detail: '1350/1500' },
|
||||||
|
{ name: '李小红', projectName: '200米自由泳', plan: '1500米', completion: 88, detail: '1320/1500' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'3': {
|
||||||
|
'2026-03-28': [
|
||||||
|
{ name: '张小明', projectName: '400米自由泳', plan: '1200米', completion: 85, detail: '1020/1200' },
|
||||||
|
{ name: '李小红', projectName: '400米自由泳', plan: '1200米', completion: 90, detail: '1080/1200' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'4': {
|
||||||
|
'2026-03-28': [
|
||||||
|
{ name: '赵小芳', projectName: '100米蛙泳', plan: '1800米', completion: 88, detail: '1584/1800' },
|
||||||
|
{ name: '陈小' + '刚', projectName: '100米蛙泳', plan: '1800米', completion: 95, detail: '1710/1800' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockStudents : Record<string, Student[]> = {
|
||||||
|
'1': [
|
||||||
|
{ id: 's1', name: '张小明', gender: '男', age: 12 },
|
||||||
|
{ id: 's2', name: '李小红', gender: '女', age: 11 },
|
||||||
|
{ id: 's3', name: '王小明', gender: '男', age: 13 }
|
||||||
|
|
||||||
|
],
|
||||||
|
'2': [
|
||||||
|
{ id: 's1', name: '张小明', gender: '男', age: 12 },
|
||||||
|
{ id: 's2', name: '李小红', gender: '女', age: 11 },
|
||||||
|
{ id: 's3', name: '王小明', gender: '男', age: 13 }
|
||||||
|
|
||||||
|
],
|
||||||
|
'3': [
|
||||||
|
{ id: 's1', name: '张小明', gender: '男', age: 12 },
|
||||||
|
{ id: 's2', name: '李小红', gender: '女', age: 11 }
|
||||||
|
],
|
||||||
|
'4': [
|
||||||
|
{ id: 's4', name: '赵小芳', gender: '女', age: 10 },
|
||||||
|
{ id: 's5', name: '陈小' + '刚', gender: '男', age: 14 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
loadData()
|
loadData()
|
||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
// 页面显示时刷新数据
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 加载数据
|
|
||||||
const loadData = () => {
|
const loadData = () => {
|
||||||
// TODO: 调用API获取数据
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 月份单元格点击处理
|
const handleCellClick = (event : any) => {
|
||||||
const handleCellClick = (event: any) => {
|
|
||||||
console.log('单元格点击事件:', event)
|
console.log('单元格点击事件:', event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 月份切换处理
|
const handleProjectChange = (e : any) => {
|
||||||
const handleMonthSwitch = (e: any) => {
|
const index = e.detail.value
|
||||||
|
selectedProjectIndex.value = index
|
||||||
|
const selectedProject = projectList.value[index]
|
||||||
|
|
||||||
|
if (selectedProject) {
|
||||||
|
selectedProjectId.value = selectedProject.id
|
||||||
|
selectedDate.value = ''
|
||||||
|
selectedStudentIndexes.value = []
|
||||||
|
selectedStudentIds.value = []
|
||||||
|
tableData.value = []
|
||||||
|
loadProjectStudents(selectedProject.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMonthSwitch = (e : any) => {
|
||||||
currentYear.value = e.year
|
currentYear.value = e.year
|
||||||
currentMonth.value = e.month
|
currentMonth.value = e.month
|
||||||
loadData()
|
loadData()
|
||||||
// 切换月份后清空选中日期
|
|
||||||
selectedDate.value = ''
|
selectedDate.value = ''
|
||||||
|
selectedStudentIndexes.value = []
|
||||||
|
selectedStudentIds.value = []
|
||||||
tableData.value = []
|
tableData.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
// 日期选择处理
|
const handleDateChange = (e : any) => {
|
||||||
const handleDateChange = (e: any) => {
|
console.log(e);
|
||||||
const date = e.fulldate
|
const date = e.fulldate
|
||||||
selectedDate.value = date
|
selectedDate.value = date
|
||||||
filterAndLoadData()
|
selectedStudentIndexes.value = []
|
||||||
|
selectedStudentIds.value = []
|
||||||
|
tableData.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
// 项目选择确认
|
const loadProjectStudents = (projectId : string) => {
|
||||||
const handleProjectConfirm = (e: any) => {
|
const students = mockStudents[projectId] || []
|
||||||
const item = e.value[0]
|
studentList.value = students
|
||||||
selectedProject.value = item.label
|
console.log(`项目 ${projectId} 的学生列表加载完成,共 ${students.length} 位学生`)
|
||||||
selectedProjectValue.value = item.value
|
|
||||||
showProjectPicker.value = false
|
|
||||||
filterAndLoadData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 学生选择确认
|
const selectAllStudents = () => {
|
||||||
const handleStudentConfirm = (e: any) => {
|
selectedStudentIndexes.value = studentList.value.map((_, index) => index)
|
||||||
const items = e.value
|
}
|
||||||
selectedStudentValues.value = items.map((item: any) => item.value)
|
|
||||||
|
|
||||||
if (selectedStudentValues.value.length === 0) {
|
const handleStudentChange = (e : any) => {
|
||||||
selectedStudent.value = '未选择'
|
console.log('学生选择变化:', e)
|
||||||
} else if (selectedStudentValues.value.includes('all')) {
|
}
|
||||||
selectedStudent.value = '全部学生'
|
|
||||||
|
const toggleStudentSelection = (index : number) => {
|
||||||
|
const idx = selectedStudentIndexes.value.indexOf(index)
|
||||||
|
if (idx > -1) {
|
||||||
|
selectedStudentIndexes.value.splice(idx, 1)
|
||||||
} else {
|
} else {
|
||||||
const selectedNames = items.map((item: any) => item.label)
|
selectedStudentIndexes.value.push(index)
|
||||||
selectedStudent.value = selectedNames.length > 2
|
|
||||||
? `${selectedNames.slice(0, 2).join(', ')}等${selectedNames.length}人`
|
|
||||||
: selectedNames.join(', ')
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defaultStudentIndex.value = e.indexs
|
const confirmStudentSelection = () => {
|
||||||
|
selectedStudentIds.value = selectedStudentIndexes.value.map(index => studentList.value[index].id)
|
||||||
showStudentPicker.value = false
|
showStudentPicker.value = false
|
||||||
filterAndLoadData()
|
filterAndLoadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 过滤并加载数据
|
const closeStudentPicker = () => {
|
||||||
|
showStudentPicker.value = false
|
||||||
|
}
|
||||||
|
|
||||||
const filterAndLoadData = () => {
|
const filterAndLoadData = () => {
|
||||||
if (!selectedDate.value) {
|
if (!selectedDate.value || selectedStudentIndexes.value.length === 0) {
|
||||||
tableData.value = []
|
tableData.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const dateData = mockData[selectedDate.value]
|
const projectData = mockData[selectedProjectId.value]
|
||||||
|
if (!projectData) {
|
||||||
|
tableData.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateData = projectData[selectedDate.value]
|
||||||
if (!dateData) {
|
if (!dateData) {
|
||||||
tableData.value = []
|
tableData.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let filteredData = [...dateData]
|
const selectedStudentNames = selectedStudentIndexes.value.map(index => studentList.value[index].name)
|
||||||
|
tableData.value = dateData.filter(item => selectedStudentNames.includes(item.name))
|
||||||
// 按项目过滤
|
|
||||||
if (selectedProjectValue.value !== 'all') {
|
|
||||||
const projectMap: Record<string, string> = {
|
|
||||||
'100m-free': '100米自由泳',
|
|
||||||
'200m-free': '200米自由泳',
|
|
||||||
'400m-free': '400米自由泳',
|
|
||||||
'100m-breast': '100米蛙泳'
|
|
||||||
}
|
|
||||||
const targetProject = projectMap[selectedProjectValue.value]
|
|
||||||
if (targetProject) {
|
|
||||||
filteredData = filteredData.filter(item => item.projectName === targetProject)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按学生过滤
|
|
||||||
if (selectedStudentValue.value !== 'all') {
|
|
||||||
const studentMap: Record<string, string> = {
|
|
||||||
'zhang-xiaoming': '张小明',
|
|
||||||
'li-xiaohong': '李小红',
|
|
||||||
'wang-xiaoming': '王小明',
|
|
||||||
'zhao-xiaofang': '赵小芳',
|
|
||||||
'chen-xiaogang': '陈小刚'
|
|
||||||
}
|
|
||||||
const targetStudent = studentMap[selectedStudentValue.value]
|
|
||||||
if (targetStudent) {
|
|
||||||
filteredData = filteredData.filter(item => item.name === targetStudent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tableData.value = filteredData
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -354,7 +392,6 @@
|
|||||||
padding-bottom: 40rpx;
|
padding-bottom: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 页面标题区域 */
|
|
||||||
.header-section {
|
.header-section {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
padding: 32rpx 28rpx 24rpx;
|
padding: 32rpx 28rpx 24rpx;
|
||||||
@@ -376,59 +413,272 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 选择器区域 */
|
.select-section {
|
||||||
.selector-section {
|
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
margin: 0 20rpx 20rpx;
|
margin: 0 20rpx 20rpx;
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
padding: 24rpx;
|
padding: 28rpx;
|
||||||
display: flex;
|
|
||||||
gap: 20rpx;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.selector-item {
|
.select-label {
|
||||||
flex: 1;
|
margin-bottom: 16rpx;
|
||||||
|
|
||||||
|
.label-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 20rpx 24rpx;
|
padding: 20rpx 24rpx;
|
||||||
background-color: #f8f8f8;
|
background-color: #f8f8f8;
|
||||||
border-radius: 12rpx;
|
border-radius: 12rpx;
|
||||||
|
border: 1rpx solid #e8e8e8;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
.selector-label {
|
&:active {
|
||||||
font-size: 28rpx;
|
background-color: #f0f0f0;
|
||||||
color: #666;
|
border-color: #faad14;
|
||||||
margin-right: 16rpx;
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selector-value {
|
.picker-text {
|
||||||
flex: 1;
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-wrapper {
|
||||||
|
background-color: #fff;
|
||||||
|
margin: 0 20rpx 20rpx;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-picker-modal {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 70vh;
|
||||||
|
background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
|
||||||
|
border-radius: 32rpx 32rpx 0 0;
|
||||||
|
z-index: 999;
|
||||||
|
animation: slideUp 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
box-shadow: 0 -8rpx 40rpx rgba(0, 0, 0, 0.12);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.picker-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);
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
background: linear-gradient(135deg, #faad14 0%, #ffc53d 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
gap: 16rpx;
|
||||||
|
|
||||||
.value-text {
|
.action-btn {
|
||||||
font-size: 28rpx;
|
font-size: 26rpx;
|
||||||
color: #333;
|
color: #666;
|
||||||
font-weight: 500;
|
padding: 14rpx 28rpx;
|
||||||
margin-right: 8rpx;
|
border-radius: 24rpx;
|
||||||
|
background: linear-gradient(135deg, #f5f5f5 0%, #f0f0f0 100%);
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(135deg, #faad14 0%, #ffc53d 50%, #d48806 100%);
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(250, 173, 20, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.92);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0 20rpx 40rpx;
|
||||||
|
|
||||||
|
.student-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
background: linear-gradient(135deg, #fff 0%, #fafbfc 100%);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
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;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-checkbox {
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.checkbox-inner {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
border: 2rpx solid #d9d9d9;
|
||||||
|
background-color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
border-color: #faad14;
|
||||||
|
background: linear-gradient(135deg, #faad14 0%, #ffc53d 100%);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(250, 173, 20, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.avatar-circle {
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 18rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&.male {
|
||||||
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 50%, #096dd9 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.female {
|
||||||
|
background: linear-gradient(135deg, #fa8c16 0%, #ffa940 50%, #d46b08 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 8rpx;
|
||||||
|
right: 8rpx;
|
||||||
|
width: 12rpx;
|
||||||
|
height: 12rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-text {
|
||||||
|
font-size: 34rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
|
||||||
|
.gender-badge {
|
||||||
|
font-size: 22rpx;
|
||||||
|
padding: 6rpx 14rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&.male {
|
||||||
|
color: #1890ff;
|
||||||
|
background: linear-gradient(135deg, rgba(24, 144, 255, 0.1) 0%, rgba(64, 169, 255, 0.05) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.female {
|
||||||
|
color: #fa8c16;
|
||||||
|
background: linear-gradient(135deg, rgba(250, 140, 22, 0.1) 0%, rgba(255, 169, 64, 0.05) 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 日历组件包装 */
|
.modal-overlay {
|
||||||
.calendar-wrapper {
|
position: fixed;
|
||||||
background-color: #fff;
|
top: 0;
|
||||||
margin-bottom: 20rpx;
|
left: 0;
|
||||||
padding: 20rpx 0;
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(180deg, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.4) 100%);
|
||||||
|
backdrop-filter: blur(10rpx);
|
||||||
|
z-index: 998;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 日期数据统计 */
|
|
||||||
.date-summary {
|
.date-summary {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
margin: 0 20rpx 20rpx;
|
margin: 0 20rpx 20rpx;
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
padding: 24rpx;
|
padding: 24rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -459,16 +709,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 表格区域 */
|
|
||||||
.table-section {
|
.table-section {
|
||||||
margin: 0 20rpx;
|
margin: 0 20rpx;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 空状态容器 */
|
|
||||||
.empty-container {
|
.empty-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -486,11 +734,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 提示状态 */
|
|
||||||
.hint-state {
|
.hint-state {
|
||||||
margin: 0 20rpx;
|
margin: 0 20rpx;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
padding: 80rpx 40rpx;
|
padding: 80rpx 40rpx;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
@@ -505,7 +752,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* sl-table 样式覆盖 */
|
|
||||||
::v-deep .sl-table {
|
::v-deep .sl-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -533,4 +779,24 @@
|
|||||||
::v-deep .sl-table__body__row:nth-child(odd) {
|
::v-deep .sl-table__body__row:nth-child(odd) {
|
||||||
background-color: #fff !important;
|
background-color: #fff !important;
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@
|
|||||||
<!-- 创建项目模块列表 -->
|
<!-- 创建项目模块列表 -->
|
||||||
<view class="create-modules">
|
<view class="create-modules">
|
||||||
<!-- 计时项目模块 -->
|
<!-- 计时项目模块 -->
|
||||||
<view @click="showTimingProjectModal" class="create-card timing-card">
|
<view @click="showTimingProjectModal(1)" class="create-card timing-card">
|
||||||
<view class="card-icon">
|
<view class="card-icon">
|
||||||
<view class="icon-circle timing-icon">
|
<view class="icon-circle timing-icon">
|
||||||
<u-icon name="clock" size="40" color="#fff"></u-icon>
|
<u-icon name="clock" size="40" color="#fff"></u-icon>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 包干项目模块 -->
|
<!-- 包干项目模块 -->
|
||||||
<view @click="createPackageProject" class="create-card package-card">
|
<view @click="showTimingProjectModal(2)" 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,7 +74,7 @@
|
|||||||
|
|
||||||
<view class="modal-content">
|
<view class="modal-content">
|
||||||
<!-- 新增按钮 -->
|
<!-- 新增按钮 -->
|
||||||
<view class="add-btn" @click="createTimingProject">
|
<view v-if="currentIndex!==2" class="add-btn" @click="createTimingProject">
|
||||||
<u-icon name="plus" size="18" color="#1890ff"></u-icon>
|
<u-icon name="plus" size="18" color="#1890ff"></u-icon>
|
||||||
<text class="add-btn-text">新增项目</text>
|
<text class="add-btn-text">新增项目</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
<text class="list-title" v-if="projects.length > 0">项目列表</text>
|
<text class="list-title" v-if="projects.length > 0">项目列表</text>
|
||||||
<view v-if="projects.length > 0" class="list-container">
|
<view v-if="projects.length > 0" class="list-container">
|
||||||
<view v-for="project in projects" :key="project.id" class="project-item"
|
<view v-for="project in projects" :key="project.id" class="project-item"
|
||||||
@click="Service.GoPage('/pages/userFunc/swiming')">
|
@click=" goPageFunc() ">
|
||||||
<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">{{ project.name.charAt(0) }}</text>
|
||||||
@@ -128,7 +128,9 @@
|
|||||||
age : string
|
age : string
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currentIndex=ref(0)
|
||||||
|
|
||||||
// 模拟项目数据
|
// 模拟项目数据
|
||||||
const projects = ref<Project[]>([
|
const projects = ref<Project[]>([
|
||||||
{
|
{
|
||||||
@@ -175,10 +177,21 @@
|
|||||||
const showTimingModal = ref(false)
|
const showTimingModal = ref(false)
|
||||||
const showDetailModal = ref(false)
|
const showDetailModal = ref(false)
|
||||||
const currentProject = ref<Project | null>(null)
|
const currentProject = ref<Project | null>(null)
|
||||||
|
|
||||||
|
|
||||||
|
// 生命周期
|
||||||
|
onMounted(() => {
|
||||||
|
// TODO: 实际应从接口获取项目列表
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 清理
|
||||||
|
})
|
||||||
|
|
||||||
// 显示计时项目列表弹窗
|
// 显示计时项目列表弹窗
|
||||||
const showTimingProjectModal = () => {
|
const showTimingProjectModal = (index:any) => {
|
||||||
showTimingModal.value = true
|
showTimingModal.value = true
|
||||||
|
currentIndex.value=index
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭计时项目列表弹窗
|
// 关闭计时项目列表弹窗
|
||||||
@@ -200,7 +213,7 @@
|
|||||||
const createSegmentProject = () => {
|
const createSegmentProject = () => {
|
||||||
Service.GoPage('/pages/userFunc/setCourse')
|
Service.GoPage('/pages/userFunc/setCourse')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示项目详情
|
// 显示项目详情
|
||||||
const showProjectDetail = (project : Project) => {
|
const showProjectDetail = (project : Project) => {
|
||||||
currentProject.value = project
|
currentProject.value = project
|
||||||
@@ -213,15 +226,17 @@
|
|||||||
showDetailModal.value = false
|
showDetailModal.value = false
|
||||||
currentProject.value = null
|
currentProject.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生命周期
|
|
||||||
onMounted(() => {
|
const goPageFunc=()=>{
|
||||||
// TODO: 实际应从接口获取项目列表
|
if(currentIndex.value==1){
|
||||||
})
|
Service.GoPage('/pages/userFunc/swiming')
|
||||||
|
}else{
|
||||||
onUnmounted(() => {
|
Service.GoPage('/pages/userFunc/project')
|
||||||
// 清理
|
}
|
||||||
})
|
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,10 @@
|
|||||||
{{ student.gender }}
|
{{ student.gender }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="detail-row">
|
||||||
|
<text class="detail-label">出生日期:</text>
|
||||||
|
<text class="detail-value">{{ student.birthDate }}</text>
|
||||||
|
</view>
|
||||||
<view class="detail-row">
|
<view class="detail-row">
|
||||||
<text class="detail-label">年龄:</text>
|
<text class="detail-label">年龄:</text>
|
||||||
<text class="detail-value">{{ student.age }}岁</text>
|
<text class="detail-value">{{ student.age }}岁</text>
|
||||||
@@ -84,18 +88,40 @@
|
|||||||
<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 = '男'">
|
||||||
<u-icon name="man" size="18" :color="formData.gender === '男' ? '#1890ff' : '#999'"></u-icon>
|
<view class="gender-icon-box">
|
||||||
|
<u-icon name="man" size="20" :color="formData.gender === '男' ? '#fff' : '#1890ff'"></u-icon>
|
||||||
|
</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 = '女'">
|
||||||
<u-icon name="woman" size="18" :color="formData.gender === '女' ? '#fa8c16' : '#999'"></u-icon>
|
<view class="gender-icon-box">
|
||||||
|
<u-icon name="woman" size="20" :color="formData.gender === '女' ? '#fff' : '#fa8c16'"></u-icon>
|
||||||
|
</view>
|
||||||
<text>女</text>
|
<text>女</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="form-group">
|
<view class="form-group">
|
||||||
<text class="form-label">年龄</text>
|
<text class="form-label">出生日期</text>
|
||||||
<input class="form-input" v-model="formData.age" placeholder="请输入年龄" type="number" />
|
<view class="date-picker-wrapper">
|
||||||
|
<picker mode="date" :value="formData.birthDate" @change="onBirthDateChange">
|
||||||
|
<view class="form-input date-picker" :class="{ placeholder: !formData.birthDate }">
|
||||||
|
<u-icon name="calendar" size="18" color="#1890ff"></u-icon>
|
||||||
|
<text>{{ formData.birthDate || '请选择出生日期' }}</text>
|
||||||
|
<u-icon name="arrow-down" size="14" color="#999"></u-icon>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
</view>
|
||||||
|
<view v-if="formData.birthDate" class="age-display">
|
||||||
|
<view class="age-icon">
|
||||||
|
<u-icon name="account" size="24" color="#fff"></u-icon>
|
||||||
|
</view>
|
||||||
|
<view class="age-info">
|
||||||
|
<text class="age-label">年龄</text>
|
||||||
|
<text class="age-value">{{ calculateAge(formData.birthDate) }}</text>
|
||||||
|
<text class="age-unit">岁</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="form-group">
|
<view class="form-group">
|
||||||
<text class="form-label">学校(选填)</text>
|
<text class="form-label">学校(选填)</text>
|
||||||
@@ -128,16 +154,17 @@
|
|||||||
name : string
|
name : string
|
||||||
gender : string
|
gender : string
|
||||||
age : string
|
age : string
|
||||||
|
birthDate : string
|
||||||
school : string
|
school : string
|
||||||
address : string
|
address : string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 学员列表
|
// 学员列表
|
||||||
const students = ref<Student[]>([
|
const students = ref<Student[]>([
|
||||||
{ id: '001', name: '张三', gender: '男', age: '12', school: '第一小学', address: '北京市朝阳区' },
|
{ id: '001', name: '张三', gender: '男', age: '12', birthDate: '2012-05-15', school: '第一小学', address: '北京市朝阳区' },
|
||||||
{ id: '002', name: '李四', gender: '女', age: '13', school: '', address: '' },
|
{ id: '002', name: '李四', gender: '女', age: '13', birthDate: '2011-08-20', school: '', address: '' },
|
||||||
{ id: '003', name: '王五', gender: '男', age: '11', school: '第二小学', address: '' },
|
{ id: '003', name: '王五', gender: '男', age: '11', birthDate: '2013-03-10', school: '第二小学', address: '' },
|
||||||
{ id: '004', name: '赵六', gender: '女', age: '12', school: '', address: '上海市浦东新区' }
|
{ id: '004', name: '赵六', gender: '女', age: '12', birthDate: '2012-11-25', school: '', address: '上海市浦东新区' }
|
||||||
])
|
])
|
||||||
|
|
||||||
// 弹窗状态
|
// 弹窗状态
|
||||||
@@ -150,6 +177,7 @@
|
|||||||
name: '',
|
name: '',
|
||||||
gender: '男',
|
gender: '男',
|
||||||
age: '',
|
age: '',
|
||||||
|
birthDate: '',
|
||||||
school: '',
|
school: '',
|
||||||
address: ''
|
address: ''
|
||||||
})
|
})
|
||||||
@@ -165,6 +193,25 @@
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 计算年龄
|
||||||
|
const calculateAge = (birthDate : string) : number => {
|
||||||
|
if (!birthDate) return 0
|
||||||
|
const birth = new Date(birthDate)
|
||||||
|
const today = new Date()
|
||||||
|
let age = today.getFullYear() - birth.getFullYear()
|
||||||
|
const monthDiff = today.getMonth() - birth.getMonth()
|
||||||
|
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
|
||||||
|
age--
|
||||||
|
}
|
||||||
|
return age
|
||||||
|
}
|
||||||
|
|
||||||
|
// 出生日期变化时自动计算年龄
|
||||||
|
const onBirthDateChange = (e : any) => {
|
||||||
|
formData.value.birthDate = e.detail.value
|
||||||
|
formData.value.age = calculateAge(e.detail.value).toString()
|
||||||
|
}
|
||||||
|
|
||||||
// 打开编辑弹窗
|
// 打开编辑弹窗
|
||||||
const openEditModal = (student : Student) => {
|
const openEditModal = (student : Student) => {
|
||||||
editingStudent.value = student
|
editingStudent.value = student
|
||||||
@@ -173,6 +220,7 @@
|
|||||||
name: student.name,
|
name: student.name,
|
||||||
gender: student.gender,
|
gender: student.gender,
|
||||||
age: student.age,
|
age: student.age,
|
||||||
|
birthDate: student.birthDate,
|
||||||
school: student.school,
|
school: student.school,
|
||||||
address: student.address
|
address: student.address
|
||||||
}
|
}
|
||||||
@@ -189,6 +237,7 @@
|
|||||||
name: '',
|
name: '',
|
||||||
gender: '男',
|
gender: '男',
|
||||||
age: '',
|
age: '',
|
||||||
|
birthDate: '',
|
||||||
school: '',
|
school: '',
|
||||||
address: ''
|
address: ''
|
||||||
}
|
}
|
||||||
@@ -201,8 +250,8 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.value.age.trim()) {
|
if (!formData.value.birthDate) {
|
||||||
Service.Msg('请输入年龄')
|
Service.Msg('请选择出生日期')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +261,8 @@
|
|||||||
id: Date.now().toString().slice(-6),
|
id: Date.now().toString().slice(-6),
|
||||||
name: formData.value.name.trim(),
|
name: formData.value.name.trim(),
|
||||||
gender: formData.value.gender,
|
gender: formData.value.gender,
|
||||||
age: formData.value.age.trim(),
|
age: formData.value.age,
|
||||||
|
birthDate: formData.value.birthDate,
|
||||||
school: formData.value.school.trim(),
|
school: formData.value.school.trim(),
|
||||||
address: formData.value.address.trim()
|
address: formData.value.address.trim()
|
||||||
}
|
}
|
||||||
@@ -226,7 +276,8 @@
|
|||||||
...students.value[index],
|
...students.value[index],
|
||||||
name: formData.value.name.trim(),
|
name: formData.value.name.trim(),
|
||||||
gender: formData.value.gender,
|
gender: formData.value.gender,
|
||||||
age: formData.value.age.trim(),
|
age: formData.value.age,
|
||||||
|
birthDate: formData.value.birthDate,
|
||||||
school: formData.value.school.trim(),
|
school: formData.value.school.trim(),
|
||||||
address: formData.value.address.trim()
|
address: formData.value.address.trim()
|
||||||
}
|
}
|
||||||
@@ -554,6 +605,101 @@
|
|||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
box-shadow: 0 0 0 4rpx rgba(24, 144, 255, 0.15);
|
box-shadow: 0 0 0 4rpx rgba(24, 144, 255, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.date-picker {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
|
||||||
|
&.placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-wrapper {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-display {
|
||||||
|
margin-top: 24rpx;
|
||||||
|
padding: 28rpx 32rpx;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
right: -20%;
|
||||||
|
width: 100rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -30%;
|
||||||
|
left: 10%;
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-icon {
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 18rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
backdrop-filter: blur(10rpx);
|
||||||
|
border: 2rpx solid rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8rpx;
|
||||||
|
|
||||||
|
.age-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-value {
|
||||||
|
font-size: 56rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 1;
|
||||||
|
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-unit {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.gender-selector {
|
.gender-selector {
|
||||||
@@ -562,17 +708,20 @@
|
|||||||
|
|
||||||
.gender-option {
|
.gender-option {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 88rpx;
|
height: 100rpx;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8rpx;
|
gap: 8rpx;
|
||||||
font-size: 28rpx;
|
font-size: 26rpx;
|
||||||
color: #666;
|
color: #666;
|
||||||
transition: all 0.25s ease;
|
transition: all 0.3s ease;
|
||||||
border: 2rpx solid transparent;
|
border: 2rpx solid transparent;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.96);
|
transform: scale(0.96);
|
||||||
@@ -580,9 +729,25 @@
|
|||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background-color: #e6f7ff;
|
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
|
||||||
border-color: #1890ff;
|
border-color: #1890ff;
|
||||||
color: #1890ff;
|
color: #fff;
|
||||||
|
box-shadow: 0 6rpx 16rpx rgba(24, 144, 255, 0.3);
|
||||||
|
|
||||||
|
.gender-icon-box {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gender-icon-box {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
background-color: #e6f7ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,6 @@
|
|||||||
<text class="time-label">当前总用时</text>
|
<text class="time-label">当前总用时</text>
|
||||||
<view class="" style="position: relative;">
|
<view class="" style="position: relative;">
|
||||||
<view class="total-time">{{ formatTime(currentTime) }}</view>
|
<view class="total-time">{{ formatTime(currentTime) }}</view>
|
||||||
<view class="" style="position: absolute; right: 0; bottom: 4rpx;">
|
|
||||||
<view v-if="stopwatchMode!=='together'" @click="recordNextAthlete"
|
|
||||||
style=" display: flex; align-items: center; justify-content: center; background-color: #fff; border: 1rpx solid #e2e2e2; width: 70rpx; height: 70rpx; padding: 10rpx; border-radius: 50%;">
|
|
||||||
<img :src="Service.GetIconImg('/static/index/Flag.png')" style="width: 35rpx; height: 35rpx;"
|
|
||||||
alt="" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -25,7 +18,7 @@
|
|||||||
|
|
||||||
<view class="athletes-list">
|
<view class="athletes-list">
|
||||||
<view v-for="(athlete, index) in athletes" :key="athlete.id" @longpress="deleStu(athlete.id)"
|
<view v-for="(athlete, index) in athletes" :key="athlete.id" @longpress="deleStu(athlete.id)"
|
||||||
class="athlete-item" :class="{ finished: athlete.finished, fastest: athlete.isFastest }">
|
class="athlete-item" :class="{ finished: athlete.finished }">
|
||||||
<view class="athlete-number">{{ index+1 }}</view>
|
<view class="athlete-number">{{ index+1 }}</view>
|
||||||
<view class="athlete-info">
|
<view class="athlete-info">
|
||||||
<text class="athlete-name">{{ athlete.name }}</text>
|
<text class="athlete-name">{{ athlete.name }}</text>
|
||||||
@@ -33,7 +26,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="time-wrapper">
|
<view class="time-wrapper">
|
||||||
<text class="athlete-time">{{ formatTime(athlete.time) }}</text>
|
<text class="athlete-time">{{ formatTime(athlete.time) }}</text>
|
||||||
<text v-if="athlete.bestTime" class="best-time">最快: {{ formatTime(athlete.bestTime) }}</text>
|
<text class="best-time">最快: {{ athlete.bestTime !== null ? formatTime(athlete.bestTime) : '无' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="" style="display: flex;align-items: center; gap: 10rpx;">
|
<view class="" style="display: flex;align-items: center; gap: 10rpx;">
|
||||||
<view @click="handleAthleteFirstButton(athlete, index)">
|
<view @click="handleAthleteFirstButton(athlete, index)">
|
||||||
@@ -49,18 +42,27 @@
|
|||||||
|
|
||||||
<!-- 底部控制按钮 -->
|
<!-- 底部控制按钮 -->
|
||||||
<view class="control-buttons">
|
<view class="control-buttons">
|
||||||
<button class="start-btn" @click="startTimer" :disabled="isRunning">
|
<view class="button-group">
|
||||||
<u-icon name="play-right" size="24" color="#fff"></u-icon>
|
<button v-if="!isRunning" class="start-btn" @click="startTimer">
|
||||||
<text class="btn-text">开始</text>
|
<u-icon name="play-right" size="24" color="#fff"></u-icon>
|
||||||
</button>
|
<text class="btn-text">开始</text>
|
||||||
<button class="pause-btn" @click="stopTimer" :disabled="!isRunning">
|
</button>
|
||||||
<u-icon name="pause" size="24" color="#fff"></u-icon>
|
|
||||||
<text class="btn-text">暂停</text>
|
<button v-if="isRunning" class="record-btn" @click="recordNextAthlete">
|
||||||
</button>
|
<u-icon name="checkmark-circle" size="24" color="#fff"></u-icon>
|
||||||
<button class="reset-all-btn" @click="resetAll">
|
<text class="btn-text">记录</text>
|
||||||
<u-icon name="reload" size="24" color="#fff"></u-icon>
|
</button>
|
||||||
<text class="btn-text">重置</text>
|
</view>
|
||||||
</button>
|
<view class="button-group">
|
||||||
|
<button v-if="!isRunning" class="reset-all-btn" @click="resetAll">
|
||||||
|
<u-icon name="reload" size="24" color="#fff"></u-icon>
|
||||||
|
<text class="btn-text">重置</text>
|
||||||
|
</button>
|
||||||
|
<button v-if="isRunning" class="reset-all-btn" @click="stopTimer">
|
||||||
|
<u-icon name="pause" size="24" color="#fff"></u-icon>
|
||||||
|
<text class="btn-text">暂停</text>
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 更多选项弹出框 -->
|
<!-- 更多选项弹出框 -->
|
||||||
@@ -76,12 +78,17 @@
|
|||||||
<text class="modal-item-text">添加学生</text>
|
<text class="modal-item-text">添加学生</text>
|
||||||
<u-icon name="arrow-right" size="16" color="#ccc"></u-icon>
|
<u-icon name="arrow-right" size="16" color="#ccc"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="modal-item" @click="showGroupModal = true; showMoreModal = false">
|
||||||
|
<u-icon name="grid" size="24" color="#fa8c16"></u-icon>
|
||||||
|
<text class="modal-item-text">修改组别</text>
|
||||||
|
<u-icon name="arrow-right" size="16" color="#ccc"></u-icon>
|
||||||
|
</view>
|
||||||
<view class="modal-cancel" @click="closeMoreModal">
|
<view class="modal-cancel" @click="closeMoreModal">
|
||||||
取消
|
取消
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 秒表模式弹出框 -->
|
<!-- 秒表模式弹出框 -->
|
||||||
<view v-if="showStopwatchModal" class="modal-overlay" @click="closeStopwatchModal"></view>
|
<view v-if="showStopwatchModal" class="modal-overlay" @click="closeStopwatchModal"></view>
|
||||||
<view v-if="showStopwatchModal" class="stopwatch-modal">
|
<view v-if="showStopwatchModal" class="stopwatch-modal">
|
||||||
<view class="modal-header">
|
<view class="modal-header">
|
||||||
@@ -164,13 +171,39 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 修改组别弹出框 -->
|
||||||
|
<view v-if="showGroupModal" class="modal-overlay" @click="closeGroupModal"></view>
|
||||||
|
<view v-if="showGroupModal" class="group-modal">
|
||||||
|
<view class="modal-header">
|
||||||
|
<text class="modal-title">修改组别</text>
|
||||||
|
<view class="modal-close" @click="closeGroupModal">
|
||||||
|
<u-icon name="close" size="20" color="#999"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="modal-content">
|
||||||
|
<view class="form-group">
|
||||||
|
<text class="form-label">组别个数</text>
|
||||||
|
<input class="form-input" v-model="groupCount" placeholder="请输入组别个数" type="number" />
|
||||||
|
</view>
|
||||||
|
<view class="form-group">
|
||||||
|
<text class="form-label">一组多少人</text>
|
||||||
|
<input class="form-input" v-model="groupSize" placeholder="请输入一组多少人" type="number" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="modal-footer">
|
||||||
|
<button class="modal-cancel-btn" @click="closeGroupModal">取消</button>
|
||||||
|
<button class="modal-confirm-btn" @click="confirmGroupSettings">确定</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 悬浮保存按钮 -->
|
<!-- 悬浮保存按钮 -->
|
||||||
<view class="float-save-btn" style="color: #fff; font-size: 24rpx;" @click="saveData">
|
<view class="float-save-btn" style="color: #fff; font-size: 24rpx;" @click="saveData">
|
||||||
保存
|
保存
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 日期选择器 -->
|
<!-- 日期选择器 -->
|
||||||
<u-datetime-picker v-model="newStudentBirthday" :show="showDatePicker" mode="date" @confirm="onDateConfirm" @cancel="showDatePicker = false" />
|
<u-datetime-picker v-model="newStudentBirthday" :show="showDatePicker" mode="date" @confirm="onDateConfirm"
|
||||||
|
@cancel="showDatePicker = false" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -189,15 +222,14 @@
|
|||||||
time : number
|
time : number
|
||||||
bestTime : number | null
|
bestTime : number | null
|
||||||
finished : boolean
|
finished : boolean
|
||||||
isFastest : boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选手列表
|
// 选手列表
|
||||||
const athletes = ref<Athlete[]>([
|
const athletes = ref<Athlete[]>([
|
||||||
{ id: '1', number: '01', name: '张三', gender: '男', age: '18', birthday: '2006-01-01', time: 0, bestTime: null, finished: false, isFastest: false },
|
{ id: '1', number: '01', name: '张三', gender: '男', age: '18', birthday: '2006-01-01', time: 0, bestTime: null, finished: false },
|
||||||
{ id: '2', number: '02', name: '李四', gender: '女', age: '17', birthday: '2007-01-01', time: 0, bestTime: null, finished: false, isFastest: false },
|
{ id: '2', number: '02', name: '李四', gender: '女', age: '17', birthday: '2007-01-01', time: 0, bestTime: null, finished: false },
|
||||||
{ id: '3', number: '03', name: '王五', gender: '男', age: '19', birthday: '2005-01-01', time: 0, bestTime: null, finished: false, isFastest: false },
|
{ id: '3', number: '03', name: '王五', gender: '男', age: '19', birthday: '2005-01-01', time: 0, bestTime: null, finished: false },
|
||||||
{ id: '4', number: '04', name: '赵六', gender: '女', age: '18', birthday: '2006-01-01', time: 0, bestTime: null, finished: false, isFastest: false }
|
{ id: '4', number: '04', name: '赵六', gender: '女', age: '18', birthday: '2006-01-01', time: 0, bestTime: null, finished: false }
|
||||||
])
|
])
|
||||||
|
|
||||||
// 计时器状态
|
// 计时器状态
|
||||||
@@ -218,6 +250,11 @@
|
|||||||
const showMoreModal = ref(false)
|
const showMoreModal = ref(false)
|
||||||
const showStopwatchModal = ref(false)
|
const showStopwatchModal = ref(false)
|
||||||
const showAddStudentModal = ref(false)
|
const showAddStudentModal = ref(false)
|
||||||
|
const showGroupModal = ref(false)
|
||||||
|
|
||||||
|
// 组别设置
|
||||||
|
const groupCount = ref('')
|
||||||
|
const groupSize = ref('')
|
||||||
|
|
||||||
// 新学生信息
|
// 新学生信息
|
||||||
const newStudentName = ref('')
|
const newStudentName = ref('')
|
||||||
@@ -293,6 +330,17 @@
|
|||||||
|
|
||||||
// 记录下一个选手
|
// 记录下一个选手
|
||||||
const recordNextAthlete = () => {
|
const recordNextAthlete = () => {
|
||||||
|
// 查找下一个未完成的选手
|
||||||
|
while (currentAthleteIndex.value < athletes.value.length) {
|
||||||
|
const athlete = athletes.value[currentAthleteIndex.value]
|
||||||
|
if (athlete.finished) {
|
||||||
|
// 已手动计时的学生,跳过
|
||||||
|
currentAthleteIndex.value++
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (currentAthleteIndex.value >= athletes.value.length) {
|
if (currentAthleteIndex.value >= athletes.value.length) {
|
||||||
Service.Msg('所有选手已完成')
|
Service.Msg('所有选手已完成')
|
||||||
return
|
return
|
||||||
@@ -302,14 +350,6 @@
|
|||||||
athlete.finished = true
|
athlete.finished = true
|
||||||
athlete.time = currentTime.value
|
athlete.time = currentTime.value
|
||||||
|
|
||||||
// 更新最快记录
|
|
||||||
if (athlete.bestTime === null || athlete.time < athlete.bestTime) {
|
|
||||||
athlete.bestTime = athlete.time
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新最快选手
|
|
||||||
updateFastestAthlete()
|
|
||||||
|
|
||||||
currentAthleteIndex.value++
|
currentAthleteIndex.value++
|
||||||
|
|
||||||
// 如果所有选手都完成了,停止计时
|
// 如果所有选手都完成了,停止计时
|
||||||
@@ -319,32 +359,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新最快选手
|
|
||||||
const updateFastestAthlete = () => {
|
|
||||||
// 先清除所有最快标记
|
|
||||||
athletes.value.forEach(a => a.isFastest = false)
|
|
||||||
|
|
||||||
// 找出已完成的选手
|
|
||||||
const finished = athletes.value.filter(a => a.finished && a.time > 0)
|
|
||||||
if (finished.length === 0) return
|
|
||||||
|
|
||||||
// 找出时间最短的
|
|
||||||
const fastest = finished.reduce((prev, curr) => {
|
|
||||||
return prev.time < curr.time ? prev : curr
|
|
||||||
})
|
|
||||||
|
|
||||||
fastest.isFastest = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置选手时间
|
// 重置选手时间
|
||||||
const resetAthleteTime = (athlete : Athlete) => {
|
const resetAthleteTime = (athlete : Athlete) => {
|
||||||
athlete.time = 0
|
athlete.time = 0
|
||||||
athlete.finished = false
|
athlete.finished = false
|
||||||
athlete.isFastest = false
|
|
||||||
|
|
||||||
// 重新排序索引
|
// 重新排序索引
|
||||||
reorderAthletes()
|
reorderAthletes()
|
||||||
updateFastestAthlete()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理选手第一个按钮点击
|
// 处理选手第一个按钮点击
|
||||||
@@ -358,14 +379,6 @@
|
|||||||
athlete.finished = true
|
athlete.finished = true
|
||||||
athlete.time = currentTime.value
|
athlete.time = currentTime.value
|
||||||
|
|
||||||
// 更新最快记录
|
|
||||||
if (athlete.bestTime === null || athlete.time < athlete.bestTime) {
|
|
||||||
athlete.bestTime = athlete.time
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新最快选手
|
|
||||||
updateFastestAthlete()
|
|
||||||
|
|
||||||
// 检查是否所有选手都完成了
|
// 检查是否所有选手都完成了
|
||||||
const allFinished = athletes.value.every(a => a.finished)
|
const allFinished = athletes.value.every(a => a.finished)
|
||||||
if (allFinished) {
|
if (allFinished) {
|
||||||
@@ -382,6 +395,24 @@
|
|||||||
currentAthleteIndex.value = athletes.value.length - unfinished.length
|
currentAthleteIndex.value = athletes.value.length - unfinished.length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录所有未完成选手
|
||||||
|
const recordAll = () => {
|
||||||
|
let recordedCount = 0
|
||||||
|
athletes.value.forEach(athlete => {
|
||||||
|
if (!athlete.finished) {
|
||||||
|
athlete.finished = true
|
||||||
|
athlete.time = currentTime.value
|
||||||
|
recordedCount++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (recordedCount > 0) {
|
||||||
|
Service.Msg(`已记录${recordedCount}位选手`, 'success')
|
||||||
|
} else {
|
||||||
|
Service.Msg('没有需要记录的选手')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 重置所有
|
// 重置所有
|
||||||
const resetAll = () => {
|
const resetAll = () => {
|
||||||
stopTimer()
|
stopTimer()
|
||||||
@@ -391,7 +422,6 @@
|
|||||||
athletes.value.forEach(athlete => {
|
athletes.value.forEach(athlete => {
|
||||||
athlete.time = 0
|
athlete.time = 0
|
||||||
athlete.finished = false
|
athlete.finished = false
|
||||||
athlete.isFastest = false
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,6 +506,37 @@
|
|||||||
newStudentAge.value = ''
|
newStudentAge.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭组别弹出框
|
||||||
|
const closeGroupModal = () => {
|
||||||
|
showGroupModal.value = false
|
||||||
|
groupCount.value = ''
|
||||||
|
groupSize.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认组别设置
|
||||||
|
const confirmGroupSettings = () => {
|
||||||
|
if (!groupCount.value.trim()) {
|
||||||
|
Service.Msg('请输入组别个数')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!groupSize.value.trim()) {
|
||||||
|
Service.Msg('请输入一组多少人')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const count = parseInt(groupCount.value)
|
||||||
|
const size = parseInt(groupSize.value)
|
||||||
|
if (isNaN(count) || count <= 0) {
|
||||||
|
Service.Msg('请输入有效的组别个数')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isNaN(size) || size <= 0) {
|
||||||
|
Service.Msg('请输入有效的一组人数')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Service.Msg(`已设置${count}组,每组${size}人`, 'success')
|
||||||
|
closeGroupModal()
|
||||||
|
}
|
||||||
|
|
||||||
// 添加新学生
|
// 添加新学生
|
||||||
const addNewStudent = () => {
|
const addNewStudent = () => {
|
||||||
if (!newStudentName.value.trim()) {
|
if (!newStudentName.value.trim()) {
|
||||||
@@ -497,8 +558,7 @@
|
|||||||
birthday: newStudentBirthday.value.trim(),
|
birthday: newStudentBirthday.value.trim(),
|
||||||
time: 0,
|
time: 0,
|
||||||
bestTime: null,
|
bestTime: null,
|
||||||
finished: false,
|
finished: false
|
||||||
isFastest: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
athletes.value.push(newAthlete)
|
athletes.value.push(newAthlete)
|
||||||
@@ -514,7 +574,7 @@
|
|||||||
stopwatchMode: stopwatchMode.value,
|
stopwatchMode: stopwatchMode.value,
|
||||||
intervalTime: intervalTime.value
|
intervalTime: intervalTime.value
|
||||||
}
|
}
|
||||||
Service.SetStorageCache('swimTimingData', data)
|
console.log(data);
|
||||||
Service.Msg('保存成功', 'success')
|
Service.Msg('保存成功', 'success')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,7 +609,7 @@
|
|||||||
.total-time {
|
.total-time {
|
||||||
font-size: 80rpx;
|
font-size: 80rpx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #1890ff;
|
color: #ff0000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -652,8 +712,8 @@
|
|||||||
|
|
||||||
.athlete-time {
|
.athlete-time {
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
font-weight: 600;
|
font-weight: bold;
|
||||||
color: #333333;
|
color: #ff0000;
|
||||||
font-family: 'DIN Alternate', monospace;
|
font-family: 'DIN Alternate', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,60 +740,122 @@
|
|||||||
/* 底部控制按钮 */
|
/* 底部控制按钮 */
|
||||||
.control-buttons {
|
.control-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20rpx;
|
gap: 24rpx;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background-color: #fff;
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, #ffffff 100%);
|
||||||
padding: 20rpx;
|
padding: 32rpx 40rpx;
|
||||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||||||
border-top: 1rpx solid #f0f0f0;
|
border-top: 1rpx solid rgba(0, 0, 0, 0.06);
|
||||||
|
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.start-btn,
|
.start-btn,
|
||||||
.pause-btn,
|
.pause-btn,
|
||||||
.reset-all-btn {
|
.reset-all-btn,
|
||||||
|
.record-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 88rpx;
|
height: 100rpx;
|
||||||
border-radius: 12rpx;
|
border-radius: 20rpx;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 30rpx;
|
font-size: 32rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 10rpx;
|
gap: 12rpx;
|
||||||
max-width: 220rpx;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
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 {
|
.start-btn {
|
||||||
background-color: #52c41a;
|
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
box-shadow: 0 6rpx 20rpx rgba(82, 196, 26, 0.4);
|
||||||
|
|
||||||
&:disabled {
|
&:active {
|
||||||
background-color: #ccc;
|
transform: scale(0.96);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pause-btn {
|
.pause-btn {
|
||||||
background-color: #fa541c;
|
background: linear-gradient(135deg, #fa541c 0%, #ff7a45 100%);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
box-shadow: 0 6rpx 20rpx rgba(250, 84, 28, 0.4);
|
||||||
|
|
||||||
&:disabled {
|
&:active {
|
||||||
background-color: #ccc;
|
transform: scale(0.96);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(250, 84, 28, 0.3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.reset-all-btn {
|
.reset-all-btn {
|
||||||
background-color: #1890ff;
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
box-shadow: 0 6rpx 20rpx rgba(24, 144, 255, 0.4);
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(24, 144, 255, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-btn {
|
||||||
|
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
box-shadow: 0 6rpx 20rpx rgba(82, 196, 26, 0.4);
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-text {
|
.btn-text {
|
||||||
font-size: 30rpx;
|
font-size: 32rpx;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -785,9 +907,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 秒表模式和添加学生弹出框 */
|
/* 秒表模式、添加学生和组别弹出框 */
|
||||||
.stopwatch-modal,
|
.stopwatch-modal,
|
||||||
.add-student-modal {
|
.add-student-modal,
|
||||||
|
.group-modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -991,15 +1114,36 @@
|
|||||||
.float-save-btn {
|
.float-save-btn {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 40rpx;
|
right: 40rpx;
|
||||||
bottom: 160rpx;
|
bottom: 180rpx;
|
||||||
width: 100rpx;
|
width: 110rpx;
|
||||||
height: 100rpx;
|
height: 110rpx;
|
||||||
background: linear-gradient(135deg, #1890ff, #40a9ff);
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.4);
|
box-shadow: 0 6rpx 20rpx rgba(24, 144, 255, 0.4);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #ffffff;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&::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: 50% 50% 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user