Files
nonye/platform/src/components/pople5.vue

1362 lines
36 KiB
Vue
Raw Normal View History

2025-07-17 23:13:04 +08:00
<template>
<div class="app-container">
<div class="personnel-pro">
<!-- 顶部统计区域 -->
<div class="top-box">
<div class="top">
<svg t="1748235916990" class="icon01" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12494" width="40" height="50">
<path d="M484 98.2c-100.8 0-182.4 81.7-182.4 182.4C301.6 381.4 383.3 463 484 463c100.8 0 182.4-81.7 182.4-182.4 0.1-100.8-81.6-182.4-182.4-182.4z m0 310.2c-70.6 0-127.8-57.2-127.8-127.8S413.4 152.8 484 152.8 611.8 210 611.8 280.6c0.1 70.6-57.2 127.8-127.8 127.8z" fill="#739FCB" p-id="12495" />
<path d="M479.6 550.8C269.7 550.8 99.5 719.6 98 928.3H861.3c-1.5-208.7-171.7-377.5-381.7-377.5zM162.5 873.3c27.4-153.2 161.7-269.6 323.4-269.6s296 116.3 323.4 269.6H162.5z" fill="#739FCB" p-id="12496" />
</svg>
<h1>人员管理系统</h1>
</div>
<div class="divider"></div>
<div class="stats-container">
<div class="stat-card">
<div class="stat-header">
<h2>{{ totalUsers }}</h2>
<span class="change-percentage"> 25%</span>
</div>
<p>总人数</p>
</div>
<div class="stat-card" v-if="isAdmin">
<div class="stat-header">
<h2>{{ adminCount }} / {{ supervisorCount + operatorCount }}</h2>
<span class="change-percentage01">
{{ adminCount }} 管理员 {{ supervisorCount }} 主管 {{ operatorCount }} 操作员
</span>
</div>
<p>管理人员</p>
</div>
<div class="stat-card" v-if="isAdmin || isSupervisor">
<div class="stat-header">
<h2>{{ onDutyCount }} / {{ totalUsers }}</h2>
<span class="change-percentage01">早晚执勤</span>
</div>
<p>值班人数</p>
</div>
</div>
</div>
<!-- 人员管理和操作日志区域 -->
<div class="management-and-logs">
<!-- 人员管理表格 -->
<div class="personnel-management">
<div class="management-header">
<h2>人员管理</h2>
<div class="management-controls" v-if="isAdmin">
<select v-model="filterPermission" class="filter-select">
<option value="all">全部权限</option>
<option value="Admin">管理员</option>
<option value="Supervisor">主管</option>
<option value="Operator">操作员</option>
</select>
<button @click="showAddModal = true" class="add-button" v-if="isAdmin">
+ 添加人员
</button>
</div>
</div>
<table class="personnel-table">
<thead>
<tr>
<th>权限级别</th>
<th>用户名</th>
<th>入职日期</th>
<th>关联设备</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="user in filteredUsers" :key="user.username" :class="{'table-row-hover': isHovered(user.username)}">
<td>
<div
:class="[
'permission-badge',
user.permissionLevel === 'Admin' ? 'admin-bg' :
user.permissionLevel === 'Supervisor' ? 'supervisor-bg' :
user.permissionLevel === 'Operator' ? 'operator-bg' :
'bg-gray-100 text-gray-600'
]"
>
{{ translatePermission(user.permissionLevel) }}
</div>
</td>
<td>{{ user.username }}</td>
<td>{{ user.hire_date || '未设置' }}</td>
<td>
<div class="device-container">
<svg t="1748246060883" class="icon" style="width: 20px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14446" width="20" height="20">
<path d="M815.43 40H200.06C91.48 40 3.16 128.33 3.16 236.9v396.37c0 108.57 88.32 196.9 196.9 196.9h268.49v76.27H269.77v78.41h475.96v-78.41H546.96v-76.27h268.47c108.58 0 196.9-88.33 196.9-196.9V236.9c0-108.57-88.32-196.9-196.9-196.9z m118.49 593.27c0 65.33-53.14 118.49-118.49 118.49H200.06c-65.34 0-118.49-53.15-118.49-118.49V236.9c0-65.33 53.14-118.49 118.49-118.49h615.37c65.34 0 118.49 53.15 118.49 118.49v396.37z" fill="#333333" p-id="14447" />
<path d="M247.69 286.38h520.12v78.41H247.69zM247.69 505.38h355.86v78.41H247.69z" fill="#C6C6C6" p-id="14448" />
</svg>
<span>{{ user.linked_devices || 0 }}</span>
</div>
</td>
<td>
<button
@click="editUser(user)"
class="edit-button"
v-if="canEditUser(user)"
:title="canEditUser(user) ? '编辑用户' : '无编辑权限'"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil" viewBox="0 0 16 16">
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-6.293 3.353 4.293-4.293 1.293 1.293-4.293 4.293-1.293-1.293z" />
</svg>
</button>
<button
@click="deleteUser(user.username)"
class="delete-button"
v-if="canDeleteUser(user)"
:title="canDeleteUser(user) ? '删除用户' : '无删除权限'"
@mouseenter="showDeleteConfirm = user.username"
@mouseleave="showDeleteConfirm = ''"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3.5 0a.5.5 0 0 1-1 0v6a.5.5 0 0 1 1 0V6Z" />
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1ZM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118ZM2.5 3h11V2h-11v1Z" />
</svg>
<!-- 删除确认提示 -->
<div v-if="showDeleteConfirm === user.username" class="delete-tooltip">
确定要删除用户 {{ user.username }} 此操作不可撤销
</div>
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 操作日志区域 -->
<div class="operation-logs" v-if="isAdmin || isSupervisor">
<div class="logs-header">
<h2>操作日志</h2>
<div class="log-filters">
<select v-model="logFilterType" class="filter-select">
<option value="all">所有类型</option>
<option value="USER_CREATE">用户创建</option>
<option value="USER_UPDATE">用户更新</option>
<option value="USER_DELETE">用户删除</option>
<option value="DEVICE_MANAGE">设备管理</option>
<option value="DATA_VIEW">数据查看</option>
<option value="PERMISSION_CHANGE">权限变更</option>
<option value="SYSTEM_OPERATION">系统操作</option>
</select>
</div>
</div>
<ul class="log-list">
<li v-for="log in filteredLogs" :key="log.id" :class="getLogClass(log.type)">
<div v-if="isAdmin || log.user === currentUser.username">
<div class="log-header">
<span class="log-type">{{ LOG_TYPES[log.type] || log.type }}</span>
<span class="log-time">{{ formatTime(log.timestamp) }}</span>
</div>
<div class="log-message">{{ log.message }}</div>
<div class="log-user">操作人: {{ log.user || '系统' }}</div>
<div class="log-details" v-if="log.details">{{ log.details }}</div>
</div>
</li>
<li v-if="logs.length === 0 && isLogsLoaded" class="empty-log">
<div class="empty-message">暂无操作日志记录</div>
</li>
</ul>
</div>
</div>
<!-- 添加人员模态框 -->
<div v-if="showAddModal" class="modal-overlay">
<div class="modal-content">
<h3>添加新人员</h3>
<div class="form-group">
<label>用户名 <span class="required">*</span></label>
<input type="text" v-model="newUser.username" placeholder="输入用户名" required>
<div v-if="usernameError" class="error-message">{{ usernameError }}</div>
</div>
<div class="form-group">
<label>密码 <span class="required">*</span></label>
<input type="password" v-model="newUser.password" placeholder="输入密码" required>
<div v-if="passwordError" class="error-message">{{ passwordError }}</div>
</div>
<div class="form-group">
<label>邮箱</label>
<input type="email" v-model="newUser.email" placeholder="输入邮箱">
<div v-if="emailError" class="error-message">{{ emailError }}</div>
</div>
<div class="form-group">
<label>权限级别 <span class="required">*</span></label>
<select
v-model="newUser.permissionLevel"
@change="formatAndValidatePermission"
>
<option value="Admin" v-if="currentUser.username === 'root'">管理员 (Admin)</option>
<option value="Supervisor">主管 (Supervisor)</option>
<option value="Operator">操作员 (Operator)</option>
</select>
<div v-if="permissionError" class="error-message">{{ permissionError }}</div>
</div>
<div class="form-group">
<label>联系电话</label>
<input type="tel" v-model="newUser.phone" placeholder="输入联系电话">
<div v-if="phoneError" class="error-message">{{ phoneError }}</div>
</div>
<div class="form-group">
<label>入职日期 <span class="required">*</span></label>
<input
type="date"
v-model="newUser.hire_date"
required
:min="getMinimumDate()"
>
</div>
<div class="form-group">
<label>关联设备</label>
<input type="number" v-model="newUser.linked_devices" min="0" max="5">
</div>
<div class="modal-actions">
<button @click="confirmCancel" class="cancel-button">取消</button>
<button @click="handleAddUser" class="create-button">创建</button>
</div>
</div>
</div>
<!-- 修改人员模态框 -->
<div v-if="showEditModal" class="modal-overlay">
<div class="modal-content">
<h3>修改人员信息</h3>
<div class="form-group">
<label>权限级别</label>
<select
v-model="editingUser.permissionLevel"
:disabled="!canEditPermission"
@change="formatAndValidatePermission"
>
<option value="Admin" v-if="currentUser.username === 'root' && editingUser.username !== 'root'">管理员 (Admin)</option>
<option value="Supervisor" v-if="canEditPermission">主管 (Supervisor)</option>
<option value="Operator" v-if="canEditPermission">操作员 (Operator)</option>
</select>
<div v-if="permissionError" class="error-message">{{ permissionError }}</div>
</div>
<div class="form-group">
<label>用户名</label>
<input type="text" v-model="editingUser.username" disabled>
</div>
<div class="form-group">
<label>入职日期</label>
<input type="date" v-model="editingUser.hire_date">
</div>
<div class="form-group">
<label>关联设备</label>
<input type="number" v-model="editingUser.linked_devices" min="0" max="5">
</div>
<div class="form-group">
<label>邮箱</label>
<input type="email" v-model="editingUser.email" placeholder="输入邮箱">
</div>
<div class="form-group">
<label>联系电话</label>
<input type="tel" v-model="editingUser.phone" placeholder="输入联系电话">
</div>
<div class="form-group" v-if="canEditStatus">
<label>状态</label>
<select v-model="editingUser.status">
<option value="Active">活跃</option>
<option value="Inactive">未活跃</option>
</select>
</div>
<div class="form-group">
<label>修改密码</label>
<input type="password" v-model="editingUser.password" placeholder="留空表示不修改密码">
</div>
<div class="modal-actions">
<button @click="showEditModal = false" class="cancel-button">取消</button>
<button @click="handleEditUser" class="create-button">保存</button>
</div>
</div>
</div>
</div>
<div>
<!-- 你的页面内容 -->
<askai />
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, watch } from 'vue';
import axios from 'axios';
import Askai from "../components1/askai.vue";
// 权限枚举
const PERMISSION_LEVEL = {
Admin: 'Admin',
Supervisor: 'Supervisor',
Operator: 'Operator'
};
// 有效权限数组
const validPermissions = Object.values(PERMISSION_LEVEL);
// 日志类型映射
const LOG_TYPES = {
'USER_CREATE': '用户创建',
'USER_UPDATE': '用户更新',
'USER_DELETE': '用户删除',
'DEVICE_MANAGE': '设备管理',
'DATA_VIEW': '数据查看',
'PERMISSION_CHANGE': '权限变更',
'SYSTEM_OPERATION': '系统操作'
};
// 日志类型样式映射
const LOG_TYPE_CLASSES = {
'USER_CREATE': 'log-success',
'USER_UPDATE': 'log-info',
'USER_DELETE': 'log-warning',
'DEVICE_MANAGE': 'log-info',
'DATA_VIEW': 'log-info',
'PERMISSION_CHANGE': 'log-warning',
'SYSTEM_OPERATION': 'log-success'
};
// 从localStorage获取用户信息
const userInfo = JSON.parse(localStorage.getItem('user') || '{}');
// 当前用户
const currentUser = ref({
username: userInfo.username || '',
permissionLevel: userInfo.is_admin ? 'Admin' : 'Supervisor' // 根据实际情况调整
});
// 计算属性判断用户角色
const isAdmin = computed(() => currentUser.value.permissionLevel === 'Admin');
const isSupervisor = computed(() => currentUser.value.permissionLevel === 'Supervisor');
const isOperator = computed(() => currentUser.value.permissionLevel === 'Operator');
// 数据状态
const users = ref([]);
const logs = ref([]);
const filterPermission = ref('all');
const logFilterType = ref('all');
const showAddModal = ref(false);
const showEditModal = ref(false);
const newUser = ref({
username: '',
password: '',
permissionLevel: 'Operator',
email: '',
phone: '',
hire_date: new Date().toISOString().split('T')[0],
linked_devices: 0
});
const editingUser = ref({});
const permissionError = ref('');
const usernameError = ref('');
const passwordError = ref('');
const emailError = ref('');
const phoneError = ref('');
const showDeleteConfirm = ref('');
const isLogsLoaded = ref(false);
2025-07-18 18:49:59 +08:00
const apiUrl = import.meta.env.VITE_API_BASE_URL // 动态获取API基础URL
2025-07-17 23:13:04 +08:00
// 统一错误处理函数
const handleError = (error, message = '操作失败') => {
console.error(message, error);
const errorMessage = error?.response?.data?.message || message;
alert(errorMessage);
};
// 权限判断方法
const canEditUser = (user) => {
// 只有管理员可以编辑用户
if (!isAdmin.value) return false;
// 禁止修改root用户
if (user.username === 'root') return false;
return true;
};
const canDeleteUser = (user) => {
// 只有管理员可以删除用户
if (!isAdmin.value) return false;
// 禁止删除root用户
if (user.username === 'root') return false;
return true;
};
// 过滤用户列表
const filteredUsers = computed(() => {
if (isOperator.value) {
return users.value.filter(u => u.username === currentUser.value.username);
}
if (filterPermission.value === 'all') {
return users.value;
}
return users.value.filter(user => user.permissionLevel === filterPermission.value);
});
// 过滤日志
const filteredLogs = computed(() => {
if (!isAdmin.value && !isSupervisor.value) return [];
return logs.value.filter(log =>
logFilterType.value === 'all' || log.type === logFilterType.value
);
});
// 统计信息
const totalUsers = computed(() => users.value.length);
const adminCount = computed(() =>
users.value.filter(u => u.permissionLevel === 'Admin').length
);
const supervisorCount = computed(() =>
users.value.filter(u => u.permissionLevel === 'Supervisor').length
);
const operatorCount = computed(() =>
users.value.filter(u => u.permissionLevel === 'Operator').length
);
const onDutyCount = computed(() =>
users.value.filter(u => u.status === 'Active').length
);
// 权限翻译函数
const translatePermission = (permission) => {
return {
'Admin': '管理员',
'Supervisor': '主管',
'Operator': '操作员'
}[permission] || '未知';
};
// 获取日志类型样式类
const getLogClass = (type) => LOG_TYPE_CLASSES[type] || 'log-info';
// 获取最小日期(今天之前)
const getMinimumDate = () => {
const today = new Date();
return today.toISOString().split('T')[0];
};
// 格式化时间
const formatTime = (timestamp) => {
if (!timestamp) return '';
const date = new Date(timestamp);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
};
// 格式化并验证权限级别
const formatAndValidatePermission = () => {
// 强制转换为首字母大写
newUser.value.permissionLevel = newUser.value.permissionLevel.charAt(0).toUpperCase() +
newUser.value.permissionLevel.slice(1).toLowerCase();
editingUser.value.permissionLevel = editingUser.value.permissionLevel.charAt(0).toUpperCase() +
editingUser.value.permissionLevel.slice(1).toLowerCase();
// 验证权限级别
if (
!validPermissions.includes(newUser.value.permissionLevel) &&
!validPermissions.includes(editingUser.value.permissionLevel)
) {
permissionError.value = '权限级别必须为Admin、Supervisor或Operator';
newUser.value.permissionLevel = 'Operator';
editingUser.value.permissionLevel = 'Operator';
} else {
permissionError.value = '';
}
// 权限级别为Admin时的额外验证
if (
(newUser.value.permissionLevel === 'Admin' && currentUser.value.username !== 'root') ||
(editingUser.value.permissionLevel === 'Admin' && currentUser.value.username !== 'root')
) {
permissionError.value = '只有root用户可以设置管理员权限';
newUser.value.permissionLevel = 'Supervisor';
editingUser.value.permissionLevel = 'Supervisor';
}
};
// 验证用户名
const validateUsername = (username) => {
if (!username.trim()) {
usernameError.value = '用户名不能为空';
return false;
}
if (username.length < 3 || username.length > 20) {
usernameError.value = '用户名长度需在3-20个字符之间';
return false;
}
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
usernameError.value = '用户名只能包含字母、数字和下划线';
return false;
}
usernameError.value = '';
return true;
};
// 验证密码
const validatePassword = (password) => {
if (!password) {
passwordError.value = '密码不能为空';
return false;
}
if (password.length < 6) {
passwordError.value = '密码长度不能少于6位';
return false;
}
passwordError.value = '';
return true;
};
// 验证邮箱
const validateEmail = (email) => {
if (!email) {
emailError.value = '';
return true;
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
emailError.value = '请输入有效的邮箱地址';
return false;
}
emailError.value = '';
return true;
};
// 验证电话
const validatePhone = (phone) => {
if (!phone) {
phoneError.value = '';
return true;
}
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(phone)) {
phoneError.value = '请输入有效的手机号码';
return false;
}
phoneError.value = '';
return true;
};
onMounted(() => {
fetchUsers();
if (isAdmin.value || isSupervisor.value) {
fetchLogs();
}
});
// 获取用户数据
const fetchUsers = async () => {
try {
2025-07-18 18:49:59 +08:00
const response = await axios.get(`${apiUrl}/personnel/users`, {
2025-07-17 23:13:04 +08:00
headers: {
Authorization: `Bearer ${userInfo.token}`
}
});
users.value = response.data.data
.filter(user => user && user.username)
.map(user => ({
...user,
permissionLevel: validPermissions.includes(user.permission_level)
? user.permission_level
: 'Operator',
hire_date: user.hire_date || '',
status: user.status || 'Active',
linked_devices: user.linked_devices || 0,
email: user.email || '',
phone: user.phone || ''
}));
// 确保root为管理员
users.value = users.value.map(user =>
user.username === 'root' ? { ...user, permissionLevel: 'Admin' } : user
);
} catch (error) {
handleError(error, '获取用户数据失败');
}
};
// 获取日志数据
const fetchLogs = async () => {
try {
2025-07-18 18:49:59 +08:00
const response = await axios.get(`${apiUrl}/personnel/logs`, {
2025-07-17 23:13:04 +08:00
headers: {
Authorization: `Bearer ${userInfo.token}`
}
});
logs.value = response.data.data || [];
isLogsLoaded.value = true;
} catch (error) {
handleError(error, '获取日志数据失败');
isLogsLoaded.value = true;
}
};
// 添加用户
const handleAddUser = async () => {
// 验证必填字段
if (!validateUsername(newUser.value.username)) return;
if (!validatePassword(newUser.value.password)) return;
if (!newUser.value.hire_date) {
alert('请选择入职日期');
return;
}
if (!validateEmail(newUser.value.email)) return;
if (!validatePhone(newUser.value.phone)) return;
// 再次验证权限级别(防止绕过前端验证)
newUser.value.permissionLevel = newUser.value.permissionLevel.charAt(0).toUpperCase() +
newUser.value.permissionLevel.slice(1).toLowerCase();
if (!validPermissions.includes(newUser.value.permissionLevel)) {
alert('权限级别必须为Admin、Supervisor或Operator');
newUser.value.permissionLevel = 'Operator';
return;
}
// 权限级别为Admin时的额外验证
if (newUser.value.permissionLevel === 'Admin' && currentUser.value.username !== 'root') {
alert('只有root用户可以创建管理员');
newUser.value.permissionLevel = 'Supervisor';
return;
}
// 检查用户名是否已存在
if (users.value.some(user => user.username === newUser.value.username)) {
usernameError.value = '用户名已存在';
return;
}
// 构建请求数据
const requestData = {
username: newUser.value.username,
password: newUser.value.password,
permissionLevel: newUser.value.permissionLevel,
hire_date: newUser.value.hire_date,
email: newUser.value.email,
phone: newUser.value.phone,
linked_devices: newUser.value.linked_devices,
status: 'Active',
createdBy: currentUser.value.username
};
try {
2025-07-18 18:49:59 +08:00
const response = await axios.post(`${apiUrl}/personnel/users`, requestData, {
2025-07-17 23:13:04 +08:00
headers: {
Authorization: `Bearer ${userInfo.token}`
}
});
if (response.status === 201) {
fetchUsers();
showAddModal.value = false;
alert('用户创建成功');
resetNewUser();
} else {
handleError(response.data, '创建用户失败');
}
} catch (error) {
handleError(error, '创建用户失败');
}
};
// 编辑用户
const editUser = (user) => {
if (!canEditUser(user)) {
alert('只有管理员可以编辑用户');
return;
}
editingUser.value = {
username: user.username,
permissionLevel: user.permissionLevel,
hire_date: user.hire_date || new Date().toISOString().split('T')[0],
linked_devices: user.linked_devices || 0,
status: user.status || 'Active',
email: user.email || '',
phone: user.phone || '',
password: '' // 密码字段留空,不修改密码
};
showEditModal.value = true;
};
// 保存编辑
const handleEditUser = async () => {
if (!editingUser.value.hire_date) {
alert('请选择入职日期');
return;
}
// 验证邮箱和电话
if (!validateEmail(editingUser.value.email)) return;
if (!validatePhone(editingUser.value.phone)) return;
// 再次验证权限级别(防止绕过前端验证)
editingUser.value.permissionLevel = editingUser.value.permissionLevel.charAt(0).toUpperCase() +
editingUser.value.permissionLevel.slice(1).toLowerCase();
if (!validPermissions.includes(editingUser.value.permissionLevel)) {
alert('权限级别必须为Admin、Supervisor或Operator');
editingUser.value.permissionLevel = 'Operator';
return;
}
// 权限级别为Admin时的额外验证
if (
editingUser.value.permissionLevel === 'Admin' &&
currentUser.value.username !== 'root' &&
editingUser.value.username !== 'root'
) {
alert('只有root用户可以设置管理员权限');
editingUser.value.permissionLevel = 'Supervisor';
return;
}
const payload = {
permissionLevel: editingUser.value.permissionLevel,
hire_date: editingUser.value.hire_date,
linked_devices: editingUser.value.linked_devices,
status: editingUser.value.status,
email: editingUser.value.email,
phone: editingUser.value.phone
};
// 如果有输入密码,则更新密码
if (editingUser.value.password) {
payload.password = editingUser.value.password;
}
try {
const response = await axios.put(
2025-07-18 18:49:59 +08:00
`${apiUrl}/personnel/users/${editingUser.value.username}`,
2025-07-17 23:13:04 +08:00
payload,
{
headers: {
Authorization: `Bearer ${userInfo.token}`
}
}
);
if (response.status === 200) {
fetchUsers();
showEditModal.value = false;
alert('用户信息更新成功');
} else {
handleError(response.data, '更新用户失败');
}
} catch (error) {
handleError(error, '更新用户失败');
}
};
// 删除用户
const deleteUser = async (username) => {
if (username === 'root') {
alert('禁止删除root用户');
return;
}
const userToDelete = users.value.find(u => u.username === username);
if (!userToDelete) {
alert('未找到该用户');
return;
}
if (!canDeleteUser(userToDelete)) {
alert('只有管理员可以删除用户');
return;
}
if (!confirm(`确定要删除用户 ${username} 吗?此操作不可撤销。`)) return;
try {
2025-07-18 18:49:59 +08:00
const response = await axios.delete(`${apiUrl}/personnel/users/${username}`, {
2025-07-17 23:13:04 +08:00
headers: {
Authorization: `Bearer ${userInfo.token}`
}
});
if (response.status === 200) {
fetchUsers();
alert('用户删除成功');
} else {
handleError(response.data, '删除用户失败');
}
} catch (error) {
handleError(error, '删除用户失败');
}
};
// 重置表单
const resetNewUser = () => {
newUser.value = {
username: '',
password: '',
permissionLevel: 'Operator',
email: '',
phone: '',
hire_date: new Date().toISOString().split('T')[0],
linked_devices: 0
};
usernameError.value = '';
passwordError.value = '';
emailError.value = '';
phoneError.value = '';
permissionError.value = '';
};
// 取消添加
const confirmCancel = () => {
showAddModal.value = false;
resetNewUser();
};
// 行悬停状态
const isHovered = (username) => {
let hovered = ref(false);
return hovered.value;
};
</script>
<style scoped>
/* 全局样式 */
.app-container {
height: 100vh;
overflow-y: scroll;
scrollbar-width: none; /* Firefox */
background-color: #fbfbfb;
}
.app-container::-webkit-scrollbar {
display: none; /* Chrome, Safari, Edge */
}
.personnel-pro {
font-family: 'Microsoft YaHei', Arial, sans-serif;
color: #333;
background: #fbfbfb;
}
/* 顶部区域样式 */
.top-box {
height: 155px;
background-color: #FFFFFF;
padding: 20px;
}
.top {
display: flex;
position: relative;
top: -20px;
height: 10px;
}
.icon01 {
width: 40px;
height: 50px;
}
h1 {
color: #2c3e50;
margin-bottom: 18px;
font-size: 23px;
}
.divider {
height: 1px;
background: #eaeaea;
margin: 27px 0;
margin-bottom: 40px;
}
.stats-container {
display: flex;
justify-content: space-between;
width: 100%;
gap: 20px;
margin-bottom: 20px;
}
.stat-card {
flex: 1;
background-color: #FFFFFF;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
text-align: center;
height: 60px;
position: relative;
top: -27px;
}
.stat-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.stat-card h2 {
margin: 0;
font-size: 24px;
color: #2B6CB0;
}
.change-percentage {
font-size: 14px;
color: green;
}
.change-percentage01 {
font-size: 14px;
color: #7E8390;
}
.stat-card p {
margin: 5px 0;
color: #6B7280;
}
/* 管理和日志区域样式 */
.management-and-logs {
display: flex;
margin-top: 20px;
}
.personnel-management {
width: 62%;
background-color: #FFFFFF;
padding: 20px;
margin-left: 20px;
border-radius: 8px;
height: 500px;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
.operation-logs {
flex-grow: 1;
background-color: #FFFFFF;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
height: calc(100vh - 290px);
overflow-y: auto;
margin-left: 20px;
}
.operation-logs h2 {
margin-top: 0;
color: #2c3e50;
font-size: 18px;
margin-bottom: 15px;
}
.logs-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.log-filters {
display: flex;
gap: 10px;
}
.filter-select {
padding: 6px 12px;
border: 1px solid #d1d5db;
border-radius: 4px;
background-color: #ffffff;
font-size: 14px;
}
/* 表格样式 */
.personnel-table {
width: 100%;
border-collapse: collapse;
}
.personnel-table th,
.personnel-table td {
padding: 15px 12px;
border-bottom: 1px solid #eaeaea;
font-size: 14px;
}
.personnel-table th {
text-align: left;
background-color: #f8f9fa;
font-weight: 500;
color: #495057;
position: sticky;
top: 0;
z-index: 1;
}
.personnel-table tbody tr {
transition: background-color 0.2s;
}
.personnel-table tbody tr:hover {
background-color: #f9f9f9;
}
.table-row-hover {
background-color: #f0f8ff;
}
.device-container {
display: flex;
align-items: center;
gap: 5px;
}
.management-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.management-header h2 {
margin: 0;
color: #2c3e50;
font-size: 18px;
}
.management-controls {
display: flex;
gap: 15px;
}
.add-button {
padding: 8px 16px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
display: flex;
align-items: center;
gap: 5px;
}
.add-button:hover {
background-color: #45a049;
}
.edit-button,
.delete-button {
background: none;
border: none;
padding: 5px;
cursor: pointer;
transition: color 0.3s;
display: flex;
align-items: center;
gap: 2px;
}
.edit-button:hover {
color: #2196F3;
}
.delete-button:hover {
color: #F44336;
}
/* 日志样式 */
/* 操作日志区域样式 */
.operation-logs {
flex-grow: 1;
background-color: #FFFFFF;
padding: 20px;
margin-left: 20px;
border-radius: 8px;
margin-right: 20px;
}
.operation-logs h2 {
margin-top: 0;
color: #2c3e50;
font-size: 18px;
}
.log-list {
list-style: none;
padding: 0;
margin: 0;
position: relative;
}
/* 时间线样式 */
.log-list::before {
content: '';
position: absolute;
left: 10px;
top: 0;
bottom: 0;
width: 2px;
background: #e0e0e0;
}
.log-list li {
margin-bottom: 15px;
padding-left: 30px;
position: relative;
}
/* 日志类型标记点 */
.log-list li::before {
content: '';
position: absolute;
left: 3px;
top: 5px;
width: 14px;
height: 14px;
border-radius: 50%;
background: #e0e0e0;
z-index: 1;
}
.log-list li.log-success::before {
background: #4CAF50;
}
.log-list li.log-warning::before {
background: #FFC107;
}
.log-list li.log-error::before {
background: #F44336;
}
.log-header {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
}
.log-type {
font-weight: 500;
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
}
.log-type-success {
background: #E8F5E9;
color: #4CAF50;
}
.log-type-warning {
background: #FFF3E0;
color: #FF9800;
}
.log-type-error {
background: #FFEBEE;
color: #F44336;
}
.log-time {
color: #7E8390;
font-size: 12px;
}
.log-message {
font-weight: 500;
margin-bottom: 3px;
}
.log-details {
color: #7E8390;
font-size: 14px;
}
/* 模态框样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background-color: #ffffff;
padding: 25px;
border-radius: 8px;
width: 400px;
max-width: 90%;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
animation: modalFadeIn 0.3s ease;
}
@keyframes modalFadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal-content h3 {
margin-top: 0;
color: #2c3e50;
font-size: 18px;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eaeaea;
}
.form-group {
margin-bottom: 18px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.required {
color: #F44336;
margin-left: 5px;
}
.form-group input,
.form-group select {
width: 100%;
padding: 10px;
border: 1px solid #d1d5db;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.3s;
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: #4a90e2;
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
.cancel-button,
.create-button {
padding: 10px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
font-size: 14px;
}
.cancel-button {
background-color: #f5f5f5;
color: #333;
}
.cancel-button:hover {
background-color: #e5e5e5;
}
.create-button {
background-color: #4CAF50;
color: white;
}
.create-button:hover {
background-color: #45a049;
}
/* 权限徽章样式 */
.permission-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
min-width: 60px;
text-align: center;
border: 1px solid transparent;
transition: all 0.2s ease;
}
.admin-bg {
background-color: #FFE0E0;
color: #FF4444;
border-color: #FFCCCC;
}
.supervisor-bg {
background-color: #E0F0FF;
color: #2176FF;
border-color: #B3D4FF;
}
.operator-bg {
background-color: #E0FFE5;
color: #00C851;
border-color: #B3FFC6;
}
.bg-gray-100 {
background-color: #F5F5F5;
color: #616161;
border-color: #D0D0D0;
}
.error-message {
font-size: 12px;
margin-top: 5px;
color: #F44336;
}
/* 删除提示样式 */
.delete-tooltip {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: white;
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
z-index: 10;
}
</style>