feat: 优化日历训练计划及自由训练流程
- 新增用户协议、隐私协议及使用教程页面 - 调整每日模板与历史记录的数据展示逻辑 - 支持未来日期保存为训练模板并锁定当日开练功能
Showing
17 changed files
with
1806 additions
and
1223 deletions
Too many changes to show.
To preserve performance only 17 of 17+ files are displayed.
| @@ -8,7 +8,7 @@ SHOPRO_VERSION=v2.4.1 | @@ -8,7 +8,7 @@ SHOPRO_VERSION=v2.4.1 | ||
| 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 |
| 11 | - #SHOPRO_DEV_BASE_URL=https://xunji.geaktec.com | 11 | +SHOPRO_DEV_BASE_URL=https://xunji.geaktec.com |
| 12 | # SHOPRO_DEV_BASE_URL=http://api-dashboard.yudao.iocoder.cn/ | 12 | # SHOPRO_DEV_BASE_URL=http://api-dashboard.yudao.iocoder.cn/ |
| 13 | ### SHOPRO_DEV_BASE_URL=http://10.171.1.188:48080 | 13 | ### SHOPRO_DEV_BASE_URL=http://10.171.1.188:48080 |
| 14 | ### SHOPRO_DEV_BASE_URL = http://yunai.natapp1.cc | 14 | ### SHOPRO_DEV_BASE_URL = http://yunai.natapp1.cc |
| @@ -43,3 +43,5 @@ SHOPRO_TENANT_ID=1 | @@ -43,3 +43,5 @@ SHOPRO_TENANT_ID=1 | ||
| 43 | 43 | ||
| 44 | # 默认头像 | 44 | # 默认头像 |
| 45 | DEFAULT_AVATAR=https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260526/默认头像_1779779926983.png | 45 | DEFAULT_AVATAR=https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260526/默认头像_1779779926983.png |
| 46 | +# appLogo | ||
| 47 | +APP_LOGO=https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260728/自己练logo_1785218679080.png |
| @@ -116,7 +116,7 @@ | @@ -116,7 +116,7 @@ | ||
| 116 | { | 116 | { |
| 117 | "path": "pages/xunji/wode-jihua-paike", | 117 | "path": "pages/xunji/wode-jihua-paike", |
| 118 | "style": { | 118 | "style": { |
| 119 | - "navigationBarTitleText": "我的计划详情的排课设置" | 119 | + "navigationBarTitleText": "计划排课" |
| 120 | } | 120 | } |
| 121 | }, | 121 | }, |
| 122 | { | 122 | { |
| @@ -144,7 +144,14 @@ | @@ -144,7 +144,14 @@ | ||
| 144 | "style": { | 144 | "style": { |
| 145 | "navigationBarTitleText": "" | 145 | "navigationBarTitleText": "" |
| 146 | } | 146 | } |
| 147 | - } | 147 | + }, |
| 148 | + { | ||
| 149 | + "path": "pages/xunji/shiyon-jiaochen", | ||
| 150 | + "style": { | ||
| 151 | + "navigationBarTitleText": "使用教程" | ||
| 152 | + | ||
| 153 | + } | ||
| 154 | + } | ||
| 148 | ] | 155 | ] |
| 149 | }, | 156 | }, |
| 150 | { | 157 | { |
| @@ -209,7 +216,19 @@ | @@ -209,7 +216,19 @@ | ||
| 209 | "navigationBarTitleText": "设置", | 216 | "navigationBarTitleText": "设置", |
| 210 | "navigationStyle": "default" | 217 | "navigationStyle": "default" |
| 211 | } | 218 | } |
| 212 | - } | 219 | + }, |
| 220 | + { | ||
| 221 | + "path": "pages/user/yonhu-xieyi", | ||
| 222 | + "style": { | ||
| 223 | + "navigationBarTitleText": "用户协议" | ||
| 224 | + } | ||
| 225 | + }, | ||
| 226 | + { | ||
| 227 | + "path": "pages/user/yinsi-xieyi", | ||
| 228 | + "style": { | ||
| 229 | + "navigationBarTitleText": "" | ||
| 230 | + } | ||
| 231 | + } | ||
| 213 | ] | 232 | ] |
| 214 | } | 233 | } |
| 215 | ], | 234 | ], |
| @@ -17,7 +17,7 @@ | @@ -17,7 +17,7 @@ | ||
| 17 | <!-- 未登录引导区 --> | 17 | <!-- 未登录引导区 --> |
| 18 | <view v-else class="section-card login-guide-box"> | 18 | <view v-else class="section-card login-guide-box"> |
| 19 | <view class="guide-txt"> | 19 | <view class="guide-txt"> |
| 20 | - <text class="title">欢迎加入鸿星运动</text> | 20 | + <text class="title">欢迎加入自己练运动</text> |
| 21 | <text class="desc">登录后即可享受课程预约及资产管理</text> | 21 | <text class="desc">登录后即可享受课程预约及资产管理</text> |
| 22 | </view> | 22 | </view> |
| 23 | <button class="login-btn" hover-class="btn-hover" @click="goLogin">立即登录</button> | 23 | <button class="login-btn" hover-class="btn-hover" @click="goLogin">立即登录</button> |
| @@ -52,8 +52,9 @@ | @@ -52,8 +52,9 @@ | ||
| 52 | <uni-icons type="right" size="14" color="#E0E0E0" /> | 52 | <uni-icons type="right" size="14" color="#E0E0E0" /> |
| 53 | </view> | 53 | </view> |
| 54 | 54 | ||
| 55 | - <view class="logout-wrap"> | ||
| 56 | - <button class="logout-btn" @click="handleLogout">退出登录</button> | 55 | + <view class="settings-item logout-item" @click="handleLogout"> |
| 56 | + <text class="logout-label">退出登录</text> | ||
| 57 | + <uni-icons type="right" size="14" color="#E0E0E0" /> | ||
| 57 | </view> | 58 | </view> |
| 58 | </view> | 59 | </view> |
| 59 | 60 | ||
| @@ -457,32 +458,17 @@ $transition-tap: background-color 0.2s ease, opacity 0.2s ease; | @@ -457,32 +458,17 @@ $transition-tap: background-color 0.2s ease, opacity 0.2s ease; | ||
| 457 | font-size: $font-body; | 458 | font-size: $font-body; |
| 458 | color: $color-text-primary; | 459 | color: $color-text-primary; |
| 459 | } | 460 | } |
| 460 | - } | ||
| 461 | - | ||
| 462 | - .logout-wrap { | ||
| 463 | - margin-top: 40rpx; | ||
| 464 | - | ||
| 465 | - .logout-btn { | ||
| 466 | - width: 100%; | ||
| 467 | - background-color: $color-danger; | ||
| 468 | - color: $color-text-white; | ||
| 469 | - border: none; | ||
| 470 | - border-radius: $radius-button; | ||
| 471 | - padding: 26rpx 0; | ||
| 472 | - font-size: 30rpx; | ||
| 473 | - font-weight: 500; | ||
| 474 | - transition: $transition-tap; | ||
| 475 | - | ||
| 476 | - &::after { | ||
| 477 | - border: none; | ||
| 478 | - } | ||
| 479 | 461 | ||
| 480 | - &:active { | ||
| 481 | - background-color: $color-danger-active; | ||
| 482 | - } | 462 | + .logout-label { |
| 463 | + font-size: $font-body; | ||
| 464 | + color: $color-text-tertiary; | ||
| 483 | } | 465 | } |
| 484 | } | 466 | } |
| 485 | 467 | ||
| 468 | + .logout-item { | ||
| 469 | + border-bottom: none; | ||
| 470 | + } | ||
| 471 | + | ||
| 486 | // 触摸反馈 | 472 | // 触摸反馈 |
| 487 | .card-hover { | 473 | .card-hover { |
| 488 | background-color: $color-bg-hover; | 474 | background-color: $color-bg-hover; |
| @@ -262,6 +262,13 @@ const exerciseNamesWithLabel = computed(() => { | @@ -262,6 +262,13 @@ const exerciseNamesWithLabel = computed(() => { | ||
| 262 | }) | 262 | }) |
| 263 | 263 | ||
| 264 | const exposeRecordList = computed(() => { | 264 | const exposeRecordList = computed(() => { |
| 265 | + // 每日模板编辑场景需要全量数据,不受 isActive 过滤影响 | ||
| 266 | + if (props.isDailyTemplates) { | ||
| 267 | + return recordList.value.map(item => ({ | ||
| 268 | + ...item, | ||
| 269 | + weight: getRealWeight(item) | ||
| 270 | + })) | ||
| 271 | + } | ||
| 265 | return recordList.value.filter(item => item.isActive).map(item => ({ | 272 | return recordList.value.filter(item => item.isActive).map(item => ({ |
| 266 | ...item, | 273 | ...item, |
| 267 | weight: getRealWeight(item) | 274 | weight: getRealWeight(item) |
| @@ -272,9 +279,11 @@ const exposeSuperRecordMap = computed(() => { | @@ -272,9 +279,11 @@ const exposeSuperRecordMap = computed(() => { | ||
| 272 | const map = {} | 279 | const map = {} |
| 273 | for (const key in superRecordMap.value) { | 280 | for (const key in superRecordMap.value) { |
| 274 | const subExercise = props.actionDetail?.exercises?.find(s => s.id == key) | 281 | const subExercise = props.actionDetail?.exercises?.find(s => s.id == key) |
| 275 | - map[key] = superRecordMap.value[key] | ||
| 276 | - .filter(item => item.isActive) | ||
| 277 | - .map(item => ({ ...item, weight: getRealWeight(item, subExercise?.exerciseType) })) | 282 | + // 每日模板编辑场景需要全量数据,不受 isActive 过滤影响 |
| 283 | + const list = props.isDailyTemplates | ||
| 284 | + ? superRecordMap.value[key] | ||
| 285 | + : superRecordMap.value[key]?.filter(item => item.isActive) | ||
| 286 | + map[key] = (list || []).map(item => ({ ...item, weight: getRealWeight(item, subExercise?.exerciseType) })) | ||
| 278 | } | 287 | } |
| 279 | return map | 288 | return map |
| 280 | }) | 289 | }) |
| @@ -578,10 +587,13 @@ function onQuickConfirm(e) { | @@ -578,10 +587,13 @@ function onQuickConfirm(e) { | ||
| 578 | 587 | ||
| 579 | // ===================== 增删行/组 ===================== | 588 | // ===================== 增删行/组 ===================== |
| 580 | function addRow() { | 589 | function addRow() { |
| 581 | - recordList.value.push({ | 590 | + const last = recordList.value[recordList.value.length - 1] |
| 591 | + const newItem = last ? JSON.parse(JSON.stringify(last)) : { | ||
| 582 | h: '00', m: '00', s: '00', quickTimeDisplay: formatQuickTime(60), | 592 | h: '00', m: '00', s: '00', quickTimeDisplay: formatQuickTime(60), |
| 583 | distance: '', weight: '', reps: '', duration: '', restTime: '', isActive: false | 593 | distance: '', weight: '', reps: '', duration: '', restTime: '', isActive: false |
| 584 | - }) | 594 | + } |
| 595 | + newItem.isActive = false | ||
| 596 | + recordList.value.push(newItem) | ||
| 585 | } | 597 | } |
| 586 | 598 | ||
| 587 | function deleteRow(index) { | 599 | function deleteRow(index) { |
| @@ -595,10 +607,14 @@ function deleteRow(index) { | @@ -595,10 +607,14 @@ function deleteRow(index) { | ||
| 595 | function addSuperSet() { | 607 | function addSuperSet() { |
| 596 | (props.actionDetail?.exercises || []).forEach(sub => { | 608 | (props.actionDetail?.exercises || []).forEach(sub => { |
| 597 | if (superRecordMap.value[sub.id]) { | 609 | if (superRecordMap.value[sub.id]) { |
| 598 | - superRecordMap.value[sub.id].push({ | 610 | + const list = superRecordMap.value[sub.id] |
| 611 | + const last = list[list.length - 1] | ||
| 612 | + const newItem = last ? JSON.parse(JSON.stringify(last)) : { | ||
| 599 | h: '00', m: '00', s: '00', quickTimeDisplay: formatQuickTime(60), | 613 | h: '00', m: '00', s: '00', quickTimeDisplay: formatQuickTime(60), |
| 600 | distance: '', weight: '', reps: '', duration: '', restTime: '', isActive: false | 614 | distance: '', weight: '', reps: '', duration: '', restTime: '', isActive: false |
| 601 | - }) | 615 | + } |
| 616 | + newItem.isActive = false | ||
| 617 | + list.push(newItem) | ||
| 602 | } | 618 | } |
| 603 | }) | 619 | }) |
| 604 | } | 620 | } |
| @@ -155,7 +155,7 @@ | @@ -155,7 +155,7 @@ | ||
| 155 | <view class="section-title">训练部位</view> | 155 | <view class="section-title">训练部位</view> |
| 156 | <view class="muscle-card"> | 156 | <view class="muscle-card"> |
| 157 | <image | 157 | <image |
| 158 | - src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260724/训练部位_1784878616826.jpg" | 158 | + :src="actionDetail.urlImage || 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260724/训练部位_1784878616826.jpg'" |
| 159 | mode="widthFix" class="muscle-map" /> | 159 | mode="widthFix" class="muscle-map" /> |
| 160 | <!-- <view class="legend"> | 160 | <!-- <view class="legend"> |
| 161 | <view class="legend-item"> | 161 | <view class="legend-item"> |
| @@ -172,13 +172,16 @@ | @@ -172,13 +172,16 @@ | ||
| 172 | <view class="muscle-group" v-if="actionDetail.primaryMuscleNames?.length"> | 172 | <view class="muscle-group" v-if="actionDetail.primaryMuscleNames?.length"> |
| 173 | <view class="group-label primary">主要训练部位</view> | 173 | <view class="group-label primary">主要训练部位</view> |
| 174 | <view class="pills"> | 174 | <view class="pills"> |
| 175 | - <text class="pill primary-pill" v-for="(value, index) in actionDetail.primaryMuscleNames" :key="index">{{ value }}</text> | 175 | + <text class="pill primary-pill" v-for="(value, index) in actionDetail.primaryMuscleNames" |
| 176 | + :key="index">{{ | ||
| 177 | + value }}</text> | ||
| 176 | </view> | 178 | </view> |
| 177 | </view> | 179 | </view> |
| 178 | <view class="muscle-group" v-if="actionDetail.secondaryMuscleNames?.length"> | 180 | <view class="muscle-group" v-if="actionDetail.secondaryMuscleNames?.length"> |
| 179 | <view class="group-label secondary">次要训练部位</view> | 181 | <view class="group-label secondary">次要训练部位</view> |
| 180 | <view class="pills"> | 182 | <view class="pills"> |
| 181 | - <text class="pill secondary-pill" v-for="(value, index) in actionDetail.secondaryMuscleNames" :key="index">{{ value }}</text> | 183 | + <text class="pill secondary-pill" v-for="(value, index) in actionDetail.secondaryMuscleNames" |
| 184 | + :key="index">{{ value }}</text> | ||
| 182 | </view> | 185 | </view> |
| 183 | </view> | 186 | </view> |
| 184 | </view> | 187 | </view> |
| @@ -187,7 +190,7 @@ | @@ -187,7 +190,7 @@ | ||
| 187 | <view v-if="contentTab === 1" class="tab-pane slide-up"> | 190 | <view v-if="contentTab === 1" class="tab-pane slide-up"> |
| 188 | <view class="history-list"> | 191 | <view class="history-list"> |
| 189 | <template v-if="historyList && historyList.length > 0"> | 192 | <template v-if="historyList && historyList.length > 0"> |
| 190 | - <view class="history-item" v-for="item in historyList" :key="item.id"> | 193 | + <view class="history-item" v-for="(item, index) in historyList" :key="index"> |
| 191 | <view class="item-header"> | 194 | <view class="item-header"> |
| 192 | <text class="date">{{ formatDate(item.date) }}</text> | 195 | <text class="date">{{ formatDate(item.date) }}</text> |
| 193 | <text class="tag">{{ item.name }}</text> | 196 | <text class="tag">{{ item.name }}</text> |
| @@ -198,14 +201,14 @@ | @@ -198,14 +201,14 @@ | ||
| 198 | <view class="dot">· </view> | 201 | <view class="dot">· </view> |
| 199 | <!-- <view class="difficulty">困难</view> --> | 202 | <!-- <view class="difficulty">困难</view> --> |
| 200 | <view class="difficulty">{{ | 203 | <view class="difficulty">{{ |
| 201 | - item.weight ? item.weight + 'kg' : '无负重' | 204 | + item.weight != null ? item.weight + 'kg' : '无负重' |
| 202 | }}</view> | 205 | }}</view> |
| 203 | </view> | 206 | </view> |
| 204 | <view class="group-chips"> | 207 | <view class="group-chips"> |
| 205 | <view class="chip" v-for="(set, index) in item.setConfigList" :key="index"> | 208 | <view class="chip" v-for="(set, index) in item.setConfigList" :key="index"> |
| 206 | <view class="idx">{{ index + 1 }}</view> | 209 | <view class="idx">{{ index + 1 }}</view> |
| 207 | <view class="group-data"> | 210 | <view class="group-data"> |
| 208 | - {{ formatSetData(set) }} | 211 | + {{ formatSetData(set, item.displayFields) }} |
| 209 | </view> | 212 | </view> |
| 210 | </view> | 213 | </view> |
| 211 | </view> | 214 | </view> |
| @@ -490,17 +493,32 @@ const loadTrainHistoryDetail = async (id, type) => { | @@ -490,17 +493,32 @@ const loadTrainHistoryDetail = async (id, type) => { | ||
| 490 | } | 493 | } |
| 491 | }; | 494 | }; |
| 492 | // 格式化时间 | 495 | // 格式化时间 |
| 493 | -const formatDate = (dateArr) => { | ||
| 494 | - if (!Array.isArray(dateArr) || dateArr.length < 3) return ''; | ||
| 495 | - return dayjs(new Date(dateArr[0], dateArr[1] - 1, dateArr[2])).format('YYYY/MM/DD dd'); | 496 | +const formatDate = (date) => { |
| 497 | + if (!date) return ''; | ||
| 498 | + // 支持数组格式 [2026, 7, 28] 和字符串格式 | ||
| 499 | + if (Array.isArray(date) && date.length >= 3) { | ||
| 500 | + return dayjs(new Date(date[0], date[1] - 1, date[2])).format('YYYY/MM/DD'); | ||
| 501 | + } | ||
| 502 | + return dayjs(date).format('YYYY/MM/DD'); | ||
| 496 | }; | 503 | }; |
| 497 | 504 | ||
| 498 | -const formatSetData = (set) => { | 505 | +const formatSetData = (set, displayFields) => { |
| 506 | + if (!displayFields || displayFields.length === 0) return '无数据'; | ||
| 499 | const parts = []; | 507 | const parts = []; |
| 500 | - if (set.weight != null && set.weight !== '') parts.push(`${set.weight}kg`); | ||
| 501 | - if (set.reps != null && set.reps !== '') parts.push(`${set.reps}次`); | ||
| 502 | - if (set.duration != null && set.duration !== '') parts.push(dayjs.duration(set.duration, 'seconds').format('HH:mm:ss')); | ||
| 503 | - if (set.distance != null && set.distance !== '') parts.push(`${set.distance}m`); | 508 | + const fieldMap = { |
| 509 | + weight: () => set.weight != null ? `${set.weight}kg` : null, | ||
| 510 | + reps: () => set.reps != null ? `${set.reps}次` : null, | ||
| 511 | + distance: () => set.distance != null ? `${set.distance}km` : null, | ||
| 512 | + duration: () => set.duration != null ? dayjs.duration(set.duration, 'seconds').format('HH:mm:ss') : null, | ||
| 513 | + restTime: () => set.restTime != null ? `休息${set.restTime}s` : null, | ||
| 514 | + }; | ||
| 515 | + displayFields.forEach(field => { | ||
| 516 | + const fn = fieldMap[field]; | ||
| 517 | + if (fn) { | ||
| 518 | + const result = fn(); | ||
| 519 | + if (result) parts.push(result); | ||
| 520 | + } | ||
| 521 | + }); | ||
| 504 | return parts.length > 0 ? parts.join(' × ') : '无数据'; | 522 | return parts.length > 0 ? parts.join(' × ') : '无数据'; |
| 505 | }; | 523 | }; |
| 506 | 524 |
| @@ -64,14 +64,17 @@ | @@ -64,14 +64,17 @@ | ||
| 64 | <WodeJihuaTianjiaTancuang | 64 | <WodeJihuaTianjiaTancuang |
| 65 | v-model:visible="showPlanPopup" | 65 | v-model:visible="showPlanPopup" |
| 66 | @get-plan-list-length="getPlanListLength" | 66 | @get-plan-list-length="getPlanListLength" |
| 67 | + @success="handlePlanAddSuccess" | ||
| 67 | :is-add="true" | 68 | :is-add="true" |
| 68 | :plan-id="lastPlanId" | 69 | :plan-id="lastPlanId" |
| 69 | :is-my-plan="true" | 70 | :is-my-plan="true" |
| 71 | + :selected-date="props.date" | ||
| 70 | /> | 72 | /> |
| 71 | </template> | 73 | </template> |
| 72 | 74 | ||
| 73 | <script setup> | 75 | <script setup> |
| 74 | import { ref, onMounted, computed } from 'vue' | 76 | import { ref, onMounted, computed } from 'vue' |
| 77 | +import dayjs from 'dayjs' | ||
| 75 | import WodeJihuaTianjiaTancuang from '@/pages/xunji/components/wode-jihua-tianjia-tancuang.vue' | 78 | import WodeJihuaTianjiaTancuang from '@/pages/xunji/components/wode-jihua-tianjia-tancuang.vue' |
| 76 | import QueryPlanApi from '@/sheep/api/plan/queryplan' | 79 | import QueryPlanApi from '@/sheep/api/plan/queryplan' |
| 77 | 80 | ||
| @@ -81,9 +84,13 @@ const props = defineProps({ | @@ -81,9 +84,13 @@ const props = defineProps({ | ||
| 81 | type: Boolean, | 84 | type: Boolean, |
| 82 | default: false, | 85 | default: false, |
| 83 | }, | 86 | }, |
| 87 | + date: { | ||
| 88 | + type: String, | ||
| 89 | + default: '', | ||
| 90 | + }, | ||
| 84 | }) | 91 | }) |
| 85 | 92 | ||
| 86 | -const emit = defineEmits(['update:visible']) | 93 | +const emit = defineEmits(['update:visible', 'successAddTrain']) |
| 87 | 94 | ||
| 88 | // ==================== 响应式状态 ==================== | 95 | // ==================== 响应式状态 ==================== |
| 89 | const showPlanPopup = ref(false) | 96 | const showPlanPopup = ref(false) |
| @@ -113,15 +120,29 @@ const handleAddFromPlan = () => { | @@ -113,15 +120,29 @@ const handleAddFromPlan = () => { | ||
| 113 | showPlanPopup.value = true | 120 | showPlanPopup.value = true |
| 114 | } | 121 | } |
| 115 | 122 | ||
| 123 | +/** 从训练计划添加成功后,通知父组件刷新 */ | ||
| 124 | +const handlePlanAddSuccess = () => { | ||
| 125 | + emit('successAddTrain') | ||
| 126 | +} | ||
| 127 | + | ||
| 116 | /** 使用训练模板:跳转模板选择页 */ | 128 | /** 使用训练模板:跳转模板选择页 */ |
| 117 | const handleAddFromTemplate = () => { | 129 | const handleAddFromTemplate = () => { |
| 118 | - uni.navigateTo({ url: '/pages4/pages/xunji/xunji-rili-tianjia-moban' }) | 130 | + uni.navigateTo({ url: `/pages4/pages/xunji/xunji-rili-tianjia-moban?date=${props.date}` }) |
| 119 | close() | 131 | close() |
| 120 | } | 132 | } |
| 121 | 133 | ||
| 122 | -/** 自由训练:跳转动作练习页 */ | 134 | +/** 自由训练:非未来日期直接训练,未来日期保存为训练模板 */ |
| 123 | const handleFreeTraining = () => { | 135 | const handleFreeTraining = () => { |
| 124 | - uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-lianxi?isTraining=true' }) | 136 | + const today = dayjs().format('YYYY-MM-DD') |
| 137 | + if (props.date && props.date > today) { | ||
| 138 | + // 未来日期:跳转训练页构建动作,完成后保存为当日模板 | ||
| 139 | + uni.navigateTo({ | ||
| 140 | + url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?isFutureTemplate=true&targetDate=${props.date}`, | ||
| 141 | + }) | ||
| 142 | + } else { | ||
| 143 | + // 非未来日期:直接开始训练,携带目标日期确保训练记录日期正确 | ||
| 144 | + uni.navigateTo({ url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?isTraining=true&targetDate=${props.date}` }) | ||
| 145 | + } | ||
| 125 | close() | 146 | close() |
| 126 | } | 147 | } |
| 127 | 148 |
| @@ -27,21 +27,17 @@ | @@ -27,21 +27,17 @@ | ||
| 27 | </view> | 27 | </view> |
| 28 | 28 | ||
| 29 | <template v-if="resdailyData.length > 0"> | 29 | <template v-if="resdailyData.length > 0"> |
| 30 | - <!-- Tab 按钮:如���有多个训记,用来源名作为标签 --> | 30 | + <!-- Tab 按钮:如果有多个训记,用来源名作为标签 --> |
| 31 | <scroll-view class="plan-tabs" scroll-x v-if="resdailyData.length > 1"> | 31 | <scroll-view class="plan-tabs" scroll-x v-if="resdailyData.length > 1"> |
| 32 | <button class="tab-btn" :class="{ active: currentPlanIndex === index }" v-for="(item, index) in resdailyData" | 32 | <button class="tab-btn" :class="{ active: currentPlanIndex === index }" v-for="(item, index) in resdailyData" |
| 33 | :key="index" @click.stop="switchPlan(index)"> | 33 | :key="index" @click.stop="switchPlan(index)"> |
| 34 | - {{ item.sourceType === 1 || item.sourceType === 2 ? (item.sourceName || item.name) : (item.name || '训记' + (index + 1)) }} | 34 | + {{ item.sourceType === 1 || item.sourceType === 2 ? (item.sourceName || item.name) : (item.name || '训记' + |
| 35 | + (index + 1)) }} | ||
| 35 | </button> | 36 | </button> |
| 36 | </scroll-view> | 37 | </scroll-view> |
| 37 | <!-- 训练内容区域 --> | 38 | <!-- 训练内容区域 --> |
| 38 | <view v-if="resdailyData.length > 0 && currentPlan" class="training-container"> | 39 | <view v-if="resdailyData.length > 0 && currentPlan" class="training-container"> |
| 39 | - <!-- 训练来源标签 --> | ||
| 40 | - <view class="source-tag-row" :class="getSourceClass(currentPlan.sourceType || 4)"> | ||
| 41 | - <text class="source-text"> | ||
| 42 | - {{ currentPlan.sourceType === 1 || currentPlan.sourceType === 2 ? (currentPlan.sourceName || currentPlan.name) : getSourceLabel(currentPlan.sourceType || 4) }} | ||
| 43 | - </text> | ||
| 44 | - </view> | 40 | + |
| 45 | <view class="unitCart"> | 41 | <view class="unitCart"> |
| 46 | <!-- 训练计划头部卡片 --> | 42 | <!-- 训练计划头部卡片 --> |
| 47 | <view class="plan-header-card"> | 43 | <view class="plan-header-card"> |
| @@ -60,8 +56,10 @@ | @@ -60,8 +56,10 @@ | ||
| 60 | <view class="plan-header-btns"> | 56 | <view class="plan-header-btns"> |
| 61 | <button class="plan-btn more-btn" @click="handlePlanMore">更多</button> | 57 | <button class="plan-btn more-btn" @click="handlePlanMore">更多</button> |
| 62 | <button class="plan-btn copy-btn" @click="openCopyCalendarPopup(currentPlan?.templateId)">复制到</button> | 58 | <button class="plan-btn copy-btn" @click="openCopyCalendarPopup(currentPlan?.templateId)">复制到</button> |
| 63 | - <button class="plan-btn go-train-btn-small" @click="handleTrainAgain"> | ||
| 64 | - <text class="go-train-text-sm">今日再练</text> | 59 | + <button class="plan-btn go-train-btn-small" :class="{ locked: trainAgainDisabled }" |
| 60 | + @click="handleTrainAgain"> | ||
| 61 | + <text class="go-train-text-sm">{{ trainAgainText }}</text> | ||
| 62 | + <up-icon v-if="trainAgainText == '当日开练'" name="lock" size="15"></up-icon> | ||
| 65 | </button> | 63 | </button> |
| 66 | </view> | 64 | </view> |
| 67 | </view> | 65 | </view> |
| @@ -88,7 +86,7 @@ | @@ -88,7 +86,7 @@ | ||
| 88 | }}kg</text> | 86 | }}kg</text> |
| 89 | </view> | 87 | </view> |
| 90 | 88 | ||
| 91 | - <!-- 下半部分:组次行 → 向左对齐图片 ✅核心--> | 89 | + <!-- 下半部分:组次行 → 向左对齐图片 ✅核心 --> |
| 92 | <view class="action-bottom"> | 90 | <view class="action-bottom"> |
| 93 | <view class="action-sets"> | 91 | <view class="action-sets"> |
| 94 | <view class="set-item" v-for="(set, setIdx) in unit.exercises[0]?.sets || []" :key="setIdx"> | 92 | <view class="set-item" v-for="(set, setIdx) in unit.exercises[0]?.sets || []" :key="setIdx"> |
| @@ -175,7 +173,7 @@ | @@ -175,7 +173,7 @@ | ||
| 175 | <!-- 空状态区域 --> | 173 | <!-- 空状态区域 --> |
| 176 | <view v-else class="empty-section"> | 174 | <view v-else class="empty-section"> |
| 177 | <image class="empty-img" | 175 | <image class="empty-img" |
| 178 | - src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260724/空状态自助_1784875704380.png" | 176 | + src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260727/空状态自助_1785117390333.png" |
| 179 | mode="aspectFit"> | 177 | mode="aspectFit"> |
| 180 | </image> | 178 | </image> |
| 181 | <text class="empty-tip">今天没有安排</text> | 179 | <text class="empty-tip">今天没有安排</text> |
| @@ -184,7 +182,7 @@ | @@ -184,7 +182,7 @@ | ||
| 184 | 182 | ||
| 185 | </view> | 183 | </view> |
| 186 | 184 | ||
| 187 | - <AddTrainPopup v-model:visible="showAddTrainPopup" @successAddTrain="handleAddTrain" /> | 185 | + <AddTrainPopup v-model:visible="showAddTrainPopup" :date="selectedDate" @successAddTrain="handleAddTrain" /> |
| 188 | 186 | ||
| 189 | <!-- 日期备注弹窗 --> | 187 | <!-- 日期备注弹窗 --> |
| 190 | <RiliRiqibeizhu v-model:visible="showRiqibeizhu" :date="date" :note-id="currentEditId" | 188 | <RiliRiqibeizhu v-model:visible="showRiqibeizhu" :date="date" :note-id="currentEditId" |
| @@ -230,7 +228,7 @@ | @@ -230,7 +228,7 @@ | ||
| 230 | <!-- 复制到弹窗/移动到/添加到 --> | 228 | <!-- 复制到弹窗/移动到/添加到 --> |
| 231 | <AddToCalendarPopup ref="calendarPopupRef" :template-id="currentPlan?.templateId ?? 0" | 229 | <AddToCalendarPopup ref="calendarPopupRef" :template-id="currentPlan?.templateId ?? 0" |
| 232 | @success="handleCalendarSuccess" :mask-click="true" @click.stop | 230 | @success="handleCalendarSuccess" :mask-click="true" @click.stop |
| 233 | - :daily-template-id="currentPlan?.dailyTemplateId" :is-copy="isCopyMode" /> | 231 | + :daily-template-id="currentPlan?.id" :is-copy="isCopyMode" /> |
| 234 | <!-- 设置日历颜色弹窗 --> | 232 | <!-- 设置日历颜色弹窗 --> |
| 235 | <CalendarColorPicker v-model:visible="showColorPopup" :daily-template-id="selectedPlanId" | 233 | <CalendarColorPicker v-model:visible="showColorPopup" :daily-template-id="selectedPlanId" |
| 236 | :current-color="currentPlan?.templateBackgroundColor || '#ffffff'" @success="calendarColorPickerSuccess" /> | 234 | :current-color="currentPlan?.templateBackgroundColor || '#ffffff'" @success="calendarColorPickerSuccess" /> |
| @@ -242,7 +240,7 @@ | @@ -242,7 +240,7 @@ | ||
| 242 | 240 | ||
| 243 | <script setup> | 241 | <script setup> |
| 244 | import dayjs from 'dayjs'; | 242 | import dayjs from 'dayjs'; |
| 245 | -import { ref, onMounted, watch, computed, nextTick } from 'vue'; | 243 | +import { ref, watch, computed, nextTick, onMounted, onBeforeUnmount } from 'vue'; |
| 246 | import dailytemplateApi from '@/sheep/api/Template/Dailytemplate'; | 244 | import dailytemplateApi from '@/sheep/api/Template/Dailytemplate'; |
| 247 | import TemplatesApi from '@/sheep/api/Template/Templates'; | 245 | import TemplatesApi from '@/sheep/api/Template/Templates'; |
| 248 | import RiliRiqibeizhu from '@/pages/xunji/components/rili-components/rili-riqibeizhu.vue' | 246 | import RiliRiqibeizhu from '@/pages/xunji/components/rili-components/rili-riqibeizhu.vue' |
| @@ -278,7 +276,7 @@ const moreShow = ref(false); | @@ -278,7 +276,7 @@ const moreShow = ref(false); | ||
| 278 | const showColorPopup = ref(false); | 276 | const showColorPopup = ref(false); |
| 279 | const calendarPopupRef = ref(null); | 277 | const calendarPopupRef = ref(null); |
| 280 | const currentEditId = ref(null); | 278 | const currentEditId = ref(null); |
| 281 | -const selectedPlanId = ref(null); | 279 | +const selectedPlanId = ref(0); |
| 282 | 280 | ||
| 283 | // Store / 子组件引用 | 281 | // Store / 子组件引用 |
| 284 | const trainingStore = useTrainingStore(); | 282 | const trainingStore = useTrainingStore(); |
| @@ -325,7 +323,7 @@ const openXiuGai = (unit, idx) => { | @@ -325,7 +323,7 @@ const openXiuGai = (unit, idx) => { | ||
| 325 | 323 | ||
| 326 | // 打开日历弹窗 | 324 | // 打开日历弹窗 |
| 327 | const openColorPopup = () => { | 325 | const openColorPopup = () => { |
| 328 | - if (!currentPlan.value) { // 加这个 | 326 | + if (!currentPlan.value) { |
| 329 | uni.showToast({ title: '空白状态无法操作', icon: 'none' }) | 327 | uni.showToast({ title: '空白状态无法操作', icon: 'none' }) |
| 330 | return | 328 | return |
| 331 | } | 329 | } |
| @@ -340,11 +338,10 @@ const openColorPopup = () => { | @@ -340,11 +338,10 @@ const openColorPopup = () => { | ||
| 340 | 338 | ||
| 341 | const handleAddTrain = async () => { | 339 | const handleAddTrain = async () => { |
| 342 | await loaddailytemplate(); | 340 | await loaddailytemplate(); |
| 343 | - // 更新主页面 | ||
| 344 | emit('refreshCalendar'); | 341 | emit('refreshCalendar'); |
| 345 | } | 342 | } |
| 343 | + | ||
| 346 | // ===== 计算属性 ===== | 344 | // ===== 计算属性 ===== |
| 347 | -// 当前正在显示的训记 | ||
| 348 | const currentPlan = computed(() => { | 345 | const currentPlan = computed(() => { |
| 349 | if (!resdailyData.value.length) return null; | 346 | if (!resdailyData.value.length) return null; |
| 350 | return resdailyData.value[currentPlanIndex.value] || null; | 347 | return resdailyData.value[currentPlanIndex.value] || null; |
| @@ -353,29 +350,19 @@ const currentPlan = computed(() => { | @@ -353,29 +350,19 @@ const currentPlan = computed(() => { | ||
| 353 | // ===== API:加载每日模板数据 ===== | 350 | // ===== API:加载每日模板数据 ===== |
| 354 | const loaddailytemplate = async () => { | 351 | const loaddailytemplate = async () => { |
| 355 | if (!selectedDate.value) return; | 352 | if (!selectedDate.value) return; |
| 356 | - console.log('【子组件】开始加载数据...'); | ||
| 357 | try { | 353 | try { |
| 358 | const resdaily = await dailytemplateApi.getdailytemplate(String(selectedDate.value)); | 354 | const resdaily = await dailytemplateApi.getdailytemplate(String(selectedDate.value)); |
| 359 | - console.log('打印每日模板数据:resdaily', resdaily); | ||
| 360 | - | ||
| 361 | const noteListFromApi = resdaily.data.notes; | 355 | const noteListFromApi = resdaily.data.notes; |
| 362 | noteList.value = noteListFromApi; | 356 | noteList.value = noteListFromApi; |
| 363 | - console.log('打印每日模板详情的备注列表:noteList', noteList.value); | ||
| 364 | - | ||
| 365 | const historyFromApi = resdaily.data.noteHistoryList || []; | 357 | const historyFromApi = resdaily.data.noteHistoryList || []; |
| 366 | - console.log('打印历史备注:', historyFromApi); | ||
| 367 | noteHistoryList.value = historyFromApi; | 358 | noteHistoryList.value = historyFromApi; |
| 368 | - | ||
| 369 | resdailyData.value = resdaily.data.templates || []; | 359 | resdailyData.value = resdaily.data.templates || []; |
| 370 | - | ||
| 371 | - // 切换回第一个训记 | ||
| 372 | const oldIndex = currentPlanIndex.value; | 360 | const oldIndex = currentPlanIndex.value; |
| 373 | if (resdailyData.value.length > oldIndex) { | 361 | if (resdailyData.value.length > oldIndex) { |
| 374 | currentPlanIndex.value = oldIndex; | 362 | currentPlanIndex.value = oldIndex; |
| 375 | } else { | 363 | } else { |
| 376 | currentPlanIndex.value = 0; | 364 | currentPlanIndex.value = 0; |
| 377 | } | 365 | } |
| 378 | - console.log('+++++【子组件】+++++加载完成,训记列表:', resdailyData.value) | ||
| 379 | } catch (error) { | 366 | } catch (error) { |
| 380 | console.error('加载训练模板失败:', error); | 367 | console.error('加载训练模板失败:', error); |
| 381 | resdailyData.value = []; | 368 | resdailyData.value = []; |
| @@ -383,21 +370,18 @@ const loaddailytemplate = async () => { | @@ -383,21 +370,18 @@ const loaddailytemplate = async () => { | ||
| 383 | }; | 370 | }; |
| 384 | 371 | ||
| 385 | const handleRenderSuccess = async () => { | 372 | const handleRenderSuccess = async () => { |
| 386 | - | ||
| 387 | await loaddailytemplate(); | 373 | await loaddailytemplate(); |
| 388 | - await nextTick() | 374 | + emit('refreshCalendar'); |
| 375 | + uni.$emit('calendarDataRefresh'); | ||
| 389 | } | 376 | } |
| 390 | 377 | ||
| 391 | const switchPlan = (index) => { | 378 | const switchPlan = (index) => { |
| 392 | currentPlanIndex.value = index; | 379 | currentPlanIndex.value = index; |
| 393 | - console.log('【切换标签后 - currentPlan】', currentPlan.value); | ||
| 394 | }; | 380 | }; |
| 395 | 381 | ||
| 396 | // ===== 更多弹窗:操作项 ===== | 382 | // ===== 更多弹窗:操作项 ===== |
| 397 | const handlePlanMore = () => { | 383 | const handlePlanMore = () => { |
| 398 | - if (!currentPlan.value) { // 加这个 | ||
| 399 | - return | ||
| 400 | - } | 384 | + if (!currentPlan.value) return |
| 401 | moreShow.value = true; | 385 | moreShow.value = true; |
| 402 | } | 386 | } |
| 403 | 387 | ||
| @@ -407,28 +391,20 @@ const closeMorePopup = () => { | @@ -407,28 +391,20 @@ const closeMorePopup = () => { | ||
| 407 | 391 | ||
| 408 | // 一键打勾(完成训练) | 392 | // 一键打勾(完成训练) |
| 409 | const handleCompleteCheck = async () => { | 393 | const handleCompleteCheck = async () => { |
| 410 | - // 日期校验:只能对今天及以前的日期一键打勾 | ||
| 411 | const today = dayjs().format('YYYY-MM-DD'); | 394 | const today = dayjs().format('YYYY-MM-DD'); |
| 412 | if (selectedDate.value > today) { | 395 | if (selectedDate.value > today) { |
| 413 | uni.showToast({ title: '一键打勾只能用于今天及以前的日期', icon: 'none' }); | 396 | uni.showToast({ title: '一键打勾只能用于今天及以前的日期', icon: 'none' }); |
| 414 | return; | 397 | return; |
| 415 | } | 398 | } |
| 416 | - | ||
| 417 | if (!currentPlan.value?.id) { | 399 | if (!currentPlan.value?.id) { |
| 418 | uni.showToast({ title: '未找到训练ID', icon: 'none' }) | 400 | uni.showToast({ title: '未找到训练ID', icon: 'none' }) |
| 419 | return | 401 | return |
| 420 | } | 402 | } |
| 421 | - | ||
| 422 | - console.log('进入一键打勾函数训练id=', currentPlan.value.id); | ||
| 423 | - | ||
| 424 | try { | 403 | try { |
| 425 | - // 2. 调用接口 | ||
| 426 | await dailytemplateApi.completeDailyTemplate(currentPlan.value.id) | 404 | await dailytemplateApi.completeDailyTemplate(currentPlan.value.id) |
| 427 | emit('refreshCalendar'); | 405 | emit('refreshCalendar'); |
| 428 | uni.showToast({ title: '一键打勾成功', icon: 'success' }) | 406 | uni.showToast({ title: '一键打勾成功', icon: 'success' }) |
| 429 | closeMorePopup() | 407 | closeMorePopup() |
| 430 | - | ||
| 431 | - // 刷新页面数据 | ||
| 432 | await loaddailytemplate(selectedDate.value) | 408 | await loaddailytemplate(selectedDate.value) |
| 433 | } catch (err) { | 409 | } catch (err) { |
| 434 | console.error('一键打勾失败:', err) | 410 | console.error('一键打勾失败:', err) |
| @@ -438,54 +414,39 @@ const handleCompleteCheck = async () => { | @@ -438,54 +414,39 @@ const handleCompleteCheck = async () => { | ||
| 438 | 414 | ||
| 439 | // ===== 保存训练为个人模板 ===== | 415 | // ===== 保存训练为个人模板 ===== |
| 440 | const openSaveTemplateDialog = async (item) => { | 416 | const openSaveTemplateDialog = async (item) => { |
| 441 | - // 关闭所有可能遮挡的弹窗 | ||
| 442 | moreShow.value = false; | 417 | moreShow.value = false; |
| 443 | showColorPopup.value = false; | 418 | showColorPopup.value = false; |
| 444 | showAddTrainPopup.value = false; | 419 | showAddTrainPopup.value = false; |
| 445 | showRiqibeizhu.value = false; | 420 | showRiqibeizhu.value = false; |
| 446 | show.value = false | 421 | show.value = false |
| 447 | - // 等待 DOM 更新后再弹出模板名称输入框,确保弹窗完全关闭 | ||
| 448 | await nextTick(); | 422 | await nextTick(); |
| 449 | - | ||
| 450 | - // uni弹窗输入框:只填模板名称 | ||
| 451 | uni.showModal({ | 423 | uni.showModal({ |
| 452 | title: '确认保存为你的训练模板?', | 424 | title: '确认保存为你的训练模板?', |
| 453 | content: '', | 425 | content: '', |
| 454 | - editable: true, // 开启输入框 | 426 | + editable: true, |
| 455 | placeholderText: '请输入模板名称', | 427 | placeholderText: '请输入模板名称', |
| 456 | success: async (res) => { | 428 | success: async (res) => { |
| 457 | - // 点击确定 | ||
| 458 | if (res.confirm) { | 429 | if (res.confirm) { |
| 459 | const templateName = res.content.trim() | 430 | const templateName = res.content.trim() |
| 460 | - // 非名校验 | ||
| 461 | if (!templateName) { | 431 | if (!templateName) { |
| 462 | return uni.showToast({ title: '请填写模板名称', icon: 'none' }) | 432 | return uni.showToast({ title: '请填写模板名称', icon: 'none' }) |
| 463 | } | 433 | } |
| 464 | - // 调用封装的数据转换函数,组装后端需要的入参 | ||
| 465 | const reqData = formatTrainToTemplate(item, templateName) | 434 | const reqData = formatTrainToTemplate(item, templateName) |
| 466 | - // 发起创建接口 | ||
| 467 | - console.log('reqData', reqData); | ||
| 468 | await submitCreateTemplate(reqData) | 435 | await submitCreateTemplate(reqData) |
| 469 | } | 436 | } |
| 470 | } | 437 | } |
| 471 | }) | 438 | }) |
| 472 | } | 439 | } |
| 473 | 440 | ||
| 474 | -/** | ||
| 475 | - */ | ||
| 476 | const formatTrainToTemplate = (item, templateName) => { | 441 | const formatTrainToTemplate = (item, templateName) => { |
| 477 | - // 初始化基础结构 | ||
| 478 | const params = { | 442 | const params = { |
| 479 | templateId: item.templateId, | 443 | templateId: item.templateId, |
| 480 | - // groupId: 0, // 默认存入根目录,后续选文件夹再传对应groupId | ||
| 481 | templateName: templateName, | 444 | templateName: templateName, |
| 482 | scene: item.scene || 0, | 445 | scene: item.scene || 0, |
| 483 | - templateCover: item.urlCover || '', // 封面取自训练封面 | ||
| 484 | - templateIntroduction: item.templateIntroduction || '', // 简介留空,可后续扩展 | 446 | + templateCover: item.urlCover || '', |
| 447 | + templateIntroduction: item.templateIntroduction || '', | ||
| 485 | units: [] | 448 | units: [] |
| 486 | } | 449 | } |
| 487 | - | ||
| 488 | - // 循环转换 units 结构(训练组→模板unit) | ||
| 489 | params.units = item.units.map(unit => { | 450 | params.units = item.units.map(unit => { |
| 490 | return { | 451 | return { |
| 491 | id: unit.unitId, | 452 | id: unit.unitId, |
| @@ -495,16 +456,11 @@ const formatTrainToTemplate = (item, templateName) => { | @@ -495,16 +456,11 @@ const formatTrainToTemplate = (item, templateName) => { | ||
| 495 | exercises: unit.exercises | 456 | exercises: unit.exercises |
| 496 | } | 457 | } |
| 497 | }) | 458 | }) |
| 498 | - | ||
| 499 | - console.log('params', params); | ||
| 500 | - | ||
| 501 | - | ||
| 502 | return params | 459 | return params |
| 503 | } | 460 | } |
| 504 | 461 | ||
| 505 | const submitCreateTemplate = async (reqData) => { | 462 | const submitCreateTemplate = async (reqData) => { |
| 506 | try { | 463 | try { |
| 507 | - // 调用创建个人模板接口 | ||
| 508 | const res = await TemplatesApi.createCustTemplate(reqData) | 464 | const res = await TemplatesApi.createCustTemplate(reqData) |
| 509 | if (res.code === 0) { | 465 | if (res.code === 0) { |
| 510 | uni.showToast({ title: '模板保存成功' }) | 466 | uni.showToast({ title: '模板保存成功' }) |
| @@ -521,9 +477,6 @@ const submitCreateTemplate = async (reqData) => { | @@ -521,9 +477,6 @@ const submitCreateTemplate = async (reqData) => { | ||
| 521 | const openAddTrainPopup = () => { | 477 | const openAddTrainPopup = () => { |
| 522 | showAddTrainPopup.value = true; | 478 | showAddTrainPopup.value = true; |
| 523 | }; | 479 | }; |
| 524 | -const closeAddTrainPopup = () => { | ||
| 525 | - showAddTrainPopup.value = false; | ||
| 526 | -}; | ||
| 527 | 480 | ||
| 528 | // ===== 工具函数 ===== | 481 | // ===== 工具函数 ===== |
| 529 | const formatSecondsToHms = (seconds) => { | 482 | const formatSecondsToHms = (seconds) => { |
| @@ -531,7 +484,6 @@ const formatSecondsToHms = (seconds) => { | @@ -531,7 +484,6 @@ const formatSecondsToHms = (seconds) => { | ||
| 531 | return dayjs.duration(parseInt(seconds, 10), 'seconds').format('HH:mm:ss'); | 484 | return dayjs.duration(parseInt(seconds, 10), 'seconds').format('HH:mm:ss'); |
| 532 | } | 485 | } |
| 533 | 486 | ||
| 534 | -// ===== 工具函数:根据动作类型生成组次显示文本 ===== | ||
| 535 | const getSetDisplayText = (set, exerciseType) => { | 487 | const getSetDisplayText = (set, exerciseType) => { |
| 536 | if (!set) return ''; | 488 | if (!set) return ''; |
| 537 | switch (exerciseType) { | 489 | switch (exerciseType) { |
| @@ -546,7 +498,6 @@ const getSetDisplayText = (set, exerciseType) => { | @@ -546,7 +498,6 @@ const getSetDisplayText = (set, exerciseType) => { | ||
| 546 | } | 498 | } |
| 547 | }; | 499 | }; |
| 548 | 500 | ||
| 549 | -// 超级组中的动作显示(与普通动作在 type 4/5 上的展示略有差异) | ||
| 550 | const getSuperSetDisplayText = (set, exerciseType) => { | 501 | const getSuperSetDisplayText = (set, exerciseType) => { |
| 551 | if (!set) return ''; | 502 | if (!set) return ''; |
| 552 | switch (exerciseType) { | 503 | switch (exerciseType) { |
| @@ -561,17 +512,6 @@ const getSuperSetDisplayText = (set, exerciseType) => { | @@ -561,17 +512,6 @@ const getSuperSetDisplayText = (set, exerciseType) => { | ||
| 561 | } | 512 | } |
| 562 | }; | 513 | }; |
| 563 | 514 | ||
| 564 | -// 返回按钮逻辑 | ||
| 565 | -const goBack = () => { | ||
| 566 | - uni.navigateBack({ | ||
| 567 | - delta: 1, | ||
| 568 | - fail: () => { | ||
| 569 | - uni.redirectTo({ url: '/pages/xunji/xunji-rili' }); | ||
| 570 | - }, | ||
| 571 | - }); | ||
| 572 | -}; | ||
| 573 | - | ||
| 574 | -// 日期格式化(使用 dayjs 处理,兼容性更好) | ||
| 575 | const formatDateWithWeek = (dateStr) => { | 515 | const formatDateWithWeek = (dateStr) => { |
| 576 | if (!dateStr) return ''; | 516 | if (!dateStr) return ''; |
| 577 | const d = dayjs(dateStr); | 517 | const d = dayjs(dateStr); |
| @@ -582,18 +522,13 @@ const formatDateWithWeek = (dateStr) => { | @@ -582,18 +522,13 @@ const formatDateWithWeek = (dateStr) => { | ||
| 582 | // ===== 日期备注相关 ===== | 522 | // ===== 日期备注相关 ===== |
| 583 | const handleDateNote = () => { | 523 | const handleDateNote = () => { |
| 584 | if (noteList.value.length >= 5) { | 524 | if (noteList.value.length >= 5) { |
| 585 | - uni.showToast({ | ||
| 586 | - title: '最多添加5条,已超出数量无法添加', | ||
| 587 | - icon: 'none', | ||
| 588 | - duration: 2000 | ||
| 589 | - }); | 525 | + uni.showToast({ title: '最多添加5条,已超出数量无法添加', icon: 'none', duration: 2000 }); |
| 590 | return; | 526 | return; |
| 591 | } | 527 | } |
| 592 | showRiqibeizhu.value = true | 528 | showRiqibeizhu.value = true |
| 593 | } | 529 | } |
| 594 | 530 | ||
| 595 | const handleEditNote = (note) => { | 531 | const handleEditNote = (note) => { |
| 596 | - console.log('准备编辑备注,ID:', note.id); | ||
| 597 | currentEditId.value = note.id; | 532 | currentEditId.value = note.id; |
| 598 | showRiqibeizhu.value = true; | 533 | showRiqibeizhu.value = true; |
| 599 | }; | 534 | }; |
| @@ -608,8 +543,6 @@ const handleCloseNotePopup = () => { | @@ -608,8 +543,6 @@ const handleCloseNotePopup = () => { | ||
| 608 | 543 | ||
| 609 | // ===== 复制到 / 移动到 ===== | 544 | // ===== 复制到 / 移动到 ===== |
| 610 | const openCalendarPopup = (templateId) => { | 545 | const openCalendarPopup = (templateId) => { |
| 611 | - console.log('点击了添加到日历按钮', templateId); // 新增日志 | ||
| 612 | - console.log('子组件实例', calendarPopupRef.value); // 新增日志 | ||
| 613 | if (calendarPopupRef.value && typeof calendarPopupRef.value.open === 'function') { | 546 | if (calendarPopupRef.value && typeof calendarPopupRef.value.open === 'function') { |
| 614 | calendarPopupRef.value.open(); | 547 | calendarPopupRef.value.open(); |
| 615 | } else { | 548 | } else { |
| @@ -617,19 +550,47 @@ const openCalendarPopup = (templateId) => { | @@ -617,19 +550,47 @@ const openCalendarPopup = (templateId) => { | ||
| 617 | } | 550 | } |
| 618 | }; | 551 | }; |
| 619 | 552 | ||
| 553 | +// 今日再练 / 去训练 / 当日开练 | ||
| 554 | +const trainAgainText = computed(() => { | ||
| 555 | + const today = dayjs().format('YYYY-MM-DD'); | ||
| 556 | + if (!selectedDate.value) return '今日再练'; | ||
| 557 | + if (selectedDate.value === today) return '今日再练'; | ||
| 558 | + if (selectedDate.value > today) return '当日开练'; | ||
| 559 | + return '去训练'; | ||
| 560 | +}); | ||
| 561 | + | ||
| 562 | +const trainAgainDisabled = computed(() => { | ||
| 563 | + const today = dayjs().format('YYYY-MM-DD'); | ||
| 564 | + return !!selectedDate.value && selectedDate.value > today; | ||
| 565 | +}); | ||
| 620 | 566 | ||
| 621 | -// 2. 今日再练方法 | ||
| 622 | const handleTrainAgain = async () => { | 567 | const handleTrainAgain = async () => { |
| 568 | + const today = dayjs().format('YYYY-MM-DD'); | ||
| 569 | + const planDate = selectedDate.value || today; | ||
| 570 | + | ||
| 571 | + if (planDate > today) { | ||
| 572 | + uni.showToast({ title: '训练将在当日解锁', icon: 'none' }); | ||
| 573 | + return; | ||
| 574 | + } | ||
| 575 | + | ||
| 576 | + if (planDate < today) { | ||
| 577 | + trainingStore.isSystem = currentPlan.value.isSystem || false; | ||
| 578 | + trainingStore.loadDailyTemplateForEdit(currentPlan.value); | ||
| 579 | + trainingStore.initDailyTemplateRecords(); | ||
| 580 | + uni.navigateTo({ | ||
| 581 | + url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${currentPlan.value.templateId}&type=3&dailyTemplateId=${currentPlan.value.id}&isTraining=true`, | ||
| 582 | + }); | ||
| 583 | + return; | ||
| 584 | + } | ||
| 585 | + | ||
| 623 | if (!currentPlan.value?.templateId) { | 586 | if (!currentPlan.value?.templateId) { |
| 624 | uni.showToast({ title: '未找到训练计划', icon: 'none' }); | 587 | uni.showToast({ title: '未找到训练计划', icon: 'none' }); |
| 625 | return; | 588 | return; |
| 626 | } | 589 | } |
| 627 | 590 | ||
| 628 | - const todayDate = new Date().toISOString().split('T')[0]; // 格式:2026-05-19 | ||
| 629 | const reqData = { | 591 | const reqData = { |
| 630 | id: currentPlan.value.templateId, | 592 | id: currentPlan.value.templateId, |
| 631 | - // planId: currentPlan.value.planId || 0, | ||
| 632 | - trainDateList: [todayDate] // 训练日期列表,这里只传今天 | 593 | + trainDateList: [today], |
| 633 | }; | 594 | }; |
| 634 | 595 | ||
| 635 | try { | 596 | try { |
| @@ -637,7 +598,7 @@ const handleTrainAgain = async () => { | @@ -637,7 +598,7 @@ const handleTrainAgain = async () => { | ||
| 637 | if (res.code === 0) { | 598 | if (res.code === 0) { |
| 638 | uni.showToast({ title: '今日再练成功', icon: 'success' }); | 599 | uni.showToast({ title: '今日再练成功', icon: 'success' }); |
| 639 | loaddailytemplate(props.date); | 600 | loaddailytemplate(props.date); |
| 640 | - emit('refreshCalendar') | 601 | + emit('refreshCalendar'); |
| 641 | } else { | 602 | } else { |
| 642 | uni.showToast({ title: res.msg || '添加失败', icon: 'none' }); | 603 | uni.showToast({ title: res.msg || '添加失败', icon: 'none' }); |
| 643 | } | 604 | } |
| @@ -646,22 +607,19 @@ const handleTrainAgain = async () => { | @@ -646,22 +607,19 @@ const handleTrainAgain = async () => { | ||
| 646 | uni.showToast({ title: '网络异常,请重试', icon: 'none' }); | 607 | uni.showToast({ title: '网络异常,请重试', icon: 'none' }); |
| 647 | } | 608 | } |
| 648 | }; | 609 | }; |
| 610 | + | ||
| 649 | // 删除训练 | 611 | // 删除训练 |
| 650 | const handleDeleteTemplate = async () => { | 612 | const handleDeleteTemplate = async () => { |
| 651 | - // 先判断有没有ID | ||
| 652 | if (!currentPlan.value?.id) { | 613 | if (!currentPlan.value?.id) { |
| 653 | uni.showToast({ title: '未找到模板ID', icon: 'none' }); | 614 | uni.showToast({ title: '未找到模板ID', icon: 'none' }); |
| 654 | return; | 615 | return; |
| 655 | } | 616 | } |
| 656 | - // 关闭所有可能遮挡的弹窗 | ||
| 657 | moreShow.value = false; | 617 | moreShow.value = false; |
| 658 | showColorPopup.value = false; | 618 | showColorPopup.value = false; |
| 659 | showAddTrainPopup.value = false; | 619 | showAddTrainPopup.value = false; |
| 660 | showRiqibeizhu.value = false; | 620 | showRiqibeizhu.value = false; |
| 661 | show.value = false | 621 | show.value = false |
| 662 | - // 等待 DOM 更新后再弹出模板名称输入框,确保弹窗完全关闭 | ||
| 663 | await nextTick(); | 622 | await nextTick(); |
| 664 | - // 弹出确认框 | ||
| 665 | uni.showModal({ | 623 | uni.showModal({ |
| 666 | title: '确认删除', | 624 | title: '确认删除', |
| 667 | content: '确定要删除这条训练记录吗?删除后无法恢复', | 625 | content: '确定要删除这条训练记录吗?删除后无法恢复', |
| @@ -674,8 +632,8 @@ const handleDeleteTemplate = async () => { | @@ -674,8 +632,8 @@ const handleDeleteTemplate = async () => { | ||
| 674 | await dailytemplateApi.deleteDailyTemplate(currentPlan.value.id); | 632 | await dailytemplateApi.deleteDailyTemplate(currentPlan.value.id); |
| 675 | emit('refreshCalendar'); | 633 | emit('refreshCalendar'); |
| 676 | uni.showToast({ title: '删除成功', icon: 'success' }); | 634 | uni.showToast({ title: '删除成功', icon: 'success' }); |
| 677 | - loaddailytemplate(props.date); // 刷新数据 | ||
| 678 | - moreShow.value = false; // 关闭弹窗 | 635 | + loaddailytemplate(props.date); |
| 636 | + moreShow.value = false; | ||
| 679 | } catch (err) { | 637 | } catch (err) { |
| 680 | console.error('删除失败', err); | 638 | console.error('删除失败', err); |
| 681 | uni.showToast({ title: '删除失败,请重试', icon: 'none' }); | 639 | uni.showToast({ title: '删除失败,请重试', icon: 'none' }); |
| @@ -687,13 +645,8 @@ const handleDeleteTemplate = async () => { | @@ -687,13 +645,8 @@ const handleDeleteTemplate = async () => { | ||
| 687 | // 更多弹窗的结束整个计划 | 645 | // 更多弹窗的结束整个计划 |
| 688 | const confirmDelete = async (id) => { | 646 | const confirmDelete = async (id) => { |
| 689 | try { | 647 | try { |
| 690 | - console.log('打印计划个人id:', id); | ||
| 691 | - | ||
| 692 | const res = await QueryPlanApi.deletePlan(id); | 648 | const res = await QueryPlanApi.deletePlan(id); |
| 693 | - console.log('删除结果:', res); | ||
| 694 | - | ||
| 695 | if (res.code === 0 && res.data) { | 649 | if (res.code === 0 && res.data) { |
| 696 | - // 2. 删除成功,从列表移除 | ||
| 697 | uni.showToast({ title: '计划已结束', icon: 'success' }); | 650 | uni.showToast({ title: '计划已结束', icon: 'success' }); |
| 698 | emit('refreshCalendar'); | 651 | emit('refreshCalendar'); |
| 699 | } else { | 652 | } else { |
| @@ -710,18 +663,12 @@ const confirmDelete = async (id) => { | @@ -710,18 +663,12 @@ const confirmDelete = async (id) => { | ||
| 710 | 663 | ||
| 711 | // 移动到:只打开弹窗 | 664 | // 移动到:只打开弹窗 |
| 712 | const handleMoveTo = async () => { | 665 | const handleMoveTo = async () => { |
| 713 | - console.log('点击了更多弹窗的移动到'); | ||
| 714 | - | ||
| 715 | - // 安全判断 | ||
| 716 | if (!currentPlan.value) { | 666 | if (!currentPlan.value) { |
| 717 | uni.showToast({ title: '未找到训练信息', icon: 'none' }); | 667 | uni.showToast({ title: '未找到训练信息', icon: 'none' }); |
| 718 | return; | 668 | return; |
| 719 | } | 669 | } |
| 720 | isMoveMode.value = true; | 670 | isMoveMode.value = true; |
| 721 | - // 关闭更多弹窗 | ||
| 722 | moreShow.value = false; | 671 | moreShow.value = false; |
| 723 | - | ||
| 724 | - // 只做一件事:打开日期选择 | ||
| 725 | nextTick(() => { | 672 | nextTick(() => { |
| 726 | openCalendarPopup(currentPlan.value.templateId); | 673 | openCalendarPopup(currentPlan.value.templateId); |
| 727 | }); | 674 | }); |
| @@ -730,9 +677,8 @@ const handleMoveTo = async () => { | @@ -730,9 +677,8 @@ const handleMoveTo = async () => { | ||
| 730 | // 复制到 | 677 | // 复制到 |
| 731 | const openCopyCalendarPopup = (templateId) => { | 678 | const openCopyCalendarPopup = (templateId) => { |
| 732 | if (!templateId) return uni.showToast({ title: '模板ID不存在', icon: 'none' }) | 679 | if (!templateId) return uni.showToast({ title: '模板ID不存在', icon: 'none' }) |
| 733 | - console.log('currentPlan?.dailyTemplateId=', currentPlan?.dailyTemplateId); | ||
| 734 | isCopyMode.value = true | 680 | isCopyMode.value = true |
| 735 | - isMoveMode.value = false // 清空移动标记,互斥 | 681 | + isMoveMode.value = false |
| 736 | moreShow.value = false | 682 | moreShow.value = false |
| 737 | nextTick(() => { | 683 | nextTick(() => { |
| 738 | calendarPopupRef.value.open(); | 684 | calendarPopupRef.value.open(); |
| @@ -740,35 +686,13 @@ const openCopyCalendarPopup = (templateId) => { | @@ -740,35 +686,13 @@ const openCopyCalendarPopup = (templateId) => { | ||
| 740 | } | 686 | } |
| 741 | 687 | ||
| 742 | const handleCalendarSuccess = () => { | 688 | const handleCalendarSuccess = () => { |
| 743 | - // console.log('✅ 添加日历成功,当前模式:', isMoveMode.value ? '移动' : '复制'); | ||
| 744 | - // 复制模式:只提示成功,不用删原数据 | ||
| 745 | - // if (!isMoveMode.value) { | ||
| 746 | - // uni.showToast({ | ||
| 747 | - // title: '复制成功', | ||
| 748 | - // icon: 'success' | ||
| 749 | - // }); | ||
| 750 | - // return; | ||
| 751 | - // } | ||
| 752 | - console.log('✅ 添加日历成功', { | ||
| 753 | - move: isMoveMode.value, | ||
| 754 | - copy: isCopyMode.value | ||
| 755 | - }); | ||
| 756 | - | ||
| 757 | - // 复制模式:仅刷新日历,不删除原数据 | ||
| 758 | if (isCopyMode.value) { | 689 | if (isCopyMode.value) { |
| 759 | - uni.showToast({ | ||
| 760 | - title: '复制成功', | ||
| 761 | - icon: 'success' | ||
| 762 | - }); | ||
| 763 | - // 重置标记 | 690 | + uni.showToast({ title: '复制成功', icon: 'success' }); |
| 764 | isCopyMode.value = false | 691 | isCopyMode.value = false |
| 765 | emit('refreshCalendar') | 692 | emit('refreshCalendar') |
| 766 | return; | 693 | return; |
| 767 | } | 694 | } |
| 768 | - // 移动模式:子组件已把数据新增到新日期,这里删除原日期的训练记录 | ||
| 769 | - if (!currentPlan.value?.id) { | ||
| 770 | - return; | ||
| 771 | - } | 695 | + if (!currentPlan.value?.id) return; |
| 772 | dailytemplateApi.deleteDailyTemplate(currentPlan.value.id) | 696 | dailytemplateApi.deleteDailyTemplate(currentPlan.value.id) |
| 773 | .then(() => { | 697 | .then(() => { |
| 774 | uni.showToast({ title: '移动成功', icon: 'success' }); | 698 | uni.showToast({ title: '移动成功', icon: 'success' }); |
| @@ -788,15 +712,10 @@ const calendarColorPickerSuccess = () => { | @@ -788,15 +712,10 @@ const calendarColorPickerSuccess = () => { | ||
| 788 | 712 | ||
| 789 | // ===== 去训练 / 每日模板编辑 ===== | 713 | // ===== 去训练 / 每日模板编辑 ===== |
| 790 | const startTraining = () => { | 714 | const startTraining = () => { |
| 791 | - | ||
| 792 | if (trainingStore.isTraining) { | 715 | if (trainingStore.isTraining) { |
| 793 | - uni.showToast({ | ||
| 794 | - title: '当前已有正在进行的训练', | ||
| 795 | - icon: 'none' | ||
| 796 | - }); | 716 | + uni.showToast({ title: '当前已有正在进行的训练', icon: 'none' }); |
| 797 | return; | 717 | return; |
| 798 | } | 718 | } |
| 799 | - | ||
| 800 | trainingStore.isTraining = true; | 719 | trainingStore.isTraining = true; |
| 801 | if (resdailyData.value.length > 0) { | 720 | if (resdailyData.value.length > 0) { |
| 802 | trainingStore.isSystem = currentPlan.value.isSystem || false; | 721 | trainingStore.isSystem = currentPlan.value.isSystem || false; |
| @@ -805,52 +724,44 @@ const startTraining = () => { | @@ -805,52 +724,44 @@ const startTraining = () => { | ||
| 805 | uni.navigateTo({ | 724 | uni.navigateTo({ |
| 806 | url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${currentPlan.value.templateId}&type=3&dailyTemplateId=${currentPlan.value.id}&isTraining=true`, | 725 | url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${currentPlan.value.templateId}&type=3&dailyTemplateId=${currentPlan.value.id}&isTraining=true`, |
| 807 | }); | 726 | }); |
| 808 | - console.log('去训练开始进入编辑模板页面,模板ID:', currentPlan.value.templateId, '每日模板ID:', currentPlan.value.id); | ||
| 809 | - | ||
| 810 | - console.log('打印传递给动作训练页面的模板id', currentPlan.value.templateId); | ||
| 811 | } else { | 727 | } else { |
| 812 | uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-lianxi' }) | 728 | uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-lianxi' }) |
| 813 | } | 729 | } |
| 814 | - | ||
| 815 | - | ||
| 816 | - | ||
| 817 | }; | 730 | }; |
| 818 | 731 | ||
| 819 | -// 每日模板编辑 | ||
| 820 | const templateEdit = (plan) => { | 732 | const templateEdit = (plan) => { |
| 821 | trainingStore.isSystem = plan.isSystem; | 733 | trainingStore.isSystem = plan.isSystem; |
| 822 | trainingStore.loadDailyTemplateForEdit(plan); | 734 | trainingStore.loadDailyTemplateForEdit(plan); |
| 823 | trainingStore.initDailyTemplateRecords() | 735 | trainingStore.initDailyTemplateRecords() |
| 824 | uni.navigateTo({ | 736 | uni.navigateTo({ |
| 825 | - url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${plan.templateId}&type=3&dailyTemplateId=${plan.id}`, | 737 | + url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${plan.templateId}&type=3&dailyTemplateId=${plan.id}&isEdit=true`, |
| 826 | }); | 738 | }); |
| 827 | - console.log('开始进入编辑模板页面,模板ID:', plan.templateId, '每日模板ID:', plan.id); | ||
| 828 | }; | 739 | }; |
| 829 | 740 | ||
| 830 | // ===== 监听 & 生命周期 ===== | 741 | // ===== 监听 & 生命周期 ===== |
| 831 | -// 监听父组件传进来的 date 变化 | ||
| 832 | watch( | 742 | watch( |
| 833 | () => props.date, | 743 | () => props.date, |
| 834 | (newDate) => { | 744 | (newDate) => { |
| 835 | if (newDate && newDate.trim()) { | 745 | if (newDate && newDate.trim()) { |
| 836 | - console.log('子组件监听到日期变化:', newDate); | ||
| 837 | selectedDate.value = newDate; | 746 | selectedDate.value = newDate; |
| 838 | displayDate.value = formatDateWithWeek(newDate); | 747 | displayDate.value = formatDateWithWeek(newDate); |
| 839 | loaddailytemplate(newDate); | 748 | loaddailytemplate(newDate); |
| 840 | - console.log('+++++++currentPlan:+++++++++++++++++', currentPlan.value) | ||
| 841 | - // console.log('props.noteList=', props.noteList); | ||
| 842 | - | ||
| 843 | } | 749 | } |
| 844 | }, | 750 | }, |
| 845 | { immediate: true } | 751 | { immediate: true } |
| 846 | ); | 752 | ); |
| 847 | 753 | ||
| 754 | +// 监听来自编辑/训练页面的数据刷新事件,自动刷新弹窗数据 | ||
| 755 | +const handleCalendarDataRefresh = () => { | ||
| 756 | + loaddailytemplate(); | ||
| 757 | +}; | ||
| 848 | 758 | ||
| 849 | onMounted(() => { | 759 | onMounted(() => { |
| 850 | - console.log('【rilicell子组件】已挂载!!!'); | ||
| 851 | - console.log('【子组件】props.date:', props.date); | ||
| 852 | - // loaddailytemplate(props.date); | 760 | + uni.$on('calendarDataRefresh', handleCalendarDataRefresh); |
| 761 | +}); | ||
| 853 | 762 | ||
| 763 | +onBeforeUnmount(() => { | ||
| 764 | + uni.$off('calendarDataRefresh', handleCalendarDataRefresh); | ||
| 854 | }); | 765 | }); |
| 855 | </script> | 766 | </script> |
| 856 | 767 | ||
| @@ -860,13 +771,6 @@ $color-primary: #333; | @@ -860,13 +771,6 @@ $color-primary: #333; | ||
| 860 | $color-accent: #ffc107; | 771 | $color-accent: #ffc107; |
| 861 | $color-bg: #f2f3f5; | 772 | $color-bg: #f2f3f5; |
| 862 | $color-white: #fff; | 773 | $color-white: #fff; |
| 863 | -$color-danger: #ff4d4f; | ||
| 864 | -$color-muted: #a2a2a2; | ||
| 865 | -$color-card-bg: #f9f9f9; | ||
| 866 | -$radius-sm: 8rpx; | ||
| 867 | -$radius-md: 12rpx; | ||
| 868 | -$radius-lg: 16rpx; | ||
| 869 | -$radius-round: 40rpx; | ||
| 870 | 774 | ||
| 871 | /* 全局容器 */ | 775 | /* 全局容器 */ |
| 872 | .container { | 776 | .container { |
| @@ -888,108 +792,70 @@ $radius-round: 40rpx; | @@ -888,108 +792,70 @@ $radius-round: 40rpx; | ||
| 888 | justify-content: space-between; | 792 | justify-content: space-between; |
| 889 | padding: 24rpx 20rpx; | 793 | padding: 24rpx 20rpx; |
| 890 | background-color: #fff; | 794 | background-color: #fff; |
| 891 | - // position: sticky; | ||
| 892 | top: 0; | 795 | top: 0; |
| 893 | z-index: 10; | 796 | z-index: 10; |
| 894 | gap: 16rpx; | 797 | gap: 16rpx; |
| 895 | -} | ||
| 896 | - | ||
| 897 | -.back-btn { | ||
| 898 | - width: 60rpx; | ||
| 899 | - height: 60rpx; | ||
| 900 | - display: flex; | ||
| 901 | - align-items: center; | ||
| 902 | - justify-content: center; | ||
| 903 | - flex-shrink: 0; | ||
| 904 | -} | ||
| 905 | - | ||
| 906 | -.date-wrapper { | ||
| 907 | - flex-grow: 1; | ||
| 908 | - text-align: left; | ||
| 909 | -} | ||
| 910 | - | ||
| 911 | -.close-btn { | ||
| 912 | - width: 50rpx; | ||
| 913 | - height: 50rpx; | ||
| 914 | - display: flex; | ||
| 915 | - align-items: center; | ||
| 916 | - justify-content: center; | ||
| 917 | - background: #f2f3f5; | ||
| 918 | - border-radius: 50%; | ||
| 919 | - flex-shrink: 0; | ||
| 920 | -} | ||
| 921 | - | ||
| 922 | -.date-text { | ||
| 923 | - font-size: 28rpx; | ||
| 924 | - white-space: nowrap; | ||
| 925 | - overflow: hidden; | ||
| 926 | - text-overflow: ellipsis; | ||
| 927 | -} | ||
| 928 | 798 | ||
| 929 | -.date-note-btn { | ||
| 930 | - display: inline-flex; | ||
| 931 | - align-items: center; | ||
| 932 | - font-size: 28rpx; | ||
| 933 | - color: #333; | ||
| 934 | - background: #e7e7e9; | ||
| 935 | - border-radius: 40rpx; | ||
| 936 | - padding: 12rpx 28rpx; | ||
| 937 | - border: none; | ||
| 938 | - margin-right: 0; | ||
| 939 | - flex-shrink: 0; | ||
| 940 | -} | 799 | + .close-btn { |
| 800 | + width: 50rpx; | ||
| 801 | + height: 50rpx; | ||
| 802 | + display: flex; | ||
| 803 | + align-items: center; | ||
| 804 | + justify-content: center; | ||
| 805 | + background: #f2f3f5; | ||
| 806 | + border-radius: 50%; | ||
| 807 | + flex-shrink: 0; | ||
| 808 | + } | ||
| 941 | 809 | ||
| 942 | -.go-train-btn { | ||
| 943 | - display: flex; | ||
| 944 | - align-items: center; | ||
| 945 | - background: #ffc107; | ||
| 946 | - border-radius: 40rpx; | ||
| 947 | - padding: 12rpx 24rpx; | ||
| 948 | - border: none; | ||
| 949 | - flex-shrink: 0; | ||
| 950 | -} | 810 | + .date-wrapper { |
| 811 | + flex-grow: 1; | ||
| 812 | + text-align: left; | ||
| 813 | + } | ||
| 951 | 814 | ||
| 952 | -.go-train-text { | ||
| 953 | - font-size: 28rpx; | ||
| 954 | - color: #000; | ||
| 955 | - margin-right: 8rpx; | ||
| 956 | - font-weight: 500; | ||
| 957 | -} | 815 | + .date-text { |
| 816 | + font-size: 28rpx; | ||
| 817 | + white-space: nowrap; | ||
| 818 | + overflow: hidden; | ||
| 819 | + text-overflow: ellipsis; | ||
| 820 | + } | ||
| 958 | 821 | ||
| 959 | -.go-train-tag { | ||
| 960 | - font-size: 22rpx; | ||
| 961 | - color: #ffc107; | ||
| 962 | - background: #000; | ||
| 963 | - border-radius: 50%; | ||
| 964 | - padding: 4rpx 8rpx; | ||
| 965 | - font-weight: bold; | ||
| 966 | -} | 822 | + .date-note-btn { |
| 823 | + display: inline-flex; | ||
| 824 | + align-items: center; | ||
| 825 | + font-size: 28rpx; | ||
| 826 | + color: #333; | ||
| 827 | + background: #e7e7e9; | ||
| 828 | + border-radius: 40rpx; | ||
| 829 | + padding: 12rpx 28rpx; | ||
| 830 | + border: none; | ||
| 831 | + margin-right: 0; | ||
| 832 | + flex-shrink: 0; | ||
| 833 | + } | ||
| 967 | 834 | ||
| 968 | -/* 训练来源标签 */ | ||
| 969 | -.source-tag-row { | ||
| 970 | - display: flex; | ||
| 971 | - align-items: center; | ||
| 972 | - padding: 16rpx 24rpx; | ||
| 973 | - margin: 0rpx 20rpx; | ||
| 974 | - border-radius: 12rpx; | ||
| 975 | - font-size: 24rpx; | ||
| 976 | - font-weight: 500; | 835 | + .go-train-btn { |
| 836 | + display: flex; | ||
| 837 | + align-items: center; | ||
| 838 | + background: #ffc107; | ||
| 839 | + border-radius: 40rpx; | ||
| 840 | + padding: 12rpx 24rpx; | ||
| 841 | + border: none; | ||
| 842 | + flex-shrink: 0; | ||
| 977 | 843 | ||
| 978 | - .source-text { | ||
| 979 | - color: #fff; | ||
| 980 | - } | 844 | + .go-train-text { |
| 845 | + font-size: 28rpx; | ||
| 846 | + color: #000; | ||
| 847 | + margin-right: 8rpx; | ||
| 848 | + font-weight: 500; | ||
| 849 | + } | ||
| 981 | 850 | ||
| 982 | - &.source-plan { | ||
| 983 | - background: #3b82f6; /* 蓝色 - 计划 */ | ||
| 984 | - } | ||
| 985 | - &.source-tpl { | ||
| 986 | - background: #8b5cf6; /* 紫色 - 模板 */ | ||
| 987 | - } | ||
| 988 | - &.source-custom { | ||
| 989 | - background: #f59e0b; /* 橙色 - 自定义 */ | ||
| 990 | - } | ||
| 991 | - &.source-free { | ||
| 992 | - background: #10b981; /* 绿色 - 自由 */ | 851 | + .go-train-tag { |
| 852 | + font-size: 22rpx; | ||
| 853 | + color: #ffc107; | ||
| 854 | + background: #000; | ||
| 855 | + border-radius: 50%; | ||
| 856 | + padding: 4rpx 8rpx; | ||
| 857 | + font-weight: bold; | ||
| 858 | + } | ||
| 993 | } | 859 | } |
| 994 | } | 860 | } |
| 995 | 861 | ||
| @@ -1003,102 +869,84 @@ $radius-round: 40rpx; | @@ -1003,102 +869,84 @@ $radius-round: 40rpx; | ||
| 1003 | padding: 32rpx 24rpx; | 869 | padding: 32rpx 24rpx; |
| 1004 | margin: 20rpx; | 870 | margin: 20rpx; |
| 1005 | margin-bottom: 24rpx; | 871 | margin-bottom: 24rpx; |
| 1006 | -} | ||
| 1007 | - | ||
| 1008 | -.plan-header-img { | ||
| 1009 | - display: none; | ||
| 1010 | - | ||
| 1011 | -} | ||
| 1012 | - | ||
| 1013 | -.plan-header-info { | ||
| 1014 | - flex-grow: 1; | ||
| 1015 | - margin-bottom: 32rpx; | ||
| 1016 | -} | ||
| 1017 | - | ||
| 1018 | -.plan-header-top { | ||
| 1019 | - display: flex; | ||
| 1020 | - justify-content: space-between; | ||
| 1021 | - align-items: center; | ||
| 1022 | - margin-bottom: 12rpx; | ||
| 1023 | -} | ||
| 1024 | - | ||
| 1025 | -.plan-heander { | ||
| 1026 | - display: flex; | ||
| 1027 | -} | ||
| 1028 | 872 | ||
| 1029 | -.plan-title { | ||
| 1030 | - font-size: 36rpx; | ||
| 1031 | - font-weight: bold; | ||
| 1032 | - display: block; | ||
| 1033 | - margin-bottom: 12rpx; | ||
| 1034 | - color: #fff; | ||
| 1035 | -} | 873 | + .plan-header-img { |
| 874 | + display: none; | ||
| 875 | + } | ||
| 1036 | 876 | ||
| 1037 | -// 卡片头部的编辑按钮 | ||
| 1038 | -.editButton { | ||
| 1039 | - display: flex; | ||
| 1040 | - align-items: center; | ||
| 1041 | - gap: 4rpx; | ||
| 1042 | - color: #fff; | ||
| 1043 | - font-size: 26rpx; | ||
| 1044 | - opacity: 0.8; | ||
| 1045 | - /* 让文字和图标稍微淡一点,和例图更像 */ | ||
| 1046 | - border: 1rpx solid rgba(255, 255, 255, 0.7); | ||
| 1047 | - border-radius: 25rpx; | ||
| 1048 | - padding: 6rpx 12rpx; | ||
| 1049 | -} | 877 | + .plan-header-info { |
| 878 | + flex-grow: 1; | ||
| 879 | + margin-bottom: 32rpx; | ||
| 880 | + } | ||
| 1050 | 881 | ||
| 882 | + .plan-header-top { | ||
| 883 | + display: flex; | ||
| 884 | + justify-content: space-between; | ||
| 885 | + align-items: center; | ||
| 886 | + margin-bottom: 12rpx; | ||
| 887 | + } | ||
| 1051 | 888 | ||
| 1052 | -.plan-meta { | ||
| 1053 | - font-size: 26rpx; | ||
| 1054 | - color: #ccc; | ||
| 1055 | - display: block; | ||
| 1056 | -} | 889 | + .plan-title { |
| 890 | + font-size: 36rpx; | ||
| 891 | + font-weight: bold; | ||
| 892 | + display: block; | ||
| 893 | + margin-bottom: 12rpx; | ||
| 894 | + color: #fff; | ||
| 895 | + } | ||
| 1057 | 896 | ||
| 1058 | -.plan-header-btns { | ||
| 1059 | - display: flex; | ||
| 1060 | - flex-direction: row; | ||
| 1061 | - align-items: center; | ||
| 1062 | - gap: 20rpx; | ||
| 1063 | - justify-content: flex-start; | ||
| 1064 | -} | 897 | + .editButton { |
| 898 | + display: flex; | ||
| 899 | + align-items: center; | ||
| 900 | + gap: 4rpx; | ||
| 901 | + color: #fff; | ||
| 902 | + font-size: 26rpx; | ||
| 903 | + opacity: 0.8; | ||
| 904 | + border: 1rpx solid rgba(255, 255, 255, 0.7); | ||
| 905 | + border-radius: 25rpx; | ||
| 906 | + padding: 6rpx 12rpx; | ||
| 907 | + } | ||
| 1065 | 908 | ||
| 1066 | -.plan-btn { | ||
| 1067 | - font-size: 26rpx; | ||
| 1068 | - border-radius: 40rpx; | ||
| 1069 | - border: none; | ||
| 1070 | - padding: 12rpx 28rpx; | ||
| 1071 | - line-height: 1; | ||
| 1072 | -} | 909 | + .plan-meta { |
| 910 | + font-size: 26rpx; | ||
| 911 | + color: #ccc; | ||
| 912 | + display: block; | ||
| 913 | + } | ||
| 1073 | 914 | ||
| 1074 | -.more-btn, | ||
| 1075 | -.copy-btn { | ||
| 1076 | - background: #fff; | ||
| 1077 | - color: #000; | ||
| 1078 | - border: none; | ||
| 1079 | -} | 915 | + .plan-header-btns { |
| 916 | + display: flex; | ||
| 917 | + flex-direction: row; | ||
| 918 | + align-items: center; | ||
| 919 | + gap: 20rpx; | ||
| 920 | + justify-content: flex-start; | ||
| 921 | + | ||
| 922 | + .plan-btn { | ||
| 923 | + font-size: 26rpx; | ||
| 924 | + border-radius: 40rpx; | ||
| 925 | + border: none; | ||
| 926 | + padding: 12rpx 28rpx; | ||
| 927 | + line-height: 1; | ||
| 928 | + } | ||
| 1080 | 929 | ||
| 1081 | -.go-train-btn-small { | ||
| 1082 | - color: #000; | ||
| 1083 | - display: flex; | ||
| 1084 | - align-items: center; | ||
| 1085 | - padding: 12rpx 20rpx; | ||
| 1086 | - background: inherit; | ||
| 1087 | -} | 930 | + .more-btn, |
| 931 | + .copy-btn { | ||
| 932 | + background: #fff; | ||
| 933 | + color: #000; | ||
| 934 | + border: none; | ||
| 935 | + } | ||
| 1088 | 936 | ||
| 1089 | -.go-train-text-sm { | ||
| 1090 | - font-size: 24rpx; | ||
| 1091 | - margin-right: 4rpx; | ||
| 1092 | - font-weight: 500; | ||
| 1093 | -} | 937 | + .go-train-btn-small { |
| 938 | + color: #000; | ||
| 939 | + display: flex; | ||
| 940 | + align-items: center; | ||
| 941 | + padding: 12rpx 20rpx; | ||
| 1094 | 942 | ||
| 1095 | -.go-train-tag-sm { | ||
| 1096 | - font-size: 20rpx; | ||
| 1097 | - background: #000; | ||
| 1098 | - color: #ffc107; | ||
| 1099 | - border-radius: 4rpx; | ||
| 1100 | - padding: 2rpx 6rpx; | ||
| 1101 | - font-weight: bold; | 943 | + .go-train-text-sm { |
| 944 | + font-size: 24rpx; | ||
| 945 | + margin-right: 4rpx; | ||
| 946 | + font-weight: 500; | ||
| 947 | + } | ||
| 948 | + } | ||
| 949 | + } | ||
| 1102 | } | 950 | } |
| 1103 | 951 | ||
| 1104 | /* 训练动作列表 */ | 952 | /* 训练动作列表 */ |
| @@ -1114,29 +962,49 @@ $radius-round: 40rpx; | @@ -1114,29 +962,49 @@ $radius-round: 40rpx; | ||
| 1114 | display: flex; | 962 | display: flex; |
| 1115 | flex-direction: column; | 963 | flex-direction: column; |
| 1116 | gap: 12rpx; | 964 | gap: 12rpx; |
| 1117 | -} | ||
| 1118 | - | ||
| 1119 | 965 | ||
| 1120 | -.set-index { | ||
| 1121 | - display: none; | ||
| 1122 | -} | 966 | + .set-content { |
| 967 | + flex-grow: 1; | ||
| 968 | + color: inherit; | ||
| 969 | + display: flex; | ||
| 970 | + justify-content: space-between; | ||
| 971 | + white-space: nowrap; | ||
| 972 | + } | ||
| 1123 | 973 | ||
| 974 | + .set-item { | ||
| 975 | + display: flex; | ||
| 976 | + align-items: center; | ||
| 977 | + font-size: 26rpx; | ||
| 978 | + color: #666; | ||
| 979 | + justify-content: space-between; | ||
| 1124 | 980 | ||
| 981 | + .set-left { | ||
| 982 | + display: flex; | ||
| 983 | + align-items: center; | ||
| 984 | + gap: 8rpx; | ||
| 985 | + } | ||
| 1125 | 986 | ||
| 1126 | -.set-content { | ||
| 1127 | - flex-grow: 1; | ||
| 1128 | - color: inherit; | ||
| 1129 | - display: flex; | ||
| 1130 | - justify-content: space-between; | ||
| 1131 | - white-space: nowrap; | 987 | + .set-right { |
| 988 | + display: flex; | ||
| 989 | + gap: 20rpx; | ||
| 990 | + } | ||
| 1132 | 991 | ||
| 1133 | -} | 992 | + .rest-time { |
| 993 | + font-size: 24rpx; | ||
| 994 | + margin-left: 40rpx; | ||
| 995 | + flex-shrink: 0; | ||
| 996 | + } | ||
| 1134 | 997 | ||
| 1135 | -.detai-data { | ||
| 1136 | - white-space: nowrap; | 998 | + .check { |
| 999 | + font-size: 26rpx; | ||
| 1000 | + color: #333; | ||
| 1001 | + margin-left: 20rpx; | ||
| 1002 | + flex-shrink: 0; | ||
| 1003 | + } | ||
| 1004 | + } | ||
| 1137 | } | 1005 | } |
| 1138 | 1006 | ||
| 1139 | -// 主卡片:横向排列 | 1007 | +/* 主卡片:横向排列 */ |
| 1140 | .action-item { | 1008 | .action-item { |
| 1141 | display: flex; | 1009 | display: flex; |
| 1142 | align-items: flex-start; | 1010 | align-items: flex-start; |
| @@ -1145,153 +1013,71 @@ $radius-round: 40rpx; | @@ -1145,153 +1013,71 @@ $radius-round: 40rpx; | ||
| 1145 | padding: 24rpx 20rpx; | 1013 | padding: 24rpx 20rpx; |
| 1146 | position: relative; | 1014 | position: relative; |
| 1147 | gap: 20rpx; | 1015 | gap: 20rpx; |
| 1148 | -} | ||
| 1149 | - | ||
| 1150 | -// 左侧:序号 + 图片 | ||
| 1151 | -.action-left { | ||
| 1152 | - display: flex; | ||
| 1153 | - align-items: center; | ||
| 1154 | - gap: 16rpx; | ||
| 1155 | - flex-shrink: 0; | ||
| 1156 | -} | ||
| 1157 | - | ||
| 1158 | -// 大序号 | ||
| 1159 | -.action-index { | ||
| 1160 | - font-size: 34rpx; | ||
| 1161 | - font-weight: 600; | ||
| 1162 | - color: #333; | ||
| 1163 | - line-height: 1; | ||
| 1164 | -} | ||
| 1165 | - | ||
| 1166 | -// 动作图片 | ||
| 1167 | -.action-img { | ||
| 1168 | - width: 80rpx; | ||
| 1169 | - height: 80rpx; | ||
| 1170 | - border-radius: 8rpx; | ||
| 1171 | -} | ||
| 1172 | - | ||
| 1173 | -// 中间区域 | ||
| 1174 | -.action-middle { | ||
| 1175 | - flex: 1; | ||
| 1176 | - display: flex; | ||
| 1177 | - flex-direction: column; | ||
| 1178 | -} | ||
| 1179 | - | ||
| 1180 | -// 上半部分:名称 + 重量(左对齐) | ||
| 1181 | -.action-top { | ||
| 1182 | - margin-bottom: 8rpx; | ||
| 1183 | -} | ||
| 1184 | - | ||
| 1185 | -.action-name { | ||
| 1186 | - font-size: 32rpx; | ||
| 1187 | - font-weight: 500; | ||
| 1188 | - color: #111; | ||
| 1189 | - display: block; | ||
| 1190 | -} | ||
| 1191 | - | ||
| 1192 | -.action-totalWeight { | ||
| 1193 | - font-size: 26rpx; | ||
| 1194 | - color: #333; | ||
| 1195 | - margin-top: 6rpx; | ||
| 1196 | - display: block; | ||
| 1197 | -} | ||
| 1198 | - | ||
| 1199 | -// 下半部分:组次行 → 向左对齐图片 ✅ 核心 | ||
| 1200 | -.action-bottom { | ||
| 1201 | - margin-top: 6rpx; | ||
| 1202 | - margin-left: -96rpx; | ||
| 1203 | - /* 往左移动,对齐图片位置 */ | ||
| 1204 | -} | ||
| 1205 | 1016 | ||
| 1017 | + .action-left { | ||
| 1018 | + display: flex; | ||
| 1019 | + align-items: center; | ||
| 1020 | + gap: 16rpx; | ||
| 1021 | + flex-shrink: 0; | ||
| 1022 | + } | ||
| 1206 | 1023 | ||
| 1207 | -.set-item { | ||
| 1208 | - display: flex; | ||
| 1209 | - align-items: center; | ||
| 1210 | - font-size: 26rpx; | ||
| 1211 | - color: #666; | ||
| 1212 | - justify-content: space-between; | ||
| 1213 | -} | 1024 | + .action-index { |
| 1025 | + font-size: 34rpx; | ||
| 1026 | + font-weight: 600; | ||
| 1027 | + color: #333; | ||
| 1028 | + line-height: 1; | ||
| 1029 | + } | ||
| 1214 | 1030 | ||
| 1215 | -.set-left { | ||
| 1216 | - display: flex; | ||
| 1217 | - align-items: center; | ||
| 1218 | - gap: 8rpx; | ||
| 1219 | - /* 控制 indexData、set-content、rest-time 之间的间距 */ | ||
| 1220 | -} | 1031 | + .action-img { |
| 1032 | + width: 80rpx; | ||
| 1033 | + height: 80rpx; | ||
| 1034 | + border-radius: 8rpx; | ||
| 1035 | + } | ||
| 1221 | 1036 | ||
| 1222 | -.set-right { | ||
| 1223 | - display: flex; | ||
| 1224 | - gap: 20rpx; | ||
| 1225 | -} | 1037 | + .action-middle { |
| 1038 | + flex: 1; | ||
| 1039 | + display: flex; | ||
| 1040 | + flex-direction: column; | ||
| 1041 | + } | ||
| 1226 | 1042 | ||
| 1227 | -// 灰色小圆点序号 | ||
| 1228 | -.indexData { | ||
| 1229 | - width: 32rpx; | ||
| 1230 | - height: 32rpx; | ||
| 1231 | - border-radius: 50%; | ||
| 1232 | - background: #f2f3f5; | ||
| 1233 | - font-size: 22rpx; | ||
| 1234 | - color: #999; | ||
| 1235 | - display: inline-flex; | ||
| 1236 | - align-items: center; | ||
| 1237 | - justify-content: center; | ||
| 1238 | - margin-right: 12rpx; | ||
| 1239 | - flex-shrink: 0; | ||
| 1240 | -} | 1043 | + .action-top { |
| 1044 | + margin-bottom: 8rpx; | ||
| 1241 | 1045 | ||
| 1242 | -// .set-content { | ||
| 1243 | -// color: #333; | ||
| 1244 | -// } | 1046 | + .action-name { |
| 1047 | + font-size: 32rpx; | ||
| 1048 | + font-weight: 500; | ||
| 1049 | + color: #111; | ||
| 1050 | + display: block; | ||
| 1051 | + } | ||
| 1245 | 1052 | ||
| 1246 | -.rest-time { | ||
| 1247 | - // margin-left: 100rpx; | ||
| 1248 | - font-size: 24rpx; | ||
| 1249 | - // color: inherit; | ||
| 1250 | - margin-left: 40rpx; | ||
| 1251 | - flex-shrink: 0; | ||
| 1252 | -} | 1053 | + .action-totalWeight { |
| 1054 | + font-size: 26rpx; | ||
| 1055 | + color: #333; | ||
| 1056 | + margin-top: 6rpx; | ||
| 1057 | + display: block; | ||
| 1058 | + } | ||
| 1059 | + } | ||
| 1253 | 1060 | ||
| 1254 | -.check { | ||
| 1255 | - font-size: 26rpx; | ||
| 1256 | - color: #333; | ||
| 1257 | - margin-left: 20rpx; | ||
| 1258 | - /* 这里控制和左边内容的距离,数值你可以自己调 */ | ||
| 1259 | - flex-shrink: 0; | ||
| 1260 | - /* 防止被压缩 */ | ||
| 1261 | -} | 1061 | + .action-bottom { |
| 1062 | + margin-top: 6rpx; | ||
| 1063 | + margin-left: -96rpx; | ||
| 1064 | + } | ||
| 1262 | 1065 | ||
| 1263 | -// 右侧:修改 + 对勾 | ||
| 1264 | -// .action-right { | ||
| 1265 | -// display: flex; | ||
| 1266 | -// flex-direction: column; | ||
| 1267 | -// align-items: center; | ||
| 1268 | -// justify-content: space-between; | ||
| 1269 | -// height: 100%; | ||
| 1270 | -// } | ||
| 1271 | -.action-right { | ||
| 1272 | - flex-shrink: 0; | ||
| 1273 | -} | 1066 | + .action-right { |
| 1067 | + flex-shrink: 0; | ||
| 1274 | 1068 | ||
| 1275 | -.modify-btn { | ||
| 1276 | - font-size: 26rpx; | ||
| 1277 | - color: #333; | ||
| 1278 | - background: #f2f3f5; | ||
| 1279 | - border: none; | ||
| 1280 | - border-radius: 40rpx; | ||
| 1281 | - padding: 8rpx 20rpx; | ||
| 1282 | - line-height: 1; | 1069 | + .modify-btn { |
| 1070 | + font-size: 26rpx; | ||
| 1071 | + color: #333; | ||
| 1072 | + background: #f2f3f5; | ||
| 1073 | + border: none; | ||
| 1074 | + border-radius: 40rpx; | ||
| 1075 | + padding: 8rpx 20rpx; | ||
| 1076 | + line-height: 1; | ||
| 1077 | + } | ||
| 1078 | + } | ||
| 1283 | } | 1079 | } |
| 1284 | 1080 | ||
| 1285 | -// .check-icon { | ||
| 1286 | -// font-size: 30rpx; | ||
| 1287 | -// color: #333; | ||
| 1288 | -// margin-top: 24rpx; | ||
| 1289 | -// } | ||
| 1290 | - | ||
| 1291 | - | ||
| 1292 | - | ||
| 1293 | -//========================= | ||
| 1294 | - | ||
| 1295 | /* 空状态区域 */ | 1081 | /* 空状态区域 */ |
| 1296 | .empty-section { | 1082 | .empty-section { |
| 1297 | flex: 1; | 1083 | flex: 1; |
| @@ -1301,32 +1087,32 @@ $radius-round: 40rpx; | @@ -1301,32 +1087,32 @@ $radius-round: 40rpx; | ||
| 1301 | justify-content: center; | 1087 | justify-content: center; |
| 1302 | padding: 80rpx 40rpx 120rpx; | 1088 | padding: 80rpx 40rpx 120rpx; |
| 1303 | box-sizing: border-box; | 1089 | box-sizing: border-box; |
| 1304 | -} | ||
| 1305 | 1090 | ||
| 1306 | -.empty-img { | ||
| 1307 | - width: 360rpx; | ||
| 1308 | - height: 360rpx; | ||
| 1309 | - margin-bottom: 40rpx; | ||
| 1310 | - transform: scale(3); | ||
| 1311 | -} | 1091 | + .empty-img { |
| 1092 | + width: 360rpx; | ||
| 1093 | + height: 360rpx; | ||
| 1094 | + margin-bottom: 40rpx; | ||
| 1095 | + transform: scale(1.5); | ||
| 1096 | + } | ||
| 1312 | 1097 | ||
| 1313 | -.empty-tip { | ||
| 1314 | - font-size: 32rpx; | ||
| 1315 | - color: #666; | ||
| 1316 | - font-weight: 500; | ||
| 1317 | - margin-bottom: 80rpx; | ||
| 1318 | - letter-spacing: 2rpx; | ||
| 1319 | -} | 1098 | + .empty-tip { |
| 1099 | + font-size: 32rpx; | ||
| 1100 | + color: #666; | ||
| 1101 | + font-weight: 500; | ||
| 1102 | + margin-bottom: 80rpx; | ||
| 1103 | + letter-spacing: 2rpx; | ||
| 1104 | + } | ||
| 1320 | 1105 | ||
| 1321 | -.add-train-btn { | ||
| 1322 | - font-size: 30rpx; | ||
| 1323 | - color: #000; | ||
| 1324 | - background: $color-accent; | ||
| 1325 | - border-radius: $radius-round; | ||
| 1326 | - padding: 24rpx 80rpx; | ||
| 1327 | - border: none; | ||
| 1328 | - font-weight: 500; | ||
| 1329 | - box-shadow: 0 4rpx 12rpx rgba(255, 193, 7, 0.3); | 1106 | + .add-train-btn { |
| 1107 | + font-size: 30rpx; | ||
| 1108 | + color: #000; | ||
| 1109 | + background: $color-accent; | ||
| 1110 | + border-radius: 40rpx; | ||
| 1111 | + padding: 24rpx 80rpx; | ||
| 1112 | + border: none; | ||
| 1113 | + font-weight: 500; | ||
| 1114 | + box-shadow: 0 4rpx 12rpx rgba(255, 193, 7, 0.3); | ||
| 1115 | + } | ||
| 1330 | } | 1116 | } |
| 1331 | 1117 | ||
| 1332 | /* 底部课程类型区域 */ | 1118 | /* 底部课程类型区域 */ |
| @@ -1346,25 +1132,25 @@ $radius-round: 40rpx; | @@ -1346,25 +1132,25 @@ $radius-round: 40rpx; | ||
| 1346 | background: #fff; | 1132 | background: #fff; |
| 1347 | border-radius: 12rpx; | 1133 | border-radius: 12rpx; |
| 1348 | padding: 30rpx 0; | 1134 | padding: 30rpx 0; |
| 1349 | -} | ||
| 1350 | 1135 | ||
| 1351 | -.course-icon { | ||
| 1352 | - width: 100rpx; | ||
| 1353 | - height: 100rpx; | ||
| 1354 | - border-radius: 50%; | ||
| 1355 | - margin-bottom: 16rpx; | ||
| 1356 | -} | 1136 | + .course-icon { |
| 1137 | + width: 100rpx; | ||
| 1138 | + height: 100rpx; | ||
| 1139 | + border-radius: 50%; | ||
| 1140 | + margin-bottom: 16rpx; | ||
| 1141 | + } | ||
| 1357 | 1142 | ||
| 1358 | -.course-title { | ||
| 1359 | - font-size: 28rpx; | ||
| 1360 | - color: #333; | ||
| 1361 | - margin-bottom: 8rpx; | ||
| 1362 | - font-weight: 500; | ||
| 1363 | -} | 1143 | + .course-title { |
| 1144 | + font-size: 28rpx; | ||
| 1145 | + color: #333; | ||
| 1146 | + margin-bottom: 8rpx; | ||
| 1147 | + font-weight: 500; | ||
| 1148 | + } | ||
| 1364 | 1149 | ||
| 1365 | -.course-desc { | ||
| 1366 | - font-size: 24rpx; | ||
| 1367 | - color: #999; | 1150 | + .course-desc { |
| 1151 | + font-size: 24rpx; | ||
| 1152 | + color: #999; | ||
| 1153 | + } | ||
| 1368 | } | 1154 | } |
| 1369 | 1155 | ||
| 1370 | /* 添加自助训练底部弹窗 */ | 1156 | /* 添加自助训练底部弹窗 */ |
| @@ -1389,14 +1175,6 @@ $radius-round: 40rpx; | @@ -1389,14 +1175,6 @@ $radius-round: 40rpx; | ||
| 1389 | box-sizing: border-box; | 1175 | box-sizing: border-box; |
| 1390 | } | 1176 | } |
| 1391 | 1177 | ||
| 1392 | -// .popup-indicator { | ||
| 1393 | -// width: 80rpx; | ||
| 1394 | -// height: 8rpx; | ||
| 1395 | -// background: #ddd; | ||
| 1396 | -// border-radius: 4rpx; | ||
| 1397 | -// margin: 0 auto 30rpx; | ||
| 1398 | -// } | ||
| 1399 | - | ||
| 1400 | .popup-title { | 1178 | .popup-title { |
| 1401 | font-size: 34rpx; | 1179 | font-size: 34rpx; |
| 1402 | color: #333; | 1180 | color: #333; |
| @@ -1410,46 +1188,41 @@ $radius-round: 40rpx; | @@ -1410,46 +1188,41 @@ $radius-round: 40rpx; | ||
| 1410 | display: flex; | 1188 | display: flex; |
| 1411 | flex-direction: column; | 1189 | flex-direction: column; |
| 1412 | gap: 20rpx; | 1190 | gap: 20rpx; |
| 1413 | -} | ||
| 1414 | 1191 | ||
| 1415 | -.popup-option-btn { | ||
| 1416 | - width: 100%; | ||
| 1417 | - height: 80rpx; | ||
| 1418 | - font-size: 30rpx; | ||
| 1419 | - // color: #ffffff; | ||
| 1420 | - // background: #f5f5f5; | ||
| 1421 | - border: none; | ||
| 1422 | - // border-radius: 12rpx; | ||
| 1423 | - display: flex; | ||
| 1424 | - flex-direction: column; | ||
| 1425 | - align-items: center; | ||
| 1426 | - justify-content: center; | ||
| 1427 | - gap: 15rpx; | 1192 | + .popup-option-btn { |
| 1193 | + width: 100%; | ||
| 1194 | + height: 80rpx; | ||
| 1195 | + font-size: 30rpx; | ||
| 1196 | + border: none; | ||
| 1197 | + display: flex; | ||
| 1198 | + flex-direction: column; | ||
| 1199 | + align-items: center; | ||
| 1200 | + justify-content: center; | ||
| 1201 | + gap: 15rpx; | ||
| 1428 | 1202 | ||
| 1429 | - .small { | ||
| 1430 | - font-size: 18rpx; | 1203 | + .small { |
| 1204 | + font-size: 18rpx; | ||
| 1205 | + } | ||
| 1431 | } | 1206 | } |
| 1432 | - | ||
| 1433 | } | 1207 | } |
| 1434 | 1208 | ||
| 1435 | -// 更多弹窗 | ||
| 1436 | -/* ========== 更多弹窗样式 ========== */ | ||
| 1437 | -:deep(.up-popup__content) { | ||
| 1438 | - border-radius: 10rpx 10rpx 0 0; | ||
| 1439 | - background: #f5f5f5; | ||
| 1440 | - padding: 30rpx 20rpx; | ||
| 1441 | -} | 1209 | +/* 更多弹窗 */ |
| 1210 | +// ::deep(.up-popup__content) { | ||
| 1211 | +// border-radius: 10rpx 10rpx 0 0; | ||
| 1212 | +// background: #f5f5f5; | ||
| 1213 | +// padding: 30rpx 20rpx; | ||
| 1214 | +// } | ||
| 1442 | 1215 | ||
| 1443 | .popup-header { | 1216 | .popup-header { |
| 1444 | text-align: center; | 1217 | text-align: center; |
| 1445 | margin-top: 30rpx; | 1218 | margin-top: 30rpx; |
| 1446 | margin-bottom: 30rpx; | 1219 | margin-bottom: 30rpx; |
| 1447 | -} | ||
| 1448 | 1220 | ||
| 1449 | -.popup-title { | ||
| 1450 | - font-size: 34rpx; | ||
| 1451 | - font-weight: 600; | ||
| 1452 | - color: #111; | 1221 | + .popup-title { |
| 1222 | + font-size: 34rpx; | ||
| 1223 | + font-weight: 600; | ||
| 1224 | + color: #111; | ||
| 1225 | + } | ||
| 1453 | } | 1226 | } |
| 1454 | 1227 | ||
| 1455 | .popup-list { | 1228 | .popup-list { |
| @@ -1464,15 +1237,23 @@ $radius-round: 40rpx; | @@ -1464,15 +1237,23 @@ $radius-round: 40rpx; | ||
| 1464 | padding: 24rpx 20rpx; | 1237 | padding: 24rpx 20rpx; |
| 1465 | background: transparent; | 1238 | background: transparent; |
| 1466 | gap: 16rpx; | 1239 | gap: 16rpx; |
| 1467 | -} | ||
| 1468 | 1240 | ||
| 1469 | -.popup-item:not(:last-child) { | ||
| 1470 | - border-bottom: 1rpx solid #eee; | ||
| 1471 | -} | 1241 | + &:not(:last-child) { |
| 1242 | + border-bottom: 1rpx solid #eee; | ||
| 1243 | + } | ||
| 1472 | 1244 | ||
| 1473 | -.item-text { | ||
| 1474 | - font-size: 30rpx; | ||
| 1475 | - color: #333; | 1245 | + .item-text { |
| 1246 | + font-size: 30rpx; | ||
| 1247 | + color: #333; | ||
| 1248 | + } | ||
| 1249 | + | ||
| 1250 | + &.delete-item { | ||
| 1251 | + background: #fff; | ||
| 1252 | + | ||
| 1253 | + .item-text { | ||
| 1254 | + color: #ff4d4f; | ||
| 1255 | + } | ||
| 1256 | + } | ||
| 1476 | } | 1257 | } |
| 1477 | 1258 | ||
| 1478 | .popup-divider { | 1259 | .popup-divider { |
| @@ -1481,56 +1262,46 @@ $radius-round: 40rpx; | @@ -1481,56 +1262,46 @@ $radius-round: 40rpx; | ||
| 1481 | margin: 20rpx 0; | 1262 | margin: 20rpx 0; |
| 1482 | } | 1263 | } |
| 1483 | 1264 | ||
| 1484 | -.delete-item { | ||
| 1485 | - background: #fff; | ||
| 1486 | -} | ||
| 1487 | - | ||
| 1488 | -.delete-text { | ||
| 1489 | - color: #ff4d4f; | ||
| 1490 | -} | ||
| 1491 | - | ||
| 1492 | -/* 按钮通用重置 */ | ||
| 1493 | -button { | ||
| 1494 | - line-height: 1; | ||
| 1495 | -} | 1265 | +.container { |
| 1266 | + /* 按钮通用重置 */ | ||
| 1267 | + button { | ||
| 1268 | + line-height: 1; | ||
| 1496 | 1269 | ||
| 1497 | -button::after { | ||
| 1498 | - border: none; | 1270 | + &::after { |
| 1271 | + border: none; | ||
| 1272 | + } | ||
| 1273 | + } | ||
| 1499 | } | 1274 | } |
| 1500 | 1275 | ||
| 1501 | - | ||
| 1502 | /* 训记切换标签 */ | 1276 | /* 训记切换标签 */ |
| 1503 | .plan-tabs { | 1277 | .plan-tabs { |
| 1504 | white-space: nowrap; | 1278 | white-space: nowrap; |
| 1505 | padding: 16rpx 20rpx; | 1279 | padding: 16rpx 20rpx; |
| 1506 | background: #fff; | 1280 | background: #fff; |
| 1507 | - | ||
| 1508 | - // 给内部的标签留出间距,同时让它不换行 | ||
| 1509 | - // display: flex; | ||
| 1510 | gap: 16rpx; | 1281 | gap: 16rpx; |
| 1511 | -} | ||
| 1512 | 1282 | ||
| 1513 | -.tab-btn { | ||
| 1514 | - display: inline-block; | ||
| 1515 | - padding: 10rpx 24rpx; | ||
| 1516 | - border-radius: 32rpx; | ||
| 1517 | - font-size: 26rpx; | ||
| 1518 | - background: #f2f2f2; | ||
| 1519 | - border: none; | ||
| 1520 | - flex-shrink: 0; | ||
| 1521 | - white-space: nowrap; | ||
| 1522 | -} | 1283 | + .tab-btn { |
| 1284 | + display: inline-block; | ||
| 1285 | + padding: 10rpx 24rpx; | ||
| 1286 | + border-radius: 32rpx; | ||
| 1287 | + font-size: 26rpx; | ||
| 1288 | + background: #f2f2f2; | ||
| 1289 | + border: none; | ||
| 1290 | + flex-shrink: 0; | ||
| 1291 | + white-space: nowrap; | ||
| 1523 | 1292 | ||
| 1524 | -.tab-btn.active { | ||
| 1525 | - background: #000; | ||
| 1526 | - color: #fff; | 1293 | + &.active { |
| 1294 | + background: #000; | ||
| 1295 | + color: #fff; | ||
| 1296 | + } | ||
| 1297 | + } | ||
| 1527 | } | 1298 | } |
| 1528 | 1299 | ||
| 1529 | -/* ========== 超级组样式 ========== */ | 1300 | +/* 超级组样式 */ |
| 1530 | .superset-wrapper { | 1301 | .superset-wrapper { |
| 1531 | margin-bottom: 20rpx; | 1302 | margin-bottom: 20rpx; |
| 1532 | background: $color-white; | 1303 | background: $color-white; |
| 1533 | - border-radius: $radius-md; | 1304 | + border-radius: 12rpx; |
| 1534 | overflow: hidden; | 1305 | overflow: hidden; |
| 1535 | 1306 | ||
| 1536 | .super-head { | 1307 | .super-head { |
| @@ -1613,9 +1384,8 @@ button::after { | @@ -1613,9 +1384,8 @@ button::after { | ||
| 1613 | width: 37rpx; | 1384 | width: 37rpx; |
| 1614 | height: 37rpx; | 1385 | height: 37rpx; |
| 1615 | text-align: center; | 1386 | text-align: center; |
| 1616 | - flex-shrink: 0; // /* 不被父容器挤压变形,保持固定大小 */ | 1387 | + flex-shrink: 0; |
| 1617 | border-radius: 50%; | 1388 | border-radius: 50%; |
| 1618 | - // line-height: 60rpx; | ||
| 1619 | } | 1389 | } |
| 1620 | 1390 | ||
| 1621 | .set-column { | 1391 | .set-column { |
| @@ -1624,23 +1394,20 @@ button::after { | @@ -1624,23 +1394,20 @@ button::after { | ||
| 1624 | } | 1394 | } |
| 1625 | 1395 | ||
| 1626 | .action-in-set { | 1396 | .action-in-set { |
| 1627 | - // display: flex; | ||
| 1628 | - // flex-direction: column; | ||
| 1629 | width: 100%; | 1397 | width: 100%; |
| 1630 | display: flex; | 1398 | display: flex; |
| 1631 | align-items: center; | 1399 | align-items: center; |
| 1632 | margin-bottom: 8rpx; | 1400 | margin-bottom: 8rpx; |
| 1633 | - } | ||
| 1634 | 1401 | ||
| 1635 | - .action-in-set:last-child { | ||
| 1636 | - margin-bottom: 0; | 1402 | + &:last-child { |
| 1403 | + margin-bottom: 0; | ||
| 1404 | + } | ||
| 1637 | } | 1405 | } |
| 1638 | 1406 | ||
| 1639 | .action-letter { | 1407 | .action-letter { |
| 1640 | display: inline-block; | 1408 | display: inline-block; |
| 1641 | font-size: 26rpx; | 1409 | font-size: 26rpx; |
| 1642 | color: #666; | 1410 | color: #666; |
| 1643 | - // width: 35rpx; | ||
| 1644 | white-space: nowrap; | 1411 | white-space: nowrap; |
| 1645 | margin-right: 12rpx; | 1412 | margin-right: 12rpx; |
| 1646 | flex-shrink: 0; | 1413 | flex-shrink: 0; |
| @@ -1650,11 +1417,14 @@ button::after { | @@ -1650,11 +1417,14 @@ button::after { | ||
| 1650 | font-size: 26rpx; | 1417 | font-size: 26rpx; |
| 1651 | color: inherit; | 1418 | color: inherit; |
| 1652 | flex: 1; | 1419 | flex: 1; |
| 1420 | + | ||
| 1421 | + .detai-data { | ||
| 1422 | + white-space: nowrap; | ||
| 1423 | + } | ||
| 1653 | } | 1424 | } |
| 1654 | 1425 | ||
| 1655 | .rest-time { | 1426 | .rest-time { |
| 1656 | font-size: 24rpx; | 1427 | font-size: 24rpx; |
| 1657 | - // color: inherit; | ||
| 1658 | margin-left: 10rpx; | 1428 | margin-left: 10rpx; |
| 1659 | flex-shrink: 0; | 1429 | flex-shrink: 0; |
| 1660 | } | 1430 | } |
| @@ -1665,44 +1435,43 @@ button::after { | @@ -1665,44 +1435,43 @@ button::after { | ||
| 1665 | margin-left: 275rpx !important; | 1435 | margin-left: 275rpx !important; |
| 1666 | flex-shrink: 0; | 1436 | flex-shrink: 0; |
| 1667 | } | 1437 | } |
| 1668 | -} | ||
| 1669 | 1438 | ||
| 1670 | -.superset-title { | ||
| 1671 | - font-size: 28rpx; | ||
| 1672 | - font-weight: bold; | ||
| 1673 | - padding: 12rpx 20rpx; | ||
| 1674 | - background: #fff6cc; | ||
| 1675 | - border-radius: 12rpx; | ||
| 1676 | - margin-bottom: 12rpx; | 1439 | + .superset-title { |
| 1440 | + font-size: 28rpx; | ||
| 1441 | + font-weight: bold; | ||
| 1442 | + padding: 12rpx 20rpx; | ||
| 1443 | + background: #fff6cc; | ||
| 1444 | + border-radius: 12rpx; | ||
| 1445 | + margin-bottom: 12rpx; | ||
| 1446 | + } | ||
| 1677 | } | 1447 | } |
| 1678 | 1448 | ||
| 1679 | -// 日程备注 | 1449 | +/* 日程备注 */ |
| 1680 | .noteContent { | 1450 | .noteContent { |
| 1681 | display: flex; | 1451 | display: flex; |
| 1682 | align-items: flex-start; | 1452 | align-items: flex-start; |
| 1683 | gap: 12rpx; | 1453 | gap: 12rpx; |
| 1684 | padding: 20rpx; | 1454 | padding: 20rpx; |
| 1685 | background: $color-white; | 1455 | background: $color-white; |
| 1686 | -} | ||
| 1687 | 1456 | ||
| 1688 | -.noteTitle { | ||
| 1689 | - font-size: 26rpx; | ||
| 1690 | - color: #666; | ||
| 1691 | - flex-shrink: 0; | ||
| 1692 | - width: 150rpx; | ||
| 1693 | -} | 1457 | + .noteTitle { |
| 1458 | + font-size: 26rpx; | ||
| 1459 | + color: #666; | ||
| 1460 | + flex-shrink: 0; | ||
| 1461 | + width: 150rpx; | ||
| 1462 | + } | ||
| 1694 | 1463 | ||
| 1695 | -.noteTags { | ||
| 1696 | - flex: 1; | ||
| 1697 | - display: flex; | ||
| 1698 | - flex-wrap: wrap; | ||
| 1699 | - gap: 12rpx; | 1464 | + .noteTags { |
| 1465 | + flex: 1; | ||
| 1466 | + display: flex; | ||
| 1467 | + flex-wrap: wrap; | ||
| 1468 | + gap: 12rpx; | ||
| 1469 | + } | ||
| 1700 | } | 1470 | } |
| 1701 | 1471 | ||
| 1702 | -// u-tag 微调 | ||
| 1703 | -:deep(.noteTag) { | ||
| 1704 | - width: auto !important; | ||
| 1705 | - font-size: 24rpx; | ||
| 1706 | - border-radius: 10rpx; | ||
| 1707 | -} | ||
| 1708 | -</style> | ||
| 1472 | +// ::deep(.noteTag) { | ||
| 1473 | +// width: auto !important; | ||
| 1474 | +// font-size: 24rpx; | ||
| 1475 | +// border-radius: 10rpx; | ||
| 1476 | +// } | ||
| 1477 | +</style> |
| @@ -25,7 +25,7 @@ | @@ -25,7 +25,7 @@ | ||
| 25 | 25 | ||
| 26 | <script setup> | 26 | <script setup> |
| 27 | import dayjs from 'dayjs'; | 27 | import dayjs from 'dayjs'; |
| 28 | -import { ref, computed, watch } from 'vue'; | 28 | +import { ref, computed, watch, onMounted } from 'vue'; |
| 29 | 29 | ||
| 30 | const emit = defineEmits(['dateTypeChange', 'dateRangeChange']); | 30 | const emit = defineEmits(['dateTypeChange', 'dateRangeChange']); |
| 31 | 31 | ||
| @@ -102,6 +102,12 @@ watch( | @@ -102,6 +102,12 @@ watch( | ||
| 102 | }, | 102 | }, |
| 103 | { immediate: true }, | 103 | { immediate: true }, |
| 104 | ); | 104 | ); |
| 105 | + | ||
| 106 | +// 首次挂载时补发初始 dateTypeChange,确保父组件 handleTypeChange 被调用, | ||
| 107 | +// 解决首次进入页面"周"数据不显示的问题(与手动切换行为保持一致)。 | ||
| 108 | +onMounted(() => { | ||
| 109 | + emit('dateTypeChange', currentDateType.value); | ||
| 110 | +}); | ||
| 105 | </script> | 111 | </script> |
| 106 | 112 | ||
| 107 | <style scoped lang="scss"> | 113 | <style scoped lang="scss"> |
| @@ -57,6 +57,7 @@ | @@ -57,6 +57,7 @@ | ||
| 57 | 57 | ||
| 58 | <script setup> | 58 | <script setup> |
| 59 | import { ref, onMounted, watch } from 'vue'; | 59 | import { ref, onMounted, watch } from 'vue'; |
| 60 | +import dayjs from 'dayjs'; | ||
| 60 | import QueryPlanApi from '@/sheep/api/plan/queryplan'; | 61 | import QueryPlanApi from '@/sheep/api/plan/queryplan'; |
| 61 | import WodeJihuaLibiaoTancuang from '@/pages/xunji/components/wode-jihua-libiao-tancuang.vue' | 62 | import WodeJihuaLibiaoTancuang from '@/pages/xunji/components/wode-jihua-libiao-tancuang.vue' |
| 62 | import { useTrainingStore } from '@/sheep/store/trainingStore' | 63 | import { useTrainingStore } from '@/sheep/store/trainingStore' |
| @@ -88,9 +89,13 @@ const props = defineProps({ | @@ -88,9 +89,13 @@ const props = defineProps({ | ||
| 88 | isMyPlan: { | 89 | isMyPlan: { |
| 89 | type: Boolean, | 90 | type: Boolean, |
| 90 | default: false | 91 | default: false |
| 92 | + }, | ||
| 93 | + selectedDate: { | ||
| 94 | + type: String, | ||
| 95 | + default: () => dayjs().format('YYYY-MM-DD') | ||
| 91 | } | 96 | } |
| 92 | }) | 97 | }) |
| 93 | -const emit = defineEmits(['update:visible', 'getPlanListLength']) | 98 | +const emit = defineEmits(['update:visible', 'getPlanListLength', 'success']) |
| 94 | 99 | ||
| 95 | // 计划详情 | 100 | // 计划详情 |
| 96 | const MyPlanDetail = ref({}) | 101 | const MyPlanDetail = ref({}) |
| @@ -135,21 +140,21 @@ const onSelectPlan = (planId) => { | @@ -135,21 +140,21 @@ const onSelectPlan = (planId) => { | ||
| 135 | }, 500) | 140 | }, 500) |
| 136 | } | 141 | } |
| 137 | 142 | ||
| 138 | -// ====================== 添加到今日 ====================== | 143 | +// ====================== 添加到选中日期 ====================== |
| 139 | const addTemplateToToday = async (templateId) => { | 144 | const addTemplateToToday = async (templateId) => { |
| 140 | if (!templateId) { | 145 | if (!templateId) { |
| 141 | uni.showToast({ title: '未找到训练模板', icon: 'none' }); | 146 | uni.showToast({ title: '未找到训练模板', icon: 'none' }); |
| 142 | return; | 147 | return; |
| 143 | } | 148 | } |
| 144 | - const todayDate = new Date().toISOString().split('T')[0]; | ||
| 145 | const reqData = { | 149 | const reqData = { |
| 146 | id: templateId, | 150 | id: templateId, |
| 147 | - trainDateList: [todayDate] | 151 | + trainDateList: [props.selectedDate] |
| 148 | }; | 152 | }; |
| 149 | try { | 153 | try { |
| 150 | const res = await QueryPlanApi.addPlanToCalendar(reqData); | 154 | const res = await QueryPlanApi.addPlanToCalendar(reqData); |
| 151 | if (res.code === 0) { | 155 | if (res.code === 0) { |
| 152 | - uni.showToast({ title: '添加到今日成功', icon: 'success' }); | 156 | + uni.showToast({ title: '添加成功', icon: 'success' }); |
| 157 | + emit('success'); | ||
| 153 | emit('update:visible', false); | 158 | emit('update:visible', false); |
| 154 | } else { | 159 | } else { |
| 155 | uni.showToast({ title: res.msg || '添加失败', icon: 'none' }); | 160 | uni.showToast({ title: res.msg || '添加失败', icon: 'none' }); |
| @@ -4,7 +4,7 @@ | @@ -4,7 +4,7 @@ | ||
| 4 | <view class="top-bar"> | 4 | <view class="top-bar"> |
| 5 | <view class="search-wrapper"> | 5 | <view class="search-wrapper"> |
| 6 | <up-search v-model="searchKeyword" placeholder="搜索动作名称" :showAction="false" shape="round" bgColor="#f3f4f6" | 6 | <up-search v-model="searchKeyword" placeholder="搜索动作名称" :showAction="false" shape="round" bgColor="#f3f4f6" |
| 7 | - placeholderColor="#9ca3af" searchIconColor="#6b7280" @clear="searchKeyword = ''" /> | 7 | + placeholderColor="#9ca3af" searchIconColor="#6b7280" @search="handleSearch" @clear="handleClearSearch" /> |
| 8 | </view> | 8 | </view> |
| 9 | 9 | ||
| 10 | <view class="add-btn-wrapper"> | 10 | <view class="add-btn-wrapper"> |
| @@ -36,13 +36,16 @@ | @@ -36,13 +36,16 @@ | ||
| 36 | <view class="layout-container"> | 36 | <view class="layout-container"> |
| 37 | <!-- 左侧一级分类导航 --> | 37 | <!-- 左侧一级分类导航 --> |
| 38 | <scroll-view scroll-y class="left-nav" :show-scrollbar="false"> | 38 | <scroll-view scroll-y class="left-nav" :show-scrollbar="false"> |
| 39 | - <view class="nav-item" :class="{ active: activeNav === 'collect' }" @click="handleCollectClick"> | ||
| 40 | - <up-icon name="star-fill" size="14" :color="activeNav === 'collect' ? '#10b981' : '#9ca3af'"></up-icon> | ||
| 41 | - <text class="nav-text">收藏</text> | ||
| 42 | - </view> | 39 | + <template v-if="!isSearchMode"> |
| 40 | + <view class="nav-item" :class="{ active: activeNav === 'collect' }" @click="handleCollectClick"> | ||
| 41 | + <up-icon name="star-fill" size="14" :color="activeNav === 'collect' ? '#10b981' : '#9ca3af'"></up-icon> | ||
| 42 | + <text class="nav-text">收藏</text> | ||
| 43 | + </view> | ||
| 44 | + </template> | ||
| 43 | 45 | ||
| 44 | - <view v-for="nav in navItems" :key="nav.id" class="nav-item" :class="{ active: activeNav === nav.id }" | ||
| 45 | - @click="switchNav(nav.id)"> | 46 | + <view v-for="nav in displayNavItems" :key="nav.id" class="nav-item" |
| 47 | + :class="{ active: isSearchMode ? activeSearchCategoryId === nav.id : activeNav === nav.id }" | ||
| 48 | + @click="handleNavClick(nav.id)"> | ||
| 46 | <text class="nav-text">{{ nav.name }}</text> | 49 | <text class="nav-text">{{ nav.name }}</text> |
| 47 | </view> | 50 | </view> |
| 48 | </scroll-view> | 51 | </scroll-view> |
| @@ -142,28 +145,30 @@ const searchKeyword = ref(''); | @@ -142,28 +145,30 @@ const searchKeyword = ref(''); | ||
| 142 | const addShow = ref(false); | 145 | const addShow = ref(false); |
| 143 | const actionDetailRef = ref(null); | 146 | const actionDetailRef = ref(null); |
| 144 | 147 | ||
| 148 | +// 搜索模式状态 | ||
| 149 | +const isSearchMode = computed(() => !!searchKeyword.value.trim()); | ||
| 150 | +const searchCategoryList = ref([]); | ||
| 151 | +const activeSearchCategoryId = ref(null); | ||
| 152 | +const searchCategoryData = ref([]); | ||
| 153 | + | ||
| 154 | +// 左栏导航项:搜索模式显示部位列表,正常模式显示分类列表 | ||
| 155 | +const displayNavItems = computed(() => { | ||
| 156 | + return isSearchMode.value ? searchCategoryList.value : navItems.value; | ||
| 157 | +}); | ||
| 158 | + | ||
| 159 | +// 右栏动作列表:搜索模式返回选中部位的器械分组,正常模式直接返回 | ||
| 145 | const displayExercises = computed(() => { | 160 | const displayExercises = computed(() => { |
| 146 | - if (!searchKeyword.value.trim()) { | ||
| 147 | - return exercises.value; | 161 | + if (isSearchMode.value && activeSearchCategoryId.value) { |
| 162 | + const category = searchCategoryData.value.find(item => item.categoryId === activeSearchCategoryId.value); | ||
| 163 | + return category ? category.equipments : []; | ||
| 148 | } | 164 | } |
| 149 | - const kw = searchKeyword.value.toLowerCase().trim(); | ||
| 150 | - | ||
| 151 | - return exercises.value | ||
| 152 | - .map((group) => { | ||
| 153 | - const filtered = (group.exercises || []).filter((ex) => | ||
| 154 | - ex.name && ex.name.toLowerCase().includes(kw) | ||
| 155 | - ); | ||
| 156 | - return { | ||
| 157 | - ...group, | ||
| 158 | - exercises: filtered, | ||
| 159 | - }; | ||
| 160 | - }) | ||
| 161 | - .filter((group) => group.exercises && group.exercises.length > 0); | 165 | + return exercises.value; |
| 162 | }); | 166 | }); |
| 163 | 167 | ||
| 164 | const handlePartClick = async (id) => { | 168 | const handlePartClick = async (id) => { |
| 165 | try { | 169 | try { |
| 166 | activeMotionPart.value = id; | 170 | activeMotionPart.value = id; |
| 171 | + searchKeyword.value = ''; | ||
| 167 | const exerciseRes = await ExercisesApi.getexercises({ | 172 | const exerciseRes = await ExercisesApi.getexercises({ |
| 168 | categoriesId: activeNav.value, | 173 | categoriesId: activeNav.value, |
| 169 | subCategoriesId: id, | 174 | subCategoriesId: id, |
| @@ -174,6 +179,60 @@ const handlePartClick = async (id) => { | @@ -174,6 +179,60 @@ const handlePartClick = async (id) => { | ||
| 174 | } | 179 | } |
| 175 | }; | 180 | }; |
| 176 | 181 | ||
| 182 | +// 服务端搜索动作 | ||
| 183 | +const handleSearch = async () => { | ||
| 184 | + const keyword = searchKeyword.value.trim(); | ||
| 185 | + if (!keyword) return; | ||
| 186 | + | ||
| 187 | + // 收藏Tab和超级组Tab不支持关键词搜索 | ||
| 188 | + if (activeNav.value === 'collect' || activeNav.value === 'super') return; | ||
| 189 | + | ||
| 190 | + try { | ||
| 191 | + await refreshCategories(); | ||
| 192 | + const exerciseRes = await ExercisesApi.getExercisesByCategory({ | ||
| 193 | + name: keyword, | ||
| 194 | + }); | ||
| 195 | + const data = exerciseRes.data || []; | ||
| 196 | + searchCategoryData.value = data; | ||
| 197 | + | ||
| 198 | + // 搜索结果按部位分组,提取部位列表作为左栏导航 | ||
| 199 | + searchCategoryList.value = data.map(item => ({ | ||
| 200 | + id: item.categoryId, | ||
| 201 | + name: item.categoryName, | ||
| 202 | + })); | ||
| 203 | + // 默认选中第一个部位 | ||
| 204 | + activeSearchCategoryId.value = searchCategoryList.value.length > 0 ? searchCategoryList.value[0].id : null; | ||
| 205 | + } catch (e) { | ||
| 206 | + console.error('搜索动作失败:', e); | ||
| 207 | + } | ||
| 208 | +}; | ||
| 209 | + | ||
| 210 | +// 清空搜索,重新加载当前分类数据 | ||
| 211 | +const handleClearSearch = async () => { | ||
| 212 | + searchKeyword.value = ''; | ||
| 213 | + searchCategoryList.value = []; | ||
| 214 | + activeSearchCategoryId.value = null; | ||
| 215 | + searchCategoryData.value = []; | ||
| 216 | + | ||
| 217 | + if (activeNav.value === 'collect') { | ||
| 218 | + loadCollectList(); | ||
| 219 | + } else if (activeNav.value === 'super') { | ||
| 220 | + // 超级组不需要重新加载 | ||
| 221 | + } else if (activeNav.value) { | ||
| 222 | + await refreshCategories(); | ||
| 223 | + loadExercises(activeNav.value); | ||
| 224 | + } | ||
| 225 | +}; | ||
| 226 | + | ||
| 227 | +// 左栏导航点击:搜索模式按部位筛选,正常模式切换分类 | ||
| 228 | +const handleNavClick = (id) => { | ||
| 229 | + if (isSearchMode.value) { | ||
| 230 | + activeSearchCategoryId.value = id; | ||
| 231 | + } else { | ||
| 232 | + switchNav(id); | ||
| 233 | + } | ||
| 234 | +}; | ||
| 235 | + | ||
| 177 | const handleCollectClick = () => { | 236 | const handleCollectClick = () => { |
| 178 | activeNav.value = 'collect'; | 237 | activeNav.value = 'collect'; |
| 179 | activeMotionPart.value = ''; | 238 | activeMotionPart.value = ''; |
| @@ -203,20 +262,24 @@ const loadSuperFavoriteList = async () => { | @@ -203,20 +262,24 @@ const loadSuperFavoriteList = async () => { | ||
| 203 | } | 262 | } |
| 204 | }; | 263 | }; |
| 205 | 264 | ||
| 206 | -// 加载大类 | ||
| 207 | -const loadCategories = async () => { | 265 | +// 仅刷新分类列表,不切换当前选中分类 |
| 266 | +const refreshCategories = async () => { | ||
| 208 | try { | 267 | try { |
| 209 | await actionStore.getloadCategories(); | 268 | await actionStore.getloadCategories(); |
| 210 | navItems.value = actionStore.showCategories; | 269 | navItems.value = actionStore.showCategories; |
| 211 | - | ||
| 212 | - if (navItems.value.length > 0) { | ||
| 213 | - switchNav(navItems.value[0].id); | ||
| 214 | - } | ||
| 215 | } catch (error) { | 270 | } catch (error) { |
| 216 | console.error('获取分类失败:', error); | 271 | console.error('获取分类失败:', error); |
| 217 | } | 272 | } |
| 218 | }; | 273 | }; |
| 219 | 274 | ||
| 275 | +// 加载大类(首次加载,自动切换到第一个分类) | ||
| 276 | +const loadCategories = async () => { | ||
| 277 | + await refreshCategories(); | ||
| 278 | + if (navItems.value.length > 0) { | ||
| 279 | + switchNav(navItems.value[0].id); | ||
| 280 | + } | ||
| 281 | +}; | ||
| 282 | + | ||
| 220 | // 加载动作列表 | 283 | // 加载动作列表 |
| 221 | const loadExercises = async (categoriesId) => { | 284 | const loadExercises = async (categoriesId) => { |
| 222 | try { | 285 | try { |
| @@ -374,7 +437,7 @@ onMounted(() => { | @@ -374,7 +437,7 @@ onMounted(() => { | ||
| 374 | .left-nav { | 437 | .left-nav { |
| 375 | width: 180rpx; | 438 | width: 180rpx; |
| 376 | height: 88%; | 439 | height: 88%; |
| 377 | - | 440 | + |
| 378 | background-color: #f9fafb; | 441 | background-color: #f9fafb; |
| 379 | border-right: 1rpx solid #f3f4f6; | 442 | border-right: 1rpx solid #f3f4f6; |
| 380 | 443 |
| @@ -28,36 +28,41 @@ | @@ -28,36 +28,41 @@ | ||
| 28 | </view> | 28 | </view> |
| 29 | 29 | ||
| 30 | <!-- 日期网格 (动态 5行 或 6行) --> | 30 | <!-- 日期网格 (动态 5行 或 6行) --> |
| 31 | - <view class="date-grid"> | ||
| 32 | - <view v-for="(date, index) in dates" :key="index" class="date-cell" :class="{ | ||
| 33 | - | ||
| 34 | - 'has-data': date.planList.length || date.noteList.length || (date.weight && date.weight > 0) | ||
| 35 | - }" :style="{ backgroundColor: getCellBgColor(date) }" @tap="selectDate(date)"> | ||
| 36 | - <!-- 日期数字 / 今日徽章 --> | ||
| 37 | - <view class="date-header"> | ||
| 38 | - <text v-if="date.today" class="today-badge">今</text> | ||
| 39 | - <text v-else class="date-number"> | ||
| 40 | - {{ date.day }} | ||
| 41 | - </text> | ||
| 42 | - </view> | ||
| 43 | 31 | ||
| 44 | - <!-- 计划/容量数据列表 --> | ||
| 45 | - <view class="plan-list" | ||
| 46 | - v-if="date.planList.length || date.noteList.length || (date.weight && date.weight > 0)"> | ||
| 47 | - <template v-for="(item, idx) in getCellVisibleItems(date).items" :key="idx"> | ||
| 48 | - <view class="plan-tag" :class="`tag-type-${item.type}`" | ||
| 49 | - :style="item.bg ? { backgroundColor: item.bg } : {}"> | ||
| 50 | - <text class="tag-text">{{ item.text }}</text> | ||
| 51 | - </view> | ||
| 52 | - </template> | 32 | + <scroll-view class="scroll-view" scroll-y> |
| 33 | + <view class="date-grid"> | ||
| 34 | + <view v-for="(date, index) in dates" :key="index" class="date-cell" :class="{ | ||
| 35 | + | ||
| 36 | + 'has-data': date.planList.length || date.noteList.length || (date.weight && date.weight > 0) | ||
| 37 | + }" :style="{ backgroundColor: getCellBgColor(date) }" @tap="selectDate(date)"> | ||
| 38 | + <!-- 日期数字 / 今日徽章 --> | ||
| 39 | + <view class="date-header"> | ||
| 40 | + <text v-if="date.today" class="today-badge">今</text> | ||
| 41 | + <text v-else class="date-number"> | ||
| 42 | + {{ date.day }} | ||
| 43 | + </text> | ||
| 44 | + </view> | ||
| 53 | 45 | ||
| 54 | - <!-- 溢出剩余条数 --> | ||
| 55 | - <view v-if="getCellVisibleItems(date).remaining > 0" class="plan-extra"> | ||
| 56 | - +{{ getCellVisibleItems(date).remaining }} | 46 | + <!-- 计划/容量数据列表 --> |
| 47 | + <view class="plan-list" | ||
| 48 | + v-if="date.planList.length || date.noteList.length || (date.weight && date.weight > 0)"> | ||
| 49 | + <template v-for="(item, idx) in getCellVisibleItems(date).items" :key="idx"> | ||
| 50 | + <view class="plan-tag" :class="`tag-type-${item.type}`" | ||
| 51 | + :style="item.bg ? { backgroundColor: item.bg } : {}"> | ||
| 52 | + <text class="tag-text">{{ item.text }}</text> | ||
| 53 | + </view> | ||
| 54 | + </template> | ||
| 55 | + <!-- 溢出剩余条数 --> | ||
| 56 | + <view v-if="getCellVisibleItems(date).remaining > 0" class="plan-extra"> | ||
| 57 | + +{{ getCellVisibleItems(date).remaining }} | ||
| 58 | + </view> | ||
| 57 | </view> | 59 | </view> |
| 58 | </view> | 60 | </view> |
| 59 | </view> | 61 | </view> |
| 60 | - </view> | 62 | + </scroll-view> |
| 63 | + | ||
| 64 | + | ||
| 65 | + | ||
| 61 | </view> | 66 | </view> |
| 62 | 67 | ||
| 63 | <!-- 单元格详情弹窗 --> | 68 | <!-- 单元格详情弹窗 --> |
| @@ -74,34 +79,50 @@ | @@ -74,34 +79,50 @@ | ||
| 74 | 79 | ||
| 75 | <view class="explain-list"> | 80 | <view class="explain-list"> |
| 76 | <view class="explain-item"> | 81 | <view class="explain-item"> |
| 77 | - <view class="color-block blue"></view> | ||
| 78 | - <view class="item-info"> | ||
| 79 | - <text class="item-label">时长</text> | ||
| 80 | - <text class="item-desc">在场馆的实际运动时长</text> | 82 | + <view class="explain-item-left"> |
| 83 | + <view class="color-block yellow"> | ||
| 84 | + | ||
| 85 | + </view> | ||
| 86 | + <view class="item-info"> | ||
| 87 | + <text class="item-label">已完成</text> | ||
| 88 | + <text class="item-desc">已完成或已结课的训练记录</text> | ||
| 89 | + </view> | ||
| 81 | </view> | 90 | </view> |
| 82 | </view> | 91 | </view> |
| 83 | 92 | ||
| 84 | <view class="explain-item"> | 93 | <view class="explain-item"> |
| 85 | - <view class="color-block green"></view> | ||
| 86 | - <view class="item-info"> | ||
| 87 | - <text class="item-label">容量</text> | ||
| 88 | - <text class="item-desc">力量训练累计产生的总负重重量</text> | 94 | + <view class="explain-item-left"> |
| 95 | + <view class="color-block gray"> | ||
| 96 | + | ||
| 97 | + </view> | ||
| 98 | + <view class="item-info"> | ||
| 99 | + <text class="item-label">待训练</text> | ||
| 100 | + <text class="item-desc">已安排的待训练计划</text> | ||
| 101 | + </view> | ||
| 89 | </view> | 102 | </view> |
| 90 | </view> | 103 | </view> |
| 91 | 104 | ||
| 92 | <view class="explain-item"> | 105 | <view class="explain-item"> |
| 93 | - <view class="color-block yellow"></view> | ||
| 94 | - <view class="item-info"> | ||
| 95 | - <text class="item-label">课程</text> | ||
| 96 | - <text class="item-desc">训记完成或已结课的线下课程</text> | 106 | + <view class="explain-item-left"> |
| 107 | + <view class="color-block green"> | ||
| 108 | + | ||
| 109 | + </view> | ||
| 110 | + <view class="item-info"> | ||
| 111 | + <text class="item-label">训练容量</text> | ||
| 112 | + <text class="item-desc">力量训练累计产生的总负重</text> | ||
| 113 | + </view> | ||
| 97 | </view> | 114 | </view> |
| 98 | </view> | 115 | </view> |
| 99 | 116 | ||
| 100 | <view class="explain-item"> | 117 | <view class="explain-item"> |
| 101 | - <view class="color-block gray"></view> | ||
| 102 | - <view class="item-info"> | ||
| 103 | - <text class="item-label">排课</text> | ||
| 104 | - <text class="item-desc">训记排课计划或待上课的课程</text> | 118 | + <view class="explain-item-left"> |
| 119 | + <view class="color-block blue"> | ||
| 120 | + | ||
| 121 | + </view> | ||
| 122 | + <view class="item-info"> | ||
| 123 | + <text class="item-label">日程备注</text> | ||
| 124 | + <text class="item-desc">相关日期的备注标记</text> | ||
| 125 | + </view> | ||
| 105 | </view> | 126 | </view> |
| 106 | </view> | 127 | </view> |
| 107 | </view> | 128 | </view> |
| @@ -111,7 +132,7 @@ | @@ -111,7 +132,7 @@ | ||
| 111 | </template> | 132 | </template> |
| 112 | 133 | ||
| 113 | <script setup> | 134 | <script setup> |
| 114 | -import { ref, watch, computed, nextTick } from 'vue'; | 135 | +import { ref, watch, computed, nextTick, onMounted, onBeforeUnmount } from 'vue'; |
| 115 | import dayjs from 'dayjs'; | 136 | import dayjs from 'dayjs'; |
| 116 | import DailyTemplateApi from '@/sheep/api/calendar/date'; | 137 | import DailyTemplateApi from '@/sheep/api/calendar/date'; |
| 117 | import GridCellContentPopup from '@/pages/xunji/components/rili-components/grid-cell-content-popup.vue'; | 138 | import GridCellContentPopup from '@/pages/xunji/components/rili-components/grid-cell-content-popup.vue'; |
| @@ -250,7 +271,7 @@ const loadPlans = async (monthStr) => { | @@ -250,7 +271,7 @@ const loadPlans = async (monthStr) => { | ||
| 250 | // 按 sourceType + sourceName 分组 | 271 | // 按 sourceType + sourceName 分组 |
| 251 | const sourceMap = new Map(); | 272 | const sourceMap = new Map(); |
| 252 | plan.templates.forEach((template) => { | 273 | plan.templates.forEach((template) => { |
| 253 | - if (!template || !template.name) return; | 274 | + if (!template) return; |
| 254 | const sourceKey = template.sourceType | 275 | const sourceKey = template.sourceType |
| 255 | ? `src_${template.sourceType}_${template.sourceName || ''}` | 276 | ? `src_${template.sourceType}_${template.sourceName || ''}` |
| 256 | : `tpl_${template.dailyTemplateId}`; | 277 | : `tpl_${template.dailyTemplateId}`; |
| @@ -323,6 +344,14 @@ const handleRefreshCalendar = () => { | @@ -323,6 +344,14 @@ const handleRefreshCalendar = () => { | ||
| 323 | loadPlans(currentMonth.value); | 344 | loadPlans(currentMonth.value); |
| 324 | }; | 345 | }; |
| 325 | 346 | ||
| 347 | +// 监听来自 xunji-moban-xiangqing 模板添加成功的刷新事件 | ||
| 348 | +onMounted(() => { | ||
| 349 | + uni.$on('calendarDataRefresh', handleRefreshCalendar); | ||
| 350 | +}); | ||
| 351 | +onBeforeUnmount(() => { | ||
| 352 | + uni.$off('calendarDataRefresh', handleRefreshCalendar); | ||
| 353 | +}); | ||
| 354 | + | ||
| 326 | watch( | 355 | watch( |
| 327 | currentMonth, | 356 | currentMonth, |
| 328 | (newVal) => { | 357 | (newVal) => { |
| @@ -395,136 +424,146 @@ const selectDate = async (date) => { | @@ -395,136 +424,146 @@ const selectDate = async (date) => { | ||
| 395 | } | 424 | } |
| 396 | } | 425 | } |
| 397 | 426 | ||
| 398 | - .date-grid { | ||
| 399 | - display: grid; | ||
| 400 | - grid-template-columns: repeat(7, 1fr); | ||
| 401 | - gap: 10rpx; | ||
| 402 | - } | ||
| 403 | - | ||
| 404 | - /* 日期格子基础样式:保证每个格子都有独立背景与微细边框 */ | ||
| 405 | - .date-cell { | ||
| 406 | - min-width: 0; | ||
| 407 | - overflow: hidden; | ||
| 408 | - display: flex; | ||
| 409 | - flex-direction: column; | ||
| 410 | - align-items: center; | ||
| 411 | - justify-content: flex-start; | ||
| 412 | - border-radius: 12rpx; | ||
| 413 | - height: 200rpx; | ||
| 414 | - padding: 8rpx 4rpx; | ||
| 415 | - box-sizing: border-box; | ||
| 416 | - transition: all 0.2s ease; | ||
| 417 | - | ||
| 418 | - &:active { | ||
| 419 | - filter: brightness(0.96); | ||
| 420 | - } | ||
| 421 | - | ||
| 422 | - &.other-month { | ||
| 423 | - opacity: 0.5; | ||
| 424 | - border-color: #f9fafc; | ||
| 425 | - } | 427 | + .scroll-view { |
| 428 | + height: 100%; | ||
| 429 | + | ||
| 430 | + .date-grid { | ||
| 431 | + display: grid; | ||
| 432 | + grid-template-columns: repeat(7, 1fr); | ||
| 433 | + gap: 10rpx; | ||
| 434 | + // #ifdef MP-WEIXIN | ||
| 435 | + padding-bottom: 100rpx; | ||
| 436 | + // #endif | ||
| 437 | + /* 日期格子基础样式:保证每个格子都有独立背景与微细边框 */ | ||
| 438 | + .date-cell { | ||
| 439 | + min-width: 0; | ||
| 440 | + overflow: hidden; | ||
| 441 | + display: flex; | ||
| 442 | + flex-direction: column; | ||
| 443 | + align-items: center; | ||
| 444 | + justify-content: flex-start; | ||
| 445 | + border-radius: 12rpx; | ||
| 446 | + height: 200rpx; | ||
| 447 | + padding: 8rpx 4rpx; | ||
| 448 | + box-sizing: border-box; | ||
| 449 | + transition: all 0.2s ease; | ||
| 426 | 450 | ||
| 427 | - &.is-today { | ||
| 428 | - border: 2rpx solid #3b82f6; | ||
| 429 | - } | 451 | + &:active { |
| 452 | + filter: brightness(0.96); | ||
| 453 | + } | ||
| 430 | 454 | ||
| 431 | - &.has-data { | ||
| 432 | - box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.02); | ||
| 433 | - } | 455 | + &.other-month { |
| 456 | + opacity: 0.5; | ||
| 457 | + border-color: #f9fafc; | ||
| 458 | + } | ||
| 434 | 459 | ||
| 435 | - .date-header { | ||
| 436 | - height: 36rpx; | ||
| 437 | - display: flex; | ||
| 438 | - align-items: center; | ||
| 439 | - justify-content: center; | ||
| 440 | - margin-bottom: 6rpx; | 460 | + &.is-today { |
| 461 | + border: 2rpx solid #3b82f6; | ||
| 462 | + } | ||
| 441 | 463 | ||
| 442 | - .date-number { | ||
| 443 | - font-size: 26rpx; | ||
| 444 | - font-weight: 600; | ||
| 445 | - color: #1f2937; | 464 | + &.has-data { |
| 465 | + box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.02); | ||
| 466 | + } | ||
| 446 | 467 | ||
| 468 | + .date-header { | ||
| 469 | + height: 36rpx; | ||
| 470 | + display: flex; | ||
| 471 | + align-items: center; | ||
| 472 | + justify-content: center; | ||
| 473 | + margin-bottom: 6rpx; | ||
| 447 | 474 | ||
| 448 | - } | 475 | + .date-number { |
| 476 | + font-size: 26rpx; | ||
| 477 | + font-weight: 600; | ||
| 478 | + color: #1f2937; | ||
| 449 | 479 | ||
| 450 | - .today-badge { | ||
| 451 | 480 | ||
| 452 | - font-weight: 600; | 481 | + } |
| 453 | 482 | ||
| 454 | - } | ||
| 455 | - } | 483 | + .today-badge { |
| 456 | 484 | ||
| 457 | - .plan-list { | ||
| 458 | - width: 100%; | ||
| 459 | - display: flex; | ||
| 460 | - flex-direction: column; | ||
| 461 | - gap: 6rpx; | 485 | + font-weight: 600; |
| 462 | 486 | ||
| 463 | - .plan-tag { | ||
| 464 | - width: 100%; | ||
| 465 | - border-radius: 6rpx; | ||
| 466 | - padding: 2rpx 4rpx; | ||
| 467 | - box-sizing: border-box; | ||
| 468 | - display: flex; | ||
| 469 | - align-items: center; | ||
| 470 | - justify-content: center; | ||
| 471 | - background-color: rgba(255, 255, 255, 0.85); | ||
| 472 | - /* 使 Tag 悬浮于彩色格子之上 */ | ||
| 473 | - | ||
| 474 | - .tag-text { | ||
| 475 | - font-size: 18rpx; | ||
| 476 | - line-height: 22rpx; | ||
| 477 | - color: #111827; | ||
| 478 | - overflow: hidden; | ||
| 479 | - white-space: nowrap; | ||
| 480 | - text-overflow: ellipsis; | ||
| 481 | - text-align: center; | ||
| 482 | - width: 100%; | ||
| 483 | - } | ||
| 484 | - | ||
| 485 | - &.tag-type-weight { | ||
| 486 | - background-color: #bbf7d0 !important; | ||
| 487 | - | ||
| 488 | - .tag-text { | ||
| 489 | - color: #14532d; | ||
| 490 | - font-weight: 700; | ||
| 491 | } | 487 | } |
| 492 | } | 488 | } |
| 493 | 489 | ||
| 494 | - &.tag-type-note { | ||
| 495 | - background-color: #ffffff; | 490 | + .plan-list { |
| 491 | + width: 100%; | ||
| 492 | + display: flex; | ||
| 493 | + flex-direction: column; | ||
| 494 | + gap: 6rpx; | ||
| 495 | + | ||
| 496 | + .plan-tag { | ||
| 497 | + width: 100%; | ||
| 498 | + border-radius: 6rpx; | ||
| 499 | + padding: 2rpx 4rpx; | ||
| 500 | + box-sizing: border-box; | ||
| 501 | + display: flex; | ||
| 502 | + align-items: center; | ||
| 503 | + justify-content: center; | ||
| 504 | + background-color: rgba(255, 255, 255, 0.85); | ||
| 505 | + /* 使 Tag 悬浮于彩色格子之上 */ | ||
| 506 | + | ||
| 507 | + .tag-text { | ||
| 508 | + font-size: 18rpx; | ||
| 509 | + line-height: 22rpx; | ||
| 510 | + color: #111827; | ||
| 511 | + overflow: hidden; | ||
| 512 | + white-space: nowrap; | ||
| 513 | + text-overflow: ellipsis; | ||
| 514 | + text-align: center; | ||
| 515 | + width: 100%; | ||
| 516 | + } | ||
| 517 | + | ||
| 518 | + &.tag-type-weight { | ||
| 519 | + background-color: #bbf7d0 !important; | ||
| 520 | + | ||
| 521 | + .tag-text { | ||
| 522 | + color: #14532d; | ||
| 523 | + font-weight: 700; | ||
| 524 | + } | ||
| 525 | + } | ||
| 526 | + | ||
| 527 | + &.tag-type-note { | ||
| 528 | + background-color: #ffffff; | ||
| 529 | + | ||
| 530 | + .tag-text { | ||
| 531 | + color: #374151; | ||
| 532 | + } | ||
| 533 | + } | ||
| 534 | + } | ||
| 496 | 535 | ||
| 497 | - .tag-text { | ||
| 498 | - color: #374151; | 536 | + .plan-extra { |
| 537 | + font-size: 16rpx; | ||
| 538 | + color: #4b5563; | ||
| 539 | + text-align: center; | ||
| 540 | + line-height: 20rpx; | ||
| 541 | + font-weight: 600; | ||
| 499 | } | 542 | } |
| 500 | } | 543 | } |
| 501 | } | 544 | } |
| 502 | - | ||
| 503 | - .plan-extra { | ||
| 504 | - font-size: 16rpx; | ||
| 505 | - color: #4b5563; | ||
| 506 | - text-align: center; | ||
| 507 | - line-height: 20rpx; | ||
| 508 | - font-weight: 600; | ||
| 509 | - } | ||
| 510 | } | 545 | } |
| 546 | + | ||
| 547 | + | ||
| 511 | } | 548 | } |
| 549 | + | ||
| 550 | + | ||
| 512 | } | 551 | } |
| 513 | } | 552 | } |
| 514 | 553 | ||
| 515 | .explain-popup-content { | 554 | .explain-popup-content { |
| 516 | - padding: 36rpx 32rpx 48rpx 32rpx; | ||
| 517 | - background-color: #ffffff; | 555 | + padding: 40rpx 32rpx 60rpx 32rpx; |
| 556 | + background-color: #f8f9fb; | ||
| 518 | 557 | ||
| 519 | .explain-header { | 558 | .explain-header { |
| 520 | display: flex; | 559 | display: flex; |
| 521 | justify-content: space-between; | 560 | justify-content: space-between; |
| 522 | align-items: center; | 561 | align-items: center; |
| 523 | - margin-bottom: 32rpx; | 562 | + margin-bottom: 36rpx; |
| 524 | 563 | ||
| 525 | .explain-title { | 564 | .explain-title { |
| 526 | - font-size: 32rpx; | ||
| 527 | - font-weight: 600; | 565 | + font-size: 34rpx; |
| 566 | + font-weight: 700; | ||
| 528 | color: #111827; | 567 | color: #111827; |
| 529 | } | 568 | } |
| 530 | } | 569 | } |
| @@ -532,50 +571,105 @@ const selectDate = async (date) => { | @@ -532,50 +571,105 @@ const selectDate = async (date) => { | ||
| 532 | .explain-list { | 571 | .explain-list { |
| 533 | display: flex; | 572 | display: flex; |
| 534 | flex-direction: column; | 573 | flex-direction: column; |
| 535 | - gap: 28rpx; | 574 | + gap: 20rpx; |
| 536 | 575 | ||
| 537 | .explain-item { | 576 | .explain-item { |
| 538 | - display: flex; | ||
| 539 | - align-items: center; | 577 | + background: #fff; |
| 578 | + border-radius: 20rpx; | ||
| 579 | + padding: 24rpx 24rpx 24rpx 20rpx; | ||
| 580 | + box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04); | ||
| 581 | + | ||
| 582 | + .explain-item-left { | ||
| 583 | + display: flex; | ||
| 584 | + align-items: center; | ||
| 585 | + gap: 20rpx; | ||
| 586 | + } | ||
| 540 | 587 | ||
| 541 | .color-block { | 588 | .color-block { |
| 542 | - width: 36rpx; | ||
| 543 | - height: 36rpx; | ||
| 544 | - border-radius: 8rpx; | ||
| 545 | - margin-right: 24rpx; | 589 | + width: 100rpx; |
| 590 | + height: 92rpx; | ||
| 591 | + border-radius: 14rpx; | ||
| 592 | + flex-shrink: 0; | ||
| 593 | + display: flex; | ||
| 594 | + align-items: center; | ||
| 595 | + justify-content: center; | ||
| 596 | + position: relative; | ||
| 546 | 597 | ||
| 547 | - &.blue { | ||
| 548 | - background-color: #bae6fd; | 598 | + &.yellow { |
| 599 | + background-color: #ffdd44; | ||
| 600 | + } | ||
| 601 | + | ||
| 602 | + &.gray { | ||
| 603 | + background-color: #e8e8e8; | ||
| 549 | } | 604 | } |
| 550 | 605 | ||
| 551 | &.green { | 606 | &.green { |
| 552 | background-color: #bbf7d0; | 607 | background-color: #bbf7d0; |
| 553 | } | 608 | } |
| 554 | 609 | ||
| 555 | - &.yellow { | ||
| 556 | - background-color: #fef08a; | 610 | + &.blue { |
| 611 | + background-color: #bae6fd; | ||
| 557 | } | 612 | } |
| 558 | 613 | ||
| 559 | - &.gray { | ||
| 560 | - background-color: #e5e7eb; | 614 | + .mini-cell-bg { |
| 615 | + width: 58rpx; | ||
| 616 | + height: 58rpx; | ||
| 617 | + background: rgba(255, 255, 255, 0.7); | ||
| 618 | + border-radius: 8rpx; | ||
| 619 | + display: flex; | ||
| 620 | + align-items: flex-end; | ||
| 621 | + justify-content: center; | ||
| 622 | + padding-bottom: 4rpx; | ||
| 623 | + } | ||
| 624 | + | ||
| 625 | + .mini-tag { | ||
| 626 | + font-size: 14rpx; | ||
| 627 | + font-weight: 600; | ||
| 628 | + border-radius: 4rpx; | ||
| 629 | + padding: 1rpx 6rpx; | ||
| 630 | + line-height: 20rpx; | ||
| 631 | + white-space: nowrap; | ||
| 632 | + | ||
| 633 | + &.yellow-tag { | ||
| 634 | + background: #fff3b0; | ||
| 635 | + color: #8b6914; | ||
| 636 | + } | ||
| 637 | + | ||
| 638 | + &.gray-tag { | ||
| 639 | + background: #f0f0f0; | ||
| 640 | + color: #555; | ||
| 641 | + } | ||
| 642 | + | ||
| 643 | + &.green-tag { | ||
| 644 | + background: #a7f3d0; | ||
| 645 | + color: #14532d; | ||
| 646 | + } | ||
| 647 | + | ||
| 648 | + &.blue-tag { | ||
| 649 | + background: #7dd3fc; | ||
| 650 | + color: #0c4a6e; | ||
| 651 | + } | ||
| 561 | } | 652 | } |
| 562 | } | 653 | } |
| 563 | 654 | ||
| 564 | .item-info { | 655 | .item-info { |
| 565 | display: flex; | 656 | display: flex; |
| 566 | - align-items: center; | ||
| 567 | - gap: 16rpx; | 657 | + flex-direction: column; |
| 658 | + gap: 6rpx; | ||
| 659 | + flex: 1; | ||
| 660 | + min-width: 0; | ||
| 568 | 661 | ||
| 569 | .item-label { | 662 | .item-label { |
| 570 | - font-size: 28rpx; | 663 | + font-size: 30rpx; |
| 571 | font-weight: 600; | 664 | font-weight: 600; |
| 572 | color: #1f2937; | 665 | color: #1f2937; |
| 573 | - width: 72rpx; | 666 | + line-height: 1.3; |
| 574 | } | 667 | } |
| 575 | 668 | ||
| 576 | .item-desc { | 669 | .item-desc { |
| 577 | - font-size: 26rpx; | ||
| 578 | - color: #6b7280; | 670 | + font-size: 24rpx; |
| 671 | + color: #9ca3af; | ||
| 672 | + line-height: 1.4; | ||
| 579 | } | 673 | } |
| 580 | } | 674 | } |
| 581 | } | 675 | } |
| @@ -50,7 +50,7 @@ | @@ -50,7 +50,7 @@ | ||
| 50 | <view class="part-select-wrapper"> | 50 | <view class="part-select-wrapper"> |
| 51 | <up-select v-model:current="partId" | 51 | <up-select v-model:current="partId" |
| 52 | :label="partOptions.find((item) => item.id === partId)?.name || '全部'" :options="partOptions" | 52 | :label="partOptions.find((item) => item.id === partId)?.name || '全部'" :options="partOptions" |
| 53 | - @select="onPartChange"></up-select> | 53 | + maxHeight="400rpx" @select="onPartChange"></up-select> |
| 54 | </view> | 54 | </view> |
| 55 | </view> | 55 | </view> |
| 56 | 56 | ||
| @@ -65,22 +65,25 @@ | @@ -65,22 +65,25 @@ | ||
| 65 | 65 | ||
| 66 | <!-- 统计图表(根据 showLastPeriod 切换 柱状图 / 折线图) --> | 66 | <!-- 统计图表(根据 showLastPeriod 切换 柱状图 / 折线图) --> |
| 67 | <view class="chart-container"> | 67 | <view class="chart-container"> |
| 68 | - <template v-if="!showLastPeriod"> | ||
| 69 | - <LineChart v-if="chartCategories.length > 0" :categories="chartCategories" :series="chartSeries" | ||
| 70 | - chartType="column" :reshow="true" :extra="{ column: { width: trendChartColumnWidth } }" /> | ||
| 71 | - </template> | ||
| 72 | - <template v-else> | ||
| 73 | - <LineChart v-if="chartCategories.length > 0" :categories="chartCategories" :series="chartSeries" | ||
| 74 | - chartType="line" :reshow="true" :extra="{ column: { width: trendChartColumnWidth } }" /> | ||
| 75 | - </template> | 68 | + <LineChart v-if="chartCategories.length > 0" :categories="chartCategories" :series="chartSeries" |
| 69 | + :chartType="showLastPeriod ? 'line' : 'column'" | ||
| 70 | + :reshow="true" | ||
| 71 | + :extra="{ column: { width: trendChartColumnWidth } }" | ||
| 72 | + :chartId="'trend-chart'" | ||
| 73 | + /> | ||
| 76 | </view> | 74 | </view> |
| 77 | 75 | ||
| 78 | <!-- 显示上周/上月数据勾选框 --> | 76 | <!-- 显示上周/上月数据勾选框 --> |
| 79 | - <view class="toggle-checkbox" @click="toggleLastWeek"> | ||
| 80 | - <checkbox :checked="showLastPeriod" color="#10b981" style="transform: scale(0.8);" /> | ||
| 81 | - <text class="toggle-label"> | ||
| 82 | - {{ MainCurrentDateType === 'week' ? '显示上周数据' : '显示上月数据' }} | ||
| 83 | - </text> | 77 | + <view class="toggle-checkbox"> |
| 78 | + <up-checkbox-group @change="toggleLastWeek"> | ||
| 79 | + <up-checkbox name="showLastPeriod" :checked="showLastPeriod" activeColor="#10b981" size="20" shape="square"> | ||
| 80 | + <template #label> | ||
| 81 | + <text class="toggle-label"> | ||
| 82 | + {{ MainCurrentDateType === 'week' ? '显示上周数据' : '显示上月数据' }} | ||
| 83 | + </text> | ||
| 84 | + </template> | ||
| 85 | + </up-checkbox> | ||
| 86 | + </up-checkbox-group> | ||
| 84 | </view> | 87 | </view> |
| 85 | 88 | ||
| 86 | <!-- 3. 数据对比区域 --> | 89 | <!-- 3. 数据对比区域 --> |
| @@ -138,19 +141,19 @@ | @@ -138,19 +141,19 @@ | ||
| 138 | 141 | ||
| 139 | <!-- 容量/组数 切换标签 --> | 142 | <!-- 容量/组数 切换标签 --> |
| 140 | <view class="muscle-tabs"> | 143 | <view class="muscle-tabs"> |
| 141 | - <view class="tab-item" :class="{ active: muscleTabIndex === 0 }" @click="muscleTabIndex = 0"> | 144 | + <view class="tab-item" :class="{ active: partTabIndex === 0 }" @click="partTabIndex = 0"> |
| 142 | 容量 | 145 | 容量 |
| 143 | </view> | 146 | </view> |
| 144 | - <view class="tab-item" :class="{ active: muscleTabIndex === 1 }" @click="muscleTabIndex = 1"> | 147 | + <view class="tab-item" :class="{ active: partTabIndex === 1 }" @click="partTabIndex = 1"> |
| 145 | 组数 | 148 | 组数 |
| 146 | </view> | 149 | </view> |
| 147 | </view> | 150 | </view> |
| 148 | - <view class="tip">{{ TREND_TIPS[muscleTabIndex] }}</view> | 151 | + <view class="tip">{{ TREND_TIPS[partTabIndex] }}</view> |
| 149 | 152 | ||
| 150 | <!-- 图表区域 --> | 153 | <!-- 图表区域 --> |
| 151 | <view class="statistical"> | 154 | <view class="statistical"> |
| 152 | <LineChart v-if="muscleShow && muscleChartCategories.length" | 155 | <LineChart v-if="muscleShow && muscleChartCategories.length" |
| 153 | - :key="`muscle-chart-${currentMuscleId}-${MainCurrentDateType}-${muscleTabIndex}`" | 156 | + :key="`muscle-chart-${currentMuscleId}-${MainCurrentDateType}-${partTabIndex}`" |
| 154 | :categories="muscleChartCategories" :series="muscleChartSeries" chartType="column" :reshow="true" | 157 | :categories="muscleChartCategories" :series="muscleChartSeries" chartType="column" :reshow="true" |
| 155 | :extra="{ column: { width: muscleChartColumnWidth } }" /> | 158 | :extra="{ column: { width: muscleChartColumnWidth } }" /> |
| 156 | </view> | 159 | </view> |
| @@ -266,7 +269,6 @@ const trainingData = ref({ setCountMap: {}, totalVolumeMap: {} }); | @@ -266,7 +269,6 @@ const trainingData = ref({ setCountMap: {}, totalVolumeMap: {} }); | ||
| 266 | 269 | ||
| 267 | // --- 肌肉弹窗 --- | 270 | // --- 肌肉弹窗 --- |
| 268 | const muscleShow = ref(false); | 271 | const muscleShow = ref(false); |
| 269 | -const muscleTabIndex = ref(0); | ||
| 270 | const currentMuscleId = ref(null); | 272 | const currentMuscleId = ref(null); |
| 271 | const currentMuscleName = ref(''); | 273 | const currentMuscleName = ref(''); |
| 272 | const muscleDetailData = ref({ muscleId: 0, dailyStatsList: [] }); | 274 | const muscleDetailData = ref({ muscleId: 0, dailyStatsList: [] }); |
| @@ -345,11 +347,10 @@ const chartSeries = computed(() => { | @@ -345,11 +347,10 @@ const chartSeries = computed(() => { | ||
| 345 | 347 | ||
| 346 | const dataKey = TREND_DATA_KEYS[NumberTabIndex.value] || 'volume'; | 348 | const dataKey = TREND_DATA_KEYS[NumberTabIndex.value] || 'volume'; |
| 347 | 349 | ||
| 348 | - /** 单位转换:秒→分钟,米→公里,保留一位小数 */ | 350 | + /** 单位转换:秒→分钟,保留一位小数;距离后端已返回 km,无需转换 */ |
| 349 | const convertVal = (raw) => { | 351 | const convertVal = (raw) => { |
| 350 | const num = Number(raw) || 0; | 352 | const num = Number(raw) || 0; |
| 351 | if (dataKey === 'duration') return parseFloat((num / 60).toFixed(1)); | 353 | if (dataKey === 'duration') return parseFloat((num / 60).toFixed(1)); |
| 352 | - if (dataKey === 'distance') return parseFloat((num / 1000).toFixed(1)); | ||
| 353 | return num; | 354 | return num; |
| 354 | }; | 355 | }; |
| 355 | 356 | ||
| @@ -369,9 +370,9 @@ const chartSeries = computed(() => { | @@ -369,9 +370,9 @@ const chartSeries = computed(() => { | ||
| 369 | 370 | ||
| 370 | return showLastPeriod.value | 371 | return showLastPeriod.value |
| 371 | ? [ | 372 | ? [ |
| 372 | - { name: lastName, data: lastData, color: '#5b8ff9' }, | ||
| 373 | - { name: currName, data: currData, color: '#ffc53d' }, | ||
| 374 | - ] | 373 | + { name: lastName, data: lastData, color: '#5b8ff9' }, |
| 374 | + { name: currName, data: currData, color: '#ffc53d' }, | ||
| 375 | + ] | ||
| 375 | : [{ name: currName, data: currData, color: '#ffc53d' }]; | 376 | : [{ name: currName, data: currData, color: '#ffc53d' }]; |
| 376 | }); | 377 | }); |
| 377 | 378 | ||
| @@ -380,15 +381,13 @@ const listData = computed(() => { | @@ -380,15 +381,13 @@ const listData = computed(() => { | ||
| 380 | if (!weeklyData.value) return []; | 381 | if (!weeklyData.value) return []; |
| 381 | 382 | ||
| 382 | const d = weeklyData.value; | 383 | const d = weeklyData.value; |
| 383 | - /** 单位转换:秒→分钟,米→公里,保留一位小数 */ | 384 | + /** 单位转换:秒→分钟,保留一位小数;距离后端已返回 km,无需转换 */ |
| 384 | const toMin = (v) => parseFloat(((v ?? 0) / 60).toFixed(1)); | 385 | const toMin = (v) => parseFloat(((v ?? 0) / 60).toFixed(1)); |
| 385 | - const toKm = (v) => parseFloat(((v ?? 0) / 1000).toFixed(1)); | ||
| 386 | - | ||
| 387 | const fields = [ | 386 | const fields = [ |
| 388 | { label: '容量', last: d.lastWeekTotalVolume, curr: d.totalVolume }, | 387 | { label: '容量', last: d.lastWeekTotalVolume, curr: d.totalVolume }, |
| 389 | { label: '组数', last: d.lastWeekTotalSets, curr: d.totalSets }, | 388 | { label: '组数', last: d.lastWeekTotalSets, curr: d.totalSets }, |
| 390 | { label: '时长', last: toMin(d.lastWeekTotalDuration), curr: toMin(d.totalDuration) }, | 389 | { label: '时长', last: toMin(d.lastWeekTotalDuration), curr: toMin(d.totalDuration) }, |
| 391 | - { label: '距离', last: toKm(d.lastWeekTotalDistance), curr: toKm(d.totalDistance) }, | 390 | + { label: '距离', last: d.lastWeekTotalDistance, curr: d.totalDistance }, |
| 392 | { label: '次数', last: d.lastWeekTotalCount, curr: d.totalCount }, | 391 | { label: '次数', last: d.lastWeekTotalCount, curr: d.totalCount }, |
| 393 | ]; | 392 | ]; |
| 394 | 393 | ||
| @@ -420,10 +419,10 @@ const convertUnit = (val, key) => { | @@ -420,10 +419,10 @@ const convertUnit = (val, key) => { | ||
| 420 | 419 | ||
| 421 | /** 肌肉弹窗图表 X 轴 */ | 420 | /** 肌肉弹窗图表 X 轴 */ |
| 422 | const muscleChartCategories = computed(() => { | 421 | const muscleChartCategories = computed(() => { |
| 422 | + if (!muscleFullDates.value.length) return []; | ||
| 423 | if (MainCurrentDateType.value === 'week') { | 423 | if (MainCurrentDateType.value === 'week') { |
| 424 | return WEEKDAY_LABELS; | 424 | return WEEKDAY_LABELS; |
| 425 | } | 425 | } |
| 426 | - if (!muscleFullDates.value.length) return []; | ||
| 427 | return muscleFullDates.value.map((d) => dayjs(d).format('M/D')); | 426 | return muscleFullDates.value.map((d) => dayjs(d).format('M/D')); |
| 428 | }); | 427 | }); |
| 429 | 428 | ||
| @@ -432,9 +431,9 @@ const muscleSetsArr = computed(() => muscleProcessedData.value.setsArr || []); | @@ -432,9 +431,9 @@ const muscleSetsArr = computed(() => muscleProcessedData.value.setsArr || []); | ||
| 432 | 431 | ||
| 433 | /** 肌肉弹窗图表 Series */ | 432 | /** 肌肉弹窗图表 Series */ |
| 434 | const muscleChartSeries = computed(() => { | 433 | const muscleChartSeries = computed(() => { |
| 435 | - const data = muscleTabIndex.value === 0 ? muscleVolumeArr.value : muscleSetsArr.value; | ||
| 436 | - const name = muscleTabIndex.value === 0 ? '容量' : '组数'; | ||
| 437 | - const color = muscleTabIndex.value === 0 ? '#ffc53d' : '#5b8ff9'; | 434 | + const data = partTabIndex.value === 0 ? muscleVolumeArr.value : muscleSetsArr.value; |
| 435 | + const name = partTabIndex.value === 0 ? '容量' : '组数'; | ||
| 436 | + const color = partTabIndex.value === 0 ? '#ffc53d' : '#5b8ff9'; | ||
| 438 | 437 | ||
| 439 | return [{ name, data: data || [], color }]; | 438 | return [{ name, data: data || [], color }]; |
| 440 | }); | 439 | }); |
| @@ -566,20 +565,14 @@ const onPartChange = (item) => { | @@ -566,20 +565,14 @@ const onPartChange = (item) => { | ||
| 566 | loadCategoryData(startDate.value); | 565 | loadCategoryData(startDate.value); |
| 567 | }; | 566 | }; |
| 568 | 567 | ||
| 569 | -/** 时间类型切换(周/月) */ | ||
| 570 | -const handleTypeChange = async (type) => { | 568 | +/** 时间类型切换(周/月) |
| 569 | + * 只负责更新类型状态,不加载数据。 | ||
| 570 | + * switchDateType 会同步触发 watch → emit dateRangeChange → handleDateChange, | ||
| 571 | + * handleDateChange 拿到正确的日期后再统一加载数据,避免用旧日期请求接口。 | ||
| 572 | + */ | ||
| 573 | +const handleTypeChange = (type) => { | ||
| 571 | MainCurrentDateType.value = type; | 574 | MainCurrentDateType.value = type; |
| 572 | showLastPeriod.value = false; | 575 | showLastPeriod.value = false; |
| 573 | - loadTrainingData(startDate.value, endDate.value); | ||
| 574 | - | ||
| 575 | - if (startDate.value) { | ||
| 576 | - await loadCategoryData(startDate.value); | ||
| 577 | - } | ||
| 578 | - | ||
| 579 | - // 如果肌肉弹窗打开,同步刷新 | ||
| 580 | - if (muscleShow.value && currentMuscleId.value) { | ||
| 581 | - await loadMuscleData(currentMuscleId.value, startDate.value); | ||
| 582 | - } | ||
| 583 | }; | 576 | }; |
| 584 | 577 | ||
| 585 | /** 日期范围变更 */ | 578 | /** 日期范围变更 */ |
| @@ -587,7 +580,7 @@ const handleDateChange = async (data) => { | @@ -587,7 +580,7 @@ const handleDateChange = async (data) => { | ||
| 587 | startDate.value = data.startDate; | 580 | startDate.value = data.startDate; |
| 588 | endDate.value = data.endDate; | 581 | endDate.value = data.endDate; |
| 589 | await loadTrainingData(startDate.value, endDate.value); | 582 | await loadTrainingData(startDate.value, endDate.value); |
| 590 | - loadCategoryData(startDate.value); | 583 | + await loadCategoryData(startDate.value); |
| 591 | 584 | ||
| 592 | if (muscleShow.value && currentMuscleId.value) { | 585 | if (muscleShow.value && currentMuscleId.value) { |
| 593 | await loadMuscleData(currentMuscleId.value, startDate.value); | 586 | await loadMuscleData(currentMuscleId.value, startDate.value); |
| @@ -598,7 +591,8 @@ const handleDateChange = async (data) => { | @@ -598,7 +591,8 @@ const handleDateChange = async (data) => { | ||
| 598 | const openMuscleDetail = async (muscleId) => { | 591 | const openMuscleDetail = async (muscleId) => { |
| 599 | currentMuscleId.value = muscleId; | 592 | currentMuscleId.value = muscleId; |
| 600 | muscleShow.value = true; | 593 | muscleShow.value = true; |
| 601 | - muscleTabIndex.value = 0; | 594 | + muscleFullDates.value = []; |
| 595 | + muscleProcessedData.value = { volumeArr: [], setsArr: [] }; | ||
| 602 | 596 | ||
| 603 | const muscle = subCategorieList.value.find((item) => item.id === muscleId); | 597 | const muscle = subCategorieList.value.find((item) => item.id === muscleId); |
| 604 | currentMuscleName.value = muscle?.name || ''; | 598 | currentMuscleName.value = muscle?.name || ''; |
| @@ -619,8 +613,8 @@ const closeMuscleDetail = () => { | @@ -619,8 +613,8 @@ const closeMuscleDetail = () => { | ||
| 619 | }; | 613 | }; |
| 620 | 614 | ||
| 621 | /** 切换显示上一周期数据 */ | 615 | /** 切换显示上一周期数据 */ |
| 622 | -const toggleLastWeek = () => { | ||
| 623 | - showLastPeriod.value = !showLastPeriod.value; | 616 | +const toggleLastWeek = (vals) => { |
| 617 | + showLastPeriod.value = vals.includes('showLastPeriod'); | ||
| 624 | if (startDate.value) { | 618 | if (startDate.value) { |
| 625 | loadCategoryData(startDate.value); | 619 | loadCategoryData(startDate.value); |
| 626 | } | 620 | } |
| @@ -631,6 +625,7 @@ const toggleLastWeek = () => { | @@ -631,6 +625,7 @@ const toggleLastWeek = () => { | ||
| 631 | onMounted(async () => { | 625 | onMounted(async () => { |
| 632 | await loadbodyPartOptions(); | 626 | await loadbodyPartOptions(); |
| 633 | await loadGetSubCategorieList(); | 627 | await loadGetSubCategorieList(); |
| 628 | + | ||
| 634 | if (startDate.value && endDate.value) { | 629 | if (startDate.value && endDate.value) { |
| 635 | await loadTrainingData(startDate.value, endDate.value); | 630 | await loadTrainingData(startDate.value, endDate.value); |
| 636 | await loadCategoryData(startDate.value); | 631 | await loadCategoryData(startDate.value); |
| @@ -701,7 +696,7 @@ $radius-item: 12rpx; | @@ -701,7 +696,7 @@ $radius-item: 12rpx; | ||
| 701 | .training-data-page { | 696 | .training-data-page { |
| 702 | display: flex; | 697 | display: flex; |
| 703 | flex-direction: column; | 698 | flex-direction: column; |
| 704 | - | 699 | + |
| 705 | width: 100vw; | 700 | width: 100vw; |
| 706 | background-color: $color-bg; | 701 | background-color: $color-bg; |
| 707 | overflow: hidden; | 702 | overflow: hidden; |
| @@ -865,8 +860,13 @@ $radius-item: 12rpx; | @@ -865,8 +860,13 @@ $radius-item: 12rpx; | ||
| 865 | border-radius: 4rpx; | 860 | border-radius: 4rpx; |
| 866 | margin-right: 8rpx; | 861 | margin-right: 8rpx; |
| 867 | 862 | ||
| 868 | - &.blue { background-color: $color-blue; } | ||
| 869 | - &.yellow { background-color: $color-yellow; } | 863 | + &.blue { |
| 864 | + background-color: $color-blue; | ||
| 865 | + } | ||
| 866 | + | ||
| 867 | + &.yellow { | ||
| 868 | + background-color: $color-yellow; | ||
| 869 | + } | ||
| 870 | } | 870 | } |
| 871 | } | 871 | } |
| 872 | } | 872 | } |
| @@ -886,8 +886,13 @@ $radius-item: 12rpx; | @@ -886,8 +886,13 @@ $radius-item: 12rpx; | ||
| 886 | border-bottom: 1rpx solid #f0f0f0; | 886 | border-bottom: 1rpx solid #f0f0f0; |
| 887 | font-size: 26rpx; | 887 | font-size: 26rpx; |
| 888 | 888 | ||
| 889 | - &:last-child { border-bottom: none; } | ||
| 890 | - &:nth-child(odd) { background-color: #f9fafc; } | 889 | + &:last-child { |
| 890 | + border-bottom: none; | ||
| 891 | + } | ||
| 892 | + | ||
| 893 | + &:nth-child(odd) { | ||
| 894 | + background-color: #f9fafc; | ||
| 895 | + } | ||
| 891 | 896 | ||
| 892 | .label { | 897 | .label { |
| 893 | flex: 1.5; | 898 | flex: 1.5; |
| @@ -903,8 +908,13 @@ $radius-item: 12rpx; | @@ -903,8 +908,13 @@ $radius-item: 12rpx; | ||
| 903 | &.diff { | 908 | &.diff { |
| 904 | font-weight: 600; | 909 | font-weight: 600; |
| 905 | 910 | ||
| 906 | - &.positive { color: $color-green; } | ||
| 907 | - &.negative { color: $color-red; } | 911 | + &.positive { |
| 912 | + color: $color-green; | ||
| 913 | + } | ||
| 914 | + | ||
| 915 | + &.negative { | ||
| 916 | + color: $color-red; | ||
| 917 | + } | ||
| 908 | } | 918 | } |
| 909 | } | 919 | } |
| 910 | } | 920 | } |
| @@ -311,6 +311,9 @@ onMounted(() => { | @@ -311,6 +311,9 @@ onMounted(() => { | ||
| 311 | width: 180rpx; | 311 | width: 180rpx; |
| 312 | background-color: #ffffff; | 312 | background-color: #ffffff; |
| 313 | border-radius: 16rpx; | 313 | border-radius: 16rpx; |
| 314 | + | ||
| 315 | + min-height: 70vh; | ||
| 316 | + | ||
| 314 | height: 88%; | 317 | height: 88%; |
| 315 | // #ifdef MP-WEIXIN | 318 | // #ifdef MP-WEIXIN |
| 316 | height: 86%; | 319 | height: 86%; |
| @@ -348,6 +351,7 @@ onMounted(() => { | @@ -348,6 +351,7 @@ onMounted(() => { | ||
| 348 | flex: 1; | 351 | flex: 1; |
| 349 | padding: 0 20rpx; | 352 | padding: 0 20rpx; |
| 350 | box-sizing: border-box; | 353 | box-sizing: border-box; |
| 354 | + min-height: 70vh; | ||
| 351 | height: 88%; | 355 | height: 88%; |
| 352 | // #ifdef MP-WEIXIN | 356 | // #ifdef MP-WEIXIN |
| 353 | height: 86%; | 357 | height: 86%; |
| @@ -36,7 +36,7 @@ | @@ -36,7 +36,7 @@ | ||
| 36 | @click="navigateTo('/pages4/pages/xunji/xunji-wode-moban')" /> | 36 | @click="navigateTo('/pages4/pages/xunji/xunji-wode-moban')" /> |
| 37 | <up-cell title="我的计划" icon="order" :isLink="true" | 37 | <up-cell title="我的计划" icon="order" :isLink="true" |
| 38 | @click="navigateTo('/pages4/pages/xunji/xunji-wode-jihua')" /> | 38 | @click="navigateTo('/pages4/pages/xunji/xunji-wode-jihua')" /> |
| 39 | - <up-cell title="训记使用教程" icon="question-circle" :isLink="true" @click="goTutorial" /> | 39 | + <up-cell title="训记使用教程" icon="question-circle" :isLink="true" @click="navigateTo('/pages4/pages/xunji/shiyon-jiaochen')" /> |
| 40 | </up-cell-group> | 40 | </up-cell-group> |
| 41 | </view> | 41 | </view> |
| 42 | </up-popup> | 42 | </up-popup> |
| @@ -61,7 +61,7 @@ import TrainingFloating from '@/pages/TrainingFloating.vue'; | @@ -61,7 +61,7 @@ import TrainingFloating from '@/pages/TrainingFloating.vue'; | ||
| 61 | 61 | ||
| 62 | const userStore = useUserStore(); | 62 | const userStore = useUserStore(); |
| 63 | const drawerShow = ref(false); | 63 | const drawerShow = ref(false); |
| 64 | -const currentTab = ref(3); | 64 | +const currentTab = ref(1); |
| 65 | 65 | ||
| 66 | const menuButtonHeight = ref(getMenuButtonHeight()); | 66 | const menuButtonHeight = ref(getMenuButtonHeight()); |
| 67 | const topSafeArea = ref(getTopSafeArea()); | 67 | const topSafeArea = ref(getTopSafeArea()); |
| @@ -73,10 +73,7 @@ PageHeight.value = menuButtonHeight.value + topSafeArea.value; | @@ -73,10 +73,7 @@ PageHeight.value = menuButtonHeight.value + topSafeArea.value; | ||
| 73 | const userInfo = computed(() => userStore.userInfo || {}); | 73 | const userInfo = computed(() => userStore.userInfo || {}); |
| 74 | 74 | ||
| 75 | onLoad((options) => { | 75 | onLoad((options) => { |
| 76 | - if (!userStore.isLogin) { | ||
| 77 | - uni.redirectTo({ url: '/pages7/pages/index/login' }); | ||
| 78 | - return; | ||
| 79 | - } | 76 | + |
| 80 | userStore.getInfo(); | 77 | userStore.getInfo(); |
| 81 | if (options && options.currentTab) { | 78 | if (options && options.currentTab) { |
| 82 | currentTab.value = Number(options.currentTab); | 79 | currentTab.value = Number(options.currentTab); |
| @@ -103,7 +100,7 @@ const handleTabClick = (index) => { | @@ -103,7 +100,7 @@ const handleTabClick = (index) => { | ||
| 103 | 100 | ||
| 104 | 101 | ||
| 105 | 102 | ||
| 106 | -const goTutorial = () => { }; | 103 | + |
| 107 | 104 | ||
| 108 | defineExpose({ switchTabIndex: handleTabClick }); | 105 | defineExpose({ switchTabIndex: handleTabClick }); |
| 109 | </script> | 106 | </script> |
| @@ -87,7 +87,7 @@ const sceneList = ref([ | @@ -87,7 +87,7 @@ const sceneList = ref([ | ||
| 87 | ]) | 87 | ]) |
| 88 | 88 | ||
| 89 | // 缺省图 | 89 | // 缺省图 |
| 90 | -const lostImage = "https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png" | 90 | +const lostImage = "https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260724/健身模板_1784871730506.png" |
| 91 | 91 | ||
| 92 | // ================================== | 92 | // ================================== |
| 93 | // 获取部位分类 | 93 | // 获取部位分类 |
pages4/pages/xunji/shiyon-jiaochen.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="tutorial-container"> | ||
| 3 | + <!-- 顶部导航栏 --> | ||
| 4 | + <u-navbar title="使用教程" :autoBack="true" bgColor="#ffffff" | ||
| 5 | + titleStyle="color: #303133; font-weight: 600; font-size: 32rpx;" leftIconColor="#303133" :placeholder="true" | ||
| 6 | + fixed /> | ||
| 7 | + | ||
| 8 | + <view class="tutorial-body"> | ||
| 9 | + <!-- 1. 顶部应用概览 --> | ||
| 10 | + <view class="overview-card"> | ||
| 11 | + <view class="overview-icon-wrapper"> | ||
| 12 | + <u-icon name="file-text-fill" color="#ffffff" size="28"></u-icon> | ||
| 13 | + </view> | ||
| 14 | + <view class="overview-content"> | ||
| 15 | + <text class="overview-title">什么是训记?</text> | ||
| 16 | + <text class="overview-desc"> | ||
| 17 | + 训记是一款专业的健身训练记录工具,帮助你科学规划训练计划、精准记录每次训练数据,让健身更高效。 | ||
| 18 | + </text> | ||
| 19 | + </view> | ||
| 20 | + </view> | ||
| 21 | + | ||
| 22 | + <!-- 2. 核心功能 --> | ||
| 23 | + <view class="section-container"> | ||
| 24 | + <view class="section-header"> | ||
| 25 | + <view class="header-line"></view> | ||
| 26 | + <text class="header-title">核心功能</text> | ||
| 27 | + <view class="header-line"></view> | ||
| 28 | + </view> | ||
| 29 | + | ||
| 30 | + <view class="features-grid"> | ||
| 31 | + <view class="feature-card" v-for="(item, index) in features" :key="index"> | ||
| 32 | + <view class="feature-icon" :style="{ backgroundColor: item.bgColor }"> | ||
| 33 | + <u-icon :name="item.icon" :color="item.iconColor" size="22"></u-icon> | ||
| 34 | + </view> | ||
| 35 | + <text class="feature-name">{{ item.name }}</text> | ||
| 36 | + <text class="feature-desc">{{ item.desc }}</text> | ||
| 37 | + </view> | ||
| 38 | + </view> | ||
| 39 | + </view> | ||
| 40 | + | ||
| 41 | + <!-- 3. 使用指南(步骤流程) --> | ||
| 42 | + <view class="section-container"> | ||
| 43 | + <view class="section-header"> | ||
| 44 | + <view class="header-line"></view> | ||
| 45 | + <text class="header-title">使用指南</text> | ||
| 46 | + <view class="header-line"></view> | ||
| 47 | + </view> | ||
| 48 | + | ||
| 49 | + <view class="steps-wrapper"> | ||
| 50 | + <view class="step-card" v-for="(step, sIndex) in steps" :key="sIndex"> | ||
| 51 | + <view class="step-header"> | ||
| 52 | + <view class="step-badge">{{ sIndex + 1 }}</view> | ||
| 53 | + <view class="step-titles"> | ||
| 54 | + <text class="step-main-title">{{ step.title }}</text> | ||
| 55 | + <text class="step-sub-title">{{ step.subtitle }}</text> | ||
| 56 | + </view> | ||
| 57 | + </view> | ||
| 58 | + | ||
| 59 | + <view class="step-details"> | ||
| 60 | + <view class="detail-item" v-for="(detail, dIndex) in step.details" :key="dIndex"> | ||
| 61 | + <view class="dot"></view> | ||
| 62 | + <text class="detail-text">{{ detail }}</text> | ||
| 63 | + </view> | ||
| 64 | + </view> | ||
| 65 | + | ||
| 66 | + <!-- 提示块:使用 uview-plus 标准图标 info-circle-fill --> | ||
| 67 | + <view class="step-tip" v-if="step.tip"> | ||
| 68 | + <u-icon name="info-circle-fill" color="#e6a23c" size="16"></u-icon> | ||
| 69 | + <text class="tip-text">{{ step.tip }}</text> | ||
| 70 | + </view> | ||
| 71 | + </view> | ||
| 72 | + </view> | ||
| 73 | + </view> | ||
| 74 | + | ||
| 75 | + <!-- 4. 常见问题 (手风琴交互) --> | ||
| 76 | + <view class="section-container"> | ||
| 77 | + <view class="section-header"> | ||
| 78 | + <view class="header-line"></view> | ||
| 79 | + <text class="header-title">常见问题</text> | ||
| 80 | + <view class="header-line"></view> | ||
| 81 | + </view> | ||
| 82 | + | ||
| 83 | + <view class="faq-wrapper"> | ||
| 84 | + <u-collapse accordion :border="false"> | ||
| 85 | + <u-collapse-item v-for="(faq, fIndex) in faqs" :key="fIndex" :name="fIndex"> | ||
| 86 | + <template #title> | ||
| 87 | + <view class="faq-title-box"> | ||
| 88 | + <view class="faq-tag q-tag">Q</view> | ||
| 89 | + <text class="faq-question-text">{{ faq.question }}</text> | ||
| 90 | + </view> | ||
| 91 | + </template> | ||
| 92 | + <view class="faq-answer-box"> | ||
| 93 | + <view class="faq-tag a-tag">A</view> | ||
| 94 | + <text class="faq-answer-text">{{ faq.answer }}</text> | ||
| 95 | + </view> | ||
| 96 | + </u-collapse-item> | ||
| 97 | + </u-collapse> | ||
| 98 | + </view> | ||
| 99 | + </view> | ||
| 100 | + | ||
| 101 | + <!-- 5. 底部标语 --> | ||
| 102 | + <view class="footer-section"> | ||
| 103 | + <text class="footer-text">训练贵在坚持,训记陪你一同成长</text> | ||
| 104 | + </view> | ||
| 105 | + </view> | ||
| 106 | + </view> | ||
| 107 | +</template> | ||
| 108 | + | ||
| 109 | +<script setup> | ||
| 110 | +import { ref } from 'vue' | ||
| 111 | + | ||
| 112 | +// 1. 核心功能数据 (均为 uview-plus 官方内置标准图标名) | ||
| 113 | +const features = ref([ | ||
| 114 | + { | ||
| 115 | + icon: 'order', | ||
| 116 | + iconColor: '#ff9900', | ||
| 117 | + name: '训练计划', | ||
| 118 | + desc: '创建和管理个性化训练计划', | ||
| 119 | + bgColor: '#fff7cc' | ||
| 120 | + }, | ||
| 121 | + { | ||
| 122 | + icon: 'grid', | ||
| 123 | + iconColor: '#19be6b', | ||
| 124 | + name: '动作库', | ||
| 125 | + desc: '丰富的训练动作库与详情', | ||
| 126 | + bgColor: '#dbf5e6' | ||
| 127 | + }, | ||
| 128 | + { | ||
| 129 | + icon: 'calendar', | ||
| 130 | + iconColor: '#2979ff', | ||
| 131 | + name: '训练日历', | ||
| 132 | + desc: '日历视图记录每日训练', | ||
| 133 | + bgColor: '#ecf5ff' | ||
| 134 | + }, | ||
| 135 | + { | ||
| 136 | + icon: 'list-dot', | ||
| 137 | + iconColor: '#fa3534', | ||
| 138 | + name: '数据分析', | ||
| 139 | + desc: '多维度训练数据统计分析', | ||
| 140 | + bgColor: '#fef0f0' | ||
| 141 | + } | ||
| 142 | +]) | ||
| 143 | + | ||
| 144 | +// 2. 使用步骤数据 | ||
| 145 | +const steps = ref([ | ||
| 146 | + { | ||
| 147 | + title: '创建训练计划', | ||
| 148 | + subtitle: '打造你的专属训练方案', | ||
| 149 | + details: [ | ||
| 150 | + '进入「我的」页面,点击「训练计划」进入计划管理', | ||
| 151 | + '选择「新建计划」,可从官方模板快速创建,也可自定义空白计划', | ||
| 152 | + '设置训练周期、训练日安排,拖拽调整动作顺序', | ||
| 153 | + '保存后即可在训练日历中按计划执行' | ||
| 154 | + ], | ||
| 155 | + tip: '建议新手从官方模板开始,逐步了解后再自定义计划' | ||
| 156 | + }, | ||
| 157 | + { | ||
| 158 | + title: '添加训练动作', | ||
| 159 | + subtitle: '每个动作都值得被记录', | ||
| 160 | + details: [ | ||
| 161 | + '在训练计划中点击「添加动作」,进入动作库', | ||
| 162 | + '按部位分类浏览动作,或直接搜索动作名称', | ||
| 163 | + '点击动作可查看详细说明、肌肉示意图和训练要点', | ||
| 164 | + '选择动作后设置组数、次数、重量等训练参数' | ||
| 165 | + ], | ||
| 166 | + tip: '支持创建超级组,将两个动作组合交替训练,提升效率' | ||
| 167 | + }, | ||
| 168 | + { | ||
| 169 | + title: '使用训练日历', | ||
| 170 | + subtitle: '一目了然的训练记录', | ||
| 171 | + details: [ | ||
| 172 | + '进入「训练」页面查看训练日历,日历标记已训练日期', | ||
| 173 | + '点击日期可添加当日训练内容,从计划模板一键导入', | ||
| 174 | + '已完成的训练会用不同颜色标识,方便回顾', | ||
| 175 | + '长按训练记录可编辑或删除' | ||
| 176 | + ], | ||
| 177 | + tip: '坚持每天打卡,日历会越来越丰富哦' | ||
| 178 | + }, | ||
| 179 | + { | ||
| 180 | + title: '记录训练数据', | ||
| 181 | + subtitle: '精准追踪每次进步', | ||
| 182 | + details: [ | ||
| 183 | + '训练过程中逐组录入重量、次数、休息时间等数据', | ||
| 184 | + '内置计时器功能,方便控制组间休息时长', | ||
| 185 | + '支持查看每个动作的历史训练数据对比', | ||
| 186 | + '训练完成后自动汇总本次训练总量' | ||
| 187 | + ], | ||
| 188 | + tip: '详细记录每次数据,才能看到自己的进步轨迹' | ||
| 189 | + }, | ||
| 190 | + { | ||
| 191 | + title: '查看训练分析', | ||
| 192 | + subtitle: '数据驱动,科学训练', | ||
| 193 | + details: [ | ||
| 194 | + '在「数据」页面查看训练频率、训练量趋势图表', | ||
| 195 | + '统计各部位训练次数,发现训练偏重或遗漏', | ||
| 196 | + '查看个人纪录(PR),见证力量增长', | ||
| 197 | + '训练日历热力图直观展示训练频率' | ||
| 198 | + ], | ||
| 199 | + tip: '定期回顾训练数据,及时调整训练计划方向' | ||
| 200 | + } | ||
| 201 | +]) | ||
| 202 | + | ||
| 203 | +// 3. 常见问题数据 | ||
| 204 | +const faqs = ref([ | ||
| 205 | + { | ||
| 206 | + question: '训记是免费的吗?', | ||
| 207 | + answer: '训记基础功能完全免费,包括训练计划创建、动作记录、日历打卡等核心功能均可畅快使用。' | ||
| 208 | + }, | ||
| 209 | + { | ||
| 210 | + question: '训练数据会丢失吗?', | ||
| 211 | + answer: '训练数据会自动保存在云端,只要登录同一账号,更换设备数据也不会丢失。' | ||
| 212 | + }, | ||
| 213 | + { | ||
| 214 | + question: '如何从模板快速创建计划?', | ||
| 215 | + answer: '在「我的模板」或「模板中心」选择适合自己的训练模板,点击「使用模板」即可一键生成训练计划。' | ||
| 216 | + }, | ||
| 217 | + { | ||
| 218 | + question: '一个计划可以包含多少动作?', | ||
| 219 | + answer: '训练计划对动作数量没有硬性限制,你可以根据实际训练需求自由添加和排序。' | ||
| 220 | + }, | ||
| 221 | + { | ||
| 222 | + question: '支持自定义动作吗?', | ||
| 223 | + answer: '支持!你可以创建个人专属动作,自定义动作名称、目标部位和训练参数。' | ||
| 224 | + } | ||
| 225 | +]) | ||
| 226 | +</script> | ||
| 227 | + | ||
| 228 | +<style lang="scss" scoped> | ||
| 229 | +.tutorial-container { | ||
| 230 | + min-height: 100vh; | ||
| 231 | + background-color: #f6f7f9; | ||
| 232 | + padding-bottom: calc(40rpx + constant(safe-area-inset-bottom)); | ||
| 233 | + padding-bottom: calc(40rpx + env(safe-area-inset-bottom)); | ||
| 234 | +} | ||
| 235 | + | ||
| 236 | +.tutorial-body { | ||
| 237 | + padding: 24rpx 30rpx; | ||
| 238 | +} | ||
| 239 | + | ||
| 240 | +/* 顶部概览卡片 */ | ||
| 241 | +.overview-card { | ||
| 242 | + display: flex; | ||
| 243 | + align-items: center; | ||
| 244 | + background: linear-gradient(135deg, #2979ff 0%, #609cff 100%); | ||
| 245 | + border-radius: 20rpx; | ||
| 246 | + padding: 32rpx; | ||
| 247 | + margin-bottom: 32rpx; | ||
| 248 | + box-shadow: 0 8rpx 24rpx rgba(41, 121, 255, 0.15); | ||
| 249 | + | ||
| 250 | + .overview-icon-wrapper { | ||
| 251 | + width: 90rpx; | ||
| 252 | + height: 90rpx; | ||
| 253 | + background-color: rgba(255, 255, 255, 0.2); | ||
| 254 | + border-radius: 50%; | ||
| 255 | + display: flex; | ||
| 256 | + align-items: center; | ||
| 257 | + justify-content: center; | ||
| 258 | + margin-right: 24rpx; | ||
| 259 | + flex-shrink: 0; | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + .overview-content { | ||
| 263 | + flex: 1; | ||
| 264 | + | ||
| 265 | + .overview-title { | ||
| 266 | + font-size: 32rpx; | ||
| 267 | + font-weight: 700; | ||
| 268 | + color: #ffffff; | ||
| 269 | + margin-bottom: 8rpx; | ||
| 270 | + display: block; | ||
| 271 | + } | ||
| 272 | + | ||
| 273 | + .overview-desc { | ||
| 274 | + font-size: 24rpx; | ||
| 275 | + color: rgba(255, 255, 255, 0.9); | ||
| 276 | + line-height: 36rpx; | ||
| 277 | + display: block; | ||
| 278 | + } | ||
| 279 | + } | ||
| 280 | +} | ||
| 281 | + | ||
| 282 | +/* 通用分区标题 */ | ||
| 283 | +.section-container { | ||
| 284 | + margin-bottom: 32rpx; | ||
| 285 | +} | ||
| 286 | + | ||
| 287 | +.section-header { | ||
| 288 | + display: flex; | ||
| 289 | + align-items: center; | ||
| 290 | + justify-content: center; | ||
| 291 | + margin-bottom: 24rpx; | ||
| 292 | + | ||
| 293 | + .header-line { | ||
| 294 | + width: 40rpx; | ||
| 295 | + height: 2rpx; | ||
| 296 | + background-color: #dcdfe6; | ||
| 297 | + } | ||
| 298 | + | ||
| 299 | + .header-title { | ||
| 300 | + font-size: 28rpx; | ||
| 301 | + font-weight: 600; | ||
| 302 | + color: #909399; | ||
| 303 | + margin: 0 20rpx; | ||
| 304 | + } | ||
| 305 | +} | ||
| 306 | + | ||
| 307 | +/* 核心功能网格 */ | ||
| 308 | +.features-grid { | ||
| 309 | + display: grid; | ||
| 310 | + grid-template-columns: repeat(2, 1fr); | ||
| 311 | + gap: 20rpx; | ||
| 312 | + | ||
| 313 | + .feature-card { | ||
| 314 | + background-color: #ffffff; | ||
| 315 | + border-radius: 16rpx; | ||
| 316 | + padding: 28rpx 20rpx; | ||
| 317 | + display: flex; | ||
| 318 | + flex-direction: column; | ||
| 319 | + align-items: center; | ||
| 320 | + box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.03); | ||
| 321 | + | ||
| 322 | + .feature-icon { | ||
| 323 | + width: 80rpx; | ||
| 324 | + height: 80rpx; | ||
| 325 | + border-radius: 20rpx; | ||
| 326 | + display: flex; | ||
| 327 | + align-items: center; | ||
| 328 | + justify-content: center; | ||
| 329 | + margin-bottom: 16rpx; | ||
| 330 | + } | ||
| 331 | + | ||
| 332 | + .feature-name { | ||
| 333 | + font-size: 28rpx; | ||
| 334 | + font-weight: 600; | ||
| 335 | + color: #303133; | ||
| 336 | + margin-bottom: 8rpx; | ||
| 337 | + } | ||
| 338 | + | ||
| 339 | + .feature-desc { | ||
| 340 | + font-size: 22rpx; | ||
| 341 | + color: #909399; | ||
| 342 | + text-align: center; | ||
| 343 | + line-height: 32rpx; | ||
| 344 | + } | ||
| 345 | + } | ||
| 346 | +} | ||
| 347 | + | ||
| 348 | +/* 步骤流程 */ | ||
| 349 | +.steps-wrapper { | ||
| 350 | + .step-card { | ||
| 351 | + background-color: #ffffff; | ||
| 352 | + border-radius: 16rpx; | ||
| 353 | + padding: 28rpx; | ||
| 354 | + margin-bottom: 20rpx; | ||
| 355 | + box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.03); | ||
| 356 | + | ||
| 357 | + .step-header { | ||
| 358 | + display: flex; | ||
| 359 | + align-items: center; | ||
| 360 | + margin-bottom: 20rpx; | ||
| 361 | + | ||
| 362 | + .step-badge { | ||
| 363 | + width: 44rpx; | ||
| 364 | + height: 44rpx; | ||
| 365 | + background: #2979ff; | ||
| 366 | + border-radius: 12rpx; | ||
| 367 | + display: flex; | ||
| 368 | + align-items: center; | ||
| 369 | + justify-content: center; | ||
| 370 | + font-size: 26rpx; | ||
| 371 | + font-weight: 700; | ||
| 372 | + color: #ffffff; | ||
| 373 | + margin-right: 16rpx; | ||
| 374 | + flex-shrink: 0; | ||
| 375 | + } | ||
| 376 | + | ||
| 377 | + .step-titles { | ||
| 378 | + display: flex; | ||
| 379 | + flex-direction: column; | ||
| 380 | + | ||
| 381 | + .step-main-title { | ||
| 382 | + font-size: 30rpx; | ||
| 383 | + font-weight: 600; | ||
| 384 | + color: #303133; | ||
| 385 | + } | ||
| 386 | + | ||
| 387 | + .step-sub-title { | ||
| 388 | + font-size: 22rpx; | ||
| 389 | + color: #909399; | ||
| 390 | + margin-top: 2rpx; | ||
| 391 | + } | ||
| 392 | + } | ||
| 393 | + } | ||
| 394 | + | ||
| 395 | + .step-details { | ||
| 396 | + padding-left: 60rpx; | ||
| 397 | + | ||
| 398 | + .detail-item { | ||
| 399 | + display: flex; | ||
| 400 | + align-items: flex-start; | ||
| 401 | + margin-bottom: 12rpx; | ||
| 402 | + | ||
| 403 | + .dot { | ||
| 404 | + width: 10rpx; | ||
| 405 | + height: 10rpx; | ||
| 406 | + background-color: #2979ff; | ||
| 407 | + border-radius: 50%; | ||
| 408 | + margin-top: 12rpx; | ||
| 409 | + margin-right: 12rpx; | ||
| 410 | + flex-shrink: 0; | ||
| 411 | + } | ||
| 412 | + | ||
| 413 | + .detail-text { | ||
| 414 | + font-size: 25rpx; | ||
| 415 | + color: #606266; | ||
| 416 | + line-height: 36rpx; | ||
| 417 | + flex: 1; | ||
| 418 | + } | ||
| 419 | + } | ||
| 420 | + } | ||
| 421 | + | ||
| 422 | + .step-tip { | ||
| 423 | + display: flex; | ||
| 424 | + align-items: center; | ||
| 425 | + background-color: #fdf6ec; | ||
| 426 | + border-radius: 8rpx; | ||
| 427 | + padding: 12rpx 16rpx; | ||
| 428 | + margin-top: 16rpx; | ||
| 429 | + margin-left: 60rpx; | ||
| 430 | + | ||
| 431 | + .tip-text { | ||
| 432 | + font-size: 22rpx; | ||
| 433 | + color: #e6a23c; | ||
| 434 | + margin-left: 10rpx; | ||
| 435 | + line-height: 32rpx; | ||
| 436 | + flex: 1; | ||
| 437 | + } | ||
| 438 | + } | ||
| 439 | + } | ||
| 440 | +} | ||
| 441 | + | ||
| 442 | +/* FAQ 折叠面板适配 */ | ||
| 443 | +.faq-wrapper { | ||
| 444 | + background-color: #ffffff; | ||
| 445 | + border-radius: 16rpx; | ||
| 446 | + padding: 10rpx 20rpx; | ||
| 447 | + box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.03); | ||
| 448 | + | ||
| 449 | + .faq-title-box { | ||
| 450 | + display: flex; | ||
| 451 | + align-items: center; | ||
| 452 | + | ||
| 453 | + .faq-question-text { | ||
| 454 | + font-size: 26rpx; | ||
| 455 | + font-weight: 600; | ||
| 456 | + color: #303133; | ||
| 457 | + } | ||
| 458 | + } | ||
| 459 | + | ||
| 460 | + .faq-answer-box { | ||
| 461 | + display: flex; | ||
| 462 | + align-items: flex-start; | ||
| 463 | + padding: 12rpx 0; | ||
| 464 | + | ||
| 465 | + .faq-answer-text { | ||
| 466 | + font-size: 24rpx; | ||
| 467 | + color: #606266; | ||
| 468 | + line-height: 36rpx; | ||
| 469 | + flex: 1; | ||
| 470 | + } | ||
| 471 | + } | ||
| 472 | + | ||
| 473 | + .faq-tag { | ||
| 474 | + width: 36rpx; | ||
| 475 | + height: 36rpx; | ||
| 476 | + border-radius: 8rpx; | ||
| 477 | + display: flex; | ||
| 478 | + align-items: center; | ||
| 479 | + justify-content: center; | ||
| 480 | + font-size: 20rpx; | ||
| 481 | + font-weight: 700; | ||
| 482 | + margin-right: 16rpx; | ||
| 483 | + flex-shrink: 0; | ||
| 484 | + | ||
| 485 | + &.q-tag { | ||
| 486 | + background-color: #2979ff; | ||
| 487 | + color: #ffffff; | ||
| 488 | + } | ||
| 489 | + | ||
| 490 | + &.a-tag { | ||
| 491 | + background-color: #e1f3d8; | ||
| 492 | + color: #67c23a; | ||
| 493 | + } | ||
| 494 | + } | ||
| 495 | +} | ||
| 496 | + | ||
| 497 | +/* 底部提示 */ | ||
| 498 | +.footer-section { | ||
| 499 | + padding: 30rpx 0 10rpx; | ||
| 500 | + text-align: center; | ||
| 501 | + | ||
| 502 | + .footer-text { | ||
| 503 | + font-size: 22rpx; | ||
| 504 | + color: #c0c4cc; | ||
| 505 | + } | ||
| 506 | +} | ||
| 507 | +</style> |
| 1 | <template> | 1 | <template> |
| 2 | - <view class="paike-wrap"> | ||
| 3 | - <!-- 顶部导航 --> | ||
| 4 | - <view class="page-header" hover-class="none"> | ||
| 5 | - <view class="nav-bar" :style="{ | ||
| 6 | - paddingTop: menuButtonInfo.top + 'px', | ||
| 7 | - height: menuButtonInfo.height + 'px' | ||
| 8 | - }"> | ||
| 9 | - <view class="nav-left" @click="goBack"> | ||
| 10 | - <uni-icons class="back-icon" type="left" size="28" color="#fff"></uni-icons> | 2 | + <view class="paike-container"> |
| 3 | + <!-- 1. 自定义顶部导航栏 (uview-plus) --> | ||
| 4 | + <u-navbar | ||
| 5 | + title="排课日历" | ||
| 6 | + :auto-back="true" | ||
| 7 | + placeholder | ||
| 8 | + bg-color="#121212" | ||
| 9 | + title-style="color: #FFFFFF; font-size: 34rpx; font-weight: 600;" | ||
| 10 | + left-icon-color="#FFFFFF" | ||
| 11 | + /> | ||
| 12 | + | ||
| 13 | + <!-- 2. 星期吸顶头部 --> | ||
| 14 | + <view class="week-header-sticky"> | ||
| 15 | + <view class="week-grid"> | ||
| 16 | + <view v-for="item in WEEK_TEXTS" :key="item" class="week-cell"> | ||
| 17 | + {{ item }} | ||
| 11 | </view> | 18 | </view> |
| 12 | - <view class="nav-title">计划排课</view> | ||
| 13 | - <view class="nav-right"></view> | ||
| 14 | </view> | 19 | </view> |
| 15 | </view> | 20 | </view> |
| 16 | - <!-- 星期头部:一二三四五六日 --> | ||
| 17 | - <view class="week-head"> | ||
| 18 | - <view v-for="item in weekText" :key="item" class="week-cell">{{ item }}</view> | 21 | + |
| 22 | + <!-- 3. 加载中状态 --> | ||
| 23 | + <view v-if="loading" class="state-box"> | ||
| 24 | + <u-loading-icon | ||
| 25 | + mode="semicircle" | ||
| 26 | + color="#E9EE50" | ||
| 27 | + text="正在加载排课数据..." | ||
| 28 | + text-color="#999999" | ||
| 29 | + vertical | ||
| 30 | + /> | ||
| 19 | </view> | 31 | </view> |
| 20 | - <!-- 日历主体容器 --> | ||
| 21 | - <view class="calendar-box"> | ||
| 22 | - <!-- 月份标题行 --> | ||
| 23 | - <view v-for="monthItem in monthList" :key="monthItem.month" class="month-wrap"> | ||
| 24 | - <view class="month-title">{{ monthItem.month }}月</view> | ||
| 25 | - <!-- 当月日期网格 Grid7列 --> | 32 | + |
| 33 | + <!-- 4. 日历主体列表 --> | ||
| 34 | + <view v-else-if="monthList.length > 0" class="calendar-content"> | ||
| 35 | + <view | ||
| 36 | + v-for="monthItem in monthList" | ||
| 37 | + :key="`${monthItem.year}-${monthItem.month}`" | ||
| 38 | + class="month-card" | ||
| 39 | + > | ||
| 40 | + <!-- 月份标题 --> | ||
| 41 | + <view class="month-header"> | ||
| 42 | + <text class="month-title">{{ monthItem.year }}年 {{ monthItem.month }}月</text> | ||
| 43 | + </view> | ||
| 44 | + | ||
| 45 | + <!-- 日期 7 列网格 --> | ||
| 26 | <view class="date-grid"> | 46 | <view class="date-grid"> |
| 27 | - <!-- 前置空白占位格子(当月开头非周一填充空白) --> | ||
| 28 | - <view v-for="empty in monthItem.emptyCount" :key="'empty' + empty" class="date-cell empty-cell"></view> | ||
| 29 | - <!-- 有效日期格子 --> | ||
| 30 | - <view v-for="day in monthItem.dayList" :key="day.day" class="date-cell" | ||
| 31 | - @click="openTemplateDatil(day.DailyTemplateId, day.year, day.month, day.day)" | ||
| 32 | - :class="{ hasClass: day.trainName }"> | ||
| 33 | - <!-- 日期数字 --> | ||
| 34 | - <text class="day-num">{{ day.day }}</text> | ||
| 35 | - <!-- 黄色排课标签 --> | ||
| 36 | - <view class="train-label-box"> | ||
| 37 | - <view v-if="day.trainName" class="train-label">{{ day.trainName }}</view> | ||
| 38 | - <view v-else class="empty-label"></view> | 47 | + <!-- 开头空白填充格 --> |
| 48 | + <view | ||
| 49 | + v-for="emptyIdx in monthItem.emptyCount" | ||
| 50 | + :key="`empty-${emptyIdx}`" | ||
| 51 | + class="date-cell cell-empty" | ||
| 52 | + /> | ||
| 53 | + | ||
| 54 | + <!-- 有效日期格 --> | ||
| 55 | + <view | ||
| 56 | + v-for="day in monthItem.dayList" | ||
| 57 | + :key="`${day.year}-${day.month}-${day.day}`" | ||
| 58 | + class="date-cell" | ||
| 59 | + :class="{ 'has-schedule': day.trainName }" | ||
| 60 | + @tap="handleDateClick(day)" | ||
| 61 | + > | ||
| 62 | + <!-- 日期数 --> | ||
| 63 | + <text class="day-number">{{ day.day }}</text> | ||
| 64 | + | ||
| 65 | + <!-- 排课标签/未排课点位 --> | ||
| 66 | + <view class="schedule-box"> | ||
| 67 | + <view v-if="day.trainName" class="schedule-label"> | ||
| 68 | + {{ day.trainName }} | ||
| 69 | + </view> | ||
| 70 | + <view v-else class="schedule-placeholder"> | ||
| 71 | + <view class="dot" /> | ||
| 72 | + </view> | ||
| 39 | </view> | 73 | </view> |
| 40 | </view> | 74 | </view> |
| 41 | </view> | 75 | </view> |
| 42 | </view> | 76 | </view> |
| 77 | + | ||
| 78 | + <!-- 底部提示 --> | ||
| 79 | + <view class="bottom-tip"> | ||
| 80 | + <text>最多仅可设置到未来 {{ MONTHS_AHEAD }} 个月哦~</text> | ||
| 81 | + </view> | ||
| 82 | + </view> | ||
| 83 | + | ||
| 84 | + <!-- 5. 空数据状态 --> | ||
| 85 | + <view v-else class="state-box"> | ||
| 86 | + <u-empty mode="data" text="暂无排课计划数据" icon-size="160rpx" /> | ||
| 43 | </view> | 87 | </view> |
| 44 | - <!-- 底部提示文案(适配底部安全区) --> | ||
| 45 | - <view class="bottom-tip">最多仅可设置到未来2个月哦~</view> | ||
| 46 | </view> | 88 | </view> |
| 47 | </template> | 89 | </template> |
| 48 | 90 | ||
| 49 | <script setup> | 91 | <script setup> |
| 50 | -import { ref, onMounted } from 'vue' | ||
| 51 | -import QueryPlanApi from '@/sheep/api/plan/queryplan'; | ||
| 52 | -import { onLoad } from '@dcloudio/uni-app'; | ||
| 53 | -// 顶部星期文案 | ||
| 54 | -const weekText = ['一', '二', '三', '四', '五', '六', '日'] | 92 | +import { ref, onMounted, onUnmounted } from 'vue' |
| 93 | +import { onLoad } from '@dcloudio/uni-app' | ||
| 94 | +import QueryPlanApi from '@/sheep/api/plan/queryplan' | ||
| 55 | 95 | ||
| 56 | -// 排课接口信息 | ||
| 57 | -const resData = ref({}) | 96 | +// ==================== 常量定义 ==================== |
| 97 | +const WEEK_TEXTS = ['一', '二', '三', '四', '五', '六', '日'] | ||
| 98 | +const MONTHS_AHEAD = 2 // 最多展示未来月份数(当前月 + 2个月) | ||
| 58 | 99 | ||
| 59 | -// 最终渲染日历数据 | 100 | +// ==================== 响应式状态 ==================== |
| 101 | +const planId = ref(0) | ||
| 102 | +const loading = ref(true) | ||
| 60 | const monthList = ref([]) | 103 | const monthList = ref([]) |
| 61 | 104 | ||
| 105 | +// ==================== 生命周期 ==================== | ||
| 106 | +onLoad((options) => { | ||
| 107 | + planId.value = Number(options?.planid) || 0 | ||
| 108 | +}) | ||
| 62 | 109 | ||
| 63 | -const planId = ref(0) | 110 | +onMounted(() => { |
| 111 | + fetchPlanCalendar() | ||
| 112 | + uni.$on('calendarDataRefresh', fetchPlanCalendar) | ||
| 113 | +}) | ||
| 64 | 114 | ||
| 65 | -onLoad((options) => { | ||
| 66 | - // 接收从详情页传过来的 planid | ||
| 67 | - planId.value = Number(options.planid) || 0 | ||
| 68 | - console.log('收到计划ID:', planId.value, typeof planId.value) | 115 | +onUnmounted(() => { |
| 116 | + uni.$off('calendarDataRefresh', fetchPlanCalendar) | ||
| 69 | }) | 117 | }) |
| 70 | 118 | ||
| 71 | -// 格式化日期、后端数据转日历 | ||
| 72 | -const formatCalendar = () => { | 119 | +// ==================== 工具函数(数据解耦) ==================== |
| 73 | 120 | ||
| 74 | - console.log('进入格式化,resData全部数据:', resData.value) | ||
| 75 | - // 没有data直接退出 | ||
| 76 | - if (!resData.value?.data || !Array.isArray(resData.value.data)) { | ||
| 77 | - console.log('data不存在,终止格式化') | ||
| 78 | - monthList.value = [] | ||
| 79 | - return | 121 | +/** |
| 122 | + * 根据基础日期与月份偏移量,计算目标的年、月 | ||
| 123 | + */ | ||
| 124 | +const getTargetYearMonth = (baseDate, offsetMonth) => { | ||
| 125 | + const d = new Date(baseDate.getFullYear(), baseDate.getMonth() + offsetMonth, 1) | ||
| 126 | + return { | ||
| 127 | + year: d.getFullYear(), | ||
| 128 | + month: d.getMonth() + 1, | ||
| 80 | } | 129 | } |
| 81 | - const now = new Date() | ||
| 82 | - const currentYear = now.getFullYear() | ||
| 83 | - const currentMonth = now.getMonth() + 1 | ||
| 84 | - const today = now.getDate() | ||
| 85 | - | ||
| 86 | - const calcMonthArr = [currentMonth, currentMonth + 1, currentMonth + 2] | 130 | +} |
| 87 | 131 | ||
| 88 | - const groupObj = {} | 132 | +/** |
| 133 | + * 计算某月某日是周几,并转换为“以周一为每周第一天”的前置空白格数量 | ||
| 134 | + */ | ||
| 135 | +const calcEmptySlots = (year, month, day = 1) => { | ||
| 136 | + const dayOfWeek = new Date(year, month - 1, day).getDay() | ||
| 137 | + const isoWeekDay = dayOfWeek === 0 ? 7 : dayOfWeek // 周日转为7 | ||
| 138 | + return isoWeekDay - 1 | ||
| 139 | +} | ||
| 89 | 140 | ||
| 90 | - resData.value.data.forEach(item => { | ||
| 91 | - // trainingDate 是 [年, 月, 日] 数组,直接解构 | ||
| 92 | - // const [y, m, d] = item.trainingDate | ||
| 93 | - // const name = item.templates?.[0]?.templateName || '' | 141 | +/** |
| 142 | + * 将后端排课接口数组构建成映射 Map,Key: `${year}-${month}`, Value: { [day]: { trainName, DailyTemplateId } } | ||
| 143 | + */ | ||
| 144 | +const buildTrainMap = (rawList = []) => { | ||
| 145 | + const map = {} | ||
| 146 | + if (!Array.isArray(rawList)) return map | ||
| 94 | 147 | ||
| 95 | - // const key = `${y}-${m}` | ||
| 96 | - // if (!groupObj[key]) groupObj[key] = {} | ||
| 97 | - // groupObj[key][d] = name | 148 | + rawList.forEach((item) => { |
| 149 | + if (!item?.trainingDate || !Array.isArray(item.trainingDate)) return | ||
| 98 | const [y, m, d] = item.trainingDate | 150 | const [y, m, d] = item.trainingDate |
| 99 | const template = item.templates?.[0] | 151 | const template = item.templates?.[0] |
| 100 | - const name = template?.templateName || '' | ||
| 101 | - const DailyTemplateId = template?.id || null | ||
| 102 | - | ||
| 103 | const key = `${y}-${m}` | 152 | const key = `${y}-${m}` |
| 104 | - if (!groupObj[key]) groupObj[key] = {} | ||
| 105 | - groupObj[key][d] = { | ||
| 106 | - trainName: name, | ||
| 107 | - DailyTemplateId: DailyTemplateId | 153 | + |
| 154 | + if (!map[key]) map[key] = {} | ||
| 155 | + map[key][d] = { | ||
| 156 | + trainName: template?.templateName || '', | ||
| 157 | + DailyTemplateId: template?.id || null, | ||
| 108 | } | 158 | } |
| 109 | }) | 159 | }) |
| 160 | + return map | ||
| 161 | +} | ||
| 110 | 162 | ||
| 163 | +/** | ||
| 164 | + * 生成多月份日历完整渲染结构 | ||
| 165 | + */ | ||
| 166 | +const generateCalendarList = (apiData) => { | ||
| 167 | + const now = new Date() | ||
| 168 | + const today = now.getDate() | ||
| 169 | + const trainMap = buildTrainMap(apiData) | ||
| 111 | const result = [] | 170 | const result = [] |
| 112 | - calcMonthArr.forEach((month, idx) => { | ||
| 113 | - const year = currentYear + Math.floor((month - 1) / 12) | ||
| 114 | - const realMonth = month > 12 ? month - 12 : month | ||
| 115 | - const totalDay = new Date(year, realMonth, 0).getDate() | ||
| 116 | - let firstWeek = new Date(`${year}-${realMonth}-01`).getDay() | ||
| 117 | - firstWeek = firstWeek === 0 ? 7 : firstWeek | ||
| 118 | - let emptyCount = firstWeek - 1 | ||
| 119 | - | ||
| 120 | - const dayList = [] | ||
| 121 | - const startDay = idx === 0 ? today : 1 | ||
| 122 | 171 | ||
| 123 | - for (let d = startDay; d <= totalDay; d++) { | ||
| 124 | - // const key = `${year}-${realMonth}` | ||
| 125 | - // const trainName = groupObj[key]?.[d] || '' | ||
| 126 | - | ||
| 127 | - // dayList.push({ day: d, trainName }) | ||
| 128 | - const key = `${year}-${realMonth}` | ||
| 129 | - const trainInfo = groupObj[key]?.[d] || {} | 172 | + for (let i = 0; i <= MONTHS_AHEAD; i++) { |
| 173 | + const { year, month } = getTargetYearMonth(now, i) | ||
| 174 | + const isCurrentMonth = i === 0 | ||
| 175 | + const startDay = isCurrentMonth ? today : 1 | ||
| 176 | + const totalDaysInMonth = new Date(year, month, 0).getDate() | ||
| 177 | + const mapKey = `${year}-${month}` | ||
| 130 | 178 | ||
| 179 | + const dayList = [] | ||
| 180 | + for (let d = startDay; d <= totalDaysInMonth; d++) { | ||
| 181 | + const scheduleInfo = trainMap[mapKey]?.[d] || {} | ||
| 131 | dayList.push({ | 182 | dayList.push({ |
| 132 | - year: year, // 年 | ||
| 133 | - month: realMonth, // 月 | 183 | + year, |
| 184 | + month, | ||
| 134 | day: d, | 185 | day: d, |
| 135 | - trainName: trainInfo.trainName || '', | ||
| 136 | - // templateId: trainInfo.templateId || null | ||
| 137 | - DailyTemplateId: trainInfo.DailyTemplateId || null | 186 | + trainName: scheduleInfo.trainName || '', |
| 187 | + DailyTemplateId: scheduleInfo.DailyTemplateId || null, | ||
| 138 | }) | 188 | }) |
| 139 | } | 189 | } |
| 140 | 190 | ||
| 141 | - if (idx === 0) { | ||
| 142 | - const firstDayWeek = new Date(`${year}-${realMonth}-${startDay}`).getDay() | ||
| 143 | - const currentWeek = firstDayWeek === 0 ? 7 : firstDayWeek | ||
| 144 | - emptyCount = currentWeek - 1 | ||
| 145 | - } | ||
| 146 | - | ||
| 147 | result.push({ | 191 | result.push({ |
| 148 | - month: realMonth, | ||
| 149 | - emptyCount, | ||
| 150 | - dayList | 192 | + year, |
| 193 | + month, | ||
| 194 | + emptyCount: calcEmptySlots(year, month, startDay), | ||
| 195 | + dayList, | ||
| 151 | }) | 196 | }) |
| 152 | - }) | 197 | + } |
| 153 | 198 | ||
| 154 | - monthList.value = result | 199 | + return result |
| 155 | } | 200 | } |
| 156 | 201 | ||
| 157 | -// 返回上一页 | ||
| 158 | -const goBack = () => uni.navigateBack() | ||
| 159 | - | ||
| 160 | -// 胶囊导航信息 | ||
| 161 | -const menuButtonInfo = ref({ | ||
| 162 | - top: 44, | ||
| 163 | - height: 32 | ||
| 164 | -}); | 202 | +// ==================== 接口交互 & 事件处理 ==================== |
| 165 | 203 | ||
| 204 | +/** 加载排课数据 */ | ||
| 205 | +const fetchPlanCalendar = async () => { | ||
| 206 | + if (!planId.value) { | ||
| 207 | + loading.value = false | ||
| 208 | + return | ||
| 209 | + } | ||
| 166 | 210 | ||
| 167 | -// 获取排课日历数据 | ||
| 168 | -const loadPlanCalendar = async () => { | ||
| 169 | - if (!planId.value) return | 211 | + loading.value = true |
| 170 | try { | 212 | try { |
| 171 | - const apiRes = await QueryPlanApi.getPlanArrangeCalendar(planId.value) | ||
| 172 | - console.log('接口返回的计划日历数据:', apiRes.value); | ||
| 173 | - resData.value = apiRes | ||
| 174 | - // 渲染日历 | ||
| 175 | - formatCalendar() | 213 | + const res = await QueryPlanApi.getPlanArrangeCalendar(planId.value) |
| 214 | + // 根据后端统一响应结构兜底处理 | ||
| 215 | + const data = res?.data || res | ||
| 216 | + monthList.value = generateCalendarList(Array.isArray(data) ? data : []) | ||
| 176 | } catch (err) { | 217 | } catch (err) { |
| 177 | - console.error('日历接口报错', err) | 218 | + console.error('[PlanCalendar] 获取排课日历失败:', err) |
| 219 | + uni.showToast({ title: '加载排课失败', icon: 'none' }) | ||
| 220 | + } finally { | ||
| 221 | + loading.value = false | ||
| 178 | } | 222 | } |
| 179 | } | 223 | } |
| 180 | 224 | ||
| 181 | -//查看模板详情 | ||
| 182 | -const openTemplateDatil = (DailyTemplateId, year, month, day) => { | ||
| 183 | - console.log('每日模板ID=', DailyTemplateId); | ||
| 184 | - console.log('完整日期=', year, month, day); | ||
| 185 | - const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}` | 225 | +/** 日期卡片点击事件 */ |
| 226 | +const handleDateClick = (dayItem) => { | ||
| 227 | + const { DailyTemplateId, year, month, day } = dayItem | ||
| 186 | if (!DailyTemplateId) return | 228 | if (!DailyTemplateId) return |
| 187 | - uni.navigateTo({ | ||
| 188 | - url: `/pages4/pages/xunji/xunji-moban-xiangqing?id=${DailyTemplateId}&date=${dateStr}&isOffice=false&isMyPlan=true&isDailytemplateId=true`, | ||
| 189 | - }); | ||
| 190 | -}; | ||
| 191 | 229 | ||
| 192 | -onMounted(async () => { | ||
| 193 | - // #ifdef MP-WEIXIN | ||
| 194 | - try { | ||
| 195 | - const rect = uni.getMenuButtonBoundingClientRect(); | ||
| 196 | - if (rect) { | ||
| 197 | - menuButtonInfo.value = { | ||
| 198 | - top: rect.top, | ||
| 199 | - height: rect.height | ||
| 200 | - }; | ||
| 201 | - } | ||
| 202 | - } catch (e) { } | ||
| 203 | - // #endif | ||
| 204 | - // #ifndef MP-WEIXIN | ||
| 205 | - try { | ||
| 206 | - const systemInfo = uni.getSystemInfoSync(); | ||
| 207 | - const statusBarHeight = systemInfo.statusBarHeight || 20; | ||
| 208 | - menuButtonInfo.value = { | ||
| 209 | - top: statusBarHeight, | ||
| 210 | - height: 44 | ||
| 211 | - }; | ||
| 212 | - } catch (e) { } | ||
| 213 | - // #endif | ||
| 214 | - // 加载排课信息 | ||
| 215 | - loadPlanCalendar() | ||
| 216 | - // formatCalendar() | ||
| 217 | -}); | 230 | + const formattedMonth = String(month).padStart(2, '0') |
| 231 | + const formattedDay = String(day).padStart(2, '0') | ||
| 232 | + const dateStr = `${year}-${formattedMonth}-${formattedDay}` | ||
| 218 | 233 | ||
| 234 | + uni.navigateTo({ | ||
| 235 | + url: `/pages4/pages/xunji/xunji-moban-xiangqing?id=${DailyTemplateId}&date=${dateStr}&isOffice=false&isMyPlan=true&isDailytemplateId=true`, | ||
| 236 | + }) | ||
| 237 | +} | ||
| 219 | </script> | 238 | </script> |
| 220 | 239 | ||
| 221 | <style scoped lang="scss"> | 240 | <style scoped lang="scss"> |
| 222 | -$bg-color: #121212; | ||
| 223 | -$text-white: #fff; | ||
| 224 | -$text-gray: #2f2f2f; | ||
| 225 | -$train-yellow: #e9ee50; | ||
| 226 | -$cell-h: 110rpx; | ||
| 227 | - | ||
| 228 | -.paike-wrap { | ||
| 229 | - background: $bg-color; | 241 | +// ==================== 主题色系与变量 ==================== |
| 242 | +$bg-main: #121212; | ||
| 243 | +$bg-card: #1e1e1e; | ||
| 244 | +$bg-cell: #262626; | ||
| 245 | + | ||
| 246 | +$text-primary: #ffffff; | ||
| 247 | +$text-secondary: #8c8c8c; | ||
| 248 | + | ||
| 249 | +$theme-yellow: #e9ee50; | ||
| 250 | +$theme-yellow-light: rgba(233, 238, 80, 0.15); | ||
| 251 | +$theme-yellow-border: rgba(233, 238, 80, 0.35); | ||
| 252 | + | ||
| 253 | +$cell-height: 116rpx; | ||
| 254 | + | ||
| 255 | +// ==================== 容器根样式 ==================== | ||
| 256 | +.paike-container { | ||
| 230 | min-height: 100vh; | 257 | min-height: 100vh; |
| 231 | - padding-bottom: env(safe-area-inset-bottom); | ||
| 232 | - overflow: hidden; | 258 | + background-color: $bg-main; |
| 259 | + padding-bottom: calc(40rpx + env(safe-area-inset-bottom)); | ||
| 260 | + box-sizing: border-box; | ||
| 233 | } | 261 | } |
| 234 | 262 | ||
| 235 | -.page-header { | ||
| 236 | - width: 100%; | ||
| 237 | - flex-shrink: 0; | ||
| 238 | - background-color: $bg-color; | ||
| 239 | -} | 263 | +// ==================== 星期头吸顶 ==================== |
| 264 | +.week-header-sticky { | ||
| 265 | + position: sticky; | ||
| 266 | + /* #ifdef H5 */ | ||
| 267 | + top: 44px; | ||
| 268 | + /* #endif */ | ||
| 269 | + /* #ifndef H5 */ | ||
| 270 | + top: 0; | ||
| 271 | + /* #endif */ | ||
| 272 | + z-index: 10; | ||
| 273 | + background-color: $bg-main; | ||
| 274 | + padding: 20rpx 16rpx 12rpx; | ||
| 275 | + border-bottom: 1rpx solid rgba(255, 255, 255, 0.06); | ||
| 240 | 276 | ||
| 241 | -.nav-bar { | ||
| 242 | - display: flex; | ||
| 243 | - align-items: center; | ||
| 244 | - justify-content: space-between; | ||
| 245 | - background-color: $bg-color; | ||
| 246 | - padding-left: 16rpx; | ||
| 247 | - padding-right: 16rpx; | ||
| 248 | - box-sizing: content-box; | 277 | + .week-grid { |
| 278 | + display: grid; | ||
| 279 | + grid-template-columns: repeat(7, 1fr); | ||
| 280 | + | ||
| 281 | + .week-cell { | ||
| 282 | + text-align: center; | ||
| 283 | + font-size: 24rpx; | ||
| 284 | + font-weight: 500; | ||
| 285 | + color: $text-secondary; | ||
| 286 | + } | ||
| 287 | + } | ||
| 249 | } | 288 | } |
| 250 | 289 | ||
| 251 | -.nav-left { | ||
| 252 | - width: 60rpx; | 290 | +// ==================== 状态容器 (Loading/Empty) ==================== |
| 291 | +.state-box { | ||
| 253 | display: flex; | 292 | display: flex; |
| 254 | align-items: center; | 293 | align-items: center; |
| 255 | - flex-shrink: 0; | 294 | + justify-content: center; |
| 295 | + min-height: 500rpx; | ||
| 256 | } | 296 | } |
| 257 | 297 | ||
| 258 | -.nav-title { | ||
| 259 | - flex: 1; | ||
| 260 | - text-align: center; | ||
| 261 | - font-size: 36rpx; | ||
| 262 | - font-weight: bold; | ||
| 263 | - color: $text-white; | 298 | +// ==================== 日历主体 ==================== |
| 299 | +.calendar-content { | ||
| 300 | + padding: 20rpx 16rpx 0; | ||
| 264 | } | 301 | } |
| 265 | 302 | ||
| 266 | -.nav-right { | ||
| 267 | - width: 60rpx; | ||
| 268 | - flex-shrink: 0; | ||
| 269 | -} | 303 | +.month-card { |
| 304 | + margin-bottom: 32rpx; | ||
| 305 | + background: $bg-card; | ||
| 306 | + border-radius: 24rpx; | ||
| 307 | + padding: 20rpx 12rpx 16rpx; | ||
| 308 | + border: 1rpx solid rgba(255, 255, 255, 0.05); | ||
| 309 | + box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.3); | ||
| 270 | 310 | ||
| 271 | -.week-head { | ||
| 272 | - margin-top: 10rpx; | ||
| 273 | - display: grid; | ||
| 274 | - grid-template-columns: repeat(7, 1fr); | ||
| 275 | - margin: 20rpx 0; | ||
| 276 | - | ||
| 277 | - .week-cell { | 311 | + .month-header { |
| 278 | text-align: center; | 312 | text-align: center; |
| 279 | - font-size: 25rpx; | ||
| 280 | - color: $text-white; | ||
| 281 | - } | ||
| 282 | -} | 313 | + padding-bottom: 20rpx; |
| 283 | 314 | ||
| 284 | -.month-wrap { | ||
| 285 | - margin-bottom: 30rpx; | ||
| 286 | - background-color: #121212; | ||
| 287 | - | ||
| 288 | - .month-title { | ||
| 289 | - font-size: 25rpx; | ||
| 290 | - color: $text-white; | ||
| 291 | - padding: 15rpx 10rpx; | ||
| 292 | - text-align: center; | 315 | + .month-title { |
| 316 | + font-size: 30rpx; | ||
| 317 | + font-weight: 600; | ||
| 318 | + color: $text-primary; | ||
| 319 | + letter-spacing: 1rpx; | ||
| 320 | + } | ||
| 293 | } | 321 | } |
| 294 | 322 | ||
| 295 | .date-grid { | 323 | .date-grid { |
| 296 | display: grid; | 324 | display: grid; |
| 297 | grid-template-columns: repeat(7, 1fr); | 325 | grid-template-columns: repeat(7, 1fr); |
| 298 | - gap: 1rpx; | 326 | + gap: 8rpx; |
| 299 | } | 327 | } |
| 300 | 328 | ||
| 301 | .date-cell { | 329 | .date-cell { |
| 302 | - height: $cell-h; | ||
| 303 | - background: $bg-color; | ||
| 304 | - border-radius: 8rpx; | ||
| 305 | - padding: 8rpx; | 330 | + height: $cell-height; |
| 331 | + background-color: $bg-cell; | ||
| 332 | + border-radius: 12rpx; | ||
| 333 | + padding: 8rpx 6rpx; | ||
| 334 | + box-sizing: border-box; | ||
| 306 | display: flex; | 335 | display: flex; |
| 307 | flex-direction: column; | 336 | flex-direction: column; |
| 308 | justify-content: space-between; | 337 | justify-content: space-between; |
| 338 | + align-items: center; | ||
| 339 | + transition: all 0.2s ease; | ||
| 309 | 340 | ||
| 310 | - .day-num { | ||
| 311 | - font-size: 25rpx; | ||
| 312 | - color: $text-white; | ||
| 313 | - text-align: center; | ||
| 314 | - display: block; | ||
| 315 | - width: 100%; | 341 | + /* 点击效果 */ |
| 342 | + &:active { | ||
| 343 | + transform: scale(0.95); | ||
| 344 | + opacity: 0.8; | ||
| 316 | } | 345 | } |
| 317 | 346 | ||
| 318 | - .train-label-box { | ||
| 319 | - min-height: 56rpx; | ||
| 320 | - width: 100%; | 347 | + /* 空白填补格 */ |
| 348 | + &.cell-empty { | ||
| 349 | + background-color: transparent !important; | ||
| 350 | + pointer-events: none; | ||
| 321 | } | 351 | } |
| 322 | 352 | ||
| 323 | - .train-label { | ||
| 324 | - background: $train-yellow; | ||
| 325 | - color: #000; | ||
| 326 | - font-size: 22rpx; | ||
| 327 | - padding: 4rpx 3rpx; | ||
| 328 | - border-radius: 2rpx; | ||
| 329 | - line-height: 1.3; | ||
| 330 | - overflow: hidden; | ||
| 331 | - text-overflow: ellipsis; | ||
| 332 | - display: -webkit-box; | ||
| 333 | - -webkit-line-clamp: 2; | ||
| 334 | - -webkit-box-orient: vertical; | ||
| 335 | - height: 56rpx; | ||
| 336 | - text-align: center; | 353 | + /* 有排课状态 */ |
| 354 | + &.has-schedule { | ||
| 355 | + background-color: $theme-yellow-light; | ||
| 356 | + border: 1rpx solid $theme-yellow-border; | ||
| 337 | 357 | ||
| 358 | + .day-number { | ||
| 359 | + color: $theme-yellow; | ||
| 360 | + font-weight: 700; | ||
| 361 | + } | ||
| 338 | } | 362 | } |
| 339 | 363 | ||
| 340 | - .empty-label { | ||
| 341 | - width: 100%; | ||
| 342 | - min-height: 56rpx; | ||
| 343 | - background: $text-gray; | ||
| 344 | - border-radius: 2rpx; | 364 | + .day-number { |
| 365 | + font-size: 26rpx; | ||
| 366 | + color: $text-primary; | ||
| 367 | + font-weight: 500; | ||
| 368 | + line-height: 1; | ||
| 345 | } | 369 | } |
| 346 | - } | ||
| 347 | 370 | ||
| 348 | - .empty-cell { | ||
| 349 | - background: transparent !important; | 371 | + /* 排课内容展示区 */ |
| 372 | + .schedule-box { | ||
| 373 | + width: 100%; | ||
| 374 | + height: 56rpx; | ||
| 375 | + display: flex; | ||
| 376 | + align-items: center; | ||
| 377 | + justify-content: center; | ||
| 378 | + | ||
| 379 | + .schedule-label { | ||
| 380 | + width: 100%; | ||
| 381 | + height: 100%; | ||
| 382 | + background-color: $theme-yellow; | ||
| 383 | + color: #000000; | ||
| 384 | + font-size: 20rpx; | ||
| 385 | + font-weight: 600; | ||
| 386 | + border-radius: 6rpx; | ||
| 387 | + padding: 4rpx; | ||
| 388 | + box-sizing: border-box; | ||
| 389 | + text-align: center; | ||
| 390 | + line-height: 1.25; | ||
| 391 | + | ||
| 392 | + /* 超出两行省略 */ | ||
| 393 | + display: -webkit-box; | ||
| 394 | + -webkit-line-clamp: 2; | ||
| 395 | + line-clamp: 2; | ||
| 396 | + -webkit-box-orient: vertical; | ||
| 397 | + overflow: hidden; | ||
| 398 | + word-break: break-all; | ||
| 399 | + } | ||
| 400 | + | ||
| 401 | + /* 无排课时的精致小点,替代原本突兀的大灰块 */ | ||
| 402 | + .schedule-placeholder { | ||
| 403 | + display: flex; | ||
| 404 | + align-items: center; | ||
| 405 | + justify-content: center; | ||
| 406 | + width: 100%; | ||
| 407 | + height: 100%; | ||
| 408 | + | ||
| 409 | + .dot { | ||
| 410 | + width: 8rpx; | ||
| 411 | + height: 8rpx; | ||
| 412 | + border-radius: 50%; | ||
| 413 | + background-color: rgba(255, 255, 255, 0.15); | ||
| 414 | + } | ||
| 415 | + } | ||
| 416 | + } | ||
| 350 | } | 417 | } |
| 351 | } | 418 | } |
| 352 | 419 | ||
| 420 | +// ==================== 底部提示 ==================== | ||
| 353 | .bottom-tip { | 421 | .bottom-tip { |
| 354 | text-align: center; | 422 | text-align: center; |
| 355 | - font-size: 30rpx; | ||
| 356 | - color: $text-white; | ||
| 357 | - margin-top: 60rpx; | ||
| 358 | - | ||
| 359 | - /* #ifdef H5 */ | ||
| 360 | - margin-bottom: 20px; | ||
| 361 | - /* #endif */ | 423 | + padding: 24rpx 0 10rpx; |
| 362 | 424 | ||
| 425 | + text { | ||
| 426 | + font-size: 24rpx; | ||
| 427 | + color: $text-secondary; | ||
| 428 | + } | ||
| 363 | } | 429 | } |
| 364 | </style> | 430 | </style> |
-
Please register or login to post a comment