自学内容网 自学内容网

健身房项目 Uniapp+若依Vue3版搭建!!

本次系统实现主要负责前端前端功能的实现。其中系统前端主要分为三大部分,首页,教练列表页,我的页面。

  • 首页

首页的实现效果如图

1.会员卡组件

首页的Vip会员卡部分,团课活动的DataPicker,团课选择都采用了组件化封装的方式,接收方式为父子组件传参模式具体代码如下。

<template>

  <!-- 卡片横向排列 -->

  <view class="card-section">

    <!-- 动态渲染卡片 -->

    <view

      class="card"

      v-for="card in cardData"

      :key="card.id"

    >

      <view class="card-title">{ { card.name }}</view>

      <view class="card-price">¥{ { card.price }}</view>

      <button

        class="card-button"

        @click="handlePurchase(card.id)"

      >

        立即抢购

      </button>

    </view>

  </view>

</template>

<script setup>

import { defineProps } from "vue";

// 接收父组件传递的数据

defineProps({

  cardData: {

    type: Array,

    required: true, // 必填属性

  },

});

// 处理抢购事件

const handlePurchase = (id) => {

  console.log(`抢购商品ID: ${id}`);

};

</script>

此处可以通过外界的数据传入实现数据的动态生成。

  1. 日期组件DatePicker

日期组件DatePicker主要实现的效果为能够左右滑动的一个日期组件,并且能够显示一周内的数据。点击选中后能够传给父组件进行以日期为条件的条件查询,查询到该日期下的团课数据,并且能够点击预约进入团课详情页面。通过对dom的操作,能够实现点击滑动的丝滑操作,使用js实现的。

<template>

  <view class="data-pick scroll-container">

    <!-- 日期项 -->

    <view

      class="date-item"

      v-for="(item, index) in weekDates"

      :key="index"

      :id="`date-${index}`"

      :class="{ active: selectedDate === item.date }"

      @click="handleDateClick(item.date, index)"

    >

      <text class="weekday">{ { item.weekday }}</text>

      <text class="date">{ { item.date }}</text>

    </view>

  </view>

</template>

<script setup>

import { ref, onMounted } from "vue";

const props = defineProps({

  // 初始化的周数据

  weekDates: {

    type: Array,

    required: true,

  },

});

const emit = defineEmits(["dateSelected"]);

// 选中的日期

const selectedDate = ref("");

// 点击日期事件

const handleDateClick = (date, index) => {

  selectedDate.value = date;

  emit("dateSelected", date); // 向父组件发送选中日期

  const container = document.querySelector(".scroll-container");

  const clickedElement = document.getElementById(`date-${index}`);

  if (!container || !clickedElement) return;

  const containerWidth = container.offsetWidth;

  const containerScrollLeft = container.scrollLeft;

  const elementLeft = clickedElement.offsetLeft;

  const elementWidth = clickedElement.offsetWidth;

  const elementRight = elementLeft + elementWidth;

  const offset = 200;

  if (elementLeft < containerScrollLeft) {

    container.scrollTo({

      left: elementLeft - offset,

      behavior: "smooth",

    });

  } else if (elementRight > containerScrollLeft + containerWidth) {

    const scrollToPosition = elementRight - containerWidth + offset;

    container.scrollTo({

      left: scrollToPosition,

      behavior: "smooth",

    });

  }

};

// 设置默认选中的日期为今天

onMounted(() => {

  const today = new Date().toISOString().split("T")[0]; // 获取当前日期(格式:YYYY-MM-DD)

  const todayItem = props.weekDates.find((item) => item.date === today);

  if (todayItem) {

    selectedDate.value = todayItem.date; // 如果今天在 weekDates 中,设置为选中状态

  }

});

</script>

  1. Img组件

用来拼接后端接收的图片url和后端服务器的ip地址,避免了每个带有图片的页面都是使用专门的函数来进行image的src的处理。

<template>

  <image :src="decodedSrc" class="img" :mode="mode" />

</template>

<script>

import { computed, watch } from "vue";

const SERVER_IP = "http://121.36.99.152:8090";

export default {

  name: "ImgComponent",

  props: {

    src: {

      type: String,

      required: true, // 图片路径必填

    },

    mode: {

      type: String,

      default: "aspectFill", // 默认裁剪模式

    },

  },

  setup(props) {

    // 解码并显示可读中文路径

    const decodeURL = (url) => {

      try {

        return decodeURIComponent(url); // 将 URL 中的乱码解码为中文

      } catch (error) {

        console.error("解码错误:", error);

        return url; // 解码失败返回原始 URL

      }

    };

    // 计算最终的图片路径(解码后)

    const decodedSrc = computed(() => {

      let completeURL;

      if (!props.src.startsWith("http")) {

        // 如果 src 不是完整 URL,则拼接服务器 IP

        completeURL = `${SERVER_IP}${props.src}`;

      } else {

        completeURL = props.src;

      }

      return completeURL; // 解码路径中的中文部分

    });

    // 动态监听 src 的变化(可选)

    watch(

      () => props.src,

      (newSrc) => {

        console.log("src 发生了变化:", newSrc);

      }

    );

    return {

      decodedSrc,

    };

  },

};

</script>

  1. request封装

对request的封装方便了我的请求的发送。

// utils/request.js

// 基础路径配置

const BASE_URL = 'http://121.36.99.152:8090'; // 替换为你的后端基础地址

export default function request(options) {

  return new Promise((resolve, reject) => {

    const token = uni.getStorageSync('token'); // 从本地存储获取 token

    uni.request({

      url: `${BASE_URL}${options.url}`, // 自动拼接基础路径和具体接口路径

      method: options.method || 'GET', // 默认请求方法为 GET

      data: options.data || {}, // 默认空数据

      header: {

        'Content-Type': 'application/json',

        Authorization: token ? `Bearer ${token}` : '', // 自动携带 Token

        ...options.header // 合并自定义请求头

      },

      success: (response) => {

        if (response.statusCode === 200) {

          resolve(response.data); // 返回业务数据

        } else {

          // 处理错误,可能包括业务状态码错误

          uni.showToast({

            title: response.data?.message || '请求失败',

            icon: 'none'

          });

          reject(response);

        }

      },

      fail: (error) => {

        // 网络错误处理

        uni.showToast({

          title: '网络错误,请稍后重试',

          icon: 'none'

        });

        reject(error);

      }

    });

  });

}

// 添加封装的常用方法

export const get = (url, data, header = {}) => {

  return request({ url, method: 'GET', data, header });

};

export const post = (url, data, header = {}) => {

  return request({ url, method: 'POST', data, header });

};

export const put = (url, data, header = {}) => {

  return request({ url, method: 'PUT', data, header });

};

export const del = (url, data, header = {}) => {

  return request({ url, method: 'DELETE', data, header });

};

  1. api封装

导出api函数与后端请求做对接

// 查询首页信息

import { get } from "../request/request";

// 获取轮播图的信息

export const getSwiperImg=()=>{

return get('/homeImage')

}

// 获取vip卡的信息

export const getVipInfo= () =>{

return get('/MembershipCard')

}

// 获取首页的部分团课信息

export const getGroupClass=(startTime)=>{

return get('/GroupClass/list',{startTime})

}

// 获取教练列表

export const getCoachList=()=>{

return get('/Coach/list')

}

// 根据id获取团课详情

export const getLessonDetai=(id)=>{

return get(`/GroupClass/${id}`)

}

// 获取教练详情

export const getCoachDetail=(id)=>{

return get(`/CoachClass/${id}`)

}

// 获取category列表

export const getCategoryList=()=>{

return get('/Category/list')

}

// 获取分类下的团课信息

export const getGroupClassByCategory=(categoryId)=>{

return get('/GroupClass/list',{categoryId})

}

// 获取商品列表

export const getProductList=()=>{

return get('/commodity/list')

}

  • 教练界面

教练页面主要是查出教练界面,里面包含各教练的简略信息,用于用户的初步的检索。

  • 我的页面

我的页面是一个十分丰富的页面,里面包含很多待开发的功能,比如课程,订单,优惠卡券,邀请好友,金币等,可以用来增加用的粘性。下方还有个人勋章,涌动日历,身体数据,后期还能进行和用户app进行连接。

  • 团课页面

团课页面是用户在主页点击进入的页面,主要的功能是通过category查询当前分类下的团课,并且能根据上方的DataPicker查看当日下的团课的安排,并且能够进行点击进入团课详情的页面,进行进一步的查看。

  • 训练营界面

训练营界面也是用户在首页通过点击进入的界面。


原文地址:https://blog.csdn.net/weixin_73733267/article/details/145312422

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!