'提交'
This commit is contained in:
@@ -46,22 +46,6 @@
|
|||||||
</view>
|
</view>
|
||||||
</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 {
|
.chart-section {
|
||||||
margin: 0 20rpx;
|
margin: 0 20rpx;
|
||||||
|
|||||||
@@ -8,6 +8,42 @@
|
|||||||
</view>
|
</view>
|
||||||
</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">
|
<view class="calendar-wrapper">
|
||||||
<uni-calendar
|
<uni-calendar
|
||||||
@@ -55,13 +91,7 @@
|
|||||||
</sl-table>
|
</sl-table>
|
||||||
</view>
|
</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>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -115,18 +145,46 @@
|
|||||||
detail: string
|
detail: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableData = ref<TableDataItem[]>([{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 95, detail: '1900/2000' },
|
const tableData = ref<TableDataItem[]>([])
|
||||||
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 88, detail: '1760/2000' },
|
|
||||||
{ name: '王小明', projectName: '200米自由泳', plan: '1500米', completion: 100, detail: '1500/1500' }])
|
// 项目列表
|
||||||
|
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([
|
// const selectedDates = ref([
|
||||||
{ date: '2026-03-15', info: '训练' },
|
// { date: '2026-03-15', info: '训练' },
|
||||||
{ date: '2026-03-18', info: '训练' },
|
// { date: '2026-03-18', info: '训练' },
|
||||||
{ date: '2026-03-20', info: '训练' },
|
// { date: '2026-03-20', info: '训练' },
|
||||||
{ date: '2026-03-22', info: '训练' },
|
// { date: '2026-03-22', info: '训练' },
|
||||||
{ date: '2026-03-25', info: '训练' }
|
// { date: '2026-03-25', info: '训练' }
|
||||||
])
|
// ])
|
||||||
|
|
||||||
// 平均完成率
|
// 平均完成率
|
||||||
const averageCompletion = computed(() => {
|
const averageCompletion = computed(() => {
|
||||||
@@ -135,37 +193,44 @@
|
|||||||
return Math.round(total / tableData.value.length)
|
return Math.round(total / tableData.value.length)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 模拟数据
|
// 完整模拟数据
|
||||||
const mockData: Record<string, TableDataItem[]> = {
|
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: 95, detail: '1900/2000' },
|
||||||
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 88, detail: '1760/2000' },
|
{ name: '张小明', projectName: '200米自由泳', plan: '1500米', completion: 88, detail: '1320/1500' },
|
||||||
{ 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: '100米自由泳', plan: '2000米', completion: 92, detail: '1840/2000' },
|
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 92, detail: '1840/2000' },
|
||||||
{ name: '赵小芳', projectName: '100米自由泳', plan: '1800米', completion: 85, detail: '1530/1800' },
|
{ name: '李小红', projectName: '400米自由泳', plan: '1200米', completion: 85, detail: '1020/1200' },
|
||||||
{ name: '王小明', projectName: '200米自由泳', plan: '1500米', completion: 95, detail: '1425/1500' }
|
{ 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': [
|
'2026-03-29': [
|
||||||
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 87, detail: '1740/2000' },
|
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 90, detail: '1800/2000' },
|
||||||
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 90, detail: '1800/2000' }
|
{ name: '张小明', projectName: '100米蛙泳', plan: '1800米', completion: 85, detail: '1530/1800' },
|
||||||
],
|
|
||||||
'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' },
|
|
||||||
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 95, detail: '1900/2000' },
|
{ 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(() => {
|
onLoad(() => {
|
||||||
loadData()
|
loadData()
|
||||||
})
|
})
|
||||||
@@ -196,16 +261,86 @@
|
|||||||
|
|
||||||
// 日期选择处理
|
// 日期选择处理
|
||||||
const handleDateChange = (e: any) => {
|
const handleDateChange = (e: any) => {
|
||||||
// 点击日期后的处理
|
|
||||||
const date = e.fulldate
|
const date = e.fulldate
|
||||||
selectedDate.value = date
|
selectedDate.value = date
|
||||||
|
filterAndLoadData()
|
||||||
|
}
|
||||||
|
|
||||||
// 加载该日期的数据
|
// 项目选择确认
|
||||||
if (mockData[date]) {
|
const handleProjectConfirm = (e: any) => {
|
||||||
tableData.value = mockData[date]
|
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 {
|
} 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>
|
</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 {
|
.calendar-wrapper {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|||||||
@@ -23,22 +23,6 @@
|
|||||||
</picker>
|
</picker>
|
||||||
</view>
|
</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>
|
<text class="rank-number">{{ index + 1 }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 学生头像 -->
|
|
||||||
<view class="student-avatar">
|
|
||||||
<text class="avatar-text">{{ item.name.charAt(0) }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 学生信息 -->
|
<!-- 学生信息 -->
|
||||||
<view class="student-info">
|
<view class="student-info">
|
||||||
<text class="student-name">{{ item.name }}</text>
|
<text class="student-name">{{ item.name }}</text>
|
||||||
@@ -308,9 +287,23 @@
|
|||||||
.project-select-section {
|
.project-select-section {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
margin: 0 20rpx 20rpx;
|
margin: 0 20rpx 20rpx;
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
padding: 28rpx;
|
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 {
|
.picker-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -323,8 +316,9 @@
|
|||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: #f0f0f0;
|
background-color: #fff0f5;
|
||||||
border-color: #eb2f96;
|
border-color: #1890ff;
|
||||||
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker-text {
|
.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 {
|
.ranking-section {
|
||||||
@@ -360,13 +337,26 @@
|
|||||||
// 统计卡片
|
// 统计卡片
|
||||||
.stats-card {
|
.stats-card {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
padding: 24rpx;
|
padding: 24rpx;
|
||||||
margin-bottom: 20rpx;
|
margin-bottom: 20rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-around;
|
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 {
|
.stat-item {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -381,7 +371,7 @@
|
|||||||
.stat-value {
|
.stat-value {
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #eb2f96;
|
color: #1890ff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,32 +388,36 @@
|
|||||||
// 排名项
|
// 排名项
|
||||||
.ranking-item {
|
.ranking-item {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
padding: 24rpx;
|
padding: 24rpx;
|
||||||
margin-bottom: 16rpx;
|
margin-bottom: 16rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20rpx;
|
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;
|
transition: all 0.3s ease;
|
||||||
position: relative;
|
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 {
|
&:active {
|
||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
box-shadow: 0 4rpx 16rpx rgba(235, 47, 150, 0.15);
|
box-shadow: 0 4rpx 16rpx rgba(235, 47, 150, 0.15);
|
||||||
}
|
|
||||||
|
|
||||||
// 前三名特殊样式
|
|
||||||
&.top-three {
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
opacity: 1;
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 6rpx;
|
|
||||||
background: linear-gradient(180deg, #eb2f96 0%, #f759ab 100%);
|
|
||||||
border-radius: 16rpx 0 0 16rpx;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,7 +431,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
background-color: #f0f0f0;
|
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 {
|
.rank-number {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
@@ -448,7 +442,7 @@
|
|||||||
// 前三名特殊颜色
|
// 前三名特殊颜色
|
||||||
&.rank-1 {
|
&.rank-1 {
|
||||||
background: linear-gradient(135deg, #ffd700 0%, #ffec3d 100%);
|
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 {
|
.rank-number {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -458,7 +452,7 @@
|
|||||||
|
|
||||||
&.rank-2 {
|
&.rank-2 {
|
||||||
background: linear-gradient(135deg, #c0c0c0 0%, #d9d9d9 100%);
|
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 {
|
.rank-number {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -467,7 +461,7 @@
|
|||||||
|
|
||||||
&.rank-3 {
|
&.rank-3 {
|
||||||
background: linear-gradient(135deg, #cd7f32 0%, #e6963d 100%);
|
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 {
|
.rank-number {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -479,13 +473,13 @@
|
|||||||
.student-avatar {
|
.student-avatar {
|
||||||
width: 70rpx;
|
width: 70rpx;
|
||||||
height: 70rpx;
|
height: 70rpx;
|
||||||
background: linear-gradient(135deg, #eb2f96 0%, #f759ab 100%);
|
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
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 {
|
.avatar-text {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
@@ -509,7 +503,7 @@
|
|||||||
|
|
||||||
.student-speed {
|
.student-speed {
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #eb2f96;
|
color: #1890ff;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -58,14 +58,7 @@
|
|||||||
</sl-table>
|
</sl-table>
|
||||||
</view>
|
</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>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -152,37 +152,36 @@
|
|||||||
|
|
||||||
/* 用户信息卡片 */
|
/* 用户信息卡片 */
|
||||||
.user-card {
|
.user-card {
|
||||||
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 50%, #096dd9 100%);
|
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
|
||||||
border-radius: 28rpx;
|
border-radius: 28rpx;
|
||||||
padding: 40rpx 30rpx;
|
padding: 40rpx 30rpx;
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 24rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20rpx;
|
gap: 20rpx;
|
||||||
box-shadow: 0 12rpx 32rpx rgba(24, 144, 255, 0.35),
|
box-shadow: 0 8rpx 24rpx rgba(24, 144, 255, 0.25);
|
||||||
0 4rpx 12rpx rgba(24, 144, 255, 0.2);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -60%;
|
top: -40%;
|
||||||
right: -40%;
|
right: -30%;
|
||||||
width: 400rpx;
|
width: 300rpx;
|
||||||
height: 400rpx;
|
height: 300rpx;
|
||||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.18) 0%, transparent 70%);
|
background: radial-gradient(circle, rgba(255, 255, 255, 0.15) 0%, transparent 70%);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -40%;
|
bottom: -30%;
|
||||||
left: -20%;
|
left: -20%;
|
||||||
width: 300rpx;
|
width: 250rpx;
|
||||||
height: 300rpx;
|
height: 250rpx;
|
||||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.12) 0%, transparent 70%);
|
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
</view>
|
</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">
|
<view class="card-icon segment-icon">
|
||||||
<u-icon name="list" size="36" color="#fff"></u-icon>
|
<u-icon name="list" size="36" color="#fff"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
902
src/pages/userFunc/setCourse.vue.bak
Normal file
902
src/pages/userFunc/setCourse.vue.bak
Normal 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>
|
||||||
@@ -24,19 +24,19 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="athletes-list">
|
<view class="athletes-list">
|
||||||
<view v-for="(athlete, index) in athletes" :key="athlete.id" @longpress="deleStu(athlete.id)" class="athlete-item"
|
<view v-for="(athlete, index) in athletes" :key="athlete.id" @longpress="deleStu(athlete.id)"
|
||||||
:class="{ finished: athlete.finished, fastest: athlete.isFastest }">
|
class="athlete-item" :class="{ finished: athlete.finished, fastest: athlete.isFastest }">
|
||||||
<view class="athlete-number">{{ index+1 }}</view>
|
<view class="athlete-number">{{ index+1 }}</view>
|
||||||
<view class="athlete-info">
|
<view class="athlete-info">
|
||||||
<text class="athlete-name">{{ athlete.name }}</text>
|
<text class="athlete-name">{{ athlete.name }}</text>
|
||||||
<text class="athlete-lane">{{ athlete.lane }}</text>
|
<text class="athlete-lane">{{ athlete.gender }} · {{ athlete.age }}岁</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="time-wrapper">
|
<view class="time-wrapper">
|
||||||
<text class="athlete-time">{{ formatTime(athlete.time) }}</text>
|
<text class="athlete-time">{{ formatTime(athlete.time) }}</text>
|
||||||
<text v-if="athlete.bestTime" class="best-time">最快: {{ formatTime(athlete.bestTime) }}</text>
|
<text v-if="athlete.bestTime" class="best-time">最快: {{ formatTime(athlete.bestTime) }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="" style="display: flex;align-items: center; gap: 10rpx;">
|
<view class="" style="display: flex;align-items: center; gap: 10rpx;">
|
||||||
<view 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>
|
<u-icon name="pause-circle" bold="true" size="22" color="#1890ff"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
<view @click="resetAthleteTime(athlete)">
|
<view @click="resetAthleteTime(athlete)">
|
||||||
@@ -128,12 +128,34 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="modal-content">
|
<view class="modal-content">
|
||||||
<view class="form-group">
|
<view class="form-group">
|
||||||
<text class="form-label">学生姓名</text>
|
<text class="form-label">姓名</text>
|
||||||
<input class="form-input" v-model="newStudentName" placeholder="请输入学生姓名" type="text" />
|
<input class="form-input" v-model="newStudentName" placeholder="请输入姓名" type="text" />
|
||||||
</view>
|
</view>
|
||||||
<view class="form-group">
|
<view class="form-group">
|
||||||
<text class="form-label">序号</text>
|
<text class="form-label">性别</text>
|
||||||
<input class="form-input" v-model="newStudentLane" placeholder="请输入序号" type="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>
|
</view>
|
||||||
<view class="modal-footer">
|
<view class="modal-footer">
|
||||||
@@ -146,6 +168,9 @@
|
|||||||
<view class="float-save-btn" style="color: #fff; font-size: 24rpx;" @click="saveData">
|
<view class="float-save-btn" style="color: #fff; font-size: 24rpx;" @click="saveData">
|
||||||
保存
|
保存
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 日期选择器 -->
|
||||||
|
<u-datetime-picker v-model="newStudentBirthday" :show="showDatePicker" mode="date" @confirm="onDateConfirm" @cancel="showDatePicker = false" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -158,7 +183,9 @@
|
|||||||
id : string
|
id : string
|
||||||
number : string
|
number : string
|
||||||
name : string
|
name : string
|
||||||
lane : string
|
gender : string
|
||||||
|
age : string
|
||||||
|
birthday : string
|
||||||
time : number
|
time : number
|
||||||
bestTime : number | null
|
bestTime : number | null
|
||||||
finished : boolean
|
finished : boolean
|
||||||
@@ -167,10 +194,10 @@
|
|||||||
|
|
||||||
// 选手列表
|
// 选手列表
|
||||||
const athletes = ref<Athlete[]>([
|
const athletes = ref<Athlete[]>([
|
||||||
{ id: '1', number: '01', 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: '李四', lane: '第二泳道', 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: '王五', lane: '第三泳道', 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: '赵六', lane: '第四泳道', 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 newStudentName = ref('')
|
||||||
const newStudentLane = ref('')
|
const newStudentGender = ref('男')
|
||||||
|
const newStudentBirthday = ref('')
|
||||||
|
const newStudentAge = ref('')
|
||||||
|
|
||||||
|
// 日期选择器相关
|
||||||
|
const showDatePicker = ref(false)
|
||||||
|
const currentDate = ref('')
|
||||||
|
|
||||||
// 计算已完成的选手
|
// 计算已完成的选手
|
||||||
const finishedAthletes = computed(() => {
|
const finishedAthletes = computed(() => {
|
||||||
return athletes.value.filter(a => a.finished)
|
return athletes.value.filter(a => a.finished)
|
||||||
})
|
})
|
||||||
|
|
||||||
const deleStu = (id:any) => {
|
const deleStu = (id : any) => {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '提示', // 对话框标题
|
title: '提示', // 对话框标题
|
||||||
content: '是否删除该学生', // 显示的内容
|
content: '是否删除该学生', // 显示的内容
|
||||||
showCancel: true, // 是否显示取消按钮
|
showCancel: true, // 是否显示取消按钮
|
||||||
success: function (res) {
|
success: function (res) {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
let index= athletes.value.findIndex((item)=>{
|
let index = athletes.value.findIndex((item) => {
|
||||||
return item.id==id
|
return item.id == id
|
||||||
})
|
})
|
||||||
athletes.value.splice(index,1)
|
athletes.value.splice(index, 1)
|
||||||
} else if (res.cancel) {
|
} else if (res.cancel) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -317,34 +350,29 @@
|
|||||||
// 处理选手第一个按钮点击
|
// 处理选手第一个按钮点击
|
||||||
const handleAthleteFirstButton = (athlete : Athlete, index : number) => {
|
const handleAthleteFirstButton = (athlete : Athlete, index : number) => {
|
||||||
// 如果是一起出发模式,记录该选手时间
|
// 如果是一起出发模式,记录该选手时间
|
||||||
if (stopwatchMode.value === 'together') {
|
if (athlete.finished) {
|
||||||
if (athlete.finished) {
|
Service.Msg('该选手已记录')
|
||||||
Service.Msg('该选手已记录')
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
|
||||||
athlete.finished = true
|
athlete.finished = true
|
||||||
athlete.time = currentTime.value
|
athlete.time = currentTime.value
|
||||||
|
|
||||||
// 更新最快记录
|
// 更新最快记录
|
||||||
if (athlete.bestTime === null || athlete.time < athlete.bestTime) {
|
if (athlete.bestTime === null || athlete.time < athlete.bestTime) {
|
||||||
athlete.bestTime = athlete.time
|
athlete.bestTime = athlete.time
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新最快选手
|
// 更新最快选手
|
||||||
updateFastestAthlete()
|
updateFastestAthlete()
|
||||||
|
|
||||||
// 检查是否所有选手都完成了
|
// 检查是否所有选手都完成了
|
||||||
const allFinished = athletes.value.every(a => a.finished)
|
const allFinished = athletes.value.every(a => a.finished)
|
||||||
if (allFinished) {
|
if (allFinished) {
|
||||||
stopTimer()
|
stopTimer()
|
||||||
Service.Msg('所有选手已完成!')
|
Service.Msg('所有选手已完成!')
|
||||||
} else {
|
|
||||||
Service.Msg(`${athlete.name} 已记录`)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// 间隔出发模式,重置选手时间
|
Service.Msg(`${athlete.name} 已记录`)
|
||||||
resetAthleteTime(athlete)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,17 +429,61 @@
|
|||||||
showStopwatchModal.value = false
|
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 = () => {
|
const closeAddStudentModal = () => {
|
||||||
showAddStudentModal.value = false
|
showAddStudentModal.value = false
|
||||||
newStudentName.value = ''
|
newStudentName.value = ''
|
||||||
newStudentLane.value = ''
|
newStudentGender.value = '男'
|
||||||
|
newStudentBirthday.value = ''
|
||||||
|
newStudentAge.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加新学生
|
// 添加新学生
|
||||||
const addNewStudent = () => {
|
const addNewStudent = () => {
|
||||||
if (!newStudentName.value.trim()) {
|
if (!newStudentName.value.trim()) {
|
||||||
Service.Msg('请输入学生姓名')
|
Service.Msg('请输入姓名')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!newStudentBirthday.value.trim()) {
|
||||||
|
Service.Msg('请选择出生日期')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,7 +492,9 @@
|
|||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
number: newNumber,
|
number: newNumber,
|
||||||
name: newStudentName.value.trim(),
|
name: newStudentName.value.trim(),
|
||||||
lane: newStudentLane.value.trim() || `第${newNumber}泳道`,
|
gender: newStudentGender.value,
|
||||||
|
age: newStudentAge.value.trim(),
|
||||||
|
birthday: newStudentBirthday.value.trim(),
|
||||||
time: 0,
|
time: 0,
|
||||||
bestTime: null,
|
bestTime: null,
|
||||||
finished: false,
|
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 {
|
.float-save-btn {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@@ -9,11 +9,13 @@
|
|||||||
"f2",
|
"f2",
|
||||||
"图表",
|
"图表",
|
||||||
"可视化"
|
"可视化"
|
||||||
],
|
],
|
||||||
"repository": "https://gitee.com/uCharts/uCharts",
|
"repository": "https://gitee.com/uCharts/uCharts",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
"uni-app": "^3.1.0",
|
||||||
|
"uni-app-x": "^3.1.0"
|
||||||
},
|
},
|
||||||
"dcloudext": {
|
"dcloudext": {
|
||||||
"sale": {
|
"sale": {
|
||||||
"regular": {
|
"regular": {
|
||||||
"price": "0.00"
|
"price": "0.00"
|
||||||
@@ -31,48 +33,66 @@
|
|||||||
"permissions": "无"
|
"permissions": "无"
|
||||||
},
|
},
|
||||||
"npmurl": "https://www.npmjs.com/~qiun",
|
"npmurl": "https://www.npmjs.com/~qiun",
|
||||||
"type": "component-vue"
|
"type": "component-vue",
|
||||||
|
"darkmode": "-",
|
||||||
|
"i18n": "-",
|
||||||
|
"widescreen": "-"
|
||||||
},
|
},
|
||||||
"uni_modules": {
|
"uni_modules": {
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"encrypt": [],
|
"encrypt": [],
|
||||||
"platforms": {
|
"platforms": {
|
||||||
"cloud": {
|
"cloud": {
|
||||||
"tcb": "y",
|
"tcb": "√",
|
||||||
"aliyun": "y"
|
"aliyun": "√"
|
||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
"App": {
|
"uni-app": {
|
||||||
"app-vue": "y",
|
"vue": {
|
||||||
"app-nvue": "y"
|
"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": {
|
"uni-app-x": {
|
||||||
"Safari": "y",
|
"web": {
|
||||||
"Android Browser": "y",
|
"safari": "-",
|
||||||
"微信浏览器(Android)": "y",
|
"chrome": "-"
|
||||||
"QQ浏览器(Android)": "y"
|
},
|
||||||
},
|
"app": {
|
||||||
"H5-pc": {
|
"android": "-",
|
||||||
"Chrome": "y",
|
"ios": "-",
|
||||||
"IE": "y",
|
"harmony": "-"
|
||||||
"Edge": "y",
|
},
|
||||||
"Firefox": "y",
|
"mp": {
|
||||||
"Safari": "y"
|
"weixin": "-"
|
||||||
},
|
}
|
||||||
"小程序": {
|
|
||||||
"微信": "y",
|
|
||||||
"阿里": "y",
|
|
||||||
"百度": "y",
|
|
||||||
"字节跳动": "y",
|
|
||||||
"QQ": "y"
|
|
||||||
},
|
|
||||||
"快应用": {
|
|
||||||
"华为": "y",
|
|
||||||
"联盟": "y"
|
|
||||||
},
|
|
||||||
"Vue": {
|
|
||||||
"vue2": "y",
|
|
||||||
"vue3": "y"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user