CodeIgniter 實現 GET 分頁完整實作
CodeIgniter 內建分頁類庫預設使用 URI 片段(/page/2),但實際專案常需搭配 GET 參數(?page=2&keyword=xxx)以保持搜尋條件。本文提供完整的 GET 分頁實作方案,涵蓋搜尋條件合併、分頁配置、資料庫條件動態構建、關聯查詢與角色權限控制。
控制器完整實作
<?php
class Project extends CI_Controller {
public function project_search() {
// 1. 載入類庫與模型
$this->load->library('pagination');
$this->load->model('depart_mdl');
$this->load->model('user_mdl');
$this->load->model('manage_info_mdl');
// 2. 獲取所有 GET 參數(搜尋條件 + 分頁)
$get_data = $this->input->get(); // 自動過濾 XSS
// 3. 提取搜尋條件變數(便於構建後綴 URL)
$name = $get_data['name'] ?? '';
$username = $get_data['username'] ?? '';
$budget = $get_data['budget'] ?? '';
$type = $get_data['type'] ?? '';
$posttime_start = $get_data['posttime_start'] ?? '';
$posttime_end = $get_data['posttime_end'] ?? '';
$purchase_type = $get_data['purchase_type'] ?? '';
$depart_code = $get_data['depart_code'] ?? '';
$project_status = $get_data['project_status'] ?? '';
$bidder_way = $get_data['bidder_way'] ?? '';
// 4. 構建分頁 base_url 後綴(保持所有搜尋條件)
$suffix = http_build_query([
'name' => $name,
'username' => $username,
'budget' => $budget,
'type' => $type,
'posttime_start' => $posttime_start,
'posttime_end' => $posttime_end,
'purchase_type' => $purchase_type,
'depart_code' => $depart_code,
'project_status' => $project_status,
'bidder_way' => $bidder_way,
]);
$config['base_url'] = site_url('project/project_search') . '?' . $suffix;
// 5. 分頁核心配置
$config['total_rows'] = $this->db->count_all($this->db->dbprefix('project')); // 初始總數,後續會被 where 覆蓋
$config['per_page'] = 10;
$config['page_query_string'] = TRUE; // ✅ 關鍵:啟用 GET 模式
$config['query_string_segment'] = 'page'; // 參數名:?page=2
$config['uri_segment'] = 3; // 相容性保留
// 樣式配置(Bootstrap 4/5)
$config['full_tag_open'] = '<nav><ul class="pagination">';
$config['full_tag_close'] = '</ul></nav>';
$config['num_tag_open'] = '<li class="page-item">';
$config['num_tag_close'] = '</li>';
$config['cur_tag_open'] = '<li class="page-item active"><span class="page-link">';
$config['cur_tag_close'] = '</span></li>';
$config['next_tag_open'] = '<li class="page-item">';
$config['next_tag_close'] = '</li>';
$config['prev_tag_open'] = '<li class="page-item">';
$config['prev_tag_close'] = '</li>';
$config['first_tag_open'] = '<li class="page-item">';
$config['first_tag_close'] = '</li>';
$config['last_tag_open'] = '<li class="page-item">';
$config['last_tag_close'] = '</li>';
$config['attributes'] = ['class' => 'page-link'];
$this->pagination->initialize($config);
// 6. 獲取當前用戶角色(權限控制)
$salary_no = $this->session->userdata('salary_no');
$user = $this->user_mdl->get_user_by_salary_no($salary_no);
// 7. 構建查詢(動態 WHERE)
$this->db->from('ustc_project');
$this->db->join('ustc_admins', 'ustc_admins.salary_no = ustc_project.salary_no');
// 權限過濾:非超管只能看自己部門或自己的專案
if ($user->role != 1) {
$depart_codes = explode(',', $user->grant_depart_code);
$this->db->where_in('grant_depart_code', $depart_codes);
$this->db->or_where('ustc_project.salary_no', $salary_no);
}
// 搜尋條件動態加入
if ($name) $this->db->like('name', $name);
if ($username) $this->db->like('username', $username);
if ($budget) $this->db->like('budget', $budget);
if ($type) $this->db->where('type', $type);
if ($depart_code) $this->db->where('depart_code', $depart_code);
if ($purchase_type) $this->db->where('purchase_type', $purchase_type);
if ($project_status) $this->db->where('project_status', $project_status);
if ($bidder_way) $this->db->where('bidder_way', $bidder_way);
// 時間範圍
if ($posttime_start) $this->db->where('posttime >=', strtotime($posttime_start));
if ($posttime_end) $this->db->where('posttime <=', strtotime($posttime_end) + 86399); // 當天結束
// 8. 計算總數(必須在 limit 之前,且條件相同)
// 為避免重複構建,可 clone 查詢或重新構建
// 這裡簡化:重新執行相同 where 獲取總數
$total_rows = $this->db->count_all_results('ustc_project', false); // false 不重置查詢
$config['total_rows'] = $total_rows;
$this->pagination->initialize($config); // 重新初始化更新總數
// 9. 分頁偏移量
$page = $get_data['page'] ?? 0;
$offset = (int)$page;
// 10. 執行查詢
$this->db->order_by('posttime', 'DESC');
$projects = $this->db->limit($config['per_page'], $offset)->get()->result_array();
// 11. 資料後處理(關聯查詢轉換)
foreach ($projects as &$p) {
$p['type'] = $this->manage_info_mdl->get_value_by_id($p['type'])->value;
$p['purchase_type'] = $this->manage_info_mdl->get_value_by_id($p['purchase_type'])->value;
$p['depart'] = $this->depart_mdl->get_depart_by_code($p['depart_code'])->name;
}
// 12. 視圖資料
$data['projects'] = $projects;
$data['user_role'] = $user->role;
$data['pagination'] = $this->pagination->create_links(); // 生成分頁 HTML
$this->load->view('project_search', $data);
}
}
?>
關鍵配置詳解
| 配置項 | 值 | 說明 |
| page_query_string | TRUE | 啟用 GET 參數模式(預設 FALSE 為 URI 片段) |
| query_string_segment | 'page' | 頁碼參數名,URL 為 ?page=2 |
| base_url | 含查詢字串 | 必須包含所有搜尋條件,否則翻頁會丟失篩選 |
| total_rows | 動態計算 | 需在所有 WHERE 條件加入後計算,否則分頁數錯誤 |
| per_page | 10 | 每頁筆數 |
視圖檔範例
<!-- project_search.php -->
<div class="card">
<div class="card-header">專案列表</div>
<div class="card-body">
<table class="table table-striped">
<thead>
<tr>
<th>名稱</th><th>預算</th><th>類型</th><th>狀態</th><th>發布時間</th><th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($projects as $p): ?>
<tr>
<td><?= htmlspecialchars($p['name']) ?></td>
<td><?= $p['budget'] ?></td>
<td><?= $p['type'] ?></td>
<td><?= $p['project_status'] ?></td>
<td><?= date('Y-m-d', $p['posttime']) ?></td>
<td><a href="<?= site_url('project/detail/'.$p['id']) ?>" class="btn btn-sm btn-primary">詳情</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- 分頁導航 -->
<div class="d-flex justify-content-center mt-3">
<?= $pagination ?>
</div>
</div>
</div>
常見坑點與解決
| 問題 | 原因 | 解決 |
| 翻頁後搜尋條件消失 | base_url 未包含查詢字串 | 用 http_build_query 合併所有 GET 參數到 base_url |
| 總筆數不準/分頁數錯誤 | count_all_results 在 limit 之後調用 | 在 limit 前計算總數,或用 clone 查詢對象 |
| page=0 顯示第一頁 | offset 計算邏輯 | CI GET 模式 page 參數直接作為 offset(0-based) |
| 分頁連結指向錯誤 URL | site_url() 未包含 index.php | 確認 config.php $config['index_page'] 設定正確 |
| 中文搜尋條件 URL 亂碼 | 瀏覽器編碼不一致 | 確保專案 charset=UTF-8,http_build_query 自動 urlencode |
進階優化建議
- 查詢複用: 將 WHERE 條件構建封裝為私有方法
_build_search_query(),同時供總數查詢與列表查詢調用,避免重複代碼。
- 索引優化: 常用搜尋欄位(name、username、posttime、depart_code、project_status)建立複合索引。
- 快取總數: 搜尋條件不變時,總數可快取 1-5 分鐘(Redis Key: search_count:{md5(serialize($conditions))})。
- 大資料集游標分頁: 超過 10 萬筆建議改用 ID 游標分頁(
WHERE id > $last_id LIMIT 10),避免 OFFSET 深分頁效能問題。