From f682c0316b2071dd661e14b0ef91f29fd385fe5f Mon Sep 17 00:00:00 2001
From: Ls <2391972606@qq.com>
Date: Mon, 6 Apr 2026 09:31:25 +0800
Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=95=B0=E6=8D=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/pages.json | 3 +-
src/pages/dataAnalyze/Curve.vue | 468 ++++----
src/pages/dataAnalyze/baoganAnalyze.vue | 758 +++++++++----
src/pages/dataAnalyze/paragraphAnalyze.vue | 1176 ++++++++------------
src/pages/index/index.vue | 49 +-
src/pages/userFunc/project.vue | 1043 +++++++++++------
src/pages/userFunc/student.vue | 201 +++-
src/pages/userFunc/swiming.vue | 338 ++++--
8 files changed, 2352 insertions(+), 1684 deletions(-)
diff --git a/src/pages.json b/src/pages.json
index a2cf92b..264bb20 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -77,7 +77,8 @@
{
"path": "project",
"style": {
- "navigationBarTitleText": "包干"
+ "navigationBarTitleText": "包干",
+ "navigationStyle": "custom"
}
}
]
diff --git a/src/pages/dataAnalyze/Curve.vue b/src/pages/dataAnalyze/Curve.vue
index 812b324..3180c34 100644
--- a/src/pages/dataAnalyze/Curve.vue
+++ b/src/pages/dataAnalyze/Curve.vue
@@ -14,7 +14,8 @@
选择项目
-
+
{{ selectedProjectName }}
@@ -31,7 +32,9 @@
-
+
{{ student.name.charAt(0) }}
@@ -75,9 +78,11 @@
历史训练成绩变化曲线
-
-
-
+
+
+
+
+
@@ -142,8 +147,8 @@
* 定义学生的基本信息
*/
interface Student {
- id: string
- name: string
+ id : string
+ name : string
}
/**
@@ -151,12 +156,12 @@
* 定义学生某次训练的详细记录
*/
interface StudentTrainingRecord {
- studentId: string
- studentName: string
- time: number
- recordDate: string
- recordFullDate: string
- round: number
+ studentId : string
+ studentName : string
+ time : number
+ recordDate : string
+ recordFullDate : string
+ 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({
color: chartColors,
@@ -187,13 +200,14 @@
},
xAxis: {
disableGrid: true,
- itemCount: 6,
- rotateLabel: true
+ rotateLabel: true,
+ scrollShow: true,
+ scrollAlign: 'left'
},
yAxis: {
data: [{
min: 0,
- format: (val: number) => val.toFixed(1) + 's'
+ format: (val : number) => val.toFixed(1) + 's'
}]
},
extra: {
@@ -226,7 +240,7 @@
// ==================== 模拟数据 ====================
// 模拟的学生数据(按项目分组)
- const mockProjectStudents: Record = {
+ const mockProjectStudents : Record = {
'1': [
{ id: 's1', name: '张小明' },
{ id: 's2', name: '李小红' },
@@ -252,7 +266,7 @@
}
// 模拟的训练记录数据(按项目分组)
- const mockTrainingRecords: Record = {
+ const mockTrainingRecords : Record = {
'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 },
@@ -319,7 +333,7 @@
// TODO: 调用后端 API 获取项目列表
}
- const handleProjectChange = (e: any) => {
+ const handleProjectChange = (e : any) => {
const index = e.detail.value
selectedProjectIndex.value = index
const selectedProject = projectList.value[index]
@@ -330,7 +344,7 @@
selectedStudentIds.value = []
}
- const loadProjectStudents = (projectId: string) => {
+ const loadProjectStudents = (projectId : string) => {
if (mockProjectStudents[projectId]) {
projectStudents.value = mockProjectStudents[projectId]
} else {
@@ -338,7 +352,7 @@
}
}
- const toggleStudent = (studentId: string) => {
+ const toggleStudent = (studentId : string) => {
const index = selectedStudentIds.value.indexOf(studentId)
if (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 studentRecords = records.filter(r => r.studentId === studentId)
if (studentRecords.length === 0) return 0
@@ -378,7 +392,7 @@
return `${parts[1]}-${parts[2]}`
})
- const seriesData: any[] = []
+ const seriesData : any[] = []
selectedStudentIds.value.forEach((studentId, index) => {
const studentRecords = projectRecords.filter(r => r.studentId === studentId)
@@ -430,249 +444,255 @@
.subtitle {
font-size: 24rpx;
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 {
- 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 {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 16rpx;
+ .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);
- .student-item {
+ .picker-wrapper {
display: flex;
- flex-direction: column;
align-items: center;
- padding: 20rpx 10rpx;
+ justify-content: space-between;
+ padding: 20rpx 24rpx;
background-color: #f8f8f8;
border-radius: 12rpx;
- border: 2rpx solid transparent;
+ border: 1rpx solid #e8e8e8;
transition: all 0.3s ease;
- position: relative;
&:active {
- transform: scale(0.95);
- }
-
- &.selected {
- background-color: #f0f9eb;
+ background-color: #f0f0f0;
border-color: #52c41a;
}
- .student-avatar {
- width: 60rpx;
- 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;
+ .picker-text {
+ font-size: 28rpx;
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;
}
}
}
- }
-
-
- .chart-section {
- margin: 0 20rpx;
-
- .stats-card {
+ .student-select-section {
background-color: #fff;
+ margin: 0 20rpx 20rpx;
border-radius: 16rpx;
- padding: 24rpx;
- margin-bottom: 20rpx;
- display: flex;
- align-items: center;
- justify-content: space-around;
+ padding: 28rpx;
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;
- }
+ .section-header {
+ margin-bottom: 24rpx;
}
- .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;
- 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;
+ .student-list {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
gap: 16rpx;
- .legend-item {
+ .student-item {
display: flex;
+ flex-direction: column;
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 {
- width: 24rpx;
- height: 24rpx;
+ &:active {
+ transform: scale(0.95);
+ }
+
+ &.selected {
+ background-color: #f0f9eb;
+ border-color: #52c41a;
+ }
+
+ .student-avatar {
+ width: 60rpx;
+ height: 60rpx;
+ background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
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 {
- font-size: 26rpx;
+ .student-name {
+ font-size: 24rpx;
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;
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;
+ }
+ }
+ }
}
}
- }
-
+
\ No newline at end of file
diff --git a/src/pages/dataAnalyze/baoganAnalyze.vue b/src/pages/dataAnalyze/baoganAnalyze.vue
index 527aa20..c3e7f63 100644
--- a/src/pages/dataAnalyze/baoganAnalyze.vue
+++ b/src/pages/dataAnalyze/baoganAnalyze.vue
@@ -8,78 +8,81 @@
-
-
-
- 项目名称
-
- {{ selectedProject }}
-
-
+
+
+
+ 选择项目
-
- 选择学生
-
- {{ selectedStudent }}
-
+
+
+ {{ selectedProjectName }}
+
-
+
-
-
-
-
-
-
-
-
+
+
-
-
-
- 选中日期
- {{ selectedDate }}
+
+
+
+ 选择学生
-
-
- 训练人数
- {{ tableData.length }}人
-
-
-
- 完成率平均
- {{ averageCompletion }}%
+
+ {{ selectedStudentDisplay }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ student.name.charAt(0) }}
+
+
+
+ {{ student.name }}
+
+
+ {{ student.gender }}
+
+ {{ student.age }}岁
+
+
+
+
+
+
-
-
+
+
@@ -91,7 +94,7 @@
-
+
@@ -100,15 +103,43 @@
import { Service } from '@/Service/Service'
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 currentMonth = ref(new Date().getMonth() + 1)
-
- // 选中的日期
const selectedDate = ref('')
- // 表格列定义
const columns = ref([
+ {
+ label: '日期',
+ prop: 'date',
+ width: '100px'
+ },
{
label: '姓名',
prop: 'name',
@@ -136,211 +167,218 @@
}
])
- // 表格数据
- interface TableDataItem {
- name: string
- projectName: string
- plan: string
- completion: number
- detail: string
- }
-
const tableData = ref([])
- // 项目列表
- const projectList = ref([
- { label: '全部项目', value: 'all' },
- { label: '100米自由泳', value: '100m-free' },
- { label: '200米自由泳', value: '200m-free' },
- { label: '400米自由泳', value: '400m-free' },
- { label: '100米蛙泳', value: '100m-breast' }
+ const projectList = ref([
+ { id: '1', name: '100米自由泳' },
+ { id: '2', name: '200米自由泳' },
+ { id: '3', name: '400米自由泳' },
+ { id: '4', name: '100米蛙泳' }
])
- // 学生列表
- 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 projectOptions = ref(projectList.value)
- // 选中的项目和学生
- const selectedProject = ref('全部项目')
- const selectedStudent = ref('全部学生')
- const selectedProjectValue = ref('all')
- const selectedStudentValues = ref([])
+ const selectedProjectIndex = ref(-1)
+ const selectedProjectId = ref('')
- // 选择器显示状态
- const showProjectPicker = ref(false)
- const showStudentPicker = ref(false)
- const defaultStudentIndex = ref([0])
-
- // 日历打点数据
- // 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 selectedProjectName = computed(() => {
+ if (selectedProjectIndex.value === -1) {
+ return '请选择项目'
+ }
+ return projectList.value[selectedProjectIndex.value]?.name || '请选择项目'
})
- // 完整模拟数据
- const mockData: Record = {
- '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([
{ date: '2026-03-28', info: '训练' },
{ date: '2026-03-29', info: '训练' },
{ date: '2026-03-30', info: '训练' }
])
+ const studentList = ref([])
+
+ const selectedStudentIndexes = ref([])
+ const selectedStudentIds = ref([])
+ 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> = {
+ '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 = {
+ '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(() => {
loadData()
})
onShow(() => {
- // 页面显示时刷新数据
+
})
- // 加载数据
const loadData = () => {
- // TODO: 调用API获取数据
+
}
- // 月份单元格点击处理
- const handleCellClick = (event: any) => {
+ const handleCellClick = (event : any) => {
console.log('单元格点击事件:', event)
}
- // 月份切换处理
- const handleMonthSwitch = (e: any) => {
+ const handleProjectChange = (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
currentMonth.value = e.month
loadData()
- // 切换月份后清空选中日期
selectedDate.value = ''
+ selectedStudentIndexes.value = []
+ selectedStudentIds.value = []
tableData.value = []
}
- // 日期选择处理
- const handleDateChange = (e: any) => {
+ const handleDateChange = (e : any) => {
+ console.log(e);
const date = e.fulldate
selectedDate.value = date
- filterAndLoadData()
+ selectedStudentIndexes.value = []
+ selectedStudentIds.value = []
+ tableData.value = []
}
- // 项目选择确认
- const handleProjectConfirm = (e: any) => {
- const item = e.value[0]
- selectedProject.value = item.label
- selectedProjectValue.value = item.value
- showProjectPicker.value = false
- filterAndLoadData()
+ const loadProjectStudents = (projectId : string) => {
+ const students = mockStudents[projectId] || []
+ studentList.value = students
+ console.log(`项目 ${projectId} 的学生列表加载完成,共 ${students.length} 位学生`)
}
- // 学生选择确认
- const handleStudentConfirm = (e: any) => {
- const items = e.value
- selectedStudentValues.value = items.map((item: any) => item.value)
+ const selectAllStudents = () => {
+ selectedStudentIndexes.value = studentList.value.map((_, index) => index)
+ }
- if (selectedStudentValues.value.length === 0) {
- selectedStudent.value = '未选择'
- } else if (selectedStudentValues.value.includes('all')) {
- selectedStudent.value = '全部学生'
+ const handleStudentChange = (e : any) => {
+ console.log('学生选择变化:', e)
+ }
+
+ const toggleStudentSelection = (index : number) => {
+ const idx = selectedStudentIndexes.value.indexOf(index)
+ if (idx > -1) {
+ selectedStudentIndexes.value.splice(idx, 1)
} else {
- const selectedNames = items.map((item: any) => item.label)
- selectedStudent.value = selectedNames.length > 2
- ? `${selectedNames.slice(0, 2).join(', ')}等${selectedNames.length}人`
- : selectedNames.join(', ')
+ selectedStudentIndexes.value.push(index)
}
+ }
- defaultStudentIndex.value = e.indexs
+ const confirmStudentSelection = () => {
+ selectedStudentIds.value = selectedStudentIndexes.value.map(index => studentList.value[index].id)
showStudentPicker.value = false
filterAndLoadData()
}
- // 过滤并加载数据
+ const closeStudentPicker = () => {
+ showStudentPicker.value = false
+ }
+
const filterAndLoadData = () => {
- if (!selectedDate.value) {
+ if (!selectedDate.value || selectedStudentIndexes.value.length === 0) {
tableData.value = []
return
}
- const dateData = mockData[selectedDate.value]
+ const projectData = mockData[selectedProjectId.value]
+ if (!projectData) {
+ tableData.value = []
+ return
+ }
+
+ const dateData = projectData[selectedDate.value]
if (!dateData) {
tableData.value = []
return
}
- let filteredData = [...dateData]
-
- // 按项目过滤
- if (selectedProjectValue.value !== 'all') {
- const projectMap: Record = {
- '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 = {
- '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
+ const selectedStudentNames = selectedStudentIndexes.value.map(index => studentList.value[index].name)
+ tableData.value = dateData.filter(item => selectedStudentNames.includes(item.name))
}
@@ -354,7 +392,6 @@
padding-bottom: 40rpx;
}
- /* 页面标题区域 */
.header-section {
background-color: #fff;
padding: 32rpx 28rpx 24rpx;
@@ -376,59 +413,272 @@
}
}
- /* 选择器区域 */
- .selector-section {
+ .select-section {
background-color: #fff;
margin: 0 20rpx 20rpx;
- border-radius: 16rpx;
- padding: 24rpx;
- display: flex;
- gap: 20rpx;
+ border-radius: 20rpx;
+ padding: 28rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+ position: relative;
+ overflow: hidden;
- .selector-item {
- flex: 1;
+ .select-label {
+ margin-bottom: 16rpx;
+
+ .label-text {
+ font-size: 26rpx;
+ font-weight: 600;
+ color: #666;
+ }
+ }
+
+ .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;
- .selector-label {
- font-size: 28rpx;
- color: #666;
- margin-right: 16rpx;
+ &:active {
+ background-color: #f0f0f0;
+ border-color: #faad14;
+ transform: scale(0.98);
}
- .selector-value {
- flex: 1;
+ .picker-text {
+ 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;
align-items: center;
- justify-content: flex-end;
+ gap: 16rpx;
- .value-text {
- font-size: 28rpx;
- color: #333;
- font-weight: 500;
- margin-right: 8rpx;
+ .action-btn {
+ font-size: 26rpx;
+ color: #666;
+ padding: 14rpx 28rpx;
+ 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;
+ }
+ }
}
}
}
}
- /* 日历组件包装 */
- .calendar-wrapper {
- background-color: #fff;
- margin-bottom: 20rpx;
- padding: 20rpx 0;
+ .modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(180deg, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.4) 100%);
+ backdrop-filter: blur(10rpx);
+ z-index: 998;
+ animation: fadeIn 0.3s ease;
}
- /* 日期数据统计 */
.date-summary {
background-color: #fff;
margin: 0 20rpx 20rpx;
- border-radius: 16rpx;
+ border-radius: 20rpx;
padding: 24rpx;
display: flex;
align-items: center;
@@ -459,16 +709,14 @@
}
}
- /* 表格区域 */
.table-section {
margin: 0 20rpx;
background-color: #fff;
- border-radius: 16rpx;
+ border-radius: 20rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}
- /* 空状态容器 */
.empty-container {
display: flex;
flex-direction: column;
@@ -486,11 +734,10 @@
}
}
- /* 提示状态 */
.hint-state {
margin: 0 20rpx;
background-color: #fff;
- border-radius: 16rpx;
+ border-radius: 20rpx;
padding: 80rpx 40rpx;
text-align: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
@@ -505,7 +752,6 @@
}
}
- /* sl-table 样式覆盖 */
::v-deep .sl-table {
width: 100%;
}
@@ -533,4 +779,24 @@
::v-deep .sl-table__body__row:nth-child(odd) {
background-color: #fff !important;
}
-
+
+ @keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+ }
+
+ @keyframes slideUp {
+ from {
+ transform: translateY(100%);
+ }
+
+ to {
+ transform: translateY(0);
+ }
+ }
+
\ No newline at end of file
diff --git a/src/pages/dataAnalyze/paragraphAnalyze.vue b/src/pages/dataAnalyze/paragraphAnalyze.vue
index 891705e..e75ba52 100644
--- a/src/pages/dataAnalyze/paragraphAnalyze.vue
+++ b/src/pages/dataAnalyze/paragraphAnalyze.vue
@@ -1,19 +1,16 @@
-
-
- 选择项目
+ ">选择项目
-
@@ -23,8 +20,6 @@
-
-
-
-
选择学生
-
-
-
- {{ selectedStudentName }}
-
-
-
+
+ {{ selectedStudentDisplay }}
+
+
-
-
-
-
-
-
-
-
-
-
@@ -132,72 +121,32 @@
import { Service } from '@/Service/Service'
import { ref, computed } from 'vue'
- // ==================== TypeScript 接口定义 ====================
-
- /**
- * 项目接口
- * 定义游泳项目的基本信息
- */
interface Project {
- id: string // 项目 ID
- name: string // 项目名称
+ id: string
+ name: string
}
- /**
- * 学生接口
- * 定义学生的基本信息和成绩
- */
interface Student {
- id: string // 学生 ID
- name: string // 学生姓名
- gender: string // 性别:男/女
- age: number // 年龄
- projectName: string // 项目名称
- totalTime: number // 总成绩(秒)
- projectId: string // 所属项目 ID
+ id: string
+ name: string
+ gender: string
+ age: number
}
- /**
- * 分段数据接口
- * 定义分段训练的详细信息
- */
- interface SegmentData {
- id: string // 分段 ID
- distance: number // 分段距离(米)
- time: number // 分段成绩(秒)
- timeDiff: number // 时间差(与平均值的差值,秒)
+ interface TableDataItem {
+ date: string
+ name: string
+ projectName: string
+ segment20: string
+ segment70: string
+ segment120: string
+ segment170: string
}
- /**
- * 完整学生记录接口
- * 包含学生信息和分段数据
- */
- interface StudentRecord {
- student: Student // 学生基本信息
- segments: SegmentData[] // 分段数据列表
- date: string // 记录日期
- }
-
- // ==================== 响应式数据 - 年月相关 ====================
-
- /**
- * 当前年份
- * 用于月份切换时记录当前年份
- */
const currentYear = ref(new Date().getFullYear())
-
- /**
- * 当前月份(1-12)
- * 用于月份切换时记录当前月份
- */
const currentMonth = ref(new Date().getMonth() + 1)
+ const selectedDate = ref('')
- // ==================== 响应式数据 - 项目相关 ====================
-
- /**
- * 项目列表数据
- * 从后端 API 获取或使用模拟数据
- */
const projectList = ref([
{ id: '1', name: '100米自由泳' },
{ id: '2', name: '200米自由泳' },
@@ -205,28 +154,10 @@
{ id: '4', name: '100米蝶泳' }
])
- /**
- * 项目选择器选项
- * 用于 picker 组件显示
- */
const projectOptions = ref(projectList.value)
-
- /**
- * 当前选中的项目索引
- * 用于 picker 组件的 value 属性
- */
const selectedProjectIndex = ref(-1)
-
- /**
- * 当前选中的项目 ID
- * 用于后续数据查询
- */
const selectedProjectId = ref('')
- /**
- * 计算属性:当前选中的项目名称
- * 根据选中的索引返回项目名称,默认显示提示文本
- */
const selectedProjectName = computed(() => {
if (selectedProjectIndex.value === -1) {
return '请选择项目'
@@ -234,20 +165,6 @@
return projectList.value[selectedProjectIndex.value]?.name || '请选择项目'
})
- // ==================== 响应式数据 - 日期相关 ====================
-
- /**
- * 当前选中的日期
- * 格式:YYYY-MM-DD
- */
- const selectedDate = ref('')
-
- /**
- * 日历打点数据
- * 用于在日历上标记有训练记录的日期
- * date: 日期字符串,'YYYY-MM-DD'
- * info: 显示在日期上的标记信息
- */
const selectedDates = ref([
{ date: '2026-03-12', info: '分段' },
{ date: '2026-03-14', info: '分段' },
@@ -256,495 +173,186 @@
{ date: '2026-03-24', info: '分段' }
])
- // ==================== 响应式数据 - 学生相关 ====================
-
- /**
- * 学生列表数据
- * 根据选择的项目动态更新
- */
const studentList = ref([])
+ const selectedStudentIndexes = ref([])
+ const selectedStudentIds = ref([])
+ const showStudentSelect = ref(false)
- /**
- * 学生选择器选项
- * 用于 picker 组件显示
- */
- const studentOptions = ref([])
-
- /**
- * 当前选中的学生索引
- * 用于 picker 组件的 value 属性
- */
- const selectedStudentIndex = ref(-1)
-
- /**
- * 当前选中的学生 ID
- * 用于后续数据查询
- */
- const selectedStudentId = ref('')
-
- /**
- * 计算属性:当前选中的学生名称
- * 根据选中的索引返回学生名称,默认显示提示文本
- */
- const selectedStudentName = computed(() => {
- if (selectedStudentIndex.value === -1) {
+ 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(', ')
}
- return studentList.value[selectedStudentIndex.value]?.name || '请选择学生'
})
- // ==================== 响应式数据 - 分段相关 ====================
+ const tableData = ref([])
- /**
- * 当前选中的学生详细信息
- * 包含学生基本信息和分段数据
- */
- const currentStudent = ref(null)
-
- /**
- * 分段数据列表(用于表格展示)
- * 按段序号排序后的数据
- */
- const segmentList = ref([])
-
- /**
- * 分段表格列定义
- * 列顺序:分段距离、时间差、分段成绩
- */
- const segmentColumns = ref([
- {
- label: '分段距离',
- prop: 'distance',
- width: '140px'
+ const mockData: Record> = {
+ '1': {
+ '2026-03-12': [
+ { name: '王小明', projectName: '100米'自由泳', segment20: 0.037, segment70: 0.128, segment120: 0.219, segment170: 0.311 },
+ { name: '张小红', projectName: '100米'自由泳', segment20: 0.042, segment70: 0.145, segment120: 0.248, segment170: 0.352 },
+ { name: '李小龙', projectName: '100米'自由泳', segment20: 0.032, segment70: 0.112, segment120: 0.192, segment170: 0.272 }
+ ],
+ '2026-03-14': [
+ { name: '王小明', projectName: '100米'自由泳', segment20: 0.035, segment70: 0.122, segment120: 0.209, segment170: 0.296 },
+ { name: '张小红', projectName: '100米'自由泳', segment20: 0.040, segment70: 0.138, segment120: 0.236, segment170: 0.334 }
+ ]
},
- {
- label: '时间差',
- prop: 'timeDiff',
- width: '140px'
+ '2': {
+ '2026-03-16': [
+ { name: '赵小芳', projectName: '200米'自由泳', segment20: 0.038, segment70: 0.133, segment120: 0.228, segment170: 0.323 }
+ ]
},
- {
- label: '分段成绩',
- prop: 'time',
- width: '140px'
+ '3': {
+ '2026-03-19': [
+ { name: '陈小刚', projectName: '50米蛙泳', segment20: 0.044, segment70: 0.154, segment120: 0.264, segment170: 0.374 }
+ ]
+ },
+ '4': {
+ '2026-03-24': [
+ { name: '周小丽', projectName: '100米'蝶泳', segment20: 0.041, segment70: 0.143, segment120: 0.245, segment170: 0.347 }
+ ]
}
- ])
+ }
- /**
- * 分段表格数据
- * 将分段数据转换为表格所需格式
- */
- const segmentTableData = computed(() => {
- return segmentList.value.map(segment => ({
- distance: segment.distance, // 分段距离
- timeDiff: segment.timeDiff, // 时间差
- time: segment.time // 分段成绩
- }))
- })
-
- // ==================== 计算属性 - 页面状态判断 ====================
-
- /**
- * 计算属性:数据是否准备就绪
- * 只有项目、日期、学生都选择后才返回 true
- */
- const isDataReady = computed(() => {
- return selectedProjectId.value !== '' &&
- selectedDate.value !== '' &&
- selectedStudentId.value !== '' &&
- currentStudent.value !== null
- })
-
- /**
- * 计算属性:提示文本
- * 根据当前选择状态返回不同的提示信息
- */
- const hintText = computed(() => {
- if (!selectedProjectId.value) {
- return '请先选择项目'
- } else if (!selectedDate.value) {
- return '请在日历选择日期'
- } else if (!selectedStudentId.value) {
- return '请选择学生'
- }
- return '暂无数据'
- })
-
- // ==================== 模拟数据 ====================
-
- /**
- * 模拟的学生记录数据
- * 按项目 ID 分组,每组包含该项目的学生记录
- * 每个学生记录包含多个日期的分段数据
- */
- const mockStudentRecords: Record = {
- '1': [ // 100米自由泳项目的学生记录
- {
- student: {
- id: 's1',
- name: '王小明',
- gender: '男',
- age: 12,
- projectName: '100米自由泳',
- totalTime: 68.5,
- projectId: '1'
- },
- date: '2026-03-12',
- segments: [
- { id: 'seg1', distance: 25, time: 17.2, timeDiff: 0.8 },
- { id: 'seg2', distance: 25, time: 17.5, timeDiff: 1.1 },
- { id: 'seg3', distance: 25, time: 16.8, timeDiff: 0.4 },
- { id: 'seg4', distance: 25, time: 17.0, timeDiff: 0.6 }
- ]
- },
- {
- student: {
- id: 's2',
- name: '张小红',
- gender: '女',
- age: 11,
- projectName: '100米自由泳',
- totalTime: 72.3,
- projectId: '1'
- },
- date: '2026-03-12',
- segments: [
- { id: 'seg5', distance: 25, time: 18.5, timeDiff: 1.2 },
- { id: 'seg6', distance: 25, time: 18.0, timeDiff: 0.7 },
- { id: 'seg7', distance: 25, time: 17.8, timeDiff: 0.5 },
- { id: 'seg8', distance: 25, time: 18.0, timeDiff: 0.7 }
- ]
- },
- {
- student: {
- id: 's3',
- name: '李小龙',
- gender: '男',
- age: 13,
- projectName: '100米自由泳',
- totalTime: 65.2,
- projectId: '1'
- },
- date: '2026-03-14',
- segments: [
- { id: 'seg9', distance: 25, time: 16.5, timeDiff: 0.1 },
- { id: 'seg10', distance: 25, time: 16.3, timeDiff: -0.1 },
- { id: 'seg11', distance: 25, time: 16.4, timeDiff: 0 },
- { id: 'seg12', distance: 25, time: 16.0, timeDiff: -0.4 }
- ]
- }
+ const mockStudents: Record = {
+ '1': [
+ { id: 's1', name: '王小明', gender: '男', age: 12 },
+ { id: 's2', name: '张小红', gender: '女', age: 11 },
+ { id: 's3', name: '李小龙', gender: '男', age: 13 }
],
- '2': [ // 200米自由泳项目的学生记录
- {
- student: {
- id: 's4',
- name: '赵小芳',
- gender: '女',
- age: 10,
- projectName: '200米自由泳',
- totalTime: 145.2,
- projectId: '2'
- },
- date: '2026-03-16',
- segments: [
- { id: 'seg13', distance: 50, time: 36.5, timeDiff: 2.1 },
- { id: 'seg14', distance: 50, time: 35.8, timeDiff: 1.4 },
- { id: 'seg15', distance: 50, time: 36.2, timeDiff: 1.8 },
- { id: 'seg16', distance: 50, time: 36.7, timeDiff: 2.3 }
- ]
- }
+ '2': [
+ { id: 's4', name: '赵小芳', gender: '女', age: 10 }
],
- '3': [ // 50米蛙泳项目的学生记录
- {
- student: {
- id: 's5',
- name: '陈小刚',
- gender: '男',
- age: 14,
- projectName: '50米蛙泳',
- totalTime: 42.8,
- projectId: '3'
- },
- date: '2026-03-19',
- segments: [
- { id: 'seg17', distance: 25, time: 21.5, timeDiff: 1.2 },
- { id: 'seg18', distance: 25, time: 21.3, timeDiff: 1.0 }
- ]
- }
+ '3': [
+ { id: 's5', name: '陈小刚', gender: '男', age: 14 }
],
- '4': [ // 100米蝶泳项目的学生记录
- {
- student: {
- id: 's6',
- name: '周小丽',
- gender: '女',
- age: 12,
- projectName: '100米蝶泳',
- totalTime: 78.6,
- projectId: '4'
- },
- date: '2026-03-24',
- segments: [
- { id: 'seg19', distance: 25, time: 19.8, timeDiff: 1.5 },
- { id: 'seg20', distance: 25, time: 19.5, timeDiff: 1.2 },
- { id: 'seg21', distance: 25, time: 19.6, timeDiff: 1.3 },
- { id: 'seg22', distance: 25, time: 19.7, timeDiff: 1.4 }
- ]
- }
+ '4': [
+ { id: 's6', name: '周小丽', gender: '女', age: 12 }
]
}
- // ==================== 生命周期钩子 ====================
-
- /**
- * 页面加载时触发
- * 在页面初始化时执行,只执行一次
- */
onLoad(() => {
- // 初始化数据,加载项目列表
- loadProjectData()
+ loadData()
})
- /**
- * 页面显示时触发
- * 每次页面从后台切换到前台时执行
- */
onShow(() => {
- // TODO: 如果需要在页面显示时刷新数据,可以在这里添加逻辑
- // 例如:检查是否有数据更新,重新加载当前选中的数据
+
})
- // ==================== 业务逻辑方法 - 项目相关 ====================
+ const loadData = () => {
- /**
- * 加载项目数据
- * 从后端 API 获取项目列表
- * TODO: 实际项目中应该调用后端 API
- */
- const loadProjectData = () => {
- // 示例代码(调用后端 API):
- // Service.Request('/api/projects', 'GET').then(res => {
- // projectList.value = res.data
- // projectOptions.value = res.data
- // })
-
- // 当前使用模拟数据,已在初始化时赋值
- console.log('项目数据加载完成')
}
- /**
- * 处理项目选择变化事件
- * 当用户选择项目时触发
- * @param e picker 选择事件对象,包含 detail.value 属性表示选中的索引
- */
const handleProjectChange = (e: any) => {
- // 获取选中的项目索引
const index = e.detail.value
-
- // 更新选中项目的索引
selectedProjectIndex.value = index
-
- // 获取选中的项目
const selectedProject = projectList.value[index]
if (selectedProject) {
- // 更新选中项目的 ID
selectedProjectId.value = selectedProject.id
-
- // 清空之前的日期和学生选择
selectedDate.value = ''
- selectedStudentId.value = ''
- selectedStudentIndex.value = -1
- currentStudent.value = null
- segmentList.value = []
-
- // 加载该项目下的学生列表
+ selectedStudentIndexes.value = []
+ selectedStudentIds.value = []
+ tableData.value = []
loadProjectStudents(selectedProject.id)
}
-
- // TODO: 实际项目中可能需要调用 API 获取该项目的学生列表
}
- // ==================== 业务逻辑方法 - 日历相关 ====================
-
- /**
- * 处理日历月份切换事件
- * 当用户切换日历的月份时触发
- * @param e 切换事件对象,包含 year 和 month 属性
- */
const handleMonthSwitch = (e: any) => {
- // 更新当前年份
currentYear.value = e.year
-
- // 更新当前月份
currentMonth.value = e.month
-
- // 重新加载数据
- // 获取切换后月份的训练记录和日历标记
- loadMonthData()
-
- // 清空选中的日期
- // 因为切换了月份,之前选择的日期可能不再显示
+ loadData()
selectedDate.value = ''
-
- // 清空学生选择和数据
- selectedStudentId.value = ''
- selectedStudentIndex.value = -1
- currentStudent.value = null
- segmentList.value = []
-
- // TODO: 实际项目中应该调用 API 获取该月份的日历标记数据
+ selectedStudentIndexes.value = []
+ selectedStudentIds.value = []
+ tableData.value = []
}
- /**
- * 处理日历日期选择事件
- * 当用户点击日历上的某个日期时触发
- * @param e 选择事件对象,fulldate 属性包含完整的日期字符串
- */
const handleDateChange = (e: any) => {
- // 获取选择的完整日期,格式为 'YYYY-MM-DD'
const date = e.fulldate
-
- // 更新选中的日期
selectedDate.value = date
-
- // 清空学生选择
- selectedStudentId.value = ''
- selectedStudentIndex.value = -1
- currentStudent.value = null
- segmentList.value = []
-
- // TODO: 实际项目中可能需要调用 API 获取该日期的数据
- // 并根据日期加载该日期的学生列表
+ selectedStudentIndexes.value = []
+ selectedStudentIds.value = []
+ tableData.value = []
}
- /**
- * 加载月份数据
- * 根据当前年月加载日历标记数据
- */
- const loadMonthData = () => {
- // TODO: 实际项目中应该调用 API 获取该月份的日历标记
- // Service.Request('/api/segment/dates', 'GET', {
- // year: currentYear.value,
- // month: currentMonth.value,
- // projectId: selectedProjectId.value
- // }).then(res => {
- // selectedDates.value = res.data
- // })
-
- console.log(`加载 ${currentYear.value}年${currentMonth.value}月 的日历标记数据`)
- }
-
- // ==================== 业务逻辑方法 - 学生相关 ====================
-
- /**
- * 加载项目学生列表
- * 根据项目 ID 获取该项目的学生列表
- * @param projectId 项目 ID
- */
const loadProjectStudents = (projectId: string) => {
- // 从模拟数据中提取该项目的学生列表(去重)
- const records = mockStudentRecords[projectId] || []
- const uniqueStudents = new Map()
- records.forEach(record => {
- if (!uniqueStudents.has(record.student.id)) {
- uniqueStudents.set(record.student.id, record.student)
- }
- })
- studentList.value = Array.from(uniqueStudents.values())
- studentOptions.value = studentList.value
-
- console.log(`项目 ${projectId} 的学生列表加载完成,共 ${studentList.value.length} 位学生`)
+ const students = mockStudents[projectId] || []
+ studentList.value = students
+ console.log(`项目 ${projectId} 的学生列表加载完成,共 ${students.length} 位学生`)
}
- /**
- * 处理学生选择变化事件
- * 当用户选择学生时触发
- * @param e picker 选择事件对象,包含 detail.value 属性表示选中的索引
- */
- const handleStudentChange = (e: any) => {
- // 获取选中的学生索引
- const index = e.detail.value
-
- // 更新选中学生的索引
- selectedStudentIndex.value = index
-
- // 获取选中的学生
- const selectedStudent = studentList.value[index]
-
- if (selectedStudent) {
- // 更新选中学生的 ID
- selectedStudentId.value = selectedStudent.id
-
- // 加载学生数据(包括分段数据)
- loadStudentData(selectedProjectId.value, selectedStudentId.value, selectedDate.value)
- }
-
- // TODO: 实际项目中可能需要调用 API 获取学生的详细数据和分段记录
+ const selectAllStudents = () => {
+ selectedStudentIndexes.value = studentList.value.map((_, index) => index)
}
- /**
- * 加载学生数据
- * 根据项目 ID、学生 ID 和日期获取学生的详细信息和分段数据
- * @param projectId 项目 ID
- * @param studentId 学生 ID
- * @param date 记录日期
- */
- const loadStudentData = (projectId: string, studentId: string, date: string) => {
- // 从模拟数据中查找匹配的记录
- const records = mockStudentRecords[projectId] || []
- const record = records.find(r => r.student.id === studentId && r.date === date)
-
- if (record) {
- // 更新学生信息
- currentStudent.value = record.student
-
- // 更新分段列表(已按原顺序,通过计算属性转换为表格数据)
- segmentList.value = record.segments
-
- console.log(`学生数据加载完成:${record.student.name},共 ${record.segments.length} 个分段`)
+ const toggleStudentSelect = (index: number) => {
+ const idx = selectedStudentIndexes.value.indexOf(index)
+ if (idx > -1) {
+ selectedStudentIndexes.value.splice(idx, 1)
} else {
- // 未找到匹配的记录
- currentStudent.value = null
- segmentList.value = []
-
- Service.Msg('未找到该日期的记录数据')
+ selectedStudentIndexes.value.push(index)
}
-
- // TODO: 实际项目中应该调用后端 API
- // Service.Request(`/api/segments?projectId=${projectId}&studentId=${studentId}&date=${date}`, 'GET')
- // .then(res => {
- // currentStudent.value = res.data.student
- // segmentList.value = res.data.segments
- // })
}
- // ==================== 业务逻辑方法 - 分段相关 ====================
+ const confirmStudentSelect = () => {
+ selectedStudentIds.value = selectedStudentIndexes.value.map(index => studentList.value[index].id)
+ showStudentSelect.value = false
+ filterAndLoadData()
+ }
- /**
- * 处理分段表格单元格点击事件
- * 点击某分段单元格查看详情
- * @param event 点击事件对象,包含行索引、列索引、单元格数据等信息
- */
- const handleSegmentCellClick = (event: any) => {
- console.log('分段表格单元格点击事件:', event)
- // TODO: 可以跳转到分段详情页面或弹出详细信息
- // Service.GoPage('/pages/dataAnalyze/segmentDetail', { segmentId: event.rowData.id })
+ const closeStudentSelect = () => {
+ showStudentSelect.value = false
+ }
+
+ const filterAndLoadData = () => {
+ if (!selectedDate.value || selectedStudentIndexes.value.length === 0) {
+ tableData.value = []
+ return
+ }
+
+ const projectData = mockData[selectedProjectId.value]
+ if (!projectData) {
+ tableData.value = []
+ return
+ }
+
+ const dateData = projectData[selectedDate.value]
+ if (!dateData) {
+ tableData.value = []
+ return
+ }
+
+ const selectedStudentNames = selectedStudentIndexes.value.map(index => studentList.value[index].name)
+ const filteredData = dateData.filter(item => selectedStudentNames.includes(item.name))
+
+ tableData.value = filteredData.map(item => ({
+ date: selectedDate.value,
+ name: item.name,
+ projectName: item.projectName,
+ segment20: item.segment20.toFixed(3),
+ segment70: item.segment70.toFixed(3),
+ segment120: item.segment120.toFixed(3),
+ segment170: item.segment170.toFixed(3)
+ }))
}
+
\ No newline at end of file
diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue
index 427c482..c3acdbb 100644
--- a/src/pages/index/index.vue
+++ b/src/pages/index/index.vue
@@ -13,7 +13,7 @@
-
+
@@ -29,7 +29,7 @@
-
+
@@ -74,7 +74,7 @@
-
+
新增项目
@@ -84,7 +84,7 @@
项目列表
+ @click=" goPageFunc() ">
{{ project.name.charAt(0) }}
@@ -128,7 +128,9 @@
age : string
}[]
}
-
+
+ let currentIndex=ref(0)
+
// 模拟项目数据
const projects = ref([
{
@@ -175,10 +177,21 @@
const showTimingModal = ref(false)
const showDetailModal = ref(false)
const currentProject = ref(null)
-
+
+
+ // 生命周期
+ onMounted(() => {
+ // TODO: 实际应从接口获取项目列表
+ })
+
+ onUnmounted(() => {
+ // 清理
+ })
+
// 显示计时项目列表弹窗
- const showTimingProjectModal = () => {
+ const showTimingProjectModal = (index:any) => {
showTimingModal.value = true
+ currentIndex.value=index
}
// 关闭计时项目列表弹窗
@@ -200,7 +213,7 @@
const createSegmentProject = () => {
Service.GoPage('/pages/userFunc/setCourse')
}
-
+
// 显示项目详情
const showProjectDetail = (project : Project) => {
currentProject.value = project
@@ -213,15 +226,17 @@
showDetailModal.value = false
currentProject.value = null
}
-
- // 生命周期
- onMounted(() => {
- // TODO: 实际应从接口获取项目列表
- })
-
- onUnmounted(() => {
- // 清理
- })
+
+
+ const goPageFunc=()=>{
+ if(currentIndex.value==1){
+ Service.GoPage('/pages/userFunc/swiming')
+ }else{
+ Service.GoPage('/pages/userFunc/project')
+ }
+
+ }
+
\ No newline at end of file
diff --git a/src/pages/userFunc/student.vue b/src/pages/userFunc/student.vue
index 47e5c59..4681250 100644
--- a/src/pages/userFunc/student.vue
+++ b/src/pages/userFunc/student.vue
@@ -32,6 +32,10 @@
{{ student.gender }}
+
+ 出生日期:
+ {{ student.birthDate }}
+
年龄:
{{ student.age }}岁
@@ -84,18 +88,40 @@
性别
-
+
+
+
男
-
+
+
+
女
- 年龄
-
+ 出生日期
+
+
+
+
+ {{ formData.birthDate || '请选择出生日期' }}
+
+
+
+
+
+
+
+
+
+ 年龄
+ {{ calculateAge(formData.birthDate) }}
+ 岁
+
+
学校(选填)
@@ -128,16 +154,17 @@
name : string
gender : string
age : string
+ birthDate : string
school : string
address : string
}
// 学员列表
const students = ref([
- { id: '001', name: '张三', gender: '男', age: '12', school: '第一小学', address: '北京市朝阳区' },
- { id: '002', name: '李四', gender: '女', age: '13', school: '', address: '' },
- { id: '003', name: '王五', gender: '男', age: '11', school: '第二小学', address: '' },
- { id: '004', name: '赵六', gender: '女', age: '12', school: '', address: '上海市浦东新区' }
+ { id: '001', name: '张三', gender: '男', age: '12', birthDate: '2012-05-15', school: '第一小学', address: '北京市朝阳区' },
+ { id: '002', name: '李四', gender: '女', age: '13', birthDate: '2011-08-20', school: '', address: '' },
+ { id: '003', name: '王五', gender: '男', age: '11', birthDate: '2013-03-10', school: '第二小学', address: '' },
+ { id: '004', name: '赵六', gender: '女', age: '12', birthDate: '2012-11-25', school: '', address: '上海市浦东新区' }
])
// 弹窗状态
@@ -150,6 +177,7 @@
name: '',
gender: '男',
age: '',
+ birthDate: '',
school: '',
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) => {
editingStudent.value = student
@@ -173,6 +220,7 @@
name: student.name,
gender: student.gender,
age: student.age,
+ birthDate: student.birthDate,
school: student.school,
address: student.address
}
@@ -189,6 +237,7 @@
name: '',
gender: '男',
age: '',
+ birthDate: '',
school: '',
address: ''
}
@@ -201,8 +250,8 @@
return
}
- if (!formData.value.age.trim()) {
- Service.Msg('请输入年龄')
+ if (!formData.value.birthDate) {
+ Service.Msg('请选择出生日期')
return
}
@@ -212,7 +261,8 @@
id: Date.now().toString().slice(-6),
name: formData.value.name.trim(),
gender: formData.value.gender,
- age: formData.value.age.trim(),
+ age: formData.value.age,
+ birthDate: formData.value.birthDate,
school: formData.value.school.trim(),
address: formData.value.address.trim()
}
@@ -226,7 +276,8 @@
...students.value[index],
name: formData.value.name.trim(),
gender: formData.value.gender,
- age: formData.value.age.trim(),
+ age: formData.value.age,
+ birthDate: formData.value.birthDate,
school: formData.value.school.trim(),
address: formData.value.address.trim()
}
@@ -554,6 +605,101 @@
background-color: #fff;
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 {
@@ -562,17 +708,20 @@
.gender-option {
flex: 1;
- height: 88rpx;
+ height: 100rpx;
background-color: #f5f5f5;
- border-radius: 16rpx;
+ border-radius: 20rpx;
display: flex;
+ flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
- font-size: 28rpx;
+ font-size: 26rpx;
color: #666;
- transition: all 0.25s ease;
+ transition: all 0.3s ease;
border: 2rpx solid transparent;
+ position: relative;
+ overflow: hidden;
&:active {
transform: scale(0.96);
@@ -580,9 +729,25 @@
&.active {
font-weight: 600;
- background-color: #e6f7ff;
+ background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
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;
}
}
}
diff --git a/src/pages/userFunc/swiming.vue b/src/pages/userFunc/swiming.vue
index 1e306c8..67764d5 100644
--- a/src/pages/userFunc/swiming.vue
+++ b/src/pages/userFunc/swiming.vue
@@ -6,13 +6,6 @@
当前总用时
{{ formatTime(currentTime) }}
-
-
-
-
-
@@ -25,7 +18,7 @@
+ class="athlete-item" :class="{ finished: athlete.finished }">
{{ index+1 }}
{{ athlete.name }}
@@ -33,7 +26,7 @@
{{ formatTime(athlete.time) }}
- 最快: {{ formatTime(athlete.bestTime) }}
+ 最快: {{ athlete.bestTime !== null ? formatTime(athlete.bestTime) : '无' }}
@@ -49,18 +42,27 @@
-
-
-
+
+
+
+
+
+
+
+
+
@@ -76,12 +78,17 @@
添加学生
+
+
+ 修改组别
+
+
取消
-
+
+
+
+
+
+
+
+ 组别个数
+
+
+
+ 一组多少人
+
+
+
+
+
+
保存
-
+
@@ -189,15 +222,14 @@
time : number
bestTime : number | null
finished : boolean
- isFastest : boolean
}
// 选手列表
const athletes = ref([
- { id: '1', number: '01', name: '张三', gender: '男', age: '18', birthday: '2006-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, isFastest: false },
- { id: '3', number: '03', name: '王五', gender: '男', age: '19', birthday: '2005-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, 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 },
+ { 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 }
])
// 计时器状态
@@ -218,6 +250,11 @@
const showMoreModal = ref(false)
const showStopwatchModal = ref(false)
const showAddStudentModal = ref(false)
+ const showGroupModal = ref(false)
+
+ // 组别设置
+ const groupCount = ref('')
+ const groupSize = ref('')
// 新学生信息
const newStudentName = ref('')
@@ -293,6 +330,17 @@
// 记录下一个选手
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) {
Service.Msg('所有选手已完成')
return
@@ -302,14 +350,6 @@
athlete.finished = true
athlete.time = currentTime.value
- // 更新最快记录
- if (athlete.bestTime === null || athlete.time < athlete.bestTime) {
- athlete.bestTime = athlete.time
- }
-
- // 更新最快选手
- updateFastestAthlete()
-
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) => {
athlete.time = 0
athlete.finished = false
- athlete.isFastest = false
// 重新排序索引
reorderAthletes()
- updateFastestAthlete()
}
// 处理选手第一个按钮点击
@@ -358,14 +379,6 @@
athlete.finished = true
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)
if (allFinished) {
@@ -382,6 +395,24 @@
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 = () => {
stopTimer()
@@ -391,7 +422,6 @@
athletes.value.forEach(athlete => {
athlete.time = 0
athlete.finished = false
- athlete.isFastest = false
})
}
@@ -476,6 +506,37 @@
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 = () => {
if (!newStudentName.value.trim()) {
@@ -497,8 +558,7 @@
birthday: newStudentBirthday.value.trim(),
time: 0,
bestTime: null,
- finished: false,
- isFastest: false
+ finished: false
}
athletes.value.push(newAthlete)
@@ -514,7 +574,7 @@
stopwatchMode: stopwatchMode.value,
intervalTime: intervalTime.value
}
- Service.SetStorageCache('swimTimingData', data)
+ console.log(data);
Service.Msg('保存成功', 'success')
}
@@ -549,7 +609,7 @@
.total-time {
font-size: 80rpx;
font-weight: bold;
- color: #1890ff;
+ color: #ff0000;
}
}
@@ -652,8 +712,8 @@
.athlete-time {
font-size: 32rpx;
- font-weight: 600;
- color: #333333;
+ font-weight: bold;
+ color: #ff0000;
font-family: 'DIN Alternate', monospace;
}
@@ -680,60 +740,122 @@
/* 底部控制按钮 */
.control-buttons {
display: flex;
- gap: 20rpx;
+ gap: 24rpx;
justify-content: center;
- padding-bottom: env(safe-area-inset-bottom);
position: fixed;
bottom: 0;
left: 0;
right: 0;
- background-color: #fff;
- padding: 20rpx;
- padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
- border-top: 1rpx solid #f0f0f0;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, #ffffff 100%);
+ padding: 32rpx 40rpx;
+ padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
+ 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,
.pause-btn,
- .reset-all-btn {
+ .reset-all-btn,
+ .record-btn {
flex: 1;
- height: 88rpx;
- border-radius: 12rpx;
+ height: 100rpx;
+ border-radius: 20rpx;
border: none;
- font-size: 30rpx;
+ font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
- gap: 10rpx;
- max-width: 220rpx;
+ gap: 12rpx;
+ 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 {
- background-color: #52c41a;
+ background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
color: #ffffff;
+ box-shadow: 0 6rpx 20rpx rgba(82, 196, 26, 0.4);
- &:disabled {
- background-color: #ccc;
+ &:active {
+ transform: scale(0.96);
+ box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
}
}
.pause-btn {
- background-color: #fa541c;
+ background: linear-gradient(135deg, #fa541c 0%, #ff7a45 100%);
color: #ffffff;
+ box-shadow: 0 6rpx 20rpx rgba(250, 84, 28, 0.4);
- &:disabled {
- background-color: #ccc;
+ &:active {
+ transform: scale(0.96);
+ box-shadow: 0 2rpx 8rpx rgba(250, 84, 28, 0.3);
}
}
.reset-all-btn {
- background-color: #1890ff;
+ background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
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 {
- font-size: 30rpx;
+ font-size: 32rpx;
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,
- .add-student-modal {
+ .add-student-modal,
+ .group-modal {
position: fixed;
top: 50%;
left: 50%;
@@ -991,15 +1114,36 @@
.float-save-btn {
position: fixed;
right: 40rpx;
- bottom: 160rpx;
- width: 100rpx;
- height: 100rpx;
- background: linear-gradient(135deg, #1890ff, #40a9ff);
+ bottom: 180rpx;
+ width: 110rpx;
+ height: 110rpx;
+ background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
border-radius: 50%;
display: flex;
align-items: 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;
+ 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);
+ }
}
\ No newline at end of file