|
...
|
...
|
@@ -4,11 +4,13 @@ |
|
|
|
<up-navbar placeholder bgColor="transparent" />
|
|
|
|
<!-- #endif -->
|
|
|
|
<view class="status-header">
|
|
|
|
<view class="timer-box" @click="show = true">
|
|
|
|
<view class="timer-box" @click="show = true" v-if="!isFutureTemplate">
|
|
|
|
<text class="time">{{ formattedTime.substring(3) }}</text>
|
|
|
|
<view class="triangle-icon"></view>
|
|
|
|
</view>
|
|
|
|
<view class="finish-btn" :class="{ disabled: !allUnitsChecked }" @click="save">完成</view>
|
|
|
|
<view v-else class="timer-box-placeholder" />
|
|
|
|
<view class="finish-btn" :class="{ disabled: isFutureTemplate ? !hasAnyAction : !allUnitsChecked }" @click="save">
|
|
|
|
{{ isFutureTemplate ? '保存模板' : '完成' }}</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="action-list">
|
|
...
|
...
|
@@ -25,18 +27,10 @@ |
|
|
|
<!-- 3. 模板训练 type=3 循环 -->
|
|
|
|
<template v-if="actionDetail?.units?.length">
|
|
|
|
<view v-for="(unit, index) in actionDetail.units" :key="unit.unitId">
|
|
|
|
<dongzuo
|
|
|
|
:id="unit.unitId"
|
|
|
|
:type="unit.unitType"
|
|
|
|
:actionDetail="trainingStore.convertUnitToActionDetail(unit)"
|
|
|
|
:ref="(el) => setActionRef(el, index)"
|
|
|
|
:unitIndex="index"
|
|
|
|
:is-daily-templates="false"
|
|
|
|
:isUnitActive="unitActiveStates[index]"
|
|
|
|
@startRestTimer="startRestCountdown"
|
|
|
|
@cancelRestTimer="onCancelRestTimer"
|
|
|
|
@updateUnitActive="(val) => onUnitActiveChange(index, val)"
|
|
|
|
/>
|
|
|
|
<dongzuo :id="unit.unitId" :type="unit.unitType" :actionDetail="trainingStore.convertUnitToActionDetail(unit)"
|
|
|
|
:ref="(el) => setActionRef(el, index)" :unitIndex="index" :is-daily-templates="false"
|
|
|
|
:isUnitActive="unitActiveStates[index]" @startRestTimer="startRestCountdown"
|
|
|
|
@cancelRestTimer="onCancelRestTimer" @updateUnitActive="(val) => onUnitActiveChange(index, val)" />
|
|
|
|
</view>
|
|
|
|
</template>
|
|
|
|
|
|
...
|
...
|
@@ -47,7 +41,7 @@ |
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view v-if="!actionDetail?.units?.length" class="tip-bubble">
|
|
|
|
<view v-if="!actionDetail?.units?.length && !addActionsShow" class="tip-bubble">
|
|
|
|
<text>挑选想要的动作开始训练吧</text>
|
|
|
|
</view>
|
|
|
|
|
|
...
|
...
|
@@ -75,25 +69,14 @@ |
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
<!-- 组间休息倒计时 -->
|
|
|
|
<view
|
|
|
|
class="rest-countdown"
|
|
|
|
v-if="restCountdownSeconds > 0"
|
|
|
|
@click="clearRestCountdown()"
|
|
|
|
>
|
|
|
|
<view class="rest-countdown" v-if="restCountdownSeconds > 0" @click="clearRestCountdown()">
|
|
|
|
<view class="countdown-circle">
|
|
|
|
<!-- H5 端:使用 SVG 进度环 -->
|
|
|
|
<!-- #ifdef H5 -->
|
|
|
|
<svg class="ring-svg" viewBox="0 0 100 100">
|
|
|
|
<circle class="ring-bg" cx="50" cy="50" r="44" />
|
|
|
|
<circle
|
|
|
|
class="ring-progress"
|
|
|
|
cx="50"
|
|
|
|
cy="50"
|
|
|
|
r="44"
|
|
|
|
:stroke-dasharray="CIRCUMFERENCE"
|
|
|
|
:stroke-dashoffset="CIRCUMFERENCE * (1 - restProgress)"
|
|
|
|
transform="rotate(-90 50 50)"
|
|
|
|
/>
|
|
|
|
<circle class="ring-progress" cx="50" cy="50" r="44" :stroke-dasharray="CIRCUMFERENCE"
|
|
|
|
:stroke-dashoffset="CIRCUMFERENCE * (1 - restProgress)" transform="rotate(-90 50 50)" />
|
|
|
|
</svg>
|
|
|
|
<!-- #endif -->
|
|
|
|
<!-- 微信小程序端:不显示进度环 -->
|
|
...
|
...
|
@@ -104,14 +87,8 @@ |
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
<!-- 设置训练时间弹窗 -->
|
|
|
|
<up-popup
|
|
|
|
:show="show"
|
|
|
|
mode="top"
|
|
|
|
@close="show = false"
|
|
|
|
:safeAreaInsetBottom="false"
|
|
|
|
safeAreaInsetTop
|
|
|
|
bgColor="#2c2c2e"
|
|
|
|
>
|
|
|
|
<up-popup :show="show" mode="top" @close="show = false" :safeAreaInsetBottom="false" safeAreaInsetTop
|
|
|
|
bgColor="#2c2c2e">
|
|
|
|
<view class="time-pop-container">
|
|
|
|
<view class="pop-content">
|
|
|
|
<view class="pop-title">设置训练时间</view>
|
|
...
|
...
|
@@ -144,11 +121,7 @@ |
|
|
|
</view>
|
|
|
|
<view class="timer-right">
|
|
|
|
<view class="action-btn gray-btn" v-if="isPause" @click="resetTimer">重置</view>
|
|
|
|
<view
|
|
|
|
class="action-btn green-btn"
|
|
|
|
:class="{ active: !isPause, bgactve: !isPause }"
|
|
|
|
@click="toggleTimer"
|
|
|
|
>
|
|
|
|
<view class="action-btn green-btn" :class="{ active: !isPause, bgactve: !isPause }" @click="toggleTimer">
|
|
|
|
{{ isPause ? '继续计时' : '暂停计时' }}
|
|
|
|
</view>
|
|
|
|
</view>
|
|
...
|
...
|
@@ -161,79 +134,39 @@ |
|
|
|
</up-popup>
|
|
|
|
|
|
|
|
<!-- 设置训练日期 -->
|
|
|
|
<up-datetime-picker
|
|
|
|
:show="datePickerShow"
|
|
|
|
v-model="dateValue"
|
|
|
|
mode="date"
|
|
|
|
closeOnClickOverlay
|
|
|
|
@confirm="confirmDate"
|
|
|
|
@cancel="datePickerShow = false"
|
|
|
|
@close="datePickerShow = false"
|
|
|
|
>
|
|
|
|
<up-datetime-picker :show="datePickerShow" v-model="dateValue" mode="date" closeOnClickOverlay
|
|
|
|
@confirm="confirmDate" @cancel="datePickerShow = false" @close="datePickerShow = false">
|
|
|
|
</up-datetime-picker>
|
|
|
|
|
|
|
|
<!-- 设置起始时间 -->
|
|
|
|
<up-datetime-picker
|
|
|
|
:show="timePickerShow"
|
|
|
|
v-model="timeValue"
|
|
|
|
mode="time"
|
|
|
|
closeOnClickOverlay
|
|
|
|
@confirm="confirmTime"
|
|
|
|
@cancel="timePickerShow = false"
|
|
|
|
@close="timePickerShow = false"
|
|
|
|
>
|
|
|
|
<up-datetime-picker :show="timePickerShow" v-model="timeValue" mode="time" closeOnClickOverlay
|
|
|
|
@confirm="confirmTime" @cancel="timePickerShow = false" @close="timePickerShow = false">
|
|
|
|
</up-datetime-picker>
|
|
|
|
|
|
|
|
<!-- 设置训练时长计时 -->
|
|
|
|
<up-picker
|
|
|
|
:show="showPicker"
|
|
|
|
:columns="timeColumns"
|
|
|
|
:defaultIndex="defaultTimeIndex"
|
|
|
|
title="修改训练时长"
|
|
|
|
@confirm="onTimeConfirm"
|
|
|
|
@cancel="showPicker = false"
|
|
|
|
closeOnClickOverlay
|
|
|
|
>
|
|
|
|
<up-picker :show="showPicker" :columns="timeColumns" :defaultIndex="defaultTimeIndex" title="修改训练时长"
|
|
|
|
@confirm="onTimeConfirm" @cancel="showPicker = false" closeOnClickOverlay>
|
|
|
|
</up-picker>
|
|
|
|
|
|
|
|
<!-- 写总结弹窗 -->
|
|
|
|
<up-popup
|
|
|
|
:show="summaryShow"
|
|
|
|
@close="summaryShow = false"
|
|
|
|
mode="bottom"
|
|
|
|
:safeAreaInsetBottom="false"
|
|
|
|
>
|
|
|
|
<up-popup :show="summaryShow" @close="summaryShow = false" mode="bottom" :safeAreaInsetBottom="false">
|
|
|
|
<view class="summaryPopup">
|
|
|
|
<view class="popup-header">
|
|
|
|
<text class="popup-title">本次训练总结</text>
|
|
|
|
<view class="save-btn" @click="saveSummary">保存</view>
|
|
|
|
</view>
|
|
|
|
<textarea
|
|
|
|
class="summary-textarea"
|
|
|
|
v-model="summaryContent"
|
|
|
|
placeholder="记录本次训练感悟"
|
|
|
|
/>
|
|
|
|
<textarea class="summary-textarea" v-model="summaryContent" placeholder="记录本次训练感悟" />
|
|
|
|
</view>
|
|
|
|
</up-popup>
|
|
|
|
<!-- 命名弹窗 -->
|
|
|
|
<up-popup
|
|
|
|
:show="inputNameShow"
|
|
|
|
@close="inputNameShow = false"
|
|
|
|
mode="bottom"
|
|
|
|
:safeAreaInsetBottom="false"
|
|
|
|
>
|
|
|
|
<up-popup :show="inputNameShow" @close="inputNameShow = false" mode="bottom" :safeAreaInsetBottom="false">
|
|
|
|
<view class="namePopup">
|
|
|
|
<view class="popup-header">
|
|
|
|
<text class="popup-title">训练名称</text>
|
|
|
|
<view class="save-btn" @click="saveTrainingName">保存</view>
|
|
|
|
</view>
|
|
|
|
<textarea
|
|
|
|
v-model="trainingStore.trainingName"
|
|
|
|
class="name-input"
|
|
|
|
placeholder="请输入训练名称"
|
|
|
|
placeholder-color="#8e8e93"
|
|
|
|
color="#fff"
|
|
|
|
/>
|
|
|
|
<textarea v-model="trainingStore.trainingName" class="name-input" placeholder="请输入训练名称"
|
|
|
|
placeholder-color="#8e8e93" color="#fff" />
|
|
|
|
</view>
|
|
|
|
</up-popup>
|
|
|
|
<!-- 动作新增组件 -->
|
|
...
|
...
|
@@ -243,263 +176,306 @@ |
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
import { ref, computed, nextTick, onUnmounted } from 'vue';
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
import { onLoad } from '@dcloudio/uni-app';
|
|
|
|
import TrainingApi from '@/sheep/api/Training/traininghistory';
|
|
|
|
import dailytemplateApi from '@/sheep/api/Template/Dailytemplate';
|
|
|
|
import { useTrainingStore } from '@/sheep/store/trainingStore';
|
|
|
|
import addActions from '@/pages/xunji/components/dongzuo-lianxi/add-actions.vue';
|
|
|
|
import ActionSort from '@/pages/xunji/components/dongzuo-lianxi/dongzuo-paixu.vue';
|
|
|
|
import dongzuo from '@/pages/xunji/components/dongzuo-lianxi/dongzuo.vue';
|
|
|
|
import { trainingSnapshot } from '@/sheep/store/trainingSnapshot';
|
|
|
|
|
|
|
|
const trainingStore = useTrainingStore();
|
|
|
|
|
|
|
|
const addActionsShow = ref(false);
|
|
|
|
const hasConverted = ref(false);
|
|
|
|
const summaryShow = ref(false);
|
|
|
|
const summaryContent = ref('');
|
|
|
|
const inputNameShow = ref(false);
|
|
|
|
const actionSortRef = ref(null);
|
|
|
|
const show = ref(false);
|
|
|
|
const datePickerShow = ref(false);
|
|
|
|
const timePickerShow = ref(false);
|
|
|
|
const dateValue = ref(Number(new Date()));
|
|
|
|
const timeValue = ref('14:13');
|
|
|
|
const trainingDateText = ref('');
|
|
|
|
|
|
|
|
const totalSeconds = computed(() => trainingStore.totalSeconds || 0);
|
|
|
|
const isPause = computed(() => trainingStore.isPause);
|
|
|
|
const showPicker = computed(() => trainingStore.showPicker);
|
|
|
|
const defaultTimeIndex = computed(() => trainingStore.defaultTimeIndex);
|
|
|
|
const trainingName = computed(() => trainingStore.trainingName);
|
|
|
|
const trainingTimeText = computed(() => trainingStore.trainingTimeText);
|
|
|
|
const actionDetail = computed(() => trainingStore.actionDetail || {});
|
|
|
|
|
|
|
|
// ===================== 组间休息倒计时 =====================
|
|
|
|
const restCountdownSeconds = ref(0);
|
|
|
|
const restCountdownTotal = ref(0);
|
|
|
|
let restCountdownInterval = null;
|
|
|
|
|
|
|
|
const restFormattedTime = computed(() => {
|
|
|
|
const min = Math.floor(restCountdownSeconds.value / 60);
|
|
|
|
const sec = restCountdownSeconds.value % 60;
|
|
|
|
return `${String(min).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
|
|
|
|
});
|
|
|
|
|
|
|
|
const restProgress = computed(() => {
|
|
|
|
if (restCountdownTotal.value <= 0) return 0;
|
|
|
|
return 1 - restCountdownSeconds.value / restCountdownTotal.value;
|
|
|
|
});
|
|
|
|
|
|
|
|
// H5 SVG 圆环周长(用于 stroke-dasharray)
|
|
|
|
const CIRCUMFERENCE = 2 * Math.PI * 44;
|
|
|
|
|
|
|
|
const clearRestCountdown = () => {
|
|
|
|
if (restCountdownInterval) {
|
|
|
|
clearInterval(restCountdownInterval);
|
|
|
|
restCountdownInterval = null;
|
|
|
|
}
|
|
|
|
restCountdownSeconds.value = 0;
|
|
|
|
restCountdownTotal.value = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
const startRestCountdown = (seconds) => {
|
|
|
|
if (!seconds || seconds <= 0) return;
|
|
|
|
// 叠加倒计时:如果已经在倒计时中则累加,否则开始新的倒计时
|
|
|
|
if (restCountdownInterval) {
|
|
|
|
restCountdownSeconds.value += seconds;
|
|
|
|
restCountdownTotal.value += seconds;
|
|
|
|
} else {
|
|
|
|
restCountdownTotal.value = seconds;
|
|
|
|
restCountdownSeconds.value = seconds;
|
|
|
|
restCountdownInterval = setInterval(() => {
|
|
|
|
restCountdownSeconds.value--;
|
|
|
|
if (restCountdownSeconds.value <= 0) {
|
|
|
|
clearRestCountdown();
|
|
|
|
// 倒计时结束:不取消勾选状态,不恢复主计时器(主计时器始终在运行)
|
|
|
|
}
|
|
|
|
}, 1000);
|
|
|
|
}
|
|
|
|
// 不暂停主计时器,训练计时器始终继续
|
|
|
|
};
|
|
|
|
|
|
|
|
function onCancelRestTimer(seconds) {
|
|
|
|
if (!restCountdownInterval) return;
|
|
|
|
// 取消勾选时减去对应秒数
|
|
|
|
restCountdownSeconds.value -= (seconds || 0);
|
|
|
|
restCountdownTotal.value -= (seconds || 0);
|
|
|
|
import { ref, computed, nextTick, onUnmounted } from 'vue';
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
import { onLoad } from '@dcloudio/uni-app';
|
|
|
|
import TrainingApi from '@/sheep/api/Training/traininghistory';
|
|
|
|
import dailytemplateApi from '@/sheep/api/Template/Dailytemplate';
|
|
|
|
import TemplatesApi from '@/sheep/api/Template/Templates';
|
|
|
|
import QueryPlanApi from '@/sheep/api/plan/queryplan';
|
|
|
|
import { useTrainingStore } from '@/sheep/store/trainingStore';
|
|
|
|
import addActions from '@/pages/xunji/components/dongzuo-lianxi/add-actions.vue';
|
|
|
|
import ActionSort from '@/pages/xunji/components/dongzuo-lianxi/dongzuo-paixu.vue';
|
|
|
|
import dongzuo from '@/pages/xunji/components/dongzuo-lianxi/dongzuo.vue';
|
|
|
|
import { trainingSnapshot } from '@/sheep/store/trainingSnapshot';
|
|
|
|
|
|
|
|
const trainingStore = useTrainingStore();
|
|
|
|
|
|
|
|
const addActionsShow = ref(false);
|
|
|
|
const hasConverted = ref(false);
|
|
|
|
const summaryShow = ref(false);
|
|
|
|
const summaryContent = ref('');
|
|
|
|
const inputNameShow = ref(false);
|
|
|
|
const actionSortRef = ref(null);
|
|
|
|
const show = ref(false);
|
|
|
|
const datePickerShow = ref(false);
|
|
|
|
const timePickerShow = ref(false);
|
|
|
|
const dateValue = ref(Number(new Date()));
|
|
|
|
const timeValue = ref('14:13');
|
|
|
|
const trainingDateText = ref('');
|
|
|
|
// 未来日期模板模式
|
|
|
|
const isFutureTemplate = ref(false);
|
|
|
|
const targetDate = ref('');
|
|
|
|
|
|
|
|
const totalSeconds = computed(() => trainingStore.totalSeconds || 0);
|
|
|
|
const isPause = computed(() => trainingStore.isPause);
|
|
|
|
const showPicker = computed(() => trainingStore.showPicker);
|
|
|
|
const defaultTimeIndex = computed(() => trainingStore.defaultTimeIndex);
|
|
|
|
const trainingName = computed(() => trainingStore.trainingName);
|
|
|
|
const trainingTimeText = computed(() => trainingStore.trainingTimeText);
|
|
|
|
const actionDetail = computed(() => trainingStore.actionDetail || {});
|
|
|
|
// 未来模板模式:是否有任何动作
|
|
|
|
const hasAnyAction = computed(() => (actionDetail.value?.units || []).length > 0);
|
|
|
|
|
|
|
|
/** 可靠的训练日期:优先使用传入的目标日期,兜底从显示文本提取 */
|
|
|
|
const reliableTrainingDate = computed(() => {
|
|
|
|
if (targetDate.value) return targetDate.value;
|
|
|
|
if (trainingDateText.value) return trainingDateText.value.split(' ')[0];
|
|
|
|
return dayjs().format('YYYY-MM-DD');
|
|
|
|
});
|
|
|
|
|
|
|
|
// ===================== 组间休息倒计时 =====================
|
|
|
|
const restCountdownSeconds = ref(0);
|
|
|
|
const restCountdownTotal = ref(0);
|
|
|
|
let restCountdownInterval = null;
|
|
|
|
|
|
|
|
const restFormattedTime = computed(() => {
|
|
|
|
const min = Math.floor(restCountdownSeconds.value / 60);
|
|
|
|
const sec = restCountdownSeconds.value % 60;
|
|
|
|
return `${String(min).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
|
|
|
|
});
|
|
|
|
|
|
|
|
const restProgress = computed(() => {
|
|
|
|
if (restCountdownTotal.value <= 0) return 0;
|
|
|
|
return 1 - restCountdownSeconds.value / restCountdownTotal.value;
|
|
|
|
});
|
|
|
|
|
|
|
|
// H5 SVG 圆环周长(用于 stroke-dasharray)
|
|
|
|
const CIRCUMFERENCE = 2 * Math.PI * 44;
|
|
|
|
|
|
|
|
const clearRestCountdown = () => {
|
|
|
|
if (restCountdownInterval) {
|
|
|
|
clearInterval(restCountdownInterval);
|
|
|
|
restCountdownInterval = null;
|
|
|
|
}
|
|
|
|
restCountdownSeconds.value = 0;
|
|
|
|
restCountdownTotal.value = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
const startRestCountdown = (seconds) => {
|
|
|
|
if (!seconds || seconds <= 0) return;
|
|
|
|
// 每次打勾用最新时间重新开始计时(替换当前倒计时,不叠加)
|
|
|
|
clearRestCountdown();
|
|
|
|
restCountdownTotal.value = seconds;
|
|
|
|
restCountdownSeconds.value = seconds;
|
|
|
|
restCountdownInterval = setInterval(() => {
|
|
|
|
restCountdownSeconds.value--;
|
|
|
|
if (restCountdownSeconds.value <= 0) {
|
|
|
|
clearRestCountdown();
|
|
|
|
// 倒计时结束:不取消勾选状态,不恢复主计时器(主计时器始终在运行)
|
|
|
|
}
|
|
|
|
}, 1000);
|
|
|
|
};
|
|
|
|
|
|
|
|
function onCancelRestTimer() {
|
|
|
|
clearRestCountdown();
|
|
|
|
}
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
clearRestCountdown();
|
|
|
|
});
|
|
|
|
|
|
|
|
const openActionSort = () => {
|
|
|
|
actionSortRef.value.openActionSort();
|
|
|
|
};
|
|
|
|
|
|
|
|
onLoad(async (options) => {
|
|
|
|
const id = Number(options.id);
|
|
|
|
const type = Number(options.type);
|
|
|
|
const isTraining = options.isTraining === 'true';
|
|
|
|
trainingStore.isTraining = isTraining;
|
|
|
|
|
|
|
|
// 未来日期模板模式
|
|
|
|
isFutureTemplate.value = options.isFutureTemplate === 'true';
|
|
|
|
targetDate.value = options.targetDate || '';
|
|
|
|
// 初始化日期选择器为目标日期,防止打开时间设置弹窗时默认显示今天导致日期被意外覆盖
|
|
|
|
if (targetDate.value) {
|
|
|
|
dateValue.value = dayjs(targetDate.value).valueOf();
|
|
|
|
}
|
|
|
|
if (isFutureTemplate.value) {
|
|
|
|
trainingStore.clearTimer();
|
|
|
|
}
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
clearRestCountdown();
|
|
|
|
});
|
|
|
|
const dailyTemplateId = options.dailyTemplateId ? Number(options.dailyTemplateId) : null;
|
|
|
|
trainingStore.dailyTemplateId = dailyTemplateId;
|
|
|
|
// 直接让 store 去请求 + 保存数据
|
|
|
|
if (!dailyTemplateId) {
|
|
|
|
await trainingStore.loadTrainingDetail(id, type);
|
|
|
|
}
|
|
|
|
|
|
|
|
const openActionSort = () => {
|
|
|
|
actionSortRef.value.openActionSort();
|
|
|
|
};
|
|
|
|
|
|
|
|
onLoad(async (options) => {
|
|
|
|
const id = Number(options.id);
|
|
|
|
const type = Number(options.type);
|
|
|
|
const isTraining = options.isTraining === 'true';
|
|
|
|
trainingStore.isTraining = isTraining;
|
|
|
|
|
|
|
|
const dailyTemplateId = options.dailyTemplateId ? Number(options.dailyTemplateId) : null;
|
|
|
|
trainingStore.dailyTemplateId = dailyTemplateId;
|
|
|
|
// 直接让 store 去请求 + 保存数据
|
|
|
|
if (!dailyTemplateId) {
|
|
|
|
await trainingStore.loadTrainingDetail(id, type);
|
|
|
|
}
|
|
|
|
if (type === 3) {
|
|
|
|
dongzuoRefs.value = [];
|
|
|
|
}
|
|
|
|
if (!hasConverted.value) {
|
|
|
|
trainingStore.convertToTemplate();
|
|
|
|
hasConverted.value = true;
|
|
|
|
}
|
|
|
|
initTrainingName();
|
|
|
|
|
|
|
|
// 编辑模式:默认勾选所有动作的全部组 + 激活整个unit背景
|
|
|
|
const isEdit = options.isEdit === 'true';
|
|
|
|
if (isEdit) {
|
|
|
|
const records = trainingStore.unitRecords;
|
|
|
|
Object.keys(records).forEach((unitIdx) => {
|
|
|
|
const unitRecord = records[unitIdx];
|
|
|
|
Object.keys(unitRecord.records).forEach((exId) => {
|
|
|
|
(unitRecord.records[exId] || []).forEach((set) => {
|
|
|
|
set.isActive = true;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
// 激活所有 unit 的整体背景
|
|
|
|
const totalUnits = actionDetail.value?.units?.length || 0;
|
|
|
|
unitActiveStates.value = Array.from({ length: totalUnits }, () => true);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type === 3) {
|
|
|
|
dongzuoRefs.value = [];
|
|
|
|
}
|
|
|
|
if (!hasConverted.value) {
|
|
|
|
trainingStore.convertToTemplate();
|
|
|
|
hasConverted.value = true;
|
|
|
|
}
|
|
|
|
initTrainingName();
|
|
|
|
// 未来模板模式:展示目标日期;训练模式有 targetDate 则用它,否则今天
|
|
|
|
trainingDateText.value = isFutureTemplate.value
|
|
|
|
? dayjs(targetDate.value).format('YYYY-MM-DD dd')
|
|
|
|
: (targetDate.value ? dayjs(targetDate.value).format('YYYY-MM-DD dd') : dayjs().format('YYYY-MM-DD dd'));
|
|
|
|
|
|
|
|
trainingDateText.value = dayjs().format('YYYY-MM-DD dd');
|
|
|
|
if (!trainingStore.trainingTimeText) {
|
|
|
|
trainingStore.setTrainingTimeText(dayjs().format('HH:mm'));
|
|
|
|
}
|
|
|
|
if (isTraining) {
|
|
|
|
trainingStore.toggleTimer();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (!trainingStore.trainingTimeText) {
|
|
|
|
trainingStore.setTrainingTimeText(dayjs().format('HH:mm'));
|
|
|
|
}
|
|
|
|
if (isTraining) {
|
|
|
|
trainingStore.toggleTimer();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const initTrainingName = () => {
|
|
|
|
trainingStore.setTrainingName(actionDetail.value?.templateName || '训练');
|
|
|
|
};
|
|
|
|
const initTrainingName = () => {
|
|
|
|
trainingStore.setTrainingName(actionDetail.value?.templateName || '训练');
|
|
|
|
};
|
|
|
|
|
|
|
|
const totalSets = computed(() => {
|
|
|
|
let count = 0;
|
|
|
|
dongzuoRefs.value.forEach((ref) => {
|
|
|
|
if (ref?.totalSetCount) count += ref.totalSetCount;
|
|
|
|
});
|
|
|
|
return count;
|
|
|
|
const totalSets = computed(() => {
|
|
|
|
let count = 0;
|
|
|
|
dongzuoRefs.value.forEach((ref) => {
|
|
|
|
if (ref?.totalSetCount) count += ref.totalSetCount;
|
|
|
|
});
|
|
|
|
return count;
|
|
|
|
});
|
|
|
|
|
|
|
|
const totalCheckedWeight = computed(() => {
|
|
|
|
let sum = 0;
|
|
|
|
dongzuoRefs.value.forEach((ref) => {
|
|
|
|
if (ref?.checkedWeight) sum += ref.checkedWeight;
|
|
|
|
});
|
|
|
|
return sum;
|
|
|
|
const totalCheckedWeight = computed(() => {
|
|
|
|
let sum = 0;
|
|
|
|
dongzuoRefs.value.forEach((ref) => {
|
|
|
|
if (ref?.checkedWeight) sum += ref.checkedWeight;
|
|
|
|
});
|
|
|
|
return sum;
|
|
|
|
});
|
|
|
|
|
|
|
|
const allTotalWeight = computed(() => {
|
|
|
|
let sum = 0;
|
|
|
|
dongzuoRefs.value.forEach((ref) => {
|
|
|
|
if (ref?.totalWeight) sum += ref.totalWeight;
|
|
|
|
});
|
|
|
|
return sum;
|
|
|
|
const allTotalWeight = computed(() => {
|
|
|
|
let sum = 0;
|
|
|
|
dongzuoRefs.value.forEach((ref) => {
|
|
|
|
if (ref?.totalWeight) sum += ref.totalWeight;
|
|
|
|
});
|
|
|
|
|
|
|
|
// 获得子组件模板的数据
|
|
|
|
const dongzuoRefs = ref([]);
|
|
|
|
const setActionRef = (el, index) => {
|
|
|
|
if (el) dongzuoRefs.value[index] = el;
|
|
|
|
};
|
|
|
|
|
|
|
|
// 每个 unit 是否有勾选的组
|
|
|
|
const unitActiveStates = ref([])
|
|
|
|
const onUnitActiveChange = (index, val) => {
|
|
|
|
unitActiveStates.value[index] = val
|
|
|
|
unitActiveStates.value = [...unitActiveStates.value]
|
|
|
|
return sum;
|
|
|
|
});
|
|
|
|
|
|
|
|
// 获得子组件模板的数据
|
|
|
|
const dongzuoRefs = ref([]);
|
|
|
|
const setActionRef = (el, index) => {
|
|
|
|
if (el) dongzuoRefs.value[index] = el;
|
|
|
|
};
|
|
|
|
|
|
|
|
// 每个 unit 是否有勾选的组
|
|
|
|
const unitActiveStates = ref([])
|
|
|
|
const onUnitActiveChange = (index, val) => {
|
|
|
|
unitActiveStates.value[index] = val
|
|
|
|
unitActiveStates.value = [...unitActiveStates.value]
|
|
|
|
}
|
|
|
|
|
|
|
|
// 全部 unit 已勾选后才能提交
|
|
|
|
const allUnitsChecked = computed(() => {
|
|
|
|
const list = actionDetail.value?.units || []
|
|
|
|
if (list.length === 0) return false
|
|
|
|
if (unitActiveStates.value.length !== list.length) return false
|
|
|
|
return unitActiveStates.value.every((v) => v === true)
|
|
|
|
})
|
|
|
|
|
|
|
|
const openSummaryPopup = () => {
|
|
|
|
summaryShow.value = true;
|
|
|
|
summaryContent.value = '';
|
|
|
|
};
|
|
|
|
|
|
|
|
const saveSummary = () => {
|
|
|
|
summaryShow.value = false;
|
|
|
|
summaryContent.value = '';
|
|
|
|
};
|
|
|
|
|
|
|
|
const openTrainingNamePopup = () => {
|
|
|
|
inputNameShow.value = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
const saveTrainingName = () => {
|
|
|
|
trainingStore.setTrainingName(trainingStore.trainingName);
|
|
|
|
if (actionDetail.value) {
|
|
|
|
trainingStore.actionDetail.templateName = trainingStore.trainingName;
|
|
|
|
}
|
|
|
|
inputNameShow.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const formattedTime = computed(() =>
|
|
|
|
dayjs.duration(totalSeconds.value, 'seconds').format('HH:mm:ss'),
|
|
|
|
);
|
|
|
|
const toggleTimer = () => trainingStore.toggleTimer();
|
|
|
|
const resetTimer = () => trainingStore.resetTimer();
|
|
|
|
|
|
|
|
const timeColumns = ref([
|
|
|
|
Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0') + '时'),
|
|
|
|
Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0') + '分'),
|
|
|
|
Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0') + '秒'),
|
|
|
|
]);
|
|
|
|
|
|
|
|
const handleTimerClick = () => {
|
|
|
|
if (isPause.value) {
|
|
|
|
trainingStore.openTimePicker();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const onTimeConfirm = (e) => {
|
|
|
|
const rawValues = e.value.map((val) => parseInt(String(val).replace(/[时分秒]/g, '')));
|
|
|
|
const seconds = rawValues[0] * 3600 + rawValues[1] * 60 + rawValues[2];
|
|
|
|
trainingStore.setTotalSeconds(seconds);
|
|
|
|
trainingStore.closeTimePicker();
|
|
|
|
};
|
|
|
|
const confirmDate = (e) => {
|
|
|
|
trainingDateText.value = dayjs(e.value).format('YYYY-MM-DD dd');
|
|
|
|
datePickerShow.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const confirmTime = (e) => {
|
|
|
|
trainingStore.setTrainingTimeText(e.value);
|
|
|
|
timePickerShow.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const mapSets = (recordItems, exerciseType) =>
|
|
|
|
recordItems.map((item, index) => {
|
|
|
|
const isInterval = exerciseType === 6;
|
|
|
|
const duration = isInterval
|
|
|
|
? Number(item.duration) || 0
|
|
|
|
: Number(item.h) * 3600 + Number(item.m) * 60 + Number(item.s) || 0;
|
|
|
|
const restTime = isInterval
|
|
|
|
? Number(item.restTime) || 0
|
|
|
|
: parseInt(item.quickTimeDisplay) || 0;
|
|
|
|
return {
|
|
|
|
weight: Number(item.weight) || 0,
|
|
|
|
reps: Number(item.reps) || 0,
|
|
|
|
distance: Number(item.distance) || 0,
|
|
|
|
duration,
|
|
|
|
restTime,
|
|
|
|
setIndex: index + 1,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
// 全部 unit 已勾选后才能提交
|
|
|
|
const allUnitsChecked = computed(() => {
|
|
|
|
const list = actionDetail.value?.units || []
|
|
|
|
if (list.length === 0) return false
|
|
|
|
if (unitActiveStates.value.length !== list.length) return false
|
|
|
|
return unitActiveStates.value.every((v) => v === true)
|
|
|
|
})
|
|
|
|
|
|
|
|
const openSummaryPopup = () => {
|
|
|
|
summaryShow.value = true;
|
|
|
|
summaryContent.value = '';
|
|
|
|
};
|
|
|
|
|
|
|
|
const saveSummary = () => {
|
|
|
|
summaryShow.value = false;
|
|
|
|
summaryContent.value = '';
|
|
|
|
};
|
|
|
|
|
|
|
|
const openTrainingNamePopup = () => {
|
|
|
|
inputNameShow.value = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
const saveTrainingName = () => {
|
|
|
|
trainingStore.setTrainingName(trainingStore.trainingName);
|
|
|
|
if (actionDetail.value) {
|
|
|
|
trainingStore.actionDetail.templateName = trainingStore.trainingName;
|
|
|
|
}
|
|
|
|
inputNameShow.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const formattedTime = computed(() =>
|
|
|
|
dayjs.duration(totalSeconds.value, 'seconds').format('HH:mm:ss'),
|
|
|
|
);
|
|
|
|
const toggleTimer = () => trainingStore.toggleTimer();
|
|
|
|
const resetTimer = () => trainingStore.resetTimer();
|
|
|
|
|
|
|
|
const timeColumns = ref([
|
|
|
|
Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0') + '时'),
|
|
|
|
Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0') + '分'),
|
|
|
|
Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0') + '秒'),
|
|
|
|
]);
|
|
|
|
|
|
|
|
const handleTimerClick = () => {
|
|
|
|
if (isPause.value) {
|
|
|
|
trainingStore.openTimePicker();
|
|
|
|
const save = async () => {
|
|
|
|
// 前置校验
|
|
|
|
if (isFutureTemplate.value) {
|
|
|
|
// 未来模板模式:必须至少有一个动作
|
|
|
|
if (!hasAnyAction.value) {
|
|
|
|
uni.showToast({ title: '请先添加训练动作', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const onTimeConfirm = (e) => {
|
|
|
|
const rawValues = e.value.map((val) => parseInt(String(val).replace(/[时分秒]/g, '')));
|
|
|
|
const seconds = rawValues[0] * 3600 + rawValues[1] * 60 + rawValues[2];
|
|
|
|
trainingStore.setTotalSeconds(seconds);
|
|
|
|
trainingStore.closeTimePicker();
|
|
|
|
};
|
|
|
|
const confirmDate = (e) => {
|
|
|
|
trainingDateText.value = dayjs(e.value).format('YYYY-MM-DD dd');
|
|
|
|
datePickerShow.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const confirmTime = (e) => {
|
|
|
|
trainingStore.setTrainingTimeText(e.value);
|
|
|
|
timePickerShow.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const mapSets = (recordItems, exerciseType) =>
|
|
|
|
recordItems.map((item, index) => {
|
|
|
|
const isInterval = exerciseType === 6;
|
|
|
|
const duration = isInterval
|
|
|
|
? Number(item.duration) || 0
|
|
|
|
: Number(item.h) * 3600 + Number(item.m) * 60 + Number(item.s) || 0;
|
|
|
|
const restTime = isInterval
|
|
|
|
? Number(item.restTime) || 0
|
|
|
|
: parseInt(item.quickTimeDisplay) || 0;
|
|
|
|
return {
|
|
|
|
weight: Number(item.weight) || 0,
|
|
|
|
reps: Number(item.reps) || 0,
|
|
|
|
distance: Number(item.distance) || 0,
|
|
|
|
duration,
|
|
|
|
restTime,
|
|
|
|
setIndex: index + 1,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
const save = async () => {
|
|
|
|
// 前置校验:所有 unit 必须已勾选(顶部 ✓)才能提交
|
|
|
|
} else {
|
|
|
|
// 训练模式:所有 unit 必须已勾选(顶部 ✓)才能提交
|
|
|
|
if (!allUnitsChecked.value) {
|
|
|
|
uni.showModal({
|
|
|
|
title: '提示',
|
|
...
|
...
|
@@ -509,13 +485,66 @@ |
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
|
|
try {
|
|
|
|
// ========== 未来日期模板模式:直接从模板结构构建 units,创建模板 → 添加到日历 ==========
|
|
|
|
if (isFutureTemplate.value && targetDate.value) {
|
|
|
|
const unitList = actionDetail.value?.units || [];
|
|
|
|
|
|
|
|
// 直接从模板 actionDetail.units 提取,不依赖子组件 refs 的 recordList/superRecordMap
|
|
|
|
const templateUnits = unitList.map((unit, i) => ({
|
|
|
|
id: null,
|
|
|
|
name: i === 0 ? trainingName.value : (unit.unitName || ''),
|
|
|
|
unitType: unit.unitType,
|
|
|
|
sortOrder: i + 1,
|
|
|
|
exercises: unit.exercises,
|
|
|
|
}));
|
|
|
|
|
|
|
|
const templateRes = await TemplatesApi.createCustTemplate({
|
|
|
|
templateName: trainingName.value,
|
|
|
|
scene: 0,
|
|
|
|
templateCover: '',
|
|
|
|
templateIntroduction: '',
|
|
|
|
units: templateUnits,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (templateRes.code == 0) {
|
|
|
|
// 兼容 templateRes.data 为数字(旧版)或对象(新版)的场景
|
|
|
|
const templateId = typeof templateRes.data === 'object'
|
|
|
|
? (templateRes.data?.templateId || templateRes.data?.id || templateRes.data)
|
|
|
|
: templateRes.data;
|
|
|
|
|
|
|
|
try {
|
|
|
|
await QueryPlanApi.addPlanToCalendar({
|
|
|
|
id: templateId,
|
|
|
|
trainDateList: [targetDate.value],
|
|
|
|
});
|
|
|
|
uni.$emit('calendarDataRefresh');
|
|
|
|
uni.showToast({ title: '已添加到训练日历', icon: 'success' });
|
|
|
|
} catch {
|
|
|
|
// 添加到日历失败,但模板已保存成功,单独提示
|
|
|
|
uni.showToast({ title: '模板已创建,但添加到日历失败', icon: 'none' });
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
uni.showToast({ title: templateRes.msg || '添加失败', icon: 'none' });
|
|
|
|
}
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
uni.navigateBack();
|
|
|
|
trainingStore.clearTrainingStore();
|
|
|
|
}, 1500);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await nextTick();
|
|
|
|
// ========== 训练/编辑模式:从子组件 refs 提取训练数据 ==========
|
|
|
|
const units = [];
|
|
|
|
const unitList = actionDetail.value?.units || [];
|
|
|
|
const unitListForTrain = actionDetail.value?.units || [];
|
|
|
|
|
|
|
|
for (let i = 0; i < unitList.length; i++) {
|
|
|
|
const unit = unitList[i];
|
|
|
|
for (let i = 0; i < unitListForTrain.length; i++) {
|
|
|
|
const unit = unitListForTrain[i];
|
|
|
|
const child = dongzuoRefs.value[i];
|
|
|
|
if (!child) continue;
|
|
|
|
|
|
...
|
...
|
@@ -548,7 +577,7 @@ |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 检查是否有打勾(已完成)的动作,只有打勾的动作才会被提交
|
|
|
|
// 检查是否有有效数据
|
|
|
|
const hasAnyChecked = units.some(u =>
|
|
|
|
u.exercises.some(e => e.sets && e.sets.length > 0)
|
|
|
|
);
|
|
...
|
...
|
@@ -570,562 +599,610 @@ |
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (trainingStore.isTraining) {
|
|
|
|
await TrainingApi.createTrainHistory({ units, allCompleted: allUnitsChecked.value });
|
|
|
|
uni.showToast({ title: '训练提交成功', icon: 'success' });
|
|
|
|
} else if (trainingStore.type === 3 && trainingStore.dailyTemplateId) {
|
|
|
|
await dailytemplateApi.updateDailyTemplate({
|
|
|
|
dailyTemplateId: trainingStore.dailyTemplateId,
|
|
|
|
units,
|
|
|
|
allCompleted: allUnitsChecked.value,
|
|
|
|
});
|
|
|
|
uni.showToast({ title: '模板修改成功', icon: 'success' });
|
|
|
|
} else {
|
|
|
|
await TrainingApi.createTrainHistory({ units, allCompleted: allUnitsChecked.value });
|
|
|
|
uni.showToast({ title: '训练提交成功', icon: 'success' });
|
|
|
|
}
|
|
|
|
setTimeout(() => {
|
|
|
|
uni.navigateBack();
|
|
|
|
trainingStore.clearTrainingStore();
|
|
|
|
}, 1500);
|
|
|
|
} catch (err) {
|
|
|
|
console.error('保存失败:', err);
|
|
|
|
uni.showToast({ title: '保存失败', icon: 'none' });
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// 删除训练
|
|
|
|
const deleteTraining = () => {
|
|
|
|
uni.showModal({
|
|
|
|
title: '确认删除',
|
|
|
|
content: '确定要删除本次训练记录吗?删除后无法恢复',
|
|
|
|
confirmText: '确认删除',
|
|
|
|
confirmColor: '#FF3B30',
|
|
|
|
cancelText: '取消',
|
|
|
|
success: (res) => {
|
|
|
|
if (res.confirm) {
|
|
|
|
uni.showToast({ title: '删除成功', icon: 'success', duration: 1500 });
|
|
|
|
setTimeout(() => {
|
|
|
|
uni.navigateBack();
|
|
|
|
trainingStore.clearTrainingStore();
|
|
|
|
}, 1500);
|
|
|
|
if (trainingStore.isTraining) {
|
|
|
|
// 自由训练到空日期(有 targetDate 但无 dailyTemplateId):
|
|
|
|
// 需要先创建模板并添加到目标日期,才能让日历在对应日期上显示该训练
|
|
|
|
if (targetDate.value && !trainingStore.dailyTemplateId) {
|
|
|
|
try {
|
|
|
|
// 1. 基于训练动作结构创建个人模板
|
|
|
|
const templateUnits = unitListForTrain.map((u, idx) => ({
|
|
|
|
id: null,
|
|
|
|
name: idx === 0 ? trainingName.value : (u.unitName || ''),
|
|
|
|
unitType: u.unitType,
|
|
|
|
sortOrder: idx + 1,
|
|
|
|
exercises: u.exercises,
|
|
|
|
}));
|
|
|
|
|
|
|
|
const templateRes = await TemplatesApi.createCustTemplate({
|
|
|
|
templateName: trainingName.value,
|
|
|
|
scene: 0,
|
|
|
|
templateCover: '',
|
|
|
|
templateIntroduction: '',
|
|
|
|
units: templateUnits,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (templateRes.code == 0) {
|
|
|
|
const templateId = typeof templateRes.data === 'object'
|
|
|
|
? (templateRes.data?.templateId || templateRes.data?.id || templateRes.data)
|
|
|
|
: templateRes.data;
|
|
|
|
|
|
|
|
// 2. 将模板添加到目标日期日历,确保训练显示在所选日期
|
|
|
|
await QueryPlanApi.addPlanToCalendar({
|
|
|
|
id: templateId,
|
|
|
|
trainDateList: [targetDate.value],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.error('创建自由训练每日模板失败:', e);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const addActionsPopup = () => {
|
|
|
|
addActionsShow.value = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
const openMin = () => {
|
|
|
|
trainingStore.min = true;
|
|
|
|
trainingSnapshot.data = JSON.parse(
|
|
|
|
JSON.stringify({
|
|
|
|
id: trainingStore.id,
|
|
|
|
type: trainingStore.type,
|
|
|
|
actionDetail: trainingStore.actionDetail,
|
|
|
|
loading: trainingStore.loading,
|
|
|
|
unitRecords: trainingStore.unitRecords,
|
|
|
|
trainingName: trainingStore.trainingName,
|
|
|
|
totalSeconds: trainingStore.totalSeconds,
|
|
|
|
isPause: trainingStore.isPause,
|
|
|
|
showPicker: trainingStore.showPicker,
|
|
|
|
defaultTimeIndex: trainingStore.defaultTimeIndex,
|
|
|
|
trainingTimeText: trainingStore.trainingTimeText,
|
|
|
|
min: trainingStore.min,
|
|
|
|
isSystem: trainingStore.isSystem,
|
|
|
|
}
|
|
|
|
|
|
|
|
await TrainingApi.createTrainHistory({ units, allCompleted: allUnitsChecked.value, trainingDate: reliableTrainingDate.value });
|
|
|
|
uni.showToast({ title: '训练提交成功', icon: 'success' });
|
|
|
|
uni.$emit('calendarDataRefresh');
|
|
|
|
} else if (trainingStore.type === 3 && trainingStore.dailyTemplateId) {
|
|
|
|
await dailytemplateApi.updateDailyTemplate({
|
|
|
|
dailyTemplateId: trainingStore.dailyTemplateId,
|
|
|
|
isTraining: trainingStore.isTraining,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
units,
|
|
|
|
allCompleted: allUnitsChecked.value,
|
|
|
|
});
|
|
|
|
uni.showToast({ title: '模板修改成功', icon: 'success' });
|
|
|
|
uni.$emit('calendarDataRefresh');
|
|
|
|
} else {
|
|
|
|
await TrainingApi.createTrainHistory({ units, allCompleted: allUnitsChecked.value, trainingDate: reliableTrainingDate.value });
|
|
|
|
uni.showToast({ title: '训练提交成功', icon: 'success' });
|
|
|
|
uni.$emit('calendarDataRefresh');
|
|
|
|
}
|
|
|
|
setTimeout(() => {
|
|
|
|
uni.navigateBack();
|
|
|
|
trainingStore.clearTrainingStore();
|
|
|
|
}, 1500);
|
|
|
|
} catch (err) {
|
|
|
|
console.error('保存失败:', err);
|
|
|
|
uni.showToast({ title: '保存失败', icon: 'none' });
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// 删除训练
|
|
|
|
const deleteTraining = () => {
|
|
|
|
uni.showModal({
|
|
|
|
title: '确认删除',
|
|
|
|
content: '确定要删除本次训练记录吗?删除后无法恢复',
|
|
|
|
confirmText: '确认删除',
|
|
|
|
confirmColor: '#FF3B30',
|
|
|
|
cancelText: '取消',
|
|
|
|
success: (res) => {
|
|
|
|
if (res.confirm) {
|
|
|
|
uni.showToast({ title: '删除成功', icon: 'success', duration: 1500 });
|
|
|
|
setTimeout(() => {
|
|
|
|
uni.navigateBack();
|
|
|
|
trainingStore.clearTrainingStore();
|
|
|
|
}, 1500);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const addActionsPopup = () => {
|
|
|
|
addActionsShow.value = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
const openMin = () => {
|
|
|
|
trainingStore.min = true;
|
|
|
|
trainingSnapshot.data = JSON.parse(
|
|
|
|
JSON.stringify({
|
|
|
|
id: trainingStore.id,
|
|
|
|
type: trainingStore.type,
|
|
|
|
actionDetail: trainingStore.actionDetail,
|
|
|
|
loading: trainingStore.loading,
|
|
|
|
unitRecords: trainingStore.unitRecords,
|
|
|
|
trainingName: trainingStore.trainingName,
|
|
|
|
totalSeconds: trainingStore.totalSeconds,
|
|
|
|
isPause: trainingStore.isPause,
|
|
|
|
showPicker: trainingStore.showPicker,
|
|
|
|
defaultTimeIndex: trainingStore.defaultTimeIndex,
|
|
|
|
trainingTimeText: trainingStore.trainingTimeText,
|
|
|
|
min: trainingStore.min,
|
|
|
|
isSystem: trainingStore.isSystem,
|
|
|
|
dailyTemplateId: trainingStore.dailyTemplateId,
|
|
|
|
isTraining: trainingStore.isTraining,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
// 2. 记录时间戳和计时器状态
|
|
|
|
trainingSnapshot.timestamp = Date.now();
|
|
|
|
trainingSnapshot.timerWasRunning = !trainingStore.isPause;
|
|
|
|
trainingSnapshot.visible = true;
|
|
|
|
// 2. 记录时间戳和计时器状态
|
|
|
|
trainingSnapshot.timestamp = Date.now();
|
|
|
|
trainingSnapshot.timerWasRunning = !trainingStore.isPause;
|
|
|
|
trainingSnapshot.visible = true;
|
|
|
|
|
|
|
|
if (!trainingStore.isPause) {
|
|
|
|
trainingStore.toggleTimer();
|
|
|
|
}
|
|
|
|
if (!trainingStore.isPause) {
|
|
|
|
trainingStore.toggleTimer();
|
|
|
|
}
|
|
|
|
|
|
|
|
uni.navigateBack();
|
|
|
|
};
|
|
|
|
uni.navigateBack();
|
|
|
|
};
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
.container {
|
|
|
|
min-height: 100vh;
|
|
|
|
background-color: #242424;
|
|
|
|
padding-bottom: 120rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.status-header {
|
|
|
|
.container {
|
|
|
|
min-height: 100vh;
|
|
|
|
background-color: #242424;
|
|
|
|
padding-bottom: 120rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.status-header {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
padding: 20rpx 30rpx;
|
|
|
|
|
|
|
|
.timer-box {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
padding: 20rpx 30rpx;
|
|
|
|
|
|
|
|
.timer-box {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
background-color: #2c2c2e;
|
|
|
|
padding: 10rpx 24rpx;
|
|
|
|
width: 120rpx;
|
|
|
|
border-radius: 40rpx;
|
|
|
|
position: relative;
|
|
|
|
background-color: #2c2c2e;
|
|
|
|
padding: 10rpx 24rpx;
|
|
|
|
width: 120rpx;
|
|
|
|
border-radius: 40rpx;
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
.time {
|
|
|
|
color: #fff;
|
|
|
|
font-weight: bold;
|
|
|
|
font-size: 32rpx;
|
|
|
|
margin-right: 8rpx;
|
|
|
|
}
|
|
|
|
.time {
|
|
|
|
color: #fff;
|
|
|
|
font-weight: bold;
|
|
|
|
font-size: 32rpx;
|
|
|
|
margin-right: 8rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.triangle-icon {
|
|
|
|
position: absolute;
|
|
|
|
right: 20rpx;
|
|
|
|
bottom: 15rpx;
|
|
|
|
transform: rotate(-50deg);
|
|
|
|
width: 0;
|
|
|
|
height: 0;
|
|
|
|
border-left: 10rpx solid transparent;
|
|
|
|
border-right: 10rpx solid transparent;
|
|
|
|
border-top: 12rpx solid #fbdf09;
|
|
|
|
}
|
|
|
|
.triangle-icon {
|
|
|
|
position: absolute;
|
|
|
|
right: 20rpx;
|
|
|
|
bottom: 15rpx;
|
|
|
|
transform: rotate(-50deg);
|
|
|
|
width: 0;
|
|
|
|
height: 0;
|
|
|
|
border-left: 10rpx solid transparent;
|
|
|
|
border-right: 10rpx solid transparent;
|
|
|
|
border-top: 12rpx solid #fbdf09;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.finish-btn {
|
|
|
|
background-color: #fbdf09;
|
|
|
|
color: #000;
|
|
|
|
padding: 10rpx 40rpx;
|
|
|
|
border-radius: 30rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
font-size: 28rpx;
|
|
|
|
transition: opacity 0.2s;
|
|
|
|
.timer-box-placeholder {
|
|
|
|
width: 120rpx;
|
|
|
|
flex-shrink: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.disabled {
|
|
|
|
background-color: #555;
|
|
|
|
color: #999;
|
|
|
|
opacity: 0.6;
|
|
|
|
pointer-events: none;
|
|
|
|
}
|
|
|
|
.finish-btn {
|
|
|
|
background-color: #fbdf09;
|
|
|
|
color: #000;
|
|
|
|
padding: 10rpx 40rpx;
|
|
|
|
border-radius: 30rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
font-size: 28rpx;
|
|
|
|
transition: opacity 0.2s;
|
|
|
|
|
|
|
|
&.disabled {
|
|
|
|
background-color: #555;
|
|
|
|
color: #999;
|
|
|
|
opacity: 0.6;
|
|
|
|
pointer-events: none;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.action-list {
|
|
|
|
padding: 0 0 20rpx;
|
|
|
|
.action-list {
|
|
|
|
padding: 0 0 20rpx;
|
|
|
|
|
|
|
|
.header {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
|
|
.header {
|
|
|
|
.title-left {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
|
|
.title-left {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
.title-name {
|
|
|
|
color: #fff;
|
|
|
|
font-size: 36rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
margin-left: 10rpx;
|
|
|
|
}
|
|
|
|
.title-name {
|
|
|
|
color: #fff;
|
|
|
|
font-size: 36rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
margin-left: 10rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.title-stats {
|
|
|
|
color: #8e8e93;
|
|
|
|
font-size: 24rpx;
|
|
|
|
}
|
|
|
|
.title-stats {
|
|
|
|
color: #8e8e93;
|
|
|
|
font-size: 24rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.empty-state {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
padding: 120rpx 40rpx;
|
|
|
|
color: #8e8e93;
|
|
|
|
text-align: center;
|
|
|
|
|
|
|
|
.empty-text {
|
|
|
|
font-size: 32rpx;
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.empty-state {
|
|
|
|
.empty-subtext {
|
|
|
|
font-size: 28rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.tip-bubble {
|
|
|
|
position: fixed;
|
|
|
|
bottom: 200rpx;
|
|
|
|
// #ifdef H5
|
|
|
|
bottom: 160rpx;
|
|
|
|
// #endif
|
|
|
|
|
|
|
|
left: 52%;
|
|
|
|
transform: translateX(-50%);
|
|
|
|
background-color: #000;
|
|
|
|
color: #fff;
|
|
|
|
padding: 16rpx 24rpx;
|
|
|
|
border-radius: 12rpx;
|
|
|
|
font-size: 28rpx;
|
|
|
|
z-index: 9999;
|
|
|
|
|
|
|
|
&::after {
|
|
|
|
content: '';
|
|
|
|
position: absolute;
|
|
|
|
bottom: -10rpx;
|
|
|
|
left: 50%;
|
|
|
|
transform: translateX(-50%);
|
|
|
|
border-left: 10rpx solid transparent;
|
|
|
|
border-right: 10rpx solid transparent;
|
|
|
|
border-top: 10rpx solid #000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.footer-bar {
|
|
|
|
position: fixed;
|
|
|
|
bottom: 0;
|
|
|
|
left: 0;
|
|
|
|
right: 0;
|
|
|
|
height: 110rpx;
|
|
|
|
background-color: #2c2c2e;
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-around;
|
|
|
|
align-items: center;
|
|
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|
|
|
z-index: 99;
|
|
|
|
|
|
|
|
.footer-item {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
padding: 120rpx 40rpx;
|
|
|
|
color: #8e8e93;
|
|
|
|
text-align: center;
|
|
|
|
|
|
|
|
.empty-text {
|
|
|
|
.count {
|
|
|
|
color: #fff;
|
|
|
|
font-size: 32rpx;
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
}
|
|
|
|
|
|
|
|
.empty-subtext {
|
|
|
|
font-size: 28rpx;
|
|
|
|
.label {
|
|
|
|
color: #8e8e93;
|
|
|
|
font-size: 20rpx;
|
|
|
|
margin-top: 4rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.tip-bubble {
|
|
|
|
position: fixed;
|
|
|
|
bottom: 230rpx;
|
|
|
|
left: 52%;
|
|
|
|
transform: translateX(-50%);
|
|
|
|
background-color: #000;
|
|
|
|
color: #fff;
|
|
|
|
padding: 16rpx 24rpx;
|
|
|
|
border-radius: 12rpx;
|
|
|
|
font-size: 28rpx;
|
|
|
|
.add-btn-wrapper {
|
|
|
|
margin-top: -60rpx;
|
|
|
|
width: 120rpx;
|
|
|
|
height: 120rpx;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
position: relative;
|
|
|
|
z-index: 9999;
|
|
|
|
|
|
|
|
&::after {
|
|
|
|
content: '';
|
|
|
|
position: absolute;
|
|
|
|
bottom: -10rpx;
|
|
|
|
left: 50%;
|
|
|
|
transform: translateX(-50%);
|
|
|
|
border-left: 10rpx solid transparent;
|
|
|
|
border-right: 10rpx solid transparent;
|
|
|
|
border-top: 10rpx solid #000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.footer-bar {
|
|
|
|
position: fixed;
|
|
|
|
bottom: 0;
|
|
|
|
left: 0;
|
|
|
|
right: 0;
|
|
|
|
height: 110rpx;
|
|
|
|
background-color: #2c2c2e;
|
|
|
|
.add-btn {
|
|
|
|
width: 100rpx;
|
|
|
|
height: 100rpx;
|
|
|
|
background-color: #ffffff;
|
|
|
|
border-radius: 50%;
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-around;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|
|
|
z-index: 99;
|
|
|
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.3);
|
|
|
|
position: relative;
|
|
|
|
z-index: 99999;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.footer-item {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
.time-pop-container {
|
|
|
|
padding: 40rpx 30rpx;
|
|
|
|
color: #fff;
|
|
|
|
|
|
|
|
.count {
|
|
|
|
color: #fff;
|
|
|
|
font-size: 32rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
}
|
|
|
|
.pop-title {
|
|
|
|
font-size: 32rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
text-align: center;
|
|
|
|
margin-bottom: 40rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.label {
|
|
|
|
color: #8e8e93;
|
|
|
|
font-size: 20rpx;
|
|
|
|
margin-top: 4rpx;
|
|
|
|
}
|
|
|
|
.setting-grid {
|
|
|
|
display: flex;
|
|
|
|
gap: 20rpx;
|
|
|
|
|
|
|
|
.flex-2 {
|
|
|
|
flex: 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
.add-btn-wrapper {
|
|
|
|
margin-top: -60rpx;
|
|
|
|
width: 120rpx;
|
|
|
|
height: 120rpx;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
position: relative;
|
|
|
|
z-index: 9999;
|
|
|
|
.flex-1 {
|
|
|
|
flex: 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
.add-btn {
|
|
|
|
width: 100rpx;
|
|
|
|
height: 100rpx;
|
|
|
|
background-color: #ffffff;
|
|
|
|
border-radius: 50%;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.3);
|
|
|
|
position: relative;
|
|
|
|
z-index: 99999;
|
|
|
|
.group-label {
|
|
|
|
font-size: 24rpx;
|
|
|
|
color: #8e8e93;
|
|
|
|
margin-bottom: 12rpx;
|
|
|
|
display: block;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.time-pop-container {
|
|
|
|
padding: 40rpx 30rpx;
|
|
|
|
color: #fff;
|
|
|
|
.dark-input-card {
|
|
|
|
background-color: #1c1c1e;
|
|
|
|
padding: 24rpx;
|
|
|
|
border-radius: 12rpx;
|
|
|
|
font-size: 28rpx;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.pop-title {
|
|
|
|
font-size: 32rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
text-align: center;
|
|
|
|
margin-bottom: 40rpx;
|
|
|
|
}
|
|
|
|
.sub-tips {
|
|
|
|
font-size: 22rpx;
|
|
|
|
color: #636366;
|
|
|
|
margin-top: 16rpx;
|
|
|
|
margin-bottom: 40rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
font-size: 26rpx;
|
|
|
|
color: #8e8e93;
|
|
|
|
margin-bottom: 12rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.timer-card {
|
|
|
|
justify-content: space-between;
|
|
|
|
padding: 20rpx 15rpx;
|
|
|
|
height: 80rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
.setting-grid {
|
|
|
|
.timer-left {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
gap: 20rpx;
|
|
|
|
padding-left: 30rpx;
|
|
|
|
|
|
|
|
.flex-2 {
|
|
|
|
flex: 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
.flex-1 {
|
|
|
|
flex: 1;
|
|
|
|
.timer-num {
|
|
|
|
font-size: 30rpx;
|
|
|
|
font-family: monospace;
|
|
|
|
}
|
|
|
|
|
|
|
|
.group-label {
|
|
|
|
font-size: 24rpx;
|
|
|
|
color: #8e8e93;
|
|
|
|
margin-bottom: 12rpx;
|
|
|
|
display: block;
|
|
|
|
.timer-status {
|
|
|
|
font-size: 22rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.dark-input-card {
|
|
|
|
background-color: #1c1c1e;
|
|
|
|
padding: 24rpx;
|
|
|
|
border-radius: 12rpx;
|
|
|
|
font-size: 28rpx;
|
|
|
|
.timer-right {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.sub-tips {
|
|
|
|
font-size: 22rpx;
|
|
|
|
color: #636366;
|
|
|
|
margin-top: 16rpx;
|
|
|
|
margin-bottom: 40rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
font-size: 26rpx;
|
|
|
|
color: #8e8e93;
|
|
|
|
margin-bottom: 12rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.timer-card {
|
|
|
|
justify-content: space-between;
|
|
|
|
padding: 20rpx 15rpx;
|
|
|
|
height: 80rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
.timer-left {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
gap: 20rpx;
|
|
|
|
padding-left: 30rpx;
|
|
|
|
|
|
|
|
.timer-num {
|
|
|
|
font-size: 30rpx;
|
|
|
|
font-family: monospace;
|
|
|
|
}
|
|
|
|
|
|
|
|
.timer-status {
|
|
|
|
font-size: 22rpx;
|
|
|
|
}
|
|
|
|
gap: 16rpx;
|
|
|
|
|
|
|
|
.action-btn {
|
|
|
|
height: 60rpx;
|
|
|
|
padding: 0 20rpx;
|
|
|
|
line-height: 60rpx;
|
|
|
|
border-radius: 8rpx;
|
|
|
|
font-size: 22rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
}
|
|
|
|
|
|
|
|
.timer-right {
|
|
|
|
display: flex;
|
|
|
|
gap: 16rpx;
|
|
|
|
|
|
|
|
.action-btn {
|
|
|
|
height: 60rpx;
|
|
|
|
padding: 0 20rpx;
|
|
|
|
line-height: 60rpx;
|
|
|
|
border-radius: 8rpx;
|
|
|
|
font-size: 22rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
}
|
|
|
|
|
|
|
|
.gray-btn {
|
|
|
|
background-color: #454545;
|
|
|
|
color: #fff;
|
|
|
|
}
|
|
|
|
|
|
|
|
.green-btn {
|
|
|
|
background-color: #213929;
|
|
|
|
color: #2f9f3f;
|
|
|
|
}
|
|
|
|
.gray-btn {
|
|
|
|
background-color: #454545;
|
|
|
|
color: #fff;
|
|
|
|
}
|
|
|
|
|
|
|
|
.bgactve {
|
|
|
|
background-color: #4c4d3b;
|
|
|
|
}
|
|
|
|
.green-btn {
|
|
|
|
background-color: #213929;
|
|
|
|
color: #2f9f3f;
|
|
|
|
}
|
|
|
|
|
|
|
|
.active {
|
|
|
|
color: #fbdf09;
|
|
|
|
}
|
|
|
|
.bgactve {
|
|
|
|
background-color: #4c4d3b;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.pull-handle {
|
|
|
|
width: 80rpx;
|
|
|
|
height: 8rpx;
|
|
|
|
background-color: #48484a;
|
|
|
|
border-radius: 4rpx;
|
|
|
|
margin: 40rpx auto 0;
|
|
|
|
.active {
|
|
|
|
color: #fbdf09;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.summaryPopup {
|
|
|
|
width: 100vw;
|
|
|
|
height: 75vh;
|
|
|
|
background-color: #2c2c2e;
|
|
|
|
border-radius: 24rpx;
|
|
|
|
overflow: hidden;
|
|
|
|
.pull-handle {
|
|
|
|
width: 80rpx;
|
|
|
|
height: 8rpx;
|
|
|
|
background-color: #48484a;
|
|
|
|
border-radius: 4rpx;
|
|
|
|
margin: 40rpx auto 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.popup-header {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
padding: 30rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
.summaryPopup {
|
|
|
|
width: 100vw;
|
|
|
|
height: 75vh;
|
|
|
|
background-color: #2c2c2e;
|
|
|
|
border-radius: 24rpx;
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
.popup-title {
|
|
|
|
font-size: 32rpx;
|
|
|
|
font-weight: 600;
|
|
|
|
color: #ffffff;
|
|
|
|
}
|
|
|
|
.popup-header {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
padding: 30rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
.save-btn {
|
|
|
|
background-color: #f8d714;
|
|
|
|
color: #000;
|
|
|
|
font-size: 28rpx;
|
|
|
|
font-weight: 500;
|
|
|
|
padding: 12rpx 30rpx;
|
|
|
|
border-radius: 30rpx;
|
|
|
|
}
|
|
|
|
.popup-title {
|
|
|
|
font-size: 32rpx;
|
|
|
|
font-weight: 600;
|
|
|
|
color: #ffffff;
|
|
|
|
}
|
|
|
|
|
|
|
|
.summary-textarea {
|
|
|
|
width: calc(100% - 60rpx);
|
|
|
|
margin: 0 30rpx 30rpx;
|
|
|
|
min-height: 300rpx;
|
|
|
|
background-color: #1f1f1f;
|
|
|
|
border-radius: 12rpx;
|
|
|
|
padding: 20rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
color: #ffffff;
|
|
|
|
.save-btn {
|
|
|
|
background-color: #f8d714;
|
|
|
|
color: #000;
|
|
|
|
font-size: 28rpx;
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
|
|
&::placeholder {
|
|
|
|
color: #8e8e93;
|
|
|
|
}
|
|
|
|
font-weight: 500;
|
|
|
|
padding: 12rpx 30rpx;
|
|
|
|
border-radius: 30rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.namePopup {
|
|
|
|
width: 100%;
|
|
|
|
height: 75vh;
|
|
|
|
background-color: #2c2c2e;
|
|
|
|
border-radius: 24rpx;
|
|
|
|
overflow: hidden;
|
|
|
|
padding: 30rpx;
|
|
|
|
.summary-textarea {
|
|
|
|
width: calc(100% - 60rpx);
|
|
|
|
margin: 0 30rpx 30rpx;
|
|
|
|
min-height: 300rpx;
|
|
|
|
background-color: #1f1f1f;
|
|
|
|
border-radius: 12rpx;
|
|
|
|
padding: 20rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
color: #ffffff;
|
|
|
|
font-size: 28rpx;
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
|
|
.popup-header {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
margin-bottom: 30rpx;
|
|
|
|
|
|
|
|
.popup-title {
|
|
|
|
font-size: 32rpx;
|
|
|
|
font-weight: 600;
|
|
|
|
color: #ffffff;
|
|
|
|
}
|
|
|
|
&::placeholder {
|
|
|
|
color: #8e8e93;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.namePopup {
|
|
|
|
width: 100%;
|
|
|
|
height: 75vh;
|
|
|
|
background-color: #2c2c2e;
|
|
|
|
border-radius: 24rpx;
|
|
|
|
overflow: hidden;
|
|
|
|
padding: 30rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
.popup-header {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
margin-bottom: 30rpx;
|
|
|
|
|
|
|
|
.save-btn {
|
|
|
|
background-color: #f8d714;
|
|
|
|
color: #000;
|
|
|
|
font-size: 28rpx;
|
|
|
|
font-weight: 500;
|
|
|
|
padding: 12rpx 30rpx;
|
|
|
|
border-radius: 30rpx;
|
|
|
|
}
|
|
|
|
.popup-title {
|
|
|
|
font-size: 32rpx;
|
|
|
|
font-weight: 600;
|
|
|
|
color: #ffffff;
|
|
|
|
}
|
|
|
|
|
|
|
|
.name-input {
|
|
|
|
width: calc(100% - 60rpx);
|
|
|
|
margin: 0 auto;
|
|
|
|
background-color: #1f1f1f;
|
|
|
|
border-radius: 12rpx;
|
|
|
|
padding: 20rpx;
|
|
|
|
.save-btn {
|
|
|
|
background-color: #f8d714;
|
|
|
|
color: #000;
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #fff;
|
|
|
|
min-height: 220rpx;
|
|
|
|
font-weight: 500;
|
|
|
|
padding: 12rpx 30rpx;
|
|
|
|
border-radius: 30rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
&::placeholder {
|
|
|
|
color: #8e8e93;
|
|
|
|
}
|
|
|
|
.name-input {
|
|
|
|
width: calc(100% - 60rpx);
|
|
|
|
margin: 0 auto;
|
|
|
|
background-color: #1f1f1f;
|
|
|
|
border-radius: 12rpx;
|
|
|
|
padding: 20rpx;
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #fff;
|
|
|
|
min-height: 220rpx;
|
|
|
|
|
|
|
|
&::placeholder {
|
|
|
|
color: #8e8e93;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ===================== 组间休息倒计时 =====================
|
|
|
|
.rest-countdown {
|
|
|
|
position: fixed;
|
|
|
|
bottom: 180rpx;
|
|
|
|
right: 30rpx;
|
|
|
|
z-index: 100;
|
|
|
|
animation: countdown-enter 0.3s ease-out;
|
|
|
|
}
|
|
|
|
|
|
|
|
@keyframes countdown-enter {
|
|
|
|
from {
|
|
|
|
opacity: 0;
|
|
|
|
transform: scale(0.5);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ===================== 组间休息倒计时 =====================
|
|
|
|
.rest-countdown {
|
|
|
|
position: fixed;
|
|
|
|
bottom: 180rpx;
|
|
|
|
right: 30rpx;
|
|
|
|
z-index: 100;
|
|
|
|
animation: countdown-enter 0.3s ease-out;
|
|
|
|
to {
|
|
|
|
opacity: 1;
|
|
|
|
transform: scale(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.countdown-circle {
|
|
|
|
position: relative;
|
|
|
|
width: 160rpx;
|
|
|
|
height: 160rpx;
|
|
|
|
border-radius: 50%;
|
|
|
|
border: 6rpx solid #4caf50;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
|
|
|
|
.ring-svg {
|
|
|
|
position: absolute;
|
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
@keyframes countdown-enter {
|
|
|
|
from {
|
|
|
|
opacity: 0;
|
|
|
|
transform: scale(0.5);
|
|
|
|
}
|
|
|
|
.ring-bg {
|
|
|
|
fill: none;
|
|
|
|
stroke: #2c2c2e;
|
|
|
|
stroke-width: 6;
|
|
|
|
}
|
|
|
|
|
|
|
|
to {
|
|
|
|
opacity: 1;
|
|
|
|
transform: scale(1);
|
|
|
|
}
|
|
|
|
.ring-progress {
|
|
|
|
fill: none;
|
|
|
|
stroke: #4caf50;
|
|
|
|
stroke-width: 6;
|
|
|
|
stroke-linecap: round;
|
|
|
|
transition: stroke-dashoffset 0.5s linear;
|
|
|
|
}
|
|
|
|
|
|
|
|
.countdown-circle {
|
|
|
|
position: relative;
|
|
|
|
width: 160rpx;
|
|
|
|
height: 160rpx;
|
|
|
|
border-radius: 50%;
|
|
|
|
border: 6rpx solid #4caf50;
|
|
|
|
.countdown-inner {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
position: relative;
|
|
|
|
z-index: 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
.ring-svg {
|
|
|
|
position: absolute;
|
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.ring-bg {
|
|
|
|
fill: none;
|
|
|
|
stroke: #2c2c2e;
|
|
|
|
stroke-width: 6;
|
|
|
|
}
|
|
|
|
|
|
|
|
.ring-progress {
|
|
|
|
fill: none;
|
|
|
|
stroke: #4caf50;
|
|
|
|
stroke-width: 6;
|
|
|
|
stroke-linecap: round;
|
|
|
|
transition: stroke-dashoffset 0.5s linear;
|
|
|
|
}
|
|
|
|
|
|
|
|
.countdown-inner {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
position: relative;
|
|
|
|
z-index: 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
.rest-time {
|
|
|
|
font-size: 36rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
color: #ffffff;
|
|
|
|
font-family: monospace;
|
|
|
|
}
|
|
|
|
.rest-time {
|
|
|
|
font-size: 36rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
color: #ffffff;
|
|
|
|
font-family: monospace;
|
|
|
|
}
|
|
|
|
|
|
|
|
.rest-total {
|
|
|
|
font-size: 20rpx;
|
|
|
|
color: #8e8e93;
|
|
|
|
margin-top: 4rpx;
|
|
|
|
}
|
|
|
|
.rest-total {
|
|
|
|
font-size: 20rpx;
|
|
|
|
color: #8e8e93;
|
|
|
|
margin-top: 4rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style> |
...
|
...
|
|