checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] === self::ROLE_SUPER_ADMIN) { return $user; } if ($user['role'] === self::ROLE_ADMIN && ($user['category'] ?? '') === self::CATEGORY_GENERAL) { return $user; } header('Location: ' . BASE_URL . '/Front/index'); exit; } public function index() { $user = $this->checkCategoryAccess(); $hrEmployee = new HrEmployee(); $asset = new Asset(); $document = new Document(); $this->assign('title', '综合管理概览'); $this->assign('user', $user); $this->assign('activeMenu', 'general_dashboard'); $this->assign('employeeCount', $hrEmployee->getCount()); $this->assign('assetStats', $asset->getCount()); $this->assign('documentCount', $document->getCount()); $this->render(); } // ========== 人事管理 ========== public function hr() { $user = $this->checkCategoryAccess(); $hrEmployee = new HrEmployee(); $employees = $hrEmployee->getAll(); $this->assign('title', '人事管理'); $this->assign('user', $user); $this->assign('activeMenu', 'hr'); $this->assign('employees', $employees); $this->render(); } public function hrAdd() { $user = $this->checkCategoryAccess(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); $data = [ 'emp_no' => trim($_POST['emp_no'] ?? ''), 'name' => trim($_POST['name'] ?? ''), 'gender' => trim($_POST['gender'] ?? ''), 'department' => trim($_POST['department'] ?? ''), 'position' => trim($_POST['position'] ?? ''), 'phone' => trim($_POST['phone'] ?? ''), 'email' => trim($_POST['email'] ?? ''), 'id_card' => trim($_POST['id_card'] ?? ''), 'entry_date' => trim($_POST['entry_date'] ?? ''), 'status' => trim($_POST['status'] ?? 'active'), 'education' => trim($_POST['education'] ?? ''), 'emergency_contact' => trim($_POST['emergency_contact'] ?? ''), 'emergency_phone' => trim($_POST['emergency_phone'] ?? ''), 'address' => trim($_POST['address'] ?? ''), 'remark' => trim($_POST['remark'] ?? ''), ]; if (empty($data['name'])) { $this->assign('error', '姓名不能为空'); $this->assign('title', '添加员工'); $this->assign('user', $user); $this->assign('activeMenu', 'hr'); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } try { $hrEmployee = new HrEmployee(); $hrEmployee->add($data); header('Location: ' . BASE_URL . '/General/hr'); exit; } catch (\Throwable $e) { error_log('[hrAdd] ' . $e->getMessage()); $this->assign('error', '添加失败:' . $e->getMessage()); $this->assign('title', '添加员工'); $this->assign('user', $user); $this->assign('activeMenu', 'hr'); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } $this->assign('title', '添加员工'); $this->assign('user', $user); $this->assign('activeMenu', 'hr'); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function hrEdit() { $user = $this->checkCategoryAccess(); $id = (int)($_GET['id'] ?? 0); $hrEmployee = new HrEmployee(); $record = $hrEmployee->getById($id); if (!$record) exit('员工不存在'); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); $data = [ 'emp_no' => trim($_POST['emp_no'] ?? ''), 'name' => trim($_POST['name'] ?? ''), 'gender' => trim($_POST['gender'] ?? ''), 'department' => trim($_POST['department'] ?? ''), 'position' => trim($_POST['position'] ?? ''), 'phone' => trim($_POST['phone'] ?? ''), 'email' => trim($_POST['email'] ?? ''), 'id_card' => trim($_POST['id_card'] ?? ''), 'entry_date' => trim($_POST['entry_date'] ?? ''), 'leave_date' => trim($_POST['leave_date'] ?? ''), 'status' => trim($_POST['status'] ?? 'active'), 'education' => trim($_POST['education'] ?? ''), 'emergency_contact' => trim($_POST['emergency_contact'] ?? ''), 'emergency_phone' => trim($_POST['emergency_phone'] ?? ''), 'address' => trim($_POST['address'] ?? ''), 'remark' => trim($_POST['remark'] ?? ''), ]; try { $hrEmployee->where(['id = :id'], [':id' => $id])->update($data); header('Location: ' . BASE_URL . '/General/hr'); exit; } catch (\Throwable $e) { error_log('[hrEdit] ' . $e->getMessage()); $this->assign('error', '更新失败:' . $e->getMessage()); $this->assign('title', '编辑员工'); $this->assign('user', $user); $this->assign('activeMenu', 'hr'); $this->assign('record', $record); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } $this->assign('title', '编辑员工'); $this->assign('user', $user); $this->assign('activeMenu', 'hr'); $this->assign('record', $record); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function hrDelete() { $this->checkLogin(); $this->requireMethod('post'); $this->csrfVerify(); $id = (int)($_POST['id'] ?? 0); $hrEmployee = new HrEmployee(); $hrEmployee->delete($id); header('Location: ' . BASE_URL . '/General/hr'); exit; } // ========== 资产管理 ========== public function asset() { $user = $this->checkCategoryAccess(); $asset = new Asset(); $assets = $asset->getAll(); $this->assign('title', '资产管理'); $this->assign('user', $user); $this->assign('activeMenu', 'asset'); $this->assign('assets', $assets); $this->render(); } public function assetAdd() { $user = $this->checkCategoryAccess(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); $data = [ 'asset_no' => trim($_POST['asset_no'] ?? ''), 'name' => trim($_POST['name'] ?? ''), 'category' => trim($_POST['category'] ?? ''), 'model' => trim($_POST['model'] ?? ''), 'quantity' => (int)($_POST['quantity'] ?? 1), 'unit' => trim($_POST['unit'] ?? '台'), 'purchase_price' => (float)($_POST['purchase_price'] ?? 0), 'purchase_date' => trim($_POST['purchase_date'] ?? ''), 'supplier' => trim($_POST['supplier'] ?? ''), 'department' => trim($_POST['department'] ?? ''), 'user_name' => trim($_POST['user_name'] ?? ''), 'location' => trim($_POST['location'] ?? ''), 'status' => trim($_POST['status'] ?? 'normal'), 'warranty_expire' => trim($_POST['warranty_expire'] ?? ''), 'remark' => trim($_POST['remark'] ?? ''), ]; if (empty($data['name'])) { $this->assign('error', '资产名称不能为空'); $this->assign('title', '添加资产'); $this->assign('user', $user); $this->assign('activeMenu', 'asset'); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } try { $asset = new Asset(); $asset->add($data); header('Location: ' . BASE_URL . '/General/asset'); exit; } catch (\Throwable $e) { error_log('[assetAdd] ' . $e->getMessage()); $this->assign('error', '添加失败:' . $e->getMessage()); $this->assign('title', '添加资产'); $this->assign('user', $user); $this->assign('activeMenu', 'asset'); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } $this->assign('title', '添加资产'); $this->assign('user', $user); $this->assign('activeMenu', 'asset'); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function assetEdit() { $user = $this->checkCategoryAccess(); $id = (int)($_GET['id'] ?? 0); $asset = new Asset(); $record = $asset->getById($id); if (!$record) exit('资产不存在'); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); $data = [ 'asset_no' => trim($_POST['asset_no'] ?? ''), 'name' => trim($_POST['name'] ?? ''), 'category' => trim($_POST['category'] ?? ''), 'model' => trim($_POST['model'] ?? ''), 'quantity' => (int)($_POST['quantity'] ?? 1), 'unit' => trim($_POST['unit'] ?? '台'), 'purchase_price' => (float)($_POST['purchase_price'] ?? 0), 'purchase_date' => trim($_POST['purchase_date'] ?? ''), 'supplier' => trim($_POST['supplier'] ?? ''), 'department' => trim($_POST['department'] ?? ''), 'user_name' => trim($_POST['user_name'] ?? ''), 'location' => trim($_POST['location'] ?? ''), 'status' => trim($_POST['status'] ?? 'normal'), 'warranty_expire' => trim($_POST['warranty_expire'] ?? ''), 'remark' => trim($_POST['remark'] ?? ''), ]; try { $asset->where(['id = :id'], [':id' => $id])->update($data); header('Location: ' . BASE_URL . '/General/asset'); exit; } catch (\Throwable $e) { error_log('[assetEdit] ' . $e->getMessage()); $this->assign('error', '更新失败:' . $e->getMessage()); $this->assign('title', '编辑资产'); $this->assign('user', $user); $this->assign('activeMenu', 'asset'); $this->assign('record', $record); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } $this->assign('title', '编辑资产'); $this->assign('user', $user); $this->assign('activeMenu', 'asset'); $this->assign('record', $record); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function assetDelete() { $this->checkLogin(); $this->requireMethod('post'); $this->csrfVerify(); $id = (int)($_POST['id'] ?? 0); $asset = new Asset(); $asset->delete($id); header('Location: ' . BASE_URL . '/General/asset'); exit; } // ========== 文档管理 ========== public function document() { $user = $this->checkCategoryAccess(); $document = new Document(); $documents = $document->getAll(); $categories = $document->getCategories(); $this->assign('title', '文档管理'); $this->assign('user', $user); $this->assign('activeMenu', 'document'); $this->assign('documents', $documents); $this->assign('categories', $categories); $this->render(); } public function documentAdd() { $user = $this->checkCategoryAccess(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); $data = [ 'title' => trim($_POST['title'] ?? ''), 'doc_no' => trim($_POST['doc_no'] ?? ''), 'category' => trim($_POST['category'] ?? ''), 'version' => trim($_POST['version'] ?? '1.0'), 'author' => trim($_POST['author'] ?? ''), 'department' => trim($_POST['department'] ?? ''), 'status' => trim($_POST['status'] ?? 'active'), 'keywords' => trim($_POST['keywords'] ?? ''), 'description' => trim($_POST['description'] ?? ''), 'operator' => $user['emp_name'], ]; // 文件上传 — 仅允许安全文档类型 if (isset($_FILES['doc_file']) && $_FILES['doc_file']['error'] === UPLOAD_ERR_OK) { $allowedExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'csv', 'zip', 'rar', 'jpg', 'jpeg', 'png', 'gif']; $allowedMimeTypes = [ 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'text/plain', 'text/csv', 'application/zip', 'application/x-zip-compressed', 'application/x-rar-compressed', 'image/jpeg', 'image/png', 'image/gif', ]; $originalName = $_FILES['doc_file']['name']; $ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION)); $tmpPath = $_FILES['doc_file']['tmp_name']; $mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $tmpPath); if (!in_array($ext, $allowedExtensions, true) || !in_array($mime, $allowedMimeTypes, true)) { error_log('[documentAdd] Upload rejected: ext=' . $ext . ' mime=' . $mime . ' file=' . $originalName); $this->assign('error', '不支持的文件类型,仅允许常见文档、图片和压缩包格式'); $this->assign('title', '添加文档'); $this->assign('user', $user); $this->assign('activeMenu', 'document'); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } $uploadDir = APP_PATH . 'static/uploads/documents/'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); } $saveName = date('YmdHis') . '_' . uniqid() . '.' . $ext; $savePath = $uploadDir . $saveName; if (move_uploaded_file($tmpPath, $savePath)) { $data['file_name'] = $originalName; $data['file_path'] = '/static/uploads/documents/' . $saveName; $data['file_size'] = $_FILES['doc_file']['size']; $data['file_type'] = $ext; } } if (empty($data['title'])) { $this->assign('error', '文档标题不能为空'); $this->assign('title', '添加文档'); $this->assign('user', $user); $this->assign('activeMenu', 'document'); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } try { $document = new Document(); $document->add($data); header('Location: ' . BASE_URL . '/General/document'); exit; } catch (\Throwable $e) { error_log('[documentAdd] ' . $e->getMessage()); $this->assign('error', '添加失败:' . $e->getMessage()); $this->assign('title', '添加文档'); $this->assign('user', $user); $this->assign('activeMenu', 'document'); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } $this->assign('title', '添加文档'); $this->assign('user', $user); $this->assign('activeMenu', 'document'); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function documentEdit() { $user = $this->checkCategoryAccess(); $id = (int)($_GET['id'] ?? 0); $document = new Document(); $record = $document->getById($id); if (!$record) exit('文档不存在'); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); $data = [ 'title' => trim($_POST['title'] ?? ''), 'doc_no' => trim($_POST['doc_no'] ?? ''), 'category' => trim($_POST['category'] ?? ''), 'version' => trim($_POST['version'] ?? '1.0'), 'author' => trim($_POST['author'] ?? ''), 'department' => trim($_POST['department'] ?? ''), 'status' => trim($_POST['status'] ?? 'active'), 'keywords' => trim($_POST['keywords'] ?? ''), 'description' => trim($_POST['description'] ?? ''), ]; // 文件上传 — 仅允许安全文档类型 if (isset($_FILES['doc_file']) && $_FILES['doc_file']['error'] === UPLOAD_ERR_OK) { $allowedExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'csv', 'zip', 'rar', 'jpg', 'jpeg', 'png', 'gif']; $allowedMimeTypes = [ 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'text/plain', 'text/csv', 'application/zip', 'application/x-zip-compressed', 'application/x-rar-compressed', 'image/jpeg', 'image/png', 'image/gif', ]; $originalName = $_FILES['doc_file']['name']; $ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION)); $tmpPath = $_FILES['doc_file']['tmp_name']; $mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $tmpPath); if (!in_array($ext, $allowedExtensions, true) || !in_array($mime, $allowedMimeTypes, true)) { error_log('[documentEdit] Upload rejected: ext=' . $ext . ' mime=' . $mime . ' file=' . $originalName); $this->assign('error', '不支持的文件类型,仅允许常见文档、图片和压缩包格式'); $this->assign('title', '编辑文档'); $this->assign('user', $user); $this->assign('activeMenu', 'document'); $this->assign('record', $record); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } $uploadDir = APP_PATH . 'static/uploads/documents/'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); } $saveName = date('YmdHis') . '_' . uniqid() . '.' . $ext; $savePath = $uploadDir . $saveName; if (move_uploaded_file($tmpPath, $savePath)) { $data['file_name'] = $originalName; $data['file_path'] = '/static/uploads/documents/' . $saveName; $data['file_size'] = $_FILES['doc_file']['size']; $data['file_type'] = $ext; } } try { $document->where(['id = :id'], [':id' => $id])->update($data); header('Location: ' . BASE_URL . '/General/document'); exit; } catch (\Throwable $e) { error_log('[documentEdit] ' . $e->getMessage()); $this->assign('error', '更新失败:' . $e->getMessage()); $this->assign('title', '编辑文档'); $this->assign('user', $user); $this->assign('activeMenu', 'document'); $this->assign('record', $record); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } $this->assign('title', '编辑文档'); $this->assign('user', $user); $this->assign('activeMenu', 'document'); $this->assign('record', $record); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function documentDelete() { $this->checkLogin(); $this->requireMethod('post'); $this->csrfVerify(); $id = (int)($_POST['id'] ?? 0); $document = new Document(); $document->delete($id); header('Location: ' . BASE_URL . '/General/document'); exit; } }