逆向软件设计和开发————基于对学生活动管理系统的功能添加
项目介绍
本文为根据一位朋友原有的大二上期末大作业所写的项目,对其进行功能的添加。
其项目主要为对学生活动的进行管理,可分为用户端和管理端。
运行环境
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">×</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中获取数据的方式,重构为统一的数据获取接口。