xunji-wode-jihua.vue 9.07 KB
<template>
  <view class="plansPage">

    <view class="page-header" hover-class="none">

      <view class="nav-bar" :style="{
        paddingTop: menuButtonInfo.top + 'px',
        height: menuButtonInfo.height + 'px'
      }">
        <view class="nav-left" @click="goBack">
          <uni-icons class="back-icon" type="left" size="28"></uni-icons>
        </view>
        <view class="nav-title">我的训练计划</view>
        <view class="nav-right"></view>
      </view>
    </view>
    <scroll-view class="plans-content" scroll-y>

      <view class="plan-list">

        <view class="plan-card" v-for="plan in planList" :key="plan.id" @click="navigateToDetail(plan)">
          <!-- 背景封面图 -->
          <image class="card-cover" :src="plan.urlCover" mode="aspectFill" />

          <view class="card-content">
            <view class="plan-name">{{ plan.name }}</view>
            <view class="plan-desc">
              {{ getDifficultyText(plan.difficultyLevel) }} ·
              {{ plan.frequencyPerWeek }}练/周 ·
              共{{ plan.durationWeeks }}周
            </view>
            <!-- 右侧「去训练」按钮 -->
            <view class="train-btn" @click.stop="handleAddFromPlan(plan.id)">
              <text class="btn-text">去训练</text>
              <text class="btn-go">GO</text>
            </view>
            <!-- 右上角更多按钮 -->
            <view class="card-more" @click.stop="toggleShow(plan.id)">
              <uni-icons type="more-filled" size="36" color="#fff"></uni-icons>
            </view>

            <view class="more-popup" v-if="activePlanId === plan.id" @click.stop="confirmDelete(plan.planId)">
              <uni-icons class="deletIcon" type="trash" size="30" color="#f62000" />
              <text>结束这个计划</text>
            </view>
          </view>


        </view>

      </view>

    </scroll-view>

    <!-- <view class="more-popup" v-if="morePopupShow">
      <u-icon class="deletIcon" name="delete" />
      <text>结束这个计划</text>
    </view> -->
    <WodeJihuaTianjiaTancuang v-if="showPlanPopup" v-model:visible="showPlanPopup" :isAdd="false"
      :plan-title="'选择课程立即开始训练'" :plan-id="selectPlanId" />

  </view>

  <!-- 我的训练计划列表 -->
</template>

<script setup>
import { onMounted, ref } from 'vue';
import QueryPlanApi from '@/sheep/api/plan/queryplan';
import WodeJihuaTianjiaTancuang from '@/pages/xunji/components/wode-jihua-tianjia-tancuang.vue'

const morePopupShow = ref(true)
const showPlanPopup = ref(false)

// 获取胶囊按钮信息,用于动态适配导航栏高度
const menuButtonInfo = ref({
  top: 44,    // 默认值,避免获取失败时样式异常
  height: 32
});

// 训练计划列表(模拟数据,后续直接替换接口返回即可)
const planList = ref([]);

const getDifficultyText = (level) => {
  const map = { 1: "初阶", 2: "中阶", 3: "高阶" };
  return map[level] || "未知";
};

const activePlanId = ref(null);

// 获取我的计划列表 —— 独立函数
const fetchMyPlanList = async () => {
  try {
    const res = await QueryPlanApi.getMyPlan();
    console.log('接口返回:', res);

    if (res.code === 0 && res.data) {
      planList.value = res.data;
    } else {
      uni.showToast({ title: '获取计划失败', icon: 'none' });
    }
  } catch (err) {
    console.error('请求失败:', err);
    uni.showToast({ title: '网络异常', icon: 'none' });
  }
};

// 点击去训练
const selectPlanId = ref(0)
const handleAddFromPlan = (id) => {
  selectPlanId.value = id;
  showPlanPopup.value = true

}

// 点击更多按钮:记录当前卡片的id,控制弹窗显示/隐藏
const toggleShow = (planId) => {
  activePlanId.value = activePlanId.value === planId ? null : planId;
};

// 跳转到计划详情页
const navigateToDetail = (plan) => {
  uni.navigateTo({
    url: `/pages4/pages/xunji/xunji-xunlian-jihua?planid=${plan.id}&isMyPlan=true`,
  });
};

// 点击弹窗的「结束计划」按钮
const confirmDelete = async (id) => {
  try {
    console.log('打印计划个人id:', id);
    const res = await QueryPlanApi.deletePlan(id);
    console.log('删除结果:', res);

    if (res.code === 0 && res.data) {
      // 2. 删除成功,从列表移除
      planList.value = planList.value.filter(item => item.id !== id);
      uni.showToast({ title: '计划已结束', icon: 'success' });
      fetchMyPlanList()
    } else {
      uni.showToast({ title: res.msg || '操作失败', icon: 'none' });
    }
  } catch (err) {
    console.error('删除计划失败:', err);
    uni.showToast({ title: '网络请求失败', icon: 'none' });
  } finally {
    // 3. 关闭弹窗
    activePlanId.value = null;
  }
};
// 点击页面空白处关闭弹窗(可选优化)
const closePopup = () => {
  activePlanId.value = null;
};

// 去训练按钮点击事件
const goTrain = (plan) => {
  console.log("点击了训练计划:", plan);
  // uni.navigateTo({ url: `/pages/training/plan-detail?id=${plan.id}` });
};

// 返回上一页
const goBack = () => {
  uni.navigateBack();
};

onMounted(async () => {
  await fetchMyPlanList();


  // 获取小程序胶囊位置信息,实现导航栏与胶囊完美对齐
  // #ifdef MP-WEIXIN
  try {
    const rect = uni.getMenuButtonBoundingClientRect();
    if (rect) {
      menuButtonInfo.value = {
        top: rect.top,
        height: rect.height
      };
    }
  } catch (e) {
    console.log('获取胶囊信息失败,使用默认值', e);
  }
  // #endif

  // #ifndef MP-WEIXIN
  // 非微信小程序环境,使用系统状态栏高度 + 标准导航栏高度
  try {
    const systemInfo = uni.getSystemInfoSync();
    const statusBarHeight = systemInfo.statusBarHeight || 20;
    menuButtonInfo.value = {
      top: statusBarHeight,
      height: 44  // 标准导航栏高度
    };
  } catch (e) {
    console.log('获取系统信息失败', e);
  }
  // #endif
});

</script>

<style scoped lang="scss">
.plansPage {
  display: flex;
  flex-direction: column;
  background-color: #f6f6f6;
  min-height: 100vh;
  overflow: hidden;
}

.page-header {
  width: 100%;
  flex-shrink: 0;
  background-color: #fff;
}

/* 动态导航栏:高度与胶囊完全对齐 */
.nav-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background-color: #fff;
  /* 左右留出安全间距,避免内容贴边 */
  padding-left: 16rpx;
  padding-right: 16rpx;
  /* 高度和顶部内边距由动态 style 控制 */
  box-sizing: content-box;
}

/* 左侧返回按钮区域 - 固定宽度确保居中对齐 */
.nav-left {
  width: 60rpx;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-shrink: 0;
}

.back-icon {
  display: block;
}

/* 标题区域 - 自适应居中 */
.nav-title {
  flex: 1;
  text-align: center;
  font-size: 36rpx;
  font-weight: bold;
  color: #333;
  /* 继承行高,保证垂直居中 */
  line-height: 1;
}

/* 右侧占位区域 - 宽度与左侧相同,保证标题绝对居中 */
.nav-right {
  width: 60rpx;
  flex-shrink: 0;
}

/* 内容滚动区域 */
.plans-content {
  flex: 1;
  padding: 20rpx 0;
  background: #eef0ef;
}

.plan-list {
  display: flex;
  flex-direction: column;
  gap: 24rpx;
  padding: 0 24rpx;
}

/* 训练计划卡片 */
.plan-card {
  position: relative;
  width: 100%;
  height: 300rpx;
  border-radius: 16rpx;
  overflow: hidden;
  background-color: #c45f5f;
  box-sizing: border-box;
}

.card-cover {
  width: 100%;
  height: 100%;
  filter: brightness(0.7);
}

.card-content {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  padding: 32rpx 24rpx;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: space-between;

  .plan-name {
    font-size: 36rpx;
    font-weight: bold;
    color: #fff;
  }

  .plan-desc {
    font-size: 26rpx;
    color: #fff;
    opacity: 0.9;
  }

  .train-btn {
    position: absolute;
    right: 24rpx;
    bottom: 32rpx;
    display: flex;
    align-items: center;
    background-color: #ffd100;
    border-radius: 40rpx;
    padding: 12rpx 24rpx;

    .btn-text {
      font-size: 28rpx;
      color: #222;
      font-weight: bold;
    }

    .btn-go {
      background-color: #222;
      color: #ffd100;
      font-size: 24rpx;
      font-weight: bold;
      border-radius: 50%;
      width: 48rpx;
      height: 48rpx;
      display: flex;
      align-items: center;
      justify-content: center;
      margin-left: 12rpx;
    }
  }

  .card-more {
    position: absolute;
    right: 24rpx;
    top: 24rpx;
    background: rgba(109, 105, 105, 0.7);
    border-radius: 50%;

  }
}

/* 改成: */
.more-popup {
  position: absolute;
  // 弹窗定位:在更多按钮的左下方
  top: 80rpx; // 更多按钮top:24rpx + 按钮高度64rpx ≈ 80rpx
  right: 24rpx; // 和更多按钮的right值保持一致
  background: #fff;
  padding: 24rpx 32rpx;
  border-radius: 12rpx;
  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
  display: flex;
  align-items: center;
  gap: 16rpx;
  color: #f62000;
  z-index: 10; // 保证弹窗在最上层

  .deletIcon {
    color: #f62000;
    font-size: 32rpx;
  }

  text {
    font-size: 28rpx;
    color: #f62000;
  }
}
</style>