checkLogin(); $user = $this->getCurrentUser(); // 只有超级管理员 或 MES类目的管理员可访问 if ($user['role'] === self::ROLE_SUPER_ADMIN) { // 超级管理员可以访问 } elseif ($user['role'] === self::ROLE_ADMIN && $user['category'] === self::CATEGORY_MES) { // MES 类目管理员可以访问 } else { header('Location: ' . BASE_URL . '/Front/index'); exit; } $config = new SysConfig(); $sysConfig = $config->getConfig(); // 统计数据 $employee = new Employee(); $employeeCount = count($employee->getAll()); $customer = new Customer(); $customerCount = count($customer->getAll()); $productModel = new ProductModel(); $modelCount = count($productModel->getAll()); $workstation = new Workstation(); $stationCount = count($workstation->getAll()); $this->assign('title', '后台管理'); $this->assign('user', $user); $this->assign('sysConfig', $sysConfig); $this->assign('employeeCount', $employeeCount); $this->assign('customerCount', $customerCount); $this->assign('modelCount', $modelCount); $this->assign('stationCount', $stationCount); $this->render(); } // 员工管理 public function employee() { $this->checkLogin(); $user = $this->getCurrentUser(); // 仅超级管理员可访问员工管理 if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } $employee = new Employee(); $employees = $employee->getAll(); // 管理员看不到超级管理员(比自己权限高的不可见) if ($user['role'] === self::ROLE_ADMIN) { $employees = array_filter($employees, function($emp) { return $emp['role'] !== self::ROLE_SUPER_ADMIN; }); $employees = array_values($employees); // re-index } // Flash 消息(一次性提示,如密码生成提示) $flash = $_SESSION['flash_message'] ?? null; if ($flash) { unset($_SESSION['flash_message']); } $this->assign('csrfToken', $this->csrfToken()); $this->assign('title', '员工管理'); $this->assign('user', $user); $this->assign('employees', $employees); $this->assign('flash', $flash); $this->render(); } public function employeeAdd() { $this->checkLogin(); $user = $this->getCurrentUser(); // 只有超级管理员可以添加员工(管理员由超管分配类目) if ($user['role'] !== self::ROLE_SUPER_ADMIN) { exit('无权限访问'); } if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); // 输入验证 $empName = trim($_POST['emp_name'] ?? ''); if (empty($empName) || mb_strlen($empName) > 50) { exit('员工姓名无效'); } $role = $_POST['role']; $category = $_POST['category'] ?? self::CATEGORY_MES; // 管理员和操作员必须分配类目 if ($role !== self::ROLE_SUPER_ADMIN && empty($category)) { exit('请为管理员/操作员选择管理类目'); } // 自动生成员工编号:ZXY + 三位流水号 $emp_no = $this->generateEmpNo(); $data = array( 'emp_no' => $emp_no, 'emp_name' => $empName, 'password' => $_POST['password'] ?? '', 'role' => $role, 'category' => ($role === self::ROLE_SUPER_ADMIN) ? self::CATEGORY_ALL : $category, 'created_by' => $user['emp_name'] ); try { $employee = new Employee(); $id = $employee->addEmployee($data); if (!$id) { // 插入失败(如编号重复),抛出异常让 catch 处理 $dbErr = $employee->getError() ?: '未知数据库错误'; throw new \RuntimeException('添加员工失败:' . $dbErr); } $genPwd = $employee->getLastGeneratedPwd(); if ($genPwd) { // 自动生成密码时,跳转到列表并显示密码提示 $_SESSION['flash_message'] = "员工 {$empName}({$emp_no})已创建,初始密码为:{$genPwd},请通知该员工首次登录后修改密码"; } } catch (\RuntimeException $e) { $this->assign('error', $e->getMessage()); $this->assign('title', '添加员工'); $this->assign('user', $user); $this->assign('manageableRoles', self::getManageableRoles($user['role'])); $this->assign('nextEmpNo', $emp_no); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } header('Location: ' . BASE_URL . '/Admin/employee'); exit; } // 传递可选角色列表和类目列表到视图 $manageableRoles = self::getManageableRoles($user['role']); $categoryOptions = self::getCategoryOptions(); // 预览下一个编号 $nextEmpNo = $this->generateEmpNo(); $this->assign('title', '添加员工'); $this->assign('user', $user); $this->assign('manageableRoles', $manageableRoles); $this->assign('categoryOptions', $categoryOptions); $this->assign('nextEmpNo', $nextEmpNo); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function employeeEdit($id) { $this->checkLogin(); $user = $this->getCurrentUser(); // 只有超级管理员可以编辑 if ($user['role'] !== self::ROLE_SUPER_ADMIN) { exit('无权限访问'); } $employee = new Employee(); $emp = $employee->where(['id = :id'], [':id' => $id])->fetch(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); try { $newRole = $_POST['role']; $newCategory = $_POST['category'] ?? self::CATEGORY_MES; $data = array( 'emp_name' => $_POST['emp_name'], 'role' => $newRole, 'category' => ($newRole === self::ROLE_SUPER_ADMIN) ? self::CATEGORY_ALL : $newCategory ); $employee->updateEmployee($id, $data); header('Location: ' . BASE_URL . '/Admin/employee'); exit; } catch (\Throwable $e) { error_log('[employeeEdit] ' . $e->getMessage()); $this->assign('error', '更新失败:' . $e->getMessage()); $this->assign('title', '编辑员工'); $this->assign('user', $user); $this->assign('emp', $emp); $manageableRoles = self::getManageableRoles($user['role']); $categoryOptions = self::getCategoryOptions(); $this->assign('manageableRoles', $manageableRoles); $this->assign('categoryOptions', $categoryOptions); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } $manageableRoles = self::getManageableRoles($user['role']); $categoryOptions = self::getCategoryOptions(); $this->assign('title', '编辑员工'); $this->assign('user', $user); $this->assign('emp', $emp); $this->assign('manageableRoles', $manageableRoles); $this->assign('categoryOptions', $categoryOptions); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function employeeDelete($id) { $this->checkLogin(); $this->requireMethod('POST'); $this->csrfVerify(); $user = $this->getCurrentUser(); // 只有超级管理员可以删除 if ($user['role'] !== self::ROLE_SUPER_ADMIN) { exit('无权限访问'); } $employee = new Employee(); $emp = $employee->where(['id = :id'], [':id' => $id])->fetch(); // 不能删除自己 if ((int)$emp['id'] === (int)$user['id']) { exit('不能删除自己的账号'); } $employee->delete($id); header('Location: ' . BASE_URL . '/Admin/employee'); } public function employeeResetPwd($id) { $this->checkLogin(); $user = $this->getCurrentUser(); // 只有超级管理员可以重置密码 if ($user['role'] !== self::ROLE_SUPER_ADMIN) { exit('无权限访问'); } $employee = new Employee(); $emp = $employee->where(['id = :id'], [':id' => $id])->fetch(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); try { $new_pwd = $_POST['new_password']; $employee = new Employee(); $employee->resetPassword($id, $new_pwd); header('Location: ' . BASE_URL . '/Admin/employee'); exit; } catch (\Throwable $e) { error_log('[employeeResetPwd] ' . $e->getMessage()); $this->assign('error', '重置密码失败:' . $e->getMessage()); $this->assign('title', '重置密码'); $this->assign('user', $user); $this->assign('emp', $emp); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } $this->assign('title', '重置密码'); $this->assign('user', $user); $this->assign('emp', $emp); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } /** * 设置员工工位权限 */ public function employeePermission($id) { $this->checkLogin(); $user = $this->getCurrentUser(); // 只有超级管理员可以设置工位权限 if ($user['role'] !== self::ROLE_SUPER_ADMIN) { exit('无权限访问'); } $employee = new Employee(); $emp = $employee->where(['id = :id'], [':id' => $id])->fetch(); if (!$emp) { exit('员工不存在'); } $workstation = new Workstation(); $allStations = $workstation->getAll(); $permModel = new EmployeePermission(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); $permissions = []; // $_POST['allow'] 是勾选的工位类型数组 $allowedTypes = isset($_POST['allow']) ? (array)$_POST['allow'] : []; foreach ($allStations as $station) { $stype = $station['station_type']; $permissions[$stype] = in_array($stype, $allowedTypes) ? 1 : 0; } $permModel->savePermissions($id, $permissions); header('Location: ' . BASE_URL . '/Admin/employeePermission/' . $id . '?saved=1'); exit; } // 获取当前权限(表不存在时友好提示,而非 500) try { $currentPermissions = $permModel->getByEmployee($id); } catch (\PDOException $e) { exit('数据表 DGZXY_employee_station_permission 不存在,请在数据库中执行 sql/create_employee_station_permission.sql'); } $saved = isset($_GET['saved']) && $_GET['saved'] === '1'; $this->assign('title', '工位权限设置 - ' . $emp['emp_name']); $this->assign('user', $user); $this->assign('emp', $emp); $this->assign('stations', $allStations); $this->assign('currentPermissions', $currentPermissions); $this->assign('saved', $saved); $this->assign('activeMenu', 'employee'); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } // 客户管理 — 超级管理员和管理员 public function customer() { $this->checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } $customer = new Customer(); $customers = $customer->getAll(); $this->assign('title', '客户管理'); $this->assign('user', $user); $this->assign('customers', $customers); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function customerAdd() { $this->checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); try { $data = array( 'company_name' => $_POST['company_name'], 'contact1' => $_POST['contact1'], 'phone1' => $_POST['phone1'], 'contact2' => $_POST['contact2'], 'phone2' => $_POST['phone2'], 'created_by' => $user['emp_name'] ); $customer = new Customer(); $customer->addCustomer($data); header('Location: ' . BASE_URL . '/Admin/customer'); exit; } catch (\Throwable $e) { error_log('[customerAdd] ' . $e->getMessage()); $this->assign('error', '添加失败:' . $e->getMessage()); $this->assign('title', '添加客户'); $this->assign('user', $user); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } $this->assign('title', '添加客户'); $this->assign('user', $user); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function customerEdit($id) { $this->checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } $customer = new Customer(); $cust = $customer->where(['id = :id'], [':id' => $id])->fetch(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); try { $data = array( 'company_name' => $_POST['company_name'], 'contact1' => $_POST['contact1'], 'phone1' => $_POST['phone1'], 'contact2' => $_POST['contact2'], 'phone2' => $_POST['phone2'] ); $customer->updateCustomer($id, $data); header('Location: ' . BASE_URL . '/Admin/customer'); exit; } catch (\Throwable $e) { error_log('[customerEdit] ' . $e->getMessage()); $this->assign('error', '更新失败:' . $e->getMessage()); $this->assign('title', '编辑客户'); $this->assign('user', $user); $this->assign('cust', $cust); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } $this->assign('title', '编辑客户'); $this->assign('user', $user); $this->assign('cust', $cust); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function customerDelete($id) { $this->checkLogin(); $this->requireMethod('POST'); $this->csrfVerify(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } $customer = new Customer(); $customer->delete($id); header('Location: ' . BASE_URL . '/Admin/customer'); } // 产品型号管理 — 仅超级管理员 public function productModel() { $this->checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } $productModel = new ProductModel(); $allModels = $productModel->getAll(); // 按 type 分组 $grouped = []; foreach ($allModels as $m) { $t = $m['type'] ?: '未分类'; $grouped[$t][] = $m; } // 从数据库读取类型配置(排序),表不存在时回退到硬编码默认值 $typeConfigModel = new ProductTypeConfig(); $typeConfigs = $typeConfigModel->getAll(); if (empty($typeConfigs)) { $typeConfigs = ProductTypeConfig::getDefaultConfigs(); } $orderedTypeNames = []; $typeColorMap = []; foreach ($typeConfigs as $cfg) { $orderedTypeNames[] = $cfg['type_name']; $typeColorMap[$cfg['type_name']] = $cfg['color'] ?? 'default'; } // 按配置的顺序排列,未配置的追加在后面 $ordered = []; foreach ($orderedTypeNames as $t) { if (isset($grouped[$t])) { $ordered[$t] = $grouped[$t]; unset($grouped[$t]); } } $ordered += $grouped; // 剩下的(包括"未分类")追加在后面 $this->assign('title', '产品型号管理'); $this->assign('user', $user); $this->assign('groupedModels', $ordered); $this->assign('typeConfigs', $typeConfigs); $this->assign('typeColorMap', $typeColorMap); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function productModelAdd() { $this->checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); $type = trim($_POST['type'] ?? ''); $product_type = trim($_POST['product_type'] ?? ''); $capacity = trim($_POST['capacity'] ?? ''); $color = trim($_POST['color'] ?? ''); $model_code = trim($_POST['model_code'] ?? ''); $model_desc = trim($_POST['description'] ?? ''); // 从数据库获取类型配置,表不存在时回退到默认配置 $typeConfigModel = new ProductTypeConfig(); $typeConfig = $typeConfigModel->getByTypeName($type); if (!$typeConfig) { // 数据库配置不可用时,使用硬编码默认配置验证类型 $defaultConfigs = ProductTypeConfig::getDefaultConfigs(); $typeNames = []; foreach ($defaultConfigs as $dc) { $typeNames[] = $dc['type_name']; if ($dc['type_name'] === $type) { $typeConfig = $dc; } } if (!$typeConfig) { $this->assign('error', '类型无效,请选择:' . implode('、', $typeNames)); $this->assign('title', '添加产品型号'); $this->assign('user', $user); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } // 根据配置决定 product_type 和 capacity 值 $productTypeField = $typeConfig['product_type_field'] ?? 'product_type'; if ($productTypeField === 'capacity' && $capacity !== '') { // 电池类型:capacity 存入 capacity 字段,product_type 留空 $product_type = ''; } elseif ($productTypeField === 'color' && $color !== '') { $product_type = $color; } // 隐藏产品类型输入框的类型 → 设为空 if (($typeConfig['product_type_state'] ?? 'editable') === 'hidden') { $product_type = ''; } // model_code 输入框隐藏时,用 product_type 值自动填充(如"外壳颜色"类型) if (($typeConfig['model_code_state'] ?? 'editable') === 'hidden' && $model_code === '') { $model_code = $product_type ?: $color; } $productModel = new ProductModel(); try { // 检测同一产品类型+类型下型号代码是否已存在 $exist = $productModel->findByTypeAndCode($type, $product_type, $model_code); if ($exist) { $this->assign('error', '该类型+产品类型下型号「' . htmlspecialchars($model_code) . '」已存在,请勿重复录入!'); $this->assign('title', '添加产品型号'); $this->assign('user', $user); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } $data = array( 'type' => $type, 'product_type' => $product_type, 'model_code' => $model_code, 'model_desc' => $model_desc, 'capacity' => $capacity, 'color' => $color, ); $result = $productModel->addModel($data); if ($result === false) { $err = $productModel->getError(); $this->assign('error', '添加失败:' . ($err ?: '未知错误')); $this->assign('title', '添加产品型号'); $this->assign('user', $user); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } catch (\Throwable $e) { error_log('[productModelAdd] ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine()); $this->assign('error', '添加失败,数据库错误:' . $e->getMessage()); $this->assign('title', '添加产品型号'); $this->assign('user', $user); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } header('Location: ' . BASE_URL . '/Admin/productModel'); exit; } // 传入类型配置列表给视图,表不存在时回退到硬编码默认值 $typeConfigModel = new ProductTypeConfig(); $typeConfigs = $typeConfigModel->getEnabled(); if (empty($typeConfigs)) { $typeConfigs = ProductTypeConfig::getDefaultConfigs(); } $this->assign('title', '添加产品型号'); $this->assign('user', $user); $this->assign('typeConfigs', $typeConfigs); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function productModelEdit($id) { $this->checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } $productModel = new ProductModel(); $model = $productModel->where(['id = :id'], [':id' => $id])->fetch(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); $type = trim($_POST['type'] ?? ''); $product_type = trim($_POST['product_type'] ?? ''); $capacity = trim($_POST['capacity'] ?? ''); $color = trim($_POST['color'] ?? ''); $model_code = trim($_POST['model_code'] ?? ''); $model_desc = trim($_POST['description'] ?? ''); // 从数据库获取类型配置,表不存在时回退到默认配置 $typeConfigModel = new ProductTypeConfig(); $typeConfig = $typeConfigModel->getByTypeName($type); if (!$typeConfig) { // 数据库配置不可用时,使用硬编码默认配置验证类型 $defaultConfigs = ProductTypeConfig::getDefaultConfigs(); $typeNames = []; foreach ($defaultConfigs as $dc) { $typeNames[] = $dc['type_name']; if ($dc['type_name'] === $type) { $typeConfig = $dc; } } if (!$typeConfig) { $this->assign('error', '类型无效,请选择:' . implode('、', $typeNames)); $this->assign('title', '编辑产品型号'); $this->assign('user', $user); $this->assign('model', $model); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } // 根据配置决定 product_type 和 capacity 值 $productTypeField = $typeConfig['product_type_field'] ?? 'product_type'; if ($productTypeField === 'capacity' && $capacity !== '') { // 电池类型:capacity 存入 capacity 字段,product_type 留空 $product_type = ''; } elseif ($productTypeField === 'color' && $color !== '') { $product_type = $color; } // 隐藏产品类型输入框的类型 → 设为空 if (($typeConfig['product_type_state'] ?? 'editable') === 'hidden') { $product_type = ''; } try { // 检测同一类型+产品类型下型号代码是否已存在(排除自身) $exist = $productModel->findByTypeAndCode($type, $product_type, $model_code); if ($exist && $exist['id'] != $id) { $this->assign('error', '该类型+产品类型下型号「' . htmlspecialchars($model_code) . '」已存在,请勿重复录入!'); $this->assign('title', '编辑产品型号'); $this->assign('user', $user); $this->assign('model', $model); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } $data = array( 'type' => $type, 'product_type' => $product_type, 'model_code' => $model_code, 'model_desc' => $model_desc, 'capacity' => $capacity, 'color' => $color, ); $result = $productModel->updateModel($id, $data); if ($result === false) { $err = $productModel->getError(); $this->assign('error', '更新失败:' . ($err ?: '未知错误')); $this->assign('title', '编辑产品型号'); $this->assign('user', $user); $this->assign('model', $model); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } catch (\Throwable $e) { error_log('[productModelEdit] ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine()); $this->assign('error', '更新失败,数据库错误:' . $e->getMessage()); $this->assign('title', '编辑产品型号'); $this->assign('user', $user); $this->assign('model', $model); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } header('Location: ' . BASE_URL . '/Admin/productModel'); exit; } if (!$model) { exit('产品型号不存在或已被删除'); } // 传入类型配置列表给视图,表不存在时回退到硬编码默认值 $typeConfigModel = new ProductTypeConfig(); $typeConfigs = $typeConfigModel->getEnabled(); if (empty($typeConfigs)) { $typeConfigs = ProductTypeConfig::getDefaultConfigs(); } $this->assign('title', '编辑产品型号'); $this->assign('user', $user); $this->assign('model', $model); $this->assign('typeConfigs', $typeConfigs); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function productModelDelete($id) { $this->checkLogin(); $this->requireMethod('POST'); $this->csrfVerify(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } $productModel = new ProductModel(); $productModel->deleteModel($id); header('Location: ' . BASE_URL . '/Admin/productModel'); } /** * 产品类型配置管理页面 * 管理员可以在页面中增删改类型,定义每种类型的显示列、标签等 */ public function productTypeConfig() { $this->checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } $typeConfigModel = new ProductTypeConfig(); // AJAX: 保存(添加/更新) if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); $action = $_POST['action'] ?? 'save'; if ($action === 'delete') { $id = intval($_POST['id'] ?? 0); if ($id > 0) { $result = $typeConfigModel->deleteConfig($id); if ($result === false) { echo json_encode(['success' => false, 'error' => '删除失败:数据库错误,请检查 DGZXY_product_type_config 表是否存在']); exit; } } echo json_encode(['success' => true]); exit; } // save 操作 $id = intval($_POST['id'] ?? 0); $type_name = trim($_POST['type_name'] ?? ''); $sort_order = intval($_POST['sort_order'] ?? 0); $display_columns = trim($_POST['display_columns'] ?? ''); $product_type_state = trim($_POST['product_type_state'] ?? 'editable'); $product_type_label = trim($_POST['product_type_label'] ?? '产品类型'); $product_type_field = trim($_POST['product_type_field'] ?? 'product_type'); $product_type_placeholder = trim($_POST['product_type_placeholder'] ?? ''); $model_code_state = trim($_POST['model_code_state'] ?? 'editable'); $color_state = trim($_POST['color_state'] ?? 'editable'); $color = trim($_POST['color'] ?? 'default'); $enabled = intval($_POST['enabled'] ?? 1); $show_in_inbound = intval($_POST['show_in_inbound'] ?? 1); // 校验三态值合法性 $validStates = ['hidden', 'readonly', 'editable']; if (!in_array($product_type_state, $validStates)) $product_type_state = 'editable'; if (!in_array($model_code_state, $validStates)) $model_code_state = 'editable'; if (!in_array($color_state, $validStates)) $color_state = 'editable'; if ($type_name === '') { echo json_encode(['success' => false, 'error' => '类型名称不能为空']); exit; } $data = [ 'type_name' => $type_name, 'sort_order' => $sort_order, 'display_columns' => $display_columns, 'product_type_state' => $product_type_state, 'product_type_label' => $product_type_label, 'product_type_field' => $product_type_field, 'product_type_placeholder' => $product_type_placeholder, 'model_code_state' => $model_code_state, 'color_state' => $color_state, 'color' => $color, 'enabled' => $enabled, 'show_in_inbound' => $show_in_inbound, ]; if ($id > 0) { $result = $typeConfigModel->updateConfig($id, $data); if ($result === false) { echo json_encode(['success' => false, 'error' => '更新失败:数据库错误,请检查 DGZXY_product_type_config 表是否存在']); exit; } } else { // 检查重名 $exist = $typeConfigModel->getByTypeName($type_name); if ($exist) { echo json_encode(['success' => false, 'error' => '类型「' . $type_name . '」已存在']); exit; } $result = $typeConfigModel->addConfig($data); if ($result === false) { echo json_encode(['success' => false, 'error' => '添加失败:数据库错误,请检查 DGZXY_product_type_config 表是否存在']); exit; } } echo json_encode(['success' => true]); exit; } // GET: 渲染页面 $configs = $typeConfigModel->getAll(); $this->assign('title', '产品类型配置'); $this->assign('user', $user); $this->assign('configs', $configs); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } // 工位管理 — 仅超级管理员 public function workstation() { $this->checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } $workstation = new Workstation(); $stations = $workstation->getAll(); $this->assign('title', '工位管理'); $this->assign('user', $user); $this->assign('stations', $stations); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function workstationAdd() { $this->checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } // 准备模板字段配置数据(提前初始化,供错误分支和 GET 视图使用) require_once APP_PATH . 'app/helpers/field_helper.php'; $templateTypes = ['inbound', 'pcb_test', 'battery', 'assembly', 'finished_test', 'warehouse', 'delivery']; $templateFieldsMap = []; foreach ($templateTypes as $type) { $templateFieldsMap[$type] = getDefaultFieldsConfig($type); } if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); $stationType = trim($_POST['station_type']); // 校验工位类型格式:必须以字母开头,只能包含字母数字和下划线 if (!preg_match('/^[a-zA-Z][a-zA-Z0-9_]*$/', $stationType)) { $this->assign('error', '工位类型格式不正确,必须以字母开头,只能包含字母、数字和下划线'); $this->assign('title', '添加工位'); $this->assign('user', $user); $this->assign('templateFieldsMap', $templateFieldsMap); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } // 状态值验证 $status = intval($_POST['status'] ?? 1); if (!in_array($status, [0, 1], true)) { $status = 1; // 默认启用 } try { $data = array( 'station_name' => $_POST['station_name'], 'station_code' => $_POST['station_code'], 'station_type' => $stationType, 'sort_order' => intval($_POST['sort_order'] ?? 1), 'status' => $status, 'fields_config' => $_POST['fields_config'] ?? '' ); // 如果未传入配置,则尝试从模板复制或使用默认配置 if (empty($data['fields_config'])) { $templateType = $_POST['template_station'] ?? ''; if (!empty($templateType)) { $templateConfig = getDefaultFieldsConfig($templateType); $data['fields_config'] = json_encode($templateConfig, JSON_UNESCAPED_UNICODE); } else { $data['fields_config'] = json_encode([], JSON_UNESCAPED_UNICODE); } } $workstation = new Workstation(); $workstation->addStation($data); header('Location: ' . BASE_URL . '/Admin/workstation'); exit; } catch (\Throwable $e) { error_log('[workstationAdd] ' . $e->getMessage()); $this->assign('error', '添加失败:' . $e->getMessage()); $this->assign('title', '添加工位'); $this->assign('user', $user); $this->assign('templateFieldsMap', $templateFieldsMap); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } $this->assign('title', '添加工位'); $this->assign('user', $user); $this->assign('templateFieldsMap', $templateFieldsMap); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function workstationEdit($id) { $this->checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } require_once APP_PATH . 'app/helpers/field_helper.php'; $workstation = new Workstation(); $station = $workstation->where(['id = :id'], [':id' => $id])->fetch(); // 加载工位定义元数据 $stationDef = new StationDefinition(); $stationDefinition = $stationDef->getByType($station['station_type']); // 解析字段配置用于编辑器(提前初始化,供错误分支使用) $fieldEditor = renderFieldConfigEditor($station['station_type'], $station['fields_config'] ?? '', $stationDefinition); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); // 状态值验证:只允许 0(禁用)和 1(启用) $status = intval($_POST['status']); if (!in_array($status, [0, 1], true)) { $this->assign('error', '状态值无效,请选择启用或禁用'); $this->assign('title', '编辑工位'); $this->assign('user', $user); $this->assign('station', $station); $this->assign('fieldEditor', $fieldEditor); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } try { $data = array( 'station_name' => $_POST['station_name'], 'sort_order' => $_POST['sort_order'], 'status' => $status, 'fields_config' => $_POST['fields_config'] ?? '' ); $workstation->updateStation($id, $data); // 同步更新 station_definitions 表(管控产品类型行为) $sdData = [ 'has_product_type' => isset($_POST['sd_has_product_type']) ? 1 : 0, 'has_product_model' => isset($_POST['sd_has_product_model']) ? 1 : 0, 'filter_enabled' => isset($_POST['sd_filter_enabled']) ? 1 : 0, 'show_today_count' => isset($_POST['sd_show_today_count']) ? 1 : 0, 'show_total_count' => isset($_POST['sd_show_total_count']) ? 1 : 0, ]; if ($stationDefinition && !empty($stationDefinition['id'])) { // 更新已有定义 $sdFields = []; $sdParams = []; foreach ($sdData as $key => $val) { $sdFields[] = "`{$key}` = :{$key}"; $sdParams[":{$key}"] = $val; } $sdParams[':id'] = $stationDefinition['id']; $stationDef->execute( "UPDATE `" . StationDefinition::TABLE_PREFIX . "station_definitions` SET " . implode(', ', $sdFields) . " WHERE id = :id", $sdParams ); // 更新已有工位:同步字段变更到数据表 StationDefinition::clearCache(); $updatedDef = $stationDef->getByType($station['station_type']); $fieldConfigs = parseFieldsConfig($station['station_type'], $_POST['fields_config'] ?? ''); unset($fieldConfigs['_meta']); $stationDef->ensureColumns($station['station_type'], array_values($fieldConfigs)); } else { // 新建定义 $sdData['station_type'] = $station['station_type']; $sdData['db_table'] = 'station_' . $station['station_type']; $sdData['time_column'] = 'created_at'; $sdFields = implode('`, `', array_keys($sdData)); $sdPlaceholders = ':' . implode(', :', array_keys($sdData)); $stationDef->execute( "INSERT INTO `" . StationDefinition::TABLE_PREFIX . "station_definitions` (`{$sdFields}`) VALUES ({$sdPlaceholders})", $sdData ); // 新工位:自动创建数据表 StationDefinition::clearCache(); $newDef = $stationDef->getByType($station['station_type']); $fieldConfigs = parseFieldsConfig($station['station_type'], $_POST['fields_config'] ?? ''); unset($fieldConfigs['_meta']); $stationDef->ensureTable($station['station_type'], array_values($fieldConfigs)); } // 清除缓存,确保下次读取时获取最新数据 StationDefinition::clearCache(); header('Location: ' . BASE_URL . '/Admin/workstation'); exit; } catch (\Throwable $e) { error_log('[workstationEdit] ' . $e->getMessage()); $this->assign('error', '更新失败:' . $e->getMessage()); $this->assign('title', '编辑工位'); $this->assign('user', $user); $this->assign('station', $station); $this->assign('fieldEditor', $fieldEditor); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } $this->assign('title', '编辑工位'); $this->assign('user', $user); $this->assign('station', $station); $this->assign('fieldEditor', $fieldEditor); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } public function workstationDelete($id) { $this->checkLogin(); $this->requireMethod('POST'); $this->csrfVerify(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } $workstation = new Workstation(); $workstation->deleteStation($id); header('Location: ' . BASE_URL . '/Admin/workstation'); } // 系统配置 — 仅超级管理员可访问 public function config() { $this->checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } $config = new SysConfig(); $sysConfig = $config->getConfig(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->csrfVerify(); try { $data = array( 'company_name' => $_POST['company_name'], 'mes_name' => $_POST['mes_name'] ); $config->updateConfig($data); // 保存代码前缀规则 if (isset($_POST['prefix_types']) && isset($_POST['prefix_values'])) { $types = $_POST['prefix_types']; $values = $_POST['prefix_values']; $rules = []; $typeCount = count($types); for ($i = 0; $i < $typeCount; $i++) { $typeName = trim($types[$i]); $prefixVal = trim($values[$i]); if ($typeName !== '') { $rules[$typeName] = $prefixVal; } } $config->updateCodePrefixRules($rules); } header('Location: ' . BASE_URL . '/Admin/config'); exit; } catch (\Throwable $e) { error_log('[config] ' . $e->getMessage()); $this->assign('error', '保存失败:' . $e->getMessage()); $this->assign('title', '系统配置'); $this->assign('user', $user); $this->assign('sysConfig', $sysConfig); $this->assign('codePrefixRules', $config->getCodePrefixRules()); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } } $this->assign('title', '系统配置'); $this->assign('user', $user); $this->assign('sysConfig', $sysConfig); $this->assign('codePrefixRules', $config->getCodePrefixRules()); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } // 批量导入 — 仅超级管理员 public function import() { $this->checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN) { exit('无权限访问'); } $msg = ''; // 表类型 -> 数据库表名 映射 $tableMap = [ 'employee' => 'employee', 'customer' => 'customer', 'product_model' => 'product_model', 'inbound' => 'station_inbound', 'pcb_test' => 'pcb_test', 'battery' => 'battery_status', 'assembly' => 'product_assembly', 'finished_test' => 'finished_test', 'warehouse' => 'warehouse_in', 'delivery' => 'delivery', ]; // 需要排除的列(自动生成的字段) $excludeColumns = ['id', 'created_at', 'updated_at']; if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['import_file'])) { $this->csrfVerify(); $tableType = $_POST['table_type'] ?? ''; if (!isset($tableMap[$tableType])) { $msg = '无效的数据表类型'; } else { $tableName = $tableMap[$tableType]; $file = $_FILES['import_file']; if ($file['error'] !== UPLOAD_ERR_OK) { $msg = '文件上传失败,错误码:' . $file['error']; } else { // 验证文件类型:仅允许 CSV $tmpPath = $file['tmp_name']; $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); $mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $tmpPath); $allowedExt = ['csv']; $allowedMime = ['text/csv', 'text/plain', 'application/csv', 'application/vnd.ms-excel']; if (!in_array($ext, $allowedExt, true) || !in_array($mime, $allowedMime, true)) { $msg = '仅支持 CSV 格式文件,当前文件类型:' . htmlspecialchars($ext) . ' (' . htmlspecialchars($mime) . ')'; error_log('[import] Upload rejected: ext=' . $ext . ' mime=' . $mime); $this->assign('title', '批量导入'); $this->assign('user', $user); $this->assign('msg', $msg); $this->assign('csrfToken', $this->csrfToken()); $this->render(); return; } $handle = fopen($tmpPath, 'r'); if (!$handle) { $msg = '无法打开文件'; } else { // 读取 CSV 头部 $headers = fgetcsv($handle); if (!$headers) { $msg = 'CSV 文件为空'; } else { // BOM 处理 $headers[0] = trim($headers[0], "\xEF\xBB\xBF"); $headers = array_map('trim', $headers); // 过滤掉排除列 $validHeaders = []; foreach ($headers as $h) { if (!in_array($h, $excludeColumns)) { $validHeaders[] = $h; } } // 解析每行数据 $success = 0; $fail = 0; $model = new \core\base\Model(); $model->table($tableName); while (($row = fgetcsv($handle)) !== false) { $data = []; foreach ($headers as $i => $col) { if (!in_array($col, $excludeColumns) && isset($row[$i])) { $val = $row[$i] !== '' ? $row[$i] : null; // 员工密码自动 bcrypt 加密 if ($tableName === 'employee' && $col === 'password' && $val !== null) { $empModel = new \app\models\Employee(); $val = $empModel->hashPassword($val); } $data[$col] = $val; } } if (!empty($data)) { try { $model->add($data); $success++; } catch (\Exception $e) { $fail++; } } } $msg = "导入完成:成功 {$success} 条,失败 {$fail} 条"; } fclose($handle); } } } } $this->assign('title', '批量导入'); $this->assign('user', $user); $this->assign('msg', $msg); $this->assign('csrfToken', $this->csrfToken()); $this->render(); } // 下载导入模板 — 仅超级管理员 public function importTemplate($type) { $this->checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN) { exit('无权限访问'); } $tableMap = [ 'employee' => 'employee', 'customer' => 'customer', 'product_model' => 'product_model', 'inbound' => 'station_inbound', 'pcb_test' => 'pcb_test', 'battery' => 'battery_status', 'assembly' => 'product_assembly', 'finished_test' => 'finished_test', 'warehouse' => 'warehouse_in', 'delivery' => 'delivery', ]; if (!isset($tableMap[$type])) { exit('无效的模板类型'); } $tableName = $tableMap[$type]; $excludeCols = ['id', 'created_at', 'updated_at']; $model = new \core\base\Model(); $model->table($tableName); $fullTableName = $model->getTable(); $columns = $model->query("SHOW COLUMNS FROM `{$fullTableName}`"); $headers = []; foreach ($columns as $col) { if (!in_array($col['Field'], $excludeCols)) { $headers[] = $col['Field']; } } header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="' . $type . '_template.csv"'); header('Pragma: no-cache'); // BOM for Excel UTF-8 compatibility echo "\xEF\xBB\xBF"; $output = fopen('php://output', 'w'); fputcsv($output, $headers); fclose($output); exit; } // 产品追溯 - 跨表查询产品完整信息 — 仅超级管理员 public function productSearch() { $this->checkLogin(); $user = $this->getCurrentUser(); if ($user['role'] !== self::ROLE_SUPER_ADMIN && $user['role'] !== self::ROLE_ADMIN) { exit('无权限访问'); } $keyword = trim($_GET['keyword'] ?? ''); $results = []; $searched = false; if (!empty($keyword)) { $searched = true; $model = new \core\base\Model(); // 用 LIKE 跨 product_assembly 表搜索 $like = '%' . $keyword . '%'; $results = $model->query( "SELECT pa.*, COALESCE(bs.voltage_platform, '') AS voltage_platform, COALESCE(bs.no_load_voltage, '') AS no_load_voltage, COALESCE(bs.internal_resistance, '') AS internal_resistance, COALESCE(pt.test_status, '') AS pcb_test_status, COALESCE(ft.test_result, '') AS finished_test_result, COALESCE(wi.in_time, '') AS warehouse_time, COALESCE(d.customer_name, '') AS delivery_customer, COALESCE(d.delivery_time, '') AS delivery_time FROM DGZXY_product_assembly pa LEFT JOIN DGZXY_battery_status bs ON pa.battery_serial = bs.serial_no LEFT JOIN DGZXY_pcb_test pt ON pa.pcb_serial = pt.serial_no LEFT JOIN DGZXY_finished_test ft ON pa.finished_serial = ft.finished_serial LEFT JOIN DGZXY_warehouse_in wi ON pa.finished_serial = wi.finished_serial LEFT JOIN DGZXY_delivery d ON pa.finished_serial = d.finished_serial WHERE pa.finished_serial LIKE :kw1 OR pa.battery_serial LIKE :kw2 OR pa.pcb_serial LIKE :kw3 OR pa.product_model LIKE :kw4 ORDER BY pa.id DESC LIMIT 200", [':kw1' => $like, ':kw2' => $like, ':kw3' => $like, ':kw4' => $like] ); } $this->assign('title', '产品追溯'); $this->assign('user', $user); $this->assign('keyword', $keyword); $this->assign('results', $results); $this->assign('searched', $searched); $this->assign('activeMenu', 'productSearch'); $this->render(); } // ========== 数据库敏感字段脱敏(P0:防止密码哈希等敏感信息泄露) ========== // 命中以下字段名(含子串匹配)的值在数据库管理页面一律显示为 ******,且禁止通过编辑页修改 protected static $SENSITIVE_FIELDS = [ 'password', 'passwd', 'pwd', 'token', 'api_token', 'access_token', 'refresh_token', 'secret', 'secret_key', 'api_key', 'private_key', 'salt', 'auth_key', 'mini_token', 'session_token', 'reset_token', 'verify_token', 'enc_key', 'cert', ]; protected function isSensitiveField($field) { $f = strtolower(trim((string)$field)); foreach (self::$SENSITIVE_FIELDS as $s) { if ($f === $s || strpos($f, $s) !== false) { return true; } } return false; } // P2-10:敏感系统表(含 token/密钥/凭证语义)不在数据库管理界面暴露 protected static $SENSITIVE_TABLE_PATTERNS = [ 'token', 'api_token', 'secret', 'password', 'passwd', 'pwd', 'session', 'miniapp', 'asd_api', 'oauth', 'credential', 'auth', 'salt', 'key', 'encrypt', 'private', 'permission', // 权限映射表(员工权限/员工工位权限)不暴露,防权限结构泄露 ]; protected function isSensitiveTable($table) { $t = strtolower(trim((string)$table)); foreach (self::$SENSITIVE_TABLE_PATTERNS as $p) { if (strpos($t, $p) !== false) { return true; } } return false; } // v3-P2:系统/框架级表(迁移、系统配置等)不在数据库管理界面暴露, // 进一步缩小可见表范围,降低结构信息泄露面。 protected static $SYSTEM_TABLE_PATTERNS = [ 'migration', 'phinxlog', 'sys_config', 'sysconfig', 'system_config', 'schema_version', 'seeds', 'cache', 'sessions', 'jobs', 'failed_jobs', 'logs', 'sys_log', 'audit', ]; protected function isSystemTable($table) { $t = strtolower(trim((string)$table)); foreach (self::$SYSTEM_TABLE_PATTERNS as $p) { if (strpos($t, $p) !== false) { return true; } } return false; } // v5-P2:数据库表业务名称映射(用于管理界面以中文业务名展示,降低原始表名结构泄露) // key 为去掉前缀后的展示名,value 为中文业务名;未命中时回退为原始展示名。 protected static $TABLE_BUSINESS_NAMES = [ 'asd_device' => '昂盛达设备', 'asd_test_data' => '昂盛达测试数据', 'asd_test_detail' => '昂盛达测试明细', 'asd_test_record' => '昂盛达测试记录', 'asset' => '资产', 'battery_status' => '电池状态', 'customer' => '客户', 'delivery' => '出货单', 'document' => '文档', 'employee' => '员工', 'finance_record' => '财务记录', 'finished_test' => '成品测试', 'hr_employee' => '人事员工', 'import_template' => '导入模板', 'inventory' => '库存', 'pcb_test' => 'PCB 测试', 'product_assembly' => '产品装配', 'product_model' => '产品型号', 'product_type_config' => '产品类型配置', 'purchase_item' => '采购明细', 'purchase_order' => '采购订单', 'receivable_payable' => '应收应付', 'sales_item' => '销售明细', 'sales_order' => '销售订单', 'station_aging' => '工位老化', 'station_definitions' => '工位定义', 'station_inbound' => '工位入库', 'station_records' => '工位记录', 'station_semi_finished' => '工位半成品', 'supplier' => '供应商', 'warehouse_in' => '入库', 'workstation' => '工位', ]; protected function tableBusinessName($displayName) { return self::$TABLE_BUSINESS_NAMES[$displayName] ?? $displayName; } // v3-P2:数据库管理操作审计日志(轻量文件日志,便于事后追溯谁访问/改动了哪张表) // 日志写入 runtime/db_audit.log,.htaccess 已禁止 .log 通过 Web 访问。 protected function dbAuditLog($action, $table = '', $extra = '') { try { $user = $this->getCurrentUser(); $dir = APP_PATH . 'runtime'; if (!is_dir($dir)) { @mkdir($dir, 0750, true); } $ip = $_SERVER['REMOTE_ADDR'] ?? '-'; $line = sprintf( "[%s] user=%s(%s) role=%s ip=%s action=%s table=%s %s\n", date('Y-m-d H:i:s'), $user['emp_no'] ?? '-', $user['emp_name'] ?? '-', $user['role'] ?? '-', $ip, $action, $table !== '' ? $table : '-', $extra ); @file_put_contents($dir . '/db_audit.log', $line, FILE_APPEND | LOCK_EX); } catch (\Throwable $e) { // 审计失败不得影响主流程 } } // 对行集合中的敏感字段值脱敏(原地修改) protected function maskSensitiveRows(array &$rows) { foreach ($rows as &$row) { if (!is_array($row)) continue; foreach ($row as $k => $v) { if ($this->isSensitiveField($k)) { $row[$k] = '******'; } } } unset($row); } // 数据库管理 - 表列表 — 仅超级管理员可访问 public function database() { $this->checkLogin(); $user = $this->getCurrentUser(); // v3-P2 安全加固:原始数据库管理(可查看/编辑/删除任意表记录)属高危操作, // 仅超级管理员可访问,对普通管理员完全隐藏表名与记录数。 if ($user['role'] !== self::ROLE_SUPER_ADMIN) { exit('无权限访问'); } $this->dbAuditLog('list_tables'); $tableList = $this->getVisibleTables(); $this->assign('title', '数据库管理'); $this->assign('user', $user); $this->assign('tables', $tableList); $this->assign('activeMenu', 'database'); $this->render(); } // v7-P3:可见表列表(过滤敏感/系统表、去前缀、按展示名稳定排序) // 同时供列表页与详情页按索引定位,避免 URL 中暴露原始表名 protected function getVisibleTables() { $model = new \core\base\Model(); $tables = $model->query("SHOW TABLES"); $prefix = \core\base\Model::TABLE_PREFIX; $list = []; foreach ($tables as $row) { $tbl = reset($row); // 获取第一个值(表名) // 跳过 jp_ 前缀的表(不显示) if (strpos($tbl, 'jp_') === 0) { continue; } // P2-10 安全加固:敏感系统表不在管理界面暴露(限制显示范围,防结构泄露) if ($this->isSensitiveTable($tbl)) { continue; } // v3-P2:框架/系统级表(迁移、系统配置、日志等)同样不暴露 if ($this->isSystemTable($tbl)) { continue; } // 使用完整表名查询行数 $count = $model->query("SELECT COUNT(*) AS cnt FROM `{$tbl}`"); // 展示给用户的名称:去掉 DGZXY_ 前缀 $displayName = $tbl; if (strpos($tbl, $prefix) === 0) { $displayName = substr($tbl, strlen($prefix)); } $list[] = [ 'name' => $displayName, 'business_name' => $this->tableBusinessName($displayName), 'full_name' => $tbl, 'count' => $count[0]['cnt'] ?? 0, ]; } // 按展示名排序,保证索引在各请求间一致(修复 v7-P3:URL 不暴露原始表名) usort($list, function ($a, $b) { return strcmp($a['name'], $b['name']); }); return $list; } // 数据库管理 - 查看表数据 — 仅超级管理员可访问 // v7-P3:$idx 为 getVisibleTables() 的稳定索引,URL 不再含原始表名 public function databaseView($idx = 0) { $this->checkLogin(); $user = $this->getCurrentUser(); // v3-P2:原始数据库查看仅超级管理员可访问 if ($user['role'] !== self::ROLE_SUPER_ADMIN) { exit('无权限访问'); } // v7-P3:通过稳定索引解析表名,不在 URL 暴露原始表名 $tables = $this->getVisibleTables(); $idx = intval($idx); if (!isset($tables[$idx])) { echo '