This commit is contained in:
Ls
2026-03-31 08:40:11 +08:00
parent b8c05d23ae
commit a4bcd5ae1f
12 changed files with 3883 additions and 1255 deletions

View File

@@ -46,22 +46,6 @@
</view>
</view>
<!-- ==================== 空状态提示 ==================== -->
<!-- 未选择项目时的提示 -->
<view class="hint-state" v-if="!selectedProjectId">
<view class="hint-icon">
<u-icon name="list" size="80" color="#52c41a"></u-icon>
</view>
<text class="hint-text">请先选择项目</text>
</view>
<!-- 无学生时的提示 -->
<view class="hint-state" v-else-if="projectStudents.length === 0">
<view class="hint-icon">
<u-icon name="account" size="80" color="#52c41a"></u-icon>
</view>
<text class="hint-text">该项目暂无学员数据</text>
</view>
<!-- ==================== 图表展示区域 ==================== -->
<!-- 仅在选择了至少一名学生后显示 -->
@@ -573,23 +557,7 @@
}
}
.hint-state {
margin: 0 20rpx;
background-color: #fff;
border-radius: 16rpx;
padding: 80rpx 40rpx;
text-align: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
.hint-icon {
margin-bottom: 24rpx;
}
.hint-text {
font-size: 28rpx;
color: #999;
}
}
.chart-section {
margin: 0 20rpx;

View File

@@ -8,6 +8,42 @@
</view>
</view>
<!-- 选择器区域 -->
<view class="selector-section">
<view class="selector-item" @click="showProjectPicker = true">
<text class="selector-label">项目名称</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 class="selector-item" @click="showStudentPicker = true">
<text class="selector-label">选择学生</text>
<view class="selector-value">
<text class="value-text">{{ selectedStudent }}</text>
<u-icon name="arrow-down" size="24" color="#999"></u-icon>
</view>
</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">
<uni-calendar
@@ -55,13 +91,7 @@
</sl-table>
</view>
<!-- 未选择日期提示 -->
<view class="hint-state" v-if="!selectedDate">
<view class="hint-icon">
<u-icon name="calendar" size="80" color="#faad14"></u-icon>
</view>
<text class="hint-text">请选择日期查看数据</text>
</view>
</view>
</template>
@@ -115,18 +145,46 @@
detail: string
}
const tableData = ref<TableDataItem[]>([{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 95, detail: '1900/2000' },
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 88, detail: '1760/2000' },
{ name: '王小明', projectName: '200米自由泳', plan: '1500米', completion: 100, detail: '1500/1500' }])
const tableData = ref<TableDataItem[]>([])
// 项目列表
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 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 selectedProject = ref('全部项目')
const selectedStudent = ref('全部学生')
const selectedProjectValue = ref('all')
const selectedStudentValues = ref<string[]>([])
// 选择器显示状态
const showProjectPicker = ref(false)
const showStudentPicker = ref(false)
const defaultStudentIndex = ref<number[]>([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 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(() => {
@@ -135,37 +193,44 @@
return Math.round(total / tableData.value.length)
})
// 模拟数据
// 完整模拟数据
const mockData: Record<string, TableDataItem[]> = {
'2026-03-15': [
'2026-03-28': [
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 95, detail: '1900/2000' },
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 88, detail: '1760/2000' },
{ name: '王小明', projectName: '200米自由泳', plan: '1500米', completion: 100, detail: '1500/1500' }
],
'2026-03-18': [
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 90, detail: '1800/2000' },
{ name: '张小明', projectName: '200米自由泳', plan: '1500米', completion: 88, detail: '1320/1500' },
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 92, detail: '1840/2000' },
{ name: '赵小芳', projectName: '100米自由泳', plan: '1800米', completion: 85, detail: '1530/1800' },
{ name: '王小明', projectName: '200米自由泳', plan: '1500米', completion: 95, detail: '1425/1500' }
{ 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-20': [
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 87, detail: '1740/2000' },
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 90, detail: '1800/2000' }
],
'2026-03-22': [
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 92, detail: '1840/2000' },
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 88, detail: '1760/2000' },
{ name: '赵小芳', projectName: '100米自由泳', plan: '1800米', completion: 90, detail: '1620/1800' },
{ name: '王小明', projectName: '200米自由泳', plan: '1500米', completion: 98, detail: '1470/1500' },
{ name: '陈小刚', projectName: '100米自由泳', plan: '2000米', completion: 75, detail: '1500/2000' }
],
'2026-03-25': [
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 85, detail: '1700/2000' },
'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: '100米自由泳', plan: '1800米', completion: 88, detail: '1584/1800' }
{ 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: '训练' }
])
onLoad(() => {
loadData()
})
@@ -196,16 +261,86 @@
// 日期选择处理
const handleDateChange = (e: any) => {
// 点击日期后的处理
const date = e.fulldate
selectedDate.value = date
filterAndLoadData()
}
// 加载该日期的数据
if (mockData[date]) {
tableData.value = mockData[date]
// 项目选择确认
const handleProjectConfirm = (e: any) => {
const item = e.value[0]
selectedProject.value = item.label
selectedProjectValue.value = item.value
showProjectPicker.value = false
filterAndLoadData()
}
// 学生选择确认
const handleStudentConfirm = (e: any) => {
const items = e.value
selectedStudentValues.value = items.map((item: any) => item.value)
if (selectedStudentValues.value.length === 0) {
selectedStudent.value = '未选择'
} else if (selectedStudentValues.value.includes('all')) {
selectedStudent.value = '全部学生'
} else {
tableData.value = []
const selectedNames = items.map((item: any) => item.label)
selectedStudent.value = selectedNames.length > 2
? `${selectedNames.slice(0, 2).join(', ')}${selectedNames.length}`
: selectedNames.join(', ')
}
defaultStudentIndex.value = e.indexs
showStudentPicker.value = false
filterAndLoadData()
}
// 过滤并加载数据
const filterAndLoadData = () => {
if (!selectedDate.value) {
tableData.value = []
return
}
const dateData = mockData[selectedDate.value]
if (!dateData) {
tableData.value = []
return
}
let filteredData = [...dateData]
// 按项目过滤
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>
@@ -241,6 +376,47 @@
}
}
/* 选择器区域 */
.selector-section {
background-color: #fff;
margin: 0 20rpx 20rpx;
border-radius: 16rpx;
padding: 24rpx;
display: flex;
gap: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
.selector-item {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 24rpx;
background-color: #f8f8f8;
border-radius: 12rpx;
.selector-label {
font-size: 28rpx;
color: #666;
margin-right: 16rpx;
}
.selector-value {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
.value-text {
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-right: 8rpx;
}
}
}
}
/* 日历组件包装 */
.calendar-wrapper {
background-color: #fff;

View File

@@ -23,22 +23,6 @@
</picker>
</view>
<!-- ==================== 空状态提示 ==================== -->
<!-- 未选择项目时的提示 -->
<view class="hint-state" v-if="!selectedProjectId">
<view class="hint-icon">
<u-icon name="list" size="80" color="#eb2f96"></u-icon>
</view>
<text class="hint-text">请先选择项目</text>
</view>
<!-- 无数据时的提示 -->
<view class="hint-state" v-else-if="gradeList.length === 0">
<view class="hint-icon">
<u-icon name="file-text" size="80" color="#eb2f96"></u-icon>
</view>
<text class="hint-text">该项目暂无排名数据</text>
</view>
<!-- ==================== 排名列表区域 ==================== -->
<!-- 仅在选择了项目且有数据时显示 -->
@@ -70,11 +54,6 @@
<text class="rank-number">{{ index + 1 }}</text>
</view>
<!-- 学生头像 -->
<view class="student-avatar">
<text class="avatar-text">{{ item.name.charAt(0) }}</text>
</view>
<!-- 学生信息 -->
<view class="student-info">
<text class="student-name">{{ item.name }}</text>
@@ -308,9 +287,23 @@
.project-select-section {
background-color: #fff;
margin: 0 20rpx 20rpx;
border-radius: 16rpx;
border-radius: 20rpx;
padding: 28rpx;
box-shadow: 0 2rpxrpx 12rpx rgba(0, 0, 0, 0.05);
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
position: relative;
overflow: hidden;
transition: all 0.3s ease;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 6rpx;
// background: linear-gradient(180deg, #1890ff 0%, #096dd9 100%);
border-radius: 20rpx 0 0 20rpx;
}
.picker-wrapper {
display: flex;
@@ -323,8 +316,9 @@
transition: all 0.3s ease;
&:active {
background-color: #f0f0f0;
border-color: #eb2f96;
background-color: #fff0f5;
border-color: #1890ff;
transform: scale(0.98);
}
.picker-text {
@@ -334,24 +328,7 @@
}
}
/* ==================== 提示状态容器 ==================== */
.hint-state {
margin: 0 20rpx;
background-color: #fff;
border-radius: 16rpx;
padding: 80rpx 40rpx;
text-align: center;
box-shadow: 0 2rpxrpx 12rpx rgba(0, 0, 0, 0.05);
.hint-icon {
margin-bottom: 24rpx;
}
.hint-text {
font-size: 28rpx;
color: #999;
}
}
/* ==================== 排名列表区域 ==================== */
.ranking-section {
@@ -360,13 +337,26 @@
// 统计卡片
.stats-card {
background-color: #fff;
border-radius: 16rpx;
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
justify-content: space-around;
box-shadow: 0 2rpxrpx 12rpx rgba(0, 0, 0, 0.05);
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 6rpx;
// background: linear-gradient(180deg, #1890ff 0%, #096dd9 100%);
border-radius: 20rpx 0 0 20rpx;
}
.stat-item {
text-align: center;
@@ -381,7 +371,7 @@
.stat-value {
font-size: 32rpx;
font-weight: 700;
color: #eb2f96;
color: #1890ff;
}
}
@@ -398,32 +388,36 @@
// 排名项
.ranking-item {
background-color: #fff;
border-radius: 16rpx;
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 16rpx;
display: flex;
align-items: center;
gap: 20rpx;
box-shadow: 0 2rpxrpx 12rpx rgba(0, 0, 0, 0.05);
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 6rpx;
// background: linear-gradient(180deg, #1890ff 0%, #096dd9 100%);
opacity: 0;
transition: opacity 0.3s ease;
border-radius: 20rpx 0 0 20rpx;
}
&:active {
transform: scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(235, 47, 150, 0.15);
}
// 前三名特殊样式
&.top-three {
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 6rpx;
background: linear-gradient(180deg, #eb2f96 0%, #f759ab 100%);
border-radius: 16rpx 0 0 16rpx;
opacity: 1;
}
}
@@ -437,7 +431,7 @@
justify-content: center;
flex-shrink: 0;
background-color: #f0f0f0;
box-shadow: 0 2rpxrpx 8rpx rgba(0, 0, 0, 0.1);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
.rank-number {
font-size: 24rpx;
@@ -448,7 +442,7 @@
// 前三名特殊颜色
&.rank-1 {
background: linear-gradient(135deg, #ffd700 0%, #ffec3d 100%);
box-shadow: 0 2rpxrpx 8rpx rgba(255, 215, 0, 0.4);
box-shadow: 0 2rpx 8.6rpx rgba(255, 215, 0, 0.4);
.rank-number {
color: #fff;
@@ -458,7 +452,7 @@
&.rank-2 {
background: linear-gradient(135deg, #c0c0c0 0%, #d9d9d9 100%);
box-shadow: 0 2rpxrpx 8rpx rgba(192, 192, 192, 0.4);
box-shadow: 0 2rpx 8rpx rgba(192, 192, 192, 0.4);
.rank-number {
color: #fff;
@@ -467,7 +461,7 @@
&.rank-3 {
background: linear-gradient(135deg, #cd7f32 0%, #e6963d 100%);
box-shadow: 0 2rpxrpx 8rpx rgba(205, 127, 50, 0.4);
box-shadow: 0 2rpx 8rpx rgba(205, 127, 50, 0.4);
.rank-number {
color: #fff;
@@ -479,13 +473,13 @@
.student-avatar {
width: 70rpx;
height: 70rpx;
background: linear-gradient(135deg, #eb2f96 0%, #f759ab 100%);
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-shadow: 0 2rpxrpx 8rpx rgba(235, 47, 150, 0.3);
box-shadow: 0 2rpx 8rpx rgba(235, 47, 150, 0.3);
.avatar-text {
font-size: 28rpx;
@@ -509,7 +503,7 @@
.student-speed {
font-size: 26rpx;
color: #eb2f96;
color: #1890ff;
font-weight: 500;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -58,14 +58,7 @@
</sl-table>
</view>
<!-- 未选择日期提示 -->
<!-- 未选择日期时显示提示信息 -->
<view class="hint-state" v-if="!selectedDate">
<view class="hint-icon">
<u-icon name="calendar" size="80" color="#1890ff"></u-icon>
</view>
<text class="hint-text">请选择日期查看数据</text>
</view>
</view>
</template>

File diff suppressed because it is too large Load Diff

View File

@@ -152,37 +152,36 @@
/* 用户信息卡片 */
.user-card {
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 50%, #096dd9 100%);
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
border-radius: 28rpx;
padding: 40rpx 30rpx;
margin-bottom: 24rpx;
display: flex;
align-items: center;
gap: 20rpx;
box-shadow: 0 12rpx 32rpx rgba(24, 144, 255, 0.35),
0 4rpx 12rpx rgba(24, 144, 255, 0.2);
box-shadow: 0 8rpx 24rpx rgba(24, 144, 255, 0.25);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: -60%;
right: -40%;
width: 400rpx;
height: 400rpx;
background: radial-gradient(circle, rgba(255, 255, 255, 0.18) 0%, transparent 70%);
top: -40%;
right: -30%;
width: 300rpx;
height: 300rpx;
background: radial-gradient(circle, rgba(255, 255, 255, 0.15) 0%, transparent 70%);
pointer-events: none;
}
&::after {
content: '';
position: absolute;
bottom: -40%;
bottom: -30%;
left: -20%;
width: 300rpx;
height: 300rpx;
background: radial-gradient(circle, rgba(255, 255, 255, 0.12) 0%, transparent 70%);
width: 250rpx;
height: 250rpx;
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
pointer-events: none;
}
}

View File

@@ -55,7 +55,7 @@
</view>
<!-- 分段数据 -->
<view class="function-card segment-card" @click="goToSegment">
<view class="function-card segment-card" @click="Service.GoPage('/pages/dataAnalyze/paragraphAnalyze')">
<view class="card-icon segment-icon">
<u-icon name="list" size="36" color="#fff"></u-icon>
</view>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,902 @@
<template>
<view class="course-container">
<!-- 表单区域 -->
<view class="form-section">
<!-- 项目名称 -->
<view class="form-card">
<view class="form-title">项目信息</view>
<view class="form-group">
<text class="form-label">项目名称</text>
<input class="form-input" v-model="courseData.projectName" placeholder="请输入项目名称" placeholder-class="input-placeholder" />
</view>
</view>
<!-- 出发方式 -->
<view class="form-card">
<view class="form-title">出发方式</view>
<view class="radio-group">
<view class="radio-item" :class="{ active: courseData.startType === 'together' }" @click="courseData.startType = 'together'">
<view class="radio-icon">
<view v-if="courseData.startType === 'together'" class="radio-inner"></view>
</view>
<text>一起出发</text>
</view>
<view class="radio-item" :class="{ active: courseData.startType === 'interval' }" @click="courseData.startType = 'interval'">
<view class="radio-icon">
<view v-if="courseData.startType === 'interval'" class="radio-inner"></view>
</view>
<text>间隔出发</text>
</view>
</view>
<view v-if="courseData.startType === 'interval'" class="interval-input-wrapper">
<text class="interval-label">间隔时间</text>
<view class="interval-input">
<input class="number-input" v-model="courseData.intervalSeconds" type="digit" placeholder="请输入秒数" />
<text class="unit-text"></text>
</view>
</view>
</view>
<!-- 泳道设置 -->
<view class="form-card">
<view class="form-title">泳道设置</view>
<view class="radio-group">
<view class="radio-item" :class="{ active: courseData.laneType === 'single' }" @click="selectSingleLane">
<view class="radio-icon">
<view v-if="courseData.laneType === 'single'" class="radio-inner"></view>
</view>
<text>一个泳道</text>
</view>
<view class="radio-item" :class="{ active: courseData.laneType === 'multi' }" @click="selectMultiLane">
<view class="radio-icon">
<view v-if="courseData.laneType === 'multi'" class="radio-inner"></view>
</view>
<text>多个泳道</text>
</view>
</view>
<view v-if="courseData.laneType === 'multi'" class="multi-lane-options">
<view class="sub-option-item" :class="{ active: courseData.multiLaneMode === 'onePerson' }" @click="courseData.multiLaneMode = 'onePerson'">
<view class="sub-option-icon">
<view v-if="courseData.multiLaneMode === 'onePerson'" class="option-inner"></view>
</view>
<text>一个泳道一个人</text>
</view>
<view class="sub-option-item" :class="{ active: courseData.multiLaneMode === 'multiPerson' }" @click="courseData.multiLaneMode = 'multiPerson'">
<view class="sub-option-icon">
<view v-if="courseData.multiLaneMode === 'multiPerson'" class="option-inner"></view>
</view>
<text>一个泳道几个人</text>
</view>
<view v-if="courseData.multiLaneMode === 'multiPerson'" class="multi-person-input-wrapper">
<text class="multi-label">每个泳道人数</text>
<view class="multi-input">
<input class="number-input" v-model="courseData.lanePersonCount" type="number" placeholder="请输入人数" />
<text class="unit-text"></text>
</view>
</view>
</view>
</view>
<!-- 学生列表 -->
<view class="form-card">
<view class="student-header">
<view class="header-left">
<text class="form-title">选择学生</text>
<text class="student-count">已选({{ selectedStudentIds.length }}/{{ allStudents.length }})</text>
</view>
<view class="header-actions">
<view class="select-all-btn" @click="toggleSelectAll">
<view class="checkbox-icon" :class="{ checked: allSelected }">
<u-icon v-if="allSelected" name="checkmark" size="14" color="#fff"></u-icon>
</view>
<text class="select-all-text">{{ allSelected ? '取消全选' : '全选' }}</text>
</view>
</view>
</view>
<view v-if="loading" class="loading-state">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="allStudents.length === 0" class="empty-student-state">
<view class="empty-icon">
<u-icon name="account" size="48" color="#ddd"></u-icon>
</view>
<text class="empty-text">暂无学生</text>
<text class="empty-desc">请先在学员管理中添加学生</text>
</view>
<view v-else class="student-list">
<view v-for="student in allStudents" :key="student.id" class="student-item" @click="toggleStudentSelect(student.id)">
<view class="student-checkbox" :class="{ checked: selectedStudentIds.includes(student.id) }">
<u-icon v-if="selectedStudentIds.includes(student.id)" name="checkmark" size="14" color="#fff"></u-icon>
</view>
<view class="student-avatar">
<text class="avatar-text">{{ student.name.charAt(0) }}</text>
</view>
<view class="student-info">
<view class="student-name">{{ student.name }}</view>
<view class="student-meta">
<text class="gender-badge" :class="student.gender === '男' ? 'male' : 'female'">
{{ student.gender }}
</text>
<text class="age-text">{{ student.age }}</text>
</view>
</view>
</view>
</view>
<!-- 已选学生预览 -->
<view v-if="selectedStudents.length > 0" class="selected-preview">
<view class="preview-header">
<text class="preview-title">已选学生</text>
</view>
<view class="preview-list">
<view v-for="(student, index) in selectedStudents" :key="student.id" class="preview-item">
<text class="preview-index">{{ index + 1 }}</text>
<text class="preview-name">{{ student.name }}</text>
<view class="preview-remove" @click.stop="removeSelectedStudent(student.id)">
<u-icon name="close" size="14" color="#ff4d4f"></u-icon>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 底部按钮区域 -->
<view class="bottom-actions">
<view class="action-buttons">
<button class="cancel-btn" @click="goBack">取消</button>
<button class="confirm-btn" @click="confirmCreate">创建项目</button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { Service } from '@/Service/Service'
// 学生类型
interface Student {
id: string
name: string
gender: string
age: string
school?: string
address?: string
}
// 课程数据
const courseData = ref({
projectName: '',
startType: 'together', // together | interval
intervalSeconds: '',
intervalSeconds: '',
laneType: 'single', // single | multi
multiLaneMode: 'onePerson', // onePerson | multiPerson
lanePersonCount: ''
})
// 所有学生列表
const allStudents = ref<Student[]>([])
// 已选学生ID列表
const selectedStudentIds = ref<string[]>([])
// 加载状态
const loading = ref(false)
// 是否全选
const allSelected = computed(() => {
return allStudents.value.length > 0 && allStudents.value.every(s => selectedStudentIds.value.includes(s.id))
})
// 已选学生列表
const selectedStudents = computed(() => {
return allStudents.value.filter(s => selectedStudentIds.value.includes(s.id))
})
// 选择单泳道
const selectSingleLane = () => {
courseData.value.laneType = 'single'
courseData.value.multiLaneMode = 'onePerson'
}
// 选择多泳道
const selectMultiLane = () => {
courseData.value.laneType = 'multi'
}
// 获取学生列表(模拟接口)
const getStudentList = async () => {
loading.value = true
try {
// TODO: 实际项目中应从接口获取
// const res = await Service.Request('/api/students', 'GET', {})
// allStudents.value = res.data
// 模拟接口延迟
await new Promise(resolve => setTimeout(resolve, 500))
// 假数据
allStudents.value = [
{ id: '001', name: '张三', gender: '男', age: '12', school: '第一小学' },
{ id: '002', name: '李四', gender: '女', age: '13', school: '第二小学' },
{ id: '003', name: '王五', gender: '男', age: '11', school: '第一小学' },
{ id: '004', name: '赵六', gender: '女', age: '12', school: '第三小学' },
{ id: '005', name: '钱七', gender: '男', age: '14', school: '第二小学' },
{ id: '006', name: '孙八', gender: '女', age: '10', school: '第一小学' },
{ id: '007', name: '周九', gender: '男', age: '13', school: '第四小学' },
{ id: '008', name: '吴十', gender: '女', age: '12', school: '第二小学' }
]
} catch (error) {
Service.Msg('获取学生列表失败')
console.error(error)
} finally {
loading.value = false
}
}
// 切换学生选中状态
const toggleStudentSelect = (id: string) => {
const index = selectedStudentIds.value.indexOf(id)
if (index > -1) {
selectedStudentIds.value.splice(index, 1)
} else {
selectedStudentIds.value.push(id)
}
}
// 全选/取消全选
const toggleSelectAll = () => {
if (allSelected.value) {
// 取消全选
selectedStudentIds.value = []
} else {
// 全选
selectedStudentIds.value = allStudents.value.map(s => s.id)
}
}
// 移除已选学生
const removeSelectedStudent = (id: string) => {
const index = selectedStudentIds.value.indexOf(id)
if (index > -1) {
selectedStudentIds.value.splice(index, 1)
}
}
// 返回
const goBack = () => {
Service.GoPageBack()
}
// 创建项目
const confirmCreate = () => {
if (!courseData.value.projectName.trim()) {
Service.Msg('请输入项目名称')
return
}
if (courseData.value.startType === 'interval' && !courseData.value.intervalSeconds.trim()) {
Service.Msg('请输入间隔秒数')
return
}
if (courseData.value.laneType === 'multi' && courseData.value.multiLaneMode === 'multiPerson' && !courseData.value.lanePersonCount.trim()) {
Service.Msg('请输入每个泳道人数')
return
}
if (selectedStudentIds.value.length === 0) {
Service.Msg('请至少选择一位学生')
return
}
// TODO: 调用API创建项目
console.log('创建项目数据:', {
course: courseData.value,
studentIds: selectedStudentIds.value,
students: selectedStudents.value
})
Service.Msg('创建成功', 'success')
setTimeout(() => {
Service.GoPageBack()
}, 1500)
}
// 页面加载时获取学生列表
onMounted(() => {
getStudentList()
})
</script>
<style lang="scss" scoped>
page {
background-color: #f5f5f5;
}
.course-container {
min-height: 100vh;
padding-bottom: 140rpx;
}
/* 表单区域 */
.form-section {
padding: 20rpx;
}
.form-card {
background-color: #fff;
border-radius: 24rpx;
padding: 32rpx 28rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
.form-title {
font-size: 32rpx;
font-weight: 700;
color: #333;
margin-bottom: 28rpx;
display: block;
}
}
/* 表单输入 */
.form-group {
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 14rpx;
}
.form-input {
width: 100%;
height: 88rpx;
background-color: #f5f5f5;
border-radius: 16rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #333;
transition: all 0.2s ease;
&:focus {
background-color: #fff;
box-shadow: 0 0 0 4rpx rgba(24, 144, 255, 0.15);
}
}
}
/* 单选按钮组 */
.radio-group {
display: flex;
gap: 20rpx;
margin-bottom: 24rpx;
.radio-item {
flex: 1;
height: 88rpx;
background-color: #f5f5f5;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
font-size: 28rpx;
color: #666;
transition: all 0.25s ease;
border: 2rpx solid transparent;
&:active {
transform: scale(0.96);
}
&.active {
font-weight: 600;
background-color: #e6f7ff;
border-color: #1890ff;
color: #1890ff;
}
.radio-icon {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 3rpx solid #d9d9d9;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
&.active .radio-icon {
border-color: #1890ff;
background-color: #1890ff;
}
.radio-inner {
width: 12rpx;
height: 12rpx;
background-color: #fff;
border-radius: 50%;
}
}
}
/* 间隔时间输入 */
.interval-input-wrapper {
background-color: #f5f5f5;
border-radius: 16rpx;
padding: 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
.interval-label {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.interval-input {
flex: 1;
margin-left: 24rpx;
display: flex;
align-items: center;
background-color: #fff;
border-radius: 12rpx;
padding: 0 20rpx;
height: 72rpx;
.number-input {
flex: 1;
font-size: 28rpx;
color: #333;
}
.unit-text {
font-size: 24rpx;
color: #999;
margin-left: 12rpx;
}
}
}
/* 多泳道选项 */
.multi-lane-options {
padding-top: 20rpx;
.sub-option-item {
padding: 20rpx 24rpx;
background-color: #f5f5f5;
border-radius: 16rpx;
display: flex;
align-items: center;
gap: 12rpx;
font-size: 26rpx;
color: #666;
margin-bottom: 16rpx;
transition: all 0.2s ease;
border: 2rpx solid transparent;
&:active {
transform: scale(0.98);
}
&.active {
font-weight: 600;
background-color: #e6f7ff;
border-color: #1890ff;
color: #1890ff;
}
&:last-child {
margin-bottom: 0;
}
.sub-option-icon {
width: 28rpx;
height: 28rpx;
border-radius: 50%;
border: 2rpx solid #d9d9d9;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
&.active .sub-option-icon {
border-color: #1890ff;
background-color: #1890ff;
}
.option-inner {
width: 10rpx;
height: 10rpx;
background-color: #fff;
border-radius: 50%;
}
}
}
/* 多泳道人数输入 */
.multi-person-input-wrapper {
margin-top: 20rpx;
background-color: #f5f5f5;
border-radius: 16rpx;
padding: 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
.multi-label {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.multi-input {
flex: 1;
margin-left: 24rpx;
display: flex;
align-items: center;
background-color: #fff;
border-radius: 12rpx;
padding: 0 20rpx;
height: 72rpx;
.number-input {
flex: 1;
font-size: 28rpx;
color: #333;
}
.unit-text {
font-size: 24rpx;
color: #999;
margin-left: 12rpx;
}
}
}
/* 学生列表头部 */
.student-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
.header-left {
display: flex;
align-items: center;
gap: 8rpx;
.student-count {
font-size: 26rpx;
color: #999;
font-weight: 500;
}
}
.header-actions {
.select-all-btn {
display: flex;
align-items: center;
gap: 8rpx;
padding: 8rpx 16rpx;
border-radius: 12rpx;
transition: all 0.2s ease;
&:active {
transform: scale(0.96);
}
.checkbox-icon {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 2rpx solid #d9d9d9;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
&.checked {
border-color: #1890ff;
background-color: #1890ff;
}
}
.select-all-text {
font-size: 24rpx;
color: #666;
}
}
}
}
/* 加载状态 */
.loading-state {
padding: 80rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #f0f0f0;
border-top-color: #1890ff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20rpx;
}
.loading-text {
font-size: 26rpx;
color: #999;
}
}
/* 空状态 */
.empty-student-state {
padding: 60rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
.empty-icon {
margin-bottom: 20rpx;
opacity: 0.6;
}
.empty-text {
font-size: 28rpx;
font-weight: 600;
color: #666;
margin-bottom: 10rpx;
}
.empty-desc {
font-size: 24rpx;
color: #999;
}
}
/* 学生列表 */
.student-list {
display: flex;
flex-direction: column;
gap: 12rpx;
.student-item {
display: flex;
align-items: center;
gap: 16rpx;
padding: 20rpx;
background-color: #f5f5f5;
border-radius: 16rpx;
transition: all 0.2s ease;
&:active {
transform: scale(0.98);
background-color: #e8e8e8;
}
.student-checkbox {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
border: 2rpx solid #d9d9d9;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: all 0.2s ease;
&.checked {
border-color: #1890ff;
background-color: #1890ff;
}
}
.student-avatar {
width: 64rpx;
height: 64rpx;
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
border-radius: 14rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
.avatar-text {
color: #fff;
font-size: 28rpx;
font-weight: 700;
}
}
.student-info {
flex: 1;
min-width: 0;
.student-name {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 6rpx;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.student-meta {
display: flex;
align-items: center;
gap: 12rpx;
.gender-badge {
font-size: 22rpx;
padding: 4rpx 10rpx;
border-radius: 8rpx;
font-weight: 500;
&.male {
color: #1890ff;
background-color: #e6f7ff;
}
&.female {
color: #fa8c16;
background-color: #fff7e6;
}
}
.age-text {
font-size: 22rpx;
color: #999;
}
}
}
}
}
/* 已选学生预览 */
.selected-preview {
margin-top: 32rpx;
padding-top: 24rpx;
border-top: 1rpx solid #f0f0f0;
.preview-header {
margin-bottom: 16rpx;
.preview-title {
font-size: 26rpx;
font-weight: 600;
color: #666;
}
}
.preview-list {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
.preview-item {
display: flex;
align-items: center;
gap: 8rpx;
padding: 10rpx 16rpx;
background-color: #e6f7ff;
border-radius: 12rpx;
.preview-index {
width: 24rpx;
height: 24rpx;
background-color: #1890ff;
color: #fff;
font-size: 18rpx;
font-weight: 600;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.preview-name {
font-size: 26rpx;
color: #1890ff;
font-weight: 500;
}
.preview-remove {
width: 32rpx;
height: 32rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
&:active {
transform: scale(0.9);
}
}
}
}
/* 底部操作按钮 */
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid #f0f0f0;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
z-index: 99;
.action-buttons {
display: flex;
gap: 16rpx;
}
}
.cancel-btn,
.confirm-btn {
flex: 1;
height: 88rpx;
border-radius: 16rpx;
border: none;
font-size: 30rpx;
font-weight: 600;
transition: all 0.25s ease;
}
.cancel-btn {
background-color: #f5f5f5;
color: #666;
&:active {
background-color: #e8e8e8;
transform: scale(0.96);
}
}
.confirm-btn {
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
color: #fff;
box-shadow: 0 6rpx 16rpx rgba(24, 144, 255, 0.35);
&:active {
transform: scale(0.96);
box-shadow: 0 3rpx 8rpx rgba(24, 144, 255, 0.25);
}
}
/* 动画 */
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>

View File

@@ -24,19 +24,19 @@
</view>
<view class="athletes-list">
<view v-for="(athlete, index) in athletes" :key="athlete.id" @longpress="deleStu(athlete.id)" class="athlete-item"
:class="{ finished: athlete.finished, fastest: athlete.isFastest }">
<view v-for="(athlete, index) in athletes" :key="athlete.id" @longpress="deleStu(athlete.id)"
class="athlete-item" :class="{ finished: athlete.finished, fastest: athlete.isFastest }">
<view class="athlete-number">{{ index+1 }}</view>
<view class="athlete-info">
<text class="athlete-name">{{ athlete.name }}</text>
<text class="athlete-lane">{{ athlete.lane }}</text>
<text class="athlete-lane">{{ athlete.gender }} · {{ athlete.age }}</text>
</view>
<view class="time-wrapper">
<text class="athlete-time">{{ formatTime(athlete.time) }}</text>
<text v-if="athlete.bestTime" class="best-time">最快: {{ formatTime(athlete.bestTime) }}</text>
</view>
<view class="" style="display: flex;align-items: center; gap: 10rpx;">
<view v-if="stopwatchMode=='together'" @click="handleAthleteFirstButton(athlete, index)">
<view @click="handleAthleteFirstButton(athlete, index)">
<u-icon name="pause-circle" bold="true" size="22" color="#1890ff"></u-icon>
</view>
<view @click="resetAthleteTime(athlete)">
@@ -128,12 +128,34 @@
</view>
<view class="modal-content">
<view class="form-group">
<text class="form-label">学生姓名</text>
<input class="form-input" v-model="newStudentName" placeholder="请输入学生姓名" type="text" />
<text class="form-label">姓名</text>
<input class="form-input" v-model="newStudentName" placeholder="请输入姓名" type="text" />
</view>
<view class="form-group">
<text class="form-label">序号</text>
<input class="form-input" v-model="newStudentLane" placeholder="请输入序号" type="text" />
<text class="form-label">性别</text>
<radio-group class="radio-group" @change="onGenderChange">
<label class="radio-item">
<radio value="男" :checked="newStudentGender === '男'" color="#1890ff" />
<text class="radio-text"></text>
</label>
<label class="radio-item">
<radio value="女" :checked="newStudentGender === '女'" color="#1890ff" />
<text class="radio-text"></text>
</label>
</radio-group>
</view>
<view class="form-group">
<text class="form-label">出生日期</text>
<view class="date-picker-wrapper" @click="openDatePicker">
<view class="date-value" :class="{ placeholder: !newStudentBirthday }">
{{ Service.formatDate(newStudentBirthday,2) || '请选择出生日期' }}
</view>
<u-icon name="calendar" size="20" color="#999"></u-icon>
</view>
</view>
<view class="form-group">
<text class="form-label">年龄</text>
<input class="form-input" v-model="newStudentAge" placeholder="自动计算" type="text" disabled />
</view>
</view>
<view class="modal-footer">
@@ -146,6 +168,9 @@
<view class="float-save-btn" style="color: #fff; font-size: 24rpx;" @click="saveData">
保存
</view>
<!-- 日期选择器 -->
<u-datetime-picker v-model="newStudentBirthday" :show="showDatePicker" mode="date" @confirm="onDateConfirm" @cancel="showDatePicker = false" />
</view>
</template>
@@ -158,7 +183,9 @@
id : string
number : string
name : string
lane : string
gender : string
age : string
birthday : string
time : number
bestTime : number | null
finished : boolean
@@ -167,10 +194,10 @@
// 选手列表
const athletes = ref<Athlete[]>([
{ id: '1', number: '01', name: '张三', lane: '第一泳道', time: 0, bestTime: null, finished: false, isFastest: false },
{ id: '2', number: '02', name: '李四', lane: '第二泳道', time: 0, bestTime: null, finished: false, isFastest: false },
{ id: '3', number: '03', name: '王五', lane: '第三泳道', time: 0, bestTime: null, finished: false, isFastest: false },
{ id: '4', number: '04', name: '赵六', lane: '第四泳道', 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, 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 }
])
// 计时器状态
@@ -194,24 +221,30 @@
// 新学生信息
const newStudentName = ref('')
const newStudentLane = ref('')
const newStudentGender = ref('')
const newStudentBirthday = ref('')
const newStudentAge = ref('')
// 日期选择器相关
const showDatePicker = ref(false)
const currentDate = ref('')
// 计算已完成的选手
const finishedAthletes = computed(() => {
return athletes.value.filter(a => a.finished)
})
const deleStu = (id:any) => {
const deleStu = (id : any) => {
uni.showModal({
title: '提示', // 对话框标题
content: '是否删除该学生', // 显示的内容
showCancel: true, // 是否显示取消按钮
success: function (res) {
if (res.confirm) {
let index= athletes.value.findIndex((item)=>{
return item.id==id
let index = athletes.value.findIndex((item) => {
return item.id == id
})
athletes.value.splice(index,1)
athletes.value.splice(index, 1)
} else if (res.cancel) {
}
@@ -317,34 +350,29 @@
// 处理选手第一个按钮点击
const handleAthleteFirstButton = (athlete : Athlete, index : number) => {
// 如果是一起出发模式,记录该选手时间
if (stopwatchMode.value === 'together') {
if (athlete.finished) {
Service.Msg('该选手已记录')
return
}
if (athlete.finished) {
Service.Msg('该选手已记录')
return
}
athlete.finished = true
athlete.time = currentTime.value
athlete.finished = true
athlete.time = currentTime.value
// 更新最快记录
if (athlete.bestTime === null || athlete.time < athlete.bestTime) {
athlete.bestTime = athlete.time
}
// 更新最快记录
if (athlete.bestTime === null || athlete.time < athlete.bestTime) {
athlete.bestTime = athlete.time
}
// 更新最快选手
updateFastestAthlete()
// 更新最快选手
updateFastestAthlete()
// 检查是否所有选手都完成了
const allFinished = athletes.value.every(a => a.finished)
if (allFinished) {
stopTimer()
Service.Msg('所有选手已完成!')
} else {
Service.Msg(`${athlete.name} 已记录`)
}
// 检查是否所有选手都完成了
const allFinished = athletes.value.every(a => a.finished)
if (allFinished) {
stopTimer()
Service.Msg('所有选手已完成!')
} else {
// 间隔出发模式,重置选手时间
resetAthleteTime(athlete)
Service.Msg(`${athlete.name} 已记录`)
}
}
@@ -401,17 +429,61 @@
showStopwatchModal.value = false
}
// 性别选择变化
const onGenderChange = (e : any) => {
newStudentGender.value = e.detail.value
}
// 打开日期选择器
const openDatePicker = () => {
showDatePicker.value = true
}
// 根据出生日期计算年龄
const calculateAge = (birthday : string) : string => {
const birthDate = new Date(birthday)
const today = new Date()
let age = today.getFullYear() - birthDate.getFullYear()
const monthDiff = today.getMonth() - birthDate.getMonth()
// 如果当前月份小于出生月份或者月份相同但日期还没到年龄减1
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--
}
return age.toString()
}
// 日期选择确认
const onDateConfirm = (e : any) => {
let selectedDate = e
if (Array.isArray(e) && e.length > 0) {
selectedDate = e[0]
} else if (e && e.value) {
selectedDate = e.value
}
newStudentBirthday.value = selectedDate
newStudentAge.value = calculateAge(selectedDate)
showDatePicker.value = false
}
// 关闭添加学生弹出框
const closeAddStudentModal = () => {
showAddStudentModal.value = false
newStudentName.value = ''
newStudentLane.value = ''
newStudentGender.value = ''
newStudentBirthday.value = ''
newStudentAge.value = ''
}
// 添加新学生
const addNewStudent = () => {
if (!newStudentName.value.trim()) {
Service.Msg('请输入学生姓名')
Service.Msg('请输入姓名')
return
}
if (!newStudentBirthday.value.trim()) {
Service.Msg('请选择出生日期')
return
}
@@ -420,7 +492,9 @@
id: Date.now().toString(),
number: newNumber,
name: newStudentName.value.trim(),
lane: newStudentLane.value.trim() || `${newNumber}泳道`,
gender: newStudentGender.value,
age: newStudentAge.value.trim(),
birthday: newStudentBirthday.value.trim(),
time: 0,
bestTime: null,
finished: false,
@@ -873,6 +947,46 @@
}
}
/* 性别单选按钮组 */
.radio-group {
display: flex;
gap: 40rpx;
.radio-item {
display: flex;
align-items: center;
gap: 12rpx;
.radio-text {
font-size: 28rpx;
color: #333;
}
}
}
/* 日期选择器包装 */
.date-picker-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 80rpx;
background-color: #f5f5f5;
border-radius: 12rpx;
padding: 0 24rpx;
cursor: pointer;
.date-value {
font-size: 28rpx;
color: #333;
flex: 1;
&.placeholder {
color: #999;
}
}
}
/* 悬浮保存按钮 */
.float-save-btn {
position: fixed;

View File

@@ -9,11 +9,13 @@
"f2",
"图表",
"可视化"
],
],
"repository": "https://gitee.com/uCharts/uCharts",
"engines": {
"engines": {
"uni-app": "^3.1.0",
"uni-app-x": "^3.1.0"
},
"dcloudext": {
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
@@ -31,48 +33,66 @@
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/~qiun",
"type": "component-vue"
"type": "component-vue",
"darkmode": "-",
"i18n": "-",
"widescreen": "-"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
"tcb": "",
"aliyun": ""
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
"uni-app": {
"vue": {
"vue2": "-",
"vue3": "-"
},
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"vue": "-",
"nvue": "-",
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-",
"alipay": "-",
"toutiao": "-",
"baidu": "-",
"kuaishou": "-",
"jd": "-",
"harmony": "-",
"qq": "-",
"lark": "-",
"xhs": "-"
},
"quickapp": {
"huawei": "-",
"union": "-"
}
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
"uni-app-x": {
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-"
}
}
}
}