员工管理添加登录验证 - 注册 - 管理授权
16lz
2021-03-02
员工管理添加登录验证 - 注册 - 管理授权
作业内容:将上周的员工管理系统加上登录验证功能,实现一个功能相对完整的:员工管理系统“,试试看,你行的
1. staffs 表添加 password 字段和密码为 md5 字段 id 的值作测试
alter table staffs add password varchar(128) not null after name;
update staffs set password = md5(id);
2. 添加前端静态页 0302.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>员工管理系统</title>
<link rel="stylesheet" href="style.css">
<style>
body {
margin: 0;
}
header {
display: flex;
width: 90%;
justify-content: space-between;
background-color: #333;
padding: 0.5em 1em;
}
header a {
color: #bbb;
text-decoration: none;
}
header a:hover {
color: white;
}
header nav {
margin-left: auto;
}
</style>
</head>
<body>
<header>
<a href="">首页</a>
<!-- 登录注册渲染区 -->
<nav></nav>
</header>
<table>
<caption>员工管理系统</caption>
<!-- 表头渲染区 -->
<thead></thead>
<!-- 数据渲染区 -->
<tbody></tbody>
</table>
<!-- 分页渲染区 -->
<p></p>
<!-- 模态框编辑区 -->
<div class="modal edit">
<div class="modal-drop"></div>
<div class="modal-body">
<button class="close">关闭</button>
<form name="editform">
<table>
<caption>员工信息编辑</caption>
<tbody>
<tr>
<td>姓名</td>
<td><input type="text" value="" name="name"></td>
</tr>
<tr>
<td>性别</td>
<td>
<select name="gender">
<option value="male" selected>男</option>
<option value="female">女</option>
</select>
</td>
</tr>
<tr>
<td>工资</td>
<td><input type="text" value="" name="salary"></td>
</tr>
<tr>
<td>邮箱</td>
<td><input type="text" value="" name="email"></td>
</tr>
<tr>
<td>生日</td>
<td><input type="text" value="" name="birthday"></td>
</tr>
<tr>
<td colspan="2"><button class="save">保存</button></td>
</tr>
</tbody>
</table>
</form>
</div>
</div>
<!-- 模态登录区 -->
<div class="modal login">
<div class="modal-drop"></div>
<div class="modal-body">
<button class="close">关闭</button>
<form name="loginform">
<table>
<caption>用户登录</caption>
<tbody>
<tr>
<td>邮箱</td>
<td><input type="text" value="" name="email" placeholder="邮箱"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password" placeholder="密码"></td>
</tr>
<tr>
<td colspan="2"><button type="button" class="login">登录</button>
<p class="tips"></p>
</td>
</tr>
</tbody>
</table>
</form>
</div>
</div>
<!-- 模态注册区 -->
<div class="modal register">
<div class="modal-drop"></div>
<div class="modal-body">
<button class="close">关闭</button>
<form name="registerform">
<table>
<caption>用户注册</caption>
<tbody>
<tr>
<td>姓名</td>
<td><input type="text" value="" name="name" placeholder="用户名"></td>
</tr>
<tr>
<td>性别</td>
<td>
<select name="gender">
<option value="male" selected>男</option>
<option value="female">女</option>
</select>
</td>
</tr>
<tr>
<td>工资</td>
<td><input type="text" value="" name="salary" placeholder="薪水"></td>
</tr>
<tr>
<td>邮箱</td>
<td><input type="text" value="" name="email" placeholder="邮箱"></td>
</tr>
<tr>
<td>生日</td>
<td><input type="text" value="" name="birthday" placeholder="生日"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password" placeholder="密码"></td>
</tr>
<tr>
<td>重复密码</td>
<td><input type="password" name="password2" placeholder="重复密码"></td>
</tr>
<tr>
<td colspan="2"><button type="button" class="register">注册</button>
<p class="tips"></p>
</td>
</tr>
</tbody>
</table>
</form>
</div>
</div>
<!-- 模态框样式 -->
<style>
/* 模态框初始化隐藏 */
.modal {
display: none;
}
/* 遮罩层 */
.modal .modal-drop {
position: fixed;
background-color: rgb(0, 0, 0, .5);
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.modal .modal-body {
position: fixed;
background-color: #fff;
padding: 1em;
overflow: hidden;
max-width: 25em;
max-height: 20em;
/* 水平垂直自动居中 */
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
/* 登录框高度 */
.modal.login .modal-body {
max-height: 14em;
}
/* 注册框高度 */
.modal.register .modal-body {
max-height: 24em;
}
/* 关闭按钮 */
.modal .modal-body .close {
position: absolute;
top: 1.1em;
right: 1.5em;
width: 3em;
height: 1.5em;
}
</style>
<!-- 引入js文件 -->
<script src="0302.js"></script>
</body>
</html>
3. 引入的 style.css 是默认的
* {
margin: 0;
padding: 0;
box-sizing: border-box;
color: #555;
}
body {
display: flex;
flex-direction: column;
align-items: center;
}
/*表格样式*/
table {
width: 90%;
border: 1px solid;
border-collapse: collapse;
text-align: center;
}
table caption {
font-size: 1.2rem;
margin: 10px;
}
table td,
table th {
border: 1px solid;
padding: 5px;
}
table tr:hover {
background-color: #eee;
}
table thead tr:only-of-type {
background-color: lightcyan;
}
table button {
width: 56px;
height: 26px;
}
table button:last-of-type {
color: red;
}
table button {
cursor: pointer;
margin: 0 3px;
}
/*分页条样式*/
body > p {
display: flex;
}
p > a {
text-decoration: none;
color: #555;
border: 1px solid #888;
padding: 5px 10px;
margin: 10px 2px;
}
.active {
background-color: seagreen;
color: white;
border: 1px solid seagreen;
}
4. 引入 0302.js
// 页面载入完成时默认渲染第一页数据
window.onload = function () {
select(1);
};
// 增
// ...
// 编辑和删除记录
document.querySelector('table:first-of-type tbody').addEventListener('click', ev => {
// 禁止自动点击
ev.preventDefault();
// 获取记录 id
const id = ev.target.parentNode.parentNode.querySelector('td').textContent * 1;
// 事件类型
switch (ev.target.getAttribute('class')) {
// 编辑
case 'edit':
// 显示模态框
document.querySelector('.modal.edit').style.display = 'block';
// 关闭模态框
document.querySelector('.modal.edit .close').addEventListener('click', (eve) => {
eve.preventDefault();
document.querySelector('.modal.edit').style.display = 'none';
});
// 保存时关闭
document.querySelector('.modal.edit .save').addEventListener('click', (eve) => {
eve.preventDefault();
document.querySelector('.modal.edit').style.display = 'none';
});
// 模态框外点击关闭
document.querySelector('.modal.edit .modal-drop').addEventListener('click', (eve) => {
eve.preventDefault();
document.querySelector('.modal.edit').style.display = 'none';
});
// 获取数据
let name = ev.target.parentNode.parentNode.querySelector('td:nth-of-type(2)').textContent;
let gender = ev.target.parentNode.parentNode.querySelector('td:nth-of-type(3)').textContent;
let salary = ev.target.parentNode.parentNode.querySelector('td:nth-of-type(4)').textContent * 1;
let email = ev.target.parentNode.parentNode.querySelector('td:nth-of-type(5)').textContent;
let birthday = ev.target.parentNode.parentNode.querySelector('td:nth-of-type(6)').textContent;
// console.log(id, name, gender, salary, email, birthday);
// 渲染到模态框
let form = document.forms.editform;
form.name.value = name;
form.gender.value = gender;
form.salary.value = salary;
form.email.value = email;
form.birthday.value = birthday;
// 编辑事件
document.querySelector('.modal .save').addEventListener('click', (eve) => {
// 禁止默认提交和冒泡
eve.preventDefault();
eve.stopPropagation();
// 获取模态框编辑数据
name = form.name.value;
gender = form.gender.value;
salary = form.salary.value;
email = form.email.value;
birthday = form.birthday.value;
// console.log(id, name, gender, salary, email, birthday);
// 创建对象
let xhr = new XMLHttpRequest();
// 配置参数
xhr.open('post', 'handle.php?action=update&id=' + id);
// 处理请求
xhr.onload = () => {
// console.log(xhr.response);
// 更新数据写回页面
ev.target.parentNode.parentNode.querySelector('td:nth-of-type(2)').textContent = name;
ev.target.parentNode.parentNode.querySelector('td:nth-of-type(3)').textContent = gender;
ev.target.parentNode.parentNode.querySelector('td:nth-of-type(4)').textContent = salary;
ev.target.parentNode.parentNode.querySelector('td:nth-of-type(5)').textContent = email;
ev.target.parentNode.parentNode.querySelector('td:nth-of-type(6)').textContent = birthday;
};
// 发送请求
xhr.send(new FormData(document.forms.namedItem('editform')));
});
break;
// 删除
case 'delete':
if (confirm('确认删除编号 ' + id + ' 记录?')) {
// 创建对象
let xhr = new XMLHttpRequest();
// 配置参数
xhr.open('get', 'handle.php?action=delete&id=' + id);
// 处理请求
xhr.onload = () => {
// 删除节点
ev.target.parentNode.parentNode.remove();
};
// 发送请求
xhr.send(null);
}
break
}
});
// 查
function select(page = 1) {
// 创建对象
const xhr = new XMLHttpRequest();
// 配置参数
xhr.open('get', 'handle.php?action=select&page=' + page);
// 数据类型
xhr.responseType = 'json';
// 处理请求
xhr.onload = () => {
// console.log(xhr.response);
let pages = xhr.response.pages;
let staffs = xhr.response.staffs;
// 是否登录
let is_login = xhr.response.is_login;
// 是否管理员 admin
let is_admin = xhr.response.is_admin;
// 渲染表头
document.querySelector('table:first-of-type thead').innerHTML = get_thead(is_admin);
// 渲染数据
document.querySelector('table:first-of-type tbody').innerHTML = get_datas(staffs, is_admin);
// 渲染分页
document.querySelector('p:first-of-type').innerHTML = get_pages(page, pages);
// 渲染登录注册区
show_loginout();
};
// 发送请求
xhr.send(null);
}
// 无刷新分页
document.querySelector('p:first-of-type').addEventListener('click', ev => {
// 禁用默认跳转
ev.preventDefault();
// 点击当前激活页,无效点击
if (ev.target.classList.contains('active')) return;
// 去掉原激活样式
[...ev.currentTarget.children].forEach(ele => ele.classList.remove('active'));
// 当前页添加激活样式
ev.target.classList.add('active');
// 获取当前页
let page = get_current_page();
// 渲染点击页的数据
select(page);
});
// 渲染数据
function get_datas(datas, is_admin = 0) {
let str = '';
for (let i = 0; i < datas.length; i++) {
str += '<tr>';
str += '<td>' + datas[i]['id'] + '</td>';
str += '<td>' + datas[i]['name'] + '</td>';
str += '<td>' + datas[i]['gender'] + '</td>';
str += '<td>' + datas[i]['salary'] + '</td>';
str += '<td>' + datas[i]['email'] + '</td>';
str += '<td>' + datas[i]['birthday'] + '</td>';
str += '<td>' + datas[i]['create_at'] + '</td>';
// 如果登录显示编辑删除
if (is_admin) {
str += '<td><button class="edit">编辑</button><button class="delete">删除</button></td>';
}
str += '</tr>';
}
return str;
}
// 表头渲染
function get_thead(is_admin = 0) {
let str = '';
str += '<tr>';
str += '<td>编号</td>';
str += '<td>姓名</td>';
str += '<td>性别</td>';
str += '<td>工资</td>';
str += '<td>邮箱</td>';
str += '<td>生日</td>';
str += '<td>入职时间</td>';
// 如果登录显示操作
if (is_admin) {
str += '<td>操作</td>';
}
str += '</tr>';
return str;
}
// 分页数据
function get_pages(page = 1, pages) {
let paginate = '';
let active = '';
// 首页|上一页
if (page <= 1) page = 1;
if (page !== 1) {
paginate += '<a href="' + document.URL + '?p=1">首页</a>';
paginate += '<a href="' + document.URL + '?p=' + Math.max(1, page - 1) + '">上一页</a>';
}
// 高亮分页
for (i = 1; i <= pages; i++) {
active = '';
if (page == i) active = ' class="active"';
paginate += '<a href="' + document.URL + '?p=' + i + '"' + active + '>' + i + '</a>';
}
// 下一页|尾页
if (page >= pages) page = pages;
if (page !== pages) {
paginate += '<a href="' + document.URL + '?p=' + Math.min(page + 1, pages) + '">下一页</a>';
paginate += '<a href="' + document.URL + '?p=' + pages + '">尾页</a>';
}
return paginate;
}
// 登录|注册模态框
document.querySelector('header > nav').addEventListener('click', ev => {
// 禁止自动点击
ev.preventDefault();
// 事件类型
switch (ev.target.getAttribute('class')) {
// 登录
case 'login':
// 显示模态框
document.querySelector('.modal.login').style.display = 'block';
// 关闭模态框
document.querySelector('.modal.login .close').addEventListener('click', (eve) => {
eve.preventDefault();
document.querySelector('.modal.login').style.display = 'none';
});
// 模态框外点击关闭
document.querySelector('.modal.login .modal-drop').addEventListener('click', (eve) => {
eve.preventDefault();
document.querySelector('.modal.login').style.display = 'none';
});
// 登录事件
document.querySelector('.modal.login .login').addEventListener('click', (eve) => {
// 获取登录框数据
let form = document.forms.loginform;
[form.email, form.password].forEach((item) => item.oninput = () => tips.innerHTML = null);
// 提示框
let tips = document.querySelector('.modal.login .tips');
// 非空验证
if (!form.email.value.trim().length) {
form.email.focus();
tips.innerHTML = '邮箱为空';
return;
}
if (!form.password.value.trim().length) {
form.password.focus();
tips.innerHTML = '密码为空';
return;
}
// 创建对象
const xhr = new XMLHttpRequest();
// 配置参数
xhr.open('post', 'handle.php?action=login');
// 响应类型
xhr.responseType = 'json';
// 处理请求
xhr.onload = () => {
// console.log(xhr.response);
// 显示登录消息
tips.innerHTML = xhr.response.msg;
// 登录失败时
if (xhr.response.status) {
return;
}
// 登录成功,延时关闭登录框
setTimeout(() => document.querySelector('.modal.login').style.display = 'none', 2000);
// 获取当前页码
let page = get_current_page();
// 渲染数据
select(page);
};
// 发送请求
xhr.send(new FormData(form));
});
break;
// 添加和注册一样
case 'add':
break;
// 注册
case 'register':
// 显示模态框
document.querySelector('.modal.register').style.display = 'block';
// 关闭模态框
document.querySelector('.modal.register .close').addEventListener('click', (eve) => {
eve.preventDefault();
document.querySelector('.modal.register').style.display = 'none';
});
// 模态框外点击关闭
document.querySelector('.modal.register .modal-drop').addEventListener('click', (eve) => {
eve.preventDefault();
document.querySelector('.modal.register').style.display = 'none';
});
// 注册事件
document.querySelector('.modal.register .register').addEventListener('click', (eve) => {
// 获取登录框数据
let form = document.forms.registerform;
[form.email, form.password, form.password2].forEach((item) => item.oninput = () => tips.innerHTML = null);
// 提示框
let tips = document.querySelector('.modal.register .tips');
// 非空验证
if (!form.email.value.trim().length) {
form.email.focus();
tips.innerHTML = '邮箱为空';
return;
}
if (!form.password.value.trim().length) {
form.password.focus();
tips.innerHTML = '密码为空';
return;
}
if (!form.password2.value.trim().length) {
form.password2.focus();
tips.innerHTML = '重复密码为空';
return;
}
if (form.password.value !== form.password2.value) {
form.password.focus();
tips.innerHTML = '两次密码不一致';
return;
}
// 创建对象
const xhr = new XMLHttpRequest();
// 配置参数
xhr.open('post', 'handle.php?action=register');
// 响应类型
xhr.responseType = 'json';
// 处理请求
xhr.onload = () => {
// console.log(xhr.response);
// 显示注册消息
tips.innerHTML = xhr.response.msg;
// 注册失败时
if (xhr.response.status) {
return;
}
// 注册成功,延时关闭注册框
setTimeout(() => document.querySelector('.modal.register').style.display = 'none', 2000);
// 获取当前页码
let page = get_current_page();
// 渲染数据
select(page);
};
//失败回调
xhr.onerror = () => console.log('XHR Fail....');
// 发送请求
const data = new FormData(form);
data.delete('password2');
xhr.send(data);
});
break;
// 登出
case 'logout':
// 创建对象
const xhr = new XMLHttpRequest();
// 配置参数
xhr.open('post', 'handle.php?action=logout');
// 响应类型
xhr.responseType = 'json';
// 处理请求
xhr.onload = () => {
alert('登出成功!');
// 获取当前页码
let page = get_current_page();
// 渲染数据
select(page);
};
// 发送请求
xhr.send(null);
break;
//添加记录
case 'add':
break;
}
});
// 计算当前页码
function get_current_page() {
let page = 1;
let select = document.querySelector('p:first-of-type').children;
if (select) {
let url = [...select].filter(ele => ele.classList.contains('active')).shift().href;
if (url.indexOf("?") !== -1) {
page = url.split("=")[1];
}
}
return page;
}
// 渲染登录注册
function show_loginout() {
// 创建对象
const xhr = new XMLHttpRequest();
// 配置参数
xhr.open('get', 'handle.php?action=loginout');
// 处理请求
xhr.onload = () => {
// 渲染登录注册区
document.querySelector('header>nav').innerHTML = xhr.response;
};
// 发送请求
xhr.send(null);
}
5. ajax 请求文件 handle.php
<?php
$config = [
'type' => 'mysql',
'host' => '127.0.0.1',
'dbname' => 'phpedu',
'port' => '3306',
'charset' => 'utf8mb4',
'username' => 'root',
'password' => 'root',
];
extract($config);
$dsn = sprintf('%s:host=%s;dbname=%s;port=%s;charset=%s', $type, $host, $dbname, $port, $charset);
try {
$pdo = new PDO($dsn, $username, $password);
// 设置结果集的返回类型
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
// var_dump($pdo,'连接成功');
} catch (PDOException $e) {
die('连接失败:' . $e->getMessage());
}
// 会话开启
if (!session_id()) session_start();
// 每页显示记录数
$action = $_GET['action'] ?? 'select';
$page = $_GET['page'] ?? 1;
switch ($action) {
// 删
case 'delete':
die(del_data($pdo, $_GET['id'] ?? 0));
break;
// 改
case 'update':
$id = $_GET['id'] ?? 0;
$name = $_POST['name'];
$gender = $_POST['gender'];
$salary = $_POST['salary'];
$email = $_POST['email'];
$birthday = $_POST['birthday'];
die(update_data($pdo, $id, $name, $gender, $salary, $email, $birthday));
break;
// 查
case 'select':
die(get_datas($pdo, $page));
// 渲染登录注册
case 'loginout':
if (isset($_SESSION['email']) && $_SESSION['email']) {
$btn = '';
// 管理权限,仅 admin 允许添加员工
if (is_admin($pdo)) {
$btn = ' <a href="javascript:;" class="add">添加</a>';
}
die('<a href="#">' . $_SESSION['email'] . '</a> <a href="javascript:;" class="logout">退出</a>' . $btn);
} else {
die('<a href="javascript:;" class="login">登录</a> <a href="javascript:;" class="register">注册</a>');
}
break;
// 登录
case 'login':
if (isset($_SESSION['email']) && $_SESSION['email']) {
die(json_encode(['status' => 1, 'msg' => '你已经登录!']));
}
$sql = "SELECT email FROM staffs WHERE email = :email AND password = md5(:password)";
$stmt = $pdo->prepare($sql);
if ($stmt->execute($_POST)) {
if ($stmt->fetch()) {
// 保存 session
$_SESSION['email'] = $_POST['email'];
die(json_encode(['status' => 0, 'msg' => $_POST['email'] . '登录成功,请稍后...']));
} else {
die(json_encode(['status' => 1, 'msg' => '邮箱或密码错误!']));
}
}
die(json_encode(['status' => 1, 'msg' => '发生错误了,重试!']));
break;
// 增,和注册一样
case 'add':
break;
// 注册
case 'register':
if (isset($_SESSION['email']) && $_SESSION['email']) {
die(json_encode(['status' => 1, 'msg' => '你已经登录!']));
}
$sql = "SELECT COUNT(1) as count FROM staffs WHERE email = '{$_POST['email']}'";
if ($pdo->query($sql)->fetch()['count']) {
die(json_encode(['status' => 1, 'msg' => '该邮箱已被注册,换一个!']));
}
$sql = 'INSERT staffs SET name = :name, password = md5(:password), gender = :gender, salary = :salary, email = :email, birthday = :birthday';
$stmt = $pdo->prepare($sql);
if ($stmt->execute($_POST)) {
if ($stmt->rowCount()) {
// 实现注册并自动登录
$_SESSION['email'] = $_POST['email'];
die(json_encode(['status' => 0, 'msg' => '注册成功,请稍后...']));
} else {
die(json_encode(['status' => 1, 'msg' => '注册失败,请联系管理员']));
}
}
die(json_encode(['status' => 1, 'msg' => '发生错误了,重试!']));
break;
// 登出
case 'logout':
if (isset($_SESSION['email'])) unset($_SESSION['email']);
break;
default:
exit('Error!');
}
// 更新记录
function update_data($pdo, $id, $name, $gender, $salary, $email, $birthday)
{
if ($id) {
$sql = "UPDATE staffs SET name = :name, gender = :gender, salary = :salary, email = :email, birthday = :birthday WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->bindParam(':name', $name);
$stmt->bindParam(':gender', $gender);
$stmt->bindParam(':salary', $salary);
$stmt->bindParam(':email', $email);
$stmt->bindParam(':birthday', $birthday);
$stmt->execute();
if ($stmt->rowCount() > 0) {
return '更新成功!';
}
}
return '更新错误!';
}
// 删除记录
function del_data($pdo, $id = 0)
{
if ($id) {
$sql = "DELETE FROM staffs WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
if ($stmt->rowCount() > 0) {
return '删除成功!';
}
}
return '删除错误!';
}
// 数据总页数
function get_pages($pdo, $page = 1, $num = 5)
{
// 总页数
$num = 5;
$sql = "SELECT CEIL(COUNT(1)/{$num}) total FROM staffs";
$pages = $pdo->query($sql)->fetch()['total'];
return $pages;
}
// 每页显示数
function get_datas($pdo, $page = 1, $num = 5)
{
// 获取总页数
$pages = get_pages($pdo);
// 每页显示的数据
$offset = ($page - 1) * $num;
$sql = "SELECT * FROM `staffs` LIMIT {$offset}, {$num}";
$stmt = $pdo->prepare($sql);
$stmt->execute();
$staffs = $stmt->fetchAll();
// $staffs = $pdo->query($sql)->fetchAll();
return json_encode(['pages' => $pages, 'staffs' => $staffs, 'is_admin' => is_admin($pdo)]);
}
// 是否管理员 admin
function is_admin($pdo)
{
if (isset($_SESSION['email']) && $_SESSION['email']) {
$sql = "SELECT COUNT(1) as count FROM staffs WHERE email = '{$_SESSION['email']}' AND name = 'admin'";
if ($pdo->query($sql)->fetch()['count']) {
return true;
}
}
return false;
}
- 注册一个 root 用户名
- 注册成功后点击最后一页查看
- 退出后,以 admin 的 admin@php.cn 登录
- 对管理员开放操作权限
更多相关文章
- PHP实战: 人员管理系统(续)
- 【asp.net core 系列】- 11 Service层的实现样板
- 辣鸡,使用CAS机制完成SSO
- cookie在二级域名间共享完成sso
- 详解微信小程序登录wx.login(Object object)
- 你没有看错,爬网页数据,C# 也可以像 Jquery 那样
- 功能测试——app测试要点最全分析
- 克隆虚拟机和相互登录
- 一套简单通用的Java后台管理系统,拿来即用,非常方便(附项目地址)