update:添加多项目

This commit is contained in:
ltx 2026-07-01 15:10:17 +08:00
parent 2e9154085b
commit 4c4c83c259
19 changed files with 1096 additions and 43 deletions

View File

@ -48,6 +48,7 @@
"train/user-report/user-report",
"train/check-type/check-page",
"train/train/train",
"train/select-sport/select-sport",
"bluebooth/search",
"train/bluetooth/train",
"train/result/result",

View File

@ -51,7 +51,7 @@ page {
}
.chose {
margin: 34rpx 20rpx 0;
margin: 64rpx 30rpx 0;
text-align: center;
color: #fff;
/* background-color: brown; */
@ -62,7 +62,7 @@ page {
align-items: center;
flex-direction: column;
justify-content: center;
border-radius: 20rpx 20rpx 0 0;
border-radius: 20rpx;
}
@ -75,8 +75,11 @@ page {
justify-content: space-evenly;
align-items: center;
background-color: #fff;
font-size: 24rpx;
font-size: 26rpx;
padding: 20rpx 0;
margin: 26rpx 30rpx 20rpx;
border-radius: 20rpx;
box-shadow:0 0 12px #0000000d;
}
.lab-item {
@ -87,10 +90,10 @@ page {
}
.lab-img {
width: 48rpx;
height: 48rpx;
width: 64rpx;
height: 64rpx;
/* margin-top: 20rpx; */
margin-bottom: 20rpx;
margin-bottom: 10rpx;
}
.cell {
@ -103,6 +106,7 @@ page {
align-items: center;
font-size: 26rpx;
margin: 20rpx 30rpx 0 30rpx;
box-shadow:0 0 12px #0000000d;
}
.left {

View File

@ -147,10 +147,16 @@ Page({
},
goTrainer(e){ //去训练
console.log('aaaa')
wx.navigateTo({
url: '/subpackage/train/train/train?type='+ e.currentTarget.id.replace('.png',''),
})
const type = e.currentTarget.id.replace('.png','')
if(type === 'fit'){
wx.navigateTo({
url: '/subpackage/train/select-sport/select-sport',
})
} else {
wx.navigateTo({
url: '/subpackage/train/train/train?type=' + type,
})
}
},
// 下拉刷新

View File

@ -47,6 +47,8 @@ page{
background: linear-gradient(180deg, #e8f5fd 0%, #fff 100%);
/* height: 480rpx; */
padding: 29rpx;
box-shadow:0 0 12px #0000000d;
}
.goal{

View File

@ -55,7 +55,7 @@
"compileType": "miniprogram",
"libVersion": "3.7.12",
"appid": "wx9d179138d229d06b",
"projectname": "YZ-Ai-Sport-Native-Demo",
"projectname": "跃动",
"condition": {},
"editorSetting": {
"tabIndent": "insertSpaces",

View File

@ -24,12 +24,19 @@
"condition": {
"miniprogram": {
"list": [
{
"name": "subpackage/train/result/result",
"pathName": "subpackage/train/result/result",
"query": "type=time&count=0&time=3&theme=myself&isAi=true&sportType=%E4%BF%AF%E5%8D%A7%E6%92%91",
"scene": null,
"launchMode": "default"
},
{
"name": "module-sport/pages/sport-full-screen",
"pathName": "module-sport/pages/sport-full-screen",
"query": "sportKey=Rope-Skipping&time=120&number=20&type=num&theme=homeWork&homeWorkId=51&groupNumber=2&timeout=3&music=https%3A%2F%2Fyuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com%2FUpload%2FBackgroundMusic%2FCarly%2520Rae%2520Jepsen-Turn%2520Me%2520Up.mp3",
"scene": null,
"launchMode": "default"
"launchMode": "default",
"scene": null
},
{
"name": "subpackage/train/check-type/check-page",

View File

@ -8,7 +8,12 @@ Page({
connectedDeviceId: null,
deviceServices: [],
deviceCharacteristics: [],
blueIsOk: false
blueIsOk: false,
battery: null,
batteryLoading: false,
deviceName: '',
nameInput: '',
nameLoading: false
},
// 1. 初始化蓝牙模块
@ -133,7 +138,7 @@ Page({
wx.onBluetoothDeviceFound((res) => {
console.log('发现新设备', res.devices);
res.devices.forEach(device => {
if (device.name == 'YD-Jump' || device.name == 'YD_Jump' || device.name == 'YD-O1' || device.localName == 'YD-O1' || device.localName == 'YD-Jump' || device.localName == 'YD_Jump') {
if (device.name == 'YD-Jump' || device.name == 'YD_Jump' || device.name == 'YD-O1' || device.localName == 'YD-O1' || device.localName == 'YD-Jump' || device.localName == 'YD_Jump') {
const exists = this.data.devices.find(d => d.deviceId === device.deviceId);
if (!exists) {
if (device.deviceId == last_device_id) {
@ -190,7 +195,7 @@ Page({
deviceId: device.deviceId,
success: () => {
console.log('连接成功', device.deviceId);
console.log('连接成功', device);
console.log('连接成功的设备信息:', JSON.stringify(device));
wx.showToast({
title: '连接成功',
icon: 'success'
@ -204,14 +209,20 @@ Page({
wx.setStorageSync('device_id', device.deviceId)
wx.setStorageSync('device_name', device.name)
if (device.name == 'YD-O1') {
wx.setStorageSync('uuid', device.advertisServiceUUIDs[0])
const serviceId = device.advertisServiceUUIDs[0];
wx.setStorageSync('uuid', serviceId)
this.setupWriteAndNotify(device.deviceId, serviceId);
} else {
// 获取设备的服务
wx.getBLEDeviceServices({
deviceId: device.deviceId,
success: (res) => {
console.log('设备服务', res.services);
wx.setStorageSync('uuid', res.services[0].uuid)
// YD_Jump(下划线)使用 services[1],YD-Jump(短横线)使用 services[0]
const serviceIndex = device.name === 'YD_Jump' ? 1 : 0;
const serviceId = res.services[serviceIndex].uuid;
wx.setStorageSync('uuid', serviceId)
this.setupWriteAndNotify(device.deviceId, serviceId);
},
fail: (err) => {
console.error('获取服务失败', err);
@ -373,6 +384,69 @@ Page({
});
},
// 连接成功后获取特征值、存储写特征并建立通知监听
setupWriteAndNotify(deviceId, serviceId) {
wx.getBLEDeviceCharacteristics({
deviceId,
serviceId,
success: (res) => {
const writeChar = res.characteristics.find(c => c.properties.write || c.properties.writeNoResponse);
if (writeChar) {
wx.setStorageSync('writeCharacteristic', writeChar.uuid);
}
const notifyChar = res.characteristics.find(c => c.properties.notify);
if (notifyChar) {
wx.notifyBLECharacteristicValueChange({
deviceId,
serviceId,
characteristicId: notifyChar.uuid,
state: true,
});
wx.onBLECharacteristicValueChange((result) => {
const deviceName = wx.getStorageSync('device_name');
if (deviceName === 'YD-O1') {
// YD-O1: 二进制协议解析电量响应
const int16Array = new Int16Array(result.value);
if (int16Array.length > 0) {
const battery = int16Array[0]; // 根据实际协议调整字段索引
if (battery >= 0 && battery <= 100) {
this.setData({ battery, batteryLoading: false });
wx.hideLoading();
}
}
} else {
// YD_Jump / YD-Jump: 字符串协议解析 $Batt,xx!
const bytes = new Uint8Array(result.value);
let str = '';
for (let i = 0; i < bytes.length; i++) {
str += String.fromCharCode(bytes[i]);
}
console.log('收到蓝牙数据:', str);
// 解析电量响应 $Batt,xx!
const battMatch = str.match(/\$Batt,(\d+)!/);
if (battMatch) {
this.setData({ battery: parseInt(battMatch[1], 10), batteryLoading: false });
wx.hideLoading();
}
// 解析获取名称响应 $My Name:xxxxxxxx!
const nameMatch = str.match(/\$My Name:(.+)!/);
if (nameMatch) {
this.setData({ deviceName: nameMatch[1].trim(), nameLoading: false });
wx.hideLoading();
}
// 解析修改名称响应 $Set Name OK!
if (str.includes('$Set Name OK!')) {
wx.hideLoading();
wx.showToast({ title: '名称修改成功', icon: 'success' });
}
}
});
}
},
fail: (err) => console.error('获取特征值失败', err)
});
},
// 4. 断开连接
disconnectDevice(id) {
wx.closeBLEConnection({
@ -381,9 +455,145 @@ Page({
console.log('断开连接');
this.setData({
status: '未连接',
connected: false
connected: false,
battery: null,
batteryLoading: false,
deviceName: '',
nameInput: '',
nameLoading: false
});
}
});
},
// 5. 获取设备电量(按设备类型发送不同协议命令)
getBattery() {
if (!this.data.connected || !this.data.connectedDeviceId) {
wx.showToast({ title: '请先连接设备', icon: 'none' });
return;
}
const characteristic = wx.getStorageSync('writeCharacteristic');
if (!characteristic) {
wx.showToast({ title: '特征值未就绪,请重新连接', icon: 'none' });
return;
}
const deviceId = this.data.connectedDeviceId;
const serviceId = wx.getStorageSync('uuid');
const deviceName = wx.getStorageSync('device_name');
wx.showLoading({ title: '获取电量中...' });
this.setData({ batteryLoading: true });
let commandData;
if (deviceName === 'YD-O1') {
// YD-O1: 二进制协议获取电量
commandData = new ArrayBuffer(4);
const dataView = new DataView(commandData);
dataView.setUint8(0, 0x07); // 获取电量命令字节
dataView.setUint8(1, 0x00);
dataView.setUint8(2, 0x00);
dataView.setUint8(3, 0x00);
} else {
// YD_Jump / YD-Jump: 字符串协议获取电量
commandData = this.stringToArrayBuffer('$Get Batt!');
}
wx.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId: characteristic,
value: commandData,
success: () => {
console.log(`${deviceName} 发送获取电量命令成功`);
},
fail: (err) => {
console.error('发送命令失败', err);
wx.hideLoading();
this.setData({ batteryLoading: false });
wx.showToast({ title: '发送命令失败', icon: 'none' });
}
});
},
// 获取设备名称
getDeviceName() {
if (!this.data.connected || !this.data.connectedDeviceId) {
wx.showToast({ title: '请先连接设备', icon: 'none' });
return;
}
const characteristic = wx.getStorageSync('writeCharacteristic');
if (!characteristic) {
wx.showToast({ title: '特征值未就绪,请重新连接', icon: 'none' });
return;
}
const deviceId = this.data.connectedDeviceId;
const serviceId = wx.getStorageSync('uuid');
wx.showLoading({ title: '获取名称中...' });
this.setData({ nameLoading: true });
wx.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId: characteristic,
value: this.stringToArrayBuffer('$Get Name!'),
success: () => console.log('发送获取名称命令成功'),
fail: (err) => {
console.error('发送命令失败', err);
wx.hideLoading();
this.setData({ nameLoading: false });
wx.showToast({ title: '发送命令失败', icon: 'none' });
}
});
},
// 修改设备名称(名称不足8位用空格填充,超过8位截断)
setDeviceName() {
if (!this.data.connected || !this.data.connectedDeviceId) {
wx.showToast({ title: '请先连接设备', icon: 'none' });
return;
}
const nameInput = this.data.nameInput.trim();
if (!nameInput) {
wx.showToast({ title: '请输入新名称', icon: 'none' });
return;
}
const characteristic = wx.getStorageSync('writeCharacteristic');
if (!characteristic) {
wx.showToast({ title: '特征值未就绪,请重新连接', icon: 'none' });
return;
}
const deviceId = this.data.connectedDeviceId;
const serviceId = wx.getStorageSync('uuid');
// 名称固定8字节:不足补空格,超过截断
const paddedName = nameInput.padEnd(8, ' ').substring(0, 8);
const cmd = `$Set Name:${paddedName}!`;
console.log('发送修改名称命令:', cmd);
wx.showLoading({ title: '修改名称中...' });
wx.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId: characteristic,
value: this.stringToArrayBuffer(cmd),
success: () => console.log('修改名称命令已发送:', cmd),
fail: (err) => {
console.error('发送命令失败', err);
wx.hideLoading();
wx.showToast({ title: '发送命令失败', icon: 'none' });
}
});
},
// 名称输入框变化
onNameInput(e) {
this.setData({ nameInput: e.detail.value });
},
// 字符串转 ArrayBuffer(与 bluetoothServe.js 保持一致)
stringToArrayBuffer(str) {
const buffer = new ArrayBuffer(str.length);
const view = new Uint8Array(buffer);
for (let i = 0; i < str.length; i++) {
view[i] = str.charCodeAt(i) & 0xFF;
}
return buffer;
}
});

View File

@ -44,4 +44,44 @@
<!-- 断开连接按钮 -->
<!-- <button bindtap="disconnectDevice" wx:if="{{connected}}">断开连接</button> -->
<!-- 获取电量 -->
<view class="battery-section" wx:if="{{connected}}">
<view class="titlee">
<view class="titlee-before"></view>设备电量
</view>
<view class="battery-card">
<view class="battery-display" wx:if="{{battery !== null}}">
<view class="battery-icon-wrap">
<view class="battery-icon">
<view class="battery-level" style="width: {{battery}}%;"></view>
</view>
<view class="battery-cap"></view>
</view>
<text class="battery-percent">{{battery}}%</text>
</view>
<view class="battery-placeholder" wx:else>
<text>--</text>
</view>
<button class="battery-btn" bindtap="getBattery" loading="{{batteryLoading}}">获取电量</button>
</view>
</view>
<!-- 设备名称管理 -->
<view class="name-section" wx:if="{{connected}}">
<view class="titlee">
<view class="titlee-before"></view>设备名称
</view>
<view class="name-card">
<view class="name-info">
<text class="name-label">当前名称</text>
<text class="name-value">{{deviceName || '--'}}</text>
</view>
<button class="name-action-btn" bindtap="getDeviceName" loading="{{nameLoading}}">获取名称</button>
</view>
<view class="name-edit-card">
<input class="name-input" placeholder="输入新名称(最多8位)" maxlength="8" value="{{nameInput}}" bindinput="onNameInput" />
<button class="name-action-btn" bindtap="setDeviceName">修改名称</button>
</view>
</view>
</view>

View File

@ -166,3 +166,149 @@ button {
margin-top: 20rpx;
font-size: 30rpx;
}
/* 电量区域 */
.battery-section {
margin-top: 10rpx;
}
.battery-card {
margin: 0 30rpx;
background-color: #fff;
border-radius: 20rpx;
box-shadow: 0 10rpx 10rpx rgba(0, 0, 0, 0.06);
padding: 40rpx 30rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.battery-display {
display: flex;
align-items: center;
gap: 20rpx;
}
.battery-placeholder {
font-size: 60rpx;
color: #ccc;
font-weight: bold;
line-height: 1;
}
.battery-icon-wrap {
display: flex;
align-items: center;
}
.battery-icon {
width: 80rpx;
height: 40rpx;
border: 4rpx solid #333;
border-radius: 6rpx;
padding: 4rpx;
box-sizing: border-box;
background-color: #f5f5f5;
overflow: hidden;
}
.battery-level {
height: 100%;
border-radius: 3rpx;
background: linear-gradient(90deg, #4cd964, #26a5f6);
transition: width 0.4s ease;
}
.battery-cap {
width: 8rpx;
height: 18rpx;
background-color: #333;
border-radius: 0 3rpx 3rpx 0;
margin-left: 2rpx;
}
.battery-percent {
font-size: 48rpx;
font-weight: bold;
color: #333;
}
.battery-btn {
margin: 0 !important;
padding: 0 30rpx !important;
height: 70rpx;
line-height: 70rpx;
font-size: 26rpx;
background-color: #007aff;
color: white;
border-radius: 50rpx;
min-width: 160rpx;
}
/* 名称管理区域 */
.name-section {
margin-top: 10rpx;
}
.name-card {
margin: 0 30rpx;
background-color: #fff;
border-radius: 20rpx;
box-shadow: 0 10rpx 10rpx rgba(0, 0, 0, 0.06);
padding: 30rpx;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
}
.name-info {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.name-label {
font-size: 24rpx;
color: #999;
}
.name-value {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.name-action-btn {
margin: 0 !important;
padding: 0 30rpx !important;
height: 70rpx;
line-height: 70rpx;
font-size: 26rpx;
background-color: #007aff;
color: white;
border-radius: 50rpx;
min-width: 160rpx;
}
.name-edit-card {
margin: 0 30rpx;
background-color: #fff;
border-radius: 20rpx;
box-shadow: 0 10rpx 10rpx rgba(0, 0, 0, 0.06);
padding: 30rpx;
display: flex;
align-items: center;
gap: 20rpx;
}
.name-input {
flex: 1;
height: 70rpx;
border: 2rpx solid #e0e0e0;
border-radius: 12rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
}

View File

@ -4,10 +4,11 @@ const COMMAND = {
PAUSE: 0x04,
RESUME: 0x05,
STOP: 0x06,
GET_COUNT: 'Get CT',
GET_COUNT: 'Get CT',
START_COUNT: 'Start Count',
STOP_COUNT: 'Stop Count',
CLEAN_COUNT: 'Clean Count'
CLEAN_COUNT: 'Clean Count',
GET_BATTERY: 'Get Batt'
};
const DEVICE_TYPE = {
@ -73,9 +74,40 @@ function stringToArrayBuffer(str) {
return buffer;
}
// 获取设备电量,根据设备型号发送不同协议的命令
function getBattery(deviceId, serviceId, deviceName) {
const characteristic = wx.getStorageSync('writeCharacteristic');
if (!characteristic) {
console.error('没有可写的特征值');
return;
}
if (deviceName === DEVICE_TYPE.YD_O1) {
// YD-O1:二进制协议获取电量
const commandData = new ArrayBuffer(4);
const dataView = new DataView(commandData);
dataView.setUint8(0, 0x07); // 获取电量命令字节
dataView.setUint8(1, 0x00);
dataView.setUint8(2, 0x00);
dataView.setUint8(3, 0x00);
wx.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId: characteristic,
value: commandData,
success: () => console.log('YD-O1 获取电量命令已发送'),
fail: err => console.error('YD-O1 获取电量命令失败', err)
});
} else {
// YD_Jump / YD-Jump:字符串协议获取电量
sendCommandJump(deviceId, serviceId, COMMAND.GET_BATTERY);
}
}
module.exports = {
COMMAND,
DEVICE_TYPE,
startFreeJumpRope,
sendCommandJump
sendCommandJump,
getBattery
};

View File

@ -58,7 +58,7 @@ Page({
})
if (who == 'user') { //用户端仅有跳绳======================================================================================
if (theme == 'team') {
if (theme == 'team') {
//2.添加团队任务成绩
const {
data
@ -77,14 +77,23 @@ Page({
//1.添加个人目标成绩
const app = getApp()
console.log(app)
console.log('自由训练')
const homeWorkList = await getHomeWorkType()
const sportObj = homeWorkList.data.filter(item => item.name == options.sportType)
let isTime = options.type == 'time'
let calorie = isTime ? options.count * sportObj[0].kcalIndex : amount * sportObj[0].kcalIndex * groupNumber
let params = {
"personalGoalName": this.data.theme == 'small' ? '小学体测' : this.data.theme == 'fit' ? '健身减肥' : '中学考试',
"workModeType": 1,
"workModeTypeName": this.data.theme == 'small' ? '小学体测' : this.data.theme == 'fit' ? '健身减肥' : '中学考试',
"workType": sportObj[0].id,
"workTypeName": sportType,
"workModeType": isTime ? 1 : 2,
"workModeTypeName": isTime ? '定时计数' : this.data.type == 'num' ? '定数计时' : '自由训练',
"duration": time,
"amount": count,
"PersonalGoalType": this.data.theme == 'small' ? 2 : this.data.theme == 'fit' ? 1 : 2,
"dataSourceType": isAi == 'true' ? 2 : 1
"dataSourceType": isAi == 'true' ? 2 : 1,
"calorie":calorie,
"startTime":this.getCurrentDateTime(),
"endTime":this.getCurrentDateTime(),
"groupNumber":1
}
if (this.data.theme == 'small') {
const dd = await getSmallScore({
@ -165,7 +174,7 @@ Page({
console.log(params)
const res = await postParentTrain(params)
}
}
}
},

View File

@ -0,0 +1,39 @@
const { sportKeyList } = require('../../../utils/soprt-key');
const sportEmojiMap = {
'Rope-Skipping': '🪢',
'Jumping-Jack': '🤸',
'Sit-Up': '💪',
'Push-Up': '🏋️',
'Squat': '🦵',
'Plank': '⚡',
'High-Knee': '🏃',
'Sit-Forward-Bend': '🧘',
};
const colorClasses = ['c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7'];
Page({
data: {
sportList: []
},
onLoad(options) {
const excluded = ['Squat-Horse', 'Crunch'];
const sportList = sportKeyList
.filter(item => !excluded.includes(item.key))
.map((item, index) => ({
...item,
emoji: sportEmojiMap[item.key] || '🏃',
colorClass: colorClasses[index % colorClasses.length],
}));
this.setData({ sportList });
},
selectSport(e) {
const sportKey = e.currentTarget.id;
wx.navigateTo({
url: '/subpackage/train/train/train?type=fit&sportKey=' + sportKey,
});
}
});

View File

@ -0,0 +1,8 @@
{
"usingComponents": {
"mp-bar": "weui-miniprogram/navigation-bar/navigation-bar"
},
"navigationStyle": "custom",
"backgroundColor": "#f0f5ff",
"backgroundColorContent": "#f0f5ff"
}

View File

@ -0,0 +1,55 @@
<view class="page page-content">
<mp-bar background="linear-gradient(190deg, #49a5fc 0%, #f7f7f7 800rpx, #f7f7f7 100%)" title="选择运动" color="#fff"></mp-bar>
<view class="play">
<view class="play-text">
<view class="text-style">
一键开启
<view class="text-line"></view>
</view>
<view>定制专属你的减脂蜕变</view>
</view>
<image src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/user-play.png" class="logo-play" />
</view>
<view class="tab {{theme=='fit'?'tab-fit':''}}">
<view class="tab-l">
<view class="tab-l-content">{{
theme=='small'?'小学体测':theme=='mid'?'中学考试':'健身减肥'
}}</view>
</view>
<view class="tab-r" bind:tap="goSetting">
<view class="tab-r-content">
<mp-icon icon="setting" class="iconn" color="#fff" type="field" size="{{16}}"></mp-icon>
运动偏好
</view>
</view>
</view>
<!-- 2 列卡片网格 -->
<view class="card1">
<view class="grid">
<view
class="card {{item.colorClass}}"
wx:for="{{sportList}}"
wx:key="key"
id="{{item.key}}"
bind:tap="selectSport"
>
<view class="card-circle-1"></view>
<view class="card-circle-2"></view>
<!-- <view class="card-emoji-wrap">
<image src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/icon/{{item.key}}.png" alt=""/>
</view> -->
<view class="card-bottom">
<view class="card-name">{{item.name}}</view>
<view class="card-arrow-circle">
<text class="card-arrow-text">›</text>
</view>
</view>
</view>
</view>
</view>
</view>

View File

@ -0,0 +1,357 @@
page{
background-color: rgb(247, 247, 247);
}
.page-content{
background-color: rgb(247, 247, 247);
background: linear-gradient(190deg, #49a5fc 0%, #f7f7f7 800rpx, #f7f7f7 100%);
padding: 0 30rpx;
box-sizing: border-box;
min-height: 100vh;
padding-bottom: 80rpx;
}
/* ===== 头部 ===== */
.header {
/* background: linear-gradient(160deg, #0d1f3c 0%, #0d2a55 100%); */
padding: 0 44rpx 52rpx;
position: relative;
overflow: hidden;
}
.header-glow {
position: absolute;
width: 500rpx;
height: 500rpx;
border-radius: 50%;
background: radial-gradient(circle, rgba(0,130,255,0.2) 0%, transparent 70%);
top: -180rpx;
right: -120rpx;
pointer-events: none;
}
.header-content {
position: relative;
z-index: 1;
padding-top: 6rpx;
}
.header-kicker {
font-size: 22rpx;
color: #4fc3f7;
letter-spacing: 3rpx;
margin-bottom: 18rpx;
font-weight: 500;
}
.header-title {
font-size: 58rpx;
font-weight: bold;
color: #fff;
line-height: 1.15;
margin-bottom: 22rpx;
letter-spacing: 1rpx;
}
.header-meta {
display: flex;
align-items: center;
gap: 14rpx;
font-size: 24rpx;
color: rgba(255,255,255,0.45);
}
.meta-dot {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: #4fc3f7;
opacity: 0.7;
flex-shrink: 0;
}
/* ===== 2列卡片网格 ===== */
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 28rpx;
padding: 30rpx 0 ;
box-sizing: border-box;
}
/* 单张卡片 */
.card {
border-radius: 22rpx;
padding: 28rpx 24rpx 24rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
/* height: 272rpx; */
position: relative;
overflow: hidden;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.12);
}
.card:active {
opacity: 0.82;
transform: scale(0.96);
}
/* 卡片内装饰圆 */
.card-circle-1 {
position: absolute;
width: 200rpx;
height: 200rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.12);
top: -80rpx;
right: -60rpx;
pointer-events: none;
}
.card-circle-2 {
position: absolute;
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
bottom: -30rpx;
left: -20rpx;
pointer-events: none;
}
/* emoji 区域:占据卡片中局上半部分 */
.card-emoji-wrap {
flex: 1;
display: flex;
align-items: center;
position: relative;
z-index: 1;
}
.card-emoji-wrap image {
width: 100rpx;
height: 100rpx;
}
.card-emoji {
font-size: 80rpx;
line-height: 1;
filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.15));
}
/* 底部:名称 + 箭头同一行 */
.card-bottom {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
z-index: 1;
}
.card-name {
font-size: 30rpx;
font-weight: bold;
color: #fff;
line-height: 1;
text-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.15);
}
.card-arrow-circle {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.25);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.card-arrow-text {
font-size: 32rpx;
color: #fff;
line-height: 1;
margin-top: -2rpx;
padding-left: 2rpx;
}
/* ===== 8种卡片颜色 ===== */
.c0 { background: linear-gradient(140deg, #ff6b6b 0%, #ee2d2d98 100%); }
.c1 { background: linear-gradient(140deg, #ffa340 0%, #f0600098 100%); }
.c2 { background: linear-gradient(140deg, #20c997 0%, #0a8f6098 100%); }
.c3 { background: linear-gradient(140deg, #a855f7 0%, #6d28d998 100%); }
.c4 { background: linear-gradient(140deg, #3b82f6 0%, #1d4ed898 100%); }
.c5 { background: linear-gradient(140deg, #06b6d4 0%, #0284c798 100%); }
.c6 { background: linear-gradient(140deg, #f43f8e 0%, #be185d98 100%); }
.c7 { background: linear-gradient(140deg, #34d399 0%, #05966998 100%); }
.title{
font-weight: bold;
font-size: 32rpx;
display: flex;
align-items: center;
justify-content: center;
margin-top: 66rpx;
margin-bottom: 20rpx;
}
.title-before{
height: 24rpx;
border-radius: 6rpx;
width: 6rpx;
margin-right: 20rpx;
background:linear-gradient(180.00deg, rgb(38, 165, 246),rgba(38, 165, 246, 0) 100%)
}
.play{
padding: 0 30rpx 0 30rpx;
box-sizing: border-box;
display: flex;
justify-content: space-between;
/* margin-bottom: -170rpx; */
/* min-height: 428rpx; */
height: 350rpx;
overflow: hidden;
}
.logo-play{
margin-right: -82rpx;
width: 428rpx;
height: 428rpx;
/* z-index: 1; */
/* overflow: hidden; */
}
.play-text{
font-size: 28rpx;
padding-top: 70rpx;
}
.text-style{
font-weight: bold;
}
.text-line{
height: 16rpx;
background-color: #FFC20E;
border-radius: 10rpx;
width: 110rpx;
margin-top: -18rpx;
margin-bottom: 10rpx;
}
.tab{
width: 536rpx;
display: flex;
height: 80rpx;
font-size: 28rpx;
margin-top: -80rpx;
font-weight: bold;
}
.tab-l{
flex: 1;
background-color: rgba(38, 165, 246, 1);
/* backdrop-filter: blur(20rpx); */
border-radius: 20rpx 0 0 0;
color: #000;
}
.tab-l-content{
background-color: #fff;
border-radius: 20rpx 38rpx 0 0;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
}
.tab-r{
flex: 1;
background:linear-gradient(90deg,rgba(255,255,255,1) 0%, rgba(255,255,255,.6) 30%) ;
border-radius: 0 20rpx 0 0rpx;
}
.tab-r-content{
background: linear-gradient(90deg, rgba(38, 165, 246, 1) 0%, rgba(38, 165, 246, 0.7) 50%,
rgba(38, 165, 246, 0.7) 100%);
border-radius: 0 20rpx 0 38rpx;
display: flex;
align-items: center;
justify-content: center;
height: 80rpx;
color: #fff;
}
.card1{
width: 100%;
background: #fff;
border-radius: 0 20rpx 20rpx 20rpx;
padding: 16rpx 18rpx 24rpx;
box-sizing: border-box;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
}
.iconn{
margin-right: 6rpx;
margin-top: -6rpx;
}
.cell{
padding: 44rpx 4rpx;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 26rpx;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.left{
display: flex;
align-items: center;
}
.left-img{
width: 48rpx;
height: 48rpx;
margin-right: 26rpx;
}
.desc{
text-align: center;
font-size: 26rpx;
max-width: 60%;
text-align: left;
}
.name{
margin-top: 4rpx;
font-size: 24rpx;
color: #777777;
}
.right{
border-radius: 75px;
background: rgb(38, 166, 246);
padding: 12rpx 0rpx;
width: 120rpx;
color: #fff;
font-size: 24rpx;
box-sizing: border-box;
text-align: center;
width: 160rpx;
min-width: 160rpx;
}
.use{
color: rgb(38, 166, 246);
background-color: #fff;
border: 2rpx solid rgb(38, 166, 246);
}

View File

@ -1,5 +1,6 @@
const tabService = require("../../../utils/tab-serve");
const util = require("../../../utils/utils")
const { sportKeyObj, sportDes } = require('../../../utils/soprt-key')
import {getAreaList} from "../../../utils/serve/train"
const app = getApp()
Page({
@ -14,6 +15,10 @@ Page({
number: 100, //计数
timeStr:'01:00',
theme:'small',
sportKey: '',
sportName: '',
sportDesc: '',
videoUrl: '',
gradeList:[
'一年级',
'二年级',
@ -34,8 +39,16 @@ Page({
*/
async onLoad(options) {
this.setData({
theme:options.type
theme: options.type
})
if (options.sportKey) {
this.setData({
sportKey: options.sportKey,
sportName: sportKeyObj[options.sportKey] || '',
sportDesc: sportDes[options.sportKey] || '',
videoUrl: 'https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/train/video/new/' + options.sportKey + '.mp4',
})
}
if(options.type == 'mid'){
const res = await getAreaList()
this.setData({
@ -132,12 +145,17 @@ Page({
if(this.data.sex == '' && this.data.theme != 'fit') return app.showToast('请选择性别')
if(this.data.theme == 'small' && this.data.grade == '') return app.showToast('请选择年级')
if(this.data.theme == 'mid' && this.data.areaId == '') return app.showToast('请选择省份')
console.log(getApp())
const data = `&type=${this.data.tabName}&time=${this.data.time}&number=${this.data.number}&theme=${this.data.theme}`
wx.navigateTo({
// url: '/module-sport/pages/sport-full-screen?sportKey=Rope-Skipping'+data,
url: '/subpackage/train/check-type/check-page?sportKey=Rope-Skipping'+data,
})
const sportKey = this.data.sportKey || 'Rope-Skipping'
const data = `&type=${this.data.tabName}&time=${this.data.time}&number=${this.data.number}&theme=myself`
if(this.data.sportKey == 'Rope-Skipping' ){
wx.navigateTo({
url: '/subpackage/train/check-type/check-page?sportKey=' + this.data.sportKey + data,
})
}else{
wx.navigateTo({
url: '/module-sport/pages/sport-full-screen?sportKey=' + this.data.sportKey + data,
})
}
},
handleCancel() {

View File

@ -1,7 +1,8 @@
<view class="page-content">
<mp-bar background="linear-gradient(190deg, #49a5fc 0%, #f7f7f7 800rpx, #f7f7f7 100%)" title="训练"></mp-bar>
<view class="play">
<!-- 非健身减肥模式:展示原来的 banner -->
<view class="play" wx:if="{{theme != 'fit'}}">
<view class="play-text">
<view class="text-style">
一键开启
@ -12,7 +13,35 @@
<image src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/user-play.png" class="logo-play" />
</view>
<view class="tab">
<!-- 健身减肥模式:展示运动演示视频 -->
<view class="play-fit" wx:if="{{theme == 'fit'}}">
<view class="play-fit-header">
<view class="play-fit-name">{{sportName}}</view>
<view class="play-fit-tag">健身减肥</view>
</view>
<view class="video-wrap">
<video
class="sport-video"
src="{{videoUrl}}"
autoplay
loop
muted
show-play-btn="{{true}}"
show-fullscreen-btn="{{true}}"
object-fit="cover"
poster="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/user-play.png"
></video>
</view>
<view class="sport-desc-card" wx:if="{{sportDesc}}">
<view class="sport-desc-title">
<!-- <view class="desc-icon"></view> -->
动作说明
</view>
<view class="sport-desc-text">{{sportDesc}}</view>
</view>
</view>
<!-- <view class="tab {{theme=='fit'?'tab-fit':''}}">
<view class="tab-l">
<view class="tab-l-content">{{
theme=='small'?'小学体测':theme=='mid'?'中学考试':'健身减肥'
@ -24,7 +53,7 @@
运动偏好
</view>
</view>
</view>
</view> -->
<view class="card">
<block wx:if="{{theme == 'fit'}}">

View File

@ -74,6 +74,10 @@ page{
margin-top: -80rpx;
}
.tab-fit {
margin-top: 0;
}
.tab-l{
flex: 1;
background-color: rgba(38, 165, 246, 1);
@ -114,10 +118,10 @@ page{
min-height: 1000rpx;
width: 100%;
background: #fff;
border-radius: 0 20rpx 20rpx 20rpx;
border-radius: 20rpx;
padding: 60rpx 100rpx;
box-sizing: border-box;
box-shadow: 1px 1px 4px 0px rgba(0, 0, 0, 0.04);
box-shadow: 1px 1px 4px 2px rgba(0, 0, 0, 0.04);
}
.iconn{
@ -223,5 +227,76 @@ page{
.click{
color: #0082FF;
background-color: #e9f6fe;
}
}
/* ===== 健身减肥模式 - 视频展示区 ===== */
.play-fit {
/* padding: 0 30rpx; */
box-sizing: border-box;
margin-bottom: 20rpx;
}
.play-fit-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 0 20rpx 0;
}
.play-fit-name {
font-size: 44rpx;
font-weight: bold;
color: #fff;
}
.play-fit-tag {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.9);
background: rgba(255, 255, 255, 0.25);
border-radius: 20rpx;
padding: 6rpx 20rpx;
backdrop-filter: blur(10rpx);
}
.video-wrap {
width: 100%;
border-radius: 24rpx;
overflow: hidden;
box-shadow: 0 8rpx 32rpx rgba(0, 130, 255, 0.2);
/* background: #000; */
}
.sport-video {
width: 100%;
height: 400rpx;
display: block;
}
.sport-desc-card {
margin-top: 20rpx;
background: rgba(255, 255, 255, 0.95);
border-radius: 20rpx;
padding: 28rpx 30rpx;
box-sizing: border-box;
box-shadow: 0 4rpx 16rpx rgba(0, 130, 255, 0.1);
}
.sport-desc-title {
display: flex;
align-items: center;
font-size: 28rpx;
font-weight: bold;
color: #1a1a2e;
margin-bottom: 16rpx;
}
.desc-icon {
margin-right: 10rpx;
font-size: 28rpx;
}
.sport-desc-text {
font-size: 26rpx;
color: #555;
line-height: 1.8;
}

View File

@ -56,8 +56,23 @@ let sportKeyObj = {
"Sit-Forward-Bend":"坐位体前屈",
}
let sportDes = {
"Rope-Skipping":"双手打开与肩同宽或稍宽,手指指向前方,手掌平放在地面上,确保双手稳定支撑身体。慢慢弯曲手肘,使身体逐渐下降,下降至胸部几乎贴近地面时停止,伸臂起身还原。",
"Jumping-Jack":"双脚掌蹬地跳起,打开双脚与肩同宽,双手举过头顶击掌落地。.面正对手机,保持在图像中间。",
"Sit-Up":"手臂抱头于头部两侧,脚底并拢,脚跟靠近臀部,腹肌发力起身,下放时背部、肩部、头部依次着地。",
"Push-Up":"双手打开与肩同宽或稍宽,手指指向前方,手掌平放在地面上,确保双手稳定支撑身体。慢慢弯曲手肘,使身体逐渐下降,下降至胸部几乎贴近地面时停止,伸臂起身还原。",
"Crunch":"",
"Squat":"双脚分开与肩同宽或略宽,脚尖可以稍微外展(约30度),在保持背部挺直的前提下,慢慢屈膝下蹲至髌骨顶端所在的水平面,再缓慢起身。",
"Plank":"俯身面对地面,屈肘,前臂平放在地面上,确保手肘与肩膀处于同一垂直线上,双腿伸直,双脚并拢或稍微分开,使身体从头到脚呈一条直线。在整个平板支撑过程中,需要保持腹部肌肉持续收紧,这有助于维持身体的稳定性和平衡性",
"High-Knee":"双脚与肩同宽,身体保持直立,保持腹部收紧,前脚掌着地快速交替抬腿。在整个动作过程中,保持身体直立且稳定,避免晃动或摇摆。",
"Squat-Horse":"马步蹲",
"Sit-Forward-Bend":"坐位体前屈",
}
module.exports = {
sportKeyList,
sportNameObj,
sportKeyObj
sportKeyObj,
sportDes
}