Showing
9 changed files
with
4394 additions
and
76 deletions
Too many changes to show.
To preserve performance only 9 of 9+ files are displayed.
pages/TrainingFloating.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="floating-train-btn" v-if="trainingStore.min" @click="goToTrainingPage"> | ||
| 3 | + <view class="icon-wrap"> | ||
| 4 | + <up-icon name="plus-circle" color="#fff" size="24"></up-icon> | ||
| 5 | + </view> | ||
| 6 | + <view class="text-wrap"> | ||
| 7 | + <text class="status-text">训练中</text> | ||
| 8 | + <text class="time-text">{{ formattedStartTime }}</text> | ||
| 9 | + </view> | ||
| 10 | + </view> | ||
| 11 | +</template> | ||
| 12 | + | ||
| 13 | +<script setup> | ||
| 14 | +import { computed, watch, onMounted } from 'vue'; | ||
| 15 | +import { useTrainingStore } from '@/sheep/store/trainingStore'; | ||
| 16 | + | ||
| 17 | +const trainingStore = useTrainingStore(); | ||
| 18 | + | ||
| 19 | +// 格式化显示“开始时间” | ||
| 20 | +const formattedStartTime = computed(() => trainingStore.trainingTimeText); | ||
| 21 | + | ||
| 22 | +// 跳转到训练页面 | ||
| 23 | +const goToTrainingPage = () => { | ||
| 24 | + // 悬浮球消失 | ||
| 25 | + trainingStore.min = false; | ||
| 26 | + uni.navigateTo({ | ||
| 27 | + url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${trainingStore.id}&type=${trainingStore.type}`, | ||
| 28 | + }); | ||
| 29 | +}; | ||
| 30 | + | ||
| 31 | +watch(() => trainingStore.min, (newVal) => { | ||
| 32 | + console.log('🟢 悬浮球组件监听到 min 变化:', newVal); | ||
| 33 | +}, { immediate: true }); | ||
| 34 | + | ||
| 35 | +// 组件挂载时打印 | ||
| 36 | +onMounted(() => { | ||
| 37 | + console.log('🟢 悬浮球组件已挂载,当前 min 值:', trainingStore.min); | ||
| 38 | +}); | ||
| 39 | +</script> | ||
| 40 | + | ||
| 41 | +<style lang="scss" scoped> | ||
| 42 | +.floating-train-btn { | ||
| 43 | + position: fixed; | ||
| 44 | + right: 20rpx; | ||
| 45 | + bottom: 200rpx; | ||
| 46 | + display: flex; | ||
| 47 | + align-items: center; | ||
| 48 | + background-color: #39d353; | ||
| 49 | + border-radius: 60rpx; | ||
| 50 | + padding: 16rpx 20rpx; | ||
| 51 | + box-shadow: 0 8rpx 20rpx rgba(57, 211, 83, 0.3); | ||
| 52 | + z-index: 9999; | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +.icon-wrap { | ||
| 56 | + margin-right: 12rpx; | ||
| 57 | + display: flex; | ||
| 58 | + align-items: center; | ||
| 59 | + justify-content: center; | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +.text-wrap { | ||
| 63 | + display: flex; | ||
| 64 | + flex-direction: column; | ||
| 65 | + color: #fff; | ||
| 66 | +} | ||
| 67 | + | ||
| 68 | +.status-text { | ||
| 69 | + font-size: 26rpx; | ||
| 70 | + font-weight: bold; | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | +.time-text { | ||
| 74 | + font-size: 22rpx; | ||
| 75 | + opacity: 0.9; | ||
| 76 | +} | ||
| 77 | +</style> |
| 1 | <template> | 1 | <template> |
| 2 | <view class="charts-box"> | 2 | <view class="charts-box"> |
| 3 | - <qiun-data-charts | ||
| 4 | - type="line" | ||
| 5 | - :opts="opts" | ||
| 6 | - :chartData="chartData" | ||
| 7 | - :reshow="reshow" | ||
| 8 | - :canvas2d="true" | ||
| 9 | - /> | 3 | + <!-- :canvas2d="true" tooltipFormat="showYLable" :onmovetip="true" --> |
| 4 | + <qiun-data-charts :type="chartType" :opts="opts" :chartData="chartData" :reshow="reshow" :tooltipShow="true" /> | ||
| 10 | </view> | 5 | </view> |
| 11 | </template> | 6 | </template> |
| 12 | 7 | ||
| 13 | <script setup> | 8 | <script setup> |
| 14 | - import { ref, reactive, watch } from 'vue'; | 9 | +import { ref, reactive, watch } from 'vue'; |
| 15 | 10 | ||
| 16 | - // 1. 定义 Props 接收父组件数据 | ||
| 17 | - const props = defineProps({ | ||
| 18 | - // 传入的分类数据 (横坐标) | ||
| 19 | - categories: { | ||
| 20 | - type: Array, | ||
| 21 | - default: () => [], | ||
| 22 | - }, | ||
| 23 | - // 传入的系列数据 (纵坐标内容) | ||
| 24 | - series: { | ||
| 25 | - type: Array, | ||
| 26 | - default: () => [], | ||
| 27 | - }, | ||
| 28 | - // 专门用于解决弹窗不显示问题的属性 | ||
| 29 | - reshow: { | ||
| 30 | - type: Boolean, | ||
| 31 | - default: false, | ||
| 32 | - }, | ||
| 33 | - }); | 11 | +// 1. 定义 Props 接收父组件数据 |
| 12 | +const props = defineProps({ | ||
| 13 | + // 传入的分类数据 (横坐标) | ||
| 14 | + chartType: { // 新增 | ||
| 15 | + type: String, | ||
| 16 | + default: 'line' | ||
| 17 | + }, | ||
| 18 | + categories: { | ||
| 19 | + type: Array, | ||
| 20 | + default: () => [], | ||
| 21 | + }, | ||
| 22 | + // 传入的系列数据 (纵坐标内容) | ||
| 23 | + series: { | ||
| 24 | + type: Array, | ||
| 25 | + default: () => [], | ||
| 26 | + }, | ||
| 27 | + // 专门用于解决弹窗不显示问题的属性 | ||
| 28 | + reshow: { | ||
| 29 | + type: Boolean, | ||
| 30 | + default: false, | ||
| 31 | + }, | ||
| 32 | +}); | ||
| 34 | 33 | ||
| 35 | - const chartData = ref({}); | 34 | +const chartData = ref({}); |
| 36 | 35 | ||
| 37 | - // 2. 图表配置项 | ||
| 38 | - const opts = reactive({ | ||
| 39 | - color: [ | ||
| 40 | - '#1890FF', | ||
| 41 | - '#91CB74', | ||
| 42 | - '#FAC858', | ||
| 43 | - '#EE6666', | ||
| 44 | - '#73C0DE', | ||
| 45 | - '#3CA272', | ||
| 46 | - '#FC8452', | ||
| 47 | - '#9A60B4', | ||
| 48 | - '#ea7ccc', | ||
| 49 | - ], | ||
| 50 | - padding: [15, 10, 0, 15], | ||
| 51 | - enableScroll: false, | ||
| 52 | - legend: {}, | ||
| 53 | - xAxis: { | ||
| 54 | - disableGrid: true, | ||
| 55 | - }, | ||
| 56 | - yAxis: { | ||
| 57 | - gridType: 'dash', | ||
| 58 | - dashLength: 2, | 36 | +// 2. 图表配置项 |
| 37 | +const opts = reactive({ | ||
| 38 | + dataLabel: false, | ||
| 39 | + color: [ | ||
| 40 | + '#1890FF', | ||
| 41 | + '#91CB74', | ||
| 42 | + '#FAC858', | ||
| 43 | + '#EE6666', | ||
| 44 | + '#73C0DE', | ||
| 45 | + '#3CA272', | ||
| 46 | + '#FC8452', | ||
| 47 | + '#9A60B4', | ||
| 48 | + '#ea7ccc', | ||
| 49 | + ], | ||
| 50 | + padding: [15, 10, 0, 15], | ||
| 51 | + enableScroll: false, | ||
| 52 | + legend: { | ||
| 53 | + show: true, | ||
| 54 | + position: 'top' | ||
| 55 | + }, | ||
| 56 | + xAxis: { | ||
| 57 | + disableGrid: true, | ||
| 58 | + // itemCount: 7, // 一共显示7个标签:1、6、11、16、21、26、31 | ||
| 59 | + labelCount: 7, | ||
| 60 | + // splitNumber: 6, | ||
| 61 | + }, | ||
| 62 | + yAxis: { | ||
| 63 | + gridType: 'dash', | ||
| 64 | + axisLabel: { show: false }, | ||
| 65 | + dashLength: 2, | ||
| 66 | + }, | ||
| 67 | + extra: { | ||
| 68 | + line: { | ||
| 69 | + type: 'straight', | ||
| 70 | + width: 2, | ||
| 71 | + activeType: 'hollow', | ||
| 59 | }, | 72 | }, |
| 60 | - extra: { | ||
| 61 | - line: { | ||
| 62 | - type: 'straight', | ||
| 63 | - width: 2, | ||
| 64 | - activeType: 'hollow', | ||
| 65 | - }, | 73 | + // 新增下面这段 |
| 74 | + column: { | ||
| 75 | + width: 30, // 柱子宽度 | ||
| 76 | + radius: [4, 4, 0, 0] // 柱子圆角 | ||
| 66 | }, | 77 | }, |
| 67 | - }); | 78 | + tooltip: { |
| 79 | + legendShow: true, | ||
| 68 | 80 | ||
| 69 | - // 3. 核心逻辑:格式化数据 | ||
| 70 | - const formatData = () => { | ||
| 71 | - if (props.categories.length > 0) { | ||
| 72 | - chartData.value = { | ||
| 73 | - categories: props.categories, | ||
| 74 | - series: props.series, | ||
| 75 | - }; | ||
| 76 | } | 81 | } |
| 77 | - }; | 82 | + }, |
| 83 | +}); | ||
| 78 | 84 | ||
| 79 | - // 4. 监听 Props 变化,当父组件传入新数据时自动重绘 | ||
| 80 | - watch( | ||
| 81 | - () => [props.categories, props.series], | ||
| 82 | - () => { | ||
| 83 | - formatData(); | ||
| 84 | - }, | ||
| 85 | - { immediate: true, deep: true }, | ||
| 86 | - ); | 85 | +// 3. 核心逻辑:格式化数据 |
| 86 | +// const formatData = () => { | ||
| 87 | +// if (props.categories.length > 0) { | ||
| 88 | +// chartData.value = { | ||
| 89 | +// categories: props.categories, | ||
| 90 | +// series: props.series, | ||
| 91 | +// }; | ||
| 92 | +// } | ||
| 93 | +// }; | ||
| 94 | + | ||
| 95 | + | ||
| 96 | + | ||
| 97 | +const formatData = () => { | ||
| 98 | + const cat = props.categories || []; | ||
| 99 | + let ser = props.series || []; | ||
| 100 | + | ||
| 101 | + console.log('cat=', cat, 'ser=', ser); | ||
| 102 | + // 强制保证 series 每一项都有 data 数组 | ||
| 103 | + ser = ser.map(item => ({ | ||
| 104 | + ...item, | ||
| 105 | + data: item.data || [] // 最关键:没有data就给空数组 | ||
| 106 | + })); | ||
| 107 | + | ||
| 108 | + if (cat.length > 0) { | ||
| 109 | + chartData.value = { | ||
| 110 | + categories: cat, | ||
| 111 | + series: ser | ||
| 112 | + }; | ||
| 113 | + } | ||
| 114 | +}; | ||
| 115 | + | ||
| 116 | + | ||
| 117 | +// 4. 监听 Props 变化,当父组件传入新数据时自动重绘 | ||
| 118 | +watch( | ||
| 119 | + () => [props.categories, props.series, props.chartType], | ||
| 120 | + () => { | ||
| 121 | + formatData(); | ||
| 122 | + }, | ||
| 123 | + { immediate: true, deep: true }, | ||
| 124 | +); | ||
| 87 | </script> | 125 | </script> |
| 88 | 126 | ||
| 89 | <style lang="scss" scoped> | 127 | <style lang="scss" scoped> |
| 90 | - .charts-box { | ||
| 91 | - width: 100%; | ||
| 92 | - height: 100%; | ||
| 93 | - } | 128 | +.charts-box { |
| 129 | + width: 100%; | ||
| 130 | + height: 100%; | ||
| 131 | +} | ||
| 94 | </style> | 132 | </style> |
| 1 | +<template> | ||
| 2 | + <!-- 一行布局:保证所有内容在同一行 ✅ 你的核心要求 --> | ||
| 3 | + <view class="record-row" :class="{ active: record.isActive }"> | ||
| 4 | + <view class="left"> | ||
| 5 | + <view class="left-header"> | ||
| 6 | + <!-- 组号:所有类型都有 --> | ||
| 7 | + <view class="index-box">{{ index + 1 }}</view> | ||
| 8 | + <!-- ====================================== | ||
| 9 | + 类型 0:力量(重量 + 次数)+ 休息 | ||
| 10 | + ====================================== --> | ||
| 11 | + <view v-if="exerciseType === 0" class="goal-list"> | ||
| 12 | + <view class="goal-item"> | ||
| 13 | + <!-- 重量 --> | ||
| 14 | + <view class="goal-input-box no-label-input"> | ||
| 15 | + <view class="values"> | ||
| 16 | + <up-input v-model="record.weight" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 17 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }" /> | ||
| 18 | + <text class="unit">kg</text> | ||
| 19 | + </view> | ||
| 20 | + </view> | ||
| 21 | + <!-- 次数 --> | ||
| 22 | + <view class="goal-input-box no-label-input"> | ||
| 23 | + <view class="values"> | ||
| 24 | + <up-input v-model="record.reps" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 25 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }" /> | ||
| 26 | + <text class="unit">次</text> | ||
| 27 | + </view> | ||
| 28 | + </view> | ||
| 29 | + </view> | ||
| 30 | + </view> | ||
| 31 | + | ||
| 32 | + <!-- ====================================== | ||
| 33 | + 类型 1:有氧(时长 + 距离)+ 休息 | ||
| 34 | + ====================================== --> | ||
| 35 | + | ||
| 36 | + <view v-else-if="exerciseType === 1" class="goal-list"> | ||
| 37 | + <view class="goal-item"> | ||
| 38 | + <view class="input-col" @click.stop="emitOpenPicker(index)"> | ||
| 39 | + <view class="label">时长</view> | ||
| 40 | + <view class="values"> | ||
| 41 | + <text class="num">{{ record.h }}</text> | ||
| 42 | + <text class="unit">时</text> | ||
| 43 | + <text class="num">{{ record.m }}</text> | ||
| 44 | + <text class="unit">分</text> | ||
| 45 | + <text class="num">{{ record.s }}</text> | ||
| 46 | + <text class="unit">秒</text> | ||
| 47 | + </view> | ||
| 48 | + </view> | ||
| 49 | + <view class="distance"> | ||
| 50 | + <view class="label">距离</view> | ||
| 51 | + <view class="values"> | ||
| 52 | + <up-input v-model="record.distance" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 53 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }" /> | ||
| 54 | + <text class="unit">km</text> | ||
| 55 | + </view> | ||
| 56 | + </view> | ||
| 57 | + </view> | ||
| 58 | + </view> | ||
| 59 | + | ||
| 60 | + <!-- ====================================== | ||
| 61 | + 类型 2:纯次数 + 休息 | ||
| 62 | + ====================================== --> | ||
| 63 | + <view v-else-if="exerciseType === 2" class="goal-list"> | ||
| 64 | + <view class="goal-item"> | ||
| 65 | + <view class="goal-input-box"> | ||
| 66 | + <view class="values"> | ||
| 67 | + <up-input v-model="record.reps" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 68 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }" /> | ||
| 69 | + <text class="unit">次</text> | ||
| 70 | + </view> | ||
| 71 | + </view> | ||
| 72 | + </view> | ||
| 73 | + </view> | ||
| 74 | + | ||
| 75 | + <!-- ====================================== | ||
| 76 | + 类型 3:纯时长 + 休息 | ||
| 77 | + ====================================== --> | ||
| 78 | + <view v-else-if="exerciseType === 3" class="input-col" @click.stop="emitOpenPicker(index)"> | ||
| 79 | + <view class="label">时长</view> | ||
| 80 | + <view class="values"> | ||
| 81 | + <text class="num">{{ record.h }}</text> | ||
| 82 | + <text class="unit">时</text> | ||
| 83 | + <text class="num">{{ record.m }}</text> | ||
| 84 | + <text class="unit">分</text> | ||
| 85 | + <text class="num">{{ record.s }}</text> | ||
| 86 | + <text class="unit">秒</text> | ||
| 87 | + </view> | ||
| 88 | + </view> | ||
| 89 | + | ||
| 90 | + <!-- ====================================== | ||
| 91 | + 类型 4:自重加重 + 休息 | ||
| 92 | + ====================================== --> | ||
| 93 | + <view v-else-if="exerciseType === 4" class="goal-list"> | ||
| 94 | + <view class="goal-item"> | ||
| 95 | + <!-- <view class="goal-input-box" hover-class="none" hover-stop-propagation="false"> | ||
| 96 | + <up-input :value="userWeight" @input="handleWeightChange" border="none" type="digit" color="#ffffff" | ||
| 97 | + :maxlength="3" :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }" /> | ||
| 98 | + </view> --> | ||
| 99 | + <view class="goal-input-box "> | ||
| 100 | + <view class="values"> | ||
| 101 | + <up-input v-model="record.weight" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 102 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }" /> | ||
| 103 | + <text class="unit">kg</text> | ||
| 104 | + </view> | ||
| 105 | + </view> | ||
| 106 | + <view class="goal-input-box"> | ||
| 107 | + <view class="values"> | ||
| 108 | + <up-input v-model="record.reps" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 109 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }" /> | ||
| 110 | + <text class="unit">次</text> | ||
| 111 | + </view> | ||
| 112 | + </view> | ||
| 113 | + </view> | ||
| 114 | + </view> | ||
| 115 | + | ||
| 116 | + <!-- ====================================== | ||
| 117 | + 类型 5:自重减重 + 休息 | ||
| 118 | + ====================================== --> | ||
| 119 | + <view v-else-if="exerciseType === 5" class="goal-list"> | ||
| 120 | + <view class="goal-item"> | ||
| 121 | + <view class="goal-input-box"> | ||
| 122 | + <!-- <up-input :value="userWeight" @input="handleWeightChange" border="none" type="digit" color="#ffffff" | ||
| 123 | + :maxlength="3" :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }" /> --> | ||
| 124 | + <view class="values"> | ||
| 125 | + <up-input v-model="record.weight" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 126 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }" /> | ||
| 127 | + <text class="unit">kg</text> | ||
| 128 | + </view> | ||
| 129 | + </view> | ||
| 130 | + <view class="goal-input-box"> | ||
| 131 | + <view class="values"> | ||
| 132 | + <up-input v-model="record.reps" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 133 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }" /> | ||
| 134 | + <text class="unit">次</text> | ||
| 135 | + </view> | ||
| 136 | + </view> | ||
| 137 | + </view> | ||
| 138 | + </view> | ||
| 139 | + | ||
| 140 | + <!-- ====================================== | ||
| 141 | + 类型 6:间歇(循环、单次、间歇)无外部休息 | ||
| 142 | + ====================================== --> | ||
| 143 | + <view v-else-if="exerciseType === 6" class="goal-list"> | ||
| 144 | + <view class="goal-item interval"> | ||
| 145 | + <view class="goal-input-box reps"> | ||
| 146 | + <view class="label">循环</view> | ||
| 147 | + <view class="values"> | ||
| 148 | + <up-input v-model="record.reps" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 149 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }" /> | ||
| 150 | + <text class="unit">组</text> | ||
| 151 | + </view> | ||
| 152 | + </view> | ||
| 153 | + <view class="goal-input-box time"> | ||
| 154 | + <view class="label">单次</view> | ||
| 155 | + <view class="values"> | ||
| 156 | + <up-input v-model="record.duration" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 157 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }" /> | ||
| 158 | + <text class="unit">秒</text> | ||
| 159 | + </view> | ||
| 160 | + </view> | ||
| 161 | + <view class="goal-input-box restTime"> | ||
| 162 | + <view class="label">间歇</view> | ||
| 163 | + <view class="values"> | ||
| 164 | + <up-input v-model="record.restTime" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 165 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }" /> | ||
| 166 | + <text class="unit">秒</text> | ||
| 167 | + </view> | ||
| 168 | + </view> | ||
| 169 | + </view> | ||
| 170 | + </view> | ||
| 171 | + | ||
| 172 | + <!-- ====================================== | ||
| 173 | + 休息时间(除了 type6 都显示) | ||
| 174 | + 移入子组件内部 ✅ | ||
| 175 | + ====================================== --> | ||
| 176 | + <view v-if="!isEditing && exerciseType !== 6" class="timer-selector" @click.stop="emitOpenQuickTimer(index)"> | ||
| 177 | + <text class="text">{{ record.quickTimeDisplay || '60s' }}</text> | ||
| 178 | + <up-icon name="arrow-down" color="#8e8e93" size="10"></up-icon> | ||
| 179 | + </view> | ||
| 180 | + </view> | ||
| 181 | + </view> | ||
| 182 | + | ||
| 183 | + <!-- ====================================== | ||
| 184 | + 右侧:勾选 / 编辑按钮 | ||
| 185 | + ====================================== --> | ||
| 186 | + <view class="icons"> | ||
| 187 | + <template v-if="!isEditing"> | ||
| 188 | + <view class="icon" @click.stop="emitToggleActive(index)" :class="{ active: record.isActive }"> | ||
| 189 | + <up-icon name="checkmark" :color="record.isActive ? '#000' : '#919191'" size="20" bold></up-icon> | ||
| 190 | + </view> | ||
| 191 | + </template> | ||
| 192 | + <template v-else> | ||
| 193 | + <view class="icon" @click.stop="emitAddRow"> | ||
| 194 | + <up-icon name="plus-circle" color="#fff" size="20" bold></up-icon> | ||
| 195 | + </view> | ||
| 196 | + <view class="icon del" @click.stop="emitDeleteRow(index)"> | ||
| 197 | + <up-icon name="trash" color="#e63e1e" size="20" bold></up-icon> | ||
| 198 | + </view> | ||
| 199 | + </template> | ||
| 200 | + </view> | ||
| 201 | + </view> | ||
| 202 | +</template> | ||
| 203 | + | ||
| 204 | +<script setup> | ||
| 205 | +const props = defineProps({ | ||
| 206 | + exerciseType: [Number, String], | ||
| 207 | + record: Object, | ||
| 208 | + index: Number, | ||
| 209 | + isEditing: Boolean, | ||
| 210 | + userWeight: { | ||
| 211 | + type: Number, | ||
| 212 | + default: 70 | ||
| 213 | + } | ||
| 214 | +}) | ||
| 215 | + | ||
| 216 | +const emit = defineEmits([ | ||
| 217 | + 'openPicker', | ||
| 218 | + 'openQuickTimer', | ||
| 219 | + 'toggleActive', | ||
| 220 | + 'addRow', | ||
| 221 | + 'deleteRow', | ||
| 222 | +]) | ||
| 223 | + | ||
| 224 | +const emitOpenPicker = (index) => emit('openPicker', index) | ||
| 225 | +const emitOpenQuickTimer = (index) => emit('openQuickTimer', index) | ||
| 226 | +const emitToggleActive = (index) => emit('toggleActive', index) | ||
| 227 | +const emitAddRow = () => emit('addRow') | ||
| 228 | +const emitDeleteRow = (index) => emit('deleteRow', index) | ||
| 229 | +</script> | ||
| 230 | + | ||
| 231 | +<style lang="scss" scoped> | ||
| 232 | +.record-row { | ||
| 233 | + display: flex; | ||
| 234 | + align-items: flex-start; | ||
| 235 | + justify-content: space-between; | ||
| 236 | + padding: 10rpx 30rpx; | ||
| 237 | + box-sizing: border-box; | ||
| 238 | + | ||
| 239 | + &.active { | ||
| 240 | + background-color: #4c4d3b; | ||
| 241 | + } | ||
| 242 | + | ||
| 243 | + .left { | ||
| 244 | + display: flex; | ||
| 245 | + flex-direction: column; | ||
| 246 | + gap: 10rpx; | ||
| 247 | + | ||
| 248 | + .left-header { | ||
| 249 | + display: flex; | ||
| 250 | + align-items: center; | ||
| 251 | + gap: 20rpx; | ||
| 252 | + } | ||
| 253 | + } | ||
| 254 | + | ||
| 255 | + .index-box { | ||
| 256 | + width: 50rpx; | ||
| 257 | + height: 70rpx; | ||
| 258 | + background: #1f1f1f; | ||
| 259 | + border-radius: 10rpx; | ||
| 260 | + display: flex; | ||
| 261 | + align-items: center; | ||
| 262 | + justify-content: center; | ||
| 263 | + font-size: 30rpx; | ||
| 264 | + color: #fff; | ||
| 265 | + } | ||
| 266 | + | ||
| 267 | + .input-col, | ||
| 268 | + .distance { | ||
| 269 | + height: 70rpx; | ||
| 270 | + background: #1f1f1f; | ||
| 271 | + border-radius: 10rpx; | ||
| 272 | + padding: 5rpx 10rpx; | ||
| 273 | + display: flex; | ||
| 274 | + flex-direction: column; | ||
| 275 | + justify-content: center; | ||
| 276 | + // flex: 1; | ||
| 277 | + // flex-shrink: 0; | ||
| 278 | + | ||
| 279 | + .label { | ||
| 280 | + font-size: 15rpx; | ||
| 281 | + color: #8e8e93; | ||
| 282 | + } | ||
| 283 | + | ||
| 284 | + .values { | ||
| 285 | + display: flex; | ||
| 286 | + align-items: baseline; | ||
| 287 | + | ||
| 288 | + .num { | ||
| 289 | + font-size: 32rpx; | ||
| 290 | + color: #fff; | ||
| 291 | + } | ||
| 292 | + | ||
| 293 | + .unit { | ||
| 294 | + font-size: 15rpx; | ||
| 295 | + color: #8e8e93; | ||
| 296 | + margin-left: 10rpx; | ||
| 297 | + margin-right: 10rpx; | ||
| 298 | + } | ||
| 299 | + } | ||
| 300 | + } | ||
| 301 | + | ||
| 302 | + .timer-selector { | ||
| 303 | + width: 75rpx; | ||
| 304 | + height: 75rpx; | ||
| 305 | + display: flex; | ||
| 306 | + flex-direction: column; | ||
| 307 | + align-items: center; | ||
| 308 | + justify-content: center; | ||
| 309 | + border-radius: 50%; | ||
| 310 | + border: 1rpx solid #a4a4a4; | ||
| 311 | + | ||
| 312 | + .text { | ||
| 313 | + font-size: 20rpx; | ||
| 314 | + color: #8e8e93; | ||
| 315 | + } | ||
| 316 | + } | ||
| 317 | + | ||
| 318 | + .goal-list { | ||
| 319 | + display: flex; | ||
| 320 | + gap: 20rpx; | ||
| 321 | + } | ||
| 322 | + | ||
| 323 | + .goal-item { | ||
| 324 | + display: flex; | ||
| 325 | + gap: 20rpx; | ||
| 326 | + } | ||
| 327 | + | ||
| 328 | + .goal-input-box { | ||
| 329 | + display: flex; | ||
| 330 | + flex-direction: column; | ||
| 331 | + justify-content: center; | ||
| 332 | + background: #1f1f1f; | ||
| 333 | + width: 100rpx; | ||
| 334 | + height: 70rpx; | ||
| 335 | + border-radius: 10rpx; | ||
| 336 | + padding: 0 10rpx; | ||
| 337 | + | ||
| 338 | + .label { | ||
| 339 | + font-size: 15rpx; | ||
| 340 | + color: #8e8e93; | ||
| 341 | + align-items: baseline; | ||
| 342 | + margin-bottom: 2rpx; | ||
| 343 | + } | ||
| 344 | + | ||
| 345 | + | ||
| 346 | + .values { | ||
| 347 | + display: flex; | ||
| 348 | + align-items: flex-end; | ||
| 349 | + } | ||
| 350 | + | ||
| 351 | + .unit { | ||
| 352 | + font-size: 15rpx; | ||
| 353 | + color: #8e8e93; | ||
| 354 | + } | ||
| 355 | + } | ||
| 356 | + | ||
| 357 | + .no-label-input { | ||
| 358 | + // padding-top: 20rpx !important; | ||
| 359 | + // align-items: flex-end; | ||
| 360 | + justify-content: flex-end; | ||
| 361 | + padding-bottom: 10rpx; | ||
| 362 | + | ||
| 363 | + .values { | ||
| 364 | + align-items: baseline; | ||
| 365 | + } | ||
| 366 | + | ||
| 367 | + .unit { | ||
| 368 | + margin-left: 4rpx; | ||
| 369 | + } | ||
| 370 | + } | ||
| 371 | + | ||
| 372 | + .interval { | ||
| 373 | + display: flex; | ||
| 374 | + gap: 20rpx; | ||
| 375 | + } | ||
| 376 | + | ||
| 377 | + .icons { | ||
| 378 | + display: flex; | ||
| 379 | + gap: 20rpx; | ||
| 380 | + | ||
| 381 | + .icon { | ||
| 382 | + width: 70rpx; | ||
| 383 | + height: 70rpx; | ||
| 384 | + background: #1f1f1f; | ||
| 385 | + border-radius: 12rpx; | ||
| 386 | + display: flex; | ||
| 387 | + align-items: center; | ||
| 388 | + justify-content: center; | ||
| 389 | + | ||
| 390 | + &.active { | ||
| 391 | + background-color: #f8d714; | ||
| 392 | + } | ||
| 393 | + } | ||
| 394 | + | ||
| 395 | + .del { | ||
| 396 | + background-color: #58372d; | ||
| 397 | + } | ||
| 398 | + } | ||
| 399 | + | ||
| 400 | + .weight-wrap-fix { | ||
| 401 | + flex-direction: row !important; | ||
| 402 | + align-items: flex-end !important; | ||
| 403 | + gap: 6rpx; | ||
| 404 | + padding-top: 8rpx; | ||
| 405 | + } | ||
| 406 | +} | ||
| 407 | +</style> |
| 1 | +<template> | ||
| 2 | + <view class="popup-container" v-if="props.show" @click="closeAddMenu"> | ||
| 3 | + <!-- 搜索栏 --> | ||
| 4 | + <view class="search-bar"> | ||
| 5 | + | ||
| 6 | + <view class="add-btn-wrapper" @click.stop="addExercise"> | ||
| 7 | + <uni-icons type="plus" size="35" color="#333"></uni-icons> | ||
| 8 | + <view class="floating-menu" v-show="addActionshow" @click.stop> | ||
| 9 | + <view class="menu-item" @click="addnewmotion"> | ||
| 10 | + <uni-icons type="plus" size="24" color="#333"></uni-icons> | ||
| 11 | + <text class="menu-text">新增动作</text> | ||
| 12 | + </view> | ||
| 13 | + </view> | ||
| 14 | + </view> | ||
| 15 | + </view> | ||
| 16 | + <!-- 左右布局 --> | ||
| 17 | + <view class="layout-container"> | ||
| 18 | + <!-- 左侧分类导航 --> | ||
| 19 | + <scroll-view scroll-y class="left-nav"> | ||
| 20 | + <view class="nav-item" @click="handleCollectClick('collect')" :class="{ active: activeNav === 'collect' }"> | ||
| 21 | + 收藏 | ||
| 22 | + </view> | ||
| 23 | + <view v-for="nav in navItems" :key="nav.id" class="nav-item" :class="{ active: activeNav === nav.id }" | ||
| 24 | + @click="switchNav(nav.id)"> | ||
| 25 | + {{ nav.name }} | ||
| 26 | + </view> | ||
| 27 | + </scroll-view> | ||
| 28 | + | ||
| 29 | + <!-- 右侧动作列表 --> | ||
| 30 | + <scroll-view scroll-y class="right-content"> | ||
| 31 | + <!-- 部位筛选(可选) --> | ||
| 32 | + <view class="tip" v-if="motionPart.length > 0"> | ||
| 33 | + <view class="item" @click="handlePartClick('')" :class="{ active: activeMotionPart == '' }">全部</view> | ||
| 34 | + <view class="item" v-for="item in motionPart" :key="item.id" :class="{ active: activeMotionPart == item.id }" | ||
| 35 | + @click="handlePartClick(item.id)"> | ||
| 36 | + {{ item.name }} | ||
| 37 | + </view> | ||
| 38 | + </view> | ||
| 39 | + | ||
| 40 | + <view class="exercise-grid"> | ||
| 41 | + <view class="content" v-if="exercises.length > 0 || superGroupInfo.length > 0"> | ||
| 42 | + <!-- 普通动作列表 --> | ||
| 43 | + <view class="equipment-list"> | ||
| 44 | + <view class="equipment-item" v-for="item in exercises" :key="item.equipmentId"> | ||
| 45 | + <view class="equipment-name"> {{ item.equipmentName }} </view> | ||
| 46 | + <view class="action-list"> | ||
| 47 | + <!-- 动作卡片(可点击赋值) --> | ||
| 48 | + <view class="action-item" v-for="e in item.exercises" :key="e.id" | ||
| 49 | + :class="{ selected: selectedList.some(i => i.action.id === e.id) }" @click="selectAction(e)"> | ||
| 50 | + <image :src="e.url3dAnimation" mode="aspectFill" lazy-load class="action-img"></image> | ||
| 51 | + <view class="action-name">{{ e.name }}</view> | ||
| 52 | + <view class="trainingReps" v-if="e.trainingReps">{{ e.trainingReps }}次</view> | ||
| 53 | + </view> | ||
| 54 | + </view> | ||
| 55 | + </view> | ||
| 56 | + | ||
| 57 | + <!-- 超级组列表(可选) --> | ||
| 58 | + <view class="supers" v-if="superGroupInfo.length > 0"> | ||
| 59 | + <view class="supers-name"> 超级组 </view> | ||
| 60 | + <view class="super" v-for="item in superGroupInfo" :key="item.id" | ||
| 61 | + :class="{ selected: selectedList.some(i => i.action.id === item.id) }" @click="selectAction(item, 2)"> | ||
| 62 | + <image src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260507/超级组_1778117889451.png" | ||
| 63 | + mode="aspectFill" lazy-load class="super-img"></image> | ||
| 64 | + <view class="right"> | ||
| 65 | + <view class="super-name">{{ item.name }}</view> | ||
| 66 | + <view class="parts">{{ item.primaryMuscles.join(' ') }}</view> | ||
| 67 | + </view> | ||
| 68 | + </view> | ||
| 69 | + </view> | ||
| 70 | + </view> | ||
| 71 | + </view> | ||
| 72 | + | ||
| 73 | + <!-- 空状态 --> | ||
| 74 | + <view class="empty-state" v-else> | ||
| 75 | + <text class="empty-text">暂无动作数据</text> | ||
| 76 | + </view> | ||
| 77 | + </view> | ||
| 78 | + </scroll-view> | ||
| 79 | + </view> | ||
| 80 | + <!-- 底部已选动作栏 --> | ||
| 81 | + <scroll-view class="selected-bar" scroll-x="true" show-scrollbar="false"> | ||
| 82 | + <view class="selected-item" v-for="(item, index) in selectedList" :key="index"> | ||
| 83 | + <text class="name">{{ item.action.name }}</text> | ||
| 84 | + <uni-icons type="close" size="16" color="#fff" @click="selectedList.splice(index, 1)"></uni-icons> | ||
| 85 | + </view> | ||
| 86 | + </scroll-view> | ||
| 87 | + | ||
| 88 | + <!-- 底部按钮栏 --> | ||
| 89 | + <view class="bottom-btn-bar"> | ||
| 90 | + <view class="btn cancel-btn" @click="handleClose">取消</view> | ||
| 91 | + <view class="btn confirm-btn" :class="{ disabled: selectedList.length === 0 }" @click="handleConfirm"> | ||
| 92 | + 确认 | ||
| 93 | + </view> | ||
| 94 | + </view> | ||
| 95 | + </view> | ||
| 96 | +</template> | ||
| 97 | + | ||
| 98 | +<script setup> | ||
| 99 | +import { ref, watch, onMounted } from 'vue'; | ||
| 100 | +import ExercisesApi from '@/sheep/api/motion/exercises'; | ||
| 101 | +import SupersetsApi from '@/sheep/api/motion/supersets'; | ||
| 102 | +import { useActionStore } from '@/sheep/store/action'; | ||
| 103 | +import { useTrainingStore } from '@/sheep/store/trainingStore' | ||
| 104 | +const trainingStore = useTrainingStore() | ||
| 105 | + | ||
| 106 | +const actionStore = useActionStore(); | ||
| 107 | + | ||
| 108 | +const emit = defineEmits(['close', 'confirm']); | ||
| 109 | + | ||
| 110 | +const addActionshow = ref(false); | ||
| 111 | + | ||
| 112 | +// 数据 | ||
| 113 | +const activeNav = ref('collect'); // 默认选中收藏 | ||
| 114 | +const navItems = ref([]); | ||
| 115 | +const exercises = ref([]); | ||
| 116 | +const superGroupInfo = ref([]); | ||
| 117 | +const motionPart = ref([]); | ||
| 118 | +const activeMotionPart = ref(''); | ||
| 119 | +const selectedList = ref([]); // 多选列表:每一项都是 { action: 动作对象, type: 1=动作 2=超级组 } | ||
| 120 | +const selectedType = ref(1); // 1=动作 2=超级组 | ||
| 121 | +const allExercises = ref([]); | ||
| 122 | + | ||
| 123 | +const props = defineProps({ | ||
| 124 | + show: { | ||
| 125 | + type: Boolean, | ||
| 126 | + default: false | ||
| 127 | + }, | ||
| 128 | + unitIndex: Number, | ||
| 129 | +}) | ||
| 130 | + | ||
| 131 | +// 监听弹窗打开,加载数据 | ||
| 132 | +watch(() => props.show, (val) => { | ||
| 133 | + if (val) { | ||
| 134 | + selectedList.value = []; | ||
| 135 | + // 加载收藏列表 | ||
| 136 | + loadCollectList(); | ||
| 137 | + loadSuperFavoriteList(); | ||
| 138 | + } | ||
| 139 | + console.log('动作替换组件 unitIndex:', props.unitIndex) | ||
| 140 | +}); | ||
| 141 | + | ||
| 142 | +// 关闭菜单 | ||
| 143 | +const closeAddMenu = () => { | ||
| 144 | + addActionshow.value = false; | ||
| 145 | +}; | ||
| 146 | + | ||
| 147 | +// 切换菜单显示/隐藏 | ||
| 148 | +const addExercise = () => { | ||
| 149 | + addActionshow.value = !addActionshow.value; | ||
| 150 | +}; | ||
| 151 | + | ||
| 152 | +// 新增动作跳转 | ||
| 153 | +const addnewmotion = () => { | ||
| 154 | + uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-xinzeng' }); | ||
| 155 | +}; | ||
| 156 | + | ||
| 157 | +// 加载分类 | ||
| 158 | +const loadCategories = async () => { | ||
| 159 | + try { | ||
| 160 | + await actionStore.getloadCategories(); | ||
| 161 | + navItems.value = actionStore.showCategories; | ||
| 162 | + if (navItems.value.length > 0) { | ||
| 163 | + switchNav(navItems.value[0].id); | ||
| 164 | + } | ||
| 165 | + } catch (error) { | ||
| 166 | + console.error('获取分类失败:', error); | ||
| 167 | + } | ||
| 168 | +}; | ||
| 169 | + | ||
| 170 | +// 加载收藏动作列表 | ||
| 171 | +const loadCollectList = async () => { | ||
| 172 | + try { | ||
| 173 | + const res = await ExercisesApi.getFavoriteExercises(); | ||
| 174 | + exercises.value = res.data || []; | ||
| 175 | + allExercises.value = [...(res.data || [])]; | ||
| 176 | + } catch (err) { | ||
| 177 | + console.error('加载动作收藏失败', err); | ||
| 178 | + } | ||
| 179 | +}; | ||
| 180 | + | ||
| 181 | +// 加载收藏超级组列表 | ||
| 182 | +const loadSuperFavoriteList = async () => { | ||
| 183 | + try { | ||
| 184 | + const res = await SupersetsApi.getFavoriteSuperset(); | ||
| 185 | + superGroupInfo.value = res.data || []; | ||
| 186 | + } catch (err) { | ||
| 187 | + console.error('加载超级组收藏失败', err); | ||
| 188 | + } | ||
| 189 | +}; | ||
| 190 | + | ||
| 191 | +// 切换左侧分类 | ||
| 192 | +const switchNav = (id) => { | ||
| 193 | + if (activeNav.value === id) return; | ||
| 194 | + activeNav.value = id; | ||
| 195 | + activeMotionPart.value = ''; | ||
| 196 | + // selectedList.value = []; // 切换分类时重置选中 | ||
| 197 | + if (id === 'super') { | ||
| 198 | + exercises.value = []; | ||
| 199 | + motionPart.value = []; | ||
| 200 | + loadsupersetsinfo(); | ||
| 201 | + } else { | ||
| 202 | + superGroupInfo.value = []; | ||
| 203 | + loadExercises(id); | ||
| 204 | + } | ||
| 205 | +}; | ||
| 206 | + | ||
| 207 | +// 加载普通动作 | ||
| 208 | +const loadExercises = async (categoriesId) => { | ||
| 209 | + try { | ||
| 210 | + const partRes = await ExercisesApi.getMotionPart(categoriesId); | ||
| 211 | + motionPart.value = partRes.data || []; | ||
| 212 | + const exerciseRes = await ExercisesApi.getexercises({ categoriesId }); | ||
| 213 | + exercises.value = exerciseRes.data; | ||
| 214 | + allExercises.value = [...(exerciseRes.data || [])]; | ||
| 215 | + } catch (error) { | ||
| 216 | + console.error('加载动作失败:', error); | ||
| 217 | + } | ||
| 218 | +}; | ||
| 219 | + | ||
| 220 | +// 加载超级组 | ||
| 221 | +const loadsupersetsinfo = async () => { | ||
| 222 | + try { | ||
| 223 | + const response = await SupersetsApi.getsupersets(); | ||
| 224 | + superGroupInfo.value = response.data || []; | ||
| 225 | + } catch (error) { | ||
| 226 | + console.error('获取超级组失败:', error); | ||
| 227 | + } | ||
| 228 | +}; | ||
| 229 | + | ||
| 230 | +// 部位筛选 | ||
| 231 | +const handlePartClick = async (id) => { | ||
| 232 | + activeMotionPart.value = id; | ||
| 233 | + const exerciseRes = await ExercisesApi.getexercises({ | ||
| 234 | + categoriesId: activeNav.value, | ||
| 235 | + subCategoriesId: id | ||
| 236 | + }); | ||
| 237 | + exercises.value = exerciseRes.data || []; | ||
| 238 | + selectedList.value = []; // 切换部位时重置选中 | ||
| 239 | +}; | ||
| 240 | + | ||
| 241 | +// 搜索动作 | ||
| 242 | +const onSearch = (e) => { | ||
| 243 | + const keyword = e.detail.value.trim().toLowerCase(); | ||
| 244 | + if (!keyword) { | ||
| 245 | + exercises.value = [...allExercises.value]; | ||
| 246 | + return; | ||
| 247 | + } | ||
| 248 | + exercises.value = allExercises.value.filter(item => | ||
| 249 | + item.name?.toLowerCase().includes(keyword) | ||
| 250 | + ); | ||
| 251 | +}; | ||
| 252 | + | ||
| 253 | +// 选择动作/超级组 | ||
| 254 | +const selectAction = (item, type = 1) => { | ||
| 255 | + // 查找是否已选中 | ||
| 256 | + const index = selectedList.value.findIndex(i => i.action.id === item.id); | ||
| 257 | + if (index > -1) { | ||
| 258 | + // 已存在 → 删除 | ||
| 259 | + selectedList.value.splice(index, 1); | ||
| 260 | + } else { | ||
| 261 | + // 不存在 → 添加 | ||
| 262 | + selectedList.value.push({ | ||
| 263 | + action: item, | ||
| 264 | + type: type | ||
| 265 | + }); | ||
| 266 | + } | ||
| 267 | + | ||
| 268 | + console.log("✅ 当前选中列表:", selectedList.value); | ||
| 269 | +}; | ||
| 270 | + | ||
| 271 | +// 关闭组件 | ||
| 272 | +const handleClose = () => { | ||
| 273 | + setTimeout(() => { | ||
| 274 | + emit('close'); | ||
| 275 | + }, 800); | ||
| 276 | +} | ||
| 277 | + | ||
| 278 | +// 确认添加(带详情接口) | ||
| 279 | +const handleConfirm = async () => { | ||
| 280 | + if (selectedList.value.length === 0) { | ||
| 281 | + uni.showToast({ title: '请选择动作', icon: 'none' }); | ||
| 282 | + return; | ||
| 283 | + } | ||
| 284 | + | ||
| 285 | + if (!trainingStore.actionDetail.units) { | ||
| 286 | + trainingStore.actionDetail.units = []; | ||
| 287 | + } | ||
| 288 | + | ||
| 289 | + // 遍历 selectedList,调用详情接口拿到完整数据,再转成 unit | ||
| 290 | + for (const item of selectedList.value) { | ||
| 291 | + let unit = null; | ||
| 292 | + | ||
| 293 | + if (item.type === 1) { | ||
| 294 | + // 单个动作:调用详情接口 | ||
| 295 | + const res = await ExercisesApi.getExerciseById(item.action.id); | ||
| 296 | + const detail = res.data; | ||
| 297 | + console.log('+++接口返回的动作详情++++', detail); | ||
| 298 | + console.log('detail.exerciseType =', detail.exerciseType); | ||
| 299 | + unit = { | ||
| 300 | + unitType: 1, | ||
| 301 | + unitId: detail.id, | ||
| 302 | + unitName: detail.name, | ||
| 303 | + exercises: [ | ||
| 304 | + { | ||
| 305 | + exerciseId: detail.id, | ||
| 306 | + exerciseName: detail.name, | ||
| 307 | + exerciseType: detail.exerciseType, | ||
| 308 | + urlImage: detail.urlImage || detail.url3dAnimation, | ||
| 309 | + categoryDescription: detail.categoryDescription, | ||
| 310 | + equipmentDescription: detail.equipmentDescription, | ||
| 311 | + } | ||
| 312 | + ] | ||
| 313 | + }; | ||
| 314 | + | ||
| 315 | + } else if (item.type === 2) { | ||
| 316 | + // 超级组:调用详情接口 | ||
| 317 | + const res = await SupersetsApi.getSupersetsInfo(item.action.id); | ||
| 318 | + const detail = res.data; | ||
| 319 | + console.log('+++接口返回的动作详情++++', detail); | ||
| 320 | + console.log('detail.exerciseType =', detail.exerciseType); | ||
| 321 | + unit = { | ||
| 322 | + unitType: 2, | ||
| 323 | + supersetId: detail.id, | ||
| 324 | + unitName: detail.name, | ||
| 325 | + exercises: detail.exercises.map(e => ({ | ||
| 326 | + exerciseId: e.id, | ||
| 327 | + exerciseName: e.name, | ||
| 328 | + exerciseType: e.exerciseType, | ||
| 329 | + urlImage: e.urlImage || e.url3dAnimation, | ||
| 330 | + })) | ||
| 331 | + }; | ||
| 332 | + } | ||
| 333 | + | ||
| 334 | + if (unit) { | ||
| 335 | + trainingStore.actionDetail.units.push(unit); | ||
| 336 | + } | ||
| 337 | + } | ||
| 338 | + | ||
| 339 | + console.log("✅ 已添加动作(含完整字段):", trainingStore.actionDetail.units); | ||
| 340 | + emit('close'); | ||
| 341 | + selectedList.value = []; // 清空选择 | ||
| 342 | +}; | ||
| 343 | + | ||
| 344 | +onMounted(() => { | ||
| 345 | + loadCategories(); | ||
| 346 | +}); | ||
| 347 | +</script> | ||
| 348 | + | ||
| 349 | +<style lang="scss" scoped> | ||
| 350 | +.popup-container { | ||
| 351 | + background-color: #fff; | ||
| 352 | + height: 100vh; | ||
| 353 | + display: flex; | ||
| 354 | + flex-direction: column; | ||
| 355 | + position: fixed; | ||
| 356 | + left: 0; | ||
| 357 | + right: 0; | ||
| 358 | + top: 0; | ||
| 359 | + bottom: 0; | ||
| 360 | + z-index: 999; | ||
| 361 | + border-radius: 0; | ||
| 362 | + | ||
| 363 | + .search-bar { | ||
| 364 | + display: flex; | ||
| 365 | + align-items: center; | ||
| 366 | + height: calc(var(--status-bar-height) + 88rpx); | ||
| 367 | + padding: 0 20rpx 0 20rpx; | ||
| 368 | + padding-top: var(--status-bar-height); | ||
| 369 | + background-color: #fff; | ||
| 370 | + position: sticky; | ||
| 371 | + top: 0; | ||
| 372 | + z-index: 10; | ||
| 373 | + } | ||
| 374 | + | ||
| 375 | + // 浮动加号按钮 + 菜单 | ||
| 376 | + .add-btn-wrapper { | ||
| 377 | + position: relative; | ||
| 378 | + width: 80rpx; | ||
| 379 | + height: 80rpx; | ||
| 380 | + display: flex; | ||
| 381 | + align-items: center; | ||
| 382 | + justify-content: center; | ||
| 383 | + z-index: 9999; | ||
| 384 | + } | ||
| 385 | + | ||
| 386 | + .floating-menu { | ||
| 387 | + position: absolute; | ||
| 388 | + top: 90rpx; | ||
| 389 | + left: 20rpx; | ||
| 390 | + background: #fff; | ||
| 391 | + border-radius: 12rpx; | ||
| 392 | + box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15); | ||
| 393 | + padding: 20rpx; | ||
| 394 | + z-index: 9999; | ||
| 395 | + width: 260rpx; | ||
| 396 | + } | ||
| 397 | + | ||
| 398 | + .menu-item { | ||
| 399 | + display: flex; | ||
| 400 | + align-items: center; | ||
| 401 | + gap: 16rpx; | ||
| 402 | + cursor: pointer; | ||
| 403 | + } | ||
| 404 | + | ||
| 405 | + .menu-text { | ||
| 406 | + font-size: 28rpx; | ||
| 407 | + } | ||
| 408 | + | ||
| 409 | + .layout-container { | ||
| 410 | + flex: 1; | ||
| 411 | + display: flex; | ||
| 412 | + background-color: #f5f5f5; | ||
| 413 | + overflow: hidden; | ||
| 414 | + | ||
| 415 | + .left-nav { | ||
| 416 | + width: 150rpx; | ||
| 417 | + background-color: #fafafa; | ||
| 418 | + border-right: 1px solid #eee; | ||
| 419 | + | ||
| 420 | + .nav-item { | ||
| 421 | + padding: 28rpx 16rpx; | ||
| 422 | + text-align: left; | ||
| 423 | + font-size: 26rpx; | ||
| 424 | + color: #666; | ||
| 425 | + position: relative; | ||
| 426 | + | ||
| 427 | + &.active { | ||
| 428 | + color: #000; | ||
| 429 | + background-color: #fff; | ||
| 430 | + border-right: none; | ||
| 431 | + font-weight: 500; | ||
| 432 | + | ||
| 433 | + &::before { | ||
| 434 | + content: ''; | ||
| 435 | + position: absolute; | ||
| 436 | + left: 0; | ||
| 437 | + top: 50%; | ||
| 438 | + transform: translateY(-50%); | ||
| 439 | + width: 6rpx; | ||
| 440 | + height: 36rpx; | ||
| 441 | + background-color: #f8d714; | ||
| 442 | + } | ||
| 443 | + } | ||
| 444 | + } | ||
| 445 | + } | ||
| 446 | + | ||
| 447 | + .right-content { | ||
| 448 | + flex: 1; | ||
| 449 | + padding: 20rpx; | ||
| 450 | + | ||
| 451 | + .tip { | ||
| 452 | + display: flex; | ||
| 453 | + flex-wrap: wrap; | ||
| 454 | + gap: 15rpx; | ||
| 455 | + margin-bottom: 20rpx; | ||
| 456 | + | ||
| 457 | + .item { | ||
| 458 | + padding: 10rpx 18rpx; | ||
| 459 | + font-size: 24rpx; | ||
| 460 | + background-color: #fff; | ||
| 461 | + border-radius: 16rpx; | ||
| 462 | + color: #666; | ||
| 463 | + | ||
| 464 | + &.active { | ||
| 465 | + background-color: #f8d714; | ||
| 466 | + color: #000; | ||
| 467 | + } | ||
| 468 | + } | ||
| 469 | + } | ||
| 470 | + | ||
| 471 | + .exercise-grid { | ||
| 472 | + .equipment-item { | ||
| 473 | + margin-bottom: 30rpx; | ||
| 474 | + | ||
| 475 | + .equipment-name { | ||
| 476 | + font-size: 28rpx; | ||
| 477 | + color: #333; | ||
| 478 | + margin-bottom: 16rpx; | ||
| 479 | + } | ||
| 480 | + | ||
| 481 | + .action-list { | ||
| 482 | + display: grid; | ||
| 483 | + grid-template-columns: repeat(2, 1fr); | ||
| 484 | + gap: 15rpx; | ||
| 485 | + | ||
| 486 | + .action-item { | ||
| 487 | + display: flex; | ||
| 488 | + flex-direction: column; | ||
| 489 | + align-items: center; | ||
| 490 | + gap: 8rpx; | ||
| 491 | + background: #fff; | ||
| 492 | + border-radius: 16rpx; | ||
| 493 | + padding: 15rpx; | ||
| 494 | + position: relative; | ||
| 495 | + | ||
| 496 | + &.selected { | ||
| 497 | + border: 2rpx solid #f8d714; | ||
| 498 | + } | ||
| 499 | + | ||
| 500 | + .action-img { | ||
| 501 | + width: 100%; | ||
| 502 | + height: 220rpx; | ||
| 503 | + border-radius: 12rpx; | ||
| 504 | + } | ||
| 505 | + | ||
| 506 | + .action-name { | ||
| 507 | + font-size: 24rpx; | ||
| 508 | + color: #333; | ||
| 509 | + } | ||
| 510 | + | ||
| 511 | + .trainingReps { | ||
| 512 | + position: absolute; | ||
| 513 | + top: 15rpx; | ||
| 514 | + right: 15rpx; | ||
| 515 | + font-size: 22rpx; | ||
| 516 | + color: #999; | ||
| 517 | + } | ||
| 518 | + } | ||
| 519 | + } | ||
| 520 | + } | ||
| 521 | + | ||
| 522 | + .supers-name { | ||
| 523 | + font-size: 28rpx; | ||
| 524 | + color: #333; | ||
| 525 | + margin: 20rpx 0 16rpx; | ||
| 526 | + } | ||
| 527 | + | ||
| 528 | + .super { | ||
| 529 | + display: flex; | ||
| 530 | + align-items: center; | ||
| 531 | + background: #fff; | ||
| 532 | + padding: 20rpx; | ||
| 533 | + border-radius: 16rpx; | ||
| 534 | + gap: 20rpx; | ||
| 535 | + margin-bottom: 15rpx; | ||
| 536 | + | ||
| 537 | + &.selected { | ||
| 538 | + border: 2rpx solid #f8d714; | ||
| 539 | + } | ||
| 540 | + | ||
| 541 | + .super-img { | ||
| 542 | + width: 100rpx; | ||
| 543 | + height: 100rpx; | ||
| 544 | + border-radius: 12rpx; | ||
| 545 | + } | ||
| 546 | + | ||
| 547 | + .right { | ||
| 548 | + flex: 1; | ||
| 549 | + | ||
| 550 | + .super-name { | ||
| 551 | + font-size: 26rpx; | ||
| 552 | + color: #333; | ||
| 553 | + margin-bottom: 8rpx; | ||
| 554 | + } | ||
| 555 | + | ||
| 556 | + .parts { | ||
| 557 | + font-size: 22rpx; | ||
| 558 | + color: #999; | ||
| 559 | + } | ||
| 560 | + } | ||
| 561 | + } | ||
| 562 | + | ||
| 563 | + .empty-state { | ||
| 564 | + display: flex; | ||
| 565 | + justify-content: center; | ||
| 566 | + align-items: center; | ||
| 567 | + padding-top: 100rpx; | ||
| 568 | + | ||
| 569 | + .empty-text { | ||
| 570 | + font-size: 28rpx; | ||
| 571 | + color: #999; | ||
| 572 | + } | ||
| 573 | + } | ||
| 574 | + } | ||
| 575 | + } | ||
| 576 | + } | ||
| 577 | + | ||
| 578 | + .bottom-btn-bar { | ||
| 579 | + display: flex; | ||
| 580 | + gap: 20rpx; | ||
| 581 | + padding: 20rpx 30rpx 30rpx; | ||
| 582 | + background-color: #fff; | ||
| 583 | + | ||
| 584 | + .btn { | ||
| 585 | + flex: 1; | ||
| 586 | + height: 80rpx; | ||
| 587 | + border-radius: 40rpx; | ||
| 588 | + display: flex; | ||
| 589 | + justify-content: center; | ||
| 590 | + align-items: center; | ||
| 591 | + font-size: 30rpx; | ||
| 592 | + font-weight: bold; | ||
| 593 | + } | ||
| 594 | + | ||
| 595 | + .cancel-btn { | ||
| 596 | + background-color: #f5f5f5; | ||
| 597 | + color: #333; | ||
| 598 | + } | ||
| 599 | + | ||
| 600 | + .confirm-btn { | ||
| 601 | + background-color: #f8d714; | ||
| 602 | + color: #000; | ||
| 603 | + | ||
| 604 | + &.disabled { | ||
| 605 | + background-color: #eeeeee; | ||
| 606 | + color: #bbbbbb; | ||
| 607 | + } | ||
| 608 | + } | ||
| 609 | + } | ||
| 610 | + | ||
| 611 | + .selected-bar { | ||
| 612 | + background-color: #2c2c2e; | ||
| 613 | + padding: 20rpx 30rpx; | ||
| 614 | + white-space: nowrap; | ||
| 615 | + display: flex; | ||
| 616 | + gap: 20rpx; | ||
| 617 | + | ||
| 618 | + .selected-item { | ||
| 619 | + background-color: #444; | ||
| 620 | + color: #fff; | ||
| 621 | + padding: 10rpx 20rpx; | ||
| 622 | + border-radius: 30rpx; | ||
| 623 | + display: flex; | ||
| 624 | + align-items: center; | ||
| 625 | + gap: 10rpx; | ||
| 626 | + | ||
| 627 | + .name { | ||
| 628 | + font-size: 26rpx; | ||
| 629 | + } | ||
| 630 | + } | ||
| 631 | + } | ||
| 632 | +} | ||
| 633 | +</style> |
| 1 | +<template> | ||
| 2 | + <up-popup :show="historyShow" mode="bottom" round="24rpx" bgColor="#2c2c2e" @close="historyShow = false"> | ||
| 3 | + <scroll-view scroll-y class="history-popup"> | ||
| 4 | + <!-- 标题栏 --> | ||
| 5 | + <view class="header"> | ||
| 6 | + <view class="title">{{ actionName }}</view> | ||
| 7 | + </view> | ||
| 8 | + <!-- 顶部数据卡片--> | ||
| 9 | + <view class="top-cards"> | ||
| 10 | + <!-- <view class="card"> | ||
| 11 | + <view class="label">最高重量</view> | ||
| 12 | + <view class="value">{{ maxWeight }} × {{ maxReps }}次</view> | ||
| 13 | + <view class="date">{{ recordDate }}</view> | ||
| 14 | + </view> | ||
| 15 | + <view class="card"> | ||
| 16 | + <view class="label">最高容量</view> | ||
| 17 | + <view class="value">{{ maxCapacity }}kg · 共{{ maxCapacitySets }}组</view> | ||
| 18 | + <view class="date">{{ recordDate }}</view> | ||
| 19 | + </view> --> | ||
| 20 | + </view> | ||
| 21 | + | ||
| 22 | + <!-- 历史记录列表 --> | ||
| 23 | + <view class="list"> | ||
| 24 | + <view class="record-item" v-for="(item, index) in formattedSetList" :key="index"> | ||
| 25 | + <view class="date">{{ item.date }}</view> | ||
| 26 | + <view class="name">{{ item.name }}</view> | ||
| 27 | + <view class="info">共{{ item.setCount }}组 · 容量{{ item.capacity }}kg</view> | ||
| 28 | + <view class="set-list"> | ||
| 29 | + <view class="set" v-for="(set, idx) in item.sets" :key="idx"> | ||
| 30 | + <view class="num">{{ set.idx }}</view> | ||
| 31 | + <text class="text">{{ set.text }}</text> | ||
| 32 | + </view> | ||
| 33 | + </view> | ||
| 34 | + </view> | ||
| 35 | + </view> | ||
| 36 | + </scroll-view> | ||
| 37 | + </up-popup> | ||
| 38 | +</template> | ||
| 39 | + | ||
| 40 | +<script setup> | ||
| 41 | +import { ref, computed } from 'vue' | ||
| 42 | +import TrainingApi from '@/sheep/api/Training/traininghistory' | ||
| 43 | + | ||
| 44 | +const historyShow = ref(false) | ||
| 45 | +const historyActionId = ref(0) | ||
| 46 | +const historyList = ref([]) | ||
| 47 | + | ||
| 48 | +const props = defineProps({ | ||
| 49 | + historyType: [Number, String], | ||
| 50 | + actionName: { | ||
| 51 | + type: String, | ||
| 52 | + default: '动作历史' | ||
| 53 | + } | ||
| 54 | +}) | ||
| 55 | + | ||
| 56 | +const emit = defineEmits(['close']) | ||
| 57 | + | ||
| 58 | +const openHistoryPopup = (id) => { | ||
| 59 | + console.log('传递到历史子组件的动作id', id) | ||
| 60 | + historyActionId.value = id; | ||
| 61 | + console.log('传递到历史子组件的动作idhistoryActionId', historyActionId.value) | ||
| 62 | + if (id !== undefined && id !== null && id !== '') { | ||
| 63 | + loadTrainHistoryDetail(id, props.historyType) | ||
| 64 | + } | ||
| 65 | + historyShow.value = true; | ||
| 66 | +} | ||
| 67 | + | ||
| 68 | +// 打开备注弹窗 | ||
| 69 | +const openBeizhu = () => { | ||
| 70 | + nextTick(() => { | ||
| 71 | + if (showBeizhuRef.value) { | ||
| 72 | + showBeizhuRef.value.open(actionId.value); | ||
| 73 | + } | ||
| 74 | + }); | ||
| 75 | +}; | ||
| 76 | + | ||
| 77 | +// 接收子组件传过来的备注内容 | ||
| 78 | +// const handleNoteSave = async (content) => { | ||
| 79 | +// try { | ||
| 80 | +// if (type.value == 2) { | ||
| 81 | +// await SupersetsApi.addNotes({ | ||
| 82 | +// supersetsId: actionId.value, | ||
| 83 | +// content: content, | ||
| 84 | +// }); | ||
| 85 | +// } else { | ||
| 86 | +// await ExercisesApi.addNotes({ | ||
| 87 | +// exerciseId: actionId.value, | ||
| 88 | +// content: content, | ||
| 89 | +// }); | ||
| 90 | +// } | ||
| 91 | +// actionDetail.value.userNote = content; | ||
| 92 | +// } catch (e) { | ||
| 93 | +// console.log(e); | ||
| 94 | +// } | ||
| 95 | +// }; | ||
| 96 | + | ||
| 97 | +// 加载训练历史 | ||
| 98 | +const loadTrainHistoryDetail = async (id, type) => { | ||
| 99 | + try { | ||
| 100 | + console.log("即将查询的历史ID =", id); | ||
| 101 | + const res = await TrainingApi.getTrainHistoryList(id, type); | ||
| 102 | + console.log('接口完整返回:', res); | ||
| 103 | + historyList.value = res.data; | ||
| 104 | + console.log('动作训练的训练历史列表接口返回结果historyList.value', historyList.value); | ||
| 105 | + } catch (error) { | ||
| 106 | + console.error('加载训练历史失败:', error); | ||
| 107 | + } | ||
| 108 | +} | ||
| 109 | + | ||
| 110 | +// 1. 格式化日期数组为 YYYY/MM/DD 字符串 | ||
| 111 | +const formatDateArr = (dateArr) => { | ||
| 112 | + if (!Array.isArray(dateArr) || dateArr.length < 3) return '' | ||
| 113 | + const [year, month, day] = dateArr | ||
| 114 | + return `${year}/${String(month).padStart(2, '0')}/${String(day).padStart(2, '0')}` | ||
| 115 | +} | ||
| 116 | + | ||
| 117 | +// 2. 把每一组的 setConfigList 转成「可渲染的文本」 | ||
| 118 | +const formattedSetList = computed(() => { | ||
| 119 | + return historyList.value.map(item => { | ||
| 120 | + const sets = (item.setConfigList || []).map(set => { | ||
| 121 | + // 自动拼接:优先显示重量/次数,再显示时长/距离 | ||
| 122 | + const parts = [] | ||
| 123 | + if (set.weight != null && set.weight !== '') { | ||
| 124 | + parts.push(`${set.weight}kg`) | ||
| 125 | + } | ||
| 126 | + if (set.reps != null && set.reps !== '') { | ||
| 127 | + parts.push(`${set.reps}次`) | ||
| 128 | + } | ||
| 129 | + if (set.duration != null && set.duration > 0) { | ||
| 130 | + // 秒转成 HH:MM:SS | ||
| 131 | + const h = Math.floor(set.duration / 3600) | ||
| 132 | + const m = Math.floor((set.duration % 3600) / 60) | ||
| 133 | + const s = set.duration % 60 | ||
| 134 | + const hh = h > 0 ? `${h}:` : '' | ||
| 135 | + parts.push(`${hh}${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`) | ||
| 136 | + } | ||
| 137 | + if (set.distance != null && set.distance !== '') { | ||
| 138 | + parts.push(`${set.distance}m`) | ||
| 139 | + } | ||
| 140 | + return { | ||
| 141 | + idx: set.setIndex + 1, | ||
| 142 | + text: parts.length > 0 ? parts.join(' × ') : '无数据' | ||
| 143 | + } | ||
| 144 | + }) | ||
| 145 | + return { | ||
| 146 | + date: formatDateArr(item.date), | ||
| 147 | + name: item.name || '训练记录', | ||
| 148 | + setCount: item.setCount || 0, | ||
| 149 | + // 容量:如果有重量,就是 重量×次数×组数;如果没有,留空或0 | ||
| 150 | + capacity: item.weight != null ? (item.weight * item.setCount) : 0, | ||
| 151 | + sets: sets | ||
| 152 | + } | ||
| 153 | + }) | ||
| 154 | +}) | ||
| 155 | + | ||
| 156 | +// 3. 自动计算「最高重量、最高容量、最新日期」 | ||
| 157 | +const statData = computed(() => { | ||
| 158 | + if (!historyList.value.length) return { maxWeight: '0kg', maxReps: 0, maxCapacity: '0', maxCapacitySets: 0, recordDate: '' } | ||
| 159 | + | ||
| 160 | + let maxW = 0, maxR = 0, maxC = 0, maxCS = 0 | ||
| 161 | + let latestDate = '' | ||
| 162 | + | ||
| 163 | + historyList.value.forEach(item => { | ||
| 164 | + // 最高重量:遍历每组数据找最大 weight | ||
| 165 | + item.setConfigList?.forEach(set => { | ||
| 166 | + if (set.weight > maxW) { | ||
| 167 | + maxW = set.weight | ||
| 168 | + maxR = set.reps || 0 | ||
| 169 | + } | ||
| 170 | + }) | ||
| 171 | + // 最高容量:按 重量×次数×组数 计算 | ||
| 172 | + const capacity = item.weight ? item.weight * (item.setConfigList?.length || 0) : 0 | ||
| 173 | + if (capacity > maxC) { | ||
| 174 | + maxC = capacity | ||
| 175 | + maxCS = item.setConfigList?.length || 0 | ||
| 176 | + } | ||
| 177 | + // 最新日期:取最大的 date 数组 | ||
| 178 | + const currentDateStr = formatDateArr(item.date) | ||
| 179 | + if (!latestDate || item.date > latestDate) { | ||
| 180 | + latestDate = currentDateStr | ||
| 181 | + } | ||
| 182 | + }) | ||
| 183 | + | ||
| 184 | + return { | ||
| 185 | + maxWeight: maxW > 0 ? `${maxW}kg` : '0kg', | ||
| 186 | + maxReps: maxR, | ||
| 187 | + maxCapacity: maxC, | ||
| 188 | + maxCapacitySets: maxCS, | ||
| 189 | + recordDate: latestDate | ||
| 190 | + } | ||
| 191 | +}) | ||
| 192 | + | ||
| 193 | +defineExpose({ openHistoryPopup }); | ||
| 194 | + | ||
| 195 | +</script> | ||
| 196 | + | ||
| 197 | +<style lang="scss" scoped> | ||
| 198 | +$bg-dark: #2c2c2e; | ||
| 199 | +$card-bg: #3a3a3c; | ||
| 200 | +$text-main: #ffffff; | ||
| 201 | +$text-gray: #8e8e93; | ||
| 202 | +$theme-yellow: #f8d714; | ||
| 203 | + | ||
| 204 | +.history-popup { | ||
| 205 | + width: 100%; | ||
| 206 | + max-height: 80vh; | ||
| 207 | + background-color: $bg-dark; | ||
| 208 | + border-radius: 24rpx 24rpx 0 0; | ||
| 209 | + padding: 30rpx; | ||
| 210 | + box-sizing: border-box; | ||
| 211 | + | ||
| 212 | + .header { | ||
| 213 | + .title { | ||
| 214 | + font-size: 40rpx; | ||
| 215 | + font-weight: bold; | ||
| 216 | + color: $text-main; | ||
| 217 | + margin-bottom: 30rpx; | ||
| 218 | + } | ||
| 219 | + } | ||
| 220 | + | ||
| 221 | + .top-cards { | ||
| 222 | + display: flex; | ||
| 223 | + gap: 20rpx; | ||
| 224 | + margin-bottom: 20rpx; | ||
| 225 | + | ||
| 226 | + .card { | ||
| 227 | + flex: 1; | ||
| 228 | + background-color: $card-bg; | ||
| 229 | + border-radius: 16rpx; | ||
| 230 | + padding: 30rpx; | ||
| 231 | + | ||
| 232 | + .label { | ||
| 233 | + font-size: 28rpx; | ||
| 234 | + color: $text-gray; | ||
| 235 | + margin-bottom: 16rpx; | ||
| 236 | + } | ||
| 237 | + | ||
| 238 | + .value { | ||
| 239 | + font-size: 36rpx; | ||
| 240 | + font-weight: bold; | ||
| 241 | + color: $theme-yellow; | ||
| 242 | + margin-bottom: 10rpx; | ||
| 243 | + } | ||
| 244 | + | ||
| 245 | + .date { | ||
| 246 | + font-size: 24rpx; | ||
| 247 | + color: $text-gray; | ||
| 248 | + } | ||
| 249 | + } | ||
| 250 | + } | ||
| 251 | + | ||
| 252 | + .chart-card { | ||
| 253 | + background-color: $card-bg; | ||
| 254 | + border-radius: 16rpx; | ||
| 255 | + padding: 30rpx; | ||
| 256 | + margin-bottom: 20rpx; | ||
| 257 | + | ||
| 258 | + .chart-header { | ||
| 259 | + display: flex; | ||
| 260 | + justify-content: space-between; | ||
| 261 | + align-items: center; | ||
| 262 | + margin-bottom: 30rpx; | ||
| 263 | + | ||
| 264 | + .title { | ||
| 265 | + font-size: 28rpx; | ||
| 266 | + color: $text-main; | ||
| 267 | + } | ||
| 268 | + | ||
| 269 | + .unit { | ||
| 270 | + font-size: 24rpx; | ||
| 271 | + color: $text-gray; | ||
| 272 | + } | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + .chart-placeholder { | ||
| 276 | + height: 200rpx; | ||
| 277 | + position: relative; | ||
| 278 | + | ||
| 279 | + .line { | ||
| 280 | + position: absolute; | ||
| 281 | + top: 0; | ||
| 282 | + left: 0; | ||
| 283 | + right: 0; | ||
| 284 | + height: 160rpx; | ||
| 285 | + border-bottom: 1rpx solid #444; | ||
| 286 | + | ||
| 287 | + .point { | ||
| 288 | + position: absolute; | ||
| 289 | + top: 0; | ||
| 290 | + transform: translateX(-50%); | ||
| 291 | + | ||
| 292 | + .dot { | ||
| 293 | + width: 12rpx; | ||
| 294 | + height: 12rpx; | ||
| 295 | + border-radius: 50%; | ||
| 296 | + background-color: $theme-yellow; | ||
| 297 | + margin: 0 auto; | ||
| 298 | + } | ||
| 299 | + | ||
| 300 | + .val { | ||
| 301 | + display: block; | ||
| 302 | + font-size: 26rpx; | ||
| 303 | + color: $theme-yellow; | ||
| 304 | + text-align: center; | ||
| 305 | + margin-bottom: 10rpx; | ||
| 306 | + } | ||
| 307 | + } | ||
| 308 | + } | ||
| 309 | + | ||
| 310 | + .axis { | ||
| 311 | + position: absolute; | ||
| 312 | + bottom: 0; | ||
| 313 | + left: 0; | ||
| 314 | + right: 0; | ||
| 315 | + display: flex; | ||
| 316 | + justify-content: space-between; | ||
| 317 | + | ||
| 318 | + .axis-item { | ||
| 319 | + font-size: 24rpx; | ||
| 320 | + color: $text-gray; | ||
| 321 | + position: absolute; | ||
| 322 | + transform: translateX(-50%); | ||
| 323 | + } | ||
| 324 | + } | ||
| 325 | + } | ||
| 326 | + } | ||
| 327 | + | ||
| 328 | + .list { | ||
| 329 | + .record-item { | ||
| 330 | + background-color: $card-bg; | ||
| 331 | + border-radius: 16rpx; | ||
| 332 | + padding: 30rpx; | ||
| 333 | + margin-bottom: 20rpx; | ||
| 334 | + | ||
| 335 | + .date { | ||
| 336 | + font-size: 26rpx; | ||
| 337 | + color: $text-gray; | ||
| 338 | + margin-bottom: 20rpx; | ||
| 339 | + } | ||
| 340 | + | ||
| 341 | + .name { | ||
| 342 | + font-size: 32rpx; | ||
| 343 | + font-weight: bold; | ||
| 344 | + color: $text-main; | ||
| 345 | + margin-bottom: 16rpx; | ||
| 346 | + } | ||
| 347 | + | ||
| 348 | + .info { | ||
| 349 | + font-size: 26rpx; | ||
| 350 | + color: $text-gray; | ||
| 351 | + margin-bottom: 20rpx; | ||
| 352 | + } | ||
| 353 | + | ||
| 354 | + .set-list { | ||
| 355 | + .set { | ||
| 356 | + display: flex; | ||
| 357 | + align-items: center; | ||
| 358 | + margin-bottom: 12rpx; | ||
| 359 | + | ||
| 360 | + .num { | ||
| 361 | + width: 40rpx; | ||
| 362 | + height: 40rpx; | ||
| 363 | + border-radius: 50%; | ||
| 364 | + background-color: #444; | ||
| 365 | + color: $text-main; | ||
| 366 | + display: flex; | ||
| 367 | + align-items: center; | ||
| 368 | + justify-content: center; | ||
| 369 | + font-size: 24rpx; | ||
| 370 | + margin-right: 16rpx; | ||
| 371 | + } | ||
| 372 | + | ||
| 373 | + .text { | ||
| 374 | + font-size: 28rpx; | ||
| 375 | + color: $text-main; | ||
| 376 | + } | ||
| 377 | + } | ||
| 378 | + } | ||
| 379 | + } | ||
| 380 | + } | ||
| 381 | +} | ||
| 382 | +</style> |
| 1 | +<template> | ||
| 2 | + <view> | ||
| 3 | + <!-- 动作排序弹窗 --> | ||
| 4 | + <up-popup :show="actionSortShow" mode="bottom" bgColor="#242424" round="24rpx"> | ||
| 5 | + <view class="action-sort-popup"> | ||
| 6 | + <view class="sort-header"> | ||
| 7 | + <view class="operate"> | ||
| 8 | + <view class="close" @click="actionSortShow = false"> | ||
| 9 | + <up-icon name="close" color="#fff" size="20"></up-icon> | ||
| 10 | + </view> | ||
| 11 | + <view class="title">动作排序</view> | ||
| 12 | + <view class="add" @click="addActionsPopup"> | ||
| 13 | + <up-icon name="plus" color="#F2DC0B" size="15"></up-icon> | ||
| 14 | + <text>添加</text> | ||
| 15 | + </view> | ||
| 16 | + </view> | ||
| 17 | + </view> | ||
| 18 | + | ||
| 19 | + <scroll-view scroll-y class="sort-scroll-view" :scroll-with-animation="true"> | ||
| 20 | + <view v-for="(item, index) in sortList" :key="item.unitId || index" class="drag-item-card"> | ||
| 21 | + <view class="item-left"> | ||
| 22 | + <up-icon name="trash" color="#fff" size="22" @click="deleteUnit(item, index)"></up-icon> | ||
| 23 | + <image class="item-img" :src="item.exercises?.[0]?.urlImage || ''" v-if="item.unitType === 1" | ||
| 24 | + mode="aspectFill" /> | ||
| 25 | + <image class="item-img" | ||
| 26 | + src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260507/超级组_1778117889451.png" v-else | ||
| 27 | + mode="aspectFill" /> | ||
| 28 | + <view class="item-info"> | ||
| 29 | + <view class="item-name">{{ item.unitName || '未命名动作' }}</view> | ||
| 30 | + <view class="item-tags" v-if="item.unitType === 2">超级组</view> | ||
| 31 | + <view class="item-tags" v-if="item.unitType === 1"> | ||
| 32 | + {{ item.exercises?.[0]?.categoryDescription || '' }} | ||
| 33 | + {{ item.exercises?.[0]?.equipmentDescription || '' }} | ||
| 34 | + </view> | ||
| 35 | + </view> | ||
| 36 | + </view> | ||
| 37 | + <view class="item-right"> | ||
| 38 | + <up-icon name="plus-circle" color="#666" size="22" @click="copyUnit(item, index)"></up-icon> | ||
| 39 | + </view> | ||
| 40 | + </view> | ||
| 41 | + </scroll-view> | ||
| 42 | + </view> | ||
| 43 | + </up-popup> | ||
| 44 | + <!-- 动作新增组件 --> | ||
| 45 | + <!-- <addActions :show="addActionsShow" @close="addActionsShow = false" /> --> | ||
| 46 | + | ||
| 47 | + </view> | ||
| 48 | +</template> | ||
| 49 | + | ||
| 50 | +<script setup> | ||
| 51 | +import { ref } from 'vue'; | ||
| 52 | +import { useTrainingStore } from '@/sheep/store/trainingStore' | ||
| 53 | +const emit = defineEmits(['open-add-actions']) | ||
| 54 | +const trainingStore = useTrainingStore() | ||
| 55 | + | ||
| 56 | +// --- 动作排序 ---弹窗 | ||
| 57 | +const actionSortShow = ref(false); | ||
| 58 | +const sortList = ref([]) | ||
| 59 | +// 这里是打开动作排序的方法 | ||
| 60 | +const openActionSort = () => { | ||
| 61 | + sortList.value = trainingStore.actionDetail?.units?.map((unit, index) => ({ | ||
| 62 | + ...unit, | ||
| 63 | + exercises: unit.exercises || [] | ||
| 64 | + })) || []; | ||
| 65 | + actionSortShow.value = true; | ||
| 66 | +}; | ||
| 67 | + | ||
| 68 | +// const addActionsPopup = () => { | ||
| 69 | +// if (actionSortShow.value === true) { | ||
| 70 | +// actionSortShow.value = false | ||
| 71 | +// } | ||
| 72 | +// console.log("==================== 点击了加号!") | ||
| 73 | +// addActionsShow.value = true // 打开弹窗 | ||
| 74 | +// console.log("✅ 弹窗打开:", addActionsShow.value) | ||
| 75 | +// } | ||
| 76 | +const addActionsPopup = () => { | ||
| 77 | + actionSortShow.value = false | ||
| 78 | + // 不在这里打开,而是通知父页面 | ||
| 79 | + emit('open-add-actions') | ||
| 80 | +} | ||
| 81 | + | ||
| 82 | +const deleteUnit = (item, index) => { | ||
| 83 | + console.log('🗑️ 点击删除:', item) | ||
| 84 | + console.log('🗑️ 删除索引:', index) | ||
| 85 | + uni.showModal({ | ||
| 86 | + title: '确认删除', | ||
| 87 | + content: `确定要删除【${item.unitName}】吗?`, | ||
| 88 | + success: (res) => { | ||
| 89 | + if (res.confirm) { | ||
| 90 | + console.log('✅ 用户确认删除') | ||
| 91 | + sortList.value.splice(index, 1) | ||
| 92 | + trainingStore.actionDetail.units.splice(index, 1) | ||
| 93 | + console.log('✅ 删除后剩余 units:', trainingStore.actionDetail.units) | ||
| 94 | + } else { | ||
| 95 | + console.log('❌ 用户取消删除') | ||
| 96 | + } | ||
| 97 | + } | ||
| 98 | + }) | ||
| 99 | +} | ||
| 100 | + | ||
| 101 | +// 复制当前 unit,并插入到下方 | ||
| 102 | +const copyUnit = (item, index) => { | ||
| 103 | + console.log('📋 点击复制:', item) | ||
| 104 | + console.log('📋 复制索引:', index) | ||
| 105 | + const newUnit = JSON.parse(JSON.stringify(item)) | ||
| 106 | + sortList.value.splice(index + 1, 0, newUnit) | ||
| 107 | + trainingStore.actionDetail.units.splice(index + 1, 0, newUnit) | ||
| 108 | + console.log('✅ 复制成功!当前列表:', sortList.value) | ||
| 109 | + uni.showToast({ | ||
| 110 | + title: '已复制到下方', | ||
| 111 | + icon: 'success' | ||
| 112 | + }) | ||
| 113 | +} | ||
| 114 | + | ||
| 115 | +defineExpose({ | ||
| 116 | + openActionSort | ||
| 117 | +}) | ||
| 118 | + | ||
| 119 | +</script> | ||
| 120 | + | ||
| 121 | +<style lang="scss" scoped> | ||
| 122 | +/* ========== 拖拽排序 优化样式 ========== */ | ||
| 123 | +.action-sort-popup { | ||
| 124 | + width: 100%; | ||
| 125 | + height: 80vh; | ||
| 126 | + display: flex; | ||
| 127 | + flex-direction: column; | ||
| 128 | + background: #242424; | ||
| 129 | + border-radius: 20rpx 20rpx 0 0; | ||
| 130 | +} | ||
| 131 | + | ||
| 132 | +.sort-header { | ||
| 133 | + padding: 30rpx; | ||
| 134 | + color: #fff; | ||
| 135 | + height: 10vh; | ||
| 136 | + box-sizing: border-box; | ||
| 137 | + | ||
| 138 | + .operate { | ||
| 139 | + display: flex; | ||
| 140 | + align-items: center; | ||
| 141 | + justify-content: space-between; | ||
| 142 | + | ||
| 143 | + .title { | ||
| 144 | + font-size: 32rpx; | ||
| 145 | + font-weight: bold; | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + .add { | ||
| 149 | + display: flex; | ||
| 150 | + align-items: center; | ||
| 151 | + color: #f2dc0b; | ||
| 152 | + font-size: 26rpx; | ||
| 153 | + gap: 8rpx; | ||
| 154 | + } | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + // .tip { | ||
| 158 | + // font-size: 24rpx; | ||
| 159 | + // color: #8e8e93; | ||
| 160 | + // margin-top: 16rpx; | ||
| 161 | + // } | ||
| 162 | +} | ||
| 163 | + | ||
| 164 | +.sort-scroll-view { | ||
| 165 | + height: 70vh; | ||
| 166 | +} | ||
| 167 | + | ||
| 168 | +// .drag-area { | ||
| 169 | +// width: 100%; | ||
| 170 | +// position: relative; | ||
| 171 | +// } | ||
| 172 | + | ||
| 173 | +// .drag-view { | ||
| 174 | +// width: 100%; | ||
| 175 | +// position: absolute; | ||
| 176 | +// transition: transform 0.2s ease; | ||
| 177 | +// } | ||
| 178 | + | ||
| 179 | +/* 拖拽项卡片 */ | ||
| 180 | +.drag-item-card { | ||
| 181 | + width: 100%; | ||
| 182 | + background: #2c2c2e; | ||
| 183 | + border-radius: 16rpx; | ||
| 184 | + padding: 24rpx; | ||
| 185 | + margin-bottom: 16rpx; | ||
| 186 | + display: flex; | ||
| 187 | + align-items: center; | ||
| 188 | + justify-content: space-between; | ||
| 189 | + box-sizing: border-box; | ||
| 190 | + transition: all 0.2s ease; | ||
| 191 | + border: 2rpx solid transparent; | ||
| 192 | +} | ||
| 193 | + | ||
| 194 | +/* 拖拽中高亮效果 */ | ||
| 195 | +.drag-item-card.is-active { | ||
| 196 | + background: #3a3a3c; | ||
| 197 | + border-color: #fbdf09; | ||
| 198 | + transform: scale(1.02); | ||
| 199 | + box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.4); | ||
| 200 | +} | ||
| 201 | + | ||
| 202 | +.item-left { | ||
| 203 | + display: flex; | ||
| 204 | + align-items: center; | ||
| 205 | + gap: 20rpx; | ||
| 206 | +} | ||
| 207 | + | ||
| 208 | +.item-img { | ||
| 209 | + width: 120rpx; | ||
| 210 | + height: 120rpx; | ||
| 211 | + border-radius: 12rpx; | ||
| 212 | + object-fit: cover; | ||
| 213 | +} | ||
| 214 | + | ||
| 215 | +.item-info { | ||
| 216 | + display: flex; | ||
| 217 | + flex-direction: column; | ||
| 218 | + gap: 8rpx; | ||
| 219 | +} | ||
| 220 | + | ||
| 221 | +.item-name { | ||
| 222 | + font-size: 28rpx; | ||
| 223 | + color: #fff; | ||
| 224 | + font-weight: 500; | ||
| 225 | +} | ||
| 226 | + | ||
| 227 | +.item-tags { | ||
| 228 | + font-size: 22rpx; | ||
| 229 | + color: #8e8e93; | ||
| 230 | +} | ||
| 231 | + | ||
| 232 | +.item-right { | ||
| 233 | + display: flex; | ||
| 234 | + align-items: center; | ||
| 235 | + gap: 30rpx; | ||
| 236 | +} | ||
| 237 | + | ||
| 238 | +/* 拖拽手柄 */ | ||
| 239 | +.drag-handle { | ||
| 240 | + padding: 20rpx; | ||
| 241 | + /* 增大点击区域 */ | ||
| 242 | + color: #8e8e93; | ||
| 243 | + transition: all 0.2s ease; | ||
| 244 | + display: flex; | ||
| 245 | + align-items: center; | ||
| 246 | + justify-content: center; | ||
| 247 | +} | ||
| 248 | + | ||
| 249 | +/* 激活状态下的高亮 */ | ||
| 250 | +.drag-item-card.is-active .drag-handle { | ||
| 251 | + color: #fbdf09; | ||
| 252 | + transform: scale(1.1); | ||
| 253 | +} | ||
| 254 | + | ||
| 255 | +/* 提示用户可长按 */ | ||
| 256 | +.drag-handle:active { | ||
| 257 | + opacity: 0.7; | ||
| 258 | +} | ||
| 259 | +</style> |
| 1 | +<template> | ||
| 2 | + <view class="action-container" @click="closeDifficulty"> | ||
| 3 | + <view class="action-list" v-if="deleteActionShow === false"> | ||
| 4 | + <view class="card"> | ||
| 5 | + <view class="card-top" @click.stop="isExpanded = !isExpanded"> | ||
| 6 | + <view v-if="type === 1"> | ||
| 7 | + <image class="action-image" :src="actionDetail?.urlImage || lostImage" mode="aspectFill"></image> | ||
| 8 | + </view> | ||
| 9 | + <view v-if="type === 2"> | ||
| 10 | + <image class="action-image" | ||
| 11 | + src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260507/超级组_1778117889451.png" | ||
| 12 | + mode="aspectFill"></image> | ||
| 13 | + </view> | ||
| 14 | + <view class="action-info"> | ||
| 15 | + <!-- 动作的 --> | ||
| 16 | + <text class="name">{{ actionDetail?.name }}</text> | ||
| 17 | + <text class="tags" v-if="type === 1">{{ actionDetail?.categoryDescription }} {{ | ||
| 18 | + actionDetail?.equipmentDescription }}</text> | ||
| 19 | + <!-- 这里还缺kg/要搞个计算属性 --> | ||
| 20 | + <text class="tags" v-if="type === 2">超级组</text> | ||
| 21 | + <view class="dot-list" v-if="!isExpanded"> | ||
| 22 | + <view class="dot" v-for="(item, index) in recordList" :key="'dot-' + index"></view> | ||
| 23 | + </view> | ||
| 24 | + </view> | ||
| 25 | + <view class="more-circle" @click.stop="toggleTips"> | ||
| 26 | + <up-icon name="more-dot-fill" color="#fff" size="15" class="icon"></up-icon> | ||
| 27 | + <view class="tips" v-if="showTips"> | ||
| 28 | + <view class="tip" @click="openBeizhu">文字备注</view> | ||
| 29 | + <view class="tip" @click="openAllQuickTimer">组间休息</view> | ||
| 30 | + <view class="tip" @click="handleReplaceAction">动作替换</view> | ||
| 31 | + <view class="tip" @click="deleteAction">删除动作</view> | ||
| 32 | + </view> | ||
| 33 | + </view> | ||
| 34 | + </view> | ||
| 35 | + | ||
| 36 | + <view class="main" v-if="isExpanded"> | ||
| 37 | + <!-- 当类型为4,5时,显示体重输入框 --> | ||
| 38 | + <!--动作的 recordList 的输入控制 --> | ||
| 39 | + <template v-if="type === 1"> | ||
| 40 | + <view class="userWeight" v-if="[4, 5].includes(actionDetail.exerciseType)"> | ||
| 41 | + <view class="row" hover-class="none" hover-stop-propagation="false"> | ||
| 42 | + <text class="label">体重</text> | ||
| 43 | + <view class="userWeight-values" @click="openWeightPicker"> | ||
| 44 | + <text class="weight-text" style="color: #fbdc00; font-size: 20rpx;"> | ||
| 45 | + {{ displayWeight }} | ||
| 46 | + </text> | ||
| 47 | + <up-icon name="edit-pen" color="#8e8e93" size="20"></up-icon> | ||
| 48 | + </view> | ||
| 49 | + </view> | ||
| 50 | + <view class="explain" v-if="actionDetail?.exerciseType === 4"> | ||
| 51 | + 该动作为自重加重,根据你的体重加上辅助器械重量计算容量,下方的填写重量为辅助器械重量。 | ||
| 52 | + </view> | ||
| 53 | + <view class="explain" v-if="actionDetail?.exerciseType === 5"> | ||
| 54 | + 该动作为自重减重,根据你的体重减去辅助器械重量计算容量,下方的填写重量为辅助器械重量。 | ||
| 55 | + </view> | ||
| 56 | + </view> | ||
| 57 | + <!-- 导入子组件 --> | ||
| 58 | + <action-set-input v-for="(item, index) in recordList" :key="index" | ||
| 59 | + :exercise-type="actionDetail.exerciseType" :record="item" :index="index" :is-editing="isEditing" | ||
| 60 | + @openPicker="openPicker" @openQuickTimer="openQuickTimer" @toggleActive="toggleActive" @addRow="addRow" | ||
| 61 | + @deleteRow="deleteRow" :user-weight="userWeight" @change-weight="userWeight = $event" /> | ||
| 62 | + </template> | ||
| 63 | + <!-- 超级组的训练数据控制 --> | ||
| 64 | + <template v-else-if="type === 2"> | ||
| 65 | + <view class="super-group"> | ||
| 66 | + <view class="img-list" v-if="type === 2"> | ||
| 67 | + <image v-for="item in (actionDetail?.exercises || [])" :key="item.id" :src="item?.urlImage || lostImage" | ||
| 68 | + class="img"></image> | ||
| 69 | + </view> | ||
| 70 | + <!-- 超级组:如果内部包含自重加重/减重,显示体重输入v-if="hasWeightExercise" --> | ||
| 71 | + <view class="userWeight" v-if="hasWeightExercise"> | ||
| 72 | + <view class="row"> | ||
| 73 | + <text class="label">体重</text> | ||
| 74 | + <view class="userWeight-values" @click="openWeightPicker"> | ||
| 75 | + <text class="weight-text" style="color: #fbdc00; font-size: 20rpx;"> | ||
| 76 | + {{ displayWeight }} | ||
| 77 | + </text> | ||
| 78 | + <up-icon name="edit-pen" color="#8e8e93" size="20"></up-icon> | ||
| 79 | + </view> | ||
| 80 | + </view> | ||
| 81 | + <!-- <view class="explain" v-if="actionDetail?.exerciseType === 4"> | ||
| 82 | + 该动作为自重加重,根据你的体重加上辅助器械重量计算容量,下方的填写重量为辅助器械重量。 | ||
| 83 | + </view> | ||
| 84 | + <view class="explain" v-if="actionDetail?.exerciseType === 5"> | ||
| 85 | + 该动作为自重减重,根据你的体重减去辅助器械重量计算容量,下方的填写重量为辅助器械重量。 | ||
| 86 | + </view> --> | ||
| 87 | + </view> | ||
| 88 | + <!-- 外层:循环【每一组】 --> | ||
| 89 | + <view class="set-group" v-for="(setItem, setIndex) in superTotalSets" :key="setIndex"> | ||
| 90 | + <view class="set-header"> | ||
| 91 | + <view class="index-box">{{ setIndex + 1 }}</view> | ||
| 92 | + <view class="set-action-names"> | ||
| 93 | + <text v-for="(sub, idx) in actionDetail.exercises" :key="sub.id"> | ||
| 94 | + {{ String.fromCharCode(65 + idx) }} {{ sub.name }} | ||
| 95 | + </text> | ||
| 96 | + </view> | ||
| 97 | + <!-- 休息时间选择 --> | ||
| 98 | + <view class="timer-selector" @click.stop="openSuperQuickTimer(setIndex)" v-if="!isEditing"> | ||
| 99 | + <text class="text">{{ superGroupSetData(setIndex).quickTimeDisplay || '60s' }}</text> | ||
| 100 | + <up-icon name="arrow-down" color="#8e8e93" size="10"></up-icon> | ||
| 101 | + </view> | ||
| 102 | + <!-- 勾选框 --> | ||
| 103 | + <view class="icon" @click.stop="toggleSuperSetActive(setIndex)" | ||
| 104 | + :class="{ active: superGroupSetData(setIndex).isActive }" v-if="!isEditing"> | ||
| 105 | + <up-icon name="checkmark" :color="superGroupSetData(setIndex).isActive ? '#000' : '#919191'" | ||
| 106 | + size="20" bold></up-icon> | ||
| 107 | + </view> | ||
| 108 | + <!-- 新增和删除按钮 --> | ||
| 109 | + <view class="icons" v-if="isEditing"> | ||
| 110 | + <view class="icon" @click.stop="addSuperSet"> | ||
| 111 | + <up-icon name="plus-circle" color="#fff" size="20" bold></up-icon> | ||
| 112 | + </view> | ||
| 113 | + <view class="icon del" @click.stop="deleteSuperSet(setIndex)"> | ||
| 114 | + <up-icon name="trash" color="#e63e1e" size="20" bold></up-icon> | ||
| 115 | + </view> | ||
| 116 | + </view> | ||
| 117 | + </view> | ||
| 118 | + | ||
| 119 | + <!-- 内层:循环【组内的每个子动作A/B】 --> | ||
| 120 | + <super-set-input v-for="(sub, idx) in actionDetail.exercises" :key="sub.id" | ||
| 121 | + :sub-exercise-type="sub.exerciseType" :set-index="setIndex" :sub-index="idx" :user-weight="userWeight" | ||
| 122 | + :data="getSubActionData(setIndex, sub.id)" @open-time-picker="openSuperPicker(setIndex, sub.id)" /> | ||
| 123 | + </view> | ||
| 124 | + </view> | ||
| 125 | + </template> | ||
| 126 | + | ||
| 127 | + </view> | ||
| 128 | + | ||
| 129 | + <view class="button-group" v-if="isExpanded"> | ||
| 130 | + <view class="btn-item" @click.stop="addGroup">加一组</view> | ||
| 131 | + <view class="btn-item" @click="openHistory(props?.id)">历史</view> | ||
| 132 | + <!-- 历史弹窗 --> | ||
| 133 | + <actionHistory ref="historyPopupRef" :actionName="actionDetail.name" :historyType="props.type" /> | ||
| 134 | + <!-- 动作难度选择弹窗 --> | ||
| 135 | + <view class="btn-item" @click.stop="showDifficulty = !showDifficulty"> | ||
| 136 | + <text :style="{ color: currentDifficulty.color }">{{ currentDifficulty.label }}</text> | ||
| 137 | + <up-icon name="arrow-down" size="8" color="#fff"></up-icon> | ||
| 138 | + | ||
| 139 | + <view class="difficulty-pop" v-if="showDifficulty"> | ||
| 140 | + <view class="difficulty-item" @click.stop="selectDifficulty('轻松', '#b2f055')"> | ||
| 141 | + <text :style="{ color: '#b2f055' }">轻松</text> | ||
| 142 | + <up-icon v-if="currentDifficulty.label === '轻松'" name="checkmark" color="#b2f055" size="16" | ||
| 143 | + bold></up-icon> | ||
| 144 | + </view> | ||
| 145 | + <view class="difficulty-item" @click.stop="selectDifficulty('正常', '#ffffff')"> | ||
| 146 | + <text :style="{ color: '#ffffff' }">正常</text> | ||
| 147 | + <up-icon v-if="currentDifficulty.label === '正常'" name="checkmark" color="#ffffff" size="16" | ||
| 148 | + bold></up-icon> | ||
| 149 | + </view> | ||
| 150 | + <view class="difficulty-item" @click.stop="selectDifficulty('困难', '#e26c2a')"> | ||
| 151 | + <text :style="{ color: '#e26c2a' }">困难</text> | ||
| 152 | + <up-icon v-if="currentDifficulty.label === '困难'" name="checkmark" color="#e26c2a" size="16" | ||
| 153 | + bold></up-icon> | ||
| 154 | + </view> | ||
| 155 | + </view> | ||
| 156 | + </view> | ||
| 157 | + | ||
| 158 | + <view class="btn-item" @click.stop="isEditing = !isEditing"> | ||
| 159 | + {{ isEditing ? '完成' : '编辑' }} | ||
| 160 | + </view> | ||
| 161 | + </view> | ||
| 162 | + </view> | ||
| 163 | + </view> | ||
| 164 | + | ||
| 165 | + <up-picker :show="showPicker" :columns="timeColumns" :defaultIndex="defaultTimeIndex" title="请选择运动时长" | ||
| 166 | + @confirm="onTimeConfirm" @cancel="showPicker = false" closeOnClickOverlay popupClass="custom-picker"></up-picker> | ||
| 167 | + | ||
| 168 | + <up-picker :show="showQuickPicker" :columns="quickColumns" title="设置休息时长" @confirm="onQuickConfirm" | ||
| 169 | + @cancel="showQuickPicker = false" closeOnClickOverlay popupClass="custom-picker"></up-picker> | ||
| 170 | + | ||
| 171 | + <!-- 备注弹窗组件(可创建备注成功,但无法显示到动作详情弹窗组件) --> | ||
| 172 | + <beizhu ref="showBeizhuRef" @saveSuccess="handleNoteSave" /> | ||
| 173 | + <!-- 动作替换弹窗 --> | ||
| 174 | + <replaceActionPopup :show="replacePopupShow" :unitIndex="unitIndex" @close="replacePopupShow = false" /> | ||
| 175 | + | ||
| 176 | + <!-- 体重选择器 --> | ||
| 177 | + <up-picker :show="showWeightPicker" :columns="weightColumns" :defaultIndex="defaultWeightIndex" title="设置体重" | ||
| 178 | + @confirm="onWeightConfirm" @cancel="showWeightPicker = false" closeOnClickOverlay | ||
| 179 | + popupClass="custom-picker"></up-picker> | ||
| 180 | + | ||
| 181 | + </view> | ||
| 182 | +</template> | ||
| 183 | + | ||
| 184 | +<script setup> | ||
| 185 | +import { ref, reactive, watch, computed, nextTick, onMounted } from 'vue'; | ||
| 186 | +import beizhu from '@/pages/xunji/components/beizhu.vue'; | ||
| 187 | +// import actionHistory from '../components/dongzuo-lianxi-history.vue' | ||
| 188 | +import actionHistory from '@/pages/xunji/components/dongzuo-lianxi/dongzuo-lianxi-history.vue' | ||
| 189 | +import SupersetsApi from '@/sheep/api/motion/supersets'; | ||
| 190 | +import ExercisesApi from '@/sheep/api/motion/exercises'; | ||
| 191 | +import { getCurrentInstance } from 'vue'; | ||
| 192 | +import { useTrainingStore } from '@/sheep/store/trainingStore' | ||
| 193 | +// import replaceActionPopup from '../components/replace-action-popup.vue' | ||
| 194 | +import replaceActionPopup from '@/pages/xunji/components/dongzuo-lianxi/replace-action-popup.vue' | ||
| 195 | +// import actionSetInput from '@/pages4/components/action-set-input.vue' | ||
| 196 | +import actionSetInput from '@/pages/xunji/components/dongzuo-lianxi/action-set-input.vue' | ||
| 197 | +// import superSetInput from '../components/super-set-input.vue' | ||
| 198 | +import superSetInput from '@/pages/xunji/components/dongzuo-lianxi/super-set-input.vue' | ||
| 199 | + | ||
| 200 | + | ||
| 201 | +const trainingStore = useTrainingStore() | ||
| 202 | +const deleteActionShow = ref(false) | ||
| 203 | +const showTips = ref(false); | ||
| 204 | +const isExpanded = ref(false); | ||
| 205 | +const isEditing = ref(false); | ||
| 206 | +const showPicker = ref(false); | ||
| 207 | +const showQuickPicker = ref(false); | ||
| 208 | +const activeIndex = ref(0); | ||
| 209 | +const activeActionId = ref(''); | ||
| 210 | +const emit = defineEmits(['register', 'update:groupCount', 'replace-action', 'deleteAction', 'replaceActionForSave']) | ||
| 211 | +const lostImage = "https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png" | ||
| 212 | + | ||
| 213 | +const historyPopupRef = ref(null) | ||
| 214 | +const showBeizhuRef = ref(null); | ||
| 215 | +const userWeight = ref(70) | ||
| 216 | +// 休息时间设置模式:single=单行,all=全部组 | ||
| 217 | +const restEditMode = ref('single'); | ||
| 218 | +const replacePopupShow = ref(false) | ||
| 219 | + | ||
| 220 | +// 接收父组件的数据 | ||
| 221 | +const props = defineProps({ | ||
| 222 | + id: [Number, String], | ||
| 223 | + type: [Number, String], | ||
| 224 | + index: Number, // 新增 | ||
| 225 | + unitIndex: Number, | ||
| 226 | + actionDetail: { | ||
| 227 | + type: Object, | ||
| 228 | + default: () => ({}) | ||
| 229 | + }, | ||
| 230 | + isDailyTemplates: { | ||
| 231 | + type: Boolean, | ||
| 232 | + default: false | ||
| 233 | + } | ||
| 234 | +}) | ||
| 235 | + | ||
| 236 | +watch(() => props.id, () => { | ||
| 237 | + console.log('dongzuo组件 id/type:', props.id, props.type) | ||
| 238 | + console.log('打印props.actionDetail', props.actionDetail) | ||
| 239 | + console.log('dongzuo组件 unitIndex:', props.unitIndex) | ||
| 240 | + if (props.type === 2) | ||
| 241 | + console.log('打印父组件传过来超级组动作列表的props.actionDetail.exercises', props.actionDetail.exercises); | ||
| 242 | + | ||
| 243 | +}, { immediate: true }) | ||
| 244 | + | ||
| 245 | +emit('register', props.index, getCurrentInstance()?.proxy) | ||
| 246 | + | ||
| 247 | +// 体重选择器 | ||
| 248 | +const displayWeight = computed(() => { | ||
| 249 | + return `${userWeight.value}kg` | ||
| 250 | +}) | ||
| 251 | + | ||
| 252 | +// 2. 体重选择器状态 | ||
| 253 | +const showWeightPicker = ref(false) | ||
| 254 | +const weightColumns = reactive([ | ||
| 255 | + // 整数部分:10 ~ 250 | ||
| 256 | + Array.from({ length: 241 }, (_, i) => (i + 10).toString()), | ||
| 257 | + // 小数部分:0 ~ 9 | ||
| 258 | + Array.from({ length: 10 }, (_, i) => i.toString()), | ||
| 259 | + ['千克(kg)'] // 第三列固定文字 | ||
| 260 | +]) | ||
| 261 | +const defaultWeightIndex = ref([60, 0, 0]) // 默认 70.0kg(10+60=70) | ||
| 262 | + | ||
| 263 | +// 3. 打开体重选择器 | ||
| 264 | +const openWeightPicker = () => { | ||
| 265 | + // 直接用数字处理,避免转字符串 | ||
| 266 | + const num = userWeight.value | ||
| 267 | + const intPart = Math.floor(num) | ||
| 268 | + const decPart = Math.round((num - intPart) * 10) // 取小数第一位 | ||
| 269 | + | ||
| 270 | + // 计算索引(整数部分:intPart - 10,小数部分:decPart) | ||
| 271 | + const intIndex = Math.max(0, Math.min(intPart - 10, 240)) | ||
| 272 | + defaultWeightIndex.value = [intIndex, decPart, 0] | ||
| 273 | + showWeightPicker.value = true | ||
| 274 | +} | ||
| 275 | + | ||
| 276 | +// 4. 体重选择器确认事件 | ||
| 277 | +const onWeightConfirm = (e) => { | ||
| 278 | + const intPart = e.value[0] // 整数部分 | ||
| 279 | + const decPart = e.value[1] // 小数部分 | ||
| 280 | + // 拼接成完整体重 | ||
| 281 | + userWeight.value = parseFloat(`${intPart}.${decPart}`) | ||
| 282 | + showWeightPicker.value = false | ||
| 283 | +} | ||
| 284 | + | ||
| 285 | +// 文字备注 | ||
| 286 | +// 打开备注弹窗 | ||
| 287 | +const openBeizhu = () => { | ||
| 288 | + nextTick(() => { | ||
| 289 | + if (showBeizhuRef.value) { | ||
| 290 | + showBeizhuRef.value.open(props.actionDetail.id); | ||
| 291 | + } | ||
| 292 | + }); | ||
| 293 | +}; | ||
| 294 | + | ||
| 295 | +// 接收子组件传过来的备注内容 | ||
| 296 | +const handleNoteSave = async (content) => { | ||
| 297 | + try { | ||
| 298 | + if (props.type == 2) { | ||
| 299 | + await SupersetsApi.addNotes({ | ||
| 300 | + supersetsId: props.id, | ||
| 301 | + content: content, | ||
| 302 | + }); | ||
| 303 | + } else { | ||
| 304 | + await ExercisesApi.addNotes({ | ||
| 305 | + exerciseId: props.id, | ||
| 306 | + content: content, | ||
| 307 | + }); | ||
| 308 | + } | ||
| 309 | + props.actionDetail.userNote = content; | ||
| 310 | + } catch (e) { | ||
| 311 | + console.log(e); | ||
| 312 | + } | ||
| 313 | +}; | ||
| 314 | + | ||
| 315 | +const toggleTips = () => { | ||
| 316 | + showTips.value = !showTips.value | ||
| 317 | +} | ||
| 318 | + | ||
| 319 | +// 超级组是否包含间歇类型动作(用来控制组头休息时间选择器的显示) | ||
| 320 | +const hasIntervalExercise = computed(() => { | ||
| 321 | + if (props.type !== 2) return false | ||
| 322 | + const exercises = props.actionDetail?.exercises || [] | ||
| 323 | + // 只要有一个子动作是间歇类型(6),就不显示组头的休息时间 | ||
| 324 | + return exercises.some(sub => sub.exerciseType === 6) | ||
| 325 | +}) | ||
| 326 | +// 替换动作 | ||
| 327 | +const handleReplaceAction = () => { | ||
| 328 | + showTips.value = false | ||
| 329 | + replacePopupShow.value = true // 打开弹窗 | ||
| 330 | + console.log("✅ 弹窗打开:", replacePopupShow.value) | ||
| 331 | +} | ||
| 332 | + | ||
| 333 | +// 超级组是否包含 4(自重加重) / 5(自重减重) | ||
| 334 | +const hasWeightExercise = computed(() => { | ||
| 335 | + if (props.type !== 2) return false | ||
| 336 | + const exercises = props.actionDetail?.exercises || [] | ||
| 337 | + return exercises.some(sub => [4, 5].includes(sub.exerciseType)) | ||
| 338 | +}) | ||
| 339 | + | ||
| 340 | + | ||
| 341 | +// 删除动作 | ||
| 342 | +const deleteAction = () => { | ||
| 343 | + | ||
| 344 | + if (props.isDailyTemplates) { | ||
| 345 | + showTips.value = false; | ||
| 346 | + emit('deleteAction'); | ||
| 347 | + // trainingStore.actionDetail.units.splice(props.unitIndex, 1) | ||
| 348 | + // trainingStore.clearTrainingStore() | ||
| 349 | + return; | ||
| 350 | + } | ||
| 351 | + trainingStore.deleteUnitRecord(props.unitIndex); | ||
| 352 | + console.log('训练类型 type =', trainingStore.type) | ||
| 353 | + console.log('组件类型 props.type =', props.type) | ||
| 354 | + console.log('unitIndex =', props.unitIndex) | ||
| 355 | + deleteActionShow.value = true | ||
| 356 | + | ||
| 357 | + // 1. 如果是【模板训练】,删除对应项 | ||
| 358 | + if (trainingStore.type === 3) { | ||
| 359 | + console.log('模板训练:删除第', props.unitIndex, '个单元') | ||
| 360 | + trainingStore.actionDetail.units.splice(props.unitIndex, 1) | ||
| 361 | + } | ||
| 362 | + else { | ||
| 363 | + console.log('单个动作/超级组:清空') | ||
| 364 | + trainingStore.clearTrainingStore() | ||
| 365 | + } | ||
| 366 | + // 关闭菜单 | ||
| 367 | + showTips.value = false | ||
| 368 | + console.log('删除完成 ✅') | ||
| 369 | + | ||
| 370 | + | ||
| 371 | +} | ||
| 372 | + | ||
| 373 | +// 难度控制逻辑 | ||
| 374 | +const showDifficulty = ref(false); | ||
| 375 | +const initialDifficulty = { label: '难度', color: '#ffffff' }; | ||
| 376 | +const currentDifficulty = ref({ ...initialDifficulty }); | ||
| 377 | + | ||
| 378 | +const selectDifficulty = (label, color) => { | ||
| 379 | + if (currentDifficulty.value.label === label) { | ||
| 380 | + currentDifficulty.value = { ...initialDifficulty }; | ||
| 381 | + } else { | ||
| 382 | + currentDifficulty.value = { label, color }; | ||
| 383 | + } | ||
| 384 | + showDifficulty.value = false; | ||
| 385 | +}; | ||
| 386 | + | ||
| 387 | +// 点击背景关闭所有弹窗 | ||
| 388 | +const closeDifficulty = () => { | ||
| 389 | + showDifficulty.value = false; | ||
| 390 | + showTips.value = false; | ||
| 391 | +}; | ||
| 392 | + | ||
| 393 | +// 超级组数据控制 | ||
| 394 | +const superRecordMap = ref({}) | ||
| 395 | +// 超级组:总组数(计算第一个动作的组数即可) | ||
| 396 | +const superTotalSets = computed(() => { | ||
| 397 | + if (props.type !== 2) return 0 | ||
| 398 | + const exercises = props.actionDetail?.exercises || [] | ||
| 399 | + if (exercises.length === 0) return 0 | ||
| 400 | + const firstId = exercises[0].id | ||
| 401 | + console.log('打印超级组的组数:', superRecordMap.value[firstId]?.length); | ||
| 402 | + return superRecordMap.value[firstId]?.length || 0 | ||
| 403 | +}) | ||
| 404 | + | ||
| 405 | +onMounted(() => { | ||
| 406 | + // 从 Pinia 加载当前 unit 的训练数据 | ||
| 407 | + const savedData = trainingStore.getUnitRecord(props.unitIndex); | ||
| 408 | + // 恢复体重 | ||
| 409 | + userWeight.value = savedData.userWeight || 70; | ||
| 410 | + console.log('从pinia加载的数据:', savedData); | ||
| 411 | + | ||
| 412 | + // 1. 先从 Pinia 恢复已有数据 | ||
| 413 | + if (props.type === 1) { | ||
| 414 | + // 关键:只有当前是初始默认1条,才赋值,不再覆盖watch结果 | ||
| 415 | + if (savedData.records?.[props.actionDetail.id]) { | ||
| 416 | + recordList.value = [...savedData.records[props.actionDetail.id]] | ||
| 417 | + console.log('onMounted兜底赋值', recordList.value) | ||
| 418 | + } | ||
| 419 | + } else if (props.type === 2) { | ||
| 420 | + if (savedData.records && Object.keys(savedData.records).length > 0) { | ||
| 421 | + superRecordMap.value = { ...savedData.records }; | ||
| 422 | + } | ||
| 423 | + } | ||
| 424 | + // 2. 超级组兜底初始化:只要有子动作,且 superRecordMap 为空就初始化 | ||
| 425 | + if (props.type === 2 && props.actionDetail?.exercises?.length) { | ||
| 426 | + const exercises = props.actionDetail.exercises; | ||
| 427 | + // 判断:当前超级组还没任何结构 才初始化 | ||
| 428 | + const firstId = exercises[0].id; | ||
| 429 | + if (!superRecordMap.value[firstId]) { | ||
| 430 | + const map = {}; | ||
| 431 | + exercises.forEach(item => { | ||
| 432 | + map[item.id] = [{ | ||
| 433 | + h: '00', m: '00', s: '00', quickTimeDisplay: '60s', | ||
| 434 | + distance: '', weight: '', reps: '', duration: '', restTime: '', isActive: false | ||
| 435 | + }]; | ||
| 436 | + }); | ||
| 437 | + superRecordMap.value = map; | ||
| 438 | + } | ||
| 439 | + } | ||
| 440 | + | ||
| 441 | + nextTick(() => { | ||
| 442 | + emit('register', props.index, { | ||
| 443 | + recordList: recordList.value, | ||
| 444 | + superRecordMap: superRecordMap.value | ||
| 445 | + }) | ||
| 446 | + }) | ||
| 447 | +}); | ||
| 448 | +// 动作组数据记录 | ||
| 449 | +const recordList = ref([ | ||
| 450 | + { h: '00', m: '00', s: '00', quickTimeDisplay: '60s', distance: '', weight: '', reps: '', duration: '', restTime: '', isActive: false }, | ||
| 451 | +]); | ||
| 452 | + | ||
| 453 | +watch(recordList, () => { | ||
| 454 | + // console.log('recordList变化,发送长度:', recordList.value.length) | ||
| 455 | + emit('update:groupCount', recordList.value.length) | ||
| 456 | +}, { deep: true, immediate: true }) | ||
| 457 | + | ||
| 458 | +const saveToStore = () => { | ||
| 459 | + let records = {}; | ||
| 460 | + | ||
| 461 | + if (props.type === 1) { | ||
| 462 | + // 普通动作 | ||
| 463 | + records[props.actionDetail.id] = recordList.value; | ||
| 464 | + | ||
| 465 | + } else if (props.type === 2) { | ||
| 466 | + // 超级组 | ||
| 467 | + records = superRecordMap.value; | ||
| 468 | + } | ||
| 469 | + | ||
| 470 | + // 调用 Pinia 保存 | ||
| 471 | + trainingStore.saveUnitRecord(props.unitIndex, { | ||
| 472 | + records: records, | ||
| 473 | + userWeight: userWeight.value | ||
| 474 | + }); | ||
| 475 | +}; | ||
| 476 | + | ||
| 477 | +watch([recordList, superRecordMap, userWeight], () => { | ||
| 478 | + saveToStore(); | ||
| 479 | + console.log('+++++++++打印全局训练数据 trainingStore.unitRecords++++++++', trainingStore.unitRecords); | ||
| 480 | +}, { deep: true, immediate: false }); | ||
| 481 | + | ||
| 482 | +const toggleActive = (index) => { | ||
| 483 | + recordList.value[index].isActive = !recordList.value[index].isActive; | ||
| 484 | +}; | ||
| 485 | + | ||
| 486 | +// 超级组工具方法 | ||
| 487 | +const superGroupSetData = (setIndex) => { | ||
| 488 | + const firstSub = props.actionDetail?.exercises?.[0] | ||
| 489 | + if (!firstSub) return {} | ||
| 490 | + return superRecordMap.value[firstSub.id]?.[setIndex] || {} | ||
| 491 | +} | ||
| 492 | +const getSubActionData = (setIndex, actionId) => { | ||
| 493 | + return superRecordMap.value[actionId]?.[setIndex] || { | ||
| 494 | + h: '00', m: '00', s: '00', | ||
| 495 | + quickTimeDisplay: '60s', | ||
| 496 | + distance: '', | ||
| 497 | + weight: '', | ||
| 498 | + reps: '', | ||
| 499 | + duration: '', | ||
| 500 | + restTime: '', | ||
| 501 | + isActive: false | ||
| 502 | + } | ||
| 503 | +} | ||
| 504 | + | ||
| 505 | +const openSuperPicker = (setIndex, actionId) => { | ||
| 506 | + activeIndex.value = setIndex | ||
| 507 | + activeActionId.value = actionId; // 保存当前动作ID | ||
| 508 | + showPicker.value = true | ||
| 509 | +} | ||
| 510 | +const openSuperQuickTimer = (setIndex) => { | ||
| 511 | + activeIndex.value = setIndex | ||
| 512 | + showQuickPicker.value = true | ||
| 513 | +} | ||
| 514 | +const toggleSuperSetActive = (setIndex) => { | ||
| 515 | + props.actionDetail.exercises.forEach(sub => { | ||
| 516 | + const list = superRecordMap.value[sub.id] | ||
| 517 | + if (list?.[setIndex]) { | ||
| 518 | + list[setIndex].isActive = !list[setIndex].isActive | ||
| 519 | + } | ||
| 520 | + }) | ||
| 521 | +} | ||
| 522 | +// 增加超级组的set组数 | ||
| 523 | +const addSuperSet = () => { | ||
| 524 | + const exercises = props.actionDetail?.exercises || [] | ||
| 525 | + exercises.forEach(sub => { | ||
| 526 | + if (superRecordMap.value[sub.id]) { | ||
| 527 | + superRecordMap.value[sub.id].push({ | ||
| 528 | + h: '00', m: '00', s: '00', | ||
| 529 | + quickTimeDisplay: '60s', | ||
| 530 | + distance: '', | ||
| 531 | + weight: '', | ||
| 532 | + reps: '', | ||
| 533 | + duration: '', | ||
| 534 | + restTime: '', | ||
| 535 | + isActive: false | ||
| 536 | + }) | ||
| 537 | + } | ||
| 538 | + }) | ||
| 539 | +} | ||
| 540 | +// 删除超级组的组数 | ||
| 541 | +const deleteSuperSet = (setIndex) => { | ||
| 542 | + const exercises = props.actionDetail?.exercises || [] | ||
| 543 | + exercises.forEach(sub => { | ||
| 544 | + const list = superRecordMap.value[sub.id] | ||
| 545 | + if (list && list.length > 1) { | ||
| 546 | + list.splice(setIndex, 1) | ||
| 547 | + } | ||
| 548 | + }) | ||
| 549 | +} | ||
| 550 | + | ||
| 551 | +// 合并后的 Picker 逻辑 | ||
| 552 | +const timeColumns = reactive([ | ||
| 553 | + Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0') + '时'), | ||
| 554 | + Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0') + '分'), | ||
| 555 | + Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0') + '秒'), | ||
| 556 | +]); | ||
| 557 | +const defaultTimeIndex = ref([0, 0, 0]); | ||
| 558 | +const quickColumns = reactive([ | ||
| 559 | + Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0') + '分'), | ||
| 560 | + Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0') + '秒'), | ||
| 561 | +]); | ||
| 562 | + | ||
| 563 | +const openPicker = (index) => { | ||
| 564 | + activeIndex.value = index; | ||
| 565 | + const item = recordList.value[index]; | ||
| 566 | + const hIdx = timeColumns[0].indexOf(item.h + '时'); | ||
| 567 | + const mIdx = timeColumns[1].indexOf(item.m + '分'); | ||
| 568 | + const sIdx = timeColumns[2].indexOf(item.s + '秒'); | ||
| 569 | + defaultTimeIndex.value = [hIdx, mIdx, sIdx]; | ||
| 570 | + showPicker.value = true; | ||
| 571 | +}; | ||
| 572 | +// | ||
| 573 | +const openQuickTimer = (index) => { | ||
| 574 | + activeIndex.value = index; | ||
| 575 | + showQuickPicker.value = true; | ||
| 576 | +}; | ||
| 577 | +// 组间休息 | ||
| 578 | +const openAllQuickTimer = () => { | ||
| 579 | + restEditMode.value = 'all'; // 重点:标记为全部修改 | ||
| 580 | + showQuickPicker.value = true; | ||
| 581 | +}; | ||
| 582 | + | ||
| 583 | +const onTimeConfirm = (e) => { | ||
| 584 | + const rawValues = e.value.map((val) => String(val).replace(/[时分秒]/g, '')) | ||
| 585 | + const setIdx = activeIndex.value | ||
| 586 | + | ||
| 587 | + if (props.type === 1) { | ||
| 588 | + const item = recordList.value[setIdx] | ||
| 589 | + item.h = rawValues[0] | ||
| 590 | + item.m = rawValues[1] | ||
| 591 | + item.s = rawValues[2] | ||
| 592 | + } else if (props.type === 2) { | ||
| 593 | + // 只修改当前点击的动作,不批量修改所有动作! | ||
| 594 | + const actionId = activeActionId.value; | ||
| 595 | + const list = superRecordMap.value[actionId]; | ||
| 596 | + if (list?.[setIdx]) { | ||
| 597 | + list[setIdx].h = rawValues[0]; | ||
| 598 | + list[setIdx].m = rawValues[1]; | ||
| 599 | + list[setIdx].s = rawValues[2]; | ||
| 600 | + } | ||
| 601 | + } | ||
| 602 | + showPicker.value = false | ||
| 603 | +} | ||
| 604 | + | ||
| 605 | +const onQuickConfirm = (e) => { | ||
| 606 | + const min = parseInt(e.value[0]) || 0 | ||
| 607 | + const sec = parseInt(e.value[1]) || 0 | ||
| 608 | + const totalSeconds = min * 60 + sec | ||
| 609 | + const setIdx = activeIndex.value | ||
| 610 | + | ||
| 611 | + // ============== 统一设置所有组 ============== | ||
| 612 | + if (restEditMode.value === 'all') { | ||
| 613 | + // 普通动作 | ||
| 614 | + if (props.type === 1) { | ||
| 615 | + recordList.value.forEach(item => { | ||
| 616 | + item.quickTimeDisplay = totalSeconds + 's' | ||
| 617 | + }) | ||
| 618 | + } | ||
| 619 | + // 超级组 | ||
| 620 | + else if (props.type === 2) { | ||
| 621 | + const firstSub = props.actionDetail?.exercises?.[0] | ||
| 622 | + if (!firstSub) return | ||
| 623 | + const allSets = superRecordMap.value[firstSub.id] | ||
| 624 | + if (!allSets) return | ||
| 625 | + allSets.forEach(set => { | ||
| 626 | + set.quickTimeDisplay = totalSeconds + 's' | ||
| 627 | + }) | ||
| 628 | + } | ||
| 629 | + } | ||
| 630 | + | ||
| 631 | + // ============== 单行修改 ============== | ||
| 632 | + else { | ||
| 633 | + if (props.type === 1) { | ||
| 634 | + recordList.value[setIdx].quickTimeDisplay = totalSeconds + 's' | ||
| 635 | + } else if (props.type === 2) { | ||
| 636 | + props.actionDetail.exercises.forEach(sub => { | ||
| 637 | + const list = superRecordMap.value[sub.id] | ||
| 638 | + if (list?.[setIdx]) { | ||
| 639 | + list[setIdx].quickTimeDisplay = totalSeconds + 's' | ||
| 640 | + } | ||
| 641 | + }) | ||
| 642 | + } | ||
| 643 | + } | ||
| 644 | + | ||
| 645 | + restEditMode.value = 'single' | ||
| 646 | + showQuickPicker.value = false | ||
| 647 | +} | ||
| 648 | + | ||
| 649 | + | ||
| 650 | +// 单动作的增加/删除 | ||
| 651 | +const addRow = () => { | ||
| 652 | + recordList.value.push({ | ||
| 653 | + h: '00', m: '00', s: '00', | ||
| 654 | + quickTimeDisplay: '60s', | ||
| 655 | + distance: '', weight: '', reps: '', isActive: false, | ||
| 656 | + }); | ||
| 657 | +}; | ||
| 658 | +const deleteRow = (index) => { | ||
| 659 | + if (recordList.value.length > 1) { | ||
| 660 | + recordList.value.splice(index, 1); | ||
| 661 | + } | ||
| 662 | +}; | ||
| 663 | + | ||
| 664 | +const addGroup = () => { | ||
| 665 | + if (props.type === 1) { | ||
| 666 | + addRow() | ||
| 667 | + } else if (props.type === 2) { | ||
| 668 | + addSuperSet() | ||
| 669 | + } | ||
| 670 | +} | ||
| 671 | + | ||
| 672 | +// 历史 | ||
| 673 | +const openHistory = (id) => { | ||
| 674 | + if (historyPopupRef.value && historyPopupRef.value.openHistoryPopup) { | ||
| 675 | + historyPopupRef.value.openHistoryPopup(id); | ||
| 676 | + } | ||
| 677 | +} | ||
| 678 | + | ||
| 679 | +// 动作名称渲染 | ||
| 680 | +const exerciseNamesWithLabel = computed(() => { | ||
| 681 | + const exercises = props.actionDetail?.exercises || [] | ||
| 682 | + return exercises.map((item, index) => { | ||
| 683 | + const label = String.fromCharCode(65 + index) | ||
| 684 | + return `${label} ${item?.name || '未知动作'}` | ||
| 685 | + }) | ||
| 686 | +}) | ||
| 687 | + | ||
| 688 | +// 获得真正的重量(支持普通动作 + 超级组) | ||
| 689 | +const getRealWeight = (item, subExerciseType = null) => { | ||
| 690 | + const userW = Number(userWeight.value) || 0 | ||
| 691 | + const inputW = Number(item.weight) || 0 | ||
| 692 | + // 重要:超级组子动作会传入自己的 exerciseType | ||
| 693 | + let type = subExerciseType ?? props.actionDetail?.exerciseType | ||
| 694 | + | ||
| 695 | + // console.log('【重量计算】', { | ||
| 696 | + // 动作类型: type, | ||
| 697 | + // 体重: userW, | ||
| 698 | + // 输入重量: inputW, | ||
| 699 | + // 最终重量: | ||
| 700 | + // type === 0 ? inputW : | ||
| 701 | + // type === 4 ? userW + inputW : | ||
| 702 | + // type === 5 ? userW - inputW : 0 | ||
| 703 | + // }) | ||
| 704 | + let realWeight = 0; | ||
| 705 | + if (type === 0) { | ||
| 706 | + realWeight = inputW; | ||
| 707 | + } else if (type === 4) { | ||
| 708 | + realWeight = userW + inputW; | ||
| 709 | + } else if (type === 5) { | ||
| 710 | + realWeight = userW - inputW; | ||
| 711 | + // 只做数据兜底修正,不在这里弹提示 | ||
| 712 | + if (realWeight < 0) { | ||
| 713 | + realWeight = 0; | ||
| 714 | + } | ||
| 715 | + } | ||
| 716 | + return realWeight; | ||
| 717 | +} | ||
| 718 | + | ||
| 719 | +// 只暴露 isActive = true 的数据 | ||
| 720 | +const exposeRecordList = computed(() => { | ||
| 721 | + return recordList.value.filter(item => item.isActive).map(item => { | ||
| 722 | + return { | ||
| 723 | + ...item, | ||
| 724 | + weight: getRealWeight(item) // 👈 自动计算真实重量 | ||
| 725 | + } | ||
| 726 | + }) | ||
| 727 | +}) | ||
| 728 | + | ||
| 729 | +const exposeSuperRecordMap = computed(() => { | ||
| 730 | + const map = {} | ||
| 731 | + for (const key in superRecordMap.value) { | ||
| 732 | + // 找到当前子动作的类型 | ||
| 733 | + const subExercise = props.actionDetail?.exercises?.find(s => s.id == key) | ||
| 734 | + const subType = subExercise?.exerciseType | ||
| 735 | + map[key] = superRecordMap.value[key] | ||
| 736 | + .filter(item => item.isActive) | ||
| 737 | + .map(item => { | ||
| 738 | + return { | ||
| 739 | + ...item, | ||
| 740 | + weight: getRealWeight(item, subType) // ✅ 传入子动作类型 | ||
| 741 | + } | ||
| 742 | + }) | ||
| 743 | + } | ||
| 744 | + return map | ||
| 745 | +}) | ||
| 746 | +// 总组数 | ||
| 747 | +const totalSetCount = computed(() => { | ||
| 748 | + // 1. 普通动作 type === 1 | ||
| 749 | + if (props.type === 1) { | ||
| 750 | + return recordList.value.length | ||
| 751 | + } | ||
| 752 | + // 2. 超级组 type === 2 | ||
| 753 | + if (props.type === 2) { | ||
| 754 | + const exercises = props.actionDetail?.exercises || [] | ||
| 755 | + if (exercises.length === 0) return 0 | ||
| 756 | + const firstId = exercises[0].id | ||
| 757 | + return superRecordMap.value[firstId]?.length || 0 | ||
| 758 | + } | ||
| 759 | + return 0 | ||
| 760 | +}) | ||
| 761 | + | ||
| 762 | +// 重量 | ||
| 763 | +// 1. 已勾选的重量总和(黄色框框)→ 现在 = 真实重量 × 次数 | ||
| 764 | +const checkedWeight = computed(() => { | ||
| 765 | + let sum = 0 | ||
| 766 | + // 普通动作 | ||
| 767 | + if (props.type === 1 && recordList.value) { | ||
| 768 | + recordList.value.forEach(item => { | ||
| 769 | + if (item.isActive) { | ||
| 770 | + const realWeight = getRealWeight(item) // 👈 真实重量(自重/加重/减重) | ||
| 771 | + const reps = Number(item.reps) || 0 // 👈 次数 | ||
| 772 | + sum += realWeight * reps // 👈 重量 × 次数 | ||
| 773 | + } | ||
| 774 | + }) | ||
| 775 | + } | ||
| 776 | + // 超级组 | ||
| 777 | + if (props.type === 2 && superRecordMap.value) { | ||
| 778 | + for (const actionId in superRecordMap.value) { | ||
| 779 | + // 拿到子动作类型 | ||
| 780 | + const subExercise = props.actionDetail?.exercises?.find(s => s.id == actionId) | ||
| 781 | + const subType = subExercise?.exerciseType | ||
| 782 | + | ||
| 783 | + superRecordMap.value[actionId].forEach(item => { | ||
| 784 | + if (item.isActive) { | ||
| 785 | + const realWeight = getRealWeight(item, subType) | ||
| 786 | + const reps = Number(item.reps) || 0 | ||
| 787 | + sum += realWeight * reps | ||
| 788 | + } | ||
| 789 | + }) | ||
| 790 | + } | ||
| 791 | + } | ||
| 792 | + return sum | ||
| 793 | +}) | ||
| 794 | + | ||
| 795 | +// 2. 所有组的重量总和(不管勾没勾)→ 现在 = 真实重量 × 次数 | ||
| 796 | +const totalWeight = computed(() => { | ||
| 797 | + let sum = 0 | ||
| 798 | + if (props.type === 1 && recordList.value) { | ||
| 799 | + recordList.value.forEach(item => { | ||
| 800 | + const realWeight = getRealWeight(item) | ||
| 801 | + const reps = Number(item.reps) || 0 | ||
| 802 | + sum += realWeight * reps | ||
| 803 | + }) | ||
| 804 | + } | ||
| 805 | + if (props.type === 2 && superRecordMap.value) { | ||
| 806 | + for (const actionId in superRecordMap.value) { | ||
| 807 | + const subExercise = props.actionDetail?.exercises?.find(s => s.id == actionId) | ||
| 808 | + const subType = subExercise?.exerciseType | ||
| 809 | + | ||
| 810 | + superRecordMap.value[actionId].forEach(item => { | ||
| 811 | + const realWeight = getRealWeight(item, subType) | ||
| 812 | + const reps = Number(item.reps) || 0 | ||
| 813 | + sum += realWeight * reps | ||
| 814 | + }) | ||
| 815 | + } | ||
| 816 | + } | ||
| 817 | + return sum | ||
| 818 | +}) | ||
| 819 | + | ||
| 820 | +// ============================================== | ||
| 821 | +// 统一监听:普通动作 + 超级组 的自重减重(type=5) | ||
| 822 | +// ============================================== | ||
| 823 | +watch( | ||
| 824 | + () => [recordList.value, superRecordMap.value], | ||
| 825 | + () => { | ||
| 826 | + const userW = userWeight.value || 0; | ||
| 827 | + | ||
| 828 | + // -------------------------- | ||
| 829 | + // 1. 监听【普通动作】 | ||
| 830 | + // -------------------------- | ||
| 831 | + if (props.type === 1 && props.actionDetail?.exerciseType === 5) { | ||
| 832 | + recordList.value.forEach((item) => { | ||
| 833 | + const inputW = Number(item.weight) || 0; | ||
| 834 | + if (inputW > userW) { | ||
| 835 | + item.weight = ""; | ||
| 836 | + uni.showToast({ | ||
| 837 | + title: "辅助重量不能超过体重", | ||
| 838 | + icon: "none", | ||
| 839 | + duration: 2000, | ||
| 840 | + }); | ||
| 841 | + } | ||
| 842 | + }); | ||
| 843 | + } | ||
| 844 | + | ||
| 845 | + // -------------------------- | ||
| 846 | + // 2. 监听【超级组】(包含多个子动作) | ||
| 847 | + // -------------------------- | ||
| 848 | + if (props.type === 2) { | ||
| 849 | + // 遍历超级组里的所有子动作 | ||
| 850 | + props.actionDetail?.exercises?.forEach((sub) => { | ||
| 851 | + // 只校验 自重减重 type=5 | ||
| 852 | + if (sub.exerciseType === 5) { | ||
| 853 | + const sets = superRecordMap.value[sub.id] || []; | ||
| 854 | + sets.forEach((item) => { | ||
| 855 | + const inputW = Number(item.weight) || 0; | ||
| 856 | + if (inputW > userW) { | ||
| 857 | + item.weight = ""; | ||
| 858 | + uni.showToast({ | ||
| 859 | + title: "辅助重量不能超过体重", | ||
| 860 | + icon: "none", | ||
| 861 | + duration: 2000, | ||
| 862 | + }); | ||
| 863 | + } | ||
| 864 | + }); | ||
| 865 | + } | ||
| 866 | + }); | ||
| 867 | + } | ||
| 868 | + }, | ||
| 869 | + { deep: true } | ||
| 870 | +); | ||
| 871 | + | ||
| 872 | +defineExpose({ | ||
| 873 | + recordList: exposeRecordList, // 过滤后 | ||
| 874 | + superRecordMap: exposeSuperRecordMap, // 过滤后 | ||
| 875 | + totalSetCount, //全部的组数 | ||
| 876 | + checkedWeight, // 已勾选重量 | ||
| 877 | + totalWeight // 全部重量 | ||
| 878 | +}) | ||
| 879 | + | ||
| 880 | +// 打开备注弹窗 | ||
| 881 | +// const openBeizhu = () => { | ||
| 882 | +// console.log('传递到备注组件的id', actionId.value) | ||
| 883 | +// showBeizhuRef.value.open(actionId.value) | ||
| 884 | +// } | ||
| 885 | + | ||
| 886 | +watch(() => props.actionDetail, () => { | ||
| 887 | + console.log('actionDetail.exercises:', props.actionDetail?.exercises) | ||
| 888 | +}, { immediate: true }) | ||
| 889 | + | ||
| 890 | +</script> | ||
| 891 | + | ||
| 892 | +<style lang="scss" scoped> | ||
| 893 | +$bg-black: #303030; | ||
| 894 | +$card-bg: #1c1c1e; | ||
| 895 | +$item-bg: #2c2c2e; | ||
| 896 | +$text-main: #ffffff; | ||
| 897 | +$text-gray: #8e8e93; | ||
| 898 | +$theme-yellow: #f8d714; | ||
| 899 | + | ||
| 900 | +.action-container { | ||
| 901 | + width: 100%; | ||
| 902 | + /* 确保容器足够高以接收点击事件 */ | ||
| 903 | + background-color: $bg-black; | ||
| 904 | + | ||
| 905 | + .action-list { | ||
| 906 | + width: 100%; | ||
| 907 | + | ||
| 908 | + .card { | ||
| 909 | + border-radius: 24rpx; | ||
| 910 | + margin-bottom: 20rpx; | ||
| 911 | + padding-bottom: 20rpx; | ||
| 912 | + | ||
| 913 | + .card-top { | ||
| 914 | + display: flex; | ||
| 915 | + align-items: center; | ||
| 916 | + padding: 20rpx 30rpx 0; | ||
| 917 | + box-sizing: border-box; | ||
| 918 | + | ||
| 919 | + .action-image { | ||
| 920 | + width: 110rpx; | ||
| 921 | + height: 110rpx; | ||
| 922 | + border-radius: 16rpx; | ||
| 923 | + background-color: #3a3a3c; | ||
| 924 | + } | ||
| 925 | + | ||
| 926 | + .action-info { | ||
| 927 | + flex: 1; | ||
| 928 | + margin-left: 24rpx; | ||
| 929 | + | ||
| 930 | + .name { | ||
| 931 | + font-size: 34rpx; | ||
| 932 | + font-weight: 600; | ||
| 933 | + color: $text-main; | ||
| 934 | + } | ||
| 935 | + | ||
| 936 | + .tags { | ||
| 937 | + font-size: 24rpx; | ||
| 938 | + color: $text-gray; | ||
| 939 | + margin-top: 6rpx; | ||
| 940 | + margin-left: 6rpx; | ||
| 941 | + } | ||
| 942 | + | ||
| 943 | + .dot-list { | ||
| 944 | + display: flex; | ||
| 945 | + gap: 10rpx; | ||
| 946 | + margin-top: 8rpx; | ||
| 947 | + | ||
| 948 | + .dot { | ||
| 949 | + width: 10rpx; | ||
| 950 | + height: 10rpx; | ||
| 951 | + border-radius: 50%; | ||
| 952 | + background-color: $text-main; | ||
| 953 | + } | ||
| 954 | + } | ||
| 955 | + } | ||
| 956 | + | ||
| 957 | + .more-circle { | ||
| 958 | + width: 60rpx; | ||
| 959 | + height: 60rpx; | ||
| 960 | + background: $item-bg; | ||
| 961 | + border-radius: 50%; | ||
| 962 | + display: flex; | ||
| 963 | + align-items: center; | ||
| 964 | + justify-content: center; | ||
| 965 | + border: 1rpx solid #a4a4a4; | ||
| 966 | + position: relative; | ||
| 967 | + | ||
| 968 | + .icon { | ||
| 969 | + transform: rotate(90deg); | ||
| 970 | + } | ||
| 971 | + | ||
| 972 | + .tips { | ||
| 973 | + position: absolute; | ||
| 974 | + color: #fff; | ||
| 975 | + background-color: #3e3e3e; | ||
| 976 | + z-index: 6; | ||
| 977 | + right: 10rpx; | ||
| 978 | + top: 20rpx; | ||
| 979 | + border-radius: 10rpx; | ||
| 980 | + | ||
| 981 | + .tip { | ||
| 982 | + width: 120rpx; | ||
| 983 | + height: 40rpx; | ||
| 984 | + padding: 20rpx 30rpx; | ||
| 985 | + text-align: center; | ||
| 986 | + font-size: 24rpx; | ||
| 987 | + line-height: 40rpx; | ||
| 988 | + } | ||
| 989 | + } | ||
| 990 | + } | ||
| 991 | + } | ||
| 992 | + | ||
| 993 | + .main { | ||
| 994 | + width: 100%; | ||
| 995 | + margin-top: 20rpx; | ||
| 996 | + | ||
| 997 | + .img-list { | ||
| 998 | + display: flex; | ||
| 999 | + align-items: center; | ||
| 1000 | + flex-wrap: wrap; | ||
| 1001 | + gap: 20rpx; | ||
| 1002 | + padding: 20rpx; | ||
| 1003 | + box-sizing: border-box; | ||
| 1004 | + | ||
| 1005 | + .img { | ||
| 1006 | + width: 120rpx; | ||
| 1007 | + height: 120rpx; | ||
| 1008 | + border-radius: 10rpx; | ||
| 1009 | + } | ||
| 1010 | + } | ||
| 1011 | + | ||
| 1012 | + // .userWeight-values { | ||
| 1013 | + // display: flex; | ||
| 1014 | + // align-items: center; | ||
| 1015 | + // gap: 8rpx; // 控制“体重文本”和“编辑图标”的间距 | ||
| 1016 | + // padding: 8rpx; | ||
| 1017 | + | ||
| 1018 | + // .weight-text { | ||
| 1019 | + // color: #fadc00; | ||
| 1020 | + // font-size: 20rpx; | ||
| 1021 | + // } | ||
| 1022 | + // } | ||
| 1023 | + | ||
| 1024 | + .userWeight { | ||
| 1025 | + padding: 16rpx 32rpx; | ||
| 1026 | + gap: 16rpx; | ||
| 1027 | + | ||
| 1028 | + .row { | ||
| 1029 | + display: flex; | ||
| 1030 | + align-items: center; | ||
| 1031 | + gap: 16rpx; | ||
| 1032 | + | ||
| 1033 | + .label { | ||
| 1034 | + font-size: 20rpx; | ||
| 1035 | + color: $text-main; | ||
| 1036 | + font-weight: 500; | ||
| 1037 | + } | ||
| 1038 | + | ||
| 1039 | + .userWeight-values { | ||
| 1040 | + display: flex; | ||
| 1041 | + align-items: center; | ||
| 1042 | + gap: 8rpx; // 控制“体重文本”和“编辑图标”的间距 | ||
| 1043 | + padding: 8rpx; | ||
| 1044 | + | ||
| 1045 | + .weight-text { | ||
| 1046 | + color: #fadc00; | ||
| 1047 | + font-size: 20rpx; | ||
| 1048 | + } | ||
| 1049 | + } | ||
| 1050 | + } | ||
| 1051 | + | ||
| 1052 | + .explain { | ||
| 1053 | + font-size: 20rpx; | ||
| 1054 | + color: #fff; | ||
| 1055 | + } | ||
| 1056 | + } | ||
| 1057 | + | ||
| 1058 | + .record-row { | ||
| 1059 | + display: flex; | ||
| 1060 | + align-items: flex-start; | ||
| 1061 | + justify-content: space-between; | ||
| 1062 | + padding: 10rpx 30rpx; | ||
| 1063 | + box-sizing: border-box; | ||
| 1064 | + | ||
| 1065 | + &.active { | ||
| 1066 | + background-color: #4c4d3b; | ||
| 1067 | + } | ||
| 1068 | + | ||
| 1069 | + .left { | ||
| 1070 | + display: flex; | ||
| 1071 | + flex-direction: column; | ||
| 1072 | + gap: 10rpx; | ||
| 1073 | + | ||
| 1074 | + .left-header { | ||
| 1075 | + display: flex; | ||
| 1076 | + align-items: center; | ||
| 1077 | + gap: 20rpx; | ||
| 1078 | + | ||
| 1079 | + .index-box { | ||
| 1080 | + width: 50rpx; | ||
| 1081 | + height: 70rpx; | ||
| 1082 | + background: #1f1f1f; | ||
| 1083 | + border-radius: 10rpx; | ||
| 1084 | + display: flex; | ||
| 1085 | + align-items: center; | ||
| 1086 | + justify-content: center; | ||
| 1087 | + font-size: 30rpx; | ||
| 1088 | + color: $text-main; | ||
| 1089 | + } | ||
| 1090 | + | ||
| 1091 | + .input-col, | ||
| 1092 | + .distance { | ||
| 1093 | + height: 70rpx; | ||
| 1094 | + background: #1f1f1f; | ||
| 1095 | + border-radius: 10rpx; | ||
| 1096 | + padding: 5rpx 10rpx; | ||
| 1097 | + display: flex; | ||
| 1098 | + flex-direction: column; | ||
| 1099 | + justify-content: center; | ||
| 1100 | + | ||
| 1101 | + .label { | ||
| 1102 | + font-size: 15rpx; | ||
| 1103 | + color: $text-gray; | ||
| 1104 | + margin-bottom: 2rpx; | ||
| 1105 | + } | ||
| 1106 | + | ||
| 1107 | + .values { | ||
| 1108 | + display: flex; | ||
| 1109 | + align-items: baseline; | ||
| 1110 | + | ||
| 1111 | + .num { | ||
| 1112 | + font-size: 32rpx; | ||
| 1113 | + color: $text-main; | ||
| 1114 | + } | ||
| 1115 | + | ||
| 1116 | + .unit { | ||
| 1117 | + font-size: 15rpx; | ||
| 1118 | + color: $text-gray; | ||
| 1119 | + margin: 0 5rpx; | ||
| 1120 | + } | ||
| 1121 | + } | ||
| 1122 | + } | ||
| 1123 | + | ||
| 1124 | + .distance { | ||
| 1125 | + min-width: 100rpx; | ||
| 1126 | + | ||
| 1127 | + :deep(.u-input) { | ||
| 1128 | + padding: 0 !important; | ||
| 1129 | + } | ||
| 1130 | + | ||
| 1131 | + .u-input__content__field { | ||
| 1132 | + font-size: 32rpx !important; | ||
| 1133 | + color: $text-main !important; | ||
| 1134 | + } | ||
| 1135 | + } | ||
| 1136 | + | ||
| 1137 | + .timer-selector { | ||
| 1138 | + width: 75rpx; | ||
| 1139 | + height: 75rpx; | ||
| 1140 | + display: flex; | ||
| 1141 | + flex-direction: column; | ||
| 1142 | + align-items: center; | ||
| 1143 | + justify-content: center; | ||
| 1144 | + border-radius: 50%; | ||
| 1145 | + border: 1rpx solid #a4a4a4; | ||
| 1146 | + | ||
| 1147 | + .text { | ||
| 1148 | + font-size: 20rpx; | ||
| 1149 | + color: $text-gray; | ||
| 1150 | + } | ||
| 1151 | + } | ||
| 1152 | + } | ||
| 1153 | + | ||
| 1154 | + .exercise-name-list { | ||
| 1155 | + display: flex; | ||
| 1156 | + flex-direction: column; | ||
| 1157 | + gap: 8rpx; | ||
| 1158 | + flex: 1; | ||
| 1159 | + } | ||
| 1160 | + | ||
| 1161 | + .exercise-name { | ||
| 1162 | + font-size: 26rpx; | ||
| 1163 | + color: $text-main; | ||
| 1164 | + line-height: 1.2; | ||
| 1165 | + } | ||
| 1166 | + | ||
| 1167 | + .goal-list { | ||
| 1168 | + width: 100%; | ||
| 1169 | + margin-top: 10rpx; | ||
| 1170 | + | ||
| 1171 | + .goal-item { | ||
| 1172 | + display: flex; | ||
| 1173 | + align-items: center; | ||
| 1174 | + gap: 20rpx; | ||
| 1175 | + | ||
| 1176 | + border-radius: 10rpx; | ||
| 1177 | + | ||
| 1178 | + .serial { | ||
| 1179 | + width: 50rpx; | ||
| 1180 | + height: 70rpx; | ||
| 1181 | + display: flex; | ||
| 1182 | + align-items: center; | ||
| 1183 | + justify-content: center; | ||
| 1184 | + font-size: 30rpx; | ||
| 1185 | + color: #929292; | ||
| 1186 | + font-weight: bold; | ||
| 1187 | + } | ||
| 1188 | + | ||
| 1189 | + .goal-input-box { | ||
| 1190 | + display: flex; | ||
| 1191 | + flex-direction: column; | ||
| 1192 | + justify-content: center; | ||
| 1193 | + background-color: #1f1f1f; | ||
| 1194 | + width: 100rpx; | ||
| 1195 | + height: 70rpx; | ||
| 1196 | + border-radius: 10rpx; | ||
| 1197 | + | ||
| 1198 | + .values { | ||
| 1199 | + display: flex; | ||
| 1200 | + align-items: flex-end; | ||
| 1201 | + padding-left: 10rpx; | ||
| 1202 | + box-sizing: border-box; | ||
| 1203 | + | ||
| 1204 | + :deep(.uni-input-input[type='number']) { | ||
| 1205 | + text-align: center; | ||
| 1206 | + } | ||
| 1207 | + | ||
| 1208 | + .num { | ||
| 1209 | + font-size: 32rpx; | ||
| 1210 | + color: $text-main; | ||
| 1211 | + } | ||
| 1212 | + | ||
| 1213 | + .unit { | ||
| 1214 | + font-size: 15rpx; | ||
| 1215 | + color: $text-gray; | ||
| 1216 | + margin: 0 5rpx; | ||
| 1217 | + } | ||
| 1218 | + | ||
| 1219 | + // 确保 up-input 内部文字样式一致 | ||
| 1220 | + } | ||
| 1221 | + } | ||
| 1222 | + | ||
| 1223 | + } | ||
| 1224 | + | ||
| 1225 | + // 新增的间歇运动样式 | ||
| 1226 | + .interval { | ||
| 1227 | + display: flex; | ||
| 1228 | + gap: 20rpx; | ||
| 1229 | + margin-top: 10rpx; | ||
| 1230 | + | ||
| 1231 | + .goal-input-box { | ||
| 1232 | + flex: none; | ||
| 1233 | + width: 120rpx; | ||
| 1234 | + | ||
| 1235 | + .label { | ||
| 1236 | + font-size: 15rpx; | ||
| 1237 | + color: $text-gray; | ||
| 1238 | + margin-bottom: 2rpx; | ||
| 1239 | + } | ||
| 1240 | + } | ||
| 1241 | + } | ||
| 1242 | + } | ||
| 1243 | + } | ||
| 1244 | + | ||
| 1245 | + .icons { | ||
| 1246 | + display: flex; | ||
| 1247 | + gap: 20rpx; | ||
| 1248 | + | ||
| 1249 | + .icon { | ||
| 1250 | + width: 70rpx; | ||
| 1251 | + height: 70rpx; | ||
| 1252 | + background-color: #1f1f1f; | ||
| 1253 | + border-radius: 12rpx; | ||
| 1254 | + display: flex; | ||
| 1255 | + align-items: center; | ||
| 1256 | + justify-content: center; | ||
| 1257 | + | ||
| 1258 | + &.active { | ||
| 1259 | + background-color: $theme-yellow; | ||
| 1260 | + } | ||
| 1261 | + } | ||
| 1262 | + | ||
| 1263 | + .del { | ||
| 1264 | + background-color: #58372d; | ||
| 1265 | + } | ||
| 1266 | + } | ||
| 1267 | + } | ||
| 1268 | + } | ||
| 1269 | + | ||
| 1270 | + // 新增超级组样式 | ||
| 1271 | + .super-group { | ||
| 1272 | + width: 100%; | ||
| 1273 | + } | ||
| 1274 | + | ||
| 1275 | + .super-record-row { | ||
| 1276 | + align-items: center; | ||
| 1277 | + background-color: red; | ||
| 1278 | + min-height: 100rpx; | ||
| 1279 | + } | ||
| 1280 | + | ||
| 1281 | + .super-left { | ||
| 1282 | + flex-direction: row !important; | ||
| 1283 | + align-items: center; | ||
| 1284 | + gap: 20rpx; | ||
| 1285 | + } | ||
| 1286 | + | ||
| 1287 | + | ||
| 1288 | + | ||
| 1289 | + | ||
| 1290 | + .set-group { | ||
| 1291 | + margin-bottom: 30rpx; | ||
| 1292 | + } | ||
| 1293 | + | ||
| 1294 | + .set-header { | ||
| 1295 | + display: flex; | ||
| 1296 | + align-items: center; | ||
| 1297 | + padding: 10rpx 30rpx; | ||
| 1298 | + gap: 20rpx; | ||
| 1299 | + | ||
| 1300 | + .icons { | ||
| 1301 | + display: flex; | ||
| 1302 | + gap: 20rpx; | ||
| 1303 | + | ||
| 1304 | + .icon { | ||
| 1305 | + width: 70rpx; | ||
| 1306 | + height: 70rpx; | ||
| 1307 | + background-color: #1f1f1f; | ||
| 1308 | + border-radius: 12rpx; | ||
| 1309 | + display: flex; | ||
| 1310 | + align-items: center; | ||
| 1311 | + justify-content: center; | ||
| 1312 | + } | ||
| 1313 | + | ||
| 1314 | + .del { | ||
| 1315 | + background-color: #58372d; | ||
| 1316 | + } | ||
| 1317 | + } | ||
| 1318 | + | ||
| 1319 | + .index-box { | ||
| 1320 | + width: 50rpx; | ||
| 1321 | + height: 70rpx; | ||
| 1322 | + background: #1f1f1f; | ||
| 1323 | + border-radius: 10rpx; | ||
| 1324 | + display: flex; | ||
| 1325 | + align-items: center; | ||
| 1326 | + justify-content: center; | ||
| 1327 | + font-size: 30rpx; | ||
| 1328 | + color: #fff; | ||
| 1329 | + } | ||
| 1330 | + | ||
| 1331 | + .set-action-names { | ||
| 1332 | + flex: 1; | ||
| 1333 | + display: flex; | ||
| 1334 | + flex-direction: column; | ||
| 1335 | + gap: 4rpx; | ||
| 1336 | + | ||
| 1337 | + text { | ||
| 1338 | + font-size: 26rpx; | ||
| 1339 | + color: #fff; | ||
| 1340 | + } | ||
| 1341 | + } | ||
| 1342 | + | ||
| 1343 | + .timer-selector { | ||
| 1344 | + width: 75rpx; | ||
| 1345 | + height: 75rpx; | ||
| 1346 | + display: flex; | ||
| 1347 | + flex-direction: column; | ||
| 1348 | + align-items: center; | ||
| 1349 | + justify-content: center; | ||
| 1350 | + border-radius: 50%; | ||
| 1351 | + border: 1rpx solid #a4a4a4; | ||
| 1352 | + | ||
| 1353 | + .text { | ||
| 1354 | + font-size: 20rpx; | ||
| 1355 | + color: #8e8e93; | ||
| 1356 | + } | ||
| 1357 | + } | ||
| 1358 | + | ||
| 1359 | + .icon { | ||
| 1360 | + width: 70rpx; | ||
| 1361 | + height: 70rpx; | ||
| 1362 | + background-color: #1f1f1f; | ||
| 1363 | + border-radius: 12rpx; | ||
| 1364 | + display: flex; | ||
| 1365 | + align-items: center; | ||
| 1366 | + justify-content: center; | ||
| 1367 | + | ||
| 1368 | + &.active { | ||
| 1369 | + background-color: $theme-yellow; | ||
| 1370 | + } | ||
| 1371 | + } | ||
| 1372 | + } | ||
| 1373 | + | ||
| 1374 | + .serial { | ||
| 1375 | + width: 50rpx; | ||
| 1376 | + height: 70rpx; | ||
| 1377 | + display: flex; | ||
| 1378 | + align-items: center; | ||
| 1379 | + justify-content: center; | ||
| 1380 | + font-size: 30rpx; | ||
| 1381 | + color: #929292; | ||
| 1382 | + font-weight: bold; | ||
| 1383 | + } | ||
| 1384 | + | ||
| 1385 | + .super-action-item { | ||
| 1386 | + margin-bottom: 30rpx; | ||
| 1387 | + } | ||
| 1388 | + | ||
| 1389 | + .super-action-title { | ||
| 1390 | + font-size: 28rpx; | ||
| 1391 | + color: #fff; | ||
| 1392 | + padding: 10rpx 30rpx; | ||
| 1393 | + font-weight: bold; | ||
| 1394 | + } | ||
| 1395 | + | ||
| 1396 | + .button-group { | ||
| 1397 | + display: flex; | ||
| 1398 | + justify-content: space-between; | ||
| 1399 | + padding: 30rpx; | ||
| 1400 | + box-sizing: border-box; | ||
| 1401 | + | ||
| 1402 | + .btn-item { | ||
| 1403 | + background-color: #1f1f1f; | ||
| 1404 | + font-size: 26rpx; | ||
| 1405 | + color: #fff; | ||
| 1406 | + padding: 15rpx 35rpx; | ||
| 1407 | + border-radius: 50rpx; | ||
| 1408 | + display: flex; | ||
| 1409 | + align-items: center; | ||
| 1410 | + gap: 10rpx; | ||
| 1411 | + position: relative; | ||
| 1412 | + | ||
| 1413 | + .difficulty-pop { | ||
| 1414 | + position: absolute; | ||
| 1415 | + bottom: 80rpx; | ||
| 1416 | + left: 50%; | ||
| 1417 | + transform: translateX(-50%); | ||
| 1418 | + background-color: #3e3e3e; | ||
| 1419 | + border-radius: 16rpx; | ||
| 1420 | + overflow: hidden; | ||
| 1421 | + z-index: 99; | ||
| 1422 | + box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.5); | ||
| 1423 | + | ||
| 1424 | + .difficulty-item { | ||
| 1425 | + width: 180rpx; | ||
| 1426 | + height: 80rpx; | ||
| 1427 | + padding: 0 25rpx; | ||
| 1428 | + box-sizing: border-box; | ||
| 1429 | + display: flex; | ||
| 1430 | + align-items: center; | ||
| 1431 | + justify-content: space-between; | ||
| 1432 | + font-size: 26rpx; | ||
| 1433 | + border-bottom: 1rpx solid #4e4e4e; | ||
| 1434 | + | ||
| 1435 | + &:last-child { | ||
| 1436 | + border-bottom: none; | ||
| 1437 | + } | ||
| 1438 | + | ||
| 1439 | + &:active { | ||
| 1440 | + background-color: #4e4e4e; | ||
| 1441 | + } | ||
| 1442 | + } | ||
| 1443 | + } | ||
| 1444 | + } | ||
| 1445 | + } | ||
| 1446 | + } | ||
| 1447 | + } | ||
| 1448 | + | ||
| 1449 | +} | ||
| 1450 | +</style> |
| 1 | +<template> | ||
| 2 | + <view class="popup-container" v-if="props.show" @click="closeAddMenu"> | ||
| 3 | + <!-- 搜索栏 --> | ||
| 4 | + <view class="add-btn-wrapper" @click.stop="addExercise"> | ||
| 5 | + <uni-icons type="plus" size="35" color="#333"></uni-icons> | ||
| 6 | + <view class="floating-menu" v-show="replaceAddshow" @click.stop> | ||
| 7 | + <view class="menu-item" @click="addnewmotion"> | ||
| 8 | + <uni-icons type="plus" size="24" color="#333"></uni-icons> | ||
| 9 | + <text class="menu-text">新增动作</text> | ||
| 10 | + </view> | ||
| 11 | + </view> | ||
| 12 | + </view> | ||
| 13 | + <view class="search-bar"> | ||
| 14 | + <!-- <view class="search-input-wrapper"> | ||
| 15 | + <uni-icons type="search" size="18" color="#999"></uni-icons> | ||
| 16 | + <input class="search-input" type="text" placeholder="搜索动作名称" @input="onSearch" /> | ||
| 17 | + </view> --> | ||
| 18 | + </view> | ||
| 19 | + <!-- 左右布局 --> | ||
| 20 | + <view class="layout-container"> | ||
| 21 | + <!-- 左侧分类导航 --> | ||
| 22 | + <scroll-view scroll-y class="left-nav"> | ||
| 23 | + <view class="nav-item" @click="handleCollectClick('collect')" :class="{ active: activeNav === 'collect' }"> | ||
| 24 | + 收藏 | ||
| 25 | + </view> | ||
| 26 | + <view v-for="nav in navItems" :key="nav.id" class="nav-item" :class="{ active: activeNav === nav.id }" | ||
| 27 | + @click="switchNav(nav.id)"> | ||
| 28 | + {{ nav.name }} | ||
| 29 | + </view> | ||
| 30 | + </scroll-view> | ||
| 31 | + | ||
| 32 | + <!-- 右侧动作列表 --> | ||
| 33 | + <scroll-view scroll-y class="right-content"> | ||
| 34 | + <!-- 部位筛选(可选) --> | ||
| 35 | + <view class="tip" v-if="motionPart.length > 0"> | ||
| 36 | + <view class="item" @click="handlePartClick('')" :class="{ active: activeMotionPart == '' }">全部</view> | ||
| 37 | + <view class="item" v-for="item in motionPart" :key="item.id" :class="{ active: activeMotionPart == item.id }" | ||
| 38 | + @click="handlePartClick(item.id)"> | ||
| 39 | + {{ item.name }} | ||
| 40 | + </view> | ||
| 41 | + </view> | ||
| 42 | + | ||
| 43 | + <view class="exercise-grid"> | ||
| 44 | + <view class="content" v-if="exercises.length > 0 || superGroupInfo.length > 0"> | ||
| 45 | + <!-- 普通动作列表 --> | ||
| 46 | + <view class="equipment-list"> | ||
| 47 | + <view class="equipment-item" v-for="item in exercises" :key="item.equipmentId"> | ||
| 48 | + <view class="equipment-name"> {{ item.equipmentName }} </view> | ||
| 49 | + <view class="action-list"> | ||
| 50 | + <view class="action-item" v-for="e in item.exercises" :key="e.id" | ||
| 51 | + :class="{ selected: selectedAction?.id === e.id }" @click="selectAction(e)"> | ||
| 52 | + <image :src="e.url3dAnimation" mode="aspectFill" lazy-load class="action-img"></image> | ||
| 53 | + <view class="action-name">{{ e.name }}</view> | ||
| 54 | + <view class="trainingReps" v-if="e.trainingReps">{{ e.trainingReps }}次</view> | ||
| 55 | + </view> | ||
| 56 | + </view> | ||
| 57 | + </view> | ||
| 58 | + | ||
| 59 | + <!-- 超级组列表(可选) --> | ||
| 60 | + <view class="supers" v-if="superGroupInfo.length > 0"> | ||
| 61 | + <view class="supers-name"> 超级组 </view> | ||
| 62 | + <view class="super" v-for="item in superGroupInfo" :key="item.id" | ||
| 63 | + :class="{ selected: selectedAction?.id === item.id }" @click="selectAction(item, 2)"> | ||
| 64 | + <image src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260507/超级组_1778117889451.png" | ||
| 65 | + mode="aspectFill" lazy-load class="super-img"></image> | ||
| 66 | + <view class="right"> | ||
| 67 | + <view class="super-name">{{ item.name }}</view> | ||
| 68 | + <view class="parts">{{ item.primaryMuscles.join(' ') }}</view> | ||
| 69 | + </view> | ||
| 70 | + </view> | ||
| 71 | + </view> | ||
| 72 | + </view> | ||
| 73 | + </view> | ||
| 74 | + | ||
| 75 | + <!-- 空状态 --> | ||
| 76 | + <view class="empty-state" v-else> | ||
| 77 | + <text class="empty-text">暂无动作数据</text> | ||
| 78 | + </view> | ||
| 79 | + </view> | ||
| 80 | + </scroll-view> | ||
| 81 | + </view> | ||
| 82 | + <!-- 底部按钮栏 --> | ||
| 83 | + <view class="bottom-btn-bar"> | ||
| 84 | + <view class="btn cancel-btn" @click="handleClose">取消</view> | ||
| 85 | + <view class="btn confirm-btn" :class="{ disabled: !selectedAction }" @click="handleConfirm"> | ||
| 86 | + 确认 | ||
| 87 | + </view> | ||
| 88 | + </view> | ||
| 89 | + </view> | ||
| 90 | +</template> | ||
| 91 | + | ||
| 92 | +<script setup> | ||
| 93 | +import { ref, watch, onMounted } from 'vue'; | ||
| 94 | +import ExercisesApi from '@/sheep/api/motion/exercises'; | ||
| 95 | +import SupersetsApi from '@/sheep/api/motion/supersets'; | ||
| 96 | +import { useActionStore } from '@/sheep/store/action'; | ||
| 97 | +import { useTrainingStore } from '@/sheep/store/trainingStore' | ||
| 98 | +import { useTemplateActionStore } from '@/sheep/store/templateAction' | ||
| 99 | + | ||
| 100 | +const templateStore = useTemplateActionStore() | ||
| 101 | + | ||
| 102 | +const trainingStore = useTrainingStore() | ||
| 103 | + | ||
| 104 | +const actionStore = useActionStore(); | ||
| 105 | + | ||
| 106 | +const emit = defineEmits(['close', 'confirm', 'replaceIdType']); | ||
| 107 | + | ||
| 108 | +const replaceAddshow = ref(false); | ||
| 109 | + | ||
| 110 | +// 数据 | ||
| 111 | +const activeNav = ref('collect'); // 默认选中收藏 | ||
| 112 | +const navItems = ref([]); | ||
| 113 | +const exercises = ref([]); | ||
| 114 | +const superGroupInfo = ref([]); | ||
| 115 | +const motionPart = ref([]); | ||
| 116 | +const activeMotionPart = ref(''); | ||
| 117 | +const selectedAction = ref(null); // 选中的动作/超级组 | ||
| 118 | +const selectedType = ref(1); // 1=动作 2=超级组 | ||
| 119 | +const allExercises = ref([]); | ||
| 120 | + | ||
| 121 | +const props = defineProps({ | ||
| 122 | + show: { | ||
| 123 | + type: Boolean, | ||
| 124 | + default: false | ||
| 125 | + }, | ||
| 126 | + unitIndex: Number, | ||
| 127 | +}) | ||
| 128 | + | ||
| 129 | +// 监听弹窗打开,加载数据 | ||
| 130 | +watch(() => props.show, (val) => { | ||
| 131 | + if (val) { | ||
| 132 | + selectedAction.value = null; | ||
| 133 | + // 加载收藏列表 | ||
| 134 | + loadCollectList(); | ||
| 135 | + loadSuperFavoriteList(); | ||
| 136 | + } | ||
| 137 | + console.log('动作替换组件 unitIndex:', props.unitIndex) | ||
| 138 | +}); | ||
| 139 | + | ||
| 140 | +// 关闭菜单 | ||
| 141 | +const closeAddMenu = () => { | ||
| 142 | + replaceAddshow.value = false; | ||
| 143 | +}; | ||
| 144 | + | ||
| 145 | +const addnewmotion = () => { | ||
| 146 | + uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-xinzeng' }); | ||
| 147 | +}; | ||
| 148 | + | ||
| 149 | +const addExercise = () => { | ||
| 150 | + replaceAddshow.value = !replaceAddshow.value; | ||
| 151 | +}; | ||
| 152 | + | ||
| 153 | + | ||
| 154 | +// 加载分类 | ||
| 155 | +const loadCategories = async () => { | ||
| 156 | + try { | ||
| 157 | + await actionStore.getloadCategories(); | ||
| 158 | + navItems.value = actionStore.showCategories; | ||
| 159 | + if (navItems.value.length > 0) { | ||
| 160 | + switchNav(navItems.value[0].id); | ||
| 161 | + } | ||
| 162 | + } catch (error) { | ||
| 163 | + console.error('获取分类失败:', error); | ||
| 164 | + } | ||
| 165 | +}; | ||
| 166 | + | ||
| 167 | +// 加载收藏动作列表 | ||
| 168 | +const loadCollectList = async () => { | ||
| 169 | + try { | ||
| 170 | + const res = await ExercisesApi.getFavoriteExercises(); | ||
| 171 | + exercises.value = res.data || []; | ||
| 172 | + allExercises.value = [...(res.data || [])]; | ||
| 173 | + } catch (err) { | ||
| 174 | + console.error('加载动作收藏失败', err); | ||
| 175 | + } | ||
| 176 | +}; | ||
| 177 | + | ||
| 178 | +// 加载收藏超级组列表 | ||
| 179 | +const loadSuperFavoriteList = async () => { | ||
| 180 | + try { | ||
| 181 | + const res = await SupersetsApi.getFavoriteSuperset(); | ||
| 182 | + superGroupInfo.value = res.data || []; | ||
| 183 | + } catch (err) { | ||
| 184 | + console.error('加载超级组收藏失败', err); | ||
| 185 | + } | ||
| 186 | +}; | ||
| 187 | + | ||
| 188 | +// 切换左侧分类 | ||
| 189 | +const switchNav = (id) => { | ||
| 190 | + if (activeNav.value === id) return; | ||
| 191 | + activeNav.value = id; | ||
| 192 | + activeMotionPart.value = ''; | ||
| 193 | + selectedAction.value = null; // 切换分类时重置选中 | ||
| 194 | + if (id === 'super') { | ||
| 195 | + exercises.value = []; | ||
| 196 | + motionPart.value = []; | ||
| 197 | + loadsupersetsinfo(); | ||
| 198 | + } else { | ||
| 199 | + superGroupInfo.value = []; | ||
| 200 | + loadExercises(id); | ||
| 201 | + } | ||
| 202 | +}; | ||
| 203 | + | ||
| 204 | +// 加载普通动作 | ||
| 205 | +const loadExercises = async (categoriesId) => { | ||
| 206 | + try { | ||
| 207 | + const partRes = await ExercisesApi.getMotionPart(categoriesId); | ||
| 208 | + motionPart.value = partRes.data || []; | ||
| 209 | + const exerciseRes = await ExercisesApi.getexercises({ categoriesId }); | ||
| 210 | + exercises.value = exerciseRes.data; | ||
| 211 | + allExercises.value = [...(exerciseRes.data || [])]; | ||
| 212 | + } catch (error) { | ||
| 213 | + console.error('加载动作失败:', error); | ||
| 214 | + } | ||
| 215 | +}; | ||
| 216 | + | ||
| 217 | +// 加载超级组 | ||
| 218 | +const loadsupersetsinfo = async () => { | ||
| 219 | + try { | ||
| 220 | + const response = await SupersetsApi.getsupersets(); | ||
| 221 | + superGroupInfo.value = response.data || []; | ||
| 222 | + } catch (error) { | ||
| 223 | + console.error('获取超级组失败:', error); | ||
| 224 | + } | ||
| 225 | +}; | ||
| 226 | + | ||
| 227 | +// 部位筛选 | ||
| 228 | +const handlePartClick = async (id) => { | ||
| 229 | + activeMotionPart.value = id; | ||
| 230 | + const exerciseRes = await ExercisesApi.getexercises({ | ||
| 231 | + categoriesId: activeNav.value, | ||
| 232 | + subCategoriesId: id | ||
| 233 | + }); | ||
| 234 | + exercises.value = exerciseRes.data || []; | ||
| 235 | + selectedAction.value = null; // 切换部位时重置选中 | ||
| 236 | +}; | ||
| 237 | + | ||
| 238 | +// 搜索动作 | ||
| 239 | +const onSearch = (e) => { | ||
| 240 | + const keyword = e.detail.value.trim().toLowerCase(); | ||
| 241 | + if (!keyword) { | ||
| 242 | + exercises.value = [...allExercises.value]; | ||
| 243 | + return; | ||
| 244 | + } | ||
| 245 | + exercises.value = allExercises.value.filter(item => | ||
| 246 | + item.name?.toLowerCase().includes(keyword) | ||
| 247 | + ); | ||
| 248 | +}; | ||
| 249 | + | ||
| 250 | +// 选择动作/超级组 | ||
| 251 | +const selectAction = (item, type = 1) => { | ||
| 252 | + // 点击同一个动作时,支持取消选中(可选) | ||
| 253 | + if (selectedAction.value?.id === item.id) { | ||
| 254 | + selectedAction.value = null; | ||
| 255 | + selectedType.value = 1; | ||
| 256 | + return; | ||
| 257 | + } | ||
| 258 | + selectedAction.value = item; | ||
| 259 | + selectedType.value = type; | ||
| 260 | + console.log("✅ 选中动作:", item.name, "类型:", type, 'id', item.id); | ||
| 261 | + console.log('打印输出selectedAction.value:', selectedAction.value) | ||
| 262 | +}; | ||
| 263 | + | ||
| 264 | +// 关闭组件 | ||
| 265 | +const handleClose = () => { | ||
| 266 | + setTimeout(() => { | ||
| 267 | + emit('close'); | ||
| 268 | + }, 800); | ||
| 269 | +} | ||
| 270 | +// 确认替换,把选中的动作抛给父组件 | ||
| 271 | +const handleConfirm = async () => { | ||
| 272 | + if (!selectedAction.value) return; | ||
| 273 | + | ||
| 274 | + console.log("【完整选中动作】", selectedAction.value); | ||
| 275 | + console.log("【当前store类型】", trainingStore.type); | ||
| 276 | + | ||
| 277 | + // ============================================== | ||
| 278 | + // 【1】如果是模板训练 → 先请求详情,再修改 units 数组 | ||
| 279 | + // ============================================== | ||
| 280 | + if (trainingStore.type === 3) { | ||
| 281 | + console.log("【模板模式】替换第", props.unitIndex, "个动作"); | ||
| 282 | + | ||
| 283 | + let newUnit = null; | ||
| 284 | + | ||
| 285 | + if (selectedType.value === 1) { | ||
| 286 | + // 替换成【单个动作】:先请求详情 | ||
| 287 | + const res = await ExercisesApi.getExerciseById(selectedAction.value.id); | ||
| 288 | + const detail = res.data; | ||
| 289 | + newUnit = { | ||
| 290 | + unitType: 1, | ||
| 291 | + unitId: detail.id, | ||
| 292 | + unitName: detail.name, | ||
| 293 | + exercises: [ | ||
| 294 | + { | ||
| 295 | + exerciseId: detail.id, | ||
| 296 | + exerciseName: detail.name, | ||
| 297 | + exerciseType: detail.exerciseType || 1, | ||
| 298 | + urlImage: detail.urlImage || detail.url3dAnimation, | ||
| 299 | + categoryDescription: detail.categoryDescription, | ||
| 300 | + equipmentDescription: detail.equipmentDescription, | ||
| 301 | + } | ||
| 302 | + ] | ||
| 303 | + }; | ||
| 304 | + } else if (selectedType.value === 2) { | ||
| 305 | + // 替换成【超级组】:先请求详情 | ||
| 306 | + const res = await SupersetsApi.getSupersetsInfo(selectedAction.value.id); | ||
| 307 | + const detail = res.data; | ||
| 308 | + newUnit = { | ||
| 309 | + unitType: 2, | ||
| 310 | + supersetId: detail.id, | ||
| 311 | + unitName: detail.name, | ||
| 312 | + exercises: detail.exercises.map(e => ({ | ||
| 313 | + exerciseId: e.id, | ||
| 314 | + exerciseName: e.name, | ||
| 315 | + exerciseType: e.exerciseType || 1, | ||
| 316 | + urlImage: e.urlImage || e.url3dAnimation, | ||
| 317 | + })) | ||
| 318 | + }; | ||
| 319 | + } | ||
| 320 | + | ||
| 321 | + // ✅ 直接替换 Pinia 数组 | ||
| 322 | + trainingStore.actionDetail.units[props.unitIndex] = newUnit; | ||
| 323 | + console.log("【替换后的新unit】", newUnit); | ||
| 324 | + | ||
| 325 | + // ✅ 关闭弹窗 | ||
| 326 | + setTimeout(() => { | ||
| 327 | + emit('close'); | ||
| 328 | + }, 800); | ||
| 329 | + | ||
| 330 | + return; | ||
| 331 | + } | ||
| 332 | + | ||
| 333 | + // ============================================== | ||
| 334 | + // 【2】原来逻辑:单个动作 / 超级组 | ||
| 335 | + // ============================================== | ||
| 336 | + if (selectedType.value === 1 || 2) { | ||
| 337 | + trainingStore.replaceAction( | ||
| 338 | + selectedAction.value.id, | ||
| 339 | + selectedType.value, | ||
| 340 | + ); | ||
| 341 | + await trainingStore.loadTrainingDetail(selectedAction.value.id, selectedType.value); | ||
| 342 | + } | ||
| 343 | + | ||
| 344 | + emit('confirm', { | ||
| 345 | + action: selectedAction.value, | ||
| 346 | + type: selectedType.value | ||
| 347 | + }); | ||
| 348 | + | ||
| 349 | + //每日模板专用 → 存入 Pinia | ||
| 350 | + templateStore.setReplaceAction( | ||
| 351 | + selectedAction.value.id, | ||
| 352 | + selectedType.value | ||
| 353 | + ) | ||
| 354 | + | ||
| 355 | + setTimeout(() => { | ||
| 356 | + emit('close'); | ||
| 357 | + }, 800); | ||
| 358 | +}; | ||
| 359 | + | ||
| 360 | +onMounted(() => { | ||
| 361 | + loadCategories(); | ||
| 362 | +}); | ||
| 363 | +</script> | ||
| 364 | + | ||
| 365 | +<style lang="scss" scoped> | ||
| 366 | +.popup-container { | ||
| 367 | + background-color: #fff; | ||
| 368 | + height: 100vh; | ||
| 369 | + display: flex; | ||
| 370 | + flex-direction: column; | ||
| 371 | + position: fixed; | ||
| 372 | + left: 0; | ||
| 373 | + right: 0; | ||
| 374 | + top: 0; | ||
| 375 | + bottom: 0; | ||
| 376 | + z-index: 999; | ||
| 377 | + border-radius: 0; | ||
| 378 | + | ||
| 379 | + // .search-bar { | ||
| 380 | + // display: flex; | ||
| 381 | + // align-items: center; | ||
| 382 | + // height: calc(var(--status-bar-height) + 88rpx); | ||
| 383 | + // padding: 0 20rpx 0 20rpx; | ||
| 384 | + // padding-top: var(--status-bar-height); | ||
| 385 | + // background-color: #fff; | ||
| 386 | + // position: sticky; | ||
| 387 | + // top: 0; | ||
| 388 | + // z-index: 10; | ||
| 389 | + | ||
| 390 | + // .add-btn { | ||
| 391 | + // width: 60rpx; | ||
| 392 | + // height: 60rpx; | ||
| 393 | + // display: flex; | ||
| 394 | + // justify-content: center; | ||
| 395 | + // align-items: center; | ||
| 396 | + // margin-right: auto; | ||
| 397 | + // } | ||
| 398 | + // } | ||
| 399 | + .search-bar { | ||
| 400 | + padding-top: var(--status-bar-height); | ||
| 401 | + height: 88rpx; | ||
| 402 | + background: #fff; | ||
| 403 | + } | ||
| 404 | + | ||
| 405 | + .add-btn-wrapper { | ||
| 406 | + position: fixed; | ||
| 407 | + left: 28rpx; | ||
| 408 | + top: calc(var(--status-bar-height) + 12rpx); | ||
| 409 | + z-index: 9999; | ||
| 410 | + width: 50rpx; | ||
| 411 | + height: 50rpx; | ||
| 412 | + } | ||
| 413 | + | ||
| 414 | + .floating-menu { | ||
| 415 | + position: absolute; | ||
| 416 | + top: 90rpx; | ||
| 417 | + left: 20rpx; | ||
| 418 | + background: #fff; | ||
| 419 | + border-radius: 12rpx; | ||
| 420 | + box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15); | ||
| 421 | + padding: 20rpx; | ||
| 422 | + z-index: 9999; | ||
| 423 | + width: 260rpx; | ||
| 424 | + } | ||
| 425 | + | ||
| 426 | + .menu-item { | ||
| 427 | + display: flex; | ||
| 428 | + align-items: center; | ||
| 429 | + gap: 16rpx; | ||
| 430 | + } | ||
| 431 | + | ||
| 432 | + .menu-text { | ||
| 433 | + box-sizing: border-box; | ||
| 434 | + display: flex; | ||
| 435 | + font-size: 28rpx; | ||
| 436 | + // margin-left: 200rpx; | ||
| 437 | + } | ||
| 438 | + | ||
| 439 | + .layout-container { | ||
| 440 | + flex: 1; | ||
| 441 | + display: flex; | ||
| 442 | + background-color: #f5f5f5; | ||
| 443 | + overflow: hidden; | ||
| 444 | + | ||
| 445 | + .left-nav { | ||
| 446 | + width: 150rpx; | ||
| 447 | + background-color: #fafafa; | ||
| 448 | + border-right: 1px solid #eee; | ||
| 449 | + | ||
| 450 | + .nav-item { | ||
| 451 | + padding: 28rpx 16rpx; | ||
| 452 | + text-align: left; | ||
| 453 | + font-size: 26rpx; | ||
| 454 | + color: #666; | ||
| 455 | + position: relative; | ||
| 456 | + | ||
| 457 | + &.active { | ||
| 458 | + color: #000; | ||
| 459 | + background-color: #fff; | ||
| 460 | + border-right: none; | ||
| 461 | + font-weight: 500; | ||
| 462 | + | ||
| 463 | + &::before { | ||
| 464 | + content: ''; | ||
| 465 | + position: absolute; | ||
| 466 | + left: 0; | ||
| 467 | + top: 50%; | ||
| 468 | + transform: translateY(-50%); | ||
| 469 | + width: 6rpx; | ||
| 470 | + height: 36rpx; | ||
| 471 | + background-color: #f8d714; | ||
| 472 | + } | ||
| 473 | + } | ||
| 474 | + } | ||
| 475 | + } | ||
| 476 | + | ||
| 477 | + .right-content { | ||
| 478 | + flex: 1; | ||
| 479 | + padding: 20rpx; | ||
| 480 | + | ||
| 481 | + .tip { | ||
| 482 | + display: flex; | ||
| 483 | + flex-wrap: wrap; | ||
| 484 | + gap: 15rpx; | ||
| 485 | + margin-bottom: 20rpx; | ||
| 486 | + | ||
| 487 | + .item { | ||
| 488 | + padding: 10rpx 18rpx; | ||
| 489 | + font-size: 24rpx; | ||
| 490 | + background-color: #fff; | ||
| 491 | + border-radius: 16rpx; | ||
| 492 | + color: #666; | ||
| 493 | + | ||
| 494 | + &.active { | ||
| 495 | + background-color: #f8d714; | ||
| 496 | + color: #000; | ||
| 497 | + } | ||
| 498 | + } | ||
| 499 | + } | ||
| 500 | + | ||
| 501 | + .exercise-grid { | ||
| 502 | + .equipment-item { | ||
| 503 | + margin-bottom: 30rpx; | ||
| 504 | + | ||
| 505 | + .equipment-name { | ||
| 506 | + font-size: 28rpx; | ||
| 507 | + color: #333; | ||
| 508 | + margin-bottom: 16rpx; | ||
| 509 | + } | ||
| 510 | + | ||
| 511 | + .action-list { | ||
| 512 | + display: grid; | ||
| 513 | + grid-template-columns: repeat(2, 1fr); | ||
| 514 | + gap: 15rpx; | ||
| 515 | + | ||
| 516 | + .action-item { | ||
| 517 | + display: flex; | ||
| 518 | + flex-direction: column; | ||
| 519 | + align-items: center; | ||
| 520 | + gap: 8rpx; | ||
| 521 | + background: #fff; | ||
| 522 | + border-radius: 16rpx; | ||
| 523 | + padding: 15rpx; | ||
| 524 | + position: relative; | ||
| 525 | + | ||
| 526 | + &.selected { | ||
| 527 | + border: 2rpx solid #f8d714; | ||
| 528 | + } | ||
| 529 | + | ||
| 530 | + .action-img { | ||
| 531 | + width: 100%; | ||
| 532 | + height: 220rpx; | ||
| 533 | + border-radius: 12rpx; | ||
| 534 | + } | ||
| 535 | + | ||
| 536 | + .action-name { | ||
| 537 | + font-size: 24rpx; | ||
| 538 | + color: #333; | ||
| 539 | + } | ||
| 540 | + | ||
| 541 | + .trainingReps { | ||
| 542 | + position: absolute; | ||
| 543 | + top: 15rpx; | ||
| 544 | + right: 15rpx; | ||
| 545 | + font-size: 22rpx; | ||
| 546 | + color: #999; | ||
| 547 | + } | ||
| 548 | + } | ||
| 549 | + } | ||
| 550 | + } | ||
| 551 | + | ||
| 552 | + .supers-name { | ||
| 553 | + font-size: 28rpx; | ||
| 554 | + color: #333; | ||
| 555 | + margin: 20rpx 0 16rpx; | ||
| 556 | + } | ||
| 557 | + | ||
| 558 | + .super { | ||
| 559 | + display: flex; | ||
| 560 | + align-items: center; | ||
| 561 | + background: #fff; | ||
| 562 | + padding: 20rpx; | ||
| 563 | + border-radius: 16rpx; | ||
| 564 | + gap: 20rpx; | ||
| 565 | + margin-bottom: 15rpx; | ||
| 566 | + | ||
| 567 | + &.selected { | ||
| 568 | + border: 2rpx solid #f8d714; | ||
| 569 | + } | ||
| 570 | + | ||
| 571 | + .super-img { | ||
| 572 | + width: 100rpx; | ||
| 573 | + height: 100rpx; | ||
| 574 | + border-radius: 12rpx; | ||
| 575 | + } | ||
| 576 | + | ||
| 577 | + .right { | ||
| 578 | + flex: 1; | ||
| 579 | + | ||
| 580 | + .super-name { | ||
| 581 | + font-size: 26rpx; | ||
| 582 | + color: #333; | ||
| 583 | + margin-bottom: 8rpx; | ||
| 584 | + } | ||
| 585 | + | ||
| 586 | + .parts { | ||
| 587 | + font-size: 22rpx; | ||
| 588 | + color: #999; | ||
| 589 | + } | ||
| 590 | + } | ||
| 591 | + } | ||
| 592 | + | ||
| 593 | + .empty-state { | ||
| 594 | + display: flex; | ||
| 595 | + justify-content: center; | ||
| 596 | + align-items: center; | ||
| 597 | + padding-top: 100rpx; | ||
| 598 | + | ||
| 599 | + .empty-text { | ||
| 600 | + font-size: 28rpx; | ||
| 601 | + color: #999; | ||
| 602 | + } | ||
| 603 | + } | ||
| 604 | + } | ||
| 605 | + } | ||
| 606 | + } | ||
| 607 | + | ||
| 608 | + .bottom-btn-bar { | ||
| 609 | + display: flex; | ||
| 610 | + gap: 20rpx; | ||
| 611 | + padding: 20rpx 30rpx 30rpx; | ||
| 612 | + background-color: #fff; | ||
| 613 | + | ||
| 614 | + .btn { | ||
| 615 | + flex: 1; | ||
| 616 | + height: 80rpx; | ||
| 617 | + border-radius: 40rpx; | ||
| 618 | + display: flex; | ||
| 619 | + justify-content: center; | ||
| 620 | + align-items: center; | ||
| 621 | + font-size: 30rpx; | ||
| 622 | + font-weight: bold; | ||
| 623 | + } | ||
| 624 | + | ||
| 625 | + .cancel-btn { | ||
| 626 | + background-color: #f5f5f5; | ||
| 627 | + color: #333; | ||
| 628 | + } | ||
| 629 | + | ||
| 630 | + .confirm-btn { | ||
| 631 | + background-color: #f8d714; | ||
| 632 | + color: #000; | ||
| 633 | + | ||
| 634 | + &.disabled { | ||
| 635 | + background-color: #eeeeee; | ||
| 636 | + color: #bbbbbb; | ||
| 637 | + } | ||
| 638 | + } | ||
| 639 | + } | ||
| 640 | +} | ||
| 641 | +</style> |
| 1 | +<template> | ||
| 2 | + <!-- 超级组 子动作输入框 独立组件 --> | ||
| 3 | + <view class="record-row super-record-row"> | ||
| 4 | + <view class="left super-left"> | ||
| 5 | + <view class="left-header super-left-header"> | ||
| 6 | + | ||
| 7 | + <view class="goal-list"> | ||
| 8 | + <view class="goal-item"> | ||
| 9 | + <view class="serial"> | ||
| 10 | + {{ subIndex + 1 }}{{ String.fromCharCode(65 + subIndex) }}</view> | ||
| 11 | + <!-- ========================================== --> | ||
| 12 | + <!-- 0:独立 重量+次数 --> | ||
| 13 | + <!-- ========================================== --> | ||
| 14 | + <template v-if="subExerciseType === 0"> | ||
| 15 | + <view class="goal-input-box"> | ||
| 16 | + <view class="values"> | ||
| 17 | + <up-input v-model="data.weight" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 18 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }"></up-input> | ||
| 19 | + <text class="unit">kg</text> | ||
| 20 | + </view> | ||
| 21 | + </view> | ||
| 22 | + <view class="goal-input-box"> | ||
| 23 | + <view class="values"> | ||
| 24 | + <up-input v-model="data.reps" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 25 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }"></up-input> | ||
| 26 | + <text class="unit">次</text> | ||
| 27 | + </view> | ||
| 28 | + </view> | ||
| 29 | + </template> | ||
| 30 | + | ||
| 31 | + <!-- ========================================== --> | ||
| 32 | + <!-- 2:独立 次数 --> | ||
| 33 | + <!-- ========================================== --> | ||
| 34 | + <template v-if="subExerciseType === 2"> | ||
| 35 | + <view class="goal-input-box"> | ||
| 36 | + <view class="values"> | ||
| 37 | + <up-input v-model="data.reps" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 38 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }"></up-input> | ||
| 39 | + <text class="unit">次</text> | ||
| 40 | + </view> | ||
| 41 | + </view> | ||
| 42 | + </template> | ||
| 43 | + | ||
| 44 | + <!-- ========================================== --> | ||
| 45 | + <!-- 1:独立 时长 + 距离 【全部写在自己内部!】 --> | ||
| 46 | + <!-- ========================================== --> | ||
| 47 | + <template v-if="subExerciseType === 1"> | ||
| 48 | + <!-- 时长:属于类型1 --> | ||
| 49 | + <view class="input-col" @click.stop="onOpenTimePicker"> | ||
| 50 | + <view class="label">时长</view> | ||
| 51 | + <view class="values"> | ||
| 52 | + <text class="num">{{ data.h }}</text> | ||
| 53 | + <text class="unit">时</text> | ||
| 54 | + <text class="num">{{ data.m }}</text> | ||
| 55 | + <text class="unit">分</text> | ||
| 56 | + <text class="num">{{ data.s }}</text> | ||
| 57 | + <text class="unit">秒</text> | ||
| 58 | + </view> | ||
| 59 | + </view> | ||
| 60 | + | ||
| 61 | + <!-- 距离:属于类型1 --> | ||
| 62 | + <view class="distance"> | ||
| 63 | + <view class="label">距离</view> | ||
| 64 | + <view class="values"> | ||
| 65 | + <up-input v-model="data.distance" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 66 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }"></up-input> | ||
| 67 | + <text class="unit">km</text> | ||
| 68 | + </view> | ||
| 69 | + </view> | ||
| 70 | + </template> | ||
| 71 | + | ||
| 72 | + <!-- ========================================== --> | ||
| 73 | + <!-- 3:独立 时长 【全部写在自己内部!】 --> | ||
| 74 | + <!-- ========================================== --> | ||
| 75 | + <template v-if="subExerciseType === 3"> | ||
| 76 | + <!-- 时长:属于类型3 --> | ||
| 77 | + <view class="input-col" @click.stop="onOpenTimePicker"> | ||
| 78 | + <view class="label">时长</view> | ||
| 79 | + <view class="values"> | ||
| 80 | + <text class="num">{{ data.h }}</text> | ||
| 81 | + <text class="unit">时</text> | ||
| 82 | + <text class="num">{{ data.m }}</text> | ||
| 83 | + <text class="unit">分</text> | ||
| 84 | + <text class="num">{{ data.s }}</text> | ||
| 85 | + <text class="unit">秒</text> | ||
| 86 | + </view> | ||
| 87 | + </view> | ||
| 88 | + </template> | ||
| 89 | + | ||
| 90 | + <!-- ========================================== --> | ||
| 91 | + <!-- 4:独立 重量+次数 --> | ||
| 92 | + <!-- ========================================== --> | ||
| 93 | + <template v-if="subExerciseType === 4"> | ||
| 94 | + <view class="goal-input-box"> | ||
| 95 | + <view class="values"> | ||
| 96 | + <up-input v-model="data.weight" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 97 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }"></up-input> | ||
| 98 | + <text class="unit">kg</text> | ||
| 99 | + </view> | ||
| 100 | + </view> | ||
| 101 | + <view class="goal-input-box"> | ||
| 102 | + <view class="values"> | ||
| 103 | + <up-input v-model="data.reps" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 104 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }"></up-input> | ||
| 105 | + <text class="unit">次</text> | ||
| 106 | + </view> | ||
| 107 | + </view> | ||
| 108 | + </template> | ||
| 109 | + | ||
| 110 | + <!-- ========================================== --> | ||
| 111 | + <!-- 5:独立 重量+次数 --> | ||
| 112 | + <!-- ========================================== --> | ||
| 113 | + <template v-if="subExerciseType === 5"> | ||
| 114 | + <view class="goal-input-box"> | ||
| 115 | + <view class="values"> | ||
| 116 | + <up-input v-model="data.weight" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 117 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }"></up-input> | ||
| 118 | + <text class="unit">kg</text> | ||
| 119 | + </view> | ||
| 120 | + </view> | ||
| 121 | + <view class="goal-input-box"> | ||
| 122 | + <view class="values"> | ||
| 123 | + <up-input v-model="data.reps" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 124 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }"></up-input> | ||
| 125 | + <text class="unit">次</text> | ||
| 126 | + </view> | ||
| 127 | + </view> | ||
| 128 | + </template> | ||
| 129 | + | ||
| 130 | + <!-- ========================================== --> | ||
| 131 | + <!-- 6:独立 间歇 --> | ||
| 132 | + <!-- ========================================== --> | ||
| 133 | + <template v-if="subExerciseType === 6"> | ||
| 134 | + <view class="goal-item interval"> | ||
| 135 | + <view class="goal-input-box reps"> | ||
| 136 | + <view class="label">循环</view> | ||
| 137 | + <view class="values"> | ||
| 138 | + <up-input v-model="data.reps" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 139 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }"></up-input> | ||
| 140 | + <text class="unit">组</text> | ||
| 141 | + </view> | ||
| 142 | + </view> | ||
| 143 | + <view class="goal-input-box time"> | ||
| 144 | + <view class="label">单次</view> | ||
| 145 | + <view class="values"> | ||
| 146 | + <up-input v-model="data.duration" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 147 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }"></up-input> | ||
| 148 | + <text class="unit">秒</text> | ||
| 149 | + </view> | ||
| 150 | + </view> | ||
| 151 | + <view class="goal-input-box restTime"> | ||
| 152 | + <view class="label">间歇</view> | ||
| 153 | + <view class="values"> | ||
| 154 | + <up-input v-model="data.restTime" border="none" type="digit" color="#ffffff" :maxlength="3" | ||
| 155 | + :customStyle="{ height: '40rpx', width: '60rpx', textAlign: 'center' }"></up-input> | ||
| 156 | + <text class="unit">秒</text> | ||
| 157 | + </view> | ||
| 158 | + </view> | ||
| 159 | + </view> | ||
| 160 | + </template> | ||
| 161 | + | ||
| 162 | + </view> | ||
| 163 | + </view> | ||
| 164 | + | ||
| 165 | + </view> | ||
| 166 | + | ||
| 167 | + </view> | ||
| 168 | + </view> | ||
| 169 | +</template> | ||
| 170 | + | ||
| 171 | +<script setup> | ||
| 172 | +import { computed, onMounted } from 'vue'; | ||
| 173 | +const props = defineProps({ | ||
| 174 | + subExerciseType: { type: Number, required: true }, | ||
| 175 | + setIndex: { type: Number, required: true }, | ||
| 176 | + subIndex: { type: Number, required: true }, | ||
| 177 | + data: { type: Object, required: true }, | ||
| 178 | + userWeight: { | ||
| 179 | + type: Number, | ||
| 180 | + default: 70 | ||
| 181 | + } | ||
| 182 | +}) | ||
| 183 | + | ||
| 184 | +const emit = defineEmits(['open-time-picker']) | ||
| 185 | + | ||
| 186 | +const onOpenTimePicker = () => { | ||
| 187 | + emit('open-time-picker') | ||
| 188 | +} | ||
| 189 | + | ||
| 190 | +onMounted(() => { | ||
| 191 | + console.log('super-set-input 已挂载', { | ||
| 192 | + subExerciseType: props.subExerciseType, | ||
| 193 | + setIndex: props.setIndex, | ||
| 194 | + subIndex: props.subIndex, | ||
| 195 | + data: props.data | ||
| 196 | + }); | ||
| 197 | +}); | ||
| 198 | + | ||
| 199 | +</script> | ||
| 200 | + | ||
| 201 | + | ||
| 202 | + | ||
| 203 | +<style lang="scss" scoped> | ||
| 204 | +$bg-black: #303030; | ||
| 205 | +$card-bg: #1c1c1e; | ||
| 206 | +$item-bg: #2c2c2e; | ||
| 207 | +$text-main: #ffffff; | ||
| 208 | +$text-gray: #8e8e93; | ||
| 209 | +$theme-yellow: #f8d714; | ||
| 210 | + | ||
| 211 | +.record-row { | ||
| 212 | + display: flex; | ||
| 213 | + align-items: flex-start; | ||
| 214 | + justify-content: space-between; | ||
| 215 | + padding: 10rpx 30rpx; | ||
| 216 | + box-sizing: border-box; | ||
| 217 | + // background: #ecb4b4; | ||
| 218 | + | ||
| 219 | + &.active { | ||
| 220 | + background-color: #4c4d3b; | ||
| 221 | + } | ||
| 222 | + | ||
| 223 | + .left { | ||
| 224 | + display: flex; | ||
| 225 | + flex-direction: column; | ||
| 226 | + gap: 10rpx; | ||
| 227 | + | ||
| 228 | + .left-header { | ||
| 229 | + display: flex; | ||
| 230 | + align-items: center; | ||
| 231 | + gap: 20rpx; | ||
| 232 | + | ||
| 233 | + .serial { | ||
| 234 | + display: flex !important; | ||
| 235 | + width: 50rpx; | ||
| 236 | + height: 70rpx; | ||
| 237 | + align-items: center; | ||
| 238 | + justify-content: center; | ||
| 239 | + font-size: 30rpx; | ||
| 240 | + color: #929292; | ||
| 241 | + font-weight: bold; | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + .index-box { | ||
| 245 | + width: 50rpx; | ||
| 246 | + height: 70rpx; | ||
| 247 | + background: #1f1f1f; | ||
| 248 | + border-radius: 10rpx; | ||
| 249 | + display: flex; | ||
| 250 | + align-items: center; | ||
| 251 | + justify-content: center; | ||
| 252 | + font-size: 30rpx; | ||
| 253 | + color: $text-main; | ||
| 254 | + } | ||
| 255 | + | ||
| 256 | + .input-col, | ||
| 257 | + .distance { | ||
| 258 | + height: 70rpx; | ||
| 259 | + background: #1f1f1f; | ||
| 260 | + border-radius: 10rpx; | ||
| 261 | + padding: 5rpx 10rpx; | ||
| 262 | + display: flex; | ||
| 263 | + flex-direction: column; | ||
| 264 | + justify-content: center; | ||
| 265 | + | ||
| 266 | + .label { | ||
| 267 | + font-size: 15rpx; | ||
| 268 | + color: $text-gray; | ||
| 269 | + margin-bottom: 2rpx; | ||
| 270 | + } | ||
| 271 | + | ||
| 272 | + .values { | ||
| 273 | + display: flex; | ||
| 274 | + align-items: baseline; | ||
| 275 | + | ||
| 276 | + .num { | ||
| 277 | + font-size: 32rpx; | ||
| 278 | + color: $text-main; | ||
| 279 | + } | ||
| 280 | + | ||
| 281 | + .unit { | ||
| 282 | + font-size: 15rpx; | ||
| 283 | + color: $text-gray; | ||
| 284 | + margin: 0 5rpx; | ||
| 285 | + } | ||
| 286 | + } | ||
| 287 | + } | ||
| 288 | + | ||
| 289 | + .distance { | ||
| 290 | + min-width: 100rpx; | ||
| 291 | + | ||
| 292 | + :deep(.u-input) { | ||
| 293 | + padding: 0 !important; | ||
| 294 | + } | ||
| 295 | + | ||
| 296 | + .u-input__content__field { | ||
| 297 | + font-size: 32rpx !important; | ||
| 298 | + color: $text-main !important; | ||
| 299 | + } | ||
| 300 | + } | ||
| 301 | + | ||
| 302 | + .timer-selector { | ||
| 303 | + width: 75rpx; | ||
| 304 | + height: 75rpx; | ||
| 305 | + display: flex; | ||
| 306 | + flex-direction: column; | ||
| 307 | + align-items: center; | ||
| 308 | + justify-content: center; | ||
| 309 | + border-radius: 50%; | ||
| 310 | + border: 1rpx solid #a4a4a4; | ||
| 311 | + | ||
| 312 | + .text { | ||
| 313 | + font-size: 20rpx; | ||
| 314 | + color: $text-gray; | ||
| 315 | + } | ||
| 316 | + } | ||
| 317 | + } | ||
| 318 | + | ||
| 319 | + .exercise-name-list { | ||
| 320 | + display: flex; | ||
| 321 | + flex-direction: column; | ||
| 322 | + gap: 8rpx; | ||
| 323 | + flex: 1; | ||
| 324 | + } | ||
| 325 | + | ||
| 326 | + .exercise-name { | ||
| 327 | + font-size: 26rpx; | ||
| 328 | + color: $text-main; | ||
| 329 | + line-height: 1.2; | ||
| 330 | + } | ||
| 331 | + | ||
| 332 | + .goal-list { | ||
| 333 | + width: 100%; | ||
| 334 | + margin-top: 10rpx; | ||
| 335 | + | ||
| 336 | + .goal-item { | ||
| 337 | + display: flex; | ||
| 338 | + align-items: center; | ||
| 339 | + gap: 20rpx; | ||
| 340 | + | ||
| 341 | + border-radius: 10rpx; | ||
| 342 | + | ||
| 343 | + .serial { | ||
| 344 | + width: 50rpx; | ||
| 345 | + height: 70rpx; | ||
| 346 | + display: flex; | ||
| 347 | + align-items: center; | ||
| 348 | + justify-content: center; | ||
| 349 | + font-size: 30rpx; | ||
| 350 | + color: #929292; | ||
| 351 | + font-weight: bold; | ||
| 352 | + } | ||
| 353 | + | ||
| 354 | + .goal-input-box { | ||
| 355 | + display: flex; | ||
| 356 | + flex-direction: column; | ||
| 357 | + justify-content: center; | ||
| 358 | + background-color: #1f1f1f; | ||
| 359 | + width: 100rpx; | ||
| 360 | + height: 70rpx; | ||
| 361 | + border-radius: 10rpx; | ||
| 362 | + | ||
| 363 | + .values { | ||
| 364 | + display: flex; | ||
| 365 | + align-items: flex-end; | ||
| 366 | + padding-left: 10rpx; | ||
| 367 | + box-sizing: border-box; | ||
| 368 | + | ||
| 369 | + :deep(.uni-input-input[type='number']) { | ||
| 370 | + text-align: center; | ||
| 371 | + } | ||
| 372 | + | ||
| 373 | + .num { | ||
| 374 | + font-size: 32rpx; | ||
| 375 | + color: $text-main; | ||
| 376 | + } | ||
| 377 | + | ||
| 378 | + .unit { | ||
| 379 | + font-size: 15rpx; | ||
| 380 | + color: $text-gray; | ||
| 381 | + margin: 0 5rpx; | ||
| 382 | + } | ||
| 383 | + | ||
| 384 | + } | ||
| 385 | + } | ||
| 386 | + | ||
| 387 | + } | ||
| 388 | + | ||
| 389 | + .interval { | ||
| 390 | + display: flex; | ||
| 391 | + gap: 20rpx; | ||
| 392 | + margin-top: 10rpx; | ||
| 393 | + | ||
| 394 | + .goal-input-box { | ||
| 395 | + flex: none; | ||
| 396 | + width: 120rpx; | ||
| 397 | + | ||
| 398 | + .label { | ||
| 399 | + font-size: 15rpx; | ||
| 400 | + color: $text-gray; | ||
| 401 | + margin-bottom: 2rpx; | ||
| 402 | + } | ||
| 403 | + } | ||
| 404 | + } | ||
| 405 | + } | ||
| 406 | + } | ||
| 407 | + | ||
| 408 | + .icons { | ||
| 409 | + display: flex; | ||
| 410 | + gap: 20rpx; | ||
| 411 | + | ||
| 412 | + .icon { | ||
| 413 | + width: 70rpx; | ||
| 414 | + height: 70rpx; | ||
| 415 | + background-color: #1f1f1f; | ||
| 416 | + border-radius: 12rpx; | ||
| 417 | + display: flex; | ||
| 418 | + align-items: center; | ||
| 419 | + justify-content: center; | ||
| 420 | + | ||
| 421 | + &.active { | ||
| 422 | + background-color: $theme-yellow; | ||
| 423 | + } | ||
| 424 | + } | ||
| 425 | + | ||
| 426 | + .del { | ||
| 427 | + background-color: #58372d; | ||
| 428 | + } | ||
| 429 | + } | ||
| 430 | +} | ||
| 431 | +</style> |
-
Please register or login to post a comment