Files
akmon/pages/sport/student/training-record.uvue
2026-01-20 08:04:15 +08:00

1112 lines
26 KiB
Plaintext

<template>
<scroll-view direction="vertical" class="training-record-container">
<!-- Assignment Data -->
<supadb v-if="assignmentId" collection="ak_assignments" :filter="{ id: assignmentId }"
@process-data="handleAssignmentData" #default="{ data, loading: assignmentLoading }">
<view style="display: none;"></view>
</supadb>
<!-- Training Project Data -->
<supadb v-if="projectId" collection="ak_training_projects" :filter="{ id: projectId }"
@process-data="handleProjectData" #default="{ data, loading: projectLoading }">
<view style="display: none;"></view>
</supadb>
<!-- Content Wrapper -->
<view class="content-wrapper" :class="{ 'large-screen': isLargeScreen }">
<!-- Header -->
<view class="header">
<view class="header-info">
<text class="assignment-title">{{ getAssignmentTitle() }}</text>
<text class="project-name">{{ projectDisplayName }}</text>
</view>
<view v-if="!isCompleted" class="header-status">
<view class="status-indicator" :class="getStatusClass()">
<text class="status-text">{{ getStatusText() }}</text>
</view>
</view>
</view>
<!-- Training Timer -->
<view class="timer-section" v-if="!isCompleted">
<view class="timer-card">
<view class="timer-display">
<text class="timer-text">{{ formattedTime }}</text>
</view>
<view class="timer-controls">
<button class="timer-btn start" v-if="!isRecording" @click="startRecording">
<simple-icon type="play-filled" :size="16" color="#FFFFFF" />
开始记录
</button>
<button class="timer-btn pause" v-else-if="isRecording && !isPaused" @click="pauseRecording">
<simple-icon type="pause" :size="16" color="#FFFFFF" />
暂停
</button>
<button class="timer-btn resume" v-else-if="isRecording && isPaused" @click="resumeRecording">
<simple-icon type="play-filled" :size="16" color="#FFFFFF" />
继续
</button>
<button class="timer-btn stop" v-if="isRecording" @click="stopRecording">
<simple-icon type="stop" :size="16" color="#FFFFFF" />
停止
</button>
</view>
</view>
</view>
<!-- Training Form -->
<view class="record-form-section">
<view class="form-section">
<view class="section-title">训练记录</view> <!-- Basic Information -->
<view class="form-item">
<view class="form-label">
<text class="label-text">训练日期</text>
</view>
<picker-date :value="formData.trainingDate" placeholder="选择训练日期" class="date-picker" />
</view>
<view class="form-item">
<view class="form-label">
<text class="label-text">训练时长</text>
<text class="required-mark">*</text>
</view>
<view class="duration-input">
<input :value="formData.durationMinutes" type="number" placeholder="30" class="number-input"
@input="onDurationInput" />
<text class="unit-text">分钟</text>
</view>
</view>
<!-- Performance Metrics -->
<view class="form-item">
<view class="form-label">
<text class="label-text">完成度</text>
<text class="required-mark">*</text>
</view>
<view class="completion-slider">
<slider :value="formData.completionRate" min="0" max="100" step="5" class="slider-input"
@change="onCompletionChange" />
<text class="completion-text">{{ formData.completionRate }}%</text>
</view>
</view> <!-- Training Notes -->
<view class="form-item">
<view class="form-label">
<text class="label-text">训练感受</text>
</view>
<textarea :value="formData.notes" placeholder="记录训练过程中的感受、困难或收获..." maxlength="500"
class="textarea-input" @input="onNotesInput" />
<view class="word-count">{{ formData.notes.length }}/500</view>
</view>
<!-- Difficulty Rating -->
<view class="form-item">
<view class="form-label">
<text class="label-text">难度评价</text>
</view>
<view class="rating-section">
<view v-for="(star, index) in 5" :key="index" class="star-item"
@click="setDifficultyRating(index + 1)">
<simple-icon :type="index < formData.difficultyRating ? 'star-filled' : 'star'"
:size="32" :color="index < formData.difficultyRating ? '#F59E0B' : '#D1D5DB'" />
</view>
<text class="rating-text">{{ getDifficultyText() }}</text>
</view>
</view>
<!-- Effort Rating -->
<view class="form-item">
<view class="form-label">
<text class="label-text">努力程度</text>
</view>
<view class="rating-section">
<view v-for="(star, index) in 5" :key="index" class="star-item"
@click="setEffortRating(index + 1)">
<simple-icon :type="index < formData.effortRating ? 'star-filled' : 'star'" :size="32"
:color="index < formData.effortRating ? '#10B981' : '#D1D5DB'" />
</view>
<text class="rating-text">{{ getEffortText() }}</text>
</view>
</view>
</view>
<!-- Performance Data -->
<view class="form-section" v-if="showPerformanceData">
<view class="section-title">成绩数据</view>
<!-- Dynamic Performance Fields -->
<view v-for="(field, index) in performanceFields" :key="index" class="form-item">
<view class="form-label">
<text class="label-text">{{ field.getString('label') ?? '' }}</text>
<text v-if="field.getBoolean('required') ?? false" class="required-mark">*</text>
</view>
<view class="performance-input">
<input :value="getPerformanceValue(field.getString('key') ?? '')"
:type="getInputType(field)" :placeholder="field.getString('placeholder') ?? ''"
class="number-input"
@input="onPerformanceInput(field.getString('key') ?? '', $event as UniInputEvent)" />
<text class="unit-text">{{ field.getString('unit') ?? '' }}</text>
</view>
</view>
</view>
<!-- Action Buttons -->
<view class="action-section">
<button v-if="!isCompleted" @click="saveDraft" class="action-btn draft-btn" :disabled="saving">
<text>保存草稿</text>
</button>
<button @click="submitRecord" class="action-btn submit-btn" :disabled="saving || !isFormValid">
<text>{{ isCompleted ? '更新记录' : '提交记录' }}</text>
</button>
</view>
</view>
</view>
<!-- Completion Modal -->
<modal :show="showCompletionModal" @close="showCompletionModal = false">
<view class="completion-modal">
<view class="modal-content">
<view class="success-icon">
<simple-icon type="checkmark-circle" :size="64" color="#10B981" />
</view>
<text class="success-title">训练记录已提交!</text>
<text class="success-message">
您的训练记录已成功提交,教师将对您的表现进行评价。
</text>
<view class="modal-actions">
<button @click="goToRecords" class="modal-btn primary">
<text>查看记录</text>
</button>
<button @click="goToDashboard" class="modal-btn secondary">
<text>返回首页</text>
</button>
</view>
</view>
</view>
</modal>
</scroll-view>
</template>
<script setup lang="uts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import {formatTime} from '../types.uts'
import { onLoad, onResize } from '@dcloudio/uni-app'
import { state, getCurrentUserId } from '@/utils/store.uts'
import supaClient from '@/components/supadb/aksupainstance.uts'
type TrainingFormData = {
assignmentId : string
projectId : string
trainingDate : string
durationMinutes : string
completionRate : number
notes : string
difficultyRating : number
effortRating : number
performanceData : UTSJSONObject
}
// Props from route
const props = defineProps<{
assignmentId ?: string
projectId ?: string
recordId ?: string
}>()
// Reactive data - User ID handling
const userId = ref('')
const assignmentId = ref<string>('')
const projectId = ref<string>('')
// Data
const currentAssignment = ref<UTSJSONObject | null>(null)
const currentProject = ref<UTSJSONObject | null>(null)
// Timer state
const isRecording = ref<boolean>(false)
const isPaused = ref<boolean>(false)
const currentTime = ref<number>(0)
const startTime = ref<number>(0)
const pausedTime = ref<number>(0)
const timerInterval = ref<number | null>(null)
// Form state
const isCompleted = ref<boolean>(false)
const saving = ref<boolean>(false)
const showCompletionModal = ref<boolean>(false)
// Form data
const formData = ref<TrainingFormData>({
assignmentId: '',
projectId: '',
trainingDate: '',
durationMinutes: '',
completionRate: 0,
notes: '',
difficultyRating: 0,
effortRating: 0,
performanceData: {} as UTSJSONObject
})
// Performance fields configuration
const performanceFields = ref<UTSJSONObject[]>([])
const showPerformanceData = ref<boolean>(false)
// Responsive state - using onResize for dynamic updates
const screenWidth = ref<number>(uni.getSystemInfoSync().windowWidth)
// Computed
const isFormValid = computed(() : boolean => {
return formData.value.durationMinutes.length > 0 &&
formData.value.completionRate > 0
})
// Computed properties for template functions to avoid UTS Android compilation issues
const isLargeScreen = computed(() : boolean => {
return screenWidth.value >= 768
})
const projectDisplayName = computed(() : string => {
const project = currentProject.value
if (project != null) {
return project.getString('name')??''
}
return ''
})
const formattedTime = computed(() : string => {
// Convert milliseconds to seconds for formatTime function
const timeInSeconds = Math.floor(currentTime.value / 1000)
return formatTime(timeInSeconds)
})
// Get current student ID using unified function
const getCurrentStudentId = () : string => {
// Use unified user ID handling
if (userId.value.length > 0) {
return userId.value
}
return getCurrentUserId()
}
const updateTimer = () => {
timerInterval.value = setInterval(() => {
currentTime.value = Date.now() - startTime.value
}, 100)
}
const clearTimer = () => {
if (timerInterval.value != null) {
clearInterval(timerInterval.value!)
timerInterval.value = null
}
}
const loadExistingRecord = (recordId : string) => {
// Load existing training record for editing
// This would typically fetch from API
console.log('Loading record:', recordId)
isCompleted.value = true
}
// Timer methods
const startRecording = () => {
isRecording.value = true
isPaused.value = false
startTime.value = Date.now() - pausedTime.value
updateTimer()
}
const pauseRecording = () => {
isPaused.value = true
clearTimer()
}
const resumeRecording = () => {
isPaused.value = false
startTime.value = Date.now() - currentTime.value
updateTimer()
}
const stopRecording = () => {
isRecording.value = false
isPaused.value = false
clearTimer()
// Auto-fill duration from timer
const minutes = Math.floor(currentTime.value / 60000)
formData.value.durationMinutes = minutes.toString()
}
// Load assignment data
// Data handlers (for compatibility with template)
const handleAssignmentData = (data : UTSJSONObject[]) => {
if (data.length > 0) {
const assignmentData = data[0]
currentAssignment.value = assignmentData
// Extract project ID if not provided
if (projectId.value.length == 0) {
projectId.value = assignmentData.getString('project_id') ?? ''
formData.value.projectId = projectId.value
}
}
}
const setupPerformanceFields = () => {
if (currentProject.value == null) return
// Configure performance fields based on project type
const projectData = currentProject.value
const projectType = projectData?.getString('category') ?? ''
switch (projectType) {
case 'running':
performanceFields.value = [
{ key: 'distance', label: '跑步距离', unit: '公里', type: 'number', required: true, placeholder: '5.0' },
{ key: 'pace', label: '配速', unit: '分钟/公里', type: 'text', required: false, placeholder: '5:30' },
{ key: 'heartRate', label: '平均心率', unit: 'bpm', type: 'number', required: false, placeholder: '150' }
]
showPerformanceData.value = true
break
case 'strength':
performanceFields.value = [
{ key: 'weight', label: '训练重量', unit: '公斤', type: 'number', required: true, placeholder: '50' },
{ key: 'reps', label: '重复次数', unit: '次', type: 'number', required: true, placeholder: '12' },
{ key: 'sets', label: '组数', unit: '组', type: 'number', required: true, placeholder: '3' }
]
showPerformanceData.value = true
break
case 'flexibility':
performanceFields.value = [
{ key: 'stretchTime', label: '拉伸时长', unit: '分钟', type: 'number', required: true, placeholder: '20' },
{ key: 'flexibility', label: '柔韧度评分', unit: '分', type: 'number', required: false, placeholder: '8' }
]
showPerformanceData.value = true
break
default:
showPerformanceData.value = false
} // Initialize performance data
performanceFields.value.forEach(field => {
const key = field.getString('key') ?? ''
if (key.length > 0) {
(formData.value.performanceData as UTSJSONObject)[key] = ''
}
})
}
const handleProjectData = (data : UTSJSONObject[]) => {
if (data.length > 0) {
currentProject.value = data[0]
setupPerformanceFields()
}
}
// Form methods
const onCompletionChange = (value : number) => {
formData.value.completionRate = value
}
const setDifficultyRating = (rating : number) => {
formData.value.difficultyRating = rating
}
const setEffortRating = (rating : number) => {
formData.value.effortRating = rating
}
const getDifficultyText = () : string => {
const ratings = ['', '很简单', '简单', '适中', '困难', '很困难']
const rating = ratings[formData.value.difficultyRating]
return rating != null ? rating : ''
}
const getEffortText = () : string => {
const ratings = ['', '很轻松', '轻松', '适中', '努力', '非常努力']
const rating = ratings[formData.value.effortRating]
return rating != null ? rating : ''
}
const getInputType = (field : UTSJSONObject) : string => {
const type = field.getString('type') ?? 'text'
return type === 'number' ? 'number' : 'text'
}
const getPerformanceValue = (key : string) : string => {
const perfData = formData.value.performanceData as UTSJSONObject
return perfData?.getString(key) ?? ''
}
// Input event handlers
const onDurationInput = (event : UniInputEvent) => {
formData.value.durationMinutes = event.detail.value
}
const onNotesInput = (event : UniInputEvent) => {
formData.value.notes = event.detail.value
}
const onPerformanceInput = (key : string, event : UniInputEvent) => {
const perfData = formData.value.performanceData as UTSJSONObject
perfData[key] = event.detail.value
}
const saveDraft = async () => {
saving.value = true
try {
const draftData = {
...formData.value,
student_id: getCurrentStudentId(),
status: 'draft',
updated_at: new Date().toISOString()
}
const result = await supaClient
.from('ak_training_records')
.insert(draftData)
.execute()
if (result.error == null) {
uni.showToast({
title: '草稿已保存',
icon: 'success'
})
} else {
throw new Error('保存失败')
}
} catch (error) {
console.error('保存草稿失败:', error)
uni.showToast({
title: '保存失败',
icon: 'none'
})
} finally {
saving.value = false
}
}
// Load project data
const loadProjectData = async () => {
try {
const result = await supaClient
.from('ak_training_projects')
.select('*', {})
.eq('id', projectId.value)
.single()
.execute()
if (result.error == null && result.data != null) {
const projectData = result.data as UTSJSONObject
currentProject.value = projectData
setupPerformanceFields()
}
} catch (error) {
console.error('加载项目失败:', error)
}
}
const loadAssignmentData = async () => {
try {
const result = await supaClient
.from('ak_assignments')
.select('*, ak_training_projects(*)', {})
.eq('id', assignmentId.value)
.execute()
if (result.error == null && result.data != null) {
const assignmentData = result.data as UTSJSONObject
currentAssignment.value = assignmentData
// Extract project ID if not provided
if (projectId.value.length == 0) {
projectId.value = assignmentData.getString('project_id') ?? ''
formData.value.projectId = projectId.value
if (projectId.value.length > 0) {
loadProjectData()
}
}
}
} catch (error) {
console.error('加载作业失败:', error)
}
}
const submitRecord = async () => {
if (!isFormValid.value) {
uni.showToast({
title: '请完善必填信息',
icon: 'error'
})
return
}
saving.value = true
try {
const recordData = {
...formData.value,
student_id: getCurrentStudentId(),
status: 'submitted',
submitted_at: new Date().toISOString()
}
const result = await supaClient
.from('ak_training_records')
.insert(recordData)
.execute()
if (result.error == null) {
isCompleted.value = true
showCompletionModal.value = true
} else {
throw new Error('提交失败')
}
} catch (error) {
console.error('提交记录失败:', error)
uni.showToast({
title: '提交失败',
icon: 'none'
})
} finally {
saving.value = false
}
}
const goToRecords = () => {
uni.navigateTo({
url: '/pages/sport/student/records'
})
}
const goToDashboard = () => {
uni.switchTab({
url: '/pages/sport/student/dashboard'
})
}
// Helper methods
const getAssignmentTitle = () : string => {
if (currentAssignment.value != null) {
return currentAssignment.value?.getString('title') ?? '训练记录'
}
return '训练记录'
}
const getStatusClass = () : string => {
if (isRecording.value && !isPaused.value) return 'status-recording'
if (isPaused.value) return 'status-paused'
return 'status-ready'
}
const getStatusText = () : string => {
if (isRecording.value && !isPaused.value) return '记录中'
if (isPaused.value) return '已暂停'
return '准备开始'
}
// Lifecycle
onLoad((options : OnLoadOptions) => {
userId.value = options['id'] ?? getCurrentUserId()
// Get page parameters from options
assignmentId.value = options['assignmentId'] ?? props.assignmentId ?? ''
projectId.value = options['projectId'] ?? props.projectId ?? ''
})
onMounted(() => {
// Get route params from props (passed via navigation) or use defaults
if (assignmentId.value.length == 0) {
assignmentId.value = props.assignmentId ?? ''
}
if (projectId.value.length == 0) {
projectId.value = props.projectId ?? ''
}
formData.value.assignmentId = assignmentId.value
formData.value.projectId = projectId.value
// Set default training date to today
const today = new Date()
formData.value.trainingDate = today.toISOString().split('T')[0]
// Check if this is editing an existing record
const recordId = props.recordId ?? ''
if (recordId.length > 0) {
loadExistingRecord(recordId)
}
// Load data
if (assignmentId.value.length > 0) {
loadAssignmentData()
}
if (projectId.value.length > 0) {
loadProjectData()
}
// Initialize screen width
screenWidth.value = uni.getSystemInfoSync().windowWidth
})
onUnmounted(() => {
clearTimer()
})
// Handle resize events for responsive design
onResize((size) => {
screenWidth.value = size.size.windowWidth
})
</script>
<style scoped>
.training-record-container {
flex: 1;
background-image: linear-gradient(to bottom right, #667eea, #764ba2);
}
.content-wrapper {
flex: 1;
padding: 30rpx;
background: #F8FAFC;
border-radius: 30rpx 30rpx 0 0;
margin-top: 20rpx;
}
.content-wrapper.large-screen {
padding: 40rpx;
max-width: 800rpx;
margin: 20rpx auto 0;
border-radius: 30rpx;
}
/* Header */
.header {
background-image: linear-gradient(to bottom right, #6366F1, #8B5CF6);
padding: 30rpx;
border-radius: 20rpx;
margin-bottom: 30rpx;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.header-info {
flex: 1;
}
.assignment-title {
font-size: 36rpx;
font-weight: bold;
color: #FFFFFF;
margin-bottom: 10rpx;
}
.project-name {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
}
.header-status {
align-items: flex-end;
}
.status-indicator {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 22rpx;
font-weight: 400;
}
.status-ready {
background: rgba(255, 255, 255, 0.2);
color: #FFFFFF;
}
.status-recording {
background: #EF4444;
color: #FFFFFF;
}
.status-paused {
background: #F59E0B;
color: #FFFFFF;
}
.status-text {
font-size: 22rpx;
}
/* Timer Section */
.timer-section {
margin-bottom: 30rpx;
}
.timer-card {
background: #FFFFFF;
padding: 40rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
align-items: center;
}
.timer-display {
margin-bottom: 30rpx;
}
.timer-text {
font-size: 72rpx;
font-weight: bold;
color: #1E293B;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
}
.timer-controls {
flex-direction: row;
justify-content: center;
}
.timer-btn {
flex-direction: row;
align-items: center;
padding: 20rpx 30rpx;
border: none;
border-radius: 15rpx;
font-size: 28rpx;
font-weight: 400;
margin: 0 10rpx;
}
.timer-btn:first-child {
margin-left: 0;
}
.timer-btn:last-child {
margin-right: 0;
}
/* Icon spacing inside timer buttons */
.timer-btn simple-icon {
margin-right: 8rpx;
}
.timer-btn.start {
background: #10B981;
color: #FFFFFF;
}
.timer-btn.pause {
background: #F59E0B;
color: #FFFFFF;
}
.timer-btn.resume {
background: #3B82F6;
color: #FFFFFF;
}
.timer-btn.stop {
background: #EF4444;
color: #FFFFFF;
}
.timer-btn:active {
opacity: 0.8;
}
/* Form Sections */
.record-form-section {
/* 使用 margin 替代 gap */
}
.record-form-section .form-section {
margin-bottom: 30rpx;
}
.record-form-section .form-section:last-child {
margin-bottom: 0;
}
.form-section {
background: #FFFFFF;
padding: 30rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
}
.section-title {
font-size: 36rpx;
font-weight: bold;
color: #1E293B;
margin-bottom: 30rpx;
padding-bottom: 15rpx;
border-bottom: 2rpx solid #E2E8F0;
}
.form-item {
margin-bottom: 30rpx;
}
.form-item:last-child {
margin-bottom: 0;
}
.form-label {
flex-direction: row;
align-items: center;
margin-bottom: 15rpx;
}
.label-text {
font-size: 28rpx;
color: #374151;
font-weight: 400;
}
.required-mark {
color: #EF4444;
font-size: 28rpx;
margin-left: 5rpx;
}
.date-picker {
width: 100%;
height: 80rpx;
padding: 0 20rpx;
border: 2rpx solid #E5E7EB;
border-radius: 15rpx;
background: #FFFFFF;
font-size: 28rpx;
color: #374151;
}
.duration-input,
.performance-input {
flex-direction: row;
align-items: center;
}
.duration-input .number-input,
.performance-input .number-input {
margin-right: 15rpx;
}
.number-input {
flex: 1;
padding: 20rpx;
border: 2rpx solid #E5E7EB;
border-radius: 15rpx;
font-size: 28rpx;
color: #374151;
background: #FFFFFF;
text-align: center;
}
.unit-text {
font-size: 28rpx;
color: #6B7280;
}
.completion-slider {
flex-direction: row;
align-items: center;
}
.completion-slider .slider-input {
margin-right: 20rpx;
}
.slider-input {
flex: 1;
height: 40rpx;
}
.completion-text {
font-size: 28rpx;
color: #1E293B;
font-weight: 400;
min-width: 80rpx;
text-align: center;
}
.textarea-input {
width: 100%;
height: 200rpx;
padding: 20rpx;
border: 2rpx solid #E5E7EB;
border-radius: 15rpx;
font-size: 28rpx;
color: #374151;
background: #FFFFFF;
text-align: top;
}
.word-count {
text-align: right;
font-size: 24rpx;
color: #9CA3AF;
margin-top: 10rpx;
}
.rating-section {
flex-direction: row;
align-items: center;
}
.star-item {
padding: 10rpx;
margin-right: 15rpx;
}
.star-item:last-child {
margin-right: 0;
}
.star-item:active {
transform: scale(0.9);
}
.rating-text {
font-size: 28rpx;
color: #6B7280;
margin-left: 15rpx;
}
/* Action Section */
.action-section {
flex-direction: row;
padding: 30rpx;
}
.action-btn {
flex: 1;
height: 90rpx;
border-radius: 20rpx;
font-size: 32rpx;
font-weight: 400;
border: none;
margin-right: 20rpx;
}
.action-btn:last-child {
margin-right: 0;
}
.draft-btn {
background: #F3F4F6;
color: #6B7280;
}
.draft-btn:active {
background: #E5E7EB;
}
.submit-btn {
background-image: linear-gradient(to bottom right, #6366F1, #8B5CF6);
color: #FFFFFF;
}
.submit-btn:active {
opacity: 0.8;
}
.action-btn:disabled {
opacity: 0.5;
}
/* Completion Modal */
.completion-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
z-index: 999;
}
.modal-content {
background: #FFFFFF;
padding: 60rpx;
border-radius: 30rpx;
margin: 60rpx;
align-items: center;
max-width: 600rpx;
}
.success-icon {
margin-bottom: 30rpx;
}
.success-title {
font-size: 42rpx;
font-weight: bold;
color: #1E293B;
margin-bottom: 20rpx;
text-align: center;
}
.success-message {
font-size: 28rpx;
color: #64748B;
text-align: center;
line-height: 1.6;
margin-bottom: 40rpx;
}
.modal-actions {
flex-direction: row;
width: 100%;
}
.modal-btn {
flex: 1;
height: 80rpx;
border-radius: 15rpx;
font-size: 28rpx;
font-weight: 400;
border: none;
margin-right: 20rpx;
}
.modal-btn:last-child {
margin-right: 0;
}
.modal-btn.primary {
background-image: linear-gradient(to bottom right, #6366F1, #8B5CF6);
color: #FFFFFF;
}
.modal-btn.secondary {
background: #F3F4F6;
color: #6B7280;
}
.modal-btn:active {
opacity: 0.8;
}
/* Responsive Design - similar to user-management page */
@media screen and (max-width: 768px) {
.content-wrapper.large-screen {
padding: 30rpx;
margin: 20rpx 10rpx 0;
border-radius: 30rpx 30rpx 0 0;
}
.timer-controls {
flex-direction: column;
}
.timer-btn {
width: 100%;
justify-content: center;
margin: 0 0 15rpx 0;
}
.timer-btn:last-child {
margin-bottom: 0;
}
.action-section {
flex-direction: column;
}
}
@media screen and (min-width: 769px) {
.content-wrapper {
max-width: 900rpx;
margin: 20rpx auto 0;
}
.timer-controls {
flex-direction: row;
justify-content: center;
}
.action-section {
flex-direction: row;
}
}
</style>