Authored by qxm

训计提交

Too many changes to show.

To preserve performance only 10 of 10+ files are displayed.

  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({ 11 +// 1. 定义 Props 接收父组件数据
  12 +const props = defineProps({
18 // 传入的分类数据 (横坐标) 13 // 传入的分类数据 (横坐标)
  14 + chartType: { // 新增
  15 + type: String,
  16 + default: 'line'
  17 + },
19 categories: { 18 categories: {
20 type: Array, 19 type: Array,
21 default: () => [], 20 default: () => [],
@@ -30,12 +29,13 @@ @@ -30,12 +29,13 @@
30 type: Boolean, 29 type: Boolean,
31 default: false, 30 default: false,
32 }, 31 },
33 - }); 32 +});
34 33
35 - const chartData = ref({}); 34 +const chartData = ref({});
36 35
37 - // 2. 图表配置项  
38 - const opts = reactive({ 36 +// 2. 图表配置项
  37 +const opts = reactive({
  38 + dataLabel: false,
39 color: [ 39 color: [
40 '#1890FF', 40 '#1890FF',
41 '#91CB74', 41 '#91CB74',
@@ -49,12 +49,19 @@ @@ -49,12 +49,19 @@
49 ], 49 ],
50 padding: [15, 10, 0, 15], 50 padding: [15, 10, 0, 15],
51 enableScroll: false, 51 enableScroll: false,
52 - legend: {}, 52 + legend: {
  53 + show: true,
  54 + position: 'top'
  55 + },
53 xAxis: { 56 xAxis: {
54 disableGrid: true, 57 disableGrid: true,
  58 + // itemCount: 7, // 一共显示7个标签:1、6、11、16、21、26、31
  59 + labelCount: 7,
  60 + // splitNumber: 6,
55 }, 61 },
56 yAxis: { 62 yAxis: {
57 gridType: 'dash', 63 gridType: 'dash',
  64 + axisLabel: { show: false },
58 dashLength: 2, 65 dashLength: 2,
59 }, 66 },
60 extra: { 67 extra: {
@@ -63,32 +70,63 @@ @@ -63,32 +70,63 @@
63 width: 2, 70 width: 2,
64 activeType: 'hollow', 71 activeType: 'hollow',
65 }, 72 },
  73 + // 新增下面这段
  74 + column: {
  75 + width: 30, // 柱子宽度
  76 + radius: [4, 4, 0, 0] // 柱子圆角
66 }, 77 },
67 - }); 78 + tooltip: {
  79 + legendShow: true,
  80 +
  81 + }
  82 + },
  83 +});
  84 +
  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 +
68 95
69 - // 3. 核心逻辑:格式化数据  
70 - const formatData = () => {  
71 - if (props.categories.length > 0) { 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) {
72 chartData.value = { 109 chartData.value = {
73 - categories: props.categories,  
74 - series: props.series, 110 + categories: cat,
  111 + series: ser
75 }; 112 };
76 } 113 }
77 - }; 114 +};
  115 +
78 116
79 - // 4. 监听 Props 变化,当父组件传入新数据时自动重绘  
80 - watch(  
81 - () => [props.categories, props.series], 117 +// 4. 监听 Props 变化,当父组件传入新数据时自动重绘
  118 +watch(
  119 + () => [props.categories, props.series, props.chartType],
82 () => { 120 () => {
83 formatData(); 121 formatData();
84 }, 122 },
85 { immediate: true, deep: true }, 123 { immediate: true, deep: true },
86 - ); 124 +);
87 </script> 125 </script>
88 126
89 <style lang="scss" scoped> 127 <style lang="scss" scoped>
90 - .charts-box { 128 +.charts-box {
91 width: 100%; 129 width: 100%;
92 height: 100%; 130 height: 100%;
93 - } 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>
1 <template> 1 <template>
2 - <up-popup  
3 - :show="actionShow"  
4 - mode="bottom"  
5 - minHeight="90vh"  
6 - @close="actionShow = false"  
7 - bgColor="#1a1a1a"  
8 - > 2 + <up-popup :show="actionShow" mode="bottom" minHeight="90vh" @close="actionShow = false" bgColor="#1a1a1a">
9 <scroll-view class="action-container" scroll-y> 3 <scroll-view class="action-container" scroll-y>
10 <view class="header" v-if="type == 1"> 4 <view class="header" v-if="type == 1">
11 <view class="explain"> 5 <view class="explain">
12 - <image  
13 - v-if="modeTab < 2"  
14 - :src="modeTab == 0 ? actionDetail.url3dAnimation : actionDetail.urlRealPerson"  
15 - class="media-content"  
16 - mode="aspectFill"  
17 - /> 6 + <image v-if="modeTab < 2" :src="modeTab == 0 ? actionDetail.url3dAnimation : actionDetail.urlRealPerson"
  7 + class="media-content" mode="aspectFill" />
18 <view class="video" v-else @click="playVideo(actionDetail.urlTutorial)"> 8 <view class="video" v-else @click="playVideo(actionDetail.urlTutorial)">
19 - <video  
20 - :src="actionDetail.urlTutorial"  
21 - class="media-content"  
22 - :autoplay="false"  
23 - :show-center-play-btn="false"  
24 - :controls="false"  
25 - /> 9 + <video :src="actionDetail.urlTutorial" class="media-content" :autoplay="false" :show-center-play-btn="false"
  10 + :controls="false" />
26 <view class="play-icon"> 11 <view class="play-icon">
27 <up-icon name="play-right" size="28" class="icon" color="#fff"></up-icon> 12 <up-icon name="play-right" size="28" class="icon" color="#fff"></up-icon>
28 </view> 13 </view>
@@ -30,28 +15,16 @@ @@ -30,28 +15,16 @@
30 </view> 15 </view>
31 16
32 <view class="mode-tabs" v-if="actionDetail.urlRealPerson || actionDetail.urlTutorial"> 17 <view class="mode-tabs" v-if="actionDetail.urlRealPerson || actionDetail.urlTutorial">
33 - <view  
34 - class="tab-item"  
35 - v-if="actionDetail.url3dAnimation"  
36 - :class="{ active: modeTab == 0 }"  
37 - @click="switchModeTab(0)"  
38 - > 18 + <view class="tab-item" v-if="actionDetail.url3dAnimation" :class="{ active: modeTab == 0 }"
  19 + @click="switchModeTab(0)">
39 3D 20 3D
40 </view> 21 </view>
41 - <view  
42 - class="tab-item"  
43 - v-if="actionDetail.urlRealPerson"  
44 - :class="{ active: modeTab == 1 }"  
45 - @click="switchModeTab(1)"  
46 - > 22 + <view class="tab-item" v-if="actionDetail.urlRealPerson" :class="{ active: modeTab == 1 }"
  23 + @click="switchModeTab(1)">
47 真人 24 真人
48 </view> 25 </view>
49 - <view  
50 - class="tab-item"  
51 - v-if="actionDetail.urlTutorial"  
52 - :class="{ active: modeTab == 2 }"  
53 - @click="switchModeTab(2)"  
54 - > 26 + <view class="tab-item" v-if="actionDetail.urlTutorial" :class="{ active: modeTab == 2 }"
  27 + @click="switchModeTab(2)">
55 讲解 28 讲解
56 </view> 29 </view>
57 </view> 30 </view>
@@ -66,12 +39,8 @@ @@ -66,12 +39,8 @@
66 <button class="share-btn" open-type="share"> 39 <button class="share-btn" open-type="share">
67 <uni-icons type="paperplane" size="24" color="#fff"></uni-icons> 40 <uni-icons type="paperplane" size="24" color="#fff"></uni-icons>
68 </button> 41 </button>
69 - <up-icon  
70 - :name="isFavorite ? 'star-fill' : 'star'"  
71 - :color="isFavorite ? '#fedc1f' : '#fff'"  
72 - size="24"  
73 - @click="toggleCollect"  
74 - ></up-icon> 42 + <up-icon :name="isFavorite ? 'star-fill' : 'star'" :color="isFavorite ? '#fedc1f' : '#fff'" size="24"
  43 + @click="toggleCollect"></up-icon>
75 <!-- <FavoriteBtn :id="actionId" :type="type" /> --> 44 <!-- <FavoriteBtn :id="actionId" :type="type" /> -->
76 </view> 45 </view>
77 </view> 46 </view>
@@ -83,12 +52,7 @@ @@ -83,12 +52,7 @@
83 <view class="tab-item" :class="{ active: contentTab === 1 }" @click="contentTab = 1"> 52 <view class="tab-item" :class="{ active: contentTab === 1 }" @click="contentTab = 1">
84 历史 53 历史
85 </view> 54 </view>
86 - <view  
87 - class="tab-item"  
88 - :class="{ active: contentTab === 2 }"  
89 - @click="contentTab = 2"  
90 - v-if="type === 1"  
91 - > 55 + <view class="tab-item" :class="{ active: contentTab === 2 }" @click="contentTab = 2" v-if="type === 1">
92 平替动作 56 平替动作
93 </view> 57 </view>
94 </view> 58 </view>
@@ -102,44 +66,26 @@ @@ -102,44 +66,26 @@
102 <view class="video-grid"> 66 <view class="video-grid">
103 <!-- <view class="video-card" v-for="i in 2" :key="i"></view> --> 67 <!-- <view class="video-card" v-for="i in 2" :key="i"></view> -->
104 <view class="video-card" @click="playVideo(actionDetail.urlTutorial)"> 68 <view class="video-card" @click="playVideo(actionDetail.urlTutorial)">
105 - <video  
106 - :src="actionDetail.urlTutorial"  
107 - class="video"  
108 - :autoplay="false"  
109 - :show-center-play-btn="false"  
110 - :controls="false"  
111 - />  
112 - <view class="play-overlay"  
113 - ><up-icon name="play-circle-fill" color="#fff" size="30"></up-icon  
114 - ></view> 69 + <video :src="actionDetail.urlTutorial" class="video" :autoplay="false" :show-center-play-btn="false"
  70 + :controls="false" />
  71 + <view class="play-overlay"><up-icon name="play-circle-fill" color="#fff" size="30"></up-icon></view>
115 </view> 72 </view>
116 </view> 73 </view>
117 </view> 74 </view>
118 75
119 <view class="memo-box" @click="openBeizhu"> 76 <view class="memo-box" @click="openBeizhu">
120 <view class="section-title">训练备注</view> 77 <view class="section-title">训练备注</view>
121 - <up-textarea  
122 - class="textarea"  
123 - v-model="actionDetail.userNote"  
124 - placeholder="点击填写备注"  
125 - autoHeight  
126 - customStyle="background: transparent; border: none; padding: 10rpx 0;"  
127 - placeholderStyle="color: #666"  
128 - disabled  
129 - border="none"  
130 - ></up-textarea> 78 + <up-textarea class="textarea" v-model="actionDetail.userNote" placeholder="点击填写备注" autoHeight
  79 + customStyle="background: transparent; border: none; padding: 10rpx 0;" placeholderStyle="color: #666"
  80 + disabled border="none"></up-textarea>
131 </view> 81 </view>
132 <!-- 动作列表,只有超级组才有 --> 82 <!-- 动作列表,只有超级组才有 -->
133 <view class="section" v-if="type === 2"> 83 <view class="section" v-if="type === 2">
134 <view class="section-title">动作列表</view> 84 <view class="section-title">动作列表</view>
135 <view class="action-list"> 85 <view class="action-list">
136 <!-- 动作循环列表 --> 86 <!-- 动作循环列表 -->
137 - <view  
138 - class="action-item"  
139 - v-for="item in actionDetail?.exercises"  
140 - :key="item.id"  
141 - @click="openActionItem(item)"  
142 - > 87 + <view class="action-item" v-for="item in actionDetail?.exercises" :key="item.id"
  88 + @click="openActionItem(item)">
143 <image :src="item.url3dAnimation || lostImage" mode="aspectFill" class="img" /> 89 <image :src="item.url3dAnimation || lostImage" mode="aspectFill" class="img" />
144 <view class="middle"> 90 <view class="middle">
145 <view class="name">{{ item.name }}</view> 91 <view class="name">{{ item.name }}</view>
@@ -191,7 +137,7 @@ @@ -191,7 +137,7 @@
191 </view> --> 137 </view> -->
192 138
193 <view class="history-list"> 139 <view class="history-list">
194 - <template v-if="historyList.length > 0"> 140 + <template v-if="historyList && historyList.length > 0">
195 <view class="history-item" v-for="item in historyList" :key="item.id"> 141 <view class="history-item" v-for="item in historyList" :key="item.id">
196 <view class="item-header"> 142 <view class="item-header">
197 <text class="date">{{ formatDate(item.date) }}</text> 143 <text class="date">{{ formatDate(item.date) }}</text>
@@ -225,12 +171,7 @@ @@ -225,12 +171,7 @@
225 <!-- 3 平替动作(只有动作组才有) --> 171 <!-- 3 平替动作(只有动作组才有) -->
226 <view v-if="contentTab === 2 && type === 1" class="tab-pane slide-up"> 172 <view v-if="contentTab === 2 && type === 1" class="tab-pane slide-up">
227 <view class="substitute-list"> 173 <view class="substitute-list">
228 - <view  
229 - class="sub-item"  
230 - v-for="item in alternativeActions"  
231 - :key="item.id"  
232 - @click="openActionItem(item)"  
233 - > 174 + <view class="sub-item" v-for="item in alternativeActions" :key="item.id" @click="openActionItem(item)">
234 <image :src="item.urlImage || lostImage" mode="aspectFill" class="img" /> 175 <image :src="item.urlImage || lostImage" mode="aspectFill" class="img" />
235 <view class="sub-info"> 176 <view class="sub-info">
236 <text class="name">{{ item.name }}</text> 177 <text class="name">{{ item.name }}</text>
@@ -253,56 +194,56 @@ @@ -253,56 +194,56 @@
253 </template> 194 </template>
254 195
255 <script setup> 196 <script setup>
256 - import { onMounted, ref, nextTick } from 'vue';  
257 - import ExercisesApi from '@/sheep/api/motion/exercises';  
258 - import beizhu from '@/pages/xunji/components/beizhu.vue';  
259 - import SupersetsApi from '@/sheep/api/motion/supersets';  
260 - import TrainingApi from '@/sheep/api/Training/traininghistory';  
261 - import { onShareAppMessage } from '@dcloudio/uni-app';  
262 -  
263 - // 静态配置  
264 -  
265 - const alternativeActions = ref([]); // 平替动作列表接口数据  
266 -  
267 - // 响应式状态  
268 - const actionShow = ref(false);  
269 - const modeTab = ref(0);  
270 - const contentTab = ref(0);  
271 - const isFavorite = ref(false);  
272 -  
273 - // 记录当前动作的详细数据  
274 - const actionDetail = ref({});  
275 - // 记录当前动作的id  
276 - const actionId = ref(0);  
277 - // 记录是超级组还是动作组 1=动作组,2=超级组  
278 - const type = ref(0);  
279 - const showBeizhuRef = ref(null);  
280 - const historyList = ref([]); // 训练历史列表接口数据  
281 - const lostImage = 197 +import { onMounted, ref, nextTick } from 'vue';
  198 +import ExercisesApi from '@/sheep/api/motion/exercises';
  199 +import beizhu from '@/pages/xunji/components/beizhu.vue';
  200 +import SupersetsApi from '@/sheep/api/motion/supersets';
  201 +import TrainingApi from '@/sheep/api/Training/traininghistory';
  202 +import { onShareAppMessage } from '@dcloudio/uni-app';
  203 +
  204 +// 静态配置
  205 +
  206 +const alternativeActions = ref([]); // 平替动作列表接口数据
  207 +
  208 +// 响应式状态
  209 +const actionShow = ref(false);
  210 +const modeTab = ref(0);
  211 +const contentTab = ref(0);
  212 +const isFavorite = ref(false);
  213 +
  214 +// 记录当前动作的详细数据
  215 +const actionDetail = ref({});
  216 +// 记录当前动作的id
  217 +const actionId = ref(0);
  218 +// 记录是超级组还是动作组 1=动作组,2=超级组
  219 +const type = ref(0);
  220 +const showBeizhuRef = ref(null);
  221 +const historyList = ref([]); // 训练历史列表接口数据
  222 +const lostImage =
282 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png'; 223 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png';
283 224
284 - // 切换图表模式  
285 - const switchModeTab = (index) => { 225 +// 切换图表模式
  226 +const switchModeTab = (index) => {
286 modeTab.value = index; 227 modeTab.value = index;
287 - }; 228 +};
288 229
289 - // 跳转到视频播放的页面  
290 - const playVideo = (url) => { 230 +// 跳转到视频播放的页面
  231 +const playVideo = (url) => {
291 uni.navigateTo({ 232 uni.navigateTo({
292 url: '/pages4/pages/xunji/xunji-shiping?url=' + url, 233 url: '/pages4/pages/xunji/xunji-shiping?url=' + url,
293 }); 234 });
294 - };  
295 - // 获取动作收藏状态  
296 - const checkExerciseFavorited = async () => { 235 +};
  236 +// 获取动作收藏状态
  237 +const checkExerciseFavorited = async () => {
297 try { 238 try {
298 const res = await ExercisesApi.checkExerciseFavorited(actionId.value); 239 const res = await ExercisesApi.checkExerciseFavorited(actionId.value);
299 isFavorite.value = res.data; 240 isFavorite.value = res.data;
300 } catch (err) { 241 } catch (err) {
301 console.log(err); 242 console.log(err);
302 } 243 }
303 - };  
304 - // 收藏  
305 - const toggleCollect = async () => { 244 +};
  245 +// 收藏
  246 +const toggleCollect = async () => {
306 try { 247 try {
307 const status = isFavorite.value ? 0 : 1; 248 const status = isFavorite.value ? 0 : 1;
308 if (type == 1) { 249 if (type == 1) {
@@ -315,25 +256,25 @@ @@ -315,25 +256,25 @@
315 } catch (err) { 256 } catch (err) {
316 console.log(err); 257 console.log(err);
317 } 258 }
318 - }; 259 +};
319 260
320 - // 获取超级组收藏状态  
321 - const checkSupersetFavorited = async () => { 261 +// 获取超级组收藏状态
  262 +const checkSupersetFavorited = async () => {
322 try { 263 try {
323 const res = await SupersetsApi.checkSupersetFavorited(actionId.value); 264 const res = await SupersetsApi.checkSupersetFavorited(actionId.value);
324 isFavorite.value = res.data; 265 isFavorite.value = res.data;
325 } catch (err) { 266 } catch (err) {
326 console.log(err); 267 console.log(err);
327 } 268 }
328 - }; 269 +};
329 270
330 - const startTraining = () => { 271 +const startTraining = () => {
331 uni.navigateTo({ 272 uni.navigateTo({
332 url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${actionId.value}&type=${type.value}`, 273 url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${actionId.value}&type=${type.value}`,
333 }); 274 });
334 - }; 275 +};
335 276
336 - const open = (id, typeData) => { 277 +const open = (id, typeData) => {
337 actionId.value = Number(id); 278 actionId.value = Number(id);
338 279
339 type.value = typeData; 280 type.value = typeData;
@@ -348,20 +289,20 @@ @@ -348,20 +289,20 @@
348 checkSupersetFavorited(); 289 checkSupersetFavorited();
349 } 290 }
350 291
351 - loadTrainHistoryDetail(actionId.value); 292 + loadTrainHistoryDetail(actionId.value, type.value);
352 actionShow.value = true; 293 actionShow.value = true;
353 - };  
354 - // 打开备注弹窗  
355 - const openBeizhu = () => { 294 +};
  295 +// 打开备注弹窗
  296 +const openBeizhu = () => {
356 nextTick(() => { 297 nextTick(() => {
357 if (showBeizhuRef.value) { 298 if (showBeizhuRef.value) {
358 showBeizhuRef.value.open(actionId.value); 299 showBeizhuRef.value.open(actionId.value);
359 } 300 }
360 }); 301 });
361 - }; 302 +};
362 303
363 - // 接收子组件传过来的备注内容  
364 - const handleNoteSave = async (content) => { 304 +// 接收子组件传过来的备注内容
  305 +const handleNoteSave = async (content) => {
365 try { 306 try {
366 if (type.value == 2) { 307 if (type.value == 2) {
367 await SupersetsApi.addNotes({ 308 await SupersetsApi.addNotes({
@@ -378,17 +319,17 @@ @@ -378,17 +319,17 @@
378 } catch (e) { 319 } catch (e) {
379 console.log(e); 320 console.log(e);
380 } 321 }
381 - }; 322 +};
382 323
383 - // 启用分享菜单  
384 - // #ifdef MP-WEIXIN  
385 - wx.showShareMenu({ 324 +// 启用分享菜单
  325 +// #ifdef MP-WEIXIN
  326 +wx.showShareMenu({
386 withShareTicket: true, 327 withShareTicket: true,
387 menus: ['shareAppMessage', 'shareTimeline'], 328 menus: ['shareAppMessage', 'shareTimeline'],
388 - });  
389 - // #endif  
390 - // 定义分享内容  
391 - onShareAppMessage((res) => { 329 +});
  330 +// #endif
  331 +// 定义分享内容
  332 +onShareAppMessage((res) => {
392 // res.from 可区分触发来源:'button'(按钮触发)或 'menu'(右上角菜单触发)[reference:3] 333 // res.from 可区分触发来源:'button'(按钮触发)或 'menu'(右上角菜单触发)[reference:3]
393 console.log('分享触发来源:', res.from); 334 console.log('分享触发来源:', res.from);
394 return { 335 return {
@@ -397,10 +338,10 @@ @@ -397,10 +338,10 @@
397 path: `/pages/xunji/xunji?currentTab=${3}`, 338 path: `/pages/xunji/xunji?currentTab=${3}`,
398 imageUrl: actionDetail.value.urlImage || lostImage, // 分享图片 339 imageUrl: actionDetail.value.urlImage || lostImage, // 分享图片
399 }; 340 };
400 - }); 341 +});
401 342
402 - // 加载单个动作详情  
403 - const loadexercisedetail = async (id) => { 343 +// 加载单个动作详情
  344 +const loadexercisedetail = async (id) => {
404 const response = await ExercisesApi.getExerciseById(id); 345 const response = await ExercisesApi.getExerciseById(id);
405 actionDetail.value = response.data; 346 actionDetail.value = response.data;
406 if (actionDetail.value.url3dAnimation) { 347 if (actionDetail.value.url3dAnimation) {
@@ -410,16 +351,16 @@ @@ -410,16 +351,16 @@
410 } else { 351 } else {
411 modeTab.value = 2; 352 modeTab.value = 2;
412 } 353 }
413 - }; 354 +};
414 355
415 - // 加载超级组详情  
416 - const loadsuperdetail = async (id) => { 356 +// 加载超级组详情
  357 +const loadsuperdetail = async (id) => {
417 const response = await SupersetsApi.getSupersetsInfo(id); 358 const response = await SupersetsApi.getSupersetsInfo(id);
418 actionDetail.value = response.data; 359 actionDetail.value = response.data;
419 console.log('显示超级组详情:', actionDetail.value); 360 console.log('显示超级组详情:', actionDetail.value);
420 - };  
421 - // 2. 加载平替动作的函数  
422 - const loadAlternativeActions = async (id) => { 361 +};
  362 +// 2. 加载平替动作的函数
  363 +const loadAlternativeActions = async (id) => {
423 if (!id || isNaN(Number(id))) { 364 if (!id || isNaN(Number(id))) {
424 console.warn('平替动作id非法,跳过请求:', id); 365 console.warn('平替动作id非法,跳过请求:', id);
425 alternativeActions.value = []; 366 alternativeActions.value = [];
@@ -436,30 +377,30 @@ @@ -436,30 +377,30 @@
436 console.error('加载平替动作失败:', error); 377 console.error('加载平替动作失败:', error);
437 alternativeActions.value = []; 378 alternativeActions.value = [];
438 } 379 }
439 - }; 380 +};
440 381
441 - // 加载训练历史  
442 - const loadTrainHistoryDetail = async (id) => { 382 +// 加载训练历史
  383 +const loadTrainHistoryDetail = async (id, type) => {
443 try { 384 try {
444 - const res = await TrainingApi.getTrainHistoryList(id); 385 + const res = await TrainingApi.getTrainHistoryList(id, type);
445 historyList.value = res.data; 386 historyList.value = res.data;
446 console.log('训练历史列表接口返回结果historyList.value', historyList.value); 387 console.log('训练历史列表接口返回结果historyList.value', historyList.value);
447 } catch (error) { 388 } catch (error) {
448 console.error('加载训练历史失败:', error); 389 console.error('加载训练历史失败:', error);
449 } 390 }
450 - };  
451 - // 格式化时间  
452 - const formatDate = (dateArr) => { 391 +};
  392 +// 格式化时间
  393 +const formatDate = (dateArr) => {
453 if (!Array.isArray(dateArr) || dateArr.length < 3) return ''; 394 if (!Array.isArray(dateArr) || dateArr.length < 3) return '';
454 const [year, month, day] = dateArr; 395 const [year, month, day] = dateArr;
455 const date = new Date(year, month - 1, day); 396 const date = new Date(year, month - 1, day);
456 const weekArr = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; 397 const weekArr = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
457 const week = weekArr[date.getDay()]; 398 const week = weekArr[date.getDay()];
458 return `${year}/${String(month).padStart(2, '0')}/${String(day).padStart(2, '0')} ${week}`; 399 return `${year}/${String(month).padStart(2, '0')}/${String(day).padStart(2, '0')} ${week}`;
459 - }; 400 +};
460 401
461 - // 格式化每组数据:自动拼接 weight/reps/duration/distance  
462 - const formatSetData = (set) => { 402 +// 格式化每组数据:自动拼接 weight/reps/duration/distance
  403 +const formatSetData = (set) => {
463 const parts = []; 404 const parts = [];
464 405
465 // 重量 406 // 重量
@@ -483,10 +424,10 @@ @@ -483,10 +424,10 @@
483 } 424 }
484 425
485 return parts.length > 0 ? parts.join(' × ') : '无数据'; 426 return parts.length > 0 ? parts.join(' × ') : '无数据';
486 - }; 427 +};
487 428
488 - // 新增:秒数转 00:00:00 格式  
489 - const formatTime = (seconds) => { 429 +// 新增:秒数转 00:00:00 格式
  430 +const formatTime = (seconds) => {
490 const h = Math.floor(seconds / 3600); 431 const h = Math.floor(seconds / 3600);
491 const m = Math.floor((seconds % 3600) / 60); 432 const m = Math.floor((seconds % 3600) / 60);
492 const s = seconds % 60; 433 const s = seconds % 60;
@@ -496,21 +437,21 @@ @@ -496,21 +437,21 @@
496 const ss = String(s).padStart(2, '0'); 437 const ss = String(s).padStart(2, '0');
497 438
498 return hh + mm + ss; 439 return hh + mm + ss;
499 - }; 440 +};
500 441
501 442
502 - // 点击超级组内部的动作 → 打开动作详情(复用同一个组件)  
503 - const openActionItem = (item) => { 443 +// 点击超级组内部的动作 → 打开动作详情(复用同一个组件)
  444 +const openActionItem = (item) => {
504 open(item.id, 1); 445 open(item.id, 1);
505 - }; 446 +};
506 447
507 - defineExpose({ open }); 448 +defineExpose({ open });
508 449
509 - onMounted(() => {}); 450 +onMounted(() => { });
510 </script> 451 </script>
511 452
512 <style lang="scss" scoped> 453 <style lang="scss" scoped>
513 - .action-container { 454 +.action-container {
514 width: 100%; 455 width: 100%;
515 height: 80vh; 456 height: 80vh;
516 background-color: #1a1a1a; 457 background-color: #1a1a1a;
@@ -529,6 +470,7 @@ @@ -529,6 +470,7 @@
529 width: 100%; 470 width: 100%;
530 height: 100%; 471 height: 100%;
531 } 472 }
  473 +
532 .video { 474 .video {
533 width: 100%; 475 width: 100%;
534 height: 100%; 476 height: 100%;
@@ -536,6 +478,7 @@ @@ -536,6 +478,7 @@
536 justify-content: center; 478 justify-content: center;
537 align-items: center; 479 align-items: center;
538 position: relative; 480 position: relative;
  481 +
539 .play-icon { 482 .play-icon {
540 width: 50px; 483 width: 50px;
541 height: 50px; 484 height: 50px;
@@ -560,6 +503,7 @@ @@ -560,6 +503,7 @@
560 border-radius: 12rpx; 503 border-radius: 12rpx;
561 backdrop-filter: blur(10px); 504 backdrop-filter: blur(10px);
562 z-index: 99; 505 z-index: 99;
  506 +
563 .tab-item { 507 .tab-item {
564 padding: 8rpx 24rpx; 508 padding: 8rpx 24rpx;
565 font-size: 24rpx; 509 font-size: 24rpx;
@@ -925,6 +869,7 @@ @@ -925,6 +869,7 @@
925 height: 120rpx; 869 height: 120rpx;
926 background-color: #242424; 870 background-color: #242424;
927 z-index: 999; 871 z-index: 999;
  872 +
928 .btn { 873 .btn {
929 width: 80%; 874 width: 80%;
930 height: 80rpx; 875 height: 80rpx;
@@ -936,14 +881,14 @@ @@ -936,14 +881,14 @@
936 border-radius: 50rpx; 881 border-radius: 50rpx;
937 } 882 }
938 } 883 }
939 - } 884 +}
940 885
941 - // 动画  
942 - .slide-up { 886 +// 动画
  887 +.slide-up {
943 animation: slideUp 0.4s ease-out; 888 animation: slideUp 0.4s ease-out;
944 - } 889 +}
945 890
946 - @keyframes slideUp { 891 +@keyframes slideUp {
947 from { 892 from {
948 opacity: 0; 893 opacity: 0;
949 transform: translateY(20rpx); 894 transform: translateY(20rpx);
@@ -953,5 +898,5 @@ @@ -953,5 +898,5 @@
953 opacity: 1; 898 opacity: 1;
954 transform: translateY(0); 899 transform: translateY(0);
955 } 900 }
956 - } 901 +}
957 </style> 902 </style>