h5样式修改,每日模板修改和最小化可运行,动作详情的收藏功能,备注弹窗修复,还有动图彻底修复,动作排序弹窗。
Showing
31 changed files
with
899 additions
and
379 deletions
| @@ -4,7 +4,7 @@ SHOPRO_VERSION=v2.4.1 | @@ -4,7 +4,7 @@ SHOPRO_VERSION=v2.4.1 | ||
| 4 | # 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development) | 4 | # 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development) |
| 5 | # SHOPRO_BASE_URL=http://api-dashboard.yudao.iocoder.cn | 5 | # SHOPRO_BASE_URL=http://api-dashboard.yudao.iocoder.cn |
| 6 | # SHOPRO_BASE_URL=http://mall.hcxtec.com | 6 | # SHOPRO_BASE_URL=http://mall.hcxtec.com |
| 7 | -SHOPRO_BASE_URL=https://xunji.geaktec.com | 7 | +# SHOPRO_BASE_URL=https://xunji.geaktec.com |
| 8 | # 后端接口 - 测试环境(通过 process.env.NODE_ENV = development) | 8 | # 后端接口 - 测试环境(通过 process.env.NODE_ENV = development) |
| 9 | # SHOPRO_DEV_BASE_URL=http://192.168.1.200:48081 | 9 | # SHOPRO_DEV_BASE_URL=http://192.168.1.200:48081 |
| 10 | # SHOPRO_DEV_BASE_URL=http://192.168.1.85:48080 | 10 | # SHOPRO_DEV_BASE_URL=http://192.168.1.85:48080 |
.workbuddy/memory/2026-06-23.md
0 → 100644
| 1 | +# 2026-06-23 工作日志 | ||
| 2 | + | ||
| 3 | +## 悬浮球快照方案 — 完整改造方案 | ||
| 4 | + | ||
| 5 | +分析了 exercising 健身小程序中 trainingStore(Pinia)被三个功能模块(动作训练、超级组、每日模板编辑/修改)共享导致的数据冲突问题。 | ||
| 6 | + | ||
| 7 | +### 问题根因 | ||
| 8 | +- `grid-cell-content-popup.vue` 的编辑按钮 (`templateEdit`) 调用 `loadDailyTemplateForEdit()` 覆盖 store | ||
| 9 | +- `wode-xinjian-moban.vue` 的 `goBack()` / `handleSave()` 调用 `clearTrainingStore()` 清空 store | ||
| 10 | +- `meiri-moban-xiugai.vue` 的修改弹窗(弹窗模式)已通过快照解决,但编辑和新建是页面跳转模式,快照来不及归还 | ||
| 11 | + | ||
| 12 | +### 最终方案:悬浮球持有快照 | ||
| 13 | + | ||
| 14 | +核心思路:最小化时将 trainingStore 全量深拷贝到共享 reactive 模块 `trainingSnapshot`(存在 JS 堆内存,非 Pinia),悬浮球由此独立控制显隐和回显。 | ||
| 15 | + | ||
| 16 | +改动清单: | ||
| 17 | +1. **新建** `sheep/store/trainingSnapshot.js` — 共享 reactive 快照模块 | ||
| 18 | +2. **重写** `pages/TrainingFloating.vue` — 自有 showBall + 回显逻辑 + 计时器补偿 | ||
| 19 | +3. **修改** `pages4/.../xunji-dongzuo-lianxi.vue` openMin() — 保存快照到共享模块 | ||
| 20 | +4. **删除** `sheep/store/trainingStore.js` clearTrainingStore() 中 `this.min = false` | ||
| 21 | +5. **不改** grid-cell-content-popup.vue 和 wode-xinjian-moban.vue | ||
| 22 | +6. **修复** meiri-moban-xiugai.vue 中 Object.assign → $patch | ||
| 23 | + | ||
| 24 | +总计约 113 行,4 文件改动,2 文件不动。 |
| 1 | { | 1 | { |
| 2 | "name": "智能健身", | 2 | "name": "智能健身", |
| 3 | - "appid": "__UNI__8F24C84", | 3 | + "appid": "__UNI__7CAA18B", |
| 4 | "description": "基于 uni-app + Vue3 技术驱动的在线商城系统,内含诸多功能与丰富的活动,期待您的使用和反馈。", | 4 | "description": "基于 uni-app + Vue3 技术驱动的在线商城系统,内含诸多功能与丰富的活动,期待您的使用和反馈。", |
| 5 | "versionName": "2025.10", | 5 | "versionName": "2025.10", |
| 6 | "versionCode": "183", | 6 | "versionCode": "183", |
| @@ -183,7 +183,7 @@ | @@ -183,7 +183,7 @@ | ||
| 183 | "versionCode": 100 | 183 | "versionCode": 100 |
| 184 | }, | 184 | }, |
| 185 | "mp-weixin": { | 185 | "mp-weixin": { |
| 186 | - "appid": "wxb827c923ce0aad4b", | 186 | + "appid": "wxa54e6a259fb3338d", |
| 187 | "setting": { | 187 | "setting": { |
| 188 | "urlCheck": true, | 188 | "urlCheck": true, |
| 189 | "minified": true, | 189 | "minified": true, |
| @@ -24,7 +24,7 @@ const goToTrainingPage = () => { | @@ -24,7 +24,7 @@ const goToTrainingPage = () => { | ||
| 24 | // 悬浮球消失 | 24 | // 悬浮球消失 |
| 25 | trainingStore.min = false; | 25 | trainingStore.min = false; |
| 26 | uni.navigateTo({ | 26 | uni.navigateTo({ |
| 27 | - url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${trainingStore.id}&type=${trainingStore.type}`, | 27 | + url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${trainingStore.id}&type=${trainingStore.type}&isTraining=true`, |
| 28 | }); | 28 | }); |
| 29 | }; | 29 | }; |
| 30 | 30 |
| @@ -148,7 +148,7 @@ const FUNCTION_CONFIG_LIST = [ | @@ -148,7 +148,7 @@ const FUNCTION_CONFIG_LIST = [ | ||
| 148 | }, | 148 | }, |
| 149 | 149 | ||
| 150 | { | 150 | { |
| 151 | - text: '关于鸿星', | 151 | + text: '关于我们', |
| 152 | url: '/pages5/pages/user/wode-guanyu-hongxing', | 152 | url: '/pages5/pages/user/wode-guanyu-hongxing', |
| 153 | icon: '/static/icons/antOutline-exclamation-circle 1@1x.png', | 153 | icon: '/static/icons/antOutline-exclamation-circle 1@1x.png', |
| 154 | }, | 154 | }, |
| @@ -218,11 +218,14 @@ const fetchPageData = async () => { | @@ -218,11 +218,14 @@ const fetchPageData = async () => { | ||
| 218 | // UserApi.getUserInfo(), | 218 | // UserApi.getUserInfo(), |
| 219 | // // MemberApi.getMemberLevel(), | 219 | // // MemberApi.getMemberLevel(), |
| 220 | // ]); | 220 | // ]); |
| 221 | - const userRes = UserApi.getUserInfo(); | ||
| 222 | 221 | ||
| 222 | + const userRes = await UserApi.getUserInfo(); | ||
| 223 | + | ||
| 224 | + console.log('后端返回个人信息:', userRes.data); | ||
| 223 | const rawUser = userRes.data || {}; | 225 | const rawUser = userRes.data || {}; |
| 224 | userInfo.value = rawUser; | 226 | userInfo.value = rawUser; |
| 225 | 227 | ||
| 228 | + | ||
| 226 | // 架构重构:数据拉取后一次性计算出等级映射结果,拒绝在 computed 内部循环执行实例化 | 229 | // 架构重构:数据拉取后一次性计算出等级映射结果,拒绝在 computed 内部循环执行实例化 |
| 227 | // const levels = levelRes.data?.detailList || []; | 230 | // const levels = levelRes.data?.detailList || []; |
| 228 | // if (rawUser.level !== undefined) { | 231 | // if (rawUser.level !== undefined) { |
| 1 | <template> | 1 | <template> |
| 2 | - <up-popup :show="show" mode="bottom" round="16" :safeAreaInsetBottom="false"> | 2 | + <!-- 全屏遮罩层 --> |
| 3 | + <view v-if="show" class="mask" @click="maskClose"> | ||
| 4 | + <!-- 底部弹窗主体 --> | ||
| 5 | + <view class="popup-wrap" @click.stop> | ||
| 3 | <view class="desc-container"> | 6 | <view class="desc-container"> |
| 4 | <view class="title"> | 7 | <view class="title"> |
| 5 | <view @click="show = false"> | 8 | <view @click="show = false"> |
| 6 | <up-icon name="close" color="#fff" size="20"></up-icon> | 9 | <up-icon name="close" color="#fff" size="20"></up-icon> |
| 7 | </view> | 10 | </view> |
| 8 | - <text class="titleName"> | ||
| 9 | - 动作备注 | ||
| 10 | - </text> | 11 | + <text class="titleName">动作备注</text> |
| 11 | <view class="btn" @click="saveNoteContent">保存</view> | 12 | <view class="btn" @click="saveNoteContent">保存</view> |
| 12 | </view> | 13 | </view> |
| 13 | - <view class="input-box"> | ||
| 14 | - <up-textarea v-model="tempNoteContent" placeholder="此处填写个人备注" class="noteConten" | ||
| 15 | - customStyle="border-radius:12rpx; padding:20rpx;background: #242424;" /> | 14 | + <view class="area-box"> |
| 15 | + <!-- <up-textarea v-model="tempNoteContent" placeholder="此处填写个人备注" class="noteConten" | ||
| 16 | + customStyle="border-radius:12rpx; padding:20rpx;background: #242424;" /> --> | ||
| 17 | + <textarea class="textarea" v-model="tempNoteContent" placeholder="此处填写个人备注" /> | ||
| 18 | + </view> | ||
| 19 | + </view> | ||
| 16 | </view> | 20 | </view> |
| 17 | </view> | 21 | </view> |
| 18 | - </up-popup> | ||
| 19 | </template> | 22 | </template> |
| 20 | 23 | ||
| 21 | <script setup> | 24 | <script setup> |
| @@ -25,35 +28,83 @@ const show = ref(false); | @@ -25,35 +28,83 @@ const show = ref(false); | ||
| 25 | const tempNoteContent = ref(''); // 临时编辑的备注内容 | 28 | const tempNoteContent = ref(''); // 临时编辑的备注内容 |
| 26 | 29 | ||
| 27 | const emit = defineEmits(['saveSuccess']); | 30 | const emit = defineEmits(['saveSuccess']); |
| 31 | + | ||
| 32 | +const props = defineProps({ | ||
| 33 | + oldNote: { | ||
| 34 | + type: String, | ||
| 35 | + default: '' | ||
| 36 | + } | ||
| 37 | +}) | ||
| 38 | + | ||
| 39 | + | ||
| 28 | // 保存备注 | 40 | // 保存备注 |
| 29 | const saveNoteContent = async () => { | 41 | const saveNoteContent = async () => { |
| 30 | const content = tempNoteContent.value.trim(); | 42 | const content = tempNoteContent.value.trim(); |
| 31 | - // 如果内容为空,直接返回,不提交 | ||
| 32 | if (!content) { | 43 | if (!content) { |
| 33 | uni.showToast({ title: '备注不能为空', icon: 'none' }); | 44 | uni.showToast({ title: '备注不能为空', icon: 'none' }); |
| 34 | return; | 45 | return; |
| 35 | } | 46 | } |
| 36 | emit('saveSuccess', content); | 47 | emit('saveSuccess', content); |
| 37 | - // 关闭弹窗 | ||
| 38 | show.value = false; | 48 | show.value = false; |
| 39 | }; | 49 | }; |
| 40 | 50 | ||
| 51 | +// 打开弹窗 | ||
| 41 | const open = () => { | 52 | const open = () => { |
| 53 | + tempNoteContent.value = props.oldNote | ||
| 42 | show.value = true; | 54 | show.value = true; |
| 43 | }; | 55 | }; |
| 44 | 56 | ||
| 57 | +// 遮罩点击关闭 | ||
| 58 | +const maskClose = () => { | ||
| 59 | + show.value = false; | ||
| 60 | +}; | ||
| 61 | + | ||
| 45 | defineExpose({ open }); | 62 | defineExpose({ open }); |
| 46 | </script> | 63 | </script> |
| 47 | 64 | ||
| 48 | <style lang="scss" scoped> | 65 | <style lang="scss" scoped> |
| 66 | +// 全屏灰色遮罩 | ||
| 67 | +.mask { | ||
| 68 | + position: fixed; | ||
| 69 | + top: 0; | ||
| 70 | + left: 0; | ||
| 71 | + right: 0; | ||
| 72 | + bottom: 0; | ||
| 73 | + background: rgba(0, 0, 0, 0.6); | ||
| 74 | + z-index: 999; | ||
| 75 | + display: flex; | ||
| 76 | + flex-direction: column; | ||
| 77 | + justify-content: flex-end; | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +// 底部弹窗容器,控制弹出动画、圆角、背景 | ||
| 81 | +.popup-wrap { | ||
| 82 | + width: 100%; | ||
| 83 | + border-radius: 16rpx 16rpx 0 0; // 对应原来 round="16" | ||
| 84 | + overflow: hidden; | ||
| 85 | + // 弹出动画可选,不需要可删除 | ||
| 86 | + animation: popUp 0.24s ease-out; | ||
| 87 | +} | ||
| 88 | + | ||
| 89 | +@keyframes popUp { | ||
| 90 | + from { | ||
| 91 | + transform: translateY(100%); | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + to { | ||
| 95 | + transform: translateY(0); | ||
| 96 | + } | ||
| 97 | +} | ||
| 98 | + | ||
| 49 | .desc-container { | 99 | .desc-container { |
| 50 | - background-color: #1a1a1a; | 100 | + background-color: #1a1a1a; // 这里直接改弹窗背景色,非常方便 |
| 51 | padding: 40rpx 30rpx 40rpx; | 101 | padding: 40rpx 30rpx 40rpx; |
| 52 | height: 75vh; | 102 | height: 75vh; |
| 53 | 103 | ||
| 54 | .title { | 104 | .title { |
| 55 | display: flex; | 105 | display: flex; |
| 56 | justify-content: space-between; | 106 | justify-content: space-between; |
| 107 | + align-items: center; | ||
| 57 | font-weight: 500; | 108 | font-weight: 500; |
| 58 | text-align: center; | 109 | text-align: center; |
| 59 | color: #fff; | 110 | color: #fff; |
| @@ -61,6 +112,24 @@ defineExpose({ open }); | @@ -61,6 +112,24 @@ defineExpose({ open }); | ||
| 61 | margin-bottom: 40rpx; | 112 | margin-bottom: 40rpx; |
| 62 | } | 113 | } |
| 63 | 114 | ||
| 115 | + .titleName { | ||
| 116 | + flex: 1; | ||
| 117 | + text-align: center; | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + .area-box { | ||
| 121 | + .textarea { | ||
| 122 | + width: 95%; | ||
| 123 | + height: 300rpx; | ||
| 124 | + background: #444; | ||
| 125 | + color: #fff; | ||
| 126 | + border-radius: 16rpx; | ||
| 127 | + padding: 20rpx; | ||
| 128 | + font-size: 30rpx; | ||
| 129 | + border: 0; | ||
| 130 | + } | ||
| 131 | + } | ||
| 132 | + | ||
| 64 | .btn { | 133 | .btn { |
| 65 | width: 100rpx; | 134 | width: 100rpx; |
| 66 | height: 50rpx; | 135 | height: 50rpx; |
| @@ -72,15 +141,5 @@ defineExpose({ open }); | @@ -72,15 +141,5 @@ defineExpose({ open }); | ||
| 72 | justify-content: center; | 141 | justify-content: center; |
| 73 | font-size: 28rpx; | 142 | font-size: 28rpx; |
| 74 | } | 143 | } |
| 75 | - | ||
| 76 | - .input-box { | ||
| 77 | - margin-bottom: 60rpx; | ||
| 78 | - | ||
| 79 | - // 输入文字白色 | ||
| 80 | - :deep(.u-textarea__field) { | ||
| 81 | - color: #fff !important; | ||
| 82 | - } | ||
| 83 | - | ||
| 84 | - } | ||
| 85 | } | 144 | } |
| 86 | </style> | 145 | </style> |
| @@ -20,13 +20,13 @@ | @@ -20,13 +20,13 @@ | ||
| 20 | <view v-for="(item, index) in sortList" :key="item.unitId || index" class="drag-item-card"> | 20 | <view v-for="(item, index) in sortList" :key="item.unitId || index" class="drag-item-card"> |
| 21 | <view class="item-left"> | 21 | <view class="item-left"> |
| 22 | <up-icon name="trash" color="#fff" size="22" @click="deleteUnit(item, index)"></up-icon> | 22 | <up-icon name="trash" color="#fff" size="22" @click="deleteUnit(item, index)"></up-icon> |
| 23 | - <image class="item-img" :src="item.exercises?.[0]?.urlImage || ''" v-if="item.unitType === 1" | ||
| 24 | - mode="aspectFill" /> | 23 | + <image class="item-img" :src="item.exercises?.[0]?.url3dAnimation || item.exercises?.[0]?.exerciseCover || |
| 24 | + item.exercises?.[0]?.urlImage" v-if="item.unitType === 1" mode="aspectFill" /> | ||
| 25 | <image class="item-img" | 25 | <image class="item-img" |
| 26 | src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260507/超级组_1778117889451.png" v-else | 26 | src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260507/超级组_1778117889451.png" v-else |
| 27 | mode="aspectFill" /> | 27 | mode="aspectFill" /> |
| 28 | <view class="item-info"> | 28 | <view class="item-info"> |
| 29 | - <view class="item-name">{{ item.unitName || '未命名动作' }}</view> | 29 | + <view class="item-name">{{ item.unitName || item.exercises?.[0]?.exerciseName || '未命名动作' }}</view> |
| 30 | <view class="item-tags" v-if="item.unitType === 2">超级组</view> | 30 | <view class="item-tags" v-if="item.unitType === 2">超级组</view> |
| 31 | <view class="item-tags" v-if="item.unitType === 1"> | 31 | <view class="item-tags" v-if="item.unitType === 1"> |
| 32 | {{ item.exercises?.[0]?.categoryDescription || '' }} | 32 | {{ item.exercises?.[0]?.categoryDescription || '' }} |
| @@ -56,6 +56,7 @@ const trainingStore = useTrainingStore() | @@ -56,6 +56,7 @@ const trainingStore = useTrainingStore() | ||
| 56 | // --- 动作排序 ---弹窗 | 56 | // --- 动作排序 ---弹窗 |
| 57 | const actionSortShow = ref(false); | 57 | const actionSortShow = ref(false); |
| 58 | const sortList = ref([]) | 58 | const sortList = ref([]) |
| 59 | + | ||
| 59 | // 这里是打开动作排序的方法 | 60 | // 这里是打开动作排序的方法 |
| 60 | const openActionSort = () => { | 61 | const openActionSort = () => { |
| 61 | sortList.value = trainingStore.actionDetail?.units?.map((unit, index) => ({ | 62 | sortList.value = trainingStore.actionDetail?.units?.map((unit, index) => ({ |
| @@ -63,8 +64,12 @@ const openActionSort = () => { | @@ -63,8 +64,12 @@ const openActionSort = () => { | ||
| 63 | exercises: unit.exercises || [] | 64 | exercises: unit.exercises || [] |
| 64 | })) || []; | 65 | })) || []; |
| 65 | actionSortShow.value = true; | 66 | actionSortShow.value = true; |
| 67 | + | ||
| 68 | + console.log('打印排序列表:', sortList.value); | ||
| 66 | }; | 69 | }; |
| 67 | 70 | ||
| 71 | + | ||
| 72 | + | ||
| 68 | // const addActionsPopup = () => { | 73 | // const addActionsPopup = () => { |
| 69 | // if (actionSortShow.value === true) { | 74 | // if (actionSortShow.value === true) { |
| 70 | // actionSortShow.value = false | 75 | // actionSortShow.value = false |
| @@ -515,34 +515,6 @@ const toggleSuperSetActive = (setIndex) => { | @@ -515,34 +515,6 @@ const toggleSuperSetActive = (setIndex) => { | ||
| 515 | } | 515 | } |
| 516 | }) | 516 | }) |
| 517 | } | 517 | } |
| 518 | -// 增加超级组的set组数 | ||
| 519 | -const addSuperSet = () => { | ||
| 520 | - const exercises = props.actionDetail?.exercises || [] | ||
| 521 | - exercises.forEach(sub => { | ||
| 522 | - if (superRecordMap.value[sub.id]) { | ||
| 523 | - superRecordMap.value[sub.id].push({ | ||
| 524 | - h: '00', m: '00', s: '00', | ||
| 525 | - quickTimeDisplay: '60s', | ||
| 526 | - distance: '', | ||
| 527 | - weight: '', | ||
| 528 | - reps: '', | ||
| 529 | - duration: '', | ||
| 530 | - restTime: '', | ||
| 531 | - isActive: false | ||
| 532 | - }) | ||
| 533 | - } | ||
| 534 | - }) | ||
| 535 | -} | ||
| 536 | -// 删除超级组的组数 | ||
| 537 | -const deleteSuperSet = (setIndex) => { | ||
| 538 | - const exercises = props.actionDetail?.exercises || [] | ||
| 539 | - exercises.forEach(sub => { | ||
| 540 | - const list = superRecordMap.value[sub.id] | ||
| 541 | - if (list && list.length > 1) { | ||
| 542 | - list.splice(setIndex, 1) | ||
| 543 | - } | ||
| 544 | - }) | ||
| 545 | -} | ||
| 546 | 518 | ||
| 547 | // 合并后的 Picker 逻辑 | 519 | // 合并后的 Picker 逻辑 |
| 548 | const timeColumns = reactive([ | 520 | const timeColumns = reactive([ |
| @@ -652,12 +624,63 @@ const addRow = () => { | @@ -652,12 +624,63 @@ const addRow = () => { | ||
| 652 | restTime: '', isActive: false, | 624 | restTime: '', isActive: false, |
| 653 | }); | 625 | }); |
| 654 | }; | 626 | }; |
| 627 | + | ||
| 628 | +// 删除单动作行数,至少保留一行,提示。 | ||
| 655 | const deleteRow = (index) => { | 629 | const deleteRow = (index) => { |
| 656 | if (recordList.value.length > 1) { | 630 | if (recordList.value.length > 1) { |
| 657 | recordList.value.splice(index, 1); | 631 | recordList.value.splice(index, 1); |
| 632 | + } else { | ||
| 633 | + uni.showToast({ | ||
| 634 | + title: '至少保留一行数据', | ||
| 635 | + icon: 'none' | ||
| 636 | + }) | ||
| 658 | } | 637 | } |
| 659 | }; | 638 | }; |
| 660 | 639 | ||
| 640 | +// 增加超级组的set组数 | ||
| 641 | +const addSuperSet = () => { | ||
| 642 | + const exercises = props.actionDetail?.exercises || [] | ||
| 643 | + exercises.forEach(sub => { | ||
| 644 | + if (superRecordMap.value[sub.id]) { | ||
| 645 | + superRecordMap.value[sub.id].push({ | ||
| 646 | + h: '00', m: '00', s: '00', | ||
| 647 | + quickTimeDisplay: '60s', | ||
| 648 | + distance: '', | ||
| 649 | + weight: '', | ||
| 650 | + reps: '', | ||
| 651 | + duration: '', | ||
| 652 | + restTime: '', | ||
| 653 | + isActive: false | ||
| 654 | + }) | ||
| 655 | + } | ||
| 656 | + }) | ||
| 657 | +} | ||
| 658 | +// 删除超级组的组数 | ||
| 659 | +const deleteSuperSet = (setIndex) => { | ||
| 660 | + const exercises = props.actionDetail?.exercises || [] | ||
| 661 | + | ||
| 662 | + // 没有子动作直接返回 | ||
| 663 | + if (!exercises.length) return | ||
| 664 | + // 取第一个子动作的组数,所有子动作数组长度保持同步 | ||
| 665 | + const firstSubId = exercises[0].id | ||
| 666 | + const list = superRecordMap.value[firstSubId] || [] | ||
| 667 | + // 判断只剩一组,弹窗提示并终止删除 | ||
| 668 | + if (list.length <= 1) { | ||
| 669 | + uni.showToast({ | ||
| 670 | + title: '至少保留一组数据', | ||
| 671 | + icon: 'none' | ||
| 672 | + }) | ||
| 673 | + return | ||
| 674 | + } | ||
| 675 | + | ||
| 676 | + exercises.forEach(sub => { | ||
| 677 | + const list = superRecordMap.value[sub.id] | ||
| 678 | + if (list && list.length > 1) { | ||
| 679 | + list.splice(setIndex, 1) | ||
| 680 | + } | ||
| 681 | + }) | ||
| 682 | +} | ||
| 683 | + | ||
| 661 | const addGroup = () => { | 684 | const addGroup = () => { |
| 662 | if (props.type === 1) { | 685 | if (props.type === 1) { |
| 663 | addRow() | 686 | addRow() |
| @@ -52,12 +52,20 @@ | @@ -52,12 +52,20 @@ | ||
| 52 | <view class="title-bar"> | 52 | <view class="title-bar"> |
| 53 | <text class="title">{{ actionDetail?.name }}</text> | 53 | <text class="title">{{ actionDetail?.name }}</text> |
| 54 | <view class="action-icons"> | 54 | <view class="action-icons"> |
| 55 | - <!-- <up-icon name="share-square" color="#fff" size="24"></up-icon> --> | 55 | + |
| 56 | + <!-- 只有个人创建的动作,才有这个编辑按钮--> | ||
| 57 | + <template v-if="actionDetail?.isSystem === 0"> | ||
| 58 | + <view class="edit-icon" @click="edit"> | ||
| 59 | + <uni-icons type="more" size="24" color="#fff"></uni-icons> | ||
| 60 | + </view> | ||
| 61 | + </template> | ||
| 62 | + | ||
| 56 | <button class="share-btn" open-type="share"> | 63 | <button class="share-btn" open-type="share"> |
| 57 | <uni-icons type="paperplane" size="24" color="#fff"></uni-icons> | 64 | <uni-icons type="paperplane" size="24" color="#fff"></uni-icons> |
| 58 | </button> | 65 | </button> |
| 59 | <up-icon :name="isFavorite ? 'star-fill' : 'star'" :color="isFavorite ? '#fedc1f' : '#fff'" size="24" | 66 | <up-icon :name="isFavorite ? 'star-fill' : 'star'" :color="isFavorite ? '#fedc1f' : '#fff'" size="24" |
| 60 | - @click="toggleCollect"></up-icon> | 67 | + @click="toggleCollect"> |
| 68 | + </up-icon> | ||
| 61 | <!-- <FavoriteBtn :id="actionId" :type="type" /> --> | 69 | <!-- <FavoriteBtn :id="actionId" :type="type" /> --> |
| 62 | </view> | 70 | </view> |
| 63 | </view> | 71 | </view> |
| @@ -91,11 +99,35 @@ | @@ -91,11 +99,35 @@ | ||
| 91 | </view> | 99 | </view> |
| 92 | 100 | ||
| 93 | <view class="memo-box" @click="openBeizhu"> | 101 | <view class="memo-box" @click="openBeizhu"> |
| 102 | + <view class="memo-title"> | ||
| 94 | <view class="section-title">训练备注</view> | 103 | <view class="section-title">训练备注</view> |
| 95 | - <up-textarea class="textarea" v-model="actionDetail.userNote" placeholder="点击填写备注" autoHeight | ||
| 96 | - customStyle="background: transparent; border: none; padding: 10rpx 0;" placeholderStyle="color: #666" | ||
| 97 | - disabled border="none"></up-textarea> | 104 | + <view class="pencil-icon"> |
| 105 | + <up-icon name="edit-pen" color="#fedc1f" size="20"></up-icon> | ||
| 106 | + </view> | ||
| 107 | + </view> | ||
| 108 | + | ||
| 109 | + <template v-if="type == 1"> | ||
| 110 | + <textarea class="textarea" v-model="actionDetail.note" placeholder="点击填写备注"></textarea> | ||
| 111 | + </template> | ||
| 112 | + | ||
| 113 | + <template v-if="type == 2"> | ||
| 114 | + <textarea class="textarea" v-model="actionDetail.userNote" placeholder="点击填写备注"></textarea> | ||
| 115 | + </template> | ||
| 116 | + | ||
| 117 | + </view> | ||
| 118 | + <view class="memo-box" v-if="type == 1"> | ||
| 119 | + <view class="memo-title"> | ||
| 120 | + <view class="section-title">步骤</view> | ||
| 121 | + <view class="pencil-icon"> | ||
| 122 | + <up-icon name="edit-pen" color="#fedc1f" size="20"></up-icon> | ||
| 123 | + </view> | ||
| 98 | </view> | 124 | </view> |
| 125 | + <view class="steps-list"> | ||
| 126 | + <rich-text class="step-text" :nodes="actionDetail.stepDescription"></rich-text> | ||
| 127 | + </view> | ||
| 128 | + </view> | ||
| 129 | + | ||
| 130 | + | ||
| 99 | <!-- 动作列表,只有超级组才有 --> | 131 | <!-- 动作列表,只有超级组才有 --> |
| 100 | <view class="section" v-if="type === 2"> | 132 | <view class="section" v-if="type === 2"> |
| 101 | <view class="section-title">动作列表</view> | 133 | <view class="section-title">动作列表</view> |
| @@ -119,12 +151,7 @@ | @@ -119,12 +151,7 @@ | ||
| 119 | </view> | 151 | </view> |
| 120 | </view> | 152 | </view> |
| 121 | 153 | ||
| 122 | - <view class="section" v-if="type == 1"> | ||
| 123 | - <view class="section-title">步骤</view> | ||
| 124 | - <view class="steps-list"> | ||
| 125 | - <rich-text class="step-text" :nodes="actionDetail.stepDescription"></rich-text> | ||
| 126 | - </view> | ||
| 127 | - </view> | 154 | + |
| 128 | 155 | ||
| 129 | <view class="section"> | 156 | <view class="section"> |
| 130 | <view class="section-title">训练部位</view> | 157 | <view class="section-title">训练部位</view> |
| @@ -206,7 +233,7 @@ | @@ -206,7 +233,7 @@ | ||
| 206 | </view> | 233 | </view> |
| 207 | </scroll-view> | 234 | </scroll-view> |
| 208 | <!-- 备注弹窗组件 --> | 235 | <!-- 备注弹窗组件 --> |
| 209 | - <beizhu ref="showBeizhuRef" @saveSuccess="handleNoteSave" /> | 236 | + <beizhu ref="showBeizhuRef" @saveSuccess="handleNoteSave" :old-note="actionDetail.note" /> |
| 210 | </up-popup> | 237 | </up-popup> |
| 211 | </template> | 238 | </template> |
| 212 | 239 | ||
| @@ -217,6 +244,10 @@ import beizhu from '@/pages/xunji/components/beizhu.vue'; | @@ -217,6 +244,10 @@ import beizhu from '@/pages/xunji/components/beizhu.vue'; | ||
| 217 | import SupersetsApi from '@/sheep/api/motion/supersets'; | 244 | import SupersetsApi from '@/sheep/api/motion/supersets'; |
| 218 | import TrainingApi from '@/sheep/api/Training/traininghistory'; | 245 | import TrainingApi from '@/sheep/api/Training/traininghistory'; |
| 219 | import { onShareAppMessage } from '@dcloudio/uni-app'; | 246 | import { onShareAppMessage } from '@dcloudio/uni-app'; |
| 247 | +import { useTrainingStore } from '@/sheep/store/trainingStore' | ||
| 248 | + | ||
| 249 | + | ||
| 250 | +const trainingStore = useTrainingStore() | ||
| 220 | 251 | ||
| 221 | // 静态配置 | 252 | // 静态配置 |
| 222 | 253 | ||
| @@ -266,23 +297,32 @@ const checkExerciseFavorited = async () => { | @@ -266,23 +297,32 @@ const checkExerciseFavorited = async () => { | ||
| 266 | try { | 297 | try { |
| 267 | const res = await ExercisesApi.checkExerciseFavorited(actionId.value); | 298 | const res = await ExercisesApi.checkExerciseFavorited(actionId.value); |
| 268 | isFavorite.value = res.data; | 299 | isFavorite.value = res.data; |
| 300 | + console.log('获得动作的收藏状态', res.data); | ||
| 269 | } catch (err) { | 301 | } catch (err) { |
| 270 | console.log(err); | 302 | console.log(err); |
| 271 | } | 303 | } |
| 272 | }; | 304 | }; |
| 305 | + | ||
| 273 | // 收藏 | 306 | // 收藏 |
| 274 | const toggleCollect = async () => { | 307 | const toggleCollect = async () => { |
| 275 | try { | 308 | try { |
| 276 | const status = isFavorite.value ? 0 : 1; | 309 | const status = isFavorite.value ? 0 : 1; |
| 277 | - if (type == 1) { | 310 | + console.log('actionId.value=', actionId.value, 'status=', status); |
| 311 | + if (type.value == 1) { | ||
| 312 | + console.log('动作收藏传递的参数', actionId.value, status); | ||
| 278 | await ExercisesApi.toggleFavorite(actionId.value, status); | 313 | await ExercisesApi.toggleFavorite(actionId.value, status); |
| 314 | + | ||
| 279 | } else { | 315 | } else { |
| 316 | + console.log('超级组收藏传递的参数', actionId.value, status); | ||
| 280 | await SupersetsApi.toggleFavorite(actionId.value, status); | 317 | await SupersetsApi.toggleFavorite(actionId.value, status); |
| 281 | } | 318 | } |
| 282 | - | ||
| 283 | isFavorite.value = !isFavorite.value; | 319 | isFavorite.value = !isFavorite.value; |
| 320 | + uni.showToast({ title: status === 1 ? '收藏成功' : '取消收藏成功' }); | ||
| 284 | } catch (err) { | 321 | } catch (err) { |
| 285 | console.log(err); | 322 | console.log(err); |
| 323 | + // 请求失败强制回滚UI | ||
| 324 | + // isFavorite.value = originState; | ||
| 325 | + uni.showToast({ title: '操作失败,请重试', icon: 'none' }); | ||
| 286 | } | 326 | } |
| 287 | }; | 327 | }; |
| 288 | 328 | ||
| @@ -297,8 +337,21 @@ const checkSupersetFavorited = async () => { | @@ -297,8 +337,21 @@ const checkSupersetFavorited = async () => { | ||
| 297 | }; | 337 | }; |
| 298 | 338 | ||
| 299 | const startTraining = () => { | 339 | const startTraining = () => { |
| 340 | + | ||
| 341 | + if (trainingStore.isTraining) { | ||
| 342 | + uni.showToast({ | ||
| 343 | + title: '当前已有正在进行的训练', | ||
| 344 | + icon: 'none' | ||
| 345 | + }); | ||
| 346 | + return; | ||
| 347 | + } | ||
| 348 | + | ||
| 349 | + console.log('动作详情页面的trainingStore.isTraining', trainingStore.isTraining); | ||
| 350 | + trainingStore.isTraining = true; | ||
| 351 | + console.log('动作详情页面的trainingStore.isTraining', trainingStore.isTraining); | ||
| 352 | + | ||
| 300 | uni.navigateTo({ | 353 | uni.navigateTo({ |
| 301 | - url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${actionId.value}&type=${type.value}`, | 354 | + url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${actionId.value}&type=${type.value}&isTraining=true`, |
| 302 | }); | 355 | }); |
| 303 | }; | 356 | }; |
| 304 | 357 | ||
| @@ -325,9 +378,13 @@ const open = async (id, typeData) => { | @@ -325,9 +378,13 @@ const open = async (id, typeData) => { | ||
| 325 | if (typeData == 1) { | 378 | if (typeData == 1) { |
| 326 | await loadexercisedetail(actionId.value); | 379 | await loadexercisedetail(actionId.value); |
| 327 | // await loadAlternativeActions(actionId.value); | 380 | // await loadAlternativeActions(actionId.value); |
| 381 | + console.log('进入动作收藏检查接口'); | ||
| 328 | await checkExerciseFavorited(); | 382 | await checkExerciseFavorited(); |
| 383 | + | ||
| 384 | + | ||
| 329 | } else { | 385 | } else { |
| 330 | await loadsuperdetail(actionId.value); | 386 | await loadsuperdetail(actionId.value); |
| 387 | + console.log('进入超级组收藏检查接口'); | ||
| 331 | await checkSupersetFavorited(); | 388 | await checkSupersetFavorited(); |
| 332 | } | 389 | } |
| 333 | 390 | ||
| @@ -351,6 +408,10 @@ const openBeizhu = () => { | @@ -351,6 +408,10 @@ const openBeizhu = () => { | ||
| 351 | 408 | ||
| 352 | // 接收子组件传过来的备注内容 | 409 | // 接收子组件传过来的备注内容 |
| 353 | const handleNoteSave = async (content) => { | 410 | const handleNoteSave = async (content) => { |
| 411 | + | ||
| 412 | + console.log('备注内容:', content); | ||
| 413 | + console.log('type.value=', type.value); | ||
| 414 | + | ||
| 354 | try { | 415 | try { |
| 355 | if (type.value == 2) { | 416 | if (type.value == 2) { |
| 356 | await SupersetsApi.addNotes({ | 417 | await SupersetsApi.addNotes({ |
| @@ -367,7 +428,7 @@ const handleNoteSave = async (content) => { | @@ -367,7 +428,7 @@ const handleNoteSave = async (content) => { | ||
| 367 | } catch (e) { | 428 | } catch (e) { |
| 368 | console.log(e); | 429 | console.log(e); |
| 369 | } finally { | 430 | } finally { |
| 370 | - if (type == 1) { | 431 | + if (type.value == 1) { |
| 371 | await loadexercisedetail(actionId.value) | 432 | await loadexercisedetail(actionId.value) |
| 372 | } else { | 433 | } else { |
| 373 | await loadsuperdetail(actionId.value) | 434 | await loadsuperdetail(actionId.value) |
| @@ -398,6 +459,7 @@ onShareAppMessage((res) => { | @@ -398,6 +459,7 @@ onShareAppMessage((res) => { | ||
| 398 | const loadexercisedetail = async (id) => { | 459 | const loadexercisedetail = async (id) => { |
| 399 | const response = await ExercisesApi.getExerciseById(id); | 460 | const response = await ExercisesApi.getExerciseById(id); |
| 400 | actionDetail.value = response.data; | 461 | actionDetail.value = response.data; |
| 462 | + console.log('显示动作详情:', actionDetail.value); | ||
| 401 | if (actionDetail.value.url3dAnimation) { | 463 | if (actionDetail.value.url3dAnimation) { |
| 402 | modeTab.value = 0; | 464 | modeTab.value = 0; |
| 403 | } else if (actionDetail.value.urlRealPerson) { | 465 | } else if (actionDetail.value.urlRealPerson) { |
| @@ -502,6 +564,12 @@ const formatTime = (seconds) => { | @@ -502,6 +564,12 @@ const formatTime = (seconds) => { | ||
| 502 | return hh + mm + ss; | 564 | return hh + mm + ss; |
| 503 | }; | 565 | }; |
| 504 | 566 | ||
| 567 | +const edit = () => { | ||
| 568 | + uni.navigateTo({ | ||
| 569 | + url: `/pages4/pages/xunji/xunji-dongzuo-xinzeng?isEdit=true&id=${actionId.value}` | ||
| 570 | + }); | ||
| 571 | +}; | ||
| 572 | + | ||
| 505 | 573 | ||
| 506 | // 点击超级组内部的动作 → 打开动作详情(复用同一个组件) | 574 | // 点击超级组内部的动作 → 打开动作详情(复用同一个组件) |
| 507 | const openActionItem = (item) => { | 575 | const openActionItem = (item) => { |
| @@ -659,6 +727,10 @@ onMounted(() => { }); | @@ -659,6 +727,10 @@ onMounted(() => { }); | ||
| 659 | color: #ddd; | 727 | color: #ddd; |
| 660 | } | 728 | } |
| 661 | 729 | ||
| 730 | + .pencil-icon { | ||
| 731 | + margin-bottom: 24rpx; | ||
| 732 | + } | ||
| 733 | + | ||
| 662 | .action-list { | 734 | .action-list { |
| 663 | .action-item { | 735 | .action-item { |
| 664 | display: flex; | 736 | display: flex; |
| @@ -730,17 +802,23 @@ onMounted(() => { }); | @@ -730,17 +802,23 @@ onMounted(() => { }); | ||
| 730 | } | 802 | } |
| 731 | 803 | ||
| 732 | .memo-box { | 804 | .memo-box { |
| 733 | - background: #262626; | 805 | + background: #464646; |
| 734 | padding: 24rpx; | 806 | padding: 24rpx; |
| 735 | border-radius: 16rpx; | 807 | border-radius: 16rpx; |
| 736 | margin-bottom: 40rpx; | 808 | margin-bottom: 40rpx; |
| 737 | 809 | ||
| 738 | - .textarea { | ||
| 739 | - height: 50rpx; | ||
| 740 | - | ||
| 741 | - :deep(.u-textarea--disabled) { | ||
| 742 | - background-color: #262626; | 810 | + .memo-title { |
| 811 | + display: flex; | ||
| 743 | } | 812 | } |
| 813 | + | ||
| 814 | + .textarea { | ||
| 815 | + width: 95%; | ||
| 816 | + color: #fff; | ||
| 817 | + height: 160rpx; | ||
| 818 | + border-radius: 16rpx; | ||
| 819 | + // padding: 20rpx; | ||
| 820 | + font-size: 30rpx; | ||
| 821 | + border: 0; | ||
| 744 | } | 822 | } |
| 745 | } | 823 | } |
| 746 | 824 | ||
| @@ -748,13 +826,13 @@ onMounted(() => { }); | @@ -748,13 +826,13 @@ onMounted(() => { }); | ||
| 748 | display: flex; | 826 | display: flex; |
| 749 | flex-direction: column; | 827 | flex-direction: column; |
| 750 | gap: 16rpx; | 828 | gap: 16rpx; |
| 751 | - background: #262626; | ||
| 752 | - padding: 20rpx; | 829 | + background: #464646; |
| 830 | + // padding: 20rpx; | ||
| 753 | box-sizing: border-box; | 831 | box-sizing: border-box; |
| 754 | border-radius: 16rpx; | 832 | border-radius: 16rpx; |
| 755 | 833 | ||
| 756 | .step-text { | 834 | .step-text { |
| 757 | - font-size: 26rpx; | 835 | + font-size: 30rpx; |
| 758 | line-height: 1.6; | 836 | line-height: 1.6; |
| 759 | color: #bbb; | 837 | color: #bbb; |
| 760 | list-style: none; | 838 | list-style: none; |
| @@ -5,7 +5,8 @@ | @@ -5,7 +5,8 @@ | ||
| 5 | <view class="popup-options"> | 5 | <view class="popup-options"> |
| 6 | <view class="popup-option" @click="handleAddFromPlan" v-if="planListCount > 0"> | 6 | <view class="popup-option" @click="handleAddFromPlan" v-if="planListCount > 0"> |
| 7 | <view class=" icon-wrap"> | 7 | <view class=" icon-wrap"> |
| 8 | - <image src="/static/icons/plan.png" mode="aspectFit"></image> | 8 | + <!-- <image src="/static/icons/plan.png" mode="aspectFit"></image> --> |
| 9 | + <up-icon name="file-text-fill" color="#040000" size="24"></up-icon> | ||
| 9 | </view> | 10 | </view> |
| 10 | <view class="text-wrap" @click=""> | 11 | <view class="text-wrap" @click=""> |
| 11 | <text class="option-title">从训练计划中添加</text> | 12 | <text class="option-title">从训练计划中添加</text> |
| @@ -15,7 +16,7 @@ | @@ -15,7 +16,7 @@ | ||
| 15 | 16 | ||
| 16 | <view class="popup-option" @click="handleAddFromTemplate"> | 17 | <view class="popup-option" @click="handleAddFromTemplate"> |
| 17 | <view class="icon-wrap"> | 18 | <view class="icon-wrap"> |
| 18 | - <image src="/static/icons/template.png" mode="aspectFit"></image> | 19 | + <up-icon name="grid-fill" color="#040000" size="24"></up-icon> |
| 19 | </view> | 20 | </view> |
| 20 | <view class="text-wrap"> | 21 | <view class="text-wrap"> |
| 21 | <text class="option-title">使用训练模板</text> | 22 | <text class="option-title">使用训练模板</text> |
| @@ -25,7 +26,7 @@ | @@ -25,7 +26,7 @@ | ||
| 25 | 26 | ||
| 26 | <view class="popup-option" @click="handleFreeTraining"> | 27 | <view class="popup-option" @click="handleFreeTraining"> |
| 27 | <view class="icon-wrap"> | 28 | <view class="icon-wrap"> |
| 28 | - <image src="/static/icons/free.png" mode="aspectFit"></image> | 29 | + <up-icon name="clock-fill" color="#040000" size="24"></up-icon> |
| 29 | </view> | 30 | </view> |
| 30 | <view class="text-wrap"> | 31 | <view class="text-wrap"> |
| 31 | <text class="option-title">自由训练</text> | 32 | <text class="option-title">自由训练</text> |
| @@ -84,7 +85,7 @@ const handleAddFromTemplate = () => { | @@ -84,7 +85,7 @@ const handleAddFromTemplate = () => { | ||
| 84 | } | 85 | } |
| 85 | 86 | ||
| 86 | const handleFreeTraining = () => { | 87 | const handleFreeTraining = () => { |
| 87 | - uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-lianxi' }) | 88 | + uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-lianxi?isTraining=true' }) |
| 88 | close() | 89 | close() |
| 89 | } | 90 | } |
| 90 | 91 |
| @@ -832,15 +832,16 @@ const calendarColorPickerSuccess = () => { | @@ -832,15 +832,16 @@ const calendarColorPickerSuccess = () => { | ||
| 832 | 832 | ||
| 833 | // 去训练 | 833 | // 去训练 |
| 834 | const startTraining = () => { | 834 | const startTraining = () => { |
| 835 | - // console.log('++点击了去训练++'); | ||
| 836 | - // uni.navigateTo({ | ||
| 837 | - // url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${currentPlan.value.templateId}&type=3`, | ||
| 838 | - // }); | ||
| 839 | - // if (!currentPlan.value) { | ||
| 840 | - // uni.showToast({ title: '今日暂无训练', icon: 'none' }) | ||
| 841 | - // return | ||
| 842 | - // } | ||
| 843 | - // v-if="resdailyData.length > 0 | 835 | + |
| 836 | + if (trainingStore.isTraining) { | ||
| 837 | + uni.showToast({ | ||
| 838 | + title: '当前已有正在进行的训练', | ||
| 839 | + icon: 'none' | ||
| 840 | + }); | ||
| 841 | + return; | ||
| 842 | + } | ||
| 843 | + | ||
| 844 | + trainingStore.isTraining = true; | ||
| 844 | if (resdailyData.value.length > 0) { | 845 | if (resdailyData.value.length > 0) { |
| 845 | trainingStore.isSystem = currentPlan.value.isSystem || false; | 846 | trainingStore.isSystem = currentPlan.value.isSystem || false; |
| 846 | trainingStore.loadDailyTemplateForEdit(currentPlan.value); | 847 | trainingStore.loadDailyTemplateForEdit(currentPlan.value); |
| @@ -38,6 +38,9 @@ const popShow = ref(false) | @@ -38,6 +38,9 @@ const popShow = ref(false) | ||
| 38 | // dongzuo实例ref,用于收集修改后数据 | 38 | // dongzuo实例ref,用于收集修改后数据 |
| 39 | const dongzuoSingleRef = ref(null) | 39 | const dongzuoSingleRef = ref(null) |
| 40 | 40 | ||
| 41 | +// 训练快照,用于在弹窗打开/关闭时保护正在进行的训练数据 | ||
| 42 | +const trainingSnapshot = ref(null) | ||
| 43 | + | ||
| 41 | const isRenderDongzuo = ref(false) | 44 | const isRenderDongzuo = ref(false) |
| 42 | 45 | ||
| 43 | // 标记是否需要删除当前动作组 | 46 | // 标记是否需要删除当前动作组 |
| @@ -187,9 +190,34 @@ const open = async (info) => { | @@ -187,9 +190,34 @@ const open = async (info) => { | ||
| 187 | isRenderDongzuo.value = false | 190 | isRenderDongzuo.value = false |
| 188 | needDeleteUnit.value = false | 191 | needDeleteUnit.value = false |
| 189 | 192 | ||
| 193 | + console.log('trainingStore.isTraining 值:', trainingStore.isTraining) | ||
| 194 | + | ||
| 195 | + // ✅ 新增:如果有正在进行的训练,先把整个 store 状态装进口袋 | ||
| 196 | + if (trainingStore.isTraining) { | ||
| 197 | + trainingSnapshot.value = JSON.parse(JSON.stringify({ | ||
| 198 | + id: trainingStore.id, | ||
| 199 | + type: trainingStore.type, | ||
| 200 | + actionDetail: trainingStore.actionDetail, | ||
| 201 | + unitRecords: trainingStore.unitRecords, | ||
| 202 | + trainingName: trainingStore.trainingName, | ||
| 203 | + totalSeconds: trainingStore.totalSeconds, | ||
| 204 | + trainingTimeText: trainingStore.trainingTimeText, | ||
| 205 | + min: trainingStore.min, | ||
| 206 | + isSystem: trainingStore.isSystem, | ||
| 207 | + isTraining: trainingStore.isTraining, | ||
| 208 | + })) | ||
| 209 | + } | ||
| 210 | + | ||
| 211 | + console.log('弹窗打开前,快照缓存:', trainingSnapshot.value); | ||
| 212 | + | ||
| 213 | + console.log('打印全局数据trainingStore:', trainingStore); | ||
| 214 | + | ||
| 215 | + | ||
| 216 | + | ||
| 217 | + | ||
| 190 | console.log('info=', info); | 218 | console.log('info=', info); |
| 191 | - // 每次打开先清空当前store临时数据,避免上次缓存污染 | ||
| 192 | - trainingStore.clearTrainingStore() | 219 | + // 每次打开先清空当前store临时数据(保存计时功能,最小化可能在运行),避免上次缓存污染 |
| 220 | + trainingStore.resetContentOnly(); | ||
| 193 | 221 | ||
| 194 | sourceInfo.value = JSON.parse(JSON.stringify(info)) | 222 | sourceInfo.value = JSON.parse(JSON.stringify(info)) |
| 195 | // 关键:手动初始化当前unit的sets数据到Pinia.unitRecords(复用initTemplateRecords逻辑) | 223 | // 关键:手动初始化当前unit的sets数据到Pinia.unitRecords(复用initTemplateRecords逻辑) |
| @@ -237,7 +265,17 @@ const closePop = () => { | @@ -237,7 +265,17 @@ const closePop = () => { | ||
| 237 | popShow.value = false | 265 | popShow.value = false |
| 238 | isRenderDongzuo.value = false | 266 | isRenderDongzuo.value = false |
| 239 | // 关闭清空临时数据 | 267 | // 关闭清空临时数据 |
| 240 | - trainingStore.clearTrainingStore() | 268 | + trainingStore.resetContentOnly() |
| 269 | + | ||
| 270 | + | ||
| 271 | + // ✅ 新增:如果有快照,把训练数据还回去 | ||
| 272 | + if (trainingSnapshot.value) { | ||
| 273 | + Object.assign(trainingStore, trainingSnapshot.value) | ||
| 274 | + trainingSnapshot.value = null // 清空口袋 | ||
| 275 | + } | ||
| 276 | + | ||
| 277 | + console.log('关闭弹窗后,快照缓存:', trainingSnapshot.value); | ||
| 278 | + | ||
| 241 | } | 279 | } |
| 242 | 280 | ||
| 243 | // 【保存修改:核心,组装参数调用更新接口】 | 281 | // 【保存修改:核心,组装参数调用更新接口】 |
| @@ -365,7 +403,16 @@ const saveEdit = async () => { | @@ -365,7 +403,16 @@ const saveEdit = async () => { | ||
| 365 | popShow.value = false | 403 | popShow.value = false |
| 366 | // 通知父页面刷新列表 | 404 | // 通知父页面刷新列表 |
| 367 | emit('saveSuccess') | 405 | emit('saveSuccess') |
| 368 | - trainingStore.clearTrainingStore() | 406 | + |
| 407 | + trainingStore.resetContentOnly() | ||
| 408 | + | ||
| 409 | + // ✅ 新增:如果有快照,把训练数据还回去 | ||
| 410 | + if (trainingSnapshot.value) { | ||
| 411 | + Object.assign(trainingStore, trainingSnapshot.value) | ||
| 412 | + trainingSnapshot.value = null | ||
| 413 | + } | ||
| 414 | + | ||
| 415 | + | ||
| 369 | } catch (err) { | 416 | } catch (err) { |
| 370 | console.error(err) | 417 | console.error(err) |
| 371 | uni.showToast({ title: '修改失败', icon: 'none' }) | 418 | uni.showToast({ title: '修改失败', icon: 'none' }) |
| @@ -180,6 +180,10 @@ watch( | @@ -180,6 +180,10 @@ watch( | ||
| 180 | padding: 6rpx; | 180 | padding: 6rpx; |
| 181 | box-sizing: border-box; | 181 | box-sizing: border-box; |
| 182 | 182 | ||
| 183 | + // #ifdef H5 | ||
| 184 | + margin-top: 30px; | ||
| 185 | + // #endif | ||
| 186 | + | ||
| 183 | .tab-item { | 187 | .tab-item { |
| 184 | display: flex; | 188 | display: flex; |
| 185 | align-items: center; | 189 | align-items: center; |
| @@ -24,8 +24,7 @@ | @@ -24,8 +24,7 @@ | ||
| 24 | <button class="add-btn" @click.stop="addTemplateToToday(template.id)">添加</button> | 24 | <button class="add-btn" @click.stop="addTemplateToToday(template.id)">添加</button> |
| 25 | </template> | 25 | </template> |
| 26 | <template v-else> | 26 | <template v-else> |
| 27 | - <button class="add-btn" | ||
| 28 | - @click="uni.navigateTo({ url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${template.id}&type=3` })"> | 27 | + <button class="add-btn" @click.stop="goTrain(template)"> |
| 29 | 去训练 | 28 | 去训练 |
| 30 | </button> | 29 | </button> |
| 31 | </template> | 30 | </template> |
| @@ -60,6 +59,9 @@ | @@ -60,6 +59,9 @@ | ||
| 60 | import { ref, onMounted, watch } from 'vue'; | 59 | import { ref, onMounted, watch } from 'vue'; |
| 61 | import QueryPlanApi from '@/sheep/api/plan/queryplan'; | 60 | import QueryPlanApi from '@/sheep/api/plan/queryplan'; |
| 62 | import WodeJihuaLibiaoTancuang from '@/pages/xunji/components/wode-jihua-libiao-tancuang.vue' | 61 | import WodeJihuaLibiaoTancuang from '@/pages/xunji/components/wode-jihua-libiao-tancuang.vue' |
| 62 | +import { useTrainingStore } from '@/sheep/store/trainingStore' | ||
| 63 | + | ||
| 64 | +const trainingStore = useTrainingStore() | ||
| 63 | 65 | ||
| 64 | const showPlanList = ref(false); | 66 | const showPlanList = ref(false); |
| 65 | const planListRef = ref(null); | 67 | const planListRef = ref(null); |
| @@ -158,6 +160,26 @@ const addTemplateToToday = async (templateId) => { | @@ -158,6 +160,26 @@ const addTemplateToToday = async (templateId) => { | ||
| 158 | } | 160 | } |
| 159 | }; | 161 | }; |
| 160 | 162 | ||
| 163 | +// ====================== 去训练 跳转函数 ====================== | ||
| 164 | +const goTrain = (template) => { | ||
| 165 | + | ||
| 166 | + if (trainingStore.isTraining) { | ||
| 167 | + uni.showToast({ | ||
| 168 | + title: '当前已有正在进行的训练', | ||
| 169 | + icon: 'none' | ||
| 170 | + }); | ||
| 171 | + return; | ||
| 172 | + } | ||
| 173 | + // 基础校验,防止id为空跳转异常 | ||
| 174 | + if (!template?.id) { | ||
| 175 | + uni.showToast({ title: "模板ID异常", icon: "none" }) | ||
| 176 | + return | ||
| 177 | + } | ||
| 178 | + uni.navigateTo({ | ||
| 179 | + url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${template.id}&type=3&isTraining=true` | ||
| 180 | + }) | ||
| 181 | +} | ||
| 182 | + | ||
| 161 | // ====================== 【关键修复】监听父组件传的 planId ====================== | 183 | // ====================== 【关键修复】监听父组件传的 planId ====================== |
| 162 | watch(() => props.planId, (newId) => { | 184 | watch(() => props.planId, (newId) => { |
| 163 | if (newId) { | 185 | if (newId) { |
| @@ -290,6 +290,11 @@ onMounted(() => { | @@ -290,6 +290,11 @@ onMounted(() => { | ||
| 290 | padding: 20rpx; | 290 | padding: 20rpx; |
| 291 | // background-color: #f5f5f5; | 291 | // background-color: #f5f5f5; |
| 292 | border-radius: 12rpx; | 292 | border-radius: 12rpx; |
| 293 | + margin-top: 20rpx; | ||
| 294 | + | ||
| 295 | + /* #ifdef H5 */ | ||
| 296 | + margin-top: 20px; | ||
| 297 | + /* #endif */ | ||
| 293 | 298 | ||
| 294 | .search { | 299 | .search { |
| 295 | display: flex; | 300 | display: flex; |
| @@ -37,10 +37,10 @@ | @@ -37,10 +37,10 @@ | ||
| 37 | <scroll-view class="template-list" enable-flex scroll-y> | 37 | <scroll-view class="template-list" enable-flex scroll-y> |
| 38 | <!-- 用父容器包裹 v-if 分支,解决 key 冲突 --> | 38 | <!-- 用父容器包裹 v-if 分支,解决 key 冲突 --> |
| 39 | <view v-if="!isFiltering" class="sub-template-list"> | 39 | <view v-if="!isFiltering" class="sub-template-list"> |
| 40 | - <view v-for="(item, index) in templateList" :key="index" class="template-item" @click="gototemplate(item)"> | 40 | + <view v-for="(item, index) in templateList" :key="index" class="template-item"> |
| 41 | <image :src="item.urlCover" mode="aspectFill" class="template-img"></image> | 41 | <image :src="item.urlCover" mode="aspectFill" class="template-img"></image> |
| 42 | <view> | 42 | <view> |
| 43 | - <view class="template-content"> | 43 | + <view class="template-content" @click="gototemplate(item)"> |
| 44 | <view class="template-title">{{ item.name }}</view> | 44 | <view class="template-title">{{ item.name }}</view> |
| 45 | <view class="template-count">{{ item.templatesCount }}个模板</view> | 45 | <view class="template-count">{{ item.templatesCount }}个模板</view> |
| 46 | <view class="template-desc">{{ item.description }}</view> | 46 | <view class="template-desc">{{ item.description }}</view> |
| @@ -212,9 +212,9 @@ const gototemplate = (item) => { | @@ -212,9 +212,9 @@ const gototemplate = (item) => { | ||
| 212 | }); | 212 | }); |
| 213 | }; | 213 | }; |
| 214 | 214 | ||
| 215 | -onMounted(() => { | ||
| 216 | - TemplatesList(); | ||
| 217 | - getPartCategories(); | 215 | +onMounted(async () => { |
| 216 | + await TemplatesList(); | ||
| 217 | + await getPartCategories(); | ||
| 218 | console.log('进入模板大类详情'); | 218 | console.log('进入模板大类详情'); |
| 219 | 219 | ||
| 220 | }); | 220 | }); |
| @@ -222,17 +222,21 @@ onMounted(() => { | @@ -222,17 +222,21 @@ onMounted(() => { | ||
| 222 | 222 | ||
| 223 | <style lang="scss" scoped> | 223 | <style lang="scss" scoped> |
| 224 | .template-page { | 224 | .template-page { |
| 225 | - width: 100%; | 225 | + width: 100vw; |
| 226 | height: 100vh; | 226 | height: 100vh; |
| 227 | box-sizing: border-box; | 227 | box-sizing: border-box; |
| 228 | 228 | ||
| 229 | .filter-section { | 229 | .filter-section { |
| 230 | position: fixed; | 230 | position: fixed; |
| 231 | - top: 80rpx; | 231 | + // top: 80rpx; |
| 232 | // #ifdef MP-WEIXIN | 232 | // #ifdef MP-WEIXIN |
| 233 | top: 234rpx; | 233 | top: 234rpx; |
| 234 | // #endif | 234 | // #endif |
| 235 | 235 | ||
| 236 | + /* #ifdef H5 */ | ||
| 237 | + top: 77px; | ||
| 238 | + /* #endif */ | ||
| 239 | + | ||
| 236 | left: 0; | 240 | left: 0; |
| 237 | right: 0; | 241 | right: 0; |
| 238 | z-index: 999; | 242 | z-index: 999; |
| @@ -260,66 +264,6 @@ onMounted(() => { | @@ -260,66 +264,6 @@ onMounted(() => { | ||
| 260 | margin-right: 5rpx; | 264 | margin-right: 5rpx; |
| 261 | } | 265 | } |
| 262 | } | 266 | } |
| 263 | - } | ||
| 264 | - | ||
| 265 | - .template-list { | ||
| 266 | - | ||
| 267 | - margin-top: 120rpx; | ||
| 268 | - padding: 0 30rpx; | ||
| 269 | - box-sizing: border-box; | ||
| 270 | - flex: 1; | ||
| 271 | - display: flex; | ||
| 272 | - flex-direction: column; | ||
| 273 | - padding-bottom: 200rpx; | ||
| 274 | - height: calc(100vh - 274rpx); | ||
| 275 | - | ||
| 276 | - .sub-template-list { | ||
| 277 | - display: flex; | ||
| 278 | - flex-direction: column; | ||
| 279 | - gap: 10rpx; | ||
| 280 | - } | ||
| 281 | - | ||
| 282 | - .template-item { | ||
| 283 | - display: flex; | ||
| 284 | - background-color: white; | ||
| 285 | - border-radius: 16rpx; | ||
| 286 | - overflow: hidden; | ||
| 287 | - box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); | ||
| 288 | - padding: 20rpx; | ||
| 289 | - | ||
| 290 | - .template-img { | ||
| 291 | - width: 120rpx; | ||
| 292 | - height: 120rpx; | ||
| 293 | - border-radius: 12rpx; | ||
| 294 | - margin-right: 20rpx; | ||
| 295 | - } | ||
| 296 | - | ||
| 297 | - .template-content { | ||
| 298 | - flex: 1; | ||
| 299 | - } | ||
| 300 | - | ||
| 301 | - .template-title { | ||
| 302 | - font-size: 32rpx; | ||
| 303 | - color: #333; | ||
| 304 | - margin-bottom: 10rpx; | ||
| 305 | - } | ||
| 306 | - | ||
| 307 | - .template-count { | ||
| 308 | - font-size: 24rpx; | ||
| 309 | - color: #666; | ||
| 310 | - margin-bottom: 10rpx; | ||
| 311 | - } | ||
| 312 | - | ||
| 313 | - .template-desc { | ||
| 314 | - font-size: 24rpx; | ||
| 315 | - color: #666; | ||
| 316 | - line-height: 1.4; | ||
| 317 | - } | ||
| 318 | - | ||
| 319 | - &:last-child { | ||
| 320 | - margin-bottom: 45rpx; | ||
| 321 | - } | ||
| 322 | - } | ||
| 323 | 267 | ||
| 324 | /* 筛选包装器 */ | 268 | /* 筛选包装器 */ |
| 325 | .filter-wrapper { | 269 | .filter-wrapper { |
| @@ -347,7 +291,7 @@ onMounted(() => { | @@ -347,7 +291,7 @@ onMounted(() => { | ||
| 347 | align-items: center; | 291 | align-items: center; |
| 348 | justify-content: center; | 292 | justify-content: center; |
| 349 | width: auto; | 293 | width: auto; |
| 350 | - min-width: 160rpx; | 294 | + min-width: 125rpx; |
| 351 | height: 50rpx; | 295 | height: 50rpx; |
| 352 | background: #fff; | 296 | background: #fff; |
| 353 | color: #333; | 297 | color: #333; |
| @@ -435,5 +379,79 @@ onMounted(() => { | @@ -435,5 +379,79 @@ onMounted(() => { | ||
| 435 | font-weight: 500; | 379 | font-weight: 500; |
| 436 | } | 380 | } |
| 437 | } | 381 | } |
| 382 | + | ||
| 383 | + .template-list { | ||
| 384 | + | ||
| 385 | + margin-top: 120rpx; | ||
| 386 | + padding: 0 30rpx; | ||
| 387 | + box-sizing: border-box; | ||
| 388 | + flex: 1; | ||
| 389 | + display: flex; | ||
| 390 | + flex-direction: column; | ||
| 391 | + // #ifdef MP-WEIXIN | ||
| 392 | + padding-bottom: 200rpx; | ||
| 393 | + height: calc(100vh - 274rpx); | ||
| 394 | + // #endif | ||
| 395 | + | ||
| 396 | + /* #ifdef H5 */ | ||
| 397 | + margin-top: 87px; | ||
| 398 | + // 关键修复:给H5固定高度,扣除顶部筛选栏77px + 自身margin-top87px | ||
| 399 | + height: calc(100vh - 77px - 87px); | ||
| 400 | + // padding-bottom: 40px; | ||
| 401 | + /* #endif */ | ||
| 402 | + | ||
| 403 | + .sub-template-list { | ||
| 404 | + display: flex; | ||
| 405 | + flex-direction: column; | ||
| 406 | + gap: 10rpx; | ||
| 407 | + } | ||
| 408 | + | ||
| 409 | + .template-item { | ||
| 410 | + display: flex; | ||
| 411 | + background-color: white; | ||
| 412 | + border-radius: 16rpx; | ||
| 413 | + overflow: hidden; | ||
| 414 | + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); | ||
| 415 | + padding: 20rpx; | ||
| 416 | + | ||
| 417 | + &:first-child { | ||
| 418 | + margin-top: 24rpx; | ||
| 419 | + } | ||
| 420 | + | ||
| 421 | + | ||
| 422 | + .template-img { | ||
| 423 | + width: 120rpx; | ||
| 424 | + height: 120rpx; | ||
| 425 | + border-radius: 12rpx; | ||
| 426 | + margin-right: 20rpx; | ||
| 427 | + } | ||
| 428 | + | ||
| 429 | + .template-content { | ||
| 430 | + flex: 1; | ||
| 431 | + } | ||
| 432 | + | ||
| 433 | + .template-title { | ||
| 434 | + font-size: 32rpx; | ||
| 435 | + color: #333; | ||
| 436 | + margin-bottom: 10rpx; | ||
| 437 | + } | ||
| 438 | + | ||
| 439 | + .template-count { | ||
| 440 | + font-size: 24rpx; | ||
| 441 | + color: #666; | ||
| 442 | + margin-bottom: 10rpx; | ||
| 443 | + } | ||
| 444 | + | ||
| 445 | + .template-desc { | ||
| 446 | + font-size: 24rpx; | ||
| 447 | + color: #666; | ||
| 448 | + line-height: 1.4; | ||
| 449 | + } | ||
| 450 | + | ||
| 451 | + &:last-child { | ||
| 452 | + margin-bottom: 45rpx; | ||
| 453 | + } | ||
| 454 | + } | ||
| 455 | + } | ||
| 438 | } | 456 | } |
| 439 | </style> | 457 | </style> |
| @@ -794,6 +794,9 @@ onMounted(async () => { | @@ -794,6 +794,9 @@ onMounted(async () => { | ||
| 794 | 794 | ||
| 795 | .content-scroll { | 795 | .content-scroll { |
| 796 | height: calc(100vh - 400rpx); | 796 | height: calc(100vh - 400rpx); |
| 797 | + // #ifdef H5 | ||
| 798 | + height: calc(100vh - 185px); | ||
| 799 | + // #endif | ||
| 797 | width: 100%; | 800 | width: 100%; |
| 798 | box-sizing: border-box; | 801 | box-sizing: border-box; |
| 799 | overflow: auto; | 802 | overflow: auto; |
| @@ -984,8 +987,14 @@ onMounted(async () => { | @@ -984,8 +987,14 @@ onMounted(async () => { | ||
| 984 | } | 987 | } |
| 985 | 988 | ||
| 986 | .muscleShow-popup { | 989 | .muscleShow-popup { |
| 990 | + | ||
| 991 | + /* #ifdef MP-WEIXIN */ | ||
| 987 | min-height: 40vh; | 992 | min-height: 40vh; |
| 988 | - background-color: #fff; | 993 | + /* #endif */ |
| 994 | + | ||
| 995 | + /* #ifdef H5 */ | ||
| 996 | + min-height: 55vh; | ||
| 997 | + /* #endif */ | ||
| 989 | 998 | ||
| 990 | .muscle-name { | 999 | .muscle-name { |
| 991 | font-size: 32rpx; | 1000 | font-size: 32rpx; |
| @@ -11,11 +11,12 @@ const trainStore = useTrainingStore(); | @@ -11,11 +11,12 @@ const trainStore = useTrainingStore(); | ||
| 11 | 11 | ||
| 12 | // 回到训练页 | 12 | // 回到训练页 |
| 13 | const goBackTrain = () => { | 13 | const goBackTrain = () => { |
| 14 | + | ||
| 14 | const info = trainStore.trainingInfo; | 15 | const info = trainStore.trainingInfo; |
| 15 | trainStore.restore(); | 16 | trainStore.restore(); |
| 16 | 17 | ||
| 17 | uni.navigateTo({ | 18 | uni.navigateTo({ |
| 18 | - url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${info.exerciseId}&unitType=${info.unitType}`, | 19 | + url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${info.exerciseId}&unitType=${info.unitType}&isTraining=true`, |
| 19 | }); | 20 | }); |
| 20 | }; | 21 | }; |
| 21 | 22 |
| @@ -355,5 +355,10 @@ $cell-h: 110rpx; | @@ -355,5 +355,10 @@ $cell-h: 110rpx; | ||
| 355 | font-size: 30rpx; | 355 | font-size: 30rpx; |
| 356 | color: $text-white; | 356 | color: $text-white; |
| 357 | margin-top: 60rpx; | 357 | margin-top: 60rpx; |
| 358 | + | ||
| 359 | + /* #ifdef H5 */ | ||
| 360 | + margin-bottom: 20px; | ||
| 361 | + /* #endif */ | ||
| 362 | + | ||
| 358 | } | 363 | } |
| 359 | </style> | 364 | </style> |
| @@ -165,7 +165,7 @@ | @@ -165,7 +165,7 @@ | ||
| 165 | </template> | 165 | </template> |
| 166 | 166 | ||
| 167 | <script setup> | 167 | <script setup> |
| 168 | -import { ref, reactive, computed, onBeforeUnmount, onMounted, nextTick, onUnmounted } from 'vue'; | 168 | +import { ref, reactive, computed, onBeforeUnmount, onMounted, nextTick, onUnmounted, watch } from 'vue'; |
| 169 | import dongzuo from '@/pages/xunji/components/dongzuo-lianxi/dongzuo.vue'; | 169 | import dongzuo from '@/pages/xunji/components/dongzuo-lianxi/dongzuo.vue'; |
| 170 | import { onLoad } from "@dcloudio/uni-app" | 170 | import { onLoad } from "@dcloudio/uni-app" |
| 171 | import TrainingApi from '@/sheep/api/Training/traininghistory' | 171 | import TrainingApi from '@/sheep/api/Training/traininghistory' |
| @@ -194,19 +194,38 @@ const trainingName = computed(() => trainingStore.trainingName) | @@ -194,19 +194,38 @@ const trainingName = computed(() => trainingStore.trainingName) | ||
| 194 | 194 | ||
| 195 | const actionSortRef = ref(null) | 195 | const actionSortRef = ref(null) |
| 196 | 196 | ||
| 197 | +watch( | ||
| 198 | + () => trainingStore.isTraining, | ||
| 199 | + (newVal, oldVal) => { | ||
| 200 | + console.log('训练状态变化:', oldVal, '→', newVal) | ||
| 201 | + if (newVal) { | ||
| 202 | + // 正在训练逻辑 | ||
| 203 | + } else { | ||
| 204 | + // 结束训练逻辑 | ||
| 205 | + } | ||
| 206 | + }, | ||
| 207 | + { immediate: true } // 页面初始化立即执行一次 | ||
| 208 | +) | ||
| 209 | + | ||
| 197 | // 动作排序弹窗 | 210 | // 动作排序弹窗 |
| 198 | const openActionSort = () => { | 211 | const openActionSort = () => { |
| 199 | - console.log('调用前,ref 的值:', actionSortRef.value) | 212 | + console.log('调用前,ref 的值:', actionSortRef.value) |
| 200 | actionSortRef.value.openActionSort() | 213 | actionSortRef.value.openActionSort() |
| 201 | } | 214 | } |
| 202 | 215 | ||
| 203 | onLoad(async (options) => { | 216 | onLoad(async (options) => { |
| 204 | 217 | ||
| 218 | + console.log('✅ 动作练习页面 onLoad 中的 options:', options); | ||
| 219 | + | ||
| 220 | + | ||
| 205 | const id = Number(options.id); | 221 | const id = Number(options.id); |
| 206 | const type = Number(options.type); | 222 | const type = Number(options.type); |
| 207 | const isTraining = options.isTraining === 'true'; | 223 | const isTraining = options.isTraining === 'true'; |
| 208 | trainingStore.isTraining = isTraining; | 224 | trainingStore.isTraining = isTraining; |
| 209 | 225 | ||
| 226 | + console.log('✅动作练习页面 onLoad 中的 options:', options); | ||
| 227 | + | ||
| 228 | + | ||
| 210 | const dailyTemplateId = options.dailyTemplateId ? Number(options.dailyTemplateId) : null; | 229 | const dailyTemplateId = options.dailyTemplateId ? Number(options.dailyTemplateId) : null; |
| 211 | trainingStore.dailyTemplateId = dailyTemplateId; | 230 | trainingStore.dailyTemplateId = dailyTemplateId; |
| 212 | console.log('接收到的 dailyTemplateId:', dailyTemplateId); | 231 | console.log('接收到的 dailyTemplateId:', dailyTemplateId); |
| @@ -508,7 +527,7 @@ const save = async () => { | @@ -508,7 +527,7 @@ const save = async () => { | ||
| 508 | // ✅ 情况1:【去训练】→ 保存历史 | 527 | // ✅ 情况1:【去训练】→ 保存历史 |
| 509 | if (trainingStore.isTraining) { | 528 | if (trainingStore.isTraining) { |
| 510 | await TrainingApi.createTrainHistory({ units }); | 529 | await TrainingApi.createTrainHistory({ units }); |
| 511 | - uni.showToast({ title: '训练保存成功', icon: 'success' }); | 530 | + uni.showToast({ title: '训练提交成功', icon: 'success' }); |
| 512 | } | 531 | } |
| 513 | // ✅ 情况2:【编辑模板】→ 更新模板 | 532 | // ✅ 情况2:【编辑模板】→ 更新模板 |
| 514 | else if (trainingStore.type === 3 && trainingStore.dailyTemplateId) { | 533 | else if (trainingStore.type === 3 && trainingStore.dailyTemplateId) { |
| @@ -521,7 +540,7 @@ const save = async () => { | @@ -521,7 +540,7 @@ const save = async () => { | ||
| 521 | // ✅ 情况3:普通训练 | 540 | // ✅ 情况3:普通训练 |
| 522 | else { | 541 | else { |
| 523 | await TrainingApi.createTrainHistory({ units }); | 542 | await TrainingApi.createTrainHistory({ units }); |
| 524 | - uni.showToast({ title: '保存成功', icon: 'success' }); | 543 | + uni.showToast({ title: '训练提交成功', icon: 'success' }); |
| 525 | } | 544 | } |
| 526 | 545 | ||
| 527 | setTimeout(() => { | 546 | setTimeout(() => { |
| @@ -571,7 +590,10 @@ const addActionsPopup = () => { | @@ -571,7 +590,10 @@ const addActionsPopup = () => { | ||
| 571 | 590 | ||
| 572 | // 最小化 | 591 | // 最小化 |
| 573 | const openMin = () => { | 592 | const openMin = () => { |
| 593 | + console.log('最小化跳转前trainingStore.isTraining', trainingStore.isTraining); | ||
| 574 | console.log('trainingStore.min+++++++++++++', trainingStore.min); | 594 | console.log('trainingStore.min+++++++++++++', trainingStore.min); |
| 595 | + | ||
| 596 | + console.log('最小化跳转后trainingStore.isTraining', trainingStore.isTraining); | ||
| 575 | trainingStore.min = true; | 597 | trainingStore.min = true; |
| 576 | // uni.navigateTo({ | 598 | // uni.navigateTo({ |
| 577 | // url: '/pages/xunji/components/xunji-dongzuo', // 改成你训练页面的实际路径 | 599 | // url: '/pages/xunji/components/xunji-dongzuo', // 改成你训练页面的实际路径 |
| 1 | -<!-- | ||
| 2 | - 训练动作详情页(xunji-dongzuo-xiangqing.vue) | ||
| 3 | - 核心用于展示单个体能训练动作的完整信息,并提供动作管理、训练启动、收藏分享等操作,适配移动端多端(微信小程序 / APP/H5 等)。 | ||
| 4 | - 备注这个字段,没有接口,没有交互,只是前端渲染 | ||
| 5 | - --> | ||
| 6 | <template> | 1 | <template> |
| 7 | <view class="container"> | 2 | <view class="container"> |
| 8 | <!-- 页面头部编辑/分享/收藏三个按钮 --> | 3 | <!-- 页面头部编辑/分享/收藏三个按钮 --> |
| 9 | <view class="page-header"> | 4 | <view class="page-header"> |
| 10 | <text class="title">{{ exercisedetail.name }}</text> | 5 | <text class="title">{{ exercisedetail.name }}</text> |
| 11 | <view class="header-icons"> | 6 | <view class="header-icons"> |
| 12 | - <uni-icons type="more" size="24" color="#fff" @click="edit"></uni-icons> | 7 | + <!-- 编辑按钮 --> |
| 8 | + <view class="edit-icon" @click="edit"> | ||
| 9 | + <uni-icons type="more" size="24" color="#fff"></uni-icons> | ||
| 10 | + </view> | ||
| 13 | <!-- 分享按钮 --> | 11 | <!-- 分享按钮 --> |
| 14 | <button class="share-btn" open-type="share"> | 12 | <button class="share-btn" open-type="share"> |
| 15 | <uni-icons type="paperplane" size="24" color="#fff"></uni-icons> | 13 | <uni-icons type="paperplane" size="24" color="#fff"></uni-icons> |
| 16 | </button> | 14 | </button> |
| 17 | <uni-icons :type="islove ? 'heart-filled' : 'heart'" size="24" color="#fff" @click="toggleCollect"></uni-icons> | 15 | <uni-icons :type="islove ? 'heart-filled' : 'heart'" size="24" color="#fff" @click="toggleCollect"></uni-icons> |
| 16 | + | ||
| 18 | </view> | 17 | </view> |
| 19 | </view> | 18 | </view> |
| 20 | <!--标签栏(可切换的 3 个页面)要点,历史,平替动作--> | 19 | <!--标签栏(可切换的 3 个页面)要点,历史,平替动作--> |
| @@ -300,6 +299,8 @@ const toggleCollect = async () => { | @@ -300,6 +299,8 @@ const toggleCollect = async () => { | ||
| 300 | // 收藏状态:传 1,取消收藏:传 0 | 299 | // 收藏状态:传 1,取消收藏:传 0 |
| 301 | const status = islove.value ? 1 : 0; | 300 | const status = islove.value ? 1 : 0; |
| 302 | 301 | ||
| 302 | + console.log('传递给收藏接口的statu', status); | ||
| 303 | + | ||
| 303 | // 调用收藏接口 | 304 | // 调用收藏接口 |
| 304 | await ExercisesApi.toggleFavorite(id.value, status); | 305 | await ExercisesApi.toggleFavorite(id.value, status); |
| 305 | 306 | ||
| @@ -320,7 +321,7 @@ const checkCollectStatus = async () => { | @@ -320,7 +321,7 @@ const checkCollectStatus = async () => { | ||
| 320 | if (!id.value) return; | 321 | if (!id.value) return; |
| 321 | try { | 322 | try { |
| 322 | const res = await ExercisesApi.checkExerciseFavorited(id.value); | 323 | const res = await ExercisesApi.checkExerciseFavorited(id.value); |
| 323 | - // 接口返回 true = 已收藏 | 324 | + console.log('动作收藏与否:', res); |
| 324 | if (res.data === true) { | 325 | if (res.data === true) { |
| 325 | // 后端说已收藏 | 326 | // 后端说已收藏 |
| 326 | islove.value = true; // 实心爱心 | 327 | islove.value = true; // 实心爱心 |
| 1 | -<!-- | ||
| 2 | - 定义训练动作新增页面(xunji-dongzuo-xinzeng.vue),核心用于创建 / 新增自定义体能训练动作,位置在训记的动作右侧抽屉点击新增动作就是这个页面. | ||
| 3 | - 支持训练部位、动作类型、目录分类、封面 / 视频等多维度配置,最终提交保存到后端接口,适配移动端多端(小程序 / APP/H5) | ||
| 4 | - --> | ||
| 5 | <template> | 1 | <template> |
| 6 | <view class="add-custom-exercise-page"> | 2 | <view class="add-custom-exercise-page"> |
| 7 | <!-- 肌肉部位图 --> | 3 | <!-- 肌肉部位图 --> |
| @@ -41,11 +37,19 @@ | @@ -41,11 +37,19 @@ | ||
| 41 | <view class="form-section"> | 37 | <view class="form-section"> |
| 42 | <!-- 动作名称 --> | 38 | <!-- 动作名称 --> |
| 43 | <view class="form-item"> | 39 | <view class="form-item"> |
| 40 | + <!-- <view class="label">动作名称</view> | ||
| 41 | + <view class="input-group"> | ||
| 42 | + <input v-model="exerciseName" class="input-field" placeholder="点击此处填写动作名称" | ||
| 43 | + placeholder-class="input-placeholder" /> | ||
| 44 | + </view> --> | ||
| 45 | + <view class="action-name"> | ||
| 44 | <view class="label">动作名称</view> | 46 | <view class="label">动作名称</view> |
| 45 | <view class="input-group"> | 47 | <view class="input-group"> |
| 46 | <input v-model="exerciseName" class="input-field" placeholder="点击此处填写动作名称" | 48 | <input v-model="exerciseName" class="input-field" placeholder="点击此处填写动作名称" |
| 47 | placeholder-class="input-placeholder" /> | 49 | placeholder-class="input-placeholder" /> |
| 48 | </view> | 50 | </view> |
| 51 | + </view> | ||
| 52 | + | ||
| 49 | <view class="upload-btn" @click="openCoverSelector"> | 53 | <view class="upload-btn" @click="openCoverSelector"> |
| 50 | <image :src="coverImagePath" class="preview-media" mode="aspectFill" /> | 54 | <image :src="coverImagePath" class="preview-media" mode="aspectFill" /> |
| 51 | <text class="btn-text">更换封面</text> | 55 | <text class="btn-text">更换封面</text> |
| @@ -67,7 +71,7 @@ | @@ -67,7 +71,7 @@ | ||
| 67 | <text class="value">{{ actionType || '请选择' }}</text> | 71 | <text class="value">{{ actionType || '请选择' }}</text> |
| 68 | <!-- <image src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png" | 72 | <!-- <image src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png" |
| 69 | class="arrow-icon" mode="aspectFill" /> --> | 73 | class="arrow-icon" mode="aspectFill" /> --> |
| 70 | - <up-icon name="arrow-right" color="#333" size="16"></up-icon> | 74 | + <up-icon v-if="!isEdit" name="arrow-right" color="#333" size="16"></up-icon> |
| 71 | </view> | 75 | </view> |
| 72 | </view> | 76 | </view> |
| 73 | 77 | ||
| @@ -121,6 +125,10 @@ | @@ -121,6 +125,10 @@ | ||
| 121 | </view> | 125 | </view> |
| 122 | </view> | 126 | </view> |
| 123 | 127 | ||
| 128 | + <view class="form-item" v-if="isEdit" @click="deleteAction"> | ||
| 129 | + <view class="center-label">删除动作</view> | ||
| 130 | + </view> | ||
| 131 | + | ||
| 124 | <!-- 保存按钮 --> | 132 | <!-- 保存按钮 --> |
| 125 | <view class="save-button"> | 133 | <view class="save-button"> |
| 126 | <button class="btn-save" :disabled="!exerciseName.trim()" @click="saveExercise">保存</button> | 134 | <button class="btn-save" :disabled="!exerciseName.trim()" @click="saveExercise">保存</button> |
| @@ -176,6 +184,7 @@ | @@ -176,6 +184,7 @@ | ||
| 176 | 184 | ||
| 177 | <script setup> | 185 | <script setup> |
| 178 | import { onMounted, ref } from 'vue'; | 186 | import { onMounted, ref } from 'vue'; |
| 187 | +import { onLoad } from '@dcloudio/uni-app'; | ||
| 179 | import ExercisesApi from '@/sheep/api/motion/exercises'; | 188 | import ExercisesApi from '@/sheep/api/motion/exercises'; |
| 180 | import LeftmotionApi from '@/sheep/api/motion/equipments'; | 189 | import LeftmotionApi from '@/sheep/api/motion/equipments'; |
| 181 | import EquipmentListApi from '@/sheep/api/motion/motionequipments'; | 190 | import EquipmentListApi from '@/sheep/api/motion/motionequipments'; |
| @@ -185,6 +194,7 @@ const exerciseName = ref(''); | @@ -185,6 +194,7 @@ const exerciseName = ref(''); | ||
| 185 | const actionType = ref(); | 194 | const actionType = ref(); |
| 186 | const directoryName = ref(); | 195 | const directoryName = ref(); |
| 187 | const selectedMusclesDisplay = ref('未选择'); | 196 | const selectedMusclesDisplay = ref('未选择'); |
| 197 | +const editActionId = ref(null) | ||
| 188 | 198 | ||
| 189 | // 选中索引 | 199 | // 选中索引 |
| 190 | const actionTypeIndex = ref(0); | 200 | const actionTypeIndex = ref(0); |
| @@ -211,6 +221,145 @@ const actionTypeOptions = ref([ | @@ -211,6 +221,145 @@ const actionTypeOptions = ref([ | ||
| 211 | // console.error('加载部位失败', err); | 221 | // console.error('加载部位失败', err); |
| 212 | // } | 222 | // } |
| 213 | // }; | 223 | // }; |
| 224 | + | ||
| 225 | +const resetForm = () => { | ||
| 226 | + exerciseName.value = '' | ||
| 227 | + actionType.value = '' | ||
| 228 | + actionTypeIndex.value = 0 | ||
| 229 | + directoryName.value = '' | ||
| 230 | + bodyPartIndex.value = 0 | ||
| 231 | + equipmentIndex.value = 0 | ||
| 232 | + selectedMusclesDisplay.value = '未选择' | ||
| 233 | + primaryMuscles.value = [] | ||
| 234 | + secondaryMuscles.value = [] | ||
| 235 | + coverImagePath.value = 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png' | ||
| 236 | + coverUploaded.value = false | ||
| 237 | + videoFilePath.value = '' | ||
| 238 | + videoUploaded.value = false | ||
| 239 | + isVideoFile.value = false | ||
| 240 | + urlTutorial.value = '' | ||
| 241 | + videoDetailName.value = '' | ||
| 242 | + // 同时关闭所有弹窗,防止上次弹窗残留 | ||
| 243 | + closeAllPopups() | ||
| 244 | +} | ||
| 245 | + | ||
| 246 | +const isEdit = ref(false) | ||
| 247 | +const actionDetail = ref({}); | ||
| 248 | + | ||
| 249 | +onLoad(async (options) => { | ||
| 250 | + | ||
| 251 | + isEdit.value = options.isEdit === 'true' | ||
| 252 | + const editId = Number(options.id) | ||
| 253 | + editActionId.value = editId | ||
| 254 | + console.log('编辑状态', isEdit, '编辑动作ID', editId) | ||
| 255 | + // 清空表单状态 | ||
| 256 | + resetForm() | ||
| 257 | + | ||
| 258 | + await Promise.all([ | ||
| 259 | + loadbodyPartOptions(), | ||
| 260 | + loadequipmentOptions(), | ||
| 261 | + loadMuscleGroups() | ||
| 262 | + ]) | ||
| 263 | + if (isEdit.value) { | ||
| 264 | + loadexercisedetail(editId) | ||
| 265 | + } | ||
| 266 | + | ||
| 267 | +}) | ||
| 268 | + | ||
| 269 | +// 加载单个动作详情 | ||
| 270 | +// const loadexercisedetail = async (id) => { | ||
| 271 | +// if (!id || !Number(id)) return | ||
| 272 | +// try { | ||
| 273 | +// const response = await ExercisesApi.getExerciseById(id); | ||
| 274 | +// actionDetail.value = response.data; | ||
| 275 | +// console.log('打印动作详情', actionDetail.value); | ||
| 276 | +// } catch (err) { | ||
| 277 | +// console.error('加载动作详情失败:', err) | ||
| 278 | +// actionDetail.value = {} | ||
| 279 | +// } | ||
| 280 | +// }; | ||
| 281 | + | ||
| 282 | +// 加载单个动作详情并回显表单 | ||
| 283 | +const loadexercisedetail = async (id) => { | ||
| 284 | + if (!id || !Number(id)) return | ||
| 285 | + try { | ||
| 286 | + const response = await ExercisesApi.getExerciseById(id); | ||
| 287 | + actionDetail.value = response.data; | ||
| 288 | + const detail = actionDetail.value | ||
| 289 | + console.log('打印动作详情', detail); | ||
| 290 | + | ||
| 291 | + // 1. 动作名称 | ||
| 292 | + exerciseName.value = detail.name || '' | ||
| 293 | + | ||
| 294 | + // 2. 动作类型回显(id匹配,编辑只读) | ||
| 295 | + const targetType = actionTypeOptions.value.find(item => item.id === detail.exerciseType) | ||
| 296 | + if (targetType) { | ||
| 297 | + actionTypeIndex.value = actionTypeOptions.value.findIndex(item => item.id === detail.exerciseType) | ||
| 298 | + actionType.value = targetType.name | ||
| 299 | + } | ||
| 300 | + | ||
| 301 | + // 3. 目录:身体部位categoryId + 器械equipmentId 匹配下标 | ||
| 302 | + const bodyIdx = bodyPartOptions.value.findIndex(item => item.id === detail.categoryId) | ||
| 303 | + if (bodyIdx > -1) bodyPartIndex.value = bodyIdx | ||
| 304 | + | ||
| 305 | + const equipIdx = equipmentOptions.value.findIndex(item => item.id === detail.equipmentId) | ||
| 306 | + if (equipIdx > -1) equipmentIndex.value = equipIdx | ||
| 307 | + // 拼接目录展示文字 | ||
| 308 | + const selBody = bodyPartOptions.value[bodyIdx] | ||
| 309 | + const selEquip = equipmentOptions.value[equipIdx] | ||
| 310 | + if (selBody && selEquip) { | ||
| 311 | + directoryName.value = ` ${selBody.name}( ${selEquip.name})` | ||
| 312 | + } | ||
| 313 | + | ||
| 314 | + // 4. 肌肉部位:后端返回字符串 "[30,28,29]" 转数字数组 | ||
| 315 | + if (detail.primaryMuscles) { | ||
| 316 | + primaryMuscles.value = JSON.parse(detail.primaryMuscles) | ||
| 317 | + } else { | ||
| 318 | + primaryMuscles.value = [] | ||
| 319 | + } | ||
| 320 | + if (detail.secondaryMuscles) { | ||
| 321 | + secondaryMuscles.value = JSON.parse(detail.secondaryMuscles) | ||
| 322 | + } else { | ||
| 323 | + secondaryMuscles.value = [] | ||
| 324 | + } | ||
| 325 | + // 直接使用后端返回的拼接好的肌肉名称展示文本,不用重新拼接 | ||
| 326 | + const primaryStr = detail.primaryMuscleNames?.length ? detail.primaryMuscleNames.join('、') + '(主)' : '' | ||
| 327 | + const secondaryStr = detail.secondaryMuscleNames?.length ? detail.secondaryMuscleNames.join('、') + '(次)' : '' | ||
| 328 | + let display = '' | ||
| 329 | + if (primaryStr) display += primaryStr | ||
| 330 | + if (secondaryStr) { | ||
| 331 | + if (display) display += ', ' | ||
| 332 | + display += secondaryStr | ||
| 333 | + } | ||
| 334 | + selectedMusclesDisplay.value = display || '未选择' | ||
| 335 | + | ||
| 336 | + // 5. 封面图 url3dAnimation | ||
| 337 | + if (detail.url3dAnimation) { | ||
| 338 | + coverImagePath.value = detail.url3dAnimation | ||
| 339 | + coverUploaded.value = true // 标记已有封面,跳过新增封面校验 | ||
| 340 | + } | ||
| 341 | + | ||
| 342 | + // 6. 动作实拍视频/图片 urlRealPerson | ||
| 343 | + if (detail.urlRealPerson) { | ||
| 344 | + videoFilePath.value = detail.urlRealPerson | ||
| 345 | + videoUploaded.value = true | ||
| 346 | + // 简单判断是否视频(后端返回mp4则标记video) | ||
| 347 | + isVideoFile.value = detail.urlRealPerson.includes('.mp4') | ||
| 348 | + } | ||
| 349 | + | ||
| 350 | + // 7. 详解视频 urlTutorial | ||
| 351 | + if (detail.urlTutorial) { | ||
| 352 | + urlTutorial.value = detail.urlTutorial | ||
| 353 | + videoDetailName.value = '已选择详解视频' | ||
| 354 | + } | ||
| 355 | + | ||
| 356 | + } catch (err) { | ||
| 357 | + console.error('加载动作详情失败:', err) | ||
| 358 | + actionDetail.value = {} | ||
| 359 | + } | ||
| 360 | +}; | ||
| 361 | + | ||
| 362 | + | ||
| 214 | // 肌肉部位列表(从接口获取) | 363 | // 肌肉部位列表(从接口获取) |
| 215 | const muscleGroups = ref([]); | 364 | const muscleGroups = ref([]); |
| 216 | // 加载肌肉部位 | 365 | // 加载肌肉部位 |
| @@ -253,6 +402,7 @@ const showVideoSelector = ref(false); | @@ -253,6 +402,7 @@ const showVideoSelector = ref(false); | ||
| 253 | 402 | ||
| 254 | // 弹窗控制 | 403 | // 弹窗控制 |
| 255 | const openActionTypePicker = () => { | 404 | const openActionTypePicker = () => { |
| 405 | + if (isEdit.value) return; | ||
| 256 | showActionTypePicker.value = true; | 406 | showActionTypePicker.value = true; |
| 257 | }; | 407 | }; |
| 258 | const openDirectoryPicker = () => { | 408 | const openDirectoryPicker = () => { |
| @@ -394,7 +544,6 @@ const saveExercise = async () => { | @@ -394,7 +544,6 @@ const saveExercise = async () => { | ||
| 394 | return; | 544 | return; |
| 395 | } | 545 | } |
| 396 | 546 | ||
| 397 | - console.log('------------------'); | ||
| 398 | console.log('--', equipmentOptions.value[equipmentIndex.value]); | 547 | console.log('--', equipmentOptions.value[equipmentIndex.value]); |
| 399 | console.log('--', bodyPartOptions.value[bodyPartIndex.value]); | 548 | console.log('--', bodyPartOptions.value[bodyPartIndex.value]); |
| 400 | // 获取 categoryId 和 equipmentId | 549 | // 获取 categoryId 和 equipmentId |
| @@ -435,14 +584,23 @@ const saveExercise = async () => { | @@ -435,14 +584,23 @@ const saveExercise = async () => { | ||
| 435 | urlRealPerson: videoFilePath.value, //真人实拍视频 | 584 | urlRealPerson: videoFilePath.value, //真人实拍视频 |
| 436 | urlTutorial: urlTutorial.value, // 详解视频 | 585 | urlTutorial: urlTutorial.value, // 详解视频 |
| 437 | }; | 586 | }; |
| 587 | + // 编辑追加id | ||
| 588 | + if (isEdit.value) { | ||
| 589 | + payload.id = editActionId.value | ||
| 590 | + } | ||
| 438 | console.log('【发送的 payload】', payload); | 591 | console.log('【发送的 payload】', payload); |
| 439 | - | 592 | + let res; |
| 440 | try { | 593 | try { |
| 441 | - const res = await ExercisesApi.createExercise(payload); | ||
| 442 | - console.log('【接口返回】', res); | 594 | + if (isEdit.value) { |
| 595 | + res = await ExercisesApi.updateexercises(payload); | ||
| 596 | + console.log('【更新接口返回】', res); | ||
| 597 | + } else { | ||
| 598 | + res = await ExercisesApi.createExercise(payload); | ||
| 599 | + console.log('【创建接口返回】', res); | ||
| 600 | + } | ||
| 601 | + | ||
| 443 | if (res.code === 0) { | 602 | if (res.code === 0) { |
| 444 | - uni.showToast({ title: '保存成功', icon: 'success' }); | ||
| 445 | - // 可选:返回上一页或清空表单 | 603 | + uni.showToast({ title: isEdit.value ? '编辑成功' : '保存成功', icon: 'success' }); |
| 446 | setTimeout(() => { | 604 | setTimeout(() => { |
| 447 | uni.navigateBack(); | 605 | uni.navigateBack(); |
| 448 | }, 1000); | 606 | }, 1000); |
| @@ -455,6 +613,29 @@ const saveExercise = async () => { | @@ -455,6 +613,29 @@ const saveExercise = async () => { | ||
| 455 | } | 613 | } |
| 456 | }; | 614 | }; |
| 457 | 615 | ||
| 616 | +// 删除动作 | ||
| 617 | +const deleteAction = () => { | ||
| 618 | + uni.showModal({ | ||
| 619 | + title: '提示', | ||
| 620 | + content: '确定要删除该动作吗?删除后无法恢复', | ||
| 621 | + success: async (res) => { | ||
| 622 | + if (res.confirm) { | ||
| 623 | + try { | ||
| 624 | + const delRes = await ExercisesApi.deleteexercises(editActionId.value) | ||
| 625 | + if (delRes.code === 0) { | ||
| 626 | + uni.showToast({ title: '删除成功', icon: 'success' }) | ||
| 627 | + setTimeout(() => uni.navigateBack(), 1000) | ||
| 628 | + } else { | ||
| 629 | + uni.showToast({ title: delRes.msg || '删除失败', icon: 'none' }) | ||
| 630 | + } | ||
| 631 | + } catch (err) { | ||
| 632 | + uni.showToast({ title: '删除请求失败', icon: 'none' }) | ||
| 633 | + } | ||
| 634 | + } | ||
| 635 | + } | ||
| 636 | + }) | ||
| 637 | +} | ||
| 638 | + | ||
| 458 | const openMusclePicker = () => { | 639 | const openMusclePicker = () => { |
| 459 | showMusclePicker.value = true; | 640 | showMusclePicker.value = true; |
| 460 | }; | 641 | }; |
| @@ -504,9 +685,9 @@ const confirmMuscleSelection = () => { | @@ -504,9 +685,9 @@ const confirmMuscleSelection = () => { | ||
| 504 | }; | 685 | }; |
| 505 | 686 | ||
| 506 | onMounted(() => { | 687 | onMounted(() => { |
| 507 | - loadbodyPartOptions(); | ||
| 508 | - loadequipmentOptions(); | ||
| 509 | - loadMuscleGroups(); | 688 | + // loadbodyPartOptions(); |
| 689 | + // loadequipmentOptions(); | ||
| 690 | + // loadMuscleGroups(); | ||
| 510 | // loadexercisesGroups(); | 691 | // loadexercisesGroups(); |
| 511 | }); | 692 | }); |
| 512 | </script> | 693 | </script> |
| @@ -602,12 +783,21 @@ onMounted(() => { | @@ -602,12 +783,21 @@ onMounted(() => { | ||
| 602 | position: relative; | 783 | position: relative; |
| 603 | } | 784 | } |
| 604 | 785 | ||
| 786 | +.action-name { | ||
| 787 | + width: 285rpx; | ||
| 788 | +} | ||
| 789 | + | ||
| 605 | .label { | 790 | .label { |
| 606 | font-size: 32rpx; | 791 | font-size: 32rpx; |
| 607 | color: #333; | 792 | color: #333; |
| 608 | margin-bottom: 10rpx; | 793 | margin-bottom: 10rpx; |
| 609 | } | 794 | } |
| 610 | 795 | ||
| 796 | +.center-label { | ||
| 797 | + text-align: center; | ||
| 798 | + color: #c44b4b; | ||
| 799 | +} | ||
| 800 | + | ||
| 611 | .input-group { | 801 | .input-group { |
| 612 | display: flex; | 802 | display: flex; |
| 613 | align-items: center; | 803 | align-items: center; |
| @@ -372,9 +372,18 @@ const handleUnitClick = (unit) => { | @@ -372,9 +372,18 @@ const handleUnitClick = (unit) => { | ||
| 372 | // 开始训练 | 372 | // 开始训练 |
| 373 | const startTraining = () => { | 373 | const startTraining = () => { |
| 374 | 374 | ||
| 375 | + if (trainingStore.isTraining) { | ||
| 376 | + uni.showToast({ | ||
| 377 | + title: '当前已有正在进行的训练', | ||
| 378 | + icon: 'none' | ||
| 379 | + }); | ||
| 380 | + return; | ||
| 381 | + } | ||
| 382 | + | ||
| 375 | trainingStore.isSystem = 1; | 383 | trainingStore.isSystem = 1; |
| 384 | + trainingStore.isTraining = true; | ||
| 376 | uni.navigateTo({ | 385 | uni.navigateTo({ |
| 377 | - url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${TemplateDetail.value.id}&type=3`, | 386 | + url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${TemplateDetail.value.id}&type=3&isTraining=true`, |
| 378 | }); | 387 | }); |
| 379 | console.log('打印传递给动作训练页面的模板id', TemplateDetail.id); | 388 | console.log('打印传递给动作训练页面的模板id', TemplateDetail.id); |
| 380 | 389 | ||
| @@ -382,6 +391,14 @@ const startTraining = () => { | @@ -382,6 +391,14 @@ const startTraining = () => { | ||
| 382 | // startDaiTemplateTraining | 391 | // startDaiTemplateTraining |
| 383 | const startDaiTemplateTraining = () => { | 392 | const startDaiTemplateTraining = () => { |
| 384 | 393 | ||
| 394 | + if (trainingStore.isTraining) { | ||
| 395 | + uni.showToast({ | ||
| 396 | + title: '当前已有正在进行的训练', | ||
| 397 | + icon: 'none' | ||
| 398 | + }); | ||
| 399 | + return; | ||
| 400 | + } | ||
| 401 | + | ||
| 385 | trainingStore.isSystem = TemplateDetail.value.isSystem; | 402 | trainingStore.isSystem = TemplateDetail.value.isSystem; |
| 386 | trainingStore.loadDailyTemplateForEdit(TemplateDetail.value); | 403 | trainingStore.loadDailyTemplateForEdit(TemplateDetail.value); |
| 387 | trainingStore.initDailyTemplateRecords() | 404 | trainingStore.initDailyTemplateRecords() |
| @@ -80,7 +80,7 @@ | @@ -80,7 +80,7 @@ | ||
| 80 | </view> | 80 | </view> |
| 81 | <!-- ==========新增:未来7天训练日日历模块 end========== --> | 81 | <!-- ==========新增:未来7天训练日日历模块 end========== --> |
| 82 | 82 | ||
| 83 | - <!-- 计划课程 --> | 83 | + <!-- 计划课程(模板) --> |
| 84 | <view class="course-section"> | 84 | <view class="course-section"> |
| 85 | <text class="section-title">计划课程</text> | 85 | <text class="section-title">计划课程</text> |
| 86 | <view class="course-card" v-for="item in plandetail.templates" :key="item.id" | 86 | <view class="course-card" v-for="item in plandetail.templates" :key="item.id" |
| @@ -312,9 +312,18 @@ const openArrageClass = () => { | @@ -312,9 +312,18 @@ const openArrageClass = () => { | ||
| 312 | // 开始训练 | 312 | // 开始训练 |
| 313 | const startTraining = (templateId) => { | 313 | const startTraining = (templateId) => { |
| 314 | 314 | ||
| 315 | + if (trainingStore.isTraining) { | ||
| 316 | + uni.showToast({ | ||
| 317 | + title: '当前已有正在进行的训练', | ||
| 318 | + icon: 'none' | ||
| 319 | + }); | ||
| 320 | + return; | ||
| 321 | + } | ||
| 322 | + | ||
| 323 | + trainingStore.isTraining = true; | ||
| 315 | trainingStore.isSystem = 1; | 324 | trainingStore.isSystem = 1; |
| 316 | uni.navigateTo({ | 325 | uni.navigateTo({ |
| 317 | - url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${templateId}&type=3`, | 326 | + url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${templateId}&type=3&isTraining=true`, |
| 318 | }); | 327 | }); |
| 319 | console.log('打印传递给动作训练页面的模板id', templateId); | 328 | console.log('打印传递给动作训练页面的模板id', templateId); |
| 320 | 329 |
| @@ -16,6 +16,10 @@ | @@ -16,6 +16,10 @@ | ||
| 16 | <button v-else class="unbind-btn" @click="unbindWeChat">解绑</button> --> | 16 | <button v-else class="unbind-btn" @click="unbindWeChat">解绑</button> --> |
| 17 | </view> | 17 | </view> |
| 18 | 18 | ||
| 19 | + <view class="setting-item" @click.stop="goToForgetPassword"> | ||
| 20 | + <view class="setting-label">修改密码</view> | ||
| 21 | + </view> | ||
| 22 | + | ||
| 19 | <!-- 推送通知开关 --> | 23 | <!-- 推送通知开关 --> |
| 20 | <!-- <view class="setting-item notice"> | 24 | <!-- <view class="setting-item notice"> |
| 21 | <view class="content"> | 25 | <view class="content"> |
| @@ -181,6 +185,14 @@ const onRecommendChange = async (e) => { | @@ -181,6 +185,14 @@ const onRecommendChange = async (e) => { | ||
| 181 | } | 185 | } |
| 182 | }; | 186 | }; |
| 183 | 187 | ||
| 188 | +// 忘记密码跳转 | ||
| 189 | +const goToForgetPassword = () => { | ||
| 190 | + uni.navigateTo({ | ||
| 191 | + url: '/pages7/pages/index/reset-password?isModify=true', | ||
| 192 | + }); | ||
| 193 | +}; | ||
| 194 | + | ||
| 195 | + | ||
| 184 | // 退出登录 | 196 | // 退出登录 |
| 185 | const logout = () => { | 197 | const logout = () => { |
| 186 | uni.showModal({ | 198 | uni.showModal({ |
| @@ -6,32 +6,18 @@ | @@ -6,32 +6,18 @@ | ||
| 6 | <!-- 优化:为了兼容微信小程序等跨端环境,原生 SVG 转换为 Base64 嵌入标准 image 标签中,确保完美渲染 --> | 6 | <!-- 优化:为了兼容微信小程序等跨端环境,原生 SVG 转换为 Base64 嵌入标准 image 标签中,确保完美渲染 --> |
| 7 | <image class="logo-img" :src="logoSvgBase64" mode="aspectFit" /> | 7 | <image class="logo-img" :src="logoSvgBase64" mode="aspectFit" /> |
| 8 | </view> | 8 | </view> |
| 9 | - <view class="brand-title">FitFlow 悦动健身</view> | 9 | + <view class="brand-title">自己练</view> |
| 10 | <view class="brand-slogan">健康极简 · 开启你的蜕变时刻</view> | 10 | <view class="brand-slogan">健康极简 · 开启你的蜕变时刻</view> |
| 11 | </view> | 11 | </view> |
| 12 | 12 | ||
| 13 | <!-- 2. 登录方式 Tab 切换 --> | 13 | <!-- 2. 登录方式 Tab 切换 --> |
| 14 | <view class="tab-container"> | 14 | <view class="tab-container"> |
| 15 | <view class="tab-item" :class="{ active: activeTab === 'sms' }" @click="switchTab('sms')"> | 15 | <view class="tab-item" :class="{ active: activeTab === 'sms' }" @click="switchTab('sms')"> |
| 16 | - <u-icon | ||
| 17 | - name="phone" | ||
| 18 | - size="18" | ||
| 19 | - :color="activeTab === 'sms' ? '#0a1931' : '#7a8290'" | ||
| 20 | - class="tab-icon" | ||
| 21 | - /> | 16 | + <u-icon name="phone" size="18" :color="activeTab === 'sms' ? '#0a1931' : '#7a8290'" class="tab-icon" /> |
| 22 | <text>免密登录</text> | 17 | <text>免密登录</text> |
| 23 | </view> | 18 | </view> |
| 24 | - <view | ||
| 25 | - class="tab-item" | ||
| 26 | - :class="{ active: activeTab === 'password' }" | ||
| 27 | - @click="switchTab('password')" | ||
| 28 | - > | ||
| 29 | - <u-icon | ||
| 30 | - name="lock" | ||
| 31 | - size="18" | ||
| 32 | - :color="activeTab === 'password' ? '#0a1931' : '#7a8290'" | ||
| 33 | - class="tab-icon" | ||
| 34 | - /> | 19 | + <view class="tab-item" :class="{ active: activeTab === 'password' }" @click="switchTab('password')"> |
| 20 | + <u-icon name="lock" size="18" :color="activeTab === 'password' ? '#0a1931' : '#7a8290'" class="tab-icon" /> | ||
| 35 | <text>密码登录</text> | 21 | <text>密码登录</text> |
| 36 | </view> | 22 | </view> |
| 37 | </view> | 23 | </view> |
| @@ -43,14 +29,8 @@ | @@ -43,14 +29,8 @@ | ||
| 43 | <text class="form-label">手机号码</text> | 29 | <text class="form-label">手机号码</text> |
| 44 | <view class="input-box"> | 30 | <view class="input-box"> |
| 45 | <u-icon name="phone" size="20" color="#a1a8b3" class="input-icon" /> | 31 | <u-icon name="phone" size="20" color="#a1a8b3" class="input-icon" /> |
| 46 | - <input | ||
| 47 | - type="number" | ||
| 48 | - v-model="phoneNumber" | ||
| 49 | - placeholder="请输入您的手机号" | ||
| 50 | - placeholder-style="color: #a1a8b3" | ||
| 51 | - maxlength="11" | ||
| 52 | - class="native-input" | ||
| 53 | - /> | 32 | + <input type="number" v-model="phoneNumber" placeholder="请输入您的手机号" placeholder-style="color: #a1a8b3" |
| 33 | + maxlength="11" class="native-input" /> | ||
| 54 | </view> | 34 | </view> |
| 55 | </view> | 35 | </view> |
| 56 | 36 | ||
| @@ -60,21 +40,11 @@ | @@ -60,21 +40,11 @@ | ||
| 60 | <view class="code-row"> | 40 | <view class="code-row"> |
| 61 | <view class="input-box flex-1"> | 41 | <view class="input-box flex-1"> |
| 62 | <u-icon name="chat" size="20" color="#a1a8b3" class="input-icon" /> | 42 | <u-icon name="chat" size="20" color="#a1a8b3" class="input-icon" /> |
| 63 | - <input | ||
| 64 | - type="number" | ||
| 65 | - v-model="verifyCode" | ||
| 66 | - placeholder="6位验证码" | ||
| 67 | - placeholder-style="color: #a1a8b3" | ||
| 68 | - maxlength="6" | ||
| 69 | - class="native-input" | ||
| 70 | - /> | 43 | + <input type="number" v-model="verifyCode" placeholder="6位验证码" placeholder-style="color: #a1a8b3" |
| 44 | + maxlength="6" class="native-input" /> | ||
| 71 | </view> | 45 | </view> |
| 72 | <!-- 获取验证码按钮 --> | 46 | <!-- 获取验证码按钮 --> |
| 73 | - <view | ||
| 74 | - class="code-btn" | ||
| 75 | - :class="{ disabled: countdown > 0 || isSending }" | ||
| 76 | - @click="handleGetCode" | ||
| 77 | - > | 47 | + <view class="code-btn" :class="{ disabled: countdown > 0 || isSending }" @click="handleGetCode"> |
| 78 | <text>{{ countdown > 0 ? `${countdown}s 后重试` : '获取验证码' }}</text> | 48 | <text>{{ countdown > 0 ? `${countdown}s 后重试` : '获取验证码' }}</text> |
| 79 | </view> | 49 | </view> |
| 80 | </view> | 50 | </view> |
| @@ -85,14 +55,8 @@ | @@ -85,14 +55,8 @@ | ||
| 85 | <text class="form-label">登录密码</text> | 55 | <text class="form-label">登录密码</text> |
| 86 | <view class="input-box"> | 56 | <view class="input-box"> |
| 87 | <u-icon name="lock" size="20" color="#a1a8b3" class="input-icon" /> | 57 | <u-icon name="lock" size="20" color="#a1a8b3" class="input-icon" /> |
| 88 | - <input | ||
| 89 | - type="password" | ||
| 90 | - v-model="password" | ||
| 91 | - placeholder="请输入您的密码" | ||
| 92 | - placeholder-style="color: #a1a8b3" | ||
| 93 | - class="native-input" | ||
| 94 | - :password="true" | ||
| 95 | - /> | 58 | + <input type="password" v-model="password" placeholder="请输入您的密码" placeholder-style="color: #a1a8b3" |
| 59 | + class="native-input" :password="true" /> | ||
| 96 | </view> | 60 | </view> |
| 97 | </view> | 61 | </view> |
| 98 | </view> | 62 | </view> |
| @@ -121,43 +85,43 @@ | @@ -121,43 +85,43 @@ | ||
| 121 | </template> | 85 | </template> |
| 122 | 86 | ||
| 123 | <script setup> | 87 | <script setup> |
| 124 | - import { ref, onUnmounted } from 'vue'; | ||
| 125 | - import sheep from '@/sheep'; | ||
| 126 | - import AuthUtil from '@/sheep/api/member/auth'; | ||
| 127 | - import { onHide } from '@dcloudio/uni-app'; | 88 | +import { ref, onUnmounted } from 'vue'; |
| 89 | +import sheep from '@/sheep'; | ||
| 90 | +import AuthUtil from '@/sheep/api/member/auth'; | ||
| 91 | +import { onHide } from '@dcloudio/uni-app'; | ||
| 128 | 92 | ||
| 129 | - // 用于跨端兼容渲染的 Base64 编码火焰 SVG (绿渐变) | ||
| 130 | - const logoSvgBase64 = | 93 | +// 用于跨端兼容渲染的 Base64 编码火焰 SVG (绿渐变) |
| 94 | +const logoSvgBase64 = | ||
| 131 | 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="%238fc31f" /><stop offset="100%" stop-color="%2300b074" /></linearGradient></defs><path d="M12 2C12 2 6 7.5 6 13C6 16.8 9.1 20 12 20C14.9 20 18 16.8 18 13C18 7.5 12 2 12 2ZM12 16C10.3 16 9 14.7 9 13C9 10.5 12 7.5 12 7.5C12 7.5 15 10.5 15 13C15 14.7 13.7 16 12 16Z" fill="url(%23g)" /></svg>'; | 95 | 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="%238fc31f" /><stop offset="100%" stop-color="%2300b074" /></linearGradient></defs><path d="M12 2C12 2 6 7.5 6 13C6 16.8 9.1 20 12 20C14.9 20 18 16.8 18 13C18 7.5 12 2 12 2ZM12 16C10.3 16 9 14.7 9 13C9 10.5 12 7.5 12 7.5C12 7.5 15 10.5 15 13C15 14.7 13.7 16 12 16Z" fill="url(%23g)" /></svg>'; |
| 132 | 96 | ||
| 133 | - const activeTab = ref('sms'); // sms: 免密, password: 密码 | ||
| 134 | - const phoneNumber = ref(''); | ||
| 135 | - const verifyCode = ref(''); | ||
| 136 | - const password = ref(''); | ||
| 137 | - const isAgreed = ref(false); | 97 | +const activeTab = ref('sms'); // sms: 免密, password: 密码 |
| 98 | +const phoneNumber = ref(''); | ||
| 99 | +const verifyCode = ref(''); | ||
| 100 | +const password = ref(''); | ||
| 101 | +const isAgreed = ref(false); | ||
| 138 | 102 | ||
| 139 | - // 防抖及加载动画状态变量 | ||
| 140 | - const isSending = ref(false); | ||
| 141 | - const isSubmitting = ref(false); | 103 | +// 防抖及加载动画状态变量 |
| 104 | +const isSending = ref(false); | ||
| 105 | +const isSubmitting = ref(false); | ||
| 142 | 106 | ||
| 143 | - // 验证码倒计时逻辑 | ||
| 144 | - const countdown = ref(0); | ||
| 145 | - let timer = null; | 107 | +// 验证码倒计时逻辑 |
| 108 | +const countdown = ref(0); | ||
| 109 | +let timer = null; | ||
| 146 | 110 | ||
| 147 | - // 验证手机号码格式 | ||
| 148 | - const validatePhone = (phone) => { | 111 | +// 验证手机号码格式 |
| 112 | +const validatePhone = (phone) => { | ||
| 149 | return /^1[3-9]\d{9}$/.test(phone); | 113 | return /^1[3-9]\d{9}$/.test(phone); |
| 150 | - }; | 114 | +}; |
| 151 | 115 | ||
| 152 | - // 切换Tab,同时清理另一个 Tab 的输入 | ||
| 153 | - const switchTab = (type) => { | 116 | +// 切换Tab,同时清理另一个 Tab 的输入 |
| 117 | +const switchTab = (type) => { | ||
| 154 | activeTab.value = type; | 118 | activeTab.value = type; |
| 155 | verifyCode.value = ''; | 119 | verifyCode.value = ''; |
| 156 | password.value = ''; | 120 | password.value = ''; |
| 157 | - }; | 121 | +}; |
| 158 | 122 | ||
| 159 | - // 发送验证码 | ||
| 160 | - const handleGetCode = async () => { | 123 | +// 发送验证码 |
| 124 | +const handleGetCode = async () => { | ||
| 161 | if (countdown.value > 0 || isSending.value) return; | 125 | if (countdown.value > 0 || isSending.value) return; |
| 162 | 126 | ||
| 163 | // 前置逻辑校验 | 127 | // 前置逻辑校验 |
| @@ -189,10 +153,10 @@ | @@ -189,10 +153,10 @@ | ||
| 189 | } finally { | 153 | } finally { |
| 190 | isSending.value = false; | 154 | isSending.value = false; |
| 191 | } | 155 | } |
| 192 | - }; | 156 | +}; |
| 193 | 157 | ||
| 194 | - // 登录按钮提交逻辑 | ||
| 195 | - const handleLogin = async () => { | 158 | +// 登录按钮提交逻辑 |
| 159 | +const handleLogin = async () => { | ||
| 196 | if (isSubmitting.value) return; | 160 | if (isSubmitting.value) return; |
| 197 | 161 | ||
| 198 | // 1. 用户协议验证 | 162 | // 1. 用户协议验证 |
| @@ -229,10 +193,10 @@ | @@ -229,10 +193,10 @@ | ||
| 229 | } | 193 | } |
| 230 | await performLogin('password'); | 194 | await performLogin('password'); |
| 231 | } | 195 | } |
| 232 | - }; | 196 | +}; |
| 233 | 197 | ||
| 234 | - // 执行最终请求与状态写入 | ||
| 235 | - const performLogin = async (type) => { | 198 | +// 执行最终请求与状态写入 |
| 199 | +const performLogin = async (type) => { | ||
| 236 | isSubmitting.value = true; | 200 | isSubmitting.value = true; |
| 237 | uni.showLoading({ title: '正在登录...', mask: true }); | 201 | uni.showLoading({ title: '正在登录...', mask: true }); |
| 238 | 202 | ||
| @@ -286,17 +250,17 @@ | @@ -286,17 +250,17 @@ | ||
| 286 | isSubmitting.value = false; | 250 | isSubmitting.value = false; |
| 287 | uni.hideLoading(); | 251 | uni.hideLoading(); |
| 288 | } | 252 | } |
| 289 | - }; | 253 | +}; |
| 290 | 254 | ||
| 291 | - // 忘记密码跳转 | ||
| 292 | - const goToForgetPassword = () => { | 255 | +// 忘记密码跳转 |
| 256 | +const goToForgetPassword = () => { | ||
| 293 | uni.navigateTo({ | 257 | uni.navigateTo({ |
| 294 | - url: '/pages7/pages/index/reset-password', | 258 | + url: '/pages7/pages/index/reset-password?isModify=false', |
| 295 | }); | 259 | }); |
| 296 | - }; | 260 | +}; |
| 297 | 261 | ||
| 298 | - // 协议详情跳转 | ||
| 299 | - const goToAgreement = (type) => { | 262 | +// 协议详情跳转 |
| 263 | +const goToAgreement = (type) => { | ||
| 300 | const url = | 264 | const url = |
| 301 | type === 'service' | 265 | type === 'service' |
| 302 | ? '/pages/public/richtext?id=service' | 266 | ? '/pages/public/richtext?id=service' |
| @@ -310,19 +274,19 @@ | @@ -310,19 +274,19 @@ | ||
| 310 | }); | 274 | }); |
| 311 | }, | 275 | }, |
| 312 | }); | 276 | }); |
| 313 | - }; | 277 | +}; |
| 314 | 278 | ||
| 315 | - // 页面卸载生命周期:清除定时器,规避潜在的内存泄露 | ||
| 316 | - onHide(() => { | 279 | +// 页面卸载生命周期:清除定时器,规避潜在的内存泄露 |
| 280 | +onHide(() => { | ||
| 317 | if (timer) { | 281 | if (timer) { |
| 318 | clearInterval(timer); | 282 | clearInterval(timer); |
| 319 | timer = null; | 283 | timer = null; |
| 320 | } | 284 | } |
| 321 | - }); | 285 | +}); |
| 322 | </script> | 286 | </script> |
| 323 | 287 | ||
| 324 | <style lang="scss" scoped> | 288 | <style lang="scss" scoped> |
| 325 | - .login-wrapper { | 289 | +.login-wrapper { |
| 326 | height: 100vh; | 290 | height: 100vh; |
| 327 | // #ifdef H5 | 291 | // #ifdef H5 |
| 328 | height: calc(100vh - 44px); | 292 | height: calc(100vh - 44px); |
| @@ -555,5 +519,5 @@ | @@ -555,5 +519,5 @@ | ||
| 555 | margin-left: 10rpx; | 519 | margin-left: 10rpx; |
| 556 | } | 520 | } |
| 557 | } | 521 | } |
| 558 | - } | 522 | +} |
| 559 | </style> | 523 | </style> |
| @@ -7,14 +7,8 @@ | @@ -7,14 +7,8 @@ | ||
| 7 | <text class="form-label">手机号码</text> | 7 | <text class="form-label">手机号码</text> |
| 8 | <view class="input-box"> | 8 | <view class="input-box"> |
| 9 | <u-icon name="phone" size="20" color="#a1a8b3" class="input-icon" /> | 9 | <u-icon name="phone" size="20" color="#a1a8b3" class="input-icon" /> |
| 10 | - <input | ||
| 11 | - type="number" | ||
| 12 | - v-model="phone" | ||
| 13 | - placeholder="请输入您的手机号" | ||
| 14 | - placeholder-style="color: #a1a8b3" | ||
| 15 | - maxlength="11" | ||
| 16 | - class="native-input" | ||
| 17 | - /> | 10 | + <input type="number" v-model="phone" placeholder="请输入您的手机号" placeholder-style="color: #a1a8b3" maxlength="11" |
| 11 | + class="native-input" /> | ||
| 18 | </view> | 12 | </view> |
| 19 | </view> | 13 | </view> |
| 20 | 14 | ||
| @@ -24,21 +18,11 @@ | @@ -24,21 +18,11 @@ | ||
| 24 | <view class="code-row"> | 18 | <view class="code-row"> |
| 25 | <view class="input-box flex-1"> | 19 | <view class="input-box flex-1"> |
| 26 | <u-icon name="chat" size="20" color="#a1a8b3" class="input-icon" /> | 20 | <u-icon name="chat" size="20" color="#a1a8b3" class="input-icon" /> |
| 27 | - <input | ||
| 28 | - type="number" | ||
| 29 | - v-model="code" | ||
| 30 | - placeholder="6位验证码" | ||
| 31 | - placeholder-style="color: #a1a8b3" | ||
| 32 | - maxlength="6" | ||
| 33 | - class="native-input" | ||
| 34 | - /> | 21 | + <input type="number" v-model="code" placeholder="6位验证码" placeholder-style="color: #a1a8b3" maxlength="6" |
| 22 | + class="native-input" /> | ||
| 35 | </view> | 23 | </view> |
| 36 | <!-- 获取验证码按钮 --> | 24 | <!-- 获取验证码按钮 --> |
| 37 | - <view | ||
| 38 | - class="code-btn" | ||
| 39 | - :class="{ disabled: countdown > 0 || isSending }" | ||
| 40 | - @click="handleSendCode" | ||
| 41 | - > | 25 | + <view class="code-btn" :class="{ disabled: countdown > 0 || isSending }" @click="handleSendCode"> |
| 42 | <text>{{ countdown > 0 ? `${countdown}s 后重试` : '获取验证码' }}</text> | 26 | <text>{{ countdown > 0 ? `${countdown}s 后重试` : '获取验证码' }}</text> |
| 43 | </view> | 27 | </view> |
| 44 | </view> | 28 | </view> |
| @@ -49,20 +33,10 @@ | @@ -49,20 +33,10 @@ | ||
| 49 | <text class="form-label">设置新密码</text> | 33 | <text class="form-label">设置新密码</text> |
| 50 | <view class="input-box"> | 34 | <view class="input-box"> |
| 51 | <u-icon name="lock" size="20" color="#a1a8b3" class="input-icon" /> | 35 | <u-icon name="lock" size="20" color="#a1a8b3" class="input-icon" /> |
| 52 | - <input | ||
| 53 | - :type="showPassword ? 'text' : 'password'" | ||
| 54 | - v-model="password" | ||
| 55 | - placeholder="请设置您的新密码" | ||
| 56 | - placeholder-style="color: #a1a8b3" | ||
| 57 | - class="native-input" | ||
| 58 | - /> | ||
| 59 | - <u-icon | ||
| 60 | - :name="showPassword ? 'eye-fill' : 'eye'" | ||
| 61 | - size="20" | ||
| 62 | - color="#a1a8b3" | ||
| 63 | - class="eye-icon" | ||
| 64 | - @click="showPassword = !showPassword" | ||
| 65 | - /> | 36 | + <input :type="showPassword ? 'text' : 'password'" v-model="password" placeholder="请设置您的新密码" |
| 37 | + placeholder-style="color: #a1a8b3" class="native-input" /> | ||
| 38 | + <u-icon :name="showPassword ? 'eye-fill' : 'eye'" size="20" color="#a1a8b3" class="eye-icon" | ||
| 39 | + @click="showPassword = !showPassword" /> | ||
| 66 | </view> | 40 | </view> |
| 67 | <text class="input-hint">请设置8~16位包含数字、大小写字母、特殊字符组合作为密码</text> | 41 | <text class="input-hint">请设置8~16位包含数字、大小写字母、特殊字符组合作为密码</text> |
| 68 | </view> | 42 | </view> |
| @@ -72,20 +46,10 @@ | @@ -72,20 +46,10 @@ | ||
| 72 | <text class="form-label">确认密码</text> | 46 | <text class="form-label">确认密码</text> |
| 73 | <view class="input-box"> | 47 | <view class="input-box"> |
| 74 | <u-icon name="lock" size="20" color="#a1a8b3" class="input-icon" /> | 48 | <u-icon name="lock" size="20" color="#a1a8b3" class="input-icon" /> |
| 75 | - <input | ||
| 76 | - :type="showConfirmPassword ? 'text' : 'password'" | ||
| 77 | - v-model="confirmPassword" | ||
| 78 | - placeholder="请再次输入新密码" | ||
| 79 | - placeholder-style="color: #a1a8b3" | ||
| 80 | - class="native-input" | ||
| 81 | - /> | ||
| 82 | - <u-icon | ||
| 83 | - :name="showConfirmPassword ? 'eye-fill' : 'eye'" | ||
| 84 | - size="20" | ||
| 85 | - color="#a1a8b3" | ||
| 86 | - class="eye-icon" | ||
| 87 | - @click="showConfirmPassword = !showConfirmPassword" | ||
| 88 | - /> | 49 | + <input :type="showConfirmPassword ? 'text' : 'password'" v-model="confirmPassword" placeholder="请再次输入新密码" |
| 50 | + placeholder-style="color: #a1a8b3" class="native-input" /> | ||
| 51 | + <u-icon :name="showConfirmPassword ? 'eye-fill' : 'eye'" size="20" color="#a1a8b3" class="eye-icon" | ||
| 52 | + @click="showConfirmPassword = !showConfirmPassword" /> | ||
| 89 | </view> | 53 | </view> |
| 90 | </view> | 54 | </view> |
| 91 | </view> | 55 | </view> |
| @@ -98,38 +62,38 @@ | @@ -98,38 +62,38 @@ | ||
| 98 | </template> | 62 | </template> |
| 99 | 63 | ||
| 100 | <script setup> | 64 | <script setup> |
| 101 | - import { ref } from 'vue'; | ||
| 102 | - import AuthUtil from '@/sheep/api/member/auth'; | ||
| 103 | - import { onHide } from '@dcloudio/uni-app'; | ||
| 104 | - const phone = ref(''); | ||
| 105 | - const code = ref(''); | ||
| 106 | - const password = ref(''); | ||
| 107 | - const confirmPassword = ref(''); | ||
| 108 | - | ||
| 109 | - // 密码显示隐藏控制 | ||
| 110 | - const showPassword = ref(false); | ||
| 111 | - const showConfirmPassword = ref(false); | ||
| 112 | - | ||
| 113 | - // 防抖及倒计时状态 | ||
| 114 | - const isSending = ref(false); | ||
| 115 | - const isSubmitting = ref(false); | ||
| 116 | - const countdown = ref(0); | ||
| 117 | - let timer = null; | ||
| 118 | - | ||
| 119 | - // 验证手机号码格式 | ||
| 120 | - const validatePhone = (num) => { | 65 | +import { ref } from 'vue'; |
| 66 | +import AuthUtil from '@/sheep/api/member/auth'; | ||
| 67 | +import { onHide, onLoad } from '@dcloudio/uni-app'; | ||
| 68 | +const phone = ref(''); | ||
| 69 | +const code = ref(''); | ||
| 70 | +const password = ref(''); | ||
| 71 | +const confirmPassword = ref(''); | ||
| 72 | + | ||
| 73 | +// 密码显示隐藏控制 | ||
| 74 | +const showPassword = ref(false); | ||
| 75 | +const showConfirmPassword = ref(false); | ||
| 76 | + | ||
| 77 | +// 防抖及倒计时状态 | ||
| 78 | +const isSending = ref(false); | ||
| 79 | +const isSubmitting = ref(false); | ||
| 80 | +const countdown = ref(0); | ||
| 81 | +let timer = null; | ||
| 82 | + | ||
| 83 | +// 验证手机号码格式 | ||
| 84 | +const validatePhone = (num) => { | ||
| 121 | return /^1[3-9]\d{9}$/.test(num); | 85 | return /^1[3-9]\d{9}$/.test(num); |
| 122 | - }; | 86 | +}; |
| 123 | 87 | ||
| 124 | - // 强密码复杂度检测:8~16位,大小写、数字、特殊字符 | ||
| 125 | - const validatePasswordStrength = (pwd) => { | 88 | +// 强密码复杂度检测:8~16位,大小写、数字、特殊字符 |
| 89 | +const validatePasswordStrength = (pwd) => { | ||
| 126 | const reg = | 90 | const reg = |
| 127 | /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&._#^&+=*!~-])[A-Za-z\d@$!%*?&._#^&+=*!~-]{8,16}$/; | 91 | /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&._#^&+=*!~-])[A-Za-z\d@$!%*?&._#^&+=*!~-]{8,16}$/; |
| 128 | return reg.test(pwd); | 92 | return reg.test(pwd); |
| 129 | - }; | 93 | +}; |
| 130 | 94 | ||
| 131 | - // 发送短信验证码 | ||
| 132 | - const handleSendCode = async () => { | 95 | +// 发送短信验证码 |
| 96 | +const handleSendCode = async () => { | ||
| 133 | if (countdown.value > 0 || isSending.value) return; | 97 | if (countdown.value > 0 || isSending.value) return; |
| 134 | 98 | ||
| 135 | if (!phone.value) { | 99 | if (!phone.value) { |
| @@ -162,10 +126,10 @@ | @@ -162,10 +126,10 @@ | ||
| 162 | } finally { | 126 | } finally { |
| 163 | isSending.value = false; | 127 | isSending.value = false; |
| 164 | } | 128 | } |
| 165 | - }; | 129 | +}; |
| 166 | 130 | ||
| 167 | - // 保存并提交重置密码 | ||
| 168 | - const handleSubmit = async () => { | 131 | +// 保存并提交重置密码 |
| 132 | +const handleSubmit = async () => { | ||
| 169 | if (isSubmitting.value) return; | 133 | if (isSubmitting.value) return; |
| 170 | 134 | ||
| 171 | if (!phone.value || !validatePhone(phone.value)) { | 135 | if (!phone.value || !validatePhone(phone.value)) { |
| @@ -208,19 +172,38 @@ | @@ -208,19 +172,38 @@ | ||
| 208 | isSubmitting.value = false; | 172 | isSubmitting.value = false; |
| 209 | uni.hideLoading(); | 173 | uni.hideLoading(); |
| 210 | } | 174 | } |
| 211 | - }; | 175 | +}; |
| 212 | 176 | ||
| 213 | - // 生命周期管理:清除定时器 | ||
| 214 | - onHide(() => { | 177 | +// 生命周期管理:清除定时器 |
| 178 | +onHide(() => { | ||
| 215 | if (timer) { | 179 | if (timer) { |
| 216 | clearInterval(timer); | 180 | clearInterval(timer); |
| 217 | timer = null; | 181 | timer = null; |
| 218 | } | 182 | } |
| 219 | - }); | 183 | +}); |
| 184 | + | ||
| 185 | +const isForgetPassword = ref(false) | ||
| 186 | + | ||
| 187 | +const isModify = ref(false); | ||
| 188 | + | ||
| 189 | +onLoad((options) => { | ||
| 190 | + // 路由参数是字符串,需要转布尔判断 | ||
| 191 | + isModify.value = options.isModify === 'true'; | ||
| 192 | + console.log('isModify.value=', isModify.value); | ||
| 193 | + | ||
| 194 | + if (isModify.value) { | ||
| 195 | + // 修改密码场景 | ||
| 196 | + uni.setNavigationBarTitle({ title: '修改密码' }); | ||
| 197 | + } else { | ||
| 198 | + // 忘记密码场景 | ||
| 199 | + uni.setNavigationBarTitle({ title: '找回密码' }); | ||
| 200 | + } | ||
| 201 | +}); | ||
| 202 | + | ||
| 220 | </script> | 203 | </script> |
| 221 | 204 | ||
| 222 | <style lang="scss" scoped> | 205 | <style lang="scss" scoped> |
| 223 | - .login-wrapper { | 206 | +.login-wrapper { |
| 224 | height: 100vh; | 207 | height: 100vh; |
| 225 | // #ifdef H5 | 208 | // #ifdef H5 |
| 226 | height: calc(100vh - 44px); | 209 | height: calc(100vh - 44px); |
| @@ -349,5 +332,5 @@ | @@ -349,5 +332,5 @@ | ||
| 349 | margin-left: 10rpx; | 332 | margin-left: 10rpx; |
| 350 | } | 333 | } |
| 351 | } | 334 | } |
| 352 | - } | 335 | +} |
| 353 | </style> | 336 | </style> |
| @@ -94,8 +94,6 @@ const ExercisesApi = { | @@ -94,8 +94,6 @@ const ExercisesApi = { | ||
| 94 | 94 | ||
| 95 | //更新动作 | 95 | //更新动作 |
| 96 | updateexercises: (updateData) => { | 96 | updateexercises: (updateData) => { |
| 97 | - const url = '/app/motion/categories/update'; | ||
| 98 | - console.log('请求URL:', url); | ||
| 99 | return request({ | 97 | return request({ |
| 100 | url: '/app/motion/exercises/update', | 98 | url: '/app/motion/exercises/update', |
| 101 | method: 'PUT', | 99 | method: 'PUT', |
| @@ -392,6 +392,21 @@ export const useTrainingStore = defineStore('training', { | @@ -392,6 +392,21 @@ export const useTrainingStore = defineStore('training', { | ||
| 392 | setTrainingTimeText(val) { | 392 | setTrainingTimeText(val) { |
| 393 | this.trainingTimeText = val; | 393 | this.trainingTimeText = val; |
| 394 | }, | 394 | }, |
| 395 | + | ||
| 396 | + // 每日模板修改的时候使用 | ||
| 397 | + resetContentOnly() { | ||
| 398 | + this.id = null; | ||
| 399 | + this.type = null; | ||
| 400 | + this.actionDetail = {}; | ||
| 401 | + this.loading = false; | ||
| 402 | + this.unitRecords = {}; | ||
| 403 | + this.trainingName = ''; | ||
| 404 | + this.dailyTemplateId = null; | ||
| 405 | + this.isSystem = 1; | ||
| 406 | + // 下面这些字段完全不碰,计时器自然运行: | ||
| 407 | + // totalSeconds, isPause, timerInterval, trainingTimeText, | ||
| 408 | + // showPicker, defaultTimeIndex, min, isTraining | ||
| 409 | + }, | ||
| 395 | // 清空 | 410 | // 清空 |
| 396 | clearTrainingStore() { | 411 | clearTrainingStore() { |
| 397 | this.clearTimer(); | 412 | this.clearTimer(); |
| @@ -403,6 +418,10 @@ export const useTrainingStore = defineStore('training', { | @@ -403,6 +418,10 @@ export const useTrainingStore = defineStore('training', { | ||
| 403 | this.trainingTimeText = ''; | 418 | this.trainingTimeText = ''; |
| 404 | this.min = false; | 419 | this.min = false; |
| 405 | this.isSystem = 1; | 420 | this.isSystem = 1; |
| 421 | + this.trainingName = ''; | ||
| 422 | + this.defaultTimeIndex = [0, 0, 0]; | ||
| 423 | + this.showPicker = false; | ||
| 424 | + this.isTraining = false; | ||
| 406 | }, | 425 | }, |
| 407 | }, | 426 | }, |
| 408 | 427 |
-
Please register or login to post a comment