Authored by qxm

训计日历的间歇动作完善,训练图片修改,复制到接口,备注

1 <template> 1 <template>
2 - <up-popup :show="show" mode="bottom" round="16" closeable @close="show = false" :safeAreaInsetBottom="false"> 2 + <up-popup :show="show" mode="bottom" round="16" :safeAreaInsetBottom="false">
3 <view class="desc-container"> 3 <view class="desc-container">
4 - <view class="title">动作备注</view>  
5 -  
6 - <view class="input-box">  
7 - <up-textarea  
8 - v-model="tempNoteContent"  
9 - placeholder="此处填写个人备注"  
10 - autoHeight  
11 - border="none"  
12 - customStyle="background: #242424; padding: 20rpx; border-radius: 12rpx; color: #fff"  
13 - placeholderStyle="color: #999"  
14 - ></up-textarea> 4 + <view class="title">
  5 + <view @click="show = false">
  6 + <up-icon name="close" color="#fff" size="20"></up-icon>
15 </view> 7 </view>
16 -  
17 - <view class="footer"> 8 + <text class="titleName">
  9 + 动作备注
  10 + </text>
18 <view class="btn" @click="saveNoteContent">保存</view> 11 <view class="btn" @click="saveNoteContent">保存</view>
19 </view> 12 </view>
  13 + <view class="input-box">
  14 + <up-textarea v-model="tempNoteContent" placeholder="此处填写个人备注" class="noteConten"
  15 + customStyle="border-radius:12rpx; padding:20rpx;background: #242424;" />
  16 + </view>
20 </view> 17 </view>
21 </up-popup> 18 </up-popup>
22 </template> 19 </template>
23 20
24 <script setup> 21 <script setup>
25 - import { ref } from 'vue'; 22 +import { ref } from 'vue';
26 23
27 - const show = ref(false);  
28 - const tempNoteContent = ref(''); // 临时编辑的备注内容 24 +const show = ref(false);
  25 +const tempNoteContent = ref(''); // 临时编辑的备注内容
29 26
30 - const emit = defineEmits(['saveSuccess']);  
31 - // 保存备注  
32 - const saveNoteContent = async () => { 27 +const emit = defineEmits(['saveSuccess']);
  28 +// 保存备注
  29 +const saveNoteContent = async () => {
33 const content = tempNoteContent.value.trim(); 30 const content = tempNoteContent.value.trim();
34 // 如果内容为空,直接返回,不提交 31 // 如果内容为空,直接返回,不提交
35 if (!content) { 32 if (!content) {
@@ -39,52 +36,51 @@ @@ -39,52 +36,51 @@
39 emit('saveSuccess', content); 36 emit('saveSuccess', content);
40 // 关闭弹窗 37 // 关闭弹窗
41 show.value = false; 38 show.value = false;
42 - }; 39 +};
43 40
44 - const open = () => { 41 +const open = () => {
45 show.value = true; 42 show.value = true;
46 - }; 43 +};
47 44
48 - defineExpose({ open }); 45 +defineExpose({ open });
49 </script> 46 </script>
50 47
51 <style lang="scss" scoped> 48 <style lang="scss" scoped>
52 - .desc-container { 49 +.desc-container {
53 background-color: #1a1a1a; 50 background-color: #1a1a1a;
54 padding: 40rpx 30rpx 40rpx; 51 padding: 40rpx 30rpx 40rpx;
  52 + height: 75vh;
55 53
56 .title { 54 .title {
57 - color: #fff;  
58 - font-size: 32rpx; 55 + display: flex;
  56 + justify-content: space-between;
59 font-weight: 500; 57 font-weight: 500;
60 text-align: center; 58 text-align: center;
  59 + color: #fff;
  60 + font-size: 32rpx;
61 margin-bottom: 40rpx; 61 margin-bottom: 40rpx;
62 } 62 }
63 63
  64 + .btn {
  65 + width: 100rpx;
  66 + height: 50rpx;
  67 + background-color: #fedc1f;
  68 + color: #000;
  69 + border-radius: 30rpx;
  70 + display: flex;
  71 + align-items: center;
  72 + justify-content: center;
  73 + font-size: 28rpx;
  74 + }
  75 +
64 .input-box { 76 .input-box {
65 margin-bottom: 60rpx; 77 margin-bottom: 60rpx;
66 78
67 - /* 穿透修改 u-textarea 内部文字颜色 */ 79 + // 输入文字白色
68 :deep(.u-textarea__field) { 80 :deep(.u-textarea__field) {
69 - color: #ccc !important;  
70 - } 81 + color: #fff !important;
71 } 82 }
72 83
73 - .footer {  
74 - width: 100%;  
75 - display: flex;  
76 - justify-content: center;  
77 - align-items: center;  
78 -  
79 - .btn {  
80 - width: 100%;  
81 - height: 80rpx;  
82 - background-color: #fedc1f;  
83 - color: #333;  
84 - border-radius: 12rpx;  
85 - text-align: center;  
86 - line-height: 80rpx;  
87 - }  
88 - }  
89 } 84 }
  85 +}
90 </style> 86 </style>
@@ -305,7 +305,8 @@ const handleConfirm = async () => { @@ -305,7 +305,8 @@ const handleConfirm = async () => {
305 exerciseId: detail.id, 305 exerciseId: detail.id,
306 exerciseName: detail.name, 306 exerciseName: detail.name,
307 exerciseType: detail.exerciseType, 307 exerciseType: detail.exerciseType,
308 - urlImage: detail.urlImage || detail.url3dAnimation, 308 + urlImage: detail.url3dAnimation || '',
  309 + // urlImage: detail.urlImage || detail.url3dAnimation,
309 categoryDescription: detail.categoryDescription, 310 categoryDescription: detail.categoryDescription,
310 equipmentDescription: detail.equipmentDescription, 311 equipmentDescription: detail.equipmentDescription,
311 } 312 }
@@ -326,7 +327,8 @@ const handleConfirm = async () => { @@ -326,7 +327,8 @@ const handleConfirm = async () => {
326 exerciseId: e.id, 327 exerciseId: e.id,
327 exerciseName: e.name, 328 exerciseName: e.name,
328 exerciseType: e.exerciseType, 329 exerciseType: e.exerciseType,
329 - urlImage: e.urlImage || e.url3dAnimation, 330 + urlImage: e.url3dAnimation || e.exerciseCover,
  331 + // exerciseCover
330 })) 332 }))
331 }; 333 };
332 } 334 }
@@ -282,7 +282,6 @@ const onWeightConfirm = (e) => { @@ -282,7 +282,6 @@ const onWeightConfirm = (e) => {
282 showWeightPicker.value = false 282 showWeightPicker.value = false
283 } 283 }
284 284
285 -// 文字备注  
286 // 打开备注弹窗 285 // 打开备注弹窗
287 const openBeizhu = () => { 286 const openBeizhu = () => {
288 nextTick(() => { 287 nextTick(() => {
@@ -652,7 +651,8 @@ const addRow = () => { @@ -652,7 +651,8 @@ const addRow = () => {
652 recordList.value.push({ 651 recordList.value.push({
653 h: '00', m: '00', s: '00', 652 h: '00', m: '00', s: '00',
654 quickTimeDisplay: '60s', 653 quickTimeDisplay: '60s',
655 - distance: '', weight: '', reps: '', isActive: false, 654 + distance: '', weight: '', reps: '', duration: '',
  655 + restTime: '', isActive: false,
656 }); 656 });
657 }; 657 };
658 const deleteRow = (index) => { 658 const deleteRow = (index) => {
@@ -347,6 +347,12 @@ const handleNoteSave = async (content) => { @@ -347,6 +347,12 @@ const handleNoteSave = async (content) => {
347 actionDetail.value.userNote = content; 347 actionDetail.value.userNote = content;
348 } catch (e) { 348 } catch (e) {
349 console.log(e); 349 console.log(e);
  350 + } finally {
  351 + if (type == 1) {
  352 + await loadexercisedetail(actionId.value)
  353 + } else {
  354 + await loadsuperdetail(actionId.value)
  355 + }
350 } 356 }
351 }; 357 };
352 358
@@ -55,7 +55,9 @@ @@ -55,7 +55,9 @@
55 </view> 55 </view>
56 <view class="plan-header-btns"> 56 <view class="plan-header-btns">
57 <button class="plan-btn more-btn" @click.stop="handlePlanMore">更多</button> 57 <button class="plan-btn more-btn" @click.stop="handlePlanMore">更多</button>
58 - <button class="plan-btn copy-btn" @click.stop="openCalendarPopup(currentPlan?.templateId)">复制到</button> 58 + <button class="plan-btn copy-btn" @click.stop="openCopyCalendarPopup(currentPlan?.templateId)">
  59 + 复制到
  60 + </button>
59 <button class="plan-btn go-train-btn-small" @click.stop="handleTrainAgain"> 61 <button class="plan-btn go-train-btn-small" @click.stop="handleTrainAgain">
60 <text class="go-train-text-sm">今日再练</text> 62 <text class="go-train-text-sm">今日再练</text>
61 <!-- <text class="go-train-tag-sm">GO</text> --> 63 <!-- <text class="go-train-tag-sm">GO</text> -->
@@ -93,10 +95,10 @@ @@ -93,10 +95,10 @@
93 <view class="set-left" :style="{ color: set.isCompleted === 1 ? '#333' : '#a2a2a2' }"> 95 <view class="set-left" :style="{ color: set.isCompleted === 1 ? '#333' : '#a2a2a2' }">
94 <!-- <text class="indexData">{{ setIdx + 1 }}</text> --> 96 <!-- <text class="indexData">{{ setIdx + 1 }}</text> -->
95 <text class="set-content" v-if="unit.exercises[0].exerciseType === 0"> 97 <text class="set-content" v-if="unit.exercises[0].exerciseType === 0">
96 - {{ set.weight }}kg × {{ set.reps }}次 98 + {{ set.weight }}kg x {{ set.reps }}次
97 </text> 99 </text>
98 <text class="set-content" v-if="unit.exercises[0].exerciseType === 1"> 100 <text class="set-content" v-if="unit.exercises[0].exerciseType === 1">
99 - {{ formatSecondsToHms(set.duration) }} × {{ set.distance }}km 101 + {{ formatSecondsToHms(set.duration) }} x {{ set.distance }}km
100 </text> 102 </text>
101 <text class="set-content" v-if="unit.exercises[0].exerciseType === 2"> 103 <text class="set-content" v-if="unit.exercises[0].exerciseType === 2">
102 {{ set.reps }}次 104 {{ set.reps }}次
@@ -108,17 +110,19 @@ @@ -108,17 +110,19 @@
108 {{ set.weight }}kg x {{ set.duration }}s x {{ set.restTime }} 110 {{ set.weight }}kg x {{ set.duration }}s x {{ set.restTime }}
109 </text> 111 </text>
110 <text class="set-content" v-if="unit.exercises[0].exerciseType === 6"> 112 <text class="set-content" v-if="unit.exercises[0].exerciseType === 6">
111 - {{ set.reps }}次 x {{ formatSecondsToHms(set.duration) }} 113 + {{ set.reps }}组 x {{ set.duration }}秒 x {{ set.restTime || 0 }}秒/组休息
112 </text> 114 </text>
  115 + </view>
  116 + <view class="set-right">
113 <text class="rest-time" v-if="set.restTime && unit.exercises[0].exerciseType != 6"> 117 <text class="rest-time" v-if="set.restTime && unit.exercises[0].exerciseType != 6">
114 {{ set.restTime || 0 }}s 118 {{ set.restTime || 0 }}s
115 </text> 119 </text>
116 - </view>  
117 <text class="check" v-if="set.isCompleted === 1">✓</text> 120 <text class="check" v-if="set.isCompleted === 1">✓</text>
118 </view> 121 </view>
119 </view> 122 </view>
120 </view> 123 </view>
121 </view> 124 </view>
  125 + </view>
122 126
123 <!-- 右侧:修改按钮 + 完成对勾 --> 127 <!-- 右侧:修改按钮 + 完成对勾 -->
124 <view class="action-right" @click="openXiuGai(unit, unitIdx)"> 128 <view class="action-right" @click="openXiuGai(unit, unitIdx)">
@@ -165,31 +169,32 @@ @@ -165,31 +169,32 @@
165 <view class="set-content" 169 <view class="set-content"
166 :style="{ color: ex.sets[idx].isCompleted === 1 ? '#333' : '#a2a2a2' }"> 170 :style="{ color: ex.sets[idx].isCompleted === 1 ? '#333' : '#a2a2a2' }">
167 <!-- 适配不同动作类型的显示文本 --> 171 <!-- 适配不同动作类型的显示文本 -->
168 - <text v-if="ex.exerciseType === 0">  
169 - {{ ex.sets[idx].weight }}kg × {{ ex.sets[idx].reps }}次 172 + <text v-if="ex.exerciseType === 0" class="detai-data">
  173 + {{ ex.sets[idx].weight }}kg x {{ ex.sets[idx].reps }}次
170 </text> 174 </text>
171 - <text v-if="ex.exerciseType === 1">  
172 - {{ formatSecondsToHms(ex.sets[idx].duration) }} × {{ 175 + <text v-if="ex.exerciseType === 1" class="detai-data">
  176 + {{ formatSecondsToHms(ex.sets[idx].duration) }} x {{
173 ex.sets[idx].distance 177 ex.sets[idx].distance
174 }}km 178 }}km
175 </text> 179 </text>
176 - <text v-if="ex.exerciseType === 2"> 180 + <text v-if="ex.exerciseType === 2" class="detai-data">
177 {{ ex.sets[idx].reps }}次 181 {{ ex.sets[idx].reps }}次
178 </text> 182 </text>
179 - <text v-if="ex.exerciseType === 3"> 183 + <text v-if="ex.exerciseType === 3" class="detai-data">
180 {{ formatSecondsToHms(ex.sets[idx].duration) }} 184 {{ formatSecondsToHms(ex.sets[idx].duration) }}
181 </text> 185 </text>
182 - <text v-if="[4, 5].includes(ex.exerciseType)">  
183 - {{ ex.sets[idx].weight }}kg × {{ ex.sets[idx].reps }}次 186 + <text v-if="[4, 5].includes(ex.exerciseType)" class="detai-data">
  187 + {{ ex.sets[idx].weight }}kg x {{ ex.sets[idx].reps }}次
184 </text> 188 </text>
185 - <text v-if="ex.exerciseType === 6">  
186 - {{ formatSecondsToHms(ex.sets[idx].duration) }} 189 + <text v-if="ex.exerciseType === 6" class="detai-data">
  190 + {{ ex.sets[idx].reps }}次 x {{ ex.sets[idx].duration }}秒 x {{ ex.sets[idx].restTime
  191 + }}秒/组休息
187 </text> 192 </text>
188 - </view>  
189 <view class="rest-time" v-if="ex.sets[idx]?.restTime && ex.exerciseType !== 6" 193 <view class="rest-time" v-if="ex.sets[idx]?.restTime && ex.exerciseType !== 6"
190 :style="{ color: ex.sets[idx].isCompleted === 1 ? '#999 !important' : '#a2a2a2 !important' }"> 194 :style="{ color: ex.sets[idx].isCompleted === 1 ? '#999 !important' : '#a2a2a2 !important' }">
191 {{ ex.sets[idx].restTime }}s 195 {{ ex.sets[idx].restTime }}s
192 </view> 196 </view>
  197 + </view>
193 <text class="check" v-if="ex.sets[idx].isCompleted === 1">✓</text> 198 <text class="check" v-if="ex.sets[idx].isCompleted === 1">✓</text>
194 </template> 199 </template>
195 </view> 200 </view>
@@ -225,7 +230,7 @@ @@ -225,7 +230,7 @@
225 </view> --> 230 </view> -->
226 </view> 231 </view>
227 232
228 - <AddTrainPopup v-model:visible="showAddTrainPopup" /> 233 + <AddTrainPopup v-model:visible="showAddTrainPopup" @successAddTrain="handleAddTrain" />
229 234
230 <!-- 日期备注弹窗 --> 235 <!-- 日期备注弹窗 -->
231 <RiliRiqibeizhu v-model:visible="showRiqibeizhu" :date="date" :note-id="currentEditId" 236 <RiliRiqibeizhu v-model:visible="showRiqibeizhu" :date="date" :note-id="currentEditId"
@@ -268,12 +273,13 @@ @@ -268,12 +273,13 @@
268 <text class="item-text delete-text">删除整个计划</text> 273 <text class="item-text delete-text">删除整个计划</text>
269 </view> 274 </view>
270 </up-popup> 275 </up-popup>
271 - <!-- 复制到弹窗 --> 276 + <!-- 复制到弹窗/移动到/添加到 -->
272 <AddToCalendarPopup ref="calendarPopupRef" :template-id="currentPlan?.templateId ?? 0" 277 <AddToCalendarPopup ref="calendarPopupRef" :template-id="currentPlan?.templateId ?? 0"
273 - @success="handleCalendarSuccess" :mask-click="true" @click.stop /> 278 + @success="handleCalendarSuccess" :mask-click="true" @click.stop
  279 + :daily-template-id="currentPlan?.dailyTemplateId" :is-copy="isCopyMode" />
274 <!-- 设置日历颜色弹窗 --> 280 <!-- 设置日历颜色弹窗 -->
275 <CalendarColorPicker v-model:visible="showColorPopup" :daily-template-id="selectedPlanId" 281 <CalendarColorPicker v-model:visible="showColorPopup" :daily-template-id="selectedPlanId"
276 - :current-color="currentPlan?.templateBackgroundColor || '#ffffff'" @success="loaddailytemplate" /> 282 + :current-color="currentPlan?.templateBackgroundColor || '#ffffff'" @success="calendarColorPickerSuccess" />
277 283
278 <MeiRiMoBanXiuGai ref="meirimobanRef" @saveSuccess="loaddailytemplate(selectedDate)" /> 284 <MeiRiMoBanXiuGai ref="meirimobanRef" @saveSuccess="loaddailytemplate(selectedDate)" />
279 </view> 285 </view>
@@ -311,6 +317,7 @@ const resdailyData = ref([]); @@ -311,6 +317,7 @@ const resdailyData = ref([]);
311 const currentPlanIndex = ref(0); 317 const currentPlanIndex = ref(0);
312 318
313 const isMoveMode = ref(false); 319 const isMoveMode = ref(false);
  320 +const isCopyMode = ref(false);
314 const showRiqibeizhu = ref(false) 321 const showRiqibeizhu = ref(false)
315 const moreShow = ref(false) 322 const moreShow = ref(false)
316 const calendarPopupRef = ref(null); 323 const calendarPopupRef = ref(null);
@@ -398,6 +405,11 @@ const openColorPopup = () => { @@ -398,6 +405,11 @@ const openColorPopup = () => {
398 showColorPopup.value = true 405 showColorPopup.value = true
399 } 406 }
400 407
  408 +const handleAddTrain = async () => {
  409 + await loaddailytemplate();
  410 + // 更新主页面
  411 + emit('refreshCalendar');
  412 +}
401 // 当前正在显示的训记 413 // 当前正在显示的训记
402 const currentPlan = computed(() => { 414 const currentPlan = computed(() => {
403 if (!resdailyData.value.length) return null; 415 if (!resdailyData.value.length) return null;
@@ -672,6 +684,7 @@ const handleTrainAgain = async () => { @@ -672,6 +684,7 @@ const handleTrainAgain = async () => {
672 if (res.code === 0) { 684 if (res.code === 0) {
673 uni.showToast({ title: '今日再练成功', icon: 'success' }); 685 uni.showToast({ title: '今日再练成功', icon: 'success' });
674 loaddailytemplate(props.date); 686 loaddailytemplate(props.date);
  687 + emit('refreshCalendar')
675 } else { 688 } else {
676 uni.showToast({ title: res.msg || '添加失败', icon: 'none' }); 689 uni.showToast({ title: res.msg || '添加失败', icon: 'none' });
677 } 690 }
@@ -753,15 +766,45 @@ const handleMoveTo = async () => { @@ -753,15 +766,45 @@ const handleMoveTo = async () => {
753 }); 766 });
754 }; 767 };
755 768
  769 +// 复制到
  770 +const openCopyCalendarPopup = (templateId) => {
  771 + if (!templateId) return uni.showToast({ title: '模板ID不存在', icon: 'none' })
  772 + console.log('currentPlan?.dailyTemplateId=', currentPlan?.dailyTemplateId);
  773 + isCopyMode.value = true
  774 + isMoveMode.value = false // 清空移动标记,互斥
  775 + moreShow.value = false
  776 + nextTick(() => {
  777 + calendarPopupRef.value.open();
  778 + });
  779 +}
  780 +
756 const handleCalendarSuccess = () => { 781 const handleCalendarSuccess = () => {
757 - console.log('✅ 添加日历成功,当前模式:', isMoveMode.value ? '移动' : '复制');  
758 - if (!isMoveMode.value) { 782 + // console.log('✅ 添加日历成功,当前模式:', isMoveMode.value ? '移动' : '复制');
  783 + // 复制模式:只提示成功,不用删原数据
  784 + // if (!isMoveMode.value) {
  785 + // uni.showToast({
  786 + // title: '复制成功',
  787 + // icon: 'success'
  788 + // });
  789 + // return;
  790 + // }
  791 + console.log('✅ 添加日历成功', {
  792 + move: isMoveMode.value,
  793 + copy: isCopyMode.value
  794 + });
  795 +
  796 + // 复制模式:仅刷新日历,不删除原数据
  797 + if (isCopyMode.value) {
759 uni.showToast({ 798 uni.showToast({
760 title: '复制成功', 799 title: '复制成功',
761 icon: 'success' 800 icon: 'success'
762 }); 801 });
  802 + // 重置标记
  803 + isCopyMode.value = false
  804 + emit('refreshCalendar')
763 return; 805 return;
764 } 806 }
  807 + // 移动模式:子组件已把数据新增到新日期,这里删除原日期的训练记录
765 if (!currentPlan.value?.id) { 808 if (!currentPlan.value?.id) {
766 return; 809 return;
767 } 810 }
@@ -769,6 +812,7 @@ const handleCalendarSuccess = () => { @@ -769,6 +812,7 @@ const handleCalendarSuccess = () => {
769 .then(() => { 812 .then(() => {
770 uni.showToast({ title: '移动成功', icon: 'success' }); 813 uni.showToast({ title: '移动成功', icon: 'success' });
771 loaddailytemplate(selectedDate.value); 814 loaddailytemplate(selectedDate.value);
  815 + emit('refreshCalendar')
772 }) 816 })
773 .catch(err => { 817 .catch(err => {
774 uni.showToast({ title: '移动失败', icon: 'none' }); 818 uni.showToast({ title: '移动失败', icon: 'none' });
@@ -776,6 +820,11 @@ const handleCalendarSuccess = () => { @@ -776,6 +820,11 @@ const handleCalendarSuccess = () => {
776 isMoveMode.value = false 820 isMoveMode.value = false
777 }; 821 };
778 822
  823 +const calendarColorPickerSuccess = () => {
  824 + loaddailytemplate(selectedDate.value)
  825 + emit('refreshCalendar')
  826 +}
  827 +
779 // 去训练 828 // 去训练
780 const startTraining = () => { 829 const startTraining = () => {
781 // console.log('++点击了去训练++'); 830 // console.log('++点击了去训练++');
@@ -1068,6 +1117,14 @@ onMounted(() => { @@ -1068,6 +1117,14 @@ onMounted(() => {
1068 .set-content { 1117 .set-content {
1069 flex-grow: 1; 1118 flex-grow: 1;
1070 color: inherit; 1119 color: inherit;
  1120 + display: flex;
  1121 + justify-content: space-between;
  1122 + white-space: nowrap;
  1123 +
  1124 +}
  1125 +
  1126 +.detai-data {
  1127 + white-space: nowrap;
1071 } 1128 }
1072 1129
1073 // 主卡片:横向排列 1130 // 主卡片:横向排列
@@ -1153,6 +1210,11 @@ onMounted(() => { @@ -1153,6 +1210,11 @@ onMounted(() => {
1153 /* 控制 indexData、set-content、rest-time 之间的间距 */ 1210 /* 控制 indexData、set-content、rest-time 之间的间距 */
1154 } 1211 }
1155 1212
  1213 +.set-right {
  1214 + display: flex;
  1215 + gap: 20rpx;
  1216 +}
  1217 +
1156 // 灰色小圆点序号 1218 // 灰色小圆点序号
1157 .indexData { 1219 .indexData {
1158 width: 32rpx; 1220 width: 32rpx;
@@ -372,18 +372,30 @@ const saveEdit = async () => { @@ -372,18 +372,30 @@ const saveEdit = async () => {
372 if (unit.unitType === 1) { 372 if (unit.unitType === 1) {
373 // 单动作:exercises只有1个 373 // 单动作:exercises只有1个
374 const exItem = targetExercises[0] 374 const exItem = targetExercises[0]
  375 + const exerciseType = exItem.exerciseType;
  376 + console.log('----------exerciseType---------', exerciseType);
375 // expose出来的recordList 377 // expose出来的recordList
376 const newSets = dongzuoSingleRef.value.recordList.map((item, idx) => { 378 const newSets = dongzuoSingleRef.value.recordList.map((item, idx) => {
377 // 组件数据转回后端sets结构(和原页面save组装逻辑一致) 379 // 组件数据转回后端sets结构(和原页面save组装逻辑一致)
378 - const duration = Number(item.h) * 3600 + Number(item.m) * 60 + Number(item.s) || 0  
379 - const rest = Number(item.quickTimeDisplay.replace('s', '')) || 0 380 + let duration = 0
  381 + let restTime = 0
  382 + // 间歇动作单独取值
  383 + if (exerciseType === 6) {
  384 + duration = Number(item.duration) || 0
  385 + restTime = Number(item.restTime) || 0
  386 + } else {
  387 + // 普通动作时分秒换算
  388 + duration = Number(item.h) * 3600 + Number(item.m) * 60 + Number(item.s) || 0
  389 + restTime = Number(item.quickTimeDisplay.replace('s', '')) || 0
  390 + }
380 return { 391 return {
381 setIndex: idx + 1, 392 setIndex: idx + 1,
382 weight: Number(item.weight) || 0, 393 weight: Number(item.weight) || 0,
383 reps: Number(item.reps) || 0, 394 reps: Number(item.reps) || 0,
384 distance: Number(item.distance) || 0, 395 distance: Number(item.distance) || 0,
385 duration, 396 duration,
386 - restTime: rest, 397 + // restTime: rest,
  398 + restTime,
387 isCompleted: item.isActive ? 1 : 0 399 isCompleted: item.isActive ? 1 : 0
388 } 400 }
389 }) 401 })
@@ -392,17 +404,30 @@ const saveEdit = async () => { @@ -392,17 +404,30 @@ const saveEdit = async () => {
392 // 超级组:superRecordMap{exId:[]} 404 // 超级组:superRecordMap{exId:[]}
393 const superMap = dongzuoSingleRef.value.superRecordMap 405 const superMap = dongzuoSingleRef.value.superRecordMap
394 targetExercises.forEach(ex => { 406 targetExercises.forEach(ex => {
  407 + // 获得当前超级组的动作类型
  408 + const exerciseType = ex.exerciseType;
  409 + console.log('----------exerciseType---------', exerciseType);
  410 +
395 const setArr = superMap[ex.exerciseId] || [] 411 const setArr = superMap[ex.exerciseId] || []
396 ex.sets = setArr.map((item, idx) => { 412 ex.sets = setArr.map((item, idx) => {
397 - const duration = Number(item.h) * 3600 + Number(item.m) * 60 + Number(item.s) || 0  
398 - const rest = Number(item.quickTimeDisplay.replace('s', '')) || 0 413 + let duration = 0
  414 + let restTime = 0
  415 + // 子动作是间歇则取专属字段
  416 + if (exerciseType === 6) {
  417 + duration = Number(item.duration) || 0
  418 + restTime = Number(item.restTime) || 0
  419 + } else {
  420 + duration = Number(item.h) * 3600 + Number(item.m) * 60 + Number(item.s) || 0
  421 + restTime = Number(item.quickTimeDisplay.replace('s', '')) || 0
  422 + }
399 return { 423 return {
400 setIndex: idx + 1, 424 setIndex: idx + 1,
401 weight: Number(item.weight) || 0, 425 weight: Number(item.weight) || 0,
402 reps: Number(item.reps) || 0, 426 reps: Number(item.reps) || 0,
403 distance: Number(item.distance) || 0, 427 distance: Number(item.distance) || 0,
404 duration, 428 duration,
405 - restTime: rest, 429 + // restTime: rest,
  430 + restTime,
406 isCompleted: item.isActive ? 1 : 0 431 isCompleted: item.isActive ? 1 : 0
407 } 432 }
408 }) 433 })
@@ -473,7 +498,7 @@ defineExpose({ open }) @@ -473,7 +498,7 @@ defineExpose({ open })
473 // padding: 0 20rpx 120rpx; 498 // padding: 0 20rpx 120rpx;
474 margin: 0 20rpx; 499 margin: 0 20rpx;
475 height: calc(100vh - 120rpx); 500 height: calc(100vh - 120rpx);
476 - background: #f5f5f5; 501 + background: #242424;
477 max-height: 70vh; 502 max-height: 70vh;
478 } 503 }
479 </style> 504 </style>
@@ -43,6 +43,7 @@ @@ -43,6 +43,7 @@
43 <script setup> 43 <script setup>
44 import { ref, computed, watch } from 'vue'; 44 import { ref, computed, watch } from 'vue';
45 import QueryPlanApi from '@/sheep/api/plan/queryplan'; 45 import QueryPlanApi from '@/sheep/api/plan/queryplan';
  46 +import dailytemplateApi from '@/sheep/api/Template/Dailytemplate';
46 47
47 const props = defineProps({ 48 const props = defineProps({
48 planId: { type: [String, Number], required: false }, 49 planId: { type: [String, Number], required: false },
@@ -50,9 +51,11 @@ const props = defineProps({ @@ -50,9 +51,11 @@ const props = defineProps({
50 isMove: { 51 isMove: {
51 type: Boolean, 52 type: Boolean,
52 default: false 53 default: false
53 - } 54 + },
  55 + isCopy: { type: Boolean, default: false },
  56 + dailyTemplateId: { type: Number, default: 0 }
54 }); 57 });
55 -const emit = defineEmits(['success']); 58 +const emit = defineEmits(['success', 'clearCopyFlag']);
56 59
57 // 弹窗显示控制 60 // 弹窗显示控制
58 const showPopup = ref(false); 61 const showPopup = ref(false);
@@ -174,6 +177,7 @@ const close = () => { @@ -174,6 +177,7 @@ const close = () => {
174 // 重置到当前月份(可选) 177 // 重置到当前月份(可选)
175 currentYear.value = new Date().getFullYear(); 178 currentYear.value = new Date().getFullYear();
176 currentMonth.value = new Date().getMonth() + 1; 179 currentMonth.value = new Date().getMonth() + 1;
  180 + emit('clearCopyFlag')
177 }; 181 };
178 182
179 // 打开弹窗(暴露给父组件) 183 // 打开弹窗(暴露给父组件)
@@ -192,27 +196,56 @@ const confirmAdd = async () => { @@ -192,27 +196,56 @@ const confirmAdd = async () => {
192 return; 196 return;
193 } 197 }
194 try { 198 try {
195 - if (props.isMove) { 199 + // if (props.isMove) {
  200 + // const params = {
  201 + // id: props.templateId,
  202 + // trainingDate: selectedDates.value[0] || ''
  203 + // };
  204 + // // 添加到日历弹窗的参数
  205 + // console.log('移动到:params', params);
  206 + // await QueryPlanApi.updateDailyTemplateDate(params);
  207 + // uni.showToast({ title: '移动成功', icon: 'success' });
  208 + // } else {
  209 + // const params = {
  210 + // id: props.templateId,
  211 + // planId: props.planId,
  212 + // trainDateList: selectedDates.value
  213 + // };
  214 + // // 添加到日历弹窗的参数
  215 + // console.log('添加到params', params);
  216 + // await QueryPlanApi.addPlanToCalendar(params);
  217 + // uni.showToast({ title: '添加到日历成功', icon: 'success' });
  218 + // }
  219 + // ========== 新增复制分支 优先判断 ==========
  220 + console.log('rops.dailyTemplateId=', props.dailyTemplateId);
  221 + if (props.isCopy) {
  222 + // 调用你新增的复制接口
  223 + await dailytemplateApi.copyTempleteDailyTemplate(props.dailyTemplateId, selectedDates.value)
  224 + uni.showToast({ title: '复制成功', icon: 'success' });
  225 + console.log('调用复制接口成功');
  226 +
  227 + } else if (props.isMove) {
  228 + // 移动到
196 const params = { 229 const params = {
197 id: props.templateId, 230 id: props.templateId,
198 trainingDate: selectedDates.value[0] || '' 231 trainingDate: selectedDates.value[0] || ''
199 }; 232 };
200 - // 添加到日历弹窗的参数  
201 - console.log('移动到:params', params);  
202 await QueryPlanApi.updateDailyTemplateDate(params); 233 await QueryPlanApi.updateDailyTemplateDate(params);
203 uni.showToast({ title: '移动成功', icon: 'success' }); 234 uni.showToast({ title: '移动成功', icon: 'success' });
  235 + console.log('移动接口成功');
204 } else { 236 } else {
  237 + //添加到日历
205 const params = { 238 const params = {
206 id: props.templateId, 239 id: props.templateId,
207 planId: props.planId, 240 planId: props.planId,
208 trainDateList: selectedDates.value 241 trainDateList: selectedDates.value
209 }; 242 };
210 - // 添加到日历弹窗的参数  
211 - console.log('添加到params', params);  
212 await QueryPlanApi.addPlanToCalendar(params); 243 await QueryPlanApi.addPlanToCalendar(params);
213 uni.showToast({ title: '添加到日历成功', icon: 'success' }); 244 uni.showToast({ title: '添加到日历成功', icon: 'success' });
  245 + console.log('添加到接口成功');
214 } 246 }
215 247
  248 +
216 close(); 249 close();
217 emit('success'); 250 emit('success');
218 } catch (err) { 251 } catch (err) {
@@ -37,10 +37,10 @@ @@ -37,10 +37,10 @@
37 <scroll-view class="template-list" enable-flex scroll-y> 37 <scroll-view class="template-list" enable-flex scroll-y>
38 <!-- 用父容器包裹 v-if 分支,解决 key 冲突 --> 38 <!-- 用父容器包裹 v-if 分支,解决 key 冲突 -->
39 <view v-if="!isFiltering" class="sub-template-list"> 39 <view v-if="!isFiltering" class="sub-template-list">
40 - <view v-for="(item, index) in templateList" :key="index" class="template-item"> 40 + <view v-for="(item, index) in templateList" :key="index" class="template-item" @click="gototemplate(item)">
41 <image :src="item.urlCover" mode="aspectFill" class="template-img"></image> 41 <image :src="item.urlCover" mode="aspectFill" class="template-img"></image>
42 <view> 42 <view>
43 - <view class="template-content" @click="gototemplate(item)"> 43 + <view class="template-content">
44 <view class="template-title">{{ item.name }}</view> 44 <view class="template-title">{{ item.name }}</view>
45 <view class="template-count">{{ item.templatesCount }}个模板</view> 45 <view class="template-count">{{ item.templatesCount }}个模板</view>
46 <view class="template-desc">{{ item.description }}</view> 46 <view class="template-desc">{{ item.description }}</view>
@@ -10,13 +10,13 @@ @@ -10,13 +10,13 @@
10 <view class="title">动作库</view> 10 <view class="title">动作库</view>
11 <view class="desc">真人视频讲解</view> 11 <view class="desc">真人视频讲解</view>
12 </view> 12 </view>
13 - <view class="item"> 13 + <view class="item" @click="goToTrainPlan">
14 <image src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/22_1773626449583.png" 14 <image src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/22_1773626449583.png"
15 class="img"></image> 15 class="img"></image>
16 <view class="title">训练计划</view> 16 <view class="title">训练计划</view>
17 <view class="desc">周期性目标达成</view> 17 <view class="desc">周期性目标达成</view>
18 </view> 18 </view>
19 - <view class="item"> 19 + <view class="item" @click="goToTrainTemplate">
20 <image src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/23_1773627239158.png" 20 <image src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/23_1773627239158.png"
21 class="img"></image> 21 class="img"></image>
22 <view class="title">训练模板</view> 22 <view class="title">训练模板</view>
@@ -122,7 +122,7 @@ @@ -122,7 +122,7 @@
122 </template> 122 </template>
123 123
124 <script setup> 124 <script setup>
125 -import { ref, computed, onMounted } from 'vue'; 125 +import { ref, computed, onMounted, getCurrentInstance } from 'vue';
126 import QueryPlanApi from '@/sheep/api/plan/queryplan'; 126 import QueryPlanApi from '@/sheep/api/plan/queryplan';
127 import coachUpdatesApi from '@/sheep/api/Coach/CoachUpdates'; // 请确认路径正确 127 import coachUpdatesApi from '@/sheep/api/Coach/CoachUpdates'; // 请确认路径正确
128 import useUserStore from '@/sheep/store/user'; 128 import useUserStore from '@/sheep/store/user';
@@ -312,12 +312,55 @@ const handleDynamicLike = async (item, index) => { @@ -312,12 +312,55 @@ const handleDynamicLike = async (item, index) => {
312 console.log('点赞失败', err); 312 console.log('点赞失败', err);
313 } 313 }
314 }; 314 };
  315 +// const goXunjiTab = (tabIndex) => {
  316 +// uni.navigateTo({
  317 +// url: `/pages4/pages/xunji/xunji-main?currentTab=${tabIndex}`
  318 +// })
  319 +// }
  320 +
  321 +
315 // 实现动作库跳转,跳转到F:\hongxing-app\hongxing-new\pages\xunji\components\xunji-dongzuo.vue 322 // 实现动作库跳转,跳转到F:\hongxing-app\hongxing-new\pages\xunji\components\xunji-dongzuo.vue
  323 +// const goToActionLibrary = () => {
  324 +// uni.navigateTo({
  325 +// url: '/pages/xunji/components/xunji-dongzuo',
  326 +// });
  327 +// };
  328 +
  329 +const instance = getCurrentInstance()
  330 +// 获取父页面(pages/xunji/xunji.vue)
  331 +const parentPage = instance.parent
  332 +
  333 +// 切换训记Tab内部子页面(适配底部tabBar)
  334 +const switchXunjiTab = (tabIndex) => {
  335 + console.log('准备跳转训记Tab,目标索引:', tabIndex);
  336 + // 存入本地缓存,训记页面onShow读取
  337 + uni.setStorageSync('xunjiTargetTab', tabIndex);
  338 + // 修正路径:去掉开头斜杠,匹配tabBar标准格式
  339 + uni.switchTab({
  340 + url: 'pages/xunji/xunji',
  341 + success: () => {
  342 + console.log('switchTab切换训记页面成功');
  343 + },
  344 + fail: (err) => {
  345 + console.error('switchTab切换失败', err);
  346 + uni.removeStorageSync('xunjiTargetTab');
  347 + uni.showToast({ title: '跳转失败,请检查tabBar配置', icon: 'none' });
  348 + }
  349 + })
  350 +}
316 const goToActionLibrary = () => { 351 const goToActionLibrary = () => {
317 - uni.navigateTo({  
318 - url: '/pages/xunji/components/xunji-dongzuo',  
319 - });  
320 -}; 352 + parentPage?.exposed?.switchTabIndex(3)
  353 +}
  354 +
  355 +// 训练计划
  356 +const goToTrainPlan = () => {
  357 + parentPage?.exposed?.switchTabIndex(1)
  358 +}
  359 +// 训练模板
  360 +const goToTrainTemplate = () => {
  361 + parentPage?.exposed?.switchTabIndex(4)
  362 +}
  363 +
321 // 实现 364 // 实现
322 // 初始化 365 // 初始化
323 onMounted(() => { 366 onMounted(() => {
@@ -85,7 +85,7 @@ @@ -85,7 +85,7 @@
85 85
86 <script setup> 86 <script setup>
87 import { ref, shallowRef, computed, onMounted } from 'vue'; 87 import { ref, shallowRef, computed, onMounted } from 'vue';
88 -import { onLoad, onHide } from '@dcloudio/uni-app'; 88 +import { onLoad, onHide, onShow } from '@dcloudio/uni-app';
89 import useUserStore from '@/sheep/store/user'; 89 import useUserStore from '@/sheep/store/user';
90 import { getMenuButtonHeight, getTopSafeArea } from '@/utils/safeArea'; 90 import { getMenuButtonHeight, getTopSafeArea } from '@/utils/safeArea';
91 91
@@ -136,6 +136,18 @@ onLoad((options) => { @@ -136,6 +136,18 @@ onLoad((options) => {
136 } 136 }
137 }); 137 });
138 138
  139 +onShow(() => {
  140 + const targetTab = uni.getStorageSync('xunjiTargetTab');
  141 + if (targetTab !== '' && targetTab != null) {
  142 + const tabNum = Number(targetTab);
  143 + currentTab.value = tabNum;
  144 + handleTabClick(tabNum);
  145 + // 使用后清空缓存,避免下次进来自动跳转
  146 + uni.removeStorageSync('xunjiTargetTab');
  147 + }
  148 +
  149 +})
  150 +
139 // --- 原有业务方法补充与修复 --- 151 // --- 原有业务方法补充与修复 ---
140 152
141 // 打开抽屉 153 // 打开抽屉
@@ -186,6 +198,10 @@ const goWidgetLib = () => { @@ -186,6 +198,10 @@ const goWidgetLib = () => {
186 const goFeedback = () => { }; 198 const goFeedback = () => { };
187 const goUserGroup = () => { }; 199 const goUserGroup = () => { };
188 const goTutorial = () => { }; 200 const goTutorial = () => { };
  201 +
  202 +defineExpose({
  203 + switchTabIndex: handleTabClick
  204 +})
189 </script> 205 </script>
190 <style scoped lang="scss"> 206 <style scoped lang="scss">
191 .home-page { 207 .home-page {
@@ -19,7 +19,7 @@ @@ -19,7 +19,7 @@
19 {{ TemplateDetail?.equipmentsUsed }} 19 {{ TemplateDetail?.equipmentsUsed }}
20 </template> 20 </template>
21 <template v-else> 21 <template v-else>
22 - {{ TemplateDetail?.equipmentNames.join(',') }} 22 + {{ (TemplateDetail?.equipmentNames || []).join(',') }}
23 </template> 23 </template>
24 24
25 </text> 25 </text>
@@ -35,14 +35,14 @@ @@ -35,14 +35,14 @@
35 <!-- 主要训练部位 --> 35 <!-- 主要训练部位 -->
36 <text class="section-title">主要训练部位: </text> 36 <text class="section-title">主要训练部位: </text>
37 <text class="description"> 37 <text class="description">
38 - {{ TemplateDetail.primaryMuscleNames?.join(', ') || '暂无数据' }} 38 + {{ (TemplateDetail.primaryMuscleNames || []).join(', ') || '暂无数据' }}
39 </text> 39 </text>
40 <!-- 换行 --> 40 <!-- 换行 -->
41 <view class="divider"></view> 41 <view class="divider"></view>
42 <!-- 次要训练部位 --> 42 <!-- 次要训练部位 -->
43 <text class="section-title">次要训练部位: </text> 43 <text class="section-title">次要训练部位: </text>
44 <text class="description"> 44 <text class="description">
45 - {{ TemplateDetail.secondaryMuscleNames?.join(', ') || '暂无数据' }} 45 + {{ (TemplateDetail.secondaryMuscleNames || []).join(', ') || '暂无数据' }}
46 </text> 46 </text>
47 </view> 47 </view>
48 </view> 48 </view>
@@ -89,43 +89,6 @@ @@ -89,43 +89,6 @@
89 89
90 </view> 90 </view>
91 91
92 - <!-- 动作内容循环:只循环内容,不循环卡片 -->  
93 - <!-- <view v-for="(item, index) in unit.exercises" :key="index" class="exercise-item-inner">  
94 - <view class="exercise-header">  
95 - <view class="exercise-info">  
96 - <template v-if="unit.unitType === 2">  
97 - <text class="super-exercise-name">{{ item.exerciseName }}</text>  
98 - </template>  
99 - </view>  
100 - </view>  
101 -  
102 - <view v-if="item.sets && item.sets.length > 0" class="exercise-details">  
103 - <view v-for="(detail, i) in item.sets" :key="i" class="detail-row">  
104 - <view class="detail-label">{{ i + 1 }}</view>  
105 - <view class="sets-data">  
106 - <view class="detail-value" v-if="[0, 4, 5].includes(item.exerciseType)">  
107 - {{ detail.weight }}kg × {{ detail.reps }}次  
108 - </view>  
109 - <view class="detail-value" v-if="item.exerciseType === 1">  
110 - {{ detail.distance }}km x {{ formatSeconds(detail.duration) }}  
111 - </view>  
112 - <view class="detail-value" v-if="item.exerciseType === 2">  
113 - {{ detail.reps }}次  
114 - </view>  
115 - <view class="detail-value" v-if="item.exerciseType === 3">  
116 - {{ detail.duration }}次  
117 - </view>  
118 - <view class="detail-value" v-if="item.exerciseType === 6">  
119 - {{ detail.duration }}组 x {{ formatSeconds(detail.duration) }}  
120 - </view>  
121 - <view class="rest" v-if="detail.restTime">  
122 - {{ detail.restTime }}s  
123 - </view>  
124 - </view>  
125 - </view>  
126 - </view>  
127 - </view> -->  
128 -  
129 <!-- 情况1:普通动作 unitType = 1 --> 92 <!-- 情况1:普通动作 unitType = 1 -->
130 <view v-if="unit.unitType === 1 && unit.exercises && unit.exercises.length" class="normal-action-wrap"> 93 <view v-if="unit.unitType === 1 && unit.exercises && unit.exercises.length" class="normal-action-wrap">
131 <!-- 取第一个动作(普通动作unit里永远只有1个exercise) --> 94 <!-- 取第一个动作(普通动作unit里永远只有1个exercise) -->
@@ -238,7 +201,7 @@ @@ -238,7 +201,7 @@
238 <uni-icons type="refresh" size="22" color="#fff"></uni-icons> 201 <uni-icons type="refresh" size="22" color="#fff"></uni-icons>
239 <text class="item-text">删除此次排课</text> 202 <text class="item-text">删除此次排课</text>
240 </view> 203 </view>
241 - <view class="more-item" @click.stop="confirmDelete(plandetail.groupId)"> 204 + <view class="more-item" @click.stop="confirmDelete(TemplateDetail.planId)">
242 <uni-icons type="close" size="22" color="#fff"></uni-icons> 205 <uni-icons type="close" size="22" color="#fff"></uni-icons>
243 <text class="item-text">结束整个计划</text> 206 <text class="item-text">结束整个计划</text>
244 </view> 207 </view>
@@ -356,7 +319,7 @@ const loadtemplatedetail = async () => { @@ -356,7 +319,7 @@ const loadtemplatedetail = async () => {
356 } 319 }
357 TemplateDetail.value = res.data; 320 TemplateDetail.value = res.data;
358 TemplateUnits.value = res.data.units || []; 321 TemplateUnits.value = res.data.units || [];
359 - console.log('✅ 模板详情:', TemplateDetail.value); 322 + console.log('✅ TemplateDetail.value模板详情:', TemplateDetail.value);
360 console.log('✅ 模板unts详情:', TemplateUnits.value); 323 console.log('✅ 模板unts详情:', TemplateUnits.value);
361 324
362 } catch (err) { 325 } catch (err) {
@@ -486,6 +449,8 @@ const handleDeleteTemplate = async (dailyTemplateId) => { @@ -486,6 +449,8 @@ const handleDeleteTemplate = async (dailyTemplateId) => {
486 await dailytemplateApi.deleteDailyTemplate(dailyTemplateId); 449 await dailytemplateApi.deleteDailyTemplate(dailyTemplateId);
487 uni.showToast({ title: '删除成功', icon: 'success' }); 450 uni.showToast({ title: '删除成功', icon: 'success' });
488 // moreShow.value = false; // 关闭弹窗 451 // moreShow.value = false; // 关闭弹窗
  452 + // 返回上一页
  453 + setTimeout(() => uni.navigateBack(), 800)
489 } catch (err) { 454 } catch (err) {
490 console.error('删除失败', err); 455 console.error('删除失败', err);
491 uni.showToast({ title: '删除失败,请重试', icon: 'none' }); 456 uni.showToast({ title: '删除失败,请重试', icon: 'none' });
@@ -493,7 +458,7 @@ const handleDeleteTemplate = async (dailyTemplateId) => { @@ -493,7 +458,7 @@ const handleDeleteTemplate = async (dailyTemplateId) => {
493 } 458 }
494 }); 459 });
495 }; 460 };
496 - 461 +// 删除整个个人计划
497 const confirmDelete = async (id) => { 462 const confirmDelete = async (id) => {
498 try { 463 try {
499 console.log('打印计划个人id:', id); 464 console.log('打印计划个人id:', id);
@@ -503,7 +468,7 @@ const confirmDelete = async (id) => { @@ -503,7 +468,7 @@ const confirmDelete = async (id) => {
503 468
504 if (res.code === 0 && res.data) { 469 if (res.code === 0 && res.data) {
505 // 2. 删除成功,从列表移除 470 // 2. 删除成功,从列表移除
506 - planList.value = planList.value.filter(item => item.id !== id); 471 + // planList.value = planList.value.filter(item => item.id !== id);
507 uni.showToast({ title: '计划已结束', icon: 'success' }); 472 uni.showToast({ title: '计划已结束', icon: 'success' });
508 } else { 473 } else {
509 uni.showToast({ title: res.msg || '操作失败', icon: 'none' }); 474 uni.showToast({ title: res.msg || '操作失败', icon: 'none' });
@@ -512,8 +477,25 @@ const confirmDelete = async (id) => { @@ -512,8 +477,25 @@ const confirmDelete = async (id) => {
512 console.error('删除计划失败:', err); 477 console.error('删除计划失败:', err);
513 uni.showToast({ title: '网络请求失败', icon: 'none' }); 478 uni.showToast({ title: '网络请求失败', icon: 'none' });
514 } finally { 479 } finally {
515 - // 3. 关闭弹窗  
516 - activePlanId.value = null; 480 + {
  481 + setTimeout(() => {
  482 + // 获取页面栈
  483 + const pages = getCurrentPages();
  484 + // 这里返回上上个(我的计划列表页面)
  485 + const backNum = pages.length - 2;
  486 + if (backNum > 0) {
  487 + // 回退对应层数
  488 + uni.navigateBack({
  489 + delta: backNum
  490 + });
  491 + } else {
  492 + // 兜底:页面栈不足时防止报错,直接返回我的计划列表页面
  493 + uni.navigateTo({
  494 + url: '/pages4/pages/xunji/xunji-wode-jihua'
  495 + })
  496 + }
  497 + }, 1500)
  498 + }
517 } 499 }
518 }; 500 };
519 501
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
6 <view class="banner-section"> 6 <view class="banner-section">
7 <image :src="plandetail.urlCover" class="banner-img"> 7 <image :src="plandetail.urlCover" class="banner-img">
8 <!-- 收藏 --> 8 <!-- 收藏 -->
9 - <uni-icons :type="isPlanAdded ? 'heart-filled' : 'heart'" size="24" color="#fff" class="collect-btn" 9 + <uni-icons :type="isFavorite ? 'heart-filled' : 'heart'" size="24" color="#fff" class="collect-btn"
10 @click="toggleAddPlan"> 10 @click="toggleAddPlan">
11 </uni-icons> 11 </uni-icons>
12 <!-- 分享按钮 --> 12 <!-- 分享按钮 -->
@@ -126,8 +126,9 @@ @@ -126,8 +126,9 @@
126 126
127 <!-- 按钮区域 --> 127 <!-- 按钮区域 -->
128 <view class="button-group"> 128 <view class="button-group">
129 - <template v-if="!plandetail.isAdded && !isMyPlan">  
130 - <button type="default" @click="showPlanPopup = true" class="btn" size="large" 129 + <template v-if="!isPlanAdded && !isMyPlan">
  130 + <!-- 官方计划的底部按钮(未添加) -->
  131 + <button type="default" @click="openTrainingPopup" class="btn" size="large"
131 style="background-color: white; flex: 1"> 132 style="background-color: white; flex: 1">
132 单节课程训练 133 单节课程训练
133 </button> 134 </button>
@@ -136,18 +137,20 @@ @@ -136,18 +137,20 @@
136 加入计划 137 加入计划
137 </button> 138 </button>
138 </template> 139 </template>
139 - <template v-else-if="plandetail.isAdded && !isMyPlan"> 140 + <!-- 官方计划已添加 -->
  141 + <template v-else-if="isPlanAdded && !isMyPlan">
140 <button type="default" @click.stop="confirmDelete(plandetail.id)" class="btn end" size="large" 142 <button type="default" @click.stop="confirmDelete(plandetail.id)" class="btn end" size="large"
141 style="background-color: white; flex: 1"> 143 style="background-color: white; flex: 1">
142 结束计划 144 结束计划
143 </button> 145 </button>
144 - <button type="default" @click="showPlanPopup = true" class="btn ones" size="large" 146 + <button type="default" @click="openTrainingPopup" class="btn ones" size="large"
145 style="background-color: #ffd700; flex: 1.5"> 147 style="background-color: #ffd700; flex: 1.5">
146 马上开练 148 马上开练
147 </button> 149 </button>
148 </template> 150 </template>
  151 + <!-- 个人计划 -->
149 <template v-else> 152 <template v-else>
150 - <button type="default" class="btn end" @click="showPlanPopup = true" size="large" 153 + <button type="default" class="btn end" @click="openTrainingPopup" size="large"
151 style="background-color: white; flex: 1"> 154 style="background-color: white; flex: 1">
152 马上开练 155 马上开练
153 </button> 156 </button>
@@ -176,18 +179,18 @@ @@ -176,18 +179,18 @@
176 </view> 179 </view>
177 </template> 180 </template>
178 </view> 181 </view>
179 - <!-- 加入计划的弹窗 --> 182 + <!-- 加入计划/重新开始计划的弹窗 -->
180 <WeekSelectPopup v-model:visible="showWeekPopup" :max-count="plandetail.frequencyPerWeek" :plan-detail="plandetail" 183 <WeekSelectPopup v-model:visible="showWeekPopup" :max-count="plandetail.frequencyPerWeek" :plan-detail="plandetail"
181 - @success="loadDetail" :is-update="isUpdate" /> 184 + @success="loadDetailUpdate" :is-update="isUpdate" />
182 185
183 <!-- 添加到日历弹窗子组件 --> 186 <!-- 添加到日历弹窗子组件 -->
184 <AddToCalendarPopup ref="calendarPopupRef" :plan-id="plandetail.id" :template-id="currentTemplateId" 187 <AddToCalendarPopup ref="calendarPopupRef" :plan-id="plandetail.id" :template-id="currentTemplateId"
185 @success="handleCalendarSuccess" /> 188 @success="handleCalendarSuccess" />
186 189
187 - <!-- 去训练弹窗,显示当前计划的所有模板,可以直接跳转到训练页面 --> 190 + <!-- 去训练弹窗,显示当前计划的所有模板,可以直接跳转到训练页面,区分个人计划和官方计划(传递的是官方的计划id) -->
188 <template v-if="plandetail?.id"> 191 <template v-if="plandetail?.id">
189 <WodeJihuaTianjiaTancuang v-if="showPlanPopup" v-model:visible="showPlanPopup" :isAdd="false" 192 <WodeJihuaTianjiaTancuang v-if="showPlanPopup" v-model:visible="showPlanPopup" :isAdd="false"
190 - :plan-title="'选择课程立即开始训练'" :plan-id="plandetail.id" :is-my-plan="false"/> 193 + :plan-title="'选择课程立即开始训练'" :plan-id="trainingPlanId" :is-my-plan="false" />
191 </template> 194 </template>
192 195
193 </view> 196 </view>
@@ -211,8 +214,7 @@ const route = defineProps(['planid']); // Vue 3 + uni-app 支持 @@ -211,8 +214,7 @@ const route = defineProps(['planid']); // Vue 3 + uni-app 支持
211 const pages = getCurrentPages(); 214 const pages = getCurrentPages();
212 const currentPage = pages[pages.length - 1]; 215 const currentPage = pages[pages.length - 1];
213 const planIdFromUrl = currentPage.options?.planid; 216 const planIdFromUrl = currentPage.options?.planid;
214 -// 计划是否已添加(收藏)  
215 -const isPlanAdded = ref(false); 217 +
216 // const plandetail = ref([]); 218 // const plandetail = ref([]);
217 const plandetail = ref({}); 219 const plandetail = ref({});
218 //传参 220 //传参
@@ -248,23 +250,6 @@ const showPlanPopup = ref(false) @@ -248,23 +250,6 @@ const showPlanPopup = ref(false)
248 // 更多弹窗显隐 250 // 更多弹窗显隐
249 const showMorePopup = ref(false) 251 const showMorePopup = ref(false)
250 252
251 -  
252 -// 接口weeklySchedule转日历渲染数据  
253 -// const formatWeekData = (weekArr) => {  
254 -// const weekMap = { 1: '一', 2: '二', 3: '三', 4: '四', 5: '五', 6: '六', 7: '日' }  
255 -// return weekArr.map((item, index) => {  
256 -// const dayNum = item.date[2]  
257 -// let topStr = index === 0 ? '今' : weekMap[item.dayIndex]  
258 -// const trainStr = item.scheduled ? item.templateName : ''  
259 -// return {  
260 -// topText: topStr,  
261 -// num: String(dayNum),  
262 -// trainName: trainStr,  
263 -// templateId: item.templateId,  
264 -// dailytemplateId: item.dailytemplateId,  
265 -// }  
266 -// })  
267 -// }  
268 // 接口weeklySchedule转日历渲染数据(修复版:星期根据真实日期计算,100%对齐num) 253 // 接口weeklySchedule转日历渲染数据(修复版:星期根据真实日期计算,100%对齐num)
269 const formatWeekData = (weekArr) => { 254 const formatWeekData = (weekArr) => {
270 // JS星期:0=日,1=一,2=二,3=三,4=四,5=五,6=六 255 // JS星期:0=日,1=一,2=二,3=三,4=四,5=五,6=六
@@ -304,6 +289,17 @@ const restartPlan = () => { @@ -304,6 +289,17 @@ const restartPlan = () => {
304 console.log('重新开始计划,planId:', plandetail.value.id) 289 console.log('重新开始计划,planId:', plandetail.value.id)
305 uni.showToast({ title: '已重新开始计划', icon: 'success' }) 290 uni.showToast({ title: '已重新开始计划', icon: 'success' })
306 } 291 }
  292 +const trainingPlanId = ref(0)
  293 +const openTrainingPopup = () => {
  294 + showPlanPopup.value = true;
  295 + if (isMyPlan.value) {
  296 + trainingPlanId.value = plandetail.value.planId
  297 +
  298 + } else {
  299 + trainingPlanId.value = plandetail.value.id
  300 + }
  301 + console.log('trainingPlanId=', trainingPlanId.value);
  302 +}
307 303
308 // 跳转到排课页面 304 // 跳转到排课页面
309 const openArrageClass = () => { 305 const openArrageClass = () => {
@@ -365,10 +361,11 @@ const confirmDelete = async (id) => { @@ -365,10 +361,11 @@ const confirmDelete = async (id) => {
365 361
366 if (res.code === 0 && res.data) { 362 if (res.code === 0 && res.data) {
367 uni.showToast({ title: '计划已结束', icon: 'success' }); 363 uni.showToast({ title: '计划已结束', icon: 'success' });
368 - 364 + if (isMyPlan.value) {
369 setTimeout(() => { 365 setTimeout(() => {
370 uni.navigateBack() 366 uni.navigateBack()
371 }, 1500) 367 }, 1500)
  368 + }
372 } else { 369 } else {
373 uni.showToast({ title: res.msg || '操作失败', icon: 'none' }); 370 uni.showToast({ title: res.msg || '操作失败', icon: 'none' });
374 } 371 }
@@ -376,8 +373,11 @@ const confirmDelete = async (id) => { @@ -376,8 +373,11 @@ const confirmDelete = async (id) => {
376 console.error('删除计划失败:', err); 373 console.error('删除计划失败:', err);
377 uni.showToast({ title: '网络请求失败', icon: 'none' }); 374 uni.showToast({ title: '网络请求失败', icon: 'none' });
378 } finally { 375 } finally {
  376 + if (!isMyPlan.value) {
379 loadDetail(); 377 loadDetail();
380 } 378 }
  379 +
  380 + }
381 } 381 }
382 // 用户点取消 → 什么都不做 382 // 用户点取消 → 什么都不做
383 else if (res.cancel) { 383 else if (res.cancel) {
@@ -433,37 +433,55 @@ const loadDetail = async () => { @@ -433,37 +433,55 @@ const loadDetail = async () => {
433 } 433 }
434 }; 434 };
435 435
436 -// ====================== 计划收藏/添加功能 ====================== 436 +const loadDetailUpdate = () => {
  437 + loadDetail();
  438 + isAddToMyPlanList();
  439 +}
437 440
438 -// 检查当前计划是否已经添加  
439 -const checkPlanAddedStatus = async () => { 441 +// // 检查当前计划是否已经添加到我的计划列表
  442 +// 计划是否已添加
  443 +const isPlanAdded = ref(false);
  444 +const isAddToMyPlanList = async () => {
440 if (!id.value) return; 445 if (!id.value) return;
441 try { 446 try {
442 - const res = await QueryPlanApi.isAddPlan(id.value);  
443 - // 接口返回 true = 已添加 447 + const res = await QueryPlanApi.isAddPlan(Number(id.value));
  448 + // 后端返回布尔值,赋值状态
444 isPlanAdded.value = res.data === true; 449 isPlanAdded.value = res.data === true;
445 - console.log("计划是否已添加:", isPlanAdded.value); 450 + console.log("当前计划是否已加入我的计划:", isPlanAdded.value);
446 } catch (err) { 451 } catch (err) {
447 - console.error("检查计划添加状态失败:", err); 452 + console.error("查询是否加入计划失败:", err);
448 isPlanAdded.value = false; 453 isPlanAdded.value = false;
449 } 454 }
450 }; 455 };
451 456
  457 +const isFavorite = ref(false)
  458 +// 检查当前计划是否已经收藏
  459 +const checkPlanAddedStatus = async () => {
  460 + if (!id.value) return;
  461 + try {
  462 + const res = await QueryPlanApi.checkFavorite(id.value);
  463 + console.log('计划是否已经收藏:', res);
  464 + isFavorite.value = res.data === true;
  465 + console.log("计划是否已经收藏:", isFavorite.value);
  466 + } catch (err) {
  467 + console.error("检查计划收藏状态失败:", err);
  468 + isFavorite.value = false;
  469 + }
  470 +};
  471 +
452 // 切换添加/取消收藏计划 472 // 切换添加/取消收藏计划
453 const toggleAddPlan = async () => { 473 const toggleAddPlan = async () => {
454 if (!id.value) { 474 if (!id.value) {
455 uni.showToast({ title: "计划ID不存在", icon: "none" }); 475 uni.showToast({ title: "计划ID不存在", icon: "none" });
456 return; 476 return;
457 } 477 }
458 -  
459 - const originalStatus = isPlanAdded.value;  
460 - // 乐观更新UI(点了立刻变爱心)  
461 - isPlanAdded.value = !originalStatus;  
462 - 478 + const originalStatus = isFavorite.value;
  479 + // 点了立刻变爱心)
  480 + isFavorite.value = !originalStatus;
463 try { 481 try {
464 // 调用添加计划接口 482 // 调用添加计划接口
465 - await QueryPlanApi.addPlan(id.value);  
466 - 483 + const params = isFavorite.value ? 1 : 0;
  484 + await QueryPlanApi.toggleFavorite(Number(id.value), params);
467 if (!originalStatus) { 485 if (!originalStatus) {
468 uni.showToast({ title: "收藏计划成功" }); 486 uni.showToast({ title: "收藏计划成功" });
469 } else { 487 } else {
@@ -471,7 +489,7 @@ const toggleAddPlan = async () => { @@ -471,7 +489,7 @@ const toggleAddPlan = async () => {
471 } 489 }
472 } catch (err) { 490 } catch (err) {
473 // 失败回滚 491 // 失败回滚
474 - isPlanAdded.value = originalStatus; 492 + isFavorite.value = originalStatus;
475 console.error("操作失败:", err); 493 console.error("操作失败:", err);
476 uni.showToast({ title: "操作失败", icon: "none" }); 494 uni.showToast({ title: "操作失败", icon: "none" });
477 } 495 }
@@ -491,6 +509,7 @@ const openCalendarPopup = (planId, templateId) => { @@ -491,6 +509,7 @@ const openCalendarPopup = (planId, templateId) => {
491 } 509 }
492 }; 510 };
493 511
  512 +
494 // 日历添加成功的回调(可选,可做刷新/提示) 513 // 日历添加成功的回调(可选,可做刷新/提示)
495 const handleCalendarSuccess = () => { 514 const handleCalendarSuccess = () => {
496 console.log('课程已成功添加到日历'); 515 console.log('课程已成功添加到日历');
@@ -512,7 +531,8 @@ onLoad((options) => { @@ -512,7 +531,8 @@ onLoad((options) => {
512 console.log("计划详情接收的参数:", options); 531 console.log("计划详情接收的参数:", options);
513 id.value = options.planid; 532 id.value = options.planid;
514 isMyPlan.value = options.isMyPlan === "true"; 533 isMyPlan.value = options.isMyPlan === "true";
515 - checkPlanAddedStatus(); // 检查计划是否已添加 534 + checkPlanAddedStatus(); // 检查计划是否已收藏
  535 + isAddToMyPlanList(); // 新增:查询是否加入我的计划
516 // #ifdef MP-WEIXIN 536 // #ifdef MP-WEIXIN
517 wx.showShareMenu({ 537 wx.showShareMenu({
518 withShareTicket: true, 538 withShareTicket: true,
@@ -69,6 +69,17 @@ const dailytemplateApi = { @@ -69,6 +69,17 @@ const dailytemplateApi = {
69 data, 69 data,
70 }); 70 });
71 }, 71 },
  72 + // 复制每日模板
  73 + copyTempleteDailyTemplate: (sourceTemplateId,targetDates) => {
  74 + return request({
  75 + url: `/app/daily/template/copy`,
  76 + method: 'POST',
  77 + data:{
  78 + sourceTemplateId,
  79 + targetDates
  80 + }
  81 + });
  82 + },
72 }; 83 };
73 84
74 export default dailytemplateApi; 85 export default dailytemplateApi;
@@ -162,7 +162,7 @@ const QueryPlanApi = { @@ -162,7 +162,7 @@ const QueryPlanApi = {
162 }); 162 });
163 }, 163 },
164 164
165 - // 移动到 165 + // 移动到/复制到
166 updateDailyTemplateDate: (data) => { 166 updateDailyTemplateDate: (data) => {
167 return request({ 167 return request({
168 url: '/app/plan/updateDailyTemplateDate', 168 url: '/app/plan/updateDailyTemplateDate',
@@ -227,7 +227,8 @@ export const useTrainingStore = defineStore('training', { @@ -227,7 +227,8 @@ export const useTrainingStore = defineStore('training', {
227 return { 227 return {
228 id: act.exerciseId, 228 id: act.exerciseId,
229 name: act.exerciseName, 229 name: act.exerciseName,
230 - urlImage: act.exerciseCover || act.url3dAnimation || act.urlImage, 230 + // urlImage: act.exerciseCover || act.url3dAnimation || act.urlImage,
  231 + urlImage: act.url3dAnimation || act.exerciseCover || act.urlImage,
231 // urlImage 232 // urlImage
232 exerciseType: act.exerciseType, 233 exerciseType: act.exerciseType,
233 categoryDescription: act.categoryDescription, 234 categoryDescription: act.categoryDescription,