Bootstrap

【前端开发】实战:课表安排(HTML + JavaScript + Vue3 + Vant)

后端开发

主要定义了三个核心部分:每周周期(WeekDays)、每天节次(TimeLessons) 和 每天节次详情(Details)

  1. 每周周期(WeekDays)
  • 存储了每周的七天(通常是从周一到周五)及其对应的中文名称(如“周一”,“周二”)
  1. 每天节次(TimeLessons)
  • 定义了每天的具体节次安排,分为上午和下午两段时间,分别列出了每节课的时间区间(如“08:10-08:50”)
  • 这里的节次数据对每一天进行了时间段划分,将课程表按照上午和下午两部分展示
  1. 每天节次详情(Details)
  • 存储了每一节课的具体内容,其中包含 WeekDay(星期几)、LessonNumber(节次编号)、LessonContent(课程内容)
    -这个数据结构是最核心的数据,描述了每一节课的具体信息,包括课程的名称、老师、时间范围等。该数据结构可以支持查询和操作,动态展示每节课的详细信息
  1. 每周周期
const WeekDays = [
	{ Number: 1, Name: "周一" },
	{ Number: 2, Name: "周二" },
	{ Number: 3, Name: "周三" },
	{ Number: 4, Name: "周四" },
	{ Number: 5, Name: "周五" },
]
  1. 每天节次
const TimeLessons = [
	{
	  Time: "上午",
	  LessonTitle: [
	    { Number: 1, Name: "第一节\n08:10-08:50" },
	    { Number: 2, Name: "第二节\n09:00-09:40" },
	    { Number: 3, Name: "第三节\n10:00-10:40" },
	    { Number: 4, Name: "第四节\n10:50-11:30" },
	  ],
	},
	{
	  Time: "下午",
	  LessonTitle: [
	    { Number: 5, Name: "第五节\n14:30-15:10" },
	    { Number: 6, Name: "第六节\n15:25-16:05" },
	  ],
	},
]
  1. 每天节次详情
const Details = [
	{
	  WeekDay: 1,
	  LessonNumber: 1,
	  LessonContent: "历史\n(1 - 21)\n王珏",
	},
	{
	  WeekDay: 2,
	  LessonNumber: 1,
	  LessonContent: "信息技术\n(1 - 21)\n陈丽华",
	},
	......
	{
	  WeekDay: 1,
	  LessonNumber: 2,
	  LessonContent: "历史\n(1 - 21)\n王珏",
	},
	{
	  WeekDay: 2,
	  LessonNumber: 2,
	  LessonContent: "历史\n(1 - 21)\n王珏",
	},
	......
	......
	{
	  WeekDay: 1,
	  LessonNumber: 3,
	  LessonContent: "历史\n(1 - 21)\n王珏",
	},
	{
	  WeekDay: 2,
	  LessonNumber: 3,
	  LessonContent: "历史\n(1 - 21)\n王珏",
	},
	......
]

前端开发

前端开发思路主要集中在如何利用后端返回的数据来动态渲染课程安排,并提供交互性和用户体验优化。以下是前端开发的思路:

  1. 获取当前星期几
  • 前端首先需要获取当前的星期几,这可以通过 getTodayWeekDay() 函数来完成。这个函数会根据当前日期返回数字 1 到 7(其中 7 代表周日)。前端可以使用这个数字来查询 WeekDays 中对应的星期名称,并将其渲染到页面上。
  1. 渲染节次时间
  • 前端通过 TimeLessons 获取每天的节次安排。每一天的节次信息包含了上午和下午的时间段。通过遍历 TimeLessons,前端可以在页面上展示这些时间段(例如:上午的“第一节”,“第二节”等)。在渲染时,前端可以按时间段划分,显示“上午”或“下午”的课程列表。
  1. 获取并渲染课程内容
  • 前端使用 getLessonContent(weekDay, lessonNumber) 函数来查询某一天某节课的具体内容。通过传递 weekDay 和 lessonNumber,前端可以查询 Details 数据数组,获取对应的课程信息(课程名称、老师等)。
  • 前端在显示课程内容时,需要将换行符(\n)转化为 HTML 的
    标签,以保证课程内容能正确显示为多行文本。
  1. 展示完整的课程表
  • 前端将通过日期(星期几)和节次(如上午第一节、第二节等)来展示完整的课程表。前端需要根据每一节的时间、课程名称以及老师信息进行展示。
  1. 动态更新与交互
  • 如果需要根据日期或节次动态加载不同的课程安排,前端可能会实现类似分页或切换功能,允许用户查看不同星期或不同节次的课程。这个操作通常会调用后端接口来获取对应的数据,然后更新视图。
  1. 封装函数方法
// 获取当前周期,并标准化周日为 7
const getTodayWeekDay = () => {
	const today = new Date();
	const day = today.getDay(); // 0=周日, ..., 6=周六
	return day === 0 ? 7 : day; // 将周日(0)转为7
};

// 根据周期(weekDay)和节次(lessonNumber)来查找并返回该课程的内容
// 格式化换行符以便于显示
const getLessonContent = (weekDay, lessonNumber) => {
    const lesson = data.value.detail.Details.find(
      (detail) =>
        detail.WeekDay === weekDay && detail.LessonNumber === lessonNumber
    );
    return lesson
      ? lesson.LessonContent.replace(/(\r\n|\n|\r)/g, "<br>")
      : "";
};

// 根据传入的 dayNumber(周期)筛选出符合该周期的所有课程,并返回过滤后的课程数据
const getFilteredLessons = (dayNumber) => {
  return data.value.detail.TimeLessons.map((timeLesson) => { // map() 会返回一个新的数组
    return {
      ...timeLesson, // 保留原始的课程信息
      LessonTitle: timeLesson.LessonTitle.filter((lesson) => {
        return data.value.detail.Details.some(
          (detail) =>
            detail.WeekDay === dayNumber &&
            detail.LessonNumber === lesson.Number // 判断该课程在指定的星期几和节次是否存在
        );
      }),
    };
  });
};
  1. 请求数据
const getSchduleDetail = () => {
  var formData = {
    action: "detail"
  };

  $.ajax({
    type: "post",
    url: handlerURL,
    async: false,
    data: formData,
    success: function (req) {
      if (req.Status) {
        const todayWeekDay = getTodayWeekDay();
        const todayDay = req.Data.WeekDays.find(
          (day) => day.Number === todayWeekDay
        );

		// 标签切换周期渲染数据
        if (todayDay) {
          data.value.activeTab = todayDay.Number - 1;
        } else {
          data.value.activeTab =
            req.Data.WeekDays[
              req.Data.WeekDays.length - 1
            ].Number - 1;
        }
        getFilteredLessons(data.value.activeTab);
      }
    },
  });
};

完整代码

应用技术:HTML + JavaScript + Vue3 + Vant

  1. HTML代码
<!DOCTYPE html>
<html lang="en" style="font-size: 50px">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1"
    />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta http-equiv="Expires" content="0" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Cache-control" content="no-cache" />
    <meta http-equiv="Cache" content="no-cache" />
    <title>课表安排</title>
    <link rel="stylesheet" href="css/detail.css" />
    <link rel="stylesheet" href="css/vant.css" />
    <script src="js/vue3.global.js"></script>
    <script src="js/vant.min.js"></script>
  </head>
  <body>
    <div id="app" class="wrapper">
      <template v-if="data.loading">
        <div class="class-schedule-container">
          <div class="schedule-title">{{ data.detail.Title }}</div>
          <van-tabs v-model:active="data.activeTab">
            <van-tab
              v-for="day in data.detail.WeekDays"
              :title="day.Name"
              :key="day.Number"
            >
              <table class="timetable">
                <thead>
                  <tr>
                    <th class="time-th">时间</th>
                    <th class="section-th">节次</th>
                    <th class="content-th">课程内容</th>
                  </tr>
                </thead>
                <tbody>
                  <tr
                    v-for="timeLesson in getFilteredLessons(day.Number)"
                    :key="timeLesson.Time"
                  >
                    <td>{{ timeLesson.Time }}</td>
                    <td>
                      <div
                        class="lesson"
                        v-for="lesson in timeLesson.LessonTitle"
                        :key="lesson.Number"
                      >
                        {{ lesson.Name }}
                      </div>
                    </td>
                    <td>
                      <div
                        class="lesson"
                        v-for="lesson in timeLesson.LessonTitle"
                        :key="lesson.Number"
                        v-html="getLessonContent(day.Number, lesson.Number)"
                      ></div>
                    </td>
                  </tr>
                </tbody>
              </table>
            </van-tab>
          </van-tabs>
        </div>
      </template>
    </div>
    <script type="text/javascript" src="/js/jquery.min.js"></script>
    <script type="text/javascript" src="/js/detail.js"></script>
  </body>
</html>

  1. CSS代码
* {
  margin: 0;
  padding: 0;
  border: 0;
}

*,
*:before,
*:after {
  max-height: 100000px;
}

html,
body {
  height: 100%;
}

body {
  margin: 0 auto;
  padding: 0;
}

a,
a:focus,
a:hover,
a:link {
  text-decoration: none;
}

.wrapper {
  font-size: 0.4rem;
  width: 100%;
  margin: 0 auto;
  background-color: #f8f8f8;
  background-repeat: no-repeat;
  background-size: contain;
  max-width: 10rem;
  min-height: 100%;
  box-sizing: border-box;
  padding-bottom: 0.8rem;
}

.class-schedule-container .schedule-title {
  flex: 1;
  text-align: center;
  font-size: 0.38rem;
  line-height: 0.9rem;
  background-color: #0091ff;
  color: #fff;
  position: relative;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0 0.2rem;
  box-shadow: -5px 3px 16.92px 1.08px rgba(0, 97, 180, 0.4);
}

.van-tab {
  font-size: 0.32rem !important;
}

.timetable {
  width: 100%;
  font-size: 0.34rem;
  border-collapse: collapse;
  background-color: #fff;
}

.timetable .lesson {
  text-align: center;
  min-height: 3rem;
  display: flex;
  align-items: center;
  justify-content: space-around;
  border-bottom: 1px solid #ddd;
  padding: 0 0.2rem;
  white-space: pre-line;
}
.timetable th,
.timetable td {
  border: 1px solid #ddd;
  padding: 0.2rem;
  text-align: center;
}
.timetable .time-th {
  width: 1rem;
}
.timetable .section-th {
  width: 2rem;
}
.timetable .content-th {
  width: 3.5rem;
}

.timetable td {
  padding: 0;
}

.timetable .lesson:last-child {
  border-bottom: none;
}

  1. JavaScript + Vue3 代码
$(function () {
    ini();
});

function ini() {
  const { createApp, ref, onMounted, computed } = Vue;

  createApp({
    setup() {
      const data = ref({
        loading: false,
        detail: {
          Title: "",
          WeekDays: [],
          TimeLessons: [],
          Details: [],
        },
        activeTab: 4,
      });

      const getTodayWeekDay = () => {
        const today = new Date();
        const day = today.getDay();
        return day === 0 ? 7 : day;
      };

      const getSchduleDetail = () => {
        var formData = {
          action: "detail",
        };

        $.ajax({
          type: "post",
          url: handlerURL,
          async: false,
          data: formData,
          success: function (req) {
            if (req.Status) {
              data.value.detail = req.Data;

              const todayWeekDay = getTodayWeekDay();
              const todayDay = data.value.detail.WeekDays.find(
                (day) => day.Number === todayWeekDay
              );

              if (todayDay) {
                data.value.activeTab = todayDay.Number - 1;
              } else {
                data.value.activeTab =
                  data.value.detail.WeekDays[
                    data.value.detail.WeekDays.length - 1
                  ].Number - 1;
              }

              getFilteredLessons(data.value.activeTab);
              data.value.loading = true;
            }
          },
        });
      };

      const getLessonContent = (weekDay, lessonNumber) => {
        const lesson = data.value.detail.Details.find(
          (detail) =>
            detail.WeekDay === weekDay && detail.LessonNumber === lessonNumber
        );
        return lesson
          ? lesson.LessonContent.replace(/(\r\n|\n|\r)/g, "<br>")
          : "";
      };

      const getFilteredLessons = (dayNumber) => {
        return data.value.detail.TimeLessons.map((timeLesson) => {
          return {
            ...timeLesson,
            LessonTitle: timeLesson.LessonTitle.filter((lesson) => {
              return data.value.detail.Details.some(
                (detail) =>
                  detail.WeekDay === dayNumber &&
                  detail.LessonNumber === lesson.Number
              );
            }),
          };
        });
      };

      onMounted(() => {
        getSchduleDetail();
      });

      return {
        data,
        getLessonContent,
        getFilteredLessons,
      };
    },
  })
    .use(vant)
    .mount("#app");
}

;