Showing
37 changed files
with
4837 additions
and
0 deletions
Too many changes to show.
To preserve performance only 37 of 37+ files are displayed.
.env
0 → 100644
| 1 | +# 版本号 | ||
| 2 | +SHOPRO_VERSION=v2.4.1 | ||
| 3 | + | ||
| 4 | +# 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development) | ||
| 5 | +# SHOPRO_BASE_URL=http://api-dashboard.yudao.iocoder.cn | ||
| 6 | +SHOPRO_BASE_URL=http://mall.hcxtec.com | ||
| 7 | + | ||
| 8 | +# 后端接口 - 测试环境(通过 process.env.NODE_ENV = development) | ||
| 9 | +SHOPRO_DEV_BASE_URL=http://192.168.1.200:48081 | ||
| 10 | +# SHOPRO_DEV_BASE_URL=http://192.168.1.85:48080 | ||
| 11 | +SHOPRO_DEV_BASE_URL=https://fitness.hcxtec.com | ||
| 12 | +# SHOPRO_DEV_BASE_URL=http://api-dashboard.yudao.iocoder.cn/ | ||
| 13 | +### SHOPRO_DEV_BASE_URL=http://10.171.1.188:48080 | ||
| 14 | +### SHOPRO_DEV_BASE_URL = http://yunai.natapp1.cc | ||
| 15 | + | ||
| 16 | +# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务 | ||
| 17 | +SHOPRO_UPLOAD_TYPE=server | ||
| 18 | + | ||
| 19 | +# 后端接口前缀(一般不建议调整) | ||
| 20 | +SHOPRO_API_PATH=/app-api | ||
| 21 | + | ||
| 22 | +# SHOPRO_API_PATH=/api | ||
| 23 | + | ||
| 24 | +# 后端 websocket 接口前缀 | ||
| 25 | +SHOPRO_WEBSOCKET_PATH=/infra/ws | ||
| 26 | + | ||
| 27 | +# 开发环境运行端口 | ||
| 28 | +SHOPRO_DEV_PORT=3000 | ||
| 29 | + | ||
| 30 | +# 客户端静态资源地址 空=默认使用服务端指定的CDN资源地址前缀 | local=本地 | http(s)://xxx.xxx=自定义静态资源地址前缀 | ||
| 31 | +SHOPRO_STATIC_URL=http://test.yudao.iocoder.cn | ||
| 32 | +### SHOPRO_STATIC_URL = https://file.sheepjs.com | ||
| 33 | + | ||
| 34 | +# 前端 H5 访问域名 | ||
| 35 | +SHOPRO_H5_URL=http://127.0.0.1:3000 | ||
| 36 | + | ||
| 37 | +# 是否开启直播 1 开启直播 | 0 关闭直播 | ||
| 38 | +SHOPRO_MPLIVE_ON=0 | ||
| 39 | + | ||
| 40 | +# 租户ID 默认 1 | ||
| 41 | +SHOPRO_TENANT_ID=1 |
.gitignore
0 → 100644
.image/common/mall-feature.png
0 → 100644
38.3 KB
.image/common/mall-preview.png
0 → 100644
204 KB
.image/common/project-vs.png
0 → 100644
139 KB
.image/common/ruoyi-vue-pro-architecture.png
0 → 100644
68.6 KB
.image/common/ruoyi-vue-pro-biz.png
0 → 100644
32.2 KB
.image/common/yudao-cloud-architecture.png
0 → 100644
201 KB
.image/common/yudao-roadmap.png
0 → 100644
59.9 KB
.image/mall/会员详情.png
0 → 100644
165 KB
.image/mall/商品详情.png
0 → 100644
168 KB
.image/mall/店铺装修.png
0 → 100644
152 KB
.image/mall/营销中心.png
0 → 100644
294 KB
.image/mall/订单详情.png
0 → 100644
177 KB
.prettierignore
0 → 100644
.prettierrc
0 → 100644
App.vue
0 → 100644
| 1 | +<script setup> | ||
| 2 | + import { onLaunch, onShow, onError, onLoad } from '@dcloudio/uni-app'; | ||
| 3 | + import { ShoproInit } from './sheep'; | ||
| 4 | + | ||
| 5 | + onLaunch(() => { | ||
| 6 | + // 隐藏原生导航栏 使用自定义底部导航 | ||
| 7 | + // uni.hideTabBar({ | ||
| 8 | + // fail: () => {}, | ||
| 9 | + // }); | ||
| 10 | + // 加载Shopro底层依赖 | ||
| 11 | + ShoproInit(); | ||
| 12 | + }); | ||
| 13 | + | ||
| 14 | + onShow(async (options) => { | ||
| 15 | + // #ifdef APP-PLUS | ||
| 16 | + // 获取urlSchemes参数 | ||
| 17 | + const args = plus.runtime.arguments; | ||
| 18 | + if (args) { | ||
| 19 | + } | ||
| 20 | + // 获取剪贴板 | ||
| 21 | + uni.getClipboardData({ | ||
| 22 | + success: (res) => { | ||
| 23 | + | ||
| 24 | + }, | ||
| 25 | + }); | ||
| 26 | + // #endif | ||
| 27 | + }); | ||
| 28 | +</script> | ||
| 29 | + | ||
| 30 | +<style lang="scss"> | ||
| 31 | + @import 'uview-plus/index.scss'; | ||
| 32 | + @import '@/sheep/scss/index.scss'; | ||
| 33 | +</style> |
LICENSE
0 → 100644
| 1 | +MIT License | ||
| 2 | + | ||
| 3 | +Copyright (c) 2022 lidongtony | ||
| 4 | + | ||
| 5 | +Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| 6 | +of this software and associated documentation files (the "Software"), to deal | ||
| 7 | +in the Software without restriction, including without limitation the rights | ||
| 8 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| 9 | +copies of the Software, and to permit persons to whom the Software is | ||
| 10 | +furnished to do so, subject to the following conditions: | ||
| 11 | + | ||
| 12 | +The above copyright notice and this permission notice shall be included in all | ||
| 13 | +copies or substantial portions of the Software. | ||
| 14 | + | ||
| 15 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 16 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 17 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 18 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 19 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 20 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 21 | +SOFTWARE. |
README.md
0 → 100644
| 1 | +**严肃声明:现在、未来都不会有商业版本,所有代码全部开源!!** | ||
| 2 | + | ||
| 3 | +**「我喜欢写代码,乐此不疲」** | ||
| 4 | +**「我喜欢做开源,以此为乐」** | ||
| 5 | + | ||
| 6 | +我 🐶 在上海艰苦奋斗,早中晚在 top3 大厂认真搬砖,夜里为开源做贡献。 | ||
| 7 | + | ||
| 8 | +如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。 | ||
| 9 | + | ||
| 10 | +## 🐶 新手必读 | ||
| 11 | + | ||
| 12 | +* 演示地址:<https://doc.iocoder.cn/mall-preview/> | ||
| 13 | +* 启动文档:<https://doc.iocoder.cn/quick-start/> | ||
| 14 | +* 视频教程:<https://doc.iocoder.cn/video/> | ||
| 15 | + | ||
| 16 | +## 🐯 商城简介 | ||
| 17 | + | ||
| 18 | +**芋道商城**,基于 [芋道开发平台](https://github.com/YunaiV/ruoyi-vue-pro) 构建,以开发者为中心,打造中国第一流的 Java 开源商城系统,全部开源,个人与企业可 100% 免费使用。 | ||
| 19 | + | ||
| 20 | +> 有任何问题,或者想要的功能,可以在 Issues 中提给艿艿。 | ||
| 21 | +> | ||
| 22 | +> 😜 给项目点点 Star 吧,这对我们真的很重要! | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | +* 基于 uni-app + Vue3 开发,支持微信小程序、微信公众号、H5 移动端,未来会支持支付宝小程序、抖音小程序等 | ||
| 27 | +* 支持 SaaS 多租户,可满足商品、订单、支付、会员、优惠券、秒杀、拼团、砍价、分销、积分等多种经营需求 | ||
| 28 | + | ||
| 29 | +## 🔥 后端架构 | ||
| 30 | + | ||
| 31 | +支持 Spring Boot、Spring Cloud 两种架构: | ||
| 32 | + | ||
| 33 | +① Spring Boot 单体架构:<https://doc.iocoder.cn> | ||
| 34 | + | ||
| 35 | + | ||
| 36 | + | ||
| 37 | +② Spring Cloud 微服务架构:<https://cloud.iocoder.cn> | ||
| 38 | + | ||
| 39 | + | ||
| 40 | + | ||
| 41 | +## 🐱 移动端预览 | ||
| 42 | + | ||
| 43 | + | ||
| 44 | + | ||
| 45 | +## 🐶 管理端预览 | ||
| 46 | + | ||
| 47 | + | ||
| 48 | + | ||
| 49 | + | ||
| 50 | + | ||
| 51 | + | ||
| 52 | + | ||
| 53 | + | ||
| 54 | + | ||
| 55 | + | ||
| 56 | + |
androidPrivacy.json
0 → 100644
components/advertPopup.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="ad-modal"> | ||
| 3 | + <u-popup | ||
| 4 | + :show="show" | ||
| 5 | + mode="center" | ||
| 6 | + @close="close" | ||
| 7 | + bgColor="transparent" | ||
| 8 | + :safeAreaInsetBottom="false" | ||
| 9 | + > | ||
| 10 | + <view class="ad-container"> | ||
| 11 | + <view class="swiper-wrapper"> | ||
| 12 | + <u-swiper | ||
| 13 | + :list="list" | ||
| 14 | + keyName="image" | ||
| 15 | + height="700rpx" | ||
| 16 | + :autoplay="false" | ||
| 17 | + circular | ||
| 18 | + @click="handleAdClick" | ||
| 19 | + radius="16rpx" | ||
| 20 | + indicator | ||
| 21 | + bgColor="transparent" | ||
| 22 | + indicatorMode="dot" | ||
| 23 | + ></u-swiper> | ||
| 24 | + </view> | ||
| 25 | + | ||
| 26 | + <view class="close-section" @click="close"> | ||
| 27 | + <u-icon name="close-circle" color="#ffffff" size="34"></u-icon> | ||
| 28 | + </view> | ||
| 29 | + </view> | ||
| 30 | + </u-popup> | ||
| 31 | + </view> | ||
| 32 | +</template> | ||
| 33 | + | ||
| 34 | +<script setup> | ||
| 35 | + const props = defineProps({ | ||
| 36 | + show: { | ||
| 37 | + type: Boolean, | ||
| 38 | + default: false, | ||
| 39 | + }, | ||
| 40 | + list: { | ||
| 41 | + type: Array, | ||
| 42 | + default: () => [], | ||
| 43 | + }, | ||
| 44 | + }); | ||
| 45 | + | ||
| 46 | + const emit = defineEmits(['updateShow', 'clickAd', 'close']); | ||
| 47 | + | ||
| 48 | + // 关闭弹窗 | ||
| 49 | + const close = () => { | ||
| 50 | + emit('close', false); | ||
| 51 | + }; | ||
| 52 | + | ||
| 53 | + // 点击广告图触发 | ||
| 54 | + const handleAdClick = (index) => { | ||
| 55 | + emit('clickAd', props.list[index]); | ||
| 56 | + }; | ||
| 57 | +</script> | ||
| 58 | + | ||
| 59 | +<style lang="scss" scoped> | ||
| 60 | + .ad-container { | ||
| 61 | + width: 580rpx; // 弹窗宽度 | ||
| 62 | + display: flex; | ||
| 63 | + flex-direction: column; | ||
| 64 | + align-items: center; | ||
| 65 | + | ||
| 66 | + .swiper-wrapper { | ||
| 67 | + width: 100%; | ||
| 68 | + overflow: hidden; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + .close-section { | ||
| 72 | + margin-top: 40rpx; | ||
| 73 | + display: flex; | ||
| 74 | + justify-content: center; | ||
| 75 | + align-items: center; | ||
| 76 | + cursor: pointer; | ||
| 77 | + | ||
| 78 | + /* 增加点击区域面积 */ | ||
| 79 | + padding: 20rpx; | ||
| 80 | + } | ||
| 81 | + } | ||
| 82 | +</style> |
components/tabbar.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="tabbar"> | ||
| 3 | + <up-tabbar | ||
| 4 | + :value="activePath" | ||
| 5 | + @change="handleTabChange" | ||
| 6 | + :placeholder="true" | ||
| 7 | + activeColor="#000" | ||
| 8 | + :fixed="true" | ||
| 9 | + > | ||
| 10 | + <up-tabbar-item | ||
| 11 | + v-for="(item, index) in tabList" | ||
| 12 | + :key="index" | ||
| 13 | + :text="item.text" | ||
| 14 | + :name="item.path" | ||
| 15 | + > | ||
| 16 | + <template #active-icon> | ||
| 17 | + <image :class="['icon', item.special ? 'mid-icon' : '']" :src="item.selectedIcon"></image> | ||
| 18 | + </template> | ||
| 19 | + <template #inactive-icon> | ||
| 20 | + <image :class="['icon', item.special ? 'mid-icon' : '']" :src="item.icon"></image> | ||
| 21 | + </template> | ||
| 22 | + </up-tabbar-item> | ||
| 23 | + </up-tabbar> | ||
| 24 | + </view> | ||
| 25 | +</template> | ||
| 26 | + | ||
| 27 | +<script setup> | ||
| 28 | + import { ref } from 'vue'; | ||
| 29 | + import { onShow } from '@dcloudio/uni-app'; | ||
| 30 | + | ||
| 31 | + const activePath = ref('pages/xunji/xunji'); | ||
| 32 | + | ||
| 33 | + // 配置化 Tabbar | ||
| 34 | + const tabList = [ | ||
| 35 | + { | ||
| 36 | + text: '训记', | ||
| 37 | + path: 'pages/xunji/xunji', | ||
| 38 | + icon: '/static/tabbar/xunji.png', | ||
| 39 | + selectedIcon: '/static/tabbar/xunji-sel.png', | ||
| 40 | + special: false, | ||
| 41 | + }, | ||
| 42 | + { | ||
| 43 | + text: '我的', | ||
| 44 | + path: 'pages/user/user', | ||
| 45 | + icon: '/static/tabbar/wode.png', | ||
| 46 | + selectedIcon: '/static/tabbar/wode-sel.png', | ||
| 47 | + special: false, | ||
| 48 | + }, | ||
| 49 | + ]; | ||
| 50 | + | ||
| 51 | + const handleTabChange = (name) => { | ||
| 52 | + activePath.value = name; | ||
| 53 | + uni.switchTab({ url: '/' + name }); | ||
| 54 | + }; | ||
| 55 | + | ||
| 56 | + onShow(() => { | ||
| 57 | + const pages = getCurrentPages(); | ||
| 58 | + const currPage = pages[pages.length - 1]; | ||
| 59 | + if (currPage && !currPage.route.includes('entry')) { | ||
| 60 | + activePath.value = currPage.route; | ||
| 61 | + } | ||
| 62 | + }); | ||
| 63 | +</script> | ||
| 64 | + | ||
| 65 | +<style lang="scss" scoped> | ||
| 66 | + .icon { | ||
| 67 | + width: 48rpx; | ||
| 68 | + height: 48rpx; | ||
| 69 | + transition: all 0.2s; | ||
| 70 | + } | ||
| 71 | +</style> |
index.html
0 → 100644
| 1 | +<!DOCTYPE html> | ||
| 2 | +<html lang="en"> | ||
| 3 | + <head> | ||
| 4 | + <meta charset="UTF-8" /> | ||
| 5 | + <meta | ||
| 6 | + name="viewport" | ||
| 7 | + content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" | ||
| 8 | + /> | ||
| 9 | + <title></title> | ||
| 10 | + <!--preload-links--> | ||
| 11 | + <!--app-context--> | ||
| 12 | + </head> | ||
| 13 | + <body> | ||
| 14 | + <div id="app"><!--app-html--></div> | ||
| 15 | + <script type="module" src="/main.js"></script> | ||
| 16 | + </body> | ||
| 17 | +</html> |
jsconfig.json
0 → 100644
main.js
0 → 100644
| 1 | +import App from './App'; | ||
| 2 | +import { createSSRApp } from 'vue'; | ||
| 3 | +import { setupPinia } from './sheep/store'; | ||
| 4 | +import uviewPlus from 'uview-plus'; | ||
| 5 | +import Tabbar from '@/components/tabbar.vue'; | ||
| 6 | +export function createApp() { | ||
| 7 | + const app = createSSRApp(App); | ||
| 8 | + | ||
| 9 | + setupPinia(app); | ||
| 10 | + app.use(uviewPlus, () => { | ||
| 11 | + return { | ||
| 12 | + options: { | ||
| 13 | + // 修改config对象的属性 | ||
| 14 | + config: { | ||
| 15 | + // 只加载一次字体图标 | ||
| 16 | + loadFontOnce: true, | ||
| 17 | + unit:'rpx' | ||
| 18 | + }, | ||
| 19 | + }, | ||
| 20 | + }; | ||
| 21 | + }); | ||
| 22 | + app.component('Tabbar', Tabbar); | ||
| 23 | + | ||
| 24 | + return { | ||
| 25 | + app, | ||
| 26 | + }; | ||
| 27 | +} |
manifest.json
0 → 100644
| 1 | +{ | ||
| 2 | + "name": "鸿星健身", | ||
| 3 | + "appid": "__UNI__0A1E345", | ||
| 4 | + "description": "基于 uni-app + Vue3 技术驱动的在线商城系统,内含诸多功能与丰富的活动,期待您的使用和反馈。", | ||
| 5 | + "versionName": "2025.10", | ||
| 6 | + "versionCode": "183", | ||
| 7 | + "transformPx": false, | ||
| 8 | + "app-plus": { | ||
| 9 | + "usingComponents": true, | ||
| 10 | + "nvueCompiler": "uni-app", | ||
| 11 | + "nvueStyleCompiler": "uni-app", | ||
| 12 | + "compilerVersion": 3, | ||
| 13 | + "nvueLaunchMode": "fast", | ||
| 14 | + "splashscreen": { | ||
| 15 | + "alwaysShowBeforeRender": true, | ||
| 16 | + "waiting": true, | ||
| 17 | + "autoclose": true, | ||
| 18 | + "delay": 0 | ||
| 19 | + }, | ||
| 20 | + "safearea": { | ||
| 21 | + "bottom": { | ||
| 22 | + "offset": "none" | ||
| 23 | + } | ||
| 24 | + }, | ||
| 25 | + "modules": { | ||
| 26 | + "Payment": {}, | ||
| 27 | + "Share": {}, | ||
| 28 | + "VideoPlayer": {}, | ||
| 29 | + "OAuth": {} | ||
| 30 | + }, | ||
| 31 | + "distribute": { | ||
| 32 | + "android": { | ||
| 33 | + "permissions": [ | ||
| 34 | + "<uses-feature android:name=\"android.hardware.camera\"/>", | ||
| 35 | + "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", | ||
| 36 | + "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>", | ||
| 37 | + "<uses-permission android:name=\"android.permission.VIBRATE\"/>", | ||
| 38 | + "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>", | ||
| 39 | + "<uses-permission android:name=\"android.permission.ACCESS_MOCK_LOCATION\"/>", | ||
| 40 | + "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", | ||
| 41 | + "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", | ||
| 42 | + "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>", | ||
| 43 | + "<uses-permission android:name=\"android.permission.CAMERA\"/>", | ||
| 44 | + "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", | ||
| 45 | + "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", | ||
| 46 | + "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", | ||
| 47 | + "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", | ||
| 48 | + "<uses-permission android:name=\"android.permission.GET_TASKS\"/>", | ||
| 49 | + "<uses-permission android:name=\"android.permission.INTERNET\"/>", | ||
| 50 | + "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>", | ||
| 51 | + "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", | ||
| 52 | + "<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>", | ||
| 53 | + "<uses-permission android:name=\"android.permission.READ_LOGS\"/>", | ||
| 54 | + "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", | ||
| 55 | + "<uses-permission android:name=\"android.permission.READ_SMS\"/>", | ||
| 56 | + "<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>", | ||
| 57 | + "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>", | ||
| 58 | + "<uses-permission android:name=\"android.permission.SEND_SMS\"/>", | ||
| 59 | + "<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>", | ||
| 60 | + "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", | ||
| 61 | + "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>", | ||
| 62 | + "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>", | ||
| 63 | + "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>", | ||
| 64 | + "<uses-permission android:name=\"android.permission.WRITE_SMS\"/>", | ||
| 65 | + "<uses-permission android:name=\"android.permission.RECEIVE_USER_PRESENT\"/>" | ||
| 66 | + ], | ||
| 67 | + "minSdkVersion": 21, | ||
| 68 | + "schemes": "shopro" | ||
| 69 | + }, | ||
| 70 | + "ios": { | ||
| 71 | + "urlschemewhitelist": [ | ||
| 72 | + "baidumap", | ||
| 73 | + "iosamap" | ||
| 74 | + ], | ||
| 75 | + "dSYMs": false, | ||
| 76 | + "privacyDescription": { | ||
| 77 | + "NSPhotoLibraryUsageDescription": "需要同意访问您的相册选取图片才能完善该条目", | ||
| 78 | + "NSPhotoLibraryAddUsageDescription": "需要同意访问您的相册才能保存该图片", | ||
| 79 | + "NSCameraUsageDescription": "需要同意访问您的摄像头拍摄照片才能完善该条目", | ||
| 80 | + "NSUserTrackingUsageDescription": "开启追踪并不会获取您在其它站点的隐私信息,该行为仅用于标识设备,保障服务安全和提升浏览体验" | ||
| 81 | + }, | ||
| 82 | + "urltypes": "shopro", | ||
| 83 | + "capabilities": { | ||
| 84 | + "entitlements": { | ||
| 85 | + "com.apple.developer.associated-domains": [ | ||
| 86 | + "applinks:shopro.sheepjs.com" | ||
| 87 | + ] | ||
| 88 | + } | ||
| 89 | + }, | ||
| 90 | + "idfa": true | ||
| 91 | + }, | ||
| 92 | + "sdkConfigs": { | ||
| 93 | + "speech": {}, | ||
| 94 | + "ad": {}, | ||
| 95 | + "oauth": { | ||
| 96 | + "apple": {}, | ||
| 97 | + "weixin": { | ||
| 98 | + "appid": "wxae7a0c156da9383b", | ||
| 99 | + "UniversalLinks": "https://shopro.sheepjs.com/uni-universallinks/__UNI__082C0BA/", | ||
| 100 | + "mergeVirtualHostAttributes": true | ||
| 101 | + } | ||
| 102 | + }, | ||
| 103 | + "payment": { | ||
| 104 | + "weixin": { | ||
| 105 | + "__platform__": [ | ||
| 106 | + "ios", | ||
| 107 | + "android" | ||
| 108 | + ], | ||
| 109 | + "appid": "wxae7a0c156da9383b", | ||
| 110 | + "UniversalLinks": "https://shopro.sheepjs.com/uni-universallinks/__UNI__082C0BA/" | ||
| 111 | + }, | ||
| 112 | + "alipay": { | ||
| 113 | + "__platform__": [ | ||
| 114 | + "ios", | ||
| 115 | + "android" | ||
| 116 | + ] | ||
| 117 | + } | ||
| 118 | + }, | ||
| 119 | + "share": { | ||
| 120 | + "weixin": { | ||
| 121 | + "appid": "wxae7a0c156da9383b", | ||
| 122 | + "UniversalLinks": "https://shopro.sheepjs.com/uni-universallinks/__UNI__082C0BA/" | ||
| 123 | + } | ||
| 124 | + } | ||
| 125 | + }, | ||
| 126 | + "orientation": [ | ||
| 127 | + "portrait-primary" | ||
| 128 | + ], | ||
| 129 | + "splashscreen": { | ||
| 130 | + "androidStyle": "common", | ||
| 131 | + "iosStyle": "common", | ||
| 132 | + "useOriginalMsgbox": true | ||
| 133 | + }, | ||
| 134 | + "icons": { | ||
| 135 | + "android": { | ||
| 136 | + "hdpi": "unpackage/res/icons/72x72.png", | ||
| 137 | + "xhdpi": "unpackage/res/icons/96x96.png", | ||
| 138 | + "xxhdpi": "unpackage/res/icons/144x144.png", | ||
| 139 | + "xxxhdpi": "unpackage/res/icons/192x192.png" | ||
| 140 | + }, | ||
| 141 | + "ios": { | ||
| 142 | + "appstore": "unpackage/res/icons/1024x1024.png", | ||
| 143 | + "ipad": { | ||
| 144 | + "app": "unpackage/res/icons/76x76.png", | ||
| 145 | + "app@2x": "unpackage/res/icons/152x152.png", | ||
| 146 | + "notification": "unpackage/res/icons/20x20.png", | ||
| 147 | + "notification@2x": "unpackage/res/icons/40x40.png", | ||
| 148 | + "proapp@2x": "unpackage/res/icons/167x167.png", | ||
| 149 | + "settings": "unpackage/res/icons/29x29.png", | ||
| 150 | + "settings@2x": "unpackage/res/icons/58x58.png", | ||
| 151 | + "spotlight": "unpackage/res/icons/40x40.png", | ||
| 152 | + "spotlight@2x": "unpackage/res/icons/80x80.png" | ||
| 153 | + }, | ||
| 154 | + "iphone": { | ||
| 155 | + "app@2x": "unpackage/res/icons/120x120.png", | ||
| 156 | + "app@3x": "unpackage/res/icons/180x180.png", | ||
| 157 | + "notification@2x": "unpackage/res/icons/40x40.png", | ||
| 158 | + "notification@3x": "unpackage/res/icons/60x60.png", | ||
| 159 | + "settings@2x": "unpackage/res/icons/58x58.png", | ||
| 160 | + "settings@3x": "unpackage/res/icons/87x87.png", | ||
| 161 | + "spotlight@2x": "unpackage/res/icons/80x80.png", | ||
| 162 | + "spotlight@3x": "unpackage/res/icons/120x120.png" | ||
| 163 | + } | ||
| 164 | + } | ||
| 165 | + } | ||
| 166 | + } | ||
| 167 | + }, | ||
| 168 | + "quickapp": {}, | ||
| 169 | + "quickapp-native": { | ||
| 170 | + "icon": "/static/logo.png", | ||
| 171 | + "package": "com.example.demo", | ||
| 172 | + "features": [ | ||
| 173 | + { | ||
| 174 | + "name": "system.clipboard" | ||
| 175 | + } | ||
| 176 | + ] | ||
| 177 | + }, | ||
| 178 | + "quickapp-webview": { | ||
| 179 | + "icon": "/static/logo.png", | ||
| 180 | + "package": "com.example.demo", | ||
| 181 | + "minPlatformVersion": 1070, | ||
| 182 | + "versionName": "1.0.0", | ||
| 183 | + "versionCode": 100 | ||
| 184 | + }, | ||
| 185 | + "mp-weixin": { | ||
| 186 | + "appid": "wxb827c923ce0aad4b", | ||
| 187 | + "setting": { | ||
| 188 | + "urlCheck": true, | ||
| 189 | + "minified": true, | ||
| 190 | + "postcss": true | ||
| 191 | + }, | ||
| 192 | + "optimization": { | ||
| 193 | + "subPackages": true | ||
| 194 | + }, | ||
| 195 | + "plugins": {}, | ||
| 196 | + "lazyCodeLoading": "requiredComponents", | ||
| 197 | + "usingComponents": {}, | ||
| 198 | + "permission": { | ||
| 199 | + "scope.userLocation": { | ||
| 200 | + "desc": "你的位置信息将用于展示附近的服务" | ||
| 201 | + } | ||
| 202 | + }, | ||
| 203 | + "requiredPrivateInfos": [ | ||
| 204 | + "chooseAddress", | ||
| 205 | + "getLocation" | ||
| 206 | + ], | ||
| 207 | + "mergeVirtualHostAttributes": true | ||
| 208 | + }, | ||
| 209 | + "mp-alipay": { | ||
| 210 | + "usingComponents": true | ||
| 211 | + }, | ||
| 212 | + "mp-baidu": { | ||
| 213 | + "usingComponents": true | ||
| 214 | + }, | ||
| 215 | + "mp-toutiao": { | ||
| 216 | + "usingComponents": true | ||
| 217 | + }, | ||
| 218 | + "mp-jd": { | ||
| 219 | + "usingComponents": true | ||
| 220 | + }, | ||
| 221 | + "h5": { | ||
| 222 | + "template": "index.html", | ||
| 223 | + "router": { | ||
| 224 | + "mode": "history", | ||
| 225 | + "base": "/" | ||
| 226 | + }, | ||
| 227 | + "sdkConfigs": { | ||
| 228 | + "maps": {} | ||
| 229 | + }, | ||
| 230 | + "async": { | ||
| 231 | + "timeout": 20000 | ||
| 232 | + }, | ||
| 233 | + "title": "芋道商城", | ||
| 234 | + "optimization": { | ||
| 235 | + "treeShaking": { | ||
| 236 | + "enable": true | ||
| 237 | + } | ||
| 238 | + } | ||
| 239 | + }, | ||
| 240 | + "vueVersion": "3", | ||
| 241 | + "_spaceID": "192b4892-5452-4e1d-9f09-eee1ece40639", | ||
| 242 | + "locale": "zh-Hans", | ||
| 243 | + "fallbackLocale": "zh-Hans" | ||
| 244 | +} |
package.json
0 → 100644
| 1 | +{ | ||
| 2 | + "id": "shopro", | ||
| 3 | + "name": "shopro", | ||
| 4 | + "displayName": "芋道商城", | ||
| 5 | + "version": "2025.10.0", | ||
| 6 | + "description": "芋道商城,一套代码,同时发行到iOS、Android、H5、微信小程序多个平台,请使用手机扫码快速体验强大功能", | ||
| 7 | + "scripts": { | ||
| 8 | + "prettier": "prettier --write \"{pages,sheep}/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"" | ||
| 9 | + }, | ||
| 10 | + "repository": "https://github.com/sheepjs/shop.git", | ||
| 11 | + "keywords": [ | ||
| 12 | + "商城", | ||
| 13 | + "B2C", | ||
| 14 | + "商城模板" | ||
| 15 | + ], | ||
| 16 | + "author": "", | ||
| 17 | + "license": "MIT", | ||
| 18 | + "bugs": { | ||
| 19 | + "url": "https://github.com/sheepjs/shop/issues" | ||
| 20 | + }, | ||
| 21 | + "homepage": "https://github.com/dcloudio/hello-uniapp#readme", | ||
| 22 | + "dcloudext": { | ||
| 23 | + "category": [ | ||
| 24 | + "前端页面模板", | ||
| 25 | + "uni-app前端项目模板" | ||
| 26 | + ], | ||
| 27 | + "sale": { | ||
| 28 | + "regular": { | ||
| 29 | + "price": "0.00" | ||
| 30 | + }, | ||
| 31 | + "sourcecode": { | ||
| 32 | + "price": "0.00" | ||
| 33 | + } | ||
| 34 | + }, | ||
| 35 | + "contact": { | ||
| 36 | + "qq": "" | ||
| 37 | + }, | ||
| 38 | + "declaration": { | ||
| 39 | + "ads": "无", | ||
| 40 | + "data": "无", | ||
| 41 | + "permissions": "无" | ||
| 42 | + }, | ||
| 43 | + "npmurl": "" | ||
| 44 | + }, | ||
| 45 | + "uni_modules": { | ||
| 46 | + "dependencies": [], | ||
| 47 | + "encrypt": [], | ||
| 48 | + "platforms": { | ||
| 49 | + "cloud": { | ||
| 50 | + "tcb": "u", | ||
| 51 | + "aliyun": "u" | ||
| 52 | + }, | ||
| 53 | + "client": { | ||
| 54 | + "App": { | ||
| 55 | + "app-vue": "y", | ||
| 56 | + "app-nvue": "u" | ||
| 57 | + }, | ||
| 58 | + "H5-mobile": { | ||
| 59 | + "Safari": "y", | ||
| 60 | + "Android Browser": "y", | ||
| 61 | + "微信浏览器(Android)": "y", | ||
| 62 | + "QQ浏览器(Android)": "y" | ||
| 63 | + }, | ||
| 64 | + "H5-pc": { | ||
| 65 | + "Chrome": "y", | ||
| 66 | + "IE": "y", | ||
| 67 | + "Edge": "y", | ||
| 68 | + "Firefox": "y", | ||
| 69 | + "Safari": "y" | ||
| 70 | + }, | ||
| 71 | + "小程序": { | ||
| 72 | + "微信": "y", | ||
| 73 | + "阿里": "u", | ||
| 74 | + "百度": "u", | ||
| 75 | + "字节跳动": "u", | ||
| 76 | + "QQ": "u", | ||
| 77 | + "京东": "u" | ||
| 78 | + }, | ||
| 79 | + "快应用": { | ||
| 80 | + "华为": "u", | ||
| 81 | + "联盟": "u" | ||
| 82 | + }, | ||
| 83 | + "Vue": { | ||
| 84 | + "vue2": "u", | ||
| 85 | + "vue3": "y" | ||
| 86 | + } | ||
| 87 | + } | ||
| 88 | + } | ||
| 89 | + }, | ||
| 90 | + "dependencies": { | ||
| 91 | + "clipboard": "^2.0.11", | ||
| 92 | + "dayjs": "^1.11.7", | ||
| 93 | + "lodash": "^4.17.21", | ||
| 94 | + "lodash-es": "^4.17.21", | ||
| 95 | + "luch-request": "^3.0.8", | ||
| 96 | + "pinia": "^2.3.1", | ||
| 97 | + "pinia-plugin-persist-uni": "^1.2.0", | ||
| 98 | + "uview-plus": "^3.6.29", | ||
| 99 | + "vue": "^2.7.16", | ||
| 100 | + "weixin-js-sdk": "^1.6.0" | ||
| 101 | + }, | ||
| 102 | + "devDependencies": { | ||
| 103 | + "prettier": "^2.8.7", | ||
| 104 | + "vconsole": "^3.15.0" | ||
| 105 | + } | ||
| 106 | +} |
pages.json
0 → 100644
| 1 | +{ | ||
| 2 | + "easycom": { | ||
| 3 | + "autoscan": true, | ||
| 4 | + "custom": { | ||
| 5 | + "^s-(.*)": "@/sheep/components/s-$1/s-$1.vue", | ||
| 6 | + "^su-(.*)": "@/sheep/ui/su-$1/su-$1.vue", | ||
| 7 | + "^u--(.*)": "@/node_modules/uview-plus/components/u-$1/u-$1.vue", | ||
| 8 | + "^up-(.*)": "@/node_modules/uview-plus/components/u-$1/u-$1.vue", | ||
| 9 | + "^u-([^-].*)": "@/node_modules/uview-plus/components/u-$1/u-$1.vue", | ||
| 10 | + "^qiun-(.*)": "@/uni_modules/qiun-data-charts/components/qiun-$1/qiun-$1.vue" | ||
| 11 | + } | ||
| 12 | + }, | ||
| 13 | + "pages": [ | ||
| 14 | + { | ||
| 15 | + "path": "pages/xunji/xunji", | ||
| 16 | + "style": { | ||
| 17 | + "navigationBarTitleText": "训记" | ||
| 18 | + } | ||
| 19 | + }, | ||
| 20 | + { | ||
| 21 | + "path": "pages/user/user", | ||
| 22 | + "style": { | ||
| 23 | + "navigationBarTitleText": "我的", | ||
| 24 | + "navigationStyle": "default" | ||
| 25 | + } | ||
| 26 | + } | ||
| 27 | + ], | ||
| 28 | + "subPackages": [ | ||
| 29 | + { | ||
| 30 | + "root": "pages4", | ||
| 31 | + "name": "分包4", | ||
| 32 | + "pages": [ | ||
| 33 | + { | ||
| 34 | + "path": "pages/xunji/xunji-xunlian-jihua", | ||
| 35 | + "style": { | ||
| 36 | + "navigationBarTitleText": "训练计划" | ||
| 37 | + } | ||
| 38 | + }, | ||
| 39 | + { | ||
| 40 | + "path": "pages/xunji/xunji-wode-zhuye", | ||
| 41 | + "style": { | ||
| 42 | + "navigationBarTitleText": "我的主页" | ||
| 43 | + } | ||
| 44 | + }, | ||
| 45 | + { | ||
| 46 | + "path": "pages/xunji/xunji-wode-qianming", | ||
| 47 | + "style": { | ||
| 48 | + "navigationBarTitleText": "个性签名", | ||
| 49 | + "navigationStyle": "default" | ||
| 50 | + } | ||
| 51 | + }, | ||
| 52 | + { | ||
| 53 | + "path": "pages/xunji/xunji-wode-moban", | ||
| 54 | + "style": { | ||
| 55 | + "navigationBarTitleText": "我的模版" | ||
| 56 | + } | ||
| 57 | + }, | ||
| 58 | + { | ||
| 59 | + "path": "pages/xunji/xunji-rili-tianjia", | ||
| 60 | + "style": { | ||
| 61 | + "navigationBarTitleText": "日历添加训练动作" | ||
| 62 | + } | ||
| 63 | + }, | ||
| 64 | + { | ||
| 65 | + "path": "pages/xunji/xunji-rili-tianjia-moban", | ||
| 66 | + "style": { | ||
| 67 | + "navigationBarTitleText": "训记-日历-训练模版" | ||
| 68 | + } | ||
| 69 | + }, | ||
| 70 | + { | ||
| 71 | + "path": "pages/xunji/xunji-moban", | ||
| 72 | + "style": { | ||
| 73 | + "navigationBarTitleText": "动作模板" | ||
| 74 | + } | ||
| 75 | + }, | ||
| 76 | + { | ||
| 77 | + "path": "pages/xunji/xunji-moban-xiangqing", | ||
| 78 | + "style": { | ||
| 79 | + "navigationBarTitleText": "模板详情查看" | ||
| 80 | + } | ||
| 81 | + }, | ||
| 82 | + { | ||
| 83 | + "path": "pages/xunji/xunji-dongzuo-xinzeng", | ||
| 84 | + "style": { | ||
| 85 | + "navigationBarTitleText": "", | ||
| 86 | + "navigationStyle": "default" | ||
| 87 | + } | ||
| 88 | + }, | ||
| 89 | + { | ||
| 90 | + "path": "pages/xunji/xunji-dongzuo-xiangqing", | ||
| 91 | + "style": { | ||
| 92 | + "navigationBarTitleText": "动作详情", | ||
| 93 | + "navigationStyle": "default" | ||
| 94 | + } | ||
| 95 | + }, | ||
| 96 | + { | ||
| 97 | + "path": "pages/xunji/xunji-dongzuo-xiangqing-chaojizu", | ||
| 98 | + "style": { | ||
| 99 | + "navigationBarTitleText": "超级组详情", | ||
| 100 | + "backgroundColor": "#1a1a1a" | ||
| 101 | + } | ||
| 102 | + }, | ||
| 103 | + | ||
| 104 | + { | ||
| 105 | + "path": "pages/xunji/xunji-dongzuo-lianxi", | ||
| 106 | + "style": { | ||
| 107 | + "navigationBarTitleText": "动作练习/训练" | ||
| 108 | + } | ||
| 109 | + }, | ||
| 110 | + { | ||
| 111 | + "path": "pages/xunji/dongzuo-xinzengchaojizu", | ||
| 112 | + "style": { | ||
| 113 | + "navigationBarTitleText": "新增超级组" | ||
| 114 | + } | ||
| 115 | + }, | ||
| 116 | + { | ||
| 117 | + "path": "pages/xunji/dongzuo-muluguanli", | ||
| 118 | + "style": { | ||
| 119 | + "navigationBarTitleText": "动作目录管理", | ||
| 120 | + "navigationStyle": "default" | ||
| 121 | + } | ||
| 122 | + }, | ||
| 123 | + { | ||
| 124 | + "path": "pages/xunji/jihua-search", | ||
| 125 | + "style": { | ||
| 126 | + "navigationBarTitleText": "训记-计划-搜索" | ||
| 127 | + } | ||
| 128 | + }, | ||
| 129 | + { | ||
| 130 | + "path": "pages/xunji/xunji-shiping", | ||
| 131 | + "style": { | ||
| 132 | + "navigationBarTitleText": "" | ||
| 133 | + } | ||
| 134 | + } | ||
| 135 | + ] | ||
| 136 | + }, | ||
| 137 | + { | ||
| 138 | + "root": "pages7", | ||
| 139 | + "name": "分包7", | ||
| 140 | + "pages": [ | ||
| 141 | + { | ||
| 142 | + "path": "pages/index/login", | ||
| 143 | + "style": { | ||
| 144 | + "navigationBarTitleText": "登录", | ||
| 145 | + "navigationStyle": "default" | ||
| 146 | + } | ||
| 147 | + } | ||
| 148 | + ] | ||
| 149 | + }, | ||
| 150 | + { | ||
| 151 | + "root": "pages5", | ||
| 152 | + "name": "分包5", | ||
| 153 | + "pages": [ | ||
| 154 | + { | ||
| 155 | + "path": "pages/user/wode-geren-ziliao", | ||
| 156 | + "style": { | ||
| 157 | + "navigationBarTitleText": "个人资料", | ||
| 158 | + "navigationStyle": "default" | ||
| 159 | + } | ||
| 160 | + } | ||
| 161 | + ] | ||
| 162 | + } | ||
| 163 | + ], | ||
| 164 | + "globalStyle": { | ||
| 165 | + "navigationBarTextStyle": "black", | ||
| 166 | + "navigationBarTitleText": "芋道商城", | ||
| 167 | + "navigationBarBackgroundColor": "#FFFFFF", | ||
| 168 | + "backgroundColor": "#FFFFFF", | ||
| 169 | + "navigationStyle": "custom" | ||
| 170 | + }, | ||
| 171 | + "pageTransition": { | ||
| 172 | + "style": "slide-in-bottom", | ||
| 173 | + "duration": 300 | ||
| 174 | + }, | ||
| 175 | + "tabBar": { | ||
| 176 | + "height": 0, | ||
| 177 | + "custom": true, | ||
| 178 | + "list": [ | ||
| 179 | + { | ||
| 180 | + "pagePath": "pages/xunji/xunji" | ||
| 181 | + }, | ||
| 182 | + { | ||
| 183 | + "pagePath": "pages/user/user" | ||
| 184 | + } | ||
| 185 | + ] | ||
| 186 | + } | ||
| 187 | +} |
pages/user/user.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="my-page"> | ||
| 3 | + <!-- 登录头部区 --> | ||
| 4 | + <view | ||
| 5 | + v-if="userStore.isLogin" | ||
| 6 | + class="section-card user-header" | ||
| 7 | + hover-class="card-hover" | ||
| 8 | + @tap="goMyPersonalData" | ||
| 9 | + > | ||
| 10 | + <image | ||
| 11 | + :src=" | ||
| 12 | + userInfo.avatar || | ||
| 13 | + 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260526/默认头像_1779779926983.png' | ||
| 14 | + " | ||
| 15 | + mode="aspectFill" | ||
| 16 | + class="avatar" | ||
| 17 | + /> | ||
| 18 | + <view class="info-content"> | ||
| 19 | + <view class="name-row"> | ||
| 20 | + <text class="nickname">{{ userInfo.nickname || '微信用户' }}</text> | ||
| 21 | + <text v-if="memberLevelName" class="tag">{{ memberLevelName }}</text> | ||
| 22 | + </view> | ||
| 23 | + </view> | ||
| 24 | + <view class="right"> | ||
| 25 | + <uni-icons type="right" size="16" color="#000" /> | ||
| 26 | + </view> | ||
| 27 | + </view> | ||
| 28 | + | ||
| 29 | + <!-- 未登录引导区 --> | ||
| 30 | + <view v-else class="section-card login-guide-box"> | ||
| 31 | + <view class="guide-txt"> | ||
| 32 | + <text class="title">欢迎加入鸿星运动</text> | ||
| 33 | + <text class="desc">登录后即可享受课程预约及资产管理</text> | ||
| 34 | + </view> | ||
| 35 | + <button class="login-btn" hover-class="btn-hover" @click="goLogin">立即登录</button> | ||
| 36 | + </view> | ||
| 37 | + | ||
| 38 | + <!-- --> | ||
| 39 | + <view class="vip-banner" hover-class="opacity-hover" @click="goAddVip"> | ||
| 40 | + <view class="vip-info"> | ||
| 41 | + <uni-icons type="vip-filled" size="22" color="#f1c40f" /> | ||
| 42 | + <text class="vip-text"> | ||
| 43 | + {{ userInfo.deposit === 1 ? '鸿星·尊享会员 | 已开通' : '鸿星·会员 | 开通只需押金¥199' }} | ||
| 44 | + </text> | ||
| 45 | + </view> | ||
| 46 | + <view class="vip-btn">{{ userInfo.deposit === 1 ? '查看权益' : '立即开通' }}</view> | ||
| 47 | + </view> | ||
| 48 | + | ||
| 49 | + <!-- 课程状态快速入口 --> | ||
| 50 | + <view class="section-card quick-entry"> | ||
| 51 | + <view | ||
| 52 | + v-for="entry in quickEntryConfig" | ||
| 53 | + :key="entry.type" | ||
| 54 | + class="entry-item" | ||
| 55 | + hover-class="opacity-hover" | ||
| 56 | + @click="handleQuickEntry(entry.type)" | ||
| 57 | + > | ||
| 58 | + <text class="num">{{ userInfo[entry.key] || 0 }}</text> | ||
| 59 | + <text class="label">{{ entry.label }}</text> | ||
| 60 | + </view> | ||
| 61 | + </view> | ||
| 62 | + | ||
| 63 | + <!-- 广告位 --> | ||
| 64 | + <view class="banner-box" @click="goJiamen"> | ||
| 65 | + <image | ||
| 66 | + src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/4_1773627891703.png" | ||
| 67 | + mode="aspectFill" | ||
| 68 | + class="banner-img" | ||
| 69 | + /> | ||
| 70 | + </view> | ||
| 71 | + | ||
| 72 | + <!-- 核心应用区 --> | ||
| 73 | + <view class="section-card apply-section"> | ||
| 74 | + <view | ||
| 75 | + v-for="(item, index) in APPLY_CONFIG_LIST" | ||
| 76 | + :key="index" | ||
| 77 | + class="apply-item" | ||
| 78 | + @click="authNavigateTo(item.url)" | ||
| 79 | + > | ||
| 80 | + <view class="icon-bg"> | ||
| 81 | + <image :src="item.icon" class="img" mode="aspectFit" /> | ||
| 82 | + </view> | ||
| 83 | + <text class="text">{{ item.text }}</text> | ||
| 84 | + </view> | ||
| 85 | + </view> | ||
| 86 | + | ||
| 87 | + <!-- 资产账户网格区 --> | ||
| 88 | + <view v-if="userStore.isLogin" class="section-card"> | ||
| 89 | + <view class="account-grid"> | ||
| 90 | + <view | ||
| 91 | + v-for="(acc, idx) in accountConfig" | ||
| 92 | + :key="idx" | ||
| 93 | + class="account-item" | ||
| 94 | + @click="authNavigateTo(acc.url)" | ||
| 95 | + > | ||
| 96 | + <text class="acc-lab">{{ acc.label }}</text> | ||
| 97 | + <text class="acc-val"> | ||
| 98 | + {{ formatAccountValue(userInfo[acc.key], acc.isFloat) }} | ||
| 99 | + <text v-if="acc.unit" class="unit">{{ acc.unit }}</text> | ||
| 100 | + </text> | ||
| 101 | + </view> | ||
| 102 | + </view> | ||
| 103 | + </view> | ||
| 104 | + | ||
| 105 | + <!-- 功能矩阵九宫格 --> | ||
| 106 | + <view class="section-card icon-grid-box"> | ||
| 107 | + <view class="icon-grid"> | ||
| 108 | + <view | ||
| 109 | + v-for="(item, index) in FUNCTION_CONFIG_LIST" | ||
| 110 | + :key="index" | ||
| 111 | + class="icon-item" | ||
| 112 | + @click="handleGridItemClick(item)" | ||
| 113 | + > | ||
| 114 | + <view class="icon-img-wrap"> | ||
| 115 | + <image :src="item.icon" mode="aspectFit" class="icon-img" /> | ||
| 116 | + | ||
| 117 | + <view v-if="item.text === '订单记录' && userInfo.orderRecordMark" class="badge"> | ||
| 118 | + {{ userInfo.orderRecordMark }} | ||
| 119 | + </view> | ||
| 120 | + </view> | ||
| 121 | + <text class="icon-text">{{ item.text }}</text> | ||
| 122 | + | ||
| 123 | + <button | ||
| 124 | + v-if="item.text === '联系客服'" | ||
| 125 | + open-type="contact" | ||
| 126 | + class="mp-contact-overlay-btn" | ||
| 127 | + /> | ||
| 128 | + </view> | ||
| 129 | + </view> | ||
| 130 | + </view> | ||
| 131 | + | ||
| 132 | + <!-- 设置区 --> | ||
| 133 | + <view v-if="userStore.isLogin" class="section-card"> | ||
| 134 | + <view class="setting-item" @click="authNavigateTo('/pages5/pages/user/wode-shezhi')"> | ||
| 135 | + <text class="setting-text">个人设置</text> | ||
| 136 | + <uni-icons type="right" size="14" color="#E0E0E0" /> | ||
| 137 | + </view> | ||
| 138 | + <view class="setting-item" @click="authNavigateTo('/pages5/pages/user/wode-yinsishezhi')"> | ||
| 139 | + <text class="setting-text">隐私中心</text> | ||
| 140 | + <uni-icons type="right" size="14" color="#E0E0E0" /> | ||
| 141 | + </view> | ||
| 142 | + </view> | ||
| 143 | + | ||
| 144 | + <Tabbar /> | ||
| 145 | + </view> | ||
| 146 | +</template> | ||
| 147 | + | ||
| 148 | +<script setup> | ||
| 149 | + import { ref } from 'vue'; | ||
| 150 | + import UserApi from '@/sheep/api/member/user'; | ||
| 151 | + import MemberApi from '@/sheep/api/member/member'; | ||
| 152 | + import useUserStore from '@/sheep/store/user'; | ||
| 153 | + import { onShow } from '@dcloudio/uni-app'; | ||
| 154 | + // 响应式数据挂载 | ||
| 155 | + const userStore = useUserStore(); | ||
| 156 | + const userInfo = ref({}); | ||
| 157 | + const memberLevelName = ref(''); | ||
| 158 | + | ||
| 159 | + // 固定的 UI 配置 | ||
| 160 | + const APPLY_CONFIG_LIST = []; | ||
| 161 | + | ||
| 162 | + const FUNCTION_CONFIG_LIST = []; | ||
| 163 | + | ||
| 164 | + // 课程计数状态配置映射 | ||
| 165 | + const quickEntryConfig = [ | ||
| 166 | + { type: 1, key: 'courseNum', label: '待上课' }, | ||
| 167 | + { type: 2, key: 'courseWaitNum', label: '等待中' }, | ||
| 168 | + { type: 3, key: 'courseEvaluationWaitNum', label: '历史课程' }, | ||
| 169 | + ]; | ||
| 170 | + | ||
| 171 | + // 资产账户字段清洗配置映射 (对应接口数据类型:number 与 integer) | ||
| 172 | + const accountConfig = []; | ||
| 173 | + | ||
| 174 | + /** | ||
| 175 | + * JSDoc 核心资产数值洗涤函数 | ||
| 176 | + * @param {number|undefined} val 原始金钱/数量值 | ||
| 177 | + * @param {boolean} isFloat 是否需要保留两位小数 | ||
| 178 | + * @returns {string|number} 格式化后的安全渲染字符串 | ||
| 179 | + */ | ||
| 180 | + const formatAccountValue = (val, isFloat) => { | ||
| 181 | + if (val === undefined || val === null) return isFloat ? '0.00' : 0; | ||
| 182 | + return isFloat ? Number(val).toFixed(2) : Math.floor(val); | ||
| 183 | + }; | ||
| 184 | + | ||
| 185 | + /** | ||
| 186 | + * 路由守卫拦截转发 | ||
| 187 | + * @param {string} url 目标绝对/相对地址 | ||
| 188 | + */ | ||
| 189 | + const authNavigateTo = (url) => { | ||
| 190 | + if (!userStore.isLogin) { | ||
| 191 | + goLogin(); | ||
| 192 | + return; | ||
| 193 | + } | ||
| 194 | + uni.navigateTo({ | ||
| 195 | + url, | ||
| 196 | + fail: (err) => console.error(`[Router] 页面跳转失败 ${url}: `, err), | ||
| 197 | + }); | ||
| 198 | + }; | ||
| 199 | + | ||
| 200 | + /** | ||
| 201 | + * 统一处理功能网格的点击分发 | ||
| 202 | + * @param {Object} item 节点配置项 | ||
| 203 | + */ | ||
| 204 | + const handleGridItemClick = (item) => { | ||
| 205 | + if (item.text === '联系客服') { | ||
| 206 | + // #ifndef MP-WEIXIN | ||
| 207 | + // 兜底非微信小程序平台(如H5、App),可以正常走原来的普通客服页面路由 | ||
| 208 | + authNavigateTo(item.url); | ||
| 209 | + // #endif | ||
| 210 | + return; | ||
| 211 | + } | ||
| 212 | + | ||
| 213 | + // 其他正常功能,正常走路由拦截守卫 | ||
| 214 | + authNavigateTo(item.url); | ||
| 215 | + }; | ||
| 216 | + | ||
| 217 | + /** | ||
| 218 | + * 异步高内聚合并请求 | ||
| 219 | + * 解决因先后触发 setData 导致微信小程序底层 AppService 与 WebView 之间高频拥堵卡顿的问题 | ||
| 220 | + */ | ||
| 221 | + const fetchPageData = async () => { | ||
| 222 | + try { | ||
| 223 | + const [userRes, levelRes] = await Promise.all([ | ||
| 224 | + UserApi.getUserInfo(), | ||
| 225 | + MemberApi.getMemberLevel(), | ||
| 226 | + ]); | ||
| 227 | + | ||
| 228 | + const rawUser = userRes.data || {}; | ||
| 229 | + userInfo.value = rawUser; | ||
| 230 | + | ||
| 231 | + // 架构重构:数据拉取后一次性计算出等级映射结果,拒绝在 computed 内部循环执行实例化 | ||
| 232 | + const levels = levelRes.data?.detailList || []; | ||
| 233 | + if (rawUser.level !== undefined && levels.length > 0) { | ||
| 234 | + const target = levels.find((item) => item.id === rawUser.level); | ||
| 235 | + memberLevelName.value = target ? target.name : ''; | ||
| 236 | + } else { | ||
| 237 | + memberLevelName.value = ''; | ||
| 238 | + } | ||
| 239 | + } catch (error) { | ||
| 240 | + console.error('[API Error] 拉取个人资产信息流失败:', error); | ||
| 241 | + } | ||
| 242 | + }; | ||
| 243 | + | ||
| 244 | + onShow(() => { | ||
| 245 | + if (userStore.isLogin) { | ||
| 246 | + fetchPageData(); | ||
| 247 | + } else { | ||
| 248 | + userInfo.value = {}; | ||
| 249 | + memberLevelName.value = ''; | ||
| 250 | + } | ||
| 251 | + }); | ||
| 252 | + | ||
| 253 | + // 路由跳转原子原子层 | ||
| 254 | + const goLogin = () => uni.navigateTo({ url: '/pages7/pages/index/login' }); | ||
| 255 | + const goMyPersonalData = () => authNavigateTo('/pages5/pages/user/wode-geren-ziliao'); | ||
| 256 | + const goAddVip = () => authNavigateTo('/pages5/pages/user/wode-hongxing-huiyuan'); | ||
| 257 | + | ||
| 258 | + const goJiamen = () => uni.navigateTo({ url: '/pages7/pages/index/shouye-jiamen-hongxing' }); | ||
| 259 | + const handleQuickEntry = (type) => authNavigateTo(`/pages5/pages/user/wode-shangke?type=${type}`); | ||
| 260 | +</script> | ||
| 261 | + | ||
| 262 | +<style scoped lang="scss"> | ||
| 263 | + $brand-color: #ff6b00; | ||
| 264 | + $page-bg: #f8f8f8; | ||
| 265 | + | ||
| 266 | + .my-page { | ||
| 267 | + min-height: 100vh; | ||
| 268 | + background-color: $page-bg; | ||
| 269 | + padding: 20rpx 28rpx calc(40rpx + env(safe-area-inset-bottom)); /* 解决部分 iOS 底部 Tabbar 高度塌陷 */ | ||
| 270 | + box-sizing: border-box; | ||
| 271 | + | ||
| 272 | + .section-card { | ||
| 273 | + background: #ffffff; | ||
| 274 | + border-radius: 24rpx; | ||
| 275 | + padding: 30rpx 20rpx; | ||
| 276 | + margin-bottom: 24rpx; | ||
| 277 | + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03); | ||
| 278 | + } | ||
| 279 | + | ||
| 280 | + .user-header { | ||
| 281 | + display: flex; | ||
| 282 | + align-items: center; | ||
| 283 | + padding: 34rpx 30rpx; | ||
| 284 | + .avatar { | ||
| 285 | + width: 110rpx; | ||
| 286 | + height: 110rpx; | ||
| 287 | + border-radius: 50%; | ||
| 288 | + background: #f0f0f0; | ||
| 289 | + border: 4rpx solid #fff; | ||
| 290 | + } | ||
| 291 | + .info-content { | ||
| 292 | + margin-left: 24rpx; | ||
| 293 | + flex: 1; | ||
| 294 | + .name-row { | ||
| 295 | + display: flex; | ||
| 296 | + flex-direction: column; | ||
| 297 | + .nickname { | ||
| 298 | + font-size: 34rpx; | ||
| 299 | + font-weight: bold; | ||
| 300 | + color: #333; | ||
| 301 | + } | ||
| 302 | + .tag { | ||
| 303 | + font-size: 20rpx; | ||
| 304 | + color: $brand-color; | ||
| 305 | + | ||
| 306 | + padding: 4rpx 12rpx; | ||
| 307 | + border-radius: 8rpx; | ||
| 308 | + | ||
| 309 | + vertical-align: middle; | ||
| 310 | + } | ||
| 311 | + } | ||
| 312 | + } | ||
| 313 | + .right { | ||
| 314 | + display: flex; | ||
| 315 | + align-items: center; | ||
| 316 | + gap: 60rpx; | ||
| 317 | + .img { | ||
| 318 | + width: 60rpx; | ||
| 319 | + height: 60rpx; | ||
| 320 | + } | ||
| 321 | + } | ||
| 322 | + } | ||
| 323 | + | ||
| 324 | + .login-guide-box { | ||
| 325 | + display: flex; | ||
| 326 | + justify-content: space-between; | ||
| 327 | + align-items: center; | ||
| 328 | + .title { | ||
| 329 | + font-size: 32rpx; | ||
| 330 | + font-weight: bold; | ||
| 331 | + color: #333; | ||
| 332 | + } | ||
| 333 | + .desc { | ||
| 334 | + font-size: 22rpx; | ||
| 335 | + color: #999; | ||
| 336 | + margin-top: 4rpx; | ||
| 337 | + display: block; | ||
| 338 | + } | ||
| 339 | + .login-btn { | ||
| 340 | + margin: 0; | ||
| 341 | + background: $brand-color; | ||
| 342 | + color: #fff; | ||
| 343 | + font-size: 24rpx; | ||
| 344 | + height: 64rpx; | ||
| 345 | + line-height: 64rpx; | ||
| 346 | + border-radius: 32rpx; | ||
| 347 | + padding: 0 30rpx; | ||
| 348 | + &::after { | ||
| 349 | + border: none; | ||
| 350 | + } /* 清理小程序 button 默认黑边线 */ | ||
| 351 | + } | ||
| 352 | + } | ||
| 353 | + | ||
| 354 | + .vip-banner { | ||
| 355 | + background: #2b2b2b; | ||
| 356 | + border-radius: 20rpx; | ||
| 357 | + padding: 24rpx 30rpx; | ||
| 358 | + display: flex; | ||
| 359 | + justify-content: space-between; | ||
| 360 | + align-items: center; | ||
| 361 | + margin-bottom: 24rpx; | ||
| 362 | + .vip-info { | ||
| 363 | + display: flex; | ||
| 364 | + align-items: center; | ||
| 365 | + } | ||
| 366 | + .vip-text { | ||
| 367 | + color: #f1c40f; | ||
| 368 | + font-size: 24rpx; | ||
| 369 | + margin-left: 14rpx; | ||
| 370 | + } | ||
| 371 | + .vip-btn { | ||
| 372 | + background: linear-gradient(90deg, #f1c40f, #f39c12); | ||
| 373 | + color: #333; | ||
| 374 | + font-size: 20rpx; | ||
| 375 | + font-weight: bold; | ||
| 376 | + padding: 8rpx 20rpx; | ||
| 377 | + border-radius: 30rpx; | ||
| 378 | + } | ||
| 379 | + } | ||
| 380 | + | ||
| 381 | + .quick-entry { | ||
| 382 | + display: flex; | ||
| 383 | + justify-content: space-around; | ||
| 384 | + .entry-item { | ||
| 385 | + text-align: center; | ||
| 386 | + .num { | ||
| 387 | + font-size: 38rpx; | ||
| 388 | + font-weight: bold; | ||
| 389 | + color: #333; | ||
| 390 | + } | ||
| 391 | + .label { | ||
| 392 | + font-size: 22rpx; | ||
| 393 | + color: #999; | ||
| 394 | + margin-top: 4rpx; | ||
| 395 | + display: block; | ||
| 396 | + } | ||
| 397 | + } | ||
| 398 | + } | ||
| 399 | + | ||
| 400 | + .banner-box { | ||
| 401 | + height: 160rpx; | ||
| 402 | + margin-bottom: 24rpx; | ||
| 403 | + border-radius: 20rpx; | ||
| 404 | + overflow: hidden; | ||
| 405 | + .banner-img { | ||
| 406 | + width: 100%; | ||
| 407 | + height: 100%; | ||
| 408 | + } | ||
| 409 | + } | ||
| 410 | + | ||
| 411 | + .apply-section { | ||
| 412 | + display: grid; | ||
| 413 | + grid-template-columns: repeat(5, 1fr); | ||
| 414 | + .apply-item { | ||
| 415 | + display: flex; | ||
| 416 | + flex-direction: column; | ||
| 417 | + align-items: center; | ||
| 418 | + .icon-bg { | ||
| 419 | + width: 80rpx; | ||
| 420 | + height: 80rpx; | ||
| 421 | + background: #f9f9f9; | ||
| 422 | + border-radius: 20rpx; | ||
| 423 | + display: flex; | ||
| 424 | + align-items: center; | ||
| 425 | + justify-content: center; | ||
| 426 | + margin-bottom: 12rpx; | ||
| 427 | + .img { | ||
| 428 | + width: 44rpx; | ||
| 429 | + height: 44rpx; | ||
| 430 | + } | ||
| 431 | + } | ||
| 432 | + .text { | ||
| 433 | + font-size: 22rpx; | ||
| 434 | + color: #666; | ||
| 435 | + } | ||
| 436 | + } | ||
| 437 | + } | ||
| 438 | + | ||
| 439 | + .account-grid { | ||
| 440 | + display: grid; | ||
| 441 | + grid-template-columns: repeat(4, 1fr); | ||
| 442 | + row-gap: 34rpx; | ||
| 443 | + .account-item { | ||
| 444 | + display: flex; | ||
| 445 | + flex-direction: column; | ||
| 446 | + align-items: center; | ||
| 447 | + .acc-val { | ||
| 448 | + font-size: 30rpx; | ||
| 449 | + font-weight: bold; | ||
| 450 | + color: #333; | ||
| 451 | + .unit { | ||
| 452 | + font-size: 20rpx; | ||
| 453 | + font-weight: normal; | ||
| 454 | + color: #666; | ||
| 455 | + margin-left: 2rpx; | ||
| 456 | + } | ||
| 457 | + } | ||
| 458 | + .acc-lab { | ||
| 459 | + font-size: 22rpx; | ||
| 460 | + color: #999; | ||
| 461 | + margin-top: 4rpx; | ||
| 462 | + } | ||
| 463 | + } | ||
| 464 | + } | ||
| 465 | + | ||
| 466 | + .icon-grid { | ||
| 467 | + display: grid; | ||
| 468 | + grid-template-columns: repeat(4, 1fr); | ||
| 469 | + row-gap: 40rpx; | ||
| 470 | + .icon-item { | ||
| 471 | + display: flex; | ||
| 472 | + flex-direction: column; | ||
| 473 | + align-items: center; | ||
| 474 | + position: relative; | ||
| 475 | + .icon-img-wrap { | ||
| 476 | + position: relative; | ||
| 477 | + margin-bottom: 12rpx; | ||
| 478 | + .icon-img { | ||
| 479 | + width: 46rpx; | ||
| 480 | + height: 46rpx; | ||
| 481 | + } | ||
| 482 | + .badge { | ||
| 483 | + position: absolute; | ||
| 484 | + top: -12rpx; | ||
| 485 | + right: -42rpx; /* 适当拓宽右移,容纳后端多字文本 */ | ||
| 486 | + background: #ff4d4f; | ||
| 487 | + color: #fff; | ||
| 488 | + font-size: 18rpx; | ||
| 489 | + padding: 2rpx 10rpx; | ||
| 490 | + border-radius: 16rpx; | ||
| 491 | + white-space: nowrap; | ||
| 492 | + } | ||
| 493 | + /* 未读状态小红点 */ | ||
| 494 | + .dot-badge { | ||
| 495 | + position: absolute; | ||
| 496 | + top: -4rpx; | ||
| 497 | + right: -4rpx; | ||
| 498 | + width: 14rpx; | ||
| 499 | + height: 14rpx; | ||
| 500 | + background: #ff4d4f; | ||
| 501 | + border-radius: 50%; | ||
| 502 | + } | ||
| 503 | + } | ||
| 504 | + .icon-text { | ||
| 505 | + font-size: 24rpx; | ||
| 506 | + color: #555; | ||
| 507 | + } | ||
| 508 | + .mp-contact-overlay-btn { | ||
| 509 | + position: absolute; | ||
| 510 | + top: 0; | ||
| 511 | + left: 0; | ||
| 512 | + width: 100% !important; | ||
| 513 | + height: 100% !important; | ||
| 514 | + opacity: 0 !important; /* 核心:完全透明 */ | ||
| 515 | + border: none !important; | ||
| 516 | + padding: 0 !important; | ||
| 517 | + margin: 0 !important; | ||
| 518 | + z-index: 10; /* 确保盖在图表和文字的最上方 */ | ||
| 519 | + | ||
| 520 | + &::after { | ||
| 521 | + border: none !important; | ||
| 522 | + } | ||
| 523 | + } | ||
| 524 | + } | ||
| 525 | + } | ||
| 526 | + | ||
| 527 | + .setting-item { | ||
| 528 | + display: flex; | ||
| 529 | + justify-content: space-between; | ||
| 530 | + align-items: center; | ||
| 531 | + padding: 24rpx 0; | ||
| 532 | + border-bottom: 1rpx solid #f9f9f9; | ||
| 533 | + &:last-child { | ||
| 534 | + border-bottom: none; | ||
| 535 | + } | ||
| 536 | + .setting-text { | ||
| 537 | + font-size: 28rpx; | ||
| 538 | + color: #444; | ||
| 539 | + } | ||
| 540 | + } | ||
| 541 | + | ||
| 542 | + .opacity-hover { | ||
| 543 | + opacity: 0.7; | ||
| 544 | + } | ||
| 545 | + .card-hover { | ||
| 546 | + background-color: #fcfcfc; | ||
| 547 | + } | ||
| 548 | + } | ||
| 549 | +</style> |
pages/xunji/components/LineChart.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="charts-box"> | ||
| 3 | + <qiun-data-charts | ||
| 4 | + type="line" | ||
| 5 | + :opts="opts" | ||
| 6 | + :chartData="chartData" | ||
| 7 | + :reshow="reshow" | ||
| 8 | + :canvas2d="true" | ||
| 9 | + /> | ||
| 10 | + </view> | ||
| 11 | +</template> | ||
| 12 | + | ||
| 13 | +<script setup> | ||
| 14 | + import { ref, reactive, watch } from 'vue'; | ||
| 15 | + | ||
| 16 | + // 1. 定义 Props 接收父组件数据 | ||
| 17 | + const props = defineProps({ | ||
| 18 | + // 传入的分类数据 (横坐标) | ||
| 19 | + categories: { | ||
| 20 | + type: Array, | ||
| 21 | + default: () => [], | ||
| 22 | + }, | ||
| 23 | + // 传入的系列数据 (纵坐标内容) | ||
| 24 | + series: { | ||
| 25 | + type: Array, | ||
| 26 | + default: () => [], | ||
| 27 | + }, | ||
| 28 | + // 专门用于解决弹窗不显示问题的属性 | ||
| 29 | + reshow: { | ||
| 30 | + type: Boolean, | ||
| 31 | + default: false, | ||
| 32 | + }, | ||
| 33 | + }); | ||
| 34 | + | ||
| 35 | + const chartData = ref({}); | ||
| 36 | + | ||
| 37 | + // 2. 图表配置项 | ||
| 38 | + const opts = reactive({ | ||
| 39 | + color: [ | ||
| 40 | + '#1890FF', | ||
| 41 | + '#91CB74', | ||
| 42 | + '#FAC858', | ||
| 43 | + '#EE6666', | ||
| 44 | + '#73C0DE', | ||
| 45 | + '#3CA272', | ||
| 46 | + '#FC8452', | ||
| 47 | + '#9A60B4', | ||
| 48 | + '#ea7ccc', | ||
| 49 | + ], | ||
| 50 | + padding: [15, 10, 0, 15], | ||
| 51 | + enableScroll: false, | ||
| 52 | + legend: {}, | ||
| 53 | + xAxis: { | ||
| 54 | + disableGrid: true, | ||
| 55 | + }, | ||
| 56 | + yAxis: { | ||
| 57 | + gridType: 'dash', | ||
| 58 | + dashLength: 2, | ||
| 59 | + }, | ||
| 60 | + extra: { | ||
| 61 | + line: { | ||
| 62 | + type: 'straight', | ||
| 63 | + width: 2, | ||
| 64 | + activeType: 'hollow', | ||
| 65 | + }, | ||
| 66 | + }, | ||
| 67 | + }); | ||
| 68 | + | ||
| 69 | + // 3. 核心逻辑:格式化数据 | ||
| 70 | + const formatData = () => { | ||
| 71 | + if (props.categories.length > 0) { | ||
| 72 | + chartData.value = { | ||
| 73 | + categories: props.categories, | ||
| 74 | + series: props.series, | ||
| 75 | + }; | ||
| 76 | + } | ||
| 77 | + }; | ||
| 78 | + | ||
| 79 | + // 4. 监听 Props 变化,当父组件传入新数据时自动重绘 | ||
| 80 | + watch( | ||
| 81 | + () => [props.categories, props.series], | ||
| 82 | + () => { | ||
| 83 | + formatData(); | ||
| 84 | + }, | ||
| 85 | + { immediate: true, deep: true }, | ||
| 86 | + ); | ||
| 87 | +</script> | ||
| 88 | + | ||
| 89 | +<style lang="scss" scoped> | ||
| 90 | + .charts-box { | ||
| 91 | + width: 100%; | ||
| 92 | + height: 100%; | ||
| 93 | + } | ||
| 94 | +</style> |
pages/xunji/components/beizhu.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <up-popup :show="show" mode="bottom" round="16" closeable @close="show = false" :safeAreaInsetBottom="false"> | ||
| 3 | + <view class="desc-container"> | ||
| 4 | + <view class="title">动作备注</view> | ||
| 5 | + | ||
| 6 | + <view class="input-box"> | ||
| 7 | + <up-textarea | ||
| 8 | + v-model="tempNoteContent" | ||
| 9 | + placeholder="此处填写个人备注" | ||
| 10 | + autoHeight | ||
| 11 | + border="none" | ||
| 12 | + customStyle="background: #242424; padding: 20rpx; border-radius: 12rpx; color: #fff" | ||
| 13 | + placeholderStyle="color: #999" | ||
| 14 | + ></up-textarea> | ||
| 15 | + </view> | ||
| 16 | + | ||
| 17 | + <view class="footer"> | ||
| 18 | + <view class="btn" @click="saveNoteContent">保存</view> | ||
| 19 | + </view> | ||
| 20 | + </view> | ||
| 21 | + </up-popup> | ||
| 22 | +</template> | ||
| 23 | + | ||
| 24 | +<script setup> | ||
| 25 | + import { ref } from 'vue'; | ||
| 26 | + | ||
| 27 | + const show = ref(false); | ||
| 28 | + const tempNoteContent = ref(''); // 临时编辑的备注内容 | ||
| 29 | + | ||
| 30 | + const emit = defineEmits(['saveSuccess']); | ||
| 31 | + // 保存备注 | ||
| 32 | + const saveNoteContent = async () => { | ||
| 33 | + const content = tempNoteContent.value.trim(); | ||
| 34 | + // 如果内容为空,直接返回,不提交 | ||
| 35 | + if (!content) { | ||
| 36 | + uni.showToast({ title: '备注不能为空', icon: 'none' }); | ||
| 37 | + return; | ||
| 38 | + } | ||
| 39 | + emit('saveSuccess', content); | ||
| 40 | + // 关闭弹窗 | ||
| 41 | + show.value = false; | ||
| 42 | + }; | ||
| 43 | + | ||
| 44 | + const open = () => { | ||
| 45 | + show.value = true; | ||
| 46 | + }; | ||
| 47 | + | ||
| 48 | + defineExpose({ open }); | ||
| 49 | +</script> | ||
| 50 | + | ||
| 51 | +<style lang="scss" scoped> | ||
| 52 | + .desc-container { | ||
| 53 | + background-color: #1a1a1a; | ||
| 54 | + padding: 40rpx 30rpx 40rpx; | ||
| 55 | + | ||
| 56 | + .title { | ||
| 57 | + color: #fff; | ||
| 58 | + font-size: 32rpx; | ||
| 59 | + font-weight: 500; | ||
| 60 | + text-align: center; | ||
| 61 | + margin-bottom: 40rpx; | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + .input-box { | ||
| 65 | + margin-bottom: 60rpx; | ||
| 66 | + | ||
| 67 | + /* 穿透修改 u-textarea 内部文字颜色 */ | ||
| 68 | + :deep(.u-textarea__field) { | ||
| 69 | + color: #ccc !important; | ||
| 70 | + } | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + .footer { | ||
| 74 | + width: 100%; | ||
| 75 | + display: flex; | ||
| 76 | + justify-content: center; | ||
| 77 | + align-items: center; | ||
| 78 | + | ||
| 79 | + .btn { | ||
| 80 | + width: 100%; | ||
| 81 | + height: 80rpx; | ||
| 82 | + background-color: #fedc1f; | ||
| 83 | + color: #333; | ||
| 84 | + border-radius: 12rpx; | ||
| 85 | + text-align: center; | ||
| 86 | + line-height: 80rpx; | ||
| 87 | + } | ||
| 88 | + } | ||
| 89 | + } | ||
| 90 | +</style> |
pages/xunji/components/dongzuo-xianqing.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <up-popup | ||
| 3 | + :show="actionShow" | ||
| 4 | + mode="bottom" | ||
| 5 | + minHeight="90vh" | ||
| 6 | + @close="actionShow = false" | ||
| 7 | + bgColor="#1a1a1a" | ||
| 8 | + > | ||
| 9 | + <scroll-view class="action-container" scroll-y> | ||
| 10 | + <view class="header" v-if="type == 1"> | ||
| 11 | + <view class="explain"> | ||
| 12 | + <image | ||
| 13 | + v-if="modeTab < 2" | ||
| 14 | + :src="modeTab == 0 ? actionDetail.url3dAnimation : actionDetail.urlRealPerson" | ||
| 15 | + class="media-content" | ||
| 16 | + mode="aspectFill" | ||
| 17 | + /> | ||
| 18 | + <view class="video" v-else @click="playVideo(actionDetail.urlTutorial)"> | ||
| 19 | + <video | ||
| 20 | + :src="actionDetail.urlTutorial" | ||
| 21 | + class="media-content" | ||
| 22 | + :autoplay="false" | ||
| 23 | + :show-center-play-btn="false" | ||
| 24 | + :controls="false" | ||
| 25 | + /> | ||
| 26 | + <view class="play-icon"> | ||
| 27 | + <up-icon name="play-right" size="28" class="icon" color="#fff"></up-icon> | ||
| 28 | + </view> | ||
| 29 | + </view> | ||
| 30 | + </view> | ||
| 31 | + | ||
| 32 | + <view class="mode-tabs" v-if="actionDetail.urlRealPerson || actionDetail.urlTutorial"> | ||
| 33 | + <view | ||
| 34 | + class="tab-item" | ||
| 35 | + v-if="actionDetail.url3dAnimation" | ||
| 36 | + :class="{ active: modeTab == 0 }" | ||
| 37 | + @click="switchModeTab(0)" | ||
| 38 | + > | ||
| 39 | + 3D | ||
| 40 | + </view> | ||
| 41 | + <view | ||
| 42 | + class="tab-item" | ||
| 43 | + v-if="actionDetail.urlRealPerson" | ||
| 44 | + :class="{ active: modeTab == 1 }" | ||
| 45 | + @click="switchModeTab(1)" | ||
| 46 | + > | ||
| 47 | + 真人 | ||
| 48 | + </view> | ||
| 49 | + <view | ||
| 50 | + class="tab-item" | ||
| 51 | + v-if="actionDetail.urlTutorial" | ||
| 52 | + :class="{ active: modeTab == 2 }" | ||
| 53 | + @click="switchModeTab(2)" | ||
| 54 | + > | ||
| 55 | + 讲解 | ||
| 56 | + </view> | ||
| 57 | + </view> | ||
| 58 | + </view> | ||
| 59 | + | ||
| 60 | + <view class="main"> | ||
| 61 | + <view class="main-header"> | ||
| 62 | + <view class="title-bar"> | ||
| 63 | + <text class="title">{{ actionDetail?.name }}</text> | ||
| 64 | + <view class="action-icons"> | ||
| 65 | + <!-- <up-icon name="share-square" color="#fff" size="24"></up-icon> --> | ||
| 66 | + <button class="share-btn" open-type="share"> | ||
| 67 | + <uni-icons type="paperplane" size="24" color="#fff"></uni-icons> | ||
| 68 | + </button> | ||
| 69 | + <up-icon | ||
| 70 | + :name="isFavorite ? 'star-fill' : 'star'" | ||
| 71 | + :color="isFavorite ? '#fedc1f' : '#fff'" | ||
| 72 | + size="24" | ||
| 73 | + @click="toggleCollect" | ||
| 74 | + ></up-icon> | ||
| 75 | + <!-- <FavoriteBtn :id="actionId" :type="type" /> --> | ||
| 76 | + </view> | ||
| 77 | + </view> | ||
| 78 | + <!-- 要点,历史,平替动作标签 --> | ||
| 79 | + <view class="content-tabs"> | ||
| 80 | + <view class="tab-item" :class="{ active: contentTab === 0 }" @click="contentTab = 0"> | ||
| 81 | + 要点 | ||
| 82 | + </view> | ||
| 83 | + <view class="tab-item" :class="{ active: contentTab === 1 }" @click="contentTab = 1"> | ||
| 84 | + 历史 | ||
| 85 | + </view> | ||
| 86 | + <view | ||
| 87 | + class="tab-item" | ||
| 88 | + :class="{ active: contentTab === 2 }" | ||
| 89 | + @click="contentTab = 2" | ||
| 90 | + v-if="type === 1" | ||
| 91 | + > | ||
| 92 | + 平替动作 | ||
| 93 | + </view> | ||
| 94 | + </view> | ||
| 95 | + </view> | ||
| 96 | + | ||
| 97 | + <view class="main-content"> | ||
| 98 | + <!-- 1 要点 --> | ||
| 99 | + <view v-if="contentTab === 0" class="tab-pane slide-up"> | ||
| 100 | + <view class="section" v-if="actionDetail.urlTutorial"> | ||
| 101 | + <view class="section-title">视频讲解</view> | ||
| 102 | + <view class="video-grid"> | ||
| 103 | + <!-- <view class="video-card" v-for="i in 2" :key="i"></view> --> | ||
| 104 | + <view class="video-card" @click="playVideo(actionDetail.urlTutorial)"> | ||
| 105 | + <video | ||
| 106 | + :src="actionDetail.urlTutorial" | ||
| 107 | + class="video" | ||
| 108 | + :autoplay="false" | ||
| 109 | + :show-center-play-btn="false" | ||
| 110 | + :controls="false" | ||
| 111 | + /> | ||
| 112 | + <view class="play-overlay" | ||
| 113 | + ><up-icon name="play-circle-fill" color="#fff" size="30"></up-icon | ||
| 114 | + ></view> | ||
| 115 | + </view> | ||
| 116 | + </view> | ||
| 117 | + </view> | ||
| 118 | + | ||
| 119 | + <view class="memo-box" @click="openBeizhu"> | ||
| 120 | + <view class="section-title">训练备注</view> | ||
| 121 | + <up-textarea | ||
| 122 | + class="textarea" | ||
| 123 | + v-model="actionDetail.userNote" | ||
| 124 | + placeholder="点击填写备注" | ||
| 125 | + autoHeight | ||
| 126 | + customStyle="background: transparent; border: none; padding: 10rpx 0;" | ||
| 127 | + placeholderStyle="color: #666" | ||
| 128 | + disabled | ||
| 129 | + border="none" | ||
| 130 | + ></up-textarea> | ||
| 131 | + </view> | ||
| 132 | + <!-- 动作列表,只有超级组才有 --> | ||
| 133 | + <view class="section" v-if="type === 2"> | ||
| 134 | + <view class="section-title">动作列表</view> | ||
| 135 | + <view class="action-list"> | ||
| 136 | + <!-- 动作循环列表 --> | ||
| 137 | + <view | ||
| 138 | + class="action-item" | ||
| 139 | + v-for="item in actionDetail?.exercises" | ||
| 140 | + :key="item.id" | ||
| 141 | + @click="openActionItem(item)" | ||
| 142 | + > | ||
| 143 | + <image :src="item.url3dAnimation || lostImage" mode="aspectFill" class="img" /> | ||
| 144 | + <view class="middle"> | ||
| 145 | + <view class="name">{{ item.name }}</view> | ||
| 146 | + <view class="tips"> | ||
| 147 | + <!-- 渲染主练肌肉标签 --> | ||
| 148 | + <view class="tip" v-for="p in item.primaryMuscles" :key="p"> | ||
| 149 | + {{ p }} | ||
| 150 | + </view> | ||
| 151 | + <view v-for="s in item.secondaryMuscles" :key="s">{{ s }}</view> | ||
| 152 | + </view> | ||
| 153 | + </view> | ||
| 154 | + <up-icon name="arrow-right" color="#fff" size="16"></up-icon> | ||
| 155 | + </view> | ||
| 156 | + </view> | ||
| 157 | + </view> | ||
| 158 | + | ||
| 159 | + <view class="section" v-if="type == 1"> | ||
| 160 | + <view class="section-title">步骤</view> | ||
| 161 | + <view class="steps-list"> | ||
| 162 | + <rich-text class="step-text" :nodes="actionDetail.stepDescription"></rich-text> | ||
| 163 | + </view> | ||
| 164 | + </view> | ||
| 165 | + | ||
| 166 | + <view class="section"> | ||
| 167 | + <view class="section-title">训练部位</view> | ||
| 168 | + <image :src="actionDetail.urlImage" mode="widthFix" class="muscle-map" /> | ||
| 169 | + <view class="legend"> | ||
| 170 | + <view class="legend-item"> | ||
| 171 | + <view class="legend-color primary"></view> | ||
| 172 | + <text class="legend-text">主要部位</text> | ||
| 173 | + </view> | ||
| 174 | + <view class="legend-item"> | ||
| 175 | + <view class="legend-color secondary"></view> | ||
| 176 | + <text class="legend-text">次要部位</text> | ||
| 177 | + </view> | ||
| 178 | + </view> | ||
| 179 | + </view> | ||
| 180 | + </view> | ||
| 181 | + <!-- 2 历史 --> | ||
| 182 | + <view v-if="contentTab === 1" class="tab-pane slide-up"> | ||
| 183 | + <!-- <view class="stat-card highlight"> | ||
| 184 | + <text class="label">最长时长</text> | ||
| 185 | + <text class="value">01:02:03</text> | ||
| 186 | + <text class="date">记录于 2026/03/04</text> | ||
| 187 | + </view> --> | ||
| 188 | + | ||
| 189 | + <!-- <view class="chart-container" v-if="chartData && chartData.series"> | ||
| 190 | + <qiun-data-charts type="line" :opts="chartOpts" :chartData="chartData" /> | ||
| 191 | + </view> --> | ||
| 192 | + | ||
| 193 | + <view class="history-list"> | ||
| 194 | + <template v-if="historyList.length > 0"> | ||
| 195 | + <view class="history-item" v-for="item in historyList" :key="item.id"> | ||
| 196 | + <view class="item-header"> | ||
| 197 | + <text class="date">{{ formatDate(item.date) }}</text> | ||
| 198 | + <text class="tag">{{ item.name }}</text> | ||
| 199 | + </view> | ||
| 200 | + <view class="item-body"> | ||
| 201 | + <view class="count"> | ||
| 202 | + <view>共 {{ item.setCount }} 组训练</view> | ||
| 203 | + <view class="dot">· </view> | ||
| 204 | + <!-- <view class="difficulty">困难</view> --> | ||
| 205 | + <view class="difficulty">{{ | ||
| 206 | + item.weight ? item.weight + 'kg' : '无负重' | ||
| 207 | + }}</view> | ||
| 208 | + </view> | ||
| 209 | + <view class="group-chips"> | ||
| 210 | + <view class="chip" v-for="(set, index) in item.setConfigList" :key="index"> | ||
| 211 | + <view class="idx">{{ index + 1 }}</view> | ||
| 212 | + <view class="group-data"> | ||
| 213 | + {{ formatSetData(set) }} | ||
| 214 | + </view> | ||
| 215 | + </view> | ||
| 216 | + </view> | ||
| 217 | + </view> | ||
| 218 | + </view> | ||
| 219 | + </template> | ||
| 220 | + <template v-else> | ||
| 221 | + <up-empty text="暂无训练历史"> </up-empty> | ||
| 222 | + </template> | ||
| 223 | + </view> | ||
| 224 | + </view> | ||
| 225 | + <!-- 3 平替动作(只有动作组才有) --> | ||
| 226 | + <view v-if="contentTab === 2 && type === 1" class="tab-pane slide-up"> | ||
| 227 | + <view class="substitute-list"> | ||
| 228 | + <view | ||
| 229 | + class="sub-item" | ||
| 230 | + v-for="item in alternativeActions" | ||
| 231 | + :key="item.id" | ||
| 232 | + @click="openActionItem(item)" | ||
| 233 | + > | ||
| 234 | + <image :src="item.urlImage || lostImage" mode="aspectFill" class="img" /> | ||
| 235 | + <view class="sub-info"> | ||
| 236 | + <text class="name">{{ item.name }}</text> | ||
| 237 | + | ||
| 238 | + <text class="meta">练过{{ item.trainingReps }}次</text> | ||
| 239 | + </view> | ||
| 240 | + <up-icon name="arrow-right" color="#666" size="16"></up-icon> | ||
| 241 | + </view> | ||
| 242 | + </view> | ||
| 243 | + </view> | ||
| 244 | + </view> | ||
| 245 | + </view> | ||
| 246 | + <view class="footer"> | ||
| 247 | + <view class="btn" @click="startTraining">开始训练</view> | ||
| 248 | + </view> | ||
| 249 | + </scroll-view> | ||
| 250 | + <!-- 备注弹窗组件 --> | ||
| 251 | + <beizhu ref="showBeizhuRef" @saveSuccess="handleNoteSave" /> | ||
| 252 | + </up-popup> | ||
| 253 | +</template> | ||
| 254 | + | ||
| 255 | +<script setup> | ||
| 256 | + import { onMounted, ref, nextTick } from 'vue'; | ||
| 257 | + import ExercisesApi from '@/sheep/api/motion/exercises'; | ||
| 258 | + import beizhu from '@/pages/xunji/components/beizhu.vue'; | ||
| 259 | + import SupersetsApi from '@/sheep/api/motion/supersets'; | ||
| 260 | + import TrainingApi from '@/sheep/api/Training/traininghistory'; | ||
| 261 | + import { onShareAppMessage } from '@dcloudio/uni-app'; | ||
| 262 | + | ||
| 263 | + // 静态配置 | ||
| 264 | + | ||
| 265 | + const alternativeActions = ref([]); // 平替动作列表接口数据 | ||
| 266 | + | ||
| 267 | + // 响应式状态 | ||
| 268 | + const actionShow = ref(false); | ||
| 269 | + const modeTab = ref(0); | ||
| 270 | + const contentTab = ref(0); | ||
| 271 | + const isFavorite = ref(false); | ||
| 272 | + | ||
| 273 | + // 记录当前动作的详细数据 | ||
| 274 | + const actionDetail = ref({}); | ||
| 275 | + // 记录当前动作的id | ||
| 276 | + const actionId = ref(0); | ||
| 277 | + // 记录是超级组还是动作组 1=动作组,2=超级组 | ||
| 278 | + const type = ref(0); | ||
| 279 | + const showBeizhuRef = ref(null); | ||
| 280 | + const historyList = ref([]); // 训练历史列表接口数据 | ||
| 281 | + const lostImage = | ||
| 282 | + 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png'; | ||
| 283 | + | ||
| 284 | + // 切换图表模式 | ||
| 285 | + const switchModeTab = (index) => { | ||
| 286 | + modeTab.value = index; | ||
| 287 | + }; | ||
| 288 | + | ||
| 289 | + // 跳转到视频播放的页面 | ||
| 290 | + const playVideo = (url) => { | ||
| 291 | + uni.navigateTo({ | ||
| 292 | + url: '/pages4/pages/xunji/xunji-shiping?url=' + url, | ||
| 293 | + }); | ||
| 294 | + }; | ||
| 295 | + // 获取动作收藏状态 | ||
| 296 | + const checkExerciseFavorited = async () => { | ||
| 297 | + try { | ||
| 298 | + const res = await ExercisesApi.checkExerciseFavorited(actionId.value); | ||
| 299 | + isFavorite.value = res.data; | ||
| 300 | + } catch (err) { | ||
| 301 | + console.log(err); | ||
| 302 | + } | ||
| 303 | + }; | ||
| 304 | + // 收藏 | ||
| 305 | + const toggleCollect = async () => { | ||
| 306 | + try { | ||
| 307 | + const status = isFavorite.value ? 0 : 1; | ||
| 308 | + if (type == 1) { | ||
| 309 | + await ExercisesApi.toggleFavorite(actionId.value, status); | ||
| 310 | + } else { | ||
| 311 | + await SupersetsApi.toggleFavorite(actionId.value, status); | ||
| 312 | + } | ||
| 313 | + | ||
| 314 | + isFavorite.value = !isFavorite.value; | ||
| 315 | + } catch (err) { | ||
| 316 | + console.log(err); | ||
| 317 | + } | ||
| 318 | + }; | ||
| 319 | + | ||
| 320 | + // 获取超级组收藏状态 | ||
| 321 | + const checkSupersetFavorited = async () => { | ||
| 322 | + try { | ||
| 323 | + const res = await SupersetsApi.checkSupersetFavorited(actionId.value); | ||
| 324 | + isFavorite.value = res.data; | ||
| 325 | + } catch (err) { | ||
| 326 | + console.log(err); | ||
| 327 | + } | ||
| 328 | + }; | ||
| 329 | + | ||
| 330 | + const startTraining = () => { | ||
| 331 | + uni.navigateTo({ | ||
| 332 | + url: `/pages4/pages/xunji/xunji-dongzuo-lianxi?id=${actionId.value}&type=${type.value}`, | ||
| 333 | + }); | ||
| 334 | + }; | ||
| 335 | + | ||
| 336 | + const open = (id, typeData) => { | ||
| 337 | + actionId.value = Number(id); | ||
| 338 | + | ||
| 339 | + type.value = typeData; | ||
| 340 | + contentTab.value = 0; | ||
| 341 | + // 如何判断是动作还是超级组 | ||
| 342 | + if (typeData == 1) { | ||
| 343 | + loadexercisedetail(actionId.value); | ||
| 344 | + loadAlternativeActions(actionId.value); | ||
| 345 | + checkExerciseFavorited(); | ||
| 346 | + } else { | ||
| 347 | + loadsuperdetail(actionId.value); | ||
| 348 | + checkSupersetFavorited(); | ||
| 349 | + } | ||
| 350 | + | ||
| 351 | + loadTrainHistoryDetail(actionId.value); | ||
| 352 | + actionShow.value = true; | ||
| 353 | + }; | ||
| 354 | + // 打开备注弹窗 | ||
| 355 | + const openBeizhu = () => { | ||
| 356 | + nextTick(() => { | ||
| 357 | + if (showBeizhuRef.value) { | ||
| 358 | + showBeizhuRef.value.open(actionId.value); | ||
| 359 | + } | ||
| 360 | + }); | ||
| 361 | + }; | ||
| 362 | + | ||
| 363 | + // 接收子组件传过来的备注内容 | ||
| 364 | + const handleNoteSave = async (content) => { | ||
| 365 | + try { | ||
| 366 | + if (type.value == 2) { | ||
| 367 | + await SupersetsApi.addNotes({ | ||
| 368 | + supersetsId: actionId.value, | ||
| 369 | + content: content, | ||
| 370 | + }); | ||
| 371 | + } else { | ||
| 372 | + await ExercisesApi.addNotes({ | ||
| 373 | + exerciseId: actionId.value, | ||
| 374 | + content: content, | ||
| 375 | + }); | ||
| 376 | + } | ||
| 377 | + actionDetail.value.userNote = content; | ||
| 378 | + } catch (e) { | ||
| 379 | + console.log(e); | ||
| 380 | + } | ||
| 381 | + }; | ||
| 382 | + | ||
| 383 | + // 启用分享菜单 | ||
| 384 | + // #ifdef MP-WEIXIN | ||
| 385 | + wx.showShareMenu({ | ||
| 386 | + withShareTicket: true, | ||
| 387 | + menus: ['shareAppMessage', 'shareTimeline'], | ||
| 388 | + }); | ||
| 389 | + // #endif | ||
| 390 | + // 定义分享内容 | ||
| 391 | + onShareAppMessage((res) => { | ||
| 392 | + // res.from 可区分触发来源:'button'(按钮触发)或 'menu'(右上角菜单触发)[reference:3] | ||
| 393 | + console.log('分享触发来源:', res.from); | ||
| 394 | + return { | ||
| 395 | + title: actionDetail.value.name || '健身动作分享', // 分享标题 | ||
| 396 | + // path: `/pages4/pages/xunji/xunji-dongzuo-xiangqing?id=${id.value}`, // 分享路径 | ||
| 397 | + path: `/pages/xunji/xunji?currentTab=${3}`, | ||
| 398 | + imageUrl: actionDetail.value.urlImage || lostImage, // 分享图片 | ||
| 399 | + }; | ||
| 400 | + }); | ||
| 401 | + | ||
| 402 | + // 加载单个动作详情 | ||
| 403 | + const loadexercisedetail = async (id) => { | ||
| 404 | + const response = await ExercisesApi.getExerciseById(id); | ||
| 405 | + actionDetail.value = response.data; | ||
| 406 | + if (actionDetail.value.url3dAnimation) { | ||
| 407 | + modeTab.value = 0; | ||
| 408 | + } else if (actionDetail.value.urlRealPerson) { | ||
| 409 | + modeTab.value = 1; | ||
| 410 | + } else { | ||
| 411 | + modeTab.value = 2; | ||
| 412 | + } | ||
| 413 | + }; | ||
| 414 | + | ||
| 415 | + // 加载超级组详情 | ||
| 416 | + const loadsuperdetail = async (id) => { | ||
| 417 | + const response = await SupersetsApi.getSupersetsInfo(id); | ||
| 418 | + actionDetail.value = response.data; | ||
| 419 | + console.log('显示超级组详情:', actionDetail.value); | ||
| 420 | + }; | ||
| 421 | + // 2. 加载平替动作的函数 | ||
| 422 | + const loadAlternativeActions = async (id) => { | ||
| 423 | + if (!id || isNaN(Number(id))) { | ||
| 424 | + console.warn('平替动作id非法,跳过请求:', id); | ||
| 425 | + alternativeActions.value = []; | ||
| 426 | + return; | ||
| 427 | + } | ||
| 428 | + try { | ||
| 429 | + const res = await ExercisesApi.getalternatives(id); | ||
| 430 | + if (res.code === 0 && res.data) { | ||
| 431 | + alternativeActions.value = Array.isArray(res.data) ? res.data : []; | ||
| 432 | + } else { | ||
| 433 | + alternativeActions.value = []; | ||
| 434 | + } | ||
| 435 | + } catch (error) { | ||
| 436 | + console.error('加载平替动作失败:', error); | ||
| 437 | + alternativeActions.value = []; | ||
| 438 | + } | ||
| 439 | + }; | ||
| 440 | + | ||
| 441 | + // 加载训练历史 | ||
| 442 | + const loadTrainHistoryDetail = async (id) => { | ||
| 443 | + try { | ||
| 444 | + const res = await TrainingApi.getTrainHistoryList(id); | ||
| 445 | + historyList.value = res.data; | ||
| 446 | + console.log('训练历史列表接口返回结果historyList.value', historyList.value); | ||
| 447 | + } catch (error) { | ||
| 448 | + console.error('加载训练历史失败:', error); | ||
| 449 | + } | ||
| 450 | + }; | ||
| 451 | + // 格式化时间 | ||
| 452 | + const formatDate = (dateArr) => { | ||
| 453 | + if (!Array.isArray(dateArr) || dateArr.length < 3) return ''; | ||
| 454 | + const [year, month, day] = dateArr; | ||
| 455 | + const date = new Date(year, month - 1, day); | ||
| 456 | + const weekArr = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; | ||
| 457 | + const week = weekArr[date.getDay()]; | ||
| 458 | + return `${year}/${String(month).padStart(2, '0')}/${String(day).padStart(2, '0')} ${week}`; | ||
| 459 | + }; | ||
| 460 | + | ||
| 461 | + // 格式化每组数据:自动拼接 weight/reps/duration/distance | ||
| 462 | + const formatSetData = (set) => { | ||
| 463 | + const parts = []; | ||
| 464 | + | ||
| 465 | + // 重量 | ||
| 466 | + if (set.weight != null && set.weight !== '') { | ||
| 467 | + parts.push(`${set.weight}kg`); | ||
| 468 | + } | ||
| 469 | + | ||
| 470 | + // 次数 | ||
| 471 | + if (set.reps != null && set.reps !== '') { | ||
| 472 | + parts.push(`${set.reps}次`); | ||
| 473 | + } | ||
| 474 | + | ||
| 475 | + // 时长(自动转 时:分:秒) | ||
| 476 | + if (set.duration != null && set.duration !== '') { | ||
| 477 | + parts.push(formatTime(set.duration)); | ||
| 478 | + } | ||
| 479 | + | ||
| 480 | + // 距离 | ||
| 481 | + if (set.distance != null && set.distance !== '') { | ||
| 482 | + parts.push(`${set.distance}m`); | ||
| 483 | + } | ||
| 484 | + | ||
| 485 | + return parts.length > 0 ? parts.join(' × ') : '无数据'; | ||
| 486 | + }; | ||
| 487 | + | ||
| 488 | + // 新增:秒数转 00:00:00 格式 | ||
| 489 | + const formatTime = (seconds) => { | ||
| 490 | + const h = Math.floor(seconds / 3600); | ||
| 491 | + const m = Math.floor((seconds % 3600) / 60); | ||
| 492 | + const s = seconds % 60; | ||
| 493 | + | ||
| 494 | + const hh = h > 0 ? String(h).padStart(2, '0') + ':' : ''; | ||
| 495 | + const mm = String(m).padStart(2, '0') + ':'; | ||
| 496 | + const ss = String(s).padStart(2, '0'); | ||
| 497 | + | ||
| 498 | + return hh + mm + ss; | ||
| 499 | + }; | ||
| 500 | + | ||
| 501 | + | ||
| 502 | + // 点击超级组内部的动作 → 打开动作详情(复用同一个组件) | ||
| 503 | + const openActionItem = (item) => { | ||
| 504 | + open(item.id, 1); | ||
| 505 | + }; | ||
| 506 | + | ||
| 507 | + defineExpose({ open }); | ||
| 508 | + | ||
| 509 | + onMounted(() => {}); | ||
| 510 | +</script> | ||
| 511 | + | ||
| 512 | +<style lang="scss" scoped> | ||
| 513 | + .action-container { | ||
| 514 | + width: 100%; | ||
| 515 | + height: 80vh; | ||
| 516 | + background-color: #1a1a1a; | ||
| 517 | + color: #ffffff; | ||
| 518 | + | ||
| 519 | + .header { | ||
| 520 | + width: 100%; | ||
| 521 | + height: 50vh; | ||
| 522 | + position: relative; | ||
| 523 | + | ||
| 524 | + .explain { | ||
| 525 | + width: 100%; | ||
| 526 | + height: 100%; | ||
| 527 | + | ||
| 528 | + .media-content { | ||
| 529 | + width: 100%; | ||
| 530 | + height: 100%; | ||
| 531 | + } | ||
| 532 | + .video { | ||
| 533 | + width: 100%; | ||
| 534 | + height: 100%; | ||
| 535 | + display: flex; | ||
| 536 | + justify-content: center; | ||
| 537 | + align-items: center; | ||
| 538 | + position: relative; | ||
| 539 | + .play-icon { | ||
| 540 | + width: 50px; | ||
| 541 | + height: 50px; | ||
| 542 | + background-color: rgb(216, 209, 209, 0.8); | ||
| 543 | + display: flex; | ||
| 544 | + justify-content: center; | ||
| 545 | + align-items: center; | ||
| 546 | + border-radius: 50%; | ||
| 547 | + position: absolute; | ||
| 548 | + z-index: 10; | ||
| 549 | + } | ||
| 550 | + } | ||
| 551 | + } | ||
| 552 | + | ||
| 553 | + .mode-tabs { | ||
| 554 | + position: absolute; | ||
| 555 | + bottom: 30rpx; | ||
| 556 | + left: 30rpx; | ||
| 557 | + display: flex; | ||
| 558 | + background: rgba(0, 0, 0, 0.6); | ||
| 559 | + padding: 6rpx; | ||
| 560 | + border-radius: 12rpx; | ||
| 561 | + backdrop-filter: blur(10px); | ||
| 562 | + z-index: 99; | ||
| 563 | + .tab-item { | ||
| 564 | + padding: 8rpx 24rpx; | ||
| 565 | + font-size: 24rpx; | ||
| 566 | + color: #999; | ||
| 567 | + transition: all 0.3s; | ||
| 568 | + | ||
| 569 | + &.active { | ||
| 570 | + background: #444; | ||
| 571 | + color: #fff; | ||
| 572 | + border-radius: 8rpx; | ||
| 573 | + } | ||
| 574 | + } | ||
| 575 | + } | ||
| 576 | + } | ||
| 577 | + | ||
| 578 | + .main { | ||
| 579 | + .main-header { | ||
| 580 | + position: sticky; | ||
| 581 | + top: 0; | ||
| 582 | + z-index: 10; | ||
| 583 | + background: #1a1a1a; | ||
| 584 | + padding: 30rpx 30rpx 0; | ||
| 585 | + | ||
| 586 | + .title-bar { | ||
| 587 | + display: flex; | ||
| 588 | + justify-content: space-between; | ||
| 589 | + align-items: center; | ||
| 590 | + margin-bottom: 30rpx; | ||
| 591 | + | ||
| 592 | + .title { | ||
| 593 | + font-size: 48rpx; | ||
| 594 | + font-weight: bold; | ||
| 595 | + } | ||
| 596 | + | ||
| 597 | + .action-icons { | ||
| 598 | + display: flex; | ||
| 599 | + gap: 30rpx; | ||
| 600 | + | ||
| 601 | + .share-btn { | ||
| 602 | + background: transparent; | ||
| 603 | + border: none; | ||
| 604 | + padding: 0; | ||
| 605 | + margin: 0; | ||
| 606 | + line-height: 1; | ||
| 607 | + } | ||
| 608 | + } | ||
| 609 | + } | ||
| 610 | + | ||
| 611 | + .content-tabs { | ||
| 612 | + display: flex; | ||
| 613 | + gap: 60rpx; | ||
| 614 | + border-bottom: 1rpx solid #333; | ||
| 615 | + | ||
| 616 | + .tab-item { | ||
| 617 | + padding-bottom: 20rpx; | ||
| 618 | + font-size: 30rpx; | ||
| 619 | + color: #666; | ||
| 620 | + position: relative; | ||
| 621 | + | ||
| 622 | + &.active { | ||
| 623 | + color: #fff; | ||
| 624 | + font-weight: 500; | ||
| 625 | + | ||
| 626 | + &::after { | ||
| 627 | + content: ''; | ||
| 628 | + position: absolute; | ||
| 629 | + bottom: 0; | ||
| 630 | + left: 0; | ||
| 631 | + width: 100%; | ||
| 632 | + height: 4rpx; | ||
| 633 | + background: #fedc1f; | ||
| 634 | + border-radius: 2rpx; | ||
| 635 | + } | ||
| 636 | + } | ||
| 637 | + } | ||
| 638 | + } | ||
| 639 | + } | ||
| 640 | + | ||
| 641 | + .main-content { | ||
| 642 | + padding: 40rpx 30rpx 160rpx; | ||
| 643 | + | ||
| 644 | + .section { | ||
| 645 | + margin-bottom: 40rpx; | ||
| 646 | + } | ||
| 647 | + | ||
| 648 | + .section-title { | ||
| 649 | + font-size: 32rpx; | ||
| 650 | + font-weight: bold; | ||
| 651 | + margin-bottom: 24rpx; | ||
| 652 | + color: #ddd; | ||
| 653 | + } | ||
| 654 | + | ||
| 655 | + .action-list { | ||
| 656 | + .action-item { | ||
| 657 | + display: flex; | ||
| 658 | + align-items: center; | ||
| 659 | + justify-content: space-between; | ||
| 660 | + padding: 20rpx; | ||
| 661 | + box-sizing: border-box; | ||
| 662 | + gap: 15rpx; | ||
| 663 | + background-color: #262626; | ||
| 664 | + border-radius: 10rpx; | ||
| 665 | + margin-bottom: 15rpx; | ||
| 666 | + | ||
| 667 | + .img { | ||
| 668 | + width: 120rpx; | ||
| 669 | + height: 120rpx; | ||
| 670 | + border-radius: 5rpx; | ||
| 671 | + } | ||
| 672 | + | ||
| 673 | + .middle { | ||
| 674 | + flex: 1; | ||
| 675 | + height: 120rpx; | ||
| 676 | + | ||
| 677 | + .name { | ||
| 678 | + margin-bottom: 20rpx; | ||
| 679 | + } | ||
| 680 | + | ||
| 681 | + .tips { | ||
| 682 | + display: flex; | ||
| 683 | + gap: 10rpx; | ||
| 684 | + align-items: center; | ||
| 685 | + flex-wrap: wrap; | ||
| 686 | + font-size: 20rpx; | ||
| 687 | + | ||
| 688 | + .tip { | ||
| 689 | + color: #fedc1f; | ||
| 690 | + } | ||
| 691 | + } | ||
| 692 | + } | ||
| 693 | + } | ||
| 694 | + } | ||
| 695 | + | ||
| 696 | + // 要点板块样式 | ||
| 697 | + .video-grid { | ||
| 698 | + display: grid; | ||
| 699 | + grid-template-columns: 1fr 1fr; | ||
| 700 | + gap: 20rpx; | ||
| 701 | + margin-bottom: 40rpx; | ||
| 702 | + | ||
| 703 | + .video-card { | ||
| 704 | + height: 360rpx; | ||
| 705 | + position: relative; | ||
| 706 | + border-radius: 16rpx; | ||
| 707 | + overflow: hidden; | ||
| 708 | + background: #333; | ||
| 709 | + | ||
| 710 | + .video { | ||
| 711 | + width: 100%; | ||
| 712 | + height: 100%; | ||
| 713 | + } | ||
| 714 | + | ||
| 715 | + .play-overlay { | ||
| 716 | + position: absolute; | ||
| 717 | + top: 20rpx; | ||
| 718 | + right: 20rpx; | ||
| 719 | + | ||
| 720 | + pointer-events: none; | ||
| 721 | + } | ||
| 722 | + } | ||
| 723 | + } | ||
| 724 | + | ||
| 725 | + .memo-box { | ||
| 726 | + background: #262626; | ||
| 727 | + padding: 24rpx; | ||
| 728 | + border-radius: 16rpx; | ||
| 729 | + margin-bottom: 40rpx; | ||
| 730 | + | ||
| 731 | + .textarea { | ||
| 732 | + height: 50rpx; | ||
| 733 | + | ||
| 734 | + :deep(.u-textarea--disabled) { | ||
| 735 | + background-color: #262626; | ||
| 736 | + } | ||
| 737 | + } | ||
| 738 | + } | ||
| 739 | + | ||
| 740 | + .steps-list { | ||
| 741 | + display: flex; | ||
| 742 | + flex-direction: column; | ||
| 743 | + gap: 16rpx; | ||
| 744 | + background: #262626; | ||
| 745 | + padding: 20rpx; | ||
| 746 | + box-sizing: border-box; | ||
| 747 | + border-radius: 16rpx; | ||
| 748 | + | ||
| 749 | + .step-text { | ||
| 750 | + font-size: 26rpx; | ||
| 751 | + line-height: 1.6; | ||
| 752 | + color: #bbb; | ||
| 753 | + list-style: none; | ||
| 754 | + } | ||
| 755 | + } | ||
| 756 | + | ||
| 757 | + .muscle-map { | ||
| 758 | + width: 100%; | ||
| 759 | + border-radius: 16rpx; | ||
| 760 | + } | ||
| 761 | + | ||
| 762 | + .legend { | ||
| 763 | + display: flex; | ||
| 764 | + justify-content: flex-end; | ||
| 765 | + align-items: center; | ||
| 766 | + gap: 20rpx; | ||
| 767 | + margin-top: 20rpx; | ||
| 768 | + } | ||
| 769 | + | ||
| 770 | + .legend-item { | ||
| 771 | + display: flex; | ||
| 772 | + align-items: center; | ||
| 773 | + gap: 10rpx; | ||
| 774 | + } | ||
| 775 | + | ||
| 776 | + .legend-color { | ||
| 777 | + width: 16rpx; | ||
| 778 | + height: 16rpx; | ||
| 779 | + border-radius: 4rpx; | ||
| 780 | + } | ||
| 781 | + | ||
| 782 | + .legend-color.primary { | ||
| 783 | + background-color: #ffd700; | ||
| 784 | + } | ||
| 785 | + | ||
| 786 | + .legend-color.secondary { | ||
| 787 | + background-color: #999; | ||
| 788 | + } | ||
| 789 | + | ||
| 790 | + .legend-text { | ||
| 791 | + font-size: 24rpx; | ||
| 792 | + color: #fff; | ||
| 793 | + } | ||
| 794 | + | ||
| 795 | + // 历史记录样式 | ||
| 796 | + .stat-card { | ||
| 797 | + background: linear-gradient(135deg, #333, #222); | ||
| 798 | + padding: 40rpx; | ||
| 799 | + border-radius: 20rpx; | ||
| 800 | + display: flex; | ||
| 801 | + flex-direction: column; | ||
| 802 | + align-items: center; | ||
| 803 | + border: 1rpx solid #444; | ||
| 804 | + | ||
| 805 | + .label { | ||
| 806 | + font-size: 24rpx; | ||
| 807 | + color: #888; | ||
| 808 | + } | ||
| 809 | + | ||
| 810 | + .value { | ||
| 811 | + font-size: 60rpx; | ||
| 812 | + font-weight: bold; | ||
| 813 | + color: #fedc1f; | ||
| 814 | + margin: 10rpx 0; | ||
| 815 | + } | ||
| 816 | + | ||
| 817 | + .date { | ||
| 818 | + font-size: 22rpx; | ||
| 819 | + color: #666; | ||
| 820 | + } | ||
| 821 | + } | ||
| 822 | + | ||
| 823 | + .chart-container { | ||
| 824 | + height: 450rpx; | ||
| 825 | + margin: 40rpx 0; | ||
| 826 | + } | ||
| 827 | + | ||
| 828 | + .history-item { | ||
| 829 | + background: #262626; | ||
| 830 | + border-radius: 16rpx; | ||
| 831 | + padding: 30rpx; | ||
| 832 | + margin-bottom: 20rpx; | ||
| 833 | + | ||
| 834 | + .count { | ||
| 835 | + display: flex; | ||
| 836 | + gap: 10rpx; | ||
| 837 | + } | ||
| 838 | + | ||
| 839 | + .item-header { | ||
| 840 | + display: flex; | ||
| 841 | + flex-direction: column; | ||
| 842 | + justify-content: space-between; | ||
| 843 | + margin-bottom: 20rpx; | ||
| 844 | + | ||
| 845 | + .date { | ||
| 846 | + font-size: 24rpx; | ||
| 847 | + color: #888; | ||
| 848 | + } | ||
| 849 | + | ||
| 850 | + .tag { | ||
| 851 | + // font-size: 20rpx; | ||
| 852 | + // background: #444; | ||
| 853 | + margin: 5rpx 5rpx; | ||
| 854 | + border-radius: 4rpx; | ||
| 855 | + } | ||
| 856 | + } | ||
| 857 | + | ||
| 858 | + .group-chips { | ||
| 859 | + display: flex; | ||
| 860 | + flex-direction: column; | ||
| 861 | + flex-wrap: wrap; | ||
| 862 | + gap: 16rpx; | ||
| 863 | + margin-top: 20rpx; | ||
| 864 | + | ||
| 865 | + .chip { | ||
| 866 | + // background: #333; | ||
| 867 | + padding: 8rpx 20rpx; | ||
| 868 | + font-size: 24rpx; | ||
| 869 | + display: flex; | ||
| 870 | + align-items: center; | ||
| 871 | + gap: 10rpx; | ||
| 872 | + | ||
| 873 | + .idx { | ||
| 874 | + background: #444; | ||
| 875 | + font-weight: bold; | ||
| 876 | + border-radius: 30rpx; | ||
| 877 | + padding: 4rpx 10rpx; | ||
| 878 | + } | ||
| 879 | + } | ||
| 880 | + } | ||
| 881 | + } | ||
| 882 | + | ||
| 883 | + // 平替动作样式 | ||
| 884 | + .sub-item { | ||
| 885 | + display: flex; | ||
| 886 | + align-items: center; | ||
| 887 | + background: #262626; | ||
| 888 | + padding: 24rpx; | ||
| 889 | + border-radius: 16rpx; | ||
| 890 | + margin-bottom: 20rpx; | ||
| 891 | + | ||
| 892 | + .img { | ||
| 893 | + width: 120rpx; | ||
| 894 | + height: 120rpx; | ||
| 895 | + border-radius: 12rpx; | ||
| 896 | + margin-right: 24rpx; | ||
| 897 | + } | ||
| 898 | + | ||
| 899 | + .sub-info { | ||
| 900 | + flex: 1; | ||
| 901 | + | ||
| 902 | + .name { | ||
| 903 | + font-size: 30rpx; | ||
| 904 | + font-weight: 500; | ||
| 905 | + display: block; | ||
| 906 | + } | ||
| 907 | + | ||
| 908 | + .meta { | ||
| 909 | + font-size: 22rpx; | ||
| 910 | + color: #666; | ||
| 911 | + margin-top: 8rpx; | ||
| 912 | + } | ||
| 913 | + } | ||
| 914 | + } | ||
| 915 | + } | ||
| 916 | + } | ||
| 917 | + | ||
| 918 | + .footer { | ||
| 919 | + width: 100%; | ||
| 920 | + display: flex; | ||
| 921 | + justify-content: center; | ||
| 922 | + align-items: center; | ||
| 923 | + position: fixed; | ||
| 924 | + bottom: 0; | ||
| 925 | + height: 120rpx; | ||
| 926 | + background-color: #242424; | ||
| 927 | + z-index: 999; | ||
| 928 | + .btn { | ||
| 929 | + width: 80%; | ||
| 930 | + height: 80rpx; | ||
| 931 | + background-color: #fedc1f; | ||
| 932 | + color: #333; | ||
| 933 | + border-radius: 12rpx; | ||
| 934 | + text-align: center; | ||
| 935 | + line-height: 80rpx; | ||
| 936 | + border-radius: 50rpx; | ||
| 937 | + } | ||
| 938 | + } | ||
| 939 | + } | ||
| 940 | + | ||
| 941 | + // 动画 | ||
| 942 | + .slide-up { | ||
| 943 | + animation: slideUp 0.4s ease-out; | ||
| 944 | + } | ||
| 945 | + | ||
| 946 | + @keyframes slideUp { | ||
| 947 | + from { | ||
| 948 | + opacity: 0; | ||
| 949 | + transform: translateY(20rpx); | ||
| 950 | + } | ||
| 951 | + | ||
| 952 | + to { | ||
| 953 | + opacity: 1; | ||
| 954 | + transform: translateY(0); | ||
| 955 | + } | ||
| 956 | + } | ||
| 957 | +</style> |
| 1 | +<template> | ||
| 2 | + <view class="container"> | ||
| 3 | + <!-- 顶部日期栏 --> | ||
| 4 | + <view class="header-bar"> | ||
| 5 | + <!-- <view class="back-btn" @click="goBack"> | ||
| 6 | + <uni-icons class="back-arrow" type="left" size="24" color="#333"></uni-icons> | ||
| 7 | + </view> --> | ||
| 8 | + <view class="date-wrapper"> | ||
| 9 | + <text class="date-text">{{ displayDate }}</text> | ||
| 10 | + </view> | ||
| 11 | + <button class="date-note-btn" @click.stop="handleDateNote">日期备注</button> | ||
| 12 | + <button class="go-train-btn" @click="handleGoTrain"> | ||
| 13 | + <text class="go-train-text">去训练</text> | ||
| 14 | + <text class="go-train-tag">GO</text> | ||
| 15 | + </button> | ||
| 16 | + </view> | ||
| 17 | + | ||
| 18 | + <!-- 训练内容区域 --> | ||
| 19 | + <view v-if="resdailyData.id" class="training-container"> | ||
| 20 | + <!-- 训练计划头部卡片 --> | ||
| 21 | + <view class="plan-header-card"> | ||
| 22 | + <image class="plan-header-img" | ||
| 23 | + src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png" | ||
| 24 | + mode="aspectFill"></image> | ||
| 25 | + <view class="plan-header-info"> | ||
| 26 | + <text class="plan-title">{{ resdailyData.name || '全能玩家DoubleDelight 05' }}</text> | ||
| 27 | + <text class="plan-meta">{{ resdailyData.exerciseCount }}个动作 · {{ resdailyData.totalSets }}组 · | ||
| 28 | + {{ resdailyData.totalWeight }}kg</text> | ||
| 29 | + </view> | ||
| 30 | + <view class="plan-header-btns"> | ||
| 31 | + <button class="plan-btn more-btn" @click="handlePlanMore">更多</button> | ||
| 32 | + <button class="plan-btn copy-btn" @click="handlePlanCopy">复制到</button> | ||
| 33 | + <button class="plan-btn go-train-btn-small" @click="handleGoTrain"> | ||
| 34 | + <text class="go-train-text-sm">去训练</text> | ||
| 35 | + <text class="go-train-tag-sm">GO</text> | ||
| 36 | + </button> | ||
| 37 | + </view> | ||
| 38 | + </view> | ||
| 39 | + | ||
| 40 | + <!-- 训练动作列表 --> | ||
| 41 | + <view class="action-list"> | ||
| 42 | + <view class="action-item" v-for="(item, index) in resdailyData.actionList" :key="index"> | ||
| 43 | + <text class="action-index">{{ index + 1 }}</text> | ||
| 44 | + <image class="action-img" :src="item.img || | ||
| 45 | + 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png' | ||
| 46 | + " mode="aspectFill"></image> | ||
| 47 | + <view class="action-info"> | ||
| 48 | + <text class="action-name">{{ item.name }}</text> | ||
| 49 | + <view class="action-sets"> | ||
| 50 | + <view class="set-item" v-for="(set, setIdx) in item.sets" :key="setIdx"> | ||
| 51 | + <text class="set-index">{{ setIdx + 1 }}</text> | ||
| 52 | + <text class="set-content">{{ set.content }}</text> | ||
| 53 | + <text class="rest-time" v-if="set.restTime">{{ set.restTime }}</text> | ||
| 54 | + </view> | ||
| 55 | + </view> | ||
| 56 | + </view> | ||
| 57 | + <button class="modify-btn" @click="handleModifyAction(item, index)">修改</button> | ||
| 58 | + </view> | ||
| 59 | + </view> | ||
| 60 | + </view> | ||
| 61 | + | ||
| 62 | + <!-- 空状态区域 --> | ||
| 63 | + <view v-else class="empty-section"> | ||
| 64 | + <image class="empty-img" | ||
| 65 | + src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/empty-calendar.png" | ||
| 66 | + mode="aspectFit"></image> | ||
| 67 | + <text class="empty-tip">今天没有安排</text> | ||
| 68 | + <button class="add-train-btn" @click="openAddTrainPopup">+ 添加自助训练</button> | ||
| 69 | + | ||
| 70 | + <!-- 底部课程类型卡片 --> | ||
| 71 | + <view class="course-section"> | ||
| 72 | + <view class="course-item" v-for="(item, index) in courseList" :key="index" | ||
| 73 | + @click="handleCourseClick(item.type)"> | ||
| 74 | + <image class="course-icon" :src="item.icon" mode="aspectFill"></image> | ||
| 75 | + <text class="course-title">{{ item.title }}</text> | ||
| 76 | + <text class="course-desc">{{ item.desc }}</text> | ||
| 77 | + </view> | ||
| 78 | + </view> | ||
| 79 | + </view> | ||
| 80 | + | ||
| 81 | + <!-- 添加自助训练底部弹窗 --> | ||
| 82 | + <view v-if="showAddTrainPopup" class="popup-overlay" @click="closeAddTrainPopup"> | ||
| 83 | + <view class="popup-bottom" @click.stop> | ||
| 84 | + <!-- 顶部拖动条 --> | ||
| 85 | + <view class="popup-indicator"></view> | ||
| 86 | + <text class="popup-title">添加自助训练</text> | ||
| 87 | + <view class="popup-options"> | ||
| 88 | + <button class="popup-option-btn" @click="handleAddFromPlan"> | ||
| 89 | + <text>从训练计划中添加</text> | ||
| 90 | + </button> | ||
| 91 | + <button class="popup-option-btn" @click="handleAddFromTemplate"> | ||
| 92 | + <text>使用训练模板</text> | ||
| 93 | + </button> | ||
| 94 | + <button class="popup-option-btn" @click="handleFreeTraining"> | ||
| 95 | + <text>自由训练</text> | ||
| 96 | + </button> | ||
| 97 | + </view> | ||
| 98 | + </view> | ||
| 99 | + </view> | ||
| 100 | + | ||
| 101 | + <!-- 日期备注弹窗 --> | ||
| 102 | + <RiliRiqibeizhu v-model:visible="showRiqibeizhu" /> | ||
| 103 | + </view> | ||
| 104 | +</template> | ||
| 105 | + | ||
| 106 | +<script setup> | ||
| 107 | +import { ref, onMounted, watch } from 'vue'; | ||
| 108 | +import dailytemplateApi from '@/sheep/api/Template/Dailytemplate'; | ||
| 109 | +import RiliRiqibeizhu from '@/pages/xunji/components/rili-components/rili-riqibeizhu.vue' | ||
| 110 | + | ||
| 111 | +const props = defineProps({ | ||
| 112 | + date: { | ||
| 113 | + type: String, | ||
| 114 | + default: '' | ||
| 115 | + } | ||
| 116 | +}) | ||
| 117 | + | ||
| 118 | +const showAddTrainPopup = ref(false); | ||
| 119 | +const selectedDate = ref(''); | ||
| 120 | +const displayDate = ref(''); | ||
| 121 | +const resdailyData = ref({ | ||
| 122 | + id: '', | ||
| 123 | + name: '', | ||
| 124 | + exerciseCount: 0, | ||
| 125 | + totalSets: 0, | ||
| 126 | + totalWeight: 0, | ||
| 127 | + actionList: [] | ||
| 128 | +}); | ||
| 129 | + | ||
| 130 | +const showRiqibeizhu = ref(false) | ||
| 131 | + | ||
| 132 | +// 模拟接口返回的 data 数据 | ||
| 133 | +const mockTemplateData = [ | ||
| 134 | + { | ||
| 135 | + id: 10001, | ||
| 136 | + name: "上肢力量基础训练模板", | ||
| 137 | + urlCover: "https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png", | ||
| 138 | + exerciseCount: 5, | ||
| 139 | + totalSets: 15, | ||
| 140 | + totalWeight: 625, | ||
| 141 | + units: [ | ||
| 142 | + { | ||
| 143 | + unitId: 20001, | ||
| 144 | + unitName: "胸肌训练单元", | ||
| 145 | + unitType: 1, | ||
| 146 | + setCount: 3, | ||
| 147 | + sortOrder: 1, | ||
| 148 | + supersetId: null, | ||
| 149 | + exercises: [ | ||
| 150 | + { | ||
| 151 | + unitId: 20001, | ||
| 152 | + exerciseId: 30001, | ||
| 153 | + exerciseType: 1, | ||
| 154 | + setCount: 3, | ||
| 155 | + weight: 50, | ||
| 156 | + reps: 12, | ||
| 157 | + distance: null, | ||
| 158 | + duration: null, | ||
| 159 | + restTime: 60, | ||
| 160 | + innerOrder: 1, | ||
| 161 | + exerciseName: "杠铃卧推", | ||
| 162 | + exerciseCover: "https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png", | ||
| 163 | + urlImage: "https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/order-empty_1773628059920.png", | ||
| 164 | + sets: [ | ||
| 165 | + { | ||
| 166 | + setIndex: 1, | ||
| 167 | + weight: 40, | ||
| 168 | + reps: 12, | ||
| 169 | + duration: null, | ||
| 170 | + distance: null, | ||
| 171 | + restTime: 60 | ||
| 172 | + }, | ||
| 173 | + { | ||
| 174 | + setIndex: 2, | ||
| 175 | + weight: 50, | ||
| 176 | + reps: 10, | ||
| 177 | + duration: null, | ||
| 178 | + distance: null, | ||
| 179 | + restTime: 60 | ||
| 180 | + }, | ||
| 181 | + { | ||
| 182 | + setIndex: 3, | ||
| 183 | + weight: 55, | ||
| 184 | + reps: 8, | ||
| 185 | + duration: null, | ||
| 186 | + distance: null, | ||
| 187 | + restTime: 90 | ||
| 188 | + } | ||
| 189 | + ] | ||
| 190 | + } | ||
| 191 | + ] | ||
| 192 | + } | ||
| 193 | + ] | ||
| 194 | + } | ||
| 195 | +] | ||
| 196 | + | ||
| 197 | +// 没有训练模板时推荐课程 | ||
| 198 | +const courseList = ref([ | ||
| 199 | + { | ||
| 200 | + type: 'group', | ||
| 201 | + title: '团课', | ||
| 202 | + desc: '大家一起练', | ||
| 203 | + icon: 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/group-course.png' | ||
| 204 | + }, | ||
| 205 | + { | ||
| 206 | + type: 'private', | ||
| 207 | + title: '私教', | ||
| 208 | + desc: '1对1专人指导', | ||
| 209 | + icon: 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/private-course.png' | ||
| 210 | + }, | ||
| 211 | + { | ||
| 212 | + type: 'smallClass', | ||
| 213 | + title: '小班课', | ||
| 214 | + desc: '28天极速瘦身', | ||
| 215 | + icon: 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260316/small-class.png' | ||
| 216 | + } | ||
| 217 | +]); | ||
| 218 | + | ||
| 219 | +// 把模拟数据 转成 页面能渲染的数据 | ||
| 220 | +const formatTemplateData = (data) => { | ||
| 221 | + if (!data) return { actionList: [] }; | ||
| 222 | + // 模板的动作列表 | ||
| 223 | + const actionList = []; | ||
| 224 | + // 循环 unit | ||
| 225 | + (data.units || []).forEach(unit => { | ||
| 226 | + // 循环每个动作 | ||
| 227 | + (unit.exercises || []).forEach(ex => { | ||
| 228 | + actionList.push({ | ||
| 229 | + name: ex.exerciseName, | ||
| 230 | + img: ex.exerciseCover, | ||
| 231 | + sets: ex.sets.map(s => ({ | ||
| 232 | + content: `${s.weight}kg × ${s.reps}次`, | ||
| 233 | + restTime: s.restTime ? `${s.restTime}s` : '' | ||
| 234 | + })) | ||
| 235 | + }); | ||
| 236 | + }); | ||
| 237 | + }); | ||
| 238 | + return { | ||
| 239 | + id: data.id, | ||
| 240 | + name: data.name, | ||
| 241 | + exerciseCount: data.exerciseCount, | ||
| 242 | + totalSets: data.totalSets, | ||
| 243 | + totalWeight: data.totalWeight, | ||
| 244 | + actionList | ||
| 245 | + }; | ||
| 246 | +}; | ||
| 247 | + | ||
| 248 | +// 加载每日训练模板数据 | ||
| 249 | +const loaddailytemplate = async () => { | ||
| 250 | + if (!selectedDate.value) return; // 没有日期就不请求 | ||
| 251 | + console.log('【子组件】开始加载数据...'); | ||
| 252 | + try { | ||
| 253 | + // const resdaily = await dailytemplateApi.getdailytemplate(String(selectedDate.value)); | ||
| 254 | + // resdailyData.value = resdaily.data || {}; | ||
| 255 | + resdailyData.value = formatTemplateData(mockTemplateData[0]); | ||
| 256 | + console.log('【子组件】加载完成,resdailyData:', resdailyData.value); | ||
| 257 | + console.log('打印resdailyData', resdailyData.value) | ||
| 258 | + } catch (error) { | ||
| 259 | + console.error('加载训练模板失败:', error); | ||
| 260 | + resdailyData.value = {}; | ||
| 261 | + } | ||
| 262 | +}; | ||
| 263 | + | ||
| 264 | +// 弹窗控制 | ||
| 265 | +const openAddTrainPopup = () => { | ||
| 266 | + showAddTrainPopup.value = true; | ||
| 267 | +}; | ||
| 268 | +const closeAddTrainPopup = () => { | ||
| 269 | + showAddTrainPopup.value = false; | ||
| 270 | +}; | ||
| 271 | + | ||
| 272 | +// 弹窗选项点击事件 | ||
| 273 | +const handleAddFromPlan = () => { | ||
| 274 | + uni.showToast({ title: '从训练计划添加', icon: 'none' }); | ||
| 275 | + closeAddTrainPopup(); | ||
| 276 | +}; | ||
| 277 | +const handleAddFromTemplate = () => { | ||
| 278 | + uni.navigateTo({ url: '/pages4/pages/xunji/xunji-rili-tianjia-moban' }); | ||
| 279 | + closeAddTrainPopup(); | ||
| 280 | +}; | ||
| 281 | +const handleFreeTraining = () => { | ||
| 282 | + uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-lianxi' }); | ||
| 283 | + closeAddTrainPopup(); | ||
| 284 | +}; | ||
| 285 | + | ||
| 286 | +// 返回按钮逻辑 | ||
| 287 | +const goBack = () => { | ||
| 288 | + uni.navigateBack({ | ||
| 289 | + delta: 1, | ||
| 290 | + fail: () => { | ||
| 291 | + uni.redirectTo({ url: '/pages/xunji/xunji-rili' }); | ||
| 292 | + }, | ||
| 293 | + }); | ||
| 294 | +}; | ||
| 295 | + | ||
| 296 | +// 日期格式化(兼容iOS) | ||
| 297 | +const formatDateWithWeek = (dateStr) => { | ||
| 298 | + if (!dateStr) return ''; | ||
| 299 | + const weekMap = ['日', '一', '二', '三', '四', '五', '六']; | ||
| 300 | + const iosSafeDate = new Date(dateStr.replace(/-/g, '/')); | ||
| 301 | + const year = iosSafeDate.getFullYear(); | ||
| 302 | + const month = String(iosSafeDate.getMonth() + 1).padStart(2, '0'); | ||
| 303 | + const day = String(iosSafeDate.getDate()).padStart(2, '0'); | ||
| 304 | + const week = weekMap[iosSafeDate.getDay()]; | ||
| 305 | + return `${year}/${month}/${day} 周${week}`; | ||
| 306 | +}; | ||
| 307 | + | ||
| 308 | +// 打开日期备注组件 | ||
| 309 | +const handleDateNote = () => { | ||
| 310 | + showRiqibeizhu.value = true | ||
| 311 | +} | ||
| 312 | + | ||
| 313 | +// 监听父组件传进来的 date 变化 | ||
| 314 | +// 子组件 script setup 中替换 watch | ||
| 315 | +watch( | ||
| 316 | + () => props.date, | ||
| 317 | + (newDate) => { | ||
| 318 | + if (newDate && newDate.trim()) { | ||
| 319 | + console.log('子组件监听到日期变化:', newDate); | ||
| 320 | + selectedDate.value = newDate; | ||
| 321 | + displayDate.value = formatDateWithWeek(newDate); | ||
| 322 | + loaddailytemplate(); | ||
| 323 | + } | ||
| 324 | + }, | ||
| 325 | + { immediate: true } | ||
| 326 | +); | ||
| 327 | + | ||
| 328 | +onMounted(() => { | ||
| 329 | + console.log('【子组件】已挂载!!!'); | ||
| 330 | + console.log('【子组件】props.date:', props.date); | ||
| 331 | +}); | ||
| 332 | +</script> | ||
| 333 | + | ||
| 334 | +<style scoped> | ||
| 335 | +/* 全局容器 */ | ||
| 336 | +.container { | ||
| 337 | + width: 100vw; | ||
| 338 | + min-height: 85vh; | ||
| 339 | + background-color: #f7f8fa; | ||
| 340 | + box-sizing: border-box; | ||
| 341 | + margin: 0; | ||
| 342 | + padding: 0; | ||
| 343 | + overflow-x: hidden; | ||
| 344 | +} | ||
| 345 | + | ||
| 346 | +/* 顶部日期栏 */ | ||
| 347 | +.header-bar { | ||
| 348 | + display: flex; | ||
| 349 | + align-items: center; | ||
| 350 | + justify-content: space-between; | ||
| 351 | + padding: 24rpx 20rpx; | ||
| 352 | + background-color: #fff; | ||
| 353 | + position: sticky; | ||
| 354 | + top: 0; | ||
| 355 | + z-index: 10; | ||
| 356 | + gap: 16rpx; | ||
| 357 | +} | ||
| 358 | + | ||
| 359 | +.back-btn { | ||
| 360 | + width: 60rpx; | ||
| 361 | + height: 60rpx; | ||
| 362 | + display: flex; | ||
| 363 | + align-items: center; | ||
| 364 | + justify-content: center; | ||
| 365 | + flex-shrink: 0; | ||
| 366 | +} | ||
| 367 | + | ||
| 368 | +.date-wrapper { | ||
| 369 | + flex-grow: 1; | ||
| 370 | + text-align: left; | ||
| 371 | +} | ||
| 372 | + | ||
| 373 | +.date-text { | ||
| 374 | + font-size: 28rpx; | ||
| 375 | +} | ||
| 376 | + | ||
| 377 | +.date-note-btn { | ||
| 378 | + font-size: 28rpx; | ||
| 379 | + color: #333; | ||
| 380 | + background: #f2f3f5; | ||
| 381 | + border-radius: 40rpx; | ||
| 382 | + padding: 12rpx 28rpx; | ||
| 383 | + border: none; | ||
| 384 | + margin-right: 0; | ||
| 385 | + flex-shrink: 0; | ||
| 386 | +} | ||
| 387 | + | ||
| 388 | +.go-train-btn { | ||
| 389 | + display: flex; | ||
| 390 | + align-items: center; | ||
| 391 | + background: #ffc107; | ||
| 392 | + border-radius: 40rpx; | ||
| 393 | + padding: 12rpx 24rpx; | ||
| 394 | + border: none; | ||
| 395 | + flex-shrink: 0; | ||
| 396 | +} | ||
| 397 | + | ||
| 398 | +.go-train-text { | ||
| 399 | + font-size: 28rpx; | ||
| 400 | + color: #000; | ||
| 401 | + margin-right: 8rpx; | ||
| 402 | + font-weight: 500; | ||
| 403 | +} | ||
| 404 | + | ||
| 405 | +.go-train-tag { | ||
| 406 | + font-size: 22rpx; | ||
| 407 | + color: #ffc107; | ||
| 408 | + background: #000; | ||
| 409 | + border-radius: 50%; | ||
| 410 | + padding: 4rpx 8rpx; | ||
| 411 | + font-weight: bold; | ||
| 412 | +} | ||
| 413 | + | ||
| 414 | +/* 训练计划头部卡片 */ | ||
| 415 | +.plan-header-card { | ||
| 416 | + display: flex; | ||
| 417 | + flex-direction: column; | ||
| 418 | + background-color: #2a2a2a; | ||
| 419 | + color: #fff; | ||
| 420 | + border-radius: 16rpx; | ||
| 421 | + padding: 32rpx 24rpx; | ||
| 422 | + margin: 20rpx; | ||
| 423 | + margin-bottom: 24rpx; | ||
| 424 | +} | ||
| 425 | + | ||
| 426 | +.plan-header-img { | ||
| 427 | + display: none; | ||
| 428 | +} | ||
| 429 | + | ||
| 430 | +.plan-header-info { | ||
| 431 | + flex-grow: 1; | ||
| 432 | + margin-bottom: 32rpx; | ||
| 433 | +} | ||
| 434 | + | ||
| 435 | +.plan-title { | ||
| 436 | + font-size: 36rpx; | ||
| 437 | + font-weight: bold; | ||
| 438 | + display: block; | ||
| 439 | + margin-bottom: 12rpx; | ||
| 440 | + color: #fff; | ||
| 441 | +} | ||
| 442 | + | ||
| 443 | +.plan-meta { | ||
| 444 | + font-size: 26rpx; | ||
| 445 | + color: #ccc; | ||
| 446 | + display: block; | ||
| 447 | +} | ||
| 448 | + | ||
| 449 | +.plan-header-btns { | ||
| 450 | + display: flex; | ||
| 451 | + flex-direction: row; | ||
| 452 | + align-items: center; | ||
| 453 | + gap: 20rpx; | ||
| 454 | + justify-content: flex-start; | ||
| 455 | +} | ||
| 456 | + | ||
| 457 | +.plan-btn { | ||
| 458 | + font-size: 26rpx; | ||
| 459 | + border-radius: 40rpx; | ||
| 460 | + border: none; | ||
| 461 | + padding: 12rpx 28rpx; | ||
| 462 | + line-height: 1; | ||
| 463 | +} | ||
| 464 | + | ||
| 465 | +.more-btn, | ||
| 466 | +.copy-btn { | ||
| 467 | + background: #fff; | ||
| 468 | + color: #000; | ||
| 469 | + border: none; | ||
| 470 | +} | ||
| 471 | + | ||
| 472 | +.go-train-btn-small { | ||
| 473 | + background: #ffc107; | ||
| 474 | + color: #000; | ||
| 475 | + display: flex; | ||
| 476 | + align-items: center; | ||
| 477 | + padding: 12rpx 20rpx; | ||
| 478 | +} | ||
| 479 | + | ||
| 480 | +.go-train-text-sm { | ||
| 481 | + font-size: 24rpx; | ||
| 482 | + margin-right: 4rpx; | ||
| 483 | + font-weight: 500; | ||
| 484 | +} | ||
| 485 | + | ||
| 486 | +.go-train-tag-sm { | ||
| 487 | + font-size: 20rpx; | ||
| 488 | + background: #000; | ||
| 489 | + color: #ffc107; | ||
| 490 | + border-radius: 4rpx; | ||
| 491 | + padding: 2rpx 6rpx; | ||
| 492 | + font-weight: bold; | ||
| 493 | +} | ||
| 494 | + | ||
| 495 | +/* 训练动作列表 */ | ||
| 496 | +.action-list { | ||
| 497 | + display: flex; | ||
| 498 | + flex-direction: column; | ||
| 499 | + gap: 16rpx; | ||
| 500 | + padding: 0 20rpx; | ||
| 501 | +} | ||
| 502 | + | ||
| 503 | +.action-item { | ||
| 504 | + display: flex; | ||
| 505 | + align-items: flex-start; | ||
| 506 | + background-color: #fff; | ||
| 507 | + border-radius: 12rpx; | ||
| 508 | + padding: 24rpx 20rpx; | ||
| 509 | + position: relative; | ||
| 510 | + gap: 16rpx; | ||
| 511 | +} | ||
| 512 | + | ||
| 513 | +.action-index { | ||
| 514 | + font-size: 34rpx; | ||
| 515 | + color: #333; | ||
| 516 | + font-weight: 600; | ||
| 517 | + line-height: 80rpx; | ||
| 518 | + width: 40rpx; | ||
| 519 | + text-align: center; | ||
| 520 | + flex-shrink: 0; | ||
| 521 | +} | ||
| 522 | + | ||
| 523 | +.action-img { | ||
| 524 | + width: 80rpx; | ||
| 525 | + height: 80rpx; | ||
| 526 | + border-radius: 8rpx; | ||
| 527 | + flex-shrink: 0; | ||
| 528 | +} | ||
| 529 | + | ||
| 530 | +.action-info { | ||
| 531 | + flex-grow: 1; | ||
| 532 | + display: flex; | ||
| 533 | + flex-direction: column; | ||
| 534 | + justify-content: center; | ||
| 535 | +} | ||
| 536 | + | ||
| 537 | +.action-name { | ||
| 538 | + font-size: 32rpx; | ||
| 539 | + color: #111; | ||
| 540 | + font-weight: 500; | ||
| 541 | + display: block; | ||
| 542 | + margin-bottom: 8rpx; | ||
| 543 | +} | ||
| 544 | + | ||
| 545 | +.action-sets { | ||
| 546 | + display: flex; | ||
| 547 | + flex-direction: column; | ||
| 548 | + gap: 12rpx; | ||
| 549 | +} | ||
| 550 | + | ||
| 551 | +.set-item { | ||
| 552 | + display: flex; | ||
| 553 | + align-items: center; | ||
| 554 | + justify-content: space-between; | ||
| 555 | + font-size: 26rpx; | ||
| 556 | + color: #666; | ||
| 557 | + padding-left: 8rpx; | ||
| 558 | +} | ||
| 559 | + | ||
| 560 | +.set-index { | ||
| 561 | + display: none; | ||
| 562 | +} | ||
| 563 | + | ||
| 564 | +.set-content { | ||
| 565 | + flex-grow: 1; | ||
| 566 | + color: #333; | ||
| 567 | +} | ||
| 568 | + | ||
| 569 | +.set-content::before { | ||
| 570 | + content: "热"; | ||
| 571 | + display: inline-block; | ||
| 572 | + font-size: 22rpx; | ||
| 573 | + color: #999; | ||
| 574 | + background: #f2f3f5; | ||
| 575 | + border-radius: 50%; | ||
| 576 | + width: 32rpx; | ||
| 577 | + height: 32rpx; | ||
| 578 | + line-height: 32rpx; | ||
| 579 | + text-align: center; | ||
| 580 | + margin-right: 12rpx; | ||
| 581 | +} | ||
| 582 | + | ||
| 583 | +.rest-time { | ||
| 584 | + font-size: 24rpx; | ||
| 585 | + color: #999; | ||
| 586 | + margin-left: 16rpx; | ||
| 587 | + flex-shrink: 0; | ||
| 588 | +} | ||
| 589 | + | ||
| 590 | +.modify-btn { | ||
| 591 | + position: absolute; | ||
| 592 | + top: 24rpx; | ||
| 593 | + right: 20rpx; | ||
| 594 | + font-size: 26rpx; | ||
| 595 | + color: #333; | ||
| 596 | + background: #f2f3f5; | ||
| 597 | + border: none; | ||
| 598 | + border-radius: 40rpx; | ||
| 599 | + padding: 8rpx 20rpx; | ||
| 600 | + line-height: 1; | ||
| 601 | +} | ||
| 602 | + | ||
| 603 | +/* 空状态区域 */ | ||
| 604 | +.empty-section { | ||
| 605 | + display: flex; | ||
| 606 | + flex-direction: column; | ||
| 607 | + align-items: center; | ||
| 608 | + padding: 100rpx 20rpx 60rpx; | ||
| 609 | +} | ||
| 610 | + | ||
| 611 | +.empty-img { | ||
| 612 | + width: 280rpx; | ||
| 613 | + height: 280rpx; | ||
| 614 | + margin-bottom: 30rpx; | ||
| 615 | +} | ||
| 616 | + | ||
| 617 | +.empty-tip { | ||
| 618 | + font-size: 32rpx; | ||
| 619 | + color: #666; | ||
| 620 | + margin-bottom: 60rpx; | ||
| 621 | +} | ||
| 622 | + | ||
| 623 | +.add-train-btn { | ||
| 624 | + font-size: 30rpx; | ||
| 625 | + color: #000; | ||
| 626 | + background: #ffc107; | ||
| 627 | + border-radius: 40rpx; | ||
| 628 | + padding: 20rpx 80rpx; | ||
| 629 | + border: none; | ||
| 630 | + margin-bottom: 60rpx; | ||
| 631 | +} | ||
| 632 | + | ||
| 633 | +/* 底部课程类型区域 */ | ||
| 634 | +.course-section { | ||
| 635 | + display: flex; | ||
| 636 | + justify-content: space-around; | ||
| 637 | + padding: 0 20rpx; | ||
| 638 | + width: 100%; | ||
| 639 | + box-sizing: border-box; | ||
| 640 | +} | ||
| 641 | + | ||
| 642 | +.course-item { | ||
| 643 | + display: flex; | ||
| 644 | + flex-direction: column; | ||
| 645 | + align-items: center; | ||
| 646 | + width: 220rpx; | ||
| 647 | + background: #fff; | ||
| 648 | + border-radius: 12rpx; | ||
| 649 | + padding: 30rpx 0; | ||
| 650 | +} | ||
| 651 | + | ||
| 652 | +.course-icon { | ||
| 653 | + width: 100rpx; | ||
| 654 | + height: 100rpx; | ||
| 655 | + border-radius: 50%; | ||
| 656 | + margin-bottom: 16rpx; | ||
| 657 | +} | ||
| 658 | + | ||
| 659 | +.course-title { | ||
| 660 | + font-size: 28rpx; | ||
| 661 | + color: #333; | ||
| 662 | + margin-bottom: 8rpx; | ||
| 663 | + font-weight: 500; | ||
| 664 | +} | ||
| 665 | + | ||
| 666 | +.course-desc { | ||
| 667 | + font-size: 24rpx; | ||
| 668 | + color: #999; | ||
| 669 | +} | ||
| 670 | + | ||
| 671 | +/* 添加自助训练底部弹窗 */ | ||
| 672 | +.popup-overlay { | ||
| 673 | + position: fixed; | ||
| 674 | + top: 0; | ||
| 675 | + left: 0; | ||
| 676 | + width: 100%; | ||
| 677 | + height: 100%; | ||
| 678 | + background: rgba(0, 0, 0, 0.4); | ||
| 679 | + z-index: 9999; | ||
| 680 | + display: flex; | ||
| 681 | + align-items: flex-end; | ||
| 682 | +} | ||
| 683 | + | ||
| 684 | +.popup-bottom { | ||
| 685 | + width: 100%; | ||
| 686 | + background: #fff; | ||
| 687 | + border-radius: 24rpx 24rpx 0 0; | ||
| 688 | + padding: 30rpx; | ||
| 689 | + box-sizing: border-box; | ||
| 690 | +} | ||
| 691 | + | ||
| 692 | +.popup-indicator { | ||
| 693 | + width: 80rpx; | ||
| 694 | + height: 8rpx; | ||
| 695 | + background: #ddd; | ||
| 696 | + border-radius: 4rpx; | ||
| 697 | + margin: 0 auto 30rpx; | ||
| 698 | +} | ||
| 699 | + | ||
| 700 | +.popup-title { | ||
| 701 | + font-size: 34rpx; | ||
| 702 | + color: #333; | ||
| 703 | + text-align: center; | ||
| 704 | + font-weight: bold; | ||
| 705 | + display: block; | ||
| 706 | + margin-bottom: 30rpx; | ||
| 707 | +} | ||
| 708 | + | ||
| 709 | +.popup-options { | ||
| 710 | + display: flex; | ||
| 711 | + flex-direction: column; | ||
| 712 | + gap: 20rpx; | ||
| 713 | +} | ||
| 714 | + | ||
| 715 | +.popup-option-btn { | ||
| 716 | + width: 100%; | ||
| 717 | + height: 80rpx; | ||
| 718 | + font-size: 30rpx; | ||
| 719 | + color: #333; | ||
| 720 | + background: #f5f5f5; | ||
| 721 | + border: none; | ||
| 722 | + border-radius: 12rpx; | ||
| 723 | + display: flex; | ||
| 724 | + align-items: center; | ||
| 725 | + justify-content: center; | ||
| 726 | +} | ||
| 727 | + | ||
| 728 | +/* 按钮通用重置 */ | ||
| 729 | +button { | ||
| 730 | + line-height: 1; | ||
| 731 | +} | ||
| 732 | + | ||
| 733 | +button::after { | ||
| 734 | + border: none; | ||
| 735 | +} | ||
| 736 | +</style> |
| 1 | +<template> | ||
| 2 | + <u-popup :show="visible" mode="bottom" :round="24" :closeable="false" :custom-style="{ height: '80vh' }" | ||
| 3 | + @close="handleClose"> | ||
| 4 | + <view class="date-note-popup"> | ||
| 5 | + <!-- 顶部导航栏 --> | ||
| 6 | + <view class="popup-header"> | ||
| 7 | + <view class="close-btn" @click="handleClose"> | ||
| 8 | + <text class="close-icon">×</text> | ||
| 9 | + </view> | ||
| 10 | + <text class="popup-title">新增日程备注</text> | ||
| 11 | + <button class="save-btn" @click="handleSave">保存</button> | ||
| 12 | + </view> | ||
| 13 | + | ||
| 14 | + <!-- 颜色选择区 --> | ||
| 15 | + <view class="color-section"> | ||
| 16 | + <text class="section-title">选择显示颜色</text> | ||
| 17 | + <view class="color-list"> | ||
| 18 | + <view v-for="(color, index) in colorList" :key="index" class="color-item" | ||
| 19 | + :class="{ active: selectedColorIndex === index }" :style="{ backgroundColor: color }" | ||
| 20 | + @click="selectedColorIndex = index" /> | ||
| 21 | + </view> | ||
| 22 | + </view> | ||
| 23 | + | ||
| 24 | + <!-- 输入框区域 --> | ||
| 25 | + <view class="input-section"> | ||
| 26 | + <text class="section-title">日程标题</text> | ||
| 27 | + <textarea v-model="noteContent" class="note-textarea" placeholder="输入当天的日程情况,如休息日、生病受伤了、经期等" | ||
| 28 | + placeholder-class="placeholder" /> | ||
| 29 | + </view> | ||
| 30 | + | ||
| 31 | + <!-- 历史备注区域 --> | ||
| 32 | + <view class="history-section"> | ||
| 33 | + <view class="history-header"> | ||
| 34 | + <text class="section-title history-title">历史备注</text> | ||
| 35 | + <text class="history-tip">点击填充到文本内容</text> | ||
| 36 | + </view> | ||
| 37 | + <view class="history-tags"> | ||
| 38 | + <view v-for="(tag, index) in historyTags" :key="index" class="tag-item" | ||
| 39 | + :style="{ backgroundColor: tag.color }" @click="fillNote(tag.text)"> | ||
| 40 | + <text class="tag-text">+{{ tag.text }}</text> | ||
| 41 | + </view> | ||
| 42 | + </view> | ||
| 43 | + </view> | ||
| 44 | + </view> | ||
| 45 | + </u-popup> | ||
| 46 | +</template> | ||
| 47 | + | ||
| 48 | +<script setup> | ||
| 49 | +import { ref } from 'vue'; | ||
| 50 | + | ||
| 51 | +const props = defineProps({ | ||
| 52 | + visible: { | ||
| 53 | + type: Boolean, | ||
| 54 | + default: false | ||
| 55 | + } | ||
| 56 | +}); | ||
| 57 | + | ||
| 58 | +const emit = defineEmits(['update:visible', 'save', 'close']); | ||
| 59 | + | ||
| 60 | +// 响应式数据 | ||
| 61 | +const noteContent = ref(''); | ||
| 62 | +const selectedColorIndex = ref(0); | ||
| 63 | + | ||
| 64 | +// 颜色列表(和截图一致) | ||
| 65 | +const colorList = ref([ | ||
| 66 | + '#ff9944', | ||
| 67 | + '#ff7766', | ||
| 68 | + '#ddaaff', | ||
| 69 | + '#aabbff', | ||
| 70 | + '#cccccc' | ||
| 71 | +]); | ||
| 72 | + | ||
| 73 | +// 历史备注数据(模拟截图效果) | ||
| 74 | +const historyTags = ref([ | ||
| 75 | + { text: '休息日', color: '#ff9944' }, | ||
| 76 | + { text: '累了', color: '#ff9944' }, | ||
| 77 | +]); | ||
| 78 | + | ||
| 79 | +// 事件处理 | ||
| 80 | +const handleClose = () => { | ||
| 81 | + emit('update:visible', false); | ||
| 82 | + emit('close'); | ||
| 83 | +}; | ||
| 84 | + | ||
| 85 | +const handleSave = () => { | ||
| 86 | + const data = { | ||
| 87 | + content: noteContent.value, | ||
| 88 | + color: colorList.value[selectedColorIndex.value] | ||
| 89 | + }; | ||
| 90 | + emit('save', data); | ||
| 91 | + handleClose(); | ||
| 92 | +}; | ||
| 93 | + | ||
| 94 | +const fillNote = (text) => { | ||
| 95 | + noteContent.value = text; | ||
| 96 | +}; | ||
| 97 | +</script> | ||
| 98 | + | ||
| 99 | +<style scoped lang="scss"> | ||
| 100 | +.date-note-popup { | ||
| 101 | + width: 100%; | ||
| 102 | + height: 100%; | ||
| 103 | + background-color: #fff; | ||
| 104 | + box-sizing: border-box; | ||
| 105 | + padding: 24rpx 32rpx; | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | +/* 顶部导航 */ | ||
| 109 | +.popup-header { | ||
| 110 | + display: flex; | ||
| 111 | + align-items: center; | ||
| 112 | + justify-content: space-between; | ||
| 113 | + margin-bottom: 40rpx; | ||
| 114 | + | ||
| 115 | + .close-btn { | ||
| 116 | + width: 48rpx; | ||
| 117 | + height: 48rpx; | ||
| 118 | + display: flex; | ||
| 119 | + align-items: center; | ||
| 120 | + justify-content: center; | ||
| 121 | + | ||
| 122 | + .close-icon { | ||
| 123 | + font-size: 40rpx; | ||
| 124 | + color: #333; | ||
| 125 | + line-height: 1; | ||
| 126 | + } | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + .popup-title { | ||
| 130 | + font-size: 34rpx; | ||
| 131 | + font-weight: 600; | ||
| 132 | + color: #111; | ||
| 133 | + text-align: center; | ||
| 134 | + display: block; | ||
| 135 | + flex: 1; | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | + .save-btn { | ||
| 139 | + margin-right: 0; | ||
| 140 | + background: #ffd100; | ||
| 141 | + color: #000; | ||
| 142 | + border: none; | ||
| 143 | + border-radius: 20rpx; | ||
| 144 | + padding: 5rpx 25rpx; | ||
| 145 | + font-size: 28rpx; | ||
| 146 | + font-weight: 500; | ||
| 147 | + } | ||
| 148 | +} | ||
| 149 | + | ||
| 150 | +/* 通用标题 */ | ||
| 151 | +.section-title { | ||
| 152 | + font-size: 30rpx; | ||
| 153 | + color: #111; | ||
| 154 | + font-weight: 500; | ||
| 155 | + display: block; | ||
| 156 | + margin-bottom: 24rpx; | ||
| 157 | +} | ||
| 158 | + | ||
| 159 | +/* 颜色选择区 */ | ||
| 160 | +.color-section { | ||
| 161 | + margin-bottom: 40rpx; | ||
| 162 | + | ||
| 163 | + .color-list { | ||
| 164 | + display: flex; | ||
| 165 | + gap: 40rpx; | ||
| 166 | + | ||
| 167 | + .color-item { | ||
| 168 | + width: 80rpx; | ||
| 169 | + height: 80rpx; | ||
| 170 | + border-radius: 12rpx; | ||
| 171 | + border: 4rpx solid transparent; | ||
| 172 | + | ||
| 173 | + &.active { | ||
| 174 | + border-color: #333; | ||
| 175 | + } | ||
| 176 | + } | ||
| 177 | + } | ||
| 178 | +} | ||
| 179 | + | ||
| 180 | +/* 输入框区域 */ | ||
| 181 | +.input-section { | ||
| 182 | + margin-bottom: 40rpx; | ||
| 183 | + | ||
| 184 | + .note-textarea { | ||
| 185 | + width: 100%; | ||
| 186 | + height: 200rpx; | ||
| 187 | + background: #f7f8fa; | ||
| 188 | + border-radius: 12rpx; | ||
| 189 | + padding: 20rpx; | ||
| 190 | + font-size: 28rpx; | ||
| 191 | + color: #333; | ||
| 192 | + line-height: 1.5; | ||
| 193 | + box-sizing: border-box; | ||
| 194 | + | ||
| 195 | + .placeholder { | ||
| 196 | + color: #999; | ||
| 197 | + } | ||
| 198 | + } | ||
| 199 | +} | ||
| 200 | + | ||
| 201 | +.history-title { | ||
| 202 | + margin-bottom: 0 !important; | ||
| 203 | + /* 覆盖原来的 margin-bottom */ | ||
| 204 | +} | ||
| 205 | + | ||
| 206 | +/* 历史备注区域 */ | ||
| 207 | +.history-section { | ||
| 208 | + .history-header { | ||
| 209 | + display: flex; | ||
| 210 | + align-items: center; | ||
| 211 | + gap: 16rpx; | ||
| 212 | + margin-bottom: 24rpx; | ||
| 213 | + margin-top: 0; | ||
| 214 | + | ||
| 215 | + .history-tip { | ||
| 216 | + font-size: 24rpx; | ||
| 217 | + color: #999; | ||
| 218 | + } | ||
| 219 | + } | ||
| 220 | + | ||
| 221 | + .history-tags { | ||
| 222 | + display: flex; | ||
| 223 | + flex-wrap: wrap; | ||
| 224 | + gap: 20rpx; | ||
| 225 | + | ||
| 226 | + .tag-item { | ||
| 227 | + border-radius: 8rpx; | ||
| 228 | + padding: 16rpx 24rpx; | ||
| 229 | + display: flex; | ||
| 230 | + align-items: center; | ||
| 231 | + | ||
| 232 | + .tag-text { | ||
| 233 | + font-size: 26rpx; | ||
| 234 | + color: #fff; | ||
| 235 | + } | ||
| 236 | + } | ||
| 237 | + } | ||
| 238 | +} | ||
| 239 | +</style> |
pages/xunji/components/time-select.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="time-select"> | ||
| 3 | + <view class="tab-container"> | ||
| 4 | + <view | ||
| 5 | + class="tab-item" | ||
| 6 | + :class="{ active: currentDateType === 'week' }" | ||
| 7 | + @click="switchDateType('week')" | ||
| 8 | + > | ||
| 9 | + 周 | ||
| 10 | + </view> | ||
| 11 | + <view | ||
| 12 | + class="tab-item" | ||
| 13 | + :class="{ active: currentDateType === 'month' }" | ||
| 14 | + @click="switchDateType('month')" | ||
| 15 | + > | ||
| 16 | + 月 | ||
| 17 | + </view> | ||
| 18 | + </view> | ||
| 19 | + | ||
| 20 | + <view class="date-stepper"> | ||
| 21 | + <view class="arrow-icon" @click="handlePrev"> | ||
| 22 | + <up-icon name="arrow-left" color="#333" size="12" bold></up-icon> | ||
| 23 | + </view> | ||
| 24 | + <view class="date-display"> {{ dateRangeText }} </view> | ||
| 25 | + <!-- 修改: 根据 isNextDisabled 判断是否隐藏下一个按钮 --> | ||
| 26 | + <view class="arrow-icon" @click="handleNext" v-if="!isNextDisabled"> | ||
| 27 | + <up-icon name="arrow-right" color="#333" size="12" bold></up-icon> | ||
| 28 | + </view> | ||
| 29 | + <view v-else class="occupy"></view> | ||
| 30 | + </view> | ||
| 31 | + </view> | ||
| 32 | +</template> | ||
| 33 | + | ||
| 34 | +<script setup> | ||
| 35 | + import { ref, computed } from 'vue'; | ||
| 36 | + | ||
| 37 | + // 当前日期类型: 'week' | 'month' | ||
| 38 | + const currentDateType = ref('week'); | ||
| 39 | + // 基准日期,用于计算范围 | ||
| 40 | + const baseDate = ref(new Date()); | ||
| 41 | + | ||
| 42 | + // 格式化日期辅助函数 YYYY/MM/DD | ||
| 43 | + const formatDate = (date) => { | ||
| 44 | + const y = date.getFullYear(); | ||
| 45 | + const m = String(date.getMonth() + 1).padStart(2, '0'); | ||
| 46 | + const d = String(date.getDate()).padStart(2, '0'); | ||
| 47 | + return `${y}/${m}/${d}`; | ||
| 48 | + }; | ||
| 49 | + | ||
| 50 | + // 计算显示的日期范围文本 | ||
| 51 | + const dateRangeText = computed(() => { | ||
| 52 | + const current = new Date(baseDate.value); | ||
| 53 | + | ||
| 54 | + if (currentDateType.value === 'week') { | ||
| 55 | + // 计算本周的周一和周日 | ||
| 56 | + const day = current.getDay(); | ||
| 57 | + const diffToMonday = day === 0 ? -6 : 1 - day; // 调整到周一 | ||
| 58 | + | ||
| 59 | + const startOfWeek = new Date(current); | ||
| 60 | + startOfWeek.setDate(current.getDate() + diffToMonday); | ||
| 61 | + | ||
| 62 | + const endOfWeek = new Date(startOfWeek); | ||
| 63 | + endOfWeek.setDate(startOfWeek.getDate() + 6); | ||
| 64 | + | ||
| 65 | + return `${formatDate(startOfWeek)} - ${formatDate(endOfWeek)}`; | ||
| 66 | + } else { | ||
| 67 | + // 计算本月的一号和最后一天 | ||
| 68 | + const startOfMonth = new Date(current.getFullYear(), current.getMonth(), 1); | ||
| 69 | + const endOfMonth = new Date(current.getFullYear(), current.getMonth() + 1, 0); | ||
| 70 | + | ||
| 71 | + return `${formatDate(startOfMonth)} - ${formatDate(endOfMonth)}`; | ||
| 72 | + } | ||
| 73 | + }); | ||
| 74 | + | ||
| 75 | + // 新增: 计算下一个日期是否不可用(即是否为未来日期) | ||
| 76 | + const isNextDisabled = computed(() => { | ||
| 77 | + const newDate = new Date(baseDate.value); | ||
| 78 | + if (currentDateType.value === 'week') { | ||
| 79 | + newDate.setDate(newDate.getDate() + 7); | ||
| 80 | + } else { | ||
| 81 | + newDate.setMonth(newDate.getMonth() + 1); | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + // 重置时分秒进行比较 | ||
| 85 | + const now = new Date(); | ||
| 86 | + now.setHours(0, 0, 0, 0); | ||
| 87 | + const checkDate = new Date(newDate); | ||
| 88 | + checkDate.setHours(0, 0, 0, 0); | ||
| 89 | + | ||
| 90 | + return checkDate > now; | ||
| 91 | + }); | ||
| 92 | + | ||
| 93 | + // 切换周/月 | ||
| 94 | + const switchDateType = (type) => { | ||
| 95 | + currentDateType.value = type; | ||
| 96 | + }; | ||
| 97 | + | ||
| 98 | + // 上一段时间 | ||
| 99 | + const handlePrev = () => { | ||
| 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 | + baseDate.value = newDate; | ||
| 107 | + }; | ||
| 108 | + | ||
| 109 | + // 下一段时间 | ||
| 110 | + const handleNext = () => { | ||
| 111 | + // 虽然按钮已隐藏,但保留逻辑判断以防直接调用或其他边界情况 | ||
| 112 | + if (isNextDisabled.value) { | ||
| 113 | + return; | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + const newDate = new Date(baseDate.value); | ||
| 117 | + if (currentDateType.value === 'week') { | ||
| 118 | + newDate.setDate(newDate.getDate() + 7); | ||
| 119 | + } else { | ||
| 120 | + newDate.setMonth(newDate.getMonth() + 1); | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + baseDate.value = newDate; | ||
| 124 | + }; | ||
| 125 | +</script> | ||
| 126 | + | ||
| 127 | +<style scoped lang="scss"> | ||
| 128 | + .time-select { | ||
| 129 | + flex-shrink: 0; /* 修改: 防止顶部选择器被压缩 */ | ||
| 130 | + width: 100%; | ||
| 131 | + display: flex; | ||
| 132 | + flex-direction: column; | ||
| 133 | + align-items: center; | ||
| 134 | + | ||
| 135 | + // 周月切换 Tab | ||
| 136 | + .tab-container { | ||
| 137 | + width: 100%; | ||
| 138 | + height: 72rpx; | ||
| 139 | + display: grid; | ||
| 140 | + grid-template-columns: 1fr 1fr; | ||
| 141 | + gap: 8rpx; | ||
| 142 | + background-color: #eee; | ||
| 143 | + border-radius: 12rpx; | ||
| 144 | + padding: 6rpx; | ||
| 145 | + box-sizing: border-box; | ||
| 146 | + | ||
| 147 | + .tab-item { | ||
| 148 | + display: flex; | ||
| 149 | + align-items: center; | ||
| 150 | + justify-content: center; | ||
| 151 | + color: #8c8c8c; | ||
| 152 | + font-size: 28rpx; | ||
| 153 | + transition: all 0.2s ease; | ||
| 154 | + | ||
| 155 | + &.active { | ||
| 156 | + background-color: #ffffff; | ||
| 157 | + color: #1a1a1a; | ||
| 158 | + font-weight: 600; | ||
| 159 | + border-radius: 8rpx; | ||
| 160 | + box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05); | ||
| 161 | + } | ||
| 162 | + } | ||
| 163 | + } | ||
| 164 | + // 日期翻页器 | ||
| 165 | + .date-stepper { | ||
| 166 | + margin-top: 32rpx; | ||
| 167 | + display: flex; | ||
| 168 | + align-items: center; | ||
| 169 | + justify-content: space-between; | ||
| 170 | + width: 100%; | ||
| 171 | + | ||
| 172 | + .date-display { | ||
| 173 | + margin: 0 40rpx; | ||
| 174 | + font-size: 28rpx; | ||
| 175 | + font-weight: 500; | ||
| 176 | + color: #333; | ||
| 177 | + font-variant-numeric: tabular-nums; // 防止数字切换时抖动 | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + .arrow-icon { | ||
| 181 | + width: 56rpx; | ||
| 182 | + height: 56rpx; | ||
| 183 | + display: flex; | ||
| 184 | + align-items: center; | ||
| 185 | + justify-content: center; | ||
| 186 | + background-color: #fff; | ||
| 187 | + border-radius: 50%; | ||
| 188 | + transition: background-color 0.2s; | ||
| 189 | + } | ||
| 190 | + } | ||
| 191 | + .occupy { | ||
| 192 | + width: 56rpx; | ||
| 193 | + height: 56rpx; | ||
| 194 | + } | ||
| 195 | + } | ||
| 196 | +</style> |
pages/xunji/components/xunji-dongzuo.vue
0 → 100644
| 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> | ||
| 15 | + </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> | ||
| 19 | + </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> | ||
| 23 | + </view> | ||
| 24 | + </view> | ||
| 25 | + </view> | ||
| 26 | + </view> | ||
| 27 | + | ||
| 28 | + <!-- 浮动菜单 --> | ||
| 29 | + | ||
| 30 | + <!-- 左右布局 --> | ||
| 31 | + <view class="layout-container"> | ||
| 32 | + <scroll-view scroll-y class="left-nav" enable-flex> | ||
| 33 | + <view | ||
| 34 | + class="nav-item" | ||
| 35 | + @click="handleCollectClick('collect')" | ||
| 36 | + :class="{ active: activeNav === 'collect' }" | ||
| 37 | + > | ||
| 38 | + 收藏 | ||
| 39 | + </view> | ||
| 40 | + <view | ||
| 41 | + v-for="nav in navItems" | ||
| 42 | + :key="nav.id" | ||
| 43 | + class="nav-item" | ||
| 44 | + :class="{ active: activeNav === nav.id }" | ||
| 45 | + @click="switchNav(nav.id)" | ||
| 46 | + > | ||
| 47 | + {{ nav.name }} | ||
| 48 | + </view> | ||
| 49 | + </scroll-view> | ||
| 50 | + | ||
| 51 | + <!-- 小动作列表 --> | ||
| 52 | + <scroll-view scroll-y class="right-content" enable-flex> | ||
| 53 | + <view class="tip" v-if="motionPart.length > 0"> | ||
| 54 | + <view | ||
| 55 | + class="item" | ||
| 56 | + @click="handlePartClick('')" | ||
| 57 | + :class="{ active: activeMotionPart == '' }" | ||
| 58 | + >全部</view | ||
| 59 | + > | ||
| 60 | + <view | ||
| 61 | + class="item" | ||
| 62 | + v-for="item in motionPart" | ||
| 63 | + :key="item.id" | ||
| 64 | + :class="{ active: activeMotionPart == item.id }" | ||
| 65 | + @click="handlePartClick(item.id)" | ||
| 66 | + > | ||
| 67 | + {{ item.name }} | ||
| 68 | + </view> | ||
| 69 | + </view> | ||
| 70 | + | ||
| 71 | + <view class="exercise-grid"> | ||
| 72 | + <view class="content" v-if="exercises.length > 0 || superGroupInfo.length > 0"> | ||
| 73 | + <view class="equipment-list"> | ||
| 74 | + <view class="equipment-item" v-for="item in exercises" :key="item.equipmentId"> | ||
| 75 | + <view class="equipment-name"> {{ item.equipmentName }} </view> | ||
| 76 | + <view class="action-list"> | ||
| 77 | + <view class="action-item" v-for="e in item.exercises" :key="e.id"> | ||
| 78 | + <image | ||
| 79 | + :src="e.url3dAnimation" | ||
| 80 | + mode="aspectFill" | ||
| 81 | + lazy-load | ||
| 82 | + class="action-img" | ||
| 83 | + @click="goToDetail(e.id, 1)" | ||
| 84 | + ></image> | ||
| 85 | + <view class="action-name">{{ e.name }}</view> | ||
| 86 | + <view class="trainingReps" v-if="e.trainingReps">{{ e.trainingReps }}次</view> | ||
| 87 | + </view> | ||
| 88 | + </view> | ||
| 89 | + </view> | ||
| 90 | + | ||
| 91 | + <view class="supers" v-if="superGroupInfo.length > 0"> | ||
| 92 | + <view class="supers-name"> 超级组 </view> | ||
| 93 | + <view | ||
| 94 | + class="super" | ||
| 95 | + v-for="item in superGroupInfo" | ||
| 96 | + :key="item.id" | ||
| 97 | + @click="goToDetail(item.id, 2)" | ||
| 98 | + > | ||
| 99 | + <image | ||
| 100 | + src="https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260507/超级组_1778117889451.png" | ||
| 101 | + mode="aspectFill" | ||
| 102 | + lazy-load | ||
| 103 | + class="super-img" | ||
| 104 | + ></image> | ||
| 105 | + <view class="right"> | ||
| 106 | + <view class="super-name">{{ item.name }}</view> | ||
| 107 | + <view class="parts">{{ item.primaryMuscles.join() }}</view> | ||
| 108 | + </view> | ||
| 109 | + </view> | ||
| 110 | + </view> | ||
| 111 | + | ||
| 112 | + <!-- <view class="add-super"> | ||
| 113 | + <view class="header"> | ||
| 114 | + <up-icon name="plus" color="#000" size="15" bold></up-icon> | ||
| 115 | + <text>添加自定义动作</text> | ||
| 116 | + </view> | ||
| 117 | + <view class="tip">添加官方库没有的动作</view> | ||
| 118 | + </view> --> | ||
| 119 | + </view> | ||
| 120 | + </view> | ||
| 121 | + | ||
| 122 | + <!-- 空状态 --> | ||
| 123 | + <view class="empty-state" v-else> | ||
| 124 | + <text class="empty-text">暂无动作数据</text> | ||
| 125 | + </view> | ||
| 126 | + </view> | ||
| 127 | + </scroll-view> | ||
| 128 | + </view> | ||
| 129 | + | ||
| 130 | + <!-- 详情弹窗 --> | ||
| 131 | + <dongzuoXianqing ref="actionDetailRef" /> | ||
| 132 | + </view> | ||
| 133 | +</template> | ||
| 134 | + | ||
| 135 | +<script setup> | ||
| 136 | + import { ref, onMounted, nextTick } from 'vue'; | ||
| 137 | + import ExercisesApi from '@/sheep/api/motion/exercises'; | ||
| 138 | + import SupersetsApi from '@/sheep/api/motion/supersets'; | ||
| 139 | + import dongzuoXianqing from '@/pages/xunji/components/dongzuo-xianqing.vue'; | ||
| 140 | + import { useActionStore } from '@/sheep/store/action'; | ||
| 141 | + | ||
| 142 | + const actionStore = useActionStore(); | ||
| 143 | + | ||
| 144 | + // 响应式数据 | ||
| 145 | + const activeNav = ref(''); | ||
| 146 | + const navItems = ref([]); | ||
| 147 | + const exercises = ref([]); | ||
| 148 | + const superGroupInfo = ref([]); | ||
| 149 | + const motionPart = ref([]); | ||
| 150 | + const activeMotionPart = ref(''); | ||
| 151 | + const currentCategoryId = ref(''); | ||
| 152 | + | ||
| 153 | + const allExercises = ref([]); | ||
| 154 | + | ||
| 155 | + const addshow = ref(false); | ||
| 156 | + | ||
| 157 | + const superFavoriteList = ref([]); | ||
| 158 | + | ||
| 159 | + const actionDetailRef = ref(null); | ||
| 160 | + | ||
| 161 | + // 关闭菜单 | ||
| 162 | + const closeAddMenu = () => { | ||
| 163 | + addshow.value = false; | ||
| 164 | + }; | ||
| 165 | + | ||
| 166 | + // 部位筛选 | ||
| 167 | + const handlePartClick = async (id) => { | ||
| 168 | + try { | ||
| 169 | + activeMotionPart.value = id; | ||
| 170 | + const exerciseRes = await ExercisesApi.getexercises({ | ||
| 171 | + categoriesId: activeNav.value, | ||
| 172 | + subCategoriesId: id, | ||
| 173 | + }); | ||
| 174 | + exercises.value = exerciseRes.data || []; | ||
| 175 | + } catch (e) { | ||
| 176 | + console.error('加载动作失败:', e); | ||
| 177 | + } | ||
| 178 | + }; | ||
| 179 | + | ||
| 180 | + // 收藏 | ||
| 181 | + const handleCollectClick = async (id) => { | ||
| 182 | + activeNav.value = id; | ||
| 183 | + loadCollectList(); | ||
| 184 | + loadSuperFavoriteList(); | ||
| 185 | + }; | ||
| 186 | + // 动作收藏 | ||
| 187 | + const loadCollectList = async () => { | ||
| 188 | + try { | ||
| 189 | + const res = await ExercisesApi.getFavoriteExercises(); | ||
| 190 | + exercises.value = res.data || []; | ||
| 191 | + } catch (err) { | ||
| 192 | + console.error('加载动作收藏失败', err); | ||
| 193 | + } | ||
| 194 | + }; | ||
| 195 | + // 超级组收藏 | ||
| 196 | + const loadSuperFavoriteList = async () => { | ||
| 197 | + try { | ||
| 198 | + const res = await SupersetsApi.getFavoriteSuperset(); | ||
| 199 | + superGroupInfo.value = res.data || []; | ||
| 200 | + } catch (err) { | ||
| 201 | + superFavoriteList.value = []; | ||
| 202 | + console.error('加载超级组收藏失败', err); | ||
| 203 | + } | ||
| 204 | + }; | ||
| 205 | + | ||
| 206 | + // 加载分类 | ||
| 207 | + const loadCategories = async () => { | ||
| 208 | + try { | ||
| 209 | + await actionStore.getloadCategories(); | ||
| 210 | + navItems.value = actionStore.showCategories; | ||
| 211 | + | ||
| 212 | + if (navItems.value.length > 0) { | ||
| 213 | + switchNav(navItems.value[0].id); | ||
| 214 | + } | ||
| 215 | + } catch (error) { | ||
| 216 | + console.error('获取分类失败:', error); | ||
| 217 | + } | ||
| 218 | + }; | ||
| 219 | + | ||
| 220 | + // 加载动作 | ||
| 221 | + const loadExercises = async (categoriesId) => { | ||
| 222 | + try { | ||
| 223 | + const partRes = await ExercisesApi.getMotionPart(categoriesId); | ||
| 224 | + motionPart.value = partRes.data || []; | ||
| 225 | + console.log(motionPart.value, 'motionPart.value'); | ||
| 226 | + | ||
| 227 | + const exerciseRes = await ExercisesApi.getexercises({ categoriesId }); | ||
| 228 | + exercises.value = exerciseRes.data; | ||
| 229 | + } catch (error) { | ||
| 230 | + console.error('加载动作失败:', error); | ||
| 231 | + } | ||
| 232 | + }; | ||
| 233 | + | ||
| 234 | + // 切换导航 | ||
| 235 | + const switchNav = (id) => { | ||
| 236 | + if (activeNav.value === id) return; | ||
| 237 | + activeNav.value = id; | ||
| 238 | + activeMotionPart.value = ''; | ||
| 239 | + if (id == 'super') { | ||
| 240 | + exercises.value = []; | ||
| 241 | + motionPart.value = []; | ||
| 242 | + loadsupersetsinfo(); | ||
| 243 | + } else { | ||
| 244 | + superGroupInfo.value = []; | ||
| 245 | + loadExercises(id); | ||
| 246 | + } | ||
| 247 | + }; | ||
| 248 | + | ||
| 249 | + // 搜索 | ||
| 250 | + const onSearch = (e) => { | ||
| 251 | + const searchText = e.detail.value.trim(); | ||
| 252 | + if (!currentCategoryId.value) return; | ||
| 253 | + | ||
| 254 | + searchText ? searchExercises(searchText) : loadExercises(currentCategoryId.value); | ||
| 255 | + }; | ||
| 256 | + | ||
| 257 | + const searchExercises = (keyword) => { | ||
| 258 | + const key = keyword.toLowerCase().trim(); | ||
| 259 | + exercises.value = allExercises.value.filter((item) => { | ||
| 260 | + return item.name && item.name.toLowerCase().includes(key); | ||
| 261 | + }); | ||
| 262 | + }; | ||
| 263 | + | ||
| 264 | + const loadsupersetsinfo = async () => { | ||
| 265 | + try { | ||
| 266 | + const response = await SupersetsApi.getsupersets(); | ||
| 267 | + superGroupInfo.value = response.data || []; | ||
| 268 | + } catch (error) { | ||
| 269 | + console.error('获取超级组失败:', error); | ||
| 270 | + } | ||
| 271 | + }; | ||
| 272 | + | ||
| 273 | + // 页面跳转 | ||
| 274 | + const goToDetail = (id, type) => { | ||
| 275 | + nextTick(() => { | ||
| 276 | + actionDetailRef.value.open(id, type); | ||
| 277 | + }); | ||
| 278 | + }; | ||
| 279 | + | ||
| 280 | + const addExercise = () => { | ||
| 281 | + addshow.value = !addshow.value; | ||
| 282 | + }; | ||
| 283 | + | ||
| 284 | + const addnewmotion = () => { | ||
| 285 | + uni.navigateTo({ url: '/pages4/pages/xunji/xunji-dongzuo-xinzeng' }); | ||
| 286 | + }; | ||
| 287 | + | ||
| 288 | + const addnewsupersets = () => { | ||
| 289 | + uni.navigateTo({ | ||
| 290 | + url: `/pages4/pages/xunji/dongzuo-xinzengchaojizu?navItems=${encodeURIComponent( | ||
| 291 | + JSON.stringify(navItems.value), | ||
| 292 | + )}`, | ||
| 293 | + }); | ||
| 294 | + }; | ||
| 295 | + | ||
| 296 | + const actionmanagement = () => { | ||
| 297 | + uni.navigateTo({ url: '/pages4/pages/xunji/dongzuo-muluguanli' }); | ||
| 298 | + }; | ||
| 299 | + | ||
| 300 | + onMounted(() => { | ||
| 301 | + loadCategories(); | ||
| 302 | + }); | ||
| 303 | +</script> | ||
| 304 | +<style lang="scss" scoped> | ||
| 305 | + .exercise-page { | ||
| 306 | + background-color: white; | ||
| 307 | + box-sizing: border-box; | ||
| 308 | + height: 100%; | ||
| 309 | + display: flex; | ||
| 310 | + flex-direction: column; | ||
| 311 | + box-sizing: border-box; | ||
| 312 | + | ||
| 313 | + .search-bar { | ||
| 314 | + box-sizing: border-box; | ||
| 315 | + width: 100%; | ||
| 316 | + display: flex; | ||
| 317 | + align-items: center; | ||
| 318 | + justify-content: flex-end; | ||
| 319 | + padding: 20rpx; | ||
| 320 | + // background-color: #f5f5f5; | ||
| 321 | + border-radius: 12rpx; | ||
| 322 | + | ||
| 323 | + .search { | ||
| 324 | + display: flex; | ||
| 325 | + align-items: center; | ||
| 326 | + padding: 10rpx 20rpx; | ||
| 327 | + flex: 1; | ||
| 328 | + border-radius: 30rpx; | ||
| 329 | + background-color: #f5f5f5; | ||
| 330 | + | ||
| 331 | + .search-icon { | ||
| 332 | + width: 30rpx; | ||
| 333 | + height: 30rpx; | ||
| 334 | + } | ||
| 335 | + | ||
| 336 | + .search-input { | ||
| 337 | + flex: 1; | ||
| 338 | + padding-left: 15rpx; | ||
| 339 | + font-size: 26rpx; | ||
| 340 | + color: #333; | ||
| 341 | + border: none; | ||
| 342 | + outline: none; | ||
| 343 | + background: transparent; | ||
| 344 | + } | ||
| 345 | + } | ||
| 346 | + | ||
| 347 | + .add-btn { | ||
| 348 | + width: 60rpx; | ||
| 349 | + height: 60rpx; | ||
| 350 | + background-color: transparent; | ||
| 351 | + border: none; | ||
| 352 | + margin-left: 10rpx; | ||
| 353 | + display: flex; | ||
| 354 | + justify-content: center; | ||
| 355 | + align-items: center; | ||
| 356 | + z-index: 99; | ||
| 357 | + position: relative; | ||
| 358 | + .plus-icon { | ||
| 359 | + width: 40rpx; | ||
| 360 | + height: 40rpx; | ||
| 361 | + } | ||
| 362 | + .floating-menu { | ||
| 363 | + position: absolute; | ||
| 364 | + top: 30rpx; | ||
| 365 | + right: 30rpx; | ||
| 366 | + width: 300rpx; | ||
| 367 | + background-color: #fff; | ||
| 368 | + border-radius: 12rpx; | ||
| 369 | + box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1); | ||
| 370 | + padding: 20rpx; | ||
| 371 | + z-index: 100; | ||
| 372 | + .menu-item { | ||
| 373 | + display: flex; | ||
| 374 | + align-items: center; | ||
| 375 | + padding: 20rpx 0; | ||
| 376 | + border-bottom: 1rpx solid #eee; | ||
| 377 | + .u-icon { | ||
| 378 | + margin-right: 16rpx; | ||
| 379 | + } | ||
| 380 | + .menu-text { | ||
| 381 | + font-size: 28rpx; | ||
| 382 | + color: #333; | ||
| 383 | + } | ||
| 384 | + | ||
| 385 | + &:last-child { | ||
| 386 | + border-bottom: none; | ||
| 387 | + } | ||
| 388 | + } | ||
| 389 | + } | ||
| 390 | + } | ||
| 391 | + } | ||
| 392 | + | ||
| 393 | + .layout-container { | ||
| 394 | + box-sizing: border-box; | ||
| 395 | + display: flex; | ||
| 396 | + height: calc(82vh + 25rpx); | ||
| 397 | + background-color: #f5f5f5; | ||
| 398 | + | ||
| 399 | + .left-nav { | ||
| 400 | + width: 160rpx; | ||
| 401 | + background-color: white; | ||
| 402 | + border-right: 1px solid #eee; | ||
| 403 | + // 添加底部 padding,避免最后一项被底部导航栏遮挡 | ||
| 404 | + padding-bottom: 20rpx; | ||
| 405 | + // #ifdef MP-WEIXIN | ||
| 406 | + // 微信小程序端可能需要更多底部空间 | ||
| 407 | + padding-bottom: 100rpx; | ||
| 408 | + | ||
| 409 | + // #endif | ||
| 410 | + .nav-item { | ||
| 411 | + padding: 24rpx 0; | ||
| 412 | + text-align: center; | ||
| 413 | + font-size: 26rpx; | ||
| 414 | + color: #333; | ||
| 415 | + } | ||
| 416 | + | ||
| 417 | + .active { | ||
| 418 | + color: #16ad40; | ||
| 419 | + background-color: #e1f6e9; | ||
| 420 | + border-right: 3px solid #16ad40; | ||
| 421 | + font-weight: bold; | ||
| 422 | + } | ||
| 423 | + } | ||
| 424 | + | ||
| 425 | + .right-content { | ||
| 426 | + box-sizing: border-box; | ||
| 427 | + flex: 1; | ||
| 428 | + padding: 20rpx; | ||
| 429 | + height: 100%; | ||
| 430 | + // 添加底部 padding,避免内容被底部导航栏遮挡 | ||
| 431 | + // 底部导航栏高度约 100rpx(50px),加上安全区域 | ||
| 432 | + padding-bottom: 20rpx; | ||
| 433 | + // #ifdef MP-WEIXIN | ||
| 434 | + // 微信小程序端可能需要更多底部空间 | ||
| 435 | + padding-bottom: 100rpx; | ||
| 436 | + | ||
| 437 | + // #endif | ||
| 438 | + .tip { | ||
| 439 | + display: flex; | ||
| 440 | + padding-left: 20rpx; | ||
| 441 | + align-items: center; | ||
| 442 | + column-gap: 15rpx; | ||
| 443 | + margin-bottom: 20rpx; | ||
| 444 | + | ||
| 445 | + .item { | ||
| 446 | + margin: 0; | ||
| 447 | + padding: 12rpx 20rpx; | ||
| 448 | + font-size: 22rpx; | ||
| 449 | + line-height: 1; | ||
| 450 | + background-color: #fff; | ||
| 451 | + border-radius: 20rpx; | ||
| 452 | + &.active { | ||
| 453 | + background-color: #d8ede0; | ||
| 454 | + color: #43b05e; | ||
| 455 | + } | ||
| 456 | + } | ||
| 457 | + } | ||
| 458 | + | ||
| 459 | + .exercise-grid { | ||
| 460 | + display: flex; | ||
| 461 | + flex-direction: column; | ||
| 462 | + | ||
| 463 | + gap: 24rpx; | ||
| 464 | + | ||
| 465 | + .title { | ||
| 466 | + font-weight: 600; | ||
| 467 | + padding-left: 20rpx; | ||
| 468 | + font-size: 30rpx; | ||
| 469 | + } | ||
| 470 | + | ||
| 471 | + .content { | ||
| 472 | + width: 100%; | ||
| 473 | + | ||
| 474 | + // display: grid; | ||
| 475 | + // grid-template-columns: repeat(2, 1fr); | ||
| 476 | + // /* 改为3列布局 */ | ||
| 477 | + // gap: 16rpx; | ||
| 478 | + | ||
| 479 | + // .exercise-item { | ||
| 480 | + // display: flex; | ||
| 481 | + // height: 150rpx; | ||
| 482 | + // /* 减小项目高度 */ | ||
| 483 | + // flex-direction: column; | ||
| 484 | + // align-items: center; | ||
| 485 | + // justify-content: center; | ||
| 486 | + // background-color: white; | ||
| 487 | + // padding: 16rpx 8rpx; | ||
| 488 | + // box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.08); | ||
| 489 | + | ||
| 490 | + // .exercise-img { | ||
| 491 | + // width: 90%; | ||
| 492 | + // height: 140rpx; | ||
| 493 | + // /* 固定图片高度 */ | ||
| 494 | + // border-radius: 8rpx; | ||
| 495 | + // margin-bottom: 12rpx; | ||
| 496 | + // } | ||
| 497 | + | ||
| 498 | + // .exercise-title { | ||
| 499 | + // font-size: 24rpx; | ||
| 500 | + // color: #333; | ||
| 501 | + // text-align: center; | ||
| 502 | + // line-height: 1.3; | ||
| 503 | + // max-width: 100%; | ||
| 504 | + | ||
| 505 | + // text-overflow: ellipsis; | ||
| 506 | + // display: -webkit-box; | ||
| 507 | + // -webkit-box-orient: vertical; | ||
| 508 | + // } | ||
| 509 | + // } | ||
| 510 | + .equipment-item { | ||
| 511 | + width: 100%; | ||
| 512 | + margin-bottom: 40rpx; | ||
| 513 | + .equipment-name { | ||
| 514 | + color: #000; | ||
| 515 | + font-size: 32rpx; | ||
| 516 | + margin-bottom: 20rpx; | ||
| 517 | + } | ||
| 518 | + .action-list { | ||
| 519 | + display: grid; | ||
| 520 | + grid-template-columns: repeat(2, 1fr); | ||
| 521 | + grid-gap: 10rpx; | ||
| 522 | + .action-item { | ||
| 523 | + display: flex; | ||
| 524 | + flex-direction: column; | ||
| 525 | + justify-content: center; | ||
| 526 | + align-items: center; | ||
| 527 | + gap: 10rpx; | ||
| 528 | + background: #fff; | ||
| 529 | + border-radius: 10rpx; | ||
| 530 | + padding: 20rpx; | ||
| 531 | + box-sizing: border-box; | ||
| 532 | + position: relative; | ||
| 533 | + .action-img { | ||
| 534 | + width: 100%; | ||
| 535 | + height: 250rpx; | ||
| 536 | + border-radius: 10rpx; | ||
| 537 | + } | ||
| 538 | + .trainingReps { | ||
| 539 | + position: absolute; | ||
| 540 | + top: 20rpx; | ||
| 541 | + right: 20rpx; | ||
| 542 | + z-index: 1; | ||
| 543 | + font-size: 22rpx; | ||
| 544 | + } | ||
| 545 | + } | ||
| 546 | + } | ||
| 547 | + } | ||
| 548 | + .supers-name { | ||
| 549 | + color: #000; | ||
| 550 | + font-size: 32rpx; | ||
| 551 | + margin-bottom: 20rpx; | ||
| 552 | + } | ||
| 553 | + | ||
| 554 | + .super { | ||
| 555 | + display: flex; | ||
| 556 | + align-items: center; | ||
| 557 | + background: #fff; | ||
| 558 | + padding: 20rpx; | ||
| 559 | + box-sizing: border-box; | ||
| 560 | + border-radius: 10rpx; | ||
| 561 | + gap: 20rpx; | ||
| 562 | + font-size: 26rpx; | ||
| 563 | + margin-bottom: 20rpx; | ||
| 564 | + .super-img { | ||
| 565 | + width: 120rpx; | ||
| 566 | + height: 120rpx; | ||
| 567 | + border-radius: 10rpx; | ||
| 568 | + } | ||
| 569 | + .super-name { | ||
| 570 | + margin-bottom: 10rpx; | ||
| 571 | + } | ||
| 572 | + } | ||
| 573 | + .add-super { | ||
| 574 | + background: #fff; | ||
| 575 | + padding: 20rpx; | ||
| 576 | + box-sizing: border-box; | ||
| 577 | + border-radius: 10rpx; | ||
| 578 | + .header { | ||
| 579 | + display: flex; | ||
| 580 | + gap: 10rpx; | ||
| 581 | + align-items: center; | ||
| 582 | + margin-bottom: 20rpx; | ||
| 583 | + } | ||
| 584 | + .tip { | ||
| 585 | + margin: 0rpx; | ||
| 586 | + padding: 0rpx; | ||
| 587 | + font-size: 22rpx; | ||
| 588 | + } | ||
| 589 | + } | ||
| 590 | + } | ||
| 591 | + | ||
| 592 | + .nav-item .category-group { | ||
| 593 | + /* 超级组特殊样式 */ | ||
| 594 | + background-color: #f0f9ff; | ||
| 595 | + font-weight: bold; | ||
| 596 | + border-top: 1px solid #eee; | ||
| 597 | + } | ||
| 598 | + | ||
| 599 | + .empty-state { | ||
| 600 | + display: flex; | ||
| 601 | + justify-content: center; | ||
| 602 | + align-items: center; | ||
| 603 | + | ||
| 604 | + .empty-text { | ||
| 605 | + font-size: 28rpx; | ||
| 606 | + color: #999; | ||
| 607 | + } | ||
| 608 | + } | ||
| 609 | + } | ||
| 610 | + } | ||
| 611 | + } | ||
| 612 | + } | ||
| 613 | +</style> |
pages/xunji/components/xunji-moban.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + 顶部两个下拉筛选器,部位筛选和场景筛选 | ||
| 3 | +<!-- 它是给用户选训练计划模板的页面比如:列表显示的是模板大类,里面包含好几个模板 | ||
| 4 | +<!-- 还未实现 部位和场景筛选到对应的模板 --> | ||
| 5 | +<!-- 第一层 模板大类 --> | ||
| 6 | +<template> | ||
| 7 | + <view class="template-page" @click="closeAllDropdowns"> | ||
| 8 | + <!-- 筛选按钮 --> | ||
| 9 | + <view class="filter-section" @click.stop> | ||
| 10 | + <!-- 部位筛选 --> | ||
| 11 | + <view class="filter-wrapper"> | ||
| 12 | + <view class="filter-btn" :class="{ active: showPartDropdown }" @click="togglePartDropdown"> | ||
| 13 | + <text class="text">{{ activePart }}</text> | ||
| 14 | + <uni-icons :type="showPartDropdown ? 'up' : 'down'" size="13"></uni-icons> | ||
| 15 | + </view> | ||
| 16 | + <!-- 部位下拉菜单 --> | ||
| 17 | + <view class="dropdown-menu" v-if="showPartDropdown"> | ||
| 18 | + <view v-for="(item, index) in partList" :key="index" class="dropdown-item" | ||
| 19 | + :class="{ selected: activePart === item.title }" @click="selectPart(item)"> | ||
| 20 | + {{ item.title }} | ||
| 21 | + </view> | ||
| 22 | + </view> | ||
| 23 | + </view> | ||
| 24 | + | ||
| 25 | + <!-- 场景筛选 --> | ||
| 26 | + <view class="filter-wrapper"> | ||
| 27 | + <view class="filter-btn" :class="{ active: showSceneDropdown }" @click="toggleSceneDropdown"> | ||
| 28 | + <text class="text">{{ activeScene }}</text> | ||
| 29 | + <uni-icons :type="showSceneDropdown ? 'up' : 'down'" size="13"></uni-icons> | ||
| 30 | + </view> | ||
| 31 | + <!-- 场景下拉菜单 --> | ||
| 32 | + <view class="dropdown-menu" v-if="showSceneDropdown"> | ||
| 33 | + <view v-for="(item, index) in sceneList" :key="index" class="dropdown-item" | ||
| 34 | + :class="{ selected: activeSceneId === item.id }" @click="selectScene(item)"> | ||
| 35 | + {{ item.title }} | ||
| 36 | + </view> | ||
| 37 | + </view> | ||
| 38 | + </view> | ||
| 39 | + </view> | ||
| 40 | + <!-- 模板列表 (根据状态自动切换显示) --> | ||
| 41 | + <scroll-view class="template-list" enable-flex scroll-y> | ||
| 42 | + <!-- 用父容器包裹 v-if 分支,解决 key 冲突 --> | ||
| 43 | + <view v-if="!isFiltering"> | ||
| 44 | + <view v-for="(item, index) in templateList" :key="index" class="template-item"> | ||
| 45 | + <image :src="item.urlCover" mode="aspectFill" class="template-img"></image> | ||
| 46 | + <view> | ||
| 47 | + <view class="template-content" @click="gototemplate(item)"> | ||
| 48 | + <view class="template-title">{{ item.name }}</view> | ||
| 49 | + <view class="template-count">{{ item.templatesCount }}个模板</view> | ||
| 50 | + <view class="template-desc">{{ item.description }}</view> | ||
| 51 | + </view> | ||
| 52 | + </view> | ||
| 53 | + </view> | ||
| 54 | + </view> | ||
| 55 | + | ||
| 56 | + <!-- 用父容器包裹 v-else 分支,解决 key 冲突 --> | ||
| 57 | + <view v-else> | ||
| 58 | + <view v-for="(template, index) in filteredTemplateList" :key="index" class="template-item"> | ||
| 59 | + <image :src="template.urlCover" mode="aspectFill" class="template-img"></image> | ||
| 60 | + <view> | ||
| 61 | + <view class="template-content" @click="goToTemplateDetail(template)"> | ||
| 62 | + <view class="template-title">{{ template.name }}</view> | ||
| 63 | + <!-- 空数据判断移到了这里 --> | ||
| 64 | + <view class="template-count" v-if="template.primaryMuscles"> | ||
| 65 | + 主要训练部位:{{ template.primaryMuscleNames.join(', ') || '无' }} | ||
| 66 | + </view> | ||
| 67 | + <view class="template-desc" v-if="template.secondaryMuscles"> | ||
| 68 | + 次要训练部位:{{ template.secondaryMuscleNames.join(', ') || '无' }} | ||
| 69 | + </view> | ||
| 70 | + </view> | ||
| 71 | + </view> | ||
| 72 | + </view> | ||
| 73 | + | ||
| 74 | + <!-- 空数据提示:移到 view-else 内部,且作为独立块 --> | ||
| 75 | + <view v-if="filteredTemplateList.length === 0" class="empty-tip"> | ||
| 76 | + 暂无符合条件的训练模板 | ||
| 77 | + </view> | ||
| 78 | + </view> | ||
| 79 | + </scroll-view> | ||
| 80 | + </view> | ||
| 81 | +</template> | ||
| 82 | + | ||
| 83 | +<script setup> | ||
| 84 | +import { onMounted, ref } from 'vue'; | ||
| 85 | +import TemplatesApi from '@/sheep/api/Template/Templates'; | ||
| 86 | + | ||
| 87 | +const templateList = ref([]); | ||
| 88 | +// 1. 存储【筛选出来的具体模板】 | ||
| 89 | +const filteredTemplateList = ref([]); | ||
| 90 | +// 2. 控制页面显示:false=显示大类,true=显示筛选后的模板 | ||
| 91 | +const isFiltering = ref(false); | ||
| 92 | +// 3. 场景ID(接口需要,你之前漏了存场景ID!) | ||
| 93 | +const activeSceneId = ref('0'); | ||
| 94 | + | ||
| 95 | +// 下拉框部位筛选相关状态 | ||
| 96 | +const partList = ref([]); | ||
| 97 | +const rawPartData = ref([]); // 存储接口返回的原始部位数据 | ||
| 98 | +const showPartDropdown = ref(false); | ||
| 99 | +const activePart = ref('不限'); | ||
| 100 | +const activePartId = ref('0'); // 新增:默认选中"不限",id=0 | ||
| 101 | +// 场景筛选相关状态 | ||
| 102 | +const sceneList = ref([ | ||
| 103 | + { id: '0', title: '不限' }, | ||
| 104 | + { id: '1', title: '健身房' }, | ||
| 105 | + { id: '2', title: '仅哑铃' }, | ||
| 106 | + { id: '3', title: '仅哑铃+杠铃' }, | ||
| 107 | + // { id: 4, title: '办公室' }, | ||
| 108 | +]); | ||
| 109 | +const showSceneDropdown = ref(false); | ||
| 110 | +const activeScene = ref('不限'); | ||
| 111 | + | ||
| 112 | +// 获取模板大类列表 | ||
| 113 | +const TemplatesList = async () => { | ||
| 114 | + try { | ||
| 115 | + const response = await TemplatesApi.getTemplates(); | ||
| 116 | + templateList.value = response.data; | ||
| 117 | + console.log('模板大类接口返回数据', templateList.value) | ||
| 118 | + } catch (error) { | ||
| 119 | + console.log('模板大类获取失败', error); | ||
| 120 | + } | ||
| 121 | +}; | ||
| 122 | +// 获取所有细分锻炼部位(接口) | ||
| 123 | +const getPartCategories = async () => { | ||
| 124 | + try { | ||
| 125 | + const res = await TemplatesApi.getPartAllCategories(); | ||
| 126 | + if (res.code === 0) { | ||
| 127 | + rawPartData.value = res.data; | ||
| 128 | + console.log('部位分类接口返回数据rawPartData.value:', rawPartData.value) | ||
| 129 | + // 下拉列表赋值 | ||
| 130 | + partList.value = [ | ||
| 131 | + { title: '不限', id: '0' }, // 给"不限"加id='0',方便后续筛选 | ||
| 132 | + ...res.data.map(item => ({ | ||
| 133 | + title: item.name, // 接口返回的name作为显示标题 | ||
| 134 | + id: String(item.id) // 保存接口的id,用于后续筛选传参 | ||
| 135 | + })) | ||
| 136 | + ]; | ||
| 137 | + } | ||
| 138 | + } catch (error) { | ||
| 139 | + console.log('部位列表获取失败', error); | ||
| 140 | + } | ||
| 141 | +}; | ||
| 142 | +// 切换部位下拉菜单 | ||
| 143 | +const togglePartDropdown = () => { | ||
| 144 | + showPartDropdown.value = !showPartDropdown.value; | ||
| 145 | + showSceneDropdown.value = false; | ||
| 146 | +}; | ||
| 147 | + | ||
| 148 | +// 切换场景下拉菜单 | ||
| 149 | +const toggleSceneDropdown = () => { | ||
| 150 | + showSceneDropdown.value = !showSceneDropdown.value; | ||
| 151 | + showPartDropdown.value = false; | ||
| 152 | +}; | ||
| 153 | + | ||
| 154 | +// 选择部位 | ||
| 155 | +const selectPart = (item) => { | ||
| 156 | + activePart.value = item.title; | ||
| 157 | + activePartId.value = item.id; | ||
| 158 | + showPartDropdown.value = false; | ||
| 159 | + // 加这一行:选完部位立刻筛选 | ||
| 160 | + doFilter(); | ||
| 161 | +}; | ||
| 162 | + | ||
| 163 | +// 选择场景 | ||
| 164 | +const selectScene = (item) => { | ||
| 165 | + activeScene.value = item.title; | ||
| 166 | + // 关键:把选中的场景ID存起来 | ||
| 167 | + activeSceneId.value = item.id; | ||
| 168 | + showSceneDropdown.value = false; | ||
| 169 | + // 选择完,立即执行筛选! | ||
| 170 | + doFilter(); | ||
| 171 | +}; | ||
| 172 | + | ||
| 173 | +// 点击页面其他地方关闭所有下拉菜单 | ||
| 174 | +const closeAllDropdowns = () => { | ||
| 175 | + showPartDropdown.value = false; | ||
| 176 | + showSceneDropdown.value = false; | ||
| 177 | +}; | ||
| 178 | +// 核心:执行筛选逻辑 | ||
| 179 | +const doFilter = async () => { | ||
| 180 | + const musclesId = activePartId.value; // 对应接口 musclesId | ||
| 181 | + const scene = activeSceneId.value; // 对应接口 scene | ||
| 182 | + // 2. 判断:是否是【全部不限】 | ||
| 183 | + if (musclesId === '0' && scene === '0') { | ||
| 184 | + // 情况A:都不限 -> 恢复显示【模板大类】 | ||
| 185 | + isFiltering.value = false; | ||
| 186 | + // 大类列表之前已经加载过了,直接显示 | ||
| 187 | + return; | ||
| 188 | + } | ||
| 189 | + // 情况B:有筛选条件 -> 请求【筛选接口】 | ||
| 190 | + try { | ||
| 191 | + isFiltering.value = true; // 切换成模板列表模式 | ||
| 192 | + const res = await TemplatesApi.queryTemplatesByCondition({ | ||
| 193 | + musclesId: musclesId, | ||
| 194 | + scene: scene | ||
| 195 | + }); | ||
| 196 | + if (res.code === 0) { | ||
| 197 | + // 把接口返回的模板数据存起来,页面会自动刷新 | ||
| 198 | + filteredTemplateList.value = res.data; | ||
| 199 | + console.log('筛选接口返回数据filteredTemplateList.value:', filteredTemplateList.value) | ||
| 200 | + } | ||
| 201 | + } catch (error) { | ||
| 202 | + console.log('筛选失败', error); | ||
| 203 | + // 失败了也清空数据 | ||
| 204 | + filteredTemplateList.value = []; | ||
| 205 | + } | ||
| 206 | +}; | ||
| 207 | +// 跳转到模板详情页,传入模板id | ||
| 208 | +// 跳转地址是F:\hongxing-app\hongxing-new\pages4\pages\xunji\xunji-moban-xiangqing.vue | ||
| 209 | +const goToTemplateDetail = (template) => { | ||
| 210 | + uni.navigateTo({ | ||
| 211 | + url: `/pages4/pages/xunji/xunji-moban-xiangqing?id=${template.id}`, | ||
| 212 | + }); | ||
| 213 | +}; | ||
| 214 | +const gototemplate = (item) => { | ||
| 215 | + // 传入的是模板大类的id,可以通过模板大类id查询到模板大类包含的模板,这个页面在page4 | ||
| 216 | + uni.navigateTo({ | ||
| 217 | + url: `/pages4/pages/xunji/xunji-moban?id=${item.id}`, | ||
| 218 | + }); | ||
| 219 | +}; | ||
| 220 | + | ||
| 221 | +onMounted(() => { | ||
| 222 | + TemplatesList(); | ||
| 223 | + getPartCategories(); | ||
| 224 | +}); | ||
| 225 | +</script> | ||
| 226 | + | ||
| 227 | +<style lang="scss" scoped> | ||
| 228 | +.template-page { | ||
| 229 | + width: 100%; | ||
| 230 | + height: 100%; | ||
| 231 | + box-sizing: border-box; | ||
| 232 | + | ||
| 233 | + .filter-section { | ||
| 234 | + position: fixed; | ||
| 235 | + top: 80rpx; | ||
| 236 | + // #ifdef MP-WEIXIN | ||
| 237 | + top: 234rpx; | ||
| 238 | + // #endif | ||
| 239 | + | ||
| 240 | + left: 0; | ||
| 241 | + right: 0; | ||
| 242 | + z-index: 999; | ||
| 243 | + | ||
| 244 | + display: flex; | ||
| 245 | + gap: 24rpx; | ||
| 246 | + flex-wrap: wrap; | ||
| 247 | + padding: 20rpx; | ||
| 248 | + background-color: white; | ||
| 249 | + | ||
| 250 | + .filter-btn { | ||
| 251 | + display: flex; | ||
| 252 | + align-items: center; | ||
| 253 | + justify-content: center; | ||
| 254 | + width: auto; | ||
| 255 | + min-width: 150rpx; | ||
| 256 | + height: 50rpx; | ||
| 257 | + background-color: #fff; | ||
| 258 | + color: #333; | ||
| 259 | + font-size: 24rpx; | ||
| 260 | + border: 2rpx solid #ddd; | ||
| 261 | + border-radius: 25rpx; | ||
| 262 | + | ||
| 263 | + .text { | ||
| 264 | + margin-right: 5rpx; | ||
| 265 | + } | ||
| 266 | + } | ||
| 267 | + } | ||
| 268 | + | ||
| 269 | + .template-list { | ||
| 270 | + margin-top: 120rpx; | ||
| 271 | + padding: 0 30rpx; | ||
| 272 | + box-sizing: border-box; | ||
| 273 | + // 设置明确的高度,使 scroll-view 可以滚动 | ||
| 274 | + height: calc(100vh - 120rpx); | ||
| 275 | + // #ifdef MP-WEIXIN | ||
| 276 | + height: calc(100vh - 274rpx); | ||
| 277 | + // #endif | ||
| 278 | + // 添加底部 padding,避免内容被底部导航栏遮挡 | ||
| 279 | + padding-bottom: 180rpx; | ||
| 280 | + // #ifdef MP-WEIXIN | ||
| 281 | + padding-bottom: 90rpx; | ||
| 282 | + // #endif | ||
| 283 | + | ||
| 284 | + .template-item { | ||
| 285 | + display: flex; | ||
| 286 | + background-color: white; | ||
| 287 | + border-radius: 16rpx; | ||
| 288 | + overflow: hidden; | ||
| 289 | + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); | ||
| 290 | + margin-bottom: 20rpx; | ||
| 291 | + padding: 20rpx; | ||
| 292 | + | ||
| 293 | + .template-img { | ||
| 294 | + width: 120rpx; | ||
| 295 | + height: 120rpx; | ||
| 296 | + border-radius: 12rpx; | ||
| 297 | + margin-right: 20rpx; | ||
| 298 | + } | ||
| 299 | + | ||
| 300 | + .template-content { | ||
| 301 | + flex: 1; | ||
| 302 | + } | ||
| 303 | + | ||
| 304 | + .template-title { | ||
| 305 | + font-size: 32rpx; | ||
| 306 | + color: #333; | ||
| 307 | + margin-bottom: 10rpx; | ||
| 308 | + } | ||
| 309 | + | ||
| 310 | + .template-count { | ||
| 311 | + font-size: 24rpx; | ||
| 312 | + color: #666; | ||
| 313 | + margin-bottom: 10rpx; | ||
| 314 | + } | ||
| 315 | + | ||
| 316 | + .template-desc { | ||
| 317 | + font-size: 24rpx; | ||
| 318 | + color: #666; | ||
| 319 | + line-height: 1.4; | ||
| 320 | + } | ||
| 321 | + } | ||
| 322 | + | ||
| 323 | + /* 筛选包装器 */ | ||
| 324 | + .filter-wrapper { | ||
| 325 | + position: relative; | ||
| 326 | + z-index: 10; | ||
| 327 | + } | ||
| 328 | + | ||
| 329 | + /* 筛选按钮激活状态 */ | ||
| 330 | + .filter-btn.active { | ||
| 331 | + border-color: #43b05e; | ||
| 332 | + background-color: #e6f7f0; | ||
| 333 | + color: #43b05e; | ||
| 334 | + } | ||
| 335 | + | ||
| 336 | + .filter-section { | ||
| 337 | + display: flex; | ||
| 338 | + gap: 24rpx; | ||
| 339 | + padding: 20rpx 30rpx; | ||
| 340 | + background-color: white; | ||
| 341 | + flex-wrap: wrap; | ||
| 342 | + } | ||
| 343 | + | ||
| 344 | + .filter-btn { | ||
| 345 | + display: flex; | ||
| 346 | + align-items: center; | ||
| 347 | + justify-content: center; | ||
| 348 | + width: auto; | ||
| 349 | + min-width: 160rpx; | ||
| 350 | + height: 50rpx; | ||
| 351 | + background: #fff; | ||
| 352 | + color: #333; | ||
| 353 | + font-size: 24rpx; | ||
| 354 | + border: 2rpx solid #ddd; | ||
| 355 | + border-radius: 25rpx; | ||
| 356 | + padding: 0 20rpx; | ||
| 357 | + white-space: nowrap; | ||
| 358 | + } | ||
| 359 | + | ||
| 360 | + .filter-btn .text { | ||
| 361 | + margin-right: 8rpx; | ||
| 362 | + } | ||
| 363 | + | ||
| 364 | + .filter-btn.active { | ||
| 365 | + border-color: #43b05e; | ||
| 366 | + background: #e6f7f0; | ||
| 367 | + color: #43b05e; | ||
| 368 | + } | ||
| 369 | + | ||
| 370 | + .filter-wrapper { | ||
| 371 | + position: relative; | ||
| 372 | + z-index: 10; | ||
| 373 | + } | ||
| 374 | + | ||
| 375 | + .dropdown-menu { | ||
| 376 | + position: absolute; | ||
| 377 | + top: calc(100% + 8rpx); | ||
| 378 | + left: 0; | ||
| 379 | + background: #fff; | ||
| 380 | + border-radius: 14rpx; | ||
| 381 | + box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.08); | ||
| 382 | + padding: 12rpx 0; | ||
| 383 | + z-index: 100; | ||
| 384 | + min-width: 160rpx; | ||
| 385 | + max-height: 320rpx; | ||
| 386 | + overflow-y: auto; | ||
| 387 | + } | ||
| 388 | + | ||
| 389 | + .dropdown-item { | ||
| 390 | + padding: 16rpx 24rpx; | ||
| 391 | + font-size: 26rpx; | ||
| 392 | + color: #333; | ||
| 393 | + white-space: nowrap; | ||
| 394 | + } | ||
| 395 | + | ||
| 396 | + .dropdown-item.selected { | ||
| 397 | + background: #f0f9f4; | ||
| 398 | + color: #2e9d5a; | ||
| 399 | + font-weight: 500; | ||
| 400 | + } | ||
| 401 | + | ||
| 402 | + /* 下拉菜单样式 */ | ||
| 403 | + .dropdown-menu { | ||
| 404 | + position: absolute; | ||
| 405 | + top: calc(100% + 8rpx); | ||
| 406 | + left: 0; | ||
| 407 | + background: #fff; | ||
| 408 | + border-radius: 14rpx; | ||
| 409 | + box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.08); | ||
| 410 | + padding: 12rpx 0; | ||
| 411 | + z-index: 100; | ||
| 412 | + min-width: 150rpx; | ||
| 413 | + max-height: 320rpx; | ||
| 414 | + overflow-y: auto; | ||
| 415 | + border: 1rpx solid #f0f0f0; | ||
| 416 | + } | ||
| 417 | + | ||
| 418 | + /* 下拉菜单项样式 */ | ||
| 419 | + .dropdown-item { | ||
| 420 | + padding: 16rpx 24rpx; | ||
| 421 | + font-size: 26rpx; | ||
| 422 | + color: #333; | ||
| 423 | + } | ||
| 424 | + | ||
| 425 | + /* 下拉菜单项悬停样式 */ | ||
| 426 | + .dropdown-item:hover { | ||
| 427 | + background-color: #43b05e; | ||
| 428 | + } | ||
| 429 | + | ||
| 430 | + /* 下拉菜单项选中样式 */ | ||
| 431 | + .dropdown-item.selected { | ||
| 432 | + background-color: #f0f9f4; | ||
| 433 | + color: #2e9d5a; | ||
| 434 | + font-weight: 500; | ||
| 435 | + } | ||
| 436 | + } | ||
| 437 | +} | ||
| 438 | +</style> |
-
Please register or login to post a comment