You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

666 lines
26 KiB

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>人员关系管理系统</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #3498db;
--secondary-color: #2c3e50;
--success-color: #2ecc71;
--warning-color: #f39c12;
--danger-color: #e74c3c;
--light-color: #ecf0f1;
--dark-color: #34495e;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f7fa;
color: #333;
padding-top: 20px;
}
.header {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: white;
padding: 20px;
border-radius: 10px;
margin-bottom: 30px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.card {
border: none;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
transition: transform 0.3s, box-shadow 0.3s;
margin-bottom: 20px;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.card-header {
background-color: var(--secondary-color);
color: white;
border-radius: 10px 10px 0 0 !important;
padding: 15px 20px;
font-weight: 600;
}
.person-item {
display: flex;
align-items: center;
padding: 12px 15px;
border-bottom: 1px solid #eee;
transition: background-color 0.2s;
}
.person-item:last-child {
border-bottom: none;
}
.person-item:hover {
background-color: #f8f9fa;
}
.person-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: var(--primary-color);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 15px;
}
.person-info {
flex-grow: 1;
}
.person-name {
font-weight: 600;
margin-bottom: 3px;
}
.person-status {
font-size: 0.85rem;
color: #6c757d;
}
.person-actions {
display: flex;
gap: 10px;
}
.btn-sm {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
.relationship-view {
height: 500px;
overflow-y: auto;
padding: 20px;
background-color: white;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
}
.tree-view ul {
list-style-type: none;
padding-left: 20px;
}
.tree-view li {
margin: 10px 0;
position: relative;
}
.tree-view li::before {
content: "";
position: absolute;
top: 0;
left: -20px;
border-left: 1px solid #ccc;
height: 100%;
}
.tree-view li::after {
content: "";
position: absolute;
top: 10px;
left: -20px;
border-top: 1px solid #ccc;
width: 20px;
}
.tree-view li:last-child::before {
height: 10px;
}
.tree-node {
display: inline-block;
padding: 5px 10px;
border-radius: 5px;
background-color: var(--light-color);
margin-bottom: 5px;
}
.modal-content {
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.modal-header {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: white;
border-radius: 10px 10px 0 0;
}
.modal-footer {
border-top: none;
}
.btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.btn-primary:hover {
background-color: #2980b9;
border-color: #2980b9;
}
.btn-success {
background-color: var(--success-color);
border-color: var(--success-color);
}
.btn-success:hover {
background-color: #27ae60;
border-color: #27ae60;
}
.btn-danger {
background-color: var(--danger-color);
border-color: var(--danger-color);
}
.btn-danger:hover {
background-color: #c0392b;
border-color: #c0392b;
}
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 1060;
}
.badge {
font-weight: 500;
padding: 5px 10px;
border-radius: 20px;
}
.badge-primary {
background-color: var(--primary-color);
}
.badge-success {
background-color: var(--success-color);
}
.badge-warning {
background-color: var(--warning-color);
}
.search-box {
position: relative;
margin-bottom: 20px;
}
.search-box input {
padding-left: 40px;
border-radius: 20px;
border: 1px solid #ddd;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
}
.search-box i {
position: absolute;
left: 15px;
top: 10px;
color: #aaa;
}
</style>
</head>
<body>
<div class="container">
<div class="header text-center">
<h1><i class="fas fa-sitemap me-2"></i>人员关系管理系统</h1>
<p class="mb-0">设置和管理人员之间的父子关系</p>
</div>
<div class="row">
<div class="col-md-5">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="fas fa-users me-2"></i>人员列表</span>
<button class="btn btn-sm btn-light" id="addPersonBtn">
<i class="fas fa-plus me-1"></i>添加人员
</button>
</div>
<div class="card-body p-0">
<div class="search-box p-3">
<i class="fas fa-search"></i>
<input type="text" class="form-control" id="searchPerson" placeholder="搜索人员...">
</div>
<div id="personList" style="max-height: 400px; overflow-y: auto;">
<!-- 人员列表将通过JS动态生成 -->
</div>
</div>
</div>
</div>
<div class="col-md-7">
<div class="card">
<div class="card-header">
<i class="fas fa-sitemap me-2"></i>组织关系图
</div>
<div class="card-body">
<div class="relationship-view">
<div class="tree-view" id="relationshipTree">
<!-- 关系树将通过JS动态生成 -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 设置父子关系的模态框 -->
<div class="modal fade" id="setRelationshipModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">设置父子关系</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p><strong id="currentPersonName"></strong> 选择父级:</p>
<select class="form-select" id="parentSelect">
<option value="">无父级(顶级人员)</option>
<!-- 可选父级将通过JS动态生成 -->
</select>
<div class="alert alert-warning mt-3" id="warningMessage" style="display: none;">
<i class="fas fa-exclamation-triangle me-2"></i>
<span id="warningText"></span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="saveRelationship">保存</button>
</div>
</div>
</div>
</div>
<!-- 添加人员的模态框 -->
<div class="modal fade" id="addPersonModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">添加新人员</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="newPersonName" class="form-label">人员姓名</label>
<input type="text" class="form-control" id="newPersonName" placeholder="请输入姓名">
</div>
<div class="mb-3">
<label for="newPersonTitle" class="form-label">职位</label>
<input type="text" class="form-control" id="newPersonTitle" placeholder="请输入职位">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-success" id="saveNewPerson">添加</button>
</div>
</div>
</div>
</div>
<!-- 提示消息 -->
<div class="toast-container"></div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
$(document).ready(function() {
// 示例数据
let people = [
{ id: 1, name: "张三", title: "总经理", parentId: null },
{ id: 2, name: "李四", title: "副总经理", parentId: 1 },
{ id: 3, name: "王五", title: "技术总监", parentId: 1 },
{ id: 4, name: "赵六", title: "市场总监", parentId: 1 },
{ id: 5, name: "钱七", title: "前端开发", parentId: 3 },
{ id: 6, name: "孙八", title: "后端开发", parentId: 3 },
{ id: 7, name: "周九", title: "UI设计师", parentId: 3 },
{ id: 8, name: "吴十", title: "销售经理", parentId: 4 },
{ id: 9, name: "郑十一", title: "销售代表", parentId: 8 },
{ id: 10, name: "王十二", title: "销售代表", parentId: 8 },
{ id: 11, name: "李十三", title: "人事经理", parentId: 2 },
{ id: 12, name: "张十四", title: "财务经理", parentId: 2 },
{ id: 13, name: "刘十五", title: "人事专员", parentId: 11 },
{ id: 14, name: "陈十六", title: "财务专员", parentId: 12 },
{ id: 15, name: "杨十七", title: "测试工程师", parentId: 3 },
{ id: 16, name: "黄十八", title: "产品经理", parentId: 1 },
{ id: 17, name: "周十九", title: "产品助理", parentId: 16 },
{ id: 18, name: "吴二十", title: "实习生", parentId: 5 },
{ id: 19, name: "郑二十一", title: "实习生", parentId: 6 },
{ id: 20, name: "王二十二", title: "实习生", parentId: 7 }
];
// 初始化
renderPersonList();
renderRelationshipTree();
// 渲染人员列表
function renderPersonList() {
const $personList = $('#personList');
$personList.empty();
people.forEach(person => {
const parentName = person.parentId ? people.find(p => p.id === person.parentId).name : "无";
const hasChildren = people.some(p => p.parentId === person.id);
const $item = $(`
<div class="person-item" data-id="${person.id}">
<div class="person-avatar">${person.name.charAt(0)}</div>
<div class="person-info">
<div class="person-name">${person.name}</div>
<div class="person-status">
<span class="text-muted">${person.title}</span>
<span class="ms-2 badge ${person.parentId ? 'bg-info' : 'bg-success'}">${person.parentId ? '子级' : '顶级'}</span>
${hasChildren ? '<span class="ms-2 badge bg-warning">有下属</span>' : ''}
</div>
</div>
<div class="person-actions">
<button class="btn btn-sm btn-outline-primary set-parent-btn">
<i class="fas fa-exchange-alt"></i>
</button>
<button class="btn btn-sm btn-outline-danger remove-btn">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`);
$personList.append($item);
});
// 绑定设置父级按钮事件
$('.set-parent-btn').click(function() {
const personId = $(this).closest('.person-item').data('id');
openSetParentModal(personId);
});
// 绑定删除按钮事件
$('.remove-btn').click(function() {
const personId = $(this).closest('.person-item').data('id');
removePerson(personId);
});
}
// 渲染关系树
function renderRelationshipTree() {
const $tree = $('#relationshipTree');
$tree.empty();
// 获取顶级人员
const topLevelPeople = people.filter(p => p.parentId === null);
if (topLevelPeople.length === 0) {
$tree.html('<div class="alert alert-info">暂无人员关系数据</div>');
return;
}
const $ul = $('<ul></ul>');
topLevelPeople.forEach(person => {
const $li = createTreeNode(person);
$ul.append($li);
});
$tree.append($ul);
}
// 创建树节点
function createTreeNode(person) {
const $li = $('<li></li>');
const $node = $(`<div class="tree-node">${person.name} (${person.title})</div>`);
$li.append($node);
// 查找子节点
const children = people.filter(p => p.parentId === person.id);
if (children.length > 0) {
const $childUl = $('<ul></ul>');
children.forEach(child => {
const $childLi = createTreeNode(child);
$childUl.append($childLi);
});
$li.append($childUl);
}
return $li;
}
// 打开设置父级模态框
function openSetParentModal(personId) {
const person = people.find(p => p.id === personId);
$('#currentPersonName').text(person.name);
// 填充可选父级下拉框
const $parentSelect = $('#parentSelect');
$parentSelect.empty();
$parentSelect.append('<option value="">无父级(顶级人员)</option>');
// 获取可以作为父级的人员(不能是自己或自己的子孙)
const descendants = getDescendants(personId);
const availableParents = people.filter(p => p.id !== personId && !descendants.includes(p.id));
availableParents.forEach(p => {
const selected = p.id === person.parentId ? 'selected' : '';
$parentSelect.append(`<option value="${p.id}" ${selected}>${p.name} (${p.title})</option>`);
});
// 检查是否有子节点
const hasChildren = people.some(p => p.parentId === personId);
const $warning = $('#warningMessage');
const $warningText = $('#warningText');
if (hasChildren) {
$warning.show();
$warningText.text('注意:此人有下属,如果设置为他人的子级,其下属将自动上移一级');
} else {
$warning.hide();
}
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('setRelationshipModal'));
modal.show();
// 保存按钮事件
$('#saveRelationship').off('click').on('click', function() {
const newParentId = $('#parentSelect').val() ? parseInt($('#parentSelect').val()) : null;
// 如果选择的新父级已经是某人的子级,且该人有子级,则不允许设置
if (newParentId !== null) {
const newParent = people.find(p => p.id === newParentId);
if (newParent.parentId !== null && people.some(p => p.parentId === newParentId)) {
showToast('错误', '不能将人员设置为已有子级的人的子级', 'danger');
return;
}
}
// 如果该人有子级,需要处理子级
if (hasChildren) {
const children = people.filter(p => p.parentId === personId);
const oldParentId = person.parentId;
// 将子级上移一级
children.forEach(child => {
child.parentId = oldParentId;
});
}
// 设置新的父级
person.parentId = newParentId;
// 更新视图
renderPersonList();
renderRelationshipTree();
// 关闭模态框
modal.hide();
showToast('成功', '已成功设置父子关系', 'success');
});
}
// 获取某人的所有子孙ID
function getDescendants(personId) {
const descendants = [];
function collectDescendants(id) {
const children = people.filter(p => p.parentId === id);
children.forEach(child => {
descendants.push(child.id);
collectDescendants(child.id);
});
}
collectDescendants(personId);
return descendants;
}
// 删除人员
function removePerson(personId) {
if (confirm('确定要删除此人员吗?其下属将自动上移一级')) {
const person = people.find(p => p.id === personId);
const children = people.filter(p => p.parentId === personId);
// 将子级上移一级
children.forEach(child => {
child.parentId = person.parentId;
});
// 删除人员
people = people.filter(p => p.id !== personId);
// 更新视图
renderPersonList();
renderRelationshipTree();
showToast('成功', '已成功删除人员', 'success');
}
}
// 显示提示消息
function showToast(title, message, type) {
const $toast = $(`
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header bg-${type} text-white">
<strong class="me-auto">${title}</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
${message}
</div>
</div>
`);
$('.toast-container').append($toast);
const toast = new bootstrap.Toast($toast[0], { delay: 3000 });
toast.show();
// 自动移除
$toast.on('hidden.bs.toast', function() {
$(this).remove();
});
}
// 添加人员按钮事件
$('#addPersonBtn').click(function() {
$('#newPersonName').val('');
$('#newPersonTitle').val('');
const modal = new bootstrap.Modal(document.getElementById('addPersonModal'));
modal.show();
// 保存按钮事件
$('#saveNewPerson').off('click').on('click', function() {
const name = $('#newPersonName').val().trim();
const title = $('#newPersonTitle').val().trim();
if (!name) {
alert('请输入姓名');
return;
}
// 生成新ID
const newId = Math.max(...people.map(p => p.id)) + 1;
// 添加新人员
people.push({
id: newId,
name: name,
title: title || '未设置',
parentId: null
});
// 更新视图
renderPersonList();
renderRelationshipTree();
// 关闭模态框
modal.hide();
showToast('成功', '已成功添加人员', 'success');
});
});
// 搜索功能
$('#searchPerson').on('input', function() {
const searchText = $(this).val().toLowerCase();
$('.person-item').each(function() {
const personId = $(this).data('id');
const person = people.find(p => p.id === personId);
const nameMatch = person.name.toLowerCase().includes(searchText);
const titleMatch = person.title.toLowerCase().includes(searchText);
if (nameMatch || titleMatch || searchText === '') {
$(this).show();
} else {
$(this).hide();
}
});
});
});
</script>
</body>
</html>