Showing
10 changed files
with
1401 additions
and
1616 deletions
Too many changes to show.
To preserve performance only 10 of 10+ files are displayed.
| @@ -32,10 +32,14 @@ SHOPRO_STATIC_URL=http://test.yudao.iocoder.cn | @@ -32,10 +32,14 @@ SHOPRO_STATIC_URL=http://test.yudao.iocoder.cn | ||
| 32 | ### SHOPRO_STATIC_URL = https://file.sheepjs.com | 32 | ### SHOPRO_STATIC_URL = https://file.sheepjs.com |
| 33 | 33 | ||
| 34 | # 前端 H5 访问域名 | 34 | # 前端 H5 访问域名 |
| 35 | -SHOPRO_H5_URL=http://127.0.0.1:3000 | 35 | +SHOPRO_H5_URL=https://xunji.geaktec.com |
| 36 | 36 | ||
| 37 | # 是否开启直播 1 开启直播 | 0 关闭直播 | 37 | # 是否开启直播 1 开启直播 | 0 关闭直播 |
| 38 | SHOPRO_MPLIVE_ON=0 | 38 | SHOPRO_MPLIVE_ON=0 |
| 39 | 39 | ||
| 40 | # 租户ID 默认 1 | 40 | # 租户ID 默认 1 |
| 41 | SHOPRO_TENANT_ID=1 | 41 | SHOPRO_TENANT_ID=1 |
| 42 | + | ||
| 43 | + | ||
| 44 | +# 默认头像 | ||
| 45 | +DEFAULT_AVATAR=https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260526/默认头像_1779779926983.png |
| @@ -6,6 +6,8 @@ | @@ -6,6 +6,8 @@ | ||
| 6 | :placeholder="false" | 6 | :placeholder="false" |
| 7 | activeColor="#000" | 7 | activeColor="#000" |
| 8 | :fixed="true" | 8 | :fixed="true" |
| 9 | + :bottom="0" | ||
| 10 | + :safeAreaInsetBottom="false" | ||
| 9 | > | 11 | > |
| 10 | <up-tabbar-item | 12 | <up-tabbar-item |
| 11 | v-for="(item, index) in tabList" | 13 | v-for="(item, index) in tabList" |
| 1 | { | 1 | { |
| 2 | "name": "智能健身", | 2 | "name": "智能健身", |
| 3 | - "appid": "__UNI__7CAA18B", | 3 | + "appid": "__UNI__8F24C84", |
| 4 | "description": "基于 uni-app + Vue3 技术驱动的在线商城系统,内含诸多功能与丰富的活动,期待您的使用和反馈。", | 4 | "description": "基于 uni-app + Vue3 技术驱动的在线商城系统,内含诸多功能与丰富的活动,期待您的使用和反馈。", |
| 5 | "versionName": "2025.10", | 5 | "versionName": "2025.10", |
| 6 | "versionCode": "183", | 6 | "versionCode": "183", |
| @@ -100,7 +100,6 @@ | @@ -100,7 +100,6 @@ | ||
| 100 | "backgroundColor": "#1a1a1a" | 100 | "backgroundColor": "#1a1a1a" |
| 101 | } | 101 | } |
| 102 | }, | 102 | }, |
| 103 | - | ||
| 104 | { | 103 | { |
| 105 | "path": "pages/xunji/xunji-dongzuo-lianxi", | 104 | "path": "pages/xunji/xunji-dongzuo-lianxi", |
| 106 | "style": { | 105 | "style": { |
| @@ -113,14 +112,12 @@ | @@ -113,14 +112,12 @@ | ||
| 113 | "navigationBarTitleText": "新增超级组" | 112 | "navigationBarTitleText": "新增超级组" |
| 114 | } | 113 | } |
| 115 | }, | 114 | }, |
| 116 | - | ||
| 117 | { | 115 | { |
| 118 | "path": "pages/xunji/wode-jihua-paike", | 116 | "path": "pages/xunji/wode-jihua-paike", |
| 119 | "style": { | 117 | "style": { |
| 120 | "navigationBarTitleText": "我的计划详情的排课设置" | 118 | "navigationBarTitleText": "我的计划详情的排课设置" |
| 121 | } | 119 | } |
| 122 | }, | 120 | }, |
| 123 | - | ||
| 124 | { | 121 | { |
| 125 | "path": "pages/xunji/dongzuo-muluguanli", | 122 | "path": "pages/xunji/dongzuo-muluguanli", |
| 126 | "style": { | 123 | "style": { |
| @@ -131,7 +128,8 @@ | @@ -131,7 +128,8 @@ | ||
| 131 | { | 128 | { |
| 132 | "path": "pages/xunji/jihua-search", | 129 | "path": "pages/xunji/jihua-search", |
| 133 | "style": { | 130 | "style": { |
| 134 | - "navigationBarTitleText": "训记-计划-搜索" | 131 | + "navigationBarTitleText": "计划-搜索", |
| 132 | + "navigationStyle": "default" | ||
| 135 | } | 133 | } |
| 136 | }, | 134 | }, |
| 137 | { | 135 | { |
| @@ -140,7 +138,7 @@ | @@ -140,7 +138,7 @@ | ||
| 140 | "navigationBarTitleText": "" | 138 | "navigationBarTitleText": "" |
| 141 | } | 139 | } |
| 142 | }, | 140 | }, |
| 143 | - { | 141 | + { |
| 144 | "path": "pages/xunji/xunji-wode-jihua", | 142 | "path": "pages/xunji/xunji-wode-jihua", |
| 145 | "style": { | 143 | "style": { |
| 146 | "navigationBarTitleText": "" | 144 | "navigationBarTitleText": "" |
| @@ -158,14 +156,14 @@ | @@ -158,14 +156,14 @@ | ||
| 158 | "navigationBarTitleText": "登录", | 156 | "navigationBarTitleText": "登录", |
| 159 | "navigationStyle": "default" | 157 | "navigationStyle": "default" |
| 160 | } | 158 | } |
| 161 | - }, | ||
| 162 | - { | ||
| 163 | - "path": "pages/index/reset-password", | ||
| 164 | - "style": { | ||
| 165 | - "navigationBarTitleText": "找回密码", | 159 | + }, |
| 160 | + { | ||
| 161 | + "path": "pages/index/reset-password", | ||
| 162 | + "style": { | ||
| 163 | + "navigationBarTitleText": "找回密码", | ||
| 166 | "navigationStyle": "default" | 164 | "navigationStyle": "default" |
| 167 | - } | ||
| 168 | - } | 165 | + } |
| 166 | + } | ||
| 169 | ] | 167 | ] |
| 170 | }, | 168 | }, |
| 171 | { | 169 | { |
| @@ -185,7 +183,6 @@ | @@ -185,7 +183,6 @@ | ||
| 185 | "navigationBarTitleText": "我的常见问题" | 183 | "navigationBarTitleText": "我的常见问题" |
| 186 | } | 184 | } |
| 187 | }, | 185 | }, |
| 188 | - | ||
| 189 | { | 186 | { |
| 190 | "path": "pages/user/wode-guanyu-hongxing", | 187 | "path": "pages/user/wode-guanyu-hongxing", |
| 191 | "style": { | 188 | "style": { |
| @@ -210,7 +207,7 @@ | @@ -210,7 +207,7 @@ | ||
| 210 | "navigationBarTitleText": "设置", | 207 | "navigationBarTitleText": "设置", |
| 211 | "navigationStyle": "default" | 208 | "navigationStyle": "default" |
| 212 | } | 209 | } |
| 213 | - } | 210 | + } |
| 214 | ] | 211 | ] |
| 215 | } | 212 | } |
| 216 | ], | 213 | ], |
| @@ -237,4 +234,4 @@ | @@ -237,4 +234,4 @@ | ||
| 237 | } | 234 | } |
| 238 | ] | 235 | ] |
| 239 | } | 236 | } |
| 240 | -} | 237 | +} |
| @@ -11,9 +11,7 @@ | @@ -11,9 +11,7 @@ | ||
| 11 | <text v-if="memberLevelName" class="tag">{{ memberLevelName }}</text> | 11 | <text v-if="memberLevelName" class="tag">{{ memberLevelName }}</text> |
| 12 | </view> | 12 | </view> |
| 13 | </view> | 13 | </view> |
| 14 | - <view class="right"> | ||
| 15 | - <uni-icons type="right" size="16" color="#000" /> | ||
| 16 | - </view> | 14 | + <uni-icons type="right" size="16" color="#999" /> |
| 17 | </view> | 15 | </view> |
| 18 | 16 | ||
| 19 | <!-- 未登录引导区 --> | 17 | <!-- 未登录引导区 --> |
| @@ -25,57 +23,8 @@ | @@ -25,57 +23,8 @@ | ||
| 25 | <button class="login-btn" hover-class="btn-hover" @click="goLogin">立即登录</button> | 23 | <button class="login-btn" hover-class="btn-hover" @click="goLogin">立即登录</button> |
| 26 | </view> | 24 | </view> |
| 27 | 25 | ||
| 28 | - <!-- --> | ||
| 29 | - <!-- <view class="vip-banner" hover-class="opacity-hover" @click="goAddVip"> | ||
| 30 | - <view class="vip-info"> | ||
| 31 | - <uni-icons type="vip-filled" size="22" color="#f1c40f" /> | ||
| 32 | - <text class="vip-text"> | ||
| 33 | - {{ userInfo.deposit === 1 ? '鸿星·尊享会员 | 已开通' : '鸿星·会员 | 开通只需押金¥199' }} | ||
| 34 | - </text> | ||
| 35 | - </view> | ||
| 36 | - <view class="vip-btn">{{ userInfo.deposit === 1 ? '查看权益' : '立即开通' }}</view> | ||
| 37 | - </view> --> | ||
| 38 | - | ||
| 39 | - <!-- 课程状态快速入口 --> | ||
| 40 | - <!-- <view class="section-card quick-entry"> | ||
| 41 | - <view v-for="entry in quickEntryConfig" :key="entry.type" class="entry-item" hover-class="opacity-hover" | ||
| 42 | - @click="handleQuickEntry(entry.type)"> | ||
| 43 | - <text class="num">{{ userInfo[entry.key] || 0 }}</text> | ||
| 44 | - <text class="label">{{ entry.label }}</text> | ||
| 45 | - </view> | ||
| 46 | - </view> --> | ||
| 47 | - | ||
| 48 | - <!-- 广告位 --> | ||
| 49 | - <!-- <view class="banner-box" @click="goJiamen"> | ||
| 50 | - <image src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/4_1773627891703.png" | ||
| 51 | - mode="aspectFill" class="banner-img" /> | ||
| 52 | - </view> --> | ||
| 53 | - | ||
| 54 | - <!-- 核心应用区 --> | ||
| 55 | - <!-- <view class="section-card apply-section"> | ||
| 56 | - <view v-for="(item, index) in APPLY_CONFIG_LIST" :key="index" class="apply-item" | ||
| 57 | - @click="authNavigateTo(item.url)"> | ||
| 58 | - <view class="icon-bg"> | ||
| 59 | - <image :src="item.icon" class="img" mode="aspectFit" /> | ||
| 60 | - </view> | ||
| 61 | - <text class="text">{{ item.text }}</text> | ||
| 62 | - </view> | ||
| 63 | - </view> --> | ||
| 64 | - | ||
| 65 | - <!-- 资产账户网格区 --> | ||
| 66 | - <!-- <view v-if="userStore.isLogin" class="section-card"> | ||
| 67 | - <view class="account-grid"> | ||
| 68 | - <view v-for="(acc, idx) in accountConfig" :key="idx" class="account-item" @click="authNavigateTo(acc.url)"> | ||
| 69 | - <text class="acc-lab">{{ acc.label }}</text> | ||
| 70 | - <text class="acc-val"> | ||
| 71 | - {{ formatAccountValue(userInfo[acc.key], acc.isFloat) }} | ||
| 72 | - <text v-if="acc.unit" class="unit">{{ acc.unit }}</text> | ||
| 73 | - </text> | ||
| 74 | - </view> | ||
| 75 | - </view> | ||
| 76 | - </view> --> | ||
| 77 | 26 | ||
| 78 | - <!-- 功能矩阵九宫格 --> | 27 | + <!-- 功能区 --> |
| 79 | <view class="section-card icon-grid-box"> | 28 | <view class="section-card icon-grid-box"> |
| 80 | <view class="icon-grid"> | 29 | <view class="icon-grid"> |
| 81 | <view v-for="(item, index) in FUNCTION_CONFIG_LIST" :key="index" class="icon-item" | 30 | <view v-for="(item, index) in FUNCTION_CONFIG_LIST" :key="index" class="icon-item" |
| @@ -95,15 +44,17 @@ | @@ -95,15 +44,17 @@ | ||
| 95 | </view> | 44 | </view> |
| 96 | 45 | ||
| 97 | <!-- 设置区 --> | 46 | <!-- 设置区 --> |
| 98 | - <view v-if="userStore.isLogin" class="section-card"> | ||
| 99 | - <view class="setting-item" @click="authNavigateTo('/pages5/pages/user/wode-shezhi')"> | ||
| 100 | - <text class="setting-text">个人设置</text> | 47 | + <view v-if="userStore.isLogin" class="section-card settings-section"> |
| 48 | + <view class="settings-title">个人设置</view> | ||
| 49 | + | ||
| 50 | + <view class="settings-item" @click="goToForgetPassword"> | ||
| 51 | + <text class="settings-label">修改密码</text> | ||
| 101 | <uni-icons type="right" size="14" color="#E0E0E0" /> | 52 | <uni-icons type="right" size="14" color="#E0E0E0" /> |
| 102 | </view> | 53 | </view> |
| 103 | - <!-- <view class="setting-item" @click="authNavigateTo('/pages5/pages/user/wode-yinsishezhi')"> | ||
| 104 | - <text class="setting-text">隐私中心</text> | ||
| 105 | - <uni-icons type="right" size="14" color="#E0E0E0" /> | ||
| 106 | - </view> --> | 54 | + |
| 55 | + <view class="logout-wrap"> | ||
| 56 | + <button class="logout-btn" @click="handleLogout">退出登录</button> | ||
| 57 | + </view> | ||
| 107 | </view> | 58 | </view> |
| 108 | 59 | ||
| 109 | <Tabbar /> | 60 | <Tabbar /> |
| @@ -113,21 +64,23 @@ | @@ -113,21 +64,23 @@ | ||
| 113 | 64 | ||
| 114 | <script setup> | 65 | <script setup> |
| 115 | import { ref } from 'vue'; | 66 | import { ref } from 'vue'; |
| 67 | +import { onShow } from '@dcloudio/uni-app'; | ||
| 116 | import UserApi from '@/sheep/api/member/user'; | 68 | import UserApi from '@/sheep/api/member/user'; |
| 117 | import MemberApi from '@/sheep/api/member/member'; | 69 | import MemberApi from '@/sheep/api/member/member'; |
| 70 | +import AuthUtil from '@/sheep/api/member/auth'; | ||
| 118 | import useUserStore from '@/sheep/store/user'; | 71 | import useUserStore from '@/sheep/store/user'; |
| 119 | -import { onShow } from '@dcloudio/uni-app'; | ||
| 120 | -import TrainingFloating from '@/pages/TrainingFloating.vue' | ||
| 121 | import { useTrainingStore } from '@/sheep/store/trainingStore'; | 72 | import { useTrainingStore } from '@/sheep/store/trainingStore'; |
| 73 | +import TrainingFloating from '@/pages/TrainingFloating.vue'; | ||
| 74 | + | ||
| 122 | const trainingStore = useTrainingStore(); | 75 | const trainingStore = useTrainingStore(); |
| 123 | 76 | ||
| 124 | 77 | ||
| 125 | -// 响应式数据挂载 | 78 | +// 状态 |
| 126 | const userStore = useUserStore(); | 79 | const userStore = useUserStore(); |
| 127 | const userInfo = ref({}); | 80 | const userInfo = ref({}); |
| 128 | const memberLevelName = ref(''); | 81 | const memberLevelName = ref(''); |
| 129 | 82 | ||
| 130 | -// 固定的 UI 配置 | 83 | +// 配置 |
| 131 | const APPLY_CONFIG_LIST = []; | 84 | const APPLY_CONFIG_LIST = []; |
| 132 | 85 | ||
| 133 | const FUNCTION_CONFIG_LIST = [ | 86 | const FUNCTION_CONFIG_LIST = [ |
| @@ -155,31 +108,23 @@ const FUNCTION_CONFIG_LIST = [ | @@ -155,31 +108,23 @@ const FUNCTION_CONFIG_LIST = [ | ||
| 155 | ]; | 108 | ]; |
| 156 | 109 | ||
| 157 | 110 | ||
| 158 | -// 课程计数状态配置映射 | ||
| 159 | const quickEntryConfig = [ | 111 | const quickEntryConfig = [ |
| 160 | { type: 1, key: 'courseNum', label: '待上课' }, | 112 | { type: 1, key: 'courseNum', label: '待上课' }, |
| 161 | { type: 2, key: 'courseWaitNum', label: '等待中' }, | 113 | { type: 2, key: 'courseWaitNum', label: '等待中' }, |
| 162 | { type: 3, key: 'courseEvaluationWaitNum', label: '历史课程' }, | 114 | { type: 3, key: 'courseEvaluationWaitNum', label: '历史课程' }, |
| 163 | ]; | 115 | ]; |
| 164 | 116 | ||
| 165 | -// 资产账户字段清洗配置映射 (对应接口数据类型:number 与 integer) | 117 | +// 资产账户 |
| 118 | + | ||
| 166 | const accountConfig = []; | 119 | const accountConfig = []; |
| 167 | 120 | ||
| 168 | -/** | ||
| 169 | - * JSDoc 核心资产数值洗涤函数 | ||
| 170 | - * @param {number|undefined} val 原始金钱/数量值 | ||
| 171 | - * @param {boolean} isFloat 是否需要保留两位小数 | ||
| 172 | - * @returns {string|number} 格式化后的安全渲染字符串 | ||
| 173 | - */ | ||
| 174 | const formatAccountValue = (val, isFloat) => { | 121 | const formatAccountValue = (val, isFloat) => { |
| 175 | if (val === undefined || val === null) return isFloat ? '0.00' : 0; | 122 | if (val === undefined || val === null) return isFloat ? '0.00' : 0; |
| 176 | return isFloat ? Number(val).toFixed(2) : Math.floor(val); | 123 | return isFloat ? Number(val).toFixed(2) : Math.floor(val); |
| 177 | }; | 124 | }; |
| 178 | 125 | ||
| 179 | -/** | ||
| 180 | - * 路由守卫拦截转发 | ||
| 181 | - * @param {string} url 目标绝对/相对地址 | ||
| 182 | - */ | 126 | +/** 路由守卫 */ |
| 127 | + | ||
| 183 | const authNavigateTo = (url) => { | 128 | const authNavigateTo = (url) => { |
| 184 | if (!userStore.isLogin) { | 129 | if (!userStore.isLogin) { |
| 185 | goLogin(); | 130 | goLogin(); |
| @@ -191,51 +136,26 @@ const authNavigateTo = (url) => { | @@ -191,51 +136,26 @@ const authNavigateTo = (url) => { | ||
| 191 | }); | 136 | }); |
| 192 | }; | 137 | }; |
| 193 | 138 | ||
| 194 | -/** | ||
| 195 | - * 统一处理功能网格的点击分发 | ||
| 196 | - * @param {Object} item 节点配置项 | ||
| 197 | - */ | 139 | +/** 功能网格点击分发 */ |
| 140 | + | ||
| 198 | const handleGridItemClick = (item) => { | 141 | const handleGridItemClick = (item) => { |
| 199 | if (item.text === '联系客服') { | 142 | if (item.text === '联系客服') { |
| 200 | // #ifndef MP-WEIXIN | 143 | // #ifndef MP-WEIXIN |
| 201 | - // 兜底非微信小程序平台(如H5、App),可以正常走原来的普通客服页面路由 | ||
| 202 | authNavigateTo(item.url); | 144 | authNavigateTo(item.url); |
| 203 | // #endif | 145 | // #endif |
| 204 | return; | 146 | return; |
| 205 | } | 147 | } |
| 206 | - | ||
| 207 | - // 其他正常功能,正常走路由拦截守卫 | ||
| 208 | authNavigateTo(item.url); | 148 | authNavigateTo(item.url); |
| 209 | }; | 149 | }; |
| 210 | 150 | ||
| 211 | -/** | ||
| 212 | - * 异步高内聚合并请求 | ||
| 213 | - * 解决因先后触发 setData 导致微信小程序底层 AppService 与 WebView 之间高频拥堵卡顿的问题 | ||
| 214 | - */ | 151 | +/** 拉取用户信息 */ |
| 152 | + | ||
| 215 | const fetchPageData = async () => { | 153 | const fetchPageData = async () => { |
| 216 | try { | 154 | try { |
| 217 | - // const [userRes, levelRes] = await Promise.all([ | ||
| 218 | - // UserApi.getUserInfo(), | ||
| 219 | - // // MemberApi.getMemberLevel(), | ||
| 220 | - // ]); | ||
| 221 | - | ||
| 222 | const userRes = await UserApi.getUserInfo(); | 155 | const userRes = await UserApi.getUserInfo(); |
| 223 | - | ||
| 224 | - console.log('后端返回个人信息:', userRes.data); | ||
| 225 | - const rawUser = userRes.data || {}; | ||
| 226 | - userInfo.value = rawUser; | ||
| 227 | - | ||
| 228 | - | ||
| 229 | - // 架构重构:数据拉取后一次性计算出等级映射结果,拒绝在 computed 内部循环执行实例化 | ||
| 230 | - // const levels = levelRes.data?.detailList || []; | ||
| 231 | - // if (rawUser.level !== undefined) { | ||
| 232 | - // // const target = levels.find((item) => item.id === rawUser.level); | ||
| 233 | - // // memberLevelName.value = target ? target.name : ''; | ||
| 234 | - // } else { | ||
| 235 | - // // memberLevelName.value = ''; | ||
| 236 | - // } | 156 | + userInfo.value = userRes.data || {}; |
| 237 | } catch (error) { | 157 | } catch (error) { |
| 238 | - console.error('[API Error] 拉取个人资产信息流失败:', error); | 158 | + console.error('[API Error] 拉取个人信息失败:', error); |
| 239 | } | 159 | } |
| 240 | }; | 160 | }; |
| 241 | 161 | ||
| @@ -248,7 +168,33 @@ onShow(() => { | @@ -248,7 +168,33 @@ onShow(() => { | ||
| 248 | } | 168 | } |
| 249 | }); | 169 | }); |
| 250 | 170 | ||
| 251 | -// 路由跳转原子原子层 | 171 | +// 设置 |
| 172 | + | ||
| 173 | +const goToForgetPassword = () => { | ||
| 174 | + uni.navigateTo({ | ||
| 175 | + url: '/pages7/pages/index/reset-password?isModify=true', | ||
| 176 | + }); | ||
| 177 | +}; | ||
| 178 | + | ||
| 179 | +const handleLogout = () => { | ||
| 180 | + uni.showModal({ | ||
| 181 | + title: '提示', | ||
| 182 | + content: '确定要退出登录吗?', | ||
| 183 | + success: async (res) => { | ||
| 184 | + if (!res.confirm) return; | ||
| 185 | + try { | ||
| 186 | + await AuthUtil.logout(); | ||
| 187 | + userStore.logout(); | ||
| 188 | + uni.reLaunch({ url: '/pages7/pages/index/login' }); | ||
| 189 | + } catch (err) { | ||
| 190 | + console.error('[API Error] 退出登录失败:', err); | ||
| 191 | + uni.showToast({ title: '退出失败', icon: 'none' }); | ||
| 192 | + } | ||
| 193 | + }, | ||
| 194 | + }); | ||
| 195 | +}; | ||
| 196 | + | ||
| 197 | +// 导航 | ||
| 252 | const goLogin = () => uni.navigateTo({ url: '/pages7/pages/index/login' }); | 198 | const goLogin = () => uni.navigateTo({ url: '/pages7/pages/index/login' }); |
| 253 | const goMyPersonalData = () => authNavigateTo('/pages5/pages/user/wode-geren-ziliao'); | 199 | const goMyPersonalData = () => authNavigateTo('/pages5/pages/user/wode-geren-ziliao'); |
| 254 | const goAddVip = () => authNavigateTo('/pages5/pages/user/wode-hongxing-huiyuan'); | 200 | const goAddVip = () => authNavigateTo('/pages5/pages/user/wode-hongxing-huiyuan'); |
| @@ -258,286 +204,215 @@ const handleQuickEntry = (type) => authNavigateTo(`/pages5/pages/user/wode-shang | @@ -258,286 +204,215 @@ const handleQuickEntry = (type) => authNavigateTo(`/pages5/pages/user/wode-shang | ||
| 258 | </script> | 204 | </script> |
| 259 | 205 | ||
| 260 | <style scoped lang="scss"> | 206 | <style scoped lang="scss"> |
| 261 | -$brand-color: #ff6b00; | ||
| 262 | -$page-bg: #f8f8f8; | ||
| 263 | - | 207 | +// 变量 |
| 208 | +// 主色 | ||
| 209 | +$color-primary: #ff6b00; | ||
| 210 | +$color-danger: #ff4757; | ||
| 211 | +$color-danger-active: #e63946; | ||
| 212 | +$color-badge: #ff4d4f; | ||
| 213 | + | ||
| 214 | +// 文字 | ||
| 215 | +$color-text-primary: #333333; | ||
| 216 | +$color-text-secondary: #555555; | ||
| 217 | +$color-text-tertiary: #999999; | ||
| 218 | +$color-text-white: #ffffff; | ||
| 219 | + | ||
| 220 | +// 背景 | ||
| 221 | +$color-bg-page: #f7f7f8; | ||
| 222 | +$color-bg-card: #ffffff; | ||
| 223 | +$color-bg-avatar: #f0f0f0; | ||
| 224 | +$color-bg-hover: #fcfcfc; | ||
| 225 | + | ||
| 226 | +// 边框 | ||
| 227 | +$color-border: #f0f0f0; | ||
| 228 | + | ||
| 229 | +// 圆角 | ||
| 230 | +$radius-card: 24rpx; | ||
| 231 | +$radius-button: 16rpx; | ||
| 232 | +$radius-round: 50%; | ||
| 233 | +$radius-tag: 8rpx; | ||
| 234 | +$radius-badge: 16rpx; | ||
| 235 | + | ||
| 236 | +// 间距 | ||
| 237 | +$gap-section: 24rpx; | ||
| 238 | +$gap-page-h: 28rpx; | ||
| 239 | +$padding-card: 30rpx 28rpx; | ||
| 240 | + | ||
| 241 | +// 字号 | ||
| 242 | +$font-title: 32rpx; | ||
| 243 | +$font-body: 28rpx; | ||
| 244 | +$font-caption: 24rpx; | ||
| 245 | +$font-small: 22rpx; | ||
| 246 | +$font-mini: 18rpx; | ||
| 247 | + | ||
| 248 | +// 效果 | ||
| 249 | +$shadow-card: 0 4rpx 20rpx rgba(0, 0, 0, 0.06); | ||
| 250 | +$transition-tap: background-color 0.2s ease, opacity 0.2s ease; | ||
| 251 | + | ||
| 252 | +// 页面 | ||
| 264 | .my-page { | 253 | .my-page { |
| 265 | min-height: 100vh; | 254 | min-height: 100vh; |
| 266 | - background-color: $page-bg; | ||
| 267 | - padding: 20rpx 28rpx calc(40rpx + env(safe-area-inset-bottom)); | ||
| 268 | - /* 解决部分 iOS 底部 Tabbar 高度塌陷 */ | 255 | + background-color: $color-bg-page; |
| 256 | + padding: 20rpx $gap-page-h calc(40rpx + env(safe-area-inset-bottom)); | ||
| 269 | box-sizing: border-box; | 257 | box-sizing: border-box; |
| 270 | 258 | ||
| 259 | + // 卡片 | ||
| 271 | .section-card { | 260 | .section-card { |
| 272 | - background: #ffffff; | ||
| 273 | - border-radius: 24rpx; | ||
| 274 | - padding: 30rpx 20rpx; | ||
| 275 | - margin-bottom: 24rpx; | ||
| 276 | - box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03); | 261 | + background: $color-bg-card; |
| 262 | + border-radius: $radius-card; | ||
| 263 | + padding: $padding-card; | ||
| 264 | + margin-bottom: $gap-section; | ||
| 265 | + box-shadow: $shadow-card; | ||
| 277 | } | 266 | } |
| 278 | 267 | ||
| 268 | + // 登录头部 | ||
| 279 | .user-header { | 269 | .user-header { |
| 280 | display: flex; | 270 | display: flex; |
| 281 | align-items: center; | 271 | align-items: center; |
| 282 | - padding: 34rpx 30rpx; | 272 | + padding: 36rpx $gap-page-h; |
| 283 | 273 | ||
| 284 | .avatar { | 274 | .avatar { |
| 285 | width: 110rpx; | 275 | width: 110rpx; |
| 286 | height: 110rpx; | 276 | height: 110rpx; |
| 287 | - border-radius: 50%; | ||
| 288 | - background: #f0f0f0; | ||
| 289 | - border: 4rpx solid #fff; | 277 | + border-radius: $radius-round; |
| 278 | + background: $color-bg-avatar; | ||
| 279 | + border: 4rpx solid $color-bg-card; | ||
| 280 | + flex-shrink: 0; | ||
| 290 | } | 281 | } |
| 291 | 282 | ||
| 292 | .info-content { | 283 | .info-content { |
| 293 | margin-left: 24rpx; | 284 | margin-left: 24rpx; |
| 294 | flex: 1; | 285 | flex: 1; |
| 286 | + min-width: 0; | ||
| 295 | 287 | ||
| 296 | .name-row { | 288 | .name-row { |
| 297 | display: flex; | 289 | display: flex; |
| 298 | flex-direction: column; | 290 | flex-direction: column; |
| 291 | + gap: 8rpx; | ||
| 299 | 292 | ||
| 300 | .nickname { | 293 | .nickname { |
| 301 | - font-size: 34rpx; | ||
| 302 | - font-weight: bold; | ||
| 303 | - color: #333; | 294 | + font-size: 36rpx; |
| 295 | + font-weight: 600; | ||
| 296 | + color: $color-text-primary; | ||
| 304 | } | 297 | } |
| 305 | 298 | ||
| 306 | .tag { | 299 | .tag { |
| 307 | - font-size: 20rpx; | ||
| 308 | - color: $brand-color; | ||
| 309 | - | 300 | + display: inline-block; |
| 301 | + font-size: $font-mini; | ||
| 302 | + color: $color-primary; | ||
| 310 | padding: 4rpx 12rpx; | 303 | padding: 4rpx 12rpx; |
| 311 | - border-radius: 8rpx; | ||
| 312 | - | ||
| 313 | - vertical-align: middle; | 304 | + border-radius: $radius-tag; |
| 305 | + background: rgba($color-primary, 0.08); | ||
| 314 | } | 306 | } |
| 315 | } | 307 | } |
| 316 | } | 308 | } |
| 317 | 309 | ||
| 318 | - .right { | ||
| 319 | - display: flex; | ||
| 320 | - align-items: center; | ||
| 321 | - gap: 60rpx; | ||
| 322 | - | ||
| 323 | - .img { | ||
| 324 | - width: 60rpx; | ||
| 325 | - height: 60rpx; | ||
| 326 | - } | ||
| 327 | - } | ||
| 328 | } | 310 | } |
| 329 | 311 | ||
| 312 | + // 未登录引导 | ||
| 330 | .login-guide-box { | 313 | .login-guide-box { |
| 331 | display: flex; | 314 | display: flex; |
| 332 | justify-content: space-between; | 315 | justify-content: space-between; |
| 333 | align-items: center; | 316 | align-items: center; |
| 317 | + padding: 36rpx $gap-page-h; | ||
| 334 | 318 | ||
| 335 | - .title { | ||
| 336 | - font-size: 32rpx; | ||
| 337 | - font-weight: bold; | ||
| 338 | - color: #333; | ||
| 339 | - } | 319 | + .guide-txt { |
| 320 | + .title { | ||
| 321 | + font-size: $font-title; | ||
| 322 | + font-weight: 600; | ||
| 323 | + color: $color-text-primary; | ||
| 324 | + display: block; | ||
| 325 | + } | ||
| 340 | 326 | ||
| 341 | - .desc { | ||
| 342 | - font-size: 22rpx; | ||
| 343 | - color: #999; | ||
| 344 | - margin-top: 4rpx; | ||
| 345 | - display: block; | 327 | + .desc { |
| 328 | + font-size: $font-small; | ||
| 329 | + color: $color-text-tertiary; | ||
| 330 | + margin-top: 6rpx; | ||
| 331 | + display: block; | ||
| 332 | + } | ||
| 346 | } | 333 | } |
| 347 | 334 | ||
| 348 | .login-btn { | 335 | .login-btn { |
| 349 | margin: 0; | 336 | margin: 0; |
| 350 | - background: $brand-color; | ||
| 351 | - color: #fff; | ||
| 352 | - font-size: 24rpx; | 337 | + flex-shrink: 0; |
| 338 | + background: $color-primary; | ||
| 339 | + color: $color-text-white; | ||
| 340 | + font-size: $font-caption; | ||
| 353 | height: 64rpx; | 341 | height: 64rpx; |
| 354 | line-height: 64rpx; | 342 | line-height: 64rpx; |
| 355 | border-radius: 32rpx; | 343 | border-radius: 32rpx; |
| 356 | - padding: 0 30rpx; | 344 | + padding: 0 32rpx; |
| 345 | + transition: $transition-tap; | ||
| 357 | 346 | ||
| 358 | &::after { | 347 | &::after { |
| 359 | border: none; | 348 | border: none; |
| 360 | } | 349 | } |
| 361 | 350 | ||
| 362 | - /* 清理小程序 button 默认黑边线 */ | ||
| 363 | - } | ||
| 364 | - } | ||
| 365 | - | ||
| 366 | - .vip-banner { | ||
| 367 | - background: #2b2b2b; | ||
| 368 | - border-radius: 20rpx; | ||
| 369 | - padding: 24rpx 30rpx; | ||
| 370 | - display: flex; | ||
| 371 | - justify-content: space-between; | ||
| 372 | - align-items: center; | ||
| 373 | - margin-bottom: 24rpx; | ||
| 374 | - | ||
| 375 | - .vip-info { | ||
| 376 | - display: flex; | ||
| 377 | - align-items: center; | ||
| 378 | - } | ||
| 379 | - | ||
| 380 | - .vip-text { | ||
| 381 | - color: #f1c40f; | ||
| 382 | - font-size: 24rpx; | ||
| 383 | - margin-left: 14rpx; | ||
| 384 | - } | ||
| 385 | - | ||
| 386 | - .vip-btn { | ||
| 387 | - background: linear-gradient(90deg, #f1c40f, #f39c12); | ||
| 388 | - color: #333; | ||
| 389 | - font-size: 20rpx; | ||
| 390 | - font-weight: bold; | ||
| 391 | - padding: 8rpx 20rpx; | ||
| 392 | - border-radius: 30rpx; | ||
| 393 | - } | ||
| 394 | - } | ||
| 395 | - | ||
| 396 | - .quick-entry { | ||
| 397 | - display: flex; | ||
| 398 | - justify-content: space-around; | ||
| 399 | - | ||
| 400 | - .entry-item { | ||
| 401 | - text-align: center; | ||
| 402 | - | ||
| 403 | - .num { | ||
| 404 | - font-size: 38rpx; | ||
| 405 | - font-weight: bold; | ||
| 406 | - color: #333; | ||
| 407 | - } | ||
| 408 | - | ||
| 409 | - .label { | ||
| 410 | - font-size: 22rpx; | ||
| 411 | - color: #999; | ||
| 412 | - margin-top: 4rpx; | ||
| 413 | - display: block; | ||
| 414 | - } | ||
| 415 | - } | ||
| 416 | - } | ||
| 417 | - | ||
| 418 | - .banner-box { | ||
| 419 | - height: 160rpx; | ||
| 420 | - margin-bottom: 24rpx; | ||
| 421 | - border-radius: 20rpx; | ||
| 422 | - overflow: hidden; | ||
| 423 | - | ||
| 424 | - .banner-img { | ||
| 425 | - width: 100%; | ||
| 426 | - height: 100%; | ||
| 427 | - } | ||
| 428 | - } | ||
| 429 | - | ||
| 430 | - .apply-section { | ||
| 431 | - display: grid; | ||
| 432 | - grid-template-columns: repeat(5, 1fr); | ||
| 433 | - | ||
| 434 | - .apply-item { | ||
| 435 | - display: flex; | ||
| 436 | - flex-direction: column; | ||
| 437 | - align-items: center; | ||
| 438 | - | ||
| 439 | - .icon-bg { | ||
| 440 | - width: 80rpx; | ||
| 441 | - height: 80rpx; | ||
| 442 | - background: #f9f9f9; | ||
| 443 | - border-radius: 20rpx; | ||
| 444 | - display: flex; | ||
| 445 | - align-items: center; | ||
| 446 | - justify-content: center; | ||
| 447 | - margin-bottom: 12rpx; | ||
| 448 | - | ||
| 449 | - .img { | ||
| 450 | - width: 44rpx; | ||
| 451 | - height: 44rpx; | ||
| 452 | - } | ||
| 453 | - } | ||
| 454 | - | ||
| 455 | - .text { | ||
| 456 | - font-size: 22rpx; | ||
| 457 | - color: #666; | ||
| 458 | - } | ||
| 459 | - } | ||
| 460 | - } | ||
| 461 | - | ||
| 462 | - .account-grid { | ||
| 463 | - display: grid; | ||
| 464 | - grid-template-columns: repeat(4, 1fr); | ||
| 465 | - row-gap: 34rpx; | ||
| 466 | - | ||
| 467 | - .account-item { | ||
| 468 | - display: flex; | ||
| 469 | - flex-direction: column; | ||
| 470 | - align-items: center; | ||
| 471 | - | ||
| 472 | - .acc-val { | ||
| 473 | - font-size: 30rpx; | ||
| 474 | - font-weight: bold; | ||
| 475 | - color: #333; | ||
| 476 | - | ||
| 477 | - .unit { | ||
| 478 | - font-size: 20rpx; | ||
| 479 | - font-weight: normal; | ||
| 480 | - color: #666; | ||
| 481 | - margin-left: 2rpx; | ||
| 482 | - } | ||
| 483 | - } | ||
| 484 | - | ||
| 485 | - .acc-lab { | ||
| 486 | - font-size: 22rpx; | ||
| 487 | - color: #999; | ||
| 488 | - margin-top: 4rpx; | 351 | + &:active { |
| 352 | + opacity: 0.85; | ||
| 489 | } | 353 | } |
| 490 | } | 354 | } |
| 491 | } | 355 | } |
| 492 | 356 | ||
| 357 | + // 功能区 | ||
| 493 | .icon-grid { | 358 | .icon-grid { |
| 494 | display: grid; | 359 | display: grid; |
| 495 | grid-template-columns: repeat(4, 1fr); | 360 | grid-template-columns: repeat(4, 1fr); |
| 496 | - row-gap: 40rpx; | 361 | + row-gap: 36rpx; |
| 497 | 362 | ||
| 498 | .icon-item { | 363 | .icon-item { |
| 499 | display: flex; | 364 | display: flex; |
| 500 | flex-direction: column; | 365 | flex-direction: column; |
| 501 | align-items: center; | 366 | align-items: center; |
| 502 | position: relative; | 367 | position: relative; |
| 368 | + transition: $transition-tap; | ||
| 369 | + | ||
| 370 | + &:active { | ||
| 371 | + opacity: 0.7; | ||
| 372 | + } | ||
| 503 | 373 | ||
| 504 | .icon-img-wrap { | 374 | .icon-img-wrap { |
| 505 | position: relative; | 375 | position: relative; |
| 376 | + width: 90rpx; | ||
| 377 | + height: 90rpx; | ||
| 378 | + display: flex; | ||
| 379 | + align-items: center; | ||
| 380 | + justify-content: center; | ||
| 506 | margin-bottom: 12rpx; | 381 | margin-bottom: 12rpx; |
| 382 | + background: #f5f6f8; | ||
| 383 | + border-radius: 22rpx; | ||
| 507 | 384 | ||
| 508 | .icon-img { | 385 | .icon-img { |
| 509 | - width: 46rpx; | ||
| 510 | - height: 46rpx; | 386 | + width: 44rpx; |
| 387 | + height: 44rpx; | ||
| 511 | } | 388 | } |
| 512 | 389 | ||
| 513 | .badge { | 390 | .badge { |
| 514 | position: absolute; | 391 | position: absolute; |
| 515 | - top: -12rpx; | ||
| 516 | - right: -42rpx; | ||
| 517 | - /* 适当拓宽右移,容纳后端多字文本 */ | ||
| 518 | - background: #ff4d4f; | ||
| 519 | - color: #fff; | ||
| 520 | - font-size: 18rpx; | 392 | + top: -10rpx; |
| 393 | + right: -36rpx; | ||
| 394 | + background: $color-badge; | ||
| 395 | + color: $color-text-white; | ||
| 396 | + font-size: $font-mini; | ||
| 521 | padding: 2rpx 10rpx; | 397 | padding: 2rpx 10rpx; |
| 522 | - border-radius: 16rpx; | 398 | + border-radius: $radius-badge; |
| 523 | white-space: nowrap; | 399 | white-space: nowrap; |
| 524 | } | 400 | } |
| 525 | 401 | ||
| 526 | - /* 未读状态小红点 */ | ||
| 527 | .dot-badge { | 402 | .dot-badge { |
| 528 | position: absolute; | 403 | position: absolute; |
| 529 | - top: -4rpx; | ||
| 530 | - right: -4rpx; | 404 | + top: -2rpx; |
| 405 | + right: -2rpx; | ||
| 531 | width: 14rpx; | 406 | width: 14rpx; |
| 532 | height: 14rpx; | 407 | height: 14rpx; |
| 533 | - background: #ff4d4f; | ||
| 534 | - border-radius: 50%; | 408 | + background: $color-badge; |
| 409 | + border-radius: $radius-round; | ||
| 535 | } | 410 | } |
| 536 | } | 411 | } |
| 537 | 412 | ||
| 538 | .icon-text { | 413 | .icon-text { |
| 539 | - font-size: 24rpx; | ||
| 540 | - color: #555; | 414 | + font-size: $font-caption; |
| 415 | + color: $color-text-secondary; | ||
| 541 | } | 416 | } |
| 542 | 417 | ||
| 543 | .mp-contact-overlay-btn { | 418 | .mp-contact-overlay-btn { |
| @@ -547,12 +422,10 @@ $page-bg: #f8f8f8; | @@ -547,12 +422,10 @@ $page-bg: #f8f8f8; | ||
| 547 | width: 100% !important; | 422 | width: 100% !important; |
| 548 | height: 100% !important; | 423 | height: 100% !important; |
| 549 | opacity: 0 !important; | 424 | opacity: 0 !important; |
| 550 | - /* 核心:完全透明 */ | ||
| 551 | border: none !important; | 425 | border: none !important; |
| 552 | padding: 0 !important; | 426 | padding: 0 !important; |
| 553 | margin: 0 !important; | 427 | margin: 0 !important; |
| 554 | z-index: 10; | 428 | z-index: 10; |
| 555 | - /* 确保盖在图表和文字的最上方 */ | ||
| 556 | 429 | ||
| 557 | &::after { | 430 | &::after { |
| 558 | border: none !important; | 431 | border: none !important; |
| @@ -561,29 +434,58 @@ $page-bg: #f8f8f8; | @@ -561,29 +434,58 @@ $page-bg: #f8f8f8; | ||
| 561 | } | 434 | } |
| 562 | } | 435 | } |
| 563 | 436 | ||
| 564 | - .setting-item { | 437 | + // 设置区 |
| 438 | + .settings-section { | ||
| 439 | + padding: 30rpx $gap-page-h; | ||
| 440 | + | ||
| 441 | + .settings-title { | ||
| 442 | + font-size: 30rpx; | ||
| 443 | + font-weight: 600; | ||
| 444 | + color: $color-text-primary; | ||
| 445 | + margin-bottom: 8rpx; | ||
| 446 | + } | ||
| 447 | + } | ||
| 448 | + | ||
| 449 | + .settings-item { | ||
| 565 | display: flex; | 450 | display: flex; |
| 566 | justify-content: space-between; | 451 | justify-content: space-between; |
| 567 | align-items: center; | 452 | align-items: center; |
| 568 | - padding: 24rpx 0; | ||
| 569 | - border-bottom: 1rpx solid #f9f9f9; | ||
| 570 | - | ||
| 571 | - &:last-child { | ||
| 572 | - border-bottom: none; | ||
| 573 | - } | 453 | + padding: 28rpx 0; |
| 454 | + border-bottom: 1rpx solid $color-border; | ||
| 574 | 455 | ||
| 575 | - .setting-text { | ||
| 576 | - font-size: 28rpx; | ||
| 577 | - color: #444; | 456 | + .settings-label { |
| 457 | + font-size: $font-body; | ||
| 458 | + color: $color-text-primary; | ||
| 578 | } | 459 | } |
| 579 | } | 460 | } |
| 580 | 461 | ||
| 581 | - .opacity-hover { | ||
| 582 | - opacity: 0.7; | 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 | + | ||
| 480 | + &:active { | ||
| 481 | + background-color: $color-danger-active; | ||
| 482 | + } | ||
| 483 | + } | ||
| 583 | } | 484 | } |
| 584 | 485 | ||
| 486 | + // 触摸反馈 | ||
| 585 | .card-hover { | 487 | .card-hover { |
| 586 | - background-color: #fcfcfc; | 488 | + background-color: $color-bg-hover; |
| 587 | } | 489 | } |
| 588 | } | 490 | } |
| 589 | </style> | 491 | </style> |
| 1 | <template> | 1 | <template> |
| 2 | <up-popup :show="actionShow" mode="bottom" minHeight="90vh" @close="actionShow = false" bgColor="#1a1a1a"> | 2 | <up-popup :show="actionShow" mode="bottom" minHeight="90vh" @close="actionShow = false" bgColor="#1a1a1a"> |
| 3 | <scroll-view class="action-container" scroll-y> | 3 | <scroll-view class="action-container" scroll-y> |
| 4 | - <view class="header" v-if="type == 1"> | 4 | + <view class="header" v-if="type === 1"> |
| 5 | <view class="explain"> | 5 | <view class="explain"> |
| 6 | <!-- 3D 固定图片 --> | 6 | <!-- 3D 固定图片 --> |
| 7 | <image v-if="modeTab === 0" :src="actionDetail.url3dAnimation" class="media-content" mode="aspectFill" /> | 7 | <image v-if="modeTab === 0" :src="actionDetail.url3dAnimation" class="media-content" mode="aspectFill" /> |
| @@ -32,15 +32,15 @@ | @@ -32,15 +32,15 @@ | ||
| 32 | </view> | 32 | </view> |
| 33 | 33 | ||
| 34 | <view class="mode-tabs" v-if="actionDetail.urlRealPerson || actionDetail.urlTutorial"> | 34 | <view class="mode-tabs" v-if="actionDetail.urlRealPerson || actionDetail.urlTutorial"> |
| 35 | - <view class="tab-item" v-if="actionDetail.url3dAnimation" :class="{ active: modeTab == 0 }" | 35 | + <view class="tab-item" v-if="actionDetail.url3dAnimation" :class="{ active: modeTab === 0 }" |
| 36 | @click="switchModeTab(0)"> | 36 | @click="switchModeTab(0)"> |
| 37 | 3D | 37 | 3D |
| 38 | </view> | 38 | </view> |
| 39 | - <view class="tab-item" v-if="actionDetail.urlRealPerson" :class="{ active: modeTab == 1 }" | 39 | + <view class="tab-item" v-if="actionDetail.urlRealPerson" :class="{ active: modeTab === 1 }" |
| 40 | @click="switchModeTab(1)"> | 40 | @click="switchModeTab(1)"> |
| 41 | 真人 | 41 | 真人 |
| 42 | </view> | 42 | </view> |
| 43 | - <view class="tab-item" v-if="actionDetail.urlTutorial" :class="{ active: modeTab == 2 }" | 43 | + <view class="tab-item" v-if="actionDetail.urlTutorial" :class="{ active: modeTab === 2 }" |
| 44 | @click="switchModeTab(2)"> | 44 | @click="switchModeTab(2)"> |
| 45 | 讲解 | 45 | 讲解 |
| 46 | </view> | 46 | </view> |
| @@ -56,17 +56,18 @@ | @@ -56,17 +56,18 @@ | ||
| 56 | <!-- 只有个人创建的动作,才有这个编辑按钮--> | 56 | <!-- 只有个人创建的动作,才有这个编辑按钮--> |
| 57 | <template v-if="actionDetail?.isSystem === 0"> | 57 | <template v-if="actionDetail?.isSystem === 0"> |
| 58 | <view class="edit-icon" @click="edit"> | 58 | <view class="edit-icon" @click="edit"> |
| 59 | - <uni-icons type="more" size="24" color="#fff"></uni-icons> | 59 | + <up-icon name="more-dot-fill" size="24" color="#fff" /> |
| 60 | </view> | 60 | </view> |
| 61 | </template> | 61 | </template> |
| 62 | - | 62 | + <!-- #ifdef H5 --> |
| 63 | <button class="share-btn" open-type="share"> | 63 | <button class="share-btn" open-type="share"> |
| 64 | - <uni-icons type="paperplane" size="24" color="#fff"></uni-icons> | 64 | + <up-icon name="share-fill" size="24" color="#fff" /> |
| 65 | </button> | 65 | </button> |
| 66 | + <!-- #endif --> | ||
| 66 | <up-icon :name="isFavorite ? 'star-fill' : 'star'" :color="isFavorite ? '#fedc1f' : '#fff'" size="24" | 67 | <up-icon :name="isFavorite ? 'star-fill' : 'star'" :color="isFavorite ? '#fedc1f' : '#fff'" size="24" |
| 67 | @click="toggleCollect"> | 68 | @click="toggleCollect"> |
| 68 | </up-icon> | 69 | </up-icon> |
| 69 | - <!-- <FavoriteBtn :id="actionId" :type="type" /> --> | 70 | + |
| 70 | </view> | 71 | </view> |
| 71 | </view> | 72 | </view> |
| 72 | <!-- 要点,历史,平替动作标签 --> | 73 | <!-- 要点,历史,平替动作标签 --> |
| @@ -106,16 +107,16 @@ | @@ -106,16 +107,16 @@ | ||
| 106 | </view> | 107 | </view> |
| 107 | </view> | 108 | </view> |
| 108 | 109 | ||
| 109 | - <template v-if="type == 1"> | 110 | + <template v-if="type === 1"> |
| 110 | <textarea class="textarea" v-model="actionDetail.note" placeholder="点击填写备注"></textarea> | 111 | <textarea class="textarea" v-model="actionDetail.note" placeholder="点击填写备注"></textarea> |
| 111 | </template> | 112 | </template> |
| 112 | 113 | ||
| 113 | - <template v-if="type == 2"> | 114 | + <template v-if="type === 2"> |
| 114 | <textarea class="textarea" v-model="actionDetail.userNote" placeholder="点击填写备注"></textarea> | 115 | <textarea class="textarea" v-model="actionDetail.userNote" placeholder="点击填写备注"></textarea> |
| 115 | </template> | 116 | </template> |
| 116 | 117 | ||
| 117 | </view> | 118 | </view> |
| 118 | - <view class="memo-box" v-if="type == 1"> | 119 | + <view class="memo-box" v-if="type === 1"> |
| 119 | <view class="memo-title"> | 120 | <view class="memo-title"> |
| 120 | <view class="section-title">步骤</view> | 121 | <view class="section-title">步骤</view> |
| 121 | <view class="pencil-icon"> | 122 | <view class="pencil-icon"> |
| @@ -168,18 +169,7 @@ | @@ -168,18 +169,7 @@ | ||
| 168 | </view> | 169 | </view> |
| 169 | </view> | 170 | </view> |
| 170 | </view> | 171 | </view> |
| 171 | - <!-- 2 历史 --> | ||
| 172 | <view v-if="contentTab === 1" class="tab-pane slide-up"> | 172 | <view v-if="contentTab === 1" class="tab-pane slide-up"> |
| 173 | - <!-- <view class="stat-card highlight"> | ||
| 174 | - <text class="label">最长时长</text> | ||
| 175 | - <text class="value">01:02:03</text> | ||
| 176 | - <text class="date">记录于 2026/03/04</text> | ||
| 177 | - </view> --> | ||
| 178 | - | ||
| 179 | - <!-- <view class="chart-container" v-if="chartData && chartData.series"> | ||
| 180 | - <qiun-data-charts type="line" :opts="chartOpts" :chartData="chartData" /> | ||
| 181 | - </view> --> | ||
| 182 | - | ||
| 183 | <view class="history-list"> | 173 | <view class="history-list"> |
| 184 | <template v-if="historyList && historyList.length > 0"> | 174 | <template v-if="historyList && historyList.length > 0"> |
| 185 | <view class="history-item" v-for="item in historyList" :key="item.id"> | 175 | <view class="history-item" v-for="item in historyList" :key="item.id"> |
| @@ -194,7 +184,7 @@ | @@ -194,7 +184,7 @@ | ||
| 194 | <!-- <view class="difficulty">困难</view> --> | 184 | <!-- <view class="difficulty">困难</view> --> |
| 195 | <view class="difficulty">{{ | 185 | <view class="difficulty">{{ |
| 196 | item.weight ? item.weight + 'kg' : '无负重' | 186 | item.weight ? item.weight + 'kg' : '无负重' |
| 197 | - }}</view> | 187 | + }}</view> |
| 198 | </view> | 188 | </view> |
| 199 | <view class="group-chips"> | 189 | <view class="group-chips"> |
| 200 | <view class="chip" v-for="(set, index) in item.setConfigList" :key="index"> | 190 | <view class="chip" v-for="(set, index) in item.setConfigList" :key="index"> |
| @@ -216,7 +206,7 @@ | @@ -216,7 +206,7 @@ | ||
| 216 | <view v-if="contentTab === 2 && type === 1" class="tab-pane slide-up"> | 206 | <view v-if="contentTab === 2 && type === 1" class="tab-pane slide-up"> |
| 217 | <view class="substitute-list"> | 207 | <view class="substitute-list"> |
| 218 | <view class="sub-item" v-for="item in alternativeActions" :key="item.id" @click="openActionItem(item)"> | 208 | <view class="sub-item" v-for="item in alternativeActions" :key="item.id" @click="openActionItem(item)"> |
| 219 | - <image :src="item.url3dAnimation || lostImage" mode="aspectFill" class="img" /> | 209 | + <image :src="item.url3dAnimation" mode="aspectFill" class="img" /> |
| 220 | <view class="sub-info"> | 210 | <view class="sub-info"> |
| 221 | <text class="name">{{ item.name }}</text> | 211 | <text class="name">{{ item.name }}</text> |
| 222 | 212 | ||
| @@ -238,47 +228,35 @@ | @@ -238,47 +228,35 @@ | ||
| 238 | </template> | 228 | </template> |
| 239 | 229 | ||
| 240 | <script setup> | 230 | <script setup> |
| 241 | -import { onMounted, ref, nextTick } from 'vue'; | 231 | +import { ref, nextTick } from 'vue'; |
| 232 | +import dayjs from 'dayjs'; | ||
| 242 | import ExercisesApi from '@/sheep/api/motion/exercises'; | 233 | import ExercisesApi from '@/sheep/api/motion/exercises'; |
| 243 | import beizhu from '@/pages/xunji/components/beizhu.vue'; | 234 | import beizhu from '@/pages/xunji/components/beizhu.vue'; |
| 244 | import SupersetsApi from '@/sheep/api/motion/supersets'; | 235 | import SupersetsApi from '@/sheep/api/motion/supersets'; |
| 245 | import TrainingApi from '@/sheep/api/Training/traininghistory'; | 236 | import TrainingApi from '@/sheep/api/Training/traininghistory'; |
| 246 | import { onShareAppMessage } from '@dcloudio/uni-app'; | 237 | import { onShareAppMessage } from '@dcloudio/uni-app'; |
| 247 | -import { useTrainingStore } from '@/sheep/store/trainingStore' | ||
| 248 | - | 238 | +import { useTrainingStore } from '@/sheep/store/trainingStore'; |
| 249 | 239 | ||
| 250 | -const trainingStore = useTrainingStore() | 240 | +const trainingStore = useTrainingStore(); |
| 241 | +const lostImage = 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png'; | ||
| 251 | 242 | ||
| 252 | -// 静态配置 | ||
| 253 | - | ||
| 254 | -const alternativeActions = ref([]); // 平替动作列表接口数据 | ||
| 255 | -const loadedHistory = ref(false) | ||
| 256 | -const loadedAlternative = ref(false) | ||
| 257 | - | ||
| 258 | -// 响应式状态 | ||
| 259 | const actionShow = ref(false); | 243 | const actionShow = ref(false); |
| 260 | const modeTab = ref(0); | 244 | const modeTab = ref(0); |
| 261 | const contentTab = ref(0); | 245 | const contentTab = ref(0); |
| 262 | const isFavorite = ref(false); | 246 | const isFavorite = ref(false); |
| 263 | - | ||
| 264 | -// 记录当前动作的详细数据 | ||
| 265 | const actionDetail = ref({}); | 247 | const actionDetail = ref({}); |
| 266 | -// 记录当前动作的id | ||
| 267 | const actionId = ref(0); | 248 | const actionId = ref(0); |
| 268 | -// 记录是超级组还是动作组 1=动作组,2=超级组 | ||
| 269 | const type = ref(0); | 249 | const type = ref(0); |
| 270 | const showBeizhuRef = ref(null); | 250 | const showBeizhuRef = ref(null); |
| 251 | +const historyList = ref([]); | ||
| 252 | +const alternativeActions = ref([]); | ||
| 253 | +const loadedHistory = ref(false); | ||
| 254 | +const loadedAlternative = ref(false); | ||
| 271 | 255 | ||
| 272 | -const historyList = ref([]); // 训练历史列表接口数据 | ||
| 273 | -const lostImage = | ||
| 274 | - 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png'; | ||
| 275 | - | ||
| 276 | -// 切换图表模式 | ||
| 277 | const switchModeTab = (index) => { | 256 | const switchModeTab = (index) => { |
| 278 | modeTab.value = index; | 257 | modeTab.value = index; |
| 279 | }; | 258 | }; |
| 280 | 259 | ||
| 281 | -// 判断链接是否为视频链接 | ||
| 282 | const isVideoUrl = (url) => { | 260 | const isVideoUrl = (url) => { |
| 283 | if (!url) return false; | 261 | if (!url) return false; |
| 284 | const videoExts = ['.mp4', '.mov', '.avi', '.mkv', '.webm']; | 262 | const videoExts = ['.mp4', '.mov', '.avi', '.mkv', '.webm']; |
| @@ -286,13 +264,10 @@ const isVideoUrl = (url) => { | @@ -286,13 +264,10 @@ const isVideoUrl = (url) => { | ||
| 286 | return videoExts.some(ext => lowerUrl.includes(ext)); | 264 | return videoExts.some(ext => lowerUrl.includes(ext)); |
| 287 | }; | 265 | }; |
| 288 | 266 | ||
| 289 | -// 跳转到视频播放的页面 | ||
| 290 | const playVideo = (url) => { | 267 | const playVideo = (url) => { |
| 291 | - uni.navigateTo({ | ||
| 292 | - url: '/pages4/pages/xunji/xunji-shiping?url=' + url, | ||
| 293 | - }); | 268 | + uni.navigateTo({ url: '/pages4/pages/xunji/xunji-shiping?url=' + url }); |
| 294 | }; | 269 | }; |
| 295 | -// 获取动作收藏状态 | 270 | + |
| 296 | const checkExerciseFavorited = async () => { | 271 | const checkExerciseFavorited = async () => { |
| 297 | try { | 272 | try { |
| 298 | const res = await ExercisesApi.checkExerciseFavorited(actionId.value); | 273 | const res = await ExercisesApi.checkExerciseFavorited(actionId.value); |
| @@ -308,21 +283,16 @@ const toggleCollect = async () => { | @@ -308,21 +283,16 @@ const toggleCollect = async () => { | ||
| 308 | try { | 283 | try { |
| 309 | const status = isFavorite.value ? 0 : 1; | 284 | const status = isFavorite.value ? 0 : 1; |
| 310 | console.log('actionId.value=', actionId.value, 'status=', status); | 285 | console.log('actionId.value=', actionId.value, 'status=', status); |
| 311 | - if (type.value == 1) { | ||
| 312 | - console.log('动作收藏传递的参数', actionId.value, status); | 286 | + if (type.value === 1) { |
| 313 | await ExercisesApi.toggleFavorite(actionId.value, status); | 287 | await ExercisesApi.toggleFavorite(actionId.value, status); |
| 314 | - | ||
| 315 | } else { | 288 | } else { |
| 316 | - console.log('超级组收藏传递的参数', actionId.value, status); | ||
| 317 | await SupersetsApi.toggleFavorite(actionId.value, status); | 289 | await SupersetsApi.toggleFavorite(actionId.value, status); |
| 318 | } | 290 | } |
| 319 | isFavorite.value = !isFavorite.value; | 291 | isFavorite.value = !isFavorite.value; |
| 320 | uni.showToast({ title: status === 1 ? '收藏成功' : '取消收藏成功' }); | 292 | uni.showToast({ title: status === 1 ? '收藏成功' : '取消收藏成功' }); |
| 321 | } catch (err) { | 293 | } catch (err) { |
| 322 | console.log(err); | 294 | console.log(err); |
| 323 | - // 请求失败强制回滚UI | ||
| 324 | - // isFavorite.value = originState; | ||
| 325 | - uni.showToast({ title: '操作失败,请重试', icon: 'none' }); | 295 | + |
| 326 | } | 296 | } |
| 327 | }; | 297 | }; |
| 328 | 298 | ||
| @@ -343,7 +313,7 @@ const startTraining = () => { | @@ -343,7 +313,7 @@ const startTraining = () => { | ||
| 343 | title: '当前已有正在进行的训练', | 313 | title: '当前已有正在进行的训练', |
| 344 | icon: 'none' | 314 | icon: 'none' |
| 345 | }); | 315 | }); |
| 346 | - return; | 316 | + return; |
| 347 | } | 317 | } |
| 348 | 318 | ||
| 349 | console.log('动作详情页面的trainingStore.isTraining', trainingStore.isTraining); | 319 | console.log('动作详情页面的trainingStore.isTraining', trainingStore.isTraining); |
| @@ -375,26 +345,16 @@ const open = async (id, typeData) => { | @@ -375,26 +345,16 @@ const open = async (id, typeData) => { | ||
| 375 | contentTab.value = 0; | 345 | contentTab.value = 0; |
| 376 | console.log('动作详情页面接收id和类型:', actionId.value, type.value); | 346 | console.log('动作详情页面接收id和类型:', actionId.value, type.value); |
| 377 | // 如何判断是动作还是超级组 | 347 | // 如何判断是动作还是超级组 |
| 378 | - if (typeData == 1) { | 348 | + if (typeData === 1) { |
| 379 | await loadexercisedetail(actionId.value); | 349 | await loadexercisedetail(actionId.value); |
| 380 | - // await loadAlternativeActions(actionId.value); | ||
| 381 | - console.log('进入动作收藏检查接口'); | ||
| 382 | await checkExerciseFavorited(); | 350 | await checkExerciseFavorited(); |
| 383 | - | ||
| 384 | - | ||
| 385 | } else { | 351 | } else { |
| 386 | await loadsuperdetail(actionId.value); | 352 | await loadsuperdetail(actionId.value); |
| 387 | - console.log('进入超级组收藏检查接口'); | ||
| 388 | await checkSupersetFavorited(); | 353 | await checkSupersetFavorited(); |
| 389 | } | 354 | } |
| 390 | 355 | ||
| 391 | - console.log('请求参数actionId/typeData', actionId.value, type.value); | ||
| 392 | - | ||
| 393 | - // 重置加载训练历史列表和平替动作列表标记 | ||
| 394 | - loadedHistory.value = false | ||
| 395 | - loadedAlternative.value = false | ||
| 396 | - | ||
| 397 | - // loadTrainHistoryDetail(actionId.value, type.value); | 356 | + loadedHistory.value = false; |
| 357 | + loadedAlternative.value = false; | ||
| 398 | actionShow.value = true; | 358 | actionShow.value = true; |
| 399 | }; | 359 | }; |
| 400 | // 打开备注弹窗 | 360 | // 打开备注弹窗 |
| @@ -413,7 +373,7 @@ const handleNoteSave = async (content) => { | @@ -413,7 +373,7 @@ const handleNoteSave = async (content) => { | ||
| 413 | console.log('type.value=', type.value); | 373 | console.log('type.value=', type.value); |
| 414 | 374 | ||
| 415 | try { | 375 | try { |
| 416 | - if (type.value == 2) { | 376 | + if (type.value === 2) { |
| 417 | await SupersetsApi.addNotes({ | 377 | await SupersetsApi.addNotes({ |
| 418 | supersetsId: actionId.value, | 378 | supersetsId: actionId.value, |
| 419 | content: content, | 379 | content: content, |
| @@ -428,7 +388,7 @@ const handleNoteSave = async (content) => { | @@ -428,7 +388,7 @@ const handleNoteSave = async (content) => { | ||
| 428 | } catch (e) { | 388 | } catch (e) { |
| 429 | console.log(e); | 389 | console.log(e); |
| 430 | } finally { | 390 | } finally { |
| 431 | - if (type.value == 1) { | 391 | + if (type.value === 1) { |
| 432 | await loadexercisedetail(actionId.value) | 392 | await loadexercisedetail(actionId.value) |
| 433 | } else { | 393 | } else { |
| 434 | await loadsuperdetail(actionId.value) | 394 | await loadsuperdetail(actionId.value) |
| @@ -451,7 +411,7 @@ onShareAppMessage((res) => { | @@ -451,7 +411,7 @@ onShareAppMessage((res) => { | ||
| 451 | title: actionDetail.value.name || '健身动作分享', // 分享标题 | 411 | title: actionDetail.value.name || '健身动作分享', // 分享标题 |
| 452 | // path: `/pages4/pages/xunji/xunji-dongzuo-xiangqing?id=${id.value}`, // 分享路径 | 412 | // path: `/pages4/pages/xunji/xunji-dongzuo-xiangqing?id=${id.value}`, // 分享路径 |
| 453 | path: `/pages/xunji/xunji?currentTab=${3}`, | 413 | path: `/pages/xunji/xunji?currentTab=${3}`, |
| 454 | - imageUrl: actionDetail.value.urlImage || lostImage, // 分享图片 | 414 | + imageUrl: actionDetail.value.urlImage, // 分享图片 |
| 455 | }; | 415 | }; |
| 456 | }); | 416 | }); |
| 457 | 417 | ||
| @@ -517,53 +477,18 @@ const loadTrainHistoryDetail = async (id, type) => { | @@ -517,53 +477,18 @@ const loadTrainHistoryDetail = async (id, type) => { | ||
| 517 | // 格式化时间 | 477 | // 格式化时间 |
| 518 | const formatDate = (dateArr) => { | 478 | const formatDate = (dateArr) => { |
| 519 | if (!Array.isArray(dateArr) || dateArr.length < 3) return ''; | 479 | if (!Array.isArray(dateArr) || dateArr.length < 3) return ''; |
| 520 | - const [year, month, day] = dateArr; | ||
| 521 | - const date = new Date(year, month - 1, day); | ||
| 522 | - const weekArr = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; | ||
| 523 | - const week = weekArr[date.getDay()]; | ||
| 524 | - return `${year}/${String(month).padStart(2, '0')}/${String(day).padStart(2, '0')} ${week}`; | 480 | + return dayjs(new Date(dateArr[0], dateArr[1] - 1, dateArr[2])).format('YYYY/MM/DD dd'); |
| 525 | }; | 481 | }; |
| 526 | 482 | ||
| 527 | -// 格式化每组数据:自动拼接 weight/reps/duration/distance | ||
| 528 | const formatSetData = (set) => { | 483 | const formatSetData = (set) => { |
| 529 | const parts = []; | 484 | const parts = []; |
| 530 | - | ||
| 531 | - // 重量 | ||
| 532 | - if (set.weight != null && set.weight !== '') { | ||
| 533 | - parts.push(`${set.weight}kg`); | ||
| 534 | - } | ||
| 535 | - | ||
| 536 | - // 次数 | ||
| 537 | - if (set.reps != null && set.reps !== '') { | ||
| 538 | - parts.push(`${set.reps}次`); | ||
| 539 | - } | ||
| 540 | - | ||
| 541 | - // 时长(自动转 时:分:秒) | ||
| 542 | - if (set.duration != null && set.duration !== '') { | ||
| 543 | - parts.push(formatTime(set.duration)); | ||
| 544 | - } | ||
| 545 | - | ||
| 546 | - // 距离 | ||
| 547 | - if (set.distance != null && set.distance !== '') { | ||
| 548 | - parts.push(`${set.distance}m`); | ||
| 549 | - } | ||
| 550 | - | 485 | + if (set.weight != null && set.weight !== '') parts.push(`${set.weight}kg`); |
| 486 | + if (set.reps != null && set.reps !== '') parts.push(`${set.reps}次`); | ||
| 487 | + if (set.duration != null && set.duration !== '') parts.push(dayjs.duration(set.duration, 'seconds').format('HH:mm:ss')); | ||
| 488 | + if (set.distance != null && set.distance !== '') parts.push(`${set.distance}m`); | ||
| 551 | return parts.length > 0 ? parts.join(' × ') : '无数据'; | 489 | return parts.length > 0 ? parts.join(' × ') : '无数据'; |
| 552 | }; | 490 | }; |
| 553 | 491 | ||
| 554 | -// 新增:秒数转 00:00:00 格式 | ||
| 555 | -const formatTime = (seconds) => { | ||
| 556 | - const h = Math.floor(seconds / 3600); | ||
| 557 | - const m = Math.floor((seconds % 3600) / 60); | ||
| 558 | - const s = seconds % 60; | ||
| 559 | - | ||
| 560 | - const hh = h > 0 ? String(h).padStart(2, '0') + ':' : ''; | ||
| 561 | - const mm = String(m).padStart(2, '0') + ':'; | ||
| 562 | - const ss = String(s).padStart(2, '0'); | ||
| 563 | - | ||
| 564 | - return hh + mm + ss; | ||
| 565 | -}; | ||
| 566 | - | ||
| 567 | const edit = () => { | 492 | const edit = () => { |
| 568 | uni.navigateTo({ | 493 | uni.navigateTo({ |
| 569 | url: `/pages4/pages/xunji/xunji-dongzuo-xinzeng?isEdit=true&id=${actionId.value}` | 494 | url: `/pages4/pages/xunji/xunji-dongzuo-xinzeng?isEdit=true&id=${actionId.value}` |
| @@ -577,8 +502,6 @@ const openActionItem = (item) => { | @@ -577,8 +502,6 @@ const openActionItem = (item) => { | ||
| 577 | }; | 502 | }; |
| 578 | 503 | ||
| 579 | defineExpose({ open }); | 504 | defineExpose({ open }); |
| 580 | - | ||
| 581 | -onMounted(() => { }); | ||
| 582 | </script> | 505 | </script> |
| 583 | 506 | ||
| 584 | <style lang="scss" scoped> | 507 | <style lang="scss" scoped> |
| @@ -772,7 +695,6 @@ onMounted(() => { }); | @@ -772,7 +695,6 @@ onMounted(() => { }); | ||
| 772 | } | 695 | } |
| 773 | } | 696 | } |
| 774 | 697 | ||
| 775 | - // 要点板块样式 | ||
| 776 | .video-grid { | 698 | .video-grid { |
| 777 | display: grid; | 699 | display: grid; |
| 778 | grid-template-columns: 1fr 1fr; | 700 | grid-template-columns: 1fr 1fr; |
| @@ -816,7 +738,6 @@ onMounted(() => { }); | @@ -816,7 +738,6 @@ onMounted(() => { }); | ||
| 816 | color: #fff; | 738 | color: #fff; |
| 817 | height: 160rpx; | 739 | height: 160rpx; |
| 818 | border-radius: 16rpx; | 740 | border-radius: 16rpx; |
| 819 | - // padding: 20rpx; | ||
| 820 | font-size: 30rpx; | 741 | font-size: 30rpx; |
| 821 | border: 0; | 742 | border: 0; |
| 822 | } | 743 | } |
| @@ -827,7 +748,6 @@ onMounted(() => { }); | @@ -827,7 +748,6 @@ onMounted(() => { }); | ||
| 827 | flex-direction: column; | 748 | flex-direction: column; |
| 828 | gap: 16rpx; | 749 | gap: 16rpx; |
| 829 | background: #464646; | 750 | background: #464646; |
| 830 | - // padding: 20rpx; | ||
| 831 | box-sizing: border-box; | 751 | box-sizing: border-box; |
| 832 | border-radius: 16rpx; | 752 | border-radius: 16rpx; |
| 833 | 753 | ||
| @@ -877,7 +797,6 @@ onMounted(() => { }); | @@ -877,7 +797,6 @@ onMounted(() => { }); | ||
| 877 | color: #fff; | 797 | color: #fff; |
| 878 | } | 798 | } |
| 879 | 799 | ||
| 880 | - // 历史记录样式 | ||
| 881 | .stat-card { | 800 | .stat-card { |
| 882 | background: linear-gradient(135deg, #333, #222); | 801 | background: linear-gradient(135deg, #333, #222); |
| 883 | padding: 40rpx; | 802 | padding: 40rpx; |
| @@ -933,8 +852,6 @@ onMounted(() => { }); | @@ -933,8 +852,6 @@ onMounted(() => { }); | ||
| 933 | } | 852 | } |
| 934 | 853 | ||
| 935 | .tag { | 854 | .tag { |
| 936 | - // font-size: 20rpx; | ||
| 937 | - // background: #444; | ||
| 938 | margin: 5rpx 5rpx; | 855 | margin: 5rpx 5rpx; |
| 939 | border-radius: 4rpx; | 856 | border-radius: 4rpx; |
| 940 | } | 857 | } |
| @@ -948,7 +865,6 @@ onMounted(() => { }); | @@ -948,7 +865,6 @@ onMounted(() => { }); | ||
| 948 | margin-top: 20rpx; | 865 | margin-top: 20rpx; |
| 949 | 866 | ||
| 950 | .chip { | 867 | .chip { |
| 951 | - // background: #333; | ||
| 952 | padding: 8rpx 20rpx; | 868 | padding: 8rpx 20rpx; |
| 953 | font-size: 24rpx; | 869 | font-size: 24rpx; |
| 954 | display: flex; | 870 | display: flex; |
| @@ -965,7 +881,6 @@ onMounted(() => { }); | @@ -965,7 +881,6 @@ onMounted(() => { }); | ||
| 965 | } | 881 | } |
| 966 | } | 882 | } |
| 967 | 883 | ||
| 968 | - // 平替动作样式 | ||
| 969 | .sub-item { | 884 | .sub-item { |
| 970 | display: flex; | 885 | display: flex; |
| 971 | align-items: center; | 886 | align-items: center; |
| @@ -1024,7 +939,6 @@ onMounted(() => { }); | @@ -1024,7 +939,6 @@ onMounted(() => { }); | ||
| 1024 | } | 939 | } |
| 1025 | } | 940 | } |
| 1026 | 941 | ||
| 1027 | -// 动画 | ||
| 1028 | .slide-up { | 942 | .slide-up { |
| 1029 | animation: slideUp 0.4s ease-out; | 943 | animation: slideUp 0.4s ease-out; |
| 1030 | } | 944 | } |
| @@ -24,138 +24,83 @@ | @@ -24,138 +24,83 @@ | ||
| 24 | </template> | 24 | </template> |
| 25 | 25 | ||
| 26 | <script setup> | 26 | <script setup> |
| 27 | +import dayjs from 'dayjs'; | ||
| 27 | import { ref, computed, watch } from 'vue'; | 28 | import { ref, computed, watch } from 'vue'; |
| 28 | 29 | ||
| 29 | const emit = defineEmits(['dateTypeChange', 'dateRangeChange']); | 30 | const emit = defineEmits(['dateTypeChange', 'dateRangeChange']); |
| 30 | 31 | ||
| 31 | -// 当前日期类型: 'week' | 'month' | 32 | +// 当前日期类型与基准日期 |
| 32 | const currentDateType = ref('week'); | 33 | const currentDateType = ref('week'); |
| 33 | -// 基准日期,用于计算范围 | ||
| 34 | -const baseDate = ref(new Date()); | ||
| 35 | - | ||
| 36 | -// 格式化日期辅助函数 YYYY/MM/DD | ||
| 37 | -const formatDate = (date) => { | ||
| 38 | - const y = date.getFullYear(); | ||
| 39 | - const m = String(date.getMonth() + 1).padStart(2, '0'); | ||
| 40 | - const d = String(date.getDate()).padStart(2, '0'); | ||
| 41 | - return `${y}/${m}/${d}`; | ||
| 42 | -}; | ||
| 43 | -// 格式化日期辅助函数 YYYY-MM-DD | ||
| 44 | -const formatDateYmd = (date) => { | ||
| 45 | - const y = date.getFullYear(); | ||
| 46 | - const m = String(date.getMonth() + 1).padStart(2, '0'); | ||
| 47 | - const d = String(date.getDate()).padStart(2, '0'); | ||
| 48 | - return `${y}-${m}-${d}`; | ||
| 49 | -}; | 34 | +const baseDate = ref(dayjs()); |
| 50 | 35 | ||
| 51 | -const currentDateRange = computed(() => { | ||
| 52 | - const current = new Date(baseDate.value); | ||
| 53 | - let start, end; | 36 | +// ==================== 公共日期范围计算 ==================== |
| 54 | 37 | ||
| 38 | +/** 根据当前类型计算周期起止日期(dayjs 实例) */ | ||
| 39 | +const periodRange = computed(() => { | ||
| 40 | + const d = baseDate.value; | ||
| 55 | if (currentDateType.value === 'week') { | 41 | if (currentDateType.value === 'week') { |
| 56 | - const day = current.getDay(); | ||
| 57 | - const diffToMonday = day === 0 ? -6 : 1 - day; | ||
| 58 | - start = new Date(current); | ||
| 59 | - start.setDate(current.getDate() + diffToMonday); | ||
| 60 | - end = new Date(start); | ||
| 61 | - end.setDate(start.getDate() + 6); | ||
| 62 | - } else { | ||
| 63 | - start = new Date(current.getFullYear(), current.getMonth(), 1); | ||
| 64 | - end = new Date(current.getFullYear(), current.getMonth() + 1, 0); | 42 | + const dayOfWeek = d.day(); // 0=周日, 1=周一, ... |
| 43 | + const start = d.subtract(dayOfWeek === 0 ? 6 : dayOfWeek - 1, 'day'); | ||
| 44 | + const end = start.add(6, 'day'); | ||
| 45 | + return { start, end }; | ||
| 65 | } | 46 | } |
| 66 | - | ||
| 67 | - return { | ||
| 68 | - start: formatDateYmd(start), | ||
| 69 | - end: formatDateYmd(end) | ||
| 70 | - }; | 47 | + // month |
| 48 | + return { start: d.startOf('month'), end: d.endOf('month') }; | ||
| 71 | }); | 49 | }); |
| 72 | 50 | ||
| 73 | -// 计算显示的日期范围文本 | ||
| 74 | -const dateRangeText = computed(() => { | ||
| 75 | - const current = new Date(baseDate.value); | ||
| 76 | - | ||
| 77 | - if (currentDateType.value === 'week') { | ||
| 78 | - // 计算本周的周一和周日 | ||
| 79 | - const day = current.getDay(); | ||
| 80 | - const diffToMonday = day === 0 ? -6 : 1 - day; // 调整到周一 | ||
| 81 | - | ||
| 82 | - const startOfWeek = new Date(current); | ||
| 83 | - startOfWeek.setDate(current.getDate() + diffToMonday); | ||
| 84 | - | ||
| 85 | - const endOfWeek = new Date(startOfWeek); | ||
| 86 | - endOfWeek.setDate(startOfWeek.getDate() + 6); | 51 | +// ==================== 计算属性 ==================== |
| 87 | 52 | ||
| 88 | - return `${formatDate(startOfWeek)} - ${formatDate(endOfWeek)}`; | ||
| 89 | - } else { | ||
| 90 | - // 计算本月的一号和最后一天 | ||
| 91 | - const startOfMonth = new Date(current.getFullYear(), current.getMonth(), 1); | ||
| 92 | - const endOfMonth = new Date(current.getFullYear(), current.getMonth() + 1, 0); | 53 | +/** 对外暴露的日期范围(YYYY-MM-DD) */ |
| 54 | +const currentDateRange = computed(() => ({ | ||
| 55 | + start: periodRange.value.start.format('YYYY-MM-DD'), | ||
| 56 | + end: periodRange.value.end.format('YYYY-MM-DD'), | ||
| 57 | +})); | ||
| 93 | 58 | ||
| 94 | - return `${formatDate(startOfMonth)} - ${formatDate(endOfMonth)}`; | ||
| 95 | - } | 59 | +/** 显示的日期范围文本 "YYYY/MM/DD - YYYY/MM/DD" */ |
| 60 | +const dateRangeText = computed(() => { | ||
| 61 | + const { start, end } = periodRange.value; | ||
| 62 | + return `${start.format('YYYY/MM/DD')} - ${end.format('YYYY/MM/DD')}`; | ||
| 96 | }); | 63 | }); |
| 97 | 64 | ||
| 98 | -// 新增: 计算下一个日期是否不可用(即是否为未来日期) | 65 | +/** 下一周期是否超过今天(超出则隐藏前进按钮) */ |
| 99 | const isNextDisabled = computed(() => { | 66 | const isNextDisabled = computed(() => { |
| 100 | - const newDate = new Date(baseDate.value); | ||
| 101 | - if (currentDateType.value === 'week') { | ||
| 102 | - newDate.setDate(newDate.getDate() + 7); | ||
| 103 | - } else { | ||
| 104 | - newDate.setMonth(newDate.getMonth() + 1); | ||
| 105 | - } | ||
| 106 | - | ||
| 107 | - // 重置时分秒进行比较 | ||
| 108 | - const now = new Date(); | ||
| 109 | - now.setHours(0, 0, 0, 0); | ||
| 110 | - const checkDate = new Date(newDate); | ||
| 111 | - checkDate.setHours(0, 0, 0, 0); | ||
| 112 | - | ||
| 113 | - return checkDate > now; | 67 | + const nextBase = currentDateType.value === 'week' |
| 68 | + ? baseDate.value.add(7, 'day') | ||
| 69 | + : baseDate.value.add(1, 'month'); | ||
| 70 | + return nextBase.startOf('day').isAfter(dayjs().startOf('day')); | ||
| 114 | }); | 71 | }); |
| 115 | 72 | ||
| 116 | -// 切换周/月 | 73 | +// ==================== 事件处理 ==================== |
| 74 | + | ||
| 117 | const switchDateType = (type) => { | 75 | const switchDateType = (type) => { |
| 118 | currentDateType.value = type; | 76 | currentDateType.value = type; |
| 119 | emit('dateTypeChange', type); | 77 | emit('dateTypeChange', type); |
| 120 | }; | 78 | }; |
| 121 | 79 | ||
| 122 | -// 上一段时间 | ||
| 123 | const handlePrev = () => { | 80 | const handlePrev = () => { |
| 124 | - const newDate = new Date(baseDate.value); | ||
| 125 | - if (currentDateType.value === 'week') { | ||
| 126 | - newDate.setDate(newDate.getDate() - 7); | ||
| 127 | - } else { | ||
| 128 | - newDate.setMonth(newDate.getMonth() - 1); | ||
| 129 | - } | ||
| 130 | - baseDate.value = newDate; | 81 | + baseDate.value = currentDateType.value === 'week' |
| 82 | + ? baseDate.value.subtract(7, 'day') | ||
| 83 | + : baseDate.value.subtract(1, 'month'); | ||
| 131 | }; | 84 | }; |
| 132 | 85 | ||
| 133 | -// 下一段时间 | ||
| 134 | const handleNext = () => { | 86 | const handleNext = () => { |
| 135 | - // 虽然按钮已隐藏,但保留逻辑判断以防直接调用或其他边界情况 | ||
| 136 | - if (isNextDisabled.value) { | ||
| 137 | - return; | ||
| 138 | - } | ||
| 139 | - | ||
| 140 | - const newDate = new Date(baseDate.value); | ||
| 141 | - if (currentDateType.value === 'week') { | ||
| 142 | - newDate.setDate(newDate.getDate() + 7); | ||
| 143 | - } else { | ||
| 144 | - newDate.setMonth(newDate.getMonth() + 1); | ||
| 145 | - } | ||
| 146 | - | ||
| 147 | - baseDate.value = newDate; | 87 | + if (isNextDisabled.value) return; |
| 88 | + baseDate.value = currentDateType.value === 'week' | ||
| 89 | + ? baseDate.value.add(7, 'day') | ||
| 90 | + : baseDate.value.add(1, 'month'); | ||
| 148 | }; | 91 | }; |
| 149 | 92 | ||
| 93 | +// ==================== 双向绑定 ==================== | ||
| 94 | + | ||
| 150 | watch( | 95 | watch( |
| 151 | [baseDate, currentDateType], | 96 | [baseDate, currentDateType], |
| 152 | () => { | 97 | () => { |
| 153 | emit('dateRangeChange', { | 98 | emit('dateRangeChange', { |
| 154 | startDate: currentDateRange.value.start, | 99 | startDate: currentDateRange.value.start, |
| 155 | - endDate: currentDateRange.value.end | 100 | + endDate: currentDateRange.value.end, |
| 156 | }); | 101 | }); |
| 157 | }, | 102 | }, |
| 158 | - { immediate: true } | 103 | + { immediate: true }, |
| 159 | ); | 104 | ); |
| 160 | </script> | 105 | </script> |
| 161 | 106 | ||
| @@ -180,10 +125,6 @@ watch( | @@ -180,10 +125,6 @@ watch( | ||
| 180 | padding: 6rpx; | 125 | padding: 6rpx; |
| 181 | box-sizing: border-box; | 126 | box-sizing: border-box; |
| 182 | 127 | ||
| 183 | - // #ifdef H5 | ||
| 184 | - margin-top: 30px; | ||
| 185 | - // #endif | ||
| 186 | - | ||
| 187 | .tab-item { | 128 | .tab-item { |
| 188 | display: flex; | 129 | display: flex; |
| 189 | align-items: center; | 130 | align-items: center; |
| 1 | <template> | 1 | <template> |
| 2 | - <view class="exercise-page" @click="closeAddMenu"> | ||
| 3 | - <!-- 搜索框 --> | ||
| 4 | - <view class="search-bar"> | ||
| 5 | - <!-- <view class="search"> | ||
| 6 | - <uni-icons type="search" size="18"></uni-icons> | ||
| 7 | - <input class="search-input" type="text" placeholder="搜索动作名称" @input="onSearch" /> | ||
| 8 | - </view> --> | ||
| 9 | - <view class="add-btn" @click.stop="addExercise"> | ||
| 10 | - <uni-icons type="plus" size="35" color="#333"></uni-icons> | ||
| 11 | - <view class="floating-menu" v-show="addshow" @click.stop> | ||
| 12 | - <view class="menu-item"> | ||
| 13 | - <uni-icons type="plus" size="24" color="#333"></uni-icons> | ||
| 14 | - <text class="menu-text" @click="addnewmotion">新增动作</text> | 2 | + <view class="exercise-page"> |
| 3 | + <!-- 顶部操作栏:搜索 + 快捷新增 --> | ||
| 4 | + <view class="top-bar"> | ||
| 5 | + <view class="search-wrapper"> | ||
| 6 | + <up-search v-model="searchKeyword" placeholder="搜索动作名称" :showAction="false" shape="round" bgColor="#f3f4f6" | ||
| 7 | + placeholderColor="#9ca3af" searchIconColor="#6b7280" @clear="searchKeyword = ''" /> | ||
| 8 | + </view> | ||
| 9 | + | ||
| 10 | + <view class="add-btn-wrapper"> | ||
| 11 | + <view class="add-icon-btn" @click.stop="addShow = !addShow"> | ||
| 12 | + <up-icon name="plus" size="20" color="#111827" bold /> | ||
| 13 | + </view> | ||
| 14 | + | ||
| 15 | + <!-- 下拉浮动菜单 --> | ||
| 16 | + <view class="floating-menu" v-if="addShow" @click.stop> | ||
| 17 | + <view class="menu-item" @click="addnewmotion"> | ||
| 18 | + <up-icon name="plus-circle" size="18" color="#10b981" /> | ||
| 19 | + <text class="menu-text">新增动作</text> | ||
| 15 | </view> | 20 | </view> |
| 16 | - <view class="menu-item"> | ||
| 17 | - <uni-icons type="link" size="24" color="#333"></uni-icons> | ||
| 18 | - <text class="menu-text" @click="addnewsupersets">新增超级组</text> | 21 | + <view class="menu-item" @click="addnewsupersets"> |
| 22 | + <up-icon name="attach" size="18" color="#3b82f6" /> | ||
| 23 | + <text class="menu-text">新增超级组</text> | ||
| 19 | </view> | 24 | </view> |
| 20 | - <view class="menu-item"> | ||
| 21 | - <uni-icons type="list" size="24" color="#333"></uni-icons> | ||
| 22 | - <text class="menu-text" @click="actionmanagement">动作目录管理</text> | 25 | + <view class="menu-item" @click="actionmanagement"> |
| 26 | + <up-icon name="list-dot" size="18" color="#f59e0b" /> | ||
| 27 | + <text class="menu-text">动作目录管理</text> | ||
| 23 | </view> | 28 | </view> |
| 24 | </view> | 29 | </view> |
| 30 | + | ||
| 31 | + <view class="menu-mask" v-if="addShow" @click="addShow = false" /> | ||
| 25 | </view> | 32 | </view> |
| 26 | </view> | 33 | </view> |
| 27 | 34 | ||
| 28 | - <!-- 浮动菜单 --> | ||
| 29 | - | ||
| 30 | - <!-- 左右布局 --> | 35 | + <!-- 主体左右双栏布局 --> |
| 31 | <view class="layout-container"> | 36 | <view class="layout-container"> |
| 32 | - <scroll-view scroll-y class="left-nav" enable-flex> | ||
| 33 | - <view class="nav-item" @click="handleCollectClick('collect')" :class="{ active: activeNav === 'collect' }"> | ||
| 34 | - 收藏 | 37 | + <!-- 左侧一级分类导航 --> |
| 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> | ||
| 35 | </view> | 42 | </view> |
| 43 | + | ||
| 36 | <view v-for="nav in navItems" :key="nav.id" class="nav-item" :class="{ active: activeNav === nav.id }" | 44 | <view v-for="nav in navItems" :key="nav.id" class="nav-item" :class="{ active: activeNav === nav.id }" |
| 37 | @click="switchNav(nav.id)"> | 45 | @click="switchNav(nav.id)"> |
| 38 | - {{ nav.name }} | 46 | + <text class="nav-text">{{ nav.name }}</text> |
| 39 | </view> | 47 | </view> |
| 40 | </scroll-view> | 48 | </scroll-view> |
| 41 | 49 | ||
| 42 | - <!-- 小动作列表 --> | ||
| 43 | - <scroll-view scroll-y class="right-content" enable-flex> | ||
| 44 | - <view class="tip" v-if="motionPart.length > 0"> | ||
| 45 | - <view class="item" @click="handlePartClick('')" :class="{ active: activeMotionPart == '' }">全部</view> | ||
| 46 | - <view class="item" v-for="item in motionPart" :key="item.id" :class="{ active: activeMotionPart == item.id }" | ||
| 47 | - @click="handlePartClick(item.id)"> | ||
| 48 | - {{ item.name }} | 50 | + <!-- 右侧二级筛选与动作网格内容 --> |
| 51 | + <scroll-view scroll-y class="right-content" :show-scrollbar="false"> | ||
| 52 | + <!-- 二级子部位 Chips 筛选条 --> | ||
| 53 | + <scroll-view scroll-x class="sub-part-scroll" v-if="motionPart.length > 0"> | ||
| 54 | + <view class="sub-part-wrapper"> | ||
| 55 | + <view class="part-chip" :class="{ active: activeMotionPart === '' }" @click="handlePartClick('')"> | ||
| 56 | + 全部 | ||
| 57 | + </view> | ||
| 58 | + <view v-for="item in motionPart" :key="item.id" class="part-chip" | ||
| 59 | + :class="{ active: activeMotionPart === item.id }" @click="handlePartClick(item.id)"> | ||
| 60 | + {{ item.name }} | ||
| 61 | + </view> | ||
| 49 | </view> | 62 | </view> |
| 50 | - </view> | 63 | + </scroll-view> |
| 51 | 64 | ||
| 65 | + <!-- 动作与超级组网格卡片区 --> | ||
| 52 | <view class="exercise-grid"> | 66 | <view class="exercise-grid"> |
| 53 | - <view class="content" v-if="exercises.length > 0 || superGroupInfo.length > 0"> | ||
| 54 | - <view class="equipment-list"> | ||
| 55 | - <view class="equipment-item" v-for="item in exercises" :key="item.equipmentId"> | ||
| 56 | - <view class="equipment-name"> {{ item.equipmentName }} </view> | ||
| 57 | - <view class="action-list"> | ||
| 58 | - <view class="action-item" v-for="e in item.exercises" :key="e.id"> | ||
| 59 | - <image :src="e.url3dAnimation" mode="aspectFill" lazy-load class="action-img" | ||
| 60 | - @click="goToDetail(e.id, 1)"></image> | ||
| 61 | - <view class="action-name">{{ e.name }}</view> | ||
| 62 | - <view class="trainingReps" v-if="e.trainingReps">{{ e.trainingReps }}次</view> | ||
| 63 | - </view> | ||
| 64 | - </view> | 67 | + <template v-if="displayExercises.length > 0 || superGroupInfo.length > 0"> |
| 68 | + <!-- 器械分组列表 --> | ||
| 69 | + <view class="equipment-group" v-for="item in displayExercises" :key="item.equipmentId"> | ||
| 70 | + <view class="equipment-title"> | ||
| 71 | + <view class="title-bar"></view> | ||
| 72 | + <text>{{ item.equipmentName || '通用器械' }}</text> | ||
| 65 | </view> | 73 | </view> |
| 66 | 74 | ||
| 67 | - <view class="supers" v-if="superGroupInfo.length > 0"> | ||
| 68 | - <view class="supers-name"> 超级组 </view> | ||
| 69 | - <view class="super" v-for="item in superGroupInfo" :key="item.id" @click="goToDetail(item.id, 2)"> | ||
| 70 | - <image src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260507/超级组_1778117889451.png" | ||
| 71 | - mode="aspectFill" lazy-load class="super-img"></image> | ||
| 72 | - <view class="right"> | ||
| 73 | - <view class="super-name">{{ item.name }}</view> | ||
| 74 | - <view class="parts">{{ item.primaryMuscles.join() }}</view> | 75 | + <view class="action-grid"> |
| 76 | + <view class="action-card" v-for="e in item.exercises" :key="e.id" @click="goToDetail(e.id, 1)"> | ||
| 77 | + <view class="image-wrapper"> | ||
| 78 | + <image :src="e.url3dAnimation" mode="aspectFil" lazy-load class="action-img"></image> | ||
| 79 | + <view class="reps-badge" v-if="e.trainingReps"> | ||
| 80 | + {{ e.trainingReps }}次 | ||
| 81 | + </view> | ||
| 82 | + </view> | ||
| 83 | + <view class="action-info"> | ||
| 84 | + <text class="action-name">{{ e.name }}</text> | ||
| 75 | </view> | 85 | </view> |
| 76 | </view> | 86 | </view> |
| 77 | </view> | 87 | </view> |
| 88 | + </view> | ||
| 89 | + | ||
| 90 | + <!-- 超级组展示模块 --> | ||
| 91 | + <view class="supers-group" v-if="superGroupInfo.length > 0"> | ||
| 92 | + <view class="equipment-title"> | ||
| 93 | + <view class="title-bar blue"></view> | ||
| 94 | + <text>超级组</text> | ||
| 95 | + </view> | ||
| 78 | 96 | ||
| 79 | - <!-- <view class="add-super"> | ||
| 80 | - <view class="header"> | ||
| 81 | - <up-icon name="plus" color="#000" size="15" bold></up-icon> | ||
| 82 | - <text>添加自定义动作</text> | 97 | + <view class="super-card" v-for="item in superGroupInfo" :key="item.id" @click="goToDetail(item.id, 2)"> |
| 98 | + <image src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260507/超级组_1778117889451.png" | ||
| 99 | + mode="aspectFill" lazy-load class="super-img"></image> | ||
| 100 | + <view class="super-info"> | ||
| 101 | + <text class="super-name">{{ item.name }}</text> | ||
| 102 | + <text class="super-muscles" v-if="item.primaryMuscles"> | ||
| 103 | + {{ Array.isArray(item.primaryMuscles) ? item.primaryMuscles.join(' · ') : item.primaryMuscles }} | ||
| 104 | + </text> | ||
| 83 | </view> | 105 | </view> |
| 84 | - <view class="tip">添加官方库没有的动作</view> | ||
| 85 | - </view> --> | 106 | + <up-icon name="arrow-right" size="14" color="#9ca3af"></up-icon> |
| 107 | + </view> | ||
| 86 | </view> | 108 | </view> |
| 87 | - </view> | 109 | + </template> |
| 88 | 110 | ||
| 89 | - <!-- 空状态 --> | ||
| 90 | - <view class="empty-state" v-else> | ||
| 91 | - <text class="empty-text">暂无动作数据</text> | 111 | + <!-- 优雅的空状态 --> |
| 112 | + <view class="empty-wrapper" v-else> | ||
| 113 | + <up-empty mode="data" text="暂无相关动作数据"></up-empty> | ||
| 92 | </view> | 114 | </view> |
| 93 | </view> | 115 | </view> |
| 116 | + | ||
| 117 | + | ||
| 94 | </scroll-view> | 118 | </scroll-view> |
| 95 | </view> | 119 | </view> |
| 96 | 120 | ||
| 97 | - <!-- 详情弹窗 --> | 121 | + <!-- 动作详情弹窗组件 --> |
| 98 | <dongzuoXianqing ref="actionDetailRef" /> | 122 | <dongzuoXianqing ref="actionDetailRef" /> |
| 99 | - | ||
| 100 | - <!-- 底部安全区占位(关键!解决不同设备适配) --> | ||
| 101 | - <view class="safe-area-bottom"></view> | ||
| 102 | </view> | 123 | </view> |
| 103 | </template> | 124 | </template> |
| 104 | 125 | ||
| 105 | <script setup> | 126 | <script setup> |
| 106 | -import { ref, onMounted, nextTick } from 'vue'; | 127 | +import { ref, onMounted, computed } from 'vue'; |
| 107 | import ExercisesApi from '@/sheep/api/motion/exercises'; | 128 | import ExercisesApi from '@/sheep/api/motion/exercises'; |
| 108 | import SupersetsApi from '@/sheep/api/motion/supersets'; | 129 | import SupersetsApi from '@/sheep/api/motion/supersets'; |
| 109 | import dongzuoXianqing from '@/pages/xunji/components/dongzuo-xianqing.vue'; | 130 | import dongzuoXianqing from '@/pages/xunji/components/dongzuo-xianqing.vue'; |
| @@ -111,29 +132,35 @@ import { useActionStore } from '@/sheep/store/action'; | @@ -111,29 +132,35 @@ import { useActionStore } from '@/sheep/store/action'; | ||
| 111 | 132 | ||
| 112 | const actionStore = useActionStore(); | 133 | const actionStore = useActionStore(); |
| 113 | 134 | ||
| 114 | -// 响应式数据 | ||
| 115 | const activeNav = ref(''); | 135 | const activeNav = ref(''); |
| 116 | const navItems = ref([]); | 136 | const navItems = ref([]); |
| 117 | const exercises = ref([]); | 137 | const exercises = ref([]); |
| 118 | const superGroupInfo = ref([]); | 138 | const superGroupInfo = ref([]); |
| 119 | const motionPart = ref([]); | 139 | const motionPart = ref([]); |
| 120 | const activeMotionPart = ref(''); | 140 | const activeMotionPart = ref(''); |
| 121 | -const currentCategoryId = ref(''); | ||
| 122 | - | ||
| 123 | -const allExercises = ref([]); | ||
| 124 | - | ||
| 125 | -const addshow = ref(false); | ||
| 126 | - | ||
| 127 | -const superFavoriteList = ref([]); | ||
| 128 | - | 141 | +const searchKeyword = ref(''); |
| 142 | +const addShow = ref(false); | ||
| 129 | const actionDetailRef = ref(null); | 143 | const actionDetailRef = ref(null); |
| 130 | 144 | ||
| 131 | -// 关闭菜单 | ||
| 132 | -const closeAddMenu = () => { | ||
| 133 | - addshow.value = false; | ||
| 134 | -}; | 145 | +const displayExercises = computed(() => { |
| 146 | + if (!searchKeyword.value.trim()) { | ||
| 147 | + return exercises.value; | ||
| 148 | + } | ||
| 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); | ||
| 162 | +}); | ||
| 135 | 163 | ||
| 136 | -// 部位筛选 | ||
| 137 | const handlePartClick = async (id) => { | 164 | const handlePartClick = async (id) => { |
| 138 | try { | 165 | try { |
| 139 | activeMotionPart.value = id; | 166 | activeMotionPart.value = id; |
| @@ -143,37 +170,40 @@ const handlePartClick = async (id) => { | @@ -143,37 +170,40 @@ const handlePartClick = async (id) => { | ||
| 143 | }); | 170 | }); |
| 144 | exercises.value = exerciseRes.data || []; | 171 | exercises.value = exerciseRes.data || []; |
| 145 | } catch (e) { | 172 | } catch (e) { |
| 146 | - console.error('加载动作失败:', e); | 173 | + console.error('加载子部位动作失败:', e); |
| 147 | } | 174 | } |
| 148 | }; | 175 | }; |
| 149 | 176 | ||
| 150 | -// 收藏 | ||
| 151 | -const handleCollectClick = async (id) => { | ||
| 152 | - activeNav.value = id; | 177 | +const handleCollectClick = () => { |
| 178 | + activeNav.value = 'collect'; | ||
| 179 | + activeMotionPart.value = ''; | ||
| 180 | + motionPart.value = []; | ||
| 153 | loadCollectList(); | 181 | loadCollectList(); |
| 154 | loadSuperFavoriteList(); | 182 | loadSuperFavoriteList(); |
| 155 | }; | 183 | }; |
| 156 | -// 动作收藏 | 184 | + |
| 185 | +// 加载动作收藏列表 | ||
| 157 | const loadCollectList = async () => { | 186 | const loadCollectList = async () => { |
| 158 | try { | 187 | try { |
| 159 | const res = await ExercisesApi.getFavoriteExercises(); | 188 | const res = await ExercisesApi.getFavoriteExercises(); |
| 160 | exercises.value = res.data || []; | 189 | exercises.value = res.data || []; |
| 161 | } catch (err) { | 190 | } catch (err) { |
| 162 | - console.error('加载动作收藏失败', err); | 191 | + console.error('加载动作收藏失败:', err); |
| 163 | } | 192 | } |
| 164 | }; | 193 | }; |
| 165 | -// 超级组收藏 | 194 | + |
| 195 | +// 加载超级组收藏列表 | ||
| 166 | const loadSuperFavoriteList = async () => { | 196 | const loadSuperFavoriteList = async () => { |
| 167 | try { | 197 | try { |
| 168 | const res = await SupersetsApi.getFavoriteSuperset(); | 198 | const res = await SupersetsApi.getFavoriteSuperset(); |
| 169 | superGroupInfo.value = res.data || []; | 199 | superGroupInfo.value = res.data || []; |
| 170 | } catch (err) { | 200 | } catch (err) { |
| 171 | - superFavoriteList.value = []; | ||
| 172 | - console.error('加载超级组收藏失败', err); | 201 | + superGroupInfo.value = []; |
| 202 | + console.error('加载超级组收藏失败:', err); | ||
| 173 | } | 203 | } |
| 174 | }; | 204 | }; |
| 175 | 205 | ||
| 176 | -// 加载分类 | 206 | +// 加载大类 |
| 177 | const loadCategories = async () => { | 207 | const loadCategories = async () => { |
| 178 | try { | 208 | try { |
| 179 | await actionStore.getloadCategories(); | 209 | await actionStore.getloadCategories(); |
| @@ -187,26 +217,27 @@ const loadCategories = async () => { | @@ -187,26 +217,27 @@ const loadCategories = async () => { | ||
| 187 | } | 217 | } |
| 188 | }; | 218 | }; |
| 189 | 219 | ||
| 190 | -// 加载动作 | 220 | +// 加载动作列表 |
| 191 | const loadExercises = async (categoriesId) => { | 221 | const loadExercises = async (categoriesId) => { |
| 192 | try { | 222 | try { |
| 193 | const partRes = await ExercisesApi.getMotionPart(categoriesId); | 223 | const partRes = await ExercisesApi.getMotionPart(categoriesId); |
| 194 | motionPart.value = partRes.data || []; | 224 | motionPart.value = partRes.data || []; |
| 195 | - console.log(motionPart.value, 'motionPart.value'); | ||
| 196 | 225 | ||
| 197 | const exerciseRes = await ExercisesApi.getexercises({ categoriesId }); | 226 | const exerciseRes = await ExercisesApi.getexercises({ categoriesId }); |
| 198 | - exercises.value = exerciseRes.data; | 227 | + exercises.value = exerciseRes.data || []; |
| 199 | } catch (error) { | 228 | } catch (error) { |
| 200 | console.error('加载动作失败:', error); | 229 | console.error('加载动作失败:', error); |
| 201 | } | 230 | } |
| 202 | }; | 231 | }; |
| 203 | 232 | ||
| 204 | -// 切换导航 | 233 | +// 切换一级导航 |
| 205 | const switchNav = (id) => { | 234 | const switchNav = (id) => { |
| 206 | if (activeNav.value === id) return; | 235 | if (activeNav.value === id) return; |
| 207 | activeNav.value = id; | 236 | activeNav.value = id; |
| 208 | activeMotionPart.value = ''; | 237 | activeMotionPart.value = ''; |
| 209 | - if (id == 'super') { | 238 | + searchKeyword.value = ''; |
| 239 | + | ||
| 240 | + if (id === 'super') { | ||
| 210 | exercises.value = []; | 241 | exercises.value = []; |
| 211 | motionPart.value = []; | 242 | motionPart.value = []; |
| 212 | loadsupersetsinfo(); | 243 | loadsupersetsinfo(); |
| @@ -216,21 +247,7 @@ const switchNav = (id) => { | @@ -216,21 +247,7 @@ const switchNav = (id) => { | ||
| 216 | } | 247 | } |
| 217 | }; | 248 | }; |
| 218 | 249 | ||
| 219 | -// 搜索 | ||
| 220 | -const onSearch = (e) => { | ||
| 221 | - const searchText = e.detail.value.trim(); | ||
| 222 | - if (!currentCategoryId.value) return; | ||
| 223 | - | ||
| 224 | - searchText ? searchExercises(searchText) : loadExercises(currentCategoryId.value); | ||
| 225 | -}; | ||
| 226 | - | ||
| 227 | -const searchExercises = (keyword) => { | ||
| 228 | - const key = keyword.toLowerCase().trim(); | ||
| 229 | - exercises.value = allExercises.value.filter((item) => { | ||
| 230 | - return item.name && item.name.toLowerCase().includes(key); | ||
| 231 | - }); | ||
| 232 | -}; | ||
| 233 | - | 250 | +// 加载超级组列表 |
| 234 | const loadsupersetsinfo = async () => { | 251 | const loadsupersetsinfo = async () => { |
| 235 | try { | 252 | try { |
| 236 | const response = await SupersetsApi.getsupersets(); | 253 | const response = await SupersetsApi.getsupersets(); |
| @@ -240,31 +257,22 @@ const loadsupersetsinfo = async () => { | @@ -240,31 +257,22 @@ const loadsupersetsinfo = async () => { | ||
| 240 | } | 257 | } |
| 241 | }; | 258 | }; |
| 242 | 259 | ||
| 243 | -// 页面跳转 | ||
| 244 | const goToDetail = (id, type) => { | 260 | const goToDetail = (id, type) => { |
| 245 | - console.log('type=', type); | ||
| 246 | - nextTick(() => { | ||
| 247 | - actionDetailRef.value.open(id, type); | ||
| 248 | - }); | ||
| 249 | -}; | ||
| 250 | - | ||
| 251 | -const addExercise = () => { | ||
| 252 | - addshow.value = !addshow.value; | 261 | + actionDetailRef.value?.open(id, type); |
| 253 | }; | 262 | }; |
| 254 | 263 | ||
| 255 | const addnewmotion = () => { | 264 | const addnewmotion = () => { |
| 265 | + addShow.value = false; | ||
| 256 | uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-xinzeng' }); | 266 | uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-xinzeng' }); |
| 257 | }; | 267 | }; |
| 258 | 268 | ||
| 259 | const addnewsupersets = () => { | 269 | const addnewsupersets = () => { |
| 260 | - uni.navigateTo({ | ||
| 261 | - url: `/pages4/pages/xunji/dongzuo-xinzengchaojizu?navItems=${encodeURIComponent( | ||
| 262 | - JSON.stringify(navItems.value), | ||
| 263 | - )}`, | ||
| 264 | - }); | 270 | + addShow.value = false; |
| 271 | + uni.navigateTo({ url: `/pages4/pages/xunji/dongzuo-xinzengchaojizu?navItems=${encodeURIComponent(JSON.stringify(navItems.value))}` }); | ||
| 265 | }; | 272 | }; |
| 266 | 273 | ||
| 267 | const actionmanagement = () => { | 274 | const actionmanagement = () => { |
| 275 | + addShow.value = false; | ||
| 268 | uni.navigateTo({ url: '/pages4/pages/xunji/dongzuo-muluguanli' }); | 276 | uni.navigateTo({ url: '/pages4/pages/xunji/dongzuo-muluguanli' }); |
| 269 | }; | 277 | }; |
| 270 | 278 | ||
| @@ -272,352 +280,324 @@ onMounted(() => { | @@ -272,352 +280,324 @@ onMounted(() => { | ||
| 272 | loadCategories(); | 280 | loadCategories(); |
| 273 | }); | 281 | }); |
| 274 | </script> | 282 | </script> |
| 283 | + | ||
| 275 | <style lang="scss" scoped> | 284 | <style lang="scss" scoped> |
| 276 | .exercise-page { | 285 | .exercise-page { |
| 277 | - background-color: white; | ||
| 278 | - box-sizing: border-box; | ||
| 279 | - height: 100vh; | ||
| 280 | - display: flex; | ||
| 281 | - flex-direction: column; | ||
| 282 | - box-sizing: border-box; | ||
| 283 | - | ||
| 284 | - .search-bar { | ||
| 285 | - box-sizing: border-box; | ||
| 286 | - width: 100%; | 286 | + height: 87vh; |
| 287 | + // #ifdef H5 | ||
| 288 | + height: calc(100vh - 44px); | ||
| 289 | + // #endif | ||
| 290 | + | ||
| 291 | + background-color: #ffffff; | ||
| 292 | + overflow: hidden; | ||
| 293 | + | ||
| 294 | + /* 顶部搜索操作栏 */ | ||
| 295 | + .top-bar { | ||
| 287 | display: flex; | 296 | display: flex; |
| 288 | align-items: center; | 297 | align-items: center; |
| 289 | - justify-content: flex-end; | ||
| 290 | - padding: 20rpx; | ||
| 291 | - // background-color: #f5f5f5; | ||
| 292 | - border-radius: 12rpx; | ||
| 293 | - margin-top: 20rpx; | ||
| 294 | - | ||
| 295 | - /* #ifdef H5 */ | ||
| 296 | - margin-top: 20px; | ||
| 297 | - /* #endif */ | ||
| 298 | - | ||
| 299 | - .search { | ||
| 300 | - display: flex; | ||
| 301 | - align-items: center; | ||
| 302 | - padding: 10rpx 20rpx; | ||
| 303 | - flex: 1; | ||
| 304 | - border-radius: 30rpx; | ||
| 305 | - background-color: #f5f5f5; | ||
| 306 | - | ||
| 307 | - .search-icon { | ||
| 308 | - width: 30rpx; | ||
| 309 | - height: 30rpx; | ||
| 310 | - } | 298 | + padding: 16rpx 24rpx; |
| 299 | + background-color: #ffffff; | ||
| 300 | + border-bottom: 1rpx solid #f3f4f6; | ||
| 301 | + z-index: 50; | ||
| 311 | 302 | ||
| 312 | - .search-input { | ||
| 313 | - flex: 1; | ||
| 314 | - padding-left: 15rpx; | ||
| 315 | - font-size: 26rpx; | ||
| 316 | - color: #333; | ||
| 317 | - border: none; | ||
| 318 | - outline: none; | ||
| 319 | - background: transparent; | ||
| 320 | - } | 303 | + .search-wrapper { |
| 304 | + flex: 1; | ||
| 305 | + margin-right: 20rpx; | ||
| 321 | } | 306 | } |
| 322 | 307 | ||
| 323 | - .add-btn { | ||
| 324 | - width: 60rpx; | ||
| 325 | - height: 60rpx; | ||
| 326 | - background-color: transparent; | ||
| 327 | - border: none; | ||
| 328 | - margin-left: 10rpx; | ||
| 329 | - display: flex; | ||
| 330 | - justify-content: center; | ||
| 331 | - align-items: center; | ||
| 332 | - z-index: 99; | 308 | + .add-btn-wrapper { |
| 333 | position: relative; | 309 | position: relative; |
| 334 | 310 | ||
| 335 | - .plus-icon { | ||
| 336 | - width: 40rpx; | ||
| 337 | - height: 40rpx; | 311 | + .add-icon-btn { |
| 312 | + width: 72rpx; | ||
| 313 | + height: 72rpx; | ||
| 314 | + border-radius: 50%; | ||
| 315 | + background-color: #f3f4f6; | ||
| 316 | + display: flex; | ||
| 317 | + justify-content: center; | ||
| 318 | + align-items: center; | ||
| 319 | + transition: background-color 0.2s; | ||
| 320 | + | ||
| 321 | + &:active { | ||
| 322 | + background-color: #e5e7eb; | ||
| 323 | + } | ||
| 338 | } | 324 | } |
| 339 | 325 | ||
| 340 | .floating-menu { | 326 | .floating-menu { |
| 341 | position: absolute; | 327 | position: absolute; |
| 342 | - top: 30rpx; | ||
| 343 | - right: 30rpx; | ||
| 344 | - width: 300rpx; | ||
| 345 | - background-color: #fff; | ||
| 346 | - border-radius: 12rpx; | ||
| 347 | - box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1); | ||
| 348 | - padding: 20rpx; | 328 | + top: 84rpx; |
| 329 | + right: 0; | ||
| 330 | + width: 320rpx; | ||
| 331 | + background-color: #ffffff; | ||
| 332 | + border-radius: 20rpx; | ||
| 333 | + box-shadow: 0 12rpx 36rpx rgba(0, 0, 0, 0.12); | ||
| 334 | + padding: 12rpx 0; | ||
| 349 | z-index: 100; | 335 | z-index: 100; |
| 336 | + border: 1rpx solid #f3f4f6; | ||
| 350 | 337 | ||
| 351 | .menu-item { | 338 | .menu-item { |
| 352 | display: flex; | 339 | display: flex; |
| 353 | align-items: center; | 340 | align-items: center; |
| 354 | - padding: 20rpx 0; | ||
| 355 | - border-bottom: 1rpx solid #eee; | 341 | + gap: 16rpx; |
| 342 | + padding: 24rpx 28rpx; | ||
| 343 | + transition: background-color 0.15s; | ||
| 356 | 344 | ||
| 357 | - .u-icon { | ||
| 358 | - margin-right: 16rpx; | 345 | + &:active { |
| 346 | + background-color: #f9fafb; | ||
| 359 | } | 347 | } |
| 360 | 348 | ||
| 361 | .menu-text { | 349 | .menu-text { |
| 362 | font-size: 28rpx; | 350 | font-size: 28rpx; |
| 363 | - color: #333; | ||
| 364 | - } | ||
| 365 | - | ||
| 366 | - &:last-child { | ||
| 367 | - border-bottom: none; | 351 | + color: #1f2937; |
| 352 | + font-weight: 500; | ||
| 368 | } | 353 | } |
| 369 | } | 354 | } |
| 370 | } | 355 | } |
| 356 | + | ||
| 357 | + .menu-mask { | ||
| 358 | + position: fixed; | ||
| 359 | + top: 0; | ||
| 360 | + left: 0; | ||
| 361 | + right: 0; | ||
| 362 | + bottom: 0; | ||
| 363 | + z-index: 90; | ||
| 364 | + background-color: transparent; | ||
| 365 | + } | ||
| 371 | } | 366 | } |
| 372 | } | 367 | } |
| 373 | 368 | ||
| 374 | .layout-container { | 369 | .layout-container { |
| 375 | - box-sizing: border-box; | 370 | + height: 100%; |
| 376 | display: flex; | 371 | display: flex; |
| 377 | - height: calc(82vh + 25rpx); | ||
| 378 | - background-color: #f5f5f5; | 372 | + overflow: hidden; |
| 379 | 373 | ||
| 380 | .left-nav { | 374 | .left-nav { |
| 381 | - width: 160rpx; | ||
| 382 | - background-color: white; | ||
| 383 | - border-right: 1px solid #eee; | ||
| 384 | - // 添加底部 padding,避免最后一项被底部导航栏遮挡 | ||
| 385 | - padding-bottom: 20rpx; | ||
| 386 | - // #ifdef MP-WEIXIN | ||
| 387 | - // 微信小程序端可能需要更多底部空间 | ||
| 388 | - padding-bottom: 200rpx; | ||
| 389 | - | ||
| 390 | - // #endif | 375 | + width: 180rpx; |
| 376 | + height: 100%; | ||
| 377 | + background-color: #f9fafb; | ||
| 378 | + border-right: 1rpx solid #f3f4f6; | ||
| 379 | + | ||
| 391 | .nav-item { | 380 | .nav-item { |
| 392 | - padding: 24rpx 0; | ||
| 393 | - text-align: center; | ||
| 394 | - font-size: 26rpx; | ||
| 395 | - color: #333; | ||
| 396 | - } | 381 | + display: flex; |
| 382 | + align-items: center; | ||
| 383 | + justify-content: center; | ||
| 384 | + gap: 8rpx; | ||
| 385 | + padding: 32rpx 16rpx; | ||
| 386 | + position: relative; | ||
| 387 | + transition: all 0.2s; | ||
| 388 | + | ||
| 389 | + .nav-text { | ||
| 390 | + font-size: 26rpx; | ||
| 391 | + color: #4b5563; | ||
| 392 | + font-weight: 400; | ||
| 393 | + text-align: center; | ||
| 394 | + line-height: 1.3; | ||
| 395 | + } | ||
| 397 | 396 | ||
| 398 | - .active { | ||
| 399 | - color: #16ad40; | ||
| 400 | - background-color: #e1f6e9; | ||
| 401 | - border-right: 3px solid #16ad40; | ||
| 402 | - font-weight: bold; | 397 | + &.active { |
| 398 | + background-color: #ffffff; | ||
| 399 | + | ||
| 400 | + .nav-text { | ||
| 401 | + color: #10b981; | ||
| 402 | + font-weight: 600; | ||
| 403 | + } | ||
| 404 | + | ||
| 405 | + &::before { | ||
| 406 | + content: ''; | ||
| 407 | + position: absolute; | ||
| 408 | + left: 0; | ||
| 409 | + top: 24rpx; | ||
| 410 | + bottom: 24rpx; | ||
| 411 | + width: 6rpx; | ||
| 412 | + background-color: #10b981; | ||
| 413 | + border-radius: 0 4rpx 4rpx 0; | ||
| 414 | + } | ||
| 415 | + } | ||
| 403 | } | 416 | } |
| 404 | } | 417 | } |
| 405 | 418 | ||
| 419 | + /* 右侧内容滚动区 */ | ||
| 406 | .right-content { | 420 | .right-content { |
| 407 | - box-sizing: border-box; | ||
| 408 | - flex: 1; | ||
| 409 | - padding: 20rpx; | 421 | + width: calc(100% - 180rpx); |
| 410 | height: 100%; | 422 | height: 100%; |
| 411 | - // 添加底部 padding,避免内容被底部导航栏遮挡 | ||
| 412 | - // 底部导航栏高度约 100rpx(50px),加上安全区域 | ||
| 413 | - padding-bottom: 20rpx; | ||
| 414 | - // #ifdef MP-WEIXIN | ||
| 415 | - // 微信小程序端可能需要更多底部空间 | ||
| 416 | - padding-bottom: 100rpx; | ||
| 417 | - | ||
| 418 | - // #endif | ||
| 419 | - .tip { | ||
| 420 | - display: flex; | ||
| 421 | - padding-left: 20rpx; | ||
| 422 | - align-items: center; | ||
| 423 | - column-gap: 15rpx; | ||
| 424 | - margin-bottom: 20rpx; | ||
| 425 | - | ||
| 426 | - .item { | ||
| 427 | - margin: 0; | ||
| 428 | - padding: 12rpx 20rpx; | ||
| 429 | - font-size: 22rpx; | ||
| 430 | - line-height: 1; | ||
| 431 | - background-color: #fff; | ||
| 432 | - border-radius: 20rpx; | ||
| 433 | - | ||
| 434 | - &.active { | ||
| 435 | - background-color: #d8ede0; | ||
| 436 | - color: #43b05e; | 423 | + background-color: #efefef; |
| 424 | + padding: 20rpx 24rpx 200rpx; | ||
| 425 | + box-sizing: border-box; | ||
| 426 | + | ||
| 427 | + /* 二级子部位芯片滚动条 */ | ||
| 428 | + .sub-part-scroll { | ||
| 429 | + width: 100%; | ||
| 430 | + white-space: nowrap; | ||
| 431 | + margin-bottom: 24rpx; | ||
| 432 | + | ||
| 433 | + .sub-part-wrapper { | ||
| 434 | + display: inline-flex; | ||
| 435 | + gap: 16rpx; | ||
| 436 | + padding-bottom: 4rpx; | ||
| 437 | + | ||
| 438 | + .part-chip { | ||
| 439 | + display: inline-block; | ||
| 440 | + padding: 10rpx 24rpx; | ||
| 441 | + font-size: 24rpx; | ||
| 442 | + color: #4b5563; | ||
| 443 | + background-color: #f3f4f6; | ||
| 444 | + border-radius: 30rpx; | ||
| 445 | + transition: all 0.2s; | ||
| 446 | + | ||
| 447 | + &.active { | ||
| 448 | + background-color: #ecfdf5; | ||
| 449 | + color: #10b981; | ||
| 450 | + font-weight: 600; | ||
| 451 | + } | ||
| 437 | } | 452 | } |
| 438 | } | 453 | } |
| 439 | } | 454 | } |
| 440 | 455 | ||
| 456 | + /* 动作网格区 */ | ||
| 441 | .exercise-grid { | 457 | .exercise-grid { |
| 442 | display: flex; | 458 | display: flex; |
| 443 | flex-direction: column; | 459 | flex-direction: column; |
| 444 | - gap: 24rpx; | 460 | + gap: 32rpx; |
| 445 | 461 | ||
| 446 | - .title { | ||
| 447 | - font-weight: 600; | ||
| 448 | - padding-left: 20rpx; | ||
| 449 | - font-size: 30rpx; | ||
| 450 | - } | 462 | + .equipment-group { |
| 463 | + .equipment-title { | ||
| 464 | + display: flex; | ||
| 465 | + align-items: center; | ||
| 466 | + gap: 12rpx; | ||
| 467 | + font-size: 28rpx; | ||
| 468 | + font-weight: 600; | ||
| 469 | + color: #111827; | ||
| 470 | + margin-bottom: 20rpx; | ||
| 471 | + | ||
| 472 | + .title-bar { | ||
| 473 | + width: 8rpx; | ||
| 474 | + height: 28rpx; | ||
| 475 | + background-color: #10b981; | ||
| 476 | + border-radius: 4rpx; | ||
| 451 | 477 | ||
| 452 | - .content { | ||
| 453 | - width: 100%; | ||
| 454 | - | ||
| 455 | - // display: grid; | ||
| 456 | - // grid-template-columns: repeat(2, 1fr); | ||
| 457 | - // /* 改为3列布局 */ | ||
| 458 | - // gap: 16rpx; | ||
| 459 | - | ||
| 460 | - // .exercise-item { | ||
| 461 | - // display: flex; | ||
| 462 | - // height: 150rpx; | ||
| 463 | - // /* 减小项目高度 */ | ||
| 464 | - // flex-direction: column; | ||
| 465 | - // align-items: center; | ||
| 466 | - // justify-content: center; | ||
| 467 | - // background-color: white; | ||
| 468 | - // padding: 16rpx 8rpx; | ||
| 469 | - // box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.08); | ||
| 470 | - | ||
| 471 | - // .exercise-img { | ||
| 472 | - // width: 90%; | ||
| 473 | - // height: 140rpx; | ||
| 474 | - // /* 固定图片高度 */ | ||
| 475 | - // border-radius: 8rpx; | ||
| 476 | - // margin-bottom: 12rpx; | ||
| 477 | - // } | ||
| 478 | - | ||
| 479 | - // .exercise-title { | ||
| 480 | - // font-size: 24rpx; | ||
| 481 | - // color: #333; | ||
| 482 | - // text-align: center; | ||
| 483 | - // line-height: 1.3; | ||
| 484 | - // max-width: 100%; | ||
| 485 | - | ||
| 486 | - // text-overflow: ellipsis; | ||
| 487 | - // display: -webkit-box; | ||
| 488 | - // -webkit-box-orient: vertical; | ||
| 489 | - // } | ||
| 490 | - // } | ||
| 491 | - .equipment-item { | ||
| 492 | - width: 100%; | ||
| 493 | - // margin-bottom: 40rpx; | ||
| 494 | - | ||
| 495 | - .equipment-name { | ||
| 496 | - color: #000; | ||
| 497 | - font-size: 32rpx; | ||
| 498 | - margin-bottom: 20rpx; | ||
| 499 | - | ||
| 500 | - | ||
| 501 | - &:last-child { | ||
| 502 | - margin-bottom: 50rpx; | 478 | + &.blue { |
| 479 | + background-color: #3b82f6; | ||
| 503 | } | 480 | } |
| 504 | } | 481 | } |
| 482 | + } | ||
| 505 | 483 | ||
| 506 | - .action-list { | ||
| 507 | - display: grid; | ||
| 508 | - grid-template-columns: repeat(2, 1fr); | ||
| 509 | - grid-gap: 10rpx; | 484 | + .action-grid { |
| 485 | + display: grid; | ||
| 486 | + grid-template-columns: repeat(2, 1fr); | ||
| 487 | + gap: 15rpx; | ||
| 488 | + | ||
| 489 | + .action-card { | ||
| 490 | + background-color: #ffffff; | ||
| 491 | + border-radius: 16rpx; | ||
| 492 | + border: 1rpx solid #f3f4f6; | ||
| 493 | + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03); | ||
| 494 | + overflow: hidden; | ||
| 495 | + display: flex; | ||
| 496 | + flex-direction: column; | ||
| 497 | + transition: transform 0.15s; | ||
| 498 | + | ||
| 499 | + &:active { | ||
| 500 | + transform: scale(0.98); | ||
| 501 | + } | ||
| 510 | 502 | ||
| 511 | - .action-item { | ||
| 512 | - display: flex; | ||
| 513 | - flex-direction: column; | ||
| 514 | - justify-content: center; | ||
| 515 | - align-items: center; | ||
| 516 | - gap: 10rpx; | ||
| 517 | - background: #fff; | ||
| 518 | - border-radius: 10rpx; | ||
| 519 | - padding: 20rpx; | ||
| 520 | - box-sizing: border-box; | 503 | + .image-wrapper { |
| 521 | position: relative; | 504 | position: relative; |
| 505 | + width: 100%; | ||
| 506 | + height: 200rpx; | ||
| 507 | + background-color: #f9fafb; | ||
| 522 | 508 | ||
| 523 | .action-img { | 509 | .action-img { |
| 524 | width: 100%; | 510 | width: 100%; |
| 525 | - height: 250rpx; | ||
| 526 | - border-radius: 10rpx; | 511 | + height: 200rpx; |
| 527 | } | 512 | } |
| 528 | 513 | ||
| 529 | - .trainingReps { | 514 | + .reps-badge { |
| 530 | position: absolute; | 515 | position: absolute; |
| 531 | - top: 20rpx; | ||
| 532 | - right: 20rpx; | ||
| 533 | - z-index: 1; | ||
| 534 | - font-size: 22rpx; | 516 | + top: 12rpx; |
| 517 | + right: 12rpx; | ||
| 518 | + background: rgba(0, 0, 0, 0.6); | ||
| 519 | + backdrop-filter: blur(8px); | ||
| 520 | + color: #ffffff; | ||
| 521 | + font-size: 20rpx; | ||
| 522 | + padding: 4rpx 12rpx; | ||
| 523 | + border-radius: 20rpx; | ||
| 524 | + font-weight: 500; | ||
| 535 | } | 525 | } |
| 526 | + } | ||
| 536 | 527 | ||
| 537 | - &:last-child { | ||
| 538 | - margin-bottom: 50rpx; | ||
| 539 | - padding-bottom: env(safe-area-inset-bottom); | ||
| 540 | - } | 528 | + .action-info { |
| 529 | + padding: 16rpx 12rpx; | ||
| 530 | + display: flex; | ||
| 531 | + align-items: center; | ||
| 532 | + justify-content: center; | ||
| 541 | 533 | ||
| 534 | + .action-name { | ||
| 535 | + font-size: 24rpx; | ||
| 536 | + font-weight: 500; | ||
| 537 | + color: #1f2937; | ||
| 538 | + text-align: center; | ||
| 539 | + overflow: hidden; | ||
| 540 | + white-space: nowrap; | ||
| 541 | + text-overflow: ellipsis; | ||
| 542 | + width: 100%; | ||
| 543 | + } | ||
| 542 | } | 544 | } |
| 543 | } | 545 | } |
| 544 | } | 546 | } |
| 545 | } | 547 | } |
| 546 | 548 | ||
| 547 | - .supers-name { | ||
| 548 | - color: #000; | ||
| 549 | - font-size: 32rpx; | ||
| 550 | - margin-bottom: 20rpx; | ||
| 551 | - } | ||
| 552 | - | ||
| 553 | - .super { | ||
| 554 | - display: flex; | ||
| 555 | - align-items: center; | ||
| 556 | - background: #fff; | ||
| 557 | - padding: 20rpx; | ||
| 558 | - box-sizing: border-box; | ||
| 559 | - border-radius: 10rpx; | ||
| 560 | - gap: 20rpx; | ||
| 561 | - font-size: 26rpx; | ||
| 562 | - margin-bottom: 20rpx; | ||
| 563 | - | ||
| 564 | - .super-img { | ||
| 565 | - width: 120rpx; | ||
| 566 | - height: 120rpx; | ||
| 567 | - border-radius: 10rpx; | ||
| 568 | - } | 549 | + /* 超级组卡片样式 */ |
| 550 | + .supers-group { | ||
| 551 | + .super-card { | ||
| 552 | + display: flex; | ||
| 553 | + align-items: center; | ||
| 554 | + background-color: #ffffff; | ||
| 555 | + border-radius: 16rpx; | ||
| 556 | + border: 1rpx solid #f3f4f6; | ||
| 557 | + padding: 20rpx; | ||
| 558 | + margin-bottom: 16rpx; | ||
| 559 | + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03); | ||
| 560 | + gap: 20rpx; | ||
| 561 | + | ||
| 562 | + &:active { | ||
| 563 | + background-color: #f9fafb; | ||
| 564 | + } | ||
| 569 | 565 | ||
| 570 | - .super-name { | ||
| 571 | - margin-bottom: 10rpx; | ||
| 572 | - } | ||
| 573 | - } | 566 | + .super-img { |
| 567 | + width: 100rpx; | ||
| 568 | + height: 100rpx; | ||
| 569 | + border-radius: 12rpx; | ||
| 570 | + background-color: #f3f4f6; | ||
| 571 | + flex-shrink: 0; | ||
| 572 | + } | ||
| 574 | 573 | ||
| 575 | - .add-super { | ||
| 576 | - background: #fff; | ||
| 577 | - padding: 20rpx; | ||
| 578 | - box-sizing: border-box; | ||
| 579 | - border-radius: 10rpx; | 574 | + .super-info { |
| 575 | + flex: 1; | ||
| 576 | + display: flex; | ||
| 577 | + flex-direction: column; | ||
| 578 | + gap: 8rpx; | ||
| 580 | 579 | ||
| 581 | - .header { | ||
| 582 | - display: flex; | ||
| 583 | - gap: 10rpx; | ||
| 584 | - align-items: center; | ||
| 585 | - margin-bottom: 20rpx; | ||
| 586 | - } | 580 | + .super-name { |
| 581 | + font-size: 28rpx; | ||
| 582 | + font-weight: 600; | ||
| 583 | + color: #111827; | ||
| 584 | + } | ||
| 587 | 585 | ||
| 588 | - .tip { | ||
| 589 | - margin: 0rpx; | ||
| 590 | - padding: 0rpx; | ||
| 591 | - font-size: 22rpx; | 586 | + .super-muscles { |
| 587 | + font-size: 22rpx; | ||
| 588 | + color: #6b7280; | ||
| 589 | + } | ||
| 590 | + } | ||
| 592 | } | 591 | } |
| 593 | } | 592 | } |
| 594 | - } | ||
| 595 | 593 | ||
| 596 | - .nav-item .category-group { | ||
| 597 | - /* 超级组特殊样式 */ | ||
| 598 | - background-color: #f0f9ff; | ||
| 599 | - font-weight: bold; | ||
| 600 | - border-top: 1px solid #eee; | 594 | + .empty-wrapper { |
| 595 | + padding: 80rpx 0; | ||
| 596 | + } | ||
| 601 | } | 597 | } |
| 602 | 598 | ||
| 603 | - .empty-state { | ||
| 604 | - display: flex; | ||
| 605 | - justify-content: center; | ||
| 606 | - align-items: center; | ||
| 607 | 599 | ||
| 608 | - .empty-text { | ||
| 609 | - font-size: 28rpx; | ||
| 610 | - color: #999; | ||
| 611 | - } | ||
| 612 | - } | ||
| 613 | } | 600 | } |
| 614 | } | 601 | } |
| 615 | } | 602 | } |
| 616 | - | ||
| 617 | -.safe-area-bottom { | ||
| 618 | - height: env(safe-area-inset-bottom); // 适配iOS安全区 | ||
| 619 | - // 额外加上 Tabbar 的高度,你的 Tabbar 高度是 85px = 170rpx | ||
| 620 | - padding-bottom: 170rpx; | ||
| 621 | - box-sizing: border-box; | ||
| 622 | -} | ||
| 623 | -</style> | 603 | +</style> |
| 1 | -<!-- 第一层 模板大类 --> | ||
| 2 | <template> | 1 | <template> |
| 3 | - <view class="template-page" @click="closeAllDropdowns"> | ||
| 4 | - <!-- 筛选按钮 --> | ||
| 5 | - <view class="filter-section" @click.stop> | ||
| 6 | - <!-- 部位筛选 --> | 2 | + <view class="template-page"> |
| 3 | + <!-- 顶部筛选操作栏(固定不缩放) --> | ||
| 4 | + <view class="filter-section"> | ||
| 5 | + <!-- 部位筛选胶囊 --> | ||
| 7 | <view class="filter-wrapper"> | 6 | <view class="filter-wrapper"> |
| 8 | - <view class="filter-btn" :class="{ active: showPartDropdown }" @click="togglePartDropdown"> | 7 | + <view class="filter-btn" :class="{ active: showPartDropdown, selected: activePartId !== '0' }" |
| 8 | + @click.stop="togglePartDropdown"> | ||
| 9 | <text class="text">{{ activePart }}</text> | 9 | <text class="text">{{ activePart }}</text> |
| 10 | - <uni-icons :type="showPartDropdown ? 'up' : 'down'" size="13"></uni-icons> | 10 | + <up-icon :name="showPartDropdown ? 'arrow-up-fill' : 'arrow-down-fill'" size="10" |
| 11 | + :color="showPartDropdown || activePartId !== '0' ? '#10b981' : '#6b7280'"></up-icon> | ||
| 11 | </view> | 12 | </view> |
| 13 | + | ||
| 12 | <!-- 部位下拉菜单 --> | 14 | <!-- 部位下拉菜单 --> |
| 13 | - <view class="dropdown-menu" v-if="showPartDropdown"> | ||
| 14 | - <view v-for="(item, index) in partList" :key="index" class="dropdown-item" | ||
| 15 | - :class="{ selected: activePart === item.title }" @click="selectPart(item)"> | ||
| 16 | - {{ item.title }} | 15 | + <view class="dropdown-menu" v-if="showPartDropdown" @click.stop> |
| 16 | + <view v-for="item in partList" :key="item.id" class="dropdown-item" | ||
| 17 | + :class="{ selected: activePartId === item.id }" @click="selectPart(item)"> | ||
| 18 | + <text>{{ item.title }}</text> | ||
| 19 | + <up-icon v-if="activePartId === item.id" name="checkbox-mark" size="14" color="#10b981" /> | ||
| 17 | </view> | 20 | </view> |
| 18 | </view> | 21 | </view> |
| 19 | </view> | 22 | </view> |
| 20 | 23 | ||
| 21 | - <!-- 场景筛选 --> | 24 | + <!-- 场景筛选胶囊 --> |
| 22 | <view class="filter-wrapper"> | 25 | <view class="filter-wrapper"> |
| 23 | - <view class="filter-btn" :class="{ active: showSceneDropdown }" @click="toggleSceneDropdown"> | 26 | + <view class="filter-btn" :class="{ active: showSceneDropdown, selected: activeSceneId !== '0' }" |
| 27 | + @click.stop="toggleSceneDropdown"> | ||
| 24 | <text class="text">{{ activeScene }}</text> | 28 | <text class="text">{{ activeScene }}</text> |
| 25 | - <uni-icons :type="showSceneDropdown ? 'up' : 'down'" size="13"></uni-icons> | 29 | + <up-icon :name="showSceneDropdown ? 'arrow-up-fill' : 'arrow-down-fill'" size="10" |
| 30 | + :color="showSceneDropdown || activeSceneId !== '0' ? '#10b981' : '#6b7280'"></up-icon> | ||
| 26 | </view> | 31 | </view> |
| 32 | + | ||
| 27 | <!-- 场景下拉菜单 --> | 33 | <!-- 场景下拉菜单 --> |
| 28 | - <view class="dropdown-menu" v-if="showSceneDropdown"> | ||
| 29 | - <view v-for="(item, index) in sceneList" :key="index" class="dropdown-item" | 34 | + <view class="dropdown-menu" v-if="showSceneDropdown" @click.stop> |
| 35 | + <view v-for="item in sceneList" :key="item.id" class="dropdown-item" | ||
| 30 | :class="{ selected: activeSceneId === item.id }" @click="selectScene(item)"> | 36 | :class="{ selected: activeSceneId === item.id }" @click="selectScene(item)"> |
| 31 | - {{ item.title }} | 37 | + <text>{{ item.title }}</text> |
| 38 | + <up-icon v-if="activeSceneId === item.id" name="checkbox-mark" size="14" color="#10b981" /> | ||
| 32 | </view> | 39 | </view> |
| 33 | </view> | 40 | </view> |
| 34 | </view> | 41 | </view> |
| 42 | + | ||
| 43 | + <!-- 下拉全屏遮罩蒙层 --> | ||
| 44 | + <view class="dropdown-mask" v-if="showPartDropdown || showSceneDropdown" @click="closeAllDropdowns"></view> | ||
| 35 | </view> | 45 | </view> |
| 36 | - <!-- 模板列表 (根据状态自动切换显示) --> | ||
| 37 | - <scroll-view class="template-list" enable-flex scroll-y> | ||
| 38 | - <!-- 用父容器包裹 v-if 分支,解决 key 冲突 --> | ||
| 39 | - <view v-if="!isFiltering" class="sub-template-list"> | ||
| 40 | - <view v-for="(item, index) in templateList" :key="index" class="template-item"> | ||
| 41 | - <image :src="item.urlCover" mode="aspectFill" class="template-img"></image> | ||
| 42 | - <view> | ||
| 43 | - <view class="template-content" @click="gototemplate(item)"> | ||
| 44 | - <view class="template-title">{{ item.name }}</view> | ||
| 45 | - <view class="template-count">{{ item.templatesCount }}个模板</view> | ||
| 46 | - <view class="template-desc">{{ item.description }}</view> | ||
| 47 | - </view> | ||
| 48 | - </view> | ||
| 49 | - </view> | ||
| 50 | - </view> | ||
| 51 | 46 | ||
| 52 | - <!-- 用父容器包裹 v-else 分支,解决 key 冲突 --> | ||
| 53 | - <view v-else> | ||
| 54 | - <view v-for="(template, index) in filteredTemplateList" :key="index" class="template-item"> | ||
| 55 | - <image :src="template.urlCover" mode="aspectFill" class="template-img"></image> | ||
| 56 | - <view> | ||
| 57 | - <view class="template-content" @click="goToTemplateDetail(template)"> | ||
| 58 | - <view class="template-title">{{ template.name }}</view> | ||
| 59 | - <!-- 空数据判断移到了这里 --> | ||
| 60 | - <view class="template-count" v-if="template.primaryMuscles"> | ||
| 61 | - 主要训练部位:{{ template.primaryMuscleNames.join(', ') || '无' }} | 47 | + <!-- 模板列表展示区(自适应滚动) --> |
| 48 | + <view class="list-container"> | ||
| 49 | + <scroll-view class="template-scroll" scroll-y :show-scrollbar="false"> | ||
| 50 | + <view class="scroll-inner"> | ||
| 51 | + <!-- 模式一:未筛选状态,展示【模板大类列表】 --> | ||
| 52 | + <template v-if="!isFiltering"> | ||
| 53 | + <view v-for="item in templateList" :key="item.id" class="template-card" @click="gototemplate(item)"> | ||
| 54 | + <image :src="item.urlCover" mode="aspectFill" class="cover-img" lazy-load /> | ||
| 55 | + <view class="card-content"> | ||
| 56 | + <view class="card-header"> | ||
| 57 | + <text class="title">{{ item.name }}</text> | ||
| 58 | + <text class="count-badge">{{ item.templatesCount || 0 }}个模板</text> | ||
| 59 | + </view> | ||
| 60 | + <text class="desc" v-if="item.description">{{ item.description }}</text> | ||
| 62 | </view> | 61 | </view> |
| 63 | - <view class="template-desc" v-if="template.secondaryMuscles"> | ||
| 64 | - 次要训练部位:{{ template.secondaryMuscleNames.join(', ') || '无' }} | 62 | + </view> |
| 63 | + </template> | ||
| 64 | + | ||
| 65 | + <!-- 模式二:已筛选状态,展示【条件检索后的细分模板列表】 --> | ||
| 66 | + <template v-else> | ||
| 67 | + <view v-for="template in filteredTemplateList" :key="template.id" class="template-card" | ||
| 68 | + @click="goToTemplateDetail(template)"> | ||
| 69 | + <image :src="template.urlCover" mode="aspectFill" class="cover-img" lazy-load /> | ||
| 70 | + <view class="card-content"> | ||
| 71 | + <text class="title">{{ template.name }}</text> | ||
| 72 | + | ||
| 73 | + <!-- 肌群说明 Chips --> | ||
| 74 | + <view class="muscle-tags"> | ||
| 75 | + <view class="tag primary" v-if="template.primaryMuscleNames && template.primaryMuscleNames.length"> | ||
| 76 | + <text class="tag-label">主要:</text> | ||
| 77 | + <text class="tag-val">{{ template.primaryMuscleNames.join(', ') }}</text> | ||
| 78 | + </view> | ||
| 79 | + | ||
| 80 | + <view class="tag secondary" | ||
| 81 | + v-if="template.secondaryMuscleNames && template.secondaryMuscleNames.length"> | ||
| 82 | + <text class="tag-label">次要:</text> | ||
| 83 | + <text class="tag-val">{{ template.secondaryMuscleNames.join(', ') }}</text> | ||
| 84 | + </view> | ||
| 85 | + </view> | ||
| 65 | </view> | 86 | </view> |
| 66 | </view> | 87 | </view> |
| 67 | - </view> | ||
| 68 | - </view> | ||
| 69 | 88 | ||
| 70 | - <!-- 空数据提示:移到 view-else 内部,且作为独立块 --> | ||
| 71 | - <view v-if="filteredTemplateList.length === 0" class="empty-tip"> | ||
| 72 | - 暂无符合条件的训练模板 | 89 | + <!-- 筛选无结果空状态 --> |
| 90 | + <view v-if="filteredTemplateList.length === 0" class="empty-wrapper"> | ||
| 91 | + <up-empty mode="search" text="暂无符合条件的训练模板"></up-empty> | ||
| 92 | + </view> | ||
| 93 | + </template> | ||
| 73 | </view> | 94 | </view> |
| 74 | - </view> | ||
| 75 | - </scroll-view> | 95 | + |
| 96 | + <!-- 底部安全区垫高 --> | ||
| 97 | + <view class="bottom-spacer"></view> | ||
| 98 | + </scroll-view> | ||
| 99 | + </view> | ||
| 76 | </view> | 100 | </view> |
| 77 | </template> | 101 | </template> |
| 78 | 102 | ||
| @@ -80,326 +104,411 @@ | @@ -80,326 +104,411 @@ | ||
| 80 | import { onMounted, ref } from 'vue'; | 104 | import { onMounted, ref } from 'vue'; |
| 81 | import TemplatesApi from '@/sheep/api/Template/Templates'; | 105 | import TemplatesApi from '@/sheep/api/Template/Templates'; |
| 82 | 106 | ||
| 107 | +// 列表响应式数据 | ||
| 83 | const templateList = ref([]); | 108 | const templateList = ref([]); |
| 84 | const filteredTemplateList = ref([]); | 109 | const filteredTemplateList = ref([]); |
| 85 | -// 2. 控制页面显示:false=显示大类,true=显示筛选后的模板 | 110 | + |
| 111 | +// 页面模式标记:false = 显示大类, true = 显示筛选后的细分模板 | ||
| 86 | const isFiltering = ref(false); | 112 | const isFiltering = ref(false); |
| 87 | -const activeSceneId = ref('0'); | ||
| 88 | 113 | ||
| 89 | -// 下拉框部位筛选相关状态 | 114 | +// 部位筛选状态 |
| 90 | const partList = ref([]); | 115 | const partList = ref([]); |
| 91 | -const rawPartData = ref([]); // 存储接口返回的原始部位数据 | 116 | +const rawPartData = ref([]); |
| 92 | const showPartDropdown = ref(false); | 117 | const showPartDropdown = ref(false); |
| 93 | const activePart = ref('部位'); | 118 | const activePart = ref('部位'); |
| 94 | -const activePartId = ref('0'); // 新增:默认选中"不限",id=0 | ||
| 95 | -// 场景筛选相关状态 | 119 | +const activePartId = ref('0'); // '0' 代表不限 |
| 120 | + | ||
| 121 | +// 场景筛选状态 | ||
| 96 | const sceneList = ref([ | 122 | const sceneList = ref([ |
| 97 | { id: '0', title: '不限' }, | 123 | { id: '0', title: '不限' }, |
| 98 | { id: '1', title: '健身房' }, | 124 | { id: '1', title: '健身房' }, |
| 99 | { id: '2', title: '仅哑铃' }, | 125 | { id: '2', title: '仅哑铃' }, |
| 100 | { id: '3', title: '仅哑铃+杠铃' }, | 126 | { id: '3', title: '仅哑铃+杠铃' }, |
| 101 | - // { id: 4, title: '办公室' }, | ||
| 102 | ]); | 127 | ]); |
| 103 | const showSceneDropdown = ref(false); | 128 | const showSceneDropdown = ref(false); |
| 104 | const activeScene = ref('场景'); | 129 | const activeScene = ref('场景'); |
| 130 | +const activeSceneId = ref('0'); // '0' 代表不限 | ||
| 105 | 131 | ||
| 106 | -// 获取模板大类列表 | ||
| 107 | -const TemplatesList = async () => { | 132 | +// ====== API 数据加载 ====== |
| 133 | + | ||
| 134 | +/** | ||
| 135 | + * 获取模板大类列表 | ||
| 136 | + */ | ||
| 137 | +const loadCategoryTemplates = async () => { | ||
| 108 | try { | 138 | try { |
| 109 | const response = await TemplatesApi.getTemplates(); | 139 | const response = await TemplatesApi.getTemplates(); |
| 110 | - templateList.value = response.data; | ||
| 111 | - console.log('模板大类接口返回数据', templateList.value) | 140 | + templateList.value = response.data || []; |
| 112 | } catch (error) { | 141 | } catch (error) { |
| 113 | - console.log('模板大类获取失败', error); | 142 | + console.error('模板大类获取失败:', error); |
| 114 | } | 143 | } |
| 115 | }; | 144 | }; |
| 116 | -// 获取所有细分锻炼部位(接口) | 145 | + |
| 146 | +/** | ||
| 147 | + * 获取所有细分锻炼部位 | ||
| 148 | + */ | ||
| 117 | const getPartCategories = async () => { | 149 | const getPartCategories = async () => { |
| 118 | try { | 150 | try { |
| 119 | const res = await TemplatesApi.getPartAllCategories(); | 151 | const res = await TemplatesApi.getPartAllCategories(); |
| 120 | if (res.code === 0) { | 152 | if (res.code === 0) { |
| 121 | - rawPartData.value = res.data; | ||
| 122 | - console.log('部位分类接口返回数据rawPartData.value:', rawPartData.value) | ||
| 123 | - // 下拉列表赋值 | 153 | + rawPartData.value = res.data || []; |
| 124 | partList.value = [ | 154 | partList.value = [ |
| 125 | - { title: '不限', id: '0' }, // 给"不限"加id='0',方便后续筛选 | ||
| 126 | - ...res.data.map(item => ({ | ||
| 127 | - title: item.name, // 接口返回的name作为显示标题 | ||
| 128 | - id: String(item.id) // 保存接口的id,用于后续筛选传参 | ||
| 129 | - })) | 155 | + { title: '不限', id: '0' }, |
| 156 | + ...rawPartData.value.map((item) => ({ | ||
| 157 | + title: item.name, | ||
| 158 | + id: String(item.id), | ||
| 159 | + })), | ||
| 130 | ]; | 160 | ]; |
| 131 | } | 161 | } |
| 132 | } catch (error) { | 162 | } catch (error) { |
| 133 | - console.log('部位列表获取失败', error); | 163 | + console.error('部位列表获取失败:', error); |
| 134 | } | 164 | } |
| 135 | }; | 165 | }; |
| 136 | -// 切换部位下拉菜单 | 166 | + |
| 167 | +// ====== 交互与筛选逻辑 ====== | ||
| 168 | + | ||
| 169 | +/** | ||
| 170 | + * 切换部位下拉框 | ||
| 171 | + */ | ||
| 137 | const togglePartDropdown = () => { | 172 | const togglePartDropdown = () => { |
| 138 | showPartDropdown.value = !showPartDropdown.value; | 173 | showPartDropdown.value = !showPartDropdown.value; |
| 139 | showSceneDropdown.value = false; | 174 | showSceneDropdown.value = false; |
| 140 | }; | 175 | }; |
| 141 | 176 | ||
| 142 | -// 切换场景下拉菜单 | 177 | +/** |
| 178 | + * 切换场景下拉框 | ||
| 179 | + */ | ||
| 143 | const toggleSceneDropdown = () => { | 180 | const toggleSceneDropdown = () => { |
| 144 | showSceneDropdown.value = !showSceneDropdown.value; | 181 | showSceneDropdown.value = !showSceneDropdown.value; |
| 145 | showPartDropdown.value = false; | 182 | showPartDropdown.value = false; |
| 146 | }; | 183 | }; |
| 147 | 184 | ||
| 148 | -// 选择部位 | 185 | +/** |
| 186 | + * 选择部位 | ||
| 187 | + */ | ||
| 149 | const selectPart = (item) => { | 188 | const selectPart = (item) => { |
| 150 | activePartId.value = item.id; | 189 | activePartId.value = item.id; |
| 151 | - // 判断是否是不限id=0 | ||
| 152 | - if (item.id === '0') { | ||
| 153 | - activePart.value = '部位'; | ||
| 154 | - } else { | ||
| 155 | - activePart.value = item.title; | ||
| 156 | - } | 190 | + activePart.value = item.id === '0' ? '部位' : item.title; |
| 157 | showPartDropdown.value = false; | 191 | showPartDropdown.value = false; |
| 158 | doFilter(); | 192 | doFilter(); |
| 159 | }; | 193 | }; |
| 160 | 194 | ||
| 161 | -// 选择场景 | 195 | +/** |
| 196 | + * 选择场景 | ||
| 197 | + */ | ||
| 162 | const selectScene = (item) => { | 198 | const selectScene = (item) => { |
| 163 | activeSceneId.value = item.id; | 199 | activeSceneId.value = item.id; |
| 164 | - // 判断是否是不限id=0 | ||
| 165 | - if (item.id === '0') { | ||
| 166 | - activeScene.value = '场景'; | ||
| 167 | - } else { | ||
| 168 | - activeScene.value = item.title; | ||
| 169 | - } | 200 | + activeScene.value = item.id === '0' ? '场景' : item.title; |
| 170 | showSceneDropdown.value = false; | 201 | showSceneDropdown.value = false; |
| 171 | doFilter(); | 202 | doFilter(); |
| 172 | }; | 203 | }; |
| 173 | 204 | ||
| 174 | -// 点击页面其他地方关闭所有下拉菜单 | 205 | +/** |
| 206 | + * 关闭所有下拉菜单 | ||
| 207 | + */ | ||
| 175 | const closeAllDropdowns = () => { | 208 | const closeAllDropdowns = () => { |
| 176 | showPartDropdown.value = false; | 209 | showPartDropdown.value = false; |
| 177 | showSceneDropdown.value = false; | 210 | showSceneDropdown.value = false; |
| 178 | }; | 211 | }; |
| 179 | -// 核心:执行筛选逻辑 | 212 | + |
| 213 | +/** | ||
| 214 | + * 执行筛选逻辑 | ||
| 215 | + */ | ||
| 180 | const doFilter = async () => { | 216 | const doFilter = async () => { |
| 181 | - const musclesId = activePartId.value; // 对应接口 musclesId | ||
| 182 | - const scene = activeSceneId.value; // 对应接口 scene | ||
| 183 | - // 2. 判断:是否是【全部不限】 | 217 | + const musclesId = activePartId.value; |
| 218 | + const scene = activeSceneId.value; | ||
| 219 | + | ||
| 220 | + // 条件判断:若均为"不限",切回展示【大类列表】 | ||
| 184 | if (musclesId === '0' && scene === '0') { | 221 | if (musclesId === '0' && scene === '0') { |
| 185 | - // 情况A:都不限 -> 恢复显示【模板大类】 | ||
| 186 | isFiltering.value = false; | 222 | isFiltering.value = false; |
| 187 | - // 重置按钮文字为默认 | ||
| 188 | activePart.value = '部位'; | 223 | activePart.value = '部位'; |
| 189 | activeScene.value = '场景'; | 224 | activeScene.value = '场景'; |
| 190 | - // 大类列表之前已经加载过了,直接显示 | ||
| 191 | return; | 225 | return; |
| 192 | } | 226 | } |
| 193 | - // 情况B:有筛选条件 -> 请求【筛选接口】 | 227 | + |
| 228 | + // 存在有效条件:发请求获取筛选列表 | ||
| 194 | try { | 229 | try { |
| 195 | - isFiltering.value = true; // 切换成模板列表模式 | 230 | + isFiltering.value = true; |
| 196 | const res = await TemplatesApi.queryTemplatesByCondition({ | 231 | const res = await TemplatesApi.queryTemplatesByCondition({ |
| 197 | - musclesId: musclesId, | ||
| 198 | - scene: scene | 232 | + musclesId, |
| 233 | + scene, | ||
| 199 | }); | 234 | }); |
| 200 | if (res.code === 0) { | 235 | if (res.code === 0) { |
| 201 | - // 把接口返回的模板数据存起来,页面会自动刷新 | ||
| 202 | - filteredTemplateList.value = res.data; | ||
| 203 | - console.log('筛选接口返回数据filteredTemplateList.value:', filteredTemplateList.value) | 236 | + filteredTemplateList.value = res.data || []; |
| 204 | } | 237 | } |
| 205 | } catch (error) { | 238 | } catch (error) { |
| 206 | - console.log('筛选失败', error); | ||
| 207 | - // 失败了也清空数据 | 239 | + console.error('筛选模板失败:', error); |
| 208 | filteredTemplateList.value = []; | 240 | filteredTemplateList.value = []; |
| 209 | } | 241 | } |
| 210 | }; | 242 | }; |
| 211 | -// 跳转到模板详情页,传入模板id | ||
| 212 | -// 跳转地址是F:\hongxing-app\hongxing-new\pages4\pages\xunji\xunji-moban-xiangqing.vue | ||
| 213 | -const goToTemplateDetail = (template) => { | 243 | + |
| 244 | +// ====== 路由跳转 ====== | ||
| 245 | + | ||
| 246 | +/** | ||
| 247 | + * 跳转至大类包含的模板列表 | ||
| 248 | + */ | ||
| 249 | +const gototemplate = (item) => { | ||
| 214 | uni.navigateTo({ | 250 | uni.navigateTo({ |
| 215 | - url: `/pages4/pages/xunji/xunji-moban-xiangqing?id=${template.id}`, | 251 | + url: `/pages4/pages/xunji/xunji-moban?id=${item.id}&isBigTemOffice=true`, |
| 216 | }); | 252 | }); |
| 217 | }; | 253 | }; |
| 218 | -const gototemplate = (item) => { | ||
| 219 | - // 传入的是模板大类的id,可以通过模板大类id查询到模板大类包含的模板,这个页面在page4 | 254 | + |
| 255 | +/** | ||
| 256 | + * 跳转至具体模板详情页 | ||
| 257 | + */ | ||
| 258 | +const goToTemplateDetail = (template) => { | ||
| 220 | uni.navigateTo({ | 259 | uni.navigateTo({ |
| 221 | - url: `/pages4/pages/xunji/xunji-moban?id=${item.id}&isBigTemOffice=true`, | 260 | + url: `/pages4/pages/xunji/xunji-moban-xiangqing?id=${template.id}`, |
| 222 | }); | 261 | }); |
| 223 | }; | 262 | }; |
| 224 | 263 | ||
| 225 | onMounted(async () => { | 264 | onMounted(async () => { |
| 226 | - await TemplatesList(); | ||
| 227 | - await getPartCategories(); | ||
| 228 | - console.log('进入模板大类详情'); | ||
| 229 | - | 265 | + await Promise.all([loadCategoryTemplates(), getPartCategories()]); |
| 230 | }); | 266 | }); |
| 231 | </script> | 267 | </script> |
| 232 | 268 | ||
| 233 | <style lang="scss" scoped> | 269 | <style lang="scss" scoped> |
| 234 | .template-page { | 270 | .template-page { |
| 271 | + display: flex; | ||
| 272 | + flex-direction: column; | ||
| 273 | + height: calc(100vh - 44px); | ||
| 274 | + // #ifdef MP-WEIXIN | ||
| 275 | + height: calc(100vh - 110px); | ||
| 276 | + // #endif | ||
| 235 | width: 100vw; | 277 | width: 100vw; |
| 236 | - height: 100vh; | ||
| 237 | - box-sizing: border-box; | 278 | + background-color: #f8fafc; |
| 279 | + overflow: hidden; | ||
| 238 | 280 | ||
| 281 | + /* 顶部筛选控制栏 */ | ||
| 239 | .filter-section { | 282 | .filter-section { |
| 240 | - position: fixed; | ||
| 241 | - // top: 80rpx; | ||
| 242 | - // #ifdef MP-WEIXIN | ||
| 243 | - top: 234rpx; | ||
| 244 | - // #endif | ||
| 245 | - | ||
| 246 | - /* #ifdef H5 */ | ||
| 247 | - top: 77px; | ||
| 248 | - /* #endif */ | ||
| 249 | - | ||
| 250 | - left: 0; | ||
| 251 | - right: 0; | ||
| 252 | - z-index: 999; | ||
| 253 | - | 283 | + flex-shrink: 0; |
| 284 | + position: relative; | ||
| 285 | + z-index: 50; | ||
| 254 | display: flex; | 286 | display: flex; |
| 255 | - gap: 24rpx; | ||
| 256 | - flex-wrap: wrap; | ||
| 257 | - padding: 20rpx; | ||
| 258 | - background-color: white; | ||
| 259 | - | ||
| 260 | - .filter-btn { | ||
| 261 | - display: flex; | ||
| 262 | - align-items: center; | ||
| 263 | - justify-content: center; | ||
| 264 | - width: auto; | ||
| 265 | - min-width: 70rpx; | ||
| 266 | - height: 50rpx; | ||
| 267 | - background: #fff; | ||
| 268 | - color: #333; | ||
| 269 | - font-size: 24rpx; | ||
| 270 | - border: 2rpx solid #ddd; | ||
| 271 | - border-radius: 25rpx; | ||
| 272 | - padding: 0 20rpx; | ||
| 273 | - white-space: nowrap; | ||
| 274 | - } | ||
| 275 | - | ||
| 276 | - .filter-btn .text { | ||
| 277 | - margin-right: 8rpx; | ||
| 278 | - } | ||
| 279 | - | ||
| 280 | - .filter-btn.active { | ||
| 281 | - border-color: #43b05e; | ||
| 282 | - background: #e6f7f0; | ||
| 283 | - color: #43b05e; | ||
| 284 | - } | 287 | + gap: 20rpx; |
| 288 | + padding: 20rpx 30rpx; | ||
| 289 | + background-color: #ffffff; | ||
| 290 | + border-bottom: 1rpx solid #f1f5f9; | ||
| 285 | 291 | ||
| 286 | .filter-wrapper { | 292 | .filter-wrapper { |
| 287 | position: relative; | 293 | position: relative; |
| 288 | - z-index: 10; | ||
| 289 | - } | ||
| 290 | 294 | ||
| 291 | - /* 下拉菜单样式 */ | ||
| 292 | - .dropdown-menu { | ||
| 293 | - position: absolute; | ||
| 294 | - top: calc(100% + 8rpx); | ||
| 295 | - left: 0; | ||
| 296 | - background: #fff; | ||
| 297 | - border-radius: 14rpx; | ||
| 298 | - box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.08); | ||
| 299 | - padding: 12rpx 0; | ||
| 300 | - z-index: 100; | ||
| 301 | - min-width: 150rpx; | ||
| 302 | - max-height: 320rpx; | ||
| 303 | - overflow-y: auto; | ||
| 304 | - border: 1rpx solid #f0f0f0; | ||
| 305 | - } | ||
| 306 | - | ||
| 307 | - /* 下拉菜单项样式 */ | ||
| 308 | - .dropdown-item { | ||
| 309 | - padding: 16rpx 24rpx; | ||
| 310 | - font-size: 26rpx; | ||
| 311 | - color: #333; | ||
| 312 | - white-space: nowrap; | ||
| 313 | - } | 295 | + .filter-btn { |
| 296 | + display: flex; | ||
| 297 | + align-items: center; | ||
| 298 | + gap: 10rpx; | ||
| 299 | + height: 58rpx; | ||
| 300 | + padding: 0 24rpx; | ||
| 301 | + background-color: #f8fafc; | ||
| 302 | + border: 1rpx solid #e2e8f0; | ||
| 303 | + border-radius: 30rpx; | ||
| 304 | + transition: all 0.2s ease; | ||
| 305 | + | ||
| 306 | + .text { | ||
| 307 | + font-size: 26rpx; | ||
| 308 | + color: #334155; | ||
| 309 | + font-weight: 500; | ||
| 310 | + } | ||
| 311 | + | ||
| 312 | + &.active, | ||
| 313 | + &.selected { | ||
| 314 | + background-color: #ecfdf5; | ||
| 315 | + border-color: #10b981; | ||
| 316 | + | ||
| 317 | + .text { | ||
| 318 | + color: #10b981; | ||
| 319 | + font-weight: 600; | ||
| 320 | + } | ||
| 321 | + } | ||
| 322 | + } | ||
| 314 | 323 | ||
| 315 | - /* 下拉菜单项悬停样式 */ | ||
| 316 | - .dropdown-item:hover { | ||
| 317 | - background-color: #43b05e; | 324 | + /* 下拉浮层 */ |
| 325 | + .dropdown-menu { | ||
| 326 | + position: absolute; | ||
| 327 | + top: 72rpx; | ||
| 328 | + left: 0; | ||
| 329 | + z-index: 100; | ||
| 330 | + min-width: 220rpx; | ||
| 331 | + max-height: 380rpx; | ||
| 332 | + background-color: #ffffff; | ||
| 333 | + border-radius: 16rpx; | ||
| 334 | + box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.1); | ||
| 335 | + border: 1rpx solid #f1f5f9; | ||
| 336 | + padding: 8rpx 0; | ||
| 337 | + overflow-y: auto; | ||
| 338 | + | ||
| 339 | + .dropdown-item { | ||
| 340 | + display: flex; | ||
| 341 | + align-items: center; | ||
| 342 | + justify-content: space-between; | ||
| 343 | + padding: 20rpx 28rpx; | ||
| 344 | + font-size: 26rpx; | ||
| 345 | + color: #334155; | ||
| 346 | + transition: background-color 0.15s; | ||
| 347 | + | ||
| 348 | + &:active { | ||
| 349 | + background-color: #f8fafc; | ||
| 350 | + } | ||
| 351 | + | ||
| 352 | + &.selected { | ||
| 353 | + background-color: #f0fdf4; | ||
| 354 | + color: #10b981; | ||
| 355 | + font-weight: 600; | ||
| 356 | + } | ||
| 357 | + } | ||
| 358 | + } | ||
| 318 | } | 359 | } |
| 319 | 360 | ||
| 320 | - /* 下拉菜单项选中样式 */ | ||
| 321 | - .dropdown-item.selected { | ||
| 322 | - background-color: #f0f9f4; | ||
| 323 | - color: #2e9d5a; | ||
| 324 | - font-weight: 500; | 361 | + /* 遮罩蒙层 */ |
| 362 | + .dropdown-mask { | ||
| 363 | + position: fixed; | ||
| 364 | + top: 0; | ||
| 365 | + left: 0; | ||
| 366 | + right: 0; | ||
| 367 | + bottom: 0; | ||
| 368 | + z-index: 40; | ||
| 369 | + background-color: rgba(0, 0, 0, 0.1); | ||
| 325 | } | 370 | } |
| 326 | } | 371 | } |
| 327 | 372 | ||
| 328 | - .template-list { | ||
| 329 | - margin-top: 120rpx; | ||
| 330 | - padding: 0 30rpx; | ||
| 331 | - box-sizing: border-box; | 373 | + /* 列表自适应滚动容器 */ |
| 374 | + .list-container { | ||
| 332 | flex: 1; | 375 | flex: 1; |
| 333 | - display: flex; | ||
| 334 | - flex-direction: column; | ||
| 335 | - | ||
| 336 | - // #ifdef MP-WEIXIN | ||
| 337 | - padding-bottom: 200rpx; | ||
| 338 | - height: calc(100vh - 274rpx); | ||
| 339 | - // #endif | ||
| 340 | - | ||
| 341 | - /* #ifdef H5 */ | ||
| 342 | - margin-top: 87px; | ||
| 343 | - // 关键修复:给H5固定高度,扣除顶部筛选栏77px + 自身margin-top87px | ||
| 344 | - height: calc(100vh - 77px - 87px); | ||
| 345 | - /* #endif */ | ||
| 346 | - | ||
| 347 | - .sub-template-list { | ||
| 348 | - display: flex; | ||
| 349 | - flex-direction: column; | ||
| 350 | - gap: 10rpx; | ||
| 351 | - } | ||
| 352 | - | ||
| 353 | - .template-item { | ||
| 354 | - display: flex; | ||
| 355 | - background-color: white; | ||
| 356 | - border-radius: 16rpx; | ||
| 357 | - overflow: hidden; | ||
| 358 | - box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); | ||
| 359 | - padding: 20rpx; | ||
| 360 | - | ||
| 361 | - &:first-child { | ||
| 362 | - margin-top: 24rpx; | 376 | + height: 0; |
| 377 | + /* 关键:接触 Flex 内容支撑限制 */ | ||
| 378 | + min-height: 0; | ||
| 379 | + overflow: hidden; | ||
| 380 | + | ||
| 381 | + .template-scroll { | ||
| 382 | + width: 100%; | ||
| 383 | + height: 100%; | ||
| 384 | + | ||
| 385 | + .scroll-inner { | ||
| 386 | + padding: 24rpx 30rpx; | ||
| 387 | + display: flex; | ||
| 388 | + flex-direction: column; | ||
| 389 | + gap: 20rpx; | ||
| 390 | + | ||
| 391 | + /* 模板卡片组件样式 */ | ||
| 392 | + .template-card { | ||
| 393 | + display: flex; | ||
| 394 | + align-items: center; | ||
| 395 | + background-color: #ffffff; | ||
| 396 | + border-radius: 20rpx; | ||
| 397 | + padding: 20rpx; | ||
| 398 | + box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.02); | ||
| 399 | + border: 1rpx solid #f1f5f9; | ||
| 400 | + transition: transform 0.15s; | ||
| 401 | + | ||
| 402 | + &:active { | ||
| 403 | + transform: scale(0.985); | ||
| 404 | + background-color: #f8fafc; | ||
| 405 | + } | ||
| 406 | + | ||
| 407 | + .cover-img { | ||
| 408 | + width: 128rpx; | ||
| 409 | + height: 128rpx; | ||
| 410 | + border-radius: 16rpx; | ||
| 411 | + margin-right: 20rpx; | ||
| 412 | + background-color: #f1f5f9; | ||
| 413 | + flex-shrink: 0; | ||
| 414 | + } | ||
| 415 | + | ||
| 416 | + .card-content { | ||
| 417 | + flex: 1; | ||
| 418 | + display: flex; | ||
| 419 | + flex-direction: column; | ||
| 420 | + justify-content: center; | ||
| 421 | + overflow: hidden; | ||
| 422 | + | ||
| 423 | + .card-header { | ||
| 424 | + display: flex; | ||
| 425 | + align-items: center; | ||
| 426 | + justify-content: space-between; | ||
| 427 | + margin-bottom: 8rpx; | ||
| 428 | + | ||
| 429 | + .title { | ||
| 430 | + font-size: 30rpx; | ||
| 431 | + font-weight: 600; | ||
| 432 | + color: #0f172a; | ||
| 433 | + overflow: hidden; | ||
| 434 | + white-space: nowrap; | ||
| 435 | + text-overflow: ellipsis; | ||
| 436 | + flex: 1; | ||
| 437 | + margin-right: 12rpx; | ||
| 438 | + } | ||
| 439 | + | ||
| 440 | + .count-badge { | ||
| 441 | + font-size: 22rpx; | ||
| 442 | + color: #10b981; | ||
| 443 | + background-color: #ecfdf5; | ||
| 444 | + padding: 4rpx 12rpx; | ||
| 445 | + border-radius: 20rpx; | ||
| 446 | + font-weight: 500; | ||
| 447 | + flex-shrink: 0; | ||
| 448 | + } | ||
| 449 | + } | ||
| 450 | + | ||
| 451 | + .title { | ||
| 452 | + font-size: 30rpx; | ||
| 453 | + font-weight: 600; | ||
| 454 | + color: #0f172a; | ||
| 455 | + margin-bottom: 8rpx; | ||
| 456 | + } | ||
| 457 | + | ||
| 458 | + .desc { | ||
| 459 | + font-size: 24rpx; | ||
| 460 | + color: #64748b; | ||
| 461 | + line-height: 1.4; | ||
| 462 | + display: -webkit-box; | ||
| 463 | + -webkit-box-orient: vertical; | ||
| 464 | + -webkit-line-clamp: 2; | ||
| 465 | + overflow: hidden; | ||
| 466 | + } | ||
| 467 | + | ||
| 468 | + /* 肌群 Tag 列表 */ | ||
| 469 | + .muscle-tags { | ||
| 470 | + display: flex; | ||
| 471 | + flex-direction: column; | ||
| 472 | + gap: 6rpx; | ||
| 473 | + margin-top: 4rpx; | ||
| 474 | + | ||
| 475 | + .tag { | ||
| 476 | + display: flex; | ||
| 477 | + align-items: center; | ||
| 478 | + font-size: 22rpx; | ||
| 479 | + line-height: 1.3; | ||
| 480 | + | ||
| 481 | + .tag-label { | ||
| 482 | + color: #94a3b8; | ||
| 483 | + flex-shrink: 0; | ||
| 484 | + } | ||
| 485 | + | ||
| 486 | + .tag-val { | ||
| 487 | + color: #475569; | ||
| 488 | + overflow: hidden; | ||
| 489 | + white-space: nowrap; | ||
| 490 | + text-overflow: ellipsis; | ||
| 491 | + } | ||
| 492 | + | ||
| 493 | + &.primary .tag-val { | ||
| 494 | + color: #059669; | ||
| 495 | + font-weight: 500; | ||
| 496 | + } | ||
| 497 | + } | ||
| 498 | + } | ||
| 499 | + } | ||
| 500 | + } | ||
| 501 | + | ||
| 502 | + .empty-wrapper { | ||
| 503 | + padding: 100rpx 0; | ||
| 504 | + } | ||
| 363 | } | 505 | } |
| 364 | 506 | ||
| 365 | - | ||
| 366 | - .template-img { | ||
| 367 | - width: 120rpx; | ||
| 368 | - height: 120rpx; | ||
| 369 | - border-radius: 12rpx; | ||
| 370 | - margin-right: 20rpx; | ||
| 371 | - } | ||
| 372 | - | ||
| 373 | - .template-content { | ||
| 374 | - flex: 1; | ||
| 375 | - } | ||
| 376 | - | ||
| 377 | - .template-title { | ||
| 378 | - font-size: 32rpx; | ||
| 379 | - color: #333; | ||
| 380 | - margin-bottom: 10rpx; | ||
| 381 | - } | ||
| 382 | - | ||
| 383 | - .template-count { | ||
| 384 | - font-size: 24rpx; | ||
| 385 | - color: #666; | ||
| 386 | - margin-bottom: 10rpx; | ||
| 387 | - } | ||
| 388 | - | ||
| 389 | - .template-desc { | ||
| 390 | - font-size: 24rpx; | ||
| 391 | - color: #666; | ||
| 392 | - line-height: 1.4; | ||
| 393 | - } | ||
| 394 | - | ||
| 395 | - &:last-child { | ||
| 396 | - margin-bottom: 5rpx; | ||
| 397 | - | ||
| 398 | - /* #ifdef H5 */ | ||
| 399 | - margin-bottom: 30rpx; | ||
| 400 | - /* #endif */ | 507 | + .bottom-spacer { |
| 508 | + height: env(safe-area-inset-bottom); | ||
| 509 | + padding-bottom: 40rpx; | ||
| 401 | } | 510 | } |
| 402 | } | 511 | } |
| 403 | } | 512 | } |
| 404 | } | 513 | } |
| 405 | -</style> | 514 | +</style> |
| 1 | <template> | 1 | <template> |
| 2 | <view class="calendar-page"> | 2 | <view class="calendar-page"> |
| 3 | - <!-- 顶部栏:年月选择器和功能按钮 --> | 3 | + <!-- 顶部操作栏 --> |
| 4 | <view class="top-bar"> | 4 | <view class="top-bar"> |
| 5 | - <view class="month-picker"> | ||
| 6 | - <uni-datetime-picker type="date" v-model="currentMonth" :clear-icon="false" /> | ||
| 7 | - <up-icon name="arrow-down" color="#333" size="15"></up-icon> | ||
| 8 | - </view> | 5 | + <!-- 年月选择胶囊 --> |
| 6 | + <picker mode="date" fields="month" :value="currentMonth" @change="onMonthChange"> | ||
| 7 | + <view class="month-picker-pill"> | ||
| 8 | + <text class="month-text">{{ displayMonthText }}</text> | ||
| 9 | + <up-icon name="arrow-down-fill" color="#333333" size="10"></up-icon> | ||
| 10 | + </view> | ||
| 11 | + </picker> | ||
| 12 | + | ||
| 13 | + <!-- 说明按钮 --> | ||
| 9 | <view class="action-buttons"> | 14 | <view class="action-buttons"> |
| 10 | - <button class="action-btn" @click="ShowExplainPopup = true">说明</button> | 15 | + <up-button text="说明" type="info" plain shape="circle" size="mini" |
| 16 | + customStyle="padding: 0 24rpx; height: 56rpx; font-size: 24rpx; border-color: #dcdfe6; color: #606266;" | ||
| 17 | + @click="showExplainPopup = true" /> | ||
| 11 | </view> | 18 | </view> |
| 12 | </view> | 19 | </view> |
| 13 | - <!-- 日历网格 --> | ||
| 14 | - <view class="calendar-grid"> | 20 | + |
| 21 | + <!-- 日历卡片容器 --> | ||
| 22 | + <view class="calendar-card"> | ||
| 23 | + <!-- 星期表头 --> | ||
| 15 | <view class="week-header"> | 24 | <view class="week-header"> |
| 16 | <text v-for="(day, index) in weekDays" :key="index" class="week-day"> | 25 | <text v-for="(day, index) in weekDays" :key="index" class="week-day"> |
| 17 | {{ day }} | 26 | {{ day }} |
| 18 | </text> | 27 | </text> |
| 19 | </view> | 28 | </view> |
| 29 | + | ||
| 30 | + <!-- 日期网格 (动态 5行 或 6行) --> | ||
| 20 | <view class="date-grid"> | 31 | <view class="date-grid"> |
| 21 | - <view v-for="(date, index) in dates" :key="index" class="date-cell" :class="{ today: date.today }" | ||
| 22 | - @tap="selectDate(date)"> | ||
| 23 | - <text v-if="!date.today" class="date-number">{{ date.day }}</text> | ||
| 24 | - <text v-else class="today-text">今</text> | 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> | ||
| 25 | 43 | ||
| 26 | - <!-- 内容显示区域 --> | 44 | + <!-- 计划/容量数据列表 --> |
| 27 | <view class="plan-list" | 45 | <view class="plan-list" |
| 28 | v-if="date.planList.length || date.noteList.length || (date.weight && date.weight > 0)"> | 46 | v-if="date.planList.length || date.noteList.length || (date.weight && date.weight > 0)"> |
| 29 | - <template v-for="(item, idx) in getVisibleItems(date).items" :key="idx"> | ||
| 30 | - <view class="plan-name" :class="{ | ||
| 31 | - 'weight-item': item.type === 'weight', | ||
| 32 | - 'note-item': item.type === 'note' | ||
| 33 | - }" :style="item.bg ? { backgroundColor: item.bg } : ''"> | ||
| 34 | - {{ item.text }} | 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> | ||
| 35 | </view> | 51 | </view> |
| 36 | </template> | 52 | </template> |
| 37 | 53 | ||
| 38 | - <!-- 统一只显示一个 + 剩余总数 --> | ||
| 39 | - <text v-if="getVisibleItems(date).remaining > 0" class="plan-extra"> | ||
| 40 | - +{{ getVisibleItems(date).remaining }} | ||
| 41 | - </text> | 54 | + <!-- 溢出剩余条数 --> |
| 55 | + <view v-if="getCellVisibleItems(date).remaining > 0" class="plan-extra"> | ||
| 56 | + +{{ getCellVisibleItems(date).remaining }} | ||
| 57 | + </view> | ||
| 42 | </view> | 58 | </view> |
| 43 | </view> | 59 | </view> |
| 44 | </view> | 60 | </view> |
| 45 | </view> | 61 | </view> |
| 46 | 62 | ||
| 47 | - <!-- <view class="gridCellContent" v-if="ShowGridCellPopup && selectedDate"> | ||
| 48 | - <GridCellContentPopup :date="selectedDate" @close="ShowGridCellPopup = false" /> | ||
| 49 | - </view> --> | ||
| 50 | - | 63 | + <!-- 单元格详情弹窗 --> |
| 51 | <GridCellContentPopup ref="gridPopupRef" :date="selectedDate" @refreshCalendar="handleRefreshCalendar" /> | 64 | <GridCellContentPopup ref="gridPopupRef" :date="selectedDate" @refreshCalendar="handleRefreshCalendar" /> |
| 52 | 65 | ||
| 53 | - <!-- 日历说明弹窗 --> | ||
| 54 | - <view class="explain-popup-overlay" v-if="ShowExplainPopup" @click.self="ShowExplainPopup = false"> | ||
| 55 | - <view class="explain-popup"> | 66 | + <!-- 日历图标说明弹窗 --> |
| 67 | + <up-popup :show="showExplainPopup" mode="bottom" round="24rpx" :safeAreaInsetBottom="true" | ||
| 68 | + @close="showExplainPopup = false"> | ||
| 69 | + <view class="explain-popup-content"> | ||
| 56 | <view class="explain-header"> | 70 | <view class="explain-header"> |
| 57 | - <text class="explain-title">日历说明</text> | ||
| 58 | - <view class="close-btn" @click="ShowExplainPopup = false"> | ||
| 59 | - <text>×</text> | ||
| 60 | - </view> | 71 | + <text class="explain-title">日历图标说明</text> |
| 72 | + <up-icon name="close" color="#909399" size="18" @click="showExplainPopup = false" /> | ||
| 61 | </view> | 73 | </view> |
| 74 | + | ||
| 62 | <view class="explain-list"> | 75 | <view class="explain-list"> |
| 63 | <view class="explain-item"> | 76 | <view class="explain-item"> |
| 64 | <view class="color-block blue"></view> | 77 | <view class="color-block blue"></view> |
| 65 | - <text class="item-label">分钟</text> | ||
| 66 | - <text class="item-desc">在场馆的运动时长</text> | 78 | + <view class="item-info"> |
| 79 | + <text class="item-label">时长</text> | ||
| 80 | + <text class="item-desc">在场馆的实际运动时长</text> | ||
| 81 | + </view> | ||
| 67 | </view> | 82 | </view> |
| 83 | + | ||
| 68 | <view class="explain-item"> | 84 | <view class="explain-item"> |
| 69 | <view class="color-block green"></view> | 85 | <view class="color-block green"></view> |
| 70 | - <text class="item-label">容量</text> | ||
| 71 | - <text class="item-desc">力量训练的总重量</text> | 86 | + <view class="item-info"> |
| 87 | + <text class="item-label">容量</text> | ||
| 88 | + <text class="item-desc">力量训练累计产生的总负重重量</text> | ||
| 89 | + </view> | ||
| 72 | </view> | 90 | </view> |
| 91 | + | ||
| 73 | <view class="explain-item"> | 92 | <view class="explain-item"> |
| 74 | <view class="color-block yellow"></view> | 93 | <view class="color-block yellow"></view> |
| 75 | - <text class="item-label">课程</text> | ||
| 76 | - <text class="item-desc">训记或已结课的线下课程</text> | 94 | + <view class="item-info"> |
| 95 | + <text class="item-label">课程</text> | ||
| 96 | + <text class="item-desc">训记完成或已结课的线下课程</text> | ||
| 97 | + </view> | ||
| 77 | </view> | 98 | </view> |
| 99 | + | ||
| 78 | <view class="explain-item"> | 100 | <view class="explain-item"> |
| 79 | <view class="color-block gray"></view> | 101 | <view class="color-block gray"></view> |
| 80 | - <text class="item-label">课程</text> | ||
| 81 | - <text class="item-desc">训记排课或待上课的线下课程</text> | 102 | + <view class="item-info"> |
| 103 | + <text class="item-label">排课</text> | ||
| 104 | + <text class="item-desc">训记排课计划或待上课的课程</text> | ||
| 105 | + </view> | ||
| 82 | </view> | 106 | </view> |
| 83 | </view> | 107 | </view> |
| 84 | </view> | 108 | </view> |
| 85 | - </view> | 109 | + </up-popup> |
| 86 | </view> | 110 | </view> |
| 87 | </template> | 111 | </template> |
| 88 | 112 | ||
| 89 | <script setup> | 113 | <script setup> |
| 90 | import { ref, watch, computed, nextTick } from 'vue'; | 114 | import { ref, watch, computed, nextTick } from 'vue'; |
| 115 | +import dayjs from 'dayjs'; | ||
| 91 | import DailyTemplateApi from '@/sheep/api/calendar/date'; | 116 | import DailyTemplateApi from '@/sheep/api/calendar/date'; |
| 92 | -import GridCellContentPopup from '@/pages/xunji/components/rili-components/grid-cell-content-popup.vue' | 117 | +import GridCellContentPopup from '@/pages/xunji/components/rili-components/grid-cell-content-popup.vue'; |
| 93 | 118 | ||
| 94 | -// ====== 响应式数据 ====== | 119 | +// 常量定义 |
| 120 | +const MAX_CELL_LINES = 3; | ||
| 95 | const weekDays = ref(['一', '二', '三', '四', '五', '六', '日']); | 121 | const weekDays = ref(['一', '二', '三', '四', '五', '六', '日']); |
| 122 | + | ||
| 123 | +// 响应式状态 | ||
| 96 | const dates = ref([]); | 124 | const dates = ref([]); |
| 97 | -const today = new Date(); | ||
| 98 | -const ShowExplainPopup = ref(false) | 125 | +const showExplainPopup = ref(false); |
| 99 | const selectedDate = ref(''); | 126 | const selectedDate = ref(''); |
| 100 | -// const ShowGridCellPopup = ref(false) | ||
| 101 | const gridPopupRef = ref(null); | 127 | const gridPopupRef = ref(null); |
| 102 | 128 | ||
| 103 | -// 自动获取当前年月 | ||
| 104 | -const now = new Date(); | ||
| 105 | -const currentMonth = ref( | ||
| 106 | - `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}` | ||
| 107 | -); | 129 | +const currentMonth = ref(dayjs().format('YYYY-MM-DD')); |
| 108 | 130 | ||
| 109 | -// ====== 工具函数 ====== | ||
| 110 | -const formatDateKey = (y, m, d) => { | ||
| 111 | - return `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`; | ||
| 112 | -}; | 131 | +// 顶部年月显示 |
| 132 | +const displayMonthText = computed(() => { | ||
| 133 | + return dayjs(currentMonth.value).format('YYYY年M月'); | ||
| 134 | +}); | ||
| 113 | 135 | ||
| 114 | -const arrayToDateStr = (arr) => { | ||
| 115 | - if (!Array.isArray(arr) || arr.length !== 3) return null; | ||
| 116 | - const [y, m, d] = arr; | ||
| 117 | - return `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`; | 136 | +// 年月 Picker 触发 |
| 137 | +const onMonthChange = (e) => { | ||
| 138 | + if (e.detail && e.detail.value) { | ||
| 139 | + currentMonth.value = dayjs(e.detail.value).startOf('month').format('YYYY-MM-DD'); | ||
| 140 | + } | ||
| 118 | }; | 141 | }; |
| 119 | 142 | ||
| 120 | -// ====== 生成日历数据 ====== | ||
| 121 | -const generateCalendar = (year, month) => { | ||
| 122 | - const firstDay = new Date(year, month - 1, 1); | ||
| 123 | - const lastDay = new Date(year, month, 0).getDate(); | ||
| 124 | - let startWeekDay = firstDay.getDay(); | ||
| 125 | - const adjustedStart = startWeekDay === 0 ? 6 : startWeekDay - 1; | 143 | +// ====== 计算每个日期格子的背景颜色 ====== |
| 144 | +const getCellBgColor = (date) => { | ||
| 126 | 145 | ||
| 127 | - const calendarDates = []; | ||
| 128 | 146 | ||
| 129 | - // 上月填充 | ||
| 130 | - const prevMonthLastDay = new Date(year, month - 1, 0).getDate(); | ||
| 131 | - for (let i = adjustedStart; i > 0; i--) { | ||
| 132 | - const day = prevMonthLastDay - i + 1; | ||
| 133 | - const y = month === 1 ? year - 1 : year; | ||
| 134 | - const m = month === 1 ? 12 : month - 1; | ||
| 135 | - calendarDates.push({ | ||
| 136 | - day, | ||
| 137 | - today: false, | ||
| 138 | - dateKey: formatDateKey(y, m, day), | ||
| 139 | - }); | 147 | + // 2. 如果存在训练计划/备注/重量,优先采用训练本身的背景色 |
| 148 | + if (date.planList && date.planList.length > 0 && date.planList[0].bg) { | ||
| 149 | + return date.planList[0].bg; | ||
| 150 | + } | ||
| 151 | + if (date.noteList && date.noteList.length > 0 && date.noteList[0].backgroundColor) { | ||
| 152 | + return date.noteList[0].backgroundColor; | ||
| 140 | } | 153 | } |
| 141 | 154 | ||
| 142 | - // 本月 | ||
| 143 | - for (let d = 1; d <= lastDay; d++) { | ||
| 144 | - const isToday = | ||
| 145 | - year === today.getFullYear() && | ||
| 146 | - month === today.getMonth() + 1 && | ||
| 147 | - d === today.getDate(); | ||
| 148 | - calendarDates.push({ | ||
| 149 | - day: d, | ||
| 150 | - today: isToday, | ||
| 151 | - dateKey: formatDateKey(year, month, d), | ||
| 152 | - }); | 155 | + // 3. 有容量负重但无特定背景色:赋予柔和浅绿 |
| 156 | + if (date.weight && date.weight > 0) { | ||
| 157 | + return '#f0fdf4'; | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + // 4. 有计划但未指定背景色:赋予柔和浅黄 | ||
| 161 | + if (date.planList && date.planList.length > 0) { | ||
| 162 | + return '#fefce8'; | ||
| 153 | } | 163 | } |
| 154 | 164 | ||
| 155 | - // 下月填充 | ||
| 156 | - const remaining = 42 - calendarDates.length; | ||
| 157 | - for (let d = 1; d <= remaining; d++) { | ||
| 158 | - const y = month === 12 ? year + 1 : year; | ||
| 159 | - const m = month === 12 ? 1 : month + 1; | 165 | + |
| 166 | + | ||
| 167 | + // 6. 普通无训练的本月休息日:基础卡片白色/浅灰 | ||
| 168 | + return '#ededed'; | ||
| 169 | +}; | ||
| 170 | + | ||
| 171 | + | ||
| 172 | +const generateCalendar = (monthStr) => { | ||
| 173 | + const current = dayjs(monthStr); | ||
| 174 | + const startOfMonth = current.startOf('month'); | ||
| 175 | + const daysInMonth = current.daysInMonth(); | ||
| 176 | + | ||
| 177 | + const dayOfWeek = startOfMonth.day(); // 0 表示周日 | ||
| 178 | + const startOffset = dayOfWeek === 0 ? 6 : dayOfWeek - 1; | ||
| 179 | + | ||
| 180 | + // 动态计算总单元格:35(5行)或 42(6行) | ||
| 181 | + const totalCellsNeeded = startOffset + daysInMonth; | ||
| 182 | + const totalGridSize = totalCellsNeeded > 35 ? 42 : 35; | ||
| 183 | + | ||
| 184 | + const gridStartDate = startOfMonth.subtract(startOffset, 'day'); | ||
| 185 | + const todayStr = dayjs().format('YYYY-MM-DD'); | ||
| 186 | + | ||
| 187 | + const calendarDates = []; | ||
| 188 | + for (let i = 0; i < totalGridSize; i++) { | ||
| 189 | + const cellDate = gridStartDate.add(i, 'day'); | ||
| 190 | + const dateKey = cellDate.format('YYYY-MM-DD'); | ||
| 191 | + | ||
| 160 | calendarDates.push({ | 192 | calendarDates.push({ |
| 161 | - day: d, | ||
| 162 | - today: false, | ||
| 163 | - dateKey: formatDateKey(y, m, d), | 193 | + day: cellDate.date(), |
| 194 | + today: dateKey === todayStr, | ||
| 195 | + isCurrentMonth: cellDate.month() === current.month(), | ||
| 196 | + dateKey, | ||
| 164 | }); | 197 | }); |
| 165 | } | 198 | } |
| 166 | 199 | ||
| @@ -168,26 +201,19 @@ const generateCalendar = (year, month) => { | @@ -168,26 +201,19 @@ const generateCalendar = (year, month) => { | ||
| 168 | }; | 201 | }; |
| 169 | 202 | ||
| 170 | // ====== 加载计划数据 ====== | 203 | // ====== 加载计划数据 ====== |
| 171 | -const loadPlans = async (year, month) => { | 204 | +const loadPlans = async (monthStr) => { |
| 172 | try { | 205 | try { |
| 173 | - // 关键:获取 上个月、当前月、下个月 → 保证前后日期都有数据 | ||
| 174 | - const prevMonth = month - 1 < 1 ? 12 : month - 1; | ||
| 175 | - const prevYear = month - 1 < 1 ? year - 1 : year; | ||
| 176 | - | ||
| 177 | - const nextMonth = month + 1 > 12 ? 1 : month + 1; | ||
| 178 | - const nextYear = month + 1 > 12 ? year + 1 : year; | ||
| 179 | - | ||
| 180 | - // 并行请求三个月数据 | ||
| 181 | - // const [prevRes, currRes, nextRes] = await Promise.all([ | ||
| 182 | - // DailyTemplateApi.getCalendarPlans(prevYear, prevMonth), | ||
| 183 | - // DailyTemplateApi.getCalendarPlans(year, month), | ||
| 184 | - // DailyTemplateApi.getCalendarPlans(nextYear, nextMonth), | ||
| 185 | - // ]); | ||
| 186 | - const prevRes = await DailyTemplateApi.getCalendarPlans(prevYear, prevMonth); | ||
| 187 | - const currRes = await DailyTemplateApi.getCalendarPlans(year, month); | ||
| 188 | - const nextRes = await DailyTemplateApi.getCalendarPlans(nextYear, nextMonth); | ||
| 189 | - | ||
| 190 | - // 合并三个月数据 | 206 | + const curr = dayjs(monthStr); |
| 207 | + const prev = curr.subtract(1, 'month'); | ||
| 208 | + const next = curr.add(1, 'month'); | ||
| 209 | + | ||
| 210 | + // 并行请求 3 个月数据 | ||
| 211 | + const [prevRes, currRes, nextRes] = await Promise.all([ | ||
| 212 | + DailyTemplateApi.getCalendarPlans(prev.year(), prev.month() + 1).catch(() => ({ data: [] })), | ||
| 213 | + DailyTemplateApi.getCalendarPlans(curr.year(), curr.month() + 1).catch(() => ({ data: [] })), | ||
| 214 | + DailyTemplateApi.getCalendarPlans(next.year(), next.month() + 1).catch(() => ({ data: [] })), | ||
| 215 | + ]); | ||
| 216 | + | ||
| 191 | const allPlans = [ | 217 | const allPlans = [ |
| 192 | ...(prevRes.data || []), | 218 | ...(prevRes.data || []), |
| 193 | ...(currRes.data || []), | 219 | ...(currRes.data || []), |
| @@ -199,24 +225,25 @@ const loadPlans = async (year, month) => { | @@ -199,24 +225,25 @@ const loadPlans = async (year, month) => { | ||
| 199 | const weightMap = {}; | 225 | const weightMap = {}; |
| 200 | 226 | ||
| 201 | allPlans.forEach((plan) => { | 227 | allPlans.forEach((plan) => { |
| 202 | - let dateStr; | 228 | + let dateStr = null; |
| 203 | if (Array.isArray(plan.trainDate)) { | 229 | if (Array.isArray(plan.trainDate)) { |
| 204 | - dateStr = arrayToDateStr(plan.trainDate); | 230 | + const [y, m, d] = plan.trainDate; |
| 231 | + dateStr = dayjs(`${y}-${m}-${d}`).format('YYYY-MM-DD'); | ||
| 205 | } else if (typeof plan.trainDate === 'string') { | 232 | } else if (typeof plan.trainDate === 'string') { |
| 206 | - dateStr = plan.trainDate; | 233 | + dateStr = dayjs(plan.trainDate).format('YYYY-MM-DD'); |
| 207 | } | 234 | } |
| 208 | 235 | ||
| 209 | - if (!dateStr) return; | 236 | + if (!dateStr || dateStr === 'Invalid Date') return; |
| 210 | 237 | ||
| 211 | - // 处理多条备注 | 238 | + // 备注处理 |
| 212 | if (Array.isArray(plan.notes) && plan.notes.length > 0) { | 239 | if (Array.isArray(plan.notes) && plan.notes.length > 0) { |
| 213 | - noteMap[dateStr] = plan.notes.map(note => ({ | 240 | + noteMap[dateStr] = plan.notes.map((note) => ({ |
| 214 | content: note.content || '', | 241 | content: note.content || '', |
| 215 | - backgroundColor: note.backgroundColor || '#ccc' | 242 | + backgroundColor: note.backgroundColor || '#e2e8f0', |
| 216 | })); | 243 | })); |
| 217 | } | 244 | } |
| 218 | 245 | ||
| 219 | - // 处理计划和重量 | 246 | + // 训练计划与容量处理 |
| 220 | planMap[dateStr] = planMap[dateStr] || []; | 247 | planMap[dateStr] = planMap[dateStr] || []; |
| 221 | if (Array.isArray(plan.templates)) { | 248 | if (Array.isArray(plan.templates)) { |
| 222 | let totalWeightForDay = 0; | 249 | let totalWeightForDay = 0; |
| @@ -224,7 +251,7 @@ const loadPlans = async (year, month) => { | @@ -224,7 +251,7 @@ const loadPlans = async (year, month) => { | ||
| 224 | if (template && template.name) { | 251 | if (template && template.name) { |
| 225 | planMap[dateStr].push({ | 252 | planMap[dateStr].push({ |
| 226 | name: template.name, | 253 | name: template.name, |
| 227 | - bg: template.backgroundColor || '#ffdd44' | 254 | + bg: template.backgroundColor || '#fef08a', |
| 228 | }); | 255 | }); |
| 229 | } | 256 | } |
| 230 | if (template && typeof template.totalWeight === 'number') { | 257 | if (template && typeof template.totalWeight === 'number') { |
| @@ -237,57 +264,37 @@ const loadPlans = async (year, month) => { | @@ -237,57 +264,37 @@ const loadPlans = async (year, month) => { | ||
| 237 | } | 264 | } |
| 238 | }); | 265 | }); |
| 239 | 266 | ||
| 240 | - const newDates = generateCalendar(year, month).map((date) => ({ | 267 | + dates.value = generateCalendar(monthStr).map((date) => ({ |
| 241 | ...date, | 268 | ...date, |
| 242 | planList: planMap[date.dateKey] || [], | 269 | planList: planMap[date.dateKey] || [], |
| 243 | noteList: noteMap[date.dateKey] || [], | 270 | noteList: noteMap[date.dateKey] || [], |
| 244 | weight: weightMap[date.dateKey] || null, | 271 | weight: weightMap[date.dateKey] || null, |
| 245 | })); | 272 | })); |
| 246 | - | ||
| 247 | - dates.value = newDates; | ||
| 248 | } catch (err) { | 273 | } catch (err) { |
| 249 | - console.error('加载日历失败:', err); | ||
| 250 | - const [y, m] = [ | ||
| 251 | - parseInt(currentMonth.value.split('-')[0]), | ||
| 252 | - parseInt(currentMonth.value.split('-')[1]), | ||
| 253 | - ]; | ||
| 254 | - dates.value = generateCalendar(y, m); | 274 | + console.error('加载日历数据失败:', err); |
| 275 | + dates.value = generateCalendar(monthStr); | ||
| 255 | } | 276 | } |
| 256 | - console.log('日历主页面处理的完的是数据:dates', dates); | ||
| 257 | - | ||
| 258 | }; | 277 | }; |
| 259 | -const selectedDateData = computed(() => { | ||
| 260 | - return dates.value.find(item => item.dateKey === selectedDate.value) || null; | ||
| 261 | -}); | ||
| 262 | 278 | ||
| 263 | -// ====================== 核心显示逻辑(已修复)====================== | ||
| 264 | -const maxLines = 3; // 一个格子最多显示3行 | ||
| 265 | - | ||
| 266 | -const getVisibleItems = (date) => { | 279 | +// ====== 视图格子显示控制 ====== |
| 280 | +const getCellVisibleItems = (date) => { | ||
| 267 | const items = []; | 281 | const items = []; |
| 268 | let remaining = 0; | 282 | let remaining = 0; |
| 269 | 283 | ||
| 270 | - // 1. 重量优先显示 | ||
| 271 | if (date.weight && date.weight > 0) { | 284 | if (date.weight && date.weight > 0) { |
| 272 | items.push({ type: 'weight', text: `${date.weight}kg` }); | 285 | items.push({ type: 'weight', text: `${date.weight}kg` }); |
| 273 | } | 286 | } |
| 274 | 287 | ||
| 275 | - // 2. 显示训练计划(已修改) | ||
| 276 | for (const plan of date.planList) { | 288 | for (const plan of date.planList) { |
| 277 | - if (items.length < maxLines) { | ||
| 278 | - items.push({ | ||
| 279 | - type: 'plan', | ||
| 280 | - text: plan.name, | ||
| 281 | - bg: plan.bg | ||
| 282 | - }); | 289 | + if (items.length < MAX_CELL_LINES) { |
| 290 | + items.push({ type: 'plan', text: plan.name, bg: plan.bg }); | ||
| 283 | } else { | 291 | } else { |
| 284 | remaining++; | 292 | remaining++; |
| 285 | } | 293 | } |
| 286 | } | 294 | } |
| 287 | 295 | ||
| 288 | - // 3. 显示备注 | ||
| 289 | for (const note of date.noteList) { | 296 | for (const note of date.noteList) { |
| 290 | - if (items.length < maxLines) { | 297 | + if (items.length < MAX_CELL_LINES) { |
| 291 | items.push({ type: 'note', text: note.content, bg: note.backgroundColor }); | 298 | items.push({ type: 'note', text: note.content, bg: note.backgroundColor }); |
| 292 | } else { | 299 | } else { |
| 293 | remaining++; | 300 | remaining++; |
| @@ -297,153 +304,79 @@ const getVisibleItems = (date) => { | @@ -297,153 +304,79 @@ const getVisibleItems = (date) => { | ||
| 297 | return { items, remaining }; | 304 | return { items, remaining }; |
| 298 | }; | 305 | }; |
| 299 | 306 | ||
| 300 | -// 接收子组件通知,刷新整个日历数据 | ||
| 301 | const handleRefreshCalendar = () => { | 307 | const handleRefreshCalendar = () => { |
| 302 | - const [year, month] = currentMonth.value.split('-').map(Number); | ||
| 303 | - loadPlans(year, month); | 308 | + loadPlans(currentMonth.value); |
| 304 | }; | 309 | }; |
| 305 | -// ==================================================================== | ||
| 306 | 310 | ||
| 307 | -// ====== 监听月份变化 ====== | ||
| 308 | watch( | 311 | watch( |
| 309 | currentMonth, | 312 | currentMonth, |
| 310 | (newVal) => { | 313 | (newVal) => { |
| 311 | - const [year, month] = newVal.split('-').map(Number); | ||
| 312 | - loadPlans(year, month); | 314 | + if (newVal) { |
| 315 | + loadPlans(newVal); | ||
| 316 | + } | ||
| 313 | }, | 317 | }, |
| 314 | - { immediate: true }, | 318 | + { immediate: true } |
| 315 | ); | 319 | ); |
| 316 | 320 | ||
| 317 | const selectDate = async (date) => { | 321 | const selectDate = async (date) => { |
| 318 | selectedDate.value = date.dateKey; | 322 | selectedDate.value = date.dateKey; |
| 319 | - // ShowGridCellPopup.value = true; | ||
| 320 | - await nextTick() | 323 | + await nextTick(); |
| 321 | gridPopupRef.value?.openPopup(); | 324 | gridPopupRef.value?.openPopup(); |
| 322 | }; | 325 | }; |
| 323 | - | ||
| 324 | </script> | 326 | </script> |
| 325 | 327 | ||
| 326 | <style lang="scss" scoped> | 328 | <style lang="scss" scoped> |
| 327 | .calendar-page { | 329 | .calendar-page { |
| 328 | height: 100%; | 330 | height: 100%; |
| 329 | - padding: 40rpx; | ||
| 330 | - background-color: white; | 331 | + padding: 30rpx 15rpx 20rpx; |
| 332 | + background-color: #fff; | ||
| 331 | box-sizing: border-box; | 333 | box-sizing: border-box; |
| 332 | 334 | ||
| 333 | .top-bar { | 335 | .top-bar { |
| 334 | display: flex; | 336 | display: flex; |
| 335 | justify-content: space-between; | 337 | justify-content: space-between; |
| 336 | align-items: center; | 338 | align-items: center; |
| 337 | - margin-bottom: 20rpx; | ||
| 338 | - margin-top: 35rpx; | ||
| 339 | - | ||
| 340 | - .month-picker { | ||
| 341 | - height: 40rpx; | ||
| 342 | - border-radius: 50rpx; | ||
| 343 | - overflow: hidden; | ||
| 344 | - padding: 0 20rpx; | ||
| 345 | - gap: 10rpx; | 339 | + margin-bottom: 24rpx; |
| 346 | 340 | ||
| 347 | - :deep(.uni-date-x--border) { | ||
| 348 | - border: none !important; | ||
| 349 | - } | ||
| 350 | - | ||
| 351 | - :deep(.uni-date__x-input) { | ||
| 352 | - height: 40rpx; | ||
| 353 | - line-height: 40rpx; | ||
| 354 | - padding: 0 10rpx; | ||
| 355 | - } | ||
| 356 | - | ||
| 357 | - // 微信小程序端样式适配 | ||
| 358 | - // #ifdef MP-WEIXIN | 341 | + .month-picker-pill { |
| 359 | display: flex; | 342 | display: flex; |
| 360 | align-items: center; | 343 | align-items: center; |
| 361 | - justify-content: center; | ||
| 362 | - min-width: 200rpx; | ||
| 363 | - box-sizing: border-box; | ||
| 364 | - | ||
| 365 | - :deep(.uni-date-x) { | ||
| 366 | - height: 35rpx; | ||
| 367 | - // min-height: 40rpx; | ||
| 368 | - border-radius: 50rpx; | ||
| 369 | - background-color: transparent; | ||
| 370 | - padding: 0; | ||
| 371 | - margin: 0; | ||
| 372 | - border: none !important; | ||
| 373 | - | ||
| 374 | - } | ||
| 375 | - | ||
| 376 | - :deep(.uni-date-editor--x) { | ||
| 377 | - height: 100%; | ||
| 378 | - width: 100%; | ||
| 379 | - display: flex; | ||
| 380 | - align-items: center; | ||
| 381 | - justify-content: center; | ||
| 382 | - border: none !important; | ||
| 383 | - } | ||
| 384 | - | ||
| 385 | - :deep(.uni-date__x-input) { | ||
| 386 | - // height: 40rpx; | ||
| 387 | - // line-height: 40rpx; | ||
| 388 | - font-size: 24rpx; | ||
| 389 | - color: #333; | ||
| 390 | - padding: 0 10rpx; | ||
| 391 | - text-align: center; | ||
| 392 | - flex: 1; | ||
| 393 | - overflow: hidden; | ||
| 394 | - text-overflow: ellipsis; | ||
| 395 | - white-space: nowrap; | ||
| 396 | - } | ||
| 397 | - | ||
| 398 | - :deep(.icon-calendar) { | ||
| 399 | - display: none !important; | 344 | + gap: 12rpx; |
| 345 | + background-color: #ececec; | ||
| 346 | + padding: 12rpx 28rpx; | ||
| 347 | + border-radius: 40rpx; | ||
| 348 | + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04); | ||
| 349 | + border: 1rpx solid #fff; | ||
| 350 | + | ||
| 351 | + .month-text { | ||
| 352 | + font-size: 30rpx; | ||
| 353 | + font-weight: 600; | ||
| 354 | + color: #1a1a1a; | ||
| 400 | } | 355 | } |
| 401 | - | ||
| 402 | - // #endif | ||
| 403 | } | 356 | } |
| 404 | - } | ||
| 405 | 357 | ||
| 406 | - .action-buttons { | ||
| 407 | - display: flex; | ||
| 408 | - justify-content: flex-start; | ||
| 409 | - gap: 10rpx; | ||
| 410 | - | ||
| 411 | - // .action-btn { | ||
| 412 | - // padding: 20rpx 20rpx; | ||
| 413 | - // margin: 0; | ||
| 414 | - // line-height: 1; | ||
| 415 | - // border-radius: 20rpx; | ||
| 416 | - // border: 1px solid #666; | ||
| 417 | - // font-size: 20rpx; | ||
| 418 | - // } | ||
| 419 | - | ||
| 420 | - :deep(uni-button), | ||
| 421 | - :deep(wx-button) { | ||
| 422 | - padding: 10rpx 20rpx; | ||
| 423 | - margin: 0; | ||
| 424 | - line-height: 1; | ||
| 425 | - border-radius: 20rpx; | ||
| 426 | - border: 1px solid #666; | ||
| 427 | - font-size: 20rpx; | 358 | + .action-buttons { |
| 359 | + display: flex; | ||
| 360 | + align-items: center; | ||
| 428 | } | 361 | } |
| 429 | } | 362 | } |
| 430 | 363 | ||
| 431 | - .calendar-grid { | ||
| 432 | - padding-bottom: 120rpx; | ||
| 433 | - // #ifdef MP-WEIXIN | ||
| 434 | - padding-bottom: 140rpx; | ||
| 435 | - // #endif | 364 | + .calendar-card { |
| 365 | + background-color: #ffffff; | ||
| 366 | + border-radius: 24rpx; | ||
| 367 | + box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.03); | ||
| 436 | 368 | ||
| 437 | .week-header { | 369 | .week-header { |
| 438 | display: flex; | 370 | display: flex; |
| 439 | justify-content: space-between; | 371 | justify-content: space-between; |
| 440 | - margin-bottom: 10rpx; | 372 | + margin-bottom: 20rpx; |
| 441 | 373 | ||
| 442 | .week-day { | 374 | .week-day { |
| 443 | flex: 1; | 375 | flex: 1; |
| 444 | text-align: center; | 376 | text-align: center; |
| 445 | - font-size: 28rpx; | ||
| 446 | - color: #666; | 377 | + font-size: 26rpx; |
| 378 | + font-weight: 550; | ||
| 379 | + | ||
| 447 | } | 380 | } |
| 448 | } | 381 | } |
| 449 | 382 | ||
| @@ -453,179 +386,182 @@ const selectDate = async (date) => { | @@ -453,179 +386,182 @@ const selectDate = async (date) => { | ||
| 453 | gap: 10rpx; | 386 | gap: 10rpx; |
| 454 | } | 387 | } |
| 455 | 388 | ||
| 389 | + /* 日期格子基础样式:保证每个格子都有独立背景与微细边框 */ | ||
| 456 | .date-cell { | 390 | .date-cell { |
| 457 | display: flex; | 391 | display: flex; |
| 458 | flex-direction: column; | 392 | flex-direction: column; |
| 459 | align-items: center; | 393 | align-items: center; |
| 460 | justify-content: flex-start; | 394 | justify-content: flex-start; |
| 461 | - border: 1px solid #eee; | ||
| 462 | - font-size: 30rpx; | ||
| 463 | - color: #333; | ||
| 464 | - background-color: #f9fafc; | ||
| 465 | - padding-top: 10rpx; | 395 | + border-radius: 12rpx; |
| 466 | height: 200rpx; | 396 | height: 200rpx; |
| 467 | - overflow: hidden; | ||
| 468 | - box-sizing: border-box; | ||
| 469 | - } | ||
| 470 | - | ||
| 471 | - .plan-list { | ||
| 472 | - width: 100%; | ||
| 473 | - display: flex; | ||
| 474 | - flex-direction: column; | ||
| 475 | - gap: 6rpx; | ||
| 476 | - margin-top: 10rpx; | ||
| 477 | - padding: 0; | 397 | + padding: 8rpx 4rpx; |
| 478 | box-sizing: border-box; | 398 | box-sizing: border-box; |
| 479 | - } | 399 | + transition: all 0.2s ease; |
| 480 | 400 | ||
| 481 | - .weight, | ||
| 482 | - .plan-extra, | ||
| 483 | - .note-item, | ||
| 484 | - .plan-name { | ||
| 485 | - font-size: 18rpx; | ||
| 486 | - background: #ffdd44; | ||
| 487 | - padding: 4rpx 0; | ||
| 488 | - text-align: center; | ||
| 489 | - overflow: hidden; | ||
| 490 | - white-space: nowrap; | ||
| 491 | - text-overflow: ellipsis; | ||
| 492 | - width: 100%; | ||
| 493 | - box-sizing: border-box; | ||
| 494 | - } | ||
| 495 | - | ||
| 496 | - .weight, | ||
| 497 | - .weight-item { | ||
| 498 | - background: #b8ff66; | ||
| 499 | - } | ||
| 500 | - } | ||
| 501 | -} | 401 | + &:active { |
| 402 | + filter: brightness(0.96); | ||
| 403 | + } | ||
| 502 | 404 | ||
| 503 | -/* 日历说明弹窗样式 */ | ||
| 504 | -.explain-popup-overlay { | ||
| 505 | - position: fixed; | ||
| 506 | - top: 0; | ||
| 507 | - left: 0; | ||
| 508 | - right: 0; | ||
| 509 | - bottom: 0; | ||
| 510 | - background-color: rgba(0, 0, 0, 0.4); | ||
| 511 | - display: flex; | ||
| 512 | - justify-content: center; | ||
| 513 | - align-items: flex-end; | ||
| 514 | - z-index: 999; | ||
| 515 | - | ||
| 516 | - .explain-popup { | ||
| 517 | - width: 100%; | ||
| 518 | - background-color: #ffffff; | ||
| 519 | - border-radius: 24rpx 24rpx 0 0; | ||
| 520 | - padding: 40rpx; | ||
| 521 | - box-sizing: border-box; | 405 | + &.other-month { |
| 406 | + opacity: 0.5; | ||
| 407 | + border-color: #f1f5f9; | ||
| 408 | + } | ||
| 522 | 409 | ||
| 523 | - .explain-header { | ||
| 524 | - display: flex; | ||
| 525 | - justify-content: space-between; | ||
| 526 | - align-items: center; | ||
| 527 | - margin-bottom: 40rpx; | 410 | + &.is-today { |
| 411 | + border: 2rpx solid #3b82f6; | ||
| 412 | + } | ||
| 528 | 413 | ||
| 529 | - .explain-title { | ||
| 530 | - font-size: 32rpx; | ||
| 531 | - font-weight: 60; | ||
| 532 | - color: #333333; | 414 | + &.has-data { |
| 415 | + box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.02); | ||
| 533 | } | 416 | } |
| 534 | 417 | ||
| 535 | - .close-btn { | ||
| 536 | - width: 48rpx; | ||
| 537 | - height: 48rpx; | ||
| 538 | - border-radius: 50%; | ||
| 539 | - background-color: #f5f5f5; | 418 | + .date-header { |
| 419 | + height: 36rpx; | ||
| 540 | display: flex; | 420 | display: flex; |
| 541 | - justify-content: center; | ||
| 542 | align-items: center; | 421 | align-items: center; |
| 422 | + justify-content: center; | ||
| 423 | + margin-bottom: 6rpx; | ||
| 424 | + | ||
| 425 | + .date-number { | ||
| 426 | + font-size: 26rpx; | ||
| 427 | + font-weight: 600; | ||
| 428 | + color: #1f2937; | ||
| 429 | + | ||
| 430 | + | ||
| 431 | + } | ||
| 432 | + | ||
| 433 | + .today-badge { | ||
| 434 | + | ||
| 435 | + font-weight: 600; | ||
| 543 | 436 | ||
| 544 | - text { | ||
| 545 | - font-size: 36rpx; | ||
| 546 | - color: #666666; | ||
| 547 | - line-height: 1; | ||
| 548 | } | 437 | } |
| 549 | } | 438 | } |
| 550 | - } | ||
| 551 | 439 | ||
| 552 | - .explain-list { | ||
| 553 | - .explain-item { | 440 | + .plan-list { |
| 441 | + width: 100%; | ||
| 554 | display: flex; | 442 | display: flex; |
| 555 | - align-items: center; | ||
| 556 | - margin-bottom: 32rpx; | 443 | + flex-direction: column; |
| 444 | + gap: 6rpx; | ||
| 445 | + | ||
| 446 | + .plan-tag { | ||
| 447 | + width: 100%; | ||
| 448 | + border-radius: 6rpx; | ||
| 449 | + padding: 2rpx 4rpx; | ||
| 450 | + box-sizing: border-box; | ||
| 451 | + display: flex; | ||
| 452 | + align-items: center; | ||
| 453 | + justify-content: center; | ||
| 454 | + background-color: rgba(255, 255, 255, 0.85); | ||
| 455 | + /* 使 Tag 悬浮于彩色格子之上 */ | ||
| 456 | + | ||
| 457 | + .tag-text { | ||
| 458 | + font-size: 18rpx; | ||
| 459 | + line-height: 22rpx; | ||
| 460 | + color: #111827; | ||
| 461 | + overflow: hidden; | ||
| 462 | + white-space: nowrap; | ||
| 463 | + text-overflow: ellipsis; | ||
| 464 | + text-align: center; | ||
| 465 | + width: 100%; | ||
| 466 | + } | ||
| 557 | 467 | ||
| 558 | - .color-block { | ||
| 559 | - width: 40rpx; | ||
| 560 | - height: 40rpx; | ||
| 561 | - border-radius: 8rpx; | ||
| 562 | - margin-right: 20rpx; | 468 | + &.tag-type-weight { |
| 469 | + background-color: #bbf7d0 !important; | ||
| 563 | 470 | ||
| 564 | - &.blue { | ||
| 565 | - background-color: #a8d8ff; | 471 | + .tag-text { |
| 472 | + color: #14532d; | ||
| 473 | + font-weight: 700; | ||
| 474 | + } | ||
| 566 | } | 475 | } |
| 567 | 476 | ||
| 568 | - &.green { | ||
| 569 | - background-color: #b8ff66; | ||
| 570 | - } | 477 | + &.tag-type-note { |
| 478 | + background-color: #ffffff; | ||
| 571 | 479 | ||
| 572 | - &.yellow { | ||
| 573 | - background-color: #ffdd44; | 480 | + .tag-text { |
| 481 | + color: #374151; | ||
| 482 | + } | ||
| 574 | } | 483 | } |
| 484 | + } | ||
| 575 | 485 | ||
| 576 | - &.gray { | ||
| 577 | - background-color: #f0f0f0; | ||
| 578 | - border: 1rpx solid #eee; | ||
| 579 | - } | 486 | + .plan-extra { |
| 487 | + font-size: 16rpx; | ||
| 488 | + color: #4b5563; | ||
| 489 | + text-align: center; | ||
| 490 | + line-height: 20rpx; | ||
| 491 | + font-weight: 600; | ||
| 580 | } | 492 | } |
| 493 | + } | ||
| 494 | + } | ||
| 495 | + } | ||
| 496 | +} | ||
| 497 | + | ||
| 498 | +.explain-popup-content { | ||
| 499 | + padding: 36rpx 32rpx 48rpx 32rpx; | ||
| 500 | + background-color: #ffffff; | ||
| 501 | + | ||
| 502 | + .explain-header { | ||
| 503 | + display: flex; | ||
| 504 | + justify-content: space-between; | ||
| 505 | + align-items: center; | ||
| 506 | + margin-bottom: 32rpx; | ||
| 507 | + | ||
| 508 | + .explain-title { | ||
| 509 | + font-size: 32rpx; | ||
| 510 | + font-weight: 600; | ||
| 511 | + color: #111827; | ||
| 512 | + } | ||
| 513 | + } | ||
| 514 | + | ||
| 515 | + .explain-list { | ||
| 516 | + display: flex; | ||
| 517 | + flex-direction: column; | ||
| 518 | + gap: 28rpx; | ||
| 519 | + | ||
| 520 | + .explain-item { | ||
| 521 | + display: flex; | ||
| 522 | + align-items: center; | ||
| 523 | + | ||
| 524 | + .color-block { | ||
| 525 | + width: 36rpx; | ||
| 526 | + height: 36rpx; | ||
| 527 | + border-radius: 8rpx; | ||
| 528 | + margin-right: 24rpx; | ||
| 529 | + | ||
| 530 | + &.blue { | ||
| 531 | + background-color: #bae6fd; | ||
| 532 | + } | ||
| 533 | + | ||
| 534 | + &.green { | ||
| 535 | + background-color: #bbf7d0; | ||
| 536 | + } | ||
| 537 | + | ||
| 538 | + &.yellow { | ||
| 539 | + background-color: #fef08a; | ||
| 540 | + } | ||
| 541 | + | ||
| 542 | + &.gray { | ||
| 543 | + background-color: #e5e7eb; | ||
| 544 | + } | ||
| 545 | + } | ||
| 546 | + | ||
| 547 | + .item-info { | ||
| 548 | + display: flex; | ||
| 549 | + align-items: center; | ||
| 550 | + gap: 16rpx; | ||
| 581 | 551 | ||
| 582 | .item-label { | 552 | .item-label { |
| 583 | font-size: 28rpx; | 553 | font-size: 28rpx; |
| 584 | - color: #333333; | ||
| 585 | - width: 80rpx; | ||
| 586 | - margin-right: 20rpx; | 554 | + font-weight: 600; |
| 555 | + color: #1f2937; | ||
| 556 | + width: 72rpx; | ||
| 587 | } | 557 | } |
| 588 | 558 | ||
| 589 | .item-desc { | 559 | .item-desc { |
| 590 | - font-size: 28rpx; | ||
| 591 | - color: #666666; | ||
| 592 | - flex: 1; | 560 | + font-size: 26rpx; |
| 561 | + color: #6b7280; | ||
| 593 | } | 562 | } |
| 594 | } | 563 | } |
| 595 | } | 564 | } |
| 596 | } | 565 | } |
| 597 | } | 566 | } |
| 598 | - | ||
| 599 | -// .gridCellContent { | ||
| 600 | -// position: fixed; | ||
| 601 | -// top: 0; | ||
| 602 | -// left: 0; | ||
| 603 | -// right: 0; | ||
| 604 | -// bottom: 0; | ||
| 605 | -// background: rgba(0, 0, 0, 0.5); | ||
| 606 | -// z-index: 9999; | ||
| 607 | -// display: flex; | ||
| 608 | -// align-items: flex-end; | ||
| 609 | -// padding: 0 !important; | ||
| 610 | -// margin: 0 !important; | ||
| 611 | -// width: 100vw !important; | ||
| 612 | -// box-sizing: border-box; | ||
| 613 | -// } | ||
| 614 | -</style> | ||
| 615 | - | ||
| 616 | -<!-- #ifdef MP-WEIXIN --> | ||
| 617 | -<style lang="scss"> | ||
| 618 | -.month-picker { | ||
| 619 | - | ||
| 620 | - .uni-date-editor--x.uni-date-x--border, | ||
| 621 | - .uni-date-x--border { | ||
| 622 | - border: none !important; | ||
| 623 | - } | ||
| 624 | - | ||
| 625 | - .uni-date-x, | ||
| 626 | - .uni-date-editor--x { | ||
| 627 | - border: none !important; | ||
| 628 | - } | ||
| 629 | -} | ||
| 630 | -</style> | ||
| 631 | -<!-- #endif --> | ||
| 567 | +</style> |
-
Please register or login to post a comment