Showing
16 changed files
with
1121 additions
and
1021 deletions
Too many changes to show.
To preserve performance only 16 of 16+ files are displayed.
| @@ -58,7 +58,8 @@ | @@ -58,7 +58,8 @@ | ||
| 58 | { | 58 | { |
| 59 | "path": "pages/xunji/xunji-wode-moban", | 59 | "path": "pages/xunji/xunji-wode-moban", |
| 60 | "style": { | 60 | "style": { |
| 61 | - "navigationBarTitleText": "我的模版" | 61 | + "navigationBarTitleText": "我的模版", |
| 62 | + "navigationStyle": "default" | ||
| 62 | } | 63 | } |
| 63 | }, | 64 | }, |
| 64 | { | 65 | { |
| @@ -82,7 +83,7 @@ | @@ -82,7 +83,7 @@ | ||
| 82 | { | 83 | { |
| 83 | "path": "pages/xunji/xunji-dongzuo-xinzeng", | 84 | "path": "pages/xunji/xunji-dongzuo-xinzeng", |
| 84 | "style": { | 85 | "style": { |
| 85 | - "navigationBarTitleText": "", | 86 | + "navigationBarTitleText": "新增自定义动作", |
| 86 | "navigationStyle": "default" | 87 | "navigationStyle": "default" |
| 87 | } | 88 | } |
| 88 | }, | 89 | }, |
| @@ -180,13 +181,14 @@ | @@ -180,13 +181,14 @@ | ||
| 180 | { | 181 | { |
| 181 | "path": "pages/user/wode-changjian-wenti", | 182 | "path": "pages/user/wode-changjian-wenti", |
| 182 | "style": { | 183 | "style": { |
| 183 | - "navigationBarTitleText": "我的常见问题" | 184 | + "navigationBarTitleText": "常见问题", |
| 185 | + "navigationStyle": "default" | ||
| 184 | } | 186 | } |
| 185 | }, | 187 | }, |
| 186 | { | 188 | { |
| 187 | "path": "pages/user/wode-guanyu-hongxing", | 189 | "path": "pages/user/wode-guanyu-hongxing", |
| 188 | "style": { | 190 | "style": { |
| 189 | - "navigationBarTitleText": "我的关于鸿星" | 191 | + "navigationBarTitleText": "关于我们" |
| 190 | } | 192 | } |
| 191 | }, | 193 | }, |
| 192 | { | 194 | { |
| @@ -8,7 +8,7 @@ | @@ -8,7 +8,7 @@ | ||
| 8 | <view @click="show = false"> | 8 | <view @click="show = false"> |
| 9 | <up-icon name="close" color="#fff" size="20"></up-icon> | 9 | <up-icon name="close" color="#fff" size="20"></up-icon> |
| 10 | </view> | 10 | </view> |
| 11 | - <text class="titleName">动作备注</text> | 11 | + <text class="titleName">{{ title }}</text> |
| 12 | <view class="btn" @click="saveNoteContent">保存</view> | 12 | <view class="btn" @click="saveNoteContent">保存</view> |
| 13 | </view> | 13 | </view> |
| 14 | <view class="area-box"> | 14 | <view class="area-box"> |
| @@ -30,6 +30,10 @@ const tempNoteContent = ref(''); // 临时编辑的备注内容 | @@ -30,6 +30,10 @@ const tempNoteContent = ref(''); // 临时编辑的备注内容 | ||
| 30 | const emit = defineEmits(['saveSuccess']); | 30 | const emit = defineEmits(['saveSuccess']); |
| 31 | 31 | ||
| 32 | const props = defineProps({ | 32 | const props = defineProps({ |
| 33 | + title: { | ||
| 34 | + type: String, | ||
| 35 | + default: '动作备注' | ||
| 36 | + }, | ||
| 33 | oldNote: { | 37 | oldNote: { |
| 34 | type: String, | 38 | type: String, |
| 35 | default: '' | 39 | default: '' |
| @@ -185,7 +185,7 @@ | @@ -185,7 +185,7 @@ | ||
| 185 | ====================================== --> | 185 | ====================================== --> |
| 186 | <view class="icons"> | 186 | <view class="icons"> |
| 187 | <template v-if="!isEditing"> | 187 | <template v-if="!isEditing"> |
| 188 | - <view class="icon" @click.stop="emitToggleActive(index)" :class="{ active: record.isActive }"> | 188 | + <view class="icon" v-if="isTick" @click.stop="emitToggleActive(index)" :class="{ active: record.isActive }"> |
| 189 | <up-icon name="checkmark" :color="record.isActive ? '#000' : '#919191'" size="20" bold></up-icon> | 189 | <up-icon name="checkmark" :color="record.isActive ? '#000' : '#919191'" size="20" bold></up-icon> |
| 190 | </view> | 190 | </view> |
| 191 | </template> | 191 | </template> |
| @@ -210,6 +210,10 @@ const props = defineProps({ | @@ -210,6 +210,10 @@ const props = defineProps({ | ||
| 210 | userWeight: { | 210 | userWeight: { |
| 211 | type: Number, | 211 | type: Number, |
| 212 | default: 70 | 212 | default: 70 |
| 213 | + }, | ||
| 214 | + isTick: { | ||
| 215 | + type: Boolean, | ||
| 216 | + default: true | ||
| 213 | } | 217 | } |
| 214 | }) | 218 | }) |
| 215 | 219 |
| @@ -51,7 +51,7 @@ | @@ -51,7 +51,7 @@ | ||
| 51 | <action-set-input v-for="(item, index) in recordList" :key="index" | 51 | <action-set-input v-for="(item, index) in recordList" :key="index" |
| 52 | :exercise-type="actionDetail.exerciseType" :record="item" :index="index" :is-editing="isEditing" | 52 | :exercise-type="actionDetail.exerciseType" :record="item" :index="index" :is-editing="isEditing" |
| 53 | @openPicker="openPicker" @openQuickTimer="openQuickTimer" @toggleActive="toggleActive" @addRow="addRow" | 53 | @openPicker="openPicker" @openQuickTimer="openQuickTimer" @toggleActive="toggleActive" @addRow="addRow" |
| 54 | - @deleteRow="deleteRow" :user-weight="userWeight" @change-weight="userWeight = $event" /> | 54 | + @deleteRow="deleteRow" :user-weight="userWeight" @change-weight="userWeight = $event" :isTick="isTick"/> |
| 55 | </template> | 55 | </template> |
| 56 | 56 | ||
| 57 | <template v-else-if="type === 2"> | 57 | <template v-else-if="type === 2"> |
| @@ -97,7 +97,7 @@ | @@ -97,7 +97,7 @@ | ||
| 97 | </view> | 97 | </view> |
| 98 | <super-set-input v-for="(sub, idx) in actionDetail.exercises" :key="sub.id" | 98 | <super-set-input v-for="(sub, idx) in actionDetail.exercises" :key="sub.id" |
| 99 | :sub-exercise-type="sub.exerciseType" :set-index="setIndex" :sub-index="idx" :user-weight="userWeight" | 99 | :sub-exercise-type="sub.exerciseType" :set-index="setIndex" :sub-index="idx" :user-weight="userWeight" |
| 100 | - :data="getSubActionData(setIndex, sub.id)" @open-time-picker="openSuperPicker(setIndex, sub.id)" /> | 100 | + :data="getSubActionData(setIndex, sub.id)" @open-time-picker="openSuperPicker(setIndex, sub.id)" :isTick="isTick"/> |
| 101 | </view> | 101 | </view> |
| 102 | </view> | 102 | </view> |
| 103 | </template> | 103 | </template> |
| @@ -171,7 +171,8 @@ const props = defineProps({ | @@ -171,7 +171,8 @@ const props = defineProps({ | ||
| 171 | unitIndex: Number, | 171 | unitIndex: Number, |
| 172 | actionDetail: { type: Object, default: () => ({}) }, | 172 | actionDetail: { type: Object, default: () => ({}) }, |
| 173 | isDailyTemplates: { type: Boolean, default: false }, | 173 | isDailyTemplates: { type: Boolean, default: false }, |
| 174 | - isUnitActive: { type: Boolean, default: false } | 174 | + isUnitActive: { type: Boolean, default: false }, |
| 175 | + isTick: { type: Boolean, default: true }, | ||
| 175 | }) | 176 | }) |
| 176 | 177 | ||
| 177 | // ===================== Emits ===================== | 178 | // ===================== Emits ===================== |
| @@ -400,19 +401,22 @@ function selectDifficulty(label, color) { | @@ -400,19 +401,22 @@ function selectDifficulty(label, color) { | ||
| 400 | 401 | ||
| 401 | function toggleActive(index) { | 402 | function toggleActive(index) { |
| 402 | const wasActive = recordList.value[index].isActive | 403 | const wasActive = recordList.value[index].isActive |
| 404 | + // 在状态改变前获取当前组的休息秒数,用于取消时传递给父组件 | ||
| 405 | + const seconds = parseQuickTime(recordList.value[index].quickTimeDisplay) | ||
| 403 | recordList.value[index].isActive = !recordList.value[index].isActive | 406 | recordList.value[index].isActive = !recordList.value[index].isActive |
| 404 | const hasAnyActive = recordList.value.some(item => item.isActive) | 407 | const hasAnyActive = recordList.value.some(item => item.isActive) |
| 405 | emit('updateUnitActive', hasAnyActive) | 408 | emit('updateUnitActive', hasAnyActive) |
| 406 | if (recordList.value[index].isActive) { | 409 | if (recordList.value[index].isActive) { |
| 407 | - const seconds = parseQuickTime(recordList.value[index].quickTimeDisplay) | ||
| 408 | emit('startRestTimer', seconds, props.unitIndex) | 410 | emit('startRestTimer', seconds, props.unitIndex) |
| 409 | } else if (wasActive) { | 411 | } else if (wasActive) { |
| 410 | - emit('cancelRestTimer', props.unitIndex) | 412 | + emit('cancelRestTimer', seconds, props.unitIndex) |
| 411 | } | 413 | } |
| 412 | } | 414 | } |
| 413 | 415 | ||
| 414 | function toggleSuperSetActive(setIndex) { | 416 | function toggleSuperSetActive(setIndex) { |
| 415 | const wasActive = superGroupSetData(setIndex).isActive | 417 | const wasActive = superGroupSetData(setIndex).isActive |
| 418 | + // 在状态改变前获取当前组的休息秒数,用于取消时传递给父组件 | ||
| 419 | + const restSeconds = parseQuickTime(superGroupSetData(setIndex).quickTimeDisplay) | ||
| 416 | props.actionDetail.exercises.forEach(sub => { | 420 | props.actionDetail.exercises.forEach(sub => { |
| 417 | const list = superRecordMap.value[sub.id] | 421 | const list = superRecordMap.value[sub.id] |
| 418 | if (list?.[setIndex]) list[setIndex].isActive = !list[setIndex].isActive | 422 | if (list?.[setIndex]) list[setIndex].isActive = !list[setIndex].isActive |
| @@ -422,10 +426,9 @@ function toggleSuperSetActive(setIndex) { | @@ -422,10 +426,9 @@ function toggleSuperSetActive(setIndex) { | ||
| 422 | ) | 426 | ) |
| 423 | emit('updateUnitActive', hasAnyActive) | 427 | emit('updateUnitActive', hasAnyActive) |
| 424 | if (!wasActive) { | 428 | if (!wasActive) { |
| 425 | - const restSeconds = parseQuickTime(superGroupSetData(setIndex).quickTimeDisplay) | ||
| 426 | emit('startRestTimer', restSeconds, props.unitIndex) | 429 | emit('startRestTimer', restSeconds, props.unitIndex) |
| 427 | } else { | 430 | } else { |
| 428 | - emit('cancelRestTimer', props.unitIndex) | 431 | + emit('cancelRestTimer', restSeconds, props.unitIndex) |
| 429 | } | 432 | } |
| 430 | } | 433 | } |
| 431 | 434 |
| @@ -119,9 +119,6 @@ | @@ -119,9 +119,6 @@ | ||
| 119 | <view class="memo-box" v-if="type === 1"> | 119 | <view class="memo-box" v-if="type === 1"> |
| 120 | <view class="memo-title"> | 120 | <view class="memo-title"> |
| 121 | <view class="section-title">步骤</view> | 121 | <view class="section-title">步骤</view> |
| 122 | - <view class="pencil-icon"> | ||
| 123 | - <up-icon name="edit-pen" color="#fedc1f" size="20"></up-icon> | ||
| 124 | - </view> | ||
| 125 | </view> | 122 | </view> |
| 126 | <view class="steps-list"> | 123 | <view class="steps-list"> |
| 127 | <rich-text class="step-text" :nodes="actionDetail.stepDescription"></rich-text> | 124 | <rich-text class="step-text" :nodes="actionDetail.stepDescription"></rich-text> |
| @@ -158,9 +155,9 @@ | @@ -158,9 +155,9 @@ | ||
| 158 | <view class="section-title">训练部位</view> | 155 | <view class="section-title">训练部位</view> |
| 159 | <view class="muscle-card"> | 156 | <view class="muscle-card"> |
| 160 | <image | 157 | <image |
| 161 | - src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260612/muscle_back_1781252170914.png" | 158 | + src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260724/训练部位_1784878616826.jpg" |
| 162 | mode="widthFix" class="muscle-map" /> | 159 | mode="widthFix" class="muscle-map" /> |
| 163 | - <view class="legend"> | 160 | + <!-- <view class="legend"> |
| 164 | <view class="legend-item"> | 161 | <view class="legend-item"> |
| 165 | <view class="legend-dot primary"></view> | 162 | <view class="legend-dot primary"></view> |
| 166 | <text>主要部位</text> | 163 | <text>主要部位</text> |
| @@ -168,8 +165,8 @@ | @@ -168,8 +165,8 @@ | ||
| 168 | <view class="legend-item"> | 165 | <view class="legend-item"> |
| 169 | <view class="legend-dot secondary"></view> | 166 | <view class="legend-dot secondary"></view> |
| 170 | <text>次要部位</text> | 167 | <text>次要部位</text> |
| 171 | - </view> | ||
| 172 | - </view> | 168 | + </view> --> |
| 169 | + <!-- </view> --> | ||
| 173 | </view> | 170 | </view> |
| 174 | <view class="muscle-tags"> | 171 | <view class="muscle-tags"> |
| 175 | <view class="muscle-group" v-if="actionDetail.primaryMuscleNames?.length"> | 172 | <view class="muscle-group" v-if="actionDetail.primaryMuscleNames?.length"> |
| @@ -768,12 +765,14 @@ defineExpose({ open }); | @@ -768,12 +765,14 @@ defineExpose({ open }); | ||
| 768 | background: #464646; | 765 | background: #464646; |
| 769 | box-sizing: border-box; | 766 | box-sizing: border-box; |
| 770 | border-radius: 16rpx; | 767 | border-radius: 16rpx; |
| 768 | + padding: 24rpx; | ||
| 771 | 769 | ||
| 772 | .step-text { | 770 | .step-text { |
| 773 | font-size: 30rpx; | 771 | font-size: 30rpx; |
| 774 | line-height: 1.6; | 772 | line-height: 1.6; |
| 775 | color: #bbb; | 773 | color: #bbb; |
| 776 | - list-style: none; | 774 | + white-space: pre-wrap; |
| 775 | + word-break: break-all; | ||
| 777 | } | 776 | } |
| 778 | } | 777 | } |
| 779 | 778 |
| 1 | <template> | 1 | <template> |
| 2 | <!-- 添加自助训练底部弹窗 --> | 2 | <!-- 添加自助训练底部弹窗 --> |
| 3 | - <view v-if="visible" class="popup-overlay" @click="close"> | ||
| 4 | - <view class="popup-bottom" @click.stop> | ||
| 5 | - <view class="popup-options"> | ||
| 6 | - <view class="popup-option" @click="handleAddFromPlan" v-if="planListCount > 0"> | ||
| 7 | - <view class=" icon-wrap"> | ||
| 8 | - <!-- <image src="/static/icons/plan.png" mode="aspectFit"></image> --> | ||
| 9 | - <up-icon name="file-text-fill" color="#040000" size="24"></up-icon> | 3 | + <up-popup |
| 4 | + :show="visible" | ||
| 5 | + mode="bottom" | ||
| 6 | + round="24rpx" | ||
| 7 | + bg-color="#1a1a1a" | ||
| 8 | + :close-on-click-overlay="true" | ||
| 9 | + :safe-area-inset-bottom="true" | ||
| 10 | + @close="close" | ||
| 11 | + > | ||
| 12 | + <view class="add-train-popup"> | ||
| 13 | + <!-- 拖拽指示条 --> | ||
| 14 | + <view class="popup-indicator" /> | ||
| 15 | + | ||
| 16 | + <!-- 菜单选项列表 --> | ||
| 17 | + <view class="popup-menu"> | ||
| 18 | + <!-- 从训练计划中添加 --> | ||
| 19 | + <view | ||
| 20 | + v-if="planListCount > 0" | ||
| 21 | + class="menu-item" | ||
| 22 | + @click="handleAddFromPlan" | ||
| 23 | + > | ||
| 24 | + <view class="menu-icon"> | ||
| 25 | + <up-icon name="file-text-fill" color="#040000" size="24" /> | ||
| 10 | </view> | 26 | </view> |
| 11 | - <view class="text-wrap" @click=""> | ||
| 12 | - <text class="option-title">从训练计划中添加</text> | ||
| 13 | - <text class="option-desc">从我的训练计划中添加</text> | 27 | + <view class="menu-text"> |
| 28 | + <text class="menu-title">从训练计划中添加</text> | ||
| 29 | + <text class="menu-desc">从我的训练计划中添加</text> | ||
| 14 | </view> | 30 | </view> |
| 15 | </view> | 31 | </view> |
| 16 | 32 | ||
| 17 | - <view class="popup-option" @click="handleAddFromTemplate"> | ||
| 18 | - <view class="icon-wrap"> | ||
| 19 | - <up-icon name="grid-fill" color="#040000" size="24"></up-icon> | 33 | + <!-- 使用训练模板 --> |
| 34 | + <view class="menu-item" @click="handleAddFromTemplate"> | ||
| 35 | + <view class="menu-icon"> | ||
| 36 | + <up-icon name="grid-fill" color="#040000" size="24" /> | ||
| 20 | </view> | 37 | </view> |
| 21 | - <view class="text-wrap"> | ||
| 22 | - <text class="option-title">使用训练模板</text> | ||
| 23 | - <text class="option-desc">官方训练模板和自定义模板</text> | 38 | + <view class="menu-text"> |
| 39 | + <text class="menu-title">使用训练模板</text> | ||
| 40 | + <text class="menu-desc">官方训练模板和自定义模板</text> | ||
| 24 | </view> | 41 | </view> |
| 25 | </view> | 42 | </view> |
| 26 | 43 | ||
| 27 | - <view class="popup-option" @click="handleFreeTraining"> | ||
| 28 | - <view class="icon-wrap"> | ||
| 29 | - <up-icon name="clock-fill" color="#040000" size="24"></up-icon> | 44 | + <!-- 自由训练 --> |
| 45 | + <view class="menu-item" @click="handleFreeTraining"> | ||
| 46 | + <view class="menu-icon"> | ||
| 47 | + <up-icon name="clock-fill" color="#040000" size="24" /> | ||
| 30 | </view> | 48 | </view> |
| 31 | - <view class="text-wrap"> | ||
| 32 | - <text class="option-title">自由训练</text> | ||
| 33 | - <text class="option-desc">新建空白训练</text> | 49 | + <view class="menu-text"> |
| 50 | + <text class="menu-title">自由训练</text> | ||
| 51 | + <text class="menu-desc">新建空白训练</text> | ||
| 34 | </view> | 52 | </view> |
| 35 | </view> | 53 | </view> |
| 36 | </view> | 54 | </view> |
| 37 | 55 | ||
| 56 | + <!-- 关闭按钮 --> | ||
| 38 | <view class="close-btn" @click="close"> | 57 | <view class="close-btn" @click="close"> |
| 39 | - <text>x</text> | 58 | + <up-icon name="close" color="#000" size="22" /> |
| 40 | </view> | 59 | </view> |
| 41 | </view> | 60 | </view> |
| 42 | - <!-- 我的计划添加弹窗,显示最新添加的计划的模板列表 传递--> | ||
| 43 | - <WodeJihuaTianjiaTancuang v-model:visible="showPlanPopup" @get-plan-list-length="getPlanListLength" :isAdd="true" | ||
| 44 | - :plan-id="lastPlanId" :is-my-plan="true" /> | ||
| 45 | - </view> | 61 | + </up-popup> |
| 62 | + | ||
| 63 | + <!-- 我的计划添加弹窗(放在外部避免弹窗嵌套) --> | ||
| 64 | + <WodeJihuaTianjiaTancuang | ||
| 65 | + v-model:visible="showPlanPopup" | ||
| 66 | + @get-plan-list-length="getPlanListLength" | ||
| 67 | + :is-add="true" | ||
| 68 | + :plan-id="lastPlanId" | ||
| 69 | + :is-my-plan="true" | ||
| 70 | + /> | ||
| 46 | </template> | 71 | </template> |
| 47 | 72 | ||
| 48 | <script setup> | 73 | <script setup> |
| @@ -50,50 +75,57 @@ import { ref, onMounted, computed } from 'vue' | @@ -50,50 +75,57 @@ import { ref, onMounted, computed } from 'vue' | ||
| 50 | import WodeJihuaTianjiaTancuang from '@/pages/xunji/components/wode-jihua-tianjia-tancuang.vue' | 75 | import WodeJihuaTianjiaTancuang from '@/pages/xunji/components/wode-jihua-tianjia-tancuang.vue' |
| 51 | import QueryPlanApi from '@/sheep/api/plan/queryplan' | 76 | import QueryPlanApi from '@/sheep/api/plan/queryplan' |
| 52 | 77 | ||
| 53 | - | ||
| 54 | -// 控制训练计划弹窗显示 | ||
| 55 | -const showPlanPopup = ref(false) | ||
| 56 | -const planListCount = ref(0) | ||
| 57 | - | 78 | +// ==================== Props & Emits ==================== |
| 58 | const props = defineProps({ | 79 | const props = defineProps({ |
| 59 | visible: { | 80 | visible: { |
| 60 | type: Boolean, | 81 | type: Boolean, |
| 61 | - default: false | ||
| 62 | - } | 82 | + default: false, |
| 83 | + }, | ||
| 63 | }) | 84 | }) |
| 64 | 85 | ||
| 65 | const emit = defineEmits(['update:visible']) | 86 | const emit = defineEmits(['update:visible']) |
| 66 | 87 | ||
| 67 | -// 关闭弹窗 | 88 | +// ==================== 响应式状态 ==================== |
| 89 | +const showPlanPopup = ref(false) | ||
| 90 | +const planListCount = ref(0) | ||
| 91 | +const planList = ref([]) | ||
| 92 | + | ||
| 93 | +// ==================== 计算属性 ==================== | ||
| 94 | +/** 获取计划列表最后一条的 id,无数据返回 null */ | ||
| 95 | +const lastPlanId = computed(() => { | ||
| 96 | + if (!Array.isArray(planList.value) || planList.value.length === 0) return null | ||
| 97 | + return planList.value.at(-1)?.id ?? null | ||
| 98 | +}) | ||
| 99 | + | ||
| 100 | +// ==================== 方法 ==================== | ||
| 101 | +/** 关闭弹窗 */ | ||
| 68 | const close = () => { | 102 | const close = () => { |
| 69 | emit('update:visible', false) | 103 | emit('update:visible', false) |
| 70 | } | 104 | } |
| 71 | 105 | ||
| 72 | -// 三个逻辑全部放在子组件内部 | ||
| 73 | -const handleAddFromPlan = () => { | ||
| 74 | - // close() // 关闭当前弹窗 | ||
| 75 | - showPlanPopup.value = true | ||
| 76 | -} | ||
| 77 | - | 106 | +/** 获取子组件传递的计划列表长度 */ |
| 78 | const getPlanListLength = (len) => { | 107 | const getPlanListLength = (len) => { |
| 79 | planListCount.value = len | 108 | planListCount.value = len |
| 80 | } | 109 | } |
| 81 | 110 | ||
| 111 | +/** 从训练计划中添加:打开计划选择弹窗 */ | ||
| 112 | +const handleAddFromPlan = () => { | ||
| 113 | + showPlanPopup.value = true | ||
| 114 | +} | ||
| 115 | + | ||
| 116 | +/** 使用训练模板:跳转模板选择页 */ | ||
| 82 | const handleAddFromTemplate = () => { | 117 | const handleAddFromTemplate = () => { |
| 83 | uni.navigateTo({ url: '/pages4/pages/xunji/xunji-rili-tianjia-moban' }) | 118 | uni.navigateTo({ url: '/pages4/pages/xunji/xunji-rili-tianjia-moban' }) |
| 84 | close() | 119 | close() |
| 85 | } | 120 | } |
| 86 | 121 | ||
| 122 | +/** 自由训练:跳转动作练习页 */ | ||
| 87 | const handleFreeTraining = () => { | 123 | const handleFreeTraining = () => { |
| 88 | uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-lianxi?isTraining=true' }) | 124 | uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-lianxi?isTraining=true' }) |
| 89 | close() | 125 | close() |
| 90 | } | 126 | } |
| 91 | 127 | ||
| 92 | -// 获取我的计划列表的最后一个planid,传递到WodeJihuaTianjiaTancuang中 | ||
| 93 | -// 计划列表 | ||
| 94 | -const planList = ref([]) | ||
| 95 | - | ||
| 96 | -// 获取我的全部训练计划 | 128 | +/** 获取我的全部训练计划 */ |
| 97 | const fetchMyPlanList = async () => { | 129 | const fetchMyPlanList = async () => { |
| 98 | try { | 130 | try { |
| 99 | const res = await QueryPlanApi.getMyPlan() | 131 | const res = await QueryPlanApi.getMyPlan() |
| @@ -108,73 +140,44 @@ const fetchMyPlanList = async () => { | @@ -108,73 +140,44 @@ const fetchMyPlanList = async () => { | ||
| 108 | } | 140 | } |
| 109 | } | 141 | } |
| 110 | 142 | ||
| 111 | -// 计算属性:拿到列表最后一条计划id,无数据返回null | ||
| 112 | -const lastPlanId = computed(() => { | ||
| 113 | - if (!Array.isArray(planList.value) || planList.value.length === 0) return null | ||
| 114 | - const lastPlan = planList.value.at(-1) | ||
| 115 | - return lastPlan?.id ?? null | ||
| 116 | -}) | ||
| 117 | - | 143 | +// ==================== 生命周期 ==================== |
| 118 | onMounted(() => { | 144 | onMounted(() => { |
| 119 | fetchMyPlanList() | 145 | fetchMyPlanList() |
| 120 | }) | 146 | }) |
| 121 | - | ||
| 122 | - | ||
| 123 | </script> | 147 | </script> |
| 124 | 148 | ||
| 125 | <style lang="scss" scoped> | 149 | <style lang="scss" scoped> |
| 126 | -/* 半透明黑色遮罩层 —— 内容靠底部!! */ | ||
| 127 | -.popup-overlay { | ||
| 128 | - position: fixed; | ||
| 129 | - top: 0; | ||
| 130 | - left: 0; | ||
| 131 | - width: 100vw; | ||
| 132 | - height: 100vh; | ||
| 133 | - background: rgba(0, 0, 0, 0.8); | ||
| 134 | - z-index: 9999; | ||
| 135 | - display: flex; | ||
| 136 | - flex-direction: column; | ||
| 137 | - /* 核心:靠下 */ | ||
| 138 | - justify-content: flex-end; | ||
| 139 | - align-items: center; | ||
| 140 | - /* 距离底部一点距离,不贴着最底下 */ | ||
| 141 | - padding-bottom: 150rpx; | ||
| 142 | -} | ||
| 143 | - | ||
| 144 | -/* 弹窗主体容器 */ | ||
| 145 | -.popup-bottom { | ||
| 146 | - width: 85%; | 150 | +.add-train-popup { |
| 147 | display: flex; | 151 | display: flex; |
| 148 | flex-direction: column; | 152 | flex-direction: column; |
| 149 | align-items: center; | 153 | align-items: center; |
| 150 | - gap: 70rpx; | ||
| 151 | - margin-bottom: 100rpx; | ||
| 152 | -} | 154 | + padding: 0 30rpx 40rpx; |
| 155 | + | ||
| 156 | + // 顶部拖拽指示条 | ||
| 157 | + .popup-indicator { | ||
| 158 | + width: 72rpx; | ||
| 159 | + height: 8rpx; | ||
| 160 | + border-radius: 4rpx; | ||
| 161 | + background: rgba(255, 255, 255, 0.3); | ||
| 162 | + margin: 16rpx 0 32rpx; | ||
| 163 | + } | ||
| 153 | 164 | ||
| 154 | -/* 选项列表 */ | ||
| 155 | -.popup-options { | 165 | + // 菜单选项列表 |
| 166 | + .popup-menu { | ||
| 156 | width: 100%; | 167 | width: 100%; |
| 157 | display: flex; | 168 | display: flex; |
| 158 | flex-direction: column; | 169 | flex-direction: column; |
| 159 | - justify-content: center; | ||
| 160 | - align-items: center; | ||
| 161 | - gap: 20rpx; | ||
| 162 | -} | 170 | + gap: 16rpx; |
| 163 | 171 | ||
| 164 | -/* 单个选项项 */ | ||
| 165 | -.popup-option { | ||
| 166 | - width: 100%; | ||
| 167 | - // background: rgba(255, 255, 255, 0.1); | ||
| 168 | - border-radius: 16rpx; | ||
| 169 | - padding: 24rpx 30rpx; | 172 | + .menu-item { |
| 170 | display: flex; | 173 | display: flex; |
| 171 | align-items: center; | 174 | align-items: center; |
| 172 | - | ||
| 173 | gap: 24rpx; | 175 | gap: 24rpx; |
| 174 | -} | 176 | + padding: 24rpx 30rpx; |
| 177 | + border-radius: 16rpx; | ||
| 178 | + } | ||
| 175 | 179 | ||
| 176 | -/* 黄色图标容器 */ | ||
| 177 | -.icon-wrap { | 180 | + .menu-icon { |
| 178 | width: 80rpx; | 181 | width: 80rpx; |
| 179 | height: 80rpx; | 182 | height: 80rpx; |
| 180 | border-radius: 50%; | 183 | border-radius: 50%; |
| @@ -183,33 +186,29 @@ onMounted(() => { | @@ -183,33 +186,29 @@ onMounted(() => { | ||
| 183 | align-items: center; | 186 | align-items: center; |
| 184 | justify-content: center; | 187 | justify-content: center; |
| 185 | flex-shrink: 0; | 188 | flex-shrink: 0; |
| 186 | - | ||
| 187 | - image { | ||
| 188 | - width: 40rpx; | ||
| 189 | - height: 40rpx; | ||
| 190 | } | 189 | } |
| 191 | -} | ||
| 192 | 190 | ||
| 193 | -/* 文字区域 */ | ||
| 194 | -.text-wrap { | 191 | + .menu-text { |
| 195 | display: flex; | 192 | display: flex; |
| 196 | flex-direction: column; | 193 | flex-direction: column; |
| 197 | gap: 8rpx; | 194 | gap: 8rpx; |
| 198 | -} | ||
| 199 | 195 | ||
| 200 | -.option-title { | 196 | + .menu-title { |
| 201 | font-size: 32rpx; | 197 | font-size: 32rpx; |
| 202 | color: #fff; | 198 | color: #fff; |
| 203 | font-weight: 500; | 199 | font-weight: 500; |
| 204 | -} | 200 | + } |
| 205 | 201 | ||
| 206 | -.option-desc { | 202 | + .menu-desc { |
| 207 | font-size: 26rpx; | 203 | font-size: 26rpx; |
| 208 | color: rgba(255, 255, 255, 0.7); | 204 | color: rgba(255, 255, 255, 0.7); |
| 209 | -} | 205 | + } |
| 206 | + } | ||
| 207 | + } | ||
| 210 | 208 | ||
| 211 | -/* 底部关闭按钮 */ | ||
| 212 | -.close-btn { | 209 | + // 底部关闭按钮 |
| 210 | + .close-btn { | ||
| 211 | + margin-top: 60rpx; | ||
| 213 | width: 88rpx; | 212 | width: 88rpx; |
| 214 | height: 88rpx; | 213 | height: 88rpx; |
| 215 | border-radius: 50%; | 214 | border-radius: 50%; |
| @@ -217,11 +216,6 @@ onMounted(() => { | @@ -217,11 +216,6 @@ onMounted(() => { | ||
| 217 | display: flex; | 216 | display: flex; |
| 218 | align-items: center; | 217 | align-items: center; |
| 219 | justify-content: center; | 218 | justify-content: center; |
| 220 | - | ||
| 221 | - text { | ||
| 222 | - font-size: 40rpx; | ||
| 223 | - color: #000; | ||
| 224 | - line-height: 1; | ||
| 225 | } | 219 | } |
| 226 | } | 220 | } |
| 227 | </style> | 221 | </style> |
| @@ -4,12 +4,12 @@ | @@ -4,12 +4,12 @@ | ||
| 4 | <!-- 顶部日期栏 --> | 4 | <!-- 顶部日期栏 --> |
| 5 | <view class="header-bar"> | 5 | <view class="header-bar"> |
| 6 | <view class="close-btn" @click="closeCellPopup"> | 6 | <view class="close-btn" @click="closeCellPopup"> |
| 7 | - <uni-icons type="down" size="24" color="#333"></uni-icons> | 7 | + <u-icon name="arrow-down" size="15" color="#333"></u-icon> |
| 8 | </view> | 8 | </view> |
| 9 | <view class="date-wrapper"> | 9 | <view class="date-wrapper"> |
| 10 | <text class="date-text">{{ displayDate }}</text> | 10 | <text class="date-text">{{ displayDate }}</text> |
| 11 | </view> | 11 | </view> |
| 12 | - <button class="date-note-btn" @click.stop="handleDateNote">日期备注</button> | 12 | + <button class="date-note-btn" @click="handleDateNote">日期备注</button> |
| 13 | <button class="go-train-btn" @click.stop="startTraining"> | 13 | <button class="go-train-btn" @click.stop="startTraining"> |
| 14 | <text class="go-train-text">去训练</text> | 14 | <text class="go-train-text">去训练</text> |
| 15 | <text class="go-train-tag">GO</text> | 15 | <text class="go-train-tag">GO</text> |
| @@ -21,10 +21,8 @@ | @@ -21,10 +21,8 @@ | ||
| 21 | <view class="noteContent" v-if="noteList.length > 0"> | 21 | <view class="noteContent" v-if="noteList.length > 0"> |
| 22 | <text class="noteTitle">日程备注:</text> | 22 | <text class="noteTitle">日程备注:</text> |
| 23 | <view class="noteTags"> | 23 | <view class="noteTags"> |
| 24 | - <view class="noteTag" v-for="(item, idx) in noteList" :key="item.id" | ||
| 25 | - :style="{ backgroundColor: item.backgroundColor }" @click="handleEditNote(item)"> | ||
| 26 | - {{ item.content }} | ||
| 27 | - </view> | 24 | + <u-tag class="noteTag" v-for="(item, idx) in noteList" :key="item.id" :text="item.content" |
| 25 | + :bg-color="item.backgroundColor" color="#fff" shape="circle" @click="handleEditNote(item)" /> | ||
| 28 | </view> | 26 | </view> |
| 29 | </view> | 27 | </view> |
| 30 | 28 | ||
| @@ -54,13 +52,10 @@ | @@ -54,13 +52,10 @@ | ||
| 54 | {{ currentPlan?.totalWeight }}kg</text> | 52 | {{ currentPlan?.totalWeight }}kg</text> |
| 55 | </view> | 53 | </view> |
| 56 | <view class="plan-header-btns"> | 54 | <view class="plan-header-btns"> |
| 57 | - <button class="plan-btn more-btn" @click.stop="handlePlanMore">更多</button> | ||
| 58 | - <button class="plan-btn copy-btn" @click.stop="openCopyCalendarPopup(currentPlan?.templateId)"> | ||
| 59 | - 复制到 | ||
| 60 | - </button> | ||
| 61 | - <button class="plan-btn go-train-btn-small" @click.stop="handleTrainAgain"> | 55 | + <button class="plan-btn more-btn" @click="handlePlanMore">更多</button> |
| 56 | + <button class="plan-btn copy-btn" @click="openCopyCalendarPopup(currentPlan?.templateId)">复制到</button> | ||
| 57 | + <button class="plan-btn go-train-btn-small" @click="handleTrainAgain"> | ||
| 62 | <text class="go-train-text-sm">今日再练</text> | 58 | <text class="go-train-text-sm">今日再练</text> |
| 63 | - <!-- <text class="go-train-tag-sm">GO</text> --> | ||
| 64 | </button> | 59 | </button> |
| 65 | </view> | 60 | </view> |
| 66 | </view> | 61 | </view> |
| @@ -93,25 +88,8 @@ | @@ -93,25 +88,8 @@ | ||
| 93 | <view class="set-item" v-for="(set, setIdx) in unit.exercises[0]?.sets || []" :key="setIdx"> | 88 | <view class="set-item" v-for="(set, setIdx) in unit.exercises[0]?.sets || []" :key="setIdx"> |
| 94 | <!-- 新增:把左边所有内容包起来 --> | 89 | <!-- 新增:把左边所有内容包起来 --> |
| 95 | <view class="set-left" :style="{ color: set.isCompleted === 1 ? '#333' : '#a2a2a2' }"> | 90 | <view class="set-left" :style="{ color: set.isCompleted === 1 ? '#333' : '#a2a2a2' }"> |
| 96 | - <!-- <text class="indexData">{{ setIdx + 1 }}</text> --> | ||
| 97 | - <text class="set-content" v-if="unit.exercises[0].exerciseType === 0"> | ||
| 98 | - {{ set.weight }}kg x {{ set.reps }}次 | ||
| 99 | - </text> | ||
| 100 | - <text class="set-content" v-if="unit.exercises[0].exerciseType === 1"> | ||
| 101 | - {{ formatSecondsToHms(set.duration) }} x {{ set.distance }}km | ||
| 102 | - </text> | ||
| 103 | - <text class="set-content" v-if="unit.exercises[0].exerciseType === 2"> | ||
| 104 | - {{ set.reps }}次 | ||
| 105 | - </text> | ||
| 106 | - <text class="set-content" v-if="unit.exercises[0].exerciseType === 3"> | ||
| 107 | - {{ formatSecondsToHms(set.duration) }} | ||
| 108 | - </text> | ||
| 109 | - <text class="set-content" v-if="[4, 5].includes(unit.exercises[0].exerciseType)"> | ||
| 110 | - {{ set.weight }}kg x {{ set.duration }}s x {{ set.restTime }} | ||
| 111 | - </text> | ||
| 112 | - <text class="set-content" v-if="unit.exercises[0].exerciseType === 6"> | ||
| 113 | - {{ set.reps }}组 x {{ set.duration }}秒 x {{ set.restTime || 0 }}秒/组休息 | ||
| 114 | - </text> | 91 | + <text class="set-content">{{ getSetDisplayText(set, unit.exercises[0].exerciseType) |
| 92 | + }}</text> | ||
| 115 | </view> | 93 | </view> |
| 116 | <view class="set-right"> | 94 | <view class="set-right"> |
| 117 | <text class="rest-time" v-if="set.restTime && unit.exercises[0].exerciseType != 6"> | 95 | <text class="rest-time" v-if="set.restTime && unit.exercises[0].exerciseType != 6"> |
| @@ -127,7 +105,6 @@ | @@ -127,7 +105,6 @@ | ||
| 127 | <!-- 右侧:修改按钮 + 完成对勾 --> | 105 | <!-- 右侧:修改按钮 + 完成对勾 --> |
| 128 | <view class="action-right" @click="openXiuGai(unit, unitIdx)"> | 106 | <view class="action-right" @click="openXiuGai(unit, unitIdx)"> |
| 129 | <button class="modify-btn">修改</button> | 107 | <button class="modify-btn">修改</button> |
| 130 | - <!-- <view class="check-icon">✓</view> --> | ||
| 131 | </view> | 108 | </view> |
| 132 | </view> | 109 | </view> |
| 133 | <!-- 情况2:超级组 unitType=2 --> | 110 | <!-- 情况2:超级组 unitType=2 --> |
| @@ -168,28 +145,8 @@ | @@ -168,28 +145,8 @@ | ||
| 168 | 145 | ||
| 169 | <view class="set-content" | 146 | <view class="set-content" |
| 170 | :style="{ color: ex.sets[idx].isCompleted === 1 ? '#333' : '#a2a2a2' }"> | 147 | :style="{ color: ex.sets[idx].isCompleted === 1 ? '#333' : '#a2a2a2' }"> |
| 171 | - <!-- 适配不同动作类型的显示文本 --> | ||
| 172 | - <text v-if="ex.exerciseType === 0" class="detai-data"> | ||
| 173 | - {{ ex.sets[idx].weight }}kg x {{ ex.sets[idx].reps }}次 | ||
| 174 | - </text> | ||
| 175 | - <text v-if="ex.exerciseType === 1" class="detai-data"> | ||
| 176 | - {{ formatSecondsToHms(ex.sets[idx].duration) }} x {{ | ||
| 177 | - ex.sets[idx].distance | ||
| 178 | - }}km | ||
| 179 | - </text> | ||
| 180 | - <text v-if="ex.exerciseType === 2" class="detai-data"> | ||
| 181 | - {{ ex.sets[idx].reps }}次 | ||
| 182 | - </text> | ||
| 183 | - <text v-if="ex.exerciseType === 3" class="detai-data"> | ||
| 184 | - {{ formatSecondsToHms(ex.sets[idx].duration) }} | ||
| 185 | - </text> | ||
| 186 | - <text v-if="[4, 5].includes(ex.exerciseType)" class="detai-data"> | ||
| 187 | - {{ ex.sets[idx].weight }}kg x {{ ex.sets[idx].reps }}次 | ||
| 188 | - </text> | ||
| 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 | - }}秒/组休息 | ||
| 192 | - </text> | 148 | + <text class="detai-data">{{ getSuperSetDisplayText(ex.sets[idx], ex.exerciseType) |
| 149 | + }}</text> | ||
| 193 | <view class="rest-time" v-if="ex.sets[idx]?.restTime && ex.exerciseType !== 6" | 150 | <view class="rest-time" v-if="ex.sets[idx]?.restTime && ex.exerciseType !== 6" |
| 194 | :style="{ color: ex.sets[idx].isCompleted === 1 ? '#999 !important' : '#a2a2a2 !important' }"> | 151 | :style="{ color: ex.sets[idx].isCompleted === 1 ? '#999 !important' : '#a2a2a2 !important' }"> |
| 195 | {{ ex.sets[idx].restTime }}s | 152 | {{ ex.sets[idx].restTime }}s |
| @@ -212,21 +169,13 @@ | @@ -212,21 +169,13 @@ | ||
| 212 | <!-- 空状态区域 --> | 169 | <!-- 空状态区域 --> |
| 213 | <view v-else class="empty-section"> | 170 | <view v-else class="empty-section"> |
| 214 | <image class="empty-img" | 171 | <image class="empty-img" |
| 215 | - src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png" | 172 | + src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260724/空状态自助_1784875704380.png" |
| 216 | mode="aspectFit"> | 173 | mode="aspectFit"> |
| 217 | </image> | 174 | </image> |
| 218 | <text class="empty-tip">今天没有安排</text> | 175 | <text class="empty-tip">今天没有安排</text> |
| 219 | <button class="add-train-btn" @click="openAddTrainPopup">+ 添加自助训练</button> | 176 | <button class="add-train-btn" @click="openAddTrainPopup">+ 添加自助训练</button> |
| 220 | 177 | ||
| 221 | - <!-- 底部课程类型卡片 --> | ||
| 222 | - <!-- <view class="course-section"> | ||
| 223 | - <view class="course-item" v-for="(item, index) in courseList" :key="index" | ||
| 224 | - @click="handleCourseClick(item.type)"> | ||
| 225 | - <image class="course-icon" :src="item.icon" mode="aspectFill"></image> | ||
| 226 | - <text class="course-title">{{ item.title }}</text> | ||
| 227 | - <text class="course-desc">{{ item.desc }}</text> | ||
| 228 | - </view> | ||
| 229 | - </view> --> | 178 | + |
| 230 | </view> | 179 | </view> |
| 231 | 180 | ||
| 232 | <AddTrainPopup v-model:visible="showAddTrainPopup" @successAddTrain="handleAddTrain" /> | 181 | <AddTrainPopup v-model:visible="showAddTrainPopup" @successAddTrain="handleAddTrain" /> |
| @@ -298,59 +247,44 @@ import CalendarColorPicker from '@/pages/xunji/components/rili-components/Calend | @@ -298,59 +247,44 @@ import CalendarColorPicker from '@/pages/xunji/components/rili-components/Calend | ||
| 298 | import { useTrainingStore } from '@/sheep/store/trainingStore' | 247 | import { useTrainingStore } from '@/sheep/store/trainingStore' |
| 299 | import MeiRiMoBanXiuGai from '../rili-components/meiri-moban-xiugai.vue' | 248 | import MeiRiMoBanXiuGai from '../rili-components/meiri-moban-xiugai.vue' |
| 300 | 249 | ||
| 250 | +// ===== Props & Emits ===== | ||
| 301 | const props = defineProps({ | 251 | const props = defineProps({ |
| 302 | date: { type: String, default: '' }, | 252 | date: { type: String, default: '' }, |
| 303 | - // noteList: { | ||
| 304 | - // type: Array, | ||
| 305 | - // default: () => [] | ||
| 306 | - // } | ||
| 307 | }) | 253 | }) |
| 308 | 254 | ||
| 309 | -const show = ref(false); | ||
| 310 | const emit = defineEmits(['close', 'refreshCalendar']); | 255 | const emit = defineEmits(['close', 'refreshCalendar']); |
| 311 | -const noteList = ref([]); | 256 | + |
| 257 | +// ===== 响应式状态 ===== | ||
| 258 | +const show = ref(false); | ||
| 312 | const showAddTrainPopup = ref(false); | 259 | const showAddTrainPopup = ref(false); |
| 313 | const selectedDate = ref(''); | 260 | const selectedDate = ref(''); |
| 314 | const displayDate = ref(''); | 261 | const displayDate = ref(''); |
| 315 | const resdailyData = ref([]); | 262 | const resdailyData = ref([]); |
| 316 | -// const noteList = ref([]) | ||
| 317 | const currentPlanIndex = ref(0); | 263 | const currentPlanIndex = ref(0); |
| 264 | +const noteList = ref([]); | ||
| 265 | +const noteHistoryList = ref([]); | ||
| 318 | 266 | ||
| 267 | +// 弹窗相关 | ||
| 319 | const isMoveMode = ref(false); | 268 | const isMoveMode = ref(false); |
| 320 | const isCopyMode = ref(false); | 269 | const isCopyMode = ref(false); |
| 321 | -const showRiqibeizhu = ref(false) | ||
| 322 | -const moreShow = ref(false) | 270 | +const showRiqibeizhu = ref(false); |
| 271 | +const moreShow = ref(false); | ||
| 272 | +const showColorPopup = ref(false); | ||
| 323 | const calendarPopupRef = ref(null); | 273 | const calendarPopupRef = ref(null); |
| 324 | -const currentEditId = ref(null); // 新增这一行 | ||
| 325 | -const noteHistoryList = ref([]); | 274 | +const currentEditId = ref(null); |
| 275 | +const selectedPlanId = ref(null); | ||
| 326 | 276 | ||
| 327 | -const showColorPopup = ref(false) | ||
| 328 | -const selectedPlanId = ref(null) | 277 | +// Store / 子组件引用 |
| 278 | +const trainingStore = useTrainingStore(); | ||
| 279 | +const meirimobanRef = ref(null); | ||
| 329 | 280 | ||
| 330 | -const trainingStore = useTrainingStore() | 281 | +// 修改单元缓存 |
| 282 | +const editUnitInfo = ref({ | ||
| 283 | + unit: null, | ||
| 284 | + unitIndex: null, | ||
| 285 | + dailyTemplate: null, | ||
| 286 | +}); | ||
| 331 | 287 | ||
| 332 | -const meirimobanRef = ref(null) | ||
| 333 | -// 没有训练模板时推荐课程 | ||
| 334 | -const courseList = ref([ | ||
| 335 | - { | ||
| 336 | - type: 'group', | ||
| 337 | - title: '团课', | ||
| 338 | - desc: '大家一起练', | ||
| 339 | - icon: 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/group-course.png' | ||
| 340 | - }, | ||
| 341 | - { | ||
| 342 | - type: 'private', | ||
| 343 | - title: '私教', | ||
| 344 | - desc: '1对1专人指导', | ||
| 345 | - icon: 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/private-course.png' | ||
| 346 | - }, | ||
| 347 | - { | ||
| 348 | - type: 'smallClass', | ||
| 349 | - title: '小班课', | ||
| 350 | - desc: '28天极速瘦身', | ||
| 351 | - icon: 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/small-class.png' | ||
| 352 | - } | ||
| 353 | -]); | ||
| 354 | 288 | ||
| 355 | // 打开弹窗 | 289 | // 打开弹窗 |
| 356 | const openPopup = async () => { | 290 | const openPopup = async () => { |
| @@ -370,14 +304,7 @@ defineExpose({ | @@ -370,14 +304,7 @@ defineExpose({ | ||
| 370 | closeCellPopup | 304 | closeCellPopup |
| 371 | }); | 305 | }); |
| 372 | 306 | ||
| 373 | -// 修改 | ||
| 374 | -// 定义缓存变量,传给弹窗 | ||
| 375 | -const editUnitInfo = ref({ | ||
| 376 | - unit: null, | ||
| 377 | - unitIndex: null, | ||
| 378 | - dailyTemplate: null // currentPlan(整个每日模板对象,含id、templateId) | ||
| 379 | -}) | ||
| 380 | -// 打开弹窗修改单个unit | 307 | +// ===== 弹窗:打开修改单个 unit ===== |
| 381 | const openXiuGai = (unit, idx) => { | 308 | const openXiuGai = (unit, idx) => { |
| 382 | editUnitInfo.value = { | 309 | editUnitInfo.value = { |
| 383 | unit: JSON.parse(JSON.stringify(unit)), // 深拷贝防止原数据被实时篡改 | 310 | unit: JSON.parse(JSON.stringify(unit)), // 深拷贝防止原数据被实时篡改 |
| @@ -410,12 +337,14 @@ const handleAddTrain = async () => { | @@ -410,12 +337,14 @@ const handleAddTrain = async () => { | ||
| 410 | // 更新主页面 | 337 | // 更新主页面 |
| 411 | emit('refreshCalendar'); | 338 | emit('refreshCalendar'); |
| 412 | } | 339 | } |
| 340 | +// ===== 计算属性 ===== | ||
| 413 | // 当前正在显示的训记 | 341 | // 当前正在显示的训记 |
| 414 | const currentPlan = computed(() => { | 342 | const currentPlan = computed(() => { |
| 415 | if (!resdailyData.value.length) return null; | 343 | if (!resdailyData.value.length) return null; |
| 416 | return resdailyData.value[currentPlanIndex.value] || null; | 344 | return resdailyData.value[currentPlanIndex.value] || null; |
| 417 | }); | 345 | }); |
| 418 | 346 | ||
| 347 | +// ===== API:加载每日模板数据 ===== | ||
| 419 | const loaddailytemplate = async () => { | 348 | const loaddailytemplate = async () => { |
| 420 | if (!selectedDate.value) return; | 349 | if (!selectedDate.value) return; |
| 421 | console.log('【子组件】开始加载数据...'); | 350 | console.log('【子组件】开始加载数据...'); |
| @@ -458,7 +387,7 @@ const switchPlan = (index) => { | @@ -458,7 +387,7 @@ const switchPlan = (index) => { | ||
| 458 | console.log('【切换标签后 - currentPlan】', currentPlan.value); | 387 | console.log('【切换标签后 - currentPlan】', currentPlan.value); |
| 459 | }; | 388 | }; |
| 460 | 389 | ||
| 461 | -// 更多弹窗控制 | 390 | +// ===== 更多弹窗:操作项 ===== |
| 462 | const handlePlanMore = () => { | 391 | const handlePlanMore = () => { |
| 463 | if (!currentPlan.value) { // 加这个 | 392 | if (!currentPlan.value) { // 加这个 |
| 464 | return | 393 | return |
| @@ -472,6 +401,13 @@ const closeMorePopup = () => { | @@ -472,6 +401,13 @@ const closeMorePopup = () => { | ||
| 472 | 401 | ||
| 473 | // 一键打勾(完成训练) | 402 | // 一键打勾(完成训练) |
| 474 | const handleCompleteCheck = async () => { | 403 | const handleCompleteCheck = async () => { |
| 404 | + // 日期校验:只能对今天及以前的日期一键打勾 | ||
| 405 | + const today = dayjs().format('YYYY-MM-DD'); | ||
| 406 | + if (selectedDate.value > today) { | ||
| 407 | + uni.showToast({ title: '一键打勾只能用于今天及以前的日期', icon: 'none' }); | ||
| 408 | + return; | ||
| 409 | + } | ||
| 410 | + | ||
| 475 | if (!currentPlan.value?.id) { | 411 | if (!currentPlan.value?.id) { |
| 476 | uni.showToast({ title: '未找到训练ID', icon: 'none' }) | 412 | uni.showToast({ title: '未找到训练ID', icon: 'none' }) |
| 477 | return | 413 | return |
| @@ -494,13 +430,17 @@ const handleCompleteCheck = async () => { | @@ -494,13 +430,17 @@ const handleCompleteCheck = async () => { | ||
| 494 | } | 430 | } |
| 495 | } | 431 | } |
| 496 | 432 | ||
| 497 | -// 保存到我的模板 | ||
| 498 | -// const saveToMyTemplate = (dailyTemplate) => { | ||
| 499 | -// console.log('dailyTemplate', dailyTemplate); | ||
| 500 | - | ||
| 501 | -// } | ||
| 502 | - | 433 | +// ===== 保存训练为个人模板 ===== |
| 503 | const openSaveTemplateDialog = async (item) => { | 434 | const openSaveTemplateDialog = async (item) => { |
| 435 | + // 关闭所有可能遮挡的弹窗 | ||
| 436 | + moreShow.value = false; | ||
| 437 | + showColorPopup.value = false; | ||
| 438 | + showAddTrainPopup.value = false; | ||
| 439 | + showRiqibeizhu.value = false; | ||
| 440 | + show.value = false | ||
| 441 | + // 等待 DOM 更新后再弹出模板名称输入框,确保弹窗完全关闭 | ||
| 442 | + await nextTick(); | ||
| 443 | + | ||
| 504 | // uni弹窗输入框:只填模板名称 | 444 | // uni弹窗输入框:只填模板名称 |
| 505 | uni.showModal({ | 445 | uni.showModal({ |
| 506 | title: '确认保存为你的训练模板?', | 446 | title: '确认保存为你的训练模板?', |
| @@ -520,7 +460,6 @@ const openSaveTemplateDialog = async (item) => { | @@ -520,7 +460,6 @@ const openSaveTemplateDialog = async (item) => { | ||
| 520 | // 发起创建接口 | 460 | // 发起创建接口 |
| 521 | console.log('reqData', reqData); | 461 | console.log('reqData', reqData); |
| 522 | await submitCreateTemplate(reqData) | 462 | await submitCreateTemplate(reqData) |
| 523 | - moreShow.value = false; | ||
| 524 | } | 463 | } |
| 525 | } | 464 | } |
| 526 | }) | 465 | }) |
| @@ -580,25 +519,42 @@ const closeAddTrainPopup = () => { | @@ -580,25 +519,42 @@ const closeAddTrainPopup = () => { | ||
| 580 | showAddTrainPopup.value = false; | 519 | showAddTrainPopup.value = false; |
| 581 | }; | 520 | }; |
| 582 | 521 | ||
| 583 | -// 弹窗选项点击事件 | ||
| 584 | -// const handleAddFromPlan = () => { | ||
| 585 | -// uni.showToast({ title: '从训练计划添加', icon: 'none' }); | ||
| 586 | -// closeAddTrainPopup(); | ||
| 587 | -// }; | ||
| 588 | -// const handleAddFromTemplate = () => { | ||
| 589 | -// uni.navigateTo({ url: '/pages4/pages/xunji/xunji-rili-tianjia-moban' }); | ||
| 590 | -// closeAddTrainPopup(); | ||
| 591 | -// }; | ||
| 592 | -// const handleFreeTraining = () => { | ||
| 593 | -// uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-lianxi' }); | ||
| 594 | -// closeAddTrainPopup(); | ||
| 595 | -// }; | ||
| 596 | - | 522 | +// ===== 工具函数 ===== |
| 597 | const formatSecondsToHms = (seconds) => { | 523 | const formatSecondsToHms = (seconds) => { |
| 598 | if (!seconds || isNaN(seconds)) return '00:00:00'; | 524 | if (!seconds || isNaN(seconds)) return '00:00:00'; |
| 599 | return dayjs.duration(parseInt(seconds, 10), 'seconds').format('HH:mm:ss'); | 525 | return dayjs.duration(parseInt(seconds, 10), 'seconds').format('HH:mm:ss'); |
| 600 | } | 526 | } |
| 601 | 527 | ||
| 528 | +// ===== 工具函数:根据动作类型生成组次显示文本 ===== | ||
| 529 | +const getSetDisplayText = (set, exerciseType) => { | ||
| 530 | + if (!set) return ''; | ||
| 531 | + switch (exerciseType) { | ||
| 532 | + case 0: return `${set.weight || 0}kg x ${set.reps || 0}次`; | ||
| 533 | + case 1: return `${formatSecondsToHms(set.duration)} x ${set.distance || 0}km`; | ||
| 534 | + case 2: return `${set.reps || 0}次`; | ||
| 535 | + case 3: return formatSecondsToHms(set.duration); | ||
| 536 | + case 4: | ||
| 537 | + case 5: return `${set.weight || 0}kg x ${set.duration || 0}s x ${set.restTime || 0}`; | ||
| 538 | + case 6: return `${set.reps || 0}组 x ${set.duration || 0}秒 x ${set.restTime || 0}秒/组休息`; | ||
| 539 | + default: return ''; | ||
| 540 | + } | ||
| 541 | +}; | ||
| 542 | + | ||
| 543 | +// 超级组中的动作显示(与普通动作在 type 4/5 上的展示略有差异) | ||
| 544 | +const getSuperSetDisplayText = (set, exerciseType) => { | ||
| 545 | + if (!set) return ''; | ||
| 546 | + switch (exerciseType) { | ||
| 547 | + case 0: return `${set.weight || 0}kg x ${set.reps || 0}次`; | ||
| 548 | + case 1: return `${formatSecondsToHms(set.duration)} x ${set.distance || 0}km`; | ||
| 549 | + case 2: return `${set.reps || 0}次`; | ||
| 550 | + case 3: return formatSecondsToHms(set.duration); | ||
| 551 | + case 4: | ||
| 552 | + case 5: return `${set.weight || 0}kg x ${set.reps || 0}次`; | ||
| 553 | + case 6: return `${set.reps || 0}次 x ${set.duration || 0}秒 x ${set.restTime || 0}秒/组休息`; | ||
| 554 | + default: return ''; | ||
| 555 | + } | ||
| 556 | +}; | ||
| 557 | + | ||
| 602 | // 返回按钮逻辑 | 558 | // 返回按钮逻辑 |
| 603 | const goBack = () => { | 559 | const goBack = () => { |
| 604 | uni.navigateBack({ | 560 | uni.navigateBack({ |
| @@ -609,19 +565,15 @@ const goBack = () => { | @@ -609,19 +565,15 @@ const goBack = () => { | ||
| 609 | }); | 565 | }); |
| 610 | }; | 566 | }; |
| 611 | 567 | ||
| 612 | -// 日期格式化(兼容iOS) | 568 | +// 日期格式化(使用 dayjs 处理,兼容性更好) |
| 613 | const formatDateWithWeek = (dateStr) => { | 569 | const formatDateWithWeek = (dateStr) => { |
| 614 | if (!dateStr) return ''; | 570 | if (!dateStr) return ''; |
| 571 | + const d = dayjs(dateStr); | ||
| 615 | const weekMap = ['日', '一', '二', '三', '四', '五', '六']; | 572 | const weekMap = ['日', '一', '二', '三', '四', '五', '六']; |
| 616 | - const iosSafeDate = new Date(dateStr.replace(/-/g, '/')); | ||
| 617 | - const year = iosSafeDate.getFullYear(); | ||
| 618 | - const month = String(iosSafeDate.getMonth() + 1).padStart(2, '0'); | ||
| 619 | - const day = String(iosSafeDate.getDate()).padStart(2, '0'); | ||
| 620 | - const week = weekMap[iosSafeDate.getDay()]; | ||
| 621 | - return `${year}/${month}/${day} 周${week}`; | 573 | + return `${d.format('YYYY/MM/DD')} 周${weekMap[d.day()]}`; |
| 622 | }; | 574 | }; |
| 623 | 575 | ||
| 624 | -// 打开日期备注组件 | 576 | +// ===== 日期备注相关 ===== |
| 625 | const handleDateNote = () => { | 577 | const handleDateNote = () => { |
| 626 | if (noteList.value.length >= 5) { | 578 | if (noteList.value.length >= 5) { |
| 627 | uni.showToast({ | 579 | uni.showToast({ |
| @@ -648,7 +600,7 @@ const handleCloseNotePopup = () => { | @@ -648,7 +600,7 @@ const handleCloseNotePopup = () => { | ||
| 648 | currentEditId.value = null; | 600 | currentEditId.value = null; |
| 649 | }; | 601 | }; |
| 650 | 602 | ||
| 651 | -// 打开「添加到日历」弹窗 | 603 | +// ===== 复制到 / 移动到 ===== |
| 652 | const openCalendarPopup = (templateId) => { | 604 | const openCalendarPopup = (templateId) => { |
| 653 | console.log('点击了添加到日历按钮', templateId); // 新增日志 | 605 | console.log('点击了添加到日历按钮', templateId); // 新增日志 |
| 654 | console.log('子组件实例', calendarPopupRef.value); // 新增日志 | 606 | console.log('子组件实例', calendarPopupRef.value); // 新增日志 |
| @@ -695,6 +647,14 @@ const handleDeleteTemplate = async () => { | @@ -695,6 +647,14 @@ const handleDeleteTemplate = async () => { | ||
| 695 | uni.showToast({ title: '未找到模板ID', icon: 'none' }); | 647 | uni.showToast({ title: '未找到模板ID', icon: 'none' }); |
| 696 | return; | 648 | return; |
| 697 | } | 649 | } |
| 650 | + // 关闭所有可能遮挡的弹窗 | ||
| 651 | + moreShow.value = false; | ||
| 652 | + showColorPopup.value = false; | ||
| 653 | + showAddTrainPopup.value = false; | ||
| 654 | + showRiqibeizhu.value = false; | ||
| 655 | + show.value = false | ||
| 656 | + // 等待 DOM 更新后再弹出模板名称输入框,确保弹窗完全关闭 | ||
| 657 | + await nextTick(); | ||
| 698 | // 弹出确认框 | 658 | // 弹出确认框 |
| 699 | uni.showModal({ | 659 | uni.showModal({ |
| 700 | title: '确认删除', | 660 | title: '确认删除', |
| @@ -820,7 +780,7 @@ const calendarColorPickerSuccess = () => { | @@ -820,7 +780,7 @@ const calendarColorPickerSuccess = () => { | ||
| 820 | emit('refreshCalendar') | 780 | emit('refreshCalendar') |
| 821 | } | 781 | } |
| 822 | 782 | ||
| 823 | -// 去训练 | 783 | +// ===== 去训练 / 每日模板编辑 ===== |
| 824 | const startTraining = () => { | 784 | const startTraining = () => { |
| 825 | 785 | ||
| 826 | if (trainingStore.isTraining) { | 786 | if (trainingStore.isTraining) { |
| @@ -861,6 +821,7 @@ const templateEdit = (plan) => { | @@ -861,6 +821,7 @@ const templateEdit = (plan) => { | ||
| 861 | console.log('开始进入编辑模板页面,模板ID:', plan.templateId, '每日模板ID:', plan.id); | 821 | console.log('开始进入编辑模板页面,模板ID:', plan.templateId, '每日模板ID:', plan.id); |
| 862 | }; | 822 | }; |
| 863 | 823 | ||
| 824 | +// ===== 监听 & 生命周期 ===== | ||
| 864 | // 监听父组件传进来的 date 变化 | 825 | // 监听父组件传进来的 date 变化 |
| 865 | watch( | 826 | watch( |
| 866 | () => props.date, | 827 | () => props.date, |
| @@ -888,6 +849,19 @@ onMounted(() => { | @@ -888,6 +849,19 @@ onMounted(() => { | ||
| 888 | </script> | 849 | </script> |
| 889 | 850 | ||
| 890 | <style lang="scss" scoped> | 851 | <style lang="scss" scoped> |
| 852 | +// ===== 公共变量 ===== | ||
| 853 | +$color-primary: #333; | ||
| 854 | +$color-accent: #ffc107; | ||
| 855 | +$color-bg: #f2f3f5; | ||
| 856 | +$color-white: #fff; | ||
| 857 | +$color-danger: #ff4d4f; | ||
| 858 | +$color-muted: #a2a2a2; | ||
| 859 | +$color-card-bg: #f9f9f9; | ||
| 860 | +$radius-sm: 8rpx; | ||
| 861 | +$radius-md: 12rpx; | ||
| 862 | +$radius-lg: 16rpx; | ||
| 863 | +$radius-round: 40rpx; | ||
| 864 | + | ||
| 891 | /* 全局容器 */ | 865 | /* 全局容器 */ |
| 892 | .container { | 866 | .container { |
| 893 | display: flex; | 867 | display: flex; |
| @@ -941,9 +915,14 @@ onMounted(() => { | @@ -941,9 +915,14 @@ onMounted(() => { | ||
| 941 | 915 | ||
| 942 | .date-text { | 916 | .date-text { |
| 943 | font-size: 28rpx; | 917 | font-size: 28rpx; |
| 918 | + white-space: nowrap; | ||
| 919 | + overflow: hidden; | ||
| 920 | + text-overflow: ellipsis; | ||
| 944 | } | 921 | } |
| 945 | 922 | ||
| 946 | .date-note-btn { | 923 | .date-note-btn { |
| 924 | + display: inline-flex; | ||
| 925 | + align-items: center; | ||
| 947 | font-size: 28rpx; | 926 | font-size: 28rpx; |
| 948 | color: #333; | 927 | color: #333; |
| 949 | background: #e7e7e9; | 928 | background: #e7e7e9; |
| @@ -1066,11 +1045,11 @@ onMounted(() => { | @@ -1066,11 +1045,11 @@ onMounted(() => { | ||
| 1066 | } | 1045 | } |
| 1067 | 1046 | ||
| 1068 | .go-train-btn-small { | 1047 | .go-train-btn-small { |
| 1069 | - // background: #ffc107; | ||
| 1070 | color: #000; | 1048 | color: #000; |
| 1071 | display: flex; | 1049 | display: flex; |
| 1072 | align-items: center; | 1050 | align-items: center; |
| 1073 | padding: 12rpx 20rpx; | 1051 | padding: 12rpx 20rpx; |
| 1052 | + background: inherit; | ||
| 1074 | } | 1053 | } |
| 1075 | 1054 | ||
| 1076 | .go-train-text-sm { | 1055 | .go-train-text-sm { |
| @@ -1286,34 +1265,34 @@ onMounted(() => { | @@ -1286,34 +1265,34 @@ onMounted(() => { | ||
| 1286 | flex-direction: column; | 1265 | flex-direction: column; |
| 1287 | align-items: center; | 1266 | align-items: center; |
| 1288 | justify-content: center; | 1267 | justify-content: center; |
| 1289 | - // width: 500rpx; | ||
| 1290 | - height: 500rpx; | ||
| 1291 | - margin-top: 100rpx; | ||
| 1292 | - padding: 20rpx 20rpx 60rpx; | 1268 | + padding: 80rpx 40rpx 120rpx; |
| 1293 | box-sizing: border-box; | 1269 | box-sizing: border-box; |
| 1294 | - | ||
| 1295 | } | 1270 | } |
| 1296 | 1271 | ||
| 1297 | .empty-img { | 1272 | .empty-img { |
| 1298 | - width: 280rpx; | ||
| 1299 | - height: 280rpx; | ||
| 1300 | - margin-bottom: 30rpx; | 1273 | + width: 360rpx; |
| 1274 | + height: 360rpx; | ||
| 1275 | + margin-bottom: 40rpx; | ||
| 1276 | + transform: scale(3); | ||
| 1301 | } | 1277 | } |
| 1302 | 1278 | ||
| 1303 | .empty-tip { | 1279 | .empty-tip { |
| 1304 | font-size: 32rpx; | 1280 | font-size: 32rpx; |
| 1305 | color: #666; | 1281 | color: #666; |
| 1306 | - margin-bottom: 60rpx; | 1282 | + font-weight: 500; |
| 1283 | + margin-bottom: 80rpx; | ||
| 1284 | + letter-spacing: 2rpx; | ||
| 1307 | } | 1285 | } |
| 1308 | 1286 | ||
| 1309 | .add-train-btn { | 1287 | .add-train-btn { |
| 1310 | font-size: 30rpx; | 1288 | font-size: 30rpx; |
| 1311 | color: #000; | 1289 | color: #000; |
| 1312 | - background: #ffc107; | ||
| 1313 | - border-radius: 40rpx; | ||
| 1314 | - padding: 20rpx 80rpx; | 1290 | + background: $color-accent; |
| 1291 | + border-radius: $radius-round; | ||
| 1292 | + padding: 24rpx 80rpx; | ||
| 1315 | border: none; | 1293 | border: none; |
| 1316 | - margin-bottom: 60rpx; | 1294 | + font-weight: 500; |
| 1295 | + box-shadow: 0 4rpx 12rpx rgba(255, 193, 7, 0.3); | ||
| 1317 | } | 1296 | } |
| 1318 | 1297 | ||
| 1319 | /* 底部课程类型区域 */ | 1298 | /* 底部课程类型区域 */ |
| @@ -1485,6 +1464,7 @@ button::after { | @@ -1485,6 +1464,7 @@ button::after { | ||
| 1485 | border: none; | 1464 | border: none; |
| 1486 | } | 1465 | } |
| 1487 | 1466 | ||
| 1467 | + | ||
| 1488 | /* 训记切换标签 */ | 1468 | /* 训记切换标签 */ |
| 1489 | .plan-tabs { | 1469 | .plan-tabs { |
| 1490 | white-space: nowrap; | 1470 | white-space: nowrap; |
| @@ -1512,93 +1492,87 @@ button::after { | @@ -1512,93 +1492,87 @@ button::after { | ||
| 1512 | color: #fff; | 1492 | color: #fff; |
| 1513 | } | 1493 | } |
| 1514 | 1494 | ||
| 1515 | -/* ========== 超级组样式(已修复 无重复 无嵌套错误)========== */ | 1495 | +/* ========== 超级组样式 ========== */ |
| 1516 | .superset-wrapper { | 1496 | .superset-wrapper { |
| 1517 | margin-bottom: 20rpx; | 1497 | margin-bottom: 20rpx; |
| 1518 | - background: #fff; | ||
| 1519 | - border-radius: 12rpx; | 1498 | + background: $color-white; |
| 1499 | + border-radius: $radius-md; | ||
| 1520 | overflow: hidden; | 1500 | overflow: hidden; |
| 1521 | -} | ||
| 1522 | 1501 | ||
| 1523 | -.superset-wrapper .super-head { | 1502 | + .super-head { |
| 1524 | display: flex; | 1503 | display: flex; |
| 1525 | align-items: center; | 1504 | align-items: center; |
| 1526 | padding: 24rpx 20rpx; | 1505 | padding: 24rpx 20rpx; |
| 1527 | position: relative; | 1506 | position: relative; |
| 1528 | gap: 16rpx; | 1507 | gap: 16rpx; |
| 1529 | -} | 1508 | + } |
| 1530 | 1509 | ||
| 1531 | -.superset-wrapper .super-image { | 1510 | + .super-image { |
| 1532 | width: 75rpx; | 1511 | width: 75rpx; |
| 1533 | height: 75rpx; | 1512 | height: 75rpx; |
| 1534 | flex-shrink: 0; | 1513 | flex-shrink: 0; |
| 1535 | -} | 1514 | + } |
| 1536 | 1515 | ||
| 1537 | -.superset-wrapper .super-title-wrap { | 1516 | + .super-title-wrap { |
| 1538 | flex: 1; | 1517 | flex: 1; |
| 1539 | display: flex; | 1518 | display: flex; |
| 1540 | flex-direction: column; | 1519 | flex-direction: column; |
| 1541 | gap: 8rpx; | 1520 | gap: 8rpx; |
| 1542 | -} | 1521 | + } |
| 1543 | 1522 | ||
| 1544 | -.superset-wrapper .super-title { | 1523 | + .super-title { |
| 1545 | font-size: 32rpx; | 1524 | font-size: 32rpx; |
| 1546 | color: #111; | 1525 | color: #111; |
| 1547 | font-weight: 500; | 1526 | font-weight: 500; |
| 1548 | -} | 1527 | + } |
| 1549 | 1528 | ||
| 1550 | -.superset-wrapper .super-totalWeight { | 1529 | + .super-totalWeight { |
| 1551 | font-size: 24rpx; | 1530 | font-size: 24rpx; |
| 1552 | color: #666; | 1531 | color: #666; |
| 1553 | -} | 1532 | + } |
| 1554 | 1533 | ||
| 1555 | -.superset-wrapper .super-actionName { | 1534 | + .super-actionName { |
| 1556 | padding: 0 20rpx 20rpx 70rpx; | 1535 | padding: 0 20rpx 20rpx 70rpx; |
| 1557 | display: flex; | 1536 | display: flex; |
| 1558 | flex-direction: column; | 1537 | flex-direction: column; |
| 1559 | gap: 12rpx; | 1538 | gap: 12rpx; |
| 1560 | -} | ||
| 1561 | 1539 | ||
| 1562 | -.superset-wrapper .name-item { | 1540 | + .action-name { |
| 1541 | + font-size: 28rpx; | ||
| 1542 | + color: $color-primary; | ||
| 1543 | + } | ||
| 1544 | + } | ||
| 1545 | + | ||
| 1546 | + .name-item { | ||
| 1563 | display: flex; | 1547 | display: flex; |
| 1564 | align-items: center; | 1548 | align-items: center; |
| 1565 | gap: 12rpx; | 1549 | gap: 12rpx; |
| 1566 | -} | 1550 | + } |
| 1567 | 1551 | ||
| 1568 | -.superset-wrapper .letter { | 1552 | + .letter { |
| 1569 | font-size: 28rpx; | 1553 | font-size: 28rpx; |
| 1570 | font-weight: bold; | 1554 | font-weight: bold; |
| 1571 | - color: #333; | ||
| 1572 | -} | ||
| 1573 | - | ||
| 1574 | -.superset-wrapper .super-actionName .action-name { | ||
| 1575 | - font-size: 28rpx; | ||
| 1576 | - color: #333; | ||
| 1577 | -} | 1555 | + color: $color-primary; |
| 1556 | + } | ||
| 1578 | 1557 | ||
| 1579 | -.superset-wrapper .super-set-list { | 1558 | + .super-set-list { |
| 1580 | padding: 0 20rpx 20rpx 20rpx; | 1559 | padding: 0 20rpx 20rpx 20rpx; |
| 1581 | display: flex; | 1560 | display: flex; |
| 1582 | flex-direction: column; | 1561 | flex-direction: column; |
| 1583 | gap: 12rpx; | 1562 | gap: 12rpx; |
| 1584 | -} | 1563 | + } |
| 1585 | 1564 | ||
| 1586 | -.superset-wrapper .super-set-item { | 1565 | + .super-set-item { |
| 1587 | display: flex; | 1566 | display: flex; |
| 1588 | - // flex-direction: column; // 新增:垂直排列 | ||
| 1589 | - align-items: stretch; // 让每个动作占满宽度 | ||
| 1590 | - gap: 12rpx; // 垂直间距(可根据需要微调) | 1567 | + align-items: stretch; |
| 1568 | + gap: 12rpx; | ||
| 1591 | padding: 16rpx; | 1569 | padding: 16rpx; |
| 1592 | background: #f9f9f9; | 1570 | background: #f9f9f9; |
| 1593 | border-radius: 12rpx; | 1571 | border-radius: 12rpx; |
| 1594 | -} | ||
| 1595 | - | ||
| 1596 | -// 为了让序号独立占一行 | ||
| 1597 | -.superset-wrapper .super-set-item { | ||
| 1598 | margin-bottom: 8rpx; | 1572 | margin-bottom: 8rpx; |
| 1599 | -} | 1573 | + } |
| 1600 | 1574 | ||
| 1601 | -.superset-wrapper .set-number { | 1575 | + .set-number { |
| 1602 | font-size: 26rpx; | 1576 | font-size: 26rpx; |
| 1603 | background: #e4e4e4; | 1577 | background: #e4e4e4; |
| 1604 | color: #282828; | 1578 | color: #282828; |
| @@ -1608,27 +1582,27 @@ button::after { | @@ -1608,27 +1582,27 @@ button::after { | ||
| 1608 | flex-shrink: 0; // /* 不被父容器挤压变形,保持固定大小 */ | 1582 | flex-shrink: 0; // /* 不被父容器挤压变形,保持固定大小 */ |
| 1609 | border-radius: 50%; | 1583 | border-radius: 50%; |
| 1610 | // line-height: 60rpx; | 1584 | // line-height: 60rpx; |
| 1611 | -} | 1585 | + } |
| 1612 | 1586 | ||
| 1613 | -.superset-wrapper .set-column { | 1587 | + .set-column { |
| 1614 | display: flex; | 1588 | display: flex; |
| 1615 | flex-direction: column; | 1589 | flex-direction: column; |
| 1616 | -} | 1590 | + } |
| 1617 | 1591 | ||
| 1618 | -.superset-wrapper .action-in-set { | 1592 | + .action-in-set { |
| 1619 | // display: flex; | 1593 | // display: flex; |
| 1620 | // flex-direction: column; | 1594 | // flex-direction: column; |
| 1621 | width: 100%; | 1595 | width: 100%; |
| 1622 | display: flex; | 1596 | display: flex; |
| 1623 | align-items: center; | 1597 | align-items: center; |
| 1624 | margin-bottom: 8rpx; | 1598 | margin-bottom: 8rpx; |
| 1625 | -} | 1599 | + } |
| 1626 | 1600 | ||
| 1627 | -.superset-wrapper .action-in-set:last-child { | 1601 | + .action-in-set:last-child { |
| 1628 | margin-bottom: 0; | 1602 | margin-bottom: 0; |
| 1629 | -} | 1603 | + } |
| 1630 | 1604 | ||
| 1631 | -.superset-wrapper .action-letter { | 1605 | + .action-letter { |
| 1632 | display: inline-block; | 1606 | display: inline-block; |
| 1633 | font-size: 26rpx; | 1607 | font-size: 26rpx; |
| 1634 | color: #666; | 1608 | color: #666; |
| @@ -1636,28 +1610,27 @@ button::after { | @@ -1636,28 +1610,27 @@ button::after { | ||
| 1636 | white-space: nowrap; | 1610 | white-space: nowrap; |
| 1637 | margin-right: 12rpx; | 1611 | margin-right: 12rpx; |
| 1638 | flex-shrink: 0; | 1612 | flex-shrink: 0; |
| 1639 | -} | 1613 | + } |
| 1640 | 1614 | ||
| 1641 | -.superset-wrapper .set-content { | 1615 | + .set-content { |
| 1642 | font-size: 26rpx; | 1616 | font-size: 26rpx; |
| 1643 | color: inherit; | 1617 | color: inherit; |
| 1644 | flex: 1; | 1618 | flex: 1; |
| 1645 | -} | 1619 | + } |
| 1646 | 1620 | ||
| 1647 | -.superset-wrapper .rest-time { | 1621 | + .rest-time { |
| 1648 | font-size: 24rpx; | 1622 | font-size: 24rpx; |
| 1649 | // color: inherit; | 1623 | // color: inherit; |
| 1650 | margin-left: 10rpx; | 1624 | margin-left: 10rpx; |
| 1651 | flex-shrink: 0; | 1625 | flex-shrink: 0; |
| 1652 | -} | 1626 | + } |
| 1653 | 1627 | ||
| 1654 | -.superset-wrapper .check { | 1628 | + .check { |
| 1655 | font-size: 26rpx; | 1629 | font-size: 26rpx; |
| 1656 | color: #333; | 1630 | color: #333; |
| 1657 | margin-left: 275rpx !important; | 1631 | margin-left: 275rpx !important; |
| 1658 | - /* 这里控制和左边内容的距离,数值你可以自己调 */ | ||
| 1659 | flex-shrink: 0; | 1632 | flex-shrink: 0; |
| 1660 | - /* 防止被压缩 */ | 1633 | + } |
| 1661 | } | 1634 | } |
| 1662 | 1635 | ||
| 1663 | .superset-title { | 1636 | .superset-title { |
| @@ -1673,16 +1646,14 @@ button::after { | @@ -1673,16 +1646,14 @@ button::after { | ||
| 1673 | .noteContent { | 1646 | .noteContent { |
| 1674 | display: flex; | 1647 | display: flex; |
| 1675 | align-items: flex-start; | 1648 | align-items: flex-start; |
| 1676 | - /* 改成顶部对齐,而不是居中 */ | ||
| 1677 | gap: 12rpx; | 1649 | gap: 12rpx; |
| 1678 | padding: 20rpx; | 1650 | padding: 20rpx; |
| 1679 | - background: #fff; | 1651 | + background: $color-white; |
| 1680 | } | 1652 | } |
| 1681 | 1653 | ||
| 1682 | .noteTitle { | 1654 | .noteTitle { |
| 1683 | font-size: 26rpx; | 1655 | font-size: 26rpx; |
| 1684 | color: #666; | 1656 | color: #666; |
| 1685 | - /* 关键:固定宽度,保证标签容器的左边距稳定 */ | ||
| 1686 | flex-shrink: 0; | 1657 | flex-shrink: 0; |
| 1687 | width: 150rpx; | 1658 | width: 150rpx; |
| 1688 | } | 1659 | } |
| @@ -1694,11 +1665,10 @@ button::after { | @@ -1694,11 +1665,10 @@ button::after { | ||
| 1694 | gap: 12rpx; | 1665 | gap: 12rpx; |
| 1695 | } | 1666 | } |
| 1696 | 1667 | ||
| 1697 | -.noteTag { | 1668 | +// u-tag 微调 |
| 1669 | +:deep(.noteTag) { | ||
| 1670 | + width: auto !important; | ||
| 1698 | font-size: 24rpx; | 1671 | font-size: 24rpx; |
| 1699 | - color: #fff; | ||
| 1700 | - padding: 6rpx 14rpx; | ||
| 1701 | border-radius: 10rpx; | 1672 | border-radius: 10rpx; |
| 1702 | - line-height: 1.2; | ||
| 1703 | } | 1673 | } |
| 1704 | </style> | 1674 | </style> |
| @@ -12,7 +12,7 @@ | @@ -12,7 +12,7 @@ | ||
| 12 | <scroll-view class="pop-content" scroll-y> | 12 | <scroll-view class="pop-content" scroll-y> |
| 13 | <dongzuo v-if="isRenderDongzuo" :id="currUnit.unitId" :type="currUnit.unitType" :actionDetail="convertAction" | 13 | <dongzuo v-if="isRenderDongzuo" :id="currUnit.unitId" :type="currUnit.unitType" :actionDetail="convertAction" |
| 14 | :unitIndex="currUnitIndex" ref="dongzuoSingleRef" @deleteAction.prevent="handleDeleteAction" | 14 | :unitIndex="currUnitIndex" ref="dongzuoSingleRef" @deleteAction.prevent="handleDeleteAction" |
| 15 | - @replace-action.prevent="handleReplaceAction" :is-daily-templates="true" /> | 15 | + @replace-action.prevent="handleReplaceAction" :is-daily-templates="true" :is-tick="false"/> |
| 16 | </scroll-view> | 16 | </scroll-view> |
| 17 | </view> | 17 | </view> |
| 18 | 18 |
| 1 | <template> | 1 | <template> |
| 2 | - <!-- 自定义底部弹窗 --> | ||
| 3 | - <view v-if="showPopup" class="custom-mask" @click="handleMaskClick"> | ||
| 4 | - <view class="custom-popup" @click.stop> | 2 | + <!-- 底部弹窗:训练日历 --> |
| 3 | + <up-popup | ||
| 4 | + :show="showPopup" | ||
| 5 | + mode="bottom" | ||
| 6 | + round="24rpx" | ||
| 7 | + :safeAreaInsetBottom="true" | ||
| 8 | + @close="close" | ||
| 9 | + > | ||
| 10 | + <view class="calendar-popup-content"> | ||
| 5 | <!-- 标题 + 月份导航 --> | 11 | <!-- 标题 + 月份导航 --> |
| 6 | <view class="popup-header"> | 12 | <view class="popup-header"> |
| 7 | - <text class="title">训练日历</text> | ||
| 8 | - <view class="month-nav"> | ||
| 9 | - <u-icon name="arrow-left" size="24" @click="prevMonth"></u-icon> | ||
| 10 | - <text class="month-text">{{ currentYearMonth }}</text> | ||
| 11 | - <u-icon name="arrow-right" size="24" @click="nextMonth"></u-icon> | 13 | + <text class="header-title">训练日历</text> |
| 14 | + <view class="month-navigator"> | ||
| 15 | + <u-icon name="arrow-left" size="24" @click="prevMonth" /> | ||
| 16 | + <text class="month-label">{{ currentYearMonth }}</text> | ||
| 17 | + <u-icon name="arrow-right" size="24" @click="nextMonth" /> | ||
| 12 | </view> | 18 | </view> |
| 13 | </view> | 19 | </view> |
| 14 | 20 | ||
| 15 | - <!-- 自定义日历主体 --> | ||
| 16 | - <view class="calendar-container"> | 21 | + <!-- 日历主体 --> |
| 22 | + <view class="calendar-body"> | ||
| 17 | <!-- 星期行 --> | 23 | <!-- 星期行 --> |
| 18 | - <view class="week-row"> | ||
| 19 | - <text v-for="week in weekDays" :key="week" class="week-day">{{ week }}</text> | 24 | + <view class="week-header"> |
| 25 | + <text | ||
| 26 | + v-for="(day, idx) in WEEK_DAYS" | ||
| 27 | + :key="idx" | ||
| 28 | + class="week-item" | ||
| 29 | + :class="{ weekend: idx === 0 || idx === 6 }" | ||
| 30 | + > | ||
| 31 | + {{ day }} | ||
| 32 | + </text> | ||
| 20 | </view> | 33 | </view> |
| 21 | - <!-- 日期格子 --> | ||
| 22 | - <view class="days-grid"> | ||
| 23 | - <view v-for="(day, idx) in calendarDays" :key="idx" class="day-cell" :class="{ | ||
| 24 | - 'other-month': !day.isCurrentMonth, | ||
| 25 | - 'selected': isDateSelected(day.fullDate), | ||
| 26 | - 'disabled': day.fullDate < minDateStr | ||
| 27 | - }" @click="toggleDate(day)"> | ||
| 28 | - <text class="day-number">{{ day.day }}</text> | 34 | + |
| 35 | + <!-- 日期网格 --> | ||
| 36 | + <view class="date-grid"> | ||
| 37 | + <view | ||
| 38 | + v-for="(item, idx) in calendarDays" | ||
| 39 | + :key="idx" | ||
| 40 | + class="date-cell" | ||
| 41 | + :class="getDateCellClass(item)" | ||
| 42 | + @click="toggleDate(item)" | ||
| 43 | + > | ||
| 44 | + <text class="date-text">{{ item.day }}</text> | ||
| 29 | </view> | 45 | </view> |
| 30 | </view> | 46 | </view> |
| 31 | </view> | 47 | </view> |
| 32 | 48 | ||
| 33 | - <!-- 底部确认按钮 --> | 49 | + <!-- 底部操作 --> |
| 34 | <view class="popup-footer"> | 50 | <view class="popup-footer"> |
| 35 | - <u-button type="primary" size="large" :disabled="selectedDates.length === 0" @click="confirmAdd"> | 51 | + <view class="selected-hint" v-if="selectedDates.length"> |
| 52 | + 已选 {{ selectedDates.length }} 天 | ||
| 53 | + </view> | ||
| 54 | + <u-button | ||
| 55 | + type="primary" | ||
| 56 | + size="large" | ||
| 57 | + :disabled="!selectedDates.length" | ||
| 58 | + @click="confirmAdd" | ||
| 59 | + > | ||
| 36 | 添加到以上日程 | 60 | 添加到以上日程 |
| 37 | </u-button> | 61 | </u-button> |
| 38 | </view> | 62 | </view> |
| 39 | </view> | 63 | </view> |
| 40 | - </view> | 64 | + </up-popup> |
| 41 | </template> | 65 | </template> |
| 42 | 66 | ||
| 43 | <script setup> | 67 | <script setup> |
| 44 | -import { ref, computed, watch } from 'vue'; | 68 | +import { ref, computed } from 'vue'; |
| 69 | +import dayjs from 'dayjs'; | ||
| 45 | import QueryPlanApi from '@/sheep/api/plan/queryplan'; | 70 | import QueryPlanApi from '@/sheep/api/plan/queryplan'; |
| 46 | import dailytemplateApi from '@/sheep/api/Template/Dailytemplate'; | 71 | import dailytemplateApi from '@/sheep/api/Template/Dailytemplate'; |
| 47 | 72 | ||
| 73 | +// ==================== 常量 ==================== | ||
| 74 | +const DATE_FORMAT = 'YYYY-MM-DD'; | ||
| 75 | +const WEEK_DAYS = ['日', '一', '二', '三', '四', '五', '六']; | ||
| 76 | +const TOTAL_GRID_CELLS = 42; // 6 行 × 7 列 | ||
| 77 | + | ||
| 78 | +// ==================== Props & Emits ==================== | ||
| 48 | const props = defineProps({ | 79 | const props = defineProps({ |
| 49 | planId: { type: [String, Number], required: false }, | 80 | planId: { type: [String, Number], required: false }, |
| 50 | templateId: { type: [String, Number], required: true, default: 0 }, | 81 | templateId: { type: [String, Number], required: true, default: 0 }, |
| 51 | - isMove: { | ||
| 52 | - type: Boolean, | ||
| 53 | - default: false | ||
| 54 | - }, | 82 | + isMove: { type: Boolean, default: false }, |
| 55 | isCopy: { type: Boolean, default: false }, | 83 | isCopy: { type: Boolean, default: false }, |
| 56 | - dailyTemplateId: { type: Number, default: 0 } | 84 | + dailyTemplateId: { type: Number, default: 0 }, |
| 57 | }); | 85 | }); |
| 86 | + | ||
| 58 | const emit = defineEmits(['success', 'clearCopyFlag']); | 87 | const emit = defineEmits(['success', 'clearCopyFlag']); |
| 59 | 88 | ||
| 60 | -// 弹窗显示控制 | 89 | +// ==================== 工具函数 ==================== |
| 90 | +/** 格式化 dayjs 实例为 YYYY-MM-DD 字符串 */ | ||
| 91 | +const fmtDate = (d) => d.format(DATE_FORMAT); | ||
| 92 | + | ||
| 93 | +/** 获取当天的 YYYY-MM-DD */ | ||
| 94 | +const getTodayStr = () => fmtDate(dayjs()); | ||
| 95 | + | ||
| 96 | +// ==================== 响应式状态 ==================== | ||
| 61 | const showPopup = ref(false); | 97 | const showPopup = ref(false); |
| 62 | 98 | ||
| 63 | -// 当前显示的日期(年、月) | ||
| 64 | -const currentYear = ref(new Date().getFullYear()); | ||
| 65 | -const currentMonth = ref(new Date().getMonth() + 1); // 1-12 | 99 | +// 当前视图的年月(用 dayjs 驱动) |
| 100 | +const viewDate = ref(dayjs()); | ||
| 66 | 101 | ||
| 67 | -// 已选择的日期列表(字符串数组,格式 YYYY-MM-DD) | 102 | +// 已选日期集合 |
| 68 | const selectedDates = ref([]); | 103 | const selectedDates = ref([]); |
| 69 | 104 | ||
| 70 | // 最小可选日期(今天) | 105 | // 最小可选日期(今天) |
| 71 | -const today = new Date(); | ||
| 72 | -const minDateStr = `${today.getFullYear()}-${(today.getMonth() + 1).toString().padStart(2, '0')}-${today.getDate().toString().padStart(2, '0')}`; | 106 | +const minDateStr = getTodayStr(); |
| 73 | 107 | ||
| 74 | -// 星期标题 | ||
| 75 | -const weekDays = ['日', '一', '二', '三', '四', '五', '六']; | 108 | +// ==================== 计算属性 ==================== |
| 109 | +/** 当前显示的年-月文本 */ | ||
| 110 | +const currentYearMonth = computed(() => viewDate.value.format('YYYY年MM月')); | ||
| 76 | 111 | ||
| 77 | -// 当前年月显示文本 | ||
| 78 | -const currentYearMonth = computed(() => `${currentYear.value}年${currentMonth.value.toString().padStart(2, '0')}月`); | ||
| 79 | - | ||
| 80 | -// 生成当前月份的日历数据(包含上个月和下个月的占位) | 112 | +/** 生成日历网格数据 */ |
| 81 | const calendarDays = computed(() => { | 113 | const calendarDays = computed(() => { |
| 82 | - const year = currentYear.value; | ||
| 83 | - const month = currentMonth.value; | ||
| 84 | - const firstDayOfMonth = new Date(year, month - 1, 1); | ||
| 85 | - const startWeekday = firstDayOfMonth.getDay(); // 0=周日 | ||
| 86 | - const daysInMonth = new Date(year, month, 0).getDate(); | ||
| 87 | - | ||
| 88 | - // 获取上个月的最后几天 | ||
| 89 | - const prevMonthDate = new Date(year, month - 1, 0); | ||
| 90 | - const daysInPrevMonth = prevMonthDate.getDate(); | 114 | + const vd = viewDate.value; |
| 115 | + const year = vd.year(); | ||
| 116 | + const month = vd.month(); // 0-11 | ||
| 117 | + | ||
| 118 | + // 当月第一天 & 当月天数 | ||
| 119 | + const firstDay = dayjs(new Date(year, month, 1)); | ||
| 120 | + const startWeekday = firstDay.day(); // 0=周日 | ||
| 121 | + const daysInMonth = firstDay.daysInMonth(); | ||
| 122 | + | ||
| 123 | + // 上个月补位 | ||
| 91 | const prevMonthDays = []; | 124 | const prevMonthDays = []; |
| 125 | + const prevLastDay = firstDay.subtract(1, 'day'); | ||
| 126 | + const prevDaysInMonth = prevLastDay.date(); | ||
| 92 | for (let i = startWeekday - 1; i >= 0; i--) { | 127 | for (let i = startWeekday - 1; i >= 0; i--) { |
| 93 | - const dayNum = daysInPrevMonth - i; | ||
| 94 | - const fullDate = `${prevMonthDate.getFullYear()}-${(prevMonthDate.getMonth() + 1).toString().padStart(2, '0')}-${dayNum.toString().padStart(2, '0')}`; | ||
| 95 | - prevMonthDays.push({ | ||
| 96 | - day: dayNum, | ||
| 97 | - fullDate: fullDate, | ||
| 98 | - isCurrentMonth: false, | ||
| 99 | - }); | 128 | + const d = dayjs(new Date(year, month - 1, prevDaysInMonth - i)); |
| 129 | + prevMonthDays.push({ day: d.date(), fullDate: fmtDate(d), isCurrentMonth: false }); | ||
| 100 | } | 130 | } |
| 101 | 131 | ||
| 102 | - // 当月天数 | ||
| 103 | - const currentMonthDays = []; | 132 | + // 当月 |
| 133 | + const curMonthDays = []; | ||
| 104 | for (let i = 1; i <= daysInMonth; i++) { | 134 | for (let i = 1; i <= daysInMonth; i++) { |
| 105 | - const fullDate = `${year}-${month.toString().padStart(2, '0')}-${i.toString().padStart(2, '0')}`; | ||
| 106 | - currentMonthDays.push({ | ||
| 107 | - day: i, | ||
| 108 | - fullDate: fullDate, | ||
| 109 | - isCurrentMonth: true, | ||
| 110 | - }); | 135 | + const d = dayjs(new Date(year, month, i)); |
| 136 | + curMonthDays.push({ day: i, fullDate: fmtDate(d), isCurrentMonth: true }); | ||
| 111 | } | 137 | } |
| 112 | 138 | ||
| 113 | - // 下个月补齐(总共42格,6行) | ||
| 114 | - const totalCells = 42; | ||
| 115 | - const remaining = totalCells - (prevMonthDays.length + currentMonthDays.length); | 139 | + // 下个月补位 |
| 116 | const nextMonthDays = []; | 140 | const nextMonthDays = []; |
| 141 | + const remaining = TOTAL_GRID_CELLS - prevMonthDays.length - curMonthDays.length; | ||
| 117 | for (let i = 1; i <= remaining; i++) { | 142 | for (let i = 1; i <= remaining; i++) { |
| 118 | - const nextDate = new Date(year, month, i); | ||
| 119 | - const fullDate = `${nextDate.getFullYear()}-${(nextDate.getMonth() + 1).toString().padStart(2, '0')}-${nextDate.getDate().toString().padStart(2, '0')}`; | ||
| 120 | - nextMonthDays.push({ | ||
| 121 | - day: i, | ||
| 122 | - fullDate: fullDate, | ||
| 123 | - isCurrentMonth: false, | ||
| 124 | - }); | 143 | + const d = dayjs(new Date(year, month + 1, i)); |
| 144 | + nextMonthDays.push({ day: i, fullDate: fmtDate(d), isCurrentMonth: false }); | ||
| 125 | } | 145 | } |
| 126 | 146 | ||
| 127 | - return [...prevMonthDays, ...currentMonthDays, ...nextMonthDays]; | 147 | + return [...prevMonthDays, ...curMonthDays, ...nextMonthDays]; |
| 128 | }); | 148 | }); |
| 129 | 149 | ||
| 130 | -// 检查某天是否被选中 | ||
| 131 | -const isDateSelected = (fullDate) => { | ||
| 132 | - return selectedDates.value.includes(fullDate); | ||
| 133 | -}; | 150 | +// ==================== 方法 ==================== |
| 151 | +/** 判断日期是否被选中 */ | ||
| 152 | +const isDateSelected = (fullDate) => selectedDates.value.includes(fullDate); | ||
| 134 | 153 | ||
| 135 | -// 切换日期选中状态(仅允许选择今天及之后) | ||
| 136 | -const toggleDate = (day) => { | ||
| 137 | - if (day.fullDate < minDateStr) { | 154 | +/** 判断是否为今天 */ |
| 155 | +const isToday = (fullDate) => fullDate === getTodayStr(); | ||
| 156 | + | ||
| 157 | +/** 获取日期格子的样式类 */ | ||
| 158 | +const getDateCellClass = (item) => ({ | ||
| 159 | + 'other-month': !item.isCurrentMonth, | ||
| 160 | + selected: isDateSelected(item.fullDate), | ||
| 161 | + disabled: item.fullDate < minDateStr, | ||
| 162 | + today: isToday(item.fullDate), | ||
| 163 | +}); | ||
| 164 | + | ||
| 165 | +/** 切换日期选中状态 */ | ||
| 166 | +const toggleDate = (item) => { | ||
| 167 | + if (item.fullDate < minDateStr) { | ||
| 138 | uni.showToast({ title: '不能选择过去的日期', icon: 'none' }); | 168 | uni.showToast({ title: '不能选择过去的日期', icon: 'none' }); |
| 139 | return; | 169 | return; |
| 140 | } | 170 | } |
| 141 | - // 移动模式下,只能选择一个日期 | 171 | + // 移动模式:单选 |
| 142 | if (props.isMove) { | 172 | if (props.isMove) { |
| 143 | - selectedDates.value = [day.fullDate]; // 直接覆盖,永远只有1个 | 173 | + selectedDates.value = [item.fullDate]; |
| 144 | return; | 174 | return; |
| 145 | } | 175 | } |
| 146 | - const index = selectedDates.value.indexOf(day.fullDate); | ||
| 147 | - if (index === -1) { | ||
| 148 | - selectedDates.value.push(day.fullDate); | 176 | + // 普通模式:多选切换 |
| 177 | + const idx = selectedDates.value.indexOf(item.fullDate); | ||
| 178 | + if (idx === -1) { | ||
| 179 | + selectedDates.value.push(item.fullDate); | ||
| 149 | } else { | 180 | } else { |
| 150 | - selectedDates.value.splice(index, 1); | 181 | + selectedDates.value.splice(idx, 1); |
| 151 | } | 182 | } |
| 152 | }; | 183 | }; |
| 153 | 184 | ||
| 154 | -// 月份切换 | 185 | +/** 上一月 */ |
| 155 | const prevMonth = () => { | 186 | const prevMonth = () => { |
| 156 | - if (currentMonth.value === 1) { | ||
| 157 | - currentYear.value--; | ||
| 158 | - currentMonth.value = 12; | ||
| 159 | - } else { | ||
| 160 | - currentMonth.value--; | ||
| 161 | - } | 187 | + viewDate.value = viewDate.value.subtract(1, 'month'); |
| 162 | }; | 188 | }; |
| 189 | + | ||
| 190 | +/** 下一月 */ | ||
| 163 | const nextMonth = () => { | 191 | const nextMonth = () => { |
| 164 | - if (currentMonth.value === 12) { | ||
| 165 | - currentYear.value++; | ||
| 166 | - currentMonth.value = 1; | ||
| 167 | - } else { | ||
| 168 | - currentMonth.value++; | ||
| 169 | - } | 192 | + viewDate.value = viewDate.value.add(1, 'month'); |
| 193 | +}; | ||
| 194 | + | ||
| 195 | +/** 重置到当前月份 */ | ||
| 196 | +const resetToToday = () => { | ||
| 197 | + viewDate.value = dayjs(); | ||
| 170 | }; | 198 | }; |
| 171 | 199 | ||
| 172 | -// 关闭弹窗 | ||
| 173 | -const handleMaskClick = () => close(); | 200 | +/** 关闭弹窗 */ |
| 174 | const close = () => { | 201 | const close = () => { |
| 175 | showPopup.value = false; | 202 | showPopup.value = false; |
| 176 | selectedDates.value = []; | 203 | selectedDates.value = []; |
| 177 | - // 重置到当前月份(可选) | ||
| 178 | - currentYear.value = new Date().getFullYear(); | ||
| 179 | - currentMonth.value = new Date().getMonth() + 1; | ||
| 180 | - emit('clearCopyFlag') | 204 | + resetToToday(); |
| 205 | + emit('clearCopyFlag'); | ||
| 181 | }; | 206 | }; |
| 182 | 207 | ||
| 183 | -// 打开弹窗(暴露给父组件) | 208 | +/** 打开弹窗 */ |
| 184 | const open = () => { | 209 | const open = () => { |
| 185 | - console.log('子组件open方法被调用了'); | ||
| 186 | showPopup.value = true; | 210 | showPopup.value = true; |
| 187 | selectedDates.value = []; | 211 | selectedDates.value = []; |
| 188 | - currentYear.value = new Date().getFullYear(); | ||
| 189 | - currentMonth.value = new Date().getMonth() + 1; | 212 | + resetToToday(); |
| 190 | }; | 213 | }; |
| 191 | 214 | ||
| 192 | -// 确认添加 | 215 | +/** 确认添加 */ |
| 193 | const confirmAdd = async () => { | 216 | const confirmAdd = async () => { |
| 194 | - if (selectedDates.value.length === 0) { | 217 | + if (!selectedDates.value.length) { |
| 195 | uni.showToast({ title: '请选择训练日期', icon: 'none' }); | 218 | uni.showToast({ title: '请选择训练日期', icon: 'none' }); |
| 196 | return; | 219 | return; |
| 197 | } | 220 | } |
| 198 | try { | 221 | try { |
| 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 | if (props.isCopy) { |
| 222 | - // 调用你新增的复制接口 | ||
| 223 | - await dailytemplateApi.copyTempleteDailyTemplate(props.dailyTemplateId, selectedDates.value) | 223 | + await dailytemplateApi.copyTempleteDailyTemplate( |
| 224 | + props.dailyTemplateId, | ||
| 225 | + selectedDates.value, | ||
| 226 | + ); | ||
| 224 | uni.showToast({ title: '复制成功', icon: 'success' }); | 227 | uni.showToast({ title: '复制成功', icon: 'success' }); |
| 225 | - console.log('调用复制接口成功'); | ||
| 226 | - | ||
| 227 | } else if (props.isMove) { | 228 | } else if (props.isMove) { |
| 228 | - // 移动到 | ||
| 229 | - const params = { | 229 | + await QueryPlanApi.updateDailyTemplateDate({ |
| 230 | id: props.templateId, | 230 | id: props.templateId, |
| 231 | - trainingDate: selectedDates.value[0] || '' | ||
| 232 | - }; | ||
| 233 | - await QueryPlanApi.updateDailyTemplateDate(params); | 231 | + trainingDate: selectedDates.value[0] || '', |
| 232 | + }); | ||
| 234 | uni.showToast({ title: '移动成功', icon: 'success' }); | 233 | uni.showToast({ title: '移动成功', icon: 'success' }); |
| 235 | - console.log('移动接口成功'); | ||
| 236 | } else { | 234 | } else { |
| 237 | - //添加到日历 | ||
| 238 | - const params = { | 235 | + await QueryPlanApi.addPlanToCalendar({ |
| 239 | id: props.templateId, | 236 | id: props.templateId, |
| 240 | planId: props.planId, | 237 | planId: props.planId, |
| 241 | - trainDateList: selectedDates.value | ||
| 242 | - }; | ||
| 243 | - await QueryPlanApi.addPlanToCalendar(params); | 238 | + trainDateList: selectedDates.value, |
| 239 | + }); | ||
| 244 | uni.showToast({ title: '添加到日历成功', icon: 'success' }); | 240 | uni.showToast({ title: '添加到日历成功', icon: 'success' }); |
| 245 | - console.log('添加到接口成功'); | ||
| 246 | } | 241 | } |
| 247 | - | ||
| 248 | - | ||
| 249 | close(); | 242 | close(); |
| 250 | emit('success'); | 243 | emit('success'); |
| 251 | } catch (err) { | 244 | } catch (err) { |
| @@ -258,120 +251,162 @@ defineExpose({ open, close }); | @@ -258,120 +251,162 @@ defineExpose({ open, close }); | ||
| 258 | </script> | 251 | </script> |
| 259 | 252 | ||
| 260 | <style scoped lang="scss"> | 253 | <style scoped lang="scss"> |
| 261 | -.custom-mask { | ||
| 262 | - position: fixed; | ||
| 263 | - top: 0; | ||
| 264 | - left: 0; | ||
| 265 | - right: 0; | ||
| 266 | - bottom: 0; | ||
| 267 | - background-color: rgba(0, 0, 0, 0.5); | ||
| 268 | - z-index: 9999; | ||
| 269 | - display: flex; | ||
| 270 | - align-items: flex-end; | ||
| 271 | -} | ||
| 272 | - | ||
| 273 | -.custom-popup { | ||
| 274 | - background: #fff; | ||
| 275 | - border-radius: 24rpx 24rpx 0 0; | 254 | +/* ==================== 弹窗内容容器 ==================== */ |
| 255 | +.calendar-popup-content { | ||
| 276 | padding: 30rpx; | 256 | padding: 30rpx; |
| 277 | - box-sizing: border-box; | ||
| 278 | - width: 100%; | ||
| 279 | - max-height: 80vh; | ||
| 280 | - overflow-y: auto; | 257 | + min-height: 50vh; |
| 258 | + background: #fff; | ||
| 281 | } | 259 | } |
| 282 | 260 | ||
| 261 | +/* ==================== 头部 ==================== */ | ||
| 283 | .popup-header { | 262 | .popup-header { |
| 284 | display: flex; | 263 | display: flex; |
| 285 | justify-content: space-between; | 264 | justify-content: space-between; |
| 286 | align-items: center; | 265 | align-items: center; |
| 287 | - margin-bottom: 30rpx; | 266 | + margin-bottom: 28rpx; |
| 288 | 267 | ||
| 289 | - .title { | ||
| 290 | - font-size: 32rpx; | ||
| 291 | - font-weight: bold; | ||
| 292 | - color: #333; | 268 | + .header-title { |
| 269 | + font-size: 34rpx; | ||
| 270 | + font-weight: 700; | ||
| 271 | + color: #1a1a1a; | ||
| 293 | } | 272 | } |
| 294 | 273 | ||
| 295 | - .month-nav { | 274 | + .month-navigator { |
| 296 | display: flex; | 275 | display: flex; |
| 297 | align-items: center; | 276 | align-items: center; |
| 298 | - gap: 20rpx; | 277 | + gap: 24rpx; |
| 299 | 278 | ||
| 300 | - .month-text { | ||
| 301 | - font-size: 28rpx; | 279 | + .month-label { |
| 280 | + font-size: 30rpx; | ||
| 281 | + font-weight: 500; | ||
| 302 | color: #333; | 282 | color: #333; |
| 283 | + min-width: 140rpx; | ||
| 284 | + text-align: center; | ||
| 303 | } | 285 | } |
| 304 | } | 286 | } |
| 305 | } | 287 | } |
| 306 | 288 | ||
| 307 | -.calendar-container { | ||
| 308 | - margin-bottom: 30rpx; | 289 | +/* ==================== 日历主体 ==================== */ |
| 290 | +.calendar-body { | ||
| 291 | + flex: 1; | ||
| 309 | } | 292 | } |
| 310 | 293 | ||
| 311 | -.week-row { | 294 | +.week-header { |
| 312 | display: flex; | 295 | display: flex; |
| 313 | - justify-content: space-around; | ||
| 314 | - margin-bottom: 20rpx; | 296 | + margin-bottom: 16rpx; |
| 297 | + padding: 0 4rpx; | ||
| 315 | 298 | ||
| 316 | - .week-day { | ||
| 317 | - width: 14.28%; | 299 | + .week-item { |
| 300 | + flex: 1; | ||
| 318 | text-align: center; | 301 | text-align: center; |
| 319 | - font-size: 28rpx; | 302 | + font-size: 26rpx; |
| 320 | color: #999; | 303 | color: #999; |
| 304 | + padding: 8rpx 0; | ||
| 305 | + | ||
| 306 | + &.weekend { | ||
| 307 | + color: #e6a23c; | ||
| 308 | + } | ||
| 321 | } | 309 | } |
| 322 | } | 310 | } |
| 323 | 311 | ||
| 324 | -.days-grid { | 312 | +.date-grid { |
| 325 | display: flex; | 313 | display: flex; |
| 326 | flex-wrap: wrap; | 314 | flex-wrap: wrap; |
| 327 | } | 315 | } |
| 328 | 316 | ||
| 329 | -.day-cell { | ||
| 330 | - width: 14.28%; | ||
| 331 | - aspect-ratio: 1 / 1; | 317 | +.date-cell { |
| 318 | + width: calc(100% / 7); | ||
| 319 | + aspect-ratio: 1; | ||
| 332 | display: flex; | 320 | display: flex; |
| 333 | align-items: center; | 321 | align-items: center; |
| 334 | justify-content: center; | 322 | justify-content: center; |
| 335 | - border-radius: 8rpx; | ||
| 336 | - margin-bottom: 8rpx; | 323 | + border-radius: 50%; |
| 324 | + position: relative; | ||
| 325 | + transition: background-color 0.2s, transform 0.15s; | ||
| 337 | 326 | ||
| 338 | - .day-number { | 327 | + .date-text { |
| 339 | font-size: 28rpx; | 328 | font-size: 28rpx; |
| 340 | color: #333; | 329 | color: #333; |
| 330 | + position: relative; | ||
| 331 | + z-index: 1; | ||
| 341 | } | 332 | } |
| 342 | 333 | ||
| 343 | - &.other-month .day-number { | ||
| 344 | - color: #ccc; | 334 | + /* 非当月 */ |
| 335 | + &.other-month { | ||
| 336 | + opacity: 0; | ||
| 337 | + pointer-events: none; | ||
| 345 | } | 338 | } |
| 346 | 339 | ||
| 340 | + /* 今天 */ | ||
| 341 | + &.today { | ||
| 342 | + .date-text { | ||
| 343 | + color: #409eff; | ||
| 344 | + font-weight: 600; | ||
| 345 | + &::after { | ||
| 346 | + content: ''; | ||
| 347 | + position: absolute; | ||
| 348 | + bottom: -4rpx; | ||
| 349 | + left: 50%; | ||
| 350 | + transform: translateX(-50%); | ||
| 351 | + width: 8rpx; | ||
| 352 | + height: 8rpx; | ||
| 353 | + border-radius: 50%; | ||
| 354 | + background: #409eff; | ||
| 355 | + } | ||
| 356 | + } | ||
| 357 | + } | ||
| 358 | + | ||
| 359 | + /* 已选中 */ | ||
| 347 | &.selected { | 360 | &.selected { |
| 348 | - background-color: #ffd700; | 361 | + background: linear-gradient(135deg, #ffd700, #ffb800); |
| 362 | + | ||
| 363 | + .date-text { | ||
| 364 | + color: #1a1a1a; | ||
| 365 | + font-weight: 700; | ||
| 366 | + } | ||
| 349 | 367 | ||
| 350 | - .day-number { | ||
| 351 | - color: #000; | ||
| 352 | - font-weight: bold; | 368 | + &.today .date-text::after { |
| 369 | + background: #1a1a1a; | ||
| 353 | } | 370 | } |
| 354 | } | 371 | } |
| 355 | 372 | ||
| 373 | + /* 不可选(过去日期) */ | ||
| 356 | &.disabled { | 374 | &.disabled { |
| 357 | - opacity: 0.4; | 375 | + opacity: 0.3; |
| 358 | pointer-events: none; | 376 | pointer-events: none; |
| 359 | } | 377 | } |
| 378 | + | ||
| 379 | + /* 可点击态 */ | ||
| 380 | + &:not(.disabled):not(.other-month):active { | ||
| 381 | + transform: scale(0.9); | ||
| 382 | + background-color: #f0f0f0; | ||
| 383 | + } | ||
| 360 | } | 384 | } |
| 361 | 385 | ||
| 386 | +/* ==================== 底部 ==================== */ | ||
| 362 | .popup-footer { | 387 | .popup-footer { |
| 363 | - margin-top: 30rpx; | ||
| 364 | - padding-top: 30rpx; | 388 | + margin-top: 24rpx; |
| 389 | + padding-top: 20rpx; | ||
| 390 | + border-top: 1rpx solid #f0f0f0; | ||
| 391 | + | ||
| 392 | + .selected-hint { | ||
| 393 | + text-align: center; | ||
| 394 | + font-size: 24rpx; | ||
| 395 | + color: #999; | ||
| 396 | + margin-bottom: 16rpx; | ||
| 397 | + } | ||
| 365 | 398 | ||
| 366 | .u-button { | 399 | .u-button { |
| 367 | background-color: #ffd700 !important; | 400 | background-color: #ffd700 !important; |
| 368 | - color: #000; | ||
| 369 | - font-weight: bold; | ||
| 370 | - border-radius: 40rpx; | 401 | + color: #1a1a1a; |
| 402 | + font-weight: 700; | ||
| 403 | + font-size: 30rpx; | ||
| 404 | + border-radius: 44rpx; | ||
| 405 | + letter-spacing: 2rpx; | ||
| 371 | 406 | ||
| 372 | &.u-button--disabled { | 407 | &.u-button--disabled { |
| 373 | - background-color: #eee !important; | ||
| 374 | - color: #999 !important; | 408 | + background-color: #e8e8e8 !important; |
| 409 | + color: #bbb !important; | ||
| 375 | } | 410 | } |
| 376 | } | 411 | } |
| 377 | } | 412 | } |
| @@ -345,15 +345,23 @@ const chartSeries = computed(() => { | @@ -345,15 +345,23 @@ const chartSeries = computed(() => { | ||
| 345 | 345 | ||
| 346 | const dataKey = TREND_DATA_KEYS[NumberTabIndex.value] || 'volume'; | 346 | const dataKey = TREND_DATA_KEYS[NumberTabIndex.value] || 'volume'; |
| 347 | 347 | ||
| 348 | + /** 单位转换:秒→分钟,米→公里,保留一位小数 */ | ||
| 349 | + const convertVal = (raw) => { | ||
| 350 | + const num = Number(raw) || 0; | ||
| 351 | + if (dataKey === 'duration') return parseFloat((num / 60).toFixed(1)); | ||
| 352 | + if (dataKey === 'distance') return parseFloat((num / 1000).toFixed(1)); | ||
| 353 | + return num; | ||
| 354 | + }; | ||
| 355 | + | ||
| 348 | // 当前周期数据 | 356 | // 当前周期数据 |
| 349 | - const currData = fullDateKeys.map((dateStr) => currDataMap[dateStr]?.[dataKey] ?? 0); | 357 | + const currData = fullDateKeys.map((dateStr) => convertVal(currDataMap[dateStr]?.[dataKey] ?? 0)); |
| 350 | 358 | ||
| 351 | // 上一周期数据(按日期偏移匹配) | 359 | // 上一周期数据(按日期偏移匹配) |
| 352 | const lastData = fullDateKeys.map((dateStr) => { | 360 | const lastData = fullDateKeys.map((dateStr) => { |
| 353 | const targetKey = isWeekly | 361 | const targetKey = isWeekly |
| 354 | ? dayjs(dateStr).subtract(7, 'day').format('YYYY-MM-DD') | 362 | ? dayjs(dateStr).subtract(7, 'day').format('YYYY-MM-DD') |
| 355 | : dayjs(dateStr).subtract(1, 'month').format('YYYY-MM-DD'); | 363 | : dayjs(dateStr).subtract(1, 'month').format('YYYY-MM-DD'); |
| 356 | - return lastDataMap[targetKey]?.[dataKey] ?? 0; | 364 | + return convertVal(lastDataMap[targetKey]?.[dataKey] ?? 0); |
| 357 | }); | 365 | }); |
| 358 | 366 | ||
| 359 | const currName = isWeekly ? '本周' : '本月'; | 367 | const currName = isWeekly ? '本周' : '本月'; |
| @@ -372,11 +380,15 @@ const listData = computed(() => { | @@ -372,11 +380,15 @@ const listData = computed(() => { | ||
| 372 | if (!weeklyData.value) return []; | 380 | if (!weeklyData.value) return []; |
| 373 | 381 | ||
| 374 | const d = weeklyData.value; | 382 | const d = weeklyData.value; |
| 383 | + /** 单位转换:秒→分钟,米→公里,保留一位小数 */ | ||
| 384 | + const toMin = (v) => parseFloat(((v ?? 0) / 60).toFixed(1)); | ||
| 385 | + const toKm = (v) => parseFloat(((v ?? 0) / 1000).toFixed(1)); | ||
| 386 | + | ||
| 375 | const fields = [ | 387 | const fields = [ |
| 376 | { label: '容量', last: d.lastWeekTotalVolume, curr: d.totalVolume }, | 388 | { label: '容量', last: d.lastWeekTotalVolume, curr: d.totalVolume }, |
| 377 | { label: '组数', last: d.lastWeekTotalSets, curr: d.totalSets }, | 389 | { label: '组数', last: d.lastWeekTotalSets, curr: d.totalSets }, |
| 378 | - { label: '时长', last: d.lastWeekTotalDuration, curr: d.totalDuration }, | ||
| 379 | - { label: '距离', last: d.lastWeekTotalDistance, curr: d.totalDistance }, | 390 | + { label: '时长', last: toMin(d.lastWeekTotalDuration), curr: toMin(d.totalDuration) }, |
| 391 | + { label: '距离', last: toKm(d.lastWeekTotalDistance), curr: toKm(d.totalDistance) }, | ||
| 380 | { label: '次数', last: d.lastWeekTotalCount, curr: d.totalCount }, | 392 | { label: '次数', last: d.lastWeekTotalCount, curr: d.totalCount }, |
| 381 | ]; | 393 | ]; |
| 382 | 394 | ||
| @@ -387,7 +399,7 @@ const listData = computed(() => { | @@ -387,7 +399,7 @@ const listData = computed(() => { | ||
| 387 | label, | 399 | label, |
| 388 | lastPeriod: lastVal, | 400 | lastPeriod: lastVal, |
| 389 | currPeriod: currVal, | 401 | currPeriod: currVal, |
| 390 | - diff: currVal - lastVal, | 402 | + diff: parseFloat((currVal - lastVal).toFixed(1)), |
| 391 | }; | 403 | }; |
| 392 | }); | 404 | }); |
| 393 | }); | 405 | }); |
| @@ -34,7 +34,7 @@ const emit = defineEmits(['success', 'close']) | @@ -34,7 +34,7 @@ const emit = defineEmits(['success', 'close']) | ||
| 34 | // 组件状态 | 34 | // 组件状态 |
| 35 | const show = ref(false) | 35 | const show = ref(false) |
| 36 | const folderList = ref([]) | 36 | const folderList = ref([]) |
| 37 | -const defaultCover = 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png' // 默认封面,可替换为你的本地图片 | 37 | +const defaultCover = 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260724/文件夹_1784867551849.png' // 默认封面,可替换为你的本地图片 |
| 38 | 38 | ||
| 39 | 39 | ||
| 40 | const props = defineProps({ | 40 | const props = defineProps({ |
| @@ -233,6 +233,11 @@ const handleDeleteTemplate = async () => { | @@ -233,6 +233,11 @@ const handleDeleteTemplate = async () => { | ||
| 233 | return; | 233 | return; |
| 234 | } | 234 | } |
| 235 | 235 | ||
| 236 | + // 先保存模板ID,再关闭菜单弹窗,避免弹窗叠加 | ||
| 237 | + const templateId = currentTemplate.value.id; | ||
| 238 | + closeTemplateMenu(); | ||
| 239 | + | ||
| 240 | + setTimeout(() => { | ||
| 236 | uni.showModal({ | 241 | uni.showModal({ |
| 237 | title: '确认删除', | 242 | title: '确认删除', |
| 238 | content: '确定要删除这个模板吗?删除后无法恢复', | 243 | content: '确定要删除这个模板吗?删除后无法恢复', |
| @@ -241,13 +246,11 @@ const handleDeleteTemplate = async () => { | @@ -241,13 +246,11 @@ const handleDeleteTemplate = async () => { | ||
| 241 | if (res.confirm) { | 246 | if (res.confirm) { |
| 242 | try { | 247 | try { |
| 243 | uni.showLoading({ title: '删除中...' }); | 248 | uni.showLoading({ title: '删除中...' }); |
| 244 | - await TemplatesApi.deleteCustTemplate(currentTemplate.value.id); | 249 | + await TemplatesApi.deleteCustTemplate(templateId); |
| 245 | uni.hideLoading(); | 250 | uni.hideLoading(); |
| 246 | uni.showToast({ title: '删除成功', icon: 'success' }); | 251 | uni.showToast({ title: '删除成功', icon: 'success' }); |
| 247 | - closeTemplateMenu(); | ||
| 248 | // 通知父组件刷新列表 | 252 | // 通知父组件刷新列表 |
| 249 | emit('refresh'); | 253 | emit('refresh'); |
| 250 | - | ||
| 251 | } catch (err) { | 254 | } catch (err) { |
| 252 | uni.hideLoading(); | 255 | uni.hideLoading(); |
| 253 | console.error('删除失败:', err); | 256 | console.error('删除失败:', err); |
| @@ -256,6 +259,7 @@ const handleDeleteTemplate = async () => { | @@ -256,6 +259,7 @@ const handleDeleteTemplate = async () => { | ||
| 256 | } | 259 | } |
| 257 | } | 260 | } |
| 258 | }); | 261 | }); |
| 262 | + }, 200); | ||
| 259 | }; | 263 | }; |
| 260 | 264 | ||
| 261 | 265 |
| 1 | <template> | 1 | <template> |
| 2 | <view class="container"> | 2 | <view class="container"> |
| 3 | + <!-- 加载中占位 --> | ||
| 4 | + <view class="loading-wrap" v-if="!isDataReady"> | ||
| 5 | + <text class="loading-text">加载中...</text> | ||
| 6 | + </view> | ||
| 7 | + | ||
| 3 | <!-- 我的分类 --> | 8 | <!-- 我的分类 --> |
| 4 | - <view class="section"> | 9 | + <view class="section" v-if="isDataReady"> |
| 5 | <view class="section-header"> | 10 | <view class="section-header"> |
| 6 | <text class="section-title">我的分类</text> | 11 | <text class="section-title">我的分类</text> |
| 7 | <text class="section-subtitle">点击隐藏分类</text> | 12 | <text class="section-subtitle">点击隐藏分类</text> |
| @@ -20,7 +25,7 @@ | @@ -20,7 +25,7 @@ | ||
| 20 | </view> | 25 | </view> |
| 21 | 26 | ||
| 22 | <!-- 已隐藏分类 --> | 27 | <!-- 已隐藏分类 --> |
| 23 | - <view class="section"> | 28 | + <view class="section" v-if="isDataReady"> |
| 24 | <view class="section-header"> | 29 | <view class="section-header"> |
| 25 | <text class="section-title">已隐藏分类</text> | 30 | <text class="section-title">已隐藏分类</text> |
| 26 | <text class="section-subtitle">点击恢复分类</text> | 31 | <text class="section-subtitle">点击恢复分类</text> |
| @@ -43,7 +48,6 @@ | @@ -43,7 +48,6 @@ | ||
| 43 | <!-- 底部按钮 --> | 48 | <!-- 底部按钮 --> |
| 44 | <view class="bottom-btns"> | 49 | <view class="bottom-btns"> |
| 45 | <view class="edit-btn" @click="openEditPopup">编辑名称</view> | 50 | <view class="edit-btn" @click="openEditPopup">编辑名称</view> |
| 46 | - <!-- 优化:点击打开自定义新增弹窗 --> | ||
| 47 | <view class="add-btn" @click="openAddPopup">+ 新增动作分类</view> | 51 | <view class="add-btn" @click="openAddPopup">+ 新增动作分类</view> |
| 48 | </view> | 52 | </view> |
| 49 | 53 | ||
| @@ -77,7 +81,7 @@ | @@ -77,7 +81,7 @@ | ||
| 77 | </view> | 81 | </view> |
| 78 | </up-popup> | 82 | </up-popup> |
| 79 | 83 | ||
| 80 | - <!-- ===================== 新增:自定义新增分类弹窗 ===================== --> | 84 | + <!-- 新增分类弹窗 --> |
| 81 | <up-popup :show="showAddPopup" mode="bottom" :mask-click="true" :safe-area-inset-bottom="true"> | 85 | <up-popup :show="showAddPopup" mode="bottom" :mask-click="true" :safe-area-inset-bottom="true"> |
| 82 | <view class="add-popup-wrap"> | 86 | <view class="add-popup-wrap"> |
| 83 | <!-- 头部 --> | 87 | <!-- 头部 --> |
| @@ -118,86 +122,92 @@ | @@ -118,86 +122,92 @@ | ||
| 118 | 122 | ||
| 119 | <script setup> | 123 | <script setup> |
| 120 | import { ref, computed } from 'vue'; | 124 | import { ref, computed } from 'vue'; |
| 121 | - import { onLoad, onShow } from '@dcloudio/uni-app'; | 125 | + import { onShow } from '@dcloudio/uni-app'; |
| 122 | import ExercisesApi from '@/sheep/api/motion/exercises'; | 126 | import ExercisesApi from '@/sheep/api/motion/exercises'; |
| 123 | import { useActionStore } from '@/sheep/store/action'; | 127 | import { useActionStore } from '@/sheep/store/action'; |
| 124 | 128 | ||
| 125 | - // 引入 Store | ||
| 126 | const actionStore = useActionStore(); | 129 | const actionStore = useActionStore(); |
| 127 | 130 | ||
| 128 | - // 从 Store 获取状态和方法 | ||
| 129 | const showCategories = computed(() => actionStore.showCategories); | 131 | const showCategories = computed(() => actionStore.showCategories); |
| 130 | const hiddenCategories = computed(() => actionStore.hiddenCategories); | 132 | const hiddenCategories = computed(() => actionStore.hiddenCategories); |
| 133 | + const { hideCategory, showCategory, getloadCategories, reloadCategories } = actionStore; | ||
| 131 | 134 | ||
| 132 | - const { hideCategory, showCategory, getloadCategories } = actionStore; | 135 | + // 页面数据就绪标记(避免 allCategories 为空时渲染空白) |
| 136 | + const isDataReady = ref(false); | ||
| 133 | 137 | ||
| 134 | - // 计算属性:获取隐藏分类的对象数组 | 138 | + // 隐藏分类的对象数组(含名称) |
| 135 | const hiddenCategoryObjects = computed(() => { | 139 | const hiddenCategoryObjects = computed(() => { |
| 140 | + if (!isDataReady.value) return []; | ||
| 136 | return actionStore.allCategories.filter(category => | 141 | return actionStore.allCategories.filter(category => |
| 137 | hiddenCategories.value.includes(category.id) | 142 | hiddenCategories.value.includes(category.id) |
| 138 | ); | 143 | ); |
| 139 | }); | 144 | }); |
| 140 | 145 | ||
| 141 | - // 自定义分类列表(弹窗用) | 146 | + // Toast 提示常量 |
| 147 | + const TOAST = { | ||
| 148 | + ADD_SUCCESS: '新增成功', | ||
| 149 | + ADD_FAIL: '新增失败', | ||
| 150 | + EDIT_SUCCESS: '修改成功', | ||
| 151 | + EDIT_FAIL: '修改失败', | ||
| 152 | + LOAD_FAIL: '加载失败', | ||
| 153 | + }; | ||
| 154 | + | ||
| 155 | + // 自定义分类列表(编辑弹窗用) | ||
| 142 | const customCategories = ref([]); | 156 | const customCategories = ref([]); |
| 143 | - // 当前要编辑的分类 | ||
| 144 | const currentEditCategory = ref(null); | 157 | const currentEditCategory = ref(null); |
| 145 | - // 控制自定义分类编辑弹窗 | ||
| 146 | const showEditPopup = ref(false); | 158 | const showEditPopup = ref(false); |
| 147 | - // 编辑弹窗输入值 | ||
| 148 | - const editInputValue = ref(''); | ||
| 149 | 159 | ||
| 150 | - // ===================== 新增:自定义新增弹窗变量 ===================== | ||
| 151 | - const showAddPopup = ref(false); // 控制新增弹窗显示 | ||
| 152 | - const addCategoryName = ref(''); // 新增分类名称 | 160 | + // 新增分类弹窗变量 |
| 161 | + const showAddPopup = ref(false); | ||
| 162 | + const addCategoryName = ref(''); | ||
| 153 | 163 | ||
| 154 | - // 打开新增弹窗 | ||
| 155 | const openAddPopup = () => { | 164 | const openAddPopup = () => { |
| 156 | - addCategoryName.value = ''; // 清空输入框 | 165 | + addCategoryName.value = ''; |
| 157 | showAddPopup.value = true; | 166 | showAddPopup.value = true; |
| 158 | }; | 167 | }; |
| 159 | - // 关闭新增弹窗 | 168 | + |
| 160 | const closeAddPopup = () => { | 169 | const closeAddPopup = () => { |
| 161 | showAddPopup.value = false; | 170 | showAddPopup.value = false; |
| 162 | }; | 171 | }; |
| 163 | 172 | ||
| 164 | - // ===================== 优化:执行新增分类(接口请求) ===================== | ||
| 165 | const doAddCategory = async () => { | 173 | const doAddCategory = async () => { |
| 166 | const name = addCategoryName.value.trim(); | 174 | const name = addCategoryName.value.trim(); |
| 167 | if (!name) return; | 175 | if (!name) return; |
| 168 | 176 | ||
| 169 | try { | 177 | try { |
| 170 | await ExercisesApi.createCustomCategory(name); | 178 | await ExercisesApi.createCustomCategory(name); |
| 171 | - uni.showToast({ title: '新增成功', icon: 'success' }); | 179 | + uni.showToast({ title: TOAST.ADD_SUCCESS, icon: 'success' }); |
| 172 | closeAddPopup(); | 180 | closeAddPopup(); |
| 173 | - getloadCategories(); | 181 | + reloadCategories(); |
| 174 | } catch (err) { | 182 | } catch (err) { |
| 175 | - uni.showToast({ title: '新增失败', icon: 'none' }); | 183 | + uni.showToast({ title: TOAST.ADD_FAIL, icon: 'none' }); |
| 176 | console.error(err); | 184 | console.error(err); |
| 177 | } | 185 | } |
| 178 | }; | 186 | }; |
| 179 | 187 | ||
| 180 | - // 打开自定义分类编辑弹窗 | ||
| 181 | - const openEditPopup = async () => { | ||
| 182 | - try { | 188 | + // 获取自定义分类列表 |
| 189 | + const fetchCustomCategories = async () => { | ||
| 183 | const res = await ExercisesApi.getCustomCategories(); | 190 | const res = await ExercisesApi.getCustomCategories(); |
| 184 | customCategories.value = res.data || []; | 191 | customCategories.value = res.data || []; |
| 192 | + }; | ||
| 193 | + | ||
| 194 | + // 打开编辑弹窗 | ||
| 195 | + const openEditPopup = async () => { | ||
| 196 | + try { | ||
| 197 | + await fetchCustomCategories(); | ||
| 185 | showEditPopup.value = true; | 198 | showEditPopup.value = true; |
| 186 | } catch (err) { | 199 | } catch (err) { |
| 187 | console.error('加载自定义分类失败', err); | 200 | console.error('加载自定义分类失败', err); |
| 188 | - uni.showToast({ title: '加载失败', icon: 'none' }); | 201 | + uni.showToast({ title: TOAST.LOAD_FAIL, icon: 'none' }); |
| 189 | } | 202 | } |
| 190 | }; | 203 | }; |
| 191 | 204 | ||
| 192 | - // 关闭弹窗 | ||
| 193 | const closeEditPopup = () => { | 205 | const closeEditPopup = () => { |
| 194 | showEditPopup.value = false; | 206 | showEditPopup.value = false; |
| 195 | }; | 207 | }; |
| 196 | 208 | ||
| 197 | - // 打开编辑弹窗 | ||
| 198 | const openEditModal = (item) => { | 209 | const openEditModal = (item) => { |
| 199 | currentEditCategory.value = item; | 210 | currentEditCategory.value = item; |
| 200 | - editInputValue.value = item.name; | ||
| 201 | uni.showModal({ | 211 | uni.showModal({ |
| 202 | title: '动作分类', | 212 | title: '动作分类', |
| 203 | editable: true, | 213 | editable: true, |
| @@ -207,27 +217,59 @@ | @@ -207,27 +217,59 @@ | ||
| 207 | if (res.confirm && res.content.trim()) { | 217 | if (res.confirm && res.content.trim()) { |
| 208 | try { | 218 | try { |
| 209 | await ExercisesApi.updateCustomCategory(item.id, res.content.trim()); | 219 | await ExercisesApi.updateCustomCategory(item.id, res.content.trim()); |
| 210 | - uni.showToast({ title: '修改成功', icon: 'success' }); | ||
| 211 | - openEditPopup(); | ||
| 212 | - getloadCategories(); | 220 | + uni.showToast({ title: TOAST.EDIT_SUCCESS, icon: 'success' }); |
| 221 | + await fetchCustomCategories(); | ||
| 222 | + reloadCategories(); | ||
| 213 | } catch (err) { | 223 | } catch (err) { |
| 214 | console.error('修改失败', err); | 224 | console.error('修改失败', err); |
| 215 | - uni.showToast({ title: '修改失败', icon: 'none' }); | 225 | + uni.showToast({ title: TOAST.EDIT_FAIL, icon: 'none' }); |
| 216 | } | 226 | } |
| 217 | } | 227 | } |
| 218 | }, | 228 | }, |
| 219 | }); | 229 | }); |
| 220 | }; | 230 | }; |
| 221 | 231 | ||
| 222 | - onShow(() => { | ||
| 223 | - getloadCategories(); | 232 | + onShow(async () => { |
| 233 | + await getloadCategories(); | ||
| 234 | + isDataReady.value = true; | ||
| 224 | }); | 235 | }); |
| 225 | </script> | 236 | </script> |
| 226 | 237 | ||
| 227 | <style scoped lang="scss"> | 238 | <style scoped lang="scss"> |
| 239 | + // ========== 设计变量 ========== | ||
| 240 | + $radius-sm: 10rpx; | ||
| 241 | + $radius-md: 12rpx; | ||
| 242 | + $radius-lg: 20rpx; | ||
| 243 | + $radius-round: 36rpx; | ||
| 244 | + $gap: 20rpx; | ||
| 245 | + $color-primary: #ffd700; | ||
| 246 | + $color-text: #333; | ||
| 247 | + $color-text-secondary: #666; | ||
| 248 | + $color-text-light: #999; | ||
| 249 | + $color-border: #eee; | ||
| 250 | + $color-bg: #f7f7f7; | ||
| 251 | + $color-bg-light: #f9f9f9; | ||
| 252 | + $transition-base: 0.2s ease; | ||
| 253 | + | ||
| 228 | .container { | 254 | .container { |
| 229 | min-height: 100vh; | 255 | min-height: 100vh; |
| 230 | background-color: #fff; | 256 | background-color: #fff; |
| 257 | + // 为底部固定栏留出空间 | ||
| 258 | + padding-bottom: 120rpx; | ||
| 259 | + box-sizing: border-box; | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + // 加载中占位 | ||
| 263 | + .loading-wrap { | ||
| 264 | + display: flex; | ||
| 265 | + justify-content: center; | ||
| 266 | + align-items: center; | ||
| 267 | + min-height: 60vh; | ||
| 268 | + } | ||
| 269 | + | ||
| 270 | + .loading-text { | ||
| 271 | + font-size: 28rpx; | ||
| 272 | + color: $color-text-light; | ||
| 231 | } | 273 | } |
| 232 | 274 | ||
| 233 | .section { | 275 | .section { |
| @@ -243,20 +285,22 @@ | @@ -243,20 +285,22 @@ | ||
| 243 | 285 | ||
| 244 | .section-title { | 286 | .section-title { |
| 245 | font-size: 28rpx; | 287 | font-size: 28rpx; |
| 246 | - color: #333; | 288 | + color: $color-text; |
| 247 | font-weight: bold; | 289 | font-weight: bold; |
| 248 | } | 290 | } |
| 249 | 291 | ||
| 250 | .section-subtitle { | 292 | .section-subtitle { |
| 251 | font-size: 24rpx; | 293 | font-size: 24rpx; |
| 252 | - color: #999; | 294 | + color: $color-text-light; |
| 253 | margin-left: 16rpx; | 295 | margin-left: 16rpx; |
| 254 | } | 296 | } |
| 255 | 297 | ||
| 256 | - .category-grid { | 298 | + // 分类网格(展示 + 隐藏共用) |
| 299 | + .category-grid, | ||
| 300 | + .hidden-grid { | ||
| 257 | display: grid; | 301 | display: grid; |
| 258 | - grid-template-columns: repeat(4, 1fr); | ||
| 259 | - gap: 20rpx; | 302 | + grid-template-columns: repeat(4, minmax(0, 1fr)); |
| 303 | + gap: $gap; | ||
| 260 | } | 304 | } |
| 261 | 305 | ||
| 262 | .category-item { | 306 | .category-item { |
| @@ -264,34 +308,36 @@ | @@ -264,34 +308,36 @@ | ||
| 264 | justify-content: center; | 308 | justify-content: center; |
| 265 | align-items: center; | 309 | align-items: center; |
| 266 | padding: 20rpx 16rpx; | 310 | padding: 20rpx 16rpx; |
| 311 | + padding-right: 28rpx; | ||
| 267 | background-color: #fff; | 312 | background-color: #fff; |
| 268 | - border: 1rpx solid #eee; | ||
| 269 | - border-radius: 10rpx; | 313 | + border: 1rpx solid $color-border; |
| 314 | + border-radius: $radius-sm; | ||
| 270 | position: relative; | 315 | position: relative; |
| 271 | - transition: all 0.2s ease; | ||
| 272 | - padding-right: 28rpx; | ||
| 273 | - } | 316 | + transition: all $transition-base; |
| 274 | 317 | ||
| 275 | - .category-item:active { | 318 | + &:active { |
| 276 | transform: scale(0.96); | 319 | transform: scale(0.96); |
| 277 | background-color: #fafafa; | 320 | background-color: #fafafa; |
| 278 | } | 321 | } |
| 322 | + } | ||
| 279 | 323 | ||
| 280 | .category-text { | 324 | .category-text { |
| 281 | font-size: 24rpx; | 325 | font-size: 24rpx; |
| 282 | - color: #333; | 326 | + color: $color-text; |
| 283 | text-align: center; | 327 | text-align: center; |
| 328 | + overflow: hidden; | ||
| 329 | + text-overflow: ellipsis; | ||
| 330 | + white-space: nowrap; | ||
| 284 | } | 331 | } |
| 285 | 332 | ||
| 286 | .delete-icon { | 333 | .delete-icon { |
| 287 | position: absolute; | 334 | position: absolute; |
| 288 | right: 4rpx; | 335 | right: 4rpx; |
| 289 | top: 4rpx; | 336 | top: 4rpx; |
| 290 | - transform: none; | ||
| 291 | - z-index: 99; | 337 | + z-index: 1; |
| 292 | padding: 4rpx; | 338 | padding: 4rpx; |
| 293 | opacity: 0.7; | 339 | opacity: 0.7; |
| 294 | - transition: all 0.2s ease; | 340 | + transition: all $transition-base; |
| 295 | } | 341 | } |
| 296 | 342 | ||
| 297 | .category-item:active .delete-icon { | 343 | .category-item:active .delete-icon { |
| @@ -299,26 +345,21 @@ | @@ -299,26 +345,21 @@ | ||
| 299 | color: #ff4d4f; | 345 | color: #ff4d4f; |
| 300 | } | 346 | } |
| 301 | 347 | ||
| 302 | - .hidden-grid { | ||
| 303 | - display: grid; | ||
| 304 | - grid-template-columns: repeat(4, 1fr); | ||
| 305 | - gap: 20rpx; | ||
| 306 | - } | ||
| 307 | - | ||
| 308 | .hidden-item { | 348 | .hidden-item { |
| 309 | display: flex; | 349 | display: flex; |
| 310 | justify-content: center; | 350 | justify-content: center; |
| 311 | align-items: center; | 351 | align-items: center; |
| 312 | padding: 20rpx 16rpx; | 352 | padding: 20rpx 16rpx; |
| 313 | - background: #f9f9f9; | ||
| 314 | - border: 1rpx solid #eee; | ||
| 315 | - border-radius: 10rpx; | ||
| 316 | - transition: all 0.2s ease; | ||
| 317 | - } | 353 | + background: $color-bg-light; |
| 354 | + border: 1rpx solid $color-border; | ||
| 355 | + border-radius: $radius-sm; | ||
| 356 | + transition: all $transition-base; | ||
| 318 | 357 | ||
| 319 | - .hidden-item:active { | 358 | + &:active { |
| 320 | transform: scale(0.96); | 359 | transform: scale(0.96); |
| 321 | - background-color: #f2f2f2; | 360 | + background-color: #e8e8e8; |
| 361 | + border-color: #ddd; | ||
| 362 | + } | ||
| 322 | } | 363 | } |
| 323 | 364 | ||
| 324 | .hidden-empty { | 365 | .hidden-empty { |
| @@ -329,10 +370,13 @@ | @@ -329,10 +370,13 @@ | ||
| 329 | 370 | ||
| 330 | .hidden-text { | 371 | .hidden-text { |
| 331 | font-size: 24rpx; | 372 | font-size: 24rpx; |
| 332 | - color: #999; | 373 | + color: $color-text-light; |
| 374 | + overflow: hidden; | ||
| 375 | + text-overflow: ellipsis; | ||
| 376 | + white-space: nowrap; | ||
| 333 | } | 377 | } |
| 334 | 378 | ||
| 335 | - /* 底部按钮 */ | 379 | + // 底部按钮栏 |
| 336 | .bottom-btns { | 380 | .bottom-btns { |
| 337 | display: flex; | 381 | display: flex; |
| 338 | justify-content: space-between; | 382 | justify-content: space-between; |
| @@ -344,9 +388,8 @@ | @@ -344,9 +388,8 @@ | ||
| 344 | background-color: rgba(255, 255, 255, 0.95); | 388 | background-color: rgba(255, 255, 255, 0.95); |
| 345 | backdrop-filter: blur(10px); | 389 | backdrop-filter: blur(10px); |
| 346 | z-index: 10; | 390 | z-index: 10; |
| 347 | - padding: 20rpx 30rpx; | 391 | + padding: 20rpx 20rpx calc(20rpx + env(safe-area-inset-bottom)); |
| 348 | box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05); | 392 | box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05); |
| 349 | - padding: 20rpx; | ||
| 350 | box-sizing: border-box; | 393 | box-sizing: border-box; |
| 351 | } | 394 | } |
| 352 | 395 | ||
| @@ -355,11 +398,11 @@ | @@ -355,11 +398,11 @@ | ||
| 355 | height: 72rpx; | 398 | height: 72rpx; |
| 356 | line-height: 72rpx; | 399 | line-height: 72rpx; |
| 357 | font-size: 26rpx; | 400 | font-size: 26rpx; |
| 358 | - color: #666; | 401 | + color: $color-text-secondary; |
| 359 | background-color: #f5f5f5; | 402 | background-color: #f5f5f5; |
| 360 | - border-radius: 36rpx; | 403 | + border-radius: $radius-round; |
| 361 | text-align: center; | 404 | text-align: center; |
| 362 | - transition: all 0.2s; | 405 | + transition: all $transition-base; |
| 363 | 406 | ||
| 364 | &:active { | 407 | &:active { |
| 365 | background-color: #e0e0e0; | 408 | background-color: #e0e0e0; |
| @@ -371,16 +414,16 @@ | @@ -371,16 +414,16 @@ | ||
| 371 | flex: 1; | 414 | flex: 1; |
| 372 | height: 72rpx; | 415 | height: 72rpx; |
| 373 | line-height: 72rpx; | 416 | line-height: 72rpx; |
| 374 | - background: linear-gradient(135deg, #ffd700 0%, #ffcc00 100%) !important; | ||
| 375 | - color: #333 !important; | 417 | + background: linear-gradient(135deg, $color-primary 0%, #ffcc00 100%); |
| 418 | + color: $color-text; | ||
| 376 | font-size: 28rpx; | 419 | font-size: 28rpx; |
| 377 | font-weight: 500; | 420 | font-weight: 500; |
| 378 | - border-radius: 36rpx; | ||
| 379 | - margin-left: 20rpx; | 421 | + border-radius: $radius-round; |
| 422 | + margin-left: $gap; | ||
| 380 | border: none; | 423 | border: none; |
| 381 | box-shadow: 0 4rpx 12rpx rgba(255, 215, 0, 0.3); | 424 | box-shadow: 0 4rpx 12rpx rgba(255, 215, 0, 0.3); |
| 382 | text-align: center; | 425 | text-align: center; |
| 383 | - transition: all 0.2s; | 426 | + transition: all $transition-base; |
| 384 | 427 | ||
| 385 | &:active { | 428 | &:active { |
| 386 | transform: scale(0.98); | 429 | transform: scale(0.98); |
| @@ -388,10 +431,10 @@ | @@ -388,10 +431,10 @@ | ||
| 388 | } | 431 | } |
| 389 | } | 432 | } |
| 390 | 433 | ||
| 391 | - /* 编辑弹窗 */ | 434 | + // 编辑分类弹窗 |
| 392 | .popup-wrap { | 435 | .popup-wrap { |
| 393 | - background: #f7f7f7; | ||
| 394 | - border-radius: 20rpx 20rpx 0 0; | 436 | + background: $color-bg; |
| 437 | + border-radius: $radius-lg $radius-lg 0 0; | ||
| 395 | padding: 0 30rpx; | 438 | padding: 0 30rpx; |
| 396 | max-height: 70vh; | 439 | max-height: 70vh; |
| 397 | display: flex; | 440 | display: flex; |
| @@ -408,12 +451,12 @@ | @@ -408,12 +451,12 @@ | ||
| 408 | .popup-title { | 451 | .popup-title { |
| 409 | font-size: 36rpx; | 452 | font-size: 36rpx; |
| 410 | font-weight: 500; | 453 | font-weight: 500; |
| 411 | - color: #333; | 454 | + color: $color-text; |
| 412 | } | 455 | } |
| 413 | 456 | ||
| 414 | .popup-tip { | 457 | .popup-tip { |
| 415 | font-size: 28rpx; | 458 | font-size: 28rpx; |
| 416 | - color: #666; | 459 | + color: $color-text-secondary; |
| 417 | margin-bottom: 30rpx; | 460 | margin-bottom: 30rpx; |
| 418 | } | 461 | } |
| 419 | 462 | ||
| @@ -427,22 +470,33 @@ | @@ -427,22 +470,33 @@ | ||
| 427 | justify-content: space-between; | 470 | justify-content: space-between; |
| 428 | align-items: center; | 471 | align-items: center; |
| 429 | background: #fff; | 472 | background: #fff; |
| 430 | - border-radius: 12rpx; | 473 | + border-radius: $radius-md; |
| 431 | padding: 30rpx; | 474 | padding: 30rpx; |
| 432 | - margin-bottom: 20rpx; | 475 | + margin-bottom: $gap; |
| 476 | + transition: all $transition-base; | ||
| 477 | + | ||
| 478 | + &:active { | ||
| 479 | + background-color: #f5f5f5; | ||
| 480 | + transform: scale(0.98); | ||
| 481 | + } | ||
| 433 | } | 482 | } |
| 434 | 483 | ||
| 435 | .item-name { | 484 | .item-name { |
| 436 | font-size: 32rpx; | 485 | font-size: 32rpx; |
| 437 | - color: #333; | 486 | + color: $color-text; |
| 487 | + flex: 1; | ||
| 488 | + overflow: hidden; | ||
| 489 | + text-overflow: ellipsis; | ||
| 490 | + white-space: nowrap; | ||
| 491 | + margin-right: 16rpx; | ||
| 438 | } | 492 | } |
| 439 | 493 | ||
| 440 | - /* ===================== 新增:自定义新增弹窗样式 ===================== */ | 494 | + // 新增分类弹窗 |
| 441 | .add-popup-wrap { | 495 | .add-popup-wrap { |
| 442 | background: #fff; | 496 | background: #fff; |
| 443 | - border-radius: 20rpx 20rpx 0 0; | 497 | + border-radius: $radius-lg $radius-lg 0 0; |
| 444 | padding: 30rpx; | 498 | padding: 30rpx; |
| 445 | - padding-bottom: calc(var(--safe-area-inset-bottom) + 30rpx); | 499 | + padding-bottom: calc(env(safe-area-inset-bottom) + 30rpx); |
| 446 | } | 500 | } |
| 447 | 501 | ||
| 448 | .add-popup-header { | 502 | .add-popup-header { |
| @@ -455,26 +509,27 @@ | @@ -455,26 +509,27 @@ | ||
| 455 | .add-popup-title { | 509 | .add-popup-title { |
| 456 | font-size: 38rpx; | 510 | font-size: 38rpx; |
| 457 | font-weight: bold; | 511 | font-weight: bold; |
| 458 | - color: #333; | 512 | + color: $color-text; |
| 459 | } | 513 | } |
| 460 | 514 | ||
| 461 | .add-input-box { | 515 | .add-input-box { |
| 462 | - background: #f7f7f7; | ||
| 463 | - border-radius: 12rpx; | 516 | + background: $color-bg; |
| 517 | + border-radius: $radius-md; | ||
| 464 | padding: 24rpx 20rpx; | 518 | padding: 24rpx 20rpx; |
| 465 | - margin-bottom: 20rpx; | 519 | + margin-bottom: $gap; |
| 466 | } | 520 | } |
| 467 | 521 | ||
| 468 | .add-input { | 522 | .add-input { |
| 469 | font-size: 32rpx; | 523 | font-size: 32rpx; |
| 470 | - color: #333; | 524 | + color: $color-text; |
| 471 | height: 48rpx; | 525 | height: 48rpx; |
| 472 | line-height: 48rpx; | 526 | line-height: 48rpx; |
| 527 | + width: 100%; | ||
| 473 | } | 528 | } |
| 474 | 529 | ||
| 475 | .add-tip { | 530 | .add-tip { |
| 476 | font-size: 24rpx; | 531 | font-size: 24rpx; |
| 477 | - color: #999; | 532 | + color: $color-text-light; |
| 478 | margin-bottom: 40rpx; | 533 | margin-bottom: 40rpx; |
| 479 | } | 534 | } |
| 480 | 535 | ||
| @@ -487,14 +542,14 @@ | @@ -487,14 +542,14 @@ | ||
| 487 | height: 88rpx; | 542 | height: 88rpx; |
| 488 | line-height: 88rpx; | 543 | line-height: 88rpx; |
| 489 | font-size: 32rpx; | 544 | font-size: 32rpx; |
| 490 | - border-radius: 12rpx; | ||
| 491 | - background-color: #ffd700; | 545 | + border-radius: $radius-md; |
| 546 | + background-color: $color-primary; | ||
| 492 | color: #000; | 547 | color: #000; |
| 493 | border: none; | 548 | border: none; |
| 494 | 549 | ||
| 495 | &[disabled] { | 550 | &[disabled] { |
| 496 | background-color: #e0e0e0; | 551 | background-color: #e0e0e0; |
| 497 | - color: #999; | 552 | + color: $color-text-light; |
| 498 | } | 553 | } |
| 499 | } | 554 | } |
| 500 | </style> | 555 | </style> |
| 1 | <template> | 1 | <template> |
| 2 | - <view class="page-wrap"> | ||
| 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> | 2 | + <view class="page-wrap" :style="{ paddingTop: PageHeight + 'px' }"> |
| 19 | 3 | ||
| 20 | <!-- 左右主体布局 --> | 4 | <!-- 左右主体布局 --> |
| 21 | <view class="layout-container"> | 5 | <view class="layout-container"> |
| @@ -29,18 +13,44 @@ | @@ -29,18 +13,44 @@ | ||
| 29 | 13 | ||
| 30 | <!-- 右侧动作列表 --> | 14 | <!-- 右侧动作列表 --> |
| 31 | <scroll-view scroll-y class="right-content"> | 15 | <scroll-view scroll-y class="right-content"> |
| 16 | + <!-- 初始引导:未选择分类 --> | ||
| 17 | + <view class="content-tip init-guide" v-if="!activeNav"> | ||
| 18 | + <text class="tip-text">请选择左侧分类查看动作</text> | ||
| 19 | + <text class="tip-subtext">选择分类后将展示该分类下的训练动作</text> | ||
| 20 | + </view> | ||
| 21 | + | ||
| 22 | + <!-- 加载中 --> | ||
| 23 | + <view class="content-tip loading-state" v-else-if="loading"> | ||
| 24 | + <text class="tip-text">加载中...</text> | ||
| 25 | + </view> | ||
| 26 | + | ||
| 27 | + <!-- 加载失败 --> | ||
| 28 | + <view class="content-tip error-state" v-else-if="loadError"> | ||
| 29 | + <text class="tip-text">加载失败,请重试</text> | ||
| 30 | + <view class="retry-btn" @click="retryLoad">点击重试</view> | ||
| 31 | + </view> | ||
| 32 | + | ||
| 33 | + <!-- 正常内容 --> | ||
| 34 | + <template v-else> | ||
| 32 | <!-- 部位筛选标签 --> | 35 | <!-- 部位筛选标签 --> |
| 33 | - <view class="tip" v-if="motionPart.length > 0"> | ||
| 34 | - <view class="item" @click="handlePartClick('')" :class="{ active: activeMotionPart == '' }">全部</view> | ||
| 35 | - <view class="item" v-for="item in motionPart" :key="item.id" :class="{ active: activeMotionPart == item.id }" | ||
| 36 | - @click="handlePartClick(item.id)"> | 36 | + <scroll-view scroll-x show-scrollbar="false" class="part-filter" v-if="motionPart.length > 0"> |
| 37 | + <view class="part-filter-inner"> | ||
| 38 | + <view class="filter-item" :class="{ active: activeMotionPart === '' }" @click="handlePartClick('')">全部 | ||
| 39 | + </view> | ||
| 40 | + <view class="filter-item" v-for="item in motionPart" :key="item.id" | ||
| 41 | + :class="{ active: activeMotionPart === item.id }" @click="handlePartClick(item.id)"> | ||
| 37 | {{ item.name }} | 42 | {{ item.name }} |
| 38 | </view> | 43 | </view> |
| 39 | </view> | 44 | </view> |
| 45 | + </scroll-view> | ||
| 40 | 46 | ||
| 41 | - <view class="exercise-grid"> | ||
| 42 | - <view class="content" v-if="exercises.length > 0"> | ||
| 43 | - <view class="equipment-list"> | 47 | + <!-- 部位筛选为空提示 --> |
| 48 | + <view class="content-tip part-empty-tip" v-else> | ||
| 49 | + <text class="tip-text">该分类下暂无可筛选部位</text> | ||
| 50 | + </view> | ||
| 51 | + | ||
| 52 | + <!-- 动作列表 --> | ||
| 53 | + <view class="equipment-list" v-if="exercises.length > 0"> | ||
| 44 | <view class="equipment-item" v-for="item in exercises" :key="item.equipmentId"> | 54 | <view class="equipment-item" v-for="item in exercises" :key="item.equipmentId"> |
| 45 | <view class="equipment-name">{{ item.equipmentName }}</view> | 55 | <view class="equipment-name">{{ item.equipmentName }}</view> |
| 46 | <view class="action-list"> | 56 | <view class="action-list"> |
| @@ -52,23 +62,27 @@ | @@ -52,23 +62,27 @@ | ||
| 52 | </view> | 62 | </view> |
| 53 | </view> | 63 | </view> |
| 54 | </view> | 64 | </view> |
| 65 | + | ||
| 66 | + <!-- 无动作数据 --> | ||
| 67 | + <view class="content-tip empty-state" v-else> | ||
| 68 | + <text class="tip-text">该分类下暂无动作</text> | ||
| 69 | + <text class="tip-subtext">可尝试切换分类或部位查看其他动作</text> | ||
| 55 | </view> | 70 | </view> |
| 56 | - <view class="empty-state" v-else> | ||
| 57 | - <text class="empty-text">暂无动作数据</text> | ||
| 58 | - </view> | ||
| 59 | - </view> | 71 | + </template> |
| 60 | </scroll-view> | 72 | </scroll-view> |
| 61 | </view> | 73 | </view> |
| 62 | 74 | ||
| 63 | - <!-- 底部横向已选标签栏 --> | 75 | + <!-- 底部已选标签栏 --> |
| 64 | <scroll-view class="selected-bar" scroll-x show-scrollbar="false" v-if="selectedActions.length > 0"> | 76 | <scroll-view class="selected-bar" scroll-x show-scrollbar="false" v-if="selectedActions.length > 0"> |
| 65 | - <view class="selected-item" v-for="(item, index) in selectedActions" :key="index"> | 77 | + <view class="selected-bar-inner"> |
| 78 | + <view class="selected-item" v-for="item in selectedActions" :key="item.id"> | ||
| 66 | <text class="name">{{ item.name }}</text> | 79 | <text class="name">{{ item.name }}</text> |
| 67 | <uni-icons type="close" size="16" color="#fff" @click="removeAction(item)"></uni-icons> | 80 | <uni-icons type="close" size="16" color="#fff" @click="removeAction(item)"></uni-icons> |
| 68 | </view> | 81 | </view> |
| 82 | + </view> | ||
| 69 | </scroll-view> | 83 | </scroll-view> |
| 70 | 84 | ||
| 71 | - <!-- 页面底部按钮 --> | 85 | + <!-- 底部操作按钮 --> |
| 72 | <view class="bottom-btn-bar"> | 86 | <view class="bottom-btn-bar"> |
| 73 | <view class="btn cancel-btn" @click="backPage">取消</view> | 87 | <view class="btn cancel-btn" @click="backPage">取消</view> |
| 74 | <view class="btn confirm-btn" :class="{ disabled: selectedActions.length < 2 }" @click="combineActions"> | 88 | <view class="btn confirm-btn" :class="{ disabled: selectedActions.length < 2 }" @click="combineActions"> |
| @@ -76,29 +90,17 @@ | @@ -76,29 +90,17 @@ | ||
| 76 | </view> | 90 | </view> |
| 77 | </view> | 91 | </view> |
| 78 | 92 | ||
| 79 | - <!-- 底部弹出命名弹窗 --> | 93 | + <!-- 命名弹窗 --> |
| 80 | <up-popup v-model:show="showNamePopup" mode="bottom" round> | 94 | <up-popup v-model:show="showNamePopup" mode="bottom" round> |
| 81 | <view class="popup-content" @click.stop> | 95 | <view class="popup-content" @click.stop> |
| 82 | - | ||
| 83 | <view class="popup-header"> | 96 | <view class="popup-header"> |
| 84 | - <!-- <text class="close-btn" @click="closePopup">x</text> --> | ||
| 85 | <up-icon name="close" color="#333" size="25" @click="closePopup"></up-icon> | 97 | <up-icon name="close" color="#333" size="25" @click="closePopup"></up-icon> |
| 86 | <text class="popup-title">命名超级组</text> | 98 | <text class="popup-title">命名超级组</text> |
| 87 | <text class="save-btn" @click="saveCombination">保存</text> | 99 | <text class="save-btn" @click="saveCombination">保存</text> |
| 88 | </view> | 100 | </view> |
| 89 | <view class="input-wrapper"> | 101 | <view class="input-wrapper"> |
| 90 | <textarea v-model="combinationName" class="name-input" placeholder="请输入超级组名称" maxlength="20" focus /> | 102 | <textarea v-model="combinationName" class="name-input" placeholder="请输入超级组名称" maxlength="20" focus /> |
| 91 | - <!-- <text class="hint">例如:胸肩超级组、背部组合训练</text> --> | ||
| 92 | - </view> | ||
| 93 | - | ||
| 94 | - <!-- <view class="preview-section"> | ||
| 95 | - <text class="preview-title">已选动作 ({{ selectedActions.length }})</text> | ||
| 96 | - <scroll-view scroll-x class="preview-scroll"> | ||
| 97 | - <view class="preview-tag" v-for="item in selectedActions" :key="item.id"> | ||
| 98 | - {{ item.name }} | ||
| 99 | </view> | 103 | </view> |
| 100 | - </scroll-view> | ||
| 101 | - </view> --> | ||
| 102 | </view> | 104 | </view> |
| 103 | </up-popup> | 105 | </up-popup> |
| 104 | </view> | 106 | </view> |
| @@ -106,48 +108,61 @@ | @@ -106,48 +108,61 @@ | ||
| 106 | 108 | ||
| 107 | <script setup> | 109 | <script setup> |
| 108 | import { ref, computed } from 'vue' | 110 | import { ref, computed } from 'vue' |
| 109 | -import { onLoad, onShow } from '@dcloudio/uni-app'; | 111 | +import { onLoad } from '@dcloudio/uni-app' |
| 110 | import { useActionStore } from '@/sheep/store/action' | 112 | import { useActionStore } from '@/sheep/store/action' |
| 111 | import ExercisesApi from '@/sheep/api/motion/exercises' | 113 | import ExercisesApi from '@/sheep/api/motion/exercises' |
| 112 | import SupersetsApi from '@/sheep/api/motion/supersets' | 114 | import SupersetsApi from '@/sheep/api/motion/supersets' |
| 113 | - | 115 | +import { getMenuButtonHeight, getTopSafeArea } from '@/utils/safeArea'; |
| 116 | +const menuButtonHeight = ref(getMenuButtonHeight()); | ||
| 117 | +const topSafeArea = ref(getTopSafeArea()); | ||
| 118 | +const PageHeight = ref(topSafeArea.value); | ||
| 119 | +// #ifdef MP-WEIXIN | ||
| 120 | +PageHeight.value = menuButtonHeight.value + topSafeArea.value; | ||
| 121 | +// #endif | ||
| 114 | const actionStore = useActionStore() | 122 | const actionStore = useActionStore() |
| 115 | 123 | ||
| 116 | -// 搜索 | ||
| 117 | -const searchValue = ref('') | 124 | +// ==================== 状态变量 ==================== |
| 118 | 125 | ||
| 119 | -// 左侧分类、部位筛选 | 126 | +// 左侧分类 |
| 120 | const navItems = ref([]) | 127 | const navItems = ref([]) |
| 121 | const activeNav = ref(null) | 128 | const activeNav = ref(null) |
| 129 | + | ||
| 130 | +// 部位筛选 | ||
| 122 | const motionPart = ref([]) | 131 | const motionPart = ref([]) |
| 123 | const activeMotionPart = ref('') | 132 | const activeMotionPart = ref('') |
| 124 | 133 | ||
| 125 | -// 原始全量动作、当前渲染动作 | 134 | +// 动作数据 |
| 126 | const exercises = ref([]) | 135 | const exercises = ref([]) |
| 127 | -const allExercises = ref([]) | 136 | +const loading = ref(false) |
| 137 | +const loadError = ref(false) | ||
| 128 | 138 | ||
| 129 | // 选中动作 | 139 | // 选中动作 |
| 130 | const selectedActions = ref([]) | 140 | const selectedActions = ref([]) |
| 131 | const selectedIds = computed(() => new Set(selectedActions.value.map(item => item.id))) | 141 | const selectedIds = computed(() => new Set(selectedActions.value.map(item => item.id))) |
| 132 | 142 | ||
| 133 | -// 命名弹窗控制 | 143 | +// 命名弹窗 |
| 134 | const showNamePopup = ref(false) | 144 | const showNamePopup = ref(false) |
| 135 | const combinationName = ref('') | 145 | const combinationName = ref('') |
| 136 | 146 | ||
| 137 | -const replaceAddshow = ref(false); | 147 | +// ==================== 生命周期 ==================== |
| 138 | 148 | ||
| 139 | -// 页面初始化(onLoad 页面生命周期) | ||
| 140 | onLoad(async () => { | 149 | onLoad(async () => { |
| 141 | selectedActions.value = [] | 150 | selectedActions.value = [] |
| 142 | combinationName.value = '' | 151 | combinationName.value = '' |
| 143 | await loadCategories() | 152 | await loadCategories() |
| 144 | }) | 153 | }) |
| 145 | 154 | ||
| 146 | -// 加载全部分类 | 155 | +// ==================== 数据加载 ==================== |
| 156 | + | ||
| 157 | +/** 加载全部分类 */ | ||
| 147 | const loadCategories = async () => { | 158 | const loadCategories = async () => { |
| 148 | try { | 159 | try { |
| 149 | await actionStore.getloadCategories() | 160 | await actionStore.getloadCategories() |
| 150 | - navItems.value = actionStore.showCategories | 161 | + // 过滤掉虚拟分类(如「超级组」id='super'),其 id 非数字,无法用于加载动作 |
| 162 | + navItems.value = actionStore.showCategories.filter(item => { | ||
| 163 | + const id = item.id | ||
| 164 | + return typeof id === 'number' || (typeof id === 'string' && /^\d+$/.test(id)) | ||
| 165 | + }) | ||
| 151 | if (navItems.value.length > 0) { | 166 | if (navItems.value.length > 0) { |
| 152 | switchNav(navItems.value[0].id) | 167 | switchNav(navItems.value[0].id) |
| 153 | } | 168 | } |
| @@ -156,77 +171,81 @@ const loadCategories = async () => { | @@ -156,77 +171,81 @@ const loadCategories = async () => { | ||
| 156 | } | 171 | } |
| 157 | } | 172 | } |
| 158 | 173 | ||
| 159 | -const addExercise = () => { | ||
| 160 | - replaceAddshow.value = !replaceAddshow.value; | ||
| 161 | -}; | ||
| 162 | - | ||
| 163 | -const addnewmotion = () => { | ||
| 164 | - uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-xinzeng' }); | ||
| 165 | -}; | ||
| 166 | - | ||
| 167 | -// 切换左侧大分类 | 174 | +/** 切换左侧大分类 */ |
| 168 | const switchNav = async (id) => { | 175 | const switchNav = async (id) => { |
| 169 | if (activeNav.value === id) return | 176 | if (activeNav.value === id) return |
| 170 | activeNav.value = id | 177 | activeNav.value = id |
| 171 | activeMotionPart.value = '' | 178 | activeMotionPart.value = '' |
| 172 | - searchValue.value = '' | 179 | + loadError.value = false |
| 173 | await loadExercises(id) | 180 | await loadExercises(id) |
| 174 | } | 181 | } |
| 175 | 182 | ||
| 176 | -// 根据分类ID加载动作+部位 | 183 | +/** 根据分类ID加载动作和部位 */ |
| 177 | const loadExercises = async (categoriesId) => { | 184 | const loadExercises = async (categoriesId) => { |
| 185 | + loading.value = true | ||
| 186 | + loadError.value = false | ||
| 178 | try { | 187 | try { |
| 179 | - const partRes = await ExercisesApi.getMotionPart(categoriesId) | 188 | + const [partRes, exerciseRes] = await Promise.all([ |
| 189 | + ExercisesApi.getMotionPart(categoriesId), | ||
| 190 | + ExercisesApi.getexercises({ categoriesId }) | ||
| 191 | + ]) | ||
| 180 | motionPart.value = partRes.data || [] | 192 | motionPart.value = partRes.data || [] |
| 181 | - | ||
| 182 | - const res = await ExercisesApi.getexercises({ categoriesId }) | ||
| 183 | - exercises.value = res.data || [] | ||
| 184 | - allExercises.value = [...exercises.value] | 193 | + exercises.value = exerciseRes.data || [] |
| 185 | } catch (err) { | 194 | } catch (err) { |
| 186 | console.error('加载动作失败', err) | 195 | console.error('加载动作失败', err) |
| 196 | + loadError.value = true | ||
| 197 | + motionPart.value = [] | ||
| 198 | + exercises.value = [] | ||
| 199 | + } finally { | ||
| 200 | + loading.value = false | ||
| 187 | } | 201 | } |
| 188 | } | 202 | } |
| 189 | 203 | ||
| 190 | -// 子部位筛选 | 204 | +/** 按子部位筛选动作 */ |
| 191 | const handlePartClick = async (id) => { | 205 | const handlePartClick = async (id) => { |
| 192 | activeMotionPart.value = id | 206 | activeMotionPart.value = id |
| 207 | + loading.value = true | ||
| 208 | + loadError.value = false | ||
| 209 | + try { | ||
| 193 | const res = await ExercisesApi.getexercises({ | 210 | const res = await ExercisesApi.getexercises({ |
| 194 | categoriesId: activeNav.value, | 211 | categoriesId: activeNav.value, |
| 195 | subCategoriesId: id | 212 | subCategoriesId: id |
| 196 | }) | 213 | }) |
| 197 | exercises.value = res.data || [] | 214 | exercises.value = res.data || [] |
| 215 | + } catch (err) { | ||
| 216 | + console.error('筛选动作失败', err) | ||
| 217 | + uni.showToast({ title: '加载失败', icon: 'none' }) | ||
| 218 | + loadError.value = true | ||
| 219 | + } finally { | ||
| 220 | + loading.value = false | ||
| 221 | + } | ||
| 198 | } | 222 | } |
| 199 | 223 | ||
| 200 | -// 搜索过滤 | ||
| 201 | -const onSearch = () => { | ||
| 202 | - const keyword = searchValue.value.trim().toLowerCase() | ||
| 203 | - if (!keyword) { | ||
| 204 | - exercises.value = [...allExercises.value] | ||
| 205 | - return | 224 | +/** 重试加载 */ |
| 225 | +const retryLoad = () => { | ||
| 226 | + if (activeNav.value) { | ||
| 227 | + loadExercises(activeNav.value) | ||
| 206 | } | 228 | } |
| 207 | - exercises.value = allExercises.value.filter(item => | ||
| 208 | - item.exercises?.some(e => e.name.toLowerCase().includes(keyword)) | ||
| 209 | - ) | ||
| 210 | } | 229 | } |
| 211 | 230 | ||
| 212 | -// 点击选中/取消单个动作 | 231 | +// ==================== 操作交互 ==================== |
| 232 | + | ||
| 233 | +/** 选中/取消单个动作 */ | ||
| 213 | const toggleAction = (action) => { | 234 | const toggleAction = (action) => { |
| 214 | - console.log('点击的动作', action, '选中前数组', selectedActions.value) | ||
| 215 | const exist = selectedIds.value.has(action.id) | 235 | const exist = selectedIds.value.has(action.id) |
| 216 | if (exist) { | 236 | if (exist) { |
| 217 | selectedActions.value = selectedActions.value.filter(i => i.id !== action.id) | 237 | selectedActions.value = selectedActions.value.filter(i => i.id !== action.id) |
| 218 | } else { | 238 | } else { |
| 219 | - selectedActions.value.push({ ...action }) | 239 | + selectedActions.value = [...selectedActions.value, { ...action }] |
| 220 | } | 240 | } |
| 221 | - console.log('选中后', selectedActions.value) | ||
| 222 | } | 241 | } |
| 223 | 242 | ||
| 224 | -// 底部标签移除动作 | 243 | +/** 移除已选动作 */ |
| 225 | const removeAction = (action) => { | 244 | const removeAction = (action) => { |
| 226 | selectedActions.value = selectedActions.value.filter(i => i.id !== action.id) | 245 | selectedActions.value = selectedActions.value.filter(i => i.id !== action.id) |
| 227 | } | 246 | } |
| 228 | 247 | ||
| 229 | -// 打开命名弹窗 | 248 | +/** 打开命名弹窗 */ |
| 230 | const combineActions = () => { | 249 | const combineActions = () => { |
| 231 | if (selectedActions.value.length < 2) { | 250 | if (selectedActions.value.length < 2) { |
| 232 | uni.showToast({ title: '至少选择2个动作', icon: 'none' }) | 251 | uni.showToast({ title: '至少选择2个动作', icon: 'none' }) |
| @@ -236,12 +255,12 @@ const combineActions = () => { | @@ -236,12 +255,12 @@ const combineActions = () => { | ||
| 236 | showNamePopup.value = true | 255 | showNamePopup.value = true |
| 237 | } | 256 | } |
| 238 | 257 | ||
| 239 | -// 关闭弹窗 | 258 | +/** 关闭弹窗 */ |
| 240 | const closePopup = () => { | 259 | const closePopup = () => { |
| 241 | showNamePopup.value = false | 260 | showNamePopup.value = false |
| 242 | } | 261 | } |
| 243 | 262 | ||
| 244 | -// 【原有创建超级组接口不动】保存超级组 | 263 | +/** 保存超级组 */ |
| 245 | const saveCombination = async () => { | 264 | const saveCombination = async () => { |
| 246 | const name = combinationName.value.trim() | 265 | const name = combinationName.value.trim() |
| 247 | if (!name) { | 266 | if (!name) { |
| @@ -258,7 +277,6 @@ const saveCombination = async () => { | @@ -258,7 +277,6 @@ const saveCombination = async () => { | ||
| 258 | uni.showToast({ title: '创建成功', icon: 'success' }) | 277 | uni.showToast({ title: '创建成功', icon: 'success' }) |
| 259 | selectedActions.value = [] | 278 | selectedActions.value = [] |
| 260 | closePopup() | 279 | closePopup() |
| 261 | - // 创建成功返回上一页 | ||
| 262 | setTimeout(() => uni.navigateBack(), 800) | 280 | setTimeout(() => uni.navigateBack(), 800) |
| 263 | } else { | 281 | } else { |
| 264 | uni.showToast({ title: res.msg || '创建失败', icon: 'none' }) | 282 | uni.showToast({ title: res.msg || '创建失败', icon: 'none' }) |
| @@ -268,99 +286,83 @@ const saveCombination = async () => { | @@ -268,99 +286,83 @@ const saveCombination = async () => { | ||
| 268 | } | 286 | } |
| 269 | } | 287 | } |
| 270 | 288 | ||
| 271 | -// 返回上一页 | 289 | +/** 返回上一页 */ |
| 272 | const backPage = () => { | 290 | const backPage = () => { |
| 273 | uni.navigateBack() | 291 | uni.navigateBack() |
| 274 | } | 292 | } |
| 275 | </script> | 293 | </script> |
| 276 | 294 | ||
| 277 | <style lang="scss" scoped> | 295 | <style lang="scss" scoped> |
| 278 | -page { | ||
| 279 | - background-color: #f5f5f5; | ||
| 280 | -} | 296 | +// ==================== SCSS 变量 ==================== |
| 297 | +$color-primary: #f8d714; | ||
| 298 | +$color-bg: #f5f5f5; | ||
| 299 | +$color-bg-nav: #fafafa; | ||
| 300 | +$color-text-primary: #333; | ||
| 301 | +$color-text-secondary: #666; | ||
| 302 | +$color-text-muted: #999; | ||
| 303 | +$color-white: #fff; | ||
| 304 | +$color-dark-bar: #2c2c2e; | ||
| 305 | +$color-dark-tag: #444; | ||
| 306 | +$color-border: #eee; | ||
| 307 | +$color-disabled-bg: #eee; | ||
| 308 | +$color-disabled-text: #bbb; | ||
| 309 | + | ||
| 310 | +$z-fixed: 100; | ||
| 311 | + | ||
| 312 | +$radius-sm: 12rpx; | ||
| 313 | +$radius-md: 16rpx; | ||
| 314 | +$radius-lg: 24rpx; | ||
| 315 | +$radius-round: 40rpx; | ||
| 316 | + | ||
| 317 | +$spacing-xs: 8rpx; | ||
| 318 | +$spacing-sm: 16rpx; | ||
| 319 | +$spacing-md: 20rpx; | ||
| 320 | +$spacing-lg: 30rpx; | ||
| 321 | + | ||
| 322 | +$font-xs: 24rpx; | ||
| 323 | +$font-sm: 26rpx; | ||
| 324 | +$font-md: 28rpx; | ||
| 325 | +$font-lg: 30rpx; | ||
| 326 | +$font-xl: 32rpx; | ||
| 327 | + | ||
| 328 | + | ||
| 281 | 329 | ||
| 282 | .page-wrap { | 330 | .page-wrap { |
| 283 | width: 100%; | 331 | width: 100%; |
| 284 | - min-height: 100vh; | 332 | + height: 100vh; |
| 333 | + // #ifdef MP-WEIXIN | ||
| 334 | + height: 88vh; | ||
| 335 | + // #endif | ||
| 285 | display: flex; | 336 | display: flex; |
| 286 | flex-direction: column; | 337 | flex-direction: column; |
| 338 | + overflow: hidden; | ||
| 287 | } | 339 | } |
| 288 | 340 | ||
| 289 | -.add-btn-wrapper { | ||
| 290 | - position: fixed; | ||
| 291 | - left: 28rpx; | ||
| 292 | - top: calc(var(--status-bar-height) + 12rpx); | ||
| 293 | - z-index: 9999; | ||
| 294 | - width: 50rpx; | ||
| 295 | - height: 50rpx; | ||
| 296 | -} | ||
| 297 | - | ||
| 298 | -.search-bar { | ||
| 299 | - padding-top: var(--status-bar-height); | ||
| 300 | - height: 88rpx; | ||
| 301 | - background: #fff; | ||
| 302 | -} | ||
| 303 | - | ||
| 304 | -.floating-menu { | ||
| 305 | - position: absolute; | ||
| 306 | - top: 90rpx; | ||
| 307 | - left: 20rpx; | ||
| 308 | - background: #fff; | ||
| 309 | - border-radius: 12rpx; | ||
| 310 | - box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15); | ||
| 311 | - padding: 20rpx; | ||
| 312 | - z-index: 9999; | ||
| 313 | - width: 260rpx; | ||
| 314 | -} | ||
| 315 | - | ||
| 316 | -.menu-item { | ||
| 317 | - display: flex; | ||
| 318 | - align-items: center; | ||
| 319 | - gap: 16rpx; | ||
| 320 | -} | ||
| 321 | - | ||
| 322 | -.menu-text { | ||
| 323 | - box-sizing: border-box; | ||
| 324 | - display: flex; | ||
| 325 | - font-size: 28rpx; | ||
| 326 | - // margin-left: 200rpx; | ||
| 327 | -} | ||
| 328 | - | ||
| 329 | - | ||
| 330 | -// .search-bar { | ||
| 331 | -// padding: 20rpx 30rpx; | ||
| 332 | -// background: #fff; | ||
| 333 | - | ||
| 334 | -// .search-input { | ||
| 335 | -// width: 100%; | ||
| 336 | -// height: 70rpx; | ||
| 337 | -// border-radius: 35rpx; | ||
| 338 | -// background: #f7f7f7; | ||
| 339 | -// padding: 0 24rpx; | ||
| 340 | -// font-size: 28rpx; | ||
| 341 | -// border: 1rpx solid #eee; | ||
| 342 | -// } | ||
| 343 | -// } | ||
| 344 | - | 341 | +// ==================== 主体布局 ==================== |
| 345 | .layout-container { | 342 | .layout-container { |
| 346 | - flex: 1; | 343 | + |
| 344 | + min-height: 0; | ||
| 347 | display: flex; | 345 | display: flex; |
| 348 | overflow: hidden; | 346 | overflow: hidden; |
| 349 | - padding-bottom: 130rpx; | 347 | + padding-bottom: calc(100rpx + 30rpx); // 底部按钮栏 + 间距 |
| 348 | +} | ||
| 350 | 349 | ||
| 351 | - .left-nav { | ||
| 352 | - width: 150rpx; | ||
| 353 | - background: #fafafa; | ||
| 354 | - border-right: 1rpx solid #eee; | 350 | +// 左侧导航 |
| 351 | +.left-nav { | ||
| 352 | + width: 180rpx; | ||
| 353 | + background: $color-bg-nav; | ||
| 354 | + border-right: 1rpx solid $color-border; | ||
| 355 | 355 | ||
| 356 | .nav-item { | 356 | .nav-item { |
| 357 | - padding: 30rpx 20rpx; | ||
| 358 | - font-size: 26rpx; | ||
| 359 | - color: #666; | 357 | + padding: $spacing-lg $spacing-sm; |
| 358 | + font-size: $font-sm; | ||
| 359 | + color: $color-text-secondary; | ||
| 360 | position: relative; | 360 | position: relative; |
| 361 | + white-space: nowrap; | ||
| 362 | + text-align: center; | ||
| 361 | 363 | ||
| 362 | &.active { | 364 | &.active { |
| 363 | - background: #fff; | 365 | + background: $color-white; |
| 364 | color: #000; | 366 | color: #000; |
| 365 | font-weight: 500; | 367 | font-weight: 500; |
| 366 | 368 | ||
| @@ -372,230 +374,263 @@ page { | @@ -372,230 +374,263 @@ page { | ||
| 372 | transform: translateY(-50%); | 374 | transform: translateY(-50%); |
| 373 | width: 6rpx; | 375 | width: 6rpx; |
| 374 | height: 36rpx; | 376 | height: 36rpx; |
| 375 | - background: #f8d714; | ||
| 376 | - } | 377 | + background: $color-primary; |
| 377 | } | 378 | } |
| 378 | } | 379 | } |
| 379 | } | 380 | } |
| 381 | +} | ||
| 380 | 382 | ||
| 381 | - .right-content { | 383 | +// 右侧内容 |
| 384 | +.right-content { | ||
| 382 | flex: 1; | 385 | flex: 1; |
| 383 | - padding: 20rpx; | 386 | + padding: $spacing-md; |
| 387 | + padding-top: $spacing-lg; | ||
| 384 | 388 | ||
| 385 | - .tip { | 389 | + // 通用内容提示块 |
| 390 | + .content-tip { | ||
| 386 | display: flex; | 391 | display: flex; |
| 387 | - flex-wrap: wrap; | 392 | + flex-direction: column; |
| 393 | + align-items: center; | ||
| 394 | + justify-content: center; | ||
| 395 | + padding: 80rpx $spacing-lg; | ||
| 396 | + text-align: center; | ||
| 397 | + | ||
| 398 | + .tip-text { | ||
| 399 | + font-size: $font-md; | ||
| 400 | + color: $color-text-muted; | ||
| 401 | + } | ||
| 402 | + | ||
| 403 | + .tip-subtext { | ||
| 404 | + margin-top: $spacing-sm; | ||
| 405 | + font-size: $font-xs; | ||
| 406 | + color: #bbb; | ||
| 407 | + } | ||
| 408 | + } | ||
| 409 | + | ||
| 410 | + // 初始引导 | ||
| 411 | + .init-guide { | ||
| 412 | + .tip-text { | ||
| 413 | + font-size: $font-lg; | ||
| 414 | + color: $color-text-secondary; | ||
| 415 | + font-weight: 500; | ||
| 416 | + } | ||
| 417 | + } | ||
| 418 | + | ||
| 419 | + // 错误重试按钮 | ||
| 420 | + .retry-btn { | ||
| 421 | + margin-top: $spacing-md; | ||
| 422 | + padding: 12rpx 40rpx; | ||
| 423 | + font-size: $font-sm; | ||
| 424 | + color: #000; | ||
| 425 | + background: $color-primary; | ||
| 426 | + border-radius: $radius-round; | ||
| 427 | + } | ||
| 428 | + | ||
| 429 | + // 部位筛选标签(横向滚动) | ||
| 430 | + .part-filter { | ||
| 431 | + margin-bottom: $spacing-md; | ||
| 432 | + white-space: nowrap; | ||
| 433 | + | ||
| 434 | + .part-filter-inner { | ||
| 435 | + display: inline-flex; | ||
| 436 | + flex-wrap: nowrap; | ||
| 388 | gap: 15rpx; | 437 | gap: 15rpx; |
| 389 | - margin-bottom: 20rpx; | 438 | + } |
| 390 | 439 | ||
| 391 | - .item { | ||
| 392 | - padding: 10rpx 20rpx; | ||
| 393 | - background: #fff; | ||
| 394 | - border-radius: 16rpx; | ||
| 395 | - font-size: 24rpx; | 440 | + .filter-item { |
| 441 | + flex-shrink: 0; | ||
| 442 | + padding: 10rpx 24rpx; | ||
| 443 | + background: $color-white; | ||
| 444 | + border-radius: $radius-sm; | ||
| 445 | + font-size: $font-xs; | ||
| 446 | + color: $color-text-secondary; | ||
| 447 | + transition: all 0.2s; | ||
| 396 | 448 | ||
| 397 | &.active { | 449 | &.active { |
| 398 | - background: #f8d714; | 450 | + background: $color-primary; |
| 399 | color: #000; | 451 | color: #000; |
| 452 | + font-weight: 500; | ||
| 400 | } | 453 | } |
| 401 | } | 454 | } |
| 402 | } | 455 | } |
| 403 | 456 | ||
| 457 | + // 部位筛选为空 | ||
| 458 | + .part-empty-tip { | ||
| 459 | + padding: 30rpx $spacing-lg 40rpx; | ||
| 460 | + } | ||
| 461 | + | ||
| 404 | .equipment-item { | 462 | .equipment-item { |
| 405 | - margin-bottom: 30rpx; | 463 | + margin-bottom: $spacing-lg; |
| 406 | 464 | ||
| 407 | .equipment-name { | 465 | .equipment-name { |
| 408 | - font-size: 28rpx; | 466 | + font-size: $font-md; |
| 409 | font-weight: 500; | 467 | font-weight: 500; |
| 410 | - margin-bottom: 16rpx; | 468 | + color: $color-text-secondary; |
| 469 | + margin-bottom: $spacing-sm; | ||
| 470 | + padding-left: $spacing-xs; | ||
| 471 | + border-left: 6rpx solid $color-primary; | ||
| 411 | } | 472 | } |
| 412 | 473 | ||
| 413 | .action-list { | 474 | .action-list { |
| 414 | display: grid; | 475 | display: grid; |
| 415 | grid-template-columns: repeat(2, 1fr); | 476 | grid-template-columns: repeat(2, 1fr); |
| 416 | - gap: 16rpx; | 477 | + gap: $spacing-sm; |
| 417 | 478 | ||
| 418 | .action-item { | 479 | .action-item { |
| 419 | - background: #fff; | ||
| 420 | - border-radius: 16rpx; | ||
| 421 | - padding: 16rpx; | 480 | + background: $color-white; |
| 481 | + border-radius: $radius-md; | ||
| 482 | + padding: $spacing-sm; | ||
| 422 | display: flex; | 483 | display: flex; |
| 423 | flex-direction: column; | 484 | flex-direction: column; |
| 424 | align-items: center; | 485 | align-items: center; |
| 425 | - gap: 8rpx; | 486 | + gap: $spacing-xs; |
| 487 | + border: 2rpx solid transparent; | ||
| 488 | + transition: border-color 0.2s, transform 0.15s; | ||
| 426 | 489 | ||
| 427 | &.selected { | 490 | &.selected { |
| 428 | - border: 2rpx solid #f8d714; | 491 | + border-color: $color-primary; |
| 492 | + } | ||
| 493 | + | ||
| 494 | + &:active { | ||
| 495 | + transform: scale(0.97); | ||
| 429 | } | 496 | } |
| 430 | 497 | ||
| 431 | .action-img { | 498 | .action-img { |
| 432 | width: 100%; | 499 | width: 100%; |
| 433 | height: 200rpx; | 500 | height: 200rpx; |
| 434 | - border-radius: 12rpx; | 501 | + border-radius: $radius-sm; |
| 435 | } | 502 | } |
| 436 | 503 | ||
| 437 | .action-name { | 504 | .action-name { |
| 438 | - font-size: 24rpx; | ||
| 439 | - color: #333; | 505 | + font-size: $font-xs; |
| 506 | + color: $color-text-primary; | ||
| 440 | } | 507 | } |
| 441 | } | 508 | } |
| 442 | } | 509 | } |
| 443 | } | 510 | } |
| 444 | 511 | ||
| 512 | + // 无动作空状态 | ||
| 445 | .empty-state { | 513 | .empty-state { |
| 446 | padding-top: 100rpx; | 514 | padding-top: 100rpx; |
| 447 | - text-align: center; | ||
| 448 | - color: #999; | ||
| 449 | - font-size: 28rpx; | ||
| 450 | - } | ||
| 451 | } | 515 | } |
| 452 | } | 516 | } |
| 453 | 517 | ||
| 518 | +// ==================== 底部已选标签栏 ==================== | ||
| 454 | .selected-bar { | 519 | .selected-bar { |
| 455 | position: fixed; | 520 | position: fixed; |
| 456 | left: 0; | 521 | left: 0; |
| 457 | right: 0; | 522 | right: 0; |
| 458 | bottom: calc(100rpx + env(safe-area-inset-bottom)); | 523 | bottom: calc(100rpx + env(safe-area-inset-bottom)); |
| 459 | - background: #2c2c2e; | ||
| 460 | - padding: 20rpx 30rpx; | ||
| 461 | - display: flex; | ||
| 462 | - gap: 20rpx; | 524 | + background: $color-dark-bar; |
| 525 | + padding: 20rpx 20rpx 40rpx; | ||
| 463 | white-space: nowrap; | 526 | white-space: nowrap; |
| 527 | + z-index: $z-fixed; | ||
| 528 | + | ||
| 529 | + .selected-bar-inner { | ||
| 530 | + display: inline-flex; | ||
| 531 | + flex-wrap: nowrap; | ||
| 532 | + gap: $spacing-md; | ||
| 533 | + } | ||
| 464 | 534 | ||
| 465 | .selected-item { | 535 | .selected-item { |
| 466 | - background: #444; | ||
| 467 | - color: #fff; | ||
| 468 | - padding: 10rpx 20rpx; | 536 | + flex-shrink: 0; |
| 537 | + background: $color-dark-tag; | ||
| 538 | + color: $color-white; | ||
| 539 | + padding: 10rpx $spacing-md; | ||
| 469 | border-radius: 30rpx; | 540 | border-radius: 30rpx; |
| 470 | display: flex; | 541 | display: flex; |
| 471 | align-items: center; | 542 | align-items: center; |
| 472 | gap: 10rpx; | 543 | gap: 10rpx; |
| 473 | 544 | ||
| 474 | .name { | 545 | .name { |
| 475 | - font-size: 26rpx; | 546 | + font-size: $font-sm; |
| 476 | } | 547 | } |
| 477 | } | 548 | } |
| 478 | } | 549 | } |
| 479 | 550 | ||
| 551 | +// ==================== 底部操作按钮 ==================== | ||
| 480 | .bottom-btn-bar { | 552 | .bottom-btn-bar { |
| 481 | position: fixed; | 553 | position: fixed; |
| 482 | bottom: 0; | 554 | bottom: 0; |
| 483 | left: 0; | 555 | left: 0; |
| 484 | right: 0; | 556 | right: 0; |
| 485 | display: flex; | 557 | display: flex; |
| 486 | - gap: 20rpx; | ||
| 487 | - padding: 20rpx 30rpx calc(20rpx + env(safe-area-inset-bottom)); | ||
| 488 | - background: #fff; | 558 | + gap: $spacing-md; |
| 559 | + padding: $spacing-md $spacing-lg calc(20rpx + env(safe-area-inset-bottom)); | ||
| 560 | + background: $color-white; | ||
| 489 | box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05); | 561 | box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05); |
| 562 | + z-index: $z-fixed; | ||
| 490 | 563 | ||
| 491 | .btn { | 564 | .btn { |
| 492 | flex: 1; | 565 | flex: 1; |
| 493 | height: 80rpx; | 566 | height: 80rpx; |
| 494 | - border-radius: 40rpx; | 567 | + border-radius: $radius-round; |
| 495 | display: flex; | 568 | display: flex; |
| 496 | align-items: center; | 569 | align-items: center; |
| 497 | justify-content: center; | 570 | justify-content: center; |
| 498 | - font-size: 30rpx; | 571 | + font-size: $font-lg; |
| 499 | font-weight: bold; | 572 | font-weight: bold; |
| 500 | } | 573 | } |
| 501 | 574 | ||
| 502 | .cancel-btn { | 575 | .cancel-btn { |
| 503 | - background: #f5f5f5; | ||
| 504 | - color: #333; | 576 | + background: $color-bg; |
| 577 | + color: $color-text-primary; | ||
| 505 | } | 578 | } |
| 506 | 579 | ||
| 507 | .confirm-btn { | 580 | .confirm-btn { |
| 508 | - background: #f8d714; | 581 | + background: $color-primary; |
| 509 | color: #000; | 582 | color: #000; |
| 510 | 583 | ||
| 511 | &.disabled { | 584 | &.disabled { |
| 512 | - background: #eee; | ||
| 513 | - color: #bbb; | 585 | + background: $color-disabled-bg; |
| 586 | + color: $color-disabled-text; | ||
| 514 | } | 587 | } |
| 515 | } | 588 | } |
| 516 | } | 589 | } |
| 517 | 590 | ||
| 591 | +// ==================== 命名弹窗 ==================== | ||
| 518 | .popup-content { | 592 | .popup-content { |
| 519 | width: 100%; | 593 | width: 100%; |
| 520 | - background: #fff; | 594 | + background: $color-white; |
| 521 | box-sizing: border-box; | 595 | box-sizing: border-box; |
| 522 | - border-radius: 24rpx 24rpx 0 0; | ||
| 523 | - padding: 30rpx; | ||
| 524 | - height: 70vh; | 596 | + border-radius: $radius-lg $radius-lg 0 0; |
| 597 | + padding: $spacing-lg; | ||
| 598 | + max-height: 70vh; | ||
| 525 | 599 | ||
| 526 | .popup-header { | 600 | .popup-header { |
| 527 | display: flex; | 601 | display: flex; |
| 528 | justify-content: space-between; | 602 | justify-content: space-between; |
| 529 | - // align-items: center; | ||
| 530 | - padding-bottom: 20rpx; | ||
| 531 | - margin-bottom: 30rpx; | ||
| 532 | - | ||
| 533 | - .close-btn { | ||
| 534 | - font-size: 32rpx; | ||
| 535 | - color: #333; | ||
| 536 | - background: none; | ||
| 537 | - } | 603 | + align-items: center; |
| 604 | + padding-bottom: $spacing-md; | ||
| 605 | + margin-bottom: $spacing-lg; | ||
| 538 | 606 | ||
| 539 | .save-btn { | 607 | .save-btn { |
| 540 | - font-size: 28rpx; | ||
| 541 | - background: #f8d714; | 608 | + font-size: $font-md; |
| 609 | + background: $color-primary; | ||
| 542 | padding: 12rpx 32rpx; | 610 | padding: 12rpx 32rpx; |
| 543 | border-radius: 32rpx; | 611 | border-radius: 32rpx; |
| 544 | color: #000; | 612 | color: #000; |
| 545 | } | 613 | } |
| 546 | 614 | ||
| 547 | .popup-title { | 615 | .popup-title { |
| 548 | - font-size: 32rpx; | 616 | + font-size: $font-xl; |
| 549 | font-weight: bold; | 617 | font-weight: bold; |
| 550 | - | ||
| 551 | } | 618 | } |
| 552 | } | 619 | } |
| 553 | 620 | ||
| 554 | .input-wrapper { | 621 | .input-wrapper { |
| 555 | - margin-bottom: 30rpx; | ||
| 556 | - display: flex; | ||
| 557 | - // justify-content: center; | 622 | + margin-bottom: $spacing-lg; |
| 558 | 623 | ||
| 559 | .name-input { | 624 | .name-input { |
| 560 | width: 100%; | 625 | width: 100%; |
| 561 | height: 300rpx; | 626 | height: 300rpx; |
| 562 | - // border: 1rpx solid #ffffff; | ||
| 563 | background-color: #ececec; | 627 | background-color: #ececec; |
| 564 | - border-radius: 16rpx; | 628 | + border-radius: $radius-md; |
| 565 | padding: 24rpx; | 629 | padding: 24rpx; |
| 566 | - font-size: 28rpx; | 630 | + font-size: $font-md; |
| 567 | text-align: left; | 631 | text-align: left; |
| 568 | vertical-align: top; | 632 | vertical-align: top; |
| 569 | } | 633 | } |
| 570 | - | ||
| 571 | - .hint { | ||
| 572 | - font-size: 24rpx; | ||
| 573 | - color: #999; | ||
| 574 | - margin-top: 12rpx; | ||
| 575 | - } | ||
| 576 | - } | ||
| 577 | - | ||
| 578 | - .preview-section { | ||
| 579 | - .preview-title { | ||
| 580 | - font-size: 28rpx; | ||
| 581 | - margin-bottom: 16rpx; | ||
| 582 | - color: #666; | ||
| 583 | - } | ||
| 584 | - | ||
| 585 | - .preview-scroll { | ||
| 586 | - display: flex; | ||
| 587 | - gap: 16rpx; | ||
| 588 | - overflow-x: auto; | ||
| 589 | - | ||
| 590 | - .preview-tag { | ||
| 591 | - background: #f0f9ff; | ||
| 592 | - color: #f8d714; | ||
| 593 | - padding: 8rpx 20rpx; | ||
| 594 | - border-radius: 50rpx; | ||
| 595 | - font-size: 24rpx; | ||
| 596 | - white-space: nowrap; | ||
| 597 | - } | ||
| 598 | - } | ||
| 599 | } | 634 | } |
| 600 | } | 635 | } |
| 601 | </style> | 636 | </style> |
| 1 | <template> | 1 | <template> |
| 2 | - <view class="template-edit-page" @click="dismissGuide"> | 2 | + <view class="template-edit-page" @click="dismissGuide" :style="{ paddingTop: PageHeight + 'px' }"> |
| 3 | <!-- 顶部导航 --> | 3 | <!-- 顶部导航 --> |
| 4 | <view class="header"> | 4 | <view class="header"> |
| 5 | <view class="close-btn" @click="goBack"> | 5 | <view class="close-btn" @click="goBack"> |
| @@ -16,26 +16,19 @@ | @@ -16,26 +16,19 @@ | ||
| 16 | </text> | 16 | </text> |
| 17 | </view> | 17 | </view> |
| 18 | 18 | ||
| 19 | - <view | ||
| 20 | - class="save-btn" | ||
| 21 | - :class="{ disabled: unitCount === 0, active: unitCount > 0 }" | ||
| 22 | - @click="handleSave" | ||
| 23 | - > | 19 | + <view class="save-btn" :class="{ disabled: unitCount === 0, active: unitCount > 0 }" @click="handleSave"> |
| 24 | 保存模板 | 20 | 保存模板 |
| 25 | </view> | 21 | </view> |
| 26 | </view> | 22 | </view> |
| 27 | 23 | ||
| 28 | <!-- 内容区 --> | 24 | <!-- 内容区 --> |
| 29 | - <view class="content-area"> | 25 | + <view class="content-wrapper"> |
| 26 | + <scroll-view scroll-y class="content-area"> | ||
| 30 | <template v-if="unitCount"> | 27 | <template v-if="unitCount"> |
| 31 | <view v-for="(unit, index) in units" :key="unit.unitId"> | 28 | <view v-for="(unit, index) in units" :key="unit.unitId"> |
| 32 | - <dongzuo | ||
| 33 | - :id="unit.unitId" | ||
| 34 | - :type="unit.unitType" | ||
| 35 | - :action-detail="trainingStore.convertUnitToActionDetail(unit)" | ||
| 36 | - :ref="(el) => setActionRef(el, index)" | ||
| 37 | - :unit-index="index" | ||
| 38 | - /> | 29 | + <dongzuo :id="unit.unitId" :type="unit.unitType" |
| 30 | + :action-detail="trainingStore.convertUnitToActionDetail(unit)" :ref="(el) => setActionRef(el, index)" | ||
| 31 | + :unit-index="index" :isTick="false" /> | ||
| 39 | </view> | 32 | </view> |
| 40 | </template> | 33 | </template> |
| 41 | 34 | ||
| @@ -45,6 +38,7 @@ | @@ -45,6 +38,7 @@ | ||
| 45 | 点击最下方 + 开始添加动作 | 38 | 点击最下方 + 开始添加动作 |
| 46 | </view> | 39 | </view> |
| 47 | </view> | 40 | </view> |
| 41 | + </scroll-view> | ||
| 48 | </view> | 42 | </view> |
| 49 | 43 | ||
| 50 | <!-- 引导气泡 --> | 44 | <!-- 引导气泡 --> |
| @@ -81,7 +75,7 @@ | @@ -81,7 +75,7 @@ | ||
| 81 | </view> | 75 | </view> |
| 82 | 76 | ||
| 83 | <!-- 文本编辑弹窗 --> | 77 | <!-- 文本编辑弹窗 --> |
| 84 | - <view v-if="popupType" class="text-popup"> | 78 | + <up-popup :show="!!popupType" mode="bottom" round="24rpx" @close="closeTextPopup" bg-color="#333"> |
| 85 | <view class="popup-header"> | 79 | <view class="popup-header"> |
| 86 | <view class="popup-close-btn" @click="closeTextPopup"> | 80 | <view class="popup-close-btn" @click="closeTextPopup"> |
| 87 | <up-icon name="close" color="#fff" size="20" bold /> | 81 | <up-icon name="close" color="#fff" size="20" bold /> |
| @@ -90,28 +84,15 @@ | @@ -90,28 +84,15 @@ | ||
| 90 | <view class="popup-save-btn" @click="saveTextPopup">保存</view> | 84 | <view class="popup-save-btn" @click="saveTextPopup">保存</view> |
| 91 | </view> | 85 | </view> |
| 92 | <view class="popup-content"> | 86 | <view class="popup-content"> |
| 93 | - <textarea | ||
| 94 | - class="intro-textarea" | ||
| 95 | - v-model="popupValue" | ||
| 96 | - :placeholder="popupType === 'intro' ? '在此处输入模板介绍' : '请输入模板标题'" | ||
| 97 | - /> | ||
| 98 | - </view> | 87 | + <textarea class="intro-textarea" v-model="popupValue" |
| 88 | + :placeholder="popupType === 'intro' ? '在此处输入模板介绍' : '请输入模板标题'" /> | ||
| 99 | </view> | 89 | </view> |
| 90 | + </up-popup> | ||
| 100 | 91 | ||
| 101 | <!-- 场景选择器 --> | 92 | <!-- 场景选择器 --> |
| 102 | - <up-picker | ||
| 103 | - :show="showScenePicker" | ||
| 104 | - :columns="sceneColumns" | ||
| 105 | - :default-index="[0]" | ||
| 106 | - title="训练场景" | ||
| 107 | - title-color="#ffffff" | ||
| 108 | - cancel-color="#cccccc" | ||
| 109 | - confirm-color="#409eff" | ||
| 110 | - active-color="#ffd100" | ||
| 111 | - @confirm="handleSceneConfirm" | ||
| 112 | - @cancel="showScenePicker = false" | ||
| 113 | - close-on-click-overlay | ||
| 114 | - /> | 93 | + <up-picker :show="showScenePicker" :columns="sceneColumns" :default-index="[0]" title="训练场景" title-color="#ffffff" |
| 94 | + cancel-color="#cccccc" confirm-color="#409eff" active-color="#ffd100" @confirm="handleSceneConfirm" | ||
| 95 | + @cancel="showScenePicker = false" close-on-click-overlay /> | ||
| 115 | 96 | ||
| 116 | <!-- 动作排序 / 新增 --> | 97 | <!-- 动作排序 / 新增 --> |
| 117 | <action-sort ref="actionSortRef" @open-add-actions="addExShow = true" /> | 98 | <action-sort ref="actionSortRef" @open-add-actions="addExShow = true" /> |
| @@ -131,11 +112,17 @@ import ActionSort from '@/pages/xunji/components/dongzuo-lianxi/dongzuo-paixu.vu | @@ -131,11 +112,17 @@ import ActionSort from '@/pages/xunji/components/dongzuo-lianxi/dongzuo-paixu.vu | ||
| 131 | import { useTrainingStore } from '@/sheep/store/trainingStore'; | 112 | import { useTrainingStore } from '@/sheep/store/trainingStore'; |
| 132 | import FileApi from '@/sheep/api/infra/file'; | 113 | import FileApi from '@/sheep/api/infra/file'; |
| 133 | import TemplatesApi from '@/sheep/api/Template/Templates'; | 114 | import TemplatesApi from '@/sheep/api/Template/Templates'; |
| 134 | - | 115 | +import { getMenuButtonHeight, getTopSafeArea } from '@/utils/safeArea'; |
| 116 | +const menuButtonHeight = ref(getMenuButtonHeight()); | ||
| 117 | +const topSafeArea = ref(getTopSafeArea()); | ||
| 118 | +const PageHeight = ref(topSafeArea.value); | ||
| 119 | +// #ifdef MP-WEIXIN | ||
| 120 | +PageHeight.value = menuButtonHeight.value + topSafeArea.value; | ||
| 121 | +// #endif | ||
| 135 | dayjs.extend(duration); | 122 | dayjs.extend(duration); |
| 136 | 123 | ||
| 137 | // ---- 常量 ---- | 124 | // ---- 常量 ---- |
| 138 | -const NAVIGATE_DELAY = 1500; | 125 | +const NAVIGATE_DELAY = 1000; |
| 139 | 126 | ||
| 140 | const SCENE_LIST = [ | 127 | const SCENE_LIST = [ |
| 141 | { id: 0, title: '不限' }, | 128 | { id: 0, title: '不限' }, |
| @@ -367,7 +354,6 @@ $radius-circle: 50%; | @@ -367,7 +354,6 @@ $radius-circle: 50%; | ||
| 367 | .template-edit-page { | 354 | .template-edit-page { |
| 368 | position: relative; | 355 | position: relative; |
| 369 | width: 100%; | 356 | width: 100%; |
| 370 | - height: 100vh; | ||
| 371 | background-color: $color-bg-primary; | 357 | background-color: $color-bg-primary; |
| 372 | display: flex; | 358 | display: flex; |
| 373 | flex-direction: column; | 359 | flex-direction: column; |
| @@ -438,14 +424,20 @@ $radius-circle: 50%; | @@ -438,14 +424,20 @@ $radius-circle: 50%; | ||
| 438 | } | 424 | } |
| 439 | 425 | ||
| 440 | // ---- 内容区 ---- | 426 | // ---- 内容区 ---- |
| 441 | -.content-area { | ||
| 442 | - flex: 1; | ||
| 443 | - overflow-y: auto; | 427 | +.content-wrapper { |
| 428 | + | ||
| 429 | + height: 83vh; | ||
| 444 | padding-bottom: 20rpx; | 430 | padding-bottom: 20rpx; |
| 431 | + box-sizing: border-box; | ||
| 432 | + overflow: hidden; | ||
| 433 | +} | ||
| 434 | + | ||
| 435 | +.content-area { | ||
| 436 | + height: 100%; | ||
| 445 | } | 437 | } |
| 446 | 438 | ||
| 447 | .empty-area { | 439 | .empty-area { |
| 448 | - flex: 1; | 440 | + height: 100%; |
| 449 | display: flex; | 441 | display: flex; |
| 450 | align-items: center; | 442 | align-items: center; |
| 451 | justify-content: center; | 443 | justify-content: center; |
| @@ -461,8 +453,8 @@ $radius-circle: 50%; | @@ -461,8 +453,8 @@ $radius-circle: 50%; | ||
| 461 | 453 | ||
| 462 | // ---- 引导气泡 ---- | 454 | // ---- 引导气泡 ---- |
| 463 | .guide-bubble { | 455 | .guide-bubble { |
| 464 | - position: absolute; | ||
| 465 | - bottom: 130rpx; | 456 | + position: fixed; |
| 457 | + bottom: 200rpx; | ||
| 466 | left: 50%; | 458 | left: 50%; |
| 467 | transform: translateX(-50%); | 459 | transform: translateX(-50%); |
| 468 | background-color: #000; | 460 | background-color: #000; |
| @@ -485,6 +477,10 @@ $radius-circle: 50%; | @@ -485,6 +477,10 @@ $radius-circle: 50%; | ||
| 485 | 477 | ||
| 486 | // ---- 底部栏 ---- | 478 | // ---- 底部栏 ---- |
| 487 | .bottom-bar { | 479 | .bottom-bar { |
| 480 | + position: fixed; | ||
| 481 | + left: 0; | ||
| 482 | + right: 0; | ||
| 483 | + bottom: 0; | ||
| 488 | display: flex; | 484 | display: flex; |
| 489 | height: 110rpx; | 485 | height: 110rpx; |
| 490 | align-items: center; | 486 | align-items: center; |
| @@ -493,7 +489,6 @@ $radius-circle: 50%; | @@ -493,7 +489,6 @@ $radius-circle: 50%; | ||
| 493 | padding-bottom: env(safe-area-inset-bottom); | 489 | padding-bottom: env(safe-area-inset-bottom); |
| 494 | background-color: $color-bg-primary; | 490 | background-color: $color-bg-primary; |
| 495 | border-top: 1rpx solid $color-border; | 491 | border-top: 1rpx solid $color-border; |
| 496 | - flex-shrink: 0; | ||
| 497 | 492 | ||
| 498 | .bar-item { | 493 | .bar-item { |
| 499 | display: flex; | 494 | display: flex; |
| @@ -528,19 +523,6 @@ $radius-circle: 50%; | @@ -528,19 +523,6 @@ $radius-circle: 50%; | ||
| 528 | } | 523 | } |
| 529 | 524 | ||
| 530 | // ---- 文本弹窗 ---- | 525 | // ---- 文本弹窗 ---- |
| 531 | -.text-popup { | ||
| 532 | - position: fixed; | ||
| 533 | - left: 0; | ||
| 534 | - right: 0; | ||
| 535 | - bottom: 0; | ||
| 536 | - height: 85vh; | ||
| 537 | - background: $color-bg-secondary; | ||
| 538 | - border-radius: $radius-lg $radius-lg 0 0; | ||
| 539 | - z-index: 1000; | ||
| 540 | - display: flex; | ||
| 541 | - flex-direction: column; | ||
| 542 | -} | ||
| 543 | - | ||
| 544 | .popup-header { | 526 | .popup-header { |
| 545 | display: flex; | 527 | display: flex; |
| 546 | justify-content: space-between; | 528 | justify-content: space-between; |
| @@ -78,14 +78,7 @@ | @@ -78,14 +78,7 @@ | ||
| 78 | <view | 78 | <view |
| 79 | class="rest-countdown" | 79 | class="rest-countdown" |
| 80 | v-if="restCountdownSeconds > 0" | 80 | v-if="restCountdownSeconds > 0" |
| 81 | - @click=" | ||
| 82 | - clearRestCountdown(); | ||
| 83 | - uncheckCurrentUnit(); | ||
| 84 | - if (restTimerWasRunning) { | ||
| 85 | - trainingStore.toggleTimer(); | ||
| 86 | - restTimerWasRunning = false; | ||
| 87 | - } | ||
| 88 | - " | 81 | + @click="clearRestCountdown()" |
| 89 | > | 82 | > |
| 90 | <view class="countdown-circle"> | 83 | <view class="countdown-circle"> |
| 91 | <!-- H5 端:使用 SVG 进度环 --> | 84 | <!-- H5 端:使用 SVG 进度环 --> |
| @@ -288,7 +281,6 @@ | @@ -288,7 +281,6 @@ | ||
| 288 | const restCountdownSeconds = ref(0); | 281 | const restCountdownSeconds = ref(0); |
| 289 | const restCountdownTotal = ref(0); | 282 | const restCountdownTotal = ref(0); |
| 290 | let restCountdownInterval = null; | 283 | let restCountdownInterval = null; |
| 291 | - let restTimerWasRunning = false; | ||
| 292 | 284 | ||
| 293 | const restFormattedTime = computed(() => { | 285 | const restFormattedTime = computed(() => { |
| 294 | const min = Math.floor(restCountdownSeconds.value / 60); | 286 | const min = Math.floor(restCountdownSeconds.value / 60); |
| @@ -304,8 +296,6 @@ | @@ -304,8 +296,6 @@ | ||
| 304 | // H5 SVG 圆环周长(用于 stroke-dasharray) | 296 | // H5 SVG 圆环周长(用于 stroke-dasharray) |
| 305 | const CIRCUMFERENCE = 2 * Math.PI * 44; | 297 | const CIRCUMFERENCE = 2 * Math.PI * 44; |
| 306 | 298 | ||
| 307 | - const currentRestUnitIndex = ref(null); | ||
| 308 | - | ||
| 309 | const clearRestCountdown = () => { | 299 | const clearRestCountdown = () => { |
| 310 | if (restCountdownInterval) { | 300 | if (restCountdownInterval) { |
| 311 | clearInterval(restCountdownInterval); | 301 | clearInterval(restCountdownInterval); |
| @@ -315,39 +305,33 @@ | @@ -315,39 +305,33 @@ | ||
| 315 | restCountdownTotal.value = 0; | 305 | restCountdownTotal.value = 0; |
| 316 | }; | 306 | }; |
| 317 | 307 | ||
| 318 | - const startRestCountdown = (seconds, unitIndex) => { | 308 | + const startRestCountdown = (seconds) => { |
| 319 | if (!seconds || seconds <= 0) return; | 309 | if (!seconds || seconds <= 0) return; |
| 320 | - clearRestCountdown(); | 310 | + // 叠加倒计时:如果已经在倒计时中则累加,否则开始新的倒计时 |
| 311 | + if (restCountdownInterval) { | ||
| 312 | + restCountdownSeconds.value += seconds; | ||
| 313 | + restCountdownTotal.value += seconds; | ||
| 314 | + } else { | ||
| 321 | restCountdownTotal.value = seconds; | 315 | restCountdownTotal.value = seconds; |
| 322 | restCountdownSeconds.value = seconds; | 316 | restCountdownSeconds.value = seconds; |
| 323 | - currentRestUnitIndex.value = unitIndex; | ||
| 324 | - restTimerWasRunning = !trainingStore.isPause; | ||
| 325 | - if (restTimerWasRunning) trainingStore.toggleTimer(); | ||
| 326 | restCountdownInterval = setInterval(() => { | 317 | restCountdownInterval = setInterval(() => { |
| 327 | restCountdownSeconds.value--; | 318 | restCountdownSeconds.value--; |
| 328 | if (restCountdownSeconds.value <= 0) { | 319 | if (restCountdownSeconds.value <= 0) { |
| 329 | clearRestCountdown(); | 320 | clearRestCountdown(); |
| 330 | - uncheckCurrentUnit(); | ||
| 331 | - if (restTimerWasRunning) trainingStore.toggleTimer(); | 321 | + // 倒计时结束:不取消勾选状态,不恢复主计时器(主计时器始终在运行) |
| 332 | } | 322 | } |
| 333 | }, 1000); | 323 | }, 1000); |
| 334 | - }; | ||
| 335 | - | ||
| 336 | - function uncheckCurrentUnit() { | ||
| 337 | - if (currentRestUnitIndex.value != null) { | ||
| 338 | - const ref = dongzuoRefs.value[currentRestUnitIndex.value]; | ||
| 339 | - if (ref?.resetActiveSets) ref.resetActiveSets(); | ||
| 340 | - currentRestUnitIndex.value = null; | ||
| 341 | - } | ||
| 342 | } | 324 | } |
| 325 | + // 不暂停主计时器,训练计时器始终继续 | ||
| 326 | + }; | ||
| 343 | 327 | ||
| 344 | - function onCancelRestTimer(unitIndex) { | 328 | + function onCancelRestTimer(seconds) { |
| 329 | + if (!restCountdownInterval) return; | ||
| 330 | + // 取消勾选时减去对应秒数 | ||
| 331 | + restCountdownSeconds.value -= (seconds || 0); | ||
| 332 | + restCountdownTotal.value -= (seconds || 0); | ||
| 333 | + if (restCountdownSeconds.value <= 0) { | ||
| 345 | clearRestCountdown(); | 334 | clearRestCountdown(); |
| 346 | - currentRestUnitIndex.value = unitIndex; | ||
| 347 | - uncheckCurrentUnit(); | ||
| 348 | - if (restTimerWasRunning) { | ||
| 349 | - trainingStore.toggleTimer(); | ||
| 350 | - restTimerWasRunning = false; | ||
| 351 | } | 335 | } |
| 352 | } | 336 | } |
| 353 | 337 | ||
| @@ -545,8 +529,25 @@ | @@ -545,8 +529,25 @@ | ||
| 545 | } | 529 | } |
| 546 | } | 530 | } |
| 547 | 531 | ||
| 548 | - if (units.length === 0) { | ||
| 549 | - uni.showToast({ title: '未获取到训练数据', icon: 'none' }); | 532 | + // 检查是否有打勾(已完成)的动作,只有打勾的动作才会被提交 |
| 533 | + const hasAnyChecked = units.some(u => | ||
| 534 | + u.exercises.some(e => e.sets && e.sets.length > 0) | ||
| 535 | + ); | ||
| 536 | + | ||
| 537 | + if (!hasAnyChecked) { | ||
| 538 | + uni.showModal({ | ||
| 539 | + title: '提示', | ||
| 540 | + content: '未完成任何动作,是否取消训练?', | ||
| 541 | + confirmText: '取消训练', | ||
| 542 | + cancelText: '继续训练', | ||
| 543 | + confirmColor: '#FF3B30', | ||
| 544 | + success: (res) => { | ||
| 545 | + if (res.confirm) { | ||
| 546 | + uni.navigateBack(); | ||
| 547 | + trainingStore.clearTrainingStore(); | ||
| 548 | + } | ||
| 549 | + }, | ||
| 550 | + }); | ||
| 550 | return; | 551 | return; |
| 551 | } | 552 | } |
| 552 | 553 |
-
Please register or login to post a comment