逆向软件设计和开发————基于对学生活动管理系统的功能添加

项目介绍

本文为根据一位朋友原有的大二上期末大作业所写的项目,对其进行功能的添加。
其项目主要为对学生活动的进行管理,可分为用户端和管理端。
运行环境

ecplise+JAVAEE+TomcatV10.0+Edge+JDK18;
前端采用原生 JavaScript + CSS 实现

项目优化

1.加入数据统计的功能,实现对用户和活动数据的一目了然

2.对管理员的活动视图界面进行优化,在原有的列表视图上添加卡片视图内容。

为其页面添加相对应的动画效果和响应式布局

下面为没加数据统计前代码的布局

添加数据统计的按钮和相对应的代码

按钮代码

点击查看代码
            <div class="nav-item" onclick="showSection('statistics')">
                <i class="fas fa-chart-bar"></i>
                数据统计
            </div>

布局代码

点击查看代码
  <!-- 数据统计部分 -->
            <div id="statistics" class="content-section">
                <h2>数据统计</h2>
                
                <div class="stats-container">
                    <!-- 用户统计卡片 -->
                    <div class="stat-card">
                        <h3><i class="fas fa-users"></i> 用户统计</h3>
                        <div class="stat-grid">
                            <div class="stat-item">
                                <span class="stat-label">总用户数</span>
                                <span class="stat-value"><%= users.size() - 1 %></span>
                            </div>
                            <div class="stat-item">
                                <span class="stat-label">活跃用户</span>
                                <span class="stat-value"><%= users.stream().filter(u -> u.getStatus() == 1).count() - 1 %></span>
                            </div>
                            <div class="stat-item">
                                <span class="stat-label">禁用用户</span>
                                <span class="stat-value"><%= users.stream().filter(u -> u.getStatus() == 0).count() %></span>
                            </div>
                        </div>
                        <div class="chart-container">
                            <canvas id="userChart"></canvas>
                        </div>
                    </div>

                    <!-- 活动统计卡片 -->
                    <div class="stat-card">
                        <h3><i class="fas fa-calendar-alt"></i> 活动统计</h3>
                        <div class="stat-grid">
                            <div class="stat-item">
                                <span class="stat-label">总活动数</span>
                                <span class="stat-value"><%= activities.size() %></span>
                            </div>
                            <div class="stat-item">
                                <span class="stat-label">进行中</span>
                                <span class="stat-value"><%= activities.stream().filter(a -> a.getStatus() == 1).count() %></span>
                            </div>
                            <div class="stat-item">
                                <span class="stat-label">未开始</span>
                                <span class="stat-value"><%= activities.stream().filter(a -> a.getStatus() == 0).count() %></span>
                            </div>
                            <div class="stat-item">
                                <span class="stat-label">已结束</span>
                                <span class="stat-value"><%= activities.stream().filter(a -> a.getStatus() == 2).count() %></span>
                            </div>
                        </div>
                        <div class="chart-container">
                            <canvas id="activityChart"></canvas>
                        </div>
                    </div>

                    <!-- 参与度统计卡片 -->
                    <div class="stat-card">
                        <h3><i class="fas fa-chart-pie"></i> 参与度统计</h3>
                        <div class="stat-grid">
                            <div class="stat-item">
                                <span class="stat-label">总参与人次</span>
                                <span class="stat-value"><%= activities.stream().mapToInt(Activity::getCurrentParticipants).sum() %></span>
                            </div>
                            <div class="stat-item">
                                <span class="stat-label">平均参与人数</span>
                                <span class="stat-value">
                                    <%= activities.isEmpty() ? 0 : 
                                        String.format("%.1f", activities.stream()
                                            .mapToInt(Activity::getCurrentParticipants)
                                            .average()
                                            .orElse(0)) %>
                                </span>
                            </div>
                            <div class="stat-item">
                                <span class="stat-label">参与率</span>
                                <span class="stat-value">
                                    <%= activities.isEmpty() ? "0%" :
                                        String.format("%.1f%%", 
                                            (double)activities.stream().mapToInt(Activity::getCurrentParticipants).sum() /
                                            activities.stream().mapToInt(Activity::getMaxParticipants).sum() * 100) %>
                                </span>
                            </div>
                        </div>
                        <div class="chart-container">
                            <canvas id="participationChart"></canvas>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

这是我添加的数据统计效果图

接下来我将会解决第二个优化,向里面添加可视化的活动卡片
这是源代码的效果图

现在我向里面添加卡片视图的代码,这是一个独立的jsp文件,我们只需要按下相对应的按钮就可跳转到ActivityCard.jsp
修改后相对应的界面显示为

点击查看代码
                    <div class="action-bar-right">
                        <button class="view-btn" onclick="transitionToCards()">
                            <i class="fas fa-th"></i>
                            切换卡片视图
                        </button>
                    </div>


ActivityCard.jsp的布局为


里面具有一定的动画效果和响应式布局,如果鼠标放在卡片上的话,也会有功能进行体现。
响应式布局

下面为这个jsp界面的代码

点击查看代码
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%> <%@ page import="bean.Activity"%> <%@ page
import="dao.ActivityDAO"%> <%@ page import="java.util.List"%> <%@ page
import="java.util.ArrayList"%> <%@ page import="java.text.SimpleDateFormat"%>
<%@ page import="java.util.Date"%>

<% ActivityDAO activityDAO = new ActivityDAO(); List<Activity>
  activities = activityDAO.getAllActivities(); %>

  <% SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); %>

  <!DOCTYPE html>
  <html>
    <head>
      <meta charset="UTF-8" />
      <title>活动卡片视图</title>
      <script>
        // 添加动态状态更新功能
        function updateActivityStatuses() {
          fetch("activity?action=getStatuses")
            .then((response) => response.json())
            .then((statuses) => {
              statuses.forEach((status) => {
                const statusElement = document.querySelector(
                  '[data-activity-id="' +
                    status.activityId +
                    '"] .activity-status'
                );
                if (statusElement) {
                  statusElement.textContent = getStatusText(status.status);
                  statusElement.className =
                    "activity-status " + getStatusClass(status.status);
                }
              });
            })
            .catch((error) => console.error("Error:", error));
        }

        // 定期更新状态
        function startStatusUpdates() {
          // 每30秒更新一次状态
          setInterval(updateActivityStatuses, 30000);
        }

        // 页面加载完成后启动自动更新
        window.onload = function () {
          startStatusUpdates();
        };

        // 辅助函数
        function getStatusText(status) {
          switch (status) {
            case 0:
              return "未开始";
            case 1:
              return "进行中";
            case 2:
              return "已结束";
            case 3:
              return "已取消";
            default:
              return "未知";
          }
        }

        function getStatusClass(status) {
          switch (status) {
            case 0:
              return "status-upcoming";
            case 1:
              return "status-ongoing";
            case 2:
              return "status-completed";
            default:
              return "";
          }
        }
      </script>

    </head>
    <body>
      <div class="container">
        <div class="page-header">
          <h1 class="page-title">活动卡片视图</h1>
          <button onclick="transitionToAdmin()" class="back-btn">
            <i class="fas fa-arrow-left"></i>
            返回列表视图
          </button>
        </div>

        <div class="search-bar">
          <input
            type="text"
            id="searchInput"
            placeholder="输入活动名称或地点搜索..."
          />
          <select id="searchType">
            <option value="name">按名称搜索</option>
            <option value="location">按地点搜索</option>
          </select>
          <button class="action-btn search-btn" onclick="searchActivities()">
            搜索
          </button>
          <button class="action-btn reset-btn" onclick="resetSearch()">
            重置
          </button>
        </div>

        <div class="cards-container">
          <% if(!activities.isEmpty()) { for(Activity activity : activities) {
          %>
          <div
            class="activity-card"
            data-activity-id="<%= activity.getActivityId() %>"
          >
            <div
              class="activity-status <%= getStatusClass(activity.getStatus()) %>"
            >
              <%= getStatusText(activity.getStatus()) %>
            </div>
            <div class="activity-title"><%= activity.getActivityName() %></div>
            <div class="activity-info">
              📍 地点:<%= activity.getLocation() %>
            </div>
            <div class="activity-info">
              🕒 开始时间:<%= dateFormat.format(activity.getStartTime()) %>
            </div>
            <div class="activity-info">
              🕕 结束时间:<%= dateFormat.format(activity.getEndTime()) %>
            </div>
            <div class="activity-info">
              👥 参与人数:<%= activity.getCurrentParticipants() %>/<%=
              activity.getMaxParticipants() %>
            </div>
            <div class="activity-info">
              📝 描述:<%= activity.getDescription() != null ?
              activity.getDescription() : "暂无描述" %>
            </div>
            <div class="activity-actions">
              <button
                type="button"
                class="action-btn edit-btn"
                onclick="showEditModal('<%= activity.getActivityId() %>')"
              >
                编辑
              </button>
              <form
                action="${pageContext.request.contextPath}/activity"
                method="post"
                style="display: inline"
              >
                <input type="hidden" name="action" value="delete" />
                <input
                  type="hidden"
                  name="activityId"
                  value="<%= activity.getActivityId() %>"
                />
                <input
                  type="hidden"
                  name="returnUrl"
                  value="activityCards.jsp"
                />
                <button
                  type="submit"
                  class="action-btn delete-btn"
                  onclick="return confirm('确定要删除该活动吗?')"
                >
                  删除
                </button>
              </form>
            </div>
          </div>
          <% } } else { %>
          <div class="no-activities">
            <p>暂无活动数据</p>
          </div>
          <% } %>
        </div>
      </div>

      <!-- 编辑活动模态框 -->
      <div id="editModal" class="modal">
        <div class="modal-content">
          <span class="close">&times;</span>
          <h2>编辑活动</h2>
          <form
            id="editActivityForm"
            action="activity"
            method="post"
            onsubmit="return validateEditForm()"
          >
            <input type="hidden" name="action" value="edit" />
            <input type="hidden" name="returnUrl" value="activityCards.jsp" />
            <input type="hidden" id="editActivityId" name="activityId" />
            <div class="form-group">
              <label for="editActivityName">活动名称:</label>
              <input
                type="text"
                id="editActivityName"
                name="activityName"
                required
                maxlength="50"
              />
            </div>
            <div class="form-group">
              <label for="editLocation">活动地点:</label>
              <input
                type="text"
                id="editLocation"
                name="location"
                required
                maxlength="100"
              />
            </div>
            <div class="form-group">
              <label for="editStartTime">开始时间:</label>
              <input
                type="datetime-local"
                id="editStartTime"
                name="startTime"
                required
              />
            </div>
            <div class="form-group">
              <label for="editEndTime">结束时间:</label>
              <input
                type="datetime-local"
                id="editEndTime"
                name="endTime"
                required
              />
            </div>
            <div class="form-group">
              <label for="editMaxParticipants">活动人数上限:</label>
              <input
                type="number"
                id="editMaxParticipants"
                name="maxParticipants"
                required
              />
              <input
                type="hidden"
                id="currentParticipants"
                name="currentParticipants"
              />
            </div>
            <div class="form-group">
              <label for="editDescription">活动描述:</label>
              <textarea
                id="editDescription"
                name="description"
                rows="4"
                maxlength="500"
              ></textarea>
            </div>
            <button type="submit" class="action-btn edit-btn">保存修改</button>
          </form>
        </div>
      </div>

      <div class="page-transition"></div>

      <script>
        function searchActivities() {
          const searchInput = document
            .getElementById("searchInput")
            .value.toLowerCase();
          const searchType = document.getElementById("searchType").value;
          const cards = document.querySelectorAll(".activity-card");
          let hasResults = false;

          cards.forEach((card) => {
            const name = card
              .querySelector(".activity-title")
              .textContent.toLowerCase();
            const location = card
              .querySelector(".activity-info")
              .textContent.toLowerCase();
            const shouldShow =
              searchType === "name"
                ? name.includes(searchInput)
                : location.includes(searchInput);

            card.classList.remove("filtered-out", "filtered-in");

            if (shouldShow) {
              card.style.display = "";
              card.classList.add("filtered-in");
              hasResults = true;
            } else {
              card.classList.add("filtered-out");
              setTimeout(() => {
                card.style.display = "none";
              }, 400); // 与动画持续时间匹配
            }
          });

          // 处理无搜索结果的情况
          const noResultsEl = document.querySelector(".no-results");
          if (!hasResults) {
            if (!noResultsEl) {
              const message = document.createElement("div");
              message.className = "no-results";
              message.textContent = "没有找到相关活动";
              document.querySelector(".cards-container").appendChild(message);
            }
          } else if (noResultsEl) {
            noResultsEl.remove();
          }
        }

        function resetSearch() {
          document.getElementById("searchInput").value = "";
          const cards = document.querySelectorAll(".activity-card");

          cards.forEach((card, index) => {
            card.style.display = "";
            card.classList.remove("filtered-out");
            card.classList.add("filtered-in");
            // 重新添加交错动画延迟
            card.style.animationDelay = `${index * 0.1}s`;
          });

          // 移除无结果提示
          const noResultsEl = document.querySelector(".no-results");
          if (noResultsEl) {
            noResultsEl.remove();
          }
        }

        // 添加回车键搜索功能
        document
          .getElementById("searchInput")
          .addEventListener("keypress", function (e) {
            if (e.key === "Enter") {
              searchActivities();
            }
          });

        // 编辑活动相关函数
        var modal = document.getElementById("editModal");
        var span = document.getElementsByClassName("close")[0];

        function formatDateTime(timestamp) {
          if (typeof timestamp === "string") {
            timestamp = parseInt(timestamp);
          }
          var date = new Date(timestamp);
          var localDate = new Date(
            date.getTime() - date.getTimezoneOffset() * 60000
          );
          return localDate.toISOString().slice(0, 16);
        }

        function showEditModal(activityId) {
          document.body.classList.add("modal-open");
          fetch("activity?action=getActivity&activityId=" + activityId)
            .then((response) => {
              if (!response.ok) {
                throw new Error("Network response was not ok");
              }
              return response.text().then((text) => {
                try {
                  return JSON.parse(text);
                } catch (e) {
                  console.error("Parse error:", text);
                  throw new Error("Invalid JSON response");
                }
              });
            })
            .then((activity) => {
              if (activity.error) {
                throw new Error(activity.error);
              }

              document.getElementById("editActivityId").value =
                activity.activityId;
              document.getElementById("editActivityName").value =
                activity.activityName;
              document.getElementById("editLocation").value = activity.location;

              var startDateTime = formatDateTime(activity.startTime);
              var endDateTime = formatDateTime(activity.endTime);

              document.getElementById("editStartTime").value = startDateTime;
              document.getElementById("editEndTime").value = endDateTime;

              document.getElementById("editMaxParticipants").value =
                activity.maxParticipants;
              document.getElementById("currentParticipants").value =
                activity.currentParticipants;
              document.getElementById("editDescription").value =
                activity.description || "";

              document.getElementById("editMaxParticipants").min =
                activity.currentParticipants;

              modal.style.display = "block";
            })
            .catch((error) => {
              console.error("Error:", error);
              alert("获取活动信息失败: " + error.message);
            });
        }

        function validateEditForm() {
          var startTime = new Date(
            document.getElementById("editStartTime").value
          );
          var endTime = new Date(document.getElementById("editEndTime").value);
          var now = new Date();
          var oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);

          if (startTime < oneHourAgo) {
            alert("开始时间不能早于当前时间前一小时");
            return false;
          }

          if (endTime < now) {
            alert("结束时间不能早于当前时间");
            return false;
          }

          if (endTime <= startTime) {
            alert("结束时间必须晚于开始时间");
            return false;
          }

          var maxParticipants = document.getElementById(
            "editMaxParticipants"
          ).value;
          var currentParticipants = document.getElementById(
            "currentParticipants"
          ).value;
          if (maxParticipants < currentParticipants) {
            alert("人数上限不能小于当前参与人数");
            return false;
          }

          return true;
        }


        function closeModal() {
          document.body.classList.remove("modal-open");
          modal.style.display = "none";
        }

        span.onclick = closeModal;

        window.onclick = function (event) {
          if (event.target == modal) {
            closeModal();
          }
        };

        // 添加删除确认功能
        function confirmDelete(activityId) {
          const confirmed = confirm("确定要删除该活动吗?");
          if (confirmed) {
            // 找到对应的表单并提交
            const form = document.querySelector(
              `form[data-activity-id="${activityId}"]`
            );
            if (form) {
              form.submit();
            }
          }
        }

        // 添加按钮波纹效果
        document.querySelectorAll(".action-btn").forEach((button) => {
          button.addEventListener("click", function (e) {
            const rect = button.getBoundingClientRect();
            const ripple = document.createElement("div");
            ripple.className = "ripple";
            ripple.style.left = `${e.clientX - rect.left}px`;
            ripple.style.top = `${e.clientY - rect.top}px`;
            button.appendChild(ripple);

            setTimeout(() => {
              ripple.remove();
            }, 600);
          });
        });

        function transitionToAdmin() {
          // 创建过渡动画元素
          const transition = document.createElement("div");
          transition.className = "page-transition";
          document.body.appendChild(transition);

          // 触发过渡动画
          setTimeout(() => {
            transition.classList.add("active");
          }, 50);

          // 等待动画完成后跳转
          setTimeout(() => {
            window.location.href = "admin.jsp";
          }, 500);
        }
      </script>
    </body>
  </html>

  <%! private String getStatusText(int status) { switch(status) { case 0: return
  "未开始"; case 1: return "进行中"; case 2: return "已结束"; case 3: return
  "已取消"; default: return "未知"; } } private String getStatusClass(int
  status) { switch(status) { case 0: return "status-upcoming"; case 1: return
  "status-ongoing"; case 2: return "status-completed"; default: return ""; } }
  %>

总结:
难点

每次修改之后都会或多或少的出现页面崩塌的现象,很多时候都是删删改改的去修理。
我需要添加布局,所以在原有的基础上,找了很久的css文件,和相对应的格式。
对响应式布局进行学习和一些简单动画进行学习。
有时候会跳转错误,我所以我从原来的相对路径改成绝对路径,且跳转到的地方也对其进行优化,原本会跳转回管理员初始页面,现在可以跳转到相对与卡片布局按钮的页面。
写入信息后,两个页面所显示的信息会出现无法同步的问题,所以我对两个页面都添加上了定期刷新的功能,保证30s可以对数据进行更新。
思考

我对于逆向工程架构的理解是。
我们需要需要深入理解原有系统的架构设计。
从直接在JSP中获取数据的方式,重构为统一的数据获取接口。

posted on 2025-02-25 16:48  传奇机长永不落幕  阅读(35)  评论(0)    收藏  举报