Authored by qxm

Merge branch 'feature/qxm'

... ... @@ -449,23 +449,15 @@ const recordList = ref([
{ h: '00', m: '00', s: '00', quickTimeDisplay: '60s', distance: '', weight: '', reps: '', duration: '', restTime: '', isActive: false },
]);
watch(recordList, () => {
// console.log('recordList变化,发送长度:', recordList.value.length)
emit('update:groupCount', recordList.value.length)
}, { deep: true, immediate: true })
const saveToStore = () => {
let records = {};
if (props.type === 1) {
// 普通动作
records[props.actionDetail.id] = recordList.value;
} else if (props.type === 2) {
// 超级组
records = superRecordMap.value;
}
// 调用 Pinia 保存
trainingStore.saveUnitRecord(props.unitIndex, {
records: records,
... ... @@ -473,6 +465,11 @@ const saveToStore = () => {
});
};
watch(recordList, () => {
// console.log('recordList变化,发送长度:', recordList.value.length)
emit('update:groupCount', recordList.value.length)
}, { deep: true, immediate: true })
watch([recordList, superRecordMap, userWeight], () => {
saveToStore();
console.log('+++++++++打印全局训练数据 trainingStore.unitRecords++++++++', trainingStore.unitRecords);
... ...
... ... @@ -63,13 +63,13 @@
</view>
<!-- 要点,历史,平替动作标签 -->
<view class="content-tabs">
<view class="tab-item" :class="{ active: contentTab === 0 }" @click="contentTab = 0">
<view class="tab-item" :class="{ active: contentTab === 0 }" @click="switchContentTab(0)">
要点
</view>
<view class="tab-item" :class="{ active: contentTab === 1 }" @click="contentTab = 1">
<view class="tab-item" :class="{ active: contentTab === 1 }" @click="switchContentTab(1)">
历史
</view>
<view class="tab-item" :class="{ active: contentTab === 2 }" @click="contentTab = 2" v-if="type === 1">
<view class="tab-item" :class="{ active: contentTab === 2 }" @click="switchContentTab(2)" v-if="type === 1">
平替动作
</view>
</view>
... ... @@ -167,7 +167,7 @@
<!-- <view class="difficulty">困难</view> -->
<view class="difficulty">{{
item.weight ? item.weight + 'kg' : '无负重'
}}</view>
}}</view>
</view>
<view class="group-chips">
<view class="chip" v-for="(set, index) in item.setConfigList" :key="index">
... ... @@ -189,7 +189,7 @@
<view v-if="contentTab === 2 && type === 1" class="tab-pane slide-up">
<view class="substitute-list">
<view class="sub-item" v-for="item in alternativeActions" :key="item.id" @click="openActionItem(item)">
<image :src="item.urlImage || lostImage" mode="aspectFill" class="img" />
<image :src="item.url3dAnimation || lostImage" mode="aspectFill" class="img" />
<view class="sub-info">
<text class="name">{{ item.name }}</text>
... ... @@ -221,6 +221,8 @@ import { onShareAppMessage } from '@dcloudio/uni-app';
// 静态配置
const alternativeActions = ref([]); // 平替动作列表接口数据
const loadedHistory = ref(false)
const loadedAlternative = ref(false)
// 响应式状态
const actionShow = ref(false);
... ... @@ -300,25 +302,42 @@ const startTraining = () => {
});
};
const open = (id, typeData) => {
const switchContentTab = async (chooseTab) => {
contentTab.value = chooseTab;
// 切历史
if (chooseTab === 1 && !loadedHistory.value) {
await loadTrainHistoryDetail(actionId.value, type.value)
loadedHistory.value = true;
}
// 切平替
if (chooseTab === 2 && type.value === 1 && !loadedAlternative.value) {
await loadAlternativeActions(actionId.value)
loadedAlternative.value = true;
}
}
const open = async (id, typeData) => {
actionId.value = Number(id);
type.value = typeData;
contentTab.value = 0;
console.log('动作详情页面接收id和类型:', actionId.value, type.value);
// 如何判断是动作还是超级组
if (typeData == 1) {
loadexercisedetail(actionId.value);
loadAlternativeActions(actionId.value);
checkExerciseFavorited();
await loadexercisedetail(actionId.value);
// await loadAlternativeActions(actionId.value);
await checkExerciseFavorited();
} else {
loadsuperdetail(actionId.value);
checkSupersetFavorited();
await loadsuperdetail(actionId.value);
await checkSupersetFavorited();
}
console.log('请求参数actionId/typeData', actionId.value, type.value);
// 重置加载训练历史列表和平替动作列表标记
loadedHistory.value = false
loadedAlternative.value = false
loadTrainHistoryDetail(actionId.value, type.value);
// loadTrainHistoryDetail(actionId.value, type.value);
actionShow.value = true;
};
// 打开备注弹窗
... ...
... ... @@ -280,7 +280,7 @@
<CalendarColorPicker v-model:visible="showColorPopup" :daily-template-id="selectedPlanId"
:current-color="currentPlan?.templateBackgroundColor || '#ffffff'" @success="calendarColorPickerSuccess" />
<MeiRiMoBanXiuGai ref="meirimobanRef" @saveSuccess="loaddailytemplate(selectedDate)" />
<MeiRiMoBanXiuGai ref="meirimobanRef" @saveSuccess="handleRenderSuccess" />
</view>
</up-popup>
</template>
... ... @@ -446,6 +446,12 @@ const loaddailytemplate = async () => {
}
};
const handleRenderSuccess = async () => {
await loaddailytemplate();
await nextTick()
}
const switchPlan = (index) => {
currentPlanIndex.value = index;
console.log('【切换标签后 - currentPlan】', currentPlan.value);
... ...
... ... @@ -43,20 +43,18 @@ const isRenderDongzuo = ref(false)
// 标记是否需要删除当前动作组
const needDeleteUnit = ref(false)
// watch(
// () => templateStore.replaceId,
// (id) => {
// if (id) {
// needReplaceUnit.value = true
// uni.showToast({
// title: '已标记替换',
// icon: 'success'
// })
// }
// }
// )
// 原watch不动,补充拉取新数据、替换source.unit
// 缓存打开弹窗传入的数据
let sourceInfo = ref({
unit: null,
unitIndex: null,
dailyTemplate: null
})
// 当前操作的unit和下标
const currUnit = computed(() => sourceInfo.value.unit || {})
const currUnitIndex = computed(() => sourceInfo.value.unitIndex ?? 0)
//当替换动作的时候, 原watch不动,补充拉取新数据、替换source.unit
const replaceCacheUnit = ref(null)
watch(
() => templateStore.replaceId,
async (id) => {
... ... @@ -97,7 +95,29 @@ watch(
}
console.log('newUnit', newUnit);
}
// ============【新增改动】缓存拼装好的替换单元,保存复用============
replaceCacheUnit.value = newUnit
// =================================================================
sourceInfo.value.unit = newUnit
// ========== 新增:同步初始化当前unitIndex的Pinia训练记录 ==========
const unitIdx = sourceInfo.value.unitIndex
const exercises = newUnit.exercises || []
let records = {}
exercises.forEach(ex => {
// 新动作无历史sets,给默认空白一组(和open初始化逻辑统一)
records[ex.exerciseId] = [{
h: '00', m: '00', s: '00', quickTimeDisplay: '60s',
distance: '', weight: '', reps: '', duration: '', restTime: '', isActive: false
}]
})
// 覆盖当前unitIndex的仓库记录
trainingStore.saveUnitRecord(unitIdx, { records, userWeight: 70 })
// =================================================================
// 销毁重建dongzuo实现刷新
isRenderDongzuo.value = false
await nextTick()
... ... @@ -106,49 +126,7 @@ watch(
}
)
// 缓存打开弹窗传入的数据
let sourceInfo = ref({
unit: null,
unitIndex: null,
dailyTemplate: null
})
// 当前操作的unit和下标
const currUnit = computed(() => sourceInfo.value.unit || {})
const currUnitIndex = computed(() => sourceInfo.value.unitIndex ?? 0)
// 格式化数据:统一 动作 / 超级组 结构 → 给 dongzuo 组件用
// const formatDongzuoData = (unit) => {
// if (!unit) return {}
// console.log('++每日模板的unit++', unit);
// if (unit.unitType === 1) {
// const ex = unit.exercises?.[0] || {}
// return {
// id: ex.exerciseId,
// name: ex.exerciseName,
// urlImage: ex.url3dAnimation,
// exerciseType: ex.exerciseType,
// categoryDescription: ex.categoryDescription || '',
// equipmentDescription: ex.equipmentDescription || '',
// }
// }
// if (unit.unitType === 2) {
// return {
// id: unit.unitId,
// name: unit.unitName || '超级组',
// exercises: unit.exercises?.map(ex => ({
// id: ex.exerciseId,
// name: ex.exerciseName,
// urlImage: ex.url3dAnimation,
// exerciseType: ex.exerciseType,
// })) || []
// }
// }
// return {}
// }
// 格式化传入动作组件的unit
const formatDongzuoData = (unit) => {
if (!unit) return {}
console.log('++每日模板的unit++', unit);
... ... @@ -183,6 +161,8 @@ const formatDongzuoData = (unit) => {
console.log('格式化后结果:', result) // 打印最终数据
return result
}
//
const handleDeleteAction = () => {
console.log('父组件处理删除动作')
needDeleteUnit.value = true
... ... @@ -262,10 +242,12 @@ const closePop = () => {
// 【保存修改:核心,组装参数调用更新接口】
const saveEdit = async () => {
if (!dongzuoSingleRef.value) return uni.showToast({ title: '获取数据失败', icon: 'none' })
if (!dongzuoSingleRef.value)
return uni.showToast({ title: '获取数据失败', icon: 'none' })
const { unit, unitIndex, dailyTemplate } = sourceInfo.value
// ================== 新增:删除逻辑 ==================
// ================== 删除逻辑 ==================
if (needDeleteUnit.value) {
// 1. 复制所有 unit,删掉当前这个
const allUnits = [...dailyTemplate.units]
... ... @@ -290,112 +272,19 @@ const saveEdit = async () => {
// 直接 return,不执行后面的修改逻辑
return
}
// ====================================================
// ========================替换
if (needReplaceUnit.value) {
const replaceId = templateStore.replaceId
const replaceType = templateStore.replaceType
// ========================替换==================
// =========【新增:替换动作分支,覆盖基础unit】=========
let baseUnit = { ...unit }
if (!replaceId || !replaceType) {
uni.showToast({ title: '替换异常', icon: 'none' })
return
}
try {
let detail = null
// ======================
// 👇 完全移植你现成的接口逻辑(动作替换)
// ======================
if (replaceType === 1) {
// 普通动作
const res = await ExercisesApi.getExerciseById(replaceId)
detail = res.data
console.log('detail=', detail);
} else if (replaceType === 2) {
// 超级组
const res = await SupersetsApi.getSupersetsInfo(replaceId)
detail = res.data
console.log('detail=', detail);
}
if (!detail) {
uni.showToast({ title: '获取动作详情失败', icon: 'none' })
return
}
// ======================
// 组装 newUnit
// ======================
let newUnit = null
if (replaceType === 1) {
newUnit = {
unitType: 1,
unitId: detail.id,
unitName: detail.name,
exercises: [
{
exerciseId: detail.id,
exerciseName: detail.name,
exerciseType: detail.exerciseType,
urlImage: detail.url3dAnimation || detail.exerciseCover,
// urlImage: detail.urlImage || detail.url3dAnimation,
categoryDescription: detail.categoryDescription || '',
equipmentDescription: detail.equipmentDescription || '',
sets: []
}
]
}
} else if (replaceType === 2) {
newUnit = {
unitType: 2,
unitId: detail.id,
unitName: detail.name,
exercises: detail.exercises.map(e => ({
exerciseId: e.id,
exerciseName: e.name,
exerciseType: e.exerciseType,
urlImage: e.url3dAnimation || e.exerciseCover,
sets: []
}))
}
}
// ======================
// 替换到模板数组
// ======================
const allUnits = [...dailyTemplate.units]
allUnits[unitIndex] = newUnit
console.log('allUnits[unitIndex]=', allUnits[unitIndex]);
await dailytemplateApi.updateDailyTemplate({
dailyTemplateId: dailyTemplate.id,
units: allUnits
})
uni.showToast({ title: '替换成功', icon: 'success' })
needReplaceUnit.value = false
emit('saveSuccess')
closePop()
return
} catch (err) {
uni.showToast({ title: '替换失败', icon: 'none' })
} finally {
// 最后清空
templateStore.clearReplaceAction()
}
if (needReplaceUnit.value && replaceCacheUnit.value) {
// 使用watch提前缓存好的替换单元作为基础载体
baseUnit = { ...replaceCacheUnit.value }
}
// =====================================================
// 动作替换
let targetExercises = [...unit.exercises]
let targetExercises = [...baseUnit.exercises]
if (unit.unitType === 1) {
// 单动作:exercises只有1个
const exItem = targetExercises[0]
... ...
<template>
<!-- 历史按钮 + 弹窗 全部封装 -->
<view>
<!-- 历史按钮 -->
<button class="history-btn" @click="openPopup">历史</button>
<!-- 弹窗(组件内部管理) -->
<u-popup :show="showPopup" mode="bottom" :mask-click="false" :safe-area-inset-bottom="true"
@update:modelValue="showPopup = $event">
<view class="history-section">
<view class="popup-header">
<u-icon name="close" size="24" color="#333" @click="closePopup"></u-icon>
</view>
<view v-if="historyList.length > 0" class="history-list">
<view v-for="(item, index) in historyList" :key="index" class="history-card">
<view class="card-date">
<text class="date-text">{{ item.date ? `${item.date[0]}/${item.date[1]}/${item.date[2]}` :
'未知日期' }}</text>
</view>
<view class="card-body">
<view class="card-header">
<text class="history-name">{{ item.name }}</text>
<text class="history-set">{{ item.setCount }}组</text>
</view>
<view class="card-config">
<text class="config-text">配置数: {{ item.setConfigList?.length || 0 }}</text>
</view>
</view>
</view>
</view>
<view v-else class="empty-container">
<image :src="lostImage" class="empty-img"></image>
<text class="empty-text">暂无历史数据</text>
</view>
<!-- 历史按钮 + 弹窗 全部封装 -->
<view>
<!-- 历史按钮 -->
<button class="history-btn" @click="openPopup">历史</button>
<!-- 弹窗(组件内部管理) -->
<u-popup :show="showPopup" mode="bottom" :mask-click="false" :safe-area-inset-bottom="true"
@update:modelValue="showPopup = $event">
<view class="history-section">
<view class="popup-header">
<u-icon name="close" size="24" color="#333" @click="closePopup"></u-icon>
</view>
<view v-if="historyList.length > 0" class="history-list">
<view v-for="(item, index) in historyList" :key="index" class="history-card">
<view class="card-date">
<text class="date-text">{{ item.date ? `${item.date[0]}/${item.date[1]}/${item.date[2]}` :
'未知日期' }}</text>
</view>
</u-popup>
</view>
<view class="card-body">
<view class="card-header">
<text class="history-name">{{ item.name }}</text>
<text class="history-set">{{ item.setCount }}组</text>
</view>
<view class="card-config">
<text class="config-text">配置数: {{ item.setConfigList?.length || 0 }}</text>
</view>
</view>
</view>
</view>
<view v-else class="empty-container">
<image :src="lostImage" class="empty-img"></image>
<text class="empty-text">暂无历史数据</text>
</view>
</view>
</u-popup>
</view>
</template>
<script setup>
... ... @@ -45,10 +45,13 @@ import TrainingApi from '@/sheep/api/Training/traininghistory';
// 接收外部传入的 动作ID
const props = defineProps({
exerciseId: {
type: [String, Number],
required: true
}
exerciseId: {
type: [String, Number],
required: true
},
historyType: {
type: String
}
});
// 内部状态(页面完全不需要知道)
... ... @@ -58,126 +61,126 @@ const lostImage = ref('https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.co
// 打开弹窗 + 加载数据
const openPopup = async () => {
showPopup.value = true;
await loadHistory(); // 打开时才加载,不打开不请求
showPopup.value = true;
await loadHistory(); // 打开时才加载,不打开不请求
};
// 关闭弹窗
const closePopup = () => {
showPopup.value = false;
showPopup.value = false;
};
// 组件自己加载历史记录(核心!你页面彻底不用管)
const loadHistory = async () => {
try {
const res = await TrainingApi.getTrainHistoryList(props.exerciseId);
historyList.value = res.data || [];
} catch (err) {
console.error('加载历史失败', err);
historyList.value = [];
}
try {
const res = await TrainingApi.getTrainHistoryList(props.exerciseId);
historyList.value = res.data || [];
} catch (err) {
console.error('加载历史失败', err);
historyList.value = [];
}
};
</script>
<style scoped lang="scss">
/* 按钮样式和你原来完全一样 */
.history-btn {
flex: 1;
height: 60rpx;
background-color: #eaeaea;
color: #333;
font-size: 28rpx;
border-radius: 30rpx;
border: none;
display: flex;
align-items: center;
justify-content: center;
flex: 1;
height: 60rpx;
background-color: #eaeaea;
color: #333;
font-size: 28rpx;
border-radius: 30rpx;
border: none;
display: flex;
align-items: center;
justify-content: center;
}
/* 下面是你原来的全部弹窗样式,完整保留 */
.history-section {
width: 100%;
margin-top: 0;
background-color: #ffffff;
padding: 30rpx;
box-sizing: border-box;
width: 100%;
margin-top: 0;
background-color: #ffffff;
padding: 30rpx;
box-sizing: border-box;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
}
.history-list {
width: 100%;
display: flex;
flex-direction: column;
gap: 20rpx;
width: 100%;
display: flex;
flex-direction: column;
gap: 20rpx;
}
.history-card {
width: 100%;
box-sizing: border-box;
background: #f0f0f0;
border-radius: 16rpx;
padding: 30rpx;
border: 1rpx solid #e5e5e5;
width: 100%;
box-sizing: border-box;
background: #f0f0f0;
border-radius: 16rpx;
padding: 30rpx;
border: 1rpx solid #e5e5e5;
}
.card-date {
margin-bottom: 16rpx;
margin-bottom: 16rpx;
}
.date-text {
font-size: 28rpx;
color: #333333;
font-weight: 500;
font-size: 28rpx;
color: #333333;
font-weight: 500;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
}
.history-name {
font-size: 32rpx;
color: #333333;
font-weight: bold;
font-size: 32rpx;
color: #333333;
font-weight: bold;
}
.history-set {
font-size: 26rpx;
color: #ffd700;
padding: 6rpx 14rpx;
border-radius: 8rpx;
font-size: 26rpx;
color: #ffd700;
padding: 6rpx 14rpx;
border-radius: 8rpx;
}
.card-config .config-text {
font-size: 26rpx;
color: #666666;
font-size: 26rpx;
color: #666666;
}
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 0;
}
.empty-img {
width: 200rpx;
height: 200rpx;
margin-bottom: 30rpx;
opacity: 0.6;
width: 200rpx;
height: 200rpx;
margin-bottom: 30rpx;
opacity: 0.6;
}
.empty-text {
font-size: 28rpx;
color: #999999;
font-size: 28rpx;
color: #999999;
}
</style>
\ No newline at end of file
... ...