Files
akmon/pages/sense/settings.uvue
2026-01-20 08:04:15 +08:00

978 lines
25 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="settings-container">
<!-- 头部导航 -->
<view class="header">
<button class="back-btn" @click="goBack">
<text class="back-icon">←</text>
</button>
<text class="title">传感器设置</text>
<button class="save-btn" @click="saveSettings">保存</button>
</view>
<!-- 通用设置 -->
<view class="section">
<text class="section-title">通用设置</text>
<view class="setting-item">
<text class="setting-label">实时数据推送</text>
<switch :checked="settings.realtime_enabled" @change="onRealtimeChange" />
</view>
<view class="setting-item">
<text class="setting-label">数据自动备份</text>
<switch :checked="settings.auto_backup" @change="onAutoBackupChange" />
</view>
<view class="setting-item">
<text class="setting-label">异常检测</text>
<switch :checked="settings.anomaly_detection" @change="onAnomalyDetectionChange" />
</view>
<view class="setting-item">
<text class="setting-label">AI分析</text>
<switch :checked="settings.ai_analysis" @change="onAIAnalysisChange" />
</view>
</view>
<!-- 数据采集频率 -->
<view class="section">
<text class="section-title">数据采集频率</text>
<view class="frequency-grid">
<view class="frequency-item" v-for="(sensor, index) in sensorFrequencies" :key="index">
<text class="sensor-name">{{ sensor.name }}</text>
<button class="picker-button" @click="showFrequencyPicker(index)">
<view class="picker-view">
<text>{{ frequencyOptions[sensor.frequency_index] }}</text>
<text class="picker-arrow">▼</text>
</view>
</button>
</view>
</view>
</view>
<!-- 阈值设置 -->
<view class="section">
<text class="section-title">异常阈值设置</text>
<view class="threshold-list">
<view class="threshold-item" v-for="(threshold, index) in thresholds" :key="index">
<text class="threshold-label">{{ threshold.name }}</text>
<view class="threshold-inputs">
<view class="input-group">
<text class="input-label">最小值</text>
<input class="threshold-input" type="number" v-model="threshold.min_value" />
</view>
<view class="input-group">
<text class="input-label">最大值</text>
<input class="threshold-input" type="number" v-model="threshold.max_value" />
</view>
</view>
</view>
</view>
</view>
<!-- 通知设置 -->
<view class="section">
<text class="section-title">通知设置</text>
<view class="notification-item">
<text class="setting-label">异常警报</text>
<switch :checked="notifications.anomaly_alert" @change="onAnomalyAlertChange" />
</view>
<view class="notification-item">
<text class="setting-label">每日报告</text>
<switch :checked="notifications.daily_report" @change="onDailyReportChange" />
</view>
<view class="notification-item">
<text class="setting-label">设备离线提醒</text>
<switch :checked="notifications.device_offline" @change="onDeviceOfflineChange" />
</view>
<view class="notification-item" v-if="notifications.daily_report">
<text class="setting-label">报告发送时间</text>
<input class="time-input" type="time" :value="notifications.report_time" @input="onReportTimeInput" />
</view>
</view>
<!-- 数据管理 -->
<view class="section">
<text class="section-title">数据管理</text>
<view class="data-management">
<view class="management-item">
<text class="setting-label">本地数据保留时间</text>
<button class="picker-button" @click="showRetentionPicker">
<view class="picker-view">
<text>{{ retentionOptions[dataManagement.retention_index] }}</text>
<text class="picker-arrow">▼</text>
</view>
</button>
</view>
<view class="management-item">
<text class="setting-label">云端同步</text>
<switch :checked="dataManagement.cloud_sync" @change="onCloudSyncChange" />
</view>
<view class="management-item">
<text class="setting-label">数据压缩</text>
<switch :checked="dataManagement.data_compression" @change="onDataCompressionChange" />
</view>
</view>
</view>
<!-- 隐私设置 -->
<view class="section">
<text class="section-title">隐私设置</text>
<view class="privacy-item">
<text class="setting-label">数据加密</text>
<switch :checked="privacy.data_encryption" @change="onDataEncryptionChange" />
</view>
<view class="privacy-item">
<text class="setting-label">匿名统计</text>
<switch :checked="privacy.anonymous_stats" @change="onAnonymousStatsChange" />
</view>
<view class="privacy-item">
<text class="setting-label">位置数据收集</text>
<switch :checked="privacy.location_data" @change="onLocationDataChange" />
</view>
</view>
<!-- 设备校准 -->
<view class="section">
<text class="section-title">设备校准</text>
<view class="calibration-list">
<button class="calibration-btn" @click="calibrateHeartRate">心率传感器校准</button>
<button class="calibration-btn" @click="calibrateSteps">步数传感器校准</button>
<button class="calibration-btn" @click="calibrateBloodPressure">血压计校准</button>
<button class="calibration-btn" @click="calibrateTemperature">体温计校准</button>
</view>
</view>
<!-- 数据导出 -->
<view class="section">
<text class="section-title">数据导出</text>
<view class="export-options">
<button class="export-btn" @click="exportData('json')">导出为JSON</button>
<button class="export-btn" @click="exportData('csv')">导出为CSV</button>
<button class="export-btn" @click="exportData('pdf')">导出为PDF报告</button>
</view>
</view>
<!-- 重置选项 -->
<view class="section">
<text class="section-title">重置选项</text>
<view class="reset-options">
<button class="reset-btn" @click="resetSettings">恢复默认设置</button>
<button class="clear-btn" @click="clearAllData">清除所有数据</button>
</view>
</view>
<!-- 校准弹窗 -->
<view class="calibration-modal" v-if="showCalibration" @click="closeCalibration">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">{{ calibrationType }}校准</text>
<button class="close-btn" @click="closeCalibration">×</button>
</view>
<view class="modal-body">
<text class="calibration-instruction">{{ calibrationInstruction }}</text>
<view class="calibration-progress" v-if="calibrationProgress > 0">
<view class="progress-bar">
<view class="progress-fill" :style="{ width: calibrationProgress + '%' }"></view>
</view>
<text class="progress-text">{{ calibrationProgress }}%</text>
</view>
<view class="calibration-actions">
<button class="start-btn" v-if="calibrationProgress === 0"
@click="startCalibration">开始校准</button>
<button class="stop-btn" v-else @click="stopCalibration">停止校准</button>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, onMounted } from 'vue'
import supa from '@/components/supadb/aksupainstance.uts'
import type {
UserSettings,
NotificationSettings,
DataManagementSettings,
PrivacySettings,
SensorFrequency,
SensorThreshold,
UserSettingsRecord,
SensorConfigItem,
ThresholdConfigItem
} from './types.uts'
// 响应式数据
const settings = ref<UserSettings>({
realtime_enabled: true,
auto_backup: true,
anomaly_detection: true,
ai_analysis: false
})
const sensorFrequencies = ref<SensorFrequency[]>([])
const thresholds = ref<SensorThreshold[]>([])
const notifications = ref<NotificationSettings>({
anomaly_alert: true,
daily_report: false,
device_offline: true,
report_time: '09:00'
})
const dataManagement = ref<DataManagementSettings>({
retention_index: 2,
cloud_sync: true,
data_compression: false
})
const privacy = ref<PrivacySettings>({
data_encryption: true,
anonymous_stats: false,
location_data: false
})
const showCalibration = ref<boolean>(false)
const calibrationType = ref<string>('')
const calibrationInstruction = ref<string>('')
const calibrationProgress = ref<number>(0)
// 选项数据
const frequencyOptions = ['关闭', '1分钟', '5分钟', '15分钟', '30分钟', '1小时']
const retentionOptions = ['7天', '30天', '90天', '1年', '永久保存']
const userId = 'eed3824b-bba1-4309-8048-19d17367c084'
let calibrationTimer : Number = 0
async function loadSettings() {
try {
const result = await supa.from('ak_training_intensity_settings')
.eq('user_id', userId)
.eq('setting_category', 'sensor')
.executeAs<Array<UserSettingsRecord>>()
if (result.data !== null && Array.isArray(result.data)) {
const dataArray = result.data as Array<any>
if (dataArray.length > 0) {
const settingsRecord = dataArray[0] as UTSJSONObject
const settingValue = settingsRecord.getJSON('setting_value')
if (settingValue !== null) {
// 更新设置
settings.value.realtime_enabled = settingValue.getBoolean('realtime_enabled') ?? true
settings.value.auto_backup = settingValue.getBoolean('auto_backup') ?? true
settings.value.anomaly_detection = settingValue.getBoolean('anomaly_detection') ?? true
settings.value.ai_analysis = settingValue.getBoolean('ai_analysis') ?? false
// 更新通知设置
const notificationData = settingValue.getJSON('notifications')
if (notificationData !== null) {
notifications.value.anomaly_alert = notificationData.getBoolean('anomaly_alert') ?? true
notifications.value.daily_report = notificationData.getBoolean('daily_report') ?? false
notifications.value.device_offline = notificationData.getBoolean('device_offline') ?? true
notifications.value.report_time = notificationData.getString('report_time') ?? '09:00'
}
// 更新数据管理设置
const dataManagementData = settingValue.getJSON('data_management')
if (dataManagementData !== null) {
dataManagement.value.retention_index = dataManagementData.getNumber('retention_index')?.toInt() ?? 2
dataManagement.value.cloud_sync = dataManagementData.getBoolean('cloud_sync') ?? true
dataManagement.value.data_compression = dataManagementData.getBoolean('data_compression') ?? false
}
// 更新隐私设置
const privacyData = settingValue.getJSON('privacy')
if (privacyData !== null) {
privacy.value.data_encryption = privacyData.getBoolean('data_encryption') ?? true
privacy.value.anonymous_stats = privacyData.getBoolean('anonymous_stats') ?? false
privacy.value.location_data = privacyData.getBoolean('location_data') ?? false
}
}
}
}
} catch (e) {
console.log('加载设置失败:', e)
}
}
function initializeSensorFrequencies() {
const frequencies : SensorFrequency[] = []
const sensors : SensorConfigItem[] = [
{ name: '心率', key: 'heart_rate', frequency_index: 2 },
{ name: '步数', key: 'steps', frequency_index: 3 },
{ name: '血氧', key: 'spo2', frequency_index: 4 },
{ name: '血压', key: 'blood_pressure', frequency_index: 5 },
{ name: '体温', key: 'temperature', frequency_index: 4 },
{ name: '位置', key: 'location', frequency_index: 3 }
]
for (let i : Int = 0; i < sensors.length; i++) {
const sensor = sensors[i]
const freq : SensorFrequency = {
name: sensor.name,
key: sensor.key,
frequency_index: sensor.frequency_index
}
frequencies.push(freq)
}
sensorFrequencies.value = frequencies
} function initializeThresholds() {
const thresholdList : SensorThreshold[] = []
const thresholdConfig : ThresholdConfigItem[] = [
{ name: '心率 (bpm)', key: 'heart_rate', min: 60, max: 100 },
{ name: '血氧 (%)', key: 'spo2', min: 95, max: 100 },
{ name: '收缩压 (mmHg)', key: 'systolic_bp', min: 90, max: 140 },
{ name: '舒张压 (mmHg)', key: 'diastolic_bp', min: 60, max: 90 },
{ name: '体温 (°C)', key: 'temperature', min: 36.0, max: 37.5 }
]
for (let i : Int = 0; i < thresholdConfig.length; i++) {
const config = thresholdConfig[i]
const threshold : SensorThreshold = {
name: config.name,
key: config.key,
min_value: config.min.toString(),
max_value: config.max.toString()
}
thresholdList.push(threshold)
}
thresholds.value = thresholdList
}
async function saveSettings() {
try {
// 构建设置数据
const settingValue = new UTSJSONObject()
settingValue.set('realtime_enabled', settings.value.realtime_enabled)
settingValue.set('auto_backup', settings.value.auto_backup)
settingValue.set('anomaly_detection', settings.value.anomaly_detection)
settingValue.set('ai_analysis', settings.value.ai_analysis)
settingValue.set('notifications', notifications.value)
settingValue.set('data_management', dataManagement.value)
settingValue.set('privacy', privacy.value)
settingValue.set('sensor_frequencies', sensorFrequencies.value)
settingValue.set('thresholds', thresholds.value)
const settingData = new UTSJSONObject()
settingData.set('user_id', userId)
settingData.set('setting_category', 'sensor')
settingData.set('setting_key', 'sensor_config')
settingData.set('setting_value', settingValue)
// 先检查是否已存在记录
const existingResult = await supa.from('ak_user_settings')
.eq('user_id', userId)
.eq('setting_category', 'sensor')
.executeAs<Array<UserSettingsRecord>>()
let result : any
if (existingResult.data !== null && Array.isArray(existingResult.data)) {
const dataArray = existingResult.data as Array<any>
if (dataArray.length > 0) {
// 存在记录,使用 update
const updateData = new UTSJSONObject()
updateData.set('setting_value', settingValue)
updateData.set('updated_at', new Date().toISOString())
result = await supa.from('ak_user_settings')
.eq('user_id', userId)
.eq('setting_category', 'sensor')
.update(updateData)
.execute()
} else {
// 不存在记录,使用 insert
settingData.set('created_at', new Date().toISOString())
settingData.set('updated_at', new Date().toISOString())
result = await supa.from('ak_user_settings')
.insert(settingData)
.execute()
}
} else {
// 不存在记录,使用 insert
settingData.set('created_at', new Date().toISOString())
settingData.set('updated_at', new Date().toISOString())
result = await supa.from('ak_user_settings')
.insert(settingData)
.execute()
}
if (result.error === null) {
uni.showToast({
title: '设置保存成功',
icon: 'success'
})
} else {
console.log('保存设置失败:', result.error)
uni.showToast({
title: '保存失败',
icon: 'error'
})
}
} catch (e) {
console.log('保存设置异常:', e)
uni.showToast({
title: '保存失败',
icon: 'error'
})
}
}
// 事件处理函数
function onRealtimeChange(e : UniSwitchChangeEvent) {
settings.value.realtime_enabled = e.detail.value
}
function onAutoBackupChange(e : UniSwitchChangeEvent) {
settings.value.auto_backup = e.detail.value
}
function onAnomalyDetectionChange(e : UniSwitchChangeEvent) {
settings.value.anomaly_detection = e.detail.value
}
function onAIAnalysisChange(e : UniSwitchChangeEvent) {
settings.value.ai_analysis = e.detail.value
}
function onFrequencyChange(value : number, index : number) {
if (index >= 0 && index < sensorFrequencies.value.length) {
sensorFrequencies.value[index].frequency_index = value
}
}
function onAnomalyAlertChange(e : UniSwitchChangeEvent) {
notifications.value.anomaly_alert = e.detail.value
}
function onDailyReportChange(e : UniSwitchChangeEvent) {
notifications.value.daily_report = e.detail.value
}
function onDeviceOfflineChange(e : UniSwitchChangeEvent) {
notifications.value.device_offline = e.detail.value
}
function onReportTimeChange(value : string) {
notifications.value.report_time = value
}
function onRetentionChange(value : number) {
dataManagement.value.retention_index = value
}
function onCloudSyncChange(e : UniSwitchChangeEvent) {
dataManagement.value.cloud_sync = e.detail.value
}
function onDataCompressionChange(e : UniSwitchChangeEvent) {
dataManagement.value.data_compression = e.detail.value
}
function onDataEncryptionChange(e : UniSwitchChangeEvent) {
privacy.value.data_encryption = e.detail.value
}
function onAnonymousStatsChange(e : UniSwitchChangeEvent) {
privacy.value.anonymous_stats = e.detail.value
}
function onLocationDataChange(e : UniSwitchChangeEvent) {
privacy.value.location_data = e.detail.value
}
function showFrequencyPicker(index : number) {
uni.showActionSheet({
itemList: frequencyOptions,
success: (res) => {
if (res.tapIndex >= 0) {
onFrequencyChange(res.tapIndex, index)
}
}
})
}
function onReportTimeInput(e : UniInputEvent) {
notifications.value.report_time = e.detail.value
}
function showRetentionPicker() {
uni.showActionSheet({
itemList: retentionOptions,
success: (res) => {
if (res.tapIndex >= 0) {
onRetentionChange(res.tapIndex)
}
}
})
}
// 校准相关函数
function startCalibrationProcess(type : string, instruction : string) {
calibrationType.value = type
calibrationInstruction.value = instruction
showCalibration.value = true
calibrationProgress.value = 0
}
function calibrateHeartRate() {
startCalibrationProcess('心率传感器', '请保持静坐放松心情校准过程约需2分钟')
}
function calibrateSteps() {
startCalibrationProcess('步数传感器', '请在平地上正常步行20步保持匀速')
}
function calibrateBloodPressure() {
startCalibrationProcess('血压计', '请使用标准血压计测量,确保袖带位置正确')
}
function calibrateTemperature() {
startCalibrationProcess('体温计', '请使用医用体温计对比校准测量时间需2分钟')
}
function stopCalibration() {
if (calibrationTimer !== 0) {
clearInterval(calibrationTimer)
calibrationTimer = 0
}
calibrationProgress.value = 0
}
function closeCalibration() {
showCalibration.value = false
stopCalibration()
}
function startCalibration() {
calibrationProgress.value = 1
// 模拟校准进度
calibrationTimer = setInterval(() => {
if (calibrationProgress.value < 100) {
calibrationProgress.value += Math.random() * 10
if (calibrationProgress.value > 100) {
calibrationProgress.value = 100
}
} else {
stopCalibration()
uni.showToast({
title: '校准完成',
icon: 'success'
})
closeCalibration()
}
}, 500)
}
// 数据导出
function exportData(format : string) {
uni.showLoading({
title: '准备导出数据...'
})
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: `${format.toUpperCase()}文件已生成`,
icon: 'success'
})
}, 2000)
}
// 重置功能
function resetSettings() {
uni.showModal({
title: '确认重置',
content: '确定要恢复所有设置到默认状态吗?',
success: (res) => {
if (res.confirm) {
// 重置所有设置到默认值
settings.value.realtime_enabled = true
settings.value.auto_backup = true
settings.value.anomaly_detection = true
settings.value.ai_analysis = false
notifications.value.anomaly_alert = true
notifications.value.daily_report = false
notifications.value.device_offline = true
notifications.value.report_time = '09:00'
dataManagement.value.retention_index = 2
dataManagement.value.cloud_sync = true
dataManagement.value.data_compression = false
privacy.value.data_encryption = true
privacy.value.anonymous_stats = false
privacy.value.location_data = false
initializeSensorFrequencies()
initializeThresholds()
uni.showToast({
title: '设置已重置',
icon: 'success'
})
}
}
})
}
function clearAllData() {
uni.showModal({
title: '危险操作',
content: '确定要清除所有传感器数据吗?此操作不可恢复!',
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '数据清除完成',
icon: 'success'
})
}
}
})
}
function goBack() {
uni.navigateBack()
}
onMounted(() => {
loadSettings()
initializeSensorFrequencies()
initializeThresholds()
})
</script>
<style scoped>
.settings-container {
flex: 1;
padding: 20rpx;
background-color: #f5f5f5;
}
.header {
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding: 20rpx;
background-color: #ffffff;
border-radius: 12rpx;
}
.back-btn {
padding: 12rpx;
background-color: #f0f0f0;
border-radius: 8rpx;
border: none;
}
.back-icon {
font-size: 32rpx;
color: #666666;
}
.title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
}
.save-btn {
padding: 16rpx 24rpx;
background-color: #409EFF;
color: #ffffff;
border-radius: 8rpx;
font-size: 28rpx;
border: none;
}
.section {
margin-bottom: 20rpx;
padding: 24rpx;
background-color: #ffffff;
border-radius: 12rpx;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333333;
margin-bottom: 20rpx;
}
.setting-item,
.notification-item,
.management-item,
.privacy-item {
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.setting-item:last-child,
.notification-item:last-child,
.management-item:last-child,
.privacy-item:last-child {
border-bottom: none;
}
.setting-label {
font-size: 28rpx;
color: #333333;
}
.frequency-grid {
flex-direction: column;
}
.frequency-item {
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.frequency-item:last-child {
border-bottom: none;
}
.sensor-name {
font-size: 28rpx;
color: #333333;
width: 200rpx;
}
.picker-view {
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 16rpx 20rpx;
background-color: #f8f9fa;
border-radius: 8rpx;
min-width: 200rpx;
}
.picker-button {
background: none;
border: none;
padding: 0;
}
.picker-arrow {
color: #999999;
font-size: 24rpx;
}
.threshold-list {
flex-direction: column;
}
.threshold-item {
margin-bottom: 24rpx;
}
.threshold-label {
font-size: 28rpx;
color: #333333;
margin-bottom: 12rpx;
}
.threshold-inputs {
flex-direction: row;
justify-content: space-between;
}
.input-group {
width: 48%;
}
.input-label {
font-size: 24rpx;
color: #666666;
margin-bottom: 8rpx;
}
.threshold-input {
width: 100%;
padding: 16rpx;
border: 2rpx solid #e0e0e0;
border-radius: 8rpx;
font-size: 28rpx;
}
.calibration-list {
flex-direction: column;
}
.calibration-btn {
padding: 20rpx;
margin-bottom: 16rpx;
background-color: #E6A23C;
color: #ffffff;
border-radius: 8rpx;
font-size: 28rpx;
border: none;
}
.calibration-btn:last-child {
margin-bottom: 0;
}
.export-options {
flex-direction: row;
justify-content: space-around;
}
.export-btn {
flex: 1;
padding: 20rpx;
margin: 0 8rpx;
background-color: #67C23A;
color: #ffffff;
border-radius: 8rpx;
font-size: 26rpx;
border: none;
}
.reset-options {
flex-direction: row;
justify-content: space-around;
}
.reset-btn {
flex: 1;
padding: 20rpx;
margin-right: 16rpx;
background-color: #909399;
color: #ffffff;
border-radius: 8rpx;
font-size: 28rpx;
border: none;
}
.clear-btn {
flex: 1;
padding: 20rpx;
background-color: #F56C6C;
color: #ffffff;
border-radius: 8rpx;
font-size: 28rpx;
border: none;
}
.calibration-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
width: 85%;
background-color: #ffffff;
border-radius: 16rpx;
padding: 32rpx;
}
.modal-header {
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
border-bottom: 2rpx solid #f0f0f0;
padding-bottom: 16rpx;
}
.modal-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
}
.close-btn {
width: 60rpx;
height: 60rpx;
border-radius: 30rpx;
background-color: #f0f0f0;
color: #666666;
font-size: 36rpx;
border: none;
justify-content: center;
align-items: center;
}
.modal-body {
align-items: center;
}
.calibration-instruction {
font-size: 28rpx;
color: #666666;
line-height: 1.6;
text-align: center;
margin-bottom: 32rpx;
}
.calibration-progress {
width: 100%;
align-items: center;
margin-bottom: 32rpx;
}
.progress-bar {
width: 100%;
height: 16rpx;
background-color: #f0f0f0;
border-radius: 8rpx;
overflow: hidden;
margin-bottom: 12rpx;
}
.progress-fill {
height: 100%;
background-color: #409EFF;
border-radius: 8rpx;
transition: width 0.3s ease;
}
.progress-text {
font-size: 24rpx;
color: #666666;
}
.calibration-actions {
width: 100%;
}
.start-btn,
.stop-btn {
width: 100%;
padding: 24rpx;
border-radius: 12rpx;
font-size: 28rpx;
border: none;
}
.start-btn {
background-color: #67C23A;
color: #ffffff;
}
.stop-btn {
background-color: #F56C6C;
color: #ffffff;
}
.time-input {
padding: 16rpx 20rpx;
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8rpx;
font-size: 28rpx;
color: #333333;
min-width: 200rpx;
text-align: center;
}
</style>