diff --git a/2_3banben/routes/api.py b/2_3banben/routes/api.py index de38abc..c916150 100644 --- a/2_3banben/routes/api.py +++ b/2_3banben/routes/api.py @@ -9,7 +9,7 @@ from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identi from extensions import db from models import Device, DeviceHistory, MaintenanceLog, User, UserDevicePermission -# 尝试导入爬虫模块 (如果没有则跳过,防止报错) +# 尝试导入爬虫模块 try: from services.core import execute_monitor_task except ImportError: @@ -19,35 +19,29 @@ api_bp = Blueprint('api', __name__, url_prefix='/api') # ======================= -# 🔧 辅助函数 (权限核心) +# 🔧 辅助函数 # ======================= def is_admin(user_id): - """ - 判断是否为超级管理员 (Root权限) - 逻辑: - 1. ID 为 '0' (硬编码后门) -> 通过 - 2. 数据库中角色为 'admin' -> 通过 - """ + """判断是否为超级管理员 (Root权限)""" if str(user_id) == '0': return True if not user_id: return False try: - u = User.query.get(int(user_id)) + uid = int(user_id) + u = User.query.get(uid) return u and u.role == 'admin' except: return False def is_manager(user_id): - """ - 判断是否为管理者 (Admin OR Engineer) - 用于:修改地点、切换维修模式、写日志 - """ + """判断是否为管理者 (Admin OR Engineer)""" if is_admin(user_id): return True try: - u = User.query.get(int(user_id)) + uid = int(user_id) + u = User.query.get(uid) return u and u.role == 'engineer' except: return False @@ -76,7 +70,10 @@ def login(): # 1. 后门判定 if username == 'admin' and password == 'licahk': - token = create_access_token(identity='0', additional_claims={'role': 'admin'}) + token = create_access_token( + identity='0', + additional_claims={'role': 'admin'} + ) return jsonify({ 'code': 200, 'message': 'Root后门登录', 'token': token, 'role': 'admin', 'user_id': 0, 'username': 'admin' @@ -85,7 +82,10 @@ def login(): # 2. 正常查库登录 user = User.query.filter_by(username=username).first() if user and user.check_password(password): - token = create_access_token(identity=str(user.id), additional_claims={'role': user.role}) + token = create_access_token( + identity=str(user.id), + additional_claims={'role': user.role} + ) return jsonify({ 'code': 200, 'message': '登录成功', 'token': token, 'role': user.role, 'user_id': user.id, 'username': user.username @@ -102,15 +102,26 @@ def login(): def devices_overview(): try: user_id = get_jwt_identity() + target_devices = [] + + # Admin 看所有,其他人看分配 if is_admin(user_id): target_devices = Device.query.all() else: - perms = UserDevicePermission.query.filter_by(user_id=int(user_id)).all() - allowed_ids = [p.device_id for p in perms] - target_devices = Device.query.filter(Device.id.in_(allowed_ids)).all() if allowed_ids else [] + try: + uid_int = int(user_id) + user = User.query.get(uid_int) + if user: + perms = UserDevicePermission.query.filter_by(user_id=user.id).all() + allowed_ids = [p.device_id for p in perms] + if allowed_ids: + target_devices = Device.query.filter(Device.id.in_(allowed_ids)).all() + except ValueError: + return jsonify({'code': 401, 'message': '无效的用户ID格式'}), 401 return jsonify({'code': 200, 'data': [d.to_dict() for d in target_devices]}) except Exception as e: + print(f"Error: {e}") return jsonify({'code': 500, 'message': str(e)}) @@ -119,19 +130,31 @@ def devices_overview(): def device_data_by_date(): name = request.args.get('name') date_str = request.args.get('date') - if not name or not date_str: return jsonify({'code': 400}), 400 + + if not name or not date_str: + return jsonify({'code': 400, 'message': 'Missing params'}), 400 device = Device.query.filter_by(name=name).first() - if not device: return jsonify({'code': 404}), 404 + if not device: return jsonify({'code': 404, 'message': 'Device not found'}), 404 - hist = DeviceHistory.query.filter(DeviceHistory.device_id == device.id, DeviceHistory.data_time.like(f"{date_str}%")).order_by(desc(DeviceHistory.id)).first() - content = hist.json_data if hist else (device.json_data if device.latest_time and str(device.latest_time).startswith(date_str) else None) + content = None + hist = DeviceHistory.query.filter( + DeviceHistory.device_id == device.id, + DeviceHistory.data_time.like(f"{date_str}%") + ).order_by(desc(DeviceHistory.id)).first() + + if hist: + content = hist.json_data + elif device.latest_time and str(device.latest_time).startswith(date_str): + content = device.json_data if content: - if isinstance(content, str): - try: content = json.loads(content) - except: pass + try: + if isinstance(content, str): content = json.loads(content) + except: + pass return jsonify({'code': 200, 'name': device.name, 'source': device.source, 'content': content}) + return jsonify({'code': 404, 'message': '无数据'}), 404 @@ -142,14 +165,21 @@ def device_data_by_date(): @jwt_required() def admin_get_users(): if not is_admin(get_jwt_identity()): return jsonify({'code': 403}), 403 + + current_id_str = str(get_jwt_identity()) users = User.query.order_by(desc(User.created_at)).all() + result = [] for u in users: - if str(u.id) == str(get_jwt_identity()): continue + if str(u.id) == current_id_str: continue + perms = UserDevicePermission.query.filter_by(user_id=u.id).all() result.append({ - "id": u.id, "username": u.username, "role": u.role, - "created_at": u.created_at, "allowed_device_ids": [p.device_id for p in perms] + "id": u.id, + "username": u.username, + "role": u.role, + "created_at": u.created_at, + "allowed_device_ids": [p.device_id for p in perms] }) return jsonify({'code': 200, 'data': result}) @@ -158,11 +188,17 @@ def admin_get_users(): @jwt_required() def admin_create_user(): if not is_admin(get_jwt_identity()): return jsonify({'code': 403}), 403 + data = request.get_json() - if User.query.filter_by(username=data.get('username')).first(): + username = data.get('username') + password = data.get('password') + role = data.get('role', 'client') + + if User.query.filter_by(username=username).first(): return jsonify({'code': 400, 'msg': '用户名已存在'}), 400 - u = User(username=data.get('username'), role=data.get('role', 'client')) - u.set_password(data.get('password')) + + u = User(username=username, role=role) + u.set_password(password) db.session.add(u) db.session.commit() return jsonify({'code': 200, 'msg': '创建成功'}) @@ -171,15 +207,21 @@ def admin_create_user(): @api_bp.route('/admin/delete_user', methods=['POST']) @jwt_required() def admin_delete_user(): - curr = get_jwt_identity() - if not is_admin(curr): return jsonify({'code': 403}), 403 - uid = request.get_json().get('user_id') - if str(uid) == str(curr): return jsonify({'code': 400, 'msg': '无法删除自己'}), 400 - user = User.query.get(uid) - if user: - UserDevicePermission.query.filter_by(user_id=user.id).delete() - db.session.delete(user) - db.session.commit() + current_admin_id = get_jwt_identity() + if not is_admin(current_admin_id): return jsonify({'code': 403}), 403 + + user_id = request.get_json().get('user_id') + if str(user_id) == str(current_admin_id): + return jsonify({'code': 400, 'msg': '无法删除当前登录账号'}), 400 + + user = User.query.get(user_id) + if not user: + return jsonify({'code': 404, 'msg': '用户不存在'}), 404 + + UserDevicePermission.query.filter_by(user_id=user.id).delete() + db.session.delete(user) + db.session.commit() + return jsonify({'code': 200, 'msg': '删除成功'}) @@ -188,25 +230,22 @@ def admin_delete_user(): def admin_assign_devices(): if not is_admin(get_jwt_identity()): return jsonify({'code': 403}), 403 data = request.get_json() - UserDevicePermission.query.filter_by(user_id=data.get('user_id')).delete() + uid = data.get('user_id') + + UserDevicePermission.query.filter_by(user_id=uid).delete() for did in data.get('device_ids', []): - db.session.add(UserDevicePermission(user_id=data.get('user_id'), device_id=did)) + db.session.add(UserDevicePermission(user_id=uid, device_id=did)) db.session.commit() return jsonify({'code': 200, 'msg': '权限已保存'}) # ======================= -# 3. 日志与工具 (权限隔离) +# 3. 日志与工具 # ======================= @api_bp.route('/logs/list', methods=['GET']) @jwt_required() def get_logs(): - """ - 获取日志列表 - 权限逻辑更新: - - Admin: 可以看所有 - - Engineer/Client: 只能看自己名下设备的日志 (严格过滤) - """ + """获取日志列表,支持按权限过滤""" user_id = get_jwt_identity() keyword = request.args.get('keyword', '') start_date = request.args.get('start_date') @@ -214,12 +253,19 @@ def get_logs(): query = MaintenanceLog.query - # 🛡️ 权限隔离 + # 🛡️ 权限过滤 if not is_admin(user_id): - perms = UserDevicePermission.query.filter_by(user_id=int(user_id)).all() - if not perms: return jsonify({'code': 200, 'data': []}) - allowed_names = [d.name for d in Device.query.filter(Device.id.in_([p.device_id for p in perms])).all()] - query = query.filter(MaintenanceLog.device_name.in_(allowed_names)) + try: + perms = UserDevicePermission.query.filter_by(user_id=int(user_id)).all() + if not perms: + return jsonify({'code': 200, 'data': []}) + + allowed_ids = [p.device_id for p in perms] + allowed_devices = Device.query.filter(Device.id.in_(allowed_ids)).all() + allowed_names = [d.name for d in allowed_devices] + query = query.filter(MaintenanceLog.device_name.in_(allowed_names)) + except: + return jsonify({'code': 200, 'data': []}) if keyword: kw = f"%{keyword}%" @@ -233,7 +279,8 @@ def get_logs(): s = datetime.strptime(start_date, '%Y-%m-%d') e = datetime.strptime(end_date, '%Y-%m-%d').replace(hour=23, minute=59, second=59) query = query.filter(MaintenanceLog.timestamp.between(s, e)) - except: pass + except: + pass logs = query.order_by(desc(MaintenanceLog.timestamp)).all() return jsonify({'code': 200, 'data': [l.to_dict() for l in logs]}) @@ -242,12 +289,24 @@ def get_logs(): @api_bp.route('/logs/add', methods=['POST']) @jwt_required() def add_log(): - if not is_manager(get_jwt_identity()): return jsonify({'code': 403}), 403 + # 获取用户信息 + current_uid = get_jwt_identity() + user = User.query.get(int(current_uid)) + + if not user or user.role not in ['admin', 'engineer']: + return jsonify({'code': 403, 'msg': '权限不足'}), 403 + data = request.get_json() - # 后端安全校验:如果是工程师,建议再次校验 engineer 字段是否匹配其 username + + # 强制逻辑:工程师必须用自己的名字;Admin可以用前端传的 + engineer_name = user.username if user.role == 'engineer' else data.get('engineer') + + if not engineer_name: + return jsonify({'code': 400, 'msg': '工程师姓名缺失'}), 400 + db.session.add(MaintenanceLog( device_name=data.get('device_name'), - engineer=data.get('engineer'), + engineer=engineer_name, location=data.get('location'), content=data.get('content') )) @@ -258,14 +317,25 @@ def add_log(): @api_bp.route('/logs/update', methods=['POST']) @jwt_required() def update_log(): - if not is_manager(get_jwt_identity()): return jsonify({'code': 403}), 403 + current_uid = get_jwt_identity() + user = User.query.get(int(current_uid)) + + if not user or user.role not in ['admin', 'engineer']: + return jsonify({'code': 403, 'msg': '权限不足'}), 403 + data = request.get_json() log = MaintenanceLog.query.get(data.get('id')) - if not log: return jsonify({'code': 404}), 404 + if not log: + return jsonify({'code': 404, 'msg': '日志不存在'}), 404 - log.engineer = data.get('engineer') + engineer_name = user.username if user.role == 'engineer' else data.get('engineer') + if not engineer_name: + return jsonify({'code': 400, 'msg': '工程师姓名缺失'}), 400 + + log.engineer = engineer_name log.location = data.get('location') log.content = data.get('content') + db.session.commit() return jsonify({'code': 200, 'msg': '更新成功'}) @@ -283,17 +353,18 @@ def delete_log(): # ======================= -# 4. 系统检测与控制 (原有功能完整保留) +# 4. 系统检测与控制 # ======================= @api_bp.route('/run_monitor', methods=['POST']) @jwt_required() def run_monitor(): if not is_admin(get_jwt_identity()): return jsonify({'code': 403}), 403 - if not execute_monitor_task: return jsonify({'code': 500, 'msg': '爬虫模块未加载'}) + if not execute_monitor_task: + return jsonify({'code': 500, 'msg': '爬虫模块未加载'}) try: task_result = execute_monitor_task() - if not task_result: return jsonify({'code': 200, 'msg': '无任务'}) + if not task_result: return jsonify({'code': 200, 'msg': '跳过'}) scraped_list = task_result.get('device_list', []) now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -307,19 +378,20 @@ def run_monitor(): target_time = item.get('target_time') source = item.get('source', '') - # 针对 106 源码进行特殊路径解析 + # 特殊处理 106 路径 if '106' in str(source): try: path_str = d_raw.get('path', '') match = re.search(r'/Data/(\d{4}_\d{2}_\d{2})/\w+_(\d{2}_\d{2}_\d{2})\.csv', path_str) if match: target_time = f"{match.group(1).replace('_', '-')} {match.group(2).replace('_', ':')}" - except: pass + except: + pass json_str = json.dumps(d_raw, ensure_ascii=False) device = Device.query.filter_by(name=d_name).first() if not device: - device = Device(name=d_name, source=source) + device = Device(name=d_name, source=source, install_site="") db.session.add(device) db.session.flush() @@ -338,7 +410,7 @@ def run_monitor(): count += 1 db.session.commit() - return jsonify({'code': 200, 'message': f'更新 {count} 台设备'}) + return jsonify({'code': 200, 'message': f'成功更新 {count} 台设备'}) except Exception as e: db.session.rollback() return jsonify({'code': 500, 'message': str(e)}) @@ -360,9 +432,22 @@ def update_site(): @jwt_required() def toggle_maintenance(): if not is_manager(get_jwt_identity()): return jsonify({'code': 403}), 403 - d = Device.query.filter_by(name=request.get_json().get('name')).first() + + data = request.get_json() + d = Device.query.filter_by(name=data.get('name')).first() + if d: - d.is_maintaining = request.get_json().get('is_maintaining') + is_maintaining = data.get('is_maintaining') + d.is_maintaining = is_maintaining + + # 🟢 [核心修改] 处理维修人名字 + if is_maintaining: + # 开启维修:从前端获取名字 (例如 "张三") 并保存 + d.maintainer = data.get('maintainer') + else: + # 结束维修:清空名字 + d.maintainer = None + db.session.commit() return jsonify({'code': 200}) return jsonify({'code': 404}) diff --git a/zhandianxinxi/光谱数据监控/src/views/Dashboard.vue b/zhandianxinxi/光谱数据监控/src/views/Dashboard.vue index 27b121f..3d16a5f 100644 --- a/zhandianxinxi/光谱数据监控/src/views/Dashboard.vue +++ b/zhandianxinxi/光谱数据监控/src/views/Dashboard.vue @@ -11,6 +11,7 @@ {{ roleDisplayName }} + ({{ currentUsername }}) @@ -53,7 +54,7 @@
- + 修 (维修中) 离线/>7天 滞后1-7天 滞后24h @@ -63,13 +64,21 @@
+ 全部 + 异常({{ summary.errorCount + summary.warningCount }}) + + + 维修({{ summary.maintenanceCount }}) + + 回收({{ summary.hiddenCount }}) + - + @@ -211,32 +222,24 @@ const windowHeight = ref(window.innerHeight) const windowWidth = ref(window.innerWidth) // --- 🔐 权限状态管理 --- -const userRole = ref('') // 存储用户角色: 'admin' | 'engineer' | 'client' +const userRole = ref('') +const currentUsername = ref('') -// 计算属性:是否为超级管理员 const isAdmin = computed(() => userRole.value === 'admin') -// 计算属性:是否为工程师 const isEngineer = computed(() => userRole.value === 'engineer') -// 计算属性:是否为客户 const isClient = computed(() => userRole.value === 'client') - -// 组合权限:是否有设备管理权限 (管理员 OR 工程师) -// 用于:切换维修模式、修改安装地点 const canManageDevice = computed(() => isAdmin.value || isEngineer.value) -// 角色显示名称 const roleDisplayName = computed(() => { if (isAdmin.value) return '超级管理员' if (isEngineer.value) return '设备工程师' return '客户/浏览者' }) -// 角色Tag颜色 const roleTagType = computed(() => { if (isAdmin.value) return 'danger' if (isEngineer.value) return 'warning' return 'info' }) -// -------------------- const tableHeight = computed(() => { const isMobile = windowWidth.value < 768 @@ -249,27 +252,29 @@ const filters = reactive({ status: 'all', keyword: '' }) const dataMonitorRef = ref(null) const maintenanceLogsRef = ref(null) +// 🟢 统计逻辑 const summary = computed(() => { const activeDevices = rawData.value.filter(r => !r.is_hidden) const errors = activeDevices.filter(r => r.statusType === 'error').length const warnings = activeDevices.filter(r => r.statusType === 'warning').length + const maintenance = activeDevices.filter(r => r.is_maintaining).length const hidden = rawData.value.filter(r => r.is_hidden).length - return { errorCount: errors, warningCount: warnings, hiddenCount: hidden } + return { errorCount: errors, warningCount: warnings, hiddenCount: hidden, maintenanceCount: maintenance } }) -// 跳转到用户管理页 -const goToUserManagement = () => { - router.push('/user-management') -} +const goToUserManagement = () => { router.push('/user-management') } const handleLogout = () => { ElMessageBox.confirm('确定退出系统吗?', '提示', { type: 'warning' }).then(() => { - localStorage.clear() // 清除所有缓存 + localStorage.clear() router.push('/') ElMessage.success('已安全退出') }).catch(() => {}) } +// ----------------------------------------------------- +// 🟢 数据获取 +// ----------------------------------------------------- const fetchData = async () => { loading.value = true try { @@ -295,14 +300,20 @@ const fetchData = async () => { } } + // 排序优先级 let sortHours = diffHours; if (item.is_maintaining) sortHours = Number.MAX_SAFE_INTEGER; else if (item.status === 'offline' || item.status === '已离线') sortHours = 1000000000; else if (!validTime) sortHours = 500000000; + // 状态标签生成逻辑 let statusColor = '#67C23A', statusLabel = '正常', statusType = 'normal', statusLabelColor = '#fff' + if (item.is_maintaining) { - statusColor = '#409EFF'; statusLabel = '维修中'; statusType = 'maintenance'; + statusColor = '#409EFF'; + statusType = 'maintenance'; + const mName = item.maintainer || ''; + statusLabel = mName ? `维修中 (${mName})` : '维修中'; } else if ((item.status === 'offline' || item.status === '已离线') || (!validTime || diffDays > 7)) { statusLabel = '离线/滞后'; statusColor = '#F56C6C'; statusType = 'error'; } else if (diffHours > 24) { @@ -330,15 +341,12 @@ const handleDeviceClick = (row) => { if (!row.is_hidden && dataMonitorRef.value) const openLogCenter = (row) => { if (maintenanceLogsRef.value) { - // 传递当前设备的名称(如果有),组件内部可以再次校验用户权限 maintenanceLogsRef.value.open(row ? { deviceName: row.name } : null) } } -// 🔐 只有 Admin 能调用的手动检测 const runManualMonitor = async () => { if (!isAdmin.value) return ElMessage.warning('权限不足') - runningTask.value = true try { const res = await request.post('/api/run_monitor') @@ -351,22 +359,31 @@ const runManualMonitor = async () => { } } +// 🟢 筛选逻辑 const filteredData = computed(() => { return rawData.value.filter(item => { + // 1. 如果选的是“隐藏/回收站”,只显示隐藏设备 if (filters.status === 'hidden') return item.is_hidden + + // 2. 对于其他选项,先排除隐藏设备 if (item.is_hidden) return false + + // 3. 维修中筛选 + if (filters.status === 'maintenance') return item.is_maintaining + + // 4. 异常筛选 (包含离线、滞后、微滞后) if (filters.status === 'abnormal') return (item.statusType === 'error' || item.statusType === 'warning' || item.statusType === 'slight-warning') + + // 5. 全部 return true }).filter(item => !filters.keyword || item.name.toLowerCase().includes(filters.keyword.toLowerCase())) }) const handleEditSite = (row) => { - // 🔐 权限校验 if (!canManageDevice.value) { ElMessage.info('您没有修改权限') return } - row.tempSite = row.install_site; row.isEditingSite = true nextTick(() => { const inputs = document.querySelectorAll('.site-input-inner input') @@ -385,19 +402,42 @@ const saveSite = async (row) => { } const handleMaintenanceBeforeChange = (row) => { - // 🔐 权限校验已经在 v-if 做过,这里是双重保险 if (!canManageDevice.value) return Promise.reject() return new Promise((resolve) => { const newVal = !row.is_maintaining - request.post('/api/toggle_maintenance', { name: row.name, is_maintaining: newVal }) - .then(() => { row.is_maintaining = newVal; fetchData(); ElMessage.success(newVal ? '已进入维修模式' : '已恢复'); resolve(true) }) - .catch(() => { ElMessage.error('操作失败'); resolve(false) }) + const maintainerName = currentUsername.value || '工程师'; + + request.post('/api/toggle_maintenance', { + name: row.name, + is_maintaining: newVal, + maintainer: newVal ? maintainerName : null + }) + .then(() => { + row.is_maintaining = newVal; + row.maintainer = newVal ? maintainerName : null; + + if (newVal) { + row.statusColor = '#409EFF'; + row.statusLabel = `维修中 (${maintainerName})`; + row.statusType = 'maintenance'; + } else { + row.statusLabel = '更新中...'; + } + + ElMessage.success(newVal ? '已进入维修模式' : '已恢复'); + fetchData(); + resolve(true) + }) + .catch(() => { + ElMessage.error('操作失败'); + resolve(false) + }) }) } const toggleHidden = async (row, targetState) => { - if (!isAdmin.value) return // 🔐 双重保险 + if (!isAdmin.value) return try { await request.post('/api/toggle_hidden', { name: row.name, is_hidden: targetState }) row.is_hidden = targetState; fetchData(); ElMessage.success(targetState ? '已隐藏' : '已恢复') @@ -419,12 +459,8 @@ const updateDimensions = () => { } onMounted(() => { - // 从本地存储获取角色,默认为 client userRole.value = localStorage.getItem('role') || 'client' - - // 安全日志 - console.log('Current User Role:', userRole.value) - + currentUsername.value = localStorage.getItem('username') || '' fetchData() window.addEventListener('resize', updateDimensions) }) @@ -464,6 +500,16 @@ onBeforeUnmount(() => window.removeEventListener('resize', updateDimensions)) } .search-input { width: 220px; transition: width 0.3s; } +/* 🟢 自定义筛选按钮样式,增加辨识度 */ +.red-radio :deep(.el-radio-button__inner) { color: #F56C6C; } +.red-radio.is-active :deep(.el-radio-button__inner) { background-color: #F56C6C; border-color: #F56C6C; color: #fff; box-shadow: -1px 0 0 0 #F56C6C; } + +.blue-radio :deep(.el-radio-button__inner) { color: #409EFF; } +.blue-radio.is-active :deep(.el-radio-button__inner) { background-color: #409EFF; border-color: #409EFF; color: #fff; box-shadow: -1px 0 0 0 #409EFF; } + +.gray-radio :deep(.el-radio-button__inner) { color: #909399; } +.gray-radio.is-active :deep(.el-radio-button__inner) { background-color: #909399; border-color: #909399; color: #fff; box-shadow: -1px 0 0 0 #909399; } + .device-name-wrapper { display: flex; align-items: center; gap: 5px; cursor: pointer; } .device-name-wrapper:hover .device-name { color: #409EFF; text-decoration: underline; } .device-name { font-weight: bold; font-size: 14px; color: #303133; } @@ -472,7 +518,7 @@ onBeforeUnmount(() => window.removeEventListener('resize', updateDimensions)) .error-text { color: #F56C6C; } .warning-text { color: #E6A23C; } .success-text { color: #67C23A; } -.maintenance-text { color: #409EFF; } +.maintenance-text { color: #409EFF; font-weight: bold; } .display-cell { padding: 4px 0; display: flex; align-items: center; justify-content: space-between; } .edit-icon { color: #409EFF; margin-left: 5px; } diff --git a/zhandianxinxi/光谱数据监控/src/views/MaintenanceLogs.vue b/zhandianxinxi/光谱数据监控/src/views/MaintenanceLogs.vue index 3b378ad..b28080f 100644 --- a/zhandianxinxi/光谱数据监控/src/views/MaintenanceLogs.vue +++ b/zhandianxinxi/光谱数据监控/src/views/MaintenanceLogs.vue @@ -9,7 +9,6 @@ append-to-body >
-
- 新增记录 + 新增记录
@@ -77,7 +76,6 @@ link icon="Edit" @click="openEditDialog(row)" - style="margin-right: 5px;" > 修改 @@ -105,19 +103,25 @@ - - + -
- 关联设备不可变更 -
+ :fetch-suggestions="querySearchDevice" + placeholder="必须选择现有设备" + :disabled="logDialog.isDeviceLocked || logDialog.isEdit" + style="width: 100%" + clearable + highlight-first-item + trigger-on-focus + > + +
- + - + { +// 统一获取认证信息 +const refreshAuth = () => { userRole.value = localStorage.getItem('role') || 'client' currentUsername.value = localStorage.getItem('username') || '' } -onMounted(() => { - refreshUserInfo() -}) +onMounted(() => { refreshAuth() }) -// --- 方法 --- - -// 1. 打开主弹窗 +// 1. 打开主列表 const open = (prefillData = null) => { - refreshUserInfo() + refreshAuth() visible.value = true isSearchLocked.value = false keyword.value = '' + // 🟢 必须:打开时立即加载设备库,否则无法校验 + fetchAllDevices() + if (prefillData && prefillData.deviceName) { keyword.value = prefillData.deviceName - // 非 Admin 从特定设备点进来时锁定搜索 if (userRole.value !== 'admin') { isSearchLocked.value = true } @@ -217,7 +222,32 @@ const open = (prefillData = null) => { fetchLogs() } -// 2. 获取列表 +// 🟢 获取设备库(核心) +const fetchAllDevices = async () => { + try { + const res = await request.get('/api/devices_overview') + if (res.data && res.data.data) { + allDevices.value = res.data.data.map(d => ({ value: d.name })) + } + } catch (e) { + console.error('无法加载设备列表', e) + } +} + +// 自动补全过滤 +const querySearchDevice = (queryString, cb) => { + const results = queryString + ? allDevices.value.filter(createFilter(queryString)) + : allDevices.value + cb(results) +} +const createFilter = (queryString) => { + return (item) => { + return (item.value.toLowerCase().indexOf(queryString.toLowerCase()) !== -1) + } +} + +// 2. 获取日志列表 const fetchLogs = async () => { loading.value = true try { @@ -229,35 +259,53 @@ const fetchLogs = async () => { const res = await request.get('/api/logs/list', { params }) logsList.value = res.data.data } catch (e) { - ElMessage.error('加载日志中心数据失败') + ElMessage.error('获取日志失败') } finally { loading.value = false } } -// 3. 打开新增对话框 +// 3. 打开新增 const openAddDialog = () => { - refreshUserInfo() + refreshAuth() logDialog.isEdit = false + + let autoEngineer = '' + if (userRole.value === 'engineer') { + autoEngineer = currentUsername.value + } + + // 逻辑:如果搜索栏锁定了(从设备卡片进来的),直接锁定设备名 + let prefillDevice = '' + let lockDevice = false + + if (isSearchLocked.value && keyword.value) { + prefillDevice = keyword.value + lockDevice = true + } + + logDialog.isDeviceLocked = lockDevice + logDialog.form = { id: null, - device_name: keyword.value || '', // 如果主表单有搜索词,自动填入设备名 - // 🟢 修复:如果是 Engineer,强制填入用户名,否则才允许手动输入 - engineer: userRole.value === 'engineer' ? currentUsername.value : '', + device_name: prefillDevice, + engineer: autoEngineer, location: '', content: '' } logDialog.visible = true } -// 4. 打开编辑对话框 +// 4. 编辑 const openEditDialog = (row) => { - refreshUserInfo() + refreshAuth() logDialog.isEdit = true + // 编辑模式禁止修改设备名(防止关联错误) + logDialog.isDeviceLocked = true + logDialog.form = { id: row.id, device_name: row.device_name, - // 🟢 修复:工程师修改时,也强制纠正为当前用户姓名,防止冒名顶替 engineer: userRole.value === 'engineer' ? currentUsername.value : row.engineer, location: row.location, content: row.content @@ -265,15 +313,37 @@ const openEditDialog = (row) => { logDialog.visible = true } -// 5. 提交 (新增/修改) +// 5. 🟢 提交保存 (核心修改区) const submitLog = async () => { - // 🟢 最终拦截:确保如果是工程师,提交上去的姓名一定是当前的正确姓名 - if (userRole.value === 'engineer') { - logDialog.form.engineer = currentUsername.value + refreshAuth() + + const inputDeviceName = logDialog.form.device_name; + + // A. 基础非空校验 + if (!inputDeviceName || !logDialog.form.content) { + return ElMessage.warning('请填写 设备名称 和 事件内容') } - if (!logDialog.form.device_name || !logDialog.form.content || !logDialog.form.engineer) { - return ElMessage.warning('信息填写不完整(设备名、工程师、内容为必填)') + // B. 🔴 关键逻辑:校验设备名是否存在于设备库中 + // 如果当前设备列表还没加载完(极少情况),尝试重新加载一次或者报错 + if (allDevices.value.length === 0) { + await fetchAllDevices(); + } + + // 检查输入的设备名是否能在 allDevices 数组里找到 exact match + const isDeviceExist = allDevices.value.some(d => d.value === inputDeviceName); + + // 如果设备名不在库中,且也不是空,直接拒绝 + if (!isDeviceExist) { + return ElMessage.error(`设备 "${inputDeviceName}" 不存在!请从下拉列表中选择有效的设备。`); + } + + // C. 身份校验 + if (userRole.value !== 'engineer' && !logDialog.form.engineer) { + return ElMessage.warning('请填写工程师姓名') + } + if (userRole.value === 'engineer') { + logDialog.form.engineer = currentUsername.value } logDialog.submitting = true @@ -281,70 +351,45 @@ const submitLog = async () => { const endpoint = logDialog.isEdit ? '/api/logs/update' : '/api/logs/add' await request.post(endpoint, logDialog.form) - ElMessage.success(logDialog.isEdit ? '日志已成功修改' : '日志已添加') + ElMessage.success(logDialog.isEdit ? '记录已更新' : '记录已添加') logDialog.visible = false fetchLogs() } catch (e) { - ElMessage.error(e.response?.data?.msg || '操作失败') + ElMessage.error(e.response?.data?.msg || '保存失败') } finally { logDialog.submitting = false } } -// 6. 删除 (仅 Admin) +// 6. 删除 const deleteLog = async (id) => { try { await request.post('/api/logs/delete', { id }) - ElMessage.success('记录已安全删除') + ElMessage.success('已删除') fetchLogs() } catch (e) { - ElMessage.error('删除失败,权限不足') + ElMessage.error('无权删除') } } -const formatDisplayName = (name) => name ? name.toUpperCase().replace(/_/g, ' ') : '' +const formatDisplayName = (n) => n ? n.toUpperCase().replace(/_/g, ' ') : '' defineExpose({ open }) diff --git a/zhandianxinxi/光谱数据监控/src/views/UserManagement.vue b/zhandianxinxi/光谱数据监控/src/views/UserManagement.vue index 9fa46a4..a3bf946 100644 --- a/zhandianxinxi/光谱数据监控/src/views/UserManagement.vue +++ b/zhandianxinxi/光谱数据监控/src/views/UserManagement.vue @@ -15,13 +15,11 @@ - - - - -